Merge "nova-manage: Add libvirt list_unset_machine_type command"

This commit is contained in:
Zuul 2021-03-06 17:21:43 +00:00 committed by Gerrit Code Review
commit 30c5b6137a
7 changed files with 353 additions and 0 deletions

View File

@ -775,6 +775,30 @@ libvirt
* - 5
- The provided machine type is unsupported
``nova-manage libvirt list_unset_machine_type [--cell-uuid]``
List the UUID of any instance without ``hw_machine_type`` set.
This command is useful for operators attempting to determine when it is
safe to change the :oslo.config:option:`libvirt.hw_machine_type` option
within an environment.
**Return Codes**
.. list-table::
:widths: 20 80
:header-rows: 1
* - Return code
- Description
* - 0
- Completed successfully, no instances found without hw_machine_type
* - 1
- An unexpected error occurred
* - 2
- Unable to find cell mapping
* - 3
- Instances found without hw_machine_type set
See Also
========

View File

@ -2718,6 +2718,37 @@ class LibvirtCommands(object):
'previous_type': ptype})
return 0
@action_description(
_("List the UUIDs of instances that do not have hw_machine_type set "
"in their image metadata"))
@args('--cell-uuid', metavar='<cell_uuid>', dest='cell_uuid',
required=False, help='UUID of cell from which to list instances')
def list_unset_machine_type(self, cell_uuid=None):
"""List the UUIDs of instances without image_hw_machine_type set
Return codes:
* 0: Command completed successfully, no instances found.
* 1: An unexpected error happened.
* 2: Unable to find cell mapping.
* 3: Instances found without hw_machine_type set.
"""
try:
instance_list = machine_type_utils.get_instances_without_type(
context.get_admin_context(), cell_uuid)
except exception.CellMappingNotFound as e:
print(str(e))
return 2
except Exception:
LOG.exception('Unexpected error')
return 1
if instance_list:
print('\n'.join(i.uuid for i in instance_list))
return 3
else:
print(_("No instances found without hw_machine_type set."))
return 0
CATEGORIES = {
'api_db': ApiDbCommands,

View File

@ -293,3 +293,15 @@ class LibvirtMachineTypeTest(base.ServersTestBase):
# 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')
def test_machine_type_list_unset_machine_type(self):
self.flags(hw_machine_type='x86_64=pc', group='libvirt')
server_with, server_without = self._create_servers()
self._unset_machine_type(server_without['id'])
instances = machine_type_utils.get_instances_without_type(self.context)
self.assertEqual(
server_without['id'],
instances[0].uuid
)

View File

@ -3241,3 +3241,74 @@ class LibvirtCommandsTestCase(test.NoDBTestCase):
"Machine type foo is not supported.",
output
)
@mock.patch(
'nova.virt.libvirt.machine_type_utils.get_instances_without_type')
@mock.patch('nova.context.get_admin_context')
def test_list_unset_machine_type_none_found(
self, mock_get_context, mock_get_instances
):
mock_get_context.return_value = mock.sentinel.admin_context
mock_get_instances.return_value = []
ret = self.commands.list_unset_machine_type(
cell_uuid=uuidsentinel.cell_uuid)
mock_get_instances.assert_called_once_with(
mock.sentinel.admin_context,
uuidsentinel.cell_uuid
)
output = self.output.getvalue()
self.assertEqual(0, ret)
self.assertIn(
"No instances found without hw_machine_type set.",
output
)
@mock.patch(
'nova.virt.libvirt.machine_type_utils.get_instances_without_type')
@mock.patch('nova.context.get_admin_context')
def test_list_unset_machine_type_unknown_failure(
self, mock_get_context, mock_get_instances
):
mock_get_instances.side_effect = Exception()
ret = self.commands.list_unset_machine_type(
cell_uuid=uuidsentinel.cell_uuid)
self.assertEqual(1, ret)
@mock.patch(
'nova.virt.libvirt.machine_type_utils.get_instances_without_type')
@mock.patch('nova.context.get_admin_context')
def test_list_unset_machine_type_cell_mapping_not_found(
self, mock_get_context, mock_get_instances
):
mock_get_context.return_value = mock.sentinel.admin_context
mock_get_instances.side_effect = exception.CellMappingNotFound(
uuid=uuidsentinel.cell_uuid
)
ret = self.commands.list_unset_machine_type(
cell_uuid=uuidsentinel.cell_uuid)
output = self.output.getvalue()
self.assertEqual(2, ret)
self.assertIn(
f"Cell {uuidsentinel.cell_uuid} has no mapping",
output
)
@mock.patch(
'nova.virt.libvirt.machine_type_utils.get_instances_without_type')
@mock.patch('nova.context.get_admin_context')
def test_list_unset_machine_type(
self, mock_get_context, mock_get_instances
):
mock_get_context.return_value = mock.sentinel.admin_context
mock_get_instances.return_value = [
mock.Mock(spec=objects.Instance, uuid=uuidsentinel.instance)
]
ret = self.commands.list_unset_machine_type(
cell_uuid=uuidsentinel.cell_uuid)
mock_get_instances.assert_called_once_with(
mock.sentinel.admin_context,
uuidsentinel.cell_uuid
)
output = self.output.getvalue()
self.assertEqual(3, ret)
self.assertIn(uuidsentinel.instance, output)

