From 5670f142d4fc6ac0b09dfd934c4a2da86b2f36fe Mon Sep 17 00:00:00 2001 From: Amala Basha Date: Tue, 2 Jul 2013 06:50:48 -0500 Subject: [PATCH] index checksum image property checksum image property will be indexed so that users can search for an image by specifying the checksum Change-Id: I31543afed31512f8f5f613640012bc7c1d7ea993 Implements: blueprint index-using-checksum-image-property --- glance/db/sqlalchemy/api.py | 5 +++ .../versions/027_checksum_index.py | 40 +++++++++++++++++++ glance/db/sqlalchemy/models.py | 3 +- glance/tests/unit/test_db.py | 36 ++++++++++++++--- glance/tests/unit/test_migrations.py | 15 +++++++ glance/tests/unit/v2/test_images_resource.py | 35 +++++++++++++--- 6 files changed, 122 insertions(+), 12 deletions(-) create mode 100644 glance/db/sqlalchemy/migrate_repo/versions/027_checksum_index.py diff --git a/glance/db/sqlalchemy/api.py b/glance/db/sqlalchemy/api.py index 7cfc605e4f..216b7b1664 100644 --- a/glance/db/sqlalchemy/api.py +++ b/glance/db/sqlalchemy/api.py @@ -607,6 +607,11 @@ def image_get_all(context, filters=None, marker=None, limit=None, query = query.filter(spec) showing_deleted = False + + if 'checksum' in filters: + checksum = filters.get('checksum') + query = query.filter_by(checksum=checksum) + if 'changes-since' in filters: # normalize timestamp to UTC, as sqlalchemy doesn't appear to # respect timezone offsets diff --git a/glance/db/sqlalchemy/migrate_repo/versions/027_checksum_index.py b/glance/db/sqlalchemy/migrate_repo/versions/027_checksum_index.py new file mode 100644 index 0000000000..7f40bee1c0 --- /dev/null +++ b/glance/db/sqlalchemy/migrate_repo/versions/027_checksum_index.py @@ -0,0 +1,40 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Rackspace Hosting +# 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. + +from sqlalchemy import MetaData, Table, Index + +INDEX_NAME = 'checksum_image_idx' + + +def upgrade(migrate_engine): + meta = MetaData() + meta.bind = migrate_engine + + images = Table('images', meta, autoload=True) + + index = Index(INDEX_NAME, images.c.checksum) + index.create(migrate_engine) + + +def downgrade(migrate_engine): + meta = MetaData() + meta.bind = migrate_engine + + images = Table('images', meta, autoload=True) + + index = Index(INDEX_NAME, images.c.checksum) + index.drop(migrate_engine) diff --git a/glance/db/sqlalchemy/models.py b/glance/db/sqlalchemy/models.py index adb8c02f63..8b971c7e16 100644 --- a/glance/db/sqlalchemy/models.py +++ b/glance/db/sqlalchemy/models.py @@ -107,6 +107,7 @@ class ModelBase(object): class Image(BASE, ModelBase): """Represents an image in the datastore""" __tablename__ = 'images' + __table_args__ = (Index('checksum_image_idx', 'checksum'),) id = Column(String(36), primary_key=True, default=uuidutils.generate_uuid) name = Column(String(255)) @@ -115,7 +116,7 @@ class Image(BASE, ModelBase): size = Column(BigInteger) status = Column(String(30), nullable=False) is_public = Column(Boolean, nullable=False, default=False) - checksum = Column(String(32)) + checksum = Column(String(32), index=True) min_disk = Column(Integer(), nullable=False, default=0) min_ram = Column(Integer(), nullable=False, default=0) owner = Column(String(255)) diff --git a/glance/tests/unit/test_db.py b/glance/tests/unit/test_db.py index 70f6c8bc7b..679dea5789 100644 --- a/glance/tests/unit/test_db.py +++ b/glance/tests/unit/test_db.py @@ -43,6 +43,9 @@ UUID1_LOCATION = 'file:///path/to/image' UUID1_LOCATION_METADATA = {'key': 'value'} UUID3_LOCATION = 'http://somehost.com/place' +CHECKSUM = '93264c3edf5972c9f1cb309543d38a5c' +CHCKSUM1 = '43264c3edf4972c9f1cb309543d38a55' + def _db_fixture(id, **kwargs): obj = { @@ -92,16 +95,17 @@ class TestImageRepo(test_utils.BaseTestCase): def _create_images(self): self.db.reset() self.images = [ - _db_fixture(UUID1, owner=TENANT1, name='1', size=256, + _db_fixture(UUID1, owner=TENANT1, checksum=CHECKSUM, + name='1', size=256, is_public=True, status='active', locations=[{'url': UUID1_LOCATION, 'metadata': UUID1_LOCATION_METADATA}]), - _db_fixture(UUID2, owner=TENANT1, name='2', - size=512, is_public=False), - _db_fixture(UUID3, owner=TENANT3, name='3', - size=1024, is_public=True, + _db_fixture(UUID2, owner=TENANT1, checksum=CHCKSUM1, + name='2', size=512, is_public=False), + _db_fixture(UUID3, owner=TENANT3, checksum=CHCKSUM1, + name='3', size=1024, is_public=True, locations=[{'url': UUID3_LOCATION, - 'metadata': {}}]), + 'metadata': {}}]), _db_fixture(UUID4, owner=TENANT4, name='4', size=2048), ] [self.db.image_create(None, image) for image in self.images] @@ -200,6 +204,26 @@ class TestImageRepo(test_utils.BaseTestCase): image_ids = set([i.image_id for i in images]) self.assertEqual(set([UUID2]), image_ids) + def test_list_with_checksum_filter_single_image(self): + filters = {'checksum': CHECKSUM} + images = self.image_repo.list(filters=filters) + image_ids = list([i.image_id for i in images]) + self.assertEquals(1, len(image_ids)) + self.assertEqual([UUID1], image_ids) + + def test_list_with_checksum_filter_multiple_images(self): + filters = {'checksum': CHCKSUM1} + images = self.image_repo.list(filters=filters) + image_ids = list([i.image_id for i in images]) + self.assertEquals(2, len(image_ids)) + self.assertEqual([UUID3, UUID2], image_ids) + + def test_list_with_wrong_checksum(self): + WRONG_CHKSUM = 'd2fd42f979e1ed1aafadc7eb9354bff839c858cd' + filters = {'checksum': WRONG_CHKSUM} + images = self.image_repo.list(filters=filters) + self.assertEquals(0, len(images)) + def test_list_public_images(self): filters = {'visibility': 'public'} images = self.image_repo.list(filters=filters) diff --git a/glance/tests/unit/test_migrations.py b/glance/tests/unit/test_migrations.py index 5a9a575ec2..0e11f20ff4 100644 --- a/glance/tests/unit/test_migrations.py +++ b/glance/tests/unit/test_migrations.py @@ -712,3 +712,18 @@ class TestMigrations(utils.BaseTestCase): self.assertTrue('meta_data' in r[0]) x = pickle.loads(r[0]['meta_data']) self.assertEqual(x, {}) + + def _check_027(self, engine, data): + table = "images" + index = "checksum_image_idx" + columns = ["checksum"] + + meta = sqlalchemy.MetaData() + meta.bind = engine + + new_table = sqlalchemy.Table(table, meta, autoload=True) + + index_data = [(idx.name, idx.columns.keys()) + for idx in new_table.indexes] + + self.assertIn((index, columns), index_data) diff --git a/glance/tests/unit/v2/test_images_resource.py b/glance/tests/unit/v2/test_images_resource.py index 9add8e7d2c..128a667fed 100644 --- a/glance/tests/unit/v2/test_images_resource.py +++ b/glance/tests/unit/v2/test_images_resource.py @@ -47,6 +47,9 @@ TENANT2 = '2c014f32-55eb-467d-8fcb-4bd706012f81' TENANT3 = '5a3e60e8-cfa9-4a9e-a90a-62b42cea92b8' TENANT4 = 'c6c87f25-8a94-47ed-8c83-053c25f42df4' +CHKSUM = '93264c3edf5972c9f1cb309543d38a5c' +CHKSUM1 = '43254c3edf6972c9f1cb309543d38a8c' + def _db_fixture(id, **kwargs): obj = { @@ -120,21 +123,22 @@ class TestImagesController(test_utils.BaseTestCase): def _create_images(self): self.db.reset() self.images = [ - _db_fixture(UUID1, owner=TENANT1, name='1', size=256, + _db_fixture(UUID1, owner=TENANT1, checksum=CHKSUM, + name='1', size=256, is_public=True, locations=[{'url': '%s/%s' % (BASE_URI, UUID1), 'metadata': {}}], disk_format='raw', container_format='bare', status='active'), - _db_fixture(UUID2, owner=TENANT1, name='2', - size=512, + _db_fixture(UUID2, owner=TENANT1, checksum=CHKSUM1, + name='2', size=512, is_public=True, disk_format='raw', container_format='bare', status='active'), - _db_fixture(UUID3, owner=TENANT3, name='3', - size=512, is_public=True), + _db_fixture(UUID3, owner=TENANT3, checksum=CHKSUM1, + name='3', size=512, is_public=True), _db_fixture(UUID4, owner=TENANT4, name='4', size=1024), ] [self.db.image_create(None, image) for image in self.images] @@ -229,6 +233,27 @@ class TestImagesController(test_utils.BaseTestCase): expected = set([UUID1]) self.assertEqual(actual, expected) + def test_index_with_checksum_filter_single_image(self): + req = unit_test_utils.get_fake_request('/images?checksum=%s' % CHKSUM) + output = self.controller.index(req, filters={'checksum': CHKSUM}) + self.assertEqual(1, len(output['images'])) + actual = list([image.image_id for image in output['images']]) + expected = [UUID1] + self.assertEqual(actual, expected) + + def test_index_with_checksum_filter_multiple_images(self): + req = unit_test_utils.get_fake_request('/images?checksum=%s' % CHKSUM1) + output = self.controller.index(req, filters={'checksum': CHKSUM1}) + self.assertEqual(2, len(output['images'])) + actual = list([image.image_id for image in output['images']]) + expected = [UUID3, UUID2] + self.assertEqual(actual, expected) + + def test_index_with_non_existent_checksum(self): + req = unit_test_utils.get_fake_request('/images?checksum=236231827') + output = self.controller.index(req, filters={'checksum': '236231827'}) + self.assertEqual(0, len(output['images'])) + def test_index_size_max_filter(self): request = unit_test_utils.get_fake_request('/images?size_max=512') output = self.controller.index(request, filters={'size_max': 512})