Merge "Add instance object"

This commit is contained in:
Jenkins 2013-05-30 15:01:43 +00:00 committed by Gerrit Code Review
commit 0e760b3e94
2 changed files with 424 additions and 0 deletions

238
nova/objects/instance.py Normal file
View File

@ -0,0 +1,238 @@
# 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 import notifications
from nova.objects import base
from nova.objects import utils as obj_utils
from nova import utils
from oslo.config import cfg
CONF = cfg.CONF
class Instance(base.NovaObject):
fields = {
'id': int,
'user_id': obj_utils.str_or_none,
'project_id': obj_utils.str_or_none,
'image_ref': obj_utils.str_or_none,
'kernel_id': obj_utils.str_or_none,
'ramdisk_id': obj_utils.str_or_none,
'hostname': obj_utils.str_or_none,
'launch_index': obj_utils.int_or_none,
'key_name': obj_utils.str_or_none,
'key_data': obj_utils.str_or_none,
'power_state': obj_utils.int_or_none,
'vm_state': obj_utils.str_or_none,
'task_state': obj_utils.str_or_none,
'memory_mb': obj_utils.int_or_none,
'vcpus': obj_utils.int_or_none,
'root_gb': obj_utils.int_or_none,
'ephemeral_gb': obj_utils.int_or_none,
'host': obj_utils.str_or_none,
'node': obj_utils.str_or_none,
'instance_type_id': obj_utils.int_or_none,
'user_data': obj_utils.str_or_none,
'reservation_id': obj_utils.str_or_none,
'scheduled_at': obj_utils.datetime_or_none,
'launched_at': obj_utils.datetime_or_none,
'terminated_at': obj_utils.datetime_or_none,
'availability_zone': obj_utils.str_or_none,
'display_name': obj_utils.str_or_none,
'display_description': obj_utils.str_or_none,
'launched_on': obj_utils.str_or_none,
'locked': bool,
'os_type': obj_utils.str_or_none,
'architecture': obj_utils.str_or_none,
'vm_mode': obj_utils.str_or_none,
'uuid': obj_utils.str_or_none,
'root_device_name': obj_utils.str_or_none,
'default_ephemeral_device': obj_utils.str_or_none,
'default_swap_device': obj_utils.str_or_none,
'config_drive': obj_utils.str_or_none,
'access_ip_v4': obj_utils.ip_or_none(4),
'access_ip_v6': obj_utils.ip_or_none(6),
'auto_disk_config': bool,
'progress': obj_utils.int_or_none,
'shutdown_terminate': bool,
'disable_terminate': bool,
'cell_name': obj_utils.str_or_none,
'metadata': dict,
'system_metadata': dict,
}
@property
def name(self):
try:
base_name = CONF.instance_name_template % self.id
except TypeError:
# Support templates like "uuid-%(uuid)s", etc.
info = {}
# NOTE(russellb): Don't use self.iteritems() here, as it will
# result in infinite recursion on the name property.
for key in self.fields:
# prevent recursion if someone specifies %(name)s
# %(name)s will not be valid.
if key == 'name':
continue
info[key] = self[key]
try:
base_name = CONF.instance_name_template % info
except KeyError:
base_name = self.uuid
return base_name
def _attr_access_ip_v4_to_primitive(self):
if self.access_ip_v4 is not None:
return str(self.access_ip_v4)
else:
return None
def _attr_access_ip_v6_to_primitive(self):
if self.access_ip_v6 is not None:
return str(self.access_ip_v6)
else:
return None
_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_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
@staticmethod
def _from_db_object(instance, db_inst, expected_attrs=None):
"""Method to help with migration to objects.
Converts a database entity to a formal object.
"""
if expected_attrs is None:
expected_attrs = []
# Most of the field names match right now, so be quick
for field in instance.fields:
if field in ['metadata', 'system_metadata']:
continue
instance[field] = db_inst[field]
if 'metadata' in expected_attrs:
instance['metadata'] = utils.metadata_to_dict(db_inst['metadata'])
if 'system_metadata' in expected_attrs:
instance['system_metadata'] = utils.metadata_to_dict(
db_inst['system_metadata'])
instance.obj_reset_changes()
return instance
@base.remotable_classmethod
def get_by_uuid(cls, context, uuid=None, expected_attrs=None):
if expected_attrs is None:
expected_attrs = []
# Construct DB-specific columns from generic expected_attrs
columns_to_join = []
if 'metadata' in expected_attrs:
columns_to_join.append('metadata')
if 'system_metadata' in expected_attrs:
columns_to_join.append('system_metadata')
db_inst = db.instance_get_by_uuid(context, uuid,
columns_to_join)
return Instance._from_db_object(cls(), db_inst, expected_attrs)
@base.remotable
def save(self, context, expected_task_state=None):
"""Save updates to this instance
Column-wise updates will be made based on the result of
self.what_changed(). If expected_task_state is provided,
it will be checked against the in-database copy of the
instance before updates are made.
:param context: Security context
:param expected_task_state: Optional tuple of valid task states
for the instance to be in.
"""
updates = {}
changes = self.obj_what_changed()
for 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)
self.obj_reset_changes()
@base.remotable
def refresh(self, context):
extra = []
for field in ['system_metadata', 'metadata']:
if hasattr(self, base.get_attrname(field)):
extra.append(field)
current = self.__class__.get_by_uuid(context, uuid=self.uuid,
expected_attrs=extra)
for field in self.fields:
if (hasattr(self, base.get_attrname(field)) and
self[field] != current[field]):
self[field] = current[field]
def obj_load(self, attrname):
extra = []
if attrname == 'system_metadata':
extra.append('system_metadata')
elif attrname == 'metadata':
extra.append('metadata')
if not extra:
raise Exception('Cannot load "%s" from instance' % attrname)
# NOTE(danms): This could be optimized to just load the bits we need
instance = self.__class__.get_by_uuid(self._context,
uuid=self.uuid,
expected_attrs=extra)
self[attrname] = instance[attrname]

