PageRenderTime 77ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/rest-api/src/main/java/com/cloudbees/workflow/rest/external/RunExt.java

https://gitlab.com/CORP-RESELLER/pipeline-stage-view-plugin
Java | 497 lines | 368 code | 75 blank | 54 comment | 95 complexity | 08100f82bda448ef17b0221808426361 MD5 | raw file
  1. /*
  2. * The MIT License
  3. *
  4. * Copyright (c) 2013-2016, CloudBees, Inc.
  5. *
  6. * Permission is hereby granted, free of charge, to any person obtaining a copy
  7. * of this software and associated documentation files (the "Software"), to deal
  8. * in the Software without restriction, including without limitation the rights
  9. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  10. * copies of the Software, and to permit persons to whom the Software is
  11. * furnished to do so, subject to the following conditions:
  12. *
  13. * The above copyright notice and this permission notice shall be included in
  14. * all copies or substantial portions of the Software.
  15. *
  16. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  17. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  18. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  19. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  20. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  21. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  22. * THE SOFTWARE.
  23. */
  24. package com.cloudbees.workflow.rest.external;
  25. import com.cloudbees.workflow.flownode.FlowNodeUtil;
  26. import com.cloudbees.workflow.rest.endpoints.RunAPI;
  27. import com.cloudbees.workflow.rest.hal.Link;
  28. import com.cloudbees.workflow.rest.hal.Links;
  29. import com.fasterxml.jackson.annotation.JsonInclude;
  30. import hudson.model.Result;
  31. import hudson.model.Run;
  32. import org.jenkinsci.plugins.workflow.actions.TimingAction;
  33. import org.jenkinsci.plugins.workflow.flow.FlowExecution;
  34. import org.jenkinsci.plugins.workflow.graph.FlowGraphWalker;
  35. import org.jenkinsci.plugins.workflow.graph.FlowNode;
  36. import org.jenkinsci.plugins.workflow.job.WorkflowJob;
  37. import org.jenkinsci.plugins.workflow.job.WorkflowRun;
  38. import org.jenkinsci.plugins.workflow.support.steps.input.InputAction;
  39. import org.jenkinsci.plugins.workflow.support.steps.input.InputStepExecution;
  40. import java.util.ArrayList;
  41. import java.util.Collections;
  42. import java.util.Comparator;
  43. import java.util.List;
  44. /**
  45. * External API response object for pipeline run
  46. * @author <a href="mailto:tom.fennelly@gmail.com">tom.fennelly@gmail.com</a>
  47. */
  48. public class RunExt {
  49. private static int MAX_ARTIFACTS_COUNT = Integer.getInteger(RunExt.class.getName()+".maxArtifactsCount", 100);
  50. private RunLinks _links;
  51. private String id;
  52. private String name;
  53. private StatusExt status;
  54. private long startTimeMillis;
  55. private long endTimeMillis;
  56. private long durationMillis;
  57. private long queueDurationMillis;
  58. private long pauseDurationMillis;
  59. private List<StageNodeExt> stages;
  60. public RunLinks get_links() {
  61. return _links;
  62. }
  63. public void set_links(RunLinks _links) {
  64. this._links = _links;
  65. }
  66. public String getId() {
  67. return id;
  68. }
  69. public void setId(String id) {
  70. this.id = id;
  71. }
  72. public String getName() {
  73. return name;
  74. }
  75. public void setName(String name) {
  76. this.name = name;
  77. }
  78. public StatusExt getStatus() {
  79. return status;
  80. }
  81. public void setStatus(StatusExt status) {
  82. this.status = status;
  83. }
  84. public long getStartTimeMillis() {
  85. return startTimeMillis;
  86. }
  87. public void setStartTimeMillis(long startTimeMillis) {
  88. this.startTimeMillis = startTimeMillis;
  89. }
  90. public long getEndTimeMillis() {
  91. return endTimeMillis;
  92. }
  93. public void setEndTimeMillis(long endTimeMillis) {
  94. this.endTimeMillis = endTimeMillis;
  95. }
  96. public long getDurationMillis() {
  97. return durationMillis;
  98. }
  99. public void setDurationMillis(long durationMillis) {
  100. this.durationMillis = durationMillis;
  101. }
  102. public long getQueueDurationMillis() {
  103. return queueDurationMillis;
  104. }
  105. public void setQueueDurationMillis(long queueDurationMillis) {
  106. this.queueDurationMillis = queueDurationMillis;
  107. }
  108. public long getPauseDurationMillis() {
  109. return pauseDurationMillis;
  110. }
  111. public void setPauseDurationMillis(long pauseDurationMillis) {
  112. this.pauseDurationMillis = pauseDurationMillis;
  113. }
  114. public List<StageNodeExt> getStages() {
  115. return stages;
  116. }
  117. public void setStages(List<StageNodeExt> stages) {
  118. this.stages = stages;
  119. }
  120. public static final class RunLinks extends Links {
  121. private Link changesets;
  122. private Link pendingInputActions;
  123. private Link nextPendingInputAction;
  124. private Link artifacts;
  125. @JsonInclude(JsonInclude.Include.NON_NULL)
  126. public Link getChangesets() {
  127. return changesets;
  128. }
  129. public void setChangesets(Link changesets) {
  130. this.changesets = changesets;
  131. }
  132. @JsonInclude(JsonInclude.Include.NON_NULL)
  133. public Link getPendingInputActions() {
  134. return pendingInputActions;
  135. }
  136. public void setPendingInputActions(Link pendingInputActions) {
  137. this.pendingInputActions = pendingInputActions;
  138. }
  139. @JsonInclude(JsonInclude.Include.NON_NULL)
  140. public Link getNextPendingInputAction() {
  141. return nextPendingInputAction;
  142. }
  143. public void setNextPendingInputAction(Link nextPendingInputAction) {
  144. this.nextPendingInputAction = nextPendingInputAction;
  145. }
  146. public Link getArtifacts() {
  147. return artifacts;
  148. }
  149. @JsonInclude(JsonInclude.Include.NON_NULL)
  150. public void setArtifacts(Link artifacts) {
  151. this.artifacts = artifacts;
  152. }
  153. }
  154. /** Computes timings after the stages have been set up
  155. * That means timing of the stage has been computed, and the stages are sorted
  156. */
  157. public static RunExt computeTimings(RunExt runExt) {
  158. // Pause is the sum of stage pauses
  159. for (StageNodeExt stage : runExt.getStages()) {
  160. runExt.setDurationMillis(runExt.getPauseDurationMillis() + stage.getPauseDurationMillis());
  161. }
  162. if (!runExt.getStages().isEmpty()) {
  163. // We're done when the last stage is, and it is the result of the overall run
  164. FlowNodeExt lastStage = runExt.getLastStage();
  165. runExt.setEndTimeMillis(lastStage.getStartTimeMillis()+lastStage.getDurationMillis());
  166. lastStage.setStatus(runExt.getStatus());
  167. }
  168. long currentTimeMillis = System.currentTimeMillis();
  169. if (runExt.getStatus() == StatusExt.IN_PROGRESS || runExt.getStatus() == StatusExt.PAUSED_PENDING_INPUT) {
  170. runExt.setEndTimeMillis(currentTimeMillis);
  171. }
  172. // FIXME this seems really questionable, we can have *no* stages defined
  173. // in which case QueueDuration should be the delay between the first FlowNode executed and the run start time
  174. if (runExt.getStages().isEmpty()) {
  175. runExt.setQueueDurationMillis(currentTimeMillis - runExt.getStartTimeMillis());
  176. } else {
  177. StageNodeExt firstExecutedStage = runExt.getFirstExecutedStage();
  178. if (firstExecutedStage != null) {
  179. runExt.setQueueDurationMillis(firstExecutedStage.getStartTimeMillis() - runExt.getStartTimeMillis());
  180. }
  181. }
  182. runExt.setDurationMillis(Math.max(0, runExt.getEndTimeMillis() - runExt.getStartTimeMillis() - runExt.getQueueDurationMillis()));
  183. return runExt;
  184. }
  185. /** Get basics set up: everything but status/timing/node walking for a run, no cache use */
  186. public static RunExt createMinimal(WorkflowRun run) {
  187. FlowExecution execution = run.getExecution();
  188. final RunExt runExt = new RunExt();
  189. runExt.set_links(new RunLinks());
  190. runExt.get_links().initSelf(RunAPI.getDescribeUrl(run));
  191. runExt.setId(run.getId());
  192. runExt.setName(run.getDisplayName());
  193. runExt.initStatus(run);
  194. runExt.setStartTimeMillis(run.getStartTimeInMillis());
  195. runExt.setStages(new ArrayList<StageNodeExt>());
  196. if (execution != null) {
  197. if (ChangeSetExt.hasChanges(run)) {
  198. runExt.get_links().setChangesets(Link.newLink(RunAPI.getChangeSetsUrl(run)));
  199. }
  200. if (isPendingInput(run)) {
  201. runExt.get_links().setPendingInputActions(Link.newLink(RunAPI.getPendingInputActionsUrl(run)));
  202. runExt.get_links().setNextPendingInputAction(Link.newLink(RunAPI.getNextPendingInputActionUrl(run)));
  203. }
  204. List<Run<WorkflowJob, WorkflowRun>.Artifact> artifacts = run.getArtifactsUpTo(MAX_ARTIFACTS_COUNT);
  205. if (artifacts != null && !artifacts.isEmpty()) {
  206. runExt.get_links().setArtifacts(Link.newLink(RunAPI.getArtifactsUrl(run)));
  207. }
  208. }
  209. return runExt;
  210. }
  211. /** Creates a wrapper of this that hides the full stage nodes
  212. * Use case: returning a minimal view of the run, while using a cached, fully-realized version
  213. */
  214. public RunExt createWrapper() {
  215. return new ChildHidingWrapper(this);
  216. }
  217. protected static class ChildHidingWrapper extends RunExt {
  218. protected RunExt myRun;
  219. protected List<StageNodeExt> wrappedStages;
  220. public RunLinks get_links() {return myRun.get_links();}
  221. public String getId() {return myRun.getId();}
  222. public String getName() {return myRun.getName();}
  223. public StatusExt getStatus() {return myRun.getStatus();}
  224. public long getStartTimeMillis() {return myRun.getStartTimeMillis();}
  225. public long getEndTimeMillis() {return myRun.getEndTimeMillis();}
  226. public long getDurationMillis() {return myRun.getDurationMillis();}
  227. public long getQueueDurationMillis() {return myRun.getQueueDurationMillis();}
  228. public long getPauseDurationMillis() {return myRun.getPauseDurationMillis();}
  229. public List<StageNodeExt> getStages() {return Collections.unmodifiableList(wrappedStages);}
  230. protected ChildHidingWrapper(RunExt run) {
  231. this.myRun = run;
  232. List<StageNodeExt> myWrappedStages = new ArrayList<StageNodeExt>();
  233. if (wrappedStages == null) {
  234. for(StageNodeExt stage : run.getStages()) {
  235. myWrappedStages.add(stage.myWrapper());
  236. }
  237. this.wrappedStages = myWrappedStages;
  238. } else {
  239. wrappedStages = null;
  240. }
  241. }
  242. }
  243. public static RunExt create(WorkflowRun run) {
  244. FlowExecution execution = run.getExecution();
  245. // Use cache if eligible
  246. boolean isNotRunning = FlowNodeUtil.isNotPartOfRunningBuild(execution);
  247. if (isNotRunning) {
  248. RunExt myRun = FlowNodeUtil.getCachedRun(run);
  249. if (myRun != null) {
  250. return myRun;
  251. }
  252. }
  253. // Compute the entire flow
  254. RunExt myRun = createOld(run);
  255. if (isNotRunning) {
  256. FlowNodeUtil.cacheRun(run, myRun);
  257. }
  258. return myRun;
  259. }
  260. public static RunExt createOld(WorkflowRun run) {
  261. FlowExecution execution = run.getExecution();
  262. final RunExt runExt = new RunExt();
  263. runExt.set_links(new RunLinks());
  264. runExt.get_links().initSelf(RunAPI.getDescribeUrl(run));
  265. runExt.setId(run.getId());
  266. runExt.setName(run.getDisplayName());
  267. runExt.initStatus(run);
  268. runExt.setStartTimeMillis(run.getStartTimeInMillis());
  269. runExt.setStages(new ArrayList<StageNodeExt>());
  270. if (execution != null) {
  271. if (ChangeSetExt.hasChanges(run)) {
  272. runExt.get_links().setChangesets(Link.newLink(RunAPI.getChangeSetsUrl(run)));
  273. }
  274. if (isPendingInput(run)) {
  275. runExt.get_links().setPendingInputActions(Link.newLink(RunAPI.getPendingInputActionsUrl(run)));
  276. runExt.get_links().setNextPendingInputAction(Link.newLink(RunAPI.getNextPendingInputActionUrl(run)));
  277. }
  278. List<Run<WorkflowJob, WorkflowRun>.Artifact> artifacts = run.getArtifactsUpTo(MAX_ARTIFACTS_COUNT);
  279. if (artifacts != null && !artifacts.isEmpty()) {
  280. runExt.get_links().setArtifacts(Link.newLink(RunAPI.getArtifactsUrl(run)));
  281. }
  282. FlowGraphWalker walker = new FlowGraphWalker(execution);
  283. List<FlowNode> sortedNodes = FlowNodeUtil.getIdSortedExecutionNodeList(execution); // Hold a ref to prevent GC until we're done analyzing
  284. for (FlowNode node : walker) {
  285. long nodeTime = TimingAction.getStartTime(node);
  286. if (nodeTime > runExt.getEndTimeMillis()) {
  287. // Use the most resent FlowNode timestamp as being
  288. // the end time for the run.
  289. runExt.setEndTimeMillis(nodeTime);
  290. }
  291. if (StageNodeExt.isStageNode(node)) {
  292. StageNodeExt stage = StageNodeExt.create(node);
  293. stage.addStageFlowNodes(node);
  294. runExt.addStage(stage);
  295. runExt.setPauseDurationMillis(runExt.getPauseDurationMillis() + stage.getPauseDurationMillis());
  296. }
  297. }
  298. if (!runExt.getStages().isEmpty()) {
  299. runExt.sortStages();
  300. FlowNodeExt lastStage = runExt.getLastStage();
  301. // Correct for some edge cases due to simplified handling of stages (just looking for executed and presence of error)
  302. for (StageNodeExt stageNodeExt : runExt.getStages()) {
  303. // If stage has an error, but run succeeded... error was caught
  304. if (stageNodeExt.getStatus() == StatusExt.FAILED && (runExt.getStatus() != StatusExt.FAILED)) {
  305. stageNodeExt.setStatus(StatusExt.SUCCESS);
  306. }
  307. // If run is unstable, one of the stages must have set the status -- no idea which though
  308. if (runExt.getStatus() == StatusExt.UNSTABLE && stageNodeExt.getStatus() != StatusExt.NOT_EXECUTED) {
  309. stageNodeExt.setStatus(StatusExt.UNSTABLE);
  310. }
  311. }
  312. // Correct for rare cases with 0 for a FlowEndNode
  313. if (lastStage.getPauseDurationMillis() < 0) {
  314. lastStage.setPauseDurationMillis(0);
  315. }
  316. if (lastStage.getDurationMillis() < 0) {
  317. lastStage.setDurationMillis(runExt.getEndTimeMillis()-lastStage.getStartTimeMillis());
  318. }
  319. lastStage.setStatus(runExt.getStatus());
  320. }
  321. long currentTimeMillis = System.currentTimeMillis();
  322. if (runExt.getStatus() == StatusExt.IN_PROGRESS || runExt.getStatus() == StatusExt.PAUSED_PENDING_INPUT) {
  323. runExt.setEndTimeMillis(currentTimeMillis);
  324. }
  325. if (runExt.getStages().isEmpty()) {
  326. runExt.setQueueDurationMillis(currentTimeMillis - runExt.getStartTimeMillis());
  327. } else {
  328. StageNodeExt firstExecutedStage = runExt.getFirstExecutedStage();
  329. if (firstExecutedStage != null) {
  330. runExt.setQueueDurationMillis(firstExecutedStage.getStartTimeMillis() - runExt.getStartTimeMillis());
  331. }
  332. }
  333. runExt.setDurationMillis(Math.max(0, runExt.getEndTimeMillis() - runExt.getStartTimeMillis() - runExt.getQueueDurationMillis()));
  334. sortedNodes = null;
  335. }
  336. return runExt;
  337. }
  338. public static boolean isPendingInput(WorkflowRun run) {
  339. InputAction inputAction = run.getAction(InputAction.class);
  340. if (inputAction != null) {
  341. List<InputStepExecution> executions = inputAction.getExecutions();
  342. if (executions != null && !executions.isEmpty()) {
  343. return true;
  344. }
  345. }
  346. return false;
  347. }
  348. private void initStatus(WorkflowRun run) {
  349. FlowExecution execution = run.getExecution();
  350. if (execution == null) {
  351. setStatus(StatusExt.NOT_EXECUTED);
  352. } else if (execution.getCauseOfFailure() != null) {
  353. setStatus(StatusExt.valueOf(execution.getCauseOfFailure()));
  354. } else if (execution.isComplete()) {
  355. Result r = run.getResult();
  356. if (r == Result.NOT_BUILT) {
  357. setStatus(StatusExt.NOT_EXECUTED);
  358. } else if (r == Result.ABORTED) {
  359. setStatus(StatusExt.ABORTED);
  360. } else if (r == Result.FAILURE ) {
  361. setStatus(StatusExt.FAILED);
  362. } else if (r == Result.UNSTABLE ) {
  363. setStatus(StatusExt.UNSTABLE);
  364. } else if (r == Result.SUCCESS) {
  365. setStatus(StatusExt.SUCCESS);
  366. } else {
  367. setStatus(StatusExt.FAILED);
  368. }
  369. } else if (isPendingInput(run)) {
  370. setStatus(StatusExt.PAUSED_PENDING_INPUT);
  371. } else {
  372. setStatus(StatusExt.IN_PROGRESS);
  373. }
  374. }
  375. private void addStage(StageNodeExt stageToAdd) {
  376. // Iterate the current list of stages and see if we already have a stage with
  377. // the same id.
  378. for (int i = 0; i < getStages().size(); i++) {
  379. FlowNodeExt stage = getStages().get(i);
  380. if (stage.getId().equals(stageToAdd.getId())) {
  381. // Same stage... don't add again.
  382. return;
  383. }
  384. }
  385. // Insert at the head, but we'll be sorting them (by time) later.
  386. getStages().add(0, stageToAdd);
  387. }
  388. private void sortStages() {
  389. // TF: Sorting in case they're somehow out of order (depending on how the FloWGraph works
  390. // wrt forks and joins etc). My understanding is that forks and joins only make sense
  391. // within a given stage (and so a stage node should only ever be traversed once),
  392. // but I may be wrong so just in case :) ...
  393. Comparator<FlowNodeExt> sortComparator = new Comparator<FlowNodeExt>() {
  394. @Override
  395. public int compare(FlowNodeExt stage1, FlowNodeExt stage2) {
  396. if (stage1.getStartTimeMillis() < stage2.getStartTimeMillis()) {
  397. return -1;
  398. } else if (stage1.getStartTimeMillis() > stage2.getStartTimeMillis()) {
  399. return 1;
  400. }
  401. return 0;
  402. }
  403. };
  404. Collections.sort(getStages(), sortComparator);
  405. }
  406. private StageNodeExt getFirstExecutedStage() {
  407. for (int i = 0; i < getStages().size(); i++) {
  408. StageNodeExt stage = getStages().get(i);
  409. if (stage.getStatus() != StatusExt.NOT_EXECUTED) {
  410. return stage;
  411. }
  412. }
  413. return null;
  414. }
  415. private FlowNodeExt getLastStage() {
  416. if (getStages().isEmpty()) {
  417. return null;
  418. } else {
  419. return getStages().get(getStages().size() - 1);
  420. }
  421. }
  422. }