Skip to content
Permalink
Browse files
Merge pull request #17 from mrdfuse/JENKINS-7543
Set tool locations from Swarm plugin CLI

Swarm plugin CLI agent cannot specify tools (JDK, Maven, etc) locations on the slave host.
Locations can differ among slaves - especially if slave OSes are different.
Automatic tool installation does not suit for many cases - shared slaves, custom tool variations.

Closes https://issues.jenkins-ci.org/browse/JENKINS-7543
  • Loading branch information
mindjiver committed Oct 2, 2014
2 parents 18a88f0 + 1c7d9d3 commit 8f666f15b20dd640149b639871ee88d3b7148fa2
Showing 6 changed files with 96 additions and 50 deletions.
@@ -61,10 +61,15 @@
<artifactId>commons-httpclient</artifactId>
<version>3.1</version>
</dependency>
<dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.9</version>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.4</version>
</dependency>
</dependencies>
</project>
@@ -1,12 +1,13 @@
package hudson.plugins.swarm;

import org.kohsuke.args4j.CmdLineException;
import org.kohsuke.args4j.CmdLineParser;

import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.net.InetAddress;

import javax.xml.parsers.ParserConfigurationException;

import org.kohsuke.args4j.CmdLineException;
import org.kohsuke.args4j.CmdLineParser;

