blob: 283f8f7843218348a45d4727038b22dc69bf6570 [file] [log] [blame]
maruel@chromium.org0437a732013-08-27 16:05:52 +00001#!/usr/bin/env python
Marc-Antoine Ruel8add1242013-11-05 17:28:27 -05002# Copyright 2013 The Swarming Authors. All rights reserved.
Marc-Antoine Ruele98b1122013-11-05 20:27:57 -05003# Use of this source code is governed under the Apache License, Version 2.0 that
4# can be found in the LICENSE file.
maruel@chromium.org0437a732013-08-27 16:05:52 +00005
6"""Client tool to trigger tasks or retrieve results from a Swarming server."""
7
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -05008__version__ = '0.3'
maruel@chromium.org0437a732013-08-27 16:05:52 +00009
10import hashlib
11import json
12import logging
13import os
maruel@chromium.org0437a732013-08-27 16:05:52 +000014import shutil
maruel@chromium.org0437a732013-08-27 16:05:52 +000015import subprocess
16import sys
17import time
18import urllib
maruel@chromium.org0437a732013-08-27 16:05:52 +000019
20from third_party import colorama
21from third_party.depot_tools import fix_encoding
22from third_party.depot_tools import subcommand
vadimsh@chromium.org6b706212013-08-28 15:03:46 +000023
24from utils import net
maruel@chromium.org0437a732013-08-27 16:05:52 +000025from utils import threading_utils
vadimsh@chromium.org6b706212013-08-28 15:03:46 +000026from utils import tools
27from utils import zip_package
maruel@chromium.org0437a732013-08-27 16:05:52 +000028
maruel@chromium.org7b844a62013-09-17 13:04:59 +000029import isolateserver
maruel@chromium.org0437a732013-08-27 16:05:52 +000030import run_isolated
31
32
33ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
34TOOLS_PATH = os.path.join(ROOT_DIR, 'tools')
35
36
maruel@chromium.org0437a732013-08-27 16:05:52 +000037# The default time to wait for a shard to finish running.
csharp@chromium.org24758492013-08-28 19:10:54 +000038DEFAULT_SHARD_WAIT_TIME = 80 * 60.
maruel@chromium.org0437a732013-08-27 16:05:52 +000039
40
41NO_OUTPUT_FOUND = (
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -050042 'No output produced by the task, it may have failed to run.\n'
maruel@chromium.org0437a732013-08-27 16:05:52 +000043 '\n')
44
45
maruel@chromium.orge9403ab2013-09-20 18:03:49 +000046# TODO(maruel): cygwin != Windows. If a swarm_bot is running in cygwin, it's
47# different from running in native python.
48PLATFORM_MAPPING_SWARMING = {
maruel@chromium.org0437a732013-08-27 16:05:52 +000049 'cygwin': 'Windows',
50 'darwin': 'Mac',
51 'linux2': 'Linux',
52 'win32': 'Windows',
53}
54
maruel@chromium.orge9403ab2013-09-20 18:03:49 +000055PLATFORM_MAPPING_ISOLATE = {
56 'linux2': 'linux',
57 'darwin': 'mac',
58 'win32': 'win',
59}
60
maruel@chromium.org0437a732013-08-27 16:05:52 +000061
62class Failure(Exception):
63 """Generic failure."""
64 pass
65
66
67class Manifest(object):
68 """Represents a Swarming task manifest.
69
70 Also includes code to zip code and upload itself.
71 """
72 def __init__(
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -050073 self, isolate_server, isolated_hash, task_name, shards, env,
Marc-Antoine Ruel92f32422013-11-06 18:12:13 -050074 dimensions, working_dir, verbose, profile, priority, algo):
maruel@chromium.org0437a732013-08-27 16:05:52 +000075 """Populates a manifest object.
76 Args:
Marc-Antoine Ruela7049872013-11-05 19:28:35 -050077 isolate_server - isolate server url.
maruel@chromium.org814d23f2013-10-01 19:08:00 +000078 isolated_hash - The manifest's sha-1 that the slave is going to fetch.
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -050079 task_name - The name to give the task request.
80 shards - The number of swarming shards to request.
Marc-Antoine Ruel05dab5e2013-11-06 15:06:47 -050081 env - environment variables to set.
Marc-Antoine Ruel92f32422013-11-06 18:12:13 -050082 dimensions - dimensions to filter the task on.
maruel@chromium.org0437a732013-08-27 16:05:52 +000083 working_dir - Relative working directory to start the script.
maruel@chromium.org0437a732013-08-27 16:05:52 +000084 verbose - if True, have the slave print more details.
85 profile - if True, have the slave print more timing data.
maruel@chromium.org7b844a62013-09-17 13:04:59 +000086 priority - int between 0 and 1000, lower the higher priority.
87 algo - hashing algorithm used.
maruel@chromium.org0437a732013-08-27 16:05:52 +000088 """
Marc-Antoine Ruela7049872013-11-05 19:28:35 -050089 self.isolate_server = isolate_server
90 self.storage = isolateserver.get_storage(isolate_server, 'default')
91
maruel@chromium.org814d23f2013-10-01 19:08:00 +000092 self.isolated_hash = isolated_hash
vadimsh@chromium.org6b706212013-08-28 15:03:46 +000093 self.bundle = zip_package.ZipPackage(ROOT_DIR)
94
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -050095 self._task_name = task_name
maruel@chromium.org0437a732013-08-27 16:05:52 +000096 self._shards = shards
Marc-Antoine Ruel92f32422013-11-06 18:12:13 -050097 self._env = env.copy()
98 self._dimensions = dimensions.copy()
maruel@chromium.org0437a732013-08-27 16:05:52 +000099 self._working_dir = working_dir
100
maruel@chromium.org0437a732013-08-27 16:05:52 +0000101 self.verbose = bool(verbose)
102 self.profile = bool(profile)
103 self.priority = priority
maruel@chromium.org7b844a62013-09-17 13:04:59 +0000104 self._algo = algo
maruel@chromium.org0437a732013-08-27 16:05:52 +0000105
vadimsh@chromium.orgf24e5c32013-10-11 21:16:21 +0000106 self._isolate_item = None
maruel@chromium.org0437a732013-08-27 16:05:52 +0000107 self._tasks = []
maruel@chromium.org0437a732013-08-27 16:05:52 +0000108
109 def add_task(self, task_name, actions, time_out=600):
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500110 """Appends a new task to the swarming manifest file."""
maruel@chromium.org0437a732013-08-27 16:05:52 +0000111 # See swarming/src/common/test_request_message.py TestObject constructor for
112 # the valid flags.
113 self._tasks.append(
114 {
115 'action': actions,
116 'decorate_output': self.verbose,
117 'test_name': task_name,
118 'time_out': time_out,
119 })
120
maruel@chromium.org0437a732013-08-27 16:05:52 +0000121 def zip_and_upload(self):
122 """Zips up all the files necessary to run a shard and uploads to Swarming
123 master.
124 """
vadimsh@chromium.orgf24e5c32013-10-11 21:16:21 +0000125 assert not self._isolate_item
vadimsh@chromium.org6b706212013-08-28 15:03:46 +0000126
maruel@chromium.org0437a732013-08-27 16:05:52 +0000127 start_time = time.time()
vadimsh@chromium.orgf24e5c32013-10-11 21:16:21 +0000128 self._isolate_item = isolateserver.BufferItem(
129 self.bundle.zip_into_buffer(), self._algo, is_isolated=True)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000130 print 'Zipping completed, time elapsed: %f' % (time.time() - start_time)
131
vadimsh@chromium.orgf24e5c32013-10-11 21:16:21 +0000132 try:
133 start_time = time.time()
Vadim Shtayura3172be52013-12-03 12:49:05 -0800134 with self.storage:
135 uploaded = self.storage.upload_items([self._isolate_item])
vadimsh@chromium.orgf24e5c32013-10-11 21:16:21 +0000136 elapsed = time.time() - start_time
137 except (IOError, OSError) as exc:
vadimsh@chromium.orgd908a542013-10-30 01:36:17 +0000138 tools.report_error('Failed to upload the zip file: %s' % exc)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000139 return False
140
vadimsh@chromium.orgf24e5c32013-10-11 21:16:21 +0000141 if self._isolate_item in uploaded:
142 print 'Upload complete, time elapsed: %f' % elapsed
143 else:
144 print 'Zip file already on server, time elapsed: %f' % elapsed
maruel@chromium.org0437a732013-08-27 16:05:52 +0000145
146 return True
147
148 def to_json(self):
149 """Exports the current configuration into a swarm-readable manifest file.
150
151 This function doesn't mutate the object.
152 """
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500153 request = {
Marc-Antoine Ruela7049872013-11-05 19:28:35 -0500154 'cleanup': 'root',
maruel@chromium.org0437a732013-08-27 16:05:52 +0000155 'configurations': [
156 {
Marc-Antoine Ruel5d799192013-11-06 15:20:39 -0500157 'config_name': 'isolated',
Marc-Antoine Ruel92f32422013-11-06 18:12:13 -0500158 'dimensions': self._dimensions,
Marc-Antoine Ruela7049872013-11-05 19:28:35 -0500159 'min_instances': self._shards,
160 'priority': self.priority,
maruel@chromium.org0437a732013-08-27 16:05:52 +0000161 },
162 ],
Marc-Antoine Ruela7049872013-11-05 19:28:35 -0500163 'data': [],
164 # TODO: Let the encoding get set from the command line.
165 'encoding': 'UTF-8',
Marc-Antoine Ruel05dab5e2013-11-06 15:06:47 -0500166 'env_vars': self._env,
maruel@chromium.org0437a732013-08-27 16:05:52 +0000167 'restart_on_failure': True,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500168 'test_case_name': self._task_name,
Marc-Antoine Ruela7049872013-11-05 19:28:35 -0500169 'tests': self._tasks,
170 'working_dir': self._working_dir,
maruel@chromium.org0437a732013-08-27 16:05:52 +0000171 }
vadimsh@chromium.orgf24e5c32013-10-11 21:16:21 +0000172 if self._isolate_item:
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500173 request['data'].append(
maruel@chromium.org814d23f2013-10-01 19:08:00 +0000174 [
vadimsh@chromium.orgf24e5c32013-10-11 21:16:21 +0000175 self.storage.get_fetch_url(self._isolate_item.digest),
maruel@chromium.org814d23f2013-10-01 19:08:00 +0000176 'swarm_data.zip',
177 ])
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500178 return json.dumps(request, sort_keys=True, separators=(',',':'))
maruel@chromium.org0437a732013-08-27 16:05:52 +0000179
180
181def now():
182 """Exists so it can be mocked easily."""
183 return time.time()
184
185
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500186def get_task_keys(swarm_base_url, task_name):
187 """Returns the Swarming task key for each shards of task_name."""
188 key_data = urllib.urlencode([('name', task_name)])
maruel@chromium.org0437a732013-08-27 16:05:52 +0000189 url = '%s/get_matching_test_cases?%s' % (swarm_base_url, key_data)
190
vadimsh@chromium.org043b76d2013-09-12 16:15:13 +0000191 for _ in net.retry_loop(max_attempts=net.URL_OPEN_MAX_ATTEMPTS):
192 result = net.url_read(url, retry_404=True)
193 if result is None:
maruel@chromium.org0437a732013-08-27 16:05:52 +0000194 raise Failure(
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500195 'Error: Unable to find any task with the name, %s, on swarming server'
196 % task_name)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000197
maruel@chromium.org0437a732013-08-27 16:05:52 +0000198 # TODO(maruel): Compare exact string.
199 if 'No matching' in result:
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500200 logging.warning('Unable to find any task with the name, %s, on swarming '
201 'server' % task_name)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000202 continue
203 return json.loads(result)
204
205 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
209
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500210def retrieve_results(base_url, task_key, timeout, should_stop):
211 """Retrieves results for a single task_key."""
maruel@chromium.org814d23f2013-10-01 19:08:00 +0000212 assert isinstance(timeout, float), timeout
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500213 params = [('r', task_key)]
maruel@chromium.org0437a732013-08-27 16:05:52 +0000214 result_url = '%s/get_result?%s' % (base_url, urllib.urlencode(params))
215 start = now()
216 while True:
217 if timeout and (now() - start) >= timeout:
218 logging.error('retrieve_results(%s) timed out', base_url)
219 return {}
220 # Do retries ourselves.
vadimsh@chromium.org043b76d2013-09-12 16:15:13 +0000221 response = net.url_read(result_url, retry_404=False, retry_50x=False)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000222 if response is None:
223 # Aggressively poll for results. Do not use retry_404 so
224 # should_stop is polled more often.
225 remaining = min(5, timeout - (now() - start)) if timeout else 5
226 if remaining > 0:
maruel@chromium.org814d23f2013-10-01 19:08:00 +0000227 if should_stop.get():
228 return {}
vadimsh@chromium.org043b76d2013-09-12 16:15:13 +0000229 net.sleep_before_retry(1, remaining)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000230 else:
231 try:
vadimsh@chromium.org043b76d2013-09-12 16:15:13 +0000232 data = json.loads(response) or {}
maruel@chromium.org0437a732013-08-27 16:05:52 +0000233 except (ValueError, TypeError):
234 logging.warning(
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500235 'Received corrupted data for task_key %s. Retrying.', task_key)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000236 else:
237 if data['output']:
238 return data
239 if should_stop.get():
240 return {}
241
242
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500243def yield_results(swarm_base_url, task_keys, timeout, max_threads):
244 """Yields swarming task results from the swarming server as (index, result).
maruel@chromium.org0437a732013-08-27 16:05:52 +0000245
246 Duplicate shards are ignored, the first one to complete is returned.
247
248 max_threads is optional and is used to limit the number of parallel fetches
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500249 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 +0000250 worth normally to limit the number threads. Mostly used for testing purposes.
251 """
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500252 shards_remaining = range(len(task_keys))
maruel@chromium.org0437a732013-08-27 16:05:52 +0000253 number_threads = (
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500254 min(max_threads, len(task_keys)) if max_threads else len(task_keys))
maruel@chromium.org0437a732013-08-27 16:05:52 +0000255 should_stop = threading_utils.Bit()
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500256 results_remaining = len(task_keys)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000257 with threading_utils.ThreadPool(number_threads, number_threads, 0) as pool:
258 try:
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500259 for task_key in task_keys:
maruel@chromium.org0437a732013-08-27 16:05:52 +0000260 pool.add_task(
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500261 0, retrieve_results, swarm_base_url, task_key, timeout, should_stop)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000262 while shards_remaining and results_remaining:
263 result = pool.get_one_result()
264 results_remaining -= 1
265 if not result:
266 # Failed to retrieve one key.
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500267 logging.error('Failed to retrieve the results for a swarming key')
maruel@chromium.org0437a732013-08-27 16:05:52 +0000268 continue
269 shard_index = result['config_instance_index']
270 if shard_index in shards_remaining:
271 shards_remaining.remove(shard_index)
272 yield shard_index, result
273 else:
274 logging.warning('Ignoring duplicate shard index %d', shard_index)
275 # Pop the last entry, there's no such shard.
276 shards_remaining.pop()
277 finally:
278 # Done, kill the remaining threads.
279 should_stop.set()
280
281
282def chromium_setup(manifest):
283 """Sets up the commands to run.
284
285 Highly chromium specific.
286 """
vadimsh@chromium.org6b706212013-08-28 15:03:46 +0000287 # Add uncompressed zip here. It'll be compressed as part of the package sent
288 # to Swarming server.
289 run_test_name = 'run_isolated.zip'
290 manifest.bundle.add_buffer(run_test_name,
291 run_isolated.get_as_zip_package().zip_into_buffer(compress=False))
maruel@chromium.org0437a732013-08-27 16:05:52 +0000292
vadimsh@chromium.org6b706212013-08-28 15:03:46 +0000293 cleanup_script_name = 'swarm_cleanup.py'
294 manifest.bundle.add_file(os.path.join(TOOLS_PATH, cleanup_script_name),
295 cleanup_script_name)
296
maruel@chromium.org0437a732013-08-27 16:05:52 +0000297 run_cmd = [
298 'python', run_test_name,
maruel@chromium.org814d23f2013-10-01 19:08:00 +0000299 '--hash', manifest.isolated_hash,
maruel@chromium.orgb7e79a22013-09-13 01:24:56 +0000300 '--isolate-server', manifest.isolate_server,
maruel@chromium.org0437a732013-08-27 16:05:52 +0000301 ]
302 if manifest.verbose or manifest.profile:
303 # Have it print the profiling section.
304 run_cmd.append('--verbose')
305 manifest.add_task('Run Test', run_cmd)
306
307 # Clean up
308 manifest.add_task('Clean Up', ['python', cleanup_script_name])
309
310
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500311def archive(isolate_server, isolated, algo, verbose):
maruel@chromium.org0437a732013-08-27 16:05:52 +0000312 """Archives a .isolated and all the dependencies on the CAC."""
313 tempdir = None
314 try:
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500315 logging.info('archive(%s, %s)', isolate_server, isolated)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000316 cmd = [
317 sys.executable,
318 os.path.join(ROOT_DIR, 'isolate.py'),
maruel@chromium.orge9403ab2013-09-20 18:03:49 +0000319 'archive',
maruel@chromium.org0437a732013-08-27 16:05:52 +0000320 '--outdir', isolate_server,
321 '--isolated', isolated,
322 ]
maruel@chromium.orge9403ab2013-09-20 18:03:49 +0000323 cmd.extend(['--verbose'] * verbose)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000324 logging.info(' '.join(cmd))
325 if subprocess.call(cmd, verbose):
326 return
maruel@chromium.org7b844a62013-09-17 13:04:59 +0000327 return isolateserver.hash_file(isolated, algo)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000328 finally:
329 if tempdir:
330 shutil.rmtree(tempdir)
331
332
333def process_manifest(
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500334 swarming, isolate_server, isolated_hash, task_name, shards,
Marc-Antoine Ruel92f32422013-11-06 18:12:13 -0500335 dimensions, env, working_dir, verbose, profile, priority, algo):
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500336 """Processes the manifest file and send off the swarming task request."""
maruel@chromium.org0437a732013-08-27 16:05:52 +0000337 try:
338 manifest = Manifest(
Marc-Antoine Ruela7049872013-11-05 19:28:35 -0500339 isolate_server=isolate_server,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500340 isolated_hash=isolated_hash,
341 task_name=task_name,
Marc-Antoine Ruela7049872013-11-05 19:28:35 -0500342 shards=shards,
Marc-Antoine Ruel92f32422013-11-06 18:12:13 -0500343 dimensions=dimensions,
Marc-Antoine Ruel05dab5e2013-11-06 15:06:47 -0500344 env=env,
Marc-Antoine Ruela7049872013-11-05 19:28:35 -0500345 working_dir=working_dir,
346 verbose=verbose,
347 profile=profile,
348 priority=priority,
349 algo=algo)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000350 except ValueError as e:
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500351 tools.report_error('Unable to process %s: %s' % (task_name, e))
maruel@chromium.org0437a732013-08-27 16:05:52 +0000352 return 1
353
354 chromium_setup(manifest)
355
maruel@chromium.org0437a732013-08-27 16:05:52 +0000356 print('Zipping up files...')
357 if not manifest.zip_and_upload():
358 return 1
359
maruel@chromium.org0437a732013-08-27 16:05:52 +0000360 print('Server: %s' % swarming)
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500361 print('Task name: %s' % task_name)
362 trigger_url = swarming + '/test'
maruel@chromium.org0437a732013-08-27 16:05:52 +0000363 manifest_text = manifest.to_json()
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500364 result = net.url_read(trigger_url, data={'request': manifest_text})
maruel@chromium.org0437a732013-08-27 16:05:52 +0000365 if not result:
vadimsh@chromium.orgd908a542013-10-30 01:36:17 +0000366 tools.report_error(
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500367 'Failed to trigger task %s\n%s' % (task_name, trigger_url))
maruel@chromium.org0437a732013-08-27 16:05:52 +0000368 return 1
369 try:
vadimsh@chromium.orgf24e5c32013-10-11 21:16:21 +0000370 json.loads(result)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000371 except (ValueError, TypeError) as e:
vadimsh@chromium.orgd908a542013-10-30 01:36:17 +0000372 msg = '\n'.join((
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500373 'Failed to trigger task %s' % task_name,
vadimsh@chromium.orgd908a542013-10-30 01:36:17 +0000374 'Manifest: %s' % manifest_text,
375 'Bad response: %s' % result,
376 str(e)))
377 tools.report_error(msg)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000378 return 1
379 return 0
380
381
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500382def isolated_to_hash(isolate_server, arg, algo, verbose):
383 """Archives a .isolated file if needed.
384
385 Returns the file hash to trigger.
386 """
387 if arg.endswith('.isolated'):
388 file_hash = archive(isolate_server, arg, algo, verbose)
389 if not file_hash:
390 tools.report_error('Archival failure %s' % arg)
391 return None
392 return file_hash
393 elif isolateserver.is_valid_hash(arg, algo):
394 return arg
395 else:
396 tools.report_error('Invalid hash %s' % arg)
397 return None
398
399
maruel@chromium.org0437a732013-08-27 16:05:52 +0000400def trigger(
Marc-Antoine Ruela7049872013-11-05 19:28:35 -0500401 swarming,
402 isolate_server,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500403 file_hash_or_isolated,
404 task_name,
405 shards,
Marc-Antoine Ruel92f32422013-11-06 18:12:13 -0500406 dimensions,
407 env,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500408 working_dir,
maruel@chromium.org0437a732013-08-27 16:05:52 +0000409 verbose,
410 profile,
411 priority):
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500412 """Sends off the hash swarming task requests."""
413 file_hash = isolated_to_hash(
414 isolate_server, file_hash_or_isolated, hashlib.sha1, verbose)
415 if not file_hash:
416 return 1
417 env = env.copy()
418 if shards > 1:
419 env['GTEST_SHARD_INDEX'] = '%(instance_index)s'
420 env['GTEST_TOTAL_SHARDS'] = '%(num_instances)s'
421 # TODO(maruel): It should first create a request manifest object, then pass
422 # it to a function to zip, archive and trigger.
423 return process_manifest(
424 swarming=swarming,
425 isolate_server=isolate_server,
426 isolated_hash=file_hash,
427 task_name=task_name,
428 shards=shards,
429 dimensions=dimensions,
430 env=env,
431 working_dir=working_dir,
432 verbose=verbose,
433 profile=profile,
434 priority=priority,
435 algo=hashlib.sha1)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000436
437
438def decorate_shard_output(result, shard_exit_code):
439 """Returns wrapped output for swarming task shard."""
440 tag = 'index %s (machine tag: %s, id: %s)' % (
441 result['config_instance_index'],
442 result['machine_id'],
443 result.get('machine_tag', 'unknown'))
444 return (
445 '\n'
446 '================================================================\n'
447 'Begin output from shard %s\n'
448 '================================================================\n'
449 '\n'
450 '%s'
451 '================================================================\n'
452 'End output from shard %s. Return %d\n'
453 '================================================================\n'
454 ) % (tag, result['output'] or NO_OUTPUT_FOUND, tag, shard_exit_code)
455
456
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500457def collect(url, task_name, timeout, decorate):
458 """Retrieves results of a Swarming task."""
459 logging.info('Collecting %s', task_name)
460 task_keys = get_task_keys(url, task_name)
461 if not task_keys:
462 raise Failure('No task keys to get results with.')
maruel@chromium.org0437a732013-08-27 16:05:52 +0000463
maruel@chromium.org9c1c7b52013-08-28 19:04:36 +0000464 exit_code = None
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500465 for _index, output in yield_results(url, task_keys, timeout, None):
maruel@chromium.org0437a732013-08-27 16:05:52 +0000466 shard_exit_codes = (output['exit_codes'] or '1').split(',')
467 shard_exit_code = max(int(i) for i in shard_exit_codes)
468 if decorate:
469 print decorate_shard_output(output, shard_exit_code)
470 else:
471 print(
472 '%s/%s: %s' % (
473 output['machine_id'],
474 output['machine_tag'],
475 output['exit_codes']))
476 print(''.join(' %s\n' % l for l in output['output'].splitlines()))
maruel@chromium.org9c1c7b52013-08-28 19:04:36 +0000477 exit_code = exit_code or shard_exit_code
478 return exit_code if exit_code is not None else 1
maruel@chromium.org0437a732013-08-27 16:05:52 +0000479
480
481def add_trigger_options(parser):
482 """Adds all options to trigger a task on Swarming."""
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500483 parser.server_group.add_option(
maruel@chromium.org0437a732013-08-27 16:05:52 +0000484 '-I', '--isolate-server',
maruel@chromium.orge9403ab2013-09-20 18:03:49 +0000485 metavar='URL', default='',
486 help='Isolate server to use')
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500487
488 parser.filter_group = tools.optparse.OptionGroup(parser, 'Filtering slaves')
489 parser.filter_group.add_option(
maruel@chromium.org0437a732013-08-27 16:05:52 +0000490 '-o', '--os', default=sys.platform,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500491 help='Slave OS to request. Should be one of the valid sys.platform '
maruel@chromium.org0437a732013-08-27 16:05:52 +0000492 'values like darwin, linux2 or win32 default: %default.')
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500493 parser.filter_group.add_option(
Marc-Antoine Ruel92f32422013-11-06 18:12:13 -0500494 '-d', '--dimensions', default=[], action='append', nargs=2,
495 dest='dimensions', metavar='FOO bar',
496 help='dimension to filter on')
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500497 parser.add_option_group(parser.filter_group)
498
499 parser.task_group = tools.optparse.OptionGroup(parser, 'Task properties')
500 parser.task_group.add_option(
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500501 '-w', '--working-dir', default='swarm_tests',
502 help='Working directory on the swarming slave side. default: %default.')
503 parser.task_group.add_option(
504 '--working_dir', help=tools.optparse.SUPPRESS_HELP)
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500505 parser.task_group.add_option(
506 '-e', '--env', default=[], action='append', nargs=2, metavar='FOO bar',
507 help='environment variables to set')
508 parser.task_group.add_option(
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500509 '--priority', type='int', default=100,
510 help='The lower value, the more important the task is')
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500511 parser.task_group.add_option(
512 '--shards', type='int', default=1, help='number of shards to use')
513 parser.task_group.add_option(
514 '-T', '--task-name', help='display name of the task')
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500515 parser.add_option_group(parser.task_group)
516
517 parser.group_logging.add_option(
maruel@chromium.org0437a732013-08-27 16:05:52 +0000518 '--profile', action='store_true',
519 default=bool(os.environ.get('ISOLATE_DEBUG')),
520 help='Have run_isolated.py print profiling info')
maruel@chromium.org0437a732013-08-27 16:05:52 +0000521
522
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500523def process_trigger_options(parser, options, args):
maruel@chromium.org0437a732013-08-27 16:05:52 +0000524 options.isolate_server = options.isolate_server.rstrip('/')
525 if not options.isolate_server:
526 parser.error('--isolate-server is required.')
527 if options.os in ('', 'None'):
528 # Use the current OS.
529 options.os = sys.platform
maruel@chromium.orge9403ab2013-09-20 18:03:49 +0000530 if not options.os in PLATFORM_MAPPING_SWARMING:
maruel@chromium.org0437a732013-08-27 16:05:52 +0000531 parser.error('Invalid --os option.')
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500532 if len(args) != 1:
533 parser.error('Must pass one .isolated file or its hash (sha1).')
534 options.dimensions.append(('os', PLATFORM_MAPPING_SWARMING[options.os]))
535 options.dimensions = dict(options.dimensions)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000536
537
538def add_collect_options(parser):
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500539 parser.server_group.add_option(
maruel@chromium.org0437a732013-08-27 16:05:52 +0000540 '-t', '--timeout',
541 type='float',
542 default=DEFAULT_SHARD_WAIT_TIME,
543 help='Timeout to wait for result, set to 0 for no timeout; default: '
544 '%default s')
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500545 parser.group_logging.add_option(
546 '--decorate', action='store_true', help='Decorate output')
maruel@chromium.org0437a732013-08-27 16:05:52 +0000547
548
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500549@subcommand.usage('task_name')
maruel@chromium.org0437a732013-08-27 16:05:52 +0000550def CMDcollect(parser, args):
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500551 """Retrieves results of a Swarming task.
maruel@chromium.org0437a732013-08-27 16:05:52 +0000552
553 The result can be in multiple part if the execution was sharded. It can
554 potentially have retries.
555 """
556 add_collect_options(parser)
557 (options, args) = parser.parse_args(args)
558 if not args:
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500559 parser.error('Must specify one task name.')
maruel@chromium.org0437a732013-08-27 16:05:52 +0000560 elif len(args) > 1:
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500561 parser.error('Must specify only one task name.')
maruel@chromium.org0437a732013-08-27 16:05:52 +0000562
563 try:
564 return collect(options.swarming, args[0], options.timeout, options.decorate)
565 except Failure as e:
vadimsh@chromium.orgd908a542013-10-30 01:36:17 +0000566 tools.report_error(e)
567 return 1
maruel@chromium.org0437a732013-08-27 16:05:52 +0000568
569
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500570@subcommand.usage('[hash|isolated]')
maruel@chromium.org0437a732013-08-27 16:05:52 +0000571def CMDrun(parser, args):
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500572 """Triggers a task and wait for the results.
maruel@chromium.org0437a732013-08-27 16:05:52 +0000573
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500574 Basically, does everything to run a command remotely.
maruel@chromium.org0437a732013-08-27 16:05:52 +0000575 """
576 add_trigger_options(parser)
577 add_collect_options(parser)
578 options, args = parser.parse_args(args)
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500579 process_trigger_options(parser, options, args)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000580
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500581 try:
582 result = trigger(
583 swarming=options.swarming,
584 isolate_server=options.isolate_server,
585 file_hash_or_isolated=args[0],
586 task_name=options.task_name,
587 shards=options.shards,
588 dimensions=options.dimensions,
589 env=dict(options.env),
590 working_dir=options.working_dir,
591 verbose=options.verbose,
592 profile=options.profile,
593 priority=options.priority)
594 except Failure as e:
595 tools.report_error(
596 'Failed to trigger %s(%s): %s' %
597 (options.task_name, args[0], e.args[0]))
598 return 1
599 if result:
600 tools.report_error('Failed to trigger the task.')
maruel@chromium.org0437a732013-08-27 16:05:52 +0000601 return result
602
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500603 try:
604 return collect(
605 options.swarming,
606 options.task_name,
607 options.timeout,
608 options.decorate)
609 except Failure as e:
610 tools.report_error(e)
611 return 1
maruel@chromium.org0437a732013-08-27 16:05:52 +0000612
613
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500614@subcommand.usage("(hash|isolated)")
maruel@chromium.org0437a732013-08-27 16:05:52 +0000615def CMDtrigger(parser, args):
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500616 """Triggers a Swarming task.
maruel@chromium.org0437a732013-08-27 16:05:52 +0000617
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500618 Accepts either the hash (sha1) of a .isolated file already uploaded or the
619 path to an .isolated file to archive, packages it if needed and sends a
620 Swarming manifest file to the Swarming server.
621
622 If an .isolated file is specified instead of an hash, it is first archived.
maruel@chromium.org0437a732013-08-27 16:05:52 +0000623 """
624 add_trigger_options(parser)
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500625 options, args = parser.parse_args(args)
626 process_trigger_options(parser, options, args)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000627
628 try:
629 return trigger(
Marc-Antoine Ruela7049872013-11-05 19:28:35 -0500630 swarming=options.swarming,
631 isolate_server=options.isolate_server,
Marc-Antoine Ruel7c543272013-11-26 13:26:15 -0500632 file_hash_or_isolated=args[0],
633 task_name=options.task_name,
634 dimensions=options.dimensions,
635 shards=options.shards,
Marc-Antoine Ruel92f32422013-11-06 18:12:13 -0500636 env=dict(options.env),
Marc-Antoine Ruela7049872013-11-05 19:28:35 -0500637 working_dir=options.working_dir,
638 verbose=options.verbose,
639 profile=options.profile,
640 priority=options.priority)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000641 except Failure as e:
vadimsh@chromium.orgd908a542013-10-30 01:36:17 +0000642 tools.report_error(e)
643 return 1
maruel@chromium.org0437a732013-08-27 16:05:52 +0000644
645
646class OptionParserSwarming(tools.OptionParserWithLogging):
647 def __init__(self, **kwargs):
648 tools.OptionParserWithLogging.__init__(
649 self, prog='swarming.py', **kwargs)
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500650 self.server_group = tools.optparse.OptionGroup(self, 'Server')
651 self.server_group.add_option(
maruel@chromium.orge9403ab2013-09-20 18:03:49 +0000652 '-S', '--swarming',
653 metavar='URL', default='',
654 help='Swarming server to use')
Marc-Antoine Ruel5471e3d2013-11-11 19:10:32 -0500655 self.add_option_group(self.server_group)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000656
657 def parse_args(self, *args, **kwargs):
658 options, args = tools.OptionParserWithLogging.parse_args(
659 self, *args, **kwargs)
660 options.swarming = options.swarming.rstrip('/')
661 if not options.swarming:
662 self.error('--swarming is required.')
663 return options, args
664
665
666def main(args):
667 dispatcher = subcommand.CommandDispatcher(__name__)
668 try:
669 return dispatcher.execute(OptionParserSwarming(version=__version__), args)
vadimsh@chromium.orgd908a542013-10-30 01:36:17 +0000670 except Exception as e:
671 tools.report_error(e)
maruel@chromium.org0437a732013-08-27 16:05:52 +0000672 return 1
673
674
675if __name__ == '__main__':
676 fix_encoding.fix_encoding()
677 tools.disable_buffering()
678 colorama.init()
679 sys.exit(main(sys.argv[1:]))