package jenkins.slaves;
import hudson.AbortException;
import hudson.Extension;
import hudson.Util;
import hudson.model.Computer;
import hudson.remoting.Channel;
import hudson.remoting.ChannelBuilder;
import hudson.slaves.SlaveComputer;
import jenkins.AgentProtocol;
import jenkins.model.Jenkins;
import jenkins.security.ChannelConfigurator;
import org.jenkinsci.remoting.engine.JnlpServer3Handshake;
import org.jenkinsci.remoting.nio.NioChannelHub;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import javax.inject.Inject;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.logging.Level;
import java.util.logging.Logger;
import jenkins.util.SystemProperties;
/**
* Master-side implementation for JNLP3-connect protocol.
*
* <p>@see {@link org.jenkinsci.remoting.engine.JnlpProtocol3} for more details.
*
* @author Akshay Dayal
* @since 1.XXX
*/
// TODO @Deprecated once JENKINS-36871 is merged
@Extension
public class JnlpSlaveAgentProtocol3 extends AgentProtocol {
@Inject
NioChannelSelector hub;
/**
* {@inheritDoc}
*/
@Override
public boolean isOptIn() {
return !ENABLED;
}
@Override
public String getName() {
// we only want to force the protocol off for users that have explicitly banned it via system property
// everyone on the A/B test will just have the opt-in flag toggled
// TODO strip all this out and hardcode OptIn==TRUE once JENKINS-36871 is merged
return forceEnabled != Boolean.FALSE ? "JNLP3-connect" : null;
}
/**
* {@inheritDoc}
*/
@Override
public String getDisplayName() {
return Messages.JnlpSlaveAgentProtocol3_displayName();
}
@Override
public void handle(Socket socket) throws IOException, InterruptedException {
new Handler(hub.getHub(), socket).run();
}
static class Handler extends JnlpServer3Handshake {
private SlaveComputer computer;
private PrintWriter logw;
private OutputStream log;
public Handler(NioChannelHub hub, Socket socket) throws IOException {
super(hub, Computer.threadPoolForRemoting, socket);
}
protected void run() throws IOException, InterruptedException {
try {
Channel channel = connect();
computer.setChannel(channel, log,
new Channel.Listener() {
@Override
public void onClosed(Channel channel, IOException cause) {
if (cause != null)
LOGGER.log(Level.WARNING,
Thread.currentThread().getName() + " for + " +
getNodeName() + " terminated", cause);
try {
socket.close();
} catch (IOException e) {
// Do nothing.
}
}
});
} catch (AbortException e) {
logw.println(e.getMessage());
logw.println("Failed to establish the connection with the agent");
throw e;
} catch (IOException e) {
logw.println("Failed to establish the connection with the agent " + getNodeName());
e.printStackTrace(logw);
throw e;
}
}
@Override
public ChannelBuilder createChannelBuilder(String nodeName) {
log = computer.openLogFile();
logw = new PrintWriter(log,true);
logw.println("JNLP agent connected from " + socket.getInetAddress());
ChannelBuilder cb = super.createChannelBuilder(nodeName).withHeaderStream(log);
for (ChannelConfigurator cc : ChannelConfigurator.all()) {
cc.onChannelBuilding(cb, computer);
}
return cb;
}
@Override
protected String getNodeSecret(String nodeName) throws Failure {
computer = (SlaveComputer) Jenkins.getInstance().getComputer(nodeName);
if (computer == null) {
throw new Failure("Agent trying to register for invalid node: " + nodeName);
}
return computer.getJnlpMac();
}
}
private static final Logger LOGGER = Logger.getLogger(JnlpSlaveAgentProtocol3.class.getName());
/**
* Flag to control the activation of JNLP3 protocol.
* This feature is being A/B tested right now.
*
* <p>
* Once this will be on by default, the flag and this field will disappear. The system property is
* an escape hatch for those who hit any issues and those who are trying this out.
*/
@Restricted(NoExternalUse.class)
public static boolean ENABLED;
private static final Boolean forceEnabled;
static {
forceEnabled = SystemProperties.optBoolean(JnlpSlaveAgentProtocol3.class.getName() + ".enabled");
if (forceEnabled != null)
ENABLED = forceEnabled;
else {
byte hash = Util.fromHexString(Jenkins.getActiveInstance().getLegacyInstanceId())[0];
ENABLED = (hash%10)==0;
}
}
}