Index: tools/telemetry/third_party/gsutil/third_party/boto/boto/glacier/vault.py |
diff --git a/tools/telemetry/third_party/gsutil/third_party/boto/boto/glacier/vault.py b/tools/telemetry/third_party/gsutil/third_party/boto/boto/glacier/vault.py |
deleted file mode 100644 |
index 45d276cadb56347130133e509f98c00359e6ce3a..0000000000000000000000000000000000000000 |
--- a/tools/telemetry/third_party/gsutil/third_party/boto/boto/glacier/vault.py |
+++ /dev/null |
@@ -1,450 +0,0 @@ |
-# -*- coding: utf-8 -*- |
-# Copyright (c) 2012 Thomas Parslow http://almostobsolete.net/ |
-# Copyright (c) 2012 Robie Basak <robie@justgohome.co.uk> |
-# |
-# 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. |
-# |
-import codecs |
-from boto.glacier.exceptions import UploadArchiveError |
-from boto.glacier.job import Job |
-from boto.glacier.writer import compute_hashes_from_fileobj, \ |
- resume_file_upload, Writer |
-from boto.glacier.concurrent import ConcurrentUploader |
-from boto.glacier.utils import minimum_part_size, DEFAULT_PART_SIZE |
-import os.path |
- |
- |
-_MEGABYTE = 1024 * 1024 |
-_GIGABYTE = 1024 * _MEGABYTE |
- |
-MAXIMUM_ARCHIVE_SIZE = 10000 * 4 * _GIGABYTE |
-MAXIMUM_NUMBER_OF_PARTS = 10000 |
- |
- |
-class Vault(object): |
- |
- DefaultPartSize = DEFAULT_PART_SIZE |
- SingleOperationThreshold = 100 * _MEGABYTE |
- |
- ResponseDataElements = (('VaultName', 'name', None), |
- ('VaultARN', 'arn', None), |
- ('CreationDate', 'creation_date', None), |
- ('LastInventoryDate', 'last_inventory_date', None), |
- ('SizeInBytes', 'size', 0), |
- ('NumberOfArchives', 'number_of_archives', 0)) |
- |
- def __init__(self, layer1, response_data=None): |
- self.layer1 = layer1 |
- if response_data: |
- for response_name, attr_name, default in self.ResponseDataElements: |
- value = response_data[response_name] |
- setattr(self, attr_name, value) |
- else: |
- for response_name, attr_name, default in self.ResponseDataElements: |
- setattr(self, attr_name, default) |
- |
- 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, description=None): |
- """ |
- Adds an archive to a vault. For archives greater than 100MB the |
- multipart upload will be used. |
- |
- :type file: str |
- :param file: A filename to upload |
- |
- :type description: str |
- :param description: An optional description for the archive. |
- |
- :rtype: str |
- :return: The archive id of the newly created archive |
- """ |
- if os.path.getsize(filename) > self.SingleOperationThreshold: |
- return self.create_archive_from_file(filename, description=description) |
- return self._upload_archive_single_operation(filename, description) |
- |
- def _upload_archive_single_operation(self, filename, description): |
- """ |
- Adds an archive to a vault in a single operation. It's recommended for |
- archives less than 100MB |
- |
- :type file: str |
- :param file: A filename to upload |
- |
- :type description: str |
- :param description: A description for the archive. |
- |
- :rtype: str |
- :return: The archive id of the newly created archive |
- """ |
- with open(filename, 'rb') as fileobj: |
- linear_hash, tree_hash = compute_hashes_from_fileobj(fileobj) |
- fileobj.seek(0) |
- response = self.layer1.upload_archive(self.name, fileobj, |
- linear_hash, tree_hash, |
- description) |
- return response['ArchiveId'] |
- |
- def create_archive_writer(self, part_size=DefaultPartSize, |
- description=None): |
- """ |
- Create a new archive and begin a multi-part upload to it. |
- Returns a file-like object to which the data for the archive |
- can be written. Once all the data is written the file-like |
- object should be closed, you can then call the get_archive_id |
- method on it to get the ID of the created archive. |
- |
- :type part_size: int |
- :param part_size: The part size for the multipart upload. |
- |
- :type description: str |
- :param description: An optional description for the archive. |
- |
- :rtype: :class:`boto.glacier.writer.Writer` |
- :return: A Writer object that to which the archive data |
- should be written. |
- """ |
- response = self.layer1.initiate_multipart_upload(self.name, |
- part_size, |
- description) |
- return Writer(self, response['UploadId'], part_size=part_size) |
- |
- def create_archive_from_file(self, filename=None, file_obj=None, |
- description=None, upload_id_callback=None): |
- """ |
- Create a new archive and upload the data from the given file |
- or file-like object. |
- |
- :type filename: str |
- :param filename: A filename to upload |
- |
- :type file_obj: file |
- :param file_obj: A file-like object to upload |
- |
- :type description: str |
- :param description: An optional description for the archive. |
- |
- :type upload_id_callback: function |
- :param upload_id_callback: if set, call with the upload_id as the |
- only parameter when it becomes known, to enable future calls |
- to resume_archive_from_file in case resume is needed. |
- |
- :rtype: str |
- :return: The archive id of the newly created archive |
- """ |
- part_size = self.DefaultPartSize |
- if not file_obj: |
- file_size = os.path.getsize(filename) |
- try: |
- part_size = minimum_part_size(file_size, part_size) |
- except ValueError: |
- raise UploadArchiveError("File size of %s bytes exceeds " |
- "40,000 GB archive limit of Glacier.") |
- file_obj = open(filename, "rb") |
- writer = self.create_archive_writer( |
- description=description, |
- part_size=part_size) |
- if upload_id_callback: |
- upload_id_callback(writer.upload_id) |
- while True: |
- data = file_obj.read(part_size) |
- if not data: |
- break |
- writer.write(data) |
- writer.close() |
- return writer.get_archive_id() |
- |
- @staticmethod |
- def _range_string_to_part_index(range_string, part_size): |
- start, inside_end = [int(value) for value in range_string.split('-')] |
- end = inside_end + 1 |
- length = end - start |
- if length == part_size + 1: |
- # Off-by-one bug in Amazon's Glacier implementation, |
- # see: https://forums.aws.amazon.com/thread.jspa?threadID=106866 |
- # Workaround: since part_size is too big by one byte, adjust it |
- end -= 1 |
- inside_end -= 1 |
- length -= 1 |
- assert not (start % part_size), ( |
- "upload part start byte is not on a part boundary") |
- assert (length <= part_size), "upload part is bigger than part size" |
- return start // part_size |
- |
- def resume_archive_from_file(self, upload_id, filename=None, |
- file_obj=None): |
- """Resume upload of a file already part-uploaded to Glacier. |
- |
- The resumption of an upload where the part-uploaded section is empty |
- is a valid degenerate case that this function can handle. |
- |
- One and only one of filename or file_obj must be specified. |
- |
- :type upload_id: str |
- :param upload_id: existing Glacier upload id of upload being resumed. |
- |
- :type filename: str |
- :param filename: file to open for resume |
- |
- :type fobj: file |
- :param fobj: file-like object containing local data to resume. This |
- must read from the start of the entire upload, not just from the |
- point being resumed. Use fobj.seek(0) to achieve this if necessary. |
- |
- :rtype: str |
- :return: The archive id of the newly created archive |
- |
- """ |
- part_list_response = self.list_all_parts(upload_id) |
- part_size = part_list_response['PartSizeInBytes'] |
- |
- part_hash_map = {} |
- for part_desc in part_list_response['Parts']: |
- part_index = self._range_string_to_part_index( |
- part_desc['RangeInBytes'], part_size) |
- part_tree_hash = codecs.decode(part_desc['SHA256TreeHash'], 'hex_codec') |
- part_hash_map[part_index] = part_tree_hash |
- |
- if not file_obj: |
- file_obj = open(filename, "rb") |
- |
- return resume_file_upload( |
- self, upload_id, part_size, file_obj, part_hash_map) |
- |
- def concurrent_create_archive_from_file(self, filename, description, |
- **kwargs): |
- """ |
- Create a new archive from a file and upload the given |
- file. |
- |
- This is a convenience method around the |
- :class:`boto.glacier.concurrent.ConcurrentUploader` |
- class. This method will perform a multipart upload |
- and upload the parts of the file concurrently. |
- |
- :type filename: str |
- :param filename: A filename to upload |
- |
- :param kwargs: Additional kwargs to pass through to |
- :py:class:`boto.glacier.concurrent.ConcurrentUploader`. |
- You can pass any argument besides the ``api`` and |
- ``vault_name`` param (these arguments are already |
- passed to the ``ConcurrentUploader`` for you). |
- |
- :raises: `boto.glacier.exception.UploadArchiveError` is an error |
- occurs during the upload process. |
- |
- :rtype: str |
- :return: The archive id of the newly created archive |
- |
- """ |
- uploader = ConcurrentUploader(self.layer1, self.name, **kwargs) |
- archive_id = uploader.upload(filename, description) |
- return archive_id |
- |
- def retrieve_archive(self, archive_id, sns_topic=None, |
- description=None): |
- """ |
- Initiate a archive retrieval job to download the data from an |
- archive. You will need to wait for the notification from |
- Amazon (via SNS) before you can actually download the data, |
- this takes around 4 hours. |
- |
- :type archive_id: str |
- :param archive_id: The id of the archive |
- |
- :type description: str |
- :param description: An optional description for the job. |
- |
- :type sns_topic: str |
- :param sns_topic: The Amazon SNS topic ARN where Amazon Glacier |
- sends notification when the job is completed and the output |
- is ready for you to download. |
- |
- :rtype: :class:`boto.glacier.job.Job` |
- :return: A Job object representing the retrieval job. |
- """ |
- job_data = {'Type': 'archive-retrieval', |
- 'ArchiveId': archive_id} |
- if sns_topic is not None: |
- job_data['SNSTopic'] = sns_topic |
- if description is not None: |
- job_data['Description'] = description |
- |
- response = self.layer1.initiate_job(self.name, job_data) |
- return self.get_job(response['JobId']) |
- |
- def retrieve_inventory(self, sns_topic=None, |
- description=None, byte_range=None, |
- start_date=None, end_date=None, |
- limit=None): |
- """ |
- Initiate a inventory retrieval job to list the items in the |
- vault. You will need to wait for the notification from |
- Amazon (via SNS) before you can actually download the data, |
- this takes around 4 hours. |
- |
- :type description: str |
- :param description: An optional description for the job. |
- |
- :type sns_topic: str |
- :param sns_topic: The Amazon SNS topic ARN where Amazon Glacier |
- sends notification when the job is completed and the output |
- is ready for you to download. |
- |
- :type byte_range: str |
- :param byte_range: Range of bytes to retrieve. |
- |
- :type start_date: DateTime |
- :param start_date: Beginning of the date range to query. |
- |
- :type end_date: DateTime |
- :param end_date: End of the date range to query. |
- |
- :type limit: int |
- :param limit: Limits the number of results returned. |
- |
- :rtype: str |
- :return: The ID of the job |
- """ |
- job_data = {'Type': 'inventory-retrieval'} |
- if sns_topic is not None: |
- job_data['SNSTopic'] = sns_topic |
- if description is not None: |
- job_data['Description'] = description |
- if byte_range is not None: |
- job_data['RetrievalByteRange'] = byte_range |
- if start_date is not None or end_date is not None or limit is not None: |
- rparams = {} |
- |
- if start_date is not None: |
- rparams['StartDate'] = start_date.strftime('%Y-%m-%dT%H:%M:%S%Z') |
- if end_date is not None: |
- rparams['EndDate'] = end_date.strftime('%Y-%m-%dT%H:%M:%S%Z') |
- if limit is not None: |
- rparams['Limit'] = limit |
- |
- job_data['InventoryRetrievalParameters'] = rparams |
- |
- response = self.layer1.initiate_job(self.name, job_data) |
- return response['JobId'] |
- |
- def retrieve_inventory_job(self, **kwargs): |
- """ |
- Identical to ``retrieve_inventory``, but returns a ``Job`` instance |
- instead of just the job ID. |
- |
- :type description: str |
- :param description: An optional description for the job. |
- |
- :type sns_topic: str |
- :param sns_topic: The Amazon SNS topic ARN where Amazon Glacier |
- sends notification when the job is completed and the output |
- is ready for you to download. |
- |
- :type byte_range: str |
- :param byte_range: Range of bytes to retrieve. |
- |
- :type start_date: DateTime |
- :param start_date: Beginning of the date range to query. |
- |
- :type end_date: DateTime |
- :param end_date: End of the date range to query. |
- |
- :type limit: int |
- :param limit: Limits the number of results returned. |
- |
- :rtype: :class:`boto.glacier.job.Job` |
- :return: A Job object representing the retrieval job. |
- """ |
- job_id = self.retrieve_inventory(**kwargs) |
- return self.get_job(job_id) |
- |
- def delete_archive(self, archive_id): |
- """ |
- This operation deletes an archive from the vault. |
- |
- :type archive_id: str |
- :param archive_id: The ID for the archive to be deleted. |
- """ |
- return self.layer1.delete_archive(self.name, archive_id) |
- |
- def get_job(self, job_id): |
- """ |
- Get an object representing a job in progress. |
- |
- :type job_id: str |
- :param job_id: The ID of the job |
- |
- :rtype: :class:`boto.glacier.job.Job` |
- :return: A Job object representing the job. |
- """ |
- response_data = self.layer1.describe_job(self.name, job_id) |
- return Job(self, response_data) |
- |
- def list_jobs(self, completed=None, status_code=None): |
- """ |
- Return a list of Job objects related to this vault. |
- |
- :type completed: boolean |
- :param completed: Specifies the state of the jobs to return. |
- If a value of True is passed, only completed jobs will |
- be returned. If a value of False is passed, only |
- uncompleted jobs will be returned. If no value is |
- passed, all jobs will be returned. |
- |
- :type status_code: string |
- :param status_code: Specifies the type of job status to return. |
- Valid values are: InProgress|Succeeded|Failed. If not |
- specified, jobs with all status codes are returned. |
- |
- :rtype: list of :class:`boto.glacier.job.Job` |
- :return: A list of Job objects related to this vault. |
- """ |
- response_data = self.layer1.list_jobs(self.name, completed, |
- status_code) |
- return [Job(self, jd) for jd in response_data['JobList']] |
- |
- def list_all_parts(self, upload_id): |
- """Automatically make and combine multiple calls to list_parts. |
- |
- Call list_parts as necessary, combining the results in case multiple |
- calls were required to get data on all available parts. |
- |
- """ |
- result = self.layer1.list_parts(self.name, upload_id) |
- marker = result['Marker'] |
- while marker: |
- additional_result = self.layer1.list_parts( |
- self.name, upload_id, marker=marker) |
- result['Parts'].extend(additional_result['Parts']) |
- marker = additional_result['Marker'] |
- # The marker makes no sense in an unpaginated result, and clearing it |
- # makes testing easier. This also has the nice property that the result |
- # is a normal (but expanded) response. |
- result['Marker'] = None |
- return result |