View File

@ -0,0 +1,186 @@
# 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.
import datetime
import iso8601
import netaddr
from nova import context
from nova import db
from nova.objects import instance
from nova.openstack.common import timeutils
from nova.tests.api.openstack import fakes
from nova.tests.objects import test_objects
class _TestInstanceObject(object):
@property
def fake_instance(self):
fake_instance = fakes.stub_instance(id=2,
access_ipv4='1.2.3.4',
access_ipv6='::1')
fake_instance['scheduled_at'] = None
fake_instance['terminated_at'] = None
fake_instance['deleted_at'] = None
fake_instance['created_at'] = None
fake_instance['updated_at'] = None
fake_instance['launched_at'] = (
fake_instance['launched_at'].replace(
tzinfo=iso8601.iso8601.Utc(), microsecond=0))
return fake_instance
def test_datetime_deserialization(self):
red_letter_date = timeutils.parse_isotime(
timeutils.isotime(datetime.datetime(1955, 11, 5)))
inst = instance.Instance()
inst.uuid = 'fake-uuid'
inst.launched_at = red_letter_date
primitive = inst.obj_to_primitive()
expected = {'nova_object.name': 'Instance',
'nova_object.namespace': 'nova',
'nova_object.version': '1.0',
'nova_object.data':
{'uuid': 'fake-uuid',
'launched_at': '1955-11-05T00:00:00Z'},
'nova_object.changes': ['uuid', 'launched_at']}
self.assertEqual(primitive, expected)
inst2 = instance.Instance.obj_from_primitive(primitive)
self.assertTrue(isinstance(inst2.launched_at,
datetime.datetime))
self.assertEqual(inst2.launched_at, red_letter_date)
def test_ip_deserialization(self):
inst = instance.Instance()
inst.uuid = 'fake-uuid'
inst.access_ip_v4 = '1.2.3.4'
inst.access_ip_v6 = '::1'
primitive = inst.obj_to_primitive()
expected = {'nova_object.name': 'Instance',
'nova_object.namespace': 'nova',
'nova_object.version': '1.0',
'nova_object.data':
{'uuid': 'fake-uuid',
'access_ip_v4': '1.2.3.4',
'access_ip_v6': '::1'},
'nova_object.changes': ['uuid', 'access_ip_v6',
'access_ip_v4']}
self.assertEqual(primitive, expected)
inst2 = instance.Instance.obj_from_primitive(primitive)
self.assertTrue(isinstance(inst2.access_ip_v4, netaddr.IPAddress))
self.assertTrue(isinstance(inst2.access_ip_v6, netaddr.IPAddress))
self.assertEqual(inst2.access_ip_v4, netaddr.IPAddress('1.2.3.4'))
self.assertEqual(inst2.access_ip_v6, netaddr.IPAddress('::1'))
def test_get_without_expected(self):
ctxt = context.get_admin_context()
self.mox.StubOutWithMock(db, 'instance_get_by_uuid')
db.instance_get_by_uuid(ctxt, 'uuid', []).AndReturn(self.fake_instance)
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'))
self.assertRemotes()
def test_get_with_expected(self):
ctxt = context.get_admin_context()
self.mox.StubOutWithMock(db, 'instance_get_by_uuid')
db.instance_get_by_uuid(
ctxt, 'uuid',
['metadata', 'system_metadata']).AndReturn(self.fake_instance)
self.mox.ReplayAll()
inst = instance.Instance.get_by_uuid(
ctxt, uuid='uuid', expected_attrs=['metadata', 'system_metadata'])
self.assertTrue(hasattr(inst, '_metadata'))
self.assertTrue(hasattr(inst, '_system_metadata'))
self.assertRemotes()
def test_load(self):
ctxt = context.get_admin_context()
self.mox.StubOutWithMock(db, 'instance_get_by_uuid')
fake_uuid = self.fake_instance['uuid']
db.instance_get_by_uuid(ctxt, fake_uuid, []).AndReturn(
self.fake_instance)
fake_inst2 = dict(self.fake_instance,
system_metadata=[{'key': 'foo', 'value': 'bar'}])
db.instance_get_by_uuid(ctxt, fake_uuid, ['system_metadata']
).AndReturn(fake_inst2)
self.mox.ReplayAll()
inst = instance.Instance.get_by_uuid(ctxt, uuid=fake_uuid)
self.assertFalse(hasattr(inst, '_system_metadata'))
sys_meta = inst.system_metadata
self.assertEqual(sys_meta, {'foo': 'bar'})
self.assertTrue(hasattr(inst, '_system_metadata'))
# Make sure we don't run load again
sys_meta2 = inst.system_metadata
self.assertEqual(sys_meta2, {'foo': 'bar'})
self.assertRemotes()
def test_get_remote(self):
# isotime doesn't have microseconds and is always UTC
ctxt = context.get_admin_context()
self.mox.StubOutWithMock(db, 'instance_get_by_uuid')
fake_instance = self.fake_instance
db.instance_get_by_uuid(ctxt, 'fake-uuid', []).AndReturn(
fake_instance)
self.mox.ReplayAll()
inst = instance.Instance.get_by_uuid(ctxt, uuid='fake-uuid')
self.assertEqual(inst.id, fake_instance['id'])
self.assertEqual(inst.launched_at, fake_instance['launched_at'])
self.assertEqual(str(inst.access_ip_v4),
fake_instance['access_ip_v4'])
self.assertEqual(str(inst.access_ip_v6),
fake_instance['access_ip_v6'])
self.assertRemotes()
def test_refresh(self):
ctxt = context.get_admin_context()
self.mox.StubOutWithMock(db, 'instance_get_by_uuid')
fake_uuid = self.fake_instance['uuid']
db.instance_get_by_uuid(ctxt, fake_uuid, []).AndReturn(
dict(self.fake_instance, host='orig-host'))
db.instance_get_by_uuid(ctxt, fake_uuid, []).AndReturn(
dict(self.fake_instance, host='new-host'))
self.mox.ReplayAll()
inst = instance.Instance.get_by_uuid(ctxt, uuid=fake_uuid)
self.assertEqual(inst.host, 'orig-host')
inst.refresh()
self.assertEqual(inst.host, 'new-host')
self.assertRemotes()
def test_save(self):
ctxt = context.get_admin_context()
fake_inst = dict(self.fake_instance, host='oldhost')
fake_uuid = fake_inst['uuid']
self.mox.StubOutWithMock(db, 'instance_get_by_uuid')
self.mox.StubOutWithMock(db, 'instance_update_and_get_original')
db.instance_get_by_uuid(ctxt, fake_uuid, []).AndReturn(fake_inst)
db.instance_update_and_get_original(
ctxt, fake_uuid, {'user_data': 'foo'}).AndReturn(
(fake_inst, dict(fake_inst, host='newhost')))
self.mox.ReplayAll()
inst = instance.Instance.get_by_uuid(ctxt, uuid=fake_uuid)
inst.user_data = 'foo'
inst.save()
self.assertEqual(inst.host, 'newhost')
class TestInstanceObject(test_objects._LocalTest,
_TestInstanceObject):
pass
class TestRemoteInstanceObject(test_objects._RemoteTest,
_TestInstanceObject):
pass