View File

@ -13,11 +13,14 @@
import ddt
import mock
from oslo_utils.fixture import uuidsentinel
from oslo_utils import uuidutils
from nova.compute import vm_states
from nova import context as nova_context
from nova import exception
from nova import objects
from nova import test
from nova.tests import fixtures as nova_fixtures
from nova.virt.libvirt import machine_type_utils
@ -295,3 +298,163 @@ class TestMachineTypeUtils(test.NoDBTestCase):
instance.system_metadata.get('image_hw_machine_type')
)
mock_instance_save.assert_called_once()
class TestMachineTypeUtilsListUnset(test.NoDBTestCase):
USES_DB_SELF = True
NUMBER_OF_CELLS = 2
def setUp(self):
super().setUp()
self.useFixture(nova_fixtures.Database(database='api'))
self.context = nova_context.get_admin_context()
@staticmethod
def _create_node_in_cell(ctxt, cell, hypervisor_type, nodename):
with nova_context.target_cell(ctxt, cell) as cctxt:
cn = objects.ComputeNode(
context=cctxt,
hypervisor_type=hypervisor_type,
hypervisor_hostname=nodename,
# The rest of these values are fakes.
host=uuidsentinel.host,
vcpus=4,
memory_mb=8 * 1024,
local_gb=40,
vcpus_used=2,
memory_mb_used=2 * 1024,
local_gb_used=10,
hypervisor_version=1,
cpu_info='{"arch": "x86_64"}')
cn.create()
return cn
@staticmethod
def _create_instance_in_cell(
ctxt,
cell,
node,
is_deleted=False,
hw_machine_type=None
):
with nova_context.target_cell(ctxt, cell) as cctxt:
inst = objects.Instance(
context=cctxt,
host=node.host,
node=node.hypervisor_hostname,
uuid=uuidutils.generate_uuid())
inst.create()
if hw_machine_type:
inst.system_metadata = {
'image_hw_machine_type': hw_machine_type
}
if is_deleted:
inst.destroy()
return inst
def _setup_instances(self):
# Setup the required cells
self._setup_cells()
# Track and return the created instances so the tests can assert
instances = {}
# Create a node in each cell
node1_cell1 = self._create_node_in_cell(
self.context,
self.cell_mappings['cell1'],
'kvm',
uuidsentinel.node_cell1_uuid
)
node2_cell2 = self._create_node_in_cell(
self.context,
self.cell_mappings['cell2'],
'kvm',
uuidsentinel.node_cell2_uuid
)
# Create some instances with and without machine types defined in cell1
instances['cell1_without_mtype'] = self._create_instance_in_cell(
self.context,
self.cell_mappings['cell1'],
node1_cell1
)
instances['cell1_with_mtype'] = self._create_instance_in_cell(
self.context,
self.cell_mappings['cell1'],
node1_cell1,
hw_machine_type='pc'
)
instances['cell1_with_mtype_deleted'] = self._create_instance_in_cell(
self.context,
self.cell_mappings['cell1'],
node1_cell1,
hw_machine_type='pc',
is_deleted=True
)
# Repeat for cell2
instances['cell2_without_mtype'] = self._create_instance_in_cell(
self.context,
self.cell_mappings['cell2'],
node2_cell2
)
instances['cell2_with_mtype'] = self._create_instance_in_cell(
self.context,
self.cell_mappings['cell2'],
node2_cell2,
hw_machine_type='pc'
)
instances['cell2_with_mtype_deleted'] = self._create_instance_in_cell(
self.context,
self.cell_mappings['cell2'],
node2_cell2,
hw_machine_type='pc',
is_deleted=True
)
return instances
def test_fresh_install_no_cell_mappings(self):
self.assertEqual(
[],
machine_type_utils.get_instances_without_type(self.context)
)
def test_fresh_install_no_computes(self):
self._setup_cells()
self.assertEqual(
[],
machine_type_utils.get_instances_without_type(self.context)
)
def test_get_from_specific_cell(self):
instances = self._setup_instances()
# Assert that we only see the uuid for instance cell1_without_mtype
instance_list = machine_type_utils.get_instances_without_type(
self.context,
cell_uuid=self.cell_mappings['cell1'].uuid
)
self.assertEqual(
instances['cell1_without_mtype'].uuid,
instance_list[0].uuid
)
def test_get_multi_cell(self):
instances = self._setup_instances()
# Assert that we see both undeleted _without_mtype instances
instance_list = machine_type_utils.get_instances_without_type(
self.context,
)
instance_uuids = [i.uuid for i in instance_list]
self.assertIn(
instances['cell1_without_mtype'].uuid,
instance_uuids
)
self.assertIn(
instances['cell2_without_mtype'].uuid,
instance_uuids
)

