Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Save information about child jobs in parent job #13

Closed
wants to merge 6 commits into from

Conversation

Jimilian
Copy link

In some cases it would be nice to know what exactly was triggered by pipeline job. I.e. this information can be used by Build Failure Analyzer Plugin to show failures in child jobs on the top trigger job.

import java.util.List;


public class BuildInfoAction extends InvisibleAction {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see no problem with making it visible, but maybe it's outside the scope

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can put this comment to the code ;)

}

public List<BuildInfo> getBuildInfoList() {
return buildInfoList;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrap with unmodifyableList()

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gone.

}

public void addBuildInfo(String projectName, int buildNumber) {
buildInfoList.add(new BuildInfo(projectName, buildNumber));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems there may be concurrency issues with this field. I would recommend CopyOnWrite list or synchronize

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CopyOnWrite looks like overkill, synchronize needs some manual work, so, I picked up ConcurrentLinkedQueue (current implementation of this action doesn't require random access).

private final String projectName;
private final int buildNumber;

public BuildInfo(String projectName, int buildNumber) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This approach may start leaking job existence fact if the API user has no Item.Discover permission. Not sure if it's acceptable

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How can I prevent this?
Btw, is it possible that user has access to list of actions of some build, but doesn't have access to the log of this build? (right now plugin publishes names/build numbers of triggered jobs to the log).

For now I made this class private and only list of runs can be retrieved from action. I hope that it would be safer.

* Make BuildInfoAction thread-safe
* Create method to provide list of child runs
* Add tests for BuildInfoAction
@@ -29,6 +30,15 @@ public void onStarted(Run<?, ?> run, TaskListener listener) {
TaskListener taskListener = stepContext.get(TaskListener.class);
// encodeTo(Run) calls getDisplayName, which does not include the project name.
taskListener.getLogger().println("Starting building: " + ModelHyperlinkNote.encodeTo("/" + run.getUrl(), run.getFullDisplayName()));
FlowNode parentNode = stepContext.get(FlowNode.class);
if (parentNode != null) {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@oleg-nenashev, I suppose that this code is not thread-safe as well. Is it correct?
I think to put synchronized(this) for addAction/getAction

@Jimilian Jimilian closed this Jun 29, 2017
@Jimilian Jimilian reopened this Jun 29, 2017
@Jimilian Jimilian closed this Jun 29, 2017
@Jimilian Jimilian reopened this Jun 29, 2017
@Jimilian Jimilian closed this Jun 30, 2017
@Jimilian Jimilian reopened this Jun 30, 2017
@Jimilian
Copy link
Author

Jimilian commented Jul 3, 2017

@oleg-nenashev, @jglick, can you take a look, please?

@jglick jglick self-requested a review July 3, 2017 13:56
@Jimilian
Copy link
Author

@jglick, will you have time to review this change? Should I rebase it first?

Copy link
Member

@oleg-nenashev oleg-nenashev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

public List<Run<?, ?>> getChildBuilds() {
List<Run<?, ?>> builds = new ArrayList<>();

for (BuildInfo buildInfo : buildInfos) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor optimization: Return Collections.emptyList() if the size is null before creating a new ArrayList. Maybe also makes sense to pre-initialize builds size to buildInfos.size

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if ConcurrentLinkedQueue.size() is fast enough though. Maybe it will hammer the performance

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ConcurrentLinkedQueue.isEmpty() comes for free, but in current implementation it's not possible to have empty collection of buildInfos longer than several ms (+ StopTheWorld pause :) ). Anyway, I added this check anyway - who knows how this Action will be used in future.
Pre-initialize - good idea, can decrease memory footprint a little bit.

Copy link
Member

@jglick jglick left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few technical mistakes which could be easily corrected. More broadly, the intent of this PR is unclear, since it adds no user-visible functionality. If it is intended as an API for other plugins to consume, there would need to be at least one downstream PR demonstrating the integrated functionality, which could be used to evaluate the suitability of the API.

Thread.sleep(100);
}

assertEquals(501, buildInfoAction.getChildBuilds().size());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test does not seem to be proving anything very interesting. I would just delete it. (PowerMock is a pain in the butt to maintain for Jenkins plugins anyway.)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right now I used PowerMock only for Jenkins singleton, for the rest of stuff I used Mockito. What do you prefer to use for mocking Jenkins.getInstance method?

BuildInfoAction() {
}

BuildInfoAction(String projectName, int buildNumber) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Useless constructor, better deleted.

for (BuildInfo buildInfo : buildInfos) {
if (buildInfo != null) {
Job job = Jenkins.getActiveInstance().getItemByFullName(buildInfo.getProjectName(), Job.class);
if (buildInfo.getBuildNumber() != 0) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Huh? Why would the build number be zero?

if (buildInfo != null) {
Job job = Jenkins.getActiveInstance().getItemByFullName(buildInfo.getProjectName(), Job.class);
if (buildInfo.getBuildNumber() != 0) {
builds.add((job != null) ? job.getBuildByNumber(buildInfo.getBuildNumber()) : null);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the job is missing, do not add null.

And getBuildByNumber may well add null; do not add that.


for (BuildInfo buildInfo : buildInfos) {
if (buildInfo != null) {
Job job = Jenkins.getActiveInstance().getItemByFullName(buildInfo.getProjectName(), Job.class);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unclear whether this should be impersonating SYSTEM. Who is calling it? Perhaps better to return the raw data and let the caller decide how to handle missing information.

@@ -29,6 +30,11 @@ public void onStarted(Run<?, ?> run, TaskListener listener) {
TaskListener taskListener = stepContext.get(TaskListener.class);
// encodeTo(Run) calls getDisplayName, which does not include the project name.
taskListener.getLogger().println("Starting building: " + ModelHyperlinkNote.encodeTo("/" + run.getUrl(), run.getFullDisplayName()));
FlowNode parentNode = stepContext.get(FlowNode.class);
if (parentNode != null) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to check for null if defined via @StepContextParameter (which is actually deprecated but fixing that requires a larger change).

if (buildInfoAction == null) {
synchronized (this) {
buildInfoAction = parentNode.getAction(BuildInfoAction.class);
if (buildInfoAction == null) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Double-checked locking, unsafe.


private static class BuildInfo {
private final String projectName;
private final int buildNumber;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Easier just to use String via Run.externalizableId.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does not look easier for me, in this case I would need one String concatenation + String.split for every call for nothing. I now that it's tiny stuff, but even from code perspective it doesn't simplify much.

@@ -430,6 +450,23 @@ public MultiBranchProjectFactory newInstance() {
}
}

private void assertBuildInfoAction(WorkflowRun workflowRun, String projectName, int buildNumber) {
FlowGraphWalker walker = new FlowGraphWalker(workflowRun.getExecution());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🐜 Deprecated, use DepthFirstScanner instead

Copy link
Author

@Jimilian Jimilian Aug 7, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It needs workflow-api:2.17, so, I updated it in pom.xml as well, but now it fails in verification: https://ci.jenkins.io/blue/organizations/jenkins/Plugins%2Fpipeline-build-step-plugin/detail/PR-13/9/tests/
Also, I think @jglick would not be happy to introduce dependency for higher version (especially in case of test code needs it -> not production).
Also I didn't find tag @Deprecated in constructor of FlowGraphWalker class in new API.

@svanoort, @jglick, @oleg-nenashev So, what should I do?

@svanoort
Copy link
Member

I'll more or less echo Jesse's comments here -- would need to see an example of how this API is used in practice.

@Jimilian Jimilian closed this Aug 6, 2017
@Jimilian Jimilian reopened this Aug 6, 2017
@Jimilian
Copy link
Author

@svanoort, @jglick I wanted to use it Build Failure Analyzer Plugin (@rsandell). I created proof of concept some time ago and it works perfect, but to create good PR I need to submit this change first. So, I wanted to use API presented in this PR in this way: https://github.com/Jimilian/build-failure-analyzer-plugin/blob/pipeline_jobs_poc/src/main/java/com/sonyericsson/jenkins/plugins/bfa/model/dbf/BuildFlowDBF.java

@jglick
Copy link
Member

jglick commented Dec 8, 2023

#127 includes the same information, but additionally indicates which step was responsible for a given downstream build.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants