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>