| OLD | NEW |
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # Copyright 2013 The Chromium Authors. All rights reserved. | 2 # Copyright 2013 The Chromium Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
| 5 | 5 |
| 6 """Client tool to trigger tasks or retrieve results from a Swarming server.""" | 6 """Client tool to trigger tasks or retrieve results from a Swarming server.""" |
| 7 | 7 |
| 8 __version__ = '0.1' | 8 __version__ = '0.1' |
| 9 | 9 |
| 10 import binascii | 10 import binascii |
| (...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 64 """Generic failure.""" | 64 """Generic failure.""" |
| 65 pass | 65 pass |
| 66 | 66 |
| 67 | 67 |
| 68 class Manifest(object): | 68 class Manifest(object): |
| 69 """Represents a Swarming task manifest. | 69 """Represents a Swarming task manifest. |
| 70 | 70 |
| 71 Also includes code to zip code and upload itself. | 71 Also includes code to zip code and upload itself. |
| 72 """ | 72 """ |
| 73 def __init__( | 73 def __init__( |
| 74 self, manifest_hash, test_name, shards, test_filter, slave_os, | 74 self, isolated_hash, test_name, shards, test_filter, slave_os, |
| 75 working_dir, isolate_server, verbose, profile, priority, algo): | 75 working_dir, isolate_server, verbose, profile, priority, algo): |
| 76 """Populates a manifest object. | 76 """Populates a manifest object. |
| 77 Args: | 77 Args: |
| 78 manifest_hash - The manifest's sha-1 that the slave is going to fetch. | 78 isolated_hash - The manifest's sha-1 that the slave is going to fetch. |
| 79 test_name - The name to give the test request. | 79 test_name - The name to give the test request. |
| 80 shards - The number of swarm shards to request. | 80 shards - The number of swarm shards to request. |
| 81 test_filter - The gtest filter to apply when running the test. | 81 test_filter - The gtest filter to apply when running the test. |
| 82 slave_os - OS to run on. | 82 slave_os - OS to run on. |
| 83 working_dir - Relative working directory to start the script. | 83 working_dir - Relative working directory to start the script. |
| 84 isolate_server - isolate server url. | 84 isolate_server - isolate server url. |
| 85 verbose - if True, have the slave print more details. | 85 verbose - if True, have the slave print more details. |
| 86 profile - if True, have the slave print more timing data. | 86 profile - if True, have the slave print more timing data. |
| 87 priority - int between 0 and 1000, lower the higher priority. | 87 priority - int between 0 and 1000, lower the higher priority. |
| 88 algo - hashing algorithm used. | 88 algo - hashing algorithm used. |
| 89 """ | 89 """ |
| 90 self.manifest_hash = manifest_hash | 90 self.isolated_hash = isolated_hash |
| 91 self.bundle = zip_package.ZipPackage(ROOT_DIR) | 91 self.bundle = zip_package.ZipPackage(ROOT_DIR) |
| 92 | 92 |
| 93 self._test_name = test_name | 93 self._test_name = test_name |
| 94 self._shards = shards | 94 self._shards = shards |
| 95 self._test_filter = test_filter | 95 self._test_filter = test_filter |
| 96 self._target_platform = PLATFORM_MAPPING_SWARMING[slave_os] | 96 self._target_platform = slave_os |
| 97 self._working_dir = working_dir | 97 self._working_dir = working_dir |
| 98 | 98 |
| 99 self.isolate_server = isolate_server | 99 self.isolate_server = isolate_server |
| 100 self._data_server_retrieval = isolate_server + '/content/retrieve/default/' | 100 self._data_server_retrieval = isolate_server + '/content/retrieve/default/' |
| 101 self._data_server_storage = isolate_server + '/content/store/default/' | 101 self._data_server_storage = isolate_server + '/content/store/default/' |
| 102 self._data_server_has = isolate_server + '/content/contains/default' | 102 self._data_server_has = isolate_server + '/content/contains/default' |
| 103 self._data_server_get_token = isolate_server + '/content/get_token' | 103 self._data_server_get_token = isolate_server + '/content/get_token' |
| 104 | 104 |
| 105 self.verbose = bool(verbose) | 105 self.verbose = bool(verbose) |
| 106 self.profile = bool(profile) | 106 self.profile = bool(profile) |
| 107 self.priority = priority | 107 self.priority = priority |
| 108 self._algo = algo | 108 self._algo = algo |
| 109 | 109 |
| 110 self._zip_file_hash = '' | 110 self._zip_file_hash = '' |
| 111 self._tasks = [] | 111 self._tasks = [] |
| 112 self._files = {} | |
| 113 self._token_cache = None | 112 self._token_cache = None |
| 114 | 113 |
| 115 def _token(self): | 114 def _token(self): |
| 116 if not self._token_cache: | 115 if not self._token_cache: |
| 117 result = net.url_open(self._data_server_get_token) | 116 result = net.url_open(self._data_server_get_token) |
| 118 if not result: | 117 if not result: |
| 119 # TODO(maruel): Implement authentication. | 118 # TODO(maruel): Implement authentication. |
| 120 raise Failure('Failed to get token, need authentication') | 119 raise Failure('Failed to get token, need authentication') |
| 121 # Quote it right away, so creating the urls is simpler. | 120 # Quote it right away, so creating the urls is simpler. |
| 122 self._token_cache = urllib.quote(result.read()) | 121 self._token_cache = urllib.quote(result.read()) |
| (...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 170 | 169 |
| 171 return True | 170 return True |
| 172 | 171 |
| 173 def to_json(self): | 172 def to_json(self): |
| 174 """Exports the current configuration into a swarm-readable manifest file. | 173 """Exports the current configuration into a swarm-readable manifest file. |
| 175 | 174 |
| 176 This function doesn't mutate the object. | 175 This function doesn't mutate the object. |
| 177 """ | 176 """ |
| 178 test_case = { | 177 test_case = { |
| 179 'test_case_name': self._test_name, | 178 'test_case_name': self._test_name, |
| 180 'data': [ | 179 'data': [], |
| 181 [self._data_server_retrieval + urllib.quote(self._zip_file_hash), | |
| 182 'swarm_data.zip'], | |
| 183 ], | |
| 184 'tests': self._tasks, | 180 'tests': self._tasks, |
| 185 'env_vars': {}, | 181 'env_vars': {}, |
| 186 'configurations': [ | 182 'configurations': [ |
| 187 { | 183 { |
| 188 'min_instances': self._shards, | 184 'min_instances': self._shards, |
| 189 'config_name': self._target_platform, | 185 'config_name': self._target_platform, |
| 190 'dimensions': { | 186 'dimensions': { |
| 191 'os': self._target_platform, | 187 'os': self._target_platform, |
| 192 }, | 188 }, |
| 193 }, | 189 }, |
| 194 ], | 190 ], |
| 195 'working_dir': self._working_dir, | 191 'working_dir': self._working_dir, |
| 196 'restart_on_failure': True, | 192 'restart_on_failure': True, |
| 197 'cleanup': 'root', | 193 'cleanup': 'root', |
| 198 'priority': self.priority, | 194 'priority': self.priority, |
| 199 } | 195 } |
| 200 | 196 if self._zip_file_hash: |
| 197 test_case['data'].append( |
| 198 [ |
| 199 self._data_server_retrieval + urllib.quote(self._zip_file_hash), |
| 200 'swarm_data.zip', |
| 201 ]) |
| 201 # These flags are googletest specific. | 202 # These flags are googletest specific. |
| 202 if self._test_filter and self._test_filter != '*': | 203 if self._test_filter and self._test_filter != '*': |
| 203 test_case['env_vars']['GTEST_FILTER'] = self._test_filter | 204 test_case['env_vars']['GTEST_FILTER'] = self._test_filter |
| 204 if self._shards > 1: | 205 if self._shards > 1: |
| 205 test_case['env_vars']['GTEST_SHARD_INDEX'] = '%(instance_index)s' | 206 test_case['env_vars']['GTEST_SHARD_INDEX'] = '%(instance_index)s' |
| 206 test_case['env_vars']['GTEST_TOTAL_SHARDS'] = '%(num_instances)s' | 207 test_case['env_vars']['GTEST_TOTAL_SHARDS'] = '%(num_instances)s' |
| 207 | 208 |
| 208 return json.dumps(test_case, separators=(',',':')) | 209 return json.dumps(test_case, separators=(',',':')) |
| 209 | 210 |
| 210 | 211 |
| (...skipping 21 matching lines...) Expand all Loading... |
| 232 continue | 233 continue |
| 233 return json.loads(result) | 234 return json.loads(result) |
| 234 | 235 |
| 235 raise Failure( | 236 raise Failure( |
| 236 'Error: Unable to find any tests with the name, %s, on swarm server' | 237 'Error: Unable to find any tests with the name, %s, on swarm server' |
| 237 % test_name) | 238 % test_name) |
| 238 | 239 |
| 239 | 240 |
| 240 def retrieve_results(base_url, test_key, timeout, should_stop): | 241 def retrieve_results(base_url, test_key, timeout, should_stop): |
| 241 """Retrieves results for a single test_key.""" | 242 """Retrieves results for a single test_key.""" |
| 242 assert isinstance(timeout, float) | 243 assert isinstance(timeout, float), timeout |
| 243 params = [('r', test_key)] | 244 params = [('r', test_key)] |
| 244 result_url = '%s/get_result?%s' % (base_url, urllib.urlencode(params)) | 245 result_url = '%s/get_result?%s' % (base_url, urllib.urlencode(params)) |
| 245 start = now() | 246 start = now() |
| 246 while True: | 247 while True: |
| 247 if timeout and (now() - start) >= timeout: | 248 if timeout and (now() - start) >= timeout: |
| 248 logging.error('retrieve_results(%s) timed out', base_url) | 249 logging.error('retrieve_results(%s) timed out', base_url) |
| 249 return {} | 250 return {} |
| 250 # Do retries ourselves. | 251 # Do retries ourselves. |
| 251 response = net.url_read(result_url, retry_404=False, retry_50x=False) | 252 response = net.url_read(result_url, retry_404=False, retry_50x=False) |
| 252 if response is None: | 253 if response is None: |
| 253 # Aggressively poll for results. Do not use retry_404 so | 254 # Aggressively poll for results. Do not use retry_404 so |
| 254 # should_stop is polled more often. | 255 # should_stop is polled more often. |
| 255 remaining = min(5, timeout - (now() - start)) if timeout else 5 | 256 remaining = min(5, timeout - (now() - start)) if timeout else 5 |
| 256 if remaining > 0: | 257 if remaining > 0: |
| 258 if should_stop.get(): |
| 259 return {} |
| 257 net.sleep_before_retry(1, remaining) | 260 net.sleep_before_retry(1, remaining) |
| 258 else: | 261 else: |
| 259 try: | 262 try: |
| 260 data = json.loads(response) or {} | 263 data = json.loads(response) or {} |
| 261 except (ValueError, TypeError): | 264 except (ValueError, TypeError): |
| 262 logging.warning( | 265 logging.warning( |
| 263 'Received corrupted data for test_key %s. Retrying.', test_key) | 266 'Received corrupted data for test_key %s. Retrying.', test_key) |
| 264 else: | 267 else: |
| 265 if data['output']: | 268 if data['output']: |
| 266 return data | 269 return data |
| (...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 317 run_test_name = 'run_isolated.zip' | 320 run_test_name = 'run_isolated.zip' |
| 318 manifest.bundle.add_buffer(run_test_name, | 321 manifest.bundle.add_buffer(run_test_name, |
| 319 run_isolated.get_as_zip_package().zip_into_buffer(compress=False)) | 322 run_isolated.get_as_zip_package().zip_into_buffer(compress=False)) |
| 320 | 323 |
| 321 cleanup_script_name = 'swarm_cleanup.py' | 324 cleanup_script_name = 'swarm_cleanup.py' |
| 322 manifest.bundle.add_file(os.path.join(TOOLS_PATH, cleanup_script_name), | 325 manifest.bundle.add_file(os.path.join(TOOLS_PATH, cleanup_script_name), |
| 323 cleanup_script_name) | 326 cleanup_script_name) |
| 324 | 327 |
| 325 run_cmd = [ | 328 run_cmd = [ |
| 326 'python', run_test_name, | 329 'python', run_test_name, |
| 327 '--hash', manifest.manifest_hash, | 330 '--hash', manifest.isolated_hash, |
| 328 '--isolate-server', manifest.isolate_server, | 331 '--isolate-server', manifest.isolate_server, |
| 329 ] | 332 ] |
| 330 if manifest.verbose or manifest.profile: | 333 if manifest.verbose or manifest.profile: |
| 331 # Have it print the profiling section. | 334 # Have it print the profiling section. |
| 332 run_cmd.append('--verbose') | 335 run_cmd.append('--verbose') |
| 333 manifest.add_task('Run Test', run_cmd) | 336 manifest.add_task('Run Test', run_cmd) |
| 334 | 337 |
| 335 # Clean up | 338 # Clean up |
| 336 manifest.add_task('Clean Up', ['python', cleanup_script_name]) | 339 manifest.add_task('Clean Up', ['python', cleanup_script_name]) |
| 337 | 340 |
| (...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 373 print >> sys.stderr, 'Archival failure %s' % file_hash_or_isolated | 376 print >> sys.stderr, 'Archival failure %s' % file_hash_or_isolated |
| 374 return 1 | 377 return 1 |
| 375 elif isolateserver.is_valid_hash(file_hash_or_isolated, algo): | 378 elif isolateserver.is_valid_hash(file_hash_or_isolated, algo): |
| 376 file_hash = file_hash_or_isolated | 379 file_hash = file_hash_or_isolated |
| 377 else: | 380 else: |
| 378 print >> sys.stderr, 'Invalid hash %s' % file_hash_or_isolated | 381 print >> sys.stderr, 'Invalid hash %s' % file_hash_or_isolated |
| 379 return 1 | 382 return 1 |
| 380 | 383 |
| 381 try: | 384 try: |
| 382 manifest = Manifest( | 385 manifest = Manifest( |
| 383 file_hash, test_name, shards, test_filter, slave_os, | 386 file_hash, |
| 384 working_dir, isolate_server, verbose, profile, priority, algo) | 387 test_name, |
| 388 shards, |
| 389 test_filter, |
| 390 PLATFORM_MAPPING_SWARMING[slave_os], |
| 391 working_dir, |
| 392 isolate_server, |
| 393 verbose, |
| 394 profile, |
| 395 priority, |
| 396 algo) |
| 385 except ValueError as e: | 397 except ValueError as e: |
| 386 print >> sys.stderr, 'Unable to process %s: %s' % (test_name, e) | 398 print >> sys.stderr, 'Unable to process %s: %s' % (test_name, e) |
| 387 return 1 | 399 return 1 |
| 388 | 400 |
| 389 chromium_setup(manifest) | 401 chromium_setup(manifest) |
| 390 | 402 |
| 391 # Zip up relevant files. | 403 # Zip up relevant files. |
| 392 print('Zipping up files...') | 404 print('Zipping up files...') |
| 393 if not manifest.zip_and_upload(): | 405 if not manifest.zip_and_upload(): |
| 394 return 1 | 406 return 1 |
| (...skipping 278 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 673 sys.stderr.write(str(e)) | 685 sys.stderr.write(str(e)) |
| 674 sys.stderr.write('\n') | 686 sys.stderr.write('\n') |
| 675 return 1 | 687 return 1 |
| 676 | 688 |
| 677 | 689 |
| 678 if __name__ == '__main__': | 690 if __name__ == '__main__': |
| 679 fix_encoding.fix_encoding() | 691 fix_encoding.fix_encoding() |
| 680 tools.disable_buffering() | 692 tools.disable_buffering() |
| 681 colorama.init() | 693 colorama.init() |
| 682 sys.exit(main(sys.argv[1:])) | 694 sys.exit(main(sys.argv[1:])) |
| OLD | NEW |