diff options
author | Benjamin Schubert <contact@benschubert.me> | 2019-10-09 17:01:57 +0100 |
---|---|---|
committer | Benjamin Schubert <contact@benschubert.me> | 2019-10-14 15:20:30 +0100 |
commit | cc8c05bdfa64db469860ee27673ea2ad2203cd1f (patch) | |
tree | 6df5b1751a5d6dee3385eb7ef4ba59d438943d2b /src | |
parent | 13d9ab50e96d4a22f26ba9e4b67e7f2088b51edf (diff) | |
download | buildstream-cc8c05bdfa64db469860ee27673ea2ad2203cd1f.tar.gz |
Buildbox-run POCbschubert/buildboxrun-sandbox
Diffstat (limited to 'src')
-rw-r--r-- | src/buildstream/_platform/linux.py | 18 | ||||
-rw-r--r-- | src/buildstream/sandbox/_sandboxbuildboxrun.py | 159 |
2 files changed, 177 insertions, 0 deletions
diff --git a/src/buildstream/_platform/linux.py b/src/buildstream/_platform/linux.py index b400bfaac..be8b771c5 100644 --- a/src/buildstream/_platform/linux.py +++ b/src/buildstream/_platform/linux.py @@ -33,6 +33,7 @@ class Linux(Platform): sandbox_setups = { 'bwrap': self._setup_bwrap_sandbox, 'buildbox': self._setup_buildbox_sandbox, + 'buildbox-run': self._setup_buildboxrun_sandbox, 'chroot': self._setup_chroot_sandbox, 'dummy': self._setup_dummy_sandbox, } @@ -143,3 +144,20 @@ class Linux(Platform): self.check_sandbox_config = self._check_sandbox_config_buildbox self.create_sandbox = self._create_buildbox_sandbox return True + + # Buildbox run sandbox methods + def _check_sandbox_config_buildboxrun(self, config): + from ..sandbox._sandboxbuildboxrun import SandboxBuildBoxRun + return SandboxBuildBoxRun.check_sandbox_config(self, config) + + @staticmethod + def _create_buildboxrun_sandbox(*args, **kwargs): + from ..sandbox._sandboxbuildboxrun import SandboxBuildBoxRun + return SandboxBuildBoxRun(*args, **kwargs) + + def _setup_buildboxrun_sandbox(self): + from ..sandbox._sandboxbuildboxrun import SandboxBuildBoxRun + self._check_sandbox(SandboxBuildBoxRun) + self.check_sandbox_config = self._check_sandbox_config_buildboxrun + self.create_sandbox = self._create_buildboxrun_sandbox + return True diff --git a/src/buildstream/sandbox/_sandboxbuildboxrun.py b/src/buildstream/sandbox/_sandboxbuildboxrun.py new file mode 100644 index 000000000..400d519a3 --- /dev/null +++ b/src/buildstream/sandbox/_sandboxbuildboxrun.py @@ -0,0 +1,159 @@ +import os +import subprocess + +from .. import utils +from .._exceptions import SandboxError +from .._protos.build.bazel.remote.execution.v2 import remote_execution_pb2 +from .._protos.google.rpc import code_pb2 +from . import Sandbox, SandboxCommandError +from .._cas import CASRemote +from ..storage._casbaseddirectory import CasBasedDirectory + + +# SandboxBuildBoxRun() +# +# BuildBox-based sandbox implementation. +# +class SandboxBuildBoxRun(Sandbox): + build_root = "/persistent-cache/userchroot/buildstream" + + @classmethod + def check_available(cls): + try: + utils.get_host_tool("buildbox-run") + except utils.ProgramNotFoundError as Error: + cls._dummy_reasons += ["buildbox-run not found"] + raise SandboxError(" and ".join(cls._dummy_reasons), + reason="unavailable-local-sandbox") from Error + + @classmethod + def check_sandbox_config(cls, platform, config): + # Report error for elements requiring non-0 UID/GID + if config.build_uid != 0 or config.build_gid != 0: + return False + + # Check host os and architecture match + if config.build_os != platform.get_host_os(): + raise SandboxError("Configured and host OS don't match.") + if config.build_arch != platform.get_host_arch(): + raise SandboxError("Configured and host architecture don't match.") + + # FIXME: check that buildbox-casd is configured correctly + # FIXME: check that userchroot is configured correctly + + return True + + def _run(self, command, flags, *, cwd, env): + stdout, stderr = self._get_output() + + if not self._has_command(command[0], env): + raise SandboxCommandError("Staged artifacts do not provide command " + "'{}'".format(command[0]), + reason="missing-command") + + cas_cache = self.get_virtual_directory().cas_cache + + with utils._tempnamedfile() as action_file, utils._tempnamedfile() as result_file, utils._tempdir(dir=self.build_root) as bldroot: + command = self._create_command(command, cwd, env).SerializeToString() + cas_cache.add_object(buffer=command) + # FIXME: we need to have + self._create_action(action_file, command) + + buildbox_command = [ + utils.get_host_tool("buildbox-run"), + "--userchroot-bin={}".format(utils.get_host_tool("userchroot")), + "--use-localcas", + "--remote=unix://{}".format(cas_cache._casd_socket_path), + "--action={}".format(action_file.name), + "--action-result={}".format(result_file.name), + "--log-level=trace", + "--workspace-path={}".format(bldroot), + ] + + # FIXME: handle pausing, sigint, etc correctly + subprocess.check_call(buildbox_command, stdout=stdout, stderr=stderr) + + action_result = remote_execution_pb2.ActionResult().FromString(result_file.read()) + + # Get output of build + self.process_job_output(action_result.output_directories, action_result.output_files, + failure=action_result.exit_code != 0) + + if stdout: + if action_result.stdout_raw: + stdout.write(str(action_result.stdout_raw, 'utf-8', errors='ignore')) + if stderr: + if action_result.stderr_raw: + stderr.write(str(action_result.stderr_raw, 'utf-8', errors='ignore')) + + if action_result.exit_code != 0: + # A normal error during the build: the remote execution system + # has worked correctly but the command failed. + return action_result.exit_code + + return 0 + + def _create_command(self, command, working_directory, environment): + # Creates a command proto + environment_variables = [remote_execution_pb2.Command. + EnvironmentVariable(name=k, value=v) + for (k, v) in environment.items()] + + # Request the whole directory tree as output + output_directory = os.path.relpath(os.path.sep, start=working_directory) + + return remote_execution_pb2.Command(arguments=command, + working_directory=working_directory, + environment_variables=environment_variables, + output_files=[], + output_directories=[output_directory], + platform=None) + + def _create_action(self, action_file, command): + command_digest = utils._message_digest(command) + input_root_digest = self.get_virtual_directory()._get_digest() + + action = remote_execution_pb2.Action(command_digest=command_digest, input_root_digest=input_root_digest) + + action_file.write(action.SerializeToString()) + action_file.seek(0) + + def _use_cas_based_directory(self): + # Always use CasBasedDirectory for BuildBoxRun + return True + + def process_job_output(self, output_directories, output_files, *, failure): + # Reads the remote execution server response to an execution request. + # + # output_directories is an array of OutputDirectory objects. + # output_files is an array of OutputFile objects. + # + # We only specify one output_directory, so it's an error + # for there to be any output files or more than one directory at the moment. + # + if output_files: + raise SandboxError("Output files were returned when we didn't request any.") + if not output_directories: + error_text = "No output directory was returned from the build server." + raise SandboxError(error_text) + if len(output_directories) > 1: + error_text = "More than one output directory was returned from the build server: {}." + raise SandboxError(error_text.format(output_directories)) + + dir_digest = output_directories[0].tree_digest + if dir_digest is None or not dir_digest.hash: + raise SandboxError("Output directory structure had no digest attached.") + + context = self._get_context() + cascache = context.get_cascache() + artifactcache = context.artifactcache + + if dir_digest is None or not dir_digest.hash or not dir_digest.size_bytes: + raise SandboxError("Output directory structure pulling from remote failed.") + + # At the moment, we will get the whole directory back in the first directory argument and we need + # to replace the sandbox's virtual directory with that. Creating a new virtual directory object + # from another hash will be interesting, though... + + new_dir = CasBasedDirectory(artifactcache.cas, digest=dir_digest) + self._set_virtual_directory(new_dir) |