Computer.java 13.2 KB
Newer Older
K
kohsuke 已提交
1 2
package hudson.model;

3
import hudson.EnvVars;
4
import hudson.Util;
5
import hudson.slaves.ComputerLauncher;
6
import hudson.slaves.RetentionStrategy;
T
tblack 已提交
7
import hudson.node_monitors.NodeMonitor;
8
import hudson.remoting.Channel;
9
import hudson.remoting.VirtualChannel;
K
kohsuke 已提交
10
import hudson.tasks.BuildWrapper;
11
import hudson.tasks.Publisher;
K
kohsuke 已提交
12
import hudson.util.DaemonThreadFactory;
13
import hudson.util.RemotingDiagnostics;
14
import hudson.util.RunList;
K
kohsuke 已提交
15 16 17 18 19
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;

import javax.servlet.ServletException;
import java.io.IOException;
20
import java.io.PrintWriter;
K
kohsuke 已提交
21 22
import java.util.ArrayList;
import java.util.List;
K
kohsuke 已提交
23
import java.util.Map;
T
tblack 已提交
24
import java.util.HashMap;
25
import java.util.concurrent.CopyOnWriteArrayList;
K
kohsuke 已提交
26 27
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
28
import java.util.logging.LogRecord;
T
tblack 已提交
29 30
import org.kohsuke.stapler.export.Exported;
import org.kohsuke.stapler.export.ExportedBean;
K
kohsuke 已提交
31 32

/**
33
 * Represents the running state of a remote computer that holds {@link Executor}s.
K
kohsuke 已提交
34 35
 *
 * <p>
K
typo.  
kohsuke 已提交
36
 * {@link Executor}s on one {@link Computer} are transparently interchangeable
K
kohsuke 已提交
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
 * (that is the definition of {@link Computer}.)
 *
 * <p>
 * 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.
 *
 * <p>
 * This object also serves UI (since {@link Node} is an interface and can't have
 * related side pages.)
 *
 * @author Kohsuke Kawaguchi
 */
T
tblack 已提交
55
@ExportedBean
K
kohsuke 已提交
56
public abstract class Computer extends AbstractModelObject {
57
    private final CopyOnWriteArrayList<Executor> executors = new CopyOnWriteArrayList<Executor>();
K
kohsuke 已提交
58 59 60 61 62 63 64 65 66 67 68 69

    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.
     */
K
kohsuke 已提交
70
    protected String nodeName;
71

K
kohsuke 已提交
72 73 74 75 76
    public Computer(Node node) {
        assert node.getNumExecutors()!=0 : "Computer created with 0 executors";
        setNode(node);
    }

K
kohsuke 已提交
77 78 79 80 81 82 83 84
    /**
     * 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();

K
kohsuke 已提交
85 86 87 88 89
    /**
     * Gets the logs recorded by this slave.
     */
    public abstract List<LogRecord> getLogRecords() throws IOException, InterruptedException;

K
kohsuke 已提交
90 91 92 93 94
    /**
     * If {@link #getChannel()}==null, attempts to relaunch the slave agent.
     */
    public abstract void doLaunchSlaveAgent( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException;

95 96 97 98 99 100 101 102
    /**
     * 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();

103
    /**
K
doc fix  
kohsuke 已提交
104
     * Disconnect this computer.
105 106 107 108 109
     *
     * If this is the master, no-op
     */
    public void disconnect() { }

K
kohsuke 已提交
110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130
    /**
     * Number of {@link Executor}s that are configured for this computer.
     *
     * <p>
     * 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);
    }

T
tblack 已提交
131
    @Exported
K
kohsuke 已提交
132 133 134 135
    public boolean isOffline() {
        return temporarilyOffline || getChannel()==null;
    }

136 137
    /**
     * Returns true if this computer is supposed to be launched via JNLP.
138
     * @deprecated see {@linkplain #isLaunchSupported()} and {@linkplain ComputerLauncher}
139
     */
T
tblack 已提交
140
    @Exported
141
    @Deprecated
142 143 144 145
    public boolean isJnlpAgent() {
        return false;
    }

146 147 148 149
    /**
     * Returns true if this computer can be launched by Hudson.
     */
    @Exported
150
    public boolean isLaunchSupported() {
151 152 153
        return true;
    }

K
kohsuke 已提交
154 155 156 157 158 159 160 161 162 163 164 165 166
    /**
     * Returns true if this node is marked temporarily offline by the user.
     *
     * <p>
     * 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.
     */
T
tblack 已提交
167
    @Exported
K
kohsuke 已提交
168 169 170 171 172 173 174 175 176
    public boolean isTemporarilyOffline() {
        return temporarilyOffline;
    }

    public void setTemporarilyOffline(boolean temporarilyOffline) {
        this.temporarilyOffline = temporarilyOffline;
        Hudson.getInstance().getQueue().scheduleMaintenance();
    }

T
tblack 已提交
177
    @Exported
K
kohsuke 已提交
178
    public String getIcon() {
K
kohsuke 已提交
179
        if(isOffline())
K
kohsuke 已提交
180 181 182 183 184
            return "computer-x.gif";
        else
            return "computer.gif";
    }

T
tblack 已提交
185
    @Exported
K
kohsuke 已提交
186
    public String getDisplayName() {
K
kohsuke 已提交
187
        return nodeName;
K
kohsuke 已提交
188 189
    }

K
kohsuke 已提交
190
    public String getCaption() {
K
i18n  
kohsuke 已提交
191
        return Messages.Computer_Caption(nodeName);
K
kohsuke 已提交
192 193
    }

K
kohsuke 已提交
194 195 196 197 198 199 200
    public String getUrl() {
        return "computer/"+getDisplayName()+"/";
    }

    /**
     * Returns projects that are tied on this node.
     */
201 202
    public List<AbstractProject> getTiedJobs() {
        return getNode().getSelfLabel().getTiedJobs();
K
kohsuke 已提交
203
    }
204

205 206 207
    public RunList getBuilds() {
    	return new RunList(Hudson.getInstance().getAllItems(Job.class)).node(getNode());
    }
K
kohsuke 已提交
208

K
kohsuke 已提交
209 210 211 212
    /**
     * Called to notify {@link Computer} that its corresponding {@link Node}
     * configuration is updated.
     */
K
kohsuke 已提交
213
    protected void setNode(Node node) {
K
kohsuke 已提交
214 215 216 217 218 219 220 221 222
        assert node!=null;
        if(node instanceof Slave)
            this.nodeName = node.getNodeName();
        else
            this.nodeName = null;

        setNumExecutors(node.getNumExecutors());
    }

K
kohsuke 已提交
223 224 225
    /**
     * Called to notify {@link Computer} that it will be discarded.
     */
K
kohsuke 已提交
226
    protected void kill() {
K
kohsuke 已提交
227 228 229 230 231 232 233 234
        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 )
235
            if(e.isIdle())
K
kohsuke 已提交
236 237 238 239 240 241 242 243 244 245
                e.interrupt();

        // if the number is increased, add new ones
        while(executors.size()<numExecutors)
            executors.add(new Executor(this));
    }

    /**
     * Returns the number of idle {@link Executor}s that can start working immediately.
     */
246
    public int countIdle() {
K
kohsuke 已提交
247 248 249 250 251 252 253 254 255
        int n = 0;
        for (Executor e : executors) {
            if(e.isIdle())
                n++;
        }
        return n;
    }

