nova-manage: Add libvirt update_machine_type command

This change adds a second update command to the libvirt group
within nova-manage. This command will set or update the machine type of
the instance when the following criteria are met:

* The instance must have a ``vm_state`` of ``STOPPED``, ``SHELVED`` or
  ``SHELVED_OFFLOADED``.

* The machine type is supported. The supported list includes alias and
  versioned types of ``pc``, ``pc-i440fx``, ``pc-q35``, ``q35``, ``virt``
  or ``s390-ccw-virtio``.

* The update will not move the instance between underlying machine types.
  For example, ``pc`` to ``q35``.

* The update will not move the instance between an alias and versioned
  machine type or vice versa. For example, ``pc`` to ``pc-1.2.3`` or
  ``pc-1.2.3`` to ``pc``.

A --force flag is provided to skip the above checks but caution
should be taken as this could easily lead to the underlying ABI of the
instance changing when moving between machine types.

blueprint: libvirt-default-machine-type
Change-Id: I6b80021a2f90d3379c821dc8f02a72f350169eb3
This commit is contained in:
Lee Yarwood 2021-01-05 15:20:44 +00:00
parent 20692c245c
commit c70cde057d
9 changed files with 740 additions and 0 deletions

View File

@ -731,6 +731,50 @@ libvirt
* - 3
- No machine type found for instance
``nova-manage libvirt update_machine_type [instance-uuid] [machine_type] [--force]``
Set or update the recorded machine type of an instance.
The following criteria must also be met when using this command:
* The instance must have a ``vm_state`` of ``STOPPED``, ``SHELVED`` or
``SHELVED_OFFLOADED``.
* The machine type is supported. The supported list includes alias and
versioned types of ``pc``, ``pc-i440fx``, ``pc-q35``, ``q35``, ``virt``
or ``s390-ccw-virtio``.
* The update will not move the instance between underlying machine types.
For example, ``pc`` to ``q35``.
* The update will not move the instance between an alias and versioned
machine type or vice versa. For example, ``pc`` to ``pc-1.2.3`` or
``pc-1.2.3`` to ``pc``.
A ``--force`` flag is provided to skip the above checks but caution
should be taken as this could easily lead to the underlying ABI of the
instance changing when moving between machine types.
**Return Codes**
.. list-table::
:widths: 20 80
:header-rows: 1
* - Return code
- Description
* - 0
- Update completed successfully
* - 1
- An unexpected error occurred
* - 2
- Unable to find instance or instance mapping
* - 3
- The instance has an invalid vm_state
* - 4
- The proposed update of the machine type is invalid
* - 5
- The provided machine type is unsupported
See Also
========

View File

@ -2657,6 +2657,67 @@ class LibvirtCommands(object):
LOG.exception('Unexpected error')
return 1
@action_description(
_("Set or update the stored machine type of the instance in the "
"database. This is only allowed for instances with a STOPPED, "
"SHELVED or SHELVED_OFFLOADED vm_state."))
@args('instance_uuid', metavar='<instance_uuid>',
help='UUID of instance to update')
@args('machine_type', metavar='<machine_type>',
help='Machine type to set')
@args('--force', action='store_true', default=False, dest='force',
help='Force the update of the stored machine type')
def update_machine_type(
self,
instance_uuid=None,
machine_type=None,
force=False
):
"""Set or update the machine type of a given instance.
Return codes:
* 0: Command completed successfully.
* 1: An unexpected error happened.
* 2: Unable to find the instance or instance cell mapping.
* 3: Invalid instance vm_state.
* 4: Unable to move between underlying machine types (pc to q35 etc)
or to older versions.
* 5: Unsupported machine type.
"""
ctxt = context.get_admin_context()
if force:
print(_("Forcing update of machine type."))
try:
rtype, ptype = machine_type_utils.update_machine_type(
ctxt, instance_uuid, machine_type, force=force)
except exception.UnsupportedMachineType as e:
print(str(e))
return 5
except exception.InvalidMachineTypeUpdate as e:
print(str(e))
return 4
except exception.InstanceInvalidState as e:
print(str(e))
return 3
except (
exception.InstanceNotFound,
exception.InstanceMappingNotFound,
) as e:
print(str(e))
return 2
except Exception:
LOG.exception('Unexpected error')
return 1
print(_("Updated instance %(instance_uuid)s machine type to "
"%(machine_type)s (previously %(previous_type)s)") %
{'instance_uuid': instance_uuid,
'machine_type': rtype,
'previous_type': ptype})
return 0
CATEGORIES = {
'api_db': ApiDbCommands,

View File

@ -1751,6 +1751,15 @@ class InvalidMachineType(Invalid):
"%(image_name)s (%(image_id)s): %(reason)s")
class InvalidMachineTypeUpdate(Invalid):
msg_fmt = _("Cannot update machine type %(existing_machine_type)s to "
"%(machine_type)s.")
class UnsupportedMachineType(Invalid):
msg_fmt = _("Machine type %(machine_type)s is not supported.")
class InvalidVirtualMachineMode(Invalid):
msg_fmt = _("Virtual machine mode '%(vmmode)s' is not recognised")

