summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLee Yarwood <lyarwood@redhat.com>2021-01-05 15:20:44 +0000
committerLee Yarwood <lyarwood@redhat.com>2021-03-03 14:03:49 +0000
commit20692c245c64a85780db620f4148889ad867b911 (patch)
treec504054e61576d277538829a620db8abcd0cd010
parent5b66caab870558b8a7f7b662c01587b959ad3d41 (diff)
downloadnova-20692c245c64a85780db620f4148889ad867b911.tar.gz
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
-rw-r--r--doc/source/cli/nova-manage.rst22
-rw-r--r--mypy-files.txt1
-rw-r--r--nova/cmd/manage.py41
-rw-r--r--nova/tests/functional/libvirt/test_machine_type.py33
-rw-r--r--nova/tests/unit/cmd/test_manage.py77
-rw-r--r--nova/tests/unit/virt/libvirt/test_machine_type_utils.py84
-rw-r--r--nova/virt/libvirt/machine_type_utils.py38
-rw-r--r--releasenotes/notes/libvirt-store-and-change-default-machine-type-bf86b4973c4dee4c.yaml8
8 files changed, 297 insertions, 7 deletions
diff --git a/doc/source/cli/nova-manage.rst b/doc/source/cli/nova-manage.rst
index b7e4880410..0495bda03d 100644
--- a/doc/source/cli/nova-manage.rst
+++ b/doc/source/cli/nova-manage.rst
@@ -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
========
diff --git a/mypy-files.txt b/mypy-files.txt
index 50700d8489..af7de8841b 100644
--- a/mypy-files.txt
+++ b/mypy-files.txt
@@ -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
diff --git a/nova/cmd/manage.py b/nova/cmd/manage.py
index 31e12ad1dc..77f2b8cb2b 100644
--- a/nova/cmd/manage.py
+++ b/nova/cmd/manage.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,
}
diff --git a/nova/tests/functional/libvirt/test_machine_type.py b/nova/tests/functional/libvirt/test_machine_type.py
index cbcef574b5..7ebe063cdd 100644
--- a/nova/tests/functional/libvirt/test_machine_type.py
+++ b/nova/tests/functional/libvirt/test_machine_type.py
@@ -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']
+ )
+ )
diff --git a/nova/tests/unit/cmd/test_manage.py b/nova/tests/unit/cmd/test_manage.py
index 29c9b6063d..6f764b9a4a 100644
--- a/nova/tests/unit/cmd/test_manage.py
+++ b/nova/tests/unit/cmd/test_manage.py
@@ -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)
diff --git a/nova/tests/unit/virt/libvirt/test_machine_type_utils.py b/nova/tests/unit/virt/libvirt/test_machine_type_utils.py
new file mode 100644
index 0000000000..16452d5bf8
--- /dev/null
+++ b/nova/tests/unit/virt/libvirt/test_machine_type_utils.py
@@ -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']
+ )
diff --git a/nova/virt/libvirt/machine_type_utils.py b/nova/virt/libvirt/machine_type_utils.py
new file mode 100644
index 0000000000..db6ddd2dc6
--- /dev/null
+++ b/nova/virt/libvirt/machine_type_utils.py
@@ -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')
diff --git a/releasenotes/notes/libvirt-store-and-change-default-machine-type-bf86b4973c4dee4c.yaml b/releasenotes/notes/libvirt-store-and-change-default-machine-type-bf86b4973c4dee4c.yaml
index e440e6bf8f..2a4b1e7726 100644
--- a/releasenotes/notes/libvirt-store-and-change-default-machine-type-bf86b4973c4dee4c.yaml
+++ b/releasenotes/notes/libvirt-store-and-change-default-machine-type-bf86b4973c4dee4c.yaml
@@ -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.