Computer.java 59.2 KB
Newer Older
K
kohsuke 已提交
1 2
/*
 * The MIT License
3
 *
4
 * Copyright (c) 2004-2010, Sun Microsystems, Inc., Kohsuke Kawaguchi,
5 6
 * Red Hat, Inc., Seiji Sogabe, Stephen Connolly, Thomas J. Black, Tom Huybrechts,
 * CloudBees, Inc., Christopher Simons
7
 *
K
kohsuke 已提交
8 9 10 11 12 13
 * 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:
14
 *
K
kohsuke 已提交
15 16
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
17
 *
K
kohsuke 已提交
18 19 20 21 22 23 24 25
 * 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.
 */
K
kohsuke 已提交
26 27
package hudson.model;

28 29
import edu.umd.cs.findbugs.annotations.OverrideMustInvoke;
import edu.umd.cs.findbugs.annotations.When;
30
import hudson.EnvVars;
31
import hudson.Extension;
32
import hudson.Launcher.ProcStarter;
33
import hudson.Util;
34
import hudson.cli.declarative.CLIMethod;
35
import hudson.cli.declarative.CLIResolver;
36
import hudson.console.AnnotatedLargeText;
37
import hudson.init.Initializer;
K
kohsuke 已提交
38
import hudson.model.Descriptor.FormException;
39
import hudson.model.Queue.FlyweightTask;
40
import hudson.model.labels.LabelAtom;
41
import hudson.model.queue.WorkUnit;
T
tblack 已提交
42
import hudson.node_monitors.NodeMonitor;
43
import hudson.remoting.Channel;
44
import hudson.remoting.VirtualChannel;
45 46 47
import hudson.security.ACL;
import hudson.security.AccessControlled;
import hudson.security.Permission;
K
kohsuke 已提交
48
import hudson.security.PermissionGroup;
49
import hudson.security.PermissionScope;
50
import hudson.slaves.AbstractCloudSlave;
K
kohsuke 已提交
51
import hudson.slaves.ComputerLauncher;
52
import hudson.slaves.ComputerListener;
53
import hudson.slaves.NodeProperty;
K
kohsuke 已提交
54
import hudson.slaves.RetentionStrategy;
K
kohsuke 已提交
55
import hudson.slaves.WorkspaceList;
56
import hudson.slaves.OfflineCause;
57
import hudson.slaves.OfflineCause.ByCLI;
58
import hudson.util.DaemonThreadFactory;
59
import hudson.util.EditDistance;
K
kohsuke 已提交
60
import hudson.util.ExceptionCatchingThreadFactory;
61
import hudson.util.RemotingDiagnostics;
62
import hudson.util.RemotingDiagnostics.HeapDump;
63
import hudson.util.RunList;
K
kohsuke 已提交
64
import hudson.util.Futures;
65
import hudson.util.NamingThreadFactory;
66
import jenkins.model.Jenkins;
67
import jenkins.util.ContextResettingExecutorService;
68
import jenkins.security.MasterToSlaveCallable;
69
import jenkins.security.NotReallyRoleSensitiveCallable;
70

71
import org.kohsuke.accmod.Restricted;
72
import org.kohsuke.accmod.restrictions.DoNotUse;
73
import org.kohsuke.accmod.restrictions.NoExternalUse;
74 75
import org.kohsuke.args4j.Argument;
import org.kohsuke.args4j.CmdLineException;
76
import org.kohsuke.stapler.Stapler;
K
kohsuke 已提交
77 78
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
79
import org.kohsuke.stapler.QueryParameter;
80 81
import org.kohsuke.stapler.HttpResponses;
import org.kohsuke.stapler.HttpResponse;
82
import org.kohsuke.stapler.HttpRedirect;
83
import org.kohsuke.stapler.WebMethod;
K
kohsuke 已提交
84 85
import org.kohsuke.stapler.export.Exported;
import org.kohsuke.stapler.export.ExportedBean;
86
import org.kohsuke.args4j.Option;
87
import org.kohsuke.stapler.interceptor.RequirePOST;
K
kohsuke 已提交
88

89
import javax.annotation.concurrent.GuardedBy;
K
kohsuke 已提交
90
import javax.servlet.ServletException;
91

K
kohsuke 已提交
92
import java.io.File;
93
import java.io.FilenameFilter;
K
kohsuke 已提交
94
import java.io.IOException;
95
import java.io.InputStream;
96
import java.io.PrintWriter;
97
import java.io.StringWriter;
98
import java.util.*;
99
import java.util.concurrent.CopyOnWriteArrayList;
K
kohsuke 已提交
100 101
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
K
kohsuke 已提交
102
import java.util.concurrent.Future;
103
import java.util.concurrent.ExecutionException;
104
import java.util.logging.LogRecord;
105 106
import java.util.logging.Level;
import java.util.logging.Logger;
107
import java.nio.charset.Charset;
108 109
import java.net.InetAddress;
import java.net.NetworkInterface;
110
import java.net.Inet4Address;
111 112
import java.util.regex.Matcher;
import java.util.regex.Pattern;
113

J
Jesse Glick 已提交
114
import javax.annotation.CheckForNull;
J
Jesse Glick 已提交
115
import javax.annotation.Nonnull;
116
import javax.annotation.Nullable;
117

118 119
import static javax.servlet.http.HttpServletResponse.*;

K
kohsuke 已提交
120
/**
121
 * Represents the running state of a remote computer that holds {@link Executor}s.
K
kohsuke 已提交
122 123
 *
 * <p>
K
typo.  
kohsuke 已提交
124
 * {@link Executor}s on one {@link Computer} are transparently interchangeable
125
 * (that is the definition of {@link Computer}).
K
kohsuke 已提交
126 127
 *
 * <p>
128
 * This object is related to {@link Node} but they have some significant differences.
K
kohsuke 已提交
129 130
 * {@link Computer} primarily works as a holder of {@link Executor}s, so
 * if a {@link Node} is configured (probably temporarily) with 0 executors,
131
 * you won't have a {@link Computer} object for it (except for the master node,
132
 * which always gets its {@link Computer} in case we have no static executors and
133
 * we need to run a {@link FlyweightTask} - see JENKINS-7291 for more discussion.)
K
kohsuke 已提交
134 135 136
 *
 * 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
K
kohsuke 已提交
137 138
 * node. Or when the node configuration is changed, unaffected {@link Computer} object
 * remains intact, while all the {@link Node} objects will go away.
K
kohsuke 已提交
139 140
 *
 * <p>
141 142
 * This object also serves UI (unlike {@link Node}), and can be used along with
 * {@link TransientComputerActionFactory} to add {@link Action}s to {@link Computer}s.
K
kohsuke 已提交
143 144 145
 *
 * @author Kohsuke Kawaguchi
 */
