From 9108fcb7bb1fa10ffce2fd787278802ee3f3354c Mon Sep 17 00:00:00 2001 From: Saravanan KR Date: Sat, 30 Jul 2016 15:22:57 +0530 Subject: Add support for OVS DPDK Bond Add functionality to os-net-config to allow DPDK bonding of interfaces, and implement support for parameters to be passed by TripleO Heat Templates. Implements: blueprint tripleo-ovs-dpdk Depends-On: Id4a23ced28b92a642c180a35c55080e5f4e2e05d Change-Id: If1c91402d2d393140dc1b4a678e68a1bcdbe81e4 --- etc/os-net-config/samples/ovs_dpdk_bond.json | 44 +++++++++++++++++ etc/os-net-config/samples/ovs_dpdk_bond.yaml | 36 ++++++++++++++ os_net_config/__init__.py | 9 ++++ os_net_config/impl_ifcfg.py | 35 ++++++++++++++ os_net_config/objects.py | 70 +++++++++++++++++++++++++++- os_net_config/tests/test_cli.py | 17 +++++++ os_net_config/tests/test_impl_ifcfg.py | 24 ++++++++++ os_net_config/tests/test_objects.py | 46 ++++++++++++++++++ 8 files changed, 280 insertions(+), 1 deletion(-) create mode 100644 etc/os-net-config/samples/ovs_dpdk_bond.json create mode 100644 etc/os-net-config/samples/ovs_dpdk_bond.yaml diff --git a/etc/os-net-config/samples/ovs_dpdk_bond.json b/etc/os-net-config/samples/ovs_dpdk_bond.json new file mode 100644 index 0000000..af82e7b --- /dev/null +++ b/etc/os-net-config/samples/ovs_dpdk_bond.json @@ -0,0 +1,44 @@ +{ "network_config": [ + { + "type": "ovs_user_bridge", + "name": "br-link", + "members": [ + { + "type" : "ovs_dpdk_bond", + "name" : "dpdkbond0", + "members": [ + { + "type" : "ovs_dpdk_port", + "name" : "dpdk0", + "members": [ + { + "type": "interface", + "name": "eth1" + } + ] + }, + { + "type" : "ovs_dpdk_port", + "name" : "dpdk1", + "members": [ + { + "type": "interface", + "name": "eth2" + } + ] + }, + ] + }, + { + "type" : "vlan", + "vlan_id" : 16, + "addresses" : [ + { + "ip_netmask" : "192.0.2.1/24" + } + ] + } + ] + } + ] +} diff --git a/etc/os-net-config/samples/ovs_dpdk_bond.yaml b/etc/os-net-config/samples/ovs_dpdk_bond.yaml new file mode 100644 index 0000000..d51fa4a --- /dev/null +++ b/etc/os-net-config/samples/ovs_dpdk_bond.yaml @@ -0,0 +1,36 @@ +# ovs_user_bridge type refers to the OVSUserBridge OVS ifup type, which will +# have the datapath type set as 'netdev' for DPDK processing. +# ovs_dpdk_port type refers to the OVSDPDKPort OVS ifup type, which will +# add the port to the bridge with the interface type as 'dpdk'. +# ovs_dpdk_bond type refers to the OVSDPDKBond OVS ifup type, which will +# create the bond with dpdk interface type for dpdk ports + +network_config: + - + type: ovs_user_bridge + name: br-link + members: + - + type: ovs_dpdk_bond + name: dpdkbond0 + members: + - + type: ovs_dpdk_port + name: dpdk0 + members: + - + type: interface + name: eth1 + - + type: ovs_dpdk_port + name: dpdk1 + members: + - + type: interface + name: eth2 + - + type: vlan + vlan_id: 16 + addresses: + - + ip_netmask: 192.0.2.1/24 \ No newline at end of file diff --git a/os_net_config/__init__.py b/os_net_config/__init__.py index d307980..19d4a1e 100644 --- a/os_net_config/__init__.py +++ b/os_net_config/__init__.py @@ -93,6 +93,8 @@ class NetConfig(object): self.add_ib_interface(obj) elif isinstance(obj, objects.OvsDpdkPort): self.add_ovs_dpdk_port(obj) + elif isinstance(obj, objects.OvsDpdkBond): + self.add_ovs_dpdk_bond(obj) def add_interface(self, interface): """Add an Interface object to the net config object. @@ -192,6 +194,13 @@ class NetConfig(object): """ raise NotImplemented("add_ovs_dpdk_port is not implemented.") + def add_ovs_dpdk_bond(self, ovs_dpdk_bond): + """Add a OvsDpdkBond object to the net config object. + + :param ovs_dpdk_bond: The OvsDpdkBond object to add. + """ + raise NotImplemented("add_ovs_dpdk_bond 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 c3d45bf..79e3e42 100644 --- a/os_net_config/impl_ifcfg.py +++ b/os_net_config/impl_ifcfg.py @@ -242,6 +242,21 @@ class IfcfgNetConfig(os_net_config.NetConfig): data += "DEVICETYPE=ovs\n" data += "TYPE=OVSDPDKPort\n" 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 + 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 + ovs_extra.extend(base_opt.ovs_extra) else: if base_opt.use_dhcp: data += "BOOTPROTO=dhcp\n" @@ -524,6 +539,26 @@ class IfcfgNetConfig(os_net_config.NetConfig): logger.debug('ovs dpdk port data: %s' % data) self.interface_data[ovs_dpdk_port.name] = data + def add_ovs_dpdk_bond(self, ovs_dpdk_bond): + """Add an OvsDPDKBond object to the net config object. + + :param ovs_dpdk_bond: The OvsBond object to add. + """ + logger.info('adding ovs dpdk bond: %s' % ovs_dpdk_bond.name) + + # Bind the dpdk interface + for dpdk_port in ovs_dpdk_bond.members: + # DPDK Port will have only one member of type Interface, validation + # checks are added at the object creation stage. + ifname = dpdk_port.members[0].name + utils.bind_dpdk_interfaces(ifname, dpdk_port.driver, self.noop) + + data = self._add_common(ovs_dpdk_bond) + logger.debug('ovs dpdk bond data: %s' % data) + self.interface_data[ovs_dpdk_bond.name] = data + if ovs_dpdk_bond.routes: + self._add_routes(ovs_dpdk_bond.name, ovs_dpdk_bond.routes) + def generate_ivs_config(self, ivs_uplinks, ivs_interfaces): """Generate configuration content for ivs.""" diff --git a/os_net_config/objects.py b/os_net_config/objects.py index 725df38..154dccf 100644 --- a/os_net_config/objects.py +++ b/os_net_config/objects.py @@ -64,6 +64,8 @@ def object_from_json(json): return IbInterface.from_json(json) elif obj_type == "ovs_dpdk_port": return OvsDpdkPort.from_json(json) + elif obj_type == "ovs_dpdk_bond": + return OvsDpdkBond.from_json(json) def _get_required_field(json, name, object_name): @@ -457,7 +459,8 @@ class OvsUserBridge(_BaseOpts): for member in self.members: member.bridge_name = name if not isinstance(member, OvsTunnel) and \ - not isinstance(member, OvsDpdkPort): + not isinstance(member, OvsDpdkPort) and \ + not isinstance(member, OvsDpdkBond): member.ovs_port = True if member.primary: if self.primary_interface_name: @@ -1005,3 +1008,68 @@ class OvsDpdkPort(_BaseOpts): opts = _BaseOpts.base_opts_from_json(json) return OvsDpdkPort(name, *opts, members=members, driver=driver, ovs_options=ovs_options, ovs_extra=ovs_extra) + + +class OvsDpdkBond(_BaseOpts): + """Base class for OVS DPDK bonds.""" + + def __init__(self, name, use_dhcp=False, use_dhcpv6=False, addresses=None, + 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): + super(OvsDpdkBond, self).__init__(name, use_dhcp, use_dhcpv6, + addresses, routes, mtu, primary, + nic_mapping, persist_mapping, + defroute, dhclient_args, dns_servers) + self.members = members or [] + self.ovs_options = ovs_options + self.ovs_extra = ovs_extra or [] + + for member in self.members: + if member.primary: + if self.primary_interface_name: + msg = 'Only one primary interface allowed per bond (dpdk).' + raise InvalidConfigException(msg) + if member.primary_interface_name: + self.primary_interface_name = member.primary_interface_name + else: + self.primary_interface_name = member.name + if not self.primary_interface_name: + bond_members = list(self.members) + bond_members.sort(key=lambda x: x.name) + self.primary_interface_name = bond_members[0].name + + @staticmethod + def from_json(json): + 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( + json, include_primary=False) + ovs_options = json.get('ovs_options') + ovs_extra = json.get('ovs_extra', []) + members = [] + + # members + members_json = json.get('members') + if members_json: + if isinstance(members_json, list): + for member in members_json: + obj = object_from_json(member) + if isinstance(obj, OvsDpdkPort): + members.append(obj) + else: + msg = 'Membrs must be of type ovs_dpdk_port' + raise InvalidConfigException(msg) + else: + msg = 'Members must be a list.' + raise InvalidConfigException(msg) + + return OvsDpdkBond(name, use_dhcp=use_dhcp, use_dhcpv6=use_dhcpv6, + addresses=addresses, routes=routes, mtu=mtu, + 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) diff --git a/os_net_config/tests/test_cli.py b/os_net_config/tests/test_cli.py index 939b12e..c5c825f 100644 --- a/os_net_config/tests/test_cli.py +++ b/os_net_config/tests/test_cli.py @@ -147,6 +147,23 @@ class TestCli(base.TestCase): '-c %s --detailed-exit-codes' % interface_yaml, exitcodes=(0,)) + def test_ovs_dpdk_bond_noop_output(self): + ivs_yaml = os.path.join(SAMPLE_BASE, 'ovs_dpdk_bond.yaml') + ivs_json = os.path.join(SAMPLE_BASE, 'ovs_dpdk_bond.json') + stdout_yaml, stderr = self.run_cli('ARG0 --provider=ifcfg --noop ' + '-c %s' % ivs_yaml) + self.assertEqual('', stderr) + stdout_json, stderr = self.run_cli('ARG0 --provider=ifcfg --noop ' + '-c %s' % ivs_json) + self.assertEqual('', stderr) + sanity_devices = ['DEVICE=br-link', + 'TYPE=OVSUserBridge', + 'DEVICE=dpdkbond0', + 'TYPE=OVSDPDKBond'] + for dev in sanity_devices: + self.assertIn(dev, stdout_yaml) + self.assertEqual(stdout_yaml, stdout_json) + def test_nfvswitch_noop_output(self): nfvswitch_yaml = os.path.join(SAMPLE_BASE, 'nfvswitch.yaml') nfvswitch_json = os.path.join(SAMPLE_BASE, 'nfvswitch.json') diff --git a/os_net_config/tests/test_impl_ifcfg.py b/os_net_config/tests/test_impl_ifcfg.py index ad27d83..74dd1f9 100644 --- a/os_net_config/tests/test_impl_ifcfg.py +++ b/os_net_config/tests/test_impl_ifcfg.py @@ -788,6 +788,30 @@ OVS_BRIDGE=br-link self.provider.bridge_data['br-link']) self.assertEqual(dpdk0_config, self.get_interface_config('dpdk0')) + def test_network_ovs_dpdk_bond(self): + iface0 = objects.Interface(name='eth1') + dpdk0 = objects.OvsDpdkPort(name='dpdk0', members=[iface0]) + iface1 = objects.Interface(name='eth2') + dpdk1 = objects.OvsDpdkPort(name='dpdk1', members=[iface1]) + bond = objects.OvsDpdkBond('dpdkbond0', members=[dpdk0, dpdk1]) + bridge = objects.OvsUserBridge('br-link', members=[bond]) + self.provider.add_bond(bond) + self.provider.add_bridge(bridge) + + dpdk_bond_config = """# This file is autogenerated by os-net-config +DEVICE=dpdkbond0 +ONBOOT=yes +HOTPLUG=no +NM_CONTROLLED=no +PEERDNS=no +DEVICETYPE=ovs +TYPE=OVSDPDKBond +OVS_BRIDGE=br-link +BOND_IFACES="dpdk0 dpdk1" +""" + self.assertEqual(dpdk_bond_config, + self.get_interface_config('dpdkbond0')) + class TestIfcfgNetConfigApply(base.TestCase): diff --git a/os_net_config/tests/test_objects.py b/os_net_config/tests/test_objects.py index 7e8441c..92d43b9 100644 --- a/os_net_config/tests/test_objects.py +++ b/os_net_config/tests/test_objects.py @@ -830,3 +830,49 @@ class TestNicMapping(base.TestCase): expected = {} # This only emits a warning, so it should still work self.assertEqual(expected, objects._mapped_nics()) + + +class TestOvsDpdkBond(base.TestCase): + + def test_from_json_dhcp(self): + data = """{ +"type": "ovs_dpdk_bond", +"name": "dpdkbond0", +"use_dhcp": true, +"members": [ + { + "type": "ovs_dpdk_port", + "name": "dpdk0", + "members": [ + { + "type": "interface", + "name": "eth1" + } + ] + }, + { + "type": "ovs_dpdk_port", + "name": "dpdk1", + "members": [ + { + "type": "interface", + "name": "eth2" + } + ] + } +] +} +""" + bond = objects.object_from_json(json.loads(data)) + self.assertEqual("dpdkbond0", bond.name) + self.assertTrue(bond.use_dhcp) + dpdk_port0 = bond.members[0] + self.assertEqual("dpdk0", dpdk_port0.name) + self.assertEqual("vfio-pci", dpdk_port0.driver) + iface1 = dpdk_port0.members[0] + self.assertEqual("eth1", iface1.name) + dpdk_port1 = bond.members[1] + self.assertEqual("dpdk1", dpdk_port1.name) + self.assertEqual("vfio-pci", dpdk_port1.driver) + iface2 = dpdk_port1.members[0] + self.assertEqual("eth2", iface2.name) -- cgit 1.2.3-korg