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)
|