~ubuntu-branches/ubuntu/quantal/glance/quantal

« back to all changes in this revision

Viewing changes to glance/api/v1/images.py

  • Committer: Package Import Robot
  • Author(s): Chuck Short, Chuck Short, Adam Gandleman
  • Date: 2012-01-26 09:22:37 UTC
  • mfrom: (1.1.25)
  • Revision ID: package-import@ubuntu.com-20120126092237-3wvlfjtg0ut3231r
Tags: 2012.1~e3-0ubuntu1
[Chuck Short]
* New upstream version.
* debian/control: Add python-crypto as a build dependency.

[Adam Gandleman]
* debian/glance-api.install: Add policy.json

Show diffs side-by-side

added added

removed removed

Lines of Context:
20
20
"""
21
21
 
22
22
import errno
23
 
import json
24
23
import logging
25
24
import traceback
26
25
 
27
 
from webob.exc import (HTTPNotFound,
 
26
from webob.exc import (HTTPError,
 
27
                       HTTPNotFound,
28
28
                       HTTPConflict,
29
29
                       HTTPBadRequest,
30
30
                       HTTPForbidden,
31
 
                       HTTPNoContent,
32
31
                       HTTPUnauthorized)
33
32
 
 
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
48
47
                          get_size_from_backend,
49
48
                          schedule_delete_from_backend,
50
49
                          get_store_from_location,
51
 
                          get_store_from_scheme,
52
 
                          UnsupportedBackend)
 
50
                          get_store_from_scheme)
53
51
from glance import registry
54
52
from glance import notifier
55
53
 
59
57
SUPPORTED_FILTERS = glance.api.v1.SUPPORTED_FILTERS
60
58
 
61
59
 
 
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
 
63
# as a BigInteger.
 
64
IMAGE_SIZE_CAP = 1 << 50
 
65
 
 
66
 
62
67
class Controller(controller.BaseController):
63
68
    """
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)
 
95
 
 
96
    def _enforce(self, req, action):
 
97
        """Authorize an action against our policies"""
 
98
        try:
 
99
            self.policy.enforce(req.context, action, {})
 
100
        except exception.NotAuthorized:
 
101
            raise HTTPUnauthorized()
89
102
 
90
103
    def index(self, req):
91
104
        """
110
123
                 'size': <SIZE>}, ...
111
124
            ]}
112
125
        """
 
126
        self._enforce(req, 'get_images')
113
127
        params = self._get_query_params(req)
114
128
        try:
115
129
            images = registry.get_images_list(req.context, **params)
142
156
                 'properties': {'distro': 'Ubuntu 10.04 LTS', ...}}, ...
143
157
            ]}
144
158
        """
 
159
        self._enforce(req, 'get_images')
145
160
        params = self._get_query_params(req)
146
161
        try:
147
162
            images = registry.get_images_detail(req.context, **params)
194
209
 
195
210
        :raises HTTPNotFound if image metadata is not available to user
196
211
        """
 
212
        self._enforce(req, 'get_image')
197
213
        image_meta = self.get_image_meta_or_404(req, id)
198
214
        del image_meta['location']
199
215
        return {
210
226
 
211
227
        :raises HTTPNotFound if image is not available to user
212
228
        """
 
229
        self._enforce(req, 'get_image')
213
230
        image_meta = self.get_active_image_meta_or_404(req, id)
214
231
 
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"))
320
337
                image_size = 0
 
338
 
 
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()
 
344
                logger.warn(msg)
 
345
                raise HTTPBadRequest(msg, request=request)
 
346
 
321
347
            location, size, checksum = store.add(image_meta['id'],
322
348
                                                 req.body_file,
323
349
                                                 image_size)
362
388
            raise HTTPForbidden(msg, request=req,
363
389
                                content_type='text/plain')
364
390
 
 
391
        except HTTPError, e:
 
392
            self._safe_kill(req, image_id)
 
393
            self.notifier.error('image.upload', e.explanation)
 
394
            raise
 
395
 
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
471
502
                image data.
472
503
        """
 
504
        self._enforce(req, 'add_image')
473
505
        if req.context.read_only:
474
506
            msg = _("Read-only access")
475
507
            logger.debug(msg)
501
533
 
502
534
        :retval Returns the updated image information as a mapping
503
535
        """
 
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"))
527
560
 
 
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")
 
568
 
528
569
        try:
529
570
            image_meta = registry.update_image_metadata(req.context, id,
530
571
                                                        image_meta,
565
606
        :raises HttpNotAuthorized if image or any chunk is not
566
607
                deleteable by the requesting user
567
608
        """
 
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
603
645
 
604
646
        :param request: The WSGI/Webob Request object
605
 
        :param id: The opaque image identifier
 
647
        :param store_name: The backend store name
606
648
 
607
 
        :raises HTTPNotFound if image does not exist
 
649
        :raises HTTPNotFound if store does not exist
608
650
        """
609
651
        try:
610
652
            return get_store_from_scheme(store_name)
615
657
            raise HTTPBadRequest(msg, request=request,
616
658
                                 content_type='text/plain')
617
659
 
 
660
    def verify_store_or_exit(self, store_name):
 
661
        """
 
662
        Verifies availability of the storage backend for the
 
663
        given store name or exits
 
664
 
 
665
        :param store_name: The backend store name
 
666
        """
 
667
        try:
 
668
            get_store_from_scheme(store_name)
 
669
        except exception.UnknownScheme:
 
670
            msg = (_("Default store %s not available on this Glance server\n")
 
671
                   % store_name)
 
672
            logger.error(msg)
 
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)
 
676
            sys.exit(255)
 
677
 
618
678
 
619
679
class ImageDeserializer(wsgi.JSONRequestDeserializer):
620
680
    """Handles deserialization of specific controller method requests."""
621
681
 
622
682
    def _deserialize(self, request):
623
683
        result = {}
624
 
        result['image_meta'] = utils.get_image_meta_from_headers(request)
 
684
        try:
 
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)
 
691
 
 
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()
 
700
                logger.warn(msg)
 
701
                raise HTTPBadRequest(msg, request=request)
 
702
 
625
703
        data = request.body_file if self.has_body(request) else None
626
704
        result['image_data'] = data
627
705
        return result