jenkinsci / java-client-api

A Jenkins API client for Java
MIT License
901 stars 470 forks source link

NullPointerException after calling triggerJobAndWaitUntilFinished #440

Open mar1ged opened 4 years ago

mar1ged commented 4 years ago

When I call triggerJobAndWaitUntilFinished() I get an NPE:

java.lang.NullPointerException: null
    at com.google.common.base.Preconditions.checkNotNull(Preconditions.java:878) ~[guava-28.1-jre.jar:na]
    at com.google.common.net.PercentEscaper.escape(PercentEscaper.java:145) ~[guava-28.1-jre.jar:na]
    at com.offbytwo.jenkins.model.Job$MapEntryToQueryStringPair.apply(Job.java:185) ~[jenkins-client-0.3.8.jar:0.3.8]
    at com.offbytwo.jenkins.model.Job$MapEntryToQueryStringPair.apply(Job.java:181) ~[jenkins-client-0.3.8.jar:0.3.8]
    at com.google.common.collect.Iterators$6.transform(Iterators.java:783) ~[guava-28.1-jre.jar:na]
    at com.google.common.collect.TransformedIterator.next(TransformedIterator.java:47) ~[guava-28.1-jre.jar:na]
    at org.apache.commons.lang.StringUtils.join(StringUtils.java:2831) ~[commons-lang-2.3.jar:2.3]
    at org.apache.commons.lang.StringUtils.join(StringUtils.java:2878) ~[commons-lang-2.3.jar:2.3]
    at com.offbytwo.jenkins.model.Job.build(Job.java:150) ~[jenkins-client-0.3.8.jar:0.3.8]
    at com.offbytwo.jenkins.model.Job.build(Job.java:125) ~[jenkins-client-0.3.8.jar:0.3.8]
    at com.offbytwo.jenkins.JenkinsTriggerHelper.triggerJobAndWaitUntilFinished(JenkinsTriggerHelper.java:88) ~[jenkins-client-0.3.8.jar:0.3.8]
...

The stacktrace was truncated because the other lines only show my code.

