blob: 535e2cbf77f7cde6725ecc4a3968ddad891310cb [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
Kevin Graneyc2c3b9e2014-08-26 09:04:17 -04008__version__ = '0.4.14'
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
Marc-Antoine Ruel8bee66d2014-08-28 19:02:07 -040036import isolated_format
maruel@chromium.org7b844a62013-09-17 13:04:59 +000037import isolateserver
maruel@chromium.org0437a732013-08-27 16:05:52 +000038import run_isolated
39
40
41ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
42TOOLS_PATH = os.path.join(ROOT_DIR, 'tools')
43
44
maruel@chromium.org0437a732013-08-27 16:05:52 +000045# The default time to wait for a shard to finish running.
csharp@chromium.org24758492013-08-28 19:10:54 +000046DEFAULT_SHARD_WAIT_TIME = 80 * 60.
maruel@chromium.org0437a732013-08-27 16:05:52 +000047
Vadim Shtayura86a2cef2014-04-18 11:13:39 -070048# How often to print status updates to stdout in 'collect'.
49STATUS_UPDATE_INTERVAL = 15 * 60.
50
maruel@chromium.org0437a732013-08-27 16:05:52 +000051
52NO_OUTPUT_FOUND = (
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -050053 'No output produced by the task, it may have failed to run.\n'
maruel@chromium.org0437a732013-08-27 16:05:52 +000054 '\n')
55
56
maruel@chromium.org0437a732013-08-27 16:05:52 +000057class Failure(Exception):
58 """Generic failure."""
59 pass
60
61
62class Manifest(object):
Vadim Shtayurab450c602014-05-12 19:23:25 -070063 """Represents a Swarming task manifest."""
maruel@chromium.org0437a732013-08-27 16:05:52 +000064
maruel@chromium.org0437a732013-08-27 16:05:52 +000065 def __init__(
Vadim Shtayuraae8085b2014-05-02 17:13:10 -070066 self, isolate_server, namespace, isolated_hash, task_name, extra_args,
Marc-Antoine Ruelaea50652014-06-12 14:23:48 -040067 env, dimensions, deadline, verbose, profile,
Vadim Shtayuraae8085b2014-05-02 17:13:10 -070068 priority):
maruel@chromium.org0437a732013-08-27 16:05:52 +000069 """Populates a manifest object.
70 Args:
Marc-Antoine Ruela7049872013-11-05 19:28:35 -050071 isolate_server - isolate server url.
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -050072 namespace - isolate server namespace to use.
Vadim Shtayuraae8085b2014-05-02 17:13:10 -070073 isolated_hash - the manifest's sha-1 that the slave is going to fetch.
74 task_name - the name to give the task request.
75 extra_args - additional arguments to pass to isolated command.
Marc-Antoine Ruel05dab5e2013-11-06 15:06:47 -050076 env - environment variables to set.
Marc-Antoine Ruel92f32422013-11-06 18:12:13 -050077 dimensions - dimensions to filter the task on.
Marc-Antoine Ruel13b7b782014-03-14 11:14:57 -040078 deadline - maximum pending time before this task expires.
maruel@chromium.org0437a732013-08-27 16:05:52 +000079 verbose - if True, have the slave print more details.
80 profile - if True, have the slave print more timing data.
maruel@chromium.org7b844a62013-09-17 13:04:59 +000081 priority - int between 0 and 1000, lower the higher priority.
maruel@chromium.org0437a732013-08-27 16:05:52 +000082 """
Marc-Antoine Ruela7049872013-11-05 19:28:35 -050083 self.isolate_server = isolate_server
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -050084 self.namespace = namespace
maruel@chromium.org814d23f2013-10-01 19:08:00 +000085 self.isolated_hash = isolated_hash
Vadim Shtayurab450c602014-05-12 19:23:25 -070086 self.task_name = task_name
Vadim Shtayuraae8085b2014-05-02 17:13:10 -070087 self.extra_args = tuple(extra_args or [])
Vadim Shtayurab450c602014-05-12 19:23:25 -070088 self.env = env.copy()
89 self.dimensions = dimensions.copy()
Vadim Shtayurab450c602014-05-12 19:23:25 -070090 self.deadline = deadline
maruel@chromium.org0437a732013-08-27 16:05:52 +000091 self.verbose = bool(verbose)
92 self.profile = bool(profile)
93 self.priority = priority
maruel@chromium.org0437a732013-08-27 16:05:52 +000094 self._tasks = []
Vadim Shtayurab450c602014-05-12 19:23:25 -070095 self._files = []
maruel@chromium.org0437a732013-08-27 16:05:52 +000096
Marc-Antoine Ruelaf78a902014-03-20 10:42:49 -040097 def add_task(self, task_name, actions, time_out=2*60*60):
Marc-Antoine Ruel5c720342014-02-21 14:46:14 -050098 """Appends a new task as a TestObject to the swarming manifest file.
Marc-Antoine Ruelcd629732013-12-20 15:00:42 -050099
100 Tasks cannot be added once the manifest was uploaded.
Marc-Antoine Ruel5c720342014-02-21 14:46:14 -0500101
Marc-Antoine Ruelaf78a902014-03-20 10:42:49 -0400102 By default, command will be killed after 2 hours of execution.
103
Marc-Antoine Ruel5c720342014-02-21 14:46:14 -0500104 See TestObject in services/swarming/src/common/test_request_message.py for
105 the valid format.
Marc-Antoine Ruelcd629732013-12-20 15:00:42 -0500106 """
maruel@chromium.org0437a732013-08-27 16:05:52 +0000107 self._tasks.append(
108 {
109 'action': actions,
110 'decorate_output': self.verbose,
111 'test_name': task_name,
Marc-Antoine Ruelaf78a902014-03-20 10:42:49 -0400112 'hard_time_out': time_out,
maruel@chromium.org0437a732013-08-27 16:05:52 +0000113 })
114
Vadim Shtayurab450c602014-05-12 19:23:25 -0700115 def add_bundled_file(self, file_name, file_url):
116 """Appends a file to the manifest.
117
118 File will be downloaded and extracted by the swarm bot before launching the
119 task.
120 """
121 self._files.append([file_url, file_name])
122
maruel@chromium.org0437a732013-08-27 16:05:52 +0000123 def to_json(self):
124 """Exports the current configuration into a swarm-readable manifest file.
125
Marc-Antoine Ruel5c720342014-02-21 14:46:14 -0500126 The actual serialization format is defined as a TestCase object as described
127 in services/swarming/src/common/test_request_message.py
maruel@chromium.org0437a732013-08-27 16:05:52 +0000128 """
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500129 request = {
Marc-Antoine Ruela7049872013-11-05 19:28:35 -0500130 'cleanup': 'root',
maruel@chromium.org0437a732013-08-27 16:05:52 +0000131 'configurations': [
Marc-Antoine Ruel5c720342014-02-21 14:46:14 -0500132 # Is a TestConfiguration.
maruel@chromium.org0437a732013-08-27 16:05:52 +0000133 {
Marc-Antoine Ruel5d799192013-11-06 15:20:39 -0500134 'config_name': 'isolated',
Vadim Shtayurab450c602014-05-12 19:23:25 -0700135 'deadline_to_run': self.deadline,
136 'dimensions': self.dimensions,
Marc-Antoine Ruela7049872013-11-05 19:28:35 -0500137 'priority': self.priority,
maruel@chromium.org0437a732013-08-27 16:05:52 +0000138 },
139 ],
Vadim Shtayurab450c602014-05-12 19:23:25 -0700140 'data': self._files,
Vadim Shtayurab450c602014-05-12 19:23:25 -0700141 'env_vars': self.env,
Vadim Shtayurab450c602014-05-12 19:23:25 -0700142 'test_case_name': self.task_name,
Marc-Antoine Ruela7049872013-11-05 19:28:35 -0500143 'tests': self._tasks,
maruel@chromium.org0437a732013-08-27 16:05:52 +0000144 }
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500145 return json.dumps(request, sort_keys=True, separators=(',',':'))
maruel@chromium.org0437a732013-08-27 16:05:52 +0000146
Marc-Antoine Ruelcd629732013-12-20 15:00:42 -0500147
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700148class TaskOutputCollector(object):
Vadim Shtayurac8437bf2014-07-09 19:45:36 -0700149 """Assembles task execution summary (for --task-summary-json output).
150
151 Optionally fetches task outputs from isolate server to local disk (used when
152 --task-output-dir is passed).
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700153
154 This object is shared among multiple threads running 'retrieve_results'
155 function, in particular they call 'process_shard_result' method in parallel.
156 """
157
158 def __init__(self, task_output_dir, task_name, shard_count):
159 """Initializes TaskOutputCollector, ensures |task_output_dir| exists.
160
161 Args:
Vadim Shtayurac8437bf2014-07-09 19:45:36 -0700162 task_output_dir: (optional) local directory to put fetched files to.
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700163 task_name: name of the swarming task results belong to.
164 shard_count: expected number of task shards.
165 """
166 self.task_output_dir = task_output_dir
167 self.task_name = task_name
168 self.shard_count = shard_count
169
170 self._lock = threading.Lock()
171 self._per_shard_results = {}
172 self._storage = None
173
Vadim Shtayurac8437bf2014-07-09 19:45:36 -0700174 if self.task_output_dir and not os.path.isdir(self.task_output_dir):
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700175 os.makedirs(self.task_output_dir)
176
Vadim Shtayurab450c602014-05-12 19:23:25 -0700177 def process_shard_result(self, shard_index, result):
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700178 """Stores results of a single task shard, fetches output files if necessary.
179
180 Called concurrently from multiple threads.
181 """
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700182 # Sanity check index is in expected range.
Vadim Shtayurab450c602014-05-12 19:23:25 -0700183 assert isinstance(shard_index, int)
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700184 if shard_index < 0 or shard_index >= self.shard_count:
185 logging.warning(
186 'Shard index %d is outside of expected range: [0; %d]',
187 shard_index, self.shard_count - 1)
188 return
189
Kevin Graneyc2c3b9e2014-08-26 09:04:17 -0400190 result = result.copy()
191
192 isolated_files_location = extract_output_files_location(result['output'])
193 if isolated_files_location:
194 result['isolated_out'] = isolated_files_location
195
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700196 # Store result dict of that shard, ignore results we've already seen.
197 with self._lock:
198 if shard_index in self._per_shard_results:
199 logging.warning('Ignoring duplicate shard index %d', shard_index)
200 return
201 self._per_shard_results[shard_index] = result
202
203 # Fetch output files if necessary.
Vadim Shtayurac8437bf2014-07-09 19:45:36 -0700204 if self.task_output_dir:
Vadim Shtayurac8437bf2014-07-09 19:45:36 -0700205 if isolated_files_location:
Kevin Graneyc2c3b9e2014-08-26 09:04:17 -0400206 storage = self._get_storage(
207 isolated_files_location['server'],
208 isolated_files_location['namespace'])
Vadim Shtayurac8437bf2014-07-09 19:45:36 -0700209 if storage:
210 # Output files are supposed to be small and they are not reused across
211 # tasks. So use MemoryCache for them instead of on-disk cache. Make
212 # files writable, so that calling script can delete them.
213 isolateserver.fetch_isolated(
Kevin Graneyc2c3b9e2014-08-26 09:04:17 -0400214 isolated_files_location['hash'],
Vadim Shtayurac8437bf2014-07-09 19:45:36 -0700215 storage,
216 isolateserver.MemoryCache(file_mode_mask=0700),
217 os.path.join(self.task_output_dir, str(shard_index)),
218 False)
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700219
220 def finalize(self):
Vadim Shtayurac8437bf2014-07-09 19:45:36 -0700221 """Assembles and returns task summary JSON, shutdowns underlying Storage."""
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700222 with self._lock:
223 # Write an array of shard results with None for missing shards.
224 summary = {
225 'task_name': self.task_name,
226 'shards': [
227 self._per_shard_results.get(i) for i in xrange(self.shard_count)
228 ],
229 }
Vadim Shtayurac8437bf2014-07-09 19:45:36 -0700230 # Write summary.json to task_output_dir as well.
231 if self.task_output_dir:
232 tools.write_json(
233 os.path.join(self.task_output_dir, 'summary.json'),
234 summary,
235 False)
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700236 if self._storage:
237 self._storage.close()
238 self._storage = None
Vadim Shtayurac8437bf2014-07-09 19:45:36 -0700239 return summary
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700240
241 def _get_storage(self, isolate_server, namespace):
242 """Returns isolateserver.Storage to use to fetch files."""
Vadim Shtayurac8437bf2014-07-09 19:45:36 -0700243 assert self.task_output_dir
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700244 with self._lock:
245 if not self._storage:
246 self._storage = isolateserver.get_storage(isolate_server, namespace)
247 else:
248 # Shards must all use exact same isolate server and namespace.
249 if self._storage.location != isolate_server:
250 logging.error(
251 'Task shards are using multiple isolate servers: %s and %s',
252 self._storage.location, isolate_server)
253 return None
254 if self._storage.namespace != namespace:
255 logging.error(
256 'Task shards are using multiple namespaces: %s and %s',
257 self._storage.namespace, namespace)
258 return None
259 return self._storage
260
261
maruel@chromium.org0437a732013-08-27 16:05:52 +0000262def now():
263 """Exists so it can be mocked easily."""
264 return time.time()
265
266
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500267def get_task_keys(swarm_base_url, task_name):
268 """Returns the Swarming task key for each shards of task_name."""
269 key_data = urllib.urlencode([('name', task_name)])
maruel@chromium.org0437a732013-08-27 16:05:52 +0000270 url = '%s/get_matching_test_cases?%s' % (swarm_base_url, key_data)
271
vadimsh@chromium.org043b76d2013-09-12 16:15:13 +0000272 for _ in net.retry_loop(max_attempts=net.URL_OPEN_MAX_ATTEMPTS):
273 result = net.url_read(url, retry_404=True)
274 if result is None:
maruel@chromium.org0437a732013-08-27 16:05:52 +0000275 raise Failure(
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500276 'Error: Unable to find any task with the name, %s, on swarming server'
277 % task_name)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000278
maruel@chromium.org0437a732013-08-27 16:05:52 +0000279 # TODO(maruel): Compare exact string.
280 if 'No matching' in result:
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500281 logging.warning('Unable to find any task with the name, %s, on swarming '
282 'server' % task_name)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000283 continue
284 return json.loads(result)
285
286 raise Failure(
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500287 'Error: Unable to find any task with the name, %s, on swarming server'
288 % task_name)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000289
290
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700291def extract_output_files_location(task_log):
292 """Task log -> location of task output files to fetch.
293
294 TODO(vadimsh,maruel): Use side-channel to get this information.
295 See 'run_tha_test' in run_isolated.py for where the data is generated.
296
297 Returns:
298 Tuple (isolate server URL, namespace, isolated hash) on success.
299 None if information is missing or can not be parsed.
300 """
301 match = re.search(
302 r'\[run_isolated_out_hack\](.*)\[/run_isolated_out_hack\]',
303 task_log,
304 re.DOTALL)
305 if not match:
306 return None
307
308 def to_ascii(val):
309 if not isinstance(val, basestring):
310 raise ValueError()
311 return val.encode('ascii')
312
313 try:
314 data = json.loads(match.group(1))
315 if not isinstance(data, dict):
316 raise ValueError()
317 isolated_hash = to_ascii(data['hash'])
318 namespace = to_ascii(data['namespace'])
319 isolate_server = to_ascii(data['storage'])
320 if not file_path.is_url(isolate_server):
321 raise ValueError()
Kevin Graneyc2c3b9e2014-08-26 09:04:17 -0400322 data = {
323 'hash': isolated_hash,
324 'namespace': namespace,
325 'server': isolate_server,
326 'view_url': '%s/browse?%s' % (isolate_server, urllib.urlencode(
327 [('namespace', namespace), ('hash', isolated_hash)])),
328 }
329 return data
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700330 except (KeyError, ValueError):
331 logging.warning(
332 'Unexpected value of run_isolated_out_hack: %s', match.group(1))
333 return None
334
335
336def retrieve_results(
Vadim Shtayurab450c602014-05-12 19:23:25 -0700337 base_url, shard_index, task_key, timeout, should_stop, output_collector):
Vadim Shtayurab19319e2014-04-27 08:50:06 -0700338 """Retrieves results for a single task_key.
339
Vadim Shtayurab450c602014-05-12 19:23:25 -0700340 Returns:
341 <result dict> on success.
342 None on failure.
Vadim Shtayurab19319e2014-04-27 08:50:06 -0700343 """
maruel@chromium.org814d23f2013-10-01 19:08:00 +0000344 assert isinstance(timeout, float), timeout
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500345 params = [('r', task_key)]
maruel@chromium.org0437a732013-08-27 16:05:52 +0000346 result_url = '%s/get_result?%s' % (base_url, urllib.urlencode(params))
Vadim Shtayurab19319e2014-04-27 08:50:06 -0700347 started = now()
348 deadline = started + timeout if timeout else None
349 attempt = 0
350
351 while not should_stop.is_set():
352 attempt += 1
353
354 # Waiting for too long -> give up.
355 current_time = now()
356 if deadline and current_time >= deadline:
357 logging.error('retrieve_results(%s) timed out on attempt %d',
358 base_url, attempt)
359 return None
360
361 # Do not spin too fast. Spin faster at the beginning though.
362 # Start with 1 sec delay and for each 30 sec of waiting add another second
363 # of delay, until hitting 15 sec ceiling.
364 if attempt > 1:
365 max_delay = min(15, 1 + (current_time - started) / 30.0)
366 delay = min(max_delay, deadline - current_time) if deadline else max_delay
367 if delay > 0:
368 logging.debug('Waiting %.1f sec before retrying', delay)
369 should_stop.wait(delay)
370 if should_stop.is_set():
371 return None
372
Marc-Antoine Ruel200b3952014-08-14 11:07:44 -0400373 # Disable internal retries in net.url_read, since we are doing retries
374 # ourselves. Do not use retry_404 so should_stop is polled more often.
375 response = net.url_read(result_url, retry_404=False, retry_50x=False)
376
377 # Request failed. Try again.
378 if response is None:
379 continue
380
381 # Got some response, ensure it is JSON dict, retry if not.
Vadim Shtayurab19319e2014-04-27 08:50:06 -0700382 try:
Marc-Antoine Ruel200b3952014-08-14 11:07:44 -0400383 result = json.loads(response) or {}
Vadim Shtayurab19319e2014-04-27 08:50:06 -0700384 if not isinstance(result, dict):
385 raise ValueError()
386 except (ValueError, TypeError):
387 logging.warning(
388 'Received corrupted or invalid data for task_key %s, retrying: %r',
Marc-Antoine Ruel200b3952014-08-14 11:07:44 -0400389 task_key, response)
Vadim Shtayurab19319e2014-04-27 08:50:06 -0700390 continue
391
392 # Swarming server uses non-empty 'output' value as a flag that task has
393 # finished. How to wait for tasks that produce no output is a mystery.
394 if result.get('output'):
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700395 # Record the result, try to fetch attached output files (if any).
396 if output_collector:
397 # TODO(vadimsh): Respect |should_stop| and |deadline| when fetching.
Vadim Shtayurab450c602014-05-12 19:23:25 -0700398 output_collector.process_shard_result(shard_index, result)
Vadim Shtayurab19319e2014-04-27 08:50:06 -0700399 return result
maruel@chromium.org0437a732013-08-27 16:05:52 +0000400
401
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700402def yield_results(
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700403 swarm_base_url, task_keys, timeout, max_threads,
404 print_status_updates, output_collector):
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500405 """Yields swarming task results from the swarming server as (index, result).
maruel@chromium.org0437a732013-08-27 16:05:52 +0000406
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700407 Duplicate shards are ignored. Shards are yielded in order of completion.
408 Timed out shards are NOT yielded at all. Caller can compare number of yielded
409 shards with len(task_keys) to verify all shards completed.
maruel@chromium.org0437a732013-08-27 16:05:52 +0000410
411 max_threads is optional and is used to limit the number of parallel fetches
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500412 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 +0000413 worth normally to limit the number threads. Mostly used for testing purposes.
Marc-Antoine Ruel5c720342014-02-21 14:46:14 -0500414
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700415 output_collector is an optional instance of TaskOutputCollector that will be
416 used to fetch files produced by a task from isolate server to the local disk.
417
Marc-Antoine Ruel5c720342014-02-21 14:46:14 -0500418 Yields:
419 (index, result). In particular, 'result' is defined as the
420 GetRunnerResults() function in services/swarming/server/test_runner.py.
maruel@chromium.org0437a732013-08-27 16:05:52 +0000421 """
maruel@chromium.org0437a732013-08-27 16:05:52 +0000422 number_threads = (
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500423 min(max_threads, len(task_keys)) if max_threads else len(task_keys))
Vadim Shtayurab19319e2014-04-27 08:50:06 -0700424 should_stop = threading.Event()
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700425 results_channel = threading_utils.TaskChannel()
Vadim Shtayurab19319e2014-04-27 08:50:06 -0700426
maruel@chromium.org0437a732013-08-27 16:05:52 +0000427 with threading_utils.ThreadPool(number_threads, number_threads, 0) as pool:
428 try:
Vadim Shtayurab450c602014-05-12 19:23:25 -0700429 # Adds a task to the thread pool to call 'retrieve_results' and return
430 # the results together with shard_index that produced them (as a tuple).
431 def enqueue_retrieve_results(shard_index, task_key):
432 task_fn = lambda *args: (shard_index, retrieve_results(*args))
maruel@chromium.org0437a732013-08-27 16:05:52 +0000433 pool.add_task(
Vadim Shtayurab450c602014-05-12 19:23:25 -0700434 0, results_channel.wrap_task(task_fn),
435 swarm_base_url, shard_index, task_key, timeout,
436 should_stop, output_collector)
437
438 # Enqueue 'retrieve_results' calls for each shard key to run in parallel.
439 for shard_index, task_key in enumerate(task_keys):
440 enqueue_retrieve_results(shard_index, task_key)
Vadim Shtayurab19319e2014-04-27 08:50:06 -0700441
442 # Wait for all of them to finish.
443 shards_remaining = range(len(task_keys))
444 active_task_count = len(task_keys)
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700445 while active_task_count:
Vadim Shtayurab450c602014-05-12 19:23:25 -0700446 shard_index, result = None, None
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700447 try:
Vadim Shtayurab450c602014-05-12 19:23:25 -0700448 shard_index, result = results_channel.pull(
449 timeout=STATUS_UPDATE_INTERVAL)
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700450 except threading_utils.TaskChannel.Timeout:
451 if print_status_updates:
452 print(
453 'Waiting for results from the following shards: %s' %
454 ', '.join(map(str, shards_remaining)))
455 sys.stdout.flush()
456 continue
457 except Exception:
458 logging.exception('Unexpected exception in retrieve_results')
Vadim Shtayurab19319e2014-04-27 08:50:06 -0700459
460 # A call to 'retrieve_results' finished (successfully or not).
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700461 active_task_count -= 1
maruel@chromium.org0437a732013-08-27 16:05:52 +0000462 if not result:
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500463 logging.error('Failed to retrieve the results for a swarming key')
maruel@chromium.org0437a732013-08-27 16:05:52 +0000464 continue
Vadim Shtayurab19319e2014-04-27 08:50:06 -0700465
Vadim Shtayurab450c602014-05-12 19:23:25 -0700466 # Yield back results to the caller.
467 assert shard_index in shards_remaining
468 shards_remaining.remove(shard_index)
469 yield shard_index, result
Vadim Shtayurab19319e2014-04-27 08:50:06 -0700470
maruel@chromium.org0437a732013-08-27 16:05:52 +0000471 finally:
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700472 # Done or aborted with Ctrl+C, kill the remaining threads.
maruel@chromium.org0437a732013-08-27 16:05:52 +0000473 should_stop.set()
474
475
Vadim Shtayurab450c602014-05-12 19:23:25 -0700476def setup_run_isolated(manifest, bundle):
477 """Sets up the manifest to run an isolated task via run_isolated.py.
maruel@chromium.org0437a732013-08-27 16:05:52 +0000478
Vadim Shtayurab450c602014-05-12 19:23:25 -0700479 Modifies |bundle| (by adding files) and |manifest| (by adding commands) in
480 place.
481
482 Args:
483 manifest: Manifest with swarm task definition.
484 bundle: ZipPackage with files that would be transfered to swarm bot.
485 If None, only |manifest| is modified (useful in tests).
maruel@chromium.org0437a732013-08-27 16:05:52 +0000486 """
vadimsh@chromium.org6b706212013-08-28 15:03:46 +0000487 # Add uncompressed zip here. It'll be compressed as part of the package sent
488 # to Swarming server.
489 run_test_name = 'run_isolated.zip'
Vadim Shtayurab450c602014-05-12 19:23:25 -0700490 if bundle and run_test_name not in bundle.files:
491 bundle.add_buffer(
492 run_test_name,
493 run_isolated.get_as_zip_package().zip_into_buffer(compress=False))
maruel@chromium.org0437a732013-08-27 16:05:52 +0000494
vadimsh@chromium.org6b706212013-08-28 15:03:46 +0000495 cleanup_script_name = 'swarm_cleanup.py'
Vadim Shtayurab450c602014-05-12 19:23:25 -0700496 if bundle and cleanup_script_name not in bundle.files:
497 bundle.add_file(
498 os.path.join(TOOLS_PATH, cleanup_script_name), cleanup_script_name)
vadimsh@chromium.org6b706212013-08-28 15:03:46 +0000499
maruel@chromium.org0437a732013-08-27 16:05:52 +0000500 run_cmd = [
501 'python', run_test_name,
maruel@chromium.org814d23f2013-10-01 19:08:00 +0000502 '--hash', manifest.isolated_hash,
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500503 '--namespace', manifest.namespace,
maruel@chromium.org0437a732013-08-27 16:05:52 +0000504 ]
Marc-Antoine Ruel8806e622014-02-12 14:15:53 -0500505 if file_path.is_url(manifest.isolate_server):
506 run_cmd.extend(('--isolate-server', manifest.isolate_server))
507 else:
508 run_cmd.extend(('--indir', manifest.isolate_server))
509
maruel@chromium.org0437a732013-08-27 16:05:52 +0000510 if manifest.verbose or manifest.profile:
511 # Have it print the profiling section.
512 run_cmd.append('--verbose')
Vadim Shtayuraae8085b2014-05-02 17:13:10 -0700513
514 # Pass all extra args for run_isolated.py, it will pass them to the command.
515 if manifest.extra_args:
516 run_cmd.append('--')
517 run_cmd.extend(manifest.extra_args)
518
maruel@chromium.org0437a732013-08-27 16:05:52 +0000519 manifest.add_task('Run Test', run_cmd)
520
521 # Clean up
522 manifest.add_task('Clean Up', ['python', cleanup_script_name])
523
524
Vadim Shtayurab450c602014-05-12 19:23:25 -0700525def setup_googletest(env, shards, index):
Marc-Antoine Ruelcd629732013-12-20 15:00:42 -0500526 """Sets googletest specific environment variables."""
527 if shards > 1:
528 env = env.copy()
Vadim Shtayurab450c602014-05-12 19:23:25 -0700529 env['GTEST_SHARD_INDEX'] = str(index)
530 env['GTEST_TOTAL_SHARDS'] = str(shards)
Marc-Antoine Ruelcd629732013-12-20 15:00:42 -0500531 return env
532
533
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500534def archive(isolate_server, namespace, isolated, algo, verbose):
maruel@chromium.org0437a732013-08-27 16:05:52 +0000535 """Archives a .isolated and all the dependencies on the CAC."""
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500536 logging.info('archive(%s, %s, %s)', isolate_server, namespace, isolated)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000537 tempdir = None
Marc-Antoine Ruel8806e622014-02-12 14:15:53 -0500538 if file_path.is_url(isolate_server):
539 command = 'archive'
540 flag = '--isolate-server'
541 else:
542 command = 'hashtable'
543 flag = '--outdir'
544
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500545 print('Archiving: %s' % isolated)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000546 try:
maruel@chromium.org0437a732013-08-27 16:05:52 +0000547 cmd = [
548 sys.executable,
549 os.path.join(ROOT_DIR, 'isolate.py'),
Marc-Antoine Ruel8806e622014-02-12 14:15:53 -0500550 command,
551 flag, isolate_server,
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500552 '--namespace', namespace,
maruel@chromium.org0437a732013-08-27 16:05:52 +0000553 '--isolated', isolated,
554 ]
maruel@chromium.orge9403ab2013-09-20 18:03:49 +0000555 cmd.extend(['--verbose'] * verbose)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000556 logging.info(' '.join(cmd))
557 if subprocess.call(cmd, verbose):
558 return
Marc-Antoine Ruel8bee66d2014-08-28 19:02:07 -0400559 return isolated_format.hash_file(isolated, algo)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000560 finally:
561 if tempdir:
562 shutil.rmtree(tempdir)
563
564
Vadim Shtayurab450c602014-05-12 19:23:25 -0700565def get_shard_task_name(task_name, shards, index):
566 """Returns a task name to use for a single shard of a task."""
567 if shards == 1:
568 return task_name
569 return '%s:%s:%s' % (task_name, shards, index)
570
571
572def upload_zip_bundle(isolate_server, bundle):
573 """Uploads a zip package to isolate storage and returns raw fetch URL.
574
575 Args:
576 isolate_server: URL of an isolate server.
577 bundle: instance of ZipPackage to upload.
578
579 Returns:
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -0400580 URL to get the file from.
Vadim Shtayurab450c602014-05-12 19:23:25 -0700581 """
582 # Swarming bot would need to be able to grab the file from the storage
583 # using raw HTTP GET. Use 'default' namespace so that the raw data returned
584 # to a bot is not zipped, since swarm_bot doesn't understand compressed
585 # data yet. This namespace have nothing to do with |namespace| passed to
586 # run_isolated.py that is used to store files for isolated task.
587 logging.info('Zipping up and uploading files...')
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -0400588 start_time = now()
589 isolate_item = isolateserver.BufferItem(
590 bundle.zip_into_buffer(), high_priority=True)
591 with isolateserver.get_storage(isolate_server, 'default') as storage:
592 uploaded = storage.upload_items([isolate_item])
593 bundle_url = storage.get_fetch_url(isolate_item)
594 elapsed = now() - start_time
Vadim Shtayurab450c602014-05-12 19:23:25 -0700595 if isolate_item in uploaded:
596 logging.info('Upload complete, time elapsed: %f', elapsed)
597 else:
598 logging.info('Zip file already on server, time elapsed: %f', elapsed)
599 return bundle_url
600
601
602def trigger_by_manifest(swarming, manifest):
603 """Given a task manifest, triggers it for execution on swarming.
604
605 Args:
606 swarming: URL of a swarming service.
607 manifest: instance of Manifest.
608
609 Returns:
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -0400610 tuple(Task id, priority) on success. tuple(None, None) on failure.
Vadim Shtayurab450c602014-05-12 19:23:25 -0700611 """
612 logging.info('Triggering: %s', manifest.task_name)
613 manifest_text = manifest.to_json()
614 result = net.url_read(swarming + '/test', data={'request': manifest_text})
615 if not result:
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -0400616 on_error.report('Failed to trigger task %s' % manifest.task_name)
Vadim Shtayura1c024f72014-07-09 19:00:10 -0700617 return None, None
Vadim Shtayurab450c602014-05-12 19:23:25 -0700618 try:
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -0400619 data = json.loads(result)
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -0400620 except (ValueError, TypeError):
Vadim Shtayurab450c602014-05-12 19:23:25 -0700621 msg = '\n'.join((
622 'Failed to trigger task %s' % manifest.task_name,
623 'Manifest: %s' % manifest_text,
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -0400624 'Bad response: %s' % result))
625 on_error.report(msg)
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -0400626 return None, None
627 if not data:
628 return None, None
629 return data['test_keys'][0]['test_key'], data['priority']
Vadim Shtayurab450c602014-05-12 19:23:25 -0700630
631
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -0400632def abort_task(_swarming, _manifest):
Vadim Shtayurab450c602014-05-12 19:23:25 -0700633 """Given a task manifest that was triggered, aborts its execution."""
634 # TODO(vadimsh): No supported by the server yet.
635
636
637def trigger_task_shards(
Vadim Shtayuraae8085b2014-05-02 17:13:10 -0700638 swarming, isolate_server, namespace, isolated_hash, task_name, extra_args,
Marc-Antoine Ruelaea50652014-06-12 14:23:48 -0400639 shards, dimensions, env, deadline, verbose, profile, priority):
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -0400640 """Triggers multiple subtasks of a sharded task.
641
642 Returns:
Vadim Shtayuraf27448e2014-06-26 11:35:05 -0700643 Dict with task details, returned to caller as part of --dump-json output.
644 None in case of failure.
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -0400645 """
Vadim Shtayurab450c602014-05-12 19:23:25 -0700646 # Collects all files that are necessary to bootstrap a task execution
647 # on the bot. Usually it includes self contained run_isolated.zip and
648 # a bunch of small other scripts. All heavy files are pulled
649 # by run_isolated.zip. Updated in 'setup_run_isolated'.
650 bundle = zip_package.ZipPackage(ROOT_DIR)
651
652 # Make a separate Manifest for each shard, put shard index and number of
653 # shards into env and subtask name.
654 manifests = []
655 for index in xrange(shards):
maruel@chromium.org0437a732013-08-27 16:05:52 +0000656 manifest = Manifest(
Marc-Antoine Ruela7049872013-11-05 19:28:35 -0500657 isolate_server=isolate_server,
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500658 namespace=namespace,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500659 isolated_hash=isolated_hash,
Vadim Shtayurab450c602014-05-12 19:23:25 -0700660 task_name=get_shard_task_name(task_name, shards, index),
Vadim Shtayuraae8085b2014-05-02 17:13:10 -0700661 extra_args=extra_args,
Marc-Antoine Ruel92f32422013-11-06 18:12:13 -0500662 dimensions=dimensions,
Vadim Shtayurab450c602014-05-12 19:23:25 -0700663 env=setup_googletest(env, shards, index),
Marc-Antoine Ruel13b7b782014-03-14 11:14:57 -0400664 deadline=deadline,
Marc-Antoine Ruela7049872013-11-05 19:28:35 -0500665 verbose=verbose,
666 profile=profile,
Vadim Shtayurabcff74f2014-02-27 16:19:34 -0800667 priority=priority)
Vadim Shtayurab450c602014-05-12 19:23:25 -0700668 setup_run_isolated(manifest, bundle)
669 manifests.append(manifest)
670
671 # Upload zip bundle file to get its URL.
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -0400672 try:
673 bundle_url = upload_zip_bundle(isolate_server, bundle)
674 except (IOError, OSError):
675 on_error.report('Failed to upload the zip file for task %s' % task_name)
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -0400676 return None, None
maruel@chromium.org0437a732013-08-27 16:05:52 +0000677
Vadim Shtayurab450c602014-05-12 19:23:25 -0700678 # Attach that file to all manifests.
679 for manifest in manifests:
680 manifest.add_bundled_file('swarm_data.zip', bundle_url)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000681
Vadim Shtayurab450c602014-05-12 19:23:25 -0700682 # Trigger all the subtasks.
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -0400683 tasks = {}
684 priority_warning = False
Vadim Shtayuraf27448e2014-06-26 11:35:05 -0700685 for index, manifest in enumerate(manifests):
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -0400686 task_id, priority = trigger_by_manifest(swarming, manifest)
687 if not task_id:
Vadim Shtayurab450c602014-05-12 19:23:25 -0700688 break
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -0400689 if not priority_warning and priority != manifest.priority:
690 priority_warning = True
691 print >> sys.stderr, 'Priority was reset to %s' % priority
Vadim Shtayuraf27448e2014-06-26 11:35:05 -0700692 tasks[manifest.task_name] = {
693 'shard_index': index,
694 'task_id': task_id,
695 'view_url': '%s/user/task/%s' % (swarming, task_id),
696 }
Vadim Shtayurab450c602014-05-12 19:23:25 -0700697
698 # Some shards weren't triggered. Abort everything.
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -0400699 if len(tasks) != len(manifests):
700 if tasks:
Vadim Shtayurab450c602014-05-12 19:23:25 -0700701 print >> sys.stderr, 'Not all shards were triggered'
Vadim Shtayuraf27448e2014-06-26 11:35:05 -0700702 for task_dict in tasks.itervalues():
703 abort_task(swarming, task_dict['task_id'])
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -0400704 return None
maruel@chromium.org0437a732013-08-27 16:05:52 +0000705
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -0400706 return tasks
maruel@chromium.org0437a732013-08-27 16:05:52 +0000707
708
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500709def isolated_to_hash(isolate_server, namespace, arg, algo, verbose):
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500710 """Archives a .isolated file if needed.
711
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500712 Returns the file hash to trigger and a bool specifying if it was a file (True)
713 or a hash (False).
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500714 """
715 if arg.endswith('.isolated'):
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500716 file_hash = archive(isolate_server, namespace, arg, algo, verbose)
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500717 if not file_hash:
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -0400718 on_error.report('Archival failure %s' % arg)
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500719 return None, True
720 return file_hash, True
Marc-Antoine Ruel8bee66d2014-08-28 19:02:07 -0400721 elif isolated_format.is_valid_hash(arg, algo):
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500722 return arg, False
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500723 else:
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -0400724 on_error.report('Invalid hash %s' % arg)
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500725 return None, False
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500726
727
maruel@chromium.org0437a732013-08-27 16:05:52 +0000728def trigger(
Marc-Antoine Ruela7049872013-11-05 19:28:35 -0500729 swarming,
730 isolate_server,
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500731 namespace,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500732 file_hash_or_isolated,
733 task_name,
Vadim Shtayuraae8085b2014-05-02 17:13:10 -0700734 extra_args,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500735 shards,
Marc-Antoine Ruel92f32422013-11-06 18:12:13 -0500736 dimensions,
737 env,
Marc-Antoine Ruel13b7b782014-03-14 11:14:57 -0400738 deadline,
maruel@chromium.org0437a732013-08-27 16:05:52 +0000739 verbose,
740 profile,
741 priority):
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -0400742 """Sends off the hash swarming task requests.
743
744 Returns:
745 tuple(dict(task_name: task_id), base task name). The dict of tasks is None
746 in case of failure.
747 """
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500748 file_hash, is_file = isolated_to_hash(
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500749 isolate_server, namespace, file_hash_or_isolated, hashlib.sha1, verbose)
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500750 if not file_hash:
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500751 return 1, ''
752 if not task_name:
753 # If a file name was passed, use its base name of the isolated hash.
754 # Otherwise, use user name as an approximation of a task name.
755 if is_file:
756 key = os.path.splitext(os.path.basename(file_hash_or_isolated))[0]
757 else:
758 key = getpass.getuser()
Vadim Shtayurac3d97b02014-04-26 19:16:05 -0700759 task_name = '%s/%s/%s/%d' % (
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500760 key,
761 '_'.join('%s=%s' % (k, v) for k, v in sorted(dimensions.iteritems())),
Vadim Shtayurac3d97b02014-04-26 19:16:05 -0700762 file_hash,
763 now() * 1000)
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500764
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -0400765 tasks = trigger_task_shards(
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500766 swarming=swarming,
767 isolate_server=isolate_server,
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500768 namespace=namespace,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500769 isolated_hash=file_hash,
770 task_name=task_name,
Vadim Shtayuraae8085b2014-05-02 17:13:10 -0700771 extra_args=extra_args,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500772 shards=shards,
773 dimensions=dimensions,
Marc-Antoine Ruel13b7b782014-03-14 11:14:57 -0400774 deadline=deadline,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500775 env=env,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500776 verbose=verbose,
777 profile=profile,
Vadim Shtayurabcff74f2014-02-27 16:19:34 -0800778 priority=priority)
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -0400779 return tasks, task_name
maruel@chromium.org0437a732013-08-27 16:05:52 +0000780
781
Vadim Shtayurab450c602014-05-12 19:23:25 -0700782def decorate_shard_output(shard_index, result, shard_exit_code):
maruel@chromium.org0437a732013-08-27 16:05:52 +0000783 """Returns wrapped output for swarming task shard."""
784 tag = 'index %s (machine tag: %s, id: %s)' % (
Vadim Shtayurab450c602014-05-12 19:23:25 -0700785 shard_index,
maruel@chromium.org0437a732013-08-27 16:05:52 +0000786 result['machine_id'],
787 result.get('machine_tag', 'unknown'))
788 return (
789 '\n'
790 '================================================================\n'
791 'Begin output from shard %s\n'
792 '================================================================\n'
793 '\n'
794 '%s'
795 '================================================================\n'
Vadim Shtayura473455a2014-05-14 15:22:35 -0700796 'End output from shard %s.\nExit code %d (%s).\n'
797 '================================================================\n') % (
798 tag, result['output'] or NO_OUTPUT_FOUND, tag,
799 shard_exit_code, hex(0xffffffff & shard_exit_code))
maruel@chromium.org0437a732013-08-27 16:05:52 +0000800
801
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700802def collect(
Vadim Shtayurab450c602014-05-12 19:23:25 -0700803 url, task_name, shards, timeout, decorate,
Vadim Shtayurac8437bf2014-07-09 19:45:36 -0700804 print_status_updates, task_summary_json, task_output_dir):
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500805 """Retrieves results of a Swarming task."""
Vadim Shtayurab450c602014-05-12 19:23:25 -0700806 # Grab task keys for each shard. Order is important, used to figure out
807 # shard index based on the key.
808 # TODO(vadimsh): Simplify this once server support is added.
809 task_keys = []
810 for index in xrange(shards):
811 shard_task_name = get_shard_task_name(task_name, shards, index)
812 logging.info('Collecting %s', shard_task_name)
813 shard_task_keys = get_task_keys(url, shard_task_name)
814 if not shard_task_keys:
815 raise Failure('No task keys to get results with: %s' % shard_task_name)
816 if len(shard_task_keys) != 1:
817 raise Failure('Expecting only one shard for a task: %s' % shard_task_name)
818 task_keys.append(shard_task_keys[0])
maruel@chromium.org0437a732013-08-27 16:05:52 +0000819
Vadim Shtayurac8437bf2014-07-09 19:45:36 -0700820 # Collect summary JSON and output files (if task_output_dir is not None).
821 output_collector = TaskOutputCollector(
822 task_output_dir, task_name, len(task_keys))
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700823
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700824 seen_shards = set()
Vadim Shtayurac524f512014-05-15 09:54:56 -0700825 exit_codes = []
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700826
827 try:
828 for index, output in yield_results(
829 url, task_keys, timeout, None, print_status_updates, output_collector):
830 seen_shards.add(index)
Vadim Shtayura473455a2014-05-14 15:22:35 -0700831
832 # Grab first non-zero exit code as an overall shard exit code.
833 shard_exit_code = 0
834 for code in map(int, (output['exit_codes'] or '1').split(',')):
835 if code:
836 shard_exit_code = code
837 break
Vadim Shtayurac524f512014-05-15 09:54:56 -0700838 exit_codes.append(shard_exit_code)
Vadim Shtayura473455a2014-05-14 15:22:35 -0700839
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700840 if decorate:
Vadim Shtayurab450c602014-05-12 19:23:25 -0700841 print decorate_shard_output(index, output, shard_exit_code)
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700842 else:
843 print(
844 '%s/%s: %s' % (
845 output['machine_id'],
846 output['machine_tag'],
847 output['exit_codes']))
848 print(''.join(' %s\n' % l for l in output['output'].splitlines()))
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700849 finally:
Vadim Shtayurac8437bf2014-07-09 19:45:36 -0700850 summary = output_collector.finalize()
851 if task_summary_json:
852 tools.write_json(task_summary_json, summary, False)
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700853
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700854 if len(seen_shards) != len(task_keys):
855 missing_shards = [x for x in range(len(task_keys)) if x not in seen_shards]
856 print >> sys.stderr, ('Results from some shards are missing: %s' %
857 ', '.join(map(str, missing_shards)))
Vadim Shtayurac524f512014-05-15 09:54:56 -0700858 return 1
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700859
Vadim Shtayurac524f512014-05-15 09:54:56 -0700860 return int(bool(any(exit_codes)))
maruel@chromium.org0437a732013-08-27 16:05:52 +0000861
862
Marc-Antoine Ruel819fb162014-03-12 16:38:26 -0400863def add_filter_options(parser):
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500864 parser.filter_group = tools.optparse.OptionGroup(parser, 'Filtering slaves')
865 parser.filter_group.add_option(
Marc-Antoine Ruelb39e8cf2014-01-20 10:39:31 -0500866 '-d', '--dimension', default=[], action='append', nargs=2,
Marc-Antoine Ruel92f32422013-11-06 18:12:13 -0500867 dest='dimensions', metavar='FOO bar',
868 help='dimension to filter on')
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500869 parser.add_option_group(parser.filter_group)
870
Marc-Antoine Ruel819fb162014-03-12 16:38:26 -0400871
Marc-Antoine Ruel025e7822014-05-01 11:50:24 -0400872def process_filter_options(parser, options):
873 options.dimensions = dict(options.dimensions)
874 if not options.dimensions:
875 parser.error('Please at least specify one --dimension')
876
877
Vadim Shtayurab450c602014-05-12 19:23:25 -0700878def add_sharding_options(parser):
879 parser.sharding_group = tools.optparse.OptionGroup(parser, 'Sharding options')
880 parser.sharding_group.add_option(
881 '--shards', type='int', default=1,
882 help='Number of shards to trigger and collect.')
883 parser.add_option_group(parser.sharding_group)
884
885
Marc-Antoine Ruel819fb162014-03-12 16:38:26 -0400886def add_trigger_options(parser):
887 """Adds all options to trigger a task on Swarming."""
888 isolateserver.add_isolate_server_options(parser, True)
889 add_filter_options(parser)
890
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500891 parser.task_group = tools.optparse.OptionGroup(parser, 'Task properties')
892 parser.task_group.add_option(
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500893 '-e', '--env', default=[], action='append', nargs=2, metavar='FOO bar',
Vadim Shtayurab450c602014-05-12 19:23:25 -0700894 help='Environment variables to set')
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500895 parser.task_group.add_option(
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500896 '--priority', type='int', default=100,
897 help='The lower value, the more important the task is')
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500898 parser.task_group.add_option(
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500899 '-T', '--task-name',
900 help='Display name of the task. It uniquely identifies the task. '
Vadim Shtayurac3d97b02014-04-26 19:16:05 -0700901 'Defaults to <base_name>/<dimensions>/<isolated hash>/<timestamp> '
902 'if an isolated file is provided, if a hash is provided, it '
903 'defaults to <user>/<dimensions>/<isolated hash>/<timestamp>')
Marc-Antoine Ruel13b7b782014-03-14 11:14:57 -0400904 parser.task_group.add_option(
905 '--deadline', type='int', default=6*60*60,
906 help='Seconds to allow the task to be pending for a bot to run before '
907 'this task request expires.')
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500908 parser.add_option_group(parser.task_group)
Marc-Antoine Ruelcd629732013-12-20 15:00:42 -0500909 # TODO(maruel): This is currently written in a chromium-specific way.
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500910 parser.group_logging.add_option(
maruel@chromium.org0437a732013-08-27 16:05:52 +0000911 '--profile', action='store_true',
912 default=bool(os.environ.get('ISOLATE_DEBUG')),
913 help='Have run_isolated.py print profiling info')
maruel@chromium.org0437a732013-08-27 16:05:52 +0000914
915
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500916def process_trigger_options(parser, options, args):
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500917 isolateserver.process_isolate_server_options(parser, options)
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500918 if len(args) != 1:
919 parser.error('Must pass one .isolated file or its hash (sha1).')
Marc-Antoine Ruel025e7822014-05-01 11:50:24 -0400920 process_filter_options(parser, options)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000921
922
923def add_collect_options(parser):
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500924 parser.server_group.add_option(
maruel@chromium.org0437a732013-08-27 16:05:52 +0000925 '-t', '--timeout',
926 type='float',
927 default=DEFAULT_SHARD_WAIT_TIME,
928 help='Timeout to wait for result, set to 0 for no timeout; default: '
929 '%default s')
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500930 parser.group_logging.add_option(
931 '--decorate', action='store_true', help='Decorate output')
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700932 parser.group_logging.add_option(
933 '--print-status-updates', action='store_true',
934 help='Print periodic status updates')
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700935 parser.task_output_group = tools.optparse.OptionGroup(parser, 'Task output')
936 parser.task_output_group.add_option(
Vadim Shtayurac8437bf2014-07-09 19:45:36 -0700937 '--task-summary-json',
938 metavar='FILE',
939 help='Dump a summary of task results to this file as json. It contains '
940 'only shards statuses as know to server directly. Any output files '
941 'emitted by the task can be collected by using --task-output-dir')
942 parser.task_output_group.add_option(
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700943 '--task-output-dir',
Vadim Shtayurac8437bf2014-07-09 19:45:36 -0700944 metavar='DIR',
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700945 help='Directory to put task results into. When the task finishes, this '
Vadim Shtayurac8437bf2014-07-09 19:45:36 -0700946 'directory contains per-shard directory with output files produced '
947 'by shards: <task-output-dir>/<zero-based-shard-index>/.')
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700948 parser.add_option_group(parser.task_output_group)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000949
950
Vadim Shtayuraae8085b2014-05-02 17:13:10 -0700951def extract_isolated_command_extra_args(args):
952 try:
953 index = args.index('--')
954 except ValueError:
955 return (args, [])
956 return (args[:index], args[index+1:])
957
958
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500959@subcommand.usage('task_name')
maruel@chromium.org0437a732013-08-27 16:05:52 +0000960def CMDcollect(parser, args):
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500961 """Retrieves results of a Swarming task.
maruel@chromium.org0437a732013-08-27 16:05:52 +0000962
963 The result can be in multiple part if the execution was sharded. It can
964 potentially have retries.
965 """
966 add_collect_options(parser)
Vadim Shtayurab450c602014-05-12 19:23:25 -0700967 add_sharding_options(parser)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000968 (options, args) = parser.parse_args(args)
969 if not args:
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500970 parser.error('Must specify one task name.')
maruel@chromium.org0437a732013-08-27 16:05:52 +0000971 elif len(args) > 1:
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500972 parser.error('Must specify only one task name.')
maruel@chromium.org0437a732013-08-27 16:05:52 +0000973
Vadim Shtayura6b555c12014-07-23 16:22:18 -0700974 auth.ensure_logged_in(options.swarming)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000975 try:
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700976 return collect(
977 options.swarming,
978 args[0],
Vadim Shtayurab450c602014-05-12 19:23:25 -0700979 options.shards,
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700980 options.timeout,
981 options.decorate,
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700982 options.print_status_updates,
Vadim Shtayurac8437bf2014-07-09 19:45:36 -0700983 options.task_summary_json,
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700984 options.task_output_dir)
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -0400985 except Failure:
986 on_error.report(None)
vadimsh@chromium.orgd908a542013-10-30 01:36:17 +0000987 return 1
maruel@chromium.org0437a732013-08-27 16:05:52 +0000988
989
Marc-Antoine Ruel819fb162014-03-12 16:38:26 -0400990def CMDquery(parser, args):
991 """Returns information about the bots connected to the Swarming server."""
992 add_filter_options(parser)
993 parser.filter_group.add_option(
Marc-Antoine Ruel28083112014-03-13 16:34:04 -0400994 '--dead-only', action='store_true',
995 help='Only print dead bots, useful to reap them and reimage broken bots')
996 parser.filter_group.add_option(
Marc-Antoine Ruel819fb162014-03-12 16:38:26 -0400997 '-k', '--keep-dead', action='store_true',
998 help='Do not filter out dead bots')
999 parser.filter_group.add_option(
1000 '-b', '--bare', action='store_true',
Marc-Antoine Ruele7b00162014-03-12 16:59:01 -04001001 help='Do not print out dimensions')
Marc-Antoine Ruel819fb162014-03-12 16:38:26 -04001002 options, args = parser.parse_args(args)
Marc-Antoine Ruel28083112014-03-13 16:34:04 -04001003
1004 if options.keep_dead and options.dead_only:
1005 parser.error('Use only one of --keep-dead and --dead-only')
Vadim Shtayura6b555c12014-07-23 16:22:18 -07001006
1007 auth.ensure_logged_in(options.swarming)
Marc-Antoine Ruel0a620612014-08-13 15:47:07 -04001008 data = net.url_read_json(options.swarming + '/swarming/api/v1/bots')
Marc-Antoine Ruel819fb162014-03-12 16:38:26 -04001009 if data is None:
1010 print >> sys.stderr, 'Failed to access %s' % options.swarming
1011 return 1
Marc-Antoine Ruele4bebbc2014-06-04 09:36:14 -04001012 for machine in natsort.natsorted(data['machines'], key=lambda x: x['id']):
Marc-Antoine Ruel28083112014-03-13 16:34:04 -04001013 if options.dead_only:
Marc-Antoine Ruelfe844672014-07-31 11:16:51 -04001014 if not machine['is_dead']:
Marc-Antoine Ruel28083112014-03-13 16:34:04 -04001015 continue
Marc-Antoine Ruelfe844672014-07-31 11:16:51 -04001016 elif not options.keep_dead and machine['is_dead']:
Marc-Antoine Ruel819fb162014-03-12 16:38:26 -04001017 continue
1018
Marc-Antoine Ruele7b00162014-03-12 16:59:01 -04001019 # If the user requested to filter on dimensions, ensure the bot has all the
1020 # dimensions requested.
Marc-Antoine Ruel819fb162014-03-12 16:38:26 -04001021 dimensions = machine['dimensions']
1022 for key, value in options.dimensions:
1023 if key not in dimensions:
1024 break
Marc-Antoine Ruele7b00162014-03-12 16:59:01 -04001025 # A bot can have multiple value for a key, for example,
1026 # {'os': ['Windows', 'Windows-6.1']}, so that --dimension os=Windows will
1027 # be accepted.
Marc-Antoine Ruel819fb162014-03-12 16:38:26 -04001028 if isinstance(dimensions[key], list):
1029 if value not in dimensions[key]:
1030 break
1031 else:
1032 if value != dimensions[key]:
1033 break
1034 else:
Marc-Antoine Ruele4bebbc2014-06-04 09:36:14 -04001035 print machine['id']
Marc-Antoine Ruele7b00162014-03-12 16:59:01 -04001036 if not options.bare:
Marc-Antoine Ruel0a620612014-08-13 15:47:07 -04001037 print ' %s' % json.dumps(dimensions, sort_keys=True)
Marc-Antoine Ruel819fb162014-03-12 16:38:26 -04001038 return 0
1039
1040
Vadim Shtayuraae8085b2014-05-02 17:13:10 -07001041@subcommand.usage('(hash|isolated) [-- extra_args]')
maruel@chromium.org0437a732013-08-27 16:05:52 +00001042def CMDrun(parser, args):
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001043 """Triggers a task and wait for the results.
maruel@chromium.org0437a732013-08-27 16:05:52 +00001044
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001045 Basically, does everything to run a command remotely.
maruel@chromium.org0437a732013-08-27 16:05:52 +00001046 """
1047 add_trigger_options(parser)
1048 add_collect_options(parser)
Vadim Shtayurab450c602014-05-12 19:23:25 -07001049 add_sharding_options(parser)
Vadim Shtayuraae8085b2014-05-02 17:13:10 -07001050 args, isolated_cmd_args = extract_isolated_command_extra_args(args)
maruel@chromium.org0437a732013-08-27 16:05:52 +00001051 options, args = parser.parse_args(args)
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001052 process_trigger_options(parser, options, args)
maruel@chromium.org0437a732013-08-27 16:05:52 +00001053
Vadim Shtayura6b555c12014-07-23 16:22:18 -07001054 auth.ensure_logged_in(options.swarming)
1055 if file_path.is_url(options.isolate_server):
1056 auth.ensure_logged_in(options.isolate_server)
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001057 try:
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -04001058 tasks, task_name = trigger(
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001059 swarming=options.swarming,
Marc-Antoine Ruel8806e622014-02-12 14:15:53 -05001060 isolate_server=options.isolate_server or options.indir,
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -05001061 namespace=options.namespace,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001062 file_hash_or_isolated=args[0],
1063 task_name=options.task_name,
Vadim Shtayuraae8085b2014-05-02 17:13:10 -07001064 extra_args=isolated_cmd_args,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001065 shards=options.shards,
1066 dimensions=options.dimensions,
1067 env=dict(options.env),
Marc-Antoine Ruel13b7b782014-03-14 11:14:57 -04001068 deadline=options.deadline,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001069 verbose=options.verbose,
1070 profile=options.profile,
1071 priority=options.priority)
1072 except Failure as e:
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -04001073 on_error.report(
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001074 'Failed to trigger %s(%s): %s' %
1075 (options.task_name, args[0], e.args[0]))
1076 return 1
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -04001077 if not tasks:
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -04001078 on_error.report('Failed to trigger the task.')
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -04001079 return 1
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -05001080 if task_name != options.task_name:
1081 print('Triggered task: %s' % task_name)
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001082 try:
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -04001083 # TODO(maruel): Use task_ids, it's much more efficient!
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001084 return collect(
1085 options.swarming,
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -05001086 task_name,
Vadim Shtayurab450c602014-05-12 19:23:25 -07001087 options.shards,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001088 options.timeout,
Vadim Shtayura86a2cef2014-04-18 11:13:39 -07001089 options.decorate,
Vadim Shtayurae3fbd102014-04-29 17:05:21 -07001090 options.print_status_updates,
Vadim Shtayurac8437bf2014-07-09 19:45:36 -07001091 options.task_summary_json,
Vadim Shtayurae3fbd102014-04-29 17:05:21 -07001092 options.task_output_dir)
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -04001093 except Failure:
1094 on_error.report(None)
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001095 return 1
maruel@chromium.org0437a732013-08-27 16:05:52 +00001096
1097
Vadim Shtayuraae8085b2014-05-02 17:13:10 -07001098@subcommand.usage("(hash|isolated) [-- extra_args]")
maruel@chromium.org0437a732013-08-27 16:05:52 +00001099def CMDtrigger(parser, args):
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001100 """Triggers a Swarming task.
maruel@chromium.org0437a732013-08-27 16:05:52 +00001101
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001102 Accepts either the hash (sha1) of a .isolated file already uploaded or the
1103 path to an .isolated file to archive, packages it if needed and sends a
1104 Swarming manifest file to the Swarming server.
1105
1106 If an .isolated file is specified instead of an hash, it is first archived.
Vadim Shtayuraae8085b2014-05-02 17:13:10 -07001107
1108 Passes all extra arguments provided after '--' as additional command line
1109 arguments for an isolated command specified in *.isolate file.
maruel@chromium.org0437a732013-08-27 16:05:52 +00001110 """
1111 add_trigger_options(parser)
Vadim Shtayurab450c602014-05-12 19:23:25 -07001112 add_sharding_options(parser)
Vadim Shtayuraae8085b2014-05-02 17:13:10 -07001113 args, isolated_cmd_args = extract_isolated_command_extra_args(args)
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -04001114 parser.add_option(
1115 '--dump-json',
1116 metavar='FILE',
1117 help='Dump details about the triggered task(s) to this file as json')
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001118 options, args = parser.parse_args(args)
1119 process_trigger_options(parser, options, args)
maruel@chromium.org0437a732013-08-27 16:05:52 +00001120
Vadim Shtayura6b555c12014-07-23 16:22:18 -07001121 auth.ensure_logged_in(options.swarming)
1122 if file_path.is_url(options.isolate_server):
1123 auth.ensure_logged_in(options.isolate_server)
maruel@chromium.org0437a732013-08-27 16:05:52 +00001124 try:
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -04001125 tasks, task_name = trigger(
Marc-Antoine Ruela7049872013-11-05 19:28:35 -05001126 swarming=options.swarming,
Marc-Antoine Ruel8806e622014-02-12 14:15:53 -05001127 isolate_server=options.isolate_server or options.indir,
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -05001128 namespace=options.namespace,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001129 file_hash_or_isolated=args[0],
1130 task_name=options.task_name,
Vadim Shtayuraae8085b2014-05-02 17:13:10 -07001131 extra_args=isolated_cmd_args,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001132 shards=options.shards,
Vadim Shtayuraae8085b2014-05-02 17:13:10 -07001133 dimensions=options.dimensions,
Marc-Antoine Ruel92f32422013-11-06 18:12:13 -05001134 env=dict(options.env),
Marc-Antoine Ruel13b7b782014-03-14 11:14:57 -04001135 deadline=options.deadline,
Marc-Antoine Ruela7049872013-11-05 19:28:35 -05001136 verbose=options.verbose,
1137 profile=options.profile,
1138 priority=options.priority)
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -04001139 if tasks:
1140 if task_name != options.task_name:
1141 print('Triggered task: %s' % task_name)
1142 if options.dump_json:
1143 data = {
1144 'base_task_name': task_name,
1145 'tasks': tasks,
1146 }
1147 tools.write_json(options.dump_json, data, True)
1148 return int(not tasks)
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -04001149 except Failure:
1150 on_error.report(None)
vadimsh@chromium.orgd908a542013-10-30 01:36:17 +00001151 return 1
maruel@chromium.org0437a732013-08-27 16:05:52 +00001152
1153
1154class OptionParserSwarming(tools.OptionParserWithLogging):
1155 def __init__(self, **kwargs):
1156 tools.OptionParserWithLogging.__init__(
1157 self, prog='swarming.py', **kwargs)
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -05001158 self.server_group = tools.optparse.OptionGroup(self, 'Server')
1159 self.server_group.add_option(
maruel@chromium.orge9403ab2013-09-20 18:03:49 +00001160 '-S', '--swarming',
Kevin Graney5346c162014-01-24 12:20:01 -05001161 metavar='URL', default=os.environ.get('SWARMING_SERVER', ''),
maruel@chromium.orge9403ab2013-09-20 18:03:49 +00001162 help='Swarming server to use')
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -05001163 self.add_option_group(self.server_group)
Vadim Shtayurae34e13a2014-02-02 11:23:26 -08001164 auth.add_auth_options(self)
maruel@chromium.org0437a732013-08-27 16:05:52 +00001165
1166 def parse_args(self, *args, **kwargs):
1167 options, args = tools.OptionParserWithLogging.parse_args(
1168 self, *args, **kwargs)
1169 options.swarming = options.swarming.rstrip('/')
1170 if not options.swarming:
1171 self.error('--swarming is required.')
Vadim Shtayura5d1efce2014-02-04 10:55:43 -08001172 auth.process_auth_options(self, options)
maruel@chromium.org0437a732013-08-27 16:05:52 +00001173 return options, args
1174
1175
1176def main(args):
1177 dispatcher = subcommand.CommandDispatcher(__name__)
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -04001178 return dispatcher.execute(OptionParserSwarming(version=__version__), args)
maruel@chromium.org0437a732013-08-27 16:05:52 +00001179
1180
1181if __name__ == '__main__':
1182 fix_encoding.fix_encoding()
1183 tools.disable_buffering()
1184 colorama.init()
1185 sys.exit(main(sys.argv[1:]))