![JAR search and dependency download from the Maven repository](/logo.png)
com.hubspot.blazar.data.service.RepositoryBuildService Maven / Gradle / Ivy
package com.hubspot.blazar.data.service;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.transaction.Transactional;
import com.google.common.primitives.Ints;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.eventbus.EventBus;
import com.hubspot.blazar.base.BuildOptions;
import com.hubspot.blazar.base.BuildTrigger;
import com.hubspot.blazar.base.GitInfo;
import com.hubspot.blazar.base.RepositoryBuild;
import com.hubspot.blazar.base.RepositoryBuild.State;
import com.hubspot.blazar.data.dao.BranchDao;
import com.hubspot.blazar.data.dao.RepositoryBuildDao;
import com.hubspot.blazar.data.util.BuildNumbers;
@Singleton
public class RepositoryBuildService {
private static final Logger LOG = LoggerFactory.getLogger(RepositoryBuildService.class);
private final RepositoryBuildDao repositoryBuildDao;
private final BranchDao branchDao;
private final EventBus eventBus;
@Inject
public RepositoryBuildService(RepositoryBuildDao repositoryBuildDao, BranchDao branchDao, EventBus eventBus) {
this.repositoryBuildDao = repositoryBuildDao;
this.branchDao = branchDao;
this.eventBus = eventBus;
}
public Optional get(long id) {
return repositoryBuildDao.get(id);
}
public List getByBranch(int branchId) {
return repositoryBuildDao.getByBranch(branchId);
}
public Optional getByBranchAndNumber(int branchId, int buildNumber) {
return repositoryBuildDao.getByBranchAndNumber(branchId, buildNumber);
}
public BuildNumbers getBuildNumbers(int branchId) {
return repositoryBuildDao.getBuildNumbers(branchId);
}
public Optional getPreviousBuild(RepositoryBuild build) {
return repositoryBuildDao.getPreviousBuild(build);
}
public long enqueue(GitInfo gitInfo, BuildTrigger trigger, BuildOptions buildOptions) {
int branchId = gitInfo.getId().get();
// determine build number first (unique index will prevent concurrent modification)
List queuedBuilds = repositoryBuildDao.getByBranchAndState(branchId, State.QUEUED);
int nextBuildNumber = determineNextBuildNumber(branchId, queuedBuilds);
// check for existing queued build triggered by push event
if (trigger.getType() == BuildTrigger.Type.PUSH) {
for (RepositoryBuild build : queuedBuilds) {
if (build.getBuildTrigger().getType() == BuildTrigger.Type.PUSH) {
long existingBuildId = build.getId().get();
LOG.info("Not enqueuing build for push to {}, pending build {} already exists", branchId, existingBuildId);
return existingBuildId;
}
}
}
RepositoryBuild build = RepositoryBuild.queuedBuild(gitInfo, trigger, nextBuildNumber, buildOptions);
LOG.info("Enqueuing build for repository {} with build number {}", branchId, nextBuildNumber);
// if no queued builds, we expect our update to succeed otherwise it shouldn't
int expectedUpdateCount = queuedBuilds.isEmpty() ? 1 : 0;
build = enqueue(build, expectedUpdateCount);
LOG.info("Enqueued build for repository {} with id {}", branchId, build.getId().get());
return build.getId().get();
}
@Transactional
protected RepositoryBuild enqueue(RepositoryBuild build, int expectedUpdateCount) {
long id = repositoryBuildDao.enqueue(build);
build = build.withId(id);
checkAffectedRowCount(branchDao.updatePendingBuild(build), expectedUpdateCount);
eventBus.post(build);
return build;
}
@Transactional
public void begin(RepositoryBuild build) {
beginNoPublish(build);
eventBus.post(build);
}
@Transactional
void beginNoPublish(RepositoryBuild build) {
Preconditions.checkArgument(build.getStartTimestamp().isPresent());
checkAffectedRowCount(repositoryBuildDao.begin(build));
// need to move the next queued build into the pending slot
Optional nextQueuedBuild = nextQueuedBuild(build.getBranchId());
if (nextQueuedBuild.isPresent()) {
checkAffectedRowCount(branchDao.updateInProgressBuild(build, nextQueuedBuild.get()));
eventBus.post(nextQueuedBuild.get());
} else {
checkAffectedRowCount(branchDao.updateInProgressBuild(build));
}
}
@Transactional
public void update(RepositoryBuild build) {
if (build.getState().isComplete()) {
Preconditions.checkArgument(build.getEndTimestamp().isPresent());
checkAffectedRowCount(repositoryBuildDao.complete(build));
checkAffectedRowCount(branchDao.updateLastBuild(build));
} else {
checkAffectedRowCount(repositoryBuildDao.update(build));
}
eventBus.post(build);
}
@Transactional
public void fail(RepositoryBuild build) {
if (build.getState().isComplete()) {
throw new IllegalStateException(String.format("Build %d has already completed", build.getId().get()));
}
if (build.getState() == State.QUEUED) {
beginNoPublish(build.withState(State.LAUNCHING).withStartTimestamp(System.currentTimeMillis()));
}
update(build.withState(State.FAILED).withEndTimestamp(System.currentTimeMillis()));
}
public void cancel(RepositoryBuild build) {
if (build.getState().isComplete()) {
throw new IllegalStateException(String.format("Build %d has already completed", build.getId().get()));
}
if (build.getState() == State.QUEUED) {
deleteQueuedBuild(build);
} else {
update(build.withState(State.CANCELLED).withEndTimestamp(System.currentTimeMillis()));
}
}
@Transactional
void deleteQueuedBuild(RepositoryBuild build) {
// call this method before deleting the row
boolean isThePendingBuild = isThePendingBuild(build);
checkAffectedRowCount(repositoryBuildDao.delete(build));
if (isThePendingBuild) {
// need to move the next queued build into the pending slot
Optional nextQueuedBuild = nextQueuedBuild(build.getBranchId());
if (nextQueuedBuild.isPresent()) {
checkAffectedRowCount(branchDao.updatePendingBuild(build, nextQueuedBuild.get()));
eventBus.post(nextQueuedBuild.get());
} else {
checkAffectedRowCount(branchDao.deletePendingBuild(build));
}
} else {
// this shouldn't update any rows
checkAffectedRowCount(branchDao.deletePendingBuild(build), 0);
}
}
private boolean isThePendingBuild(RepositoryBuild build) {
BuildNumbers buildNumbers = getBuildNumbers(build.getBranchId());
return buildNumbers.getPendingBuildId().equals(build.getId());
}
private Optional nextQueuedBuild(int branchId) {
List queuedBuilds = repositoryBuildDao.getByBranchAndState(branchId, State.QUEUED);
if (queuedBuilds.isEmpty()) {
return Optional.absent();
} else {
return Optional.of(Collections.min(queuedBuilds, new Comparator() {
@Override
public int compare(RepositoryBuild build1, RepositoryBuild build2) {
return Ints.compare(build1.getBuildNumber(), build2.getBuildNumber());
}
}));
}
}
private int determineNextBuildNumber(int branchId, List queuedBuilds) {
if (queuedBuilds.isEmpty()) {
BuildNumbers buildNumbers = getBuildNumbers(branchId);
if (buildNumbers.getInProgressBuildNumber().isPresent()) {
return buildNumbers.getInProgressBuildNumber().get() + 1;
} else if (buildNumbers.getLastBuildNumber().isPresent()) {
return buildNumbers.getLastBuildNumber().get() + 1;
} else {
return 1;
}
} else {
int max = 0;
for (RepositoryBuild build : queuedBuilds) {
max = Math.max(max, build.getBuildNumber());
}
return max + 1;
}
}
private static void checkAffectedRowCount(int affectedRows) {
checkAffectedRowCount(affectedRows, 1);
}
private static void checkAffectedRowCount(int affectedRows, int expectedAffectedRows) {
Preconditions.checkState(affectedRows == expectedAffectedRows, "Expected to update %s rows but updated %s", expectedAffectedRows, affectedRows);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy