# HG changeset patch # User saurabh.vyas@oracle.com # Date 1450166920 28800 # Node ID bca6b9853ab7c458c9cae75d240534b39a3bad03 # Parent 8566c7ab4a73a7cd513bf90bf9a5ebec1c0b5448 PSARC 2015/324 OpenStack Nova support for kernel zone live-migration 19438929 'nova live-migration' shouldn't leave instances in an error state 20739475 Nova driver should support live-migration diff -r 8566c7ab4a73 -r bca6b9853ab7 components/openstack/horizon/files/overrides.py --- a/components/openstack/horizon/files/overrides.py Tue Dec 15 00:08:40 2015 -0800 +++ b/components/openstack/horizon/files/overrides.py Tue Dec 15 00:08:40 2015 -0800 @@ -1,4 +1,4 @@ -# Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2014, 2016, Oracle and/or its affiliates. All rights reserved. # # 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 @@ -16,6 +16,8 @@ Solaris-specific customizations for Horizon """ +from openstack_dashboard.dashboards.admin.instances.forms import \ + LiveMigrateForm from openstack_dashboard.dashboards.admin.instances import tables \ as admin_tables from openstack_dashboard.dashboards.admin.networks.forms import CreateNetwork @@ -29,17 +31,12 @@ DeleteNetwork, NetworksTable from openstack_dashboard.dashboards.project.access_and_security.tabs import \ AccessAndSecurityTabs, APIAccessTab, FloatingIPsTab, KeypairsTab -from \ - openstack_dashboard.dashboards.project.images.images.tables \ - import EditImage, DeleteImage, ImagesTable, LaunchImage +from openstack_dashboard.dashboards.project.images.images.tables import \ + EditImage, DeleteImage, ImagesTable, LaunchImage from openstack_dashboard.dashboards.project.instances import tables \ as project_tables -from openstack_dashboard.dashboards.project.instances.tabs import \ - InstanceDetailTabs, LogTab, OverviewTab from openstack_dashboard.dashboards.project.instances.workflows import \ - create_instance -from openstack_dashboard.dashboards.project.instances.workflows import \ - update_instance + create_instance, update_instance from openstack_dashboard.dashboards.project.networks.ports.tables import \ PortsTable as projectPortsTable from openstack_dashboard.dashboards.project.networks.subnets.tables import \ @@ -54,10 +51,9 @@ NetworksTable as projectNetworksTable from openstack_dashboard.dashboards.project.networks.workflows import \ CreateNetworkInfoAction, CreateSubnetDetailAction, CreateSubnetInfoAction -from openstack_dashboard.dashboards.project.stacks.tabs import \ - StackDetailTabs, StackEventsTab, StackOverviewTab, StackResourcesTab -# Remove 'PostCreationStep' from Project/Instances/Launch Instance +# Remove 'PostCreationStep', 'SetAdvanced' from +# Project/Compute/Instances/Launch Instance create_instance.LaunchInstance.default_steps = ( create_instance.SelectProjectUser, create_instance.SetInstanceDetails, @@ -66,29 +62,30 @@ ) # Remove 'UpdateInstanceSecurityGroups' from -# Project/Instances/Actions/Edit Instance +# Project/Compute/Instances/Actions/Edit Instance update_instance.UpdateInstance.default_steps = ( update_instance.UpdateInstanceInfo, ) -# Remove 'SecurityGroupsTab' tab from Project/Access & Security +# Remove 'SecurityGroupsTab' tab from Project/Compute/Access & Security AccessAndSecurityTabs.tabs = (KeypairsTab, FloatingIPsTab, APIAccessTab) -# Remove 'ConfirmResize', 'RevertResize', 'TogglePause', -# 'ToggleSuspend', 'MigrateInstance' actions from Admin/Instances/Actions +# Remove 'ConfirmResize', 'RevertResize', 'TogglePause', 'ToggleSuspend', +# 'MigrateInstance' actions from Admin/System/Instances/Actions admin_tables.AdminInstancesTable._meta.row_actions = ( admin_tables.AdminEditInstance, project_tables.ConsoleLink, project_tables.LogLink, project_tables.CreateSnapshot, + admin_tables.LiveMigrateInstance, project_tables.SoftRebootInstance, project_tables.RebootInstance, project_tables.TerminateInstance ) -# Remove 'ConfirmResize', 'RevertResize', 'EditInstanceSecurityGroups', -# 'TogglePause', 'ToggleSuspend', 'ResizeLink', 'RebuildInstance' actions -# from Project/Instances/Actions +# Remove 'ConfirmResize', 'RevertResize', 'DecryptInstancePassword', +# 'EditInstanceSecurityGroups', 'TogglePause', 'ToggleSuspend', 'ResizeLink', +# 'RebuildInstance' actions from Project/Compute/Instances/Actions project_tables.InstancesTable._meta.row_actions = ( project_tables.StartInstance, project_tables.CreateSnapshot, @@ -104,41 +101,49 @@ project_tables.TerminateInstance ) -# Disable 'admin_state' in Admin/Networks/Create Network +# Disable 'disk_over_commit', 'block_migration' in +# Admin/System/Instances/Actions/Live Migrate Instance. Note that this is +# unchecked by default. +LiveMigrateForm.base_fields['disk_over_commit'].widget.attrs['disabled'] = True +LiveMigrateForm.base_fields['block_migration'].widget.attrs['disabled'] = True + +# Disable 'admin_state' in Admin/System/Networks/Create Network admin_state = CreateNetwork.base_fields['admin_state'] admin_state.widget.attrs['disabled'] = True admin_state.widget.value_from_datadict = lambda *args: True -# Disable 'shared' in Admin/Networks/Create Network. Note that this -# is unchecked by default. +# Disable 'shared' in Admin/System/Networks/Create Network. Note that this is +# unchecked by default. CreateNetwork.base_fields['shared'].widget.attrs['disabled'] = True -# Disable 'admin_state' in Admin/Networks/Network Name/Create Port +# Disable 'admin_state' in Admin/System/Networks/Network Name/Create Port admin_state = CreatePort.base_fields['admin_state'] admin_state.widget.attrs['disabled'] = True admin_state.widget.value_from_datadict = lambda *args: True -# Remove 'UpdatePort' action from Admin/Networks/Network Name/Actions +# Remove 'UpdatePort' action from Admin/System/Networks/Network Name/Actions PortsTable._meta.row_actions = (DeletePort,) -# Remove 'UpdateSubnet' action from Admin/Networks/Network Name/Actions +# Remove 'UpdateSubnet' action from +# Admin/System/Networks/Network Name/Subnets/Actions SubnetsTable._meta.row_actions = (DeleteSubnet,) -# Remove the 'EditNetwork' action from Admin/Networks/Actions +# Remove the 'EditNetwork' action from Admin/System/Networks/Actions NetworksTable._meta.row_actions = (DeleteNetwork,) -# Remove the 'UpdatePort' action from Project/Networks/Name/Ports/Actions +# Remove the 'UpdatePort' action from +# Project/Network/Networks/Name/Ports/Actions projectPortsTable._meta.row_actions = () # Remove the 'UpdateSubnet' action from -# Project/Networks/Name/Subnets/Actions +# Project/Network/Networks/Name/Subnets/Actions projectSubnetsTable._meta.row_actions = (projectDeleteSubnet,) -# Remove the 'EditNetwork' action from Project/Networks/Actions +# Remove the 'EditNetwork' action from Project/Network/Networks/Actions projectNetworksTable._meta.row_actions = (projectCreateSubnet, projectDeleteNetwork) -# Disable 'admin_state' in Project/Networks/Create Network/Network +# Disable 'admin_state' in Project/Network/Networks/Create Network/Network admin_state = CreateNetworkInfoAction.base_fields['admin_state'] admin_state.widget.attrs['disabled'] = True admin_state.widget.value_from_datadict = lambda *args: True @@ -148,11 +153,10 @@ CreateSubnetInfoAction.base_fields['no_gateway'].widget.attrs['disabled'] = \ True -# Remove 'CreateVolumeFromImage' checkbox from -# Project/Images & Snapshots/Actions +# Remove 'CreateVolumeFromImage' checkbox from Project/Compute/Images/Actions ImagesTable._meta.row_actions = (LaunchImage, EditImage, DeleteImage,) # Change 'host_routes' field to read-only in -# Project/Networks/Create Network/Subnet Detail +# Project/Network/Networks/Create Network/Subnet Detail base_fields = CreateSubnetDetailAction.base_fields base_fields['host_routes'].widget.attrs['readonly'] = 'readonly' diff -r 8566c7ab4a73 -r bca6b9853ab7 components/openstack/nova/files/nova.conf --- a/components/openstack/nova/files/nova.conf Tue Dec 15 00:08:40 2015 -0800 +++ b/components/openstack/nova/files/nova.conf Tue Dec 15 00:08:40 2015 -0800 @@ -1972,9 +1972,14 @@ # value) #glancecache_dirname=$state_path/images -# Location where solariszones driver will store snapshots -# before uploading them to the Glance image service (string -# value) +# Cipher to use for encryption of memory traffic during live +# migration. If not specified, a common encryption algorithm +# will be negotiated. Options include: none or the name of a +# supported OpenSSL cipher algorithm. (string value) +#live_migration_cipher= + +# Location to store snapshots before uploading them to the +# Glance image service. (string value) #solariszones_snapshots_directory=$instances_path/snapshots diff -r 8566c7ab4a73 -r bca6b9853ab7 components/openstack/nova/files/nova.prof_attr --- a/components/openstack/nova/files/nova.prof_attr Tue Dec 15 00:08:40 2015 -0800 +++ b/components/openstack/nova/files/nova.prof_attr Tue Dec 15 00:08:40 2015 -0800 @@ -15,4 +15,5 @@ auths=solaris.smf.manage.nova,solaris.smf.modify,solaris.smf.value.nova;\ profiles=Unified Archive Administration,\ Zone Management,\ +Zone Migration,\ Zone Security diff -r 8566c7ab4a73 -r bca6b9853ab7 components/openstack/nova/files/solariszones/driver.py --- a/components/openstack/nova/files/solariszones/driver.py Tue Dec 15 00:08:40 2015 -0800 +++ b/components/openstack/nova/files/solariszones/driver.py Tue Dec 15 00:08:40 2015 -0800 @@ -1,7 +1,7 @@ # Copyright 2011 Justin Santa Barbara # All Rights Reserved. # -# Copyright (c) 2013, 2015, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2013, 2016, Oracle and/or its affiliates. All rights reserved. # # 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 @@ -52,6 +52,7 @@ from nova.network import neutronv2 from nova import objects from nova.objects import flavor as flavor_obj +from nova.openstack.common import excutils from nova.openstack.common import fileutils from nova.openstack.common import jsonutils from nova.openstack.common import log as logging @@ -69,10 +70,15 @@ cfg.StrOpt('glancecache_dirname', default='$state_path/images', help='Default path to Glance cache for Solaris Zones.'), + cfg.StrOpt('live_migration_cipher', + help='Cipher to use for encryption of memory traffic during ' + 'live migration. If not specified, a common encryption ' + 'algorithm will be negotiated. Options include: none or ' + 'the name of a supported OpenSSL cipher algorithm.'), cfg.StrOpt('solariszones_snapshots_directory', default='$instances_path/snapshots', - help='Location where solariszones driver will store snapshots ' - 'before uploading them to the Glance image service'), + help='Location to store snapshots before uploading them to the ' + 'Glance image service.'), ] CONF = cfg.CONF @@ -121,11 +127,19 @@ } MAX_CONSOLE_BYTES = 102400 + VNC_CONSOLE_BASE_FMRI = 'svc:/application/openstack/nova/zone-vnc-console' # Required in order to create a zone VNC console SMF service instance VNC_SERVER_PATH = '/usr/bin/vncserver' XTERM_PATH = '/usr/bin/xterm' +# The underlying Solaris Zones framework does not expose a specific +# version number, instead relying on feature tests to identify what is +# and what is not supported. A HYPERVISOR_VERSION is defined here for +# Nova's use but it generally should not be changed unless there is a +# incompatible change such as concerning kernel zone live migration. +HYPERVISOR_VERSION = '5.11' + def lookup_resource_property(zone, resource, prop, filter=None): """Lookup specified property from specified Solaris Zone resource.""" @@ -2244,7 +2258,8 @@ host_stats['local_gb_used'] = host_stats['local_gb'] - free_disk_gb host_stats['hypervisor_type'] = 'solariszones' - host_stats['hypervisor_version'] = int(self._uname[2].replace('.', '')) + host_stats['hypervisor_version'] = \ + utils.convert_version_to_int(HYPERVISOR_VERSION) host_stats['hypervisor_hostname'] = self._uname[1] if self._uname[4] == 'i86pc': @@ -2306,7 +2321,22 @@ :param disk_info: instance disk information :param migrate_data: implementation specific data dict. """ - raise NotImplementedError() + return {} + + def _live_migration(self, name, dest, dry_run=False): + """Live migration of a Solaris kernel zone to another host.""" + zone = self._get_zone_by_name(name) + if zone is None: + raise exception.InstanceNotFound(instance_id=name) + + options = [] + live_migration_cipher = CONF.live_migration_cipher + if live_migration_cipher is not None: + options.extend(['-c', live_migration_cipher]) + if dry_run: + options.append('-nq') + options.append('ssh://nova@' + dest) + zone.migrate(options) def live_migration(self, context, instance, dest, post_method, recover_method, block_migration=False, @@ -2328,7 +2358,17 @@ :param migrate_data: implementation specific params. """ - raise NotImplementedError() + name = instance['name'] + try: + self._live_migration(name, dest, dry_run=False) + except Exception as ex: + with excutils.save_and_reraise_exception(): + LOG.error(_("Unable to live migrate instance '%s' to host " + "'%s' via zonemgr(3RAD): %s") + % (name, dest, ex)) + recover_method(context, instance, dest, block_migration) + + post_method(context, instance, dest, block_migration, migrate_data) def rollback_live_migration_at_destination(self, context, instance, network_info, @@ -2346,7 +2386,7 @@ :param migrate_data: implementation specific params """ - raise NotImplementedError() + pass def post_live_migration(self, context, instance, block_device_info, migrate_data=None): @@ -2357,7 +2397,30 @@ :block_device_info: instance block device information :param migrate_data: if not None, it is a dict which has data """ - pass + try: + # These methods log if problems occur so no need to double log + # here. Just catch any stray exceptions and allow destroy to + # proceed. + if self._has_vnc_console_service(instance): + self._disable_vnc_console_service(instance) + self._delete_vnc_console_service(instance) + except Exception: + pass + + name = instance['name'] + zone = self._get_zone_by_name(name) + # If instance cannot be found, just return. + if zone is None: + LOG.warning(_("Unable to find instance '%s' via zonemgr(3RAD)") + % name) + return + + try: + self._delete_config(instance) + except Exception as ex: + LOG.error(_("Unable to delete configuration for instance '%s' via " + "zonemgr(3RAD): %s") % (name, ex)) + raise def post_live_migration_at_source(self, context, instance, network_info): """Unplug VIFs from networks at source. @@ -2380,7 +2443,7 @@ :param network_info: instance network information :param block_migration: if true, post operation of block_migration. """ - raise NotImplementedError() + pass def check_instance_shared_storage_local(self, context, instance): """Check if instance files located on shared storage. @@ -2426,7 +2489,37 @@ :param disk_over_commit: if true, allow disk over commit :returns: a dict containing migration info (hypervisor-dependent) """ - raise NotImplementedError() + src_cpu_info = jsonutils.loads(src_compute_info['cpu_info']) + src_cpu_arch = src_cpu_info['arch'] + dst_cpu_info = jsonutils.loads(dst_compute_info['cpu_info']) + dst_cpu_arch = dst_cpu_info['arch'] + if src_cpu_arch != dst_cpu_arch: + reason = (_("CPU architectures between source host '%s' (%s) and " + "destination host '%s' (%s) are incompatible.") + % (src_compute_info['hypervisor_hostname'], src_cpu_arch, + dst_compute_info['hypervisor_hostname'], + dst_cpu_arch)) + raise exception.MigrationPreCheckError(reason=reason) + + extra_specs = self._get_extra_specs(instance) + brand = extra_specs.get('zonecfg:brand', ZONE_BRAND_SOLARIS) + if brand != ZONE_BRAND_SOLARIS_KZ: + # Only Solaris kernel zones are currently supported. + reason = (_("'%s' branded zones do not currently support live " + "migration.") % brand) + raise exception.MigrationPreCheckError(reason=reason) + + if block_migration: + reason = (_('Block migration is not currently supported.')) + raise exception.MigrationPreCheckError(reason=reason) + if disk_over_commit: + reason = (_('Disk overcommit is not currently supported.')) + raise exception.MigrationPreCheckError(reason=reason) + + dest_check_data = { + 'hypervisor_hostname': dst_compute_info['hypervisor_hostname'] + } + return dest_check_data def check_can_live_migrate_destination_cleanup(self, context, dest_check_data): @@ -2435,10 +2528,21 @@ :param context: security context :param dest_check_data: result of check_can_live_migrate_destination """ - raise NotImplementedError() + pass + + def _check_local_volumes_present(self, block_device_info): + """Check if local volumes are attached to the instance.""" + bmap = block_device_info.get('block_device_mapping') + for entry in bmap: + connection_info = entry['connection_info'] + driver_type = connection_info['driver_volume_type'] + if driver_type == 'local': + reason = (_("Instances with attached '%s' volumes are not " + "currently supported.") % driver_type) + raise exception.MigrationPreCheckError(reason=reason) def check_can_live_migrate_source(self, context, instance, - dest_check_data): + dest_check_data, block_device_info): """Check if it is possible to execute live migration. This checks if the live migration can succeed, based on the @@ -2447,9 +2551,17 @@ :param context: security context :param instance: nova.db.sqlalchemy.models.Instance :param dest_check_data: result of check_can_live_migrate_destination + :param block_device_info: result of _get_instance_block_device_info :returns: a dict containing migration info (hypervisor-dependent) """ - raise NotImplementedError() + self._check_local_volumes_present(block_device_info) + name = instance['name'] + dest = dest_check_data['hypervisor_hostname'] + try: + self._live_migration(name, dest, dry_run=True) + except Exception as ex: + raise exception.MigrationPreCheckError(reason=ex) + return dest_check_data def get_instance_disk_info(self, instance_name, block_device_info=None): @@ -2578,7 +2690,7 @@ """ # TODO(Vek): Need to pass context in for access to auth_token - raise NotImplementedError() + pass def filter_defer_apply_on(self): """Defer application of IPTables rules.""" @@ -2591,7 +2703,7 @@ def unfilter_instance(self, instance, network_info): """Stop filtering instance.""" # TODO(Vek): Need to pass context in for access to auth_token - raise NotImplementedError() + pass def set_admin_password(self, instance, new_pass): """Set the root password on the specified instance. diff -r 8566c7ab4a73 -r bca6b9853ab7 components/openstack/nova/nova.p5m --- a/components/openstack/nova/nova.p5m Tue Dec 15 00:08:40 2015 -0800 +++ b/components/openstack/nova/nova.p5m Tue Dec 15 00:08:40 2015 -0800 @@ -854,7 +854,7 @@ # group groupname=nova gid=85 user username=nova ftpuser=false gcos-field="OpenStack Nova" group=nova \ - home-dir=/var/lib/nova uid=85 + home-dir=/var/lib/nova password=NP uid=85 # license nova.license license="Apache v2.0" diff -r 8566c7ab4a73 -r bca6b9853ab7 components/openstack/nova/patches/10-launchpad-1356552.patch --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/components/openstack/nova/patches/10-launchpad-1356552.patch Tue Dec 15 00:08:40 2015 -0800 @@ -0,0 +1,208 @@ +commit 42cae28241cd0c213201d036bfbe13fb118e4bee +Author: Cyril Roelandt +Date: Mon Aug 18 17:45:35 2014 +0000 + + libvirt: Make sure volumes are well detected during block migration + + Current implementation of live migration in libvirt incorrectly includes + block devices on shared storage (e.g., NFS) when computing destination + storage requirements. Since these volumes are already on shared storage + they do not need to be migrated. As a result, migration fails if the + amount of free space on the shared drive is less than the size of the + volume to be migrated. The problem is addressed by adding a + block_device_info parameter to check_can_live_migrate_source() to allow + volumes to be filtered correctly when computing migration space + requirements. + + This only fixes the issue on libvirt: it is unclear whether other + implementations suffer from the same issue. + + Thanks to Florent Flament for spotting and fixing an issue while trying out + this patch. + + Co-Authored-By: Florent Flament + Change-Id: Iac7d2cd2a70800fd89864463ca45c030c47411b0 + Closes-Bug: #1356552 + (cherry picked from commit 671aa9f8b7ca5274696f83bde0d4822ee431b837) + +--- nova-2014.2.2/nova/compute/manager.py.~3~ 2015-12-01 05:07:52.781465660 -0800 ++++ nova-2014.2.2/nova/compute/manager.py 2015-12-01 05:07:52.795381628 -0800 +@@ -4891,8 +4891,11 @@ class ComputeManager(manager.Manager): + is_volume_backed = self.compute_api.is_volume_backed_instance(ctxt, + instance) + dest_check_data['is_volume_backed'] = is_volume_backed ++ block_device_info = self._get_instance_block_device_info( ++ ctxt, instance, refresh_conn_info=True) + return self.driver.check_can_live_migrate_source(ctxt, instance, +- dest_check_data) ++ dest_check_data, ++ block_device_info) + + @object_compat + @wrap_exception() +--- nova-2014.2.2/nova/tests/compute/test_compute_mgr.py.~2~ 2015-12-01 05:07:52.782691092 -0800 ++++ nova-2014.2.2/nova/tests/compute/test_compute_mgr.py 2015-12-01 05:07:52.796520248 -0800 +@@ -1302,13 +1302,19 @@ class ComputeManagerUnitTestCase(test.No + + self.mox.StubOutWithMock(self.compute.compute_api, + 'is_volume_backed_instance') ++ self.mox.StubOutWithMock(self.compute, ++ '_get_instance_block_device_info') + self.mox.StubOutWithMock(self.compute.driver, + 'check_can_live_migrate_source') + + self.compute.compute_api.is_volume_backed_instance( + self.context, instance).AndReturn(is_volume_backed) ++ self.compute._get_instance_block_device_info( ++ self.context, instance, refresh_conn_info=True ++ ).AndReturn({'block_device_mapping': 'fake'}) + self.compute.driver.check_can_live_migrate_source( +- self.context, instance, expected_dest_check_data) ++ self.context, instance, expected_dest_check_data, ++ {'block_device_mapping': 'fake'}) + + self.mox.ReplayAll() + +diff --git a/nova/tests/virt/libvirt/test_driver.py b/nova/tests/virt/libvirt/test_driver.py +index 613943b..c686b88 100644 +--- a/nova/tests/virt/libvirt/test_driver.py ++++ b/nova/tests/virt/libvirt/test_driver.py +@@ -5323,7 +5323,7 @@ class LibvirtConnTestCase(test.TestCase): + self.mox.StubOutWithMock(conn, "_assert_dest_node_has_enough_disk") + conn._assert_dest_node_has_enough_disk( + self.context, instance, dest_check_data['disk_available_mb'], +- False) ++ False, None) + + self.mox.ReplayAll() + ret = conn.check_can_live_migrate_source(self.context, instance, +@@ -5386,8 +5386,9 @@ class LibvirtConnTestCase(test.TestCase): + disk_available_mb=0) + + self.mox.StubOutWithMock(conn, "get_instance_disk_info") +- conn.get_instance_disk_info(instance["name"]).AndReturn( +- '[{"virt_disk_size":2}]') ++ conn.get_instance_disk_info(instance["name"], ++ block_device_info=None).AndReturn( ++ '[{"virt_disk_size":2}]') + + self.mox.ReplayAll() + self.assertRaises(exception.MigrationError, +diff --git a/nova/virt/driver.py b/nova/virt/driver.py +index fd483e5..20f4dd1 100644 +--- a/nova/virt/driver.py ++++ b/nova/virt/driver.py +@@ -808,7 +808,7 @@ class ComputeDriver(object): + raise NotImplementedError() + + def check_can_live_migrate_source(self, context, instance, +- dest_check_data): ++ dest_check_data, block_device_info=None): + """Check if it is possible to execute live migration. + + This checks if the live migration can succeed, based on the +@@ -817,6 +817,7 @@ class ComputeDriver(object): + :param context: security context + :param instance: nova.db.sqlalchemy.models.Instance + :param dest_check_data: result of check_can_live_migrate_destination ++ :param block_device_info: result of _get_instance_block_device_info + :returns: a dict containing migration info (hypervisor-dependent) + """ + raise NotImplementedError() +diff --git a/nova/virt/fake.py b/nova/virt/fake.py +index 049c519..fe9ff1c 100644 +--- a/nova/virt/fake.py ++++ b/nova/virt/fake.py +@@ -426,7 +426,7 @@ class FakeDriver(driver.ComputeDriver): + return {} + + def check_can_live_migrate_source(self, ctxt, instance_ref, +- dest_check_data): ++ dest_check_data, block_device_info=None): + return + + def finish_migration(self, context, migration, instance, disk_info, +diff --git a/nova/virt/hyperv/driver.py b/nova/virt/hyperv/driver.py +index 485aa23..fa42130 100644 +--- a/nova/virt/hyperv/driver.py ++++ b/nova/virt/hyperv/driver.py +@@ -169,7 +169,7 @@ class HyperVDriver(driver.ComputeDriver): + context, dest_check_data) + + def check_can_live_migrate_source(self, context, instance, +- dest_check_data): ++ dest_check_data, block_device_info=None): + return self._livemigrationops.check_can_live_migrate_source( + context, instance, dest_check_data) + +diff --git a/nova/virt/libvirt/driver.py b/nova/virt/libvirt/driver.py +index cec2013..d420f15 100644 +--- a/nova/virt/libvirt/driver.py ++++ b/nova/virt/libvirt/driver.py +@@ -5028,7 +5028,8 @@ class LibvirtDriver(driver.ComputeDriver): + self._cleanup_shared_storage_test_file(filename) + + def check_can_live_migrate_source(self, context, instance, +- dest_check_data): ++ dest_check_data, ++ block_device_info=None): + """Check if it is possible to execute live migration. + + This checks if the live migration can succeed, based on the +@@ -5037,6 +5038,7 @@ class LibvirtDriver(driver.ComputeDriver): + :param context: security context + :param instance: nova.db.sqlalchemy.models.Instance + :param dest_check_data: result of check_can_live_migrate_destination ++ :param block_device_info: result of _get_instance_block_device_info + :returns: a dict containing migration info + """ + # Checking shared storage connectivity +@@ -5058,7 +5060,8 @@ class LibvirtDriver(driver.ComputeDriver): + raise exception.InvalidLocalStorage(reason=reason, path=source) + self._assert_dest_node_has_enough_disk(context, instance, + dest_check_data['disk_available_mb'], +- dest_check_data['disk_over_commit']) ++ dest_check_data['disk_over_commit'], ++ block_device_info) + + elif not (dest_check_data['is_shared_block_storage'] or + dest_check_data['is_shared_instance_path']): +@@ -5106,7 +5109,8 @@ class LibvirtDriver(driver.ComputeDriver): + return False + + def _assert_dest_node_has_enough_disk(self, context, instance, +- available_mb, disk_over_commit): ++ available_mb, disk_over_commit, ++ block_device_info=None): + """Checks if destination has enough disk for block migration.""" + # Libvirt supports qcow2 disk format,which is usually compressed + # on compute nodes. +@@ -5122,7 +5126,8 @@ class LibvirtDriver(driver.ComputeDriver): + if available_mb: + available = available_mb * units.Mi + +- ret = self.get_instance_disk_info(instance['name']) ++ ret = self.get_instance_disk_info(instance['name'], ++ block_device_info=block_device_info) + disk_infos = jsonutils.loads(ret) + + necessary = 0 +--- nova-2014.2.2/nova/virt/xenapi/driver.py.~2~ 2015-12-01 05:16:19.562306358 -0800 ++++ nova-2014.2.2/nova/virt/xenapi/driver.py 2015-12-01 05:16:19.614403555 -0800 +@@ -501,7 +501,7 @@ class XenAPIDriver(driver.ComputeDriver) + pass + + def check_can_live_migrate_source(self, context, instance, +- dest_check_data): ++ dest_check_data, block_device_info=None): + """Check if it is possible to execute live migration. + + This checks if the live migration can succeed, based on the +@@ -511,6 +511,7 @@ class XenAPIDriver(driver.ComputeDriver) + :param instance: nova.db.sqlalchemy.models.Instance + :param dest_check_data: result of check_can_live_migrate_destination + includes the block_migration flag ++ :param block_device_info: result of _get_instance_block_device_info + """ + return self._vmops.check_can_live_migrate_source(context, instance, + dest_check_data) diff -r 8566c7ab4a73 -r bca6b9853ab7 components/openstack/nova/patches/11-launchpad-1377644.patch --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/components/openstack/nova/patches/11-launchpad-1377644.patch Tue Dec 15 00:08:40 2015 -0800 @@ -0,0 +1,62 @@ +This external patch was a follow up fix to Launchpad bug 1377644. +While the original fix was backported to Juno 2014.2.1, the subsequent +one was not. + +commit 64882d39d9fea9d6001ccc61973624949825c52f +Author: He Jie Xu +Date: Fri Nov 7 23:24:12 2014 +0800 + + Fix circular reference error when live migration failed + + When unexpected exception raised in live migration, the MigrationError + carry another original exception in it. But when oslo.message + serialize the exception, the circular reference error happended. This + patch just pass the exception as unicode string into MigrationError. + + Change-Id: I4e449baae74bd9a15490ae7accbd2103bae90d57 + Related-Bug: #1377644 + +--- nova-2014.2.2/nova/conductor/manager.py.~1~ 2015-12-01 04:52:25.839270759 -0800 ++++ nova-2014.2.2/nova/conductor/manager.py 2015-12-01 04:54:08.341268026 -0800 +@@ -589,7 +589,7 @@ class ComputeTaskManager(base.Base): + ' %(dest)s unexpectedly failed.'), + {'instance_id': instance['uuid'], 'dest': destination}, + exc_info=True) +- raise exception.MigrationError(reason=ex) ++ raise exception.MigrationError(reason=six.text_type(ex)) + + def build_instances(self, context, instances, image, filter_properties, + admin_password, injected_files, requested_networks, +--- nova-2014.2.2/nova/tests/conductor/test_conductor.py.~1~ 2015-02-05 06:26:50.000000000 -0800 ++++ nova-2014.2.2/nova/tests/conductor/test_conductor.py 2015-12-01 04:55:27.135695264 -0800 +@@ -20,6 +20,7 @@ import contextlib + import mock + import mox + from oslo import messaging ++import six + + from nova.api.ec2 import ec2utils + from nova.compute import arch +@@ -1711,18 +1712,19 @@ class ConductorTaskTestCase(_BaseTaskTes + self.mox.StubOutWithMock(scheduler_utils, + 'set_vm_state_and_notify') + +- ex = IOError() ++ expected_ex = IOError('fake error') + live_migrate.execute(self.context, mox.IsA(objects.Instance), + 'destination', 'block_migration', +- 'disk_over_commit').AndRaise(ex) ++ 'disk_over_commit').AndRaise(expected_ex) + self.mox.ReplayAll() + + self.conductor = utils.ExceptionHelper(self.conductor) + +- self.assertRaises(exc.MigrationError, ++ ex = self.assertRaises(exc.MigrationError, + self.conductor.migrate_server, self.context, inst_obj, + {'host': 'destination'}, True, False, None, 'block_migration', + 'disk_over_commit') ++ self.assertEqual(ex.kwargs['reason'], six.text_type(expected_ex)) + + def test_set_vm_state_and_notify(self): + self.mox.StubOutWithMock(scheduler_utils, diff -r 8566c7ab4a73 -r bca6b9853ab7 components/openstack/nova/patches/12-launchpad-1397153.patch --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/components/openstack/nova/patches/12-launchpad-1397153.patch Tue Dec 15 00:08:40 2015 -0800 @@ -0,0 +1,103 @@ +commit 40a37269c52977a718e91012cfd4580b2e31ec65 +Author: He Jie Xu +Date: Fri Nov 7 23:29:18 2014 +0800 + + Set vm state error when raising unexpected exception in live migrate + + The instance stuck at migrating task_state when unexpected exception. + This is confuse for user, this patch set the vm_state to error. + + Change-Id: Ib1b97452bc5e777c66c4d368f71156dbe1e116b7 + Partial-Bug: 1397153 + +--- nova-2014.2.2/nova/conductor/manager.py.~2~ 2015-12-01 04:57:58.583807338 -0800 ++++ nova-2014.2.2/nova/conductor/manager.py 2015-12-01 05:00:43.319845439 -0800 +@@ -558,6 +558,19 @@ class ComputeTaskManager(base.Base): + def _live_migrate(self, context, instance, scheduler_hint, + block_migration, disk_over_commit): + destination = scheduler_hint.get("host") ++ ++ def _set_vm_state(context, instance, ex, vm_state=None, ++ task_state=None): ++ request_spec = {'instance_properties': { ++ 'uuid': instance['uuid'], }, ++ } ++ scheduler_utils.set_vm_state_and_notify(context, ++ 'compute_task', 'migrate_server', ++ dict(vm_state=vm_state, ++ task_state=task_state, ++ expected_task_state=task_states.MIGRATING,), ++ ex, request_spec, self.db) ++ + try: + live_migrate.execute(context, instance, destination, + block_migration, disk_over_commit) +@@ -575,20 +588,14 @@ class ComputeTaskManager(base.Base): + exception.LiveMigrationWithOldNovaNotSafe) as ex: + with excutils.save_and_reraise_exception(): + # TODO(johngarbutt) - eventually need instance actions here +- request_spec = {'instance_properties': { +- 'uuid': instance['uuid'], }, +- } +- scheduler_utils.set_vm_state_and_notify(context, +- 'compute_task', 'migrate_server', +- dict(vm_state=instance['vm_state'], +- task_state=None, +- expected_task_state=task_states.MIGRATING,), +- ex, request_spec, self.db) ++ _set_vm_state(context, instance, ex, instance['vm_state']) + except Exception as ex: + LOG.error(_('Migration of instance %(instance_id)s to host' + ' %(dest)s unexpectedly failed.'), + {'instance_id': instance['uuid'], 'dest': destination}, + exc_info=True) ++ _set_vm_state(context, instance, ex, vm_states.ERROR, ++ instance['task_state']) + raise exception.MigrationError(reason=six.text_type(ex)) + + def build_instances(self, context, instances, image, filter_properties, +--- nova-2014.2.2/nova/tests/conductor/test_conductor.py.~2~ 2015-12-01 04:57:58.599204982 -0800 ++++ nova-2014.2.2/nova/tests/conductor/test_conductor.py 2015-12-01 05:04:39.416251458 -0800 +@@ -1704,27 +1704,28 @@ class ConductorTaskTestCase(_BaseTaskTes + ex = exc.LiveMigrationWithOldNovaNotSafe(server='dummy') + self._test_migrate_server_deals_with_expected_exceptions(ex) + +- def test_migrate_server_deals_with_unexpected_exceptions(self): ++ @mock.patch.object(scheduler_utils, 'set_vm_state_and_notify') ++ @mock.patch.object(live_migrate, 'execute') ++ def test_migrate_server_deals_with_unexpected_exceptions(self, ++ mock_live_migrate, mock_set_state): ++ expected_ex = IOError('fake error') ++ mock_live_migrate.side_effect = expected_ex + instance = fake_instance.fake_db_instance() + inst_obj = objects.Instance._from_db_object( + self.context, objects.Instance(), instance, []) +- self.mox.StubOutWithMock(live_migrate, 'execute') +- self.mox.StubOutWithMock(scheduler_utils, +- 'set_vm_state_and_notify') +- +- expected_ex = IOError('fake error') +- live_migrate.execute(self.context, mox.IsA(objects.Instance), +- 'destination', 'block_migration', +- 'disk_over_commit').AndRaise(expected_ex) +- self.mox.ReplayAll() +- +- self.conductor = utils.ExceptionHelper(self.conductor) +- + ex = self.assertRaises(exc.MigrationError, + self.conductor.migrate_server, self.context, inst_obj, + {'host': 'destination'}, True, False, None, 'block_migration', + 'disk_over_commit') +- self.assertEqual(ex.kwargs['reason'], six.text_type(expected_ex)) ++ request_spec = {'instance_properties': { ++ 'uuid': instance['uuid'], }, ++ } ++ mock_set_state.assert_called_once_with(self.context, ++ 'compute_task', 'migrate_server', ++ dict(vm_state=vm_states.ERROR, ++ task_state=inst_obj.task_state, ++ expected_task_state=task_states.MIGRATING,), ++ expected_ex, request_spec, self.conductor.db) + + def test_set_vm_state_and_notify(self): + self.mox.StubOutWithMock(scheduler_utils,