package com.hubspot.blazar.data.service; import java.util.ArrayList; import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.stream.Collectors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.base.Optional; import com.google.common.collect.ImmutableList; import com.google.inject.Inject; import com.hubspot.blazar.base.GitInfo; import com.hubspot.blazar.base.MalformedFile; import com.hubspot.blazar.base.ModuleBuild; import com.hubspot.blazar.base.ModuleBuildInfo; import com.hubspot.blazar.base.ModuleState; import com.hubspot.blazar.base.RepositoryBuild; import com.hubspot.blazar.base.branch.BranchStatus; import com.hubspot.blazar.data.dao.BranchDao; import com.hubspot.blazar.data.dao.MalformedFileDao; import com.hubspot.blazar.data.dao.RepositoryBuildDao; import com.hubspot.blazar.data.dao.StateDao; public class BranchStatusService { private static final Logger LOG = LoggerFactory.getLogger(BranchStatusService.class); private final BranchDao branchDao; private StateDao stateDao; private MalformedFileDao malformedFileDao; private final RepositoryBuildDao repositoryBuildDao; @Inject public BranchStatusService(BranchDao branchDao, StateDao stateDao, MalformedFileDao malformedFileDao, RepositoryBuildDao repositoryBuildDao) { this.branchDao = branchDao; this.stateDao = stateDao; this.malformedFileDao = malformedFileDao; this.repositoryBuildDao = repositoryBuildDao; } public Optional<BranchStatus> getBranchStatusById(int branchId) { Optional<GitInfo> maybeBranch = branchDao.get(branchId); if (!maybeBranch.isPresent()) { return Optional.absent(); } GitInfo branch = maybeBranch.get(); Set<ModuleState> moduleStates = getModuleStatesForBranch(branchId); Set<GitInfo> otherBranches = branchDao.getByRepository(branch.getRepositoryId()).stream().filter(GitInfo::isActive).collect(Collectors.toSet()); otherBranches.remove(branch); Set<RepositoryBuild> queuedBuilds = repositoryBuildDao.getRepositoryBuildsByState(branchId, ImmutableList.of(RepositoryBuild.State.QUEUED)); List<RepositoryBuild> queuedBuildsList = new ArrayList<>(queuedBuilds); queuedBuildsList.sort(Comparator.comparingInt(RepositoryBuild::getBuildNumber)); Set<MalformedFile> malformedFiles = malformedFileDao.getMalformedFiles(branchId); // We can only have up to 1 build in either of these states at a time Set<RepositoryBuild> launchingOrInProgressBuilds = repositoryBuildDao .getRepositoryBuildsByState(branchId, ImmutableList.of(RepositoryBuild.State.LAUNCHING, RepositoryBuild.State.IN_PROGRESS)); Optional<RepositoryBuild> maybeActiveBuild = launchingOrInProgressBuilds.isEmpty() ? Optional.absent() : Optional.of(launchingOrInProgressBuilds.iterator().next()); return Optional.of(new BranchStatus(queuedBuildsList, maybeActiveBuild, moduleStates, otherBranches, malformedFiles, branch)); } private Set<ModuleState> getModuleStatesForBranch(int branchId) { long start = System.currentTimeMillis(); // We retrieve the required info per state in two steps // In the first step we retrieve the module itself along with the build info about the 'last', 'in progress' // and 'pending' builds for the given branch as well as the included modules Set<ModuleState> partialStates = stateDao.getLastAndInProgressAndPendingBuildsForBranchAndIncludedModules(branchId); Set<ModuleState> completeStates = new HashSet<>(); // In the second state we enrich the module state with info about the 'last successful' and 'non-skipped' module builds for (ModuleState partialState : partialStates) { completeStates.add(completePartialModuleStateWithLastSuccessfulAndNonSkippedModuleBuilds(partialState)); } LOG.info("Built all states for branch {} in {}", branchId, System.currentTimeMillis() - start); return completeStates; } // remaining info contains: the most recent successful build and the most recent non-skipped build private ModuleState completePartialModuleStateWithLastSuccessfulAndNonSkippedModuleBuilds(ModuleState partialState) { Set<ModuleBuildInfo> lastSuccessfulAndNonSkippedModuleBuilds = stateDao.getLastSuccessfulAndNonSkippedModuleBuilds(partialState.getModule().getId().get()); Optional<ModuleBuildInfo> successfulModuleBuildInfo = Optional.absent(); Optional<ModuleBuildInfo> nonSkippedModuleBuildInfo = Optional.absent(); for (ModuleBuildInfo moduleBuildInfo : lastSuccessfulAndNonSkippedModuleBuilds) { if (moduleBuildInfo.getModuleBuild().getState().equals(ModuleBuild.State.SUCCEEDED)) { successfulModuleBuildInfo = Optional.of(moduleBuildInfo); } else { nonSkippedModuleBuildInfo = Optional.of(moduleBuildInfo); } } // If the most recent non-skipped build is not present that means it is the successful build if (!nonSkippedModuleBuildInfo.isPresent()) { nonSkippedModuleBuildInfo = successfulModuleBuildInfo; } return new ModuleState(partialState.getModule(), successfulModuleBuildInfo.isPresent() ? Optional.of(successfulModuleBuildInfo.get().getModuleBuild()) : Optional.absent(), successfulModuleBuildInfo.isPresent() ? Optional.of(successfulModuleBuildInfo.get().getBranchBuild()) : Optional.absent(), nonSkippedModuleBuildInfo.isPresent() ? Optional.of(nonSkippedModuleBuildInfo.get().getModuleBuild()) : Optional.absent(), nonSkippedModuleBuildInfo.isPresent() ? Optional.of(nonSkippedModuleBuildInfo.get().getBranchBuild()) : Optional.absent(), partialState.getLastModuleBuild(), partialState.getLastBranchBuild(), partialState.getInProgressModuleBuild(), partialState.getInProgressBranchBuild(), partialState.getPendingModuleBuild(), partialState.getPendingBranchBuild()); } }