diff --git a/core/src/main/java/hudson/Util.java b/core/src/main/java/hudson/Util.java index 0c2d721812733ea5e44ec3bc2d42dba5bc169a55..67932ee5bacdcd48a2adeb8713a67ac91c6b451f 100644 --- a/core/src/main/java/hudson/Util.java +++ b/core/src/main/java/hudson/Util.java @@ -949,6 +949,20 @@ public class Util { } } + /** + * Checks if the public method defined on the base type with the given arguments + * are overridden in the given derived type. + */ + public static boolean isOverridden(Class base, Class derived, String methodName, Class... types) { + // the rewriteHudsonWar method isn't overridden. + try { + return !base.getMethod(methodName, types).equals( + derived.getMethod(methodName,types)); + } catch (NoSuchMethodException e) { + throw new AssertionError(e); + } + } + public static final FastDateFormat XS_DATETIME_FORMATTER = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss'Z'",new SimpleTimeZone(0,"GMT")); // Note: RFC822 dates must not be localized! diff --git a/core/src/main/java/hudson/lifecycle/Lifecycle.java b/core/src/main/java/hudson/lifecycle/Lifecycle.java index bb84e58a0eab85e81f144401c566b28916ac257a..9f1ec2087db148539ad5353311a96ca6fe223f10 100644 --- a/core/src/main/java/hudson/lifecycle/Lifecycle.java +++ b/core/src/main/java/hudson/lifecycle/Lifecycle.java @@ -24,6 +24,7 @@ package hudson.lifecycle; import hudson.ExtensionPoint; +import hudson.Util; import hudson.model.Hudson; import java.io.File; @@ -131,16 +132,6 @@ public abstract class Lifecycle implements ExtensionPoint { return getHudsonWar()!=null; } - private boolean isOverridden(String methodName, Class... types) { - // the rewriteHudsonWar method isn't overridden. - try { - return !getClass().getMethod(methodName, types).equals( - Lifecycle.class.getMethod(methodName,types)); - } catch (NoSuchMethodException e) { - throw new AssertionError(e); - } - } - /** * If this life cycle supports a restart of Hudson, do so. * Otherwise, throw {@link UnsupportedOperationException}, @@ -162,7 +153,8 @@ public abstract class Lifecycle implements ExtensionPoint { * Can the {@link #restart()} method restart Hudson? */ public boolean canRestart() { - return isOverridden("restart"); + // the rewriteHudsonWar method isn't overridden. + return Util.isOverridden(Lifecycle.class,getClass(), "restart"); } private static final Logger LOGGER = Logger.getLogger(Lifecycle.class.getName()); diff --git a/core/src/main/java/hudson/model/Computer.java b/core/src/main/java/hudson/model/Computer.java index 81e5f091229cd8e9c6aa91657ddca3c0f2b4c4d4..f0318986824011b19e782d39903b4acca6443ffc 100644 --- a/core/src/main/java/hudson/model/Computer.java +++ b/core/src/main/java/hudson/model/Computer.java @@ -25,7 +25,6 @@ package hudson.model; import hudson.EnvVars; import hudson.Util; -import hudson.FilePath; import hudson.model.Descriptor.FormException; import hudson.node_monitors.NodeMonitor; import hudson.remoting.Channel; @@ -38,6 +37,7 @@ import hudson.security.PermissionGroup; import hudson.slaves.ComputerLauncher; import hudson.slaves.RetentionStrategy; import hudson.slaves.WorkspaceList; +import hudson.slaves.OfflineCause; import hudson.tasks.BuildWrapper; import hudson.tasks.Publisher; import hudson.util.DaemonThreadFactory; @@ -47,6 +47,7 @@ import hudson.util.RunList; import hudson.util.Futures; import org.kohsuke.stapler.StaplerRequest; import org.kohsuke.stapler.StaplerResponse; +import org.kohsuke.stapler.QueryParameter; import org.kohsuke.stapler.export.Exported; import org.kohsuke.stapler.export.ExportedBean; @@ -60,9 +61,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Enumeration; -import java.util.Set; -import java.util.HashSet; -import java.util.Collections; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -107,6 +105,11 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces private final CopyOnWriteArrayList oneOffExecutors = new CopyOnWriteArrayList(); private int numExecutors; + + /** + * Contains info about reason behind computer being offline. + */ + protected volatile OfflineCause offlineCause; private long connectTime = 0; @@ -167,6 +170,18 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces return getACL().hasPermission(permission); } + /** + * If the computer was offline (either temporarily or not), + * this method will return the cause. + * + * @return + * null if the system was put offline without given a cause. + */ + @Exported + public OfflineCause getOfflineCause() { + return offlineCause; + } + /** * Gets the channel that can be used to run a program on this computer. * @@ -261,15 +276,38 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces * If this is the master, no-op. This method may return immediately * while the launch operation happens asynchronously. * + * @param cause + * Object that identifies the reason the node was disconnected. + * * @return * {@link Future} to track the asynchronous disconnect operation. * @see #connect(boolean) + * @since 1.320 */ - public Future disconnect() { - connectTime=0; - return Futures.precomputed(null); + public Future disconnect(OfflineCause cause) { + offlineCause = cause; + if (Util.isOverridden(Computer.class,getClass(),"disconnect")) + return disconnect(); // legacy subtypes that extend disconnect(). + + connectTime=0; + return Futures.precomputed(null); } + /** + * Equivalent to {@code disconnect(null)} + * + * @deprecated as of 1.320. + * Use {@link #disconnect(OfflineCause)} and specify the cause. + */ + public Future disconnect() { + if (Util.isOverridden(Computer.class,getClass(),"disconnect",OfflineCause.class)) + // if the subtype already derives disconnect(OfflineCause), delegate to it + return disconnect(null); + + connectTime=0; + return Futures.precomputed(null); + } + /** * Number of {@link Executor}s that are configured for this computer. * @@ -388,7 +426,24 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces return temporarilyOffline; } + /** + * @deprecated as of 1.320. + * Use {@link #setTemporarilyOffline(boolean, OfflineCause)} + */ public void setTemporarilyOffline(boolean temporarilyOffline) { + setTemporarilyOffline(temporarilyOffline,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. + */ + public void setTemporarilyOffline(boolean temporarilyOffline, OfflineCause cause) { + offlineCause = temporarilyOffline ? cause : null; this.temporarilyOffline = temporarilyOffline; Hudson.getInstance().getQueue().scheduleMaintenance(); } @@ -739,10 +794,17 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces runs.newBuilds(), Run.FEED_ADAPTER, req, rsp ); } - public void doToggleOffline( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException { + public void doToggleOffline( StaplerRequest req, StaplerResponse rsp, @QueryParameter String offlineMessage) throws IOException, ServletException { checkPermission(Hudson.ADMINISTER); - - setTemporarilyOffline(!temporarilyOffline); + if(!temporarilyOffline) { + offlineMessage = Util.fixEmptyAndTrim(offlineMessage); + setTemporarilyOffline(!temporarilyOffline, + OfflineCause.create(hudson.slaves.Messages._SlaveComputer_DisconnectedBy( + Hudson.getAuthentication().getName(), + offlineMessage!=null ? " : " + offlineMessage : ""))); + } else { + setTemporarilyOffline(!temporarilyOffline,null); + } rsp.forwardToPreviousPage(req); } diff --git a/core/src/main/java/hudson/node_monitors/AbstractNodeMonitorDescriptor.java b/core/src/main/java/hudson/node_monitors/AbstractNodeMonitorDescriptor.java index d049ee97a7292f47a172ff68eaaa2858407ff0bc..f685bbb8c05ed75e184c44beaaf2e3c103c46305 100644 --- a/core/src/main/java/hudson/node_monitors/AbstractNodeMonitorDescriptor.java +++ b/core/src/main/java/hudson/node_monitors/AbstractNodeMonitorDescriptor.java @@ -30,6 +30,7 @@ import hudson.model.ComputerSet; import hudson.model.AdministrativeMonitor; import hudson.triggers.Trigger; import hudson.triggers.SafeTimerTask; +import hudson.slaves.OfflineCause; import java.io.IOException; import java.util.Date; @@ -135,11 +136,10 @@ public abstract class AbstractNodeMonitorDescriptor extends Descriptor extends DescriptorViews + *

+ * {@link OfflineCause} must have cause.jelly that renders a cause + * into HTML. This is used to tell users why the node is put offline. + * This view should render a block element like DIV. + * + * @author Kohsuke Kawaguchi + * @since 1.320 + */ +@ExportedBean +public abstract class OfflineCause { + /** + * {@link OfflineCause} that renders a static text, + * but without any further UI. + */ + public static class SimpleOfflineCause extends OfflineCause { + public final Localizable description; + + private SimpleOfflineCause(Localizable description) { + this.description = description; + } + + @Exported(name="description") + public String toString() { + return description.toString(); + } + } + + public static OfflineCause create(Localizable d) { + if (d==null) return null; + return new SimpleOfflineCause(d); + } + + /** + * Caused by unexpected channel termination. + */ + public static class ChannelTermination extends OfflineCause { + @Exported + public final Exception cause; + + public ChannelTermination(Exception cause) { + this.cause = cause; + } + + public String getShortDescription() { + return cause.toString(); + } + } +} diff --git a/core/src/main/java/hudson/slaves/SimpleScheduledRetentionStrategy.java b/core/src/main/java/hudson/slaves/SimpleScheduledRetentionStrategy.java index 9a5d213e101b45eefa63d2c27f2ea9dd1f5cc5de..0aeb5b916f57120bdbe478acd9d446bc34da94c4 100644 --- a/core/src/main/java/hudson/slaves/SimpleScheduledRetentionStrategy.java +++ b/core/src/main/java/hudson/slaves/SimpleScheduledRetentionStrategy.java @@ -40,6 +40,7 @@ import java.util.GregorianCalendar; import java.util.concurrent.ExecutionException; import java.util.logging.Level; import java.util.logging.Logger; +import static java.util.logging.Level.INFO; /** * {@link RetentionStrategy} that controls the slave based on a schedule. @@ -166,7 +167,7 @@ public class SimpleScheduledRetentionStrategy extends RetentionStrategy disconnect() { - super.disconnect(); + public Future disconnect(OfflineCause cause) { + super.disconnect(cause); return Computer.threadPoolForRemoting.submit(new Runnable() { public void run() { // do this on another thread so that any lengthy disconnect operation diff --git a/core/src/main/resources/hudson/model/Computer/index.jelly b/core/src/main/resources/hudson/model/Computer/index.jelly index 09bb00f5f8ad04649d51232e4e3b58245c71c5af..4a7030942cd6a2e2ff30fafcd0a4ea9eb9fff1b2 100644 --- a/core/src/main/resources/hudson/model/Computer/index.jelly +++ b/core/src/main/resources/hudson/model/Computer/index.jelly @@ -29,12 +29,13 @@ THE SOFTWARE.

-
+ +
@@ -48,6 +49,10 @@ THE SOFTWARE. + + + + diff --git a/core/src/main/resources/hudson/node_monitors/DiskSpaceMonitorDescriptor/DiskSpace/cause.jelly b/core/src/main/resources/hudson/node_monitors/DiskSpaceMonitorDescriptor/DiskSpace/cause.jelly new file mode 100644 index 0000000000000000000000000000000000000000..e9fe102b859c56b7e3e16ca9edec8ce6d97b0d57 --- /dev/null +++ b/core/src/main/resources/hudson/node_monitors/DiskSpaceMonitorDescriptor/DiskSpace/cause.jelly @@ -0,0 +1,31 @@ + + + +
+ + ${%blurb(it.gbLeft)} + +
+
\ No newline at end of file diff --git a/core/src/main/resources/hudson/node_monitors/DiskSpaceMonitorDescriptor/DiskSpace/cause.properties b/core/src/main/resources/hudson/node_monitors/DiskSpaceMonitorDescriptor/DiskSpace/cause.properties new file mode 100644 index 0000000000000000000000000000000000000000..3320b5136a43867b463074279ca487e8abf35547 --- /dev/null +++ b/core/src/main/resources/hudson/node_monitors/DiskSpaceMonitorDescriptor/DiskSpace/cause.properties @@ -0,0 +1 @@ +blurb=Disk space is too low. Only {0}GB left. \ No newline at end of file diff --git a/core/src/main/resources/hudson/node_monitors/ResponseTimeMonitor/Data/cause.jelly b/core/src/main/resources/hudson/node_monitors/ResponseTimeMonitor/Data/cause.jelly new file mode 100644 index 0000000000000000000000000000000000000000..c5b6db5a0e7822c825f89787679155edd93aab8f --- /dev/null +++ b/core/src/main/resources/hudson/node_monitors/ResponseTimeMonitor/Data/cause.jelly @@ -0,0 +1,31 @@ + + + +
+ + ${%Ping response time is too long or timed out.} + +
+
\ No newline at end of file diff --git a/core/src/main/resources/hudson/slaves/Messages.properties b/core/src/main/resources/hudson/slaves/Messages.properties index 70ec20fefc420b38cdc40944cffe3e3618ba4e10..69c1826a01667a51b17a54ac9699b1951dbe1179 100644 --- a/core/src/main/resources/hudson/slaves/Messages.properties +++ b/core/src/main/resources/hudson/slaves/Messages.properties @@ -28,5 +28,7 @@ ComputerLauncher.unexpectedError=Unexpected error in launching a slave. This is ComputerLauncher.abortedLaunch=Launching slave process aborted. CommandLauncher.NoLaunchCommand=No launch command specified DumbSlave.displayName=Dumb Slave +SimpleScheduledRetentionStrategy.FinishedUpTime=Computer has finished its scheduled uptime SimpleScheduledRetentionStrategy.displayName=Take this slave on-line according to a schedule EnvironmentVariablesNodeProperty.displayName=Environment variables +SlaveComputer.DisconnectedBy=Disconnected by {0}{1} diff --git a/core/src/main/resources/hudson/slaves/OfflineCause/ChannelTermination/cause.jelly b/core/src/main/resources/hudson/slaves/OfflineCause/ChannelTermination/cause.jelly new file mode 100644 index 0000000000000000000000000000000000000000..34435bdbce4c156b971670f1566d1b672ba24250 --- /dev/null +++ b/core/src/main/resources/hudson/slaves/OfflineCause/ChannelTermination/cause.jelly @@ -0,0 +1,27 @@ + + + +
${h.Throwable(it.exception)}
+
\ No newline at end of file diff --git a/core/src/main/resources/hudson/slaves/OfflineCause/SimpleOfflineCause/cause.jelly b/core/src/main/resources/hudson/slaves/OfflineCause/SimpleOfflineCause/cause.jelly new file mode 100644 index 0000000000000000000000000000000000000000..964c0474b9152e03b0b631b296427c96c5cc8b39 --- /dev/null +++ b/core/src/main/resources/hudson/slaves/OfflineCause/SimpleOfflineCause/cause.jelly @@ -0,0 +1,27 @@ + + + +
${it}
+
\ No newline at end of file diff --git a/core/src/main/resources/hudson/slaves/SlaveComputer/disconnect.jelly b/core/src/main/resources/hudson/slaves/SlaveComputer/disconnect.jelly index a1c2b02facf86096529e7c3b5ed54ff7423b7328..d7ab7f72c282c0f97021d34361ef59dec3437f67 100644 --- a/core/src/main/resources/hudson/slaves/SlaveComputer/disconnect.jelly +++ b/core/src/main/resources/hudson/slaves/SlaveComputer/disconnect.jelly @@ -27,9 +27,10 @@ THE SOFTWARE. -
+ ${%Are you sure about disconnecting?} +
diff --git a/remoting/src/main/java/hudson/remoting/Channel.java b/remoting/src/main/java/hudson/remoting/Channel.java index a68697f234bea353fe8a0fb071a2daf9650fa61e..0763815046441dd321953cc8b25fd7dfd9e87001 100644 --- a/remoting/src/main/java/hudson/remoting/Channel.java +++ b/remoting/src/main/java/hudson/remoting/Channel.java @@ -355,6 +355,7 @@ public class Channel implements VirtualChannel, IChannel { * @param cause * if the channel is closed abnormally, this parameter * represents an exception that has triggered it. + * Otherwise null. */ public void onClosed(Channel channel, IOException cause) {} } @@ -571,6 +572,7 @@ public class Channel implements VirtualChannel, IChannel { } finally { notifyAll(); + if (e instanceof OrderlyShutdown) e = null; for (Listener l : listeners.toArray(new Listener[listeners.size()])) l.onClosed(this,e); }