Chromium Code Reviews| 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', 'gsutil', |
| 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(): |
| 47 if SupportsProdaccess(gsutil_path) and _FindExecutableInPath('prodaccess'): | 44 command = _GSUTIL_PATH |
| 48 return 'Run prodaccess to authenticate.' | 45 if util.IsRunningOnCrosDevice(): |
| 49 else: | 46 command = 'HOME=%s %s' % (_CROS_GSUTIL_HOME_WAR, _GSUTIL_PATH) |
| 50 if util.IsRunningOnCrosDevice(): | 47 return ('To configure your credentials:\n' |
| 51 gsutil_path = ('HOME=%s %s' % (_CROS_GSUTIL_HOME_WAR, gsutil_path)) | 48 ' 1. Run "%s config" and follow its instructions.\n' |
| 52 return ('To configure your credentials:\n' | 49 ' 2. If you have a @google.com account, use that account.\n' |
| 53 ' 1. Run "%s config" and follow its instructions.\n' | 50 ' 3. For the project-id, just enter 0.' % command) |
| 54 ' 2. If you have a @google.com account, use that account.\n' | |
| 55 ' 3. For the project-id, just enter 0.' % gsutil_path) | |
| 56 | 51 |
| 57 | 52 |
| 58 class PermissionError(CloudStorageError): | 53 class PermissionError(CloudStorageError): |
| 59 def __init__(self, gsutil_path): | 54 def __init__(self): |
| 60 super(PermissionError, self).__init__( | 55 super(PermissionError, self).__init__( |
| 61 '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 ' |
| 62 'have permission. ' + self._GetConfigInstructions(gsutil_path)) | 57 'have permission. ' + self._GetConfigInstructions()) |
| 63 | 58 |
| 64 | 59 |
| 65 class CredentialsError(CloudStorageError): | 60 class CredentialsError(CloudStorageError): |
| 66 def __init__(self, gsutil_path): | 61 def __init__(self): |
| 67 super(CredentialsError, self).__init__( | 62 super(CredentialsError, self).__init__( |
| 68 '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 ' |
| 69 'configured credentials. ' + self._GetConfigInstructions(gsutil_path)) | 64 'configured credentials. ' + self._GetConfigInstructions()) |
| 70 | 65 |
| 71 | 66 |
| 72 class NotFoundError(CloudStorageError): | 67 class NotFoundError(CloudStorageError): |
| 73 pass | 68 pass |
| 74 | 69 |
| 75 | 70 |
| 76 class ServerError(CloudStorageError): | 71 class ServerError(CloudStorageError): |
| 77 pass | 72 pass |
| 78 | 73 |
| 79 | 74 |
| 80 # 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()? |
| 81 def _FindExecutableInPath(relative_executable_path, *extra_search_paths): | 76 def _FindExecutableInPath(relative_executable_path, *extra_search_paths): |
| 82 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) |
| 83 for search_path in search_paths: | 78 for search_path in search_paths: |
| 84 executable_path = os.path.join(search_path, relative_executable_path) | 79 executable_path = os.path.join(search_path, relative_executable_path) |
| 85 if path.IsExecutable(executable_path): | 80 if path.IsExecutable(executable_path): |
| 86 return executable_path | 81 return executable_path |
| 87 return None | 82 return None |
| 88 | 83 |
| 89 | 84 |
| 90 def _DownloadGsutil(): | |
| 91 logging.info('Downloading gsutil') | |
| 92 with contextlib.closing(urllib2.urlopen(_GSUTIL_URL, timeout=60)) as response: | |
| 93 with tarfile.open(fileobj=cStringIO.StringIO(response.read())) as tar_file: | |
| 94 tar_file.extractall(os.path.dirname(_DOWNLOAD_PATH)) | |
| 95 logging.info('Downloaded gsutil to %s' % _DOWNLOAD_PATH) | |
| 96 | |
| 97 return os.path.join(_DOWNLOAD_PATH, 'gsutil') | |
| 98 | |
| 99 | |
| 100 def FindGsutil(): | |
| 101 """Return the gsutil executable path. If we can't find it, download it.""" | |
| 102 # Look for a depot_tools installation. | |
| 103 # FIXME: gsutil in depot_tools is not working correctly. crbug.com/413414 | |
| 104 #gsutil_path = _FindExecutableInPath( | |
| 105 # os.path.join('third_party', 'gsutil', 'gsutil'), _DOWNLOAD_PATH) | |
| 106 #if gsutil_path: | |
| 107 # return gsutil_path | |
| 108 | |
| 109 # Look for a gsutil installation. | |
| 110 gsutil_path = _FindExecutableInPath('gsutil', _DOWNLOAD_PATH) | |
| 111 if gsutil_path: | |
| 112 return gsutil_path | |
| 113 | |
| 114 # Failed to find it. Download it! | |
| 115 return _DownloadGsutil() | |
| 116 | |
| 117 | |
| 118 def SupportsProdaccess(gsutil_path): | |
|
nednguyen
2015/07/29 18:01:19
I delete this because this code doesn't make any s
aiolos (Not reviewing)
2015/07/29 23:22:27
I *think* this is fine to remove at this point, bu
aiolos (Not reviewing)
2015/07/29 23:26:29
It might make more sense to do this in a separate
nednguyen
2015/08/01 04:16:17
Done.
| |
| 119 with open(gsutil_path, 'r') as gsutil: | |
| 120 return 'prodaccess' in gsutil.read() | |
| 121 | |
| 122 | |
| 123 def _RunCommand(args): | 85 def _RunCommand(args): |
| 124 gsutil_path = FindGsutil() | |
| 125 | |
| 126 # 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/, |
| 127 # 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 |
| 128 # 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 |
| 129 # when running on cros device. | 89 # when running on cros device. |
| 130 # | 90 # |
| 131 # 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. |
| 132 # http://crbug.com/386416, http://crbug.com/359293. | 92 # http://crbug.com/386416, http://crbug.com/359293. |
| 133 gsutil_env = None | 93 gsutil_env = None |
| 134 if util.IsRunningOnCrosDevice(): | 94 if util.IsRunningOnCrosDevice(): |
| 135 gsutil_env = os.environ.copy() | 95 gsutil_env = os.environ.copy() |
| 136 gsutil_env['HOME'] = _CROS_GSUTIL_HOME_WAR | 96 gsutil_env['HOME'] = _CROS_GSUTIL_HOME_WAR |
| 137 | 97 |
| 138 if os.name == 'nt': | 98 if os.name == 'nt': |
| 139 # If Windows, prepend python. Python scripts aren't directly executable. | 99 # If Windows, prepend python. Python scripts aren't directly executable. |
| 140 args = [sys.executable, gsutil_path] + args | 100 args = [sys.executable, _GSUTIL_PATH] + args |
| 141 else: | 101 else: |
| 142 # 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. |
| 143 args = [gsutil_path] + args | 103 args = [_GSUTIL_PATH] + args |
| 144 | 104 |
| 145 gsutil = subprocess.Popen(args, stdout=subprocess.PIPE, | 105 gsutil = subprocess.Popen(args, stdout=subprocess.PIPE, |
| 146 stderr=subprocess.PIPE, env=gsutil_env) | 106 stderr=subprocess.PIPE, env=gsutil_env) |
| 147 stdout, stderr = gsutil.communicate() | 107 stdout, stderr = gsutil.communicate() |
| 148 | 108 |
| 149 if gsutil.returncode: | 109 if gsutil.returncode: |
| 150 if stderr.startswith(( | 110 if stderr.startswith(( |
| 151 'You are attempting to access protected data with no configured', | 111 'You are attempting to access protected data with no configured', |
| 152 'Failure: No handler was ready to authenticate.')): | 112 'Failure: No handler was ready to authenticate.')): |
| 153 raise CredentialsError(gsutil_path) | 113 raise CredentialsError() |
| 154 if ('status=403' in stderr or 'status 403' in stderr or | 114 if ('status=403' in stderr or 'status 403' in stderr or |
| 155 '403 Forbidden' in stderr): | 115 '403 Forbidden' in stderr): |
| 156 raise PermissionError(gsutil_path) | 116 raise PermissionError() |
| 157 if (stderr.startswith('InvalidUriError') or 'No such object' in stderr or | 117 if (stderr.startswith('InvalidUriError') or 'No such object' in stderr or |
| 158 '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): |
| 159 raise NotFoundError(stderr) | 119 raise NotFoundError(stderr) |
| 160 if '500 Internal Server Error' in stderr: | 120 if '500 Internal Server Error' in stderr: |
| 161 raise ServerError(stderr) | 121 raise ServerError(stderr) |
| 162 raise CloudStorageError(stderr) | 122 raise CloudStorageError(stderr) |
| 163 | 123 |
| 164 return stdout | 124 return stdout |
| 165 | 125 |
| 166 | 126 |
| (...skipping 129 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 296 chunk = f.read(1024*1024) | 256 chunk = f.read(1024*1024) |
| 297 if not chunk: | 257 if not chunk: |
| 298 break | 258 break |
| 299 sha1.update(chunk) | 259 sha1.update(chunk) |
| 300 return sha1.hexdigest() | 260 return sha1.hexdigest() |
| 301 | 261 |
| 302 | 262 |
| 303 def ReadHash(hash_path): | 263 def ReadHash(hash_path): |
| 304 with open(hash_path, 'rb') as f: | 264 with open(hash_path, 'rb') as f: |
| 305 return f.read(1024).rstrip() | 265 return f.read(1024).rstrip() |
| OLD | NEW |