Skip to content

Commit

Permalink
Merge pull request #17 from mrdfuse/JENKINS-7543
Browse files Browse the repository at this point in the history
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 8f666f1
Show file tree
Hide file tree
Showing 6 changed files with 96 additions and 50 deletions.
7 changes: 6 additions & 1 deletion client/pom.xml
Expand Up @@ -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>
11 changes: 5 additions & 6 deletions client/src/main/java/hudson/plugins/swarm/Client.java
@@ -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/>
Expand Down Expand Up @@ -105,7 +106,5 @@ public void run() throws InterruptedException {
System.out.println("Retrying in 10 seconds");
Thread.sleep(10 * 1000);
}

}

}
3 changes: 3 additions & 0 deletions client/src/main/java/hudson/plugins/swarm/Options.java
Expand Up @@ -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;
Expand Down
61 changes: 28 additions & 33 deletions client/src/main/java/hudson/plugins/swarm/SwarmClient.java
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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);
Expand Down Expand Up @@ -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);
Expand All @@ -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);

}
}

Expand Down
59 changes: 51 additions & 8 deletions plugin/src/main/java/hudson/plugins/swarm/PluginImpl.java
@@ -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.
Expand All @@ -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);
Expand All @@ -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) {
Expand All @@ -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();
}
Expand Down
5 changes: 3 additions & 2 deletions plugin/src/main/java/hudson/plugins/swarm/SwarmSlave.java
Expand Up @@ -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
Expand Down

0 comments on commit 8f666f1

Please sign in to comment.