Update zuul-gearman protocol.

* Use name+number as the build identifier for all meta-jobs.
  (Zuul has name + number as build metadata, so avoid adding new/duplicate
  features).
* Refer to the name of the manager worker as 'manager' instead of 'master'
  to avoid jenkins specifics.
* Just call the url "url" instead of "full_url".
* Change SetDescriptionWorker to use name+number as the build id.
  Also, expect 'html_description' instead of 'description'.
* Don't catch as many exceptions, instead, let them propogate so that they
  get turned into WORK_EXCEPTION packets with information (instead of
  WORK_FAIL packets which have no info).
* Change StopJobWorker to use the same name+number interface as
  setDescriptionWorker (for consistency, expandability, plus it makes
  the code simpler).

Change-Id: I8e078540c252bf9c1f14b79f8182517cbaa13555
This commit is contained in:
James E. Blair 2013-06-04 15:52:01 -07:00
parent 78abc3d903
commit 7a8d940c4b
4 changed files with 106 additions and 205 deletions

View File

@ -18,8 +18,12 @@
package hudson.plugins.gearman;
import hudson.model.AbstractProject;
import hudson.model.Computer;
import hudson.model.Node;
import hudson.model.Run;
import jenkins.model.Jenkins;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -55,4 +59,25 @@ public class GearmanPluginUtil {
}
}
/**
* Function to finds the build with the unique build id.
*
* @param jobName
* The jenkins job or project name
* @param buildNumber
* The jenkins build number
* @return
* the build Run if found, otherwise return null
*/
public static Run<?,?> findBuild(String jobName, int buildNumber) {
AbstractProject<?,?> project = Jenkins.getInstance().getItemByFullName(jobName, AbstractProject.class);
if (project != null){
Run<?,?> run = project.getBuildByNumber(buildNumber);
if (run != null) {
return run;
}
}
return null;
}
}

View File

@ -19,15 +19,12 @@
package hudson.plugins.gearman;
import hudson.model.AbstractProject;
import hudson.model.Run;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Map;
import jenkins.model.Jenkins;
import org.gearman.client.GearmanJobResult;
import org.gearman.client.GearmanJobResultImpl;
import org.gearman.worker.AbstractGearmanFunction;
@ -59,82 +56,49 @@ public class SetDescriptionWorker extends AbstractGearmanFunction {
// check job results
boolean jobResult = false;
String jobExceptionMsg = "";
String jobWarningMsg = "";
String jobResultMsg = "";
String decodedData;
// decode json
try {
// decode json
String decodedData = new String((byte[]) this.data, "UTF-8");
// convert parameters passed in from client to hash map
Gson gson = new Gson();
Map<String, String> data = gson.fromJson(decodedData,
new TypeToken<Map<String, String>>() {
}.getType());
// get build description
String buildDescription = data.get("description");
// get build id
String buildId = data.get("build_id");
String[] idToken = buildId.split(":");
if (idToken.length != 2 || buildDescription == null || buildId == null) {
jobExceptionMsg = "Invalid Unique Id";
throw new IllegalArgumentException(jobExceptionMsg);
} else {
String jobName = idToken[0];
String jobId = idToken[1];
if (!jobName.isEmpty() && !jobId.isEmpty()) {
// find build then update its description
Run<?,?> build = findBuild(jobName, jobId);
if (build != null) {
build.setDescription(buildDescription);
jobResultMsg = "Description for Jenkins build " +buildId+" was pdated to " + buildDescription;
jobResult = true;
} else {
jobExceptionMsg = "Cannot find build with id " + buildId;
throw new IllegalArgumentException(jobExceptionMsg);
}
} else {
jobExceptionMsg = "Build id is invalid or not specified";
throw new IllegalArgumentException(jobExceptionMsg);
}
}
decodedData = new String((byte[]) this.data, "UTF-8");
} catch (UnsupportedEncodingException e) {
jobExceptionMsg = "Error decoding parameters";
} catch (NullPointerException e) {
jobExceptionMsg = "Error decoding parameters";
} catch (IOException e) {
jobExceptionMsg = "Error setting build description";
} catch (IllegalArgumentException e) {
jobExceptionMsg = e.getMessage();
throw new IllegalArgumentException("Unsupported encoding exception in argument");
}
// convert parameters passed in from client to hash map
Gson gson = new Gson();
Map<String, String> data = gson.fromJson(decodedData,
new TypeToken<Map<String, String>>() {
}.getType());
// get build description
String buildDescription = data.get("html_description");
// get build id
String jobName = data.get("name");
String buildNumber = data.get("number");
if (!jobName.isEmpty() && !buildNumber.isEmpty()) {
// find build then update its description
Run<?,?> build = GearmanPluginUtil.findBuild(jobName, Integer.parseInt(buildNumber));
if (build != null) {
try {
build.setDescription(buildDescription);
} catch (IOException e) {
throw new IllegalArgumentException("Unable to set description for " +
jobName + ": " + buildNumber);
}
jobResultMsg = "Description for Jenkins build " +buildNumber+" was updated to " + buildDescription;
jobResult = true;
} else {
throw new IllegalArgumentException("Cannot find build number " +
buildNumber);
}
} else {
throw new IllegalArgumentException("Build id is invalid or not specified");
}
GearmanJobResult gjr = new GearmanJobResultImpl(this.jobHandle, jobResult,
jobResultMsg.getBytes(), jobWarningMsg.getBytes(),
jobExceptionMsg.getBytes(), 0, 0);
jobResultMsg.getBytes(), null, null, 0, 0);
return gjr;
}
/**
* Function to finds the build with the unique build id.
*
* @param jobName
* The jenkins job or project name
* @param uuid
* The jenkins job id
* @return
* the build Run if found, otherwise return null
*/
private Run<?,?> findBuild(String jobName, String jobId) {
AbstractProject<?,?> project = Jenkins.getInstance().getItemByFullName(jobName, AbstractProject.class);
if (project != null){
Run<?,?> run = project.getBuild(jobId);
if (run != null) {
return run;
}
}
return null;
}
}

