# 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. from unittest import mock import ddt 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 @ddt.ddt 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={}, image_ref=uuidsentinel.image_ref ) 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'] ) @ddt.data( 'pc', 'q35', 'virt', 's390-ccw-virtio', 'pc-i440fx-2.12', 'pc-q35-2.12', 'virt-2.12', 'pc-i440fx-rhel8.2.0', 'pc-q35-rhel8.2.0') def test_check_machine_type_support(self, machine_type): # Assert UnsupportedMachineType isn't raised for supported types machine_type_utils._check_machine_type_support( machine_type) @ddt.data( 'pc-foo', 'pc-foo-1.2', 'bar-q35', 'virt-foo', 'pc-virt') def test_check_machine_type_support_failure(self, machine_type): # Assert UnsupportedMachineType is raised for unsupported types self.assertRaises( exception.UnsupportedMachineType, machine_type_utils._check_machine_type_support, machine_type ) @ddt.data( ('pc-i440fx-2.10', 'pc-i440fx-2.11'), ('pc-q35-2.10', 'pc-q35-2.11')) def test_check_update_to_existing_type(self, machine_types): # Assert that exception.InvalidMachineTypeUpdate is not raised when # updating to the same type or between versions of the same type original_type, update_type = machine_types machine_type_utils._check_update_to_existing_type( original_type, update_type) @ddt.data( ('pc', 'q35'), ('q35', 'pc'), ('pc-i440fx-2.12', 'pc-q35-2.12'), ('pc', 'pc-i440fx-2.12'), ('pc-i440fx-2.12', 'pc'), ('pc-i440fx-2.12', 'pc-i440fx-2.11')) def test_check_update_to_existing_type_failure(self, machine_types): # Assert that exception.InvalidMachineTypeUpdate is raised when # updating to a different underlying machine type or between versioned # and aliased machine types existing_type, update_type = machine_types self.assertRaises( exception.InvalidMachineTypeUpdate, machine_type_utils._check_update_to_existing_type, existing_type, update_type ) @ddt.data( vm_states.STOPPED, vm_states.SHELVED, vm_states.SHELVED_OFFLOADED) def test_check_vm_state(self, vm_state): instance = self._create_test_instance_obj( vm_state=vm_state ) machine_type_utils._check_vm_state(instance) @ddt.data( vm_states.ACTIVE, vm_states.PAUSED, vm_states.ERROR) def test_check_vm_state_failure(self, vm_state): instance = self._create_test_instance_obj( vm_state=vm_state ) self.assertRaises( exception.InstanceInvalidState, machine_type_utils._check_vm_state, instance ) @mock.patch('nova.objects.instance.Instance.save') @mock.patch('nova.virt.libvirt.machine_type_utils._check_vm_state') @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_update_noop( self, mock_target_cell, mock_get_instance, mock_check_vm_state, mock_instance_save ): # Assert that update_machine_type is a noop when the type is already # set within the instance, even if forced existing_type = 'pc' mock_target_cell.return_value.__enter__.return_value = ( mock.sentinel.cell_context) mock_get_instance.return_value = self._create_test_instance_obj( mtype=existing_type, ) self.assertEqual( (existing_type, existing_type), machine_type_utils.update_machine_type( mock.sentinel.context, instance_uuid=uuidsentinel.instance, machine_type=existing_type ), ) mock_check_vm_state.assert_not_called() mock_instance_save.assert_not_called() self.assertEqual( (existing_type, existing_type), machine_type_utils.update_machine_type( mock.sentinel.context, instance_uuid=uuidsentinel.instance, machine_type=existing_type, force=True ), ) mock_check_vm_state.assert_not_called() mock_instance_save.assert_not_called() @ddt.data( ('foobar', 'foobar', None), ('foobar-1.3', 'foobar-1.3', 'foobar-1.2'), ('foobar-1.2', 'foobar-1.2', 'foobar-1.3'), ('foobar', 'foobar', 'q35'), ('pc', 'pc', 'q35')) @mock.patch('nova.objects.instance.Instance.save') @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_update_force( self, types, mock_target_cell, mock_get_instance, mock_instance_save ): expected_type, update_type, existing_type = types mock_target_cell.return_value.__enter__.return_value = ( mock.sentinel.cell_context) instance = self._create_test_instance_obj( mtype=existing_type ) mock_get_instance.return_value = instance returned_type = machine_type_utils.update_machine_type( mock.sentinel.context, uuidsentinel.instance, machine_type=update_type, force=True ) # Assert that the instance machine type was updated and saved self.assertEqual((expected_type, existing_type), returned_type) self.assertEqual( expected_type, instance.system_metadata.get('image_hw_machine_type') ) mock_instance_save.assert_called_once() @ddt.data( ('pc', 'pc', None), ('q35', 'q35', None), ('pc-1.2', 'pc-1.2', None), ('pc-q35-1.2', 'pc-q35-1.2', None), ('pc-1.2', 'pc-1.2', 'pc-1.1'), ('pc-i440fx-1.2', 'pc-i440fx-1.2', 'pc-i440fx-1.1'), ('pc-q35-1.2', 'pc-q35-1.2', 'pc-q35-1.1'), ('pc-q35-rhel8.2.0', 'pc-q35-rhel8.2.0', 'pc-q35-rhel8.1.0')) @mock.patch('nova.objects.instance.Instance.save') @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_update( self, types, mock_target_cell, mock_get_instance, mock_instance_save ): expected_type, update_type, existing_type = types mock_target_cell.return_value.__enter__.return_value = ( mock.sentinel.cell_context) instance = self._create_test_instance_obj( mtype=existing_type ) mock_get_instance.return_value = instance returned_type = machine_type_utils.update_machine_type( mock.sentinel.context, uuidsentinel.instance, machine_type=update_type ) # Assert that the instance machine type was updated and saved self.assertEqual((expected_type, existing_type), returned_type) self.assertEqual( expected_type, 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 )