blob: ccde42e3bfe3737c64e9b31f60b75f4e52ab44e3 [file] [log] [blame]
maruel@chromium.org0437a732013-08-27 16:05:52 +00001#!/usr/bin/env python
Marc-Antoine Ruel8add1242013-11-05 17:28:27 -05002# Copyright 2013 The Swarming Authors. All rights reserved.
Marc-Antoine Ruele98b1122013-11-05 20:27:57 -05003# Use of this source code is governed under the Apache License, Version 2.0 that
4# can be found in the LICENSE file.
maruel@chromium.org0437a732013-08-27 16:05:52 +00005
6"""Client tool to trigger tasks or retrieve results from a Swarming server."""
7
Marc-Antoine Ruelb39e8cf2014-01-20 10:39:31 -05008__version__ = '0.4'
maruel@chromium.org0437a732013-08-27 16:05:52 +00009
10import hashlib
11import json
12import logging
13import os
maruel@chromium.org0437a732013-08-27 16:05:52 +000014import shutil
maruel@chromium.org0437a732013-08-27 16:05:52 +000015import subprocess
16import sys
17import time
18import urllib
maruel@chromium.org0437a732013-08-27 16:05:52 +000019
20from third_party import colorama
21from third_party.depot_tools import fix_encoding
22from third_party.depot_tools import subcommand
vadimsh@chromium.org6b706212013-08-28 15:03:46 +000023
24from utils import net
maruel@chromium.org0437a732013-08-27 16:05:52 +000025from utils import threading_utils
vadimsh@chromium.org6b706212013-08-28 15:03:46 +000026from utils import tools
27from utils import zip_package
maruel@chromium.org0437a732013-08-27 16:05:52 +000028
maruel@chromium.org7b844a62013-09-17 13:04:59 +000029import isolateserver
maruel@chromium.org0437a732013-08-27 16:05:52 +000030import run_isolated
31
32
33ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
34TOOLS_PATH = os.path.join(ROOT_DIR, 'tools')
35
36
maruel@chromium.org0437a732013-08-27 16:05:52 +000037# The default time to wait for a shard to finish running.
csharp@chromium.org24758492013-08-28 19:10:54 +000038DEFAULT_SHARD_WAIT_TIME = 80 * 60.
maruel@chromium.org0437a732013-08-27 16:05:52 +000039
40
41NO_OUTPUT_FOUND = (
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -050042 'No output produced by the task, it may have failed to run.\n'
maruel@chromium.org0437a732013-08-27 16:05:52 +000043 '\n')
44
45
maruel@chromium.org0437a732013-08-27 16:05:52 +000046class Failure(Exception):
47 """Generic failure."""
48 pass
49
50
51class Manifest(object):
52 """Represents a Swarming task manifest.
53
54 Also includes code to zip code and upload itself.
55 """
56 def __init__(
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -050057 self, isolate_server, isolated_hash, task_name, shards, env,
Marc-Antoine Ruel92f32422013-11-06 18:12:13 -050058 dimensions, working_dir, verbose, profile, priority, algo):
maruel@chromium.org0437a732013-08-27 16:05:52 +000059 """Populates a manifest object.
60 Args:
Marc-Antoine Ruela7049872013-11-05 19:28:35 -050061 isolate_server - isolate server url.
maruel@chromium.org814d23f2013-10-01 19:08:00 +000062 isolated_hash - The manifest's sha-1 that the slave is going to fetch.
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -050063 task_name - The name to give the task request.
64 shards - The number of swarming shards to request.
Marc-Antoine Ruel05dab5e2013-11-06 15:06:47 -050065 env - environment variables to set.
Marc-Antoine Ruel92f32422013-11-06 18:12:13 -050066 dimensions - dimensions to filter the task on.
maruel@chromium.org0437a732013-08-27 16:05:52 +000067 working_dir - Relative working directory to start the script.
maruel@chromium.org0437a732013-08-27 16:05:52 +000068 verbose - if True, have the slave print more details.
69 profile - if True, have the slave print more timing data.
maruel@chromium.org7b844a62013-09-17 13:04:59 +000070 priority - int between 0 and 1000, lower the higher priority.
71 algo - hashing algorithm used.
maruel@chromium.org0437a732013-08-27 16:05:52 +000072 """
Marc-Antoine Ruela7049872013-11-05 19:28:35 -050073 self.isolate_server = isolate_server
74 self.storage = isolateserver.get_storage(isolate_server, 'default')
75
maruel@chromium.org814d23f2013-10-01 19:08:00 +000076 self.isolated_hash = isolated_hash
vadimsh@chromium.org6b706212013-08-28 15:03:46 +000077 self.bundle = zip_package.ZipPackage(ROOT_DIR)
78
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -050079 self._task_name = task_name
maruel@chromium.org0437a732013-08-27 16:05:52 +000080 self._shards = shards
Marc-Antoine Ruel92f32422013-11-06 18:12:13 -050081 self._env = env.copy()
82 self._dimensions = dimensions.copy()
maruel@chromium.org0437a732013-08-27 16:05:52 +000083 self._working_dir = working_dir
84
maruel@chromium.org0437a732013-08-27 16:05:52 +000085 self.verbose = bool(verbose)
86 self.profile = bool(profile)
87 self.priority = priority
maruel@chromium.org7b844a62013-09-17 13:04:59 +000088 self._algo = algo
maruel@chromium.org0437a732013-08-27 16:05:52 +000089
vadimsh@chromium.orgf24e5c32013-10-11 21:16:21 +000090 self._isolate_item = None
maruel@chromium.org0437a732013-08-27 16:05:52 +000091 self._tasks = []
maruel@chromium.org0437a732013-08-27 16:05:52 +000092
93 def add_task(self, task_name, actions, time_out=600):
Marc-Antoine Ruelcd629732013-12-20 15:00:42 -050094 """Appends a new task to the swarming manifest file.
95
96 Tasks cannot be added once the manifest was uploaded.
97 """
98 assert not self._isolate_item
maruel@chromium.org0437a732013-08-27 16:05:52 +000099 # See swarming/src/common/test_request_message.py TestObject constructor for
100 # the valid flags.
101 self._tasks.append(
102 {
103 'action': actions,
104 'decorate_output': self.verbose,
105 'test_name': task_name,
106 'time_out': time_out,
107 })
108
maruel@chromium.org0437a732013-08-27 16:05:52 +0000109 def to_json(self):
110 """Exports the current configuration into a swarm-readable manifest file.
111
112 This function doesn't mutate the object.
113 """
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500114 request = {
Marc-Antoine Ruela7049872013-11-05 19:28:35 -0500115 'cleanup': 'root',
maruel@chromium.org0437a732013-08-27 16:05:52 +0000116 'configurations': [
117 {
Marc-Antoine Ruel5d799192013-11-06 15:20:39 -0500118 'config_name': 'isolated',
Marc-Antoine Ruel92f32422013-11-06 18:12:13 -0500119 'dimensions': self._dimensions,
Marc-Antoine Ruela7049872013-11-05 19:28:35 -0500120 'min_instances': self._shards,
121 'priority': self.priority,
maruel@chromium.org0437a732013-08-27 16:05:52 +0000122 },
123 ],
Marc-Antoine Ruela7049872013-11-05 19:28:35 -0500124 'data': [],
125 # TODO: Let the encoding get set from the command line.
126 'encoding': 'UTF-8',
Marc-Antoine Ruel05dab5e2013-11-06 15:06:47 -0500127 'env_vars': self._env,
maruel@chromium.org0437a732013-08-27 16:05:52 +0000128 'restart_on_failure': True,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500129 'test_case_name': self._task_name,
Marc-Antoine Ruela7049872013-11-05 19:28:35 -0500130 'tests': self._tasks,
131 'working_dir': self._working_dir,
maruel@chromium.org0437a732013-08-27 16:05:52 +0000132 }
vadimsh@chromium.orgf24e5c32013-10-11 21:16:21 +0000133 if self._isolate_item:
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500134 request['data'].append(
maruel@chromium.org814d23f2013-10-01 19:08:00 +0000135 [
vadimsh@chromium.orgf24e5c32013-10-11 21:16:21 +0000136 self.storage.get_fetch_url(self._isolate_item.digest),
maruel@chromium.org814d23f2013-10-01 19:08:00 +0000137 'swarm_data.zip',
138 ])
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500139 return json.dumps(request, sort_keys=True, separators=(',',':'))
maruel@chromium.org0437a732013-08-27 16:05:52 +0000140
Marc-Antoine Ruelcd629732013-12-20 15:00:42 -0500141 @property
142 def isolate_item(self):
143 """Calling this property 'closes' the manifest and it can't be modified
144 afterward.
145 """
146 if self._isolate_item is None:
147 self._isolate_item = isolateserver.BufferItem(
148 self.bundle.zip_into_buffer(), self._algo, is_isolated=True)
149 return self._isolate_item
150
151
152def zip_and_upload(manifest):
153 """Zips up all the files necessary to run a manifest and uploads to Swarming
154 master.
155 """
156 try:
157 start_time = time.time()
158 with manifest.storage:
159 uploaded = manifest.storage.upload_items([manifest.isolate_item])
160 elapsed = time.time() - start_time
161 except (IOError, OSError) as exc:
162 tools.report_error('Failed to upload the zip file: %s' % exc)
163 return False
164
165 if manifest.isolate_item in uploaded:
166 logging.info('Upload complete, time elapsed: %f', elapsed)
167 else:
168 logging.info('Zip file already on server, time elapsed: %f', elapsed)
169 return True
170
maruel@chromium.org0437a732013-08-27 16:05:52 +0000171
172def now():
173 """Exists so it can be mocked easily."""
174 return time.time()
175
176
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500177def get_task_keys(swarm_base_url, task_name):
178 """Returns the Swarming task key for each shards of task_name."""
179 key_data = urllib.urlencode([('name', task_name)])
maruel@chromium.org0437a732013-08-27 16:05:52 +0000180 url = '%s/get_matching_test_cases?%s' % (swarm_base_url, key_data)
181
vadimsh@chromium.org043b76d2013-09-12 16:15:13 +0000182 for _ in net.retry_loop(max_attempts=net.URL_OPEN_MAX_ATTEMPTS):
183 result = net.url_read(url, retry_404=True)
184 if result is None:
maruel@chromium.org0437a732013-08-27 16:05:52 +0000185 raise Failure(
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500186 'Error: Unable to find any task with the name, %s, on swarming server'
187 % task_name)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000188
maruel@chromium.org0437a732013-08-27 16:05:52 +0000189 # TODO(maruel): Compare exact string.
190 if 'No matching' in result:
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500191 logging.warning('Unable to find any task with the name, %s, on swarming '
192 'server' % task_name)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000193 continue
194 return json.loads(result)
195
196 raise Failure(
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500197 'Error: Unable to find any task with the name, %s, on swarming server'
198 % task_name)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000199
200
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500201def retrieve_results(base_url, task_key, timeout, should_stop):
202 """Retrieves results for a single task_key."""
maruel@chromium.org814d23f2013-10-01 19:08:00 +0000203 assert isinstance(timeout, float), timeout
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500204 params = [('r', task_key)]
maruel@chromium.org0437a732013-08-27 16:05:52 +0000205 result_url = '%s/get_result?%s' % (base_url, urllib.urlencode(params))
206 start = now()
207 while True:
208 if timeout and (now() - start) >= timeout:
209 logging.error('retrieve_results(%s) timed out', base_url)
210 return {}
211 # Do retries ourselves.
vadimsh@chromium.org043b76d2013-09-12 16:15:13 +0000212 response = net.url_read(result_url, retry_404=False, retry_50x=False)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000213 if response is None:
214 # Aggressively poll for results. Do not use retry_404 so
215 # should_stop is polled more often.
216 remaining = min(5, timeout - (now() - start)) if timeout else 5
217 if remaining > 0:
maruel@chromium.org814d23f2013-10-01 19:08:00 +0000218 if should_stop.get():
219 return {}
vadimsh@chromium.org043b76d2013-09-12 16:15:13 +0000220 net.sleep_before_retry(1, remaining)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000221 else:
222 try:
vadimsh@chromium.org043b76d2013-09-12 16:15:13 +0000223 data = json.loads(response) or {}
maruel@chromium.org0437a732013-08-27 16:05:52 +0000224 except (ValueError, TypeError):
225 logging.warning(
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500226 'Received corrupted data for task_key %s. Retrying.', task_key)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000227 else:
228 if data['output']:
229 return data
230 if should_stop.get():
231 return {}
232
233
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500234def yield_results(swarm_base_url, task_keys, timeout, max_threads):
235 """Yields swarming task results from the swarming server as (index, result).
maruel@chromium.org0437a732013-08-27 16:05:52 +0000236
237 Duplicate shards are ignored, the first one to complete is returned.
238
239 max_threads is optional and is used to limit the number of parallel fetches
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500240 done. Since in general the number of task_keys is in the range <=10, it's not
maruel@chromium.org0437a732013-08-27 16:05:52 +0000241 worth normally to limit the number threads. Mostly used for testing purposes.
242 """
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500243 shards_remaining = range(len(task_keys))
maruel@chromium.org0437a732013-08-27 16:05:52 +0000244 number_threads = (
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500245 min(max_threads, len(task_keys)) if max_threads else len(task_keys))
maruel@chromium.org0437a732013-08-27 16:05:52 +0000246 should_stop = threading_utils.Bit()
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500247 results_remaining = len(task_keys)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000248 with threading_utils.ThreadPool(number_threads, number_threads, 0) as pool:
249 try:
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500250 for task_key in task_keys:
maruel@chromium.org0437a732013-08-27 16:05:52 +0000251 pool.add_task(
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500252 0, retrieve_results, swarm_base_url, task_key, timeout, should_stop)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000253 while shards_remaining and results_remaining:
254 result = pool.get_one_result()
255 results_remaining -= 1
256 if not result:
257 # Failed to retrieve one key.
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500258 logging.error('Failed to retrieve the results for a swarming key')
maruel@chromium.org0437a732013-08-27 16:05:52 +0000259 continue
260 shard_index = result['config_instance_index']
261 if shard_index in shards_remaining:
262 shards_remaining.remove(shard_index)
263 yield shard_index, result
264 else:
265 logging.warning('Ignoring duplicate shard index %d', shard_index)
266 # Pop the last entry, there's no such shard.
267 shards_remaining.pop()
268 finally:
269 # Done, kill the remaining threads.
270 should_stop.set()
271
272
273def chromium_setup(manifest):
274 """Sets up the commands to run.
275
276 Highly chromium specific.
277 """
vadimsh@chromium.org6b706212013-08-28 15:03:46 +0000278 # Add uncompressed zip here. It'll be compressed as part of the package sent
279 # to Swarming server.
280 run_test_name = 'run_isolated.zip'
281 manifest.bundle.add_buffer(run_test_name,
282 run_isolated.get_as_zip_package().zip_into_buffer(compress=False))
maruel@chromium.org0437a732013-08-27 16:05:52 +0000283
vadimsh@chromium.org6b706212013-08-28 15:03:46 +0000284 cleanup_script_name = 'swarm_cleanup.py'
285 manifest.bundle.add_file(os.path.join(TOOLS_PATH, cleanup_script_name),
286 cleanup_script_name)
287
maruel@chromium.org0437a732013-08-27 16:05:52 +0000288 run_cmd = [
289 'python', run_test_name,
maruel@chromium.org814d23f2013-10-01 19:08:00 +0000290 '--hash', manifest.isolated_hash,
maruel@chromium.orgb7e79a22013-09-13 01:24:56 +0000291 '--isolate-server', manifest.isolate_server,
maruel@chromium.org0437a732013-08-27 16:05:52 +0000292 ]
293 if manifest.verbose or manifest.profile:
294 # Have it print the profiling section.
295 run_cmd.append('--verbose')
296 manifest.add_task('Run Test', run_cmd)
297
298 # Clean up
299 manifest.add_task('Clean Up', ['python', cleanup_script_name])
300
301
Marc-Antoine Ruelcd629732013-12-20 15:00:42 -0500302def googletest_setup(env, shards):
303 """Sets googletest specific environment variables."""
304 if shards > 1:
305 env = env.copy()
306 env['GTEST_SHARD_INDEX'] = '%(instance_index)s'
307 env['GTEST_TOTAL_SHARDS'] = '%(num_instances)s'
308 return env
309
310
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500311def archive(isolate_server, isolated, algo, verbose):
maruel@chromium.org0437a732013-08-27 16:05:52 +0000312 """Archives a .isolated and all the dependencies on the CAC."""
313 tempdir = None
314 try:
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500315 logging.info('archive(%s, %s)', isolate_server, isolated)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000316 cmd = [
317 sys.executable,
318 os.path.join(ROOT_DIR, 'isolate.py'),
maruel@chromium.orge9403ab2013-09-20 18:03:49 +0000319 'archive',
maruel@chromium.org0437a732013-08-27 16:05:52 +0000320 '--outdir', isolate_server,
321 '--isolated', isolated,
322 ]
maruel@chromium.orge9403ab2013-09-20 18:03:49 +0000323 cmd.extend(['--verbose'] * verbose)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000324 logging.info(' '.join(cmd))
325 if subprocess.call(cmd, verbose):
326 return
maruel@chromium.org7b844a62013-09-17 13:04:59 +0000327 return isolateserver.hash_file(isolated, algo)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000328 finally:
329 if tempdir:
330 shutil.rmtree(tempdir)
331
332
333def process_manifest(
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500334 swarming, isolate_server, isolated_hash, task_name, shards,
Marc-Antoine Ruel92f32422013-11-06 18:12:13 -0500335 dimensions, env, working_dir, verbose, profile, priority, algo):
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500336 """Processes the manifest file and send off the swarming task request."""
maruel@chromium.org0437a732013-08-27 16:05:52 +0000337 try:
338 manifest = Manifest(
Marc-Antoine Ruela7049872013-11-05 19:28:35 -0500339 isolate_server=isolate_server,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500340 isolated_hash=isolated_hash,
341 task_name=task_name,
Marc-Antoine Ruela7049872013-11-05 19:28:35 -0500342 shards=shards,
Marc-Antoine Ruel92f32422013-11-06 18:12:13 -0500343 dimensions=dimensions,
Marc-Antoine Ruel05dab5e2013-11-06 15:06:47 -0500344 env=env,
Marc-Antoine Ruela7049872013-11-05 19:28:35 -0500345 working_dir=working_dir,
346 verbose=verbose,
347 profile=profile,
348 priority=priority,
349 algo=algo)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000350 except ValueError as e:
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500351 tools.report_error('Unable to process %s: %s' % (task_name, e))
maruel@chromium.org0437a732013-08-27 16:05:52 +0000352 return 1
353
354 chromium_setup(manifest)
355
Marc-Antoine Ruelcd629732013-12-20 15:00:42 -0500356 logging.info('Zipping up files...')
357 if not zip_and_upload(manifest):
maruel@chromium.org0437a732013-08-27 16:05:52 +0000358 return 1
359
Marc-Antoine Ruelcd629732013-12-20 15:00:42 -0500360 logging.info('Server: %s', swarming)
361 logging.info('Task name: %s', task_name)
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500362 trigger_url = swarming + '/test'
maruel@chromium.org0437a732013-08-27 16:05:52 +0000363 manifest_text = manifest.to_json()
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500364 result = net.url_read(trigger_url, data={'request': manifest_text})
maruel@chromium.org0437a732013-08-27 16:05:52 +0000365 if not result:
vadimsh@chromium.orgd908a542013-10-30 01:36:17 +0000366 tools.report_error(
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500367 'Failed to trigger task %s\n%s' % (task_name, trigger_url))
maruel@chromium.org0437a732013-08-27 16:05:52 +0000368 return 1
369 try:
vadimsh@chromium.orgf24e5c32013-10-11 21:16:21 +0000370 json.loads(result)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000371 except (ValueError, TypeError) as e:
vadimsh@chromium.orgd908a542013-10-30 01:36:17 +0000372 msg = '\n'.join((
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500373 'Failed to trigger task %s' % task_name,
vadimsh@chromium.orgd908a542013-10-30 01:36:17 +0000374 'Manifest: %s' % manifest_text,
375 'Bad response: %s' % result,
376 str(e)))
377 tools.report_error(msg)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000378 return 1
379 return 0
380
381
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500382def isolated_to_hash(isolate_server, arg, algo, verbose):
383 """Archives a .isolated file if needed.
384
385 Returns the file hash to trigger.
386 """
387 if arg.endswith('.isolated'):
388 file_hash = archive(isolate_server, arg, algo, verbose)
389 if not file_hash:
390 tools.report_error('Archival failure %s' % arg)
391 return None
392 return file_hash
393 elif isolateserver.is_valid_hash(arg, algo):
394 return arg
395 else:
396 tools.report_error('Invalid hash %s' % arg)
397 return None
398
399
maruel@chromium.org0437a732013-08-27 16:05:52 +0000400def trigger(
Marc-Antoine Ruela7049872013-11-05 19:28:35 -0500401 swarming,
402 isolate_server,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500403 file_hash_or_isolated,
404 task_name,
405 shards,
Marc-Antoine Ruel92f32422013-11-06 18:12:13 -0500406 dimensions,
407 env,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500408 working_dir,
maruel@chromium.org0437a732013-08-27 16:05:52 +0000409 verbose,
410 profile,
411 priority):
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500412 """Sends off the hash swarming task requests."""
413 file_hash = isolated_to_hash(
414 isolate_server, file_hash_or_isolated, hashlib.sha1, verbose)
415 if not file_hash:
416 return 1
Marc-Antoine Ruelcd629732013-12-20 15:00:42 -0500417 env = googletest_setup(env, shards)
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500418 # TODO(maruel): It should first create a request manifest object, then pass
419 # it to a function to zip, archive and trigger.
420 return process_manifest(
421 swarming=swarming,
422 isolate_server=isolate_server,
423 isolated_hash=file_hash,
424 task_name=task_name,
425 shards=shards,
426 dimensions=dimensions,
427 env=env,
428 working_dir=working_dir,
429 verbose=verbose,
430 profile=profile,
431 priority=priority,
432 algo=hashlib.sha1)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000433
434
435def decorate_shard_output(result, shard_exit_code):
436 """Returns wrapped output for swarming task shard."""
437 tag = 'index %s (machine tag: %s, id: %s)' % (
438 result['config_instance_index'],
439 result['machine_id'],
440 result.get('machine_tag', 'unknown'))
441 return (
442 '\n'
443 '================================================================\n'
444 'Begin output from shard %s\n'
445 '================================================================\n'
446 '\n'
447 '%s'
448 '================================================================\n'
449 'End output from shard %s. Return %d\n'
450 '================================================================\n'
451 ) % (tag, result['output'] or NO_OUTPUT_FOUND, tag, shard_exit_code)
452
453
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500454def collect(url, task_name, timeout, decorate):
455 """Retrieves results of a Swarming task."""
456 logging.info('Collecting %s', task_name)
457 task_keys = get_task_keys(url, task_name)
458 if not task_keys:
459 raise Failure('No task keys to get results with.')
maruel@chromium.org0437a732013-08-27 16:05:52 +0000460
maruel@chromium.org9c1c7b52013-08-28 19:04:36 +0000461 exit_code = None
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500462 for _index, output in yield_results(url, task_keys, timeout, None):
maruel@chromium.org0437a732013-08-27 16:05:52 +0000463 shard_exit_codes = (output['exit_codes'] or '1').split(',')
464 shard_exit_code = max(int(i) for i in shard_exit_codes)
465 if decorate:
466 print decorate_shard_output(output, shard_exit_code)
467 else:
468 print(
469 '%s/%s: %s' % (
470 output['machine_id'],
471 output['machine_tag'],
472 output['exit_codes']))
473 print(''.join(' %s\n' % l for l in output['output'].splitlines()))
maruel@chromium.org9c1c7b52013-08-28 19:04:36 +0000474 exit_code = exit_code or shard_exit_code
475 return exit_code if exit_code is not None else 1
maruel@chromium.org0437a732013-08-27 16:05:52 +0000476
477
478def add_trigger_options(parser):
479 """Adds all options to trigger a task on Swarming."""
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500480 parser.server_group.add_option(
maruel@chromium.org0437a732013-08-27 16:05:52 +0000481 '-I', '--isolate-server',
Kevin Graney5346c162014-01-24 12:20:01 -0500482 metavar='URL', default=os.environ.get('ISOLATE_SERVER', ''),
maruel@chromium.orge9403ab2013-09-20 18:03:49 +0000483 help='Isolate server to use')
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500484
485 parser.filter_group = tools.optparse.OptionGroup(parser, 'Filtering slaves')
486 parser.filter_group.add_option(
Marc-Antoine Ruelb39e8cf2014-01-20 10:39:31 -0500487 '-d', '--dimension', default=[], action='append', nargs=2,
Marc-Antoine Ruel92f32422013-11-06 18:12:13 -0500488 dest='dimensions', metavar='FOO bar',
489 help='dimension to filter on')
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500490 parser.add_option_group(parser.filter_group)
491
492 parser.task_group = tools.optparse.OptionGroup(parser, 'Task properties')
493 parser.task_group.add_option(
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500494 '-w', '--working-dir', default='swarm_tests',
495 help='Working directory on the swarming slave side. default: %default.')
496 parser.task_group.add_option(
497 '--working_dir', help=tools.optparse.SUPPRESS_HELP)
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500498 parser.task_group.add_option(
499 '-e', '--env', default=[], action='append', nargs=2, metavar='FOO bar',
500 help='environment variables to set')
501 parser.task_group.add_option(
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500502 '--priority', type='int', default=100,
503 help='The lower value, the more important the task is')
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500504 parser.task_group.add_option(
505 '--shards', type='int', default=1, help='number of shards to use')
506 parser.task_group.add_option(
507 '-T', '--task-name', help='display name of the task')
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500508 parser.add_option_group(parser.task_group)
Marc-Antoine Ruelcd629732013-12-20 15:00:42 -0500509 # TODO(maruel): This is currently written in a chromium-specific way.
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500510 parser.group_logging.add_option(
maruel@chromium.org0437a732013-08-27 16:05:52 +0000511 '--profile', action='store_true',
512 default=bool(os.environ.get('ISOLATE_DEBUG')),
513 help='Have run_isolated.py print profiling info')
maruel@chromium.org0437a732013-08-27 16:05:52 +0000514
515
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500516def process_trigger_options(parser, options, args):
maruel@chromium.org0437a732013-08-27 16:05:52 +0000517 options.isolate_server = options.isolate_server.rstrip('/')
518 if not options.isolate_server:
519 parser.error('--isolate-server is required.')
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500520 if len(args) != 1:
521 parser.error('Must pass one .isolated file or its hash (sha1).')
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500522 options.dimensions = dict(options.dimensions)
Marc-Antoine Ruelb39e8cf2014-01-20 10:39:31 -0500523 if not options.dimensions.get('os'):
524 parser.error(
525 'Please at least specify the dimension of the swarming bot OS with '
526 '--dimension os <something>.')
maruel@chromium.org0437a732013-08-27 16:05:52 +0000527
528
529def add_collect_options(parser):
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500530 parser.server_group.add_option(
maruel@chromium.org0437a732013-08-27 16:05:52 +0000531 '-t', '--timeout',
532 type='float',
533 default=DEFAULT_SHARD_WAIT_TIME,
534 help='Timeout to wait for result, set to 0 for no timeout; default: '
535 '%default s')
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500536 parser.group_logging.add_option(
537 '--decorate', action='store_true', help='Decorate output')
maruel@chromium.org0437a732013-08-27 16:05:52 +0000538
539
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500540@subcommand.usage('task_name')
maruel@chromium.org0437a732013-08-27 16:05:52 +0000541def CMDcollect(parser, args):
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500542 """Retrieves results of a Swarming task.
maruel@chromium.org0437a732013-08-27 16:05:52 +0000543
544 The result can be in multiple part if the execution was sharded. It can
545 potentially have retries.
546 """
547 add_collect_options(parser)
548 (options, args) = parser.parse_args(args)
549 if not args:
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500550 parser.error('Must specify one task name.')
maruel@chromium.org0437a732013-08-27 16:05:52 +0000551 elif len(args) > 1:
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500552 parser.error('Must specify only one task name.')
maruel@chromium.org0437a732013-08-27 16:05:52 +0000553
554 try:
555 return collect(options.swarming, args[0], options.timeout, options.decorate)
556 except Failure as e:
vadimsh@chromium.orgd908a542013-10-30 01:36:17 +0000557 tools.report_error(e)
558 return 1
maruel@chromium.org0437a732013-08-27 16:05:52 +0000559
560
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500561@subcommand.usage('[hash|isolated]')
maruel@chromium.org0437a732013-08-27 16:05:52 +0000562def CMDrun(parser, args):
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500563 """Triggers a task and wait for the results.
maruel@chromium.org0437a732013-08-27 16:05:52 +0000564
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500565 Basically, does everything to run a command remotely.
maruel@chromium.org0437a732013-08-27 16:05:52 +0000566 """
567 add_trigger_options(parser)
568 add_collect_options(parser)
569 options, args = parser.parse_args(args)
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500570 process_trigger_options(parser, options, args)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000571
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500572 try:
573 result = trigger(
574 swarming=options.swarming,
575 isolate_server=options.isolate_server,
576 file_hash_or_isolated=args[0],
577 task_name=options.task_name,
578 shards=options.shards,
579 dimensions=options.dimensions,
580 env=dict(options.env),
581 working_dir=options.working_dir,
582 verbose=options.verbose,
583 profile=options.profile,
584 priority=options.priority)
585 except Failure as e:
586 tools.report_error(
587 'Failed to trigger %s(%s): %s' %
588 (options.task_name, args[0], e.args[0]))
589 return 1
590 if result:
591 tools.report_error('Failed to trigger the task.')
maruel@chromium.org0437a732013-08-27 16:05:52 +0000592 return result
593
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500594 try:
595 return collect(
596 options.swarming,
597 options.task_name,
598 options.timeout,
599 options.decorate)
600 except Failure as e:
601 tools.report_error(e)
602 return 1
maruel@chromium.org0437a732013-08-27 16:05:52 +0000603
604
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500605@subcommand.usage("(hash|isolated)")
maruel@chromium.org0437a732013-08-27 16:05:52 +0000606def CMDtrigger(parser, args):
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500607 """Triggers a Swarming task.
maruel@chromium.org0437a732013-08-27 16:05:52 +0000608
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500609 Accepts either the hash (sha1) of a .isolated file already uploaded or the
610 path to an .isolated file to archive, packages it if needed and sends a
611 Swarming manifest file to the Swarming server.
612
613 If an .isolated file is specified instead of an hash, it is first archived.
maruel@chromium.org0437a732013-08-27 16:05:52 +0000614 """
615 add_trigger_options(parser)
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500616 options, args = parser.parse_args(args)
617 process_trigger_options(parser, options, args)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000618
619 try:
620 return trigger(
Marc-Antoine Ruela7049872013-11-05 19:28:35 -0500621 swarming=options.swarming,
622 isolate_server=options.isolate_server,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500623 file_hash_or_isolated=args[0],
624 task_name=options.task_name,
625 dimensions=options.dimensions,
626 shards=options.shards,
Marc-Antoine Ruel92f32422013-11-06 18:12:13 -0500627 env=dict(options.env),
Marc-Antoine Ruela7049872013-11-05 19:28:35 -0500628 working_dir=options.working_dir,
629 verbose=options.verbose,
630 profile=options.profile,
631 priority=options.priority)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000632 except Failure as e:
vadimsh@chromium.orgd908a542013-10-30 01:36:17 +0000633 tools.report_error(e)
634 return 1
maruel@chromium.org0437a732013-08-27 16:05:52 +0000635
636
637class OptionParserSwarming(tools.OptionParserWithLogging):
638 def __init__(self, **kwargs):
639 tools.OptionParserWithLogging.__init__(
640 self, prog='swarming.py', **kwargs)
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500641 self.server_group = tools.optparse.OptionGroup(self, 'Server')
642 self.server_group.add_option(
maruel@chromium.orge9403ab2013-09-20 18:03:49 +0000643 '-S', '--swarming',
Kevin Graney5346c162014-01-24 12:20:01 -0500644 metavar='URL', default=os.environ.get('SWARMING_SERVER', ''),
maruel@chromium.orge9403ab2013-09-20 18:03:49 +0000645 help='Swarming server to use')
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500646 self.add_option_group(self.server_group)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000647
648 def parse_args(self, *args, **kwargs):
649 options, args = tools.OptionParserWithLogging.parse_args(
650 self, *args, **kwargs)
651 options.swarming = options.swarming.rstrip('/')
652 if not options.swarming:
653 self.error('--swarming is required.')
654 return options, args
655
656
657def main(args):
658 dispatcher = subcommand.CommandDispatcher(__name__)
659 try:
660 return dispatcher.execute(OptionParserSwarming(version=__version__), args)
vadimsh@chromium.orgd908a542013-10-30 01:36:17 +0000661 except Exception as e:
662 tools.report_error(e)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000663 return 1
664
665
666if __name__ == '__main__':
667 fix_encoding.fix_encoding()
668 tools.disable_buffering()
669 colorama.init()
670 sys.exit(main(sys.argv[1:]))