| OLD | NEW |
| 1 # Copyright 2014 The Chromium Authors. All rights reserved. | 1 # Copyright 2014 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
| 4 | 4 |
| 5 """Wrappers for gsutil, for basic interaction with Google Cloud Storage.""" | 5 """Wrappers for gsutil, for basic interaction with Google Cloud Storage.""" |
| 6 | 6 |
| 7 import collections | 7 import collections |
| 8 import contextlib | |
| 9 import cStringIO | |
| 10 import hashlib | 8 import hashlib |
| 11 import logging | 9 import logging |
| 12 import os | 10 import os |
| 13 import subprocess | 11 import subprocess |
| 14 import sys | 12 import sys |
| 15 import tarfile | |
| 16 import urllib2 | |
| 17 | 13 |
| 18 from telemetry.core import util | 14 from telemetry.core import util |
| 19 from telemetry import decorators | 15 from telemetry import decorators |
| 20 from telemetry.internal.util import path | 16 from telemetry.internal.util import path |
| 21 | 17 |
| 22 | 18 |
| 23 PUBLIC_BUCKET = 'chromium-telemetry' | 19 PUBLIC_BUCKET = 'chromium-telemetry' |
| 24 PARTNER_BUCKET = 'chrome-partner-telemetry' | 20 PARTNER_BUCKET = 'chrome-partner-telemetry' |
| 25 INTERNAL_BUCKET = 'chrome-telemetry' | 21 INTERNAL_BUCKET = 'chrome-telemetry' |
| 26 | 22 |
| 27 | 23 |
| 28 # Uses ordered dict to make sure that bucket's key-value items are ordered from | 24 # Uses ordered dict to make sure that bucket's key-value items are ordered from |
| 29 # the most open to the most restrictive. | 25 # the most open to the most restrictive. |
| 30 BUCKET_ALIASES = collections.OrderedDict(( | 26 BUCKET_ALIASES = collections.OrderedDict(( |
| 31 ('public', PUBLIC_BUCKET), | 27 ('public', PUBLIC_BUCKET), |
| 32 ('partner', PARTNER_BUCKET), | 28 ('partner', PARTNER_BUCKET), |
| 33 ('internal', INTERNAL_BUCKET), | 29 ('internal', INTERNAL_BUCKET), |
| 34 )) | 30 )) |
| 35 | 31 |
| 36 | 32 |
| 37 _GSUTIL_URL = 'http://storage.googleapis.com/pub/gsutil.tar.gz' | 33 _GSUTIL_PATH = os.path.join(path.GetTelemetryDir(), 'third_party', 'gsutilz', |
| 38 _DOWNLOAD_PATH = os.path.join(path.GetTelemetryDir(), 'third_party', 'gsutil') | 34 'gsutil') |
| 35 |
| 39 # TODO(tbarzic): A workaround for http://crbug.com/386416 and | 36 # TODO(tbarzic): A workaround for http://crbug.com/386416 and |
| 40 # http://crbug.com/359293. See |_RunCommand|. | 37 # http://crbug.com/359293. See |_RunCommand|. |
| 41 _CROS_GSUTIL_HOME_WAR = '/home/chromeos-test/' | 38 _CROS_GSUTIL_HOME_WAR = '/home/chromeos-test/' |
| 42 | 39 |
| 43 | 40 |
| 44 class CloudStorageError(Exception): | 41 class CloudStorageError(Exception): |
| 45 @staticmethod | 42 @staticmethod |
| 46 def _GetConfigInstructions(gsutil_path): | 43 def _GetConfigInstructions(): |
| 44 command = _GSUTIL_PATH |
| 47 if util.IsRunningOnCrosDevice(): | 45 if util.IsRunningOnCrosDevice(): |
| 48 gsutil_path = ('HOME=%s %s' % (_CROS_GSUTIL_HOME_WAR, gsutil_path)) | 46 command = 'HOME=%s %s' % (_CROS_GSUTIL_HOME_WAR, _GSUTIL_PATH) |
| 49 return ('To configure your credentials:\n' | 47 return ('To configure your credentials:\n' |
| 50 ' 1. Run "%s config" and follow its instructions.\n' | 48 ' 1. Run "%s config" and follow its instructions.\n' |
| 51 ' 2. If you have a @google.com account, use that account.\n' | 49 ' 2. If you have a @google.com account, use that account.\n' |
| 52 ' 3. For the project-id, just enter 0.' % gsutil_path) | 50 ' 3. For the project-id, just enter 0.' % command) |
| 53 | 51 |
| 54 | 52 |
| 55 class PermissionError(CloudStorageError): | 53 class PermissionError(CloudStorageError): |
| 56 def __init__(self, gsutil_path): | 54 def __init__(self): |
| 57 super(PermissionError, self).__init__( | 55 super(PermissionError, self).__init__( |
| 58 'Attempted to access a file from Cloud Storage but you don\'t ' | 56 'Attempted to access a file from Cloud Storage but you don\'t ' |
| 59 'have permission. ' + self._GetConfigInstructions(gsutil_path)) | 57 'have permission. ' + self._GetConfigInstructions()) |
| 60 | 58 |
| 61 | 59 |
| 62 class CredentialsError(CloudStorageError): | 60 class CredentialsError(CloudStorageError): |
| 63 def __init__(self, gsutil_path): | 61 def __init__(self): |
| 64 super(CredentialsError, self).__init__( | 62 super(CredentialsError, self).__init__( |
| 65 'Attempted to access a file from Cloud Storage but you have no ' | 63 'Attempted to access a file from Cloud Storage but you have no ' |
| 66 'configured credentials. ' + self._GetConfigInstructions(gsutil_path)) | 64 'configured credentials. ' + self._GetConfigInstructions()) |
| 67 | 65 |
| 68 | 66 |
| 69 class NotFoundError(CloudStorageError): | 67 class NotFoundError(CloudStorageError): |
| 70 pass | 68 pass |
| 71 | 69 |
| 72 | 70 |
| 73 class ServerError(CloudStorageError): | 71 class ServerError(CloudStorageError): |
| 74 pass | 72 pass |
| 75 | 73 |
| 76 | 74 |
| 77 # TODO(tonyg/dtu): Can this be replaced with distutils.spawn.find_executable()? | 75 # TODO(tonyg/dtu): Can this be replaced with distutils.spawn.find_executable()? |
| 78 def _FindExecutableInPath(relative_executable_path, *extra_search_paths): | 76 def _FindExecutableInPath(relative_executable_path, *extra_search_paths): |
| 79 search_paths = list(extra_search_paths) + os.environ['PATH'].split(os.pathsep) | 77 search_paths = list(extra_search_paths) + os.environ['PATH'].split(os.pathsep) |
| 80 for search_path in search_paths: | 78 for search_path in search_paths: |
| 81 executable_path = os.path.join(search_path, relative_executable_path) | 79 executable_path = os.path.join(search_path, relative_executable_path) |
| 82 if path.IsExecutable(executable_path): | 80 if path.IsExecutable(executable_path): |
| 83 return executable_path | 81 return executable_path |
| 84 return None | 82 return None |
| 85 | 83 |
| 86 | 84 |
| 87 def _DownloadGsutil(): | |
| 88 logging.info('Downloading gsutil') | |
| 89 with contextlib.closing(urllib2.urlopen(_GSUTIL_URL, timeout=60)) as response: | |
| 90 with tarfile.open(fileobj=cStringIO.StringIO(response.read())) as tar_file: | |
| 91 tar_file.extractall(os.path.dirname(_DOWNLOAD_PATH)) | |
| 92 logging.info('Downloaded gsutil to %s' % _DOWNLOAD_PATH) | |
| 93 | |
| 94 return os.path.join(_DOWNLOAD_PATH, 'gsutil') | |
| 95 | |
| 96 | |
| 97 def FindGsutil(): | |
| 98 """Return the gsutil executable path. If we can't find it, download it.""" | |
| 99 # Look for a depot_tools installation. | |
| 100 # FIXME: gsutil in depot_tools is not working correctly. crbug.com/413414 | |
| 101 #gsutil_path = _FindExecutableInPath( | |
| 102 # os.path.join('third_party', 'gsutil', 'gsutil'), _DOWNLOAD_PATH) | |
| 103 #if gsutil_path: | |
| 104 # return gsutil_path | |
| 105 | |
| 106 # Look for a gsutil installation. | |
| 107 gsutil_path = _FindExecutableInPath('gsutil', _DOWNLOAD_PATH) | |
| 108 if gsutil_path: | |
| 109 return gsutil_path | |
| 110 | |
| 111 # Failed to find it. Download it! | |
| 112 return _DownloadGsutil() | |
| 113 | |
| 114 | |
| 115 def _RunCommand(args): | 85 def _RunCommand(args): |
| 116 gsutil_path = FindGsutil() | |
| 117 | |
| 118 # On cros device, as telemetry is running as root, home will be set to /root/, | 86 # On cros device, as telemetry is running as root, home will be set to /root/, |
| 119 # which is not writable. gsutil will attempt to create a download tracker dir | 87 # which is not writable. gsutil will attempt to create a download tracker dir |
| 120 # in home dir and fail. To avoid this, override HOME dir to something writable | 88 # in home dir and fail. To avoid this, override HOME dir to something writable |
| 121 # when running on cros device. | 89 # when running on cros device. |
| 122 # | 90 # |
| 123 # TODO(tbarzic): Figure out a better way to handle gsutil on cros. | 91 # TODO(tbarzic): Figure out a better way to handle gsutil on cros. |
| 124 # http://crbug.com/386416, http://crbug.com/359293. | 92 # http://crbug.com/386416, http://crbug.com/359293. |
| 125 gsutil_env = None | 93 gsutil_env = None |
| 126 if util.IsRunningOnCrosDevice(): | 94 if util.IsRunningOnCrosDevice(): |
| 127 gsutil_env = os.environ.copy() | 95 gsutil_env = os.environ.copy() |
| 128 gsutil_env['HOME'] = _CROS_GSUTIL_HOME_WAR | 96 gsutil_env['HOME'] = _CROS_GSUTIL_HOME_WAR |
| 129 | 97 |
| 130 if os.name == 'nt': | 98 if os.name == 'nt': |
| 131 # If Windows, prepend python. Python scripts aren't directly executable. | 99 # If Windows, prepend python. Python scripts aren't directly executable. |
| 132 args = [sys.executable, gsutil_path] + args | 100 args = [sys.executable, _GSUTIL_PATH] + args |
| 133 else: | 101 else: |
| 134 # Don't do it on POSIX, in case someone is using a shell script to redirect. | 102 # Don't do it on POSIX, in case someone is using a shell script to redirect. |
| 135 args = [gsutil_path] + args | 103 args = [_GSUTIL_PATH] + args |
| 136 | 104 |
| 137 gsutil = subprocess.Popen(args, stdout=subprocess.PIPE, | 105 gsutil = subprocess.Popen(args, stdout=subprocess.PIPE, |
| 138 stderr=subprocess.PIPE, env=gsutil_env) | 106 stderr=subprocess.PIPE, env=gsutil_env) |
| 139 stdout, stderr = gsutil.communicate() | 107 stdout, stderr = gsutil.communicate() |
| 140 | 108 |
| 141 if gsutil.returncode: | 109 if gsutil.returncode: |
| 142 if stderr.startswith(( | 110 if stderr.startswith(( |
| 143 'You are attempting to access protected data with no configured', | 111 'You are attempting to access protected data with no configured', |
| 144 'Failure: No handler was ready to authenticate.')): | 112 'Failure: No handler was ready to authenticate.')): |
| 145 raise CredentialsError(gsutil_path) | 113 raise CredentialsError() |
| 146 if ('status=403' in stderr or 'status 403' in stderr or | 114 if ('status=403' in stderr or 'status 403' in stderr or |
| 147 '403 Forbidden' in stderr): | 115 '403 Forbidden' in stderr): |
| 148 raise PermissionError(gsutil_path) | 116 raise PermissionError() |
| 149 if (stderr.startswith('InvalidUriError') or 'No such object' in stderr or | 117 if (stderr.startswith('InvalidUriError') or 'No such object' in stderr or |
| 150 'No URLs matched' in stderr or 'One or more URLs matched no' in stderr): | 118 'No URLs matched' in stderr or 'One or more URLs matched no' in stderr): |
| 151 raise NotFoundError(stderr) | 119 raise NotFoundError(stderr) |
| 152 if '500 Internal Server Error' in stderr: | 120 if '500 Internal Server Error' in stderr: |
| 153 raise ServerError(stderr) | 121 raise ServerError(stderr) |
| 154 raise CloudStorageError(stderr) | 122 raise CloudStorageError(stderr) |
| 155 | 123 |
| 156 return stdout | 124 return stdout |
| 157 | 125 |
| 158 | 126 |
| (...skipping 129 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 288 chunk = f.read(1024*1024) | 256 chunk = f.read(1024*1024) |
| 289 if not chunk: | 257 if not chunk: |
| 290 break | 258 break |
| 291 sha1.update(chunk) | 259 sha1.update(chunk) |
| 292 return sha1.hexdigest() | 260 return sha1.hexdigest() |
| 293 | 261 |
| 294 | 262 |
| 295 def ReadHash(hash_path): | 263 def ReadHash(hash_path): |
| 296 with open(hash_path, 'rb') as f: | 264 with open(hash_path, 'rb') as f: |
| 297 return f.read(1024).rstrip() | 265 return f.read(1024).rstrip() |
| OLD | NEW |