blob: dbaa09cd935f72c929d2fee798eacb3c039f2fa1 [file] [log] [blame]
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +00001#!/usr/bin/env python
maruelea586f32016-04-05 11:11:33 -07002# Copyright 2012 The LUCI Authors. All rights reserved.
maruelf1f5e2a2016-05-25 17:10:39 -07003# Use of this source code is governed under the Apache License, Version 2.0
4# that can be found in the LICENSE file.
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +00005
nodir55be77b2016-05-03 09:39:57 -07006"""Runs a command with optional isolated input/output.
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +00007
Marc-Antoine Rueleed2f3a2019-03-14 00:00:40 +00008run_isolated takes cares of setting up a temporary environment, running a
9command, and tearing it down.
nodir55be77b2016-05-03 09:39:57 -070010
Marc-Antoine Rueleed2f3a2019-03-14 00:00:40 +000011It handles downloading and uploading isolated files, mapping CIPD packages and
12reusing stateful named caches.
13
14The isolated files, CIPD packages and named caches are kept as a global LRU
15cache.
Marc-Antoine Ruel2283ad12014-02-09 11:14:57 -050016
Roberto Carrillo71ade6d2018-10-08 22:30:24 +000017Any ${EXECUTABLE_SUFFIX} on the command line or the environment variables passed
18with the --env option will be replaced with ".exe" string on Windows and "" on
19other platforms.
nodirbe642ff2016-06-09 15:51:51 -070020
Roberto Carrillo71ade6d2018-10-08 22:30:24 +000021Any ${ISOLATED_OUTDIR} on the command line or the environment variables passed
22with the --env option will be replaced by the location of a temporary directory
23upon execution of the command specified in the .isolated file. All content
24written to this directory will be uploaded upon termination and the .isolated
25file describing this directory will be printed to stdout.
bpastene447c1992016-06-20 15:21:47 -070026
Marc-Antoine Rueleed2f3a2019-03-14 00:00:40 +000027Any ${SWARMING_BOT_FILE} on the command line or the environment variables passed
28with the --env option will be replaced by the value of the --bot-file parameter.
29This file is used by a swarming bot to communicate state of the host to tasks.
30It is written to by the swarming bot's on_before_task() hook in the swarming
31server's custom bot_config.py.
32
Joanna Wang4cec0e42021-08-26 00:48:37 +000033Any ${SWARMING_TASK_ID} on the command line will be replaced by the
34SWARMING_TASK_ID value passed with the --env option.
35
Marc-Antoine Rueleed2f3a2019-03-14 00:00:40 +000036See
37https://chromium.googlesource.com/infra/luci/luci-py.git/+/master/appengine/swarming/doc/Magic-Values.md
38for all the variables.
39
40See
41https://chromium.googlesource.com/infra/luci/luci-py.git/+/master/appengine/swarming/swarming_bot/config/bot_config.py
42for more information about bot_config.py.
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +000043"""
44
Marc-Antoine Ruelf899c482019-10-10 23:32:06 +000045from __future__ import print_function
46
47__version__ = '1.0.1'
maruel@chromium.orgdedbf492013-09-12 20:42:11 +000048
aludwin7556e0c2016-10-26 08:46:10 -070049import argparse
maruel064c0a32016-04-05 11:47:15 -070050import base64
iannucci96fcccc2016-08-30 15:52:22 -070051import collections
vadimsh232f5a82017-01-20 19:23:44 -080052import contextlib
Ye Kuangfff1e502020-07-13 13:21:57 +000053import distutils
Sadaf Matinkhoo10743a62018-03-29 16:28:58 -040054import errno
aludwin7556e0c2016-10-26 08:46:10 -070055import json
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +000056import logging
57import optparse
58import os
Takuto Ikuta5c59a842020-01-24 03:05:24 +000059import platform
Marc-Antoine Ruel8b11dbd2018-05-18 14:31:22 -040060import re
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +000061import sys
62import tempfile
maruel064c0a32016-04-05 11:47:15 -070063import time
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +000064
Marc-Antoine Ruel016c7602019-04-02 18:31:13 +000065from utils import tools
66tools.force_local_third_party()
vadimsh@chromium.orga4326472013-08-24 02:05:41 +000067
Marc-Antoine Ruel016c7602019-04-02 18:31:13 +000068# third_party/
69from depot_tools import fix_encoding
Takuto Ikuta6e2ff962019-10-29 12:35:27 +000070import six
Marc-Antoine Ruel016c7602019-04-02 18:31:13 +000071
72# pylint: disable=ungrouped-imports
Takuto Ikutad53d7bd2021-07-16 03:09:33 +000073import DEPS
Marc-Antoine Ruel016c7602019-04-02 18:31:13 +000074import auth
75import cipd
76import isolate_storage
77import isolateserver
78import local_caching
79from libs import luci_context
Vadim Shtayura6b555c12014-07-23 16:22:18 -070080from utils import file_path
maruel12e30012015-10-09 11:55:35 -070081from utils import fs
maruel064c0a32016-04-05 11:47:15 -070082from utils import large
Marc-Antoine Ruelf74cffe2015-07-15 15:21:34 -040083from utils import logging_utils
Ye Kuang2dd17442020-04-22 08:45:52 +000084from utils import net
Marc-Antoine Ruelcfb60852014-07-02 15:22:00 -040085from utils import on_error
Marc-Antoine Ruelc44f5722015-01-08 16:10:01 -050086from utils import subprocess42
maruel@chromium.orgdedbf492013-09-12 20:42:11 +000087
vadimsh@chromium.orga4326472013-08-24 02:05:41 +000088
maruele2f2cb82016-07-13 14:41:03 -070089# Magic variables that can be found in the isolate task command line.
90ISOLATED_OUTDIR_PARAMETER = '${ISOLATED_OUTDIR}'
91EXECUTABLE_SUFFIX_PARAMETER = '${EXECUTABLE_SUFFIX}'
92SWARMING_BOT_FILE_PARAMETER = '${SWARMING_BOT_FILE}'
Joanna Wang4cec0e42021-08-26 00:48:37 +000093SWARMING_TASK_ID_PARAMETER = '${SWARMING_TASK_ID}'
maruele2f2cb82016-07-13 14:41:03 -070094
95
csharp@chromium.orgff2a4662012-11-21 20:49:32 +000096# The name of the log file to use.
97RUN_ISOLATED_LOG_FILE = 'run_isolated.log'
98
maruele2f2cb82016-07-13 14:41:03 -070099
maruele2f2cb82016-07-13 14:41:03 -0700100# Use short names for temporary directories. This is driven by Windows, which
101# imposes a relatively short maximum path length of 260 characters, often
102# referred to as MAX_PATH. It is relatively easy to create files with longer
Marc-Antoine Ruel793bff32019-04-18 17:50:48 +0000103# path length. A use case is with recursive dependency trees like npm packages.
maruele2f2cb82016-07-13 14:41:03 -0700104#
105# It is recommended to start the script with a `root_dir` as short as
106# possible.
107# - ir stands for isolated_run
108# - io stands for isolated_out
109# - it stands for isolated_tmp
Takuto Ikutab7ce0e32019-11-27 23:26:18 +0000110# - ic stands for isolated_client
Anirudh Mathukumilli92d57b62021-08-04 23:21:57 +0000111# - ns stands for nsjail
maruele2f2cb82016-07-13 14:41:03 -0700112ISOLATED_RUN_DIR = u'ir'
113ISOLATED_OUT_DIR = u'io'
114ISOLATED_TMP_DIR = u'it'
Takuto Ikutab7ce0e32019-11-27 23:26:18 +0000115ISOLATED_CLIENT_DIR = u'ic'
Junji Watanabe4b890ef2020-09-16 01:43:27 +0000116_CAS_CLIENT_DIR = u'cc'
Anirudh Mathukumilli92d57b62021-08-04 23:21:57 +0000117_NSJAIL_DIR = u'ns'
maruele2f2cb82016-07-13 14:41:03 -0700118
Takuto Ikuta02edca22019-11-29 10:04:51 +0000119# TODO(tikuta): take these parameter from luci-config?
Takuto Ikuta02edca22019-11-29 10:04:51 +0000120ISOLATED_PACKAGE = 'infra/tools/luci/isolated/${platform}'
Junji Watanabe4b890ef2020-09-16 01:43:27 +0000121_CAS_PACKAGE = 'infra/tools/luci/cas/${platform}'
Takuto Ikutad53d7bd2021-07-16 03:09:33 +0000122_LUCI_GO_REVISION = DEPS.deps['luci-go']['packages'][0]['version']
Anirudh Mathukumilli92d57b62021-08-04 23:21:57 +0000123_NSJAIL_PACKAGE = 'infra/3pp/tools/nsjail/${platform}'
124_NSJAIL_VERSION = DEPS.deps['nsjail']['packages'][0]['version']
maruele2f2cb82016-07-13 14:41:03 -0700125
Marc-Antoine Ruel8b11dbd2018-05-18 14:31:22 -0400126# Keep synced with task_request.py
Lei Leife202df2019-06-11 17:33:34 +0000127CACHE_NAME_RE = re.compile(r'^[a-z0-9_]{1,4096}$')
Marc-Antoine Ruel8b11dbd2018-05-18 14:31:22 -0400128
Takuto Ikutac9ddff22021-02-18 07:58:39 +0000129_FREE_SPACE_BUFFER_FOR_CIPD_PACKAGES = 2 * 1024 * 1024 * 1024
Marc-Antoine Ruel8b11dbd2018-05-18 14:31:22 -0400130
marueld928c862017-06-08 08:20:04 -0700131OUTLIVING_ZOMBIE_MSG = """\
132*** Swarming tried multiple times to delete the %s directory and failed ***
133*** Hard failing the task ***
134
135Swarming detected that your testing script ran an executable, which may have
136started a child executable, and the main script returned early, leaving the
137children executables playing around unguided.
138
139You don't want to leave children processes outliving the task on the Swarming
140bot, do you? The Swarming bot doesn't.
141
142How to fix?
143- For any process that starts children processes, make sure all children
144 processes terminated properly before each parent process exits. This is
145 especially important in very deep process trees.
146 - This must be done properly both in normal successful task and in case of
147 task failure. Cleanup is very important.
148- The Swarming bot sends a SIGTERM in case of timeout.
149 - You have %s seconds to comply after the signal was sent to the process
150 before the process is forcibly killed.
151- To achieve not leaking children processes in case of signals on timeout, you
152 MUST handle signals in each executable / python script and propagate them to
153 children processes.
154 - When your test script (python or binary) receives a signal like SIGTERM or
155 CTRL_BREAK_EVENT on Windows), send it to all children processes and wait for
156 them to terminate before quitting.
157
158See
Marc-Antoine Ruelc7243592018-05-24 17:04:04 -0400159https://chromium.googlesource.com/infra/luci/luci-py.git/+/master/appengine/swarming/doc/Bot.md#Graceful-termination_aka-the-SIGTERM-and-SIGKILL-dance
marueld928c862017-06-08 08:20:04 -0700160for more information.
161
162*** May the SIGKILL force be with you ***
163"""
164
165
Marc-Antoine Ruel5d7606b2018-06-15 19:06:12 +0000166# Currently hardcoded. Eventually could be exposed as a flag once there's value.
167# 3 weeks
168MAX_AGE_SECS = 21*24*60*60
169
Takuto Ikuta7ff4b242020-12-03 08:07:06 +0000170_CAS_KVS_CACHE_THRESHOLD = 5 * 1024 * 1024 * 1024 # 5 GiB
171
Marc-Antoine Ruel7de52592017-12-07 10:41:12 -0500172TaskData = collections.namedtuple(
Marc-Antoine Ruel03c6fd12019-04-30 12:12:55 +0000173 'TaskData',
174 [
Takuto Ikuta9a319502019-11-26 07:40:14 +0000175 # List of strings; the command line to use, independent of what was
176 # specified in the isolated file.
177 'command',
178 # Relative directory to start command into.
179 'relative_cwd',
Takuto Ikuta9a319502019-11-26 07:40:14 +0000180 # Hash of the .isolated file that must be retrieved to recreate the tree
181 # of files to run the target executable. The command specified in the
182 # .isolated is executed. Mutually exclusive with command argument.
183 'isolated_hash',
184 # isolateserver.Storage instance to retrieve remote objects. This object
185 # has a reference to an isolateserver.StorageApi, which does the actual
186 # I/O.
187 'storage',
188 # isolateserver.LocalCache instance to keep from retrieving the same
189 # objects constantly by caching the objects retrieved. Can be on-disk or
190 # in-memory.
191 'isolate_cache',
Junji Watanabe54925c32020-09-08 00:56:18 +0000192 # Digest of the input root on RBE-CAS.
193 'cas_digest',
194 # Full CAS instance name.
195 'cas_instance',
Takuto Ikuta9a319502019-11-26 07:40:14 +0000196 # List of paths relative to root_dir to put into the output isolated
197 # bundle upon task completion (see link_outputs_to_outdir).
198 'outputs',
199 # Function (run_dir) => context manager that installs named caches into
200 # |run_dir|.
201 'install_named_caches',
202 # If True, the temporary directory will be deliberately leaked for later
203 # examination.
204 'leak_temp_dir',
205 # Path to the directory to use to create the temporary directory. If not
206 # specified, a random temporary directory is created.
207 'root_dir',
208 # Kills the process if it lasts more than this amount of seconds.
209 'hard_timeout',
210 # Number of seconds to wait between SIGTERM and SIGKILL.
211 'grace_period',
212 # Path to a file with bot state, used in place of ${SWARMING_BOT_FILE}
213 # task command line argument.
214 'bot_file',
215 # Logical account to switch LUCI_CONTEXT into.
216 'switch_to_account',
217 # Context manager dir => CipdInfo, see install_client_and_packages.
218 'install_packages_fn',
Takuto Ikutad03ffcc2019-12-02 01:04:23 +0000219 # Use go isolated client.
220 'use_go_isolated',
Junji Watanabeb03450b2020-09-25 05:09:27 +0000221 # Cache directory for go `isolated` client.
Takuto Ikuta057c5342019-12-03 04:05:05 +0000222 'go_cache_dir',
Junji Watanabeb03450b2020-09-25 05:09:27 +0000223 # Parameters passed to go `isolated` client.
Takuto Ikuta879788c2020-01-10 08:00:26 +0000224 'go_cache_policies',
Junji Watanabeb03450b2020-09-25 05:09:27 +0000225 # Cache directory for `cas` client.
226 'cas_cache_dir',
227 # Parameters passed to `cas` client.
228 'cas_cache_policies',
Takuto Ikutaae391c52020-12-03 08:43:45 +0000229 # Parameters for kvs file used by `cas` client.
230 'cas_kvs',
Takuto Ikuta9a319502019-11-26 07:40:14 +0000231 # Environment variables to set.
232 'env',
233 # Environment variables to mutate with relative directories.
234 # Example: {"ENV_KEY": ['relative', 'paths', 'to', 'prepend']}
235 'env_prefix',
236 # Lowers the task process priority.
237 'lower_priority',
238 # subprocess42.Containment instance. Can be None.
239 'containment',
Junji Watanabeaee69ad2021-04-28 03:17:34 +0000240 # Function to trim caches before installing cipd packages and
241 # downloading isolated files.
242 'trim_caches_fn',
Marc-Antoine Ruel03c6fd12019-04-30 12:12:55 +0000243 ])
Marc-Antoine Ruel7de52592017-12-07 10:41:12 -0500244
245
Marc-Antoine Ruelee6ca622017-11-29 11:19:16 -0500246def _to_str(s):
247 """Downgrades a unicode instance to str. Pass str through as-is."""
248 if isinstance(s, str):
249 return s
250 # This is technically incorrect, especially on Windows. In theory
251 # sys.getfilesystemencoding() should be used to use the right 'ANSI code
252 # page' on Windows, but that causes other problems, as the character set
253 # is very limited.
254 return s.encode('utf-8')
255
256
Marc-Antoine Ruel7a68f712017-12-01 18:45:18 -0500257def _to_unicode(s):
258 """Upgrades a str instance to unicode. Pass unicode through as-is."""
Takuto Ikuta95459dd2019-10-29 12:39:47 +0000259 if isinstance(s, six.text_type) or s is None:
Marc-Antoine Ruel7a68f712017-12-01 18:45:18 -0500260 return s
261 return s.decode('utf-8')
262
263
maruel03e11842016-07-14 10:50:16 -0700264def make_temp_dir(prefix, root_dir):
265 """Returns a new unique temporary directory."""
Takuto Ikuta6e2ff962019-10-29 12:35:27 +0000266 return six.text_type(tempfile.mkdtemp(prefix=prefix, dir=root_dir))
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +0000267
268
vadimsh9c54b2c2017-07-25 14:08:29 -0700269@contextlib.contextmanager
270def set_luci_context_account(account, tmp_dir):
271 """Sets LUCI_CONTEXT account to be used by the task.
272
273 If 'account' is None or '', does nothing at all. This happens when
274 run_isolated.py is called without '--switch-to-account' flag. In this case,
275 if run_isolated.py is running in some LUCI_CONTEXT environment, the task will
Takuto Ikuta33e2ff32019-09-30 12:44:03 +0000276 just inherit whatever account is already set. This may happen if users invoke
vadimsh9c54b2c2017-07-25 14:08:29 -0700277 run_isolated.py explicitly from their code.
278
279 If the requested account is not defined in the context, switches to
280 non-authenticated access. This happens for Swarming tasks that don't use
281 'task' service accounts.
282
283 If not using LUCI_CONTEXT-based auth, does nothing.
284 If already running as requested account, does nothing.
285 """
286 if not account:
287 # Not actually switching.
288 yield
289 return
290
291 local_auth = luci_context.read('local_auth')
292 if not local_auth:
293 # Not using LUCI_CONTEXT auth at all.
294 yield
295 return
296
297 # See LUCI_CONTEXT.md for the format of 'local_auth'.
298 if local_auth.get('default_account_id') == account:
299 # Already set, no need to switch.
300 yield
301 return
302
303 available = {a['id'] for a in local_auth.get('accounts') or []}
304 if account in available:
305 logging.info('Switching default LUCI_CONTEXT account to %r', account)
306 local_auth['default_account_id'] = account
307 else:
308 logging.warning(
309 'Requested LUCI_CONTEXT account %r is not available (have only %r), '
310 'disabling authentication', account, sorted(available))
311 local_auth.pop('default_account_id', None)
312
313 with luci_context.write(_tmpdir=tmp_dir, local_auth=local_auth):
314 yield
315
316
nodir90bc8dc2016-06-15 13:35:21 -0700317def process_command(command, out_dir, bot_file):
Roberto Carrillo71ade6d2018-10-08 22:30:24 +0000318 """Replaces parameters in a command line.
nodirbe642ff2016-06-09 15:51:51 -0700319
320 Raises:
321 ValueError if a parameter is requested in |command| but its value is not
322 provided.
323 """
Roberto Carrillo71ade6d2018-10-08 22:30:24 +0000324 return [replace_parameters(arg, out_dir, bot_file) for arg in command]
325
326
327def replace_parameters(arg, out_dir, bot_file):
328 """Replaces parameter tokens with appropriate values in a string.
329
330 Raises:
331 ValueError if a parameter is requested in |arg| but its value is not
332 provided.
333 """
334 arg = arg.replace(EXECUTABLE_SUFFIX_PARAMETER, cipd.EXECUTABLE_SUFFIX)
335 replace_slash = False
336 if ISOLATED_OUTDIR_PARAMETER in arg:
337 if not out_dir:
338 raise ValueError(
339 'output directory is requested in command or env var, but not '
340 'provided; please specify one')
341 arg = arg.replace(ISOLATED_OUTDIR_PARAMETER, out_dir)
342 replace_slash = True
343 if SWARMING_BOT_FILE_PARAMETER in arg:
344 if bot_file:
345 arg = arg.replace(SWARMING_BOT_FILE_PARAMETER, bot_file)
nodirbe642ff2016-06-09 15:51:51 -0700346 replace_slash = True
Roberto Carrillo71ade6d2018-10-08 22:30:24 +0000347 else:
348 logging.warning('SWARMING_BOT_FILE_PARAMETER found in command or env '
349 'var, but no bot_file specified. Leaving parameter '
350 'unchanged.')
Joanna Wang4cec0e42021-08-26 00:48:37 +0000351 if SWARMING_TASK_ID_PARAMETER in arg:
352 task_id = os.environ.get('SWARMING_TASK_ID')
353 if task_id:
354 arg = arg.replace(SWARMING_TASK_ID_PARAMETER, task_id)
Roberto Carrillo71ade6d2018-10-08 22:30:24 +0000355 if replace_slash:
356 # Replace slashes only if parameters are present
357 # because of arguments like '${ISOLATED_OUTDIR}/foo/bar'
358 arg = arg.replace('/', os.sep)
359 return arg
maruela9cfd6f2015-09-15 11:03:15 -0700360
361
Takuto Ikuta0f8a19c2021-03-02 00:50:38 +0000362def set_temp_dir(env, tmp_dir):
363 """Set temp dir to given env var dictionary"""
364 tmp_dir = _to_str(tmp_dir)
365 # pylint: disable=line-too-long
366 # * python respects $TMPDIR, $TEMP, and $TMP in this order, regardless of
367 # platform. So $TMPDIR must be set on all platforms.
368 # https://github.com/python/cpython/blob/2.7/Lib/tempfile.py#L155
369 env['TMPDIR'] = tmp_dir
370 if sys.platform == 'win32':
371 # * chromium's base utils uses GetTempPath().
372 # https://cs.chromium.org/chromium/src/base/files/file_util_win.cc?q=GetTempPath
373 # * Go uses GetTempPath().
374 # * GetTempDir() uses %TMP%, then %TEMP%, then other stuff. So %TMP% must be
375 # set.
376 # https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-gettemppathw
377 env['TMP'] = tmp_dir
378 # https://blogs.msdn.microsoft.com/oldnewthing/20150417-00/?p=44213
379 env['TEMP'] = tmp_dir
380 elif sys.platform == 'darwin':
381 # * Chromium uses an hack on macOS before calling into
382 # NSTemporaryDirectory().
383 # https://cs.chromium.org/chromium/src/base/files/file_util_mac.mm?q=GetTempDir
384 # https://developer.apple.com/documentation/foundation/1409211-nstemporarydirectory
385 env['MAC_CHROMIUM_TMPDIR'] = tmp_dir
386 else:
387 # TMPDIR is specified as the POSIX standard envvar for the temp directory.
388 # http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html
389 # * mktemp on linux respects $TMPDIR.
390 # * Chromium respects $TMPDIR on linux.
391 # https://cs.chromium.org/chromium/src/base/files/file_util_posix.cc?q=GetTempDir
392 # * Go uses $TMPDIR.
393 # https://go.googlesource.com/go/+/go1.10.3/src/os/file_unix.go#307
394 pass
395
Roberto Carrillo71ade6d2018-10-08 22:30:24 +0000396
397def get_command_env(tmp_dir, cipd_info, run_dir, env, env_prefixes, out_dir,
398 bot_file):
vadimsh232f5a82017-01-20 19:23:44 -0800399 """Returns full OS environment to run a command in.
400
Robert Iannuccibf5f84c2017-11-22 12:56:50 -0800401 Sets up TEMP, puts directory with cipd binary in front of PATH, exposes
402 CIPD_CACHE_DIR env var, and installs all env_prefixes.
vadimsh232f5a82017-01-20 19:23:44 -0800403
404 Args:
405 tmp_dir: temp directory.
406 cipd_info: CipdInfo object is cipd client is used, None if not.
Marc-Antoine Ruel9ec1e9f2017-12-20 16:36:54 -0500407 run_dir: The root directory the isolated tree is mapped in.
Marc-Antoine Ruel19dd8872017-11-28 18:33:39 -0500408 env: environment variables to use
Robert Iannuccibf5f84c2017-11-22 12:56:50 -0800409 env_prefixes: {"ENV_KEY": ['cwd', 'relative', 'paths', 'to', 'prepend']}
Roberto Carrillo71ade6d2018-10-08 22:30:24 +0000410 out_dir: Isolated output directory. Required to be != None if any of the
411 env vars contain ISOLATED_OUTDIR_PARAMETER.
412 bot_file: Required to be != None if any of the env vars contain
413 SWARMING_BOT_FILE_PARAMETER.
vadimsh232f5a82017-01-20 19:23:44 -0800414 """
Marc-Antoine Ruel19dd8872017-11-28 18:33:39 -0500415 out = os.environ.copy()
Marc-Antoine Ruel04903a32019-10-09 21:09:25 +0000416 for k, v in env.items():
Marc-Antoine Ruel19dd8872017-11-28 18:33:39 -0500417 if not v:
Marc-Antoine Ruel9ec1e9f2017-12-20 16:36:54 -0500418 out.pop(k, None)
Marc-Antoine Ruel19dd8872017-11-28 18:33:39 -0500419 else:
Roberto Carrillo71ade6d2018-10-08 22:30:24 +0000420 out[k] = replace_parameters(v, out_dir, bot_file)
Marc-Antoine Ruel19dd8872017-11-28 18:33:39 -0500421
422 if cipd_info:
423 bin_dir = os.path.dirname(cipd_info.client.binary_path)
Marc-Antoine Ruelee6ca622017-11-29 11:19:16 -0500424 out['PATH'] = '%s%s%s' % (_to_str(bin_dir), os.pathsep, out['PATH'])
425 out['CIPD_CACHE_DIR'] = _to_str(cipd_info.cache_dir)
Takuto Ikuta4ec3e8f2021-04-05 10:21:29 +0000426 cipd_info_path = os.path.join(tmp_dir, 'cipd_info.json')
427 with open(cipd_info_path, 'w') as f:
428 json.dump(cipd_info.pins, f)
429 out['ISOLATED_RESOLVED_PACKAGE_VERSIONS_FILE'] = cipd_info_path
Marc-Antoine Ruel19dd8872017-11-28 18:33:39 -0500430
Marc-Antoine Ruel04903a32019-10-09 21:09:25 +0000431 for key, paths in env_prefixes.items():
Marc-Antoine Ruel9ec1e9f2017-12-20 16:36:54 -0500432 assert isinstance(paths, list), paths
433 paths = [os.path.normpath(os.path.join(run_dir, p)) for p in paths]
Marc-Antoine Ruel19dd8872017-11-28 18:33:39 -0500434 cur = out.get(key)
435 if cur:
436 paths.append(cur)
Marc-Antoine Ruelee6ca622017-11-29 11:19:16 -0500437 out[key] = _to_str(os.path.pathsep.join(paths))
vadimsh232f5a82017-01-20 19:23:44 -0800438
Takuto Ikuta0f8a19c2021-03-02 00:50:38 +0000439 set_temp_dir(out, tmp_dir)
Marc-Antoine Ruel19dd8872017-11-28 18:33:39 -0500440 return out
vadimsh232f5a82017-01-20 19:23:44 -0800441
442
Marc-Antoine Ruel1b65f4e2019-05-02 21:56:58 +0000443def run_command(
444 command, cwd, env, hard_timeout, grace_period, lower_priority, containment):
maruel6be7f9e2015-10-01 12:25:30 -0700445 """Runs the command.
446
447 Returns:
448 tuple(process exit code, bool if had a hard timeout)
449 """
Marc-Antoine Ruel03c6fd12019-04-30 12:12:55 +0000450 logging.info(
Marc-Antoine Ruel1b65f4e2019-05-02 21:56:58 +0000451 'run_command(%s, %s, %s, %s, %s, %s)',
452 command, cwd, hard_timeout, grace_period, lower_priority, containment)
marueleb5fbee2015-09-17 13:01:36 -0700453
maruel6be7f9e2015-10-01 12:25:30 -0700454 exit_code = None
455 had_hard_timeout = False
maruela9cfd6f2015-09-15 11:03:15 -0700456 with tools.Profiler('RunTest'):
maruel6be7f9e2015-10-01 12:25:30 -0700457 proc = None
458 had_signal = []
maruela9cfd6f2015-09-15 11:03:15 -0700459 try:
maruel6be7f9e2015-10-01 12:25:30 -0700460 # TODO(maruel): This code is imperfect. It doesn't handle well signals
461 # during the download phase and there's short windows were things can go
462 # wrong.
463 def handler(signum, _frame):
464 if proc and not had_signal:
465 logging.info('Received signal %d', signum)
466 had_signal.append(True)
maruel556d9052015-10-05 11:12:44 -0700467 raise subprocess42.TimeoutExpired(command, None)
maruel6be7f9e2015-10-01 12:25:30 -0700468
Marc-Antoine Ruel30b80fe2019-02-08 13:51:31 +0000469 proc = subprocess42.Popen(
Marc-Antoine Ruel03c6fd12019-04-30 12:12:55 +0000470 command, cwd=cwd, env=env, detached=True, close_fds=True,
Marc-Antoine Ruel1b65f4e2019-05-02 21:56:58 +0000471 lower_priority=lower_priority, containment=containment)
Joanna Wang40959bf2021-08-12 18:10:12 +0000472 logging.info('Subprocess for command started')
maruel6be7f9e2015-10-01 12:25:30 -0700473 with subprocess42.set_signal_handler(subprocess42.STOP_SIGNALS, handler):
474 try:
John Budorickc398f092019-06-10 22:49:44 +0000475 exit_code = proc.wait(hard_timeout or None)
maruel6be7f9e2015-10-01 12:25:30 -0700476 except subprocess42.TimeoutExpired:
477 if not had_signal:
478 logging.warning('Hard timeout')
479 had_hard_timeout = True
480 logging.warning('Sending SIGTERM')
481 proc.terminate()
482
Takuto Ikuta684f7912020-09-29 07:49:49 +0000483 kill_sent = False
maruel6be7f9e2015-10-01 12:25:30 -0700484 # Ignore signals in grace period. Forcibly give the grace period to the
485 # child process.
486 if exit_code is None:
487 ignore = lambda *_: None
488 with subprocess42.set_signal_handler(subprocess42.STOP_SIGNALS, ignore):
489 try:
490 exit_code = proc.wait(grace_period or None)
491 except subprocess42.TimeoutExpired:
492 # Now kill for real. The user can distinguish between the
493 # following states:
494 # - signal but process exited within grace period,
495 # hard_timed_out will be set but the process exit code will be
496 # script provided.
497 # - processed exited late, exit code will be -9 on posix.
498 logging.warning('Grace exhausted; sending SIGKILL')
499 proc.kill()
Takuto Ikuta684f7912020-09-29 07:49:49 +0000500 kill_sent = True
martiniss5c8043e2017-08-01 17:09:43 -0700501 logging.info('Waiting for process exit')
maruel6be7f9e2015-10-01 12:25:30 -0700502 exit_code = proc.wait()
Takuto Ikuta684f7912020-09-29 07:49:49 +0000503
504 # the process group / job object may be dangling so if we didn't kill
505 # it already, give it a poke now.
506 if not kill_sent:
507 proc.kill()
Takuto Ikutaeccf0862020-03-19 03:05:55 +0000508 except OSError as e:
maruela9cfd6f2015-09-15 11:03:15 -0700509 # This is not considered to be an internal error. The executable simply
510 # does not exit.
maruela72f46e2016-02-24 11:05:45 -0800511 sys.stderr.write(
tikuta2d678212019-09-23 23:12:08 +0000512 '<The executable does not exist, a dependent library is missing or '
513 'the command line is too long>\n'
514 '<Check for missing .so/.dll in the .isolate or GN file or length of '
515 'command line args>\n'
Takuto Ikutae900df42021-04-14 04:40:11 +0000516 '<Command: %s>\n'
517 '<Exception: %s>\n' % (command, e))
maruela72f46e2016-02-24 11:05:45 -0800518 if os.environ.get('SWARMING_TASK_ID'):
519 # Give an additional hint when running as a swarming task.
520 sys.stderr.write(
521 '<See the task\'s page for commands to help diagnose this issue '
522 'by reproducing the task locally>\n')
maruela9cfd6f2015-09-15 11:03:15 -0700523 exit_code = 1
524 logging.info(
525 'Command finished with exit code %d (%s)',
526 exit_code, hex(0xffffffff & exit_code))
maruel6be7f9e2015-10-01 12:25:30 -0700527 return exit_code, had_hard_timeout
maruela9cfd6f2015-09-15 11:03:15 -0700528
529
Takuto Ikuta0f8a19c2021-03-02 00:50:38 +0000530def _run_go_cmd_and_wait(cmd, tmp_dir):
Ye Kuangc0cf9ca2020-07-16 08:56:51 +0000531 """
Junji Watanabe4b890ef2020-09-16 01:43:27 +0000532 Runs an external Go command, `isolated` or `cas`, and wait for its completion.
Ye Kuangc0cf9ca2020-07-16 08:56:51 +0000533
534 While this is a generic function to launch a subprocess, it has logic that
Junji Watanabe4b890ef2020-09-16 01:43:27 +0000535 is specific to Go `isolated` and `cas` for waiting and logging.
Ye Kuangc0cf9ca2020-07-16 08:56:51 +0000536
537 Returns:
538 The subprocess object
539 """
Ye Kuang3c40e9f2020-07-28 13:15:25 +0000540 cmd_str = ' '.join(cmd)
Ye Kuangc1d800f2020-07-28 10:14:55 +0000541 try:
Takuto Ikuta0f8a19c2021-03-02 00:50:38 +0000542 env = os.environ.copy()
543 set_temp_dir(env, tmp_dir)
544 proc = subprocess42.Popen(cmd, env=env)
Ye Kuangc0cf9ca2020-07-16 08:56:51 +0000545
Ye Kuangc1d800f2020-07-28 10:14:55 +0000546 exceeded_max_timeout = True
547 check_period_sec = 30
548 max_checks = 100
549 # max timeout = max_checks * check_period_sec = 50 minutes
550 for i in range(max_checks):
Junji Watanabe4b890ef2020-09-16 01:43:27 +0000551 # This is to prevent I/O timeout error during setup.
Ye Kuangc1d800f2020-07-28 10:14:55 +0000552 try:
553 retcode = proc.wait(check_period_sec)
554 if retcode != 0:
Takuto Ikuta27f4b2f2021-04-26 07:18:55 +0000555 raise subprocess42.CalledProcessError(retcode, cmd=cmd_str)
Ye Kuangc1d800f2020-07-28 10:14:55 +0000556 exceeded_max_timeout = False
557 break
558 except subprocess42.TimeoutExpired:
Junji Watanabe4b890ef2020-09-16 01:43:27 +0000559 print('still running (after %d seconds)' % ((i + 1) * check_period_sec))
Ye Kuangc0cf9ca2020-07-16 08:56:51 +0000560
Ye Kuangc1d800f2020-07-28 10:14:55 +0000561 if exceeded_max_timeout:
562 proc.terminate()
563 try:
564 proc.wait(check_period_sec)
565 except subprocess42.TimeoutExpired:
566 logging.exception(
567 "failed to terminate? timeout happened after %d seconds",
568 check_period_sec)
569 proc.kill()
570 proc.wait()
571 # Raise unconditionally, because |proc| was forcefully terminated.
572 raise ValueError("timedout after %d seconds (cmd=%s)" %
573 (check_period_sec * max_checks, cmd_str))
Ye Kuangc0cf9ca2020-07-16 08:56:51 +0000574
Ye Kuangc1d800f2020-07-28 10:14:55 +0000575 return proc
576 except Exception:
577 logging.exception('Failed to run Go cmd %s', cmd_str)
578 raise
Ye Kuangc0cf9ca2020-07-16 08:56:51 +0000579
580
Junji Watanabe4b890ef2020-09-16 01:43:27 +0000581def _fetch_and_map_with_cas(cas_client, digest, instance, output_dir, cache_dir,
Takuto Ikuta91cb5ca2021-03-17 07:19:30 +0000582 policies, kvs_dir, tmp_dir):
Junji Watanabe4b890ef2020-09-16 01:43:27 +0000583 """
584 Fetches a CAS tree using cas client, create the tree and returns download
585 stats.
586 """
587
588 start = time.time()
589 result_json_handle, result_json_path = tempfile.mkstemp(
590 prefix=u'fetch-and-map-result-', suffix=u'.json')
591 os.close(result_json_handle)
Takuto Ikutad5749ac2021-04-07 06:16:19 +0000592 profile_dir = tempfile.mkdtemp(dir=tmp_dir)
593
Junji Watanabe4b890ef2020-09-16 01:43:27 +0000594 try:
595 cmd = [
596 cas_client,
597 'download',
598 '-digest',
599 digest,
Junji Watanabe4b890ef2020-09-16 01:43:27 +0000600 # flags for cache.
601 '-cache-dir',
602 cache_dir,
Junji Watanabe4b890ef2020-09-16 01:43:27 +0000603 '-cache-max-size',
604 str(policies.max_cache_size),
605 '-cache-min-free-space',
606 str(policies.min_free_space),
607 # flags for output.
608 '-dir',
609 output_dir,
610 '-dump-stats-json',
611 result_json_path,
Takuto Ikuta557025b2021-02-01 08:37:40 +0000612 '-log-level',
Takuto Ikutad5749ac2021-04-07 06:16:19 +0000613 'info',
Junji Watanabe4b890ef2020-09-16 01:43:27 +0000614 ]
Takuto Ikutaae391c52020-12-03 08:43:45 +0000615
Junji Watanabe66d807b2021-11-08 03:20:10 +0000616 # When RUN_ISOLATED_CAS_ADDRESS is set in test mode,
617 # Use it and ignore CAS instance option.
618 cas_addr = os.environ.get('RUN_ISOLATED_CAS_ADDRESS')
619 if cas_addr:
620 cmd.extend([
621 '-cas-addr',
622 cas_addr,
623 ])
624 else:
625 cmd.extend([
626 '-cas-instance',
627 instance
628 ])
629
Takuto Ikuta91cb5ca2021-03-17 07:19:30 +0000630 if kvs_dir:
631 cmd.extend(['-kvs-dir', kvs_dir])
Takuto Ikutaae391c52020-12-03 08:43:45 +0000632
Takuto Ikuta27f4b2f2021-04-26 07:18:55 +0000633 try:
634 _run_go_cmd_and_wait(cmd, tmp_dir)
Takuto Ikuta0909eae2021-04-27 02:54:07 +0000635 except subprocess42.CalledProcessError as ex:
Takuto Ikuta27f4b2f2021-04-26 07:18:55 +0000636 if not kvs_dir:
637 raise
638 logging.exception('Failed to run cas, removing kvs cache dir and retry.')
Takuto Ikuta0909eae2021-04-27 02:54:07 +0000639 on_error.report("Failed to run cas %s" % ex)
Takuto Ikuta27f4b2f2021-04-26 07:18:55 +0000640 file_path.rmtree(kvs_dir)
Takuto Ikutacffabfb2021-11-01 08:05:43 +0000641 file_path.rmtree(output_dir)
Takuto Ikuta27f4b2f2021-04-26 07:18:55 +0000642 _run_go_cmd_and_wait(cmd, tmp_dir)
643
Junji Watanabe4b890ef2020-09-16 01:43:27 +0000644 with open(result_json_path) as json_file:
645 result_json = json.load(json_file)
646
647 return {
648 'duration': time.time() - start,
649 'items_cold': result_json['items_cold'],
650 'items_hot': result_json['items_hot'],
651 }
652 finally:
653 fs.remove(result_json_path)
Takuto Ikutad5749ac2021-04-07 06:16:19 +0000654 file_path.rmtree(profile_dir)
Junji Watanabe4b890ef2020-09-16 01:43:27 +0000655
656
657def _fetch_and_map_with_go_isolated(isolated_hash, storage, outdir,
Takuto Ikuta0f8a19c2021-03-02 00:50:38 +0000658 go_cache_dir, policies, isolated_client,
659 tmp_dir):
Takuto Ikutad03ffcc2019-12-02 01:04:23 +0000660 """
661 Fetches an isolated tree using go client, create the tree and returns
Takuto Ikuta57219f42020-11-02 07:35:36 +0000662 stats.
Takuto Ikutad03ffcc2019-12-02 01:04:23 +0000663 """
664 start = time.time()
665 server_ref = storage.server_ref
Takuto Ikutad03ffcc2019-12-02 01:04:23 +0000666 result_json_handle, result_json_path = tempfile.mkstemp(
667 prefix=u'fetch-and-map-result-', suffix=u'.json')
668 os.close(result_json_handle)
669 try:
Ye Kuanga98764c2020-04-09 03:17:37 +0000670 cmd = [
Takuto Ikutad03ffcc2019-12-02 01:04:23 +0000671 isolated_client,
672 'download',
673 '-isolate-server',
674 server_ref.url,
675 '-namespace',
676 server_ref.namespace,
677 '-isolated',
678 isolated_hash,
679
680 # flags for cache
681 '-cache-dir',
Takuto Ikuta057c5342019-12-03 04:05:05 +0000682 go_cache_dir,
Takuto Ikutad03ffcc2019-12-02 01:04:23 +0000683 '-cache-max-items',
Takuto Ikuta50bc0552019-12-03 03:26:46 +0000684 str(policies.max_items),
Takuto Ikutad03ffcc2019-12-02 01:04:23 +0000685 '-cache-max-size',
Takuto Ikuta50bc0552019-12-03 03:26:46 +0000686 str(policies.max_cache_size),
Takuto Ikutad03ffcc2019-12-02 01:04:23 +0000687 '-cache-min-free-space',
Takuto Ikuta50bc0552019-12-03 03:26:46 +0000688 str(policies.min_free_space),
Takuto Ikutad03ffcc2019-12-02 01:04:23 +0000689
690 # flags for output
691 '-output-dir',
692 outdir,
693 '-fetch-and-map-result-json',
694 result_json_path,
Ye Kuanga98764c2020-04-09 03:17:37 +0000695 ]
Takuto Ikuta0f8a19c2021-03-02 00:50:38 +0000696 _run_go_cmd_and_wait(cmd, tmp_dir)
Takuto Ikuta3153e3b2020-02-18 06:11:47 +0000697
Takuto Ikutad03ffcc2019-12-02 01:04:23 +0000698 with open(result_json_path) as json_file:
699 result_json = json.load(json_file)
700
Takuto Ikuta57219f42020-11-02 07:35:36 +0000701 return {
Takuto Ikutad03ffcc2019-12-02 01:04:23 +0000702 'duration': time.time() - start,
703 'items_cold': result_json['items_cold'],
704 'items_hot': result_json['items_hot'],
Ye Kuang65a1de52020-10-16 08:31:16 +0000705 'initial_number_items': result_json['initial_number_items'],
706 'initial_size': result_json['initial_size'],
Takuto Ikutad03ffcc2019-12-02 01:04:23 +0000707 }
708 finally:
709 fs.remove(result_json_path)
710
711
712# TODO(crbug.com/932396): remove this function.
Takuto Ikuta16fac4b2019-12-09 04:57:18 +0000713def fetch_and_map(isolated_hash, storage, cache, outdir):
Takuto Ikuta57219f42020-11-02 07:35:36 +0000714 """Fetches an isolated tree, create the tree and returns stats."""
nodir6f801882016-04-29 14:41:50 -0700715 start = time.time()
Takuto Ikuta57219f42020-11-02 07:35:36 +0000716 isolateserver.fetch_isolated(
nodir6f801882016-04-29 14:41:50 -0700717 isolated_hash=isolated_hash,
718 storage=storage,
719 cache=cache,
maruel4409e302016-07-19 14:25:51 -0700720 outdir=outdir,
Takuto Ikuta16fac4b2019-12-09 04:57:18 +0000721 use_symlinks=False)
Takuto Ikuta2b9640e2019-06-19 00:53:23 +0000722 hot = (collections.Counter(cache.used) -
723 collections.Counter(cache.added)).elements()
Takuto Ikuta57219f42020-11-02 07:35:36 +0000724 return {
Takuto Ikuta630f99d2020-07-02 12:59:35 +0000725 'duration': time.time() - start,
726 'items_cold': base64.b64encode(large.pack(sorted(cache.added))).decode(),
727 'items_hot': base64.b64encode(large.pack(sorted(hot))).decode(),
nodir6f801882016-04-29 14:41:50 -0700728 }
729
730
aludwin0a8e17d2016-10-27 15:57:39 -0700731def link_outputs_to_outdir(run_dir, out_dir, outputs):
732 """Links any named outputs to out_dir so they can be uploaded.
733
734 Raises an error if the file already exists in that directory.
735 """
736 if not outputs:
737 return
738 isolateserver.create_directories(out_dir, outputs)
739 for o in outputs:
Sadaf Matinkhoo10743a62018-03-29 16:28:58 -0400740 copy_recursively(os.path.join(run_dir, o), os.path.join(out_dir, o))
741
742
743def copy_recursively(src, dst):
744 """Efficiently copies a file or directory from src_dir to dst_dir.
745
746 `item` may be a file, directory, or a symlink to a file or directory.
747 All symlinks are replaced with their targets, so the resulting
748 directory structure in dst_dir will never have any symlinks.
749
750 To increase speed, copy_recursively hardlinks individual files into the
751 (newly created) directory structure if possible, unlike Python's
752 shutil.copytree().
753 """
754 orig_src = src
755 try:
756 # Replace symlinks with their final target.
757 while fs.islink(src):
758 res = fs.readlink(src)
Takuto Ikutaf2ad0a02021-06-24 08:38:40 +0000759 src = os.path.realpath(os.path.join(os.path.dirname(src), res))
Sadaf Matinkhoo10743a62018-03-29 16:28:58 -0400760 # TODO(sadafm): Explicitly handle cyclic symlinks.
761
Takuto Ikutaf2ad0a02021-06-24 08:38:40 +0000762 if not fs.exists(src):
763 logging.warning('Path %s does not exist or %s is a broken symlink', src,
764 orig_src)
765 return
766
Sadaf Matinkhoo10743a62018-03-29 16:28:58 -0400767 if fs.isfile(src):
768 file_path.link_file(dst, src, file_path.HARDLINK_WITH_FALLBACK)
769 return
770
771 if not fs.exists(dst):
772 os.makedirs(dst)
773
774 for child in fs.listdir(src):
775 copy_recursively(os.path.join(src, child), os.path.join(dst, child))
776
777 except OSError as e:
778 if e.errno == errno.ENOENT:
779 logging.warning('Path %s does not exist or %s is a broken symlink',
780 src, orig_src)
781 else:
782 logging.info("Couldn't collect output file %s: %s", src, e)
aludwin0a8e17d2016-10-27 15:57:39 -0700783
784
Ye Kuangfb0bad62020-07-28 08:07:25 +0000785def _upload_with_py(storage, out_dir):
786
787 def process_stats(f_st):
788 st = sorted(i.size for i in f_st)
789 return base64.b64encode(large.pack(st)).decode()
790
791 try:
792 results, f_cold, f_hot = isolateserver.archive_files_to_storage(
793 storage, [out_dir], None, verify_push=True)
794
795 isolated = list(results.values())[0]
796 cold = process_stats(f_cold)
797 hot = process_stats(f_hot)
798 return isolated, cold, hot
799
800 except isolateserver.Aborted:
801 # This happens when a signal SIGTERM was received while uploading data.
802 # There is 2 causes:
803 # - The task was too slow and was about to be killed anyway due to
804 # exceeding the hard timeout.
805 # - The amount of data uploaded back is very large and took too much
806 # time to archive.
807 sys.stderr.write('Received SIGTERM while uploading')
808 # Re-raise, so it will be treated as an internal failure.
809 raise
810
811
Takuto Ikutaf5173872021-05-11 03:18:40 +0000812def upload_out_dir(storage, out_dir):
Ye Kuangbc4e8402020-07-29 09:54:30 +0000813 """Uploads the results in |out_dir| back, if there is any.
maruela9cfd6f2015-09-15 11:03:15 -0700814
815 Returns:
Ye Kuangbc4e8402020-07-29 09:54:30 +0000816 tuple(outputs_ref, stats)
maruel064c0a32016-04-05 11:47:15 -0700817 - outputs_ref: a dict referring to the results archived back to the isolated
818 server, if applicable.
nodir6f801882016-04-29 14:41:50 -0700819 - stats: uploading stats.
maruela9cfd6f2015-09-15 11:03:15 -0700820 """
maruela9cfd6f2015-09-15 11:03:15 -0700821 # Upload out_dir and generate a .isolated file out of this directory. It is
822 # only done if files were written in the directory.
823 outputs_ref = None
Ye Kuangfb0bad62020-07-28 08:07:25 +0000824 cold = ''
825 hot = ''
nodir6f801882016-04-29 14:41:50 -0700826 start = time.time()
827
maruel12e30012015-10-09 11:55:35 -0700828 if fs.isdir(out_dir) and fs.listdir(out_dir):
maruela9cfd6f2015-09-15 11:03:15 -0700829 with tools.Profiler('ArchiveOutput'):
Takuto Ikutaf5173872021-05-11 03:18:40 +0000830 isolated, cold, hot = _upload_with_py(storage, out_dir)
Ye Kuangfb0bad62020-07-28 08:07:25 +0000831 outputs_ref = {
832 'isolated': isolated,
833 'isolatedserver': storage.server_ref.url,
834 'namespace': storage.server_ref.namespace,
835 }
nodir6f801882016-04-29 14:41:50 -0700836
nodir6f801882016-04-29 14:41:50 -0700837 stats = {
Takuto Ikuta630f99d2020-07-02 12:59:35 +0000838 'duration': time.time() - start,
Ye Kuangfb0bad62020-07-28 08:07:25 +0000839 'items_cold': cold,
840 'items_hot': hot,
nodir6f801882016-04-29 14:41:50 -0700841 }
Ye Kuangbc4e8402020-07-29 09:54:30 +0000842 return outputs_ref, stats
maruela9cfd6f2015-09-15 11:03:15 -0700843
844
Takuto Ikuta0f8a19c2021-03-02 00:50:38 +0000845def upload_outdir_with_cas(cas_client, cas_instance, outdir, tmp_dir):
Junji Watanabe1adba7b2020-09-18 07:03:58 +0000846 """Uploads the results in |outdir|, if there is any.
847
848 Returns:
849 tuple(root_digest, stats)
850 - root_digest: a digest of the output directory.
851 - stats: uploading stats.
852 """
853 digest_file_handle, digest_path = tempfile.mkstemp(
854 prefix=u'cas-digest', suffix=u'.txt')
855 os.close(digest_file_handle)
856 stats_json_handle, stats_json_path = tempfile.mkstemp(
857 prefix=u'upload-stats', suffix=u'.json')
858 os.close(stats_json_handle)
859
860 try:
861 cmd = [
862 cas_client,
863 'archive',
Junji Watanabe1adba7b2020-09-18 07:03:58 +0000864 '-paths',
865 # Format: <working directory>:<relative path to dir>
866 outdir + ':',
867 # output
868 '-dump-digest',
869 digest_path,
870 '-dump-stats-json',
871 stats_json_path,
872 ]
873
Junji Watanabe66d807b2021-11-08 03:20:10 +0000874 # When RUN_ISOLATED_CAS_ADDRESS is set in test mode,
875 # Use it and ignore CAS instance option.
876 cas_addr = os.environ.get('RUN_ISOLATED_CAS_ADDRESS')
877 if cas_addr:
878 cmd.extend([
879 '-cas-addr',
880 cas_addr,
881 ])
882 else:
883 cmd.extend([
884 '-cas-instance',
885 cas_instance
886 ])
887
Takuto Ikutabfcef252021-08-25 07:46:19 +0000888 if sys.platform.startswith('linux'):
889 # TODO(crbug.com/1243194): remove this after investigation.
890 cmd.extend(['-log-level', 'debug'])
891
Junji Watanabe1adba7b2020-09-18 07:03:58 +0000892 start = time.time()
893
Takuto Ikuta0f8a19c2021-03-02 00:50:38 +0000894 _run_go_cmd_and_wait(cmd, tmp_dir)
Junji Watanabe1adba7b2020-09-18 07:03:58 +0000895
896 with open(digest_path) as digest_file:
897 digest = digest_file.read()
Junji Watanabec208b302020-09-25 09:18:27 +0000898 h, s = digest.split('/')
899 cas_output_root = {
900 'cas_instance': cas_instance,
901 'digest': {
902 'hash': h,
903 'size_bytes': int(s)
904 }
905 }
Junji Watanabe1adba7b2020-09-18 07:03:58 +0000906 with open(stats_json_path) as stats_file:
907 stats = json.load(stats_file)
908
909 stats['duration'] = time.time() - start
910
Junji Watanabec208b302020-09-25 09:18:27 +0000911 return cas_output_root, stats
Junji Watanabe1adba7b2020-09-18 07:03:58 +0000912 finally:
913 fs.remove(digest_path)
914 fs.remove(stats_json_path)
915
916
Marc-Antoine Ruel7de52592017-12-07 10:41:12 -0500917def map_and_run(data, constant_run_path):
nodir55be77b2016-05-03 09:39:57 -0700918 """Runs a command with optional isolated input/output.
919
Marc-Antoine Ruel7de52592017-12-07 10:41:12 -0500920 Arguments:
921 - data: TaskData instance.
922 - constant_run_path: TODO
nodir55be77b2016-05-03 09:39:57 -0700923
924 Returns metadata about the result.
925 """
Takuto Ikuta00cf8fc2020-01-14 01:36:00 +0000926
927 if data.isolate_cache:
928 download_stats = {
929 #'duration': 0.,
930 'initial_number_items': len(data.isolate_cache),
931 'initial_size': data.isolate_cache.total_size,
932 #'items_cold': '<large.pack()>',
933 #'items_hot': '<large.pack()>',
934 }
935 else:
936 # TODO(tikuta): take stats from state.json in this case too.
937 download_stats = {}
938
maruela9cfd6f2015-09-15 11:03:15 -0700939 result = {
Takuto Ikuta5ed62ad2019-09-26 09:16:00 +0000940 'duration': None,
941 'exit_code': None,
942 'had_hard_timeout': False,
943 'internal_failure': 'run_isolated did not complete properly',
944 'stats': {
Junji Watanabeaee69ad2021-04-28 03:17:34 +0000945 'trim_caches': {
946 'duration': 0,
947 },
Takuto Ikuta5ed62ad2019-09-26 09:16:00 +0000948 #'cipd': {
949 # 'duration': 0.,
950 # 'get_client_duration': 0.,
951 #},
952 'isolated': {
Takuto Ikuta00cf8fc2020-01-14 01:36:00 +0000953 'download': download_stats,
Takuto Ikuta5ed62ad2019-09-26 09:16:00 +0000954 #'upload': {
955 # 'duration': 0.,
956 # 'items_cold': '<large.pack()>',
957 # 'items_hot': '<large.pack()>',
958 #},
959 },
Junji Watanabeaee69ad2021-04-28 03:17:34 +0000960 'named_caches': {
961 'install': {
962 'duration': 0,
963 },
964 'uninstall': {
965 'duration': 0,
966 },
967 },
968 'cleanup': {
969 'duration': 0,
970 }
Marc-Antoine Ruel5d7606b2018-06-15 19:06:12 +0000971 },
Takuto Ikuta5ed62ad2019-09-26 09:16:00 +0000972 #'cipd_pins': {
973 # 'packages': [
974 # {'package_name': ..., 'version': ..., 'path': ...},
975 # ...
976 # ],
977 # 'client_package': {'package_name': ..., 'version': ...},
978 #},
979 'outputs_ref': None,
Junji Watanabe54925c32020-09-08 00:56:18 +0000980 'cas_output_root': None,
Takuto Ikuta5ed62ad2019-09-26 09:16:00 +0000981 'version': 5,
maruela9cfd6f2015-09-15 11:03:15 -0700982 }
nodirbe642ff2016-06-09 15:51:51 -0700983
Takuto Ikutad46ea762020-10-07 05:43:22 +0000984 assert os.path.isabs(data.root_dir), ("data.root_dir is not abs path: %s" %
985 data.root_dir)
986 file_path.ensure_tree(data.root_dir, 0o700)
987
maruele2f2cb82016-07-13 14:41:03 -0700988 # See comment for these constants.
maruelcffa0542017-04-07 08:39:20 -0700989 # TODO(maruel): This is not obvious. Change this to become an error once we
990 # make the constant_run_path an exposed flag.
Marc-Antoine Ruel7de52592017-12-07 10:41:12 -0500991 if constant_run_path and data.root_dir:
992 run_dir = os.path.join(data.root_dir, ISOLATED_RUN_DIR)
maruel5c4eed82017-05-26 05:33:40 -0700993 if os.path.isdir(run_dir):
994 file_path.rmtree(run_dir)
Lei Leife202df2019-06-11 17:33:34 +0000995 os.mkdir(run_dir, 0o700)
maruelcffa0542017-04-07 08:39:20 -0700996 else:
Marc-Antoine Ruel7de52592017-12-07 10:41:12 -0500997 run_dir = make_temp_dir(ISOLATED_RUN_DIR, data.root_dir)
Junji Watanabe1adba7b2020-09-18 07:03:58 +0000998
999 # True if CAS is used for download/upload files.
1000 use_cas = bool(data.cas_digest)
1001
maruel03e11842016-07-14 10:50:16 -07001002 # storage should be normally set but don't crash if it is not. This can happen
1003 # as Swarming task can run without an isolate server.
Junji Watanabe1adba7b2020-09-18 07:03:58 +00001004 out_dir = None
1005 if data.storage or use_cas:
1006 out_dir = make_temp_dir(ISOLATED_OUT_DIR, data.root_dir)
Marc-Antoine Ruel7de52592017-12-07 10:41:12 -05001007 tmp_dir = make_temp_dir(ISOLATED_TMP_DIR, data.root_dir)
Takuto Ikutab7ce0e32019-11-27 23:26:18 +00001008 isolated_client_dir = make_temp_dir(ISOLATED_CLIENT_DIR, data.root_dir)
nodir55be77b2016-05-03 09:39:57 -07001009 cwd = run_dir
Marc-Antoine Ruel95068cf2017-12-07 21:35:05 -05001010 if data.relative_cwd:
1011 cwd = os.path.normpath(os.path.join(cwd, data.relative_cwd))
Marc-Antoine Ruel7de52592017-12-07 10:41:12 -05001012 command = data.command
Ye Kuangfb0bad62020-07-28 08:07:25 +00001013 go_isolated_client = None
1014 if data.use_go_isolated:
1015 go_isolated_client = os.path.join(isolated_client_dir,
1016 'isolated' + cipd.EXECUTABLE_SUFFIX)
Junji Watanabe1adba7b2020-09-18 07:03:58 +00001017
Junji Watanabe4b890ef2020-09-16 01:43:27 +00001018 cas_client = None
1019 cas_client_dir = make_temp_dir(_CAS_CLIENT_DIR, data.root_dir)
Junji Watanabe1adba7b2020-09-18 07:03:58 +00001020 if use_cas:
Junji Watanabe4b890ef2020-09-16 01:43:27 +00001021 cas_client = os.path.join(cas_client_dir, 'cas' + cipd.EXECUTABLE_SUFFIX)
1022
Junji Watanabeaee69ad2021-04-28 03:17:34 +00001023 data.trim_caches_fn(result['stats']['trim_caches'])
1024
Anirudh Mathukumilli92d57b62021-08-04 23:21:57 +00001025 nsjail_dir = None
1026 if (sys.platform == "linux" and cipd.get_platform() == "amd64" and
1027 data.containment.containment_type == subprocess42.Containment.NSJAIL):
1028 nsjail_dir = make_temp_dir(_NSJAIL_DIR, data.root_dir)
1029
nodir55be77b2016-05-03 09:39:57 -07001030 try:
Anirudh Mathukumilli92d57b62021-08-04 23:21:57 +00001031 with data.install_packages_fn(run_dir, isolated_client_dir, cas_client_dir,
1032 nsjail_dir) as cipd_info:
vadimsh232f5a82017-01-20 19:23:44 -08001033 if cipd_info:
1034 result['stats']['cipd'] = cipd_info.stats
1035 result['cipd_pins'] = cipd_info.pins
nodir90bc8dc2016-06-15 13:35:21 -07001036
Junji Watanabe4b890ef2020-09-16 01:43:27 +00001037 isolated_stats = result['stats'].setdefault('isolated', {})
Marc-Antoine Ruel7de52592017-12-07 10:41:12 -05001038 if data.isolated_hash:
Takuto Ikutad03ffcc2019-12-02 01:04:23 +00001039 if data.use_go_isolated:
Takuto Ikuta57219f42020-11-02 07:35:36 +00001040 stats = _fetch_and_map_with_go_isolated(
Takuto Ikuta90397ca2020-01-08 10:07:55 +00001041 isolated_hash=data.isolated_hash,
1042 storage=data.storage,
Takuto Ikuta90397ca2020-01-08 10:07:55 +00001043 outdir=run_dir,
1044 go_cache_dir=data.go_cache_dir,
Takuto Ikuta879788c2020-01-10 08:00:26 +00001045 policies=data.go_cache_policies,
Takuto Ikuta0f8a19c2021-03-02 00:50:38 +00001046 isolated_client=go_isolated_client,
1047 tmp_dir=tmp_dir)
Takuto Ikuta90397ca2020-01-08 10:07:55 +00001048 else:
Takuto Ikuta57219f42020-11-02 07:35:36 +00001049 stats = fetch_and_map(
Takuto Ikutad03ffcc2019-12-02 01:04:23 +00001050 isolated_hash=data.isolated_hash,
1051 storage=data.storage,
1052 cache=data.isolate_cache,
Takuto Ikuta16fac4b2019-12-09 04:57:18 +00001053 outdir=run_dir)
Marc-Antoine Ruel5d7606b2018-06-15 19:06:12 +00001054 isolated_stats['download'].update(stats)
Takuto Ikutab58dbd12020-06-05 09:29:14 +00001055
Junji Watanabe54925c32020-09-08 00:56:18 +00001056 elif data.cas_digest:
Junji Watanabe4b890ef2020-09-16 01:43:27 +00001057 stats = _fetch_and_map_with_cas(
1058 cas_client=cas_client,
1059 digest=data.cas_digest,
1060 instance=data.cas_instance,
1061 output_dir=run_dir,
Junji Watanabeb03450b2020-09-25 05:09:27 +00001062 cache_dir=data.cas_cache_dir,
Takuto Ikutaae391c52020-12-03 08:43:45 +00001063 policies=data.cas_cache_policies,
Takuto Ikuta91cb5ca2021-03-17 07:19:30 +00001064 kvs_dir=data.cas_kvs,
Takuto Ikuta0f8a19c2021-03-02 00:50:38 +00001065 tmp_dir=tmp_dir)
Junji Watanabe4b890ef2020-09-16 01:43:27 +00001066 isolated_stats['download'].update(stats)
Junji Watanabe54925c32020-09-08 00:56:18 +00001067
maruelabec63c2017-04-26 11:53:24 -07001068 if not command:
1069 # Handle this as a task failure, not an internal failure.
1070 sys.stderr.write(
1071 '<No command was specified!>\n'
1072 '<Please secify a command when triggering your Swarming task>\n')
1073 result['exit_code'] = 1
1074 return result
nodirbe642ff2016-06-09 15:51:51 -07001075
Marc-Antoine Ruel95068cf2017-12-07 21:35:05 -05001076 if not cwd.startswith(run_dir):
1077 # Handle this as a task failure, not an internal failure. This is a
1078 # 'last chance' way to gate against directory escape.
1079 sys.stderr.write('<Relative CWD is outside of run directory!>\n')
1080 result['exit_code'] = 1
1081 return result
1082
1083 if not os.path.isdir(cwd):
1084 # Accepts relative_cwd that does not exist.
Lei Leife202df2019-06-11 17:33:34 +00001085 os.makedirs(cwd, 0o700)
Marc-Antoine Ruel95068cf2017-12-07 21:35:05 -05001086
vadimsh232f5a82017-01-20 19:23:44 -08001087 # If we have an explicit list of files to return, make sure their
1088 # directories exist now.
Marc-Antoine Ruel7de52592017-12-07 10:41:12 -05001089 if data.storage and data.outputs:
1090 isolateserver.create_directories(run_dir, data.outputs)
aludwin0a8e17d2016-10-27 15:57:39 -07001091
Junji Watanabeaee69ad2021-04-28 03:17:34 +00001092 with data.install_named_caches(run_dir, result['stats']['named_caches']):
nodird6160682017-02-02 13:03:35 -08001093 sys.stdout.flush()
1094 start = time.time()
1095 try:
vadimsh9c54b2c2017-07-25 14:08:29 -07001096 # Need to switch the default account before 'get_command_env' call,
1097 # so it can grab correct value of LUCI_CONTEXT env var.
Marc-Antoine Ruel7de52592017-12-07 10:41:12 -05001098 with set_luci_context_account(data.switch_to_account, tmp_dir):
1099 env = get_command_env(
Roberto Carrillo71ade6d2018-10-08 22:30:24 +00001100 tmp_dir, cipd_info, run_dir, data.env, data.env_prefix, out_dir,
1101 data.bot_file)
Brian Sheedy7a761172019-08-30 22:55:14 +00001102 command = tools.find_executable(command, env)
Robert Iannucci24ae76a2018-02-26 12:51:18 -08001103 command = process_command(command, out_dir, data.bot_file)
1104 file_path.ensure_command_has_abs_path(command, cwd)
1105
vadimsh9c54b2c2017-07-25 14:08:29 -07001106 result['exit_code'], result['had_hard_timeout'] = run_command(
Marc-Antoine Ruel03c6fd12019-04-30 12:12:55 +00001107 command, cwd, env, data.hard_timeout, data.grace_period,
Marc-Antoine Ruel1b65f4e2019-05-02 21:56:58 +00001108 data.lower_priority, data.containment)
nodird6160682017-02-02 13:03:35 -08001109 finally:
1110 result['duration'] = max(time.time() - start, 0)
Seth Koehler49139812017-12-19 13:59:33 -05001111
Ye Kuangbc4e8402020-07-29 09:54:30 +00001112 if out_dir:
1113 # Try to link files to the output directory, if specified.
1114 link_outputs_to_outdir(run_dir, out_dir, data.outputs)
1115 isolated_stats = result['stats'].setdefault('isolated', {})
Junji Watanabe1adba7b2020-09-18 07:03:58 +00001116 if use_cas:
1117 result['cas_output_root'], isolated_stats['upload'] = (
Takuto Ikuta0f8a19c2021-03-02 00:50:38 +00001118 upload_outdir_with_cas(cas_client, data.cas_instance, out_dir,
1119 tmp_dir))
Junji Watanabe1adba7b2020-09-18 07:03:58 +00001120 else:
1121 # This could use |go_isolated_client|, so make sure it runs when the
1122 # CIPD package still exists.
1123 result['outputs_ref'], isolated_stats['upload'] = (
Takuto Ikutaf5173872021-05-11 03:18:40 +00001124 upload_out_dir(data.storage, out_dir))
Seth Koehler49139812017-12-19 13:59:33 -05001125 # We successfully ran the command, set internal_failure back to
1126 # None (even if the command failed, it's not an internal error).
1127 result['internal_failure'] = None
maruela9cfd6f2015-09-15 11:03:15 -07001128 except Exception as e:
nodir90bc8dc2016-06-15 13:35:21 -07001129 # An internal error occurred. Report accordingly so the swarming task will
1130 # be retried automatically.
maruel12e30012015-10-09 11:55:35 -07001131 logging.exception('internal failure: %s', e)
maruela9cfd6f2015-09-15 11:03:15 -07001132 result['internal_failure'] = str(e)
1133 on_error.report(None)
aludwin0a8e17d2016-10-27 15:57:39 -07001134
1135 # Clean up
maruela9cfd6f2015-09-15 11:03:15 -07001136 finally:
1137 try:
Junji Watanabeaee69ad2021-04-28 03:17:34 +00001138 cleanup_start = time.time()
Ye Kuangbc4e8402020-07-29 09:54:30 +00001139 success = True
Marc-Antoine Ruel7de52592017-12-07 10:41:12 -05001140 if data.leak_temp_dir:
nodir32a1ec12016-10-26 18:34:07 -07001141 success = True
maruela9cfd6f2015-09-15 11:03:15 -07001142 logging.warning(
1143 'Deliberately leaking %s for later examination', run_dir)
marueleb5fbee2015-09-17 13:01:36 -07001144 else:
maruel84537cb2015-10-16 14:21:28 -07001145 # On Windows rmtree(run_dir) call above has a synchronization effect: it
1146 # finishes only when all task child processes terminate (since a running
1147 # process locks *.exe file). Examine out_dir only after that call
1148 # completes (since child processes may write to out_dir too and we need
1149 # to wait for them to finish).
Junji Watanabeb03450b2020-09-25 05:09:27 +00001150 dirs_to_remove = [run_dir, tmp_dir, isolated_client_dir, cas_client_dir]
Ye Kuangbc4e8402020-07-29 09:54:30 +00001151 if out_dir:
1152 dirs_to_remove.append(out_dir)
1153 for directory in dirs_to_remove:
Takuto Ikuta69c0d662019-11-27 01:18:08 +00001154 if not fs.isdir(directory):
1155 continue
Junji Watanabe9cdfff52021-01-08 07:20:35 +00001156 start = time.time()
maruel84537cb2015-10-16 14:21:28 -07001157 try:
Junji Watanabecc4eefd2021-01-19 01:46:10 +00001158 file_path.rmtree(directory)
maruel84537cb2015-10-16 14:21:28 -07001159 except OSError as e:
Takuto Ikuta69c0d662019-11-27 01:18:08 +00001160 logging.error('rmtree(%r) failed: %s', directory, e)
maruel84537cb2015-10-16 14:21:28 -07001161 success = False
Junji Watanabe9cdfff52021-01-08 07:20:35 +00001162 finally:
1163 logging.info('Cleanup: rmtree(%r) took %d seconds', directory,
1164 time.time() - start)
maruel84537cb2015-10-16 14:21:28 -07001165 if not success:
Takuto Ikuta69c0d662019-11-27 01:18:08 +00001166 sys.stderr.write(
1167 OUTLIVING_ZOMBIE_MSG % (directory, data.grace_period))
Junji Watanabed952bf12021-05-13 03:15:54 +00001168 if sys.platform == 'win32':
1169 subprocess42.check_call(['tasklist.exe', '/V'], stdout=sys.stderr)
1170 else:
1171 subprocess42.check_call(['ps', 'axu'], stdout=sys.stderr)
maruel84537cb2015-10-16 14:21:28 -07001172 if result['exit_code'] == 0:
1173 result['exit_code'] = 1
maruela9cfd6f2015-09-15 11:03:15 -07001174
maruela9cfd6f2015-09-15 11:03:15 -07001175 if not success and result['exit_code'] == 0:
1176 result['exit_code'] = 1
1177 except Exception as e:
1178 # Swallow any exception in the main finally clause.
nodir9130f072016-05-27 13:59:08 -07001179 if out_dir:
1180 logging.exception('Leaking out_dir %s: %s', out_dir, e)
maruela9cfd6f2015-09-15 11:03:15 -07001181 result['internal_failure'] = str(e)
Takuto Ikutaa9a907b2020-04-17 08:50:50 +00001182 on_error.report(None)
Junji Watanabeaee69ad2021-04-28 03:17:34 +00001183 finally:
1184 cleanup_duration = time.time() - cleanup_start
1185 result['stats']['cleanup']['duration'] = cleanup_duration
1186 logging.info('Cleanup: removing directories took %d seconds',
1187 cleanup_duration)
maruela9cfd6f2015-09-15 11:03:15 -07001188 return result
Marc-Antoine Ruel2283ad12014-02-09 11:14:57 -05001189
1190
Marc-Antoine Ruel7de52592017-12-07 10:41:12 -05001191def run_tha_test(data, result_json):
nodir55be77b2016-05-03 09:39:57 -07001192 """Runs an executable and records execution metadata.
1193
nodir55be77b2016-05-03 09:39:57 -07001194 If isolated_hash is specified, downloads the dependencies in the cache,
1195 hardlinks them into a temporary directory and runs the command specified in
1196 the .isolated.
Marc-Antoine Ruel2283ad12014-02-09 11:14:57 -05001197
1198 A temporary directory is created to hold the output files. The content inside
1199 this directory will be uploaded back to |storage| packaged as a .isolated
1200 file.
1201
1202 Arguments:
Marc-Antoine Ruel7de52592017-12-07 10:41:12 -05001203 - data: TaskData instance.
1204 - result_json: File path to dump result metadata into. If set, the process
1205 exit code is always 0 unless an internal error occurred.
maruela9cfd6f2015-09-15 11:03:15 -07001206
1207 Returns:
1208 Process exit code that should be used.
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +00001209 """
maruela76b9ee2015-12-15 06:18:08 -08001210 if result_json:
1211 # Write a json output file right away in case we get killed.
1212 result = {
Junji Watanabe54925c32020-09-08 00:56:18 +00001213 'exit_code': None,
1214 'had_hard_timeout': False,
1215 'internal_failure': 'Was terminated before completion',
1216 'outputs_ref': None,
1217 'cas_output_root': None,
1218 'version': 5,
maruela76b9ee2015-12-15 06:18:08 -08001219 }
1220 tools.write_json(result_json, result, dense=True)
1221
maruela9cfd6f2015-09-15 11:03:15 -07001222 # run_isolated exit code. Depends on if result_json is used or not.
Marc-Antoine Ruel7de52592017-12-07 10:41:12 -05001223 result = map_and_run(data, True)
maruela9cfd6f2015-09-15 11:03:15 -07001224 logging.info('Result:\n%s', tools.format_json(result, dense=True))
bpastene3ae09522016-06-10 17:12:59 -07001225
maruela9cfd6f2015-09-15 11:03:15 -07001226 if result_json:
maruel05d5a882015-09-21 13:59:02 -07001227 # We've found tests to delete 'work' when quitting, causing an exception
1228 # here. Try to recreate the directory if necessary.
nodire5028a92016-04-29 14:38:21 -07001229 file_path.ensure_tree(os.path.dirname(result_json))
maruela9cfd6f2015-09-15 11:03:15 -07001230 tools.write_json(result_json, result, dense=True)
1231 # Only return 1 if there was an internal error.
1232 return int(bool(result['internal_failure']))
maruel@chromium.org781ccf62013-09-17 19:39:47 +00001233
maruela9cfd6f2015-09-15 11:03:15 -07001234 # Marshall into old-style inline output.
1235 if result['outputs_ref']:
Marc-Antoine Ruel793bff32019-04-18 17:50:48 +00001236 # pylint: disable=unsubscriptable-object
maruela9cfd6f2015-09-15 11:03:15 -07001237 data = {
Junji Watanabe38b28b02020-04-23 10:23:30 +00001238 'hash': result['outputs_ref']['isolated'],
1239 'namespace': result['outputs_ref']['namespace'],
1240 'storage': result['outputs_ref']['isolatedserver'],
maruela9cfd6f2015-09-15 11:03:15 -07001241 }
Marc-Antoine Ruelc44f5722015-01-08 16:10:01 -05001242 sys.stdout.flush()
Junji Watanabe38b28b02020-04-23 10:23:30 +00001243 print('[run_isolated_out_hack]%s[/run_isolated_out_hack]' %
1244 tools.format_json(data, dense=True))
maruelb76604c2015-11-11 11:53:44 -08001245 sys.stdout.flush()
maruela9cfd6f2015-09-15 11:03:15 -07001246 return result['exit_code'] or int(bool(result['internal_failure']))
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +00001247
1248
iannuccib58d10d2017-03-18 02:00:25 -07001249# Yielded by 'install_client_and_packages'.
vadimsh232f5a82017-01-20 19:23:44 -08001250CipdInfo = collections.namedtuple('CipdInfo', [
1251 'client', # cipd.CipdClient object
1252 'cache_dir', # absolute path to bot-global cipd tag and instance cache
1253 'stats', # dict with stats to return to the server
1254 'pins', # dict with installed cipd pins to return to the server
1255])
1256
1257
1258@contextlib.contextmanager
Anirudh Mathukumilli92d57b62021-08-04 23:21:57 +00001259def noop_install_packages(_run_dir, _isolated_dir, _cas_dir, _nsjail_dir):
iannuccib58d10d2017-03-18 02:00:25 -07001260 """Placeholder for 'install_client_and_packages' if cipd is disabled."""
vadimsh232f5a82017-01-20 19:23:44 -08001261 yield None
1262
1263
Takuto Ikuta2efc7792019-11-27 14:33:34 +00001264def _install_packages(run_dir, cipd_cache_dir, client, packages):
iannuccib58d10d2017-03-18 02:00:25 -07001265 """Calls 'cipd ensure' for packages.
1266
1267 Args:
1268 run_dir (str): root of installation.
1269 cipd_cache_dir (str): the directory to use for the cipd package cache.
1270 client (CipdClient): the cipd client to use
1271 packages: packages to install, list [(path, package_name, version), ...].
iannuccib58d10d2017-03-18 02:00:25 -07001272
1273 Returns: list of pinned packages. Looks like [
1274 {
1275 'path': 'subdirectory',
1276 'package_name': 'resolved/package/name',
1277 'version': 'deadbeef...',
1278 },
1279 ...
1280 ]
1281 """
1282 package_pins = [None]*len(packages)
1283 def insert_pin(path, name, version, idx):
1284 package_pins[idx] = {
1285 'package_name': name,
1286 # swarming deals with 'root' as '.'
1287 'path': path or '.',
1288 'version': version,
1289 }
1290
1291 by_path = collections.defaultdict(list)
1292 for i, (path, name, version) in enumerate(packages):
1293 # cipd deals with 'root' as ''
1294 if path == '.':
1295 path = ''
1296 by_path[path].append((name, version, i))
1297
1298 pins = client.ensure(
Takuto Ikuta2efc7792019-11-27 14:33:34 +00001299 run_dir,
1300 {
1301 subdir: [(name, vers) for name, vers, _ in pkgs
1302 ] for subdir, pkgs in by_path.items()
1303 },
1304 cache_dir=cipd_cache_dir,
iannuccib58d10d2017-03-18 02:00:25 -07001305 )
1306
Marc-Antoine Ruel04903a32019-10-09 21:09:25 +00001307 for subdir, pin_list in sorted(pins.items()):
iannuccib58d10d2017-03-18 02:00:25 -07001308 this_subdir = by_path[subdir]
1309 for i, (name, version) in enumerate(pin_list):
1310 insert_pin(subdir, name, version, this_subdir[i][2])
1311
Robert Iannucci461b30d2017-12-13 11:34:03 -08001312 assert None not in package_pins, (packages, pins, package_pins)
iannuccib58d10d2017-03-18 02:00:25 -07001313
1314 return package_pins
1315
1316
vadimsh232f5a82017-01-20 19:23:44 -08001317@contextlib.contextmanager
Takuto Ikuta2efc7792019-11-27 14:33:34 +00001318def install_client_and_packages(run_dir, packages, service_url,
Takuto Ikutab7ce0e32019-11-27 23:26:18 +00001319 client_package_name, client_version, cache_dir,
Anirudh Mathukumilli92d57b62021-08-04 23:21:57 +00001320 isolated_dir, cas_dir, nsjail_dir):
vadimsh902948e2017-01-20 15:57:32 -08001321 """Bootstraps CIPD client and installs CIPD packages.
iannucci96fcccc2016-08-30 15:52:22 -07001322
vadimsh232f5a82017-01-20 19:23:44 -08001323 Yields CipdClient, stats, client info and pins (as single CipdInfo object).
1324
1325 Pins and the CIPD client info are in the form of:
iannucci96fcccc2016-08-30 15:52:22 -07001326 [
1327 {
1328 "path": path, "package_name": package_name, "version": version,
1329 },
1330 ...
1331 ]
vadimsh902948e2017-01-20 15:57:32 -08001332 (the CIPD client info is a single dictionary instead of a list)
iannucci96fcccc2016-08-30 15:52:22 -07001333
1334 such that they correspond 1:1 to all input package arguments from the command
1335 line. These dictionaries make their all the way back to swarming, where they
1336 become the arguments of CipdPackage.
nodirbe642ff2016-06-09 15:51:51 -07001337
vadimsh902948e2017-01-20 15:57:32 -08001338 If 'packages' list is empty, will bootstrap CIPD client, but won't install
1339 any packages.
1340
1341 The bootstrapped client (regardless whether 'packages' list is empty or not),
vadimsh232f5a82017-01-20 19:23:44 -08001342 will be made available to the task via $PATH.
vadimsh902948e2017-01-20 15:57:32 -08001343
nodirbe642ff2016-06-09 15:51:51 -07001344 Args:
nodir90bc8dc2016-06-15 13:35:21 -07001345 run_dir (str): root of installation.
vadimsh902948e2017-01-20 15:57:32 -08001346 packages: packages to install, list [(path, package_name, version), ...].
nodirbe642ff2016-06-09 15:51:51 -07001347 service_url (str): CIPD server url, e.g.
1348 "https://chrome-infra-packages.appspot.com."
nodir90bc8dc2016-06-15 13:35:21 -07001349 client_package_name (str): CIPD package name of CIPD client.
1350 client_version (str): Version of CIPD client.
nodirbe642ff2016-06-09 15:51:51 -07001351 cache_dir (str): where to keep cache of cipd clients, packages and tags.
Takuto Ikutab7ce0e32019-11-27 23:26:18 +00001352 isolated_dir (str): where to download isolated client.
Junji Watanabe4b890ef2020-09-16 01:43:27 +00001353 cas_dir (str): where to download cas client.
Anirudh Mathukumilli92d57b62021-08-04 23:21:57 +00001354 nsjail_dir (str): where to download nsjail. If set to None, nsjail is not
1355 downloaded.
nodirbe642ff2016-06-09 15:51:51 -07001356 """
1357 assert cache_dir
nodir90bc8dc2016-06-15 13:35:21 -07001358
nodirbe642ff2016-06-09 15:51:51 -07001359 start = time.time()
nodirbe642ff2016-06-09 15:51:51 -07001360
vadimsh902948e2017-01-20 15:57:32 -08001361 cache_dir = os.path.abspath(cache_dir)
vadimsh232f5a82017-01-20 19:23:44 -08001362 cipd_cache_dir = os.path.join(cache_dir, 'cache') # tag and instance caches
nodir90bc8dc2016-06-15 13:35:21 -07001363 run_dir = os.path.abspath(run_dir)
vadimsh902948e2017-01-20 15:57:32 -08001364 packages = packages or []
nodir90bc8dc2016-06-15 13:35:21 -07001365
nodirbe642ff2016-06-09 15:51:51 -07001366 get_client_start = time.time()
Junji Watanabe4b890ef2020-09-16 01:43:27 +00001367 client_manager = cipd.get_client(cache_dir, service_url, client_package_name,
1368 client_version)
iannucci96fcccc2016-08-30 15:52:22 -07001369
nodirbe642ff2016-06-09 15:51:51 -07001370 with client_manager as client:
1371 get_client_duration = time.time() - get_client_start
nodir90bc8dc2016-06-15 13:35:21 -07001372
iannuccib58d10d2017-03-18 02:00:25 -07001373 package_pins = []
1374 if packages:
Takuto Ikuta2efc7792019-11-27 14:33:34 +00001375 package_pins = _install_packages(run_dir, cipd_cache_dir, client,
1376 packages)
iannuccib58d10d2017-03-18 02:00:25 -07001377
Takuto Ikutab7ce0e32019-11-27 23:26:18 +00001378 # Install isolated client to |isolated_dir|.
Takuto Ikuta02edca22019-11-29 10:04:51 +00001379 _install_packages(isolated_dir, cipd_cache_dir, client,
Takuto Ikuta9c4eb1d2020-10-05 03:40:14 +00001380 [('', ISOLATED_PACKAGE, _LUCI_GO_REVISION)])
Takuto Ikutab7ce0e32019-11-27 23:26:18 +00001381
Junji Watanabe4b890ef2020-09-16 01:43:27 +00001382 # Install cas client to |cas_dir|.
1383 _install_packages(cas_dir, cipd_cache_dir, client,
Takuto Ikuta9c4eb1d2020-10-05 03:40:14 +00001384 [('', _CAS_PACKAGE, _LUCI_GO_REVISION)])
Junji Watanabe4b890ef2020-09-16 01:43:27 +00001385
Anirudh Mathukumilli92d57b62021-08-04 23:21:57 +00001386 # Install nsjail to |nsjail_dir|.
1387 if nsjail_dir is not None:
1388 _install_packages(nsjail_dir, cipd_cache_dir, client,
1389 [('', _NSJAIL_PACKAGE, _NSJAIL_VERSION)])
1390
iannuccib58d10d2017-03-18 02:00:25 -07001391 file_path.make_tree_files_read_only(run_dir)
nodir90bc8dc2016-06-15 13:35:21 -07001392
vadimsh232f5a82017-01-20 19:23:44 -08001393 total_duration = time.time() - start
Junji Watanabe38b28b02020-04-23 10:23:30 +00001394 logging.info('Installing CIPD client and packages took %d seconds',
1395 total_duration)
nodir90bc8dc2016-06-15 13:35:21 -07001396
vadimsh232f5a82017-01-20 19:23:44 -08001397 yield CipdInfo(
Junji Watanabe38b28b02020-04-23 10:23:30 +00001398 client=client,
1399 cache_dir=cipd_cache_dir,
1400 stats={
1401 'duration': total_duration,
1402 'get_client_duration': get_client_duration,
iannuccib58d10d2017-03-18 02:00:25 -07001403 },
Junji Watanabe38b28b02020-04-23 10:23:30 +00001404 pins={
1405 'client_package': {
1406 'package_name': client.package_name,
1407 'version': client.instance_id,
1408 },
1409 'packages': package_pins,
1410 })
nodirbe642ff2016-06-09 15:51:51 -07001411
1412
1413def create_option_parser():
Marc-Antoine Ruelf74cffe2015-07-15 15:21:34 -04001414 parser = logging_utils.OptionParserWithLogging(
nodir55be77b2016-05-03 09:39:57 -07001415 usage='%prog <options> [command to run or extra args]',
maruel@chromium.orgdedbf492013-09-12 20:42:11 +00001416 version=__version__,
1417 log_file=RUN_ISOLATED_LOG_FILE)
maruela9cfd6f2015-09-15 11:03:15 -07001418 parser.add_option(
Junji Watanabe38b28b02020-04-23 10:23:30 +00001419 '--clean',
1420 action='store_true',
maruel36a963d2016-04-08 17:15:49 -07001421 help='Cleans the cache, trimming it necessary and remove corrupted items '
Junji Watanabe38b28b02020-04-23 10:23:30 +00001422 'and returns without executing anything; use with -v to know what '
1423 'was done')
maruel36a963d2016-04-08 17:15:49 -07001424 parser.add_option(
maruela9cfd6f2015-09-15 11:03:15 -07001425 '--json',
1426 help='dump output metadata to json file. When used, run_isolated returns '
Junji Watanabe38b28b02020-04-23 10:23:30 +00001427 'non-zero only on internal failure')
maruel6be7f9e2015-10-01 12:25:30 -07001428 parser.add_option(
maruel5c9e47b2015-12-18 13:02:30 -08001429 '--hard-timeout', type='float', help='Enforce hard timeout in execution')
maruel6be7f9e2015-10-01 12:25:30 -07001430 parser.add_option(
Junji Watanabe38b28b02020-04-23 10:23:30 +00001431 '--grace-period',
1432 type='float',
maruel6be7f9e2015-10-01 12:25:30 -07001433 help='Grace period between SIGTERM and SIGKILL')
bpastene3ae09522016-06-10 17:12:59 -07001434 parser.add_option(
Marc-Antoine Ruel95068cf2017-12-07 21:35:05 -05001435 '--relative-cwd',
Takuto Ikuta18ca29a2020-12-04 07:34:20 +00001436 help='Ignore the isolated \'relative_cwd\' and use this one instead')
Marc-Antoine Ruel95068cf2017-12-07 21:35:05 -05001437 parser.add_option(
Junji Watanabe38b28b02020-04-23 10:23:30 +00001438 '--env',
1439 default=[],
1440 action='append',
Marc-Antoine Ruel19dd8872017-11-28 18:33:39 -05001441 help='Environment variables to set for the child process')
1442 parser.add_option(
Junji Watanabe38b28b02020-04-23 10:23:30 +00001443 '--env-prefix',
1444 default=[],
1445 action='append',
Robert Iannuccibf5f84c2017-11-22 12:56:50 -08001446 help='Specify a VAR=./path/fragment to put in the environment variable '
Junji Watanabe38b28b02020-04-23 10:23:30 +00001447 'before executing the command. The path fragment must be relative '
1448 'to the isolated run directory, and must not contain a `..` token. '
1449 'The path will be made absolute and prepended to the indicated '
1450 '$VAR using the OS\'s path separator. Multiple items for the same '
1451 '$VAR will be prepended in order.')
Robert Iannuccibf5f84c2017-11-22 12:56:50 -08001452 parser.add_option(
bpastene3ae09522016-06-10 17:12:59 -07001453 '--bot-file',
1454 help='Path to a file describing the state of the host. The content is '
Junji Watanabe38b28b02020-04-23 10:23:30 +00001455 'defined by on_before_task() in bot_config.')
aludwin7556e0c2016-10-26 08:46:10 -07001456 parser.add_option(
vadimsh9c54b2c2017-07-25 14:08:29 -07001457 '--switch-to-account',
1458 help='If given, switches LUCI_CONTEXT to given logical service account '
Junji Watanabe38b28b02020-04-23 10:23:30 +00001459 '(e.g. "task" or "system") before launching the isolated process.')
vadimsh9c54b2c2017-07-25 14:08:29 -07001460 parser.add_option(
Junji Watanabe38b28b02020-04-23 10:23:30 +00001461 '--output',
1462 action='append',
aludwin0a8e17d2016-10-27 15:57:39 -07001463 help='Specifies an output to return. If no outputs are specified, all '
Junji Watanabe38b28b02020-04-23 10:23:30 +00001464 'files located in $(ISOLATED_OUTDIR) will be returned; '
1465 'otherwise, outputs in both $(ISOLATED_OUTDIR) and those '
1466 'specified by --output option (there can be multiple) will be '
1467 'returned. Note that if a file in OUT_DIR has the same path '
1468 'as an --output option, the --output version will be returned.')
aludwin0a8e17d2016-10-27 15:57:39 -07001469 parser.add_option(
Junji Watanabe38b28b02020-04-23 10:23:30 +00001470 '-a',
1471 '--argsfile',
aludwin7556e0c2016-10-26 08:46:10 -07001472 # This is actually handled in parse_args; it's included here purely so it
1473 # can make it into the help text.
1474 help='Specify a file containing a JSON array of arguments to this '
Junji Watanabe38b28b02020-04-23 10:23:30 +00001475 'script. If --argsfile is provided, no other argument may be '
1476 'provided on the command line.')
Takuto Ikutad4be2f12020-05-12 02:15:25 +00001477 parser.add_option(
1478 '--report-on-exception',
1479 action='store_true',
1480 help='Whether report exception during execution to isolate server. '
1481 'This flag should only be used in swarming bot.')
Marc-Antoine Ruel1b65f4e2019-05-02 21:56:58 +00001482
Junji Watanabe4b890ef2020-09-16 01:43:27 +00001483 group = optparse.OptionGroup(parser, 'Data source - Isolate server')
Junji Watanabe54925c32020-09-08 00:56:18 +00001484 # Deprecated. Isoate server is being migrated to RBE-CAS.
1485 # Remove --isolated and isolate server options after migration.
Marc-Antoine Ruel1b65f4e2019-05-02 21:56:58 +00001486 group.add_option(
Marc-Antoine Ruel185ded42015-01-28 20:49:18 -05001487 '-s', '--isolated',
nodir55be77b2016-05-03 09:39:57 -07001488 help='Hash of the .isolated to grab from the isolate server.')
Marc-Antoine Ruel1b65f4e2019-05-02 21:56:58 +00001489 isolateserver.add_isolate_server_options(group)
Junji Watanabe4b890ef2020-09-16 01:43:27 +00001490 parser.add_option_group(group)
1491
1492 group = optparse.OptionGroup(parser,
1493 'Data source - Content Addressed Storage')
Junji Watanabe54925c32020-09-08 00:56:18 +00001494 group.add_option(
1495 '--cas-instance', help='Full CAS instance name for input/output files.')
1496 group.add_option(
1497 '--cas-digest',
1498 help='Digest of the input root on RBE-CAS. The format is '
1499 '`{hash}/{size_bytes}`.')
Marc-Antoine Ruel1b65f4e2019-05-02 21:56:58 +00001500 parser.add_option_group(group)
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +00001501
Junji Watanabeb03450b2020-09-25 05:09:27 +00001502 # Cache options.
Marc-Antoine Ruela57d7db2014-10-15 20:31:19 -04001503 isolateserver.add_cache_options(parser)
Junji Watanabeb03450b2020-09-25 05:09:27 +00001504 add_cas_cache_options(parser)
nodirbe642ff2016-06-09 15:51:51 -07001505
1506 cipd.add_cipd_options(parser)
Marc-Antoine Ruel8b11dbd2018-05-18 14:31:22 -04001507
1508 group = optparse.OptionGroup(parser, 'Named caches')
1509 group.add_option(
1510 '--named-cache',
1511 dest='named_caches',
1512 action='append',
Marc-Antoine Ruelc7a704b2018-08-29 19:02:23 +00001513 nargs=3,
Marc-Antoine Ruel8b11dbd2018-05-18 14:31:22 -04001514 default=[],
Marc-Antoine Ruelc7a704b2018-08-29 19:02:23 +00001515 help='A named cache to request. Accepts 3 arguments: name, path, hint. '
Junji Watanabe38b28b02020-04-23 10:23:30 +00001516 'name identifies the cache, must match regex [a-z0-9_]{1,4096}. '
1517 'path is a path relative to the run dir where the cache directory '
1518 'must be put to. '
1519 'This option can be specified more than once.')
Marc-Antoine Ruel8b11dbd2018-05-18 14:31:22 -04001520 group.add_option(
Junji Watanabe38b28b02020-04-23 10:23:30 +00001521 '--named-cache-root',
1522 default='named_caches',
Marc-Antoine Ruel8b11dbd2018-05-18 14:31:22 -04001523 help='Cache root directory. Default=%default')
1524 parser.add_option_group(group)
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +00001525
Marc-Antoine Ruel1b65f4e2019-05-02 21:56:58 +00001526 group = optparse.OptionGroup(parser, 'Process containment')
1527 parser.add_option(
1528 '--lower-priority', action='store_true',
1529 help='Lowers the child process priority')
1530 parser.add_option(
Junji Watanabe38b28b02020-04-23 10:23:30 +00001531 '--containment-type',
Anirudh Mathukumilli92d57b62021-08-04 23:21:57 +00001532 choices=('NONE', 'AUTO', 'JOB_OBJECT', 'NSJAIL'),
Marc-Antoine Ruel1b65f4e2019-05-02 21:56:58 +00001533 default='NONE',
1534 help='Type of container to use')
1535 parser.add_option(
Junji Watanabe38b28b02020-04-23 10:23:30 +00001536 '--limit-processes',
1537 type='int',
1538 default=0,
Marc-Antoine Ruel1b65f4e2019-05-02 21:56:58 +00001539 help='Maximum number of active processes in the containment')
1540 parser.add_option(
Junji Watanabe38b28b02020-04-23 10:23:30 +00001541 '--limit-total-committed-memory',
1542 type='int',
1543 default=0,
Marc-Antoine Ruel1b65f4e2019-05-02 21:56:58 +00001544 help='Maximum sum of committed memory in the containment')
1545 parser.add_option_group(group)
1546
1547 group = optparse.OptionGroup(parser, 'Debugging')
1548 group.add_option(
Kenneth Russell61d42352014-09-15 11:41:16 -07001549 '--leak-temp-dir',
1550 action='store_true',
nodirbe642ff2016-06-09 15:51:51 -07001551 help='Deliberately leak isolate\'s temp dir for later examination. '
Junji Watanabe38b28b02020-04-23 10:23:30 +00001552 'Default: %default')
1553 group.add_option('--root-dir', help='Use a directory instead of a random one')
Marc-Antoine Ruel1b65f4e2019-05-02 21:56:58 +00001554 parser.add_option_group(group)
Kenneth Russell61d42352014-09-15 11:41:16 -07001555
Vadim Shtayurae34e13a2014-02-02 11:23:26 -08001556 auth.add_auth_options(parser)
nodirbe642ff2016-06-09 15:51:51 -07001557
Ye Kuang1d096cb2020-06-26 08:38:21 +00001558 parser.set_defaults(cache='cache')
nodirbe642ff2016-06-09 15:51:51 -07001559 return parser
1560
1561
Junji Watanabeb03450b2020-09-25 05:09:27 +00001562def add_cas_cache_options(parser):
1563 group = optparse.OptionGroup(parser, 'CAS cache management')
1564 group.add_option(
1565 '--cas-cache',
1566 metavar='DIR',
1567 default='cas-cache',
1568 help='Directory to keep a local cache of the files. Accelerates download '
1569 'by reusing already downloaded files. Default=%default')
Takuto Ikuta7ff4b242020-12-03 08:07:06 +00001570 group.add_option(
Takuto Ikuta91cb5ca2021-03-17 07:19:30 +00001571 '--kvs-dir',
Takuto Ikuta7ff4b242020-12-03 08:07:06 +00001572 default='',
Takuto Ikuta91cb5ca2021-03-17 07:19:30 +00001573 help='CAS cache dir using kvs for small files. Default=%default')
Junji Watanabeb03450b2020-09-25 05:09:27 +00001574 parser.add_option_group(group)
1575
1576
1577def process_cas_cache_options(options):
1578 if options.cas_cache:
1579 policies = local_caching.CachePolicies(
1580 max_cache_size=options.max_cache_size,
1581 min_free_space=options.min_free_space,
1582 # max_items isn't used for CAS cache for now.
1583 max_items=None,
1584 max_age_secs=MAX_AGE_SECS)
1585
1586 return local_caching.DiskContentAddressedCache(
1587 six.text_type(os.path.abspath(options.cas_cache)), policies, trim=False)
1588 return local_caching.MemoryContentAddressedCache()
1589
1590
Marc-Antoine Ruel49f9f8d2018-05-24 15:57:06 -04001591def process_named_cache_options(parser, options, time_fn=None):
Marc-Antoine Ruel8b11dbd2018-05-18 14:31:22 -04001592 """Validates named cache options and returns a CacheManager."""
1593 if options.named_caches and not options.named_cache_root:
1594 parser.error('--named-cache is specified, but --named-cache-root is empty')
Marc-Antoine Ruelc7a704b2018-08-29 19:02:23 +00001595 for name, path, hint in options.named_caches:
Marc-Antoine Ruel8b11dbd2018-05-18 14:31:22 -04001596 if not CACHE_NAME_RE.match(name):
1597 parser.error(
1598 'cache name %r does not match %r' % (name, CACHE_NAME_RE.pattern))
1599 if not path:
1600 parser.error('cache path cannot be empty')
Marc-Antoine Ruelc7a704b2018-08-29 19:02:23 +00001601 try:
Takuto Ikuta630f99d2020-07-02 12:59:35 +00001602 int(hint)
Marc-Antoine Ruelc7a704b2018-08-29 19:02:23 +00001603 except ValueError:
1604 parser.error('cache hint must be a number')
Marc-Antoine Ruel8b11dbd2018-05-18 14:31:22 -04001605 if options.named_cache_root:
1606 # Make these configurable later if there is use case but for now it's fairly
1607 # safe values.
1608 # In practice, a fair chunk of bots are already recycled on a daily schedule
1609 # so this code doesn't have any effect to them, unless they are preloaded
1610 # with a really old cache.
1611 policies = local_caching.CachePolicies(
1612 # 1TiB.
1613 max_cache_size=1024*1024*1024*1024,
1614 min_free_space=options.min_free_space,
1615 max_items=50,
Marc-Antoine Ruel5d7606b2018-06-15 19:06:12 +00001616 max_age_secs=MAX_AGE_SECS)
Takuto Ikuta6e2ff962019-10-29 12:35:27 +00001617 root_dir = six.text_type(os.path.abspath(options.named_cache_root))
John Budorickc6186972020-02-26 00:58:14 +00001618 cache = local_caching.NamedCache(root_dir, policies, time_fn=time_fn)
1619 # Touch any named caches we're going to use to minimize thrashing
1620 # between tasks that request some (but not all) of the same named caches.
John Budorick0a4dab62020-03-02 22:23:35 +00001621 cache.touch(*[name for name, _, _ in options.named_caches])
John Budorickc6186972020-02-26 00:58:14 +00001622 return cache
Marc-Antoine Ruel8b11dbd2018-05-18 14:31:22 -04001623 return None
1624
1625
aludwin7556e0c2016-10-26 08:46:10 -07001626def parse_args(args):
1627 # Create a fake mini-parser just to get out the "-a" command. Note that
1628 # it's not documented here; instead, it's documented in create_option_parser
1629 # even though that parser will never actually get to parse it. This is
1630 # because --argsfile is exclusive with all other options and arguments.
1631 file_argparse = argparse.ArgumentParser(add_help=False)
1632 file_argparse.add_argument('-a', '--argsfile')
1633 (file_args, nonfile_args) = file_argparse.parse_known_args(args)
1634 if file_args.argsfile:
1635 if nonfile_args:
1636 file_argparse.error('Can\'t specify --argsfile with'
1637 'any other arguments (%s)' % nonfile_args)
1638 try:
1639 with open(file_args.argsfile, 'r') as f:
1640 args = json.load(f)
1641 except (IOError, OSError, ValueError) as e:
1642 # We don't need to error out here - "args" is now empty,
1643 # so the call below to parser.parse_args(args) will fail
1644 # and print the full help text.
Marc-Antoine Ruelf899c482019-10-10 23:32:06 +00001645 print('Couldn\'t read arguments: %s' % e, file=sys.stderr)
aludwin7556e0c2016-10-26 08:46:10 -07001646
1647 # Even if we failed to read the args, just call the normal parser now since it
1648 # will print the correct help message.
nodirbe642ff2016-06-09 15:51:51 -07001649 parser = create_option_parser()
Marc-Antoine Ruel90c98162013-12-18 15:11:57 -05001650 options, args = parser.parse_args(args)
Ye Kuangfff1e502020-07-13 13:21:57 +00001651 if not isinstance(options.cipd_enabled, (bool, int)):
1652 options.cipd_enabled = distutils.util.strtobool(options.cipd_enabled)
aludwin7556e0c2016-10-26 08:46:10 -07001653 return (parser, options, args)
1654
1655
Marc-Antoine Ruelc7a704b2018-08-29 19:02:23 +00001656def _calc_named_cache_hint(named_cache, named_caches):
1657 """Returns the expected size of the missing named caches."""
1658 present = named_cache.available
1659 size = 0
Takuto Ikutad169bfd2021-08-02 05:45:09 +00001660 logging.info('available named cache %s', present)
Marc-Antoine Ruelc7a704b2018-08-29 19:02:23 +00001661 for name, _, hint in named_caches:
1662 if name not in present:
Takuto Ikuta630f99d2020-07-02 12:59:35 +00001663 hint = int(hint)
Marc-Antoine Ruelc7a704b2018-08-29 19:02:23 +00001664 if hint > 0:
Takuto Ikuta74686842021-07-30 04:11:03 +00001665 logging.info("named cache hint: %s, %d", name, hint)
Marc-Antoine Ruelc7a704b2018-08-29 19:02:23 +00001666 size += hint
Takuto Ikuta74686842021-07-30 04:11:03 +00001667 logging.info("total size of named cache hint: %d", size)
Marc-Antoine Ruelc7a704b2018-08-29 19:02:23 +00001668 return size
1669
1670
Takuto Ikutaae391c52020-12-03 08:43:45 +00001671def _clean_cmd(parser, options, caches, root):
Takuto Ikuta7ff4b242020-12-03 08:07:06 +00001672 """Cleanup cache dirs/files."""
1673 if options.isolated:
1674 parser.error('Can\'t use --isolated with --clean.')
1675 if options.isolate_server:
1676 parser.error('Can\'t use --isolate-server with --clean.')
1677 if options.json:
1678 parser.error('Can\'t use --json with --clean.')
1679 if options.named_caches:
1680 parser.error('Can\t use --named-cache with --clean.')
1681 if options.cas_instance or options.cas_digest:
1682 parser.error('Can\t use --cas-instance, --cas-digest with --clean.')
1683
1684 logging.info("initial free space: %d", file_path.get_free_space(root))
1685
Takuto Ikuta91cb5ca2021-03-17 07:19:30 +00001686 if options.kvs_dir and fs.isdir(six.text_type(options.kvs_dir)):
Takuto Ikuta7ff4b242020-12-03 08:07:06 +00001687 # Remove kvs file if its size exceeds fixed threshold.
Takuto Ikutab1b70062021-03-22 01:02:41 +00001688 kvs_dir = six.text_type(options.kvs_dir)
1689 size = file_path.get_recursive_size(kvs_dir)
Takuto Ikuta91cb5ca2021-03-17 07:19:30 +00001690 if size >= _CAS_KVS_CACHE_THRESHOLD:
1691 logging.info("remove kvs dir with size: %d", size)
Takuto Ikutab1b70062021-03-22 01:02:41 +00001692 file_path.rmtree(kvs_dir)
Takuto Ikuta7ff4b242020-12-03 08:07:06 +00001693
1694 # Trim first, then clean.
1695 local_caching.trim_caches(
1696 caches,
1697 root,
1698 min_free_space=options.min_free_space,
1699 max_age_secs=MAX_AGE_SECS)
1700 logging.info("free space after trim: %d", file_path.get_free_space(root))
1701 for c in caches:
1702 c.cleanup()
1703 logging.info("free space after cleanup: %d", file_path.get_free_space(root))
1704
1705
aludwin7556e0c2016-10-26 08:46:10 -07001706def main(args):
Marc-Antoine Ruelee6ca622017-11-29 11:19:16 -05001707 # Warning: when --argsfile is used, the strings are unicode instances, when
1708 # parsed normally, the strings are str instances.
aludwin7556e0c2016-10-26 08:46:10 -07001709 (parser, options, args) = parse_args(args)
maruel36a963d2016-04-08 17:15:49 -07001710
Joanna Wang40959bf2021-08-12 18:10:12 +00001711 # Must be logged after parse_args(), which eventually calls
1712 # logging_utils.prepare_logging() which expects no logs before its call.
1713 logging.info('Starting run_isolated script')
1714
Junji Watanabe1d83d282021-05-11 05:50:40 +00001715 SWARMING_SERVER = os.environ.get('SWARMING_SERVER')
1716 SWARMING_TASK_ID = os.environ.get('SWARMING_TASK_ID')
1717 if options.report_on_exception and SWARMING_SERVER:
1718 task_url = None
1719 if SWARMING_TASK_ID:
1720 task_url = '%s/task?id=%s' % (SWARMING_SERVER, SWARMING_TASK_ID)
1721 on_error.report_on_exception_exit(SWARMING_SERVER, source=task_url)
Takuto Ikutad4be2f12020-05-12 02:15:25 +00001722
Marc-Antoine Ruel5028ba22017-08-25 17:37:51 -04001723 if not file_path.enable_symlink():
Marc-Antoine Ruel5a024272019-01-15 20:11:16 +00001724 logging.warning('Symlink support is not enabled')
Marc-Antoine Ruel5028ba22017-08-25 17:37:51 -04001725
Marc-Antoine Ruelc7a704b2018-08-29 19:02:23 +00001726 named_cache = process_named_cache_options(parser, options)
Marc-Antoine Ruel0d8b0f62018-09-10 14:40:35 +00001727 # hint is 0 if there's no named cache.
Marc-Antoine Ruelc7a704b2018-08-29 19:02:23 +00001728 hint = _calc_named_cache_hint(named_cache, options.named_caches)
1729 if hint:
1730 # Increase the --min-free-space value by the hint, and recreate the
1731 # NamedCache instance so it gets the updated CachePolicy.
1732 options.min_free_space += hint
1733 named_cache = process_named_cache_options(parser, options)
1734
Takuto Ikuta5c59a842020-01-24 03:05:24 +00001735 # TODO(crbug.com/932396): Remove this.
Takuto Ikuta4a22c2c2020-06-05 02:02:23 +00001736 use_go_isolated = options.cipd_enabled
Takuto Ikuta5c59a842020-01-24 03:05:24 +00001737
Marc-Antoine Ruel7139d912018-06-15 20:04:42 +00001738 # TODO(maruel): CIPD caches should be defined at an higher level here too, so
1739 # they can be cleaned the same way.
Takuto Ikutaf1c58442020-10-20 09:03:27 +00001740
1741 isolate_cache = isolateserver.process_cache_options(options, trim=False)
1742 cas_cache = process_cas_cache_options(options)
Takuto Ikuta00cf8fc2020-01-14 01:36:00 +00001743
Marc-Antoine Ruel7139d912018-06-15 20:04:42 +00001744 caches = []
1745 if isolate_cache:
1746 caches.append(isolate_cache)
Junji Watanabeb03450b2020-09-25 05:09:27 +00001747 if cas_cache:
1748 caches.append(cas_cache)
Marc-Antoine Ruel7139d912018-06-15 20:04:42 +00001749 if named_cache:
1750 caches.append(named_cache)
Takuto Ikuta6e2ff962019-10-29 12:35:27 +00001751 root = caches[0].cache_dir if caches else six.text_type(os.getcwd())
maruel36a963d2016-04-08 17:15:49 -07001752 if options.clean:
Takuto Ikutaae391c52020-12-03 08:43:45 +00001753 _clean_cmd(parser, options, caches, root)
maruel36a963d2016-04-08 17:15:49 -07001754 return 0
Marc-Antoine Ruelc7a704b2018-08-29 19:02:23 +00001755
1756 # Trim must still be done for the following case:
1757 # - named-cache was used
1758 # - some entries, with a large hint, where missing
1759 # - --min-free-space was increased accordingly, thus trimming is needed
1760 # Otherwise, this will have no effect, as bot_main calls run_isolated with
1761 # --clean after each task.
Takuto Ikutac9ddff22021-02-18 07:58:39 +00001762 additional_buffer = _FREE_SPACE_BUFFER_FOR_CIPD_PACKAGES
Takuto Ikuta91cb5ca2021-03-17 07:19:30 +00001763 if options.kvs_dir:
Takuto Ikuta7f45c592021-02-09 05:57:05 +00001764 additional_buffer += _CAS_KVS_CACHE_THRESHOLD
Junji Watanabeaee69ad2021-04-28 03:17:34 +00001765 # Add some buffer for Go CLI.
1766 min_free_space = options.min_free_space + additional_buffer
1767
1768 def trim_caches_fn(stats):
1769 start = time.time()
1770 local_caching.trim_caches(
1771 caches, root, min_free_space=min_free_space, max_age_secs=MAX_AGE_SECS)
1772 duration = time.time() - start
1773 stats['duration'] = duration
1774 logging.info('trim_caches: took %d seconds', duration)
maruel36a963d2016-04-08 17:15:49 -07001775
Takuto Ikutaf1c58442020-10-20 09:03:27 +00001776 # Save state of isolate/cas cache not to overwrite state from go client.
1777 if use_go_isolated:
1778 isolate_cache.save()
1779 isolate_cache = None
1780 if cas_cache:
1781 cas_cache.save()
1782 cas_cache = None
1783
nodir55be77b2016-05-03 09:39:57 -07001784 if not options.isolated and not args:
1785 parser.error('--isolated or command to run is required.')
1786
Vadim Shtayura5d1efce2014-02-04 10:55:43 -08001787 auth.process_auth_options(parser, options)
nodir55be77b2016-05-03 09:39:57 -07001788
Takuto Ikutaae767b32020-05-11 01:22:19 +00001789 isolateserver.process_isolate_server_options(parser, options, False)
Junji Watanabeed9ce352020-09-25 12:32:07 +00001790 if ISOLATED_OUTDIR_PARAMETER in args and (not options.isolate_server and
1791 not options.cas_instance):
1792 parser.error('%s in args requires --isolate-server or --cas-instance' %
1793 ISOLATED_OUTDIR_PARAMETER)
1794
1795 if options.isolated and not options.isolate_server:
1796 parser.error('--isolated requires --isolate-server')
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +00001797
nodir90bc8dc2016-06-15 13:35:21 -07001798 if options.root_dir:
Takuto Ikuta6e2ff962019-10-29 12:35:27 +00001799 options.root_dir = six.text_type(os.path.abspath(options.root_dir))
Takuto Ikutad46ea762020-10-07 05:43:22 +00001800 else:
1801 options.root_dir = six.text_type(tempfile.mkdtemp(prefix='root'))
maruel12e30012015-10-09 11:55:35 -07001802 if options.json:
Takuto Ikuta6e2ff962019-10-29 12:35:27 +00001803 options.json = six.text_type(os.path.abspath(options.json))
nodir55be77b2016-05-03 09:39:57 -07001804
Marc-Antoine Ruel19dd8872017-11-28 18:33:39 -05001805 if any('=' not in i for i in options.env):
1806 parser.error(
1807 '--env required key=value form. value can be skipped to delete '
1808 'the variable')
Marc-Antoine Ruel7a68f712017-12-01 18:45:18 -05001809 options.env = dict(i.split('=', 1) for i in options.env)
Marc-Antoine Ruel19dd8872017-11-28 18:33:39 -05001810
1811 prefixes = {}
1812 cwd = os.path.realpath(os.getcwd())
1813 for item in options.env_prefix:
1814 if '=' not in item:
1815 parser.error(
1816 '--env-prefix %r is malformed, must be in the form `VAR=./path`'
1817 % item)
Marc-Antoine Ruel7a68f712017-12-01 18:45:18 -05001818 key, opath = item.split('=', 1)
Marc-Antoine Ruel19dd8872017-11-28 18:33:39 -05001819 if os.path.isabs(opath):
1820 parser.error('--env-prefix %r path is bad, must be relative.' % opath)
1821 opath = os.path.normpath(opath)
1822 if not os.path.realpath(os.path.join(cwd, opath)).startswith(cwd):
1823 parser.error(
Junji Watanabe38b28b02020-04-23 10:23:30 +00001824 '--env-prefix %r path is bad, must be relative and not contain `..`.'
1825 % opath)
Marc-Antoine Ruel19dd8872017-11-28 18:33:39 -05001826 prefixes.setdefault(key, []).append(opath)
1827 options.env_prefix = prefixes
Robert Iannuccibf5f84c2017-11-22 12:56:50 -08001828
nodirbe642ff2016-06-09 15:51:51 -07001829 cipd.validate_cipd_options(parser, options)
1830
vadimsh232f5a82017-01-20 19:23:44 -08001831 install_packages_fn = noop_install_packages
Ye Kuang1d096cb2020-06-26 08:38:21 +00001832 tmp_cipd_cache_dir = None
vadimsh902948e2017-01-20 15:57:32 -08001833 if options.cipd_enabled:
Ye Kuang1d096cb2020-06-26 08:38:21 +00001834 cache_dir = options.cipd_cache
1835 if not cache_dir:
1836 tmp_cipd_cache_dir = six.text_type(tempfile.mkdtemp())
1837 cache_dir = tmp_cipd_cache_dir
Anirudh Mathukumilli92d57b62021-08-04 23:21:57 +00001838 install_packages_fn = (lambda run_dir, isolated_dir, cas_dir, nsjail_dir:
1839 install_client_and_packages(
1840 run_dir,
1841 cipd.parse_package_args(options.cipd_packages),
1842 options.cipd_server,
1843 options.cipd_client_package,
1844 options.cipd_client_version,
1845 cache_dir=cache_dir,
1846 isolated_dir=isolated_dir,
1847 cas_dir=cas_dir,
1848 nsjail_dir=nsjail_dir,
1849 ))
nodirbe642ff2016-06-09 15:51:51 -07001850
nodird6160682017-02-02 13:03:35 -08001851 @contextlib.contextmanager
Junji Watanabeaee69ad2021-04-28 03:17:34 +00001852 def install_named_caches(run_dir, stats):
nodird6160682017-02-02 13:03:35 -08001853 # WARNING: this function depends on "options" variable defined in the outer
1854 # function.
Takuto Ikuta6e2ff962019-10-29 12:35:27 +00001855 assert six.text_type(run_dir), repr(run_dir)
Marc-Antoine Ruel49f9f8d2018-05-24 15:57:06 -04001856 assert os.path.isabs(run_dir), run_dir
Takuto Ikuta6e2ff962019-10-29 12:35:27 +00001857 named_caches = [(os.path.join(run_dir, six.text_type(relpath)), name)
1858 for name, relpath, _ in options.named_caches]
Junji Watanabeaee69ad2021-04-28 03:17:34 +00001859 install_start = time.time()
Marc-Antoine Ruelc7a704b2018-08-29 19:02:23 +00001860 for path, name in named_caches:
Marc-Antoine Ruele79ddbf2018-06-13 18:33:07 +00001861 named_cache.install(path, name)
Junji Watanabeaee69ad2021-04-28 03:17:34 +00001862 install_duration = time.time() - install_start
1863 stats['install']['duration'] = install_duration
1864 logging.info('named_caches: install took %d seconds', install_duration)
nodird6160682017-02-02 13:03:35 -08001865 try:
1866 yield
1867 finally:
dnje289d132017-07-07 11:16:44 -07001868 # Uninstall each named cache, returning it to the cache pool. If an
1869 # uninstall fails for a given cache, it will remain in the task's
1870 # temporary space, get cleaned up by the Swarming bot, and be lost.
1871 #
1872 # If the Swarming bot cannot clean up the cache, it will handle it like
1873 # any other bot file that could not be removed.
Junji Watanabeaee69ad2021-04-28 03:17:34 +00001874 uninstall_start = time.time()
Marc-Antoine Ruelc7a704b2018-08-29 19:02:23 +00001875 for path, name in reversed(named_caches):
Marc-Antoine Ruele79ddbf2018-06-13 18:33:07 +00001876 try:
Marc-Antoine Ruele9558372018-08-03 03:41:22 +00001877 # uninstall() doesn't trim but does call save() implicitly. Trimming
1878 # *must* be done manually via periodic 'run_isolated.py --clean'.
Marc-Antoine Ruele79ddbf2018-06-13 18:33:07 +00001879 named_cache.uninstall(path, name)
1880 except local_caching.NamedCacheError:
Takuto Ikuta463ecdd2021-03-05 09:35:38 +00001881 if sys.platform == 'win32':
1882 # Show running processes.
1883 sys.stderr.write("running process\n")
1884 subprocess42.check_call(['tasklist.exe', '/V'], stdout=sys.stderr)
1885
Junji Watanabed2ab86b2021-08-13 07:20:23 +00001886 error = (
1887 'Error while removing named cache %r at %r. The cache will be'
1888 ' lost.' % (path, name))
1889 logging.exception(error)
1890 on_error.report(error)
Junji Watanabeaee69ad2021-04-28 03:17:34 +00001891 uninstall_duration = time.time() - uninstall_start
1892 stats['uninstall']['duration'] = uninstall_duration
1893 logging.info('named_caches: uninstall took %d seconds',
1894 uninstall_duration)
nodirf33b8d62016-10-26 22:34:58 -07001895
Takuto Ikutaf3caa9b2020-11-02 05:38:26 +00001896 command = args
1897 if options.relative_cwd:
1898 a = os.path.normpath(os.path.abspath(options.relative_cwd))
1899 if not a.startswith(os.getcwd()):
1900 parser.error(
1901 '--relative-cwd must not try to escape the working directory')
Marc-Antoine Ruel7de52592017-12-07 10:41:12 -05001902
Marc-Antoine Ruel1b65f4e2019-05-02 21:56:58 +00001903 containment_type = subprocess42.Containment.NONE
1904 if options.containment_type == 'AUTO':
1905 containment_type = subprocess42.Containment.AUTO
1906 if options.containment_type == 'JOB_OBJECT':
1907 containment_type = subprocess42.Containment.JOB_OBJECT
Anirudh Mathukumilli92d57b62021-08-04 23:21:57 +00001908 if options.containment_type == 'NSJAIL':
1909 containment_type = subprocess42.Containment.NSJAIL
1910 # TODO(https://crbug.com/1227833): This object should eventually contain the
1911 # path to the nsjail binary and the nsjail configuration file.
Marc-Antoine Ruel1b65f4e2019-05-02 21:56:58 +00001912 containment = subprocess42.Containment(
1913 containment_type=containment_type,
1914 limit_processes=options.limit_processes,
1915 limit_total_committed_memory=options.limit_total_committed_memory)
1916
Marc-Antoine Ruel7de52592017-12-07 10:41:12 -05001917 data = TaskData(
1918 command=command,
Marc-Antoine Ruel95068cf2017-12-07 21:35:05 -05001919 relative_cwd=options.relative_cwd,
Marc-Antoine Ruel7de52592017-12-07 10:41:12 -05001920 isolated_hash=options.isolated,
1921 storage=None,
1922 isolate_cache=isolate_cache,
Junji Watanabe54925c32020-09-08 00:56:18 +00001923 cas_instance=options.cas_instance,
1924 cas_digest=options.cas_digest,
Marc-Antoine Ruel7de52592017-12-07 10:41:12 -05001925 outputs=options.output,
1926 install_named_caches=install_named_caches,
1927 leak_temp_dir=options.leak_temp_dir,
1928 root_dir=_to_unicode(options.root_dir),
1929 hard_timeout=options.hard_timeout,
1930 grace_period=options.grace_period,
1931 bot_file=options.bot_file,
1932 switch_to_account=options.switch_to_account,
1933 install_packages_fn=install_packages_fn,
Takuto Ikuta5c59a842020-01-24 03:05:24 +00001934 use_go_isolated=use_go_isolated,
Takuto Ikuta10cae642020-01-08 08:12:07 +00001935 go_cache_dir=options.cache,
Takuto Ikuta879788c2020-01-10 08:00:26 +00001936 go_cache_policies=local_caching.CachePolicies(
1937 max_cache_size=options.max_cache_size,
1938 min_free_space=options.min_free_space,
1939 max_items=options.max_items,
1940 max_age_secs=None,
1941 ),
Junji Watanabeb03450b2020-09-25 05:09:27 +00001942 cas_cache_dir=options.cas_cache,
1943 cas_cache_policies=local_caching.CachePolicies(
1944 max_cache_size=options.max_cache_size,
1945 min_free_space=options.min_free_space,
1946 max_items=None,
1947 max_age_secs=None,
1948 ),
Takuto Ikuta91cb5ca2021-03-17 07:19:30 +00001949 cas_kvs=options.kvs_dir,
Marc-Antoine Ruel7de52592017-12-07 10:41:12 -05001950 env=options.env,
Marc-Antoine Ruel03c6fd12019-04-30 12:12:55 +00001951 env_prefix=options.env_prefix,
Marc-Antoine Ruel1b65f4e2019-05-02 21:56:58 +00001952 lower_priority=bool(options.lower_priority),
Junji Watanabeaee69ad2021-04-28 03:17:34 +00001953 containment=containment,
1954 trim_caches_fn=trim_caches_fn)
nodirbe642ff2016-06-09 15:51:51 -07001955 try:
nodir90bc8dc2016-06-15 13:35:21 -07001956 if options.isolate_server:
Marc-Antoine Ruelb8513132018-11-20 19:48:53 +00001957 server_ref = isolate_storage.ServerRef(
nodir90bc8dc2016-06-15 13:35:21 -07001958 options.isolate_server, options.namespace)
Marc-Antoine Ruelb8513132018-11-20 19:48:53 +00001959 storage = isolateserver.get_storage(server_ref)
nodir90bc8dc2016-06-15 13:35:21 -07001960 with storage:
Marc-Antoine Ruel7de52592017-12-07 10:41:12 -05001961 data = data._replace(storage=storage)
nodirf33b8d62016-10-26 22:34:58 -07001962 # Hashing schemes used by |storage| and |isolate_cache| MUST match.
Marc-Antoine Ruelb8513132018-11-20 19:48:53 +00001963 assert storage.server_ref.hash_algo == server_ref.hash_algo
Marc-Antoine Ruel7de52592017-12-07 10:41:12 -05001964 return run_tha_test(data, options.json)
1965 return run_tha_test(data, options.json)
Junji Watanabe38b28b02020-04-23 10:23:30 +00001966 except (cipd.Error, local_caching.NamedCacheError,
1967 local_caching.NoMoreSpace) as ex:
Marc-Antoine Ruelf899c482019-10-10 23:32:06 +00001968 print(ex.message, file=sys.stderr)
Junji Watanabed2ab86b2021-08-13 07:20:23 +00001969 on_error.report(None)
nodirbe642ff2016-06-09 15:51:51 -07001970 return 1
Ye Kuang1d096cb2020-06-26 08:38:21 +00001971 finally:
1972 if tmp_cipd_cache_dir is not None:
1973 try:
1974 file_path.rmtree(tmp_cipd_cache_dir)
1975 except OSError:
1976 logging.exception('Remove tmp_cipd_cache_dir=%s failed',
1977 tmp_cipd_cache_dir)
1978 # Best effort clean up. Failed to do so doesn't affect the outcome.
maruel@chromium.org9c72d4e2012-09-28 19:20:25 +00001979
1980
1981if __name__ == '__main__':
maruel8e4e40c2016-05-30 06:21:07 -07001982 subprocess42.inhibit_os_error_reporting()
csharp@chromium.orgbfb98742013-03-26 20:28:36 +00001983 # Ensure that we are always running with the correct encoding.
vadimsh@chromium.orga4326472013-08-24 02:05:41 +00001984 fix_encoding.fix_encoding()
Ye Kuang2dd17442020-04-22 08:45:52 +00001985 net.set_user_agent('run_isolated.py/' + __version__)
Marc-Antoine Ruel90c98162013-12-18 15:11:57 -05001986 sys.exit(main(sys.argv[1:]))