From 181d86c22626dba4d79d36c82ce56df92bc32c14 Mon Sep 17 00:00:00 2001 From: kairat_kushaev Date: Wed, 21 Sep 2016 11:52:06 +0300 Subject: [PATCH] Add sha1, sha256 support for blobs Previsously, we required only md5 checksum to be specified. This patch provides possibility to calculate sha1, sha256 when uploading to glare. It also provides possibility to specify sha1, sha256 when add custom locations. These hash algorithm are optional but md5 is still required. Change-Id: I07b6868b29b6b32fffa39f50a992d4f8d49b8781 --- glare/api/v1/resource.py | 8 +- glare/common/store_api.py | 14 ++- glare/common/utils.py | 9 +- .../versions/001_initial_version.py | 4 +- glare/db/sqlalchemy/api.py | 2 +- glare/db/sqlalchemy/models.py | 8 +- glare/objects/base.py | 67 ++++++---- glare/objects/meta/fields.py | 8 +- .../tests/functional/test_sample_artifact.py | 21 +++- glare/tests/functional/test_schemas.py | 114 ++++++++++-------- .../unit/db/migrations/test_migrations.py | 4 +- 11 files changed, 159 insertions(+), 100 deletions(-) diff --git a/glare/api/v1/resource.py b/glare/api/v1/resource.py index b5e9e76..351abb2 100644 --- a/glare/api/v1/resource.py +++ b/glare/api/v1/resource.py @@ -481,14 +481,18 @@ class ResponseSerializer(api_versioning.VersionedResource, def _serialize_blob(response, result): data, meta = result['data'], result['meta'] response.headers['Content-Type'] = meta['content_type'] - response.headers['Content-MD5'] = meta['checksum'] + response.headers['Content-MD5'] = meta['md5'] + response.headers['X-Openstack-Glare-Content-SHA1'] = meta['sha1'] + response.headers['X-Openstack-Glare-Content-SHA256'] = meta['sha256'] response.headers['Content-Length'] = str(meta['size']) response.app_iter = iter(data) @staticmethod def _serialize_location(response, result): data, meta = result['data'], result['meta'] - response.headers['Content-MD5'] = meta['checksum'] + response.headers['Content-MD5'] = meta['md5'] + response.headers['X-Openstack-Glare-Content-SHA1'] = meta['sha1'] + response.headers['X-Openstack-Glare-Content-SHA256'] = meta['sha256'] response.location = data['url'] response.content_type = 'application/json' response.status = http_client.MOVED_PERMANENTLY diff --git a/glare/common/store_api.py b/glare/common/store_api.py index a4f87a0..51f04a4 100644 --- a/glare/common/store_api.py +++ b/glare/common/store_api.py @@ -60,13 +60,15 @@ def save_blob_to_store(blob_id, blob, context, max_size, :param blob: blob file iterator :param context: user context :param verifier:signature verified - :return: tuple of values: (location_uri, size, checksum, metadata) + :return: tuple of values: (location_uri, size, checksums, metadata) """ - (location, size, checksum, metadata) = backend.add_to_backend( - CONF, blob_id, - utils.LimitingReader(utils.CooperativeReader(blob), max_size), - 0, store_type, context, verifier) - return location, size, checksum + data = utils.LimitingReader(utils.CooperativeReader(blob), max_size) + (location, size, md5checksum, metadata) = backend.add_to_backend( + CONF, blob_id, data, 0, store_type, context, verifier) + checksums = {"md5": md5checksum, + "sha1": data.sha1.hexdigest(), + "sha256": data.sha256.hexdigest()} + return location, size, checksums @utils.error_handler(error_map) diff --git a/glare/common/utils.py b/glare/common/utils.py index e008673..cbd6aa1 100644 --- a/glare/common/utils.py +++ b/glare/common/utils.py @@ -29,6 +29,7 @@ except ImportError: from eventlet.green import socket import functools +import hashlib import os import re import uuid @@ -213,6 +214,8 @@ class LimitingReader(object): self.data = data self.limit = limit self.bytes_read = 0 + self.sha1 = hashlib.sha1() + self.sha256 = hashlib.sha256() def __iter__(self): for chunk in self.data: @@ -224,7 +227,11 @@ class LimitingReader(object): def read(self, i): result = self.data.read(i) - self.bytes_read += len(result) + len_result = len(result) + self.bytes_read += len_result + if len_result: + self.sha1.update(result) + self.sha256.update(result) if self.bytes_read > self.limit: raise exception.RequestEntityTooLarge() return result diff --git a/glare/db/migration/alembic_migrations/versions/001_initial_version.py b/glare/db/migration/alembic_migrations/versions/001_initial_version.py index 32376c5..468a240 100644 --- a/glare/db/migration/alembic_migrations/versions/001_initial_version.py +++ b/glare/db/migration/alembic_migrations/versions/001_initial_version.py @@ -99,7 +99,9 @@ def upgrade(): sa.Column('artifact_id', sa.String(36), sa.ForeignKey('glare_artifacts.id'), nullable=False), sa.Column('size', sa.BigInteger()), - sa.Column('checksum', sa.String(32)), + sa.Column('md5', sa.String(32)), + sa.Column('sha1', sa.String(40)), + sa.Column('sha256', sa.String(64)), sa.Column('name', sa.String(255), nullable=False), sa.Column('status', sa.String(32), nullable=False), sa.Column('external', sa.Boolean()), diff --git a/glare/db/sqlalchemy/api.py b/glare/db/sqlalchemy/api.py index 5eb5789..6f5eecd 100644 --- a/glare/db/sqlalchemy/api.py +++ b/glare/db/sqlalchemy/api.py @@ -534,7 +534,7 @@ def _do_properties(artifact, new_properties): def _update_blob_values(blob, values): - for elem in ('size', 'checksum', 'url', 'external', 'status', + for elem in ('size', 'md5', 'sha1', 'sha256', 'url', 'external', 'status', 'content_type'): setattr(blob, elem, values[elem]) return blob diff --git a/glare/db/sqlalchemy/models.py b/glare/db/sqlalchemy/models.py index 9eeebbc..b3b8fb9 100644 --- a/glare/db/sqlalchemy/models.py +++ b/glare/db/sqlalchemy/models.py @@ -81,7 +81,9 @@ def _parse_blob_value(blob): "url": blob.url, "status": blob.status, "external": blob.external, - "checksum": blob.checksum, + "md5": blob.md5, + "sha1": blob.sha1, + "sha256": blob.sha256, "size": blob.size, "content_type": blob.content_type } @@ -222,7 +224,9 @@ class ArtifactBlob(BASE, ArtifactBase): nullable=False) name = Column(String(255), nullable=False) size = Column(BigInteger().with_variant(Integer, "sqlite")) - checksum = Column(String(32)) + md5 = Column(String(32)) + sha1 = Column(String(40)) + sha256 = Column(String(64)) external = Column(Boolean) url = Column(Text) status = Column(String(32), nullable=False) diff --git a/glare/objects/base.py b/glare/objects/base.py index 4009d61..a3b07d2 100644 --- a/glare/objects/base.py +++ b/glare/objects/base.py @@ -811,20 +811,21 @@ class BaseArtifact(base.VersionedObject): "upload passed for blob %(blob)s. " "Start blob uploading to backend.", {'artifact': af.id, 'blob': field_name}) - blob = {'url': None, 'size': None, 'checksum': None, - 'status': glare_fields.BlobFieldType.SAVING, 'external': False, - 'content_type': content_type} + blob = {'url': None, 'size': None, 'md5': None, 'sha1': None, + 'sha256': None, 'status': glare_fields.BlobFieldType.SAVING, + 'external': False, 'content_type': content_type} setattr(af, field_name, blob) cls.db_api.update( context, af.id, {field_name: getattr(af, field_name)}) blob_id = getattr(af, field_name)['id'] try: - location_uri, size, checksum = store_api.save_blob_to_store( + location_uri, size, checksums = store_api.save_blob_to_store( blob_id, fd, context, cls._get_max_blob_size(field_name)) blob.update({'url': location_uri, 'status': glare_fields.BlobFieldType.ACTIVE, - 'size': size, 'checksum': checksum}) + 'size': size}) + blob.update(checksums) setattr(af, field_name, blob) af_upd = cls.db_api.update( context, af.id, {field_name: getattr(af, field_name)}) @@ -856,7 +857,9 @@ class BaseArtifact(base.VersionedObject): if blob is None or blob['status'] != glare_fields.BlobFieldType.ACTIVE: msg = _("%s is not ready for download") % field_name raise exception.BadRequest(message=msg) - meta = {'checksum': blob.get('checksum'), + meta = {'md5': blob.get('md5'), + 'sha1': blob.get('sha1'), + 'sha256': blob.get('sha256'), 'external': blob.get('external')} if blob['external']: data = {'url': blob['url']} @@ -886,20 +889,21 @@ class BaseArtifact(base.VersionedObject): "upload passed for blob dict %(blob)s with key %(key)s. " "Start blob uploading to backend.", {'artifact': af.id, 'blob': field_name, 'key': blob_key}) - blob = {'url': None, 'size': None, 'checksum': None, - 'status': glare_fields.BlobFieldType.SAVING, 'external': False, - 'content_type': content_type} + blob = {'url': None, 'size': None, 'md5': None, 'sha1': None, + 'sha256': None, 'status': glare_fields.BlobFieldType.SAVING, + 'external': False, 'content_type': content_type} blob_dict_attr = getattr(af, field_name) blob_dict_attr[blob_key] = blob cls.db_api.update( context, af.id, {field_name: blob_dict_attr}) blob_id = getattr(af, field_name)[blob_key]['id'] try: - location_uri, size, checksum = store_api.save_blob_to_store( + location_uri, size, checksums = store_api.save_blob_to_store( blob_id, fd, context, cls._get_max_blob_size(field_name)) blob.update({'url': location_uri, 'status': glare_fields.BlobFieldType.ACTIVE, - 'size': size, 'checksum': checksum}) + 'size': size}) + blob.update(checksums) af_values = cls.db_api.update( context, af.id, {field_name: blob_dict_attr}) LOG.info(_LI("Successfully finished blob upload for artifact " @@ -940,8 +944,11 @@ class BaseArtifact(base.VersionedObject): "is not ready for download") % (blob_key, field_name) LOG.error(msg) raise exception.BadRequest(message=msg) - meta = {'checksum': blob.get('checksum'), + meta = {'md5': blob.get('md5'), + 'sha1': blob.get('sha1'), + 'sha256': blob.get('sha256'), 'external': blob.get('external')} + if blob['external']: data = {'url': blob['url']} else: @@ -965,17 +972,20 @@ class BaseArtifact(base.VersionedObject): "passed for blob %(blob)s. Start location check for artifact" ".", {'artifact': af.id, 'blob': field_name}) - blob = {'url': location, 'size': None, 'checksum': None, - 'status': glare_fields.BlobFieldType.ACTIVE, 'external': True, - 'content_type': None} + blob = {'url': location, 'size': None, 'md5': None, 'sha1': None, + 'sha256': None, 'status': glare_fields.BlobFieldType.ACTIVE, + 'external': True, 'content_type': None} - if blob_meta.get('checksum') is None: - msg = (_("Incorrect blob metadata %(meta)s. Checksum is required " + md5 = blob_meta.pop("md5", None) + if md5 is None: + msg = (_("Incorrect blob metadata %(meta)s. MD5 must be specified " "for external location in artifact blob %(field_name)."), {"meta": str(blob_meta), "field_name": field_name}) raise exception.BadRequest(msg) else: - blob['checksum'] = blob_meta['checksum'] + blob["md5"] = md5 + blob["sha1"] = blob_meta.pop("sha1", None) + blob["sha256"] = blob_meta.pop("sha256", None) setattr(af, field_name, blob) updated_af = cls.db_api.update( @@ -991,19 +1001,22 @@ class BaseArtifact(base.VersionedObject): blob_key, location, blob_meta): cls._validate_upload_allowed(context, af, field_name, blob_key) - blob = {'url': location, 'size': None, 'checksum': None, - 'status': glare_fields.BlobFieldType.ACTIVE, 'external': True, - 'content_type': None} + blob = {'url': location, 'size': None, 'md5': None, 'sha1': None, + 'sha256': None, 'status': glare_fields.BlobFieldType.ACTIVE, + 'external': True, 'content_type': None} - if blob_meta.get('checksum') is None: - msg = (_("Incorrect blob metadata %(meta)s. Checksum is required " + md5 = blob_meta.pop("md5", None) + if md5 is None: + msg = (_("Incorrect blob metadata %(meta)s. MD5 must be specified " "for external location in artifact blob " "%(field_name)[%(blob_key)s]."), {"meta": str(blob_meta), "field_name": field_name, "blob_key": str(blob_key)}) raise exception.BadRequest(msg) else: - blob['checksum'] = blob_meta['checksum'] + blob["md5"] = md5 + blob["sha1"] = blob_meta.pop("sha1", None) + blob["sha256"] = blob_meta.pop("sha256", None) blob_dict_attr = getattr(af, field_name) blob_dict_attr[blob_key] = blob @@ -1095,14 +1108,16 @@ class BaseArtifact(base.VersionedObject): 'type': ['object', 'null'], 'properties': { 'size': {'type': ['number', 'null']}, - 'checksum': {'type': ['string', 'null']}, + 'md5': {'type': ['string', 'null']}, + 'sha1': {'type': ['string', 'null']}, + 'sha256': {'type': ['string', 'null']}, 'external': {'type': 'boolean'}, 'status': {'type': 'string', 'enum': list( glare_fields.BlobFieldType.BLOB_STATUS)}, 'content_type': {'type': 'string'}, }, - 'required': ['size', 'checksum', 'external', 'status', + 'required': ['size', 'md5', 'sha1', 'sha256', 'external', 'status', 'content_type'], 'additionalProperties': False } diff --git a/glare/objects/meta/fields.py b/glare/objects/meta/fields.py index 036242f..5109899 100644 --- a/glare/objects/meta/fields.py +++ b/glare/objects/meta/fields.py @@ -63,15 +63,17 @@ class BlobFieldType(fields.FieldType): 'url': {'type': ['string', 'null'], 'format': 'uri', 'max_length': 255}, 'size': {'type': ['number', 'null']}, - 'checksum': {'type': ['string', 'null']}, + 'md5': {'type': ['string', 'null']}, + 'sha1': {'type': ['string', 'null']}, + 'sha256': {'type': ['string', 'null']}, 'external': {'type': 'boolean'}, 'id': {'type': 'string'}, 'status': {'type': 'string', 'enum': list(BLOB_STATUS)}, 'content_type': {'type': ['string', 'null']}, }, - 'required': ['url', 'size', 'checksum', 'external', 'status', - 'id', 'content_type'] + 'required': ['url', 'size', 'md5', 'sha1', 'sha256', 'external', + 'status', 'id', 'content_type'] } @staticmethod diff --git a/glare/tests/functional/test_sample_artifact.py b/glare/tests/functional/test_sample_artifact.py index 39d8299..0d6a551 100644 --- a/glare/tests/functional/test_sample_artifact.py +++ b/glare/tests/functional/test_sample_artifact.py @@ -13,6 +13,7 @@ # License for the specific language governing permissions and limitations # under the License. +import hashlib import uuid from oslo_serialization import jsonutils @@ -833,7 +834,7 @@ class TestBlobs(TestArtifact): self.delete(url) def test_blob_download(self): - data = 'data' + data = 'some_arbitrary_testing_data' art = self.create_artifact(data={'name': 'test_af', 'version': '0.0.1'}) url = '/sample_artifact/%s' % art['id'] @@ -851,6 +852,12 @@ class TestBlobs(TestArtifact): art = self.put(url=url + '/blob', data=data, status=200, headers=headers) self.assertEqual('active', art['blob']['status']) + md5 = hashlib.md5(data.encode('UTF-8')).hexdigest() + sha1 = hashlib.sha1(data.encode('UTF-8')).hexdigest() + sha256 = hashlib.sha256(data.encode('UTF-8')).hexdigest() + self.assertEqual(md5, art['blob']['md5']) + self.assertEqual(sha1, art['blob']['sha1']) + self.assertEqual(sha256, art['blob']['sha256']) blob_data = self.get(url=url + '/blob') self.assertEqual(data, blob_data) @@ -880,7 +887,7 @@ class TestBlobs(TestArtifact): url = '/sample_artifact/%s' % art['id'] body = jsonutils.dumps( {'url': 'https://www.apache.org/licenses/LICENSE-2.0.txt', - 'checksum': "fake"}) + 'md5': "fake", 'sha1': "fake_sha", "sha256": "fake_sha256"}) headers = {'Content-Type': 'application/vnd+openstack.glare-custom-location+json'} self.put(url=url + '/blob', data=body, @@ -895,7 +902,9 @@ class TestBlobs(TestArtifact): # Get the artifact, blob property should have status 'active' art = self.get(url=url, status=200) self.assertEqual('active', art['blob']['status']) - self.assertIsNotNone(art['blob']['checksum']) + self.assertEqual('fake', art['blob']['md5']) + self.assertEqual('fake_sha', art['blob']['sha1']) + self.assertEqual('fake_sha256', art['blob']['sha256']) self.assertIsNone(art['blob']['size']) self.assertIsNone(art['blob']['content_type']) self.assertEqual('https://www.apache.org/licenses/LICENSE-2.0.txt', @@ -910,7 +919,7 @@ class TestBlobs(TestArtifact): # Get the artifact, blob property should have status 'active' art = self.get(url=url, status=200) self.assertEqual('active', art['dict_of_blobs']['blob']['status']) - self.assertIsNotNone(art['dict_of_blobs']['blob']['checksum']) + self.assertIsNotNone(art['dict_of_blobs']['blob']['md5']) self.assertIsNone(art['dict_of_blobs']['blob']['size']) self.assertIsNone(art['dict_of_blobs']['blob']['content_type']) self.assertEqual('https://www.apache.org/licenses/LICENSE-2.0.txt', @@ -1041,7 +1050,7 @@ class TestArtifactOps(TestArtifact): self.create_artifact(data={"name": "test_af", "version": "0.0.2", "blob": { 'url': None, 'size': None, - 'checksum': None, 'status': 'saving', + 'md5': None, 'status': 'saving', 'external': False}}, status=400) # check that anonymous user cannot create artifact self.set_user("anonymous") @@ -1361,7 +1370,7 @@ class TestUpdate(TestArtifact): "op": "replace", "path": "/blob", "value": {"name": "test_af", "version": "0.0.2", - "blob": {'url': None, 'size': None, 'checksum': None, + "blob": {'url': None, 'size': None, 'md5': None, 'status': 'saving', 'external': False}}}] self.patch(url, blob_attr, 400) blob_attr[0]["path"] = "/dict_of_blobs/-" diff --git a/glare/tests/functional/test_schemas.py b/glare/tests/functional/test_schemas.py index e6e32ad..29b6cc0 100644 --- a/glare/tests/functional/test_schemas.py +++ b/glare/tests/functional/test_schemas.py @@ -63,8 +63,9 @@ fixture_base_props = { u'icon': {u'additionalProperties': False, u'description': u'Artifact icon.', u'filter_ops': [], - u'properties': {u'checksum': {u'type': [u'string', - u'null']}, + u'properties': {u'md5': {u'type': [u'string', u'null']}, + u'sha1': {u'type': [u'string', u'null']}, + u'sha256': {u'type': [u'string', u'null']}, u'content_type': {u'type': u'string'}, u'external': {u'type': u'boolean'}, u'size': {u'type': [u'number', @@ -74,7 +75,7 @@ fixture_base_props = { u'pending_delete'], u'type': u'string'}}, u'required': [u'size', - u'checksum', + u'md5', u'sha1', u'sha256', u'external', u'status', u'content_type'], @@ -248,9 +249,10 @@ fixtures = { u'description': u'I am Blob', u'filter_ops': [], u'mutable': True, - u'properties': {u'checksum': { - u'type': [u'string', - u'null']}, + u'properties': { + u'md5': {u'type': [u'string', u'null']}, + u'sha1': {u'type': [u'string', u'null']}, + u'sha256': {u'type': [u'string', u'null']}, u'content_type': { u'type': u'string'}, u'external': { @@ -265,7 +267,7 @@ fixtures = { u'pending_delete'], u'type': u'string'}}, u'required': [u'size', - u'checksum', + u'md5', u'sha1', u'sha256', u'external', u'status', u'content_type'], @@ -297,9 +299,10 @@ fixtures = { u'dict_of_blobs': { u'additionalProperties': { u'additionalProperties': False, - u'properties': {u'checksum': { - u'type': [u'string', - u'null']}, + u'properties': { + u'md5': {u'type': [u'string', u'null']}, + u'sha1': {u'type': [u'string', u'null']}, + u'sha256': {u'type': [u'string', u'null']}, u'content_type': { u'type': u'string'}, u'external': { @@ -315,7 +318,7 @@ fixtures = { u'pending_delete'], u'type': u'string'}}, u'required': [u'size', - u'checksum', + u'md5', u'sha1', u'sha256', u'external', u'status', u'content_type'], @@ -450,9 +453,10 @@ fixtures = { u'small_blob': {u'additionalProperties': False, u'filter_ops': [], u'mutable': True, - u'properties': {u'checksum': { - u'type': [u'string', - u'null']}, + u'properties': { + u'md5': {u'type': [u'string', u'null']}, + u'sha1': {u'type': [u'string', u'null']}, + u'sha256': {u'type': [u'string', u'null']}, u'content_type': { u'type': u'string'}, u'external': { @@ -468,7 +472,7 @@ fixtures = { u'pending_delete'], u'type': u'string'}}, u'required': [u'size', - u'checksum', + u'md5', u'sha1', u'sha256', u'external', u'status', u'content_type'], @@ -546,19 +550,21 @@ fixtures = { u'additionalProperties': False, u'description': u'TOSCA template body.', u'filter_ops': [], - u'properties': {u'checksum': {u'type': [u'string', - u'null']}, - u'content_type': { - u'type': u'string'}, - u'external': {u'type': u'boolean'}, - u'size': {u'type': [u'number', - u'null']}, - u'status': {u'enum': [u'saving', - u'active', - u'pending_delete'], - u'type': u'string'}}, + u'properties': { + u'md5': {u'type': [u'string', u'null']}, + u'sha1': {u'type': [u'string', u'null']}, + u'sha256': {u'type': [u'string', u'null']}, + u'content_type': { + u'type': u'string'}, + u'external': {u'type': u'boolean'}, + u'size': {u'type': [u'number', + u'null']}, + u'status': {u'enum': [u'saving', + u'active', + u'pending_delete'], + u'type': u'string'}}, u'required': [u'size', - u'checksum', + u'md5', u'sha1', u'sha256', u'external', u'status', u'content_type'], @@ -645,8 +651,9 @@ fixtures = { u'additionalProperties': False, u'description': u'Murano Package binary.', u'filter_ops': [], - u'properties': {u'checksum': {u'type': [u'string', - u'null']}, + u'properties': {u'md5': {u'type': [u'string', u'null']}, + u'sha1': {u'type': [u'string', u'null']}, + u'sha256': {u'type': [u'string', u'null']}, u'content_type': {u'type': u'string'}, u'external': {u'type': u'boolean'}, u'size': {u'type': [u'number', @@ -656,7 +663,7 @@ fixtures = { u'pending_delete'], u'type': u'string'}}, u'required': [u'size', - u'checksum', + u'md5', u'sha1', u'sha256', u'external', u'status', u'content_type'], @@ -738,8 +745,9 @@ fixtures = { u'description': u'Image binary.', u'filter_ops': [], u'properties': { - u'checksum': {u'type': [u'string', - u'null']}, + u'md5': {u'type': [u'string', u'null']}, + u'sha1': {u'type': [u'string', u'null']}, + u'sha256': {u'type': [u'string', u'null']}, u'content_type': {u'type': u'string'}, u'external': {u'type': u'boolean'}, u'size': {u'type': [u'number', @@ -749,7 +757,7 @@ fixtures = { u'pending_delete'], u'type': u'string'}}, u'required': [u'size', - u'checksum', + u'md5', u'sha1', u'sha256', u'external', u'status', u'content_type'], @@ -877,8 +885,9 @@ fixtures = { u'additionalProperties': {u'additionalProperties': False, u'properties': { - u'checksum': {u'type': [u'string', - u'null']}, + u'md5': {u'type': [u'string', u'null']}, + u'sha1': {u'type': [u'string', u'null']}, + u'sha256': {u'type': [u'string', u'null']}, u'content_type': { u'type': u'string'}, u'external': {u'type': u'boolean'}, @@ -889,7 +898,7 @@ fixtures = { u'pending_delete'], u'type': u'string'}}, u'required': [u'size', - u'checksum', + u'md5', u'sha1', u'sha256', u'external', u'status', u'content_type'], @@ -907,19 +916,21 @@ fixtures = { u'additionalProperties': False, u'description': u'Heat template body.', u'filter_ops': [], - u'properties': {u'checksum': {u'type': [u'string', - u'null']}, - u'content_type': { - u'type': u'string'}, - u'external': {u'type': u'boolean'}, - u'size': {u'type': [u'number', - u'null']}, - u'status': {u'enum': [u'saving', - u'active', - u'pending_delete'], - u'type': u'string'}}, + u'properties': { + u'md5': {u'type': [u'string', u'null']}, + u'sha1': {u'type': [u'string', u'null']}, + u'sha256': {u'type': [u'string', u'null']}, + u'content_type': { + u'type': u'string'}, + u'external': {u'type': u'boolean'}, + u'size': {u'type': [u'number', + u'null']}, + u'status': {u'enum': [u'saving', + u'active', + u'pending_delete'], + u'type': u'string'}}, u'required': [u'size', - u'checksum', + u'md5', u'sha1', u'sha256', u'external', u'status', u'content_type'], @@ -937,8 +948,9 @@ fixtures = { u'additionalProperties': False, u'description': u'Heat Environment text body.', u'filter_ops': [], - u'properties': {u'checksum': {u'type': [u'string', - u'null']}, + u'properties': {u'md5': {u'type': [u'string', u'null']}, + u'sha1': {u'type': [u'string', u'null']}, + u'sha256': {u'type': [u'string', u'null']}, u'content_type': {u'type': u'string'}, u'external': {u'type': u'boolean'}, u'size': {u'type': [u'number', @@ -948,7 +960,7 @@ fixtures = { u'pending_delete'], u'type': u'string'}}, u'required': [u'size', - u'checksum', + u'md5', u'sha1', u'sha256', u'external', u'status', u'content_type'], diff --git a/glare/tests/unit/db/migrations/test_migrations.py b/glare/tests/unit/db/migrations/test_migrations.py index 48e7ab0..e6c1600 100644 --- a/glare/tests/unit/db/migrations/test_migrations.py +++ b/glare/tests/unit/db/migrations/test_migrations.py @@ -190,7 +190,9 @@ class GlareMigrationsCheckers(object): blobs_columns = ['id', 'artifact_id', 'size', - 'checksum', + 'md5', + 'sha1', + 'sha256', 'name', 'key_name', 'external',