blob: 275fba5b7a55fbac61792693a41af67a0c99c6b1 [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 Ruelfe844672014-07-31 11:16:51 -04008__version__ = '0.4.13'
maruel@chromium.org0437a732013-08-27 16:05:52 +00009
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -050010import getpass
maruel@chromium.org0437a732013-08-27 16:05:52 +000011import hashlib
12import json
13import logging
14import os
Vadim Shtayurae3fbd102014-04-29 17:05:21 -070015import re
maruel@chromium.org0437a732013-08-27 16:05:52 +000016import shutil
maruel@chromium.org0437a732013-08-27 16:05:52 +000017import subprocess
18import sys
Vadim Shtayurab19319e2014-04-27 08:50:06 -070019import threading
maruel@chromium.org0437a732013-08-27 16:05:52 +000020import time
21import urllib
maruel@chromium.org0437a732013-08-27 16:05:52 +000022
23from third_party import colorama
24from third_party.depot_tools import fix_encoding
25from third_party.depot_tools import subcommand
vadimsh@chromium.org6b706212013-08-28 15:03:46 +000026
Marc-Antoine Ruel8806e622014-02-12 14:15:53 -050027from utils import file_path
Marc-Antoine Ruel819fb162014-03-12 16:38:26 -040028from third_party.chromium import natsort
vadimsh@chromium.org6b706212013-08-28 15:03:46 +000029from utils import net
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -040030from utils import on_error
maruel@chromium.org0437a732013-08-27 16:05:52 +000031from utils import threading_utils
vadimsh@chromium.org6b706212013-08-28 15:03:46 +000032from utils import tools
33from utils import zip_package
maruel@chromium.org0437a732013-08-27 16:05:52 +000034
Vadim Shtayurae34e13a2014-02-02 11:23:26 -080035import auth
maruel@chromium.org7b844a62013-09-17 13:04:59 +000036import isolateserver
maruel@chromium.org0437a732013-08-27 16:05:52 +000037import run_isolated
38
39
40ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
41TOOLS_PATH = os.path.join(ROOT_DIR, 'tools')
42
43
maruel@chromium.org0437a732013-08-27 16:05:52 +000044# The default time to wait for a shard to finish running.
csharp@chromium.org24758492013-08-28 19:10:54 +000045DEFAULT_SHARD_WAIT_TIME = 80 * 60.
maruel@chromium.org0437a732013-08-27 16:05:52 +000046
Vadim Shtayura86a2cef2014-04-18 11:13:39 -070047# How often to print status updates to stdout in 'collect'.
48STATUS_UPDATE_INTERVAL = 15 * 60.
49
maruel@chromium.org0437a732013-08-27 16:05:52 +000050
51NO_OUTPUT_FOUND = (
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -050052 'No output produced by the task, it may have failed to run.\n'
maruel@chromium.org0437a732013-08-27 16:05:52 +000053 '\n')
54
55
maruel@chromium.org0437a732013-08-27 16:05:52 +000056class Failure(Exception):
57 """Generic failure."""
58 pass
59
60
61class Manifest(object):
Vadim Shtayurab450c602014-05-12 19:23:25 -070062 """Represents a Swarming task manifest."""
maruel@chromium.org0437a732013-08-27 16:05:52 +000063
maruel@chromium.org0437a732013-08-27 16:05:52 +000064 def __init__(
Vadim Shtayuraae8085b2014-05-02 17:13:10 -070065 self, isolate_server, namespace, isolated_hash, task_name, extra_args,
Marc-Antoine Ruelaea50652014-06-12 14:23:48 -040066 env, dimensions, deadline, verbose, profile,
Vadim Shtayuraae8085b2014-05-02 17:13:10 -070067 priority):
maruel@chromium.org0437a732013-08-27 16:05:52 +000068 """Populates a manifest object.
69 Args:
Marc-Antoine Ruela7049872013-11-05 19:28:35 -050070 isolate_server - isolate server url.
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -050071 namespace - isolate server namespace to use.
Vadim Shtayuraae8085b2014-05-02 17:13:10 -070072 isolated_hash - the manifest's sha-1 that the slave is going to fetch.
73 task_name - the name to give the task request.
74 extra_args - additional arguments to pass to isolated command.
Marc-Antoine Ruel05dab5e2013-11-06 15:06:47 -050075 env - environment variables to set.
Marc-Antoine Ruel92f32422013-11-06 18:12:13 -050076 dimensions - dimensions to filter the task on.
Marc-Antoine Ruel13b7b782014-03-14 11:14:57 -040077 deadline - maximum pending time before this task expires.
maruel@chromium.org0437a732013-08-27 16:05:52 +000078 verbose - if True, have the slave print more details.
79 profile - if True, have the slave print more timing data.
maruel@chromium.org7b844a62013-09-17 13:04:59 +000080 priority - int between 0 and 1000, lower the higher priority.
maruel@chromium.org0437a732013-08-27 16:05:52 +000081 """
Marc-Antoine Ruela7049872013-11-05 19:28:35 -050082 self.isolate_server = isolate_server
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -050083 self.namespace = namespace
maruel@chromium.org814d23f2013-10-01 19:08:00 +000084 self.isolated_hash = isolated_hash
Vadim Shtayurab450c602014-05-12 19:23:25 -070085 self.task_name = task_name
Vadim Shtayuraae8085b2014-05-02 17:13:10 -070086 self.extra_args = tuple(extra_args or [])
Vadim Shtayurab450c602014-05-12 19:23:25 -070087 self.env = env.copy()
88 self.dimensions = dimensions.copy()
Vadim Shtayurab450c602014-05-12 19:23:25 -070089 self.deadline = deadline
maruel@chromium.org0437a732013-08-27 16:05:52 +000090 self.verbose = bool(verbose)
91 self.profile = bool(profile)
92 self.priority = priority
maruel@chromium.org0437a732013-08-27 16:05:52 +000093 self._tasks = []
Vadim Shtayurab450c602014-05-12 19:23:25 -070094 self._files = []
maruel@chromium.org0437a732013-08-27 16:05:52 +000095
Marc-Antoine Ruelaf78a902014-03-20 10:42:49 -040096 def add_task(self, task_name, actions, time_out=2*60*60):
Marc-Antoine Ruel5c720342014-02-21 14:46:14 -050097 """Appends a new task as a TestObject to the swarming manifest file.
Marc-Antoine Ruelcd629732013-12-20 15:00:42 -050098
99 Tasks cannot be added once the manifest was uploaded.
Marc-Antoine Ruel5c720342014-02-21 14:46:14 -0500100
Marc-Antoine Ruelaf78a902014-03-20 10:42:49 -0400101 By default, command will be killed after 2 hours of execution.
102
Marc-Antoine Ruel5c720342014-02-21 14:46:14 -0500103 See TestObject in services/swarming/src/common/test_request_message.py for
104 the valid format.
Marc-Antoine Ruelcd629732013-12-20 15:00:42 -0500105 """
maruel@chromium.org0437a732013-08-27 16:05:52 +0000106 self._tasks.append(
107 {
108 'action': actions,
109 'decorate_output': self.verbose,
110 'test_name': task_name,
Marc-Antoine Ruelaf78a902014-03-20 10:42:49 -0400111 'hard_time_out': time_out,
maruel@chromium.org0437a732013-08-27 16:05:52 +0000112 })
113
Vadim Shtayurab450c602014-05-12 19:23:25 -0700114 def add_bundled_file(self, file_name, file_url):
115 """Appends a file to the manifest.
116
117 File will be downloaded and extracted by the swarm bot before launching the
118 task.
119 """
120 self._files.append([file_url, file_name])
121
maruel@chromium.org0437a732013-08-27 16:05:52 +0000122 def to_json(self):
123 """Exports the current configuration into a swarm-readable manifest file.
124
Marc-Antoine Ruel5c720342014-02-21 14:46:14 -0500125 The actual serialization format is defined as a TestCase object as described
126 in services/swarming/src/common/test_request_message.py
maruel@chromium.org0437a732013-08-27 16:05:52 +0000127 """
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500128 request = {
Marc-Antoine Ruela7049872013-11-05 19:28:35 -0500129 'cleanup': 'root',
maruel@chromium.org0437a732013-08-27 16:05:52 +0000130 'configurations': [
Marc-Antoine Ruel5c720342014-02-21 14:46:14 -0500131 # Is a TestConfiguration.
maruel@chromium.org0437a732013-08-27 16:05:52 +0000132 {
Marc-Antoine Ruel5d799192013-11-06 15:20:39 -0500133 'config_name': 'isolated',
Vadim Shtayurab450c602014-05-12 19:23:25 -0700134 'deadline_to_run': self.deadline,
135 'dimensions': self.dimensions,
Marc-Antoine Ruela7049872013-11-05 19:28:35 -0500136 'priority': self.priority,
maruel@chromium.org0437a732013-08-27 16:05:52 +0000137 },
138 ],
Vadim Shtayurab450c602014-05-12 19:23:25 -0700139 'data': self._files,
Vadim Shtayurab450c602014-05-12 19:23:25 -0700140 'env_vars': self.env,
Vadim Shtayurab450c602014-05-12 19:23:25 -0700141 'test_case_name': self.task_name,
Marc-Antoine Ruela7049872013-11-05 19:28:35 -0500142 'tests': self._tasks,
maruel@chromium.org0437a732013-08-27 16:05:52 +0000143 }
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500144 return json.dumps(request, sort_keys=True, separators=(',',':'))
maruel@chromium.org0437a732013-08-27 16:05:52 +0000145
Marc-Antoine Ruelcd629732013-12-20 15:00:42 -0500146
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700147class TaskOutputCollector(object):
Vadim Shtayurac8437bf2014-07-09 19:45:36 -0700148 """Assembles task execution summary (for --task-summary-json output).
149
150 Optionally fetches task outputs from isolate server to local disk (used when
151 --task-output-dir is passed).
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700152
153 This object is shared among multiple threads running 'retrieve_results'
154 function, in particular they call 'process_shard_result' method in parallel.
155 """
156
157 def __init__(self, task_output_dir, task_name, shard_count):
158 """Initializes TaskOutputCollector, ensures |task_output_dir| exists.
159
160 Args:
Vadim Shtayurac8437bf2014-07-09 19:45:36 -0700161 task_output_dir: (optional) local directory to put fetched files to.
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700162 task_name: name of the swarming task results belong to.
163 shard_count: expected number of task shards.
164 """
165 self.task_output_dir = task_output_dir
166 self.task_name = task_name
167 self.shard_count = shard_count
168
169 self._lock = threading.Lock()
170 self._per_shard_results = {}
171 self._storage = None
172
Vadim Shtayurac8437bf2014-07-09 19:45:36 -0700173 if self.task_output_dir and not os.path.isdir(self.task_output_dir):
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700174 os.makedirs(self.task_output_dir)
175
Vadim Shtayurab450c602014-05-12 19:23:25 -0700176 def process_shard_result(self, shard_index, result):
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700177 """Stores results of a single task shard, fetches output files if necessary.
178
179 Called concurrently from multiple threads.
180 """
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700181 # Sanity check index is in expected range.
Vadim Shtayurab450c602014-05-12 19:23:25 -0700182 assert isinstance(shard_index, int)
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700183 if shard_index < 0 or shard_index >= self.shard_count:
184 logging.warning(
185 'Shard index %d is outside of expected range: [0; %d]',
186 shard_index, self.shard_count - 1)
187 return
188
189 # Store result dict of that shard, ignore results we've already seen.
190 with self._lock:
191 if shard_index in self._per_shard_results:
192 logging.warning('Ignoring duplicate shard index %d', shard_index)
193 return
194 self._per_shard_results[shard_index] = result
195
196 # Fetch output files if necessary.
Vadim Shtayurac8437bf2014-07-09 19:45:36 -0700197 if self.task_output_dir:
198 isolated_files_location = extract_output_files_location(result['output'])
199 if isolated_files_location:
200 isolate_server, namespace, isolated_hash = isolated_files_location
201 storage = self._get_storage(isolate_server, namespace)
202 if storage:
203 # Output files are supposed to be small and they are not reused across
204 # tasks. So use MemoryCache for them instead of on-disk cache. Make
205 # files writable, so that calling script can delete them.
206 isolateserver.fetch_isolated(
207 isolated_hash,
208 storage,
209 isolateserver.MemoryCache(file_mode_mask=0700),
210 os.path.join(self.task_output_dir, str(shard_index)),
211 False)
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700212
213 def finalize(self):
Vadim Shtayurac8437bf2014-07-09 19:45:36 -0700214 """Assembles and returns task summary JSON, shutdowns underlying Storage."""
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700215 with self._lock:
216 # Write an array of shard results with None for missing shards.
217 summary = {
218 'task_name': self.task_name,
219 'shards': [
220 self._per_shard_results.get(i) for i in xrange(self.shard_count)
221 ],
222 }
Vadim Shtayurac8437bf2014-07-09 19:45:36 -0700223 # Write summary.json to task_output_dir as well.
224 if self.task_output_dir:
225 tools.write_json(
226 os.path.join(self.task_output_dir, 'summary.json'),
227 summary,
228 False)
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700229 if self._storage:
230 self._storage.close()
231 self._storage = None
Vadim Shtayurac8437bf2014-07-09 19:45:36 -0700232 return summary
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700233
234 def _get_storage(self, isolate_server, namespace):
235 """Returns isolateserver.Storage to use to fetch files."""
Vadim Shtayurac8437bf2014-07-09 19:45:36 -0700236 assert self.task_output_dir
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700237 with self._lock:
238 if not self._storage:
239 self._storage = isolateserver.get_storage(isolate_server, namespace)
240 else:
241 # Shards must all use exact same isolate server and namespace.
242 if self._storage.location != isolate_server:
243 logging.error(
244 'Task shards are using multiple isolate servers: %s and %s',
245 self._storage.location, isolate_server)
246 return None
247 if self._storage.namespace != namespace:
248 logging.error(
249 'Task shards are using multiple namespaces: %s and %s',
250 self._storage.namespace, namespace)
251 return None
252 return self._storage
253
254
maruel@chromium.org0437a732013-08-27 16:05:52 +0000255def now():
256 """Exists so it can be mocked easily."""
257 return time.time()
258
259
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500260def get_task_keys(swarm_base_url, task_name):
261 """Returns the Swarming task key for each shards of task_name."""
262 key_data = urllib.urlencode([('name', task_name)])
maruel@chromium.org0437a732013-08-27 16:05:52 +0000263 url = '%s/get_matching_test_cases?%s' % (swarm_base_url, key_data)
264
vadimsh@chromium.org043b76d2013-09-12 16:15:13 +0000265 for _ in net.retry_loop(max_attempts=net.URL_OPEN_MAX_ATTEMPTS):
266 result = net.url_read(url, retry_404=True)
267 if result is None:
maruel@chromium.org0437a732013-08-27 16:05:52 +0000268 raise Failure(
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500269 'Error: Unable to find any task with the name, %s, on swarming server'
270 % task_name)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000271
maruel@chromium.org0437a732013-08-27 16:05:52 +0000272 # TODO(maruel): Compare exact string.
273 if 'No matching' in result:
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500274 logging.warning('Unable to find any task with the name, %s, on swarming '
275 'server' % task_name)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000276 continue
277 return json.loads(result)
278
279 raise Failure(
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500280 'Error: Unable to find any task with the name, %s, on swarming server'
281 % task_name)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000282
283
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700284def extract_output_files_location(task_log):
285 """Task log -> location of task output files to fetch.
286
287 TODO(vadimsh,maruel): Use side-channel to get this information.
288 See 'run_tha_test' in run_isolated.py for where the data is generated.
289
290 Returns:
291 Tuple (isolate server URL, namespace, isolated hash) on success.
292 None if information is missing or can not be parsed.
293 """
294 match = re.search(
295 r'\[run_isolated_out_hack\](.*)\[/run_isolated_out_hack\]',
296 task_log,
297 re.DOTALL)
298 if not match:
299 return None
300
301 def to_ascii(val):
302 if not isinstance(val, basestring):
303 raise ValueError()
304 return val.encode('ascii')
305
306 try:
307 data = json.loads(match.group(1))
308 if not isinstance(data, dict):
309 raise ValueError()
310 isolated_hash = to_ascii(data['hash'])
311 namespace = to_ascii(data['namespace'])
312 isolate_server = to_ascii(data['storage'])
313 if not file_path.is_url(isolate_server):
314 raise ValueError()
315 return (isolate_server, namespace, isolated_hash)
316 except (KeyError, ValueError):
317 logging.warning(
318 'Unexpected value of run_isolated_out_hack: %s', match.group(1))
319 return None
320
321
322def retrieve_results(
Vadim Shtayurab450c602014-05-12 19:23:25 -0700323 base_url, shard_index, task_key, timeout, should_stop, output_collector):
Vadim Shtayurab19319e2014-04-27 08:50:06 -0700324 """Retrieves results for a single task_key.
325
Vadim Shtayurab450c602014-05-12 19:23:25 -0700326 Returns:
327 <result dict> on success.
328 None on failure.
Vadim Shtayurab19319e2014-04-27 08:50:06 -0700329 """
maruel@chromium.org814d23f2013-10-01 19:08:00 +0000330 assert isinstance(timeout, float), timeout
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500331 params = [('r', task_key)]
maruel@chromium.org0437a732013-08-27 16:05:52 +0000332 result_url = '%s/get_result?%s' % (base_url, urllib.urlencode(params))
Vadim Shtayurab19319e2014-04-27 08:50:06 -0700333 started = now()
334 deadline = started + timeout if timeout else None
335 attempt = 0
336
337 while not should_stop.is_set():
338 attempt += 1
339
340 # Waiting for too long -> give up.
341 current_time = now()
342 if deadline and current_time >= deadline:
343 logging.error('retrieve_results(%s) timed out on attempt %d',
344 base_url, attempt)
345 return None
346
347 # Do not spin too fast. Spin faster at the beginning though.
348 # Start with 1 sec delay and for each 30 sec of waiting add another second
349 # of delay, until hitting 15 sec ceiling.
350 if attempt > 1:
351 max_delay = min(15, 1 + (current_time - started) / 30.0)
352 delay = min(max_delay, deadline - current_time) if deadline else max_delay
353 if delay > 0:
354 logging.debug('Waiting %.1f sec before retrying', delay)
355 should_stop.wait(delay)
356 if should_stop.is_set():
357 return None
358
Marc-Antoine Ruel200b3952014-08-14 11:07:44 -0400359 # Disable internal retries in net.url_read, since we are doing retries
360 # ourselves. Do not use retry_404 so should_stop is polled more often.
361 response = net.url_read(result_url, retry_404=False, retry_50x=False)
362
363 # Request failed. Try again.
364 if response is None:
365 continue
366
367 # Got some response, ensure it is JSON dict, retry if not.
Vadim Shtayurab19319e2014-04-27 08:50:06 -0700368 try:
Marc-Antoine Ruel200b3952014-08-14 11:07:44 -0400369 result = json.loads(response) or {}
Vadim Shtayurab19319e2014-04-27 08:50:06 -0700370 if not isinstance(result, dict):
371 raise ValueError()
372 except (ValueError, TypeError):
373 logging.warning(
374 'Received corrupted or invalid data for task_key %s, retrying: %r',
Marc-Antoine Ruel200b3952014-08-14 11:07:44 -0400375 task_key, response)
Vadim Shtayurab19319e2014-04-27 08:50:06 -0700376 continue
377
378 # Swarming server uses non-empty 'output' value as a flag that task has
379 # finished. How to wait for tasks that produce no output is a mystery.
380 if result.get('output'):
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700381 # Record the result, try to fetch attached output files (if any).
382 if output_collector:
383 # TODO(vadimsh): Respect |should_stop| and |deadline| when fetching.
Vadim Shtayurab450c602014-05-12 19:23:25 -0700384 output_collector.process_shard_result(shard_index, result)
Vadim Shtayurab19319e2014-04-27 08:50:06 -0700385 return result
maruel@chromium.org0437a732013-08-27 16:05:52 +0000386
387
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700388def yield_results(
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700389 swarm_base_url, task_keys, timeout, max_threads,
390 print_status_updates, output_collector):
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500391 """Yields swarming task results from the swarming server as (index, result).
maruel@chromium.org0437a732013-08-27 16:05:52 +0000392
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700393 Duplicate shards are ignored. Shards are yielded in order of completion.
394 Timed out shards are NOT yielded at all. Caller can compare number of yielded
395 shards with len(task_keys) to verify all shards completed.
maruel@chromium.org0437a732013-08-27 16:05:52 +0000396
397 max_threads is optional and is used to limit the number of parallel fetches
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500398 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 +0000399 worth normally to limit the number threads. Mostly used for testing purposes.
Marc-Antoine Ruel5c720342014-02-21 14:46:14 -0500400
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700401 output_collector is an optional instance of TaskOutputCollector that will be
402 used to fetch files produced by a task from isolate server to the local disk.
403
Marc-Antoine Ruel5c720342014-02-21 14:46:14 -0500404 Yields:
405 (index, result). In particular, 'result' is defined as the
406 GetRunnerResults() function in services/swarming/server/test_runner.py.
maruel@chromium.org0437a732013-08-27 16:05:52 +0000407 """
maruel@chromium.org0437a732013-08-27 16:05:52 +0000408 number_threads = (
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500409 min(max_threads, len(task_keys)) if max_threads else len(task_keys))
Vadim Shtayurab19319e2014-04-27 08:50:06 -0700410 should_stop = threading.Event()
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700411 results_channel = threading_utils.TaskChannel()
Vadim Shtayurab19319e2014-04-27 08:50:06 -0700412
maruel@chromium.org0437a732013-08-27 16:05:52 +0000413 with threading_utils.ThreadPool(number_threads, number_threads, 0) as pool:
414 try:
Vadim Shtayurab450c602014-05-12 19:23:25 -0700415 # Adds a task to the thread pool to call 'retrieve_results' and return
416 # the results together with shard_index that produced them (as a tuple).
417 def enqueue_retrieve_results(shard_index, task_key):
418 task_fn = lambda *args: (shard_index, retrieve_results(*args))
maruel@chromium.org0437a732013-08-27 16:05:52 +0000419 pool.add_task(
Vadim Shtayurab450c602014-05-12 19:23:25 -0700420 0, results_channel.wrap_task(task_fn),
421 swarm_base_url, shard_index, task_key, timeout,
422 should_stop, output_collector)
423
424 # Enqueue 'retrieve_results' calls for each shard key to run in parallel.
425 for shard_index, task_key in enumerate(task_keys):
426 enqueue_retrieve_results(shard_index, task_key)
Vadim Shtayurab19319e2014-04-27 08:50:06 -0700427
428 # Wait for all of them to finish.
429 shards_remaining = range(len(task_keys))
430 active_task_count = len(task_keys)
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700431 while active_task_count:
Vadim Shtayurab450c602014-05-12 19:23:25 -0700432 shard_index, result = None, None
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700433 try:
Vadim Shtayurab450c602014-05-12 19:23:25 -0700434 shard_index, result = results_channel.pull(
435 timeout=STATUS_UPDATE_INTERVAL)
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700436 except threading_utils.TaskChannel.Timeout:
437 if print_status_updates:
438 print(
439 'Waiting for results from the following shards: %s' %
440 ', '.join(map(str, shards_remaining)))
441 sys.stdout.flush()
442 continue
443 except Exception:
444 logging.exception('Unexpected exception in retrieve_results')
Vadim Shtayurab19319e2014-04-27 08:50:06 -0700445
446 # A call to 'retrieve_results' finished (successfully or not).
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700447 active_task_count -= 1
maruel@chromium.org0437a732013-08-27 16:05:52 +0000448 if not result:
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500449 logging.error('Failed to retrieve the results for a swarming key')
maruel@chromium.org0437a732013-08-27 16:05:52 +0000450 continue
Vadim Shtayurab19319e2014-04-27 08:50:06 -0700451
Vadim Shtayurab450c602014-05-12 19:23:25 -0700452 # Yield back results to the caller.
453 assert shard_index in shards_remaining
454 shards_remaining.remove(shard_index)
455 yield shard_index, result
Vadim Shtayurab19319e2014-04-27 08:50:06 -0700456
maruel@chromium.org0437a732013-08-27 16:05:52 +0000457 finally:
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700458 # Done or aborted with Ctrl+C, kill the remaining threads.
maruel@chromium.org0437a732013-08-27 16:05:52 +0000459 should_stop.set()
460
461
Vadim Shtayurab450c602014-05-12 19:23:25 -0700462def setup_run_isolated(manifest, bundle):
463 """Sets up the manifest to run an isolated task via run_isolated.py.
maruel@chromium.org0437a732013-08-27 16:05:52 +0000464
Vadim Shtayurab450c602014-05-12 19:23:25 -0700465 Modifies |bundle| (by adding files) and |manifest| (by adding commands) in
466 place.
467
468 Args:
469 manifest: Manifest with swarm task definition.
470 bundle: ZipPackage with files that would be transfered to swarm bot.
471 If None, only |manifest| is modified (useful in tests).
maruel@chromium.org0437a732013-08-27 16:05:52 +0000472 """
vadimsh@chromium.org6b706212013-08-28 15:03:46 +0000473 # Add uncompressed zip here. It'll be compressed as part of the package sent
474 # to Swarming server.
475 run_test_name = 'run_isolated.zip'
Vadim Shtayurab450c602014-05-12 19:23:25 -0700476 if bundle and run_test_name not in bundle.files:
477 bundle.add_buffer(
478 run_test_name,
479 run_isolated.get_as_zip_package().zip_into_buffer(compress=False))
maruel@chromium.org0437a732013-08-27 16:05:52 +0000480
vadimsh@chromium.org6b706212013-08-28 15:03:46 +0000481 cleanup_script_name = 'swarm_cleanup.py'
Vadim Shtayurab450c602014-05-12 19:23:25 -0700482 if bundle and cleanup_script_name not in bundle.files:
483 bundle.add_file(
484 os.path.join(TOOLS_PATH, cleanup_script_name), cleanup_script_name)
vadimsh@chromium.org6b706212013-08-28 15:03:46 +0000485
maruel@chromium.org0437a732013-08-27 16:05:52 +0000486 run_cmd = [
487 'python', run_test_name,
maruel@chromium.org814d23f2013-10-01 19:08:00 +0000488 '--hash', manifest.isolated_hash,
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500489 '--namespace', manifest.namespace,
maruel@chromium.org0437a732013-08-27 16:05:52 +0000490 ]
Marc-Antoine Ruel8806e622014-02-12 14:15:53 -0500491 if file_path.is_url(manifest.isolate_server):
492 run_cmd.extend(('--isolate-server', manifest.isolate_server))
493 else:
494 run_cmd.extend(('--indir', manifest.isolate_server))
495
maruel@chromium.org0437a732013-08-27 16:05:52 +0000496 if manifest.verbose or manifest.profile:
497 # Have it print the profiling section.
498 run_cmd.append('--verbose')
Vadim Shtayuraae8085b2014-05-02 17:13:10 -0700499
500 # Pass all extra args for run_isolated.py, it will pass them to the command.
501 if manifest.extra_args:
502 run_cmd.append('--')
503 run_cmd.extend(manifest.extra_args)
504
maruel@chromium.org0437a732013-08-27 16:05:52 +0000505 manifest.add_task('Run Test', run_cmd)
506
507 # Clean up
508 manifest.add_task('Clean Up', ['python', cleanup_script_name])
509
510
Vadim Shtayurab450c602014-05-12 19:23:25 -0700511def setup_googletest(env, shards, index):
Marc-Antoine Ruelcd629732013-12-20 15:00:42 -0500512 """Sets googletest specific environment variables."""
513 if shards > 1:
514 env = env.copy()
Vadim Shtayurab450c602014-05-12 19:23:25 -0700515 env['GTEST_SHARD_INDEX'] = str(index)
516 env['GTEST_TOTAL_SHARDS'] = str(shards)
Marc-Antoine Ruelcd629732013-12-20 15:00:42 -0500517 return env
518
519
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500520def archive(isolate_server, namespace, isolated, algo, verbose):
maruel@chromium.org0437a732013-08-27 16:05:52 +0000521 """Archives a .isolated and all the dependencies on the CAC."""
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500522 logging.info('archive(%s, %s, %s)', isolate_server, namespace, isolated)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000523 tempdir = None
Marc-Antoine Ruel8806e622014-02-12 14:15:53 -0500524 if file_path.is_url(isolate_server):
525 command = 'archive'
526 flag = '--isolate-server'
527 else:
528 command = 'hashtable'
529 flag = '--outdir'
530
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500531 print('Archiving: %s' % isolated)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000532 try:
maruel@chromium.org0437a732013-08-27 16:05:52 +0000533 cmd = [
534 sys.executable,
535 os.path.join(ROOT_DIR, 'isolate.py'),
Marc-Antoine Ruel8806e622014-02-12 14:15:53 -0500536 command,
537 flag, isolate_server,
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500538 '--namespace', namespace,
maruel@chromium.org0437a732013-08-27 16:05:52 +0000539 '--isolated', isolated,
540 ]
maruel@chromium.orge9403ab2013-09-20 18:03:49 +0000541 cmd.extend(['--verbose'] * verbose)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000542 logging.info(' '.join(cmd))
543 if subprocess.call(cmd, verbose):
544 return
maruel@chromium.org7b844a62013-09-17 13:04:59 +0000545 return isolateserver.hash_file(isolated, algo)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000546 finally:
547 if tempdir:
548 shutil.rmtree(tempdir)
549
550
Vadim Shtayurab450c602014-05-12 19:23:25 -0700551def get_shard_task_name(task_name, shards, index):
552 """Returns a task name to use for a single shard of a task."""
553 if shards == 1:
554 return task_name
555 return '%s:%s:%s' % (task_name, shards, index)
556
557
558def upload_zip_bundle(isolate_server, bundle):
559 """Uploads a zip package to isolate storage and returns raw fetch URL.
560
561 Args:
562 isolate_server: URL of an isolate server.
563 bundle: instance of ZipPackage to upload.
564
565 Returns:
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -0400566 URL to get the file from.
Vadim Shtayurab450c602014-05-12 19:23:25 -0700567 """
568 # Swarming bot would need to be able to grab the file from the storage
569 # using raw HTTP GET. Use 'default' namespace so that the raw data returned
570 # to a bot is not zipped, since swarm_bot doesn't understand compressed
571 # data yet. This namespace have nothing to do with |namespace| passed to
572 # run_isolated.py that is used to store files for isolated task.
573 logging.info('Zipping up and uploading files...')
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -0400574 start_time = now()
575 isolate_item = isolateserver.BufferItem(
576 bundle.zip_into_buffer(), high_priority=True)
577 with isolateserver.get_storage(isolate_server, 'default') as storage:
578 uploaded = storage.upload_items([isolate_item])
579 bundle_url = storage.get_fetch_url(isolate_item)
580 elapsed = now() - start_time
Vadim Shtayurab450c602014-05-12 19:23:25 -0700581 if isolate_item in uploaded:
582 logging.info('Upload complete, time elapsed: %f', elapsed)
583 else:
584 logging.info('Zip file already on server, time elapsed: %f', elapsed)
585 return bundle_url
586
587
588def trigger_by_manifest(swarming, manifest):
589 """Given a task manifest, triggers it for execution on swarming.
590
591 Args:
592 swarming: URL of a swarming service.
593 manifest: instance of Manifest.
594
595 Returns:
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -0400596 tuple(Task id, priority) on success. tuple(None, None) on failure.
Vadim Shtayurab450c602014-05-12 19:23:25 -0700597 """
598 logging.info('Triggering: %s', manifest.task_name)
599 manifest_text = manifest.to_json()
600 result = net.url_read(swarming + '/test', data={'request': manifest_text})
601 if not result:
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -0400602 on_error.report('Failed to trigger task %s' % manifest.task_name)
Vadim Shtayura1c024f72014-07-09 19:00:10 -0700603 return None, None
Vadim Shtayurab450c602014-05-12 19:23:25 -0700604 try:
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -0400605 data = json.loads(result)
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -0400606 except (ValueError, TypeError):
Vadim Shtayurab450c602014-05-12 19:23:25 -0700607 msg = '\n'.join((
608 'Failed to trigger task %s' % manifest.task_name,
609 'Manifest: %s' % manifest_text,
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -0400610 'Bad response: %s' % result))
611 on_error.report(msg)
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -0400612 return None, None
613 if not data:
614 return None, None
615 return data['test_keys'][0]['test_key'], data['priority']
Vadim Shtayurab450c602014-05-12 19:23:25 -0700616
617
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -0400618def abort_task(_swarming, _manifest):
Vadim Shtayurab450c602014-05-12 19:23:25 -0700619 """Given a task manifest that was triggered, aborts its execution."""
620 # TODO(vadimsh): No supported by the server yet.
621
622
623def trigger_task_shards(
Vadim Shtayuraae8085b2014-05-02 17:13:10 -0700624 swarming, isolate_server, namespace, isolated_hash, task_name, extra_args,
Marc-Antoine Ruelaea50652014-06-12 14:23:48 -0400625 shards, dimensions, env, deadline, verbose, profile, priority):
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -0400626 """Triggers multiple subtasks of a sharded task.
627
628 Returns:
Vadim Shtayuraf27448e2014-06-26 11:35:05 -0700629 Dict with task details, returned to caller as part of --dump-json output.
630 None in case of failure.
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -0400631 """
Vadim Shtayurab450c602014-05-12 19:23:25 -0700632 # Collects all files that are necessary to bootstrap a task execution
633 # on the bot. Usually it includes self contained run_isolated.zip and
634 # a bunch of small other scripts. All heavy files are pulled
635 # by run_isolated.zip. Updated in 'setup_run_isolated'.
636 bundle = zip_package.ZipPackage(ROOT_DIR)
637
638 # Make a separate Manifest for each shard, put shard index and number of
639 # shards into env and subtask name.
640 manifests = []
641 for index in xrange(shards):
maruel@chromium.org0437a732013-08-27 16:05:52 +0000642 manifest = Manifest(
Marc-Antoine Ruela7049872013-11-05 19:28:35 -0500643 isolate_server=isolate_server,
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500644 namespace=namespace,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500645 isolated_hash=isolated_hash,
Vadim Shtayurab450c602014-05-12 19:23:25 -0700646 task_name=get_shard_task_name(task_name, shards, index),
Vadim Shtayuraae8085b2014-05-02 17:13:10 -0700647 extra_args=extra_args,
Marc-Antoine Ruel92f32422013-11-06 18:12:13 -0500648 dimensions=dimensions,
Vadim Shtayurab450c602014-05-12 19:23:25 -0700649 env=setup_googletest(env, shards, index),
Marc-Antoine Ruel13b7b782014-03-14 11:14:57 -0400650 deadline=deadline,
Marc-Antoine Ruela7049872013-11-05 19:28:35 -0500651 verbose=verbose,
652 profile=profile,
Vadim Shtayurabcff74f2014-02-27 16:19:34 -0800653 priority=priority)
Vadim Shtayurab450c602014-05-12 19:23:25 -0700654 setup_run_isolated(manifest, bundle)
655 manifests.append(manifest)
656
657 # Upload zip bundle file to get its URL.
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -0400658 try:
659 bundle_url = upload_zip_bundle(isolate_server, bundle)
660 except (IOError, OSError):
661 on_error.report('Failed to upload the zip file for task %s' % task_name)
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -0400662 return None, None
maruel@chromium.org0437a732013-08-27 16:05:52 +0000663
Vadim Shtayurab450c602014-05-12 19:23:25 -0700664 # Attach that file to all manifests.
665 for manifest in manifests:
666 manifest.add_bundled_file('swarm_data.zip', bundle_url)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000667
Vadim Shtayurab450c602014-05-12 19:23:25 -0700668 # Trigger all the subtasks.
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -0400669 tasks = {}
670 priority_warning = False
Vadim Shtayuraf27448e2014-06-26 11:35:05 -0700671 for index, manifest in enumerate(manifests):
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -0400672 task_id, priority = trigger_by_manifest(swarming, manifest)
673 if not task_id:
Vadim Shtayurab450c602014-05-12 19:23:25 -0700674 break
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -0400675 if not priority_warning and priority != manifest.priority:
676 priority_warning = True
677 print >> sys.stderr, 'Priority was reset to %s' % priority
Vadim Shtayuraf27448e2014-06-26 11:35:05 -0700678 tasks[manifest.task_name] = {
679 'shard_index': index,
680 'task_id': task_id,
681 'view_url': '%s/user/task/%s' % (swarming, task_id),
682 }
Vadim Shtayurab450c602014-05-12 19:23:25 -0700683
684 # Some shards weren't triggered. Abort everything.
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -0400685 if len(tasks) != len(manifests):
686 if tasks:
Vadim Shtayurab450c602014-05-12 19:23:25 -0700687 print >> sys.stderr, 'Not all shards were triggered'
Vadim Shtayuraf27448e2014-06-26 11:35:05 -0700688 for task_dict in tasks.itervalues():
689 abort_task(swarming, task_dict['task_id'])
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -0400690 return None
maruel@chromium.org0437a732013-08-27 16:05:52 +0000691
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -0400692 return tasks
maruel@chromium.org0437a732013-08-27 16:05:52 +0000693
694
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500695def isolated_to_hash(isolate_server, namespace, arg, algo, verbose):
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500696 """Archives a .isolated file if needed.
697
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500698 Returns the file hash to trigger and a bool specifying if it was a file (True)
699 or a hash (False).
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500700 """
701 if arg.endswith('.isolated'):
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500702 file_hash = archive(isolate_server, namespace, arg, algo, verbose)
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500703 if not file_hash:
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -0400704 on_error.report('Archival failure %s' % arg)
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500705 return None, True
706 return file_hash, True
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500707 elif isolateserver.is_valid_hash(arg, algo):
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500708 return arg, False
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500709 else:
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -0400710 on_error.report('Invalid hash %s' % arg)
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500711 return None, False
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500712
713
maruel@chromium.org0437a732013-08-27 16:05:52 +0000714def trigger(
Marc-Antoine Ruela7049872013-11-05 19:28:35 -0500715 swarming,
716 isolate_server,
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500717 namespace,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500718 file_hash_or_isolated,
719 task_name,
Vadim Shtayuraae8085b2014-05-02 17:13:10 -0700720 extra_args,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500721 shards,
Marc-Antoine Ruel92f32422013-11-06 18:12:13 -0500722 dimensions,
723 env,
Marc-Antoine Ruel13b7b782014-03-14 11:14:57 -0400724 deadline,
maruel@chromium.org0437a732013-08-27 16:05:52 +0000725 verbose,
726 profile,
727 priority):
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -0400728 """Sends off the hash swarming task requests.
729
730 Returns:
731 tuple(dict(task_name: task_id), base task name). The dict of tasks is None
732 in case of failure.
733 """
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500734 file_hash, is_file = isolated_to_hash(
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500735 isolate_server, namespace, file_hash_or_isolated, hashlib.sha1, verbose)
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500736 if not file_hash:
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500737 return 1, ''
738 if not task_name:
739 # If a file name was passed, use its base name of the isolated hash.
740 # Otherwise, use user name as an approximation of a task name.
741 if is_file:
742 key = os.path.splitext(os.path.basename(file_hash_or_isolated))[0]
743 else:
744 key = getpass.getuser()
Vadim Shtayurac3d97b02014-04-26 19:16:05 -0700745 task_name = '%s/%s/%s/%d' % (
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500746 key,
747 '_'.join('%s=%s' % (k, v) for k, v in sorted(dimensions.iteritems())),
Vadim Shtayurac3d97b02014-04-26 19:16:05 -0700748 file_hash,
749 now() * 1000)
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500750
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -0400751 tasks = trigger_task_shards(
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500752 swarming=swarming,
753 isolate_server=isolate_server,
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500754 namespace=namespace,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500755 isolated_hash=file_hash,
756 task_name=task_name,
Vadim Shtayuraae8085b2014-05-02 17:13:10 -0700757 extra_args=extra_args,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500758 shards=shards,
759 dimensions=dimensions,
Marc-Antoine Ruel13b7b782014-03-14 11:14:57 -0400760 deadline=deadline,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500761 env=env,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500762 verbose=verbose,
763 profile=profile,
Vadim Shtayurabcff74f2014-02-27 16:19:34 -0800764 priority=priority)
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -0400765 return tasks, task_name
maruel@chromium.org0437a732013-08-27 16:05:52 +0000766
767
Vadim Shtayurab450c602014-05-12 19:23:25 -0700768def decorate_shard_output(shard_index, result, shard_exit_code):
maruel@chromium.org0437a732013-08-27 16:05:52 +0000769 """Returns wrapped output for swarming task shard."""
770 tag = 'index %s (machine tag: %s, id: %s)' % (
Vadim Shtayurab450c602014-05-12 19:23:25 -0700771 shard_index,
maruel@chromium.org0437a732013-08-27 16:05:52 +0000772 result['machine_id'],
773 result.get('machine_tag', 'unknown'))
774 return (
775 '\n'
776 '================================================================\n'
777 'Begin output from shard %s\n'
778 '================================================================\n'
779 '\n'
780 '%s'
781 '================================================================\n'
Vadim Shtayura473455a2014-05-14 15:22:35 -0700782 'End output from shard %s.\nExit code %d (%s).\n'
783 '================================================================\n') % (
784 tag, result['output'] or NO_OUTPUT_FOUND, tag,
785 shard_exit_code, hex(0xffffffff & shard_exit_code))
maruel@chromium.org0437a732013-08-27 16:05:52 +0000786
787
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700788def collect(
Vadim Shtayurab450c602014-05-12 19:23:25 -0700789 url, task_name, shards, timeout, decorate,
Vadim Shtayurac8437bf2014-07-09 19:45:36 -0700790 print_status_updates, task_summary_json, task_output_dir):
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500791 """Retrieves results of a Swarming task."""
Vadim Shtayurab450c602014-05-12 19:23:25 -0700792 # Grab task keys for each shard. Order is important, used to figure out
793 # shard index based on the key.
794 # TODO(vadimsh): Simplify this once server support is added.
795 task_keys = []
796 for index in xrange(shards):
797 shard_task_name = get_shard_task_name(task_name, shards, index)
798 logging.info('Collecting %s', shard_task_name)
799 shard_task_keys = get_task_keys(url, shard_task_name)
800 if not shard_task_keys:
801 raise Failure('No task keys to get results with: %s' % shard_task_name)
802 if len(shard_task_keys) != 1:
803 raise Failure('Expecting only one shard for a task: %s' % shard_task_name)
804 task_keys.append(shard_task_keys[0])
maruel@chromium.org0437a732013-08-27 16:05:52 +0000805
Vadim Shtayurac8437bf2014-07-09 19:45:36 -0700806 # Collect summary JSON and output files (if task_output_dir is not None).
807 output_collector = TaskOutputCollector(
808 task_output_dir, task_name, len(task_keys))
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700809
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700810 seen_shards = set()
Vadim Shtayurac524f512014-05-15 09:54:56 -0700811 exit_codes = []
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700812
813 try:
814 for index, output in yield_results(
815 url, task_keys, timeout, None, print_status_updates, output_collector):
816 seen_shards.add(index)
Vadim Shtayura473455a2014-05-14 15:22:35 -0700817
818 # Grab first non-zero exit code as an overall shard exit code.
819 shard_exit_code = 0
820 for code in map(int, (output['exit_codes'] or '1').split(',')):
821 if code:
822 shard_exit_code = code
823 break
Vadim Shtayurac524f512014-05-15 09:54:56 -0700824 exit_codes.append(shard_exit_code)
Vadim Shtayura473455a2014-05-14 15:22:35 -0700825
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700826 if decorate:
Vadim Shtayurab450c602014-05-12 19:23:25 -0700827 print decorate_shard_output(index, output, shard_exit_code)
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700828 else:
829 print(
830 '%s/%s: %s' % (
831 output['machine_id'],
832 output['machine_tag'],
833 output['exit_codes']))
834 print(''.join(' %s\n' % l for l in output['output'].splitlines()))
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700835 finally:
Vadim Shtayurac8437bf2014-07-09 19:45:36 -0700836 summary = output_collector.finalize()
837 if task_summary_json:
838 tools.write_json(task_summary_json, summary, False)
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700839
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700840 if len(seen_shards) != len(task_keys):
841 missing_shards = [x for x in range(len(task_keys)) if x not in seen_shards]
842 print >> sys.stderr, ('Results from some shards are missing: %s' %
843 ', '.join(map(str, missing_shards)))
Vadim Shtayurac524f512014-05-15 09:54:56 -0700844 return 1
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700845
Vadim Shtayurac524f512014-05-15 09:54:56 -0700846 return int(bool(any(exit_codes)))
maruel@chromium.org0437a732013-08-27 16:05:52 +0000847
848
Marc-Antoine Ruel819fb162014-03-12 16:38:26 -0400849def add_filter_options(parser):
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500850 parser.filter_group = tools.optparse.OptionGroup(parser, 'Filtering slaves')
851 parser.filter_group.add_option(
Marc-Antoine Ruelb39e8cf2014-01-20 10:39:31 -0500852 '-d', '--dimension', default=[], action='append', nargs=2,
Marc-Antoine Ruel92f32422013-11-06 18:12:13 -0500853 dest='dimensions', metavar='FOO bar',
854 help='dimension to filter on')
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500855 parser.add_option_group(parser.filter_group)
856
Marc-Antoine Ruel819fb162014-03-12 16:38:26 -0400857
Marc-Antoine Ruel025e7822014-05-01 11:50:24 -0400858def process_filter_options(parser, options):
859 options.dimensions = dict(options.dimensions)
860 if not options.dimensions:
861 parser.error('Please at least specify one --dimension')
862
863
Vadim Shtayurab450c602014-05-12 19:23:25 -0700864def add_sharding_options(parser):
865 parser.sharding_group = tools.optparse.OptionGroup(parser, 'Sharding options')
866 parser.sharding_group.add_option(
867 '--shards', type='int', default=1,
868 help='Number of shards to trigger and collect.')
869 parser.add_option_group(parser.sharding_group)
870
871
Marc-Antoine Ruel819fb162014-03-12 16:38:26 -0400872def add_trigger_options(parser):
873 """Adds all options to trigger a task on Swarming."""
874 isolateserver.add_isolate_server_options(parser, True)
875 add_filter_options(parser)
876
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500877 parser.task_group = tools.optparse.OptionGroup(parser, 'Task properties')
878 parser.task_group.add_option(
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500879 '-e', '--env', default=[], action='append', nargs=2, metavar='FOO bar',
Vadim Shtayurab450c602014-05-12 19:23:25 -0700880 help='Environment variables to set')
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500881 parser.task_group.add_option(
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500882 '--priority', type='int', default=100,
883 help='The lower value, the more important the task is')
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500884 parser.task_group.add_option(
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500885 '-T', '--task-name',
886 help='Display name of the task. It uniquely identifies the task. '
Vadim Shtayurac3d97b02014-04-26 19:16:05 -0700887 'Defaults to <base_name>/<dimensions>/<isolated hash>/<timestamp> '
888 'if an isolated file is provided, if a hash is provided, it '
889 'defaults to <user>/<dimensions>/<isolated hash>/<timestamp>')
Marc-Antoine Ruel13b7b782014-03-14 11:14:57 -0400890 parser.task_group.add_option(
891 '--deadline', type='int', default=6*60*60,
892 help='Seconds to allow the task to be pending for a bot to run before '
893 'this task request expires.')
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500894 parser.add_option_group(parser.task_group)
Marc-Antoine Ruelcd629732013-12-20 15:00:42 -0500895 # TODO(maruel): This is currently written in a chromium-specific way.
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500896 parser.group_logging.add_option(
maruel@chromium.org0437a732013-08-27 16:05:52 +0000897 '--profile', action='store_true',
898 default=bool(os.environ.get('ISOLATE_DEBUG')),
899 help='Have run_isolated.py print profiling info')
maruel@chromium.org0437a732013-08-27 16:05:52 +0000900
901
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500902def process_trigger_options(parser, options, args):
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500903 isolateserver.process_isolate_server_options(parser, options)
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500904 if len(args) != 1:
905 parser.error('Must pass one .isolated file or its hash (sha1).')
Marc-Antoine Ruel025e7822014-05-01 11:50:24 -0400906 process_filter_options(parser, options)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000907
908
909def add_collect_options(parser):
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500910 parser.server_group.add_option(
maruel@chromium.org0437a732013-08-27 16:05:52 +0000911 '-t', '--timeout',
912 type='float',
913 default=DEFAULT_SHARD_WAIT_TIME,
914 help='Timeout to wait for result, set to 0 for no timeout; default: '
915 '%default s')
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500916 parser.group_logging.add_option(
917 '--decorate', action='store_true', help='Decorate output')
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700918 parser.group_logging.add_option(
919 '--print-status-updates', action='store_true',
920 help='Print periodic status updates')
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700921 parser.task_output_group = tools.optparse.OptionGroup(parser, 'Task output')
922 parser.task_output_group.add_option(
Vadim Shtayurac8437bf2014-07-09 19:45:36 -0700923 '--task-summary-json',
924 metavar='FILE',
925 help='Dump a summary of task results to this file as json. It contains '
926 'only shards statuses as know to server directly. Any output files '
927 'emitted by the task can be collected by using --task-output-dir')
928 parser.task_output_group.add_option(
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700929 '--task-output-dir',
Vadim Shtayurac8437bf2014-07-09 19:45:36 -0700930 metavar='DIR',
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700931 help='Directory to put task results into. When the task finishes, this '
Vadim Shtayurac8437bf2014-07-09 19:45:36 -0700932 'directory contains per-shard directory with output files produced '
933 'by shards: <task-output-dir>/<zero-based-shard-index>/.')
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700934 parser.add_option_group(parser.task_output_group)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000935
936
Vadim Shtayuraae8085b2014-05-02 17:13:10 -0700937def extract_isolated_command_extra_args(args):
938 try:
939 index = args.index('--')
940 except ValueError:
941 return (args, [])
942 return (args[:index], args[index+1:])
943
944
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500945@subcommand.usage('task_name')
maruel@chromium.org0437a732013-08-27 16:05:52 +0000946def CMDcollect(parser, args):
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500947 """Retrieves results of a Swarming task.
maruel@chromium.org0437a732013-08-27 16:05:52 +0000948
949 The result can be in multiple part if the execution was sharded. It can
950 potentially have retries.
951 """
952 add_collect_options(parser)
Vadim Shtayurab450c602014-05-12 19:23:25 -0700953 add_sharding_options(parser)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000954 (options, args) = parser.parse_args(args)
955 if not args:
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500956 parser.error('Must specify one task name.')
maruel@chromium.org0437a732013-08-27 16:05:52 +0000957 elif len(args) > 1:
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500958 parser.error('Must specify only one task name.')
maruel@chromium.org0437a732013-08-27 16:05:52 +0000959
Vadim Shtayura6b555c12014-07-23 16:22:18 -0700960 auth.ensure_logged_in(options.swarming)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000961 try:
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700962 return collect(
963 options.swarming,
964 args[0],
Vadim Shtayurab450c602014-05-12 19:23:25 -0700965 options.shards,
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700966 options.timeout,
967 options.decorate,
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700968 options.print_status_updates,
Vadim Shtayurac8437bf2014-07-09 19:45:36 -0700969 options.task_summary_json,
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700970 options.task_output_dir)
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -0400971 except Failure:
972 on_error.report(None)
vadimsh@chromium.orgd908a542013-10-30 01:36:17 +0000973 return 1
maruel@chromium.org0437a732013-08-27 16:05:52 +0000974
975
Marc-Antoine Ruel819fb162014-03-12 16:38:26 -0400976def CMDquery(parser, args):
977 """Returns information about the bots connected to the Swarming server."""
978 add_filter_options(parser)
979 parser.filter_group.add_option(
Marc-Antoine Ruel28083112014-03-13 16:34:04 -0400980 '--dead-only', action='store_true',
981 help='Only print dead bots, useful to reap them and reimage broken bots')
982 parser.filter_group.add_option(
Marc-Antoine Ruel819fb162014-03-12 16:38:26 -0400983 '-k', '--keep-dead', action='store_true',
984 help='Do not filter out dead bots')
985 parser.filter_group.add_option(
986 '-b', '--bare', action='store_true',
Marc-Antoine Ruele7b00162014-03-12 16:59:01 -0400987 help='Do not print out dimensions')
Marc-Antoine Ruel819fb162014-03-12 16:38:26 -0400988 options, args = parser.parse_args(args)
Marc-Antoine Ruel28083112014-03-13 16:34:04 -0400989
990 if options.keep_dead and options.dead_only:
991 parser.error('Use only one of --keep-dead and --dead-only')
Vadim Shtayura6b555c12014-07-23 16:22:18 -0700992
993 auth.ensure_logged_in(options.swarming)
Marc-Antoine Ruel0a620612014-08-13 15:47:07 -0400994 data = net.url_read_json(options.swarming + '/swarming/api/v1/bots')
Marc-Antoine Ruel819fb162014-03-12 16:38:26 -0400995 if data is None:
996 print >> sys.stderr, 'Failed to access %s' % options.swarming
997 return 1
Marc-Antoine Ruele4bebbc2014-06-04 09:36:14 -0400998 for machine in natsort.natsorted(data['machines'], key=lambda x: x['id']):
Marc-Antoine Ruel28083112014-03-13 16:34:04 -0400999 if options.dead_only:
Marc-Antoine Ruelfe844672014-07-31 11:16:51 -04001000 if not machine['is_dead']:
Marc-Antoine Ruel28083112014-03-13 16:34:04 -04001001 continue
Marc-Antoine Ruelfe844672014-07-31 11:16:51 -04001002 elif not options.keep_dead and machine['is_dead']:
Marc-Antoine Ruel819fb162014-03-12 16:38:26 -04001003 continue
1004
Marc-Antoine Ruele7b00162014-03-12 16:59:01 -04001005 # If the user requested to filter on dimensions, ensure the bot has all the
1006 # dimensions requested.
Marc-Antoine Ruel819fb162014-03-12 16:38:26 -04001007 dimensions = machine['dimensions']
1008 for key, value in options.dimensions:
1009 if key not in dimensions:
1010 break
Marc-Antoine Ruele7b00162014-03-12 16:59:01 -04001011 # A bot can have multiple value for a key, for example,
1012 # {'os': ['Windows', 'Windows-6.1']}, so that --dimension os=Windows will
1013 # be accepted.
Marc-Antoine Ruel819fb162014-03-12 16:38:26 -04001014 if isinstance(dimensions[key], list):
1015 if value not in dimensions[key]:
1016 break
1017 else:
1018 if value != dimensions[key]:
1019 break
1020 else:
Marc-Antoine Ruele4bebbc2014-06-04 09:36:14 -04001021 print machine['id']
Marc-Antoine Ruele7b00162014-03-12 16:59:01 -04001022 if not options.bare:
Marc-Antoine Ruel0a620612014-08-13 15:47:07 -04001023 print ' %s' % json.dumps(dimensions, sort_keys=True)
Marc-Antoine Ruel819fb162014-03-12 16:38:26 -04001024 return 0
1025
1026
Vadim Shtayuraae8085b2014-05-02 17:13:10 -07001027@subcommand.usage('(hash|isolated) [-- extra_args]')
maruel@chromium.org0437a732013-08-27 16:05:52 +00001028def CMDrun(parser, args):
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001029 """Triggers a task and wait for the results.
maruel@chromium.org0437a732013-08-27 16:05:52 +00001030
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001031 Basically, does everything to run a command remotely.
maruel@chromium.org0437a732013-08-27 16:05:52 +00001032 """
1033 add_trigger_options(parser)
1034 add_collect_options(parser)
Vadim Shtayurab450c602014-05-12 19:23:25 -07001035 add_sharding_options(parser)
Vadim Shtayuraae8085b2014-05-02 17:13:10 -07001036 args, isolated_cmd_args = extract_isolated_command_extra_args(args)
maruel@chromium.org0437a732013-08-27 16:05:52 +00001037 options, args = parser.parse_args(args)
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001038 process_trigger_options(parser, options, args)
maruel@chromium.org0437a732013-08-27 16:05:52 +00001039
Vadim Shtayura6b555c12014-07-23 16:22:18 -07001040 auth.ensure_logged_in(options.swarming)
1041 if file_path.is_url(options.isolate_server):
1042 auth.ensure_logged_in(options.isolate_server)
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001043 try:
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -04001044 tasks, task_name = trigger(
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001045 swarming=options.swarming,
Marc-Antoine Ruel8806e622014-02-12 14:15:53 -05001046 isolate_server=options.isolate_server or options.indir,
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -05001047 namespace=options.namespace,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001048 file_hash_or_isolated=args[0],
1049 task_name=options.task_name,
Vadim Shtayuraae8085b2014-05-02 17:13:10 -07001050 extra_args=isolated_cmd_args,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001051 shards=options.shards,
1052 dimensions=options.dimensions,
1053 env=dict(options.env),
Marc-Antoine Ruel13b7b782014-03-14 11:14:57 -04001054 deadline=options.deadline,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001055 verbose=options.verbose,
1056 profile=options.profile,
1057 priority=options.priority)
1058 except Failure as e:
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -04001059 on_error.report(
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001060 'Failed to trigger %s(%s): %s' %
1061 (options.task_name, args[0], e.args[0]))
1062 return 1
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -04001063 if not tasks:
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -04001064 on_error.report('Failed to trigger the task.')
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -04001065 return 1
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -05001066 if task_name != options.task_name:
1067 print('Triggered task: %s' % task_name)
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001068 try:
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -04001069 # TODO(maruel): Use task_ids, it's much more efficient!
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001070 return collect(
1071 options.swarming,
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -05001072 task_name,
Vadim Shtayurab450c602014-05-12 19:23:25 -07001073 options.shards,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001074 options.timeout,
Vadim Shtayura86a2cef2014-04-18 11:13:39 -07001075 options.decorate,
Vadim Shtayurae3fbd102014-04-29 17:05:21 -07001076 options.print_status_updates,
Vadim Shtayurac8437bf2014-07-09 19:45:36 -07001077 options.task_summary_json,
Vadim Shtayurae3fbd102014-04-29 17:05:21 -07001078 options.task_output_dir)
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -04001079 except Failure:
1080 on_error.report(None)
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001081 return 1
maruel@chromium.org0437a732013-08-27 16:05:52 +00001082
1083
Vadim Shtayuraae8085b2014-05-02 17:13:10 -07001084@subcommand.usage("(hash|isolated) [-- extra_args]")
maruel@chromium.org0437a732013-08-27 16:05:52 +00001085def CMDtrigger(parser, args):
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001086 """Triggers a Swarming task.
maruel@chromium.org0437a732013-08-27 16:05:52 +00001087
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001088 Accepts either the hash (sha1) of a .isolated file already uploaded or the
1089 path to an .isolated file to archive, packages it if needed and sends a
1090 Swarming manifest file to the Swarming server.
1091
1092 If an .isolated file is specified instead of an hash, it is first archived.
Vadim Shtayuraae8085b2014-05-02 17:13:10 -07001093
1094 Passes all extra arguments provided after '--' as additional command line
1095 arguments for an isolated command specified in *.isolate file.
maruel@chromium.org0437a732013-08-27 16:05:52 +00001096 """
1097 add_trigger_options(parser)
Vadim Shtayurab450c602014-05-12 19:23:25 -07001098 add_sharding_options(parser)
Vadim Shtayuraae8085b2014-05-02 17:13:10 -07001099 args, isolated_cmd_args = extract_isolated_command_extra_args(args)
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -04001100 parser.add_option(
1101 '--dump-json',
1102 metavar='FILE',
1103 help='Dump details about the triggered task(s) to this file as json')
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001104 options, args = parser.parse_args(args)
1105 process_trigger_options(parser, options, args)
maruel@chromium.org0437a732013-08-27 16:05:52 +00001106
Vadim Shtayura6b555c12014-07-23 16:22:18 -07001107 auth.ensure_logged_in(options.swarming)
1108 if file_path.is_url(options.isolate_server):
1109 auth.ensure_logged_in(options.isolate_server)
maruel@chromium.org0437a732013-08-27 16:05:52 +00001110 try:
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -04001111 tasks, task_name = trigger(
Marc-Antoine Ruela7049872013-11-05 19:28:35 -05001112 swarming=options.swarming,
Marc-Antoine Ruel8806e622014-02-12 14:15:53 -05001113 isolate_server=options.isolate_server or options.indir,
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -05001114 namespace=options.namespace,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001115 file_hash_or_isolated=args[0],
1116 task_name=options.task_name,
Vadim Shtayuraae8085b2014-05-02 17:13:10 -07001117 extra_args=isolated_cmd_args,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001118 shards=options.shards,
Vadim Shtayuraae8085b2014-05-02 17:13:10 -07001119 dimensions=options.dimensions,
Marc-Antoine Ruel92f32422013-11-06 18:12:13 -05001120 env=dict(options.env),
Marc-Antoine Ruel13b7b782014-03-14 11:14:57 -04001121 deadline=options.deadline,
Marc-Antoine Ruela7049872013-11-05 19:28:35 -05001122 verbose=options.verbose,
1123 profile=options.profile,
1124 priority=options.priority)
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -04001125 if tasks:
1126 if task_name != options.task_name:
1127 print('Triggered task: %s' % task_name)
1128 if options.dump_json:
1129 data = {
1130 'base_task_name': task_name,
1131 'tasks': tasks,
1132 }
1133 tools.write_json(options.dump_json, data, True)
1134 return int(not tasks)
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -04001135 except Failure:
1136 on_error.report(None)
vadimsh@chromium.orgd908a542013-10-30 01:36:17 +00001137 return 1
maruel@chromium.org0437a732013-08-27 16:05:52 +00001138
1139
1140class OptionParserSwarming(tools.OptionParserWithLogging):
1141 def __init__(self, **kwargs):
1142 tools.OptionParserWithLogging.__init__(
1143 self, prog='swarming.py', **kwargs)
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -05001144 self.server_group = tools.optparse.OptionGroup(self, 'Server')
1145 self.server_group.add_option(
maruel@chromium.orge9403ab2013-09-20 18:03:49 +00001146 '-S', '--swarming',
Kevin Graney5346c162014-01-24 12:20:01 -05001147 metavar='URL', default=os.environ.get('SWARMING_SERVER', ''),
maruel@chromium.orge9403ab2013-09-20 18:03:49 +00001148 help='Swarming server to use')
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -05001149 self.add_option_group(self.server_group)
Vadim Shtayurae34e13a2014-02-02 11:23:26 -08001150 auth.add_auth_options(self)
maruel@chromium.org0437a732013-08-27 16:05:52 +00001151
1152 def parse_args(self, *args, **kwargs):
1153 options, args = tools.OptionParserWithLogging.parse_args(
1154 self, *args, **kwargs)
1155 options.swarming = options.swarming.rstrip('/')
1156 if not options.swarming:
1157 self.error('--swarming is required.')
Vadim Shtayura5d1efce2014-02-04 10:55:43 -08001158 auth.process_auth_options(self, options)
maruel@chromium.org0437a732013-08-27 16:05:52 +00001159 return options, args
1160
1161
1162def main(args):
1163 dispatcher = subcommand.CommandDispatcher(__name__)
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -04001164 return dispatcher.execute(OptionParserSwarming(version=__version__), args)
maruel@chromium.org0437a732013-08-27 16:05:52 +00001165
1166
1167if __name__ == '__main__':
1168 fix_encoding.fix_encoding()
1169 tools.disable_buffering()
1170 colorama.init()
1171 sys.exit(main(sys.argv[1:]))