Adding member to image fails for multiple stores
If multiple stores is enabled then adding member to image fails with UnknownScheme: Unknown scheme 'XXXX' found in URI while setting _acls to the store. Made provision to call 'set_acls_for_multi_store' if multiple stores are enabled. Change-Id: I7594185396f7a58a3c0aeeacab891b5eb18a8bf3 Closes-Bug: #1838332
This commit is contained in:
parent
3fda53c520
commit
aca6996ea9
@ -564,9 +564,16 @@ class ImageMemberRepoProxy(glance.domain.proxy.Repo):
|
||||
if self.image.locations and not public:
|
||||
member_ids = [m.member_id for m in self.repo.list()]
|
||||
for location in self.image.locations:
|
||||
self.store_api.set_acls(location['url'], public=public,
|
||||
read_tenants=member_ids,
|
||||
context=self.context)
|
||||
if CONF.enabled_backends:
|
||||
self.store_api.set_acls_for_multi_store(
|
||||
location['url'], location['metadata'].get('store'),
|
||||
public=public, read_tenants=member_ids,
|
||||
context=self.context
|
||||
)
|
||||
else:
|
||||
self.store_api.set_acls(location['url'], public=public,
|
||||
read_tenants=member_ids,
|
||||
context=self.context)
|
||||
|
||||
def add(self, member):
|
||||
super(ImageMemberRepoProxy, self).add(member)
|
||||
|
@ -5475,3 +5475,410 @@ class TestImagesMultipleBackend(functional.MultipleBackendFunctionalTest):
|
||||
self.assertEqual(0, len(images))
|
||||
|
||||
self.stop_servers()
|
||||
|
||||
|
||||
class TestMultiStoreImageMembers(functional.MultipleBackendFunctionalTest):
|
||||
|
||||
def setUp(self):
|
||||
super(TestMultiStoreImageMembers, self).setUp()
|
||||
self.cleanup()
|
||||
self.include_scrubber = False
|
||||
self.api_server_multiple_backend.deployment_flavor = 'noauth'
|
||||
self.api_server_multiple_backend.data_api = 'glance.db.sqlalchemy.api'
|
||||
for i in range(3):
|
||||
ret = test_utils.start_http_server("foo_image_id%d" % i,
|
||||
"foo_image%d" % i)
|
||||
setattr(self, 'http_server%d' % i, ret[1])
|
||||
setattr(self, 'http_port%d' % i, ret[2])
|
||||
|
||||
def tearDown(self):
|
||||
for i in range(3):
|
||||
httpd = getattr(self, 'http_server%d' % i, None)
|
||||
if httpd:
|
||||
httpd.shutdown()
|
||||
httpd.server_close()
|
||||
|
||||
super(TestMultiStoreImageMembers, self).tearDown()
|
||||
|
||||
def _url(self, path):
|
||||
return 'http://127.0.0.1:%d%s' % (self.api_port, path)
|
||||
|
||||
def _headers(self, custom_headers=None):
|
||||
base_headers = {
|
||||
'X-Identity-Status': 'Confirmed',
|
||||
'X-Auth-Token': '932c5c84-02ac-4fe5-a9ba-620af0e2bb96',
|
||||
'X-User-Id': 'f9a41d13-0c13-47e9-bee2-ce4e8bfe958e',
|
||||
'X-Tenant-Id': TENANT1,
|
||||
'X-Roles': 'member',
|
||||
}
|
||||
base_headers.update(custom_headers or {})
|
||||
return base_headers
|
||||
|
||||
def test_image_member_lifecycle_for_multiple_stores(self):
|
||||
self.start_servers(**self.__dict__.copy())
|
||||
|
||||
def get_header(tenant, tenant_id=None, role=''):
|
||||
auth_token = 'user:%s:%s' % (tenant, role)
|
||||
headers = {'X-Auth-Token': auth_token}
|
||||
if tenant_id:
|
||||
headers.update({'X-Tenant-Id': tenant_id})
|
||||
return self._headers(custom_headers=headers)
|
||||
|
||||
# Image list should be empty
|
||||
path = self._url('/v2/images')
|
||||
response = requests.get(path, headers=get_header('tenant1'))
|
||||
self.assertEqual(http.OK, response.status_code)
|
||||
images = jsonutils.loads(response.text)['images']
|
||||
self.assertEqual(0, len(images))
|
||||
|
||||
owners = ['tenant1', 'tenant2', 'admin']
|
||||
visibilities = ['community', 'private', 'public', 'shared']
|
||||
image_fixture = []
|
||||
for owner in owners:
|
||||
for visibility in visibilities:
|
||||
path = self._url('/v2/images')
|
||||
headers = self._headers(custom_headers={
|
||||
'content-type': 'application/json',
|
||||
'X-Auth-Token': 'createuser:%s:admin' % owner,
|
||||
})
|
||||
data = jsonutils.dumps({
|
||||
'name': '%s-%s' % (owner, visibility),
|
||||
'visibility': visibility,
|
||||
})
|
||||
response = requests.post(path, headers=headers, data=data)
|
||||
self.assertEqual(http.CREATED, response.status_code)
|
||||
image_fixture.append(jsonutils.loads(response.text))
|
||||
|
||||
# Image list should contain 12 images for tenant1
|
||||
path = self._url('/v2/images')
|
||||
response = requests.get(path, headers=get_header('tenant1'))
|
||||
self.assertEqual(http.OK, response.status_code)
|
||||
images = jsonutils.loads(response.text)['images']
|
||||
self.assertEqual(12, len(images))
|
||||
|
||||
# Image list should contain 3 images for TENANT3
|
||||
path = self._url('/v2/images')
|
||||
response = requests.get(path, headers=get_header(TENANT3,
|
||||
tenant_id=TENANT3))
|
||||
self.assertEqual(http.OK, response.status_code)
|
||||
images = jsonutils.loads(response.text)['images']
|
||||
self.assertEqual(3, len(images))
|
||||
|
||||
# Add Image member for tenant1-shared image
|
||||
path = self._url('/v2/images/%s/members' % image_fixture[3]['id'])
|
||||
body = jsonutils.dumps({'member': TENANT3})
|
||||
response = requests.post(path, headers=get_header('tenant1',
|
||||
tenant_id=TENANT1),
|
||||
data=body)
|
||||
self.assertEqual(http.OK, response.status_code)
|
||||
image_member = jsonutils.loads(response.text)
|
||||
self.assertEqual(image_fixture[3]['id'], image_member['image_id'])
|
||||
self.assertEqual(TENANT3, image_member['member_id'])
|
||||
self.assertIn('created_at', image_member)
|
||||
self.assertIn('updated_at', image_member)
|
||||
self.assertEqual('pending', image_member['status'])
|
||||
|
||||
# Image list should contain 3 images for TENANT3
|
||||
path = self._url('/v2/images')
|
||||
response = requests.get(path, headers=get_header(TENANT3,
|
||||
tenant_id=TENANT3))
|
||||
self.assertEqual(http.OK, response.status_code)
|
||||
images = jsonutils.loads(response.text)['images']
|
||||
self.assertEqual(3, len(images))
|
||||
|
||||
# Image list should contain 0 shared images for TENANT3
|
||||
# because default is accepted
|
||||
path = self._url('/v2/images?visibility=shared')
|
||||
response = requests.get(path, headers=get_header(TENANT3,
|
||||
tenant_id=TENANT3))
|
||||
self.assertEqual(http.OK, response.status_code)
|
||||
images = jsonutils.loads(response.text)['images']
|
||||
self.assertEqual(0, len(images))
|
||||
|
||||
# Image list should contain 4 images for TENANT3 with status pending
|
||||
path = self._url('/v2/images?member_status=pending')
|
||||
response = requests.get(path, headers=get_header(TENANT3,
|
||||
tenant_id=TENANT3))
|
||||
self.assertEqual(http.OK, response.status_code)
|
||||
images = jsonutils.loads(response.text)['images']
|
||||
self.assertEqual(4, len(images))
|
||||
|
||||
# Image list should contain 4 images for TENANT3 with status all
|
||||
path = self._url('/v2/images?member_status=all')
|
||||
response = requests.get(path, headers=get_header(TENANT3,
|
||||
tenant_id=TENANT3))
|
||||
self.assertEqual(http.OK, response.status_code)
|
||||
images = jsonutils.loads(response.text)['images']
|
||||
self.assertEqual(4, len(images))
|
||||
|
||||
# Image list should contain 1 image for TENANT3 with status pending
|
||||
# and visibility shared
|
||||
path = self._url('/v2/images?member_status=pending&visibility=shared')
|
||||
response = requests.get(path, headers=get_header(TENANT3,
|
||||
tenant_id=TENANT3))
|
||||
self.assertEqual(http.OK, response.status_code)
|
||||
images = jsonutils.loads(response.text)['images']
|
||||
self.assertEqual(1, len(images))
|
||||
self.assertEqual(images[0]['name'], 'tenant1-shared')
|
||||
|
||||
# Image list should contain 0 image for TENANT3 with status rejected
|
||||
# and visibility shared
|
||||
path = self._url('/v2/images?member_status=rejected&visibility=shared')
|
||||
response = requests.get(path, headers=get_header(TENANT3,
|
||||
tenant_id=TENANT3))
|
||||
self.assertEqual(http.OK, response.status_code)
|
||||
images = jsonutils.loads(response.text)['images']
|
||||
self.assertEqual(0, len(images))
|
||||
|
||||
# Image list should contain 0 image for TENANT3 with status accepted
|
||||
# and visibility shared
|
||||
path = self._url('/v2/images?member_status=accepted&visibility=shared')
|
||||
response = requests.get(path, headers=get_header(TENANT3,
|
||||
tenant_id=TENANT3))
|
||||
self.assertEqual(http.OK, response.status_code)
|
||||
images = jsonutils.loads(response.text)['images']
|
||||
self.assertEqual(0, len(images))
|
||||
|
||||
# Image list should contain 0 image for TENANT3 with status accepted
|
||||
# and visibility private
|
||||
path = self._url('/v2/images?visibility=private')
|
||||
response = requests.get(path, headers=get_header(TENANT3,
|
||||
tenant_id=TENANT3))
|
||||
self.assertEqual(http.OK, response.status_code)
|
||||
images = jsonutils.loads(response.text)['images']
|
||||
self.assertEqual(0, len(images))
|
||||
|
||||
# Image tenant2-shared's image members list should contain no members
|
||||
path = self._url('/v2/images/%s/members' % image_fixture[7]['id'])
|
||||
response = requests.get(path, headers=get_header('tenant2'))
|
||||
self.assertEqual(http.OK, response.status_code)
|
||||
body = jsonutils.loads(response.text)
|
||||
self.assertEqual(0, len(body['members']))
|
||||
|
||||
# Tenant 1, who is the owner cannot change status of image member
|
||||
path = self._url('/v2/images/%s/members/%s' % (image_fixture[3]['id'],
|
||||
TENANT3))
|
||||
body = jsonutils.dumps({'status': 'accepted'})
|
||||
response = requests.put(path, headers=get_header(
|
||||
'tenant1', tenant_id=TENANT1), data=body)
|
||||
self.assertEqual(http.FORBIDDEN, response.status_code)
|
||||
|
||||
# Tenant 1, who is the owner can get status of its own image member
|
||||
path = self._url('/v2/images/%s/members/%s' % (image_fixture[3]['id'],
|
||||
TENANT3))
|
||||
response = requests.get(path, headers=get_header('tenant1',
|
||||
tenant_id=TENANT1))
|
||||
self.assertEqual(http.OK, response.status_code)
|
||||
body = jsonutils.loads(response.text)
|
||||
self.assertEqual('pending', body['status'])
|
||||
self.assertEqual(image_fixture[3]['id'], body['image_id'])
|
||||
self.assertEqual(TENANT3, body['member_id'])
|
||||
|
||||
# Tenant 3, who is the member can get status of its own status
|
||||
path = self._url('/v2/images/%s/members/%s' % (image_fixture[3]['id'],
|
||||
TENANT3))
|
||||
response = requests.get(path, headers=get_header(TENANT3,
|
||||
tenant_id=TENANT3))
|
||||
self.assertEqual(http.OK, response.status_code)
|
||||
body = jsonutils.loads(response.text)
|
||||
self.assertEqual('pending', body['status'])
|
||||
self.assertEqual(image_fixture[3]['id'], body['image_id'])
|
||||
self.assertEqual(TENANT3, body['member_id'])
|
||||
|
||||
# Tenant 2, who not the owner cannot get status of image member
|
||||
path = self._url('/v2/images/%s/members/%s' % (image_fixture[3]['id'],
|
||||
TENANT3))
|
||||
response = requests.get(path, headers=get_header('tenant2',
|
||||
tenant_id=TENANT2))
|
||||
self.assertEqual(http.NOT_FOUND, response.status_code)
|
||||
|
||||
# Tenant 3 can change status of image member
|
||||
path = self._url('/v2/images/%s/members/%s' % (image_fixture[3]['id'],
|
||||
TENANT3))
|
||||
body = jsonutils.dumps({'status': 'accepted'})
|
||||
response = requests.put(path, headers=get_header(
|
||||
TENANT3, tenant_id=TENANT3), data=body)
|
||||
self.assertEqual(http.OK, response.status_code)
|
||||
image_member = jsonutils.loads(response.text)
|
||||
self.assertEqual(image_fixture[3]['id'], image_member['image_id'])
|
||||
self.assertEqual(TENANT3, image_member['member_id'])
|
||||
self.assertEqual('accepted', image_member['status'])
|
||||
|
||||
# Image list should contain 4 images for TENANT3 because status is
|
||||
# accepted
|
||||
path = self._url('/v2/images')
|
||||
response = requests.get(path, headers=get_header(TENANT3,
|
||||
tenant_id=TENANT3))
|
||||
self.assertEqual(http.OK, response.status_code)
|
||||
images = jsonutils.loads(response.text)['images']
|
||||
self.assertEqual(4, len(images))
|
||||
|
||||
# Tenant 3 invalid status change
|
||||
path = self._url('/v2/images/%s/members/%s' % (image_fixture[3]['id'],
|
||||
TENANT3))
|
||||
body = jsonutils.dumps({'status': 'invalid-status'})
|
||||
response = requests.put(path, headers=get_header(
|
||||
TENANT3, tenant_id=TENANT3), data=body)
|
||||
self.assertEqual(http.BAD_REQUEST, response.status_code)
|
||||
|
||||
# Owner cannot change status of image
|
||||
path = self._url('/v2/images/%s/members/%s' % (image_fixture[3]['id'],
|
||||
TENANT3))
|
||||
body = jsonutils.dumps({'status': 'accepted'})
|
||||
response = requests.put(path, headers=get_header(
|
||||
'tenant1', tenant_id=TENANT1), data=body)
|
||||
self.assertEqual(http.FORBIDDEN, response.status_code)
|
||||
|
||||
# Add Image member for tenant2-shared image
|
||||
path = self._url('/v2/images/%s/members' % image_fixture[7]['id'])
|
||||
body = jsonutils.dumps({'member': TENANT4})
|
||||
response = requests.post(path, headers=get_header('tenant2'),
|
||||
data=body)
|
||||
self.assertEqual(http.OK, response.status_code)
|
||||
image_member = jsonutils.loads(response.text)
|
||||
self.assertEqual(image_fixture[7]['id'], image_member['image_id'])
|
||||
self.assertEqual(TENANT4, image_member['member_id'])
|
||||
self.assertIn('created_at', image_member)
|
||||
self.assertIn('updated_at', image_member)
|
||||
|
||||
# Add Image member to public image
|
||||
path = self._url('/v2/images/%s/members' % image_fixture[2]['id'])
|
||||
body = jsonutils.dumps({'member': TENANT2})
|
||||
response = requests.post(path, headers=get_header('tenant1',
|
||||
tenant_id=TENANT1),
|
||||
data=body)
|
||||
self.assertEqual(http.FORBIDDEN, response.status_code)
|
||||
|
||||
# Add Image member to private image
|
||||
path = self._url('/v2/images/%s/members' % image_fixture[1]['id'])
|
||||
body = jsonutils.dumps({'member': TENANT2})
|
||||
response = requests.post(path, headers=get_header('tenant1',
|
||||
tenant_id=TENANT1),
|
||||
data=body)
|
||||
self.assertEqual(http.FORBIDDEN, response.status_code)
|
||||
|
||||
# Add Image member to community image
|
||||
path = self._url('/v2/images/%s/members' % image_fixture[0]['id'])
|
||||
body = jsonutils.dumps({'member': TENANT2})
|
||||
response = requests.post(path, headers=get_header('tenant1',
|
||||
tenant_id=TENANT1),
|
||||
data=body)
|
||||
self.assertEqual(http.FORBIDDEN, response.status_code)
|
||||
|
||||
# Image tenant1-shared's members list should contain 1 member
|
||||
path = self._url('/v2/images/%s/members' % image_fixture[3]['id'])
|
||||
response = requests.get(path, headers=get_header('tenant1',
|
||||
tenant_id=TENANT1))
|
||||
self.assertEqual(http.OK, response.status_code)
|
||||
body = jsonutils.loads(response.text)
|
||||
self.assertEqual(1, len(body['members']))
|
||||
|
||||
# Admin can see any members
|
||||
path = self._url('/v2/images/%s/members' % image_fixture[3]['id'])
|
||||
response = requests.get(path, headers=get_header('tenant1',
|
||||
tenant_id=TENANT1,
|
||||
role='admin'))
|
||||
self.assertEqual(http.OK, response.status_code)
|
||||
body = jsonutils.loads(response.text)
|
||||
self.assertEqual(1, len(body['members']))
|
||||
|
||||
# Image members forbidden for public image
|
||||
path = self._url('/v2/images/%s/members' % image_fixture[2]['id'])
|
||||
response = requests.get(path, headers=get_header('tenant1',
|
||||
tenant_id=TENANT1))
|
||||
self.assertIn("Only shared images have members", response.text)
|
||||
self.assertEqual(http.FORBIDDEN, response.status_code)
|
||||
|
||||
# Image members forbidden for community image
|
||||
path = self._url('/v2/images/%s/members' % image_fixture[0]['id'])
|
||||
response = requests.get(path, headers=get_header('tenant1',
|
||||
tenant_id=TENANT1))
|
||||
self.assertIn("Only shared images have members", response.text)
|
||||
self.assertEqual(http.FORBIDDEN, response.status_code)
|
||||
|
||||
# Image members forbidden for private image
|
||||
path = self._url('/v2/images/%s/members' % image_fixture[1]['id'])
|
||||
response = requests.get(path, headers=get_header('tenant1',
|
||||
tenant_id=TENANT1))
|
||||
self.assertIn("Only shared images have members", response.text)
|
||||
self.assertEqual(http.FORBIDDEN, response.status_code)
|
||||
|
||||
# Image Member Cannot delete Image membership
|
||||
path = self._url('/v2/images/%s/members/%s' % (image_fixture[3]['id'],
|
||||
TENANT3))
|
||||
response = requests.delete(path, headers=get_header(TENANT3,
|
||||
tenant_id=TENANT3))
|
||||
self.assertEqual(http.FORBIDDEN, response.status_code)
|
||||
|
||||
# Delete Image member
|
||||
path = self._url('/v2/images/%s/members/%s' % (image_fixture[3]['id'],
|
||||
TENANT3))
|
||||
response = requests.delete(path, headers=get_header('tenant1',
|
||||
tenant_id=TENANT1))
|
||||
self.assertEqual(http.NO_CONTENT, response.status_code)
|
||||
|
||||
# Now the image has no members
|
||||
path = self._url('/v2/images/%s/members' % image_fixture[3]['id'])
|
||||
response = requests.get(path, headers=get_header('tenant1',
|
||||
tenant_id=TENANT1))
|
||||
self.assertEqual(http.OK, response.status_code)
|
||||
body = jsonutils.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[3]['id'])
|
||||
for i in range(10):
|
||||
body = jsonutils.dumps({'member': str(uuid.uuid4())})
|
||||
response = requests.post(path, headers=get_header(
|
||||
'tenant1', tenant_id=TENANT1), data=body)
|
||||
self.assertEqual(http.OK, response.status_code)
|
||||
|
||||
body = jsonutils.dumps({'member': str(uuid.uuid4())})
|
||||
response = requests.post(path, headers=get_header('tenant1',
|
||||
tenant_id=TENANT1),
|
||||
data=body)
|
||||
self.assertEqual(http.REQUEST_ENTITY_TOO_LARGE, response.status_code)
|
||||
|
||||
# Get Image member should return not found for public image
|
||||
path = self._url('/v2/images/%s/members/%s' % (image_fixture[2]['id'],
|
||||
TENANT3))
|
||||
response = requests.get(path, headers=get_header('tenant1',
|
||||
tenant_id=TENANT1))
|
||||
self.assertEqual(http.NOT_FOUND, response.status_code)
|
||||
|
||||
# Get Image member should return not found for community image
|
||||
path = self._url('/v2/images/%s/members/%s' % (image_fixture[0]['id'],
|
||||
TENANT3))
|
||||
response = requests.get(path, headers=get_header('tenant1',
|
||||
tenant_id=TENANT1))
|
||||
self.assertEqual(http.NOT_FOUND, response.status_code)
|
||||
|
||||
# Get Image member should return not found for private image
|
||||
path = self._url('/v2/images/%s/members/%s' % (image_fixture[1]['id'],
|
||||
TENANT3))
|
||||
response = requests.get(path, headers=get_header('tenant1',
|
||||
tenant_id=TENANT1))
|
||||
self.assertEqual(http.NOT_FOUND, response.status_code)
|
||||
|
||||
# Delete Image member should return forbidden for public image
|
||||
path = self._url('/v2/images/%s/members/%s' % (image_fixture[2]['id'],
|
||||
TENANT3))
|
||||
response = requests.delete(path, headers=get_header('tenant1',
|
||||
tenant_id=TENANT1))
|
||||
self.assertEqual(http.FORBIDDEN, response.status_code)
|
||||
|
||||
# Delete Image member should return forbidden for community image
|
||||
path = self._url('/v2/images/%s/members/%s' % (image_fixture[0]['id'],
|
||||
TENANT3))
|
||||
response = requests.delete(path, headers=get_header('tenant1',
|
||||
tenant_id=TENANT1))
|
||||
self.assertEqual(http.FORBIDDEN, response.status_code)
|
||||
|
||||
# Delete Image member should return forbidden for private image
|
||||
path = self._url('/v2/images/%s/members/%s' % (image_fixture[1]['id'],
|
||||
TENANT3))
|
||||
response = requests.delete(path, headers=get_header('tenant1',
|
||||
tenant_id=TENANT1))
|
||||
self.assertEqual(http.FORBIDDEN, response.status_code)
|
||||
|
||||
self.stop_servers()
|
||||
|
Loading…
Reference in New Issue
Block a user