package hudson.model; import hudson.EnvVars; import hudson.remoting.Channel; import hudson.remoting.VirtualChannel; import hudson.tasks.BuildWrapper; import hudson.tasks.Publisher; import hudson.util.DaemonThreadFactory; import hudson.util.RemotingDiagnostics; import hudson.util.RunList; import org.kohsuke.stapler.StaplerRequest; import org.kohsuke.stapler.StaplerResponse; import javax.servlet.ServletException; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.logging.LogRecord; import org.kohsuke.stapler.export.Exported; import org.kohsuke.stapler.export.ExportedBean; /** * Represents a set of {@link Executor}s on the same computer. * *

* {@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. * *

* This object also serves UI (since {@link Node} is an interface and can't have * related side pages.) * * @author Kohsuke Kawaguchi */ @ExportedBean(defaultVisibility=2) public abstract class Computer extends AbstractModelObject { private final CopyOnWriteArrayList executors = new CopyOnWriteArrayList(); private int numExecutors; /** * True if Hudson shouldn't start new builds on this node. */ private boolean temporarilyOffline; /** * {@link Node} object may be created and deleted independently * from this object. */ protected String nodeName; public Computer(Node node) { assert node.getNumExecutors()!=0 : "Computer created with 0 executors"; setNode(node); } /** * Gets the channel that can be used to run a program on this computer. * * @return * never null when {@link #isOffline()}==false. */ public abstract VirtualChannel getChannel(); /** * Gets the logs recorded by this slave. */ public abstract List getLogRecords() throws IOException, InterruptedException; /** * If {@link #getChannel()}==null, attempts to relaunch the slave agent. */ public abstract void doLaunchSlaveAgent( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException; /** * Do the same as {@link #doLaunchSlaveAgent(StaplerRequest, StaplerResponse)} * but outside the context of serving a request. * * If already connected, no-op. */ public abstract void launch(); /** * 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 public int getNumExecutors() { return numExecutors; } /** * Returns the {@link Node} that this computer represents. */ public Node getNode() { if(nodeName==null) return Hudson.getInstance(); return Hudson.getInstance().getSlave(nodeName); } @Exported public boolean isOffline() { return temporarilyOffline || getChannel()==null; } /** * Returns true if this computer is supposed to be launched via JNLP. */ public boolean isJnlpAgent() { return false; } /** * 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. */ 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"; } @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 getTiedJobs() { return getNode().getSelfLabel().getTiedJobs(); } /** * Called to notify {@link Computer} that its corresponding {@link Node} * configuration is updated. */ protected void setNode(Node node) { assert node!=null; if(node instanceof Slave) this.nodeName = node.getNodeName(); else this.nodeName = null; setNumExecutors(node.getNumExecutors()); } /** * Called to notify {@link Computer} that it will be discarded. */ protected void kill() { setNumExecutors(0); } private synchronized void setNumExecutors(int n) { this.numExecutors = n; // send signal to all idle executors to potentially kill them off for( Executor e : executors ) if(e.isIdle()) e.interrupt(); // if the number is increased, add new ones while(executors.size() getExecutors() { return new ArrayList(executors); } /** * 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; } /** * Gets the system properties of the JVM on this computer. * If this is the master, it returns the system property of the master computer. */ public Map getSystemProperties() throws IOException, InterruptedException { return RemotingDiagnostics.getSystemProperties(getChannel()); } /** * Gets the environment variables of the JVM on this computer. * If this is the master, it returns the system property of the master computer. */ public Map getEnvVars() throws IOException, InterruptedException { return EnvVars.getRemote(getChannel()); } /** * Gets the thread dump of the slave JVM. * @return * key is the thread name, and the value is the pre-formatted dump. */ public Map getThreadDump() throws IOException, InterruptedException { return RemotingDiagnostics.getThreadDump(getChannel()); } public static final ExecutorService threadPoolForRemoting = Executors.newCachedThreadPool(new DaemonThreadFactory()); // // // UI // // public void doRssAll( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException { rss(req, rsp, " all builds", new RunList(getTiedJobs())); } public void doRssFailed( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException { rss(req, rsp, " failed builds", new RunList(getTiedJobs()).failureOnly()); } private void rss(StaplerRequest req, StaplerResponse rsp, String suffix, RunList runs) throws IOException, ServletException { RSS.forwardToRss(getDisplayName()+ suffix, getUrl(), runs.newBuilds(), Run.FEED_ADAPTER, req, rsp ); } public void doToggleOffline( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException { Hudson.getInstance().checkPermission(Hudson.ADMINISTER); setTemporarilyOffline(!temporarilyOffline); rsp.forwardToPreviousPage(req); } /** * Dumps the contents of the export table. */ public void doDumpExportTable( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException { // this is a debug probe and may expose sensitive information Hudson.getInstance().checkPermission(Hudson.ADMINISTER); rsp.setContentType("text/plain"); rsp.setCharacterEncoding("UTF-8"); PrintWriter w = new PrintWriter(rsp.getCompressedWriter(req)); ((Channel)getChannel()).dumpExportTable(w); w.close(); } /** * Gets the current {@link Computer} that the build is running. * This method only works when called during a build, such as by * {@link Publisher}, {@link BuildWrapper}, etc. */ public static Computer currentComputer() { return Executor.currentExecutor().getOwner(); } }