From 97745f2bee288f1f88649f92e3c3e377d3e014fa Mon Sep 17 00:00:00 2001 From: jianghuaw Date: Sun, 8 Nov 2015 14:17:51 +0000 Subject: [PATCH] XenAPI: OVS agent updates the wrong port with Neutron With xen hypervisor, it will create two sets of port/interface for each VM vif: One is named as tapx.y which is qemu-emulated NIC and is used when no PV drivers installed with the VM; The other one is named as vifx.0 which is the xen network frontend NIC and is used when VM has PV drivers installed. But neutron only handle one port. If the finally used port is not the one handled by neutron, the guest VM will have no network access. The solution is to change nova xenapi to add a bridge, which we call the 'interim bridge', between the VM and the OVS integration bridge. In this way it will only expose to neutron with the integration bridge's port which connected to the interim bridge. So it will fix the issue mentioned above. Closes-Bug: #1268955 Change-Id: I0cfc0284e1fcd1a6169d31a7ad410716037e5cc2 --- nova/exception.py | 5 + .../unit/virt/xenapi/client/test_objects.py | 5 + .../unit/virt/xenapi/client/test_session.py | 4 + nova/tests/unit/virt/xenapi/test_vif.py | 146 ++++++++++++--- nova/virt/xenapi/client/objects.py | 6 + nova/virt/xenapi/client/session.py | 3 +- nova/virt/xenapi/fake.py | 2 +- nova/virt/xenapi/vif.py | 169 +++++++++++++++++- nova/virt/xenapi/vmops.py | 21 ++- .../etc/xapi.d/plugins/nova_plugin_version | 3 +- .../xenapi/etc/xapi.d/plugins/xenhost | 65 ++++++- 11 files changed, 397 insertions(+), 32 deletions(-) diff --git a/nova/exception.py b/nova/exception.py index e5135253e6..de5145f92a 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -181,6 +181,11 @@ class VirtualInterfacePlugException(NovaException): msg_fmt = _("Virtual interface plugin failed") +class VirtualInterfaceUnplugException(NovaException): + msg_fmt = _("Virtual interface unplugin failed: " + "%(reason)s") + + class GlanceConnectionFailed(NovaException): msg_fmt = _("Connection to glance host %(server)s failed: " "%(reason)s") diff --git a/nova/tests/unit/virt/xenapi/client/test_objects.py b/nova/tests/unit/virt/xenapi/client/test_objects.py index efaf17a9c7..6730745df9 100644 --- a/nova/tests/unit/virt/xenapi/client/test_objects.py +++ b/nova/tests/unit/virt/xenapi/client/test_objects.py @@ -55,6 +55,11 @@ class ObjectsTestCase(stubs.XenAPITestBaseNoDB): vdi.get_X("ref") self.session.call_xenapi.assert_called_once_with("VDI.get_X", "ref") + def test_VIF(self): + vdi = objects.VIF(self.session) + vdi.get_X("ref") + self.session.call_xenapi.assert_called_once_with("VIF.get_X", "ref") + def test_VBD(self): vbd = objects.VBD(self.session) vbd.get_X("ref") diff --git a/nova/tests/unit/virt/xenapi/client/test_session.py b/nova/tests/unit/virt/xenapi/client/test_session.py index 16c352feac..f9b490eca9 100644 --- a/nova/tests/unit/virt/xenapi/client/test_session.py +++ b/nova/tests/unit/virt/xenapi/client/test_session.py @@ -100,6 +100,10 @@ class ApplySessionHelpersTestCase(stubs.XenAPITestBaseNoDB): self.session.VDI.get_X("ref") self.session.call_xenapi.assert_called_once_with("VDI.get_X", "ref") + def test_apply_session_helpers_add_VIF(self): + self.session.VIF.get_X("ref") + self.session.call_xenapi.assert_called_once_with("VIF.get_X", "ref") + def test_apply_session_helpers_add_VBD(self): self.session.VBD.get_X("ref") self.session.call_xenapi.assert_called_once_with("VBD.get_X", "ref") diff --git a/nova/tests/unit/virt/xenapi/test_vif.py b/nova/tests/unit/virt/xenapi/test_vif.py index a41e506451..ebd3a9a85c 100644 --- a/nova/tests/unit/virt/xenapi/test_vif.py +++ b/nova/tests/unit/virt/xenapi/test_vif.py @@ -17,6 +17,7 @@ import mock from nova import exception from nova.network import model +from nova import test from nova.tests.unit.virt.xenapi import stubs from nova.virt.xenapi import network_utils from nova.virt.xenapi import vif @@ -67,6 +68,22 @@ class XenVIFDriverTestBase(stubs.XenAPITestBaseNoDB): self._session = mock.Mock() self._session.call_xenapi.side_effect = fake_call_xenapi + def mock_patch_object(self, target, attribute, return_val=None, + side_effect=None): + """Utilility function to mock object's attribute at runtime: + Some methods are dynamic, so standard mocking does not work + and we need to mock them at runtime. + e.g. self._session.VIF.get_record which is dynamically + created via the override function of __getattr__. + """ + + patcher = mock.patch.object(target, attribute, + return_value=return_val, + side_effect=side_effect) + mock_one = patcher.start() + self.addCleanup(patcher.stop) + return mock_one + class XenVIFDriverTestCase(XenVIFDriverTestBase): def setUp(self): @@ -149,41 +166,126 @@ class XenAPIBridgeDriverTestCase(XenVIFDriverTestBase, object): ret_vif_ref = self.bridge_driver.plug(instance, vif, vm_ref, device) self.assertEqual('fake_vif_ref', ret_vif_ref) - @mock.patch.object(vif.XenVIFDriver, '_get_vif_ref', - return_value='fake_vif_ref') - def test_unplug(self, mock_get_vif_ref): - instance = {'name': "fake_instance"} - vm_ref = "fake_vm_ref" - self.bridge_driver.unplug(instance, fake_vif, vm_ref) - - expected = [mock.call('VIF.destroy', 'fake_vif_ref')] - self.assertEqual(expected, self._session.call_xenapi.call_args_list) - class XenAPIOpenVswitchDriverTestCase(XenVIFDriverTestBase): def setUp(self): super(XenAPIOpenVswitchDriverTestCase, self).setUp() self.ovs_driver = vif.XenAPIOpenVswitchDriver(self._session) - @mock.patch.object(network_utils, 'find_network_with_bridge', - return_value='fake_network_ref') @mock.patch.object(vif.XenVIFDriver, '_create_vif', return_value='fake_vif_ref') + @mock.patch.object(vif.XenAPIOpenVswitchDriver, + 'create_vif_interim_network') @mock.patch.object(vif.XenVIFDriver, '_get_vif_ref', return_value=None) - def test_plug(self, mock_get_vif_ref, mock_create_vif, - mock_find_network_with_bridge): + @mock.patch.object(vif.vm_utils, 'lookup', return_value='fake_vm_ref') + def test_plug(self, mock_lookup, mock_get_vif_ref, + mock_create_vif_interim_network, + mock_create_vif): instance = {'name': "fake_instance_name"} - vm_ref = "fake_vm_ref" - device = 1 - ret_vif_ref = self.ovs_driver.plug(instance, fake_vif, vm_ref, device) + ret_vif_ref = self.ovs_driver.plug( + instance, fake_vif, vm_ref=None, device=1) + self.assertTrue(mock_lookup.called) + self.assertTrue(mock_get_vif_ref.called) + self.assertTrue(mock_create_vif_interim_network.called) + self.assertTrue(mock_create_vif.called) self.assertEqual('fake_vif_ref', ret_vif_ref) - @mock.patch.object(vif.XenVIFDriver, '_get_vif_ref', - return_value='fake_vif_ref') - def test_unplug(self, mock_get_vif_ref): + @mock.patch.object(vif.XenAPIOpenVswitchDriver, '_ovs_del_br') + @mock.patch.object(vif.XenAPIOpenVswitchDriver, '_ovs_del_port') + @mock.patch.object(network_utils, 'find_network_with_name_label', + return_value='fake_network') + @mock.patch.object(vif.XenVIFDriver, 'unplug') + def test_unplug(self, mock_super_unplug, + mock_find_network_with_name_label, + mock_ovs_del_port, + mock_ovs_del_br): instance = {'name': "fake_instance"} vm_ref = "fake_vm_ref" + + mock_network_get_VIFs = self.mock_patch_object( + self._session.network, 'get_VIFs', return_val=None) + mock_network_get_bridge = self.mock_patch_object( + self._session.network, 'get_bridge', return_val='fake_bridge') + mock_network_destroy = self.mock_patch_object( + self._session.network, 'destroy') self.ovs_driver.unplug(instance, fake_vif, vm_ref) - expected = [mock.call('VIF.destroy', 'fake_vif_ref')] - self.assertEqual(expected, self._session.call_xenapi.call_args_list) + self.assertTrue(mock_super_unplug.called) + self.assertTrue(mock_find_network_with_name_label.called) + self.assertTrue(mock_network_get_VIFs.called) + self.assertTrue(mock_network_get_bridge.called) + self.assertEqual(mock_ovs_del_port.call_count, 2) + self.assertTrue(mock_network_destroy.called) + + @mock.patch.object(vif.XenAPIOpenVswitchDriver, '_ovs_del_br') + @mock.patch.object(vif.XenAPIOpenVswitchDriver, '_ovs_del_port') + @mock.patch.object(network_utils, 'find_network_with_name_label', + return_value='fake_network') + @mock.patch.object(vif.XenVIFDriver, 'unplug') + def test_unplug_exception(self, mock_super_unplug, + mock_find_network_with_name_label, + mock_ovs_del_port, + mock_ovs_del_br): + instance = {'name': "fake_instance"} + vm_ref = "fake_vm_ref" + + self.mock_patch_object( + self._session.network, 'get_VIFs', return_val=None) + self.mock_patch_object( + self._session.network, 'get_bridge', return_val='fake_bridge') + self.mock_patch_object( + self._session.network, 'destroy', + side_effect=test.TestingException) + + self.assertRaises(exception.VirtualInterfaceUnplugException, + self.ovs_driver.unplug, instance, fake_vif, + vm_ref) + + @mock.patch.object(vif.XenAPIOpenVswitchDriver, '_ovs_add_patch_port') + @mock.patch.object(vif.XenAPIOpenVswitchDriver, '_ovs_map_external_ids') + def test_post_start_actions(self, mock_ovs_map_external_ids, + mock_ovs_add_patch_port): + vif_ref = "fake_vif_ref" + instance = {'name': 'fake_instance_name'} + fake_vif_rec = {'uuid': fake_vif['uuid'], + 'MAC': fake_vif['address'], + 'network': 'fake_network', + 'other_config': { + 'nicira-iface-id': 'fake-nicira-iface-id'} + } + mock_VIF_get_record = self.mock_patch_object( + self._session.VIF, 'get_record', return_val=fake_vif_rec) + mock_network_get_bridge = self.mock_patch_object( + self._session.network, 'get_bridge', + return_val='fake_bridge_name') + mock_network_get_uuid = self.mock_patch_object( + self._session.network, 'get_uuid', + return_val='fake_network_uuid') + + self.ovs_driver.post_start_actions(instance, vif_ref) + + self.assertTrue(mock_VIF_get_record.called) + self.assertTrue(mock_network_get_bridge.called) + self.assertTrue(mock_network_get_uuid.called) + self.assertEqual(mock_ovs_add_patch_port.call_count, 2) + self.assertTrue(mock_ovs_map_external_ids.called) + + @mock.patch.object(network_utils, 'find_network_with_name_label', + return_value="exist_network_ref") + def test_create_vif_interim_network_exist(self, + mock_find_network_with_name_label): + mock_network_create = self.mock_patch_object( + self._session.network, 'create', return_val='new_network_ref') + network_ref = self.ovs_driver.create_vif_interim_network(fake_vif) + self.assertFalse(mock_network_create.called) + self.assertEqual(network_ref, 'exist_network_ref') + + @mock.patch.object(network_utils, 'find_network_with_name_label', + return_value=None) + def test_create_vif_interim_network_new(self, + mock_find_network_with_name_label): + mock_network_create = self.mock_patch_object( + self._session.network, 'create', return_val='new_network_ref') + network_ref = self.ovs_driver.create_vif_interim_network(fake_vif) + self.assertTrue(mock_network_create.called) + self.assertEqual(network_ref, 'new_network_ref') diff --git a/nova/virt/xenapi/client/objects.py b/nova/virt/xenapi/client/objects.py index 5cc91eb4c7..7f7215c047 100644 --- a/nova/virt/xenapi/client/objects.py +++ b/nova/virt/xenapi/client/objects.py @@ -100,6 +100,12 @@ class VDI(XenAPISessionObject): super(VDI, self).__init__(session, "VDI") +class VIF(XenAPISessionObject): + """Virtual Network Interface.""" + def __init__(self, session): + super(VIF, self).__init__(session, "VIF") + + class SR(XenAPISessionObject): """Storage Repository.""" def __init__(self, session): diff --git a/nova/virt/xenapi/client/session.py b/nova/virt/xenapi/client/session.py index cc5dd39248..ada1b7b927 100644 --- a/nova/virt/xenapi/client/session.py +++ b/nova/virt/xenapi/client/session.py @@ -53,6 +53,7 @@ def apply_session_helpers(session): session.VM = cli_objects.VM(session) session.SR = cli_objects.SR(session) session.VDI = cli_objects.VDI(session) + session.VIF = cli_objects.VIF(session) session.VBD = cli_objects.VBD(session) session.PBD = cli_objects.PBD(session) session.PIF = cli_objects.PIF(session) @@ -69,7 +70,7 @@ class XenAPISession(object): # changed in development environments. # MAJOR VERSION: Incompatible changes with the plugins # MINOR VERSION: Compatible changes, new plguins, etc - PLUGIN_REQUIRED_VERSION = '1.4' + PLUGIN_REQUIRED_VERSION = '1.5' def __init__(self, url, user, pw): version_string = version.version_string_with_package() diff --git a/nova/virt/xenapi/fake.py b/nova/virt/xenapi/fake.py index 75fab189f1..b368dac2fa 100644 --- a/nova/virt/xenapi/fake.py +++ b/nova/virt/xenapi/fake.py @@ -760,7 +760,7 @@ class SessionBase(object): return base64.b64encode(zlib.compress("dom_id: %s" % dom_id)) def _plugin_nova_plugin_version_get_version(self, method, args): - return pickle.dumps("1.4") + return pickle.dumps("1.5") def _plugin_xenhost_query_gc(self, method, args): return pickle.dumps("False") diff --git a/nova/virt/xenapi/vif.py b/nova/virt/xenapi/vif.py index f671736026..79d27fcb56 100644 --- a/nova/virt/xenapi/vif.py +++ b/nova/virt/xenapi/vif.py @@ -23,6 +23,7 @@ import nova.conf from nova import exception from nova.i18n import _ from nova.i18n import _LW +from nova.network import model as network_model from nova.virt.xenapi import network_utils from nova.virt.xenapi import vm_utils @@ -180,11 +181,18 @@ class XenAPIBridgeDriver(XenVIFDriver): def unplug(self, instance, vif, vm_ref): super(XenAPIBridgeDriver, self).unplug(instance, vif, vm_ref) + def post_start_actions(self, instance, vif_ref): + """no further actions needed for this driver type""" + pass + class XenAPIOpenVswitchDriver(XenVIFDriver): """VIF driver for Open vSwitch with XenAPI.""" def plug(self, instance, vif, vm_ref=None, device=None): + """create an interim network for this vif; and build + the vif_rec which will be used by xapi to create VM vif + """ if not vm_ref: vm_ref = vm_utils.lookup(self._session, instance['name']) @@ -198,10 +206,10 @@ class XenAPIOpenVswitchDriver(XenVIFDriver): if not device: device = 0 - # with OVS model, always plug into an OVS integration bridge - # that is already created - network_ref = network_utils.find_network_with_bridge( - self._session, CONF.xenserver.ovs_integration_bridge) + # Create an interim network for each VIF, so dom0 has a single + # bridge for each device (the emulated and PV ethernet devices + # will both be on this bridge. + network_ref = self.create_vif_interim_network(vif) vif_rec = {} vif_rec['device'] = str(device) vif_rec['network'] = network_ref @@ -216,4 +224,157 @@ class XenAPIOpenVswitchDriver(XenVIFDriver): return self._create_vif(vif, vif_rec, vm_ref) def unplug(self, instance, vif, vm_ref): + """unplug vif: + 1. unplug and destroy vif. + 2. delete the patch port pair between the integration bridge and + the interim network. + 3. destroy the interim network + 4. delete the OVS bridge service for the interim network + """ super(XenAPIOpenVswitchDriver, self).unplug(instance, vif, vm_ref) + + net_name = self.get_vif_interim_net_name(vif) + network = network_utils.find_network_with_name_label( + self._session, net_name) + if network is None: + return + vifs = self._session.network.get_VIFs(network) + if vifs: + # only remove the interim network when it's empty. + # for resize/migrate on local host, vifs on both of the + # source and target VM will be connected to the same + # interim network. + return + LOG.debug('destroying patch port pair for vif: vif_id=%(vif_id)s', + {'vif_id': vif['id']}) + bridge_name = self._session.network.get_bridge(network) + patch_port1, patch_port2 = self._get_patch_port_pair_names(vif['id']) + try: + # delete the patch port pair + self._ovs_del_port(bridge_name, patch_port1) + self._ovs_del_port(CONF.xenserver.ovs_integration_bridge, + patch_port2) + except Exception as e: + LOG.warn(_LW("Failed to delete patch port pair for vif %(if)s," + " exception:%(exception)s"), + {'if': vif, 'exception': e}, instance=instance) + raise exception.VirtualInterfaceUnplugException( + reason=_("Failed to delete patch port pair")) + + LOG.debug('destroying network: network=%(network)s,' + 'bridge=%(br)s', + {'network': network, 'br': bridge_name}) + try: + self._session.network.destroy(network) + # delete bridge if it still exists. + # As there is patch port existing on this bridge when destroying + # the VM vif (which happens when shutdown the VM), the bridge + # won't be destroyed automatically by XAPI. So let's destroy it + # at here. + self._ovs_del_br(bridge_name) + except Exception as e: + LOG.warn(_LW("Failed to delete bridge for vif %(if)s, " + "exception:%(exception)s"), + {'if': vif, 'exception': e}, instance=instance) + raise exception.VirtualInterfaceUnplugException( + reason=_("Failed to delete bridge")) + + def post_start_actions(self, instance, vif_ref): + """Do needed actions post vif start: + plug the interim ovs bridge to the integration bridge; + set external_ids to the int-br port which will service + for this vif. + """ + vif_rec = self._session.VIF.get_record(vif_ref) + network_ref = vif_rec['network'] + bridge_name = self._session.network.get_bridge(network_ref) + network_uuid = self._session.network.get_uuid(network_ref) + iface_id = vif_rec['other_config']['nicira-iface-id'] + patch_port1, patch_port2 = self._get_patch_port_pair_names(iface_id) + LOG.debug('plug_ovs_bridge: port1=%(port1)s, port2=%(port2)s,' + 'network_uuid=%(uuid)s, bridge_name=%(bridge_name)s', + {'port1': patch_port1, 'port2': patch_port2, + 'uuid': network_uuid, 'bridge_name': bridge_name}) + if bridge_name is None: + raise exception.VirtualInterfacePlugException( + _("Failed to find bridge for vif")) + + self._ovs_add_patch_port(bridge_name, patch_port1, patch_port2) + self._ovs_add_patch_port(CONF.xenserver.ovs_integration_bridge, + patch_port2, patch_port1) + self._ovs_map_external_ids(patch_port2, vif_rec) + + def get_vif_interim_net_name(self, vif): + return ("net-" + vif['id'])[:network_model.NIC_NAME_LEN] + + def create_vif_interim_network(self, vif): + net_name = self.get_vif_interim_net_name(vif) + network_rec = {'name_label': net_name, + 'name_description': "interim network for vif", + 'other_config': {}} + network_ref = network_utils.find_network_with_name_label( + self._session, net_name) + if network_ref: + # already exist, just return + # in some scenarios: e..g resize/migrate, it won't create new + # interim network. + return network_ref + try: + network_ref = self._session.network.create(network_rec) + except Exception as e: + LOG.warn(_LW("Failed to create interim network for vif %(if)s, " + "exception:%(exception)s"), + {'if': vif, 'exception': e}) + raise exception.VirtualInterfacePlugException( + _("Failed to create the interim network for vif")) + return network_ref + + def _get_patch_port_pair_names(self, iface_id): + return (("pp1-%s" % iface_id)[:network_model.NIC_NAME_LEN], + ("pp2-%s" % iface_id)[:network_model.NIC_NAME_LEN]) + + def _ovs_add_patch_port(self, bridge_name, port_name, peer_port_name): + cmd = 'ovs_add_patch_port' + args = {'bridge_name': bridge_name, + 'port_name': port_name, + 'peer_port_name': peer_port_name + } + self._exec_dom0_cmd(cmd, args) + + def _ovs_del_port(self, bridge_name, port_name): + cmd = 'ovs_del_port' + args = {'bridge_name': bridge_name, + 'port_name': port_name + } + self._exec_dom0_cmd(cmd, args) + + def _ovs_del_br(self, bridge_name): + cmd = 'ovs_del_br' + args = {'bridge_name': bridge_name} + self._exec_dom0_cmd(cmd, args) + + def _ovs_set_if_external_id(self, interface, extneral_id, value): + cmd = 'ovs_set_if_external_id' + args = {'interface': interface, + 'extneral_id': extneral_id, + 'value': value} + self._exec_dom0_cmd(cmd, args) + + def _ovs_map_external_ids(self, interface, vif_rec): + '''set external ids on the integration bridge vif + ''' + mac = vif_rec['MAC'] + iface_id = vif_rec['other_config']['nicira-iface-id'] + vif_uuid = vif_rec['uuid'] + status = 'active' + + self._ovs_set_if_external_id(interface, 'attached-mac', mac) + self._ovs_set_if_external_id(interface, 'iface-id', iface_id) + self._ovs_set_if_external_id(interface, 'xs-vif-uuid', vif_uuid) + self._ovs_set_if_external_id(interface, 'iface-status', status) + + def _exec_dom0_cmd(self, cmd, cmd_args): + args = {'cmd': cmd, + 'args': cmd_args + } + self._session.call_plugin_serialized('xenhost', 'network_config', args) diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 69b5e89e7a..206412f149 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -333,6 +333,18 @@ class VMOps(object): if bad_volumes_callback and bad_devices: bad_volumes_callback(bad_devices) + # Do some operations which have to be done after start: + # e.g. The vif's interim bridge won't be created until VM starts. + # So the operations on the interim bridge have be done after + # start. + self._post_start_actions(instance) + + def _post_start_actions(self, instance): + vm_ref = vm_utils.lookup(self._session, instance['name']) + vif_refs = self._session.call_xenapi("VM.get_VIFs", vm_ref) + for vif_ref in vif_refs: + self.vif_driver.post_start_actions(instance, vif_ref) + def _get_vdis_for_instance(self, context, instance, name_label, image_meta, image_type, block_device_info): """Create or connect to all virtual disks for this instance.""" @@ -1920,13 +1932,20 @@ class VMOps(object): """Creates vifs for an instance.""" LOG.debug("Creating vifs", instance=instance) + vif_refs = [] # this function raises if vm_ref is not a vm_opaque_ref self._session.call_xenapi("VM.get_domid", vm_ref) for device, vif in enumerate(network_info): LOG.debug('Create VIF %s', vif, instance=instance) - self.vif_driver.plug(instance, vif, vm_ref=vm_ref, device=device) + vif_ref = self.vif_driver.plug(instance, vif, + vm_ref=vm_ref, device=device) + vif_refs.append(vif_ref) + + LOG.debug('Created the vif_refs: %(vifs)s for VM name: %(name)s', + {'vifs': vif_refs, 'name': instance['name']}, + instance=instance) def plug_vifs(self, instance, network_info): """Set up VIF networking on the host.""" diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/nova_plugin_version b/plugins/xenserver/xenapi/etc/xapi.d/plugins/nova_plugin_version index bec3a96f88..e82a41396a 100755 --- a/plugins/xenserver/xenapi/etc/xapi.d/plugins/nova_plugin_version +++ b/plugins/xenserver/xenapi/etc/xapi.d/plugins/nova_plugin_version @@ -30,7 +30,8 @@ import utils # 1.2 - Added support for pci passthrough devices # 1.3 - Add vhd2 functions for doing glance operations by url # 1.4 - Add support of Glance v2 api -PLUGIN_VERSION = "1.4" +# 1.5 - Added function for network configuration on ovs bridge +PLUGIN_VERSION = "1.5" def get_version(session): diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/xenhost b/plugins/xenserver/xenapi/etc/xapi.d/plugins/xenhost index 7417a3cd46..ed0ff0fd13 100755 --- a/plugins/xenserver/xenapi/etc/xapi.d/plugins/xenhost +++ b/plugins/xenserver/xenapi/etc/xapi.d/plugins/xenhost @@ -217,6 +217,65 @@ def iptables_config(session, args): raise pluginlib.PluginError(_("Invalid iptables command")) +def _ovs_add_patch_port(args): + bridge_name = pluginlib.exists(args, 'bridge_name') + port_name = pluginlib.exists(args, 'port_name') + peer_port_name = pluginlib.exists(args, 'peer_port_name') + cmd_args = ['ovs-vsctl', '--', '--if-exists', 'del-port', + port_name, '--', 'add-port', bridge_name, port_name, + '--', 'set', 'interface', port_name, + 'type=patch', 'options:peer=%s' % peer_port_name] + return _run_command(cmd_args) + + +def _ovs_del_port(args): + bridge_name = pluginlib.exists(args, 'bridge_name') + port_name = pluginlib.exists(args, 'port_name') + cmd_args = ['ovs-vsctl', '--', '--if-exists', 'del-port', + bridge_name, port_name] + return _run_command(cmd_args) + + +def _ovs_del_br(args): + bridge_name = pluginlib.exists(args, 'bridge_name') + cmd_args = ['ovs-vsctl', '--', '--if-exists', + 'del-br', bridge_name] + return _run_command(cmd_args) + + +def _ovs_set_if_external_id(args): + interface = pluginlib.exists(args, 'interface') + extneral_id = pluginlib.exists(args, 'extneral_id') + value = pluginlib.exists(args, 'value') + cmd_args = ['ovs-vsctl', 'set', 'Interface', interface, + 'external-ids:%s=%s' % (extneral_id, value)] + return _run_command(cmd_args) + + +ALLOWED_NETWORK_CMDS = { + # allowed cmds to config OVS bridge + 'ovs_add_patch_port': _ovs_add_patch_port, + 'ovs_del_port': _ovs_del_port, + 'ovs_del_br': _ovs_del_br, + 'ovs_set_if_external_id': _ovs_set_if_external_id + } + + +def network_config(session, args): + """network config functions""" + cmd = pluginlib.exists(args, 'cmd') + if not isinstance(cmd, basestring): + msg = _("invalid command '%s'") % str(cmd) + raise pluginlib.PluginError(msg) + return + if cmd not in ALLOWED_NETWORK_CMDS: + msg = _("Dom0 execution of '%s' is not permitted") % cmd + raise pluginlib.PluginError(msg) + return + cmd_args = pluginlib.exists(args, 'args') + return ALLOWED_NETWORK_CMDS[cmd](cmd_args) + + def _power_action(action, arg_dict): # Host must be disabled first host_uuid = arg_dict['host_uuid'] @@ -453,10 +512,12 @@ def get_pci_type(session, pci_device): if __name__ == "__main__": # Support both serialized and non-serialized plugin approaches _, methodname = xmlrpclib.loads(sys.argv[1]) - if methodname in ['query_gc', 'get_pci_device_details', 'get_pci_type']: + if methodname in ['query_gc', 'get_pci_device_details', 'get_pci_type', + 'network_config']: utils.register_plugin_calls(query_gc, get_pci_device_details, - get_pci_type) + get_pci_type, + network_config) XenAPIPlugin.dispatch( {"host_data": host_data,