Index: tools/telemetry/third_party/gsutil/gslib/tests/testcase/integration_testcase.py |
diff --git a/tools/telemetry/third_party/gsutil/gslib/tests/testcase/integration_testcase.py b/tools/telemetry/third_party/gsutil/gslib/tests/testcase/integration_testcase.py |
deleted file mode 100644 |
index 0979c453a4e17565b1b4f6e36cefe6f29ed9e95b..0000000000000000000000000000000000000000 |
--- a/tools/telemetry/third_party/gsutil/gslib/tests/testcase/integration_testcase.py |
+++ /dev/null |
@@ -1,472 +0,0 @@ |
-# -*- coding: utf-8 -*- |
-# Copyright 2013 Google Inc. All Rights Reserved. |
-# |
-# Licensed under the Apache License, Version 2.0 (the "License"); |
-# you may not use this file except in compliance with the License. |
-# You may obtain a copy of the License at |
-# |
-# http://www.apache.org/licenses/LICENSE-2.0 |
-# |
-# Unless required by applicable law or agreed to in writing, software |
-# distributed under the License is distributed on an "AS IS" BASIS, |
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
-# See the License for the specific language governing permissions and |
-# limitations under the License. |
-"""Contains gsutil base integration test case class.""" |
- |
-from __future__ import absolute_import |
- |
-from contextlib import contextmanager |
-import cStringIO |
-import locale |
-import logging |
-import os |
-import subprocess |
-import sys |
-import tempfile |
- |
-import boto |
-from boto.exception import StorageResponseError |
-from boto.s3.deletemarker import DeleteMarker |
-from boto.storage_uri import BucketStorageUri |
- |
-import gslib |
-from gslib.gcs_json_api import GcsJsonApi |
-from gslib.hashing_helper import Base64ToHexHash |
-from gslib.project_id import GOOG_PROJ_ID_HDR |
-from gslib.project_id import PopulateProjectId |
-from gslib.tests.testcase import base |
-import gslib.tests.util as util |
-from gslib.tests.util import ObjectToURI as suri |
-from gslib.tests.util import RUN_S3_TESTS |
-from gslib.tests.util import SetBotoConfigFileForTest |
-from gslib.tests.util import SetBotoConfigForTest |
-from gslib.tests.util import SetEnvironmentForTest |
-from gslib.tests.util import unittest |
-import gslib.third_party.storage_apitools.storage_v1_messages as apitools_messages |
-from gslib.util import IS_WINDOWS |
-from gslib.util import Retry |
-from gslib.util import UTF8 |
- |
- |
-LOGGER = logging.getLogger('integration-test') |
- |
-# Contents of boto config file that will tell gsutil not to override the real |
-# error message with a warning about anonymous access if no credentials are |
-# provided in the config file. Also, because we retry 401s, reduce the number |
-# of retries so we don't go through a long exponential backoff in tests. |
-BOTO_CONFIG_CONTENTS_IGNORE_ANON_WARNING = """ |
-[Boto] |
-num_retries = 2 |
-[Tests] |
-bypass_anonymous_access_warning = True |
-""" |
- |
- |
-def SkipForGS(reason): |
- if not RUN_S3_TESTS: |
- return unittest.skip(reason) |
- else: |
- return lambda func: func |
- |
- |
-def SkipForS3(reason): |
- if RUN_S3_TESTS: |
- return unittest.skip(reason) |
- else: |
- return lambda func: func |
- |
- |
-# TODO: Right now, most tests use the XML API. Instead, they should respect |
-# prefer_api in the same way that commands do. |
-@unittest.skipUnless(util.RUN_INTEGRATION_TESTS, |
- 'Not running integration tests.') |
-class GsUtilIntegrationTestCase(base.GsUtilTestCase): |
- """Base class for gsutil integration tests.""" |
- GROUP_TEST_ADDRESS = 'gs-discussion@googlegroups.com' |
- GROUP_TEST_ID = ( |
- '00b4903a97d097895ab58ef505d535916a712215b79c3e54932c2eb502ad97f5') |
- USER_TEST_ADDRESS = 'gs-team@google.com' |
- USER_TEST_ID = ( |
- '00b4903a9703325c6bfc98992d72e75600387a64b3b6bee9ef74613ef8842080') |
- DOMAIN_TEST = 'google.com' |
- # No one can create this bucket without owning the gmail.com domain, and we |
- # won't create this bucket, so it shouldn't exist. |
- # It would be nice to use google.com here but JSON API disallows |
- # 'google' in resource IDs. |
- nonexistent_bucket_name = 'nonexistent-bucket-foobar.gmail.com' |
- |
- def setUp(self): |
- """Creates base configuration for integration tests.""" |
- super(GsUtilIntegrationTestCase, self).setUp() |
- self.bucket_uris = [] |
- |
- # Set up API version and project ID handler. |
- self.api_version = boto.config.get_value( |
- 'GSUtil', 'default_api_version', '1') |
- |
- # Instantiate a JSON API for use by the current integration test. |
- self.json_api = GcsJsonApi(BucketStorageUri, logging.getLogger(), |
- 'gs') |
- |
- if util.RUN_S3_TESTS: |
- self.nonexistent_bucket_name = ( |
- 'nonexistentbucket-asf801rj3r9as90mfnnkjxpo02') |
- |
- # Retry with an exponential backoff if a server error is received. This |
- # ensures that we try *really* hard to clean up after ourselves. |
- # TODO: As long as we're still using boto to do the teardown, |
- # we decorate with boto exceptions. Eventually this should be migrated |
- # to CloudApi exceptions. |
- @Retry(StorageResponseError, tries=7, timeout_secs=1) |
- def tearDown(self): |
- super(GsUtilIntegrationTestCase, self).tearDown() |
- |
- while self.bucket_uris: |
- bucket_uri = self.bucket_uris[-1] |
- try: |
- bucket_list = self._ListBucket(bucket_uri) |
- except StorageResponseError, e: |
- # This can happen for tests of rm -r command, which for bucket-only |
- # URIs delete the bucket at the end. |
- if e.status == 404: |
- self.bucket_uris.pop() |
- continue |
- else: |
- raise |
- while bucket_list: |
- error = None |
- for k in bucket_list: |
- try: |
- if isinstance(k, DeleteMarker): |
- bucket_uri.get_bucket().delete_key(k.name, |
- version_id=k.version_id) |
- else: |
- k.delete() |
- except StorageResponseError, e: |
- # This could happen if objects that have already been deleted are |
- # still showing up in the listing due to eventual consistency. In |
- # that case, we continue on until we've tried to deleted every |
- # object in the listing before raising the error on which to retry. |
- if e.status == 404: |
- error = e |
- else: |
- raise |
- if error: |
- raise error # pylint: disable=raising-bad-type |
- bucket_list = self._ListBucket(bucket_uri) |
- bucket_uri.delete_bucket() |
- self.bucket_uris.pop() |
- |
- def _ListBucket(self, bucket_uri): |
- if bucket_uri.scheme == 's3': |
- # storage_uri will omit delete markers from bucket listings, but |
- # these must be deleted before we can remove an S3 bucket. |
- return list(v for v in bucket_uri.get_bucket().list_versions()) |
- return list(bucket_uri.list_bucket(all_versions=True)) |
- |
- def AssertNObjectsInBucket(self, bucket_uri, num_objects, versioned=False): |
- """Checks (with retries) that 'ls bucket_uri/**' returns num_objects. |
- |
- This is a common test pattern to deal with eventual listing consistency for |
- tests that rely on a set of objects to be listed. |
- |
- Args: |
- bucket_uri: storage_uri for the bucket. |
- num_objects: number of objects expected in the bucket. |
- versioned: If True, perform a versioned listing. |
- |
- Raises: |
- AssertionError if number of objects does not match expected value. |
- |
- Returns: |
- Listing split across lines. |
- """ |
- # Use @Retry as hedge against bucket listing eventual consistency. |
- @Retry(AssertionError, tries=5, timeout_secs=1) |
- def _Check1(): |
- command = ['ls', '-a'] if versioned else ['ls'] |
- b_uri = [suri(bucket_uri) + '/**'] if num_objects else [suri(bucket_uri)] |
- listing = self.RunGsUtil(command + b_uri, return_stdout=True).split('\n') |
- # num_objects + one trailing newline. |
- self.assertEquals(len(listing), num_objects + 1) |
- return listing |
- return _Check1() |
- |
- def CreateBucket(self, bucket_name=None, test_objects=0, storage_class=None, |
- provider=None, prefer_json_api=False): |
- """Creates a test bucket. |
- |
- The bucket and all of its contents will be deleted after the test. |
- |
- Args: |
- bucket_name: Create the bucket with this name. If not provided, a |
- temporary test bucket name is constructed. |
- test_objects: The number of objects that should be placed in the bucket. |
- Defaults to 0. |
- storage_class: storage class to use. If not provided we us standard. |
- provider: Provider to use - either "gs" (the default) or "s3". |
- prefer_json_api: If true, use the JSON creation functions where possible. |
- |
- Returns: |
- StorageUri for the created bucket. |
- """ |
- if not provider: |
- provider = self.default_provider |
- |
- if prefer_json_api and provider == 'gs': |
- json_bucket = self.CreateBucketJson(bucket_name=bucket_name, |
- test_objects=test_objects, |
- storage_class=storage_class) |
- bucket_uri = boto.storage_uri( |
- 'gs://%s' % json_bucket.name.encode(UTF8).lower(), |
- suppress_consec_slashes=False) |
- self.bucket_uris.append(bucket_uri) |
- return bucket_uri |
- |
- bucket_name = bucket_name or self.MakeTempName('bucket') |
- |
- bucket_uri = boto.storage_uri('%s://%s' % (provider, bucket_name.lower()), |
- suppress_consec_slashes=False) |
- |
- if provider == 'gs': |
- # Apply API version and project ID headers if necessary. |
- headers = {'x-goog-api-version': self.api_version} |
- headers[GOOG_PROJ_ID_HDR] = PopulateProjectId() |
- else: |
- headers = {} |
- |
- # Parallel tests can easily run into bucket creation quotas. |
- # Retry with exponential backoff so that we create them as fast as we |
- # reasonably can. |
- @Retry(StorageResponseError, tries=7, timeout_secs=1) |
- def _CreateBucketWithExponentialBackoff(): |
- bucket_uri.create_bucket(storage_class=storage_class, headers=headers) |
- |
- _CreateBucketWithExponentialBackoff() |
- self.bucket_uris.append(bucket_uri) |
- for i in range(test_objects): |
- self.CreateObject(bucket_uri=bucket_uri, |
- object_name=self.MakeTempName('obj'), |
- contents='test %d' % i) |
- return bucket_uri |
- |
- def CreateVersionedBucket(self, bucket_name=None, test_objects=0): |
- """Creates a versioned test bucket. |
- |
- The bucket and all of its contents will be deleted after the test. |
- |
- Args: |
- bucket_name: Create the bucket with this name. If not provided, a |
- temporary test bucket name is constructed. |
- test_objects: The number of objects that should be placed in the bucket. |
- Defaults to 0. |
- |
- Returns: |
- StorageUri for the created bucket with versioning enabled. |
- """ |
- bucket_uri = self.CreateBucket(bucket_name=bucket_name, |
- test_objects=test_objects) |
- bucket_uri.configure_versioning(True) |
- return bucket_uri |
- |
- def CreateObject(self, bucket_uri=None, object_name=None, contents=None, |
- prefer_json_api=False): |
- """Creates a test object. |
- |
- Args: |
- bucket_uri: The URI of the bucket to place the object in. If not |
- specified, a new temporary bucket is created. |
- object_name: The name to use for the object. If not specified, a temporary |
- test object name is constructed. |
- contents: The contents to write to the object. If not specified, the key |
- is not written to, which means that it isn't actually created |
- yet on the server. |
- prefer_json_api: If true, use the JSON creation functions where possible. |
- |
- Returns: |
- A StorageUri for the created object. |
- """ |
- bucket_uri = bucket_uri or self.CreateBucket() |
- |
- if prefer_json_api and bucket_uri.scheme == 'gs' and contents: |
- object_name = object_name or self.MakeTempName('obj') |
- json_object = self.CreateObjectJson(contents=contents, |
- bucket_name=bucket_uri.bucket_name, |
- object_name=object_name) |
- object_uri = bucket_uri.clone_replace_name(object_name) |
- # pylint: disable=protected-access |
- # Need to update the StorageUri with the correct values while |
- # avoiding creating a versioned string. |
- object_uri._update_from_values(None, |
- json_object.generation, |
- True, |
- md5=(Base64ToHexHash(json_object.md5Hash), |
- json_object.md5Hash.strip('\n"\''))) |
- # pylint: enable=protected-access |
- return object_uri |
- |
- bucket_uri = bucket_uri or self.CreateBucket() |
- object_name = object_name or self.MakeTempName('obj') |
- key_uri = bucket_uri.clone_replace_name(object_name) |
- if contents is not None: |
- key_uri.set_contents_from_string(contents) |
- return key_uri |
- |
- def CreateBucketJson(self, bucket_name=None, test_objects=0, |
- storage_class=None): |
- """Creates a test bucket using the JSON API. |
- |
- The bucket and all of its contents will be deleted after the test. |
- |
- Args: |
- bucket_name: Create the bucket with this name. If not provided, a |
- temporary test bucket name is constructed. |
- test_objects: The number of objects that should be placed in the bucket. |
- Defaults to 0. |
- storage_class: storage class to use. If not provided we us standard. |
- |
- Returns: |
- Apitools Bucket for the created bucket. |
- """ |
- bucket_name = bucket_name or self.MakeTempName('bucket') |
- bucket_metadata = None |
- if storage_class: |
- bucket_metadata = apitools_messages.Bucket( |
- name=bucket_name.lower(), |
- storageClass=storage_class) |
- |
- # TODO: Add retry and exponential backoff. |
- bucket = self.json_api.CreateBucket(bucket_name.lower(), |
- metadata=bucket_metadata) |
- # Add bucket to list of buckets to be cleaned up. |
- # TODO: Clean up JSON buckets using JSON API. |
- self.bucket_uris.append( |
- boto.storage_uri('gs://%s' % (bucket_name.lower()), |
- suppress_consec_slashes=False)) |
- for i in range(test_objects): |
- self.CreateObjectJson(bucket_name=bucket_name, |
- object_name=self.MakeTempName('obj'), |
- contents='test %d' % i) |
- return bucket |
- |
- def CreateObjectJson(self, contents, bucket_name=None, object_name=None): |
- """Creates a test object (GCS provider only) using the JSON API. |
- |
- Args: |
- contents: The contents to write to the object. |
- bucket_name: Name of bucket to place the object in. If not |
- specified, a new temporary bucket is created. |
- object_name: The name to use for the object. If not specified, a temporary |
- test object name is constructed. |
- |
- Returns: |
- An apitools Object for the created object. |
- """ |
- bucket_name = bucket_name or self.CreateBucketJson().name |
- object_name = object_name or self.MakeTempName('obj') |
- object_metadata = apitools_messages.Object( |
- name=object_name, |
- bucket=bucket_name, |
- contentType='application/octet-stream') |
- return self.json_api.UploadObject(cStringIO.StringIO(contents), |
- object_metadata, provider='gs') |
- |
- def RunGsUtil(self, cmd, return_status=False, return_stdout=False, |
- return_stderr=False, expected_status=0, stdin=None): |
- """Runs the gsutil command. |
- |
- Args: |
- cmd: The command to run, as a list, e.g. ['cp', 'foo', 'bar'] |
- return_status: If True, the exit status code is returned. |
- return_stdout: If True, the standard output of the command is returned. |
- return_stderr: If True, the standard error of the command is returned. |
- expected_status: The expected return code. If not specified, defaults to |
- 0. If the return code is a different value, an exception |
- is raised. |
- stdin: A string of data to pipe to the process as standard input. |
- |
- Returns: |
- A tuple containing the desired return values specified by the return_* |
- arguments. |
- """ |
- cmd = ([gslib.GSUTIL_PATH] + ['--testexceptiontraces'] + |
- ['-o', 'GSUtil:default_project_id=' + PopulateProjectId()] + |
- cmd) |
- if IS_WINDOWS: |
- cmd = [sys.executable] + cmd |
- p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, |
- stdin=subprocess.PIPE) |
- (stdout, stderr) = p.communicate(stdin) |
- status = p.returncode |
- |
- if expected_status is not None: |
- self.assertEqual( |
- status, expected_status, |
- msg='Expected status %d, got %d.\nCommand:\n%s\n\nstderr:\n%s' % ( |
- expected_status, status, ' '.join(cmd), stderr)) |
- |
- toreturn = [] |
- if return_status: |
- toreturn.append(status) |
- if return_stdout: |
- if IS_WINDOWS: |
- stdout = stdout.replace('\r\n', '\n') |
- toreturn.append(stdout) |
- if return_stderr: |
- if IS_WINDOWS: |
- stderr = stderr.replace('\r\n', '\n') |
- toreturn.append(stderr) |
- |
- if len(toreturn) == 1: |
- return toreturn[0] |
- elif toreturn: |
- return tuple(toreturn) |
- |
- def RunGsUtilTabCompletion(self, cmd, expected_results=None): |
- """Runs the gsutil command in tab completion mode. |
- |
- Args: |
- cmd: The command to run, as a list, e.g. ['cp', 'foo', 'bar'] |
- expected_results: The expected tab completion results for the given input. |
- """ |
- cmd = [gslib.GSUTIL_PATH] + ['--testexceptiontraces'] + cmd |
- cmd_str = ' '.join(cmd) |
- |
- @Retry(AssertionError, tries=5, timeout_secs=1) |
- def _RunTabCompletion(): |
- """Runs the tab completion operation with retries.""" |
- results_string = None |
- with tempfile.NamedTemporaryFile( |
- delete=False) as tab_complete_result_file: |
- # argcomplete returns results via the '8' file descriptor so we |
- # redirect to a file so we can capture them. |
- cmd_str_with_result_redirect = '%s 8>%s' % ( |
- cmd_str, tab_complete_result_file.name) |
- env = os.environ.copy() |
- env['_ARGCOMPLETE'] = '1' |
- env['COMP_LINE'] = cmd_str |
- env['COMP_POINT'] = str(len(cmd_str)) |
- subprocess.call(cmd_str_with_result_redirect, env=env, shell=True) |
- results_string = tab_complete_result_file.read().decode( |
- locale.getpreferredencoding()) |
- if results_string: |
- results = results_string.split('\013') |
- else: |
- results = [] |
- self.assertEqual(results, expected_results) |
- |
- # When tests are run in parallel, tab completion could take a long time, |
- # so choose a long timeout value. |
- with SetBotoConfigForTest([('GSUtil', 'tab_completion_timeout', '120')]): |
- _RunTabCompletion() |
- |
- @contextmanager |
- def SetAnonymousBotoCreds(self): |
- boto_config_path = self.CreateTempFile( |
- contents=BOTO_CONFIG_CONTENTS_IGNORE_ANON_WARNING) |
- with SetBotoConfigFileForTest(boto_config_path): |
- # Make sure to reset Developer Shell credential port so that the child |
- # gsutil process is really anonymous. |
- with SetEnvironmentForTest({'DEVSHELL_CLIENT_PORT': None}): |
- yield |