finn pushed to branch jmac/expand-user-for-tls at BuildGrid / buildgrid
Commits:
- 
8a0bae22
by finnball at 2018-09-20T10:04:22Z
- 
2809507f
by finnball at 2018-09-20T10:08:27Z
- 
d30e5899
by finnball at 2018-09-20T10:08:32Z
- 
d2f9f287
by finnball at 2018-09-20T10:08:32Z
- 
bd304649
by finnball at 2018-09-20T10:08:32Z
- 
bc342dc5
by finnball at 2018-09-20T10:08:32Z
- 
7126d219
by finnball at 2018-09-20T10:08:32Z
- 
127ac5ac
by finnball at 2018-09-20T10:08:32Z
- 
9bccb336
by finnball at 2018-09-20T10:08:32Z
- 
8dae2eed
by finnball at 2018-09-20T10:16:37Z
- 
e274249e
by finnball at 2018-09-20T10:20:46Z
28 changed files:
- buildgrid/_app/commands/cmd_server.py
- − buildgrid/_app/server.py
- buildgrid/_app/settings/cas.yml
- buildgrid/_app/settings/default.yml
- buildgrid/_app/settings/parser.py
- buildgrid/_app/settings/remote-storage.yml
- buildgrid/server/actioncache/service.py
- buildgrid/server/actioncache/storage.py
- buildgrid/server/bots/instance.py
- buildgrid/server/bots/service.py
- buildgrid/server/cas/instance.py
- buildgrid/server/cas/service.py
- buildgrid/server/controller.py
- buildgrid/server/execution/instance.py
- buildgrid/server/execution/service.py
- + buildgrid/server/instance.py
- buildgrid/server/operations/instance.py
- buildgrid/server/operations/service.py
- buildgrid/server/referencestorage/service.py
- buildgrid/server/referencestorage/storage.py
- tests/cas/test_services.py
- tests/cas/test_storage.py
- tests/integration/action_cache_service.py
- tests/integration/bots_service.py
- tests/integration/execution_service.py
- tests/integration/operations_service.py
- tests/integration/reference_storage_service.py
- + tests/server_instance.py
Changes:
| ... | ... | @@ -26,14 +26,10 @@ import sys | 
| 26 | 26 |  | 
| 27 | 27 |  import click
 | 
| 28 | 28 |  | 
| 29 | -from buildgrid.server.controller import ExecutionController
 | |
| 30 | -from buildgrid.server.actioncache.storage import ActionCache
 | |
| 31 | -from buildgrid.server.cas.instance import ByteStreamInstance, ContentAddressableStorageInstance
 | |
| 32 | -from buildgrid.server.referencestorage.storage import ReferenceCache
 | |
| 29 | +from buildgrid.server.instance import BuildGridServer
 | |
| 33 | 30 |  | 
| 34 | 31 |  from ..cli import pass_context
 | 
| 35 | 32 |  from ..settings import parser
 | 
| 36 | -from ..server import BuildGridServer
 | |
| 37 | 33 |  | 
| 38 | 34 |  | 
| 39 | 35 |  @click.group(name='server', short_help="Start a local server instance.")
 | 
| ... | ... | @@ -50,58 +46,12 @@ def start(context, config): | 
| 50 | 46 |          settings = parser.get_parser().safe_load(f)
 | 
| 51 | 47 |  | 
| 52 | 48 |      try:
 | 
| 53 | -        server_settings = settings['server']
 | |
| 54 | -        insecure_mode = server_settings['insecure-mode']
 | |
| 55 | - | |
| 56 | -        credentials = None
 | |
| 57 | -        if not insecure_mode:
 | |
| 58 | -            credential_settings = server_settings['credentials']
 | |
| 59 | -            server_key = credential_settings['tls-server-key']
 | |
| 60 | -            server_cert = credential_settings['tls-server-cert']
 | |
| 61 | -            client_certs = credential_settings['tls-client-certs']
 | |
| 62 | -            credentials = context.load_server_credentials(server_key, server_cert, client_certs)
 | |
| 63 | - | |
| 64 | -            if not credentials:
 | |
| 65 | -                click.echo("ERROR: no TLS keys were specified and no defaults could be found.\n" +
 | |
| 66 | -                           "Set `insecure-mode: false` in order to deactivate TLS encryption.\n", err=True)
 | |
| 67 | -                sys.exit(-1)
 | |
| 68 | - | |
| 69 | -        port = server_settings['port']
 | |
| 70 | -        instances = settings['instances']
 | |
| 71 | - | |
| 72 | -        execution_controllers = _instance_maker(instances, ExecutionController)
 | |
| 73 | - | |
| 74 | -        execution_instances = {}
 | |
| 75 | -        bots_interfaces = {}
 | |
| 76 | -        operations_instances = {}
 | |
| 77 | - | |
| 78 | -        # TODO: map properly in parser
 | |
| 79 | -        # Issue 82
 | |
| 80 | -        for k, v in execution_controllers.items():
 | |
| 81 | -            execution_instances[k] = v.execution_instance
 | |
| 82 | -            bots_interfaces[k] = v.bots_interface
 | |
| 83 | -            operations_instances[k] = v.operations_instance
 | |
| 84 | - | |
| 85 | -        reference_caches = _instance_maker(instances, ReferenceCache)
 | |
| 86 | -        action_caches = _instance_maker(instances, ActionCache)
 | |
| 87 | -        cas = _instance_maker(instances, ContentAddressableStorageInstance)
 | |
| 88 | -        bytestreams = _instance_maker(instances, ByteStreamInstance)
 | |
| 49 | +        server = _create_server_from_config(settings)
 | |
| 89 | 50 |  | 
| 90 | 51 |      except KeyError as e:
 | 
| 91 | 52 |          click.echo("ERROR: Could not parse config: {}.\n".format(str(e)), err=True)
 | 
| 92 | 53 |          sys.exit(-1)
 | 
| 93 | 54 |  | 
| 94 | -    server = BuildGridServer(port=port,
 | |
| 95 | -                             credentials=credentials,
 | |
| 96 | -                             execution_instances=execution_instances,
 | |
| 97 | -                             bots_interfaces=bots_interfaces,
 | |
| 98 | -                             operations_instances=operations_instances,
 | |
| 99 | -                             reference_storage_instances=reference_caches,
 | |
| 100 | -                             action_cache_instances=action_caches,
 | |
| 101 | -                             cas_instances=cas,
 | |
| 102 | -                             bytestream_instances=bytestreams)
 | |
| 103 | - | |
| 104 | -    context.logger.info("Starting server on port {}".format(port))
 | |
| 105 | 55 |      loop = asyncio.get_event_loop()
 | 
| 106 | 56 |      try:
 | 
| 107 | 57 |          server.start()
 | 
| ... | ... | @@ -116,15 +66,25 @@ def start(context, config): | 
| 116 | 66 |          loop.close()
 | 
| 117 | 67 |  | 
| 118 | 68 |  | 
| 119 | -# Turn away now if you want to keep your eyes
 | |
| 120 | -def _instance_maker(instances, service_type):
 | |
| 121 | -    # TODO get this mapped in parser
 | |
| 122 | -    made = {}
 | |
| 69 | +def _create_server_from_config(config):
 | |
| 70 | +    server_settings = config['server']
 | |
| 123 | 71 |  | 
| 72 | +    server = BuildGridServer()
 | |
| 73 | + | |
| 74 | +    try:
 | |
| 75 | +        for channel in server_settings:
 | |
| 76 | +            server.add_port(channel.address, channel.credentials)
 | |
| 77 | + | |
| 78 | +    except (AttributeError, TypeError) as e:
 | |
| 79 | +        click.echo("Error: Use list of `!channel` tags: {}.\n".format(e), err=True)
 | |
| 80 | +        sys.exit(-1)
 | |
| 81 | + | |
| 82 | +    instances = config['instances']
 | |
| 124 | 83 |      for instance in instances:
 | 
| 125 | -        services = instance['services']
 | |
| 126 | 84 |          instance_name = instance['name']
 | 
| 85 | +        services = instance['services']
 | |
| 86 | + | |
| 127 | 87 |          for service in services:
 | 
| 128 | -            if isinstance(service, service_type):
 | |
| 129 | -                made[instance_name] = service
 | |
| 130 | -    return made | |
| 88 | +            service.register_instance_with_server(instance_name, server)
 | |
| 89 | + | |
| 90 | +    return server | 
| 1 | -# Copyright (C) 2018 Bloomberg LP
 | |
| 2 | -#
 | |
| 3 | -# Licensed under the Apache License, Version 2.0 (the "License");
 | |
| 4 | -# you may not use this file except in compliance with the License.
 | |
| 5 | -# You may obtain a copy of the License at
 | |
| 6 | -#
 | |
| 7 | -#  <http://www.apache.org/licenses/LICENSE-2.0>
 | |
| 8 | -#
 | |
| 9 | -# Unless required by applicable law or agreed to in writing, software
 | |
| 10 | -# distributed under the License is distributed on an "AS IS" BASIS,
 | |
| 11 | -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | |
| 12 | -# See the License for the specific language governing permissions and
 | |
| 13 | -# limitations under the License.
 | |
| 14 | - | |
| 15 | - | |
| 16 | -"""
 | |
| 17 | -BuildGridServer
 | |
| 18 | -==============
 | |
| 19 | - | |
| 20 | -Creates a BuildGrid server, binding all the requisite service instances together.
 | |
| 21 | -"""
 | |
| 22 | - | |
| 23 | -import logging
 | |
| 24 | -from concurrent import futures
 | |
| 25 | - | |
| 26 | -import grpc
 | |
| 27 | - | |
| 28 | -from buildgrid.server.cas.service import ByteStreamService, ContentAddressableStorageService
 | |
| 29 | -from buildgrid.server.actioncache.service import ActionCacheService
 | |
| 30 | -from buildgrid.server.execution.service import ExecutionService
 | |
| 31 | -from buildgrid.server.operations.service import OperationsService
 | |
| 32 | -from buildgrid.server.bots.service import BotsService
 | |
| 33 | -from buildgrid.server.referencestorage.service import ReferenceStorageService
 | |
| 34 | - | |
| 35 | - | |
| 36 | -class BuildGridServer:
 | |
| 37 | - | |
| 38 | -    def __init__(self, port=50051, max_workers=10, credentials=None,
 | |
| 39 | -                 execution_instances=None, bots_interfaces=None, operations_instances=None,
 | |
| 40 | -                 operations_service_instances=None, reference_storage_instances=None,
 | |
| 41 | -                 action_cache_instances=None, cas_instances=None, bytestream_instances=None):
 | |
| 42 | - | |
| 43 | -        self.logger = logging.getLogger(__name__)
 | |
| 44 | -        address = '[::]:{0}'.format(port)
 | |
| 45 | - | |
| 46 | -        server = grpc.server(futures.ThreadPoolExecutor(max_workers))
 | |
| 47 | - | |
| 48 | -        if credentials is not None:
 | |
| 49 | -            self.logger.info("Secure connection")
 | |
| 50 | -            server.add_secure_port(address, credentials)
 | |
| 51 | - | |
| 52 | -        else:
 | |
| 53 | -            self.logger.info("Insecure connection")
 | |
| 54 | -            server.add_insecure_port(address)
 | |
| 55 | - | |
| 56 | -        if execution_instances:
 | |
| 57 | -            self.logger.debug("Adding execution instances {}".format(
 | |
| 58 | -                execution_instances.keys()))
 | |
| 59 | -            ExecutionService(server, execution_instances)
 | |
| 60 | - | |
| 61 | -        if bots_interfaces:
 | |
| 62 | -            self.logger.debug("Adding bots interfaces {}".format(
 | |
| 63 | -                bots_interfaces.keys()))
 | |
| 64 | -            BotsService(server, bots_interfaces)
 | |
| 65 | - | |
| 66 | -        if operations_instances:
 | |
| 67 | -            self.logger.debug("Adding operations instances {}".format(
 | |
| 68 | -                operations_instances.keys()))
 | |
| 69 | -            OperationsService(server, operations_instances)
 | |
| 70 | - | |
| 71 | -        if reference_storage_instances:
 | |
| 72 | -            self.logger.debug("Adding reference storages {}".format(
 | |
| 73 | -                reference_storage_instances.keys()))
 | |
| 74 | -            ReferenceStorageService(server, reference_storage_instances)
 | |
| 75 | - | |
| 76 | -        if action_cache_instances:
 | |
| 77 | -            self.logger.debug("Adding action cache instances {}".format(
 | |
| 78 | -                action_cache_instances.keys()))
 | |
| 79 | -            ActionCacheService(server, action_cache_instances)
 | |
| 80 | - | |
| 81 | -        if cas_instances:
 | |
| 82 | -            self.logger.debug("Adding cas instances {}".format(
 | |
| 83 | -                cas_instances.keys()))
 | |
| 84 | -            ContentAddressableStorageService(server, cas_instances)
 | |
| 85 | - | |
| 86 | -        if bytestream_instances:
 | |
| 87 | -            self.logger.debug("Adding bytestream instances {}".format(
 | |
| 88 | -                bytestream_instances.keys()))
 | |
| 89 | -            ByteStreamService(server, bytestream_instances)
 | |
| 90 | - | |
| 91 | -        self._server = server
 | |
| 92 | - | |
| 93 | -    def start(self):
 | |
| 94 | -        self._server.start()
 | |
| 95 | - | |
| 96 | -    def stop(self):
 | |
| 97 | -        self._server.stop(grace=0) | 
| 1 | 1 |  server:
 | 
| 2 | -  port: 50052
 | |
| 3 | -  insecure-mode: true
 | |
| 4 | -  credentials:
 | |
| 5 | -    tls-server-key: null
 | |
| 6 | -    tls-server-cert: null
 | |
| 7 | -    tls-client-certs: null
 | |
| 2 | +  - !channel
 | |
| 3 | +    port: 50051
 | |
| 4 | +    insecure_mode: true
 | |
| 5 | +#    credentials:
 | |
| 6 | +#      tls-server-key: null
 | |
| 7 | +#      tls-server-cert: null
 | |
| 8 | +#      tls-client-certs: null
 | |
| 8 | 9 |  | 
| 9 | 10 |  description: |
 | 
| 10 | 11 |    Just a CAS with some reference storage.
 | 
| ... | ... | @@ -16,7 +17,7 @@ instances: | 
| 16 | 17 |  | 
| 17 | 18 |      storages:
 | 
| 18 | 19 |          - !disk-storage &main-storage
 | 
| 19 | -          path: ~/cas/
 | |
| 20 | +          path: !path ~/cas/
 | |
| 20 | 21 |  | 
| 21 | 22 |      services:
 | 
| 22 | 23 |        - !cas
 | 
| 1 | 1 |  server:
 | 
| 2 | -  port: 50051
 | |
| 3 | -  insecure-mode: true
 | |
| 4 | -  credentials:
 | |
| 5 | -    tls-server-key: null
 | |
| 6 | -    tls-server-cert: null
 | |
| 7 | -    tls-client-certs: null
 | |
| 2 | +  - !channel
 | |
| 3 | +    port: 50051
 | |
| 4 | +    insecure_mode: true
 | |
| 5 | +#    credentials:
 | |
| 6 | +#      tls-server-key: null
 | |
| 7 | +#      tls-server-cert: null
 | |
| 8 | +#      tls-client-certs: null
 | |
| 8 | 9 |  | 
| 9 | 10 |  description: |
 | 
| 10 | 11 |    A single default instance
 | 
| ... | ... | @@ -16,7 +17,7 @@ instances: | 
| 16 | 17 |  | 
| 17 | 18 |      storages:
 | 
| 18 | 19 |          - !disk-storage &main-storage
 | 
| 19 | -          path: ~/cas/
 | |
| 20 | +          path: !path ~/cas/
 | |
| 20 | 21 |  | 
| 21 | 22 |      services:
 | 
| 22 | 23 |        - !action-cache &main-action
 | 
| ... | ... | @@ -37,8 +37,44 @@ from ..cli import Context | 
| 37 | 37 |  class YamlFactory(yaml.YAMLObject):
 | 
| 38 | 38 |      @classmethod
 | 
| 39 | 39 |      def from_yaml(cls, loader, node):
 | 
| 40 | -        values = loader.construct_mapping(node, deep=True)
 | |
| 41 | -        return cls(**values)
 | |
| 40 | +        if isinstance(node, yaml.ScalarNode):
 | |
| 41 | +            value = loader.construct_scalar(node)
 | |
| 42 | +            return cls(value)
 | |
| 43 | + | |
| 44 | +        else:
 | |
| 45 | +            values = loader.construct_mapping(node, deep=True)
 | |
| 46 | +            return cls(**values)
 | |
| 47 | + | |
| 48 | + | |
| 49 | +class Channel(YamlFactory):
 | |
| 50 | + | |
| 51 | +    yaml_tag = u'!channel'
 | |
| 52 | + | |
| 53 | +    def __init__(self, port, insecure_mode, credentials=None):
 | |
| 54 | +        self.address = '[::]:{0}'.format(port)
 | |
| 55 | +        self.credentials = None
 | |
| 56 | + | |
| 57 | +        context = Context()
 | |
| 58 | + | |
| 59 | +        if not insecure_mode:
 | |
| 60 | +            server_key = credentials['tls-server-key']
 | |
| 61 | +            server_cert = credentials['tls-server-cert']
 | |
| 62 | +            client_certs = credentials['tls-client-certs']
 | |
| 63 | +            self.credentials = context.load_server_credentials(server_key, server_cert, client_certs)
 | |
| 64 | + | |
| 65 | +            if not credentials:
 | |
| 66 | +                click.echo("ERROR: no TLS keys were specified and no defaults could be found.\n" +
 | |
| 67 | +                           "Set `insecure-mode: false` in order to deactivate TLS encryption.\n", err=True)
 | |
| 68 | +                sys.exit(-1)
 | |
| 69 | + | |
| 70 | + | |
| 71 | +class Path(YamlFactory):
 | |
| 72 | + | |
| 73 | +    yaml_tag = u'!path'
 | |
| 74 | + | |
| 75 | +    def __new__(cls, path):
 | |
| 76 | +        path = os.path.expanduser(path)
 | |
| 77 | +        return path
 | |
| 42 | 78 |  | 
| 43 | 79 |  | 
| 44 | 80 |  class Disk(YamlFactory):
 | 
| ... | ... | @@ -46,7 +82,6 @@ class Disk(YamlFactory): | 
| 46 | 82 |      yaml_tag = u'!disk-storage'
 | 
| 47 | 83 |  | 
| 48 | 84 |      def __new__(cls, path):
 | 
| 49 | -        path = os.path.expanduser(path)
 | |
| 50 | 85 |          return DiskStorage(path)
 | 
| 51 | 86 |  | 
| 52 | 87 |  | 
| ... | ... | @@ -169,6 +204,8 @@ def _parse_size(size): | 
| 169 | 204 |  | 
| 170 | 205 |  def get_parser():
 | 
| 171 | 206 |  | 
| 207 | +    yaml.SafeLoader.add_constructor(Channel.yaml_tag, Channel.from_yaml)
 | |
| 208 | +    yaml.SafeLoader.add_constructor(Path.yaml_tag, Path.from_yaml)
 | |
| 172 | 209 |      yaml.SafeLoader.add_constructor(Execution.yaml_tag, Execution.from_yaml)
 | 
| 173 | 210 |      yaml.SafeLoader.add_constructor(Action.yaml_tag, Action.from_yaml)
 | 
| 174 | 211 |      yaml.SafeLoader.add_constructor(Reference.yaml_tag, Reference.from_yaml)
 | 
| 1 | 1 |  server:
 | 
| 2 | -  port: 50051
 | |
| 3 | -  insecure-mode: true
 | |
| 4 | -  credentials:
 | |
| 5 | -    tls-server-key: null
 | |
| 6 | -    tls-server-cert: null
 | |
| 7 | -    tls-client-certs: null
 | |
| 8 | - | |
| 2 | +  - !channel
 | |
| 3 | +    port: 50051
 | |
| 4 | +    insecure_mode: true
 | |
| 5 | +#    credentials:
 | |
| 6 | +#      tls-server-key: null
 | |
| 7 | +#      tls-server-cert: null
 | |
| 8 | +#      tls-client-certs: null
 | |
| 9 | 9 |  | 
| 10 | 10 |  description: |
 | 
| 11 | 11 |    A single default instance with remote storage.
 | 
| ... | ... | @@ -19,10 +19,10 @@ instances: | 
| 19 | 19 |          - !remote-storage &main-storage
 | 
| 20 | 20 |            url: "http://localhost:50052"
 | 
| 21 | 21 |            instance_name: main
 | 
| 22 | -          credentials:
 | |
| 23 | -            tls-client-key: null
 | |
| 24 | -            tls-client-cert: null
 | |
| 25 | -            tls-server-cert: null
 | |
| 22 | +#          credentials:
 | |
| 23 | +#            tls-client-key: null
 | |
| 24 | +#            tls-client-cert: null
 | |
| 25 | +#            tls-server-cert: null
 | |
| 26 | 26 |  | 
| 27 | 27 |      services:
 | 
| 28 | 28 |        - !action-cache &main-action
 | 
| ... | ... | @@ -32,13 +32,16 @@ from .._exceptions import InvalidArgumentError, NotFoundError | 
| 32 | 32 |  | 
| 33 | 33 |  class ActionCacheService(remote_execution_pb2_grpc.ActionCacheServicer):
 | 
| 34 | 34 |  | 
| 35 | -    def __init__(self, server, instances):
 | |
| 36 | -        self._instances = instances
 | |
| 37 | - | |
| 35 | +    def __init__(self, server):
 | |
| 38 | 36 |          self.logger = logging.getLogger(__name__)
 | 
| 39 | 37 |  | 
| 38 | +        self._instances = {}
 | |
| 39 | + | |
| 40 | 40 |          remote_execution_pb2_grpc.add_ActionCacheServicer_to_server(self, server)
 | 
| 41 | 41 |  | 
| 42 | +    def add_instance(self, name, instance):
 | |
| 43 | +        self._instances[name] = instance
 | |
| 44 | + | |
| 42 | 45 |      def GetActionResult(self, request, context):
 | 
| 43 | 46 |          try:
 | 
| 44 | 47 |              instance = self._get_instance(request.instance_name)
 | 
| ... | ... | @@ -26,6 +26,9 @@ from ..referencestorage.storage import ReferenceCache | 
| 26 | 26 |  | 
| 27 | 27 |  class ActionCache(ReferenceCache):
 | 
| 28 | 28 |  | 
| 29 | +    def register_instance_with_server(self, instance_name, server):
 | |
| 30 | +        server.add_action_cache_instance(self, instance_name)
 | |
| 31 | + | |
| 29 | 32 |      def get_action_result(self, action_digest):
 | 
| 30 | 33 |          key = self._get_key(action_digest)
 | 
| 31 | 34 |          return self.get_action_reference(key)
 | 
