From 15974244f6212905540e1daf9ca596c83f510bdd Mon Sep 17 00:00:00 2001 From: Sarath Kumar Date: Thu, 21 Jul 2016 11:14:39 -0700 Subject: Enable os-net-config to support and configure NFVSwitch These changes are to generate /etc/sysconf/network-scripts/ifcfg-* and /etc/sysconfig/nfvswitch configuration files for nfvswitch and its interfaces. NFVSwitch is a virtual switch implementation based on DPDK for datacenter workloads with very high throughput needs. Change-Id: If02edb9c4c54c014f67290fe0c34e2fc73cb95bd --- etc/os-net-config/samples/nfvswitch.json | 38 ++++++++++++ etc/os-net-config/samples/nfvswitch.yaml | 25 ++++++++ os_net_config/__init__.py | 13 ++++ os_net_config/impl_ifcfg.py | 101 +++++++++++++++++++++++++++++++ os_net_config/objects.py | 101 +++++++++++++++++++++++++++++++ os_net_config/tests/test_cli.py | 18 ++++++ os_net_config/tests/test_impl_ifcfg.py | 47 ++++++++++++++ os_net_config/tests/test_objects.py | 81 +++++++++++++++++++++++++ 8 files changed, 424 insertions(+) create mode 100644 etc/os-net-config/samples/nfvswitch.json create mode 100644 etc/os-net-config/samples/nfvswitch.yaml 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/os_net_config/__init__.py b/os_net_config/__init__.py index 18c96a2..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: @@ -117,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. diff --git a/os_net_config/impl_ifcfg.py b/os_net_config/impl_ifcfg.py index 49ad317..a478e27 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,6 +62,8 @@ 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 = {} @@ -103,6 +109,8 @@ 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): @@ -117,6 +125,9 @@ class IfcfgNetConfig(os_net_config.NetConfig): 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: if not isinstance(base_opt, objects.LinuxTeam): data += "DEVICETYPE=ovs\n" @@ -333,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. @@ -369,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. @@ -462,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. @@ -487,6 +544,8 @@ class IfcfgNetConfig(os_net_config.NetConfig): 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, '') @@ -499,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 @@ -533,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, '') @@ -698,6 +780,12 @@ 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) @@ -728,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 5137263..4a15d05 100644 --- a/os_net_config/objects.py +++ b/os_net_config/objects.py @@ -50,6 +50,10 @@ def object_from_json(json): 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": @@ -181,6 +185,7 @@ 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 @@ -333,6 +338,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.""" @@ -510,6 +541,76 @@ 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.""" 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 b8a8402..467db65 100644 --- a/os_net_config/tests/test_impl_ifcfg.py +++ b/os_net_config/tests/test_impl_ifcfg.py @@ -292,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 @@ -553,6 +581,25 @@ 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'), diff --git a/os_net_config/tests/test_objects.py b/os_net_config/tests/test_objects.py index 17b6927..bbff1f3 100644 --- a/os_net_config/tests/test_objects.py +++ b/os_net_config/tests/test_objects.py @@ -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): -- cgit 1.2.3-korg