finn pushed to branch finn/separate-services at BuildGrid / buildgrid
Commits:
-
1f83dfc9
by Finn Ball at 2018-09-11T12:45:32Z
-
61d23dec
by Finn Ball at 2018-09-11T12:45:35Z
-
041a1b4c
by finnball at 2018-09-11T12:45:35Z
11 changed files:
- .gitlab-ci.yml
- + buildgrid/_app/commands/cmd_operation.py
- docs/source/using_dummy_build.rst
- docs/source/using_simple_build.rst
- 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
Changes:
... | ... | @@ -30,7 +30,7 @@ before_script: |
30 | 30 |
.run-dummy-job-template: &dummy-job
|
31 | 31 |
stage: test
|
32 | 32 |
script:
|
33 |
- - ${BGD} server start --allow-insecure &
|
|
33 |
+ - ${BGD} server start buildgrid/_app/settings/default.yml &
|
|
34 | 34 |
- sleep 1 # Allow server to boot
|
35 | 35 |
- ${BGD} bot dummy &
|
36 | 36 |
- ${BGD} execute request-dummy --wait-for-completion
|
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 |
+Operations command
|
|
18 |
+=================
|
|
19 |
+ |
|
20 |
+Check the status of operations
|
|
21 |
+"""
|
|
22 |
+ |
|
23 |
+import logging
|
|
24 |
+from urllib.parse import urlparse
|
|
25 |
+import sys
|
|
26 |
+ |
|
27 |
+import click
|
|
28 |
+import grpc
|
|
29 |
+ |
|
30 |
+from buildgrid._protos.google.longrunning import operations_pb2, operations_pb2_grpc
|
|
31 |
+ |
|
32 |
+from ..cli import pass_context
|
|
33 |
+ |
|
34 |
+ |
|
35 |
+@click.group(name='operation', short_help="Long running operations commands")
|
|
36 |
+@click.option('--remote', type=click.STRING, default='http://localhost:50051', show_default=True,
|
|
37 |
+ help="Remote execution server's URL (port defaults to 50051 if no specified).")
|
|
38 |
+@click.option('--client-key', type=click.Path(exists=True, dir_okay=False), default=None,
|
|
39 |
+ help="Private client key for TLS (PEM-encoded)")
|
|
40 |
+@click.option('--client-cert', type=click.Path(exists=True, dir_okay=False), default=None,
|
|
41 |
+ help="Public client certificate for TLS (PEM-encoded)")
|
|
42 |
+@click.option('--server-cert', type=click.Path(exists=True, dir_okay=False), default=None,
|
|
43 |
+ help="Public server certificate for TLS (PEM-encoded)")
|
|
44 |
+@click.option('--instance-name', type=click.STRING, default='main', show_default=True,
|
|
45 |
+ help="Targeted farm instance name.")
|
|
46 |
+@pass_context
|
|
47 |
+def cli(context, remote, instance_name, client_key, client_cert, server_cert):
|
|
48 |
+ url = urlparse(remote)
|
|
49 |
+ |
|
50 |
+ context.remote = '{}:{}'.format(url.hostname, url.port or 50051)
|
|
51 |
+ context.instance_name = instance_name
|
|
52 |
+ |
|
53 |
+ if url.scheme == 'http':
|
|
54 |
+ context.channel = grpc.insecure_channel(context.remote)
|
|
55 |
+ else:
|
|
56 |
+ credentials = context.load_client_credentials(client_key, client_cert, server_cert)
|
|
57 |
+ if not credentials:
|
|
58 |
+ click.echo("ERROR: no TLS keys were specified and no defaults could be found.\n" +
|
|
59 |
+ "Use `insecure-mode: false` in order to deactivate TLS encryption.\n", err=True)
|
|
60 |
+ sys.exit(-1)
|
|
61 |
+ |
|
62 |
+ context.channel = grpc.secure_channel(context.remote, credentials)
|
|
63 |
+ |
|
64 |
+ context.logger = logging.getLogger(__name__)
|
|
65 |
+ context.logger.debug("Starting for remote {}".format(context.remote))
|
|
66 |
+ |
|
67 |
+ |
|
68 |
+@cli.command('status', short_help="Get the status of an operation.")
|
|
69 |
+@click.argument('operation-name', nargs=1, type=click.STRING, required=True)
|
|
70 |
+@pass_context
|
|
71 |
+def status(context, operation_name):
|
|
72 |
+ context.logger.info("Getting operation status...")
|
|
73 |
+ stub = operations_pb2_grpc.OperationsStub(context.channel)
|
|
74 |
+ |
|
75 |
+ request = operations_pb2.GetOperationRequest(name=operation_name)
|
|
76 |
+ |
|
77 |
+ response = stub.GetOperation(request)
|
|
78 |
+ context.logger.info(response)
|
|
79 |
+ |
|
80 |
+ |
|
81 |
+@cli.command('list', short_help="List operations.")
|
|
82 |
+@pass_context
|
|
83 |
+def lists(context):
|
|
84 |
+ context.logger.info("Getting list of operations")
|
|
85 |
+ stub = operations_pb2_grpc.OperationsStub(context.channel)
|
|
86 |
+ |
|
87 |
+ request = operations_pb2.ListOperationsRequest(name=context.instance_name)
|
|
88 |
+ |
|
89 |
+ response = stub.ListOperations(request)
|
|
90 |
+ |
|
91 |
+ if not response.operations:
|
|
92 |
+ context.logger.warning("No operations to list")
|
|
93 |
+ return
|
|
94 |
+ |
|
95 |
+ for op in response.operations:
|
|
96 |
+ context.logger.info(op)
|
1 |
- |
|
2 | 1 |
.. _dummy-build:
|
3 | 2 |
|
4 | 3 |
Dummy build
|
... | ... | @@ -8,7 +7,7 @@ In one terminal, start a server: |
8 | 7 |
|
9 | 8 |
.. code-block:: sh
|
10 | 9 |
|
11 |
- bgd server start --allow-insecure
|
|
10 |
+ bgd server start buildgrid/_app/settings/default.yml
|
|
12 | 11 |
|
13 | 12 |
In another terminal, send a request for work:
|
14 | 13 |
|
1 |
- |
|
2 | 1 |
.. _simple-build:
|
3 | 2 |
|
4 | 3 |
Simple build
|
... | ... | @@ -27,7 +26,7 @@ Now start a BuildGrid server, passing it a directory it can write a CAS to: |
27 | 26 |
|
28 | 27 |
.. code-block:: sh
|
29 | 28 |
|
30 |
- bgd server start --allow-insecure --cas disk --cas-cache disk --cas-disk-directory /path/to/empty/directory
|
|
29 |
+ bgd server start buildgrid/_app/settings/default.yml
|
|
31 | 30 |
|
32 | 31 |
Start the following bot session:
|
33 | 32 |
|
... | ... | @@ -28,11 +28,13 @@ from buildgrid._protos.google.bytestream import bytestream_pb2 |
28 | 28 |
from buildgrid._protos.build.bazel.remote.execution.v2 import remote_execution_pb2 as re_pb2
|
29 | 29 |
from buildgrid.server.cas.storage.storage_abc import StorageABC
|
30 | 30 |
from buildgrid.server.cas.instance import ByteStreamInstance, ContentAddressableStorageInstance
|
31 |
+from buildgrid.server.cas import service
|
|
31 | 32 |
from buildgrid.server.cas.service import ByteStreamService, ContentAddressableStorageService
|
32 | 33 |
from buildgrid.settings import HASH
|
33 | 34 |
|
34 | 35 |
|
35 | 36 |
context = mock.create_autospec(_Context)
|
37 |
+server = mock.create_autospec(grpc.server)
|
|
36 | 38 |
|
37 | 39 |
|
38 | 40 |
class SimpleStorage(StorageABC):
|
... | ... | @@ -73,11 +75,12 @@ instances = ["", "test_inst"] |
73 | 75 |
|
74 | 76 |
@pytest.mark.parametrize("data_to_read", test_strings)
|
75 | 77 |
@pytest.mark.parametrize("instance", instances)
|
76 |
-def test_bytestream_read(data_to_read, instance):
|
|
78 |
+@mock.patch.object(service, 'bytestream_pb2_grpc', autospec=True)
|
|
79 |
+def test_bytestream_read(mocked, data_to_read, instance):
|
|
77 | 80 |
storage = SimpleStorage([b"abc", b"defg", data_to_read])
|
78 | 81 |
|
79 | 82 |
bs_instance = ByteStreamInstance(storage)
|
80 |
- servicer = ByteStreamService({instance: bs_instance})
|
|
83 |
+ servicer = ByteStreamService(server, {instance: bs_instance})
|
|
81 | 84 |
|
82 | 85 |
request = bytestream_pb2.ReadRequest()
|
83 | 86 |
if instance != "":
|
... | ... | @@ -91,12 +94,13 @@ def test_bytestream_read(data_to_read, instance): |
91 | 94 |
|
92 | 95 |
|
93 | 96 |
@pytest.mark.parametrize("instance", instances)
|
94 |
-def test_bytestream_read_many(instance):
|
|
97 |
+@mock.patch.object(service, 'bytestream_pb2_grpc', autospec=True)
|
|
98 |
+def test_bytestream_read_many(mocked, instance):
|
|
95 | 99 |
data_to_read = b"testing" * 10000
|
96 | 100 |
|
97 | 101 |
storage = SimpleStorage([b"abc", b"defg", data_to_read])
|
98 | 102 |
bs_instance = ByteStreamInstance(storage)
|
99 |
- servicer = ByteStreamService({instance: bs_instance})
|
|
103 |
+ servicer = ByteStreamService(server, {instance: bs_instance})
|
|
100 | 104 |
|
101 | 105 |
request = bytestream_pb2.ReadRequest()
|
102 | 106 |
if instance != "":
|
... | ... | @@ -111,10 +115,11 @@ def test_bytestream_read_many(instance): |
111 | 115 |
|
112 | 116 |
@pytest.mark.parametrize("instance", instances)
|
113 | 117 |
@pytest.mark.parametrize("extra_data", ["", "/", "/extra/data"])
|
114 |
-def test_bytestream_write(instance, extra_data):
|
|
118 |
+@mock.patch.object(service, 'bytestream_pb2_grpc', autospec=True)
|
|
119 |
+def test_bytestream_write(mocked, instance, extra_data):
|
|
115 | 120 |
storage = SimpleStorage()
|
116 | 121 |
bs_instance = ByteStreamInstance(storage)
|
117 |
- servicer = ByteStreamService({instance: bs_instance})
|
|
122 |
+ servicer = ByteStreamService(server, {instance: bs_instance})
|
|
118 | 123 |
|
119 | 124 |
resource_name = ""
|
120 | 125 |
if instance != "":
|
... | ... | @@ -134,10 +139,11 @@ def test_bytestream_write(instance, extra_data): |
134 | 139 |
assert storage.data[(hash_, 6)] == b'abcdef'
|
135 | 140 |
|
136 | 141 |
|
137 |
-def test_bytestream_write_rejects_wrong_hash():
|
|
142 |
+@mock.patch.object(service, 'bytestream_pb2_grpc', autospec=True)
|
|
143 |
+def test_bytestream_write_rejects_wrong_hash(mocked):
|
|
138 | 144 |
storage = SimpleStorage()
|
139 | 145 |
bs_instance = ByteStreamInstance(storage)
|
140 |
- servicer = ByteStreamService({"": bs_instance})
|
|
146 |
+ servicer = ByteStreamService(server, {"": bs_instance})
|
|
141 | 147 |
|
142 | 148 |
data = b'some data'
|
143 | 149 |
wrong_hash = HASH(b'incorrect').hexdigest()
|
... | ... | @@ -153,10 +159,11 @@ def test_bytestream_write_rejects_wrong_hash(): |
153 | 159 |
|
154 | 160 |
|
155 | 161 |
@pytest.mark.parametrize("instance", instances)
|
156 |
-def test_cas_find_missing_blobs(instance):
|
|
162 |
+@mock.patch.object(service, 'remote_execution_pb2_grpc', autospec=True)
|
|
163 |
+def test_cas_find_missing_blobs(mocked, instance):
|
|
157 | 164 |
storage = SimpleStorage([b'abc', b'def'])
|
158 | 165 |
cas_instance = ContentAddressableStorageInstance(storage)
|
159 |
- servicer = ContentAddressableStorageService({instance: cas_instance})
|
|
166 |
+ servicer = ContentAddressableStorageService(server, {instance: cas_instance})
|
|
160 | 167 |
digests = [
|
161 | 168 |
re_pb2.Digest(hash=HASH(b'def').hexdigest(), size_bytes=3),
|
162 | 169 |
re_pb2.Digest(hash=HASH(b'ghij').hexdigest(), size_bytes=4)
|
... | ... | @@ -168,10 +175,11 @@ def test_cas_find_missing_blobs(instance): |
168 | 175 |
|
169 | 176 |
|
170 | 177 |
@pytest.mark.parametrize("instance", instances)
|
171 |
-def test_cas_batch_update_blobs(instance):
|
|
178 |
+@mock.patch.object(service, 'remote_execution_pb2_grpc', autospec=True)
|
|
179 |
+def test_cas_batch_update_blobs(mocked, instance):
|
|
172 | 180 |
storage = SimpleStorage()
|
173 | 181 |
cas_instance = ContentAddressableStorageInstance(storage)
|
174 |
- servicer = ContentAddressableStorageService({instance: cas_instance})
|
|
182 |
+ servicer = ContentAddressableStorageService(server, {instance: cas_instance})
|
|
175 | 183 |
|
176 | 184 |
update_requests = [
|
177 | 185 |
re_pb2.BatchUpdateBlobsRequest.Request(
|
... | ... | @@ -22,6 +22,7 @@ import tempfile |
22 | 22 |
from unittest import mock
|
23 | 23 |
|
24 | 24 |
import boto3
|
25 |
+import grpc
|
|
25 | 26 |
from grpc._server import _Context
|
26 | 27 |
import pytest
|
27 | 28 |
from moto import mock_s3
|
... | ... | @@ -38,6 +39,7 @@ from buildgrid.settings import HASH |
38 | 39 |
|
39 | 40 |
|
40 | 41 |
context = mock.create_autospec(_Context)
|
42 |
+server = mock.create_autospec(grpc.server)
|
|
41 | 43 |
|
42 | 44 |
abc = b"abc"
|
43 | 45 |
abc_digest = Digest(hash=HASH(abc).hexdigest(), size_bytes=3)
|
... | ... | @@ -66,8 +68,10 @@ class MockStubServer: |
66 | 68 |
def __init__(self):
|
67 | 69 |
instances = {"": MockCASStorage(), "dna": MockCASStorage()}
|
68 | 70 |
self._requests = []
|
69 |
- self._bs_service = service.ByteStreamService(instances)
|
|
70 |
- self._cas_service = service.ContentAddressableStorageService(instances)
|
|
71 |
+ with mock.patch.object(service, 'bytestream_pb2_grpc'):
|
|
72 |
+ self._bs_service = service.ByteStreamService(server, instances)
|
|
73 |
+ with mock.patch.object(service, 'remote_execution_pb2_grpc'):
|
|
74 |
+ self._cas_service = service.ContentAddressableStorageService(server, instances)
|
|
71 | 75 |
|
72 | 76 |
def Read(self, request):
|
73 | 77 |
yield from self._bs_service.Read(request, context)
|
... | ... | @@ -26,10 +26,14 @@ import pytest |
26 | 26 |
|
27 | 27 |
from buildgrid._protos.build.bazel.remote.execution.v2 import remote_execution_pb2
|
28 | 28 |
from buildgrid.server.cas.storage import lru_memory_cache
|
29 |
+from buildgrid.server.actioncache import service
|
|
29 | 30 |
from buildgrid.server.actioncache.storage import ActionCache
|
30 | 31 |
from buildgrid.server.actioncache.service import ActionCacheService
|
31 | 32 |
|
32 | 33 |
|
34 |
+server = mock.create_autospec(grpc.server)
|
|
35 |
+ |
|
36 |
+ |
|
33 | 37 |
# Can mock this
|
34 | 38 |
@pytest.fixture
|
35 | 39 |
def context():
|
... | ... | @@ -42,36 +46,41 @@ def cas(): |
42 | 46 |
|
43 | 47 |
|
44 | 48 |
@pytest.fixture
|
45 |
-def cache(cas):
|
|
46 |
- yield ActionCache(cas, 50)
|
|
49 |
+def cache_instances(cas):
|
|
50 |
+ yield {"": ActionCache(cas, 50)}
|
|
47 | 51 |
|
48 | 52 |
|
49 |
-def test_simple_action_result(cache, context):
|
|
50 |
- service = ActionCacheService(cache)
|
|
53 |
+def test_simple_action_result(cache_instances, context):
|
|
54 |
+ with mock.patch.object(service, 'remote_execution_pb2_grpc'):
|
|
55 |
+ ac_service = ActionCacheService(server, cache_instances)
|
|
56 |
+ |
|
57 |
+ print(cache_instances)
|
|
51 | 58 |
action_digest = remote_execution_pb2.Digest(hash='sample', size_bytes=4)
|
52 | 59 |
|
53 | 60 |
# Check that before adding the ActionResult, attempting to fetch it fails
|
54 |
- request = remote_execution_pb2.GetActionResultRequest(action_digest=action_digest)
|
|
55 |
- service.GetActionResult(request, context)
|
|
61 |
+ request = remote_execution_pb2.GetActionResultRequest(instance_name="",
|
|
62 |
+ action_digest=action_digest)
|
|
63 |
+ ac_service.GetActionResult(request, context)
|
|
56 | 64 |
context.set_code.assert_called_once_with(grpc.StatusCode.NOT_FOUND)
|
57 | 65 |
|
58 | 66 |
# Add an ActionResult to the cache
|
59 | 67 |
action_result = remote_execution_pb2.ActionResult(stdout_raw=b'example output')
|
60 | 68 |
request = remote_execution_pb2.UpdateActionResultRequest(action_digest=action_digest,
|
61 | 69 |
action_result=action_result)
|
62 |
- service.UpdateActionResult(request, context)
|
|
70 |
+ ac_service.UpdateActionResult(request, context)
|
|
63 | 71 |
|
64 | 72 |
# Check that fetching it now works
|
65 | 73 |
request = remote_execution_pb2.GetActionResultRequest(action_digest=action_digest)
|
66 |
- fetched_result = service.GetActionResult(request, context)
|
|
74 |
+ fetched_result = ac_service.GetActionResult(request, context)
|
|
67 | 75 |
assert fetched_result.stdout_raw == action_result.stdout_raw
|
68 | 76 |
|
69 | 77 |
|
70 |
-def test_disabled_update_action_result(cache, context):
|
|
78 |
+def test_disabled_update_action_result(context):
|
|
71 | 79 |
disabled_push = ActionCache(cas, 50, False)
|
72 |
- service = ActionCacheService(disabled_push)
|
|
80 |
+ with mock.patch.object(service, 'remote_execution_pb2_grpc'):
|
|
81 |
+ ac_service = ActionCacheService(server, {"": disabled_push})
|
|
73 | 82 |
|
74 |
- request = remote_execution_pb2.UpdateActionResultRequest()
|
|
75 |
- service.UpdateActionResult(request, context)
|
|
83 |
+ request = remote_execution_pb2.UpdateActionResultRequest(instance_name='')
|
|
84 |
+ ac_service.UpdateActionResult(request, context)
|
|
76 | 85 |
|
77 | 86 |
context.set_code.assert_called_once_with(grpc.StatusCode.UNIMPLEMENTED)
|
... | ... | @@ -27,25 +27,21 @@ import pytest |
27 | 27 |
from buildgrid._protos.build.bazel.remote.execution.v2 import remote_execution_pb2
|
28 | 28 |
from buildgrid._protos.google.devtools.remoteworkers.v1test2 import bots_pb2
|
29 | 29 |
from buildgrid.server import job
|
30 |
-from buildgrid.server.instance import BuildGridInstance
|
|
30 |
+from buildgrid.server.controller import ExecutionController
|
|
31 | 31 |
from buildgrid.server.job import LeaseState
|
32 |
-from buildgrid.server.bots.instance import BotsInterface
|
|
32 |
+from buildgrid.server.bots import service
|
|
33 | 33 |
from buildgrid.server.bots.service import BotsService
|
34 | 34 |
|
35 | 35 |
|
36 |
+server = mock.create_autospec(grpc.server)
|
|
37 |
+ |
|
38 |
+ |
|
36 | 39 |
# GRPC context
|
37 | 40 |
@pytest.fixture
|
38 | 41 |
def context():
|
39 | 42 |
yield mock.MagicMock(spec=_Context)
|
40 | 43 |
|
41 | 44 |
|
42 |
-@pytest.fixture
|
|
43 |
-def action_job():
|
|
44 |
- action_digest = remote_execution_pb2.Digest()
|
|
45 |
- j = job.Job(action_digest, None)
|
|
46 |
- yield j
|
|
47 |
- |
|
48 |
- |
|
49 | 45 |
@pytest.fixture
|
50 | 46 |
def bot_session():
|
51 | 47 |
bot = bots_pb2.BotSession()
|
... | ... | @@ -54,20 +50,16 @@ def bot_session(): |
54 | 50 |
|
55 | 51 |
|
56 | 52 |
@pytest.fixture
|
57 |
-def buildgrid():
|
|
58 |
- yield BuildGridInstance()
|
|
59 |
- |
|
60 |
- |
|
61 |
-@pytest.fixture
|
|
62 |
-def bots(schedule):
|
|
63 |
- yield BotsInterface(schedule)
|
|
53 |
+def controller():
|
|
54 |
+ yield ExecutionController()
|
|
64 | 55 |
|
65 | 56 |
|
66 | 57 |
# Instance to test
|
67 | 58 |
@pytest.fixture
|
68 |
-def instance(buildgrid):
|
|
69 |
- instances = {"": buildgrid}
|
|
70 |
- yield BotsService(instances)
|
|
59 |
+def instance(controller):
|
|
60 |
+ instances = {"": controller.bots_interface}
|
|
61 |
+ with mock.patch.object(service, 'bots_pb2_grpc'):
|
|
62 |
+ yield BotsService(server, instances)
|
|
71 | 63 |
|
72 | 64 |
|
73 | 65 |
def test_create_bot_session(bot_session, context, instance):
|
... | ... | @@ -127,12 +119,11 @@ def test_update_bot_session_bot_id_fail(bot_session, context, instance): |
127 | 119 |
|
128 | 120 |
@pytest.mark.parametrize("number_of_jobs", [0, 1, 3, 500])
|
129 | 121 |
def test_number_of_leases(number_of_jobs, bot_session, context, instance):
|
130 |
- request = bots_pb2.CreateBotSessionRequest(bot_session=bot_session)
|
|
131 |
- # Inject work
|
|
122 |
+ |
|
132 | 123 |
for _ in range(0, number_of_jobs):
|
133 |
- action_digest = remote_execution_pb2.Digest()
|
|
134 |
- instance._instances[""].execute(action_digest, True)
|
|
124 |
+ _inject_work(instance._instances[""]._scheduler)
|
|
135 | 125 |
|
126 |
+ request = bots_pb2.CreateBotSessionRequest(bot_session=bot_session)
|
|
136 | 127 |
response = instance.CreateBotSession(request, context)
|
137 | 128 |
|
138 | 129 |
assert len(response.leases) == number_of_jobs
|
... | ... | @@ -141,9 +132,9 @@ def test_number_of_leases(number_of_jobs, bot_session, context, instance): |
141 | 132 |
def test_update_leases_with_work(bot_session, context, instance):
|
142 | 133 |
request = bots_pb2.CreateBotSessionRequest(parent='',
|
143 | 134 |
bot_session=bot_session)
|
144 |
- # Inject work
|
|
135 |
+ |
|
145 | 136 |
action_digest = remote_execution_pb2.Digest(hash='gaff')
|
146 |
- instance._instances[""].execute(action_digest, True)
|
|
137 |
+ _inject_work(instance._instances[""]._scheduler, action_digest)
|
|
147 | 138 |
|
148 | 139 |
response = instance.CreateBotSession(request, context)
|
149 | 140 |
|
... | ... | @@ -165,7 +156,7 @@ def test_update_leases_work_complete(bot_session, context, instance): |
165 | 156 |
|
166 | 157 |
# Inject work
|
167 | 158 |
action_digest = remote_execution_pb2.Digest(hash='gaff')
|
168 |
- instance._instances[""].execute(action_digest, True)
|
|
159 |
+ _inject_work(instance._instances[""]._scheduler, action_digest)
|
|
169 | 160 |
|
170 | 161 |
request = bots_pb2.UpdateBotSessionRequest(name=response.name,
|
171 | 162 |
bot_session=response)
|
... | ... | @@ -193,7 +184,7 @@ def test_work_rejected_by_bot(bot_session, context, instance): |
193 | 184 |
bot_session=bot_session)
|
194 | 185 |
# Inject work
|
195 | 186 |
action_digest = remote_execution_pb2.Digest(hash='gaff')
|
196 |
- instance._instances[""].execute(action_digest, True)
|
|
187 |
+ _inject_work(instance._instances[""]._scheduler, action_digest)
|
|
197 | 188 |
|
198 | 189 |
# Simulated the severed binding between client and server
|
199 | 190 |
response = copy.deepcopy(instance.CreateBotSession(request, context))
|
... | ... | @@ -215,7 +206,7 @@ def test_work_out_of_sync_from_pending(state, bot_session, context, instance): |
215 | 206 |
bot_session=bot_session)
|
216 | 207 |
# Inject work
|
217 | 208 |
action_digest = remote_execution_pb2.Digest(hash='gaff')
|
218 |
- instance._instances[""].execute(action_digest, True)
|
|
209 |
+ _inject_work(instance._instances[""]._scheduler, action_digest)
|
|
219 | 210 |
|
220 | 211 |
# Simulated the severed binding between client and server
|
221 | 212 |
response = copy.deepcopy(instance.CreateBotSession(request, context))
|
... | ... | @@ -236,7 +227,7 @@ def test_work_out_of_sync_from_active(state, bot_session, context, instance): |
236 | 227 |
bot_session=bot_session)
|
237 | 228 |
# Inject work
|
238 | 229 |
action_digest = remote_execution_pb2.Digest(hash='gaff')
|
239 |
- instance._instances[""].execute(action_digest, True)
|
|
230 |
+ _inject_work(instance._instances[""]._scheduler, action_digest)
|
|
240 | 231 |
|
241 | 232 |
# Simulated the severed binding between client and server
|
242 | 233 |
response = copy.deepcopy(instance.CreateBotSession(request, context))
|
... | ... | @@ -263,7 +254,7 @@ def test_work_active_to_active(bot_session, context, instance): |
263 | 254 |
bot_session=bot_session)
|
264 | 255 |
# Inject work
|
265 | 256 |
action_digest = remote_execution_pb2.Digest(hash='gaff')
|
266 |
- instance._instances[""].execute(action_digest, True)
|
|
257 |
+ _inject_work(instance._instances[""]._scheduler, action_digest)
|
|
267 | 258 |
|
268 | 259 |
# Simulated the severed binding between client and server
|
269 | 260 |
response = copy.deepcopy(instance.CreateBotSession(request, context))
|
... | ... | @@ -283,3 +274,10 @@ def test_post_bot_event_temp(context, instance): |
283 | 274 |
instance.PostBotEventTemp(request, context)
|
284 | 275 |
|
285 | 276 |
context.set_code.assert_called_once_with(grpc.StatusCode.UNIMPLEMENTED)
|
277 |
+ |
|
278 |
+ |
|
279 |
+def _inject_work(scheduler, action_digest=None):
|
|
280 |
+ if not action_digest:
|
|
281 |
+ action_digest = remote_execution_pb2.Digest()
|
|
282 |
+ j = job.Job(action_digest, False)
|
|
283 |
+ scheduler.append_job(j, True)
|
... | ... | @@ -29,12 +29,16 @@ from buildgrid._protos.build.bazel.remote.execution.v2 import remote_execution_p |
29 | 29 |
from buildgrid._protos.google.longrunning import operations_pb2
|
30 | 30 |
|
31 | 31 |
from buildgrid.server import job
|
32 |
-from buildgrid.server.instance import BuildGridInstance
|
|
32 |
+from buildgrid.server.controller import ExecutionController
|
|
33 | 33 |
from buildgrid.server.cas.storage import lru_memory_cache
|
34 | 34 |
from buildgrid.server.actioncache.storage import ActionCache
|
35 |
+from buildgrid.server.execution import service
|
|
35 | 36 |
from buildgrid.server.execution.service import ExecutionService
|
36 | 37 |
|
37 | 38 |
|
39 |
+server = mock.create_autospec(grpc.server)
|
|
40 |
+ |
|
41 |
+ |
|
38 | 42 |
@pytest.fixture
|
39 | 43 |
def context():
|
40 | 44 |
cxt = mock.MagicMock(spec=_Context)
|
... | ... | @@ -42,21 +46,21 @@ def context(): |
42 | 46 |
|
43 | 47 |
|
44 | 48 |
@pytest.fixture(params=["action-cache", "no-action-cache"])
|
45 |
-def buildgrid(request):
|
|
49 |
+def controller(request):
|
|
46 | 50 |
if request.param == "action-cache":
|
47 | 51 |
storage = lru_memory_cache.LRUMemoryCache(1024 * 1024)
|
48 | 52 |
cache = ActionCache(storage, 50)
|
49 |
- |
|
50 |
- return BuildGridInstance(action_cache=cache,
|
|
51 |
- cas_storage=storage)
|
|
52 |
- return BuildGridInstance()
|
|
53 |
+ yield ExecutionController(cache, storage)
|
|
54 |
+ else:
|
|
55 |
+ yield ExecutionController()
|
|
53 | 56 |
|
54 | 57 |
|
55 | 58 |
# Instance to test
|
56 | 59 |
@pytest.fixture
|
57 |
-def instance(buildgrid):
|
|
58 |
- instances = {"": buildgrid}
|
|
59 |
- yield ExecutionService(instances)
|
|
60 |
+def instance(controller):
|
|
61 |
+ instances = {"": controller.execution_instance}
|
|
62 |
+ with mock.patch.object(service, 'remote_execution_pb2_grpc'):
|
|
63 |
+ yield ExecutionService(server, instances)
|
|
60 | 64 |
|
61 | 65 |
|
62 | 66 |
@pytest.mark.parametrize("skip_cache_lookup", [True, False])
|
... | ... | @@ -86,7 +90,7 @@ def test_wrong_execute_instance(instance, context): |
86 | 90 |
context.set_code.assert_called_once_with(grpc.StatusCode.INVALID_ARGUMENT)
|
87 | 91 |
|
88 | 92 |
|
89 |
-def test_wait_execution(instance, buildgrid, context):
|
|
93 |
+def test_wait_execution(instance, controller, context):
|
|
90 | 94 |
action_digest = remote_execution_pb2.Digest()
|
91 | 95 |
action_digest.hash = 'zhora'
|
92 | 96 |
|
... | ... | @@ -95,7 +99,7 @@ def test_wait_execution(instance, buildgrid, context): |
95 | 99 |
|
96 | 100 |
request = remote_execution_pb2.WaitExecutionRequest(name="{}/{}".format('', j.name))
|
97 | 101 |
|
98 |
- buildgrid._scheduler.jobs[j.name] = j
|
|
102 |
+ controller.execution_instance._scheduler.jobs[j.name] = j
|
|
99 | 103 |
|
100 | 104 |
action_result_any = any_pb2.Any()
|
101 | 105 |
action_result = remote_execution_pb2.ActionResult()
|
... | ... | @@ -115,7 +119,7 @@ def test_wait_execution(instance, buildgrid, context): |
115 | 119 |
assert result.done is True
|
116 | 120 |
|
117 | 121 |
|
118 |
-def test_wrong_instance_wait_execution(instance, buildgrid, context):
|
|
122 |
+def test_wrong_instance_wait_execution(instance, context):
|
|
119 | 123 |
request = remote_execution_pb2.WaitExecutionRequest(name="blade")
|
120 | 124 |
next(instance.WaitExecution(request, context))
|
121 | 125 |
|
... | ... | @@ -28,12 +28,14 @@ from google.protobuf import any_pb2 |
28 | 28 |
from buildgrid._protos.build.bazel.remote.execution.v2 import remote_execution_pb2
|
29 | 29 |
from buildgrid._protos.google.longrunning import operations_pb2
|
30 | 30 |
|
31 |
-from buildgrid.server.instance import BuildGridInstance
|
|
31 |
+from buildgrid.server.controller import ExecutionController
|
|
32 | 32 |
from buildgrid.server._exceptions import InvalidArgumentError
|
33 | 33 |
|
34 |
+from buildgrid.server.operations import service
|
|
34 | 35 |
from buildgrid.server.operations.service import OperationsService
|
35 | 36 |
|
36 | 37 |
|
38 |
+server = mock.create_autospec(grpc.server)
|
|
37 | 39 |
instance_name = "blade"
|
38 | 40 |
|
39 | 41 |
|
... | ... | @@ -55,21 +57,22 @@ def execute_request(): |
55 | 57 |
|
56 | 58 |
|
57 | 59 |
@pytest.fixture
|
58 |
-def buildgrid():
|
|
59 |
- yield BuildGridInstance()
|
|
60 |
+def controller():
|
|
61 |
+ yield ExecutionController()
|
|
60 | 62 |
|
61 | 63 |
|
62 | 64 |
# Instance to test
|
63 | 65 |
@pytest.fixture
|
64 |
-def instance(buildgrid):
|
|
65 |
- instances = {instance_name: buildgrid}
|
|
66 |
- yield OperationsService(instances)
|
|
66 |
+def instance(controller):
|
|
67 |
+ instances = {instance_name: controller.operations_instance}
|
|
68 |
+ with mock.patch.object(service, 'operations_pb2_grpc'):
|
|
69 |
+ yield OperationsService(server, instances)
|
|
67 | 70 |
|
68 | 71 |
|
69 | 72 |
# Queue an execution, get operation corresponding to that request
|
70 |
-def test_get_operation(instance, buildgrid, execute_request, context):
|
|
71 |
- response_execute = buildgrid.execute(execute_request.action_digest,
|
|
72 |
- execute_request.skip_cache_lookup)
|
|
73 |
+def test_get_operation(instance, controller, execute_request, context):
|
|
74 |
+ response_execute = controller.execution_instance.execute(execute_request.action_digest,
|
|
75 |
+ execute_request.skip_cache_lookup)
|
|
73 | 76 |
|
74 | 77 |
request = operations_pb2.GetOperationRequest()
|
75 | 78 |
|
... | ... | @@ -94,9 +97,9 @@ def test_get_operation_instance_fail(instance, context): |
94 | 97 |
context.set_code.assert_called_once_with(grpc.StatusCode.INVALID_ARGUMENT)
|
95 | 98 |
|
96 | 99 |
|
97 |
-def test_list_operations(instance, buildgrid, execute_request, context):
|
|
98 |
- response_execute = buildgrid.execute(execute_request.action_digest,
|
|
99 |
- execute_request.skip_cache_lookup)
|
|
100 |
+def test_list_operations(instance, controller, execute_request, context):
|
|
101 |
+ response_execute = controller.execution_instance.execute(execute_request.action_digest,
|
|
102 |
+ execute_request.skip_cache_lookup)
|
|
100 | 103 |
|
101 | 104 |
request = operations_pb2.ListOperationsRequest(name=instance_name)
|
102 | 105 |
response = instance.ListOperations(request, context)
|
... | ... | @@ -104,9 +107,9 @@ def test_list_operations(instance, buildgrid, execute_request, context): |
104 | 107 |
assert response.operations[0].name.split('/')[-1] == response_execute.name
|
105 | 108 |
|
106 | 109 |
|
107 |
-def test_list_operations_instance_fail(instance, buildgrid, execute_request, context):
|
|
108 |
- buildgrid.execute(execute_request.action_digest,
|
|
109 |
- execute_request.skip_cache_lookup)
|
|
110 |
+def test_list_operations_instance_fail(instance, controller, execute_request, context):
|
|
111 |
+ controller.execution_instance.execute(execute_request.action_digest,
|
|
112 |
+ execute_request.skip_cache_lookup)
|
|
110 | 113 |
|
111 | 114 |
request = operations_pb2.ListOperationsRequest()
|
112 | 115 |
instance.ListOperations(request, context)
|
... | ... | @@ -114,16 +117,16 @@ def test_list_operations_instance_fail(instance, buildgrid, execute_request, con |
114 | 117 |
context.set_code.assert_called_once_with(grpc.StatusCode.INVALID_ARGUMENT)
|
115 | 118 |
|
116 | 119 |
|
117 |
-def test_list_operations_with_result(instance, buildgrid, execute_request, context):
|
|
118 |
- response_execute = buildgrid.execute(execute_request.action_digest,
|
|
119 |
- execute_request.skip_cache_lookup)
|
|
120 |
+def test_list_operations_with_result(instance, controller, execute_request, context):
|
|
121 |
+ response_execute = controller.execution_instance.execute(execute_request.action_digest,
|
|
122 |
+ execute_request.skip_cache_lookup)
|
|
120 | 123 |
|
121 | 124 |
action_result = remote_execution_pb2.ActionResult()
|
122 | 125 |
output_file = remote_execution_pb2.OutputFile(path='unicorn')
|
123 | 126 |
action_result.output_files.extend([output_file])
|
124 | 127 |
|
125 |
- buildgrid._scheduler.job_complete(response_execute.name,
|
|
126 |
- _pack_any(action_result))
|
|
128 |
+ controller.operations_instance._scheduler.job_complete(response_execute.name,
|
|
129 |
+ _pack_any(action_result))
|
|
127 | 130 |
|
128 | 131 |
request = operations_pb2.ListOperationsRequest(name=instance_name)
|
129 | 132 |
response = instance.ListOperations(request, context)
|
... | ... | @@ -144,9 +147,9 @@ def test_list_operations_empty(instance, context): |
144 | 147 |
|
145 | 148 |
|
146 | 149 |
# Send execution off, delete, try to find operation should fail
|
147 |
-def test_delete_operation(instance, buildgrid, execute_request, context):
|
|
148 |
- response_execute = buildgrid.execute(execute_request.action_digest,
|
|
149 |
- execute_request.skip_cache_lookup)
|
|
150 |
+def test_delete_operation(instance, controller, execute_request, context):
|
|
151 |
+ response_execute = controller.execution_instance.execute(execute_request.action_digest,
|
|
152 |
+ execute_request.skip_cache_lookup)
|
|
150 | 153 |
request = operations_pb2.DeleteOperationRequest()
|
151 | 154 |
request.name = "{}/{}".format(instance_name, response_execute.name)
|
152 | 155 |
instance.DeleteOperation(request, context)
|
... | ... | @@ -155,7 +158,7 @@ def test_delete_operation(instance, buildgrid, execute_request, context): |
155 | 158 |
request.name = "{}/{}".format(instance_name, response_execute.name)
|
156 | 159 |
|
157 | 160 |
with pytest.raises(InvalidArgumentError):
|
158 |
- buildgrid.get_operation(response_execute.name)
|
|
161 |
+ controller.operations_instance.get_operation(response_execute.name)
|
|
159 | 162 |
|
160 | 163 |
|
161 | 164 |
def test_delete_operation_fail(instance, context):
|
... | ... | @@ -25,10 +25,15 @@ from buildgrid._protos.build.bazel.remote.execution.v2 import remote_execution_p |
25 | 25 |
from buildgrid._protos.buildstream.v2 import buildstream_pb2
|
26 | 26 |
|
27 | 27 |
from buildgrid.server.cas.storage import lru_memory_cache
|
28 |
+from buildgrid.server.referencestorage import service
|
|
28 | 29 |
from buildgrid.server.referencestorage.service import ReferenceStorageService
|
29 | 30 |
from buildgrid.server.referencestorage.storage import ReferenceCache
|
30 | 31 |
|
31 | 32 |
|
33 |
+server = mock.create_autospec(grpc.server)
|
|
34 |
+instance_name = ''
|
|
35 |
+ |
|
36 |
+ |
|
32 | 37 |
# Can mock this
|
33 | 38 |
@pytest.fixture
|
34 | 39 |
def context():
|
... | ... | @@ -45,41 +50,49 @@ def cache(cas): |
45 | 50 |
yield ReferenceCache(cas, 50)
|
46 | 51 |
|
47 | 52 |
|
48 |
-def test_simple_result(cache, context):
|
|
53 |
+@pytest.fixture
|
|
54 |
+def instance(cache):
|
|
55 |
+ instances = {instance_name: cache}
|
|
56 |
+ with mock.patch.object(service, 'buildstream_pb2_grpc'):
|
|
57 |
+ yield ReferenceStorageService(server, instances)
|
|
58 |
+ |
|
59 |
+ |
|
60 |
+def test_simple_result(instance, context):
|
|
49 | 61 |
keys = ["rick", "roy", "rach"]
|
50 |
- service = ReferenceStorageService(cache)
|
|
51 | 62 |
|
52 | 63 |
# Check that before adding the ReferenceResult, attempting to fetch it fails
|
53 | 64 |
request = buildstream_pb2.GetReferenceRequest(key=keys[0])
|
54 |
- service.GetReference(request, context)
|
|
65 |
+ instance.GetReference(request, context)
|
|
55 | 66 |
context.set_code.assert_called_once_with(grpc.StatusCode.NOT_FOUND)
|
56 | 67 |
|
57 | 68 |
# Add an ReferenceResult to the cache
|
58 | 69 |
reference_result = remote_execution_pb2.Digest(hash='deckard')
|
59 | 70 |
request = buildstream_pb2.UpdateReferenceRequest(keys=keys,
|
60 | 71 |
digest=reference_result)
|
61 |
- service.UpdateReference(request, context)
|
|
72 |
+ instance.UpdateReference(request, context)
|
|
62 | 73 |
|
63 | 74 |
# Check that fetching it now works
|
64 | 75 |
for key in keys:
|
65 | 76 |
request = buildstream_pb2.GetReferenceRequest(key=key)
|
66 |
- fetched_result = service.GetReference(request, context)
|
|
77 |
+ fetched_result = instance.GetReference(request, context)
|
|
67 | 78 |
assert fetched_result.digest == reference_result
|
68 | 79 |
|
69 | 80 |
|
70 |
-def test_disabled_update_result(cache, context):
|
|
81 |
+def test_disabled_update_result(context):
|
|
71 | 82 |
disabled_push = ReferenceCache(cas, 50, False)
|
72 | 83 |
keys = ["rick", "roy", "rach"]
|
73 |
- service = ReferenceStorageService(disabled_push)
|
|
84 |
+ |
|
85 |
+ with mock.patch.object(service, 'buildstream_pb2_grpc'):
|
|
86 |
+ instance = ReferenceStorageService(server, {'': disabled_push})
|
|
74 | 87 |
|
75 | 88 |
# Add an ReferenceResult to the cache
|
76 | 89 |
reference_result = remote_execution_pb2.Digest(hash='deckard')
|
77 | 90 |
request = buildstream_pb2.UpdateReferenceRequest(keys=keys,
|
78 | 91 |
digest=reference_result)
|
79 |
- service.UpdateReference(request, context)
|
|
92 |
+ instance.UpdateReference(request, context)
|
|
80 | 93 |
|
81 | 94 |
request = buildstream_pb2.UpdateReferenceRequest()
|
82 |
- service.UpdateReference(request, context)
|
|
95 |
+ instance.UpdateReference(request, context)
|
|
83 | 96 |
|
84 | 97 |
context.set_code.assert_called_once_with(grpc.StatusCode.UNIMPLEMENTED)
|
85 | 98 |
|
... | ... | @@ -87,9 +100,10 @@ def test_disabled_update_result(cache, context): |
87 | 100 |
@pytest.mark.parametrize("allow_updates", [True, False])
|
88 | 101 |
def test_status(allow_updates, context):
|
89 | 102 |
cache = ReferenceCache(cas, 5, allow_updates)
|
90 |
- service = ReferenceStorageService(cache)
|
|
103 |
+ with mock.patch.object(service, 'buildstream_pb2_grpc'):
|
|
104 |
+ instance = ReferenceStorageService(server, {'': cache})
|
|
91 | 105 |
|
92 | 106 |
request = buildstream_pb2.StatusRequest()
|
93 |
- response = service.Status(request, context)
|
|
107 |
+ response = instance.Status(request, context)
|
|
94 | 108 |
|
95 | 109 |
assert response.allow_updates == allow_updates
|