View File

@ -526,6 +526,14 @@ class InstanceHelperMixin:
self._wait_for_migration_status(server, [expected_migration_status])
return self._wait_for_server_parameter(server, expected_result)
def _start_server(self, server):
self.api.post_server_action(server['id'], {'os-start': None})
return self._wait_for_state_change(server, 'ACTIVE')
def _stop_server(self, server):
self.api.post_server_action(server['id'], {'os-stop': None})
return self._wait_for_state_change(server, 'SHUTOFF')
class PlacementHelperMixin:
"""A helper mixin for interacting with placement."""

View File

@ -16,6 +16,7 @@ import fixtures
from oslo_utils.fixture import uuidsentinel
from nova import context as nova_context
from nova import exception
from nova import objects
from nova.tests.functional.libvirt import base
from nova.virt.libvirt import machine_type_utils
@ -199,3 +200,96 @@ class LibvirtMachineTypeTest(base.ServersTestBase):
self.context, server_without['id']
)
)
def test_machine_type_update_stopped(self):
self.flags(hw_machine_type='x86_64=pc-1.2.3', group='libvirt')
server = self._create_server(networks='none')
self._assert_machine_type(server['id'], 'pc-1.2.3')
self._stop_server(server)
machine_type_utils.update_machine_type(
self.context,
server['id'],
'pc-1.2.4'
)
self._start_server(server)
self._assert_machine_type(server['id'], 'pc-1.2.4')
def test_machine_type_update_blocked_active(self):
self.flags(hw_machine_type='x86_64=pc-1.2.3', group='libvirt')
server = self._create_server(networks='none')
self._assert_machine_type(server['id'], 'pc-1.2.3')
self.assertRaises(
exception.InstanceInvalidState,
machine_type_utils.update_machine_type,
self.context,
server['id'],
'pc-1.2.4'
)
def test_machine_type_update_blocked_between_alias_and_versioned(self):
self.flags(hw_machine_type='x86_64=pc', group='libvirt')
server = self._create_server(networks='none')
self._assert_machine_type(server['id'], 'pc')
self._stop_server(server)
self.assertRaises(
exception.InvalidMachineTypeUpdate,
machine_type_utils.update_machine_type,
self.context,
server['id'],
'pc-1.2.4'
)
def test_machine_type_update_blocked_between_versioned_and_alias(self):
self.flags(hw_machine_type='x86_64=pc-1.2.3', group='libvirt')
server = self._create_server(networks='none')
self._assert_machine_type(server['id'], 'pc-1.2.3')
self._stop_server(server)
self.assertRaises(
exception.InvalidMachineTypeUpdate,
machine_type_utils.update_machine_type,
self.context,
server['id'],
'pc'
)
def test_machine_type_update_blocked_between_types(self):
self.flags(hw_machine_type='x86_64=pc', group='libvirt')
server = self._create_server(networks='none')
self._assert_machine_type(server['id'], 'pc')
self._stop_server(server)
self.assertRaises(
exception.InvalidMachineTypeUpdate,
machine_type_utils.update_machine_type,
self.context,
server['id'],
'q35'
)
def test_machine_type_update_force(self):
self.flags(hw_machine_type='x86_64=pc', group='libvirt')
server = self._create_server(networks='none')
self._assert_machine_type(server['id'], 'pc')
# Force through the update on an ACTIVE instance
machine_type_utils.update_machine_type(
self.context,
server['id'],
'q35',
force=True
)
# Reboot the server so the config is updated so we can assert
self._reboot_server(server, hard=True)
self._assert_machine_type(server['id'], 'q35')

