diff options
-rw-r--r-- | etc/os-net-config/samples/ib_interface.json | 2 | ||||
-rw-r--r-- | etc/os-net-config/samples/ib_interface.yaml | 2 | ||||
-rw-r--r-- | etc/os-net-config/samples/interface.json | 8 | ||||
-rw-r--r-- | etc/os-net-config/samples/interface.yaml | 7 | ||||
-rw-r--r-- | etc/os-net-config/samples/linux_bond_networkmanager.yaml | 18 | ||||
-rw-r--r-- | etc/os-net-config/samples/ovs_patch_port.yaml | 2 | ||||
-rw-r--r-- | os_net_config/cli.py | 2 | ||||
-rw-r--r-- | os_net_config/impl_eni.py | 9 | ||||
-rw-r--r-- | os_net_config/impl_ifcfg.py | 70 | ||||
-rw-r--r-- | os_net_config/objects.py | 165 | ||||
-rw-r--r-- | os_net_config/tests/test_impl_eni.py | 17 | ||||
-rw-r--r-- | os_net_config/tests/test_impl_ifcfg.py | 87 | ||||
-rw-r--r-- | os_net_config/tests/test_objects.py | 53 | ||||
-rw-r--r-- | os_net_config/tests/test_utils.py | 24 | ||||
-rw-r--r-- | os_net_config/utils.py | 11 | ||||
-rw-r--r-- | tox.ini | 2 |
16 files changed, 386 insertions, 93 deletions
diff --git a/etc/os-net-config/samples/ib_interface.json b/etc/os-net-config/samples/ib_interface.json index 4e42867..f552af3 100644 --- a/etc/os-net-config/samples/ib_interface.json +++ b/etc/os-net-config/samples/ib_interface.json @@ -20,7 +20,7 @@ "type": "ib_interface", "name": "ib1", "use_dhcp": true, - "defroute": no + "defroute": false } ] } diff --git a/etc/os-net-config/samples/ib_interface.yaml b/etc/os-net-config/samples/ib_interface.yaml index f930471..6210f0a 100644 --- a/etc/os-net-config/samples/ib_interface.yaml +++ b/etc/os-net-config/samples/ib_interface.yaml @@ -15,4 +15,4 @@ network_config: type: interface name: ib1 use_dhcp: true - defroute: no
\ No newline at end of file + defroute: false diff --git a/etc/os-net-config/samples/interface.json b/etc/os-net-config/samples/interface.json index 6eb8f62..7b70e05 100644 --- a/etc/os-net-config/samples/interface.json +++ b/etc/os-net-config/samples/interface.json @@ -25,8 +25,14 @@ "type": "interface", "name": "em2", "use_dhcp": true, - "defroute": no, + "defroute": false, "ethtool_opts": "speed 1000 duplex full" + }, + { + "type": "interface", + "name": "em3", + "use_dhcp": true, + "hotplug": true } ] } diff --git a/etc/os-net-config/samples/interface.yaml b/etc/os-net-config/samples/interface.yaml index 725091b..4c4269e 100644 --- a/etc/os-net-config/samples/interface.yaml +++ b/etc/os-net-config/samples/interface.yaml @@ -19,5 +19,10 @@ network_config: type: interface name: em2 use_dhcp: true - defroute: no + defroute: false ethtool_opts: "speed 1000 duplex full" + - + type: interface + name: em3 + use_dhcp: true + hotplug: true diff --git a/etc/os-net-config/samples/linux_bond_networkmanager.yaml b/etc/os-net-config/samples/linux_bond_networkmanager.yaml new file mode 100644 index 0000000..c7036c0 --- /dev/null +++ b/etc/os-net-config/samples/linux_bond_networkmanager.yaml @@ -0,0 +1,18 @@ +# To use NetworkManager to manage an interface, set nm_controlled=true. +# Note that some interface types may not be supported by NetworkManager. +network_config: + - + type: linux_bond + name: bond1 + nm_controlled: true + use_dhcp: true + bonding_options: "mode=active-backup" + members: + - + type: interface + name: em1 + nm_controlled: true + - + type: interface + name: em2 + nm_controlled: true diff --git a/etc/os-net-config/samples/ovs_patch_port.yaml b/etc/os-net-config/samples/ovs_patch_port.yaml index 91858be..bae8880 100644 --- a/etc/os-net-config/samples/ovs_patch_port.yaml +++ b/etc/os-net-config/samples/ovs_patch_port.yaml @@ -10,7 +10,7 @@ network_config: # force the MAC address of the bridge to this interface primary: true mtu: 1500 - ovs_extra: "br-set-external-id br-ctlplane bridge-id br-ctlplane" + ovs_extra: ["br-set-external-id br-ctlplane bridge-id br-ctlplane"] - type: ovs_patch_port name: br_pub-patch diff --git a/os_net_config/cli.py b/os_net_config/cli.py index c0ac5c4..479b3a3 100644 --- a/os_net_config/cli.py +++ b/os_net_config/cli.py @@ -186,7 +186,7 @@ def main(argv=sys.argv): files_changed = provider.apply(cleanup=opts.cleanup, activate=not opts.no_activate) if opts.noop: - for location, data in files_changed.iteritems(): + for location, data in files_changed.items(): print("File: %s\n" % location) print(data) print("----") diff --git a/os_net_config/impl_eni.py b/os_net_config/impl_eni.py index 5b91270..360d8c8 100644 --- a/os_net_config/impl_eni.py +++ b/os_net_config/impl_eni.py @@ -127,7 +127,10 @@ class ENINetConfig(os_net_config.NetConfig): data += address_data data += " vlan-raw-device %s\n" % interface.device else: - data += "auto %s\n" % interface.name + if isinstance(interface, objects.Interface) and interface.hotplug: + data += "allow-hotplug %s\n" % interface.name + else: + data += "auto %s\n" % interface.name data += _iface data += address_data if interface.mtu: @@ -212,12 +215,12 @@ class ENINetConfig(os_net_config.NetConfig): # write out bridges first. This ensures that an ifup -a # on reboot brings them up first - for bridge_name, bridge_data in self.bridges.iteritems(): + for bridge_name, bridge_data in self.bridges.items(): route_data = self.routes.get(bridge_name) bridge_data += (route_data or '') new_config += bridge_data - for interface_name, iface_data in self.interfaces.iteritems(): + for interface_name, iface_data in self.interfaces.items(): route_data = self.routes.get(interface_name) iface_data += (route_data or '') new_config += iface_data diff --git a/os_net_config/impl_ifcfg.py b/os_net_config/impl_ifcfg.py index 41d5f8c..cf3b257 100644 --- a/os_net_config/impl_ifcfg.py +++ b/os_net_config/impl_ifcfg.py @@ -34,6 +34,13 @@ def ifcfg_config_path(name): return "/etc/sysconfig/network-scripts/ifcfg-%s" % name +def remove_ifcfg_config(ifname): + if re.match('[\w-]+$', ifname): + ifcfg_file = ifcfg_config_path(ifname) + if os.path.exists(ifcfg_file): + os.remove(ifcfg_file) + + # NOTE(dprince): added here for testability def bridge_config_path(name): return ifcfg_config_path(name) @@ -131,8 +138,14 @@ class IfcfgNetConfig(os_net_config.NetConfig): data = "# This file is autogenerated by os-net-config\n" data += "DEVICE=%s\n" % base_opt.name data += "ONBOOT=yes\n" - data += "HOTPLUG=no\n" - data += "NM_CONTROLLED=no\n" + if isinstance(base_opt, objects.Interface) and base_opt.hotplug: + data += "HOTPLUG=yes\n" + else: + data += "HOTPLUG=no\n" + if base_opt.nm_controlled: + data += "NM_CONTROLLED=yes\n" + else: + data += "NM_CONTROLLED=no\n" if not base_opt.dns_servers and not base_opt.use_dhcp: data += "PEERDNS=no\n" if isinstance(base_opt, objects.Vlan): @@ -283,15 +296,18 @@ class IfcfgNetConfig(os_net_config.NetConfig): data += "OVS_BRIDGE=%s\n" % base_opt.bridge_name elif isinstance(base_opt, objects.OvsDpdkBond): ovs_extra.extend(base_opt.ovs_extra) - if base_opt.primary_interface_name: - primary_name = base_opt.primary_interface_name - self.bond_primary_ifaces[base_opt.name] = primary_name + # Referring to bug:1643026, the below commenting of the interfaces, + # is to workaround the error, but is not the long term solution. + # The long term solution is to run DPDK options before + # os-net-config, which is being tracked at BUG:1654975 + # if base_opt.primary_interface_name: + # primary_name = base_opt.primary_interface_name + # self.bond_primary_ifaces[base_opt.name] = primary_name data += "DEVICETYPE=ovs\n" data += "TYPE=OVSDPDKBond\n" data += "OVS_BRIDGE=%s\n" % base_opt.bridge_name if base_opt.members: members = [member.name for member in base_opt.members] - self.member_names[base_opt.name] = members data += ("BOND_IFACES=\"%s\"\n" % " ".join(members)) if base_opt.ovs_options: data += "OVS_OPTIONS=\"%s\"\n" % base_opt.ovs_options @@ -581,6 +597,8 @@ class IfcfgNetConfig(os_net_config.NetConfig): # Bind the dpdk interface utils.bind_dpdk_interfaces(ifname, ovs_dpdk_port.driver, self.noop) + if not self.noop: + remove_ifcfg_config(ifname) data = self._add_common(ovs_dpdk_port) logger.debug('ovs dpdk port data: %s' % data) @@ -599,6 +617,8 @@ class IfcfgNetConfig(os_net_config.NetConfig): # checks are added at the object creation stage. ifname = dpdk_port.members[0].name utils.bind_dpdk_interfaces(ifname, dpdk_port.driver, self.noop) + if not self.noop: + remove_ifcfg_config(ifname) data = self._add_common(ovs_dpdk_bond) logger.debug('ovs dpdk bond data: %s' % data) @@ -677,8 +697,9 @@ class IfcfgNetConfig(os_net_config.NetConfig): nfvswitch_interfaces = [] # nfvswitch physical interfaces nfvswitch_internal_ifaces = [] # nfvswitch internal/management ports stop_dhclient_interfaces = [] + ovs_needs_restart = False - for interface_name, iface_data in self.interface_data.iteritems(): + for interface_name, iface_data in self.interface_data.items(): route_data = self.route_data.get(interface_name, '') route6_data = self.route6_data.get(interface_name, '') interface_path = self.root_dir + ifcfg_config_path(interface_name) @@ -702,11 +723,16 @@ class IfcfgNetConfig(os_net_config.NetConfig): update_files[route6_path] = route6_data if "BOOTPROTO=dhcp" not in iface_data: stop_dhclient_interfaces.append(interface_name) + # Openvswitch needs to be restarted when OVSDPDKPort or + # OVSDPDKBond is added + if "OVSDPDK" in iface_data: + ovs_needs_restart = True + else: logger.info('No changes required for interface: %s' % interface_name) - for interface_name, iface_data in self.ivsinterface_data.iteritems(): + for interface_name, iface_data in self.ivsinterface_data.items(): route_data = self.route_data.get(interface_name, '') route6_data = self.route6_data.get(interface_name, '') interface_path = self.root_dir + ifcfg_config_path(interface_name) @@ -727,7 +753,7 @@ class IfcfgNetConfig(os_net_config.NetConfig): logger.info('No changes required for ivs interface: %s' % interface_name) - for iface_name, iface_data in self.nfvswitch_intiface_data.iteritems(): + for iface_name, iface_data in self.nfvswitch_intiface_data.items(): route_data = self.route_data.get(iface_name, '') route6_data = self.route6_data.get(iface_name, '') iface_path = self.root_dir + ifcfg_config_path(iface_name) @@ -748,7 +774,7 @@ class IfcfgNetConfig(os_net_config.NetConfig): logger.info('No changes required for nfvswitch interface: %s' % iface_name) - for vlan_name, vlan_data in self.vlan_data.iteritems(): + for vlan_name, vlan_data in self.vlan_data.items(): route_data = self.route_data.get(vlan_name, '') route6_data = self.route6_data.get(vlan_name, '') vlan_path = self.root_dir + ifcfg_config_path(vlan_name) @@ -768,7 +794,7 @@ class IfcfgNetConfig(os_net_config.NetConfig): logger.info('No changes required for vlan interface: %s' % vlan_name) - for bridge_name, bridge_data in self.bridge_data.iteritems(): + for bridge_name, bridge_data in self.bridge_data.items(): route_data = self.route_data.get(bridge_name, '') route6_data = self.route6_data.get(bridge_name, '') bridge_path = self.root_dir + bridge_config_path(bridge_name) @@ -792,7 +818,7 @@ class IfcfgNetConfig(os_net_config.NetConfig): else: logger.info('No changes required for bridge: %s' % bridge_name) - for bridge_name, bridge_data in self.linuxbridge_data.iteritems(): + for bridge_name, bridge_data in self.linuxbridge_data.items(): route_data = self.route_data.get(bridge_name, '') route6_data = self.route6_data.get(bridge_name, '') bridge_path = self.root_dir + bridge_config_path(bridge_name) @@ -812,7 +838,7 @@ class IfcfgNetConfig(os_net_config.NetConfig): else: logger.info('No changes required for bridge: %s' % bridge_name) - for team_name, team_data in self.linuxteam_data.iteritems(): + for team_name, team_data in self.linuxteam_data.items(): route_data = self.route_data.get(team_name, '') route6_data = self.route6_data.get(team_name, '') team_path = self.root_dir + bridge_config_path(team_name) @@ -833,7 +859,7 @@ class IfcfgNetConfig(os_net_config.NetConfig): logger.info('No changes required for linux team: %s' % team_name) - for bond_name, bond_data in self.linuxbond_data.iteritems(): + for bond_name, bond_data in self.linuxbond_data.items(): route_data = self.route_data.get(bond_name, '') route6_data = self.route6_data.get(bond_name, '') bond_path = self.root_dir + bridge_config_path(bond_name) @@ -855,7 +881,7 @@ class IfcfgNetConfig(os_net_config.NetConfig): bond_name) # Infiniband interfaces are handled similarly to Ethernet interfaces - for interface_name, iface_data in self.ib_interface_data.iteritems(): + for interface_name, iface_data in self.ib_interface_data.items(): route_data = self.route_data.get(interface_name, '') route6_data = self.route6_data.get(interface_name, '') interface_path = self.root_dir + ifcfg_config_path(interface_name) @@ -906,10 +932,20 @@ class IfcfgNetConfig(os_net_config.NetConfig): for bridge in restart_bridges: self.ifdown(bridge, iftype='bridge') - for oldname, newname in self.renamed_interfaces.iteritems(): + for oldname, newname in self.renamed_interfaces.items(): self.ifrename(oldname, newname) - for location, data in update_files.iteritems(): + # DPDK initialization is done before running os-net-config, to make + # the DPDK ports available when enabled. DPDK Hotplug support is + # supported only in OvS 2.7 version. Until then, OvS needs to be + # restarted after adding a DPDK port. This change will be removed on + # migration to OvS 2.7 where DPDK Hotplug support is available. + if ovs_needs_restart: + msg = "Restart openvswitch" + self.execute(msg, '/usr/bin/systemctl', + 'restart', 'openvswitch') + + for location, data in update_files.items(): self.write_config(location, data) if ivs_uplinks or ivs_interfaces: diff --git a/os_net_config/objects.py b/os_net_config/objects.py index 8d52e73..944aecd 100644 --- a/os_net_config/objects.py +++ b/os_net_config/objects.py @@ -181,7 +181,7 @@ class _BaseOpts(object): def __init__(self, name, use_dhcp=False, use_dhcpv6=False, addresses=None, routes=None, mtu=None, primary=False, nic_mapping=None, persist_mapping=False, defroute=True, dhclient_args=None, - dns_servers=None): + dns_servers=None, nm_controlled=False): addresses = addresses or [] routes = routes or [] dns_servers = dns_servers or [] @@ -209,6 +209,7 @@ class _BaseOpts(object): self.defroute = defroute self.dhclient_args = dhclient_args self.dns_servers = dns_servers + self.nm_controlled = nm_controlled self.bridge_name = None # internal self.linux_bridge_name = None # internal self.ivs_bridge_name = None # internal @@ -244,6 +245,7 @@ class _BaseOpts(object): mtu = json.get('mtu', None) dhclient_args = json.get('dhclient_args') dns_servers = json.get('dns_servers') + nm_controlled = json.get('nm_controlled') primary = strutils.bool_from_string(str(json.get('primary', False))) addresses = [] routes = [] @@ -274,11 +276,11 @@ class _BaseOpts(object): if include_primary: return (use_dhcp, use_dhcpv6, addresses, routes, mtu, primary, nic_mapping, persist_mapping, defroute, dhclient_args, - dns_servers) + dns_servers, nm_controlled) else: return (use_dhcp, use_dhcpv6, addresses, routes, mtu, nic_mapping, persist_mapping, defroute, dhclient_args, - dns_servers) + dns_servers, nm_controlled) class Interface(_BaseOpts): @@ -287,22 +289,27 @@ class Interface(_BaseOpts): def __init__(self, name, use_dhcp=False, use_dhcpv6=False, addresses=None, routes=None, mtu=None, primary=False, nic_mapping=None, persist_mapping=False, defroute=True, dhclient_args=None, - dns_servers=None, ethtool_opts=None): + dns_servers=None, nm_controlled=False, ethtool_opts=None, + hotplug=False): addresses = addresses or [] routes = routes or [] dns_servers = dns_servers or [] super(Interface, self).__init__(name, use_dhcp, use_dhcpv6, addresses, routes, mtu, primary, nic_mapping, persist_mapping, defroute, - dhclient_args, dns_servers) + dhclient_args, dns_servers, + nm_controlled) self.ethtool_opts = ethtool_opts + self.hotplug = hotplug @staticmethod def from_json(json): name = _get_required_field(json, 'name', 'Interface') + hotplug = strutils.bool_from_string(str(json.get('hotplug', False))) opts = _BaseOpts.base_opts_from_json(json) ethtool_opts = json.get('ethtool_opts', None) - return Interface(name, *opts, ethtool_opts=ethtool_opts) + return Interface(name, *opts, ethtool_opts=ethtool_opts, + hotplug=hotplug) class Vlan(_BaseOpts): @@ -315,7 +322,7 @@ class Vlan(_BaseOpts): def __init__(self, device, vlan_id, use_dhcp=False, use_dhcpv6=False, addresses=None, routes=None, mtu=None, primary=False, nic_mapping=None, persist_mapping=False, defroute=True, - dhclient_args=None, dns_servers=None): + dhclient_args=None, dns_servers=None, nm_controlled=False): addresses = addresses or [] routes = routes or [] dns_servers = dns_servers or [] @@ -323,7 +330,7 @@ class Vlan(_BaseOpts): super(Vlan, self).__init__(name, use_dhcp, use_dhcpv6, addresses, routes, mtu, primary, nic_mapping, persist_mapping, defroute, dhclient_args, - dns_servers) + dns_servers, nm_controlled) self.vlan_id = int(vlan_id) mapped_nic_names = _mapped_nics(nic_mapping) @@ -347,7 +354,7 @@ class IvsInterface(_BaseOpts): def __init__(self, vlan_id, name='ivs', use_dhcp=False, use_dhcpv6=False, addresses=None, routes=None, mtu=1500, primary=False, nic_mapping=None, persist_mapping=False, defroute=True, - dhclient_args=None, dns_servers=None): + dhclient_args=None, dns_servers=None, nm_controlled=False): addresses = addresses or [] routes = routes or [] dns_servers = dns_servers or [] @@ -356,7 +363,7 @@ class IvsInterface(_BaseOpts): addresses, routes, mtu, primary, nic_mapping, persist_mapping, defroute, dhclient_args, - dns_servers) + dns_servers, nm_controlled) self.vlan_id = int(vlan_id) @staticmethod @@ -373,7 +380,8 @@ class NfvswitchInternal(_BaseOpts): def __init__(self, vlan_id, name='nfvswitch', use_dhcp=False, use_dhcpv6=False, addresses=None, routes=None, mtu=1500, primary=False, nic_mapping=None, persist_mapping=False, - defroute=True, dhclient_args=None, dns_servers=None): + defroute=True, dhclient_args=None, dns_servers=None, + nm_controlled=False): addresses = addresses or [] routes = routes or [] dns_servers = dns_servers or [] @@ -382,7 +390,8 @@ class NfvswitchInternal(_BaseOpts): use_dhcpv6, addresses, routes, mtu, primary, nic_mapping, persist_mapping, defroute, - dhclient_args, dns_servers) + dhclient_args, dns_servers, + nm_controlled) self.vlan_id = int(vlan_id) @staticmethod @@ -400,7 +409,7 @@ class OvsBridge(_BaseOpts): routes=None, mtu=None, members=None, ovs_options=None, ovs_extra=None, nic_mapping=None, persist_mapping=False, defroute=True, dhclient_args=None, dns_servers=None, - fail_mode=None): + nm_controlled=False, fail_mode=None): addresses = addresses or [] routes = routes or [] members = members or [] @@ -408,7 +417,8 @@ class OvsBridge(_BaseOpts): super(OvsBridge, self).__init__(name, use_dhcp, use_dhcpv6, addresses, routes, mtu, False, nic_mapping, persist_mapping, defroute, - dhclient_args, dns_servers) + dhclient_args, dns_servers, + nm_controlled) self.members = members self.ovs_options = ovs_options ovs_extra = ovs_extra or [] @@ -433,10 +443,13 @@ class OvsBridge(_BaseOpts): name = _get_required_field(json, 'name', 'OvsBridge') (use_dhcp, use_dhcpv6, addresses, routes, mtu, nic_mapping, persist_mapping, defroute, - dhclient_args, dns_servers) = _BaseOpts.base_opts_from_json( + dhclient_args, dns_servers, + nm_controlled) = _BaseOpts.base_opts_from_json( json, include_primary=False) ovs_options = json.get('ovs_options') - ovs_extra = json.get('ovs_extra') + ovs_extra = json.get('ovs_extra', []) + if not isinstance(ovs_extra, list): + ovs_extra = [ovs_extra] fail_mode = json.get('ovs_fail_mode', DEFAULT_OVS_BRIDGE_FAIL_MODE) members = [] @@ -456,7 +469,7 @@ class OvsBridge(_BaseOpts): ovs_extra=ovs_extra, nic_mapping=nic_mapping, persist_mapping=persist_mapping, defroute=defroute, dhclient_args=dhclient_args, dns_servers=dns_servers, - fail_mode=fail_mode) + nm_controlled=nm_controlled, fail_mode=fail_mode) class OvsUserBridge(_BaseOpts): @@ -466,12 +479,12 @@ class OvsUserBridge(_BaseOpts): routes=None, mtu=None, members=None, ovs_options=None, ovs_extra=None, nic_mapping=None, persist_mapping=False, defroute=True, dhclient_args=None, dns_servers=None, - fail_mode=None): + nm_controlled=False, fail_mode=None): super(OvsUserBridge, self).__init__(name, use_dhcp, use_dhcpv6, addresses, routes, mtu, False, nic_mapping, persist_mapping, defroute, dhclient_args, - dns_servers) + dns_servers, nm_controlled) self.members = members or [] self.ovs_options = ovs_options ovs_extra = ovs_extra or [] @@ -498,10 +511,13 @@ class OvsUserBridge(_BaseOpts): name = _get_required_field(json, 'name', 'OvsUserBridge') (use_dhcp, use_dhcpv6, addresses, routes, mtu, nic_mapping, persist_mapping, defroute, - dhclient_args, dns_servers) = _BaseOpts.base_opts_from_json( + dhclient_args, dns_servers, + nm_controlled) = _BaseOpts.base_opts_from_json( json, include_primary=False) ovs_options = json.get('ovs_options') - ovs_extra = json.get('ovs_extra') + ovs_extra = json.get('ovs_extra', []) + if not isinstance(ovs_extra, list): + ovs_extra = [ovs_extra] fail_mode = json.get('ovs_fail_mode', DEFAULT_OVS_BRIDGE_FAIL_MODE) members = [] @@ -521,7 +537,8 @@ class OvsUserBridge(_BaseOpts): ovs_extra=ovs_extra, nic_mapping=nic_mapping, persist_mapping=persist_mapping, defroute=defroute, dhclient_args=dhclient_args, - dns_servers=dns_servers, fail_mode=fail_mode) + dns_servers=dns_servers, + nm_controlled=nm_controlled, fail_mode=fail_mode) class LinuxBridge(_BaseOpts): @@ -530,7 +547,7 @@ class LinuxBridge(_BaseOpts): def __init__(self, name, use_dhcp=False, use_dhcpv6=False, addresses=None, routes=None, mtu=None, members=None, nic_mapping=None, persist_mapping=False, defroute=True, dhclient_args=None, - dns_servers=None): + dns_servers=None, nm_controlled=False): addresses = addresses or [] routes = routes or [] members = members or [] @@ -538,7 +555,8 @@ class LinuxBridge(_BaseOpts): super(LinuxBridge, self).__init__(name, use_dhcp, use_dhcpv6, addresses, routes, mtu, False, nic_mapping, persist_mapping, - defroute, dhclient_args, dns_servers) + defroute, dhclient_args, dns_servers, + nm_controlled) self.members = members for member in self.members: member.linux_bridge_name = name @@ -557,7 +575,7 @@ class LinuxBridge(_BaseOpts): name = _get_required_field(json, 'name', 'LinuxBridge') (use_dhcp, use_dhcpv6, addresses, routes, mtu, nic_mapping, persist_mapping, defroute, dhclient_args, - dns_servers) = _BaseOpts.base_opts_from_json( + dns_servers, nm_controlled) = _BaseOpts.base_opts_from_json( json, include_primary=False) members = [] @@ -576,7 +594,8 @@ class LinuxBridge(_BaseOpts): members=members, nic_mapping=nic_mapping, persist_mapping=persist_mapping, defroute=defroute, dhclient_args=dhclient_args, - dns_servers=dns_servers) + dns_servers=dns_servers, + nm_controlled=nm_controlled) class IvsBridge(_BaseOpts): @@ -595,7 +614,7 @@ class IvsBridge(_BaseOpts): def __init__(self, name='ivs', use_dhcp=False, use_dhcpv6=False, addresses=None, routes=None, mtu=1500, members=None, nic_mapping=None, persist_mapping=False, defroute=True, - dhclient_args=None, dns_servers=None): + dhclient_args=None, dns_servers=None, nm_controlled=False): addresses = addresses or [] routes = routes or [] members = members or [] @@ -603,7 +622,8 @@ class IvsBridge(_BaseOpts): super(IvsBridge, self).__init__(name, use_dhcp, use_dhcpv6, addresses, routes, mtu, False, nic_mapping, persist_mapping, - defroute, dhclient_args, dns_servers) + defroute, dhclient_args, dns_servers, + nm_controlled) self.members = members for member in self.members: if isinstance(member, OvsBond) or isinstance(member, LinuxBond): @@ -618,7 +638,7 @@ class IvsBridge(_BaseOpts): name = 'ivs' (use_dhcp, use_dhcpv6, addresses, routes, mtu, nic_mapping, persist_mapping, defroute, dhclient_args, - dns_servers) = _BaseOpts.base_opts_from_json( + dns_servers, nm_controlled) = _BaseOpts.base_opts_from_json( json, include_primary=False) members = [] @@ -637,7 +657,7 @@ class IvsBridge(_BaseOpts): members=members, nic_mapping=nic_mapping, persist_mapping=persist_mapping, defroute=defroute, dhclient_args=dhclient_args, - dns_servers=dns_servers) + dns_servers=dns_servers, nm_controlled=nm_controlled) class NfvswitchBridge(_BaseOpts): @@ -651,7 +671,8 @@ class NfvswitchBridge(_BaseOpts): def __init__(self, name='nfvswitch', use_dhcp=False, use_dhcpv6=False, addresses=None, routes=None, mtu=1500, members=None, nic_mapping=None, persist_mapping=False, defroute=True, - dhclient_args=None, dns_servers=None, options=""): + dhclient_args=None, dns_servers=None, nm_controlled=False, + options=""): addresses = addresses or [] routes = routes or [] members = members or [] @@ -660,7 +681,7 @@ class NfvswitchBridge(_BaseOpts): addresses, routes, mtu, False, nic_mapping, persist_mapping, defroute, dhclient_args, - dns_servers) + dns_servers, nm_controlled) self.options = options self.members = members for member in self.members: @@ -676,7 +697,7 @@ class NfvswitchBridge(_BaseOpts): name = 'nfvswitch' (use_dhcp, use_dhcpv6, addresses, routes, mtu, nic_mapping, persist_mapping, defroute, dhclient_args, - dns_servers) = _BaseOpts.base_opts_from_json( + dns_servers, nm_controlled) = _BaseOpts.base_opts_from_json( json, include_primary=False) # members @@ -700,7 +721,8 @@ class NfvswitchBridge(_BaseOpts): members=members, nic_mapping=nic_mapping, persist_mapping=persist_mapping, defroute=defroute, dhclient_args=dhclient_args, - dns_servers=dns_servers, options=options) + dns_servers=dns_servers, + nm_controlled=nm_controlled, options=options) class LinuxTeam(_BaseOpts): @@ -709,7 +731,8 @@ class LinuxTeam(_BaseOpts): def __init__(self, name, use_dhcp=False, use_dhcpv6=False, addresses=None, routes=None, mtu=None, primary=False, members=None, bonding_options=None, nic_mapping=None, persist_mapping=False, - defroute=True, dhclient_args=None, dns_servers=None): + defroute=True, dhclient_args=None, dns_servers=None, + nm_controlled=False): addresses = addresses or [] routes = routes or [] members = members or [] @@ -717,7 +740,8 @@ class LinuxTeam(_BaseOpts): super(LinuxTeam, self).__init__(name, use_dhcp, use_dhcpv6, addresses, routes, mtu, primary, nic_mapping, persist_mapping, defroute, - dhclient_args, dns_servers) + dhclient_args, dns_servers, + nm_controlled) self.members = members self.bonding_options = bonding_options for member in self.members: @@ -736,7 +760,7 @@ class LinuxTeam(_BaseOpts): name = _get_required_field(json, 'name', 'LinuxTeam') (use_dhcp, use_dhcpv6, addresses, routes, mtu, nic_mapping, persist_mapping, defroute, dhclient_args, - dns_servers) = _BaseOpts.base_opts_from_json( + dns_servers, nm_controlled) = _BaseOpts.base_opts_from_json( json, include_primary=False) bonding_options = json.get('bonding_options') members = [] @@ -756,7 +780,8 @@ class LinuxTeam(_BaseOpts): members=members, bonding_options=bonding_options, nic_mapping=nic_mapping, persist_mapping=persist_mapping, defroute=defroute, - dhclient_args=dhclient_args, dns_servers=dns_servers) + dhclient_args=dhclient_args, dns_servers=dns_servers, + nm_controlled=nm_controlled) class LinuxBond(_BaseOpts): @@ -765,7 +790,8 @@ class LinuxBond(_BaseOpts): def __init__(self, name, use_dhcp=False, use_dhcpv6=False, addresses=None, routes=None, mtu=None, primary=False, members=None, bonding_options=None, nic_mapping=None, persist_mapping=False, - defroute=True, dhclient_args=None, dns_servers=None): + defroute=True, dhclient_args=None, dns_servers=None, + nm_controlled=False): addresses = addresses or [] routes = routes or [] members = members or [] @@ -773,7 +799,8 @@ class LinuxBond(_BaseOpts): super(LinuxBond, self).__init__(name, use_dhcp, use_dhcpv6, addresses, routes, mtu, primary, nic_mapping, persist_mapping, defroute, - dhclient_args, dns_servers) + dhclient_args, dns_servers, + nm_controlled) self.members = members self.bonding_options = bonding_options for member in self.members: @@ -792,7 +819,7 @@ class LinuxBond(_BaseOpts): name = _get_required_field(json, 'name', 'LinuxBond') (use_dhcp, use_dhcpv6, addresses, routes, mtu, nic_mapping, persist_mapping, defroute, dhclient_args, - dns_servers) = _BaseOpts.base_opts_from_json( + dns_servers, nm_controlled) = _BaseOpts.base_opts_from_json( json, include_primary=False) bonding_options = json.get('bonding_options') members = [] @@ -812,7 +839,8 @@ class LinuxBond(_BaseOpts): members=members, bonding_options=bonding_options, nic_mapping=nic_mapping, persist_mapping=persist_mapping, defroute=defroute, - dhclient_args=dhclient_args, dns_servers=dns_servers) + dhclient_args=dhclient_args, dns_servers=dns_servers, + nm_controlled=nm_controlled) class OvsBond(_BaseOpts): @@ -822,7 +850,7 @@ class OvsBond(_BaseOpts): routes=None, mtu=None, primary=False, members=None, ovs_options=None, ovs_extra=None, nic_mapping=None, persist_mapping=False, defroute=True, dhclient_args=None, - dns_servers=None): + dns_servers=None, nm_controlled=False): addresses = addresses or [] routes = routes or [] members = members or [] @@ -830,7 +858,7 @@ class OvsBond(_BaseOpts): super(OvsBond, self).__init__(name, use_dhcp, use_dhcpv6, addresses, routes, mtu, primary, nic_mapping, persist_mapping, defroute, dhclient_args, - dns_servers) + dns_servers, nm_controlled) self.members = members self.ovs_options = ovs_options self.ovs_extra = format_ovs_extra(self, ovs_extra) @@ -853,10 +881,12 @@ class OvsBond(_BaseOpts): name = _get_required_field(json, 'name', 'OvsBond') (use_dhcp, use_dhcpv6, addresses, routes, mtu, nic_mapping, persist_mapping, defroute, dhclient_args, - dns_servers) = _BaseOpts.base_opts_from_json( + dns_servers, nm_controlled) = _BaseOpts.base_opts_from_json( json, include_primary=False) ovs_options = json.get('ovs_options') ovs_extra = json.get('ovs_extra', []) + if not isinstance(ovs_extra, list): + ovs_extra = [ovs_extra] members = [] # members @@ -874,7 +904,8 @@ class OvsBond(_BaseOpts): members=members, ovs_options=ovs_options, ovs_extra=ovs_extra, nic_mapping=nic_mapping, persist_mapping=persist_mapping, defroute=defroute, - dhclient_args=dhclient_args, dns_servers=dns_servers) + dhclient_args=dhclient_args, dns_servers=dns_servers, + nm_controlled=nm_controlled) class OvsTunnel(_BaseOpts): @@ -883,15 +914,16 @@ class OvsTunnel(_BaseOpts): def __init__(self, name, use_dhcp=False, use_dhcpv6=False, addresses=None, routes=None, mtu=None, primary=False, nic_mapping=None, persist_mapping=False, defroute=True, dhclient_args=None, - dns_servers=None, tunnel_type=None, ovs_options=None, - ovs_extra=None): + dns_servers=None, nm_controlled=False, tunnel_type=None, + ovs_options=None, ovs_extra=None): addresses = addresses or [] routes = routes or [] dns_servers = dns_servers or [] super(OvsTunnel, self).__init__(name, use_dhcp, use_dhcpv6, addresses, routes, mtu, primary, nic_mapping, persist_mapping, defroute, - dhclient_args, dns_servers) + dhclient_args, dns_servers, + nm_controlled) self.tunnel_type = tunnel_type self.ovs_options = ovs_options or [] self.ovs_extra = format_ovs_extra(self, ovs_extra) @@ -903,6 +935,8 @@ class OvsTunnel(_BaseOpts): ovs_options = json.get('ovs_options', []) ovs_options = ['options:%s' % opt for opt in ovs_options] ovs_extra = json.get('ovs_extra', []) + if not isinstance(ovs_extra, list): + ovs_extra = [ovs_extra] opts = _BaseOpts.base_opts_from_json(json) return OvsTunnel(name, *opts, tunnel_type=tunnel_type, ovs_options=ovs_options, ovs_extra=ovs_extra) @@ -914,8 +948,8 @@ class OvsPatchPort(_BaseOpts): def __init__(self, name, use_dhcp=False, use_dhcpv6=False, addresses=None, routes=None, mtu=None, primary=False, nic_mapping=None, persist_mapping=False, defroute=True, dhclient_args=None, - dns_servers=None, bridge_name=None, peer=None, - ovs_options=None, ovs_extra=None): + dns_servers=None, nm_controlled=False, bridge_name=None, + peer=None, ovs_options=None, ovs_extra=None): addresses = addresses or [] routes = routes or [] dns_servers = dns_servers or [] @@ -923,7 +957,7 @@ class OvsPatchPort(_BaseOpts): addresses, routes, mtu, primary, nic_mapping, persist_mapping, defroute, dhclient_args, - dns_servers) + dns_servers, nm_controlled) self.bridge_name = bridge_name self.peer = peer self.ovs_options = ovs_options or [] @@ -937,6 +971,8 @@ class OvsPatchPort(_BaseOpts): ovs_options = json.get('ovs_options', []) ovs_options = ['options:%s' % opt for opt in ovs_options] ovs_extra = json.get('ovs_extra', []) + if not isinstance(ovs_extra, list): + ovs_extra = [ovs_extra] opts = _BaseOpts.base_opts_from_json(json) return OvsPatchPort(name, *opts, bridge_name=bridge_name, peer=peer, ovs_options=ovs_options, ovs_extra=ovs_extra) @@ -948,14 +984,15 @@ class IbInterface(_BaseOpts): def __init__(self, name, use_dhcp=False, use_dhcpv6=False, addresses=None, routes=None, mtu=None, primary=False, nic_mapping=None, persist_mapping=False, defroute=True, dhclient_args=None, - dns_servers=None, ethtool_opts=None): + dns_servers=None, nm_controlled=False, ethtool_opts=None): addresses = addresses or [] routes = routes or [] dns_servers = dns_servers or [] super(IbInterface, self).__init__(name, use_dhcp, use_dhcpv6, addresses, routes, mtu, primary, nic_mapping, persist_mapping, - defroute, dhclient_args, dns_servers) + defroute, dhclient_args, dns_servers, + nm_controlled) self.ethtool_opts = ethtool_opts @staticmethod @@ -972,14 +1009,14 @@ class OvsDpdkPort(_BaseOpts): def __init__(self, name, use_dhcp=False, use_dhcpv6=False, addresses=None, routes=None, mtu=None, primary=False, nic_mapping=None, persist_mapping=False, defroute=True, dhclient_args=None, - dns_servers=None, members=None, driver='vfio-pci', - ovs_options=None, ovs_extra=None): + dns_servers=None, nm_controlled=False, members=None, + driver='vfio-pci', ovs_options=None, ovs_extra=None): super(OvsDpdkPort, self).__init__(name, use_dhcp, use_dhcpv6, addresses, routes, mtu, primary, nic_mapping, persist_mapping, defroute, dhclient_args, - dns_servers) + dns_servers, nm_controlled) self.members = members or [] self.ovs_options = ovs_options or [] self.ovs_extra = format_ovs_extra(self, ovs_extra) @@ -1020,6 +1057,8 @@ class OvsDpdkPort(_BaseOpts): ovs_options = json.get('ovs_options', []) ovs_options = ['options:%s' % opt for opt in ovs_options] ovs_extra = json.get('ovs_extra', []) + if not isinstance(ovs_extra, list): + ovs_extra = [ovs_extra] opts = _BaseOpts.base_opts_from_json(json) return OvsDpdkPort(name, *opts, members=members, driver=driver, ovs_options=ovs_options, ovs_extra=ovs_extra) @@ -1032,11 +1071,12 @@ class OvsDpdkBond(_BaseOpts): routes=None, mtu=None, primary=False, members=None, ovs_options=None, ovs_extra=None, nic_mapping=None, persist_mapping=False, defroute=True, dhclient_args=None, - dns_servers=None): + dns_servers=None, nm_controlled=False): super(OvsDpdkBond, self).__init__(name, use_dhcp, use_dhcpv6, addresses, routes, mtu, primary, nic_mapping, persist_mapping, - defroute, dhclient_args, dns_servers) + defroute, dhclient_args, dns_servers, + nm_controlled) self.members = members or [] self.ovs_options = ovs_options self.ovs_extra = format_ovs_extra(self, ovs_extra) @@ -1060,10 +1100,12 @@ class OvsDpdkBond(_BaseOpts): name = _get_required_field(json, 'name', 'OvsDpdkBond') (use_dhcp, use_dhcpv6, addresses, routes, mtu, nic_mapping, persist_mapping, defroute, dhclient_args, - dns_servers) = _BaseOpts.base_opts_from_json( + dns_servers, nm_controlled) = _BaseOpts.base_opts_from_json( json, include_primary=False) ovs_options = json.get('ovs_options') ovs_extra = json.get('ovs_extra', []) + if not isinstance(ovs_extra, list): + ovs_extra = [ovs_extra] members = [] # members @@ -1087,4 +1129,5 @@ class OvsDpdkBond(_BaseOpts): ovs_extra=ovs_extra, nic_mapping=nic_mapping, persist_mapping=persist_mapping, defroute=defroute, dhclient_args=dhclient_args, - dns_servers=dns_servers) + dns_servers=dns_servers, + nm_controlled=nm_controlled) diff --git a/os_net_config/tests/test_impl_eni.py b/os_net_config/tests/test_impl_eni.py index e445ed4..4911cb9 100644 --- a/os_net_config/tests/test_impl_eni.py +++ b/os_net_config/tests/test_impl_eni.py @@ -32,6 +32,12 @@ _V4_IFACE_STATIC_IP = _AUTO + """iface eth0 inet static netmask 255.255.255.0 """ +_IFACE_HOTPLUG = """allow-hotplug eth0 +iface eth0 inet static + address 192.168.1.2 + netmask 255.255.255.0 +""" + _V4_IFACE_STATIC_IP_MULTIPLE = (_V4_IFACE_STATIC_IP + _AUTO + """iface eth0 inet static address 10.0.0.2 @@ -117,8 +123,9 @@ class TestENINetConfig(base.TestCase): def get_route_config(self): return self.provider.routes[self.if_name] - def _default_interface(self, addr=[], rts=[]): - return objects.Interface(self.if_name, addresses=addr, routes=rts) + def _default_interface(self, addr=[], rts=[], hotplug=False): + return objects.Interface(self.if_name, addresses=addr, routes=rts, + hotplug=hotplug) def test_interface_no_ip(self): interface = self._default_interface() @@ -131,6 +138,12 @@ class TestENINetConfig(base.TestCase): self.provider.add_interface(interface) self.assertEqual(_V4_IFACE_STATIC_IP, self.get_interface_config()) + def test_add_interface_with_hotplug(self): + v4_addr = objects.Address('192.168.1.2/24') + interface = self._default_interface(addr=[v4_addr], hotplug=True) + self.provider.add_interface(interface) + self.assertEqual(_IFACE_HOTPLUG, self.get_interface_config()) + def test_add_interface_with_v4_multiple(self): v4_addresses = [objects.Address('192.168.1.2/24'), objects.Address('10.0.0.2/8')] diff --git a/os_net_config/tests/test_impl_ifcfg.py b/os_net_config/tests/test_impl_ifcfg.py index 3a5a7db..82ca116 100644 --- a/os_net_config/tests/test_impl_ifcfg.py +++ b/os_net_config/tests/test_impl_ifcfg.py @@ -20,6 +20,7 @@ import tempfile from oslo_concurrency import processutils from os_net_config import impl_ifcfg +from os_net_config import NetConfig from os_net_config import objects from os_net_config.tests import base from os_net_config import utils @@ -33,6 +34,23 @@ NM_CONTROLLED=no PEERDNS=no """ +_BASE_IFCFG_NETWORKMANAGER = """# This file is autogenerated by os-net-config +DEVICE=em1 +ONBOOT=yes +HOTPLUG=no +NM_CONTROLLED=yes +PEERDNS=no +""" + +_HOTPLUG = """# This file is autogenerated by os-net-config +DEVICE=em1 +ONBOOT=yes +HOTPLUG=yes +NM_CONTROLLED=no +PEERDNS=no +BOOTPROTO=none +""" + _NO_IP = _BASE_IFCFG + "BOOTPROTO=none\n" _V4_IFCFG = _BASE_IFCFG + """BOOTPROTO=static @@ -143,6 +161,19 @@ OVSBOOTPROTO=dhcp OVSDHCPINTERFACES="em1" """ +_NM_CONTROLLED_INTERFACE = _BASE_IFCFG_NETWORKMANAGER + """MASTER=bond1 +SLAVE=yes +BOOTPROTO=none +""" + +_NM_CONTROLLED_BOND = """# This file is autogenerated by os-net-config +DEVICE=bond1 +ONBOOT=yes +HOTPLUG=no +NM_CONTROLLED=yes +PEERDNS=no +""" + _OVS_BRIDGE_DHCP_STANDALONE = _OVS_BRIDGE_DHCP + \ "OVS_EXTRA=\"set bridge br-ctlplane fail_mode=standalone\"\n" @@ -386,6 +417,11 @@ class TestIfcfgNetConfig(base.TestCase): self.provider.add_interface(interface) self.assertEqual(_NO_IP, self.get_interface_config()) + def test_add_interface_with_hotplug(self): + interface = objects.Interface('em1', hotplug=True) + self.provider.add_interface(interface) + self.assertEqual(_HOTPLUG, self.get_interface_config()) + def test_add_base_interface_vlan(self): interface = objects.Interface('em1.120') self.provider.add_interface(interface) @@ -830,6 +866,20 @@ DNS2=5.6.7.8 """ self.assertEqual(em1_config, self.get_interface_config('em1')) + def test_nm_controlled(self): + interface1 = objects.Interface('em1', nm_controlled=True) + interface2 = objects.Interface('em2', nm_controlled=True) + bond = objects.LinuxBond('bond1', nm_controlled=True, + members=[interface1, interface2]) + self.provider.add_linux_bond(bond) + self.provider.add_interface(interface1) + self.provider.add_interface(interface2) + + ifcfg_data = self.get_interface_config('em1') + self.assertEqual(_NM_CONTROLLED_INTERFACE, ifcfg_data) + bond_data = self.get_linux_bond_config('bond1') + self.assertEqual(_NM_CONTROLLED_BOND, bond_data) + def test_network_ovs_dpdk_bridge_and_port(self): nic_mapping = {'nic1': 'eth0', 'nic2': 'eth1', 'nic3': 'eth2'} self.stubbed_mapped_nics = nic_mapping @@ -909,6 +959,7 @@ class TestIfcfgNetConfigApply(base.TestCase): def setUp(self): super(TestIfcfgNetConfigApply, self).setUp() self.temp_ifcfg_file = tempfile.NamedTemporaryFile() + self.temp_bond_file = tempfile.NamedTemporaryFile() self.temp_route_file = tempfile.NamedTemporaryFile() self.temp_route6_file = tempfile.NamedTemporaryFile() self.temp_bridge_file = tempfile.NamedTemporaryFile() @@ -921,6 +972,13 @@ class TestIfcfgNetConfigApply(base.TestCase): return self.temp_ifcfg_file.name self.stubs.Set(impl_ifcfg, 'ifcfg_config_path', test_ifcfg_path) + def test_remove_ifcfg_config(name): + ifcfg_file = self.temp_ifcfg_file.name + if os.path.exists(ifcfg_file): + os.remove(ifcfg_file) + self.stubs.Set(impl_ifcfg, 'remove_ifcfg_config', + test_remove_ifcfg_config) + def test_routes_path(name): return self.temp_route_file.name self.stubs.Set(impl_ifcfg, 'route_config_path', test_routes_path) @@ -1143,3 +1201,32 @@ class TestIfcfgNetConfigApply(base.TestCase): self.provider.apply(cleanup=True) self.assertTrue(os.path.exists(tmp_lo_file)) os.remove(tmp_lo_file) + + def test_ovs_restart_called(self): + interface = objects.Interface('em1') + dpdk_port = objects.OvsDpdkPort('dpdk0', members=[interface]) + execute_strings = [] + + def test_execute(*args, **kwargs): + execute_strings.append(args[1]) + pass + self.stubs.Set(NetConfig, 'execute', test_execute) + + self.provider.noop = True + self.provider.add_ovs_dpdk_port(dpdk_port) + self.provider.apply() + self.assertIn('Restart openvswitch', execute_strings) + + def test_ovs_restart_not_called(self): + interface = objects.Interface('em1') + execute_strings = [] + + def test_execute(*args, **kwargs): + execute_strings.append(args[1]) + pass + self.stubs.Set(NetConfig, 'execute', test_execute) + + self.provider.noop = True + self.provider.add_interface(interface) + self.provider.apply() + self.assertNotIn('Restart openvswitch', execute_strings) diff --git a/os_net_config/tests/test_objects.py b/os_net_config/tests/test_objects.py index ca2dd47..f5daf31 100644 --- a/os_net_config/tests/test_objects.py +++ b/os_net_config/tests/test_objects.py @@ -98,6 +98,27 @@ class TestInterface(base.TestCase): self.assertEqual("em1", interface.name) self.assertTrue(interface.use_dhcp) + def test_from_json_hotplug(self): + data = """{ +"type": "interface", +"name": "em1", +"hotplug": true +} +""" + interface = objects.object_from_json(json.loads(data)) + self.assertEqual("em1", interface.name) + self.assertTrue(interface.hotplug) + + def test_from_json_hotplug_off_by_default(self): + data = """{ +"type": "interface", +"name": "em1" +} +""" + interface = objects.object_from_json(json.loads(data)) + self.assertEqual("em1", interface.name) + self.assertFalse(interface.hotplug) + def test_from_json_defroute(self): data = '{"type": "interface", "name": "em1", "use_dhcp": true}' interface1 = objects.object_from_json(json.loads(data)) @@ -274,6 +295,34 @@ class TestBridge(base.TestCase): self.assertTrue(interface2.ovs_port) self.assertEqual("br-foo", interface2.bridge_name) + def test_from_json_ovs_extra(self): + data = """{ +"type": "ovs_bridge", +"name": "br-foo", +"ovs_extra": ["bar"], +"ovs_fail_mode": "standalone" +} +""" + bridge = objects.object_from_json(json.loads(data)) + self.assertTrue(2 == len(bridge.ovs_extra)) + self.assertEqual("bar", bridge.ovs_extra[0]) + self.assertEqual("set bridge br-foo fail_mode=standalone", + bridge.ovs_extra[1]) + + def test_from_json_ovs_extra_string(self): + data = """{ +"type": "ovs_bridge", +"name": "br-foo", +"ovs_extra": "bar", +"ovs_fail_mode": "standalone" +} +""" + bridge = objects.object_from_json(json.loads(data)) + self.assertTrue(2 == len(bridge.ovs_extra)) + self.assertEqual("bar", bridge.ovs_extra[0]) + self.assertEqual("set bridge br-foo fail_mode=standalone", + bridge.ovs_extra[1]) + class TestLinuxBridge(base.TestCase): @@ -407,7 +456,7 @@ class TestIvsInterface(base.TestCase): objects.IvsBridge.from_json, json.loads(data)) expected = 'IVS does not support bond interfaces.' - self.assertIn(expected, err) + self.assertIn(expected, six.text_type(err)) class TestNfvswitchBridge(base.TestCase): @@ -472,7 +521,7 @@ class TestNfvswitchInterface(base.TestCase): objects.NfvswitchBridge.from_json, json.loads(data)) expected = 'NFVSwitch does not support bond interfaces.' - self.assertIn(expected, err) + self.assertIn(expected, six.text_type(err)) class TestBond(base.TestCase): diff --git a/os_net_config/tests/test_utils.py b/os_net_config/tests/test_utils.py index b766384..1885cbb 100644 --- a/os_net_config/tests/test_utils.py +++ b/os_net_config/tests/test_utils.py @@ -217,3 +217,27 @@ class TestUtils(base.TestCase): def test_interface_mac_raises(self): self.assertRaises(IOError, utils.interface_mac, 'ens20f2p3') + + def test_is_active_nic_for_sriov_vf(self): + + tmpdir = tempfile.mkdtemp() + self.stubs.Set(utils, '_SYS_CLASS_NET', tmpdir) + + # SR-IOV PF = ens802f0 + # SR-IOV VF = enp129s2 + for nic in ['ens802f0', 'enp129s2']: + nic_path = os.path.join(tmpdir, nic) + os.makedirs(nic_path) + os.makedirs(os.path.join(nic_path, 'device')) + with open(os.path.join(nic_path, 'operstate'), 'w') as f: + f.write('up') + with open(os.path.join(nic_path, 'address'), 'w') as f: + f.write('1.2.3.4') + + nic_path = os.path.join(tmpdir, 'enp129s2', 'device', 'physfn') + os.makedirs(nic_path) + + self.assertEqual(utils._is_active_nic('ens802f0'), True) + self.assertEqual(utils._is_active_nic('enp129s2'), False) + + shutil.rmtree(tmpdir) diff --git a/os_net_config/utils.py b/os_net_config/utils.py index af359d5..98bfe99 100644 --- a/os_net_config/utils.py +++ b/os_net_config/utils.py @@ -107,7 +107,16 @@ def _is_active_nic(interface_name): with open(_SYS_CLASS_NET + '/%s/address' % interface_name, 'r') as f: address = f.read().rstrip() - if has_device_dir and operstate == 'up' and address: + # If SR-IOV Virtual Functions (VF) are enabled in an interface, there + # will be additional nics created for each VF. It has to be ignored in + # the nic numbering. All the VFs will have a reference to the PF with + # directory name as 'physfn', if this directory is present it should be + # ignored. + vf_path_check = _SYS_CLASS_NET + '/%s/device/physfn' % interface_name + is_sriov_vf = os.path.isdir(vf_path_check) + + if (has_device_dir and operstate == 'up' and address and + not is_sriov_vf): return True else: return False @@ -1,6 +1,6 @@ [tox] minversion = 1.6 -envlist = py27,py33,pypy,pep8 +envlist = py35,py27,pep8 skipsdist = True [testenv] |