You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mesos.apache.org by vi...@apache.org on 2013/06/26 05:11:35 UTC
[1/2] git commit: Added Jenkins plugin to the build tool chain.
Updated Branches:
refs/heads/master fe5f05f2d -> 7741822d4
Added Jenkins plugin to the build tool chain.
Review: https://reviews.apache.org/r/12034
Project: http://git-wip-us.apache.org/repos/asf/incubator-mesos/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-mesos/commit/7741822d
Tree: http://git-wip-us.apache.org/repos/asf/incubator-mesos/tree/7741822d
Diff: http://git-wip-us.apache.org/repos/asf/incubator-mesos/diff/7741822d
Branch: refs/heads/master
Commit: 7741822d497db4c2dd9308244e3be7c50ef2b732
Parents: 4b5f370
Author: Vinod Kone <vi...@twitter.com>
Authored: Fri Jun 21 14:33:34 2013 -0700
Committer: Vinod Kone <vi...@twitter.com>
Committed: Tue Jun 25 20:09:50 2013 -0700
----------------------------------------------------------------------
Makefile.am | 2 +-
configure.ac | 1 +
jenkins/Makefile.am | 51 ++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 53 insertions(+), 1 deletion(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-mesos/blob/7741822d/Makefile.am
----------------------------------------------------------------------
diff --git a/Makefile.am b/Makefile.am
index 8e7a32f..fc1c1d0 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -18,7 +18,7 @@ ACLOCAL_AMFLAGS = -I m4
AUTOMAKE_OPTIONS = foreign
-SUBDIRS = . 3rdparty src ec2 hadoop
+SUBDIRS = . 3rdparty src ec2 hadoop jenkins
EXTRA_DIST =
http://git-wip-us.apache.org/repos/asf/incubator-mesos/blob/7741822d/configure.ac
----------------------------------------------------------------------
diff --git a/configure.ac b/configure.ac
index 872e9e7..fbe22e5 100644
--- a/configure.ac
+++ b/configure.ac
@@ -79,6 +79,7 @@ AC_CONFIG_SUBDIRS([3rdparty/libprocess])
AC_CONFIG_FILES([Makefile])
AC_CONFIG_FILES([ec2/Makefile])
AC_CONFIG_FILES([hadoop/Makefile])
+AC_CONFIG_FILES([jenkins/Makefile])
AC_CONFIG_FILES([src/Makefile])
AC_CONFIG_FILES([3rdparty/Makefile])
http://git-wip-us.apache.org/repos/asf/incubator-mesos/blob/7741822d/jenkins/Makefile.am
----------------------------------------------------------------------
diff --git a/jenkins/Makefile.am b/jenkins/Makefile.am
new file mode 100644
index 0000000..9f9ebf4
--- /dev/null
+++ b/jenkins/Makefile.am
@@ -0,0 +1,51 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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
+
+EXTRA_DIST = pom.xml \
+ src/main/java/org/jenkinsci/plugins/mesos/JenkinsScheduler.java \
+ src/main/java/org/jenkinsci/plugins/mesos/Mesos.java \
+ src/main/java/org/jenkinsci/plugins/mesos/MesosCloud.java \
+ src/main/java/org/jenkinsci/plugins/mesos/MesosComputer.java \
+ src/main/java/org/jenkinsci/plugins/mesos/MesosComputerLauncher.java \
+ src/main/java/org/jenkinsci/plugins/mesos/MesosRetentionStrategy.java \
+ src/main/java/org/jenkinsci/plugins/mesos/MesosSlave.java \
+ src/main/java/org/jenkinsci/plugins/mesos/TaskTemplate.java \
+ src/main/resources/index.jelly \
+ src/main/resources/org/jenkinsci/plugins/mesos/MesosCloud/computerSet.jelly \
+ src/main/resources/org/jenkinsci/plugins/mesos/MesosCloud/config.jelly \
+ src/main/resources/org/jenkinsci/plugins/mesos/MesosCloud/help-master.html \
+ src/main/resources/org/jenkinsci/plugins/mesos/MesosSlave/configure-entries.jelly \
+ src/main/resources/org/jenkinsci/plugins/mesos/TaskTemplate/config.jelly
+
+
+# Defines a target to build the Jenkins plugin.
+# This will create ./target/mesos.hpi that you can deploy to Jenkins.
+jenkins:
+ if test "$(top_srcdir)" != "$(top_builddir)"; then \
+ cp -p $(srcdir)/pom.xml .; \
+ cp -rp $(srcdir)/src .; \
+ fi
+ mvn install
+
+
+clean-local:
+ if test "$(top_srcdir)" != "$(top_builddir)"; then \
+ rm -f pom.xml; \
+ rm -rf src target work; \
+ fi
+
+
+.PHONY: jenkins
[2/2] git commit: Added Jenkins scheduler.
Posted by vi...@apache.org.
Added Jenkins scheduler.
Review: https://reviews.apache.org/r/12033
Project: http://git-wip-us.apache.org/repos/asf/incubator-mesos/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-mesos/commit/4b5f3709
Tree: http://git-wip-us.apache.org/repos/asf/incubator-mesos/tree/4b5f3709
Diff: http://git-wip-us.apache.org/repos/asf/incubator-mesos/diff/4b5f3709
Branch: refs/heads/master
Commit: 4b5f3709e6388c19befaa4d725a0d82848f546b2
Parents: fe5f05f
Author: Vinod Kone <vi...@twitter.com>
Authored: Fri Jun 14 11:38:38 2013 -0700
Committer: Vinod Kone <vi...@twitter.com>
Committed: Tue Jun 25 20:09:50 2013 -0700
----------------------------------------------------------------------
jenkins/pom.xml | 80 +++++
.../plugins/mesos/JenkinsScheduler.java | 331 +++++++++++++++++++
.../java/org/jenkinsci/plugins/mesos/Mesos.java | 103 ++++++
.../org/jenkinsci/plugins/mesos/MesosCloud.java | 208 ++++++++++++
.../jenkinsci/plugins/mesos/MesosComputer.java | 28 ++
.../plugins/mesos/MesosComputerLauncher.java | 99 ++++++
.../plugins/mesos/MesosRetentionStrategy.java | 88 +++++
.../org/jenkinsci/plugins/mesos/MesosSlave.java | 92 ++++++
.../jenkinsci/plugins/mesos/TaskTemplate.java | 39 +++
jenkins/src/main/resources/index.jelly | 7 +
.../plugins/mesos/MesosCloud/computerSet.jelly | 12 +
.../plugins/mesos/MesosCloud/config.jelly | 12 +
.../plugins/mesos/MesosCloud/help-master.html | 3 +
.../mesos/MesosSlave/configure-entries.jelly | 29 ++
.../plugins/mesos/TaskTemplate/config.jelly | 33 ++
15 files changed, 1164 insertions(+)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-mesos/blob/4b5f3709/jenkins/pom.xml
----------------------------------------------------------------------
diff --git a/jenkins/pom.xml b/jenkins/pom.xml
new file mode 100644
index 0000000..c8d0deb
--- /dev/null
+++ b/jenkins/pom.xml
@@ -0,0 +1,80 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.jenkins-ci.plugins</groupId>
+ <artifactId>plugin</artifactId>
+ <version>1.509.1</version><!-- which version of Jenkins is this plugin built against? -->
+ </parent>
+
+ <groupId>org.jenkins-ci.plugins</groupId>
+ <artifactId>mesos</artifactId>
+ <version>0.1-SNAPSHOT</version>
+ <packaging>hpi</packaging>
+
+ <!-- get every artifact through repo.jenkins-ci.org, which proxies all the artifacts that we need -->
+ <repositories>
+ <repository>
+ <id>repo.jenkins-ci.org</id>
+ <url>http://repo.jenkins-ci.org/public/</url>
+ </repository>
+ <repository>
+ <id>repository.apache.org</id>
+ <url>https://repository.apache.org/content/repositories/releases</url>
+ </repository>
+
+ </repositories>
+
+ <pluginRepositories>
+ <pluginRepository>
+ <id>repo.jenkins-ci.org</id>
+ <url>http://repo.jenkins-ci.org/public/</url>
+ </pluginRepository>
+ </pluginRepositories>
+
+ <properties>
+ <!--
+ explicitly specifying the latest version here because one we get from the parent POM
+ tends to lag behind a bit
+ -->
+ <maven-hpi-plugin.version>1.95</maven-hpi-plugin.version>
+ </properties>
+
+ <!-- TODO(vinod): Use the mesos jar built from 'make maven-install' instead. -->
+ <!-- NOTE: Need to run jenkins by providing the mesos lib path. -->
+ <!-- $ MESOS_NATIVE_LIBRARY=/path/to/libmesos.dylib mvn hpi:run -->
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.mesos</groupId>
+ <artifactId>mesos</artifactId>
+ <version>0.11.0-incubating</version>
+ </dependency>
+ <dependency>
+ <groupId>com.google.protobuf</groupId>
+ <artifactId>protobuf-java</artifactId>
+ <version>2.4.1</version>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-dependency-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>copy-dependencies</id>
+ <phase>package</phase>
+ <goals>
+ <goal>copy-dependencies</goal>
+ </goals>
+ <configuration>
+ <outputDirectory>${project.build.directory}</outputDirectory>
+ <overWriteReleases>false</overWriteReleases>
+ <overWriteSnapshots>true</overWriteSnapshots>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
http://git-wip-us.apache.org/repos/asf/incubator-mesos/blob/4b5f3709/jenkins/src/main/java/org/jenkinsci/plugins/mesos/JenkinsScheduler.java
----------------------------------------------------------------------
diff --git a/jenkins/src/main/java/org/jenkinsci/plugins/mesos/JenkinsScheduler.java b/jenkins/src/main/java/org/jenkinsci/plugins/mesos/JenkinsScheduler.java
new file mode 100644
index 0000000..9166e70
--- /dev/null
+++ b/jenkins/src/main/java/org/jenkinsci/plugins/mesos/JenkinsScheduler.java
@@ -0,0 +1,331 @@
+package org.jenkinsci.plugins.mesos;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Queue;
+import java.util.logging.Logger;
+
+import org.apache.mesos.MesosSchedulerDriver;
+import org.apache.mesos.Protos.CommandInfo;
+import org.apache.mesos.Protos.ExecutorID;
+import org.apache.mesos.Protos.Filters;
+import org.apache.mesos.Protos.FrameworkID;
+import org.apache.mesos.Protos.FrameworkInfo;
+import org.apache.mesos.Protos.MasterInfo;
+import org.apache.mesos.Protos.Offer;
+import org.apache.mesos.Protos.OfferID;
+import org.apache.mesos.Protos.Resource;
+import org.apache.mesos.Protos.SlaveID;
+import org.apache.mesos.Protos.Status;
+import org.apache.mesos.Protos.TaskID;
+import org.apache.mesos.Protos.TaskInfo;
+import org.apache.mesos.Protos.TaskStatus;
+import org.apache.mesos.Protos.Value;
+import org.apache.mesos.Scheduler;
+import org.apache.mesos.SchedulerDriver;
+
+public class JenkinsScheduler implements Scheduler {
+ private static final int JENKINS_SLAVE_CPUS = 1;
+
+ // TODO(vinod): Revert these to real values when using in production.
+ private static final int JENKINS_SLAVE_MEM = 512;
+ private static final int JENKINS_EXECUTOR_MEM = 100;
+
+ private static final String SLAVE_JAR_URI_SUFFIX = "jnlpJars/slave.jar";
+
+ private static final String SLAVE_COMMAND_FORMAT =
+ "java -DHUDSON_HOME=jenkins -server -Xmx%dm -Xms16m -XX:+UseConcMarkSweepGC " +
+ "-Djava.net.preferIPv4Stack=true -jar slave.jar -jnlpUrl %s";
+
+ private Queue<Request> requests;
+ private Map<TaskID, Result> results;
+ private volatile MesosSchedulerDriver driver;
+ private final String jenkinsMaster;
+ private final String mesosMaster;
+
+ private static final Logger LOGGER = Logger.getLogger(JenkinsScheduler.class.getName());
+
+ public JenkinsScheduler(String jenkinsMaster, String mesosMaster) {
+ LOGGER.info("JenkinsScheduler instantiated with jenkins " + jenkinsMaster +
+ " and mesos " + mesosMaster);
+
+ this.jenkinsMaster = jenkinsMaster;
+ this.mesosMaster = mesosMaster;
+ requests = new LinkedList<Request>();
+ results = new HashMap<TaskID, Result>();
+ }
+
+ public synchronized void init() {
+ // Start the framework.
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ // Have Mesos fill in the current user.
+ FrameworkInfo framework = FrameworkInfo.newBuilder().setUser("")
+ .setName("Jenkins Framework").build();
+
+ driver = new MesosSchedulerDriver(JenkinsScheduler.this, framework, mesosMaster);
+
+ if (driver.run() != Status.DRIVER_STOPPED) {
+ LOGGER.severe("The mesos driver was aborted!");
+ }
+
+ driver = null;
+ }
+ }).start();
+ }
+
+ public synchronized void stop() {
+ driver.stop();
+ }
+
+ public synchronized boolean isRunning() {
+ return driver != null;
+ }
+
+ public void requestJenkinsSlave(Mesos.SlaveRequest request, Mesos.SlaveResult result) {
+ LOGGER.info("Enqueuing jenkins slave request " + request.executors);
+ requests.add(new Request(request, result));
+ }
+
+ /**
+ * @param slaveName the slave name in jenkins
+ * @return the jnlp url for the slave: http://[master]/computer/[slaveName]/slave-agent.jnlp
+ */
+ private String getJnlpUrl(String slaveName) {
+ return joinPaths(joinPaths(joinPaths(jenkinsMaster, "computer"), slaveName), "slave-agent.jnlp");
+ }
+
+ private static String joinPaths(String prefix, String suffix) {
+ if (prefix.endsWith("/")) prefix = prefix.substring(0, prefix.length()-1);
+ if (suffix.startsWith("/")) suffix = suffix.substring(1, suffix.length());
+
+ return prefix + '/' + suffix;
+ }
+
+ public void terminateJenkinsSlave(String name) {
+ LOGGER.info("Terminating jenkins slave " + name);
+
+ TaskID taskId = TaskID.newBuilder().setValue(name).build();
+
+ if (results.containsKey(taskId)) {
+ LOGGER.info("Killing mesos task " + taskId);
+ driver.killTask(taskId);
+ } else {
+ LOGGER.warning("Asked to kill unknown mesos task " + taskId);
+ }
+ }
+
+ @Override
+ public void registered(SchedulerDriver driver, FrameworkID frameworkId, MasterInfo masterInfo) {
+ LOGGER.info("Framework registered! ID = " + frameworkId.getValue());
+ }
+
+ @Override
+ public void reregistered(SchedulerDriver driver, MasterInfo masterInfo) {
+ LOGGER.info("Framework re-registered");
+ }
+
+ @Override
+ public void disconnected(SchedulerDriver driver) {
+ LOGGER.info("Framework disconnected!");
+ }
+
+ @Override
+ public void resourceOffers(SchedulerDriver driver, List<Offer> offers) {
+ LOGGER.info("Received offers " + offers.size());
+ for (Offer offer : offers) {
+ boolean matched = false;
+ for (Request request : requests) {
+ if (matches(offer, request)) {
+ matched = true;
+ LOGGER.info("Offer matched! Creating mesos task");
+ createMesosTask(offer, request);
+ requests.remove(request);
+ break;
+ }
+ }
+
+ if (!matched) {
+ driver.declineOffer(offer.getId());
+ }
+ }
+ }
+
+ private boolean matches(Offer offer, Request request) {
+ double cpus = -1;
+ double mem = -1;
+
+ for (Resource resource : offer.getResourcesList()) {
+ if (resource.getName().equals("cpus")) {
+ if (resource.getType().equals(Value.Type.SCALAR)) {
+ cpus = resource.getScalar().getValue();
+ } else {
+ LOGGER.severe("Cpus resource was not a scalar: " + resource.getType().toString());
+ }
+ } else if (resource.getName().equals("mem")) {
+ if (resource.getType().equals(Value.Type.SCALAR)) {
+ mem = resource.getScalar().getValue();
+ } else {
+ LOGGER.severe("Mem resource was not a scalar: " + resource.getType().toString());
+ }
+ } else if (resource.getName().equals("disk")) {
+ LOGGER.warning("Ignoring disk resources from offer");
+ } else if (resource.getName().equals("ports")) {
+ LOGGER.info("Ignoring ports resources from offer");
+ } else {
+ LOGGER.warning("Ignoring unknown resource type: " + resource.getName());
+ }
+ }
+
+ if (cpus < 0) LOGGER.severe("No cpus resource present");
+ if (mem < 0) LOGGER.severe("No mem resource present");
+
+ // Check for sufficient cpu and memory resources in the offer.
+ double requestedCpus = JENKINS_SLAVE_CPUS * request.request.executors;
+ double requestedMem = JENKINS_SLAVE_MEM + (request.request.executors * JENKINS_EXECUTOR_MEM);
+
+ if (requestedCpus <= cpus && requestedMem <= mem) {
+ return true;
+ } else {
+ LOGGER.info(
+ "Offer not sufficient for slave request:\n" +
+ offer.getResourcesList().toString() +
+ "\nRequested for Jenkins slave:\n" +
+ " cpus: " + requestedCpus + "\n" +
+ " mem: " + requestedMem);
+ return false;
+ }
+ }
+
+ private void createMesosTask(Offer offer, Request request) {
+ TaskID taskId = TaskID.newBuilder().setValue(request.request.slave.name).build();
+
+ LOGGER.info("Launching task " + taskId.getValue() + " with URI " +
+ joinPaths(jenkinsMaster, SLAVE_JAR_URI_SUFFIX));
+
+ TaskInfo task = TaskInfo
+ .newBuilder()
+ .setName("task " + taskId.getValue())
+ .setTaskId(taskId)
+ .setSlaveId(offer.getSlaveId())
+ .addResources(
+ Resource
+ .newBuilder()
+ .setName("cpus")
+ .setType(Value.Type.SCALAR)
+ .setScalar(
+ Value.Scalar.newBuilder()
+ .setValue(JENKINS_SLAVE_CPUS + request.request.executors).build()).build())
+ .addResources(
+ Resource
+ .newBuilder()
+ .setName("mem")
+ .setType(Value.Type.SCALAR)
+ .setScalar(
+ Value.Scalar
+ .newBuilder()
+ .setValue(
+ JENKINS_SLAVE_MEM + JENKINS_EXECUTOR_MEM * request.request.executors)
+ .build()).build())
+ .setCommand(
+ CommandInfo
+ .newBuilder()
+ .setValue(
+ String.format(SLAVE_COMMAND_FORMAT, JENKINS_SLAVE_MEM,
+ getJnlpUrl(request.request.slave.name)))
+ .addUris(
+ CommandInfo.URI.newBuilder().setValue(
+ joinPaths(jenkinsMaster, SLAVE_JAR_URI_SUFFIX)))).build();
+
+ List<TaskInfo> tasks = new ArrayList<TaskInfo>();
+ tasks.add(task);
+ Filters filters = Filters.newBuilder().setRefuseSeconds(1).build();
+ driver.launchTasks(offer.getId(), tasks, filters);
+
+ results.put(taskId, new Result(request.result, new Mesos.JenkinsSlave(offer.getSlaveId()
+ .getValue())));
+ }
+
+ @Override
+ public void offerRescinded(SchedulerDriver driver, OfferID offerId) {
+ LOGGER.info("Rescinded offer " + offerId);
+ }
+
+ @Override
+ public void statusUpdate(SchedulerDriver driver, TaskStatus status) {
+ TaskID taskId = status.getTaskId();
+ LOGGER.info("Status update: task " + taskId + " is in state " + status.getState());
+
+ if (!results.containsKey(taskId)) {
+ throw new IllegalStateException("Unknown taskId: " + taskId);
+ }
+
+ Result result = results.get(taskId);
+
+ switch (status.getState()) {
+ case TASK_STAGING:
+ case TASK_STARTING:
+ break;
+ case TASK_RUNNING:
+ result.result.running(result.slave);
+ break;
+ case TASK_FINISHED:
+ result.result.finished(result.slave);
+ break;
+ case TASK_FAILED:
+ case TASK_KILLED:
+ case TASK_LOST:
+ result.result.failed(result.slave);
+ break;
+ default:
+ throw new IllegalStateException("Invalid State: " + status.getState());
+ }
+ }
+
+ @Override
+ public void frameworkMessage(SchedulerDriver driver, ExecutorID executorId,
+ SlaveID slaveId, byte[] data) {
+ LOGGER.info("Received framework message from executor " + executorId
+ + " of slave " + slaveId);
+ }
+
+ @Override
+ public void slaveLost(SchedulerDriver driver, SlaveID slaveId) {
+ LOGGER.info("Slave " + slaveId + " lost!");
+ }
+
+ @Override
+ public void executorLost(SchedulerDriver driver, ExecutorID executorId,
+ SlaveID slaveId, int status) {
+ LOGGER.info("Executor " + executorId + " of slave " + slaveId + " lost!");
+ }
+
+ @Override
+ public void error(SchedulerDriver driver, String message) {
+ LOGGER.severe(message);
+ }
+
+ private class Result {
+ private final Mesos.SlaveResult result;
+ private final Mesos.JenkinsSlave slave;
+
+ private Result(Mesos.SlaveResult result, Mesos.JenkinsSlave slave) {
+ this.result = result;
+ this.slave = slave;
+ }
+ }
+
+ private class Request {
+ private final Mesos.SlaveRequest request;
+ private final Mesos.SlaveResult result;
+
+ public Request(Mesos.SlaveRequest request, Mesos.SlaveResult result) {
+ this.request = request;
+ this.result = result;
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-mesos/blob/4b5f3709/jenkins/src/main/java/org/jenkinsci/plugins/mesos/Mesos.java
----------------------------------------------------------------------
diff --git a/jenkins/src/main/java/org/jenkinsci/plugins/mesos/Mesos.java b/jenkins/src/main/java/org/jenkinsci/plugins/mesos/Mesos.java
new file mode 100644
index 0000000..f5234d4
--- /dev/null
+++ b/jenkins/src/main/java/org/jenkinsci/plugins/mesos/Mesos.java
@@ -0,0 +1,103 @@
+package org.jenkinsci.plugins.mesos;
+
+public abstract class Mesos {
+ private static MesosImpl mesos;
+
+ public static class JenkinsSlave {
+ String name;
+
+ public JenkinsSlave(String name) {
+ this.name = name;
+ }
+ }
+
+ public static class SlaveRequest {
+ JenkinsSlave slave;
+ int executors;
+
+ public SlaveRequest(JenkinsSlave _slave, int _executors) {
+ this.slave = _slave;
+ this.executors = _executors;
+ }
+ }
+
+ interface SlaveResult {
+ void running(JenkinsSlave slave);
+
+ void finished(JenkinsSlave slave);
+
+ void failed(JenkinsSlave slave);
+ }
+
+ abstract public void startScheduler(String jenkinsMaster, String mesosMaster);
+ abstract public boolean isSchedulerRunning();
+ abstract public void stopScheduler();
+
+ /**
+ * Starts a jenkins slave asynchronously in the mesos cluster.
+ *
+ * @param request
+ * slave request.
+ * @param result
+ * this callback will be called when the slave starts.
+ */
+ abstract public void startJenkinsSlave(SlaveRequest request, SlaveResult result);
+
+
+ /**
+ * Stop a jenkins slave asynchronously in the mesos cluster.
+ *
+ * @param name
+ * jenkins slave.
+ *
+ */
+ abstract public void stopJenkinsSlave(String name);
+
+ /**
+ * @return the mesos implementation instance
+ */
+ public static synchronized Mesos getInstance() {
+ if (mesos == null) {
+ mesos = new MesosImpl();
+ }
+ return mesos;
+ }
+
+ public static class MesosImpl extends Mesos {
+ @Override
+ public synchronized void startScheduler(String jenkinsMaster, String mesosMaster) {
+ stopScheduler();
+ scheduler = new JenkinsScheduler(jenkinsMaster, mesosMaster);
+ scheduler.init();
+ }
+
+ @Override
+ public synchronized boolean isSchedulerRunning() {
+ return scheduler != null && scheduler.isRunning();
+ }
+
+ @Override
+ public synchronized void stopScheduler() {
+ if (scheduler != null) {
+ scheduler.stop();
+ scheduler = null;
+ }
+ }
+
+ @Override
+ public synchronized void startJenkinsSlave(SlaveRequest request, SlaveResult result) {
+ if (scheduler != null) {
+ scheduler.requestJenkinsSlave(request, result);
+ }
+ }
+
+ @Override
+ public synchronized void stopJenkinsSlave(String name) {
+ if (scheduler != null) {
+ scheduler.terminateJenkinsSlave(name);
+ }
+ }
+
+ private JenkinsScheduler scheduler;
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-mesos/blob/4b5f3709/jenkins/src/main/java/org/jenkinsci/plugins/mesos/MesosCloud.java
----------------------------------------------------------------------
diff --git a/jenkins/src/main/java/org/jenkinsci/plugins/mesos/MesosCloud.java b/jenkins/src/main/java/org/jenkinsci/plugins/mesos/MesosCloud.java
new file mode 100644
index 0000000..340f75f
--- /dev/null
+++ b/jenkins/src/main/java/org/jenkinsci/plugins/mesos/MesosCloud.java
@@ -0,0 +1,208 @@
+package org.jenkinsci.plugins.mesos;
+
+import hudson.Extension;
+import hudson.model.Computer;
+import hudson.model.Descriptor;
+import hudson.model.Descriptor.FormException;
+import hudson.model.Hudson;
+import hudson.model.Label;
+import hudson.model.Node;
+import hudson.model.Node.Mode;
+import hudson.slaves.Cloud;
+import hudson.slaves.NodeProperty;
+import hudson.slaves.NodeProvisioner.PlannedNode;
+import hudson.util.FormValidation;
+
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.Callable;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.servlet.ServletException;
+
+import jenkins.model.Jenkins;
+import net.sf.json.JSONObject;
+
+import org.kohsuke.stapler.DataBoundConstructor;
+import org.kohsuke.stapler.QueryParameter;
+import org.kohsuke.stapler.StaplerRequest;
+import org.kohsuke.stapler.StaplerResponse;
+
+public class MesosCloud extends Cloud {
+
+ private String master;
+ private String description;
+
+ private static String staticMaster;
+
+ private static final Logger LOGGER = Logger.getLogger(MesosCloud.class.getName());
+
+ @DataBoundConstructor
+ public MesosCloud(String master, String description) {
+ super("MesosCloud");
+
+ // Restart the scheduler if the master has changed or a scheduler is not up.
+ if (!master.equals(staticMaster) || !Mesos.getInstance().isSchedulerRunning()) {
+ if (!master.equals(staticMaster)) {
+ LOGGER.info("Mesos master changed, restarting the scheduler");
+ } else {
+ LOGGER.info("Scheduler was down, restarting the scheduler");
+ }
+
+ Mesos.getInstance().stopScheduler();
+ Mesos.getInstance().startScheduler(Jenkins.getInstance().getRootUrl(), master);
+ } else {
+ LOGGER.info("Mesos master has not changed, leaving the scheduler running");
+ }
+
+ this.master = master;
+ this.description = description;
+
+ staticMaster = this.master;
+ }
+
+ @Override
+ public Collection<PlannedNode> provision(Label label, int excessWorkload) {
+ final int numExecutors = 1;
+ List<PlannedNode> list = new ArrayList<PlannedNode>();
+
+ try {
+ list.add(new PlannedNode(this.getDisplayName(), Computer.threadPoolForRemoting
+ .submit(new Callable<Node>() {
+ public Node call() throws Exception {
+ // TODO: record the output somewhere
+ MesosSlave s = doProvision(numExecutors);
+ Hudson.getInstance().addNode(s);
+ return s;
+ }
+ }), numExecutors));
+
+ } catch (Exception e) {
+ LOGGER.log(Level.WARNING, "Failed to create instances on Mesos", e);
+ return Collections.emptyList();
+ }
+
+ return list;
+ }
+
+ public void doProvision(StaplerRequest req, StaplerResponse rsp) throws ServletException,
+ IOException, FormException {
+ checkPermission(PROVISION);
+ LOGGER.log(Level.INFO, "Create a new node by user request");
+
+ try {
+ MesosSlave node = doProvision(1);
+ Hudson.getInstance().addNode(node);
+
+ rsp.sendRedirect2(req.getContextPath() + "/computer/" + node.getNodeName());
+ } catch (Exception e) {
+ sendError(e.getMessage(), req, rsp);
+ }
+ }
+
+ private MesosSlave doProvision(int numExecutors) throws Descriptor.FormException, IOException {
+ String name = "mesos-" + UUID.randomUUID().toString();
+ String nodeDescription = "mesos";
+ String remoteFS = "jenkins";
+ Mode mode = Mode.NORMAL;
+ String labelString = "mesos";
+ List<? extends NodeProperty<?>> nodeProperties = null;
+
+ return new MesosSlave(name, nodeDescription, remoteFS, numExecutors, mode, labelString,
+ nodeProperties, "1", "0");
+ }
+
+ @Override
+ public boolean canProvision(Label label) {
+ // Provisioning is simply creating a task for a jenkins slave.
+ // Therefore, we can always provision, however the framework may
+ // not have the resources necessary to start a task when it comes
+ // time to launch the slave.
+ return true;
+ }
+
+ public String getMaster() {
+ return this.master;
+ }
+
+ public void setMaster(String master) {
+ this.master = master;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ @Override
+ public DescriptorImpl getDescriptor() {
+ return (DescriptorImpl) super.getDescriptor();
+ }
+
+ public static MesosCloud get() {
+ return Hudson.getInstance().clouds.get(MesosCloud.class);
+ }
+
+ @Extension
+ public static class DescriptorImpl extends Descriptor<Cloud> {
+ private String master;
+ private String description;
+
+ @Override
+ public String getDisplayName() {
+ return "Mesos Cloud";
+ }
+
+ @Override
+ public boolean configure(StaplerRequest req, JSONObject o) throws FormException {
+ master = o.getString("master");
+ description = o.getString("description");
+ save();
+ return super.configure(req, o);
+ }
+
+ /**
+ * Test connection from configuration page.
+ */
+ public FormValidation doTestConnection(@QueryParameter String master) throws IOException,
+ ServletException {
+ master = master.trim();
+
+ if (master.equals("local")) {
+ return FormValidation.warning("'local' triggers a local mesos cluster");
+ }
+
+ if (master.startsWith("zk://")) {
+ return FormValidation.warning("Zookeeper paths can be used, but the connection cannot be " +
+ "tested prior to saving this page.");
+ }
+
+ try {
+ HttpURLConnection urlConn = (HttpURLConnection) new URL(master).openConnection();
+ urlConn.connect();
+ int code = urlConn.getResponseCode();
+ urlConn.disconnect();
+
+ if (code == 200) {
+ return FormValidation.ok("Connected to Mesos successfully");
+ } else {
+ return FormValidation.error("Status returned from url was " + code);
+ }
+ } catch (IOException e) {
+ LOGGER.log(Level.WARNING, "Failed to connect to Mesos " + master, e);
+ return FormValidation.error(e.getMessage());
+ }
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-mesos/blob/4b5f3709/jenkins/src/main/java/org/jenkinsci/plugins/mesos/MesosComputer.java
----------------------------------------------------------------------
diff --git a/jenkins/src/main/java/org/jenkinsci/plugins/mesos/MesosComputer.java b/jenkins/src/main/java/org/jenkinsci/plugins/mesos/MesosComputer.java
new file mode 100644
index 0000000..1196b9d
--- /dev/null
+++ b/jenkins/src/main/java/org/jenkinsci/plugins/mesos/MesosComputer.java
@@ -0,0 +1,28 @@
+package org.jenkinsci.plugins.mesos;
+
+import hudson.model.Slave;
+import hudson.slaves.SlaveComputer;
+
+import java.io.IOException;
+
+import org.kohsuke.stapler.HttpRedirect;
+import org.kohsuke.stapler.HttpResponse;
+
+public class MesosComputer extends SlaveComputer {
+ public MesosComputer(Slave slave) {
+ super(slave);
+ }
+
+ @Override
+ public MesosSlave getNode() {
+ return (MesosSlave) super.getNode();
+ }
+
+ @Override
+ public HttpResponse doDoDelete() throws IOException {
+ checkPermission(DELETE);
+ if (getNode() != null)
+ getNode().terminate();
+ return new HttpRedirect("..");
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-mesos/blob/4b5f3709/jenkins/src/main/java/org/jenkinsci/plugins/mesos/MesosComputerLauncher.java
----------------------------------------------------------------------
diff --git a/jenkins/src/main/java/org/jenkinsci/plugins/mesos/MesosComputerLauncher.java b/jenkins/src/main/java/org/jenkinsci/plugins/mesos/MesosComputerLauncher.java
new file mode 100644
index 0000000..dc3bf8e
--- /dev/null
+++ b/jenkins/src/main/java/org/jenkinsci/plugins/mesos/MesosComputerLauncher.java
@@ -0,0 +1,99 @@
+package org.jenkinsci.plugins.mesos;
+
+import hudson.model.TaskListener;
+import hudson.slaves.ComputerLauncher;
+import hudson.slaves.SlaveComputer;
+
+import java.io.PrintStream;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.logging.Logger;
+
+import org.jenkinsci.plugins.mesos.Mesos.JenkinsSlave;
+
+public class MesosComputerLauncher extends ComputerLauncher {
+
+ enum State { INIT, RUNNING, FAILURE }
+
+ private static final Logger LOGGER = Logger.getLogger(MesosComputerLauncher.class.getName());
+
+ public MesosComputerLauncher(String _name) {
+ super();
+ LOGGER.info("Constructing MesosComputerLauncher");
+ this.state = State.INIT;
+ this.name = _name;
+ }
+
+ /**
+ * Launches a mesos task that starts the jenkins slave.
+ *
+ * NOTE: This has to be a blocking call:
+ *
+ * @see hudson.slaves.ComputerLauncher#launch(hudson.slaves.SlaveComputer,
+ * hudson.model.TaskListener)
+ */
+ @Override
+ public void launch(SlaveComputer _computer, TaskListener listener) throws InterruptedException {
+ LOGGER.info("Launching slave computer " + name);
+
+ MesosComputer computer = (MesosComputer) _computer;
+ PrintStream logger = listener.getLogger();
+
+ // Get a handle to mesos.
+ Mesos mesos = Mesos.getInstance();
+
+ // Create the request.
+ int numExecutors = computer.getNode().getNumExecutors();
+ Mesos.SlaveRequest request = new Mesos.SlaveRequest(new JenkinsSlave(name), numExecutors);
+
+ // Launch the jenkins slave.
+ final Lock lock = new ReentrantLock();
+ final CountDownLatch latch = new CountDownLatch(1);
+
+ logger.println("Starting mesos slave " + name);
+ LOGGER.info("Sending a request to start jenkins slave " + name);
+ mesos.startJenkinsSlave(request, new Mesos.SlaveResult() {
+ @Override
+ public void running(JenkinsSlave slave) {
+ state = State.RUNNING;
+ latch.countDown();
+ }
+
+ @Override
+ public void finished(JenkinsSlave slave) {
+ state = State.FAILURE;
+ latch.countDown();
+ }
+
+ @Override
+ public void failed(JenkinsSlave slave) {
+ state = State.FAILURE;
+ latch.countDown();
+ }
+ });
+
+ // Block until we know the status of the slave.
+ // TODO(vinod): What happens if the callback is called again!
+ latch.await();
+
+ if (state == State.RUNNING) {
+ logger.println("Successfully launched slave" + name);
+ }
+
+ LOGGER.info("Finished launching slave computer " + name);
+ }
+
+ /**
+ * Kills the mesos task that corresponds to the Jenkins slave, asynchronously.
+ */
+ public void terminate() {
+ // Get a handle to mesos.
+ Mesos mesos = Mesos.getInstance();
+
+ mesos.stopJenkinsSlave(name);
+ }
+
+ private volatile State state;
+ private final String name;
+}
http://git-wip-us.apache.org/repos/asf/incubator-mesos/blob/4b5f3709/jenkins/src/main/java/org/jenkinsci/plugins/mesos/MesosRetentionStrategy.java
----------------------------------------------------------------------
diff --git a/jenkins/src/main/java/org/jenkinsci/plugins/mesos/MesosRetentionStrategy.java b/jenkins/src/main/java/org/jenkinsci/plugins/mesos/MesosRetentionStrategy.java
new file mode 100644
index 0000000..e02d3c7
--- /dev/null
+++ b/jenkins/src/main/java/org/jenkinsci/plugins/mesos/MesosRetentionStrategy.java
@@ -0,0 +1,88 @@
+package org.jenkinsci.plugins.mesos;
+
+import java.util.logging.Logger;
+
+import org.kohsuke.stapler.DataBoundConstructor;
+
+import hudson.model.Descriptor;
+import hudson.slaves.RetentionStrategy;
+import hudson.util.TimeUnit2;
+
+/**
+ * This is basically a copy of the EC2 plugin's retention strategy.
+ * https://github.com/jenkinsci/ec2-plugin/blob/master/src/main/java/hudson
+ * /plugins/ec2/EC2RetentionStrategy.java
+ */
+public class MesosRetentionStrategy extends RetentionStrategy<MesosComputer> {
+
+ /**
+ * Number of minutes of idleness before an instance should be terminated. A
+ * value of zero indicates that the instance should never be automatically
+ * terminated.
+ */
+ public final int idleTerminationMinutes;
+
+ private static final Logger LOGGER = Logger
+ .getLogger(MesosRetentionStrategy.class.getName());
+
+ public static boolean disabled = Boolean
+ .getBoolean(MesosRetentionStrategy.class.getName() + ".disabled");
+
+ @DataBoundConstructor
+ public MesosRetentionStrategy(String idleTerminationMinutes) {
+ // Since a Mesos node is fast to start/stop we default this value to 3 mins.
+ int value = 3;
+ if (idleTerminationMinutes != null && idleTerminationMinutes.trim() != "") {
+ try {
+ value = Integer.parseInt(idleTerminationMinutes);
+ } catch (NumberFormatException nfe) {
+ LOGGER.info("Malformed default idleTermination value: "
+ + idleTerminationMinutes);
+ }
+ }
+ this.idleTerminationMinutes = value;
+ }
+
+ @Override
+ public synchronized long check(MesosComputer c) {
+ // If we've been told never to terminate, then we're done.
+ if (idleTerminationMinutes == 0)
+ return 1;
+
+ final long idleMilliseconds1 = System.currentTimeMillis()
+ - c.getIdleStartMilliseconds();
+
+ System.out.println(c.getName() + " idle: " + idleMilliseconds1);
+
+ if (c.isIdle() && c.isOnline() && !disabled) {
+ final long idleMilliseconds = System.currentTimeMillis()
+ - c.getIdleStartMilliseconds();
+
+ if (idleMilliseconds > TimeUnit2.MINUTES.toMillis(idleTerminationMinutes)) {
+ LOGGER.info("Idle timeout after " + idleTerminationMinutes + "mins: "
+ + c.getName());
+ c.getNode().idleTimeout();
+ }
+ }
+ return 1;
+ }
+
+ /**
+ * Try to connect to it ASAP to launch the slave agent.
+ */
+ @Override
+ public void start(MesosComputer c) {
+ c.connect(false);
+ }
+
+ /**
+ * No registration since this retention strategy is used only for Mesos nodes
+ * that we provision automatically.
+ */
+ public static class DescriptorImpl extends Descriptor<RetentionStrategy<?>> {
+ @Override
+ public String getDisplayName() {
+ return "MESOS";
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-mesos/blob/4b5f3709/jenkins/src/main/java/org/jenkinsci/plugins/mesos/MesosSlave.java
----------------------------------------------------------------------
diff --git a/jenkins/src/main/java/org/jenkinsci/plugins/mesos/MesosSlave.java b/jenkins/src/main/java/org/jenkinsci/plugins/mesos/MesosSlave.java
new file mode 100644
index 0000000..eed0ba3
--- /dev/null
+++ b/jenkins/src/main/java/org/jenkinsci/plugins/mesos/MesosSlave.java
@@ -0,0 +1,92 @@
+package org.jenkinsci.plugins.mesos;
+
+import hudson.model.Computer;
+import hudson.model.Descriptor.FormException;
+import hudson.model.Hudson;
+import hudson.model.Slave;
+import hudson.slaves.NodeProperty;
+import hudson.slaves.ComputerLauncher;
+import hudson.slaves.RetentionStrategy;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.kohsuke.stapler.DataBoundConstructor;
+
+public class MesosSlave extends Slave {
+
+ private static final Logger LOGGER = Logger.getLogger(MesosSlave.class
+ .getName());
+
+ private final String idleTerminationMinutes;
+ private final String limitedBuildCount;
+ private final String clusterId;
+
+ @DataBoundConstructor
+ public MesosSlave(String name, String description, String remoteFS,
+ int numExecutors, Mode mode, String labelString,
+ List<? extends NodeProperty<?>> nodeProperties,
+ String idleTerminationMinutes, String limitedBuildCount)
+ throws FormException, IOException {
+
+ this(name, description, remoteFS, numExecutors, Mode.NORMAL, labelString,
+ new MesosComputerLauncher(name), null, Collections
+ .<NodeProperty<?>> emptyList(), idleTerminationMinutes,
+ limitedBuildCount);
+ }
+
+ public MesosSlave(String name, String nodeDescription, String remoteFS,
+ int numExecutors, Mode mode, String labelString,
+ ComputerLauncher launcher,
+ RetentionStrategy<MesosComputer> retentionStrategy,
+ List<? extends NodeProperty<?>> nodeProperties,
+ String idleTerminationMinutes, String limitedBuildCount)
+ throws FormException, IOException {
+
+ super(name, nodeDescription, remoteFS, numExecutors, Mode.NORMAL,
+ labelString, launcher, new MesosRetentionStrategy(
+ idleTerminationMinutes), Collections.<NodeProperty<?>> emptyList());
+
+ LOGGER.info("Constructing Mesos slave");
+
+ this.idleTerminationMinutes = idleTerminationMinutes;
+ this.limitedBuildCount = limitedBuildCount;
+ this.clusterId = null;
+ }
+
+ public void terminate() {
+ LOGGER.info("Terminating slave " + getNodeName());
+ try {
+ // Remove the node from hudson.
+ Hudson.getInstance().removeNode(this);
+
+ ComputerLauncher launcher = getLauncher();
+
+ // If this is a mesos computer launcher, terminate the launcher.
+ if (launcher instanceof MesosComputerLauncher) {
+ ((MesosComputerLauncher) launcher).terminate();
+ }
+ } catch (IOException e) {
+ LOGGER.log(Level.WARNING, "Failed to terminate Mesos instance: "
+ + getInstanceId(), e);
+ }
+ }
+
+ private String getInstanceId() {
+ return getNodeName();
+ }
+
+ public void idleTimeout() {
+ LOGGER.info("Mesos instance idle time expired: " + getInstanceId()
+ + ", terminate now");
+ terminate();
+ }
+
+ @Override
+ public Computer createComputer() {
+ return new MesosComputer(this);
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-mesos/blob/4b5f3709/jenkins/src/main/java/org/jenkinsci/plugins/mesos/TaskTemplate.java
----------------------------------------------------------------------
diff --git a/jenkins/src/main/java/org/jenkinsci/plugins/mesos/TaskTemplate.java b/jenkins/src/main/java/org/jenkinsci/plugins/mesos/TaskTemplate.java
new file mode 100644
index 0000000..ff42b9f
--- /dev/null
+++ b/jenkins/src/main/java/org/jenkinsci/plugins/mesos/TaskTemplate.java
@@ -0,0 +1,39 @@
+package org.jenkinsci.plugins.mesos;
+
+import org.kohsuke.stapler.DataBoundConstructor;
+
+import hudson.Extension;
+import hudson.model.Describable;
+import hudson.model.Descriptor;
+import hudson.model.Hudson;
+
+public class TaskTemplate implements Describable<TaskTemplate> {
+ public final String description;
+ public final String label;
+ public final String idleTerminationMinutes;
+ public final String numExecutors;
+ public final String executorMem;
+ public final String slaveJarMem;
+
+ @DataBoundConstructor
+ public TaskTemplate(String description, String label, String idleTerminationMinutes, String numExecutors, String executorMem, String slaveJarMem) {
+ this.description = description;
+ this.label = label;
+ this.idleTerminationMinutes = idleTerminationMinutes;
+ this.numExecutors = numExecutors;
+ this.executorMem = executorMem;
+ this.slaveJarMem = slaveJarMem;
+ }
+
+ @Override
+ public Descriptor<TaskTemplate> getDescriptor() {
+ return Hudson.getInstance().getDescriptor(getClass());
+ }
+
+ @Extension
+ public static final class DescriptorImpl extends Descriptor<TaskTemplate> {
+ @Override public String getDisplayName() {
+ return null;
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-mesos/blob/4b5f3709/jenkins/src/main/resources/index.jelly
----------------------------------------------------------------------
diff --git a/jenkins/src/main/resources/index.jelly b/jenkins/src/main/resources/index.jelly
new file mode 100644
index 0000000..62343d2
--- /dev/null
+++ b/jenkins/src/main/resources/index.jelly
@@ -0,0 +1,7 @@
+<!--
+ This view is used to render the installed plugins page.
+-->
+<div>
+ This plugin can be used to connect Jenkins master to a Mesos cluster
+ and dynamically launch Jenkins slaves based on the work load.
+</div>
http://git-wip-us.apache.org/repos/asf/incubator-mesos/blob/4b5f3709/jenkins/src/main/resources/org/jenkinsci/plugins/mesos/MesosCloud/computerSet.jelly
----------------------------------------------------------------------
diff --git a/jenkins/src/main/resources/org/jenkinsci/plugins/mesos/MesosCloud/computerSet.jelly b/jenkins/src/main/resources/org/jenkinsci/plugins/mesos/MesosCloud/computerSet.jelly
new file mode 100644
index 0000000..9d1ec6c
--- /dev/null
+++ b/jenkins/src/main/resources/org/jenkinsci/plugins/mesos/MesosCloud/computerSet.jelly
@@ -0,0 +1,12 @@
+<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:if test="${it.hasPermission(it.PROVISION)}">
+ <tr>
+ <td />
+ <td colspan="${monitors.size()+1}">
+ <f:form action="${rootURL}/cloud/${it.name}/provision" method="post" name="provision">
+ <input type="submit" class="mesos-provision-button" value="${%Provision via Mesos}" />
+ </f:form>
+ </td>
+ </tr>
+ </j:if>
+</j:jelly>
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-mesos/blob/4b5f3709/jenkins/src/main/resources/org/jenkinsci/plugins/mesos/MesosCloud/config.jelly
----------------------------------------------------------------------
diff --git a/jenkins/src/main/resources/org/jenkinsci/plugins/mesos/MesosCloud/config.jelly b/jenkins/src/main/resources/org/jenkinsci/plugins/mesos/MesosCloud/config.jelly
new file mode 100644
index 0000000..a2e754e
--- /dev/null
+++ b/jenkins/src/main/resources/org/jenkinsci/plugins/mesos/MesosCloud/config.jelly
@@ -0,0 +1,12 @@
+<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:entry title="${%Mesos Master [hostname:port]}" help="help-master.html">
+ <f:textbox field="master" />
+ </f:entry>
+
+ <f:entry title="${%Description}">
+ <f:textbox field="description" />
+ </f:entry>
+
+ <f:validateButton title="${%Test Connection}" progress="${%Testing...}" method="testConnection" with="master"/>
+</j:jelly>
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-mesos/blob/4b5f3709/jenkins/src/main/resources/org/jenkinsci/plugins/mesos/MesosCloud/help-master.html
----------------------------------------------------------------------
diff --git a/jenkins/src/main/resources/org/jenkinsci/plugins/mesos/MesosCloud/help-master.html b/jenkins/src/main/resources/org/jenkinsci/plugins/mesos/MesosCloud/help-master.html
new file mode 100644
index 0000000..dc218be
--- /dev/null
+++ b/jenkins/src/main/resources/org/jenkinsci/plugins/mesos/MesosCloud/help-master.html
@@ -0,0 +1,3 @@
+<div>
+ Specifies the hostname:port of the Mesos master.
+</div>
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-mesos/blob/4b5f3709/jenkins/src/main/resources/org/jenkinsci/plugins/mesos/MesosSlave/configure-entries.jelly
----------------------------------------------------------------------
diff --git a/jenkins/src/main/resources/org/jenkinsci/plugins/mesos/MesosSlave/configure-entries.jelly b/jenkins/src/main/resources/org/jenkinsci/plugins/mesos/MesosSlave/configure-entries.jelly
new file mode 100644
index 0000000..24448a8
--- /dev/null
+++ b/jenkins/src/main/resources/org/jenkinsci/plugins/mesos/MesosSlave/configure-entries.jelly
@@ -0,0 +1,29 @@
+<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:entry title="${%Mesos Cluster ID}" field="mesosClusterID">
+ <f:readOnlyTextbox />
+ </f:entry>
+
+ <f:entry title="${%Description}" >
+ <f:textbox field="description" />
+ </f:entry>
+
+ <f:entry title="${%# of executors}" field="numExecutors">
+ <f:textbox />
+ </f:entry>
+
+ <f:entry title="${%Labels}" field="labelString">
+ <f:textbox />
+ </f:entry>
+
+ <f:entry title="${%Idle termination time}" field="idleTerminationMinutes">
+ <f:textbox />
+ </f:entry>
+
+ <f:entry title="${%Terminate After Limited Builds}" description="${%Number of builds to allow before terminate the slave; use 0 for NEVER.}">
+
+ <f:textbox default="0" field="limitedBuildCount"/>
+
+ </f:entry>
+
+</j:jelly>
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-mesos/blob/4b5f3709/jenkins/src/main/resources/org/jenkinsci/plugins/mesos/TaskTemplate/config.jelly
----------------------------------------------------------------------
diff --git a/jenkins/src/main/resources/org/jenkinsci/plugins/mesos/TaskTemplate/config.jelly b/jenkins/src/main/resources/org/jenkinsci/plugins/mesos/TaskTemplate/config.jelly
new file mode 100644
index 0000000..e131673
--- /dev/null
+++ b/jenkins/src/main/resources/org/jenkinsci/plugins/mesos/TaskTemplate/config.jelly
@@ -0,0 +1,33 @@
+<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" >
+ <table width="100%">
+
+ <f:entry title="Description" field="description">
+ <f:textbox />
+ </f:entry>
+
+ <f:entry title="Label" field="label">
+ <f:textbox />
+ </f:entry>
+
+ <f:advanced>
+
+ <f:entry title="Idle Termination Time (min)" field="idleTerminationMinutes">
+ <f:textbox default="30" />
+ </f:entry>
+
+ <f:entry title="Number of Jenkins Executors Per Mesos Task" field="numExecutors">
+ <f:textbox />
+ </f:entry>
+
+ <f:entry title="Executor Mem (MB)" field="executorMem">
+ <f:textbox />
+ </f:entry>
+
+ <f:entry title="Slave JVM Max Heap (MB)" field="slaveJarMem">
+ <f:textbox/>
+ </f:entry>
+
+ </f:advanced>
+
+ </table>
+</j:jelly>