/*************************GO-LICENSE-START*********************************
* Copyright 2014 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.
*************************GO-LICENSE-END***********************************/
package com.thoughtworks.go.server.service;
import com.thoughtworks.go.config.CaseInsensitiveString;
import com.thoughtworks.go.config.CruiseConfig;
import com.thoughtworks.go.config.materials.MaterialConfigs;
import com.thoughtworks.go.domain.JobInstance;
import com.thoughtworks.go.domain.MaterialRevision;
import com.thoughtworks.go.domain.MaterialRevisions;
import com.thoughtworks.go.domain.Pipeline;
import com.thoughtworks.go.domain.materials.Material;
import com.thoughtworks.go.domain.materials.MaterialConfig;
import com.thoughtworks.go.server.dao.PipelineSqlMapDao;
import com.thoughtworks.go.server.materials.StaleMaterialsOnBuildCause;
import com.thoughtworks.go.server.transaction.TransactionSynchronizationManager;
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.SystemEnvironment;
import com.thoughtworks.go.utils.Timeout;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.support.TransactionSynchronizationAdapter;
/**
* @understands loads scheduled pipeline for creating work
*/
@Component
public class ScheduledPipelineLoader {
final private PipelineSqlMapDao pipelineDao;
final private GoConfigService goConfigService;
final private JobInstanceService jobInstanceService;
final private ServerHealthService serverHealthService;
final private TransactionSynchronizationManager transactionSynchronizationManager;
private final ScheduleService scheduleService;
private MaterialExpansionService materialExpansionService;
private ConsoleService consoleService;
@Autowired
public ScheduledPipelineLoader(TransactionSynchronizationManager transactionSynchronizationManager, PipelineSqlMapDao pipelineDao, GoConfigService goConfigService,
JobInstanceService jobInstanceService, ServerHealthService serverHealthService, ScheduleService scheduleService,
MaterialExpansionService materialExpansionService, ConsoleService consoleService) {
this.transactionSynchronizationManager = transactionSynchronizationManager;
this.pipelineDao = pipelineDao;
this.goConfigService = goConfigService;
this.jobInstanceService = jobInstanceService;
this.serverHealthService = serverHealthService;
this.scheduleService = scheduleService;
this.materialExpansionService = materialExpansionService;
this.consoleService = consoleService;
}
//TODO: Do we need to do this differently than PipelineService#fullPipeline?
public Pipeline pipelineWithPasswordAwareBuildCauseByBuildId(final long buildId) {
Pipeline pipeline = pipelineDao.pipelineWithMaterialsAndModsByBuildId(buildId);
MaterialRevisions scheduledRevs = pipeline.getBuildCause().getMaterialRevisions();
MaterialConfigs knownMaterials = knownMaterials(pipeline, scheduledRevs);
for (MaterialRevision materialRevision : scheduledRevs) {
MaterialConfig materialConfig = materialFrom(knownMaterials, materialRevision);
Material usedMaterial = materialRevision.getMaterial();
if (materialConfig == null) {
final JobInstance jobInstance = jobInstanceService.buildByIdWithTransitions(buildId);
scheduleService.failJob(jobInstance);
final String message = "Cannot load job '" + jobInstance.buildLocator() + "' because material " + usedMaterial.config() + " was not found in config.";
final String description = "Job for pipeline '" + jobInstance.buildLocator() + "' has been failed as one or more material configurations were either changed or removed.";
transactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
@Override public void afterCommit() {
final ServerHealthState error = ServerHealthState.error(message, description, HealthStateType.general(HealthStateScope.forJob(jobInstance.getPipelineName(), jobInstance.getStageName(), jobInstance.getName())));
error.setTimeout(Timeout.FIVE_MINUTES);
serverHealthService.update(error);
appendToConsoleLog(jobInstance, message);
appendToConsoleLog(jobInstance, description);
}
});
throw new StaleMaterialsOnBuildCause(message);
}
usedMaterial.updateFromConfig(materialConfig);
}
return pipeline;
}
private MaterialConfig materialFrom(MaterialConfigs knownMaterials, MaterialRevision materialRevision) {
for (MaterialConfig knownMaterial : knownMaterials) {
if (knownMaterial.getFingerprint().equals(materialRevision.getMaterial().getFingerprint())) {
return knownMaterial;
}
}
return null;
}
private MaterialConfigs knownMaterials(Pipeline pipeline, MaterialRevisions scheduledRevs) {
CruiseConfig currentConfig = goConfigService.getCurrentConfig();
MaterialConfigs configuredMaterials = new MaterialConfigs();
for (MaterialRevision revision : scheduledRevs) {
String fingerprint = revision.getMaterial().getFingerprint();
// first try to find material config from current pipeline config
MaterialConfig configuredMaterial = currentConfig.materialConfigFor(new CaseInsensitiveString(pipeline.getName()), fingerprint);
if (configuredMaterial != null) {
configuredMaterials.add(configuredMaterial);
continue;
}
//todo: remove the global lookup fallback code after we feel safe
if(new SystemEnvironment().get(SystemEnvironment.GO_SERVER_SCHEDULED_PIPELINE_LOADER_GLOBAL_MATERIAL_LOOKUP)) {
// fallback to global lookup if material is not in current pipeline config (old behavior)
configuredMaterial = currentConfig.materialConfigFor(fingerprint);
if (configuredMaterial != null) {
configuredMaterials.add((configuredMaterial));
}
}
}
MaterialConfigs knownMaterials = new MaterialConfigs();
for (MaterialConfig configuredMaterial : configuredMaterials) {
materialExpansionService.expandForScheduling(configuredMaterial, knownMaterials);
}
return knownMaterials;
}
private void appendToConsoleLog(JobInstance jobInstance, String message) {
try {
consoleService.appendToConsoleLog(jobInstance.getIdentifier(), "\n" + message + "\n");
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}