Jim MacArthur pushed to branch jmac/remote_execution_split at BuildStream / buildstream
Commits:
- 
cdcfc0d2
by Jim MacArthur at 2018-11-23T17:53:44Z
- 
46aa0237
by Jim MacArthur at 2018-11-23T17:53:44Z
- 
20cfc39b
by Jim MacArthur at 2018-11-23T17:53:44Z
- 
d980a5c3
by Jim MacArthur at 2018-11-23T17:53:44Z
6 changed files:
- buildstream/_artifactcache/artifactcache.py
- buildstream/sandbox/_sandboxremote.py
- + tests/sandboxes/remote-exec-config.py
- + tests/sandboxes/remote-exec-config/missing-certs/certificates/client.crt
- + tests/sandboxes/remote-exec-config/missing-certs/certificates/client.key
- + tests/sandboxes/remote-exec-config/missing-certs/element.bst
Changes:
| ... | ... | @@ -751,34 +751,6 @@ class ArtifactCache(): | 
| 751 | 751 |  | 
| 752 | 752 |          return message_digest
 | 
| 753 | 753 |  | 
| 754 | -    # verify_digest_pushed():
 | |
| 755 | -    #
 | |
| 756 | -    # Check whether the object is already on the server in which case
 | |
| 757 | -    # there is no need to upload it.
 | |
| 758 | -    #
 | |
| 759 | -    # Args:
 | |
| 760 | -    #     project (Project): The current project
 | |
| 761 | -    #     digest (Digest): The object digest.
 | |
| 762 | -    #
 | |
| 763 | -    def verify_digest_pushed(self, project, digest):
 | |
| 764 | - | |
| 765 | -        if self._has_push_remotes:
 | |
| 766 | -            push_remotes = [r for r in self._remotes[project] if r.spec.push]
 | |
| 767 | -        else:
 | |
| 768 | -            push_remotes = []
 | |
| 769 | - | |
| 770 | -        if not push_remotes:
 | |
| 771 | -            raise ArtifactError("verify_digest_pushed was called, but no remote artifact " +
 | |
| 772 | -                                "servers are configured as push remotes.")
 | |
| 773 | - | |
| 774 | -        pushed = False
 | |
| 775 | - | |
| 776 | -        for remote in push_remotes:
 | |
| 777 | -            if self.cas.verify_digest_on_remote(remote, digest):
 | |
| 778 | -                pushed = True
 | |
| 779 | - | |
| 780 | -        return pushed
 | |
| 781 | - | |
| 782 | 754 |      # link_key():
 | 
| 783 | 755 |      #
 | 
| 784 | 756 |      # Add a key for an existing artifact.
 | 
| ... | ... | @@ -36,8 +36,6 @@ from .. import _yaml | 
| 36 | 36 |  from .._protos.google.longrunning import operations_pb2, operations_pb2_grpc
 | 
| 37 | 37 |  from .._artifactcache.cascache import CASRemote, CASRemoteSpec
 | 
| 38 | 38 |  | 
| 39 | -from .._exceptions import SandboxError
 | |
| 40 | - | |
| 41 | 39 |  | 
| 42 | 40 |  class RemoteExecutionSpec(namedtuple('RemoteExecutionSpec', 'exec_service storage_service')):
 | 
| 43 | 41 |      pass
 | 
| ... | ... | @@ -64,6 +62,7 @@ class SandboxRemote(Sandbox): | 
| 64 | 62 |                                                   server_cert=config.storage_service['server-cert'],
 | 
| 65 | 63 |                                                   client_key=config.storage_service['client-key'],
 | 
| 66 | 64 |                                                   client_cert=config.storage_service['client-cert'])
 | 
| 65 | +        self.operation_name = None
 | |
| 67 | 66 |  | 
| 68 | 67 |      @staticmethod
 | 
| 69 | 68 |      def specs_from_config_node(config_node, basedir):
 | 
| ... | ... | @@ -73,9 +72,9 @@ class SandboxRemote(Sandbox): | 
| 73 | 72 |              if val is None:
 | 
| 74 | 73 |                  provenance = _yaml.node_get_provenance(remote_config, key=keyname)
 | 
| 75 | 74 |                  raise _yaml.LoadError(_yaml.LoadErrorReason.INVALID_DATA,
 | 
| 76 | -                                      "'{}' was not present in the remote "
 | |
| 75 | +                                      "{}: '{}' was not present in the remote "
 | |
| 77 | 76 |                                        "execution configuration (remote-execution). "
 | 
| 78 | -                                      .format(keyname))
 | |
| 77 | +                                      .format(str(provenance), keyname))
 | |
| 79 | 78 |              return val
 | 
| 80 | 79 |  | 
| 81 | 80 |          remote_config = config_node.get("remote-execution", None)
 | 
