Merge "Update v1/v2 images APIs to set store ACLs."

This commit is contained in:
Jenkins 2012-08-02 18:37:58 +00:00 committed by Gerrit Code Review
commit 4c4f62751b
11 changed files with 145 additions and 14 deletions

View File

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

View File

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

View File

@ -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"""

View File

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

View File

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

View File

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

View File

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

View File

@ -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."))

View File

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

View File

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

View File

@ -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'],