diff --git a/nova/notifications/objects/image.py b/nova/notifications/objects/image.py index 107500c5b01b..43e6530ad401 100644 --- a/nova/notifications/objects/image.py +++ b/nova/notifications/objects/image.py @@ -15,6 +15,7 @@ from nova.notifications.objects import base from nova.objects import base as nova_base from nova.objects import fields +from nova.objects import image_meta @nova_base.NovaObjectRegistry.register_notification @@ -104,6 +105,17 @@ class ImageMetaPayload(base.NotificationPayloadBase): @nova_base.NovaObjectRegistry.register_notification class ImageMetaPropsPayload(base.NotificationPayloadBase): + """Built dynamically from ImageMetaProps. + + This has the following implications: + + * When you make a versioned update to ImageMetaProps, you must *also* bump + the version of this object, even though you didn't make any explicit + changes here. There's an object hash test that should catch this for you. + * As currently written, this relies on all of the fields of ImageMetaProps + being initialized with no arguments. If you add one with arguments (e.g. + ``nullable=True`` or with a ``default``), something needs to change here. + """ # Version 1.0: Initial version # Version 1.1: Added 'gop', 'virtio' and 'none' to hw_video_model field # Version 1.2: Added hw_pci_numa_affinity_policy field @@ -111,159 +123,16 @@ class ImageMetaPropsPayload(base.NotificationPayloadBase): VERSION = '1.3' SCHEMA = { - 'hw_architecture': ('image_meta_props', 'hw_architecture'), - 'hw_auto_disk_config': ('image_meta_props', 'hw_auto_disk_config'), - 'hw_boot_menu': ('image_meta_props', 'hw_boot_menu'), - 'hw_cdrom_bus': ('image_meta_props', 'hw_cdrom_bus'), - 'hw_cpu_cores': ('image_meta_props', 'hw_cpu_cores'), - 'hw_cpu_sockets': ('image_meta_props', 'hw_cpu_sockets'), - 'hw_cpu_max_cores': ('image_meta_props', 'hw_cpu_max_cores'), - 'hw_cpu_max_sockets': ('image_meta_props', 'hw_cpu_max_sockets'), - 'hw_cpu_max_threads': ('image_meta_props', 'hw_cpu_max_threads'), - 'hw_cpu_policy': ('image_meta_props', 'hw_cpu_policy'), - 'hw_cpu_thread_policy': ('image_meta_props', 'hw_cpu_thread_policy'), - 'hw_cpu_realtime_mask': ('image_meta_props', 'hw_cpu_realtime_mask'), - 'hw_cpu_threads': ('image_meta_props', 'hw_cpu_threads'), - 'hw_device_id': ('image_meta_props', 'hw_device_id'), - 'hw_disk_bus': ('image_meta_props', 'hw_disk_bus'), - 'hw_disk_type': ('image_meta_props', 'hw_disk_type'), - 'hw_floppy_bus': ('image_meta_props', 'hw_floppy_bus'), - 'hw_firmware_type': ('image_meta_props', 'hw_firmware_type'), - 'hw_ipxe_boot': ('image_meta_props', 'hw_ipxe_boot'), - 'hw_machine_type': ('image_meta_props', 'hw_machine_type'), - 'hw_mem_encryption': ('image_meta_props', 'hw_mem_encryption'), - 'hw_mem_page_size': ('image_meta_props', 'hw_mem_page_size'), - 'hw_numa_nodes': ('image_meta_props', 'hw_numa_nodes'), - 'hw_numa_cpus': ('image_meta_props', 'hw_numa_cpus'), - 'hw_numa_mem': ('image_meta_props', 'hw_numa_mem'), - 'hw_pci_numa_affinity_policy': ('image_meta_props', - 'hw_pci_numa_affinity_policy'), - 'hw_pmu': ('image_meta_props', 'hw_pmu'), - 'hw_pointer_model': ('image_meta_props', 'hw_pointer_model'), - 'hw_qemu_guest_agent': ('image_meta_props', 'hw_qemu_guest_agent'), - 'hw_rescue_bus': ('image_meta_props', 'hw_rescue_bus'), - 'hw_rescue_device': ('image_meta_props', 'hw_rescue_device'), - 'hw_rng_model': ('image_meta_props', 'hw_rng_model'), - 'hw_serial_port_count': ('image_meta_props', 'hw_serial_port_count'), - 'hw_scsi_model': ('image_meta_props', 'hw_scsi_model'), - 'hw_time_hpet': ('image_meta_props', 'hw_time_hpet'), - 'hw_video_model': ('image_meta_props', 'hw_video_model'), - 'hw_video_ram': ('image_meta_props', 'hw_video_ram'), - 'hw_vif_model': ('image_meta_props', 'hw_vif_model'), - 'hw_vm_mode': ('image_meta_props', 'hw_vm_mode'), - 'hw_watchdog_action': ('image_meta_props', 'hw_watchdog_action'), - 'hw_vif_multiqueue_enabled': ('image_meta_props', - 'hw_vif_multiqueue_enabled'), - 'img_bittorrent': ('image_meta_props', 'img_bittorrent'), - 'img_bdm_v2': ('image_meta_props', 'img_bdm_v2'), - 'img_block_device_mapping': ('image_meta_props', - 'img_block_device_mapping'), - 'img_cache_in_nova': ('image_meta_props', 'img_cache_in_nova'), - 'img_compression_level': ('image_meta_props', 'img_compression_level'), - 'img_hv_requested_version': ('image_meta_props', - 'img_hv_requested_version'), - 'img_hv_type': ('image_meta_props', 'img_hv_type'), - 'img_config_drive': ('image_meta_props', 'img_config_drive'), - 'img_linked_clone': ('image_meta_props', 'img_linked_clone'), - 'img_mappings': ('image_meta_props', 'img_mappings'), - 'img_owner_id': ('image_meta_props', 'img_owner_id'), - 'img_root_device_name': ('image_meta_props', 'img_root_device_name'), - 'img_use_agent': ('image_meta_props', 'img_use_agent'), - 'img_version': ('image_meta_props', 'img_version'), - 'img_signature': ('image_meta_props', 'img_signature'), - 'img_signature_hash_method': ('image_meta_props', - 'img_signature_hash_method'), - 'img_signature_certificate_uuid': ('image_meta_props', - 'img_signature_certificate_uuid'), - 'img_signature_key_type': ('image_meta_props', - 'img_signature_key_type'), - 'img_hide_hypervisor_id': ('image_meta_props', - 'img_hide_hypervisor_id'), - 'os_admin_user': ('image_meta_props', 'os_admin_user'), - 'os_command_line': ('image_meta_props', 'os_command_line'), - 'os_distro': ('image_meta_props', 'os_distro'), - 'os_require_quiesce': ('image_meta_props', 'os_require_quiesce'), - 'os_secure_boot': ('image_meta_props', 'os_secure_boot'), - 'os_skip_agent_inject_files_at_boot': ( - 'image_meta_props', 'os_skip_agent_inject_files_at_boot'), - 'os_skip_agent_inject_ssh': ('image_meta_props', - 'os_skip_agent_inject_ssh'), - 'os_type': ('image_meta_props', 'os_type'), - 'traits_required': ('image_meta_props', 'traits_required') - } + k: ('image_meta_props', k) for k in image_meta.ImageMetaProps.fields} + # NOTE(efried): This logic currently relies on all of the fields of + # ImageMetaProps being initialized with no arguments. See the docstring. + # NOTE(efried): It's possible this could just be: + # fields = image_meta.ImageMetaProps.fields + # But it is not clear that OVO can tolerate the same *instance* of a type + # class being used in more than one place. fields = { - 'hw_architecture': fields.ArchitectureField(), - 'hw_auto_disk_config': fields.StringField(), - 'hw_boot_menu': fields.FlexibleBooleanField(), - 'hw_cdrom_bus': fields.DiskBusField(), - 'hw_cpu_cores': fields.IntegerField(), - 'hw_cpu_sockets': fields.IntegerField(), - 'hw_cpu_max_cores': fields.IntegerField(), - 'hw_cpu_max_sockets': fields.IntegerField(), - 'hw_cpu_max_threads': fields.IntegerField(), - 'hw_cpu_policy': fields.CPUAllocationPolicyField(), - 'hw_cpu_thread_policy': fields.CPUThreadAllocationPolicyField(), - 'hw_cpu_realtime_mask': fields.StringField(), - 'hw_cpu_threads': fields.IntegerField(), - 'hw_device_id': fields.IntegerField(), - 'hw_disk_bus': fields.DiskBusField(), - 'hw_disk_type': fields.StringField(), - 'hw_floppy_bus': fields.DiskBusField(), - 'hw_firmware_type': fields.FirmwareTypeField(), - 'hw_ipxe_boot': fields.FlexibleBooleanField(), - 'hw_machine_type': fields.StringField(), - 'hw_mem_encryption': fields.FlexibleBooleanField(), - 'hw_mem_page_size': fields.StringField(), - 'hw_numa_nodes': fields.IntegerField(), - 'hw_numa_cpus': fields.ListOfSetsOfIntegersField(), - 'hw_numa_mem': fields.ListOfIntegersField(), - 'hw_pci_numa_affinity_policy': fields.PCINUMAAffinityPolicyField(), - 'hw_pmu': fields.FlexibleBooleanField(), - 'hw_pointer_model': fields.PointerModelField(), - 'hw_qemu_guest_agent': fields.FlexibleBooleanField(), - 'hw_rescue_bus': fields.DiskBusField(), - 'hw_rescue_device': fields.BlockDeviceTypeField(), - 'hw_rng_model': fields.RNGModelField(), - 'hw_serial_port_count': fields.IntegerField(), - 'hw_scsi_model': fields.SCSIModelField(), - 'hw_time_hpet': fields.FlexibleBooleanField(), - 'hw_video_model': fields.VideoModelField(), - 'hw_video_ram': fields.IntegerField(), - 'hw_vif_model': fields.VIFModelField(), - 'hw_vm_mode': fields.VMModeField(), - 'hw_watchdog_action': fields.WatchdogActionField(), - 'hw_vif_multiqueue_enabled': fields.FlexibleBooleanField(), - 'img_bittorrent': fields.FlexibleBooleanField(), - 'img_bdm_v2': fields.FlexibleBooleanField(), - 'img_block_device_mapping': - fields.ListOfDictOfNullableStringsField(), - 'img_cache_in_nova': fields.FlexibleBooleanField(), - 'img_compression_level': fields.IntegerField(), - 'img_hv_requested_version': fields.VersionPredicateField(), - 'img_hv_type': fields.HVTypeField(), - 'img_config_drive': fields.ConfigDrivePolicyField(), - 'img_linked_clone': fields.FlexibleBooleanField(), - 'img_mappings': fields.ListOfDictOfNullableStringsField(), - 'img_owner_id': fields.StringField(), - 'img_root_device_name': fields.StringField(), - 'img_use_agent': fields.FlexibleBooleanField(), - 'img_version': fields.IntegerField(), - 'img_signature': fields.StringField(), - 'img_signature_hash_method': fields.ImageSignatureHashTypeField(), - 'img_signature_certificate_uuid': fields.UUIDField(), - 'img_signature_key_type': fields.ImageSignatureKeyTypeField(), - 'img_hide_hypervisor_id': fields.FlexibleBooleanField(), - 'os_admin_user': fields.StringField(), - 'os_command_line': fields.StringField(), - 'os_distro': fields.StringField(), - 'os_require_quiesce': fields.FlexibleBooleanField(), - 'os_secure_boot': fields.SecureBootField(), - 'os_skip_agent_inject_files_at_boot': fields.FlexibleBooleanField(), - 'os_skip_agent_inject_ssh': fields.FlexibleBooleanField(), - 'os_type': fields.OSTypeField(), - 'traits_required': fields.ListOfStringsField() - } + k: v.__class__() for k, v in image_meta.ImageMetaProps.fields.items()} def __init__(self, image_meta_props): super(ImageMetaPropsPayload, self).__init__() diff --git a/nova/objects/image_meta.py b/nova/objects/image_meta.py index de28041276a2..cbfdbc6bd857 100644 --- a/nova/objects/image_meta.py +++ b/nova/objects/image_meta.py @@ -175,6 +175,8 @@ class ImageMetaProps(base.NovaObject): # Version 1.23: Added 'hw_pmu' field # Version 1.24: Added 'hw_mem_encryption' field # Version 1.25: Added 'hw_pci_numa_affinity_policy' field + # NOTE(efried): When bumping this version, the version of + # ImageMetaPropsPayload must also be bumped. See its docstring for details. VERSION = '1.25' def obj_make_compatible(self, primitive, target_version): diff --git a/nova/tests/unit/notifications/objects/test_notification.py b/nova/tests/unit/notifications/objects/test_notification.py index f67806ddbbc8..f3b91ef21e58 100644 --- a/nova/tests/unit/notifications/objects/test_notification.py +++ b/nova/tests/unit/notifications/objects/test_notification.py @@ -20,7 +20,6 @@ from oslo_versionedobjects import fixture from nova import exception from nova.notifications.objects import base as notification -from nova.notifications.objects.image import ImageMetaPropsPayload from nova import objects from nova.objects import base from nova.objects import fields @@ -384,6 +383,10 @@ notification_object_data = { 'FlavorNotification': '1.0-a73147b93b520ff0061865849d3dfa56', 'FlavorPayload': '1.4-2e7011b8b4e59167fe8b7a0a81f0d452', 'ImageMetaPayload': '1.0-0e65beeacb3393beed564a57bc2bc989', + # NOTE(efried): ImageMetaPropsPayload is built dynamically from + # ImageMetaProps, so when you see a fail here for that reason, you must + # *also* bump the version of ImageMetaPropsPayload. See its docstring for + # more information. 'ImageMetaPropsPayload': '1.3-9c200c895932163a4e14e6bb385fa1e0', 'InstanceActionNotification': '1.0-a73147b93b520ff0061865849d3dfa56', 'InstanceActionPayload': '1.8-4fa3da9cbf0761f1f700ae578f36dc2f', @@ -480,33 +483,6 @@ class TestNotificationObjectVersions(test.NoDBTestCase): self.assertNotEqual(old_hash, new_hash) -class TestImageMetaPropsPayloadNotifications(test.NoDBTestCase): - def setUp(self): - super().setUp() - base.NovaObjectRegistry.register_notification_objects() - - def test_object_field_sync(self): - """Assert the fields of the ImageMetaPropsPayload object are - in sync with the fields of the ImageMetaProps object. - """ - self.assertEqual( - ImageMetaPropsPayload.fields.keys(), - objects.image_meta.ImageMetaProps.fields.keys()) - - def test_schema_field_sync(self): - """Assert the fields of the ImageMetaPropsPayload object are - in sync with schema definition. - """ - self.assertEqual( - ImageMetaPropsPayload.fields.keys(), - ImageMetaPropsPayload.SCHEMA.keys()) - - def test_schema(self): - """Assert the schema is generated correctly""" - for key, value in ImageMetaPropsPayload.SCHEMA.items(): - self.assertEqual(('image_meta_props', key), value) - - def get_extra_data(obj_class): extra_data = tuple()