Author: Yu, Qingguo
Background
Jenkins is an open source continuous integration tool written in Java, known as CI server. Plugins extend Jenkins use, which provides all kind of functionalities such as downloading code from version control system, building code with different kinds of tools like Maven, Ant, reporting testing results, and so on.
Jenkins has two working mode: one is only one instance, on which all jobs run on ; the other is that there may be one master node and several slave nodes in one Jenkins system, named distributed Jenkins, and the jobs may run on either master node or slave nodes. When a Jenkins plugin being developed, one potential issue is that the plugin can only run on Jenkins master node even the job is configured running on slave nodes. For beginners on Jenkins plugin development, it is very easy to fall into such trap. If your Jenkins system will be migrated to Apache Mesos platform, nearly all of the CI jobs will be run on slave node. That would be an issue if Jenkins plugin cannot handle that.
During developing a plugin to collect code coverage of integration testing, we also faced that issue. The plain code to download and build source cannot work well when CI jobs are configured running on slave nodes. In addition, there is no handy complete example available on the internet which can be referenced directly. It took us quite a long time to do the research and eventually overcome such issue. Here a complete example will be provided on developing a Jenkins plugin working well in a distributed Jenkins.
Overview
This blog will demonstrate how to build a Jenkins working in distributed Jenkins from scratch, and will focus on details how to leverage interfaces of Jenkins to access remote workspace. Also, please note the methods we introduce in this blog will make plugin work on both master node and slave node, not only on slave node, which is transparent to the user without any more code change, depending on which node where the CI job is configured to run.
There are four sections in this blog:
1. Go through the basic steps to create a Jenkins plugin and set up development environment;
2. Introduce two ways to leverage Jenkins’ interface to make your Jenkins plugin run on slave nodes;
3. Introduce how to leverage the functionalities of existing plugins in your plugin to handle remote execution cases;
4. A short introduction on what the theory is to support plugin work in a distributed Jenkins.
Create a sample Jenkins plugin
1. First of all, you need to install JDK, Maven into your development server and configure related environment variable such as JDK_HOME, MAVEN_HOME, and so on.
2. If you already have a seetings.xml file for you maven, please add two more repositories of Jenkins into this setting.xml, one in release repositories, the other in plugin repositories. Or you may create settings.xml as shown in reference [3]
<repository>
<id>repo.jenkins-ci.org</id>
<url>http://repo.jenkins-ci.org/public/</url>
</repository>
<pluginRepository>
<id>repo.jenkins-ci.org</id>
<url>http://repo.jenkins-ci.org/public/</url>
</pluginRepository>
3. Create folder for your Jenkins plugin project. Here I use name “pluginsample” for the folder;
4. Create the plugin using Jenkins Maven archetype by run the command under the folder in a command line console. Or you may create a maven command in Eclipse to do the same thing as shown below. Here we use “com.ebay.learn.jenkins.plugins” as groupId, “sample” as artifactId. After the maven command finishes, a project named “sample” created under the folder “pluginsample”
Figure 1
5. Import the project into Eclipse. Or you may import the project into IntelliJ or Netbeans as you like. When you import project, please import the project through “Import Maven Projects” to make eclipse create the project metadata
6. After importing is done, build and run this plugin in a Jenkins by run maven command:
7. Enter the url like http://localhost:9090/jenkins/ and open the main page of Jenkins;
8. Add a CI job named “TestSample” and add the build step of the new plugin we just created, which is named “Say hello world”. And enter “Jenkins” in the name field.
9. Click “build now” to trigger the build of this CI job. The build finishes successfully, and from console you may check out the words like “hello, Jenkins”.
Next, we will add code to make this plugin access workspace on the remote slave node.
Method 1: leverage launcher to make logic run on slave
1. Open class “HelloWorldBuilder” in the sample project;
2. Add static class implementing interface Callable
//The 1st method
private static class LauncherCallable implements Callable<String, IOException>{
private BuildListener listener;
public LauncherCallable(BuildListener listener){
this.listener = listener;
}
private static final long serialVersionUID = 1L;
public String call() throws IOException {
final RemoteOutputStream ros = newRemoteOutputStream(listener.getLogger());
// This code will run on the slave node
String hostname = InetAddress.getLocalHost().getHostName();
hostname = hostname + "\n";
ros.write(hostname.getBytes());
return hostname;
}
};
3. Add following code to method “perform”:
// Get a "channel" to the build machine and run the task there
try {
//The 1st method
String hostname = launcher.getChannel().call(new LauncherCallable(listener));
} catch (Exception e) {
RuntimeException re = new RuntimeException();
re.initCause(e);
throw re;
}
4. Build and run the plugin on a local Jenkins using the same command as above when creating Jenkins plugin ;
5. Kick off the CI job “TestSample” we created just now. You will find the hostname of current server will be got successfully and printed into the console of the log of the CI job.
6. Compile and build a hpi file of this plugin;
7. Install it onto a distributed Jenkins. Please refer to reference[5] on how to configure a distributed Jenkins.
a. Open link of advance tab of plugin manager. ${master} refer to the master node in your distributed Jenkins. http://${master}/pluginManager/advanced
b. In “Upload Plugin” section, click “Browser” button to locate hpi file built just now. The hpi file is always located under target folder like “pluginsample\sample\target\sample.hpi”.
c. Click “Upload” button to install this hpi file to Jenkins.
d. Restart Jenkins by invoking link http://${master}/restart
8. Create a CI “TestSample” and restrict it running on Slave node. Check “Restrict where this project can be run” and enter the name of slave node
9. Kick off this CI job. After job execution is finished, the hostname of slave node instead of master node will be found in the console log.
Method 2: leverage FilePath to make logic run on slave
1. Open class “HelloWorldBuilder” in the sample project;
2. Add static class implementing interface Callable
//The 2nd method
private static class MyFileCallable implements FileCallable<String> {
private BuildListener listener;
public MyFileCallable(BuildListener listener){
this.listener = listener;
}
private static final long serialVersionUID = 1L;
public String invoke(File file, VirtualChannel channel) throws IOException {
final RemoteOutputStream ros = newRemoteOutputStream(listener.getLogger());
// This code will run on the build slave
String hostname = InetAddress.getLocalHost().getHostName();
hostname = hostname + "\n";
ros.write(hostname.getBytes());
return hostname;
}
};
3. Add following code to method “perform”:
FileCallable<String> fTask = new MyFileCallable(listener);
// Get a "channel" to the build machine and run the task there
try {
build.getWorkspace().act(fTask);
} catch (Exception e) {
RuntimeException re = new RuntimeException();
re.initCause(e);
throw re;
}
4. Build and run the plugin on a local Jenkins using the same command as above when creating Jenkins plugin ;
5. Kick off the CI job “TestSample” we created just now. You will find the hostname will be got successfully and printed into the console of the log of the CI job.
6. Compile and build a hpi file of this plugin;
7. Install it onto a distributed Jenkins as I did in “Method 1” and restart CI server.
8. Kick off CI job “TestSample” on your distributed Jenkins.
9. After job execution is finished, two of the hostnames of slave node instead of master node will be found in the console log, one is printed by Method1, the other is printed by Method 2.
How to do the logging when plugin runs on slave
The key point for logging remote operation is creating a serializable output stream. Fortunately Jenkins already provides one output stream class named RemoteOutputStream to support that. The common practice is wrapping up current logger with RemoteOutputStream like:
final RemoteOutputStream ros = newRemoteOutputStream(listener.getLogger());
Then print any log as you want like:
ros.write(hostname.getBytes());
Leverage existing Jenkins plugins
When we need to download code, build code in our own plugin, it is naturally to think about to leverage some existing library like JGit, invoking maven in a ProcessBuilder per our existing experience on Java coding. However, the mechanisms mentioned cannot handle the scenario when your plugin runs in a distributed Jenkins.
For example, if you use JGit library to download the code in your plugin, when you make the CI job run on slave node, you will find that the source code is still downloaded to the master. If you want to download code to the workspace on the slave node, one possible way is to leverage the methods introduced above, and put code of Jgit invocation into them. Actually another simpler way is to leverage the existing Git Client Jenkins plugin in your project, which has already supported to download code to the remote workspace if you need. Here are the steps how to integrate Git Client Jenkins plugin into your plugin:
1. Add dependency of Git Client to the pom.xml of your plugin project. Generally, you need to use latest version of the dependency.
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>Git Client</artifactId>
<version>1.10.0</version>
</dependency>
2. Add code into the method to download the code;
//code demo for git clone
try {
GitClient git = Git.with(listener, new EnvVars(EnvVars.masterEnvVars))
.in(build.getWorkspace()).getClient();
git.clone("https://github.scm.corp.ebay.com/RaptorCOE/WebResTest2.git",
"origin", false, null);
} catch (GitException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
3. Build and run the plugin on a local Jenkins;
4. Kick off the CI job “TestSample” we created just now. You will find the code will be downloaded successfully into the workspace.
5. Install Git Client plugin to your master node of CI server. Or the sample plugin’s installation will fail due to dependency missing.
6. Compile and build a hpi file of this plugin, and install it onto a distributed Jenkins as mentioned above.
7. Kick off this CI job “TestSample”. After job execution is finished, check the workspace folder on slave node. It will be found that the code is downloaded successfully.
The Theory behind the Scene
Jenkins uses a mechanism similar to distributed agents to perform distributed computing. That is, the thread that's running on the master can send serialized objects to remote machines, then get the result back when that the object finishes computation. The key point is the object serialization and deserialization when sending objects to remote slave nodes and getting the result back. If you want to do further reading please read reference [4].
Summary
In this blog, a complete example is provided about how to develop a Jenkins plugin to work in a distributed Jenkins. Please check out the complete source code from this link of git repo: https://github.com/qingguo-yu/jenkinspluginsample
Also you may check out many code examples on this topic on reference [6]
References
1. The official web site for Jenkins: http://jenkins-ci.org/
2. The description about Jenkins on Wikipedia:http://en.wikipedia.org/wiki/Jenkins_%28software%29
3. https://wiki.jenkins-ci.org/display/JENKINS/Plugin+tutorial
4. https://wiki.jenkins-ci.org/display/JENKINS/Making+your+plugin+behave+in+distributed+Jenkins
5. https://wiki.jenkins-ci.org/display/JENKINS/Step+by+step+guide+to+set+up+master+and+slave+machines
6. http://www.programcreek.com/java-api-examples/index.php?api=hudson.remoting.Callable
7. http://stackoverflow.com/questions/17727054/cannot-access-file-on-jenkins-slave
* 本文版权和/或知识产权归eBay Inc所有。如需引述,请和联系我们DL-eBay-CCOE-Tech@ebay.com。本文旨在进行学术探讨交流,如您认为某些信息侵犯您的合法权益,请联系我们DL-eBay-CCOE-Tech@ebay.com,并在通知中列明国家法律法规要求的必要信息,我们在收到您的通知后将根据国家法律法规尽快采取措施。