summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDan Smith <danms@us.ibm.com>2013-06-04 16:49:40 -0700
committerDan Smith <danms@us.ibm.com>2013-06-07 11:43:37 -0700
commite95807e8cda4463d7c9a2aab918bd2bda3d3cf9e (patch)
tree83959867726d347e15774d7f5c5be8901c73ad2a
parent6fcf4133b49cfefa77151937dec4db097a85c349 (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.py65
-rw-r--r--nova/objects/instance_info_cache.py42
-rw-r--r--nova/objects/utils.py17
-rw-r--r--nova/tests/api/openstack/compute/contrib/test_server_start_stop.py2
-rw-r--r--nova/tests/objects/test_instance.py37
-rw-r--r--nova/tests/objects/test_instance_info_cache.py54
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