diff options
author | James Saryerwinnie <js@jamesls.com> | 2012-09-06 13:56:22 -0700 |
---|---|---|
committer | James Saryerwinnie <js@jamesls.com> | 2012-09-06 13:56:22 -0700 |
commit | 67c88bea46756afc6b19fc8bd3caa307160d6958 (patch) | |
tree | dffcd519c10611c9a97db47946b695d9f60b14e5 | |
parent | 5f8d0652c846d89bbb08762aef63e96a3d045ad7 (diff) | |
parent | acf17b8f391dc4803487146517e80af246da8174 (diff) | |
download | boto-67c88bea46756afc6b19fc8bd3caa307160d6958.tar.gz |
Merge remote-tracking branch 'almost/glacier' into glacier
-rw-r--r-- | README.rst | 1 | ||||
-rw-r--r-- | boto/glacier/layer1.py | 2 | ||||
-rw-r--r-- | boto/glacier/layer2.py | 6 | ||||
-rw-r--r-- | boto/glacier/vault.py | 18 | ||||
-rw-r--r-- | boto/glacier/writer.py | 41 | ||||
-rw-r--r-- | tests/integration/glacier/__init__.py | 22 | ||||
-rw-r--r-- | tests/unit/glacier/test_layer2.py | 130 |
7 files changed, 192 insertions, 28 deletions
@@ -37,6 +37,7 @@ At the moment, boto supports: * Amazon SimpleWorkflow * CloudSearch * Marketplace Web Services +* Glacier The goal of boto is to support the full breadth and depth of Amazon Web Services. In addition, boto provides support for other public diff --git a/boto/glacier/layer1.py b/boto/glacier/layer1.py index dca5f06c..ab517ba6 100644 --- a/boto/glacier/layer1.py +++ b/boto/glacier/layer1.py @@ -39,7 +39,7 @@ class Layer1(AWSAuthConnection): def __init__(self, aws_access_key_id=None, aws_secret_access_key=None, account_id='-', is_secure=True, port=None, proxy=None, proxy_port=None, - proxy_user=None, proxy_pass=None, debug=2, + proxy_user=None, proxy_pass=None, debug=0, https_connection_factory=None, path='/', provider='aws', security_token=None, suppress_consec_slashes=True, diff --git a/boto/glacier/layer2.py b/boto/glacier/layer2.py index 84e3d56a..e519ca89 100644 --- a/boto/glacier/layer2.py +++ b/boto/glacier/layer2.py @@ -31,7 +31,11 @@ class Layer2(object): """ def __init__(self, *args, **kwargs): - self.layer1 = Layer1(*args, **kwargs) + # Accept a passed in layer1, mainly to allow easier testing + if "layer1" in kwargs: + self.layer1 = kwargs["layer1"] + else: + self.layer1 = Layer1(*args, **kwargs) def create_vault(self, name): """Creates a vault. diff --git a/boto/glacier/vault.py b/boto/glacier/vault.py index 15ede747..a10ac241 100644 --- a/boto/glacier/vault.py +++ b/boto/glacier/vault.py @@ -53,6 +53,12 @@ class Vault(object): def __repr__(self): return 'Vault("%s")' % self.arn + def delete(self): + """ + Delete's this vault. WARNING! + """ + self.layer1.delete_vault(self.name) + def upload_archive(self, filename): """ Adds an archive to a vault. For archives greater than 100MB the @@ -67,9 +73,9 @@ class Vault(object): megabyte = 1024 * 1024 if os.path.getsize(filename) > 100 * megabyte: return self.create_archive_from_file(filename) - return self.upload_archive_single_operation(filename) + return self._upload_archive_single_operation(filename) - def upload_archive_single_operation(self, filename): + def _upload_archive_single_operation(self, filename): """ Adds an archive to a vault in a single operation. It's recommended for archives less than 100MB @@ -135,7 +141,7 @@ class Vault(object): writer.close() return writer.get_archive_id() - def retrieve_archive(self, archive_name, sns_topic=None, + def retrieve_archive(self, archive_id, sns_topic=None, description=None): """ Initiate a archive retrieval job to download the data from an @@ -143,8 +149,8 @@ class Vault(object): Amazon (via SNS) before you can actually download the data, this takes around 4 hours. - :type archive_name: str - :param archive_name: The name of the archive + :type archive_id: str + :param archive_id: The id of the archive :type description: str :param description: An optional description for the job. @@ -158,7 +164,7 @@ class Vault(object): :return: A Job object representing the retrieval job. """ job_data = {'Type': 'archive-retrieval', - 'ArchiveId': archive_name} + 'ArchiveId': archive_id} if sns_topic is not None: job_data['SNSTopic'] = sns_topic if description is not None: diff --git a/boto/glacier/writer.py b/boto/glacier/writer.py index 2c046414..a62ef89c 100644 --- a/boto/glacier/writer.py +++ b/boto/glacier/writer.py @@ -76,61 +76,62 @@ class Writer(object): self.vault = vault self.upload_id = upload_id self.part_size = part_size - self.buffer_size = 0 - self.uploaded_size = 0 - self.buffer = [] - self.vault = vault - self.tree_hashes = [] + + self._buffer_size = 0 + self._uploaded_size = 0 + self._buffer = [] + self._tree_hashes = [] + self.archive_location = None self.closed = False def send_part(self): - buf = "".join(self.buffer) + buf = "".join(self._buffer) # Put back any data remaining over the part size into the # buffer if len(buf) > self.part_size: - self.buffer = [buf[self.part_size:]] - self.buffer_size = len(self.buffer[0]) + self._buffer = [buf[self.part_size:]] + self._buffer_size = len(self._buffer[0]) else: - self.buffer = [] - self.buffer_size = 0 + self._buffer = [] + self._buffer_size = 0 # The part we will send part = buf[:self.part_size] # Create a request and sign it part_tree_hash = tree_hash(chunk_hashes(part)) - self.tree_hashes.append(part_tree_hash) + self._tree_hashes.append(part_tree_hash) hex_tree_hash = bytes_to_hex(part_tree_hash) linear_hash = hashlib.sha256(part).hexdigest() - content_range = (self.uploaded_size, - (self.uploaded_size + len(part)) - 1) + content_range = (self._uploaded_size, + (self._uploaded_size + len(part)) - 1) response = self.vault.layer1.upload_part(self.vault.name, self.upload_id, linear_hash, hex_tree_hash, content_range, part) - self.uploaded_size += len(part) + self._uploaded_size += len(part) def write(self, str): assert not self.closed, "Tried to write to a Writer that is already closed!" if str == "": return - self.buffer.append(str) - self.buffer_size += len(str) - while self.buffer_size > self.part_size: + self._buffer.append(str) + self._buffer_size += len(str) + while self._buffer_size > self.part_size: self.send_part() def close(self): if self.closed: return - if self.buffer_size > 0: + if self._buffer_size > 0: self.send_part() # Complete the multiplart glacier upload - hex_tree_hash = bytes_to_hex(tree_hash(self.tree_hashes)) + hex_tree_hash = bytes_to_hex(tree_hash(self._tree_hashes)) response = self.vault.layer1.complete_multipart_upload(self.vault.name, self.upload_id, hex_tree_hash, - self.uploaded_size) + self._uploaded_size) self.archive_id = response['ArchiveId'] self.closed = True diff --git a/tests/integration/glacier/__init__.py b/tests/integration/glacier/__init__.py index e69de29b..5326afc1 100644 --- a/tests/integration/glacier/__init__.py +++ b/tests/integration/glacier/__init__.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2012 Thomas Parslow http://almostobsolete.net/ +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, dis- +# tribute, sublicense, and/or sell copies of the Software, and to permit +# persons to whom the Software is furnished to do so, subject to the fol- +# lowing conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL- +# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. +# diff --git a/tests/unit/glacier/test_layer2.py b/tests/unit/glacier/test_layer2.py new file mode 100644 index 00000000..f6c9098a --- /dev/null +++ b/tests/unit/glacier/test_layer2.py @@ -0,0 +1,130 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2012 Thomas Parslow http://almostobsolete.net/ +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, dis- +# tribute, sublicense, and/or sell copies of the Software, and to permit +# persons to whom the Software is furnished to do so, subject to the fol- +# lowing conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL- +# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. +# + +try: + import unittest2 as unittest +except ImportError: + import unittest +import httplib + +from mock import Mock + +from boto.glacier.layer1 import Layer1 +from boto.glacier.layer2 import Layer2 +from boto.glacier.vault import Vault +from boto.glacier.vault import Job + +# Some fixture data from the Glacier docs +FIXTURE_VAULT = { + "CreationDate" : "2012-02-20T17:01:45.198Z", + "LastInventoryDate" : "2012-03-20T17:03:43.221Z", + "NumberOfArchives" : 192, + "SizeInBytes" : 78088912, + "VaultARN" : "arn:aws:glacier:us-east-1:012345678901:vaults/examplevault", + "VaultName" : "examplevault" +} + +FIXTURE_ARCHIVE_JOB = { + "Action": "ArchiveRetrieval", + "ArchiveId": "NkbByEejwEggmBz2fTHgJrg0XBoDfjP4q6iu87-TjhqG6eGoOY9Z8i1_AUyUsuhPAdTqLHy8pTl5nfCFJmDl2yEZONi5L26Omw12vcs01MNGntHEQL8MBfGlqrEXAMPLEArchiveId", + "ArchiveSizeInBytes": 16777216, + "Completed": False, + "CreationDate": "2012-05-15T17:21:39.339Z", + "CompletionDate": "2012-05-15T17:21:43.561Z", + "InventorySizeInBytes": None, + "JobDescription": "My ArchiveRetrieval Job", + "JobId": "HkF9p6o7yjhFx-K3CGl6fuSm6VzW9T7esGQfco8nUXVYwS0jlb5gq1JZ55yHgt5vP54ZShjoQzQVVh7vEXAMPLEjobID", + "SHA256TreeHash": "beb0fe31a1c7ca8c6c04d574ea906e3f97b31fdca7571defb5b44dca89b5af60", + "SNSTopic": "arn:aws:sns:us-east-1:012345678901:mytopic", + "StatusCode": "InProgress", + "StatusMessage": "Operation in progress.", + "VaultARN": "arn:aws:glacier:us-east-1:012345678901:vaults/examplevault" +} + +class GlacierLayer2Base(unittest.TestCase): + def setUp(self): + self.mock_layer1 = Mock(spec=Layer1) + +class TestGlacierLayer2Connection(GlacierLayer2Base): + def setUp(self): + GlacierLayer2Base.setUp(self) + self.layer2 = Layer2(layer1=self.mock_layer1) + + def test_create_vault(self): + self.mock_layer1.describe_vault.return_value = FIXTURE_VAULT + self.layer2.create_vault("My Vault") + self.mock_layer1.create_vault.assert_called_with("My Vault") + + def test_get_vault(self): + self.mock_layer1.describe_vault.return_value = FIXTURE_VAULT + vault = self.layer2.get_vault("examplevault") + assert vault.layer1 == self.mock_layer1 + assert vault.name == "examplevault" + assert vault.size == 78088912 + assert vault.number_of_archives == 192 + + def list_vaults(self): + self.mock_layer1.list_vaults.return_value = [FIXTURE_VAULT] + vaults = self.layer2.list_vaults() + assert vaults[0].name == "examplevault" + +class TestVault(GlacierLayer2Base): + def setUp(self): + GlacierLayer2Base.setUp(self) + self.vault = Vault(self.mock_layer1, FIXTURE_VAULT) + + # TODO: Tests for the other methods of uploading + + def test_create_archive_writer(self): + self.mock_layer1.initiate_multipart_upload.return_value = {"UploadId": "UPLOADID"} + writer = self.vault.create_archive_writer(description="stuff") + self.mock_layer1.initiate_multipart_upload.assert_called_with("examplevault", self.vault.DefaultPartSize, "stuff") + assert writer.vault == self.vault + assert writer.upload_id == "UPLOADID" + + def test_delete_vault(self): + self.vault.delete_archive("archive") + self.mock_layer1.delete_archive.assert_called_with("examplevault","archive") + + def test_get_job(self): + self.mock_layer1.describe_job.return_value = FIXTURE_ARCHIVE_JOB + job = self.vault.get_job("NkbByEejwEggmBz2fTHgJrg0XBoDfjP4q6iu87-TjhqG6eGoOY9Z8i1_AUyUsuhPAdTqLHy8pTl5nfCFJmDl2yEZONi5L26Omw12vcs01MNGntHEQL8MBfGlqrEXAMPLEArchiveId") + assert job.action == "ArchiveRetrieval" + + def test_list_jobs(self): + self.mock_layer1.list_jobs.return_value = {"JobList": [FIXTURE_ARCHIVE_JOB]} + jobs = self.vault.list_jobs(False, "InProgress") + self.mock_layer1.list_jobs.assert_called_with("examplevault", False, "InProgress") + assert jobs[0].archive_id == "NkbByEejwEggmBz2fTHgJrg0XBoDfjP4q6iu87-TjhqG6eGoOY9Z8i1_AUyUsuhPAdTqLHy8pTl5nfCFJmDl2yEZONi5L26Omw12vcs01MNGntHEQL8MBfGlqrEXAMPLEArchiveId" + +class TestJob(GlacierLayer2Base): + def setUp(self): + GlacierLayer2Base.setUp(self) + self.vault = Vault(self.mock_layer1, FIXTURE_VAULT) + self.job = Job(self.vault, FIXTURE_ARCHIVE_JOB) + + def test_get_job_output(self): + self.mock_layer1.get_job_output.return_value = "TEST_OUTPUT" + self.job.get_output((0,100)) + self.mock_layer1.get_job_output.assert_called_with("examplevault", "HkF9p6o7yjhFx-K3CGl6fuSm6VzW9T7esGQfco8nUXVYwS0jlb5gq1JZ55yHgt5vP54ZShjoQzQVVh7vEXAMPLEjobID", (0,100)) + |