fix plugin extension point, enhanced jelly script, added test connection button

.classpath
  Added eclipse classpath to web resources

pom.xml
  Update jenkins dependency to ver 1.502

src/main/java/hudson/plugins/gearman/Constants.java
  Added default setting for launchWorker configuration.

src/main/java/hudson/plugins/gearman/GearmanPluginConfig.java
  This plugin was extending the wrong jenkins extension point.
  It's now extending GlobalConfiguration instead of Builder which I
  believe is more correct. I had to rename GearmanPlugin to GearmanPluginConfig
  because for some reason extending GlobalConfiguration from a GearmanPlugin
  class doesn't work. Also had to rename the associated jelly script from
  global.jelly to config.jelly

  Moved and renamed help files to let jenkins framework load help files named help-$field.html

src/main/java/hudson/plugins/gearman/ManagementWorkerThread.java
  changed debugging from System.out to logger.info

src/main/resources/hudson/plugins/gearman/GearmanPluginConfig/config.jelly
  Added basic verification for host config (make it required).
  Changed port config type from textbox to number and added basic verification (make it a required number)
  Added a 'Test Connection' button to test the Gearman server connection.
  Remove references to help files to let the jenkins framework load help files named help-$field.html

src/main/resources/index.jelly
  Update description

Change-Id: If0a521c8611d6a93f0434000452dc8c284f5fd74
This commit is contained in:
zaro 2013-02-24 14:24:49 -08:00
parent 6a4a12feca
commit ac1d57e4bf
15 changed files with 298 additions and 317 deletions

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<classpath> <classpath>
<classpathentry kind="src" path="src/main/java"/> <classpathentry kind="src" path="src/main/java"/>
<classpathentry kind="src" path="src/test/java"/> <classpathentry kind="src" path="src/main/resources"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/J2SE-1.5"/> <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/J2SE-1.5"/>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER"/> <classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER"/>
<classpathentry kind="output" path="target/classes"/> <classpathentry kind="output" path="target/classes"/>

View File

@ -24,7 +24,7 @@
<parent> <parent>
<groupId>org.jenkins-ci.plugins</groupId> <groupId>org.jenkins-ci.plugins</groupId>
<artifactId>plugin</artifactId> <artifactId>plugin</artifactId>
<version>1.460</version><!-- which version of Jenkins is this plugin built against? --> <version>1.502</version><!-- which version of Jenkins is this plugin built against? -->
</parent> </parent>
<artifactId>gearman-plugin</artifactId> <artifactId>gearman-plugin</artifactId>

View File

