diff options
| author | Dan Smith <danms@us.ibm.com> | 2013-06-04 16:49:40 -0700 |
|---|---|---|
| committer | Dan Smith <danms@us.ibm.com> | 2013-06-07 11:43:37 -0700 |
| commit | e95807e8cda4463d7c9a2aab918bd2bda3d3cf9e (patch) | |
| tree | 83959867726d347e15774d7f5c5be8901c73ad2a | |
| parent | 6fcf4133b49cfefa77151937dec4db097a85c349 (diff) | |
Add Instance.info_cache
This adds our first nested object: the InstanceInfoCache at
Instance.info_cache.
Related to blueprint unified-object-model
Change-Id: I2dd8727bbe38eadd080e9ea1ef9feabc2fa50520
| -rw-r--r-- | nova/objects/instance.py | 65 | ||||
| -rw-r--r-- | nova/objects/instance_info_cache.py | 42 | ||||
| -rw-r--r-- | nova/objects/utils.py | 17 | ||||
| -rw-r--r-- | nova/tests/api/openstack/compute/contrib/test_server_start_stop.py | 2 | ||||
| -rw-r--r-- | nova/tests/objects/test_instance.py | 37 | ||||
| -rw-r--r-- | nova/tests/objects/test_instance_info_cache.py | 54 |
6 files changed, 196 insertions, 21 deletions
diff --git a/nova/objects/instance.py b/nova/objects/instance.py index 58c581542..18fdfb94d 100644 --- a/nova/objects/instance.py +++ b/nova/objects/instance.py @@ -15,6 +15,7 @@ from nova import db from nova import notifications from nova.objects import base +from nova.objects import instance_info_cache from nova.objects import utils as obj_utils from nova import utils @@ -24,7 +25,17 @@ from oslo.config import cfg CONF = cfg.CONF +# These are fields that can be specified as expected_attrs +INSTANCE_OPTIONAL_FIELDS = ['metadata', 'system_metadata'] +# These are fields that are always joined by the db right now +INSTANCE_IMPLIED_FIELDS = ['info_cache'] + + class Instance(base.NovaObject): + # Version 1.0: Initial version + # Version 1.1: Added info_cache + VERSION = '1.1' + fields = { 'id': int, @@ -94,6 +105,9 @@ class Instance(base.NovaObject): 'metadata': dict, 'system_metadata': dict, + 'info_cache': obj_utils.nested_object_or_none( + instance_info_cache.InstanceInfoCache) + } @property @@ -132,11 +146,15 @@ class Instance(base.NovaObject): _attr_scheduled_at_to_primitive = obj_utils.dt_serializer('scheduled_at') _attr_launched_at_to_primitive = obj_utils.dt_serializer('launched_at') _attr_terminated_at_to_primitive = obj_utils.dt_serializer('terminated_at') + _attr_info_cache_to_primitive = obj_utils.obj_serializer('info_cache') _attr_scheduled_at_from_primitive = obj_utils.dt_deserializer _attr_launched_at_from_primitive = obj_utils.dt_deserializer _attr_terminated_at_from_primitive = obj_utils.dt_deserializer + def _attr_info_cache_from_primitive(self, val): + return base.NovaObject.obj_from_primitive(val) + @staticmethod def _from_db_object(instance, db_inst, expected_attrs=None): """Method to help with migration to objects. @@ -147,7 +165,7 @@ class Instance(base.NovaObject): expected_attrs = [] # Most of the field names match right now, so be quick for field in instance.fields: - if field in ['metadata', 'system_metadata']: + if field in INSTANCE_OPTIONAL_FIELDS + INSTANCE_IMPLIED_FIELDS: continue elif field == 'deleted': instance.deleted = db_inst['deleted'] == db_inst['id'] @@ -159,6 +177,13 @@ class Instance(base.NovaObject): if 'system_metadata' in expected_attrs: instance['system_metadata'] = utils.metadata_to_dict( db_inst['system_metadata']) + # NOTE(danms): info_cache and security_groups are almost always joined + # in the DB layer right now, so check to see if they're filled instead + # of looking at expected_attrs + if db_inst['info_cache']: + instance['info_cache'] = instance_info_cache.InstanceInfoCache() + instance_info_cache.InstanceInfoCache._from_db_object( + instance['info_cache'], db_inst['info_cache']) instance.obj_reset_changes() return instance @@ -174,6 +199,9 @@ class Instance(base.NovaObject): columns_to_join.append('metadata') if 'system_metadata' in expected_attrs: columns_to_join.append('system_metadata') + # NOTE(danms): The DB API currently always joins info_cache and + # security_groups for get operations, so don't add them to the + # list of columns db_inst = db.instance_get_by_uuid(context, uuid, columns_to_join) @@ -193,28 +221,33 @@ class Instance(base.NovaObject): """ updates = {} changes = self.obj_what_changed() - for field in changes: - updates[field] = self[field] + for field in self.fields: + if (hasattr(self, base.get_attrname(field)) and + isinstance(self[field], base.NovaObject)): + self[field].save(context) + elif field in changes: + updates[field] = self[field] if expected_task_state is not None: updates['expected_task_state'] = expected_task_state - old_ref, inst_ref = db.instance_update_and_get_original(context, - self.uuid, - updates) - - expected_attrs = [] - for attr in ('metadata', 'system_metadata'): - if hasattr(self, base.get_attrname(attr)): - expected_attrs.append(attr) - Instance._from_db_object(self, inst_ref, expected_attrs) - if 'vm_state' in changes or 'task_state' in changes: - notifications.send_update(context, old_ref, inst_ref) + + if updates: + old_ref, inst_ref = db.instance_update_and_get_original(context, + self.uuid, + updates) + expected_attrs = [] + for attr in INSTANCE_OPTIONAL_FIELDS: + if hasattr(self, base.get_attrname(attr)): + expected_attrs.append(attr) + Instance._from_db_object(self, inst_ref, expected_attrs) + if 'vm_state' in changes or 'task_state' in changes: + notifications.send_update(context, old_ref, inst_ref) self.obj_reset_changes() @base.remotable def refresh(self, context): extra = [] - for field in ['system_metadata', 'metadata']: + for field in INSTANCE_OPTIONAL_FIELDS: if hasattr(self, base.get_attrname(field)): extra.append(field) current = self.__class__.get_by_uuid(context, uuid=self.uuid, @@ -230,6 +263,8 @@ class Instance(base.NovaObject): extra.append('system_metadata') elif attrname == 'metadata': extra.append('metadata') + elif attrname == 'info_cache': + extra.append('info_cache') if not extra: raise Exception('Cannot load "%s" from instance' % attrname) diff --git a/nova/objects/instance_info_cache.py b/nova/objects/instance_info_cache.py new file mode 100644 index 000000000..6b46559ed --- /dev/null +++ b/nova/objects/instance_info_cache.py @@ -0,0 +1,42 @@ +# Copyright 2013 IBM Corp. +# +# 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 import db +from nova.objects import base + + +class InstanceInfoCache(base.NovaObject): + fields = { + 'instance_uuid': str, + 'network_info': str, + } + + @staticmethod + def _from_db_object(info_cache, db_obj): + info_cache.instance_uuid = db_obj['instance_uuid'] + info_cache.network_info = db_obj['network_info'] + info_cache.obj_reset_changes() + return info_cache + + @base.remotable_classmethod + def get_by_instance_uuid(cls, context, instance_uuid): + db_obj = db.instance_info_cache_get(context, instance_uuid) + return InstanceInfoCache._from_db_object(cls(), db_obj) + + @base.remotable + def save(self, context): + if 'network_info' in self.obj_what_changed(): + db.instance_info_cache_update(context, self.instance_uuid, + {'network_info': self.network_info}) + self.obj_reset_changes() diff --git a/nova/objects/utils.py b/nova/objects/utils.py index 21ef6faeb..e79b36e7e 100644 --- a/nova/objects/utils.py +++ b/nova/objects/utils.py @@ -70,6 +70,14 @@ def ip_or_none(version): return validator +def nested_object_or_none(objclass): + def validator(val, objclass=objclass): + if val is None or isinstance(val, objclass): + return val + raise ValueError('An object of class %s is required here' % objclass) + return validator + + def dt_serializer(name): """Return a datetime serializer for a named attribute.""" def serializer(self, name=name): @@ -86,3 +94,12 @@ def dt_deserializer(instance, val): return None else: return timeutils.parse_isotime(val) + + +def obj_serializer(name): + def serializer(self, name=name): + if getattr(self, name) is not None: + return getattr(self, name).obj_to_primitive() + else: + return None + return serializer diff --git a/nova/tests/api/openstack/compute/contrib/test_server_start_stop.py b/nova/tests/api/openstack/compute/contrib/test_server_start_stop.py index 31b832084..05b83131c 100644 --- a/nova/tests/api/openstack/compute/contrib/test_server_start_stop.py +++ b/nova/tests/api/openstack/compute/contrib/test_server_start_stop.py @@ -29,6 +29,8 @@ def fake_instance_get(self, context, instance_id): result['deleted_at'] = None result['updated_at'] = None result['deleted'] = 0 + result['info_cache'] = {'network_info': 'foo', + 'instance_uuid': result['uuid']} return result diff --git a/nova/tests/objects/test_instance.py b/nova/tests/objects/test_instance.py index 54e010a8a..a9238a924 100644 --- a/nova/tests/objects/test_instance.py +++ b/nova/tests/objects/test_instance.py @@ -18,6 +18,7 @@ import netaddr from nova import context from nova import db +from nova.objects import base from nova.objects import instance from nova.openstack.common import timeutils from nova.tests.api.openstack import fakes @@ -39,6 +40,7 @@ class _TestInstanceObject(object): fake_instance['launched_at'].replace( tzinfo=iso8601.iso8601.Utc(), microsecond=0)) fake_instance['deleted'] = False + fake_instance['info_cache']['instance_uuid'] = fake_instance['uuid'] return fake_instance def test_datetime_deserialization(self): @@ -90,8 +92,9 @@ class _TestInstanceObject(object): self.mox.ReplayAll() inst = instance.Instance.get_by_uuid(ctxt, uuid='uuid') # Make sure these weren't loaded - self.assertFalse(hasattr(inst, '_metadata')) - self.assertFalse(hasattr(inst, '_system_metadata')) + for attr in instance.INSTANCE_OPTIONAL_FIELDS: + attrname = base.get_attrname(attr) + self.assertFalse(hasattr(inst, attrname)) self.assertRemotes() def test_get_with_expected(self): @@ -99,12 +102,13 @@ class _TestInstanceObject(object): self.mox.StubOutWithMock(db, 'instance_get_by_uuid') db.instance_get_by_uuid( ctxt, 'uuid', - ['metadata', 'system_metadata']).AndReturn(self.fake_instance) + instance.INSTANCE_OPTIONAL_FIELDS).AndReturn(self.fake_instance) self.mox.ReplayAll() inst = instance.Instance.get_by_uuid( - ctxt, 'uuid', expected_attrs=['metadata', 'system_metadata']) - self.assertTrue(hasattr(inst, '_metadata')) - self.assertTrue(hasattr(inst, '_system_metadata')) + ctxt, 'uuid', expected_attrs=instance.INSTANCE_OPTIONAL_FIELDS) + for attr in instance.INSTANCE_OPTIONAL_FIELDS: + attrname = base.get_attrname(attr) + self.assertTrue(hasattr(inst, attrname)) self.assertRemotes() def test_load(self): @@ -166,6 +170,7 @@ class _TestInstanceObject(object): fake_uuid = fake_inst['uuid'] self.mox.StubOutWithMock(db, 'instance_get_by_uuid') self.mox.StubOutWithMock(db, 'instance_update_and_get_original') + self.mox.StubOutWithMock(db, 'instance_info_cache_update') db.instance_get_by_uuid(ctxt, fake_uuid, []).AndReturn(fake_inst) db.instance_update_and_get_original( ctxt, fake_uuid, {'user_data': 'foo'}).AndReturn( @@ -187,6 +192,26 @@ class _TestInstanceObject(object): # NOTE(danms): Make sure it's actually a bool self.assertEqual(inst.deleted, True) + def test_with_info_cache(self): + ctxt = context.get_admin_context() + fake_inst = dict(self.fake_instance) + fake_uuid = fake_inst['uuid'] + fake_inst['info_cache'] = {'network_info': 'foo', + 'instance_uuid': fake_uuid} + self.mox.StubOutWithMock(db, 'instance_get_by_uuid') + self.mox.StubOutWithMock(db, 'instance_update_and_get_original') + self.mox.StubOutWithMock(db, 'instance_info_cache_update') + db.instance_get_by_uuid(ctxt, fake_uuid, []).AndReturn(fake_inst) + db.instance_info_cache_update(ctxt, fake_uuid, + {'network_info': 'bar'}) + self.mox.ReplayAll() + inst = instance.Instance.get_by_uuid(ctxt, fake_uuid) + self.assertEqual(inst.info_cache.network_info, + fake_inst['info_cache']['network_info']) + self.assertEqual(inst.info_cache.instance_uuid, fake_uuid) + inst.info_cache.network_info = 'bar' + inst.save() + class TestInstanceObject(test_objects._LocalTest, _TestInstanceObject): diff --git a/nova/tests/objects/test_instance_info_cache.py b/nova/tests/objects/test_instance_info_cache.py new file mode 100644 index 000000000..74362d178 --- /dev/null +++ b/nova/tests/objects/test_instance_info_cache.py @@ -0,0 +1,54 @@ +# Copyright 2013 IBM Corp. +# +# 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 import context +from nova import db +from nova.objects import instance_info_cache +from nova.tests.objects import test_objects + + +class _TestInstanceInfoCacheObject(object): + def test_get_by_instance_uuid(self): + ctxt = context.get_admin_context() + self.mox.StubOutWithMock(db, 'instance_info_cache_get') + db.instance_info_cache_get(ctxt, 'fake-uuid').AndReturn( + {'instance_uuid': 'fake-uuid', 'network_info': 'foo'}) + self.mox.ReplayAll() + obj = instance_info_cache.InstanceInfoCache.get_by_instance_uuid( + ctxt, 'fake-uuid') + self.assertEqual(obj.instance_uuid, 'fake-uuid') + self.assertEqual(obj.network_info, 'foo') + self.assertRemotes() + + def test_save(self): + ctxt = context.get_admin_context() + self.mox.StubOutWithMock(db, 'instance_info_cache_update') + db.instance_info_cache_update(ctxt, 'fake-uuid', + {'network_info': 'foo'}) + self.mox.ReplayAll() + obj = instance_info_cache.InstanceInfoCache() + obj._context = ctxt + obj.instance_uuid = 'fake-uuid' + obj.network_info = 'foo' + obj.save() + + +class TestInstanceInfoCacheObject(test_objects._LocalTest, + _TestInstanceInfoCacheObject): + pass + + +class TestInstanceInfoCacheObjectRemote(test_objects._RemoteTest, + _TestInstanceInfoCacheObject): + pass |
