diff --git a/glance/db/simple/api.py b/glance/db/simple/api.py index 489d2474..44a0f6fc 100644 --- a/glance/db/simple/api.py +++ b/glance/db/simple/api.py @@ -28,6 +28,7 @@ DATA = { 'images': {}, 'members': {}, 'tags': {}, + 'locations': [], } @@ -52,6 +53,7 @@ def reset(): 'images': {}, 'members': [], 'tags': {}, + 'locations': [], } @@ -77,6 +79,20 @@ def _get_session(): return DATA +def _image_locations_format(image_id, value, meta_data): + dt = timeutils.utcnow() + return { + 'id': uuidutils.generate_uuid(), + 'image_id': image_id, + 'created_at': dt, + 'updated_at': dt, + 'deleted_at': None, + 'deleted': False, + 'url': value, + 'metadata': meta_data, + } + + def _image_property_format(image_id, name, value): return { 'image_id': image_id, @@ -123,6 +139,15 @@ def _image_format(image_id, **values): 'deleted': False, } + locations = values.pop('locations', None) + if locations is not None: + locations = [ + _image_locations_format(image_id, location['url'], + location['metadata']) + for location in locations + ] + image['locations'] = locations + #NOTE(bcwaldon): store properties as a list to match sqlalchemy driver properties = values.pop('properties', {}) properties = [{'name': k, @@ -246,6 +271,8 @@ def _sort_images(images, sort_key, sort_dir): def _image_get(context, image_id, force_show_deleted=False, status=None): try: image = DATA['images'][image_id] + image['locations'] = _image_location_get_all(image_id) + except KeyError: LOG.info(_('Could not find image %s') % image_id) raise exception.NotFound() @@ -264,6 +291,7 @@ def _image_get(context, image_id, force_show_deleted=False, status=None): @log_call def image_get(context, image_id, session=None, force_show_deleted=False): image = _image_get(context, image_id, force_show_deleted) + image = _normalize_locations(image) return copy.deepcopy(image) @@ -279,6 +307,11 @@ def image_get_all(context, filters=None, marker=None, limit=None, images = _sort_images(images, sort_key, sort_dir) images = _do_pagination(context, images, marker, limit, filters.get('deleted')) + + for image in images: + image['locations'] = _image_location_get_all(image['id']) + _normalize_locations(image) + return images @@ -364,6 +397,42 @@ def image_member_delete(context, member_id): raise exception.NotFound() +def _image_locations_set(image_id, locations): + global DATA + image = DATA['images'][image_id] + for location in image['locations']: + location['deleted'] = True + location['deleted_at'] = timeutils.utcnow() + + for i, location in enumerate(DATA['locations']): + if image_id == location['image_id'] and location['deleted'] is False: + del DATA['locations'][i] + + for location in locations: + location_ref = _image_locations_format(image_id, value=location['url'], + meta_data=location['metadata']) + DATA['locations'].append(location_ref) + + image['locations'].append(location_ref) + + +def _normalize_locations(image): + undeleted_locations = filter(lambda x: not x['deleted'], + image['locations']) + image['locations'] = [{'url': loc['url'], + 'metadata': loc['metadata']} + for loc in undeleted_locations] + return image + + +def _image_location_get_all(image_id): + location_data = [] + for location in DATA['locations']: + if image_id == location['image_id']: + location_data.append(location) + return location_data + + @log_call def image_create(context, image_values): global DATA @@ -388,8 +457,14 @@ def image_create(context, image_values): image = _image_format(image_id, **image_values) DATA['images'][image_id] = image + + location_data = image_values.get('locations', None) + if location_data is not None: + _image_locations_set(image_id, location_data) + DATA['tags'][image_id] = image.pop('tags', []) - return image + + return _normalize_locations(copy.deepcopy(image)) @log_call @@ -400,6 +475,10 @@ def image_update(context, image_id, image_values, purge_props=False): except KeyError: raise exception.NotFound(image_id=image_id) + location_data = image_values.pop('locations', None) + if location_data is not None: + _image_locations_set(image_id, location_data) + # replace values for properties that already exist new_properties = image_values.pop('properties', {}) for prop in image['properties']: @@ -417,7 +496,7 @@ def image_update(context, image_id, image_values, purge_props=False): image['updated_at'] = timeutils.utcnow() image.update(image_values) DATA['images'][image_id] = image - return image + return _normalize_locations(image) @log_call @@ -427,6 +506,8 @@ def image_destroy(context, image_id): DATA['images'][image_id]['deleted'] = True DATA['images'][image_id]['deleted_at'] = timeutils.utcnow() + _image_locations_set(image_id, []) + for prop in DATA['images'][image_id]['properties']: image_property_delete(context, prop['name'], image_id) @@ -438,6 +519,8 @@ def image_destroy(context, image_id): for tag in tags: image_tag_delete(context, image_id, tag) + _normalize_locations(DATA['images'][image_id]) + return copy.deepcopy(DATA['images'][image_id]) except KeyError: raise exception.NotFound() diff --git a/glance/tests/functional/db/base.py b/glance/tests/functional/db/base.py index 7f70a5da..34b7b415 100644 --- a/glance/tests/functional/db/base.py +++ b/glance/tests/functional/db/base.py @@ -158,14 +158,18 @@ class DriverTests(object): fixture = {'status': 'queued', 'locations': locations} image = self.db_api.image_create(self.context, fixture) - self.assertEqual(locations, image['locations']) + actual = [{'url': l['url'], 'metadata': l['metadata']} + for l in image['locations']] + self.assertEqual(locations, actual) def test_image_create_with_location_data(self): location_data = [{'url': 'a', 'metadata': {'key': 'value'}}, {'url': 'b', 'metadata': {}}] fixture = {'status': 'queued', 'locations': location_data} image = self.db_api.image_create(self.context, fixture) - self.assertEqual(location_data, image['locations']) + actual = [{'url': l['url'], 'metadata': l['metadata']} + for l in image['locations']] + self.assertEqual(location_data, actual) def test_image_create_properties(self): fixture = {'status': 'queued', 'properties': {'ping': 'pong'}} diff --git a/glance/tests/unit/utils.py b/glance/tests/unit/utils.py index adfb88e3..ef18279a 100644 --- a/glance/tests/unit/utils.py +++ b/glance/tests/unit/utils.py @@ -84,6 +84,7 @@ class FakeDB(object): 'images': {}, 'members': [], 'tags': {}, + 'locations': [], } def __getattr__(self, key): diff --git a/glance/tests/unit/v2/test_image_members_resource.py b/glance/tests/unit/v2/test_image_members_resource.py index 8b9c4ea7..681e3eb5 100644 --- a/glance/tests/unit/v2/test_image_members_resource.py +++ b/glance/tests/unit/v2/test_image_members_resource.py @@ -107,7 +107,8 @@ class TestImageMembersController(test_utils.BaseTestCase): self.images = [ _db_fixture(UUID1, owner=TENANT1, name='1', size=256, is_public=True, - locations=['%s/%s' % (BASE_URI, UUID1)]), + locations=[{'url': '%s/%s' % (BASE_URI, UUID1), + 'metadata': {}}]), _db_fixture(UUID2, owner=TENANT1, name='2', size=512), _db_fixture(UUID3, owner=TENANT3, name='3', size=512), _db_fixture(UUID4, owner=TENANT4, name='4', size=1024),