@ -24,6 +24,7 @@ package hudson.plugins.gearman;
public interface Constants { public interface Constants {
/* Defines. */ /* Defines. */
public static final boolean GEARMAN_DEFAULT_LAUNCH_WORKER = false;
public static final String GEARMAN_DEFAULT_TCP_HOST = "127.0.0.1"; public static final String GEARMAN_DEFAULT_TCP_HOST = "127.0.0.1";
public static final int GEARMAN_DEFAULT_TCP_PORT = 4730; public static final int GEARMAN_DEFAULT_TCP_PORT = 4730;

View File

@ -1,277 +0,0 @@
/*
*
* 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.Extension;
import hudson.Launcher;
import hudson.model.BuildListener;
import hudson.model.AbstractBuild;
import hudson.model.Computer;
import hudson.model.Node;
import hudson.tasks.BuildStepDescriptor;
import hudson.tasks.Builder;
import java.util.List;
import java.util.Stack;
import jenkins.model.Jenkins;
import net.sf.json.JSONObject;
import org.apache.commons.lang.StringUtils;
import org.gearman.common.GearmanNIOJobServerConnection;
import org.gearman.worker.GearmanWorkerImpl;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.StaplerRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* GearmanPlugin {@link Builder}.
*
* <p>
* This sets up the gearman plugin as another plugin in Jenkins
* It will allow us to start and stop the gearman workers.
* <p>
*
* @author Khai Do
*/
public class GearmanPlugin extends Builder {
private static final Logger logger = LoggerFactory
.getLogger(Constants.PLUGIN_LOGGER_NAME);
private final String name;
@DataBoundConstructor
public GearmanPlugin(String name) {
logger.info("--- GearmanPlugin Constructor ---" + name);
this.name = name;
}
public String getName() {
return name;
}
@Override
public boolean perform(AbstractBuild build, Launcher launcher, BuildListener listener) {
return true;
}
@Override
public DescriptorImpl getDescriptor() {
return (DescriptorImpl)super.getDescriptor();
}
@Extension
public static final class DescriptorImpl extends
BuildStepDescriptor<Builder> {
private static final Logger logger = LoggerFactory
.getLogger(Constants.PLUGIN_LOGGER_NAME);
private boolean launchWorker; // launchWorker state (from UI checkbox)
private String host; // gearman server host
private int port; // gearman server port
private final Jenkins jenkins;
// handles to gearman workers
public static Stack<AbstractWorkerThread> gewtHandles;
public static Stack<AbstractWorkerThread> gmwtHandles;
public DescriptorImpl() {
logger.info("--- DescriptorImpl Constructor ---");
jenkins = Jenkins.getInstance();
gewtHandles = new Stack<AbstractWorkerThread>();
gmwtHandles = new Stack<AbstractWorkerThread>();
logger.info("--- DescriptorImpl Constructor ---" + host);
logger.info("--- DescriptorImpl Constructor ---" + port);
load();
/*
* Not sure when to register gearman functions yet so for now always
* initialize the launch worker flag to disabled state at jenkins
* startup so we are always at a known state
*/
this.launchWorker = false;
save();
}
@Override
public String getDisplayName() {
return "Gearman Plugin";
}
@Override
public boolean isApplicable(Class type) {
return true;
}
@Override
public boolean configure(StaplerRequest staplerRequest, JSONObject json)
throws FormException {
launchWorker = json.getBoolean("launchWorker");
logger.info("--- LaunchWorker = "+ launchWorker);
// set the gearman server host from value in jenkins config page
try {
host = json.getString("host");
} catch (Exception e) {
throw new RuntimeException("Error getting the gearman host name");
}
// set the gearman server port from value in jenkins config page
try {
port = Integer.parseInt(json.getString("port"));
} catch (Exception e) {
throw new RuntimeException("Invalid gearman port value");
}
/*
* Purpose here is to create a 1:1 mapping of 'gearman
* worker':'jenkins executor' then use the gearman worker to execute
* builds on that jenkins nodes
*/
if (launchWorker && gmwtHandles.isEmpty() && gewtHandles.isEmpty()) {
// user input verification
if (StringUtils.isEmpty(host) || StringUtils.isBlank(host))
throw new RuntimeException("Invalid gearman host name");
// i believe gearman already checks for port range, just want to do
// basic verification here
if (port <= 0)
throw new RuntimeException("Invalid gearman port value");
logger.info("--- Hostname = "+ this.getHost());
logger.info("--- Port = "+ this.getPort());
// check for a valid connection to gearman server
logger.info("--- Check connection to Gearman Server "+host+":"+port);
boolean validConn = new GearmanWorkerImpl().addServer(
new GearmanNIOJobServerConnection(host, port));
if (!validConn) {
logger.info("--- Could not get connection to Gearman Server "+host+":"+port);
this.launchWorker = false; // will not spawn any workers, disable flag because
throw new RuntimeException("Could not get connection to Gearman Server " +
host+":"+port);
}
/*
* Spawn management executor. This worker does not need any
* executors. It only needs to work with gearman.
*/
AbstractWorkerThread gwt = null;
gwt = new ManagementWorkerThread(host, port, host);
gwt.registerJobs();
gwt.start();
gmwtHandles.push(gwt);
/*
* Spawn executors for the jenkins master
* Need to treat the master differently than slaves because
* the master is not the same as a slave
*/
// make sure master is enabled (or has executors)
Node masterNode = null;
try {
masterNode = Computer.currentComputer().getNode();
} catch (NullPointerException npe) {
logger.info("--- Master is offline");
} catch (Exception e) {
logger.info("--- Can't get Master");
e.printStackTrace();
}
if (masterNode != null) {
Computer computer = masterNode.toComputer();
int executors = computer.getExecutors().size();
for (int i=0; i<executors; i++) {
// create a gearman executor for every jenkins executor
gwt = new ExecutorWorkerThread(host, port,
"master-exec"+Integer.toString(i), masterNode);
gwt.registerJobs();
gwt.start();
gewtHandles.push(gwt);
}
}
/*
* Spawn executors for the jenkins slaves
*/
List<Node> nodes = jenkins.getNodes();
if (!nodes.isEmpty()) {
for (Node node : nodes) {
Computer computer = node.toComputer();
if (computer.isOnline()) {
// create a gearman executor for every jenkins executor
int slaveExecutors = computer.getExecutors().size();
for (int i=0; i<slaveExecutors; i++) {
gwt = new ExecutorWorkerThread(host, port,
node.getNodeName()+"-exec"+Integer.toString(i), node);
gwt.registerJobs();
gwt.start();
gewtHandles.push(gwt);
}
}
}
}
}
//stop gearman workers
if (!launchWorker) {
while (!gewtHandles.isEmpty()) { // stop executors
gewtHandles.pop().stop();
}
while (!gmwtHandles.isEmpty()) { // stop management
gmwtHandles.pop().stop();
}
}
int runningExecutors = gmwtHandles.size()+gewtHandles.size();
logger.info("--- Num of executors running = "+runningExecutors);
save();
return true;
}
/**
* This method returns true if the global configuration says we should
* launch worker.
*/
public boolean launchWorker() {
return launchWorker;
}
/**
* This method returns the value from the server host text box
*/
public String getHost() {
return host;
}
/**
* This method returns the value from the server port text box
*/
public int getPort() {
return port;
}
}
}

View File

@ -0,0 +1,272 @@
/*
*
* 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.Extension;
import hudson.model.Computer;
import hudson.model.Descriptor;
import hudson.model.Node;
import hudson.util.FormValidation;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.List;
import java.util.Stack;
import javax.servlet.ServletException;
import jenkins.model.GlobalConfiguration;
import jenkins.model.Jenkins;
import net.sf.json.JSONObject;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.StaplerRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class is used to set the global configuration for the gearman-plugin It
* is also used to launch gearman workers on this Jenkins server
*
* @author Khai Do
*/
@Extension
public class GearmanPluginConfig extends GlobalConfiguration {
private static final Logger logger = LoggerFactory
.getLogger(Constants.PLUGIN_LOGGER_NAME);
private boolean launchWorker; // launchWorker state (from UI checkbox)
private String host; // gearman server host
private int port; // gearman server port
// handles to gearman workers
public static Stack<AbstractWorkerThread> gewtHandles;
public static Stack<AbstractWorkerThread> gmwtHandles;
/**
* Constructor.
*/
public GearmanPluginConfig() {
logger.info("--- GearmanPluginConfig Constructor ---");
gewtHandles = new Stack<AbstractWorkerThread>();
gmwtHandles = new Stack<AbstractWorkerThread>();
load();
/*
* Not sure when to register gearman functions yet so for now always
* initialize the launch worker flag to disabled state at jenkins
* startup so we are always at a known state
*/
this.launchWorker = Constants.GEARMAN_DEFAULT_LAUNCH_WORKER;
save();
}
/*
* This method checks whether a connection can be made to a host:port
*
* @param host
* the host name
*
* @param port
* the host port
*
* @param timeout
* the timeout (milliseconds) to try the connection
*
* @return
* true if a socket connection can be established otherwise false
*/
public boolean connectionIsAvailable(String host, int port, int timeout) {
InetSocketAddress endPoint = new InetSocketAddress(host, port);
Socket socket = new Socket();
if (endPoint.isUnresolved()) {
System.out.println("Failure " + endPoint);
} else {
try {
socket.connect(endPoint, timeout);
logger.info("Connection Success: "+endPoint);
return true;
} catch (Exception e) {
logger.info("Connection Failure: "+endPoint+" message: "
+e.getClass().getSimpleName()+" - "
+e.getMessage());
} finally {
if (socket != null) {
try {
socket.close();
} catch (Exception e) {
logger.info(e.getMessage());
}
}
}
}
return false;
}
/*
* This method runs when user clicks Test Connection button.
*
* @return
* message indicating whether connection test passed or failed
*/
public FormValidation doTestConnection(
@QueryParameter("host") final String host,
@QueryParameter("port") final int port) throws IOException,
ServletException {
if (connectionIsAvailable(host, port, 5000)) {
return FormValidation.ok("Success");
} else {
return FormValidation.error("Failed: Unable to Connect");
}
}
@Override
public boolean configure(StaplerRequest req, JSONObject json)
throws Descriptor.FormException {
// set the gearman config from user entered values in jenkins config page
launchWorker = json.getBoolean("launchWorker");
host = json.getString("host");
port = json.getInt("port");
/*
* Purpose here is to create a 1:1 mapping of 'gearman worker':'jenkins
* executor' then use the gearman worker to execute builds on that
* jenkins nodes
*/
if (launchWorker && gmwtHandles.isEmpty() && gewtHandles.isEmpty()) {
// check for a valid connection to gearman server
logger.info("--- Check connection to Gearman Server " + getHost() + ":"
+ getPort());
if (!connectionIsAvailable(host, port, 5000)) {
this.launchWorker = false;
throw new RuntimeException(
"Could not get connection to Gearman Server " + getHost()
+ ":" + getPort());
}
/*
* Spawn management executor worker. This worker does not need any
* executors. It only needs to work with gearman.
*/
AbstractWorkerThread gwt = null;
gwt = new ManagementWorkerThread(host, port, host);
gwt.registerJobs();
gwt.start();
gmwtHandles.push(gwt);
/*
* Spawn executors for the jenkins master Need to treat the master
* differently than slaves because the master is not the same as a
* slave
*/
// first make sure master is enabled (or has executors)
Node masterNode = null;
try {
masterNode = Computer.currentComputer().getNode();
} catch (NullPointerException npe) {
logger.info("--- Master is offline");
} catch (Exception e) {
logger.info("--- Can't get Master");
e.printStackTrace();
}
if (masterNode != null) {
Computer computer = masterNode.toComputer();
int executors = computer.getExecutors().size();
for (int i = 0; i < executors; i++) {
// create a gearman worker for every executor on the master
gwt = new ExecutorWorkerThread(host, port, "master-exec"
+ Integer.toString(i), masterNode);
gwt.registerJobs();
gwt.start();
gewtHandles.push(gwt);
}
}
/*
* Spawn executors for the jenkins slaves
*/
List<Node> nodes = Jenkins.getInstance().getNodes();
if (!nodes.isEmpty()) {
for (Node node : nodes) {
Computer computer = node.toComputer();
if (computer.isOnline()) {
// create a gearman worker for every executor on the slave
int slaveExecutors = computer.getExecutors().size();
for (int i = 0; i < slaveExecutors; i++) {
gwt = new ExecutorWorkerThread(host, port,
node.getNodeName() + "-exec"
+ Integer.toString(i), node);
gwt.registerJobs();
gwt.start();
gewtHandles.push(gwt);
}
}
}
}
}
// stop gearman workers
if (!launchWorker) {
while (!gewtHandles.isEmpty()) { // stop executors
gewtHandles.pop().stop();
}
while (!gmwtHandles.isEmpty()) { // stop management
gmwtHandles.pop().stop();
}
}
int runningExecutors = gmwtHandles.size() + gewtHandles.size();
logger.info("--- Num of executors running = " + runningExecutors);
req.bindJSON(this, json);
save();
return true;
}
/**
* This method returns true if the global configuration says we should
* launch worker.
*/
public boolean launchWorker() {
return launchWorker;
}
/**
* This method returns the value from the server host text box
*/
public String getHost() {
return host;
}
/**
* This method returns the value from the server port text box
*/
public int getPort() {
return port;
}
}

View File

@ -34,10 +34,6 @@ public class ManagementWorkerThread extends AbstractWorkerThread{
private static final Logger logger = LoggerFactory private static final Logger logger = LoggerFactory
.getLogger(AbstractWorkerThread.class); .getLogger(AbstractWorkerThread.class);
public ManagementWorkerThread(String host, int port){
super(host, port);
}
public ManagementWorkerThread(String host, int port, String name){ public ManagementWorkerThread(String host, int port, String name){
super(host, port, name); super(host, port, name);
} }

View File

@ -1,2 +0,0 @@
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
</j:jelly>

View File

@ -1,21 +0,0 @@
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<f:section title="Gearman Plugin">
<f:entry title="Gearman Server Host" field="host"
description="Set the Gearman Server Host Name"
help="${rootURL}/plugin/gearman/help-serverHost.html">
<f:textbox name="gearman.host" value="${descriptor.host()}" />
</f:entry>
<f:entry title="Gearman Server Port" field="port"
description="Set the Gearman Server Port"
help="${rootURL}/plugin/gearman/help-serverPort.html">
<f:textbox name="gearman.port" value="${descriptor.port()}" />
</f:entry>
<f:entry title="Launch Worker"
description="Check to launch the Gearman worker(s)"
help="${rootURL}/plugin/gearman/help-globalConfig.html">
<f:checkbox name="gearman.launchWorker" checked="${descriptor.launchWorker()}" />
</f:entry>
</f:section>
</j:jelly>

View File

@ -0,0 +1,17 @@
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<f:section title="Gearman Plugin Config">
<f:entry title="Gearman Server Host" field="host">
<f:textbox value="${descriptor.host()}" clazz="required"/>
</f:entry>
<f:entry title="Gearman Server Port" field="port">
<f:number value="${descriptor.port()}" clazz="required number"/>
</f:entry>
<f:validateButton
title="${%Test Connection}" progress="${%Testing...}"
method="testConnection" with="host,port"/>
<f:entry title="Launch Worker" field="launchWorker"
description="Select to run Gearman workers, Unselect to stop">
<f:checkbox checked="${descriptor.launchWorker()}"/>
</f:entry>
</f:section>
</j:jelly>

View File

@ -0,0 +1,5 @@
<div>
<p>
Set the Gearman server port. By default the Gearman server's port typically set to 4730
</p>
</div>

View File

@ -1,4 +1,4 @@
<div> <div>
This plugin integrates Gearman with Jenkins. Its purpose is to use Gearman workers This plugin integrates Gearman with Jenkins. Its purpose is to use Gearman workers
to execute Jenkins builds. to manage Jenkins builds.
</div> </div>

View File

@ -1,5 +0,0 @@
<div>
<p>
This plugin integrates Gearman with Jenkins.
</p>
</div>

View File

@ -1,5 +0,0 @@
<div>
<p>
Set the Gearman server port. The Gearman server's port is typically set to 4730 by default.
</p>
</div>