| ... | ... | @@ -100,9 +99,10 @@ class SandboxRemote(Sandbox): | 
| 100 | 99 |              else:
 | 
| 101 | 100 |                  provenance = _yaml.node_get_provenance(remote_config, key='url')
 | 
| 102 | 101 |                  raise _yaml.LoadError(_yaml.LoadErrorReason.INVALID_DATA,
 | 
| 103 | -                                      "'url' and 'execution-service' keys were found in the remote "
 | |
| 102 | +                                      "{}: 'url' and 'execution-service' keys were found in the remote "
 | |
| 104 | 103 |                                        "execution configuration (remote-execution). "
 | 
| 105 | -                                      "You can only specify one of these.")
 | |
| 104 | +                                      "You can only specify one of these."
 | |
| 105 | +                                      .format(str(provenance)))
 | |
| 106 | 106 |  | 
| 107 | 107 |          for key in tls_keys:
 | 
| 108 | 108 |              if key not in remote_exec_storage_config:
 | 
| ... | ... | @@ -139,7 +139,6 @@ class SandboxRemote(Sandbox): | 
| 139 | 139 |          command_digest = cascache.push_message(casremote, remote_command)
 | 
| 140 | 140 |          if not command_digest or not cascache.verify_digest_on_remote(casremote, command_digest):
 | 
| 141 | 141 |              raise SandboxError("Failed pushing build command to remote CAS.")
 | 
| 142 | -        print("Created command digest: {}".format(command_digest.hash))
 | |
| 143 | 142 |          # Create and send the action.
 | 