| ... | ... | @@ -36,6 +36,9 @@ class BotsInterface: | 
| 36 | 36 |          self._bot_sessions = {}
 | 
| 37 | 37 |          self._scheduler = scheduler
 | 
| 38 | 38 |  | 
| 39 | +    def register_instance_with_server(self, instance_name, server):
 | |
| 40 | +        server.add_bots_interface(self, instance_name)
 | |
| 41 | + | |
| 39 | 42 |      def create_bot_session(self, parent, bot_session):
 | 
| 40 | 43 |          """ Creates a new bot session. Server should assign a unique
 | 
| 41 | 44 |          name to the session. If a bot with the same bot id tries to
 | 
| ... | ... | @@ -33,12 +33,16 @@ from .._exceptions import InvalidArgumentError, OutofSyncError | 
| 33 | 33 |  | 
| 34 | 34 |  class BotsService(bots_pb2_grpc.BotsServicer):
 | 
| 35 | 35 |  | 
| 36 | -    def __init__(self, server, instances):
 | |
| 37 | -        self._instances = instances
 | |
| 36 | +    def __init__(self, server):
 | |
| 38 | 37 |          self.logger = logging.getLogger(__name__)
 | 
| 39 | 38 |  | 
| 39 | +        self._instances = {}
 | |
| 40 | + | |
| 40 | 41 |          bots_pb2_grpc.add_BotsServicer_to_server(self, server)
 | 
| 41 | 42 |  | 
| 43 | +    def add_instance(self, name, instance):
 | |
| 44 | +        self._instances[name] = instance
 | |
| 45 | + | |
| 42 | 46 |      def CreateBotSession(self, request, context):
 | 
| 43 | 47 |          try:
 | 
| 44 | 48 |              parent = request.parent
 | 
| ... | ... | @@ -31,6 +31,9 @@ class ContentAddressableStorageInstance: | 
| 31 | 31 |      def __init__(self, storage):
 | 
| 32 | 32 |          self._storage = storage
 | 
| 33 | 33 |  | 
| 34 | +    def register_instance_with_server(self, instance_name, server):
 | |
| 35 | +        server.add_cas_instance(self, instance_name)
 | |
| 36 | + | |
| 34 | 37 |      def find_missing_blobs(self, blob_digests):
 | 
| 35 | 38 |          storage = self._storage
 | 
| 36 | 39 |          return re_pb2.FindMissingBlobsResponse(
 | 
| ... | ... | @@ -60,6 +63,9 @@ class ByteStreamInstance: | 
| 60 | 63 |      def __init__(self, storage):
 | 
| 61 | 64 |          self._storage = storage
 | 
| 62 | 65 |  | 
| 66 | +    def register_instance_with_server(self, instance_name, server):
 | |
| 67 | +        server.add_bytestream_instance(self, instance_name)
 | |
| 68 | + | |
| 63 | 69 |      def read(self, path, read_offset, read_limit):
 | 
| 64 | 70 |          storage = self._storage
 | 
| 65 | 71 |  | 
| ... | ... | @@ -35,12 +35,16 @@ from .._exceptions import InvalidArgumentError, NotFoundError, OutOfRangeError | 
| 35 | 35 |  | 
| 36 | 36 |  class ContentAddressableStorageService(remote_execution_pb2_grpc.ContentAddressableStorageServicer):
 | 
| 37 | 37 |  | 
| 38 | -    def __init__(self, server, instances):
 | |
| 38 | +    def __init__(self, server):
 | |
| 39 | 39 |          self.logger = logging.getLogger(__name__)
 | 
| 40 | -        self._instances = instances
 | |
| 40 | + | |
| 41 | +        self._instances = {}
 | |
| 41 | 42 |  | 
| 42 | 43 |          remote_execution_pb2_grpc.add_ContentAddressableStorageServicer_to_server(self, server)
 | 
| 43 | 44 |  | 
| 45 | +    def add_instance(self, name, instance):
 | |
| 46 | +        self._instances[name] = instance
 | |
| 47 | + | |
| 44 | 48 |      def FindMissingBlobs(self, request, context):
 | 
| 45 | 49 |          try:
 | 
| 46 | 50 |              instance = self._get_instance(request.instance_name)
 | 
| ... | ... | @@ -75,12 +79,16 @@ class ContentAddressableStorageService(remote_execution_pb2_grpc.ContentAddressa | 
| 75 | 79 |  | 
| 76 | 80 |  class ByteStreamService(bytestream_pb2_grpc.ByteStreamServicer):
 | 
| 77 | 81 |  | 
| 78 | -    def __init__(self, server, instances):
 | |
| 82 | +    def __init__(self, server):
 | |
| 79 | 83 |          self.logger = logging.getLogger(__name__)
 | 
| 80 | -        self._instances = instances
 | |
| 84 | + | |
| 85 | +        self._instances = {}
 | |
| 81 | 86 |  | 
| 82 | 87 |          bytestream_pb2_grpc.add_ByteStreamServicer_to_server(self, server)
 | 
| 83 | 88 |  | 
| 89 | +    def add_instance(self, name, instance):
 | |
| 90 | +        self._instances[name] = instance
 | |
| 91 | + | |
| 84 | 92 |      def Read(self, request, context):
 | 
| 85 | 93 |          try:
 | 
| 86 | 94 |              path = request.resource_name.split("/")
 | 
| ... | ... | @@ -45,6 +45,11 @@ class ExecutionController: | 
| 45 | 45 |          self._bots_interface = BotsInterface(scheduler)
 | 
| 46 | 46 |          self._operations_instance = OperationsInstance(scheduler)
 | 
| 47 | 47 |  | 
| 48 | +    def register_instance_with_server(self, instance_name, server):
 | |
| 49 | +        server.add_execution_instance(self._execution_instance, instance_name)
 | |
| 50 | +        server.add_bots_interface(self._bots_interface, instance_name)
 | |
| 51 | +        server.add_operations_instance(self._operations_instance, instance_name)
 | |
| 52 | + | |
| 48 | 53 |      def stream_operation_updates(self, message_queue, operation_name):
 | 
| 49 | 54 |          operation = message_queue.get()
 | 
| 50 | 55 |          while not operation.done:
 | 
| ... | ... | @@ -34,6 +34,9 @@ class ExecutionInstance: | 
| 34 | 34 |          self._storage = storage
 | 
| 35 | 35 |          self._scheduler = scheduler
 | 
| 36 | 36 |  | 
| 37 | +    def register_instance_with_server(self, instance_name, server):
 | |
| 38 | +        server.add_execution_instance(self, instance_name)
 | |
| 39 | + | |
| 37 | 40 |      def execute(self, action_digest, skip_cache_lookup, message_queue=None):
 | 
| 38 | 41 |          """ Sends a job for execution.
 | 
| 39 | 42 |          Queues an action and creates an Operation instance to be associated with
 | 
| ... | ... | @@ -35,12 +35,14 @@ from .._exceptions import InvalidArgumentError | 
| 35 | 35 |  | 
| 36 | 36 |  class ExecutionService(remote_execution_pb2_grpc.ExecutionServicer):
 | 
| 37 | 37 |  | 
| 38 | -    def __init__(self, server, instances):
 | |
| 38 | +    def __init__(self, server):
 | |
| 39 | 39 |          self.logger = logging.getLogger(__name__)
 | 
| 40 | -        self._instances = instances
 | |
| 41 | - | |
| 40 | +        self._instances = {}
 | |
| 42 | 41 |          remote_execution_pb2_grpc.add_ExecutionServicer_to_server(self, server)
 | 
| 43 | 42 |  | 
| 43 | +    def add_instance(self, name, instance):
 | |
| 44 | +        self._instances[name] = instance
 | |
| 45 | + | |
| 44 | 46 |      def Execute(self, request, context):
 | 
