summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJames Saryerwinnie <js@jamesls.com>2012-09-06 13:56:22 -0700
committerJames Saryerwinnie <js@jamesls.com>2012-09-06 13:56:22 -0700
commit67c88bea46756afc6b19fc8bd3caa307160d6958 (patch)
treedffcd519c10611c9a97db47946b695d9f60b14e5
parent5f8d0652c846d89bbb08762aef63e96a3d045ad7 (diff)
parentacf17b8f391dc4803487146517e80af246da8174 (diff)
downloadboto-67c88bea46756afc6b19fc8bd3caa307160d6958.tar.gz
Merge remote-tracking branch 'almost/glacier' into glacier
-rw-r--r--README.rst1
-rw-r--r--boto/glacier/layer1.py2
-rw-r--r--boto/glacier/layer2.py6
-rw-r--r--boto/glacier/vault.py18
-rw-r--r--boto/glacier/writer.py41
-rw-r--r--tests/integration/glacier/__init__.py22
-rw-r--r--tests/unit/glacier/test_layer2.py130
7 files changed, 192 insertions, 28 deletions
diff --git a/README.rst b/README.rst
index 2a16bb67..0ed8e079 100644
--- a/README.rst
+++ b/README.rst
@@ -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))
+