From ef3df2039ac8e0659d2048ffe287efe76a72f441 Mon Sep 17 00:00:00 2001 From: Khai Do Date: Sun, 27 Jan 2013 09:18:49 -0800 Subject: [PATCH] Add comments, decouple build actions, and cancel jobs. ExecutorWorkerThread - added comments ManagementWorkerThread - added comments NodeAssignmentAction - de-couple build actions. Action to send jenkins build to a specific jenkins node NodeParametersAction - de-couple build actions. Action to send parameters to a jenkins build StartJobWorker - De-couple build actions. Now schedule a build with NodeAssignmentAction and NodeParametersAction StopJobWorker - Cancel or abort builds. This only cancels build right now, abort does not work yet Change-Id: I72247f9a292fc78f5ea48b7c50d1f5df386efd00 --- .../gearman/ManagementWorkerThread.java | 15 +- .../plugins/gearman/NodeAssignmentAction.java | 65 ++++++++ .../plugins/gearman/NodeParametersAction.java | 34 ++-- .../plugins/gearman/StartJobWorker.java | 30 ++-- .../hudson/plugins/gearman/StopJobWorker.java | 157 +++++++++++++++++- 5 files changed, 264 insertions(+), 37 deletions(-) create mode 100644 src/main/java/hudson/plugins/gearman/NodeAssignmentAction.java diff --git a/src/main/java/hudson/plugins/gearman/ManagementWorkerThread.java b/src/main/java/hudson/plugins/gearman/ManagementWorkerThread.java index 535941e..41c4a31 100644 --- a/src/main/java/hudson/plugins/gearman/ManagementWorkerThread.java +++ b/src/main/java/hudson/plugins/gearman/ManagementWorkerThread.java @@ -23,10 +23,10 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; - -/* - * This is a thread to manage gearman executors - * This is used to abort/delete jenkins jobs. +/** + * This is a thread to run gearman management workers + * + * @author Khai Do */ public class ManagementWorkerThread extends AbstractWorkerThread{ @@ -39,6 +39,13 @@ public class ManagementWorkerThread extends AbstractWorkerThread{ super(host, port, name); } + /** + * Register gearman functions on this executor. This will unregister all + * functions before registering new functions. + * + * This executor only registers one function "stop:$hostname". + * + */ @Override public void registerJobs(){ String jobFunctionName = "stop:"+host; diff --git a/src/main/java/hudson/plugins/gearman/NodeAssignmentAction.java b/src/main/java/hudson/plugins/gearman/NodeAssignmentAction.java new file mode 100644 index 0000000..54a73fd --- /dev/null +++ b/src/main/java/hudson/plugins/gearman/NodeAssignmentAction.java @@ -0,0 +1,65 @@ +/* + * + * Copyright 2013 Hewlett-Packard Development Company, L.P. + * + * 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. + * + */ + + +package hudson.plugins.gearman; + +import hudson.model.Label; +import hudson.model.labels.LabelAssignmentAction; +import hudson.model.labels.LabelAtom; +import hudson.model.queue.SubTask; + +/** + * Action to send jenkins build to a specific node + * + * @author Khai Do + */ +public class NodeAssignmentAction implements LabelAssignmentAction { + + LabelAtom labelAtom; + + + public NodeAssignmentAction(String label) { + this.labelAtom = new LabelAtom(label); + } + + @Override + public String getIconFileName() { + // TODO Auto-generated method stub + return null; + } + + @Override + public String getDisplayName() { + // TODO Auto-generated method stub + return null; + } + + @Override + public String getUrlName() { + // TODO Auto-generated method stub + return null; + } + + @Override + public Label getAssignedLabel(SubTask task) { + // TODO Auto-generated method stub + return labelAtom; + } + +} diff --git a/src/main/java/hudson/plugins/gearman/NodeParametersAction.java b/src/main/java/hudson/plugins/gearman/NodeParametersAction.java index 572768f..52f0341 100644 --- a/src/main/java/hudson/plugins/gearman/NodeParametersAction.java +++ b/src/main/java/hudson/plugins/gearman/NodeParametersAction.java @@ -20,40 +20,36 @@ package hudson.plugins.gearman; import java.util.List; -import hudson.model.Label; import hudson.model.ParameterValue; import hudson.model.ParametersAction; -import hudson.model.labels.LabelAtom; -import hudson.model.queue.SubTask; -/* - * Overview: This class provides the ability to send - * a build with parameters to a specific jenkins node +/** + * Action to send parameters to a jenkins build. * + * @author Khai Do */ - public class NodeParametersAction extends ParametersAction { - LabelAtom labelAtom; // node label to assign build to a node - - public NodeParametersAction(List parameters, String label) { - super(parameters); - this.labelAtom = new LabelAtom(label); - - } + String id; // the id used to track the build job public NodeParametersAction(List parameters) { - super(parameters); + this(parameters, ""); + } - public NodeParametersAction(ParameterValue... parameters) { + public NodeParametersAction(List parameters, String id) { super(parameters); + this.id = id; + } - @Override - public Label getAssignedLabel(SubTask task) { - return labelAtom; + public String getUuid() { + return id; + } + + public void setUuid(String uuid) { + this.id = uuid; } diff --git a/src/main/java/hudson/plugins/gearman/StartJobWorker.java b/src/main/java/hudson/plugins/gearman/StartJobWorker.java index 4fd1b3a..923d871 100644 --- a/src/main/java/hudson/plugins/gearman/StartJobWorker.java +++ b/src/main/java/hudson/plugins/gearman/StartJobWorker.java @@ -25,9 +25,11 @@ import java.util.List; import java.util.Map; import hudson.model.Cause; import hudson.model.Node; +import hudson.model.Action; import hudson.model.ParameterValue; import hudson.model.Project; import hudson.model.StringParameterValue; + import org.gearman.client.GearmanJobResult; import org.gearman.client.GearmanJobResultImpl; import org.gearman.worker.AbstractGearmanFunction; @@ -37,14 +39,16 @@ import org.slf4j.LoggerFactory; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; -/* - * Overview: This is a gearman function that will start jenkins builds +/** + * This is a gearman function that will start jenkins builds + * * Assumptions: When this function is created it has an associated * node and project. The build will start a jenkins build * on its assigned assigned project and node and pass along * all of the parameters from the client. + * + * @author Khai Do */ - public class StartJobWorker extends AbstractGearmanFunction { private static final Logger logger = LoggerFactory @@ -58,11 +62,15 @@ public class StartJobWorker extends AbstractGearmanFunction { this.node = node; } + /* + * The Gearman Function + * @see org.gearman.worker.AbstractGearmanFunction#executeFunction() + */ @Override public GearmanJobResult executeFunction() { logger.info("----- Running executeFunction in " + name + " ----"); - // decode the data + // decode the data from the client String decoded = null; try { decoded = new String((byte[]) this.data, "UTF-8"); @@ -86,13 +94,6 @@ public class StartJobWorker extends AbstractGearmanFunction { // create new parameter objects to pass to jenkins build List buildParams = new ArrayList(); - - - // tried to use a RunParameterValue but not sure what - // jenkins expects for a "valid id". doing this way - // fails with java.lang.IllegalArgumentException: Invalid id -// buildParams.add(new RunParameterValue("uuid", "16")); - String uuid = null; // create the build parameters that were passed in from client for (Map.Entry entry : inParams.entrySet()) { @@ -108,7 +109,12 @@ public class StartJobWorker extends AbstractGearmanFunction { logger.info("Scheduling build on " + node.getNodeName() + " with UUID " + uuid + " and build params " + inParams); - project.scheduleBuild2(0, new Cause.UserIdCause(), new NodeParametersAction(buildParams, node.getNodeName())); + // create action to run on a specified node + Action runNode = new NodeAssignmentAction(node.getNodeName()); + // create action for parameters + Action params = new NodeParametersAction(buildParams, uuid); + Action [] actions = {runNode, params}; + project.scheduleBuild2(0, new Cause.UserIdCause(), actions); GearmanJobResult gjr = new GearmanJobResultImpl(this.jobHandle, true, diff --git a/src/main/java/hudson/plugins/gearman/StopJobWorker.java b/src/main/java/hudson/plugins/gearman/StopJobWorker.java index 39fc139..e4f4622 100644 --- a/src/main/java/hudson/plugins/gearman/StopJobWorker.java +++ b/src/main/java/hudson/plugins/gearman/StopJobWorker.java @@ -19,7 +19,21 @@ package hudson.plugins.gearman; +import hudson.model.Action; +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.Task; +import hudson.model.queue.SubTask; + import java.io.UnsupportedEncodingException; +import java.util.List; +import java.util.Map; + +import jenkins.model.Jenkins; import org.gearman.client.GearmanJobResult; import org.gearman.client.GearmanJobResultImpl; @@ -27,17 +41,31 @@ import org.gearman.worker.AbstractGearmanFunction; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.gson.Gson; +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 { private static final Logger logger = LoggerFactory .getLogger(Constants.PLUGIN_EXECTUOR_LOGGER_NAME); + + /* + * The Gearman Function + * @see org.gearman.worker.AbstractGearmanFunction#executeFunction() + */ + @Override public GearmanJobResult executeFunction() { String decoded = null; - logger.info("---- Running executeFunction in " + this.getName() - + " -------"); + logger.info("---- Running executeFunction in " + this.getName()+ " -------"); try { decoded = new String((byte[]) this.data, "UTF-8"); @@ -46,9 +74,134 @@ public class StopJobWorker extends AbstractGearmanFunction { e.printStackTrace(); } + Gson gson = new Gson(); + Map inParams = gson.fromJson(decoded, + new TypeToken>() { + }.getType()); + + // 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")) { + inUuid = entry.getValue(); + break; + } + + } + + // Cancel jenkins jobs that contain matching uuid from client + boolean canceled = cancelBuild(inUuid); + GearmanJobResult gjr = new GearmanJobResultImpl(this.jobHandle, true, decoded.toString().getBytes(), new byte[0], new byte[0], 0, 0); return gjr; } + + /** + * Function to cancel a jenkins build from the jenkins queue + * + * @param id + * The build Id + * @return + * true if build was cancel, otherwise false + */ + private boolean cancelBuild (String id) { + + // Cancel jenkins job from the jenkins queue + Queue queue = Jenkins.getInstance().getQueue(); + + if (id.isEmpty() || id == null){ // error checking + return false; + } + + if (queue.isEmpty()) { // do nothing if queue is empty + return false; + } + + Queue.Item[] qItems = queue.getItems(); + for (Queue.Item qi : qItems) { + List actions = qi + .getActions(NodeParametersAction.class); + + for (NodeParametersAction gpa : actions) { + + String jenkinsJobId = gpa.getUuid(); + + if (jenkinsJobId.equals(id)) { + logger.info("---- Cancelling Jenkins build " + jenkinsJobId + + " -------"); + return queue.cancel(qi); + } + } + } + return false; + } + + /** + * Function to abort a running jenkins build + * + * @param id + * The build Id + * @return + * true if build was aborted, otherwise false + */ + private boolean abortBuild (String id) { + + if (id.isEmpty() || id == null){ // error checking + 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 + */ + + // abort running jenkins job + List nodes = Jenkins.getInstance().getNodes(); + + if (nodes.isEmpty()) { + return false; + } + + for (Node node: nodes){ + + Computer computer = node.toComputer(); + if (computer.isIdle()) { // ignore all idle slaves + continue; + } + + List executors = computer.getExecutors(); + + for (Executor executor: executors) { + + if (executor.isIdle()) { + 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); + + for (NodeParametersAction param: params){ + if (param.getUuid().equals(id)){ + executor.interrupt(); + if (executor.interrupted()){ + return true; + } + } + } + } + } + return false; + } }