diff --git a/releasenotes/notes/vhost-user-reconnect-fa4cbb731b787f71.yaml b/releasenotes/notes/vhost-user-reconnect-fa4cbb731b787f71.yaml new file mode 100644 index 00000000..0aea5205 --- /dev/null +++ b/releasenotes/notes/vhost-user-reconnect-fa4cbb731b787f71.yaml @@ -0,0 +1,14 @@ +--- +features: + - vhost-user reconnect is a new feature of qemu that + allows a vhost-user frontend(e.g. qemu) to reconnect + to a vhost-user backend (e.g. ovs with dpdk) in the event + that backend is restarted while the interface is in use. + vhost-user reconnect leverages qemu vhost-user server mode + with ovs-dpdk in client mode. This configuration requires + ovs 2.6 with dpdk 16.07 and qemu 2.7 or newer to function. + When qemu server mode is used with older qemu versions such + as 2.5, vhost-user will still function with ovs 2.6 and + dpdk 16.07, however, reconnect functionality will not be + available. + diff --git a/vif_plug_ovs/constants.py b/vif_plug_ovs/constants.py index b1da7431..0908980f 100644 --- a/vif_plug_ovs/constants.py +++ b/vif_plug_ovs/constants.py @@ -11,6 +11,8 @@ # under the License. OVS_VHOSTUSER_INTERFACE_TYPE = 'dpdkvhostuser' +OVS_VHOSTUSER_CLIENT_INTERFACE_TYPE = 'dpdkvhostuserclient' +OVS_VHOSTUSER_PREFIX = 'vhu' OVS_DATAPATH_SYSTEM = 'system' OVS_DATAPATH_NETDEV = 'netdev' diff --git a/vif_plug_ovs/linux_net.py b/vif_plug_ovs/linux_net.py index b8aa762c..59e96995 100644 --- a/vif_plug_ovs/linux_net.py +++ b/vif_plug_ovs/linux_net.py @@ -47,7 +47,8 @@ def _ovs_vsctl(args, timeout=None): def _create_ovs_vif_cmd(bridge, dev, iface_id, mac, - instance_id, interface_type=None): + instance_id, interface_type=None, + vhost_server_path=None): cmd = ['--', '--if-exists', 'del-port', dev, '--', 'add-port', bridge, dev, '--', 'set', 'Interface', dev, @@ -57,6 +58,8 @@ def _create_ovs_vif_cmd(bridge, dev, iface_id, mac, 'external-ids:vm-uuid=%s' % instance_id] if interface_type: cmd += ['type=%s' % interface_type] + if vhost_server_path: + cmd += ['options:vhost-server-path=%s' % vhost_server_path] return cmd @@ -67,13 +70,16 @@ def _create_ovs_bridge_cmd(bridge, datapath_type): @privsep.vif_plug.entrypoint def create_ovs_vif_port(bridge, dev, iface_id, mac, instance_id, - mtu=None, interface_type=None, timeout=None): + mtu=None, interface_type=None, timeout=None, + vhost_server_path=None): _ovs_vsctl(_create_ovs_vif_cmd(bridge, dev, iface_id, - mac, instance_id, - interface_type), timeout=timeout) + mac, instance_id, interface_type, + vhost_server_path), timeout=timeout) # Note at present there is no support for setting the # mtu for vhost-user type ports. - if mtu and interface_type != constants.OVS_VHOSTUSER_INTERFACE_TYPE: + if mtu and interface_type not in [ + constants.OVS_VHOSTUSER_INTERFACE_TYPE, + constants.OVS_VHOSTUSER_CLIENT_INTERFACE_TYPE]: _set_device_mtu(dev, mtu) else: LOG.debug("MTU not set on %(interface_name)s interface " diff --git a/vif_plug_ovs/ovs.py b/vif_plug_ovs/ovs.py index 95f37a36..44b4cabe 100644 --- a/vif_plug_ovs/ovs.py +++ b/vif_plug_ovs/ovs.py @@ -99,10 +99,19 @@ class OvsPlugin(plugin.PluginBase): def _plug_vhostuser(self, vif, instance_info): linux_net.ensure_ovs_bridge(vif.network.bridge, constants.OVS_DATAPATH_NETDEV) - vif_name = OvsPlugin.gen_port_name("vhu", vif.id) + vif_name = OvsPlugin.gen_port_name( + constants.OVS_VHOSTUSER_PREFIX, vif.id) + args = {} + if vif.mode == "client": + args['interface_type'] = \ + constants.OVS_VHOSTUSER_INTERFACE_TYPE + else: + args['interface_type'] = \ + constants.OVS_VHOSTUSER_CLIENT_INTERFACE_TYPE + args['vhost_server_path'] = vif.path + self._create_vif_port( - vif, vif_name, instance_info, - interface_type=constants.OVS_VHOSTUSER_INTERFACE_TYPE) + vif, vif_name, instance_info, **args) def _plug_bridge(self, vif, instance_info): """Plug using hybrid strategy @@ -160,7 +169,9 @@ class OvsPlugin(plugin.PluginBase): def _unplug_vhostuser(self, vif, instance_info): linux_net.delete_ovs_vif_port(vif.network.bridge, - OvsPlugin.gen_port_name("vhu", vif.id), + OvsPlugin.gen_port_name( + constants.OVS_VHOSTUSER_PREFIX, + vif.id), timeout=self.config.ovs_vsctl_timeout) def _unplug_bridge(self, vif, instance_info): diff --git a/vif_plug_ovs/tests/test_linux_net.py b/vif_plug_ovs/tests/test_linux_net.py index 3ab62d70..25a10b69 100644 --- a/vif_plug_ovs/tests/test_linux_net.py +++ b/vif_plug_ovs/tests/test_linux_net.py @@ -131,6 +131,14 @@ class LinuxNetTest(testtools.TestCase): 'fake-type') self.assertEqual(expected, cmd) + expected += ['options:vhost-server-path=/fake/path'] + cmd = linux_net._create_ovs_vif_cmd('fake-bridge', 'fake-dev', + 'fake-iface-id', 'fake-mac', + 'fake-instance-uuid', + 'fake-type', + vhost_server_path='/fake/path') + self.assertEqual(expected, cmd) + @mock.patch.object(linux_net, '_create_ovs_bridge_cmd') @mock.patch.object(linux_net, '_ovs_vsctl') def test_ensure_ovs_bridge(self, mock_vsctl, mock_create_ovs_bridge): @@ -161,7 +169,27 @@ class LinuxNetTest(testtools.TestCase): 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) + "fake-instance-uuid", constants.OVS_VHOSTUSER_INTERFACE_TYPE, + None) + self.assertFalse(mock_set_device_mtu.called) + self.assertTrue(mock_vsctl.called) + + @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_vhostuserclient(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_CLIENT_INTERFACE_TYPE, + vhost_server_path="/fake/path") + mock_create_cmd.assert_called_once_with('fake-bridge', + 'fake-dev', 'fake-iface-id', 'fake-mac', + "fake-instance-uuid", + constants.OVS_VHOSTUSER_CLIENT_INTERFACE_TYPE, + "/fake/path") self.assertFalse(mock_set_device_mtu.called) self.assertTrue(mock_vsctl.called) @@ -176,7 +204,7 @@ class LinuxNetTest(testtools.TestCase): "fake-instance-uuid") mock_create_cmd.assert_called_once_with('fake-bridge', 'fake-dev', 'fake-iface-id', 'fake-mac', - "fake-instance-uuid", None) + "fake-instance-uuid", None, None) 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 884c4b5a..d442591d 100644 --- a/vif_plug_ovs/tests/test_plugin.py +++ b/vif_plug_ovs/tests/test_plugin.py @@ -80,6 +80,14 @@ class PluginTest(testtools.TestCase): mode='client', port_profile=self.profile_ovs) + self.vif_vhostuser_client = 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='server', # qemu server mode <=> ovs client mode + port_profile=self.profile_ovs) + self.instance = objects.instance_info.InstanceInfo( name='demo', uuid='f0000000-0000-0000-0000-000000000001') @@ -242,6 +250,29 @@ class PluginTest(testtools.TestCase): _create_vif_port.assert_has_calls(calls['_create_vif_port']) ensure_ovs_bridge.assert_has_calls(calls['ensure_ovs_bridge']) + @mock.patch.object(linux_net, 'ensure_ovs_bridge') + @mock.patch.object(linux_net, 'create_ovs_vif_port') + def test_plug_ovs_vhostuser_client(self, create_ovs_vif_port, + ensure_ovs_bridge): + 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='dpdkvhostuserclient', + vhost_server_path='/var/run/openvswitch/vhub679325f-ca', + timeout=120)], + 'ensure_ovs_bridge': [mock.call('br0', + constants.OVS_DATAPATH_NETDEV)] + } + + plugin = ovs.OvsPlugin.load("ovs") + plugin.plug(self.vif_vhostuser_client, self.instance) + create_ovs_vif_port.assert_has_calls(calls['create_ovs_vif_port']) + ensure_ovs_bridge.assert_has_calls(calls['ensure_ovs_bridge']) + @mock.patch.object(linux_net, 'delete_ovs_vif_port') def test_unplug_ovs_vhostuser(self, delete_ovs_vif_port): calls = {