27
from webob.exc import (HTTPNotFound,
26
from webob.exc import (HTTPError,
33
from glance.api import policy
34
34
import glance.api.v1
35
35
from glance.api.v1 import controller
36
from glance import image_cache
37
36
from glance.common import cfg
38
37
from glance.common import exception
39
38
from glance.common import wsgi
59
57
SUPPORTED_FILTERS = glance.api.v1.SUPPORTED_FILTERS
60
# 1 PiB, which is a *huge* image by anyone's measure. This is just to protect
61
# against client programming errors (or DoS attacks) in the image metadata.
62
# We have a known limit of 1 << 63 in the database -- images.size is declared
64
IMAGE_SIZE_CAP = 1 << 50
62
67
class Controller(controller.BaseController):
64
69
WSGI controller for images resource in Glance v1 API
86
91
glance.store.create_stores(conf)
87
92
self.notifier = notifier.Notifier(conf)
88
93
registry.configure_registry_client(conf)
94
self.policy = policy.Enforcer(conf)
96
def _enforce(self, req, action):
97
"""Authorize an action against our policies"""
99
self.policy.enforce(req.context, action, {})
100
except exception.NotAuthorized:
101
raise HTTPUnauthorized()
90
103
def index(self, req):
142
156
'properties': {'distro': 'Ubuntu 10.04 LTS', ...}}, ...
159
self._enforce(req, 'get_images')
145
160
params = self._get_query_params(req)
147
162
images = registry.get_images_detail(req.context, **params)
195
210
:raises HTTPNotFound if image metadata is not available to user
212
self._enforce(req, 'get_image')
197
213
image_meta = self.get_image_meta_or_404(req, id)
198
214
del image_meta['location']
211
227
:raises HTTPNotFound if image is not available to user
229
self._enforce(req, 'get_image')
213
230
image_meta = self.get_active_image_meta_or_404(req, id)
215
232
def get_from_store(image_meta):
318
335
logger.debug(_("Got request with no content-length and no "
319
336
"x-image-meta-size header"))
339
if image_size > IMAGE_SIZE_CAP:
340
max_image_size = IMAGE_SIZE_CAP
341
msg = _("Denying attempt to upload image larger than "
342
"%(max_image_size)d. Supplied image size was "
343
"%(image_size)d") % locals()
345
raise HTTPBadRequest(msg, request=request)
321
347
location, size, checksum = store.add(image_meta['id'],
362
388
raise HTTPForbidden(msg, request=req,
363
389
content_type='text/plain')
392
self._safe_kill(req, image_id)
393
self.notifier.error('image.upload', e.explanation)
365
396
except Exception, e:
366
397
tb_info = traceback.format_exc()
367
398
logger.error(tb_info)
470
501
and the request body is not application/octet-stream
504
self._enforce(req, 'add_image')
473
505
if req.context.read_only:
474
506
msg = _("Read-only access")
475
507
logger.debug(msg)
502
534
:retval Returns the updated image information as a mapping
536
self._enforce(req, 'modify_image')
504
537
if req.context.read_only:
505
538
msg = _("Read-only access")
506
539
logger.debug(msg)
525
558
if image_data is not None and orig_status != 'queued':
526
559
raise HTTPConflict(_("Cannot upload to an unqueued image"))
561
# Only allow the Location fields to be modified if the image is
562
# in queued status, which indicates that the user called POST /images
563
# but did not supply either a Location field OR image data
564
if not orig_status == 'queued' and 'location' in image_meta:
565
msg = _("Attempted to update Location field for an image "
566
"not in queued status.")
567
raise HTTPBadRequest(msg, request=req, content_type="text/plain")
529
570
image_meta = registry.update_image_metadata(req.context, id,
565
606
:raises HttpNotAuthorized if image or any chunk is not
566
607
deleteable by the requesting user
609
self._enforce(req, 'delete_image')
568
610
if req.context.read_only:
569
611
msg = _("Read-only access")
570
612
logger.debug(msg)
602
644
or raises an HTTPBadRequest (400) response
604
646
:param request: The WSGI/Webob Request object
605
:param id: The opaque image identifier
647
:param store_name: The backend store name
607
:raises HTTPNotFound if image does not exist
649
:raises HTTPNotFound if store does not exist
610
652
return get_store_from_scheme(store_name)
615
657
raise HTTPBadRequest(msg, request=request,
616
658
content_type='text/plain')
660
def verify_store_or_exit(self, store_name):
662
Verifies availability of the storage backend for the
663
given store name or exits
665
:param store_name: The backend store name
668
get_store_from_scheme(store_name)
669
except exception.UnknownScheme:
670
msg = (_("Default store %s not available on this Glance server\n")
673
# message on stderr will only be visible if started directly via
674
# bin/glance-api, as opposed to being daemonized by glance-control
675
sys.stderr.write(msg)
619
679
class ImageDeserializer(wsgi.JSONRequestDeserializer):
620
680
"""Handles deserialization of specific controller method requests."""
622
682
def _deserialize(self, request):
624
result['image_meta'] = utils.get_image_meta_from_headers(request)
685
result['image_meta'] = utils.get_image_meta_from_headers(request)
686
except exception.Invalid:
687
image_size_str = request.headers['x-image-meta-size']
688
msg = _("Incoming image size of %s was not convertible to "
689
"an integer.") % image_size_str
690
raise HTTPBadRequest(msg, request=request)
692
image_meta = result['image_meta']
693
if 'size' in image_meta:
694
incoming_image_size = image_meta['size']
695
if incoming_image_size > IMAGE_SIZE_CAP:
696
max_image_size = IMAGE_SIZE_CAP
697
msg = _("Denying attempt to upload image larger than "
698
"%(max_image_size)d. Supplied image size was "
699
"%(incoming_image_size)d") % locals()
701
raise HTTPBadRequest(msg, request=request)
625
703
data = request.body_file if self.has_body(request) else None
626
704
result['image_data'] = data