blob: 81f5cf49e8a85cca91642ca1ed75106faf3e6614 [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 Ruel0a620612014-08-13 15:47:07 -0400359 result = None
Vadim Shtayurab19319e2014-04-27 08:50:06 -0700360 try:
Marc-Antoine Ruel0a620612014-08-13 15:47:07 -0400361 # Disable internal retries in net.url_read, since we are doing retries
362 # ourselves. Do not use retry_404 so should_stop is polled more often.
363 result = net.url_read_json(result_url, retry_404=False, retry_50x=False)
364
365 # Request failed. Try again.
366 if result is None:
367 continue
368
Vadim Shtayurab19319e2014-04-27 08:50:06 -0700369 if not isinstance(result, dict):
370 raise ValueError()
371 except (ValueError, TypeError):
372 logging.warning(
373 'Received corrupted or invalid data for task_key %s, retrying: %r',
Marc-Antoine Ruel0a620612014-08-13 15:47:07 -0400374 task_key, result)
Vadim Shtayurab19319e2014-04-27 08:50:06 -0700375 continue
376
377 # Swarming server uses non-empty 'output' value as a flag that task has
378 # finished. How to wait for tasks that produce no output is a mystery.
379 if result.get('output'):
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700380 # Record the result, try to fetch attached output files (if any).
381 if output_collector:
382 # TODO(vadimsh): Respect |should_stop| and |deadline| when fetching.
Vadim Shtayurab450c602014-05-12 19:23:25 -0700383 output_collector.process_shard_result(shard_index, result)
Vadim Shtayurab19319e2014-04-27 08:50:06 -0700384 return result
maruel@chromium.org0437a732013-08-27 16:05:52 +0000385
386
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700387def yield_results(
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700388 swarm_base_url, task_keys, timeout, max_threads,
389 print_status_updates, output_collector):
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500390 """Yields swarming task results from the swarming server as (index, result).
maruel@chromium.org0437a732013-08-27 16:05:52 +0000391
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700392 Duplicate shards are ignored. Shards are yielded in order of completion.
393 Timed out shards are NOT yielded at all. Caller can compare number of yielded
394 shards with len(task_keys) to verify all shards completed.
maruel@chromium.org0437a732013-08-27 16:05:52 +0000395
396 max_threads is optional and is used to limit the number of parallel fetches
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500397 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 +0000398 worth normally to limit the number threads. Mostly used for testing purposes.
Marc-Antoine Ruel5c720342014-02-21 14:46:14 -0500399
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700400 output_collector is an optional instance of TaskOutputCollector that will be
401 used to fetch files produced by a task from isolate server to the local disk.
402
Marc-Antoine Ruel5c720342014-02-21 14:46:14 -0500403 Yields:
404 (index, result). In particular, 'result' is defined as the
405 GetRunnerResults() function in services/swarming/server/test_runner.py.
maruel@chromium.org0437a732013-08-27 16:05:52 +0000406 """
maruel@chromium.org0437a732013-08-27 16:05:52 +0000407 number_threads = (
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500408 min(max_threads, len(task_keys)) if max_threads else len(task_keys))
Vadim Shtayurab19319e2014-04-27 08:50:06 -0700409 should_stop = threading.Event()
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700410 results_channel = threading_utils.TaskChannel()
Vadim Shtayurab19319e2014-04-27 08:50:06 -0700411
maruel@chromium.org0437a732013-08-27 16:05:52 +0000412 with threading_utils.ThreadPool(number_threads, number_threads, 0) as pool:
413 try:
Vadim Shtayurab450c602014-05-12 19:23:25 -0700414 # Adds a task to the thread pool to call 'retrieve_results' and return
415 # the results together with shard_index that produced them (as a tuple).
416 def enqueue_retrieve_results(shard_index, task_key):
417 task_fn = lambda *args: (shard_index, retrieve_results(*args))
maruel@chromium.org0437a732013-08-27 16:05:52 +0000418 pool.add_task(
Vadim Shtayurab450c602014-05-12 19:23:25 -0700419 0, results_channel.wrap_task(task_fn),
420 swarm_base_url, shard_index, task_key, timeout,
421 should_stop, output_collector)
422
423 # Enqueue 'retrieve_results' calls for each shard key to run in parallel.
424 for shard_index, task_key in enumerate(task_keys):
425 enqueue_retrieve_results(shard_index, task_key)
Vadim Shtayurab19319e2014-04-27 08:50:06 -0700426
427 # Wait for all of them to finish.
428 shards_remaining = range(len(task_keys))
429 active_task_count = len(task_keys)
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700430 while active_task_count:
Vadim Shtayurab450c602014-05-12 19:23:25 -0700431 shard_index, result = None, None
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700432 try:
Vadim Shtayurab450c602014-05-12 19:23:25 -0700433 shard_index, result = results_channel.pull(
434 timeout=STATUS_UPDATE_INTERVAL)
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700435 except threading_utils.TaskChannel.Timeout:
436 if print_status_updates:
437 print(
438 'Waiting for results from the following shards: %s' %
439 ', '.join(map(str, shards_remaining)))
440 sys.stdout.flush()
441 continue
442 except Exception:
443 logging.exception('Unexpected exception in retrieve_results')
Vadim Shtayurab19319e2014-04-27 08:50:06 -0700444
445 # A call to 'retrieve_results' finished (successfully or not).
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700446 active_task_count -= 1
maruel@chromium.org0437a732013-08-27 16:05:52 +0000447 if not result:
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500448 logging.error('Failed to retrieve the results for a swarming key')
maruel@chromium.org0437a732013-08-27 16:05:52 +0000449 continue
Vadim Shtayurab19319e2014-04-27 08:50:06 -0700450
Vadim Shtayurab450c602014-05-12 19:23:25 -0700451 # Yield back results to the caller.
452 assert shard_index in shards_remaining
453 shards_remaining.remove(shard_index)
454 yield shard_index, result
Vadim Shtayurab19319e2014-04-27 08:50:06 -0700455
maruel@chromium.org0437a732013-08-27 16:05:52 +0000456 finally:
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700457 # Done or aborted with Ctrl+C, kill the remaining threads.
maruel@chromium.org0437a732013-08-27 16:05:52 +0000458 should_stop.set()
459
460
Vadim Shtayurab450c602014-05-12 19:23:25 -0700461def setup_run_isolated(manifest, bundle):
462 """Sets up the manifest to run an isolated task via run_isolated.py.
maruel@chromium.org0437a732013-08-27 16:05:52 +0000463
Vadim Shtayurab450c602014-05-12 19:23:25 -0700464 Modifies |bundle| (by adding files) and |manifest| (by adding commands) in
465 place.
466
467 Args:
468 manifest: Manifest with swarm task definition.
469 bundle: ZipPackage with files that would be transfered to swarm bot.
470 If None, only |manifest| is modified (useful in tests).
maruel@chromium.org0437a732013-08-27 16:05:52 +0000471 """
vadimsh@chromium.org6b706212013-08-28 15:03:46 +0000472 # Add uncompressed zip here. It'll be compressed as part of the package sent
473 # to Swarming server.
474 run_test_name = 'run_isolated.zip'
Vadim Shtayurab450c602014-05-12 19:23:25 -0700475 if bundle and run_test_name not in bundle.files:
476 bundle.add_buffer(
477 run_test_name,
478 run_isolated.get_as_zip_package().zip_into_buffer(compress=False))
maruel@chromium.org0437a732013-08-27 16:05:52 +0000479
vadimsh@chromium.org6b706212013-08-28 15:03:46 +0000480 cleanup_script_name = 'swarm_cleanup.py'
Vadim Shtayurab450c602014-05-12 19:23:25 -0700481 if bundle and cleanup_script_name not in bundle.files:
482 bundle.add_file(
483 os.path.join(TOOLS_PATH, cleanup_script_name), cleanup_script_name)
vadimsh@chromium.org6b706212013-08-28 15:03:46 +0000484
maruel@chromium.org0437a732013-08-27 16:05:52 +0000485 run_cmd = [
486 'python', run_test_name,
maruel@chromium.org814d23f2013-10-01 19:08:00 +0000487 '--hash', manifest.isolated_hash,
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500488 '--namespace', manifest.namespace,
maruel@chromium.org0437a732013-08-27 16:05:52 +0000489 ]
Marc-Antoine Ruel8806e622014-02-12 14:15:53 -0500490 if file_path.is_url(manifest.isolate_server):
491 run_cmd.extend(('--isolate-server', manifest.isolate_server))
492 else:
493 run_cmd.extend(('--indir', manifest.isolate_server))
494
maruel@chromium.org0437a732013-08-27 16:05:52 +0000495 if manifest.verbose or manifest.profile:
496 # Have it print the profiling section.
497 run_cmd.append('--verbose')
Vadim Shtayuraae8085b2014-05-02 17:13:10 -0700498
499 # Pass all extra args for run_isolated.py, it will pass them to the command.
500 if manifest.extra_args:
501 run_cmd.append('--')
502 run_cmd.extend(manifest.extra_args)
503
maruel@chromium.org0437a732013-08-27 16:05:52 +0000504 manifest.add_task('Run Test', run_cmd)
505
506 # Clean up
507 manifest.add_task('Clean Up', ['python', cleanup_script_name])
508
509
Vadim Shtayurab450c602014-05-12 19:23:25 -0700510def setup_googletest(env, shards, index):
Marc-Antoine Ruelcd629732013-12-20 15:00:42 -0500511 """Sets googletest specific environment variables."""
512 if shards > 1:
513 env = env.copy()
Vadim Shtayurab450c602014-05-12 19:23:25 -0700514 env['GTEST_SHARD_INDEX'] = str(index)
515 env['GTEST_TOTAL_SHARDS'] = str(shards)
Marc-Antoine Ruelcd629732013-12-20 15:00:42 -0500516 return env
517
518
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500519def archive(isolate_server, namespace, isolated, algo, verbose):
maruel@chromium.org0437a732013-08-27 16:05:52 +0000520 """Archives a .isolated and all the dependencies on the CAC."""
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500521 logging.info('archive(%s, %s, %s)', isolate_server, namespace, isolated)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000522 tempdir = None
Marc-Antoine Ruel8806e622014-02-12 14:15:53 -0500523 if file_path.is_url(isolate_server):
524 command = 'archive'
525 flag = '--isolate-server'
526 else:
527 command = 'hashtable'
528 flag = '--outdir'
529
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500530 print('Archiving: %s' % isolated)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000531 try:
maruel@chromium.org0437a732013-08-27 16:05:52 +0000532 cmd = [
533 sys.executable,
534 os.path.join(ROOT_DIR, 'isolate.py'),
Marc-Antoine Ruel8806e622014-02-12 14:15:53 -0500535 command,
536 flag, isolate_server,
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500537 '--namespace', namespace,
maruel@chromium.org0437a732013-08-27 16:05:52 +0000538 '--isolated', isolated,
539 ]
maruel@chromium.orge9403ab2013-09-20 18:03:49 +0000540 cmd.extend(['--verbose'] * verbose)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000541 logging.info(' '.join(cmd))
542 if subprocess.call(cmd, verbose):
543 return
maruel@chromium.org7b844a62013-09-17 13:04:59 +0000544 return isolateserver.hash_file(isolated, algo)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000545 finally:
546 if tempdir:
547 shutil.rmtree(tempdir)
548
549
Vadim Shtayurab450c602014-05-12 19:23:25 -0700550def get_shard_task_name(task_name, shards, index):
551 """Returns a task name to use for a single shard of a task."""
552 if shards == 1:
553 return task_name
554 return '%s:%s:%s' % (task_name, shards, index)
555
556
557def upload_zip_bundle(isolate_server, bundle):
558 """Uploads a zip package to isolate storage and returns raw fetch URL.
559
560 Args:
561 isolate_server: URL of an isolate server.
562 bundle: instance of ZipPackage to upload.
563
564 Returns:
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -0400565 URL to get the file from.
Vadim Shtayurab450c602014-05-12 19:23:25 -0700566 """
567 # Swarming bot would need to be able to grab the file from the storage
568 # using raw HTTP GET. Use 'default' namespace so that the raw data returned
569 # to a bot is not zipped, since swarm_bot doesn't understand compressed
570 # data yet. This namespace have nothing to do with |namespace| passed to
571 # run_isolated.py that is used to store files for isolated task.
572 logging.info('Zipping up and uploading files...')
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -0400573 start_time = now()
574 isolate_item = isolateserver.BufferItem(
575 bundle.zip_into_buffer(), high_priority=True)
576 with isolateserver.get_storage(isolate_server, 'default') as storage:
577 uploaded = storage.upload_items([isolate_item])
578 bundle_url = storage.get_fetch_url(isolate_item)
579 elapsed = now() - start_time
Vadim Shtayurab450c602014-05-12 19:23:25 -0700580 if isolate_item in uploaded:
581 logging.info('Upload complete, time elapsed: %f', elapsed)
582 else:
583 logging.info('Zip file already on server, time elapsed: %f', elapsed)
584 return bundle_url
585
586
587def trigger_by_manifest(swarming, manifest):
588 """Given a task manifest, triggers it for execution on swarming.
589
590 Args:
591 swarming: URL of a swarming service.
592 manifest: instance of Manifest.
593
594 Returns:
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -0400595 tuple(Task id, priority) on success. tuple(None, None) on failure.
Vadim Shtayurab450c602014-05-12 19:23:25 -0700596 """
597 logging.info('Triggering: %s', manifest.task_name)
598 manifest_text = manifest.to_json()
599 result = net.url_read(swarming + '/test', data={'request': manifest_text})
600 if not result:
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -0400601 on_error.report('Failed to trigger task %s' % manifest.task_name)
Vadim Shtayura1c024f72014-07-09 19:00:10 -0700602 return None, None
Vadim Shtayurab450c602014-05-12 19:23:25 -0700603 try:
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -0400604 data = json.loads(result)
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -0400605 except (ValueError, TypeError):
Vadim Shtayurab450c602014-05-12 19:23:25 -0700606 msg = '\n'.join((
607 'Failed to trigger task %s' % manifest.task_name,
608 'Manifest: %s' % manifest_text,
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -0400609 'Bad response: %s' % result))
610 on_error.report(msg)
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -0400611 return None, None
612 if not data:
613 return None, None
614 return data['test_keys'][0]['test_key'], data['priority']
Vadim Shtayurab450c602014-05-12 19:23:25 -0700615
616
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -0400617def abort_task(_swarming, _manifest):
Vadim Shtayurab450c602014-05-12 19:23:25 -0700618 """Given a task manifest that was triggered, aborts its execution."""
619 # TODO(vadimsh): No supported by the server yet.
620
621
622def trigger_task_shards(
Vadim Shtayuraae8085b2014-05-02 17:13:10 -0700623 swarming, isolate_server, namespace, isolated_hash, task_name, extra_args,
Marc-Antoine Ruelaea50652014-06-12 14:23:48 -0400624 shards, dimensions, env, deadline, verbose, profile, priority):
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -0400625 """Triggers multiple subtasks of a sharded task.
626
627 Returns:
Vadim Shtayuraf27448e2014-06-26 11:35:05 -0700628 Dict with task details, returned to caller as part of --dump-json output.
629 None in case of failure.
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -0400630 """
Vadim Shtayurab450c602014-05-12 19:23:25 -0700631 # Collects all files that are necessary to bootstrap a task execution
632 # on the bot. Usually it includes self contained run_isolated.zip and
633 # a bunch of small other scripts. All heavy files are pulled
634 # by run_isolated.zip. Updated in 'setup_run_isolated'.
635 bundle = zip_package.ZipPackage(ROOT_DIR)
636
637 # Make a separate Manifest for each shard, put shard index and number of
638 # shards into env and subtask name.
639 manifests = []
640 for index in xrange(shards):
maruel@chromium.org0437a732013-08-27 16:05:52 +0000641 manifest = Manifest(
Marc-Antoine Ruela7049872013-11-05 19:28:35 -0500642 isolate_server=isolate_server,
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500643 namespace=namespace,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500644 isolated_hash=isolated_hash,
Vadim Shtayurab450c602014-05-12 19:23:25 -0700645 task_name=get_shard_task_name(task_name, shards, index),
Vadim Shtayuraae8085b2014-05-02 17:13:10 -0700646 extra_args=extra_args,
Marc-Antoine Ruel92f32422013-11-06 18:12:13 -0500647 dimensions=dimensions,
Vadim Shtayurab450c602014-05-12 19:23:25 -0700648 env=setup_googletest(env, shards, index),
Marc-Antoine Ruel13b7b782014-03-14 11:14:57 -0400649 deadline=deadline,
Marc-Antoine Ruela7049872013-11-05 19:28:35 -0500650 verbose=verbose,
651 profile=profile,
Vadim Shtayurabcff74f2014-02-27 16:19:34 -0800652 priority=priority)
Vadim Shtayurab450c602014-05-12 19:23:25 -0700653 setup_run_isolated(manifest, bundle)
654 manifests.append(manifest)
655
656 # Upload zip bundle file to get its URL.
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -0400657 try:
658 bundle_url = upload_zip_bundle(isolate_server, bundle)
659 except (IOError, OSError):
660 on_error.report('Failed to upload the zip file for task %s' % task_name)
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -0400661 return None, None
maruel@chromium.org0437a732013-08-27 16:05:52 +0000662
Vadim Shtayurab450c602014-05-12 19:23:25 -0700663 # Attach that file to all manifests.
664 for manifest in manifests:
665 manifest.add_bundled_file('swarm_data.zip', bundle_url)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000666
Vadim Shtayurab450c602014-05-12 19:23:25 -0700667 # Trigger all the subtasks.
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -0400668 tasks = {}
669 priority_warning = False
Vadim Shtayuraf27448e2014-06-26 11:35:05 -0700670 for index, manifest in enumerate(manifests):
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -0400671 task_id, priority = trigger_by_manifest(swarming, manifest)
672 if not task_id:
Vadim Shtayurab450c602014-05-12 19:23:25 -0700673 break
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -0400674 if not priority_warning and priority != manifest.priority:
675 priority_warning = True
676 print >> sys.stderr, 'Priority was reset to %s' % priority
Vadim Shtayuraf27448e2014-06-26 11:35:05 -0700677 tasks[manifest.task_name] = {
678 'shard_index': index,
679 'task_id': task_id,
680 'view_url': '%s/user/task/%s' % (swarming, task_id),
681 }
Vadim Shtayurab450c602014-05-12 19:23:25 -0700682
683 # Some shards weren't triggered. Abort everything.
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -0400684 if len(tasks) != len(manifests):
685 if tasks:
Vadim Shtayurab450c602014-05-12 19:23:25 -0700686 print >> sys.stderr, 'Not all shards were triggered'
Vadim Shtayuraf27448e2014-06-26 11:35:05 -0700687 for task_dict in tasks.itervalues():
688 abort_task(swarming, task_dict['task_id'])
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -0400689 return None
maruel@chromium.org0437a732013-08-27 16:05:52 +0000690
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -0400691 return tasks
maruel@chromium.org0437a732013-08-27 16:05:52 +0000692
693
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500694def isolated_to_hash(isolate_server, namespace, arg, algo, verbose):
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500695 """Archives a .isolated file if needed.
696
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500697 Returns the file hash to trigger and a bool specifying if it was a file (True)
698 or a hash (False).
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500699 """
700 if arg.endswith('.isolated'):
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500701 file_hash = archive(isolate_server, namespace, arg, algo, verbose)
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500702 if not file_hash:
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -0400703 on_error.report('Archival failure %s' % arg)
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500704 return None, True
705 return file_hash, True
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500706 elif isolateserver.is_valid_hash(arg, algo):
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500707 return arg, False
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500708 else:
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -0400709 on_error.report('Invalid hash %s' % arg)
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500710 return None, False
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500711
712
maruel@chromium.org0437a732013-08-27 16:05:52 +0000713def trigger(
Marc-Antoine Ruela7049872013-11-05 19:28:35 -0500714 swarming,
715 isolate_server,
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500716 namespace,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500717 file_hash_or_isolated,
718 task_name,
Vadim Shtayuraae8085b2014-05-02 17:13:10 -0700719 extra_args,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500720 shards,
Marc-Antoine Ruel92f32422013-11-06 18:12:13 -0500721 dimensions,
722 env,
Marc-Antoine Ruel13b7b782014-03-14 11:14:57 -0400723 deadline,
maruel@chromium.org0437a732013-08-27 16:05:52 +0000724 verbose,
725 profile,
726 priority):
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -0400727 """Sends off the hash swarming task requests.
728
729 Returns:
730 tuple(dict(task_name: task_id), base task name). The dict of tasks is None
731 in case of failure.
732 """
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500733 file_hash, is_file = isolated_to_hash(
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500734 isolate_server, namespace, file_hash_or_isolated, hashlib.sha1, verbose)
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500735 if not file_hash:
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500736 return 1, ''
737 if not task_name:
738 # If a file name was passed, use its base name of the isolated hash.
739 # Otherwise, use user name as an approximation of a task name.
740 if is_file:
741 key = os.path.splitext(os.path.basename(file_hash_or_isolated))[0]
742 else:
743 key = getpass.getuser()
Vadim Shtayurac3d97b02014-04-26 19:16:05 -0700744 task_name = '%s/%s/%s/%d' % (
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500745 key,
746 '_'.join('%s=%s' % (k, v) for k, v in sorted(dimensions.iteritems())),
Vadim Shtayurac3d97b02014-04-26 19:16:05 -0700747 file_hash,
748 now() * 1000)
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500749
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -0400750 tasks = trigger_task_shards(
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500751 swarming=swarming,
752 isolate_server=isolate_server,
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500753 namespace=namespace,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500754 isolated_hash=file_hash,
755 task_name=task_name,
Vadim Shtayuraae8085b2014-05-02 17:13:10 -0700756 extra_args=extra_args,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500757 shards=shards,
758 dimensions=dimensions,
Marc-Antoine Ruel13b7b782014-03-14 11:14:57 -0400759 deadline=deadline,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500760 env=env,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500761 verbose=verbose,
762 profile=profile,
Vadim Shtayurabcff74f2014-02-27 16:19:34 -0800763 priority=priority)
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -0400764 return tasks, task_name
maruel@chromium.org0437a732013-08-27 16:05:52 +0000765
766
Vadim Shtayurab450c602014-05-12 19:23:25 -0700767def decorate_shard_output(shard_index, result, shard_exit_code):
maruel@chromium.org0437a732013-08-27 16:05:52 +0000768 """Returns wrapped output for swarming task shard."""
769 tag = 'index %s (machine tag: %s, id: %s)' % (
Vadim Shtayurab450c602014-05-12 19:23:25 -0700770 shard_index,
maruel@chromium.org0437a732013-08-27 16:05:52 +0000771 result['machine_id'],
772 result.get('machine_tag', 'unknown'))
773 return (
774 '\n'
775 '================================================================\n'
776 'Begin output from shard %s\n'
777 '================================================================\n'
778 '\n'
779 '%s'
780 '================================================================\n'
Vadim Shtayura473455a2014-05-14 15:22:35 -0700781 'End output from shard %s.\nExit code %d (%s).\n'
782 '================================================================\n') % (
783 tag, result['output'] or NO_OUTPUT_FOUND, tag,
784 shard_exit_code, hex(0xffffffff & shard_exit_code))
maruel@chromium.org0437a732013-08-27 16:05:52 +0000785
786
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700787def collect(
Vadim Shtayurab450c602014-05-12 19:23:25 -0700788 url, task_name, shards, timeout, decorate,
Vadim Shtayurac8437bf2014-07-09 19:45:36 -0700789 print_status_updates, task_summary_json, task_output_dir):
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500790 """Retrieves results of a Swarming task."""
Vadim Shtayurab450c602014-05-12 19:23:25 -0700791 # Grab task keys for each shard. Order is important, used to figure out
792 # shard index based on the key.
793 # TODO(vadimsh): Simplify this once server support is added.
794 task_keys = []
795 for index in xrange(shards):
796 shard_task_name = get_shard_task_name(task_name, shards, index)
797 logging.info('Collecting %s', shard_task_name)
798 shard_task_keys = get_task_keys(url, shard_task_name)
799 if not shard_task_keys:
800 raise Failure('No task keys to get results with: %s' % shard_task_name)
801 if len(shard_task_keys) != 1:
802 raise Failure('Expecting only one shard for a task: %s' % shard_task_name)
803 task_keys.append(shard_task_keys[0])
maruel@chromium.org0437a732013-08-27 16:05:52 +0000804
Vadim Shtayurac8437bf2014-07-09 19:45:36 -0700805 # Collect summary JSON and output files (if task_output_dir is not None).
806 output_collector = TaskOutputCollector(
807 task_output_dir, task_name, len(task_keys))
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700808
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700809 seen_shards = set()
Vadim Shtayurac524f512014-05-15 09:54:56 -0700810 exit_codes = []
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700811
812 try:
813 for index, output in yield_results(
814 url, task_keys, timeout, None, print_status_updates, output_collector):
815 seen_shards.add(index)
Vadim Shtayura473455a2014-05-14 15:22:35 -0700816
817 # Grab first non-zero exit code as an overall shard exit code.
818 shard_exit_code = 0
819 for code in map(int, (output['exit_codes'] or '1').split(',')):
820 if code:
821 shard_exit_code = code
822 break
Vadim Shtayurac524f512014-05-15 09:54:56 -0700823 exit_codes.append(shard_exit_code)
Vadim Shtayura473455a2014-05-14 15:22:35 -0700824
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700825 if decorate:
Vadim Shtayurab450c602014-05-12 19:23:25 -0700826 print decorate_shard_output(index, output, shard_exit_code)
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700827 else:
828 print(
829 '%s/%s: %s' % (
830 output['machine_id'],
831 output['machine_tag'],
832 output['exit_codes']))
833 print(''.join(' %s\n' % l for l in output['output'].splitlines()))
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700834 finally:
Vadim Shtayurac8437bf2014-07-09 19:45:36 -0700835 summary = output_collector.finalize()
836 if task_summary_json:
837 tools.write_json(task_summary_json, summary, False)
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700838
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700839 if len(seen_shards) != len(task_keys):
840 missing_shards = [x for x in range(len(task_keys)) if x not in seen_shards]
841 print >> sys.stderr, ('Results from some shards are missing: %s' %
842 ', '.join(map(str, missing_shards)))
Vadim Shtayurac524f512014-05-15 09:54:56 -0700843 return 1
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700844
Vadim Shtayurac524f512014-05-15 09:54:56 -0700845 return int(bool(any(exit_codes)))
maruel@chromium.org0437a732013-08-27 16:05:52 +0000846
847
Marc-Antoine Ruel819fb162014-03-12 16:38:26 -0400848def add_filter_options(parser):
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500849 parser.filter_group = tools.optparse.OptionGroup(parser, 'Filtering slaves')
850 parser.filter_group.add_option(
Marc-Antoine Ruelb39e8cf2014-01-20 10:39:31 -0500851 '-d', '--dimension', default=[], action='append', nargs=2,
Marc-Antoine Ruel92f32422013-11-06 18:12:13 -0500852 dest='dimensions', metavar='FOO bar',
853 help='dimension to filter on')
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500854 parser.add_option_group(parser.filter_group)
855
Marc-Antoine Ruel819fb162014-03-12 16:38:26 -0400856
Marc-Antoine Ruel025e7822014-05-01 11:50:24 -0400857def process_filter_options(parser, options):
858 options.dimensions = dict(options.dimensions)
859 if not options.dimensions:
860 parser.error('Please at least specify one --dimension')
861
862
Vadim Shtayurab450c602014-05-12 19:23:25 -0700863def add_sharding_options(parser):
864 parser.sharding_group = tools.optparse.OptionGroup(parser, 'Sharding options')
865 parser.sharding_group.add_option(
866 '--shards', type='int', default=1,
867 help='Number of shards to trigger and collect.')
868 parser.add_option_group(parser.sharding_group)
869
870
Marc-Antoine Ruel819fb162014-03-12 16:38:26 -0400871def add_trigger_options(parser):
872 """Adds all options to trigger a task on Swarming."""
873 isolateserver.add_isolate_server_options(parser, True)
874 add_filter_options(parser)
875
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500876 parser.task_group = tools.optparse.OptionGroup(parser, 'Task properties')
877 parser.task_group.add_option(
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500878 '-e', '--env', default=[], action='append', nargs=2, metavar='FOO bar',
Vadim Shtayurab450c602014-05-12 19:23:25 -0700879 help='Environment variables to set')
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500880 parser.task_group.add_option(
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500881 '--priority', type='int', default=100,
882 help='The lower value, the more important the task is')
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500883 parser.task_group.add_option(
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500884 '-T', '--task-name',
885 help='Display name of the task. It uniquely identifies the task. '
Vadim Shtayurac3d97b02014-04-26 19:16:05 -0700886 'Defaults to <base_name>/<dimensions>/<isolated hash>/<timestamp> '
887 'if an isolated file is provided, if a hash is provided, it '
888 'defaults to <user>/<dimensions>/<isolated hash>/<timestamp>')
Marc-Antoine Ruel13b7b782014-03-14 11:14:57 -0400889 parser.task_group.add_option(
890 '--deadline', type='int', default=6*60*60,
891 help='Seconds to allow the task to be pending for a bot to run before '
892 'this task request expires.')
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500893 parser.add_option_group(parser.task_group)
Marc-Antoine Ruelcd629732013-12-20 15:00:42 -0500894 # TODO(maruel): This is currently written in a chromium-specific way.
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500895 parser.group_logging.add_option(
maruel@chromium.org0437a732013-08-27 16:05:52 +0000896 '--profile', action='store_true',
897 default=bool(os.environ.get('ISOLATE_DEBUG')),
898 help='Have run_isolated.py print profiling info')
maruel@chromium.org0437a732013-08-27 16:05:52 +0000899
900
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500901def process_trigger_options(parser, options, args):
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500902 isolateserver.process_isolate_server_options(parser, options)
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500903 if len(args) != 1:
904 parser.error('Must pass one .isolated file or its hash (sha1).')
Marc-Antoine Ruel025e7822014-05-01 11:50:24 -0400905 process_filter_options(parser, options)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000906
907
908def add_collect_options(parser):
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500909 parser.server_group.add_option(
maruel@chromium.org0437a732013-08-27 16:05:52 +0000910 '-t', '--timeout',
911 type='float',
912 default=DEFAULT_SHARD_WAIT_TIME,
913 help='Timeout to wait for result, set to 0 for no timeout; default: '
914 '%default s')
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500915 parser.group_logging.add_option(
916 '--decorate', action='store_true', help='Decorate output')
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700917 parser.group_logging.add_option(
918 '--print-status-updates', action='store_true',
919 help='Print periodic status updates')
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700920 parser.task_output_group = tools.optparse.OptionGroup(parser, 'Task output')
921 parser.task_output_group.add_option(
Vadim Shtayurac8437bf2014-07-09 19:45:36 -0700922 '--task-summary-json',
923 metavar='FILE',
924 help='Dump a summary of task results to this file as json. It contains '
925 'only shards statuses as know to server directly. Any output files '
926 'emitted by the task can be collected by using --task-output-dir')
927 parser.task_output_group.add_option(
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700928 '--task-output-dir',
Vadim Shtayurac8437bf2014-07-09 19:45:36 -0700929 metavar='DIR',
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700930 help='Directory to put task results into. When the task finishes, this '
Vadim Shtayurac8437bf2014-07-09 19:45:36 -0700931 'directory contains per-shard directory with output files produced '
932 'by shards: <task-output-dir>/<zero-based-shard-index>/.')
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700933 parser.add_option_group(parser.task_output_group)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000934
935
Vadim Shtayuraae8085b2014-05-02 17:13:10 -0700936def extract_isolated_command_extra_args(args):
937 try:
938 index = args.index('--')
939 except ValueError:
940 return (args, [])
941 return (args[:index], args[index+1:])
942
943
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500944@subcommand.usage('task_name')
maruel@chromium.org0437a732013-08-27 16:05:52 +0000945def CMDcollect(parser, args):
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500946 """Retrieves results of a Swarming task.
maruel@chromium.org0437a732013-08-27 16:05:52 +0000947
948 The result can be in multiple part if the execution was sharded. It can
949 potentially have retries.
950 """
951 add_collect_options(parser)
Vadim Shtayurab450c602014-05-12 19:23:25 -0700952 add_sharding_options(parser)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000953 (options, args) = parser.parse_args(args)
954 if not args:
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500955 parser.error('Must specify one task name.')
maruel@chromium.org0437a732013-08-27 16:05:52 +0000956 elif len(args) > 1:
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500957 parser.error('Must specify only one task name.')
maruel@chromium.org0437a732013-08-27 16:05:52 +0000958
Vadim Shtayura6b555c12014-07-23 16:22:18 -0700959 auth.ensure_logged_in(options.swarming)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000960 try:
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700961 return collect(
962 options.swarming,
963 args[0],
Vadim Shtayurab450c602014-05-12 19:23:25 -0700964 options.shards,
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700965 options.timeout,
966 options.decorate,
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700967 options.print_status_updates,
Vadim Shtayurac8437bf2014-07-09 19:45:36 -0700968 options.task_summary_json,
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700969 options.task_output_dir)
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -0400970 except Failure:
971 on_error.report(None)
vadimsh@chromium.orgd908a542013-10-30 01:36:17 +0000972 return 1
maruel@chromium.org0437a732013-08-27 16:05:52 +0000973
974
Marc-Antoine Ruel819fb162014-03-12 16:38:26 -0400975def CMDquery(parser, args):
976 """Returns information about the bots connected to the Swarming server."""
977 add_filter_options(parser)
978 parser.filter_group.add_option(
Marc-Antoine Ruel28083112014-03-13 16:34:04 -0400979 '--dead-only', action='store_true',
980 help='Only print dead bots, useful to reap them and reimage broken bots')
981 parser.filter_group.add_option(
Marc-Antoine Ruel819fb162014-03-12 16:38:26 -0400982 '-k', '--keep-dead', action='store_true',
983 help='Do not filter out dead bots')
984 parser.filter_group.add_option(
985 '-b', '--bare', action='store_true',
Marc-Antoine Ruele7b00162014-03-12 16:59:01 -0400986 help='Do not print out dimensions')
Marc-Antoine Ruel819fb162014-03-12 16:38:26 -0400987 options, args = parser.parse_args(args)
Marc-Antoine Ruel28083112014-03-13 16:34:04 -0400988
989 if options.keep_dead and options.dead_only:
990 parser.error('Use only one of --keep-dead and --dead-only')
Vadim Shtayura6b555c12014-07-23 16:22:18 -0700991
992 auth.ensure_logged_in(options.swarming)
Marc-Antoine Ruel0a620612014-08-13 15:47:07 -0400993 data = net.url_read_json(options.swarming + '/swarming/api/v1/bots')
Marc-Antoine Ruel819fb162014-03-12 16:38:26 -0400994 if data is None:
995 print >> sys.stderr, 'Failed to access %s' % options.swarming
996 return 1
Marc-Antoine Ruele4bebbc2014-06-04 09:36:14 -0400997 for machine in natsort.natsorted(data['machines'], key=lambda x: x['id']):
Marc-Antoine Ruel28083112014-03-13 16:34:04 -0400998 if options.dead_only:
Marc-Antoine Ruelfe844672014-07-31 11:16:51 -0400999 if not machine['is_dead']:
Marc-Antoine Ruel28083112014-03-13 16:34:04 -04001000 continue
Marc-Antoine Ruelfe844672014-07-31 11:16:51 -04001001 elif not options.keep_dead and machine['is_dead']:
Marc-Antoine Ruel819fb162014-03-12 16:38:26 -04001002 continue
1003
Marc-Antoine Ruele7b00162014-03-12 16:59:01 -04001004 # If the user requested to filter on dimensions, ensure the bot has all the
1005 # dimensions requested.
Marc-Antoine Ruel819fb162014-03-12 16:38:26 -04001006 dimensions = machine['dimensions']
1007 for key, value in options.dimensions:
1008 if key not in dimensions:
1009 break
Marc-Antoine Ruele7b00162014-03-12 16:59:01 -04001010 # A bot can have multiple value for a key, for example,
1011 # {'os': ['Windows', 'Windows-6.1']}, so that --dimension os=Windows will
1012 # be accepted.
Marc-Antoine Ruel819fb162014-03-12 16:38:26 -04001013 if isinstance(dimensions[key], list):
1014 if value not in dimensions[key]:
1015 break
1016 else:
1017 if value != dimensions[key]:
1018 break
1019 else:
Marc-Antoine Ruele4bebbc2014-06-04 09:36:14 -04001020 print machine['id']
Marc-Antoine Ruele7b00162014-03-12 16:59:01 -04001021 if not options.bare:
Marc-Antoine Ruel0a620612014-08-13 15:47:07 -04001022 print ' %s' % json.dumps(dimensions, sort_keys=True)
Marc-Antoine Ruel819fb162014-03-12 16:38:26 -04001023 return 0
1024
1025
Vadim Shtayuraae8085b2014-05-02 17:13:10 -07001026@subcommand.usage('(hash|isolated) [-- extra_args]')
maruel@chromium.org0437a732013-08-27 16:05:52 +00001027def CMDrun(parser, args):
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001028 """Triggers a task and wait for the results.
maruel@chromium.org0437a732013-08-27 16:05:52 +00001029
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001030 Basically, does everything to run a command remotely.
maruel@chromium.org0437a732013-08-27 16:05:52 +00001031 """
1032 add_trigger_options(parser)
1033 add_collect_options(parser)
Vadim Shtayurab450c602014-05-12 19:23:25 -07001034 add_sharding_options(parser)
Vadim Shtayuraae8085b2014-05-02 17:13:10 -07001035 args, isolated_cmd_args = extract_isolated_command_extra_args(args)
maruel@chromium.org0437a732013-08-27 16:05:52 +00001036 options, args = parser.parse_args(args)
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001037 process_trigger_options(parser, options, args)
maruel@chromium.org0437a732013-08-27 16:05:52 +00001038
Vadim Shtayura6b555c12014-07-23 16:22:18 -07001039 auth.ensure_logged_in(options.swarming)
1040 if file_path.is_url(options.isolate_server):
1041 auth.ensure_logged_in(options.isolate_server)
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001042 try:
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -04001043 tasks, task_name = trigger(
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001044 swarming=options.swarming,
Marc-Antoine Ruel8806e622014-02-12 14:15:53 -05001045 isolate_server=options.isolate_server or options.indir,
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -05001046 namespace=options.namespace,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001047 file_hash_or_isolated=args[0],
1048 task_name=options.task_name,
Vadim Shtayuraae8085b2014-05-02 17:13:10 -07001049 extra_args=isolated_cmd_args,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001050 shards=options.shards,
1051 dimensions=options.dimensions,
1052 env=dict(options.env),
Marc-Antoine Ruel13b7b782014-03-14 11:14:57 -04001053 deadline=options.deadline,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001054 verbose=options.verbose,
1055 profile=options.profile,
1056 priority=options.priority)
1057 except Failure as e:
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -04001058 on_error.report(
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001059 'Failed to trigger %s(%s): %s' %
1060 (options.task_name, args[0], e.args[0]))
1061 return 1
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -04001062 if not tasks:
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -04001063 on_error.report('Failed to trigger the task.')
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -04001064 return 1
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -05001065 if task_name != options.task_name:
1066 print('Triggered task: %s' % task_name)
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001067 try:
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -04001068 # TODO(maruel): Use task_ids, it's much more efficient!
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001069 return collect(
1070 options.swarming,
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -05001071 task_name,
Vadim Shtayurab450c602014-05-12 19:23:25 -07001072 options.shards,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001073 options.timeout,
Vadim Shtayura86a2cef2014-04-18 11:13:39 -07001074 options.decorate,
Vadim Shtayurae3fbd102014-04-29 17:05:21 -07001075 options.print_status_updates,
Vadim Shtayurac8437bf2014-07-09 19:45:36 -07001076 options.task_summary_json,
Vadim Shtayurae3fbd102014-04-29 17:05:21 -07001077 options.task_output_dir)
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -04001078 except Failure:
1079 on_error.report(None)
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001080 return 1
maruel@chromium.org0437a732013-08-27 16:05:52 +00001081
1082
Vadim Shtayuraae8085b2014-05-02 17:13:10 -07001083@subcommand.usage("(hash|isolated) [-- extra_args]")
maruel@chromium.org0437a732013-08-27 16:05:52 +00001084def CMDtrigger(parser, args):
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001085 """Triggers a Swarming task.
maruel@chromium.org0437a732013-08-27 16:05:52 +00001086
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001087 Accepts either the hash (sha1) of a .isolated file already uploaded or the
1088 path to an .isolated file to archive, packages it if needed and sends a
1089 Swarming manifest file to the Swarming server.
1090
1091 If an .isolated file is specified instead of an hash, it is first archived.
Vadim Shtayuraae8085b2014-05-02 17:13:10 -07001092
1093 Passes all extra arguments provided after '--' as additional command line
1094 arguments for an isolated command specified in *.isolate file.
maruel@chromium.org0437a732013-08-27 16:05:52 +00001095 """
1096 add_trigger_options(parser)
Vadim Shtayurab450c602014-05-12 19:23:25 -07001097 add_sharding_options(parser)
Vadim Shtayuraae8085b2014-05-02 17:13:10 -07001098 args, isolated_cmd_args = extract_isolated_command_extra_args(args)
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -04001099 parser.add_option(
1100 '--dump-json',
1101 metavar='FILE',
1102 help='Dump details about the triggered task(s) to this file as json')
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001103 options, args = parser.parse_args(args)
1104 process_trigger_options(parser, options, args)
maruel@chromium.org0437a732013-08-27 16:05:52 +00001105
Vadim Shtayura6b555c12014-07-23 16:22:18 -07001106 auth.ensure_logged_in(options.swarming)
1107 if file_path.is_url(options.isolate_server):
1108 auth.ensure_logged_in(options.isolate_server)
maruel@chromium.org0437a732013-08-27 16:05:52 +00001109 try:
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -04001110 tasks, task_name = trigger(
Marc-Antoine Ruela7049872013-11-05 19:28:35 -05001111 swarming=options.swarming,
Marc-Antoine Ruel8806e622014-02-12 14:15:53 -05001112 isolate_server=options.isolate_server or options.indir,
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -05001113 namespace=options.namespace,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001114 file_hash_or_isolated=args[0],
1115 task_name=options.task_name,
Vadim Shtayuraae8085b2014-05-02 17:13:10 -07001116 extra_args=isolated_cmd_args,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001117 shards=options.shards,
Vadim Shtayuraae8085b2014-05-02 17:13:10 -07001118 dimensions=options.dimensions,
Marc-Antoine Ruel92f32422013-11-06 18:12:13 -05001119 env=dict(options.env),
Marc-Antoine Ruel13b7b782014-03-14 11:14:57 -04001120 deadline=options.deadline,
Marc-Antoine Ruela7049872013-11-05 19:28:35 -05001121 verbose=options.verbose,
1122 profile=options.profile,
1123 priority=options.priority)
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -04001124 if tasks:
1125 if task_name != options.task_name:
1126 print('Triggered task: %s' % task_name)
1127 if options.dump_json:
1128 data = {
1129 'base_task_name': task_name,
1130 'tasks': tasks,
1131 }
1132 tools.write_json(options.dump_json, data, True)
1133 return int(not tasks)
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -04001134 except Failure:
1135 on_error.report(None)
vadimsh@chromium.orgd908a542013-10-30 01:36:17 +00001136 return 1
maruel@chromium.org0437a732013-08-27 16:05:52 +00001137
1138
1139class OptionParserSwarming(tools.OptionParserWithLogging):
1140 def __init__(self, **kwargs):
1141 tools.OptionParserWithLogging.__init__(
1142 self, prog='swarming.py', **kwargs)
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -05001143 self.server_group = tools.optparse.OptionGroup(self, 'Server')
1144 self.server_group.add_option(
maruel@chromium.orge9403ab2013-09-20 18:03:49 +00001145 '-S', '--swarming',
Kevin Graney5346c162014-01-24 12:20:01 -05001146 metavar='URL', default=os.environ.get('SWARMING_SERVER', ''),
maruel@chromium.orge9403ab2013-09-20 18:03:49 +00001147 help='Swarming server to use')
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -05001148 self.add_option_group(self.server_group)
Vadim Shtayurae34e13a2014-02-02 11:23:26 -08001149 auth.add_auth_options(self)
maruel@chromium.org0437a732013-08-27 16:05:52 +00001150
1151 def parse_args(self, *args, **kwargs):
1152 options, args = tools.OptionParserWithLogging.parse_args(
1153 self, *args, **kwargs)
1154 options.swarming = options.swarming.rstrip('/')
1155 if not options.swarming:
1156 self.error('--swarming is required.')
Vadim Shtayura5d1efce2014-02-04 10:55:43 -08001157 auth.process_auth_options(self, options)
maruel@chromium.org0437a732013-08-27 16:05:52 +00001158 return options, args
1159
1160
1161def main(args):
1162 dispatcher = subcommand.CommandDispatcher(__name__)
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -04001163 return dispatcher.execute(OptionParserSwarming(version=__version__), args)
maruel@chromium.org0437a732013-08-27 16:05:52 +00001164
1165
1166if __name__ == '__main__':
1167 fix_encoding.fix_encoding()
1168 tools.disable_buffering()
1169 colorama.init()
1170 sys.exit(main(sys.argv[1:]))