/**
* Swarm client.
* <p/>
@@ -105,7 +106,5 @@ public void run() throws InterruptedException {
System.out.println("Retrying in 10 seconds");
Thread.sleep(10 * 1000);
}

}

}
@@ -49,6 +49,9 @@ public class Options {
handler = ModeOptionHandler.class
)
public String mode = ModeOptionHandler.NORMAL;

@Option(name = "-toolLocations", usage = "Whitespace-separated list of tool locations to be defined on this slave. A tool location is specified as 'toolName:location'")
public List<String> toolLocations = new ArrayList<String>();

@Option(name = "-username", usage = "The Jenkins username for authentication")
public String username;
@@ -2,24 +2,7 @@

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.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.IOException;
import java.io.InputStream;
@@ -45,9 +28,26 @@
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;
@@ -57,7 +57,7 @@ public SwarmClient(Options options) {
}

public Candidate discoverFromBroadcast() throws IOException,
InterruptedException, RetryException, ParserConfigurationException {
RetryException, ParserConfigurationException {

DatagramSocket socket = new DatagramSocket();
socket.setBroadcast(true);
@@ -273,21 +273,17 @@ protected void createSwarmSlave(Candidate target) throws IOException, Interrupte
// ie. it does not return a 401 (Unauthorized)
// but immediately a 403 (Forbidden)

StringBuilder labelStr = new StringBuilder();
for (String l : options.labels) {
if (labelStr.length() > 0) {
labelStr.append(' ');
}
labelStr.append(l);
}
String labelStr = StringUtils.join(options.labels, ' ');
String toolLocationsStr = StringUtils.join(options.toolLocations, ' ');

PostMethod post = new PostMethod(target.url
+ "/plugin/swarm/createSlave?name=" + options.name + "&executors="
+ options.executors
+ "/plugin/swarm/createSlave?name=" + options.name
+ "&executors=" + options.executors
+ param("remoteFsRoot", options.remoteFsRoot.getAbsolutePath())
+ param("description", options.description)
+ param("labels", labelStr.toString()) + "&secret="
+ target.secret
+ param("labels", labelStr)
+ param("toolLocations", toolLocationsStr)
+ "&secret=" + target.secret
+ param("mode", options.mode.toUpperCase()));

post.setDoAuthentication(true);
@@ -301,7 +297,6 @@ protected void createSwarmSlave(Candidate target) throws IOException, Interrupte
if (responseCode != 200) {
throw new RetryException(
"Failed to create a slave on Jenkins CODE: " + responseCode);

}
}

@@ -1,19 +1,28 @@
package hudson.plugins.swarm;

import static javax.servlet.http.HttpServletResponse.*;
import hudson.Plugin;
import hudson.Util;
import hudson.model.Descriptor.FormException;
import hudson.model.Node;
import hudson.slaves.NodeProperty;
import hudson.slaves.SlaveComputer;
import hudson.tools.ToolDescriptor;
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.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;

import java.io.IOException;
import java.io.Writer;

import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN;
import com.google.common.collect.Lists;

/**
* Exposes an entry point to add a new swarm slave.
@@ -26,7 +35,8 @@ public class PluginImpl extends Plugin {
* Adds a new swarm slave.
*/
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) throws IOException, FormException {
@QueryParameter String remoteFsRoot, @QueryParameter String labels, @QueryParameter String secret, @QueryParameter Node.Mode mode,
@QueryParameter String toolLocations) throws IOException {

if (!getSwarmSecret().equals(secret)) {
rsp.setStatus(SC_FORBIDDEN);
@@ -38,15 +48,17 @@ public void doCreateSlave(StaplerRequest req, StaplerResponse rsp, @QueryParamet

jenkins.checkPermission(SlaveComputer.CREATE);

// try to make the name unique. Swarm clients are often repliated VMs, and they may have the same name.
ToolLocationNodeProperty toolLocationNodeProperty = new ToolLocationNodeProperty(parseToolLocations(toolLocations));

// 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();
}

SwarmSlave slave = new SwarmSlave(name, "Swarm slave from " + req.getRemoteHost() + " : " + description,
remoteFsRoot, String.valueOf(executors), mode, "swarm " + Util.fixNull(labels));
remoteFsRoot, String.valueOf(executors), mode, "swarm " + Util.fixNull(labels), Lists.newArrayList(toolLocationNodeProperty));

// if this still results in a dupliate, so be it
// if this still results in a duplicate, so be it
synchronized (jenkins) {
Node n = jenkins.getNode(name);
if (n != null) {
@@ -59,6 +71,37 @@ public void doCreateSlave(StaplerRequest req, StaplerResponse rsp, @QueryParamet
}
}

private List<ToolLocation> parseToolLocations(String toolLocations) {
List<ToolLocationNodeProperty.ToolLocation> result = Lists.newArrayList();

String[] toolLocsArray = toolLocations.split(" ");
for (String toolLocKeyValue : toolLocsArray) {
boolean found = false;
String[] toolLoc = toolLocKeyValue.split(":");

for (ToolDescriptor<?> desc : ToolInstallation.all()) {
for (ToolInstallation inst : desc.getInstallations()) {
if (inst.getName().equals(toolLoc[0])) {
found = true;

String key = inst.getClass().getCanonicalName().toString() + "$DescriptorImpl@" + inst.getName();
String location = toolLoc[1];

ToolLocationNodeProperty.ToolLocation toolLocation = new ToolLocationNodeProperty.ToolLocation(key, location);
result.add(toolLocation);
}
}
}

// don't fail silently, inform the user what tool is missing
if (!found) {
throw new RuntimeException("No tool '" + toolLoc[0] + "' is defined on Jenkins.");
}
}

return result;
}

static String getSwarmSecret() {
return UDPFragmentImpl.all().get(UDPFragmentImpl.class).secret.toString();
}
@@ -30,9 +30,10 @@
*/
public class SwarmSlave extends Slave implements EphemeralNode {

public SwarmSlave(String name, String nodeDescription, String remoteFS, String numExecutors, Mode mode, String label) throws IOException, FormException {
public SwarmSlave(String name, String nodeDescription, String remoteFS, String numExecutors, Mode mode, String label,
List<? extends NodeProperty<?>> nodeProperties) throws IOException, FormException {
super(name, nodeDescription, remoteFS, numExecutors, mode, label,
SELF_CLEANUP_LAUNCHER, RetentionStrategy.NOOP, Collections.<NodeProperty<?>>emptyList());
SELF_CLEANUP_LAUNCHER, RetentionStrategy.NOOP, nodeProperties);
}

@DataBoundConstructor

0 comments on commit 8f666f1

Please sign in to comment.