blob: 7d687a0057a7f159d0c482f2feccde256f687a6b [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
19import time
20import urllib
maruel@chromium.org0437a732013-08-27 16:05:52 +000021
22from third_party import colorama
23from third_party.depot_tools import fix_encoding
24from third_party.depot_tools import subcommand
vadimsh@chromium.org6b706212013-08-28 15:03:46 +000025
Marc-Antoine Ruel8806e622014-02-12 14:15:53 -050026from utils import file_path
Marc-Antoine Ruel819fb162014-03-12 16:38:26 -040027from third_party.chromium import natsort
vadimsh@chromium.org6b706212013-08-28 15:03:46 +000028from utils import net
maruel@chromium.org0437a732013-08-27 16:05:52 +000029from utils import threading_utils
vadimsh@chromium.org6b706212013-08-28 15:03:46 +000030from utils import tools
31from utils import zip_package
maruel@chromium.org0437a732013-08-27 16:05:52 +000032
Vadim Shtayurae34e13a2014-02-02 11:23:26 -080033import auth
maruel@chromium.org7b844a62013-09-17 13:04:59 +000034import isolateserver
maruel@chromium.org0437a732013-08-27 16:05:52 +000035import run_isolated
36
37
38ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
39TOOLS_PATH = os.path.join(ROOT_DIR, 'tools')
40
41
maruel@chromium.org0437a732013-08-27 16:05:52 +000042# The default time to wait for a shard to finish running.
csharp@chromium.org24758492013-08-28 19:10:54 +000043DEFAULT_SHARD_WAIT_TIME = 80 * 60.
maruel@chromium.org0437a732013-08-27 16:05:52 +000044
Vadim Shtayura86a2cef2014-04-18 11:13:39 -070045# How often to print status updates to stdout in 'collect'.
46STATUS_UPDATE_INTERVAL = 15 * 60.
47
maruel@chromium.org0437a732013-08-27 16:05:52 +000048
49NO_OUTPUT_FOUND = (
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -050050 'No output produced by the task, it may have failed to run.\n'
maruel@chromium.org0437a732013-08-27 16:05:52 +000051 '\n')
52
53
maruel@chromium.org0437a732013-08-27 16:05:52 +000054class Failure(Exception):
55 """Generic failure."""
56 pass
57
58
59class Manifest(object):
60 """Represents a Swarming task manifest.
61
62 Also includes code to zip code and upload itself.
63 """
64 def __init__(
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -050065 self, isolate_server, namespace, isolated_hash, task_name, shards, env,
Marc-Antoine Ruel13b7b782014-03-14 11:14:57 -040066 dimensions, working_dir, deadline, verbose, profile, priority):
maruel@chromium.org0437a732013-08-27 16:05:52 +000067 """Populates a manifest object.
68 Args:
Marc-Antoine Ruela7049872013-11-05 19:28:35 -050069 isolate_server - isolate server url.
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -050070 namespace - isolate server namespace to use.
maruel@chromium.org814d23f2013-10-01 19:08:00 +000071 isolated_hash - The manifest's sha-1 that the slave is going to fetch.
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -050072 task_name - The name to give the task request.
73 shards - The number of swarming shards to request.
Marc-Antoine Ruel05dab5e2013-11-06 15:06:47 -050074 env - environment variables to set.
Marc-Antoine Ruel92f32422013-11-06 18:12:13 -050075 dimensions - dimensions to filter the task on.
maruel@chromium.org0437a732013-08-27 16:05:52 +000076 working_dir - Relative working directory to start the script.
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
84 # The reason is that swarm_bot doesn't understand compressed data yet. So
85 # the data to be downloaded by swarm_bot is in 'default', independent of
86 # what run_isolated.py is going to fetch.
Marc-Antoine Ruela7049872013-11-05 19:28:35 -050087 self.storage = isolateserver.get_storage(isolate_server, 'default')
88
maruel@chromium.org814d23f2013-10-01 19:08:00 +000089 self.isolated_hash = isolated_hash
vadimsh@chromium.org6b706212013-08-28 15:03:46 +000090 self.bundle = zip_package.ZipPackage(ROOT_DIR)
91
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -050092 self._task_name = task_name
maruel@chromium.org0437a732013-08-27 16:05:52 +000093 self._shards = shards
Marc-Antoine Ruel92f32422013-11-06 18:12:13 -050094 self._env = env.copy()
95 self._dimensions = dimensions.copy()
maruel@chromium.org0437a732013-08-27 16:05:52 +000096 self._working_dir = working_dir
Marc-Antoine Ruel13b7b782014-03-14 11:14:57 -040097 self._deadline = deadline
maruel@chromium.org0437a732013-08-27 16:05:52 +000098
maruel@chromium.org0437a732013-08-27 16:05:52 +000099 self.verbose = bool(verbose)
100 self.profile = bool(profile)
101 self.priority = priority
102
vadimsh@chromium.orgf24e5c32013-10-11 21:16:21 +0000103 self._isolate_item = None
maruel@chromium.org0437a732013-08-27 16:05:52 +0000104 self._tasks = []
maruel@chromium.org0437a732013-08-27 16:05:52 +0000105
Marc-Antoine Ruelaf78a902014-03-20 10:42:49 -0400106 def add_task(self, task_name, actions, time_out=2*60*60):
Marc-Antoine Ruel5c720342014-02-21 14:46:14 -0500107 """Appends a new task as a TestObject to the swarming manifest file.
Marc-Antoine Ruelcd629732013-12-20 15:00:42 -0500108
109 Tasks cannot be added once the manifest was uploaded.
Marc-Antoine Ruel5c720342014-02-21 14:46:14 -0500110
Marc-Antoine Ruelaf78a902014-03-20 10:42:49 -0400111 By default, command will be killed after 2 hours of execution.
112
Marc-Antoine Ruel5c720342014-02-21 14:46:14 -0500113 See TestObject in services/swarming/src/common/test_request_message.py for
114 the valid format.
Marc-Antoine Ruelcd629732013-12-20 15:00:42 -0500115 """
116 assert not self._isolate_item
maruel@chromium.org0437a732013-08-27 16:05:52 +0000117 self._tasks.append(
118 {
119 'action': actions,
120 'decorate_output': self.verbose,
121 'test_name': task_name,
Marc-Antoine Ruelaf78a902014-03-20 10:42:49 -0400122 'hard_time_out': time_out,
maruel@chromium.org0437a732013-08-27 16:05:52 +0000123 })
124
maruel@chromium.org0437a732013-08-27 16:05:52 +0000125 def to_json(self):
126 """Exports the current configuration into a swarm-readable manifest file.
127
Marc-Antoine Ruel5c720342014-02-21 14:46:14 -0500128 The actual serialization format is defined as a TestCase object as described
129 in services/swarming/src/common/test_request_message.py
130
maruel@chromium.org0437a732013-08-27 16:05:52 +0000131 This function doesn't mutate the object.
132 """
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500133 request = {
Marc-Antoine Ruela7049872013-11-05 19:28:35 -0500134 'cleanup': 'root',
maruel@chromium.org0437a732013-08-27 16:05:52 +0000135 'configurations': [
Marc-Antoine Ruel5c720342014-02-21 14:46:14 -0500136 # Is a TestConfiguration.
maruel@chromium.org0437a732013-08-27 16:05:52 +0000137 {
Marc-Antoine Ruel5d799192013-11-06 15:20:39 -0500138 'config_name': 'isolated',
Marc-Antoine Ruel13b7b782014-03-14 11:14:57 -0400139 'deadline_to_run': self._deadline,
Marc-Antoine Ruel92f32422013-11-06 18:12:13 -0500140 'dimensions': self._dimensions,
Marc-Antoine Ruela7049872013-11-05 19:28:35 -0500141 'min_instances': self._shards,
142 'priority': self.priority,
maruel@chromium.org0437a732013-08-27 16:05:52 +0000143 },
144 ],
Marc-Antoine Ruela7049872013-11-05 19:28:35 -0500145 'data': [],
Marc-Antoine Ruela7049872013-11-05 19:28:35 -0500146 'encoding': 'UTF-8',
Marc-Antoine Ruel05dab5e2013-11-06 15:06:47 -0500147 'env_vars': self._env,
maruel@chromium.org0437a732013-08-27 16:05:52 +0000148 'restart_on_failure': True,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500149 'test_case_name': self._task_name,
Marc-Antoine Ruela7049872013-11-05 19:28:35 -0500150 'tests': self._tasks,
151 'working_dir': self._working_dir,
maruel@chromium.org0437a732013-08-27 16:05:52 +0000152 }
vadimsh@chromium.orgf24e5c32013-10-11 21:16:21 +0000153 if self._isolate_item:
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500154 request['data'].append(
maruel@chromium.org814d23f2013-10-01 19:08:00 +0000155 [
Vadim Shtayurabcff74f2014-02-27 16:19:34 -0800156 self.storage.get_fetch_url(self._isolate_item),
maruel@chromium.org814d23f2013-10-01 19:08:00 +0000157 'swarm_data.zip',
158 ])
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500159 return json.dumps(request, sort_keys=True, separators=(',',':'))
maruel@chromium.org0437a732013-08-27 16:05:52 +0000160
Marc-Antoine Ruelcd629732013-12-20 15:00:42 -0500161 @property
162 def isolate_item(self):
163 """Calling this property 'closes' the manifest and it can't be modified
164 afterward.
165 """
166 if self._isolate_item is None:
167 self._isolate_item = isolateserver.BufferItem(
Vadim Shtayurabcff74f2014-02-27 16:19:34 -0800168 self.bundle.zip_into_buffer(), high_priority=True)
Marc-Antoine Ruelcd629732013-12-20 15:00:42 -0500169 return self._isolate_item
170
171
172def zip_and_upload(manifest):
173 """Zips up all the files necessary to run a manifest and uploads to Swarming
174 master.
175 """
176 try:
177 start_time = time.time()
178 with manifest.storage:
179 uploaded = manifest.storage.upload_items([manifest.isolate_item])
180 elapsed = time.time() - start_time
181 except (IOError, OSError) as exc:
182 tools.report_error('Failed to upload the zip file: %s' % exc)
183 return False
184
185 if manifest.isolate_item in uploaded:
186 logging.info('Upload complete, time elapsed: %f', elapsed)
187 else:
188 logging.info('Zip file already on server, time elapsed: %f', elapsed)
189 return True
190
maruel@chromium.org0437a732013-08-27 16:05:52 +0000191
192def now():
193 """Exists so it can be mocked easily."""
194 return time.time()
195
196
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500197def get_task_keys(swarm_base_url, task_name):
198 """Returns the Swarming task key for each shards of task_name."""
199 key_data = urllib.urlencode([('name', task_name)])
maruel@chromium.org0437a732013-08-27 16:05:52 +0000200 url = '%s/get_matching_test_cases?%s' % (swarm_base_url, key_data)
201
vadimsh@chromium.org043b76d2013-09-12 16:15:13 +0000202 for _ in net.retry_loop(max_attempts=net.URL_OPEN_MAX_ATTEMPTS):
203 result = net.url_read(url, retry_404=True)
204 if result is None:
maruel@chromium.org0437a732013-08-27 16:05:52 +0000205 raise Failure(
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500206 'Error: Unable to find any task with the name, %s, on swarming server'
207 % task_name)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000208
maruel@chromium.org0437a732013-08-27 16:05:52 +0000209 # TODO(maruel): Compare exact string.
210 if 'No matching' in result:
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500211 logging.warning('Unable to find any task with the name, %s, on swarming '
212 'server' % task_name)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000213 continue
214 return json.loads(result)
215
216 raise Failure(
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500217 'Error: Unable to find any task with the name, %s, on swarming server'
218 % task_name)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000219
220
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500221def retrieve_results(base_url, task_key, timeout, should_stop):
222 """Retrieves results for a single task_key."""
maruel@chromium.org814d23f2013-10-01 19:08:00 +0000223 assert isinstance(timeout, float), timeout
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500224 params = [('r', task_key)]
maruel@chromium.org0437a732013-08-27 16:05:52 +0000225 result_url = '%s/get_result?%s' % (base_url, urllib.urlencode(params))
226 start = now()
227 while True:
228 if timeout and (now() - start) >= timeout:
229 logging.error('retrieve_results(%s) timed out', base_url)
230 return {}
231 # Do retries ourselves.
vadimsh@chromium.org043b76d2013-09-12 16:15:13 +0000232 response = net.url_read(result_url, retry_404=False, retry_50x=False)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000233 if response is None:
234 # Aggressively poll for results. Do not use retry_404 so
235 # should_stop is polled more often.
236 remaining = min(5, timeout - (now() - start)) if timeout else 5
237 if remaining > 0:
maruel@chromium.org814d23f2013-10-01 19:08:00 +0000238 if should_stop.get():
239 return {}
vadimsh@chromium.org043b76d2013-09-12 16:15:13 +0000240 net.sleep_before_retry(1, remaining)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000241 else:
242 try:
vadimsh@chromium.org043b76d2013-09-12 16:15:13 +0000243 data = json.loads(response) or {}
maruel@chromium.org0437a732013-08-27 16:05:52 +0000244 except (ValueError, TypeError):
245 logging.warning(
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500246 'Received corrupted data for task_key %s. Retrying.', task_key)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000247 else:
248 if data['output']:
249 return data
250 if should_stop.get():
251 return {}
252
253
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700254def yield_results(
255 swarm_base_url, task_keys, timeout, max_threads, print_status_updates):
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500256 """Yields swarming task results from the swarming server as (index, result).
maruel@chromium.org0437a732013-08-27 16:05:52 +0000257
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700258 Duplicate shards are ignored. Shards are yielded in order of completion.
259 Timed out shards are NOT yielded at all. Caller can compare number of yielded
260 shards with len(task_keys) to verify all shards completed.
maruel@chromium.org0437a732013-08-27 16:05:52 +0000261
262 max_threads is optional and is used to limit the number of parallel fetches
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500263 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 +0000264 worth normally to limit the number threads. Mostly used for testing purposes.
Marc-Antoine Ruel5c720342014-02-21 14:46:14 -0500265
266 Yields:
267 (index, result). In particular, 'result' is defined as the
268 GetRunnerResults() function in services/swarming/server/test_runner.py.
maruel@chromium.org0437a732013-08-27 16:05:52 +0000269 """
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500270 shards_remaining = range(len(task_keys))
maruel@chromium.org0437a732013-08-27 16:05:52 +0000271 number_threads = (
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500272 min(max_threads, len(task_keys)) if max_threads else len(task_keys))
maruel@chromium.org0437a732013-08-27 16:05:52 +0000273 should_stop = threading_utils.Bit()
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700274 results_channel = threading_utils.TaskChannel()
275 active_task_count = len(task_keys)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000276 with threading_utils.ThreadPool(number_threads, number_threads, 0) as pool:
277 try:
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500278 for task_key in task_keys:
maruel@chromium.org0437a732013-08-27 16:05:52 +0000279 pool.add_task(
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700280 0, results_channel.wrap_task(retrieve_results),
281 swarm_base_url, task_key, timeout, should_stop)
282 while active_task_count:
283 try:
284 result = results_channel.pull(timeout=STATUS_UPDATE_INTERVAL)
285 except threading_utils.TaskChannel.Timeout:
286 if print_status_updates:
287 print(
288 'Waiting for results from the following shards: %s' %
289 ', '.join(map(str, shards_remaining)))
290 sys.stdout.flush()
291 continue
292 except Exception:
293 logging.exception('Unexpected exception in retrieve_results')
294 result = None
295 active_task_count -= 1
maruel@chromium.org0437a732013-08-27 16:05:52 +0000296 if not result:
297 # Failed to retrieve one key.
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500298 logging.error('Failed to retrieve the results for a swarming key')
maruel@chromium.org0437a732013-08-27 16:05:52 +0000299 continue
300 shard_index = result['config_instance_index']
301 if shard_index in shards_remaining:
302 shards_remaining.remove(shard_index)
303 yield shard_index, result
304 else:
305 logging.warning('Ignoring duplicate shard index %d', shard_index)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000306 finally:
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700307 # Done or aborted with Ctrl+C, kill the remaining threads.
maruel@chromium.org0437a732013-08-27 16:05:52 +0000308 should_stop.set()
309
310
311def chromium_setup(manifest):
312 """Sets up the commands to run.
313
314 Highly chromium specific.
315 """
vadimsh@chromium.org6b706212013-08-28 15:03:46 +0000316 # Add uncompressed zip here. It'll be compressed as part of the package sent
317 # to Swarming server.
318 run_test_name = 'run_isolated.zip'
319 manifest.bundle.add_buffer(run_test_name,
320 run_isolated.get_as_zip_package().zip_into_buffer(compress=False))
maruel@chromium.org0437a732013-08-27 16:05:52 +0000321
vadimsh@chromium.org6b706212013-08-28 15:03:46 +0000322 cleanup_script_name = 'swarm_cleanup.py'
323 manifest.bundle.add_file(os.path.join(TOOLS_PATH, cleanup_script_name),
324 cleanup_script_name)
325
maruel@chromium.org0437a732013-08-27 16:05:52 +0000326 run_cmd = [
327 'python', run_test_name,
maruel@chromium.org814d23f2013-10-01 19:08:00 +0000328 '--hash', manifest.isolated_hash,
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500329 '--namespace', manifest.namespace,
maruel@chromium.org0437a732013-08-27 16:05:52 +0000330 ]
Marc-Antoine Ruel8806e622014-02-12 14:15:53 -0500331 if file_path.is_url(manifest.isolate_server):
332 run_cmd.extend(('--isolate-server', manifest.isolate_server))
333 else:
334 run_cmd.extend(('--indir', manifest.isolate_server))
335
maruel@chromium.org0437a732013-08-27 16:05:52 +0000336 if manifest.verbose or manifest.profile:
337 # Have it print the profiling section.
338 run_cmd.append('--verbose')
339 manifest.add_task('Run Test', run_cmd)
340
341 # Clean up
342 manifest.add_task('Clean Up', ['python', cleanup_script_name])
343
344
Marc-Antoine Ruelcd629732013-12-20 15:00:42 -0500345def googletest_setup(env, shards):
346 """Sets googletest specific environment variables."""
347 if shards > 1:
348 env = env.copy()
349 env['GTEST_SHARD_INDEX'] = '%(instance_index)s'
350 env['GTEST_TOTAL_SHARDS'] = '%(num_instances)s'
351 return env
352
353
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500354def archive(isolate_server, namespace, isolated, algo, verbose):
maruel@chromium.org0437a732013-08-27 16:05:52 +0000355 """Archives a .isolated and all the dependencies on the CAC."""
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500356 logging.info('archive(%s, %s, %s)', isolate_server, namespace, isolated)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000357 tempdir = None
Marc-Antoine Ruel8806e622014-02-12 14:15:53 -0500358 if file_path.is_url(isolate_server):
359 command = 'archive'
360 flag = '--isolate-server'
361 else:
362 command = 'hashtable'
363 flag = '--outdir'
364
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500365 print('Archiving: %s' % isolated)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000366 try:
maruel@chromium.org0437a732013-08-27 16:05:52 +0000367 cmd = [
368 sys.executable,
369 os.path.join(ROOT_DIR, 'isolate.py'),
Marc-Antoine Ruel8806e622014-02-12 14:15:53 -0500370 command,
371 flag, isolate_server,
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500372 '--namespace', namespace,
maruel@chromium.org0437a732013-08-27 16:05:52 +0000373 '--isolated', isolated,
374 ]
maruel@chromium.orge9403ab2013-09-20 18:03:49 +0000375 cmd.extend(['--verbose'] * verbose)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000376 logging.info(' '.join(cmd))
377 if subprocess.call(cmd, verbose):
378 return
maruel@chromium.org7b844a62013-09-17 13:04:59 +0000379 return isolateserver.hash_file(isolated, algo)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000380 finally:
381 if tempdir:
382 shutil.rmtree(tempdir)
383
384
385def process_manifest(
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500386 swarming, isolate_server, namespace, isolated_hash, task_name, shards,
Marc-Antoine Ruel13b7b782014-03-14 11:14:57 -0400387 dimensions, env, working_dir, deadline, verbose, profile, priority):
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500388 """Processes the manifest file and send off the swarming task request."""
maruel@chromium.org0437a732013-08-27 16:05:52 +0000389 try:
390 manifest = Manifest(
Marc-Antoine Ruela7049872013-11-05 19:28:35 -0500391 isolate_server=isolate_server,
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500392 namespace=namespace,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500393 isolated_hash=isolated_hash,
394 task_name=task_name,
Marc-Antoine Ruela7049872013-11-05 19:28:35 -0500395 shards=shards,
Marc-Antoine Ruel92f32422013-11-06 18:12:13 -0500396 dimensions=dimensions,
Marc-Antoine Ruel05dab5e2013-11-06 15:06:47 -0500397 env=env,
Marc-Antoine Ruela7049872013-11-05 19:28:35 -0500398 working_dir=working_dir,
Marc-Antoine Ruel13b7b782014-03-14 11:14:57 -0400399 deadline=deadline,
Marc-Antoine Ruela7049872013-11-05 19:28:35 -0500400 verbose=verbose,
401 profile=profile,
Vadim Shtayurabcff74f2014-02-27 16:19:34 -0800402 priority=priority)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000403 except ValueError as e:
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500404 tools.report_error('Unable to process %s: %s' % (task_name, e))
maruel@chromium.org0437a732013-08-27 16:05:52 +0000405 return 1
406
407 chromium_setup(manifest)
408
Marc-Antoine Ruelcd629732013-12-20 15:00:42 -0500409 logging.info('Zipping up files...')
410 if not zip_and_upload(manifest):
maruel@chromium.org0437a732013-08-27 16:05:52 +0000411 return 1
412
Marc-Antoine Ruelcd629732013-12-20 15:00:42 -0500413 logging.info('Server: %s', swarming)
414 logging.info('Task name: %s', task_name)
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500415 trigger_url = swarming + '/test'
maruel@chromium.org0437a732013-08-27 16:05:52 +0000416 manifest_text = manifest.to_json()
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500417 result = net.url_read(trigger_url, data={'request': manifest_text})
maruel@chromium.org0437a732013-08-27 16:05:52 +0000418 if not result:
vadimsh@chromium.orgd908a542013-10-30 01:36:17 +0000419 tools.report_error(
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500420 'Failed to trigger task %s\n%s' % (task_name, trigger_url))
maruel@chromium.org0437a732013-08-27 16:05:52 +0000421 return 1
422 try:
vadimsh@chromium.orgf24e5c32013-10-11 21:16:21 +0000423 json.loads(result)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000424 except (ValueError, TypeError) as e:
vadimsh@chromium.orgd908a542013-10-30 01:36:17 +0000425 msg = '\n'.join((
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500426 'Failed to trigger task %s' % task_name,
vadimsh@chromium.orgd908a542013-10-30 01:36:17 +0000427 'Manifest: %s' % manifest_text,
428 'Bad response: %s' % result,
429 str(e)))
430 tools.report_error(msg)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000431 return 1
432 return 0
433
434
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500435def isolated_to_hash(isolate_server, namespace, arg, algo, verbose):
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500436 """Archives a .isolated file if needed.
437
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500438 Returns the file hash to trigger and a bool specifying if it was a file (True)
439 or a hash (False).
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500440 """
441 if arg.endswith('.isolated'):
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500442 file_hash = archive(isolate_server, namespace, arg, algo, verbose)
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500443 if not file_hash:
444 tools.report_error('Archival failure %s' % arg)
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500445 return None, True
446 return file_hash, True
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500447 elif isolateserver.is_valid_hash(arg, algo):
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500448 return arg, False
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500449 else:
450 tools.report_error('Invalid hash %s' % arg)
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500451 return None, False
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500452
453
maruel@chromium.org0437a732013-08-27 16:05:52 +0000454def trigger(
Marc-Antoine Ruela7049872013-11-05 19:28:35 -0500455 swarming,
456 isolate_server,
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500457 namespace,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500458 file_hash_or_isolated,
459 task_name,
460 shards,
Marc-Antoine Ruel92f32422013-11-06 18:12:13 -0500461 dimensions,
462 env,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500463 working_dir,
Marc-Antoine Ruel13b7b782014-03-14 11:14:57 -0400464 deadline,
maruel@chromium.org0437a732013-08-27 16:05:52 +0000465 verbose,
466 profile,
467 priority):
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500468 """Sends off the hash swarming task requests."""
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500469 file_hash, is_file = isolated_to_hash(
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500470 isolate_server, namespace, file_hash_or_isolated, hashlib.sha1, verbose)
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500471 if not file_hash:
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500472 return 1, ''
473 if not task_name:
474 # If a file name was passed, use its base name of the isolated hash.
475 # Otherwise, use user name as an approximation of a task name.
476 if is_file:
477 key = os.path.splitext(os.path.basename(file_hash_or_isolated))[0]
478 else:
479 key = getpass.getuser()
Vadim Shtayurac3d97b02014-04-26 19:16:05 -0700480 task_name = '%s/%s/%s/%d' % (
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500481 key,
482 '_'.join('%s=%s' % (k, v) for k, v in sorted(dimensions.iteritems())),
Vadim Shtayurac3d97b02014-04-26 19:16:05 -0700483 file_hash,
484 now() * 1000)
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500485
Marc-Antoine Ruelcd629732013-12-20 15:00:42 -0500486 env = googletest_setup(env, shards)
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500487 # TODO(maruel): It should first create a request manifest object, then pass
488 # it to a function to zip, archive and trigger.
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500489 result = process_manifest(
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500490 swarming=swarming,
491 isolate_server=isolate_server,
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500492 namespace=namespace,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500493 isolated_hash=file_hash,
494 task_name=task_name,
495 shards=shards,
496 dimensions=dimensions,
Marc-Antoine Ruel13b7b782014-03-14 11:14:57 -0400497 deadline=deadline,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500498 env=env,
499 working_dir=working_dir,
500 verbose=verbose,
501 profile=profile,
Vadim Shtayurabcff74f2014-02-27 16:19:34 -0800502 priority=priority)
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500503 return result, task_name
maruel@chromium.org0437a732013-08-27 16:05:52 +0000504
505
506def decorate_shard_output(result, shard_exit_code):
507 """Returns wrapped output for swarming task shard."""
508 tag = 'index %s (machine tag: %s, id: %s)' % (
509 result['config_instance_index'],
510 result['machine_id'],
511 result.get('machine_tag', 'unknown'))
512 return (
513 '\n'
514 '================================================================\n'
515 'Begin output from shard %s\n'
516 '================================================================\n'
517 '\n'
518 '%s'
519 '================================================================\n'
520 'End output from shard %s. Return %d\n'
521 '================================================================\n'
522 ) % (tag, result['output'] or NO_OUTPUT_FOUND, tag, shard_exit_code)
523
524
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700525def collect(url, task_name, timeout, decorate, print_status_updates):
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500526 """Retrieves results of a Swarming task."""
527 logging.info('Collecting %s', task_name)
528 task_keys = get_task_keys(url, task_name)
529 if not task_keys:
530 raise Failure('No task keys to get results with.')
maruel@chromium.org0437a732013-08-27 16:05:52 +0000531
maruel@chromium.org9c1c7b52013-08-28 19:04:36 +0000532 exit_code = None
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700533 seen_shards = set()
534 for index, output in yield_results(
535 url, task_keys, timeout, None, print_status_updates):
536 seen_shards.add(index)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000537 shard_exit_codes = (output['exit_codes'] or '1').split(',')
538 shard_exit_code = max(int(i) for i in shard_exit_codes)
539 if decorate:
540 print decorate_shard_output(output, shard_exit_code)
541 else:
542 print(
543 '%s/%s: %s' % (
544 output['machine_id'],
545 output['machine_tag'],
546 output['exit_codes']))
547 print(''.join(' %s\n' % l for l in output['output'].splitlines()))
maruel@chromium.org9c1c7b52013-08-28 19:04:36 +0000548 exit_code = exit_code or shard_exit_code
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700549 if len(seen_shards) != len(task_keys):
550 missing_shards = [x for x in range(len(task_keys)) if x not in seen_shards]
551 print >> sys.stderr, ('Results from some shards are missing: %s' %
552 ', '.join(map(str, missing_shards)))
553 exit_code = exit_code or 1
maruel@chromium.org9c1c7b52013-08-28 19:04:36 +0000554 return exit_code if exit_code is not None else 1
maruel@chromium.org0437a732013-08-27 16:05:52 +0000555
556
Marc-Antoine Ruel819fb162014-03-12 16:38:26 -0400557def add_filter_options(parser):
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500558 parser.filter_group = tools.optparse.OptionGroup(parser, 'Filtering slaves')
559 parser.filter_group.add_option(
Marc-Antoine Ruelb39e8cf2014-01-20 10:39:31 -0500560 '-d', '--dimension', default=[], action='append', nargs=2,
Marc-Antoine Ruel92f32422013-11-06 18:12:13 -0500561 dest='dimensions', metavar='FOO bar',
562 help='dimension to filter on')
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500563 parser.add_option_group(parser.filter_group)
564
Marc-Antoine Ruel819fb162014-03-12 16:38:26 -0400565
566def add_trigger_options(parser):
567 """Adds all options to trigger a task on Swarming."""
568 isolateserver.add_isolate_server_options(parser, True)
569 add_filter_options(parser)
570
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500571 parser.task_group = tools.optparse.OptionGroup(parser, 'Task properties')
572 parser.task_group.add_option(
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500573 '-w', '--working-dir', default='swarm_tests',
574 help='Working directory on the swarming slave side. default: %default.')
575 parser.task_group.add_option(
576 '--working_dir', help=tools.optparse.SUPPRESS_HELP)
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500577 parser.task_group.add_option(
578 '-e', '--env', default=[], action='append', nargs=2, metavar='FOO bar',
579 help='environment variables to set')
580 parser.task_group.add_option(
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500581 '--priority', type='int', default=100,
582 help='The lower value, the more important the task is')
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500583 parser.task_group.add_option(
584 '--shards', type='int', default=1, help='number of shards to use')
585 parser.task_group.add_option(
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500586 '-T', '--task-name',
587 help='Display name of the task. It uniquely identifies the task. '
Vadim Shtayurac3d97b02014-04-26 19:16:05 -0700588 'Defaults to <base_name>/<dimensions>/<isolated hash>/<timestamp> '
589 'if an isolated file is provided, if a hash is provided, it '
590 'defaults to <user>/<dimensions>/<isolated hash>/<timestamp>')
Marc-Antoine Ruel13b7b782014-03-14 11:14:57 -0400591 parser.task_group.add_option(
592 '--deadline', type='int', default=6*60*60,
593 help='Seconds to allow the task to be pending for a bot to run before '
594 'this task request expires.')
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500595 parser.add_option_group(parser.task_group)
Marc-Antoine Ruelcd629732013-12-20 15:00:42 -0500596 # TODO(maruel): This is currently written in a chromium-specific way.
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500597 parser.group_logging.add_option(
maruel@chromium.org0437a732013-08-27 16:05:52 +0000598 '--profile', action='store_true',
599 default=bool(os.environ.get('ISOLATE_DEBUG')),
600 help='Have run_isolated.py print profiling info')
maruel@chromium.org0437a732013-08-27 16:05:52 +0000601
602
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500603def process_trigger_options(parser, options, args):
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500604 isolateserver.process_isolate_server_options(parser, options)
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500605 if len(args) != 1:
606 parser.error('Must pass one .isolated file or its hash (sha1).')
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500607 options.dimensions = dict(options.dimensions)
Marc-Antoine Ruel2d1bee82014-03-12 14:10:25 -0400608 if not options.dimensions:
609 parser.error('Please at least specify one --dimension')
maruel@chromium.org0437a732013-08-27 16:05:52 +0000610
611
612def add_collect_options(parser):
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500613 parser.server_group.add_option(
maruel@chromium.org0437a732013-08-27 16:05:52 +0000614 '-t', '--timeout',
615 type='float',
616 default=DEFAULT_SHARD_WAIT_TIME,
617 help='Timeout to wait for result, set to 0 for no timeout; default: '
618 '%default s')
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500619 parser.group_logging.add_option(
620 '--decorate', action='store_true', help='Decorate output')
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700621 parser.group_logging.add_option(
622 '--print-status-updates', action='store_true',
623 help='Print periodic status updates')
maruel@chromium.org0437a732013-08-27 16:05:52 +0000624
625
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500626@subcommand.usage('task_name')
maruel@chromium.org0437a732013-08-27 16:05:52 +0000627def CMDcollect(parser, args):
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500628 """Retrieves results of a Swarming task.
maruel@chromium.org0437a732013-08-27 16:05:52 +0000629
630 The result can be in multiple part if the execution was sharded. It can
631 potentially have retries.
632 """
633 add_collect_options(parser)
634 (options, args) = parser.parse_args(args)
635 if not args:
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500636 parser.error('Must specify one task name.')
maruel@chromium.org0437a732013-08-27 16:05:52 +0000637 elif len(args) > 1:
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500638 parser.error('Must specify only one task name.')
maruel@chromium.org0437a732013-08-27 16:05:52 +0000639
640 try:
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700641 return collect(
642 options.swarming,
643 args[0],
644 options.timeout,
645 options.decorate,
646 options.print_status_updates)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000647 except Failure as e:
vadimsh@chromium.orgd908a542013-10-30 01:36:17 +0000648 tools.report_error(e)
649 return 1
maruel@chromium.org0437a732013-08-27 16:05:52 +0000650
651
Marc-Antoine Ruel819fb162014-03-12 16:38:26 -0400652def CMDquery(parser, args):
653 """Returns information about the bots connected to the Swarming server."""
654 add_filter_options(parser)
655 parser.filter_group.add_option(
Marc-Antoine Ruel28083112014-03-13 16:34:04 -0400656 '--dead-only', action='store_true',
657 help='Only print dead bots, useful to reap them and reimage broken bots')
658 parser.filter_group.add_option(
Marc-Antoine Ruel819fb162014-03-12 16:38:26 -0400659 '-k', '--keep-dead', action='store_true',
660 help='Do not filter out dead bots')
661 parser.filter_group.add_option(
662 '-b', '--bare', action='store_true',
Marc-Antoine Ruele7b00162014-03-12 16:59:01 -0400663 help='Do not print out dimensions')
Marc-Antoine Ruel819fb162014-03-12 16:38:26 -0400664 options, args = parser.parse_args(args)
Marc-Antoine Ruel28083112014-03-13 16:34:04 -0400665
666 if options.keep_dead and options.dead_only:
667 parser.error('Use only one of --keep-dead and --dead-only')
Marc-Antoine Ruel819fb162014-03-12 16:38:26 -0400668 service = net.get_http_service(options.swarming)
669 data = service.json_request('GET', '/swarming/api/v1/bots')
670 if data is None:
671 print >> sys.stderr, 'Failed to access %s' % options.swarming
672 return 1
673 timeout = datetime.timedelta(seconds=data['machine_death_timeout'])
674 utcnow = datetime.datetime.utcnow()
675 for machine in natsort.natsorted(data['machines'], key=lambda x: x['tag']):
676 last_seen = datetime.datetime.strptime(
677 machine['last_seen'], '%Y-%m-%d %H:%M:%S')
Marc-Antoine Ruel28083112014-03-13 16:34:04 -0400678 is_dead = utcnow - last_seen > timeout
679 if options.dead_only:
680 if not is_dead:
681 continue
682 elif not options.keep_dead and is_dead:
Marc-Antoine Ruel819fb162014-03-12 16:38:26 -0400683 continue
684
Marc-Antoine Ruele7b00162014-03-12 16:59:01 -0400685 # If the user requested to filter on dimensions, ensure the bot has all the
686 # dimensions requested.
Marc-Antoine Ruel819fb162014-03-12 16:38:26 -0400687 dimensions = machine['dimensions']
688 for key, value in options.dimensions:
689 if key not in dimensions:
690 break
Marc-Antoine Ruele7b00162014-03-12 16:59:01 -0400691 # A bot can have multiple value for a key, for example,
692 # {'os': ['Windows', 'Windows-6.1']}, so that --dimension os=Windows will
693 # be accepted.
Marc-Antoine Ruel819fb162014-03-12 16:38:26 -0400694 if isinstance(dimensions[key], list):
695 if value not in dimensions[key]:
696 break
697 else:
698 if value != dimensions[key]:
699 break
700 else:
701 print machine['tag']
Marc-Antoine Ruele7b00162014-03-12 16:59:01 -0400702 if not options.bare:
Marc-Antoine Ruel819fb162014-03-12 16:38:26 -0400703 print ' %s' % dimensions
704 return 0
705
706
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500707@subcommand.usage('[hash|isolated]')
maruel@chromium.org0437a732013-08-27 16:05:52 +0000708def CMDrun(parser, args):
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500709 """Triggers a task and wait for the results.
maruel@chromium.org0437a732013-08-27 16:05:52 +0000710
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500711 Basically, does everything to run a command remotely.
maruel@chromium.org0437a732013-08-27 16:05:52 +0000712 """
713 add_trigger_options(parser)
714 add_collect_options(parser)
715 options, args = parser.parse_args(args)
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500716 process_trigger_options(parser, options, args)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000717
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500718 try:
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500719 result, task_name = trigger(
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500720 swarming=options.swarming,
Marc-Antoine Ruel8806e622014-02-12 14:15:53 -0500721 isolate_server=options.isolate_server or options.indir,
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500722 namespace=options.namespace,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500723 file_hash_or_isolated=args[0],
724 task_name=options.task_name,
725 shards=options.shards,
726 dimensions=options.dimensions,
727 env=dict(options.env),
728 working_dir=options.working_dir,
Marc-Antoine Ruel13b7b782014-03-14 11:14:57 -0400729 deadline=options.deadline,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500730 verbose=options.verbose,
731 profile=options.profile,
732 priority=options.priority)
733 except Failure as e:
734 tools.report_error(
735 'Failed to trigger %s(%s): %s' %
736 (options.task_name, args[0], e.args[0]))
737 return 1
738 if result:
739 tools.report_error('Failed to trigger the task.')
maruel@chromium.org0437a732013-08-27 16:05:52 +0000740 return result
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500741 if task_name != options.task_name:
742 print('Triggered task: %s' % task_name)
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500743 try:
744 return collect(
745 options.swarming,
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500746 task_name,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500747 options.timeout,
Vadim Shtayura86a2cef2014-04-18 11:13:39 -0700748 options.decorate,
749 options.print_status_updates)
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500750 except Failure as e:
751 tools.report_error(e)
752 return 1
maruel@chromium.org0437a732013-08-27 16:05:52 +0000753
754
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500755@subcommand.usage("(hash|isolated)")
maruel@chromium.org0437a732013-08-27 16:05:52 +0000756def CMDtrigger(parser, args):
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500757 """Triggers a Swarming task.
maruel@chromium.org0437a732013-08-27 16:05:52 +0000758
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500759 Accepts either the hash (sha1) of a .isolated file already uploaded or the
760 path to an .isolated file to archive, packages it if needed and sends a
761 Swarming manifest file to the Swarming server.
762
763 If an .isolated file is specified instead of an hash, it is first archived.
maruel@chromium.org0437a732013-08-27 16:05:52 +0000764 """
765 add_trigger_options(parser)
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500766 options, args = parser.parse_args(args)
767 process_trigger_options(parser, options, args)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000768
769 try:
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500770 result, task_name = trigger(
Marc-Antoine Ruela7049872013-11-05 19:28:35 -0500771 swarming=options.swarming,
Marc-Antoine Ruel8806e622014-02-12 14:15:53 -0500772 isolate_server=options.isolate_server or options.indir,
Marc-Antoine Ruel1687b5e2014-02-06 17:47:53 -0500773 namespace=options.namespace,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500774 file_hash_or_isolated=args[0],
775 task_name=options.task_name,
776 dimensions=options.dimensions,
777 shards=options.shards,
Marc-Antoine Ruel92f32422013-11-06 18:12:13 -0500778 env=dict(options.env),
Marc-Antoine Ruela7049872013-11-05 19:28:35 -0500779 working_dir=options.working_dir,
Marc-Antoine Ruel13b7b782014-03-14 11:14:57 -0400780 deadline=options.deadline,
Marc-Antoine Ruela7049872013-11-05 19:28:35 -0500781 verbose=options.verbose,
782 profile=options.profile,
783 priority=options.priority)
Marc-Antoine Ruel5b475782014-02-14 20:57:59 -0500784 if task_name != options.task_name and not result:
785 print('Triggered task: %s' % task_name)
786 return result
maruel@chromium.org0437a732013-08-27 16:05:52 +0000787 except Failure as e:
vadimsh@chromium.orgd908a542013-10-30 01:36:17 +0000788 tools.report_error(e)
789 return 1
maruel@chromium.org0437a732013-08-27 16:05:52 +0000790
791
792class OptionParserSwarming(tools.OptionParserWithLogging):
793 def __init__(self, **kwargs):
794 tools.OptionParserWithLogging.__init__(
795 self, prog='swarming.py', **kwargs)
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500796 self.server_group = tools.optparse.OptionGroup(self, 'Server')
797 self.server_group.add_option(
maruel@chromium.orge9403ab2013-09-20 18:03:49 +0000798 '-S', '--swarming',
Kevin Graney5346c162014-01-24 12:20:01 -0500799 metavar='URL', default=os.environ.get('SWARMING_SERVER', ''),
maruel@chromium.orge9403ab2013-09-20 18:03:49 +0000800 help='Swarming server to use')
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500801 self.add_option_group(self.server_group)
Vadim Shtayurae34e13a2014-02-02 11:23:26 -0800802 auth.add_auth_options(self)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000803
804 def parse_args(self, *args, **kwargs):
805 options, args = tools.OptionParserWithLogging.parse_args(
806 self, *args, **kwargs)
807 options.swarming = options.swarming.rstrip('/')
808 if not options.swarming:
809 self.error('--swarming is required.')
Vadim Shtayura5d1efce2014-02-04 10:55:43 -0800810 auth.process_auth_options(self, options)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000811 return options, args
812
813
814def main(args):
815 dispatcher = subcommand.CommandDispatcher(__name__)
816 try:
817 return dispatcher.execute(OptionParserSwarming(version=__version__), args)
vadimsh@chromium.orgd908a542013-10-30 01:36:17 +0000818 except Exception as e:
819 tools.report_error(e)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000820 return 1
821
822
823if __name__ == '__main__':
824 fix_encoding.fix_encoding()
825 tools.disable_buffering()
826 colorama.init()
827 sys.exit(main(sys.argv[1:]))