Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(830)

Side by Side Diff: client/run_isolated.py

Issue 2069903003: swarming: custom cipd package paths (Closed) Base URL: https://chromium.googlesource.com/external/github.com/luci/luci-py@cipd-win
Patch Set: fix _validate_cipd_path Created 4 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « client/isolateserver.py ('k') | client/swarming.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 #!/usr/bin/env python 1 #!/usr/bin/env python
2 # Copyright 2012 The LUCI Authors. All rights reserved. 2 # Copyright 2012 The LUCI Authors. All rights reserved.
3 # Use of this source code is governed under the Apache License, Version 2.0 3 # Use of this source code is governed under the Apache License, Version 2.0
4 # that can be found in the LICENSE file. 4 # that can be found in the LICENSE file.
5 5
6 """Runs a command with optional isolated input/output. 6 """Runs a command with optional isolated input/output.
7 7
8 Despite name "run_isolated", can run a generic non-isolated command specified as 8 Despite name "run_isolated", can run a generic non-isolated command specified as
9 args. 9 args.
10 10
11 If input isolated hash is provided, fetches it, creates a tree of hard links, 11 If input isolated hash is provided, fetches it, creates a tree of hard links,
12 appends args to the command in the fetched isolated and runs it. 12 appends args to the command in the fetched isolated and runs it.
13 To improve performance, keeps a local cache. 13 To improve performance, keeps a local cache.
14 The local cache can safely be deleted. 14 The local cache can safely be deleted.
15 15
16 Any ${EXECUTABLE_SUFFIX} on the command line will be replaced with ".exe" string 16 Any ${EXECUTABLE_SUFFIX} on the command line will be replaced with ".exe" string
17 on Windows and "" on other platforms. 17 on Windows and "" on other platforms.
18 18
19 If at least one CIPD package was specified, any ${CIPD_PATH} on the command line
20 will be replaced by location of a temporary directory that contains installed
21 packages.
22
23 Any ${ISOLATED_OUTDIR} on the command line will be replaced by the location of a 19 Any ${ISOLATED_OUTDIR} on the command line will be replaced by the location of a
24 temporary directory upon execution of the command specified in the .isolated 20 temporary directory upon execution of the command specified in the .isolated
25 file. All content written to this directory will be uploaded upon termination 21 file. All content written to this directory will be uploaded upon termination
26 and the .isolated file describing this directory will be printed to stdout. 22 and the .isolated file describing this directory will be printed to stdout.
27 """ 23 """
28 24
29 __version__ = '0.8.0' 25 __version__ = '0.8.1'
30 26
31 import base64 27 import base64
32 import contextlib
33 import hashlib
34 import logging 28 import logging
35 import optparse 29 import optparse
36 import os 30 import os
37 import sys 31 import sys
38 import tempfile 32 import tempfile
39 import time 33 import time
40 34
41 from third_party.depot_tools import fix_encoding 35 from third_party.depot_tools import fix_encoding
42 36
43 from utils import file_path 37 from utils import file_path
44 from utils import fs 38 from utils import fs
45 from utils import large 39 from utils import large
46 from utils import logging_utils 40 from utils import logging_utils
47 from utils import on_error 41 from utils import on_error
48 from utils import subprocess42 42 from utils import subprocess42
49 from utils import tools 43 from utils import tools
50 from utils import zip_package 44 from utils import zip_package
51 45
52 import auth 46 import auth
53 import cipd 47 import cipd
54 import isolateserver 48 import isolateserver
55 49
56 50
57 ISOLATED_OUTDIR_PARAMETER = '${ISOLATED_OUTDIR}' 51 ISOLATED_OUTDIR_PARAMETER = '${ISOLATED_OUTDIR}'
58 CIPD_PATH_PARAMETER = '${CIPD_PATH}'
59 EXECUTABLE_SUFFIX_PARAMETER = '${EXECUTABLE_SUFFIX}' 52 EXECUTABLE_SUFFIX_PARAMETER = '${EXECUTABLE_SUFFIX}'
60 SWARMING_BOT_FILE_PARAMETER = '${SWARMING_BOT_FILE}' 53 SWARMING_BOT_FILE_PARAMETER = '${SWARMING_BOT_FILE}'
61 54
62 # Absolute path to this file (can be None if running from zip on Mac). 55 # Absolute path to this file (can be None if running from zip on Mac).
63 THIS_FILE_PATH = os.path.abspath(__file__) if __file__ else None 56 THIS_FILE_PATH = os.path.abspath(__file__) if __file__ else None
64 57
65 # Directory that contains this file (might be inside zip package). 58 # Directory that contains this file (might be inside zip package).
66 BASE_DIR = os.path.dirname(THIS_FILE_PATH) if __file__ else None 59 BASE_DIR = os.path.dirname(THIS_FILE_PATH) if __file__ else None
67 60
68 # Directory that contains currently running script file. 61 # Directory that contains currently running script file.
(...skipping 78 matching lines...) Expand 10 before | Expand all | Expand 10 after
147 # is not yet changed to verify the hash of the content of the files it is 140 # is not yet changed to verify the hash of the content of the files it is
148 # looking at, so that if a test modifies an input file, the file must be 141 # looking at, so that if a test modifies an input file, the file must be
149 # deleted. 142 # deleted.
150 file_path.make_tree_writeable(rootdir) 143 file_path.make_tree_writeable(rootdir)
151 else: 144 else:
152 raise ValueError( 145 raise ValueError(
153 'change_tree_read_only(%s, %s): Unknown flag %s' % 146 'change_tree_read_only(%s, %s): Unknown flag %s' %
154 (rootdir, read_only, read_only)) 147 (rootdir, read_only, read_only))
155 148
156 149
157 def process_command(command, out_dir, cipd_path, bot_file): 150 def process_command(command, out_dir, bot_file):
158 """Replaces variables in a command line. 151 """Replaces variables in a command line.
159 152
160 Raises: 153 Raises:
161 ValueError if a parameter is requested in |command| but its value is not 154 ValueError if a parameter is requested in |command| but its value is not
162 provided. 155 provided.
163 """ 156 """
164 def fix(arg): 157 def fix(arg):
165 arg = arg.replace(EXECUTABLE_SUFFIX_PARAMETER, cipd.EXECUTABLE_SUFFIX) 158 arg = arg.replace(EXECUTABLE_SUFFIX_PARAMETER, cipd.EXECUTABLE_SUFFIX)
166 replace_slash = False 159 replace_slash = False
167 if CIPD_PATH_PARAMETER in arg:
168 if not cipd_path:
169 raise ValueError('cipd_path is requested in command, but not provided')
170 arg = arg.replace(CIPD_PATH_PARAMETER, cipd_path)
171 replace_slash = True
172 if ISOLATED_OUTDIR_PARAMETER in arg: 160 if ISOLATED_OUTDIR_PARAMETER in arg:
173 if not out_dir: 161 if not out_dir:
174 raise ValueError('out_dir is requested in command, but not provided') 162 raise ValueError('out_dir is requested in command, but not provided')
175 arg = arg.replace(ISOLATED_OUTDIR_PARAMETER, out_dir) 163 arg = arg.replace(ISOLATED_OUTDIR_PARAMETER, out_dir)
176 replace_slash = True 164 replace_slash = True
165 if SWARMING_BOT_FILE_PARAMETER in arg:
166 if bot_file:
167 arg = arg.replace(SWARMING_BOT_FILE_PARAMETER, bot_file)
168 replace_slash = True
169 else:
170 logging.warning('SWARMING_BOT_FILE_PARAMETER found in command, but no '
171 'bot_file specified. Leaving parameter unchanged.')
177 if replace_slash: 172 if replace_slash:
178 # Replace slashes only if parameters are present 173 # Replace slashes only if parameters are present
179 # because of arguments like '${ISOLATED_OUTDIR}/foo/bar' 174 # because of arguments like '${ISOLATED_OUTDIR}/foo/bar'
180 arg = arg.replace('/', os.sep) 175 arg = arg.replace('/', os.sep)
181 if SWARMING_BOT_FILE_PARAMETER in arg:
182 if bot_file:
183 arg = arg.replace(SWARMING_BOT_FILE_PARAMETER, bot_file)
184 arg = arg.replace('/', os.sep)
185 else:
186 logging.warning('SWARMING_BOT_FILE_PARAMETER found in command, but no '
187 'bot_file specified. Leaving paramater unchanged.')
188 return arg 176 return arg
189 177
190 return [fix(arg) for arg in command] 178 return [fix(arg) for arg in command]
191 179
192 180
193 def run_command(command, cwd, tmp_dir, hard_timeout, grace_period): 181 def run_command(command, cwd, tmp_dir, hard_timeout, grace_period):
194 """Runs the command. 182 """Runs the command.
195 183
196 Returns: 184 Returns:
197 tuple(process exit code, bool if had a hard timeout) 185 tuple(process exit code, bool if had a hard timeout)
(...skipping 144 matching lines...) Expand 10 before | Expand all | Expand 10 after
342 stats = { 330 stats = {
343 'duration': time.time() - start, 331 'duration': time.time() - start,
344 'items_cold': base64.b64encode(large.pack(cold)), 332 'items_cold': base64.b64encode(large.pack(cold)),
345 'items_hot': base64.b64encode(large.pack(hot)), 333 'items_hot': base64.b64encode(large.pack(hot)),
346 } 334 }
347 return outputs_ref, success, stats 335 return outputs_ref, success, stats
348 336
349 337
350 def map_and_run( 338 def map_and_run(
351 command, isolated_hash, storage, cache, leak_temp_dir, root_dir, 339 command, isolated_hash, storage, cache, leak_temp_dir, root_dir,
352 hard_timeout, grace_period, bot_file, extra_args, cipd_path, cipd_stats): 340 hard_timeout, grace_period, bot_file, extra_args, install_packages_fn):
353 """Runs a command with optional isolated input/output. 341 """Runs a command with optional isolated input/output.
354 342
355 See run_tha_test for argument documentation. 343 See run_tha_test for argument documentation.
356 344
357 Returns metadata about the result. 345 Returns metadata about the result.
358 """ 346 """
359 assert bool(command) ^ bool(isolated_hash) 347 assert bool(command) ^ bool(isolated_hash)
360 result = { 348 result = {
361 'duration': None, 349 'duration': None,
362 'exit_code': None, 350 'exit_code': None,
(...skipping 15 matching lines...) Expand all
378 # 'upload': { 366 # 'upload': {
379 # 'duration': 0., 367 # 'duration': 0.,
380 # 'items_cold': '<large.pack()>', 368 # 'items_cold': '<large.pack()>',
381 # 'items_hot': '<large.pack()>', 369 # 'items_hot': '<large.pack()>',
382 # }, 370 # },
383 # }, 371 # },
384 }, 372 },
385 'outputs_ref': None, 373 'outputs_ref': None,
386 'version': 5, 374 'version': 5,
387 } 375 }
388 if cipd_stats:
389 result['stats']['cipd'] = cipd_stats
390 376
391 if root_dir: 377 if root_dir:
392 file_path.ensure_tree(root_dir, 0700) 378 file_path.ensure_tree(root_dir, 0700)
393 else: 379 else:
394 root_dir = os.path.dirname(cache.cache_dir) if cache.cache_dir else None 380 root_dir = os.path.dirname(cache.cache_dir) if cache.cache_dir else None
395 run_dir = make_temp_dir(u'isolated_run', root_dir) 381 run_dir = make_temp_dir(u'isolated_run', root_dir)
396 out_dir = make_temp_dir(u'isolated_out', root_dir) if storage else None 382 out_dir = make_temp_dir(u'isolated_out', root_dir) if storage else None
397 tmp_dir = make_temp_dir(u'isolated_tmp', root_dir) 383 tmp_dir = make_temp_dir(u'isolated_tmp', root_dir)
398 cwd = run_dir 384 cwd = run_dir
399 385
400 try: 386 try:
387 cipd_stats = install_packages_fn(run_dir)
388 if cipd_stats:
389 result['stats']['cipd'] = cipd_stats
390
401 if isolated_hash: 391 if isolated_hash:
402 isolated_stats = result['stats'].setdefault('isolated', {}) 392 isolated_stats = result['stats'].setdefault('isolated', {})
403 bundle, isolated_stats['download'] = fetch_and_measure( 393 bundle, isolated_stats['download'] = fetch_and_measure(
404 isolated_hash=isolated_hash, 394 isolated_hash=isolated_hash,
405 storage=storage, 395 storage=storage,
406 cache=cache, 396 cache=cache,
407 outdir=run_dir) 397 outdir=run_dir)
408 if not bundle.command: 398 if not bundle.command:
409 # Handle this as a task failure, not an internal failure. 399 # Handle this as a task failure, not an internal failure.
410 sys.stderr.write( 400 sys.stderr.write(
411 '<The .isolated doesn\'t declare any command to run!>\n' 401 '<The .isolated doesn\'t declare any command to run!>\n'
412 '<Check your .isolate for missing \'command\' variable>\n') 402 '<Check your .isolate for missing \'command\' variable>\n')
413 if os.environ.get('SWARMING_TASK_ID'): 403 if os.environ.get('SWARMING_TASK_ID'):
414 # Give an additional hint when running as a swarming task. 404 # Give an additional hint when running as a swarming task.
415 sys.stderr.write('<This occurs at the \'isolate\' step>\n') 405 sys.stderr.write('<This occurs at the \'isolate\' step>\n')
416 result['exit_code'] = 1 406 result['exit_code'] = 1
417 return result 407 return result
418 408
419 change_tree_read_only(run_dir, bundle.read_only) 409 change_tree_read_only(run_dir, bundle.read_only)
420 cwd = os.path.normpath(os.path.join(cwd, bundle.relative_cwd)) 410 cwd = os.path.normpath(os.path.join(cwd, bundle.relative_cwd))
421 command = bundle.command + extra_args 411 command = bundle.command + extra_args
422 412
423 command = tools.fix_python_path(command) 413 command = tools.fix_python_path(command)
424 command = process_command(command, out_dir, cipd_path, bot_file) 414 command = process_command(command, out_dir, bot_file)
425 file_path.ensure_command_has_abs_path(command, cwd) 415 file_path.ensure_command_has_abs_path(command, cwd)
426 416
427 sys.stdout.flush() 417 sys.stdout.flush()
428 start = time.time() 418 start = time.time()
429 try: 419 try:
430 result['exit_code'], result['had_hard_timeout'] = run_command( 420 result['exit_code'], result['had_hard_timeout'] = run_command(
431 command, cwd, tmp_dir, hard_timeout, grace_period) 421 command, cwd, tmp_dir, hard_timeout, grace_period)
432 finally: 422 finally:
433 result['duration'] = max(time.time() - start, 0) 423 result['duration'] = max(time.time() - start, 0)
434 except Exception as e: 424 except Exception as e:
435 # An internal error occured. Report accordingly so the swarming task will be 425 # An internal error occurred. Report accordingly so the swarming task will
436 # retried automatically. 426 # be retried automatically.
437 logging.exception('internal failure: %s', e) 427 logging.exception('internal failure: %s', e)
438 result['internal_failure'] = str(e) 428 result['internal_failure'] = str(e)
439 on_error.report(None) 429 on_error.report(None)
440 finally: 430 finally:
441 try: 431 try:
442 if leak_temp_dir: 432 if leak_temp_dir:
443 logging.warning( 433 logging.warning(
444 'Deliberately leaking %s for later examination', run_dir) 434 'Deliberately leaking %s for later examination', run_dir)
445 else: 435 else:
446 # On Windows rmtree(run_dir) call above has a synchronization effect: it 436 # On Windows rmtree(run_dir) call above has a synchronization effect: it
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after
488 # Swallow any exception in the main finally clause. 478 # Swallow any exception in the main finally clause.
489 if out_dir: 479 if out_dir:
490 logging.exception('Leaking out_dir %s: %s', out_dir, e) 480 logging.exception('Leaking out_dir %s: %s', out_dir, e)
491 result['internal_failure'] = str(e) 481 result['internal_failure'] = str(e)
492 return result 482 return result
493 483
494 484
495 def run_tha_test( 485 def run_tha_test(
496 command, isolated_hash, storage, cache, leak_temp_dir, result_json, 486 command, isolated_hash, storage, cache, leak_temp_dir, result_json,
497 root_dir, hard_timeout, grace_period, bot_file, extra_args, 487 root_dir, hard_timeout, grace_period, bot_file, extra_args,
498 cipd_path, cipd_stats): 488 install_packages_fn):
499 """Runs an executable and records execution metadata. 489 """Runs an executable and records execution metadata.
500 490
501 Either command or isolated_hash must be specified. 491 Either command or isolated_hash must be specified.
502 492
503 If isolated_hash is specified, downloads the dependencies in the cache, 493 If isolated_hash is specified, downloads the dependencies in the cache,
504 hardlinks them into a temporary directory and runs the command specified in 494 hardlinks them into a temporary directory and runs the command specified in
505 the .isolated. 495 the .isolated.
506 496
507 A temporary directory is created to hold the output files. The content inside 497 A temporary directory is created to hold the output files. The content inside
508 this directory will be uploaded back to |storage| packaged as a .isolated 498 this directory will be uploaded back to |storage| packaged as a .isolated
509 file. 499 file.
510 500
511 Arguments: 501 Arguments:
512 command: the command to run, a list of strings. Mutually exclusive with 502 command: the command to run, a list of strings. Mutually exclusive with
513 isolated_hash. 503 isolated_hash.
514 isolated_hash: the SHA-1 of the .isolated file that must be retrieved to 504 isolated_hash: the SHA-1 of the .isolated file that must be retrieved to
515 recreate the tree of files to run the target executable. 505 recreate the tree of files to run the target executable.
516 The command specified in the .isolated is executed. 506 The command specified in the .isolated is executed.
517 Mutually exclusive with command argument. 507 Mutually exclusive with command argument.
518 storage: an isolateserver.Storage object to retrieve remote objects. This 508 storage: an isolateserver.Storage object to retrieve remote objects. This
519 object has a reference to an isolateserver.StorageApi, which does 509 object has a reference to an isolateserver.StorageApi, which does
520 the actual I/O. 510 the actual I/O.
521 cache: an isolateserver.LocalCache to keep from retrieving the same objects 511 cache: an isolateserver.LocalCache to keep from retrieving the same objects
522 constantly by caching the objects retrieved. Can be on-disk or 512 constantly by caching the objects retrieved. Can be on-disk or
523 in-memory. 513 in-memory.
524 leak_temp_dir: if true, the temporary directory will be deliberately leaked 514 leak_temp_dir: if true, the temporary directory will be deliberately leaked
525 for later examination. 515 for later examination.
526 result_json: file path to dump result metadata into. If set, the process 516 result_json: file path to dump result metadata into. If set, the process
527 exit code is always 0 unless an internal error occurred. 517 exit code is always 0 unless an internal error occurred.
528 root_dir: directory to the path to use to create the temporary directory. If 518 root_dir: path to the directory to use to create the temporary directory. If
529 not specified, a random temporary directory is created. 519 not specified, a random temporary directory is created.
530 hard_timeout: kills the process if it lasts more than this amount of 520 hard_timeout: kills the process if it lasts more than this amount of
531 seconds. 521 seconds.
532 grace_period: number of seconds to wait between SIGTERM and SIGKILL. 522 grace_period: number of seconds to wait between SIGTERM and SIGKILL.
533 extra_args: optional arguments to add to the command stated in the .isolate 523 extra_args: optional arguments to add to the command stated in the .isolate
534 file. Ignored if isolate_hash is empty. 524 file. Ignored if isolate_hash is empty.
535 cipd_path: value for CIPD_PATH_PARAMETER. If empty, command or extra_args 525 install_packages_fn: function (dir) => cipd_stats. Installs packages.
536 must not use CIPD_PATH_PARAMETER.
537 cipd_stats: CIPD stats to include in the metadata written to result_json.
538 526
539 Returns: 527 Returns:
540 Process exit code that should be used. 528 Process exit code that should be used.
541 """ 529 """
542 assert bool(command) ^ bool(isolated_hash) 530 assert bool(command) ^ bool(isolated_hash)
543 extra_args = extra_args or [] 531 extra_args = extra_args or []
544 532
545 if any(ISOLATED_OUTDIR_PARAMETER in a for a in (command or extra_args)): 533 if any(ISOLATED_OUTDIR_PARAMETER in a for a in (command or extra_args)):
546 assert storage is not None, 'storage is None although outdir is specified' 534 assert storage is not None, 'storage is None although outdir is specified'
547 535
548 if result_json: 536 if result_json:
549 # Write a json output file right away in case we get killed. 537 # Write a json output file right away in case we get killed.
550 result = { 538 result = {
551 'exit_code': None, 539 'exit_code': None,
552 'had_hard_timeout': False, 540 'had_hard_timeout': False,
553 'internal_failure': 'Was terminated before completion', 541 'internal_failure': 'Was terminated before completion',
554 'outputs_ref': None, 542 'outputs_ref': None,
555 'version': 5, 543 'version': 5,
556 } 544 }
557 if cipd_stats:
558 result['stats'] = {'cipd': cipd_stats}
559 tools.write_json(result_json, result, dense=True) 545 tools.write_json(result_json, result, dense=True)
560 546
561 # run_isolated exit code. Depends on if result_json is used or not. 547 # run_isolated exit code. Depends on if result_json is used or not.
562 result = map_and_run( 548 result = map_and_run(
563 command, isolated_hash, storage, cache, leak_temp_dir, root_dir, 549 command, isolated_hash, storage, cache, leak_temp_dir, root_dir,
564 hard_timeout, grace_period, bot_file, extra_args, cipd_path, cipd_stats) 550 hard_timeout, grace_period, bot_file, extra_args, install_packages_fn)
565 logging.info('Result:\n%s', tools.format_json(result, dense=True)) 551 logging.info('Result:\n%s', tools.format_json(result, dense=True))
566 552
567 if result_json: 553 if result_json:
568 # We've found tests to delete 'work' when quitting, causing an exception 554 # We've found tests to delete 'work' when quitting, causing an exception
569 # here. Try to recreate the directory if necessary. 555 # here. Try to recreate the directory if necessary.
570 file_path.ensure_tree(os.path.dirname(result_json)) 556 file_path.ensure_tree(os.path.dirname(result_json))
571 tools.write_json(result_json, result, dense=True) 557 tools.write_json(result_json, result, dense=True)
572 # Only return 1 if there was an internal error. 558 # Only return 1 if there was an internal error.
573 return int(bool(result['internal_failure'])) 559 return int(bool(result['internal_failure']))
574 560
575 # Marshall into old-style inline output. 561 # Marshall into old-style inline output.
576 if result['outputs_ref']: 562 if result['outputs_ref']:
577 data = { 563 data = {
578 'hash': result['outputs_ref']['isolated'], 564 'hash': result['outputs_ref']['isolated'],
579 'namespace': result['outputs_ref']['namespace'], 565 'namespace': result['outputs_ref']['namespace'],
580 'storage': result['outputs_ref']['isolatedserver'], 566 'storage': result['outputs_ref']['isolatedserver'],
581 } 567 }
582 sys.stdout.flush() 568 sys.stdout.flush()
583 print( 569 print(
584 '[run_isolated_out_hack]%s[/run_isolated_out_hack]' % 570 '[run_isolated_out_hack]%s[/run_isolated_out_hack]' %
585 tools.format_json(data, dense=True)) 571 tools.format_json(data, dense=True))
586 sys.stdout.flush() 572 sys.stdout.flush()
587 return result['exit_code'] or int(bool(result['internal_failure'])) 573 return result['exit_code'] or int(bool(result['internal_failure']))
588 574
589 575
590 @contextlib.contextmanager 576 def install_packages(
591 def ensure_packages( 577 run_dir, package_list_file, service_url, client_package_name,
592 site_root, packages, service_url, client_package, cache_dir=None, 578 client_version, cache_dir=None, timeout=None):
593 timeout=None): 579 """Installs packages. Returns stats.
594 """Returns a context manager that installs packages into site_root.
595
596 Creates/recreates site_root dir and install packages before yielding.
597 After yielding deletes site_root dir.
598 580
599 Args: 581 Args:
600 site_root (str): where to install the packages. 582 run_dir (str): root of installation.
601 packages (list of str): list of package to ensure. 583 package_list_file (str): path to a file with a list of packages to install.
602 Package format is same as for "--cipd-package" option.
603 service_url (str): CIPD server url, e.g. 584 service_url (str): CIPD server url, e.g.
604 "https://chrome-infra-packages.appspot.com." 585 "https://chrome-infra-packages.appspot.com."
605 client_package (str): CIPD package of CIPD client. 586 client_package_name (str): CIPD package name of CIPD client.
606 Format is same as for "--cipd-package" option. 587 client_version (str): Version of CIPD client.
607 cache_dir (str): where to keep cache of cipd clients, packages and tags. 588 cache_dir (str): where to keep cache of cipd clients, packages and tags.
608 timeout: max duration in seconds that this function can take. 589 timeout: max duration in seconds that this function can take.
609
610 Yields:
611 CIPD stats as dict.
612 """ 590 """
613 assert cache_dir 591 assert cache_dir
592 if not package_list_file:
593 return None
594
614 timeoutfn = tools.sliding_timeout(timeout) 595 timeoutfn = tools.sliding_timeout(timeout)
615 if not packages:
616 yield
617 return
618
619 start = time.time() 596 start = time.time()
620
621 cache_dir = os.path.abspath(cache_dir) 597 cache_dir = os.path.abspath(cache_dir)
622 598
623 # Get CIPD client. 599 run_dir = os.path.abspath(run_dir)
624 client_package_name, client_version = cipd.parse_package(client_package) 600 package_list = cipd.parse_package_list_file(package_list_file)
601
625 get_client_start = time.time() 602 get_client_start = time.time()
626 client_manager = cipd.get_client( 603 client_manager = cipd.get_client(
627 service_url, client_package_name, client_version, cache_dir, 604 service_url, client_package_name, client_version, cache_dir,
628 timeout=timeoutfn()) 605 timeout=timeoutfn())
629 with client_manager as client: 606 with client_manager as client:
630 get_client_duration = time.time() - get_client_start 607 get_client_duration = time.time() - get_client_start
631 # Create site_root, install packages, yield, delete site_root. 608 for path, packages in package_list.iteritems():
632 if fs.isdir(site_root): 609 site_root = os.path.abspath(os.path.join(run_dir, path))
633 file_path.rmtree(site_root) 610 if not site_root.startswith(run_dir):
634 file_path.ensure_tree(site_root, 0770) 611 raise cipd.Error('Invalid CIPD package path "%s"' % path)
635 try: 612
613 # Do not clean site_root before installation because it may contain other
614 # site roots.
615 file_path.ensure_tree(site_root, 0770)
636 client.ensure( 616 client.ensure(
637 site_root, packages, 617 site_root, packages,
638 cache_dir=os.path.join(cache_dir, 'cipd_internal'), 618 cache_dir=os.path.join(cache_dir, 'cipd_internal'),
639 timeout=timeoutfn()) 619 timeout=timeoutfn())
620 file_path.make_tree_files_read_only(site_root)
640 621
641 total_duration = time.time() - start 622 total_duration = time.time() - start
642 logging.info( 623 logging.info(
643 'Installing CIPD client and packages took %d seconds', total_duration) 624 'Installing CIPD client and packages took %d seconds', total_duration)
644 625
645 file_path.make_tree_files_read_only(site_root) 626 return {
646 yield { 627 'duration': total_duration,
647 'duration': total_duration, 628 'get_client_duration': get_client_duration,
648 'get_client_duration': get_client_duration, 629 }
649 }
650 finally:
651 file_path.rmtree(site_root)
652 630
653 631
654 def create_option_parser(): 632 def create_option_parser():
655 parser = logging_utils.OptionParserWithLogging( 633 parser = logging_utils.OptionParserWithLogging(
656 usage='%prog <options> [command to run or extra args]', 634 usage='%prog <options> [command to run or extra args]',
657 version=__version__, 635 version=__version__,
658 log_file=RUN_ISOLATED_LOG_FILE) 636 log_file=RUN_ISOLATED_LOG_FILE)
659 parser.add_option( 637 parser.add_option(
660 '--clean', action='store_true', 638 '--clean', action='store_true',
661 help='Cleans the cache, trimming it necessary and remove corrupted items ' 639 help='Cleans the cache, trimming it necessary and remove corrupted items '
(...skipping 61 matching lines...) Expand 10 before | Expand all | Expand 10 after
723 701
724 isolateserver.process_isolate_server_options( 702 isolateserver.process_isolate_server_options(
725 parser, options, True, False) 703 parser, options, True, False)
726 if not options.isolate_server: 704 if not options.isolate_server:
727 if options.isolated: 705 if options.isolated:
728 parser.error('--isolated requires --isolate-server') 706 parser.error('--isolated requires --isolate-server')
729 if ISOLATED_OUTDIR_PARAMETER in args: 707 if ISOLATED_OUTDIR_PARAMETER in args:
730 parser.error( 708 parser.error(
731 '%s in args requires --isolate-server' % ISOLATED_OUTDIR_PARAMETER) 709 '%s in args requires --isolate-server' % ISOLATED_OUTDIR_PARAMETER)
732 710
711 if options.root_dir:
712 options.root_dir = unicode(os.path.abspath(options.root_dir))
733 if options.json: 713 if options.json:
734 options.json = unicode(os.path.abspath(options.json)) 714 options.json = unicode(os.path.abspath(options.json))
735 715
736 cipd.validate_cipd_options(parser, options) 716 cipd.validate_cipd_options(parser, options)
737 717
738 root_dir = options.root_dir 718 install_packages_fn = lambda run_dir: install_packages(
739 if root_dir: 719 run_dir, options.cipd_package_list, options.cipd_server,
740 root_dir = unicode(os.path.abspath(root_dir)) 720 options.cipd_client_package, options.cipd_client_version,
741 file_path.ensure_tree(root_dir, 0700) 721 cache_dir=options.cipd_cache)
742 else:
743 root_dir = os.path.dirname(cache.cache_dir) if cache.cache_dir else None
744
745 cipd_path = None
746 if not options.cipd_package:
747 if CIPD_PATH_PARAMETER in args:
748 parser.error('%s in args requires --cipd-package' % CIPD_PATH_PARAMETER)
749 else:
750 cipd_path = make_temp_dir(u'cipd_site_root', root_dir)
751 722
752 try: 723 try:
753 with ensure_packages( 724 command = [] if options.isolated else args
754 cipd_path, options.cipd_package, options.cipd_server, 725 if options.isolate_server:
755 options.cipd_client_package, options.cipd_cache) as cipd_stats: 726 storage = isolateserver.get_storage(
756 command = [] if options.isolated else args 727 options.isolate_server, options.namespace)
757 if options.isolate_server: 728 with storage:
758 storage = isolateserver.get_storage( 729 # Hashing schemes used by |storage| and |cache| MUST match.
759 options.isolate_server, options.namespace) 730 assert storage.hash_algo == cache.hash_algo
760 with storage:
761 # Hashing schemes used by |storage| and |cache| MUST match.
762 assert storage.hash_algo == cache.hash_algo
763 return run_tha_test(
764 command, options.isolated, storage, cache, options.leak_temp_dir,
765 options.json, root_dir, options.hard_timeout,
766 options.grace_period, options.bot_file, args,
767 cipd_path, cipd_stats)
768 else:
769 return run_tha_test( 731 return run_tha_test(
770 command, options.isolated, None, cache, options.leak_temp_dir, 732 command, options.isolated, storage, cache, options.leak_temp_dir,
771 options.json, root_dir, options.hard_timeout, 733 options.json, options.root_dir, options.hard_timeout,
772 options.grace_period, options.bot_file, args, 734 options.grace_period, options.bot_file, args, install_packages_fn)
773 cipd_path, cipd_stats) 735 else:
736 return run_tha_test(
737 command, options.isolated, None, cache, options.leak_temp_dir,
738 options.json, options.root_dir, options.hard_timeout,
739 options.grace_period, options.bot_file, args, install_packages_fn)
774 except cipd.Error as ex: 740 except cipd.Error as ex:
775 print >> sys.stderr, ex.message 741 print >> sys.stderr, ex.message
776 return 1 742 return 1
777 743
778 744
779 if __name__ == '__main__': 745 if __name__ == '__main__':
780 subprocess42.inhibit_os_error_reporting() 746 subprocess42.inhibit_os_error_reporting()
781 # Ensure that we are always running with the correct encoding. 747 # Ensure that we are always running with the correct encoding.
782 fix_encoding.fix_encoding() 748 fix_encoding.fix_encoding()
783 sys.exit(main(sys.argv[1:])) 749 sys.exit(main(sys.argv[1:]))
OLDNEW
« no previous file with comments | « client/isolateserver.py ('k') | client/swarming.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698