View File

@ -10,6 +10,7 @@
# License for the specific language governing permissions and limitations
# under the License.
import itertools
import re
import typing as ty
@ -178,3 +179,48 @@ def update_machine_type(
instance.save()
return machine_type, existing_mtype
def _get_instances_without_mtype(
context: 'nova_context.RequestContext',
) -> ty.List[objects.instance.Instance]:
"""Fetch a list of instance UUIDs from the DB without hw_machine_type set
:param meta: 'sqlalchemy.MetaData' pointing to a given cell DB
:returns: A list of Instance objects or an empty list
"""
instances = objects.InstanceList.get_all(
context, expected_attrs=['system_metadata'])
instances_without = []
for instance in instances:
if instance.deleted == 0 and instance.vm_state != vm_states.BUILDING:
if instance.image_meta.properties.get('hw_machine_type') is None:
instances_without.append(instance)
return instances_without
def get_instances_without_type(
context: 'nova_context.RequestContext',
cell_uuid: ty.Optional[str] = None,
) -> ty.List[objects.instance.Instance]:
"""Find instances without hw_machine_type set, optionally within a cell.
:param context: Request context
:param cell_uuid: Optional cell UUID to look within
:returns: A list of Instance objects or an empty list
"""
if cell_uuid:
cell_mapping = objects.CellMapping.get_by_uuid(context, cell_uuid)
results = nova_context.scatter_gather_single_cell(
context,
cell_mapping,
_get_instances_without_mtype
)
results = nova_context.scatter_gather_skip_cell0(
context,
_get_instances_without_mtype
)
# Flatten the returned list of results into a single list of instances
return list(itertools.chain(*[r for c, r in results.items()]))

View File

@ -40,3 +40,9 @@ upgrade:
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.
``nova-manage libvirt list_unset_machine_type``
This command will list instance UUIDs that do not have a machine type
recorded. An optional cell UUID can be provided to list on instances
without a machine type from that cell.