View File

@ -3096,3 +3096,148 @@ class LibvirtCommandsTestCase(test.NoDBTestCase):
self.assertEqual(3, ret)
self.assertIn("No machine type registered for instance "
f"{uuidsentinel.instance}", output)
@mock.patch('nova.virt.libvirt.machine_type_utils.update_machine_type')
@mock.patch('nova.context.get_admin_context')
def test_update(self, mock_get_context, mock_update):
mock_update.return_value = ('pc-1.2', 'pc-1.1')
ret = self.commands.update_machine_type(
instance_uuid=uuidsentinel.instance,
machine_type='pc-1.2'
)
mock_update.assert_called_once_with(
mock_get_context.return_value,
uuidsentinel.instance,
'pc-1.2',
force=False
)
output = self.output.getvalue()
self.assertEqual(0, ret)
self.assertIn(
f"Updated instance {uuidsentinel.instance} machine type to pc-1.2 "
"(previously pc-1.1)",
output
)
@mock.patch('nova.virt.libvirt.machine_type_utils.update_machine_type')
@mock.patch('nova.context.get_admin_context')
def test_update_force(self, mock_get_context, mock_update):
mock_update.return_value = ('q35', 'pc')
ret = self.commands.update_machine_type(
instance_uuid=uuidsentinel.instance,
machine_type='q35',
force=True
)
mock_update.assert_called_once_with(
mock_get_context.return_value,
uuidsentinel.instance,
'q35',
force=True
)
output = self.output.getvalue()
self.assertEqual(0, ret)
self.assertIn(
f"Updated instance {uuidsentinel.instance} machine type to q35 "
"(previously pc)",
output
)
@mock.patch('nova.virt.libvirt.machine_type_utils.update_machine_type')
@mock.patch('nova.context.get_admin_context', new=mock.Mock())
def test_update_unknown_failure(self, mock_update):
mock_update.side_effect = Exception()
ret = self.commands.update_machine_type(
instance_uuid=uuidsentinel.instance,
machine_type=mock.sentinel.machine_type
)
self.assertEqual(1, ret)
@mock.patch('nova.virt.libvirt.machine_type_utils.update_machine_type')
@mock.patch('nova.context.get_admin_context', new=mock.Mock())
def test_update_instance_mapping_not_found(self, mock_update):
mock_update.side_effect = exception.InstanceMappingNotFound(
uuid=uuidsentinel.instance
)
ret = self.commands.update_machine_type(
instance_uuid=uuidsentinel.instance,
machine_type=mock.sentinel.machine_type
)
output = self.output.getvalue()
self.assertEqual(2, ret)
self.assertIn(
f"Instance {uuidsentinel.instance} has no mapping to a cell.",
output
)
@mock.patch('nova.virt.libvirt.machine_type_utils.update_machine_type')
@mock.patch('nova.context.get_admin_context', new=mock.Mock())
def test_update_instance_not_found(self, mock_update):
mock_update.side_effect = exception.InstanceNotFound(
instance_id=uuidsentinel.instance
)
ret = self.commands.update_machine_type(
instance_uuid=uuidsentinel.instance,
machine_type=mock.sentinel.machine_type
)
output = self.output.getvalue()
self.assertEqual(2, ret)
self.assertIn(
f"Instance {uuidsentinel.instance} could not be found.",
output
)
@mock.patch('nova.virt.libvirt.machine_type_utils.update_machine_type')
@mock.patch('nova.context.get_admin_context', new=mock.Mock())
def test_update_instance_invalid_state(self, mock_update):
mock_update.side_effect = exception.InstanceInvalidState(
instance_uuid=uuidsentinel.instance,
attr='vm_state',
state='ACTIVE',
method='update machine type'
)
ret = self.commands.update_machine_type(
instance_uuid=uuidsentinel.instance,
machine_type=mock.sentinel.machine_type
)
output = self.output.getvalue()
self.assertEqual(3, ret)
self.assertIn(
f"Instance {uuidsentinel.instance} in vm_state ACTIVE. Cannot "
"update machine type while the instance is in this state.",
output
)
@mock.patch('nova.virt.libvirt.machine_type_utils.update_machine_type')
@mock.patch('nova.context.get_admin_context', new=mock.Mock())
def test_update_invalid_machine_type_update(self, mock_update):
mock_update.side_effect = exception.InvalidMachineTypeUpdate(
existing_machine_type='q35',
machine_type='pc',
)
ret = self.commands.update_machine_type(
instance_uuid=uuidsentinel.instance,
machine_type=mock.sentinel.machine_type
)
output = self.output.getvalue()
self.assertEqual(4, ret)
self.assertIn(
"Cannot update machine type q35 to pc.",
output
)
@mock.patch('nova.virt.libvirt.machine_type_utils.update_machine_type')
@mock.patch('nova.context.get_admin_context', new=mock.Mock())
def test_update_unsupported_machine_type(self, mock_update):
mock_update.side_effect = exception.UnsupportedMachineType(
machine_type='foo'
)
ret = self.commands.update_machine_type(
instance_uuid=uuidsentinel.instance,
machine_type=mock.sentinel.machine_type
)
output = self.output.getvalue()
self.assertEqual(5, ret)
self.assertIn(
"Machine type foo is not supported.",
output
)

