Merge "Update v1/v2 images APIs to set store ACLs."
This commit is contained in:
commit
4c4f62751b
@ -20,6 +20,7 @@ import webob.exc
|
||||
from glance.common import exception
|
||||
import glance.openstack.common.log as logging
|
||||
from glance import registry
|
||||
import glance.store as store
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
@ -61,3 +62,24 @@ class BaseController(object):
|
||||
raise webob.exc.HTTPNotFound(
|
||||
msg, request=request, content_type='text/plain')
|
||||
return image
|
||||
|
||||
def update_store_acls(self, req, image_id, location_uri, public=False):
|
||||
if location_uri:
|
||||
try:
|
||||
read_tenants = []
|
||||
write_tenants = []
|
||||
members = registry.get_image_members(req.context, image_id)
|
||||
if members:
|
||||
for member in members:
|
||||
if member['can_share']:
|
||||
write_tenants.append(member['member_id'])
|
||||
else:
|
||||
read_tenants.append(member['member_id'])
|
||||
store.set_acls(req.context, location_uri, public=public,
|
||||
read_tenants=read_tenants,
|
||||
write_tenants=write_tenants)
|
||||
except exception.UnknownScheme:
|
||||
msg = _("Store for image_id not found: %s") % image_id
|
||||
raise HTTPBadRequest(explanation=msg,
|
||||
request=req,
|
||||
content_type='text/plain')
|
||||
|
@ -617,7 +617,8 @@ class Controller(controller.BaseController):
|
||||
image data.
|
||||
"""
|
||||
self._enforce(req, 'add_image')
|
||||
if image_meta.get('is_public'):
|
||||
is_public = image_meta.get('is_public')
|
||||
if is_public:
|
||||
self._enforce(req, 'publicize_image')
|
||||
|
||||
image_meta = self._reserve(req, image_meta)
|
||||
@ -625,6 +626,10 @@ class Controller(controller.BaseController):
|
||||
|
||||
image_meta = self._handle_source(req, id, image_meta, image_data)
|
||||
|
||||
location_uri = image_meta.get('location')
|
||||
if location_uri:
|
||||
self.update_store_acls(req, id, location_uri, public=is_public)
|
||||
|
||||
# Prevent client from learning the location, as it
|
||||
# could contain security credentials
|
||||
image_meta.pop('location', None)
|
||||
@ -642,7 +647,8 @@ class Controller(controller.BaseController):
|
||||
:retval Returns the updated image information as a mapping
|
||||
"""
|
||||
self._enforce(req, 'modify_image')
|
||||
if image_meta.get('is_public'):
|
||||
is_public = image_meta.get('is_public')
|
||||
if is_public:
|
||||
self._enforce(req, 'publicize_image')
|
||||
|
||||
orig_image_meta = self.get_image_meta_or_404(req, id)
|
||||
@ -671,6 +677,12 @@ class Controller(controller.BaseController):
|
||||
reactivating = orig_status != 'queued' and location
|
||||
activating = orig_status == 'queued' and (location or image_data)
|
||||
|
||||
# Make image public in the backend store (if implemented)
|
||||
orig_or_updated_loc = location or orig_image_meta.get('location', None)
|
||||
if orig_or_updated_loc:
|
||||
self.update_store_acls(req, id, orig_or_updated_loc,
|
||||
public=is_public)
|
||||
|
||||
if reactivating:
|
||||
msg = _("Attempted to update Location field for an image "
|
||||
"not in queued status.")
|
||||
@ -691,6 +703,7 @@ class Controller(controller.BaseController):
|
||||
if activating:
|
||||
image_meta = self._handle_source(req, id, image_meta,
|
||||
image_data)
|
||||
|
||||
except exception.Invalid, e:
|
||||
msg = (_("Failed to update image metadata. Got error: %(e)s")
|
||||
% locals())
|
||||
|
@ -18,16 +18,16 @@
|
||||
import webob.exc
|
||||
|
||||
from glance.common import exception
|
||||
from glance.api.v1 import controller
|
||||
from glance.common import utils
|
||||
from glance.common import wsgi
|
||||
import glance.openstack.common.log as logging
|
||||
from glance import registry
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Controller(object):
|
||||
class Controller(controller.BaseController):
|
||||
|
||||
def _check_can_access_image_members(self, context):
|
||||
if context.owner is None and not context.is_admin:
|
||||
@ -68,6 +68,7 @@ class Controller(object):
|
||||
|
||||
try:
|
||||
registry.delete_member(req.context, image_id, id)
|
||||
self._update_store_acls(req, image_id)
|
||||
except exception.NotFound, e:
|
||||
msg = "%s" % e
|
||||
LOG.debug(msg)
|
||||
@ -105,6 +106,7 @@ class Controller(object):
|
||||
can_share = bool(body['member']['can_share'])
|
||||
try:
|
||||
registry.add_member(req.context, image_id, id, can_share)
|
||||
self._update_store_acls(req, image_id)
|
||||
except exception.Invalid, e:
|
||||
msg = "%s" % e
|
||||
LOG.debug(msg)
|
||||
@ -135,6 +137,7 @@ class Controller(object):
|
||||
|
||||
try:
|
||||
registry.replace_members(req.context, image_id, body)
|
||||
self._update_store_acls(req, image_id)
|
||||
except exception.Invalid, e:
|
||||
msg = "%s" % e
|
||||
LOG.debug(msg)
|
||||
@ -175,6 +178,12 @@ class Controller(object):
|
||||
raise webob.exc.HTTPForbidden(msg)
|
||||
return dict(shared_images=members)
|
||||
|
||||
def _update_store_acls(self, req, image_id):
|
||||
image_meta = self.get_image_meta_or_404(req, image_id)
|
||||
location_uri = image_meta.get('location')
|
||||
public = image_meta.get('is_public')
|
||||
self.update_store_acls(req, image_id, location_uri, public)
|
||||
|
||||
|
||||
def create_resource():
|
||||
"""Image members resource factory method"""
|
||||
|
@ -0,0 +1,46 @@
|
||||
# Copyright 2012 Red Hat Inc.
|
||||
# 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 webob.exc
|
||||
|
||||
from glance.common import exception
|
||||
from glance.store import set_acls
|
||||
|
||||
|
||||
def update_image_read_acl(req, db_api, image):
|
||||
"""Helper function to set ACL permissions on images in the image store"""
|
||||
location_uri = image['location']
|
||||
public = image['is_public']
|
||||
image_id = image['id']
|
||||
if location_uri:
|
||||
try:
|
||||
read_tenants = []
|
||||
write_tenants = []
|
||||
members = db_api.image_member_find(req.context,
|
||||
image_id=image_id)
|
||||
for member in members:
|
||||
if not member['deleted']:
|
||||
if member['can_share']:
|
||||
write_tenants.append(member['member'])
|
||||
else:
|
||||
read_tenants.append(member['member'])
|
||||
set_acls(req.context, location_uri, public=public,
|
||||
read_tenants=read_tenants,
|
||||
write_tenants=write_tenants)
|
||||
except exception.UnknownScheme:
|
||||
msg = _("Store for image_id not found: %s") % image_id
|
||||
raise webob.exc.HTTPBadRequest(explanation=msg,
|
||||
request=req,
|
||||
content_type='text/plain')
|
@ -17,6 +17,7 @@ import json
|
||||
|
||||
import webob.exc
|
||||
|
||||
import glance.api.v2 as v2
|
||||
from glance.common import exception
|
||||
from glance.common import utils
|
||||
from glance.common import wsgi
|
||||
@ -73,13 +74,27 @@ class Controller(object):
|
||||
raise webob.exc.HTTPForbidden(msg)
|
||||
|
||||
access_record['image_id'] = image_id
|
||||
return self.db_api.image_member_create(req.context, access_record)
|
||||
member = self.db_api.image_member_create(req.context, access_record)
|
||||
|
||||
v2.update_image_read_acl(req, self.db_api, image)
|
||||
|
||||
return member
|
||||
|
||||
@utils.mutating
|
||||
def delete(self, req, image_id, tenant_id):
|
||||
#TODO(bcwaldon): Refactor these methods so we don't need to explicitly
|
||||
# retrieve a session object here
|
||||
session = self.db_api.get_session()
|
||||
try:
|
||||
image = self.db_api.image_get(req.context, image_id,
|
||||
session=session)
|
||||
except exception.NotFound:
|
||||
raise webob.exc.HTTPNotFound()
|
||||
except exception.Forbidden:
|
||||
# If it's private and doesn't belong to them, don't let on
|
||||
# that it exists
|
||||
raise webob.exc.HTTPNotFound()
|
||||
|
||||
members = self.db_api.image_member_find(req.context,
|
||||
image_id=image_id,
|
||||
member=tenant_id,
|
||||
@ -90,6 +105,7 @@ class Controller(object):
|
||||
raise webob.exc.HTTPNotFound()
|
||||
|
||||
self.db_api.image_member_delete(req.context, member, session=session)
|
||||
v2.update_image_read_acl(req, self.db_api, image)
|
||||
|
||||
|
||||
class RequestDeserializer(wsgi.JSONRequestDeserializer):
|
||||
|
@ -16,6 +16,7 @@
|
||||
import webob.exc
|
||||
|
||||
from glance.api import common
|
||||
import glance.api.v2 as v2
|
||||
from glance.common import exception
|
||||
from glance.common import utils
|
||||
from glance.common import wsgi
|
||||
@ -39,13 +40,15 @@ class ImageDataController(object):
|
||||
|
||||
@utils.mutating
|
||||
def upload(self, req, image_id, data, size):
|
||||
self._get_image(req.context, image_id)
|
||||
image = self._get_image(req.context, image_id)
|
||||
try:
|
||||
location, size, checksum = self.store_api.add_to_backend(
|
||||
req.context, 'file', image_id, data, size)
|
||||
except exception.Duplicate:
|
||||
raise webob.exc.HTTPConflict()
|
||||
|
||||
v2.update_image_read_acl(req, self.db_api, image)
|
||||
|
||||
values = {'location': location, 'size': size, 'checksum': checksum}
|
||||
self.db_api.image_update(req.context, image_id, values)
|
||||
|
||||
|
@ -20,6 +20,7 @@ import urllib
|
||||
|
||||
import webob.exc
|
||||
|
||||
import glance.api.v2 as v2
|
||||
from glance.common import exception
|
||||
from glance.common import utils
|
||||
from glance.common import wsgi
|
||||
@ -78,6 +79,8 @@ class ImagesController(object):
|
||||
else:
|
||||
image['tags'] = []
|
||||
|
||||
v2.update_image_read_acl(req, self.db_api, image)
|
||||
|
||||
return self._normalize_properties(dict(image))
|
||||
|
||||
def index(self, req, marker=None, limit=None, sort_key='created_at',
|
||||
@ -128,6 +131,8 @@ class ImagesController(object):
|
||||
|
||||
image = self._normalize_properties(dict(image))
|
||||
|
||||
v2.update_image_read_acl(req, self.db_api, image)
|
||||
|
||||
if tags is not None:
|
||||
self.db_api.image_tag_set_all(req.context, image_id, tags)
|
||||
image['tags'] = tags
|
||||
|
@ -296,3 +296,16 @@ def schedule_delete_from_backend(uri, context, image_id, **kwargs):
|
||||
def add_to_backend(context, scheme, image_id, data, size):
|
||||
store = get_store_from_scheme(context, scheme)
|
||||
return store.add(image_id, data, size)
|
||||
|
||||
|
||||
def set_acls(context, location_uri, public=False, read_tenants=[],
|
||||
write_tenants=[]):
|
||||
scheme = get_store_from_location(location_uri)
|
||||
store = get_store_from_scheme(context, scheme)
|
||||
try:
|
||||
store.set_acls(location.get_location_from_uri(location_uri),
|
||||
public=public,
|
||||
read_tenants=read_tenants,
|
||||
write_tenants=write_tenants)
|
||||
except NotImplementedError:
|
||||
LOG.debug(_("Skipping store.set_acls... not implemented."))
|
||||
|
@ -57,7 +57,8 @@ class FakeDB(object):
|
||||
@staticmethod
|
||||
def init_db():
|
||||
images = [
|
||||
{'id': UUID1, 'owner': TENANT1, 'location': UUID1},
|
||||
{'id': UUID1, 'owner': TENANT1,
|
||||
'location': 'swift+http://storeurl.com/container/%s' % UUID1},
|
||||
{'id': UUID2, 'owner': TENANT1},
|
||||
]
|
||||
[simple_db.image_create(None, image) for image in images]
|
||||
@ -85,7 +86,7 @@ class FakeDB(object):
|
||||
class FakeStoreAPI(object):
|
||||
def __init__(self):
|
||||
self.data = {
|
||||
UUID1: ('XXX', 3),
|
||||
'swift+http://storeurl.com/container/%s' % UUID1: ('XXX', 3),
|
||||
}
|
||||
|
||||
def create_stores(self):
|
||||
@ -93,8 +94,6 @@ class FakeStoreAPI(object):
|
||||
|
||||
def get_from_backend(self, context, location):
|
||||
try:
|
||||
#NOTE(bcwaldon): This fake API is store-agnostic, so we only
|
||||
# care about location being some unique string
|
||||
return self.data[location]
|
||||
except KeyError:
|
||||
raise exception.NotFound()
|
||||
@ -103,8 +102,9 @@ class FakeStoreAPI(object):
|
||||
return self.get_from_backend(context, location)[1]
|
||||
|
||||
def add_to_backend(self, context, scheme, image_id, data, size):
|
||||
if image_id in self.data:
|
||||
raise exception.Duplicate()
|
||||
for location in self.data.keys():
|
||||
if image_id in location:
|
||||
raise exception.Duplicate()
|
||||
self.data[image_id] = (data, size or len(data))
|
||||
checksum = 'Z'
|
||||
return (image_id, size, checksum)
|
||||
|
@ -21,6 +21,7 @@ import glance.api.v2.image_access
|
||||
from glance.common import exception
|
||||
from glance.common import utils
|
||||
import glance.schema
|
||||
import glance.store
|
||||
import glance.tests.unit.utils as unit_test_utils
|
||||
import glance.tests.utils as test_utils
|
||||
|
||||
@ -31,6 +32,7 @@ class TestImageAccessController(test_utils.BaseTestCase):
|
||||
super(TestImageAccessController, self).setUp()
|
||||
self.db = unit_test_utils.FakeDB()
|
||||
self.controller = glance.api.v2.image_access.Controller(self.db)
|
||||
glance.store.create_stores()
|
||||
|
||||
def test_index(self):
|
||||
req = unit_test_utils.get_fake_request()
|
||||
|
@ -25,6 +25,7 @@ from glance.openstack.common import cfg
|
||||
import glance.schema
|
||||
import glance.tests.unit.utils as unit_test_utils
|
||||
import glance.tests.utils as test_utils
|
||||
import glance.store
|
||||
|
||||
|
||||
DATETIME = datetime.datetime(2012, 5, 16, 15, 27, 36, 325355)
|
||||
@ -52,6 +53,7 @@ class TestImagesController(test_utils.BaseTestCase):
|
||||
self.db = unit_test_utils.FakeDB()
|
||||
self._create_images()
|
||||
self.controller = glance.api.v2.images.ImagesController(self.db)
|
||||
glance.store.create_stores()
|
||||
|
||||
def _create_images(self):
|
||||
self.db.reset()
|
||||
@ -59,7 +61,7 @@ class TestImagesController(test_utils.BaseTestCase):
|
||||
{
|
||||
'id': UUID1,
|
||||
'owner': TENANT1,
|
||||
'location': UUID1,
|
||||
'location': 'swift+http://storeurl.com/container/%s' % UUID1,
|
||||
'name': '1',
|
||||
'is_public': True,
|
||||
'size': 256,
|
||||
@ -362,7 +364,7 @@ class TestImagesController(test_utils.BaseTestCase):
|
||||
'name': 'image-2',
|
||||
'owner': TENANT1,
|
||||
'size': 256,
|
||||
'location': UUID1,
|
||||
'location': 'swift+http://storeurl.com/container/%s' % UUID1,
|
||||
'status': 'queued',
|
||||
'is_public': True,
|
||||
'tags': ['ping', 'pong'],
|
||||
|
Loading…
Reference in New Issue
Block a user