Merge "nova-manage: Add libvirt list_unset_machine_type command"
This commit is contained in:
commit
30c5b6137a
|
@ -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
|
||||
========
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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()]))
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in New Issue