View File

@ -10,15 +10,18 @@
# License for the specific language governing permissions and limitations
# under the License.
import ddt
import mock
from oslo_utils.fixture import uuidsentinel
from nova.compute import vm_states
from nova import exception
from nova import objects
from nova import test
from nova.virt.libvirt import machine_type_utils
@ddt.ddt
class TestMachineTypeUtils(test.NoDBTestCase):
def _create_test_instance_obj(
@ -82,3 +85,213 @@ class TestMachineTypeUtils(test.NoDBTestCase):
uuidsentinel.instance,
expected_attrs=['system_metadata']
)
@ddt.data(
'pc',
'q35',
'virt',
's390-ccw-virtio',
'pc-i440fx-2.12',
'pc-q35-2.12',
'virt-2.12',
'pc-i440fx-rhel8.2.0',
'pc-q35-rhel8.2.0')
def test_check_machine_type_support(self, machine_type):
# Assert UnsupportedMachineType isn't raised for supported types
machine_type_utils._check_machine_type_support(
machine_type)
@ddt.data(
'pc-foo',
'pc-foo-1.2',
'bar-q35',
'virt-foo',
'pc-virt')
def test_check_machine_type_support_failure(self, machine_type):
# Assert UnsupportedMachineType is raised for unsupported types
self.assertRaises(
exception.UnsupportedMachineType,
machine_type_utils._check_machine_type_support,
machine_type
)
@ddt.data(
('pc-i440fx-2.10', 'pc-i440fx-2.11'),
('pc-q35-2.10', 'pc-q35-2.11'))
def test_check_update_to_existing_type(self, machine_types):
# Assert that exception.InvalidMachineTypeUpdate is not raised when
# updating to the same type or between versions of the same type
original_type, update_type = machine_types
machine_type_utils._check_update_to_existing_type(
original_type, update_type)
@ddt.data(
('pc', 'q35'),
('q35', 'pc'),
('pc-i440fx-2.12', 'pc-q35-2.12'),
('pc', 'pc-i440fx-2.12'),
('pc-i440fx-2.12', 'pc'),
('pc-i440fx-2.12', 'pc-i440fx-2.11'))
def test_check_update_to_existing_type_failure(self, machine_types):
# Assert that exception.InvalidMachineTypeUpdate is raised when
# updating to a different underlying machine type or between versioned
# and aliased machine types
existing_type, update_type = machine_types
self.assertRaises(
exception.InvalidMachineTypeUpdate,
machine_type_utils._check_update_to_existing_type,
existing_type, update_type
)
@ddt.data(
vm_states.STOPPED,
vm_states.SHELVED,
vm_states.SHELVED_OFFLOADED)
def test_check_vm_state(self, vm_state):
instance = self._create_test_instance_obj(
vm_state=vm_state
)
machine_type_utils._check_vm_state(instance)
@ddt.data(
vm_states.ACTIVE,
vm_states.PAUSED,
vm_states.ERROR)
def test_check_vm_state_failure(self, vm_state):
instance = self._create_test_instance_obj(
vm_state=vm_state
)
self.assertRaises(
exception.InstanceInvalidState,
machine_type_utils._check_vm_state,
instance
)
@mock.patch('nova.objects.instance.Instance.save')
@mock.patch('nova.virt.libvirt.machine_type_utils._check_vm_state')
@mock.patch('nova.objects.Instance.get_by_uuid')
@mock.patch('nova.context.target_cell')
@mock.patch('nova.objects.InstanceMapping.get_by_instance_uuid',
new=mock.Mock(cell_mapping=mock.sentinel.cell_mapping))
def test_update_noop(
self,
mock_target_cell,
mock_get_instance,
mock_check_vm_state,
mock_instance_save
):
# Assert that update_machine_type is a noop when the type is already
# set within the instance, even if forced
existing_type = 'pc'
mock_target_cell.return_value.__enter__.return_value = (
mock.sentinel.cell_context)
mock_get_instance.return_value = self._create_test_instance_obj(
mtype=existing_type,
)
self.assertEqual(
(existing_type, existing_type),
machine_type_utils.update_machine_type(
mock.sentinel.context,
instance_uuid=uuidsentinel.instance,
machine_type=existing_type
),
)
mock_check_vm_state.assert_not_called()
mock_instance_save.assert_not_called()
self.assertEqual(
(existing_type, existing_type),
machine_type_utils.update_machine_type(
mock.sentinel.context,
instance_uuid=uuidsentinel.instance,
machine_type=existing_type,
force=True
),
)
mock_check_vm_state.assert_not_called()
mock_instance_save.assert_not_called()
@ddt.data(
('foobar', 'foobar', None),
('foobar-1.3', 'foobar-1.3', 'foobar-1.2'),
('foobar-1.2', 'foobar-1.2', 'foobar-1.3'),
('foobar', 'foobar', 'q35'),
('pc', 'pc', 'q35'))
@mock.patch('nova.objects.instance.Instance.save')
@mock.patch('nova.objects.Instance.get_by_uuid')
@mock.patch('nova.context.target_cell')
@mock.patch('nova.objects.InstanceMapping.get_by_instance_uuid',
new=mock.Mock(cell_mapping=mock.sentinel.cell_mapping))
def test_update_force(
self,
types,
mock_target_cell,
mock_get_instance,
mock_instance_save
):
expected_type, update_type, existing_type = types
mock_target_cell.return_value.__enter__.return_value = (
mock.sentinel.cell_context)
instance = self._create_test_instance_obj(
mtype=existing_type
)
mock_get_instance.return_value = instance
returned_type = machine_type_utils.update_machine_type(
mock.sentinel.context,
uuidsentinel.instance,
machine_type=update_type,
force=True
)
# Assert that the instance machine type was updated and saved
self.assertEqual((expected_type, existing_type), returned_type)
self.assertEqual(
expected_type,
instance.system_metadata.get('image_hw_machine_type')
)
mock_instance_save.assert_called_once()
@ddt.data(
('pc', 'pc', None),
('q35', 'q35', None),
('pc-1.2', 'pc-1.2', None),
('pc-q35-1.2', 'pc-q35-1.2', None),
('pc-1.2', 'pc-1.2', 'pc-1.1'),
('pc-i440fx-1.2', 'pc-i440fx-1.2', 'pc-i440fx-1.1'),
('pc-q35-1.2', 'pc-q35-1.2', 'pc-q35-1.1'),
('pc-q35-rhel8.2.0', 'pc-q35-rhel8.2.0', 'pc-q35-rhel8.1.0'))
@mock.patch('nova.objects.instance.Instance.save')
@mock.patch('nova.objects.Instance.get_by_uuid')
@mock.patch('nova.context.target_cell')
@mock.patch('nova.objects.InstanceMapping.get_by_instance_uuid',
new=mock.Mock(cell_mapping=mock.sentinel.cell_mapping))
def test_update(
self,
types,
mock_target_cell,
mock_get_instance,
mock_instance_save
):
expected_type, update_type, existing_type = types
mock_target_cell.return_value.__enter__.return_value = (
mock.sentinel.cell_context)
instance = self._create_test_instance_obj(
mtype=existing_type
)
mock_get_instance.return_value = instance
returned_type = machine_type_utils.update_machine_type(
mock.sentinel.context,
uuidsentinel.instance,
machine_type=update_type
)
# Assert that the instance machine type was updated and saved
self.assertEqual((expected_type, existing_type), returned_type)
self.assertEqual(
expected_type,
instance.system_metadata.get('image_hw_machine_type')
)
mock_instance_save.assert_called_once()

