blob: 33384506a2a358fe3239d860319130dc43c9453c [file] [log] [blame]
Ola Rozenfeldde32ae72016-09-20 14:13:56 +00001// Copyright 2015 The Bazel Authors. All rights reserved.
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.
14package com.google.devtools.build.lib.remote;
15
16import static com.google.common.truth.Truth.assertThat;
buchgrff008f42018-06-02 14:13:43 -070017import static com.google.devtools.build.lib.remote.util.Utils.getFromFuture;
Ola Rozenfeldde32ae72016-09-20 14:13:56 +000018import static java.nio.charset.StandardCharsets.UTF_8;
olaola460a1052017-06-09 04:33:25 +020019import static org.junit.Assert.fail;
20import static org.mockito.Mockito.when;
Ola Rozenfeldde32ae72016-09-20 14:13:56 +000021
olaolaf0aa55d2018-08-16 08:51:06 -070022import build.bazel.remote.execution.v2.Action;
23import build.bazel.remote.execution.v2.ActionCacheGrpc.ActionCacheImplBase;
24import build.bazel.remote.execution.v2.ActionResult;
25import build.bazel.remote.execution.v2.Command;
26import build.bazel.remote.execution.v2.ContentAddressableStorageGrpc.ContentAddressableStorageImplBase;
27import build.bazel.remote.execution.v2.Digest;
28import build.bazel.remote.execution.v2.Directory;
29import build.bazel.remote.execution.v2.DirectoryNode;
30import build.bazel.remote.execution.v2.FileNode;
31import build.bazel.remote.execution.v2.FindMissingBlobsRequest;
32import build.bazel.remote.execution.v2.FindMissingBlobsResponse;
33import build.bazel.remote.execution.v2.GetActionResultRequest;
34import build.bazel.remote.execution.v2.Tree;
35import build.bazel.remote.execution.v2.UpdateActionResultRequest;
olaola3ffc6a72017-04-19 19:01:43 +020036import com.google.api.client.json.GenericJson;
37import com.google.api.client.json.jackson2.JacksonFactory;
olaola460a1052017-06-09 04:33:25 +020038import com.google.bytestream.ByteStreamGrpc.ByteStreamImplBase;
39import com.google.bytestream.ByteStreamProto.ReadRequest;
40import com.google.bytestream.ByteStreamProto.ReadResponse;
41import com.google.bytestream.ByteStreamProto.WriteRequest;
42import com.google.bytestream.ByteStreamProto.WriteResponse;
Ola Rozenfeldde32ae72016-09-20 14:13:56 +000043import com.google.common.collect.ImmutableList;
Hadrien Chauvin3d0a04d2017-12-20 08:45:45 -080044import com.google.common.collect.ImmutableMap;
tomlud1a203b2018-08-02 11:44:26 -070045import com.google.common.collect.ImmutableSortedMap;
buchgrff008f42018-06-02 14:13:43 -070046import com.google.common.util.concurrent.ListeningScheduledExecutorService;
47import com.google.common.util.concurrent.MoreExecutors;
olaola460a1052017-06-09 04:33:25 +020048import com.google.devtools.build.lib.actions.ActionInputHelper;
tomlud1a203b2018-08-02 11:44:26 -070049import com.google.devtools.build.lib.actions.cache.VirtualActionInput;
buchgrf89689b2017-05-30 18:00:59 +020050import com.google.devtools.build.lib.authandtls.AuthAndTLSOptions;
Jakob Buchgraberdd11a0e2017-12-18 04:40:16 -080051import com.google.devtools.build.lib.authandtls.GoogleAuthUtils;
buchgr559a07d2017-11-30 11:09:35 -080052import com.google.devtools.build.lib.clock.JavaClock;
Jakob Buchgraberbc06db92019-03-07 00:21:52 -080053import com.google.devtools.build.lib.remote.RemoteRetrier.ExponentialBackoff;
buchgr7f725442019-03-08 03:20:25 -080054import com.google.devtools.build.lib.remote.merkletree.MerkleTree;
Jakob Buchgraber75b7ed42019-03-27 10:27:13 -070055import com.google.devtools.build.lib.remote.options.RemoteOptions;
Googler922d1e62018-03-05 14:49:00 -080056import com.google.devtools.build.lib.remote.util.DigestUtil;
57import com.google.devtools.build.lib.remote.util.DigestUtil.ActionKey;
buchgr7f725442019-03-08 03:20:25 -080058import com.google.devtools.build.lib.remote.util.StringActionInput;
Jakob Buchgraberbc06db92019-03-07 00:21:52 -080059import com.google.devtools.build.lib.remote.util.TestUtils;
Googler922d1e62018-03-05 14:49:00 -080060import com.google.devtools.build.lib.remote.util.TracingMetadataUtils;
Ola Rozenfeldde32ae72016-09-20 14:13:56 +000061import com.google.devtools.build.lib.testutil.Scratch;
ulfjack0d007ff2017-06-27 13:27:16 +020062import com.google.devtools.build.lib.util.io.FileOutErr;
ccalvarinbda12a12018-06-21 18:57:26 -070063import com.google.devtools.build.lib.vfs.DigestHashFunction;
olaola460a1052017-06-09 04:33:25 +020064import com.google.devtools.build.lib.vfs.FileSystem;
65import com.google.devtools.build.lib.vfs.FileSystemUtils;
Ola Rozenfeldde32ae72016-09-20 14:13:56 +000066import com.google.devtools.build.lib.vfs.Path;
tomlud1a203b2018-08-02 11:44:26 -070067import com.google.devtools.build.lib.vfs.PathFragment;
olaola460a1052017-06-09 04:33:25 +020068import com.google.devtools.build.lib.vfs.inmemoryfs.InMemoryFileSystem;
Ola Rozenfeldde32ae72016-09-20 14:13:56 +000069import com.google.devtools.common.options.Options;
70import com.google.protobuf.ByteString;
buchgrfe9ba892017-08-04 15:09:08 +020071import io.grpc.CallCredentials;
olaola3ffc6a72017-04-19 19:01:43 +020072import io.grpc.CallOptions;
73import io.grpc.Channel;
74import io.grpc.ClientCall;
75import io.grpc.ClientInterceptor;
olaola6f32d5a2017-09-20 17:12:19 +020076import io.grpc.Context;
olaola3ffc6a72017-04-19 19:01:43 +020077import io.grpc.MethodDescriptor;
Ola Rozenfeldde32ae72016-09-20 14:13:56 +000078import io.grpc.Server;
olaolafdd66ef2017-06-30 05:07:13 +020079import io.grpc.Status;
Ola Rozenfeldde32ae72016-09-20 14:13:56 +000080import io.grpc.inprocess.InProcessChannelBuilder;
81import io.grpc.inprocess.InProcessServerBuilder;
82import io.grpc.stub.StreamObserver;
olaola460a1052017-06-09 04:33:25 +020083import io.grpc.util.MutableHandlerRegistry;
olaola3ffc6a72017-04-19 19:01:43 +020084import java.io.IOException;
laszlocsomor59f17d62018-07-05 00:17:55 -070085import java.io.InputStream;
Jakob Buchgraberbac30fe2019-01-28 05:24:23 -080086import java.util.List;
buchgrff008f42018-06-02 14:13:43 -070087import java.util.concurrent.Executors;
tomlud1a203b2018-08-02 11:44:26 -070088import java.util.concurrent.atomic.AtomicBoolean;
olaola6a4cdf92018-11-23 08:29:02 -080089import java.util.concurrent.atomic.AtomicInteger;
Ola Rozenfeldde32ae72016-09-20 14:13:56 +000090import org.junit.After;
buchgrff008f42018-06-02 14:13:43 -070091import org.junit.AfterClass;
Ola Rozenfeldde32ae72016-09-20 14:13:56 +000092import org.junit.Before;
buchgrff008f42018-06-02 14:13:43 -070093import org.junit.BeforeClass;
Ola Rozenfeldde32ae72016-09-20 14:13:56 +000094import org.junit.Test;
95import org.junit.runner.RunWith;
96import org.junit.runners.JUnit4;
olaola460a1052017-06-09 04:33:25 +020097import org.mockito.Mockito;
98import org.mockito.invocation.InvocationOnMock;
99import org.mockito.stubbing.Answer;
Ola Rozenfeldde32ae72016-09-20 14:13:56 +0000100
ulfjackfc5cbbe2017-07-04 04:44:38 -0400101/** Tests for {@link GrpcRemoteCache}. */
Ola Rozenfeldde32ae72016-09-20 14:13:56 +0000102@RunWith(JUnit4.class)
ulfjackfc5cbbe2017-07-04 04:44:38 -0400103public class GrpcRemoteCacheTest {
buchgr559a07d2017-11-30 11:09:35 -0800104
ccalvarinbda12a12018-06-21 18:57:26 -0700105 private static final DigestUtil DIGEST_UTIL = new DigestUtil(DigestHashFunction.SHA256);
buchgr559a07d2017-11-30 11:09:35 -0800106
olaola460a1052017-06-09 04:33:25 +0200107 private FileSystem fs;
108 private Path execRoot;
ulfjack0d007ff2017-06-27 13:27:16 +0200109 private FileOutErr outErr;
olaola460a1052017-06-09 04:33:25 +0200110 private FakeActionInputFileCache fakeFileCache;
111 private final MutableHandlerRegistry serviceRegistry = new MutableHandlerRegistry();
112 private final String fakeServerName = "fake server for " + getClass();
113 private Server fakeServer;
Googlerf2867562018-05-06 07:51:45 -0700114 private Context withEmptyMetadata;
115 private Context prevContext;
buchgrff008f42018-06-02 14:13:43 -0700116 private static ListeningScheduledExecutorService retryService;
117
118 @BeforeClass
119 public static void beforeEverything() {
120 retryService = MoreExecutors.listeningDecorator(Executors.newScheduledThreadPool(1));
121 }
Ola Rozenfeldde32ae72016-09-20 14:13:56 +0000122
123 @Before
124 public final void setUp() throws Exception {
olaola460a1052017-06-09 04:33:25 +0200125 // Use a mutable service registry for later registering the service impl for each test case.
126 fakeServer =
127 InProcessServerBuilder.forName(fakeServerName)
128 .fallbackHandlerRegistry(serviceRegistry)
129 .directExecutor()
130 .build()
131 .start();
132 Chunker.setDefaultChunkSizeForTesting(1000); // Enough for everything to be one chunk.
ccalvarinbda12a12018-06-21 18:57:26 -0700133 fs = new InMemoryFileSystem(new JavaClock(), DigestHashFunction.SHA256);
olaola460a1052017-06-09 04:33:25 +0200134 execRoot = fs.getPath("/exec/root");
135 FileSystemUtils.createDirectoryAndParents(execRoot);
136 fakeFileCache = new FakeActionInputFileCache(execRoot);
ulfjack0d007ff2017-06-27 13:27:16 +0200137
138 Path stdout = fs.getPath("/tmp/stdout");
139 Path stderr = fs.getPath("/tmp/stderr");
140 FileSystemUtils.createDirectoryAndParents(stdout.getParentDirectory());
141 FileSystemUtils.createDirectoryAndParents(stderr.getParentDirectory());
142 outErr = new FileOutErr(stdout, stderr);
Googlerf2867562018-05-06 07:51:45 -0700143 withEmptyMetadata =
olaola6f32d5a2017-09-20 17:12:19 +0200144 TracingMetadataUtils.contextWithMetadata(
buchgr559a07d2017-11-30 11:09:35 -0800145 "none", "none", DIGEST_UTIL.asActionKey(Digest.getDefaultInstance()));
Googlerf2867562018-05-06 07:51:45 -0700146 prevContext = withEmptyMetadata.attach();
Ola Rozenfeldde32ae72016-09-20 14:13:56 +0000147 }
148
149 @After
olaola460a1052017-06-09 04:33:25 +0200150 public void tearDown() throws Exception {
Googlerf2867562018-05-06 07:51:45 -0700151 withEmptyMetadata.detach(prevContext);
olaola460a1052017-06-09 04:33:25 +0200152 fakeServer.shutdownNow();
olaola28228e02018-02-08 11:04:46 -0800153 fakeServer.awaitTermination();
Ola Rozenfeldde32ae72016-09-20 14:13:56 +0000154 }
155
buchgrff008f42018-06-02 14:13:43 -0700156 @AfterClass
157 public static void afterEverything() {
158 retryService.shutdownNow();
159 }
160
buchgrfe9ba892017-08-04 15:09:08 +0200161 private static class CallCredentialsInterceptor implements ClientInterceptor {
162 private final CallCredentials credentials;
olaola3ffc6a72017-04-19 19:01:43 +0200163
buchgrfe9ba892017-08-04 15:09:08 +0200164 public CallCredentialsInterceptor(CallCredentials credentials) {
165 this.credentials = credentials;
olaola3ffc6a72017-04-19 19:01:43 +0200166 }
167
168 @Override
169 public <RequestT, ResponseT> ClientCall<RequestT, ResponseT> interceptCall(
170 MethodDescriptor<RequestT, ResponseT> method, CallOptions callOptions, Channel next) {
buchgrfe9ba892017-08-04 15:09:08 +0200171 assertThat(callOptions.getCredentials()).isEqualTo(credentials);
olaola3ffc6a72017-04-19 19:01:43 +0200172 // Remove the call credentials to allow testing with dummy ones.
173 return next.newCall(method, callOptions.withCallCredentials(null));
174 }
175 }
176
ulfjackfc5cbbe2017-07-04 04:44:38 -0400177 private GrpcRemoteCache newClient() throws IOException {
olaola6a4cdf92018-11-23 08:29:02 -0800178 return newClient(Options.getDefaults(RemoteOptions.class));
179 }
180
181 private GrpcRemoteCache newClient(RemoteOptions remoteOptions) throws IOException {
olaola460a1052017-06-09 04:33:25 +0200182 AuthAndTLSOptions authTlsOptions = Options.getDefaults(AuthAndTLSOptions.class);
Jakob Buchgraber8a7c63e2017-12-20 10:13:23 -0800183 authTlsOptions.useGoogleDefaultCredentials = true;
184 authTlsOptions.googleCredentials = "/exec/root/creds.json";
185 authTlsOptions.googleAuthScopes = ImmutableList.of("dummy.scope");
olaola3ffc6a72017-04-19 19:01:43 +0200186
187 GenericJson json = new GenericJson();
188 json.put("type", "authorized_user");
189 json.put("client_id", "some_client");
190 json.put("client_secret", "foo");
191 json.put("refresh_token", "bar");
olaola460a1052017-06-09 04:33:25 +0200192 Scratch scratch = new Scratch();
Jakob Buchgraber8a7c63e2017-12-20 10:13:23 -0800193 scratch.file(authTlsOptions.googleCredentials, new JacksonFactory().toString(json));
olaola3ffc6a72017-04-19 19:01:43 +0200194
buchgrb50fe862018-07-12 04:01:45 -0700195 CallCredentials creds;
laszlocsomor59f17d62018-07-05 00:17:55 -0700196 try (InputStream in = scratch.resolve(authTlsOptions.googleCredentials).getInputStream()) {
buchgrb50fe862018-07-12 04:01:45 -0700197 creds = GoogleAuthUtils.newCallCredentials(in, authTlsOptions.googleAuthScopes);
laszlocsomor59f17d62018-07-05 00:17:55 -0700198 }
buchgr44e40bc2017-12-04 10:44:47 -0800199 RemoteRetrier retrier =
Jakob Buchgraberbc06db92019-03-07 00:21:52 -0800200 TestUtils.newRemoteRetrier(
201 () -> new ExponentialBackoff(remoteOptions),
buchgrff008f42018-06-02 14:13:43 -0700202 RemoteRetrier.RETRIABLE_GRPC_ERRORS,
Jakob Buchgraberbc06db92019-03-07 00:21:52 -0800203 retryService);
buchgrb50fe862018-07-12 04:01:45 -0700204 ReferenceCountedChannel channel =
205 new ReferenceCountedChannel(InProcessChannelBuilder.forName(fakeServerName).directExecutor()
206 .intercept(new CallCredentialsInterceptor(creds)).build());
207 ByteStreamUploader uploader =
208 new ByteStreamUploader(remoteOptions.remoteInstanceName, channel.retain(), creds,
209 remoteOptions.remoteTimeout, retrier);
210 return new GrpcRemoteCache(channel.retain(),
buchgrfe9ba892017-08-04 15:09:08 +0200211 creds,
buchgr67bd6fc2017-07-12 15:59:31 +0200212 remoteOptions,
buchgr559a07d2017-11-30 11:09:35 -0800213 retrier,
buchgrb50fe862018-07-12 04:01:45 -0700214 DIGEST_UTIL,
215 uploader);
olaola3ffc6a72017-04-19 19:01:43 +0200216 }
217
tomlud1a203b2018-08-02 11:44:26 -0700218 @Test
219 public void testVirtualActionInputSupport() throws Exception {
220 GrpcRemoteCache client = newClient();
tomlud1a203b2018-08-02 11:44:26 -0700221 PathFragment execPath = PathFragment.create("my/exec/path");
buchgr7f725442019-03-08 03:20:25 -0800222 VirtualActionInput virtualActionInput = new StringActionInput("hello", execPath);
223 MerkleTree merkleTree =
224 MerkleTree.build(
225 ImmutableSortedMap.of(execPath, virtualActionInput),
226 fakeFileCache,
227 execRoot,
228 DIGEST_UTIL);
tomlud1a203b2018-08-02 11:44:26 -0700229 Digest digest = DIGEST_UTIL.compute(virtualActionInput.getBytes().toByteArray());
tomlud1a203b2018-08-02 11:44:26 -0700230
231 // Add a fake CAS that responds saying that the above virtual action input is missing
232 serviceRegistry.addService(
233 new ContentAddressableStorageImplBase() {
234 @Override
235 public void findMissingBlobs(
236 FindMissingBlobsRequest request,
237 StreamObserver<FindMissingBlobsResponse> responseObserver) {
238 responseObserver.onNext(
239 FindMissingBlobsResponse.newBuilder().addMissingBlobDigests(digest).build());
240 responseObserver.onCompleted();
241 }
242 });
243
244 // Mock a byte stream and assert that we see the virtual action input with contents 'hello'
245 AtomicBoolean writeOccurred = new AtomicBoolean();
246 serviceRegistry.addService(
247 new ByteStreamImplBase() {
248 @Override
249 public StreamObserver<WriteRequest> write(
250 final StreamObserver<WriteResponse> responseObserver) {
251 return new StreamObserver<WriteRequest>() {
252 @Override
253 public void onNext(WriteRequest request) {
254 assertThat(request.getResourceName()).contains(digest.getHash());
255 assertThat(request.getFinishWrite()).isTrue();
256 assertThat(request.getData().toStringUtf8()).isEqualTo("hello");
257 writeOccurred.set(true);
258 }
259
260 @Override
261 public void onCompleted() {
262 responseObserver.onNext(WriteResponse.newBuilder().setCommittedSize(5).build());
263 responseObserver.onCompleted();
264 }
265
266 @Override
267 public void onError(Throwable t) {
268 fail("An error occurred: " + t);
269 }
270 };
271 }
272 });
273
274 // Upload all missing inputs (that is, the virtual action input from above)
buchgr7f725442019-03-08 03:20:25 -0800275 client.ensureInputsPresent(merkleTree, ImmutableMap.of(), execRoot);
tomlud1a203b2018-08-02 11:44:26 -0700276 }
277
olaola460a1052017-06-09 04:33:25 +0200278 @Test
279 public void testDownloadEmptyBlob() throws Exception {
ulfjackfc5cbbe2017-07-04 04:44:38 -0400280 GrpcRemoteCache client = newClient();
buchgr559a07d2017-11-30 11:09:35 -0800281 Digest emptyDigest = DIGEST_UTIL.compute(new byte[0]);
olaola460a1052017-06-09 04:33:25 +0200282 // Will not call the mock Bytestream interface at all.
buchgrff008f42018-06-02 14:13:43 -0700283 assertThat(getFromFuture(client.downloadBlob(emptyDigest))).isEmpty();
olaola460a1052017-06-09 04:33:25 +0200284 }
Ola Rozenfeldde32ae72016-09-20 14:13:56 +0000285
olaola460a1052017-06-09 04:33:25 +0200286 @Test
287 public void testDownloadBlobSingleChunk() throws Exception {
ulfjackfc5cbbe2017-07-04 04:44:38 -0400288 final GrpcRemoteCache client = newClient();
buchgr559a07d2017-11-30 11:09:35 -0800289 final Digest digest = DIGEST_UTIL.computeAsUtf8("abcdefg");
olaola460a1052017-06-09 04:33:25 +0200290 serviceRegistry.addService(
291 new ByteStreamImplBase() {
292 @Override
293 public void read(ReadRequest request, StreamObserver<ReadResponse> responseObserver) {
294 assertThat(request.getResourceName().contains(digest.getHash())).isTrue();
295 responseObserver.onNext(
296 ReadResponse.newBuilder().setData(ByteString.copyFromUtf8("abcdefg")).build());
297 responseObserver.onCompleted();
Ola Rozenfeldde32ae72016-09-20 14:13:56 +0000298 }
olaola460a1052017-06-09 04:33:25 +0200299 });
buchgrff008f42018-06-02 14:13:43 -0700300 assertThat(new String(getFromFuture(client.downloadBlob(digest)), UTF_8)).isEqualTo("abcdefg");
olaola460a1052017-06-09 04:33:25 +0200301 }
Ola Rozenfeldde32ae72016-09-20 14:13:56 +0000302
olaola460a1052017-06-09 04:33:25 +0200303 @Test
304 public void testDownloadBlobMultipleChunks() throws Exception {
ulfjackfc5cbbe2017-07-04 04:44:38 -0400305 final GrpcRemoteCache client = newClient();
buchgr559a07d2017-11-30 11:09:35 -0800306 final Digest digest = DIGEST_UTIL.computeAsUtf8("abcdefg");
olaola460a1052017-06-09 04:33:25 +0200307 serviceRegistry.addService(
308 new ByteStreamImplBase() {
309 @Override
310 public void read(ReadRequest request, StreamObserver<ReadResponse> responseObserver) {
311 assertThat(request.getResourceName().contains(digest.getHash())).isTrue();
312 responseObserver.onNext(
313 ReadResponse.newBuilder().setData(ByteString.copyFromUtf8("abc")).build());
314 responseObserver.onNext(
315 ReadResponse.newBuilder().setData(ByteString.copyFromUtf8("def")).build());
316 responseObserver.onNext(
317 ReadResponse.newBuilder().setData(ByteString.copyFromUtf8("g")).build());
318 responseObserver.onCompleted();
319 }
320 });
buchgrff008f42018-06-02 14:13:43 -0700321 assertThat(new String(getFromFuture(client.downloadBlob(digest)), UTF_8)).isEqualTo("abcdefg");
olaola460a1052017-06-09 04:33:25 +0200322 }
Ola Rozenfeldde32ae72016-09-20 14:13:56 +0000323
olaola460a1052017-06-09 04:33:25 +0200324 @Test
325 public void testDownloadAllResults() throws Exception {
ulfjackfc5cbbe2017-07-04 04:44:38 -0400326 GrpcRemoteCache client = newClient();
buchgr559a07d2017-11-30 11:09:35 -0800327 Digest fooDigest = DIGEST_UTIL.computeAsUtf8("foo-contents");
328 Digest barDigest = DIGEST_UTIL.computeAsUtf8("bar-contents");
329 Digest emptyDigest = DIGEST_UTIL.compute(new byte[0]);
olaola460a1052017-06-09 04:33:25 +0200330 serviceRegistry.addService(
331 new FakeImmutableCacheByteStreamImpl(fooDigest, "foo-contents", barDigest, "bar-contents"));
332
333 ActionResult.Builder result = ActionResult.newBuilder();
334 result.addOutputFilesBuilder().setPath("a/foo").setDigest(fooDigest);
335 result.addOutputFilesBuilder().setPath("b/empty").setDigest(emptyDigest);
336 result.addOutputFilesBuilder().setPath("a/bar").setDigest(barDigest).setIsExecutable(true);
ulfjack0d007ff2017-06-27 13:27:16 +0200337 client.download(result.build(), execRoot, null);
buchgr559a07d2017-11-30 11:09:35 -0800338 assertThat(DIGEST_UTIL.compute(execRoot.getRelative("a/foo"))).isEqualTo(fooDigest);
339 assertThat(DIGEST_UTIL.compute(execRoot.getRelative("b/empty"))).isEqualTo(emptyDigest);
340 assertThat(DIGEST_UTIL.compute(execRoot.getRelative("a/bar"))).isEqualTo(barDigest);
olaola460a1052017-06-09 04:33:25 +0200341 assertThat(execRoot.getRelative("a/bar").isExecutable()).isTrue();
342 }
343
344 @Test
Hadrien Chauvin3d0a04d2017-12-20 08:45:45 -0800345 public void testDownloadDirectory() throws Exception {
346 GrpcRemoteCache client = newClient();
347 Digest fooDigest = DIGEST_UTIL.computeAsUtf8("foo-contents");
348 Digest quxDigest = DIGEST_UTIL.computeAsUtf8("qux-contents");
349 Tree barTreeMessage =
350 Tree.newBuilder()
351 .setRoot(
352 Directory.newBuilder()
353 .addFiles(
354 FileNode.newBuilder()
355 .setName("qux")
356 .setDigest(quxDigest)
357 .setIsExecutable(true)))
358 .build();
359 Digest barTreeDigest = DIGEST_UTIL.compute(barTreeMessage);
360 serviceRegistry.addService(
361 new FakeImmutableCacheByteStreamImpl(
362 ImmutableMap.of(
363 fooDigest, "foo-contents",
364 barTreeDigest, barTreeMessage.toByteString(),
365 quxDigest, "qux-contents")));
366
367 ActionResult.Builder result = ActionResult.newBuilder();
368 result.addOutputFilesBuilder().setPath("a/foo").setDigest(fooDigest);
369 result.addOutputDirectoriesBuilder().setPath("a/bar").setTreeDigest(barTreeDigest);
370 client.download(result.build(), execRoot, null);
371
372 assertThat(DIGEST_UTIL.compute(execRoot.getRelative("a/foo"))).isEqualTo(fooDigest);
373 assertThat(DIGEST_UTIL.compute(execRoot.getRelative("a/bar/qux"))).isEqualTo(quxDigest);
374 assertThat(execRoot.getRelative("a/bar/qux").isExecutable()).isTrue();
375 }
376
377 @Test
378 public void testDownloadDirectoryEmpty() throws Exception {
379 GrpcRemoteCache client = newClient();
380 Tree barTreeMessage = Tree.newBuilder().setRoot(Directory.newBuilder()).build();
381 Digest barTreeDigest = DIGEST_UTIL.compute(barTreeMessage);
382 serviceRegistry.addService(
383 new FakeImmutableCacheByteStreamImpl(
384 ImmutableMap.of(barTreeDigest, barTreeMessage.toByteString())));
385
386 ActionResult.Builder result = ActionResult.newBuilder();
387 result.addOutputDirectoriesBuilder().setPath("a/bar").setTreeDigest(barTreeDigest);
388 client.download(result.build(), execRoot, null);
389
390 assertThat(execRoot.getRelative("a/bar").isDirectory()).isTrue();
391 }
392
393 @Test
394 public void testDownloadDirectoryNested() throws Exception {
395 GrpcRemoteCache client = newClient();
396 Digest fooDigest = DIGEST_UTIL.computeAsUtf8("foo-contents");
397 Digest quxDigest = DIGEST_UTIL.computeAsUtf8("qux-contents");
398 Directory wobbleDirMessage =
399 Directory.newBuilder()
400 .addFiles(FileNode.newBuilder().setName("qux").setDigest(quxDigest))
401 .build();
402 Digest wobbleDirDigest = DIGEST_UTIL.compute(wobbleDirMessage);
403 Tree barTreeMessage =
404 Tree.newBuilder()
405 .setRoot(
406 Directory.newBuilder()
407 .addFiles(
408 FileNode.newBuilder()
409 .setName("qux")
410 .setDigest(quxDigest)
411 .setIsExecutable(true))
412 .addDirectories(
413 DirectoryNode.newBuilder().setName("wobble").setDigest(wobbleDirDigest)))
414 .addChildren(wobbleDirMessage)
415 .build();
416 Digest barTreeDigest = DIGEST_UTIL.compute(barTreeMessage);
417 serviceRegistry.addService(
418 new FakeImmutableCacheByteStreamImpl(
419 ImmutableMap.of(
420 fooDigest, "foo-contents",
421 barTreeDigest, barTreeMessage.toByteString(),
422 quxDigest, "qux-contents")));
423
424 ActionResult.Builder result = ActionResult.newBuilder();
425 result.addOutputFilesBuilder().setPath("a/foo").setDigest(fooDigest);
426 result.addOutputDirectoriesBuilder().setPath("a/bar").setTreeDigest(barTreeDigest);
427 client.download(result.build(), execRoot, null);
428
429 assertThat(DIGEST_UTIL.compute(execRoot.getRelative("a/foo"))).isEqualTo(fooDigest);
430 assertThat(DIGEST_UTIL.compute(execRoot.getRelative("a/bar/wobble/qux"))).isEqualTo(quxDigest);
431 assertThat(execRoot.getRelative("a/bar/wobble/qux").isExecutable()).isFalse();
432 }
433
434 @Test
olaolafdd66ef2017-06-30 05:07:13 +0200435 public void testUploadBlobCacheHitWithRetries() throws Exception {
ulfjackfc5cbbe2017-07-04 04:44:38 -0400436 final GrpcRemoteCache client = newClient();
buchgr559a07d2017-11-30 11:09:35 -0800437 final Digest digest = DIGEST_UTIL.computeAsUtf8("abcdefg");
olaola460a1052017-06-09 04:33:25 +0200438 serviceRegistry.addService(
439 new ContentAddressableStorageImplBase() {
olaolafdd66ef2017-06-30 05:07:13 +0200440 private int numErrors = 4;
441
olaola460a1052017-06-09 04:33:25 +0200442 @Override
443 public void findMissingBlobs(
444 FindMissingBlobsRequest request,
445 StreamObserver<FindMissingBlobsResponse> responseObserver) {
olaolafdd66ef2017-06-30 05:07:13 +0200446 if (numErrors-- <= 0) {
447 responseObserver.onNext(FindMissingBlobsResponse.getDefaultInstance());
448 responseObserver.onCompleted();
449 } else {
450 responseObserver.onError(Status.UNAVAILABLE.asRuntimeException());
451 }
olaola460a1052017-06-09 04:33:25 +0200452 }
453 });
454 assertThat(client.uploadBlob("abcdefg".getBytes(UTF_8))).isEqualTo(digest);
455 }
456
457 @Test
458 public void testUploadBlobSingleChunk() throws Exception {
ulfjackfc5cbbe2017-07-04 04:44:38 -0400459 final GrpcRemoteCache client = newClient();
buchgr559a07d2017-11-30 11:09:35 -0800460 final Digest digest = DIGEST_UTIL.computeAsUtf8("abcdefg");
olaola460a1052017-06-09 04:33:25 +0200461 serviceRegistry.addService(
462 new ContentAddressableStorageImplBase() {
463 @Override
464 public void findMissingBlobs(
465 FindMissingBlobsRequest request,
466 StreamObserver<FindMissingBlobsResponse> responseObserver) {
467 responseObserver.onNext(
468 FindMissingBlobsResponse.newBuilder().addMissingBlobDigests(digest).build());
469 responseObserver.onCompleted();
470 }
471 });
472 serviceRegistry.addService(
473 new ByteStreamImplBase() {
474 @Override
475 public StreamObserver<WriteRequest> write(
476 final StreamObserver<WriteResponse> responseObserver) {
477 return new StreamObserver<WriteRequest>() {
478 @Override
479 public void onNext(WriteRequest request) {
480 assertThat(request.getResourceName()).contains(digest.getHash());
481 assertThat(request.getFinishWrite()).isTrue();
482 assertThat(request.getData().toStringUtf8()).isEqualTo("abcdefg");
483 }
484
485 @Override
486 public void onCompleted() {
487 responseObserver.onNext(WriteResponse.newBuilder().setCommittedSize(7).build());
488 responseObserver.onCompleted();
489 }
490
491 @Override
492 public void onError(Throwable t) {
493 fail("An error occurred: " + t);
494 }
495 };
496 }
497 });
498 assertThat(client.uploadBlob("abcdefg".getBytes(UTF_8))).isEqualTo(digest);
499 }
500
501 static class TestChunkedRequestObserver implements StreamObserver<WriteRequest> {
502 private final StreamObserver<WriteResponse> responseObserver;
503 private final String contents;
504 private Chunker chunker;
505
506 public TestChunkedRequestObserver(
507 StreamObserver<WriteResponse> responseObserver, String contents, int chunkSizeBytes) {
508 this.responseObserver = responseObserver;
509 this.contents = contents;
tomlue18be0b2018-08-03 11:34:23 -0700510 chunker =
511 Chunker.builder(DIGEST_UTIL)
512 .setInput(contents.getBytes(UTF_8))
513 .setChunkSize(chunkSizeBytes)
514 .build();
Ola Rozenfeldde32ae72016-09-20 14:13:56 +0000515 }
olaola460a1052017-06-09 04:33:25 +0200516
517 @Override
518 public void onNext(WriteRequest request) {
519 assertThat(chunker.hasNext()).isTrue();
520 try {
521 Chunker.Chunk chunk = chunker.next();
522 Digest digest = chunk.getDigest();
523 long offset = chunk.getOffset();
buchgr226510b2017-07-12 10:29:27 +0200524 ByteString data = chunk.getData();
olaola460a1052017-06-09 04:33:25 +0200525 if (offset == 0) {
526 assertThat(request.getResourceName()).contains(digest.getHash());
527 } else {
528 assertThat(request.getResourceName()).isEmpty();
529 }
530 assertThat(request.getFinishWrite())
buchgr226510b2017-07-12 10:29:27 +0200531 .isEqualTo(offset + data.size() == digest.getSizeBytes());
532 assertThat(request.getData()).isEqualTo(data);
olaola460a1052017-06-09 04:33:25 +0200533 } catch (IOException e) {
534 fail("An error occurred:" + e);
535 }
536 }
537
538 @Override
539 public void onCompleted() {
540 assertThat(chunker.hasNext()).isFalse();
541 responseObserver.onNext(
542 WriteResponse.newBuilder().setCommittedSize(contents.length()).build());
543 responseObserver.onCompleted();
544 }
545
546 @Override
547 public void onError(Throwable t) {
548 fail("An error occurred: " + t);
549 }
550 }
551
552 private Answer<StreamObserver<WriteRequest>> blobChunkedWriteAnswer(
553 final String contents, final int chunkSize) {
554 return new Answer<StreamObserver<WriteRequest>>() {
555 @Override
556 @SuppressWarnings("unchecked")
557 public StreamObserver<WriteRequest> answer(InvocationOnMock invocation) {
558 return new TestChunkedRequestObserver(
559 (StreamObserver<WriteResponse>) invocation.getArguments()[0], contents, chunkSize);
560 }
561 };
562 }
563
564 @Test
565 public void testUploadBlobMultipleChunks() throws Exception {
buchgr559a07d2017-11-30 11:09:35 -0800566 final Digest digest = DIGEST_UTIL.computeAsUtf8("abcdef");
olaola460a1052017-06-09 04:33:25 +0200567 serviceRegistry.addService(
568 new ContentAddressableStorageImplBase() {
569 @Override
570 public void findMissingBlobs(
571 FindMissingBlobsRequest request,
572 StreamObserver<FindMissingBlobsResponse> responseObserver) {
573 responseObserver.onNext(
574 FindMissingBlobsResponse.newBuilder().addMissingBlobDigests(digest).build());
575 responseObserver.onCompleted();
576 }
577 });
578
579 ByteStreamImplBase mockByteStreamImpl = Mockito.mock(ByteStreamImplBase.class);
580 serviceRegistry.addService(mockByteStreamImpl);
581 for (int chunkSize = 1; chunkSize <= 6; ++chunkSize) {
ulfjackfc5cbbe2017-07-04 04:44:38 -0400582 GrpcRemoteCache client = newClient();
olaola460a1052017-06-09 04:33:25 +0200583 Chunker.setDefaultChunkSizeForTesting(chunkSize);
584 when(mockByteStreamImpl.write(Mockito.<StreamObserver<WriteResponse>>anyObject()))
585 .thenAnswer(blobChunkedWriteAnswer("abcdef", chunkSize));
586 assertThat(client.uploadBlob("abcdef".getBytes(UTF_8))).isEqualTo(digest);
587 }
Ola Rozenfeld688dbf72017-07-14 16:36:08 +0200588 Mockito.verify(mockByteStreamImpl, Mockito.times(6))
589 .write(Mockito.<StreamObserver<WriteResponse>>anyObject());
olaola460a1052017-06-09 04:33:25 +0200590 }
591
592 @Test
Hadrien Chauvin3d0a04d2017-12-20 08:45:45 -0800593 public void testUploadDirectory() throws Exception {
594 final GrpcRemoteCache client = newClient();
595 final Digest fooDigest =
596 fakeFileCache.createScratchInput(ActionInputHelper.fromPath("a/foo"), "xyz");
597 final Digest quxDigest =
598 fakeFileCache.createScratchInput(ActionInputHelper.fromPath("bar/qux"), "abc");
599 final Digest barDigest =
600 fakeFileCache.createScratchInputDirectory(
601 ActionInputHelper.fromPath("bar"),
602 Tree.newBuilder()
603 .setRoot(
604 Directory.newBuilder()
605 .addFiles(
606 FileNode.newBuilder()
607 .setIsExecutable(true)
608 .setName("qux")
609 .setDigest(quxDigest)
610 .build())
611 .build())
612 .build());
613 final Path fooFile = execRoot.getRelative("a/foo");
614 final Path quxFile = execRoot.getRelative("bar/qux");
615 quxFile.setExecutable(true);
616 final Path barDir = execRoot.getRelative("bar");
617 serviceRegistry.addService(
618 new ContentAddressableStorageImplBase() {
619 @Override
620 public void findMissingBlobs(
621 FindMissingBlobsRequest request,
622 StreamObserver<FindMissingBlobsResponse> responseObserver) {
Jakob Buchgraberbac30fe2019-01-28 05:24:23 -0800623 assertThat(request.getBlobDigestsList()).containsAllOf(fooDigest, quxDigest, barDigest);
Hadrien Chauvin3d0a04d2017-12-20 08:45:45 -0800624 // Nothing is missing.
625 responseObserver.onNext(FindMissingBlobsResponse.getDefaultInstance());
626 responseObserver.onCompleted();
627 }
628 });
629
Jakob Buchgraberbac30fe2019-01-28 05:24:23 -0800630 ActionResult result = uploadDirectory(client, ImmutableList.<Path>of(fooFile, barDir));
Hadrien Chauvin3d0a04d2017-12-20 08:45:45 -0800631 ActionResult.Builder expectedResult = ActionResult.newBuilder();
632 expectedResult.addOutputFilesBuilder().setPath("a/foo").setDigest(fooDigest);
633 expectedResult.addOutputDirectoriesBuilder().setPath("bar").setTreeDigest(barDigest);
Jakob Buchgraberbac30fe2019-01-28 05:24:23 -0800634 assertThat(result).isEqualTo(expectedResult.build());
Hadrien Chauvin3d0a04d2017-12-20 08:45:45 -0800635 }
636
637 @Test
638 public void testUploadDirectoryEmpty() throws Exception {
639 final GrpcRemoteCache client = newClient();
640 final Digest barDigest =
641 fakeFileCache.createScratchInputDirectory(
642 ActionInputHelper.fromPath("bar"),
643 Tree.newBuilder().setRoot(Directory.newBuilder().build()).build());
644 final Path barDir = execRoot.getRelative("bar");
645 serviceRegistry.addService(
646 new ContentAddressableStorageImplBase() {
647 @Override
648 public void findMissingBlobs(
649 FindMissingBlobsRequest request,
650 StreamObserver<FindMissingBlobsResponse> responseObserver) {
Jakob Buchgraberbac30fe2019-01-28 05:24:23 -0800651 assertThat(request.getBlobDigestsList()).contains(barDigest);
Hadrien Chauvin3d0a04d2017-12-20 08:45:45 -0800652 // Nothing is missing.
653 responseObserver.onNext(FindMissingBlobsResponse.getDefaultInstance());
654 responseObserver.onCompleted();
655 }
656 });
657
Jakob Buchgraberbac30fe2019-01-28 05:24:23 -0800658 ActionResult result = uploadDirectory(client, ImmutableList.<Path>of(barDir));
Hadrien Chauvin3d0a04d2017-12-20 08:45:45 -0800659 ActionResult.Builder expectedResult = ActionResult.newBuilder();
660 expectedResult.addOutputDirectoriesBuilder().setPath("bar").setTreeDigest(barDigest);
Jakob Buchgraberbac30fe2019-01-28 05:24:23 -0800661 assertThat(result).isEqualTo(expectedResult.build());
Hadrien Chauvin3d0a04d2017-12-20 08:45:45 -0800662 }
663
664 @Test
665 public void testUploadDirectoryNested() throws Exception {
666 final GrpcRemoteCache client = newClient();
667 final Digest wobbleDigest =
668 fakeFileCache.createScratchInput(ActionInputHelper.fromPath("bar/test/wobble"), "xyz");
669 final Digest quxDigest =
670 fakeFileCache.createScratchInput(ActionInputHelper.fromPath("bar/qux"), "abc");
671 final Directory testDirMessage =
672 Directory.newBuilder()
673 .addFiles(FileNode.newBuilder().setName("wobble").setDigest(wobbleDigest).build())
674 .build();
675 final Digest testDigest = DIGEST_UTIL.compute(testDirMessage);
676 final Tree barTree =
677 Tree.newBuilder()
678 .setRoot(
679 Directory.newBuilder()
680 .addFiles(
681 FileNode.newBuilder()
682 .setIsExecutable(true)
683 .setName("qux")
684 .setDigest(quxDigest))
685 .addDirectories(
686 DirectoryNode.newBuilder().setName("test").setDigest(testDigest)))
687 .addChildren(testDirMessage)
688 .build();
689 final Digest barDigest =
690 fakeFileCache.createScratchInputDirectory(ActionInputHelper.fromPath("bar"), barTree);
691 final Path quxFile = execRoot.getRelative("bar/qux");
692 quxFile.setExecutable(true);
693 final Path barDir = execRoot.getRelative("bar");
694 serviceRegistry.addService(
695 new ContentAddressableStorageImplBase() {
696 @Override
697 public void findMissingBlobs(
698 FindMissingBlobsRequest request,
699 StreamObserver<FindMissingBlobsResponse> responseObserver) {
700 assertThat(request.getBlobDigestsList())
Jakob Buchgraberbac30fe2019-01-28 05:24:23 -0800701 .containsAllOf(quxDigest, barDigest, wobbleDigest);
Hadrien Chauvin3d0a04d2017-12-20 08:45:45 -0800702 // Nothing is missing.
703 responseObserver.onNext(FindMissingBlobsResponse.getDefaultInstance());
704 responseObserver.onCompleted();
705 }
706 });
707
Jakob Buchgraberbac30fe2019-01-28 05:24:23 -0800708 ActionResult result = uploadDirectory(client, ImmutableList.of(barDir));
Hadrien Chauvin3d0a04d2017-12-20 08:45:45 -0800709 ActionResult.Builder expectedResult = ActionResult.newBuilder();
710 expectedResult.addOutputDirectoriesBuilder().setPath("bar").setTreeDigest(barDigest);
Jakob Buchgraberbac30fe2019-01-28 05:24:23 -0800711 assertThat(result).isEqualTo(expectedResult.build());
712 }
713
714 private ActionResult uploadDirectory(GrpcRemoteCache client, List<Path> outputs)
715 throws Exception {
716 ActionResult.Builder result = ActionResult.newBuilder();
717 Action action = Action.getDefaultInstance();
718 ActionKey actionKey = DIGEST_UTIL.computeActionKey(action);
719 Command cmd = Command.getDefaultInstance();
720 client.upload(execRoot, actionKey, action, cmd, outputs, outErr, result);
721 return result.build();
Hadrien Chauvin3d0a04d2017-12-20 08:45:45 -0800722 }
723
724 @Test
olaolafdd66ef2017-06-30 05:07:13 +0200725 public void testUploadCacheHits() throws Exception {
ulfjackfc5cbbe2017-07-04 04:44:38 -0400726 final GrpcRemoteCache client = newClient();
olaola460a1052017-06-09 04:33:25 +0200727 final Digest fooDigest =
728 fakeFileCache.createScratchInput(ActionInputHelper.fromPath("a/foo"), "xyz");
729 final Digest barDigest =
730 fakeFileCache.createScratchInput(ActionInputHelper.fromPath("bar"), "x");
731 final Path fooFile = execRoot.getRelative("a/foo");
732 final Path barFile = execRoot.getRelative("bar");
733 barFile.setExecutable(true);
olaolaf0aa55d2018-08-16 08:51:06 -0700734 Command command = Command.newBuilder().addOutputFiles("a/foo").build();
735 final Digest cmdDigest = DIGEST_UTIL.compute(command.toByteArray());
736 Action action = Action.newBuilder().setCommandDigest(cmdDigest).build();
737 final Digest actionDigest = DIGEST_UTIL.compute(action.toByteArray());
olaola460a1052017-06-09 04:33:25 +0200738 serviceRegistry.addService(
739 new ContentAddressableStorageImplBase() {
740 @Override
741 public void findMissingBlobs(
742 FindMissingBlobsRequest request,
743 StreamObserver<FindMissingBlobsResponse> responseObserver) {
olaolaf0aa55d2018-08-16 08:51:06 -0700744 assertThat(request.getBlobDigestsList())
745 .containsExactly(cmdDigest, actionDigest, fooDigest, barDigest);
olaola460a1052017-06-09 04:33:25 +0200746 // Nothing is missing.
747 responseObserver.onNext(FindMissingBlobsResponse.getDefaultInstance());
748 responseObserver.onCompleted();
749 }
750 });
751
752 ActionResult.Builder result = ActionResult.newBuilder();
olaolaf0aa55d2018-08-16 08:51:06 -0700753 client.upload(
Benjamin Petersonab439762018-09-28 12:51:40 -0700754 execRoot,
755 DIGEST_UTIL.asActionKey(actionDigest),
756 action,
757 command,
758 ImmutableList.<Path>of(fooFile, barFile),
759 outErr,
Benjamin Petersonab439762018-09-28 12:51:40 -0700760 result);
olaola460a1052017-06-09 04:33:25 +0200761 ActionResult.Builder expectedResult = ActionResult.newBuilder();
762 expectedResult.addOutputFilesBuilder().setPath("a/foo").setDigest(fooDigest);
763 expectedResult
764 .addOutputFilesBuilder()
765 .setPath("bar")
766 .setDigest(barDigest)
767 .setIsExecutable(true);
768 assertThat(result.build()).isEqualTo(expectedResult.build());
769 }
770
771 @Test
olaola6a4cdf92018-11-23 08:29:02 -0800772 public void testUploadSplitMissingDigestsCall() throws Exception {
773 final Digest fooDigest =
774 fakeFileCache.createScratchInput(ActionInputHelper.fromPath("a/foo"), "xyz");
775 final Digest barDigest =
776 fakeFileCache.createScratchInput(ActionInputHelper.fromPath("bar"), "x");
777 final Path fooFile = execRoot.getRelative("a/foo");
778 final Path barFile = execRoot.getRelative("bar");
779 barFile.setExecutable(true);
780 Command command = Command.newBuilder().addOutputFiles("a/foo").build();
781 final Digest cmdDigest = DIGEST_UTIL.compute(command.toByteArray());
782 Action action = Action.newBuilder().setCommandDigest(cmdDigest).build();
783 final Digest actionDigest = DIGEST_UTIL.compute(action.toByteArray());
784 AtomicInteger numGetMissingCalls = new AtomicInteger();
785 serviceRegistry.addService(
786 new ContentAddressableStorageImplBase() {
787 @Override
788 public void findMissingBlobs(
789 FindMissingBlobsRequest request,
790 StreamObserver<FindMissingBlobsResponse> responseObserver) {
791 numGetMissingCalls.incrementAndGet();
792 assertThat(request.getBlobDigestsCount()).isEqualTo(1);
793 // Nothing is missing.
794 responseObserver.onNext(FindMissingBlobsResponse.getDefaultInstance());
795 responseObserver.onCompleted();
796 }
797 });
798
799 RemoteOptions options = Options.getDefaults(RemoteOptions.class);
800 options.maxOutboundMessageSize = 80; // Enough for one digest, but not two.
801 final GrpcRemoteCache client = newClient(options);
802 ActionResult.Builder result = ActionResult.newBuilder();
803 client.upload(
804 execRoot,
805 DIGEST_UTIL.asActionKey(actionDigest),
806 action,
807 command,
808 ImmutableList.<Path>of(fooFile, barFile),
809 outErr,
olaola6a4cdf92018-11-23 08:29:02 -0800810 result);
811 ActionResult.Builder expectedResult = ActionResult.newBuilder();
812 expectedResult.addOutputFilesBuilder().setPath("a/foo").setDigest(fooDigest);
813 expectedResult
814 .addOutputFilesBuilder()
815 .setPath("bar")
816 .setDigest(barDigest)
817 .setIsExecutable(true);
818 assertThat(result.build()).isEqualTo(expectedResult.build());
819 assertThat(numGetMissingCalls.get()).isEqualTo(4);
820 }
821
822 @Test
olaolafdd66ef2017-06-30 05:07:13 +0200823 public void testUploadCacheMissesWithRetries() throws Exception {
ulfjackfc5cbbe2017-07-04 04:44:38 -0400824 final GrpcRemoteCache client = newClient();
olaola460a1052017-06-09 04:33:25 +0200825 final Digest fooDigest =
826 fakeFileCache.createScratchInput(ActionInputHelper.fromPath("a/foo"), "xyz");
827 final Digest barDigest =
828 fakeFileCache.createScratchInput(ActionInputHelper.fromPath("bar"), "x");
olaolafdd66ef2017-06-30 05:07:13 +0200829 final Digest bazDigest =
830 fakeFileCache.createScratchInput(ActionInputHelper.fromPath("baz"), "z");
olaola460a1052017-06-09 04:33:25 +0200831 final Path fooFile = execRoot.getRelative("a/foo");
832 final Path barFile = execRoot.getRelative("bar");
olaolafdd66ef2017-06-30 05:07:13 +0200833 final Path bazFile = execRoot.getRelative("baz");
buchgr559a07d2017-11-30 11:09:35 -0800834 ActionKey actionKey = DIGEST_UTIL.asActionKey(fooDigest); // Could be any key.
olaola460a1052017-06-09 04:33:25 +0200835 barFile.setExecutable(true);
836 serviceRegistry.addService(
837 new ContentAddressableStorageImplBase() {
olaolafdd66ef2017-06-30 05:07:13 +0200838 private int numErrors = 4;
839
olaola460a1052017-06-09 04:33:25 +0200840 @Override
841 public void findMissingBlobs(
842 FindMissingBlobsRequest request,
843 StreamObserver<FindMissingBlobsResponse> responseObserver) {
olaolafdd66ef2017-06-30 05:07:13 +0200844 if (numErrors-- <= 0) {
olaolaf0aa55d2018-08-16 08:51:06 -0700845 // All outputs are missing.
Ola Rozenfeld688dbf72017-07-14 16:36:08 +0200846 responseObserver.onNext(
847 FindMissingBlobsResponse.newBuilder()
848 .addMissingBlobDigests(fooDigest)
849 .addMissingBlobDigests(barDigest)
850 .addMissingBlobDigests(bazDigest)
851 .build());
olaolafdd66ef2017-06-30 05:07:13 +0200852 responseObserver.onCompleted();
853 } else {
854 responseObserver.onError(Status.UNAVAILABLE.asRuntimeException());
855 }
856 }
857 });
858 ActionResult.Builder rb = ActionResult.newBuilder();
859 rb.addOutputFilesBuilder().setPath("a/foo").setDigest(fooDigest);
860 rb.addOutputFilesBuilder().setPath("bar").setDigest(barDigest).setIsExecutable(true);
861 rb.addOutputFilesBuilder().setPath("baz").setDigest(bazDigest);
862 ActionResult result = rb.build();
863 serviceRegistry.addService(
864 new ActionCacheImplBase() {
865 private int numErrors = 4;
866
867 @Override
868 public void updateActionResult(
869 UpdateActionResultRequest request, StreamObserver<ActionResult> responseObserver) {
olaola460a1052017-06-09 04:33:25 +0200870 assertThat(request)
871 .isEqualTo(
olaolafdd66ef2017-06-30 05:07:13 +0200872 UpdateActionResultRequest.newBuilder()
873 .setActionDigest(fooDigest)
874 .setActionResult(result)
olaola460a1052017-06-09 04:33:25 +0200875 .build());
olaolafdd66ef2017-06-30 05:07:13 +0200876 if (numErrors-- <= 0) {
877 responseObserver.onNext(result);
878 responseObserver.onCompleted();
879 } else {
880 responseObserver.onError(Status.UNAVAILABLE.asRuntimeException());
881 }
olaola460a1052017-06-09 04:33:25 +0200882 }
883 });
884 ByteStreamImplBase mockByteStreamImpl = Mockito.mock(ByteStreamImplBase.class);
885 serviceRegistry.addService(mockByteStreamImpl);
886 when(mockByteStreamImpl.write(Mockito.<StreamObserver<WriteResponse>>anyObject()))
Ola Rozenfeld688dbf72017-07-14 16:36:08 +0200887 .thenAnswer(
888 new Answer<StreamObserver<WriteRequest>>() {
889 private int numErrors = 4;
olaola460a1052017-06-09 04:33:25 +0200890
Ola Rozenfeld688dbf72017-07-14 16:36:08 +0200891 @Override
892 @SuppressWarnings("unchecked")
893 public StreamObserver<WriteRequest> answer(InvocationOnMock invocation) {
894 StreamObserver<WriteResponse> responseObserver =
895 (StreamObserver<WriteResponse>) invocation.getArguments()[0];
896 return new StreamObserver<WriteRequest>() {
897 @Override
898 public void onNext(WriteRequest request) {
899 numErrors--;
900 if (numErrors >= 0) {
901 responseObserver.onError(Status.UNAVAILABLE.asRuntimeException());
902 return;
903 }
904 assertThat(request.getFinishWrite()).isTrue();
905 String resourceName = request.getResourceName();
906 String dataStr = request.getData().toStringUtf8();
907 int size = 0;
908 if (resourceName.contains(fooDigest.getHash())) {
909 assertThat(dataStr).isEqualTo("xyz");
910 size = 3;
911 } else if (resourceName.contains(barDigest.getHash())) {
912 assertThat(dataStr).isEqualTo("x");
913 size = 1;
914 } else if (resourceName.contains(bazDigest.getHash())) {
915 assertThat(dataStr).isEqualTo("z");
916 size = 1;
917 } else {
918 fail("Unexpected resource name in upload: " + resourceName);
919 }
920 responseObserver.onNext(
921 WriteResponse.newBuilder().setCommittedSize(size).build());
922 }
923
924 @Override
925 public void onCompleted() {
926 responseObserver.onCompleted();
927 }
928
929 @Override
930 public void onError(Throwable t) {
931 fail("An error occurred: " + t);
932 }
933 };
934 }
935 });
olaola7744b862017-09-18 23:04:33 +0200936 client.upload(
olaolaf0aa55d2018-08-16 08:51:06 -0700937 actionKey,
938 Action.getDefaultInstance(),
939 Command.getDefaultInstance(),
940 execRoot,
941 ImmutableList.<Path>of(fooFile, barFile, bazFile),
Jakob Buchgraberbac30fe2019-01-28 05:24:23 -0800942 outErr);
Ola Rozenfeld688dbf72017-07-14 16:36:08 +0200943 // 4 times for the errors, 3 times for the successful uploads.
944 Mockito.verify(mockByteStreamImpl, Mockito.times(7))
945 .write(Mockito.<StreamObserver<WriteResponse>>anyObject());
olaolafdd66ef2017-06-30 05:07:13 +0200946 }
947
948 @Test
949 public void testGetCachedActionResultWithRetries() throws Exception {
ulfjackfc5cbbe2017-07-04 04:44:38 -0400950 final GrpcRemoteCache client = newClient();
buchgr559a07d2017-11-30 11:09:35 -0800951 ActionKey actionKey = DIGEST_UTIL.asActionKey(DIGEST_UTIL.computeAsUtf8("key"));
olaolafdd66ef2017-06-30 05:07:13 +0200952 serviceRegistry.addService(
953 new ActionCacheImplBase() {
954 private int numErrors = 4;
955
956 @Override
957 public void getActionResult(
958 GetActionResultRequest request, StreamObserver<ActionResult> responseObserver) {
959 responseObserver.onError(
960 (numErrors-- <= 0 ? Status.NOT_FOUND : Status.UNAVAILABLE).asRuntimeException());
961 }
962 });
963 assertThat(client.getCachedActionResult(actionKey)).isNull();
Ola Rozenfeldde32ae72016-09-20 14:13:56 +0000964 }
George Gensure9813c582019-03-20 02:59:44 -0700965
966 @Test
967 public void downloadBlobIsRetriedWithProgress() throws IOException, InterruptedException {
968 final GrpcRemoteCache client = newClient();
969 final Digest digest = DIGEST_UTIL.computeAsUtf8("abcdefg");
970 serviceRegistry.addService(
971 new ByteStreamImplBase() {
972 @Override
973 public void read(ReadRequest request, StreamObserver<ReadResponse> responseObserver) {
974 assertThat(request.getResourceName().contains(digest.getHash())).isTrue();
975 ByteString data = ByteString.copyFromUtf8("abcdefg");
976 int off = (int) request.getReadOffset();
977 if (off == 0) {
978 data = data.substring(0, 1);
979 } else {
980 data = data.substring(off);
981 }
982 responseObserver.onNext(ReadResponse.newBuilder().setData(data).build());
983 if (off == 0) {
984 responseObserver.onError(Status.DEADLINE_EXCEEDED.asException());
985 } else {
986 responseObserver.onCompleted();
987 }
988 }
989 });
990 assertThat(new String(getFromFuture(client.downloadBlob(digest)), UTF_8)).isEqualTo("abcdefg");
991 }
992
993 @Test
994 public void downloadBlobPassesThroughDeadlineExceededWithoutProgress()
995 throws IOException, InterruptedException {
996 final GrpcRemoteCache client = newClient();
997 final Digest digest = DIGEST_UTIL.computeAsUtf8("abcdefg");
998 serviceRegistry.addService(
999 new ByteStreamImplBase() {
1000 @Override
1001 public void read(ReadRequest request, StreamObserver<ReadResponse> responseObserver) {
1002 assertThat(request.getResourceName().contains(digest.getHash())).isTrue();
1003 ByteString data = ByteString.copyFromUtf8("abcdefg");
1004 if (request.getReadOffset() == 0) {
1005 responseObserver.onNext(
1006 ReadResponse.newBuilder().setData(data.substring(0, 2)).build());
1007 }
1008 responseObserver.onError(Status.DEADLINE_EXCEEDED.asException());
1009 }
1010 });
George Gensure9813c582019-03-20 02:59:44 -07001011 try {
1012 getFromFuture(client.downloadBlob(digest));
George Gensure80bff992019-03-28 09:58:35 -07001013 fail("Should have thrown an exception.");
1014 } catch (IOException e) {
George Gensure9813c582019-03-20 02:59:44 -07001015 Status st = Status.fromThrowable(e);
George Gensure80bff992019-03-28 09:58:35 -07001016 assertThat(st.getCode()).isEqualTo(Status.Code.DEADLINE_EXCEEDED);
George Gensure9813c582019-03-20 02:59:44 -07001017 }
George Gensure9813c582019-03-20 02:59:44 -07001018 }
ishikhmane038a262019-04-03 07:06:58 -07001019
1020 @Test
1021 public void isRemoteCacheOptionsWhenGrpcEnabled() {
1022 RemoteOptions options = Options.getDefaults(RemoteOptions.class);
1023 options.remoteCache = "grpc://some-host.com";
1024
1025 assertThat(GrpcRemoteCache.isRemoteCacheOptions(options)).isTrue();
1026 }
1027
1028 @Test
1029 public void isRemoteCacheOptionsWhenGrpcEnabledUpperCase() {
1030 RemoteOptions options = Options.getDefaults(RemoteOptions.class);
1031 options.remoteCache = "GRPC://some-host.com";
1032
1033 assertThat(GrpcRemoteCache.isRemoteCacheOptions(options)).isTrue();
1034 }
1035
1036 @Test
1037 public void isRemoteCacheOptionsWhenDefaultRemoteCacheEnabledForLocalhost() {
1038 RemoteOptions options = Options.getDefaults(RemoteOptions.class);
1039 options.remoteCache = "localhost:1234";
1040
1041 assertThat(GrpcRemoteCache.isRemoteCacheOptions(options)).isTrue();
1042 }
1043
1044 @Test
1045 public void isRemoteCacheOptionsWhenDefaultRemoteCacheEnabled() {
1046 RemoteOptions options = Options.getDefaults(RemoteOptions.class);
1047 options.remoteCache = "some-host.com:1234";
1048
1049 assertThat(GrpcRemoteCache.isRemoteCacheOptions(options)).isTrue();
1050 }
1051
1052 @Test
1053 public void isRemoteCacheOptionsWhenHttpEnabled() {
1054 RemoteOptions options = Options.getDefaults(RemoteOptions.class);
1055 options.remoteCache = "http://some-host.com";
1056
1057 assertThat(GrpcRemoteCache.isRemoteCacheOptions(options)).isFalse();
1058 }
1059
1060 @Test
1061 public void isRemoteCacheOptionsWhenHttpEnabledWithUpperCase() {
1062 RemoteOptions options = Options.getDefaults(RemoteOptions.class);
1063 options.remoteCache = "HTTP://some-host.com";
1064
1065 assertThat(GrpcRemoteCache.isRemoteCacheOptions(options)).isFalse();
1066 }
1067
1068 @Test
1069 public void isRemoteCacheOptionsWhenHttpsEnabled() {
1070 RemoteOptions options = Options.getDefaults(RemoteOptions.class);
1071 options.remoteCache = "https://some-host.com";
1072
1073 assertThat(GrpcRemoteCache.isRemoteCacheOptions(options)).isFalse();
1074 }
1075
1076 @Test
1077 public void isRemoteCacheOptionsWhenUnknownScheme() {
1078 RemoteOptions options = Options.getDefaults(RemoteOptions.class);
1079 options.remoteCache = "grp://some-host.com";
1080
1081 // TODO(ishikhman): add proper vaildation and flip to false
1082 assertThat(GrpcRemoteCache.isRemoteCacheOptions(options)).isTrue();
1083 }
1084
1085 @Test
1086 public void isRemoteCacheOptionsWhenUnknownSchemeStartsAsGrpc() {
1087 RemoteOptions options = Options.getDefaults(RemoteOptions.class);
1088 options.remoteCache = "grpcsss://some-host.com";
1089
1090 // TODO(ishikhman): add proper vaildation and flip to false
1091 assertThat(GrpcRemoteCache.isRemoteCacheOptions(options)).isTrue();
1092 }
1093
1094 @Test
1095 public void isRemoteCacheOptionsWhenEmptyCacheProvided() {
1096 RemoteOptions options = Options.getDefaults(RemoteOptions.class);
1097 options.remoteCache = "";
1098
1099 assertThat(GrpcRemoteCache.isRemoteCacheOptions(options)).isFalse();
1100 }
1101
1102 @Test
1103 public void isRemoteCacheOptionsWhenRemoteCacheDisabled() {
1104 RemoteOptions options = Options.getDefaults(RemoteOptions.class);
1105
1106 assertThat(GrpcRemoteCache.isRemoteCacheOptions(options)).isFalse();
1107 }
Ola Rozenfeldde32ae72016-09-20 14:13:56 +00001108}