/*
* Copyright 2016 ThoughtWorks, 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.thoughtworks.go.server.service;
import com.thoughtworks.go.config.CaseInsensitiveString;
import com.thoughtworks.go.config.CruiseConfig;
import com.thoughtworks.go.config.PipelineConfig;
import com.thoughtworks.go.domain.MaterialRevision;
import com.thoughtworks.go.domain.MaterialRevisions;
import com.thoughtworks.go.domain.buildcause.BuildCause;
import com.thoughtworks.go.domain.materials.MaterialConfig;
import com.thoughtworks.go.server.domain.PipelineConfigDependencyGraph;
import com.thoughtworks.go.server.materials.MaterialChecker;
import com.thoughtworks.go.server.service.result.OperationResult;
import com.thoughtworks.go.serverhealth.HealthStateScope;
import com.thoughtworks.go.serverhealth.HealthStateType;
import com.thoughtworks.go.serverhealth.ServerHealthService;
import com.thoughtworks.go.serverhealth.ServerHealthState;
import com.thoughtworks.go.util.GoConstants;
import com.thoughtworks.go.util.SystemEnvironment;
import org.apache.log4j.Logger;
/**
* @understands a build that was triggered by a change in some external materials
*/
public class AutoBuild implements BuildType {
private final GoConfigService goConfigService;
private final PipelineService pipelineService;
private final String pipelineName;
private final SystemEnvironment systemEnvironment;
private final MaterialChecker materialChecker;
private final ServerHealthService serverHealthService;
private static final Logger LOGGER = Logger.getLogger(AutoBuild.class);
public AutoBuild(GoConfigService goConfigService, PipelineService pipelineService, String pipelineName, SystemEnvironment systemEnvironment, MaterialChecker materialChecker,
ServerHealthService serverHealthService) {
this.goConfigService = goConfigService;
this.pipelineService = pipelineService;
this.pipelineName = pipelineName;
this.systemEnvironment = systemEnvironment;
this.materialChecker = materialChecker;
this.serverHealthService = serverHealthService;
}
public BuildCause onModifications(MaterialRevisions originalMaterialRevisions, boolean materialConfigurationChanged, MaterialRevisions previousMaterialRevisions) {
if (originalMaterialRevisions == null || originalMaterialRevisions.isEmpty()) {
throw new RuntimeException("Cannot find modifications, please check your SCM setting or environment.");
}
if (!originalMaterialRevisions.hasDependencyMaterials()) {
return BuildCause.createWithModifications(originalMaterialRevisions, GoConstants.DEFAULT_APPROVED_BY);
}
CruiseConfig cruiseConfig = goConfigService.currentCruiseConfig();
MaterialRevisions recomputedBasedOnDependencies;
if (systemEnvironment.enforceRevisionCompatibilityWithUpstream() && systemEnvironment.enforceFanInFallbackBehaviour()) {
recomputedBasedOnDependencies = fanInOnWithFallback(originalMaterialRevisions, cruiseConfig, new CaseInsensitiveString(pipelineName));
} else if (systemEnvironment.enforceRevisionCompatibilityWithUpstream()) {
recomputedBasedOnDependencies = fanInOn(originalMaterialRevisions, cruiseConfig, new CaseInsensitiveString(pipelineName));
} else {
recomputedBasedOnDependencies = fanInOffTriangleDependency(originalMaterialRevisions, cruiseConfig);
}
if (recomputedBasedOnDependencies != null && canRunWithRecomputedRevisions(materialConfigurationChanged, previousMaterialRevisions, recomputedBasedOnDependencies)) {
return BuildCause.createWithModifications(recomputedBasedOnDependencies, GoConstants.DEFAULT_APPROVED_BY);
}
return null;
}
public BuildCause onEmptyModifications(PipelineConfig pipelineConfig, MaterialRevisions materialRevisions) {
return null;
}
public void canProduce(PipelineConfig pipelineConfig, SchedulingCheckerService schedulingChecker, ServerHealthService serverHealthService, OperationResult operationResult) {
schedulingChecker.canAutoTriggerProducer(pipelineConfig, operationResult);
}
public boolean isValidBuildCause(PipelineConfig pipelineConfig, BuildCause buildCause) {
for (MaterialRevision materialRevision : buildCause.getMaterialRevisions()) {
if (materialRevision.isChanged()) {
return true;
}
}
return false;
}
@Override
public boolean shouldCheckWhetherOlderRunsHaveRunWithLatestMaterials() {
return true;
}
@Override
public void notifyPipelineNotScheduled(PipelineConfig pipelineConfig) {
}
private MaterialRevisions fanInOnWithFallback(MaterialRevisions originalMaterialRevisions, CruiseConfig cruiseConfig, CaseInsensitiveString targetPipelineName) {
try {
MaterialRevisions materialRevisions = fanInOn(originalMaterialRevisions, cruiseConfig, targetPipelineName);
serverHealthService.removeByScope(HealthStateScope.forFanin(pipelineName));
return materialRevisions;
} catch (NoCompatibleUpstreamRevisionsException | NoModificationsPresentForDependentMaterialException e) {
throw e;
} catch (RuntimeException e) {
serverHealthService.update(ServerHealthState.warning("Turning off Fan-In for pipeline: '" + pipelineName + "'", "Error occurred during Fan-In resolution for the pipeline.",
HealthStateType.general(HealthStateScope.forFanin(pipelineName))));
LOGGER.info("[Revision Resolution] Error occurred during Fan-In resolution for the pipeline: '" + pipelineName + "'. Switching to Triangle Resolution");
return fanInOffTriangleDependency(originalMaterialRevisions, cruiseConfig);
}
}
private MaterialRevisions fanInOn(MaterialRevisions originalMaterialRevisions, CruiseConfig cruiseConfig, CaseInsensitiveString targetPipelineName) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("[Revision Resolution] Fan-In Resolution is active for pipeline " + pipelineName);
}
return pipelineService.getRevisionsBasedOnDependencies(originalMaterialRevisions, cruiseConfig, targetPipelineName);
}
private MaterialRevisions fanInOffTriangleDependency(MaterialRevisions originalMaterialRevisions, CruiseConfig cruiseConfig) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("[Revision Resolution] Triangle Resolution is active for pipeline " + pipelineName);
}
PipelineConfigDependencyGraph dependencyGraph = goConfigService.upstreamDependencyGraphOf(pipelineName, cruiseConfig);
if (hasAnyUnsharedMaterialChanged(dependencyGraph, originalMaterialRevisions) || dependencyGraph.isRevisionsOfSharedMaterialsIgnored(originalMaterialRevisions)) {
return pipelineService.getRevisionsBasedOnDependencies(dependencyGraph, originalMaterialRevisions);
}
return null;
}
private boolean canRunWithRecomputedRevisions(boolean materialConfigurationChanged, MaterialRevisions previousMaterialRevisions, MaterialRevisions recomputedBasedOnDependencies) {
return materialConfigurationChanged || previousMaterialRevisions == null ||
(recomputedBasedOnDependencies.hasChangedSince(previousMaterialRevisions)) && !materialChecker.hasPipelineEverRunWith(pipelineName, recomputedBasedOnDependencies);
}
/* TRIANGLE BEGIN */
boolean hasAnyUnsharedMaterialChanged(PipelineConfigDependencyGraph dependencyGraph, MaterialRevisions originalMaterialRevisions) {
for (MaterialConfig materialConfig : dependencyGraph.unsharedMaterialConfigs()) {
MaterialRevision revision = originalMaterialRevisions.findRevisionFor(materialConfig);
if (revision == null) {
String message = String.format("Couldn't find material-revision for material '%s' while auto-scheduling pipeline named '%s'", materialConfig, pipelineName);
RuntimeException exception = new NullPointerException(message);
LOGGER.error(message, exception);
throw exception;
}
if (revision.isChanged()) {
return true;
}
}
return false;
}
/* TRIANGLE END */
}