nova-manage: Add libvirt get_machine_type command
This change introduces the first machine_type command to nova-manage to fetch and display the current machine type if set in the system metadata of the instance. blueprint: libvirt-default-machine-type Change-Id: Idc035671892e4668141a93763f8f2bed7a630812
This commit is contained in:
parent
5b66caab87
commit
20692c245c
|
@ -708,6 +708,28 @@ Placement
|
||||||
* - 127
|
* - 127
|
||||||
- Invalid input
|
- Invalid input
|
||||||
|
|
||||||
|
libvirt
|
||||||
|
~~~~~~~
|
||||||
|
|
||||||
|
``nova-manage libvirt get_machine_type [instance-uuid]``
|
||||||
|
Fetch and display the recorded machine type of a libvirt instance.
|
||||||
|
|
||||||
|
**Return Codes**
|
||||||
|
|
||||||
|
.. list-table::
|
||||||
|
:widths: 20 80
|
||||||
|
:header-rows: 1
|
||||||
|
|
||||||
|
* - Return code
|
||||||
|
- Description
|
||||||
|
* - 0
|
||||||
|
- Successfully completed
|
||||||
|
* - 1
|
||||||
|
- An unexpected error occurred
|
||||||
|
* - 2
|
||||||
|
- Unable to find instance or instance mapping
|
||||||
|
* - 3
|
||||||
|
- No machine type found for instance
|
||||||
|
|
||||||
See Also
|
See Also
|
||||||
========
|
========
|
||||||
|
|
|
@ -5,6 +5,7 @@ nova/scheduler/request_filter.py
|
||||||
nova/scheduler/utils.py
|
nova/scheduler/utils.py
|
||||||
nova/virt/driver.py
|
nova/virt/driver.py
|
||||||
nova/virt/hardware.py
|
nova/virt/hardware.py
|
||||||
|
nova/virt/libvirt/machine_type_utils.py
|
||||||
nova/virt/libvirt/__init__.py
|
nova/virt/libvirt/__init__.py
|
||||||
nova/virt/libvirt/driver.py
|
nova/virt/libvirt/driver.py
|
||||||
nova/virt/libvirt/event.py
|
nova/virt/libvirt/event.py
|
||||||
|
|
|
@ -67,6 +67,7 @@ from nova import rpc
|
||||||
from nova.scheduler.client import report
|
from nova.scheduler.client import report
|
||||||
from nova.scheduler import utils as scheduler_utils
|
from nova.scheduler import utils as scheduler_utils
|
||||||
from nova import version
|
from nova import version
|
||||||
|
from nova.virt.libvirt import machine_type_utils
|
||||||
|
|
||||||
CONF = nova.conf.CONF
|
CONF = nova.conf.CONF
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
@ -2620,11 +2621,49 @@ class PlacementCommands(object):
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
class LibvirtCommands(object):
|
||||||
|
"""Commands for managing libvirt instances"""
|
||||||
|
|
||||||
|
@action_description(
|
||||||
|
_("Fetch the stored machine type of the instance from the database."))
|
||||||
|
@args('instance_uuid', metavar='<instance_uuid>',
|
||||||
|
help='UUID of instance to fetch the machine type for')
|
||||||
|
def get_machine_type(self, instance_uuid=None):
|
||||||
|
"""Fetch the stored machine type of the instance from the database.
|
||||||
|
|
||||||
|
Return codes:
|
||||||
|
|
||||||
|
* 0: Command completed successfully.
|
||||||
|
* 1: An unexpected error happened.
|
||||||
|
* 2: Unable to find instance or instance mapping.
|
||||||
|
* 3: No machine type found for the instance.
|
||||||
|
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
ctxt = context.get_admin_context()
|
||||||
|
mtype = machine_type_utils.get_machine_type(ctxt, instance_uuid)
|
||||||
|
if mtype:
|
||||||
|
print(mtype)
|
||||||
|
return 0
|
||||||
|
else:
|
||||||
|
print(_('No machine type registered for instance %s' %
|
||||||
|
instance_uuid))
|
||||||
|
return 3
|
||||||
|
except (exception.InstanceNotFound,
|
||||||
|
exception.InstanceMappingNotFound) as e:
|
||||||
|
print(str(e))
|
||||||
|
return 2
|
||||||
|
except Exception:
|
||||||
|
LOG.exception('Unexpected error')
|
||||||
|
return 1
|
||||||
|
|
||||||
|
|
||||||
CATEGORIES = {
|
CATEGORIES = {
|
||||||
'api_db': ApiDbCommands,
|
'api_db': ApiDbCommands,
|
||||||
'cell_v2': CellV2Commands,
|
'cell_v2': CellV2Commands,
|
||||||
'db': DbCommands,
|
'db': DbCommands,
|
||||||
'placement': PlacementCommands
|
'placement': PlacementCommands,
|
||||||
|
'libvirt': LibvirtCommands,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ from oslo_utils.fixture import uuidsentinel
|
||||||
from nova import context as nova_context
|
from nova import context as nova_context
|
||||||
from nova import objects
|
from nova import objects
|
||||||
from nova.tests.functional.libvirt import base
|
from nova.tests.functional.libvirt import base
|
||||||
|
from nova.virt.libvirt import machine_type_utils
|
||||||
|
|
||||||
|
|
||||||
class LibvirtMachineTypeTest(base.ServersTestBase):
|
class LibvirtMachineTypeTest(base.ServersTestBase):
|
||||||
|
@ -78,6 +79,14 @@ class LibvirtMachineTypeTest(base.ServersTestBase):
|
||||||
self.guest_configs[server_id].os_mach_type
|
self.guest_configs[server_id].os_mach_type
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _unset_machine_type(self, server_id):
|
||||||
|
instance = objects.Instance.get_by_uuid(
|
||||||
|
self.context,
|
||||||
|
server_id,
|
||||||
|
)
|
||||||
|
instance.system_metadata.pop('image_hw_machine_type')
|
||||||
|
instance.save()
|
||||||
|
|
||||||
def test_init_host_register_machine_type(self):
|
def test_init_host_register_machine_type(self):
|
||||||
"""Assert that the machine type of an instance is recorded during
|
"""Assert that the machine type of an instance is recorded during
|
||||||
init_host if not already captured by an image prop.
|
init_host if not already captured by an image prop.
|
||||||
|
@ -91,12 +100,7 @@ class LibvirtMachineTypeTest(base.ServersTestBase):
|
||||||
# Stop n-cpu and clear the recorded machine type from server_without to
|
# Stop n-cpu and clear the recorded machine type from server_without to
|
||||||
# allow init_host to register the machine type.
|
# allow init_host to register the machine type.
|
||||||
self.computes['compute1'].stop()
|
self.computes['compute1'].stop()
|
||||||
instance_without = objects.Instance.get_by_uuid(
|
self._unset_machine_type(server_without['id'])
|
||||||
self.context,
|
|
||||||
server_without['id'],
|
|
||||||
)
|
|
||||||
instance_without.system_metadata.pop('image_hw_machine_type')
|
|
||||||
instance_without.save()
|
|
||||||
|
|
||||||
self.flags(hw_machine_type='x86_64=pc-q35-1.2.3', group='libvirt')
|
self.flags(hw_machine_type='x86_64=pc-q35-1.2.3', group='libvirt')
|
||||||
|
|
||||||
|
@ -178,3 +182,20 @@ class LibvirtMachineTypeTest(base.ServersTestBase):
|
||||||
|
|
||||||
def test_machine_type_after_server_hard_reboot(self):
|
def test_machine_type_after_server_hard_reboot(self):
|
||||||
self._test_machine_type_after_server_reboot(hard=True)
|
self._test_machine_type_after_server_reboot(hard=True)
|
||||||
|
|
||||||
|
def test_machine_type_get(self):
|
||||||
|
self.flags(hw_machine_type='x86_64=pc', group='libvirt')
|
||||||
|
|
||||||
|
server_with, server_without = self._create_servers()
|
||||||
|
self.assertEqual(
|
||||||
|
'q35',
|
||||||
|
machine_type_utils.get_machine_type(
|
||||||
|
self.context, server_with['id']
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
'pc',
|
||||||
|
machine_type_utils.get_machine_type(
|
||||||
|
self.context, server_without['id']
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
|
@ -3019,3 +3019,80 @@ class TestNovaManageMain(test.NoDBTestCase):
|
||||||
mock_conf.post_mortem = True
|
mock_conf.post_mortem = True
|
||||||
self.assertEqual(255, manage.main())
|
self.assertEqual(255, manage.main())
|
||||||
self.assertTrue(mock_pm.called)
|
self.assertTrue(mock_pm.called)
|
||||||
|
|
||||||
|
|
||||||
|
class LibvirtCommandsTestCase(test.NoDBTestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
self.output = StringIO()
|
||||||
|
self.useFixture(fixtures.MonkeyPatch('sys.stdout', self.output))
|
||||||
|
self.commands = manage.LibvirtCommands()
|
||||||
|
|
||||||
|
@mock.patch('nova.virt.libvirt.machine_type_utils.get_machine_type')
|
||||||
|
@mock.patch('nova.context.get_admin_context')
|
||||||
|
def test_get(self, mock_get_context, mock_get_machine_type):
|
||||||
|
mock_get_context.return_value = mock.sentinel.admin_context
|
||||||
|
mock_get_machine_type.return_value = 'pc'
|
||||||
|
ret = self.commands.get_machine_type(
|
||||||
|
instance_uuid=uuidsentinel.instance
|
||||||
|
)
|
||||||
|
mock_get_machine_type.assert_called_once_with(
|
||||||
|
mock.sentinel.admin_context,
|
||||||
|
uuidsentinel.instance
|
||||||
|
)
|
||||||
|
output = self.output.getvalue()
|
||||||
|
self.assertEqual(0, ret)
|
||||||
|
self.assertIn('pc', output)
|
||||||
|
|
||||||
|
@mock.patch('nova.virt.libvirt.machine_type_utils.get_machine_type')
|
||||||
|
@mock.patch('nova.context.get_admin_context')
|
||||||
|
def test_get_unknown_failure(
|
||||||
|
self, mock_get_context, mock_get_machine_type
|
||||||
|
):
|
||||||
|
mock_get_machine_type.side_effect = Exception()
|
||||||
|
ret = self.commands.get_machine_type(
|
||||||
|
instance_uuid=uuidsentinel.instance
|
||||||
|
)
|
||||||
|
self.assertEqual(1, ret)
|
||||||
|
|
||||||
|
@mock.patch('nova.virt.libvirt.machine_type_utils.get_machine_type')
|
||||||
|
@mock.patch('nova.context.get_admin_context', new=mock.Mock())
|
||||||
|
def test_get_unable_to_find_instance_mapping(self, mock_get_machine_type):
|
||||||
|
mock_get_machine_type.side_effect = exception.InstanceMappingNotFound(
|
||||||
|
uuid=uuidsentinel.instance)
|
||||||
|
ret = self.commands.get_machine_type(
|
||||||
|
instance_uuid=uuidsentinel.instance
|
||||||
|
)
|
||||||
|
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.get_machine_type')
|
||||||
|
@mock.patch('nova.context.get_admin_context', new=mock.Mock())
|
||||||
|
def test_get_machine_type_unable_to_find_instance(
|
||||||
|
self, mock_get_machine_type
|
||||||
|
):
|
||||||
|
mock_get_machine_type.side_effect = exception.InstanceNotFound(
|
||||||
|
instance_id=uuidsentinel.instance)
|
||||||
|
ret = self.commands.get_machine_type(
|
||||||
|
instance_uuid=uuidsentinel.instance)
|
||||||
|
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.get_machine_type',
|
||||||
|
new=mock.Mock(return_value=None))
|
||||||
|
@mock.patch('nova.context.get_admin_context', new=mock.Mock())
|
||||||
|
def test_get_none_found(self):
|
||||||
|
ret = self.commands.get_machine_type(
|
||||||
|
instance_uuid=uuidsentinel.instance
|
||||||
|
)
|
||||||
|
output = self.output.getvalue()
|
||||||
|
self.assertEqual(3, ret)
|
||||||
|
self.assertIn("No machine type registered for instance "
|
||||||
|
f"{uuidsentinel.instance}", output)
|
||||||
|
|
|
@ -0,0 +1,84 @@
|
||||||
|
# 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 mock
|
||||||
|
from oslo_utils.fixture import uuidsentinel
|
||||||
|
|
||||||
|
from nova.compute import vm_states
|
||||||
|
from nova import objects
|
||||||
|
from nova import test
|
||||||
|
from nova.virt.libvirt import machine_type_utils
|
||||||
|
|
||||||
|
|
||||||
|
class TestMachineTypeUtils(test.NoDBTestCase):
|
||||||
|
|
||||||
|
def _create_test_instance_obj(
|
||||||
|
self,
|
||||||
|
vm_state=vm_states.STOPPED,
|
||||||
|
mtype=None
|
||||||
|
):
|
||||||
|
instance = objects.Instance(
|
||||||
|
uuid=uuidsentinel.instance, host='fake', node='fake',
|
||||||
|
task_state=None, flavor=objects.Flavor(),
|
||||||
|
project_id='fake-project', user_id='fake-user',
|
||||||
|
vm_state=vm_state, system_metadata={}
|
||||||
|
)
|
||||||
|
if mtype:
|
||||||
|
instance.system_metadata = {
|
||||||
|
'image_hw_machine_type': mtype,
|
||||||
|
}
|
||||||
|
return instance
|
||||||
|
|
||||||
|
@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_get_machine_type(self, mock_target_cell, mock_get_instance):
|
||||||
|
mock_target_cell.return_value.__enter__.return_value = (
|
||||||
|
mock.sentinel.cell_context)
|
||||||
|
mock_get_instance.return_value = self._create_test_instance_obj(
|
||||||
|
mtype='pc'
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
'pc',
|
||||||
|
machine_type_utils.get_machine_type(
|
||||||
|
mock.sentinel.context,
|
||||||
|
instance_uuid=uuidsentinel.instance
|
||||||
|
)
|
||||||
|
)
|
||||||
|
mock_get_instance.assert_called_once_with(
|
||||||
|
mock.sentinel.cell_context,
|
||||||
|
uuidsentinel.instance,
|
||||||
|
expected_attrs=['system_metadata']
|
||||||
|
)
|
||||||
|
|
||||||
|
@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_get_machine_type_none_found(
|
||||||
|
self, mock_target_cell, mock_get_instance
|
||||||
|
):
|
||||||
|
mock_target_cell.return_value.__enter__.return_value = (
|
||||||
|
mock.sentinel.cell_context)
|
||||||
|
mock_get_instance.return_value = self._create_test_instance_obj()
|
||||||
|
self.assertIsNone(
|
||||||
|
machine_type_utils.get_machine_type(
|
||||||
|
mock.sentinel.context,
|
||||||
|
instance_uuid=uuidsentinel.instance
|
||||||
|
)
|
||||||
|
)
|
||||||
|
mock_get_instance.assert_called_once_with(
|
||||||
|
mock.sentinel.cell_context,
|
||||||
|
uuidsentinel.instance,
|
||||||
|
expected_attrs=['system_metadata']
|
||||||
|
)
|
|
@ -0,0 +1,38 @@
|
||||||
|
# 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 typing as ty
|
||||||
|
|
||||||
|
from nova import context as nova_context
|
||||||
|
from nova import objects
|
||||||
|
|
||||||
|
|
||||||
|
def get_machine_type(
|
||||||
|
context: 'nova_context.RequestContext',
|
||||||
|
instance_uuid: str,
|
||||||
|
) -> ty.Optional[str]:
|
||||||
|
"""Get the registered machine type of an instance
|
||||||
|
|
||||||
|
:param context: Request context.
|
||||||
|
:param instance_uuid: Instance UUID to check.
|
||||||
|
:returns: The machine type or None.
|
||||||
|
:raises: exception.InstanceNotFound, exception.InstanceMappingNotFound
|
||||||
|
"""
|
||||||
|
im = objects.InstanceMapping.get_by_instance_uuid(context, instance_uuid)
|
||||||
|
with nova_context.target_cell(context, im.cell_mapping) as cctxt:
|
||||||
|
# NOTE(lyarwood): While we are after the value of 'hw_machine_type'
|
||||||
|
# stored as an image metadata property this actually comes from the
|
||||||
|
# system metadata of the instance so we need to add
|
||||||
|
# expected_attrs=['system_metadata'] here.
|
||||||
|
instance = objects.instance.Instance.get_by_uuid(
|
||||||
|
cctxt, instance_uuid, expected_attrs=['system_metadata'])
|
||||||
|
return instance.image_meta.properties.get('hw_machine_type')
|
|
@ -8,3 +8,11 @@ upgrade:
|
||||||
This machine type will then be used when the instance is restarted or
|
This machine type will then be used when the instance is restarted or
|
||||||
migrated as it will now appear as an image metadata property associated
|
migrated as it will now appear as an image metadata property associated
|
||||||
with the instance.
|
with the instance.
|
||||||
|
|
||||||
|
The following new ``nova-manage`` commands have been introduced to help
|
||||||
|
operators manage the ``hw_machine_type`` image property:
|
||||||
|
|
||||||
|
``nova-manage libvirt get_machine_type``
|
||||||
|
|
||||||
|
This command will print the current machine type if set in the image
|
||||||
|
metadata of the instance.
|
||||||
|
|
Loading…
Reference in New Issue