| 45 | 47 |          try:
 | 
| 46 | 48 |              message_queue = queue.Queue()
 | 
| 1 | +# Copyright (C) 2018 Bloomberg LP
 | |
| 2 | +#
 | |
| 3 | +# Licensed under the Apache License, Version 2.0 (the "License");
 | |
| 4 | +# you may not use this file except in compliance with the License.
 | |
| 5 | +# You may obtain a copy of the License at
 | |
| 6 | +#
 | |
| 7 | +#  <http://www.apache.org/licenses/LICENSE-2.0>
 | |
| 8 | +#
 | |
| 9 | +# Unless required by applicable law or agreed to in writing, software
 | |
| 10 | +# distributed under the License is distributed on an "AS IS" BASIS,
 | |
| 11 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | |
| 12 | +# See the License for the specific language governing permissions and
 | |
| 13 | +# limitations under the License.
 | |
| 14 | + | |
| 15 | + | |
| 16 | +import logging
 | |
| 17 | +import os
 | |
| 18 | +from concurrent import futures
 | |
| 19 | + | |
| 20 | +import grpc
 | |
| 21 | + | |
| 22 | +from .cas.service import ByteStreamService, ContentAddressableStorageService
 | |
| 23 | +from .actioncache.service import ActionCacheService
 | |
| 24 | +from .execution.service import ExecutionService
 | |
| 25 | +from .operations.service import OperationsService
 | |
| 26 | +from .bots.service import BotsService
 | |
| 27 | +from .referencestorage.service import ReferenceStorageService
 | |
| 28 | + | |
| 29 | + | |
| 30 | +class BuildGridServer:
 | |
| 31 | +    """Creates a BuildGrid server.
 | |
| 32 | + | |
| 33 | +    The :class:`BuildGridServer` class binds together all the
 | |
| 34 | +    requisite services.
 | |
| 35 | +    """
 | |
| 36 | + | |
| 37 | +    def __init__(self, max_workers=None):
 | |
| 38 | +        """Initializes a new :class:`BuildGridServer` instance.
 | |
| 39 | + | |
| 40 | +        Args:
 | |
| 41 | +            max_workers (int, optional): A pool of max worker threads.
 | |
| 42 | +        """
 | |
| 43 | + | |
| 44 | +        self.logger = logging.getLogger(__name__)
 | |
| 45 | + | |
| 46 | +        if max_workers is None:
 | |
| 47 | +            # Use max_workers default from Python 3.5+
 | |
| 48 | +            max_workers = (os.cpu_count() or 1) * 5
 | |
| 49 | + | |
| 50 | +        server = grpc.server(futures.ThreadPoolExecutor(max_workers))
 | |
| 51 | + | |
| 52 | +        self._server = server
 | |
| 53 | + | |
| 54 | +        self._execution_service = None
 | |
| 55 | +        self._bots_service = None
 | |
| 56 | +        self._operations_service = None
 | |
| 57 | +        self._reference_storage_service = None
 | |
| 58 | +        self._action_cache_service = None
 | |
| 59 | +        self._cas_service = None
 | |
| 60 | +        self._bytestream_service = None
 | |
| 61 | + | |
| 62 | +    def start(self):
 | |
| 63 | +        """Starts the server.
 | |
| 64 | +        """
 | |
| 65 | +        self._server.start()
 | |
| 66 | + | |
| 67 | +    def stop(self, grace=0):
 | |
| 68 | +        """Stops the server.
 | |
| 69 | +        """
 | |
| 70 | +        self._server.stop(grace)
 | |
| 71 | + | |
| 72 | +    def add_port(self, address, credentials):
 | |
| 73 | +        """Adds a port to the server.
 | |
| 74 | + | |
| 75 | +        Must be called before the server starts. If a credentials object exists,
 | |
| 76 | +        it will make a secure port.
 | |
| 77 | + | |
| 78 | +        Args:
 | |
| 79 | +            address (str): The address with port number.
 | |
| 80 | +            credentials (:obj:`grpc.ChannelCredentials`): Credentials object.
 | |
| 81 | +        """
 | |
| 82 | +        if credentials is not None:
 | |
| 83 | +            self.logger.info("Adding secure connection on: [{}]".format(address))
 | |
| 84 | +            self._server.add_secure_port(address, credentials)
 | |
| 85 | + | |
| 86 | +        else:
 | |
| 87 | +            self.logger.info("Adding insecure connection on [{}]".format(address))
 | |
| 88 | +            self._server.add_insecure_port(address)
 | |
| 89 | + | |
| 90 | +    def add_execution_instance(self, instance, instance_name):
 | |
| 91 | +        """Adds an :obj:`ExecutionInstance` to the service.
 | |
| 92 | + | |
| 93 | +        If no service exists, it creates one.
 | |
| 94 | + | |
| 95 | +        Args:
 | |
| 96 | +            instance (:obj:`ExecutionInstance`): Instance to add.
 | |
| 97 | +            instance_name (str): Instance name.
 | |
| 98 | +        """
 | |
| 99 | +        if self._execution_service is None:
 | |
| 100 | +            self._execution_service = ExecutionService(self._server)
 | |
| 101 | + | |
| 102 | +        self._execution_service.add_instance(instance_name, instance)
 | |
| 103 | + | |
| 104 | +    def add_bots_interface(self, instance, instance_name):
 | |
| 105 | +        """Adds a :obj:`BotsInterface` to the service.
 | |
| 106 | + | |
| 107 | +        If no service exists, it creates one.
 | |
| 108 | + | |
| 109 | +        Args:
 | |
| 110 | +            instance (:obj:`BotsInterface`): Instance to add.
 | |
| 111 | +            instance_name (str): Instance name.
 | |
| 112 | +        """
 | |
| 113 | +        if self._bots_service is None:
 | |
| 114 | +            self._bots_service = BotsService(self._server)
 | |
| 115 | + | |
| 116 | +        self._bots_service.add_instance(instance_name, instance)
 | |
| 117 | + | |
| 118 | +    def add_operations_instance(self, instance, instance_name):
 | |
| 119 | +        """Adds an :obj:`OperationsInstance` to the service.
 | |
| 120 | + | |
| 121 | +        If no service exists, it creates one.
 | |
| 122 | + | |
| 123 | +        Args:
 | |
| 124 | +            instance (:obj:`OperationsInstance`): Instance to add.
 | |
| 125 | +            instance_name (str): Instance name.
 | |
| 126 | +        """
 | |
| 127 | +        if self._operations_service is None:
 | |
| 128 | +            self._operations_service = OperationsService(self._server)
 | |
| 129 | + | |
| 130 | +        self._operations_service.add_instance(instance_name, instance)
 | |
| 131 | + | |
| 132 | +    def add_reference_storage_instance(self, instance, instance_name):
 | |
| 133 | +        """Adds a :obj:`ReferenceCache` to the service.
 | |
| 134 | + | |
| 135 | +        If no service exists, it creates one.
 | |
| 136 | + | |
| 137 | +        Args:
 | |
| 138 | +            instance (:obj:`ReferenceCache`): Instance to add.
 | |
| 139 | +            instance_name (str): Instance name.
 | |
| 140 | +        """
 | |
| 141 | +        if self._reference_storage_service is None:
 | |
| 142 | +            self._reference_storage_service = ReferenceStorageService(self._server)
 | |
| 143 | + | |
| 144 | +        self._reference_storage_service.add_instance(instance_name, instance)
 | |
| 145 | + | |
| 146 | +    def add_action_cache_instance(self, instance, instance_name):
 | |
| 147 | +        """Adds a :obj:`ReferenceCache` to the service.
 | |
| 148 | + | |
| 149 | +        If no service exists, it creates one.
 | |
| 150 | + | |
| 151 | +        Args:
 | |
| 152 | +            instance (:obj:`ReferenceCache`): Instance to add.
 | |
| 153 | +            instance_name (str): Instance name.
 | |
| 154 | +        """
 | |
| 155 | +        if self._action_cache_service is None:
 | |
| 156 | +            self._action_cache_service = ActionCacheService(self._server)
 | |
| 157 | + | |
| 158 | +        self._action_cache_service.add_instance(instance_name, instance)
 | |
| 159 | + | |
| 160 | +    def add_cas_instance(self, instance, instance_name):
 | |
| 161 | +        """Stores a :obj:`ContentAddressableStorageInstance` to the service.
 | |
| 162 | + | |
| 163 | +        If no service exists, it creates one.
 | |
| 164 | + | |
| 165 | +        Args:
 | |
| 166 | +            instance (:obj:`ReferenceCache`): Instance to add.
 | |
| 167 | +            instance_name (str): Instance name.
 | |
| 168 | +        """
 | |
| 169 | +        if self._cas_service is None:
 | |
| 170 | +            self._cas_service = ContentAddressableStorageService(self._server)
 | |
| 171 | + | |
| 172 | +        self._cas_service.add_instance(instance_name, instance)
 | |
| 173 | + | |
| 174 | +    def add_bytestream_instance(self, instance, instance_name):
 | |
| 175 | +        """Stores a :obj:`ByteStreamInstance` to the service.
 | |
| 176 | + | |
| 177 | +        If no service exists, it creates one.
 | |
| 178 | + | |
| 179 | +        Args:
 | |
| 180 | +            instance (:obj:`ByteStreamInstance`): Instance to add.
 | |
| 181 | +            instance_name (str): Instance name.
 | |
| 182 | +        """
 | |
| 183 | +        if self._bytestream_service is None:
 | |
| 184 | +            self._bytestream_service = ByteStreamService(self._server)
 | |
| 185 | + | |
| 186 | +        self._bytestream_service.add_instance(instance_name, instance) | 
| ... | ... | @@ -30,6 +30,9 @@ class OperationsInstance: | 
| 30 | 30 |          self.logger = logging.getLogger(__name__)
 | 
| 31 | 31 |          self._scheduler = scheduler
 | 
| 32 | 32 |  | 
| 33 | +    def register_instance_with_server(self, instance_name, server):
 | |
| 34 | +        server.add_operations_instance(self, instance_name)
 | |
| 35 | + | |
| 33 | 36 |      def get_operation(self, name):
 | 
| 34 | 37 |          operation = self._scheduler.jobs.get(name)
 | 
| 35 | 38 |  | 
| ... | ... | @@ -32,12 +32,16 @@ from .._exceptions import InvalidArgumentError | 
| 32 | 32 |  | 
| 33 | 33 |  class OperationsService(operations_pb2_grpc.OperationsServicer):
 | 
| 34 | 34 |  | 
| 35 | -    def __init__(self, server, instances):
 | |
| 36 | -        self._instances = instances
 | |
| 35 | +    def __init__(self, server):
 | |
| 37 | 36 |          self.logger = logging.getLogger(__name__)
 | 
| 38 | 37 |  | 
| 38 | +        self._instances = {}
 | |
| 39 | + | |
| 39 | 40 |          operations_pb2_grpc.add_OperationsServicer_to_server(self, server)
 | 
| 40 | 41 |  | 
| 42 | +    def add_instance(self, name, instance):
 | |
| 43 | +        self._instances[name] = instance
 | |
| 44 | + | |
| 41 | 45 |      def GetOperation(self, request, context):
 | 
| 42 | 46 |          try:
 | 
| 43 | 47 |              name = request.name
 | 
| ... | ... | @@ -25,13 +25,16 @@ from .._exceptions import InvalidArgumentError, NotFoundError | 
| 25 | 25 |  | 
| 26 | 26 |  class ReferenceStorageService(buildstream_pb2_grpc.ReferenceStorageServicer):
 | 
| 27 | 27 |  | 
| 28 | -    def __init__(self, server, instances):
 | |
| 28 | +    def __init__(self, server):
 | |
| 29 | 29 |          self.logger = logging.getLogger(__name__)
 | 
| 30 | 30 |  | 
| 31 | -        self._instances = instances
 | |
| 31 | +        self._instances = {}
 | |
| 32 | 32 |  | 
| 33 | 33 |          buildstream_pb2_grpc.add_ReferenceStorageServicer_to_server(self, server)
 | 
| 34 | 34 |  | 
| 35 | +    def add_instance(self, name, instance):
 | |
| 36 | +        self._instances[name] = instance
 | |
| 37 | + | |
| 35 | 38 |      def GetReference(self, request, context):
 | 
| 36 | 39 |          try:
 | 
| 37 | 40 |              instance = self._get_instance(request.instance_name)
 | 
| ... | ... | @@ -44,6 +44,9 @@ class ReferenceCache: | 
| 44 | 44 |          self._max_cached_refs = max_cached_refs
 | 
| 45 | 45 |          self._digest_map = collections.OrderedDict()
 | 
| 46 | 46 |  | 
| 47 | +    def register_instance_with_server(self, instance_name, server):
 | |
| 48 | +        server.add_reference_storage_instance(self, instance_name)
 | |
| 49 | + | |
| 47 | 50 |      @property
 | 
| 48 | 51 |      def allow_updates(self):
 | 
| 49 | 52 |          return self._allow_updates
 | 
| ... | ... | @@ -80,7 +80,8 @@ def test_bytestream_read(mocked, data_to_read, instance): | 
| 80 | 80 |      storage = SimpleStorage([b"abc", b"defg", data_to_read])
 | 
| 81 | 81 |  | 
| 82 | 82 |      bs_instance = ByteStreamInstance(storage)
 | 
| 83 | -    servicer = ByteStreamService(server, {instance: bs_instance})
 | |
| 83 | +    servicer = ByteStreamService(server)
 | |
| 84 | +    servicer.add_instance(instance, bs_instance)
 | |
| 84 | 85 |  | 
| 85 | 86 |      request = bytestream_pb2.ReadRequest()
 | 
| 86 | 87 |      if instance != "":
 | 
| ... | ... | @@ -99,8 +100,10 @@ def test_bytestream_read_many(mocked, instance): | 
| 99 | 100 |      data_to_read = b"testing" * 10000
 | 
| 100 | 101 |  | 
| 101 | 102 |      storage = SimpleStorage([b"abc", b"defg", data_to_read])
 | 
| 103 | + | |
| 102 | 104 |      bs_instance = ByteStreamInstance(storage)
 | 
| 103 | -    servicer = ByteStreamService(server, {instance: bs_instance})
 | |
| 105 | +    servicer = ByteStreamService(server)
 | |
| 106 | +    servicer.add_instance(instance, bs_instance)
 | |
| 104 | 107 |  | 
| 105 | 108 |      request = bytestream_pb2.ReadRequest()
 | 
| 106 | 109 |      if instance != "":
 | 
| ... | ... | @@ -118,8 +121,10 @@ def test_bytestream_read_many(mocked, instance): | 
| 118 | 121 |  @mock.patch.object(service, 'bytestream_pb2_grpc', autospec=True)
 | 
| 119 | 122 |  def test_bytestream_write(mocked, instance, extra_data):
 | 
| 120 | 123 |      storage = SimpleStorage()
 | 
| 124 | + | |
| 121 | 125 |      bs_instance = ByteStreamInstance(storage)
 | 
| 122 | -    servicer = ByteStreamService(server, {instance: bs_instance})
 | |
| 126 | +    servicer = ByteStreamService(server)
 | |
| 127 | +    servicer.add_instance(instance, bs_instance)
 | |
| 123 | 128 |  | 
| 124 | 129 |      resource_name = ""
 | 
