Merge "Add BuildRequest object"

This commit is contained in:
Jenkins
2016-03-03 11:06:34 +00:00
committed by Gerrit Code Review
7 changed files with 439 additions and 0 deletions
+4
View File
@@ -2098,3 +2098,7 @@ class RealtimeMaskNotFoundOrInvalid(Invalid):
class OsInfoNotFound(NotFound):
msg_fmt = _("No configuration information found for operating system "
"%(os_name)s")
class BuildRequestNotFound(NotFound):
msg_fmt = _("BuildRequest not found for instance %(uuid)s")
+1
View File
@@ -28,6 +28,7 @@ def register_all():
__import__('nova.objects.aggregate')
__import__('nova.objects.bandwidth_usage')
__import__('nova.objects.block_device')
__import__('nova.objects.build_request')
__import__('nova.objects.cell_mapping')
__import__('nova.objects.compute_node')
__import__('nova.objects.dns_domain')
+162
View File
@@ -0,0 +1,162 @@
# 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_config import cfg
from oslo_log import log as logging
from oslo_serialization import jsonutils
import six
from nova.db.sqlalchemy import api as db
from nova.db.sqlalchemy import api_models
from nova import exception
from nova.i18n import _LE
from nova import objects
from nova.objects import base
from nova.objects import fields
CONF = cfg.CONF
LOG = logging.getLogger(__name__)
OBJECT_FIELDS = ['info_cache', 'security_groups']
JSON_FIELDS = ['instance_metadata']
IP_FIELDS = ['access_ip_v4', 'access_ip_v6']
@base.NovaObjectRegistry.register
class BuildRequest(base.NovaObject):
# Version 1.0: Initial version
VERSION = '1.0'
fields = {
'id': fields.IntegerField(),
'project_id': fields.StringField(),
'user_id': fields.StringField(),
'display_name': fields.StringField(nullable=True),
'instance_metadata': fields.DictOfStringsField(nullable=True),
'progress': fields.IntegerField(nullable=True),
'vm_state': fields.StringField(nullable=True),
'task_state': fields.StringField(nullable=True),
'image_ref': fields.StringField(nullable=True),
'access_ip_v4': fields.IPV4AddressField(nullable=True),
'access_ip_v6': fields.IPV6AddressField(nullable=True),
'info_cache': fields.ObjectField('InstanceInfoCache', nullable=True),
'security_groups': fields.ObjectField('SecurityGroupList'),
'config_drive': fields.BooleanField(default=False),
'key_name': fields.StringField(nullable=True),
'locked_by': fields.EnumField(['owner', 'admin'], nullable=True),
'request_spec': fields.ObjectField('RequestSpec'),
# NOTE(alaski): Normally these would come from the NovaPersistentObject
# mixin but they're being set explicitly because we only need
# created_at/updated_at. There is no soft delete for this object.
# These fields should be carried over to the instance when it is
# scheduled and created in a cell database.
'created_at': fields.DateTimeField(nullable=True),
'updated_at': fields.DateTimeField(nullable=True),
}
def _load_request_spec(self, db_spec):
self.request_spec = objects.RequestSpec._from_db_object(self._context,
objects.RequestSpec(), db_spec)
def _load_info_cache(self, db_info_cache):
self.info_cache = objects.InstanceInfoCache.obj_from_primitive(
jsonutils.loads(db_info_cache))
def _load_security_groups(self, db_sec_group):
self.security_groups = objects.SecurityGroupList.obj_from_primitive(
jsonutils.loads(db_sec_group))
@staticmethod
def _from_db_object(context, req, db_req):
for key in req.fields:
if isinstance(req.fields[key], fields.ObjectField):
try:
getattr(req, '_load_%s' % key)(db_req[key])
except AttributeError:
LOG.exception(_LE('No load handler for %s'), key)
elif key in JSON_FIELDS and db_req[key] is not None:
setattr(req, key, jsonutils.loads(db_req[key]))
else:
setattr(req, key, db_req[key])
req.obj_reset_changes()
req._context = context
return req
@staticmethod
@db.api_context_manager.reader
def _get_by_instance_uuid_from_db(context, instance_uuid):
db_req = (context.session.query(api_models.BuildRequest)
.join(api_models.RequestSpec)
.with_entities(api_models.BuildRequest,
api_models.RequestSpec)
.filter(
api_models.RequestSpec.instance_uuid == instance_uuid)
).first()
if not db_req:
raise exception.BuildRequestNotFound(uuid=instance_uuid)
# db_req is a tuple (api_models.BuildRequest, api_models.RequestSpect)
build_req = db_req[0]
build_req['request_spec'] = db_req[1]
return build_req
@base.remotable_classmethod
def get_by_instance_uuid(cls, context, instance_uuid):
db_req = cls._get_by_instance_uuid_from_db(context, instance_uuid)
return cls._from_db_object(context, cls(), db_req)
@staticmethod
@db.api_context_manager.writer
def _create_in_db(context, updates):
db_req = api_models.BuildRequest()
db_req.update(updates)
db_req.save(context.session)
# NOTE: This is done because a later access will trigger a lazy load
# outside of the db session so it will fail. We don't lazy load
# request_spec on the object later because we never need a BuildRequest
# without the RequestSpec.
db_req.request_spec
return db_req
def _get_update_primitives(self):
updates = self.obj_get_changes()
for key, value in six.iteritems(updates):
if key in OBJECT_FIELDS and value is not None:
updates[key] = jsonutils.dumps(value.obj_to_primitive())
elif key in JSON_FIELDS and value is not None:
updates[key] = jsonutils.dumps(value)
elif key in IP_FIELDS and value is not None:
# These are stored as a string in the db and must be converted
updates[key] = str(value)
req_spec_obj = updates.pop('request_spec', None)
if req_spec_obj:
updates['request_spec_id'] = req_spec_obj.id
return updates
@base.remotable
def create(self):
if self.obj_attr_is_set('id'):
raise exception.ObjectActionError(action='create',
reason='already created')
updates = self._get_update_primitives()
db_req = self._create_in_db(self._context, updates)
self._from_db_object(self._context, self, db_req)
@staticmethod
@db.api_context_manager.writer
def _destroy_in_db(context, id):
context.session.query(api_models.BuildRequest).filter_by(
id=id).delete()
@base.remotable
def destroy(self):
self._destroy_in_db(self._context, self.id)
@@ -0,0 +1,78 @@
# 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 oslo_utils import uuidutils
from nova import context
from nova import exception
from nova import objects
from nova.objects import build_request
from nova import test
from nova.tests import fixtures
from nova.tests.unit import fake_build_request
from nova.tests.unit import fake_request_spec
class BuildRequestTestCase(test.NoDBTestCase):
def setUp(self):
super(BuildRequestTestCase, self).setUp()
# NOTE: This means that we're using a database for this test suite
# despite inheriting from NoDBTestCase
self.useFixture(fixtures.Database(database='api'))
self.context = context.RequestContext('fake-user', 'fake-project')
self.build_req_obj = build_request.BuildRequest()
self.instance_uuid = uuidutils.generate_uuid()
self.project_id = 'fake-project'
def _create_req(self):
req_spec = fake_request_spec.fake_spec_obj(remove_id=True)
req_spec.instance_uuid = self.instance_uuid
req_spec.create()
args = fake_build_request.fake_db_req(
request_spec_id=req_spec.id)
args.pop('id', None)
args.pop('request_spec', None)
args['project_id'] = self.project_id
return build_request.BuildRequest._from_db_object(self.context,
self.build_req_obj,
self.build_req_obj._create_in_db(self.context, args))
def test_get_by_instance_uuid_not_found(self):
self.assertRaises(exception.BuildRequestNotFound,
self.build_req_obj._get_by_instance_uuid_from_db, self.context,
self.instance_uuid)
def test_get_by_uuid(self):
req = self._create_req()
db_req = self.build_req_obj._get_by_instance_uuid_from_db(self.context,
self.instance_uuid)
for key in self.build_req_obj.fields.keys():
expected = getattr(req, key)
db_value = db_req[key]
if key == 'request_spec':
# NOTE: The object and db value can't be compared directly as
# objects, so serialize them to a comparable form.
db_value = jsonutils.dumps(objects.RequestSpec._from_db_object(
self.context, objects.RequestSpec(),
db_value).obj_to_primitive())
expected = jsonutils.dumps(expected.obj_to_primitive())
elif key in build_request.OBJECT_FIELDS:
expected = jsonutils.dumps(expected.obj_to_primitive())
elif key in build_request.JSON_FIELDS:
expected = jsonutils.dumps(expected)
elif key in build_request.IP_FIELDS:
expected = str(expected)
elif key in ['created_at', 'updated_at']:
# Objects store tz aware datetimes but the db does not.
expected = expected.replace(tzinfo=None)
self.assertEqual(expected, db_value)
+112
View File
@@ -0,0 +1,112 @@
# 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
from oslo_serialization import jsonutils
from oslo_utils import uuidutils
from nova.compute import task_states
from nova.compute import vm_states
from nova import context
from nova.network import model as network_model
from nova import objects
from nova.objects import fields
from nova.tests.unit import fake_request_spec
def _req_spec_to_db_format(req_spec):
db_spec = {'spec': jsonutils.dumps(req_spec.obj_to_primitive()),
'id': req_spec.id,
'instance_uuid': req_spec.instance_uuid,
}
return db_spec
def fake_db_req(**updates):
instance_uuid = uuidutils.generate_uuid()
info_cache = objects.InstanceInfoCache()
info_cache.instance_uuid = instance_uuid
info_cache.network_info = network_model.NetworkInfo()
req_spec = fake_request_spec.fake_spec_obj(
context.RequestContext('fake-user', 'fake-project'))
req_spec.id = 42
req_spec.obj_reset_changes()
db_build_request = {
'id': 1,
'project_id': 'fake-project',
'user_id': 'fake-user',
'display_name': '',
'instance_metadata': jsonutils.dumps({'foo': 'bar'}),
'progress': 0,
'vm_state': vm_states.BUILDING,
'task_state': task_states.SCHEDULING,
'image_ref': None,
'access_ip_v4': '1.2.3.4',
'access_ip_v6': '::1',
'info_cache': jsonutils.dumps(info_cache.obj_to_primitive()),
'security_groups': jsonutils.dumps(
objects.SecurityGroupList().obj_to_primitive()),
'config_drive': False,
'key_name': None,
'locked_by': None,
'request_spec': _req_spec_to_db_format(req_spec),
'created_at': datetime.datetime(2016, 1, 16),
'updated_at': datetime.datetime(2016, 1, 16),
}
for name, field in objects.BuildRequest.fields.items():
if name in db_build_request:
continue
if field.nullable:
db_build_request[name] = None
elif field.default != fields.UnspecifiedDefault:
db_build_request[name] = field.default
else:
raise Exception('fake_db_req needs help with %s' % name)
if updates:
db_build_request.update(updates)
return db_build_request
def fake_req_obj(context, db_req=None):
if db_req is None:
db_req = fake_db_req()
req_obj = objects.BuildRequest(context)
for field in req_obj.fields:
value = db_req[field]
# create() can't be called if this is set
if field == 'id':
continue
if isinstance(req_obj.fields[field], fields.ObjectField):
value = value
if field == 'request_spec':
req_spec = objects.RequestSpec._from_db_object(context,
objects.RequestSpec(), value)
req_obj.request_spec = req_spec
elif field == 'info_cache':
setattr(req_obj, field,
objects.InstanceInfoCache.obj_from_primitive(
jsonutils.loads(value)))
elif field == 'security_groups':
setattr(req_obj, field,
objects.SecurityGroupList.obj_from_primitive(
jsonutils.loads(value)))
elif field == 'instance_metadata':
setattr(req_obj, field, jsonutils.loads(value))
else:
setattr(req_obj, field, value)
# This should never be a changed field
req_obj.obj_reset_changes(['id'])
return req_obj
@@ -0,0 +1,81 @@
# 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 mock
from nova import exception
from nova import objects
from nova.objects import build_request
from nova.tests.unit import fake_build_request
from nova.tests.unit.objects import test_objects
class _TestBuildRequestObject(object):
@mock.patch.object(build_request.BuildRequest,
'_get_by_instance_uuid_from_db')
def test_get_by_instance_uuid(self, get_by_uuid):
fake_req = fake_build_request.fake_db_req()
get_by_uuid.return_value = fake_req
req_obj = build_request.BuildRequest.get_by_instance_uuid(self.context,
fake_req['request_spec']['instance_uuid'])
self.assertEqual(fake_req['request_spec']['instance_uuid'],
req_obj.request_spec.instance_uuid)
self.assertEqual(fake_req['project_id'], req_obj.project_id)
self.assertIsInstance(req_obj.request_spec, objects.RequestSpec)
get_by_uuid.assert_called_once_with(self.context,
fake_req['request_spec']['instance_uuid'])
@mock.patch.object(build_request.BuildRequest,
'_create_in_db')
def test_create(self, create_in_db):
fake_req = fake_build_request.fake_db_req()
req_obj = fake_build_request.fake_req_obj(self.context, fake_req)
def _test_create_args(self2, context, changes):
for field in [fields for fields in
build_request.BuildRequest.fields if fields not in
['created_at', 'updated_at', 'request_spec', 'id']]:
self.assertEqual(fake_req[field], changes[field])
self.assertEqual(fake_req['request_spec']['id'],
changes['request_spec_id'])
return fake_req
with mock.patch.object(build_request.BuildRequest, '_create_in_db',
_test_create_args):
req_obj.create()
def test_create_id_set(self):
req_obj = build_request.BuildRequest(self.context)
req_obj.id = 3
self.assertRaises(exception.ObjectActionError, req_obj.create)
@mock.patch.object(build_request.BuildRequest, '_destroy_in_db')
def test_destroy(self, destroy_in_db):
req_obj = build_request.BuildRequest(self.context)
req_obj.id = 1
req_obj.destroy()
destroy_in_db.assert_called_once_with(self.context, req_obj.id)
class TestBuildRequestObject(test_objects._LocalTest,
_TestBuildRequestObject):
pass
class TestRemoteBuildRequestObject(test_objects._RemoteTest,
_TestBuildRequestObject):
pass
+1
View File
@@ -1105,6 +1105,7 @@ object_data = {
'BandwidthUsageList': '1.2-5fe7475ada6fe62413cbfcc06ec70746',
'BlockDeviceMapping': '1.16-12319f6f47f740a67a88a23f7c7ee6ef',
'BlockDeviceMappingList': '1.17-1e568eecb91d06d4112db9fd656de235',
'BuildRequest': '1.0-e4ca475cabb07f73d8176f661afe8c55',
'CellMapping': '1.0-7f1a7e85a22bbb7559fc730ab658b9bd',
'ComputeNode': '1.16-2436e5b836fa0306a3c4e6d9e5ddacec',
'ComputeNodeList': '1.14-3b6f4f5ade621c40e70cb116db237844',