From 556b4abd2630c29cd96f35d60f6d1fdada76cdec Mon Sep 17 00:00:00 2001 From: Przemyslaw Czesnowicz Date: Tue, 13 Jan 2015 14:16:37 +0000 Subject: [PATCH] Libvirt: Support ovs plug in vhostuser vif This commit adds support for plugging vhostuser ports into ovs bridge. Change-Id: I08687f1d7aca8151119d314a83ff0d7e0d25f622 Blueprint: libvirt-vif-vhost-user --- nova/network/linux_net.py | 4 ++ nova/network/model.py | 7 ++- nova/network/neutronv2/api.py | 6 +++ nova/tests/unit/network/test_linux_net.py | 10 ++++ nova/tests/unit/network/test_neutronv2.py | 26 ++++++++++ nova/tests/unit/virt/libvirt/test_vif.py | 62 ++++++++++++++++++++++- nova/virt/libvirt/vif.py | 31 ++++++++---- 7 files changed, 134 insertions(+), 12 deletions(-) diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py index 129f6666736d..37bf56206bc9 100644 --- a/nova/network/linux_net.py +++ b/nova/network/linux_net.py @@ -1366,6 +1366,10 @@ def delete_ovs_vif_port(bridge, dev): delete_net_dev(dev) +def ovs_set_vhostuser_port_type(dev): + _ovs_vsctl(['--', 'set', 'Interface', dev, 'type=dpdkvhostuser']) + + def create_ivs_vif_port(dev, iface_id, mac, instance_id): utils.execute('ivs-ctl', 'add-port', dev, run_as_root=True) diff --git a/nova/network/model.py b/nova/network/model.py index 1673884c57bc..1f251c17e9df 100644 --- a/nova/network/model.py +++ b/nova/network/model.py @@ -58,8 +58,11 @@ VIF_DETAILS_VLAN = 'vlan' # Sets mode on vhost-user socket, valid values are 'client' # and 'server' VIF_DETAILS_VHOSTUSER_MODE = 'vhostuser_mode' -# Location of the directory to store vhost-user sockets -VIF_DETAILS_VHOSTUSER_DIR = 'vhostuser_sock_dir' +# vhost-user socket path +VIF_DETAILS_VHOSTUSER_SOCKET = 'vhostuser_socket' +# Specifies whether vhost-user socket should be plugged +# into ovs bridge. Valid values are True and False +VIF_DETAILS_VHOSTUSER_OVS_PLUG = 'vhostuser_ovs_plug' # Define supported virtual NIC types. VNIC_TYPE_DIRECT and VNIC_TYPE_MACVTAP # are used for SR-IOV ports diff --git a/nova/network/neutronv2/api.py b/nova/network/neutronv2/api.py index 9b8335dd1b4d..0d4be849cdac 100644 --- a/nova/network/neutronv2/api.py +++ b/nova/network/neutronv2/api.py @@ -1373,6 +1373,7 @@ class API(base_api.NetworkAPI): # Network model metadata should_create_bridge = None vif_type = port.get('binding:vif_type') + port_details = port.get('binding:vif_details') # TODO(berrange) Neutron should pass the bridge name # in another binding metadata field if vif_type == network_model.VIF_TYPE_OVS: @@ -1385,6 +1386,11 @@ class API(base_api.NetworkAPI): # The name of the DVS port group will contain the neutron # network id bridge = port['network_id'] + elif (vif_type == network_model.VIF_TYPE_VHOSTUSER and + port_details.get(network_model.VIF_DETAILS_VHOSTUSER_OVS_PLUG, + False)): + bridge = CONF.neutron.ovs_bridge + ovs_interfaceid = port['id'] # Prune the bridge name if necessary. For the DVS this is not done # as the bridge is a '-'. diff --git a/nova/tests/unit/network/test_linux_net.py b/nova/tests/unit/network/test_linux_net.py index ef3f7a774728..e824541292af 100644 --- a/nova/tests/unit/network/test_linux_net.py +++ b/nova/tests/unit/network/test_linux_net.py @@ -1267,3 +1267,13 @@ class LinuxNetworkTestCase(test.NoDBTestCase): self.driver._exec_ebtables('fake') self.assertEqual(2, len(executes)) self.mox.UnsetStubs() + + def test_ovs_set_vhostuser_type(self): + calls = [ + mock.call('ovs-vsctl', '--timeout=120', '--', 'set', + 'Interface', 'fake-dev', 'type=dpdkvhostuser', + run_as_root=True) + ] + with mock.patch.object(utils, 'execute', return_value=('', '')) as ex: + linux_net.ovs_set_vhostuser_port_type('fake-dev') + ex.assert_has_calls(calls) diff --git a/nova/tests/unit/network/test_neutronv2.py b/nova/tests/unit/network/test_neutronv2.py index 3fd29a8f581d..df081ff7a88e 100644 --- a/nova/tests/unit/network/test_neutronv2.py +++ b/nova/tests/unit/network/test_neutronv2.py @@ -2412,6 +2412,32 @@ class TestNeutronv2(TestNeutronv2Base): self.assertEqual('net-id1', net['id']) self.assertEqual('tenant', net['meta']['tenant_id']) + def test_nw_info_build_network_vhostuser(self): + fake_port = { + 'fixed_ips': [{'ip_address': '1.1.1.1'}], + 'id': 'port-id', + 'network_id': 'net-id', + 'binding:vif_type': model.VIF_TYPE_VHOSTUSER, + 'binding:vif_details': { + model.VIF_DETAILS_VHOSTUSER_OVS_PLUG: True + } + } + fake_subnets = [model.Subnet(cidr='1.0.0.0/8')] + fake_nets = [{'id': 'net-id', 'name': 'foo', 'tenant_id': 'tenant'}] + api = neutronapi.API() + self.mox.ReplayAll() + neutronapi.get_client('fake') + net, iid = api._nw_info_build_network(fake_port, fake_nets, + fake_subnets) + self.assertEqual(net['subnets'], fake_subnets) + self.assertEqual(net['id'], 'net-id') + self.assertEqual(net['label'], 'foo') + self.assertEqual(net.get_meta('tenant_id'), 'tenant') + self.assertEqual(net.get_meta('injected'), CONF.flat_injected) + self.assertEqual(net['bridge'], CONF.neutron.ovs_bridge) + self.assertNotIn('should_create_bridge', net) + self.assertEqual(iid, 'port-id') + def test_build_network_info_model(self): api = neutronapi.API() fake_inst = {'project_id': 'fake', 'uuid': 'uuid', diff --git a/nova/tests/unit/virt/libvirt/test_vif.py b/nova/tests/unit/virt/libvirt/test_vif.py index f68b8363ccc8..3b7883f84365 100644 --- a/nova/tests/unit/virt/libvirt/test_vif.py +++ b/nova/tests/unit/virt/libvirt/test_vif.py @@ -261,12 +261,25 @@ class LibvirtVifTestCase(test.NoDBTestCase): type=network_model.VIF_TYPE_IOVISOR, devname='tap-xxx-yyy-zzz', ovs_interfaceid=None) + vif_vhostuser = network_model.VIF(id='vif-xxx-yyy-zzz', address='ca:fe:de:ad:be:ef', network=network_bridge, type=network_model.VIF_TYPE_VHOSTUSER, details = {network_model.VIF_DETAILS_VHOSTUSER_MODE: 'client', - network_model.VIF_DETAILS_VHOSTUSER_DIR: '/tmp'} + network_model.VIF_DETAILS_VHOSTUSER_SOCKET: + '/tmp/vif-xxx-yyy-zzz'} + ) + + vif_vhostuser_ovs = network_model.VIF(id='vif-xxx-yyy-zzz', + address='ca:fe:de:ad:be:ef', + network=network_bridge, + type=network_model.VIF_TYPE_VHOSTUSER, + 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' ) vif_vhostuser_no_path = network_model.VIF(id='vif-xxx-yyy-zzz', @@ -1060,3 +1073,50 @@ class LibvirtVifTestCase(test.NoDBTestCase): self._get_instance_xml, d, self.vif_vhostuser_no_path) + + def test_vhostuser_driver_ovs(self): + d = vif.LibvirtGenericVIFDriver() + xml = self._get_instance_xml(d, + self.vif_vhostuser_ovs) + 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_vhostuser_ovs) + self._assertModel(xml, network_model.VIF_MODEL_VIRTIO) + + def test_vhostuser_ovs_plug(self): + + calls = { + 'create_ovs_vif_port': [mock.call('br0', + 'usv-xxx-yyy-zzz', + 'aaa-bbb-ccc', + 'ca:fe:de:ad:be:ef', + 'instance-uuid')], + 'ovs_set_vhostuser_port_type': [mock.call('usv-xxx-yyy-zzz')] + } + with contextlib.nested( + mock.patch.object(linux_net, 'create_ovs_vif_port'), + mock.patch.object(linux_net, 'ovs_set_vhostuser_port_type') + ) as (create_ovs_vif_port, ovs_set_vhostuser_port_type): + d = vif.LibvirtGenericVIFDriver() + d.plug_vhostuser(self.instance, self.vif_vhostuser_ovs) + create_ovs_vif_port.assert_has_calls(calls['create_ovs_vif_port']) + ovs_set_vhostuser_port_type.assert_has_calls( + calls['ovs_set_vhostuser_port_type']) + + def test_vhostuser_ovs_unplug(self): + calls = { + 'delete_ovs_vif_port': [mock.call('br0', 'usv-xxx-yyy-zzz')] + } + with mock.patch.object(linux_net, + 'delete_ovs_vif_port') as delete_port: + d = vif.LibvirtGenericVIFDriver() + d.unplug_vhostuser(None, self.vif_vhostuser_ovs) + delete_port.assert_has_calls(calls['delete_ovs_vif_port']) diff --git a/nova/virt/libvirt/vif.py b/nova/virt/libvirt/vif.py index 8ccb1047f949..5bbc5caf1f83 100644 --- a/nova/virt/libvirt/vif.py +++ b/nova/virt/libvirt/vif.py @@ -337,15 +337,11 @@ class LibvirtGenericVIFDriver(object): vif_details = vif['details'] mode = vif_details.get(network_model.VIF_DETAILS_VHOSTUSER_MODE, 'server') - path = vif_details.get(network_model.VIF_DETAILS_VHOSTUSER_DIR) - if path is None: + sock_path = vif_details.get(network_model.VIF_DETAILS_VHOSTUSER_SOCKET) + if sock_path is None: raise exception.VifDetailsMissingVhostuserSockPath( vif_id=vif['id']) - - designer.set_vif_host_backend_vhostuser_config( - conf, - mode, - os.path.join(path, vif['id'])) + designer.set_vif_host_backend_vhostuser_config(conf, mode, sock_path) return conf def get_config(self, instance, vif, image_meta, @@ -530,7 +526,17 @@ class LibvirtGenericVIFDriver(object): LOG.exception(_LE("Failed while plugging vif"), instance=instance) def plug_vhostuser(self, instance, vif): - pass + ovs_plug = vif['details'].get( + network_model.VIF_DETAILS_VHOSTUSER_OVS_PLUG, + False) + if ovs_plug: + iface_id = self.get_ovs_interfaceid(vif) + port_name = os.path.basename( + vif['details'][network_model.VIF_DETAILS_VHOSTUSER_SOCKET]) + linux_net.create_ovs_vif_port(self.get_bridge_name(vif), + port_name, iface_id, vif['address'], + instance['uuid']) + linux_net.ovs_set_vhostuser_port_type(port_name) def plug(self, instance, vif): vif_type = vif['type'] @@ -686,7 +692,14 @@ class LibvirtGenericVIFDriver(object): instance=instance) def unplug_vhostuser(self, instance, vif): - pass + ovs_plug = vif['details'].get( + network_model.VIF_DETAILS_VHOSTUSER_OVS_PLUG, + False) + if ovs_plug: + port_name = os.path.basename( + vif['details'][network_model.VIF_DETAILS_VHOSTUSER_SOCKET]) + linux_net.delete_ovs_vif_port(self.get_bridge_name(vif), + port_name) def unplug(self, instance, vif): vif_type = vif['type']