00d6561934
The BuildRequest object represents a boot request before an instance has been created and written to the database. It will be required to respond to API requests to list/show an instance in cellsv2 where the instance is not written to the database immediately. An upcoming change will modify the boot process to defer instance creation until after scheduling has picked a cell/host and having this BuildRequest object is a prerequisite for that work. Change-Id: Ie9307388c2e068229177bcc4690cb834028c1481 Partially-implements: bp cells-scheduling-interaction
163 lines
6.6 KiB
Python
163 lines
6.6 KiB
Python
# 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)
|