From 2bd008ef5c56b85c85b09ec94d7ed079116c8e3c Mon Sep 17 00:00:00 2001 From: Jan Gutter Date: Thu, 13 Jul 2017 18:04:04 +0200 Subject: [PATCH] Netronome SmartNIC Enablement These patches enable Netronome Agilio SmartNIC acceleration in Nova. * This patch set adds VIF_TYPE_AGILIO_OVS to hook in the external ML2 plugin. * This patch set adds VNIC_TYPE_VIRTIO_FORWARDER to enable the virtio-forwarder plugging mode. * nova/network/neutronv2/api.py has been modified to pass the bridge in the os-vif objects, similar to other OVS VIF types. * os_vif_util.py has been extended to handle the agilio_ovs os-vif plugin. * VIFPortProfileOVSRepresentor is used to pass the PCI address and vif name to the os-vif plugin. * Note: this enables the external ML2 and OS-VIF plugin, hosted at https://github.com/Netronome/agilio-ovs-openstack-plugin to support the out-of-tree version of Agilio OVS. Consult the Agilio documentation for configuration recommendations. Change-Id: I3da059fb62f085ff04532f8ec05857acfb628b06 Depends-On: Id95a9f311e71b3cefb170704728641a4a1a91144 Signed-off-by: Jan Gutter --- nova/network/model.py | 6 +- nova/network/neutronv2/api.py | 3 +- nova/network/os_vif_util.py | 43 +++++ nova/tests/unit/network/test_os_vif_util.py | 120 +++++++++++++ nova/tests/unit/virt/libvirt/test_vif.py | 170 ++++++++++++++++++ ...-smartnic-enablement-d3897fb294429282.yaml | 14 ++ 6 files changed, 354 insertions(+), 2 deletions(-) create mode 100644 releasenotes/notes/netronome-smartnic-enablement-d3897fb294429282.yaml diff --git a/nova/network/model.py b/nova/network/model.py index f82fa5154128..763e56d68dc4 100644 --- a/nova/network/model.py +++ b/nova/network/model.py @@ -42,6 +42,7 @@ VIF_TYPE_VROUTER = 'vrouter' VIF_TYPE_OTHER = 'other' VIF_TYPE_TAP = 'tap' VIF_TYPE_MACVTAP = 'macvtap' +VIF_TYPE_AGILIO_OVS = 'agilio_ovs' VIF_TYPE_BINDING_FAILED = 'binding_failed' VIF_TYPE_VIF = 'vif' @@ -97,12 +98,15 @@ VNIC_TYPE_DIRECT = 'direct' VNIC_TYPE_MACVTAP = 'macvtap' VNIC_TYPE_DIRECT_PHYSICAL = 'direct-physical' VNIC_TYPE_BAREMETAL = 'baremetal' +VNIC_TYPE_VIRTIO_FORWARDER = 'virtio-forwarder' # Define list of ports which needs pci request. # Note: The macvtap port needs a PCI request as it is a tap interface # with VF as the lower physical interface. +# Note: Currently, VNIC_TYPE_VIRTIO_FORWARDER assumes a 1:1 +# relationship with a VF. This is expected to change in the future. VNIC_TYPES_SRIOV = (VNIC_TYPE_DIRECT, VNIC_TYPE_MACVTAP, - VNIC_TYPE_DIRECT_PHYSICAL) + VNIC_TYPE_DIRECT_PHYSICAL, VNIC_TYPE_VIRTIO_FORWARDER) # Define list of ports which are passthrough to the guest # and need a special treatment on snapshot and suspend/resume diff --git a/nova/network/neutronv2/api.py b/nova/network/neutronv2/api.py index 5d98e910fadb..65cc89baef70 100644 --- a/nova/network/neutronv2/api.py +++ b/nova/network/neutronv2/api.py @@ -2159,7 +2159,8 @@ class API(base_api.NetworkAPI): should_create_bridge = None vif_type = port.get('binding:vif_type') port_details = port.get('binding:vif_details', {}) - if vif_type == network_model.VIF_TYPE_OVS: + if vif_type in [network_model.VIF_TYPE_OVS, + network_model.VIF_TYPE_AGILIO_OVS]: bridge = port_details.get(network_model.VIF_DETAILS_BRIDGE_NAME, CONF.neutron.ovs_bridge) ovs_interfaceid = port['id'] diff --git a/nova/network/os_vif_util.py b/nova/network/os_vif_util.py index cf142c131264..32b2465ee8ea 100644 --- a/nova/network/os_vif_util.py +++ b/nova/network/os_vif_util.py @@ -299,6 +299,49 @@ def _nova_to_osvif_vif_ovs(vif): return obj +# VIF_TYPE_AGILIO_OVS = 'agilio_ovs' +def _nova_to_osvif_vif_agilio_ovs(vif): + vnic_type = vif.get('vnic_type', model.VNIC_TYPE_NORMAL) + # In practice, vif_name gets its value from vif["devname"], passed by + # the mechanism driver. + vif_name = _get_vif_name(vif) + agilio_vnic_types = [model.VNIC_TYPE_DIRECT, + model.VNIC_TYPE_VIRTIO_FORWARDER] + if vnic_type in agilio_vnic_types: + # Note: passing representor_name asks os-vif to rename the + # representor, setting this to vif_name is helpful for tracing. + # VIF.port_profile.representor_address is used by the os-vif plugin's + # plug/unplug, this should be the same as VIF.dev_address in the + # VNIC_TYPE_DIRECT case. + profile = objects.vif.VIFPortProfileOVSRepresentor( + interface_id=vif.get('ovs_interfaceid') or vif['id'], + representor_name=vif_name, + representor_address=vif["profile"]["pci_slot"]) + if vnic_type == model.VNIC_TYPE_DIRECT: + # VIF.dev_address is used by the hypervisor to plug the instance into + # the PCI device. + obj = _get_vif_instance( + vif, + objects.vif.VIFHostDevice, + port_profile=profile, + plugin="agilio_ovs", + dev_address=vif["profile"]["pci_slot"], + dev_type=objects.fields.VIFHostDeviceDevType.ETHERNET) + if vif["network"]["bridge"] is not None: + obj.network.bridge = vif["network"]["bridge"] + elif vnic_type == model.VNIC_TYPE_VIRTIO_FORWARDER: + obj = _get_vif_instance(vif, objects.vif.VIFVHostUser, + port_profile=profile, plugin="agilio_ovs", + vif_name=vif_name) + _set_vhostuser_settings(vif, obj) + if vif["network"]["bridge"] is not None: + obj.network.bridge = vif["network"]["bridge"] + else: + LOG.debug("agilio_ovs falling through to ovs %s", vif) + obj = _nova_to_osvif_vif_ovs(vif) + return obj + + # VIF_TYPE_VHOST_USER = 'vhostuser' def _nova_to_osvif_vif_vhostuser(vif): if vif['details'].get(model.VIF_DETAILS_VHOSTUSER_FP_PLUG, False): diff --git a/nova/tests/unit/network/test_os_vif_util.py b/nova/tests/unit/network/test_os_vif_util.py index acc0bc3c46d1..930c59b1ba11 100644 --- a/nova/tests/unit/network/test_os_vif_util.py +++ b/nova/tests/unit/network/test_os_vif_util.py @@ -475,6 +475,126 @@ class OSVIFUtilTestCase(test.NoDBTestCase): self.assertObjEqual(expect, actual) + def test_nova_to_osvif_vif_agilio_ovs_fallthrough(self): + vif = model.VIF( + id="dc065497-3c8d-4f44-8fb4-e1d33c16a536", + type=model.VIF_TYPE_AGILIO_OVS, + address="22:52:25:62:e2:aa", + network=model.Network( + id="b82c1929-051e-481d-8110-4669916c7915", + label="Demo Net", + subnets=[]), + details={ + model.VIF_DETAILS_PORT_FILTER: True, + } + ) + + actual = os_vif_util.nova_to_osvif_vif(vif) + + expect = osv_objects.vif.VIFOpenVSwitch( + id="dc065497-3c8d-4f44-8fb4-e1d33c16a536", + active=False, + address="22:52:25:62:e2:aa", + has_traffic_filtering=True, + plugin="ovs", + port_profile=osv_objects.vif.VIFPortProfileOpenVSwitch( + interface_id="dc065497-3c8d-4f44-8fb4-e1d33c16a536"), + preserve_on_delete=False, + vif_name="nicdc065497-3c", + network=osv_objects.network.Network( + id="b82c1929-051e-481d-8110-4669916c7915", + bridge_interface=None, + label="Demo Net", + subnets=osv_objects.subnet.SubnetList( + objects=[]))) + + self.assertObjEqual(expect, actual) + + def test_nova_to_osvif_vif_agilio_ovs_direct(self): + vif = model.VIF( + id="dc065497-3c8d-4f44-8fb4-e1d33c16a536", + type=model.VIF_TYPE_AGILIO_OVS, + address="22:52:25:62:e2:aa", + profile={ + "pci_slot": "0000:08:08.5", + }, + network=model.Network( + id="b82c1929-051e-481d-8110-4669916c7915", + label="Demo Net", + subnets=[]), + vnic_type=model.VNIC_TYPE_DIRECT, + ) + + actual = os_vif_util.nova_to_osvif_vif(vif) + + expect = osv_objects.vif.VIFHostDevice( + id="dc065497-3c8d-4f44-8fb4-e1d33c16a536", + active=False, + has_traffic_filtering=False, + address="22:52:25:62:e2:aa", + dev_type=osv_objects.fields.VIFHostDeviceDevType.ETHERNET, + dev_address="0000:08:08.5", + plugin="agilio_ovs", + port_profile=osv_objects.vif.VIFPortProfileOVSRepresentor( + interface_id="dc065497-3c8d-4f44-8fb4-e1d33c16a536", + representor_name="nicdc065497-3c", + representor_address="0000:08:08.5"), + preserve_on_delete=False, + vif_name="nicdc065497-3c", + network=osv_objects.network.Network( + id="b82c1929-051e-481d-8110-4669916c7915", + bridge_interface=None, + label="Demo Net", + subnets=osv_objects.subnet.SubnetList( + objects=[]))) + + self.assertObjEqual(expect, actual) + + def test_nova_to_osvif_vif_agilio_ovs_forwarder(self): + vif = model.VIF( + id="dc065497-3c8d-4f44-8fb4-e1d33c16a536", + type=model.VIF_TYPE_AGILIO_OVS, + address="22:52:25:62:e2:aa", + profile={ + "pci_slot": "0000:08:08.5", + }, + network=model.Network( + id="b82c1929-051e-481d-8110-4669916c7915", + label="Demo Net", + subnets=[]), + vnic_type=model.VNIC_TYPE_VIRTIO_FORWARDER, + details={ + model.VIF_DETAILS_VHOSTUSER_MODE: 'client', + model.VIF_DETAILS_VHOSTUSER_OVS_PLUG: True, + model.VIF_DETAILS_VHOSTUSER_SOCKET: '/fake/socket', + } + ) + + actual = os_vif_util.nova_to_osvif_vif(vif) + + expect = osv_objects.vif.VIFVHostUser( + id="dc065497-3c8d-4f44-8fb4-e1d33c16a536", + active=False, + address="22:52:25:62:e2:aa", + has_traffic_filtering=False, + plugin="agilio_ovs", + port_profile=osv_objects.vif.VIFPortProfileOVSRepresentor( + interface_id="dc065497-3c8d-4f44-8fb4-e1d33c16a536", + representor_address="0000:08:08.5", + representor_name="nicdc065497-3c",), + preserve_on_delete=False, + vif_name="nicdc065497-3c", + path='/fake/socket', + mode='client', + network=osv_objects.network.Network( + id="b82c1929-051e-481d-8110-4669916c7915", + bridge_interface=None, + label="Demo Net", + subnets=osv_objects.subnet.SubnetList( + objects=[]))) + + self.assertObjEqual(expect, actual) + def test_nova_to_osvif_vif_ovs_plain(self): vif = model.VIF( id="dc065497-3c8d-4f44-8fb4-e1d33c16a536", diff --git a/nova/tests/unit/virt/libvirt/test_vif.py b/nova/tests/unit/virt/libvirt/test_vif.py index 8711cc023f7f..48af5e564323 100644 --- a/nova/tests/unit/virt/libvirt/test_vif.py +++ b/nova/tests/unit/virt/libvirt/test_vif.py @@ -105,6 +105,35 @@ class LibvirtVifTestCase(test.NoDBTestCase): bridge_interface=None, vlan=99) + vif_agilio_ovs = network_model.VIF(id='vif-xxx-yyy-zzz', + address='ca:fe:de:ad:be:ef', + network=network_ovs, + type=network_model.VIF_TYPE_AGILIO_OVS, + details={'port_filter': False}, + devname='tap-xxx-yyy-zzz', + ovs_interfaceid='aaa-bbb-ccc') + + vif_agilio_ovs_direct = network_model.VIF(id='vif-xxx-yyy-zzz', + address='ca:fe:de:ad:be:ef', + network=network_ovs, + type=network_model.VIF_TYPE_AGILIO_OVS, + vnic_type=network_model.VNIC_TYPE_DIRECT, + ovs_interfaceid='aaa-bbb-ccc', + devname='tap-xxx-yyy-zzz', + profile={'pci_slot': '0000:0a:00.1'}) + + vif_agilio_ovs_forwarder = network_model.VIF(id='vif-xxx-yyy-zzz', + address='ca:fe:de:ad:be:ef', + network=network_ovs, + type=network_model.VIF_TYPE_AGILIO_OVS, + vnic_type=network_model.VNIC_TYPE_VIRTIO_FORWARDER, + profile={'pci_slot': '0000:0a:00.1'}, + details={ + network_model.VIF_DETAILS_VHOSTUSER_MODE: 'client', + network_model.VIF_DETAILS_VHOSTUSER_SOCKET: '/tmp/usv-xxx-yyy-zzz', + network_model.VIF_DETAILS_VHOSTUSER_OVS_PLUG: True}, + ovs_interfaceid='aaa-bbb-ccc', mtu=1500) + vif_ovs = network_model.VIF(id='vif-xxx-yyy-zzz', address='ca:fe:de:ad:be:ef', network=network_ovs, @@ -370,6 +399,41 @@ class LibvirtVifTestCase(test.NoDBTestCase): interface_id="07bd6cea-fb37-4594-b769-90fc51854ee9", profile_id="fishfood") + self.os_vif_repr_prof = osv_objects.vif.VIFPortProfileOVSRepresentor( + interface_id="07bd6cea-fb37-4594-b769-90fc51854ee9", + profile_id="fishfood", + representor_name='nicdc065497-3c', + representor_address='0000:0a:00.1') + + self.os_vif_agilio_ovs = osv_objects.vif.VIFOpenVSwitch( + id="dc065497-3c8d-4f44-8fb4-e1d33c16a536", + address="22:52:25:62:e2:aa", + plugin="agilio_ovs", + vif_name="nicdc065497-3c", + bridge_name="br0", + port_profile=self.os_vif_ovs_prof, + network=self.os_vif_network) + + self.os_vif_agilio_forwarder = osv_objects.vif.VIFVHostUser( + id="dc065497-3c8d-4f44-8fb4-e1d33c16a536", + address="22:52:25:62:e2:aa", + plugin="agilio_ovs", + vif_name="nicdc065497-3c", + path='/var/run/openvswitch/vhudc065497-3c', + mode='client', + port_profile=self.os_vif_repr_prof, + network=self.os_vif_network) + + self.os_vif_agilio_direct = osv_objects.vif.VIFHostDevice( + id="dc065497-3c8d-4f44-8fb4-e1d33c16a536", + address="22:52:25:62:e2:aa", + plugin="agilio_ovs", + vif_name="nicdc065497-3c", + dev_type=osv_fields.VIFHostDeviceDevType.ETHERNET, + dev_address='0000:0a:00.1', + port_profile=self.os_vif_repr_prof, + network=self.os_vif_network) + self.os_vif_ovs = osv_objects.vif.VIFOpenVSwitch( id="dc065497-3c8d-4f44-8fb4-e1d33c16a536", address="22:52:25:62:e2:aa", @@ -1416,6 +1480,32 @@ class LibvirtVifTestCase(test.NoDBTestCase): self._assertMacEquals(node, self.vif_vhostuser_ovs) self._assertModel(xml, network_model.VIF_MODEL_VIRTIO) + def test_agilio_ovs_direct(self): + d = vif.LibvirtGenericVIFDriver() + xml = self._get_instance_xml(d, self.vif_agilio_ovs_direct) + node = self._get_node(xml) + self._assertTypeAndPciEquals(node, + "hostdev", + self.vif_agilio_ovs_direct) + self._assertMacEquals(node, self.vif_agilio_ovs_direct) + + def test_agilio_ovs_forwarder(self): + d = vif.LibvirtGenericVIFDriver() + xml = self._get_instance_xml(d, + self.vif_agilio_ovs_forwarder) + node = self._get_node(xml) + self.assertEqual(node.get("type"), + network_model.VIF_TYPE_VHOSTUSER) + + self._assertTypeEquals(node, network_model.VIF_TYPE_VHOSTUSER, + "source", "mode", "client") + self._assertTypeEquals(node, network_model.VIF_TYPE_VHOSTUSER, + "source", "path", "/tmp/usv-xxx-yyy-zzz") + self._assertTypeEquals(node, network_model.VIF_TYPE_VHOSTUSER, + "source", "type", "unix") + self._assertMacEquals(node, self.vif_agilio_ovs_forwarder) + self._assertModel(xml, network_model.VIF_MODEL_VIRTIO) + @mock.patch("nova.network.os_vif_util.nova_to_osvif_instance") @mock.patch("nova.network.os_vif_util.nova_to_osvif_vif") @mock.patch.object(os_vif, "plug") @@ -1521,6 +1611,86 @@ class LibvirtVifTestCase(test.NoDBTestCase): """, cfg.to_xml()) + @mock.patch("nova.network.os_vif_util.nova_to_osvif_instance") + @mock.patch("nova.network.os_vif_util.nova_to_osvif_vif") + def test_config_os_vif_agilio_ovs_fallthrough(self, mock_convert_vif, + mock_convert_inst): + mock_convert_vif.return_value = self.os_vif_agilio_ovs + mock_convert_inst.return_value = self.os_vif_inst_info + + d = vif.LibvirtGenericVIFDriver() + hostimpl = host.Host("qemu:///system") + flavor = objects.Flavor(name='m1.small') + image_meta = objects.ImageMeta.from_dict({}) + d = vif.LibvirtGenericVIFDriver() + cfg = d.get_config(self.instance, self.vif_agilio_ovs, + image_meta, flavor, + CONF.libvirt.virt_type, + hostimpl) + + self._assertXmlEqual(""" + + + + + + + + + """, cfg.to_xml()) + + @mock.patch("nova.network.os_vif_util.nova_to_osvif_instance") + @mock.patch("nova.network.os_vif_util.nova_to_osvif_vif") + def test_config_os_vif_agilio_ovs_forwarder(self, mock_convert_vif, + mock_convert_inst): + mock_convert_vif.return_value = self.os_vif_agilio_forwarder + mock_convert_inst.return_value = self.os_vif_inst_info + + d = vif.LibvirtGenericVIFDriver() + hostimpl = host.Host("qemu:///system") + flavor = objects.Flavor(name='m1.small') + image_meta = objects.ImageMeta.from_dict({}) + d = vif.LibvirtGenericVIFDriver() + cfg = d.get_config(self.instance, self.vif_agilio_ovs_forwarder, + image_meta, flavor, + CONF.libvirt.virt_type, + hostimpl) + + self._assertXmlEqual(""" + + + + + """, cfg.to_xml()) + + @mock.patch("nova.network.os_vif_util.nova_to_osvif_instance") + @mock.patch("nova.network.os_vif_util.nova_to_osvif_vif") + def test_config_os_vif_agilio_ovs_direct(self, mock_convert_vif, + mock_convert_inst): + mock_convert_vif.return_value = self.os_vif_agilio_direct + mock_convert_inst.return_value = self.os_vif_inst_info + + d = vif.LibvirtGenericVIFDriver() + hostimpl = host.Host("qemu:///system") + flavor = objects.Flavor(name='m1.small') + image_meta = objects.ImageMeta.from_dict({}) + d = vif.LibvirtGenericVIFDriver() + cfg = d.get_config(self.instance, self.vif_agilio_ovs_direct, + image_meta, flavor, + CONF.libvirt.virt_type, + hostimpl) + + self._assertXmlEqual(""" + + + +
+ + """, cfg.to_xml()) + @mock.patch("nova.network.os_vif_util.nova_to_osvif_instance") @mock.patch("nova.network.os_vif_util.nova_to_osvif_vif") def test_config_os_vif_ovs(self, mock_convert_vif, mock_convert_inst): diff --git a/releasenotes/notes/netronome-smartnic-enablement-d3897fb294429282.yaml b/releasenotes/notes/netronome-smartnic-enablement-d3897fb294429282.yaml new file mode 100644 index 000000000000..63b534e8470a --- /dev/null +++ b/releasenotes/notes/netronome-smartnic-enablement-d3897fb294429282.yaml @@ -0,0 +1,14 @@ +--- +features: + - This release adds support for Netronome's Agilio OVS VIF type. In order + to use the accelerated plugging modes, external Neutron and OS-VIF plugins + are required. Consult + https://github.com/Netronome/agilio-ovs-openstack-plugin for installation + and operation instructions. + Consult the Agilio documentation available at + https://support.netronome.com/ for more information about the plugin + compatibility and support matrix. + - The ``virtio-forwarder`` VNIC type has been added to the list of VNICs. + This VNIC type is intended to request a low-latency virtio port inside the + instance, likely backed by hardware acceleration. Currently the Agilio OVS + external Neutron and OS-VIF plugins provide support for this VNIC mode.