abort running jenkins builds
This checkin will allow a gearman client to request a job to abort a build that Jenkins is currently running. The client request needs to send in the build id to request an abort. This only aborts builds that have already made it to jenkins. It will NOT abort builds that are still on the gearman queue. Canceling jobs from the gearman queue is a TODO Added pydev nature due to the example python script added in last commit. Also setup auto formatting to help remove trailing white spaces. add additional documentation set the plugin version starting at 0.0.1 Change-Id: I533934723ee4f4814ebd68c8a2f9fe32a6aef718
This commit is contained in:
parent
b4440ef521
commit
8c06c46014
6
.project
6
.project
|
@ -5,6 +5,11 @@
|
|||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.python.pydev.PyDevBuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
|
@ -19,5 +24,6 @@
|
|||
<natures>
|
||||
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
<nature>org.python.pydev.pythonNature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<?eclipse-pydev version="1.0"?>
|
||||
|
||||
<pydev_project>
|
||||
<pydev_property name="org.python.pydev.PYTHON_PROJECT_INTERPRETER">Default</pydev_property>
|
||||
<pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">python 2.7</pydev_property>
|
||||
</pydev_project>
|
62
README.txt
62
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.
|
||||
|
|
1
pom.xml
1
pom.xml
|
@ -29,6 +29,7 @@
|
|||
|
||||
<artifactId>gearman-plugin</artifactId>
|
||||
<packaging>hpi</packaging>
|
||||
<version>0.0.1</version>
|
||||
|
||||
<name>Gearman Plugin</name>
|
||||
<url>http://wiki.jenkins-ci.org/display/JENKINS/Gearman+Plugin</url>
|
||||
|
|
|
@ -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<String, String> 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<NodeParametersAction> 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<Executor> 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<Node> 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<Executor> executors = computer.getExecutors();
|
||||
|
||||
List<Executor> 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<NodeParametersAction> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue