Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support labels to run build on Mesos. #30

Merged
merged 17 commits into from
Apr 24, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .envrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export JAVA_HOME=`/usr/libexec/java_home -v 1.8`
23 changes: 15 additions & 8 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ plugins {
id 'idea'
id 'java-library'
id "com.diffplug.gradle.spotless" version "3.19.0"
id "org.jenkins-ci.jpi" version "0.30.0-SNAPSHOT"
id "org.jenkins-ci.jpi" version "0.31.0"
}

repositories {
Expand All @@ -11,6 +11,8 @@ repositories {

ext {
usiBranch = 'master'
scalaVersion = '2.12'
akkaVersion = '2.5.19'
}

dependencies {
Expand All @@ -20,7 +22,7 @@ dependencies {
api ('com.mesosphere.usi:core') { version { branch = usiBranch } }
api ('com.mesosphere.usi:core-models') { version { branch = usiBranch } }
api ('com.mesosphere.usi:mesos-client') { version { branch = usiBranch } }
api group: 'org.slf4j', name: 'slf4j-api', version: '1.7.26'
api group: 'org.slf4j', name: 'slf4j-api', version: '1.7.25'
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why the downgrade here? is that version only supported in java 11?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed the plugin to use its class loader first. This resulted in a version conflict with Jenkins. So I downgraded it. I since dropped it.


// Test dependencies
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.3.1'
Expand All @@ -35,18 +37,23 @@ test {

group = "org.jenkins-ci.plugins"
description = "Allows the dynamic launch Jenkins agent on a Mesos cluster, depending on workload"
sourceCompatibility = 1.11
targetCompatibility = 1.11
sourceCompatibility = 1.8
targetCompatibility = 1.8

jenkinsPlugin {
coreVersion = "2.155"
coreVersion = "2.164"
displayName = "Mesos Cloud"
url = "https://wiki.jenkins-ci.org/display/JENKINS/Mesos+Plugin"
gitHubUrl = "https://github.com/mesosphere/mesos-plugin/"
shortName = "mesos"

// The developers section is optional, and corresponds to the POM developers section.
developers {
developer {
id 'jeschkies'
name 'Karsten Jeschkies'
email 'kjeschkies@mesosphere.com'
}
}
}

Expand All @@ -58,8 +65,8 @@ spotless {

idea {
project {
jdkName = '1.11'
languageLevel = '1.11'
jdkName = '1.8'
languageLevel = '1.8'
vcs = 'Git'
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package org.jenkinsci.plugins.mesos;

import hudson.Extension;
import hudson.model.AbstractDescribableImpl;
import hudson.model.Descriptor;
import hudson.model.Label;
import hudson.model.Node;
import hudson.model.labels.LabelAtom;
import java.util.Set;
import java.util.UUID;
import org.kohsuke.stapler.DataBoundConstructor;

/** This is the Mesos agent pod spec config set by a user. */
public class MesosAgentSpecTemplate extends AbstractDescribableImpl<MesosAgentSpecTemplate> {

private final String label;
private final Set<LabelAtom> labelSet;

private final Node.Mode mode;

@DataBoundConstructor
public MesosAgentSpecTemplate(String label, Node.Mode mode) {
this.label = label;
this.labelSet = Label.parse(label);
this.mode = mode;
}

@Extension
public static final class DescriptorImpl extends Descriptor<MesosAgentSpecTemplate> {

public DescriptorImpl() {
load();
}
}

// Getters

public String getLabel() {
return this.label;
}

public Set<LabelAtom> getLabelSet() {
return this.labelSet;
}

public Node.Mode getMode() {
return this.mode;
}

public String getName() {
return String.format("jenkins-agent-%s-%s", this.label, UUID.randomUUID().toString());
}

public double getCpu() {
return 0.1;
}

public int getMemory() {
return 32;
}
}
110 changes: 69 additions & 41 deletions src/main/java/org/jenkinsci/plugins/mesos/MesosApi.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import com.mesosphere.mesos.conf.MesosClientSettings;
import com.mesosphere.usi.core.japi.Scheduler;
import com.mesosphere.usi.core.models.*;
import com.thoughtworks.xstream.annotations.XStreamOmitField;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
import com.typesafe.config.ConfigValueFactory;
Expand All @@ -21,6 +22,7 @@
import java.util.concurrent.CompletionStage;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import jenkins.model.Jenkins;
import org.apache.mesos.v1.Protos;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -31,49 +33,59 @@ public class MesosApi {

private static final Logger logger = LoggerFactory.getLogger(MesosApi.class);

private final String slavesUser;
private final String frameworkName;
private final String role;
private final String agentUser;
private final String frameworkId;
private final URL jenkinsUrl;
private final Protos.FrameworkID frameworkId;
private final MesosClientSettings clientSettings;
private final MesosClient client;

private final SourceQueueWithComplete<SpecUpdated> updates;
private final ConcurrentHashMap<PodId, MesosSlave> stateMap;
@XStreamOmitField private final MesosClient client;

private final ActorSystem system;
private final ActorMaterializer materializer;
private final ExecutionContext context;
@XStreamOmitField private final SourceQueueWithComplete<SpecUpdated> updates;

private final ConcurrentHashMap<PodId, MesosJenkinsAgent> stateMap;

@XStreamOmitField private final ActorSystem system;

@XStreamOmitField private final ActorMaterializer materializer;

@XStreamOmitField private final ExecutionContext context;

/**
* Establishes a connection to Mesos and provides a simple interface to start and stop {@link
* MesosSlave} instances.
* MesosJenkinsAgent} instances.
*
* @param masterUrl The Mesos master address to connect to.
* @param jenkinsUrl The Jenkins address to fetch the agent jar from.
* @param user The username used for executing Mesos tasks.
* @param agentUser The username used for executing Mesos tasks.
* @param frameworkName The name of the framework the Mesos client should register as.
* @param role The Mesos role to assume.
* @throws InterruptedException
* @throws ExecutionException
*/
public MesosApi(String masterUrl, URL jenkinsUrl, String user, String frameworkName)
public MesosApi(
URL masterUrl, URL jenkinsUrl, String agentUser, String frameworkName, String role)
throws InterruptedException, ExecutionException {
this.frameworkName = frameworkName;
this.frameworkId =
Protos.FrameworkID.newBuilder().setValue(UUID.randomUUID().toString()).build();
this.slavesUser = user;
this.frameworkId = UUID.randomUUID().toString();
this.role = role;
this.agentUser = agentUser;
this.jenkinsUrl = jenkinsUrl;

Config conf =
ConfigFactory.load()
.getConfig("mesos-client")
.withValue("master-url", ConfigValueFactory.fromAnyRef(masterUrl));
this.clientSettings = MesosClientSettings.fromConfig(conf);
system = ActorSystem.create("mesos-scheduler");
ClassLoader classLoader = Jenkins.getInstanceOrNull().pluginManager.uberClassLoader;

Config conf = ConfigFactory.load(classLoader);
Config clientConf =
conf.getConfig("mesos-client")
.withValue("master-url", ConfigValueFactory.fromAnyRef(masterUrl.toString()));

logger.info("Config: {}", conf);
MesosClientSettings clientSettings = MesosClientSettings.fromConfig(clientConf);
system = ActorSystem.create("mesos-scheduler", conf, classLoader);
context = system.dispatcher();
materializer = ActorMaterializer.create(system);

client = connectClient().get();
client = connectClient(clientSettings).get();

stateMap = new ConcurrentHashMap<>();

Expand All @@ -96,7 +108,7 @@ private CompletableFuture<SourceQueueWithComplete<SpecUpdated>> runScheduler(
.thenApply(
builder -> {
// We create a SourceQueue and assume that the very first item is a spec snapshot.
var queue =
SourceQueueWithComplete<SpecUpdated> queue =
Source.<SpecUpdated>queue(256, OverflowStrategy.fail())
.via(builder.getFlow())
.toMat(Sink.foreach(this::updateState), Keep.left())
Expand All @@ -110,10 +122,10 @@ private CompletableFuture<SourceQueueWithComplete<SpecUpdated>> runScheduler(
* Enqueue spec for a Jenkins event, passing a non-null existing podId will trigger a kill for
* that pod
*
* @return a {@link MesosSlave} once it's queued for running.
* @return a {@link MesosJenkinsAgent} once it's queued for running.
*/
public CompletionStage<Void> killAgent(String id) throws Exception {
PodSpec spec = stateMap.get(new PodId(id)).getPodSpec(0.1, 32, Goal.Terminal$.MODULE$);
PodSpec spec = stateMap.get(new PodId(id)).getPodSpec(Goal.Terminal$.MODULE$);
SpecUpdated update = new PodSpecUpdated(spec.id(), Option.apply(spec));
return updates.offer(update).thenRun(() -> {});
}
Expand All @@ -122,30 +134,35 @@ public CompletionStage<Void> killAgent(String id) throws Exception {
* Enqueue spec for a Jenkins event, passing a non-null existing podId will trigger a kill for
* that pod
*
* @return a {@link MesosSlave} once it's queued for running.
* @return a {@link MesosJenkinsAgent} once it's queued for running.
*/
public CompletionStage<MesosSlave> enqueueAgent(MesosCloud cloud, double cpu, int mem)
public CompletionStage<MesosJenkinsAgent> enqueueAgent(
MesosCloud cloud, String name, MesosAgentSpecTemplate spec)
throws IOException, FormException, URISyntaxException {

var name = String.format("jenkins-test-%s", UUID.randomUUID().toString());
MesosSlave mesosSlave =
new MesosSlave(cloud, name, "Mesos Jenkins Slave", jenkinsUrl, "label", List.of());
PodSpec spec = mesosSlave.getPodSpec(cpu, mem, Goal.Running$.MODULE$);
SpecUpdated update = new PodSpecUpdated(spec.id(), Option.apply(spec));
MesosJenkinsAgent mesosJenkinsAgent =
new MesosJenkinsAgent(
cloud, name, spec, "Mesos Jenkins Slave", jenkinsUrl, Collections.emptyList());
PodSpec podSpec = mesosJenkinsAgent.getPodSpec(Goal.Running$.MODULE$);
SpecUpdated update = new PodSpecUpdated(podSpec.id(), Option.apply(podSpec));

stateMap.put(spec.id(), mesosSlave);
stateMap.put(podSpec.id(), mesosJenkinsAgent);
// async add agent to queue
return updates.offer(update).thenApply(result -> mesosSlave); // TODO: handle QueueOfferResult.
return updates
.offer(update)
.thenApply(result -> mesosJenkinsAgent); // TODO: handle QueueOfferResult.
}

/** Establish a connection to Mesos via the v1 client. */
private CompletableFuture<MesosClient> connectClient() {
private CompletableFuture<MesosClient> connectClient(MesosClientSettings clientSettings) {
Protos.FrameworkID frameworkId =
Protos.FrameworkID.newBuilder().setValue(this.frameworkId).build();
Protos.FrameworkInfo frameworkInfo =
Protos.FrameworkInfo.newBuilder()
.setUser(slavesUser)
.setName(frameworkName)
.setUser(this.agentUser)
.setName(this.frameworkName)
.setId(frameworkId)
.addRoles("test")
.addRoles(role)
.addCapabilities(
Protos.FrameworkInfo.Capability.newBuilder()
.setType(Protos.FrameworkInfo.Capability.Type.MULTI_ROLE))
Expand All @@ -166,13 +183,13 @@ public ActorMaterializer getMaterializer() {
* Callback for USI to process state events.
*
* <p>This method will filter out {@link PodStatusUpdated} and pass them on to their {@link
* MesosSlave}. It should be threadsafe.
* MesosJenkinsAgent}. It should be threadsafe.
*
* @param event The {@link PodStatusUpdated} for a USI pod.
*/
public void updateState(StateEvent event) {
if (event instanceof PodStatusUpdated) {
var podStateEvent = (PodStatusUpdated) event;
PodStatusUpdated podStateEvent = (PodStatusUpdated) event;
logger.info("Got status update for pod {}", podStateEvent.id().value());
stateMap.computeIfPresent(
podStateEvent.id(),
Expand All @@ -181,6 +198,17 @@ public void updateState(StateEvent event) {
return slave;
});
}
// TODO: kill pod if unknown.
}

// Getters

/** @return the name of the registered Mesos framework. */
public String getFrameworkName() {
return this.frameworkName;
}

/** @return the current state map. */
public Map<PodId, MesosJenkinsAgent> getState() {
return Collections.unmodifiableMap(this.stateMap);
}
}