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

[JENKINS-59656] check build id before interrupting from the executors widget #4264

Merged
merged 11 commits into from
Dec 10, 2019

Conversation

thomasgl-orange
Copy link
Contributor

@thomasgl-orange thomasgl-orange commented Oct 4, 2019

See JENKINS-59656

The approach I propose here is to add a new stopBuild method to the executor API (Executor#doStopBuild(String)), which takes an identifier for the target build as parameter. It's value (if set) should match the externalizable id of the executor's current executable. If it doesn't match, the stopBuild request does nothing (similar to the case of the user not having the right permissions). Of course, the executors view adds the right build ids to the target URL of its stop buttons.

The behaviour of the existing stop API (which takes no parameter) is left unchanged. Usage as an HTTP API method should be avoided (and the one I've substituted is the only one I've found in Jenkins code base), because it's error-prone (it can stop a build which is not the intended one), but I think it's usage as a Java method is still okay (see discussion below about implementation of AbstractBuild#doStop()), so I don't think it should be deprecated.

Note that I've assumed that a Queue.Executable was also always a Run (on which I could call getExternalizableId()). Please let me know if it's not true.

Proposed changelog entries

  • improved stop button behavior in the executors widget of the classical GUI, to avoid accidentally interrupting the wrong job

Submitter checklist

  • JIRA issue is well described
  • Changelog entry appropriate for the audience affected by the change (users or developer, depending on the change)
  • Appropriate autotests or explanation to why this change has no tests

Desired reviewers

Maybe @daniel-beck, for the test case? (because it's based on something you wrote in Security400Test)

Copy link
Member

@jvz jvz left a comment

Choose a reason for hiding this comment

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

LGTM

@alecharp
Copy link
Member

is this still a work in progress?

@thomasgl-orange
Copy link
Contributor Author

is this still a work in progress?

Yes, I'm waiting for #4278 to be merged, to shorten the jelly code.
Also, I should probably add a test case, something very similar to Security400Test.ensureDoStopStillReachable().

@thomasgl-orange
Copy link
Contributor Author

is this still a work in progress?

Yes, I'm waiting for #4278 to be merged, to shorten the jelly code.
Also, I should probably add a test case, something very similar to Security400Test.ensureDoStopStillReachable().

Now using h.urlEncode from #4278, and added test case. I will remove the WIP tag, I think the PR is ready.

@thomasgl-orange thomasgl-orange changed the title [WIP][JENKINS-59656] check build id before interrupting from the executors widget [JENKINS-59656] check build id before interrupting from the executors widget Oct 11, 2019
@oleg-nenashev oleg-nenashev requested a review from a team October 11, 2019 14:35
@oleg-nenashev oleg-nenashev added the needs-more-reviews Complex change, which would benefit from more eyes label Oct 11, 2019
@daniel-beck daniel-beck added the plugin-api-changes Changes the API of Jenkins available for use in plugins. label Oct 11, 2019
Copy link
Contributor

@res0nance res0nance left a comment

Choose a reason for hiding this comment

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

LGTM, AFAIK queue executables are builds, freestylebuilds, abstractbuilds and workflowruns all inherit from run
https://javadoc.jenkins.io/hudson/model/Queue.Executable.html

@rsandell
Copy link
Member

rsandell commented Nov 7, 2019

I think you need to change the stop button on the build page as well?

@thomasgl-orange
Copy link
Contributor Author

I think you need to change the stop button on the build page as well?

I don't think so. This button already POSTs to the stop API of the build itself (/job/something/NNN/stop), not the one of an executor, so it's much less error-prone:
https://github.com/jenkinsci/jenkins/blob/jenkins-2.203/core/src/main/resources/lib/hudson/buildCaption.jelly#L41
Same for the one in a job's history widget:
https://github.com/jenkinsci/jenkins/blob/jenkins-2.203/core/src/main/resources/hudson/widgets/HistoryWidget/entry.jelly#L65

This doStop() method (on a build) stops the (OneOff)Executor for which the build is the current executable:
https://github.com/jenkinsci/jenkins/blob/jenkins-2.203/core/src/main/java/hudson/model/AbstractBuild.java#L1307
https://github.com/jenkinsci/jenkins/blob/jenkins-2.203/core/src/main/java/hudson/model/Executor.java#L935

Of course, we could change AbstractBuild#doStop() implementation to make it call Executor#doStopBuild(runExtId) rather than Executor#doStop(), but to me it seems a bit redundant (it would then become something like "find the right executor via its current executable, and then check again that its current executable is the right one").

@thomasgl-orange
Copy link
Contributor Author

Updated the PR text, because it was still describing the first version of the changes (with an optional parameter to doStop instead of the new doStopBuild method).

@@ -110,7 +110,7 @@ THE SOFTWARE.
</st:include>
<td class="pane" align="center" valign="middle">
<j:if test="${e.hasStopPermission()}">
<l:stopButton href="${rootURL}/${c.url}${url}/stop" confirm="${%confirm(exe.fullDisplayName)}" alt="${%terminate this build}" />
<l:stopButton href="${rootURL}/${c.url}${url}/stopBuild?runExtId=${h.urlEncode(exe.externalizableId)}" confirm="${%confirm(exe.fullDisplayName)}" alt="${%terminate this build}" />
Copy link
Member

Choose a reason for hiding this comment

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

I think that h.urlEncode expects a non null object. But if exe is not a Run that method is not defined on the object and I think jelly will then just pass in null causing a NullPointerException

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You're right. I've addressed that in db5d364, by making h.urlEncode tolerant to null. It will then return an empty string, which is, I think, more convenient than returning null for the intended use case (encoding URL query parameters).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh, and you're right about jelly passing null for unknown properties, I tested it to be sure.

Copy link
Contributor

@MRamonLeon MRamonLeon left a comment

Choose a reason for hiding this comment

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

Do you agree to keep the former behavior if the executable is not a Run?

@thomasgl-orange
Copy link
Contributor Author

Do you agree to keep the former behavior if the executable is not a Run?

Agreed. I was kind of assuming AbstractBuild (a Run) was the only implementation of Queue.Executable, but it's true only in Jenkins core.
Searching in plugins code reveals several other implementations, like for instance this one:

https://github.com/jenkinsci/slave-squatter-plugin/blob/master/src/main/java/hudson/plugins/slave_squatter/ReservationExecutable.java

You and @rsandell are absolutely right that the change I propose should not break these cases (by throwing an NPE from the jelly view, or by not interrupting the current executable).

@varyvol
Copy link

varyvol commented Nov 28, 2019

@thomasgl-orange thanks for your changes! I'm not sure about changing the behaviour in a public method in such an extended class. What do you think about checking in the Jelly file if the externalizableId is null or not and call the proper method depending on that, given you already implemented the two methods (the one receiving the id and the one not receiving it).

@fcojfernandez
Copy link
Member

I have the same concern than @varyvol and I think its suggestion makes sense

@thomasgl-orange
Copy link
Contributor Author

@varyvol @fcojfernandez : While I would agree, in general, that changing behavior of an existing public method is not good, I think here we should tolerate it.

The Functions.urlEncode(String) method has been introduced very recently, in PR #4278, in order to simplify the Jelly code of this PR. The whole point of puting it in Functions was to encourage proper URL-encoding of requests parameters from Jelly code, by providing something which would be simple enough to be actually used. See for example this fix, in the same PR:
https://github.com/jenkinsci/jenkins/pull/4278/files#diff-757c85f964f10fece894a97dd912e2a4L34
Having to guard its usages with null-check conditionals in Jelly code defeats this purpose.

Also, the current implementation, when called with a null parameter, raises an NPE (sorry about that, didn't think it through enough, obviously, when I've submitted this implementation). It is hard to believe that, in the last five weeks, someone has already discovered this new method, and has started using it in a way which actually relies on this NPE being thrown.

So, my opinion is still that "improving" this method, thus slightly changing its behavior on this corner case, is the way to go.

The best alternative being, in my opinion, to add a new Functions.urlEncode2(String) with the null handling change, and to deprecate of Functions.urlEncode(String). But it sounds like a very heavy way to fix what sounds, to me, like a purely theoretical retro-compatibility issue.

@fcojfernandez
Copy link
Member

@thomasgl-orange my concern is because if I make a quick search, I can see uses of that method: https://github.com/search?q=org%3Ajenkinsci+Functions.urlEncode&type=Code

At a very first glance, the change does not seem to have a great impact, but maybe it's wort to have a deeper look at those uses.

@timja
Copy link
Member

timja commented Nov 28, 2019

@thomasgl-orange my concern is because if I make a quick search, I can see uses of that method: github.com/search?q=org%3Ajenkinsci+Functions.urlEncode&type=Code

At a very first glance, the change does not seem to have a great impact, but maybe it's wort to have a deeper look at those uses.

The only use from that search I see is https://github.com/jenkinsci/jenkins/blob/96cc9ce0c57fe25e1538c9e8601672eed4b059db/core/src/main/resources/hudson/search/Search/search-failed.jelly and that's in core

@thomasgl-orange
Copy link
Contributor Author

@fcojfernandez @timja : I went through results from https://github.com/search?q=org%3Ajenkinsci+"urlEncode"&type=Code (without "Functions" in the query, because it could hide results in Jelly files), and the two relevant results I find are usages I had introduced in #4278:

<em>result has been truncated, <a href="?q=${h.urlEncode(q)}&amp;max=${max+100}">see 100 more</a></em>

<a href="${rootURL}/projectRelationship?lhs=${h.urlEncode(lhs.name)}&amp;rhs=${h.urlEncode(rhs.name)}">

(and these ones are alright with the null change)

One more thing, if it counts: Functions.urlEncode(String) is not yet in an LTS version, so less likely to have ever been used, but in core.

Copy link
Member

@fcojfernandez fcojfernandez left a comment

Choose a reason for hiding this comment

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

Fair enough then!

@varyvol
Copy link

varyvol commented Nov 28, 2019

@thomasgl-orange makes sense, thanks for the explanation and the in-depth verification.

@fcojfernandez fcojfernandez added the ready-for-merge The PR is ready to go, and it will be merged soon if there is no negative feedback label Nov 28, 2019
@timja timja removed the needs-more-reviews Complex change, which would benefit from more eyes label Nov 28, 2019
@oleg-nenashev oleg-nenashev added bug For changelog: Minor bug. Will be listed after features web-ui The PR includes WebUI changes which may need special expertise labels Dec 4, 2019
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.

The implementation looks great, thanks a lot!
One minor comment about making the new API public, but it does not block the merge.

* @since TODO
*/
@RequirePOST
public HttpResponse doStopBuild(@CheckForNull @QueryParameter(fixEmpty = true) String runExtId) {
Copy link
Member

Choose a reason for hiding this comment

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

Does it have to be a public API? We could mark it as @Restricted(NoExternalUse.class) which is a good practice for stapler endpoints IMHO

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think you're right, fixed in f01385e.
FYI, to make sure this restriction was alright, I've searched through all existing uses of the original Executor#doStop() method by plugins in the jenkinsci organisation, in case some would have a reason to be changed to Executor#doStopBuild(String) instead. But the few call sites I've found are always similar to what is done in AbstractBuild#doStop(), with Executor#doStop() being called immediately on an executor which has just been retrieved as the current one for a specific build, so it would be redundant to check it's the "right" one twice.

@oleg-nenashev oleg-nenashev merged commit d36acc1 into jenkinsci:master Dec 10, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug For changelog: Minor bug. Will be listed after features plugin-api-changes Changes the API of Jenkins available for use in plugins. ready-for-merge The PR is ready to go, and it will be merged soon if there is no negative feedback web-ui The PR includes WebUI changes which may need special expertise
Projects
None yet
Development

Successfully merging this pull request may close these issues.