blob: bdd2baee57bb5b3192685403b91696d69c3ad0af [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 Ruel2f6581a2014-10-03 11:09:53 -04008__version__ = '0.5.2'
maruel@chromium.org0437a732013-08-27 16:05:52 +00009
10import hashlib
11import json
12import logging
13import os
Vadim Shtayurae3fbd102014-04-29 17:05:21 -070014import re
maruel@chromium.org0437a732013-08-27 16:05:52 +000015import shutil
maruel@chromium.org0437a732013-08-27 16:05:52 +000016import subprocess
17import sys
Vadim Shtayurab19319e2014-04-27 08:50:06 -070018import threading
maruel@chromium.org0437a732013-08-27 16:05:52 +000019import time
20import urllib
maruel@chromium.org0437a732013-08-27 16:05:52 +000021
22from third_party import colorama
23from third_party.depot_tools import fix_encoding
24from third_party.depot_tools import subcommand
vadimsh@chromium.org6b706212013-08-28 15:03:46 +000025
Marc-Antoine Ruel8806e622014-02-12 14:15:53 -050026from utils import file_path
Marc-Antoine Ruel819fb162014-03-12 16:38:26 -040027from third_party.chromium import natsort
vadimsh@chromium.org6b706212013-08-28 15:03:46 +000028from utils import net
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -040029from utils import on_error
maruel@chromium.org0437a732013-08-27 16:05:52 +000030from utils import threading_utils
vadimsh@chromium.org6b706212013-08-28 15:03:46 +000031from utils import tools
32from utils import zip_package
maruel@chromium.org0437a732013-08-27 16:05:52 +000033
Vadim Shtayurae34e13a2014-02-02 11:23:26 -080034import auth
Marc-Antoine Ruel8bee66d2014-08-28 19:02:07 -040035import isolated_format
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
Vadim Shtayura86a2cef2014-04-18 11:13:39 -070044# How often to print status updates to stdout in 'collect'.
45STATUS_UPDATE_INTERVAL = 15 * 60.
46
Marc-Antoine Ruel2f6581a2014-10-03 11:09:53 -040047
Marc-Antoine Ruel12a7da42014-10-01 08:29:47 -040048class State(object):
49 """States in which a task can be.
maruel@chromium.org0437a732013-08-27 16:05:52 +000050
Marc-Antoine Ruel12a7da42014-10-01 08:29:47 -040051 WARNING: Copy-pasted from appengine/swarming/server/task_result.py. These
52 values are part of the API so if they change, the API changed.
53
54 It's in fact an enum. Values should be in decreasing order of importance.
55 """
56 RUNNING = 0x10
57 PENDING = 0x20
58 EXPIRED = 0x30
59 TIMED_OUT = 0x40
60 BOT_DIED = 0x50
61 CANCELED = 0x60
62 COMPLETED = 0x70
63
64 STATES = (RUNNING, PENDING, EXPIRED, TIMED_OUT, BOT_DIED, CANCELED, COMPLETED)
65 STATES_RUNNING = (RUNNING, PENDING)
66 STATES_NOT_RUNNING = (EXPIRED, TIMED_OUT, BOT_DIED, CANCELED, COMPLETED)
67 STATES_DONE = (TIMED_OUT, COMPLETED)
68 STATES_ABANDONED = (EXPIRED, BOT_DIED, CANCELED)
69
70 _NAMES = {
71 RUNNING: 'Running',
72 PENDING: 'Pending',
73 EXPIRED: 'Expired',
74 TIMED_OUT: 'Execution timed out',
75 BOT_DIED: 'Bot died',
76 CANCELED: 'User canceled',
77 COMPLETED: 'Completed',
78 }
79
80 @classmethod
81 def to_string(cls, state):
82 """Returns a user-readable string representing a State."""
83 if state not in cls._NAMES:
84 raise ValueError('Invalid state %s' % state)
85 return cls._NAMES[state]
maruel@chromium.org0437a732013-08-27 16:05:52 +000086
87
maruel@chromium.org0437a732013-08-27 16:05:52 +000088class Failure(Exception):
89 """Generic failure."""
90 pass
91
92
Vadim Shtayurae3fbd102014-04-29 17:05:21 -070093class TaskOutputCollector(object):
Vadim Shtayurac8437bf2014-07-09 19:45:36 -070094 """Assembles task execution summary (for --task-summary-json output).
95
96 Optionally fetches task outputs from isolate server to local disk (used when
97 --task-output-dir is passed).
Vadim Shtayurae3fbd102014-04-29 17:05:21 -070098
99 This object is shared among multiple threads running 'retrieve_results'
100 function, in particular they call 'process_shard_result' method in parallel.
101 """
102
103 def __init__(self, task_output_dir, task_name, shard_count):
104 """Initializes TaskOutputCollector, ensures |task_output_dir| exists.
105
106 Args:
Vadim Shtayurac8437bf2014-07-09 19:45:36 -0700107 task_output_dir: (optional) local directory to put fetched files to.
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700108 task_name: name of the swarming task results belong to.
109 shard_count: expected number of task shards.
110 """
111 self.task_output_dir = task_output_dir
112 self.task_name = task_name
113 self.shard_count = shard_count
114
115 self._lock = threading.Lock()
116 self._per_shard_results = {}
117 self._storage = None
118
Vadim Shtayurac8437bf2014-07-09 19:45:36 -0700119 if self.task_output_dir and not os.path.isdir(self.task_output_dir):
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700120 os.makedirs(self.task_output_dir)
121
Vadim Shtayurab450c602014-05-12 19:23:25 -0700122 def process_shard_result(self, shard_index, result):
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700123 """Stores results of a single task shard, fetches output files if necessary.
124
Marc-Antoine Ruele4dcbb82014-10-01 09:30:56 -0400125 Modifies |result| in place.
126
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700127 Called concurrently from multiple threads.
128 """
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700129 # Sanity check index is in expected range.
Vadim Shtayurab450c602014-05-12 19:23:25 -0700130 assert isinstance(shard_index, int)
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700131 if shard_index < 0 or shard_index >= self.shard_count:
132 logging.warning(
133 'Shard index %d is outside of expected range: [0; %d]',
134 shard_index, self.shard_count - 1)
135 return
136
Marc-Antoine Ruele4dcbb82014-10-01 09:30:56 -0400137 assert not 'isolated_out' in result
Marc-Antoine Ruel12a7da42014-10-01 08:29:47 -0400138 result['isolated_out'] = None
139 for output in result['outputs']:
140 isolated_files_location = extract_output_files_location(output)
141 if isolated_files_location:
142 if result['isolated_out']:
143 raise ValueError('Unexpected two task with output')
144 result['isolated_out'] = isolated_files_location
Kevin Graneyc2c3b9e2014-08-26 09:04:17 -0400145
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700146 # Store result dict of that shard, ignore results we've already seen.
147 with self._lock:
148 if shard_index in self._per_shard_results:
149 logging.warning('Ignoring duplicate shard index %d', shard_index)
150 return
151 self._per_shard_results[shard_index] = result
152
153 # Fetch output files if necessary.
Marc-Antoine Ruele4dcbb82014-10-01 09:30:56 -0400154 if self.task_output_dir and result['isolated_out']:
155 storage = self._get_storage(
156 result['isolated_out']['server'],
157 result['isolated_out']['namespace'])
158 if storage:
159 # Output files are supposed to be small and they are not reused across
160 # tasks. So use MemoryCache for them instead of on-disk cache. Make
161 # files writable, so that calling script can delete them.
162 isolateserver.fetch_isolated(
163 result['isolated_out']['hash'],
164 storage,
165 isolateserver.MemoryCache(file_mode_mask=0700),
166 os.path.join(self.task_output_dir, str(shard_index)),
167 False)
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700168
169 def finalize(self):
Vadim Shtayurac8437bf2014-07-09 19:45:36 -0700170 """Assembles and returns task summary JSON, shutdowns underlying Storage."""
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700171 with self._lock:
172 # Write an array of shard results with None for missing shards.
173 summary = {
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700174 'shards': [
175 self._per_shard_results.get(i) for i in xrange(self.shard_count)
176 ],
177 }
Vadim Shtayurac8437bf2014-07-09 19:45:36 -0700178 # Write summary.json to task_output_dir as well.
179 if self.task_output_dir:
180 tools.write_json(
181 os.path.join(self.task_output_dir, 'summary.json'),
182 summary,
183 False)
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700184 if self._storage:
185 self._storage.close()
186 self._storage = None
Vadim Shtayurac8437bf2014-07-09 19:45:36 -0700187 return summary
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700188
189 def _get_storage(self, isolate_server, namespace):
190 """Returns isolateserver.Storage to use to fetch files."""
Vadim Shtayurac8437bf2014-07-09 19:45:36 -0700191 assert self.task_output_dir
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700192 with self._lock:
193 if not self._storage:
194 self._storage = isolateserver.get_storage(isolate_server, namespace)
195 else:
196 # Shards must all use exact same isolate server and namespace.
197 if self._storage.location != isolate_server:
198 logging.error(
199 'Task shards are using multiple isolate servers: %s and %s',
200 self._storage.location, isolate_server)
201 return None
202 if self._storage.namespace != namespace:
203 logging.error(
204 'Task shards are using multiple namespaces: %s and %s',
205 self._storage.namespace, namespace)
206 return None
207 return self._storage
208
209
maruel@chromium.org0437a732013-08-27 16:05:52 +0000210def now():
211 """Exists so it can be mocked easily."""
212 return time.time()
213
214
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700215def extract_output_files_location(task_log):
216 """Task log -> location of task output files to fetch.
217
218 TODO(vadimsh,maruel): Use side-channel to get this information.
219 See 'run_tha_test' in run_isolated.py for where the data is generated.
220
221 Returns:
222 Tuple (isolate server URL, namespace, isolated hash) on success.
223 None if information is missing or can not be parsed.
224 """
Marc-Antoine Ruel12a7da42014-10-01 08:29:47 -0400225 if not task_log:
226 return None
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700227 match = re.search(
228 r'\[run_isolated_out_hack\](.*)\[/run_isolated_out_hack\]',
229 task_log,
230 re.DOTALL)
231 if not match:
232 return None
233
234 def to_ascii(val):
235 if not isinstance(val, basestring):
236 raise ValueError()
237 return val.encode('ascii')
238
239 try:
240 data = json.loads(match.group(1))
241 if not isinstance(data, dict):
242 raise ValueError()
243 isolated_hash = to_ascii(data['hash'])
244 namespace = to_ascii(data['namespace'])
245 isolate_server = to_ascii(data['storage'])
246 if not file_path.is_url(isolate_server):
247 raise ValueError()
Kevin Graneyc2c3b9e2014-08-26 09:04:17 -0400248 data = {
249 'hash': isolated_hash,
250 'namespace': namespace,
251 'server': isolate_server,
252 'view_url': '%s/browse?%s' % (isolate_server, urllib.urlencode(
253 [('namespace', namespace), ('hash', isolated_hash)])),
254 }
255 return data
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700256 except (KeyError, ValueError):
257 logging.warning(
258 'Unexpected value of run_isolated_out_hack: %s', match.group(1))
259 return None
260
261
262def retrieve_results(
Marc-Antoine Ruel12a7da42014-10-01 08:29:47 -0400263 base_url, shard_index, task_id, timeout, should_stop, output_collector):
264 """Retrieves results for a single task ID.
Vadim Shtayurab19319e2014-04-27 08:50:06 -0700265
Vadim Shtayurab450c602014-05-12 19:23:25 -0700266 Returns:
267 <result dict> on success.
268 None on failure.
Vadim Shtayurab19319e2014-04-27 08:50:06 -0700269 """
maruel@chromium.org814d23f2013-10-01 19:08:00 +0000270 assert isinstance(timeout, float), timeout
Marc-Antoine Ruel12a7da42014-10-01 08:29:47 -0400271 result_url = '%s/swarming/api/v1/client/task/%s' % (base_url, task_id)
272 output_url = '%s/swarming/api/v1/client/task/%s/output/all' % (
273 base_url, task_id)
Vadim Shtayurab19319e2014-04-27 08:50:06 -0700274 started = now()
275 deadline = started + timeout if timeout else None
276 attempt = 0
277
278 while not should_stop.is_set():
279 attempt += 1
280
281 # Waiting for too long -> give up.
282 current_time = now()
283 if deadline and current_time >= deadline:
284 logging.error('retrieve_results(%s) timed out on attempt %d',
285 base_url, attempt)
286 return None
287
288 # Do not spin too fast. Spin faster at the beginning though.
289 # Start with 1 sec delay and for each 30 sec of waiting add another second
290 # of delay, until hitting 15 sec ceiling.
291 if attempt > 1:
292 max_delay = min(15, 1 + (current_time - started) / 30.0)
293 delay = min(max_delay, deadline - current_time) if deadline else max_delay
294 if delay > 0:
295 logging.debug('Waiting %.1f sec before retrying', delay)
296 should_stop.wait(delay)
297 if should_stop.is_set():
298 return None
299
Marc-Antoine Ruel12a7da42014-10-01 08:29:47 -0400300 # Disable internal retries in net.url_read_json, since we are doing retries
301 # ourselves.
302 # TODO(maruel): We'd need to know if it's a 404 and not retry at all.
303 result = net.url_read_json(result_url, retry_50x=False)
304 if not result:
Marc-Antoine Ruel200b3952014-08-14 11:07:44 -0400305 continue
Marc-Antoine Ruel12a7da42014-10-01 08:29:47 -0400306 if result['state'] in State.STATES_NOT_RUNNING:
307 out = net.url_read_json(output_url)
308 result['outputs'] = (out or {}).get('outputs', [])
309 if not result['outputs']:
310 logging.error('No output found for task %s', task_id)
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700311 # Record the result, try to fetch attached output files (if any).
312 if output_collector:
313 # TODO(vadimsh): Respect |should_stop| and |deadline| when fetching.
Vadim Shtayurab450c602014-05-12 19:23:25 -0700314 output_collector.process_shard_result(shard_index, result)
Vadim Shtayurab19319e2014-04-27 08:50:06 -0700315 return result
maruel@chromium.org0437a732013-08-27 16:05:52 +0000316
317
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700318def yield_results(
Marc-Antoine Ruel12a7da42014-10-01 08:29:47 -0400319 swarm_base_url, task_ids, timeout, max_threads, print_status_updates,
320 output_collector):
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500321 """Yields swarming task results from the swarming server as (index, result).
maruel@chromium.org0437a732013-08-27 16:05:52 +0000322
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700323 Duplicate shards are ignored. Shards are yielded in order of completion.
324 Timed out shards are NOT yielded at all. Caller can compare number of yielded
325 shards with len(task_keys) to verify all shards completed.
maruel@chromium.org0437a732013-08-27 16:05:52 +0000326
327 max_threads is optional and is used to limit the number of parallel fetches
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500328 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 +0000329 worth normally to limit the number threads. Mostly used for testing purposes.
Marc-Antoine Ruel5c720342014-02-21 14:46:14 -0500330
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700331 output_collector is an optional instance of TaskOutputCollector that will be
332 used to fetch files produced by a task from isolate server to the local disk.
333
Marc-Antoine Ruel5c720342014-02-21 14:46:14 -0500334 Yields:
335 (index, result). In particular, 'result' is defined as the
336 GetRunnerResults() function in services/swarming/server/test_runner.py.
maruel@chromium.org0437a732013-08-27 16:05:52 +0000337 """
maruel@chromium.org0437a732013-08-27 16:05:52 +0000338 number_threads = (
Marc-Antoine Ruel12a7da42014-10-01 08:29:47 -0400339 min(max_threads, len(task_ids)) if max_threads else len(task_ids))
Vadim Shtayurab19319e2014-04-27 08:50:06 -0700340 should_stop = threading.Event()
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700341 results_channel = threading_utils.TaskChannel()
Vadim Shtayurab19319e2014-04-27 08:50:06 -0700342
maruel@chromium.org0437a732013-08-27 16:05:52 +0000343 with threading_utils.ThreadPool(number_threads, number_threads, 0) as pool:
344 try:
Vadim Shtayurab450c602014-05-12 19:23:25 -0700345 # Adds a task to the thread pool to call 'retrieve_results' and return
346 # the results together with shard_index that produced them (as a tuple).
Marc-Antoine Ruel12a7da42014-10-01 08:29:47 -0400347 def enqueue_retrieve_results(shard_index, task_id):
Vadim Shtayurab450c602014-05-12 19:23:25 -0700348 task_fn = lambda *args: (shard_index, retrieve_results(*args))
maruel@chromium.org0437a732013-08-27 16:05:52 +0000349 pool.add_task(
Marc-Antoine Ruel12a7da42014-10-01 08:29:47 -0400350 0, results_channel.wrap_task(task_fn), swarm_base_url, shard_index,
351 task_id, timeout, should_stop, output_collector)
Vadim Shtayurab450c602014-05-12 19:23:25 -0700352
353 # Enqueue 'retrieve_results' calls for each shard key to run in parallel.
Marc-Antoine Ruel12a7da42014-10-01 08:29:47 -0400354 for shard_index, task_id in enumerate(task_ids):
355 enqueue_retrieve_results(shard_index, task_id)
Vadim Shtayurab19319e2014-04-27 08:50:06 -0700356
357 # Wait for all of them to finish.
Marc-Antoine Ruel12a7da42014-10-01 08:29:47 -0400358 shards_remaining = range(len(task_ids))
359 active_task_count = len(task_ids)
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700360 while active_task_count:
Vadim Shtayurab450c602014-05-12 19:23:25 -0700361 shard_index, result = None, None
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700362 try:
Vadim Shtayurab450c602014-05-12 19:23:25 -0700363 shard_index, result = results_channel.pull(
364 timeout=STATUS_UPDATE_INTERVAL)
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700365 except threading_utils.TaskChannel.Timeout:
366 if print_status_updates:
367 print(
368 'Waiting for results from the following shards: %s' %
369 ', '.join(map(str, shards_remaining)))
370 sys.stdout.flush()
371 continue
372 except Exception:
373 logging.exception('Unexpected exception in retrieve_results')
Vadim Shtayurab19319e2014-04-27 08:50:06 -0700374
375 # A call to 'retrieve_results' finished (successfully or not).
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700376 active_task_count -= 1
maruel@chromium.org0437a732013-08-27 16:05:52 +0000377 if not result:
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500378 logging.error('Failed to retrieve the results for a swarming key')
maruel@chromium.org0437a732013-08-27 16:05:52 +0000379 continue
Vadim Shtayurab19319e2014-04-27 08:50:06 -0700380
Vadim Shtayurab450c602014-05-12 19:23:25 -0700381 # Yield back results to the caller.
382 assert shard_index in shards_remaining
383 shards_remaining.remove(shard_index)
384 yield shard_index, result
Vadim Shtayurab19319e2014-04-27 08:50:06 -0700385
maruel@chromium.org0437a732013-08-27 16:05:52 +0000386 finally:
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700387 # Done or aborted with Ctrl+C, kill the remaining threads.
maruel@chromium.org0437a732013-08-27 16:05:52 +0000388 should_stop.set()
389
390
Marc-Antoine Ruel2f6581a2014-10-03 11:09:53 -0400391def get_data(isolate_server):
392 """Returns the 'data' section with all files necessary to bootstrap a task
393 execution.
maruel@chromium.org0437a732013-08-27 16:05:52 +0000394 """
Marc-Antoine Ruel2f6581a2014-10-03 11:09:53 -0400395 bundle = zip_package.ZipPackage(ROOT_DIR)
396 bundle.add_buffer(
397 'run_isolated.zip',
398 run_isolated.get_as_zip_package().zip_into_buffer(compress=False))
399 bundle.add_file(
400 os.path.join(TOOLS_PATH, 'swarm_cleanup.py'), 'swarm_cleanup.py')
maruel@chromium.org0437a732013-08-27 16:05:52 +0000401
Marc-Antoine Ruel2f6581a2014-10-03 11:09:53 -0400402 # TODO(maruel): Get rid of this.
403 bundle_url = upload_zip_bundle(isolate_server, bundle)
404 return [(bundle_url, 'swarm_data.zip')]
vadimsh@chromium.org6b706212013-08-28 15:03:46 +0000405
Marc-Antoine Ruel2f6581a2014-10-03 11:09:53 -0400406
407def get_run_isolated_commands(
408 isolate_server, namespace, isolated_hash, extra_args, profile, verbose):
409 """Returns the 'commands' to run an isolated task via run_isolated.py.
410
411 Returns:
412 commands list to be added to the request.
413 """
maruel@chromium.org0437a732013-08-27 16:05:52 +0000414 run_cmd = [
Marc-Antoine Ruel2f6581a2014-10-03 11:09:53 -0400415 'python', 'run_isolated.zip',
416 '--hash', isolated_hash,
417 '--isolate-server', isolate_server,
418 '--namespace', namespace,
maruel@chromium.org0437a732013-08-27 16:05:52 +0000419 ]
Marc-Antoine Ruel470bbe42014-10-03 11:47:30 -0400420 # TODO(maruel): Decide what to do with profile.
421 if verbose or profile:
maruel@chromium.org0437a732013-08-27 16:05:52 +0000422 run_cmd.append('--verbose')
Vadim Shtayuraae8085b2014-05-02 17:13:10 -0700423 # Pass all extra args for run_isolated.py, it will pass them to the command.
Marc-Antoine Ruel2f6581a2014-10-03 11:09:53 -0400424 if extra_args:
Vadim Shtayuraae8085b2014-05-02 17:13:10 -0700425 run_cmd.append('--')
Marc-Antoine Ruel2f6581a2014-10-03 11:09:53 -0400426 run_cmd.extend(extra_args)
427 return [run_cmd, ['python', 'swarm_cleanup.py']]
maruel@chromium.org0437a732013-08-27 16:05:52 +0000428
429
Vadim Shtayurab450c602014-05-12 19:23:25 -0700430def setup_googletest(env, shards, index):
Marc-Antoine Ruelcd629732013-12-20 15:00:42 -0500431 """Sets googletest specific environment variables."""
432 if shards > 1:
433 env = env.copy()
Vadim Shtayurab450c602014-05-12 19:23:25 -0700434 env['GTEST_SHARD_INDEX'] = str(index)
435 env['GTEST_TOTAL_SHARDS'] = str(shards)
Marc-Antoine Ruelcd629732013-12-20 15:00:42 -0500436 return env
437
438
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500439def archive(isolate_server, namespace, isolated, algo, verbose):
maruel@chromium.org0437a732013-08-27 16:05:52 +0000440 """Archives a .isolated and all the dependencies on the CAC."""
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500441 logging.info('archive(%s, %s, %s)', isolate_server, namespace, isolated)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000442 tempdir = None
Marc-Antoine Ruel8806e622014-02-12 14:15:53 -0500443 if file_path.is_url(isolate_server):
444 command = 'archive'
445 flag = '--isolate-server'
446 else:
447 command = 'hashtable'
448 flag = '--outdir'
449
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500450 print('Archiving: %s' % isolated)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000451 try:
maruel@chromium.org0437a732013-08-27 16:05:52 +0000452 cmd = [
453 sys.executable,
454 os.path.join(ROOT_DIR, 'isolate.py'),
Marc-Antoine Ruel8806e622014-02-12 14:15:53 -0500455 command,
456 flag, isolate_server,
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500457 '--namespace', namespace,
maruel@chromium.org0437a732013-08-27 16:05:52 +0000458 '--isolated', isolated,
459 ]
maruel@chromium.orge9403ab2013-09-20 18:03:49 +0000460 cmd.extend(['--verbose'] * verbose)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000461 logging.info(' '.join(cmd))
462 if subprocess.call(cmd, verbose):
463 return
Marc-Antoine Ruel8bee66d2014-08-28 19:02:07 -0400464 return isolated_format.hash_file(isolated, algo)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000465 finally:
466 if tempdir:
467 shutil.rmtree(tempdir)
468
469
Marc-Antoine Ruel2f6581a2014-10-03 11:09:53 -0400470def get_shard_task_name(task_name, index, shards):
Vadim Shtayurab450c602014-05-12 19:23:25 -0700471 """Returns a task name to use for a single shard of a task."""
472 if shards == 1:
473 return task_name
Marc-Antoine Ruel2f6581a2014-10-03 11:09:53 -0400474 return '%s:%s:%s' % (task_name, index, shards)
Vadim Shtayurab450c602014-05-12 19:23:25 -0700475
476
477def upload_zip_bundle(isolate_server, bundle):
478 """Uploads a zip package to isolate storage and returns raw fetch URL.
479
480 Args:
481 isolate_server: URL of an isolate server.
482 bundle: instance of ZipPackage to upload.
483
484 Returns:
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -0400485 URL to get the file from.
Vadim Shtayurab450c602014-05-12 19:23:25 -0700486 """
487 # Swarming bot would need to be able to grab the file from the storage
488 # using raw HTTP GET. Use 'default' namespace so that the raw data returned
489 # to a bot is not zipped, since swarm_bot doesn't understand compressed
490 # data yet. This namespace have nothing to do with |namespace| passed to
491 # run_isolated.py that is used to store files for isolated task.
492 logging.info('Zipping up and uploading files...')
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -0400493 start_time = now()
494 isolate_item = isolateserver.BufferItem(
495 bundle.zip_into_buffer(), high_priority=True)
496 with isolateserver.get_storage(isolate_server, 'default') as storage:
497 uploaded = storage.upload_items([isolate_item])
498 bundle_url = storage.get_fetch_url(isolate_item)
499 elapsed = now() - start_time
Vadim Shtayurab450c602014-05-12 19:23:25 -0700500 if isolate_item in uploaded:
501 logging.info('Upload complete, time elapsed: %f', elapsed)
502 else:
503 logging.info('Zip file already on server, time elapsed: %f', elapsed)
504 return bundle_url
505
506
Marc-Antoine Ruel2f6581a2014-10-03 11:09:53 -0400507def trigger_request(swarming, request, xsrf_token):
508 """Triggers a requests on the Swarming server and returns the json data.
Vadim Shtayurab450c602014-05-12 19:23:25 -0700509
510 Returns:
Marc-Antoine Ruel2f6581a2014-10-03 11:09:53 -0400511 {
512 'request': {
513 'created_ts': u'2010-01-02 03:04:05',
514 'name': ..
515 },
516 'task_id': '12300',
517 }
Vadim Shtayurab450c602014-05-12 19:23:25 -0700518 """
Marc-Antoine Ruel2f6581a2014-10-03 11:09:53 -0400519 logging.info('Triggering: %s', request['name'])
520
521 headers = {'X-XSRF-Token': xsrf_token}
522 result = net.url_read_json(
523 swarming + '/swarming/api/v1/client/request',
524 data=request,
525 headers=headers)
Vadim Shtayurab450c602014-05-12 19:23:25 -0700526 if not result:
Marc-Antoine Ruel2f6581a2014-10-03 11:09:53 -0400527 on_error.report('Failed to trigger task %s' % request['name'])
528 return None
529 return result
Vadim Shtayurab450c602014-05-12 19:23:25 -0700530
531
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -0400532def abort_task(_swarming, _manifest):
Vadim Shtayurab450c602014-05-12 19:23:25 -0700533 """Given a task manifest that was triggered, aborts its execution."""
534 # TODO(vadimsh): No supported by the server yet.
535
536
537def trigger_task_shards(
Vadim Shtayuraae8085b2014-05-02 17:13:10 -0700538 swarming, isolate_server, namespace, isolated_hash, task_name, extra_args,
Marc-Antoine Ruel2f6581a2014-10-03 11:09:53 -0400539 shards, dimensions, env, expiration, io_timeout, hard_timeout, verbose,
540 profile, priority, tags, user):
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -0400541 """Triggers multiple subtasks of a sharded task.
542
543 Returns:
Vadim Shtayuraf27448e2014-06-26 11:35:05 -0700544 Dict with task details, returned to caller as part of --dump-json output.
545 None in case of failure.
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -0400546 """
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -0400547 try:
Marc-Antoine Ruel2f6581a2014-10-03 11:09:53 -0400548 data = get_data(isolate_server)
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -0400549 except (IOError, OSError):
550 on_error.report('Failed to upload the zip file for task %s' % task_name)
Marc-Antoine Ruel2f6581a2014-10-03 11:09:53 -0400551 return None
maruel@chromium.org0437a732013-08-27 16:05:52 +0000552
Marc-Antoine Ruel2f6581a2014-10-03 11:09:53 -0400553 def gen_request(env, index):
554 """Returns the json dict expected by the Swarming server for new request."""
555 return {
556 'name': get_shard_task_name(task_name, index, shards),
557 'priority': priority,
558 'properties': {
559 'commands': get_run_isolated_commands(
560 isolate_server, namespace, isolated_hash, extra_args, profile,
561 verbose),
562 'data': data,
563 'dimensions': dimensions,
564 'env': env,
565 'execution_timeout_secs': hard_timeout,
566 'io_timeout_secs': io_timeout,
567 },
568 'scheduling_expiration_secs': expiration,
569 'tags': tags,
570 'user': user,
571 }
maruel@chromium.org0437a732013-08-27 16:05:52 +0000572
Marc-Antoine Ruel2f6581a2014-10-03 11:09:53 -0400573 requests = [
574 gen_request(setup_googletest(env, shards, index), index)
575 for index in xrange(shards)
576 ]
577
578 headers = {'X-XSRF-Token-Request': '1'}
579 response = net.url_read_json(
580 swarming + '/swarming/api/v1/client/handshake',
581 headers=headers,
582 data={})
583 if not response:
584 logging.error('Failed to handshake with server')
585 return None
586 logging.info('Connected to server version: %s', response['server_version'])
587 xsrf_token = response['xsrf_token']
588
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -0400589 tasks = {}
590 priority_warning = False
Marc-Antoine Ruel2f6581a2014-10-03 11:09:53 -0400591 for index, request in enumerate(requests):
592 task = trigger_request(swarming, request, xsrf_token)
593 if not task:
Vadim Shtayurab450c602014-05-12 19:23:25 -0700594 break
Marc-Antoine Ruel2f6581a2014-10-03 11:09:53 -0400595 logging.info('Request result: %s', task)
596 priority = task['request']['priority']
597 if not priority_warning and priority != request['priority']:
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -0400598 priority_warning = True
599 print >> sys.stderr, 'Priority was reset to %s' % priority
Marc-Antoine Ruel2f6581a2014-10-03 11:09:53 -0400600 tasks[request['name']] = {
Vadim Shtayuraf27448e2014-06-26 11:35:05 -0700601 'shard_index': index,
Marc-Antoine Ruel2f6581a2014-10-03 11:09:53 -0400602 'task_id': task['task_id'],
603 'view_url': '%s/user/task/%s' % (swarming, task['task_id']),
Vadim Shtayuraf27448e2014-06-26 11:35:05 -0700604 }
Vadim Shtayurab450c602014-05-12 19:23:25 -0700605
606 # Some shards weren't triggered. Abort everything.
Marc-Antoine Ruel2f6581a2014-10-03 11:09:53 -0400607 if len(tasks) != len(requests):
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -0400608 if tasks:
Marc-Antoine Ruel2f6581a2014-10-03 11:09:53 -0400609 print >> sys.stderr, 'Only %d shard(s) out of %d were triggered' % (
610 len(tasks), len(requests))
Vadim Shtayuraf27448e2014-06-26 11:35:05 -0700611 for task_dict in tasks.itervalues():
612 abort_task(swarming, task_dict['task_id'])
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -0400613 return None
maruel@chromium.org0437a732013-08-27 16:05:52 +0000614
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -0400615 return tasks
maruel@chromium.org0437a732013-08-27 16:05:52 +0000616
617
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500618def isolated_to_hash(isolate_server, namespace, arg, algo, verbose):
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500619 """Archives a .isolated file if needed.
620
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500621 Returns the file hash to trigger and a bool specifying if it was a file (True)
622 or a hash (False).
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500623 """
624 if arg.endswith('.isolated'):
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500625 file_hash = archive(isolate_server, namespace, arg, algo, verbose)
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500626 if not file_hash:
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -0400627 on_error.report('Archival failure %s' % arg)
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500628 return None, True
629 return file_hash, True
Marc-Antoine Ruel8bee66d2014-08-28 19:02:07 -0400630 elif isolated_format.is_valid_hash(arg, algo):
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500631 return arg, False
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500632 else:
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -0400633 on_error.report('Invalid hash %s' % arg)
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500634 return None, False
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500635
636
maruel@chromium.org0437a732013-08-27 16:05:52 +0000637def trigger(
Marc-Antoine Ruela7049872013-11-05 19:28:35 -0500638 swarming,
639 isolate_server,
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500640 namespace,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500641 file_hash_or_isolated,
642 task_name,
Vadim Shtayuraae8085b2014-05-02 17:13:10 -0700643 extra_args,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500644 shards,
Marc-Antoine Ruel92f32422013-11-06 18:12:13 -0500645 dimensions,
646 env,
Marc-Antoine Ruel2f6581a2014-10-03 11:09:53 -0400647 expiration,
648 io_timeout,
649 hard_timeout,
maruel@chromium.org0437a732013-08-27 16:05:52 +0000650 verbose,
651 profile,
Marc-Antoine Ruel2f6581a2014-10-03 11:09:53 -0400652 priority,
653 tags,
654 user):
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -0400655 """Sends off the hash swarming task requests.
656
657 Returns:
658 tuple(dict(task_name: task_id), base task name). The dict of tasks is None
659 in case of failure.
660 """
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500661 file_hash, is_file = isolated_to_hash(
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500662 isolate_server, namespace, file_hash_or_isolated, hashlib.sha1, verbose)
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500663 if not file_hash:
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500664 return 1, ''
665 if not task_name:
666 # If a file name was passed, use its base name of the isolated hash.
667 # Otherwise, use user name as an approximation of a task name.
668 if is_file:
669 key = os.path.splitext(os.path.basename(file_hash_or_isolated))[0]
670 else:
Marc-Antoine Ruel2f6581a2014-10-03 11:09:53 -0400671 key = user
Vadim Shtayurac3d97b02014-04-26 19:16:05 -0700672 task_name = '%s/%s/%s/%d' % (
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500673 key,
674 '_'.join('%s=%s' % (k, v) for k, v in sorted(dimensions.iteritems())),
Vadim Shtayurac3d97b02014-04-26 19:16:05 -0700675 file_hash,
676 now() * 1000)
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500677
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -0400678 tasks = trigger_task_shards(
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500679 swarming=swarming,
680 isolate_server=isolate_server,
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500681 namespace=namespace,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500682 isolated_hash=file_hash,
683 task_name=task_name,
Vadim Shtayuraae8085b2014-05-02 17:13:10 -0700684 extra_args=extra_args,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500685 shards=shards,
686 dimensions=dimensions,
Marc-Antoine Ruel2f6581a2014-10-03 11:09:53 -0400687 expiration=expiration,
688 io_timeout=io_timeout,
689 hard_timeout=hard_timeout,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500690 env=env,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500691 verbose=verbose,
692 profile=profile,
Marc-Antoine Ruel2f6581a2014-10-03 11:09:53 -0400693 priority=priority,
694 tags=tags,
695 user=user)
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -0400696 return tasks, task_name
maruel@chromium.org0437a732013-08-27 16:05:52 +0000697
698
Marc-Antoine Ruel4e6b73d2014-10-03 18:00:05 -0400699def decorate_shard_output(swarming, shard_index, result, shard_exit_code):
maruel@chromium.org0437a732013-08-27 16:05:52 +0000700 """Returns wrapped output for swarming task shard."""
Marc-Antoine Ruel4e6b73d2014-10-03 18:00:05 -0400701 url = '%s/user/task/%s' % (swarming, result['id'])
702 duration = sum(i for i in result['durations'] if i)
703 tag_header = 'Shard %d %s' % (shard_index, url)
704 tag_footer = 'End of shard %d Duration: %.1fs Bot: %s Exit code %d' % (
705 shard_index, duration, result['bot_id'], shard_exit_code)
Marc-Antoine Ruel12a7da42014-10-01 08:29:47 -0400706
707 tag_len = max(len(tag_header), len(tag_footer))
708 dash_pad = '+-%s-+\n' % ('-' * tag_len)
709 tag_header = '| %s |\n' % tag_header.ljust(tag_len)
710 tag_footer = '| %s |\n' % tag_footer.ljust(tag_len)
711
712 header = dash_pad + tag_header + dash_pad
713 footer = dash_pad + tag_footer + dash_pad[:-1]
714 output = '\n'.join(o for o in result['outputs'] if o).rstrip() + '\n'
715 return header + output + footer
maruel@chromium.org0437a732013-08-27 16:05:52 +0000716
717
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700718def collect(
Marc-Antoine Ruel4e6b73d2014-10-03 18:00:05 -0400719 swarming, task_name, task_ids, timeout, decorate, print_status_updates,
Marc-Antoine Ruel12a7da42014-10-01 08:29:47 -0400720 task_summary_json, task_output_dir):
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500721 """Retrieves results of a Swarming task."""
Vadim Shtayurac8437bf2014-07-09 19:45:36 -0700722 # Collect summary JSON and output files (if task_output_dir is not None).
723 output_collector = TaskOutputCollector(
Marc-Antoine Ruel12a7da42014-10-01 08:29:47 -0400724 task_output_dir, task_name, len(task_ids))
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700725
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700726 seen_shards = set()
Marc-Antoine Ruel4e6b73d2014-10-03 18:00:05 -0400727 exit_code = 0
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700728 try:
729 for index, output in yield_results(
Marc-Antoine Ruel4e6b73d2014-10-03 18:00:05 -0400730 swarming, task_ids, timeout, None, print_status_updates,
731 output_collector):
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700732 seen_shards.add(index)
Vadim Shtayura473455a2014-05-14 15:22:35 -0700733
734 # Grab first non-zero exit code as an overall shard exit code.
Marc-Antoine Ruel4e6b73d2014-10-03 18:00:05 -0400735 shard_exit_code = sorted(output['exit_codes'], key=lambda x: not x)[0]
736 if shard_exit_code:
737 exit_code = shard_exit_code
Vadim Shtayura473455a2014-05-14 15:22:35 -0700738
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700739 if decorate:
Marc-Antoine Ruel4e6b73d2014-10-03 18:00:05 -0400740 print(decorate_shard_output(swarming, index, output, shard_exit_code))
Marc-Antoine Ruel12a7da42014-10-01 08:29:47 -0400741 if len(seen_shards) < len(task_ids):
742 print('')
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700743 else:
Marc-Antoine Ruel4e6b73d2014-10-03 18:00:05 -0400744 print('%s: %s %d' % (output['bot_id'], output['id'], shard_exit_code))
Marc-Antoine Ruel12a7da42014-10-01 08:29:47 -0400745 for output in output['outputs']:
746 if not output:
747 continue
748 output = output.rstrip()
749 if output:
750 print(''.join(' %s\n' % l for l in output.splitlines()))
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700751 finally:
Vadim Shtayurac8437bf2014-07-09 19:45:36 -0700752 summary = output_collector.finalize()
753 if task_summary_json:
754 tools.write_json(task_summary_json, summary, False)
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700755
Marc-Antoine Ruel12a7da42014-10-01 08:29:47 -0400756 if len(seen_shards) != len(task_ids):
757 missing_shards = [x for x in range(len(task_ids)) if x not in seen_shards]
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700758 print >> sys.stderr, ('Results from some shards are missing: %s' %
759 ', '.join(map(str, missing_shards)))
Vadim Shtayurac524f512014-05-15 09:54:56 -0700760 return 1
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700761
Marc-Antoine Ruel4e6b73d2014-10-03 18:00:05 -0400762 return exit_code
maruel@chromium.org0437a732013-08-27 16:05:52 +0000763
764
Marc-Antoine Ruel819fb162014-03-12 16:38:26 -0400765def add_filter_options(parser):
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500766 parser.filter_group = tools.optparse.OptionGroup(parser, 'Filtering slaves')
767 parser.filter_group.add_option(
Marc-Antoine Ruelb39e8cf2014-01-20 10:39:31 -0500768 '-d', '--dimension', default=[], action='append', nargs=2,
Marc-Antoine Ruel92f32422013-11-06 18:12:13 -0500769 dest='dimensions', metavar='FOO bar',
770 help='dimension to filter on')
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500771 parser.add_option_group(parser.filter_group)
772
Marc-Antoine Ruel819fb162014-03-12 16:38:26 -0400773
Marc-Antoine Ruel025e7822014-05-01 11:50:24 -0400774def process_filter_options(parser, options):
775 options.dimensions = dict(options.dimensions)
776 if not options.dimensions:
777 parser.error('Please at least specify one --dimension')
778
779
Vadim Shtayurab450c602014-05-12 19:23:25 -0700780def add_sharding_options(parser):
781 parser.sharding_group = tools.optparse.OptionGroup(parser, 'Sharding options')
782 parser.sharding_group.add_option(
783 '--shards', type='int', default=1,
784 help='Number of shards to trigger and collect.')
785 parser.add_option_group(parser.sharding_group)
786
787
Marc-Antoine Ruel819fb162014-03-12 16:38:26 -0400788def add_trigger_options(parser):
789 """Adds all options to trigger a task on Swarming."""
790 isolateserver.add_isolate_server_options(parser, True)
791 add_filter_options(parser)
792
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500793 parser.task_group = tools.optparse.OptionGroup(parser, 'Task properties')
794 parser.task_group.add_option(
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500795 '-e', '--env', default=[], action='append', nargs=2, metavar='FOO bar',
Vadim Shtayurab450c602014-05-12 19:23:25 -0700796 help='Environment variables to set')
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500797 parser.task_group.add_option(
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500798 '--priority', type='int', default=100,
799 help='The lower value, the more important the task is')
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500800 parser.task_group.add_option(
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500801 '-T', '--task-name',
Marc-Antoine Ruel2f6581a2014-10-03 11:09:53 -0400802 help='Display name of the task. Defaults to '
803 '<base_name>/<dimensions>/<isolated hash>/<timestamp> if an '
804 'isolated file is provided, if a hash is provided, it defaults to '
805 '<user>/<dimensions>/<isolated hash>/<timestamp>')
Marc-Antoine Ruel13b7b782014-03-14 11:14:57 -0400806 parser.task_group.add_option(
Marc-Antoine Ruel2f6581a2014-10-03 11:09:53 -0400807 '--tags', action='append', default=[],
808 help='Tags to assign to the task.')
809 parser.task_group.add_option(
810 '--user',
811 help='User associated with the task. Defaults to authenticated user on '
812 'the server.')
813 parser.task_group.add_option(
814 '--expiration', type='int', default=6*60*60,
Marc-Antoine Ruel13b7b782014-03-14 11:14:57 -0400815 help='Seconds to allow the task to be pending for a bot to run before '
816 'this task request expires.')
Marc-Antoine Ruel2f6581a2014-10-03 11:09:53 -0400817 parser.task_group.add_option(
Marc-Antoine Ruel77142812014-10-03 11:19:43 -0400818 '--deadline', type='int', dest='expiration',
819 help=tools.optparse.SUPPRESS_HELP)
820 parser.task_group.add_option(
Marc-Antoine Ruel2f6581a2014-10-03 11:09:53 -0400821 '--hard-timeout', type='int', default=60*60,
822 help='Seconds to allow the task to complete.')
823 parser.task_group.add_option(
824 '--io-timeout', type='int', default=20*60,
825 help='Seconds to allow the task to be silent.')
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500826 parser.add_option_group(parser.task_group)
Marc-Antoine Ruelcd629732013-12-20 15:00:42 -0500827 # TODO(maruel): This is currently written in a chromium-specific way.
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500828 parser.group_logging.add_option(
maruel@chromium.org0437a732013-08-27 16:05:52 +0000829 '--profile', action='store_true',
830 default=bool(os.environ.get('ISOLATE_DEBUG')),
831 help='Have run_isolated.py print profiling info')
maruel@chromium.org0437a732013-08-27 16:05:52 +0000832
833
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500834def process_trigger_options(parser, options, args):
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500835 isolateserver.process_isolate_server_options(parser, options)
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500836 if len(args) != 1:
837 parser.error('Must pass one .isolated file or its hash (sha1).')
Marc-Antoine Ruel025e7822014-05-01 11:50:24 -0400838 process_filter_options(parser, options)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000839
840
841def add_collect_options(parser):
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500842 parser.server_group.add_option(
maruel@chromium.org0437a732013-08-27 16:05:52 +0000843 '-t', '--timeout',
844 type='float',
Marc-Antoine Ruel2f6581a2014-10-03 11:09:53 -0400845 default=80*60.,
maruel@chromium.org0437a732013-08-27 16:05:52 +0000846 help='Timeout to wait for result, set to 0 for no timeout; default: '
847 '%default s')
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500848 parser.group_logging.add_option(
849 '--decorate', action='store_true', help='Decorate output')
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700850 parser.group_logging.add_option(
851 '--print-status-updates', action='store_true',
852 help='Print periodic status updates')
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700853 parser.task_output_group = tools.optparse.OptionGroup(parser, 'Task output')
854 parser.task_output_group.add_option(
Vadim Shtayurac8437bf2014-07-09 19:45:36 -0700855 '--task-summary-json',
856 metavar='FILE',
857 help='Dump a summary of task results to this file as json. It contains '
858 'only shards statuses as know to server directly. Any output files '
859 'emitted by the task can be collected by using --task-output-dir')
860 parser.task_output_group.add_option(
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700861 '--task-output-dir',
Vadim Shtayurac8437bf2014-07-09 19:45:36 -0700862 metavar='DIR',
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700863 help='Directory to put task results into. When the task finishes, this '
Vadim Shtayurac8437bf2014-07-09 19:45:36 -0700864 'directory contains per-shard directory with output files produced '
865 'by shards: <task-output-dir>/<zero-based-shard-index>/.')
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700866 parser.add_option_group(parser.task_output_group)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000867
868
Vadim Shtayuraae8085b2014-05-02 17:13:10 -0700869def extract_isolated_command_extra_args(args):
870 try:
871 index = args.index('--')
872 except ValueError:
873 return (args, [])
874 return (args[:index], args[index+1:])
875
876
Marc-Antoine Ruel79940ae2014-09-23 17:55:41 -0400877def CMDbots(parser, args):
Marc-Antoine Ruel819fb162014-03-12 16:38:26 -0400878 """Returns information about the bots connected to the Swarming server."""
879 add_filter_options(parser)
880 parser.filter_group.add_option(
Marc-Antoine Ruel28083112014-03-13 16:34:04 -0400881 '--dead-only', action='store_true',
882 help='Only print dead bots, useful to reap them and reimage broken bots')
883 parser.filter_group.add_option(
Marc-Antoine Ruel819fb162014-03-12 16:38:26 -0400884 '-k', '--keep-dead', action='store_true',
885 help='Do not filter out dead bots')
886 parser.filter_group.add_option(
887 '-b', '--bare', action='store_true',
Marc-Antoine Ruele7b00162014-03-12 16:59:01 -0400888 help='Do not print out dimensions')
Marc-Antoine Ruel819fb162014-03-12 16:38:26 -0400889 options, args = parser.parse_args(args)
Marc-Antoine Ruel28083112014-03-13 16:34:04 -0400890
891 if options.keep_dead and options.dead_only:
892 parser.error('Use only one of --keep-dead and --dead-only')
Vadim Shtayura6b555c12014-07-23 16:22:18 -0700893
894 auth.ensure_logged_in(options.swarming)
Marc-Antoine Ruelc6c579e2014-09-08 18:43:45 -0400895
896 bots = []
897 cursor = None
898 limit = 250
899 # Iterate via cursors.
900 base_url = options.swarming + '/swarming/api/v1/client/bots?limit=%d' % limit
901 while True:
902 url = base_url
903 if cursor:
Marc-Antoine Ruel79940ae2014-09-23 17:55:41 -0400904 url += '&cursor=%s' % urllib.quote(cursor)
Marc-Antoine Ruelc6c579e2014-09-08 18:43:45 -0400905 data = net.url_read_json(url)
906 if data is None:
907 print >> sys.stderr, 'Failed to access %s' % options.swarming
908 return 1
Marc-Antoine Ruel79940ae2014-09-23 17:55:41 -0400909 bots.extend(data['items'])
Marc-Antoine Ruelc6c579e2014-09-08 18:43:45 -0400910 cursor = data['cursor']
911 if not cursor:
912 break
913
914 for bot in natsort.natsorted(bots, key=lambda x: x['id']):
Marc-Antoine Ruel28083112014-03-13 16:34:04 -0400915 if options.dead_only:
Marc-Antoine Ruelc6c579e2014-09-08 18:43:45 -0400916 if not bot['is_dead']:
Marc-Antoine Ruel28083112014-03-13 16:34:04 -0400917 continue
Marc-Antoine Ruelc6c579e2014-09-08 18:43:45 -0400918 elif not options.keep_dead and bot['is_dead']:
Marc-Antoine Ruel819fb162014-03-12 16:38:26 -0400919 continue
920
Marc-Antoine Ruele7b00162014-03-12 16:59:01 -0400921 # If the user requested to filter on dimensions, ensure the bot has all the
922 # dimensions requested.
Marc-Antoine Ruelc6c579e2014-09-08 18:43:45 -0400923 dimensions = bot['dimensions']
Marc-Antoine Ruel819fb162014-03-12 16:38:26 -0400924 for key, value in options.dimensions:
925 if key not in dimensions:
926 break
Marc-Antoine Ruele7b00162014-03-12 16:59:01 -0400927 # A bot can have multiple value for a key, for example,
928 # {'os': ['Windows', 'Windows-6.1']}, so that --dimension os=Windows will
929 # be accepted.
Marc-Antoine Ruel819fb162014-03-12 16:38:26 -0400930 if isinstance(dimensions[key], list):
931 if value not in dimensions[key]:
932 break
933 else:
934 if value != dimensions[key]:
935 break
936 else:
Marc-Antoine Ruelc6c579e2014-09-08 18:43:45 -0400937 print bot['id']
Marc-Antoine Ruele7b00162014-03-12 16:59:01 -0400938 if not options.bare:
Marc-Antoine Ruel0a620612014-08-13 15:47:07 -0400939 print ' %s' % json.dumps(dimensions, sort_keys=True)
Marc-Antoine Ruelc6c579e2014-09-08 18:43:45 -0400940 if bot['task']:
941 print ' task: %s' % bot['task']
Marc-Antoine Ruel819fb162014-03-12 16:38:26 -0400942 return 0
943
944
Marc-Antoine Ruel12a7da42014-10-01 08:29:47 -0400945@subcommand.usage('--json file | task_id...')
Marc-Antoine Ruel79940ae2014-09-23 17:55:41 -0400946def CMDcollect(parser, args):
Marc-Antoine Ruel12a7da42014-10-01 08:29:47 -0400947 """Retrieves results of one or multiple Swarming task by its ID.
Marc-Antoine Ruel79940ae2014-09-23 17:55:41 -0400948
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)
Marc-Antoine Ruel12a7da42014-10-01 08:29:47 -0400953 parser.add_option(
954 '-j', '--json',
955 help='Load the task ids from .json as saved by trigger --dump-json')
Marc-Antoine Ruel79940ae2014-09-23 17:55:41 -0400956 (options, args) = parser.parse_args(args)
Marc-Antoine Ruel12a7da42014-10-01 08:29:47 -0400957 if not args and not options.json:
958 parser.error('Must specify at least one task id or --json.')
959 if args and options.json:
960 parser.error('Only use one of task id or --json.')
961
962 if options.json:
963 with open(options.json) as f:
964 tasks = sorted(
965 json.load(f)['tasks'].itervalues(), key=lambda x: x['shard_index'])
966 args = [t['task_id'] for t in tasks]
967 else:
968 valid = frozenset('0123456789abcdef')
969 if any(not valid.issuperset(task_id) for task_id in args):
970 parser.error('Task ids are 0-9a-f.')
Marc-Antoine Ruel79940ae2014-09-23 17:55:41 -0400971
972 auth.ensure_logged_in(options.swarming)
973 try:
974 return collect(
975 options.swarming,
Marc-Antoine Ruel12a7da42014-10-01 08:29:47 -0400976 None,
977 args,
Marc-Antoine Ruel79940ae2014-09-23 17:55:41 -0400978 options.timeout,
979 options.decorate,
980 options.print_status_updates,
981 options.task_summary_json,
982 options.task_output_dir)
983 except Failure:
984 on_error.report(None)
985 return 1
986
987
988@subcommand.usage('[resource name]')
989def CMDquery(parser, args):
990 """Returns raw JSON information via an URL endpoint. Use 'list' to gather the
991 list of valid values from the server.
992
993 Examples:
994 Printing the list of known URLs:
995 swarming.py query -S https://server-url list
996
997 Listing last 50 tasks on a specific bot named 'swarm1'
998 swarming.py query -S https://server-url --limit 50 bot/swarm1/tasks
999 """
1000 CHUNK_SIZE = 250
1001
1002 parser.add_option(
1003 '-L', '--limit', type='int', default=200,
1004 help='Limit to enforce on limitless items (like number of tasks); '
1005 'default=%default')
1006 (options, args) = parser.parse_args(args)
1007 if len(args) != 1:
1008 parser.error('Must specify only one resource name.')
1009
1010 auth.ensure_logged_in(options.swarming)
1011
1012 base_url = options.swarming + '/swarming/api/v1/client/' + args[0]
1013 url = base_url
1014 if options.limit:
1015 url += '?limit=%d' % min(CHUNK_SIZE, options.limit)
1016 data = net.url_read_json(url)
1017 if data is None:
1018 print >> sys.stderr, 'Failed to access %s' % options.swarming
1019 return 1
1020
1021 # Some items support cursors. Try to get automatically if cursors are needed
1022 # by looking at the 'cursor' items.
1023 while (
1024 data.get('cursor') and
1025 (not options.limit or len(data['items']) < options.limit)):
1026 url = base_url + '?cursor=%s' % urllib.quote(data['cursor'])
1027 if options.limit:
1028 url += '&limit=%d' % min(CHUNK_SIZE, options.limit - len(data['items']))
1029 new = net.url_read_json(url)
1030 if new is None:
1031 print >> sys.stderr, 'Failed to access %s' % options.swarming
1032 return 1
1033 data['items'].extend(new['items'])
1034 data['cursor'] = new['cursor']
1035
1036 if options.limit and len(data.get('items', [])) > options.limit:
1037 data['items'] = data['items'][:options.limit]
1038 data.pop('cursor', None)
1039
1040 json.dump(data, sys.stdout, indent=2, sort_keys=True)
1041 sys.stdout.write('\n')
1042 return 0
1043
1044
Vadim Shtayuraae8085b2014-05-02 17:13:10 -07001045@subcommand.usage('(hash|isolated) [-- extra_args]')
maruel@chromium.org0437a732013-08-27 16:05:52 +00001046def CMDrun(parser, args):
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001047 """Triggers a task and wait for the results.
maruel@chromium.org0437a732013-08-27 16:05:52 +00001048
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001049 Basically, does everything to run a command remotely.
maruel@chromium.org0437a732013-08-27 16:05:52 +00001050 """
1051 add_trigger_options(parser)
1052 add_collect_options(parser)
Vadim Shtayurab450c602014-05-12 19:23:25 -07001053 add_sharding_options(parser)
Vadim Shtayuraae8085b2014-05-02 17:13:10 -07001054 args, isolated_cmd_args = extract_isolated_command_extra_args(args)
maruel@chromium.org0437a732013-08-27 16:05:52 +00001055 options, args = parser.parse_args(args)
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001056 process_trigger_options(parser, options, args)
maruel@chromium.org0437a732013-08-27 16:05:52 +00001057
Marc-Antoine Ruel2f6581a2014-10-03 11:09:53 -04001058 user = auth.ensure_logged_in(options.swarming)
Vadim Shtayura6b555c12014-07-23 16:22:18 -07001059 if file_path.is_url(options.isolate_server):
1060 auth.ensure_logged_in(options.isolate_server)
Marc-Antoine Ruel2f6581a2014-10-03 11:09:53 -04001061
1062 options.user = options.user or user or 'unknown'
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001063 try:
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -04001064 tasks, task_name = trigger(
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001065 swarming=options.swarming,
Marc-Antoine Ruel8806e622014-02-12 14:15:53 -05001066 isolate_server=options.isolate_server or options.indir,
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -05001067 namespace=options.namespace,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001068 file_hash_or_isolated=args[0],
1069 task_name=options.task_name,
Vadim Shtayuraae8085b2014-05-02 17:13:10 -07001070 extra_args=isolated_cmd_args,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001071 shards=options.shards,
1072 dimensions=options.dimensions,
1073 env=dict(options.env),
Marc-Antoine Ruel2f6581a2014-10-03 11:09:53 -04001074 expiration=options.expiration,
1075 io_timeout=options.io_timeout,
1076 hard_timeout=options.hard_timeout,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001077 verbose=options.verbose,
1078 profile=options.profile,
Marc-Antoine Ruel2f6581a2014-10-03 11:09:53 -04001079 priority=options.priority,
1080 tags=options.tags,
1081 user=options.user)
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001082 except Failure as e:
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -04001083 on_error.report(
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001084 'Failed to trigger %s(%s): %s' %
1085 (options.task_name, args[0], e.args[0]))
1086 return 1
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -04001087 if not tasks:
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -04001088 on_error.report('Failed to trigger the task.')
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -04001089 return 1
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -05001090 if task_name != options.task_name:
1091 print('Triggered task: %s' % task_name)
Marc-Antoine Ruel12a7da42014-10-01 08:29:47 -04001092 task_ids = [
1093 t['task_id']
1094 for t in sorted(tasks.itervalues(), key=lambda x: x['shard_index'])
1095 ]
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001096 try:
1097 return collect(
1098 options.swarming,
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -05001099 task_name,
Marc-Antoine Ruel12a7da42014-10-01 08:29:47 -04001100 task_ids,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001101 options.timeout,
Vadim Shtayura86a2cef2014-04-18 11:13:39 -07001102 options.decorate,
Vadim Shtayurae3fbd102014-04-29 17:05:21 -07001103 options.print_status_updates,
Vadim Shtayurac8437bf2014-07-09 19:45:36 -07001104 options.task_summary_json,
Vadim Shtayurae3fbd102014-04-29 17:05:21 -07001105 options.task_output_dir)
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -04001106 except Failure:
1107 on_error.report(None)
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001108 return 1
maruel@chromium.org0437a732013-08-27 16:05:52 +00001109
1110
Vadim Shtayuraae8085b2014-05-02 17:13:10 -07001111@subcommand.usage("(hash|isolated) [-- extra_args]")
maruel@chromium.org0437a732013-08-27 16:05:52 +00001112def CMDtrigger(parser, args):
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001113 """Triggers a Swarming task.
maruel@chromium.org0437a732013-08-27 16:05:52 +00001114
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001115 Accepts either the hash (sha1) of a .isolated file already uploaded or the
Marc-Antoine Ruel2f6581a2014-10-03 11:09:53 -04001116 path to an .isolated file to archive.
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001117
1118 If an .isolated file is specified instead of an hash, it is first archived.
Vadim Shtayuraae8085b2014-05-02 17:13:10 -07001119
1120 Passes all extra arguments provided after '--' as additional command line
1121 arguments for an isolated command specified in *.isolate file.
maruel@chromium.org0437a732013-08-27 16:05:52 +00001122 """
1123 add_trigger_options(parser)
Vadim Shtayurab450c602014-05-12 19:23:25 -07001124 add_sharding_options(parser)
Vadim Shtayuraae8085b2014-05-02 17:13:10 -07001125 args, isolated_cmd_args = extract_isolated_command_extra_args(args)
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -04001126 parser.add_option(
1127 '--dump-json',
1128 metavar='FILE',
1129 help='Dump details about the triggered task(s) to this file as json')
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001130 options, args = parser.parse_args(args)
1131 process_trigger_options(parser, options, args)
maruel@chromium.org0437a732013-08-27 16:05:52 +00001132
Marc-Antoine Ruel2f6581a2014-10-03 11:09:53 -04001133 user = auth.ensure_logged_in(options.swarming)
Vadim Shtayura6b555c12014-07-23 16:22:18 -07001134 if file_path.is_url(options.isolate_server):
1135 auth.ensure_logged_in(options.isolate_server)
Marc-Antoine Ruel2f6581a2014-10-03 11:09:53 -04001136
1137 options.user = options.user or user or 'unknown'
maruel@chromium.org0437a732013-08-27 16:05:52 +00001138 try:
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -04001139 tasks, task_name = trigger(
Marc-Antoine Ruela7049872013-11-05 19:28:35 -05001140 swarming=options.swarming,
Marc-Antoine Ruel8806e622014-02-12 14:15:53 -05001141 isolate_server=options.isolate_server or options.indir,
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -05001142 namespace=options.namespace,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001143 file_hash_or_isolated=args[0],
1144 task_name=options.task_name,
Vadim Shtayuraae8085b2014-05-02 17:13:10 -07001145 extra_args=isolated_cmd_args,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001146 shards=options.shards,
Vadim Shtayuraae8085b2014-05-02 17:13:10 -07001147 dimensions=options.dimensions,
Marc-Antoine Ruel92f32422013-11-06 18:12:13 -05001148 env=dict(options.env),
Marc-Antoine Ruel2f6581a2014-10-03 11:09:53 -04001149 expiration=options.expiration,
1150 io_timeout=options.io_timeout,
1151 hard_timeout=options.hard_timeout,
Marc-Antoine Ruela7049872013-11-05 19:28:35 -05001152 verbose=options.verbose,
1153 profile=options.profile,
Marc-Antoine Ruel2f6581a2014-10-03 11:09:53 -04001154 priority=options.priority,
1155 tags=options.tags,
1156 user=options.user)
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -04001157 if tasks:
1158 if task_name != options.task_name:
1159 print('Triggered task: %s' % task_name)
Marc-Antoine Ruel2f6581a2014-10-03 11:09:53 -04001160 tasks_sorted = sorted(
1161 tasks.itervalues(), key=lambda x: x['shard_index'])
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -04001162 if options.dump_json:
1163 data = {
1164 'base_task_name': task_name,
1165 'tasks': tasks,
1166 }
1167 tools.write_json(options.dump_json, data, True)
Marc-Antoine Ruel12a7da42014-10-01 08:29:47 -04001168 print('To collect results, use:')
1169 print(' swarming.py collect -S %s --json %s' %
1170 (options.swarming, options.dump_json))
1171 else:
Marc-Antoine Ruel12a7da42014-10-01 08:29:47 -04001172 print('To collect results, use:')
1173 print(' swarming.py collect -S %s %s' %
Marc-Antoine Ruel2f6581a2014-10-03 11:09:53 -04001174 (options.swarming, ' '.join(t['task_id'] for t in tasks_sorted)))
1175 print('Or visit:')
1176 for t in tasks_sorted:
1177 print(' ' + t['view_url'])
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -04001178 return int(not tasks)
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -04001179 except Failure:
1180 on_error.report(None)
vadimsh@chromium.orgd908a542013-10-30 01:36:17 +00001181 return 1
maruel@chromium.org0437a732013-08-27 16:05:52 +00001182
1183
1184class OptionParserSwarming(tools.OptionParserWithLogging):
1185 def __init__(self, **kwargs):
1186 tools.OptionParserWithLogging.__init__(
1187 self, prog='swarming.py', **kwargs)
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -05001188 self.server_group = tools.optparse.OptionGroup(self, 'Server')
1189 self.server_group.add_option(
maruel@chromium.orge9403ab2013-09-20 18:03:49 +00001190 '-S', '--swarming',
Kevin Graney5346c162014-01-24 12:20:01 -05001191 metavar='URL', default=os.environ.get('SWARMING_SERVER', ''),
maruel@chromium.orge9403ab2013-09-20 18:03:49 +00001192 help='Swarming server to use')
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -05001193 self.add_option_group(self.server_group)
Vadim Shtayurae34e13a2014-02-02 11:23:26 -08001194 auth.add_auth_options(self)
maruel@chromium.org0437a732013-08-27 16:05:52 +00001195
1196 def parse_args(self, *args, **kwargs):
1197 options, args = tools.OptionParserWithLogging.parse_args(
1198 self, *args, **kwargs)
1199 options.swarming = options.swarming.rstrip('/')
1200 if not options.swarming:
1201 self.error('--swarming is required.')
Vadim Shtayura5d1efce2014-02-04 10:55:43 -08001202 auth.process_auth_options(self, options)
maruel@chromium.org0437a732013-08-27 16:05:52 +00001203 return options, args
1204
1205
1206def main(args):
1207 dispatcher = subcommand.CommandDispatcher(__name__)
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -04001208 return dispatcher.execute(OptionParserSwarming(version=__version__), args)
maruel@chromium.org0437a732013-08-27 16:05:52 +00001209
1210
1211if __name__ == '__main__':
1212 fix_encoding.fix_encoding()
1213 tools.disable_buffering()
1214 colorama.init()
1215 sys.exit(main(sys.argv[1:]))