/*
* The MIT License
*
* Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Stephen Connolly
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package hudson.slaves;
import hudson.AbortException;
import hudson.FilePath;
import hudson.Functions;
import hudson.Main;
import hudson.RestrictedSince;
import hudson.Util;
import hudson.console.ConsoleLogFilter;
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;
import hudson.remoting.ChannelBuilder;
import hudson.remoting.ChannelClosedException;
import hudson.remoting.CommandTransport;
import hudson.remoting.Launcher;
import hudson.remoting.VirtualChannel;
import hudson.security.ACL;
import hudson.slaves.OfflineCause.ChannelTermination;
import hudson.util.Futures;
import hudson.util.NullStream;
import hudson.util.RingBufferLogHandler;
import hudson.util.StreamTaskListener;
import hudson.util.VersionNumber;
import hudson.util.io.RewindableFileOutputStream;
import hudson.util.io.RewindableRotatingFileOutputStream;
import jenkins.model.Jenkins;
import jenkins.security.ChannelConfigurator;
import jenkins.security.MasterToSlaveCallable;
import jenkins.slaves.EncryptedSlaveAgentJnlpFile;
import jenkins.slaves.JnlpSlaveAgentProtocol;
import jenkins.slaves.RemotingVersionInfo;
import jenkins.slaves.systemInfo.SlaveSystemInfo;
import jenkins.util.SystemProperties;
import org.acegisecurity.context.SecurityContext;
import org.acegisecurity.context.SecurityContextHolder;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.Beta;
import org.kohsuke.accmod.restrictions.DoNotUse;
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;
import org.kohsuke.stapler.WebMethod;
import org.kohsuke.stapler.export.Exported;
import org.kohsuke.stapler.interceptor.RequirePOST;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import javax.annotation.OverridingMethodsMustInvokeSuper;
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.Map;
import java.util.TreeMap;
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;
import static hudson.slaves.SlaveComputer.LogHolder.SLAVE_LOG_HANDLER;
import org.jenkinsci.remoting.util.LoggingChannelListener;
/**
* {@link Computer} for {@link Slave}s.
*
* @author Kohsuke Kawaguchi
*/
public class SlaveComputer extends Computer {
private volatile Channel channel;
private volatile transient boolean acceptingTasks = true;
private Charset defaultCharset;
private Boolean isUnix;
/**
* Effective {@link ComputerLauncher} that hides the details of
* how we launch a agent agent on this computer.
*
*
* This is normally the same as {@link Slave#getLauncher()} but
* can be different. See {@link #grabLauncher(Node)}.
*/
private ComputerLauncher launcher;
/**
* Perpetually writable log file.
*/
private final RewindableFileOutputStream log;
/**
* {@link StreamTaskListener} that wraps {@link #log}, hence perpetually writable.
*/
private final TaskListener taskListener;
/**
* 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;
/**
* 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();
private transient volatile String absoluteRemoteFs;
public SlaveComputer(Slave slave) {
super(slave);
this.log = new RewindableRotatingFileOutputStream(getLogFile(), 10);
this.taskListener = new StreamTaskListener(decorate(this.log));
assert slave.getNumExecutors()!=0 : "Computer created with 0 executors";
}
/**
* 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;
}
/**
* {@inheritDoc}
*/
@Override
@OverridingMethodsMustInvokeSuper
public boolean isAcceptingTasks() {
// our boolean flag is an override on any additional programmatic reasons why this agent might not be
// accepting tasks.
return acceptingTasks && super.isAcceptingTasks();
}
/**
* @since 1.498
*/
public String getJnlpMac() {
return JnlpSlaveAgentProtocol.SLAVE_SECRET.mac(getName());
}
/**
* Allows suspension of tasks being accepted by the agent computer. While this could be called by a
* {@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.
*
* @param acceptingTasks {@code true} if the agent can accept tasks.
*/
public void setAcceptingTasks(boolean acceptingTasks) {
this.acceptingTasks = acceptingTasks;
}
@Override
public Boolean isUnix() {
return isUnix;
}
@CheckForNull
@Override
public Slave getNode() {
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;
}
}
/**
* Return the {@code TaskListener} for this SlaveComputer. Never null
* @since 2.9
*/
public TaskListener getListener() {
return taskListener;
}
@Override
public String getIcon() {
Future> l = lastConnectActivity;
if(l!=null && !l.isDone())
return "computer-flash.gif";
return super.getIcon();
}
/**
* @deprecated since 2008-05-20.
*/
@Deprecated @Override
public boolean isJnlpAgent() {
return launcher instanceof JNLPLauncher;
}
@Override
public boolean isLaunchSupported() {
return launcher.isLaunchSupported();
}
/**
* Return the {@code ComputerLauncher} for this SlaveComputer.
* @since 1.312
*/
public ComputerLauncher getLauncher() {
return launcher;
}
/**
* 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;
}
protected Future> _connect(boolean forceReconnect) {
if(channel!=null) return Futures.precomputed(null);
if(!forceReconnect && isConnecting())
return lastConnectActivity;
if(forceReconnect && isConnecting())
logger.fine("Forcing a reconnect on "+getName());
closeChannel();
return lastConnectActivity = Computer.threadPoolForRemoting.submit(new java.util.concurrent.Callable