View File

@ -87,14 +87,11 @@ public class StartJobWorker extends AbstractGearmanFunction {
data.put("name", project.getName());
data.put("number", build.getNumber());
data.put("id", build.getId());
data.put("build_id", project.getName()+":"+build.getId());
data.put("url", build.getUrl());
data.put("master", masterName);
data.put("manager", masterName);
String rootUrl = Hudson.getInstance().getRootUrl();
if (rootUrl != null) {
data.put("full_url", rootUrl + build.getUrl());
data.put("url", rootUrl + build.getUrl());
}
Result result = build.getResult();

View File

@ -19,16 +19,11 @@
package hudson.plugins.gearman;
import hudson.model.AbstractBuild;
import hudson.model.Computer;
import hudson.model.Run;
import hudson.model.Executor;
import hudson.model.Node;
import hudson.model.Queue;
import java.io.UnsupportedEncodingException;
import java.util.List;
import jenkins.model.Jenkins;
import java.util.Map;
import org.gearman.client.GearmanJobResult;
import org.gearman.client.GearmanJobResultImpl;
@ -36,6 +31,9 @@ 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
*
@ -53,137 +51,54 @@ public class StopJobWorker extends AbstractGearmanFunction {
*/
@Override
public GearmanJobResult executeFunction() {
String cancelID = null;
if (this.data != null) {
// decode the data from the client
try {
cancelID = new String((byte[]) this.data, "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
// check build and pass results back to client
boolean jobResult = true;
String jobFailureMsg = "";
String jobWarningMsg = "";
// check job results
boolean jobResult = false;
String jobResultMsg = "";
if (cancelID == null || cancelID.isEmpty()) {
logger.info("---- Client passed in an invalid UUID");
jobFailureMsg = "I need the job Id please";
jobResult = false;
} else {
String decodedData;
// decode json
try {
decodedData = new String((byte[]) this.data, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new IllegalArgumentException("Unsupported encoding exception in argument");
}
// convert parameters passed in from client to hash map
Gson gson = new Gson();
Map<String, String> data = gson.fromJson(decodedData,
new TypeToken<Map<String, String>>() {
}.getType());
// Abort running jenkins build that contain matching uuid
jobResult = abortBuild(cancelID);
// get build id
String jobName = data.get("name");
String buildNumber = data.get("number");
if (jobName.isEmpty() || buildNumber.isEmpty()) {
throw new IllegalArgumentException("Build id is invalid or not specified");
}
if (jobResult){
jobResultMsg = "Canceled jenkins build " + cancelID;
// Abort running jenkins build that contain matching uuid
Run<?,?> build = GearmanPluginUtil.findBuild(jobName, Integer.parseInt(buildNumber));
if (build != null) {
if (build.isBuilding()) {
Executor executor = build.getExecutor();
// abort the running jenkins build
if (!executor.isInterrupted()) {
executor.interrupt();
logger.info("---- Aborting build : " +
jobName + ": " + buildNumber);
jobResult = true;
}
} else {
jobFailureMsg = "Could not cancel build " + cancelID;
jobResult = false;
logger.info("---- Request to abourt non-building build : " +
jobName + ": " + buildNumber);
}
} else {
throw new IllegalArgumentException("Cannot find build " +
jobName + ": " + buildNumber);
}
GearmanJobResult gjr = new GearmanJobResultImpl(this.jobHandle, jobResult,
jobResultMsg.getBytes(), jobWarningMsg.getBytes(),
jobFailureMsg.getBytes(), 0, 0);
jobResultMsg.getBytes(), null, null, 0, 0);
return gjr;
}
/**
* Function to abort a currently running Jenkins build
* Running Jenkins builds are builds that actively being
* executed by Jenkins
*
* @param uuid
* The build UUID
* @return
* true if build was aborted, otherwise false
*/
private boolean abortBuild (String uuid) {
/*
* 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) {
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()) { //NOOP
return false;
}
for (Node node: nodes){
Computer slave = node.toComputer();
if (slave.isIdle()) { // ignore all idle slaves
continue;
}
List<Executor> executors = slave.getExecutors();
for (Executor executor: executors) {
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;
}
}
}
}
return false;
}
}