blob: 0b68e6c47416d320022b627a460ec53120529a94 [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
Vadim Shtayura86a2cef2014-04-18 11:13:39 -07008__version__ = '0.4.5'
maruel@chromium.org0437a732013-08-27 16:05:52 +00009
Marc-Antoine Ruel819fb162014-03-12 16:38:26 -040010import datetime
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -050011import getpass
maruel@chromium.org0437a732013-08-27 16:05:52 +000012import hashlib
13import json
14import logging
15import os
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
maruel@chromium.org0437a732013-08-27 16:05:52 +000030from utils import threading_utils
vadimsh@chromium.org6b706212013-08-28 15:03:46 +000031from utils import tools
32from utils import zip_package
maruel@chromium.org0437a732013-08-27 16:05:52 +000033
Vadim Shtayurae34e13a2014-02-02 11:23:26 -080034import auth
maruel@chromium.org7b844a62013-09-17 13:04:59 +000035import isolateserver
maruel@chromium.org0437a732013-08-27 16:05:52 +000036import run_isolated
37
38
39ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
40TOOLS_PATH = os.path.join(ROOT_DIR, 'tools')
41
42
maruel@chromium.org0437a732013-08-27 16:05:52 +000043# The default time to wait for a shard to finish running.
csharp@chromium.org24758492013-08-28 19:10:54 +000044DEFAULT_SHARD_WAIT_TIME = 80 * 60.
maruel@chromium.org0437a732013-08-27 16:05:52 +000045
Vadim Shtayura86a2cef2014-04-18 11:13:39 -070046# How often to print status updates to stdout in 'collect'.
47STATUS_UPDATE_INTERVAL = 15 * 60.
48
maruel@chromium.org0437a732013-08-27 16:05:52 +000049
50NO_OUTPUT_FOUND = (
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -050051 'No output produced by the task, it may have failed to run.\n'
maruel@chromium.org0437a732013-08-27 16:05:52 +000052 '\n')
53
54
maruel@chromium.org0437a732013-08-27 16:05:52 +000055class Failure(Exception):
56 """Generic failure."""
57 pass
58
59
60class Manifest(object):
61 """Represents a Swarming task manifest.
62
63 Also includes code to zip code and upload itself.
64 """
65 def __init__(
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -050066 self, isolate_server, namespace, isolated_hash, task_name, shards, env,
Marc-Antoine Ruel13b7b782014-03-14 11:14:57 -040067 dimensions, working_dir, deadline, verbose, profile, 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.
maruel@chromium.org814d23f2013-10-01 19:08:00 +000072 isolated_hash - The manifest's sha-1 that the slave is going to fetch.
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -050073 task_name - The name to give the task request.
74 shards - The number of swarming shards to request.
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.
maruel@chromium.org0437a732013-08-27 16:05:52 +000077 working_dir - Relative working directory to start the script.
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
85 # The reason is that swarm_bot doesn't understand compressed data yet. So
86 # the data to be downloaded by swarm_bot is in 'default', independent of
87 # what run_isolated.py is going to fetch.
Marc-Antoine Ruela7049872013-11-05 19:28:35 -050088 self.storage = isolateserver.get_storage(isolate_server, 'default')
89
maruel@chromium.org814d23f2013-10-01 19:08:00 +000090 self.isolated_hash = isolated_hash
vadimsh@chromium.org6b706212013-08-28 15:03:46 +000091 self.bundle = zip_package.ZipPackage(ROOT_DIR)
92
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -050093 self._task_name = task_name
maruel@chromium.org0437a732013-08-27 16:05:52 +000094 self._shards = shards
Marc-Antoine Ruel92f32422013-11-06 18:12:13 -050095 self._env = env.copy()
96 self._dimensions = dimensions.copy()
maruel@chromium.org0437a732013-08-27 16:05:52 +000097 self._working_dir = working_dir
Marc-Antoine Ruel13b7b782014-03-14 11:14:57 -040098 self._deadline = deadline
maruel@chromium.org0437a732013-08-27 16:05:52 +000099
maruel@chromium.org0437a732013-08-27 16:05:52 +0000100 self.verbose = bool(verbose)
101 self.profile = bool(profile)
102 self.priority = priority
103
vadimsh@chromium.orgf24e5c32013-10-11 21:16:21 +0000104 self._isolate_item = None
maruel@chromium.org0437a732013-08-27 16:05:52 +0000105 self._tasks = []
maruel@chromium.org0437a732013-08-27 16:05:52 +0000106
Marc-Antoine Ruelaf78a902014-03-20 10:42:49 -0400107 def add_task(self, task_name, actions, time_out=2*60*60):
Marc-Antoine Ruel5c720342014-02-21 14:46:14 -0500108 """Appends a new task as a TestObject to the swarming manifest file.
Marc-Antoine Ruelcd629732013-12-20 15:00:42 -0500109
110 Tasks cannot be added once the manifest was uploaded.
Marc-Antoine Ruel5c720342014-02-21 14:46:14 -0500111
Marc-Antoine Ruelaf78a902014-03-20 10:42:49 -0400112 By default, command will be killed after 2 hours of execution.
113
Marc-Antoine Ruel5c720342014-02-21 14:46:14 -0500114 See TestObject in services/swarming/src/common/test_request_message.py for
115 the valid format.
Marc-Antoine Ruelcd629732013-12-20 15:00:42 -0500116 """
117 assert not self._isolate_item
maruel@chromium.org0437a732013-08-27 16:05:52 +0000118 self._tasks.append(
119 {
120 'action': actions,
121 'decorate_output': self.verbose,
122 'test_name': task_name,
Marc-Antoine Ruelaf78a902014-03-20 10:42:49 -0400123 'hard_time_out': time_out,
maruel@chromium.org0437a732013-08-27 16:05:52 +0000124 })
125
maruel@chromium.org0437a732013-08-27 16:05:52 +0000126 def to_json(self):
127 """Exports the current configuration into a swarm-readable manifest file.
128
Marc-Antoine Ruel5c720342014-02-21 14:46:14 -0500129 The actual serialization format is defined as a TestCase object as described
130 in services/swarming/src/common/test_request_message.py
131
maruel@chromium.org0437a732013-08-27 16:05:52 +0000132 This function doesn't mutate the object.
133 """
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500134 request = {
Marc-Antoine Ruela7049872013-11-05 19:28:35 -0500135 'cleanup': 'root',
maruel@chromium.org0437a732013-08-27 16:05:52 +0000136 'configurations': [
Marc-Antoine Ruel5c720342014-02-21 14:46:14 -0500137 # Is a TestConfiguration.
maruel@chromium.org0437a732013-08-27 16:05:52 +0000138 {
Marc-Antoine Ruel5d799192013-11-06 15:20:39 -0500139 'config_name': 'isolated',
Marc-Antoine Ruel13b7b782014-03-14 11:14:57 -0400140 'deadline_to_run': self._deadline,
Marc-Antoine Ruel92f32422013-11-06 18:12:13 -0500141 'dimensions': self._dimensions,
Marc-Antoine Ruela7049872013-11-05 19:28:35 -0500142 'min_instances': self._shards,
143 'priority': self.priority,
maruel@chromium.org0437a732013-08-27 16:05:52 +0000144 },
145 ],
Marc-Antoine Ruela7049872013-11-05 19:28:35 -0500146 'data': [],
Marc-Antoine Ruela7049872013-11-05 19:28:35 -0500147 'encoding': 'UTF-8',
Marc-Antoine Ruel05dab5e2013-11-06 15:06:47 -0500148 'env_vars': self._env,
maruel@chromium.org0437a732013-08-27 16:05:52 +0000149 'restart_on_failure': True,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500150 'test_case_name': self._task_name,
Marc-Antoine Ruela7049872013-11-05 19:28:35 -0500151 'tests': self._tasks,
152 'working_dir': self._working_dir,
maruel@chromium.org0437a732013-08-27 16:05:52 +0000153 }
vadimsh@chromium.orgf24e5c32013-10-11 21:16:21 +0000154 if self._isolate_item:
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500155 request['data'].append(
maruel@chromium.org814d23f2013-10-01 19:08:00 +0000156 [
Vadim Shtayurabcff74f2014-02-27 16:19:34 -0800157 self.storage.get_fetch_url(self._isolate_item),
maruel@chromium.org814d23f2013-10-01 19:08:00 +0000158 'swarm_data.zip',
159 ])
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500160 return json.dumps(request, sort_keys=True, separators=(',',':'))
maruel@chromium.org0437a732013-08-27 16:05:52 +0000161
Marc-Antoine Ruelcd629732013-12-20 15:00:42 -0500162 @property
163 def isolate_item(self):
164 """Calling this property 'closes' the manifest and it can't be modified
165 afterward.
166 """
167 if self._isolate_item is None:
168 self._isolate_item = isolateserver.BufferItem(
Vadim Shtayurabcff74f2014-02-27 16:19:34 -0800169 self.bundle.zip_into_buffer(), high_priority=True)
Marc-Antoine Ruelcd629732013-12-20 15:00:42 -0500170 return self._isolate_item
171
172
173def zip_and_upload(manifest):
174 """Zips up all the files necessary to run a manifest and uploads to Swarming
175 master.
176 """
177 try:
Vadim Shtayurab19319e2014-04-27 08:50:06 -0700178 start_time = now()
Marc-Antoine Ruelcd629732013-12-20 15:00:42 -0500179 with manifest.storage:
180 uploaded = manifest.storage.upload_items([manifest.isolate_item])
Vadim Shtayurab19319e2014-04-27 08:50:06 -0700181 elapsed = now() - start_time
Marc-Antoine Ruelcd629732013-12-20 15:00:42 -0500182 except (IOError, OSError) as exc:
183 tools.report_error('Failed to upload the zip file: %s' % exc)
184 return False
185
186 if manifest.isolate_item in uploaded:
187 logging.info('Upload complete, time elapsed: %f', elapsed)
188 else:
189 logging.info('Zip file already on server, time elapsed: %f', elapsed)
190 return True
191
maruel@chromium.org0437a732013-08-27 16:05:52 +0000192
193def now():
194 """Exists so it can be mocked easily."""
195 return time.time()
196
197
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500198def get_task_keys(swarm_base_url, task_name):
199 """Returns the Swarming task key for each shards of task_name."""
200 key_data = urllib.urlencode([('name', task_name)])
maruel@chromium.org0437a732013-08-27 16:05:52 +0000201 url = '%s/get_matching_test_cases?%s' % (swarm_base_url, key_data)
202
vadimsh@chromium.org043b76d2013-09-12 16:15:13 +0000203 for _ in net.retry_loop(max_attempts=net.URL_OPEN_MAX_ATTEMPTS):
204 result = net.url_read(url, retry_404=True)
205 if result is None:
maruel@chromium.org0437a732013-08-27 16:05:52 +0000206 raise Failure(
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500207 'Error: Unable to find any task with the name, %s, on swarming server'
208 % task_name)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000209
maruel@chromium.org0437a732013-08-27 16:05:52 +0000210 # TODO(maruel): Compare exact string.
211 if 'No matching' in result:
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500212 logging.warning('Unable to find any task with the name, %s, on swarming '
213 'server' % task_name)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000214 continue
215 return json.loads(result)
216
217 raise Failure(
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500218 'Error: Unable to find any task with the name, %s, on swarming server'
219 % task_name)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000220
221
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500222def retrieve_results(base_url, task_key, timeout, should_stop):
Vadim Shtayurab19319e2014-04-27 08:50:06 -0700223 """Retrieves results for a single task_key.
224
225 Returns a dict with results on success or None on failure or timeout.
226 """
maruel@chromium.org814d23f2013-10-01 19:08:00 +0000227 assert isinstance(timeout, float), timeout
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500228 params = [('r', task_key)]
maruel@chromium.org0437a732013-08-27 16:05:52 +0000229 result_url = '%s/get_result?%s' % (base_url, urllib.urlencode(params))
Vadim Shtayurab19319e2014-04-27 08:50:06 -0700230 started = now()
231 deadline = started + timeout if timeout else None
232 attempt = 0
233
234 while not should_stop.is_set():
235 attempt += 1
236
237 # Waiting for too long -> give up.
238 current_time = now()
239 if deadline and current_time >= deadline:
240 logging.error('retrieve_results(%s) timed out on attempt %d',
241 base_url, attempt)
242 return None
243
244 # Do not spin too fast. Spin faster at the beginning though.
245 # Start with 1 sec delay and for each 30 sec of waiting add another second
246 # of delay, until hitting 15 sec ceiling.
247 if attempt > 1:
248 max_delay = min(15, 1 + (current_time - started) / 30.0)
249 delay = min(max_delay, deadline - current_time) if deadline else max_delay
250 if delay > 0:
251 logging.debug('Waiting %.1f sec before retrying', delay)
252 should_stop.wait(delay)
253 if should_stop.is_set():
254 return None
255
256 # Disable internal retries in net.url_read, since we are doing retries
257 # ourselves. Do not use retry_404 so should_stop is polled more often.
vadimsh@chromium.org043b76d2013-09-12 16:15:13 +0000258 response = net.url_read(result_url, retry_404=False, retry_50x=False)
Vadim Shtayurab19319e2014-04-27 08:50:06 -0700259
260 # Request failed. Try again.
maruel@chromium.org0437a732013-08-27 16:05:52 +0000261 if response is None:
Vadim Shtayurab19319e2014-04-27 08:50:06 -0700262 continue
263
264 # Got some response, ensure it is JSON dict, retry if not.
265 try:
266 result = json.loads(response) or {}
267 if not isinstance(result, dict):
268 raise ValueError()
269 except (ValueError, TypeError):
270 logging.warning(
271 'Received corrupted or invalid data for task_key %s, retrying: %r',
272 task_key, response)
273 continue
274
275 # Swarming server uses non-empty 'output' value as a flag that task has
276 # finished. How to wait for tasks that produce no output is a mystery.
277 if result.get('output'):
278 return result
maruel@chromium.org0437a732013-08-27 16:05:52 +0000279
280
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700281def yield_results(
282 swarm_base_url, task_keys, timeout, max_threads, print_status_updates):
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500283 """Yields swarming task results from the swarming server as (index, result).
maruel@chromium.org0437a732013-08-27 16:05:52 +0000284
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700285 Duplicate shards are ignored. Shards are yielded in order of completion.
286 Timed out shards are NOT yielded at all. Caller can compare number of yielded
287 shards with len(task_keys) to verify all shards completed.
maruel@chromium.org0437a732013-08-27 16:05:52 +0000288
289 max_threads is optional and is used to limit the number of parallel fetches
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500290 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 +0000291 worth normally to limit the number threads. Mostly used for testing purposes.
Marc-Antoine Ruel5c720342014-02-21 14:46:14 -0500292
293 Yields:
294 (index, result). In particular, 'result' is defined as the
295 GetRunnerResults() function in services/swarming/server/test_runner.py.
maruel@chromium.org0437a732013-08-27 16:05:52 +0000296 """
maruel@chromium.org0437a732013-08-27 16:05:52 +0000297 number_threads = (
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500298 min(max_threads, len(task_keys)) if max_threads else len(task_keys))
Vadim Shtayurab19319e2014-04-27 08:50:06 -0700299 should_stop = threading.Event()
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700300 results_channel = threading_utils.TaskChannel()
Vadim Shtayurab19319e2014-04-27 08:50:06 -0700301
maruel@chromium.org0437a732013-08-27 16:05:52 +0000302 with threading_utils.ThreadPool(number_threads, number_threads, 0) as pool:
303 try:
Vadim Shtayurab19319e2014-04-27 08:50:06 -0700304 # Enqueue 'retrieve_results' calls for each shard key to run in parallel.
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500305 for task_key in task_keys:
maruel@chromium.org0437a732013-08-27 16:05:52 +0000306 pool.add_task(
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700307 0, results_channel.wrap_task(retrieve_results),
308 swarm_base_url, task_key, timeout, should_stop)
Vadim Shtayurab19319e2014-04-27 08:50:06 -0700309
310 # Wait for all of them to finish.
311 shards_remaining = range(len(task_keys))
312 active_task_count = len(task_keys)
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700313 while active_task_count:
314 try:
315 result = results_channel.pull(timeout=STATUS_UPDATE_INTERVAL)
316 except threading_utils.TaskChannel.Timeout:
317 if print_status_updates:
318 print(
319 'Waiting for results from the following shards: %s' %
320 ', '.join(map(str, shards_remaining)))
321 sys.stdout.flush()
322 continue
323 except Exception:
324 logging.exception('Unexpected exception in retrieve_results')
325 result = None
Vadim Shtayurab19319e2014-04-27 08:50:06 -0700326
327 # A call to 'retrieve_results' finished (successfully or not).
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700328 active_task_count -= 1
maruel@chromium.org0437a732013-08-27 16:05:52 +0000329 if not result:
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500330 logging.error('Failed to retrieve the results for a swarming key')
maruel@chromium.org0437a732013-08-27 16:05:52 +0000331 continue
Vadim Shtayurab19319e2014-04-27 08:50:06 -0700332
maruel@chromium.org0437a732013-08-27 16:05:52 +0000333 shard_index = result['config_instance_index']
334 if shard_index in shards_remaining:
335 shards_remaining.remove(shard_index)
336 yield shard_index, result
337 else:
338 logging.warning('Ignoring duplicate shard index %d', shard_index)
Vadim Shtayurab19319e2014-04-27 08:50:06 -0700339
maruel@chromium.org0437a732013-08-27 16:05:52 +0000340 finally:
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700341 # Done or aborted with Ctrl+C, kill the remaining threads.
maruel@chromium.org0437a732013-08-27 16:05:52 +0000342 should_stop.set()
343
344
345def chromium_setup(manifest):
346 """Sets up the commands to run.
347
348 Highly chromium specific.
349 """
vadimsh@chromium.org6b706212013-08-28 15:03:46 +0000350 # Add uncompressed zip here. It'll be compressed as part of the package sent
351 # to Swarming server.
352 run_test_name = 'run_isolated.zip'
353 manifest.bundle.add_buffer(run_test_name,
354 run_isolated.get_as_zip_package().zip_into_buffer(compress=False))
maruel@chromium.org0437a732013-08-27 16:05:52 +0000355
vadimsh@chromium.org6b706212013-08-28 15:03:46 +0000356 cleanup_script_name = 'swarm_cleanup.py'
357 manifest.bundle.add_file(os.path.join(TOOLS_PATH, cleanup_script_name),
358 cleanup_script_name)
359
maruel@chromium.org0437a732013-08-27 16:05:52 +0000360 run_cmd = [
361 'python', run_test_name,
maruel@chromium.org814d23f2013-10-01 19:08:00 +0000362 '--hash', manifest.isolated_hash,
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500363 '--namespace', manifest.namespace,
maruel@chromium.org0437a732013-08-27 16:05:52 +0000364 ]
Marc-Antoine Ruel8806e622014-02-12 14:15:53 -0500365 if file_path.is_url(manifest.isolate_server):
366 run_cmd.extend(('--isolate-server', manifest.isolate_server))
367 else:
368 run_cmd.extend(('--indir', manifest.isolate_server))
369
maruel@chromium.org0437a732013-08-27 16:05:52 +0000370 if manifest.verbose or manifest.profile:
371 # Have it print the profiling section.
372 run_cmd.append('--verbose')
373 manifest.add_task('Run Test', run_cmd)
374
375 # Clean up
376 manifest.add_task('Clean Up', ['python', cleanup_script_name])
377
378
Marc-Antoine Ruelcd629732013-12-20 15:00:42 -0500379def googletest_setup(env, shards):
380 """Sets googletest specific environment variables."""
381 if shards > 1:
382 env = env.copy()
383 env['GTEST_SHARD_INDEX'] = '%(instance_index)s'
384 env['GTEST_TOTAL_SHARDS'] = '%(num_instances)s'
385 return env
386
387
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500388def archive(isolate_server, namespace, isolated, algo, verbose):
maruel@chromium.org0437a732013-08-27 16:05:52 +0000389 """Archives a .isolated and all the dependencies on the CAC."""
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500390 logging.info('archive(%s, %s, %s)', isolate_server, namespace, isolated)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000391 tempdir = None
Marc-Antoine Ruel8806e622014-02-12 14:15:53 -0500392 if file_path.is_url(isolate_server):
393 command = 'archive'
394 flag = '--isolate-server'
395 else:
396 command = 'hashtable'
397 flag = '--outdir'
398
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500399 print('Archiving: %s' % isolated)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000400 try:
maruel@chromium.org0437a732013-08-27 16:05:52 +0000401 cmd = [
402 sys.executable,
403 os.path.join(ROOT_DIR, 'isolate.py'),
Marc-Antoine Ruel8806e622014-02-12 14:15:53 -0500404 command,
405 flag, isolate_server,
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500406 '--namespace', namespace,
maruel@chromium.org0437a732013-08-27 16:05:52 +0000407 '--isolated', isolated,
408 ]
maruel@chromium.orge9403ab2013-09-20 18:03:49 +0000409 cmd.extend(['--verbose'] * verbose)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000410 logging.info(' '.join(cmd))
411 if subprocess.call(cmd, verbose):
412 return
maruel@chromium.org7b844a62013-09-17 13:04:59 +0000413 return isolateserver.hash_file(isolated, algo)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000414 finally:
415 if tempdir:
416 shutil.rmtree(tempdir)
417
418
419def process_manifest(
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500420 swarming, isolate_server, namespace, isolated_hash, task_name, shards,
Marc-Antoine Ruel13b7b782014-03-14 11:14:57 -0400421 dimensions, env, working_dir, deadline, verbose, profile, priority):
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500422 """Processes the manifest file and send off the swarming task request."""
maruel@chromium.org0437a732013-08-27 16:05:52 +0000423 try:
424 manifest = Manifest(
Marc-Antoine Ruela7049872013-11-05 19:28:35 -0500425 isolate_server=isolate_server,
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500426 namespace=namespace,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500427 isolated_hash=isolated_hash,
428 task_name=task_name,
Marc-Antoine Ruela7049872013-11-05 19:28:35 -0500429 shards=shards,
Marc-Antoine Ruel92f32422013-11-06 18:12:13 -0500430 dimensions=dimensions,
Marc-Antoine Ruel05dab5e2013-11-06 15:06:47 -0500431 env=env,
Marc-Antoine Ruela7049872013-11-05 19:28:35 -0500432 working_dir=working_dir,
Marc-Antoine Ruel13b7b782014-03-14 11:14:57 -0400433 deadline=deadline,
Marc-Antoine Ruela7049872013-11-05 19:28:35 -0500434 verbose=verbose,
435 profile=profile,
Vadim Shtayurabcff74f2014-02-27 16:19:34 -0800436 priority=priority)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000437 except ValueError as e:
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500438 tools.report_error('Unable to process %s: %s' % (task_name, e))
maruel@chromium.org0437a732013-08-27 16:05:52 +0000439 return 1
440
441 chromium_setup(manifest)
442
Marc-Antoine Ruelcd629732013-12-20 15:00:42 -0500443 logging.info('Zipping up files...')
444 if not zip_and_upload(manifest):
maruel@chromium.org0437a732013-08-27 16:05:52 +0000445 return 1
446
Marc-Antoine Ruelcd629732013-12-20 15:00:42 -0500447 logging.info('Server: %s', swarming)
448 logging.info('Task name: %s', task_name)
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500449 trigger_url = swarming + '/test'
maruel@chromium.org0437a732013-08-27 16:05:52 +0000450 manifest_text = manifest.to_json()
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500451 result = net.url_read(trigger_url, data={'request': manifest_text})
maruel@chromium.org0437a732013-08-27 16:05:52 +0000452 if not result:
vadimsh@chromium.orgd908a542013-10-30 01:36:17 +0000453 tools.report_error(
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500454 'Failed to trigger task %s\n%s' % (task_name, trigger_url))
maruel@chromium.org0437a732013-08-27 16:05:52 +0000455 return 1
456 try:
vadimsh@chromium.orgf24e5c32013-10-11 21:16:21 +0000457 json.loads(result)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000458 except (ValueError, TypeError) as e:
vadimsh@chromium.orgd908a542013-10-30 01:36:17 +0000459 msg = '\n'.join((
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500460 'Failed to trigger task %s' % task_name,
vadimsh@chromium.orgd908a542013-10-30 01:36:17 +0000461 'Manifest: %s' % manifest_text,
462 'Bad response: %s' % result,
463 str(e)))
464 tools.report_error(msg)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000465 return 1
466 return 0
467
468
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500469def isolated_to_hash(isolate_server, namespace, arg, algo, verbose):
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500470 """Archives a .isolated file if needed.
471
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500472 Returns the file hash to trigger and a bool specifying if it was a file (True)
473 or a hash (False).
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500474 """
475 if arg.endswith('.isolated'):
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500476 file_hash = archive(isolate_server, namespace, arg, algo, verbose)
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500477 if not file_hash:
478 tools.report_error('Archival failure %s' % arg)
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500479 return None, True
480 return file_hash, True
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500481 elif isolateserver.is_valid_hash(arg, algo):
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500482 return arg, False
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500483 else:
484 tools.report_error('Invalid hash %s' % arg)
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500485 return None, False
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500486
487
maruel@chromium.org0437a732013-08-27 16:05:52 +0000488def trigger(
Marc-Antoine Ruela7049872013-11-05 19:28:35 -0500489 swarming,
490 isolate_server,
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500491 namespace,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500492 file_hash_or_isolated,
493 task_name,
494 shards,
Marc-Antoine Ruel92f32422013-11-06 18:12:13 -0500495 dimensions,
496 env,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500497 working_dir,
Marc-Antoine Ruel13b7b782014-03-14 11:14:57 -0400498 deadline,
maruel@chromium.org0437a732013-08-27 16:05:52 +0000499 verbose,
500 profile,
501 priority):
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500502 """Sends off the hash swarming task requests."""
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500503 file_hash, is_file = isolated_to_hash(
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500504 isolate_server, namespace, file_hash_or_isolated, hashlib.sha1, verbose)
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500505 if not file_hash:
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500506 return 1, ''
507 if not task_name:
508 # If a file name was passed, use its base name of the isolated hash.
509 # Otherwise, use user name as an approximation of a task name.
510 if is_file:
511 key = os.path.splitext(os.path.basename(file_hash_or_isolated))[0]
512 else:
513 key = getpass.getuser()
Vadim Shtayurac3d97b02014-04-26 19:16:05 -0700514 task_name = '%s/%s/%s/%d' % (
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500515 key,
516 '_'.join('%s=%s' % (k, v) for k, v in sorted(dimensions.iteritems())),
Vadim Shtayurac3d97b02014-04-26 19:16:05 -0700517 file_hash,
518 now() * 1000)
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500519
Marc-Antoine Ruelcd629732013-12-20 15:00:42 -0500520 env = googletest_setup(env, shards)
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500521 # TODO(maruel): It should first create a request manifest object, then pass
522 # it to a function to zip, archive and trigger.
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500523 result = process_manifest(
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500524 swarming=swarming,
525 isolate_server=isolate_server,
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500526 namespace=namespace,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500527 isolated_hash=file_hash,
528 task_name=task_name,
529 shards=shards,
530 dimensions=dimensions,
Marc-Antoine Ruel13b7b782014-03-14 11:14:57 -0400531 deadline=deadline,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500532 env=env,
533 working_dir=working_dir,
534 verbose=verbose,
535 profile=profile,
Vadim Shtayurabcff74f2014-02-27 16:19:34 -0800536 priority=priority)
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500537 return result, task_name
maruel@chromium.org0437a732013-08-27 16:05:52 +0000538
539
540def decorate_shard_output(result, shard_exit_code):
541 """Returns wrapped output for swarming task shard."""
542 tag = 'index %s (machine tag: %s, id: %s)' % (
543 result['config_instance_index'],
544 result['machine_id'],
545 result.get('machine_tag', 'unknown'))
546 return (
547 '\n'
548 '================================================================\n'
549 'Begin output from shard %s\n'
550 '================================================================\n'
551 '\n'
552 '%s'
553 '================================================================\n'
554 'End output from shard %s. Return %d\n'
555 '================================================================\n'
556 ) % (tag, result['output'] or NO_OUTPUT_FOUND, tag, shard_exit_code)
557
558
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700559def collect(url, task_name, timeout, decorate, print_status_updates):
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500560 """Retrieves results of a Swarming task."""
561 logging.info('Collecting %s', task_name)
562 task_keys = get_task_keys(url, task_name)
563 if not task_keys:
564 raise Failure('No task keys to get results with.')
maruel@chromium.org0437a732013-08-27 16:05:52 +0000565
maruel@chromium.org9c1c7b52013-08-28 19:04:36 +0000566 exit_code = None
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700567 seen_shards = set()
568 for index, output in yield_results(
569 url, task_keys, timeout, None, print_status_updates):
570 seen_shards.add(index)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000571 shard_exit_codes = (output['exit_codes'] or '1').split(',')
572 shard_exit_code = max(int(i) for i in shard_exit_codes)
573 if decorate:
574 print decorate_shard_output(output, shard_exit_code)
575 else:
576 print(
577 '%s/%s: %s' % (
578 output['machine_id'],
579 output['machine_tag'],
580 output['exit_codes']))
581 print(''.join(' %s\n' % l for l in output['output'].splitlines()))
maruel@chromium.org9c1c7b52013-08-28 19:04:36 +0000582 exit_code = exit_code or shard_exit_code
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700583 if len(seen_shards) != len(task_keys):
584 missing_shards = [x for x in range(len(task_keys)) if x not in seen_shards]
585 print >> sys.stderr, ('Results from some shards are missing: %s' %
586 ', '.join(map(str, missing_shards)))
587 exit_code = exit_code or 1
maruel@chromium.org9c1c7b52013-08-28 19:04:36 +0000588 return exit_code if exit_code is not None else 1
maruel@chromium.org0437a732013-08-27 16:05:52 +0000589
590
Marc-Antoine Ruel819fb162014-03-12 16:38:26 -0400591def add_filter_options(parser):
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500592 parser.filter_group = tools.optparse.OptionGroup(parser, 'Filtering slaves')
593 parser.filter_group.add_option(
Marc-Antoine Ruelb39e8cf2014-01-20 10:39:31 -0500594 '-d', '--dimension', default=[], action='append', nargs=2,
Marc-Antoine Ruel92f32422013-11-06 18:12:13 -0500595 dest='dimensions', metavar='FOO bar',
596 help='dimension to filter on')
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500597 parser.add_option_group(parser.filter_group)
598
Marc-Antoine Ruel819fb162014-03-12 16:38:26 -0400599
600def add_trigger_options(parser):
601 """Adds all options to trigger a task on Swarming."""
602 isolateserver.add_isolate_server_options(parser, True)
603 add_filter_options(parser)
604
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500605 parser.task_group = tools.optparse.OptionGroup(parser, 'Task properties')
606 parser.task_group.add_option(
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500607 '-w', '--working-dir', default='swarm_tests',
608 help='Working directory on the swarming slave side. default: %default.')
609 parser.task_group.add_option(
610 '--working_dir', help=tools.optparse.SUPPRESS_HELP)
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500611 parser.task_group.add_option(
612 '-e', '--env', default=[], action='append', nargs=2, metavar='FOO bar',
613 help='environment variables to set')
614 parser.task_group.add_option(
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500615 '--priority', type='int', default=100,
616 help='The lower value, the more important the task is')
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500617 parser.task_group.add_option(
618 '--shards', type='int', default=1, help='number of shards to use')
619 parser.task_group.add_option(
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500620 '-T', '--task-name',
621 help='Display name of the task. It uniquely identifies the task. '
Vadim Shtayurac3d97b02014-04-26 19:16:05 -0700622 'Defaults to <base_name>/<dimensions>/<isolated hash>/<timestamp> '
623 'if an isolated file is provided, if a hash is provided, it '
624 'defaults to <user>/<dimensions>/<isolated hash>/<timestamp>')
Marc-Antoine Ruel13b7b782014-03-14 11:14:57 -0400625 parser.task_group.add_option(
626 '--deadline', type='int', default=6*60*60,
627 help='Seconds to allow the task to be pending for a bot to run before '
628 'this task request expires.')
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500629 parser.add_option_group(parser.task_group)
Marc-Antoine Ruelcd629732013-12-20 15:00:42 -0500630 # TODO(maruel): This is currently written in a chromium-specific way.
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500631 parser.group_logging.add_option(
maruel@chromium.org0437a732013-08-27 16:05:52 +0000632 '--profile', action='store_true',
633 default=bool(os.environ.get('ISOLATE_DEBUG')),
634 help='Have run_isolated.py print profiling info')
maruel@chromium.org0437a732013-08-27 16:05:52 +0000635
636
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500637def process_trigger_options(parser, options, args):
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500638 isolateserver.process_isolate_server_options(parser, options)
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500639 if len(args) != 1:
640 parser.error('Must pass one .isolated file or its hash (sha1).')
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500641 options.dimensions = dict(options.dimensions)
Marc-Antoine Ruel2d1bee82014-03-12 14:10:25 -0400642 if not options.dimensions:
643 parser.error('Please at least specify one --dimension')
maruel@chromium.org0437a732013-08-27 16:05:52 +0000644
645
646def add_collect_options(parser):
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500647 parser.server_group.add_option(
maruel@chromium.org0437a732013-08-27 16:05:52 +0000648 '-t', '--timeout',
649 type='float',
650 default=DEFAULT_SHARD_WAIT_TIME,
651 help='Timeout to wait for result, set to 0 for no timeout; default: '
652 '%default s')
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500653 parser.group_logging.add_option(
654 '--decorate', action='store_true', help='Decorate output')
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700655 parser.group_logging.add_option(
656 '--print-status-updates', action='store_true',
657 help='Print periodic status updates')
maruel@chromium.org0437a732013-08-27 16:05:52 +0000658
659
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500660@subcommand.usage('task_name')
maruel@chromium.org0437a732013-08-27 16:05:52 +0000661def CMDcollect(parser, args):
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500662 """Retrieves results of a Swarming task.
maruel@chromium.org0437a732013-08-27 16:05:52 +0000663
664 The result can be in multiple part if the execution was sharded. It can
665 potentially have retries.
666 """
667 add_collect_options(parser)
668 (options, args) = parser.parse_args(args)
669 if not args:
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500670 parser.error('Must specify one task name.')
maruel@chromium.org0437a732013-08-27 16:05:52 +0000671 elif len(args) > 1:
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500672 parser.error('Must specify only one task name.')
maruel@chromium.org0437a732013-08-27 16:05:52 +0000673
674 try:
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700675 return collect(
676 options.swarming,
677 args[0],
678 options.timeout,
679 options.decorate,
680 options.print_status_updates)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000681 except Failure as e:
vadimsh@chromium.orgd908a542013-10-30 01:36:17 +0000682 tools.report_error(e)
683 return 1
maruel@chromium.org0437a732013-08-27 16:05:52 +0000684
685
Marc-Antoine Ruel819fb162014-03-12 16:38:26 -0400686def CMDquery(parser, args):
687 """Returns information about the bots connected to the Swarming server."""
688 add_filter_options(parser)
689 parser.filter_group.add_option(
Marc-Antoine Ruel28083112014-03-13 16:34:04 -0400690 '--dead-only', action='store_true',
691 help='Only print dead bots, useful to reap them and reimage broken bots')
692 parser.filter_group.add_option(
Marc-Antoine Ruel819fb162014-03-12 16:38:26 -0400693 '-k', '--keep-dead', action='store_true',
694 help='Do not filter out dead bots')
695 parser.filter_group.add_option(
696 '-b', '--bare', action='store_true',
Marc-Antoine Ruele7b00162014-03-12 16:59:01 -0400697 help='Do not print out dimensions')
Marc-Antoine Ruel819fb162014-03-12 16:38:26 -0400698 options, args = parser.parse_args(args)
Marc-Antoine Ruel28083112014-03-13 16:34:04 -0400699
700 if options.keep_dead and options.dead_only:
701 parser.error('Use only one of --keep-dead and --dead-only')
Marc-Antoine Ruel819fb162014-03-12 16:38:26 -0400702 service = net.get_http_service(options.swarming)
703 data = service.json_request('GET', '/swarming/api/v1/bots')
704 if data is None:
705 print >> sys.stderr, 'Failed to access %s' % options.swarming
706 return 1
707 timeout = datetime.timedelta(seconds=data['machine_death_timeout'])
708 utcnow = datetime.datetime.utcnow()
709 for machine in natsort.natsorted(data['machines'], key=lambda x: x['tag']):
710 last_seen = datetime.datetime.strptime(
711 machine['last_seen'], '%Y-%m-%d %H:%M:%S')
Marc-Antoine Ruel28083112014-03-13 16:34:04 -0400712 is_dead = utcnow - last_seen > timeout
713 if options.dead_only:
714 if not is_dead:
715 continue
716 elif not options.keep_dead and is_dead:
Marc-Antoine Ruel819fb162014-03-12 16:38:26 -0400717 continue
718
Marc-Antoine Ruele7b00162014-03-12 16:59:01 -0400719 # If the user requested to filter on dimensions, ensure the bot has all the
720 # dimensions requested.
Marc-Antoine Ruel819fb162014-03-12 16:38:26 -0400721 dimensions = machine['dimensions']
722 for key, value in options.dimensions:
723 if key not in dimensions:
724 break
Marc-Antoine Ruele7b00162014-03-12 16:59:01 -0400725 # A bot can have multiple value for a key, for example,
726 # {'os': ['Windows', 'Windows-6.1']}, so that --dimension os=Windows will
727 # be accepted.
Marc-Antoine Ruel819fb162014-03-12 16:38:26 -0400728 if isinstance(dimensions[key], list):
729 if value not in dimensions[key]:
730 break
731 else:
732 if value != dimensions[key]:
733 break
734 else:
735 print machine['tag']
Marc-Antoine Ruele7b00162014-03-12 16:59:01 -0400736 if not options.bare:
Marc-Antoine Ruel819fb162014-03-12 16:38:26 -0400737 print ' %s' % dimensions
738 return 0
739
740
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500741@subcommand.usage('[hash|isolated]')
maruel@chromium.org0437a732013-08-27 16:05:52 +0000742def CMDrun(parser, args):
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500743 """Triggers a task and wait for the results.
maruel@chromium.org0437a732013-08-27 16:05:52 +0000744
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500745 Basically, does everything to run a command remotely.
maruel@chromium.org0437a732013-08-27 16:05:52 +0000746 """
747 add_trigger_options(parser)
748 add_collect_options(parser)
749 options, args = parser.parse_args(args)
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500750 process_trigger_options(parser, options, args)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000751
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500752 try:
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500753 result, task_name = trigger(
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500754 swarming=options.swarming,
Marc-Antoine Ruel8806e622014-02-12 14:15:53 -0500755 isolate_server=options.isolate_server or options.indir,
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500756 namespace=options.namespace,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500757 file_hash_or_isolated=args[0],
758 task_name=options.task_name,
759 shards=options.shards,
760 dimensions=options.dimensions,
761 env=dict(options.env),
762 working_dir=options.working_dir,
Marc-Antoine Ruel13b7b782014-03-14 11:14:57 -0400763 deadline=options.deadline,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500764 verbose=options.verbose,
765 profile=options.profile,
766 priority=options.priority)
767 except Failure as e:
768 tools.report_error(
769 'Failed to trigger %s(%s): %s' %
770 (options.task_name, args[0], e.args[0]))
771 return 1
772 if result:
773 tools.report_error('Failed to trigger the task.')
maruel@chromium.org0437a732013-08-27 16:05:52 +0000774 return result
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500775 if task_name != options.task_name:
776 print('Triggered task: %s' % task_name)
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500777 try:
778 return collect(
779 options.swarming,
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500780 task_name,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500781 options.timeout,
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700782 options.decorate,
783 options.print_status_updates)
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500784 except Failure as e:
785 tools.report_error(e)
786 return 1
maruel@chromium.org0437a732013-08-27 16:05:52 +0000787
788
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500789@subcommand.usage("(hash|isolated)")
maruel@chromium.org0437a732013-08-27 16:05:52 +0000790def CMDtrigger(parser, args):
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500791 """Triggers a Swarming task.
maruel@chromium.org0437a732013-08-27 16:05:52 +0000792
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500793 Accepts either the hash (sha1) of a .isolated file already uploaded or the
794 path to an .isolated file to archive, packages it if needed and sends a
795 Swarming manifest file to the Swarming server.
796
797 If an .isolated file is specified instead of an hash, it is first archived.
maruel@chromium.org0437a732013-08-27 16:05:52 +0000798 """
799 add_trigger_options(parser)
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500800 options, args = parser.parse_args(args)
801 process_trigger_options(parser, options, args)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000802
803 try:
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500804 result, task_name = trigger(
Marc-Antoine Ruela7049872013-11-05 19:28:35 -0500805 swarming=options.swarming,
Marc-Antoine Ruel8806e622014-02-12 14:15:53 -0500806 isolate_server=options.isolate_server or options.indir,
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500807 namespace=options.namespace,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500808 file_hash_or_isolated=args[0],
809 task_name=options.task_name,
810 dimensions=options.dimensions,
811 shards=options.shards,
Marc-Antoine Ruel92f32422013-11-06 18:12:13 -0500812 env=dict(options.env),
Marc-Antoine Ruela7049872013-11-05 19:28:35 -0500813 working_dir=options.working_dir,
Marc-Antoine Ruel13b7b782014-03-14 11:14:57 -0400814 deadline=options.deadline,
Marc-Antoine Ruela7049872013-11-05 19:28:35 -0500815 verbose=options.verbose,
816 profile=options.profile,
817 priority=options.priority)
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500818 if task_name != options.task_name and not result:
819 print('Triggered task: %s' % task_name)
820 return result
maruel@chromium.org0437a732013-08-27 16:05:52 +0000821 except Failure as e:
vadimsh@chromium.orgd908a542013-10-30 01:36:17 +0000822 tools.report_error(e)
823 return 1
maruel@chromium.org0437a732013-08-27 16:05:52 +0000824
825
826class OptionParserSwarming(tools.OptionParserWithLogging):
827 def __init__(self, **kwargs):
828 tools.OptionParserWithLogging.__init__(
829 self, prog='swarming.py', **kwargs)
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500830 self.server_group = tools.optparse.OptionGroup(self, 'Server')
831 self.server_group.add_option(
maruel@chromium.orge9403ab2013-09-20 18:03:49 +0000832 '-S', '--swarming',
Kevin Graney5346c162014-01-24 12:20:01 -0500833 metavar='URL', default=os.environ.get('SWARMING_SERVER', ''),
maruel@chromium.orge9403ab2013-09-20 18:03:49 +0000834 help='Swarming server to use')
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500835 self.add_option_group(self.server_group)
Vadim Shtayurae34e13a2014-02-02 11:23:26 -0800836 auth.add_auth_options(self)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000837
838 def parse_args(self, *args, **kwargs):
839 options, args = tools.OptionParserWithLogging.parse_args(
840 self, *args, **kwargs)
841 options.swarming = options.swarming.rstrip('/')
842 if not options.swarming:
843 self.error('--swarming is required.')
Vadim Shtayura5d1efce2014-02-04 10:55:43 -0800844 auth.process_auth_options(self, options)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000845 return options, args
846
847
848def main(args):
849 dispatcher = subcommand.CommandDispatcher(__name__)
850 try:
851 return dispatcher.execute(OptionParserSwarming(version=__version__), args)
vadimsh@chromium.orgd908a542013-10-30 01:36:17 +0000852 except Exception as e:
853 tools.report_error(e)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000854 return 1
855
856
857if __name__ == '__main__':
858 fix_encoding.fix_encoding()
859 tools.disable_buffering()
860 colorama.init()
861 sys.exit(main(sys.argv[1:]))