In my code I basically do this:

            Map<String, String> params = new HashMap<>();
    params.put("identifier", command.getIdentifier());
            ...
            BuildWithDetails details = null;
    try {
        details = th.triggerJobAndWaitUntilFinished(jp.getJob(), params, true);
    } catch (HttpResponseException e) {
        log.warn("You were denied by Jenkins. Please check user and password used", e);
    } catch (IOException e) {
        e.printStackTrace();
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (NullPointerException e) {
        log.warn("It looks like calling job {} failed. Value of details: {}", jp.getJob(), details, e);
    }

I already forced the use of a current version of Guava because by default v17 was chosen. After checking the params I found out that in some situations one of the parameters turns null. And this causes the library to fail.

Is it possible to have the library return a descriptive error message in such situations ?

But even after I fixed this and made sure only to call the method when no null values are inside the map, the call fails with another NPE:

java.lang.NullPointerException: null
    at com.offbytwo.jenkins.JenkinsServer.getBuild(JenkinsServer.java:824) ~[jenkins-client-0.3.8.jar:0.3.8]
    at com.offbytwo.jenkins.JenkinsTriggerHelper.triggerJobAndWaitUntilFinished(JenkinsTriggerHelper.java:178) ~[jenkins-client-0.3.8.jar:0.3.8]
    at com.offbytwo.jenkins.JenkinsTriggerHelper.triggerJobAndWaitUntilFinished(JenkinsTriggerHelper.java:90) ~[jenkins-client-0.3.8.jar:0.3.8]
...

I tried to debug this but all I could find out is that here the queueItem becomes null:

image

When checking the Jenkins job I can see that it was created (it failed with exit code 6 due to some other reasons, but it was created and ran)

mar1ged commented 4 years ago

I did some deeper analysis and copied the code from your two triggerJobAndWaitUntilFinished methods in order to add some debugging info.

while (!queueItem.isCancelled() && job.isInQueue()) {
    log.debug("checking state of queue");
    Thread.sleep(2000L);
    job = jenkins.getJob(jobName);
    queueItem = jenkins.getQueueItem(queuseRef);
}

int runs = 1;
while (queueItem.getExecutable() == null && runs <= 60) {
    log.debug(".getExecutable() returns null, checking again ({})", runs);
    queueItem = jenkins.getQueueItem(queuseRef);
    Thread.sleep(1000);
    runs++;
}

Build build = jenkins.getBuild(queueItem);
if (queueItem.isCancelled()) {
    return build.details();
}

From what I could find out the call to getExecutable() returns null because there is no job id available at the point in time where you query it. Therefore I added the check in the while loop you can find in the middle.

2019-12-04 16:12:08.805  INFO 16936 --- [nio-8081-exec-1] d.a.b.s.services.JenkinsConnectivity     : Triggering job
2019-12-04 16:12:09.163  INFO 16936 --- [nio-8081-exec-1] d.a.b.s.services.JenkinsConnectivity     : Connected to Jenkins with version 2.176.1
2019-12-04 16:12:10.166 DEBUG 16936 --- [nio-8081-exec-1] d.a.b.s.services.JenkinsConnectivity     : .getExecutable() returns null, checking again (1)
2019-12-04 16:12:11.384 DEBUG 16936 --- [nio-8081-exec-1] d.a.b.s.services.JenkinsConnectivity     : .getExecutable() returns null, checking again (2)
2019-12-04 16:12:12.604 DEBUG 16936 --- [nio-8081-exec-1] d.a.b.s.services.JenkinsConnectivity     : .getExecutable() returns null, checking again (3)
2019-12-04 16:12:13.854 DEBUG 16936 --- [nio-8081-exec-1] d.a.b.s.services.JenkinsConnectivity     : .getExecutable() returns null, checking again (4)
2019-12-04 16:12:15.104 DEBUG 16936 --- [nio-8081-exec-1] d.a.b.s.services.JenkinsConnectivity     : .getExecutable() returns null, checking again (5)
2019-12-04 16:12:16.354 DEBUG 16936 --- [nio-8081-exec-1] d.a.b.s.services.JenkinsConnectivity     : .getExecutable() returns null, checking again (6)
2019-12-04 16:12:18.233 DEBUG 16936 --- [nio-8081-exec-1] d.a.b.s.services.JenkinsConnectivity     : Waiting until build is completed
2019-12-04 16:12:20.462 DEBUG 16936 --- [nio-8081-exec-1] d.a.b.s.services.JenkinsConnectivity     : Waiting until build is completed
2019-12-04 16:12:22.710 DEBUG 16936 --- [nio-8081-exec-1] d.a.b.s.services.JenkinsConnectivity     : Waiting until build is completed
2019-12-04 16:12:24.962 DEBUG 16936 --- [nio-8081-exec-1] d.a.b.s.services.JenkinsConnectivity     : Waiting until build is completed
2019-12-04 16:12:27.464  INFO 16936 --- [nio-8081-exec-1] d.a.b.s.services.JenkinsConnectivity     : Result of job is desc=failed in foo, id=216 and state=FAILURE

This code can certainly be improved by combining the two loops into one or by handling if the Executable takes more then 60 iterations. But for now this solved the problem for me.

vxe commented 4 years ago

What do you mean by:

I already forced the use of a current version of Guava because by default v17 was chosen. After checking the params I found out that in some situations one of the parameters turns null. And this causes the library to fail.

I'm having this issue as well, aka

When I call triggerJobAndWaitUntilFinished() I get an NPE:

githubzjx2016 commented 3 years ago

我也遇到了这个问题,2019年提出来,现在都2021 年了,这个问题竟然还存在...

arijitdhar commented 2 years ago

Exactly, as of now, we have to continuously check in a loop for a predefined number of times, to see if queueItem.getExecutable() is non null and then get the build info. And yes, for doing this please use the source code from the library class "JenkinsTriggerHelper" and use it in you code as below:

` private BuildWithDetails triggerJobAndWaitUntilFinished(final String jobName, final Map<String, String> params, final long retryInterval) throws IOException, InterruptedException {

    JobWithDetails job = this.jenkinsServer.getJob(jobName);
    QueueReference queueRef = job.build(params);

    QueueItem queueItem = this.jenkinsServer.getQueueItem(queueRef);

    while (!queueItem.isCancelled() && job.isInQueue()) {
        Thread.sleep(retryInterval);
        job = this.jenkinsServer.getJob(jobName);
        queueItem = this.jenkinsServer.getQueueItem(queueRef);
    }

    int runs = 1;
    while (queueItem.getExecutable() == null && runs <= 20) {
        System.out.println(".getExecutable() returns null, checking again ({"+runs+"})");
        queueItem = this.jenkinsServer.getQueueItem(queueRef);
        Thread.sleep(retryInterval);
        runs++;
    }

    Build build = this.jenkinsServer.getBuild(queueItem);
    if (queueItem.isCancelled()) {
        return build.details();
    }

    boolean isBuilding = build.details().isBuilding();
    while (isBuilding) {
        Thread.sleep(retryInterval);
        isBuilding = build.details().isBuilding();
    }

    return build.details();
}

` Note: jenkinsServer is an instance of JenkinsServer class configured with jenkins url, username and password/token

JenkinsServer jenkinsServer = new JenkinsServer(jenkinsUri, userName, apiToken);