| 125 | 130 |      if instance != "":
 | 
| ... | ... | @@ -142,8 +147,10 @@ def test_bytestream_write(mocked, instance, extra_data): | 
| 142 | 147 |  @mock.patch.object(service, 'bytestream_pb2_grpc', autospec=True)
 | 
| 143 | 148 |  def test_bytestream_write_rejects_wrong_hash(mocked):
 | 
| 144 | 149 |      storage = SimpleStorage()
 | 
| 150 | + | |
| 145 | 151 |      bs_instance = ByteStreamInstance(storage)
 | 
| 146 | -    servicer = ByteStreamService(server, {"": bs_instance})
 | |
| 152 | +    servicer = ByteStreamService(server)
 | |
| 153 | +    servicer.add_instance("", bs_instance)
 | |
| 147 | 154 |  | 
| 148 | 155 |      data = b'some data'
 | 
| 149 | 156 |      wrong_hash = HASH(b'incorrect').hexdigest()
 | 
| ... | ... | @@ -163,7 +170,9 @@ def test_bytestream_write_rejects_wrong_hash(mocked): | 
| 163 | 170 |  def test_cas_find_missing_blobs(mocked, instance):
 | 
| 164 | 171 |      storage = SimpleStorage([b'abc', b'def'])
 | 
| 165 | 172 |      cas_instance = ContentAddressableStorageInstance(storage)
 | 
| 166 | -    servicer = ContentAddressableStorageService(server, {instance: cas_instance})
 | |
| 173 | +    servicer = ContentAddressableStorageService(server)
 | |
| 174 | +    servicer.add_instance(instance, cas_instance)
 | |
