blob: 110f3cbd1e035d82c752dd90515ae6edd4544a81 [file] [log] [blame]
buchgrb50fe862018-07-12 04:01:45 -07001// Copyright 2018 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;
jcaterb9226772019-04-29 12:04:52 -070017import static com.google.devtools.build.lib.testutil.MoreAsserts.assertThrows;
tomlu9efbc252018-08-10 11:51:20 -070018import static org.mockito.Mockito.mock;
buchgrb50fe862018-07-12 04:01:45 -070019
olaolaf0aa55d2018-08-16 08:51:06 -070020import build.bazel.remote.execution.v2.Digest;
buchgrb50fe862018-07-12 04:01:45 -070021import com.google.bytestream.ByteStreamProto.WriteRequest;
22import com.google.bytestream.ByteStreamProto.WriteResponse;
George Gensure3c9089b2019-05-02 07:07:55 -070023import com.google.common.hash.HashCode;
buchgrb50fe862018-07-12 04:01:45 -070024import com.google.common.io.BaseEncoding;
25import com.google.common.util.concurrent.ListeningScheduledExecutorService;
26import com.google.common.util.concurrent.MoreExecutors;
27import com.google.devtools.build.lib.buildeventstream.BuildEvent.LocalFile;
28import com.google.devtools.build.lib.buildeventstream.BuildEvent.LocalFile.LocalFileType;
29import com.google.devtools.build.lib.buildeventstream.PathConverter;
30import com.google.devtools.build.lib.clock.JavaClock;
31import com.google.devtools.build.lib.remote.ByteStreamUploaderTest.FixedBackoff;
32import com.google.devtools.build.lib.remote.ByteStreamUploaderTest.MaybeFailOnceUploadService;
buchgrb50fe862018-07-12 04:01:45 -070033import com.google.devtools.build.lib.remote.util.DigestUtil;
Jakob Buchgraberbc06db92019-03-07 00:21:52 -080034import com.google.devtools.build.lib.remote.util.TestUtils;
buchgrb50fe862018-07-12 04:01:45 -070035import com.google.devtools.build.lib.remote.util.TracingMetadataUtils;
36import com.google.devtools.build.lib.vfs.DigestHashFunction;
37import com.google.devtools.build.lib.vfs.FileSystem;
38import com.google.devtools.build.lib.vfs.Path;
39import com.google.devtools.build.lib.vfs.inmemoryfs.InMemoryFileSystem;
buchgrb50fe862018-07-12 04:01:45 -070040import io.grpc.Context;
41import io.grpc.ManagedChannel;
42import io.grpc.Server;
43import io.grpc.Status;
44import io.grpc.inprocess.InProcessChannelBuilder;
45import io.grpc.inprocess.InProcessServerBuilder;
46import io.grpc.stub.StreamObserver;
47import io.grpc.util.MutableHandlerRegistry;
48import java.io.OutputStream;
49import java.util.HashMap;
50import java.util.Map;
51import java.util.Random;
52import java.util.concurrent.ExecutionException;
53import java.util.concurrent.Executors;
Benjamin Peterson97b67322019-04-23 06:03:59 -070054import java.util.concurrent.TimeUnit;
buchgrb50fe862018-07-12 04:01:45 -070055import org.junit.After;
56import org.junit.AfterClass;
57import org.junit.Before;
58import org.junit.BeforeClass;
59import org.junit.Test;
60import org.junit.runner.RunWith;
61import org.junit.runners.JUnit4;
62import org.mockito.MockitoAnnotations;
63
64/** Test for {@link ByteStreamBuildEventArtifactUploader}. */
65@RunWith(JUnit4.class)
66public class ByteStreamBuildEventArtifactUploaderTest {
67
68 private static final DigestUtil DIGEST_UTIL = new DigestUtil(DigestHashFunction.SHA256);
69
70 private final MutableHandlerRegistry serviceRegistry = new MutableHandlerRegistry();
71 private static ListeningScheduledExecutorService retryService;
72
73 private Server server;
74 private ManagedChannel channel;
75 private Context withEmptyMetadata;
76 private Context prevContext;
77 private final FileSystem fs = new InMemoryFileSystem(new JavaClock(), DigestHashFunction.SHA256);
78
79 @BeforeClass
80 public static void beforeEverything() {
81 retryService = MoreExecutors.listeningDecorator(Executors.newScheduledThreadPool(1));
82 }
83
84 @Before
85 public final void setUp() throws Exception {
86 MockitoAnnotations.initMocks(this);
87
88 String serverName = "Server for " + this.getClass();
89 server =
90 InProcessServerBuilder.forName(serverName)
91 .fallbackHandlerRegistry(serviceRegistry)
92 .build()
93 .start();
94 channel = InProcessChannelBuilder.forName(serverName).build();
95 withEmptyMetadata =
96 TracingMetadataUtils.contextWithMetadata(
97 "none", "none", DIGEST_UTIL.asActionKey(Digest.getDefaultInstance()));
98 // Needs to be repeated in every test that uses the timeout setting, since the tests run
99 // on different threads than the setUp.
100 prevContext = withEmptyMetadata.attach();
101 }
102
103 @After
104 public void tearDown() throws Exception {
105 // Needs to be repeated in every test that uses the timeout setting, since the tests run
106 // on different threads than the tearDown.
107 withEmptyMetadata.detach(prevContext);
108
Benjamin Peterson97b67322019-04-23 06:03:59 -0700109 channel.shutdownNow();
110 channel.awaitTermination(5, TimeUnit.SECONDS);
buchgrb50fe862018-07-12 04:01:45 -0700111 server.shutdownNow();
112 server.awaitTermination();
113 }
114
115 @AfterClass
116 public static void afterEverything() {
117 retryService.shutdownNow();
118 }
119
120 @Before
121 public void setup() {
122 MockitoAnnotations.initMocks(this);
123 }
124
125 @Test
126 public void uploadsShouldWork() throws Exception {
127 int numUploads = 2;
George Gensure3c9089b2019-05-02 07:07:55 -0700128 Map<HashCode, byte[]> blobsByHash = new HashMap<>();
buchgrb50fe862018-07-12 04:01:45 -0700129 Map<Path, LocalFile> filesToUpload = new HashMap<>();
130 Random rand = new Random();
131 for (int i = 0; i < numUploads; i++) {
132 Path file = fs.getPath("/file" + i);
133 OutputStream out = file.getOutputStream();
134 int blobSize = rand.nextInt(100) + 1;
135 byte[] blob = new byte[blobSize];
136 rand.nextBytes(blob);
137 out.write(blob);
138 out.close();
George Gensure3c9089b2019-05-02 07:07:55 -0700139 blobsByHash.put(HashCode.fromString(DIGEST_UTIL.compute(file).getHash()), blob);
buchgrb50fe862018-07-12 04:01:45 -0700140 filesToUpload.put(file, new LocalFile(file, LocalFileType.OUTPUT));
141 }
142 serviceRegistry.addService(new MaybeFailOnceUploadService(blobsByHash));
143
144 RemoteRetrier retrier =
Jakob Buchgraberbc06db92019-03-07 00:21:52 -0800145 TestUtils.newRemoteRetrier(() -> new FixedBackoff(1, 0), (e) -> true, retryService);
buchgrb50fe862018-07-12 04:01:45 -0700146 ReferenceCountedChannel refCntChannel = new ReferenceCountedChannel(channel);
147 ByteStreamUploader uploader =
148 new ByteStreamUploader("instance", refCntChannel, null, 3, retrier);
149 ByteStreamBuildEventArtifactUploader artifactUploader =
150 new ByteStreamBuildEventArtifactUploader(
pcloudy798b9a92018-12-04 02:31:49 -0800151 uploader, "localhost", withEmptyMetadata, "instance", /* maxUploadThreads= */ 100);
buchgrb50fe862018-07-12 04:01:45 -0700152
153 PathConverter pathConverter = artifactUploader.upload(filesToUpload).get();
154 for (Path file : filesToUpload.keySet()) {
155 String hash = BaseEncoding.base16().lowerCase().encode(file.getDigest());
156 long size = file.getFileSize();
157 String conversion = pathConverter.apply(file);
158 assertThat(conversion)
159 .isEqualTo("bytestream://localhost/instance/blobs/" + hash + "/" + size);
160 }
161
162 artifactUploader.shutdown();
163
164 assertThat(uploader.refCnt()).isEqualTo(0);
165 assertThat(refCntChannel.isShutdown()).isTrue();
166 }
167
168 @Test
tomlu9efbc252018-08-10 11:51:20 -0700169 public void testUploadDirectoryDoesNotCrash() throws Exception {
170 Path dir = fs.getPath("/dir");
171 dir.createDirectoryAndParents();
172 Map<Path, LocalFile> filesToUpload = new HashMap<>();
173 filesToUpload.put(dir, new LocalFile(dir, LocalFileType.OUTPUT));
174 ByteStreamUploader uploader = mock(ByteStreamUploader.class);
175 ByteStreamBuildEventArtifactUploader artifactUploader =
176 new ByteStreamBuildEventArtifactUploader(
pcloudy798b9a92018-12-04 02:31:49 -0800177 uploader, "localhost", withEmptyMetadata, "instance", /* maxUploadThreads= */ 100);
tomlu9efbc252018-08-10 11:51:20 -0700178 PathConverter pathConverter = artifactUploader.upload(filesToUpload).get();
179 assertThat(pathConverter.apply(dir)).isNull();
180 artifactUploader.shutdown();
181 }
182
183 @Test
buchgrb50fe862018-07-12 04:01:45 -0700184 public void someUploadsFail() throws Exception {
185 // Test that if one of multiple file uploads fails, the upload future fails and that the
186 // error is propagated correctly.
187
188 int numUploads = 10;
George Gensure3c9089b2019-05-02 07:07:55 -0700189 Map<HashCode, byte[]> blobsByHash = new HashMap<>();
buchgrb50fe862018-07-12 04:01:45 -0700190 Map<Path, LocalFile> filesToUpload = new HashMap<>();
191 Random rand = new Random();
192 for (int i = 0; i < numUploads; i++) {
193 Path file = fs.getPath("/file" + i);
194 OutputStream out = file.getOutputStream();
195 int blobSize = rand.nextInt(100) + 1;
196 byte[] blob = new byte[blobSize];
197 rand.nextBytes(blob);
198 out.write(blob);
199 out.flush();
200 out.close();
George Gensure3c9089b2019-05-02 07:07:55 -0700201 blobsByHash.put(HashCode.fromString(DIGEST_UTIL.compute(file).getHash()), blob);
buchgrb50fe862018-07-12 04:01:45 -0700202 filesToUpload.put(file, new LocalFile(file, LocalFileType.OUTPUT));
203 }
George Gensure3c9089b2019-05-02 07:07:55 -0700204 String hashOfBlobThatShouldFail = blobsByHash.keySet().iterator().next().toString();
buchgrb50fe862018-07-12 04:01:45 -0700205 serviceRegistry.addService(new MaybeFailOnceUploadService(blobsByHash) {
206 @Override
207 public StreamObserver<WriteRequest> write(StreamObserver<WriteResponse> response) {
208 StreamObserver<WriteRequest> delegate = super.write(response);
209 return new StreamObserver<WriteRequest>() {
210 @Override
211 public void onNext(WriteRequest value) {
212 if (value.getResourceName().contains(hashOfBlobThatShouldFail)) {
213 response.onError(Status.CANCELLED.asException());
214 } else {
215 delegate.onNext(value);
216 }
217 }
218
219 @Override
220 public void onError(Throwable t) {
221 delegate.onError(t);
222 }
223
224 @Override
225 public void onCompleted() {
226 delegate.onCompleted();
227 }
228 };
229 }
230 });
231
232 RemoteRetrier retrier =
Jakob Buchgraberbc06db92019-03-07 00:21:52 -0800233 TestUtils.newRemoteRetrier(() -> new FixedBackoff(1, 0), (e) -> true, retryService);
buchgrb50fe862018-07-12 04:01:45 -0700234 ReferenceCountedChannel refCntChannel = new ReferenceCountedChannel(channel);
235 ByteStreamUploader uploader =
236 new ByteStreamUploader("instance", refCntChannel, null, 3, retrier);
237 ByteStreamBuildEventArtifactUploader artifactUploader =
238 new ByteStreamBuildEventArtifactUploader(
pcloudy798b9a92018-12-04 02:31:49 -0800239 uploader, "localhost", withEmptyMetadata, "instance", /* maxUploadThreads= */ 100);
buchgrb50fe862018-07-12 04:01:45 -0700240
jcaterb9226772019-04-29 12:04:52 -0700241 ExecutionException e =
242 assertThrows(ExecutionException.class, () -> artifactUploader.upload(filesToUpload).get());
243 assertThat(Status.fromThrowable(e).getCode()).isEqualTo(Status.CANCELLED.getCode());
buchgrb50fe862018-07-12 04:01:45 -0700244
245 artifactUploader.shutdown();
246
247 assertThat(uploader.refCnt()).isEqualTo(0);
248 assertThat(refCntChannel.isShutdown()).isTrue();
249 }
250}