Files
nova/nova/tests/unit/objects/test_image_meta.py
T
Stephen Finucane 3f63c68195 libvirt: Add support for virtio-based input devices
The USB-based tablet is often the only USB device in an x86 instance,
while the USB-based keyboard is often the only such device in an AArch64
instance (x86 have PS2 keyboards and mice). Replacing these with
virtio-based devices can eliminate the need to have a USB host adapter
in the instance. Enable just that possibility by adding a new value
image metadata property, 'hw_input_bus'. This allows us to specify not
only virtio-based pointer and keyboard input devices but also USB
equivalents.

Note that this also fixes one instance of a particular class of bugs,
whereby we have checks for *guest* architecture-specific behavior that
are being toggled based on the *host* architecture. In this instance,
we were attempting to add a keyboard device on AArch64 guests since they
don't have one by default, but we were determining the architecture by
looking at the CPU architecture reported in the host capabilities. By
replacing this check of the host capabilities with a call to the
'nova.virt.libvirt.utils.get_arch' helper, we correctly handle requests
to create non-host architecture guests via the 'hw_architecture' image
metadata property. There are many other instances of this bug and those
can be resolved separately.

Change-Id: If9f3ede3e8449f9a6c8d1da927974c0a73923d51
Signed-off-by: Stephen Finucane <stephenfin@redhat.com>
2021-03-05 11:00:02 +00:00

448 lines
19 KiB
Python