| 175 | + | |
| 167 | 176 |      digests = [
 | 
| 168 | 177 |          re_pb2.Digest(hash=HASH(b'def').hexdigest(), size_bytes=3),
 | 
| 169 | 178 |          re_pb2.Digest(hash=HASH(b'ghij').hexdigest(), size_bytes=4)
 | 
| ... | ... | @@ -178,8 +187,10 @@ def test_cas_find_missing_blobs(mocked, instance): | 
| 178 | 187 |  @mock.patch.object(service, 'remote_execution_pb2_grpc', autospec=True)
 | 
| 179 | 188 |  def test_cas_batch_update_blobs(mocked, instance):
 | 
| 180 | 189 |      storage = SimpleStorage()
 | 
| 190 | + | |
| 181 | 191 |      cas_instance = ContentAddressableStorageInstance(storage)
 | 
| 182 | -    servicer = ContentAddressableStorageService(server, {instance: cas_instance})
 | |
| 192 | +    servicer = ContentAddressableStorageService(server)
 | |
| 193 | +    servicer.add_instance(instance, cas_instance)
 | |
| 183 | 194 |  | 
| 184 | 195 |      update_requests = [
 | 
| 185 | 196 |          re_pb2.BatchUpdateBlobsRequest.Request(
 | 
| ... | ... | @@ -69,9 +69,13 @@ class MockStubServer: | 
| 69 | 69 |          instances = {"": MockCASStorage(), "dna": MockCASStorage()}
 | 
| 70 | 70 |          self._requests = []
 | 
| 71 | 71 |          with mock.patch.object(service, 'bytestream_pb2_grpc'):
 | 
| 72 | -            self._bs_service = service.ByteStreamService(server, instances)
 | |
| 72 | +            self._bs_service = service.ByteStreamService(server)
 | |
| 73 | +            for k, v in instances.items():
 | |
| 74 | +                self._bs_service.add_instance(k, v)
 | |
| 73 | 75 |          with mock.patch.object(service, 'remote_execution_pb2_grpc'):
 | 
| 74 | -            self._cas_service = service.ContentAddressableStorageService(server, instances)
 | |
| 76 | +            self._cas_service = service.ContentAddressableStorageService(server)
 | |
| 77 | +            for k, v in instances.items():
 | |
| 78 | +                self._cas_service.add_instance(k, v)
 | |
| 75 | 79 |  | 
| 76 | 80 |      def Read(self, request):
 | 
| 77 | 81 |          yield from self._bs_service.Read(request, context)
 | 
| ... | ... | @@ -127,7 +131,7 @@ def any_storage(request): | 
| 127 | 131 |          with mock.patch.object(remote, 'bytestream_pb2_grpc'):
 | 
| 128 | 132 |              with mock.patch.object(remote, 'remote_execution_pb2_grpc'):
 | 
| 129 | 133 |                  mock_server = MockStubServer()
 | 
| 130 | -                storage = remote.RemoteStorage(instance, "")
 | |
| 134 | +                storage = remote.RemoteStorage(None, "")
 | |
| 131 | 135 |                  storage._stub_bs = mock_server
 | 
| 132 | 136 |                  storage._stub_cas = mock_server
 | 
| 133 | 137 |                  yield storage
 | 
| ... | ... | @@ -52,9 +52,11 @@ def cache_instances(cas): | 
| 52 | 52 |  | 
| 53 | 53 |  def test_simple_action_result(cache_instances, context):
 | 
| 54 | 54 |      with mock.patch.object(service, 'remote_execution_pb2_grpc'):
 | 
| 55 | -        ac_service = ActionCacheService(server, cache_instances)
 | |
| 55 | +        ac_service = ActionCacheService(server)
 | |
| 56 | + | |
| 57 | +    for k, v in cache_instances.items():
 | |
| 58 | +        ac_service.add_instance(k, v)
 | |
| 56 | 59 |  | 
| 57 | -    print(cache_instances)
 | |
| 58 | 60 |      action_digest = remote_execution_pb2.Digest(hash='sample', size_bytes=4)
 | 
| 59 | 61 |  | 
| 60 | 62 |      # Check that before adding the ActionResult, attempting to fetch it fails
 | 
| ... | ... | @@ -78,7 +80,8 @@ def test_simple_action_result(cache_instances, context): | 
| 78 | 80 |  def test_disabled_update_action_result(context):
 | 
| 79 | 81 |      disabled_push = ActionCache(cas, 50, False)
 | 
| 80 | 82 |      with mock.patch.object(service, 'remote_execution_pb2_grpc'):
 | 
| 81 | -        ac_service = ActionCacheService(server, {"": disabled_push})
 | |
| 83 | +        ac_service = ActionCacheService(server)
 | |
| 84 | +        ac_service.add_instance("", disabled_push)
 | |
| 82 | 85 |  | 
| 83 | 86 |      request = remote_execution_pb2.UpdateActionResultRequest(instance_name='')
 | 
| 84 | 87 |      ac_service.UpdateActionResult(request, context)
 | 
| ... | ... | @@ -59,7 +59,10 @@ def controller(): | 
| 59 | 59 |  def instance(controller):
 | 
| 60 | 60 |      instances = {"": controller.bots_interface}
 | 
| 61 | 61 |      with mock.patch.object(service, 'bots_pb2_grpc'):
 | 
| 62 | -        yield BotsService(server, instances)
 | |
| 62 | +        bots_service = BotsService(server)
 | |
| 63 | +        for k, v in instances.items():
 | |
| 64 | +            bots_service.add_instance(k, v)
 | |
| 65 | +        yield bots_service
 | |
| 63 | 66 |  | 
| 64 | 67 |  | 
| 65 | 68 |  def test_create_bot_session(bot_session, context, instance):
 | 
| ... | ... | @@ -58,9 +58,10 @@ def controller(request): | 
| 58 | 58 |  # Instance to test
 | 
| 59 | 59 |  @pytest.fixture
 | 
| 60 | 60 |  def instance(controller):
 | 
| 61 | -    instances = {"": controller.execution_instance}
 | |
| 62 | 61 |      with mock.patch.object(service, 'remote_execution_pb2_grpc'):
 | 
| 63 | -        yield ExecutionService(server, instances)
 | |
| 62 | +        execution_service = ExecutionService(server)
 | |
| 63 | +        execution_service.add_instance("", controller.execution_instance)
 | |
| 64 | +        yield execution_service
 | |
| 64 | 65 |  | 
| 65 | 66 |  | 
| 66 | 67 |  @pytest.mark.parametrize("skip_cache_lookup", [True, False])
 | 
| ... | ... | @@ -63,9 +63,11 @@ def controller(): | 
| 63 | 63 |  # Instance to test
 | 
| 64 | 64 |  @pytest.fixture
 | 
| 65 | 65 |  def instance(controller):
 | 
| 66 | -    instances = {instance_name: controller.operations_instance}
 | |
| 67 | 66 |      with mock.patch.object(service, 'operations_pb2_grpc'):
 | 
| 68 | -        yield OperationsService(server, instances)
 | |
| 67 | +        operation_service = OperationsService(server)
 | |
| 68 | +        operation_service.add_instance(instance_name, controller.operations_instance)
 | |
| 69 | + | |
| 70 | +        yield operation_service
 | |
| 69 | 71 |  | 
| 70 | 72 |  | 
| 71 | 73 |  # Queue an execution, get operation corresponding to that request
 | 
| ... | ... | @@ -52,9 +52,10 @@ def cache(cas): | 
| 52 | 52 |  | 
| 53 | 53 |  @pytest.fixture
 | 
| 54 | 54 |  def instance(cache):
 | 
| 55 | -    instances = {instance_name: cache}
 | |
| 56 | 55 |      with mock.patch.object(service, 'buildstream_pb2_grpc'):
 | 
| 57 | -        yield ReferenceStorageService(server, instances)
 | |
| 56 | +        ref_service = ReferenceStorageService(server)
 | |
| 57 | +        ref_service.add_instance(instance_name, cache)
 | |
| 58 | +        yield ref_service
 | |
| 58 | 59 |  | 
| 59 | 60 |  | 
| 60 | 61 |  def test_simple_result(instance, context):
 | 
| ... | ... | @@ -83,7 +84,8 @@ def test_disabled_update_result(context): | 
| 83 | 84 |      keys = ["rick", "roy", "rach"]
 | 
| 84 | 85 |  | 
| 85 | 86 |      with mock.patch.object(service, 'buildstream_pb2_grpc'):
 | 
| 86 | -        instance = ReferenceStorageService(server, {'': disabled_push})
 | |
| 87 | +        instance = ReferenceStorageService(server)
 | |
| 88 | +        instance.add_instance(instance_name, disabled_push)
 | |
| 87 | 89 |  | 
| 88 | 90 |      # Add an ReferenceResult to the cache
 | 
| 89 | 91 |      reference_result = remote_execution_pb2.Digest(hash='deckard')
 | 
| ... | ... | @@ -101,7 +103,8 @@ def test_disabled_update_result(context): | 
| 101 | 103 |  def test_status(allow_updates, context):
 | 
| 102 | 104 |      cache = ReferenceCache(cas, 5, allow_updates)
 | 
| 103 | 105 |      with mock.patch.object(service, 'buildstream_pb2_grpc'):
 | 
| 104 | -        instance = ReferenceStorageService(server, {'': cache})
 | |
| 106 | +        instance = ReferenceStorageService(server)
 | |
| 107 | +        instance.add_instance("", cache)
 | |
| 105 | 108 |  | 
| 106 | 109 |      request = buildstream_pb2.StatusRequest()
 | 
| 107 | 110 |      response = instance.Status(request, context)
 | 
| 1 | +# Copyright (C) 2018 Bloomberg LP
 | |
| 2 | +#
 | |
| 3 | +# Licensed under the Apache License, Version 2.0 (the "License");
 | |
| 4 | +# you may not use this file except in compliance with the License.
 | |
| 5 | +# You may obtain a copy of the License at
 | |
| 6 | +#
 | |
| 7 | +#  <http://www.apache.org/licenses/LICENSE-2.0>
 | |
| 8 | +#
 | |
| 9 | +# Unless required by applicable law or agreed to in writing, software
 | |
| 10 | +# distributed under the License is distributed on an "AS IS" BASIS,
 | |
| 11 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | |
| 12 | +# See the License for the specific language governing permissions and
 | |
| 13 | +# limitations under the License.
 | |
| 14 | + | |
| 15 | + | |
| 16 | +from buildgrid.server.cas.service import ByteStreamService, ContentAddressableStorageService
 | |
| 17 | +from buildgrid.server.actioncache.service import ActionCacheService
 | |
| 18 | +from buildgrid.server.execution.service import ExecutionService
 | |
| 19 | +from buildgrid.server.operations.service import OperationsService
 | |
| 20 | +from buildgrid.server.bots.service import BotsService
 | |
| 21 | +from buildgrid.server.referencestorage.service import ReferenceStorageService
 | |
| 22 | +from buildgrid._app.settings import parser
 | |
| 23 | +from buildgrid._app.commands.cmd_server import _create_server_from_config
 | |
| 24 | + | |
| 25 | + | |
| 26 | +config = """
 | |
| 27 | +server:
 | |
| 28 | +  - !channel
 | |
| 29 | +    port: 50051
 | |
| 30 | +    insecure_mode: true
 | |
| 31 | +    credentials:
 | |
| 32 | +      tls-server-key: null
 | |
| 33 | +      tls-server-cert: null
 | |
| 34 | +      tls-client-certs: null
 | |
| 35 | + | |
| 36 | +description: |
 | |
| 37 | +  A single default instance
 | |
| 38 | + | |
| 39 | +instances:
 | |
| 40 | +  - name: main
 | |
| 41 | +    description: |
 | |
| 42 | +      The main server
 | |
| 43 | + | |
| 44 | +    storages:
 | |
| 45 | +        - !disk-storage &main-storage
 | |
| 46 | +          path: ~/cas/
 | |
| 47 | + | |
| 48 | +    services:
 | |
| 49 | +      - !action-cache &main-action
 | |
| 50 | +        storage: *main-storage
 | |
| 51 | +        max_cached_refs: 256
 | |
| 52 | +        allow_updates: true
 | |
| 53 | + | |
| 54 | +      - !execution
 | |
| 55 | +        storage: *main-storage
 | |
| 56 | +        action_cache: *main-action
 | |
| 57 | + | |
| 58 | +      - !cas
 | |
| 59 | +        storage: *main-storage
 | |
| 60 | + | |
| 61 | +      - !bytestream
 | |
| 62 | +        storage: *main-storage
 | |
| 63 | + | |
| 64 | +      - !reference-cache
 | |
| 65 | +        storage: *main-storage
 | |
| 66 | +        max_cached_refs: 256
 | |
| 67 | +        allow_updates: true
 | |
| 68 | +"""
 | |
| 69 | + | |
| 70 | + | |
| 71 | +def test_create_server():
 | |
| 72 | +    settings = parser.get_parser().safe_load(config)
 | |
| 73 | + | |
| 74 | +    server = _create_server_from_config(settings)
 | |
| 75 | + | |
| 76 | +    server.start()
 | |
| 77 | +    server.stop()
 | |
| 78 | + | |
| 79 | +    assert isinstance(server._execution_service, ExecutionService)
 | |
| 80 | +    assert isinstance(server._operations_service, OperationsService)
 | |
| 81 | +    assert isinstance(server._bots_service, BotsService)
 | |
| 82 | +    assert isinstance(server._reference_storage_service, ReferenceStorageService)
 | |
| 83 | +    assert isinstance(server._action_cache_service, ActionCacheService)
 | |
| 84 | +    assert isinstance(server._cas_service, ContentAddressableStorageService)
 | |
| 85 | +    assert isinstance(server._bytestream_service, ByteStreamService) | 