T
tblack 已提交
146
@ExportedBean
147
public /*transient*/ abstract class Computer extends Actionable implements AccessControlled, ExecutorListener {
148

K
TAB->WS  
kohsuke 已提交
149
    private final CopyOnWriteArrayList<Executor> executors = new CopyOnWriteArrayList<Executor>();
150
    // TODO:
151
    private final CopyOnWriteArrayList<OneOffExecutor> oneOffExecutors = new CopyOnWriteArrayList<OneOffExecutor>();
K
kohsuke 已提交
152 153

    private int numExecutors;
154 155 156 157 158

    /**
     * Contains info about reason behind computer being offline.
     */
    protected volatile OfflineCause offlineCause;
159

160
    private long connectTime = 0;
K
kohsuke 已提交
161 162

    /**
163
     * True if Jenkins shouldn't start new builds on this node.
K
kohsuke 已提交
164 165 166 167 168 169 170
     */
    private boolean temporarilyOffline;

    /**
     * {@link Node} object may be created and deleted independently
     * from this object.
     */
K
kohsuke 已提交
171
    protected String nodeName;
172

173 174 175 176 177 178
    /**
     * @see #getHostName()
     */
    private volatile String cachedHostName;
    private volatile boolean hostNameCached;

179 180 181 182 183 184
    /**
     * @see #getEnvironment()
     */
    private volatile EnvVars cachedEnvironment;


K
kohsuke 已提交
185 186
    private final WorkspaceList workspaceList = new WorkspaceList();

187 188
    protected transient List<Action> transientActions;

189 190
    protected final Object statusChangeLock = new Object();

191 192 193
    /**
     * Keeps track of stack traces to track the tremination requests for this computer.
     *
S
Stephen Connolly 已提交
194
     * @since 1.607
195 196 197 198
     * @see Executor#resetWorkUnit(String)
     */
    private transient final List<TerminationRequest> terminatedBy = Collections.synchronizedList(new ArrayList
            <TerminationRequest>());
199

200 201 202 203 204 205
    /**
     * This method captures the information of a request to terminate a computer instance. Method is public as
     * it needs to be called from {@link AbstractCloudSlave} and {@link jenkins.model.Nodes}. In general you should
     * not need to call this method directly, however if implementing a custom node type or a different path
     * for removing nodes, it may make sense to call this method in order to capture the originating request.
     *
S
Stephen Connolly 已提交
206
     * @since 1.607
207
     */
208
    public void recordTermination() {
209 210 211
        StaplerRequest request = Stapler.getCurrentRequest();
        if (request != null) {
            terminatedBy.add(new TerminationRequest(
212 213
                    String.format("Termination requested at %s by %s [id=%d] from HTTP request for %s",
                            new Date(),
214 215 216 217 218 219 220
                            Thread.currentThread(),
                            Thread.currentThread().getId(),
                            request.getRequestURL()
                    )
            ));
        } else {
            terminatedBy.add(new TerminationRequest(
221 222
                    String.format("Termination requested at %s by %s [id=%d]",
                            new Date(),
223 224 225 226
                            Thread.currentThread(),
                            Thread.currentThread().getId()
                    )
            ));
227 228 229
        }
    }

230 231 232 233 234 235 236
    /**
     * Returns the list of captured termination requests for this Computer. This method is used by {@link Executor}
     * to provide details on why a Computer was removed in-between work being scheduled against the {@link Executor}
     * and the {@link Executor} starting to execute the task.
     *
     * @return the (possibly empty) list of termination requests.
     * @see Executor#resetWorkUnit(String)
S
Stephen Connolly 已提交
237
     * @since 1.607
238 239 240
     */
    public List<TerminationRequest> getTerminatedBy() {
        return new ArrayList<TerminationRequest>(terminatedBy);
241 242
    }

K
kohsuke 已提交
243 244 245 246
    public Computer(Node node) {
        setNode(node);
    }

247 248 249 250 251 252
     /**
     * Returns list of all boxes {@link ComputerPanelBox}s.
     */
    public List<ComputerPanelBox> getComputerPanelBoxs(){
        return ComputerPanelBox.all(this);
    }
253

254 255 256
    /**
     * Returns the transient {@link Action}s associated with the computer.
     */
257
    @SuppressWarnings("deprecation")
258 259 260 261 262 263 264 265 266
    public List<Action> getActions() {
    	List<Action> result = new ArrayList<Action>();
    	result.addAll(super.getActions());
    	synchronized (this) {
    		if (transientActions == null) {
    			transientActions = TransientComputerActionFactory.createAllFor(this);
    		}
    		result.addAll(transientActions);
    	}
267
    	return Collections.unmodifiableList(result);
268 269
    }

270
    @SuppressWarnings("deprecation")
271 272 273 274 275 276
    @Override
    public void addAction(Action a) {
        if(a==null) throw new IllegalArgumentException();
        super.getActions().add(a);
    }

277 278
    /**
     * This is where the log from the remote agent goes.
279
     * The method also creates a log directory if required.
280
     * @see #getLogDir(), #relocateOldLogs()
281
     */
282 283 284 285 286
    public @Nonnull File getLogFile() {
        return new File(getLogDir(),"slave.log");
    }

    /**
287
     * Directory where rotated agent logs are stored.
288 289 290
     *
     * The method also creates a log directory if required.
     *
291
     * @since 1.613
292 293
     */
    protected @Nonnull File getLogDir() {
294
        File dir = new File(Jenkins.getInstance().getRootDir(),"logs/slaves/"+nodeName);
295
        if (!dir.exists() && !dir.mkdirs()) {
296
            LOGGER.severe("Failed to create agent log directory " + dir.getAbsolutePath());
297
        }
298
        return dir;
299 300
    }

K
kohsuke 已提交
301 302 303 304 305 306 307
    /**
     * Gets the object that coordinates the workspace allocation on this computer.
     */
    public WorkspaceList getWorkspaceList() {
        return workspaceList;
    }

308
    /**
309
     * Gets the string representation of the agent log.
310 311 312 313 314
     */
    public String getLog() throws IOException {
        return Util.loadFile(getLogFile());
    }

315 316 317 318 319 320 321
    /**
     * Used to URL-bind {@link AnnotatedLargeText}.
     */
    public AnnotatedLargeText<Computer> getLogText() {
        return new AnnotatedLargeText<Computer>(getLogFile(), Charset.defaultCharset(), false, this);
    }

322
    public ACL getACL() {
323
        return Jenkins.getInstance().getAuthorizationStrategy().getACL(this);
324 325 326 327 328 329 330 331 332 333
    }

    public void checkPermission(Permission permission) {
        getACL().checkPermission(permission);
    }

    public boolean hasPermission(Permission permission) {
        return getACL().hasPermission(permission);
    }

334 335 336 337 338 339 340 341 342 343 344 345
    /**
     * 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;
    }

346 347 348 349 350
    /**
     * If the computer was offline (either temporarily or not),
     * this method will return the cause as a string (without user info).
     *
     * @return
351
     *      empty string if the system was put offline without given a cause.
352 353 354
     */
    @Exported
    public String getOfflineCauseReason() {
355
        if (offlineCause == null) {
A
Andrew J. Erickson 已提交
356
            return "";
357
        }
358 359 360 361 362 363 364 365 366
        // fetch the localized string for "Disconnected By"
        String gsub_base = hudson.slaves.Messages.SlaveComputer_DisconnectedBy("","");
        // regex to remove commented reason base string
        String gsub1 = "^" + gsub_base + "[\\w\\W]* \\: ";
        // regex to remove non-commented reason base string
        String gsub2 = "^" + gsub_base + "[\\w\\W]*";

        String newString = offlineCause.toString().replaceAll(gsub1, "");
        return newString.replaceAll(gsub2, "");
367 368
    }

K
kohsuke 已提交
369 370 371 372 373 374
    /**
     * Gets the channel that can be used to run a program on this computer.
     *
     * @return
     *      never null when {@link #isOffline()}==false.
     */
375
    public abstract @Nullable VirtualChannel getChannel();
K
kohsuke 已提交
376

377 378 379 380 381 382 383 384
    /**
     * Gets the default charset of this computer.
     *
     * @return
     *      never null when {@link #isOffline()}==false.
     */
    public abstract Charset getDefaultCharset();

K
kohsuke 已提交
385
    /**
386
     * Gets the logs recorded by this agent.
K
kohsuke 已提交
387 388 389
     */
    public abstract List<LogRecord> getLogRecords() throws IOException, InterruptedException;

K
kohsuke 已提交
390
    /**
391
     * If {@link #getChannel()}==null, attempts to relaunch the agent.
K
kohsuke 已提交
392 393 394
     */
    public abstract void doLaunchSlaveAgent( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException;

K
kohsuke 已提交
395
    /**
M
mindless 已提交
396
     * @deprecated since 2009-01-06.  Use {@link #connect(boolean)}
K
kohsuke 已提交
397
     */
398
    @Deprecated
K
kohsuke 已提交
399 400 401 402
    public final void launch() {
        connect(true);
    }

403 404 405 406
    /**
     * Do the same as {@link #doLaunchSlaveAgent(StaplerRequest, StaplerResponse)}
     * but outside the context of serving a request.
     *
K
kohsuke 已提交
407 408 409 410 411 412 413 414 415 416 417 418
     * <p>
     * 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
419 420 421
     *      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.)
422
     */
423 424 425 426
    public final Future<?> connect(boolean forceReconnect) {
    	connectTime = System.currentTimeMillis();
    	return _connect(forceReconnect);
    }
427

428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447
    /**
     * Allows implementing-classes to provide an implementation for the connect method.
     *
     * <p>
     * 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.)
     */
    protected abstract Future<?> _connect(boolean forceReconnect);
448

449 450 451 452 453
    /**
     * CLI command to reconnect this node.
     */
    @CLIMethod(name="connect-node")
    public void cliConnect(@Option(name="-f",usage="Cancel any currently pending connect operation and retry from scratch") boolean force) throws ExecutionException, InterruptedException {
454
        checkPermission(CONNECT);
455 456 457
        connect(force).get();
    }

458 459
    /**
     * Gets the time (since epoch) when this computer connected.
460
     *
461 462 463 464 465
     * @return The time in ms since epoch when this computer last connected.
     */
    public final long getConnectTime() {
    	return connectTime;
    }
466

467
    /**
K
doc fix  
kohsuke 已提交
468
     * Disconnect this computer.
469
     *
K
kohsuke 已提交
470 471 472
     * If this is the master, no-op. This method may return immediately
     * while the launch operation happens asynchronously.
     *
473 474 475
     * @param cause
     *      Object that identifies the reason the node was disconnected.
     *
K
kohsuke 已提交
476 477 478
     * @return
     *      {@link Future} to track the asynchronous disconnect operation.
     * @see #connect(boolean)
479
     * @since 1.320
480
     */
481
    public Future<?> disconnect(OfflineCause cause) {
482
        recordTermination();
483 484 485 486 487 488
        offlineCause = cause;
        if (Util.isOverridden(Computer.class,getClass(),"disconnect"))
            return disconnect();    // legacy subtypes that extend disconnect().

        connectTime=0;
        return Futures.precomputed(null);
489
    }
490

491 492 493 494 495 496
    /**
     * Equivalent to {@code disconnect(null)}
     *
     * @deprecated as of 1.320.
     *      Use {@link #disconnect(OfflineCause)} and specify the cause.
     */
497
    @Deprecated
498
    public Future<?> disconnect() {
499
        recordTermination();
500 501 502 503 504 505 506 507
        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);
    }

508 509 510 511 512
    /**
     * CLI command to disconnects this node.
     */
    @CLIMethod(name="disconnect-node")
    public void cliDisconnect(@Option(name="-m",usage="Record the note about why you are disconnecting this node") String cause) throws ExecutionException, InterruptedException {
513
        checkPermission(DISCONNECT);
514 515 516 517 518 519 520 521
        disconnect(new ByCLI(cause)).get();
    }

    /**
     * CLI command to mark the node offline.
     */
    @CLIMethod(name="offline-node")
    public void cliOffline(@Option(name="-m",usage="Record the note about why you are disconnecting this node") String cause) throws ExecutionException, InterruptedException {
522
        checkPermission(DISCONNECT);
523
        setTemporarilyOffline(true, new ByCLI(cause));
524 525 526
    }

    public void cliOnline() throws ExecutionException, InterruptedException {
527
        checkPermission(CONNECT);
528
        setTemporarilyOffline(false, null);
529 530
    }

K
kohsuke 已提交
531 532 533 534 535 536 537 538
    /**
     * 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
K
kohsuke 已提交
539
    @Exported
K
kohsuke 已提交
540 541 542 543
    public int getNumExecutors() {
        return numExecutors;
    }

K
kohsuke 已提交
544
    /**
545
     * Returns {@link Node#getNodeName() the name of the node}.
K
kohsuke 已提交
546
     */
J
Jesse Glick 已提交
547
    public @Nonnull String getName() {
J
Jesse Glick 已提交
548
        return nodeName != null ? nodeName : "";
K
kohsuke 已提交
549 550
    }

551 552 553
    /**
     * True if this computer is a Unix machine (as opposed to Windows machine).
     *
554
     * @since 1.624
555 556 557
     * @return
     *      null if the computer is disconnected and therefore we don't know whether it is Unix or not.
     */
558
    public abstract @CheckForNull Boolean isUnix();
559

K
kohsuke 已提交
560 561
    /**
     * Returns the {@link Node} that this computer represents.
K
kohsuke 已提交
562 563 564 565
     *
     * @return
     *      null if the configuration has changed and the node is removed, yet the corresponding {@link Computer}
     *      is not yet gone.
K
kohsuke 已提交
566
     */
J
Jesse Glick 已提交
567
    public @CheckForNull Node getNode() {
J
Jesse Glick 已提交
568 569 570 571 572 573 574 575
        Jenkins j = Jenkins.getInstance();
        if (j == null) {
            return null;
        }
        if (nodeName == null) {
            return j;
        }
        return j.getNode(nodeName);
K
kohsuke 已提交
576 577
    }

578
    @Exported
K
kohsuke 已提交
579
    public LoadStatistics getLoadStatistics() {
580
        return LabelAtom.get(nodeName != null ? nodeName : Jenkins.getInstance().getSelfLabel().toString()).loadStatistics;
K
kohsuke 已提交
581 582
    }

583 584 585 586
    public BuildTimelineWidget getTimeline() {
        return new BuildTimelineWidget(getBuilds());
    }

587 588 589
    /**
     * {@inheritDoc}
     */
590
    @Override
591 592 593 594 595 596 597
    public void taskAccepted(Executor executor, Queue.Task task) {
        // dummy implementation
    }

    /**
     * {@inheritDoc}
     */
598
    @Override
599 600 601 602 603 604 605
    public void taskCompleted(Executor executor, Queue.Task task, long durationMS) {
        // dummy implementation
    }

    /**
     * {@inheritDoc}
     */
606
    @Override
607 608 609 610
    public void taskCompletedWithProblems(Executor executor, Queue.Task task, long durationMS, Throwable problems) {
        // dummy implementation
    }

T
tblack 已提交
611
    @Exported
K
kohsuke 已提交
612 613 614 615
    public boolean isOffline() {
        return temporarilyOffline || getChannel()==null;
    }

K
kohsuke 已提交
616 617 618 619
    public final boolean isOnline() {
        return !isOffline();
    }

620
    /**
621 622
     * This method is called to determine whether manual launching of the agent is allowed at this point in time.
     * @return {@code true} if manual launching of the agent is allowed at this point in time.
623 624 625
     */
    @Exported
    public boolean isManualLaunchAllowed() {
626
        return getRetentionStrategy().isManualLaunchAllowed(this);
627 628 629
    }


K
kohsuke 已提交
630 631 632 633 634
    /**
     * Is a {@link #connect(boolean)} operation in progress?
     */
    public abstract boolean isConnecting();

635 636
    /**
     * Returns true if this computer is supposed to be launched via JNLP.
M
mindless 已提交
637 638
     * @deprecated since 2008-05-18.
     *     See {@linkplain #isLaunchSupported()} and {@linkplain ComputerLauncher}
639
     */
T
tblack 已提交
640
    @Exported
641
    @Deprecated
642 643 644 645
    public boolean isJnlpAgent() {
        return false;
    }

646
    /**
647 648 649
     * Returns true if this computer can be launched by Hudson proactively and automatically.
     *
     * <p>
650 651
     * For example, JNLP agents return {@code false} from this, because the launch process
     * needs to be initiated from the agent side.
652 653
     */
    @Exported
654
    public boolean isLaunchSupported() {
655 656 657
        return true;
    }

K
kohsuke 已提交
658 659 660 661 662 663
    /**
     * 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()}
664
     * returns true if the agent failed to launch.
K
kohsuke 已提交
665 666 667 668 669 670
     *
     * @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 已提交
671
    @Exported
672
    @Deprecated
K
kohsuke 已提交
673 674 675 676
    public boolean isTemporarilyOffline() {
        return temporarilyOffline;
    }

677 678 679 680
    /**
     * @deprecated as of 1.320.
     *      Use {@link #setTemporarilyOffline(boolean, OfflineCause)}
     */
681
    @Deprecated
K
kohsuke 已提交
682
    public void setTemporarilyOffline(boolean temporarilyOffline) {
683 684 685 686
        setTemporarilyOffline(temporarilyOffline,null);
    }

    /**
687
     * Marks the computer as temporarily offline. This retains the underlying
688 689 690 691
     * {@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
692
     *      offline.
693 694 695
     */
    public void setTemporarilyOffline(boolean temporarilyOffline, OfflineCause cause) {
        offlineCause = temporarilyOffline ? cause : null;
K
kohsuke 已提交
696
        this.temporarilyOffline = temporarilyOffline;
697 698 699 700
        Node node = getNode();
        if (node != null) {
            node.setTemporaryOfflineCause(offlineCause);
        }
701 702 703
        synchronized (statusChangeLock) {
            statusChangeLock.notifyAll();
        }
704 705 706 707
        for (ComputerListener cl : ComputerListener.all()) {
            if (temporarilyOffline)     cl.onTemporarilyOffline(this,cause);
            else                        cl.onTemporarilyOnline(this);
        }
K
kohsuke 已提交
708 709
    }

T
tblack 已提交
710
    @Exported
K
kohsuke 已提交
711
    public String getIcon() {
K
kohsuke 已提交
712
        if(isOffline())
K
use PNG  
Kohsuke Kawaguchi 已提交
713
            return "computer-x.png";
K
kohsuke 已提交
714
        else
K
use PNG  
Kohsuke Kawaguchi 已提交
715
            return "computer.png";
K
kohsuke 已提交
716 717
    }

T
tfennelly 已提交
718 719 720 721 722 723 724 725
    @Exported
    public String getIconClassName() {
        if(isOffline())
            return "icon-computer-x";
        else
            return "icon-computer";
    }

726 727 728 729 730 731 732
    public String getIconAltText() {
        if(isOffline())
            return "[offline]";
        else
            return "[online]";
    }

T
tblack 已提交
733
    @Exported
J
Jesse Glick 已提交
734
    @Override public @Nonnull String getDisplayName() {
K
kohsuke 已提交
735
        return nodeName;
K
kohsuke 已提交
736 737
    }

K
kohsuke 已提交
738
    public String getCaption() {
K
i18n  
kohsuke 已提交
739
        return Messages.Computer_Caption(nodeName);
K
kohsuke 已提交
740 741
    }

K
kohsuke 已提交
742
    public String getUrl() {
743
        return "computer/" + Util.rawEncode(getName()) + "/";
K
kohsuke 已提交
744 745 746 747 748
    }

    /**
     * Returns projects that are tied on this node.
     */
749
    public List<AbstractProject> getTiedJobs() {
750 751
        Node node = getNode();
        return (node != null) ? node.getSelfLabel().getTiedJobs() : Collections.EMPTY_LIST;
K
kohsuke 已提交
752
    }
753

754
    public RunList getBuilds() {
755
    	return new RunList(Jenkins.getInstance().getAllItems(Job.class)).node(getNode());
756
    }
K
kohsuke 已提交
757

K
kohsuke 已提交
758 759 760 761
    /**
     * Called to notify {@link Computer} that its corresponding {@link Node}
     * configuration is updated.
     */
K
kohsuke 已提交
762
    protected void setNode(Node node) {
K
kohsuke 已提交
763 764 765 766 767 768 769
        assert node!=null;
        if(node instanceof Slave)
            this.nodeName = node.getNodeName();
        else
            this.nodeName = null;

        setNumExecutors(node.getNumExecutors());
N
Nathan Parry 已提交
770 771 772 773 774 775 776 777 778 779
        if (this.temporarilyOffline) {
            // When we get a new node, push our current temp offline
            // status to it (as the status is not carried across
            // configuration changes that recreate the node).
            // Since this is also called the very first time this
            // Computer is created, avoid pushing an empty status
            // as that could overwrite any status that the Node
            // brought along from its persisted config data.
            node.setTemporaryOfflineCause(this.offlineCause);
        }
K
kohsuke 已提交
780 781
    }

K
kohsuke 已提交
782
    /**
783
     * Called by {@link Jenkins#updateComputerList()} to notify {@link Computer} that it will be discarded.
K
Kohsuke Kawaguchi 已提交
784
     *
K
Kohsuke Kawaguchi 已提交
785 786 787
     * <p>
     * Note that at this point {@link #getNode()} returns null.
     *
K
Kohsuke Kawaguchi 已提交
788
     * @see #onRemoved()
K
kohsuke 已提交
789
     */
K
kohsuke 已提交
790
    protected void kill() {
791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810
        // On most code paths, this should already be zero, and thus this next call becomes a no-op... and more
        // importantly it will not acquire a lock on the Queue... not that the lock is bad, more that the lock
        // may delay unnecessarily
        setNumExecutors(0);
    }

    /**
     * Called by {@link Jenkins#updateComputerList()} to notify {@link Computer} that it will be discarded.
     *
     * <p>
     * Note that at this point {@link #getNode()} returns null.
     *
     * <p>
     * Note that the Queue lock is already held when this method is called.
     *
     * @see #onRemoved()
     */
    @Restricted(NoExternalUse.class)
    @GuardedBy("hudson.model.Queue.lock")
    /*package*/ void inflictMortalWound() {
K
kohsuke 已提交
811 812 813
        setNumExecutors(0);
    }

K
Kohsuke Kawaguchi 已提交
814 815 816
    /**
     * Called by {@link Jenkins} when this computer is removed.
     *
K
Kohsuke Kawaguchi 已提交
817
     * <p>
K
Kohsuke Kawaguchi 已提交
818 819 820 821
     * This happens when list of nodes are updated (for example by {@link Jenkins#setNodes(List)} and
     * the computer becomes redundant. Such {@link Computer}s get {@linkplain #kill() killed}, then
     * after all its executors are finished, this method is called.
     *
K
Kohsuke Kawaguchi 已提交
822 823 824
     * <p>
     * Note that at this point {@link #getNode()} returns null.
     *
K
Kohsuke Kawaguchi 已提交
825 826 827 828 829 830
     * @see #kill()
     * @since 1.510
     */
    protected void onRemoved(){
    }

K
kohsuke 已提交
831
    private synchronized void setNumExecutors(int n) {
K
kohsuke 已提交
832
        this.numExecutors = n;
833
        final int diff = executors.size()-n;
K
kohsuke 已提交
834

835 836
        if (diff>0) {
            // we have too many executors
K
kohsuke 已提交
837
            // send signal to all idle executors to potentially kill them off
838 839 840 841 842 843 844 845 846
            // need the Queue maintenance lock held to prevent concurrent job assignment on the idle executors
            Queue.withLock(new Runnable() {
                @Override
                public void run() {
                    for( Executor e : executors )
                        if(e.isIdle())
                            e.interrupt();
                }
            });
847 848 849
        }

        if (diff<0) {
K
kohsuke 已提交
850
            // if the number is increased, add new ones
851
            addNewExecutorIfNecessary();
K
kohsuke 已提交
852
        }
K
kohsuke 已提交
853 854
    }

855 856
    private void addNewExecutorIfNecessary() {
        Set<Integer> availableNumbers  = new HashSet<Integer>();
K
Kohsuke Kawaguchi 已提交
857 858
        for (int i = 0; i < numExecutors; i++)
            availableNumbers.add(i);
859 860 861 862

        for (Executor executor : executors)
            availableNumbers.remove(executor.getNumber());

K
Kohsuke Kawaguchi 已提交
863
        for (Integer number : availableNumbers) {
864 865 866 867 868 869 870 871
            /* There may be busy executors with higher index, so only
               fill up until numExecutors is reached.
               Extra executors will call removeExecutor(...) and that
               will create any necessary executors from #0 again. */
            if (executors.size() < numExecutors) {
                Executor e = new Executor(this, number);
                executors.add(e);
            }
K
kohsuke 已提交
872
        }
873

K
kohsuke 已提交
874 875 876 877 878
    }

    /**
     * Returns the number of idle {@link Executor}s that can start working immediately.
     */
879
    public int countIdle() {
K
kohsuke 已提交
880 881 882 883 884 885 886 887
        int n = 0;
        for (Executor e : executors) {
            if(e.isIdle())
                n++;
        }
        return n;
    }

K
kohsuke 已提交
888 889 890 891 892 893 894
    /**
     * Returns the number of {@link Executor}s that are doing some work right now.
     */
    public final int countBusy() {
        return countExecutors()-countIdle();
    }

895 896 897 898 899 900
    /**
     * Returns the current size of the executor pool for this computer.
     * This number may temporarily differ from {@link #getNumExecutors()} if there
     * are busy tasks when the configured size is decreased.  OneOffExecutors are
     * not included in this count.
     */
K
kohsuke 已提交
901 902 903 904
    public final int countExecutors() {
        return executors.size();
    }

K
kohsuke 已提交
905
    /**
906
     * Gets the read-only snapshot view of all {@link Executor}s.
K
kohsuke 已提交
907
     */
K
kohsuke 已提交
908
    @Exported
909
    public List<Executor> getExecutors() {
K
kohsuke 已提交
910 911 912
        return new ArrayList<Executor>(executors);
    }

913 914 915 916 917 918 919 920
    /**
     * Gets the read-only snapshot view of all {@link OneOffExecutor}s.
     */
    @Exported
    public List<OneOffExecutor> getOneOffExecutors() {
        return new ArrayList<OneOffExecutor>(oneOffExecutors);
    }

921 922 923
    /**
     * Used to render the list of executors.
     * @return a snapshot of the executor display information
S
Stephen Connolly 已提交
924
     * @since 1.607
925 926
     */
    @Restricted(NoExternalUse.class)
927
    public List<DisplayExecutor> getDisplayExecutors() {
928
        // The size may change while we are populating, but let's start with a reasonable guess to minimize resizing
929 930 931
        List<DisplayExecutor> result = new ArrayList<DisplayExecutor>(executors.size()+oneOffExecutors.size());
        int index = 0;
        for (Executor e: executors) {
932
            if (e.isDisplayCell()) {
933 934
                result.add(new DisplayExecutor(Integer.toString(index + 1), String.format("executors/%d", index), e));
            }
935 936 937 938
            index++;
        }
        index = 0;
        for (OneOffExecutor e: oneOffExecutors) {
939
            if (e.isDisplayCell()) {
940 941
                result.add(new DisplayExecutor("", String.format("oneOffExecutors/%d", index), e));
            }
942 943 944 945 946
            index++;
        }
        return result;
    }

K
kohsuke 已提交
947
    /**
948
     * Returns true if all the executors of this computer are idle.
K
kohsuke 已提交
949
     */
K
kohsuke 已提交
950
    @Exported
951
    public final boolean isIdle() {
952 953
        if (!oneOffExecutors.isEmpty())
            return false;
K
kohsuke 已提交
954 955 956 957 958 959
        for (Executor e : executors)
            if(!e.isIdle())
                return false;
        return true;
    }

K
Kohsuke Kawaguchi 已提交
960 961 962 963 964 965 966 967 968 969
    /**
     * Returns true if this computer has some idle executors that can take more workload.
     */
    public final boolean isPartiallyIdle() {
        for (Executor e : executors)
            if(e.isIdle())
                return true;
        return false;
    }

970
    /**
K
kohsuke 已提交
971
     * Returns the time when this computer last became idle.
K
kohsuke 已提交
972 973 974 975 976 977 978 979
     *
     * <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.
980 981 982
     */
    public final long getIdleStartMilliseconds() {
        long firstIdle = Long.MIN_VALUE;
983 984 985
        for (Executor e : oneOffExecutors) {
            firstIdle = Math.max(firstIdle, e.getIdleStartMilliseconds());
        }
986 987 988 989 990 991
        for (Executor e : executors) {
            firstIdle = Math.max(firstIdle, e.getIdleStartMilliseconds());
        }
        return firstIdle;
    }

992 993 994 995 996
    /**
     * Returns the time when this computer first became in demand.
     */
    public final long getDemandStartMilliseconds() {
        long firstDemand = Long.MAX_VALUE;
997
        for (Queue.BuildableItem item : Jenkins.getInstance().getQueue().getBuildableItems(this)) {
998 999 1000 1001 1002
            firstDemand = Math.min(item.buildableStartMilliseconds, firstDemand);
        }
        return firstDemand;
    }

K
kohsuke 已提交
1003 1004 1005
    /**
     * Called by {@link Executor} to kill excessive executors from this computer.
     */
1006
    /*package*/ void removeExecutor(final Executor e) {
1007
        final Runnable task = new Runnable() {
1008 1009 1010 1011 1012
            @Override
            public void run() {
                synchronized (Computer.this) {
                    executors.remove(e);
                    addNewExecutorIfNecessary();
1013
                    if (!isAlive()) {
1014
                        AbstractCIBase ciBase = Jenkins.getInstance();
J
Jesse Glick 已提交
1015 1016 1017
                        if (ciBase != null) {
                            ciBase.removeComputer(Computer.this);
                        }
1018 1019 1020
                    }
                }
            }
1021 1022 1023 1024 1025
        };
        if (!Queue.tryWithLock(task)) {
            // JENKINS-28840 if we couldn't get the lock push the operation to a separate thread to avoid deadlocks
            threadPoolForRemoting.submit(Queue.wrapWithLock(task));
        }
K
kohsuke 已提交
1026 1027
    }

1028
    /**
1029
     * Returns true if any of the executors are {@linkplain Executor#isActive active}.
1030 1031 1032
     *
     * Note that if an executor dies, we'll leave it in {@link #executors} until
     * the administrator yanks it out, so that we can see why it died.
1033 1034
     *
     * @since 1.509
1035
     */
1036
    protected boolean isAlive() {
1037
        for (Executor e : executors)
K
Kohsuke Kawaguchi 已提交
1038
            if (e.isActive())
1039 1040 1041 1042
                return true;
        return false;
    }

K
kohsuke 已提交
1043 1044
    /**
     * Interrupt all {@link Executor}s.
J
Jesse Glick 已提交
1045
     * Called from {@link Jenkins#cleanUp}.
K
kohsuke 已提交
1046
     */
1047
    public void interrupt() {
1048 1049 1050 1051 1052 1053 1054 1055
        Queue.withLock(new Runnable() {
            @Override
            public void run() {
                for (Executor e : executors) {
                    e.interruptForShutdown();
                }
            }
        });
K
kohsuke 已提交
1056 1057
    }

K
kohsuke 已提交
1058
    public String getSearchUrl() {
1059
        return getUrl();
K
kohsuke 已提交
1060 1061
    }

1062 1063 1064 1065
    /**
     * {@link RetentionStrategy} associated with this computer.
     *
     * @return
K
kohsuke 已提交
1066 1067
     *      never null. This method return {@code RetentionStrategy<? super T>} where
     *      {@code T=this.getClass()}.
1068 1069 1070
     */
    public abstract RetentionStrategy getRetentionStrategy();

T
tblack 已提交
1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081
    /**
     * 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 已提交
1082 1083 1084 1085 1086
    /**
     * 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 {
1087
        return RemotingDiagnostics.getSystemProperties(getChannel());
K
kohsuke 已提交
1088 1089
    }

1090 1091 1092 1093
    /**
     * @deprecated as of 1.292
     *      Use {@link #getEnvironment()} instead.
     */
1094
    @Deprecated
1095 1096 1097 1098
    public Map<String,String> getEnvVars() throws IOException, InterruptedException {
        return getEnvironment();
    }

K
kohsuke 已提交
1099
    /**
1100
     * Returns cached environment variables (copy to prevent modification) for the JVM on this computer.
K
kohsuke 已提交
1101 1102
     * If this is the master, it returns the system property of the master computer.
     */
1103
    public EnvVars getEnvironment() throws IOException, InterruptedException {
1104 1105
        EnvVars cachedEnvironment = this.cachedEnvironment;
        if (cachedEnvironment != null) {
1106
            return new EnvVars(cachedEnvironment);
1107 1108 1109
        }

        cachedEnvironment = EnvVars.getRemote(getChannel());
1110
        this.cachedEnvironment = cachedEnvironment;
1111
        return new EnvVars(cachedEnvironment);
K
kohsuke 已提交
1112 1113
    }

1114 1115 1116 1117
    /**
     * Creates an environment variable override to be used for launching processes on this node.
     *
     * @see ProcStarter#envs(Map)
1118
     * @since 1.489
1119
     */
1120
    public @Nonnull EnvVars buildEnvironment(@Nonnull TaskListener listener) throws IOException, InterruptedException {
1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134
        EnvVars env = new EnvVars();

        Node node = getNode();
        if (node==null)     return env; // bail out

        for (NodeProperty nodeProperty: Jenkins.getInstance().getGlobalNodeProperties()) {
            nodeProperty.buildEnvVars(env,listener);
        }

        for (NodeProperty nodeProperty: node.getNodeProperties()) {
            nodeProperty.buildEnvVars(env,listener);
        }

        // TODO: hmm, they don't really belong
J
Jesse Glick 已提交
1135
        String rootUrl = Jenkins.getInstance().getRootUrl();
1136 1137 1138 1139 1140 1141 1142 1143
        if(rootUrl!=null) {
            env.put("HUDSON_URL", rootUrl); // Legacy.
            env.put("JENKINS_URL", rootUrl);
        }

        return env;
    }

K
kohsuke 已提交
1144
    /**
1145
     * Gets the thread dump of the agent JVM.
K
kohsuke 已提交
1146 1147 1148 1149
     * @return
     *      key is the thread name, and the value is the pre-formatted dump.
     */
    public Map<String,String> getThreadDump() throws IOException, InterruptedException {
1150
        return RemotingDiagnostics.getThreadDump(getChannel());
K
kohsuke 已提交
1151 1152
    }

1153 1154 1155 1156 1157 1158 1159
    /**
     * Obtains the heap dump.
     */
    public HeapDump getHeapDump() throws IOException {
        return new HeapDump(this,getChannel());
    }

1160 1161 1162 1163
    /**
     * This method tries to compute the name of the host that's reachable by all the other nodes.
     *
     * <p>
1164
     * Since it's possible that the agent is not reachable from the master (it may be behind a firewall,
1165
     * connecting to master via JNLP), this method may return null.
1166 1167 1168 1169 1170
     *
     * It's surprisingly tricky for a machine to know a name that other systems can get to,
     * especially between things like DNS search suffix, the hosts file, and YP.
     *
     * <p>
1171
     * So the technique here is to compute possible interfaces and names on the agent,
1172 1173
     * then try to ping them from the master, and pick the one that worked.
     *
1174 1175 1176
     * <p>
     * The computation may take some time, so it employs caching to make the successive lookups faster.
     *
1177
     * @since 1.300
K
kohsuke 已提交
1178 1179
     * @return
     *      null if the host name cannot be computed (for example because this computer is offline,
1180
     *      because the agent is behind the firewall, etc.)
1181 1182
     */
    public String getHostName() throws IOException, InterruptedException {
1183 1184 1185 1186
        if(hostNameCached)
            // in the worst case we end up having multiple threads computing the host name simultaneously, but that's not harmful, just wasteful.
            return cachedHostName;

K
kohsuke 已提交
1187 1188 1189 1190
        VirtualChannel channel = getChannel();
        if(channel==null)   return null; // can't compute right now

        for( String address : channel.call(new ListPossibleNames())) {
1191 1192
            try {
                InetAddress ia = InetAddress.getByName(address);
1193
                if(!(ia instanceof Inet4Address)) {
1194
                    LOGGER.log(Level.FINE, "{0} is not an IPv4 address", address);
1195 1196
                    continue;
                }
1197
                if(!ComputerPinger.checkIsReachable(ia, 3)) {
1198
                    LOGGER.log(Level.FINE, "{0} didn't respond to ping", address);
1199 1200
                    continue;
                }
1201 1202 1203
                cachedHostName = ia.getCanonicalHostName();
                hostNameCached = true;
                return cachedHostName;
1204 1205
            } catch (IOException e) {
                // if a given name fails to parse on this host, we get this error
1206 1207 1208 1209
                LogRecord lr = new LogRecord(Level.FINE, "Failed to parse {0}");
                lr.setThrown(e);
                lr.setParameters(new Object[]{address});
                LOGGER.log(lr);
1210 1211
            }
        }
K
kohsuke 已提交
1212 1213 1214

        // allow the administrator to manually specify the host name as a fallback. HUDSON-5373
        cachedHostName = channel.call(new GetFallbackName());
1215
        hostNameCached = true;
1216
        return cachedHostName;
1217 1218
    }

1219 1220 1221
    /**
     * Starts executing a fly-weight task.
     */
1222
    /*package*/ final void startFlyWeightTask(WorkUnit p) {
K
Kohsuke Kawaguchi 已提交
1223 1224
        OneOffExecutor e = new OneOffExecutor(this);
        e.start(p);
1225
        oneOffExecutors.add(e);
1226 1227 1228 1229 1230 1231
    }

    /*package*/ final void remove(OneOffExecutor e) {
        oneOffExecutors.remove(e);
    }

1232
    private static class ListPossibleNames extends MasterToSlaveCallable<List<String>,IOException> {
1233 1234 1235 1236 1237 1238 1239 1240 1241
        /**
         * In the normal case we would use {@link Computer} as the logger's name, however to
         * do that we would have to send the {@link Computer} class over to the remote classloader
         * and then it would need to be loaded, which pulls in {@link Jenkins} and loads that
         * and then that fails to load as you are not supposed to do that. Another option
         * would be to export the logger over remoting, with increased complexity as a result.
         * Instead we just use a loger based on this class name and prevent any references to
         * other classes from being transferred over remoting.
         */
1242 1243
        private static final Logger LOGGER = Logger.getLogger(ListPossibleNames.class.getName());
        
1244 1245 1246 1247 1248 1249
        public List<String> call() throws IOException {
            List<String> names = new ArrayList<String>();

            Enumeration<NetworkInterface> nis = NetworkInterface.getNetworkInterfaces();
            while (nis.hasMoreElements()) {
                NetworkInterface ni =  nis.nextElement();
1250
                LOGGER.log(Level.FINE, "Listing up IP addresses for {0}", ni.getDisplayName());
1251 1252 1253
                Enumeration<InetAddress> e = ni.getInetAddresses();
                while (e.hasMoreElements()) {
                    InetAddress ia =  e.nextElement();
1254
                    if(ia.isLoopbackAddress()) {
1255
                        LOGGER.log(Level.FINE, "{0} is a loopback address", ia);
1256 1257 1258 1259
                        continue;
                    }

                    if(!(ia instanceof Inet4Address)) {
1260
                        LOGGER.log(Level.FINE, "{0} is not an IPv4 address", ia);
1261 1262 1263
                        continue;
                    }

1264
                    LOGGER.log(Level.FINE, "{0} is a viable candidate", ia);
1265 1266 1267 1268 1269 1270 1271 1272
                    names.add(ia.getHostAddress());
                }
            }
            return names;
        }
        private static final long serialVersionUID = 1L;
    }

1273
    private static class GetFallbackName extends MasterToSlaveCallable<String,IOException> {
K
kohsuke 已提交
1274 1275 1276 1277 1278 1279
        public String call() throws IOException {
            return System.getProperty("host.name");
        }
        private static final long serialVersionUID = 1L;
    }

1280 1281 1282 1283
    public static final ExecutorService threadPoolForRemoting = new ContextResettingExecutorService(
            Executors.newCachedThreadPool(
                    new ExceptionCatchingThreadFactory(
                            new NamingThreadFactory(new DaemonThreadFactory(), "Computer.threadPoolForRemoting"))));
K
kohsuke 已提交
1284

K
kohsuke 已提交
1285 1286 1287 1288 1289 1290
//
//
// UI
//
//
    public void doRssAll( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
1291
        rss(req, rsp, " all builds", getBuilds());
K
kohsuke 已提交
1292
    }
1293 1294

    public void doRssFailed(StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
1295
        rss(req, rsp, " failed builds", getBuilds().failureOnly());
K
kohsuke 已提交
1296 1297
    }
    private void rss(StaplerRequest req, StaplerResponse rsp, String suffix, RunList runs) throws IOException, ServletException {
1298 1299
        RSS.forwardToRss(getDisplayName() + suffix, getUrl(),
                runs.newBuilds(), Run.FEED_ADAPTER, req, rsp);
1300
    }
K
kohsuke 已提交
1301

1302
    @RequirePOST
1303
    public HttpResponse doToggleOffline(@QueryParameter String offlineMessage) throws IOException, ServletException {
1304
        if(!temporarilyOffline) {
1305
            checkPermission(DISCONNECT);
1306 1307
            offlineMessage = Util.fixEmptyAndTrim(offlineMessage);
            setTemporarilyOffline(!temporarilyOffline,
1308
                    new OfflineCause.UserCause(User.current(), offlineMessage));
1309
        } else {
1310
            checkPermission(CONNECT);
1311 1312
            setTemporarilyOffline(!temporarilyOffline,null);
        }
1313
        return HttpResponses.redirectToDot();
K
kohsuke 已提交
1314
    }
1315

1316
    @RequirePOST
1317 1318 1319 1320
    public HttpResponse doChangeOfflineCause(@QueryParameter String offlineMessage) throws IOException, ServletException {
        checkPermission(DISCONNECT);
        offlineMessage = Util.fixEmptyAndTrim(offlineMessage);
        setTemporarilyOffline(true,
1321
                new OfflineCause.UserCause(User.current(), offlineMessage));
1322
        return HttpResponses.redirectToDot();
K
kohsuke 已提交
1323
    }
1324

T
tblack 已提交
1325 1326 1327 1328
    public Api getApi() {
        return new Api(this);
    }

1329 1330 1331
    /**
     * Dumps the contents of the export table.
     */
1332
    public void doDumpExportTable( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException, InterruptedException {
1333
        // this is a debug probe and may expose sensitive information
1334
        checkPermission(Jenkins.ADMINISTER);
1335 1336 1337

        rsp.setContentType("text/plain");
        PrintWriter w = new PrintWriter(rsp.getCompressedWriter(req));
1338 1339 1340 1341
        VirtualChannel vc = getChannel();
        if (vc instanceof Channel) {
            w.println("Master to slave");
            ((Channel)vc).dumpExportTable(w);
1342
            w.flush(); // flush here once so that even if the dump from the agent fails, the client gets some useful info
1343 1344 1345 1346 1347 1348

            w.println("\n\n\nSlave to master");
            w.print(vc.call(new DumpExportTableTask()));
        } else {
            w.println(Messages.Computer_BadChannel());
        }
1349 1350
        w.close();
    }
K
kohsuke 已提交
1351

1352
    private static final class DumpExportTableTask extends MasterToSlaveCallable<String,IOException> {
1353 1354 1355 1356 1357 1358 1359 1360 1361
        public String call() throws IOException {
            StringWriter sw = new StringWriter();
            PrintWriter pw = new PrintWriter(sw);
            Channel.current().dumpExportTable(pw);
            pw.close();
            return sw.toString();
        }
    }

1362 1363 1364 1365 1366
    /**
     * For system diagnostics.
     * Run arbitrary Groovy script.
     */
    public void doScript(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
1367
        _doScript(req, rsp, "_script.jelly");
1368 1369 1370 1371 1372 1373
    }

    /**
     * Run arbitrary Groovy script and return result as plain text.
     */
    public void doScriptText(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
1374
        _doScript(req, rsp, "_scriptText.jelly");
1375 1376
    }

1377
    protected void _doScript(StaplerRequest req, StaplerResponse rsp, String view) throws IOException, ServletException {
1378
        Jenkins._doScript(req, rsp, req.getView(this, view), getChannel(), getACL());
K
kohsuke 已提交
1379 1380
    }

K
kohsuke 已提交
1381 1382 1383
    /**
     * Accepts the update to the node configuration.
     */
1384
    @RequirePOST
1385
    public void doConfigSubmit( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException, FormException {
1386
        checkPermission(CONFIGURE);
S
Seiji Sogabe 已提交
1387

1388 1389
        String proposedName = Util.fixEmptyAndTrim(req.getSubmittedForm().getString("name"));
        Jenkins.checkGoodName(proposedName);
1390

1391 1392 1393 1394
        Node node = getNode();
        if (node == null) {
            throw new ServletException("No such node " + nodeName);
        }
1395 1396 1397 1398 1399 1400

        if ((!proposedName.equals(nodeName))
                && Jenkins.getActiveInstance().getNode(proposedName) != null) {
            throw new FormException(Messages.ComputerSet_SlaveAlreadyExists(proposedName), "name");
        }

1401
        Node result = node.reconfigure(req, req.getSubmittedForm());
1402 1403
        replaceBy(result);

1404
        // take the user back to the agent top page.
1405
        rsp.sendRedirect2("../" + result.getNodeName() + '/');
1406 1407 1408 1409 1410 1411 1412 1413
    }

    /**
     * Accepts <tt>config.xml</tt> submission, as well as serve it.
     */
    @WebMethod(name = "config.xml")
    public void doConfigDotXml(StaplerRequest req, StaplerResponse rsp)
            throws IOException, ServletException {
1414

1415 1416
        if (req.getMethod().equals("GET")) {
            // read
1417
            checkPermission(EXTENDED_READ);
1418
            rsp.setContentType("application/xml");
1419 1420
            Node node = getNode();
            if (node == null) {
1421
                throw HttpResponses.notFound();
1422 1423
            }
            Jenkins.XSTREAM2.toXMLUTF8(node, rsp.getOutputStream());
1424 1425 1426 1427
            return;
        }
        if (req.getMethod().equals("POST")) {
            // submission
1428
            updateByXml(req.getInputStream());
1429 1430 1431 1432 1433 1434 1435 1436 1437 1438
            return;
        }

        // huh?
        rsp.sendError(SC_BAD_REQUEST);
    }

    /**
     * Replaces the current {@link Node} by another one.
     */
1439
    private void replaceBy(final Node newNode) throws ServletException, IOException {
1440
        final Jenkins app = Jenkins.getInstance();
K
kohsuke 已提交
1441

1442
        // use the queue lock until Nodes has a way of directly modifying a single node.
1443
        Queue.withLock(new NotReallyRoleSensitiveCallable<Void, IOException>() {
1444 1445 1446 1447 1448
            public Void call() throws IOException {
                List<Node> nodes = new ArrayList<Node>(app.getNodes());
                Node node = getNode();
                int i  = (node != null) ? nodes.indexOf(node) : -1;
                if(i<0) {
1449
                    throw new IOException("This agent appears to be removed while you were editing the configuration");
1450 1451 1452 1453
                }
                nodes.set(i, newNode);
                app.setNodes(nodes);
                return null;
K
kohsuke 已提交
1454
            }
1455
        });
K
kohsuke 已提交
1456
    }
1457

1458 1459 1460
    /**
     * Updates Job by its XML definition.
     *
1461
     * @since 1.526
1462 1463
     */
    public void updateByXml(final InputStream source) throws IOException, ServletException {
1464
        checkPermission(CONFIGURE);
1465 1466 1467 1468
        Node result = (Node)Jenkins.XSTREAM2.fromXML(source);
        replaceBy(result);
    }

K
kohsuke 已提交
1469
    /**
1470
     * Really deletes the agent.
K
kohsuke 已提交
1471
     */
1472
    @RequirePOST
1473
    public HttpResponse doDoDelete() throws IOException {
K
kohsuke 已提交
1474
        checkPermission(DELETE);
1475
        Node node = getNode();
1476 1477 1478 1479 1480
        if (node != null) {
            Jenkins.getInstance().removeNode(node);
        } else {
            AbstractCIBase app = Jenkins.getInstance();
            app.removeComputer(this);
1481
        }
1482
        return new HttpRedirect("..");
K
kohsuke 已提交
1483 1484
    }

1485 1486 1487 1488 1489 1490 1491
    /**
     * Blocks until the node becomes online/offline.
     */
    @CLIMethod(name="wait-node-online")
    public void waitUntilOnline() throws InterruptedException {
        synchronized (statusChangeLock) {
            while (!isOnline())
K
oops  
Kohsuke Kawaguchi 已提交
1492
                statusChangeLock.wait(1000);
1493 1494 1495 1496 1497 1498 1499
        }
    }

    @CLIMethod(name="wait-node-offline")
    public void waitUntilOffline() throws InterruptedException {
        synchronized (statusChangeLock) {
            while (!isOffline())
K
oops  
Kohsuke Kawaguchi 已提交
1500
                statusChangeLock.wait(1000);
1501 1502 1503
        }
    }

1504 1505 1506 1507
    /**
     * Handles incremental log.
     */
    public void doProgressiveLog( StaplerRequest req, StaplerResponse rsp) throws IOException {
1508
        getLogText().doProgressText(req, rsp);
1509
    }
1510

K
kohsuke 已提交
1511 1512 1513
    /**
     * Gets the current {@link Computer} that the build is running.
     * This method only works when called during a build, such as by
M
Mark Waite 已提交
1514
     * {@link hudson.tasks.Publisher}, {@link hudson.tasks.BuildWrapper}, etc.
1515
     * @return the {@link Computer} associated with {@link Executor#currentExecutor}, or (consistently as of 1.591) null if not on an executor thread
K
kohsuke 已提交
1516
     */
1517
    public static @Nullable Computer currentComputer() {
1518
        Executor e = Executor.currentExecutor();
1519
        return e != null ? e.getOwner() : null;
K
kohsuke 已提交
1520
    }
1521 1522

    /**
1523
     * Returns {@code true} if the computer is accepting tasks. Needed to allow agents programmatic suspension of task
1524 1525 1526
     * scheduling that does not overlap with being offline.
     *
     * @return {@code true} if the computer is accepting tasks
1527 1528
     * @see hudson.slaves.RetentionStrategy#isAcceptingTasks(Computer)
     * @see hudson.model.Node#isAcceptingTasks()
1529
     */
1530
    @OverrideMustInvoke(When.ANYTIME)
1531
    public boolean isAcceptingTasks() {
1532 1533
        final Node node = getNode();
        return getRetentionStrategy().isAcceptingTasks(this) && (node == null || node.isAcceptingTasks());
1534
    }
K
kohsuke 已提交
1535

1536 1537 1538 1539 1540
    /**
     * Used for CLI binding.
     */
    @CLIResolver
    public static Computer resolveForCLI(
1541
            @Argument(required=true,metaVar="NAME",usage="Agent name, or empty string for master") String name) throws CmdLineException {
1542
        Jenkins h = Jenkins.getInstance();
1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553
        Computer item = h.getComputer(name);
        if (item==null) {
            List<String> names = new ArrayList<String>();
            for (Computer c : h.getComputers())
                if (c.getName().length()>0)
                    names.add(c.getName());
            throw new CmdLineException(null,Messages.Computer_NoSuchSlaveExists(name,EditDistance.findNearest(name,names)));
        }
        return item;
    }

1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580
    /**
     * Relocate log files in the old location to the new location.
     *
     * Files were used to be $JENKINS_ROOT/slave-NAME.log (and .1, .2, ...)
     * but now they are at $JENKINS_ROOT/logs/slaves/NAME/slave.log (and .1, .2, ...)
     *
     * @see #getLogFile()
     */
    @Initializer
    public static void relocateOldLogs() {
        relocateOldLogs(Jenkins.getInstance().getRootDir());
    }

    /*package*/ static void relocateOldLogs(File dir) {
        final Pattern logfile = Pattern.compile("slave-(.*)\\.log(\\.[0-9]+)?");
        File[] logfiles = dir.listFiles(new FilenameFilter() {
            public boolean accept(File dir, String name) {
                return logfile.matcher(name).matches();
            }
        });
        if (logfiles==null)     return;

        for (File f : logfiles) {
            Matcher m = logfile.matcher(f.getName());
            if (m.matches()) {
                File newLocation = new File(dir, "logs/slaves/" + m.group(1) + "/slave.log" + Util.fixNull(m.group(2)));
                newLocation.getParentFile().mkdirs();
1581 1582
                boolean relocationSuccessful=f.renameTo(newLocation);
                if (relocationSuccessful) { // The operation will fail if mkdir fails
1583 1584 1585 1586
                    LOGGER.log(Level.INFO, "Relocated log file {0} to {1}",new Object[] {f.getPath(),newLocation.getPath()});
                } else {
                    LOGGER.log(Level.WARNING, "Cannot relocate log file {0} to {1}",new Object[] {f.getPath(),newLocation.getPath()});
                }
1587 1588 1589 1590 1591 1592
            } else {
                assert false;
            }
        }
    }

1593 1594 1595 1596
    /**
     * A value class to provide a consistent snapshot view of the state of an executor to avoid race conditions
     * during rendering of the executors list.
     *
S
Stephen Connolly 已提交
1597
     * @since 1.607
1598 1599
     */
    @Restricted(NoExternalUse.class)
1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658
    public static class DisplayExecutor implements ModelObject {

        @Nonnull
        private final String displayName;
        @Nonnull
        private final String url;
        @Nonnull
        private final Executor executor;

        public DisplayExecutor(@Nonnull String displayName, @Nonnull String url, @Nonnull Executor executor) {
            this.displayName = displayName;
            this.url = url;
            this.executor = executor;
        }

        @Override
        @Nonnull
        public String getDisplayName() {
            return displayName;
        }

        @Nonnull
        public String getUrl() {
            return url;
        }

        @Nonnull
        public Executor getExecutor() {
            return executor;
        }

        @Override
        public String toString() {
            final StringBuilder sb = new StringBuilder("DisplayExecutor{");
            sb.append("displayName='").append(displayName).append('\'');
            sb.append(", url='").append(url).append('\'');
            sb.append(", executor=").append(executor);
            sb.append('}');
            return sb.toString();
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (o == null || getClass() != o.getClass()) {
                return false;
            }

            DisplayExecutor that = (DisplayExecutor) o;

            if (!executor.equals(that.executor)) {
                return false;
            }

            return true;
        }

1659
        @Extension(ordinal = Double.MAX_VALUE)
1660 1661 1662 1663 1664 1665 1666 1667
        @Restricted(DoNotUse.class)
        public static class InternalComputerListener extends ComputerListener {
            @Override
            public void onOnline(Computer c, TaskListener listener) throws IOException, InterruptedException {
                c.cachedEnvironment = null;
            }
        }

1668 1669 1670 1671 1672 1673
        @Override
        public int hashCode() {
            return executor.hashCode();
        }
    }

1674 1675 1676
    /**
     * Used to trace requests to terminate a computer.
     *
S
Stephen Connolly 已提交
1677
     * @since 1.607
1678
     */
1679
    public static class TerminationRequest extends RuntimeException {
1680
        private final long when;
1681 1682
        public TerminationRequest(String message) {
            super(message);
1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693
            this.when = System.currentTimeMillis();
        }

        /**
         * Returns the when the termination request was created.
         *
         * @return the difference, measured in milliseconds, between
         * the time of the termination request and midnight, January 1, 1970 UTC.
         */
        public long getWhen() {
            return when;
1694 1695 1696
        }
    }

K
kohsuke 已提交
1697
    public static final PermissionGroup PERMISSIONS = new PermissionGroup(Computer.class,Messages._Computer_Permissions_Title());
1698
    public static final Permission CONFIGURE = new Permission(PERMISSIONS,"Configure", Messages._Computer_ConfigurePermission_Description(), Permission.CONFIGURE, PermissionScope.COMPUTER);
K
kohsuke 已提交
1699
    /**
1700
     * @since 1.532
K
kohsuke 已提交
1701
     */
1702
    public static final Permission EXTENDED_READ = new Permission(PERMISSIONS,"ExtendedRead", Messages._Computer_ExtendedReadPermission_Description(), CONFIGURE, Boolean.getBoolean("hudson.security.ExtendedReadPermission"), new PermissionScope[]{PermissionScope.COMPUTER});
1703
    public static final Permission DELETE = new Permission(PERMISSIONS,"Delete", Messages._Computer_DeletePermission_Description(), Permission.DELETE, PermissionScope.COMPUTER);
1704
    public static final Permission CREATE = new Permission(PERMISSIONS,"Create", Messages._Computer_CreatePermission_Description(), Permission.CREATE, PermissionScope.COMPUTER);
1705 1706
    public static final Permission DISCONNECT = new Permission(PERMISSIONS,"Disconnect", Messages._Computer_DisconnectPermission_Description(), Jenkins.ADMINISTER, PermissionScope.COMPUTER);
    public static final Permission CONNECT = new Permission(PERMISSIONS,"Connect", Messages._Computer_ConnectPermission_Description(), DISCONNECT, PermissionScope.COMPUTER);
1707
    public static final Permission BUILD = new Permission(PERMISSIONS, "Build", Messages._Computer_BuildPermission_Description(),  Permission.WRITE, PermissionScope.COMPUTER);
K
kohsuke 已提交
1708

1709
    private static final Logger LOGGER = Logger.getLogger(Computer.class.getName());
K
kohsuke 已提交
1710
}