blob: ad6c1f7ceac10a9c3f5032974d98cacb05e1d6fe [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
maruel@chromium.org7b844a62013-09-17 13:04:59 +000036import isolateserver
maruel@chromium.org0437a732013-08-27 16:05:52 +000037import run_isolated
38
39
40ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
41TOOLS_PATH = os.path.join(ROOT_DIR, 'tools')
42
43
maruel@chromium.org0437a732013-08-27 16:05:52 +000044# The default time to wait for a shard to finish running.
csharp@chromium.org24758492013-08-28 19:10:54 +000045DEFAULT_SHARD_WAIT_TIME = 80 * 60.
maruel@chromium.org0437a732013-08-27 16:05:52 +000046
Vadim Shtayura86a2cef2014-04-18 11:13:39 -070047# How often to print status updates to stdout in 'collect'.
48STATUS_UPDATE_INTERVAL = 15 * 60.
49
maruel@chromium.org0437a732013-08-27 16:05:52 +000050
51NO_OUTPUT_FOUND = (
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -050052 'No output produced by the task, it may have failed to run.\n'
maruel@chromium.org0437a732013-08-27 16:05:52 +000053 '\n')
54
55
maruel@chromium.org0437a732013-08-27 16:05:52 +000056class Failure(Exception):
57 """Generic failure."""
58 pass
59
60
61class Manifest(object):
Vadim Shtayurab450c602014-05-12 19:23:25 -070062 """Represents a Swarming task manifest."""
maruel@chromium.org0437a732013-08-27 16:05:52 +000063
maruel@chromium.org0437a732013-08-27 16:05:52 +000064 def __init__(
Vadim Shtayuraae8085b2014-05-02 17:13:10 -070065 self, isolate_server, namespace, isolated_hash, task_name, extra_args,
Marc-Antoine Ruelaea50652014-06-12 14:23:48 -040066 env, dimensions, deadline, verbose, profile,
Vadim Shtayuraae8085b2014-05-02 17:13:10 -070067 priority):
maruel@chromium.org0437a732013-08-27 16:05:52 +000068 """Populates a manifest object.
69 Args:
Marc-Antoine Ruela7049872013-11-05 19:28:35 -050070 isolate_server - isolate server url.
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -050071 namespace - isolate server namespace to use.
Vadim Shtayuraae8085b2014-05-02 17:13:10 -070072 isolated_hash - the manifest's sha-1 that the slave is going to fetch.
73 task_name - the name to give the task request.
74 extra_args - additional arguments to pass to isolated command.
Marc-Antoine Ruel05dab5e2013-11-06 15:06:47 -050075 env - environment variables to set.
Marc-Antoine Ruel92f32422013-11-06 18:12:13 -050076 dimensions - dimensions to filter the task on.
Marc-Antoine Ruel13b7b782014-03-14 11:14:57 -040077 deadline - maximum pending time before this task expires.
maruel@chromium.org0437a732013-08-27 16:05:52 +000078 verbose - if True, have the slave print more details.
79 profile - if True, have the slave print more timing data.
maruel@chromium.org7b844a62013-09-17 13:04:59 +000080 priority - int between 0 and 1000, lower the higher priority.
maruel@chromium.org0437a732013-08-27 16:05:52 +000081 """
Marc-Antoine Ruela7049872013-11-05 19:28:35 -050082 self.isolate_server = isolate_server
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -050083 self.namespace = namespace
maruel@chromium.org814d23f2013-10-01 19:08:00 +000084 self.isolated_hash = isolated_hash
Vadim Shtayurab450c602014-05-12 19:23:25 -070085 self.task_name = task_name
Vadim Shtayuraae8085b2014-05-02 17:13:10 -070086 self.extra_args = tuple(extra_args or [])
Vadim Shtayurab450c602014-05-12 19:23:25 -070087 self.env = env.copy()
88 self.dimensions = dimensions.copy()
Vadim Shtayurab450c602014-05-12 19:23:25 -070089 self.deadline = deadline
maruel@chromium.org0437a732013-08-27 16:05:52 +000090 self.verbose = bool(verbose)
91 self.profile = bool(profile)
92 self.priority = priority
maruel@chromium.org0437a732013-08-27 16:05:52 +000093 self._tasks = []
Vadim Shtayurab450c602014-05-12 19:23:25 -070094 self._files = []
maruel@chromium.org0437a732013-08-27 16:05:52 +000095
Marc-Antoine Ruelaf78a902014-03-20 10:42:49 -040096 def add_task(self, task_name, actions, time_out=2*60*60):
Marc-Antoine Ruel5c720342014-02-21 14:46:14 -050097 """Appends a new task as a TestObject to the swarming manifest file.
Marc-Antoine Ruelcd629732013-12-20 15:00:42 -050098
99 Tasks cannot be added once the manifest was uploaded.
Marc-Antoine Ruel5c720342014-02-21 14:46:14 -0500100
Marc-Antoine Ruelaf78a902014-03-20 10:42:49 -0400101 By default, command will be killed after 2 hours of execution.
102
Marc-Antoine Ruel5c720342014-02-21 14:46:14 -0500103 See TestObject in services/swarming/src/common/test_request_message.py for
104 the valid format.
Marc-Antoine Ruelcd629732013-12-20 15:00:42 -0500105 """
maruel@chromium.org0437a732013-08-27 16:05:52 +0000106 self._tasks.append(
107 {
108 'action': actions,
109 'decorate_output': self.verbose,
110 'test_name': task_name,
Marc-Antoine Ruelaf78a902014-03-20 10:42:49 -0400111 'hard_time_out': time_out,
maruel@chromium.org0437a732013-08-27 16:05:52 +0000112 })
113
Vadim Shtayurab450c602014-05-12 19:23:25 -0700114 def add_bundled_file(self, file_name, file_url):
115 """Appends a file to the manifest.
116
117 File will be downloaded and extracted by the swarm bot before launching the
118 task.
119 """
120 self._files.append([file_url, file_name])
121
maruel@chromium.org0437a732013-08-27 16:05:52 +0000122 def to_json(self):
123 """Exports the current configuration into a swarm-readable manifest file.
124
Marc-Antoine Ruel5c720342014-02-21 14:46:14 -0500125 The actual serialization format is defined as a TestCase object as described
126 in services/swarming/src/common/test_request_message.py
maruel@chromium.org0437a732013-08-27 16:05:52 +0000127 """
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500128 request = {
Marc-Antoine Ruela7049872013-11-05 19:28:35 -0500129 'cleanup': 'root',
maruel@chromium.org0437a732013-08-27 16:05:52 +0000130 'configurations': [
Marc-Antoine Ruel5c720342014-02-21 14:46:14 -0500131 # Is a TestConfiguration.
maruel@chromium.org0437a732013-08-27 16:05:52 +0000132 {
Marc-Antoine Ruel5d799192013-11-06 15:20:39 -0500133 'config_name': 'isolated',
Vadim Shtayurab450c602014-05-12 19:23:25 -0700134 'deadline_to_run': self.deadline,
135 'dimensions': self.dimensions,
Marc-Antoine Ruela7049872013-11-05 19:28:35 -0500136 'priority': self.priority,
maruel@chromium.org0437a732013-08-27 16:05:52 +0000137 },
138 ],
Vadim Shtayurab450c602014-05-12 19:23:25 -0700139 'data': self._files,
Vadim Shtayurab450c602014-05-12 19:23:25 -0700140 'env_vars': self.env,
Vadim Shtayurab450c602014-05-12 19:23:25 -0700141 'test_case_name': self.task_name,
Marc-Antoine Ruela7049872013-11-05 19:28:35 -0500142 'tests': self._tasks,
maruel@chromium.org0437a732013-08-27 16:05:52 +0000143 }
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500144 return json.dumps(request, sort_keys=True, separators=(',',':'))
maruel@chromium.org0437a732013-08-27 16:05:52 +0000145
Marc-Antoine Ruelcd629732013-12-20 15:00:42 -0500146
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700147class TaskOutputCollector(object):
Vadim Shtayurac8437bf2014-07-09 19:45:36 -0700148 """Assembles task execution summary (for --task-summary-json output).
149
150 Optionally fetches task outputs from isolate server to local disk (used when
151 --task-output-dir is passed).
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700152
153 This object is shared among multiple threads running 'retrieve_results'
154 function, in particular they call 'process_shard_result' method in parallel.
155 """
156
157 def __init__(self, task_output_dir, task_name, shard_count):
158 """Initializes TaskOutputCollector, ensures |task_output_dir| exists.
159
160 Args:
Vadim Shtayurac8437bf2014-07-09 19:45:36 -0700161 task_output_dir: (optional) local directory to put fetched files to.
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700162 task_name: name of the swarming task results belong to.
163 shard_count: expected number of task shards.
164 """
165 self.task_output_dir = task_output_dir
166 self.task_name = task_name
167 self.shard_count = shard_count
168
169 self._lock = threading.Lock()
170 self._per_shard_results = {}
171 self._storage = None
172
Vadim Shtayurac8437bf2014-07-09 19:45:36 -0700173 if self.task_output_dir and not os.path.isdir(self.task_output_dir):
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700174 os.makedirs(self.task_output_dir)
175
Vadim Shtayurab450c602014-05-12 19:23:25 -0700176 def process_shard_result(self, shard_index, result):
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700177 """Stores results of a single task shard, fetches output files if necessary.
178
179 Called concurrently from multiple threads.
180 """
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700181 # Sanity check index is in expected range.
Vadim Shtayurab450c602014-05-12 19:23:25 -0700182 assert isinstance(shard_index, int)
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700183 if shard_index < 0 or shard_index >= self.shard_count:
184 logging.warning(
185 'Shard index %d is outside of expected range: [0; %d]',
186 shard_index, self.shard_count - 1)
187 return
188
Kevin Graneyc2c3b9e2014-08-26 09:04:17 -0400189 result = result.copy()
190
191 isolated_files_location = extract_output_files_location(result['output'])
192 if isolated_files_location:
193 result['isolated_out'] = isolated_files_location
194
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700195 # Store result dict of that shard, ignore results we've already seen.
196 with self._lock:
197 if shard_index in self._per_shard_results:
198 logging.warning('Ignoring duplicate shard index %d', shard_index)
199 return
200 self._per_shard_results[shard_index] = result
201
202 # Fetch output files if necessary.
Vadim Shtayurac8437bf2014-07-09 19:45:36 -0700203 if self.task_output_dir:
Vadim Shtayurac8437bf2014-07-09 19:45:36 -0700204 if isolated_files_location:
Kevin Graneyc2c3b9e2014-08-26 09:04:17 -0400205 storage = self._get_storage(
206 isolated_files_location['server'],
207 isolated_files_location['namespace'])
Vadim Shtayurac8437bf2014-07-09 19:45:36 -0700208 if storage:
209 # Output files are supposed to be small and they are not reused across
210 # tasks. So use MemoryCache for them instead of on-disk cache. Make
211 # files writable, so that calling script can delete them.
212 isolateserver.fetch_isolated(
Kevin Graneyc2c3b9e2014-08-26 09:04:17 -0400213 isolated_files_location['hash'],
Vadim Shtayurac8437bf2014-07-09 19:45:36 -0700214 storage,
215 isolateserver.MemoryCache(file_mode_mask=0700),
216 os.path.join(self.task_output_dir, str(shard_index)),
217 False)
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700218
219 def finalize(self):
Vadim Shtayurac8437bf2014-07-09 19:45:36 -0700220 """Assembles and returns task summary JSON, shutdowns underlying Storage."""
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700221 with self._lock:
222 # Write an array of shard results with None for missing shards.
223 summary = {
224 'task_name': self.task_name,
225 'shards': [
226 self._per_shard_results.get(i) for i in xrange(self.shard_count)
227 ],
228 }
Vadim Shtayurac8437bf2014-07-09 19:45:36 -0700229 # Write summary.json to task_output_dir as well.
230 if self.task_output_dir:
231 tools.write_json(
232 os.path.join(self.task_output_dir, 'summary.json'),
233 summary,
234 False)
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700235 if self._storage:
236 self._storage.close()
237 self._storage = None
Vadim Shtayurac8437bf2014-07-09 19:45:36 -0700238 return summary
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700239
240 def _get_storage(self, isolate_server, namespace):
241 """Returns isolateserver.Storage to use to fetch files."""
Vadim Shtayurac8437bf2014-07-09 19:45:36 -0700242 assert self.task_output_dir
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700243 with self._lock:
244 if not self._storage:
245 self._storage = isolateserver.get_storage(isolate_server, namespace)
246 else:
247 # Shards must all use exact same isolate server and namespace.
248 if self._storage.location != isolate_server:
249 logging.error(
250 'Task shards are using multiple isolate servers: %s and %s',
251 self._storage.location, isolate_server)
252 return None
253 if self._storage.namespace != namespace:
254 logging.error(
255 'Task shards are using multiple namespaces: %s and %s',
256 self._storage.namespace, namespace)
257 return None
258 return self._storage
259
260
maruel@chromium.org0437a732013-08-27 16:05:52 +0000261def now():
262 """Exists so it can be mocked easily."""
263 return time.time()
264
265
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500266def get_task_keys(swarm_base_url, task_name):
267 """Returns the Swarming task key for each shards of task_name."""
268 key_data = urllib.urlencode([('name', task_name)])
maruel@chromium.org0437a732013-08-27 16:05:52 +0000269 url = '%s/get_matching_test_cases?%s' % (swarm_base_url, key_data)
270
vadimsh@chromium.org043b76d2013-09-12 16:15:13 +0000271 for _ in net.retry_loop(max_attempts=net.URL_OPEN_MAX_ATTEMPTS):
272 result = net.url_read(url, retry_404=True)
273 if result is None:
maruel@chromium.org0437a732013-08-27 16:05:52 +0000274 raise Failure(
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500275 'Error: Unable to find any task with the name, %s, on swarming server'
276 % task_name)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000277
maruel@chromium.org0437a732013-08-27 16:05:52 +0000278 # TODO(maruel): Compare exact string.
279 if 'No matching' in result:
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500280 logging.warning('Unable to find any task with the name, %s, on swarming '
281 'server' % task_name)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000282 continue
283 return json.loads(result)
284
285 raise Failure(
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500286 'Error: Unable to find any task with the name, %s, on swarming server'
287 % task_name)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000288
289
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700290def extract_output_files_location(task_log):
291 """Task log -> location of task output files to fetch.
292
293 TODO(vadimsh,maruel): Use side-channel to get this information.
294 See 'run_tha_test' in run_isolated.py for where the data is generated.
295
296 Returns:
297 Tuple (isolate server URL, namespace, isolated hash) on success.
298 None if information is missing or can not be parsed.
299 """
300 match = re.search(
301 r'\[run_isolated_out_hack\](.*)\[/run_isolated_out_hack\]',
302 task_log,
303 re.DOTALL)
304 if not match:
305 return None
306
307 def to_ascii(val):
308 if not isinstance(val, basestring):
309 raise ValueError()
310 return val.encode('ascii')
311
312 try:
313 data = json.loads(match.group(1))
314 if not isinstance(data, dict):
315 raise ValueError()
316 isolated_hash = to_ascii(data['hash'])
317 namespace = to_ascii(data['namespace'])
318 isolate_server = to_ascii(data['storage'])
319 if not file_path.is_url(isolate_server):
320 raise ValueError()
Kevin Graneyc2c3b9e2014-08-26 09:04:17 -0400321 data = {
322 'hash': isolated_hash,
323 'namespace': namespace,
324 'server': isolate_server,
325 'view_url': '%s/browse?%s' % (isolate_server, urllib.urlencode(
326 [('namespace', namespace), ('hash', isolated_hash)])),
327 }
328 return data
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700329 except (KeyError, ValueError):
330 logging.warning(
331 'Unexpected value of run_isolated_out_hack: %s', match.group(1))
332 return None
333
334
335def retrieve_results(
Vadim Shtayurab450c602014-05-12 19:23:25 -0700336 base_url, shard_index, task_key, timeout, should_stop, output_collector):
Vadim Shtayurab19319e2014-04-27 08:50:06 -0700337 """Retrieves results for a single task_key.
338
Vadim Shtayurab450c602014-05-12 19:23:25 -0700339 Returns:
340 <result dict> on success.
341 None on failure.
Vadim Shtayurab19319e2014-04-27 08:50:06 -0700342 """
maruel@chromium.org814d23f2013-10-01 19:08:00 +0000343 assert isinstance(timeout, float), timeout
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500344 params = [('r', task_key)]
maruel@chromium.org0437a732013-08-27 16:05:52 +0000345 result_url = '%s/get_result?%s' % (base_url, urllib.urlencode(params))
Vadim Shtayurab19319e2014-04-27 08:50:06 -0700346 started = now()
347 deadline = started + timeout if timeout else None
348 attempt = 0
349
350 while not should_stop.is_set():
351 attempt += 1
352
353 # Waiting for too long -> give up.
354 current_time = now()
355 if deadline and current_time >= deadline:
356 logging.error('retrieve_results(%s) timed out on attempt %d',
357 base_url, attempt)
358 return None
359
360 # Do not spin too fast. Spin faster at the beginning though.
361 # Start with 1 sec delay and for each 30 sec of waiting add another second
362 # of delay, until hitting 15 sec ceiling.
363 if attempt > 1:
364 max_delay = min(15, 1 + (current_time - started) / 30.0)
365 delay = min(max_delay, deadline - current_time) if deadline else max_delay
366 if delay > 0:
367 logging.debug('Waiting %.1f sec before retrying', delay)
368 should_stop.wait(delay)
369 if should_stop.is_set():
370 return None
371
Marc-Antoine Ruel200b3952014-08-14 11:07:44 -0400372 # Disable internal retries in net.url_read, since we are doing retries
373 # ourselves. Do not use retry_404 so should_stop is polled more often.
374 response = net.url_read(result_url, retry_404=False, retry_50x=False)
375
376 # Request failed. Try again.
377 if response is None:
378 continue
379
380 # Got some response, ensure it is JSON dict, retry if not.
Vadim Shtayurab19319e2014-04-27 08:50:06 -0700381 try:
Marc-Antoine Ruel200b3952014-08-14 11:07:44 -0400382 result = json.loads(response) or {}
Vadim Shtayurab19319e2014-04-27 08:50:06 -0700383 if not isinstance(result, dict):
384 raise ValueError()
385 except (ValueError, TypeError):
386 logging.warning(
387 'Received corrupted or invalid data for task_key %s, retrying: %r',
Marc-Antoine Ruel200b3952014-08-14 11:07:44 -0400388 task_key, response)
Vadim Shtayurab19319e2014-04-27 08:50:06 -0700389 continue
390
391 # Swarming server uses non-empty 'output' value as a flag that task has
392 # finished. How to wait for tasks that produce no output is a mystery.
393 if result.get('output'):
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700394 # Record the result, try to fetch attached output files (if any).
395 if output_collector:
396 # TODO(vadimsh): Respect |should_stop| and |deadline| when fetching.
Vadim Shtayurab450c602014-05-12 19:23:25 -0700397 output_collector.process_shard_result(shard_index, result)
Vadim Shtayurab19319e2014-04-27 08:50:06 -0700398 return result
maruel@chromium.org0437a732013-08-27 16:05:52 +0000399
400
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700401def yield_results(
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700402 swarm_base_url, task_keys, timeout, max_threads,
403 print_status_updates, output_collector):
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500404 """Yields swarming task results from the swarming server as (index, result).
maruel@chromium.org0437a732013-08-27 16:05:52 +0000405
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700406 Duplicate shards are ignored. Shards are yielded in order of completion.
407 Timed out shards are NOT yielded at all. Caller can compare number of yielded
408 shards with len(task_keys) to verify all shards completed.
maruel@chromium.org0437a732013-08-27 16:05:52 +0000409
410 max_threads is optional and is used to limit the number of parallel fetches
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500411 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 +0000412 worth normally to limit the number threads. Mostly used for testing purposes.
Marc-Antoine Ruel5c720342014-02-21 14:46:14 -0500413
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700414 output_collector is an optional instance of TaskOutputCollector that will be
415 used to fetch files produced by a task from isolate server to the local disk.
416
Marc-Antoine Ruel5c720342014-02-21 14:46:14 -0500417 Yields:
418 (index, result). In particular, 'result' is defined as the
419 GetRunnerResults() function in services/swarming/server/test_runner.py.
maruel@chromium.org0437a732013-08-27 16:05:52 +0000420 """
maruel@chromium.org0437a732013-08-27 16:05:52 +0000421 number_threads = (
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500422 min(max_threads, len(task_keys)) if max_threads else len(task_keys))
Vadim Shtayurab19319e2014-04-27 08:50:06 -0700423 should_stop = threading.Event()
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700424 results_channel = threading_utils.TaskChannel()
Vadim Shtayurab19319e2014-04-27 08:50:06 -0700425
maruel@chromium.org0437a732013-08-27 16:05:52 +0000426 with threading_utils.ThreadPool(number_threads, number_threads, 0) as pool:
427 try:
Vadim Shtayurab450c602014-05-12 19:23:25 -0700428 # Adds a task to the thread pool to call 'retrieve_results' and return
429 # the results together with shard_index that produced them (as a tuple).
430 def enqueue_retrieve_results(shard_index, task_key):
431 task_fn = lambda *args: (shard_index, retrieve_results(*args))
maruel@chromium.org0437a732013-08-27 16:05:52 +0000432 pool.add_task(
Vadim Shtayurab450c602014-05-12 19:23:25 -0700433 0, results_channel.wrap_task(task_fn),
434 swarm_base_url, shard_index, task_key, timeout,
435 should_stop, output_collector)
436
437 # Enqueue 'retrieve_results' calls for each shard key to run in parallel.
438 for shard_index, task_key in enumerate(task_keys):
439 enqueue_retrieve_results(shard_index, task_key)
Vadim Shtayurab19319e2014-04-27 08:50:06 -0700440
441 # Wait for all of them to finish.
442 shards_remaining = range(len(task_keys))
443 active_task_count = len(task_keys)
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700444 while active_task_count:
Vadim Shtayurab450c602014-05-12 19:23:25 -0700445 shard_index, result = None, None
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700446 try:
Vadim Shtayurab450c602014-05-12 19:23:25 -0700447 shard_index, result = results_channel.pull(
448 timeout=STATUS_UPDATE_INTERVAL)
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700449 except threading_utils.TaskChannel.Timeout:
450 if print_status_updates:
451 print(
452 'Waiting for results from the following shards: %s' %
453 ', '.join(map(str, shards_remaining)))
454 sys.stdout.flush()
455 continue
456 except Exception:
457 logging.exception('Unexpected exception in retrieve_results')
Vadim Shtayurab19319e2014-04-27 08:50:06 -0700458
459 # A call to 'retrieve_results' finished (successfully or not).
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700460 active_task_count -= 1
maruel@chromium.org0437a732013-08-27 16:05:52 +0000461 if not result:
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500462 logging.error('Failed to retrieve the results for a swarming key')
maruel@chromium.org0437a732013-08-27 16:05:52 +0000463 continue
Vadim Shtayurab19319e2014-04-27 08:50:06 -0700464
Vadim Shtayurab450c602014-05-12 19:23:25 -0700465 # Yield back results to the caller.
466 assert shard_index in shards_remaining
467 shards_remaining.remove(shard_index)
468 yield shard_index, result
Vadim Shtayurab19319e2014-04-27 08:50:06 -0700469
maruel@chromium.org0437a732013-08-27 16:05:52 +0000470 finally:
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700471 # Done or aborted with Ctrl+C, kill the remaining threads.
maruel@chromium.org0437a732013-08-27 16:05:52 +0000472 should_stop.set()
473
474
Vadim Shtayurab450c602014-05-12 19:23:25 -0700475def setup_run_isolated(manifest, bundle):
476 """Sets up the manifest to run an isolated task via run_isolated.py.
maruel@chromium.org0437a732013-08-27 16:05:52 +0000477
Vadim Shtayurab450c602014-05-12 19:23:25 -0700478 Modifies |bundle| (by adding files) and |manifest| (by adding commands) in
479 place.
480
481 Args:
482 manifest: Manifest with swarm task definition.
483 bundle: ZipPackage with files that would be transfered to swarm bot.
484 If None, only |manifest| is modified (useful in tests).
maruel@chromium.org0437a732013-08-27 16:05:52 +0000485 """
vadimsh@chromium.org6b706212013-08-28 15:03:46 +0000486 # Add uncompressed zip here. It'll be compressed as part of the package sent
487 # to Swarming server.
488 run_test_name = 'run_isolated.zip'
Vadim Shtayurab450c602014-05-12 19:23:25 -0700489 if bundle and run_test_name not in bundle.files:
490 bundle.add_buffer(
491 run_test_name,
492 run_isolated.get_as_zip_package().zip_into_buffer(compress=False))
maruel@chromium.org0437a732013-08-27 16:05:52 +0000493
vadimsh@chromium.org6b706212013-08-28 15:03:46 +0000494 cleanup_script_name = 'swarm_cleanup.py'
Vadim Shtayurab450c602014-05-12 19:23:25 -0700495 if bundle and cleanup_script_name not in bundle.files:
496 bundle.add_file(
497 os.path.join(TOOLS_PATH, cleanup_script_name), cleanup_script_name)
vadimsh@chromium.org6b706212013-08-28 15:03:46 +0000498
maruel@chromium.org0437a732013-08-27 16:05:52 +0000499 run_cmd = [
500 'python', run_test_name,
maruel@chromium.org814d23f2013-10-01 19:08:00 +0000501 '--hash', manifest.isolated_hash,
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500502 '--namespace', manifest.namespace,
maruel@chromium.org0437a732013-08-27 16:05:52 +0000503 ]
Marc-Antoine Ruel8806e622014-02-12 14:15:53 -0500504 if file_path.is_url(manifest.isolate_server):
505 run_cmd.extend(('--isolate-server', manifest.isolate_server))
506 else:
507 run_cmd.extend(('--indir', manifest.isolate_server))
508
maruel@chromium.org0437a732013-08-27 16:05:52 +0000509 if manifest.verbose or manifest.profile:
510 # Have it print the profiling section.
511 run_cmd.append('--verbose')
Vadim Shtayuraae8085b2014-05-02 17:13:10 -0700512
513 # Pass all extra args for run_isolated.py, it will pass them to the command.
514 if manifest.extra_args:
515 run_cmd.append('--')
516 run_cmd.extend(manifest.extra_args)
517
maruel@chromium.org0437a732013-08-27 16:05:52 +0000518 manifest.add_task('Run Test', run_cmd)
519
520 # Clean up
521 manifest.add_task('Clean Up', ['python', cleanup_script_name])
522
523
Vadim Shtayurab450c602014-05-12 19:23:25 -0700524def setup_googletest(env, shards, index):
Marc-Antoine Ruelcd629732013-12-20 15:00:42 -0500525 """Sets googletest specific environment variables."""
526 if shards > 1:
527 env = env.copy()
Vadim Shtayurab450c602014-05-12 19:23:25 -0700528 env['GTEST_SHARD_INDEX'] = str(index)
529 env['GTEST_TOTAL_SHARDS'] = str(shards)
Marc-Antoine Ruelcd629732013-12-20 15:00:42 -0500530 return env
531
532
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500533def archive(isolate_server, namespace, isolated, algo, verbose):
maruel@chromium.org0437a732013-08-27 16:05:52 +0000534 """Archives a .isolated and all the dependencies on the CAC."""
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500535 logging.info('archive(%s, %s, %s)', isolate_server, namespace, isolated)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000536 tempdir = None
Marc-Antoine Ruel8806e622014-02-12 14:15:53 -0500537 if file_path.is_url(isolate_server):
538 command = 'archive'
539 flag = '--isolate-server'
540 else:
541 command = 'hashtable'
542 flag = '--outdir'
543
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500544 print('Archiving: %s' % isolated)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000545 try:
maruel@chromium.org0437a732013-08-27 16:05:52 +0000546 cmd = [
547 sys.executable,
548 os.path.join(ROOT_DIR, 'isolate.py'),
Marc-Antoine Ruel8806e622014-02-12 14:15:53 -0500549 command,
550 flag, isolate_server,
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500551 '--namespace', namespace,
maruel@chromium.org0437a732013-08-27 16:05:52 +0000552 '--isolated', isolated,
553 ]
maruel@chromium.orge9403ab2013-09-20 18:03:49 +0000554 cmd.extend(['--verbose'] * verbose)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000555 logging.info(' '.join(cmd))
556 if subprocess.call(cmd, verbose):
557 return
maruel@chromium.org7b844a62013-09-17 13:04:59 +0000558 return isolateserver.hash_file(isolated, algo)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000559 finally:
560 if tempdir:
561 shutil.rmtree(tempdir)
562
563
Vadim Shtayurab450c602014-05-12 19:23:25 -0700564def get_shard_task_name(task_name, shards, index):
565 """Returns a task name to use for a single shard of a task."""
566 if shards == 1:
567 return task_name
568 return '%s:%s:%s' % (task_name, shards, index)
569
570
571def upload_zip_bundle(isolate_server, bundle):
572 """Uploads a zip package to isolate storage and returns raw fetch URL.
573
574 Args:
575 isolate_server: URL of an isolate server.
576 bundle: instance of ZipPackage to upload.
577
578 Returns:
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -0400579 URL to get the file from.
Vadim Shtayurab450c602014-05-12 19:23:25 -0700580 """
581 # Swarming bot would need to be able to grab the file from the storage
582 # using raw HTTP GET. Use 'default' namespace so that the raw data returned
583 # to a bot is not zipped, since swarm_bot doesn't understand compressed
584 # data yet. This namespace have nothing to do with |namespace| passed to
585 # run_isolated.py that is used to store files for isolated task.
586 logging.info('Zipping up and uploading files...')
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -0400587 start_time = now()
588 isolate_item = isolateserver.BufferItem(
589 bundle.zip_into_buffer(), high_priority=True)
590 with isolateserver.get_storage(isolate_server, 'default') as storage:
591 uploaded = storage.upload_items([isolate_item])
592 bundle_url = storage.get_fetch_url(isolate_item)
593 elapsed = now() - start_time
Vadim Shtayurab450c602014-05-12 19:23:25 -0700594 if isolate_item in uploaded:
595 logging.info('Upload complete, time elapsed: %f', elapsed)
596 else:
597 logging.info('Zip file already on server, time elapsed: %f', elapsed)
598 return bundle_url
599
600
601def trigger_by_manifest(swarming, manifest):
602 """Given a task manifest, triggers it for execution on swarming.
603
604 Args:
605 swarming: URL of a swarming service.
606 manifest: instance of Manifest.
607
608 Returns:
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -0400609 tuple(Task id, priority) on success. tuple(None, None) on failure.
Vadim Shtayurab450c602014-05-12 19:23:25 -0700610 """
611 logging.info('Triggering: %s', manifest.task_name)
612 manifest_text = manifest.to_json()
613 result = net.url_read(swarming + '/test', data={'request': manifest_text})
614 if not result:
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -0400615 on_error.report('Failed to trigger task %s' % manifest.task_name)
Vadim Shtayura1c024f72014-07-09 19:00:10 -0700616 return None, None
Vadim Shtayurab450c602014-05-12 19:23:25 -0700617 try:
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -0400618 data = json.loads(result)
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -0400619 except (ValueError, TypeError):
Vadim Shtayurab450c602014-05-12 19:23:25 -0700620 msg = '\n'.join((
621 'Failed to trigger task %s' % manifest.task_name,
622 'Manifest: %s' % manifest_text,
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -0400623 'Bad response: %s' % result))
624 on_error.report(msg)
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -0400625 return None, None
626 if not data:
627 return None, None
628 return data['test_keys'][0]['test_key'], data['priority']
Vadim Shtayurab450c602014-05-12 19:23:25 -0700629
630
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -0400631def abort_task(_swarming, _manifest):
Vadim Shtayurab450c602014-05-12 19:23:25 -0700632 """Given a task manifest that was triggered, aborts its execution."""
633 # TODO(vadimsh): No supported by the server yet.
634
635
636def trigger_task_shards(
Vadim Shtayuraae8085b2014-05-02 17:13:10 -0700637 swarming, isolate_server, namespace, isolated_hash, task_name, extra_args,
Marc-Antoine Ruelaea50652014-06-12 14:23:48 -0400638 shards, dimensions, env, deadline, verbose, profile, priority):
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -0400639 """Triggers multiple subtasks of a sharded task.
640
641 Returns:
Vadim Shtayuraf27448e2014-06-26 11:35:05 -0700642 Dict with task details, returned to caller as part of --dump-json output.
643 None in case of failure.
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -0400644 """
Vadim Shtayurab450c602014-05-12 19:23:25 -0700645 # Collects all files that are necessary to bootstrap a task execution
646 # on the bot. Usually it includes self contained run_isolated.zip and
647 # a bunch of small other scripts. All heavy files are pulled
648 # by run_isolated.zip. Updated in 'setup_run_isolated'.
649 bundle = zip_package.ZipPackage(ROOT_DIR)
650
651 # Make a separate Manifest for each shard, put shard index and number of
652 # shards into env and subtask name.
653 manifests = []
654 for index in xrange(shards):
maruel@chromium.org0437a732013-08-27 16:05:52 +0000655 manifest = Manifest(
Marc-Antoine Ruela7049872013-11-05 19:28:35 -0500656 isolate_server=isolate_server,
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500657 namespace=namespace,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500658 isolated_hash=isolated_hash,
Vadim Shtayurab450c602014-05-12 19:23:25 -0700659 task_name=get_shard_task_name(task_name, shards, index),
Vadim Shtayuraae8085b2014-05-02 17:13:10 -0700660 extra_args=extra_args,
Marc-Antoine Ruel92f32422013-11-06 18:12:13 -0500661 dimensions=dimensions,
Vadim Shtayurab450c602014-05-12 19:23:25 -0700662 env=setup_googletest(env, shards, index),
Marc-Antoine Ruel13b7b782014-03-14 11:14:57 -0400663 deadline=deadline,
Marc-Antoine Ruela7049872013-11-05 19:28:35 -0500664 verbose=verbose,
665 profile=profile,
Vadim Shtayurabcff74f2014-02-27 16:19:34 -0800666 priority=priority)
Vadim Shtayurab450c602014-05-12 19:23:25 -0700667 setup_run_isolated(manifest, bundle)
668 manifests.append(manifest)
669
670 # Upload zip bundle file to get its URL.
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -0400671 try:
672 bundle_url = upload_zip_bundle(isolate_server, bundle)
673 except (IOError, OSError):
674 on_error.report('Failed to upload the zip file for task %s' % task_name)
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -0400675 return None, None
maruel@chromium.org0437a732013-08-27 16:05:52 +0000676
Vadim Shtayurab450c602014-05-12 19:23:25 -0700677 # Attach that file to all manifests.
678 for manifest in manifests:
679 manifest.add_bundled_file('swarm_data.zip', bundle_url)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000680
Vadim Shtayurab450c602014-05-12 19:23:25 -0700681 # Trigger all the subtasks.
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -0400682 tasks = {}
683 priority_warning = False
Vadim Shtayuraf27448e2014-06-26 11:35:05 -0700684 for index, manifest in enumerate(manifests):
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -0400685 task_id, priority = trigger_by_manifest(swarming, manifest)
686 if not task_id:
Vadim Shtayurab450c602014-05-12 19:23:25 -0700687 break
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -0400688 if not priority_warning and priority != manifest.priority:
689 priority_warning = True
690 print >> sys.stderr, 'Priority was reset to %s' % priority
Vadim Shtayuraf27448e2014-06-26 11:35:05 -0700691 tasks[manifest.task_name] = {
692 'shard_index': index,
693 'task_id': task_id,
694 'view_url': '%s/user/task/%s' % (swarming, task_id),
695 }
Vadim Shtayurab450c602014-05-12 19:23:25 -0700696
697 # Some shards weren't triggered. Abort everything.
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -0400698 if len(tasks) != len(manifests):
699 if tasks:
Vadim Shtayurab450c602014-05-12 19:23:25 -0700700 print >> sys.stderr, 'Not all shards were triggered'
Vadim Shtayuraf27448e2014-06-26 11:35:05 -0700701 for task_dict in tasks.itervalues():
702 abort_task(swarming, task_dict['task_id'])
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -0400703 return None
maruel@chromium.org0437a732013-08-27 16:05:52 +0000704
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -0400705 return tasks
maruel@chromium.org0437a732013-08-27 16:05:52 +0000706
707
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500708def isolated_to_hash(isolate_server, namespace, arg, algo, verbose):
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500709 """Archives a .isolated file if needed.
710
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500711 Returns the file hash to trigger and a bool specifying if it was a file (True)
712 or a hash (False).
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500713 """
714 if arg.endswith('.isolated'):
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500715 file_hash = archive(isolate_server, namespace, arg, algo, verbose)
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500716 if not file_hash:
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -0400717 on_error.report('Archival failure %s' % arg)
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500718 return None, True
719 return file_hash, True
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500720 elif isolateserver.is_valid_hash(arg, algo):
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500721 return arg, False
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500722 else:
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -0400723 on_error.report('Invalid hash %s' % arg)
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500724 return None, False
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500725
726
maruel@chromium.org0437a732013-08-27 16:05:52 +0000727def trigger(
Marc-Antoine Ruela7049872013-11-05 19:28:35 -0500728 swarming,
729 isolate_server,
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500730 namespace,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500731 file_hash_or_isolated,
732 task_name,
Vadim Shtayuraae8085b2014-05-02 17:13:10 -0700733 extra_args,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500734 shards,
Marc-Antoine Ruel92f32422013-11-06 18:12:13 -0500735 dimensions,
736 env,
Marc-Antoine Ruel13b7b782014-03-14 11:14:57 -0400737 deadline,
maruel@chromium.org0437a732013-08-27 16:05:52 +0000738 verbose,
739 profile,
740 priority):
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -0400741 """Sends off the hash swarming task requests.
742
743 Returns:
744 tuple(dict(task_name: task_id), base task name). The dict of tasks is None
745 in case of failure.
746 """
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500747 file_hash, is_file = isolated_to_hash(
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500748 isolate_server, namespace, file_hash_or_isolated, hashlib.sha1, verbose)
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500749 if not file_hash:
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500750 return 1, ''
751 if not task_name:
752 # If a file name was passed, use its base name of the isolated hash.
753 # Otherwise, use user name as an approximation of a task name.
754 if is_file:
755 key = os.path.splitext(os.path.basename(file_hash_or_isolated))[0]
756 else:
757 key = getpass.getuser()
Vadim Shtayurac3d97b02014-04-26 19:16:05 -0700758 task_name = '%s/%s/%s/%d' % (
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500759 key,
760 '_'.join('%s=%s' % (k, v) for k, v in sorted(dimensions.iteritems())),
Vadim Shtayurac3d97b02014-04-26 19:16:05 -0700761 file_hash,
762 now() * 1000)
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500763
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -0400764 tasks = trigger_task_shards(
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500765 swarming=swarming,
766 isolate_server=isolate_server,
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500767 namespace=namespace,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500768 isolated_hash=file_hash,
769 task_name=task_name,
Vadim Shtayuraae8085b2014-05-02 17:13:10 -0700770 extra_args=extra_args,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500771 shards=shards,
772 dimensions=dimensions,
Marc-Antoine Ruel13b7b782014-03-14 11:14:57 -0400773 deadline=deadline,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500774 env=env,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500775 verbose=verbose,
776 profile=profile,
Vadim Shtayurabcff74f2014-02-27 16:19:34 -0800777 priority=priority)
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -0400778 return tasks, task_name
maruel@chromium.org0437a732013-08-27 16:05:52 +0000779
780
Vadim Shtayurab450c602014-05-12 19:23:25 -0700781def decorate_shard_output(shard_index, result, shard_exit_code):
maruel@chromium.org0437a732013-08-27 16:05:52 +0000782 """Returns wrapped output for swarming task shard."""
783 tag = 'index %s (machine tag: %s, id: %s)' % (
Vadim Shtayurab450c602014-05-12 19:23:25 -0700784 shard_index,
maruel@chromium.org0437a732013-08-27 16:05:52 +0000785 result['machine_id'],
786 result.get('machine_tag', 'unknown'))
787 return (
788 '\n'
789 '================================================================\n'
790 'Begin output from shard %s\n'
791 '================================================================\n'
792 '\n'
793 '%s'
794 '================================================================\n'
Vadim Shtayura473455a2014-05-14 15:22:35 -0700795 'End output from shard %s.\nExit code %d (%s).\n'
796 '================================================================\n') % (
797 tag, result['output'] or NO_OUTPUT_FOUND, tag,
798 shard_exit_code, hex(0xffffffff & shard_exit_code))
maruel@chromium.org0437a732013-08-27 16:05:52 +0000799
800
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700801def collect(
Vadim Shtayurab450c602014-05-12 19:23:25 -0700802 url, task_name, shards, timeout, decorate,
Vadim Shtayurac8437bf2014-07-09 19:45:36 -0700803 print_status_updates, task_summary_json, task_output_dir):
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500804 """Retrieves results of a Swarming task."""
Vadim Shtayurab450c602014-05-12 19:23:25 -0700805 # Grab task keys for each shard. Order is important, used to figure out
806 # shard index based on the key.
807 # TODO(vadimsh): Simplify this once server support is added.
808 task_keys = []
809 for index in xrange(shards):
810 shard_task_name = get_shard_task_name(task_name, shards, index)
811 logging.info('Collecting %s', shard_task_name)
812 shard_task_keys = get_task_keys(url, shard_task_name)
813 if not shard_task_keys:
814 raise Failure('No task keys to get results with: %s' % shard_task_name)
815 if len(shard_task_keys) != 1:
816 raise Failure('Expecting only one shard for a task: %s' % shard_task_name)
817 task_keys.append(shard_task_keys[0])
maruel@chromium.org0437a732013-08-27 16:05:52 +0000818
Vadim Shtayurac8437bf2014-07-09 19:45:36 -0700819 # Collect summary JSON and output files (if task_output_dir is not None).
820 output_collector = TaskOutputCollector(
821 task_output_dir, task_name, len(task_keys))
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700822
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700823 seen_shards = set()
Vadim Shtayurac524f512014-05-15 09:54:56 -0700824 exit_codes = []
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700825
826 try:
827 for index, output in yield_results(
828 url, task_keys, timeout, None, print_status_updates, output_collector):
829 seen_shards.add(index)
Vadim Shtayura473455a2014-05-14 15:22:35 -0700830
831 # Grab first non-zero exit code as an overall shard exit code.
832 shard_exit_code = 0
833 for code in map(int, (output['exit_codes'] or '1').split(',')):
834 if code:
835 shard_exit_code = code
836 break
Vadim Shtayurac524f512014-05-15 09:54:56 -0700837 exit_codes.append(shard_exit_code)
Vadim Shtayura473455a2014-05-14 15:22:35 -0700838
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700839 if decorate:
Vadim Shtayurab450c602014-05-12 19:23:25 -0700840 print decorate_shard_output(index, output, shard_exit_code)
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700841 else:
842 print(
843 '%s/%s: %s' % (
844 output['machine_id'],
845 output['machine_tag'],
846 output['exit_codes']))
847 print(''.join(' %s\n' % l for l in output['output'].splitlines()))
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700848 finally:
Vadim Shtayurac8437bf2014-07-09 19:45:36 -0700849 summary = output_collector.finalize()
850 if task_summary_json:
851 tools.write_json(task_summary_json, summary, False)
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700852
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700853 if len(seen_shards) != len(task_keys):
854 missing_shards = [x for x in range(len(task_keys)) if x not in seen_shards]
855 print >> sys.stderr, ('Results from some shards are missing: %s' %
856 ', '.join(map(str, missing_shards)))
Vadim Shtayurac524f512014-05-15 09:54:56 -0700857 return 1
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700858
Vadim Shtayurac524f512014-05-15 09:54:56 -0700859 return int(bool(any(exit_codes)))
maruel@chromium.org0437a732013-08-27 16:05:52 +0000860
861
Marc-Antoine Ruel819fb162014-03-12 16:38:26 -0400862def add_filter_options(parser):
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500863 parser.filter_group = tools.optparse.OptionGroup(parser, 'Filtering slaves')
864 parser.filter_group.add_option(
Marc-Antoine Ruelb39e8cf2014-01-20 10:39:31 -0500865 '-d', '--dimension', default=[], action='append', nargs=2,
Marc-Antoine Ruel92f32422013-11-06 18:12:13 -0500866 dest='dimensions', metavar='FOO bar',
867 help='dimension to filter on')
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500868 parser.add_option_group(parser.filter_group)
869
Marc-Antoine Ruel819fb162014-03-12 16:38:26 -0400870
Marc-Antoine Ruel025e7822014-05-01 11:50:24 -0400871def process_filter_options(parser, options):
872 options.dimensions = dict(options.dimensions)
873 if not options.dimensions:
874 parser.error('Please at least specify one --dimension')
875
876
Vadim Shtayurab450c602014-05-12 19:23:25 -0700877def add_sharding_options(parser):
878 parser.sharding_group = tools.optparse.OptionGroup(parser, 'Sharding options')
879 parser.sharding_group.add_option(
880 '--shards', type='int', default=1,
881 help='Number of shards to trigger and collect.')
882 parser.add_option_group(parser.sharding_group)
883
884
Marc-Antoine Ruel819fb162014-03-12 16:38:26 -0400885def add_trigger_options(parser):
886 """Adds all options to trigger a task on Swarming."""
887 isolateserver.add_isolate_server_options(parser, True)
888 add_filter_options(parser)
889
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500890 parser.task_group = tools.optparse.OptionGroup(parser, 'Task properties')
891 parser.task_group.add_option(
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500892 '-e', '--env', default=[], action='append', nargs=2, metavar='FOO bar',
Vadim Shtayurab450c602014-05-12 19:23:25 -0700893 help='Environment variables to set')
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500894 parser.task_group.add_option(
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500895 '--priority', type='int', default=100,
896 help='The lower value, the more important the task is')
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500897 parser.task_group.add_option(
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500898 '-T', '--task-name',
899 help='Display name of the task. It uniquely identifies the task. '
Vadim Shtayurac3d97b02014-04-26 19:16:05 -0700900 'Defaults to <base_name>/<dimensions>/<isolated hash>/<timestamp> '
901 'if an isolated file is provided, if a hash is provided, it '
902 'defaults to <user>/<dimensions>/<isolated hash>/<timestamp>')
Marc-Antoine Ruel13b7b782014-03-14 11:14:57 -0400903 parser.task_group.add_option(
904 '--deadline', type='int', default=6*60*60,
905 help='Seconds to allow the task to be pending for a bot to run before '
906 'this task request expires.')
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500907 parser.add_option_group(parser.task_group)
Marc-Antoine Ruelcd629732013-12-20 15:00:42 -0500908 # TODO(maruel): This is currently written in a chromium-specific way.
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500909 parser.group_logging.add_option(
maruel@chromium.org0437a732013-08-27 16:05:52 +0000910 '--profile', action='store_true',
911 default=bool(os.environ.get('ISOLATE_DEBUG')),
912 help='Have run_isolated.py print profiling info')
maruel@chromium.org0437a732013-08-27 16:05:52 +0000913
914
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500915def process_trigger_options(parser, options, args):
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500916 isolateserver.process_isolate_server_options(parser, options)
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500917 if len(args) != 1:
918 parser.error('Must pass one .isolated file or its hash (sha1).')
Marc-Antoine Ruel025e7822014-05-01 11:50:24 -0400919 process_filter_options(parser, options)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000920
921
922def add_collect_options(parser):
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500923 parser.server_group.add_option(
maruel@chromium.org0437a732013-08-27 16:05:52 +0000924 '-t', '--timeout',
925 type='float',
926 default=DEFAULT_SHARD_WAIT_TIME,
927 help='Timeout to wait for result, set to 0 for no timeout; default: '
928 '%default s')
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500929 parser.group_logging.add_option(
930 '--decorate', action='store_true', help='Decorate output')
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700931 parser.group_logging.add_option(
932 '--print-status-updates', action='store_true',
933 help='Print periodic status updates')
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700934 parser.task_output_group = tools.optparse.OptionGroup(parser, 'Task output')
935 parser.task_output_group.add_option(
Vadim Shtayurac8437bf2014-07-09 19:45:36 -0700936 '--task-summary-json',
937 metavar='FILE',
938 help='Dump a summary of task results to this file as json. It contains '
939 'only shards statuses as know to server directly. Any output files '
940 'emitted by the task can be collected by using --task-output-dir')
941 parser.task_output_group.add_option(
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700942 '--task-output-dir',
Vadim Shtayurac8437bf2014-07-09 19:45:36 -0700943 metavar='DIR',
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700944 help='Directory to put task results into. When the task finishes, this '
Vadim Shtayurac8437bf2014-07-09 19:45:36 -0700945 'directory contains per-shard directory with output files produced '
946 'by shards: <task-output-dir>/<zero-based-shard-index>/.')
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700947 parser.add_option_group(parser.task_output_group)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000948
949
Vadim Shtayuraae8085b2014-05-02 17:13:10 -0700950def extract_isolated_command_extra_args(args):
951 try:
952 index = args.index('--')
953 except ValueError:
954 return (args, [])
955 return (args[:index], args[index+1:])
956
957
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500958@subcommand.usage('task_name')
maruel@chromium.org0437a732013-08-27 16:05:52 +0000959def CMDcollect(parser, args):
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500960 """Retrieves results of a Swarming task.
maruel@chromium.org0437a732013-08-27 16:05:52 +0000961
962 The result can be in multiple part if the execution was sharded. It can
963 potentially have retries.
964 """
965 add_collect_options(parser)
Vadim Shtayurab450c602014-05-12 19:23:25 -0700966 add_sharding_options(parser)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000967 (options, args) = parser.parse_args(args)
968 if not args:
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500969 parser.error('Must specify one task name.')
maruel@chromium.org0437a732013-08-27 16:05:52 +0000970 elif len(args) > 1:
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500971 parser.error('Must specify only one task name.')
maruel@chromium.org0437a732013-08-27 16:05:52 +0000972
Vadim Shtayura6b555c12014-07-23 16:22:18 -0700973 auth.ensure_logged_in(options.swarming)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000974 try:
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700975 return collect(
976 options.swarming,
977 args[0],
Vadim Shtayurab450c602014-05-12 19:23:25 -0700978 options.shards,
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700979 options.timeout,
980 options.decorate,
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700981 options.print_status_updates,
Vadim Shtayurac8437bf2014-07-09 19:45:36 -0700982 options.task_summary_json,
Vadim Shtayurae3fbd102014-04-29 17:05:21 -0700983 options.task_output_dir)
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -0400984 except Failure:
985 on_error.report(None)
vadimsh@chromium.orgd908a542013-10-30 01:36:17 +0000986 return 1
maruel@chromium.org0437a732013-08-27 16:05:52 +0000987
988
Marc-Antoine Ruel819fb162014-03-12 16:38:26 -0400989def CMDquery(parser, args):
990 """Returns information about the bots connected to the Swarming server."""
991 add_filter_options(parser)
992 parser.filter_group.add_option(
Marc-Antoine Ruel28083112014-03-13 16:34:04 -0400993 '--dead-only', action='store_true',
994 help='Only print dead bots, useful to reap them and reimage broken bots')
995 parser.filter_group.add_option(
Marc-Antoine Ruel819fb162014-03-12 16:38:26 -0400996 '-k', '--keep-dead', action='store_true',
997 help='Do not filter out dead bots')
998 parser.filter_group.add_option(
999 '-b', '--bare', action='store_true',
Marc-Antoine Ruele7b00162014-03-12 16:59:01 -04001000 help='Do not print out dimensions')
Marc-Antoine Ruel819fb162014-03-12 16:38:26 -04001001 options, args = parser.parse_args(args)
Marc-Antoine Ruel28083112014-03-13 16:34:04 -04001002
1003 if options.keep_dead and options.dead_only:
1004 parser.error('Use only one of --keep-dead and --dead-only')
Vadim Shtayura6b555c12014-07-23 16:22:18 -07001005
1006 auth.ensure_logged_in(options.swarming)
Marc-Antoine Ruel0a620612014-08-13 15:47:07 -04001007 data = net.url_read_json(options.swarming + '/swarming/api/v1/bots')
Marc-Antoine Ruel819fb162014-03-12 16:38:26 -04001008 if data is None:
1009 print >> sys.stderr, 'Failed to access %s' % options.swarming
1010 return 1
Marc-Antoine Ruele4bebbc2014-06-04 09:36:14 -04001011 for machine in natsort.natsorted(data['machines'], key=lambda x: x['id']):
Marc-Antoine Ruel28083112014-03-13 16:34:04 -04001012 if options.dead_only:
Marc-Antoine Ruelfe844672014-07-31 11:16:51 -04001013 if not machine['is_dead']:
Marc-Antoine Ruel28083112014-03-13 16:34:04 -04001014 continue
Marc-Antoine Ruelfe844672014-07-31 11:16:51 -04001015 elif not options.keep_dead and machine['is_dead']:
Marc-Antoine Ruel819fb162014-03-12 16:38:26 -04001016 continue
1017
Marc-Antoine Ruele7b00162014-03-12 16:59:01 -04001018 # If the user requested to filter on dimensions, ensure the bot has all the
1019 # dimensions requested.
Marc-Antoine Ruel819fb162014-03-12 16:38:26 -04001020 dimensions = machine['dimensions']
1021 for key, value in options.dimensions:
1022 if key not in dimensions:
1023 break
Marc-Antoine Ruele7b00162014-03-12 16:59:01 -04001024 # A bot can have multiple value for a key, for example,
1025 # {'os': ['Windows', 'Windows-6.1']}, so that --dimension os=Windows will
1026 # be accepted.
Marc-Antoine Ruel819fb162014-03-12 16:38:26 -04001027 if isinstance(dimensions[key], list):
1028 if value not in dimensions[key]:
1029 break
1030 else:
1031 if value != dimensions[key]:
1032 break
1033 else:
Marc-Antoine Ruele4bebbc2014-06-04 09:36:14 -04001034 print machine['id']
Marc-Antoine Ruele7b00162014-03-12 16:59:01 -04001035 if not options.bare:
Marc-Antoine Ruel0a620612014-08-13 15:47:07 -04001036 print ' %s' % json.dumps(dimensions, sort_keys=True)
Marc-Antoine Ruel819fb162014-03-12 16:38:26 -04001037 return 0
1038
1039
Vadim Shtayuraae8085b2014-05-02 17:13:10 -07001040@subcommand.usage('(hash|isolated) [-- extra_args]')
maruel@chromium.org0437a732013-08-27 16:05:52 +00001041def CMDrun(parser, args):
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001042 """Triggers a task and wait for the results.
maruel@chromium.org0437a732013-08-27 16:05:52 +00001043
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001044 Basically, does everything to run a command remotely.
maruel@chromium.org0437a732013-08-27 16:05:52 +00001045 """
1046 add_trigger_options(parser)
1047 add_collect_options(parser)
Vadim Shtayurab450c602014-05-12 19:23:25 -07001048 add_sharding_options(parser)
Vadim Shtayuraae8085b2014-05-02 17:13:10 -07001049 args, isolated_cmd_args = extract_isolated_command_extra_args(args)
maruel@chromium.org0437a732013-08-27 16:05:52 +00001050 options, args = parser.parse_args(args)
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001051 process_trigger_options(parser, options, args)
maruel@chromium.org0437a732013-08-27 16:05:52 +00001052
Vadim Shtayura6b555c12014-07-23 16:22:18 -07001053 auth.ensure_logged_in(options.swarming)
1054 if file_path.is_url(options.isolate_server):
1055 auth.ensure_logged_in(options.isolate_server)
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001056 try:
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -04001057 tasks, task_name = trigger(
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001058 swarming=options.swarming,
Marc-Antoine Ruel8806e622014-02-12 14:15:53 -05001059 isolate_server=options.isolate_server or options.indir,
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -05001060 namespace=options.namespace,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001061 file_hash_or_isolated=args[0],
1062 task_name=options.task_name,
Vadim Shtayuraae8085b2014-05-02 17:13:10 -07001063 extra_args=isolated_cmd_args,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001064 shards=options.shards,
1065 dimensions=options.dimensions,
1066 env=dict(options.env),
Marc-Antoine Ruel13b7b782014-03-14 11:14:57 -04001067 deadline=options.deadline,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001068 verbose=options.verbose,
1069 profile=options.profile,
1070 priority=options.priority)
1071 except Failure as e:
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -04001072 on_error.report(
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001073 'Failed to trigger %s(%s): %s' %
1074 (options.task_name, args[0], e.args[0]))
1075 return 1
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -04001076 if not tasks:
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -04001077 on_error.report('Failed to trigger the task.')
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -04001078 return 1
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -05001079 if task_name != options.task_name:
1080 print('Triggered task: %s' % task_name)
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001081 try:
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -04001082 # TODO(maruel): Use task_ids, it's much more efficient!
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001083 return collect(
1084 options.swarming,
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -05001085 task_name,
Vadim Shtayurab450c602014-05-12 19:23:25 -07001086 options.shards,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001087 options.timeout,
Vadim Shtayura86a2cef2014-04-18 11:13:39 -07001088 options.decorate,
Vadim Shtayurae3fbd102014-04-29 17:05:21 -07001089 options.print_status_updates,
Vadim Shtayurac8437bf2014-07-09 19:45:36 -07001090 options.task_summary_json,
Vadim Shtayurae3fbd102014-04-29 17:05:21 -07001091 options.task_output_dir)
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -04001092 except Failure:
1093 on_error.report(None)
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001094 return 1
maruel@chromium.org0437a732013-08-27 16:05:52 +00001095
1096
Vadim Shtayuraae8085b2014-05-02 17:13:10 -07001097@subcommand.usage("(hash|isolated) [-- extra_args]")
maruel@chromium.org0437a732013-08-27 16:05:52 +00001098def CMDtrigger(parser, args):
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001099 """Triggers a Swarming task.
maruel@chromium.org0437a732013-08-27 16:05:52 +00001100
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001101 Accepts either the hash (sha1) of a .isolated file already uploaded or the
1102 path to an .isolated file to archive, packages it if needed and sends a
1103 Swarming manifest file to the Swarming server.
1104
1105 If an .isolated file is specified instead of an hash, it is first archived.
Vadim Shtayuraae8085b2014-05-02 17:13:10 -07001106
1107 Passes all extra arguments provided after '--' as additional command line
1108 arguments for an isolated command specified in *.isolate file.
maruel@chromium.org0437a732013-08-27 16:05:52 +00001109 """
1110 add_trigger_options(parser)
Vadim Shtayurab450c602014-05-12 19:23:25 -07001111 add_sharding_options(parser)
Vadim Shtayuraae8085b2014-05-02 17:13:10 -07001112 args, isolated_cmd_args = extract_isolated_command_extra_args(args)
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -04001113 parser.add_option(
1114 '--dump-json',
1115 metavar='FILE',
1116 help='Dump details about the triggered task(s) to this file as json')
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001117 options, args = parser.parse_args(args)
1118 process_trigger_options(parser, options, args)
maruel@chromium.org0437a732013-08-27 16:05:52 +00001119
Vadim Shtayura6b555c12014-07-23 16:22:18 -07001120 auth.ensure_logged_in(options.swarming)
1121 if file_path.is_url(options.isolate_server):
1122 auth.ensure_logged_in(options.isolate_server)
maruel@chromium.org0437a732013-08-27 16:05:52 +00001123 try:
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -04001124 tasks, task_name = trigger(
Marc-Antoine Ruela7049872013-11-05 19:28:35 -05001125 swarming=options.swarming,
Marc-Antoine Ruel8806e622014-02-12 14:15:53 -05001126 isolate_server=options.isolate_server or options.indir,
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -05001127 namespace=options.namespace,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001128 file_hash_or_isolated=args[0],
1129 task_name=options.task_name,
Vadim Shtayuraae8085b2014-05-02 17:13:10 -07001130 extra_args=isolated_cmd_args,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05001131 shards=options.shards,
Vadim Shtayuraae8085b2014-05-02 17:13:10 -07001132 dimensions=options.dimensions,
Marc-Antoine Ruel92f32422013-11-06 18:12:13 -05001133 env=dict(options.env),
Marc-Antoine Ruel13b7b782014-03-14 11:14:57 -04001134 deadline=options.deadline,
Marc-Antoine Ruela7049872013-11-05 19:28:35 -05001135 verbose=options.verbose,
1136 profile=options.profile,
1137 priority=options.priority)
Marc-Antoine Rueld6dbe762014-06-18 13:49:42 -04001138 if tasks:
1139 if task_name != options.task_name:
1140 print('Triggered task: %s' % task_name)
1141 if options.dump_json:
1142 data = {
1143 'base_task_name': task_name,
1144 'tasks': tasks,
1145 }
1146 tools.write_json(options.dump_json, data, True)
1147 return int(not tasks)
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -04001148 except Failure:
1149 on_error.report(None)
vadimsh@chromium.orgd908a542013-10-30 01:36:17 +00001150 return 1
maruel@chromium.org0437a732013-08-27 16:05:52 +00001151
1152
1153class OptionParserSwarming(tools.OptionParserWithLogging):
1154 def __init__(self, **kwargs):
1155 tools.OptionParserWithLogging.__init__(
1156 self, prog='swarming.py', **kwargs)
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -05001157 self.server_group = tools.optparse.OptionGroup(self, 'Server')
1158 self.server_group.add_option(
maruel@chromium.orge9403ab2013-09-20 18:03:49 +00001159 '-S', '--swarming',
Kevin Graney5346c162014-01-24 12:20:01 -05001160 metavar='URL', default=os.environ.get('SWARMING_SERVER', ''),
maruel@chromium.orge9403ab2013-09-20 18:03:49 +00001161 help='Swarming server to use')
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -05001162 self.add_option_group(self.server_group)
Vadim Shtayurae34e13a2014-02-02 11:23:26 -08001163 auth.add_auth_options(self)
maruel@chromium.org0437a732013-08-27 16:05:52 +00001164
1165 def parse_args(self, *args, **kwargs):
1166 options, args = tools.OptionParserWithLogging.parse_args(
1167 self, *args, **kwargs)
1168 options.swarming = options.swarming.rstrip('/')
1169 if not options.swarming:
1170 self.error('--swarming is required.')
Vadim Shtayura5d1efce2014-02-04 10:55:43 -08001171 auth.process_auth_options(self, options)
maruel@chromium.org0437a732013-08-27 16:05:52 +00001172 return options, args
1173
1174
1175def main(args):
1176 dispatcher = subcommand.CommandDispatcher(__name__)
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -04001177 return dispatcher.execute(OptionParserSwarming(version=__version__), args)
maruel@chromium.org0437a732013-08-27 16:05:52 +00001178
1179
1180if __name__ == '__main__':
1181 fix_encoding.fix_encoding()
1182 tools.disable_buffering()
1183 colorama.init()
1184 sys.exit(main(sys.argv[1:]))