8b3701490e
This is needed in order to pass TPM secret information to the destination over RPC to support the 'host' secret security mode. The fields are nullable so that secret security modes 'user' and 'deployment' may set them to None. A setting of None lets the other security modes convey that they are actively choosing not to pass any data in the vTPM fields. This is important for interacting with older compute hosts in the middle of a rolling upgrade. We do not want to backlevel new LibvirtLiveMigrateData objects involving vTPM because older compute hosts cannot support vTPM live migration in any capacity. Related to blueprint vtpm-live-migration Change-Id: If2ff2a7bb41dea6e0959c965477b79f3f7d633e7 Signed-off-by: melanie witt <melwittt@gmail.com>
417 lines
18 KiB
Python
417 lines
18 KiB
Python
# Copyright 2015 Red Hat, Inc.
|
|
#
|
|
# 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
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
import copy
|
|
|
|
from oslo_log import log
|
|
from oslo_serialization import jsonutils
|
|
from oslo_utils import versionutils
|
|
|
|
from nova import exception
|
|
from nova.objects import base as obj_base
|
|
from nova.objects import fields
|
|
|
|
LOG = log.getLogger(__name__)
|
|
OS_VIF_DELEGATION = 'os_vif_delegation'
|
|
|
|
__all__ = [
|
|
'HyperVLiveMigrateData',
|
|
'LibvirtLiveMigrateBDMInfo',
|
|
'LibvirtLiveMigrateData',
|
|
'LibvirtLiveMigrateNUMAInfo',
|
|
'LiveMigrateData',
|
|
'VIFMigrateData',
|
|
'VMwareLiveMigrateData',
|
|
]
|
|
|
|
|
|
@obj_base.NovaObjectRegistry.register
|
|
class VIFMigrateData(obj_base.NovaObject):
|
|
# Version 1.0: Initial version
|
|
VERSION = '1.0'
|
|
|
|
# The majority of the fields here represent a port binding on the
|
|
# **destination** host during a live migration. The vif_type, among
|
|
# other fields, could be different from the existing binding on the
|
|
# source host, which is represented by the "source_vif" field.
|
|
fields = {
|
|
'port_id': fields.StringField(),
|
|
'vnic_type': fields.StringField(), # TODO(sean-k-mooney): make enum?
|
|
'vif_type': fields.StringField(),
|
|
# vif_details is a dict whose contents are dependent on the vif_type
|
|
# and can be any number of types for the values, so we just store it
|
|
# as a serialized dict
|
|
'vif_details_json': fields.StringField(),
|
|
# profile is in the same random dict of terrible boat as vif_details
|
|
# so it's stored as a serialized json string
|
|
'profile_json': fields.StringField(),
|
|
'host': fields.StringField(),
|
|
# The source_vif attribute is a copy of the VIF network model
|
|
# representation of the port on the source host which can be used
|
|
# for filling in blanks about the VIF (port) when building a
|
|
# configuration reference for the destination host.
|
|
# NOTE(mriedem): This might not be sufficient based on how the
|
|
# destination host is configured for all vif types. See the note in
|
|
# the libvirt driver here: https://review.opendev.org/#/c/551370/
|
|
# 29/nova/virt/libvirt/driver.py@7036
|
|
'source_vif': fields.NetworkVIFModelField(),
|
|
}
|
|
|
|
@property
|
|
def vif_details(self):
|
|
if 'vif_details_json' not in self:
|
|
return {}
|
|
return jsonutils.loads(self.vif_details_json)
|
|
|
|
@vif_details.setter
|
|
def vif_details(self, vif_details_dict):
|
|
self.vif_details_json = jsonutils.dumps(vif_details_dict)
|
|
|
|
@property
|
|
def profile(self):
|
|
if 'profile_json' not in self:
|
|
return {}
|
|
return jsonutils.loads(self.profile_json)
|
|
|
|
@profile.setter
|
|
def profile(self, profile_dict):
|
|
self.profile_json = jsonutils.dumps(profile_dict)
|
|
|
|
@property
|
|
def supports_os_vif_delegation(self):
|
|
return self.profile.get(OS_VIF_DELEGATION, False)
|
|
|
|
# TODO(stephenfin): add a proper delegation field instead of storing this
|
|
# info in the profile catch-all blob
|
|
@supports_os_vif_delegation.setter
|
|
def supports_os_vif_delegation(self, supported):
|
|
# we can't simply set the attribute using dict notation since the
|
|
# getter returns a copy of the data, not the data itself
|
|
self.profile = dict(
|
|
self.profile or {}, **{OS_VIF_DELEGATION: supported})
|
|
|
|
def get_dest_vif(self):
|
|
"""Get a destination VIF representation of this object.
|
|
|
|
This method takes the source_vif and updates it to include the
|
|
destination host port binding information using the other fields
|
|
on this object.
|
|
|
|
:return: nova.network.model.VIF object
|
|
"""
|
|
if 'source_vif' not in self:
|
|
raise exception.ObjectActionError(
|
|
action='get_dest_vif', reason='source_vif is not set')
|
|
vif = copy.deepcopy(self.source_vif)
|
|
vif['type'] = self.vif_type
|
|
vif['vnic_type'] = self.vnic_type
|
|
vif['profile'] = self.profile
|
|
vif['details'] = self.vif_details
|
|
vif['delegate_create'] = self.supports_os_vif_delegation
|
|
return vif
|
|
|
|
@classmethod
|
|
def create_skeleton_migrate_vifs(cls, vifs):
|
|
"""Create migrate vifs for live migration.
|
|
|
|
:param vifs: a list of VIFs.
|
|
:return: list of VIFMigrateData object corresponding to the provided
|
|
VIFs.
|
|
"""
|
|
vif_mig_data = []
|
|
|
|
for vif in vifs:
|
|
mig_vif = cls(port_id=vif['id'], source_vif=vif)
|
|
vif_mig_data.append(mig_vif)
|
|
return vif_mig_data
|
|
|
|
|
|
@obj_base.NovaObjectRegistry.register
|
|
class LibvirtLiveMigrateNUMAInfo(obj_base.NovaObject):
|
|
# Version 1.0: Initial version
|
|
VERSION = '1.0'
|
|
|
|
fields = {
|
|
# NOTE(artom) We need a 1:many cardinality here, so DictOfIntegers with
|
|
# its 1:1 cardinality cannot work here. cpu_pins can have a single
|
|
# guest CPU pinned to multiple host CPUs.
|
|
'cpu_pins': fields.DictOfSetOfIntegersField(),
|
|
# NOTE(artom) Currently we never pin a guest cell to more than a single
|
|
# host cell, so cell_pins could be a DictOfIntegers, but
|
|
# DictOfSetOfIntegers is more future-proof.
|
|
'cell_pins': fields.DictOfSetOfIntegersField(),
|
|
'emulator_pins': fields.SetOfIntegersField(),
|
|
'sched_vcpus': fields.SetOfIntegersField(),
|
|
'sched_priority': fields.IntegerField(),
|
|
}
|
|
|
|
|
|
@obj_base.NovaObjectRegistry.register_if(False)
|
|
class LiveMigrateData(obj_base.NovaObject):
|
|
# Version 1.0: Initial version
|
|
# Version 1.1: Added old_vol_attachment_ids field.
|
|
# Version 1.2: Added wait_for_vif_plugged
|
|
# Version 1.3: Added vifs field.
|
|
# Version 1.4: Added pci_dev_map_src_dst field.
|
|
VERSION = '1.4'
|
|
|
|
fields = {
|
|
'is_volume_backed': fields.BooleanField(),
|
|
'migration': fields.ObjectField('Migration'),
|
|
# old_vol_attachment_ids is a dict used to store the old attachment_ids
|
|
# for each volume so they can be restored on a migration rollback. The
|
|
# key is the volume_id, and the value is the attachment_id.
|
|
# TODO(mdbooth): This field was made redundant by change Ibe9215c0. We
|
|
# should eventually remove it.
|
|
'old_vol_attachment_ids': fields.DictOfStringsField(),
|
|
# wait_for_vif_plugged is set in pre_live_migration on the destination
|
|
# compute host based on the [compute]/live_migration_wait_for_vif_plug
|
|
# config option value; a default value is not set here since the
|
|
# default for the config option may change in the future
|
|
'wait_for_vif_plugged': fields.BooleanField(),
|
|
'vifs': fields.ListOfObjectsField('VIFMigrateData'),
|
|
'pci_dev_map_src_dst': fields.DictOfStringsField(),
|
|
}
|
|
|
|
|
|
@obj_base.NovaObjectRegistry.register
|
|
class LibvirtLiveMigrateBDMInfo(obj_base.NovaObject):
|
|
# VERSION 1.0 : Initial version
|
|
# VERSION 1.1 : Added encryption_secret_uuid for tracking volume secret
|
|
# uuid created on dest during migration with encrypted vols.
|
|
VERSION = '1.1'
|
|
|
|
fields = {
|
|
# FIXME(danms): some of these can be enums?
|
|
'serial': fields.StringField(),
|
|
'bus': fields.StringField(),
|
|
'dev': fields.StringField(),
|
|
'type': fields.StringField(),
|
|
'format': fields.StringField(nullable=True),
|
|
'boot_index': fields.IntegerField(nullable=True),
|
|
'connection_info_json': fields.StringField(),
|
|
'encryption_secret_uuid': fields.UUIDField(nullable=True),
|
|
}
|
|
|
|
def obj_make_compatible(self, primitive, target_version):
|
|
super(LibvirtLiveMigrateBDMInfo, self).obj_make_compatible(
|
|
primitive, target_version)
|
|
target_version = versionutils.convert_version_to_tuple(target_version)
|
|
if target_version < (1, 1) and 'encryption_secret_uuid' in primitive:
|
|
del primitive['encryption_secret_uuid']
|
|
|
|
# NOTE(danms): We don't have a connection_info object right
|
|
# now, and instead mostly store/pass it as JSON that we're
|
|
# careful with. When we get a connection_info object in the
|
|
# future, we should use it here, so make this easy to convert
|
|
# for later.
|
|
@property
|
|
def connection_info(self):
|
|
return jsonutils.loads(self.connection_info_json)
|
|
|
|
@connection_info.setter
|
|
def connection_info(self, info):
|
|
self.connection_info_json = jsonutils.dumps(info)
|
|
|
|
def as_disk_info(self):
|
|
info_dict = {
|
|
'dev': self.dev,
|
|
'bus': self.bus,
|
|
'type': self.type,
|
|
}
|
|
if self.obj_attr_is_set('format') and self.format:
|
|
info_dict['format'] = self.format
|
|
if self.obj_attr_is_set('boot_index') and self.boot_index is not None:
|
|
info_dict['boot_index'] = str(self.boot_index)
|
|
return info_dict
|
|
|
|
|
|
@obj_base.NovaObjectRegistry.register
|
|
class LibvirtLiveMigrateData(LiveMigrateData):
|
|
# Version 1.0: Initial version
|
|
# Version 1.1: Added target_connect_addr
|
|
# Version 1.2: Added 'serial_listen_ports' to allow live migration with
|
|
# serial console.
|
|
# Version 1.3: Added 'supported_perf_events'
|
|
# Version 1.4: Added old_vol_attachment_ids
|
|
# Version 1.5: Added src_supports_native_luks
|
|
# Version 1.6: Added wait_for_vif_plugged
|
|
# Version 1.7: Added dst_wants_file_backed_memory
|
|
# Version 1.8: Added file_backed_memory_discard
|
|
# Version 1.9: Inherited vifs from LiveMigrateData
|
|
# Version 1.10: Added dst_numa_info, src_supports_numa_live_migration, and
|
|
# dst_supports_numa_live_migration fields
|
|
# Version 1.11: Added dst_supports_mdev_live_migration,
|
|
# source_mdev_types and target_mdevs fields
|
|
# Version 1.12: Added dst_cpu_shared_set_info
|
|
# Version 1.13: Inherited pci_dev_map_src_dst from LiveMigrateData
|
|
# Version 1.14: Added vtpm_secret_uuid and vtpm_secret_value
|
|
VERSION = '1.14'
|
|
|
|
fields = {
|
|
'filename': fields.StringField(),
|
|
# FIXME: image_type should be enum?
|
|
'image_type': fields.StringField(),
|
|
'block_migration': fields.BooleanField(),
|
|
'disk_over_commit': fields.BooleanField(),
|
|
'disk_available_mb': fields.IntegerField(nullable=True),
|
|
'is_shared_instance_path': fields.BooleanField(),
|
|
'is_shared_block_storage': fields.BooleanField(),
|
|
'instance_relative_path': fields.StringField(),
|
|
'graphics_listen_addr_vnc': fields.IPAddressField(nullable=True),
|
|
'graphics_listen_addr_spice': fields.IPAddressField(nullable=True),
|
|
'serial_listen_addr': fields.StringField(nullable=True),
|
|
'serial_listen_ports': fields.ListOfIntegersField(),
|
|
'bdms': fields.ListOfObjectsField('LibvirtLiveMigrateBDMInfo'),
|
|
'target_connect_addr': fields.StringField(nullable=True),
|
|
'supported_perf_events': fields.ListOfStringsField(),
|
|
# TODO(lyarwood): No longer used, drop in version 2.0
|
|
'src_supports_native_luks': fields.BooleanField(),
|
|
'dst_wants_file_backed_memory': fields.BooleanField(),
|
|
# TODO(lyarwood): No longer used, drop in version 2.0
|
|
'file_backed_memory_discard': fields.BooleanField(),
|
|
# TODO(artom) (src|dst)_supports_numa_live_migration are only used as
|
|
# flags to indicate that the compute host is new enough to perform a
|
|
# NUMA-aware live migration. Remove in version 2.0.
|
|
'src_supports_numa_live_migration': fields.BooleanField(),
|
|
'dst_supports_numa_live_migration': fields.BooleanField(),
|
|
'dst_numa_info': fields.ObjectField('LibvirtLiveMigrateNUMAInfo'),
|
|
# TODO(sbauza) dst_supports_mdev_live_migration is only used as
|
|
# flag to indicate that the compute host is new enough to perform a
|
|
# mediated-device-aware live migration. Remove in version 2.0.
|
|
'dst_supports_mdev_live_migration': fields.BooleanField(),
|
|
# key is mdev UUID and value is its type.
|
|
'source_mdev_types': fields.DictOfStringsField(),
|
|
# key is source mdev UUID and value is the destination mdev UUID.
|
|
'target_mdevs': fields.DictOfStringsField(),
|
|
'dst_cpu_shared_set_info': fields.SetOfIntegersField(),
|
|
'vtpm_secret_uuid': fields.UUIDField(nullable=True),
|
|
'vtpm_secret_value': fields.SensitiveStringField(nullable=True),
|
|
}
|
|
|
|
def obj_make_compatible(self, primitive, target_version):
|
|
super(LibvirtLiveMigrateData, self).obj_make_compatible(
|
|
primitive, target_version)
|
|
target_version = versionutils.convert_version_to_tuple(target_version)
|
|
if (target_version < (1, 14)):
|
|
# In an object primitive, a field will be included if and only if
|
|
# it is set to something (including None, if the field is
|
|
# nullable). So instances with no vTPM will not have these fields
|
|
# set.
|
|
vtpm_fields = ['vtpm_secret_uuid', 'vtpm_secret_value']
|
|
if any(field in vtpm_fields for field in primitive):
|
|
# If either field is set, then we know this object was passed
|
|
# from newer compute which could support vTPM live migration.
|
|
# We raise here instead of silently removing the fields because
|
|
# we cannot backport the object to something compatible.
|
|
raise exception.ObjectActionError(
|
|
action='obj_make_compatible',
|
|
reason='Unable to backport newer vTPM support to '
|
|
'requested version %d.%d' % target_version)
|
|
if (target_version < (1, 13)):
|
|
primitive.pop('pci_dev_map_src_dst', None)
|
|
if (target_version < (1, 12)):
|
|
primitive.pop('dst_cpu_shared_set_info', None)
|
|
if target_version < (1, 11):
|
|
primitive.pop('target_mdevs', None)
|
|
primitive.pop('source_mdev_types', None)
|
|
primitive.pop('dst_supports_mdev_live_migration', None)
|
|
if (target_version < (1, 10) and
|
|
'src_supports_numa_live_migration' in primitive):
|
|
del primitive['src_supports_numa_live_migration']
|
|
if (target_version < (1, 10) and
|
|
'dst_supports_numa_live_migration' in primitive):
|
|
del primitive['dst_supports_numa_live_migration']
|
|
if target_version < (1, 10) and 'dst_numa_info' in primitive:
|
|
del primitive['dst_numa_info']
|
|
if target_version < (1, 9) and 'vifs' in primitive:
|
|
del primitive['vifs']
|
|
if target_version < (1, 8):
|
|
if 'file_backed_memory_discard' in primitive:
|
|
del primitive['file_backed_memory_discard']
|
|
if target_version < (1, 7):
|
|
if 'dst_wants_file_backed_memory' in primitive:
|
|
del primitive['dst_wants_file_backed_memory']
|
|
if target_version < (1, 6) and 'wait_for_vif_plugged' in primitive:
|
|
del primitive['wait_for_vif_plugged']
|
|
if target_version < (1, 5):
|
|
if 'src_supports_native_luks' in primitive:
|
|
del primitive['src_supports_native_luks']
|
|
if target_version < (1, 4):
|
|
if 'old_vol_attachment_ids' in primitive:
|
|
del primitive['old_vol_attachment_ids']
|
|
if target_version < (1, 3):
|
|
if 'supported_perf_events' in primitive:
|
|
del primitive['supported_perf_events']
|
|
if target_version < (1, 2):
|
|
if 'serial_listen_ports' in primitive:
|
|
del primitive['serial_listen_ports']
|
|
if target_version < (1, 1) and 'target_connect_addr' in primitive:
|
|
del primitive['target_connect_addr']
|
|
|
|
def is_on_shared_storage(self):
|
|
return self.is_shared_block_storage or self.is_shared_instance_path
|
|
|
|
|
|
# TODO(gmann): HyperV virt driver has been removed in Nova 29.0.0 (OpenStack
|
|
# 2024.1) release but we kept this object for a couple of cycle. This can be
|
|
# removed too in Nova 31.0.0 (OpenStack 2025.1) or later.
|
|
@obj_base.NovaObjectRegistry.register
|
|
class HyperVLiveMigrateData(LiveMigrateData):
|
|
# Version 1.0: Initial version
|
|
# Version 1.1: Added is_shared_instance_path
|
|
# Version 1.2: Added old_vol_attachment_ids
|
|
# Version 1.3: Added wait_for_vif_plugged
|
|
# Version 1.4: Inherited vifs from LiveMigrateData
|
|
# Version 1.5: Inherited pci_dev_map_src_dst from LiveMigrateData
|
|
VERSION = '1.5'
|
|
|
|
fields = {'is_shared_instance_path': fields.BooleanField()}
|
|
|
|
def obj_make_compatible(self, primitive, target_version):
|
|
super(HyperVLiveMigrateData, self).obj_make_compatible(
|
|
primitive, target_version)
|
|
target_version = versionutils.convert_version_to_tuple(target_version)
|
|
if (target_version < (1, 5)):
|
|
primitive.pop('pci_dev_map_src_dst', None)
|
|
if target_version < (1, 4) and 'vifs' in primitive:
|
|
del primitive['vifs']
|
|
if target_version < (1, 3) and 'wait_for_vif_plugged' in primitive:
|
|
del primitive['wait_for_vif_plugged']
|
|
if target_version < (1, 2):
|
|
if 'old_vol_attachment_ids' in primitive:
|
|
del primitive['old_vol_attachment_ids']
|
|
if target_version < (1, 1):
|
|
if 'is_shared_instance_path' in primitive:
|
|
del primitive['is_shared_instance_path']
|
|
|
|
|
|
@obj_base.NovaObjectRegistry.register
|
|
class VMwareLiveMigrateData(LiveMigrateData):
|
|
# Version 1.0: Initial version
|
|
# Version 1.1: Inherited pci_dev_map_src_dst from LiveMigrateData
|
|
VERSION = '1.1'
|
|
|
|
fields = {
|
|
'cluster_name': fields.StringField(nullable=False),
|
|
'datastore_regex': fields.StringField(nullable=False),
|
|
}
|
|
|
|
def obj_make_compatible(self, primitive, target_version):
|
|
super(VMwareLiveMigrateData, self).obj_make_compatible(
|
|
primitive, target_version)
|
|
target_version = versionutils.convert_version_to_tuple(target_version)
|
|
if (target_version < (1, 1)):
|
|
primitive.pop('pci_dev_map_src_dst', None)
|