Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 45 additions & 5 deletions nova/conductor/tasks/migrate.py
Original file line number Diff line number Diff line change
Expand Up @@ -363,10 +363,11 @@ def _execute(self):
# Convert image/local roots before the BFV-only prep_resize path.
# Revert keeps the instance BFV-on-VMware.
if self.instance.system_metadata.get('cross_hv_resize') == 'true':
root_bdm = self._get_root_bdm()
bdms = self._get_bdms()
root_bdm = self._get_root_bdm(bdms)
self._validate_cross_hv_root_bdm(root_bdm)
self._validate_cross_hv_attached_volume_types(bdms)
if self._is_image_backed_local_root(root_bdm):
self._validate_cross_hv_manage_config()
self._convert_image_backed_root_to_bfv(root_bdm)

# NOTE: set after the conversion above, which saves the instance
Expand Down Expand Up @@ -415,12 +416,30 @@ def _validate_cross_hv_manage_config():
raise exception.CrossHVConfigurationMissing(
option='fcd_volume_type')

def _get_root_bdm(self):
"""Load and return the root BDM for the instance, or None."""
bdms = objects.BlockDeviceMappingList.get_by_instance_uuid(
def _get_bdms(self):
"""Load and return all BDMs for the instance."""
return objects.BlockDeviceMappingList.get_by_instance_uuid(
self.context, self.instance.uuid)

def _get_root_bdm(self, bdms=None):
"""Load and return the root BDM for the instance, or None."""
if bdms is None:
bdms = self._get_bdms()
return bdms.root_bdm()

def _get_cross_hv_expected_volume_type(self):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we cache the result? Volume types should be basically static and I don't think we should rely on the configured volume type becoming available after we rolled out Nova.

"""Resolve the configured FCD volume type."""
self._validate_cross_hv_manage_config()
configured_type = CONF.cross_hv.fcd_volume_type

for volume_type in self.volume_api.get_all_volume_types(self.context):
if configured_type in (volume_type['id'], volume_type['name']):
return volume_type

raise exception.InvalidCrossHvResizePrecondition(
reason='Configured [cross_hv] fcd_volume_type %s was not found '
'in Cinder volume types.' % configured_type)

def _is_image_backed_local_root(self, root_bdm):
"""Return True if root_bdm is an image-backed local ephemeral disk."""
if root_bdm is None:
Expand Down Expand Up @@ -456,6 +475,27 @@ def _validate_cross_hv_root_bdm(self, root_bdm):
'or */volume root disks are supported.' %
(root_bdm.source_type, root_bdm.destination_type))

def _validate_cross_hv_attached_volume_types(self, bdms):
"""Ensure every attached Cinder volume matches the supported type."""
expected_type = self._get_cross_hv_expected_volume_type()
expected_types = {expected_type['id'], expected_type['name']}
expected_type_name = expected_type['name']
unsupported = []

for bdm in bdms:
if bdm.destination_type != 'volume' or not bdm.volume_id:
continue
volume = self.volume_api.get(self.context, bdm.volume_id)
volume_type = volume.get('volume_type_id') or '<unset>'
if volume_type not in expected_types:
unsupported.append('%s (%s)' % (bdm.volume_id, volume_type))

if unsupported:
raise exception.InvalidCrossHvResizePrecondition(
reason='All attached Cinder volumes must use volume type %s '
'for cross-HV resize. Unsupported volumes: %s' %
(expected_type_name, ', '.join(unsupported)))

def _set_cross_hv_root_detach_state(self, value):
"""Set or clear cross_hv_root_detach_state and save the instance.

Expand Down
Loading