| 144 | 143 |          action = remote_execution_pb2.Action(command_digest=command_digest,
 | 
| 145 | 144 |                                               input_root_digest=input_root_digest,
 | 
| ... | ... | @@ -148,7 +147,6 @@ class SandboxRemote(Sandbox): | 
| 148 | 147 |  | 
| 149 | 148 |          # Upload the Action message to the remote CAS server
 | 
| 150 | 149 |          action_digest = cascache.push_message(casremote, action)
 | 
| 151 | -        print("Created action digest: {}".format(action_digest.hash))
 | |
| 152 | 150 |          if not action_digest or not cascache.verify_digest_on_remote(casremote, action_digest):
 | 
| 153 | 151 |              raise SandboxError("Failed pushing build action to remote CAS.")
 | 
| 154 | 152 |  | 
| ... | ... | @@ -289,14 +287,14 @@ class SandboxRemote(Sandbox): | 
| 289 | 287 |          # Upload sources
 | 
| 290 | 288 |          upload_vdir = self.get_virtual_directory()
 | 
| 291 | 289 |  | 
| 290 | +        cascache = self._get_context().get_cascache()
 | |
| 292 | 291 |          if isinstance(upload_vdir, FileBasedDirectory):
 | 
| 293 | 292 |              # Make a new temporary directory to put source in
 | 
| 294 | -            upload_vdir = CasBasedDirectory(self._get_context().artifactcache.cas, ref=None)
 | |
| 293 | +            upload_vdir = CasBasedDirectory(cascache, ref=None)
 | |
| 295 | 294 |              upload_vdir.import_files(self.get_virtual_directory()._get_underlying_directory())
 | 
| 296 | 295 |  | 
| 297 | 296 |          upload_vdir.recalculate_hash()
 | 
| 298 | 297 |  | 
| 299 | -        cascache = context.get_cascache()
 | |
| 300 | 298 |          casremote = CASRemote(self.storage_remote_spec)
 | 
| 301 | 299 |          # Now, push that key (without necessarily needing a ref) to the remote.
 | 
| 302 | 300 |  | 
| 1 | +import pytest
 | |
| 2 | + | |
| 3 | +import itertools
 | |
| 4 | +import os
 | |
| 5 | + | |
| 6 | +from buildstream import _yaml
 | |
| 7 | +from buildstream._exceptions import ErrorDomain, LoadErrorReason
 | |
| 8 | + | |
| 9 | +from tests.testutils.runcli import cli
 | |
| 10 | + | |
| 11 | +DATA_DIR = os.path.join(
 | |
| 12 | +    os.path.dirname(os.path.realpath(__file__)),
 | |
| 13 | +    "remote-exec-config"
 | |
| 14 | +)
 | |
| 15 | + | |
| 16 | +# Tests that we get a useful error message when supplying invalid
 | |
| 17 | +# remote execution configurations.
 | |
| 18 | + | |
| 19 | + | |
| 20 | +# Assert that if both 'url' (the old style) and 'execution-service' (the new style)
 | |
| 21 | +# are used at once, a LoadError results.
 | |
| 22 | +@pytest.mark.datafiles(DATA_DIR)
 | |
| 23 | +def test_old_and_new_configs(cli, datafiles):
 | |
| 24 | +    project = os.path.join(datafiles.dirname, datafiles.basename, 'missing-certs')
 | |
| 25 | + | |
| 26 | +    project_conf = {
 | |
| 27 | +        'name': 'test',
 | |
| 28 | + | |
| 29 | +        'remote-execution': {
 | |
| 30 | +            'url': 'https://cache.example.com:12345',
 | |
| 31 | +            'execution-service': {
 | |
| 32 | +                'url': 'http://localhost:8088'
 | |
| 33 | +            },
 | |
| 34 | +            'storage-service': {
 | |
| 35 | +                'url': 'http://charactron:11001',
 | |
| 36 | +            }
 | |
| 37 | +        }
 | |
| 38 | +    }
 | |
| 39 | +    project_conf_file = os.path.join(project, 'project.conf')
 | |
| 40 | +    _yaml.dump(project_conf, project_conf_file)
 | |
| 41 | + | |
| 42 | +    # Use `pull` here to ensure we try to initialize the remotes, triggering the error
 | |
| 43 | +    #
 | |
| 44 | +    # This does not happen for a simple `bst show`.
 | |
| 45 | +    result = cli.run(project=project, args=['pull', 'element.bst'])
 | |
| 46 | +    result.assert_main_error(ErrorDomain.LOAD, LoadErrorReason.INVALID_DATA, "specify one")
 | |
| 47 | + | |
| 48 | + | |
| 49 | +# Assert that if either the client key or client cert is specified
 | |
| 50 | +# without specifying its counterpart, we get a comprehensive LoadError
 | |
| 51 | +# instead of an unhandled exception.
 | |
| 52 | +@pytest.mark.datafiles(DATA_DIR)
 | |
| 53 | +@pytest.mark.parametrize('config_key, config_value', [
 | |
| 54 | +    ('client-cert', 'client.crt'),
 | |
| 55 | +    ('client-key', 'client.key')
 | |
| 56 | +])
 | |
| 57 | +def test_missing_certs(cli, datafiles, config_key, config_value):
 | |
| 58 | +    project = os.path.join(datafiles.dirname, datafiles.basename, 'missing-certs')
 | |
| 59 | + | |
| 60 | +    project_conf = {
 | |
| 61 | +        'name': 'test',
 | |
| 62 | + | |
| 63 | +        'remote-execution': {
 | |
| 64 | +            'execution-service': {
 | |
| 65 | +                'url': 'http://localhost:8088'
 | |
| 66 | +            },
 | |
| 67 | +            'storage-service': {
 | |
| 68 | +                'url': 'http://charactron:11001',
 | |
| 69 | +                config_key: config_value,
 | |
| 70 | +            }
 | |
| 71 | +        }
 | |
| 72 | +    }
 | |
| 73 | +    project_conf_file = os.path.join(project, 'project.conf')
 | |
| 74 | +    _yaml.dump(project_conf, project_conf_file)
 | |
| 75 | + | |
| 76 | +    # Use `pull` here to ensure we try to initialize the remotes, triggering the error
 | |
| 77 | +    #
 | |
| 78 | +    # This does not happen for a simple `bst show`.
 | |
| 79 | +    result = cli.run(project=project, args=['show', 'element.bst'])
 | |
| 80 | +    result.assert_main_error(ErrorDomain.LOAD, LoadErrorReason.INVALID_DATA, "Your config is missing")
 | |
| 81 | + | |
| 82 | + | |
| 83 | +# Assert that if incomplete information is supplied we get a sensible error message.
 | |
| 84 | +@pytest.mark.datafiles(DATA_DIR)
 | |
| 85 | +def test_empty_config(cli, datafiles):
 | |
| 86 | +    project = os.path.join(datafiles.dirname, datafiles.basename, 'missing-certs')
 | |
| 87 | + | |
| 88 | +    project_conf = {
 | |
| 89 | +        'name': 'test',
 | |
| 90 | + | |
| 91 | +        'remote-execution': {
 | |
| 92 | +        }
 | |
| 93 | +    }
 | |
| 94 | +    project_conf_file = os.path.join(project, 'project.conf')
 | |
| 95 | +    _yaml.dump(project_conf, project_conf_file)
 | |
| 96 | + | |
| 97 | +    # Use `pull` here to ensure we try to initialize the remotes, triggering the error
 | |
| 98 | +    #
 | |
| 99 | +    # This does not happen for a simple `bst show`.
 | |
| 100 | +    result = cli.run(project=project, args=['pull', 'element.bst'])
 | |
| 101 | +    result.assert_main_error(ErrorDomain.LOAD, LoadErrorReason.INVALID_DATA, "specify one") | 
| 1 | +kind: autotools | 
