Add config option to limit image members
This patch adds the image_member_quota config option. This allows a deployer to limit the number of image members allowed per image. The default value is 128, to be consistent with other quota defaults. Users will only be able to update an image if the result of the transaction would be under this limit. This is for both Glance v1 and v2 Fixes bug 1252459 docImpact Change-Id: I02f5e82ca4c4acf6cd7bc94f9b99086054a616c9
This commit is contained in:
parent
4e7d9cdaf9
commit
b13e10b5e5
|
@ -431,6 +431,9 @@ scrubber_datadir = /var/lib/glance/scrubber
|
|||
|
||||
# =============== Quota Options ==================================
|
||||
|
||||
# The maximum number of image members allowed per image
|
||||
#image_member_quota = 128
|
||||
|
||||
# The maximum number of image properties allowed per image
|
||||
#image_property_quota = 128
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from oslo.config import cfg
|
||||
import webob.exc
|
||||
|
||||
from glance.api import policy
|
||||
|
@ -27,6 +28,8 @@ import glance.openstack.common.log as logging
|
|||
import glance.registry.client.v1.api as registry
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
CONF = cfg.CONF
|
||||
CONF.import_opt('image_member_quota', 'glance.common.config')
|
||||
|
||||
|
||||
class Controller(controller.BaseController):
|
||||
|
@ -107,6 +110,19 @@ class Controller(controller.BaseController):
|
|||
"""This will cover the missing 'show' and 'create' actions"""
|
||||
raise webob.exc.HTTPMethodNotAllowed()
|
||||
|
||||
def _enforce_image_member_quota(self, req, attempted):
|
||||
if CONF.image_member_quota < 0:
|
||||
# If value is negative, allow unlimited number of members
|
||||
return
|
||||
|
||||
maximum = CONF.image_member_quota
|
||||
if attempted > maximum:
|
||||
msg = _("The limit has been exceeded on the number of allowed "
|
||||
"image members for this image. Attempted: %(attempted)s, "
|
||||
"Maximum: %(maximum)s") % locals()
|
||||
raise webob.exc.HTTPRequestEntityTooLarge(explanation=msg,
|
||||
request=req)
|
||||
|
||||
@utils.mutating
|
||||
def update(self, req, image_id, id, body=None):
|
||||
"""
|
||||
|
@ -125,6 +141,10 @@ class Controller(controller.BaseController):
|
|||
self._enforce(req, 'modify_member')
|
||||
self._raise_404_if_image_deleted(req, image_id)
|
||||
|
||||
new_number_of_members = len(registry.get_image_members(req.context,
|
||||
image_id)) + 1
|
||||
self._enforce_image_member_quota(req, new_number_of_members)
|
||||
|
||||
# Figure out can_share
|
||||
can_share = None
|
||||
if body and 'member' in body and 'can_share' in body['member']:
|
||||
|
@ -162,6 +182,11 @@ class Controller(controller.BaseController):
|
|||
self._enforce(req, 'modify_member')
|
||||
self._raise_404_if_image_deleted(req, image_id)
|
||||
|
||||
memberships = body.get('memberships')
|
||||
if memberships:
|
||||
new_number_of_members = len(body['memberships'])
|
||||
self._enforce_image_member_quota(req, new_number_of_members)
|
||||
|
||||
try:
|
||||
registry.replace_members(req.context, image_id, body)
|
||||
self._update_store_acls(req, image_id)
|
||||
|
|
|
@ -74,6 +74,8 @@ class ImageMembersController(object):
|
|||
raise webob.exc.HTTPForbidden(explanation=unicode(e))
|
||||
except exception.Duplicate as e:
|
||||
raise webob.exc.HTTPConflict(explanation=unicode(e))
|
||||
except exception.ImageMemberLimitExceeded as e:
|
||||
raise webob.exc.HTTPRequestEntityTooLarge(explanation=unicode(e))
|
||||
|
||||
@utils.mutating
|
||||
def update(self, req, image_id, member_id, status):
|
||||
|
|
|
@ -44,6 +44,9 @@ common_opts = [
|
|||
cfg.BoolOpt('allow_additional_image_properties', default=True,
|
||||
help=_('Whether to allow users to specify image properties '
|
||||
'beyond what the image schema provides')),
|
||||
cfg.IntOpt('image_member_quota', default=128,
|
||||
help=_('Maximum number of image members per image. '
|
||||
'Negative values evaluate to unlimited.')),
|
||||
cfg.IntOpt('image_property_quota', default=128,
|
||||
help=_('Maximum number of properties allowed on an image. '
|
||||
'Negative values evaluate to unlimited.')),
|
||||
|
|
|
@ -289,6 +289,12 @@ class ImageSizeLimitExceeded(GlanceException):
|
|||
message = _("The provided image is too large.")
|
||||
|
||||
|
||||
class ImageMemberLimitExceeded(LimitExceeded):
|
||||
message = _("The limit has been exceeded on the number of allowed image "
|
||||
"members for this image. Attempted: %(attempted)s, "
|
||||
"Maximum: %(maximum)s")
|
||||
|
||||
|
||||
class ImagePropertyLimitExceeded(LimitExceeded):
|
||||
message = _("The limit has been exceeded on the number of allowed image "
|
||||
"properties. Attempted: %(attempted)s, Maximum: %(maximum)s")
|
||||
|
|
|
@ -195,6 +195,15 @@ def image_member_find(client, image_id=None, member=None, status=None):
|
|||
status=status)
|
||||
|
||||
|
||||
@_get_client
|
||||
def image_member_count(client, image_id):
|
||||
"""Return the number of image members for this image
|
||||
|
||||
:param image_id: identifier of image entity
|
||||
"""
|
||||
return client.image_member_count(image_id=image_id)
|
||||
|
||||
|
||||
@_get_client
|
||||
def image_tag_set_all(client, image_id, tags):
|
||||
client.image_tag_set_all(image_id=image_id, tags=tags)
|
||||
|
|
|
@ -393,6 +393,20 @@ def image_member_find(context, image_id=None, member=None, status=None):
|
|||
return [copy.deepcopy(m) for m in members]
|
||||
|
||||
|
||||
@log_call
|
||||
def image_member_count(context, image_id):
|
||||
"""Return the number of image members for this image
|
||||
|
||||
:param image_id: identifier of image entity
|
||||
"""
|
||||
if not image_id:
|
||||
msg = _("Image id is required.")
|
||||
raise exception.Invalid(msg)
|
||||
|
||||
members = DATA['members']
|
||||
return len(filter(lambda x: x['image_id'] == image_id, members))
|
||||
|
||||
|
||||
@log_call
|
||||
def image_member_create(context, values):
|
||||
member = _image_member_format(values['image_id'],
|
||||
|
|
|
@ -1079,6 +1079,24 @@ def _image_member_find(context, session, image_id=None,
|
|||
return query.all()
|
||||
|
||||
|
||||
def image_member_count(context, image_id):
|
||||
"""Return the number of image members for this image
|
||||
|
||||
:param image_id: identifier of image entity
|
||||
"""
|
||||
session = _get_session()
|
||||
|
||||
if not image_id:
|
||||
msg = _("Image id is required.")
|
||||
raise exception.Invalid(msg)
|
||||
|
||||
query = session.query(models.ImageMember)
|
||||
query = query.filter_by(deleted=False)
|
||||
query = query.filter(models.ImageMember.image_id == str(image_id))
|
||||
|
||||
return query.count()
|
||||
|
||||
|
||||
# pylint: disable-msg=C0111
|
||||
def _can_show_deleted(context):
|
||||
"""
|
||||
|
|
|
@ -58,8 +58,10 @@ class Gateway(object):
|
|||
|
||||
def get_image_member_factory(self, context):
|
||||
image_factory = glance.domain.ImageMemberFactory()
|
||||
quota_image_factory = glance.quota.ImageMemberFactoryProxy(
|
||||
image_factory, context, self.db_api)
|
||||
policy_member_factory = policy.ImageMemberFactoryProxy(
|
||||
image_factory, context, self.policy)
|
||||
quota_image_factory, context, self.policy)
|
||||
authorized_image_factory = authorization.ImageMemberFactoryProxy(
|
||||
policy_member_factory, context)
|
||||
return authorized_image_factory
|
||||
|
|
|
@ -27,6 +27,7 @@ import glance.openstack.common.log as logging
|
|||
|
||||
LOG = logging.getLogger(__name__)
|
||||
CONF = cfg.CONF
|
||||
CONF.import_opt('image_member_quota', 'glance.common.config')
|
||||
CONF.import_opt('image_property_quota', 'glance.common.config')
|
||||
CONF.import_opt('image_tag_quota', 'glance.common.config')
|
||||
|
||||
|
@ -118,6 +119,35 @@ class QuotaImageTagsProxy(object):
|
|||
return getattr(self.tags, name)
|
||||
|
||||
|
||||
class ImageMemberFactoryProxy(glance.domain.proxy.ImageMembershipFactory):
|
||||
|
||||
def __init__(self, member_factory, context, db_api):
|
||||
self.db_api = db_api
|
||||
self.context = context
|
||||
super(ImageMemberFactoryProxy, self).__init__(
|
||||
member_factory,
|
||||
image_proxy_class=ImageProxy,
|
||||
image_proxy_kwargs={})
|
||||
|
||||
def _enforce_image_member_quota(self, image):
|
||||
if CONF.image_member_quota < 0:
|
||||
# If value is negative, allow unlimited number of members
|
||||
return
|
||||
|
||||
current_member_count = self.db_api.image_member_count(self.context,
|
||||
image.image_id)
|
||||
attempted = current_member_count + 1
|
||||
maximum = CONF.image_member_quota
|
||||
if attempted > maximum:
|
||||
raise exception.ImageMemberLimitExceeded(attempted=attempted,
|
||||
maximum=maximum)
|
||||
|
||||
def new_image_member(self, image, member_id):
|
||||
self._enforce_image_member_quota(image)
|
||||
return super(ImageMemberFactoryProxy, self).new_image_member(image,
|
||||
member_id)
|
||||
|
||||
|
||||
class QuotaImageLocationsProxy(object):
|
||||
|
||||
def __init__(self, image, context, db_api):
|
||||
|
|
|
@ -312,6 +312,7 @@ class ApiServer(Server):
|
|||
self.policy_file = policy_file
|
||||
self.policy_default_rule = 'default'
|
||||
self.property_protection_rule_format = 'roles'
|
||||
self.image_member_quota = 10
|
||||
self.image_property_quota = 10
|
||||
self.image_tag_quota = 10
|
||||
|
||||
|
@ -375,6 +376,7 @@ lock_path = %(lock_path)s
|
|||
enable_v2_api= %(enable_v2_api)s
|
||||
property_protection_file = %(property_protection_file)s
|
||||
property_protection_rule_format = %(property_protection_rule_format)s
|
||||
image_member_quota=%(image_member_quota)s
|
||||
image_property_quota=%(image_property_quota)s
|
||||
image_tag_quota=%(image_tag_quota)s
|
||||
[paste_deploy]
|
||||
|
|
|
@ -1097,6 +1097,34 @@ class DriverTests(object):
|
|||
image_id=image_id)
|
||||
_assertMemberListMatch([], output)
|
||||
|
||||
def test_image_member_count(self):
|
||||
TENANT1 = uuidutils.generate_uuid()
|
||||
self.db_api.image_member_create(self.context,
|
||||
{'member': TENANT1,
|
||||
'image_id': UUID1})
|
||||
|
||||
actual = self.db_api.image_member_count(self.context, UUID1)
|
||||
|
||||
self.assertEqual(actual, 1)
|
||||
|
||||
def test_image_member_count_invalid_image_id(self):
|
||||
TENANT1 = uuidutils.generate_uuid()
|
||||
self.db_api.image_member_create(self.context,
|
||||
{'member': TENANT1,
|
||||
'image_id': UUID1})
|
||||
|
||||
self.assertRaises(exception.Invalid, self.db_api.image_member_count,
|
||||
self.context, None)
|
||||
|
||||
def test_image_member_count_empty_image_id(self):
|
||||
TENANT1 = uuidutils.generate_uuid()
|
||||
self.db_api.image_member_create(self.context,
|
||||
{'member': TENANT1,
|
||||
'image_id': UUID1})
|
||||
|
||||
self.assertRaises(exception.Invalid, self.db_api.image_member_count,
|
||||
self.context, "")
|
||||
|
||||
def test_image_member_delete(self):
|
||||
TENANT1 = uuidutils.generate_uuid()
|
||||
# NOTE(flaper87): Update auth token, otherwise
|
||||
|
|
|
@ -72,15 +72,19 @@ class TestApi(functional.FunctionalTest):
|
|||
- List image members
|
||||
15. DELETE image/members/member1
|
||||
- Delete image member1
|
||||
16. DELETE image
|
||||
16. PUT image/members
|
||||
- Attempt to replace members with an overlimit amount
|
||||
17. PUT image/members/member11
|
||||
- Attempt to add a member while at limit
|
||||
18. DELETE image
|
||||
- Delete image
|
||||
17. GET image/members
|
||||
19. GET image/members
|
||||
- List deleted image members
|
||||
18. PUT image/members/member2
|
||||
20. PUT image/members/member2
|
||||
- Update existing member2 of deleted image
|
||||
19. PUT image/members/member3
|
||||
21. PUT image/members/member3
|
||||
- Add member3 to deleted image
|
||||
20. DELETE image/members/member2
|
||||
22. DELETE image/members/member2
|
||||
- Delete member2 from deleted image
|
||||
"""
|
||||
self.cleanup()
|
||||
|
@ -329,21 +333,53 @@ class TestApi(functional.FunctionalTest):
|
|||
response, content = http.request(path, 'DELETE')
|
||||
self.assertEqual(response.status, 204)
|
||||
|
||||
# 16. DELETE image
|
||||
# 16. Attempt to replace members with an overlimit amount
|
||||
# Adding 11 image members should fail since configured limit is 10
|
||||
path = ("http://%s:%d/v1/images/%s/members" %
|
||||
("127.0.0.1", self.api_port, image_id))
|
||||
memberships = []
|
||||
for i in range(11):
|
||||
member_id = "foo%d" % i
|
||||
memberships.append(dict(member_id=member_id))
|
||||
http = httplib2.Http()
|
||||
body = json.dumps(dict(memberships=memberships))
|
||||
response, content = http.request(path, 'PUT', body=body)
|
||||
self.assertEqual(response.status, 413)
|
||||
|
||||
# 17. Attempt to add a member while at limit
|
||||
# Adding an 11th member should fail since configured limit is 10
|
||||
path = ("http://%s:%d/v1/images/%s/members" %
|
||||
("127.0.0.1", self.api_port, image_id))
|
||||
memberships = []
|
||||
for i in range(10):
|
||||
member_id = "foo%d" % i
|
||||
memberships.append(dict(member_id=member_id))
|
||||
http = httplib2.Http()
|
||||
body = json.dumps(dict(memberships=memberships))
|
||||
response, content = http.request(path, 'PUT', body=body)
|
||||
self.assertEqual(response.status, 204)
|
||||
|
||||
path = ("http://%s:%d/v1/images/%s/members/fail_me" %
|
||||
("127.0.0.1", self.api_port, image_id))
|
||||
http = httplib2.Http()
|
||||
response, content = http.request(path, 'PUT')
|
||||
self.assertEqual(response.status, 413)
|
||||
|
||||
# 18. DELETE image
|
||||
path = "http://%s:%d/v1/images/%s" % ("127.0.0.1", self.api_port,
|
||||
image_id)
|
||||
http = httplib2.Http()
|
||||
response, content = http.request(path, 'DELETE')
|
||||
self.assertEqual(response.status, 200)
|
||||
|
||||
# 17. Try to list members of deleted image
|
||||
# 19. Try to list members of deleted image
|
||||
path = ("http://%s:%d/v1/images/%s/members" %
|
||||
("127.0.0.1", self.api_port, image_id))
|
||||
http = httplib2.Http()
|
||||
response, content = http.request(path, 'GET')
|
||||
self.assertEqual(response.status, 404)
|
||||
|
||||
# 18. Try to update member of deleted image
|
||||
# 20. Try to update member of deleted image
|
||||
path = ("http://%s:%d/v1/images/%s/members" %
|
||||
("127.0.0.1", self.api_port, image_id))
|
||||
http = httplib2.Http()
|
||||
|
@ -352,14 +388,14 @@ class TestApi(functional.FunctionalTest):
|
|||
response, content = http.request(path, 'PUT', body=body)
|
||||
self.assertEqual(response.status, 404)
|
||||
|
||||
# 19. Try to add member to deleted image
|
||||
# 21. Try to add member to deleted image
|
||||
path = ("http://%s:%d/v1/images/%s/members/chickenpattie" %
|
||||
("127.0.0.1", self.api_port, image_id))
|
||||
http = httplib2.Http()
|
||||
response, content = http.request(path, 'PUT')
|
||||
self.assertEqual(response.status, 404)
|
||||
|
||||
# 20. Try to delete member of deleted image
|
||||
# 22. Try to delete member of deleted image
|
||||
path = ("http://%s:%d/v1/images/%s/members/pattieblack" %
|
||||
("127.0.0.1", self.api_port, image_id))
|
||||
http = httplib2.Http()
|
||||
|
|
|
@ -2039,6 +2039,19 @@ class TestImageMembers(functional.FunctionalTest):
|
|||
body = json.loads(response.text)
|
||||
self.assertEqual(0, len(body['members']))
|
||||
|
||||
# Adding 11 image members should fail since configured limit is 10
|
||||
path = self._url('/v2/images/%s/members' % image_fixture[1]['id'])
|
||||
for i in range(10):
|
||||
body = json.dumps({'member': uuidutils.generate_uuid()})
|
||||
response = requests.post(path, headers=get_header('tenant1'),
|
||||
data=body)
|
||||
self.assertEqual(200, response.status_code)
|
||||
|
||||
body = json.dumps({'member': uuidutils.generate_uuid()})
|
||||
response = requests.post(path, headers=get_header('tenant1'),
|
||||
data=body)
|
||||
self.assertEqual(413, response.status_code)
|
||||
|
||||
# Delete Image members not found for public image
|
||||
path = self._url('/v2/images/%s/members/%s' % (image_fixture[0]['id'],
|
||||
TENANT3))
|
||||
|
|
|
@ -422,3 +422,38 @@ class TestQuotaImageTagsProxy(test_utils.BaseTestCase):
|
|||
for item in proxy:
|
||||
items.remove(item)
|
||||
self.assertEqual(len(items), 0)
|
||||
|
||||
|
||||
class TestImageMemberQuotas(test_utils.BaseTestCase):
|
||||
def setUp(self):
|
||||
super(TestImageMemberQuotas, self).setUp()
|
||||
db_api = unit_test_utils.FakeDB()
|
||||
context = FakeContext()
|
||||
self.image = mock.Mock()
|
||||
self.base_image_member_factory = mock.Mock()
|
||||
self.image_member_factory = glance.quota.ImageMemberFactoryProxy(
|
||||
self.base_image_member_factory, context,
|
||||
db_api)
|
||||
|
||||
def test_new_image_member(self):
|
||||
self.config(image_member_quota=1)
|
||||
|
||||
self.image_member_factory.new_image_member(self.image,
|
||||
'fake_id')
|
||||
self.base_image_member_factory.new_image_member\
|
||||
.assert_called_once_with(self.image.base, 'fake_id')
|
||||
|
||||
def test_new_image_member_unlimited_members(self):
|
||||
self.config(image_member_quota=-1)
|
||||
|
||||
self.image_member_factory.new_image_member(self.image,
|
||||
'fake_id')
|
||||
self.base_image_member_factory.new_image_member\
|
||||
.assert_called_once_with(self.image.base, 'fake_id')
|
||||
|
||||
def test_new_image_member_too_many_members(self):
|
||||
self.config(image_member_quota=0)
|
||||
|
||||
self.assertRaises(exception.ImageMemberLimitExceeded,
|
||||
self.image_member_factory.new_image_member,
|
||||
self.image, 'fake_id')
|
||||
|
|
|
@ -2141,6 +2141,28 @@ class TestGlanceAPI(base.IsolatedUnitTest):
|
|||
res = req.get_response(self.api)
|
||||
self.assertEqual(res.status_int, 204)
|
||||
|
||||
def test_add_member_overlimit(self):
|
||||
self.config(image_member_quota=0)
|
||||
test_router_api = router.API(self.mapper)
|
||||
self.api = test_utils.FakeAuthMiddleware(
|
||||
test_router_api, is_admin=True)
|
||||
req = webob.Request.blank('/images/%s/members/pattieblack' % UUID2)
|
||||
req.method = 'PUT'
|
||||
|
||||
res = req.get_response(self.api)
|
||||
self.assertEqual(res.status_int, 413)
|
||||
|
||||
def test_add_member_unlimited(self):
|
||||
self.config(image_member_quota=-1)
|
||||
test_router_api = router.API(self.mapper)
|
||||
self.api = test_utils.FakeAuthMiddleware(
|
||||
test_router_api, is_admin=True)
|
||||
req = webob.Request.blank('/images/%s/members/pattieblack' % UUID2)
|
||||
req.method = 'PUT'
|
||||
|
||||
res = req.get_response(self.api)
|
||||
self.assertEqual(res.status_int, 204)
|
||||
|
||||
def test_add_member_forbidden_by_policy(self):
|
||||
rules = {"modify_member": '!'}
|
||||
self.set_policy_rules(rules)
|
||||
|
@ -2226,6 +2248,87 @@ class TestGlanceAPI(base.IsolatedUnitTest):
|
|||
self.assertTrue(
|
||||
'Image with identifier %s has been deleted.' % UUID2 in res.body)
|
||||
|
||||
def test_replace_members_of_image(self):
|
||||
test_router = router.API(self.mapper)
|
||||
self.api = test_utils.FakeAuthMiddleware(test_router, is_admin=True)
|
||||
|
||||
fixture = [{'member_id': 'pattieblack', 'can_share': 'false'}]
|
||||
req = webob.Request.blank('/images/%s/members' % UUID2)
|
||||
req.method = 'PUT'
|
||||
req.body = json.dumps(dict(memberships=fixture))
|
||||
res = req.get_response(self.api)
|
||||
self.assertEqual(res.status_int, 204)
|
||||
|
||||
req = webob.Request.blank('/images/%s/members' % UUID2)
|
||||
req.method = 'GET'
|
||||
res = req.get_response(self.api)
|
||||
self.assertEqual(res.status_int, 200)
|
||||
|
||||
memb_list = json.loads(res.body)
|
||||
self.assertEqual(len(memb_list), 1)
|
||||
|
||||
def test_replace_members_of_image_overlimit(self):
|
||||
# Set image_member_quota to 1
|
||||
self.config(image_member_quota=1)
|
||||
test_router = router.API(self.mapper)
|
||||
self.api = test_utils.FakeAuthMiddleware(test_router, is_admin=True)
|
||||
|
||||
# PUT an original member entry
|
||||
fixture = [{'member_id': 'baz', 'can_share': False}]
|
||||
req = webob.Request.blank('/images/%s/members' % UUID2)
|
||||
req.method = 'PUT'
|
||||
req.body = json.dumps(dict(memberships=fixture))
|
||||
res = req.get_response(self.api)
|
||||
self.assertEqual(res.status_int, 204)
|
||||
|
||||
# GET original image member list
|
||||
req = webob.Request.blank('/images/%s/members' % UUID2)
|
||||
req.method = 'GET'
|
||||
res = req.get_response(self.api)
|
||||
self.assertEqual(res.status_int, 200)
|
||||
original_members = json.loads(res.body)['members']
|
||||
self.assertEqual(len(original_members), 1)
|
||||
|
||||
# PUT 2 image members to replace existing (overlimit)
|
||||
fixture = [{'member_id': 'foo1', 'can_share': False},
|
||||
{'member_id': 'foo2', 'can_share': False}]
|
||||
req = webob.Request.blank('/images/%s/members' % UUID2)
|
||||
req.method = 'PUT'
|
||||
req.body = json.dumps(dict(memberships=fixture))
|
||||
res = req.get_response(self.api)
|
||||
self.assertEqual(res.status_int, 413)
|
||||
|
||||
# GET member list
|
||||
req = webob.Request.blank('/images/%s/members' % UUID2)
|
||||
req.method = 'GET'
|
||||
res = req.get_response(self.api)
|
||||
self.assertEqual(res.status_int, 200)
|
||||
|
||||
# Assert the member list was not changed
|
||||
memb_list = json.loads(res.body)['members']
|
||||
self.assertEqual(memb_list, original_members)
|
||||
|
||||
def test_replace_members_of_image_unlimited(self):
|
||||
self.config(image_member_quota=-1)
|
||||
test_router = router.API(self.mapper)
|
||||
self.api = test_utils.FakeAuthMiddleware(test_router, is_admin=True)
|
||||
|
||||
fixture = [{'member_id': 'foo1', 'can_share': False},
|
||||
{'member_id': 'foo2', 'can_share': False}]
|
||||
req = webob.Request.blank('/images/%s/members' % UUID2)
|
||||
req.method = 'PUT'
|
||||
req.body = json.dumps(dict(memberships=fixture))
|
||||
res = req.get_response(self.api)
|
||||
self.assertEqual(res.status_int, 204)
|
||||
|
||||
req = webob.Request.blank('/images/%s/members' % UUID2)
|
||||
req.method = 'GET'
|
||||
res = req.get_response(self.api)
|
||||
self.assertEqual(res.status_int, 200)
|
||||
|
||||
memb_list = json.loads(res.body)['members']
|
||||
self.assertEqual(memb_list, fixture)
|
||||
|
||||
def test_create_member_to_deleted_image_raises_404(self):
|
||||
"""
|
||||
Tests adding members to deleted image raises 404.
|
||||
|
|
|
@ -254,6 +254,25 @@ class TestImageMembersController(test_utils.BaseTestCase):
|
|||
self.assertRaises(webob.exc.HTTPConflict, self.controller.create,
|
||||
request, image_id=image_id, member_id=member_id)
|
||||
|
||||
def test_create_overlimit(self):
|
||||
self.config(image_member_quota=0)
|
||||
request = unit_test_utils.get_fake_request()
|
||||
image_id = UUID2
|
||||
member_id = TENANT3
|
||||
self.assertRaises(webob.exc.HTTPRequestEntityTooLarge,
|
||||
self.controller.create, request,
|
||||
image_id=image_id, member_id=member_id)
|
||||
|
||||
def test_create_unlimited(self):
|
||||
self.config(image_member_quota=-1)
|
||||
request = unit_test_utils.get_fake_request()
|
||||
image_id = UUID2
|
||||
member_id = TENANT3
|
||||
output = self.controller.create(request, image_id=image_id,
|
||||
member_id=member_id)
|
||||
self.assertEqual(UUID2, output.image_id)
|
||||
self.assertEqual(TENANT3, output.member_id)
|
||||
|
||||
def test_update_done_by_member(self):
|
||||
request = unit_test_utils.get_fake_request(tenant=TENANT4)
|
||||
image_id = UUID2
|
||||
|
|
Loading…
Reference in New Issue