[JENKINS-49779] sequential stages inside parallel in Declarative syntax (#1778)

* [JENKINS-49779] Support for sequential stage groups in BlueOcean nodes API

Signed-off-by: olivier lamy <olamy@apache.org>
This commit is contained in:
Olivier Lamy 2018-07-31 11:04:29 +10:00 committed by GitHub
parent 90371826c7
commit 55db7d5f67
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 379 additions and 171 deletions

View File

@ -19,6 +19,7 @@ import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
/**
* @author Vivek Pandey
@ -43,7 +44,7 @@ public class FlowNodeWrapper {
private Collection<Action> pipelineActions;
public FlowNodeWrapper(@Nonnull FlowNode node, @Nonnull NodeRunStatus status, @Nonnull TimingInfo timingInfo, @Nonnull WorkflowRun run) {
public FlowNodeWrapper(@Nonnull FlowNode node, @Nonnull NodeRunStatus status, @Nonnull TimingInfo timingInfo, @Nonnull WorkflowRun run) {
this.node = node;
this.status = status;
this.timingInfo = timingInfo;
@ -160,6 +161,11 @@ public class FlowNodeWrapper {
return node.hashCode();
}
@Override
public String toString() {
return getClass().getName() + "[id=" + node.getId() + ",displayName=" + this.displayName + ",type=" + this.type + "]";
}
boolean hasBlockError(){
return blockErrorAction != null
&& blockErrorAction.getError() != null;

View File

@ -21,7 +21,7 @@ public class PipelineNodeContainerImpl extends BluePipelineNodeContainer {
private final WorkflowRun run;
private final Map<String, BluePipelineNode> nodeMap = new HashMap<>();
List<BluePipelineNode> nodes = new ArrayList<>();
private final List<BluePipelineNode> nodes;
private final Link self;
public PipelineNodeContainerImpl(WorkflowRun run, Link parentLink) {

View File

@ -1,13 +1,13 @@
package io.jenkins.blueocean.rest.impl.pipeline;
import com.google.common.base.Predicate;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import hudson.model.Action;
import io.jenkins.blueocean.rest.Reachable;
import io.jenkins.blueocean.rest.hal.Link;
import io.jenkins.blueocean.rest.model.BluePipelineNode;
import io.jenkins.blueocean.rest.model.BluePipelineStep;
import io.jenkins.blueocean.rest.model.BlueRun;
import org.apache.commons.lang3.StringUtils;
import org.jenkinsci.plugins.pipeline.modeldefinition.actions.ExecutionModelAction;
import org.jenkinsci.plugins.workflow.actions.LabelAction;
import org.jenkinsci.plugins.workflow.actions.NotExecutedNodeAction;
import org.jenkinsci.plugins.workflow.actions.TimingAction;
@ -49,6 +49,7 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import java.util.stream.Collectors;
/**
* @author Vivek Pandey
@ -68,6 +69,8 @@ public class PipelineNodeGraphVisitor extends StandardChunkVisitor implements No
public final Map<String, FlowNodeWrapper> nodeMap = new LinkedHashMap<>();
public final Map<String, Stack<FlowNodeWrapper>> stackPerEnd = new HashMap<>();
private static final Logger logger = LoggerFactory.getLogger(PipelineNodeGraphVisitor.class);
private static final boolean isNodeVisitorDumpEnabled = Boolean.getBoolean("NODE-DUMP-ENABLED");
@ -78,6 +81,7 @@ public class PipelineNodeGraphVisitor extends StandardChunkVisitor implements No
private final ArrayDeque<FlowNode> pendingInputSteps = new ArrayDeque<>();
private final Stack<FlowNode> parallelBranchEndNodes = new Stack<>();
private final Stack<FlowNode> parallelBranchStartNodes = new Stack<>();
private final InputAction inputAction;
@ -91,11 +95,14 @@ public class PipelineNodeGraphVisitor extends StandardChunkVisitor implements No
private final static String PARALLEL_SYNTHETIC_STAGE_NAME = "Parallel";
private final boolean declarative;
public PipelineNodeGraphVisitor(WorkflowRun run) {
this.run = run;
this.inputAction = run.getAction(InputAction.class);
this.pipelineActions = new HashSet<>();
this.pendingActionsForBranches = new HashMap<>();
declarative = run.getAction(ExecutionModelAction.class) != null;
FlowExecution execution = run.getExecution();
if(execution!=null) {
try {
@ -150,10 +157,9 @@ public class PipelineNodeGraphVisitor extends StandardChunkVisitor implements No
//if block stage node push it to stack as it may have nested stages
if(parallelEnd == null &&
endNode instanceof StepEndNode
&& !PipelineNodeUtil.isSyntheticStage(((StepEndNode) endNode).getStartNode()) //skip synthetic stages
&& PipelineNodeUtil.isStage(((StepEndNode) endNode).getStartNode())) {
endNode instanceof StepEndNode
&& !PipelineNodeUtil.isSyntheticStage(((StepEndNode) endNode).getStartNode()) //skip synthetic stages
&& PipelineNodeUtil.isStage(((StepEndNode) endNode).getStartNode())) {
//XXX: There seems to be bug in eventing, chunkEnd is sent twice for the same FlowNode
// Lets peek and if the last one is same as this endNode then skip adding it
@ -187,14 +193,21 @@ public class PipelineNodeGraphVisitor extends StandardChunkVisitor implements No
return;
}
// its stage inside parallel, we skip it and clear nest stages collected inside parallel
boolean parallelNestedStages = false;
// it's nested stages inside parallel so let's collect them later
if(parallelEnd != null){
return;
// nested stages not supported in scripted pipeline.
if(!isDeclarative()){
return;
}
parallelNestedStages = true;
}
if(!nestedStages.empty()){
nestedStages.pop(); //we throw away nested stages
if(!nestedStages.empty()){ //there is still a nested stage, return
nestedStages.pop(); //we throw away first nested stage
// nested stages not supported in scripted pipeline.
if(!nestedStages.isEmpty()&&!isDeclarative()){
return;
}
}
@ -218,8 +231,8 @@ public class PipelineNodeGraphVisitor extends StandardChunkVisitor implements No
status = new NodeRunStatus(GenericStatus.NOT_EXECUTED);
}else if(chunk.getLastNode() != null){
status = new NodeRunStatus(StatusAndTiming
.computeChunkStatus2(run, chunk.getNodeBefore(),
firstExecuted, chunk.getLastNode(), chunk.getNodeAfter()));
.computeChunkStatus2(run, chunk.getNodeBefore(),
firstExecuted, chunk.getLastNode(), chunk.getNodeAfter()));
}else{
status = new NodeRunStatus(firstExecuted);
}
@ -228,14 +241,16 @@ public class PipelineNodeGraphVisitor extends StandardChunkVisitor implements No
status = new NodeRunStatus(BlueRun.BlueRunResult.UNKNOWN, BlueRun.BlueRunState.PAUSED);
}
FlowNodeWrapper stage = new FlowNodeWrapper(chunk.getFirstNode(),
status, times, run);
status, times, run);
stage.setCauseOfFailure(PipelineNodeUtil.getCauseOfBlockage(stage.getNode(), agentNode));
accumulatePipelineActions(chunk.getFirstNode());
stage.setPipelineActions(drainPipelineActions());
nodes.push(stage);
nodeMap.put(stage.getId(), stage);
if(!parallelNestedStages){
nodes.push( stage );
nodeMap.put( stage.getId(), stage );
}
if(!skippedStage && !parallelBranches.isEmpty()){
Iterator<FlowNodeWrapper> branches = parallelBranches.descendingIterator();
while(branches.hasNext()){
@ -244,13 +259,24 @@ public class PipelineNodeGraphVisitor extends StandardChunkVisitor implements No
stage.addEdge(p);
}
}else{
if(parallelNestedStages) {
String endId = parallelBranchEndNodes.peek().getId();
Stack<FlowNodeWrapper> stack = stackPerEnd.get(endId);
if(stack==null){
stack=new Stack<>();
stackPerEnd.put(endId, stack);
}
stack.add(stage);
}
if(nextStage != null) {
nextStage.addParent(stage);
stage.addEdge(nextStage);
}
}
parallelBranches.clear();
this.nextStage = stage;
if(!parallelNestedStages) {
this.nextStage = stage;
}
}
@ -265,14 +291,14 @@ public class PipelineNodeGraphVisitor extends StandardChunkVisitor implements No
public void parallelStart(@Nonnull FlowNode parallelStartNode, @Nonnull FlowNode branchNode, @Nonnull ForkScanner scanner) {
if(isNodeVisitorDumpEnabled) {
dump(String.format("parallelStart=> id: %s, name: %s, function: %s", parallelStartNode.getId(),
parallelStartNode.getDisplayName(), parallelStartNode.getDisplayFunctionName()));
parallelStartNode.getDisplayName(), parallelStartNode.getDisplayFunctionName()));
dump(String.format("\tbranch=> id: %s, name: %s, function: %s", branchNode.getId(),
branchNode.getDisplayName(), branchNode.getDisplayFunctionName()));
branchNode.getDisplayName(), branchNode.getDisplayFunctionName()));
}
if(nestedbranches.size() != parallelBranchEndNodes.size()){
logger.debug(String.format("nestedBranches size: %s not equal to parallelBranchEndNodes: %s",
nestedbranches.size(), parallelBranchEndNodes.size()));
nestedbranches.size(), parallelBranchEndNodes.size()));
return;
}
@ -323,20 +349,44 @@ public class PipelineNodeGraphVisitor extends StandardChunkVisitor implements No
}
branch.setPipelineActions(branchActions);
if(nextStage!=null) {
// do we have nested sequential stages for this parallel branch?
Stack<FlowNodeWrapper> stack = stackPerEnd.get(endNode.getId());
if(stack!=null&&!stack.isEmpty()){
// yes so we can rebuild the graph here
// we don't want the first one as it's a duplicate of the parallel parent but with stage type so rid of it
FlowNodeWrapper flowNodeWrapper = stack.pop();
if(stack.isEmpty()){
if(nextStage!=null) {
branch.addEdge(nextStage);
}
parallelBranches.push(branch);
continue;
}
// if name is different it's not a dummy 'duplicate' stage with the same name so let's add it to the graph
if(!StringUtils.equals( flowNodeWrapper.getDisplayName(), branch.getDisplayName())){
branch.addEdge(flowNodeWrapper);
flowNodeWrapper.addParent(branch);
nodes.add(flowNodeWrapper);
}
flowNodeWrapper = stack.pop();
// here we rebuild parent/edge relation
branch.addEdge(flowNodeWrapper);
flowNodeWrapper.addParent(branch);
nodes.add(flowNodeWrapper);
stack.stream().forEach(nodeWrapper->{
nodes.peekLast().addEdge(nodeWrapper);
nodeWrapper.addParent(nodes.peekLast());
nodes.add( nodeWrapper );
});
}
else if(nextStage!=null) {
branch.addEdge(nextStage);
}
parallelBranches.push(branch);
}
FlowNodeWrapper[] sortedBranches = parallelBranches.toArray(new FlowNodeWrapper[parallelBranches.size()]);
Arrays.sort(sortedBranches, new Comparator<FlowNodeWrapper>() {
@Override
public int compare(FlowNodeWrapper o1, FlowNodeWrapper o2) {
return o1.getDisplayName().compareTo(o2.getDisplayName());
}
});
Arrays.sort(sortedBranches, Comparator.comparing(FlowNodeWrapper::getDisplayName));
parallelBranches.clear();
for(int i=0; i< sortedBranches.length; i++){
@ -355,10 +405,10 @@ public class PipelineNodeGraphVisitor extends StandardChunkVisitor implements No
public void parallelEnd(@Nonnull FlowNode parallelStartNode, @Nonnull FlowNode parallelEndNode, @Nonnull ForkScanner scanner) {
if(isNodeVisitorDumpEnabled) {
dump(String.format("parallelEnd=> id: %s, name: %s, function: %s", parallelEndNode.getId(),
parallelEndNode.getDisplayName(), parallelEndNode.getDisplayFunctionName()));
parallelEndNode.getDisplayName(), parallelEndNode.getDisplayFunctionName()));
if(parallelEndNode instanceof StepEndNode){
dump(String.format("parallelEnd=> id: %s, StartNode: %s, name: %s, function: %s", parallelEndNode.getId(),
((StepEndNode) parallelEndNode).getStartNode().getId(),((StepEndNode) parallelEndNode).getStartNode().getDisplayName(), ((StepEndNode) parallelEndNode).getStartNode().getDisplayFunctionName()));
((StepEndNode) parallelEndNode).getStartNode().getId(),((StepEndNode) parallelEndNode).getStartNode().getDisplayName(), ((StepEndNode) parallelEndNode).getStartNode().getDisplayFunctionName()));
}
}
captureOrphanParallelBranches();
@ -381,14 +431,15 @@ public class PipelineNodeGraphVisitor extends StandardChunkVisitor implements No
public void parallelBranchEnd(@Nonnull FlowNode parallelStartNode, @Nonnull FlowNode branchEndNode, @Nonnull ForkScanner scanner) {
if(isNodeVisitorDumpEnabled) {
dump(String.format("parallelBranchEnd=> id: %s, name: %s, function: %s, type: %s", branchEndNode.getId(),
branchEndNode.getDisplayName(), branchEndNode.getDisplayFunctionName(), branchEndNode.getClass()));
branchEndNode.getDisplayName(), branchEndNode.getDisplayFunctionName(), branchEndNode.getClass()));
if(branchEndNode instanceof StepEndNode){
dump(String.format("parallelBranchEnd=> id: %s, StartNode: %s, name: %s, function: %s", branchEndNode.getId(),
((StepEndNode) branchEndNode).getStartNode().getId(),((StepEndNode) branchEndNode).getStartNode().getDisplayName(),
((StepEndNode) branchEndNode).getStartNode().getDisplayFunctionName()));
((StepEndNode) branchEndNode).getStartNode().getId(),((StepEndNode) branchEndNode).getStartNode().getDisplayName(),
((StepEndNode) branchEndNode).getStartNode().getDisplayFunctionName()));
}
}
parallelBranchEndNodes.add(branchEndNode);
parallelBranchStartNodes.add(parallelStartNode);
}
@Override
@ -413,7 +464,7 @@ public class PipelineNodeGraphVisitor extends StandardChunkVisitor implements No
chunk.setPauseTimeMillis(chunk.getPauseTimeMillis()+pause);
if(atomNode instanceof StepAtomNode
&& PipelineNodeUtil.isPausedForInputStep((StepAtomNode) atomNode, inputAction)){
&& PipelineNodeUtil.isPausedForInputStep((StepAtomNode) atomNode, inputAction)){
pendingInputSteps.add(atomNode);
}
}
@ -458,16 +509,9 @@ public class PipelineNodeGraphVisitor extends StandardChunkVisitor implements No
@Override
public List<BluePipelineNode> getPipelineNodes(final Link parent) {
List<BluePipelineNode> nodes = new ArrayList<>();
for(FlowNodeWrapper n: this.nodes){
nodes.add(new PipelineNodeImpl(n,new Reachable() {
@Override
public Link getLink() {
return parent;
}
}, run));
}
return nodes;
return this.nodes.stream()
.map( n -> new PipelineNodeImpl(n, () -> parent, run))
.collect( Collectors.toList() );
}
@Override
@ -479,24 +523,21 @@ public class PipelineNodeGraphVisitor extends StandardChunkVisitor implements No
}
DepthFirstScanner depthFirstScanner = new DepthFirstScanner();
//If blocked scope, get the end node
FlowNode n = depthFirstScanner.findFirstMatch(execution.getCurrentHeads(), new Predicate<FlowNode>() {
@Override
public boolean apply(@Nullable FlowNode input) {
return (input!= null && input.getId().equals(nodeId) &&
(PipelineNodeUtil.isStage(input) || PipelineNodeUtil.isParallelBranch(input)));
}
});
FlowNode n = depthFirstScanner
.findFirstMatch(execution.getCurrentHeads(),
input -> (input!= null
&& input.getId().equals(nodeId)
&& (PipelineNodeUtil.isStage(input) || PipelineNodeUtil.isParallelBranch(input))));
if(n == null){ //if no node found or the node is not stage or parallel we return empty steps
return Collections.emptyList();
}
PipelineStepVisitor visitor = new PipelineStepVisitor(run, n);
ForkScanner.visitSimpleChunks(execution.getCurrentHeads(), visitor, new StageChunkFinder());
List<BluePipelineStep> steps = new ArrayList<>();
for(FlowNodeWrapper node: visitor.getSteps()){
steps.add(new PipelineStepImpl(node, parent));
}
return steps;
return visitor.getSteps()
.stream()
.map( node -> new PipelineStepImpl(node, parent))
.collect( Collectors.toList() );
}
@Override
@ -507,11 +548,10 @@ public class PipelineNodeGraphVisitor extends StandardChunkVisitor implements No
}
PipelineStepVisitor visitor = new PipelineStepVisitor(run, null);
ForkScanner.visitSimpleChunks(execution.getCurrentHeads(), visitor, new StageChunkFinder());
List<BluePipelineStep> steps = new ArrayList<>();
for(FlowNodeWrapper node: visitor.getSteps()){
steps.add(new PipelineStepImpl(node, parent));
}
return steps;
return visitor.getSteps()
.stream()
.map( node -> new PipelineStepImpl(node, parent))
.collect( Collectors.toList() );
}
@Override
@ -562,8 +602,8 @@ public class PipelineNodeGraphVisitor extends StandardChunkVisitor implements No
thatStage = futureNode;
futureStage = futureNode;
}else if(futureNode.type == FlowNodeWrapper.NodeType.PARALLEL &&
futureNodeParent != null &&
futureNodeParent.equals(latestNode.getFirstParent())){
futureNodeParent != null &&
futureNodeParent.equals(latestNode.getFirstParent())){
thatStage = futureNode.getFirstParent();
if(futureNode.edges.size() > 0){
futureStage = futureNode.edges.get(0);
@ -591,8 +631,8 @@ public class PipelineNodeGraphVisitor extends StandardChunkVisitor implements No
}
}
FlowNodeWrapper n = new FlowNodeWrapper(futureNode.getNode(),
new NodeRunStatus(null,null),
new TimingInfo(), run);
new NodeRunStatus(null,null),
new TimingInfo(), run);
n.addEdges(futureNode.edges);
n.addParents(futureNode.getParents());
currentNodes.add(n);
@ -600,12 +640,7 @@ public class PipelineNodeGraphVisitor extends StandardChunkVisitor implements No
}
List<BluePipelineNode> newNodes = new ArrayList<>();
for(FlowNodeWrapper n: currentNodes){
newNodes.add(new PipelineNodeImpl(n,new Reachable() {
@Override
public Link getLink() {
return parent;
}
},run));
newNodes.add(new PipelineNodeImpl(n,() -> parent,run));
}
return newNodes;
}
@ -613,7 +648,7 @@ public class PipelineNodeGraphVisitor extends StandardChunkVisitor implements No
private void captureOrphanParallelBranches(){
if(!parallelBranches.isEmpty() && (firstExecuted == null
|| !PipelineNodeUtil.isStage(firstExecuted)
|| !PipelineNodeUtil.isStage(firstExecuted)
)){
FlowNodeWrapper synStage = createParallelSyntheticNode();
if(synStage!=null) {
@ -649,7 +684,7 @@ public class PipelineNodeGraphVisitor extends StandardChunkVisitor implements No
parents = new ArrayList<>();
}
FlowNode syntheticNode = new FlowNode(firstBranch.getNode().getExecution(),
createSyntheticStageId(firstNodeId, PARALLEL_SYNTHETIC_STAGE_NAME), parents){
createSyntheticStageId(firstNodeId, PARALLEL_SYNTHETIC_STAGE_NAME), parents){
@Override
public void save() throws IOException {
// no-op to avoid JENKINS-45892 violations from serializing the synthetic FlowNode.
@ -689,9 +724,9 @@ public class PipelineNodeGraphVisitor extends StandardChunkVisitor implements No
}
BlueRun.BlueRunState state = isCompleted ? BlueRun.BlueRunState.FINISHED :
(isPaused ? BlueRun.BlueRunState.PAUSED : BlueRun.BlueRunState.RUNNING);
(isPaused ? BlueRun.BlueRunState.PAUSED : BlueRun.BlueRunState.RUNNING);
BlueRun.BlueRunResult result = isFailure ? BlueRun.BlueRunResult.FAILURE :
(isUnknown ? BlueRun.BlueRunResult.UNKNOWN : BlueRun.BlueRunResult.SUCCESS);
(isUnknown ? BlueRun.BlueRunResult.UNKNOWN : BlueRun.BlueRunResult.SUCCESS);
TimingInfo timingInfo = new TimingInfo(duration,pauseDuration, startTime);
@ -706,6 +741,10 @@ public class PipelineNodeGraphVisitor extends StandardChunkVisitor implements No
return synStage;
}
public boolean isDeclarative() {
return declarative;
}
/**
* Create id of synthetic stage in a deterministic base.
*
@ -717,4 +756,4 @@ public class PipelineNodeGraphVisitor extends StandardChunkVisitor implements No
private @Nonnull String createSyntheticStageId(@Nonnull String firstNodeId, @Nonnull String syntheticStageName){
return String.format("%s-%s-synthetic",firstNodeId, syntheticStageName.toLowerCase());
}
}
}

View File

@ -49,6 +49,7 @@ import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.logging.LogManager;
import java.util.stream.Collectors;
import static io.jenkins.blueocean.auth.jwt.JwtToken.X_BLUEOCEAN_JWT;
import static org.junit.Assert.fail;
@ -344,15 +345,12 @@ public abstract class PipelineBaseTest{
}
protected List<FlowNode> getStages(NodeGraphBuilder builder){
List<FlowNode> nodes = new ArrayList<>();
for(FlowNodeWrapper node: builder.getPipelineNodes()){
if(node.type == FlowNodeWrapper.NodeType.STAGE){
nodes.add(node.getNode());
}
}
return nodes;
return builder.getPipelineNodes().stream()
.filter( nodeWrapper -> nodeWrapper.type == FlowNodeWrapper.NodeType.STAGE )
.map( nodeWrapper -> nodeWrapper.getNode() )
.collect( Collectors.toList() );
}
protected List<FlowNode> getAllSteps(WorkflowRun run){
PipelineStepVisitor visitor = new PipelineStepVisitor(run, null);
ForkScanner.visitSimpleChunks(run.getExecution().getCurrentHeads(), visitor, new StageChunkFinder());
@ -363,25 +361,18 @@ public abstract class PipelineBaseTest{
return steps;
}
protected List<FlowNode> getStagesAndParallels(NodeGraphBuilder builder){
List<FlowNode> nodes = new ArrayList<>();
for(FlowNodeWrapper node: builder.getPipelineNodes()){
if(node.type == FlowNodeWrapper.NodeType.STAGE || node.type == FlowNodeWrapper.NodeType.PARALLEL){
nodes.add(node.getNode());
}
}
return builder.getPipelineNodes().stream()
.filter( nodeWrapper -> nodeWrapper.type == FlowNodeWrapper.NodeType.PARALLEL || nodeWrapper.type == FlowNodeWrapper.NodeType.STAGE)
.map( nodeWrapper -> nodeWrapper.getNode() )
.collect( Collectors.toList() );
return nodes;
}
protected List<FlowNode> getParallelNodes(NodeGraphBuilder builder){
List<FlowNode> nodes = new ArrayList<>();
for(FlowNodeWrapper node: builder.getPipelineNodes()){
if(node.type == FlowNodeWrapper.NodeType.PARALLEL){
nodes.add(node.getNode());
}
}
return nodes;
return builder.getPipelineNodes().stream()
.filter( nodeWrapper -> nodeWrapper.type == FlowNodeWrapper.NodeType.PARALLEL )
.map( nodeWrapper -> nodeWrapper.getNode() )
.collect( Collectors.toList() );
}
protected String getHrefFromLinks(Map resp, String link){

View File

@ -4,13 +4,17 @@ import com.google.common.base.Charsets;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.io.Resources;
import com.mashape.unirest.http.Unirest;
import hudson.FilePath;
import hudson.model.FreeStyleProject;
import hudson.model.Result;
import hudson.model.Run;
import hudson.model.Slave;
import hudson.model.queue.QueueTaskFuture;
import hudson.util.RunList;
import io.jenkins.blueocean.listeners.NodeDownstreamBuildAction;
import io.jenkins.blueocean.rest.hal.Link;
import io.jenkins.blueocean.rest.model.BluePipelineNode;
import jenkins.branch.BranchSource;
import jenkins.model.Jenkins;
import jenkins.plugins.git.GitSCMSource;
@ -60,6 +64,7 @@ public class PipelineNodeTest extends PipelineBaseTest {
@BeforeClass
public static void setupStatic() throws Exception {
System.setProperty("NODE-DUMP-ENABLED", "true");//tests node dump code path, also helps debug test failure
Unirest.setTimeouts( 10000, 600000000 );
}
@Test
@ -454,12 +459,12 @@ public class PipelineNodeTest extends PipelineBaseTest {
" },\n" +
" secondBranch: {"+
" echo 'first Branch'\n" +
" stage('firstBranchTest') {"+
" echo 'running firstBranchTest'\n" +
" sh 'sleep 1'\n" +
" }\n"+
" echo 'first Branch end'\n" +
" },\n"+
" stage('firstBranchTest') {"+
" echo 'running firstBranchTest'\n" +
" sh 'sleep 1'\n" +
" }\n"+
" echo 'first Branch end'\n" +
" },\n"+
" failFast: false\n" +
" } \n" +
" stage ('deploy') { " +
@ -478,7 +483,8 @@ public class PipelineNodeTest extends PipelineBaseTest {
j.assertBuildStatusSuccess(b1);
NodeGraphBuilder builder = NodeGraphBuilder.NodeGraphBuilderFactory.getInstance(b1);
PipelineNodeGraphVisitor builder = new PipelineNodeGraphVisitor(b1);
assertFalse( builder.isDeclarative() );
List<FlowNode> stages = getStages(builder);
List<FlowNode> parallels = getParallelNodes(builder);
@ -2357,6 +2363,79 @@ public class PipelineNodeTest extends PipelineBaseTest {
assertEquals("Wait for interactive input", steps.get(3).get("displayName"));
}
@Test
@Issue("JENKINS-49050")
public void parallelStagesGroupsAndNestedStages() throws Exception {
WorkflowJob p = createWorkflowJobWithJenkinsfile( getClass(), "parallelStagesGroupsAndStages.jenkinsfile");
Slave s = j.createOnlineSlave();
s.setLabelString( "foo" );
s.setNumExecutors(2);
// Run until completed
WorkflowRun run = p.scheduleBuild2( 0).waitForStart();
j.waitForCompletion( run );
PipelineNodeGraphVisitor pipelineNodeGraphVisitor = new PipelineNodeGraphVisitor( run );
assertTrue( pipelineNodeGraphVisitor.isDeclarative() );
List<FlowNodeWrapper> wrappers = pipelineNodeGraphVisitor.getPipelineNodes();
FlowNodeWrapper flowNodeWrapper = wrappers.get( 0 );
assertEquals( "top", flowNodeWrapper.getDisplayName() );
assertEquals( 2, flowNodeWrapper.edges.size() );
flowNodeWrapper = wrappers.get( 1 );
assertEquals( "first", flowNodeWrapper.getDisplayName() );
assertEquals( 1, flowNodeWrapper.edges.size() );
assertEquals( 1, flowNodeWrapper.getParents().size() );
assertEquals( "first-inner-first", flowNodeWrapper.edges.get( 0 ).getDisplayName() );
assertEquals(7, wrappers.size());
List<Map> nodes = get("/organizations/jenkins/pipelines/" + p.getName() + "/runs/1/nodes/", List.class);
assertEquals(7, nodes.size());
}
@Test
public void nestedStagesGroups() throws Exception {
WorkflowJob p = createWorkflowJobWithJenkinsfile( getClass(), "nestedStagesGroups.jenkinsfile");
Slave s = j.createOnlineSlave();
s.setLabelString( "foo" );
s.setNumExecutors(4);
// Run until completed
WorkflowRun run = p.scheduleBuild2( 0).waitForStart();
j.waitForCompletion( run );
PipelineNodeGraphVisitor pipelineNodeGraphVisitor = new PipelineNodeGraphVisitor( run );
assertTrue( pipelineNodeGraphVisitor.isDeclarative() );
List<FlowNodeWrapper> wrappers = pipelineNodeGraphVisitor.getPipelineNodes();
assertEquals(7, wrappers.size());
}
@Test
@Issue("JENKINS-49050")
public void parallelStagesNonNested() throws Exception {
WorkflowJob p = createWorkflowJobWithJenkinsfile( getClass(), "parallelStagesNonNested.jenkinsfile");
Slave s = j.createOnlineSlave();
s.setLabelString( "foo" );
s.setNumExecutors(2);
// Run until completed
WorkflowRun run = p.scheduleBuild2( 0).waitForStart();
j.waitForCompletion( run );
PipelineNodeGraphVisitor pipelineNodeGraphVisitor = new PipelineNodeGraphVisitor( run );
List<FlowNodeWrapper> wrappers = pipelineNodeGraphVisitor.getPipelineNodes();
assertEquals(3, wrappers.size());
List<Map> nodes = get("/organizations/jenkins/pipelines/" + p.getName() + "/runs/1/nodes/", List.class);
assertEquals(3, nodes.size());
}
@Test
public void pipelineLogError() throws Exception {
String script = "def foo = null\n" +
@ -2425,50 +2504,51 @@ public class PipelineNodeTest extends PipelineBaseTest {
WorkflowJob job1 = j.jenkins.createProject(WorkflowJob.class, "pipeline1");
job1.setDefinition(new CpsFlowDefinition(script, false));
WorkflowRun b1 = job1.scheduleBuild2(0).get();
j.assertBuildStatus(Result.SUCCESS, b1);
WorkflowRun run = j.waitForCompletion( b1 );
j.assertBuildStatus(Result.SUCCESS, run);
List<Map> resp = get("/organizations/jenkins/pipelines/pipeline1/runs/1/nodes/", List.class);
Assert.assertEquals(3, resp.size());
PipelineNodeGraphVisitor pipelineNodeGraphVisitor = new PipelineNodeGraphVisitor( run );
List<FlowNodeWrapper> wrappers = pipelineNodeGraphVisitor.getPipelineNodes();
Assert.assertEquals(3, wrappers.size());
}
@Test
public void orphanParallels2() throws Exception{
String script = "stage(\"stage1\"){\n" +
" echo \"stage 1...\"\n" +
"}\n" +
"parallel('branch1':{\n" +
" node {\n" +
" stage('Setup') {\n" +
" sh 'echo \"Setup...\"'\n" +
" }\n" +
" stage('Unit and Integration Tests') {\n" +
" sh 'echo \"Unit and Integration Tests...\"'\n" +
" }\n" +
" }\n" +
"}, 'branch3': {\n" +
" node {\n" +
" stage('Setup') {\n" +
" sh 'echo \"Branch3 setup...\"'\n" +
" }\n" +
" stage('Unit and Integration Tests') {\n" +
" echo '\"my command to execute tests\"'\n" +
" }\n" +
" }\n" +
"}, 'branch2': {\n" +
" node {\n" +
" stage('Setup') {\n" +
" sh 'echo \"Branch2 setup...\"'\n" +
" }\n" +
" stage('Unit and Integration Tests') {\n" +
" echo '\"my command to execute tests\"'\n" +
" }\n" +
" }\n" +
"})\n" +
"stage(\"stage2\"){\n" +
" echo \"stage 2...\"\n" +
"}";
" echo \"stage 1...\"\n" +
"}\n" +
"parallel('branch1':{\n" +
" node {\n" +
" stage('Setup') {\n" +
" sh 'echo \"Setup...\"'\n" +
" }\n" +
" stage('Unit and Integration Tests') {\n" +
" sh 'echo \"Unit and Integration Tests...\"'\n" +
" }\n" +
" }\n" +
"}, 'branch3': {\n" +
" node {\n" +
" stage('Setup') {\n" +
" sh 'echo \"Branch3 setup...\"'\n" +
" }\n" +
" stage('Unit and Integration Tests') {\n" +
" echo '\"my command to execute tests\"'\n" +
" }\n" +
" }\n" +
"}, 'branch2': {\n" +
" node {\n" +
" stage('Setup') {\n" +
" sh 'echo \"Branch2 setup...\"'\n" +
" }\n" +
" stage('Unit and Integration Tests') {\n" +
" echo '\"my command to execute tests\"'\n" +
" }\n" +
" }\n" +
"})\n" +
"stage(\"stage2\"){\n" +
" echo \"stage 2...\"\n" +
"}";
WorkflowJob job1 = j.jenkins.createProject(WorkflowJob.class, "pipeline1");
job1.setDefinition(new CpsFlowDefinition(script, false));
WorkflowRun b1 = job1.scheduleBuild2(0).get();
@ -2514,21 +2594,6 @@ public class PipelineNodeTest extends PipelineBaseTest {
assertEquals(0, edges.size());
}
}
Map synNode = nodes.get(1);
List<Map> edges = (List<Map>) synNode.get("edges");
Map n = get("/organizations/jenkins/pipelines/pipeline1/runs/1/nodes/"+ synNode.get("id") +"/", Map.class);
List<Map> receivedEdges = (List<Map>) n.get("edges");
assertNotNull(n);
assertEquals(synNode.get("displayName"), n.get("displayName"));
assertEquals(synNode.get("id"), n.get("id"));
Assert.assertEquals(3, edges.size());
assertEquals(edges.get(0).get("id"), receivedEdges.get(0).get("id"));
assertEquals(edges.get(1).get("id"), receivedEdges.get(1).get("id"));
assertEquals(edges.get(2).get("id"), receivedEdges.get(2).get("id"));
}
@Issue("JENKINS-47158")

View File

@ -0,0 +1,47 @@
pipeline {
agent any
stages {
stage("top") {
stages {
stage("first") {
stages {
stage("first-inner-first") {
steps {
echo "#1 second-inner-first"
echo "#2 second-inner-first"
echo "#3 second-inner-first"
echo "#4 second-inner-first"
}
}
stage("first-inner-second") {
steps {
echo "first-inner-second"
}
}
}
}
stage("second") {
stages {
stage("second-inner-first") {
steps {
echo "second-inner-first"
}
}
stage("second-inner-second") {
when {
expression {
return false
}
}
steps {
echo "WE SHOULD NEVER GET HERE"
}
}
}
}
}
}
}
}

View File

@ -0,0 +1,38 @@
pipeline {
agent any
stages {
stage("top") {
parallel {
stage("first") {
stages {
stage("first-inner-first") {
steps {
echo "stage first-inner-first"
}
}
stage("first-inner-second") {
steps {
echo "stage first-inner-second"
}
}
}
}
stage("second") {
stages {
stage("second-inner-first") {
steps {
echo "stage second-inner-first"
}
}
stage("second-inner-second") {
steps {
echo "stage second-inner-second"
}
}
}
}
}
}
}
}

View File

@ -0,0 +1,21 @@
pipeline {
agent any
stages {
stage("top") {
parallel {
stage("first") {
steps {
echo "stage first"
}
}
stage("second") {
steps {
echo "stage second"
}
}
}
}
}
}

View File

@ -4,4 +4,5 @@ handlers=java.util.logging.ConsoleHandler
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
java.util.logging.ConsoleHandler.level = ALL
org.apache.http.wire.level = FINE
#org.apache.http.wire.level = FINE
io.jenkins.blueocean.rest.impl.pipeline.level = FINE

22
pom.xml
View File

@ -321,7 +321,7 @@
<dependency>
<groupId>org.jenkinsci.plugins</groupId>
<artifactId>pipeline-model-definition</artifactId>
<version>1.3</version>
<version>1.3.1</version>
<exclusions>
<exclusion>
<groupId>org.jenkins-ci.plugins</groupId>
@ -332,27 +332,27 @@
<dependency>
<groupId>org.jenkinsci.plugins</groupId>
<artifactId>pipeline-stage-tags-metadata</artifactId>
<version>1.3</version>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>org.jenkinsci.plugins</groupId>
<artifactId>pipeline-model-api</artifactId>
<version>1.3</version>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-cps</artifactId>
<version>2.46</version>
<version>2.54</version>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-api</artifactId>
<version>2.25</version>
<version>2.28</version>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-job</artifactId>
<version>2.17</version>
<version>2.23</version>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
@ -362,17 +362,17 @@
<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-multibranch</artifactId>
<version>2.17</version>
<version>2.20</version>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-step-api</artifactId>
<version>2.14</version>
<version>2.16</version>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-durable-task-step</artifactId>
<version>2.18</version>
<version>2.19</version>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
@ -388,7 +388,7 @@
<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-basic-steps</artifactId>
<version>2.6</version>
<version>2.9</version>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
@ -466,7 +466,7 @@
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>apache-httpcomponents-client-4-api</artifactId>
<version>4.5.5-2.0</version>
<version>4.5.5-3.0</version>
</dependency>
<!-- Other -->