/*
* Copyright 2016-present Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package com.facebook.buck.distributed;
import com.facebook.buck.distributed.thrift.AppendBuildSlaveEventsRequest;
import com.facebook.buck.distributed.thrift.AppendBuildSlaveEventsResponse;
import com.facebook.buck.distributed.thrift.BuildJob;
import com.facebook.buck.distributed.thrift.BuildJobState;
import com.facebook.buck.distributed.thrift.BuildJobStateFileHashEntry;
import com.facebook.buck.distributed.thrift.BuildJobStateFileHashes;
import com.facebook.buck.distributed.thrift.BuildJobStateTargetGraph;
import com.facebook.buck.distributed.thrift.BuildJobStateTargetNode;
import com.facebook.buck.distributed.thrift.BuildMode;
import com.facebook.buck.distributed.thrift.BuildSlaveConsoleEvent;
import com.facebook.buck.distributed.thrift.BuildSlaveEvent;
import com.facebook.buck.distributed.thrift.BuildSlaveEventType;
import com.facebook.buck.distributed.thrift.BuildSlaveEventsQuery;
import com.facebook.buck.distributed.thrift.BuildSlaveEventsRange;
import com.facebook.buck.distributed.thrift.BuildSlaveStatus;
import com.facebook.buck.distributed.thrift.BuildStatusResponse;
import com.facebook.buck.distributed.thrift.CASContainsResponse;
import com.facebook.buck.distributed.thrift.CreateBuildResponse;
import com.facebook.buck.distributed.thrift.FetchBuildSlaveStatusRequest;
import com.facebook.buck.distributed.thrift.FetchBuildSlaveStatusResponse;
import com.facebook.buck.distributed.thrift.FrontendRequest;
import com.facebook.buck.distributed.thrift.FrontendRequestType;
import com.facebook.buck.distributed.thrift.FrontendResponse;
import com.facebook.buck.distributed.thrift.MultiGetBuildSlaveEventsRequest;
import com.facebook.buck.distributed.thrift.MultiGetBuildSlaveEventsResponse;
import com.facebook.buck.distributed.thrift.PathWithUnixSeparators;
import com.facebook.buck.distributed.thrift.RunId;
import com.facebook.buck.distributed.thrift.SequencedBuildSlaveEvent;
import com.facebook.buck.distributed.thrift.StampedeId;
import com.facebook.buck.distributed.thrift.StartBuildResponse;
import com.facebook.buck.distributed.thrift.UpdateBuildSlaveStatusRequest;
import com.facebook.buck.distributed.thrift.UpdateBuildSlaveStatusResponse;
import com.facebook.buck.model.Pair;
import com.facebook.buck.testutil.integration.TemporaryPaths;
import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.easymock.Capture;
import org.easymock.EasyMock;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
// TODO(ruibm, shivanker): Revisit these tests and clean them up.
public class DistBuildServiceTest {
@Rule public TemporaryPaths temporaryFolder = new TemporaryPaths();
private FrontendService frontendService;
private DistBuildService distBuildService;
private ListeningExecutorService executor;
@Before
public void setUp() throws IOException, InterruptedException {
frontendService = EasyMock.createStrictMock(FrontendService.class);
executor = MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor());
distBuildService = new DistBuildService(frontendService);
}
@After
public void tearDown() {
executor.shutdown();
}
@Test
public void canUploadTargetGraph() throws IOException, ExecutionException, InterruptedException {
Capture<FrontendRequest> request = EasyMock.newCapture();
FrontendResponse response = new FrontendResponse();
response.setType(FrontendRequestType.STORE_BUILD_GRAPH);
response.setWasSuccessful(true);
EasyMock.expect(frontendService.makeRequest(EasyMock.capture(request)))
.andReturn(response)
.once();
EasyMock.replay(frontendService);
BuildJobState buildJobState = new BuildJobState();
List<BuildJobStateFileHashes> fileHashes = new ArrayList<>();
buildJobState.setFileHashes(fileHashes);
BuildJobStateTargetGraph graph = new BuildJobStateTargetGraph();
graph.setNodes(new ArrayList<BuildJobStateTargetNode>());
BuildJobStateTargetNode node1 = new BuildJobStateTargetNode();
node1.setRawNode("node1");
BuildJobStateTargetNode node2 = new BuildJobStateTargetNode();
node2.setRawNode("node1");
graph.addToNodes(node1);
graph.addToNodes(node2);
buildJobState.setTargetGraph(graph);
StampedeId stampedeId = new StampedeId();
stampedeId.setId("check-id");
distBuildService.uploadTargetGraph(buildJobState, stampedeId);
Assert.assertTrue(request.getValue().isSetType());
Assert.assertEquals(request.getValue().getType(), FrontendRequestType.STORE_BUILD_GRAPH);
Assert.assertTrue(request.getValue().isSetStoreBuildGraphRequest());
Assert.assertTrue(request.getValue().getStoreBuildGraphRequest().isSetStampedeId());
Assert.assertEquals(request.getValue().getStoreBuildGraphRequest().getStampedeId(), stampedeId);
Assert.assertTrue(request.getValue().getStoreBuildGraphRequest().isSetBuildGraph());
BuildJobState sentState =
BuildJobStateSerializer.deserialize(
request.getValue().getStoreBuildGraphRequest().getBuildGraph());
Assert.assertTrue(buildJobState.equals(sentState));
}
@Test
public void canUploadFiles() throws Exception {
final List<Boolean> fileExistence = Arrays.asList(true, false, true);
Capture<FrontendRequest> containsRequest = EasyMock.newCapture();
FrontendResponse containsResponse = new FrontendResponse();
containsResponse.setType(FrontendRequestType.CAS_CONTAINS);
CASContainsResponse casContainsResponse = new CASContainsResponse();
casContainsResponse.setExists(fileExistence);
containsResponse.setCasContainsResponse(casContainsResponse);
containsResponse.setWasSuccessful(true);
EasyMock.expect(frontendService.makeRequest(EasyMock.capture(containsRequest)))
.andReturn(containsResponse)
.once();
Capture<FrontendRequest> storeRequest = EasyMock.newCapture();
FrontendResponse storeResponse = new FrontendResponse();
storeResponse.setType(FrontendRequestType.STORE_LOCAL_CHANGES);
storeResponse.setWasSuccessful(true);
EasyMock.expect(frontendService.makeRequest(EasyMock.capture(storeRequest)))
.andReturn(storeResponse)
.once();
EasyMock.replay(frontendService);
BuildJobStateFileHashEntry files[] = new BuildJobStateFileHashEntry[3];
for (int i = 0; i < 3; i++) {
files[i] = new BuildJobStateFileHashEntry();
files[i].setHashCode(Integer.toString(i));
files[i].setContents(("content" + Integer.toString(i)).getBytes());
files[i].setPath(new PathWithUnixSeparators().setPath("/tmp/" + i));
}
List<BuildJobStateFileHashes> fileHashes = new ArrayList<>();
fileHashes.add(new BuildJobStateFileHashes());
fileHashes.get(0).setCellIndex(0);
fileHashes.get(0).setEntries(new ArrayList<BuildJobStateFileHashEntry>());
fileHashes.get(0).getEntries().add(files[0]);
fileHashes.get(0).getEntries().add(files[1]);
fileHashes.add(new BuildJobStateFileHashes());
fileHashes.get(1).setCellIndex(1);
fileHashes.get(1).setEntries(new ArrayList<BuildJobStateFileHashEntry>());
fileHashes.get(1).getEntries().add(files[2]);
distBuildService.uploadMissingFilesAsync(fileHashes, executor).get();
Assert.assertEquals(containsRequest.getValue().getType(), FrontendRequestType.CAS_CONTAINS);
Assert.assertTrue(containsRequest.getValue().isSetCasContainsRequest());
Assert.assertTrue(containsRequest.getValue().getCasContainsRequest().isSetContentSha1s());
Assert.assertEquals(
new HashSet<String>(containsRequest.getValue().getCasContainsRequest().getContentSha1s()),
new HashSet<String>(Arrays.asList("0", "1", "2")));
Assert.assertEquals(storeRequest.getValue().getType(), FrontendRequestType.STORE_LOCAL_CHANGES);
Assert.assertTrue(storeRequest.getValue().isSetStoreLocalChangesRequest());
Assert.assertTrue(storeRequest.getValue().getStoreLocalChangesRequest().isSetFiles());
Assert.assertEquals(storeRequest.getValue().getStoreLocalChangesRequest().getFiles().size(), 1);
Assert.assertEquals(
storeRequest.getValue().getStoreLocalChangesRequest().getFiles().get(0).getContentHash(),
"1");
Assert.assertTrue(
Arrays.equals(
storeRequest.getValue().getStoreLocalChangesRequest().getFiles().get(0).getContent(),
"content1".getBytes()));
}
@Test
public void canCreateBuild() throws Exception {
final String idString = "create id";
Capture<FrontendRequest> request = EasyMock.newCapture();
FrontendResponse response = new FrontendResponse();
response.setType(FrontendRequestType.CREATE_BUILD);
CreateBuildResponse createBuildResponse = new CreateBuildResponse();
BuildJob buildJob = new BuildJob();
StampedeId stampedeId = new StampedeId();
stampedeId.setId(idString);
buildJob.setStampedeId(stampedeId);
createBuildResponse.setBuildJob(buildJob);
response.setCreateBuildResponse(createBuildResponse);
response.setWasSuccessful(true);
EasyMock.expect(frontendService.makeRequest(EasyMock.capture(request)))
.andReturn(response)
.once();
EasyMock.replay(frontendService);
BuildJob job = distBuildService.createBuild(BuildMode.REMOTE_BUILD, 1);
Assert.assertEquals(request.getValue().getType(), FrontendRequestType.CREATE_BUILD);
Assert.assertTrue(request.getValue().isSetCreateBuildRequest());
Assert.assertTrue(request.getValue().getCreateBuildRequest().isSetCreateTimestampMillis());
Assert.assertTrue(job.isSetStampedeId());
Assert.assertTrue(job.getStampedeId().isSetId());
Assert.assertEquals(job.getStampedeId().getId(), idString);
}
@Test
public void canStartBuild() throws Exception {
final String idString = "start id";
Capture<FrontendRequest> request = EasyMock.newCapture();
FrontendResponse response = new FrontendResponse();
response.setType(FrontendRequestType.START_BUILD);
StartBuildResponse startBuildResponse = new StartBuildResponse();
BuildJob buildJob = new BuildJob();
StampedeId stampedeId = new StampedeId();
stampedeId.setId(idString);
buildJob.setStampedeId(stampedeId);
startBuildResponse.setBuildJob(buildJob);
response.setStartBuildResponse(startBuildResponse);
response.setWasSuccessful(true);
EasyMock.expect(frontendService.makeRequest(EasyMock.capture(request)))
.andReturn(response)
.once();
EasyMock.replay(frontendService);
StampedeId id = new StampedeId();
id.setId(idString);
BuildJob job = distBuildService.startBuild(id);
Assert.assertEquals(request.getValue().getType(), FrontendRequestType.START_BUILD);
Assert.assertTrue(request.getValue().isSetStartBuildRequest());
Assert.assertTrue(request.getValue().getStartBuildRequest().isSetStampedeId());
Assert.assertEquals(request.getValue().getStartBuildRequest().getStampedeId(), id);
Assert.assertTrue(job.isSetStampedeId());
Assert.assertEquals(job.getStampedeId(), id);
}
@Test
public void canPollBuild() throws Exception {
final String idString = "poll id";
Capture<FrontendRequest> request = EasyMock.newCapture();
FrontendResponse response = new FrontendResponse();
response.setType(FrontendRequestType.BUILD_STATUS);
BuildStatusResponse buildStatusResponse = new BuildStatusResponse();
BuildJob buildJob = new BuildJob();
StampedeId stampedeId = new StampedeId();
stampedeId.setId(idString);
buildJob.setStampedeId(stampedeId);
buildStatusResponse.setBuildJob(buildJob);
response.setBuildStatusResponse(buildStatusResponse);
response.setWasSuccessful(true);
EasyMock.expect(frontendService.makeRequest(EasyMock.capture(request)))
.andReturn(response)
.once();
EasyMock.replay(frontendService);
StampedeId id = new StampedeId();
id.setId(idString);
BuildJob job = distBuildService.getCurrentBuildJobState(id);
Assert.assertEquals(request.getValue().getType(), FrontendRequestType.BUILD_STATUS);
Assert.assertTrue(request.getValue().isSetBuildStatusRequest());
Assert.assertTrue(request.getValue().getBuildStatusRequest().isSetStampedeId());
Assert.assertEquals(request.getValue().getBuildStatusRequest().getStampedeId(), id);
Assert.assertTrue(job.isSetStampedeId());
Assert.assertEquals(job.getStampedeId(), id);
}
@Test
public void canTransmitConsoleEvents() throws IOException {
// Check that uploadBuildSlaveConsoleEvents sends a valid thing,
// and then use that capture to reply to a multiGetBuildSlaveEvents request (using `andAnswer`),
// and finally verify that the events are the same.
StampedeId stampedeId = new StampedeId();
stampedeId.setId("super");
RunId runId = new RunId();
runId.setId("duper");
ImmutableList<BuildSlaveConsoleEvent> consoleEvents =
ImmutableList.of(
new BuildSlaveConsoleEvent(),
new BuildSlaveConsoleEvent(),
new BuildSlaveConsoleEvent());
consoleEvents.get(0).setMessage("a");
consoleEvents.get(1).setMessage("b");
consoleEvents.get(2).setMessage("c");
Capture<FrontendRequest> request1 = EasyMock.newCapture();
Capture<FrontendRequest> request2 = EasyMock.newCapture();
FrontendResponse response = new FrontendResponse();
response.setWasSuccessful(true);
response.setType(FrontendRequestType.APPEND_BUILD_SLAVE_EVENTS);
response.setAppendBuildSlaveEventsResponse(new AppendBuildSlaveEventsResponse());
EasyMock.expect(frontendService.makeRequest(EasyMock.capture(request1)))
.andReturn(response)
.once();
EasyMock.expect(frontendService.makeRequest(EasyMock.capture(request2)))
.andAnswer(
() -> {
List<ByteBuffer> receivedBinaryEvents =
request1.getValue().getAppendBuildSlaveEventsRequest().getEvents();
List<SequencedBuildSlaveEvent> sequencedEvents =
IntStream.range(0, 3)
.mapToObj(
i -> {
SequencedBuildSlaveEvent slaveEvent = new SequencedBuildSlaveEvent();
slaveEvent.setEventNumber(i);
slaveEvent.setEvent(receivedBinaryEvents.get(i));
return slaveEvent;
})
.collect(Collectors.toList());
BuildSlaveEventsQuery query =
distBuildService.createBuildSlaveEventsQuery(stampedeId, runId, 2);
BuildSlaveEventsRange eventsRange = new BuildSlaveEventsRange();
eventsRange.setSuccess(true);
eventsRange.setQuery(query);
eventsRange.setEvents(sequencedEvents);
MultiGetBuildSlaveEventsResponse eventsResponse =
new MultiGetBuildSlaveEventsResponse();
eventsResponse.addToResponses(eventsRange);
FrontendResponse response2 = new FrontendResponse();
response2.setWasSuccessful(true);
response2.setType(FrontendRequestType.MULTI_GET_BUILD_SLAVE_EVENTS);
response2.setMultiGetBuildSlaveEventsResponse(eventsResponse);
return response2;
})
.once();
EasyMock.replay(frontendService);
distBuildService.uploadBuildSlaveConsoleEvents(stampedeId, runId, consoleEvents);
BuildSlaveEventsQuery query =
distBuildService.createBuildSlaveEventsQuery(stampedeId, runId, 2);
List<Pair<Integer, BuildSlaveEvent>> events =
distBuildService.multiGetBuildSlaveEvents(ImmutableList.of(query));
// Verify correct events are received.
Assert.assertEquals(events.size(), 3);
Assert.assertEquals((int) events.get(0).getFirst(), 0);
Assert.assertEquals((int) events.get(1).getFirst(), 1);
Assert.assertEquals((int) events.get(2).getFirst(), 2);
List<BuildSlaveEvent> buildSlaveEvents =
events.stream().map(x -> x.getSecond()).collect(Collectors.toList());
for (int i = 0; i < 3; ++i) {
BuildSlaveEvent event = buildSlaveEvents.get(i);
Assert.assertEquals(event.getEventType(), BuildSlaveEventType.CONSOLE_EVENT);
Assert.assertEquals(event.getStampedeId(), stampedeId);
Assert.assertEquals(event.getRunId(), runId);
Assert.assertEquals(event.getConsoleEvent(), consoleEvents.get(i));
}
// Verify validity of first request.
FrontendRequest receivedRequest = request1.getValue();
Assert.assertTrue(receivedRequest.isSetType());
Assert.assertEquals(receivedRequest.getType(), FrontendRequestType.APPEND_BUILD_SLAVE_EVENTS);
Assert.assertTrue(receivedRequest.isSetAppendBuildSlaveEventsRequest());
AppendBuildSlaveEventsRequest appendRequest =
receivedRequest.getAppendBuildSlaveEventsRequest();
Assert.assertTrue(appendRequest.isSetStampedeId());
Assert.assertEquals(appendRequest.getStampedeId(), stampedeId);
Assert.assertTrue(appendRequest.isSetRunId());
Assert.assertEquals(appendRequest.getRunId(), runId);
Assert.assertTrue(appendRequest.isSetEvents());
Assert.assertEquals(appendRequest.getEvents().size(), 3);
// Verify validity of second request.
receivedRequest = request2.getValue();
Assert.assertTrue(receivedRequest.isSetType());
Assert.assertEquals(
receivedRequest.getType(), FrontendRequestType.MULTI_GET_BUILD_SLAVE_EVENTS);
Assert.assertTrue(receivedRequest.isSetMultiGetBuildSlaveEventsRequest());
MultiGetBuildSlaveEventsRequest eventsRequest =
receivedRequest.getMultiGetBuildSlaveEventsRequest();
Assert.assertTrue(eventsRequest.isSetRequests());
Assert.assertEquals(eventsRequest.getRequests().size(), 1);
BuildSlaveEventsQuery receivedQuery = eventsRequest.getRequests().get(0);
Assert.assertTrue(receivedQuery.isSetStampedeId());
Assert.assertEquals(receivedQuery.getStampedeId(), stampedeId);
Assert.assertTrue(receivedQuery.isSetRunId());
Assert.assertEquals(receivedQuery.getRunId(), runId);
Assert.assertTrue(receivedQuery.isSetFirstEventNumber());
Assert.assertEquals(receivedQuery.getFirstEventNumber(), 2);
}
@Test
public void canTransmitSlaveStatus() throws IOException {
// Check that updateBuildSlaveStatus sends a valid thing,
// and then use that capture to reply to a fetchBuildSlaveStatusRequest request,
// and finally verify that the statuses is the same.
StampedeId stampedeId = new StampedeId();
stampedeId.setId("super");
RunId runId = new RunId();
runId.setId("duper");
BuildSlaveStatus slaveStatus = new BuildSlaveStatus();
slaveStatus.setStampedeId(stampedeId);
slaveStatus.setRunId(runId);
slaveStatus.setTotalRulesCount(123);
Capture<FrontendRequest> request1 = EasyMock.newCapture();
Capture<FrontendRequest> request2 = EasyMock.newCapture();
FrontendResponse response = new FrontendResponse();
response.setWasSuccessful(true);
response.setType(FrontendRequestType.UPDATE_BUILD_SLAVE_STATUS);
response.setUpdateBuildSlaveStatusResponse(new UpdateBuildSlaveStatusResponse());
EasyMock.expect(frontendService.makeRequest(EasyMock.capture(request1)))
.andReturn(response)
.once();
EasyMock.expect(frontendService.makeRequest(EasyMock.capture(request2)))
.andAnswer(
() -> {
FetchBuildSlaveStatusResponse statusResponse = new FetchBuildSlaveStatusResponse();
statusResponse.setBuildSlaveStatus(
request1.getValue().getUpdateBuildSlaveStatusRequest().getBuildSlaveStatus());
FrontendResponse response2 = new FrontendResponse();
response2.setWasSuccessful(true);
response2.setType(FrontendRequestType.FETCH_BUILD_SLAVE_STATUS);
response2.setFetchBuildSlaveStatusResponse(statusResponse);
return response2;
})
.once();
EasyMock.replay(frontendService);
distBuildService.updateBuildSlaveStatus(stampedeId, runId, slaveStatus);
BuildSlaveStatus receivedStatus =
distBuildService.fetchBuildSlaveStatus(stampedeId, runId).get();
Assert.assertEquals(receivedStatus, slaveStatus);
// Verify validity of first request.
FrontendRequest receivedRequest = request1.getValue();
Assert.assertTrue(receivedRequest.isSetType());
Assert.assertEquals(receivedRequest.getType(), FrontendRequestType.UPDATE_BUILD_SLAVE_STATUS);
Assert.assertTrue(receivedRequest.isSetUpdateBuildSlaveStatusRequest());
UpdateBuildSlaveStatusRequest updateRequest =
receivedRequest.getUpdateBuildSlaveStatusRequest();
Assert.assertTrue(updateRequest.isSetStampedeId());
Assert.assertEquals(updateRequest.getStampedeId(), stampedeId);
Assert.assertTrue(updateRequest.isSetRunId());
Assert.assertEquals(updateRequest.getRunId(), runId);
Assert.assertTrue(updateRequest.isSetBuildSlaveStatus());
// Verify validity of second request.
receivedRequest = request2.getValue();
Assert.assertTrue(receivedRequest.isSetType());
Assert.assertEquals(receivedRequest.getType(), FrontendRequestType.FETCH_BUILD_SLAVE_STATUS);
Assert.assertTrue(receivedRequest.isSetFetchBuildSlaveStatusRequest());
FetchBuildSlaveStatusRequest statusRequest = receivedRequest.getFetchBuildSlaveStatusRequest();
Assert.assertTrue(statusRequest.isSetStampedeId());
Assert.assertEquals(statusRequest.getStampedeId(), stampedeId);
Assert.assertTrue(statusRequest.isSetRunId());
Assert.assertEquals(statusRequest.getRunId(), runId);
}
@Test
public void testRequestContainsStampedeId() {
StampedeId stampedeId = createStampedeId("topspin");
FrontendRequest request = DistBuildService.createFrontendBuildStatusRequest(stampedeId);
Assert.assertEquals(stampedeId, request.getBuildStatusRequest().getStampedeId());
}
private static StampedeId createStampedeId(String id) {
StampedeId stampedeId = new StampedeId();
stampedeId.setId(id);
return stampedeId;
}
}