View File

@ -10,10 +10,39 @@
# License for the specific language governing permissions and limitations
# under the License.
import re
import typing as ty
from nova.compute import vm_states
from nova import context as nova_context
from nova import exception
from nova import objects
from oslo_utils import versionutils
SUPPORTED_TYPE_PATTERNS = [
# As defined by nova.virt.libvirt.utils.get_default_machine_type
r'^pc$',
r'^q35$',
r'^virt$',
r'^s390-ccw-virtio$',
# versioned types of the above
r'^pc-\d+.\d+',
r'^pc-i440fx-\d+.\d+',
r'^pc-q35-\d+.\d+',
r'^virt-\d+.\d+',
r'^s390-ccw-virtio-\d+.\d+',
# RHEL specific versions of the pc-i440fx and pc-q35 types
r'^pc-i440fx-rhel\d.\d+.\d+',
r'^pc-q35-rhel\d.\d+.\d+',
]
ALLOWED_UPDATE_VM_STATES = [
vm_states.STOPPED,
vm_states.SHELVED,
vm_states.SHELVED_OFFLOADED
]
def get_machine_type(
@ -36,3 +65,116 @@ def get_machine_type(
instance = objects.instance.Instance.get_by_uuid(
cctxt, instance_uuid, expected_attrs=['system_metadata'])
return instance.image_meta.properties.get('hw_machine_type')
def _check_machine_type_support(
mtype: str
) -> None:
"""Check that the provided machine type is supported
This check is done without access to the compute host and
so instead relies on a hardcoded list of supported machine types to
validate the provided machine type.
:param machine_type: Machine type to check
:raises: nova.exception.UnsupportedMachineType
"""
if not any(m for m in SUPPORTED_TYPE_PATTERNS if re.match(m, mtype)):
raise exception.UnsupportedMachineType(machine_type=mtype)
def _check_update_to_existing_type(
existing_type: str,
machine_type: str
) -> None:
"""Check the update to an existing machine type
The aim here is to block operators from moving between the underying
machine types, between versioned and aliased types or to an older version
of the same type during an update.
:param existing_type: The existing machine type
:param machine_type: The new machine type
:raises: nova.exception.InvalidMachineTypeUpdate
"""
# Check that we are not switching between types or between an alias and
# versioned type such as q35 to pc-q35-5.2.0 etc.
for m in SUPPORTED_TYPE_PATTERNS:
if re.match(m, existing_type) and not re.match(m, machine_type):
raise exception.InvalidMachineTypeUpdate(
existing_machine_type=existing_type, machine_type=machine_type)
# Now check that the new version isn't older than the original.
# This needs to support x.y and x.y.z as used by RHEL shipped QEMU
version_pattern = r'\d+\.\d+$|\d+\.\d+\.\d+$'
if any(re.findall(version_pattern, existing_type)):
existing_version = re.findall(version_pattern, existing_type)[0]
new_version = re.findall(version_pattern, machine_type)[0]
if (versionutils.convert_version_to_int(new_version) <
versionutils.convert_version_to_int(existing_version)):
raise exception.InvalidMachineTypeUpdate(
existing_machine_type=existing_type,
machine_type=machine_type)
def _check_vm_state(
instance: 'objects.Instance',
):
"""Ensure the vm_state of the instance is in ALLOWED_UPDATE_VM_STATES
:param instance: Instance object to check
:raises: nova.exception.InstanceInvalidState
"""
if instance.vm_state not in ALLOWED_UPDATE_VM_STATES:
raise exception.InstanceInvalidState(
instance_uuid=instance.uuid, attr='vm_state',
state=instance.vm_state, method='update machine type.')
def update_machine_type(
context: nova_context.RequestContext,
instance_uuid: str,
machine_type: str,
force: bool = False,
) -> ty.Tuple[str, str]:
"""Set or update the stored machine type of an instance
:param instance_uuid: Instance UUID to update.
:param machine_type: Machine type to update.
:param force: If the update should be forced.
:returns: A tuple of the updated machine type and original machine type.
"""
im = objects.InstanceMapping.get_by_instance_uuid(context, instance_uuid)
with nova_context.target_cell(context, im.cell_mapping) as cctxt:
instance = objects.instance.Instance.get_by_uuid(
cctxt, instance_uuid, expected_attrs=['system_metadata'])
# Fetch the existing system metadata machine type if one is recorded
existing_mtype = instance.image_meta.properties.get('hw_machine_type')
# Return if the type is already updated
if existing_mtype and existing_mtype == machine_type:
return machine_type, existing_mtype
# If the caller wants to force the update now is the time to do it.
if force:
instance.system_metadata['image_hw_machine_type'] = machine_type
instance.save()
return machine_type, existing_mtype
# Ensure the instance is in a suitable vm_state to update
_check_vm_state(instance)
# Ensure the supplied machine type is supported
_check_machine_type_support(machine_type)
# If the instance already has a type ensure the update is valid
if existing_mtype:
_check_update_to_existing_type(existing_mtype, machine_type)
# Finally save the machine type in the instance system metadata
instance.system_metadata['image_hw_machine_type'] = machine_type
instance.save()
return machine_type, existing_mtype

View File

@ -16,3 +16,27 @@ upgrade:
This command will print the current machine type if set in the image
metadata of the instance.
``nova-manage libvirt set_machine_type``
This command will set or update the machine type of the instance assuming
the following criteria are met:
* The instance must have a ``vm_state`` of ``STOPPED``, ``SHELVED`` or
``SHELVED_OFFLOADED``.
* The machine type is supported. The supported list includes alias and
versioned types of ``pc``, ``pc-i440fx``, ``pc-q35``, ``q35``, ``virt``,
``s390-ccw-virtio``, ``hyperv-gen1`` and ``hyperv-gen2`` as supported by
the hyperv driver.
* The update will not move the instance between underlying machine types.
For example, ``pc`` to ``q35``.
* The update will not move the instance between an alias and versioned
machine type or vice versa. For example, ``pc`` to ``pc-1.2.3`` or
``pc-1.2.3`` to ``pc``.
A ``--force`` flag is provided to skip the above checks but caution
should be taken as this could easily lead to the underlying ABI of the
instance changing when moving between machine types.