diff --git a/nova/compute/cpumodel.py b/nova/compute/cpumodel.py new file mode 100644 index 0000000000..5f531fbe63 --- /dev/null +++ b/nova/compute/cpumodel.py @@ -0,0 +1,45 @@ +# 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. + +POLICY_FORCE = 'force' +POLICY_REQUIRE = 'require' +POLICY_OPTIONAL = 'optional' +POLICY_DISABLE = 'disable' +POLICY_FORBID = 'forbid' + +ALL_POLICIES = [ + POLICY_FORCE, + POLICY_REQUIRE, + POLICY_OPTIONAL, + POLICY_DISABLE, + POLICY_FORBID, +] + +MODE_CUSTOM = 'custom' +MODE_HOST_MODEL = 'host-model' +MODE_HOST_PASSTHROUGH = 'host-passthrough' + +ALL_CPUMODES = [ + MODE_CUSTOM, + MODE_HOST_MODEL, + MODE_HOST_PASSTHROUGH, +] + +MATCH_MINIMUM = 'minimum' +MATCH_EXACT = 'exact' +MATCH_STRICT = 'strict' + +ALL_MATCHES = [ + MATCH_MINIMUM, + MATCH_EXACT, + MATCH_STRICT, +] diff --git a/nova/objects/__init__.py b/nova/objects/__init__.py index 6ab4ee0fad..27c6ea20e7 100644 --- a/nova/objects/__init__.py +++ b/nova/objects/__init__.py @@ -55,5 +55,6 @@ def register_all(): __import__('nova.objects.security_group') __import__('nova.objects.security_group_rule') __import__('nova.objects.service') + __import__('nova.objects.vcpu_model') __import__('nova.objects.virt_cpu_topology') __import__('nova.objects.virtual_interface') diff --git a/nova/objects/vcpu_model.py b/nova/objects/vcpu_model.py new file mode 100644 index 0000000000..fbdf6eebc4 --- /dev/null +++ b/nova/objects/vcpu_model.py @@ -0,0 +1,75 @@ +# 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 oslo.serialization import jsonutils + +from nova.compute import arch +from nova.compute import cpumodel +from nova import db +from nova.objects import base +from nova.objects import fields + + +class VirtCPUModel(base.NovaObject): + # Version 1.0: Initial version + VERSION = '1.0' + + fields = { + 'arch': fields.EnumField(nullable=True, + valid_values=arch.ALL), + 'vendor': fields.StringField(nullable=True), + 'topology': fields.ObjectField('VirtCPUTopology', + nullable=True), + 'features': fields.ListOfObjectsField("VirtCPUFeature", + default=[]), + 'mode': fields.EnumField(nullable=True, + valid_values=cpumodel.ALL_CPUMODES), + 'model': fields.StringField(nullable=True), + 'match': fields.EnumField(nullable=True, + valid_values=cpumodel.ALL_MATCHES), + } + + obj_relationships = { + 'topology': [('1.0', '1.0')], + 'features': [('1.0', '1.0')], + } + + def obj_load_attr(self, attrname): + setattr(self, attrname, None) + + def to_json(self): + return jsonutils.dumps(self.obj_to_primitive()) + + @classmethod + def from_json(cls, jsonstr): + return cls.obj_from_primitive(jsonutils.loads(jsonstr)) + + @base.remotable_classmethod + def get_by_instance_uuid(cls, context, instance_uuid): + db_extra = db.instance_extra_get_by_instance_uuid( + context, instance_uuid, columns=['vcpu_model']) + if not db_extra or not db_extra['vcpu_model']: + return None + return cls.obj_from_primitive(jsonutils.loads(db_extra['vcpu_model'])) + + +class VirtCPUFeature(base.NovaObject): + VERSION = VirtCPUModel.VERSION + + fields = { + 'policy': fields.EnumField(nullable=True, + valid_values=cpumodel.ALL_POLICIES), + 'name': fields.StringField(nullable=False), + } + + def obj_load_attr(self, attrname): + setattr(self, attrname, None) diff --git a/nova/tests/unit/objects/test_objects.py b/nova/tests/unit/objects/test_objects.py index 9f31d89c7a..12d34fe234 100644 --- a/nova/tests/unit/objects/test_objects.py +++ b/nova/tests/unit/objects/test_objects.py @@ -1182,6 +1182,8 @@ object_data = { 'TestSubclassedObject': '1.6-87177ccbefd7a740a9e261f958e15b00', 'VirtualInterface': '1.0-10fdac4c704102b6d57d6936d6d790d2', 'VirtualInterfaceList': '1.0-accbf02628a8063c1d885077a2bf49b6', + 'VirtCPUFeature': '1.0-3cac8c77d84a632ba79da01a4b87afb9', + 'VirtCPUModel': '1.0-ae051080026849eddf7179e353673756', 'VirtCPUTopology': '1.0-fc694de72e20298f7c6bab1083fd4563', } @@ -1205,7 +1207,8 @@ object_relationships = { 'MyObj': {'MyOwnedObject': '1.0'}, 'SecurityGroupRule': {'SecurityGroup': '1.1'}, 'Service': {'ComputeNode': '1.10'}, - 'TestSubclassedObject': {'MyOwnedObject': '1.0'} + 'TestSubclassedObject': {'MyOwnedObject': '1.0'}, + 'VirtCPUModel': {'VirtCPUTopology': '1.0'}, } diff --git a/nova/tests/unit/objects/test_vcpu_model.py b/nova/tests/unit/objects/test_vcpu_model.py new file mode 100644 index 0000000000..0dde852fc5 --- /dev/null +++ b/nova/tests/unit/objects/test_vcpu_model.py @@ -0,0 +1,102 @@ +# 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.compute import arch +from nova.compute import cpumodel +from nova import objects +from nova.tests.unit.objects import test_objects + +fake_cpu_model_feature = { + 'policy': cpumodel.POLICY_REQUIRE, + 'name': 'sse2', +} + +fake_cpu_model_feature_obj = objects.VirtCPUFeature( + **fake_cpu_model_feature) + +fake_vcpumodel_dict = { + 'arch': arch.I686, + 'vendor': 'fake-vendor', + 'match': cpumodel.MATCH_EXACT, + 'topology': objects.VirtCPUTopology(sockets=1, cores=1, threads=1), + 'features': [fake_cpu_model_feature_obj], + 'mode': cpumodel.MODE_HOST_MODEL, + 'model': 'fake-model', +} +fake_vcpumodel = objects.VirtCPUModel(**fake_vcpumodel_dict) + + +class _TestVirtCPUFeatureObj(object): + def test_policy_limitation(self): + obj = objects.VirtCPUFeature() + self.assertRaises(ValueError, setattr, obj, 'policy', 'foo') + + +class TestVirtCPUFeatureObj(test_objects._LocalTest, + _TestVirtCPUFeatureObj): + pass + + +class TestRemoteVirtCPUFeatureObj(test_objects._LocalTest, + _TestVirtCPUFeatureObj): + pass + + +class _TestVirtCPUModel(object): + def test_create(self): + model = objects.VirtCPUModel(**fake_vcpumodel_dict) + self.assertEqual(fake_vcpumodel_dict['model'], model.model) + self.assertEqual(fake_vcpumodel_dict['topology']['sockets'], + model.topology.sockets) + feature = model.features[0] + self.assertEqual(fake_cpu_model_feature['policy'], + feature.policy) + + def test_defaults(self): + model = objects.VirtCPUModel() + self.assertIsNone(model.mode) + self.assertIsNone(model.model) + self.assertIsNone(model.vendor) + self.assertIsNone(model.arch) + self.assertIsNone(model.match) + self.assertEqual([], model.features) + self.assertIsNone(model.topology) + + def test_arch_field(self): + model = objects.VirtCPUModel(**fake_vcpumodel_dict) + self.assertRaises(ValueError, setattr, model, 'arch', 'foo') + + def test_serialize(self): + modelin = objects.VirtCPUModel(**fake_vcpumodel_dict) + modelout = objects.VirtCPUModel.from_json(modelin.to_json()) + + self.assertEqual(modelin.mode, modelout.mode) + self.assertEqual(modelin.model, modelout.model) + self.assertEqual(modelin.vendor, modelout.vendor) + self.assertEqual(modelin.arch, modelout.arch) + self.assertEqual(modelin.match, modelout.match) + self.assertEqual(modelin.features[0].policy, + modelout.features[0].policy) + self.assertEqual(modelin.features[0].name, modelout.features[0].name) + self.assertEqual(modelin.topology.sockets, modelout.topology.sockets) + self.assertEqual(modelin.topology.cores, modelout.topology.cores) + self.assertEqual(modelin.topology.threads, modelout.topology.threads) + + +class TestVirtCPUModel(test_objects._LocalTest, + _TestVirtCPUModel): + pass + + +class TestRemoteVirtCPUModel(test_objects._LocalTest, + _TestVirtCPUModel): + pass