diff options
author | Darja Shakhray <dshakhray@mirantis.com> | 2015-07-02 16:12:50 +0300 |
---|---|---|
committer | Mike Fedosin <mfedosin@mirantis.com> | 2015-08-26 20:02:25 +0300 |
commit | 2506953b7fdd9ee899cd0589b8995ae6f4db30ca (patch) | |
tree | 36fe16d63fc67cfad94f61e1bdee8ab8ad4ba40e | |
parent | c71a87241e15d819b08dd182b7cc295bf6b3ab62 (diff) | |
download | python-glanceclient-2506953b7fdd9ee899cd0589b8995ae6f4db30ca.tar.gz |
Glance v3 client methods for work with blobs
This commit adds methods for work with BLOB and related tests to the client.
Currently implemented methods: 'delete_blob', 'download_blob', 'upload_blob'
Co-Authored-By: Mike Fedosin <mfedosin@mirantis.com>
FastTrack
Implements-blueprint: artifact-repository
Change-Id: I29357ee117d7dbdaed58ab12ccb10ba575820b2a
-rw-r--r-- | glanceclient/tests/unit/v3/test_artifacts.py | 179 | ||||
-rw-r--r-- | glanceclient/v3/artifacts.py | 60 |
2 files changed, 235 insertions, 4 deletions
diff --git a/glanceclient/tests/unit/v3/test_artifacts.py b/glanceclient/tests/unit/v3/test_artifacts.py index 91597e0..d40528f 100644 --- a/glanceclient/tests/unit/v3/test_artifacts.py +++ b/glanceclient/tests/unit/v3/test_artifacts.py @@ -11,7 +11,7 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. - +import errno import testtools from glanceclient import exc @@ -124,6 +124,63 @@ data_fixtures = { finn='human', **type_fixture) ), }, + '/v3/artifacts/adventure_time/v1.0.2/' + '18f67e12-88e0-4b13-86fb-5adc36d884b6/image_file': { + 'PUT': ( + {}, + '', + ), + + 'DELETE': ( + {}, + '', + ), + }, + '/v3/artifacts/adventure_time/v1.0.2/' + '18f67e12-88e0-4b13-86fb-5adc36d884b6/image_file/download': { + 'GET': ( + {}, + 'Princess Bubblegum rocks!!!', + ), + }, + '/v3/artifacts/adventure_time/v1.0.2/' + '5cc4bebc-db27-11e1-a1eb-080027cbe205/image_file/download': { + 'GET': ( + { + 'content-md5': '5a3c872ee92e2c58efd0d47862eb9c85' + }, + 'Princess Bubblegum rocks!!!', + ), + }, + '/v3/artifacts/adventure_time/v1.0.2/' + '66fb18d6-db27-11e1-a1eb-080027cbe205/image_file/download': { + 'GET': ( + { + 'content-md5': 'Lich was here!!!' + }, + 'Princess Bubblegum rocks!!!', + ), + }, + '/v3/artifacts/adventure_time/v1.0.2/' + '18f67e12-88e0-4b13-86fb-5adc36d884b6/screenshots/1': { + 'PUT': ( + {}, + '', + ), + + 'DELETE': ( + {}, + '', + ), + }, + '/v3/artifacts/adventure_time/v1.0.2/' + '18f67e12-88e0-4b13-86fb-5adc36d884b6/screenshots/1/download': { + 'GET': ( + {}, + 'What time is it?', + ), + }, + } @@ -300,3 +357,123 @@ class TestController(testtools.TestCase): self.assertEqual(artifact_id, artifact.id) self.assertEqual('Gunter The Penguin', artifact.name) self.assertEqual('human', artifact.type_specific_properties['finn']) + + def test_upload_blob(self): + image_data = 'Adventure Time with Finn & Jake' + + artifact_id = '18f67e12-88e0-4b13-86fb-5adc36d884b6' + + params = {'blob_property': 'image_file', 'artifact_id': artifact_id, + 'data': image_data} + params.update(type_fixture) + + self.controller.upload_blob(**params) + expect_hdrs = {'Content-Type': 'application/octet-stream'} + expect = [('PUT', '/v3/artifacts/adventure_time/v1.0.2/' + '%s/image_file' % artifact_id, expect_hdrs, image_data)] + + self.assertEqual(expect, self.api.calls) + + def test_delete_blob(self): + artifact_id = '18f67e12-88e0-4b13-86fb-5adc36d884b6' + + params = {'blob_property': 'image_file', 'artifact_id': artifact_id} + params.update(type_fixture) + self.controller.delete_blob(**params) + expect = [('DELETE', '/v3/artifacts/adventure_time/v1.0.2/' + '%s/image_file' % artifact_id, {}, None)] + + self.assertEqual(expect, self.api.calls) + + def test_download_blob(self): + artifact_id = '18f67e12-88e0-4b13-86fb-5adc36d884b6' + + params = {'blob_property': 'image_file', 'artifact_id': artifact_id} + params.update(type_fixture) + body = ''.join([b for b in self.controller.download_blob(**params)]) + + expect = [('GET', '/v3/artifacts/adventure_time/v1.0.2/' + '%s/image_file/download' % artifact_id, {}, None)] + + self.assertEqual(expect, self.api.calls) + self.assertEqual('Princess Bubblegum rocks!!!', body) + + def test_download_blob_with_checksum(self): + artifact_id = '5cc4bebc-db27-11e1-a1eb-080027cbe205' + params = {'blob_property': 'image_file', 'artifact_id': artifact_id} + params.update(type_fixture) + body = ''.join([b for b in self.controller.download_blob(**params)]) + + self.assertEqual('Princess Bubblegum rocks!!!', body) + + params['do_checksum'] = False + body = ''.join([b for b in self.controller.download_blob(**params)]) + + expect = [('GET', '/v3/artifacts/adventure_time/v1.0.2/' + '%s/image_file/download' % artifact_id, {}, None)] * 2 + + self.assertEqual(expect, self.api.calls) + self.assertEqual('Princess Bubblegum rocks!!!', body) + + def test_download_blob_with_wrong_checksum(self): + artifact_id = '66fb18d6-db27-11e1-a1eb-080027cbe205' + + params = {'blob_property': 'image_file', 'artifact_id': artifact_id} + params.update(type_fixture) + try: + ''.join([b for b in self.controller.download_blob(**params)]) + self.fail('data did not raise an error.') + except IOError as e: + self.assertEqual(errno.EPIPE, e.errno) + msg = 'was 5a3c872ee92e2c58efd0d47862eb9c85 expected Lich' + self.assertIn(msg, str(e)) + + params['do_checksum'] = False + body = ''.join([b for b in self.controller.download_blob(**params)]) + + expect = [('GET', '/v3/artifacts/adventure_time/v1.0.2/' + '%s/image_file/download' % artifact_id, {}, None)] * 2 + + self.assertEqual(expect, self.api.calls) + self.assertEqual('Princess Bubblegum rocks!!!', body) + + def test_data_upload_blob_with_position(self): + image_data = 'Adventure Time with Finn & Jake' + + artifact_id = '18f67e12-88e0-4b13-86fb-5adc36d884b6' + + params = {'blob_property': 'screenshots', 'artifact_id': artifact_id, + 'position': 1, 'data': image_data} + params.update(type_fixture) + + self.controller.upload_blob(**params) + expect_hdrs = {'Content-Type': 'application/octet-stream'} + expect = [('PUT', '/v3/artifacts/adventure_time/v1.0.2/' + '%s/screenshots/1' % artifact_id, expect_hdrs, image_data)] + + self.assertEqual(expect, self.api.calls) + + def test_delete_blob_with_position(self): + artifact_id = '18f67e12-88e0-4b13-86fb-5adc36d884b6' + + params = {'blob_property': 'screenshots', 'artifact_id': artifact_id, + 'position': 1} + params.update(type_fixture) + self.controller.delete_blob(**params) + expect = [('DELETE', '/v3/artifacts/adventure_time/v1.0.2/' + '%s/screenshots/1' % artifact_id, {}, None)] + + self.assertEqual(expect, self.api.calls) + + def test_download_blob_with_position(self): + artifact_id = '18f67e12-88e0-4b13-86fb-5adc36d884b6' + + params = {'blob_property': 'screenshots', 'artifact_id': artifact_id, + 'position': 1} + params.update(type_fixture) + body = ''.join([b for b in self.controller.download_blob(**params)]) + expect = [('GET', '/v3/artifacts/adventure_time/v1.0.2/' + '%s/screenshots/1/download' % artifact_id, {}, None)] + + self.assertEqual(expect, self.api.calls) + self.assertEqual('What time is it?', body) diff --git a/glanceclient/v3/artifacts.py b/glanceclient/v3/artifacts.py index 2440a36..971be98 100644 --- a/glanceclient/v3/artifacts.py +++ b/glanceclient/v3/artifacts.py @@ -12,6 +12,7 @@ # License for the specific language governing permissions and limitations # under the License. +from glanceclient.common import utils from glanceclient import exc from glanceclient.v3 import ArtifactType @@ -140,15 +141,68 @@ class Controller(object): def upload_blob(self, artifact_id, blob_property, data, position=None, type_name=None, type_version=None): - raise NotImplementedError() + """Upload blob data. + + :param artifact_id: ID of the artifact to download a blob + :param blob_property: blob property name + :param position: if blob_property is a list then the + position must be specified + """ + type_name, type_version = self._check_type_params(type_name, + type_version) + hdrs = {'Content-Type': 'application/octet-stream'} + + url = '/v3/artifacts/%s/v%s/%s/%s' % (type_name, type_version, + artifact_id, blob_property) + if position: + url += "/%s" % position + + self.http_client.put(url, headers=hdrs, data=data) def download_blob(self, artifact_id, blob_property, position=None, type_name=None, type_version=None, do_checksum=True): - raise NotImplementedError() + """Get blob data. + + :param artifact_id: ID of the artifact to download a blob + :param blob_property: blob property name + :param position: if blob_property is a list then the + position must be specified + :param do_checksum: Enable/disable checksum validation. + """ + type_name, type_version = self._check_type_params(type_name, + type_version) + url = '/v3/artifacts/%s/v%s/%s/%s' % (type_name, type_version, + artifact_id, blob_property) + if position: + url += '/%s' % position + + url += '/download' + + resp, body = self.http_client.get(url) + checksum = resp.headers.get('content-md5', None) + content_length = int(resp.headers.get('content-length', 0)) + + if checksum is not None and do_checksum: + body = utils.integrity_iter(body, checksum) + + return utils.IterableWithLength(body, content_length) def delete_blob(self, artifact_id, blob_property, position=None, type_name=None, type_version=None): - raise NotImplementedError() + """Delete blob and related data. + + :param artifact_id: ID of the artifact to delete a blob + :param blob_property: blob property name + :param position: if blob_property is a list then the + position must be specified + """ + type_name, type_version = self._check_type_params(type_name, + type_version) + url = '/v3/artifacts/%s/v%s/%s/%s' % (type_name, type_version, + artifact_id, blob_property) + if position: + url += '/%s' % position + self.http_client.delete(url) def add_property(self, artifact_id, dependency_id, position=None, type_name=None, type_version=None): |