Merge "Add instance object"
This commit is contained in:
commit
0e760b3e94
238
nova/objects/instance.py
Normal file
238
nova/objects/instance.py
Normal 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]
|
186
nova/tests/objects/test_instance.py
Normal file
186
nova/tests/objects/test_instance.py
Normal 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
|
Loading…
Reference in New Issue
Block a user