-
-
Notifications
You must be signed in to change notification settings - Fork 9.8k
[JENKINS-70059] Improve Safe Restart interface and user information #7355
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
Changes from 58 commits
24be93a
dbf0194
3c4b989
4beaa7b
7121f6d
9980d18
feaf3a1
4fc22a2
b5b35f5
650eed0
a344b08
99d5ed6
c7f7795
bcbffe1
8635553
fb4eecd
402b30c
6e1b715
d4fe2b7
5abd8c0
0f1fba9
8a4ff7c
ea7bc73
b0178b1
25ce12e
12e0483
09243fc
1e5074e
989d836
7f792a3
5c4b20f
0ec3323
75710c0
612aa46
f961cd7
6b16167
33681e1
c9dd18d
326ebfb
67b92a2
9446037
c6ffe16
ebb285d
bd250a2
4779b46
459ec06
8571d73
773b1bd
2d82089
7730051
f11d5eb
66900a3
de78fce
1553c03
b80928c
b6f78bb
e177791
8b657f4
85dc1bd
23107ec
1bee860
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| /* | ||
| * The MIT License | ||
| * | ||
| * Copyright (c) 2023, Jan Meiswinkel | ||
| * | ||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||
| * of this software and associated documentation files (the "Software"), to deal | ||
| * in the Software without restriction, including without limitation the rights | ||
| * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
| * copies of the Software, and to permit persons to whom the Software is | ||
| * furnished to do so, subject to the following conditions: | ||
| * | ||
| * The above copyright notice and this permission notice shall be included in | ||
| * all copies or substantial portions of the Software. | ||
| * | ||
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
| * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
| * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
| * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
| * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
| * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
| * THE SOFTWARE. | ||
| */ | ||
|
|
||
| package jenkins.cli; | ||
|
meiswjn marked this conversation as resolved.
|
||
|
|
||
| import hudson.Extension; | ||
| import hudson.cli.CLICommand; | ||
| import hudson.cli.Messages; | ||
| import java.util.logging.Logger; | ||
| import jenkins.model.Jenkins; | ||
| import org.kohsuke.accmod.Restricted; | ||
| import org.kohsuke.accmod.restrictions.NoExternalUse; | ||
| import org.kohsuke.args4j.Option; | ||
|
|
||
| /** | ||
| * Safe Restart Jenkins - do not accept any new jobs and try to pause existing. | ||
| * | ||
| * @since TODO | ||
| */ | ||
| @Extension | ||
|
meiswjn marked this conversation as resolved.
|
||
| @Restricted(NoExternalUse.class) | ||
| public class SafeRestartCommand extends CLICommand { | ||
| private static final Logger LOGGER = Logger.getLogger(SafeRestartCommand.class.getName()); | ||
|
|
||
| @Option(name = "-message", usage = "Message for safe restart that will be visible to users") | ||
| public String message = null; | ||
|
|
||
| @Override | ||
| public String getShortDescription() { | ||
| return Messages.SafeRestartCommand_ShortDescription(); | ||
| } | ||
|
|
||
| @Override | ||
| protected int run() throws Exception { | ||
| Jenkins.get().doSafeRestart(null, message); | ||
| return 0; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -482,6 +482,7 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve | |
|
|
||
| @CheckForNull | ||
| private transient volatile QuietDownInfo quietDownInfo; | ||
|
|
||
|
meiswjn marked this conversation as resolved.
|
||
| private transient volatile boolean terminating; | ||
| @GuardedBy("Jenkins.class") | ||
| private transient boolean cleanUpStarted; | ||
|
|
@@ -2943,6 +2944,20 @@ public boolean isQuietingDown() { | |
| return quietDownInfo != null; | ||
| } | ||
|
|
||
| /** | ||
| * Returns if the quietingDown is a safe restart. | ||
| * @since TODO | ||
| */ | ||
| @Restricted(NoExternalUse.class) | ||
| @NonNull | ||
| public boolean isPreparingSafeRestart() { | ||
|
meiswjn marked this conversation as resolved.
|
||
| QuietDownInfo quietDownInfo = this.quietDownInfo; | ||
| if (quietDownInfo != null) { | ||
| return quietDownInfo.isSafeRestart(); | ||
| } | ||
| return false; | ||
| } | ||
|
|
||
| /** | ||
| * Returns quiet down reason if it was indicated. | ||
| * @return | ||
|
|
@@ -2953,7 +2968,7 @@ public boolean isQuietingDown() { | |
| @CheckForNull | ||
| public String getQuietDownReason() { | ||
| final QuietDownInfo info = quietDownInfo; | ||
| return info != null ? info.reason : null; | ||
| return info != null ? info.message : null; | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -4093,7 +4108,7 @@ public synchronized HttpRedirect doQuietDown() { | |
| * | ||
| * @param block Block until the system really quiets down and no builds are running | ||
| * @param timeout If non-zero, only block up to the specified number of milliseconds | ||
| * @deprecated since 2.267; use {@link #doQuietDown(boolean, int, String)} instead. | ||
| * @deprecated since 2.267; use {@link #doQuietDown(boolean, int, String, boolean)} instead. | ||
|
meiswjn marked this conversation as resolved.
|
||
| */ | ||
| @Deprecated | ||
| public synchronized HttpRedirect doQuietDown(boolean block, int timeout) { | ||
|
|
@@ -4109,16 +4124,34 @@ public synchronized HttpRedirect doQuietDown(boolean block, int timeout) { | |
| * | ||
| * @param block Block until the system really quiets down and no builds are running | ||
| * @param timeout If non-zero, only block up to the specified number of milliseconds | ||
| * @param reason Quiet reason that will be visible to user | ||
| * @since 2.267 | ||
| * @param message Quiet reason that will be visible to user | ||
| * @deprecated use {@link #doQuietDown(boolean, int, String, boolean)} instead. | ||
| */ | ||
| @Deprecated(since = "TODO") | ||
| public HttpRedirect doQuietDown(boolean block, | ||
| int timeout, | ||
| @CheckForNull String message) throws InterruptedException, IOException { | ||
|
|
||
| return doQuietDown(block, timeout, message, false); | ||
| } | ||
|
|
||
| /** | ||
| * Quiet down Jenkins - preparation for a restart | ||
| * | ||
| * @param block Block until the system really quiets down and no builds are running | ||
| * @param timeout If non-zero, only block up to the specified number of milliseconds | ||
| * @param message Quiet reason that will be visible to user | ||
| * @param safeRestart If the quietDown is for a safeRestart | ||
| * @since TODO | ||
| */ | ||
| @RequirePOST | ||
| public HttpRedirect doQuietDown(@QueryParameter boolean block, | ||
| @QueryParameter int timeout, | ||
| @QueryParameter @CheckForNull String reason) throws InterruptedException, IOException { | ||
| @QueryParameter @CheckForNull String message, | ||
| @QueryParameter boolean safeRestart) throws InterruptedException, IOException { | ||
| synchronized (this) { | ||
| checkPermission(MANAGE); | ||
| quietDownInfo = new QuietDownInfo(reason); | ||
| quietDownInfo = new QuietDownInfo(message, safeRestart); | ||
| } | ||
| if (block) { | ||
| long waitUntil = timeout; | ||
|
|
@@ -4513,20 +4546,35 @@ public void doRestart(StaplerRequest req, StaplerResponse rsp) throws IOExceptio | |
| } | ||
|
|
||
| /** | ||
| * Queues up a restart of Jenkins for when there are no builds running, if we can. | ||
| * Queues up a safe restart of Jenkins. | ||
| * Builds that cannot continue while the controller is not running have to finish or pause before it can proceed. | ||
| * No new builds will be started. No new jobs are accepted. | ||
| * | ||
| * This first replaces "app" to {@link HudsonIsRestarting} | ||
| * @deprecated use {@link #doSafeRestart(StaplerRequest, String)} instead. | ||
| * | ||
| * @since 1.332 | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please keep
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @meiswjn ⬆️ |
||
| */ | ||
| @CLIMethod(name = "safe-restart") | ||
| @Deprecated(since = "TODO") | ||
| public HttpResponse doSafeRestart(StaplerRequest req) throws IOException, ServletException, RestartNotSupportedException { | ||
| return doSafeRestart(req, null); | ||
| } | ||
|
|
||
| /** | ||
| * Queues up a safe restart of Jenkins. Jobs have to finish or pause before it can proceed. No new jobs are accepted. | ||
| * | ||
| * @since TODO | ||
| */ | ||
| public HttpResponse doSafeRestart(StaplerRequest req, @QueryParameter("message") String message) throws IOException, ServletException, RestartNotSupportedException { | ||
| checkPermission(MANAGE); | ||
| if (req != null && req.getMethod().equals("GET")) | ||
| if (req != null && req.getMethod().equals("GET")) { | ||
| return HttpResponses.forwardToView(this, "_safeRestart.jelly"); | ||
| } | ||
|
|
||
| if (req != null && req.getParameter("cancel") != null) { | ||
| return doCancelQuietDown(); | ||
| } | ||
|
|
||
| if (req == null || req.getMethod().equals("POST")) { | ||
| safeRestart(); | ||
| safeRestart(message); | ||
| } | ||
|
|
||
| return HttpResponses.redirectToDot(); | ||
|
|
@@ -4572,11 +4620,22 @@ public void run() { | |
| /** | ||
| * Queues up a restart to be performed once there are no builds currently running. | ||
| * @since 1.332 | ||
|
meiswjn marked this conversation as resolved.
|
||
| * @deprecated use {@link #safeRestart(String)} instead. | ||
| */ | ||
| @Deprecated(since = "TODO") | ||
| public void safeRestart() throws RestartNotSupportedException { | ||
| safeRestart(null); | ||
| } | ||
|
|
||
| /** | ||
| * Queues up a restart to be performed once there are no builds currently running. | ||
| * @param message the message to show to users in the shutdown banner. | ||
| * @since TODO | ||
| */ | ||
| public void safeRestart(String message) throws RestartNotSupportedException { | ||
| final Lifecycle lifecycle = restartableLifecycle(); | ||
| // Quiet down so that we won't launch new builds. | ||
| quietDownInfo = new QuietDownInfo(); | ||
| quietDownInfo = new QuietDownInfo(message, true); | ||
|
|
||
| new Thread("safe-restart thread") { | ||
| final String exitUser = getAuthentication2().getName(); | ||
|
|
@@ -4585,11 +4644,10 @@ public void run() { | |
| try (ACLContext ctx = ACL.as2(ACL.SYSTEM2)) { | ||
|
|
||
| // Wait 'til we have no active executors. | ||
| doQuietDown(true, 0, null); | ||
|
|
||
| doQuietDown(true, 0, message, true); | ||
| // Make sure isQuietingDown is still true. | ||
| if (isQuietingDown()) { | ||
| servletContext.setAttribute("app", new HudsonIsRestarting()); | ||
| servletContext.setAttribute("app", new HudsonIsRestarting(true)); | ||
| // give some time for the browser to load the "reloading" page | ||
| lifecycle.onStatusUpdate("Restart in 10 seconds"); | ||
| Thread.sleep(TimeUnit.SECONDS.toMillis(10)); | ||
|
|
@@ -5761,16 +5819,31 @@ private static void _setJenkinsJVM(boolean jenkinsJVM) { | |
| } | ||
|
|
||
| private static final class QuietDownInfo { | ||
|
|
||
| @CheckForNull | ||
| final String reason; | ||
| final String message; | ||
|
|
||
| private boolean safeRestart; | ||
|
|
||
| QuietDownInfo() { | ||
| this(null); | ||
| this(null, false); | ||
| } | ||
|
|
||
| QuietDownInfo(final String message) { | ||
| this(message, false); | ||
| } | ||
|
|
||
| QuietDownInfo(final String message, final boolean safeRestart) { | ||
| this.message = message; | ||
| this.safeRestart = safeRestart; | ||
| } | ||
|
|
||
|
|
||
| boolean isSafeRestart() { | ||
| return safeRestart; | ||
| } | ||
|
|
||
| QuietDownInfo(final String reason) { | ||
| this.reason = reason; | ||
| void setSafeRestart(boolean safeRestart) { | ||
| this.safeRestart = safeRestart; | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -56,6 +56,17 @@ THE SOFTWARE. | |
| <p class="restarting"> | ||
| ${%Your browser will reload automatically when Jenkins is ready.} | ||
| </p> | ||
| <j:if test="${it.safeRestart}"> | ||
| <div class="safe-restarting"> | ||
| <p> | ||
| <strong>${%Safe Restart}</strong> | ||
| </p> | ||
| <p> | ||
| ${%Builds on agents can usually continue.} | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it worth pointing out that this applies only to Pipeline builds, not to other project types?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good idea! What would you think about that? |
||
| </p> | ||
| </div> | ||
| </j:if> | ||
|
|
||
| </div> | ||
| </div> | ||
| </div> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,2 +1,6 @@ | ||
| Please\ wait\ while\ Jenkins\ is\ restarting=Jenkins wird neu gestartet. Bitte warten | ||
| Please\ wait\ while\ Jenkins\ is\ restarting=Jenkins wird neu gestartet. Bitte warten. | ||
| Your\ browser\ will\ reload\ automatically\ when\ Jenkins\ is\ ready.=Der Webbrowser wird diese Seite automatisch neu laden, sobald Jenkins hochgefahren ist. | ||
| Safe\ Restart=Sicherer Neustart | ||
| Builds\ on\ agents\ can\ usually\ continue.=Builds auf Agenten laufen in der Regel weiter. | ||
| Restart=Neustarten | ||
| Cancel=Abbrechen |
Uh oh!
There was an error while loading. Please reload this page.