SlaveComputer.java 34.6 KB
Newer Older
K
kohsuke 已提交
1 2
/*
 * The MIT License
3
 *
K
kohsuke 已提交
4
 * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Stephen Connolly
5
 *
K
kohsuke 已提交
6 7 8 9 10 11
 * 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:
12
 *
K
kohsuke 已提交
13 14
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
15
 *
K
kohsuke 已提交
16 17 18 19 20 21 22 23
 * 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.
 */
24 25
package hudson.slaves;

K
Kohsuke Kawaguchi 已提交
26
import hudson.AbortException;
27
import hudson.FilePath;
28
import hudson.Functions;
K
kohsuke 已提交
29
import hudson.Util;
30
import hudson.console.ConsoleLogFilter;
K
Kohsuke Kawaguchi 已提交
31 32 33 34 35 36 37 38 39
import hudson.model.Computer;
import hudson.model.Executor;
import hudson.model.ExecutorListener;
import hudson.model.Node;
import hudson.model.Queue;
import hudson.model.Slave;
import hudson.model.TaskListener;
import hudson.model.User;
import hudson.remoting.Channel;
40
import hudson.remoting.ChannelBuilder;
41
import hudson.remoting.ChannelClosedException;
42
import hudson.remoting.Launcher;
K
Kohsuke Kawaguchi 已提交
43
import hudson.remoting.VirtualChannel;
44
import hudson.security.ACL;
45
import hudson.slaves.OfflineCause.ChannelTermination;
K
Kohsuke Kawaguchi 已提交
46 47 48 49
import hudson.util.Futures;
import hudson.util.NullStream;
import hudson.util.RingBufferLogHandler;
import hudson.util.StreamTaskListener;
50
import hudson.util.VersionNumber;
51 52
import hudson.util.io.RewindableFileOutputStream;
import hudson.util.io.RewindableRotatingFileOutputStream;
53 54 55
import jenkins.model.Jenkins;
import jenkins.security.ChannelConfigurator;
import jenkins.security.MasterToSlaveCallable;
56
import jenkins.slaves.EncryptedSlaveAgentJnlpFile;
57
import jenkins.slaves.JnlpSlaveAgentProtocol;
58
import jenkins.slaves.RemotingVersionInfo;
59
import jenkins.slaves.systemInfo.SlaveSystemInfo;
60
import jenkins.util.SystemProperties;
61 62
import org.acegisecurity.context.SecurityContext;
import org.acegisecurity.context.SecurityContextHolder;
63 64 65 66 67
import org.kohsuke.stapler.HttpRedirect;
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
K
Kohsuke Kawaguchi 已提交
68
import org.kohsuke.stapler.WebMethod;
K
Kohsuke Kawaguchi 已提交
69
import org.kohsuke.stapler.interceptor.RequirePOST;
70

71
import javax.annotation.CheckForNull;
72
import javax.annotation.OverridingMethodsMustInvokeSuper;
K
Kohsuke Kawaguchi 已提交
73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
import javax.servlet.ServletException;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.nio.charset.Charset;
import java.security.Security;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Future;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;

90
import static hudson.slaves.SlaveComputer.LogHolder.SLAVE_LOG_HANDLER;
91
import org.jenkinsci.remoting.util.LoggingChannelListener;
K
Kohsuke Kawaguchi 已提交
92

93

94 95 96 97 98
/**
 * {@link Computer} for {@link Slave}s.
 *
 * @author Kohsuke Kawaguchi
 */
K
kohsuke 已提交
99
public class SlaveComputer extends Computer {
100 101 102 103
    private volatile Channel channel;
    private volatile transient boolean acceptingTasks = true;
    private Charset defaultCharset;
    private Boolean isUnix;
K
kohsuke 已提交
104 105
    /**
     * Effective {@link ComputerLauncher} that hides the details of
106
     * how we launch a agent agent on this computer.
K
kohsuke 已提交
107 108 109
     *
     * <p>
     * This is normally the same as {@link Slave#getLauncher()} but
110
     * can be different. See {@link #grabLauncher(Node)}.
K
kohsuke 已提交
111
     */
112 113
    private ComputerLauncher launcher;

114 115 116
    /**
     * Perpetually writable log file.
     */
117
    private final RewindableFileOutputStream log;
118 119 120 121 122 123 124

    /**
     * {@link StreamTaskListener} that wraps {@link #log}, hence perpetually writable.
     */
    private final TaskListener taskListener;


125 126 127 128 129 130 131
    /**
     * Number of failed attempts to reconnect to this node
     * (so that if we keep failing to reconnect, we can stop
     * trying.)
     */
    private transient int numRetryAttempt;

K
kohsuke 已提交
132 133 134 135 136 137 138 139
    /**
     * Tracks the status of the last launch operation, which is always asynchronous.
     * This can be used to wait for the completion, or cancel the launch activity.
     */
    private volatile Future<?> lastConnectActivity = null;

