diff --git a/glance/db/simple/api.py b/glance/db/simple/api.py index db4ebbbad0..939d04a3bf 100644 --- a/glance/db/simple/api.py +++ b/glance/db/simple/api.py @@ -128,7 +128,8 @@ def _image_property_format(image_id, name, value): } -def _image_member_format(image_id, tenant_id, can_share, status='pending'): +def _image_member_format(image_id, tenant_id, can_share, status='pending', + deleted=False): dt = timeutils.utcnow() return { 'id': str(uuid.uuid4()), @@ -138,6 +139,7 @@ def _image_member_format(image_id, tenant_id, can_share, status='pending'): 'status': status, 'created_at': dt, 'updated_at': dt, + 'deleted': deleted, } @@ -503,7 +505,8 @@ def image_member_create(context, values): member = _image_member_format(values['image_id'], values['member'], values.get('can_share', False), - values.get('status', 'pending')) + values.get('status', 'pending'), + values.get('deleted', False)) global DATA DATA['members'].append(member) return copy.deepcopy(member) diff --git a/glance/db/sqlalchemy/api.py b/glance/db/sqlalchemy/api.py index 30c5758232..25a7eeca43 100644 --- a/glance/db/sqlalchemy/api.py +++ b/glance/db/sqlalchemy/api.py @@ -1056,7 +1056,8 @@ def _image_member_format(member_ref): 'can_share': member_ref['can_share'], 'status': member_ref['status'], 'created_at': member_ref['created_at'], - 'updated_at': member_ref['updated_at'] + 'updated_at': member_ref['updated_at'], + 'deleted': member_ref['deleted'] } diff --git a/glance/registry/api/v1/members.py b/glance/registry/api/v1/members.py index d6383ef49a..5c542dd5a4 100644 --- a/glance/registry/api/v1/members.py +++ b/glance/registry/api/v1/members.py @@ -176,7 +176,8 @@ class Controller(object): # memberships to modify. Let's start by walking through all # the existing image memberships... existing_members = self.db_api.image_member_find(req.context, - image_id=image['id']) + image_id=image['id'], + include_deleted=True) for member in existing_members: if member['id'] in existing: # Just update the membership in place @@ -185,8 +186,9 @@ class Controller(object): member['id'], update) else: - # Outdated one; needs to be deleted - self.db_api.image_member_delete(req.context, member['id']) + if not member['deleted']: + # Outdated one; needs to be deleted + self.db_api.image_member_delete(req.context, member['id']) # Now add the non-existent ones for memb in add: diff --git a/glance/tests/functional/db/base.py b/glance/tests/functional/db/base.py index 5a2a7268bb..5ab72ffabc 100644 --- a/glance/tests/functional/db/base.py +++ b/glance/tests/functional/db/base.py @@ -1173,6 +1173,7 @@ class DriverTests(object): 'image_id': UUID1, 'can_share': False, 'status': 'pending', + 'deleted': False, } self.assertEqual(expected, actual) @@ -1192,7 +1193,8 @@ class DriverTests(object): expected = {'member': TENANT1, 'image_id': UUID1, 'status': 'pending', - 'can_share': False} + 'can_share': False, + 'deleted': False} self.assertEqual(expected, member) member = self.db_api.image_member_update(self.context, @@ -1206,7 +1208,8 @@ class DriverTests(object): expected = {'member': TENANT1, 'image_id': UUID1, 'status': 'pending', - 'can_share': True} + 'can_share': True, + 'deleted': False} self.assertEqual(expected, member) members = self.db_api.image_member_find(self.context, @@ -1233,7 +1236,8 @@ class DriverTests(object): expected = {'member': TENANT1, 'image_id': UUID1, 'status': 'pending', - 'can_share': False} + 'can_share': False, + 'deleted': False} self.assertEqual(expected, member) member = self.db_api.image_member_update(self.context, @@ -1247,7 +1251,8 @@ class DriverTests(object): expected = {'member': TENANT1, 'image_id': UUID1, 'status': 'accepted', - 'can_share': False} + 'can_share': False, + 'deleted': False} self.assertEqual(expected, member) members = self.db_api.image_member_find(self.context, diff --git a/glance/tests/unit/v1/test_registry_api.py b/glance/tests/unit/v1/test_registry_api.py index 065143e88c..41dc3014be 100644 --- a/glance/tests/unit/v1/test_registry_api.py +++ b/glance/tests/unit/v1/test_registry_api.py @@ -1640,6 +1640,34 @@ class TestRegistryAPI(base.IsolatedUnitTest, test_utils.RegistryAPIMixIn): method='PUT', body=body, content_type='json') + def test_update_all_image_existing_deleted_members(self): + """ + Test update existing image members + """ + UUID8 = _gen_uuid() + extra_fixture = self.get_fixture(id=UUID8, size=19, protected=False, + owner='test user') + + db_api.image_create(self.context, extra_fixture) + + # Add a new member to an image + req = webob.Request.blank('/images/%s/members/test1' % UUID8) + req.method = 'PUT' + req.get_response(self.api) + + # Delete the existing member + self.get_api_response_ext(204, method='DELETE', + url='/images/%s/members/test1' % UUID8) + + # Re-add the deleted member by replacing membership list + fixture = [dict(member_id='test1', can_share=False)] + body = jsonutils.dump_as_bytes(dict(memberships=fixture)) + self.get_api_response_ext(204, url='/images/%s/members' % UUID8, + method='PUT', body=body, + content_type='json') + memb_list = db_api.image_member_find(self.context, image_id=UUID8) + self.assertEqual(1, len(memb_list)) + def test_add_member(self): """ Tests adding image members raises right exception