finn pushed to branch finn/78-capabilities-service at BuildGrid / buildgrid
Commits:
-
f3c00752
by Finn at 2018-11-27T15:38:08Z
-
1a2d0de2
by Finn at 2018-11-27T15:38:09Z
-
da7dd836
by Finn at 2018-11-27T15:38:09Z
-
49259800
by Finn at 2018-11-27T15:38:09Z
-
d2c304d5
by Finn at 2018-11-27T15:38:09Z
-
c926da1e
by Finn at 2018-11-27T15:38:09Z
6 changed files:
- + buildgrid/_app/commands/cmd_capabilities.py
- + buildgrid/client/capabilities.py
- + buildgrid/server/capabilities/instance.py
- buildgrid/server/instance.py
- + tests/integration/capabilities_service.py
- + tests/utils/capabilities.py
Changes:
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 sys
|
|
17 |
+from urllib.parse import urlparse
|
|
18 |
+ |
|
19 |
+import click
|
|
20 |
+import grpc
|
|
21 |
+ |
|
22 |
+from buildgrid.client.capabilities import CapabilitiesInterface
|
|
23 |
+ |
|
24 |
+from ..cli import pass_context
|
|
25 |
+ |
|
26 |
+ |
|
27 |
+@click.command(name='capabilities', short_help="Capabilities service.")
|
|
28 |
+@click.option('--remote', type=click.STRING, default='http://localhost:50051', show_default=True,
|
|
29 |
+ help="Remote execution server's URL (port defaults to 50051 if no specified).")
|
|
30 |
+@click.option('--client-key', type=click.Path(exists=True, dir_okay=False), default=None,
|
|
31 |
+ help="Private client key for TLS (PEM-encoded)")
|
|
32 |
+@click.option('--client-cert', type=click.Path(exists=True, dir_okay=False), default=None,
|
|
33 |
+ help="Public client certificate for TLS (PEM-encoded)")
|
|
34 |
+@click.option('--server-cert', type=click.Path(exists=True, dir_okay=False), default=None,
|
|
35 |
+ help="Public server certificate for TLS (PEM-encoded)")
|
|
36 |
+@click.option('--instance-name', type=click.STRING, default='main', show_default=True,
|
|
37 |
+ help="Targeted farm instance name.")
|
|
38 |
+@pass_context
|
|
39 |
+def cli(context, remote, instance_name, client_key, client_cert, server_cert):
|
|
40 |
+ click.echo("Getting capabilities...")
|
|
41 |
+ url = urlparse(remote)
|
|
42 |
+ |
|
43 |
+ remote = '{}:{}'.format(url.hostname, url.port or 50051)
|
|
44 |
+ instance_name = instance_name
|
|
45 |
+ |
|
46 |
+ if url.scheme == 'http':
|
|
47 |
+ channel = grpc.insecure_channel(remote)
|
|
48 |
+ else:
|
|
49 |
+ credentials = context.load_client_credentials(client_key, client_cert, server_cert)
|
|
50 |
+ if not credentials:
|
|
51 |
+ click.echo("ERROR: no TLS keys were specified and no defaults could be found.", err=True)
|
|
52 |
+ sys.exit(-1)
|
|
53 |
+ |
|
54 |
+ channel = grpc.secure_channel(remote, credentials)
|
|
55 |
+ |
|
56 |
+ interface = CapabilitiesInterface(channel)
|
|
57 |
+ response = interface.get_capabilities(instance_name)
|
|
58 |
+ click.echo(response)
|
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 grpc
|
|
18 |
+ |
|
19 |
+from buildgrid._protos.build.bazel.remote.execution.v2 import remote_execution_pb2, remote_execution_pb2_grpc
|
|
20 |
+ |
|
21 |
+ |
|
22 |
+class CapabilitiesInterface:
|
|
23 |
+ """Interface for calls the the Capabilities Service."""
|
|
24 |
+ |
|
25 |
+ def __init__(self, channel):
|
|
26 |
+ """Initialises an instance of the capabilities service.
|
|
27 |
+ |
|
28 |
+ Args:
|
|
29 |
+ channel (grpc.Channel): A gRPC channel to the CAS endpoint.
|
|
30 |
+ """
|
|
31 |
+ self.__logger = logging.getLogger(__name__)
|
|
32 |
+ self.__stub = remote_execution_pb2_grpc.CapabilitiesStub(channel)
|
|
33 |
+ |
|
34 |
+ def get_capabilities(self, instance_name):
|
|
35 |
+ """Returns the capabilities or the server to the user.
|
|
36 |
+ |
|
37 |
+ Args:
|
|
38 |
+ instance_name (str): The name of the instance."""
|
|
39 |
+ |
|
40 |
+ request = remote_execution_pb2.GetCapabilitiesRequest(instance_name=instance_name)
|
|
41 |
+ try:
|
|
42 |
+ return self.__stub.GetCapabilities(request)
|
|
43 |
+ |
|
44 |
+ except grpc.RpcError as e:
|
|
45 |
+ self.__logger.error(e)
|
|
46 |
+ raise
|
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 |
+ |
|
18 |
+from buildgrid._protos.build.bazel.remote.execution.v2 import remote_execution_pb2
|
|
19 |
+ |
|
20 |
+ |
|
21 |
+class CapabilitiesInstance:
|
|
22 |
+ |
|
23 |
+ def __init__(self, cas_instance=None, action_cache_instance=None, execution_instance=None):
|
|
24 |
+ self.__logger = logging.getLogger(__name__)
|
|
25 |
+ self.__cas_instance = cas_instance
|
|
26 |
+ self.__action_cache_instance = action_cache_instance
|
|
27 |
+ self.__execution_instance = execution_instance
|
|
28 |
+ |
|
29 |
+ def register_instance_with_server(self, instance_name, server):
|
|
30 |
+ server.add_capabilities_instance(self, instance_name)
|
|
31 |
+ |
|
32 |
+ def add_cas_instance(self, cas_instance):
|
|
33 |
+ self.__cas_instance = cas_instance
|
|
34 |
+ |
|
35 |
+ def add_action_cache_instance(self, action_cache_instance):
|
|
36 |
+ self.__action_cache_instance = action_cache_instance
|
|
37 |
+ |
|
38 |
+ def add_execution_instance(self, execution_instance):
|
|
39 |
+ self.__execution_instance = execution_instance
|
|
40 |
+ |
|
41 |
+ def get_capabilities(self):
|
|
42 |
+ server_capabilities = remote_execution_pb2.ServerCapabilities()
|
|
43 |
+ server_capabilities.cache_capabilities.CopyFrom(self._get_cache_capabilities())
|
|
44 |
+ server_capabilities.execution_capabilities.CopyFrom(self._get_capabilities_execution())
|
|
45 |
+ # TODO
|
|
46 |
+ # When API is stable, fill out SemVer values
|
|
47 |
+ # server_capabilities.deprecated_api_version =
|
|
48 |
+ # server_capabilities.low_api_version =
|
|
49 |
+ # server_capabilities.low_api_version =
|
|
50 |
+ # server_capabilities.hig_api_version =
|
|
51 |
+ return server_capabilities
|
|
52 |
+ |
|
53 |
+ def _get_cache_capabilities(self):
|
|
54 |
+ capabilities = remote_execution_pb2.CacheCapabilities()
|
|
55 |
+ action_cache_update_capabilities = remote_execution_pb2.ActionCacheUpdateCapabilities()
|
|
56 |
+ |
|
57 |
+ if self.__cas_instance:
|
|
58 |
+ capabilities.digest_function.extend([self.__cas_instance.hash_type()])
|
|
59 |
+ capabilities.max_batch_total_size_bytes = self.__cas_instance.max_batch_total_size_bytes()
|
|
60 |
+ capabilities.symlink_absolute_path_strategy = self.__cas_instance.symlink_absolute_path_strategy()
|
|
61 |
+ # TODO: execution priority #102
|
|
62 |
+ # capabilities.cache_priority_capabilities =
|
|
63 |
+ |
|
64 |
+ if self.__action_cache_instance:
|
|
65 |
+ action_cache_update_capabilities.update_enabled = self.__action_cache_instance.allow_updates
|
|
66 |
+ |
|
67 |
+ capabilities.action_cache_update_capabilities.CopyFrom(action_cache_update_capabilities)
|
|
68 |
+ return capabilities
|
|
69 |
+ |
|
70 |
+ def _get_capabilities_execution(self):
|
|
71 |
+ capabilities = remote_execution_pb2.ExecutionCapabilities()
|
|
72 |
+ if self.__execution_instance:
|
|
73 |
+ capabilities.exec_enabled = True
|
|
74 |
+ capabilities.digest_function = self.__execution_instance.hash_type()
|
|
75 |
+ # TODO: execution priority #102
|
|
76 |
+ # capabilities.execution_priority =
|
|
77 |
+ |
|
78 |
+ else:
|
|
79 |
+ capabilities.exec_enabled = False
|
|
80 |
+ |
|
81 |
+ return capabilities
|
... | ... | @@ -28,6 +28,7 @@ from buildgrid.server.execution.service import ExecutionService |
28 | 28 |
from buildgrid.server._monitoring import MonitoringBus, MonitoringOutputType, MonitoringOutputFormat
|
29 | 29 |
from buildgrid.server.operations.service import OperationsService
|
30 | 30 |
from buildgrid.server.referencestorage.service import ReferenceStorageService
|
31 |
+from buildgrid.server.capabilities.instance import CapabilitiesInstance
|
|
31 | 32 |
|
32 | 33 |
|
33 | 34 |
class BuildGridServer:
|
... | ... | @@ -55,6 +56,9 @@ class BuildGridServer: |
55 | 56 |
self.__main_loop = asyncio.get_event_loop()
|
56 | 57 |
self.__monitoring_bus = None
|
57 | 58 |
|
59 |
+ # We always want a capabilities service
|
|
60 |
+ self._capabilities_service = CapabilitiesService(self.__grpc_server)
|
|
61 |
+ |
|
58 | 62 |
self._execution_service = None
|
59 | 63 |
self._bots_service = None
|
60 | 64 |
self._operations_service = None
|
... | ... | @@ -128,6 +132,7 @@ class BuildGridServer: |
128 | 132 |
self._execution_service = ExecutionService(self.__grpc_server)
|
129 | 133 |
|
130 | 134 |
self._execution_service.add_instance(instance_name, instance)
|
135 |
+ self._add_capabilities_instance(instance_name, execution_instance=instance)
|
|
131 | 136 |
|
132 | 137 |
def add_bots_interface(self, instance, instance_name):
|
133 | 138 |
"""Adds a :obj:`BotsInterface` to the service.
|
... | ... | @@ -184,9 +189,10 @@ class BuildGridServer: |
184 | 189 |
self._action_cache_service = ActionCacheService(self.__grpc_server)
|
185 | 190 |
|
186 | 191 |
self._action_cache_service.add_instance(instance_name, instance)
|
192 |
+ self._add_capabilities_instance(instance_name, action_cache_instance=instance)
|
|
187 | 193 |
|
188 | 194 |
def add_cas_instance(self, instance, instance_name):
|
189 |
- """Stores a :obj:`ContentAddressableStorageInstance` to the service.
|
|
195 |
+ """Adds a :obj:`ContentAddressableStorageInstance` to the service.
|
|
190 | 196 |
|
191 | 197 |
If no service exists, it creates one.
|
192 | 198 |
|
... | ... | @@ -198,9 +204,10 @@ class BuildGridServer: |
198 | 204 |
self._cas_service = ContentAddressableStorageService(self.__grpc_server)
|
199 | 205 |
|
200 | 206 |
self._cas_service.add_instance(instance_name, instance)
|
207 |
+ self._add_capabilities_instance(instance_name, cas_instance=instance)
|
|
201 | 208 |
|
202 | 209 |
def add_bytestream_instance(self, instance, instance_name):
|
203 |
- """Stores a :obj:`ByteStreamInstance` to the service.
|
|
210 |
+ """Adds a :obj:`ByteStreamInstance` to the service.
|
|
204 | 211 |
|
205 | 212 |
If no service exists, it creates one.
|
206 | 213 |
|
... | ... | @@ -213,6 +220,31 @@ class BuildGridServer: |
213 | 220 |
|
214 | 221 |
self._bytestream_service.add_instance(instance_name, instance)
|
215 | 222 |
|
223 |
+ def _add_capabilities_instance(self, instance_name,
|
|
224 |
+ cas_instance=None,
|
|
225 |
+ action_cache_instance=None,
|
|
226 |
+ execution_instance=None):
|
|
227 |
+ """Adds a :obj:`CapabilitiesInstance` to the service.
|
|
228 |
+ |
|
229 |
+ Args:
|
|
230 |
+ instance (:obj:`CapabilitiesInstance`): Instance to add.
|
|
231 |
+ instance_name (str): Instance name.
|
|
232 |
+ """
|
|
233 |
+ |
|
234 |
+ try:
|
|
235 |
+ if cas_instance:
|
|
236 |
+ self._capabilities_service.add_cas_instance(instance_name, cas_instance)
|
|
237 |
+ if action_cache_instance:
|
|
238 |
+ self._capabilities_service.add_action_cache_instance(instance_name, action_cache_instance)
|
|
239 |
+ if execution_instance:
|
|
240 |
+ self._capabilities_service.add_execution_instance(instance_name, execution_instance)
|
|
241 |
+ |
|
242 |
+ except KeyError:
|
|
243 |
+ capabilities_instance = CapabilitiesInstance(cas_instance,
|
|
244 |
+ action_cache_instance,
|
|
245 |
+ execution_instance)
|
|
246 |
+ self._capabilities_service.add_instance(instance_name, capabilities_instance)
|
|
247 |
+ |
|
216 | 248 |
# --- Public API: Monitoring ---
|
217 | 249 |
|
218 | 250 |
@property
|
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 |
+# pylint: disable=redefined-outer-name
|
|
16 |
+ |
|
17 |
+ |
|
18 |
+import grpc
|
|
19 |
+import pytest
|
|
20 |
+ |
|
21 |
+from buildgrid._protos.build.bazel.remote.execution.v2 import remote_execution_pb2
|
|
22 |
+from buildgrid.client.capabilities import CapabilitiesInterface
|
|
23 |
+from buildgrid.server.controller import ExecutionController
|
|
24 |
+from buildgrid.server.actioncache.storage import ActionCache
|
|
25 |
+from buildgrid.server.cas.instance import ContentAddressableStorageInstance
|
|
26 |
+from buildgrid.server.cas.storage.lru_memory_cache import LRUMemoryCache
|
|
27 |
+ |
|
28 |
+from ..utils.utils import run_in_subprocess
|
|
29 |
+from ..utils.capabilities import serve_capabilities_service
|
|
30 |
+ |
|
31 |
+ |
|
32 |
+INSTANCES = ['', 'instance']
|
|
33 |
+ |
|
34 |
+ |
|
35 |
+# Use subprocess to avoid creation of gRPC threads in main process
|
|
36 |
+# See https://github.com/grpc/grpc/blob/master/doc/fork_support.md
|
|
37 |
+# Multiprocessing uses pickle which protobufs don't work with
|
|
38 |
+# Workaround wrapper to send messages as strings
|
|
39 |
+class ServerInterface:
|
|
40 |
+ |
|
41 |
+ def __init__(self, remote):
|
|
42 |
+ self.__remote = remote
|
|
43 |
+ |
|
44 |
+ def get_capabilities(self, instance_name):
|
|
45 |
+ |
|
46 |
+ def __get_capabilities(queue, remote, instance_name):
|
|
47 |
+ interface = CapabilitiesInterface(grpc.insecure_channel(remote))
|
|
48 |
+ |
|
49 |
+ result = interface.get_capabilities(instance_name)
|
|
50 |
+ queue.put(result.SerializeToString())
|
|
51 |
+ |
|
52 |
+ result = run_in_subprocess(__get_capabilities,
|
|
53 |
+ self.__remote, instance_name)
|
|
54 |
+ |
|
55 |
+ capabilities = remote_execution_pb2.ServerCapabilities()
|
|
56 |
+ capabilities.ParseFromString(result)
|
|
57 |
+ return capabilities
|
|
58 |
+ |
|
59 |
+ |
|
60 |
+@pytest.mark.parametrize('instance', INSTANCES)
|
|
61 |
+def test_execution_not_available_capabilities(instance):
|
|
62 |
+ with serve_capabilities_service([instance]) as server:
|
|
63 |
+ server_interface = ServerInterface(server.remote)
|
|
64 |
+ response = server_interface.get_capabilities(instance)
|
|
65 |
+ |
|
66 |
+ assert not response.execution_capabilities.exec_enabled
|
|
67 |
+ |
|
68 |
+ |
|
69 |
+@pytest.mark.parametrize('instance', INSTANCES)
|
|
70 |
+def test_execution_available_capabilities(instance):
|
|
71 |
+ controller = ExecutionController()
|
|
72 |
+ |
|
73 |
+ with serve_capabilities_service([instance],
|
|
74 |
+ execution_instance=controller.execution_instance) as server:
|
|
75 |
+ server_interface = ServerInterface(server.remote)
|
|
76 |
+ response = server_interface.get_capabilities(instance)
|
|
77 |
+ |
|
78 |
+ assert response.execution_capabilities.exec_enabled
|
|
79 |
+ assert response.execution_capabilities.digest_function
|
|
80 |
+ |
|
81 |
+ |
|
82 |
+@pytest.mark.parametrize('instance', INSTANCES)
|
|
83 |
+def test_action_cache_allow_updates_capabilities(instance):
|
|
84 |
+ storage = LRUMemoryCache(limit=256)
|
|
85 |
+ action_cache = ActionCache(storage, max_cached_refs=256, allow_updates=True)
|
|
86 |
+ |
|
87 |
+ with serve_capabilities_service([instance],
|
|
88 |
+ action_cache_instance=action_cache) as server:
|
|
89 |
+ server_interface = ServerInterface(server.remote)
|
|
90 |
+ response = server_interface.get_capabilities(instance)
|
|
91 |
+ |
|
92 |
+ assert response.cache_capabilities.action_cache_update_capabilities.update_enabled
|
|
93 |
+ |
|
94 |
+ |
|
95 |
+@pytest.mark.parametrize('instance', INSTANCES)
|
|
96 |
+def test_action_cache_not_allow_updates_capabilities(instance):
|
|
97 |
+ storage = LRUMemoryCache(limit=256)
|
|
98 |
+ action_cache = ActionCache(storage, max_cached_refs=256, allow_updates=False)
|
|
99 |
+ |
|
100 |
+ with serve_capabilities_service([instance],
|
|
101 |
+ action_cache_instance=action_cache) as server:
|
|
102 |
+ server_interface = ServerInterface(server.remote)
|
|
103 |
+ response = server_interface.get_capabilities(instance)
|
|
104 |
+ |
|
105 |
+ assert not response.cache_capabilities.action_cache_update_capabilities.update_enabled
|
|
106 |
+ |
|
107 |
+ |
|
108 |
+@pytest.mark.parametrize('instance', INSTANCES)
|
|
109 |
+def test_cas_capabilities(instance):
|
|
110 |
+ cas = ContentAddressableStorageInstance(None)
|
|
111 |
+ |
|
112 |
+ with serve_capabilities_service([instance],
|
|
113 |
+ cas_instance=cas) as server:
|
|
114 |
+ server_interface = ServerInterface(server.remote)
|
|
115 |
+ response = server_interface.get_capabilities(instance)
|
|
116 |
+ |
|
117 |
+ assert len(response.cache_capabilities.digest_function) == 1
|
|
118 |
+ assert response.cache_capabilities.digest_function[0]
|
|
119 |
+ assert response.cache_capabilities.symlink_absolute_path_strategy
|
|
120 |
+ assert response.cache_capabilities.max_batch_total_size_bytes
|
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 concurrent import futures
|
|
17 |
+from contextlib import contextmanager
|
|
18 |
+import multiprocessing
|
|
19 |
+import os
|
|
20 |
+import signal
|
|
21 |
+ |
|
22 |
+import grpc
|
|
23 |
+import pytest_cov
|
|
24 |
+ |
|
25 |
+from buildgrid.server.capabilities.service import CapabilitiesService
|
|
26 |
+from buildgrid.server.capabilities.instance import CapabilitiesInstance
|
|
27 |
+ |
|
28 |
+ |
|
29 |
+@contextmanager
|
|
30 |
+def serve_capabilities_service(instances,
|
|
31 |
+ cas_instance=None,
|
|
32 |
+ action_cache_instance=None,
|
|
33 |
+ execution_instance=None):
|
|
34 |
+ server = Server(instances,
|
|
35 |
+ cas_instance,
|
|
36 |
+ action_cache_instance,
|
|
37 |
+ execution_instance)
|
|
38 |
+ try:
|
|
39 |
+ yield server
|
|
40 |
+ finally:
|
|
41 |
+ server.quit()
|
|
42 |
+ |
|
43 |
+ |
|
44 |
+class Server:
|
|
45 |
+ |
|
46 |
+ def __init__(self, instances,
|
|
47 |
+ cas_instance=None,
|
|
48 |
+ action_cache_instance=None,
|
|
49 |
+ execution_instance=None):
|
|
50 |
+ self.instances = instances
|
|
51 |
+ |
|
52 |
+ self.__queue = multiprocessing.Queue()
|
|
53 |
+ self.__process = multiprocessing.Process(
|
|
54 |
+ target=Server.serve,
|
|
55 |
+ args=(self.__queue, self.instances, cas_instance, action_cache_instance, execution_instance))
|
|
56 |
+ self.__process.start()
|
|
57 |
+ |
|
58 |
+ self.port = self.__queue.get(timeout=1)
|
|
59 |
+ self.remote = 'localhost:{}'.format(self.port)
|
|
60 |
+ |
|
61 |
+ @staticmethod
|
|
62 |
+ def serve(queue, instances, cas_instance, action_cache_instance, execution_instance):
|
|
63 |
+ pytest_cov.embed.cleanup_on_sigterm()
|
|
64 |
+ |
|
65 |
+ # Use max_workers default from Python 3.5+
|
|
66 |
+ max_workers = (os.cpu_count() or 1) * 5
|
|
67 |
+ server = grpc.server(futures.ThreadPoolExecutor(max_workers))
|
|
68 |
+ port = server.add_insecure_port('localhost:0')
|
|
69 |
+ |
|
70 |
+ capabilities_service = CapabilitiesService(server)
|
|
71 |
+ for name in instances:
|
|
72 |
+ capabilities_instance = CapabilitiesInstance(cas_instance, action_cache_instance, execution_instance)
|
|
73 |
+ capabilities_service.add_instance(name, capabilities_instance)
|
|
74 |
+ |
|
75 |
+ server.start()
|
|
76 |
+ queue.put(port)
|
|
77 |
+ signal.pause()
|
|
78 |
+ |
|
79 |
+ def quit(self):
|
|
80 |
+ if self.__process:
|
|
81 |
+ self.__process.terminate()
|
|
82 |
+ self.__process.join()
|