diff --git a/.project b/.project
index bdc7aa3..65e2d1d 100644
--- a/.project
+++ b/.project
@@ -5,6 +5,11 @@
+
+ org.python.pydev.PyDevBuilder
+
+
+
org.eclipse.jdt.core.javabuilder
@@ -19,5 +24,6 @@
org.eclipse.m2e.core.maven2Nature
org.eclipse.jdt.core.javanature
+ org.python.pydev.pythonNature
diff --git a/.pydevproject b/.pydevproject
new file mode 100644
index 0000000..a9cca03
--- /dev/null
+++ b/.pydevproject
@@ -0,0 +1,7 @@
+
+
+
+
+Default
+python 2.7
+
diff --git a/README.txt b/README.txt
index 8bfdb28..f023371 100644
--- a/README.txt
+++ b/README.txt
@@ -1,3 +1,61 @@
-This plugin attempts to integrate Gearman with Jenkins.
+---------------------------------------------------------------------
+Copyright 2013 Hewlett-Packard Development Company, L.P.
-http://wiki.jenkins-ci.org/display/JENKINS/Gearman+Plugin
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+---------------------------------------------------------------------
+
+Jenkins does not support multiple masters. You can setup multiple Jenkins
+masters but there is no coordination between them.
+
+One problem with scheduling builds on Jenkins master (“MasterA”) server
+is that MasterA only knows about its connected slaves. If all slaves on
+MasterA are busy then MasterA will just put the next scheduled build on
+its queue. Now MasterA needs to wait for an available slave to run
+the build. This will be very in-efficient if your builds take a long
+time to run. So..what if there is another Jenkins master (“MasterB”)
+that has free slaves to service the next scheduled build? Your probably
+saying “Then slaves on MasterB should run the build”. However MasterB
+will never service the builds on MasterA's queue. The client that schedules
+the builds must know about MasterB and then schedule builds on MasterB.
+This is what we mean by lack of coordination between masters.
+The gearman-plugin attempts to fill this gap.
+
+This plugin integrates Gearman with Jenkins and will make it so that
+any Jenkins slave on any Jenkins master can service a job in the queue.
+It will essentially replace the Jenkins build queue with the Gearman
+job queue. The job should stay in the gearman queue until there is a
+Jenkins node that can run that job.
+
+
+This is the typical workflow:
+
+1) On a 'Launch Workers', we spawn a Gearman worker for each Jenkins
+executor. We'll call these "executor worker threads".
+
+Each executor worker thread is associated 1:1 with a Jenkins node (slave or master)
+
+2) Now we register jobs for each Gearman executor depending on
+projects and nodes. View the image to see the mapping.
+
+3) On a 'Launch Workers', we spawn one more thread to be a Gearman
+worker to handle job management for this Jenkins master. We'll call
+it the "management worker thread" and register the following function:
+
+ stop:$hostname
+
+4) Any Gearman client can connect to the Gearman server and send a
+request to build a Jenkins project or cancel a project. View the
+examples to see how this can be done.
+
+5) The Gearman workers will service any client request that come
+through to start/cancel a Jenkins build.
diff --git a/pom.xml b/pom.xml
index 5e91814..bda1cef 100644
--- a/pom.xml
+++ b/pom.xml
@@ -29,6 +29,7 @@
gearman-plugin
hpi
+ 0.0.1
Gearman Plugin
http://wiki.jenkins-ci.org/display/JENKINS/Gearman+Plugin
diff --git a/src/main/java/hudson/plugins/gearman/StopJobWorker.java b/src/main/java/hudson/plugins/gearman/StopJobWorker.java
index f734788..b3c867c 100644
--- a/src/main/java/hudson/plugins/gearman/StopJobWorker.java
+++ b/src/main/java/hudson/plugins/gearman/StopJobWorker.java
@@ -19,13 +19,11 @@
package hudson.plugins.gearman;
+import hudson.model.AbstractBuild;
import hudson.model.Computer;
import hudson.model.Executor;
-import hudson.model.Label;
import hudson.model.Node;
import hudson.model.Queue;
-import hudson.model.Queue.Executable;
-import hudson.model.queue.SubTask;
import java.io.UnsupportedEncodingException;
import java.util.List;
@@ -45,7 +43,6 @@ import com.google.gson.reflect.TypeToken;
/**
* This is a gearman function that will cancel/abort jenkins builds
*
- *
* @author Khai Do
*/
public class StopJobWorker extends AbstractGearmanFunction {
@@ -80,7 +77,6 @@ public class StopJobWorker extends AbstractGearmanFunction {
// need to pass on uuid from client.
// temporarily passing uuid as a build parameter due to
// issue: https://answers.launchpad.net/gearman-java/+question/218865
-
String inUuid = null;
for (Map.Entry entry : inParams.entrySet()) {
if (entry.getKey().equals("uuid")) {
@@ -90,39 +86,44 @@ public class StopJobWorker extends AbstractGearmanFunction {
}
-
- // Cancel jenkins jobs that contain matching uuid from client
-
- boolean jobResult = cancelBuild(inUuid);
- String jobResultMsg = null;
- if (jobResult){
- jobResultMsg = "Canceled jenkins build " + inUuid;
-
- } else {
- jobResultMsg = "Could not cancel build " + inUuid;
-
+ boolean abortResult = false;
+ if (inUuid != null) {
+ // Abort running jenkins build that contain matching uuid
+ abortResult = abortBuild(inUuid);
}
+ //TODO: build might be on gearman queue if it's not currently
+ // running by jenkins, need to check the gearman queue for the
+ // job and remove it.
+
+ String jobResultMsg = "";
+ String jobResultEx = "";
+ boolean jobResult = true;
+ if (abortResult){
+ jobResultMsg = "Canceled jenkins build " + inUuid;
+ } else {
+ jobResultMsg = "Did not cancel jenkins build " + inUuid;
+ jobResultEx = "Could not cancel build " + inUuid;
+ }
GearmanJobResult gjr = new GearmanJobResultImpl(this.jobHandle, jobResult,
- jobResultMsg.getBytes(), new byte[0], new byte[0], 0, 0);
+ jobResultMsg.getBytes(), new byte[0], jobResultEx.getBytes(), 0, 0);
return gjr;
}
/**
* Function to cancel a jenkins build from the jenkins queue
*
- * @param id
- * The build Id
+ * @param uuid
+ * The build uuid
* @return
* true if build was cancel, otherwise false
*/
- private boolean cancelBuild (String id) {
+ private boolean cancelBuild (String uuid) {
- // Cancel jenkins job from the jenkins queue
Queue queue = Jenkins.getInstance().getQueue();
- if (id.isEmpty() || id == null){ // error checking
+ if (uuid.isEmpty() || uuid == null){ //NOOP
return false;
}
@@ -130,6 +131,7 @@ public class StopJobWorker extends AbstractGearmanFunction {
return false;
}
+ // locate the build with matching uuid
Queue.Item[] qItems = queue.getItems();
for (Queue.Item qi : qItems) {
List actions = qi
@@ -139,7 +141,8 @@ public class StopJobWorker extends AbstractGearmanFunction {
String jenkinsJobId = gpa.getUuid();
- if (jenkinsJobId.equals(id)) {
+ if (jenkinsJobId.equals(uuid)) {
+ // Cancel jenkins job from the jenkins queue
logger.info("---- Cancelling Jenkins build " + jenkinsJobId
+ " -------");
return queue.cancel(qi);
@@ -150,62 +153,97 @@ public class StopJobWorker extends AbstractGearmanFunction {
}
/**
- * Function to abort a running jenkins build
+ * Function to abort a currently running Jenkins build
+ * Running Jenkins builds are builds that actively being
+ * executed by Jenkins
*
- * @param id
- * The build Id
+ * @param uuid
+ * The build UUID
* @return
* true if build was aborted, otherwise false
*/
- private boolean abortBuild (String id) {
+ private boolean abortBuild (String uuid) {
- if (id.isEmpty() || id == null){ // error checking
+ if (uuid.isEmpty() || uuid == null){ //NOOP
return false;
}
-
/*
- * iterate over the executors on all the nodes then find the build
- * on that executor with the specified id.
- *
- * I'm able to iterate across the executors but not able to get
- * the build object from the executor to lookup the id parameter value
+ * iterate over the executors on master and slave nodes to find the
+ * build on the executor with the matching uuid
*/
+ // look at executors on master
+ Node masterNode = Computer.currentComputer().getNode();
+ Computer masterComp = masterNode.toComputer();
+ if (!masterComp.isIdle()) { // ignore idle master
+ List masterExecutors = masterComp.getExecutors();
+ for (Executor executor: masterExecutors) {
- // abort running jenkins job
+ if (executor.isIdle()) { // ignore idle executors
+ continue;
+ }
+
+ // lookup the running build with matching uuid
+ Queue.Executable executable = executor.getCurrentExecutable();
+ AbstractBuild, ?> currBuild = (AbstractBuild) executable;
+ int buildNum = currBuild.getNumber();
+ String buildId = currBuild.getId();
+ String runNodeName = currBuild.getBuiltOn().getNodeName();
+ NodeParametersAction param = currBuild.getAction(NodeParametersAction.class);
+ String buildParams = param.getParameters().toString();
+
+ if (param.getUuid().equals(uuid)) {
+
+ logger.info("Aborting build : "+buildNum+": "+buildId+" on " + runNodeName
+ +" with UUID " + uuid + " and build params " + buildParams);
+
+ // abort the running jenkins build
+ if (!executor.isInterrupted()) {
+ executor.interrupt();
+ return true;
+ }
+ }
+ }
+ }
+
+ // look at executors on slave nodes
List nodes = Jenkins.getInstance().getNodes();
-
- if (nodes.isEmpty()) {
+ if (nodes.isEmpty()) { //NOOP
return false;
}
for (Node node: nodes){
- Computer computer = node.toComputer();
- if (computer.isIdle()) { // ignore all idle slaves
+ Computer slave = node.toComputer();
+ if (slave.isIdle()) { // ignore all idle slaves
continue;
}
- List executors = computer.getExecutors();
-
+ List executors = slave.getExecutors();
for (Executor executor: executors) {
- if (executor.isIdle()) {
+ if (executor.isIdle()) { // ignore idle executors
continue;
}
- // lookup the running build with the id
- Executable executable = executor.getCurrentExecutable();
- SubTask subtask = executable.getParent();
- Label label = subtask.getAssignedLabel();
- List params = label.getActions(NodeParametersAction.class);
+ // lookup the running build with matching uuid
+ Queue.Executable executable = executor.getCurrentExecutable();
+ AbstractBuild, ?> currBuild = (AbstractBuild) executable;
+ int buildNum = currBuild.getNumber();
+ String buildId = currBuild.getId();
+ String runNodeName = currBuild.getBuiltOn().getNodeName();
+ NodeParametersAction param = currBuild.getAction(NodeParametersAction.class);
+ String buildParams = param.getParameters().toString();
- for (NodeParametersAction param: params){
- if (param.getUuid().equals(id)){
+ if (param.getUuid().equals(uuid)) {
+
+ logger.info("Aborting build : "+buildNum+": "+buildId+" on " + runNodeName
+ +" with UUID " + uuid + " and build params " + buildParams);
+
+ // abort the running jenkins build
+ if (!executor.isInterrupted()) {
executor.interrupt();
- if (executor.interrupted()){
- return true;
- }
+ return true;
}
}
}
diff --git a/src/main/java/hudson/plugins/gearman/example/StartJobClient.py b/src/main/java/hudson/plugins/gearman/example/StartJobClient.py
index 6b16693..5f6bdf1 100644
--- a/src/main/java/hudson/plugins/gearman/example/StartJobClient.py
+++ b/src/main/java/hudson/plugins/gearman/example/StartJobClient.py
@@ -32,7 +32,7 @@ server = '127.0.0.1:4730'
client = GearmanClient([server])
function = 'build:pep8:precise'
build_id = uuid.uuid4().hex
-build_params = {'param1':"red",'param2':"white",'param3':"blue"}
+build_params = {'param1':"red", 'param2':"white", 'param3':"blue"}
# Submit a synchronous job request to the job server
print 'Sending job ' + build_id + ' to ' + server + ' with params ' + str(build_params)
@@ -41,4 +41,5 @@ request = client.submit_job(function,
poll_timeout=60,
unique=build_id)
+
print request.result