diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml
index 7634f375c150..f7032327b8df 100644
--- a/.github/workflows/changelog.yml
+++ b/.github/workflows/changelog.yml
@@ -67,4 +67,8 @@ jobs:
GIT_COMMITTER_EMAIL: <86592549+jenkins-infra-changelog-generator[bot]@users.noreply.github.com>
run: |
wget --quiet https://raw.githubusercontent.com/jenkinsci/core-changelog-generator/master/generate-weekly-changelog.sh
+ # Create a Python virtual environment for pip install
+ # See https://github.com/jenkinsci/core-changelog-generator/issues/37
+ python3 -m venv venv
+ source venv/bin/activate
bash generate-weekly-changelog.sh
diff --git a/ath.sh b/ath.sh
index 7009d692b8d0..3326a2118260 100644
--- a/ath.sh
+++ b/ath.sh
@@ -6,7 +6,7 @@ set -o xtrace
cd "$(dirname "$0")"
# https://github.com/jenkinsci/acceptance-test-harness/releases
-export ATH_VERSION=6038.v190f938efc87
+export ATH_VERSION=6040.v72ed2f5b_59f6
if [[ $# -eq 0 ]]; then
export JDK=17
diff --git a/bom/pom.xml b/bom/pom.xml
index 0065b595ae9a..bd8699441c8b 100644
--- a/bom/pom.xml
+++ b/bom/pom.xml
@@ -62,7 +62,7 @@ THE SOFTWARE.
org.springframework
spring-framework-bom
- 6.1.13
+ 6.1.14
pom
import
diff --git a/core/src/main/java/hudson/logging/LogRecorder.java b/core/src/main/java/hudson/logging/LogRecorder.java
index c11c1bbfa57e..a4c42bdfcba7 100644
--- a/core/src/main/java/hudson/logging/LogRecorder.java
+++ b/core/src/main/java/hudson/logging/LogRecorder.java
@@ -44,6 +44,7 @@
import hudson.remoting.VirtualChannel;
import hudson.slaves.ComputerListener;
import hudson.util.CopyOnWriteList;
+import hudson.util.FormApply;
import hudson.util.FormValidation;
import hudson.util.HttpResponses;
import hudson.util.RingBufferLogHandler;
@@ -463,7 +464,7 @@ public synchronized void doConfigSubmit(StaplerRequest2 req, StaplerResponse2 rs
save();
if (oldFile != null) oldFile.delete();
- rsp.sendRedirect2(redirect);
+ FormApply.success(redirect).generateResponse(req, rsp, null);
}
@RequirePOST
diff --git a/core/src/main/java/hudson/model/Computer.java b/core/src/main/java/hudson/model/Computer.java
index 1047fe97000c..8c3d814c91fe 100644
--- a/core/src/main/java/hudson/model/Computer.java
+++ b/core/src/main/java/hudson/model/Computer.java
@@ -66,6 +66,7 @@
import hudson.util.DaemonThreadFactory;
import hudson.util.EditDistance;
import hudson.util.ExceptionCatchingThreadFactory;
+import hudson.util.FormApply;
import hudson.util.Futures;
import hudson.util.IOUtils;
import hudson.util.NamingThreadFactory;
@@ -179,11 +180,6 @@
private long connectTime = 0;
- /**
- * True if Jenkins shouldn't start new builds on this node.
- */
- private boolean temporarilyOffline;
-
/**
* {@link Node} object may be created and deleted independently
* from this object.
@@ -360,6 +356,13 @@ public AnnotatedLargeText getLogText() {
*/
@Exported
public OfflineCause getOfflineCause() {
+ var node = getNode();
+ if (node != null) {
+ var temporaryOfflineCause = node.getTemporaryOfflineCause();
+ if (temporaryOfflineCause != null) {
+ return temporaryOfflineCause;
+ }
+ }
return offlineCause;
}
@@ -371,6 +374,7 @@ public boolean hasOfflineCause() {
@Exported
@Override
public String getOfflineCauseReason() {
+ var offlineCause = getOfflineCause();
if (offlineCause == null) {
return "";
}
@@ -549,7 +553,7 @@ public void cliDisconnect(String cause) throws ExecutionException, InterruptedEx
@Deprecated
public void cliOffline(String cause) throws ExecutionException, InterruptedException {
checkPermission(DISCONNECT);
- setTemporarilyOffline(true, new ByCLI(cause));
+ setTemporaryOfflineCause(new ByCLI(cause));
}
/**
@@ -558,7 +562,7 @@ public void cliOffline(String cause) throws ExecutionException, InterruptedExcep
@Deprecated
public void cliOnline() throws ExecutionException, InterruptedException {
checkPermission(CONNECT);
- setTemporarilyOffline(false, null);
+ setTemporaryOfflineCause(null);
}
/**
@@ -620,7 +624,7 @@ public BuildTimelineWidget getTimeline() {
@Exported
@Override
public boolean isOffline() {
- return temporarilyOffline || getChannel() == null;
+ return isTemporarilyOffline() || getChannel() == null;
}
public final boolean isOnline() {
@@ -669,41 +673,64 @@ public boolean isLaunchSupported() {
@Exported
@Deprecated
public boolean isTemporarilyOffline() {
- return temporarilyOffline;
+ var node = getNode();
+ return node != null && node.isTemporarilyOffline();
}
/**
* @deprecated as of 1.320.
- * Use {@link #setTemporarilyOffline(boolean, OfflineCause)}
+ * Use {@link #setTemporaryOfflineCause(OfflineCause)}
*/
@Deprecated
public void setTemporarilyOffline(boolean temporarilyOffline) {
- setTemporarilyOffline(temporarilyOffline, null);
+ setTemporaryOfflineCause(temporarilyOffline ? new OfflineCause.LegacyOfflineCause() : null);
+ }
+
+ /**
+ * @deprecated
+ * Use {@link #setTemporaryOfflineCause(OfflineCause)} instead.
+ */
+ @Deprecated(since = "TODO")
+ public void setTemporarilyOffline(boolean temporarilyOffline, OfflineCause cause) {
+ if (cause == null) {
+ setTemporarilyOffline(temporarilyOffline);
+ } else {
+ setTemporaryOfflineCause(temporarilyOffline ? cause : null);
+ }
}
/**
* Marks the computer as temporarily offline. This retains the underlying
* {@link Channel} connection, but prevent builds from executing.
*
- * @param cause
- * If the first argument is true, specify the reason why the node is being put
- * offline.
+ * @param temporaryOfflineCause The reason why the node is being put offline.
+ * If null, this cancels the status
+ * @since TODO
*/
- public void setTemporarilyOffline(boolean temporarilyOffline, OfflineCause cause) {
- offlineCause = temporarilyOffline ? cause : null;
- this.temporarilyOffline = temporarilyOffline;
- Node node = getNode();
- if (node != null) {
- node.setTemporaryOfflineCause(offlineCause);
+ public void setTemporaryOfflineCause(@CheckForNull OfflineCause temporaryOfflineCause) {
+ var node = getNode();
+ if (node == null) {
+ throw new IllegalStateException("Can't set a temporary offline cause if the node has been removed");
}
- synchronized (statusChangeLock) {
- statusChangeLock.notifyAll();
+ node.setTemporaryOfflineCause(temporaryOfflineCause);
+ }
+
+ /**
+ * @since TODO
+ * @return If the node is temporarily offline, the reason why.
+ */
+ @SuppressWarnings("unused") // used by setOfflineCause.jelly
+ public String getTemporaryOfflineCauseReason() {
+ var node = getNode();
+ if (node == null) {
+ // Node was deleted; computer still exists
+ return null;
}
- if (temporarilyOffline) {
- Listeners.notify(ComputerListener.class, false, l -> l.onTemporarilyOffline(this, cause));
- } else {
- Listeners.notify(ComputerListener.class, false, l -> l.onTemporarilyOnline(this));
+ var cause = node.getTemporaryOfflineCause();
+ if (cause instanceof OfflineCause.UserCause userCause) {
+ return userCause.getMessage();
}
+ return cause != null ? cause.toString() : "";
}
@Exported
@@ -785,16 +812,6 @@ protected void setNode(Node node) {
this.nodeName = null;
setNumExecutors(node.getNumExecutors());
- if (this.temporarilyOffline) {
- // When we get a new node, push our current temp offline
- // status to it (as the status is not carried across
- // configuration changes that recreate the node).
- // Since this is also called the very first time this
- // Computer is created, avoid pushing an empty status
- // as that could overwrite any status that the Node
- // brought along from its persisted config data.
- node.setTemporaryOfflineCause(this.offlineCause);
- }
}
/**
@@ -1396,24 +1413,23 @@ public void doRssLatest(StaplerRequest2 req, StaplerResponse2 rsp) throws IOExce
@RequirePOST
public HttpResponse doToggleOffline(@QueryParameter String offlineMessage) throws IOException, ServletException {
- if (!temporarilyOffline) {
- checkPermission(DISCONNECT);
- offlineMessage = Util.fixEmptyAndTrim(offlineMessage);
- setTemporarilyOffline(!temporarilyOffline,
- new OfflineCause.UserCause(User.current(), offlineMessage));
- } else {
+ var node = getNode();
+ if (node == null) {
+ return HttpResponses.notFound();
+ }
+ if (node.isTemporarilyOffline()) {
checkPermission(CONNECT);
- setTemporarilyOffline(!temporarilyOffline, null);
+ setTemporaryOfflineCause(null);
+ return HttpResponses.redirectToDot();
+ } else {
+ return doChangeOfflineCause(offlineMessage);
}
- return HttpResponses.redirectToDot();
}
@RequirePOST
public HttpResponse doChangeOfflineCause(@QueryParameter String offlineMessage) throws IOException, ServletException {
checkPermission(DISCONNECT);
- offlineMessage = Util.fixEmptyAndTrim(offlineMessage);
- setTemporarilyOffline(true,
- new OfflineCause.UserCause(User.current(), offlineMessage));
+ setTemporaryOfflineCause(new OfflineCause.UserCause(User.current(), Util.fixEmptyAndTrim(offlineMessage)));
return HttpResponses.redirectToDot();
}
@@ -1512,7 +1528,7 @@ public void doConfigSubmit(StaplerRequest2 req, StaplerResponse2 rsp) throws IOE
}
// take the user back to the agent top page.
- rsp.sendRedirect2("../" + result.getNodeName() + '/');
+ FormApply.success("../" + result.getNodeName() + '/').generateResponse(req, rsp, null);
}
/**
diff --git a/core/src/main/java/hudson/model/Node.java b/core/src/main/java/hudson/model/Node.java
index 55cacd269133..d918b0f1db34 100644
--- a/core/src/main/java/hudson/model/Node.java
+++ b/core/src/main/java/hudson/model/Node.java
@@ -30,7 +30,6 @@
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import hudson.BulkChange;
-import hudson.Extension;
import hudson.ExtensionPoint;
import hudson.FilePath;
import hudson.FileSystemProvisioner;
@@ -69,6 +68,7 @@
import java.util.logging.Logger;
import jenkins.model.Jenkins;
import jenkins.model.Nodes;
+import jenkins.util.Listeners;
import jenkins.util.SystemProperties;
import jenkins.util.io.OnMaster;
import net.sf.json.JSONObject;
@@ -265,24 +265,13 @@ public void onLoad(Nodes parent, String name) {
}
/**
- * Let Nodes be aware of the lifecycle of their own {@link Computer}.
+ * @return true if this node has a temporary offline cause set.
*/
- @Extension
- public static class InternalComputerListener extends ComputerListener {
- @Override
- public void onOnline(Computer c, TaskListener listener) {
- Node node = c.getNode();
-
- // At startup, we need to restore any previously in-effect temp offline cause.
- // We wait until the computer is started rather than getting the data to it sooner
- // so that the normal computer start up processing works as expected.
- if (node != null && node.temporaryOfflineCause != null && node.temporaryOfflineCause != c.getOfflineCause()) {
- c.setTemporarilyOffline(true, node.temporaryOfflineCause);
- }
- }
+ boolean isTemporarilyOffline() {
+ return temporaryOfflineCause != null;
}
- private OfflineCause temporaryOfflineCause;
+ private volatile OfflineCause temporaryOfflineCause;
/**
* Enable a {@link Computer} to inform its node when it is taken
@@ -294,6 +283,11 @@ void setTemporaryOfflineCause(OfflineCause cause) {
temporaryOfflineCause = cause;
save();
}
+ if (temporaryOfflineCause != null) {
+ Listeners.notify(ComputerListener.class, false, l -> l.onTemporarilyOffline(toComputer(), temporaryOfflineCause));
+ } else {
+ Listeners.notify(ComputerListener.class, false, l -> l.onTemporarilyOnline(toComputer()));
+ }
} catch (java.io.IOException e) {
LOGGER.warning("Unable to complete save, temporary offline status will not be persisted: " + e.getMessage());
}
diff --git a/core/src/main/java/hudson/node_monitors/AbstractNodeMonitorDescriptor.java b/core/src/main/java/hudson/node_monitors/AbstractNodeMonitorDescriptor.java
index 7cd1c75abc8d..d49924508bee 100644
--- a/core/src/main/java/hudson/node_monitors/AbstractNodeMonitorDescriptor.java
+++ b/core/src/main/java/hudson/node_monitors/AbstractNodeMonitorDescriptor.java
@@ -233,7 +233,7 @@ public boolean isIgnored() {
*/
protected boolean markOnline(Computer c) {
if (isIgnored() || c.isOnline()) return false; // noop
- c.setTemporarilyOffline(false, null);
+ c.setTemporaryOfflineCause(null);
return true;
}
@@ -247,7 +247,7 @@ protected boolean markOnline(Computer c) {
protected boolean markOffline(Computer c, OfflineCause oc) {
if (isIgnored() || c.isTemporarilyOffline()) return false; // noop
- c.setTemporarilyOffline(true, oc);
+ c.setTemporaryOfflineCause(oc);
// notify the admin
MonitorMarkedNodeOffline no = AdministrativeMonitor.all().get(MonitorMarkedNodeOffline.class);
diff --git a/core/src/main/java/hudson/slaves/OfflineCause.java b/core/src/main/java/hudson/slaves/OfflineCause.java
index 556c0ebb0c53..2a267ef03a4a 100644
--- a/core/src/main/java/hudson/slaves/OfflineCause.java
+++ b/core/src/main/java/hudson/slaves/OfflineCause.java
@@ -33,6 +33,8 @@
import java.util.Date;
import jenkins.model.Jenkins;
import org.jvnet.localizer.Localizable;
+import org.kohsuke.accmod.Restricted;
+import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.stapler.export.Exported;
import org.kohsuke.stapler.export.ExportedBean;
@@ -71,6 +73,19 @@ public long getTimestamp() {
return new Date(timestamp);
}
+ /**
+ * @deprecated Only exists for backward compatibility.
+ * @see Computer#setTemporarilyOffline(boolean)
+ */
+ @Deprecated
+ @Restricted(NoExternalUse.class)
+ public static class LegacyOfflineCause extends OfflineCause {
+ @Exported(name = "description") @Override
+ public String toString() {
+ return "";
+ }
+ }
+
/**
* {@link OfflineCause} that renders a static text,
* but without any further UI.
@@ -136,15 +151,15 @@ public static class UserCause extends SimpleOfflineCause {
// null when unknown
private /*final*/ @CheckForNull String userId;
+ private final String message;
+
public UserCause(@CheckForNull User user, @CheckForNull String message) {
- this(
- user != null ? user.getId() : null,
- message != null ? " : " + message : ""
- );
+ this(user != null ? user.getId() : null, message);
}
private UserCause(String userId, String message) {
- super(hudson.slaves.Messages._SlaveComputer_DisconnectedBy(userId != null ? userId : Jenkins.ANONYMOUS2.getName(), message));
+ super(hudson.slaves.Messages._SlaveComputer_DisconnectedBy(userId != null ? userId : Jenkins.ANONYMOUS2.getName(), message != null ? " : " + message : ""));
+ this.message = message;
this.userId = userId;
}
@@ -155,6 +170,13 @@ public User getUser() {
;
}
+ /**
+ * @return the message that was provided when the computer was taken offline
+ */
+ public String getMessage() {
+ return message;
+ }
+
// Storing the User in a filed was a mistake, switch to userId
private Object readResolve() throws ObjectStreamException {
if (user != null) {
diff --git a/core/src/main/java/jenkins/model/Jenkins.java b/core/src/main/java/jenkins/model/Jenkins.java
index 90ffceefb456..dc2c7926f605 100644
--- a/core/src/main/java/jenkins/model/Jenkins.java
+++ b/core/src/main/java/jenkins/model/Jenkins.java
@@ -4159,7 +4159,7 @@ public synchronized void doConfigExecutorsSubmit(StaplerRequest2 req, StaplerRes
updateComputerList();
- rsp.sendRedirect(req.getContextPath() + '/' + toComputer().getUrl()); // back to the computer page
+ FormApply.success(req.getContextPath() + '/' + toComputer().getUrl()).generateResponse(req, rsp, null);
}
/**
diff --git a/core/src/main/resources/hudson/logging/LogRecorder/configure.jelly b/core/src/main/resources/hudson/logging/LogRecorder/configure.jelly
index b49ccc278858..ef7c6f3781b2 100644
--- a/core/src/main/resources/hudson/logging/LogRecorder/configure.jelly
+++ b/core/src/main/resources/hudson/logging/LogRecorder/configure.jelly
@@ -66,12 +66,8 @@ THE SOFTWARE.
-
-
-
-
+
-
diff --git a/core/src/main/resources/hudson/model/Computer/configure.jelly b/core/src/main/resources/hudson/model/Computer/configure.jelly
index bd33078013cd..ce76f5fb2d3b 100644
--- a/core/src/main/resources/hudson/model/Computer/configure.jelly
+++ b/core/src/main/resources/hudson/model/Computer/configure.jelly
@@ -46,15 +46,8 @@ THE SOFTWARE.
-
-
-
-
-
+
-
-
-
diff --git a/core/src/main/resources/hudson/model/Computer/setOfflineCause.jelly b/core/src/main/resources/hudson/model/Computer/setOfflineCause.jelly
index 587057a464e1..8bac3c7e27c7 100644
--- a/core/src/main/resources/hudson/model/Computer/setOfflineCause.jelly
+++ b/core/src/main/resources/hudson/model/Computer/setOfflineCause.jelly
@@ -34,7 +34,7 @@ THE SOFTWARE.
${%blurb}