    private Object constructed = new Object();

140 141
    private transient volatile String absoluteRemoteFs;

K
kohsuke 已提交
142 143
    public SlaveComputer(Slave slave) {
        super(slave);
144
        this.log = new RewindableRotatingFileOutputStream(getLogFile(), 10);
145
        this.taskListener = new StreamTaskListener(decorate(this.log));
146
        assert slave.getNumExecutors()!=0 : "Computer created with 0 executors";
K
kohsuke 已提交
147
    }
148

149 150 151 152 153 154 155 156 157 158 159 160 161 162
    /**
     * Uses {@link ConsoleLogFilter} to decorate logger.
     */
    private OutputStream decorate(OutputStream os) {
        for (ConsoleLogFilter f : ConsoleLogFilter.all()) {
            try {
                os = f.decorateLogger(this,os);
            } catch (IOException|InterruptedException e) {
                LOGGER.log(Level.WARNING, "Failed to filter log with "+f, e);
            }
        }
        return os;
    }

163 164 165 166
    /**
     * {@inheritDoc}
     */
    @Override
167
    @OverridingMethodsMustInvokeSuper
168
    public boolean isAcceptingTasks() {
169
        // our boolean flag is an override on any additional programmatic reasons why this agent might not be
170 171
        // accepting tasks.
        return acceptingTasks && super.isAcceptingTasks();
172 173
    }

K
Kohsuke Kawaguchi 已提交
174 175 176
    /**
     * @since 1.498
     */
177 178 179 180
    public String getJnlpMac() {
        return JnlpSlaveAgentProtocol.SLAVE_SECRET.mac(getName());
    }

181
    /**
182
     * Allows suspension of tasks being accepted by the agent computer. While this could be called by a
183 184 185 186
     * {@linkplain hudson.slaves.ComputerLauncher} or a {@linkplain hudson.slaves.RetentionStrategy}, such usage
     * can result in fights between multiple actors calling setting differential values. A better approach
     * is to override {@link hudson.slaves.RetentionStrategy#isAcceptingTasks(hudson.model.Computer)} if the
     * {@link hudson.slaves.RetentionStrategy} needs to control availability.
187
     *
188
     * @param acceptingTasks {@code true} if the agent can accept tasks.
189 190 191 192 193
     */
    public void setAcceptingTasks(boolean acceptingTasks) {
        this.acceptingTasks = acceptingTasks;
    }

194
    @Override
195 196 197 198
    public Boolean isUnix() {
        return isUnix;
    }

199
    @CheckForNull
200
    @Override
201
    public Slave getNode() {
J
Jesse Glick 已提交
202 203 204 205 206 207 208
        Node node = super.getNode();
        if (node == null || node instanceof Slave) {
            return (Slave)node;
        } else {
            logger.log(Level.WARNING, "found an unexpected kind of node {0} from {1} with nodeName={2}", new Object[] {node, this, nodeName});
            return null;
        }
209 210
    }

211 212 213 214
    /**
     * Return the {@code TaskListener} for this SlaveComputer. Never null
     * @since 2.9
     */
N
Nicolas De Loof 已提交
215 216 217 218
    public TaskListener getListener() {
        return taskListener;
    }

K
kohsuke 已提交
219 220 221 222 223 224 225 226
    @Override
    public String getIcon() {
        Future<?> l = lastConnectActivity;
        if(l!=null && !l.isDone())
            return "computer-flash.gif";
        return super.getIcon();
    }

M
mindless 已提交
227 228 229 230
    /**
     * @deprecated since 2008-05-20.
     */
    @Deprecated @Override
231 232 233 234 235 236 237 238 239
    public boolean isJnlpAgent() {
        return launcher instanceof JNLPLauncher;
    }