# Copyright 2014 Red Hat, Inc.
#
# 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 datetime
from nova import exception
from nova import objects
from nova.objects import fields
from nova import test
class TestImageMeta(test.NoDBTestCase):
def test_basic_attrs(self):
image = {'status': 'active',
'container_format': 'bare',
'min_ram': 0,
'updated_at': '2014-12-12T11:16:36.000000',
# Testing string -> int conversion
'min_disk': '0',
'owner': '2d8b9502858c406ebee60f0849486222',
# Testing string -> bool conversion
'protected': 'yes',
'properties': {
'os_type': 'Linux',
'hw_video_model': 'vga',
'hw_video_ram': '512',
'hw_qemu_guest_agent': 'yes',
'hw_scsi_model': 'virtio-scsi',
},
'size': 213581824,
'name': 'f16-x86_64-openstack-sda',
'checksum': '755122332caeb9f661d5c978adb8b45f',
'created_at': '2014-12-10T16:23:14.000000',
'disk_format': 'qcow2',
'id': 'c8b1790e-a07d-4971-b137-44f2432936cd'
}
image_meta = objects.ImageMeta.from_dict(image)
self.assertEqual('active', image_meta.status)
self.assertEqual('bare', image_meta.container_format)
self.assertEqual(0, image_meta.min_ram)
self.assertIsInstance(image_meta.updated_at, datetime.datetime)
self.assertEqual(0, image_meta.min_disk)
self.assertEqual('2d8b9502858c406ebee60f0849486222', image_meta.owner)
self.assertTrue(image_meta.protected)
self.assertEqual(213581824, image_meta.size)
self.assertEqual('f16-x86_64-openstack-sda', image_meta.name)
self.assertEqual('755122332caeb9f661d5c978adb8b45f',
image_meta.checksum)
self.assertIsInstance(image_meta.created_at, datetime.datetime)
self.assertEqual('qcow2', image_meta.disk_format)
self.assertEqual('c8b1790e-a07d-4971-b137-44f2432936cd', image_meta.id)
self.assertIsInstance(image_meta.properties, objects.ImageMetaProps)
def test_no_props(self):
image_meta = objects.ImageMeta.from_dict({})
self.assertIsInstance(image_meta.properties, objects.ImageMetaProps)
def test_volume_backed_image(self):
image = {'container_format': None,
'size': 0,
'checksum': None,
'disk_format': None,
}
image_meta = objects.ImageMeta.from_dict(image)
self.assertEqual('', image_meta.container_format)
self.assertEqual(0, image_meta.size)
self.assertEqual('', image_meta.checksum)
self.assertEqual('', image_meta.disk_format)
def test_null_substitution(self):
image = {'name': None,
'checksum': None,
'owner': None,
'size': None,
'virtual_size': None,
'container_format': None,
'disk_format': None,
}
image_meta = objects.ImageMeta.from_dict(image)
self.assertEqual('', image_meta.name)
self.assertEqual('', image_meta.checksum)
self.assertEqual('', image_meta.owner)
self.assertEqual(0, image_meta.size)
self.assertEqual(0, image_meta.virtual_size)
self.assertEqual('', image_meta.container_format)
self.assertEqual('', image_meta.disk_format)
class TestImageMetaProps(test.NoDBTestCase):
def test_normal_props(self):
props = {'os_type': 'windows',
'hw_video_model': 'vga',
'hw_video_ram': '512',
'hw_qemu_guest_agent': 'yes',
'trait:CUSTOM_TRUSTED': 'required',
# Fill sane values for the rest here
}
virtprops = objects.ImageMetaProps.from_dict(props)
self.assertEqual('windows', virtprops.os_type)
self.assertEqual('vga', virtprops.hw_video_model)
self.assertEqual(512, virtprops.hw_video_ram)
self.assertTrue(virtprops.hw_qemu_guest_agent)
self.assertIsNotNone(virtprops.traits_required)
self.assertIn('CUSTOM_TRUSTED', virtprops.traits_required)
def test_default_props(self):
props = {}
virtprops = objects.ImageMetaProps.from_dict(props)
for prop in virtprops.fields:
self.assertIsNone(virtprops.get(prop))
def test_default_prop_value(self):
props = {}
virtprops = objects.ImageMetaProps.from_dict(props)
self.assertEqual("hvm", virtprops.get("hw_vm_mode", "hvm"))
self.assertIsNone(virtprops.get("traits_required"))
def test_non_existent_prop(self):
props = {}
virtprops = objects.ImageMetaProps.from_dict(props)
self.assertRaises(AttributeError,
virtprops.get,
"doesnotexist")
def test_legacy_compat(self):
legacy_props = {
'architecture': 'x86_64',
'owner_id': '123',
'vmware_adaptertype': 'lsiLogic',
'vmware_disktype': 'preallocated',
'vmware_image_version': '2',
'vmware_ostype': 'rhel3_64Guest',
'auto_disk_config': 'yes',
'ipxe_boot': 'yes',
'xenapi_device_id': '3',
'xenapi_image_compression_level': '2',
'vmware_linked_clone': 'false',
'xenapi_use_agent': 'yes',
'xenapi_skip_agent_inject_ssh': 'no',
'xenapi_skip_agent_inject_files_at_boot': 'no',
'cache_in_nova': 'yes',
'vm_mode': 'hvm',
'bittorrent': 'yes',
'mappings': [],
'block_device_mapping': [],
'bdm_v2': 'yes',
'root_device_name': '/dev/vda',
'hypervisor_version_requires': '>=1.5.3',
'hypervisor_type': 'qemu',
}
image_meta = objects.ImageMetaProps.from_dict(legacy_props)
self.assertEqual('x86_64', image_meta.hw_architecture)
self.assertEqual('123', image_meta.img_owner_id)
self.assertEqual('lsilogic', image_meta.hw_scsi_model)
self.assertEqual('preallocated', image_meta.hw_disk_type)
self.assertEqual(2, image_meta.img_version)
self.assertEqual('rhel3_64Guest', image_meta.os_distro)
self.assertTrue(image_meta.hw_auto_disk_config)
self.assertTrue(image_meta.hw_ipxe_boot)
self.assertEqual(3, image_meta.hw_device_id)
self.assertEqual(2, image_meta.img_compression_level)
self.assertFalse(image_meta.img_linked_clone)
self.assertTrue(image_meta.img_use_agent)
self.assertFalse(image_meta.os_skip_agent_inject_ssh)
self.assertFalse(image_meta.os_skip_agent_inject_files_at_boot)
self.assertTrue(image_meta.img_cache_in_nova)
self.assertTrue(image_meta.img_bittorrent)
self.assertEqual([], image_meta.img_mappings)
self.assertEqual([], image_meta.img_block_device_mapping)
self.assertTrue(image_meta.img_bdm_v2)
self.assertEqual("/dev/vda", image_meta.img_root_device_name)
self.assertEqual('>=1.5.3', image_meta.img_hv_requested_version)
self.assertEqual('qemu', image_meta.img_hv_type)
def test_legacy_compat_vmware_adapter_types(self):
legacy_types = ['lsiLogic', 'busLogic', 'ide', 'lsiLogicsas',
'paraVirtual', None, '']
for legacy_type in legacy_types:
legacy_props = {
'vmware_adaptertype': legacy_type,
}
image_meta = objects.ImageMetaProps.from_dict(legacy_props)
if legacy_type == 'ide':
self.assertEqual('ide', image_meta.hw_disk_bus)
elif not legacy_type:
self.assertFalse(image_meta.obj_attr_is_set('hw_disk_bus'))
self.assertFalse(image_meta.obj_attr_is_set('hw_scsi_model'))
else:
self.assertEqual('scsi', image_meta.hw_disk_bus)
if legacy_type == 'lsiLogicsas':
expected = 'lsisas1068'
elif legacy_type == 'paraVirtual':
expected = 'vmpvscsi'
else:
expected = legacy_type.lower()
self.assertEqual(expected, image_meta.hw_scsi_model)
def test_duplicate_legacy_and_normal_props(self):
# Both keys are referring to the same object field
props = {'hw_scsi_model': 'virtio-scsi',
'vmware_adaptertype': 'lsiLogic',
}
virtprops = objects.ImageMetaProps.from_dict(props)
# The normal property always wins vs. the legacy field since
# _set_attr_from_current_names is called finally
self.assertEqual('virtio-scsi', virtprops.hw_scsi_model)
def test_get(self):
props = objects.ImageMetaProps(os_distro='linux')
self.assertEqual('linux', props.get('os_distro'))
self.assertIsNone(props.get('img_version'))
self.assertEqual(1, props.get('img_version', 1))
def test_set_numa_mem(self):
props = {'hw_numa_nodes': 2,
'hw_numa_mem.0': "2048",
'hw_numa_mem.1': "4096"}
virtprops = objects.ImageMetaProps.from_dict(props)
self.assertEqual(2, virtprops.hw_numa_nodes)
self.assertEqual([2048, 4096], virtprops.hw_numa_mem)
def test_set_numa_mem_sparse(self):
props = {'hw_numa_nodes': 2,
'hw_numa_mem.0': "2048",
'hw_numa_mem.1': "1024",
'hw_numa_mem.3': "4096"}
virtprops = objects.ImageMetaProps.from_dict(props)
self.assertEqual(2, virtprops.hw_numa_nodes)
self.assertEqual([2048, 1024], virtprops.hw_numa_mem)
def test_set_numa_mem_no_count(self):
props = {'hw_numa_mem.0': "2048",
'hw_numa_mem.3': "4096"}
virtprops = objects.ImageMetaProps.from_dict(props)
self.assertIsNone(virtprops.get("hw_numa_nodes"))
self.assertEqual([2048], virtprops.hw_numa_mem)
def test_set_numa_cpus(self):
props = {'hw_numa_nodes': 2,
'hw_numa_cpus.0': "0-3",
'hw_numa_cpus.1': "4-7"}
virtprops = objects.ImageMetaProps.from_dict(props)
self.assertEqual(2, virtprops.hw_numa_nodes)
self.assertEqual([set([0, 1, 2, 3]), set([4, 5, 6, 7])],
virtprops.hw_numa_cpus)
def test_set_numa_cpus_sparse(self):
props = {'hw_numa_nodes': 4,
'hw_numa_cpus.0': "0-3",
'hw_numa_cpus.1': "4,5",
'hw_numa_cpus.3': "6-7"}
virtprops = objects.ImageMetaProps.from_dict(props)
self.assertEqual(4, virtprops.hw_numa_nodes)
self.assertEqual([set([0, 1, 2, 3]), set([4, 5])],
virtprops.hw_numa_cpus)
def test_set_numa_cpus_no_count(self):
props = {'hw_numa_cpus.0': "0-3",
'hw_numa_cpus.3': "4-7"}
virtprops = objects.ImageMetaProps.from_dict(props)
self.assertIsNone(virtprops.get("hw_numa_nodes"))
self.assertEqual([set([0, 1, 2, 3])],
virtprops.hw_numa_cpus)
def test_get_unnumbered_trait_fields(self):
"""Tests that only valid un-numbered required traits are parsed from
the properties.
"""
props = {'trait:HW_CPU_X86_AVX2': 'required',
'trait:CUSTOM_TRUSTED': 'required',
'trait1:CUSTOM_FPGA': 'required',
'trai:CUSTOM_FOO': 'required',
'trait:CUSTOM_XYZ': 'xyz'}
virtprops = objects.ImageMetaProps.from_dict(props)
self.assertIn('CUSTOM_TRUSTED', virtprops.traits_required)
self.assertIn('HW_CPU_X86_AVX2', virtprops.traits_required)
# numbered traits are ignored
self.assertNotIn('CUSTOM_FPGA', virtprops.traits_required)
# property key does not start with `trait:` exactly
self.assertNotIn('CUSTOM_FOO', virtprops.traits_required)
# property value is not required
self.assertNotIn('CUSTOM_XYZ', virtprops.traits_required)
def test_traits_required_initialized_as_list(self):
"""Tests that traits_required field is set as a list even if the same
property is set on the image metadata.
"""
props = {'trait:HW_CPU_X86_AVX2': 'required',
'trait:CUSTOM_TRUSTED': 'required',
'traits_required': 'foo'}
virtprops = objects.ImageMetaProps.from_dict(props)
self.assertIsInstance(virtprops.traits_required, list)
self.assertIn('CUSTOM_TRUSTED', virtprops.traits_required)
self.assertIn('HW_CPU_X86_AVX2', virtprops.traits_required)
self.assertEqual(2, len(virtprops.traits_required))
def test_obj_make_compatible(self):
props = {
'hw_firmware_type': 'uefi',
'hw_cpu_realtime_mask': '^0-1',
'hw_cpu_thread_policy': 'prefer',
'img_config_drive': 'mandatory',
'os_admin_user': 'root',
'hw_vif_multiqueue_enabled': True,
'img_hv_type': 'kvm',
'img_hv_requested_version': '>= 1.0',
'os_require_quiesce': True,
'os_secure_boot': 'required',
'hw_rescue_bus': 'ide',
'hw_rescue_device': 'disk',
'hw_watchdog_action': fields.WatchdogAction.DISABLED,
}
obj = objects.ImageMetaProps(**props)
primitive = obj.obj_to_primitive('1.0')
self.assertFalse(any([x in primitive['nova_object.data']
for x in props]))
for bus in ('lxc', 'uml'):
obj.hw_disk_bus = bus
self.assertRaises(exception.ObjectActionError,
obj.obj_to_primitive, '1.0')
def test_obj_make_compatible_input_bus(self):
"""Check 'hw_input_bus' compatibility."""
# assert that 'hw_input_bus' is supported on a suitably new version
obj = objects.ImageMetaProps(
hw_input_bus=objects.fields.InputBus.VIRTIO,
)
primitive = obj.obj_to_primitive('1.29')
self.assertIn('hw_input_bus', primitive['nova_object.data'])
self.assertEqual(
objects.fields.InputBus.VIRTIO,
primitive['nova_object.data']['hw_input_bus'])
# and is absent on older versions
primitive = obj.obj_to_primitive('1.28')
self.assertNotIn('hw_input_bus', primitive['nova_object.data'])
def test_obj_make_compatible_video_model(self):
# assert that older video models are preserved.
obj = objects.ImageMetaProps(
hw_video_model=objects.fields.VideoModel.QXL,
hw_disk_bus=objects.fields.DiskBus.VIRTIO
)
primitive = obj.obj_to_primitive('1.21')
self.assertIn("hw_video_model", primitive['nova_object.data'])
self.assertEqual(objects.fields.VideoModel.QXL,
primitive['nova_object.data']['hw_video_model'])
self.assertIn("hw_disk_bus", primitive['nova_object.data'])
self.assertEqual(objects.fields.DiskBus.VIRTIO,
primitive['nova_object.data']['hw_disk_bus'])
# Virtio, GOP and None were added in 1.22 and should raise an
# exception when backleveling.
models = [objects.fields.VideoModel.VIRTIO,
objects.fields.VideoModel.GOP,
objects.fields.VideoModel.NONE]
for model in models:
obj = objects.ImageMetaProps(hw_video_model=model)
ex = self.assertRaises(exception.ObjectActionError,
obj.obj_to_primitive, '1.21')
self.assertIn('hw_video_model', str(ex))
def test_obj_make_compatible_watchdog_action_not_disabled(self):
"""Tests that we don't pop the hw_watchdog_action if the value is not
'disabled'.
"""
obj = objects.ImageMetaProps(
hw_watchdog_action=fields.WatchdogAction.PAUSE)
primitive = obj.obj_to_primitive('1.0')
self.assertIn('hw_watchdog_action', primitive['nova_object.data'])
self.assertEqual(fields.WatchdogAction.PAUSE,
primitive['nova_object.data']['hw_watchdog_action'])
def test_set_os_secure_boot(self):
props = {'os_secure_boot': "required"}
secure_props = objects.ImageMetaProps.from_dict(props)
self.assertEqual("required", secure_props.os_secure_boot)
def test_obj_make_compatible_img_hide_hypervisor_id(self):
"""Tests that checks if we pop img_hide_hypervisor_id."""
obj = objects.ImageMetaProps(img_hide_hypervisor_id=True)
primitive = obj.obj_to_primitive('1.0')
self.assertNotIn('img_hide_hypervisor_id',
primitive['nova_object.data'])
def test_obj_make_compatible_trait_fields(self):
"""Tests that checks if we pop traits_required."""
obj = objects.ImageMetaProps(traits_required=['CUSTOM_TRUSTED'])
primitive = obj.obj_to_primitive('1.19')
self.assertNotIn('traits_required', primitive['nova_object.data'])
def test_obj_make_compatible_pmu(self):
"""Tests that checks if we pop hw_pmu."""
obj = objects.ImageMetaProps(hw_pmu=True)
primitive = obj.obj_to_primitive()
old_primitive = obj.obj_to_primitive('1.22')
self.assertIn('hw_pmu', primitive['nova_object.data'])
self.assertNotIn('hw_pmu', old_primitive['nova_object.data'])
def test_obj_make_compatible_vtpm(self):
"""Test that checks if we pop hw_tpm_model and hw_tpm_version."""
obj = objects.ImageMetaProps(
hw_tpm_model='tpm-tis', hw_tpm_version='1.2',
)
primitive = obj.obj_to_primitive()
self.assertIn('hw_tpm_model', primitive['nova_object.data'])
self.assertIn('hw_tpm_version', primitive['nova_object.data'])
primitive = obj.obj_to_primitive('1.26')
self.assertNotIn('hw_tpm_model', primitive['nova_object.data'])
self.assertNotIn('hw_tpm_version', primitive['nova_object.data'])
def test_obj_make_compatible_socket_policy(self):
obj = objects.ImageMetaProps(
hw_pci_numa_affinity_policy=fields.PCINUMAAffinityPolicy.SOCKET)
self.assertRaises(exception.ObjectActionError,
obj.obj_to_primitive, '1.27')