diff --git a/releasenotes/notes/add-ovs-vhostuser-support-2ba8de51c1f3a244.yaml b/releasenotes/notes/add-ovs-vhostuser-support-2ba8de51c1f3a244.yaml new file mode 100644 index 00000000..9048e581 --- /dev/null +++ b/releasenotes/notes/add-ovs-vhostuser-support-2ba8de51c1f3a244.yaml @@ -0,0 +1,6 @@ +--- +features: + - The ovs plugin has been extended to support + vhost-user interfaces. vhost-user is a userspace + protocol for high speed virtual networking introduced + in qemu 2.1 and first supported in ovs 2.4 with dpdk 2.0 diff --git a/vif_plug_ovs/constants.py b/vif_plug_ovs/constants.py new file mode 100644 index 00000000..b30959ba --- /dev/null +++ b/vif_plug_ovs/constants.py @@ -0,0 +1,13 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +OVS_VHOSTUSER_INTERFACE_TYPE = 'dpdkvhostuser' diff --git a/vif_plug_ovs/linux_net.py b/vif_plug_ovs/linux_net.py index 6bf7b7bb..a9f7bb3a 100644 --- a/vif_plug_ovs/linux_net.py +++ b/vif_plug_ovs/linux_net.py @@ -25,6 +25,7 @@ from oslo_concurrency import processutils from oslo_log import log as logging from oslo_utils import excutils +from vif_plug_ovs import constants from vif_plug_ovs import exception from vif_plug_ovs.i18n import _LE from vif_plug_ovs import privsep @@ -45,18 +46,35 @@ def _ovs_vsctl(args, timeout=None): raise exception.AgentError(method=full_args) +def _create_ovs_vif_cmd(bridge, dev, iface_id, mac, + instance_id, interface_type=None): + cmd = ['--', '--if-exists', 'del-port', dev, '--', + 'add-port', bridge, dev, + '--', 'set', 'Interface', dev, + 'external-ids:iface-id=%s' % iface_id, + 'external-ids:iface-status=active', + 'external-ids:attached-mac=%s' % mac, + 'external-ids:vm-uuid=%s' % instance_id] + if interface_type: + cmd += ['type=%s' % interface_type] + return cmd + + @privsep.vif_plug.entrypoint -def create_ovs_vif_port(bridge, dev, iface_id, mac, instance_id, mtu, - timeout=None): - _ovs_vsctl(['--', '--if-exists', 'del-port', dev, '--', - 'add-port', bridge, dev, - '--', 'set', 'Interface', dev, - 'external-ids:iface-id=%s' % iface_id, - 'external-ids:iface-status=active', - 'external-ids:attached-mac=%s' % mac, - 'external-ids:vm-uuid=%s' % instance_id], - timeout=timeout) - _set_device_mtu(dev, mtu) +def create_ovs_vif_port(bridge, dev, iface_id, mac, instance_id, + mtu=None, interface_type=None): + _ovs_vsctl(_create_ovs_vif_cmd(bridge, dev, iface_id, + mac, instance_id, + interface_type)) + # Note at present there is no support for setting the + # mtu for vhost-user type ports. + if interface_type != constants.OVS_VHOSTUSER_INTERFACE_TYPE: + _set_device_mtu(dev, mtu) + else: + LOG.debug("MTU not set on %(interface_name)s interface " + "of type %(interface_type)s.", + {'interface_name': dev, + 'interface_type': interface_type}) @privsep.vif_plug.entrypoint diff --git a/vif_plug_ovs/ovs.py b/vif_plug_ovs/ovs.py index 74f97d5c..a854607c 100644 --- a/vif_plug_ovs/ovs.py +++ b/vif_plug_ovs/ovs.py @@ -21,6 +21,7 @@ from os_vif import objects from os_vif import plugin from oslo_config import cfg +from vif_plug_ovs import constants from vif_plug_ovs import exception from vif_plug_ovs import linux_net @@ -52,11 +53,14 @@ class OvsPlugin(plugin.PluginBase): deprecated_group="DEFAULT"), ) + @staticmethod + def gen_port_name(prefix, id): + return ("%s%s" % (prefix, id))[:OvsPlugin.NIC_NAME_LEN] + @staticmethod def get_veth_pair_names(vif): - iface_id = vif.id - return (("qvb%s" % iface_id)[:OvsPlugin.NIC_NAME_LEN], - ("qvo%s" % iface_id)[:OvsPlugin.NIC_NAME_LEN]) + return (OvsPlugin.gen_port_name("qvb", vif.id), + OvsPlugin.gen_port_name("qvo", vif.id)) def describe(self): return objects.host_info.HostPluginInfo( @@ -69,9 +73,23 @@ class OvsPlugin(plugin.PluginBase): objects.host_info.HostVIFInfo( vif_object_name=objects.vif.VIFOpenVSwitch.__name__, min_version="1.0", + max_version="1.0"), + objects.host_info.HostVIFInfo( + vif_object_name=objects.vif.VIFVHostUser.__name__, + min_version="1.0", max_version="1.0") ]) + def _plug_vhostuser(self, vif, instance_info): + linux_net.create_ovs_vif_port( + vif.network.bridge, + OvsPlugin.gen_port_name("vhu", vif.id), + vif.port_profile.interface_id, + vif.address, instance_info.uuid, + self.config.network_device_mtu, + timeout=self.config.ovs_vsctl_timeout, + interface_type=constants.OVS_VHOSTUSER_INTERFACE_TYPE) + def _plug_bridge(self, vif, instance_info): """Plug using hybrid strategy @@ -107,6 +125,13 @@ class OvsPlugin(plugin.PluginBase): if isinstance(vif, objects.vif.VIFBridge): self._plug_bridge(vif, instance_info) + elif isinstance(vif, objects.vif.VIFVHostUser): + self._plug_vhostuser(vif, instance_info) + + def _unplug_vhostuser(self, vif, instance_info): + linux_net.delete_ovs_vif_port(vif.network.bridge, + OvsPlugin.gen_port_name("vhu", vif.id), + timeout=self.config.ovs_vsctl_timeout) def _unplug_bridge(self, vif, instance_info): """UnPlug using hybrid strategy @@ -132,3 +157,5 @@ class OvsPlugin(plugin.PluginBase): if isinstance(vif, objects.vif.VIFBridge): self._unplug_bridge(vif, instance_info) + elif isinstance(vif, objects.vif.VIFVHostUser): + self._unplug_vhostuser(vif, instance_info) diff --git a/vif_plug_ovs/tests/test_linux_net.py b/vif_plug_ovs/tests/test_linux_net.py index ed10ebdf..c445ad51 100644 --- a/vif_plug_ovs/tests/test_linux_net.py +++ b/vif_plug_ovs/tests/test_linux_net.py @@ -18,10 +18,10 @@ import testtools from oslo_concurrency import processutils +from vif_plug_ovs import constants from vif_plug_ovs import linux_net from vif_plug_ovs import privsep - if six.PY2: nested = contextlib.nested else: @@ -118,3 +118,41 @@ class LinuxNetTest(testtools.TestCase): mock.call('ip', 'link', 'set', 'br0', 'up'), mock.call('brctl', 'addif', 'br0', 'vnet1'), ]) + + def test_ovs_vif_port_cmd(self): + expected = ['--', '--if-exists', + 'del-port', 'fake-dev', '--', 'add-port', + 'fake-bridge', 'fake-dev', + '--', 'set', 'Interface', 'fake-dev', + 'external-ids:iface-id=fake-iface-id', + 'external-ids:iface-status=active', + 'external-ids:attached-mac=fake-mac', + 'external-ids:vm-uuid=fake-instance-uuid'] + cmd = linux_net._create_ovs_vif_cmd('fake-bridge', 'fake-dev', + 'fake-iface-id', 'fake-mac', + 'fake-instance-uuid') + + self.assertEqual(expected, cmd) + + expected += ['type=fake-type'] + cmd = linux_net._create_ovs_vif_cmd('fake-bridge', 'fake-dev', + 'fake-iface-id', 'fake-mac', + 'fake-instance-uuid', + 'fake-type') + self.assertEqual(expected, cmd) + + @mock.patch.object(linux_net, '_ovs_vsctl') + @mock.patch.object(linux_net, '_create_ovs_vif_cmd') + @mock.patch.object(linux_net, '_set_device_mtu') + def test_ovs_vif_port_with_type_vhostuser(self, mock_set_device_mtu, + mock_create_cmd, mock_vsctl): + linux_net.create_ovs_vif_port( + 'fake-bridge', + 'fake-dev', 'fake-iface-id', 'fake-mac', + "fake-instance-uuid", mtu=1500, + interface_type=constants.OVS_VHOSTUSER_INTERFACE_TYPE) + mock_create_cmd.assert_called_once_with('fake-bridge', + 'fake-dev', 'fake-iface-id', 'fake-mac', + "fake-instance-uuid", constants.OVS_VHOSTUSER_INTERFACE_TYPE) + self.assertFalse(mock_set_device_mtu.called) + self.assertTrue(mock_vsctl.called) diff --git a/vif_plug_ovs/tests/test_plugin.py b/vif_plug_ovs/tests/test_plugin.py index aadb668a..3f190e4c 100644 --- a/vif_plug_ovs/tests/test_plugin.py +++ b/vif_plug_ovs/tests/test_plugin.py @@ -59,6 +59,7 @@ class PluginTest(testtools.TestCase): self.profile_ovs = objects.vif.VIFPortProfileOpenVSwitch( interface_id='e65867e0-9340-4a7f-a256-09af6eb7a3aa') + self.vif_ovs = objects.vif.VIFBridge( id='b679325f-ca89-4ee0-a8be-6db1409b69ea', address='ca:fe:de:ad:be:ef', @@ -67,6 +68,14 @@ class PluginTest(testtools.TestCase): bridge_name="qbrvif-xxx-yyy", port_profile=self.profile_ovs) + self.vif_vhostuser = objects.vif.VIFVHostUser( + id='b679325f-ca89-4ee0-a8be-6db1409b69ea', + address='ca:fe:de:ad:be:ef', + network=self.network_ovs, + path='/var/run/openvswitch/vhub679325f-ca', + mode='client', + port_profile=self.profile_ovs) + self.instance = objects.instance_info.InstanceInfo( name='demo', uuid='f0000000-0000-0000-0000-000000000001') @@ -120,3 +129,32 @@ class PluginTest(testtools.TestCase): plugin.unplug(self.vif_ovs, self.instance) delete_bridge.assert_has_calls(calls['delete_bridge']) delete_ovs_vif_port.assert_has_calls(calls['delete_ovs_vif_port']) + + def test_plug_ovs_vhostuser(self): + calls = { + 'create_ovs_vif_port': [mock.call( + 'br0', 'vhub679325f-ca', + 'e65867e0-9340-4a7f-a256-09af6eb7a3aa', + 'ca:fe:de:ad:be:ef', + 'f0000000-0000-0000-0000-000000000001', + 1500, + interface_type='dpdkvhostuser', + timeout=120)] + } + + with mock.patch.object(linux_net, 'create_ovs_vif_port') \ + as (create_ovs_vif_port): + plugin = ovs.OvsPlugin.load("ovs") + plugin.plug(self.vif_vhostuser, self.instance) + create_ovs_vif_port.assert_has_calls(calls['create_ovs_vif_port']) + + def test_unplug_ovs_vhostuser(self): + calls = { + 'delete_ovs_vif_port': [mock.call('br0', 'vhub679325f-ca', + timeout=120)] + } + with mock.patch.object(linux_net, 'delete_ovs_vif_port') \ + as delete_ovs_vif_port: + plugin = ovs.OvsPlugin.load("ovs") + plugin.unplug(self.vif_vhostuser, self.instance) + delete_ovs_vif_port.assert_has_calls(calls['delete_ovs_vif_port'])