7add037259
The current tacker returns 500 Internal Server Error to requests with invalid filter conditions where a VNFID is not UUID. However, as the error is caused by a bad request, the status code should be 400. This bug occurs because data types of 'id' and 'vnfd_Id' in TackerObject are 'string' which don't match the data types in DB, i.e., UUID. In order to fix this bug, those data types in TackerObject are changed to 'uuid'. Closes-Bug: 1924666 Closes-Bug: 1926632 Change-Id: I8decbb42d661a44c6c382cb1abbfc37d95084ffb
301 lines
11 KiB
Python
301 lines
11 KiB
Python
# Copyright 2019 NTT DATA.
|
|
#
|
|
# 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_log import log as logging
|
|
from oslo_utils import uuidutils
|
|
from oslo_versionedobjects import base as ovoo_base
|
|
from sqlalchemy.orm import joinedload
|
|
|
|
from tacker.common import exceptions
|
|
from tacker.common import utils
|
|
from tacker.db import api as db_api
|
|
from tacker.db.db_sqlalchemy import api
|
|
from tacker.db.db_sqlalchemy import models
|
|
from tacker.objects import base
|
|
from tacker.objects import fields
|
|
|
|
|
|
VNF_SOFTWARE_IMAGE_OPTIONAL_ATTRS = ['metadata']
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
def _metadata_add_to_db(context, id, metadata, max_retries=10):
|
|
for attempt in range(max_retries):
|
|
with db_api.context_manager.writer.using(context):
|
|
|
|
new_entries = []
|
|
for key, value in metadata.items():
|
|
new_entries.append({"key": key,
|
|
"value": value,
|
|
"image_uuid": id})
|
|
if new_entries:
|
|
context.session.execute(
|
|
models.VnfSoftwareImageMetadata.__table__.insert(None),
|
|
new_entries)
|
|
|
|
return metadata
|
|
|
|
|
|
@db_api.context_manager.writer
|
|
def _vnf_sw_image_create(context, values, metadata=None):
|
|
vnf_sw_image = models.VnfSoftwareImage()
|
|
|
|
vnf_sw_image.update(values)
|
|
vnf_sw_image.save(context.session)
|
|
vnf_sw_image._metadata = []
|
|
|
|
if metadata:
|
|
_metadata_add_to_db(context, vnf_sw_image.id, metadata)
|
|
context.session.expire(vnf_sw_image, ['_metadata'])
|
|
vnf_sw_image._metadata
|
|
|
|
return vnf_sw_image
|
|
|
|
|
|
@db_api.context_manager.reader
|
|
def _vnf_sw_image_get_by_id(context, id):
|
|
|
|
query = api.model_query(context, models.VnfSoftwareImage,
|
|
read_deleted="no").filter_by(id=id).options(joinedload('_metadata'))
|
|
|
|
result = query.first()
|
|
|
|
if not result:
|
|
raise exceptions.VnfSoftwareImageNotFound(id=id)
|
|
|
|
return result
|
|
|
|
|
|
@base.TackerObjectRegistry.register
|
|
class VnfSoftwareImage(base.TackerObject, base.TackerPersistentObject):
|
|
|
|
ALL_ATTRIBUTES = {
|
|
"softwareImages": {
|
|
'id': ('software_image_id', 'uuid', 'VnfSoftwareImage'),
|
|
'imagePath': ('image_path', 'string', 'VnfSoftwareImage'),
|
|
'diskFormat': ('disk_format', 'string', 'VnfSoftwareImage'),
|
|
'userMetadata/*': ('metadata', 'key_value_pair',
|
|
{"key_column": "key", "value_column": "value",
|
|
"model": "VnfSoftwareImageMetadata"}),
|
|
'size': ('size', 'number', 'VnfSoftwareImage'),
|
|
'createdAt': ('created_at', 'datetime', 'VnfSoftwareImage'),
|
|
'name': ('name', 'string', 'VnfSoftwareImage'),
|
|
'minDisk': ('min_disk', 'number', 'VnfSoftwareImage'),
|
|
'version': ('version', 'string', 'VnfSoftwareImage'),
|
|
'provider': ('provider', 'string', 'VnfSoftwareImage'),
|
|
'minRam': ('min_ram', 'number', 'VnfSoftwareImage'),
|
|
'containerFormat': ('container_format', 'string',
|
|
'VnfSoftwareImage'),
|
|
"checksum": {
|
|
'hash': ('hash', 'string', 'VnfSoftwareImage'),
|
|
'algorithm': ('algorithm', 'string', 'VnfSoftwareImage')
|
|
}
|
|
}
|
|
}
|
|
|
|
FLATTEN_ATTRIBUTES = utils.flatten_dict(ALL_ATTRIBUTES.copy())
|
|
SIMPLE_ATTRIBUTES = ['id', 'imagePath', 'diskFormat', 'size',
|
|
'createdAt', 'name', 'minDisk', 'version', 'provider', 'minRam',
|
|
'containerFormat']
|
|
COMPLEX_ATTRIBUTES = ['softwareImages', 'softwareImages/userMetadata',
|
|
'softwareImages/checksum']
|
|
|
|
# Version 1.0: Initial version
|
|
VERSION = '1.0'
|
|
|
|
fields = {
|
|
'id': fields.UUIDField(nullable=False),
|
|
'software_image_id': fields.StringField(nullable=False),
|
|
'flavour_uuid': fields.UUIDField(nullable=False),
|
|
'name': fields.StringField(nullable=True),
|
|
'provider': fields.StringField(nullable=True),
|
|
'version': fields.StringField(nullable=True),
|
|
'algorithm': fields.StringField(nullable=True),
|
|
'hash': fields.StringField(nullable=True),
|
|
'container_format': fields.StringField(nullable=True),
|
|
'disk_format': fields.StringField(nullable=True),
|
|
'min_disk': fields.IntegerField(),
|
|
'min_ram': fields.IntegerField(default=0),
|
|
'size': fields.IntegerField(),
|
|
'image_path': fields.StringField(),
|
|
'metadata': fields.DictOfStringsField(nullable=True)
|
|
}
|
|
|
|
@staticmethod
|
|
def _from_db_object(context, vnf_sw_image, db_sw_image,
|
|
expected_attrs=None):
|
|
|
|
vnf_sw_image._context = context
|
|
for key in vnf_sw_image.fields:
|
|
if key in VNF_SOFTWARE_IMAGE_OPTIONAL_ATTRS:
|
|
continue
|
|
else:
|
|
db_key = key
|
|
|
|
setattr(vnf_sw_image, key, db_sw_image[db_key])
|
|
|
|
vnf_sw_image._extra_attributes_from_db_object(vnf_sw_image,
|
|
db_sw_image, expected_attrs)
|
|
|
|
vnf_sw_image.obj_reset_changes()
|
|
|
|
return vnf_sw_image
|
|
|
|
@staticmethod
|
|
def _extra_attributes_from_db_object(vnf_sw_image, db_sw_image,
|
|
expected_attrs=None):
|
|
"""Method to help with migration of extra attributes to objects.
|
|
|
|
"""
|
|
if expected_attrs is None:
|
|
expected_attrs = []
|
|
|
|
if 'metadata' in expected_attrs:
|
|
setattr(vnf_sw_image, 'metadata', db_sw_image['metadetails'])
|
|
|
|
def obj_load_attr(self, attrname):
|
|
if not self._context:
|
|
raise exceptions.OrphanedObjectError(method='obj_load_attr',
|
|
objtype=self.obj_name())
|
|
if 'id' not in self:
|
|
raise exceptions.ObjectActionError(
|
|
action='obj_load_attr',
|
|
reason=_('attribute %s not lazy-loadable') % attrname)
|
|
|
|
LOG.debug("Lazy-loading '%(attr)s' on %(name)s id %(id)s",
|
|
{'attr': attrname,
|
|
'name': self.obj_name(),
|
|
'id': self.id,
|
|
})
|
|
|
|
self._obj_load_attr(attrname)
|
|
|
|
def _obj_load_attr(self, attrname):
|
|
"""Internal method for loading attributes from vnf flavour."""
|
|
|
|
if attrname in self.fields and attrname != 'id':
|
|
self._load_generic(attrname)
|
|
else:
|
|
# NOTE(nirajsingh): Raise error if non existing field is
|
|
# requested.
|
|
raise exceptions.ObjectActionError(
|
|
action='obj_load_attr',
|
|
reason=_('attribute %s not lazy-loadable') % attrname)
|
|
|
|
self.obj_reset_changes([attrname])
|
|
|
|
def _load_generic(self, attrname):
|
|
software_image = self.__class__.get_by_id(self._context,
|
|
id=self.id,
|
|
expected_attrs=attrname)
|
|
if attrname not in software_image:
|
|
raise exceptions.ObjectActionError(
|
|
action='obj_load_attr',
|
|
reason=_('loading %s requires recursion') % attrname)
|
|
|
|
for field in self.fields:
|
|
if field in software_image and field not in self:
|
|
setattr(self, field, getattr(software_image, field))
|
|
|
|
@base.remotable
|
|
def create(self):
|
|
if self.obj_attr_is_set('id'):
|
|
raise exceptions.ObjectActionError(action='create',
|
|
reason=_('already created'))
|
|
updates = self.obj_get_changes()
|
|
|
|
if 'id' not in updates:
|
|
updates['id'] = uuidutils.generate_uuid()
|
|
self.id = updates['id']
|
|
|
|
metadata = updates.pop('metadata', None)
|
|
db_sw_image = _vnf_sw_image_create(self._context, updates,
|
|
metadata=metadata)
|
|
self._from_db_object(self._context, self, db_sw_image)
|
|
|
|
@base.remotable_classmethod
|
|
def get_by_id(cls, context, id, expected_attrs=None):
|
|
db_sw_image = _vnf_sw_image_get_by_id(context, id)
|
|
return cls._from_db_object(context, cls(), db_sw_image,
|
|
expected_attrs=expected_attrs)
|
|
|
|
def _get_user_metadata(self, include_fields=None):
|
|
# Need special handling for field containing key-value pair.
|
|
# If user requests softwareImages/userMetadata/key1 and if
|
|
# softwareImages/userMetadata contains key1=value1, key2=value2,
|
|
# it should return only keys that are requested in include_fields.
|
|
# If user requests only softwareImages/userMetadata, then in that
|
|
# case, it should return all key/value pairs. If any of the requested
|
|
# key is not present, then it will siliently ignore it.
|
|
key = 'softwareImages/userMetadata'
|
|
if key in include_fields or '%s/*' % key in \
|
|
include_fields:
|
|
return self.metadata
|
|
else:
|
|
# Check if user has requested specified keys from
|
|
# softwareImages/userMetadata.
|
|
key_list = []
|
|
special_key = '%s/' % key
|
|
for field in include_fields:
|
|
if field.startswith(special_key):
|
|
key_list.append(field[len(special_key):])
|
|
|
|
data_resp = dict()
|
|
for key_req in key_list:
|
|
if key_req in self.metadata:
|
|
data_resp[key_req] = self.metadata[key_req]
|
|
|
|
if len(key_list) > 0:
|
|
return data_resp
|
|
|
|
def to_dict(self, include_fields=None):
|
|
response = dict()
|
|
fields = ['softwareImages/%s' % attribute for attribute in
|
|
self.SIMPLE_ATTRIBUTES]
|
|
to_fields = set(fields).intersection(include_fields)
|
|
for field in to_fields:
|
|
display_field = field.split("/")[-1]
|
|
response[display_field] = getattr(self,
|
|
self.FLATTEN_ATTRIBUTES[field][0])
|
|
|
|
# add checksum
|
|
to_fields = set([key for key in self.FLATTEN_ATTRIBUTES.keys()
|
|
if key.startswith('softwareImages/checksum')])
|
|
checksum = dict()
|
|
to_fields = to_fields.intersection(include_fields)
|
|
for field in to_fields:
|
|
display_field = field.split("/")[-1]
|
|
checksum[display_field] = getattr(self,
|
|
self.FLATTEN_ATTRIBUTES[field][0])
|
|
|
|
if checksum:
|
|
response.update({"checksum": checksum})
|
|
|
|
user_metadata = self._get_user_metadata(include_fields)
|
|
if user_metadata is not None:
|
|
response.update({"userMetadata": user_metadata})
|
|
|
|
return response
|
|
|
|
|
|
@base.TackerObjectRegistry.register
|
|
class VnfSoftwareImagesList(ovoo_base.ObjectListBase, base.TackerObject):
|
|
|
|
VERSION = '1.0'
|
|
|
|
fields = {
|
|
'objects': fields.ListOfObjectsField('VnfSoftwareImage')
|
|
}
|