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
--- 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'
--- 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=<None>
+
+# Location to store snapshots before uploading them to the
+# Glance image service. (string value)
#solariszones_snapshots_directory=$instances_path/snapshots
--- 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
--- 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.
--- 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"
--- /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 <[email protected]>
+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 <[email protected]>
+ 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)
--- /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 <[email protected]>
+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,
--- /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 <[email protected]>
+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,