Path: blob/develop/tests/unit/customizations/test_s3uploader.py
1567 views
# Copyright 2014 Amazon.com, Inc. or its affiliates. All Rights Reserved.1#2# Licensed under the Apache License, Version 2.0 (the "License"). You3# may not use this file except in compliance with the License. A copy of4# the License is located at5#6# http://aws.amazon.com/apache2.0e7#8# or in the "license" file accompanying this file. This file is9# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF10# ANY KIND, either express or implied. See the License for the specific11# language governing permissions and limitations under the License.12import os13import random14import string15import tempfile16import hashlib17import shutil1819import botocore20import botocore.session21import botocore.exceptions22from botocore.stub import Stubber23from s3transfer import S3Transfer2425from awscli.compat import OrderedDict26from awscli.testutils import mock, unittest27from awscli.customizations.s3uploader import S3Uploader28from awscli.customizations.s3uploader import NoSuchBucketError293031class TestS3Uploader(unittest.TestCase):3233def setUp(self):34self._construct_uploader("us-east-1")3536def _construct_uploader(self, region):37self.s3client = botocore.session.get_session().create_client(38's3', region_name=region)39self.s3client_stub = Stubber(self.s3client)40self.transfer_manager_mock = mock.Mock(spec=S3Transfer)41self.transfer_manager_mock.upload = mock.Mock()42self.bucket_name = "bucketname"43self.prefix = None4445self.s3uploader = S3Uploader(46self.s3client, self.bucket_name, self.prefix, None, False,47self.transfer_manager_mock)4849@mock.patch('os.path.getsize', return_value=1)50@mock.patch("awscli.customizations.s3uploader.ProgressPercentage")51def test_upload_successful(self, progress_percentage_mock, get_size_patch):52file_name = "filename"53remote_path = "remotepath"54prefix = "SomePrefix"55remote_path_with_prefix = "{0}/{1}".format(prefix, remote_path)56s3uploader = S3Uploader(57self.s3client, self.bucket_name, prefix, None, False,58self.transfer_manager_mock)59expected_upload_url = "s3://{0}/{1}/{2}".format(60self.bucket_name, prefix, remote_path)6162# Setup mock to fake that file does not exist63s3uploader.file_exists = mock.Mock()64s3uploader.file_exists.return_value = False65# set the metadata used by the uploader when uploading66artifact_metadata = {"key": "val"}67s3uploader.artifact_metadata = artifact_metadata6869upload_url = s3uploader.upload(file_name, remote_path)70self.assertEqual(expected_upload_url, upload_url)7172expected_extra_args = {73# expected encryption args74"ServerSideEncryption": "AES256",75# expected metadata76"Metadata": artifact_metadata77}78self.transfer_manager_mock.upload.assert_called_once_with(79file_name, self.bucket_name, remote_path_with_prefix,80expected_extra_args, mock.ANY)81s3uploader.file_exists.assert_called_once_with(remote_path_with_prefix)8283@mock.patch('os.path.getsize', return_value=1)84@mock.patch("awscli.customizations.s3uploader.ProgressPercentage")85def test_upload_successful_odict(self, progress_percentage_mock, get_size_patch):86file_name = "filename"87remote_path = "remotepath"88prefix = "SomePrefix"89remote_path_with_prefix = "{0}/{1}".format(prefix, remote_path)90s3uploader = S3Uploader(91self.s3client, self.bucket_name, prefix, None, False,92self.transfer_manager_mock)93expected_upload_url = "s3://{0}/{1}/{2}".format(94self.bucket_name, prefix, remote_path)9596# Setup mock to fake that file does not exist97s3uploader.file_exists = mock.Mock()98s3uploader.file_exists.return_value = False99# set the metadata used by the uploader when uploading100artifact_metadata = OrderedDict({"key": "val"})101s3uploader.artifact_metadata = artifact_metadata102103upload_url = s3uploader.upload(file_name, remote_path)104self.assertEqual(expected_upload_url, upload_url)105106expected_extra_args = {107# expected encryption args108"ServerSideEncryption": "AES256",109# expected metadata110"Metadata": artifact_metadata111}112self.transfer_manager_mock.upload.assert_called_once_with(113file_name, self.bucket_name, remote_path_with_prefix,114expected_extra_args, mock.ANY)115s3uploader.file_exists.assert_called_once_with(remote_path_with_prefix)116117@mock.patch("awscli.customizations.s3uploader.ProgressPercentage")118def test_upload_idempotency(self, progress_percentage_mock):119file_name = "filename"120remote_path = "remotepath"121122# Setup mock to fake that file was already uploaded123self.s3uploader.file_exists = mock.Mock()124self.s3uploader.file_exists.return_value = True125126self.s3uploader.upload(file_name, remote_path)127128self.transfer_manager_mock.upload.assert_not_called()129self.s3uploader.file_exists.assert_called_once_with(remote_path)130131@mock.patch('os.path.getsize', return_value=1)132@mock.patch("awscli.customizations.s3uploader.ProgressPercentage")133def test_upload_force_upload(self, progress_percentage_mock, get_size_patch):134file_name = "filename"135remote_path = "remotepath"136expected_upload_url = "s3://{0}/{1}".format(self.bucket_name,137remote_path)138139# Set ForceUpload = True140self.s3uploader = S3Uploader(141self.s3client, self.bucket_name, self.prefix,142None, True, self.transfer_manager_mock)143144# Pretend file already exists145self.s3uploader.file_exists = mock.Mock()146self.s3uploader.file_exists.return_value = True147148# Because we forced an update, this should reupload even if file exists149upload_url = self.s3uploader.upload(file_name, remote_path)150self.assertEqual(expected_upload_url, upload_url)151152expected_encryption_args = {153"ServerSideEncryption": "AES256"154}155self.transfer_manager_mock.upload.assert_called_once_with(156file_name, self.bucket_name, remote_path,157expected_encryption_args, mock.ANY)158159# Since ForceUpload=True, we should NEVER do the file-exists check160self.s3uploader.file_exists.assert_not_called()161162@mock.patch('os.path.getsize', return_value=1)163@mock.patch("awscli.customizations.s3uploader.ProgressPercentage")164def test_upload_successful_custom_kms_key(self, progress_percentage_mock, get_size_patch):165file_name = "filename"166remote_path = "remotepath"167kms_key_id = "kms_id"168expected_upload_url = "s3://{0}/{1}".format(self.bucket_name,169remote_path)170# Set KMS Key Id171self.s3uploader = S3Uploader(172self.s3client, self.bucket_name, self.prefix,173kms_key_id, False, self.transfer_manager_mock)174175# Setup mock to fake that file does not exist176self.s3uploader.file_exists = mock.Mock()177self.s3uploader.file_exists.return_value = False178179upload_url = self.s3uploader.upload(file_name, remote_path)180self.assertEqual(expected_upload_url, upload_url)181182expected_encryption_args = {183"ServerSideEncryption": "aws:kms",184"SSEKMSKeyId": kms_key_id185}186self.transfer_manager_mock.upload.assert_called_once_with(187file_name, self.bucket_name, remote_path,188expected_encryption_args, mock.ANY)189self.s3uploader.file_exists.assert_called_once_with(remote_path)190191@mock.patch('os.path.getsize', return_value=1)192@mock.patch("awscli.customizations.s3uploader.ProgressPercentage")193def test_upload_successful_nobucket(self, progress_percentage_mock, get_size_patch):194file_name = "filename"195remote_path = "remotepath"196197# Setup mock to fake that file does not exist198self.s3uploader.file_exists = mock.Mock()199self.s3uploader.file_exists.return_value = False200201# Setup uploader to return a NOSuchBucket exception202exception = botocore.exceptions.ClientError(203{"Error": {"Code": "NoSuchBucket"}}, "OpName")204self.transfer_manager_mock.upload.side_effect = exception205206with self.assertRaises(NoSuchBucketError):207self.s3uploader.upload(file_name, remote_path)208209@mock.patch('os.path.getsize', return_value=1)210@mock.patch("awscli.customizations.s3uploader.ProgressPercentage")211def test_upload_successful_exceptions(self, progress_percentage_mock, get_size_patch):212file_name = "filename"213remote_path = "remotepath"214215# Setup mock to fake that file does not exist216self.s3uploader.file_exists = mock.Mock()217self.s3uploader.file_exists.return_value = False218219# Raise an unrecognized botocore error220exception = botocore.exceptions.ClientError(221{"Error": {"Code": "SomeError"}}, "OpName")222self.transfer_manager_mock.upload.side_effect = exception223224with self.assertRaises(botocore.exceptions.ClientError):225self.s3uploader.upload(file_name, remote_path)226227# Some other exception228self.transfer_manager_mock.upload.side_effect = FloatingPointError()229with self.assertRaises(FloatingPointError):230self.s3uploader.upload(file_name, remote_path)231232def test_upload_with_dedup(self):233234checksum = "some md5 checksum"235filename = "filename"236extension = "extn"237238self.s3uploader.file_checksum = mock.Mock()239self.s3uploader.file_checksum.return_value = checksum240241self.s3uploader.upload = mock.Mock()242243self.s3uploader.upload_with_dedup(filename, extension)244245remotepath = "{0}.{1}".format(checksum, extension)246self.s3uploader.upload.assert_called_once_with(filename, remotepath)247248def test_file_exists(self):249key = "some/path"250expected_params = {251"Bucket": self.bucket_name,252"Key": key253}254response = {255"AcceptRanges": "bytes",256"ContentType": "text/html",257"LastModified": "Thu, 16 Apr 2015 18:19:14 GMT",258"ContentLength": 77,259"VersionId": "null",260"ETag": "\"30a6ec7e1a9ad79c203d05a589c8b400\"",261"Metadata": {}262}263264# Let's pretend file exists265self.s3client_stub.add_response("head_object",266response,267expected_params)268269with self.s3client_stub:270self.assertTrue(self.s3uploader.file_exists(key))271272# Let's pretend file does not exist273self.s3client_stub.add_client_error(274'head_object', "ClientError", "some error")275with self.s3client_stub:276self.assertFalse(self.s3uploader.file_exists(key))277278# Let's pretend some other unknown exception happened279s3mock = mock.Mock()280uploader = S3Uploader(s3mock, self.bucket_name)281s3mock.head_object = mock.Mock()282s3mock.head_object.side_effect = RuntimeError()283284with self.assertRaises(RuntimeError):285uploader.file_exists(key)286287def test_file_checksum(self):288num_chars = 4096*5289data = ''.join(random.choice(string.ascii_uppercase)290for _ in range(num_chars)).encode('utf-8')291md5 = hashlib.md5()292md5.update(data)293expected_checksum = md5.hexdigest()294295tempdir = tempfile.mkdtemp()296try:297filename = os.path.join(tempdir, 'tempfile')298with open(filename, 'wb') as f:299f.write(data)300301actual_checksum = self.s3uploader.file_checksum(filename)302self.assertEqual(expected_checksum, actual_checksum)303finally:304shutil.rmtree(tempdir)305306@mock.patch("awscli.customizations.s3uploader.get_md5")307def test_file_checksum_fips_fallback(self, get_md5_mock):308num_chars = 4096*5309data = ''.join(random.choice(string.ascii_uppercase)310for _ in range(num_chars)).encode('utf-8')311checksum = hashlib.sha256(usedforsecurity=False)312checksum.update(data)313expected_checksum = checksum.hexdigest()314315tempdir = tempfile.mkdtemp()316get_md5_mock.side_effect = botocore.exceptions.MD5UnavailableError()317try:318filename = os.path.join(tempdir, 'tempfile')319with open(filename, 'wb') as f:320f.write(data)321322actual_checksum = self.s3uploader.file_checksum(filename)323self.assertEqual(expected_checksum, actual_checksum)324finally:325shutil.rmtree(tempdir)326327def test_make_url(self):328path = "Hello/how/are/you"329expected = "s3://{0}/{1}".format(self.bucket_name, path)330self.assertEqual(expected, self.s3uploader.make_url(path))331332def test_to_path_style_s3_url_us_east_1(self):333key = "path/to/file"334version = "someversion"335region = "us-east-1"336self._construct_uploader(region)337338s3uploader = S3Uploader(self.s3client, self.bucket_name)339result = s3uploader.to_path_style_s3_url(key, version)340self.assertEqual(341result,342"https://s3.amazonaws.com/{0}/{1}?versionId={2}".format(343self.bucket_name, key, version))344345# Without versionId, that query parameter should be omitted346s3uploader = S3Uploader(self.s3client, self.bucket_name, region)347result = s3uploader.to_path_style_s3_url(key)348self.assertEqual(349result,350"https://s3.amazonaws.com/{0}/{1}".format(351self.bucket_name, key, version))352353def test_to_path_style_s3_url_other_regions(self):354key = "path/to/file"355version = "someversion"356region = "us-west-2"357self._construct_uploader(region)358359s3uploader = S3Uploader(self.s3client, self.bucket_name, region)360result = s3uploader.to_path_style_s3_url(key, version)361self.assertEqual(362result,363"https://s3.{0}.amazonaws.com/{1}/{2}?versionId={3}".format(364region, self.bucket_name, key, version))365366# Without versionId, that query parameter should be omitted367s3uploader = S3Uploader(self.s3client, self.bucket_name, region)368result = s3uploader.to_path_style_s3_url(key)369self.assertEqual(370result,371"https://s3.{0}.amazonaws.com/{1}/{2}".format(372region, self.bucket_name, key))373374375def test_to_path_style_s3_url_china_regions(self):376key = "path/to/file"377version = "someversion"378region = "cn-northwest-1"379self._construct_uploader(region)380381s3uploader = S3Uploader(self.s3client, self.bucket_name, region)382result = s3uploader.to_path_style_s3_url(key, version)383self.assertEqual(384result,385"https://s3.{0}.amazonaws.com.cn/{1}/{2}?versionId={3}".format(386region, self.bucket_name, key, version))387388# Without versionId, that query parameter should be omitted389s3uploader = S3Uploader(self.s3client, self.bucket_name, region)390result = s3uploader.to_path_style_s3_url(key)391self.assertEqual(392result,393"https://s3.{0}.amazonaws.com.cn/{1}/{2}".format(394region, self.bucket_name, key))395396def test_artifact_metadata_invalid_type(self):397prefix = "SomePrefix"398s3uploader = S3Uploader(399self.s3client, self.bucket_name, prefix, None, False,400self.transfer_manager_mock)401invalid_metadata = ["key", "val"]402with self.assertRaises(TypeError):403s3uploader.artifact_metadata = invalid_metadata404405406