# Copyright 2010 United States Government as represented by the # Administrator of the National Aeronautics and Space Administration. # Copyright 2010-2012 OpenStack Foundation # Copyright 2013 IBM Corp. # All Rights Reserved. # # 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.utils import importutils from wsme.rest import json from glance.api.v2.model.metadef_property_type import PropertyType from glance.common import crypt from glance.common import exception from glance.common import location_strategy import glance.domain import glance.domain.proxy from glance import i18n _ = i18n._ CONF = cfg.CONF CONF.import_opt('image_size_cap', 'glance.common.config') CONF.import_opt('metadata_encryption_key', 'glance.common.config') def get_api(): api = importutils.import_module(CONF.data_api) if hasattr(api, 'configure'): api.configure() return api def unwrap(db_api): return db_api # attributes common to all models BASE_MODEL_ATTRS = set(['id', 'created_at', 'updated_at', 'deleted_at', 'deleted']) IMAGE_ATTRS = BASE_MODEL_ATTRS | set(['name', 'status', 'size', 'virtual_size', 'disk_format', 'container_format', 'min_disk', 'min_ram', 'is_public', 'locations', 'checksum', 'owner', 'protected']) class ImageRepo(object): def __init__(self, context, db_api): self.context = context self.db_api = db_api def get(self, image_id): try: db_api_image = dict(self.db_api.image_get(self.context, image_id)) assert not db_api_image['deleted'] except (exception.NotFound, exception.Forbidden, AssertionError): msg = _("No image found with ID %s") % image_id raise exception.NotFound(msg) tags = self.db_api.image_tag_get_all(self.context, image_id) image = self._format_image_from_db(db_api_image, tags) return ImageProxy(image, self.context, self.db_api) def list(self, marker=None, limit=None, sort_key=['created_at'], sort_dir='desc', filters=None, member_status='accepted'): db_api_images = self.db_api.image_get_all( self.context, filters=filters, marker=marker, limit=limit, sort_key=sort_key, sort_dir=sort_dir, member_status=member_status, return_tag=True) images = [] for db_api_image in db_api_images: db_image = dict(db_api_image) image = self._format_image_from_db(db_image, db_image['tags']) images.append(image) return images def _format_image_from_db(self, db_image, db_tags): visibility = 'public' if db_image['is_public'] else 'private' properties = {} for prop in db_image.pop('properties'): # NOTE(markwash) db api requires us to filter deleted if not prop['deleted']: properties[prop['name']] = prop['value'] locations = [loc for loc in db_image['locations'] if loc['status'] == 'active'] if CONF.metadata_encryption_key: key = CONF.metadata_encryption_key for l in locations: l['url'] = crypt.urlsafe_decrypt(key, l['url']) return glance.domain.Image( image_id=db_image['id'], name=db_image['name'], status=db_image['status'], created_at=db_image['created_at'], updated_at=db_image['updated_at'], visibility=visibility, min_disk=db_image['min_disk'], min_ram=db_image['min_ram'], protected=db_image['protected'], locations=location_strategy.get_ordered_locations(locations), checksum=db_image['checksum'], owner=db_image['owner'], disk_format=db_image['disk_format'], container_format=db_image['container_format'], size=db_image['size'], virtual_size=db_image['virtual_size'], extra_properties=properties, tags=db_tags ) def _format_image_to_db(self, image): locations = image.locations if CONF.metadata_encryption_key: key = CONF.metadata_encryption_key ld = [] for loc in locations: url = crypt.urlsafe_encrypt(key, loc['url']) ld.append({'url': url, 'metadata': loc['metadata'], 'status': loc['status'], # NOTE(zhiyan): New location has no ID field. 'id': loc.get('id')}) locations = ld return { 'id': image.image_id, 'name': image.name, 'status': image.status, 'created_at': image.created_at, 'min_disk': image.min_disk, 'min_ram': image.min_ram, 'protected': image.protected, 'locations': locations, 'checksum': image.checksum, 'owner': image.owner, 'disk_format': image.disk_format, 'container_format': image.container_format, 'size': image.size, 'virtual_size': image.virtual_size, 'is_public': image.visibility == 'public', 'properties': dict(image.extra_properties), } def add(self, image): image_values = self._format_image_to_db(image) if image_values['size'] > CONF.image_size_cap: raise exception.ImageSizeLimitExceeded # the updated_at value is not set in the _format_image_to_db # function since it is specific to image create image_values['updated_at'] = image.updated_at new_values = self.db_api.image_create(self.context, image_values) self.db_api.image_tag_set_all(self.context, image.image_id, image.tags) image.created_at = new_values['created_at'] image.updated_at = new_values['updated_at'] def save(self, image): image_values = self._format_image_to_db(image) if image_values['size'] > CONF.image_size_cap: raise exception.ImageSizeLimitExceeded try: new_values = self.db_api.image_update(self.context, image.image_id, image_values, purge_props=True) except (exception.NotFound, exception.Forbidden): msg = _("No image found with ID %s") % image.image_id raise exception.NotFound(msg) self.db_api.image_tag_set_all(self.context, image.image_id, image.tags) image.updated_at = new_values['updated_at'] def remove(self, image): image_values = self._format_image_to_db(image) try: self.db_api.image_update(self.context, image.image_id, image_values, purge_props=True) except (exception.NotFound, exception.Forbidden): msg = _("No image found with ID %s") % image.image_id raise exception.NotFound(msg) # NOTE(markwash): don't update tags? new_values = self.db_api.image_destroy(self.context, image.image_id) image.updated_at = new_values['updated_at'] class ImageProxy(glance.domain.proxy.Image): def __init__(self, image, context, db_api): self.context = context self.db_api = db_api self.image = image super(ImageProxy, self).__init__(image) def get_member_repo(self): member_repo = ImageMemberRepo(self.context, self.db_api, self.image) return member_repo class ImageMemberRepo(object): def __init__(self, context, db_api, image): self.context = context self.db_api = db_api self.image = image def _format_image_member_from_db(self, db_image_member): return glance.domain.ImageMembership( id=db_image_member['id'], image_id=db_image_member['image_id'], member_id=db_image_member['member'], status=db_image_member['status'], created_at=db_image_member['created_at'], updated_at=db_image_member['updated_at'] ) def _format_image_member_to_db(self, image_member): image_member = {'image_id': self.image.image_id, 'member': image_member.member_id, 'status': image_member.status, 'created_at': image_member.created_at} return image_member def list(self): db_members = self.db_api.image_member_find( self.context, image_id=self.image.image_id) image_members = [] for db_member in db_members: image_members.append(self._format_image_member_from_db(db_member)) return image_members def add(self, image_member): try: self.get(image_member.member_id) except exception.NotFound: pass else: msg = _('The target member %(member_id)s is already ' 'associated with image %(image_id)s.') % { 'member_id': image_member.member_id, 'image_id': self.image.image_id} raise exception.Duplicate(msg) image_member_values = self._format_image_member_to_db(image_member) new_values = self.db_api.image_member_create(self.context, image_member_values) image_member.created_at = new_values['created_at'] image_member.updated_at = new_values['updated_at'] image_member.id = new_values['id'] def remove(self, image_member): try: self.db_api.image_member_delete(self.context, image_member.id) except (exception.NotFound, exception.Forbidden): msg = _("The specified member %s could not be found") raise exception.NotFound(msg % image_member.id) def save(self, image_member): image_member_values = self._format_image_member_to_db(image_member) try: new_values = self.db_api.image_member_update(self.context, image_member.id, image_member_values) except (exception.NotFound, exception.Forbidden): raise exception.NotFound() image_member.updated_at = new_values['updated_at'] def get(self, member_id): try: db_api_image_member = self.db_api.image_member_find( self.context, self.image.image_id, member_id) if not db_api_image_member: raise exception.NotFound() except (exception.NotFound, exception.Forbidden): raise exception.NotFound() image_member = self._format_image_member_from_db( db_api_image_member[0]) return image_member class TaskRepo(object): def __init__(self, context, db_api): self.context = context self.db_api = db_api def _format_task_from_db(self, db_task): return glance.domain.Task( task_id=db_task['id'], task_type=db_task['type'], status=db_task['status'], owner=db_task['owner'], expires_at=db_task['expires_at'], created_at=db_task['created_at'], updated_at=db_task['updated_at'], task_input=db_task['input'], result=db_task['result'], message=db_task['message'], ) def _format_task_stub_from_db(self, db_task): return glance.domain.TaskStub( task_id=db_task['id'], task_type=db_task['type'], status=db_task['status'], owner=db_task['owner'], expires_at=db_task['expires_at'], created_at=db_task['created_at'], updated_at=db_task['updated_at'], ) def _format_task_to_db(self, task): task = {'id': task.task_id, 'type': task.type, 'status': task.status, 'input': task.task_input, 'result': task.result, 'owner': task.owner, 'message': task.message, 'expires_at': task.expires_at, 'created_at': task.created_at, 'updated_at': task.updated_at, } return task def get(self, task_id): try: db_api_task = self.db_api.task_get(self.context, task_id) except (exception.NotFound, exception.Forbidden): msg = _('Could not find task %s') % task_id raise exception.NotFound(msg) return self._format_task_from_db(db_api_task) def list(self, marker=None, limit=None, sort_key='created_at', sort_dir='desc', filters=None): db_api_tasks = self.db_api.task_get_all(self.context, filters=filters, marker=marker, limit=limit, sort_key=sort_key, sort_dir=sort_dir) return [self._format_task_stub_from_db(task) for task in db_api_tasks] def save(self, task): task_values = self._format_task_to_db(task) try: updated_values = self.db_api.task_update(self.context, task.task_id, task_values) except (exception.NotFound, exception.Forbidden): msg = _('Could not find task %s') % task.task_id raise exception.NotFound(msg) task.updated_at = updated_values['updated_at'] def add(self, task): task_values = self._format_task_to_db(task) updated_values = self.db_api.task_create(self.context, task_values) task.created_at = updated_values['created_at'] task.updated_at = updated_values['updated_at'] def remove(self, task): task_values = self._format_task_to_db(task) try: self.db_api.task_update(self.context, task.task_id, task_values) updated_values = self.db_api.task_delete(self.context, task.task_id) except (exception.NotFound, exception.Forbidden): msg = _('Could not find task %s') % task.task_id raise exception.NotFound(msg) task.updated_at = updated_values['updated_at'] task.deleted_at = updated_values['deleted_at'] class MetadefNamespaceRepo(object): def __init__(self, context, db_api): self.context = context self.db_api = db_api def _format_namespace_from_db(self, namespace_obj): return glance.domain.MetadefNamespace( namespace_id=namespace_obj['id'], namespace=namespace_obj['namespace'], display_name=namespace_obj['display_name'], description=namespace_obj['description'], owner=namespace_obj['owner'], visibility=namespace_obj['visibility'], protected=namespace_obj['protected'], created_at=namespace_obj['created_at'], updated_at=namespace_obj['updated_at'] ) def _format_namespace_to_db(self, namespace_obj): namespace = { 'namespace': namespace_obj.namespace, 'display_name': namespace_obj.display_name, 'description': namespace_obj.description, 'visibility': namespace_obj.visibility, 'protected': namespace_obj.protected, 'owner': namespace_obj.owner } return namespace def add(self, namespace): self.db_api.metadef_namespace_create( self.context, self._format_namespace_to_db(namespace) ) def get(self, namespace): try: db_api_namespace = self.db_api.metadef_namespace_get( self.context, namespace) except (exception.NotFound, exception.Forbidden): msg = _('Could not find namespace %s') % namespace raise exception.NotFound(msg) return self._format_namespace_from_db(db_api_namespace) def list(self, marker=None, limit=None, sort_key='created_at', sort_dir='desc', filters=None): db_namespaces = self.db_api.metadef_namespace_get_all( self.context, marker=marker, limit=limit, sort_key=sort_key, sort_dir=sort_dir, filters=filters ) return [self._format_namespace_from_db(namespace_obj) for namespace_obj in db_namespaces] def remove(self, namespace): try: self.db_api.metadef_namespace_delete(self.context, namespace.namespace) except (exception.NotFound, exception.Forbidden): msg = _("The specified namespace %s could not be found") raise exception.NotFound(msg % namespace.namespace) def remove_objects(self, namespace): try: self.db_api.metadef_object_delete_namespace_content( self.context, namespace.namespace ) except (exception.NotFound, exception.Forbidden): msg = _("The specified namespace %s could not be found") raise exception.NotFound(msg % namespace.namespace) def remove_properties(self, namespace): try: self.db_api.metadef_property_delete_namespace_content( self.context, namespace.namespace ) except (exception.NotFound, exception.Forbidden): msg = _("The specified namespace %s could not be found") raise exception.NotFound(msg % namespace.namespace) def object_count(self, namespace_name): return self.db_api.metadef_object_count( self.context, namespace_name ) def property_count(self, namespace_name): return self.db_api.metadef_property_count( self.context, namespace_name ) def save(self, namespace): try: self.db_api.metadef_namespace_update( self.context, namespace.namespace_id, self._format_namespace_to_db(namespace) ) except exception.NotFound as e: raise exception.NotFound(explanation=e.msg) return namespace class MetadefObjectRepo(object): def __init__(self, context, db_api): self.context = context self.db_api = db_api self.meta_namespace_repo = MetadefNamespaceRepo(context, db_api) def _format_metadef_object_from_db(self, metadata_object, namespace_entity): required_str = metadata_object['required'] required_list = required_str.split(",") if required_str else [] # Convert the persisted json schema to a dict of PropertyTypes property_types = {} json_props = metadata_object['json_schema'] for id in json_props: property_types[id] = json.fromjson(PropertyType, json_props[id]) return glance.domain.MetadefObject( namespace=namespace_entity, object_id=metadata_object['id'], name=metadata_object['name'], required=required_list, description=metadata_object['description'], properties=property_types, created_at=metadata_object['created_at'], updated_at=metadata_object['updated_at'] ) def _format_metadef_object_to_db(self, metadata_object): required_str = (",".join(metadata_object.required) if metadata_object.required else None) # Convert the model PropertyTypes dict to a JSON string properties = metadata_object.properties db_schema = {} if properties: for k, v in properties.items(): json_data = json.tojson(PropertyType, v) db_schema[k] = json_data db_metadata_object = { 'name': metadata_object.name, 'required': required_str, 'description': metadata_object.description, 'json_schema': db_schema } return db_metadata_object def add(self, metadata_object): self.db_api.metadef_object_create( self.context, metadata_object.namespace, self._format_metadef_object_to_db(metadata_object) ) def get(self, namespace, object_name): try: namespace_entity = self.meta_namespace_repo.get(namespace) db_metadata_object = self.db_api.metadef_object_get( self.context, namespace, object_name) except (exception.NotFound, exception.Forbidden): msg = _('Could not find metadata object %s') % object_name raise exception.NotFound(msg) return self._format_metadef_object_from_db(db_metadata_object, namespace_entity) def list(self, marker=None, limit=None, sort_key='created_at', sort_dir='desc', filters=None): namespace = filters['namespace'] namespace_entity = self.meta_namespace_repo.get(namespace) db_metadata_objects = self.db_api.metadef_object_get_all( self.context, namespace) return [self._format_metadef_object_from_db(metadata_object, namespace_entity) for metadata_object in db_metadata_objects] def remove(self, metadata_object): try: self.db_api.metadef_object_delete( self.context, metadata_object.namespace.namespace, metadata_object.name ) except (exception.NotFound, exception.Forbidden): msg = _("The specified metadata object %s could not be found") raise exception.NotFound(msg % metadata_object.name) def save(self, metadata_object): try: self.db_api.metadef_object_update( self.context, metadata_object.namespace.namespace, metadata_object.object_id, self._format_metadef_object_to_db(metadata_object)) except exception.NotFound as e: raise exception.NotFound(explanation=e.msg) return metadata_object class MetadefResourceTypeRepo(object): def __init__(self, context, db_api): self.context = context self.db_api = db_api self.meta_namespace_repo = MetadefNamespaceRepo(context, db_api) def _format_resource_type_from_db(self, resource_type, namespace): return glance.domain.MetadefResourceType( namespace=namespace, name=resource_type['name'], prefix=resource_type['prefix'], properties_target=resource_type['properties_target'], created_at=resource_type['created_at'], updated_at=resource_type['updated_at'] ) def _format_resource_type_to_db(self, resource_type): db_resource_type = { 'name': resource_type.name, 'prefix': resource_type.prefix, 'properties_target': resource_type.properties_target } return db_resource_type def add(self, resource_type): self.db_api.metadef_resource_type_association_create( self.context, resource_type.namespace, self._format_resource_type_to_db(resource_type) ) def get(self, resource_type, namespace): namespace_entity = self.meta_namespace_repo.get(namespace) db_resource_type = ( self.db_api. metadef_resource_type_association_get( self.context, namespace, resource_type ) ) return self._format_resource_type_from_db(db_resource_type, namespace_entity) def list(self, filters=None): namespace = filters['namespace'] if namespace: namespace_entity = self.meta_namespace_repo.get(namespace) db_resource_types = ( self.db_api. metadef_resource_type_association_get_all_by_namespace( self.context, namespace ) ) return [self._format_resource_type_from_db(resource_type, namespace_entity) for resource_type in db_resource_types] else: db_resource_types = ( self.db_api. metadef_resource_type_get_all(self.context) ) return [glance.domain.MetadefResourceType( namespace=None, name=resource_type['name'], prefix=None, properties_target=None, created_at=resource_type['created_at'], updated_at=resource_type['updated_at'] ) for resource_type in db_resource_types] def remove(self, resource_type): try: self.db_api.metadef_resource_type_association_delete( self.context, resource_type.namespace.namespace, resource_type.name) except (exception.NotFound, exception.Forbidden): msg = _("The specified resource type %s could not be found ") raise exception.NotFound(msg % resource_type.name) class MetadefPropertyRepo(object): def __init__(self, context, db_api): self.context = context self.db_api = db_api self.meta_namespace_repo = MetadefNamespaceRepo(context, db_api) def _format_metadef_property_from_db( self, property, namespace_entity): return glance.domain.MetadefProperty( namespace=namespace_entity, property_id=property['id'], name=property['name'], schema=property['json_schema'] ) def _format_metadef_property_to_db(self, property): db_metadata_object = { 'name': property.name, 'json_schema': property.schema } return db_metadata_object def add(self, property): self.db_api.metadef_property_create( self.context, property.namespace, self._format_metadef_property_to_db(property) ) def get(self, namespace, property_name): try: namespace_entity = self.meta_namespace_repo.get(namespace) db_property_type = self.db_api.metadef_property_get( self.context, namespace, property_name ) except (exception.NotFound, exception.Forbidden): msg = _('Could not find property %s') % property_name raise exception.NotFound(msg) return self._format_metadef_property_from_db( db_property_type, namespace_entity) def list(self, marker=None, limit=None, sort_key='created_at', sort_dir='desc', filters=None): namespace = filters['namespace'] namespace_entity = self.meta_namespace_repo.get(namespace) db_properties = self.db_api.metadef_property_get_all( self.context, namespace) return ( [self._format_metadef_property_from_db( property, namespace_entity) for property in db_properties] ) def remove(self, property): try: self.db_api.metadef_property_delete( self.context, property.namespace.namespace, property.name) except (exception.NotFound, exception.Forbidden): msg = _("The specified property %s could not be found") raise exception.NotFound(msg % property.name) def save(self, property): try: self.db_api.metadef_property_update( self.context, property.namespace.namespace, property.property_id, self._format_metadef_property_to_db(property) ) except exception.NotFound as e: raise exception.NotFound(explanation=e.msg) return property