From 38be6d95446665ad7c2fe89e38bb5044fd86cf55 Mon Sep 17 00:00:00 2001 From: Huan Xie Date: Mon, 6 Feb 2017 19:31:20 -0800 Subject: [PATCH] Add nova compute patches There are some important patches in nova computes introduced recently, this patch it to apply these patches and add corresponding Change_Id: compute node: 1. live-migration-iscsi.patch 2. support-vif-hotplug.patch 3. fix-rescue-vm.patch 4. live-migration-vifmapping.patch 5. assert_can_migrated.patch controller node: 1. live-migration-vifmapping-controller.patch Change-Id: Ib0bfb97fa61323d101ea49faaf410d1576ca111b (cherry picked from commit e4ab4f95bcbe20370d7d1069491b20e52770ca3e) --- .../compute_post_deployment.py | 27 +- .../controller_post_deployment.py | 18 ++ .../patchset/assert_can_migrated.patch | 29 ++ .../patchset/fix-rescue-vm.patch | 71 +++++ .../patchset/live-migration-iscsi.patch | 64 +++++ ...live-migration-vifmapping-controller.patch | 49 ++++ .../patchset/live-migration-vifmapping.patch | 253 ++++++++++++++++++ .../patchset/support-vif-hotplug.patch | 231 ++++++++++++++++ plugin_source/deployment_scripts/utils.py | 1 + 9 files changed, 739 insertions(+), 4 deletions(-) create mode 100644 plugin_source/deployment_scripts/patchset/assert_can_migrated.patch create mode 100644 plugin_source/deployment_scripts/patchset/fix-rescue-vm.patch create mode 100644 plugin_source/deployment_scripts/patchset/live-migration-iscsi.patch create mode 100644 plugin_source/deployment_scripts/patchset/live-migration-vifmapping-controller.patch create mode 100644 plugin_source/deployment_scripts/patchset/live-migration-vifmapping.patch create mode 100644 plugin_source/deployment_scripts/patchset/support-vif-hotplug.patch diff --git a/plugin_source/deployment_scripts/compute_post_deployment.py b/plugin_source/deployment_scripts/compute_post_deployment.py index e3f99e3..5b59578 100755 --- a/plugin_source/deployment_scripts/compute_post_deployment.py +++ b/plugin_source/deployment_scripts/compute_post_deployment.py @@ -15,7 +15,6 @@ from utils import HIMN_IP INT_BRIDGE = 'br-int' XS_PLUGIN_ISO = 'xenapi-plugins-mitaka.iso' -DIST_PACKAGES_DIR = '/usr/lib/python2.7/dist-packages/' CONNTRACK_CONF_SAMPLE =\ '/usr/share/doc/conntrack-tools-1.4.2/doc/stats/conntrackd.conf' @@ -38,7 +37,7 @@ def get_endpoints(astute): def install_xenapi_sdk(): """Install XenAPI Python SDK""" - utils.execute('cp', 'XenAPI.py', DIST_PACKAGES_DIR) + utils.execute('cp', 'XenAPI.py', utils.DIST_PACKAGES_DIR) def create_novacompute_conf(himn, username, password, public_ip, services_ssl): @@ -314,7 +313,7 @@ def patch_ceilometer(): 'ceilometer-add-purge_inspection_cache.patch', ] for patch_file in patchfile_list: - utils.patch(DIST_PACKAGES_DIR, patch_file, 1) + utils.patch(utils.DIST_PACKAGES_DIR, patch_file, 1) def patch_compute_xenapi(): @@ -325,15 +324,35 @@ def patch_compute_xenapi(): speed-up-config-drive.patch ovs-interim-bridge.patch neutron-security-group.patch + live-migration-iscsi.patch + support-vif-hotplug.patch + fix-rescue-vm.patch + live-migration-vifmapping.patch """ patchfile_list = [ + # Change-Id: I5ebff2c1f7534b06233a4d41d7f5f2e5e3b60b5a 'support-disable-image-cache.patch', + # Change-Id: I359e17d6d5838f4028df0bd47e4825de420eb383 'speed-up-config-drive.patch', + # Change-Id: I0cfc0284e1fcd1a6169d31a7ad410716037e5cc2 'ovs-interim-bridge.patch', + # Change-Id: Id9b39aa86558a9f7099caedabd2d517bf8ad3d68 'neutron-security-group.patch', + # Change-Id: I88d1d384ab7587c428e517d184258bb517dfb4ab + 'live-migration-iscsi.patch', + # Change-Id: I22f3fe52d07100592015007653c7f8c47c25d22c + 'support-vif-hotplug.patch', + # Change-Id: I32c66733330bc9877caea7e2a2290c02b3906708 + 'fix-rescue-vm.patch', + # Change-Id: If0fb5d764011521916fbbe15224f524a220052f3 + 'live-migration-vifmapping.patch', + # TODO(huanxie): below patch isn't merged into upstream yet, + # it only affects XS7.1 and later + # Change-Id: I31850b25e2f32eb65a00fbb824b08646c9ed340a + 'assert_can_migrated.patch', ] for patch_file in patchfile_list: - utils.patch(DIST_PACKAGES_DIR, patch_file, 1) + utils.patch(utils.DIST_PACKAGES_DIR, patch_file, 1) def patch_neutron_ovs_agent(): diff --git a/plugin_source/deployment_scripts/controller_post_deployment.py b/plugin_source/deployment_scripts/controller_post_deployment.py index 2d82f20..36c118a 100755 --- a/plugin_source/deployment_scripts/controller_post_deployment.py +++ b/plugin_source/deployment_scripts/controller_post_deployment.py @@ -36,5 +36,23 @@ def mod_novnc(): utils.reportError('Cannot set configurations to %s' % filename) +def patch_nova_conductor(): + """Add patches which are not merged to upstream + + Order of patches applied: + live-migration-vifmapping-controller.patch + """ + patchfile_list = [ + # Change-Id: If0fb5d764011521916fbbe15224f524a220052f3 + 'live-migration-vifmapping-controller.patch', + ] + for patch_file in patchfile_list: + utils.patch(utils.DIST_PACKAGES_DIR, patch_file, 1) + + # Restart related service + utils.execute('service', 'nova-conductor', 'restart') + + if __name__ == '__main__': + patch_nova_conductor() mod_novnc() diff --git a/plugin_source/deployment_scripts/patchset/assert_can_migrated.patch b/plugin_source/deployment_scripts/patchset/assert_can_migrated.patch new file mode 100644 index 0000000..c90150a --- /dev/null +++ b/plugin_source/deployment_scripts/patchset/assert_can_migrated.patch @@ -0,0 +1,29 @@ +diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py +index 82a9aef..d5048cd 100644 +--- a/nova/virt/xenapi/vmops.py ++++ b/nova/virt/xenapi/vmops.py +@@ -2278,10 +2278,11 @@ class VMOps(object): + self._call_live_migrate_command( + "VM.assert_can_migrate", vm_ref, dest_check_data) + except self._session.XenAPI.Failure as exc: +- reason = exc.details[0] +- msg = _('assert_can_migrate failed because: %s') % reason +- LOG.debug(msg, exc_info=True) +- raise exception.MigrationPreCheckError(reason=msg) ++ reason = '%s' % exc.details[0] ++ if reason.strip().upper() != "VIF_NOT_IN_MAP": ++ msg = _('assert_can_migrate failed because: %s') % reason ++ LOG.debug(msg, exc_info=True) ++ raise exception.MigrationPreCheckError(reason=msg) + return dest_check_data + + def _ensure_pv_driver_info_for_live_migration(self, instance, vm_ref): +@@ -2500,6 +2501,8 @@ class VMOps(object): + def post_live_migration_at_destination(self, context, instance, + network_info, block_migration, + block_device_info): ++ # Hook interim bridge with ovs bridge ++ self._post_start_actions(instance) + # FIXME(johngarbutt): we should block all traffic until we have + # applied security groups, however this requires changes to XenServer + self._prepare_instance_filter(instance, network_info) diff --git a/plugin_source/deployment_scripts/patchset/fix-rescue-vm.patch b/plugin_source/deployment_scripts/patchset/fix-rescue-vm.patch new file mode 100644 index 0000000..2155d0d --- /dev/null +++ b/plugin_source/deployment_scripts/patchset/fix-rescue-vm.patch @@ -0,0 +1,71 @@ +diff --git a/nova/virt/xenapi/vif.py b/nova/virt/xenapi/vif.py +index 6b07a62..ac271e3 100644 +--- a/nova/virt/xenapi/vif.py ++++ b/nova/virt/xenapi/vif.py +@@ -463,14 +463,15 @@ class XenAPIOpenVswitchDriver(XenVIFDriver): + + # Create Linux bridge qbrXXX + linux_br_name = self._create_linux_bridge(vif_rec) +- LOG.debug("create veth pair for interim bridge %(interim_bridge)s and " +- "linux bridge %(linux_bridge)s", +- {'interim_bridge': bridge_name, +- 'linux_bridge': linux_br_name}) +- self._create_veth_pair(tap_name, patch_port1) +- self._brctl_add_if(linux_br_name, tap_name) +- # Add port to interim bridge +- self._ovs_add_port(bridge_name, patch_port1) ++ if not self._device_exists(tap_name): ++ LOG.debug("create veth pair for interim bridge %(interim_bridge)s " ++ "and linux bridge %(linux_bridge)s", ++ {'interim_bridge': bridge_name, ++ 'linux_bridge': linux_br_name}) ++ self._create_veth_pair(tap_name, patch_port1) ++ self._brctl_add_if(linux_br_name, tap_name) ++ # Add port to interim bridge ++ self._ovs_add_port(bridge_name, patch_port1) + + def get_vif_interim_net_name(self, vif): + return ("net-" + vif['id'])[:network_model.NIC_NAME_LEN] +diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py +index 182873f..e44117e 100644 +--- a/nova/virt/xenapi/vmops.py ++++ b/nova/virt/xenapi/vmops.py +@@ -603,15 +603,18 @@ class VMOps(object): + # for neutron event regardless of whether or not it is + # migrated to another host, if unplug VIFs locally, the + # port status may not changed in neutron side and we +- # cannot get the vif plug event from neturon ++ # cannot get the vif plug event from neutron ++ # rescue is True in rescued instance and the port in neutron side ++ # won't change, so we don't wait event from neutron + timeout = CONF.vif_plugging_timeout +- events = self._get_neutron_events(network_info, +- power_on, first_boot) ++ events = self._get_neutron_events(network_info, power_on, ++ first_boot, rescue) + try: + with self._virtapi.wait_for_instance_event( + instance, events, deadline=timeout, + error_callback=self._neutron_failed_callback): +- LOG.debug("wait for instance event:%s", events) ++ LOG.debug("wait for instance event:%s", events, ++ instance=instance) + setup_network_step(undo_mgr, vm_ref) + if rescue: + attach_orig_disks_step(undo_mgr, vm_ref) +@@ -647,11 +650,13 @@ class VMOps(object): + if CONF.vif_plugging_is_fatal: + raise exception.VirtualInterfaceCreateException() + +- def _get_neutron_events(self, network_info, power_on, first_boot): ++ def _get_neutron_events(self, network_info, power_on, first_boot, rescue): + # Only get network-vif-plugged events with VIF's status is not active. + # With VIF whose status is active, neutron may not notify such event. ++ # Don't get network-vif-plugged events from rescued VM or migrated VM + timeout = CONF.vif_plugging_timeout +- if (utils.is_neutron() and power_on and timeout and first_boot): ++ if (utils.is_neutron() and power_on and timeout and first_boot and ++ not rescue): + return [('network-vif-plugged', vif['id']) + for vif in network_info if vif.get('active', True) is False] + else: diff --git a/plugin_source/deployment_scripts/patchset/live-migration-iscsi.patch b/plugin_source/deployment_scripts/patchset/live-migration-iscsi.patch new file mode 100644 index 0000000..ba4cc9c --- /dev/null +++ b/plugin_source/deployment_scripts/patchset/live-migration-iscsi.patch @@ -0,0 +1,64 @@ +diff --git a/nova/virt/xenapi/client/session.py b/nova/virt/xenapi/client/session.py +index 70e9bec..80bf235 100644 +--- a/nova/virt/xenapi/client/session.py ++++ b/nova/virt/xenapi/client/session.py +@@ -102,8 +102,9 @@ class XenAPISession(object): + self.host_ref = self._get_host_ref() + self.product_version, self.product_brand = \ + self._get_product_version_and_brand() +- + self._verify_plugin_version() ++ self.platform_version = self._get_platform_version() ++ self._cached_xsm_sr_relaxed = None + + apply_session_helpers(self) + +@@ -177,6 +178,15 @@ class XenAPISession(object): + + return product_version, product_brand + ++ def _get_platform_version(self): ++ """Return a tuple of (major, minor, rev) for the host version""" ++ software_version = self._get_software_version() ++ platform_version_str = software_version.get('platform_version', ++ '0.0.0') ++ platform_version = versionutils.convert_version_to_tuple( ++ platform_version_str) ++ return platform_version ++ + def _get_software_version(self): + return self.call_xenapi('host.get_software_version', self.host_ref) + +@@ -328,3 +338,19 @@ class XenAPISession(object): + """ + + return self.call_xenapi('%s.get_all_records' % record_type).items() ++ ++ def is_xsm_sr_check_relaxed(self): ++ if self._cached_xsm_sr_relaxed is None: ++ config_value = self.call_plugin('config_file', 'get_val', ++ key='relax-xsm-sr-check') ++ if not config_value: ++ version_str = '.'.join(str(v) for v in self.platform_version) ++ if versionutils.is_compatible('2.1.0', version_str, ++ same_major=False): ++ self._cached_xsm_sr_relaxed = True ++ else: ++ self._cached_xsm_sr_relaxed = False ++ else: ++ self._cached_xsm_sr_relaxed = config_value.lower() == 'true' ++ ++ return self._cached_xsm_sr_relaxed +diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py +index 51d9627..1c93eac 100644 +--- a/nova/virt/xenapi/vmops.py ++++ b/nova/virt/xenapi/vmops.py +@@ -2257,7 +2257,7 @@ class VMOps(object): + if len(self._get_iscsi_srs(ctxt, instance_ref)) > 0: + # XAPI must support the relaxed SR check for live migrating with + # iSCSI VBDs +- if not self._is_xsm_sr_check_relaxed(): ++ if not self._session.is_xsm_sr_check_relaxed(): + raise exception.MigrationError(reason=_('XAPI supporting ' + 'relax-xsm-sr-check=true required')) + diff --git a/plugin_source/deployment_scripts/patchset/live-migration-vifmapping-controller.patch b/plugin_source/deployment_scripts/patchset/live-migration-vifmapping-controller.patch new file mode 100644 index 0000000..59c7605 --- /dev/null +++ b/plugin_source/deployment_scripts/patchset/live-migration-vifmapping-controller.patch @@ -0,0 +1,49 @@ +diff --git a/nova/objects/migrate_data.py b/nova/objects/migrate_data.py +index 44dce4f..07a16ea 100644 +--- a/nova/objects/migrate_data.py ++++ b/nova/objects/migrate_data.py +@@ -210,7 +210,9 @@ class LibvirtLiveMigrateData(LiveMigrateData): + + @obj_base.NovaObjectRegistry.register + class XenapiLiveMigrateData(LiveMigrateData): +- VERSION = '1.0' ++ # Version 1.0: Initial version ++ # Version 1.1: Added vif_uuid_map ++ VERSION = '1.1' + + fields = { + 'block_migration': fields.BooleanField(nullable=True), +@@ -219,6 +221,7 @@ class XenapiLiveMigrateData(LiveMigrateData): + 'sr_uuid_map': fields.DictOfStringsField(), + 'kernel_file': fields.StringField(), + 'ramdisk_file': fields.StringField(), ++ 'vif_uuid_map': fields.DictOfStringsField(), + } + + def to_legacy_dict(self, pre_migration_result=False): +@@ -233,6 +236,8 @@ class XenapiLiveMigrateData(LiveMigrateData): + live_result = { + 'sr_uuid_map': ('sr_uuid_map' in self and self.sr_uuid_map + or {}), ++ 'vif_uuid_map': ('vif_uuid_map' in self and self.vif_uuid_map ++ or {}), + } + if pre_migration_result: + legacy['pre_live_migration_result'] = live_result +@@ -252,6 +257,16 @@ class XenapiLiveMigrateData(LiveMigrateData): + if 'pre_live_migration_result' in legacy: + self.sr_uuid_map = \ + legacy['pre_live_migration_result']['sr_uuid_map'] ++ self.vif_uuid_map = \ ++ legacy['pre_live_migration_result'].get('vif_uuid_map', {}) ++ ++ def obj_make_compatible(self, primitive, target_version): ++ super(XenapiLiveMigrateData, self).obj_make_compatible( ++ primitive, target_version) ++ target_version = versionutils.convert_version_to_tuple(target_version) ++ if target_version < (1, 1): ++ if 'vif_uuid_map' in primitive: ++ del primitive['vif_uuid_map'] + + + @obj_base.NovaObjectRegistry.register diff --git a/plugin_source/deployment_scripts/patchset/live-migration-vifmapping.patch b/plugin_source/deployment_scripts/patchset/live-migration-vifmapping.patch new file mode 100644 index 0000000..3f52afb --- /dev/null +++ b/plugin_source/deployment_scripts/patchset/live-migration-vifmapping.patch @@ -0,0 +1,253 @@ +diff --git a/nova/objects/migrate_data.py b/nova/objects/migrate_data.py +index 44dce4f..07a16ea 100644 +--- a/nova/objects/migrate_data.py ++++ b/nova/objects/migrate_data.py +@@ -210,7 +210,9 @@ class LibvirtLiveMigrateData(LiveMigrateData): + + @obj_base.NovaObjectRegistry.register + class XenapiLiveMigrateData(LiveMigrateData): +- VERSION = '1.0' ++ # Version 1.0: Initial version ++ # Version 1.1: Added vif_uuid_map ++ VERSION = '1.1' + + fields = { + 'block_migration': fields.BooleanField(nullable=True), +@@ -219,6 +221,7 @@ class XenapiLiveMigrateData(LiveMigrateData): + 'sr_uuid_map': fields.DictOfStringsField(), + 'kernel_file': fields.StringField(), + 'ramdisk_file': fields.StringField(), ++ 'vif_uuid_map': fields.DictOfStringsField(), + } + + def to_legacy_dict(self, pre_migration_result=False): +@@ -233,6 +236,8 @@ class XenapiLiveMigrateData(LiveMigrateData): + live_result = { + 'sr_uuid_map': ('sr_uuid_map' in self and self.sr_uuid_map + or {}), ++ 'vif_uuid_map': ('vif_uuid_map' in self and self.vif_uuid_map ++ or {}), + } + if pre_migration_result: + legacy['pre_live_migration_result'] = live_result +@@ -252,6 +257,16 @@ class XenapiLiveMigrateData(LiveMigrateData): + if 'pre_live_migration_result' in legacy: + self.sr_uuid_map = \ + legacy['pre_live_migration_result']['sr_uuid_map'] ++ self.vif_uuid_map = \ ++ legacy['pre_live_migration_result'].get('vif_uuid_map', {}) ++ ++ def obj_make_compatible(self, primitive, target_version): ++ super(XenapiLiveMigrateData, self).obj_make_compatible( ++ primitive, target_version) ++ target_version = versionutils.convert_version_to_tuple(target_version) ++ if target_version < (1, 1): ++ if 'vif_uuid_map' in primitive: ++ del primitive['vif_uuid_map'] + + + @obj_base.NovaObjectRegistry.register +diff --git a/nova/virt/xenapi/driver.py b/nova/virt/xenapi/driver.py +index 899c083..1639861 100644 +--- a/nova/virt/xenapi/driver.py ++++ b/nova/virt/xenapi/driver.py +@@ -569,6 +569,7 @@ class XenAPIDriver(driver.ComputeDriver): + # any volume that was attached to the destination during + # live migration. XAPI should take care of all other cleanup. + self._vmops.rollback_live_migration_at_destination(instance, ++ network_info, + block_device_info) + + def pre_live_migration(self, context, instance, block_device_info, +@@ -594,6 +595,16 @@ class XenAPIDriver(driver.ComputeDriver): + """ + self._vmops.post_live_migration(context, instance, migrate_data) + ++ def post_live_migration_at_source(self, context, instance, network_info): ++ """Unplug VIFs from networks at source. ++ ++ :param context: security context ++ :param instance: instance object reference ++ :param network_info: instance network information ++ """ ++ self._vmops.post_live_migration_at_source(context, instance, ++ network_info) ++ + def post_live_migration_at_destination(self, context, instance, + network_info, + block_migration=False, +diff --git a/nova/virt/xenapi/vif.py b/nova/virt/xenapi/vif.py +index ac271e3..6925426 100644 +--- a/nova/virt/xenapi/vif.py ++++ b/nova/virt/xenapi/vif.py +@@ -88,6 +88,9 @@ class XenVIFDriver(object): + raise exception.NovaException( + reason=_("Failed to unplug vif %s") % vif) + ++ def get_vif_interim_net_name(self, vif_id): ++ return ("net-" + vif_id)[:network_model.NIC_NAME_LEN] ++ + def hot_plug(self, vif, instance, vm_ref, vif_ref): + """hotplug virtual interface to running instance. + :param nova.network.model.VIF vif: +@@ -126,10 +129,20 @@ class XenVIFDriver(object): + """ + pass + ++ def create_vif_interim_network(self, vif): ++ pass ++ ++ def delete_network_and_bridge(self, instance, vif): ++ pass ++ + + class XenAPIBridgeDriver(XenVIFDriver): + """VIF Driver for XenAPI that uses XenAPI to create Networks.""" + ++ # NOTE(huanxie): This driver uses linux bridge as backend for XenServer, ++ # it only supports nova network, for using neutron, you should use ++ # XenAPIOpenVswitchDriver ++ + def plug(self, instance, vif, vm_ref=None, device=None): + if not vm_ref: + vm_ref = vm_utils.lookup(self._session, instance['name']) +@@ -279,8 +292,7 @@ class XenAPIOpenVswitchDriver(XenVIFDriver): + 4. delete linux bridge qbr and related ports if exist + """ + super(XenAPIOpenVswitchDriver, self).unplug(instance, vif, vm_ref) +- +- net_name = self.get_vif_interim_net_name(vif) ++ net_name = self.get_vif_interim_net_name(vif['id']) + network = network_utils.find_network_with_name_label( + self._session, net_name) + if network is None: +@@ -292,6 +304,16 @@ class XenAPIOpenVswitchDriver(XenVIFDriver): + # source and target VM will be connected to the same + # interim network. + return ++ self.delete_network_and_bridge(instance, vif) ++ ++ def delete_network_and_bridge(self, instance, vif): ++ net_name = self.get_vif_interim_net_name(vif['id']) ++ network = network_utils.find_network_with_name_label( ++ self._session, net_name) ++ if network is None: ++ LOG.debug("Didn't find network by name %s", net_name, ++ instance=instance) ++ 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) +@@ -473,11 +495,8 @@ class XenAPIOpenVswitchDriver(XenVIFDriver): + # Add port to interim bridge + self._ovs_add_port(bridge_name, patch_port1) + +- 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) ++ net_name = self.get_vif_interim_net_name(vif['id']) + network_rec = {'name_label': net_name, + 'name_description': "interim network for vif", + 'other_config': {}} +diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py +index e44117e..82a9aef 100644 +--- a/nova/virt/xenapi/vmops.py ++++ b/nova/virt/xenapi/vmops.py +@@ -2388,11 +2388,41 @@ class VMOps(object): + self._generate_vdi_map( + sr_uuid_map[sr_uuid], vm_ref, sr_ref)) + vif_map = {} ++ vif_uuid_map = None ++ if 'vif_uuid_map' in migrate_data: ++ vif_uuid_map = migrate_data.vif_uuid_map ++ if vif_uuid_map: ++ vif_map = self._generate_vif_network_map(vm_ref, vif_uuid_map) ++ LOG.debug("Generated vif_map for live migration: %s", vif_map) + options = {} + self._session.call_xenapi(command_name, vm_ref, + migrate_send_data, True, + vdi_map, vif_map, options) + ++ def _generate_vif_network_map(self, vm_ref, vif_uuid_map): ++ # Generate a mapping dictionary of src_vif_ref: dest_network_ref ++ vif_map = {} ++ # vif_uuid_map is dictionary of neutron_vif_uuid: dest_network_ref ++ vifs = self._session.VM.get_VIFs(vm_ref) ++ for vif in vifs: ++ other_config = self._session.VIF.get_other_config(vif) ++ neutron_id = other_config.get('nicira-iface-id') ++ if neutron_id is None or neutron_id not in vif_uuid_map.keys(): ++ raise exception.MigrationError( ++ reason=_('No mapping for source network %s') % ( ++ neutron_id)) ++ network_ref = vif_uuid_map[neutron_id] ++ vif_map[vif] = network_ref ++ return vif_map ++ ++ def create_interim_networks(self, network_info): ++ # Creating an interim bridge in destination host before live_migration ++ vif_map = {} ++ for vif in network_info: ++ network_ref = self.vif_driver.create_vif_interim_network(vif) ++ vif_map.update({vif['id']: network_ref}) ++ return vif_map ++ + def pre_live_migration(self, context, instance, block_device_info, + network_info, disk_info, migrate_data): + if migrate_data is None: +@@ -2401,6 +2431,11 @@ class VMOps(object): + + migrate_data.sr_uuid_map = self.connect_block_device_volumes( + block_device_info) ++ migrate_data.vif_uuid_map = self.create_interim_networks(network_info) ++ LOG.debug("pre_live_migration, vif_uuid_map: %(vif_map)s, " ++ "sr_uuid_map: %(sr_map)s", ++ {'vif_map': migrate_data.vif_uuid_map, ++ 'sr_map': migrate_data.sr_uuid_map}, instance=instance) + return migrate_data + + def live_migrate(self, context, instance, destination_hostname, +@@ -2457,6 +2492,11 @@ class VMOps(object): + migrate_data.kernel_file, + migrate_data.ramdisk_file) + ++ def post_live_migration_at_source(self, context, instance, network_info): ++ LOG.debug('post_live_migration_at_source, delete networks and bridges', ++ instance=instance) ++ self._delete_networks_and_bridges(instance, network_info) ++ + def post_live_migration_at_destination(self, context, instance, + network_info, block_migration, + block_device_info): +@@ -2471,7 +2511,7 @@ class VMOps(object): + vm_ref = self._get_vm_opaque_ref(instance) + vm_utils.strip_base_mirror_from_vdis(self._session, vm_ref) + +- def rollback_live_migration_at_destination(self, instance, ++ def rollback_live_migration_at_destination(self, instance, network_info, + block_device_info): + bdms = block_device_info['block_device_mapping'] or [] + +@@ -2488,6 +2528,20 @@ class VMOps(object): + LOG.exception(_LE('Failed to forget the SR for volume %s'), + params['id'], instance=instance) + ++ # delete VIF and network in destination host ++ LOG.debug('rollback_live_migration_at_destination, delete networks ' ++ 'and bridges', instance=instance) ++ self._delete_networks_and_bridges(instance, network_info) ++ ++ def _delete_networks_and_bridges(self, instance, network_info): ++ # Unplug VIFs and delete networks ++ for vif in network_info: ++ try: ++ self.vif_driver.delete_network_and_bridge(instance, vif) ++ except Exception: ++ LOG.exception(_LE('Failed to delete networks and bridges with ' ++ 'VIF %s'), vif['id'], instance=instance) ++ + def get_per_instance_usage(self): + """Get usage info about each active instance.""" + usage = {} diff --git a/plugin_source/deployment_scripts/patchset/support-vif-hotplug.patch b/plugin_source/deployment_scripts/patchset/support-vif-hotplug.patch new file mode 100644 index 0000000..b56a66d --- /dev/null +++ b/plugin_source/deployment_scripts/patchset/support-vif-hotplug.patch @@ -0,0 +1,231 @@ +diff --git a/nova/virt/xenapi/driver.py b/nova/virt/xenapi/driver.py +index 77483fa..899c083 100644 +--- a/nova/virt/xenapi/driver.py ++++ b/nova/virt/xenapi/driver.py +@@ -104,6 +104,13 @@ OVERHEAD_PER_VCPU = 1.5 + + class XenAPIDriver(driver.ComputeDriver): + """A connection to XenServer or Xen Cloud Platform.""" ++ capabilities = { ++ "has_imagecache": False, ++ "supports_recreate": False, ++ "supports_migrate_to_same_host": False, ++ "supports_attach_interface": True, ++ "supports_device_tagging": False, ++ } + + def __init__(self, virtapi, read_only=False): + super(XenAPIDriver, self).__init__(virtapi) +@@ -681,3 +688,39 @@ class XenAPIDriver(driver.ComputeDriver): + :returns: dict of nova uuid => dict of usage info + """ + return self._vmops.get_per_instance_usage() ++ ++ def attach_interface(self, context, instance, image_meta, vif): ++ """Use hotplug to add a network interface to a running instance. ++ ++ The counter action to this is :func:`detach_interface`. ++ ++ :param context: The request context. ++ :param nova.objects.instance.Instance instance: ++ The instance which will get an additional network interface. ++ :param nova.objects.ImageMeta image_meta: ++ The metadata of the image of the instance. ++ :param nova.network.model.VIF vif: ++ The object which has the information about the interface to attach. ++ ++ :raise nova.exception.NovaException: If the attach fails. ++ ++ :return: None ++ """ ++ self._vmops.attach_interface(instance, vif) ++ ++ def detach_interface(self, context, instance, vif): ++ """Use hotunplug to remove a network interface from a running instance. ++ ++ The counter action to this is :func:`attach_interface`. ++ ++ :param context: The request context. ++ :param nova.objects.instance.Instance instance: ++ The instance which gets a network interface removed. ++ :param nova.network.model.VIF vif: ++ The object which has the information about the interface to detach. ++ ++ :raise nova.exception.NovaException: If the detach fails. ++ ++ :return: None ++ """ ++ self._vmops.detach_interface(instance, vif) +diff --git a/nova/virt/xenapi/vif.py b/nova/virt/xenapi/vif.py +index a474d23..6b07a62 100644 +--- a/nova/virt/xenapi/vif.py ++++ b/nova/virt/xenapi/vif.py +@@ -20,6 +20,7 @@ + from oslo_config import cfg + from oslo_log import log as logging + ++from nova.compute import power_state + from nova import exception + from nova.i18n import _ + from nova.i18n import _LW +@@ -77,6 +78,8 @@ class XenVIFDriver(object): + LOG.debug("vif didn't exist, no need to unplug vif %s", + vif, instance=instance) + return ++ # hot unplug the VIF first ++ self.hot_unplug(vif, instance, vm_ref, vif_ref) + self._session.call_xenapi('VIF.destroy', vif_ref) + except Exception as e: + LOG.warn( +@@ -85,6 +88,44 @@ class XenVIFDriver(object): + raise exception.NovaException( + reason=_("Failed to unplug vif %s") % vif) + ++ def hot_plug(self, vif, instance, vm_ref, vif_ref): ++ """hotplug virtual interface to running instance. ++ :param nova.network.model.VIF vif: ++ The object which has the information about the interface to attach. ++ :param nova.objects.instance.Instance instance: ++ The instance which will get an additional network interface. ++ :param string vm_ref: ++ The instance's reference from hypervisor's point of view. ++ :param string vif_ref: ++ The interface's reference from hypervisor's point of view. ++ :return: None ++ """ ++ pass ++ ++ def hot_unplug(self, vif, instance, vm_ref, vif_ref): ++ """hot unplug virtual interface from running instance. ++ :param nova.network.model.VIF vif: ++ The object which has the information about the interface to detach. ++ :param nova.objects.instance.Instance instance: ++ The instance which will remove additional network interface. ++ :param string vm_ref: ++ The instance's reference from hypervisor's point of view. ++ :param string vif_ref: ++ The interface's reference from hypervisor's point of view. ++ :return: None ++ """ ++ pass ++ ++ def post_start_actions(self, instance, vif_ref): ++ """post actions when the instance is power on. ++ :param nova.objects.instance.Instance instance: ++ The instance which will execute extra actions after power on ++ :param string vif_ref: ++ The interface's reference from hypervisor's point of view. ++ :return: None ++ """ ++ pass ++ + + class XenAPIBridgeDriver(XenVIFDriver): + """VIF Driver for XenAPI that uses XenAPI to create Networks.""" +@@ -186,10 +227,6 @@ 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.""" +@@ -226,7 +263,12 @@ class XenAPIOpenVswitchDriver(XenVIFDriver): + # OVS on the hypervisor monitors this key and uses it to + # set the iface-id attribute + vif_rec['other_config'] = {'nicira-iface-id': vif['id']} +- return self._create_vif(vif, vif_rec, vm_ref) ++ vif_ref = self._create_vif(vif, vif_rec, vm_ref) ++ ++ # call XenAPI to plug vif ++ self.hot_plug(vif, instance, vm_ref, vif_ref) ++ ++ return vif_ref + + def unplug(self, instance, vif, vm_ref): + """unplug vif: +@@ -294,6 +336,29 @@ class XenAPIOpenVswitchDriver(XenVIFDriver): + raise exception.VirtualInterfaceUnplugException( + reason=_("Failed to delete bridge")) + ++ def hot_plug(self, vif, instance, vm_ref, vif_ref): ++ # hot plug vif only when VM's power state is running ++ LOG.debug("Hot plug vif, vif: %s", vif, instance=instance) ++ state = vm_utils.get_power_state(self._session, vm_ref) ++ if state != power_state.RUNNING: ++ LOG.debug("Skip hot plug VIF, VM is not running, vif: %s", vif, ++ instance=instance) ++ return ++ ++ self._session.VIF.plug(vif_ref) ++ self.post_start_actions(instance, vif_ref) ++ ++ def hot_unplug(self, vif, instance, vm_ref, vif_ref): ++ # hot unplug vif only when VM's power state is running ++ LOG.debug("Hot unplug vif, vif: %s", vif, instance=instance) ++ state = vm_utils.get_power_state(self._session, vm_ref) ++ if state != power_state.RUNNING: ++ LOG.debug("Skip hot unplug VIF, VM is not running, vif: %s", vif, ++ instance=instance) ++ return ++ ++ self._session.VIF.unplug(vif_ref) ++ + def _get_qbr_name(self, iface_id): + return ("qbr" + iface_id)[:network_model.NIC_NAME_LEN] + +diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py +index 1c93eac..182873f 100644 +--- a/nova/virt/xenapi/vmops.py ++++ b/nova/virt/xenapi/vmops.py +@@ -2522,3 +2522,47 @@ class VMOps(object): + volume_utils.forget_sr(self._session, sr_uuid_map[sr_ref]) + + return sr_uuid_map ++ ++ def attach_interface(self, instance, vif): ++ LOG.debug("Attach interface, vif info: %s", vif, instance=instance) ++ vm_ref = self._get_vm_opaque_ref(instance) ++ ++ @utils.synchronized('xenapi-vif-' + vm_ref) ++ def _attach_interface(instance, vm_ref, vif): ++ # find device for use with XenAPI ++ allowed_devices = self._session.VM.get_allowed_VIF_devices(vm_ref) ++ if allowed_devices is None or len(allowed_devices) == 0: ++ raise exception.InterfaceAttachFailed( ++ _('attach network interface %(vif_id)s to instance ' ++ '%(instance_uuid)s failed, no allowed devices.'), ++ vif_id=vif['id'], instance_uuid=instance.uuid) ++ device = allowed_devices[0] ++ try: ++ # plug VIF ++ self.vif_driver.plug(instance, vif, vm_ref=vm_ref, ++ device=device) ++ # set firewall filtering ++ self.firewall_driver.setup_basic_filtering(instance, [vif]) ++ except exception.NovaException: ++ with excutils.save_and_reraise_exception(): ++ LOG.exception(_LE('attach network interface %s failed.'), ++ vif['id'], instance=instance) ++ try: ++ self.vif_driver.unplug(instance, vif, vm_ref) ++ except exception.NovaException: ++ # if unplug failed, no need to raise exception ++ LOG.warning(_LW('Unplug VIF %s failed.'), ++ vif['id'], instance=instance) ++ ++ _attach_interface(instance, vm_ref, vif) ++ ++ def detach_interface(self, instance, vif): ++ LOG.debug("Detach interface, vif info: %s", vif, instance=instance) ++ ++ try: ++ vm_ref = self._get_vm_opaque_ref(instance) ++ self.vif_driver.unplug(instance, vif, vm_ref) ++ except exception.NovaException: ++ with excutils.save_and_reraise_exception(): ++ LOG.exception(_LE('detach network interface %s failed.'), ++ vif['id'], instance=instance) diff --git a/plugin_source/deployment_scripts/utils.py b/plugin_source/deployment_scripts/utils.py index 2dacd9b..073eaaf 100644 --- a/plugin_source/deployment_scripts/utils.py +++ b/plugin_source/deployment_scripts/utils.py @@ -13,6 +13,7 @@ ASTUTE_SECTION = '@PLUGIN_NAME@' PLUGIN_NAME = '@PLUGIN_NAME@' LOG_ROOT = '/var/log/@PLUGIN_NAME@' HIMN_IP = '169.254.0.1' +DIST_PACKAGES_DIR = '/usr/lib/python2.7/dist-packages/' LOG = logging.getLogger('@PLUGIN_NAME@')