/* * The MIT License * * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Red Hat, Inc., Seiji Sogabe, Stephen Connolly, Thomas J. Black, Tom Huybrechts * * 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.model; import hudson.EnvVars; import hudson.Util; import hudson.model.Descriptor.FormException; import hudson.node_monitors.NodeMonitor; import hudson.remoting.Channel; import hudson.remoting.VirtualChannel; import hudson.remoting.Callable; import hudson.security.ACL; import hudson.security.AccessControlled; import hudson.security.Permission; import hudson.security.PermissionGroup; import hudson.slaves.ComputerLauncher; import hudson.slaves.RetentionStrategy; import hudson.tasks.BuildWrapper; import hudson.tasks.Publisher; import hudson.util.DaemonThreadFactory; import hudson.util.ExceptionCatchingThreadFactory; import hudson.util.RemotingDiagnostics; import hudson.util.RunList; import hudson.util.Futures; import org.kohsuke.stapler.StaplerRequest; import org.kohsuke.stapler.StaplerResponse; import org.kohsuke.stapler.export.Exported; import org.kohsuke.stapler.export.ExportedBean; import javax.servlet.ServletException; import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Enumeration; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.logging.LogRecord; import java.util.logging.Level; import java.util.logging.Logger; import java.nio.charset.Charset; import java.net.InetAddress; import java.net.NetworkInterface; /** * Represents the running state of a remote computer that holds {@link Executor}s. * *
* {@link Executor}s on one {@link Computer} are transparently interchangeable * (that is the definition of {@link Computer}.) * *
* This object is related to {@link Node} but they have some significant difference. * {@link Computer} primarily works as a holder of {@link Executor}s, so * if a {@link Node} is configured (probably temporarily) with 0 executors, * you won't have a {@link Computer} object for it. * * Also, even if you remove a {@link Node}, it takes time for the corresponding * {@link Computer} to be removed, if some builds are already in progress on that * node. Or when the node configuration is changed, unaffected {@link Computer} object * remains intact, while all the {@link Node} objects will go away. * *
* This object also serves UI (since {@link Node} is an interface and can't have
* related side pages.)
*
* @author Kohsuke Kawaguchi
*/
@ExportedBean
public abstract class Computer extends AbstractModelObject implements AccessControlled, ExecutorListener {
private final CopyOnWriteArrayList
* If already connected or if this computer doesn't support proactive launching, no-op.
* This method may return immediately
* while the launch operation happens asynchronously.
*
* @see #disconnect()
*
* @param forceReconnect
* If true and a connect activity is already in progress, it will be cancelled and
* the new one will be started. If false, and a connect activity is already in progress, this method
* will do nothing and just return the pending connection operation.
* @return
* A {@link Future} representing pending completion of the task. The 'completion' includes
* both a successful completion and a non-successful completion (such distinction typically doesn't
* make much sense because as soon as {@link Computer} is connected it can be disconnected by some other threads.)
*/
public abstract Future> connect(boolean forceReconnect);
/**
* Disconnect this computer.
*
* If this is the master, no-op. This method may return immediately
* while the launch operation happens asynchronously.
*
* @return
* {@link Future} to track the asynchronous disconnect operation.
* @see #connect(boolean)
*/
public Future> disconnect() { return Futures.precomputed(null); }
/**
* Number of {@link Executor}s that are configured for this computer.
*
*
* When this value is decreased, it is temporarily possible
* for {@link #executors} to have a larger number than this.
*/
// ugly name to let EL access this
@Exported
public int getNumExecutors() {
return numExecutors;
}
/**
* Returns {@link Node#getNodeName() the name of the node}.
*/
public String getName() {
return nodeName;
}
/**
* Returns the {@link Node} that this computer represents.
*/
public Node getNode() {
if(nodeName==null)
return Hudson.getInstance();
return Hudson.getInstance().getNode(nodeName);
}
public LoadStatistics getLoadStatistics() {
return getNode().getSelfLabel().loadStatistics;
}
/**
* {@inheritDoc}
*/
public void taskAccepted(Executor executor, Queue.Task task) {
// dummy implementation
}
/**
* {@inheritDoc}
*/
public void taskCompleted(Executor executor, Queue.Task task, long durationMS) {
// dummy implementation
}
/**
* {@inheritDoc}
*/
public void taskCompletedWithProblems(Executor executor, Queue.Task task, long durationMS, Throwable problems) {
// dummy implementation
}
@Exported
public boolean isOffline() {
return temporarilyOffline || getChannel()==null;
}
public final boolean isOnline() {
return !isOffline();
}
/**
* This method is called to determine whether manual launching of the slave is allowed at this point in time.
* @return {@code true} if manual launching of the slave is allowed at this point in time.
*/
@Exported
public boolean isManualLaunchAllowed() {
return getRetentionStrategy().isManualLaunchAllowed(this);
}
/**
* Is a {@link #connect(boolean)} operation in progress?
*/
public abstract boolean isConnecting();
/**
* Returns true if this computer is supposed to be launched via JNLP.
* @deprecated see {@linkplain #isLaunchSupported()} and {@linkplain ComputerLauncher}
*/
@Exported
@Deprecated
public boolean isJnlpAgent() {
return false;
}
/**
* Returns true if this computer can be launched by Hudson proactively and automatically.
*
*
* For example, JNLP slaves return {@code false} from this, because the launch process
* needs to be initiated from the slave side.
*/
@Exported
public boolean isLaunchSupported() {
return true;
}
/**
* Returns true if this node is marked temporarily offline by the user.
*
*
* In contrast, {@link #isOffline()} represents the actual online/offline
* state. For example, this method may return false while {@link #isOffline()}
* returns true if the slave agent failed to launch.
*
* @deprecated
* You should almost always want {@link #isOffline()}.
* This method is marked as deprecated to warn people when they
* accidentally call this method.
*/
@Exported
public boolean isTemporarilyOffline() {
return temporarilyOffline;
}
public void setTemporarilyOffline(boolean temporarilyOffline) {
this.temporarilyOffline = temporarilyOffline;
Hudson.getInstance().getQueue().scheduleMaintenance();
}
@Exported
public String getIcon() {
if(isOffline())
return "computer-x.gif";
else
return "computer.gif";
}
public String getIconAltText() {
if(isOffline())
return "[offline]";
else
return "[online]";
}
@Exported
public String getDisplayName() {
return nodeName;
}
public String getCaption() {
return Messages.Computer_Caption(nodeName);
}
public String getUrl() {
return "computer/"+getDisplayName()+"/";
}
/**
* Returns projects that are tied on this node.
*/
public List
* If this computer is already idle, the return value will point to the
* time in the past since when this computer has been idle.
*
*
* If this computer is busy, the return value will point to the
* time in the future where this computer will be expected to become free.
*/
public final long getIdleStartMilliseconds() {
long firstIdle = Long.MIN_VALUE;
for (Executor e : executors) {
firstIdle = Math.max(firstIdle, e.getIdleStartMilliseconds());
}
return firstIdle;
}
/**
* Returns the time when this computer first became in demand.
*/
public final long getDemandStartMilliseconds() {
long firstDemand = Long.MAX_VALUE;
for (Queue.BuildableItem item : Hudson.getInstance().getQueue().getBuildableItems(this)) {
firstDemand = Math.min(item.buildableStartMilliseconds, firstDemand);
}
return firstDemand;
}
/**
* Called by {@link Executor} to kill excessive executors from this computer.
*/
/*package*/ synchronized void removeExecutor(Executor e) {
executors.remove(e);
if(executors.isEmpty())
Hudson.getInstance().removeComputer(this);
}
/**
* Interrupt all {@link Executor}s.
*/
public void interrupt() {
for (Executor e : executors) {
e.interrupt();
}
}
public String getSearchUrl() {
return "computer/"+nodeName;
}
/**
* {@link RetentionStrategy} associated with this computer.
*
* @return
* never null. This method return {@code RetentionStrategy super T>} where
* {@code T=this.getClass()}.
*/
public abstract RetentionStrategy getRetentionStrategy();
/**
* Expose monitoring data for the remote API.
*/
@Exported(inline=true)
public Map