/*************************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.dd; import com.thoughtworks.go.config.CaseInsensitiveString; import com.thoughtworks.go.config.materials.dependency.DependencyMaterialConfig; import com.thoughtworks.go.domain.PipelineTimelineEntry; import com.thoughtworks.go.domain.StageIdentifier; import com.thoughtworks.go.domain.materials.MaterialConfig; import com.thoughtworks.go.domain.materials.dependency.DependencyMaterialRevision; import com.thoughtworks.go.server.domain.PipelineTimeline; import com.thoughtworks.go.server.service.NoCompatibleUpstreamRevisionsException; import com.thoughtworks.go.util.Pair; import org.apache.commons.collections.CollectionUtils; import org.apache.log4j.Logger; import java.util.*; import java.util.concurrent.ConcurrentLinkedQueue; import static com.thoughtworks.go.server.service.dd.DependencyFanInNode.RevisionAlteration.*; public class DependencyFanInNode extends FanInNode { private static final Logger LOGGER = Logger.getLogger(DependencyFanInNode.class); private int totalInstanceCount = Integer.MAX_VALUE; private int maxBackTrackLimit = Integer.MAX_VALUE; private int currentCount; StageIdentifier currentRevision; private Map<StageIdentifier, Set<FaninScmMaterial>> stageIdentifierScmMaterial = new LinkedHashMap<>(); public Set<FanInNode> children = new HashSet<>(); public Set<? extends FaninScmMaterial> stageIdentifierScmMaterialForCurrentRevision() { return stageIdentifierScmMaterial.get(currentRevision); } enum RevisionAlteration { NOT_APPLICABLE, SAME_AS_CURRENT_REVISION, ALTERED_TO_CORRECT_REVISION, ALL_OPTIONS_EXHAUSTED, NEED_MORE_REVISIONS } DependencyFanInNode(MaterialConfig material) { super(material); } public void populateRevisions(CaseInsensitiveString pipelineName, FanInGraphContext context) { initialize(context); fillNextRevisions(context); if (initRevision(context) == ALL_OPTIONS_EXHAUSTED) { throw NoCompatibleUpstreamRevisionsException.noValidRevisionsForUpstream(pipelineName, materialConfig); } } private void setCurrentRevision() { currentRevision = stageIdentifierScmMaterial.keySet().toArray(new StageIdentifier[0])[0]; } private RevisionAlteration initRevision(FanInGraphContext context) { if (!stageIdentifierScmMaterial.isEmpty()) { setCurrentRevision(); } else { return handleNeedMoreRevisions(context); } return ALTERED_TO_CORRECT_REVISION; } private RevisionAlteration handleNeedMoreRevisions(FanInGraphContext context) { while (hasMoreInstances()) { fillNextRevisions(context); if (!stageIdentifierScmMaterial.isEmpty()) { setCurrentRevision(); return ALTERED_TO_CORRECT_REVISION; } } return ALL_OPTIONS_EXHAUSTED; } public RevisionAlteration setRevisionTo(StageIdFaninScmMaterialPair revisionToSet, FanInGraphContext context) { RevisionAlteration revisionAlteration = alterRevision(revisionToSet, context); while (revisionAlteration == NEED_MORE_REVISIONS) { fillNextRevisions(context); revisionAlteration = alterRevision(revisionToSet, context); } return revisionAlteration; } public void initialize(FanInGraphContext context) { totalInstanceCount = context.pipelineTimeline.instanceCount(((DependencyMaterialConfig) materialConfig).getPipelineName()); maxBackTrackLimit = context.maxBackTrackLimit; } public PipelineTimelineEntry latestPipelineTimelineEntry(FanInGraphContext context) { if (totalInstanceCount == 0) { return null; } return context.pipelineTimeline.instanceFor(((DependencyMaterialConfig) materialConfig).getPipelineName(), totalInstanceCount - 1); } private void fillNextRevisions(FanInGraphContext context) { if (!hasMoreInstances()) { return; } int batchOffset = currentCount; for (int i = 1; i <= context.revBatchCount; ++i) { final Pair<StageIdentifier, List<FaninScmMaterial>> sIdScmPair = getRevisionNthFor(i + batchOffset, context); if (!validateAllScmRevisionsAreSameWithinAFingerprint(sIdScmPair)) { ++currentCount; if (!hasMoreInstances()) { break; } continue; } validateIfRevisionMatchesTheCurrentConfigAndUpdateTheMaterialMap(context, sIdScmPair); if (!hasMoreInstances()) { break; } } } private Pair<StageIdentifier, List<FaninScmMaterial>> getRevisionNthFor(int n, FanInGraphContext context) { List<FaninScmMaterial> scmMaterials = new ArrayList<>(); PipelineTimeline pipelineTimeline = context.pipelineTimeline; Queue<PipelineTimelineEntry.Revision> revisionQueue = new ConcurrentLinkedQueue<>(); DependencyMaterialConfig dependencyMaterial = (DependencyMaterialConfig) materialConfig; PipelineTimelineEntry entry = pipelineTimeline.instanceFor(dependencyMaterial.getPipelineName(), totalInstanceCount - n); Set<CaseInsensitiveString> visitedNodes = new HashSet<>(); StageIdentifier dependentStageIdentifier = dependentStageIdentifier(context, entry, CaseInsensitiveString.str(dependencyMaterial.getStageName())); if (!StageIdentifier.NULL.equals(dependentStageIdentifier)) { addToRevisionQueue(entry, revisionQueue, scmMaterials, context, visitedNodes); } else { return null; } while (!revisionQueue.isEmpty()) { PipelineTimelineEntry.Revision revision = revisionQueue.poll(); DependencyMaterialRevision dmr = DependencyMaterialRevision.create(revision.revision, null); PipelineTimelineEntry pte = pipelineTimeline.getEntryFor(new CaseInsensitiveString(dmr.getPipelineName()), dmr.getPipelineCounter()); addToRevisionQueue(pte, revisionQueue, scmMaterials, context, visitedNodes); } return new Pair<>(dependentStageIdentifier, scmMaterials); } private boolean validateAllScmRevisionsAreSameWithinAFingerprint(Pair<StageIdentifier, List<FaninScmMaterial>> pIdScmPair) { if (pIdScmPair == null) { return false; } Map<FaninScmMaterial, PipelineTimelineEntry.Revision> versionsByMaterial = new HashMap<>(); List<FaninScmMaterial> scmMaterialList = pIdScmPair.last(); for (final FaninScmMaterial scmMaterial : scmMaterialList) { PipelineTimelineEntry.Revision revision = versionsByMaterial.get(scmMaterial); if (revision == null) { versionsByMaterial.put(scmMaterial, scmMaterial.revision); } else if (!revision.equals(scmMaterial.revision)) { return false; } } return true; } private void validateIfRevisionMatchesTheCurrentConfigAndUpdateTheMaterialMap(FanInGraphContext context, Pair<StageIdentifier, List<FaninScmMaterial>> stageIdentifierScmPair) { final Set<MaterialConfig> currentScmMaterials = context.pipelineScmDepMap.get(materialConfig); final Set<FaninScmMaterial> scmMaterials = new HashSet<>(stageIdentifierScmPair.last()); final Set<String> currentScmFingerprint = new HashSet<>(); for (MaterialConfig currentScmMaterial : currentScmMaterials) { currentScmFingerprint.add(currentScmMaterial.getFingerprint()); } final Set<String> scmMaterialsFingerprint = new HashSet<>(); for (FaninScmMaterial scmMaterial : scmMaterials) { scmMaterialsFingerprint.add(scmMaterial.fingerprint); } final Collection commonMaterials = CollectionUtils.intersection(currentScmFingerprint, scmMaterialsFingerprint); if (commonMaterials.size() == scmMaterials.size() && commonMaterials.size() == currentScmMaterials.size()) { stageIdentifierScmMaterial.put(stageIdentifierScmPair.first(), scmMaterials); ++currentCount; } else { Collection disjunctionWithConfig = CollectionUtils.disjunction(currentScmFingerprint, commonMaterials); Collection disjunctionWithInstance = CollectionUtils.disjunction(scmMaterialsFingerprint, commonMaterials); LOGGER.warn(String.format("[Fan-in] - Incompatible materials for %s. Config: %s. Instance: %s.", stageIdentifierScmPair.first().getStageLocator(), disjunctionWithConfig, disjunctionWithInstance)); //This is it. We will not go beyond this revision in history totalInstanceCount = currentCount; } } private StageIdentifier dependentStageIdentifier(FanInGraphContext context, PipelineTimelineEntry entry, final String stageName) { return context.pipelineDao.latestPassedStageIdentifier(entry.getId(), stageName); } private void addToRevisionQueue(PipelineTimelineEntry entry, Queue<PipelineTimelineEntry.Revision> revisionQueue, List<FaninScmMaterial> scmMaterials, FanInGraphContext context, Set<CaseInsensitiveString> visitedNodes) { for (Map.Entry<String, List<PipelineTimelineEntry.Revision>> revisionList : entry.revisions().entrySet()) { String fingerprint = revisionList.getKey(); PipelineTimelineEntry.Revision revision = revisionList.getValue().get(0); if (isScmMaterial(fingerprint, context)) { scmMaterials.add(new FaninScmMaterial(fingerprint, revision)); continue; } if (isDependencyMaterial(fingerprint, context) && !visitedNodes.contains(new CaseInsensitiveString(revision.revision))) { revisionQueue.add(revision); visitedNodes.add(new CaseInsensitiveString(revision.revision)); } } } private boolean isDependencyMaterial(String fingerprint, FanInGraphContext context) { return context.fingerprintDepMaterialMap.containsKey(fingerprint); } private boolean isScmMaterial(String fingerprint, FanInGraphContext context) { return context.fingerprintScmMaterialMap.containsKey(fingerprint); } private boolean hasMoreInstances() { if (currentCount > maxBackTrackLimit) { throw new MaxBackTrackLimitReachedException(materialConfig); } return currentCount < totalInstanceCount; } private RevisionAlteration alterRevision(StageIdFaninScmMaterialPair revisionToSet, FanInGraphContext context) { if (currentRevision == revisionToSet.stageIdentifier) { return RevisionAlteration.SAME_AS_CURRENT_REVISION; } if (!stageIdentifierScmMaterial.get(currentRevision).contains(revisionToSet.faninScmMaterial)) { return RevisionAlteration.NOT_APPLICABLE; } List<StageIdentifier> stageIdentifiers = new ArrayList<>(stageIdentifierScmMaterial.keySet()); int currentRevIndex = stageIdentifiers.indexOf(currentRevision); for (int i = currentRevIndex; i < stageIdentifiers.size(); i++) { final StageIdentifier key = stageIdentifiers.get(i); final List<FaninScmMaterial> materials = new ArrayList<>(stageIdentifierScmMaterial.get(key)); final int index = materials.indexOf(revisionToSet.faninScmMaterial); if (index == -1) { return ALL_OPTIONS_EXHAUSTED; } final FaninScmMaterial faninScmMaterial = materials.get(index); if (faninScmMaterial.revision.equals(revisionToSet.faninScmMaterial.revision)) { currentRevision = key; return ALTERED_TO_CORRECT_REVISION; } if (faninScmMaterial.revision.lessThan(revisionToSet.faninScmMaterial.revision)) { currentRevision = key; return ALTERED_TO_CORRECT_REVISION; } } if (!hasMoreInstances()) { return ALL_OPTIONS_EXHAUSTED; } return NEED_MORE_REVISIONS; } public List<StageIdFaninScmMaterialPair> getCurrentFaninScmMaterials() { List<StageIdFaninScmMaterialPair> stageIdScmPairs = new ArrayList<>(); Set<FaninScmMaterial> faninScmMaterials = stageIdentifierScmMaterial.get(currentRevision); for (FaninScmMaterial faninScmMaterial : faninScmMaterials) { StageIdFaninScmMaterialPair pIdScmPair = new StageIdFaninScmMaterialPair(currentRevision, faninScmMaterial); stageIdScmPairs.add(pIdScmPair); } return stageIdScmPairs; } }