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
|
||||
- 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
|
||||
========
|
||||
|
|
|
@ -5,6 +5,7 @@ nova/scheduler/request_filter.py
|
|||
nova/scheduler/utils.py
|
||||
nova/virt/driver.py
|
||||
nova/virt/hardware.py
|
||||
nova/virt/libvirt/machine_type_utils.py
|
||||
nova/virt/libvirt/__init__.py
|
||||
nova/virt/libvirt/driver.py
|
||||
nova/virt/libvirt/event.py
|
||||
|
|
|
@ -67,6 +67,7 @@ from nova import rpc
|
|||
from nova.scheduler.client import report
|
||||
from nova.scheduler import utils as scheduler_utils
|
||||
from nova import version
|
||||
from nova.virt.libvirt import machine_type_utils
|
||||
|
||||
CONF = nova.conf.CONF
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
@ -2620,11 +2621,49 @@ class PlacementCommands(object):
|
|||
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 = {
|
||||
'api_db': ApiDbCommands,
|
||||
'cell_v2': CellV2Commands,
|
||||
'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 objects
|
||||
from nova.tests.functional.libvirt import base
|
||||
from nova.virt.libvirt import machine_type_utils
|
||||
|
||||
|
||||
class LibvirtMachineTypeTest(base.ServersTestBase):
|
||||
|
@ -78,6 +79,14 @@ class LibvirtMachineTypeTest(base.ServersTestBase):
|
|||
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):
|
||||
"""Assert that the machine type of an instance is recorded during
|
||||
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
|
||||
# allow init_host to register the machine type.
|
||||
self.computes['compute1'].stop()
|
||||
instance_without = objects.Instance.get_by_uuid(
|
||||
self.context,
|
||||
server_without['id'],
|
||||
)
|
||||
instance_without.system_metadata.pop('image_hw_machine_type')
|
||||
instance_without.save()
|
||||
self._unset_machine_type(server_without['id'])
|
||||
|
||||
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):
|
||||
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
|
||||
self.assertEqual(255, manage.main())
|
||||
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
|
||||
migrated as it will now appear as an image metadata property associated
|
||||
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