Jim MacArthur pushed to branch jmac/remote_execution_rebase at BuildStream / buildstream
Commits:
-
5f7f7a20
by Jim MacArthur at 2018-08-17T09:06:37Z
-
a2d7b910
by Jim MacArthur at 2018-08-17T09:09:36Z
-
b2a9027f
by Jim MacArthur at 2018-08-17T09:10:05Z
-
7b763b95
by Jim MacArthur at 2018-08-17T09:11:14Z
-
c067b85e
by Jim MacArthur at 2018-08-17T09:13:09Z
-
29dab6fc
by Jim MacArthur at 2018-08-17T09:13:37Z
8 changed files:
- buildstream/_loader/loader.py
- buildstream/_loader/metaelement.py
- buildstream/_loader/types.py
- buildstream/_platform/linux.py
- buildstream/_project.py
- buildstream/element.py
- buildstream/plugins/elements/autotools.py
- buildstream/sandbox/_sandboxremote.py
Changes:
| ... | ... | @@ -446,6 +446,7 @@ class Loader(): |
| 446 | 446 |
_yaml.node_get(node, list, Symbol.ENV_NOCACHE, default_value=[]),
|
| 447 | 447 |
_yaml.node_get(node, Mapping, Symbol.PUBLIC, default_value={}),
|
| 448 | 448 |
_yaml.node_get(node, Mapping, Symbol.SANDBOX, default_value={}),
|
| 449 |
+ _yaml.node_get(node, Mapping, Symbol.REMOTE_EXECUTION, default_value={}),
|
|
| 449 | 450 |
element_kind == 'junction')
|
| 450 | 451 |
|
| 451 | 452 |
# Cache it now, make sure it's already there before recursing
|
| ... | ... | @@ -39,7 +39,7 @@ class MetaElement(): |
| 39 | 39 |
# first_pass: The element is to be loaded with first pass configuration (junction)
|
| 40 | 40 |
#
|
| 41 | 41 |
def __init__(self, project, name, kind, provenance, sources, config,
|
| 42 |
- variables, environment, env_nocache, public, sandbox,
|
|
| 42 |
+ variables, environment, env_nocache, public, sandbox, remote_execution,
|
|
| 43 | 43 |
first_pass):
|
| 44 | 44 |
self.project = project
|
| 45 | 45 |
self.name = name
|
| ... | ... | @@ -52,6 +52,7 @@ class MetaElement(): |
| 52 | 52 |
self.env_nocache = env_nocache
|
| 53 | 53 |
self.public = public
|
| 54 | 54 |
self.sandbox = sandbox
|
| 55 |
+ self.remote_execution = remote_execution
|
|
| 55 | 56 |
self.build_dependencies = []
|
| 56 | 57 |
self.dependencies = []
|
| 57 | 58 |
self.first_pass = first_pass
|
| ... | ... | @@ -39,6 +39,7 @@ class Symbol(): |
| 39 | 39 |
DIRECTORY = "directory"
|
| 40 | 40 |
JUNCTION = "junction"
|
| 41 | 41 |
SANDBOX = "sandbox"
|
| 42 |
+ REMOTE_EXECUTION = "remote-execution"
|
|
| 42 | 43 |
|
| 43 | 44 |
|
| 44 | 45 |
# Dependency()
|
| ... | ... | @@ -47,7 +47,7 @@ class Linux(Platform): |
| 47 | 47 |
# Inform the bubblewrap sandbox as to whether it can use user namespaces or not
|
| 48 | 48 |
kwargs['user_ns_available'] = self._user_ns_available
|
| 49 | 49 |
kwargs['die_with_parent_available'] = self._die_with_parent_available
|
| 50 |
- return SandboxRemote(*args, **kwargs)
|
|
| 50 |
+ return SandboxBwrap(*args, **kwargs)
|
|
| 51 | 51 |
|
| 52 | 52 |
################################################
|
| 53 | 53 |
# Private Methods #
|
| ... | ... | @@ -127,6 +127,7 @@ class Project(): |
| 127 | 127 |
|
| 128 | 128 |
self.artifact_cache_specs = None
|
| 129 | 129 |
self._sandbox = None
|
| 130 |
+ self._remote_execution = None
|
|
| 130 | 131 |
self._splits = None
|
| 131 | 132 |
|
| 132 | 133 |
self._context.add_project(self)
|
| ... | ... | @@ -458,7 +459,8 @@ class Project(): |
| 458 | 459 |
'aliases', 'name',
|
| 459 | 460 |
'artifacts', 'options',
|
| 460 | 461 |
'fail-on-overlap', 'shell',
|
| 461 |
- 'ref-storage', 'sandbox', 'mirrors'
|
|
| 462 |
+ 'ref-storage', 'sandbox',
|
|
| 463 |
+ 'remote-execution', 'mirrors'
|
|
| 462 | 464 |
])
|
| 463 | 465 |
|
| 464 | 466 |
#
|
| ... | ... | @@ -476,6 +478,9 @@ class Project(): |
| 476 | 478 |
# Load sandbox configuration
|
| 477 | 479 |
self._sandbox = _yaml.node_get(config, Mapping, 'sandbox')
|
| 478 | 480 |
|
| 481 |
+ # Load remote execution configuration
|
|
| 482 |
+ self._remote_execution = _yaml.node_get(config, Mapping, 'remote-execution')
|
|
| 483 |
+ |
|
| 479 | 484 |
# Load project split rules
|
| 480 | 485 |
self._splits = _yaml.node_get(config, Mapping, 'split-rules')
|
| 481 | 486 |
|
| ... | ... | @@ -94,6 +94,7 @@ from . import _signals |
| 94 | 94 |
from . import _site
|
| 95 | 95 |
from ._platform import Platform
|
| 96 | 96 |
from .sandbox._config import SandboxConfig
|
| 97 |
+from .sandbox._sandboxremote import SandboxRemote
|
|
| 97 | 98 |
|
| 98 | 99 |
from .storage.directory import Directory
|
| 99 | 100 |
from .storage._filebaseddirectory import FileBasedDirectory
|
| ... | ... | @@ -249,6 +250,9 @@ class Element(Plugin): |
| 249 | 250 |
# Extract Sandbox config
|
| 250 | 251 |
self.__sandbox_config = self.__extract_sandbox_config(meta)
|
| 251 | 252 |
|
| 253 |
+ # Extract remote execution URL
|
|
| 254 |
+ self.__remote_execution_url = self.__extract_remote_execution_config(meta)
|
|
| 255 |
+ |
|
| 252 | 256 |
def __lt__(self, other):
|
| 253 | 257 |
return self.name < other.name
|
| 254 | 258 |
|
| ... | ... | @@ -2122,7 +2126,24 @@ class Element(Plugin): |
| 2122 | 2126 |
project = self._get_project()
|
| 2123 | 2127 |
platform = Platform.get_platform()
|
| 2124 | 2128 |
|
| 2125 |
- if directory is not None and os.path.exists(directory):
|
|
| 2129 |
+ if self.__remote_execution_url is not None and self.BST_VIRTUAL_DIRECTORY:
|
|
| 2130 |
+ if not self.__artifacts.has_push_remotes(element=self):
|
|
| 2131 |
+ # Give an early warning if remote execution will not work
|
|
| 2132 |
+ raise ElementError("Artifact {} is configured to use remote execution but has no push remotes. "
|
|
| 2133 |
+ .format(self.name) +
|
|
| 2134 |
+ "The remote artifact server(s) may not be correctly configured or contactable.")
|
|
| 2135 |
+ |
|
| 2136 |
+ self.info("Using a remote 'sandbox' for artifact {}".format(self.name))
|
|
| 2137 |
+ sandbox = SandboxRemote(context, project,
|
|
| 2138 |
+ directory,
|
|
| 2139 |
+ stdout=stdout,
|
|
| 2140 |
+ stderr=stderr,
|
|
| 2141 |
+ config=config,
|
|
| 2142 |
+ server_url=self.__remote_execution_url,
|
|
| 2143 |
+ allow_real_directory=False)
|
|
| 2144 |
+ yield sandbox
|
|
| 2145 |
+ elif directory is not None and os.path.exists(directory):
|
|
| 2146 |
+ self.info("Using a local sandbox for artifact {}".format(self.name))
|
|
| 2126 | 2147 |
sandbox = platform.create_sandbox(context, project,
|
| 2127 | 2148 |
directory,
|
| 2128 | 2149 |
stdout=stdout,
|
| ... | ... | @@ -2294,6 +2315,24 @@ class Element(Plugin): |
| 2294 | 2315 |
return SandboxConfig(self.node_get_member(sandbox_config, int, 'build-uid'),
|
| 2295 | 2316 |
self.node_get_member(sandbox_config, int, 'build-gid'))
|
| 2296 | 2317 |
|
| 2318 |
+ def __extract_remote_execution_config(self, meta):
|
|
| 2319 |
+ project = self._get_project()
|
|
| 2320 |
+ project.ensure_fully_loaded()
|
|
| 2321 |
+ rexec_config = _yaml.node_chain_copy(project._remote_execution)
|
|
| 2322 |
+ |
|
| 2323 |
+ # The default config is already composited with the project overrides
|
|
| 2324 |
+ rexec_defaults = _yaml.node_get(self.__defaults, Mapping, 'remote-execution', default_value={})
|
|
| 2325 |
+ rexec_defaults = _yaml.node_chain_copy(rexec_defaults)
|
|
| 2326 |
+ |
|
| 2327 |
+ _yaml.composite(rexec_config, rexec_defaults)
|
|
| 2328 |
+ _yaml.composite(rexec_config, meta.remote_execution)
|
|
| 2329 |
+ _yaml.node_final_assertions(rexec_config)
|
|
| 2330 |
+ |
|
| 2331 |
+ # Rexec config, unlike others, has fixed members so we should validate them
|
|
| 2332 |
+ _yaml.node_validate(rexec_config, ['url'])
|
|
| 2333 |
+ |
|
| 2334 |
+ return self.node_get_member(rexec_config, str, 'url')
|
|
| 2335 |
+ |
|
| 2297 | 2336 |
# This makes a special exception for the split rules, which
|
| 2298 | 2337 |
# elements may extend but whos defaults are defined in the project.
|
| 2299 | 2338 |
#
|
| ... | ... | @@ -57,7 +57,7 @@ from buildstream import BuildElement |
| 57 | 57 |
|
| 58 | 58 |
# Element implementation for the 'autotools' kind.
|
| 59 | 59 |
class AutotoolsElement(BuildElement):
|
| 60 |
- pass
|
|
| 60 |
+ BST_VIRTUAL_DIRECTORY = True
|
|
| 61 | 61 |
|
| 62 | 62 |
|
| 63 | 63 |
# Plugin entry point
|
| ... | ... | @@ -19,6 +19,7 @@ |
| 19 | 19 |
# Jim MacArthur <jim macarthur codethink co uk>
|
| 20 | 20 |
|
| 21 | 21 |
import os
|
| 22 |
+import re
|
|
| 22 | 23 |
import time
|
| 23 | 24 |
|
| 24 | 25 |
import grpc
|
| ... | ... | @@ -45,9 +46,16 @@ class SandboxRemote(Sandbox): |
| 45 | 46 |
|
| 46 | 47 |
def __init__(self, *args, **kwargs):
|
| 47 | 48 |
super().__init__(*args, **kwargs)
|
| 48 |
- self.user_ns_available = kwargs['user_ns_available']
|
|
| 49 |
- self.die_with_parent_available = kwargs['die_with_parent_available']
|
|
| 50 | 49 |
self.cascache = None
|
| 50 |
+ self.server_url = kwargs['server_url']
|
|
| 51 |
+ # Check the format of the url ourselves to save the user from
|
|
| 52 |
+ # whatever error messages grpc will produce
|
|
| 53 |
+ m = re.match('^(.+):(\d+)$', self.server_url)
|
|
| 54 |
+ if m is None:
|
|
| 55 |
+ raise SandboxError("Configured remote URL '{}' does not match the expected layout. " +
|
|
| 56 |
+ "It should be of the form <protocol>://<domain name>:<port>."
|
|
| 57 |
+ .format(server_url))
|
|
| 58 |
+ |
|
| 51 | 59 |
|
| 52 | 60 |
def _get_cascache(self):
|
| 53 | 61 |
if self.cascache is None:
|
| ... | ... | @@ -92,16 +100,16 @@ class SandboxRemote(Sandbox): |
| 92 | 100 |
return None
|
| 93 | 101 |
|
| 94 | 102 |
# Next, try to create a communication channel to the BuildGrid server.
|
| 95 |
- port = 50051
|
|
| 96 |
- channel = grpc.insecure_channel('dekatron.office.codethink.co.uk:{}'.format(port))
|
|
| 97 |
- stub = remote_execution_pb2_grpc.ExecutionStub(channel)
|
|
| 98 | 103 |
|
| 104 |
+ channel = grpc.insecure_channel(self.server_url)
|
|
| 105 |
+ stub = remote_execution_pb2_grpc.ExecutionStub(channel)
|
|
| 99 | 106 |
request = remote_execution_pb2.ExecuteRequest(instance_name='default',
|
| 100 | 107 |
action_digest=action_digest,
|
| 101 | 108 |
skip_cache_lookup=True)
|
| 102 | 109 |
|
| 103 | 110 |
operation_iterator = stub.Execute(request)
|
| 104 |
- |
|
| 111 |
+ if operation_iterator.code() != grpc.StatusCode.OK:
|
|
| 112 |
+ raise SandboxError("GRPC Execute failed; is the remote system connected?")
|
|
| 105 | 113 |
for operation in operation_iterator:
|
| 106 | 114 |
if operation.done:
|
| 107 | 115 |
break
|