    @Override
    public boolean isLaunchSupported() {
        return launcher.isLaunchSupported();
    }

240 241 242 243
    /**
     * Return the {@code ComputerLauncher} for this SlaveComputer.
     * @since 1.312
     */
244 245 246 247
    public ComputerLauncher getLauncher() {
        return launcher;
    }

248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266
    /**
     * Return the {@code ComputerLauncher} for this SlaveComputer, strips off
     * any {@code DelegatingComputerLauncher}s or {@code ComputerLauncherFilter}s.
     * @since 2.83
     */
    public ComputerLauncher getDelegatedLauncher() {
        ComputerLauncher l = launcher;
        while (true) {
            if (l instanceof DelegatingComputerLauncher) {
                l = ((DelegatingComputerLauncher) l).getLauncher();
            } else if (l instanceof ComputerLauncherFilter) {
                l = ((ComputerLauncherFilter) l).getCore();
            } else {
                break;
            }
        }
        return l;
    }

267
    protected Future<?> _connect(boolean forceReconnect) {
K
kohsuke 已提交
268
        if(channel!=null)   return Futures.precomputed(null);
269
        if(!forceReconnect && isConnecting())
K
kohsuke 已提交
270
            return lastConnectActivity;
271
        if(forceReconnect && isConnecting())
K
kohsuke 已提交
272
            logger.fine("Forcing a reconnect on "+getName());
273 274

        closeChannel();
K
kohsuke 已提交
275 276
        return lastConnectActivity = Computer.threadPoolForRemoting.submit(new java.util.concurrent.Callable<Object>() {
            public Object call() throws Exception {
277 278
                // do this on another thread so that the lengthy launch operation
                // (which is typical) won't block UI thread.
K
Kohsuke Kawaguchi 已提交
279 280 281

                ACL.impersonate(ACL.SYSTEM);    // background activity should run like a super user

K
kohsuke 已提交
282
                try {
283
                    log.rewind();
284
                    try {
T
Tom Rini 已提交
285
                        for (ComputerListener cl : ComputerListener.all())
286
                            cl.preLaunch(SlaveComputer.this, taskListener);
287
                        offlineCause = null;
288
                        launcher.launch(SlaveComputer.this, taskListener);
289
                    } catch (AbortException e) {
290
                        taskListener.error(e.getMessage());
291 292
                        throw e;
                    } catch (IOException e) {
293
                        Util.displayIOException(e,taskListener);
294
                        Functions.printStackTrace(e, taskListener.error(Messages.ComputerLauncher_unexpectedError()));
295 296
                        throw e;
                    } catch (InterruptedException e) {
297
                        Functions.printStackTrace(e, taskListener.error(Messages.ComputerLauncher_abortedLaunch()));
298
                        throw e;
299
                    } catch (Exception e) {
300
                        Functions.printStackTrace(e, taskListener.error(Messages.ComputerLauncher_unexpectedError()));
301
                        throw e;
302
                    }
303
                } finally {
304
                    if (channel==null && offlineCause == null) {
305
                        offlineCause = new OfflineCause.LaunchFailed();
306 307 308
                        for (ComputerListener cl : ComputerListener.all())
                            cl.onLaunchFailure(SlaveComputer.this, taskListener);
                    }
K
kohsuke 已提交
309
                }
310 311

                if (channel==null)
312
                    throw new IOException("Agent failed to connect, even though the launcher didn't report it. See the log output for details.");
313
                return null;
314 315 316 317 318 319 320 321 322 323 324 325 326
            }
        });
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void taskAccepted(Executor executor, Queue.Task task) {
        super.taskAccepted(executor, task);
        if (launcher instanceof ExecutorListener) {
            ((ExecutorListener)launcher).taskAccepted(executor, task);
        }
327

328 329 330 331
        //getNode() can return null at indeterminate times when nodes go offline
        Slave node = getNode();
        if (node != null && node.getRetentionStrategy() instanceof ExecutorListener) {
            ((ExecutorListener)node.getRetentionStrategy()).taskAccepted(executor, task);
332 333 334 335 336 337 338 339 340 341 342 343
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void taskCompleted(Executor executor, Queue.Task task, long durationMS) {
        super.taskCompleted(executor, task, durationMS);
        if (launcher instanceof ExecutorListener) {
            ((ExecutorListener)launcher).taskCompleted(executor, task, durationMS);
        }
344
        RetentionStrategy r = getRetentionStrategy();
K
kohsuke 已提交
345 346
        if (r instanceof ExecutorListener) {
            ((ExecutorListener) r).taskCompleted(executor, task, durationMS);
347 348 349 350 351 352 353 354 355 356 357 358
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void taskCompletedWithProblems(Executor executor, Queue.Task task, long durationMS, Throwable problems) {
        super.taskCompletedWithProblems(executor, task, durationMS, problems);
        if (launcher instanceof ExecutorListener) {
            ((ExecutorListener)launcher).taskCompletedWithProblems(executor, task, durationMS, problems);
        }
359 360 361
        RetentionStrategy r = getRetentionStrategy();
        if (r instanceof ExecutorListener) {
            ((ExecutorListener) r).taskCompletedWithProblems(executor, task, durationMS, problems);
362 363 364
        }
    }

K
kohsuke 已提交
365 366 367 368 369 370
    @Override
    public boolean isConnecting() {
        Future<?> l = lastConnectActivity;
        return isOffline() && l!=null && !l.isDone();
    }

371 372
    public OutputStream openLogFile() {
        try {
373 374 375
            log.rewind();
            return log;
        } catch (IOException e) {
376
            logger.log(Level.SEVERE, "Failed to create log file "+getLogFile(),e);
377
            return new NullStream();
378 379 380 381 382
        }
    }

    private final Object channelLock = new Object();

K
kohsuke 已提交
383 384 385 386
    public void setChannel(InputStream in, OutputStream out, TaskListener taskListener, Channel.Listener listener) throws IOException, InterruptedException {
        setChannel(in,out,taskListener.getLogger(),listener);
    }

387
    /**
388
     * Creates a {@link Channel} from the given stream and sets that to this agent.
K
kohsuke 已提交
389 390
     *
     * @param in
N
Nicolas De Loof 已提交
391
     *      Stream connected to the remote agent. It's the caller's responsibility to do
K
kohsuke 已提交
392 393 394 395 396 397 398 399 400 401
     *      buffering on this stream, if that's necessary.
     * @param out
     *      Stream connected to the remote peer. It's the caller's responsibility to do
     *      buffering on this stream, if that's necessary.
     * @param launchLog
     *      If non-null, receive the portion of data in <tt>is</tt> before
     *      the data goes into the "binary mode". This is useful
     *      when the established communication channel might include some data that might
     *      be useful for debugging/trouble-shooting.
     * @param listener
402
     *      Gets a notification when the channel closes, to perform clean up. Can be null.
403 404
     *      By the time this method is called, the cause of the termination is reported to the user,
     *      so the implementation of the listener doesn't need to do that again.
405 406
     */
    public void setChannel(InputStream in, OutputStream out, OutputStream launchLog, Channel.Listener listener) throws IOException, InterruptedException {
K
Kohsuke Kawaguchi 已提交
407 408 409 410
        ChannelBuilder cb = new ChannelBuilder(nodeName,threadPoolForRemoting)
            .withMode(Channel.Mode.NEGOTIATE)
            .withHeaderStream(launchLog);

411 412
        for (ChannelConfigurator cc : ChannelConfigurator.all()) {
            cc.onChannelBuilding(cb,this);
K
Kohsuke Kawaguchi 已提交
413 414 415
        }

        Channel channel = cb.build(in,out);
416 417 418
        setChannel(channel,launchLog,listener);
    }

J
Jesse Glick 已提交
419 420 421 422 423 424 425 426
    /**
     * Shows {@link Channel#classLoadingCount}.
     * @since 1.495
     */
    public int getClassLoadingCount() throws IOException, InterruptedException {
        return channel.call(new LoadingCount(false));
    }

427 428 429
    /**
     * Shows {@link Channel#classLoadingPrefetchCacheCount}.
     * @return -1 in case that capability is not supported
J
Jesse Glick 已提交
430
     * @since 1.519
431 432 433 434 435 436 437 438
     */
    public int getClassLoadingPrefetchCacheCount() throws IOException, InterruptedException {
        if (!channel.remoteCapability.supportsPrefetch()) {
            return -1;
        }
        return channel.call(new LoadingPrefetchCacheCount());
    }

J
Jesse Glick 已提交
439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462
    /**
     * Shows {@link Channel#resourceLoadingCount}.
     * @since 1.495
     */
    public int getResourceLoadingCount() throws IOException, InterruptedException {
        return channel.call(new LoadingCount(true));
    }

    /**
     * Shows {@link Channel#classLoadingTime}.
     * @since 1.495
     */
    public long getClassLoadingTime() throws IOException, InterruptedException {
        return channel.call(new LoadingTime(false));
    }

    /**
     * Shows {@link Channel#resourceLoadingTime}.
     * @since 1.495
     */
    public long getResourceLoadingTime() throws IOException, InterruptedException {
        return channel.call(new LoadingTime(true));
    }

463
    /**
464
     * Returns the remote FS root absolute path or {@code null} if the agent is off-line. The absolute path may change
465 466 467
     * between connections if the connection method does not provide a consistent working directory and the node's
     * remote FS is specified as a relative path.
     *
468
     * @return the remote FS root absolute path or {@code null} if the agent is off-line.
469
     * @since 1.606
470
     */
471 472 473 474 475
    @CheckForNull
    public String getAbsoluteRemoteFs() {
        return channel == null ? null : absoluteRemoteFs;
    }

476
    static class LoadingCount extends MasterToSlaveCallable<Integer,RuntimeException> {
J
Jesse Glick 已提交
477 478 479 480 481 482
        private final boolean resource;
        LoadingCount(boolean resource) {
            this.resource = resource;
        }
        @Override public Integer call() {
            Channel c = Channel.current();
O
Oleg Nenashev 已提交
483 484 485
            if (c == null) {
                return -1;
            }
J
Jesse Glick 已提交
486 487 488 489
            return resource ? c.resourceLoadingCount.get() : c.classLoadingCount.get();
        }
    }

490
    static class LoadingPrefetchCacheCount extends MasterToSlaveCallable<Integer,RuntimeException> {
491 492 493 494 495
        @Override public Integer call() {
            return Channel.current().classLoadingPrefetchCacheCount.get();
        }
    }

496
    static class LoadingTime extends MasterToSlaveCallable<Long,RuntimeException> {
J
Jesse Glick 已提交
497 498 499 500 501 502
        private final boolean resource;
        LoadingTime(boolean resource) {
            this.resource = resource;
        }
        @Override public Long call() {
            Channel c = Channel.current();
O
Oleg Nenashev 已提交
503 504 505
            if (c == null) {
                return Long.valueOf(-1);
            }
J
Jesse Glick 已提交
506 507 508 509
            return resource ? c.resourceLoadingTime.get() : c.classLoadingTime.get();
        }
    }

510
    /**
511
     * Sets up the connection through an existing channel.
J
Jesse Glick 已提交
512
     * @param channel the channel to use; <strong>warning:</strong> callers are expected to have called {@link ChannelConfigurator} already
513 514 515
     * @since 1.444
     */
    public void setChannel(Channel channel, OutputStream launchLog, Channel.Listener listener) throws IOException, InterruptedException {
516 517 518
        if(this.channel!=null)
            throw new IllegalStateException("Already connected");

519 520
        final TaskListener taskListener = new StreamTaskListener(launchLog);
        PrintStream log = taskListener.getLogger();
521

522 523
        channel.setProperty(SlaveComputer.class, this);

524
        channel.addListener(new LoggingChannelListener(logger, Level.FINEST) {
525
            @Override
526 527
            public void onClosed(Channel c, IOException cause) {
                // Orderly shutdown will have null exception
528 529
                if (cause!=null) {
                    offlineCause = new ChannelTermination(cause);
530
                    Functions.printStackTrace(cause, taskListener.error("Connection terminated"));
531 532 533
                } else {
                    taskListener.getLogger().println("Connection terminated");
                }
534
                closeChannel();
535 536 537 538 539 540
                try {
                    launcher.afterDisconnect(SlaveComputer.this, taskListener);
                } catch (Throwable t) {
                    LogRecord lr = new LogRecord(Level.SEVERE,
                            "Launcher {0}'s afterDisconnect method propagated an exception when {1}'s connection was closed: {2}");
                    lr.setThrown(t);
541
                    lr.setParameters(new Object[]{launcher, SlaveComputer.this.getName(), t.getMessage()});
542 543
                    logger.log(lr);
                }
544 545
            }
        });
546 547
        if(listener!=null)
            channel.addListener(listener);
548

549
        String slaveVersion = channel.call(new SlaveVersion());
N
Nicolas De Loof 已提交
550
        log.println("Remoting version: " + slaveVersion);
551 552 553 554 555 556
        VersionNumber agentVersion = new VersionNumber(slaveVersion);
        if (agentVersion.isOlderThan(RemotingVersionInfo.getMinimumSupportedVersion())) {
            log.println(String.format("WARNING: Remoting version is older than a minimum required one (%s). " +
                    "Connection will not be rejected, but the compatibility is NOT guaranteed",
                    RemotingVersionInfo.getMinimumSupportedVersion()));
        }
557

558 559 560 561 562
        boolean _isUnix = channel.call(new DetectOS());
        log.println(_isUnix? hudson.model.Messages.Slave_UnixSlave():hudson.model.Messages.Slave_WindowsSlave());

        String defaultCharsetName = channel.call(new DetectDefaultCharset());

563 564 565 566
        Slave node = getNode();
        if (node == null) { // Node has been disabled/removed during the connection
            throw new IOException("Node "+nodeName+" has been deleted during the channel setup");
        }
567

568 569 570 571
        String remoteFS = node.getRemoteFS();
        if (Util.isRelativePath(remoteFS)) {
            remoteFS = channel.call(new AbsolutePath(remoteFS));
            log.println("NOTE: Relative remote path resolved to: "+remoteFS);
572
        }
573 574 575 576
        if(_isUnix && !remoteFS.contains("/") && remoteFS.contains("\\"))
            log.println("WARNING: "+remoteFS
                    +" looks suspiciously like Windows path. Maybe you meant "+remoteFS.replace('\\','/')+"?");
        FilePath root = new FilePath(channel,remoteFS);
577

K
Kohsuke Kawaguchi 已提交
578 579 580 581 582
        // reference counting problem is known to happen, such as JENKINS-9017, and so as a preventive measure
        // we pin the base classloader so that it'll never get GCed. When this classloader gets released,
        // it'll have a catastrophic impact on the communication.
        channel.pinClassLoader(getClass().getClassLoader());

583
        channel.call(new SlaveInitializer(DEFAULT_RING_BUFFER_SIZE));
584 585 586 587 588 589 590 591
        SecurityContext old = ACL.impersonate(ACL.SYSTEM);
        try {
            for (ComputerListener cl : ComputerListener.all()) {
                cl.preOnline(this,channel,root,taskListener);
            }
        } finally {
            SecurityContextHolder.setContext(old);
        }
592

593 594
        offlineCause = null;

595 596 597
        // update the data structure atomically to prevent others from seeing a channel that's not properly initialized yet
        synchronized(channelLock) {
            if(this.channel!=null) {
598
                // check again. we used to have this entire method in a big synchronization block,
599 600 601 602 603 604 605 606 607 608 609
                // but Channel constructor blocks for an external process to do the connection
                // if CommandLauncher is used, and that cannot be interrupted because it blocks at InputStream.
                // so if the process hangs, it hangs the thread in a lock, and since Hudson will try to relaunch,
                // we'll end up queuing the lot of threads in a pseudo deadlock.
                // This implementation prevents that by avoiding a lock. HUDSON-1705 is likely a manifestation of this.
                channel.close();
                throw new IllegalStateException("Already connected");
            }
            isUnix = _isUnix;
            numRetryAttempt = 0;
            this.channel = channel;
610
            this.absoluteRemoteFs = remoteFS;
611
            defaultCharset = Charset.forName(defaultCharsetName);
612 613 614 615

            synchronized (statusChangeLock) {
                statusChangeLock.notifyAll();
            }
616
        }
617 618 619 620 621 622 623 624
        old = ACL.impersonate(ACL.SYSTEM);
        try {
            for (ComputerListener cl : ComputerListener.all()) {
                cl.onOnline(this,taskListener);
            }
        } finally {
            SecurityContextHolder.setContext(old);
        }
625
        log.println("Agent successfully connected and online");
626
        Jenkins.getInstance().getQueue().scheduleMaintenance();
627 628 629
    }

    @Override
630
    public Channel getChannel() {
631 632 633 634 635 636 637 638 639 640 641
        return channel;
    }

    public Charset getDefaultCharset() {
        return defaultCharset;
    }

    public List<LogRecord> getLogRecords() throws IOException, InterruptedException {
        if(channel==null)
            return Collections.emptyList();
        else
642
            return channel.call(new SlaveLogFetcher());
643 644
    }

645
    @RequirePOST
646 647 648
    public HttpResponse doDoDisconnect(@QueryParameter String offlineMessage) throws IOException, ServletException {
        if (channel!=null) {
            //does nothing in case computer is already disconnected
649
            checkPermission(DISCONNECT);
650
            offlineMessage = Util.fixEmptyAndTrim(offlineMessage);
651
            disconnect(new OfflineCause.UserCause(User.current(), offlineMessage));
652 653
        }
        return new HttpRedirect(".");
654 655 656
    }

    @Override
657 658
    public Future<?> disconnect(OfflineCause cause) {
        super.disconnect(cause);
K
kohsuke 已提交
659
        return Computer.threadPoolForRemoting.submit(new Runnable() {
660 661 662
            public void run() {
                // do this on another thread so that any lengthy disconnect operation
                // (which could be typical) won't block UI thread.
663 664 665
                launcher.beforeDisconnect(SlaveComputer.this, taskListener);
                closeChannel();
                launcher.afterDisconnect(SlaveComputer.this, taskListener);
666 667 668 669
            }
        });
    }

670
    @RequirePOST
671 672
    public void doLaunchSlaveAgent(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
        if(channel!=null) {
673
            req.getView(this,"already-launched.jelly").forward(req, rsp);
674 675 676
            return;
        }

K
kohsuke 已提交
677
        connect(true);
678 679 680 681 682 683 684 685 686 687 688

        // TODO: would be nice to redirect the user to "launching..." wait page,
        // then spend a few seconds there and poll for the completion periodically.
        rsp.sendRedirect("log");
    }

    public void tryReconnect() {
        numRetryAttempt++;
        if(numRetryAttempt<6 || (numRetryAttempt%12)==0) {
            // initially retry several times quickly, and after that, do it infrequently.
            logger.info("Attempting to reconnect "+nodeName);
K
kohsuke 已提交
689
            connect(true);
690 691 692 693
        }
    }

    /**
694
     * Serves jar files for JNLP agents.
695
     *
M
mindless 已提交
696
     * @deprecated since 2008-08-18.
697
     *      This URL binding is no longer used and moved up directly under to {@link jenkins.model.Jenkins},
698
     *      but it's left here for now just in case some old JNLP agents request it.
699
     */
700
    @Deprecated
701 702 703 704
    public Slave.JnlpJar getJnlpJars(String fileName) {
        return new Slave.JnlpJar(fileName);
    }

K
Kohsuke Kawaguchi 已提交
705
    @WebMethod(name="slave-agent.jnlp")
706 707
    public HttpResponse doSlaveAgentJnlp(StaplerRequest req, StaplerResponse res) throws IOException, ServletException {
        return new EncryptedSlaveAgentJnlpFile(this, "slave-agent.jnlp.jelly", getName(), CONNECT);
K
Kohsuke Kawaguchi 已提交
708 709
    }

710 711 712 713
    @Override
    protected void kill() {
        super.kill();
        closeChannel();
714 715 716 717 718
        try {
            log.close();
        } catch (IOException x) {
            LOGGER.log(Level.WARNING, "Failed to close agent log", x);
        }
719 720

        try {
721
            Util.deleteRecursive(getLogDir());
722
        } catch (IOException ex) {
723
            logger.log(Level.WARNING, "Unable to delete agent logs", ex);
724
        }
725 726 727
    }

    public RetentionStrategy getRetentionStrategy() {
728
        Slave n = getNode();
729
        return n==null ? RetentionStrategy.INSTANCE : n.getRetentionStrategy();
730 731 732 733 734 735 736
    }

    /**
     * If still connected, disconnect.
     */
    private void closeChannel() {
        // TODO: race condition between this and the setChannel method.
737 738 739 740 741 742 743
        Channel c;
        synchronized (channelLock) {
            c = channel;
            channel = null;
            absoluteRemoteFs = null;
            isUnix = null;
        }
744 745 746 747 748 749
        if (c != null) {
            try {
                c.close();
            } catch (IOException e) {
                logger.log(Level.SEVERE, "Failed to terminate channel to " + getDisplayName(), e);
            }
750
            for (ComputerListener cl : ComputerListener.all())
751
                cl.onOffline(this, offlineCause);
752 753 754 755
        }
    }

    @Override
756
    protected void setNode(final Node node) {
757
        super.setNode(node);
K
kohsuke 已提交
758
        launcher = grabLauncher(node);
759

760
        // maybe the configuration was changed to relaunch the agent, so try to re-launch now.
K
kohsuke 已提交
761 762
        // "constructed==null" test is an ugly hack to avoid launching before the object is fully
        // constructed.
763
        if(constructed!=null) {
764 765 766 767 768 769 770 771
            if (node instanceof Slave) {
                Queue.withLock(new Runnable() {
                    @Override
                    public void run() {
                        ((Slave)node).getRetentionStrategy().check(SlaveComputer.this);
                    }
                });
            } else {
772
                connect(false);
773
            }
774
        }
K
kohsuke 已提交
775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790
    }

    /**
     * Grabs a {@link ComputerLauncher} out of {@link Node} to keep it in this {@link Computer}.
     * The returned launcher will be set to {@link #launcher} and used to carry out the actual launch operation.
     *
     * <p>
     * Subtypes that needs to decorate {@link ComputerLauncher} can do so by overriding this method.
     * This is useful for {@link SlaveComputer}s for clouds for example, where one normally needs
     * additional pre-launch step (such as waiting for the provisioned node to become available)
     * before the user specified launch step (like SSH connection) kicks in.
     *
     * @see ComputerLauncherFilter
     */
    protected ComputerLauncher grabLauncher(Node node) {
        return ((Slave)node).getLauncher();
791 792
    }

793
    /**
794
     * Get the agent version
795 796 797 798 799 800 801 802 803 804 805 806
     */
    public String getSlaveVersion() throws IOException, InterruptedException {
        return channel.call(new SlaveVersion());
    }

    /**
     * Get the OS description.
     */
    public String getOSDescription() throws IOException, InterruptedException {
        return channel.call(new DetectOS()) ? "Unix" : "Windows";
    }

807 808
    private static final Logger logger = Logger.getLogger(SlaveComputer.class.getName());

809
    private static final class SlaveVersion extends MasterToSlaveCallable<String,IOException> {
810 811 812 813 814
        public String call() throws IOException {
            try { return Launcher.VERSION; }
            catch (Throwable ex) { return "< 1.335"; } // Older slave.jar won't have VERSION
        }
    }
815
    private static final class DetectOS extends MasterToSlaveCallable<Boolean,IOException> {
816 817 818 819 820
        public Boolean call() throws IOException {
            return File.pathSeparatorChar==':';
        }
    }

821
    private static final class AbsolutePath extends MasterToSlaveCallable<String,IOException> {
822 823 824

        private static final long serialVersionUID = 1L;

825 826 827 828 829 830 831 832 833 834 835
        private final String relativePath;

        private AbsolutePath(String relativePath) {
            this.relativePath = relativePath;
        }

        public String call() throws IOException {
            return new File(relativePath).getAbsolutePath();
        }
    }

836
    private static final class DetectDefaultCharset extends MasterToSlaveCallable<String,IOException> {
837 838 839 840 841 842
        public String call() throws IOException {
            return Charset.defaultCharset().name();
        }
    }

    /**
843 844
     * Puts the {@link #SLAVE_LOG_HANDLER} into a separate class so that loading this class
     * in JVM doesn't end up loading tons of additional classes.
845
     */
846 847
    static final class LogHolder {
        /**
848
         * This field is used on each agent to record logs on the agent.
849
         */
850
        static RingBufferLogHandler SLAVE_LOG_HANDLER;
851
    }
852

853
    private static class SlaveInitializer extends MasterToSlaveCallable<Void,RuntimeException> {
854 855 856 857 858 859
        final int ringBufferSize;

        public SlaveInitializer(int ringBufferSize) {
            this.ringBufferSize = ringBufferSize;
        }

860
        public Void call() {
861 862
            SLAVE_LOG_HANDLER = new RingBufferLogHandler(ringBufferSize);

863 864 865
            // avoid double installation of the handler. JNLP slaves can reconnect to the master multiple times
            // and each connection gets a different RemoteClassLoader, so we need to evict them by class name,
            // not by their identity.
K
Kohsuke Kawaguchi 已提交
866
            for (Handler h : LOGGER.getHandlers()) {
867
                if (h.getClass().getName().equals(SLAVE_LOG_HANDLER.getClass().getName()))
K
Kohsuke Kawaguchi 已提交
868
                    LOGGER.removeHandler(h);
869
            }
K
Kohsuke Kawaguchi 已提交
870
            LOGGER.addHandler(SLAVE_LOG_HANDLER);
871

K
Kohsuke Kawaguchi 已提交
872
            // remove Sun PKCS11 provider if present. See http://wiki.jenkins-ci.org/display/JENKINS/Solaris+Issue+6276483
873 874 875 876 877 878
            try {
                Security.removeProvider("SunPKCS11-Solaris");
            } catch (SecurityException e) {
                // ignore this error.
            }

879
            try {
880
                getChannelOrFail().setProperty("slave",Boolean.TRUE); // indicate that this side of the channel is the slave side.
881 882 883
            } catch (ChannelClosedException e) {
                throw new IllegalStateException(e);
            }
884

885 886 887
            return null;
        }
        private static final long serialVersionUID = 1L;
888
        private static final Logger LOGGER = Logger.getLogger("");
889
    }
890 891 892

    /**
     * Obtains a {@link VirtualChannel} that allows some computation to be performed on the master.
893 894 895
     * This method can be called from any thread on the master, or from agent (more precisely,
     * it only works from the remoting request-handling thread in agents, which means if you've started
     * separate thread on agents, that'll fail.)
896 897 898 899 900
     *
     * @return null if the calling thread doesn't have any trace of where its master is.
     * @since 1.362
     */
    public static VirtualChannel getChannelToMaster() {
901
        if (Jenkins.getInstanceOrNull()!=null) // check if calling thread is on master or on slave
K
Kohsuke Kawaguchi 已提交
902
            return FilePath.localChannel;
903

904
        // if this method is called from within the agent computation thread, this should work
905
        Channel c = Channel.current();
906
        if (c!=null && Boolean.TRUE.equals(c.getProperty("slave")))
907 908 909 910
            return c;

        return null;
    }
911

912 913 914 915 916 917 918
    /**
     * Helper method for Jelly.
     */
    public static List<SlaveSystemInfo> getSystemInfoExtensions() {
        return SlaveSystemInfo.all();
    }

919
    private static class SlaveLogFetcher extends MasterToSlaveCallable<List<LogRecord>,RuntimeException> {
920 921 922 923
        public List<LogRecord> call() {
            return new ArrayList<LogRecord>(SLAVE_LOG_HANDLER.getView());
        }
    }
924

925 926 927
    // use RingBufferLogHandler class name to configure for backward compatibility
    private static final int DEFAULT_RING_BUFFER_SIZE = SystemProperties.getInteger(RingBufferLogHandler.class.getName() + ".defaultSize", 256);

928
    private static final Logger LOGGER = Logger.getLogger(SlaveComputer.class.getName());
929
}