diff options
-rw-r--r-- | etc/os-net-config/samples/ib_interface.json | 26 | ||||
-rw-r--r-- | etc/os-net-config/samples/ib_interface.yaml | 18 | ||||
-rw-r--r-- | etc/os-net-config/samples/mapping_mnemonic.yaml | 8 | ||||
-rw-r--r-- | etc/os-net-config/samples/nfvswitch.json | 38 | ||||
-rw-r--r-- | etc/os-net-config/samples/nfvswitch.yaml | 25 | ||||
-rw-r--r-- | etc/os-net-config/samples/team.json | 13 | ||||
-rw-r--r-- | etc/os-net-config/samples/team.yaml | 18 | ||||
-rw-r--r-- | os_net_config/__init__.py | 33 | ||||
-rw-r--r-- | os_net_config/impl_ifcfg.py | 212 | ||||
-rw-r--r-- | os_net_config/objects.py | 246 | ||||
-rw-r--r-- | os_net_config/tests/base.py | 12 | ||||
-rw-r--r-- | os_net_config/tests/test_cli.py | 18 | ||||
-rw-r--r-- | os_net_config/tests/test_impl_ifcfg.py | 140 | ||||
-rw-r--r-- | os_net_config/tests/test_objects.py | 265 |
14 files changed, 1006 insertions, 66 deletions
diff --git a/etc/os-net-config/samples/ib_interface.json b/etc/os-net-config/samples/ib_interface.json new file mode 100644 index 0000000..4e42867 --- /dev/null +++ b/etc/os-net-config/samples/ib_interface.json @@ -0,0 +1,26 @@ +{"network_config": [ + { + "type": "ib_interface", + "name": "ib0", + "use_dhcp": false, + "addresses": [ + { + "ip_netmask": "192.0.2.1/24" + } + ], + "routes": [ + { + "ip_netmask": "0.0.0.0/0", + "next_hop": "192.0.2.254", + "default": "true" + } + ] + }, + { + "type": "ib_interface", + "name": "ib1", + "use_dhcp": true, + "defroute": no + } + ] +} diff --git a/etc/os-net-config/samples/ib_interface.yaml b/etc/os-net-config/samples/ib_interface.yaml new file mode 100644 index 0000000..f930471 --- /dev/null +++ b/etc/os-net-config/samples/ib_interface.yaml @@ -0,0 +1,18 @@ +network_config: + - + type: ib_interface + name: ib0 + use_dhcp: false + addresses: + - + ip_netmask: 192.0.2.1/24 + routes: + - + ip_netmask: 0.0.0.0/0 + next_hop: 192.0.2.254 + default: true + - + type: interface + name: ib1 + use_dhcp: true + defroute: no
\ No newline at end of file diff --git a/etc/os-net-config/samples/mapping_mnemonic.yaml b/etc/os-net-config/samples/mapping_mnemonic.yaml new file mode 100644 index 0000000..10f31de --- /dev/null +++ b/etc/os-net-config/samples/mapping_mnemonic.yaml @@ -0,0 +1,8 @@ +# See the mapping.yaml sample for an overview of the mapping functionality. +# This shows the use of mnemonic aliases instead of the default nicN aliases +# in configs. It can be used with the -m option to override the default +# mapping of the nicN aliases. +interface_mapping: + provision: em1 + bond0p1: em2 + bond0p2: 12:34:56:de:f0:12 diff --git a/etc/os-net-config/samples/nfvswitch.json b/etc/os-net-config/samples/nfvswitch.json new file mode 100644 index 0000000..2d8af8a --- /dev/null +++ b/etc/os-net-config/samples/nfvswitch.json @@ -0,0 +1,38 @@ +{ + "network_config": [ + { + "type": "nfvswitch_bridge", + "cpus": "2,3,4,5", + "members": [ + { + "type": "interface", + "name": "nic2", + }, + { + "type": "interface", + "name": "nic3" + }, + { + "type": "nfvswitch_internal", + "name": "api", + "addresses": [ + { + "ip_netmask": "172.16.2.7/24" + } + ], + "vlan_id": 201 + }, + { + "type": "nfvswitch_internal", + "name": "storage", + "addresses": [ + { + "ip_netmask": "172.16.1.6/24" + } + ], + "vlan_id": 202 + } + ] + } + ] +} diff --git a/etc/os-net-config/samples/nfvswitch.yaml b/etc/os-net-config/samples/nfvswitch.yaml new file mode 100644 index 0000000..5af3f70 --- /dev/null +++ b/etc/os-net-config/samples/nfvswitch.yaml @@ -0,0 +1,25 @@ +network_config: + - + type: nfvswitch_bridge + cpus: "2,3,4,5" + members: + - + type: interface + name: nic2 + - + type: interface + name: nic3 + - + type: nfvswitch_internal + name: api + vlan_id: 201 + addresses: + - + ip_netmask: 172.16.2.7/24 + - + type: nfvswitch_internal + name: storage + vlan_id: 202 + addresses: + - + ip_netmask: 172.16.1.6/24
\ No newline at end of file diff --git a/etc/os-net-config/samples/team.json b/etc/os-net-config/samples/team.json new file mode 100644 index 0000000..4524b3c --- /dev/null +++ b/etc/os-net-config/samples/team.json @@ -0,0 +1,13 @@ +{ "network_config": [ + { + "type": "team", + "name": "team1", + "use_dhcp": true, + "bonding_options": "{\"runner\": {\"name\": \"activebackup\"}}", + "members": [ + { "type": "interface", "name": "em1", "primary": true }, + { "type": "interface", "name": "em2" } + ] + } + ] +} diff --git a/etc/os-net-config/samples/team.yaml b/etc/os-net-config/samples/team.yaml new file mode 100644 index 0000000..3770de0 --- /dev/null +++ b/etc/os-net-config/samples/team.yaml @@ -0,0 +1,18 @@ +# Config for bonding with teamd. Bonding options are provided as a JSON +# string. The following runners are available in teamd: broadcast, +# roundrobin, activebackup, loadbalance, and lacp. +# Please see the teamd.conf(5) man page for more information. +network_config: + - + type: team + name: team1 + use_dhcp: true + bonding_options: '{"runner": {"name": "activebackup"}}' + members: + - + type: interface + name: em1 + primary: true + - + type: interface + name: em2 diff --git a/os_net_config/__init__.py b/os_net_config/__init__.py index 88e8900..2aafc23 100644 --- a/os_net_config/__init__.py +++ b/os_net_config/__init__.py @@ -51,6 +51,8 @@ class NetConfig(object): self.add_vlan(obj) elif isinstance(obj, objects.IvsInterface): self.add_ivs_interface(obj) + elif isinstance(obj, objects.NfvswitchInternal): + self.add_nfvswitch_internal(obj) elif isinstance(obj, objects.OvsBridge): self.add_bridge(obj) for member in obj.members: @@ -63,6 +65,10 @@ class NetConfig(object): self.add_ivs_bridge(obj) for member in obj.members: self.add_object(member) + elif isinstance(obj, objects.NfvswitchBridge): + self.add_nfvswitch_bridge(obj) + for member in obj.members: + self.add_object(member) elif isinstance(obj, objects.OvsBond): self.add_bond(obj) for member in obj.members: @@ -71,10 +77,16 @@ class NetConfig(object): self.add_linux_bond(obj) for member in obj.members: self.add_object(member) + elif isinstance(obj, objects.LinuxTeam): + self.add_linux_team(obj) + for member in obj.members: + self.add_object(member) elif isinstance(obj, objects.OvsTunnel): self.add_ovs_tunnel(obj) elif isinstance(obj, objects.OvsPatchPort): self.add_ovs_patch_port(obj) + elif isinstance(obj, objects.IbInterface): + self.add_ib_interface(obj) def add_interface(self, interface): """Add an Interface object to the net config object. @@ -111,6 +123,13 @@ class NetConfig(object): """ raise NotImplemented("add_ivs_bridge is not implemented.") + def add_nfvswitch_bridge(self, bridge): + """Add a NfvswitchBridge object to the net config object. + + :param bridge: The NfvswitchBridge object to add. + """ + raise NotImplemented("add_nfvswitch_bridge is not implemented.") + def add_bond(self, bond): """Add an OvsBond object to the net config object. @@ -125,6 +144,13 @@ class NetConfig(object): """ raise NotImplemented("add_linux_bond is not implemented.") + def add_linux_team(self, team): + """Add a LinuxTeam object to the net config object. + + :param team: The LinuxTeam object to add. + """ + raise NotImplemented("add_linux_team is not implemented.") + def add_ovs_tunnel(self, tunnel): """Add a OvsTunnel object to the net config object. @@ -139,6 +165,13 @@ class NetConfig(object): """ raise NotImplemented("add_ovs_patch_port is not implemented.") + def add_ib_interface(self, ib_interface): + """Add an InfiniBand Interface object to the net config object. + + :param interface: The InfiniBand Interface object to add. + """ + raise NotImplemented("add_ib_interface is not implemented.") + def apply(self, cleanup=False): """Apply the network configuration. diff --git a/os_net_config/impl_ifcfg.py b/os_net_config/impl_ifcfg.py index fbd1c3a..d49b57b 100644 --- a/os_net_config/impl_ifcfg.py +++ b/os_net_config/impl_ifcfg.py @@ -39,6 +39,10 @@ def ivs_config_path(): return "/etc/sysconfig/ivs" +def nfvswitch_config_path(): + return "/etc/sysconfig/nfvswitch" + + def route_config_path(name): return "/etc/sysconfig/network-scripts/route-%s" % name @@ -58,12 +62,16 @@ class IfcfgNetConfig(os_net_config.NetConfig): super(IfcfgNetConfig, self).__init__(noop, root_dir) self.interface_data = {} self.ivsinterface_data = {} + self.nfvswitch_intiface_data = {} + self.nfvswitch_cpus = None self.vlan_data = {} self.route_data = {} self.route6_data = {} self.bridge_data = {} self.linuxbridge_data = {} self.linuxbond_data = {} + self.ib_interface_data = {} + self.linuxteam_data = {} self.member_names = {} self.renamed_interfaces = {} self.bond_primary_ifaces = {} @@ -101,16 +109,28 @@ class IfcfgNetConfig(os_net_config.NetConfig): data += "PHYSDEV=%s\n" % base_opt.linux_bond_name elif isinstance(base_opt, objects.IvsInterface): data += "TYPE=IVSIntPort\n" + elif isinstance(base_opt, objects.NfvswitchInternal): + data += "TYPE=NFVSWITCHIntPort\n" + elif isinstance(base_opt, objects.IbInterface): + data += "TYPE=Infiniband\n" elif re.match('\w+\.\d+$', base_opt.name): data += "VLAN=yes\n" if base_opt.linux_bond_name: data += "MASTER=%s\n" % base_opt.linux_bond_name data += "SLAVE=yes\n" + if base_opt.linux_team_name: + data += "TEAM_MASTER=%s\n" % base_opt.linux_team_name + if base_opt.primary: + data += "TEAM_PORT_CONFIG='{\"prio\": 100}'\n" if base_opt.ivs_bridge_name: data += "DEVICETYPE=ivs\n" data += "IVS_BRIDGE=%s\n" % base_opt.ivs_bridge_name + if base_opt.nfvswitch_bridge_name: + data += "DEVICETYPE=nfvswitch\n" + data += "NFVSWITCH_BRIDGE=%s\n" % base_opt.nfvswitch_bridge_name if base_opt.ovs_port: - data += "DEVICETYPE=ovs\n" + if not isinstance(base_opt, objects.LinuxTeam): + data += "DEVICETYPE=ovs\n" if base_opt.bridge_name: if isinstance(base_opt, objects.Vlan): data += "TYPE=OVSIntPort\n" @@ -177,6 +197,19 @@ class IfcfgNetConfig(os_net_config.NetConfig): self.member_names[base_opt.name] = members if base_opt.bonding_options: data += "BONDING_OPTS=\"%s\"\n" % base_opt.bonding_options + elif isinstance(base_opt, objects.LinuxTeam): + if base_opt.primary_interface_name: + primary_name = base_opt.primary_interface_name + primary_mac = utils.interface_mac(primary_name) + data += "MACADDR=\"%s\"\n" % primary_mac + if base_opt.use_dhcp: + data += "BOOTPROTO=dhcp\n" + if base_opt.members: + members = [member.name for member in base_opt.members] + self.member_names[base_opt.name] = members + data += "DEVICETYPE=Team\n" + if base_opt.bonding_options: + data += "TEAM_CONFIG='%s'\n" % base_opt.bonding_options elif isinstance(base_opt, objects.OvsTunnel): ovs_extra.extend(base_opt.ovs_extra) data += "DEVICETYPE=ovs\n" @@ -311,6 +344,19 @@ class IfcfgNetConfig(os_net_config.NetConfig): if ivs_interface.routes: self._add_routes(ivs_interface.name, ivs_interface.routes) + def add_nfvswitch_internal(self, nfvswitch_internal): + """Add a nfvswitch_internal interface object to the net config object. + + :param nfvswitch_internal: The nfvswitch_internal object to add. + """ + iface_name = nfvswitch_internal.name + logger.info('adding nfvswitch_internal interface: %s' % iface_name) + data = self._add_common(nfvswitch_internal) + logger.debug('nfvswitch_internal interface data: %s' % data) + self.nfvswitch_intiface_data[iface_name] = data + if nfvswitch_internal.routes: + self._add_routes(iface_name, nfvswitch_internal.routes) + def add_bridge(self, bridge): """Add an OvsBridge object to the net config object. @@ -347,6 +393,16 @@ class IfcfgNetConfig(os_net_config.NetConfig): """ pass + def add_nfvswitch_bridge(self, bridge): + """Add a NFVSwitchBridge object to the net config object. + + NFVSwitch can only support one virtual switch per node, + using "nfvswitch" as its name. As long as the nfvswitch service + is running, the nfvswitch virtual switch will be available. + :param bridge: The NfvswitchBridge object to add. + """ + self.nfvswitch_cpus = bridge.cpus + def add_bond(self, bond): """Add an OvsBond object to the net config object. @@ -371,6 +427,18 @@ class IfcfgNetConfig(os_net_config.NetConfig): if bond.routes: self._add_routes(bond.name, bond.routes) + def add_linux_team(self, team): + """Add a LinuxTeam object to the net config object. + + :param team: The LinuxTeam object to add. + """ + logger.info('adding linux team: %s' % team.name) + data = self._add_common(team) + logger.debug('team data: %s' % data) + self.linuxteam_data[team.name] = data + if team.routes: + self._add_routes(team.name, team.routes) + def add_ovs_tunnel(self, tunnel): """Add a OvsTunnel object to the net config object. @@ -391,6 +459,23 @@ class IfcfgNetConfig(os_net_config.NetConfig): logger.debug('ovs patch port data: %s' % data) self.interface_data[ovs_patch_port.name] = data + def add_ib_interface(self, ib_interface): + """Add an InfiniBand interface object to the net config object. + + :param ib_interface: The InfiniBand interface object to add. + """ + logger.info('adding ib_interface: %s' % ib_interface.name) + data = self._add_common(ib_interface) + logger.debug('ib_interface data: %s' % data) + self.ib_interface_data[ib_interface.name] = data + if ib_interface.routes: + self._add_routes(ib_interface.name, ib_interface.routes) + + if ib_interface.renamed: + logger.info("InfiniBand interface %s being renamed to %s" + % (ib_interface.hwname, ib_interface.name)) + self.renamed_interfaces[ib_interface.hwname] = ib_interface.name + def generate_ivs_config(self, ivs_uplinks, ivs_interfaces): """Generate configuration content for ivs.""" @@ -411,6 +496,29 @@ class IfcfgNetConfig(os_net_config.NetConfig): % (uplink_str, intf_str)) return data + def generate_nfvswitch_config(self, nfvswitch_ifaces, + nfvswitch_internal_ifaces): + """Generate configuration content for nfvswitch.""" + + cpu_str = "" + if self.nfvswitch_cpus: + cpu_str = " -c " + self.nfvswitch_cpus + + ifaces = [] + for iface in nfvswitch_ifaces: + ifaces.append(' -u ') + ifaces.append(iface) + iface_str = ''.join(ifaces) + + ifaces = [] + for iface in nfvswitch_internal_ifaces: + ifaces.append(' -m ') + ifaces.append(iface) + internal_str = ''.join(ifaces) + + data = ("SETUP_ARGS=\"%s%s%s\"" % (cpu_str, iface_str, internal_str)) + return data + def apply(self, cleanup=False, activate=True): """Apply the network configuration. @@ -431,10 +539,13 @@ class IfcfgNetConfig(os_net_config.NetConfig): restart_vlans = [] restart_bridges = [] restart_linux_bonds = [] + restart_linux_teams = [] update_files = {} all_file_names = [] ivs_uplinks = [] # ivs physical uplinks ivs_interfaces = [] # ivs internal ports + nfvswitch_interfaces = [] # nfvswitch physical interfaces + nfvswitch_internal_ifaces = [] # nfvswitch internal/management ports for interface_name, iface_data in self.interface_data.iteritems(): route_data = self.route_data.get(interface_name, '') @@ -447,6 +558,8 @@ class IfcfgNetConfig(os_net_config.NetConfig): all_file_names.append(route6_path) if "IVS_BRIDGE" in iface_data: ivs_uplinks.append(interface_name) + if "NFVSWITCH_BRIDGE" in iface_data: + nfvswitch_interfaces.append(interface_name) all_file_names.append(route6_path) if (utils.diff(interface_path, iface_data) or utils.diff(route_path, route_data) or @@ -481,6 +594,27 @@ 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(): + 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) + route_path = self.root_dir + route_config_path(iface_name) + route6_path = self.root_dir + route6_config_path(iface_name) + all_file_names.append(iface_path) + all_file_names.append(route_path) + all_file_names.append(route6_path) + nfvswitch_internal_ifaces.append(iface_name) + if (utils.diff(iface_path, iface_data) or + utils.diff(route_path, route_data)): + restart_interfaces.append(iface_name) + restart_interfaces.extend(self.child_members(iface_name)) + update_files[iface_path] = iface_data + update_files[route_path] = route_data + update_files[route6_path] = route6_data + else: + logger.info('No changes required for nfvswitch interface: %s' % + iface_name) + for vlan_name, vlan_data in self.vlan_data.iteritems(): route_data = self.route_data.get(vlan_name, '') route6_data = self.route6_data.get(vlan_name, '') @@ -541,6 +675,27 @@ 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(): + 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) + team_route_path = self.root_dir + route_config_path(team_name) + team_route6_path = self.root_dir + route6_config_path(team_name) + all_file_names.append(team_path) + all_file_names.append(team_route_path) + all_file_names.append(team_route6_path) + if (utils.diff(team_path, team_data) or + utils.diff(team_route_path, route_data) or + utils.diff(team_route6_path, route6_data)): + restart_linux_teams.append(team_name) + restart_interfaces.extend(self.child_members(team_name)) + update_files[team_path] = team_data + update_files[team_route_path] = route_data + update_files[team_route6_path] = route6_data + else: + logger.info('No changes required for linux team: %s' % + team_name) + for bond_name, bond_data in self.linuxbond_data.iteritems(): route_data = self.route_data.get(bond_name, '') route6_data = self.route6_data.get(bond_name, '') @@ -562,6 +717,32 @@ class IfcfgNetConfig(os_net_config.NetConfig): logger.info('No changes required for linux bond: %s' % bond_name) + # Infiniband interfaces are handled similarly to Ethernet interfaces + for interface_name, iface_data in self.ib_interface_data.iteritems(): + 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) + route_path = self.root_dir + route_config_path(interface_name) + route6_path = self.root_dir + route6_config_path(interface_name) + all_file_names.append(interface_path) + all_file_names.append(route_path) + all_file_names.append(route6_path) + # TODO(dsneddon) determine if InfiniBand can be used with IVS + if "IVS_BRIDGE" in iface_data: + ivs_uplinks.append(interface_name) + all_file_names.append(route6_path) + if (utils.diff(interface_path, iface_data) or + utils.diff(route_path, route_data) or + utils.diff(route6_path, route6_data)): + restart_interfaces.append(interface_name) + restart_interfaces.extend(self.child_members(interface_name)) + update_files[interface_path] = iface_data + update_files[route_path] = route_data + update_files[route6_path] = route6_data + else: + logger.info('No changes required for InfiniBand iface: %s' % + interface_name) + if cleanup: for ifcfg_file in glob.iglob(cleanup_pattern()): if ifcfg_file not in all_file_names: @@ -582,6 +763,9 @@ class IfcfgNetConfig(os_net_config.NetConfig): for linux_bond in restart_linux_bonds: self.ifdown(linux_bond) + for linux_team in restart_linux_teams: + self.ifdown(linux_team) + for bridge in restart_bridges: self.ifdown(bridge, iftype='bridge') @@ -596,9 +780,15 @@ class IfcfgNetConfig(os_net_config.NetConfig): data = self.generate_ivs_config(ivs_uplinks, ivs_interfaces) self.write_config(location, data) + if nfvswitch_interfaces or nfvswitch_internal_ifaces: + location = nfvswitch_config_path() + data = self.generate_nfvswitch_config(nfvswitch_interfaces, + nfvswitch_internal_ifaces) + self.write_config(location, data) + if activate: - for linux_bond in restart_linux_bonds: - self.ifup(linux_bond) + for linux_team in restart_linux_teams: + self.ifup(linux_team) for bridge in restart_bridges: self.ifup(bridge, iftype='bridge') @@ -606,6 +796,9 @@ class IfcfgNetConfig(os_net_config.NetConfig): for interface in restart_interfaces: self.ifup(interface) + for linux_bond in restart_linux_bonds: + self.ifup(linux_bond) + for bond in self.bond_primary_ifaces: self.ovs_appctl('bond/set-active-slave', bond, self.bond_primary_ifaces[bond]) @@ -623,6 +816,19 @@ class IfcfgNetConfig(os_net_config.NetConfig): self.execute(msg, '/usr/bin/systemctl', 'restart', 'ivs') + if nfvswitch_interfaces or nfvswitch_internal_ifaces: + logger.info("Attach to nfvswitch with " + "interfaces: %s, " + "internal interfaces: %s" % + (nfvswitch_interfaces, nfvswitch_internal_ifaces)) + for nfvswitch_interface in nfvswitch_interfaces: + self.ifup(nfvswitch_interface) + for nfvswitch_internal in nfvswitch_internal_ifaces: + self.ifup(nfvswitch_internal) + msg = "Restart nfvswitch" + self.execute(msg, '/usr/bin/systemctl', + 'restart', 'nfvswitch') + for vlan in restart_vlans: self.ifup(vlan) diff --git a/os_net_config/objects.py b/os_net_config/objects.py index 9815832..9b5523f 100644 --- a/os_net_config/objects.py +++ b/os_net_config/objects.py @@ -23,7 +23,7 @@ from os_net_config import utils logger = logging.getLogger(__name__) -_NUMBERED_NICS = None +_MAPPED_NICS = None class InvalidConfigException(ValueError): @@ -42,16 +42,24 @@ def object_from_json(json): return OvsBond.from_json(json) elif obj_type == "linux_bond": return LinuxBond.from_json(json) + elif obj_type == "team": + return LinuxTeam.from_json(json) elif obj_type == "linux_bridge": return LinuxBridge.from_json(json) elif obj_type == "ivs_bridge": return IvsBridge.from_json(json) elif obj_type == "ivs_interface": return IvsInterface.from_json(json) + elif obj_type == "nfvswitch_bridge": + return NfvswitchBridge.from_json(json) + elif obj_type == "nfvswitch_internal": + return NfvswitchInternal.from_json(json) elif obj_type == "ovs_tunnel": return OvsTunnel.from_json(json) elif obj_type == "ovs_patch_port": return OvsPatchPort.from_json(json) + elif obj_type == "ib_interface": + return IbInterface.from_json(json) def _get_required_field(json, name, object_name): @@ -63,21 +71,17 @@ def _get_required_field(json, name, object_name): return field -def _numbered_nics(nic_mapping=None): +def _mapped_nics(nic_mapping=None): mapping = nic_mapping or {} - global _NUMBERED_NICS - if _NUMBERED_NICS: - return _NUMBERED_NICS - _NUMBERED_NICS = {} - count = 0 + global _MAPPED_NICS + if _MAPPED_NICS: + return _MAPPED_NICS + _MAPPED_NICS = {} active_nics = utils.ordered_active_nics() - for nic in active_nics: - count += 1 - nic_alias = "nic%i" % count - nic_mapped = mapping.get(nic_alias, nic) - - # The mapping is either invalid, or specifies a mac + for nic_alias, nic_mapped in mapping.items(): if nic_mapped not in active_nics: + # The mapping is either invalid, or specifies a mac + is_mapping_valid = False for active in active_nics: try: active_mac = utils.interface_mac(active) @@ -86,25 +90,39 @@ def _numbered_nics(nic_mapping=None): if nic_mapped == active_mac: logger.debug("%s matches device %s" % (nic_mapped, active)) nic_mapped = active + is_mapping_valid = True break - else: + + if not is_mapping_valid: # The mapping can't specify a non-active or non-existent nic - logger.warning('interface %s is not in an active nic (%s)' + logger.warning('interface %s is not an active nic (%s)' % (nic_mapped, ', '.join(active_nics))) continue # Duplicate mappings are not allowed - if nic_mapped in _NUMBERED_NICS.values(): + if nic_mapped in _MAPPED_NICS.values(): msg = ('interface %s already mapped, ' 'check mapping file for duplicates' % nic_mapped) raise InvalidConfigException(msg) - _NUMBERED_NICS[nic_alias] = nic_mapped + _MAPPED_NICS[nic_alias] = nic_mapped logger.info("%s mapped to: %s" % (nic_alias, nic_mapped)) - if not _NUMBERED_NICS: + + # Add default numbered mappings, but do not overwrite existing entries + for nic_mapped in set(active_nics).difference(set(_MAPPED_NICS.values())): + nic_alias = "nic%i" % (active_nics.index(nic_mapped) + 1) + if nic_alias in _MAPPED_NICS: + logger.warning("no mapping for interface %s because " + "%s is mapped to %s" + % (nic_mapped, nic_alias, _MAPPED_NICS[nic_alias])) + else: + _MAPPED_NICS[nic_alias] = nic_mapped + logger.info("%s mapped to: %s" % (nic_alias, nic_mapped)) + + if not _MAPPED_NICS: logger.warning('No active nics found.') - return _NUMBERED_NICS + return _MAPPED_NICS class Route(object): @@ -150,18 +168,18 @@ class _BaseOpts(object): addresses = addresses or [] routes = routes or [] dns_servers = dns_servers or [] - numbered_nic_names = _numbered_nics(nic_mapping) + mapped_nic_names = _mapped_nics(nic_mapping) self.hwaddr = None self.hwname = None self.renamed = False - if name in numbered_nic_names: + if name in mapped_nic_names: if persist_mapping: self.name = name - self.hwname = numbered_nic_names[name] + self.hwname = mapped_nic_names[name] self.hwaddr = utils.interface_mac(self.hwname) self.renamed = True else: - self.name = numbered_nic_names[name] + self.name = mapped_nic_names[name] else: self.name = name @@ -177,7 +195,9 @@ class _BaseOpts(object): self.bridge_name = None # internal self.linux_bridge_name = None # internal self.ivs_bridge_name = None # internal + self.nfvswitch_bridge_name = None # internal self.linux_bond_name = None # internal + self.linux_team_name = None # internal self.ovs_port = False # internal self.primary_interface_name = None # internal @@ -287,9 +307,9 @@ class Vlan(_BaseOpts): dns_servers) self.vlan_id = int(vlan_id) - numbered_nic_names = _numbered_nics(nic_mapping) - if device in numbered_nic_names: - self.device = numbered_nic_names[device] + mapped_nic_names = _mapped_nics(nic_mapping) + if device in mapped_nic_names: + self.device = mapped_nic_names[device] else: self.device = device @@ -328,6 +348,32 @@ class IvsInterface(_BaseOpts): return IvsInterface(vlan_id, name, *opts) +class NfvswitchInternal(_BaseOpts): + """Base class for nfvswitch internal interfaces.""" + + 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): + addresses = addresses or [] + routes = routes or [] + dns_servers = dns_servers or [] + name_vlan = '%s%i' % (name, vlan_id) + super(NfvswitchInternal, self).__init__(name_vlan, use_dhcp, + use_dhcpv6, addresses, routes, + mtu, primary, nic_mapping, + persist_mapping, defroute, + dhclient_args, dns_servers) + self.vlan_id = int(vlan_id) + + @staticmethod + def from_json(json): + name = json.get('name') + vlan_id = _get_required_field(json, 'vlan_id', 'NfvswitchInternal') + opts = _BaseOpts.base_opts_from_json(json) + return NfvswitchInternal(vlan_id, name, *opts) + + class OvsBridge(_BaseOpts): """Base class for OVS bridges.""" @@ -505,6 +551,132 @@ class IvsBridge(_BaseOpts): dns_servers=dns_servers) +class NfvswitchBridge(_BaseOpts): + """Base class for NFVSwitch bridges. + + NFVSwitch is a virtual switch for Linux. + It is compatible with the KVM hypervisor and uses DPDK for packet + forwarding. + """ + + 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, cpus=""): + addresses = addresses or [] + routes = routes or [] + members = members or [] + dns_servers = dns_servers or [] + super(NfvswitchBridge, self).__init__(name, use_dhcp, use_dhcpv6, + addresses, routes, mtu, False, + nic_mapping, persist_mapping, + defroute, dhclient_args, + dns_servers) + self.cpus = cpus + self.members = members + for member in self.members: + if isinstance(member, OvsBond) or isinstance(member, LinuxBond): + msg = 'NFVSwitch does not support bond interfaces.' + raise InvalidConfigException(msg) + member.nfvswitch_bridge_name = name + member.ovs_port = False + self.primary_interface_name = None + + @staticmethod + def from_json(json): + name = 'nfvswitch' + (use_dhcp, use_dhcpv6, addresses, routes, mtu, nic_mapping, + persist_mapping, defroute, dhclient_args, + dns_servers) = _BaseOpts.base_opts_from_json( + json, include_primary=False) + + # members + members = [] + members_json = json.get('members') + if members_json: + if isinstance(members_json, list): + for member in members_json: + members.append(object_from_json(member)) + else: + msg = 'Members must be a list.' + raise InvalidConfigException(msg) + + cpus = '' + cpus_json = json.get('cpus') + if cpus_json: + if isinstance(cpus_json, basestring): + cpus = cpus_json + else: + msg = '"cpus" must be a string of numbers separated by commas.' + raise InvalidConfigException(msg) + else: + msg = 'Config "cpus" is mandatory.' + raise InvalidConfigException(msg) + + return NfvswitchBridge(name, use_dhcp=use_dhcp, use_dhcpv6=use_dhcpv6, + addresses=addresses, routes=routes, mtu=mtu, + members=members, nic_mapping=nic_mapping, + persist_mapping=persist_mapping, + defroute=defroute, dhclient_args=dhclient_args, + dns_servers=dns_servers, cpus=cpus) + + +class LinuxTeam(_BaseOpts): + """Base class for Linux bonds using teamd.""" + + 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): + addresses = addresses or [] + routes = routes or [] + members = members or [] + dns_servers = dns_servers or [] + super(LinuxTeam, self).__init__(name, use_dhcp, use_dhcpv6, addresses, + routes, mtu, primary, nic_mapping, + persist_mapping, defroute, + dhclient_args, dns_servers) + self.members = members + self.bonding_options = bonding_options + for member in self.members: + member.linux_team_name = name + if member.primary: + if self.primary_interface_name: + msg = 'Only one primary interface allowed per team.' + raise InvalidConfigException(msg) + if member.primary_interface_name: + self.primary_interface_name = member.primary_interface_name + else: + self.primary_interface_name = member.name + + @staticmethod + def from_json(json): + 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( + json, include_primary=False) + bonding_options = json.get('bonding_options') + members = [] + + # members + members_json = json.get('members') + if members_json: + if isinstance(members_json, list): + for member in members_json: + members.append(object_from_json(member)) + else: + msg = 'Members must be a list.' + raise InvalidConfigException(msg) + + return LinuxTeam(name, use_dhcp=use_dhcp, use_dhcpv6=use_dhcpv6, + addresses=addresses, routes=routes, mtu=mtu, + members=members, bonding_options=bonding_options, + nic_mapping=nic_mapping, + persist_mapping=persist_mapping, defroute=defroute, + dhclient_args=dhclient_args, dns_servers=dns_servers) + + class LinuxBond(_BaseOpts): """Base class for Linux bonds.""" @@ -689,3 +861,25 @@ class OvsPatchPort(_BaseOpts): 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) + + +class IbInterface(_BaseOpts): + """Base class for InfiniBand network interfaces.""" + + 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): + 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) + + @staticmethod + def from_json(json): + name = _get_required_field(json, 'name', 'IbInterface') + opts = _BaseOpts.base_opts_from_json(json) + return IbInterface(name, *opts) diff --git a/os_net_config/tests/base.py b/os_net_config/tests/base.py index d5adc66..55e0b61 100644 --- a/os_net_config/tests/base.py +++ b/os_net_config/tests/base.py @@ -29,19 +29,19 @@ _TRUE_VALUES = ('True', 'true', '1', 'yes') class TestCase(testtools.TestCase): """Test case base class for all unit tests.""" - stub_numbered_nics = True + stub_mapped_nics = True def setUp(self): """Run before each test method to initialize test environment.""" super(TestCase, self).setUp() self.stubs = stubout.StubOutForTesting() - self.stubbed_numbered_nics = {} + self.stubbed_mapped_nics = {} - def dummy_numbered_nics(nic_mapping=None): - return self.stubbed_numbered_nics - if self.stub_numbered_nics: - self.stubs.Set(objects, '_numbered_nics', dummy_numbered_nics) + def dummy_mapped_nics(nic_mapping=None): + return self.stubbed_mapped_nics + if self.stub_mapped_nics: + self.stubs.Set(objects, '_mapped_nics', dummy_mapped_nics) test_timeout = os.environ.get('OS_TEST_TIMEOUT', 0) try: diff --git a/os_net_config/tests/test_cli.py b/os_net_config/tests/test_cli.py index 98477fc..e598dcb 100644 --- a/os_net_config/tests/test_cli.py +++ b/os_net_config/tests/test_cli.py @@ -146,3 +146,21 @@ class TestCli(base.TestCase): stdout_yaml, stderr = self.run_cli('ARG0 --provider=ifcfg --noop ' '-c %s --detailed-exit-codes' % interface_yaml, exitcodes=(0,)) + + def test_nfvswitch_noop_output(self): + nfvswitch_yaml = os.path.join(SAMPLE_BASE, 'nfvswitch.yaml') + nfvswitch_json = os.path.join(SAMPLE_BASE, 'nfvswitch.json') + stdout_yaml, stderr = self.run_cli('ARG0 --provider=ifcfg --noop ' + '-c %s' % nfvswitch_yaml) + self.assertEqual('', stderr) + stdout_json, stderr = self.run_cli('ARG0 --provider=ifcfg --noop ' + '-c %s' % nfvswitch_json) + self.assertEqual('', stderr) + sanity_devices = ['DEVICE=nic2', + 'DEVICE=nic3', + 'DEVICE=api201', + 'DEVICE=storage202', + 'DEVICETYPE=nfvswitch'] + for dev in sanity_devices: + self.assertIn(dev, stdout_yaml) + self.assertEqual(stdout_yaml, stdout_json) diff --git a/os_net_config/tests/test_impl_ifcfg.py b/os_net_config/tests/test_impl_ifcfg.py index 71ad9ae..9b24717 100644 --- a/os_net_config/tests/test_impl_ifcfg.py +++ b/os_net_config/tests/test_impl_ifcfg.py @@ -60,12 +60,33 @@ BOOTPROTO=none _V4_IFCFG_MAPPED = _V4_IFCFG.replace('em1', 'nic1') + "HWADDR=a1:b2:c3:d4:e5\n" + +_BASE_IB_IFCFG = """# This file is autogenerated by os-net-config +DEVICE=ib0 +ONBOOT=yes +HOTPLUG=no +NM_CONTROLLED=no +PEERDNS=no +TYPE=Infiniband +""" + +_V4_IB_IFCFG = _BASE_IB_IFCFG + """BOOTPROTO=static +IPADDR=192.168.1.2 +NETMASK=255.255.255.0 +""" + _V4_IFCFG_MULTIPLE = _V4_IFCFG + """IPADDR1=192.168.1.3 NETMASK1=255.255.255.255 IPADDR2=10.0.0.2 NETMASK2=255.0.0.0 """ +_IB_V4_IFCFG_MULTIPLE = _V4_IB_IFCFG + """IPADDR1=192.168.1.3 +NETMASK1=255.255.255.255 +IPADDR2=10.0.0.2 +NETMASK2=255.0.0.0 +""" + _V6_IFCFG = _BASE_IFCFG + """IPV6INIT=yes IPV6_AUTOCONF=no IPV6ADDR=2001:abc:a::/64 @@ -223,11 +244,24 @@ BOOTPROTO=dhcp """ +_LINUX_TEAM_DHCP = """# This file is autogenerated by os-net-config +DEVICE=team0 +ONBOOT=yes +HOTPLUG=no +NM_CONTROLLED=no +BOOTPROTO=dhcp +DEVICETYPE=Team +""" + + _LINUX_BOND_INTERFACE = _BASE_IFCFG + """MASTER=bond0 SLAVE=yes BOOTPROTO=none """ +_LINUX_TEAM_INTERFACE = _BASE_IFCFG + """TEAM_MASTER=team0 +BOOTPROTO=none +""" _IVS_UPLINK = """# This file is autogenerated by os-net-config DEVICE=em1 @@ -258,6 +292,34 @@ NETMASK=255.255.255.0 _IVS_CONFIG = ('DAEMON_ARGS=\"--hitless --certificate /etc/ivs ' '--inband-vlan 4092 -u em1 --internal-port=storage5\"') +_NFVSWITCH_INTERFACE = """# This file is autogenerated by os-net-config +DEVICE=em1 +ONBOOT=yes +HOTPLUG=no +NM_CONTROLLED=no +PEERDNS=no +DEVICETYPE=nfvswitch +NFVSWITCH_BRIDGE=nfvswitch +BOOTPROTO=none +""" + +_NFVSWITCH_INTERNAL = """# This file is autogenerated by os-net-config +DEVICE=storage5 +ONBOOT=yes +HOTPLUG=no +NM_CONTROLLED=no +PEERDNS=no +TYPE=NFVSWITCHIntPort +DEVICETYPE=nfvswitch +NFVSWITCH_BRIDGE=nfvswitch +MTU=1500 +BOOTPROTO=static +IPADDR=172.16.2.7 +NETMASK=255.255.255.0 +""" + +_NFVSWITCH_CONFIG = ('SETUP_ARGS=\" -c 2,3,4,5 -u em1 -m storage5\"') + _OVS_IFCFG_PATCH_PORT = """# This file is autogenerated by os-net-config DEVICE=br-pub-patch ONBOOT=yes @@ -270,6 +332,17 @@ OVS_BRIDGE=br-ex OVS_PATCH_PEER=br-ex-patch """ +_LINUX_TEAM_PRIMARY_IFACE = """# This file is autogenerated by os-net-config +DEVICE=em1 +ONBOOT=yes +HOTPLUG=no +NM_CONTROLLED=no +PEERDNS=no +TEAM_MASTER=team1 +TEAM_PORT_CONFIG='{"prio": 100}' +BOOTPROTO=none +""" + class TestIfcfgNetConfig(base.TestCase): @@ -290,6 +363,9 @@ class TestIfcfgNetConfig(base.TestCase): def get_linux_bond_config(self, name='bond0'): return self.provider.linuxbond_data[name] + def get_linux_team_config(self, name='team0'): + return self.provider.linuxteam_data[name] + def get_route_config(self, name='em1'): return self.provider.route_data.get(name, '') @@ -353,7 +429,7 @@ class TestIfcfgNetConfig(base.TestCase): self.stubs.Set(utils, 'interface_mac', test_interface_mac) nic_mapping = {'nic1': 'em1'} - self.stubbed_numbered_nics = nic_mapping + self.stubbed_mapped_nics = nic_mapping v4_addr = objects.Address('192.168.1.2/24') interface = objects.Interface('nic1', addresses=[v4_addr], nic_mapping=nic_mapping, @@ -505,6 +581,35 @@ class TestIfcfgNetConfig(base.TestCase): data = self.provider.generate_ivs_config(['em1'], ['storage5']) self.assertEqual(_IVS_CONFIG, data) + def test_network_nfvswitch_with_interfaces_and_internal_interfaces(self): + interface = objects.Interface('em1') + v4_addr = objects.Address('172.16.2.7/24') + nfvswitch_internal = objects.NfvswitchInternal(vlan_id=5, + name='storage', + addresses=[v4_addr]) + iface_name = nfvswitch_internal.name + bridge = objects.NfvswitchBridge(members=[interface, + nfvswitch_internal], + cpus="2,3,4,5") + self.provider.add_interface(interface) + self.provider.add_nfvswitch_internal(nfvswitch_internal) + self.provider.add_nfvswitch_bridge(bridge) + self.assertEqual(_NFVSWITCH_INTERFACE, self.get_interface_config()) + self.assertEqual(_NFVSWITCH_INTERNAL, + self.provider.nfvswitch_intiface_data[iface_name]) + data = self.provider.generate_nfvswitch_config(['em1'], ['storage5']) + self.assertEqual(_NFVSWITCH_CONFIG, data) + + def test_add_ib_interface_with_v4_multiple(self): + addresses = [objects.Address('192.168.1.2/24'), + objects.Address('192.168.1.3/32'), + objects.Address('10.0.0.2/8')] + ib_interface = objects.IbInterface('ib0', addresses=addresses) + self.provider.add_interface(ib_interface) + self.assertEqual(_IB_V4_IFCFG_MULTIPLE, + self.get_interface_config('ib0')) + self.assertEqual('', self.get_route_config()) + def test_add_vlan(self): vlan = objects.Vlan('em1', 5) self.provider.add_vlan(vlan) @@ -573,6 +678,19 @@ BOOTPROTO=none self.assertEqual(_LINUX_BOND_INTERFACE, self.get_interface_config('em1')) + def test_linux_team(self): + interface1 = objects.Interface('em1') + interface2 = objects.Interface('em2') + team = objects.LinuxTeam('team0', use_dhcp=True, + members=[interface1, interface2]) + self.provider.add_linux_team(team) + self.provider.add_interface(interface1) + self.provider.add_interface(interface2) + self.assertEqual(_LINUX_TEAM_DHCP, + self.get_linux_team_config('team0')) + self.assertEqual(_LINUX_TEAM_INTERFACE, + self.get_interface_config('em1')) + def test_interface_defroute(self): interface1 = objects.Interface('em1') interface2 = objects.Interface('em2', defroute=False) @@ -775,6 +893,26 @@ class TestIfcfgNetConfigApply(base.TestCase): self.provider.add_interface(interface) self.provider.add_bridge(bridge) self.provider.apply() + self.assertIn('em1', self.ifup_interface_names) + + # test infiniband interfaces act as proper bridge members + ib_interface = objects.IbInterface('ib0') + bridge = objects.OvsBridge('br-ctlplane', use_dhcp=True, + members=[ib_interface]) + self.provider.add_interface(ib_interface) + self.provider.add_bridge(bridge) + self.provider.apply() + self.assertIn('ib0', self.ifup_interface_names) + self.assertIn('br-ctlplane', self.ifup_interface_names) + + # changing the bridge should restart the interface too + self.ifup_interface_names = [] + bridge = objects.OvsBridge('br-ctlplane', use_dhcp=False, + members=[ib_interface]) + self.provider.add_interface(interface) + self.provider.add_bridge(bridge) + self.provider.apply() + self.assertIn('ib0', self.ifup_interface_names) # setup and apply a bond on a bridge self.ifup_interface_names = [] diff --git a/os_net_config/tests/test_objects.py b/os_net_config/tests/test_objects.py index 7d8d3b3..7e8441c 100644 --- a/os_net_config/tests/test_objects.py +++ b/os_net_config/tests/test_objects.py @@ -131,9 +131,9 @@ class TestInterface(base.TestCase): self.assertEqual(["1.2.3.4"], interface1.dns_servers) def test_from_json_dhcp_nic1(self): - def dummy_numbered_nics(nic_mapping=None): + def dummy_mapped_nics(nic_mapping=None): return {"nic1": "em3"} - self.stubs.Set(objects, '_numbered_nics', dummy_numbered_nics) + self.stubs.Set(objects, '_mapped_nics', dummy_mapped_nics) data = '{"type": "interface", "name": "nic1", "use_dhcp": true}' interface = objects.object_from_json(json.loads(data)) @@ -179,9 +179,9 @@ class TestVlan(base.TestCase): self.assertTrue(vlan.use_dhcp) def test_from_json_dhcp_nic1(self): - def dummy_numbered_nics(nic_mapping=None): + def dummy_mapped_nics(nic_mapping=None): return {"nic1": "em4"} - self.stubs.Set(objects, '_numbered_nics', dummy_numbered_nics) + self.stubs.Set(objects, '_mapped_nics', dummy_mapped_nics) data = '{"type": "vlan", "device": "nic1", "vlan_id": 16,' \ '"use_dhcp": true}' @@ -213,9 +213,9 @@ class TestBridge(base.TestCase): self.assertEqual("br-foo", interface1.bridge_name) def test_from_json_dhcp_with_nic1(self): - def dummy_numbered_nics(nic_mapping=None): + def dummy_mapped_nics(nic_mapping=None): return {"nic1": "em5"} - self.stubs.Set(objects, '_numbered_nics', dummy_numbered_nics) + self.stubs.Set(objects, '_mapped_nics', dummy_mapped_nics) data = """{ "type": "ovs_bridge", @@ -289,9 +289,9 @@ class TestLinuxBridge(base.TestCase): self.assertEqual("br-foo", interface1.linux_bridge_name) def test_from_json_dhcp_with_nic1(self): - def dummy_numbered_nics(nic_mapping=None): + def dummy_mapped_nics(nic_mapping=None): return {"nic1": "em5"} - self.stubs.Set(objects, '_numbered_nics', dummy_numbered_nics) + self.stubs.Set(objects, '_mapped_nics', dummy_mapped_nics) data = """{ "type": "linux_bridge", @@ -398,6 +398,87 @@ class TestIvsBridge(base.TestCase): self.assertIn(expected, err) +class TestNfvswitchBridge(base.TestCase): + + def test_from_json(self): + data = """{ +"type": "nfvswitch_bridge", +"cpus": "2,3,4,5", +"members": [ + {"type": "interface", "name": "nic2"} + ] +} +""" + bridge = objects.object_from_json(json.loads(data)) + self.assertEqual("nfvswitch", bridge.name) + self.assertEqual("2,3,4,5", bridge.cpus) + interface1 = bridge.members[0] + self.assertEqual("nic2", interface1.name) + self.assertEqual(False, interface1.ovs_port) + self.assertEqual("nfvswitch", interface1.nfvswitch_bridge_name) + + +class TestNfvswitchInterface(base.TestCase): + + def test_interface_from_json(self): + data = """{ +"type": "nfvswitch_bridge", +"cpus": "2,3,4,5", +"members": [ + {"type": "interface","name": "nic1"}, + {"type": "interface","name": "nic2"} + ] +} +""" + bridge = objects.object_from_json(json.loads(data)) + self.assertEqual("nfvswitch", bridge.name) + self.assertEqual("2,3,4,5", bridge.cpus) + interface1 = bridge.members[0] + self.assertEqual("nic1", interface1.name) + interface2 = bridge.members[1] + self.assertEqual("nic2", interface2.name) + self.assertEqual(False, interface2.ovs_port) + self.assertEqual("nfvswitch", interface1.nfvswitch_bridge_name) + + def test_nfvswitch_internal_from_json(self): + data = """{ +"type": "nfvswitch_bridge", +"cpus": "2,3,4,5", +"members": [ + {"type": "nfvswitch_internal", "name": "storage", "vlan_id": 202}, + {"type": "nfvswitch_internal", "name": "api", "vlan_id": 201} + ] +} +""" + bridge = objects.object_from_json(json.loads(data)) + self.assertEqual("nfvswitch", bridge.name) + self.assertEqual("2,3,4,5", bridge.cpus) + interface1 = bridge.members[0] + self.assertEqual("storage202", interface1.name) + interface2 = bridge.members[1] + self.assertEqual("api201", interface2.name) + self.assertEqual(False, interface1.ovs_port) + self.assertEqual("nfvswitch", interface1.nfvswitch_bridge_name) + + def test_bond_interface_from_json(self): + data = """{ +"type": "nfvswitch_bridge", +"cpus": "2,3,4,5", +"members": [{ + "type": "linux_bond", "name": "bond1", "members": + [{"type": "interface", "name": "nic2"}, + {"type": "interface", "name": "nic3"}] + } + ] +} +""" + err = self.assertRaises(objects.InvalidConfigException, + objects.NfvswitchBridge.from_json, + json.loads(data)) + expected = 'NFVSwitch does not support bond interfaces.' + self.assertIn(expected, err) + + class TestBond(base.TestCase): def test_from_json_dhcp(self): @@ -427,9 +508,9 @@ class TestBond(base.TestCase): def test_from_json_dhcp_with_nic1_nic2(self): - def dummy_numbered_nics(nic_mapping=None): + def dummy_mapped_nics(nic_mapping=None): return {"nic1": "em1", "nic2": "em2"} - self.stubs.Set(objects, '_numbered_nics', dummy_numbered_nics) + self.stubs.Set(objects, '_mapped_nics', dummy_mapped_nics) data = """{ "type": "ovs_bond", @@ -456,6 +537,28 @@ class TestBond(base.TestCase): self.assertEqual("em2", interface2.name) +class TestLinuxTeam(base.TestCase): + + def test_from_json_dhcp(self): + data = """{ +"type": "team", +"name": "team1", +"use_dhcp": true, +"members": [ + { "type": "interface", "name": "em1", "primary": true }, + { "type": "interface", "name": "em2" } +] +} +""" + team = objects.object_from_json(json.loads(data)) + self.assertEqual("team1", team.name) + self.assertTrue(team.use_dhcp) + interface1 = team.members[0] + self.assertEqual("em1", interface1.name) + interface2 = team.members[1] + self.assertEqual("em2", interface2.name) + + class TestLinuxBond(base.TestCase): def test_from_json_dhcp(self): @@ -485,9 +588,9 @@ class TestLinuxBond(base.TestCase): def test_from_json_dhcp_with_nic1_nic2(self): - def dummy_numbered_nics(nic_mapping=None): + def dummy_mapped_nics(nic_mapping=None): return {"nic1": "em1", "nic2": "em2"} - self.stubs.Set(objects, '_numbered_nics', dummy_numbered_nics) + self.stubs.Set(objects, '_mapped_nics', dummy_mapped_nics) data = """{ "type": "ovs_bond", @@ -564,52 +667,154 @@ class TestOvsPatchPort(base.TestCase): self.assertEqual("br-ex-patch", patch_port.peer) -class TestNumberedNicsMapping(base.TestCase): +class TestIbInterface(base.TestCase): + + def test_ib_interface_addresses(self): + v4_addr = objects.Address('192.168.1.1/24') + v6_addr = objects.Address('2001:abc:a::/64') + ib_interface = objects.IbInterface('foo', addresses=[v4_addr, v6_addr]) + self.assertEqual("192.168.1.1", ib_interface.v4_addresses()[0].ip) + self.assertEqual("2001:abc:a::", ib_interface.v6_addresses()[0].ip) + + def test_from_json_dhcp(self): + data = '{"type": "ib_interface", "name": "ib0", "use_dhcp": true}' + ib_interface = objects.object_from_json(json.loads(data)) + self.assertEqual("ib0", ib_interface.name) + self.assertTrue(ib_interface.use_dhcp) + + def test_from_json_defroute(self): + data = '{"type": "ib_interface", "name": "ib0", "use_dhcp": true}' + ib_interface1 = objects.object_from_json(json.loads(data)) + data = """{ +"type": "ib_interface", +"name": "ib0", +"use_dhcp": true, +"defroute": false +} +""" + ib_interface2 = objects.object_from_json(json.loads(data)) + self.assertTrue(ib_interface1.defroute) + self.assertFalse(ib_interface2.defroute) + + def test_from_json_dhclient_args(self): + data = """{ +"type": "ib_interface", +"name": "ib0", +"use_dhcp": true, +"dhclient_args": "--foobar" +} +""" + ib_interface1 = objects.object_from_json(json.loads(data)) + self.assertEqual("--foobar", ib_interface1.dhclient_args) + + def test_from_json_dns_servers(self): + data = """{ +"type": "ib_interface", +"name": "ib0", +"use_dhcp": true, +"dns_servers": ["1.2.3.4"] +} +""" + ib_interface1 = objects.object_from_json(json.loads(data)) + self.assertEqual(["1.2.3.4"], ib_interface1.dns_servers) + + def test_from_json_dhcp_nic1(self): + def dummy_mapped_nics(nic_mapping=None): + return {"nic1": "ib0"} + self.stubs.Set(objects, '_mapped_nics', dummy_mapped_nics) + + data = '{"type": "ib_interface", "name": "nic1", "use_dhcp": true}' + ib_interface = objects.object_from_json(json.loads(data)) + self.assertEqual("ib0", ib_interface.name) + self.assertTrue(ib_interface.use_dhcp) + + def test_from_json_with_addresses(self): + data = """{ +"type": "ib_interface", +"name": "ib0", +"use_dhcp": false, +"mtu": 1501, +"addresses": [{ + "ip_netmask": "192.0.2.1/24" +}], +"routes": [{ + "next_hop": "192.0.2.1", + "ip_netmask": "192.0.2.1/24" +}] +} +""" + ib_interface = objects.object_from_json(json.loads(data)) + self.assertEqual("ib0", ib_interface.name) + self.assertFalse(ib_interface.use_dhcp) + self.assertFalse(ib_interface.use_dhcpv6) + self.assertEqual(1501, ib_interface.mtu) + address1 = ib_interface.v4_addresses()[0] + self.assertEqual("192.0.2.1", address1.ip) + self.assertEqual("255.255.255.0", address1.netmask) + route1 = ib_interface.routes[0] + self.assertEqual("192.0.2.1", route1.next_hop) + self.assertEqual("192.0.2.1/24", route1.ip_netmask) + + +class TestNicMapping(base.TestCase): # We want to test the function, not the dummy.. - stub_numbered_nics = False + stub_mapped_nics = False def tearDown(self): - super(TestNumberedNicsMapping, self).tearDown() - objects._NUMBERED_NICS = None + super(TestNicMapping, self).tearDown() + objects._MAPPED_NICS = None def _stub_active_nics(self, nics): def dummy_ordered_active_nics(): return nics self.stubs.Set(utils, 'ordered_active_nics', dummy_ordered_active_nics) - def test_numbered_nics_default(self): + def test_mapped_nics_default(self): self._stub_active_nics(['em1', 'em2']) expected = {'nic1': 'em1', 'nic2': 'em2'} - self.assertEqual(expected, objects._numbered_nics()) + self.assertEqual(expected, objects._mapped_nics()) - def test_numbered_nics_mapped(self): + def test_mapped_nics_mapped(self): self._stub_active_nics(['em1', 'em2']) mapping = {'nic1': 'em2', 'nic2': 'em1'} expected = {'nic1': 'em2', 'nic2': 'em1'} - self.assertEqual(expected, objects._numbered_nics(nic_mapping=mapping)) + self.assertEqual(expected, objects._mapped_nics(nic_mapping=mapping)) - def test_numbered_nics_mapped_partial(self): + def test_mapped_nics_mapped_partial(self): self._stub_active_nics(['em1', 'em2', 'em3', 'em4']) mapping = {'nic1': 'em2', 'nic2': 'em1'} expected = {'nic1': 'em2', 'nic2': 'em1', 'nic3': 'em3', 'nic4': 'em4'} - self.assertEqual(expected, objects._numbered_nics(nic_mapping=mapping)) + self.assertEqual(expected, objects._mapped_nics(nic_mapping=mapping)) + + def test_mapped_nics_mapped_partial_reordered(self): + self._stub_active_nics(['em1', 'em2', 'em3', 'em4']) + mapping = {'nic1': 'em1', 'nic2': 'em3'} + expected = {'nic1': 'em1', 'nic2': 'em3', 'nic4': 'em4'} + self.assertEqual(expected, objects._mapped_nics(nic_mapping=mapping)) + + def test_mapped_nics_mapped_unnumbered(self): + self._stub_active_nics(['em1', 'em2', 'em3', 'em4']) + mapping = {'John': 'em1', 'Paul': 'em2', 'George': 'em3'} + expected = {'John': 'em1', 'Paul': 'em2', 'George': 'em3', + 'nic4': 'em4'} + self.assertEqual(expected, objects._mapped_nics(nic_mapping=mapping)) - def test_numbered_nics_map_error_notactive(self): + def test_mapped_nics_map_error_notactive(self): self._stub_active_nics(['em1', 'em2']) mapping = {'nic1': 'em3', 'nic2': 'em1'} expected = {'nic2': 'em1'} - self.assertEqual(expected, objects._numbered_nics(nic_mapping=mapping)) + self.assertEqual(expected, objects._mapped_nics(nic_mapping=mapping)) - def test_numbered_nics_map_error_duplicate(self): + def test_mapped_nics_map_error_duplicate(self): self._stub_active_nics(['em1', 'em2']) mapping = {'nic1': 'em1', 'nic2': 'em1'} err = self.assertRaises(objects.InvalidConfigException, - objects._numbered_nics, nic_mapping=mapping) + objects._mapped_nics, nic_mapping=mapping) expected = 'em1 already mapped, check mapping file for duplicates' self.assertIn(expected, six.text_type(err)) - def test_numbered_nics_map_mac(self): + def test_mapped_nics_map_mac(self): def dummy_interface_mac(name): mac_map = {'em1': '12:34:56:78:9a:bc', 'em2': '12:34:56:de:f0:12'} @@ -618,10 +823,10 @@ class TestNumberedNicsMapping(base.TestCase): self._stub_active_nics(['em1', 'em2']) mapping = {'nic1': '12:34:56:de:f0:12', 'nic2': '12:34:56:78:9a:bc'} expected = {'nic1': 'em2', 'nic2': 'em1'} - self.assertEqual(expected, objects._numbered_nics(nic_mapping=mapping)) + self.assertEqual(expected, objects._mapped_nics(nic_mapping=mapping)) - def test_numbered_nics_no_active(self): + def test_mapped_nics_no_active(self): self._stub_active_nics([]) expected = {} # This only emits a warning, so it should still work - self.assertEqual(expected, objects._numbered_nics()) + self.assertEqual(expected, objects._mapped_nics()) |