From 9bb35a78b8298930adf978046e9f6c53c6853e27 Mon Sep 17 00:00:00 2001 From: Akshay Dayal Date: Fri, 17 Apr 2015 08:11:43 -0700 Subject: [PATCH 001/156] [JENKINS-26580] Initial implementation of JNLP3-connect protocol --- .../slaves/JnlpSlaveAgentProtocol3.java | 221 ++++++++++++++++++ .../jenkins/slaves/JnlpSlaveHandshake.java | 2 +- pom.xml | 2 +- 3 files changed, 223 insertions(+), 2 deletions(-) create mode 100644 core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java diff --git a/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java b/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java new file mode 100644 index 0000000000..5c4d348b00 --- /dev/null +++ b/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java @@ -0,0 +1,221 @@ +package jenkins.slaves; + +import hudson.AbortException; +import hudson.Extension; +import hudson.TcpSlaveAgentListener; +import hudson.Util; +import hudson.remoting.Channel; +import hudson.remoting.ChannelBuilder; +import hudson.remoting.SocketChannelStream; +import hudson.slaves.SlaveComputer; +import jenkins.AgentProtocol; +import jenkins.model.Jenkins; +import org.jenkinsci.remoting.engine.JnlpProtocol; +import org.jenkinsci.remoting.engine.JnlpProtocol3; +import org.jenkinsci.remoting.engine.jnlp3.ChannelCiphers; +import org.jenkinsci.remoting.engine.jnlp3.CipherUtils; +import org.jenkinsci.remoting.engine.jnlp3.HandshakeCiphers; +import org.jenkinsci.remoting.nio.NioChannelHub; + +import javax.crypto.CipherInputStream; +import javax.crypto.CipherOutputStream; +import javax.inject.Inject; +import java.io.BufferedWriter; +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.net.Socket; +import java.security.SecureRandom; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.logging.Level; + +/** + * Master-side implementation for JNLP3-connect protocol. + * + *

@see {@link JnlpProtocol3} for more details. + */ +@Extension +public class JnlpSlaveAgentProtocol3 extends AgentProtocol { + @Inject + NioChannelSelector hub; + + @Override + public String getName() { + return "JNLP3-connect"; + } + + @Override + public void handle(Socket socket) throws IOException, InterruptedException { + new Handler(hub.getHub(), socket).run(); + } + + static class Handler extends JnlpSlaveHandshake { + + public Handler(NioChannelHub hub, Socket socket) throws IOException { + super(hub,socket, + new DataInputStream(socket.getInputStream()), + new PrintWriter(new BufferedWriter( + new OutputStreamWriter(socket.getOutputStream(), "UTF-8")), true)); + } + + protected void run() throws IOException, InterruptedException { + request.load(new ByteArrayInputStream(in.readUTF().getBytes("UTF-8"))); + String nodeName = request.getProperty(JnlpProtocol3.SLAVE_NAME_KEY); + String encryptedChallenge = request.getProperty(JnlpProtocol3.CHALLENGE_KEY); + byte[] handshakeSpecKey = CipherUtils.keyFromString( + request.getProperty(JnlpProtocol3.HANDSHAKE_SPEC_KEY)); + String cookie = request.getProperty(JnlpProtocol3.COOKIE_KEY); + + SlaveComputer computer = (SlaveComputer) Jenkins.getInstance().getComputer(nodeName); + if(computer == null) { + error("Slave trying to register for invalid node: " + nodeName); + return; + } + String slaveSecret = computer.getJnlpMac(); + + HandshakeCiphers handshakeCiphers = null; + try { + handshakeCiphers = HandshakeCiphers.create(nodeName, slaveSecret, handshakeSpecKey); + } catch (Exception e) { + error("Failed to create handshake ciphers for node: " + nodeName); + return; + } + + String challenge = null; + try { + challenge = handshakeCiphers.decrypt(encryptedChallenge); + } catch (Exception e) { + throw new IOException("Unable to decrypt challenge", e); + } + if (!challenge.startsWith(JnlpProtocol3.CHALLENGE_PREFIX)) { + error("Received invalid challenge"); + return; + } + + // At this point the slave looks legit, check if we think they are already connected. + Channel oldChannel = computer.getChannel(); + if(oldChannel != null) { + if (cookie != null && cookie.equals(oldChannel.getProperty(COOKIE_NAME))) { + // We think we are currently connected, but this request proves that it's from + // the party we are supposed to be communicating to. so let the current one get + // disconnected + LOGGER.info("Disconnecting " + nodeName + + " as we are reconnected from the current peer"); + try { + computer.disconnect(new TcpSlaveAgentListener.ConnectionFromCurrentPeer()) + .get(15, TimeUnit.SECONDS); + } catch (ExecutionException e) { + throw new IOException("Failed to disconnect the current client",e); + } catch (TimeoutException e) { + throw new IOException("Failed to disconnect the current client",e); + } + } else { + error(nodeName + + " is already connected to this master. Rejecting this connection."); + return; + } + } + + // Send challenge response. + String challengeReverse = new StringBuilder( + challenge.substring(JnlpProtocol3.CHALLENGE_PREFIX.length())) + .reverse().toString(); + String challengeResponse = JnlpProtocol3.CHALLENGE_PREFIX + challengeReverse; + String encryptedChallengeResponse = null; + try { + encryptedChallengeResponse = handshakeCiphers.encrypt(challengeResponse); + } catch (Exception e) { + throw new IOException("Error encrypting challenge response", e); + } + out.println(encryptedChallengeResponse.getBytes("UTF-8").length); + out.print(encryptedChallengeResponse); + out.flush(); + + // If the slave accepted our challenge response it will send channel cipher keys. + String challengeVerificationMessage = in.readUTF(); + if (!challengeVerificationMessage.equals(JnlpProtocol.GREETING_SUCCESS)) { + error("Slave did not accept our challenge response"); + return; + } + String encryptedAesKeyString = in.readUTF(); + String encryptedSpecKeyString = in.readUTF(); + ChannelCiphers channelCiphers = null; + try { + String aesKeyString = handshakeCiphers.decrypt(encryptedAesKeyString); + String specKeyString = handshakeCiphers.decrypt(encryptedSpecKeyString); + channelCiphers = ChannelCiphers.create( + CipherUtils.keyFromString(aesKeyString), + CipherUtils.keyFromString(specKeyString)); + } catch (Exception e) { + error("Failed to decrypt channel cipher keys"); + return; + } + + String newCookie = generateCookie(); + try { + out.println(handshakeCiphers.encrypt(newCookie)); + } catch (Exception e) { + throw new IOException("Error encrypting cookie", e); + } + + Channel establishedChannel = jnlpConnect(computer, channelCiphers); + establishedChannel.setProperty(COOKIE_NAME, newCookie); + } + + protected Channel jnlpConnect( + SlaveComputer computer, ChannelCiphers channelCiphers) + throws InterruptedException, IOException { + final String nodeName = computer.getName(); + final OutputStream log = computer.openLogFile(); + PrintWriter logw = new PrintWriter(log,true); + logw.println("JNLP agent connected from "+ socket.getInetAddress()); + + try { + ChannelBuilder cb = createChannelBuilder(nodeName); + Channel channel = cb.withHeaderStream(log) + .build(new CipherInputStream(SocketChannelStream.in(socket), + channelCiphers.getDecryptCipher()), + new CipherOutputStream(SocketChannelStream.out(socket), + channelCiphers.getEncryptCipher())); + + 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 + " + + nodeName + " terminated", cause); + try { + socket.close(); + } catch (IOException e) { + // Do nothing. + } + } + }); + return computer.getChannel(); + } catch (AbortException e) { + logw.println(e.getMessage()); + logw.println("Failed to establish the connection with the slave"); + throw e; + } catch (IOException e) { + logw.println("Failed to establish the connection with the slave " + nodeName); + e.printStackTrace(logw); + throw e; + } + } + + private String generateCookie() { + byte[] cookie = new byte[32]; + new SecureRandom().nextBytes(cookie); + return Util.toHexString(cookie); + } + } + + static final String COOKIE_NAME = JnlpSlaveAgentProtocol3.class.getName() + ".cookie"; +} diff --git a/core/src/main/java/jenkins/slaves/JnlpSlaveHandshake.java b/core/src/main/java/jenkins/slaves/JnlpSlaveHandshake.java index b7c01a239e..eb9499e1c3 100644 --- a/core/src/main/java/jenkins/slaves/JnlpSlaveHandshake.java +++ b/core/src/main/java/jenkins/slaves/JnlpSlaveHandshake.java @@ -114,5 +114,5 @@ public class JnlpSlaveHandshake { } - private static final Logger LOGGER = Logger.getLogger(JnlpSlaveHandshake.class.getName()); + static final Logger LOGGER = Logger.getLogger(JnlpSlaveHandshake.class.getName()); } diff --git a/pom.xml b/pom.xml index bb2e89b46a..4a525155a5 100644 --- a/pom.xml +++ b/pom.xml @@ -176,7 +176,7 @@ THE SOFTWARE. org.jenkins-ci.main remoting - 2.50 + 2.51-SNAPSHOT From e9f5caa13fa1a3d1bf602ec9d67dac75f3310889 Mon Sep 17 00:00:00 2001 From: Akshay Dayal Date: Thu, 23 Apr 2015 02:24:40 -0700 Subject: [PATCH 002/156] [JENKINS-26580] Updated implementation of Jnlp3 protocol --- .../slaves/JnlpSlaveAgentProtocol3.java | 131 ++++++++++-------- 1 file changed, 70 insertions(+), 61 deletions(-) diff --git a/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java b/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java index 5c4d348b00..ce0a59beef 100644 --- a/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java +++ b/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java @@ -13,8 +13,8 @@ import jenkins.model.Jenkins; import org.jenkinsci.remoting.engine.JnlpProtocol; import org.jenkinsci.remoting.engine.JnlpProtocol3; import org.jenkinsci.remoting.engine.jnlp3.ChannelCiphers; -import org.jenkinsci.remoting.engine.jnlp3.CipherUtils; import org.jenkinsci.remoting.engine.jnlp3.HandshakeCiphers; +import org.jenkinsci.remoting.engine.jnlp3.Jnlp3Util; import org.jenkinsci.remoting.nio.NioChannelHub; import javax.crypto.CipherInputStream; @@ -28,6 +28,7 @@ import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.net.Socket; +import java.nio.charset.Charset; import java.security.SecureRandom; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; @@ -38,6 +39,8 @@ import java.util.logging.Level; * Master-side implementation for JNLP3-connect protocol. * *

@see {@link JnlpProtocol3} for more details. + * + * @author Akshay Dayal */ @Extension public class JnlpSlaveAgentProtocol3 extends AgentProtocol { @@ -59,45 +62,41 @@ public class JnlpSlaveAgentProtocol3 extends AgentProtocol { public Handler(NioChannelHub hub, Socket socket) throws IOException { super(hub,socket, new DataInputStream(socket.getInputStream()), - new PrintWriter(new BufferedWriter( - new OutputStreamWriter(socket.getOutputStream(), "UTF-8")), true)); + new PrintWriter(new BufferedWriter(new OutputStreamWriter( + socket.getOutputStream(), Charset.forName("UTF-8"))), true)); } protected void run() throws IOException, InterruptedException { - request.load(new ByteArrayInputStream(in.readUTF().getBytes("UTF-8"))); + // Get initiation information from slave. + request.load(new ByteArrayInputStream(in.readUTF().getBytes(Charset.forName("UTF-8")))); String nodeName = request.getProperty(JnlpProtocol3.SLAVE_NAME_KEY); - String encryptedChallenge = request.getProperty(JnlpProtocol3.CHALLENGE_KEY); - byte[] handshakeSpecKey = CipherUtils.keyFromString( - request.getProperty(JnlpProtocol3.HANDSHAKE_SPEC_KEY)); - String cookie = request.getProperty(JnlpProtocol3.COOKIE_KEY); + // Create handshake ciphers. SlaveComputer computer = (SlaveComputer) Jenkins.getInstance().getComputer(nodeName); if(computer == null) { error("Slave trying to register for invalid node: " + nodeName); return; } String slaveSecret = computer.getJnlpMac(); + HandshakeCiphers handshakeCiphers = HandshakeCiphers.create(nodeName, slaveSecret); - HandshakeCiphers handshakeCiphers = null; - try { - handshakeCiphers = HandshakeCiphers.create(nodeName, slaveSecret, handshakeSpecKey); - } catch (Exception e) { - error("Failed to create handshake ciphers for node: " + nodeName); + // Authenticate to the slave. + if (!authenticateToSlave(handshakeCiphers)) { return; } - String challenge = null; - try { - challenge = handshakeCiphers.decrypt(encryptedChallenge); - } catch (Exception e) { - throw new IOException("Unable to decrypt challenge", e); + // If there is a cookie decrypt it. + String cookie = null; + if (request.getProperty(JnlpProtocol3.COOKIE_KEY) != null) { + cookie = handshakeCiphers.decrypt(request.getProperty(JnlpProtocol3.COOKIE_KEY)); } - if (!challenge.startsWith(JnlpProtocol3.CHALLENGE_PREFIX)) { - error("Received invalid challenge"); + + // Validate the slave. + if (!validateSlave(handshakeCiphers)) { return; } - // At this point the slave looks legit, check if we think they are already connected. + // The slave is authenticated, see if its already connected. Channel oldChannel = computer.getChannel(); if(oldChannel != null) { if (cookie != null && cookie.equals(oldChannel.getProperty(COOKIE_NAME))) { @@ -121,52 +120,62 @@ public class JnlpSlaveAgentProtocol3 extends AgentProtocol { } } - // Send challenge response. - String challengeReverse = new StringBuilder( - challenge.substring(JnlpProtocol3.CHALLENGE_PREFIX.length())) - .reverse().toString(); - String challengeResponse = JnlpProtocol3.CHALLENGE_PREFIX + challengeReverse; - String encryptedChallengeResponse = null; - try { - encryptedChallengeResponse = handshakeCiphers.encrypt(challengeResponse); - } catch (Exception e) { - throw new IOException("Error encrypting challenge response", e); - } - out.println(encryptedChallengeResponse.getBytes("UTF-8").length); - out.print(encryptedChallengeResponse); - out.flush(); - - // If the slave accepted our challenge response it will send channel cipher keys. - String challengeVerificationMessage = in.readUTF(); - if (!challengeVerificationMessage.equals(JnlpProtocol.GREETING_SUCCESS)) { - error("Slave did not accept our challenge response"); - return; - } - String encryptedAesKeyString = in.readUTF(); - String encryptedSpecKeyString = in.readUTF(); - ChannelCiphers channelCiphers = null; - try { - String aesKeyString = handshakeCiphers.decrypt(encryptedAesKeyString); - String specKeyString = handshakeCiphers.decrypt(encryptedSpecKeyString); - channelCiphers = ChannelCiphers.create( - CipherUtils.keyFromString(aesKeyString), - CipherUtils.keyFromString(specKeyString)); - } catch (Exception e) { - error("Failed to decrypt channel cipher keys"); - return; - } - + // Send greeting and new cookie. + out.println(JnlpProtocol.GREETING_SUCCESS); String newCookie = generateCookie(); - try { - out.println(handshakeCiphers.encrypt(newCookie)); - } catch (Exception e) { - throw new IOException("Error encrypting cookie", e); - } + out.println(handshakeCiphers.encrypt(newCookie)); + + // Now get the channel cipher information. + String aesKeyString = handshakeCiphers.decrypt(in.readUTF()); + String specKeyString = handshakeCiphers.decrypt(in.readUTF()); + ChannelCiphers channelCiphers = ChannelCiphers.create( + Jnlp3Util.keyFromString(aesKeyString), + Jnlp3Util.keyFromString(specKeyString)); Channel establishedChannel = jnlpConnect(computer, channelCiphers); establishedChannel.setProperty(COOKIE_NAME, newCookie); } + private boolean authenticateToSlave(HandshakeCiphers handshakeCiphers) throws IOException { + String challenge = handshakeCiphers.decrypt( + request.getProperty(JnlpProtocol3.CHALLENGE_KEY)); + + // Send slave challenge response. + String challengeResponse = Jnlp3Util.createChallengeResponse(challenge); + String encryptedChallengeResponse = handshakeCiphers.encrypt(challengeResponse); + out.println(encryptedChallengeResponse.getBytes(Charset.forName("UTF-8")).length); + out.print(encryptedChallengeResponse); + out.flush(); + + // If the slave accepted our challenge response send our challenge. + String challengeVerificationMessage = in.readUTF(); + if (!challengeVerificationMessage.equals(JnlpProtocol.GREETING_SUCCESS)) { + error("Slave did not accept our challenge response"); + return false; + } + + return true; + } + + private boolean validateSlave(HandshakeCiphers handshakeCiphers) throws IOException { + String masterChallenge = Jnlp3Util.generateChallenge(); + String encryptedMasterChallenge = handshakeCiphers.encrypt(masterChallenge); + out.println(encryptedMasterChallenge.getBytes(Charset.forName("UTF-8")).length); + out.print(encryptedMasterChallenge); + out.flush(); + + // Verify the challenge response from the slave. + String encryptedMasterChallengeResponse = in.readUTF(); + String masterChallengeResponse = handshakeCiphers.decrypt( + encryptedMasterChallengeResponse); + if (!Jnlp3Util.validateChallengeResponse(masterChallenge, masterChallengeResponse)) { + error("Incorrect master challenge response from slave"); + return false; + } + + return true; + } + protected Channel jnlpConnect( SlaveComputer computer, ChannelCiphers channelCiphers) throws InterruptedException, IOException { From a0d390a9f65e3f045679f7fdceab3e560f06c84c Mon Sep 17 00:00:00 2001 From: Allan Burdajewicz Date: Mon, 21 Dec 2015 13:20:03 +1000 Subject: [PATCH 003/156] [JENKINS-32134]: Improved the Build Other Projects help message for projects inside Folder. --- war/src/main/webapp/help/project-config/downstream.html | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/war/src/main/webapp/help/project-config/downstream.html b/war/src/main/webapp/help/project-config/downstream.html index 80fb27b920..ee74952072 100644 --- a/war/src/main/webapp/help/project-config/downstream.html +++ b/war/src/main/webapp/help/project-config/downstream.html @@ -6,4 +6,8 @@ Other than the obvious use case where you'd like to build other projects that have a dependency on the current project, this can also be useful to split a long build process in to multiple stages (such as the build portion and the test portion). + +

+ Projects located inside folders can be specified using an absolute path starting with "/" or a + relative path from the current project location. \ No newline at end of file From 384f73b49b09d4d7f67fede1c948c79c2247842d Mon Sep 17 00:00:00 2001 From: Allan Burdajewicz Date: Tue, 22 Dec 2015 21:56:05 +1000 Subject: [PATCH 004/156] [JENKINS-32134]: Referenced CloudBees Folder Plugin and Multi-Branch Project Plugin --- war/src/main/webapp/help/project-config/downstream.html | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/war/src/main/webapp/help/project-config/downstream.html b/war/src/main/webapp/help/project-config/downstream.html index ee74952072..51eb77d2f5 100644 --- a/war/src/main/webapp/help/project-config/downstream.html +++ b/war/src/main/webapp/help/project-config/downstream.html @@ -6,8 +6,11 @@ Other than the obvious use case where you'd like to build other projects that have a dependency on the current project, this can also be useful to split a long build process in to multiple stages (such as the build portion and the test portion). -

- Projects located inside folders can be specified using an absolute path starting with "/" or a + When using + CloudBees Folder Plugin + or other plugins depending on it and organizing jobs into folders such as + Multi-Branch Project Plugin, + projects located inside folders can be specified using an absolute path starting with "/" or a relative path from the current project location. \ No newline at end of file From bcba404dca6cb7239ba78381d88e927dda2c1c18 Mon Sep 17 00:00:00 2001 From: Allan Burdajewicz Date: Thu, 31 Dec 2015 16:11:09 +1000 Subject: [PATCH 005/156] [JENKINS-32134]: Used the core terminology "ItemGroup". Simplified and added examples. Added the closing paragraph tags. --- .../help/project-config/downstream.html | 31 +++++++++++-------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/war/src/main/webapp/help/project-config/downstream.html b/war/src/main/webapp/help/project-config/downstream.html index 51eb77d2f5..58055de756 100644 --- a/war/src/main/webapp/help/project-config/downstream.html +++ b/war/src/main/webapp/help/project-config/downstream.html @@ -1,16 +1,21 @@

- Trigger builds of the other projects once a build is successfully completed. - Multiple projects can be specified by using comma, like "abc, def". + Trigger builds of the other projects once a build is successfully completed. + Multiple projects can be specified by using comma, like "abc, def". -

- Other than the obvious use case where you'd like to build other projects that have a dependency - on the current project, this can also be useful to split a long build process in to multiple - stages (such as the build portion and the test portion). -

- When using - CloudBees Folder Plugin - or other plugins depending on it and organizing jobs into folders such as - Multi-Branch Project Plugin, - projects located inside folders can be specified using an absolute path starting with "/" or a - relative path from the current project location. +

+ Other than the obvious use case where you'd like to build other projects that have a dependency + on the current project, this can also be useful to split a long build process in to multiple + stages (such as the build portion and the test portion). +

+ +

+ A project located inside an ItemGroup need to be specified by using a path. It can be an absolute + path starting from Jenkins root, like "/grp/abc" where "grp" is an ItemGroup located at the root + of Jenkins. It can also be a relative path from the current project location, like "grp/abc" where + "grp" is an ItemGroup located at the same level as the current project. + Examples of plugins that provide an ItemGroup implementation include but are not limited to the + CloudBees Folder Plugin + and the + Multi-Branch Project Plugin +

\ No newline at end of file From 4915c31629afbc092345e600855279ce891e0dd4 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Fri, 29 Jan 2016 08:28:22 +0000 Subject: [PATCH 006/156] Moved some of the server-side logic to remoting This way, the inner-working of the protocol can be kept private within the remoting module, which increases a modularity. --- .../slaves/JnlpSlaveAgentProtocol.java | 8 +- .../slaves/JnlpSlaveAgentProtocol3.java | 173 +++--------------- .../jenkins/slaves/JnlpSlaveHandshake.java | 3 + pom.xml | 2 +- 4 files changed, 30 insertions(+), 156 deletions(-) diff --git a/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol.java b/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol.java index d82a42e51a..f0f51d9244 100644 --- a/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol.java +++ b/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol.java @@ -2,6 +2,7 @@ package jenkins.slaves; import hudson.AbortException; import hudson.Extension; +import hudson.model.Computer; import hudson.remoting.Channel; import hudson.remoting.Channel.Listener; import hudson.remoting.ChannelBuilder; @@ -10,6 +11,7 @@ import hudson.slaves.SlaveComputer; import jenkins.AgentProtocol; import jenkins.model.Jenkins; import jenkins.security.HMACConfidentialKey; +import org.jenkinsci.remoting.engine.JnlpServerHandshake; import org.jenkinsci.remoting.nio.NioChannelHub; import javax.inject.Inject; @@ -66,7 +68,7 @@ public class JnlpSlaveAgentProtocol extends AgentProtocol { new Handler(hub.getHub(),socket).run(); } - protected static class Handler extends JnlpSlaveHandshake { + protected static class Handler extends JnlpServerHandshake { /** * @deprecated as of 1.559 @@ -77,9 +79,7 @@ public class JnlpSlaveAgentProtocol extends AgentProtocol { } public Handler(NioChannelHub hub, Socket socket) throws IOException { - super(hub,socket, - new DataInputStream(socket.getInputStream()), - new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(),"UTF-8")),true)); + super(hub, Computer.threadPoolForRemoting, socket); } protected void run() throws IOException, InterruptedException { diff --git a/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java b/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java index ce0a59beef..38ea985358 100644 --- a/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java +++ b/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java @@ -2,43 +2,27 @@ package jenkins.slaves; import hudson.AbortException; import hudson.Extension; -import hudson.TcpSlaveAgentListener; -import hudson.Util; +import hudson.model.Computer; import hudson.remoting.Channel; import hudson.remoting.ChannelBuilder; -import hudson.remoting.SocketChannelStream; import hudson.slaves.SlaveComputer; import jenkins.AgentProtocol; import jenkins.model.Jenkins; -import org.jenkinsci.remoting.engine.JnlpProtocol; -import org.jenkinsci.remoting.engine.JnlpProtocol3; -import org.jenkinsci.remoting.engine.jnlp3.ChannelCiphers; -import org.jenkinsci.remoting.engine.jnlp3.HandshakeCiphers; -import org.jenkinsci.remoting.engine.jnlp3.Jnlp3Util; +import org.jenkinsci.remoting.engine.JnlpServer3Handshake; import org.jenkinsci.remoting.nio.NioChannelHub; -import javax.crypto.CipherInputStream; -import javax.crypto.CipherOutputStream; import javax.inject.Inject; -import java.io.BufferedWriter; -import java.io.ByteArrayInputStream; -import java.io.DataInputStream; import java.io.IOException; import java.io.OutputStream; -import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.net.Socket; -import java.nio.charset.Charset; -import java.security.SecureRandom; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; import java.util.logging.Level; +import java.util.logging.Logger; /** * Master-side implementation for JNLP3-connect protocol. * - *

@see {@link JnlpProtocol3} for more details. + *

@see {@link org.jenkinsci.remoting.engine.JnlpProtocol3} for more details. * * @author Akshay Dayal */ @@ -57,146 +41,30 @@ public class JnlpSlaveAgentProtocol3 extends AgentProtocol { new Handler(hub.getHub(), socket).run(); } - static class Handler extends JnlpSlaveHandshake { + static class Handler extends JnlpServer3Handshake { + + private SlaveComputer computer; public Handler(NioChannelHub hub, Socket socket) throws IOException { - super(hub,socket, - new DataInputStream(socket.getInputStream()), - new PrintWriter(new BufferedWriter(new OutputStreamWriter( - socket.getOutputStream(), Charset.forName("UTF-8"))), true)); + super(hub, Computer.threadPoolForRemoting, socket); } protected void run() throws IOException, InterruptedException { - // Get initiation information from slave. - request.load(new ByteArrayInputStream(in.readUTF().getBytes(Charset.forName("UTF-8")))); - String nodeName = request.getProperty(JnlpProtocol3.SLAVE_NAME_KEY); - - // Create handshake ciphers. - SlaveComputer computer = (SlaveComputer) Jenkins.getInstance().getComputer(nodeName); - if(computer == null) { - error("Slave trying to register for invalid node: " + nodeName); - return; - } - String slaveSecret = computer.getJnlpMac(); - HandshakeCiphers handshakeCiphers = HandshakeCiphers.create(nodeName, slaveSecret); - - // Authenticate to the slave. - if (!authenticateToSlave(handshakeCiphers)) { - return; - } - - // If there is a cookie decrypt it. - String cookie = null; - if (request.getProperty(JnlpProtocol3.COOKIE_KEY) != null) { - cookie = handshakeCiphers.decrypt(request.getProperty(JnlpProtocol3.COOKIE_KEY)); - } - - // Validate the slave. - if (!validateSlave(handshakeCiphers)) { - return; - } - - // The slave is authenticated, see if its already connected. - Channel oldChannel = computer.getChannel(); - if(oldChannel != null) { - if (cookie != null && cookie.equals(oldChannel.getProperty(COOKIE_NAME))) { - // We think we are currently connected, but this request proves that it's from - // the party we are supposed to be communicating to. so let the current one get - // disconnected - LOGGER.info("Disconnecting " + nodeName + - " as we are reconnected from the current peer"); - try { - computer.disconnect(new TcpSlaveAgentListener.ConnectionFromCurrentPeer()) - .get(15, TimeUnit.SECONDS); - } catch (ExecutionException e) { - throw new IOException("Failed to disconnect the current client",e); - } catch (TimeoutException e) { - throw new IOException("Failed to disconnect the current client",e); - } - } else { - error(nodeName + - " is already connected to this master. Rejecting this connection."); - return; - } - } - - // Send greeting and new cookie. - out.println(JnlpProtocol.GREETING_SUCCESS); - String newCookie = generateCookie(); - out.println(handshakeCiphers.encrypt(newCookie)); - - // Now get the channel cipher information. - String aesKeyString = handshakeCiphers.decrypt(in.readUTF()); - String specKeyString = handshakeCiphers.decrypt(in.readUTF()); - ChannelCiphers channelCiphers = ChannelCiphers.create( - Jnlp3Util.keyFromString(aesKeyString), - Jnlp3Util.keyFromString(specKeyString)); - - Channel establishedChannel = jnlpConnect(computer, channelCiphers); - establishedChannel.setProperty(COOKIE_NAME, newCookie); - } - - private boolean authenticateToSlave(HandshakeCiphers handshakeCiphers) throws IOException { - String challenge = handshakeCiphers.decrypt( - request.getProperty(JnlpProtocol3.CHALLENGE_KEY)); - - // Send slave challenge response. - String challengeResponse = Jnlp3Util.createChallengeResponse(challenge); - String encryptedChallengeResponse = handshakeCiphers.encrypt(challengeResponse); - out.println(encryptedChallengeResponse.getBytes(Charset.forName("UTF-8")).length); - out.print(encryptedChallengeResponse); - out.flush(); - - // If the slave accepted our challenge response send our challenge. - String challengeVerificationMessage = in.readUTF(); - if (!challengeVerificationMessage.equals(JnlpProtocol.GREETING_SUCCESS)) { - error("Slave did not accept our challenge response"); - return false; - } - - return true; - } - - private boolean validateSlave(HandshakeCiphers handshakeCiphers) throws IOException { - String masterChallenge = Jnlp3Util.generateChallenge(); - String encryptedMasterChallenge = handshakeCiphers.encrypt(masterChallenge); - out.println(encryptedMasterChallenge.getBytes(Charset.forName("UTF-8")).length); - out.print(encryptedMasterChallenge); - out.flush(); - - // Verify the challenge response from the slave. - String encryptedMasterChallengeResponse = in.readUTF(); - String masterChallengeResponse = handshakeCiphers.decrypt( - encryptedMasterChallengeResponse); - if (!Jnlp3Util.validateChallengeResponse(masterChallenge, masterChallengeResponse)) { - error("Incorrect master challenge response from slave"); - return false; - } - - return true; - } - - protected Channel jnlpConnect( - SlaveComputer computer, ChannelCiphers channelCiphers) - throws InterruptedException, IOException { final String nodeName = computer.getName(); final OutputStream log = computer.openLogFile(); PrintWriter logw = new PrintWriter(log,true); - logw.println("JNLP agent connected from "+ socket.getInetAddress()); + logw.println("JNLP agent connected from " + socket.getInetAddress()); + + ChannelBuilder cb = createChannelBuilder(nodeName).withHeaderStream(log); try { - ChannelBuilder cb = createChannelBuilder(nodeName); - Channel channel = cb.withHeaderStream(log) - .build(new CipherInputStream(SocketChannelStream.in(socket), - channelCiphers.getDecryptCipher()), - new CipherOutputStream(SocketChannelStream.out(socket), - channelCiphers.getEncryptCipher())); + Channel channel = connect(cb); computer.setChannel(channel, log, new Channel.Listener() { @Override public void onClosed(Channel channel, IOException cause) { - if(cause != null) + if (cause != null) LOGGER.log(Level.WARNING, Thread.currentThread().getName() + " for + " + nodeName + " terminated", cause); @@ -207,7 +75,6 @@ public class JnlpSlaveAgentProtocol3 extends AgentProtocol { } } }); - return computer.getChannel(); } catch (AbortException e) { logw.println(e.getMessage()); logw.println("Failed to establish the connection with the slave"); @@ -219,12 +86,16 @@ public class JnlpSlaveAgentProtocol3 extends AgentProtocol { } } - private String generateCookie() { - byte[] cookie = new byte[32]; - new SecureRandom().nextBytes(cookie); - return Util.toHexString(cookie); + @Override + protected String getNodeSecret(String nodeName) throws Failure { + computer = (SlaveComputer) Jenkins.getInstance().getComputer(nodeName); + if (computer == null) { + throw new Failure("Slave trying to register for invalid node: " + nodeName); + } + return computer.getJnlpMac(); } + } - static final String COOKIE_NAME = JnlpSlaveAgentProtocol3.class.getName() + ".cookie"; + private static final Logger LOGGER = Logger.getLogger(JnlpSlaveAgentProtocol3.class.getName()); } diff --git a/core/src/main/java/jenkins/slaves/JnlpSlaveHandshake.java b/core/src/main/java/jenkins/slaves/JnlpSlaveHandshake.java index eb9499e1c3..3890668251 100644 --- a/core/src/main/java/jenkins/slaves/JnlpSlaveHandshake.java +++ b/core/src/main/java/jenkins/slaves/JnlpSlaveHandshake.java @@ -4,6 +4,7 @@ import hudson.model.Computer; import hudson.remoting.Channel; import hudson.remoting.ChannelBuilder; import hudson.remoting.Engine; +import org.jenkinsci.remoting.engine.JnlpServerHandshake; import org.jenkinsci.remoting.nio.NioChannelHub; import java.io.DataInputStream; @@ -21,6 +22,8 @@ import java.util.logging.Logger; * * @author Kohsuke Kawaguchi * @since 1.561 + * @deprecated as of 1.609 + * Use {@link JnlpServerHandshake} */ public class JnlpSlaveHandshake { /** diff --git a/pom.xml b/pom.xml index 4a525155a5..af244f801a 100644 --- a/pom.xml +++ b/pom.xml @@ -176,7 +176,7 @@ THE SOFTWARE. org.jenkins-ci.main remoting - 2.51-SNAPSHOT + 2.54-SNAPSHOT From a1d3634b0b1b6b20c075caf3b1ab73e5556acd62 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Tue, 2 Feb 2016 11:04:04 +0100 Subject: [PATCH 007/156] JnlpAgentReceiver needs to be adjusted for the new type Given that there's only one caller, I think handling AbstractMethodError is better than doing the compatibility method ping pong --- .../slaves/DefaultJnlpSlaveReceiver.java | 3 +- .../jenkins/slaves/JnlpAgentReceiver.java | 12 +- .../slaves/JnlpSlaveAgentProtocol2.java | 10 +- .../jenkins/slaves/JnlpSlaveHandshake.java | 107 +----------------- 4 files changed, 25 insertions(+), 107 deletions(-) diff --git a/core/src/main/java/jenkins/slaves/DefaultJnlpSlaveReceiver.java b/core/src/main/java/jenkins/slaves/DefaultJnlpSlaveReceiver.java index 1011db9334..1a2cdfc1b8 100644 --- a/core/src/main/java/jenkins/slaves/DefaultJnlpSlaveReceiver.java +++ b/core/src/main/java/jenkins/slaves/DefaultJnlpSlaveReceiver.java @@ -7,6 +7,7 @@ import hudson.model.Slave; import hudson.remoting.Channel; import hudson.slaves.SlaveComputer; import jenkins.model.Jenkins; +import org.jenkinsci.remoting.engine.JnlpServerHandshake; import java.io.IOException; import java.security.SecureRandom; @@ -25,7 +26,7 @@ import java.util.logging.Logger; @Extension public class DefaultJnlpSlaveReceiver extends JnlpAgentReceiver { @Override - public boolean handle(String nodeName, JnlpSlaveHandshake handshake) throws IOException, InterruptedException { + public boolean handle(String nodeName, JnlpServerHandshake handshake) throws IOException, InterruptedException { SlaveComputer computer = (SlaveComputer) Jenkins.getInstance().getComputer(nodeName); if(computer==null) { diff --git a/core/src/main/java/jenkins/slaves/JnlpAgentReceiver.java b/core/src/main/java/jenkins/slaves/JnlpAgentReceiver.java index 914447332f..3a382fd6c4 100644 --- a/core/src/main/java/jenkins/slaves/JnlpAgentReceiver.java +++ b/core/src/main/java/jenkins/slaves/JnlpAgentReceiver.java @@ -4,6 +4,7 @@ import hudson.ExtensionList; import hudson.ExtensionPoint; import hudson.model.Slave; import jenkins.model.Jenkins; +import org.jenkinsci.remoting.engine.JnlpServerHandshake; import java.io.IOException; import java.util.Properties; @@ -56,7 +57,16 @@ public abstract class JnlpAgentReceiver implements ExtensionPoint { * @throws Exception * Any exception thrown from this method will fatally terminate the connection. */ - public abstract boolean handle(String name, JnlpSlaveHandshake handshake) throws IOException, InterruptedException; + public abstract boolean handle(String name, JnlpServerHandshake handshake) throws IOException, InterruptedException; + + /** + * @deprecated + * Use {@link #handle(String, JnlpServerHandshake)} + */ + public boolean handle(String name, JnlpSlaveHandshake handshake) throws IOException, InterruptedException { + return handle(name,(JnlpServerHandshake)handshake); + } + public static ExtensionList all() { return ExtensionList.lookup(JnlpAgentReceiver.class); diff --git a/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol2.java b/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol2.java index 30993bc08b..157811c948 100644 --- a/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol2.java +++ b/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol2.java @@ -1,6 +1,7 @@ package jenkins.slaves; import hudson.Extension; +import org.jenkinsci.remoting.engine.JnlpServerHandshake; import org.jenkinsci.remoting.nio.NioChannelHub; import java.io.ByteArrayInputStream; @@ -54,8 +55,13 @@ public class JnlpSlaveAgentProtocol2 extends JnlpSlaveAgentProtocol { final String nodeName = request.getProperty("Node-Name"); for (JnlpAgentReceiver recv : JnlpAgentReceiver.all()) { - if (recv.handle(nodeName,this)) - return; + try { + if (recv.handle(nodeName,this)) + return; + } catch (AbstractMethodError e) { + if (recv.handle(nodeName,new JnlpSlaveHandshake(this))) + return; + } } error("Unrecognized name: "+nodeName); diff --git a/core/src/main/java/jenkins/slaves/JnlpSlaveHandshake.java b/core/src/main/java/jenkins/slaves/JnlpSlaveHandshake.java index 3890668251..4c9dcfa873 100644 --- a/core/src/main/java/jenkins/slaves/JnlpSlaveHandshake.java +++ b/core/src/main/java/jenkins/slaves/JnlpSlaveHandshake.java @@ -1,21 +1,11 @@ package jenkins.slaves; -import hudson.model.Computer; -import hudson.remoting.Channel; -import hudson.remoting.ChannelBuilder; -import hudson.remoting.Engine; import org.jenkinsci.remoting.engine.JnlpServerHandshake; import org.jenkinsci.remoting.nio.NioChannelHub; -import java.io.DataInputStream; -import java.io.DataOutputStream; import java.io.IOException; -import java.io.PrintWriter; import java.net.Socket; -import java.util.Map.Entry; -import java.util.Properties; -import java.util.logging.Level; -import java.util.logging.Logger; +import java.util.concurrent.ExecutorService; /** * Palette of objects to talk to the incoming JNLP slave connection. @@ -25,97 +15,8 @@ import java.util.logging.Logger; * @deprecated as of 1.609 * Use {@link JnlpServerHandshake} */ -public class JnlpSlaveHandshake { - /** - * Useful for creating a {@link Channel} with NIO as the underlying transport. - */ - /*package*/ final NioChannelHub hub; - - /** - * Socket connection to the slave. - */ - /*package*/ final Socket socket; - - /** - * Wrapping Socket input stream. - */ - /*package*/ final DataInputStream in; - - /** - * For writing handshaking response. - * - * This is a poor design choice that we just carry forward for compatibility. - * For better protocol design, {@link DataOutputStream} is preferred for newer - * protocols. - */ - /*package*/ final PrintWriter out; - - /** - * Bag of properties the JNLP agent have sent us during the hand-shake. - */ - /*package*/ final Properties request = new Properties(); - - - /*package*/ JnlpSlaveHandshake(NioChannelHub hub, Socket socket, DataInputStream in, PrintWriter out) { - this.hub = hub; - this.socket = socket; - this.in = in; - this.out = out; +public class JnlpSlaveHandshake extends JnlpServerHandshake { + /*package*/ JnlpSlaveHandshake(JnlpServerHandshake rhs) { + super(rhs); } - - public NioChannelHub getHub() { - return hub; - } - - public Socket getSocket() { - return socket; - } - - public DataInputStream getIn() { - return in; - } - - public PrintWriter getOut() { - return out; - } - - public Properties getRequestProperties() { - return request; - } - - public String getRequestProperty(String name) { - return request.getProperty(name); - } - - - /** - * Sends the error output and bail out. - */ - public void error(String msg) throws IOException { - out.println(msg); - LOGGER.log(Level.WARNING,Thread.currentThread().getName()+" is aborted: "+msg); - socket.close(); - } - - /** - * {@link JnlpAgentReceiver} calls this method to tell the client that the server - * is happy with the handshaking and is ready to move on to build a channel. - */ - public void success(Properties response) { - out.println(Engine.GREETING_SUCCESS); - for (Entry e : response.entrySet()) { - out.println(e.getKey()+": "+e.getValue()); - } - out.println(); // empty line to conclude the response header - } - - public ChannelBuilder createChannelBuilder(String nodeName) { - if (hub==null) - return new ChannelBuilder(nodeName, Computer.threadPoolForRemoting); - else - return hub.newChannelBuilder(nodeName, Computer.threadPoolForRemoting); - } - - - static final Logger LOGGER = Logger.getLogger(JnlpSlaveHandshake.class.getName()); } From 3e17176fb4e6fc3783276e419e1739f74ec0785a Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Tue, 2 Feb 2016 11:32:53 +0100 Subject: [PATCH 008/156] Fixing a compilation problem --- core/src/main/java/jenkins/slaves/DefaultJnlpSlaveReceiver.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/jenkins/slaves/DefaultJnlpSlaveReceiver.java b/core/src/main/java/jenkins/slaves/DefaultJnlpSlaveReceiver.java index 31aefa71e3..179ae52a0e 100644 --- a/core/src/main/java/jenkins/slaves/DefaultJnlpSlaveReceiver.java +++ b/core/src/main/java/jenkins/slaves/DefaultJnlpSlaveReceiver.java @@ -88,7 +88,7 @@ public class DefaultJnlpSlaveReceiver extends JnlpAgentReceiver { * @return * true if the slave secret matches the handshake secret, false otherwise. */ - private boolean matchesSecret(String nodeName, JnlpSlaveHandshake handshake){ + private boolean matchesSecret(String nodeName, JnlpServerHandshake handshake){ SlaveComputer computer = (SlaveComputer) Jenkins.getInstance().getComputer(nodeName); String handshakeSecret = handshake.getRequestProperty("Secret-Key"); // Verify that the slave secret matches the handshake secret. From 7b6a6c6316862c05d79cc6cd1194e9e8be4f6525 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Tue, 2 Feb 2016 11:33:02 +0100 Subject: [PATCH 009/156] Java7 code transformation --- .../main/java/jenkins/slaves/DefaultJnlpSlaveReceiver.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/core/src/main/java/jenkins/slaves/DefaultJnlpSlaveReceiver.java b/core/src/main/java/jenkins/slaves/DefaultJnlpSlaveReceiver.java index 179ae52a0e..3de0ab274a 100644 --- a/core/src/main/java/jenkins/slaves/DefaultJnlpSlaveReceiver.java +++ b/core/src/main/java/jenkins/slaves/DefaultJnlpSlaveReceiver.java @@ -44,9 +44,7 @@ public class DefaultJnlpSlaveReceiver extends JnlpAgentReceiver { LOGGER.info("Disconnecting "+nodeName+" as we are reconnected from the current peer"); try { computer.disconnect(new ConnectionFromCurrentPeer()).get(15, TimeUnit.SECONDS); - } catch (ExecutionException e) { - throw new IOException("Failed to disconnect the current client",e); - } catch (TimeoutException e) { + } catch (ExecutionException | TimeoutException e) { throw new IOException("Failed to disconnect the current client",e); } } else { From 19ad0e5d68d3c133e2fd3e61cda3d23492433b15 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Tue, 2 Feb 2016 12:03:33 +0100 Subject: [PATCH 010/156] Log cannot be opened twice so it needs to be kept around. --- .../slaves/JnlpSlaveAgentProtocol3.java | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java b/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java index 38ea985358..3c5b8e9fa3 100644 --- a/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java +++ b/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java @@ -42,23 +42,17 @@ public class JnlpSlaveAgentProtocol3 extends AgentProtocol { } 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 { - final String nodeName = computer.getName(); - final OutputStream log = computer.openLogFile(); - PrintWriter logw = new PrintWriter(log,true); - logw.println("JNLP agent connected from " + socket.getInetAddress()); - - ChannelBuilder cb = createChannelBuilder(nodeName).withHeaderStream(log); - try { - Channel channel = connect(cb); + Channel channel = connect(); computer.setChannel(channel, log, new Channel.Listener() { @@ -67,7 +61,7 @@ public class JnlpSlaveAgentProtocol3 extends AgentProtocol { if (cause != null) LOGGER.log(Level.WARNING, Thread.currentThread().getName() + " for + " + - nodeName + " terminated", cause); + getNodeName() + " terminated", cause); try { socket.close(); } catch (IOException e) { @@ -80,12 +74,21 @@ public class JnlpSlaveAgentProtocol3 extends AgentProtocol { logw.println("Failed to establish the connection with the slave"); throw e; } catch (IOException e) { - logw.println("Failed to establish the connection with the slave " + nodeName); + logw.println("Failed to establish the connection with the slave " + 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()); + + return super.createChannelBuilder(nodeName).withHeaderStream(log); + } + @Override protected String getNodeSecret(String nodeName) throws Failure { computer = (SlaveComputer) Jenkins.getInstance().getComputer(nodeName); From 1ba9c3065fb6786b153cd61ae91f3207999e653c Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Tue, 2 Feb 2016 16:04:04 -0500 Subject: [PATCH 011/156] Terminology fix --- .../main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java b/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java index 3c5b8e9fa3..c964824237 100644 --- a/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java +++ b/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java @@ -71,10 +71,10 @@ public class JnlpSlaveAgentProtocol3 extends AgentProtocol { }); } catch (AbortException e) { logw.println(e.getMessage()); - logw.println("Failed to establish the connection with the slave"); + logw.println("Failed to establish the connection with the agent"); throw e; } catch (IOException e) { - logw.println("Failed to establish the connection with the slave " + getNodeName()); + logw.println("Failed to establish the connection with the agent " + getNodeName()); e.printStackTrace(logw); throw e; } @@ -93,7 +93,7 @@ public class JnlpSlaveAgentProtocol3 extends AgentProtocol { protected String getNodeSecret(String nodeName) throws Failure { computer = (SlaveComputer) Jenkins.getInstance().getComputer(nodeName); if (computer == null) { - throw new Failure("Slave trying to register for invalid node: " + nodeName); + throw new Failure("Agent trying to register for invalid node: " + nodeName); } return computer.getJnlpMac(); } From b9a72bcd967fd8b0d422ccf81daf1de576702fd1 Mon Sep 17 00:00:00 2001 From: Emilio Escobar Date: Fri, 5 Feb 2016 15:35:18 +0100 Subject: [PATCH 012/156] [JENKINS-28790] solved the issue masked parameters not masked in windows system. toWindowsCommand joins all args in one, missing masked. --- .../java/hudson/util/ArgumentListBuilder.java | 26 ++++---- .../hudson/util/ArgumentListBuilderTest.java | 62 +++++++++++++------ 2 files changed, 59 insertions(+), 29 deletions(-) diff --git a/core/src/main/java/hudson/util/ArgumentListBuilder.java b/core/src/main/java/hudson/util/ArgumentListBuilder.java index e9edd17b1e..38f92329a6 100644 --- a/core/src/main/java/hudson/util/ArgumentListBuilder.java +++ b/core/src/main/java/hudson/util/ArgumentListBuilder.java @@ -314,42 +314,46 @@ public class ArgumentListBuilder implements Serializable, Cloneable { * @since 1.386 */ public ArgumentListBuilder toWindowsCommand(boolean escapeVars) { - StringBuilder quotedArgs = new StringBuilder(); + ArgumentListBuilder windowsCommand = new ArgumentListBuilder().add("cmd.exe", "/C"); boolean quoted, percent; - for (String arg : args) { + for (int i = 0; i < args.size(); i++) { + StringBuilder quotedArgs = new StringBuilder(); + String arg = args.get(i); quoted = percent = false; - for (int i = 0; i < arg.length(); i++) { - char c = arg.charAt(i); + for (int j = 0; j < arg.length(); j++) { + char c = arg.charAt(j); if (!quoted && (c == ' ' || c == '*' || c == '?' || c == ',' || c == ';')) { - quoted = startQuoting(quotedArgs, arg, i); + quoted = startQuoting(quotedArgs, arg, j); } else if (c == '^' || c == '&' || c == '<' || c == '>' || c == '|') { - if (!quoted) quoted = startQuoting(quotedArgs, arg, i); + if (!quoted) quoted = startQuoting(quotedArgs, arg, j); // quotedArgs.append('^'); See note in javadoc above } else if (c == '"') { - if (!quoted) quoted = startQuoting(quotedArgs, arg, i); + if (!quoted) quoted = startQuoting(quotedArgs, arg, j); quotedArgs.append('"'); } else if (percent && escapeVars && ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'))) { - if (!quoted) quoted = startQuoting(quotedArgs, arg, i); + if (!quoted) quoted = startQuoting(quotedArgs, arg, j); quotedArgs.append('"').append(c); c = '"'; } percent = (c == '%'); if (quoted) quotedArgs.append(c); } + if(i == 0) quotedArgs.append('"'); if (quoted) quotedArgs.append('"'); else quotedArgs.append(arg); - quotedArgs.append(' '); + + windowsCommand.add(quotedArgs, mask.get(i)); } // (comment copied from old code in hudson.tasks.Ant) // on Windows, executing batch file can't return the correct error code, // so we need to wrap it into cmd.exe. // double %% is needed because we want ERRORLEVEL to be expanded after // batch file executed, not before. This alone shows how broken Windows is... - quotedArgs.append("&& exit %%ERRORLEVEL%%"); - return new ArgumentListBuilder().add("cmd.exe", "/C").addQuoted(quotedArgs.toString()); + windowsCommand.add("&&").add("exit").add("%%ERRORLEVEL%%\""); + return windowsCommand; } /** diff --git a/core/src/test/java/hudson/util/ArgumentListBuilderTest.java b/core/src/test/java/hudson/util/ArgumentListBuilderTest.java index 7afe8c670e..5981c0913f 100644 --- a/core/src/test/java/hudson/util/ArgumentListBuilderTest.java +++ b/core/src/test/java/hudson/util/ArgumentListBuilderTest.java @@ -31,7 +31,10 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; + +import org.junit.Ignore; import org.junit.Test; +import org.jvnet.hudson.test.Issue; public class ArgumentListBuilderTest { @@ -101,28 +104,51 @@ public class ArgumentListBuilderTest { @Test public void testToWindowsCommand() { - ArgumentListBuilder builder = new ArgumentListBuilder( - "ant.bat", "-Dfoo1=abc", // nothing special, no quotes - "-Dfoo2=foo bar", "-Dfoo3=/u*r", "-Dfoo4=/us?", // add quotes - "-Dfoo10=bar,baz", - "-Dfoo5=foo;bar^baz", "-Dfoo6=&here;", // add quotes - "-Dfoo7=foo|bar\"baz", // add quotes and "" for " - "-Dfoo8=% %QED% %comspec% %-%(%.%", // add quotes, and extra quotes for %Q and %c - "-Dfoo9=%'''%%@%"); // no quotes as none of the % are followed by a letter + ArgumentListBuilder builder = new ArgumentListBuilder(). + add("ant.bat").add("-Dfoo1=abc"). // nothing special, no quotes + add("-Dfoo2=foo bar").add("-Dfoo3=/u*r").add("-Dfoo4=/us?"). // add quotes + add("-Dfoo10=bar,baz"). + add("-Dfoo5=foo;bar^baz").add("-Dfoo6=&here;"). // add quotes + add("-Dfoo7=foo|bar\"baz"). // add quotes and "" for " + add("-Dfoo8=% %QED% %comspec% %-%(%.%"). // add quotes, and extra quotes for %Q and %c + add("-Dfoo9=%'''%%@%"); // no quotes as none of the % are followed by a letter // By default, does not escape %VAR% assertThat(builder.toWindowsCommand().toCommandArray(), is(new String[] { "cmd.exe", "/C", - "\"ant.bat -Dfoo1=abc \"-Dfoo2=foo bar\"" - + " \"-Dfoo3=/u*r\" \"-Dfoo4=/us?\" \"-Dfoo10=bar,baz\" \"-Dfoo5=foo;bar^baz\"" - + " \"-Dfoo6=&here;\" \"-Dfoo7=foo|bar\"\"baz\"" - + " \"-Dfoo8=% %QED% %comspec% %-%(%.%\"" - + " -Dfoo9=%'''%%@% && exit %%ERRORLEVEL%%\"" })); + "\"ant.bat", "-Dfoo1=abc", "\"-Dfoo2=foo bar\"", "\"-Dfoo3=/u*r\"", "\"-Dfoo4=/us?\"", + "\"-Dfoo10=bar,baz\"", "\"-Dfoo5=foo;bar^baz\"", "\"-Dfoo6=&here;\"", + "\"-Dfoo7=foo|bar\"\"baz\"", "\"-Dfoo8=% %QED% %comspec% %-%(%.%\"", + "-Dfoo9=%'''%%@%", "&&", "exit", "%%ERRORLEVEL%%\"" })); // Pass flag to escape %VAR% assertThat(builder.toWindowsCommand(true).toCommandArray(), is(new String[] { "cmd.exe", "/C", - "\"ant.bat -Dfoo1=abc \"-Dfoo2=foo bar\"" - + " \"-Dfoo3=/u*r\" \"-Dfoo4=/us?\" \"-Dfoo10=bar,baz\" \"-Dfoo5=foo;bar^baz\"" - + " \"-Dfoo6=&here;\" \"-Dfoo7=foo|bar\"\"baz\"" - + " \"-Dfoo8=% %\"Q\"ED% %\"c\"omspec% %-%(%.%\"" - + " -Dfoo9=%'''%%@% && exit %%ERRORLEVEL%%\"" })); + "\"ant.bat", "-Dfoo1=abc", "\"-Dfoo2=foo bar\"", "\"-Dfoo3=/u*r\"", "\"-Dfoo4=/us?\"", + "\"-Dfoo10=bar,baz\"", "\"-Dfoo5=foo;bar^baz\"", "\"-Dfoo6=&here;\"", + "\"-Dfoo7=foo|bar\"\"baz\"", "\"-Dfoo8=% %\"Q\"ED% %\"c\"omspec% %-%(%.%\"", + "-Dfoo9=%'''%%@%", "&&", "exit", "%%ERRORLEVEL%%\"" })); + // Try to hide password + builder.add("-Dpassword=hidden", true); + // By default, does not escape %VAR% + assertThat(builder.toWindowsCommand().toString(), is("cmd.exe /C \"ant.bat -Dfoo1=abc \"\"-Dfoo2=foo bar\"\" \"-Dfoo3=/u*r\" \"-Dfoo4=/us?\" \"-Dfoo10=bar,baz\" \"-Dfoo5=foo;bar^baz\" \"-Dfoo6=&here;\" \"-Dfoo7=foo|bar\"\"baz\" \"\"-Dfoo8=% %QED% %comspec% %-%(%.%\"\" -Dfoo9=%'''%%@% ****** && exit %%ERRORLEVEL%%\"" )); + // Pass flag to escape %VAR% + assertThat(builder.toWindowsCommand(true).toString(), is("cmd.exe /C \"ant.bat -Dfoo1=abc \"\"-Dfoo2=foo bar\"\" \"-Dfoo3=/u*r\" \"-Dfoo4=/us?\" \"-Dfoo10=bar,baz\" \"-Dfoo5=foo;bar^baz\" \"-Dfoo6=&here;\" \"-Dfoo7=foo|bar\"\"baz\" \"\"-Dfoo8=% %\"Q\"ED% %\"c\"omspec% %-%(%.%\"\" -Dfoo9=%'''%%@% ****** && exit %%ERRORLEVEL%%\"")); + } + + @Test + @Ignore + @Issue("JENKINS-28790") + public void testToWindowsCommandMasked() { + ArgumentListBuilder builder = new ArgumentListBuilder(). + add("ant.bat").add("-Dfoo1=abc"). // nothing special, no quotes + add("-Dfoo2=foo bar").add("-Dfoo3=/u*r").add("-Dfoo4=/us?"). // add quotes + add("-Dfoo10=bar,baz"). + add("-Dfoo5=foo;bar^baz").add("-Dfoo6=&here;"). // add quotes + add("-Dfoo7=foo|bar\"baz"). // add quotes and "" for " + add("-Dfoo8=% %QED% %comspec% %-%(%.%"). // add quotes, and extra quotes for %Q and %c + add("-Dfoo9=%'''%%@%"). // no quotes as none of the % are followed by a letter + add("-Dpassword=hidden", true); + // By default, does not escape %VAR% + assertThat(builder.toWindowsCommand().toString(), is("cmd.exe /C \"ant.bat -Dfoo1=abc \"\"-Dfoo2=foo bar\"\" \"-Dfoo3=/u*r\" \"-Dfoo4=/us?\" \"-Dfoo10=bar,baz\" \"-Dfoo5=foo;bar^baz\" \"-Dfoo6=&here;\" \"-Dfoo7=foo|bar\"\"baz\" \"\"-Dfoo8=% %QED% %comspec% %-%(%.%\"\" -Dfoo9=%'''%%@% ****** && exit %%ERRORLEVEL%%\"" )); + // Pass flag to escape %VAR% + assertThat(builder.toWindowsCommand(true).toString(), is("cmd.exe /C \"ant.bat -Dfoo1=abc \"\"-Dfoo2=foo bar\"\" \"-Dfoo3=/u*r\" \"-Dfoo4=/us?\" \"-Dfoo10=bar,baz\" \"-Dfoo5=foo;bar^baz\" \"-Dfoo6=&here;\" \"-Dfoo7=foo|bar\"\"baz\" \"\"-Dfoo8=% %\"Q\"ED% %\"c\"omspec% %-%(%.%\"\" -Dfoo9=%'''%%@% ****** && exit %%ERRORLEVEL%%\"")); } @Test From 1b720f8711445a134aac0f3de0d9f0ab831dfc0c Mon Sep 17 00:00:00 2001 From: Allan Burdajewicz Date: Sat, 6 Feb 2016 12:36:37 +1000 Subject: [PATCH 013/156] [JENKINS-32134]: Change the terminology to make it more understandable for users. --- .../main/webapp/help/project-config/downstream.html | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/war/src/main/webapp/help/project-config/downstream.html b/war/src/main/webapp/help/project-config/downstream.html index 58055de756..33853b49bd 100644 --- a/war/src/main/webapp/help/project-config/downstream.html +++ b/war/src/main/webapp/help/project-config/downstream.html @@ -9,11 +9,11 @@

- A project located inside an ItemGroup need to be specified by using a path. It can be an absolute - path starting from Jenkins root, like "/grp/abc" where "grp" is an ItemGroup located at the root - of Jenkins. It can also be a relative path from the current project location, like "grp/abc" where - "grp" is an ItemGroup located at the same level as the current project. - Examples of plugins that provide an ItemGroup implementation include but are not limited to the + Nested projects need to be specified by using a path. It can be an absolute path starting from Jenkins + root, like "/grp/abc" where "grp" is located at the root of Jenkins. It can also be a relative path + from the current project location, like "grp/abc" where "grp" is located at the same level as the + current project. + Examples of plugins that provide such implementation include but are not limited to the CloudBees Folder Plugin and the Multi-Branch Project Plugin From 7fb1ac9d9f1bc006ca9d90ccaef402eaba4404a2 Mon Sep 17 00:00:00 2001 From: Emilio Escobar Date: Mon, 8 Feb 2016 11:02:54 -0600 Subject: [PATCH 014/156] [JENKINS-28790] Baptiste comments fixed --- .../test/java/hudson/util/ArgumentListBuilderTest.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/src/test/java/hudson/util/ArgumentListBuilderTest.java b/core/src/test/java/hudson/util/ArgumentListBuilderTest.java index 5981c0913f..e3fb261abc 100644 --- a/core/src/test/java/hudson/util/ArgumentListBuilderTest.java +++ b/core/src/test/java/hudson/util/ArgumentListBuilderTest.java @@ -104,8 +104,8 @@ public class ArgumentListBuilderTest { @Test public void testToWindowsCommand() { - ArgumentListBuilder builder = new ArgumentListBuilder(). - add("ant.bat").add("-Dfoo1=abc"). // nothing special, no quotes + ArgumentListBuilder builder = new ArgumentListBuilder(). + add("ant.bat").add("-Dfoo1=abc"). // nothing special, no quotes add("-Dfoo2=foo bar").add("-Dfoo3=/u*r").add("-Dfoo4=/us?"). // add quotes add("-Dfoo10=bar,baz"). add("-Dfoo5=foo;bar^baz").add("-Dfoo6=&here;"). // add quotes @@ -133,11 +133,11 @@ public class ArgumentListBuilderTest { } @Test - @Ignore + @Ignore("It's only for reproduce JENKINS-28790 issue. It's added to testToWindowsCommand") @Issue("JENKINS-28790") public void testToWindowsCommandMasked() { ArgumentListBuilder builder = new ArgumentListBuilder(). - add("ant.bat").add("-Dfoo1=abc"). // nothing special, no quotes + add("ant.bat").add("-Dfoo1=abc"). // nothing special, no quotes add("-Dfoo2=foo bar").add("-Dfoo3=/u*r").add("-Dfoo4=/us?"). // add quotes add("-Dfoo10=bar,baz"). add("-Dfoo5=foo;bar^baz").add("-Dfoo6=&here;"). // add quotes From 5423f695ab83ec7d49daae538d25177872b0aada Mon Sep 17 00:00:00 2001 From: Emilio Escobar Date: Tue, 9 Feb 2016 14:28:08 -0600 Subject: [PATCH 015/156] [JENKINS-28790] NIT: tabulation issue --- core/src/main/java/hudson/util/ArgumentListBuilder.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/hudson/util/ArgumentListBuilder.java b/core/src/main/java/hudson/util/ArgumentListBuilder.java index 38f92329a6..e54f032b5c 100644 --- a/core/src/main/java/hudson/util/ArgumentListBuilder.java +++ b/core/src/main/java/hudson/util/ArgumentListBuilder.java @@ -317,8 +317,8 @@ public class ArgumentListBuilder implements Serializable, Cloneable { ArgumentListBuilder windowsCommand = new ArgumentListBuilder().add("cmd.exe", "/C"); boolean quoted, percent; for (int i = 0; i < args.size(); i++) { - StringBuilder quotedArgs = new StringBuilder(); - String arg = args.get(i); + StringBuilder quotedArgs = new StringBuilder(); + String arg = args.get(i); quoted = percent = false; for (int j = 0; j < arg.length(); j++) { char c = arg.charAt(j); From 344e917cc4a1096bb1805b14f0f2e45978b62757 Mon Sep 17 00:00:00 2001 From: Emilio Escobar Date: Thu, 11 Feb 2016 16:42:05 -0600 Subject: [PATCH 016/156] [JENKINS-28790] added valentina condition when the first argument is quoted. --- core/src/main/java/hudson/util/ArgumentListBuilder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/hudson/util/ArgumentListBuilder.java b/core/src/main/java/hudson/util/ArgumentListBuilder.java index e54f032b5c..255208737a 100644 --- a/core/src/main/java/hudson/util/ArgumentListBuilder.java +++ b/core/src/main/java/hudson/util/ArgumentListBuilder.java @@ -342,7 +342,7 @@ public class ArgumentListBuilder implements Serializable, Cloneable { percent = (c == '%'); if (quoted) quotedArgs.append(c); } - if(i == 0) quotedArgs.append('"'); + if(i == 0 && quoted) quotedArgs.insert(0, '"'); else if (i == 0 && !quoted) quotedArgs.append('"'); if (quoted) quotedArgs.append('"'); else quotedArgs.append(arg); windowsCommand.add(quotedArgs, mask.get(i)); From 8d3eb20e5d82cf3528849301493ec5b2ddf9dd87 Mon Sep 17 00:00:00 2001 From: Yoann Dubreuil Date: Fri, 19 Feb 2016 17:47:55 +0100 Subject: [PATCH 017/156] [JENKINS-33038] correctly set executor in FutureImpl This patch fixes cancellation of the build linked to the Future object. Before, the executor set in FutureImpl was always empty, because WorkUnitContext.createWorkUnit() is not called from an Executor thread anymore. --- core/src/main/java/hudson/model/queue/WorkUnit.java | 3 +++ core/src/main/java/hudson/model/queue/WorkUnitContext.java | 7 +------ 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/hudson/model/queue/WorkUnit.java b/core/src/main/java/hudson/model/queue/WorkUnit.java index 58c4ba31ca..68a3d7fce7 100644 --- a/core/src/main/java/hudson/model/queue/WorkUnit.java +++ b/core/src/main/java/hudson/model/queue/WorkUnit.java @@ -71,6 +71,9 @@ public final class WorkUnit { public void setExecutor(@CheckForNull Executor e) { executor = e; + if (e != null) { + context.future.addExecutor(e); + } } /** diff --git a/core/src/main/java/hudson/model/queue/WorkUnitContext.java b/core/src/main/java/hudson/model/queue/WorkUnitContext.java index 503a177491..7c030234aa 100644 --- a/core/src/main/java/hudson/model/queue/WorkUnitContext.java +++ b/core/src/main/java/hudson/model/queue/WorkUnitContext.java @@ -88,14 +88,9 @@ public final class WorkUnitContext { } /** - * Called by the executor that executes a member {@link SubTask} that belongs to this task - * to create its {@link WorkUnit}. + * Called within the queue maintenance process to create a {@link WorkUnit} for the given {@link SubTask} */ public WorkUnit createWorkUnit(SubTask execUnit) { - Executor executor = Executor.currentExecutor(); - if (executor != null) { // TODO is it legal for this to be called by a non-executor thread? - future.addExecutor(executor); - } WorkUnit wu = new WorkUnit(this, execUnit); workUnits.add(wu); return wu; From 451186d97008470a29aaa2b026ee1e021756edd4 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Fri, 19 Feb 2016 10:42:05 -0800 Subject: [PATCH 018/156] 2.54 contains JNLP3 change --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 8eb258229d..08871b1952 100644 --- a/pom.xml +++ b/pom.xml @@ -180,7 +180,7 @@ THE SOFTWARE. org.jenkins-ci.main remoting - 2.54-SNAPSHOT + 2.54 From a6cd33f3a644a8b170306659f566ae0e532abe29 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Mon, 22 Feb 2016 11:10:06 -0800 Subject: [PATCH 019/156] Need to call ChannelConfigurator --- .../java/jenkins/slaves/JnlpSlaveAgentProtocol3.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java b/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java index c964824237..42eb806324 100644 --- a/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java +++ b/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java @@ -8,6 +8,7 @@ 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; @@ -86,7 +87,13 @@ public class JnlpSlaveAgentProtocol3 extends AgentProtocol { logw = new PrintWriter(log,true); logw.println("JNLP agent connected from " + socket.getInetAddress()); - return super.createChannelBuilder(nodeName).withHeaderStream(log); + ChannelBuilder cb = super.createChannelBuilder(nodeName).withHeaderStream(log); + + for (ChannelConfigurator cc : ChannelConfigurator.all()) { + cc.onChannelBuilding(cb, computer); + } + + return cb; } @Override From e6065062809a3ae1a204c3d30db60354a11a2070 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Mon, 22 Feb 2016 11:11:21 -0800 Subject: [PATCH 020/156] When this gets merged let's record the version number --- core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java | 1 + 1 file changed, 1 insertion(+) diff --git a/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java b/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java index 42eb806324..0187a8b066 100644 --- a/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java +++ b/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java @@ -26,6 +26,7 @@ import java.util.logging.Logger; *

@see {@link org.jenkinsci.remoting.engine.JnlpProtocol3} for more details. * * @author Akshay Dayal + * @since 1.XXX */ @Extension public class JnlpSlaveAgentProtocol3 extends AgentProtocol { From 236d2474a6951cf4987d0c24ea41c29d129a9604 Mon Sep 17 00:00:00 2001 From: Daniel Beck Date: Wed, 24 Feb 2016 15:41:19 +0100 Subject: [PATCH 021/156] [FIX JENKINS-33127] BootFailures send HTTP 500 --- core/src/main/resources/hudson/util/AWTProblem/index.jelly | 1 + .../main/resources/hudson/util/HudsonFailedToLoad/index.jelly | 1 + .../hudson/util/IncompatibleAntVersionDetected/index.jelly | 1 + .../hudson/util/IncompatibleServletVersionDetected/index.jelly | 1 + .../resources/hudson/util/IncompatibleVMDetected/index.jelly | 1 + .../hudson/util/InsufficientPermissionDetected/index.jelly | 1 + .../resources/hudson/util/JenkinsReloadFailed/index.groovy | 3 +++ core/src/main/resources/hudson/util/NoHomeDir/index.jelly | 1 + core/src/main/resources/hudson/util/NoTempDir/index.jelly | 1 + 9 files changed, 11 insertions(+) diff --git a/core/src/main/resources/hudson/util/AWTProblem/index.jelly b/core/src/main/resources/hudson/util/AWTProblem/index.jelly index c5acf32c36..ec8df8e329 100644 --- a/core/src/main/resources/hudson/util/AWTProblem/index.jelly +++ b/core/src/main/resources/hudson/util/AWTProblem/index.jelly @@ -24,6 +24,7 @@ THE SOFTWARE. + diff --git a/core/src/main/resources/hudson/util/HudsonFailedToLoad/index.jelly b/core/src/main/resources/hudson/util/HudsonFailedToLoad/index.jelly index a69eb1d4db..3c868da831 100644 --- a/core/src/main/resources/hudson/util/HudsonFailedToLoad/index.jelly +++ b/core/src/main/resources/hudson/util/HudsonFailedToLoad/index.jelly @@ -24,6 +24,7 @@ THE SOFTWARE. + diff --git a/core/src/main/resources/hudson/util/IncompatibleAntVersionDetected/index.jelly b/core/src/main/resources/hudson/util/IncompatibleAntVersionDetected/index.jelly index 6c88a89b90..e516ea8025 100644 --- a/core/src/main/resources/hudson/util/IncompatibleAntVersionDetected/index.jelly +++ b/core/src/main/resources/hudson/util/IncompatibleAntVersionDetected/index.jelly @@ -24,6 +24,7 @@ THE SOFTWARE. + diff --git a/core/src/main/resources/hudson/util/IncompatibleServletVersionDetected/index.jelly b/core/src/main/resources/hudson/util/IncompatibleServletVersionDetected/index.jelly index 76407ab939..926a5cfd79 100644 --- a/core/src/main/resources/hudson/util/IncompatibleServletVersionDetected/index.jelly +++ b/core/src/main/resources/hudson/util/IncompatibleServletVersionDetected/index.jelly @@ -24,6 +24,7 @@ THE SOFTWARE. + diff --git a/core/src/main/resources/hudson/util/IncompatibleVMDetected/index.jelly b/core/src/main/resources/hudson/util/IncompatibleVMDetected/index.jelly index ee1d8a0797..c9baaa5ce2 100644 --- a/core/src/main/resources/hudson/util/IncompatibleVMDetected/index.jelly +++ b/core/src/main/resources/hudson/util/IncompatibleVMDetected/index.jelly @@ -24,6 +24,7 @@ THE SOFTWARE. + diff --git a/core/src/main/resources/hudson/util/InsufficientPermissionDetected/index.jelly b/core/src/main/resources/hudson/util/InsufficientPermissionDetected/index.jelly index e3ef38d31b..0dc65cfc28 100644 --- a/core/src/main/resources/hudson/util/InsufficientPermissionDetected/index.jelly +++ b/core/src/main/resources/hudson/util/InsufficientPermissionDetected/index.jelly @@ -24,6 +24,7 @@ THE SOFTWARE. + diff --git a/core/src/main/resources/hudson/util/JenkinsReloadFailed/index.groovy b/core/src/main/resources/hudson/util/JenkinsReloadFailed/index.groovy index 761ec1c4e0..9405ab55c6 100644 --- a/core/src/main/resources/hudson/util/JenkinsReloadFailed/index.groovy +++ b/core/src/main/resources/hudson/util/JenkinsReloadFailed/index.groovy @@ -1,6 +1,9 @@ import hudson.Functions def l = namespace(lib.LayoutTagLib) +def st = namespace("jelly:stapler") + +st.statusCode(value: 500) l.layout { l.header(title:"Jenkins") diff --git a/core/src/main/resources/hudson/util/NoHomeDir/index.jelly b/core/src/main/resources/hudson/util/NoHomeDir/index.jelly index a75b653e04..0476004a78 100644 --- a/core/src/main/resources/hudson/util/NoHomeDir/index.jelly +++ b/core/src/main/resources/hudson/util/NoHomeDir/index.jelly @@ -24,6 +24,7 @@ THE SOFTWARE. + diff --git a/core/src/main/resources/hudson/util/NoTempDir/index.jelly b/core/src/main/resources/hudson/util/NoTempDir/index.jelly index c78d70f11c..f6b27dcef0 100644 --- a/core/src/main/resources/hudson/util/NoTempDir/index.jelly +++ b/core/src/main/resources/hudson/util/NoTempDir/index.jelly @@ -24,6 +24,7 @@ THE SOFTWARE. + From af8bb33673167d1014a20d5ec8f1ae3681384cb1 Mon Sep 17 00:00:00 2001 From: Reginaldo Russinholi Date: Thu, 25 Feb 2016 14:52:54 -0300 Subject: [PATCH 022/156] Fix proxy server name documentation (#2060) --- .../resources/hudson/ProxyConfiguration/help-name_pt_BR.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/resources/hudson/ProxyConfiguration/help-name_pt_BR.html b/core/src/main/resources/hudson/ProxyConfiguration/help-name_pt_BR.html index 2b4882af53..41f744e143 100644 --- a/core/src/main/resources/hudson/ProxyConfiguration/help-name_pt_BR.html +++ b/core/src/main/resources/hudson/ProxyConfiguration/help-name_pt_BR.html @@ -6,8 +6,8 @@ instalar plugins para você. Note que o Jenkins usa HTTPS para se comunicar com o centro de atualização para baixar os plugins.

- O valor que você colocar aqui será atribuído a propriedade de sistema http.proxyHost. Deixar este campo vazio - deixa esta propriedade sem atribuição, o que significa que o Jenkins tentará conectar ao host externo diretamente. + Deixar este campo vazio + faz com que o Jenkins tente conectar à internet diretamente.

Se você não está certo sobre o valor, cheque a configuração de proxy de seu browser. From e4128963b203fce373d1b823bea33432972314b9 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Thu, 25 Feb 2016 15:04:03 -0500 Subject: [PATCH 023/156] We expect a modelClass to be assignable to ModelObject. --- core/src/main/java/hudson/security/PermissionScope.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/hudson/security/PermissionScope.java b/core/src/main/java/hudson/security/PermissionScope.java index c647b33e2c..e9a2f54292 100644 --- a/core/src/main/java/hudson/security/PermissionScope.java +++ b/core/src/main/java/hudson/security/PermissionScope.java @@ -29,6 +29,7 @@ import hudson.model.Computer; import hudson.model.Item; import hudson.model.ItemGroup; import hudson.model.Job; +import hudson.model.ModelObject; import hudson.model.Run; import jenkins.model.Jenkins; @@ -56,7 +57,7 @@ public final class PermissionScope { /** * Domain model type that approximates this scope. */ - public final Class modelClass; + public final Class modelClass; /** * Other bigger scopes that this scope divides. For example, permissions scoped to {@link ItemGroup} @@ -65,7 +66,7 @@ public final class PermissionScope { */ private final Set containers; - public PermissionScope(Class modelClass, PermissionScope... containers) { + public PermissionScope(Class modelClass, PermissionScope... containers) { this.modelClass = modelClass; this.containers = ImmutableSet.copyOf(containers); } From c7687cd9ebc2ffa45215c457ed45dc52f22a5d85 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Thu, 25 Feb 2016 15:04:31 -0500 Subject: [PATCH 024/156] COMPUTER-scoped permissions are in fact often checked on Node. --- core/src/main/java/hudson/security/PermissionScope.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/hudson/security/PermissionScope.java b/core/src/main/java/hudson/security/PermissionScope.java index e9a2f54292..cf258d62ea 100644 --- a/core/src/main/java/hudson/security/PermissionScope.java +++ b/core/src/main/java/hudson/security/PermissionScope.java @@ -30,6 +30,7 @@ import hudson.model.Item; import hudson.model.ItemGroup; import hudson.model.Job; import hudson.model.ModelObject; +import hudson.model.Node; import hudson.model.Run; import jenkins.model.Jenkins; @@ -111,7 +112,7 @@ public final class PermissionScope { public static final PermissionScope RUN = new PermissionScope(Run.class,ITEM); /** - * Permissions scoped to current instances of {@link Computer}s. + * Permissions scoped to {@link Node}s or {@link Computer}s (generally interchangeably). */ public static final PermissionScope COMPUTER = new PermissionScope(Computer.class,JENKINS); } From d00cf06adb6674e7af5188c6a07e15374758f322 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Thu, 25 Feb 2016 15:06:27 -0500 Subject: [PATCH 025/156] Computer.CREATE should be scoped to JENKINS, not COMPUTER, since it is checked on Jenkins, not a Node or Computer. Compare Item.CREATE, scoped to ITEM_GROUP, not ITEM. --- core/src/main/java/hudson/model/Computer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/hudson/model/Computer.java b/core/src/main/java/hudson/model/Computer.java index 4ce4a3b7a3..f20a27cc02 100644 --- a/core/src/main/java/hudson/model/Computer.java +++ b/core/src/main/java/hudson/model/Computer.java @@ -1701,7 +1701,7 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces */ public static final Permission EXTENDED_READ = new Permission(PERMISSIONS,"ExtendedRead", Messages._Computer_ExtendedReadPermission_Description(), CONFIGURE, Boolean.getBoolean("hudson.security.ExtendedReadPermission"), new PermissionScope[]{PermissionScope.COMPUTER}); public static final Permission DELETE = new Permission(PERMISSIONS,"Delete", Messages._Computer_DeletePermission_Description(), Permission.DELETE, PermissionScope.COMPUTER); - public static final Permission CREATE = new Permission(PERMISSIONS,"Create", Messages._Computer_CreatePermission_Description(), Permission.CREATE, PermissionScope.COMPUTER); + public static final Permission CREATE = new Permission(PERMISSIONS,"Create", Messages._Computer_CreatePermission_Description(), Permission.CREATE, PermissionScope.JENKINS); 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); public static final Permission BUILD = new Permission(PERMISSIONS, "Build", Messages._Computer_BuildPermission_Description(), Permission.WRITE, PermissionScope.COMPUTER); From f3a9dd5bb07e3e920410c838b63eb82d33bcb7df Mon Sep 17 00:00:00 2001 From: Sam Van Oort Date: Mon, 29 Feb 2016 14:36:39 -0500 Subject: [PATCH 026/156] Remove broken XStream2 test of legacy custom ConcurrentHashMapSerialiation This one requires some explanation: there is a bug As custom serialization of ConcurrentHashMaps was removed circa 2010 (see eb51d46d56369915ae4031dc167322ad126f0be6) We can assume that testing conversion is no longer a high priority. Root cause appears to be a subtle change in the XStream attribute access behavior when run under JDK8 only. This appears to be the only case that triggers it. --- .../test/java/hudson/util/XStream2Test.java | 5 - .../hudson/util/old-concurrentHashMap.xml | 225 ------------------ 2 files changed, 230 deletions(-) delete mode 100644 core/src/test/resources/hudson/util/old-concurrentHashMap.xml diff --git a/core/src/test/java/hudson/util/XStream2Test.java b/core/src/test/java/hudson/util/XStream2Test.java index ed29c8c8f2..1316965867 100644 --- a/core/src/test/java/hudson/util/XStream2Test.java +++ b/core/src/test/java/hudson/util/XStream2Test.java @@ -296,11 +296,6 @@ public class XStream2Test { } finally { v.delete(); } - - // should be able to read in old data just fine - Foo2 map = (Foo2) new XStream2().fromXML(getClass().getResourceAsStream("old-concurrentHashMap.xml")); - assertEquals(1,map.m.size()); - assertEquals("def",map.m.get("abc")); } @Issue("SECURITY-105") diff --git a/core/src/test/resources/hudson/util/old-concurrentHashMap.xml b/core/src/test/resources/hudson/util/old-concurrentHashMap.xml deleted file mode 100644 index 14b7ef152c..0000000000 --- a/core/src/test/resources/hudson/util/old-concurrentHashMap.xml +++ /dev/null @@ -1,225 +0,0 @@ - - - - - - 15 - 28 - - - - - - 0 - - - - - - - 0.75 - - - - - - 0 - - - - - - - 0.75 - - - - - - 0 - - - - - - - 0.75 - - - - - - 0 - - - - - - - 0.75 - - - - - - 0 - - - - - - - 0.75 - - - - - - 0 - - - - - - - 0.75 - - - - - - 0 - - - - - - - 0.75 - - - - - - 0 - - - - - - - 0.75 - - - - - - 0 - - - - - - - 0.75 - - - - - - 0 - - - - - - - 0.75 - - - - - - 0 - - - - - - - 0.75 - - - - - - 0 - - - - - - - 0.75 - - - - - - 0 - - - - - - - 0.75 - - - - - - 0 - - - - - - - 0.75 - - - - - - 0 - - - - - - - 0.75 - - - - - - 0 - - - - - - - 0.75 - - - - abc - def - - - - - \ No newline at end of file From e136952d2ab6e5db3684bdb60af1f782d672177e Mon Sep 17 00:00:00 2001 From: Sam Van Oort Date: Wed, 2 Mar 2016 10:53:03 -0500 Subject: [PATCH 027/156] Remove remainder of ConcurrentHashMap testcase --- .../test/java/hudson/util/XStream2Test.java | 26 ------------------- 1 file changed, 26 deletions(-) diff --git a/core/src/test/java/hudson/util/XStream2Test.java b/core/src/test/java/hudson/util/XStream2Test.java index 1316965867..aeb781f672 100644 --- a/core/src/test/java/hudson/util/XStream2Test.java +++ b/core/src/test/java/hudson/util/XStream2Test.java @@ -272,32 +272,6 @@ public class XStream2Test { ConcurrentHashMap m = new ConcurrentHashMap(); } - /** - * Tests that ConcurrentHashMap is serialized into a more compact format, - * but still can deserialize to older, verbose format. - */ - @Test - public void concurrentHashMapSerialization() throws Exception { - Foo2 foo = new Foo2(); - foo.m.put("abc","def"); - foo.m.put("ghi","jkl"); - File v = File.createTempFile("hashmap", "xml"); - try { - new XmlFile(v).write(foo); - - // should serialize like map - String xml = FileUtils.readFileToString(v); - assertFalse(xml.contains("java.util.concurrent")); - //System.out.println(xml); - Foo2 deserialized = (Foo2) new XStream2().fromXML(xml); - assertEquals(2,deserialized.m.size()); - assertEquals("def", deserialized.m.get("abc")); - assertEquals("jkl", deserialized.m.get("ghi")); - } finally { - v.delete(); - } - } - @Issue("SECURITY-105") @Test public void dynamicProxyBlocked() { From 7dd72969d98b63d90f71ba85a498f61e84c21bac Mon Sep 17 00:00:00 2001 From: Sam Van Oort Date: Wed, 2 Mar 2016 11:00:42 -0500 Subject: [PATCH 028/156] Remove the broken ConcurrentHashMapConverter impl, since it handles a conversion dating back to 2010 and does not work with Java 8 --- .../util/ConcurrentHashMapConverter.java | 69 ------------------- core/src/main/java/hudson/util/XStream2.java | 1 - 2 files changed, 70 deletions(-) delete mode 100644 core/src/main/java/hudson/util/ConcurrentHashMapConverter.java diff --git a/core/src/main/java/hudson/util/ConcurrentHashMapConverter.java b/core/src/main/java/hudson/util/ConcurrentHashMapConverter.java deleted file mode 100644 index ae917d4996..0000000000 --- a/core/src/main/java/hudson/util/ConcurrentHashMapConverter.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * The MIT License - * - * Copyright (c) 2010, InfraDNA, Inc. - * - * 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.util; - -import com.thoughtworks.xstream.XStream; -import com.thoughtworks.xstream.converters.UnmarshallingContext; -import com.thoughtworks.xstream.converters.collections.MapConverter; -import com.thoughtworks.xstream.converters.reflection.ReflectionProvider; -import com.thoughtworks.xstream.converters.reflection.SerializableConverter; -import com.thoughtworks.xstream.io.HierarchicalStreamReader; -import com.thoughtworks.xstream.mapper.Mapper; - -import java.util.concurrent.ConcurrentHashMap; - -/** - * {@link ConcurrentHashMap} should convert like a map, instead of via serialization. - * - * @author Kohsuke Kawaguchi - */ -public class ConcurrentHashMapConverter extends MapConverter { - private final SerializableConverter sc; - - public ConcurrentHashMapConverter(XStream xs) { - this(xs.getMapper(),xs.getReflectionProvider()); - } - - public ConcurrentHashMapConverter(Mapper mapper, ReflectionProvider reflectionProvider) { - super(mapper); - sc = new SerializableConverter(mapper,reflectionProvider); - } - - @Override - public boolean canConvert(Class type) { - return type==ConcurrentHashMap.class; - } - - @Override - public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) { - // ConcurrentHashMap used to serialize as custom serialization, - // so read it in a compatible fashion. - String s = reader.getAttribute("serialization"); - if(s!=null && s.equals("custom")) { - return sc.unmarshal(reader,context); - } else { - return super.unmarshal(reader, context); - } - } -} diff --git a/core/src/main/java/hudson/util/XStream2.java b/core/src/main/java/hudson/util/XStream2.java index bfe924e70a..8ea539fc25 100644 --- a/core/src/main/java/hudson/util/XStream2.java +++ b/core/src/main/java/hudson/util/XStream2.java @@ -151,7 +151,6 @@ public class XStream2 extends XStream { registerConverter(new ImmutableSortedSetConverter(getMapper(),getReflectionProvider()),10); registerConverter(new ImmutableSetConverter(getMapper(),getReflectionProvider()),10); registerConverter(new ImmutableListConverter(getMapper(),getReflectionProvider()),10); - registerConverter(new ConcurrentHashMapConverter(getMapper(),getReflectionProvider()),10); registerConverter(new CopyOnWriteMap.Tree.ConverterImpl(getMapper()),10); // needs to override MapConverter registerConverter(new DescribableList.ConverterImpl(getMapper()),10); // explicitly added to handle subtypes registerConverter(new Label.ConverterImpl(),10); From 2a61f30250f930b579424dea9682a09e03e7d9db Mon Sep 17 00:00:00 2001 From: Stephen Connolly Date: Fri, 4 Mar 2016 10:08:13 +0000 Subject: [PATCH 029/156] [FIXED JENKINS-33319] Premtively wake the acceptor thread to let it close cleanly - Also adds a Ping Agent protocol that could be used by nagios monitoring, etc. to verify that the slave agent listener is alive - We use the ping agent protocol to ensure that the acceptor thread wakes up, loops and sees that the shutdown is started that prevents the socket close exception from being thrown --- .../java/hudson/TcpSlaveAgentListener.java | 95 ++++++++++++++++++- 1 file changed, 92 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/hudson/TcpSlaveAgentListener.java b/core/src/main/java/hudson/TcpSlaveAgentListener.java index 059e2af965..02f17946e6 100644 --- a/core/src/main/java/hudson/TcpSlaveAgentListener.java +++ b/core/src/main/java/hudson/TcpSlaveAgentListener.java @@ -24,6 +24,12 @@ package hudson; import hudson.slaves.OfflineCause; +import java.io.DataOutputStream; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.net.SocketAddress; +import java.util.Arrays; import jenkins.AgentProtocol; import java.io.BufferedWriter; @@ -37,6 +43,7 @@ import java.net.Socket; import java.nio.channels.ServerSocketChannel; import java.util.logging.Level; import java.util.logging.Logger; +import org.apache.commons.io.IOUtils; /** * Listens to incoming TCP connections from JNLP slave agents and CLI. @@ -91,7 +98,7 @@ public final class TcpSlaveAgentListener extends Thread { public void run() { try { // the loop eventually terminates when the socket is closed. - while (true) { + while (!shuttingDown) { Socket s = serverSocket.accept().socket(); // this prevents a connection from silently terminated by the router in between or the other peer @@ -115,6 +122,16 @@ public final class TcpSlaveAgentListener extends Thread { */ public void shutdown() { shuttingDown = true; + try { + SocketAddress localAddress = serverSocket.getLocalAddress(); + if (localAddress instanceof InetSocketAddress) { + InetSocketAddress address = (InetSocketAddress) localAddress; + Socket client = new Socket(address.getHostName(), address.getPort()); + new PingAgentProtocol().connect(client); + } + } catch (IOException e) { + LOGGER.log(Level.FINE, "Failed to send Ping to wake acceptor loop", e); + } try { serverSocket.close(); } catch (IOException e) { @@ -140,7 +157,7 @@ public final class TcpSlaveAgentListener extends Thread { @Override public void run() { try { - LOGGER.info("Accepted connection #"+id+" from "+s.getRemoteSocketAddress()); + LOGGER.log(Level.INFO, "Accepted connection #{0} from {1}", new Object[]{id,s.getRemoteSocketAddress()}); DataInputStream in = new DataInputStream(s.getInputStream()); PrintWriter out = new PrintWriter( @@ -183,6 +200,78 @@ public final class TcpSlaveAgentListener extends Thread { } } + /** + * This extension provides a Ping protocol that allows people to verify that the TcpSlaveAgentListener is alive. + * We also use this + * @since + */ + @Extension + public static class PingAgentProtocol extends AgentProtocol { + + private final byte[] ping; + + public PingAgentProtocol() { + try { + ping = "Ping\n".getBytes("UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new IllegalStateException("JLS mandates support for UTF-8 charset", e); + } + } + + @Override + public String getName() { + return "Ping"; + } + + @Override + public void handle(Socket socket) throws IOException, InterruptedException { + try { + OutputStream stream = socket.getOutputStream(); + try { + LOGGER.log(Level.FINE, "Received ping request from {0}", socket.getRemoteSocketAddress()); + stream.write(ping); + stream.flush(); + LOGGER.log(Level.FINE, "Sent ping response to {0}", socket.getRemoteSocketAddress()); + } finally { + stream.close(); + } + } finally { + socket.close(); + } + } + + public boolean connect(Socket socket) throws IOException { + try { + DataOutputStream out = null; + InputStream in = null; + try { + LOGGER.log(Level.FINE, "Requesting ping from {0}", socket.getRemoteSocketAddress()); + out = new DataOutputStream(socket.getOutputStream()); + out.writeUTF("Protocol:Ping"); + in = socket.getInputStream(); + byte[] response = new byte[ping.length]; + int responseLength = in.read(response); + if (responseLength == ping.length && Arrays.equals(response, ping)) { + LOGGER.log(Level.FINE, "Received ping response from {0}", socket.getRemoteSocketAddress()); + return true; + } else { + LOGGER.log(Level.FINE, "Expected ping response from {0} of {1} got {2}", new Object[]{ + socket.getRemoteSocketAddress(), + new String(ping, "UTF-8"), + new String(response, 0, responseLength, "UTF-8") + }); + return false; + } + } finally { + IOUtils.closeQuietly(out); + IOUtils.closeQuietly(in); + } + } finally { + socket.close(); + } + } + } + /** * Connection terminated because we are reconnected from the current peer. */ @@ -238,4 +327,4 @@ the application gets it from the dynamic JNLP. Just write it so that it can't do anything useful without going through a protected path or doing something to present credentials that could only have come from a valid user. -*/ \ No newline at end of file +*/ From b1e3f6fdd2095d17293ce2c9b26e40ea98380d73 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Fri, 4 Mar 2016 13:45:54 -0800 Subject: [PATCH 030/156] A/B test this feature in production JNLP3 is activated now for 10% of users. Let's keep it like this for a while and if no major issue occurs we should expose it to everyone. --- .../slaves/JnlpSlaveAgentProtocol3.java | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java b/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java index 0187a8b066..8b6b3df129 100644 --- a/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java +++ b/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java @@ -2,6 +2,7 @@ package jenkins.slaves; import hudson.AbortException; import hudson.Extension; +import hudson.Util; import hudson.model.Computer; import hudson.remoting.Channel; import hudson.remoting.ChannelBuilder; @@ -11,6 +12,8 @@ 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; @@ -35,7 +38,8 @@ public class JnlpSlaveAgentProtocol3 extends AgentProtocol { @Override public String getName() { - return "JNLP3-connect"; + if (ENABLED) return "JNLP3-connect"; + else return "JNLP3-disabled"; } @Override @@ -109,4 +113,25 @@ public class JnlpSlaveAgentProtocol3 extends AgentProtocol { } 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. + * + *

+ * 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; + + static { + String propName = JnlpSlaveAgentProtocol3.class.getName() + ".enabled"; + if (System.getProperties().containsKey(propName)) + ENABLED = Boolean.getBoolean(propName); + else { + byte hash = Util.fromHexString(Jenkins.getActiveInstance().getLegacyInstanceId())[0]; + ENABLED = (hash%10)==0; + } + } } From d0d39705105179f7d3b8cdc7a73ccc2056fbf2e5 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Fri, 4 Mar 2016 14:31:29 -0800 Subject: [PATCH 031/156] Pick up a new version of remoting See https://github.com/jenkinsci/remoting/pull/78 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index b4bcc0f5d0..bf317f7b61 100644 --- a/pom.xml +++ b/pom.xml @@ -179,7 +179,7 @@ THE SOFTWARE. org.jenkins-ci.main remoting - 2.55 + 2.56-20160304.223037-1 From eed5e913bea6def218cfc1bebe30c405db75861f Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Sun, 6 Mar 2016 19:28:52 -0800 Subject: [PATCH 032/156] [maven-release-plugin] prepare for next development iteration --- cli/pom.xml | 2 +- core/pom.xml | 2 +- pom.xml | 4 ++-- test/pom.xml | 2 +- war/pom.xml | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cli/pom.xml b/cli/pom.xml index 2139c424cf..4cfc6c0bf0 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -5,7 +5,7 @@ org.jenkins-ci.main pom - 1.652 + 1.653-SNAPSHOT cli diff --git a/core/pom.xml b/core/pom.xml index f7fd9698db..bc192fee05 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -29,7 +29,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 1.652 + 1.653-SNAPSHOT jenkins-core diff --git a/pom.xml b/pom.xml index d55dca854b..01a965a058 100644 --- a/pom.xml +++ b/pom.xml @@ -33,7 +33,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 1.652 + 1.653-SNAPSHOT pom Jenkins main module @@ -58,7 +58,7 @@ THE SOFTWARE. scm:git:git://github.com/jenkinsci/jenkins.git scm:git:ssh://git@github.com/jenkinsci/jenkins.git https://github.com/jenkinsci/jenkins - jenkins-1.652 + HEAD diff --git a/test/pom.xml b/test/pom.xml index 1064aba5e7..92355957c4 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 1.652 + 1.653-SNAPSHOT test diff --git a/war/pom.xml b/war/pom.xml index e79f82aa65..bc06446dea 100644 --- a/war/pom.xml +++ b/war/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 1.652 + 1.653-SNAPSHOT jenkins-war From 9cb52bf42e486699c2f6a23a5128057e1715829e Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Sun, 6 Mar 2016 19:36:07 -0800 Subject: [PATCH 033/156] updated changelog for release --- changelog.html | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/changelog.html b/changelog.html index c06c1d4c2d..9843033d92 100644 --- a/changelog.html +++ b/changelog.html @@ -54,12 +54,16 @@ Upcoming changes

+

What's new in 1.652 (2016/03/06)

-

What's new in 1.651 (2016/02/28)