| # Copyright 2021 The Fuchsia Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| from contextlib import contextmanager |
| |
| from recipe_engine import recipe_api |
| |
| |
| class RbeApi(recipe_api.RecipeApi): |
| """RemoteExecutionApi contains helper functions for using remote execution |
| services via re-client/re-proxy.""" |
| |
| def __init__(self, props, *args, **kwargs): |
| super(RbeApi, self).__init__(*args, **kwargs) |
| |
| self._rbe_path = None |
| self._config_path = None |
| self._instance = props.instance |
| self._started = False |
| |
| @contextmanager |
| def __call__(self): |
| """Make context wrapping reproxy start/stop. |
| |
| Raises: |
| StepFailure or InfraFailure if it fails to start/stop. |
| """ |
| |
| # Save current value of infra_step so we can reset it when we |
| # yield back. |
| is_infra_step = self.m.context.infra_step |
| |
| # Separate invocations of RBE tools should use unique paths to avoid |
| # conflicts between log/metric files. |
| working_dir = self.m.path.mkdtemp(prefix="rbe") |
| |
| with self.m.context(env=self._environment(working_dir), infra_steps=True): |
| try: |
| self._start() |
| with self.m.context(infra_steps=is_infra_step): |
| yield |
| finally: |
| self._stop(working_dir) |
| |
| @property |
| def _bootstrap_path(self): |
| assert self._rbe_path |
| return self._rbe_path.join("bootstrap") |
| |
| def _environment(self, working_dir): |
| cache_dir = self.m.path["cache"].join("rbe") |
| |
| # Environment. The source of truth for remote execution configuration |
| # is the Fuchsia tree (see $FUCHSIA_OUT_DIR/rbe_config.json). These |
| # values are used to modify the configuration in Infrastructure when |
| # appropriate. These should not be used to modify the behavior of the |
| # build in a meaningful way. |
| return { |
| # Override default instance. Infrastructure uses different RBE |
| # backends for different environments. |
| "RBE_instance": self._instance, |
| # Set deps cache path. |
| "RBE_deps_cache_dir": cache_dir.join("deps"), |
| # Set local paths within the task working directory. |
| "RBE_log_dir": working_dir, |
| "RBE_output_dir": working_dir, |
| "RBE_proxy_log_dir": working_dir, |
| "RBE_server_address": "unix://{}".format(working_dir.join("reproxy.sock")), |
| # Use GCE credentials by default. Infrastructure presents an |
| # emulated GCE metadata server in all environments for uniformity. |
| "RBE_use_application_default_credentials": "False", |
| "RBE_use_gce_credentials": "True", |
| } |
| |
| @property |
| def _reproxy_path(self): |
| assert self._rbe_path |
| return self._rbe_path.join("reproxy") |
| |
| def set_path(self, path): |
| """Path to the reproxy/bootstrap binary directory.""" |
| self._rbe_path = path |
| |
| def set_config_path(self, config_path): |
| """Path to the config file for the repository being built. |
| |
| In the case of Fuchsia, this should be set to the path referenced by |
| $FUCHSIA_OUT_DIR/rbe_config.json as reported by `gn gen`. |
| """ |
| self._config_path = config_path |
| |
| def _start(self): |
| """Start reproxy.""" |
| assert not self._started |
| |
| with self.m.step.nest("setup remote execution") as presentation: |
| cmd = [self._bootstrap_path, "--re_proxy={}".format(self._reproxy_path)] |
| if self._config_path: |
| cmd += ["--cfg={}".format(self._config_path)] |
| self.m.step("start reproxy", cmd) |
| self._started = True |
| |
| def _stop(self, working_dir): |
| """Stop reproxy.""" |
| with self.m.step.nest("teardown remote execution") as presentation: |
| cmd = [self._bootstrap_path, "--shutdown"] |
| if self._config_path: |
| cmd += ["--cfg={}".format(self._config_path)] |
| try: |
| self.m.step("stop reproxy", cmd) |
| self._started = False |
| finally: |
| # reproxy/rewrapper/bootstrap record various log information in |
| # a number of locations. At the time of this implementation, |
| # the following log files are used: |
| # 1. bootstrap.<INFO|WARNING|ERROR|FATAL> is standard logging |
| # for `bootstrap`. Each log file includes more severe logging |
| # levels, e.g. bootstrap.WARNING includes WARNNG, ERROR & FATAL |
| # log messages. |
| # 2. rbe_metrics.txt is the text representation of a proto |
| # message that describes metrics related to the rbe execution. |
| # 3. reproxy.<INFO|WARNING|ERROR|FATAL> is standard logging for |
| # `reproxy`. See notes in #1 for more details. |
| # 4. reproxy_log.txt is the log file that records all info |
| # about all actions that are processed through reproxy. |
| # 5. reproxy_outerr.log is merged stderr/stdout of `reproxy`. |
| # 6. rewrapper.<INFO|WARNING|ERROR|FATAL> is standard logging |
| # for `rewrapper`. See notes in #1 for more details. |
| # |
| # We extract the WARNING log messages for each portion of the |
| # local rbe client as well as reproxy stdout/stderr and metrics |
| # from the build by default. If further debugging is required, |
| # you could increase the verbosity of log messages that we |
| # retain in logdog or add the full reproxy_log.txt log file to |
| # the list of outputs. |
| diagnostic_outputs = [ |
| "bootstrap.WARNING", |
| "rbe_metrics.txt", |
| "reproxy.WARNING", |
| "reproxy_outerr.log", |
| "rewrapper.WARNING", |
| ] |
| |
| for output in diagnostic_outputs: |
| path = working_dir.join(output) |
| # Not all builds use rbe, so it might not exist. |
| self.m.path.mock_add_paths(path) |
| if self.m.path.exists(path): |
| self.m.file.read_text( |
| "read {}".format(output), |
| path, |
| test_data="test log", |
| ) |