From ab37bc84eb9639888f3a66c68a9b1536c5882c88 Mon Sep 17 00:00:00 2001 From: Stephen Connolly Date: Fri, 24 Apr 2015 10:36:36 +0100 Subject: [PATCH 1/9] [FIXED JENKINS-26558] Clients should provide a unique ID to be used for name collision avoidance - The current name collision avoidance uses the requests address, which could very likely be the same for all clients as they could be being routed through a HTTP proxy (or two) so that is not a good disambiguator - We use a digest of the client's interfaces and MAC addresses and the remoteFSRoot to try and give a consistent ID - We ALWAYS append the ID if we have it as otherwise during reconnect the slaves with the same name will shuffle around which defeats a lot of the login that Jenkins has internally based on slaves having a consistent name - In the event of legacy clients that do not have the ID we will let them connect with their name as long as there is no online slave with that name. This does mean that where there are multiple legacy swarm clients with the same name, only one can be on-line at any moment in time, but that is an improvement on the current where once a shuffle starts, none can stay on-line --- .../java/hudson/plugins/swarm/Client.java | 2 +- .../hudson/plugins/swarm/SwarmClient.java | 112 ++++++++++++++---- .../java/hudson/plugins/swarm/PluginImpl.java | 47 ++++++-- 3 files changed, 127 insertions(+), 34 deletions(-) diff --git a/client/src/main/java/hudson/plugins/swarm/Client.java b/client/src/main/java/hudson/plugins/swarm/Client.java index 8aa053dd..91213cb6 100644 --- a/client/src/main/java/hudson/plugins/swarm/Client.java +++ b/client/src/main/java/hudson/plugins/swarm/Client.java @@ -77,7 +77,7 @@ public void run() throws InterruptedException { swarmClient.verifyThatUrlIsHudson(target); } - System.out.println("Attempting to connect to " + target.url + " " + target.secret); + System.out.println("Attempting to connect to " + target.url + " " + target.secret + " with ID " + swarmClient.getHash()); // create a new swarm slave swarmClient.createSwarmSlave(target); diff --git a/client/src/main/java/hudson/plugins/swarm/SwarmClient.java b/client/src/main/java/hudson/plugins/swarm/SwarmClient.java index 2078a0e7..5b9bd332 100644 --- a/client/src/main/java/hudson/plugins/swarm/SwarmClient.java +++ b/client/src/main/java/hudson/plugins/swarm/SwarmClient.java @@ -2,21 +2,45 @@ import hudson.remoting.Launcher; import hudson.remoting.jnlp.Main; +import org.apache.commons.httpclient.HttpClient; +import org.apache.commons.httpclient.HttpStatus; +import org.apache.commons.httpclient.UsernamePasswordCredentials; +import org.apache.commons.httpclient.auth.AuthScope; +import org.apache.commons.httpclient.methods.GetMethod; +import org.apache.commons.httpclient.methods.PostMethod; +import org.apache.commons.lang.StringUtils; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.Text; +import org.xml.sax.SAXException; +import javax.net.ssl.KeyManager; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; import java.io.ByteArrayInputStream; +import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.UnsupportedEncodingException; +import java.math.BigInteger; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.HttpURLConnection; +import java.net.Inet4Address; import java.net.InetAddress; import java.net.MalformedURLException; +import java.net.NetworkInterface; +import java.net.SocketException; import java.net.SocketTimeoutException; import java.net.URL; import java.net.URLEncoder; import java.security.KeyManagementException; +import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.security.cert.CertificateException; @@ -24,36 +48,24 @@ import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.Random; -import javax.net.ssl.KeyManager; -import javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManager; -import javax.net.ssl.X509TrustManager; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; - -import org.apache.commons.httpclient.HttpClient; -import org.apache.commons.httpclient.HttpStatus; -import org.apache.commons.httpclient.UsernamePasswordCredentials; -import org.apache.commons.httpclient.auth.AuthScope; -import org.apache.commons.httpclient.methods.GetMethod; -import org.apache.commons.httpclient.methods.PostMethod; -import org.apache.commons.lang.StringUtils; -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.Node; -import org.w3c.dom.Text; -import org.xml.sax.SAXException; - public class SwarmClient { private final Options options; + + private final String hash; public SwarmClient(Options options) { this.options = options; + this.hash = hash(options.remoteFsRoot); + } + + public String getHash() { + return hash; } public Candidate discoverFromBroadcast() throws IOException, @@ -292,7 +304,9 @@ protected void createSwarmSlave(Candidate target) throws IOException, Interrupte + param("labels", labelStr) + param("toolLocations", toolLocationsStr) + "&secret=" + target.secret - + param("mode", options.mode.toUpperCase())); + + param("mode", options.mode.toUpperCase()) + + param("hash", hash) + ); post.setDoAuthentication(true); @@ -306,6 +320,15 @@ protected void createSwarmSlave(Candidate target) throws IOException, Interrupte throw new RetryException( "Failed to create a slave on Jenkins CODE: " + responseCode); } + String name = post.getResponseBodyAsString(); + if (name == null) { + return; + } + name = name.trim(); + if (name.isEmpty()) { + return; + } + options.name = name; } private String encode(String value) throws UnsupportedEncodingException { @@ -368,6 +391,53 @@ private static String getChildElementString(Element parent, String tagName) { return null; } + /** + * Returns a hash that should be consistent for any individual swarm client (as long as it has a persistent IP) + * and should be unique to that client. + * + * @param remoteFsRoot the file system root should be part of the hash (to support multiple swarm clients from + * the same machine) + * @return our best effort at a consistent hash + */ + public static String hash(File remoteFsRoot) { + StringBuilder buf = new StringBuilder(); + try { + buf.append(remoteFsRoot.getCanonicalPath()).append('\n'); + } catch (IOException e) { + buf.append(remoteFsRoot.getAbsolutePath()).append('\n'); + } + try { + for (NetworkInterface ni : Collections.list(NetworkInterface.getNetworkInterfaces())) { + for (InetAddress ia : Collections.list(ni.getInetAddresses())) { + if (ia instanceof Inet4Address) { + buf.append(ia.getHostAddress()).append('\n'); + } + } + byte[] hardwareAddress = ni.getHardwareAddress(); + if (hardwareAddress != null) { + buf.append(Arrays.toString(hardwareAddress)); + } + } + } catch (SocketException e) { + // oh well we tried + } + return getDigestOf(buf.toString()).substring(0, 8); + } + + public static String getDigestOf(String text) { + try { + return toHexString(MessageDigest.getInstance("MD5").digest(text.getBytes("UTF-8"))); + } catch (NoSuchAlgorithmException e) { + throw new IllegalStateException("MD5 not installed", e); // impossible according to JLS + } catch (UnsupportedEncodingException e) { + throw new IllegalStateException("UTF-8 not supported", e); // impossible according to JLS + } + } + + public static String toHexString(byte[] bytes) { + return String.format("%0" + (bytes.length * 2) + "x", new BigInteger(1, bytes)); + } + private static class DefaultTrustManager implements X509TrustManager { public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException { diff --git a/plugin/src/main/java/hudson/plugins/swarm/PluginImpl.java b/plugin/src/main/java/hudson/plugins/swarm/PluginImpl.java index 2f8ab573..25201c7c 100644 --- a/plugin/src/main/java/hudson/plugins/swarm/PluginImpl.java +++ b/plugin/src/main/java/hudson/plugins/swarm/PluginImpl.java @@ -1,6 +1,6 @@ package hudson.plugins.swarm; -import static javax.servlet.http.HttpServletResponse.*; +import com.google.common.collect.Lists; import hudson.Plugin; import hudson.Util; import hudson.model.Descriptor.FormException; @@ -10,19 +10,21 @@ import hudson.tools.ToolInstallation; import hudson.tools.ToolLocationNodeProperty; import hudson.tools.ToolLocationNodeProperty.ToolLocation; - -import java.io.IOException; -import java.io.Writer; -import java.util.List; - import jenkins.model.Jenkins; - import org.apache.commons.lang.StringUtils; import org.kohsuke.stapler.QueryParameter; import org.kohsuke.stapler.StaplerRequest; import org.kohsuke.stapler.StaplerResponse; -import com.google.common.collect.Lists; +import javax.servlet.ServletOutputStream; +import java.io.IOException; +import java.io.Writer; +import java.util.List; + +import static javax.servlet.http.HttpServletResponse.SC_CONFLICT; +import static javax.servlet.http.HttpServletResponse.SC_EXPECTATION_FAILED; +import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN; +import static javax.servlet.http.HttpServletResponse.SC_SERVICE_UNAVAILABLE; /** * Exposes an entry point to add a new swarm slave. @@ -36,7 +38,7 @@ public class PluginImpl extends Plugin { */ public void doCreateSlave(StaplerRequest req, StaplerResponse rsp, @QueryParameter String name, @QueryParameter String description, @QueryParameter int executors, @QueryParameter String remoteFsRoot, @QueryParameter String labels, @QueryParameter String secret, @QueryParameter Node.Mode mode, - @QueryParameter String toolLocations) throws IOException { + @QueryParameter String toolLocations, @QueryParameter(fixEmpty = true) String hash) throws IOException { if (!getSwarmSecret().equals(secret)) { rsp.setStatus(SC_FORBIDDEN); @@ -54,9 +56,24 @@ public void doCreateSlave(StaplerRequest req, StaplerResponse rsp, @QueryParamet nodeProperties = Lists.newArrayList(new ToolLocationNodeProperty(parsedToolLocations)); } - // try to make the name unique. Swarm clients are often replicated VMs, and they may have the same name. - if (jenkins.getNode(name) != null) { - name = name + '-' + req.getRemoteAddr(); + if (hash == null && jenkins.getNode(name) != null) { + // this is a legacy client, they won't be able to pick up the new name, so throw them away + // perhaps they can find another master to connect to + rsp.setStatus(SC_CONFLICT); + return; + } + if (hash != null) { + // try to make the name unique. Swarm clients are often replicated VMs, and they may have the same name. + name = name + '-' + hash; + } + // check for existing connections + { + Node n = jenkins.getNode(name); + if (n != null && n.toComputer().isOnline()) { + // this is an existing connection, we'll only cause issues if we trample over an online connection + rsp.setStatus(SC_CONFLICT); + return; + } } SwarmSlave slave = new SwarmSlave(name, "Swarm slave from " + req.getRemoteHost() + " : " + description, @@ -70,6 +87,12 @@ public void doCreateSlave(StaplerRequest req, StaplerResponse rsp, @QueryParamet } jenkins.addNode(slave); } + rsp.setContentType("text/plain; UTF-8"); + byte[] response = name.getBytes("UTF-8"); + rsp.setContentLength(response.length); + ServletOutputStream outputStream = rsp.getOutputStream(); + outputStream.write(response); + outputStream.flush(); } catch (FormException e) { e.printStackTrace(); } From 087165877f7d1e9cb88179d87b9321c8cb811427 Mon Sep 17 00:00:00 2001 From: Stephen Connolly Date: Fri, 24 Apr 2015 11:24:05 +0100 Subject: [PATCH 2/9] Actually need to keep `options.name` as immutable or else it will keep growing --- .../src/main/java/hudson/plugins/swarm/SwarmClient.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/client/src/main/java/hudson/plugins/swarm/SwarmClient.java b/client/src/main/java/hudson/plugins/swarm/SwarmClient.java index 5b9bd332..48bf5729 100644 --- a/client/src/main/java/hudson/plugins/swarm/SwarmClient.java +++ b/client/src/main/java/hudson/plugins/swarm/SwarmClient.java @@ -58,10 +58,13 @@ public class SwarmClient { private final Options options; private final String hash; + + private String name; public SwarmClient(Options options) { this.options = options; this.hash = hash(options.remoteFsRoot); + this.name = options.name; } public String getHash() { @@ -203,7 +206,7 @@ protected void connect(Candidate target) throws InterruptedException { try { Launcher launcher = new Launcher(); - launcher.slaveJnlpURL = new URL(target.url + "computer/" + options.name + launcher.slaveJnlpURL = new URL(target.url + "computer/" + name + "/slave-agent.jnlp"); if (options.username != null && options.password != null) { @@ -322,13 +325,14 @@ protected void createSwarmSlave(Candidate target) throws IOException, Interrupte } String name = post.getResponseBodyAsString(); if (name == null) { + this.name = options.name; return; } name = name.trim(); if (name.isEmpty()) { return; } - options.name = name; + this.name = name; } private String encode(String value) throws UnsupportedEncodingException { From 2132772e9764bd60616505105dbdb62d09299716 Mon Sep 17 00:00:00 2001 From: Stephen Connolly Date: Fri, 24 Apr 2015 16:24:41 +0100 Subject: [PATCH 3/9] extra null safety --- .../main/java/hudson/plugins/swarm/PluginImpl.java | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/plugin/src/main/java/hudson/plugins/swarm/PluginImpl.java b/plugin/src/main/java/hudson/plugins/swarm/PluginImpl.java index 25201c7c..390ad0b3 100644 --- a/plugin/src/main/java/hudson/plugins/swarm/PluginImpl.java +++ b/plugin/src/main/java/hudson/plugins/swarm/PluginImpl.java @@ -3,6 +3,7 @@ import com.google.common.collect.Lists; import hudson.Plugin; import hudson.Util; +import hudson.model.Computer; import hudson.model.Descriptor.FormException; import hudson.model.Node; import hudson.slaves.SlaveComputer; @@ -69,10 +70,14 @@ public void doCreateSlave(StaplerRequest req, StaplerResponse rsp, @QueryParamet // check for existing connections { Node n = jenkins.getNode(name); - if (n != null && n.toComputer().isOnline()) { - // this is an existing connection, we'll only cause issues if we trample over an online connection - rsp.setStatus(SC_CONFLICT); - return; + if (n != null) { + Computer c = n.toComputer(); + if (c != null && c.isOnline()) { + // this is an existing connection, we'll only cause issues if we trample over an online connection + + rsp.setStatus(SC_CONFLICT); + return; + } } } From 7984ef2612f4a0259677ab406142cb6cd9dee4c9 Mon Sep 17 00:00:00 2001 From: Stephen Connolly Date: Mon, 27 Apr 2015 12:36:39 +0100 Subject: [PATCH 4/9] Add support for IPv6 addresses --- client/src/main/java/hudson/plugins/swarm/SwarmClient.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/client/src/main/java/hudson/plugins/swarm/SwarmClient.java b/client/src/main/java/hudson/plugins/swarm/SwarmClient.java index 48bf5729..f05519a8 100644 --- a/client/src/main/java/hudson/plugins/swarm/SwarmClient.java +++ b/client/src/main/java/hudson/plugins/swarm/SwarmClient.java @@ -32,6 +32,7 @@ import java.net.DatagramSocket; import java.net.HttpURLConnection; import java.net.Inet4Address; +import java.net.Inet6Address; import java.net.InetAddress; import java.net.MalformedURLException; import java.net.NetworkInterface; @@ -415,6 +416,8 @@ public static String hash(File remoteFsRoot) { for (InetAddress ia : Collections.list(ni.getInetAddresses())) { if (ia instanceof Inet4Address) { buf.append(ia.getHostAddress()).append('\n'); + } else if (ia instanceof Inet6Address) { + buf.append(ia.getHostAddress()).append('\n'); } } byte[] hardwareAddress = ni.getHardwareAddress(); From 640637c30cf0c84bc40604f25aa4fd12b44e84d0 Mon Sep 17 00:00:00 2001 From: Stephen Connolly Date: Mon, 27 Apr 2015 12:49:46 +0100 Subject: [PATCH 5/9] Return key-value properties to support future extensions --- .../java/hudson/plugins/swarm/SwarmClient.java | 13 ++++++++++++- .../main/java/hudson/plugins/swarm/PluginImpl.java | 14 +++++++++++++- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/client/src/main/java/hudson/plugins/swarm/SwarmClient.java b/client/src/main/java/hudson/plugins/swarm/SwarmClient.java index f05519a8..af16eadd 100644 --- a/client/src/main/java/hudson/plugins/swarm/SwarmClient.java +++ b/client/src/main/java/hudson/plugins/swarm/SwarmClient.java @@ -52,6 +52,7 @@ import java.util.Collections; import java.util.LinkedList; import java.util.List; +import java.util.Properties; import java.util.Random; public class SwarmClient { @@ -324,13 +325,23 @@ protected void createSwarmSlave(Candidate target) throws IOException, Interrupte throw new RetryException( "Failed to create a slave on Jenkins CODE: " + responseCode); } - String name = post.getResponseBodyAsString(); + Properties props = new Properties(); + InputStream stream = post.getResponseBodyAsStream(); + if (stream != null) { + try { + props.load(stream); + } finally { + stream.close(); + } + } + String name = props.getProperty("name"); if (name == null) { this.name = options.name; return; } name = name.trim(); if (name.isEmpty()) { + this.name = options.name; return; } this.name = name; diff --git a/plugin/src/main/java/hudson/plugins/swarm/PluginImpl.java b/plugin/src/main/java/hudson/plugins/swarm/PluginImpl.java index 390ad0b3..5148e588 100644 --- a/plugin/src/main/java/hudson/plugins/swarm/PluginImpl.java +++ b/plugin/src/main/java/hudson/plugins/swarm/PluginImpl.java @@ -18,9 +18,11 @@ import org.kohsuke.stapler.StaplerResponse; import javax.servlet.ServletOutputStream; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.Writer; import java.util.List; +import java.util.Properties; import static javax.servlet.http.HttpServletResponse.SC_CONFLICT; import static javax.servlet.http.HttpServletResponse.SC_EXPECTATION_FAILED; @@ -61,6 +63,10 @@ public void doCreateSlave(StaplerRequest req, StaplerResponse rsp, @QueryParamet // this is a legacy client, they won't be able to pick up the new name, so throw them away // perhaps they can find another master to connect to rsp.setStatus(SC_CONFLICT); + rsp.setContentType("text/plain; UTF-8"); + rsp.getWriter().printf( + "A slave called '%s' already exists and legacy clients do not support name disambiguation%n", + name); return; } if (hash != null) { @@ -76,6 +82,8 @@ public void doCreateSlave(StaplerRequest req, StaplerResponse rsp, @QueryParamet // this is an existing connection, we'll only cause issues if we trample over an online connection rsp.setStatus(SC_CONFLICT); + rsp.setContentType("text/plain; UTF-8"); + rsp.getWriter().printf("A slave called '%s' is already created and on-line%n", name); return; } } @@ -93,7 +101,11 @@ public void doCreateSlave(StaplerRequest req, StaplerResponse rsp, @QueryParamet jenkins.addNode(slave); } rsp.setContentType("text/plain; UTF-8"); - byte[] response = name.getBytes("UTF-8"); + Properties props = new Properties(); + props.put("name", name); + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + props.store(bos, ""); + byte[] response = bos.toByteArray(); rsp.setContentLength(response.length); ServletOutputStream outputStream = rsp.getOutputStream(); outputStream.write(response); From 78c8650367a4755c7bf973a18ef1f0a5708a6700 Mon Sep 17 00:00:00 2001 From: Stephen Connolly Date: Mon, 27 Apr 2015 12:52:00 +0100 Subject: [PATCH 6/9] Explain what's going wrong --- client/src/main/java/hudson/plugins/swarm/SwarmClient.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/main/java/hudson/plugins/swarm/SwarmClient.java b/client/src/main/java/hudson/plugins/swarm/SwarmClient.java index af16eadd..10d5d45e 100644 --- a/client/src/main/java/hudson/plugins/swarm/SwarmClient.java +++ b/client/src/main/java/hudson/plugins/swarm/SwarmClient.java @@ -322,8 +322,8 @@ protected void createSwarmSlave(Candidate target) throws IOException, Interrupte int responseCode = client.executeMethod(post); if (responseCode != 200) { - throw new RetryException( - "Failed to create a slave on Jenkins CODE: " + responseCode); + throw new RetryException(String.format("Failed to create a slave on Jenkins CODE: %s%n%s",responseCode, + post.getResponseBodyAsString()) ); } Properties props = new Properties(); InputStream stream = post.getResponseBodyAsStream(); From b7e147095aec9b1560fad4ae817da48d6045d430 Mon Sep 17 00:00:00 2001 From: Stephen Connolly Date: Mon, 27 Apr 2015 12:57:58 +0100 Subject: [PATCH 7/9] Use commons-codec DigestUtils --- .../java/hudson/plugins/swarm/SwarmClient.java | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/client/src/main/java/hudson/plugins/swarm/SwarmClient.java b/client/src/main/java/hudson/plugins/swarm/SwarmClient.java index 10d5d45e..b3a68101 100644 --- a/client/src/main/java/hudson/plugins/swarm/SwarmClient.java +++ b/client/src/main/java/hudson/plugins/swarm/SwarmClient.java @@ -2,6 +2,7 @@ import hudson.remoting.Launcher; import hudson.remoting.jnlp.Main; +import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.HttpStatus; import org.apache.commons.httpclient.UsernamePasswordCredentials; @@ -439,23 +440,9 @@ public static String hash(File remoteFsRoot) { } catch (SocketException e) { // oh well we tried } - return getDigestOf(buf.toString()).substring(0, 8); + return DigestUtils.md5Hex(buf.toString()).substring(0, 8); } - public static String getDigestOf(String text) { - try { - return toHexString(MessageDigest.getInstance("MD5").digest(text.getBytes("UTF-8"))); - } catch (NoSuchAlgorithmException e) { - throw new IllegalStateException("MD5 not installed", e); // impossible according to JLS - } catch (UnsupportedEncodingException e) { - throw new IllegalStateException("UTF-8 not supported", e); // impossible according to JLS - } - } - - public static String toHexString(byte[] bytes) { - return String.format("%0" + (bytes.length * 2) + "x", new BigInteger(1, bytes)); - } - private static class DefaultTrustManager implements X509TrustManager { public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException { From ff5dd5346cdf40f75ee4a074e81fd6ad45fd0b52 Mon Sep 17 00:00:00 2001 From: Stephen Connolly Date: Mon, 27 Apr 2015 12:58:33 +0100 Subject: [PATCH 8/9] Specify locale --- client/src/main/java/hudson/plugins/swarm/SwarmClient.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/src/main/java/hudson/plugins/swarm/SwarmClient.java b/client/src/main/java/hudson/plugins/swarm/SwarmClient.java index b3a68101..1f8756fc 100644 --- a/client/src/main/java/hudson/plugins/swarm/SwarmClient.java +++ b/client/src/main/java/hudson/plugins/swarm/SwarmClient.java @@ -53,6 +53,7 @@ import java.util.Collections; import java.util.LinkedList; import java.util.List; +import java.util.Locale; import java.util.Properties; import java.util.Random; @@ -310,7 +311,7 @@ protected void createSwarmSlave(Candidate target) throws IOException, Interrupte + param("labels", labelStr) + param("toolLocations", toolLocationsStr) + "&secret=" + target.secret - + param("mode", options.mode.toUpperCase()) + + param("mode", options.mode.toUpperCase(Locale.ENGLISH)) + param("hash", hash) ); From 96a1a848be4f4087a3357ec31fbc54e3e3c38732 Mon Sep 17 00:00:00 2001 From: Stephen Connolly Date: Mon, 27 Apr 2015 13:51:10 +0100 Subject: [PATCH 9/9] oops missed the charset fix --- plugin/src/main/java/hudson/plugins/swarm/PluginImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/src/main/java/hudson/plugins/swarm/PluginImpl.java b/plugin/src/main/java/hudson/plugins/swarm/PluginImpl.java index 5148e588..92fffa60 100644 --- a/plugin/src/main/java/hudson/plugins/swarm/PluginImpl.java +++ b/plugin/src/main/java/hudson/plugins/swarm/PluginImpl.java @@ -100,7 +100,7 @@ public void doCreateSlave(StaplerRequest req, StaplerResponse rsp, @QueryParamet } jenkins.addNode(slave); } - rsp.setContentType("text/plain; UTF-8"); + rsp.setContentType("text/plain; charset=iso-8859-1"); Properties props = new Properties(); props.put("name", name); ByteArrayOutputStream bos = new ByteArrayOutputStream();