diff --git a/nova/objects/__init__.py b/nova/objects/__init__.py index be4aa0e563..c5d28f574b 100644 --- a/nova/objects/__init__.py +++ b/nova/objects/__init__.py @@ -50,6 +50,7 @@ def register_all(): __import__('nova.objects.instance_pci_requests') __import__('nova.objects.keypair') __import__('nova.objects.migrate_data') + __import__('nova.objects.virt_device_metadata') __import__('nova.objects.migration') __import__('nova.objects.migration_context') __import__('nova.objects.monitor_metric') diff --git a/nova/objects/fields.py b/nova/objects/fields.py index 59f40c52f2..f5d8915e37 100644 --- a/nova/objects/fields.py +++ b/nova/objects/fields.py @@ -13,6 +13,7 @@ # under the License. from oslo_versionedobjects import fields +import re import six # TODO(berrange) Temporary import for Arch class @@ -64,6 +65,7 @@ IPV4NetworkField = fields.IPV4NetworkField IPV6NetworkField = fields.IPV6NetworkField AutoTypedField = fields.AutoTypedField BaseEnumField = fields.BaseEnumField +MACAddressField = fields.MACAddressField # NOTE(danms): These are things we need to import for some of our @@ -645,6 +647,63 @@ class NonNegativeInteger(FieldType): return v +class AddressBase(FieldType): + @staticmethod + def coerce(obj, attr, value): + if re.match(obj.PATTERN, str(value)): + return str(value) + else: + raise ValueError(_('Value must match %s') % obj.PATTERN) + + +class PCIAddress(AddressBase): + PATTERN = '[a-f0-9]{4}:[a-f0-9]{2}:[a-f0-9]{2}.[a-f0-9]' + + @staticmethod + def coerce(obj, attr, value): + return AddressBase.coerce(PCIAddress, attr, value) + + +class USBAddress(AddressBase): + PATTERN = '[a-f0-9]+:[a-f0-9]+' + + @staticmethod + def coerce(obj, attr, value): + return AddressBase.coerce(USBAddress, attr, value) + + +class SCSIAddress(AddressBase): + PATTERN = '[a-f0-9]+:[a-f0-9]+:[a-f0-9]+:[a-f0-9]+' + + @staticmethod + def coerce(obj, attr, value): + return AddressBase.coerce(SCSIAddress, attr, value) + + +class IDEAddress(AddressBase): + PATTERN = '[0-1]:[0-1]' + + @staticmethod + def coerce(obj, attr, value): + return AddressBase.coerce(IDEAddress, attr, value) + + +class PCIAddressField(AutoTypedField): + AUTO_TYPE = PCIAddress() + + +class USBAddressField(AutoTypedField): + AUTO_TYPE = USBAddress() + + +class SCSIAddressField(AutoTypedField): + AUTO_TYPE = SCSIAddress() + + +class IDEAddressField(AutoTypedField): + AUTO_TYPE = IDEAddress() + + class ArchitectureField(BaseEnumField): AUTO_TYPE = Architecture() diff --git a/nova/objects/virt_device_metadata.py b/nova/objects/virt_device_metadata.py new file mode 100644 index 0000000000..3fb17e5250 --- /dev/null +++ b/nova/objects/virt_device_metadata.py @@ -0,0 +1,96 @@ +# Copyright (C) 2016, 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. + + +from nova.objects import base +from nova.objects import fields + + +@base.NovaObjectRegistry.register_if(False) +class DeviceBus(base.NovaObject): + VERSION = '1.0' + + +@base.NovaObjectRegistry.register +class PCIDeviceBus(DeviceBus): + VERSION = '1.0' + + fields = { + 'address': fields.PCIAddressField(), + } + + +@base.NovaObjectRegistry.register +class USBDeviceBus(DeviceBus): + VERSION = '1.0' + + fields = { + 'address': fields.USBAddressField(), + } + + +@base.NovaObjectRegistry.register +class SCSIDeviceBus(DeviceBus): + VERSION = '1.0' + + fields = { + 'address': fields.SCSIAddressField(), + } + + +@base.NovaObjectRegistry.register +class IDEDeviceBus(DeviceBus): + VERSION = '1.0' + + fields = { + 'address': fields.IDEAddressField(), + } + + +@base.NovaObjectRegistry.register +class DeviceMetadata(base.NovaObject): + VERSION = '1.0' + + fields = { + 'bus': fields.ObjectField("DeviceBus", subclasses=True), + 'tags': fields.ListOfStringsField(), + } + + +@base.NovaObjectRegistry.register +class NetworkInterfaceMetadata(DeviceMetadata): + VERSION = '1.0' + + fields = { + 'mac': fields.MACAddressField(), + } + + +@base.NovaObjectRegistry.register +class DiskMetadata(DeviceMetadata): + VERSION = '1.0' + + fields = { + 'serial': fields.StringField(nullable=True), + 'path': fields.StringField(nullable=True), + } + + +@base.NovaObjectRegistry.register +class DeviceMetadataList(base.ObjectListBase, base.NovaObject): + VERSION = '1.0' + fields = { + 'objects': fields.ListOfObjectsField('DeviceMetadata', + subclasses=True), + } diff --git a/nova/tests/unit/objects/test_fields.py b/nova/tests/unit/objects/test_fields.py index bb7243e8f8..94b83acb07 100644 --- a/nova/tests/unit/objects/test_fields.py +++ b/nova/tests/unit/objects/test_fields.py @@ -536,3 +536,70 @@ class TestNotificationAction(TestField): def test_stringify_invalid(self): self.assertRaises(ValueError, self.field.stringify, 'magic') + + +class TestPCIAddress(TestField): + def setUp(self): + super(TestPCIAddress, self).setUp() + self.field = fields.Field(fields.PCIAddressField()) + self.coerce_good_values = [('0000:00:02.0', '0000:00:02.0')] + self.coerce_bad_values = [ + '000:00:02.0', + '0000:0:02.0', + '0000:00:2.0', + '0000:00:02.', + '-000:00:02.0', + '0000:0-:02.0', + '0000:00:-2.0', + '0000:00:02.-', + '000000:02.0', + '0000:0:02.0', + '0000:00:020', + ] + self.to_primitive_values = self.coerce_good_values + self.from_primitive_values = self.coerce_good_values + + +class TestUSBAddress(TestField): + def setUp(self): + super(TestUSBAddress, self).setUp() + self.field = fields.Field(fields.USBAddressField()) + self.coerce_good_values = [('0:0', '0:0')] + self.coerce_bad_values = [ + '00', + '0:', + '0.0', + '-.0', + ] + self.to_primitive_values = self.coerce_good_values + self.from_primitive_values = self.coerce_good_values + + +class TestSCSIAddress(TestField): + def setUp(self): + super(TestSCSIAddress, self).setUp() + self.field = fields.Field(fields.SCSIAddressField()) + self.coerce_good_values = [('1:0:2:0', '1:0:2:0')] + self.coerce_bad_values = [ + '1:0:2', + '-:0:2:0', + '1:-:2:0', + '1:0:-:0', + '1:0:2:-', + ] + self.to_primitive_values = self.coerce_good_values + self.from_primitive_values = self.coerce_good_values + + +class TestIDEAddress(TestField): + def setUp(self): + super(TestIDEAddress, self).setUp() + self.field = fields.Field(fields.IDEAddressField()) + self.coerce_good_values = [('0:0', '0:0')] + self.coerce_bad_values = [ + '0:2', + '00', + '0', + ] + self.to_primitive_values = self.coerce_good_values + self.from_primitive_values = self.coerce_good_values diff --git a/nova/tests/unit/objects/test_objects.py b/nova/tests/unit/objects/test_objects.py index aae2f7e3a5..77cfb93891 100644 --- a/nova/tests/unit/objects/test_objects.py +++ b/nova/tests/unit/objects/test_objects.py @@ -36,6 +36,7 @@ from nova import objects from nova.objects import base from nova.objects import fields from nova.objects import notification +from nova.objects import virt_device_metadata from nova import test from nova.tests import fixtures as nova_fixtures from nova.tests.unit import fake_notifier @@ -1111,6 +1112,9 @@ object_data = { 'ComputeNodeList': '1.14-3b6f4f5ade621c40e70cb116db237844', 'DNSDomain': '1.0-7b0b2dab778454b6a7b6c66afe163a1a', 'DNSDomainList': '1.0-4ee0d9efdfd681fed822da88376e04d2', + 'DeviceMetadata': '1.0-04eb8fd218a49cbc3b1e54b774d179f7', + 'DeviceMetadataList': '1.0-15ecf022a68ddbb8c2a6739cfc9f8f5e', + 'DiskMetadata': '1.0-e7a0f1ccccf10d26a76b28e7492f3788', 'EC2Ids': '1.0-474ee1094c7ec16f8ce657595d8c49d9', 'EC2InstanceMapping': '1.0-a4556eb5c5e94c045fe84f49cf71644f', 'EC2SnapshotMapping': '1.0-47e7ddabe1af966dce0cfd0ed6cd7cd1', @@ -1125,6 +1129,7 @@ object_data = { 'HostMapping': '1.0-1a3390a696792a552ab7bd31a77ba9ac', 'HyperVLiveMigrateData': '1.0-0b868dd6228a09c3f3e47016dddf6a1c', 'HVSpec': '1.2-db672e73304da86139086d003f3977e7', + 'IDEDeviceBus': '1.0-29d4c9f27ac44197f01b6ac1b7e16502', 'ImageMeta': '1.8-642d1b2eb3e880a367f37d72dd76162d', 'ImageMetaProps': '1.12-6a132dee47931447bf86c03c7006d96c', 'Instance': '2.1-416fdd0dfc33dfa12ff2cfdd8cc32e17', @@ -1162,10 +1167,12 @@ object_data = { 'NUMATopology': '1.2-c63fad38be73b6afd04715c9c1b29220', 'NUMATopologyLimits': '1.0-9463e0edd40f64765ae518a539b9dfd2', 'Network': '1.2-a977ab383aa462a479b2fae8211a5dde', + 'NetworkInterfaceMetadata': '1.0-99a9574d086feb5ad45cd04a34855647', 'NetworkList': '1.2-69eca910d8fa035dfecd8ba10877ee59', 'NetworkRequest': '1.1-7a3e4ca2ce1e7b62d8400488f2f2b756', 'NetworkRequestList': '1.1-15ecf022a68ddbb8c2a6739cfc9f8f5e', 'PciDevice': '1.5-0d5abe5c91645b8469eb2a93fc53f932', + 'PCIDeviceBus': '1.0-2b891cb77e42961044689f3dc2718995', 'PciDeviceList': '1.3-52ff14355491c8c580bdc0ba34c26210', 'PciDevicePool': '1.1-3f5ddc3ff7bfa14da7f6c7e9904cc000', 'PciDevicePoolList': '1.1-15ecf022a68ddbb8c2a6739cfc9f8f5e', @@ -1176,6 +1183,7 @@ object_data = { 'S3ImageMapping': '1.0-7dd7366a890d82660ed121de9092276e', 'SchedulerLimits': '1.0-249c4bd8e62a9b327b7026b7f19cc641', 'SchedulerRetries': '1.1-3c9c8b16143ebbb6ad7030e999d14cc0', + 'SCSIDeviceBus': '1.0-61c1e89a00901069ab1cf2991681533b', 'SecurityGroup': '1.1-0e1b9ba42fe85c13c1437f8b74bdb976', 'SecurityGroupList': '1.0-dc8bbea01ba09a2edb6e5233eae85cbc', 'SecurityGroupRule': '1.1-ae1da17b79970012e8536f88cb3c6b29', @@ -1188,6 +1196,7 @@ object_data = { 'TaskLogList': '1.0-cc8cce1af8a283b9d28b55fcd682e777', 'Tag': '1.1-8b8d7d5b48887651a0e01241672e2963', 'TagList': '1.1-55231bdb671ecf7641d6a2e9109b5d8e', + 'USBDeviceBus': '1.0-e4c7dd6032e46cd74b027df5eb2d4750', 'VirtCPUFeature': '1.0-3310718d8c72309259a6e39bdefe83ee', 'VirtCPUModel': '1.0-6a5cc9f322729fc70ddc6733bacd57d3', 'VirtCPUTopology': '1.0-fc694de72e20298f7c6bab1083fd4563', @@ -1243,6 +1252,13 @@ class TestObjectVersions(test.NoDBTestCase): self.assertNotEqual(old_hash, new_hash) def test_obj_make_compatible(self): + # NOTE(danms): This is normally not registered because it is just a + # base class. However, the test fixture below requires it to be + # in the registry so that it can verify backports based on its + # children. So, register it here, which will be reverted after the + # cleanUp for this (and all) tests is run. + base.NovaObjectRegistry.register(virt_device_metadata.DeviceBus) + # Iterate all object classes and verify that we can run # obj_make_compatible with every older version than current. # This doesn't actually test the data conversions, but it at least