提交 d6522918 编写于 作者: K kohsuke

[FIXED HUDSON-2431] added OfflineCause class to keep track of why a node is put offline.

git-svn-id: https://hudson.dev.java.net/svn/hudson/trunk/hudson/main@20558 71c3de6d-444a-0410-be80-ed276b4c234a
上级 ba3a27a4
......@@ -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!
......
......@@ -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());
......
......@@ -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<OneOffExecutor> oneOffExecutors = new CopyOnWriteArrayList<OneOffExecutor>();
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);
}
......
......@@ -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<T> extends Descriptor<NodeMo
* if the node was actually taken offline by this act (as opposed to us deciding not to do it,
* or the computer already marked offline.)
*/
protected boolean markOffline(Computer c) {
protected boolean markOffline(Computer c, OfflineCause oc) {
if(isIgnored() || c.isTemporarilyOffline()) return false; // noop
// TODO: define a mechanism to leave a note on this computer so that people know why we took it offline
c.setTemporarilyOffline(true);
c.setTemporarilyOffline(true, oc);
// notify the admin
MonitorMarkedNodeOffline no = AdministrativeMonitor.all().get(MonitorMarkedNodeOffline.class);
......@@ -148,6 +148,14 @@ public abstract class AbstractNodeMonitorDescriptor<T> extends Descriptor<NodeMo
return true;
}
/**
* @deprecated as of 1.320
* Use {@link #markOffline(Computer, OfflineCause)} to specify the cause.
*/
protected boolean markOffline(Computer c) {
return markOffline(c,null);
}
/**
* @see NodeMonitor#triggerUpdate()
*/
......
......@@ -27,6 +27,7 @@ import hudson.FilePath.FileCallable;
import hudson.model.Computer;
import hudson.remoting.VirtualChannel;
import hudson.Util;
import hudson.slaves.OfflineCause;
import hudson.node_monitors.DiskSpaceMonitorDescriptor.DiskSpace;
import org.jvnet.animal_sniffer.IgnoreJRERequirement;
......@@ -36,6 +37,7 @@ import java.io.Serializable;
import java.util.logging.Logger;
import java.math.BigDecimal;
import org.kohsuke.stapler.export.ExportedBean;
import org.kohsuke.stapler.export.Exported;
/**
* {@link AbstractNodeMonitorDescriptor} for {@link NodeMonitor} that checks a free disk space of some directory.
......@@ -47,7 +49,8 @@ import org.kohsuke.stapler.export.ExportedBean;
* Value object that represents the disk space.
*/
@ExportedBean
public static final class DiskSpace implements Serializable {
public static final class DiskSpace extends OfflineCause implements Serializable {
@Exported
public final long size;
public DiskSpace(long size) {
......@@ -59,6 +62,17 @@ import org.kohsuke.stapler.export.ExportedBean;
return String.valueOf(size);
}
/**
* Gets GB left.
*/
public String getGbLeft() {
long space = size;
space/=1024L; // convert to KB
space/=1024L; // convert to MB
return new BigDecimal(space).scaleByPowerOfTen(-3).toPlainString();
}
/**
* Returns the HTML representation of the space.
*/
......@@ -83,7 +97,7 @@ import org.kohsuke.stapler.export.ExportedBean;
protected DiskSpace monitor(Computer c) throws IOException, InterruptedException {
DiskSpace size = getFreeSpace(c);
if(size!=null && !size.moreThanGB() && markOffline(c))
if(size!=null && !size.moreThanGB() && markOffline(c,size))
LOGGER.warning(Messages.DiskSpaceMonitor_MarkedOffline(c.getName()));
return size;
}
......
......@@ -25,6 +25,7 @@ package hudson.node_monitors;
import hudson.Util;
import hudson.Extension;
import hudson.slaves.OfflineCause;
import hudson.model.Computer;
import hudson.remoting.Callable;
import hudson.remoting.Future;
......@@ -66,7 +67,7 @@ public class ResponseTimeMonitor extends NodeMonitor {
d = new Data(old,-1L);
}
if(d.hasTooManyTimeouts() && markOffline(c))
if(d.hasTooManyTimeouts() && markOffline(c,d))
LOGGER.warning(Messages.ResponseTimeMonitor_MarkedOffline(c.getName()));
return d;
}
......@@ -84,7 +85,7 @@ public class ResponseTimeMonitor extends NodeMonitor {
* Immutable representation of the monitoring data.
*/
@ExportedBean
public static final class Data {
public static final class Data extends OfflineCause {
/**
* Record of the past 5 times. -1 if time out. Otherwise in milliseconds.
* Old ones first.
......
/*
* The MIT License
*
* Copyright (c) 2004-2009, Sun Microsystems, Inc.
*
* 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 hudson.slaves;
import hudson.model.Computer;
import org.jvnet.localizer.Localizable;
import org.kohsuke.stapler.export.ExportedBean;
import org.kohsuke.stapler.export.Exported;
/**
* Represents a cause that puts a {@linkplain Computer#isOffline() computer offline}.
*
* <h2>Views</h2>
* <p>
* {@link OfflineCause} must have <tt>cause.jelly</tt> 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();
}
}
}
......@@ -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<SlaveCom
LOGGER.log(Level.FINE, "Checking computer {0} against schedule. online = {1}, shouldBeOnline = {2}",
new Object[]{c.getName(), c.isOnline(), shouldBeOnline});
if (shouldBeOnline && c.isOffline()) {
LOGGER.log(Level.INFO, "Trying to launch computer {0} as schedule says it should be on-line at "
LOGGER.log(INFO, "Trying to launch computer {0} as schedule says it should be on-line at "
+ "this point in time", new Object[]{c.getName()});
if (c.isLaunchSupported()) {
Computer.threadPoolForRemoting.submit(new Runnable() {
......@@ -174,10 +175,10 @@ public class SimpleScheduledRetentionStrategy extends RetentionStrategy<SlaveCom
try {
c.connect(true).get();
if (c.isOnline()) {
LOGGER.log(Level.INFO, "Launched computer {0} per schedule", new Object[]{c.getName()});
LOGGER.log(INFO, "Launched computer {0} per schedule", new Object[]{c.getName()});
}
if (keepUpWhenActive && c.isOnline() && !c.isAcceptingTasks()) {
LOGGER.log(Level.INFO,
LOGGER.log(INFO,
"Enabling new jobs for computer {0} as it has started its scheduled uptime",
new Object[]{c.getName()});
c.setAcceptingTasks(true);
......@@ -192,23 +193,23 @@ public class SimpleScheduledRetentionStrategy extends RetentionStrategy<SlaveCom
if (keepUpWhenActive) {
if (!c.isIdle() && c.isAcceptingTasks()) {
c.setAcceptingTasks(false);
LOGGER.log(Level.INFO,
LOGGER.log(INFO,
"Disabling new jobs for computer {0} as it has finished its scheduled uptime",
new Object[]{c.getName()});
return 1;
} else if (c.isIdle() && c.isAcceptingTasks()) {
LOGGER.log(Level.INFO, "Disconnecting computer {0} as it has finished its scheduled uptime",
LOGGER.log(INFO, "Disconnecting computer {0} as it has finished its scheduled uptime",
new Object[]{c.getName()});
c.disconnect();
c.disconnect(OfflineCause.create(Messages._SimpleScheduledRetentionStrategy_FinishedUpTime()));
} else if (c.isIdle() && !c.isAcceptingTasks()) {
LOGGER.log(Level.INFO, "Disconnecting computer {0} as it has finished all jobs running when "
LOGGER.log(INFO, "Disconnecting computer {0} as it has finished all jobs running when "
+ "it completed its scheduled uptime", new Object[]{c.getName()});
c.disconnect();
c.disconnect(OfflineCause.create(Messages._SimpleScheduledRetentionStrategy_FinishedUpTime()));
}
} else {
LOGGER.log(Level.INFO, "Disconnecting computer {0} as it has finished its scheduled uptime",
LOGGER.log(INFO, "Disconnecting computer {0} as it has finished its scheduled uptime",
new Object[]{c.getName()});
c.disconnect();
c.disconnect(OfflineCause.create(Messages._SimpleScheduledRetentionStrategy_FinishedUpTime()));
}
}
return 1;
......
......@@ -36,6 +36,7 @@ import hudson.lifecycle.WindowsSlaveInstaller;
import hudson.Util;
import hudson.AbortException;
import static hudson.slaves.SlaveComputer.LogHolder.SLAVE_LOG_HANDLER;
import hudson.slaves.OfflineCause.ChannelTermination;
import java.io.File;
import java.io.OutputStream;
......@@ -56,6 +57,9 @@ import java.security.Security;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.HttpRedirect;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletResponse;
......@@ -281,6 +285,7 @@ public class SlaveComputer extends Computer {
channel.addListener(new Channel.Listener() {
public void onClosed(Channel c,IOException cause) {
SlaveComputer.this.channel = null;
offlineCause = new ChannelTermination(cause);
launcher.afterDisconnect(SlaveComputer.this, taskListener);
}
});
......@@ -302,6 +307,8 @@ public class SlaveComputer extends Computer {
for (ComputerListener cl : ComputerListener.all())
cl.preOnline(this,channel,root,taskListener);
offlineCause = null;
// update the data structure atomically to prevent others from seeing a channel that's not properly initialized yet
synchronized(channelLock) {
if(this.channel!=null) {
......@@ -344,15 +351,22 @@ public class SlaveComputer extends Computer {
});
}
public void doDoDisconnect(StaplerResponse rsp) throws IOException, ServletException {
checkPermission(Hudson.ADMINISTER);
disconnect();
rsp.sendRedirect(".");
public HttpResponse doDoDisconnect(@QueryParameter String offlineMessage) throws IOException, ServletException {
if (channel!=null) {
//does nothing in case computer is already disconnected
checkPermission(Hudson.ADMINISTER);
offlineMessage = Util.fixEmptyAndTrim(offlineMessage);
disconnect(OfflineCause.create(Messages._SlaveComputer_DisconnectedBy(
Hudson.getAuthentication().getName(),
offlineMessage!=null ? " : " + offlineMessage : "")
));
}
return new HttpRedirect(".");
}
@Override
public Future<?> 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
......
......@@ -29,12 +29,13 @@ THE SOFTWARE.
<!-- temporarily offline switch -->
<l:isAdmin>
<div style="float:right">
<form method="get" action="toggleOffline">
<form method="post" action="toggleOffline">
<j:if test="${it.temporarilyOffline}">
<f:submit value="${%submit.temporarilyOffline}" />
</j:if>
<j:if test="${!it.temporarilyOffline}">
<f:submit value="${%submit.not.temporarilyOffline}" />
<br/><input name="offlineMessage" size="40"/>
</j:if>
</form>
</div>
......@@ -48,6 +49,10 @@ THE SOFTWARE.
</j:if>
</h1>
<j:if test="${it.offlineCause!=null}">
<st:include it="${it.offlineCause}" page="cause.jelly" />
</j:if>
<j:if test="${it.manualLaunchAllowed}">
<st:include from="${it.launcher}" page="main.jelly" optional="true"/>
</j:if>
......
<!--
The MIT License
Copyright (c) 2004-2009, Sun Microsystems, Inc.
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.
-->
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<div>
<span class="error">
${%blurb(it.gbLeft)}
</span>
</div>
</j:jelly>
\ No newline at end of file
blurb=Disk space is too low. Only {0}GB left.
\ No newline at end of file
<!--
The MIT License
Copyright (c) 2004-2009, Sun Microsystems, Inc.
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.
-->
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<div>
<span class="error">
${%Ping response time is too long or timed out.}
</span>
</div>
</j:jelly>
\ No newline at end of file
......@@ -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}
<!--
The MIT License
Copyright (c) 2004-2009, Sun Microsystems, Inc.
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.
-->
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<pre>${h.Throwable(it.exception)}</pre>
</j:jelly>
\ No newline at end of file
<!--
The MIT License
Copyright (c) 2004-2009, Sun Microsystems, Inc.
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.
-->
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<div>${it}</div>
</j:jelly>
\ No newline at end of file
......@@ -27,9 +27,10 @@ THE SOFTWARE.
<l:layout title="${it.displayName} ${%disconnect}">
<st:include page="sidepanel.jelly" />
<l:main-panel>
<form method="get" action="doDisconnect">
<form method="post" action="doDisconnect">
${%Are you sure about disconnecting?}
<f:submit value="${%Yes}" />
<f:textarea name="offlineMessage" />
</form>
</l:main-panel>
</l:layout>
......
......@@ -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);
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册