461 lines
16 KiB
Python
461 lines
16 KiB
Python
# 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 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
|
|
|
|
|
|
@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={}
|
|
)
|
|
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
|
|
)
|