
Move the core of glance.tests.unit.utils:FakeDB to glance.db.simple. This 'simple' driver is an alternative to the traditional sqlalchemy driver. Additionally, the sqlalchemy driver has been moved from glance.db to glance.db.sqlalchemy. The simple db driver is only available to be used by tests for now. Related to bp refactor-db-layer Change-Id: I9d33a433c0c03e53fb5a3491076086427ae694b3
231 lines
7.9 KiB
Python
231 lines
7.9 KiB
Python
# Copyright 2012 OpenStack LLC.
|
|
# 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.
|
|
|
|
import datetime
|
|
import json
|
|
|
|
import webob.exc
|
|
|
|
from glance.api.v2 import base
|
|
from glance.common import exception
|
|
from glance.common import utils
|
|
from glance.common import wsgi
|
|
import glance.db.sqlalchemy.api
|
|
from glance.openstack.common import timeutils
|
|
|
|
|
|
class ImagesController(base.Controller):
|
|
def __init__(self, conf, db_api=None):
|
|
super(ImagesController, self).__init__(conf)
|
|
self.db_api = db_api or glance.db.sqlalchemy.api
|
|
self.db_api.configure_db(conf)
|
|
|
|
def _normalize_properties(self, image):
|
|
"""Convert the properties from the stored format to a dict
|
|
|
|
The db api returns a list of dicts that look like
|
|
{'name': <key>, 'value': <value>}, while it expects a format
|
|
like {<key>: <value>} in image create and update calls. This
|
|
function takes the extra step that the db api should be
|
|
responsible for in the image get calls.
|
|
"""
|
|
properties = [(p['name'], p['value']) for p in image['properties']]
|
|
image['properties'] = dict(properties)
|
|
return image
|
|
|
|
def _extract_tags(self, image):
|
|
try:
|
|
return image.pop('tags')
|
|
except KeyError:
|
|
pass
|
|
|
|
def _append_tags(self, context, image):
|
|
image['tags'] = self.db_api.image_tag_get_all(context, image['id'])
|
|
return image
|
|
|
|
@utils.mutating
|
|
def create(self, req, image):
|
|
if 'owner' not in image:
|
|
image['owner'] = req.context.owner
|
|
elif not req.context.is_admin:
|
|
raise webob.exc.HTTPForbidden()
|
|
|
|
#TODO(bcwaldon): this should eventually be settable through the API
|
|
image['status'] = 'queued'
|
|
|
|
tags = self._extract_tags(image)
|
|
|
|
image = dict(self.db_api.image_create(req.context, image))
|
|
|
|
if tags is not None:
|
|
self.db_api.image_tag_set_all(req.context, image['id'], tags)
|
|
image['tags'] = tags
|
|
else:
|
|
image['tags'] = []
|
|
|
|
return self._normalize_properties(dict(image))
|
|
|
|
def index(self, req):
|
|
#NOTE(bcwaldon): is_public=True gets public images and those
|
|
# owned by the authenticated tenant
|
|
filters = {'deleted': False, 'is_public': True}
|
|
images = self.db_api.image_get_all(req.context, filters=filters)
|
|
images = [self._normalize_properties(dict(image)) for image in images]
|
|
return [self._append_tags(req.context, image) for image in images]
|
|
|
|
def show(self, req, image_id):
|
|
try:
|
|
image = self.db_api.image_get(req.context, image_id)
|
|
except (exception.NotFound, exception.Forbidden):
|
|
raise webob.exc.HTTPNotFound()
|
|
image = self._normalize_properties(dict(image))
|
|
return self._append_tags(req.context, image)
|
|
|
|
@utils.mutating
|
|
def update(self, req, image_id, image):
|
|
tags = self._extract_tags(image)
|
|
|
|
try:
|
|
image = self.db_api.image_update(req.context, image_id, image)
|
|
except (exception.NotFound, exception.Forbidden):
|
|
raise webob.exc.HTTPNotFound()
|
|
|
|
image = self._normalize_properties(dict(image))
|
|
|
|
if tags is not None:
|
|
self.db_api.image_tag_set_all(req.context, image_id, tags)
|
|
image['tags'] = tags
|
|
else:
|
|
self._append_tags(req.context, image)
|
|
|
|
return image
|
|
|
|
@utils.mutating
|
|
def delete(self, req, image_id):
|
|
try:
|
|
self.db_api.image_destroy(req.context, image_id)
|
|
except (exception.NotFound, exception.Forbidden):
|
|
raise webob.exc.HTTPNotFound()
|
|
|
|
|
|
class RequestDeserializer(wsgi.JSONRequestDeserializer):
|
|
def __init__(self, conf, schema_api):
|
|
super(RequestDeserializer, self).__init__()
|
|
self.conf = conf
|
|
self.schema_api = schema_api
|
|
|
|
def _parse_image(self, request):
|
|
output = super(RequestDeserializer, self).default(request)
|
|
body = output.pop('body')
|
|
self.schema_api.validate('image', body)
|
|
|
|
# Create a dict of base image properties, with user- and deployer-
|
|
# defined properties contained in a 'properties' dictionary
|
|
image = {'properties': body}
|
|
for key in ['id', 'name', 'visibility', 'created_at', 'updated_at',
|
|
'tags']:
|
|
try:
|
|
image[key] = image['properties'].pop(key)
|
|
except KeyError:
|
|
pass
|
|
|
|
if 'visibility' in image:
|
|
image['is_public'] = image.pop('visibility') == 'public'
|
|
|
|
self._remove_readonly(image)
|
|
return {'image': image}
|
|
|
|
@staticmethod
|
|
def _remove_readonly(image):
|
|
for key in ['created_at', 'updated_at']:
|
|
if key in image:
|
|
del image[key]
|
|
|
|
def create(self, request):
|
|
return self._parse_image(request)
|
|
|
|
def update(self, request):
|
|
return self._parse_image(request)
|
|
|
|
|
|
class ResponseSerializer(wsgi.JSONResponseSerializer):
|
|
def __init__(self, schema_api):
|
|
super(ResponseSerializer, self).__init__()
|
|
self.schema_api = schema_api
|
|
|
|
def _get_image_href(self, image, subcollection=''):
|
|
base_href = '/v2/images/%s' % image['id']
|
|
if subcollection:
|
|
base_href = '%s/%s' % (base_href, subcollection)
|
|
return base_href
|
|
|
|
def _get_image_links(self, image):
|
|
return [
|
|
{'rel': 'self', 'href': self._get_image_href(image)},
|
|
{'rel': 'file', 'href': self._get_image_href(image, 'file')},
|
|
{'rel': 'describedby', 'href': '/v2/schemas/image'},
|
|
]
|
|
|
|
def _filter_allowed_image_attributes(self, image):
|
|
schema = self.schema_api.get_schema('image')
|
|
if schema.get('additionalProperties', True):
|
|
return dict(image.iteritems())
|
|
attrs = schema['properties'].keys()
|
|
return dict((k, v) for (k, v) in image.iteritems() if k in attrs)
|
|
|
|
def _format_image(self, image):
|
|
_image = image['properties']
|
|
_image = self._filter_allowed_image_attributes(_image)
|
|
for key in ['id', 'name', 'created_at', 'updated_at', 'tags']:
|
|
_image[key] = image[key]
|
|
_image['visibility'] = 'public' if image['is_public'] else 'private'
|
|
_image['links'] = self._get_image_links(image)
|
|
self._serialize_datetimes(_image)
|
|
return _image
|
|
|
|
@staticmethod
|
|
def _serialize_datetimes(image):
|
|
for (key, value) in image.iteritems():
|
|
if isinstance(value, datetime.datetime):
|
|
image[key] = timeutils.isotime(value)
|
|
|
|
def create(self, response, image):
|
|
response.body = json.dumps({'image': self._format_image(image)})
|
|
response.location = self._get_image_href(image)
|
|
|
|
def show(self, response, image):
|
|
response.body = json.dumps({'image': self._format_image(image)})
|
|
|
|
def update(self, response, image):
|
|
response.body = json.dumps({'image': self._format_image(image)})
|
|
|
|
def index(self, response, images):
|
|
body = {
|
|
'images': [self._format_image(i) for i in images],
|
|
'links': [],
|
|
}
|
|
response.body = json.dumps(body)
|
|
|
|
def delete(self, response, result):
|
|
response.status_int = 204
|
|
|
|
|
|
def create_resource(conf, schema_api):
|
|
"""Images resource factory method"""
|
|
deserializer = RequestDeserializer(conf, schema_api)
|
|
serializer = ResponseSerializer(schema_api)
|
|
controller = ImagesController(conf)
|
|
return wsgi.Resource(controller, deserializer, serializer)
|