    /**
256
     * Gets the read-only snapshot view of all {@link Executor}s.
K
kohsuke 已提交
257
     */
258
    public List<Executor> getExecutors() {
K
kohsuke 已提交
259 260 261
        return new ArrayList<Executor>(executors);
    }

K
kohsuke 已提交
262 263 264
    /**
     * Returns true if all the executors of this computer is idle.
     */
265
    public final boolean isIdle() {
K
kohsuke 已提交
266 267 268 269 270 271
        for (Executor e : executors)
            if(!e.isIdle())
                return false;
        return true;
    }

272 273
    /**
     * Returns the time when this computer first became idle.
K
kohsuke 已提交
274 275 276 277 278 279 280 281
     *
     * <p>
     * If this computer is already idle, the return value will point to the
     * time in the past since when this computer has been idle.
     *
     * <p>
     * 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.
282 283 284 285 286 287 288 289 290
     */
    public final long getIdleStartMilliseconds() {
        long firstIdle = Long.MIN_VALUE;
        for (Executor e : executors) {
            firstIdle = Math.max(firstIdle, e.getIdleStartMilliseconds());
        }
        return firstIdle;
    }

291 292 293 294 295
    /**
     * Returns the time when this computer first became in demand.
     */
    public final long getDemandStartMilliseconds() {
        long firstDemand = Long.MAX_VALUE;
296
        for (Queue.BuildableItem item : Hudson.getInstance().getQueue().getBuildableItems(this)) {
297 298 299 300 301
            firstDemand = Math.min(item.buildableStartMilliseconds, firstDemand);
        }
        return firstDemand;
    }

K
kohsuke 已提交
302 303 304 305 306 307 308 309 310 311 312 313
    /**
     * 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.
     */
314
    public void interrupt() {
K
kohsuke 已提交
315 316 317 318 319
        for (Executor e : executors) {
            e.interrupt();
        }
    }

K
kohsuke 已提交
320 321 322 323
    public String getSearchUrl() {
        return "computer/"+nodeName;
    }

324 325 326 327
    /**
     * {@link RetentionStrategy} associated with this computer.
     *
     * @return
K
kohsuke 已提交
328 329
     *      never null. This method return {@code RetentionStrategy<? super T>} where
     *      {@code T=this.getClass()}.
330 331 332
     */
    public abstract RetentionStrategy getRetentionStrategy();

T
tblack 已提交
333 334 335 336 337 338 339 340 341 342 343
    /**
     * Expose monitoring data for the remote API.
     */
    @Exported(inline=true)
    public Map<String/*monitor name*/,Object> getMonitorData() {
        Map<String,Object> r = new HashMap<String, Object>();
        for (NodeMonitor monitor : NodeMonitor.getAll())
            r.put(monitor.getClass().getName(),monitor.data(this));
        return r;
    }

K
kohsuke 已提交
344 345 346 347 348
    /**
     * 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<Object,Object> getSystemProperties() throws IOException, InterruptedException {
349
        return RemotingDiagnostics.getSystemProperties(getChannel());
K
kohsuke 已提交
350 351 352 353 354 355 356
    }

    /**
     * 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<String,String> getEnvVars() throws IOException, InterruptedException {
357
        return EnvVars.getRemote(getChannel());
K
kohsuke 已提交
358 359
    }

K
kohsuke 已提交
360 361 362 363 364 365
    /**
     * Gets the thread dump of the slave JVM.
     * @return
     *      key is the thread name, and the value is the pre-formatted dump.
     */
    public Map<String,String> getThreadDump() throws IOException, InterruptedException {
366
        return RemotingDiagnostics.getThreadDump(getChannel());
K
kohsuke 已提交
367 368
    }

369
    public static final ExecutorService threadPoolForRemoting = Executors.newCachedThreadPool(new DaemonThreadFactory());
K
kohsuke 已提交
370

K
kohsuke 已提交
371 372 373 374 375 376
//
//
// UI
//
//
    public void doRssAll( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
377
        rss(req, rsp, " all builds", getBuilds());
K
kohsuke 已提交
378 379
    }
    public void doRssFailed( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
380
        rss(req, rsp, " failed builds", getBuilds().failureOnly());
K
kohsuke 已提交
381 382 383 384
    }
    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 );
385
    }
K
kohsuke 已提交
386 387

    public void doToggleOffline( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
388
        Hudson.getInstance().checkPermission(Hudson.ADMINISTER);
K
kohsuke 已提交
389 390 391 392

        setTemporarilyOffline(!temporarilyOffline);
        rsp.forwardToPreviousPage(req);
    }
393

T
tblack 已提交
394 395 396 397
    public Api getApi() {
        return new Api(this);
    }

398 399 400 401
    /**
     * Dumps the contents of the export table.
     */
    public void doDumpExportTable( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
402 403
        // this is a debug probe and may expose sensitive information
        Hudson.getInstance().checkPermission(Hudson.ADMINISTER);
404 405 406 407 408 409 410

        rsp.setContentType("text/plain");
        rsp.setCharacterEncoding("UTF-8");
        PrintWriter w = new PrintWriter(rsp.getCompressedWriter(req));
        ((Channel)getChannel()).dumpExportTable(w);
        w.close();
    }
K
kohsuke 已提交
411

K
kohsuke 已提交
412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429
    public void doScript( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
        // ability to run arbitrary script is dangerous,
        // so tie it to the admin access
        Hudson.getInstance().checkPermission(Hudson.ADMINISTER);

        String text = req.getParameter("script");
        if(text!=null) {
            try {
                req.setAttribute("output",
                RemotingDiagnostics.executeGroovy(text,getChannel()));
            } catch (InterruptedException e) {
                throw new ServletException(e);
            }
        }

        req.getView(this,"_script.jelly").forward(req,rsp);
    }

K
kohsuke 已提交
430 431 432 433 434 435 436 437
    /**
     * 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();
    }
K
kohsuke 已提交
438
}