From c4d7f63e4958d1c574b955d6cb941e90c8269517 Mon Sep 17 00:00:00 2001 From: Frank Scholten Date: Fri, 27 May 2016 17:55:27 +0200 Subject: [PATCH 1/5] #456 Added initial api module --- api/Dockerfile | 4 + api/build.gradle | 127 ++++++++++++++++++ .../minimesos/api/ClusterStartedResponse.java | 10 ++ .../containersol/minimesos/api/JsonUtils.java | 15 +++ .../com/containersol/minimesos/api/Main.java | 50 +++++++ api/src/main/resources/logback.xml | 14 ++ settings.gradle | 1 + 7 files changed, 221 insertions(+) create mode 100644 api/Dockerfile create mode 100644 api/build.gradle create mode 100644 api/src/main/java/com/containersol/minimesos/api/ClusterStartedResponse.java create mode 100644 api/src/main/java/com/containersol/minimesos/api/JsonUtils.java create mode 100644 api/src/main/java/com/containersol/minimesos/api/Main.java create mode 100644 api/src/main/resources/logback.xml diff --git a/api/Dockerfile b/api/Dockerfile new file mode 100644 index 00000000..80988949 --- /dev/null +++ b/api/Dockerfile @@ -0,0 +1,4 @@ +FROM containersol/alpine3.3-java8-jre:v1 +MAINTAINER Container Solutions BV + +ADD minimesos-api.jar /usr/local/share/minimesos/minimesos-api.jar diff --git a/api/build.gradle b/api/build.gradle new file mode 100644 index 00000000..f1fa1032 --- /dev/null +++ b/api/build.gradle @@ -0,0 +1,127 @@ +import com.bmuschko.gradle.docker.tasks.image.DockerBuildImage +import com.bmuschko.gradle.docker.tasks.image.DockerPushImage +import com.bmuschko.gradle.docker.tasks.image.DockerTagImage + +apply plugin: 'java' +apply plugin: 'application' +apply plugin: 'com.bmuschko.docker-remote-api' + +repositories { + mavenLocal() + mavenCentral() + maven { + url "https://jitpack.io" + } +} + +group = "com.containersol.minimesos" + +dependencies { + compile 'com.beust:jcommander:1.48' + compile 'org.slf4j:slf4j-api:1.7.12' + compile 'com.sparkjava:spark-core:2.5' + compile 'com.google.code.gson:gson:2.3.1' + + compile project(':minimesos') + + testCompile 'junit:junit:4.11' + testCompile "org.mockito:mockito-core:1.+" + // using guru.nidi as maintenanance of the original project is dropped https://github.com/clarkware/jdepend/pull/9 + testCompile "guru.nidi:jdepend:2.9.5" +} + +mainClassName = "com.containersol.minimesos.api.Main" + +ext { + imageName = imagePrefix + '/minimesos-api' +} + +test { + testLogging { + showStandardStreams = true + } +} + +task executableJar(type: Jar) { + baseName = "minimesos-api" + from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } } + with jar + manifest { + attributes( + 'Main-Class': mainClassName, + 'Implementation-Version': project.version + ) + } + exclude 'META-INF/*.RSA', 'META-INF/*.SF', 'META-INF/*.DSA' +} + +artifacts { + archives executableJar +} + +task copyFilesForDocker(type: Copy) { + dependsOn 'executableJar' + from "build/libs/minimesos-api-${project.version}.jar" + into 'build/docker' + rename { String fileName -> + fileName.replace("-${project.version}", "") + } +} + +task copyDockerfile(type: Copy) { + dependsOn 'copyFilesForDocker' + from "Dockerfile" + into 'build/docker' +} + +afterEvaluate { project -> + if (new File(project.projectDir, 'Dockerfile').exists()) { + if (!project.hasProperty('imageName')) { + throw new GradleException('Root directory of ' + project.name + + ' contains Dockerfile, but it does not define project.ext.imageName value') + } + docker.url = 'unix:///var/run/docker.sock' + if (!System.properties['os.name'].equals('Mac OS X')) { + docker.certPath = null + } + if (System.env.DOCKER_HOST) { + docker.url = "$System.env.DOCKER_HOST".replace("tcp", "https") + if (System.env.DOCKER_CERT_PATH) { + docker.certPath = new File(System.env.DOCKER_CERT_PATH) + } + } + task buildDockerImage(type: DockerBuildImage, dependsOn: [copyDockerfile], description: 'build Docker image') { + inputDir = new File("${buildDir}/docker") + tag = project.imageName + } + project.build.dependsOn buildDockerImage + ['snapshot', 'version'].each { aTag -> + String uppercasedName = aTag.capitalize() + task "tagDockerImageWith$uppercasedName"(type: DockerTagImage, description: 'tag Docker image') { + imageId = project.imageName + tag = ('version'.equals(aTag)) ? project.version : aTag + repository = project.imageName + force = true + } + task "publishDockerImageWith$uppercasedName"(type: DockerPushImage, dependsOn: ["tagDockerImageWith$uppercasedName"], + description: 'publish Docker image') { + imageName = project.imageName + tag = ('version'.equals(aTag)) ? project.version : aTag + doFirst { + ['dockerHubUsername', 'dockerHubPassword', 'dockerHubEmail'].each { + assert project.hasProperty(it): 'Undefined "' + it + '" property' + } + docker { + registryCredentials { + username = project.property('dockerHubUsername') + password = project.property('dockerHubPassword') + email = project.property('dockerHubEmail') + } + } + } + } + } + } +} + +assemble.dependsOn executableJar diff --git a/api/src/main/java/com/containersol/minimesos/api/ClusterStartedResponse.java b/api/src/main/java/com/containersol/minimesos/api/ClusterStartedResponse.java new file mode 100644 index 00000000..e0bbef1a --- /dev/null +++ b/api/src/main/java/com/containersol/minimesos/api/ClusterStartedResponse.java @@ -0,0 +1,10 @@ +package com.containersol.minimesos.api; + +public class ClusterStartedResponse { + + private String clusterId; + + public ClusterStartedResponse(String clusterId) { + this.clusterId = clusterId; + } +} diff --git a/api/src/main/java/com/containersol/minimesos/api/JsonUtils.java b/api/src/main/java/com/containersol/minimesos/api/JsonUtils.java new file mode 100644 index 00000000..fd0264bb --- /dev/null +++ b/api/src/main/java/com/containersol/minimesos/api/JsonUtils.java @@ -0,0 +1,15 @@ +package com.containersol.minimesos.api; + +import com.google.gson.Gson; + +import spark.ResponseTransformer; + +public class JsonUtils { + + public static String toJson(Object object) { + return new Gson().toJson(object); + } + public static ResponseTransformer json() { + return JsonUtils::toJson; + } +} diff --git a/api/src/main/java/com/containersol/minimesos/api/Main.java b/api/src/main/java/com/containersol/minimesos/api/Main.java new file mode 100644 index 00000000..9da4397c --- /dev/null +++ b/api/src/main/java/com/containersol/minimesos/api/Main.java @@ -0,0 +1,50 @@ +package com.containersol.minimesos.api; + +import com.containersol.minimesos.cluster.ClusterRepository; +import com.containersol.minimesos.cluster.MesosCluster; +import com.containersol.minimesos.config.ClusterConfig; +import com.containersol.minimesos.config.ConfigParser; +import com.containersol.minimesos.mesos.MesosClusterContainersFactory; + +import java.net.Inet4Address; +import java.net.UnknownHostException; + +import static spark.Spark.exception; +import static spark.Spark.port; +import static spark.Spark.post; + +/** + * Start the REST API + */ +public class Main { + + private ClusterRepository repository; + private final MesosClusterContainersFactory factory; + + public Main() { + repository = new ClusterRepository(); + factory = new MesosClusterContainersFactory(); + } + + public static void main(String[] args) throws UnknownHostException { + Main main = new Main(); + main.run(); + } + + private void run() throws UnknownHostException { + port(8080); + + post("/start", "text/plain", (request, response) -> { + ClusterConfig clusterConfig = new ConfigParser().parse(request.body()); + MesosCluster mesosCluster = factory.createMesosCluster(clusterConfig); + mesosCluster.start(); + return new ClusterStartedResponse(mesosCluster.getClusterId()); + }, JsonUtils.json()); + + exception(Exception.class, (exception, request, response) -> { + exception.printStackTrace(); + }); + + System.out.println("minimesos is running on http://" + Inet4Address.getLocalHost().getHostAddress()); + } +} diff --git a/api/src/main/resources/logback.xml b/api/src/main/resources/logback.xml new file mode 100644 index 00000000..39a568c1 --- /dev/null +++ b/api/src/main/resources/logback.xml @@ -0,0 +1,14 @@ + + + + System.out + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + diff --git a/settings.gradle b/settings.gradle index ea472a58..2a33c995 100644 --- a/settings.gradle +++ b/settings.gradle @@ -4,3 +4,4 @@ include "test-framework-docker:scheduler" include "test-framework-docker:executor" include "test-framework-docker:system-test" include "cli" +include "api" From 22ca0c0b600b493a3901cf1ed2d0521e25d8f87c Mon Sep 17 00:00:00 2001 From: Adam Sandor Date: Fri, 3 Jun 2016 11:26:27 +0200 Subject: [PATCH 2/5] Verify running status when starting container --- .../container/AbstractContainer.java | 28 ++++++++--------- .../docker/DockerContainersUtil.java | 30 ++++++++----------- 2 files changed, 26 insertions(+), 32 deletions(-) diff --git a/minimesos/src/main/java/com/containersol/minimesos/container/AbstractContainer.java b/minimesos/src/main/java/com/containersol/minimesos/container/AbstractContainer.java index f6b65862..24f44d0a 100644 --- a/minimesos/src/main/java/com/containersol/minimesos/container/AbstractContainer.java +++ b/minimesos/src/main/java/com/containersol/minimesos/container/AbstractContainer.java @@ -1,5 +1,11 @@ package com.containersol.minimesos.container; +import java.net.URI; +import java.net.URISyntaxException; +import java.security.SecureRandom; +import java.util.List; +import java.util.concurrent.TimeUnit; + import com.containersol.minimesos.MinimesosException; import com.containersol.minimesos.cluster.ClusterProcess; import com.containersol.minimesos.cluster.MesosCluster; @@ -10,16 +16,11 @@ import com.github.dockerjava.api.model.Container; import com.github.dockerjava.api.model.Image; import com.jayway.awaitility.core.ConditionTimeoutException; + import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.net.URI; -import java.net.URISyntaxException; -import java.security.SecureRandom; -import java.util.List; -import java.util.concurrent.TimeUnit; - import static com.jayway.awaitility.Awaitility.await; /** @@ -95,17 +96,13 @@ public void start(int timeout) { try { await().atMost(timeout, TimeUnit.SECONDS).pollDelay(1, TimeUnit.SECONDS).until(() -> { - List containers = DockerClientFactory.build().listContainersCmd().withShowAll(true).exec(); - for (Container container : containers) { - if (container.getId().equals(containerId)) { - return true; - } - } - return false; + Container container = DockerContainersUtil.getContainer(containerId); + return container != null && container.getStatus().equals("Running"); }); } catch (ConditionTimeoutException cte) { - String errorMessage = String.format("Container [%s] did not start within %d seconds.", createCommand.getName(), timeout); + String errorMessage = String.format("Container [%s] did not start within %d seconds. Status is '%s'", createCommand.getName(), timeout, + DockerContainersUtil.getContainer(containerId).getStatus()); LOGGER.error(errorMessage); try { for (String logLine : DockerContainersUtil.getDockerLogs(containerId)) { @@ -151,6 +148,7 @@ public URI getServiceUrl() { /** * Enables derived classes to override + * * @return protocol of the service */ protected String getServiceProtocol() { @@ -159,6 +157,7 @@ protected String getServiceProtocol() { /** * Enables derived classes to override + * * @return port of the service */ protected int getServicePort() { @@ -167,6 +166,7 @@ protected int getServicePort() { /** * Enables derived classes to override + * * @return protocol of the service */ protected String getServicePath() { diff --git a/minimesos/src/main/java/com/containersol/minimesos/docker/DockerContainersUtil.java b/minimesos/src/main/java/com/containersol/minimesos/docker/DockerContainersUtil.java index 852cd4de..00cdbcc5 100644 --- a/minimesos/src/main/java/com/containersol/minimesos/docker/DockerContainersUtil.java +++ b/minimesos/src/main/java/com/containersol/minimesos/docker/DockerContainersUtil.java @@ -1,5 +1,14 @@ package com.containersol.minimesos.docker; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + import com.containersol.minimesos.MinimesosException; import com.github.dockerjava.api.DockerException; import com.github.dockerjava.api.command.InspectContainerResponse; @@ -10,16 +19,6 @@ import com.github.dockerjava.core.command.LogContainerResultCallback; import com.github.dockerjava.core.command.PullImageResultCallback; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Optional; -import java.util.Set; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - /** * Immutable utility class, which represents set of docker containers with filters and operations on this list */ @@ -246,14 +245,9 @@ public static String getGatewayIpAddress(String containerId) { */ public static Container getContainer(String containerId) { List containers = DockerClientFactory.build().listContainersCmd().withShowAll(true).exec(); - Container container = null; - if (containers != null && !containers.isEmpty()) { - Optional optional = containers.stream().filter(c -> c.getId().equals(containerId)).findFirst(); - if (optional.isPresent()) { - container = optional.get(); - } - } - return container; + if (containers == null) return null; + + return containers.stream().filter(c -> c.getId().equals(containerId)).findFirst().orElse(null); } } From 9e6dac57f641b76c11e11a5dda11b751c647ddfa Mon Sep 17 00:00:00 2001 From: Adam Sandor Date: Fri, 3 Jun 2016 23:37:47 +0200 Subject: [PATCH 3/5] Starting API container --- api/Dockerfile | 2 + .../minimesos/main/ApiContainer.java | 32 +++++ .../minimesos/main/CommandServe.java | 36 +++++ .../com/containersol/minimesos/main/Main.java | 1 + cli/src/main/resources/logback.xml | 16 +++ docs/index.md | 124 ++++++++++++++++++ gradle/wrapper/gradle-wrapper.properties | 4 +- .../container/AbstractContainer.java | 2 +- 8 files changed, 214 insertions(+), 3 deletions(-) create mode 100644 cli/src/main/java/com/containersol/minimesos/main/ApiContainer.java create mode 100644 cli/src/main/java/com/containersol/minimesos/main/CommandServe.java create mode 100644 cli/src/main/resources/logback.xml diff --git a/api/Dockerfile b/api/Dockerfile index 80988949..1ac12efb 100644 --- a/api/Dockerfile +++ b/api/Dockerfile @@ -2,3 +2,5 @@ FROM containersol/alpine3.3-java8-jre:v1 MAINTAINER Container Solutions BV ADD minimesos-api.jar /usr/local/share/minimesos/minimesos-api.jar + +ENTRYPOINT java -Dminimesos.dir=/tmp/.minimesos -jar /usr/local/share/minimesos/minimesos-api.jar diff --git a/cli/src/main/java/com/containersol/minimesos/main/ApiContainer.java b/cli/src/main/java/com/containersol/minimesos/main/ApiContainer.java new file mode 100644 index 00000000..bf79e3b5 --- /dev/null +++ b/cli/src/main/java/com/containersol/minimesos/main/ApiContainer.java @@ -0,0 +1,32 @@ +package com.containersol.minimesos.main; + +import com.containersol.minimesos.config.ContainerConfigBlock; +import com.containersol.minimesos.container.AbstractContainer; +import com.containersol.minimesos.docker.DockerClientFactory; +import com.github.dockerjava.api.command.CreateContainerCmd; +import com.github.dockerjava.api.model.ExposedPort; + +import static com.containersol.minimesos.util.EnvironmentBuilder.newEnvironment; +import static java.lang.String.valueOf; + +public class ApiContainer extends AbstractContainer { + + public ApiContainer() { + super(new ContainerConfigBlock("containersol/minimesos-api", "latest")); + } + + @Override protected CreateContainerCmd dockerCommand() { + ExposedPort exposedPort = ExposedPort.tcp(0); + return DockerClientFactory.build().createContainerCmd(getImageName() + ":" + getImageTag()) + .withEnv(newEnvironment() + .withValue("PORT", valueOf(exposedPort.getPort())) + .createEnvironment()) + .withPrivileged(true) + .withName(getName()) + .withExposedPorts(exposedPort); + } + + @Override public String getRole() { + return "api"; + } +} diff --git a/cli/src/main/java/com/containersol/minimesos/main/CommandServe.java b/cli/src/main/java/com/containersol/minimesos/main/CommandServe.java new file mode 100644 index 00000000..83838699 --- /dev/null +++ b/cli/src/main/java/com/containersol/minimesos/main/CommandServe.java @@ -0,0 +1,36 @@ +package com.containersol.minimesos.main; + +import com.beust.jcommander.Parameters; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Initializes a default minimesosFile in the directory where minimesos is run + */ +@Parameters(separators = "=", commandDescription = "Start the API Webservice") +public class CommandServe implements Command { + + private static final Logger LOGGER = LoggerFactory.getLogger(CommandServe.class); + + public static final String CLINAME = "serve"; + + @Override + public boolean validateParameters() { + return true; + } + + @Override + public String getName() { + return CLINAME; + } + + @Override + public void execute() { + + ApiContainer apiContainer = new ApiContainer(); + apiContainer.start(5); + + } + +} diff --git a/cli/src/main/java/com/containersol/minimesos/main/Main.java b/cli/src/main/java/com/containersol/minimesos/main/Main.java index 0dd7f1c5..8b2d9894 100644 --- a/cli/src/main/java/com/containersol/minimesos/main/Main.java +++ b/cli/src/main/java/com/containersol/minimesos/main/Main.java @@ -46,6 +46,7 @@ public class Main { public static void main(String[] args) { Main main = new Main(); main.addCommand(new CommandUp()); + main.addCommand(new CommandServe()); main.addCommand(new CommandDestroy()); main.addCommand(new CommandHelp()); main.addCommand(new CommandInstall()); diff --git a/cli/src/main/resources/logback.xml b/cli/src/main/resources/logback.xml new file mode 100644 index 00000000..aa7d4a71 --- /dev/null +++ b/cli/src/main/resources/logback.xml @@ -0,0 +1,16 @@ + + + + System.out + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36}: %msg%n + + + + + + + + + + diff --git a/docs/index.md b/docs/index.md index 200edb3c..93a615e3 100644 --- a/docs/index.md +++ b/docs/index.md @@ -9,6 +9,7 @@ If you have used Vagrant and Docker before, the set of the commands will be very - Website https://minimesos.org/ - Blog https://minimesos.org/blog - Interactive tutorial https://minimesos.org/try + - API Webservice `` ## System Requirements @@ -288,3 +289,126 @@ export LIBPROCESS_IP=$(ifconfig | grep -Eo 'inet (addr:)?([0-9]*\.){3}[0-9]*' | ``` This ensures your executor task will be assigned an interface to allow communication within the cluster. + +# minimesos API Webservice + +The minimesos API provides functionality to run and monitor a single Mesos cluster in minimesos. + +## Start minimesos cluster [/start] + +### POST + +Starts the Mesos cluster and returns it's ID. + ++ Request (text/plain) + + ``` + Contents of a minimesosFile + ``` + ++ Response 200 (application/json) + + ```json + { + "cluster_id": "123456", + "endpoints": { + "network-gateway": { + "ip": "172.17.0.1" + }, + "master": { + "ip": "172.10.0.5", + "endpoint": "http://172.17.0.4:5050" + }, + "zookeeper": { + "ip": "172.10.0.7", + "endpoint": "zk://172.17.0.3:2181/mesos" + }, + "marathon": { + "ip": "172.10.0.8", + "endpoint": "http://172.17.0.8:8080" + }, + "api": { + "ip": "172.10.0.10", + "endpoint": "http://172.10.0.10" + } + } + } + ``` + ++ Response 500 (text/plain) + + ``` + Cluster failed to start: + ``` + +## Retrieve cluster info [/info] + +### GET + +Returns all info about the running cluster. + ++ Response 200 (application/json) + + ```json + { + "cluster_id": "123456", + "endpoints": { + "master": "172.10.0.5", + "agent": "172.10.0.6", + "zookeeper": "172.10.0.7", + "marathon": "172.10.0.8", + "api": "172.10.0.1" + } + } + ``` + +## Install Marathon framework [/install] + +Install a Mesos framework using Marathon. The input is expected to be a valid Marathon file. + +### POST + +* Request (application/json) + + ```json + { + "id": "elasticsearch", + "container": { + "docker": { + "image": "mesos/elasticsearch-scheduler:1.0.1", + "network": "BRIDGE" + } + }, + "args": [ + "--zookeeperMesosUrl", "${MINIMESOS_ZOOKEEPER}", + "--useIpAddress", "true", + "--frameworkUseDocker", "false", + "--elasticsearchNodes", "1" + ], + "cpus": 0.2, + "mem": 512.0, + "env": { + "JAVA_OPTS": "-Xms128m -Xmx256m" + }, + "instances": 1 + } + ``` +* Response 200 (text/plain) + + ``` + + ``` +* Response 500 (text/plain) + + ``` + Failed to install framework: + ``` + +## Uninstall Marathon framework [/uninstall/{framework_id}] + +Uninstall a framework using Marathon + ++ Parameters + + framework_id: (string) - The id of the framework that will be uninstalled + +### POST diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index f02243ee..b70f4868 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Thu Apr 21 17:15:12 CEST 2016 +#Thu Jun 02 12:03:52 CEST 2016 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.12-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.12-all.zip diff --git a/minimesos/src/main/java/com/containersol/minimesos/container/AbstractContainer.java b/minimesos/src/main/java/com/containersol/minimesos/container/AbstractContainer.java index 24f44d0a..3867ed2c 100644 --- a/minimesos/src/main/java/com/containersol/minimesos/container/AbstractContainer.java +++ b/minimesos/src/main/java/com/containersol/minimesos/container/AbstractContainer.java @@ -97,7 +97,7 @@ public void start(int timeout) { await().atMost(timeout, TimeUnit.SECONDS).pollDelay(1, TimeUnit.SECONDS).until(() -> { Container container = DockerContainersUtil.getContainer(containerId); - return container != null && container.getStatus().equals("Running"); + return container != null && container.getStatus().startsWith("Up"); }); } catch (ConditionTimeoutException cte) { From fc0783f05b85649b6b7d1a805c428eb33755cd83 Mon Sep 17 00:00:00 2001 From: Frank Scholten Date: Fri, 24 Jun 2016 16:57:56 +0200 Subject: [PATCH 4/5] #456 Added info endpoint and unit test. Refactored Main to ApiServer --- .../api/{Main.java => ApiServer.java} | 40 ++++++++++++------- .../containersol/minimesos/api/ApiTest.java | 21 ++++++++++ 2 files changed, 47 insertions(+), 14 deletions(-) rename api/src/main/java/com/containersol/minimesos/api/{Main.java => ApiServer.java} (56%) create mode 100644 api/src/test/java/com/containersol/minimesos/api/ApiTest.java diff --git a/api/src/main/java/com/containersol/minimesos/api/Main.java b/api/src/main/java/com/containersol/minimesos/api/ApiServer.java similarity index 56% rename from api/src/main/java/com/containersol/minimesos/api/Main.java rename to api/src/main/java/com/containersol/minimesos/api/ApiServer.java index 9da4397c..cb89f5cc 100644 --- a/api/src/main/java/com/containersol/minimesos/api/Main.java +++ b/api/src/main/java/com/containersol/minimesos/api/ApiServer.java @@ -5,46 +5,58 @@ import com.containersol.minimesos.config.ClusterConfig; import com.containersol.minimesos.config.ConfigParser; import com.containersol.minimesos.mesos.MesosClusterContainersFactory; +import spark.Spark; import java.net.Inet4Address; import java.net.UnknownHostException; -import static spark.Spark.exception; -import static spark.Spark.port; -import static spark.Spark.post; - /** - * Start the REST API + * minimesos API server */ -public class Main { +public class ApiServer { + + public static final int PORT = 8080; private ClusterRepository repository; + private final MesosClusterContainersFactory factory; - public Main() { + public ApiServer() { repository = new ClusterRepository(); factory = new MesosClusterContainersFactory(); } public static void main(String[] args) throws UnknownHostException { - Main main = new Main(); - main.run(); + ApiServer apiServer = new ApiServer(); + apiServer.start(); } - private void run() throws UnknownHostException { - port(8080); + public void start() { + Spark.port(PORT); - post("/start", "text/plain", (request, response) -> { + Spark.post("/start", "text/plain", (request, response) -> { ClusterConfig clusterConfig = new ConfigParser().parse(request.body()); MesosCluster mesosCluster = factory.createMesosCluster(clusterConfig); mesosCluster.start(); return new ClusterStartedResponse(mesosCluster.getClusterId()); }, JsonUtils.json()); - exception(Exception.class, (exception, request, response) -> { + Spark.get("/info", (req, res) -> "No cluster is running"); + + Spark.exception(Exception.class, (exception, request, response) -> { exception.printStackTrace(); }); + } + + public void stop() { + Spark.stop(); + } - System.out.println("minimesos is running on http://" + Inet4Address.getLocalHost().getHostAddress()); + public String getServiceUrl() { + try { + return "http://" + Inet4Address.getLocalHost().getHostAddress() + ":" + PORT; + } catch (UnknownHostException e) { + throw new RuntimeException("Could not determine IP address: " + e.getMessage()); + } } } diff --git a/api/src/test/java/com/containersol/minimesos/api/ApiTest.java b/api/src/test/java/com/containersol/minimesos/api/ApiTest.java new file mode 100644 index 00000000..626613af --- /dev/null +++ b/api/src/test/java/com/containersol/minimesos/api/ApiTest.java @@ -0,0 +1,21 @@ +package com.containersol.minimesos.api; + +import com.mashape.unirest.http.Unirest; +import com.mashape.unirest.http.exceptions.UnirestException; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class ApiTest { + + @Test + public void testInfo() throws UnirestException { + ApiServer apiServer = new ApiServer(); + apiServer.start(); + + assertEquals("No cluster is running", Unirest.get(apiServer.getServiceUrl() + "/info").asString().getBody()); + + apiServer.stop(); + } + +} From 9cf90884afda63f5b7b117c9b6b8d4b0d86f286a Mon Sep 17 00:00:00 2001 From: Frank Scholten Date: Tue, 5 Jul 2016 16:36:30 +0200 Subject: [PATCH 5/5] #456 Fixed failing RunTaskTest --- .../containersol/minimesos/api/ApiServer.java | 25 +++++--- .../minimesos/api/ApiServerTest.java | 62 +++++++++++++++++++ .../containersol/minimesos/api/ApiTest.java | 21 ------- .../test/resources/minimesosFile-apiServer | 54 ++++++++++++++++ .../container/AbstractContainer.java | 6 +- .../containersol/minimesos/RunTaskTest.java | 22 +++---- 6 files changed, 143 insertions(+), 47 deletions(-) create mode 100644 api/src/test/java/com/containersol/minimesos/api/ApiServerTest.java delete mode 100644 api/src/test/java/com/containersol/minimesos/api/ApiTest.java create mode 100644 api/src/test/resources/minimesosFile-apiServer diff --git a/api/src/main/java/com/containersol/minimesos/api/ApiServer.java b/api/src/main/java/com/containersol/minimesos/api/ApiServer.java index cb89f5cc..eb8e9026 100644 --- a/api/src/main/java/com/containersol/minimesos/api/ApiServer.java +++ b/api/src/main/java/com/containersol/minimesos/api/ApiServer.java @@ -7,7 +7,6 @@ import com.containersol.minimesos.mesos.MesosClusterContainersFactory; import spark.Spark; -import java.net.Inet4Address; import java.net.UnknownHostException; /** @@ -21,6 +20,8 @@ public class ApiServer { private final MesosClusterContainersFactory factory; + private MesosCluster mesosCluster; + public ApiServer() { repository = new ClusterRepository(); factory = new MesosClusterContainersFactory(); @@ -35,10 +36,14 @@ public void start() { Spark.port(PORT); Spark.post("/start", "text/plain", (request, response) -> { - ClusterConfig clusterConfig = new ConfigParser().parse(request.body()); - MesosCluster mesosCluster = factory.createMesosCluster(clusterConfig); - mesosCluster.start(); - return new ClusterStartedResponse(mesosCluster.getClusterId()); + if (mesosCluster != null) { + return new ClusterStartedResponse(mesosCluster.getClusterId()); + } else { + ClusterConfig clusterConfig = new ConfigParser().parse(request.body()); + mesosCluster = factory.createMesosCluster(clusterConfig); + mesosCluster.start(); + return new ClusterStartedResponse(mesosCluster.getClusterId()); + } }, JsonUtils.json()); Spark.get("/info", (req, res) -> "No cluster is running"); @@ -49,14 +54,14 @@ public void start() { } public void stop() { + if (mesosCluster != null) { + mesosCluster.destroy(factory); + } + Spark.stop(); } public String getServiceUrl() { - try { - return "http://" + Inet4Address.getLocalHost().getHostAddress() + ":" + PORT; - } catch (UnknownHostException e) { - throw new RuntimeException("Could not determine IP address: " + e.getMessage()); - } + return "http://localhost:" + PORT; } } diff --git a/api/src/test/java/com/containersol/minimesos/api/ApiServerTest.java b/api/src/test/java/com/containersol/minimesos/api/ApiServerTest.java new file mode 100644 index 00000000..d396af6f --- /dev/null +++ b/api/src/test/java/com/containersol/minimesos/api/ApiServerTest.java @@ -0,0 +1,62 @@ +package com.containersol.minimesos.api; + +import com.containersol.minimesos.cluster.MesosCluster; +import com.containersol.minimesos.mesos.MesosClusterContainersFactory; +import com.mashape.unirest.http.HttpResponse; +import com.mashape.unirest.http.Unirest; +import com.mashape.unirest.http.exceptions.UnirestException; + +import org.apache.commons.io.IOUtils; +import org.json.JSONObject; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.FileInputStream; +import java.io.IOException; + +import static org.junit.Assert.assertEquals; + +public class ApiServerTest { + + private static ApiServer apiServer; + + @BeforeClass + public static void before() { + apiServer = new ApiServer(); + apiServer.start(); + } + + @AfterClass + public static void after() { + apiServer.stop(); + } + + @Test + public void testInfo() throws UnirestException { + HttpResponse response = Unirest.get(apiServer.getServiceUrl() + "/info").asString(); + + assertEquals("No cluster is running", response.getBody()); + } + + @Test + public void testStart() throws UnirestException, IOException { + HttpResponse firstResponse = Unirest.post(apiServer.getServiceUrl() + "/start").body(IOUtils.toByteArray(new FileInputStream("src/test/resources/minimesosFile-apiServer"))).asString(); + + assertEquals(200, firstResponse.getStatus()); + + JSONObject jsonObject = new JSONObject(firstResponse.getBody()); + String clusterId = jsonObject.getString("clusterId"); + + MesosCluster mesosCluster = MesosCluster.loadCluster(clusterId, new MesosClusterContainersFactory()); + assertEquals(1, mesosCluster.getAgents().size()); + + HttpResponse secondResponse = Unirest.post(apiServer.getServiceUrl() + "/start").body(IOUtils.toByteArray(new FileInputStream("src/test/resources/minimesosFile-apiServer"))).asString(); + + assertEquals(200, firstResponse.getStatus()); + assertEquals(200, secondResponse.getStatus()); + + assertEquals(firstResponse.getBody(), secondResponse.getBody()); + } + +} diff --git a/api/src/test/java/com/containersol/minimesos/api/ApiTest.java b/api/src/test/java/com/containersol/minimesos/api/ApiTest.java deleted file mode 100644 index 626613af..00000000 --- a/api/src/test/java/com/containersol/minimesos/api/ApiTest.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.containersol.minimesos.api; - -import com.mashape.unirest.http.Unirest; -import com.mashape.unirest.http.exceptions.UnirestException; -import org.junit.Test; - -import static org.junit.Assert.assertEquals; - -public class ApiTest { - - @Test - public void testInfo() throws UnirestException { - ApiServer apiServer = new ApiServer(); - apiServer.start(); - - assertEquals("No cluster is running", Unirest.get(apiServer.getServiceUrl() + "/info").asString().getBody()); - - apiServer.stop(); - } - -} diff --git a/api/src/test/resources/minimesosFile-apiServer b/api/src/test/resources/minimesosFile-apiServer new file mode 100644 index 00000000..e1eed60d --- /dev/null +++ b/api/src/test/resources/minimesosFile-apiServer @@ -0,0 +1,54 @@ +minimesos { + clusterName = "api-server-cluster" + mapPortsToHost = false + loggingLevel = "INFO" + mapAgentSandboxVolume = false + mesosVersion = "0.25" + timeout = 60 + + agent { + imageName = "containersol/mesos-agent" + imageTag = "# derive from mesos version" + loggingLevel = "# INHERIT FROM CLUSTER" + portNumber = 5051 + + resources { + + cpu { + role = "*" + value = 1 + } + + disk { + role = "*" + value = 200 + } + + mem { + role = "*" + value = 256 + } + + ports { + role = "*" + value = "[31000-32000]" + } + } + } + + consul { + imageName = "containersol/consul-server" + imageTag = "0.6" + } + + master { + imageName = "containersol/mesos-master" + imageTag = "# derive from mesos version" + loggingLevel = "# INHERIT FROM CLUSTER" + } + + zookeeper { + imageName = "jplock/zookeeper" + imageTag = "3.4.6" + } +} diff --git a/minimesos/src/main/java/com/containersol/minimesos/container/AbstractContainer.java b/minimesos/src/main/java/com/containersol/minimesos/container/AbstractContainer.java index 3867ed2c..7ab9c038 100644 --- a/minimesos/src/main/java/com/containersol/minimesos/container/AbstractContainer.java +++ b/minimesos/src/main/java/com/containersol/minimesos/container/AbstractContainer.java @@ -79,7 +79,7 @@ public String getImageTag() { protected abstract CreateContainerCmd dockerCommand(); /** - * Starts the container and waits until is started + * Starts the container and waits until is started or already exited without error. * * @param timeout in seconds */ @@ -94,12 +94,10 @@ public void start(int timeout) { DockerClientFactory.build().startContainerCmd(containerId).exec(); try { - await().atMost(timeout, TimeUnit.SECONDS).pollDelay(1, TimeUnit.SECONDS).until(() -> { Container container = DockerContainersUtil.getContainer(containerId); - return container != null && container.getStatus().startsWith("Up"); + return container != null && (container.getStatus().startsWith("Up") || container.getStatus().startsWith("Exited (0)")); }); - } catch (ConditionTimeoutException cte) { String errorMessage = String.format("Container [%s] did not start within %d seconds. Status is '%s'", createCommand.getName(), timeout, DockerContainersUtil.getContainer(containerId).getStatus()); diff --git a/minimesos/src/test/java/com/containersol/minimesos/RunTaskTest.java b/minimesos/src/test/java/com/containersol/minimesos/RunTaskTest.java index 98c36dde..a2204d37 100644 --- a/minimesos/src/test/java/com/containersol/minimesos/RunTaskTest.java +++ b/minimesos/src/test/java/com/containersol/minimesos/RunTaskTest.java @@ -11,12 +11,13 @@ import com.github.dockerjava.api.command.CreateContainerCmd; import com.github.dockerjava.api.model.Frame; import com.github.dockerjava.core.command.LogContainerResultCallback; -import com.jayway.awaitility.Awaitility; import org.junit.ClassRule; import org.junit.Test; import java.util.concurrent.TimeUnit; +import static com.jayway.awaitility.Awaitility.*; + public class RunTaskTest { private static final String TASK_CLUSTER_ROLE = "test"; @@ -63,17 +64,14 @@ protected CreateContainerCmd dockerCommand() { } }; - CLUSTER.addAndStartProcess(mesosAgent); - LogContainerTestCallback cb = new LogContainerTestCallback(); - DockerClientFactory.build().logContainerCmd(mesosAgent.getContainerId()).withStdOut(true).exec(cb); - cb.awaitCompletion(); - - Awaitility.await().atMost(60, TimeUnit.SECONDS).until(() -> { - LogContainerTestCallback cb1 = new LogContainerTestCallback(); - DockerClientFactory.build().logContainerCmd(mesosAgent.getContainerId()).withStdOut(true).exec(cb1); - cb1.awaitCompletion(); - String log = cb1.toString(); - return log.contains("Received status update TASK_FINISHED for task test-cmd"); + mesosAgent.start(10); + + await().timeout(60, TimeUnit.SECONDS).until(() -> { + LogContainerTestCallback cb = new LogContainerTestCallback(); + DockerClientFactory.build().logContainerCmd(mesosAgent.getContainerId()).withStdErr().withStdOut(true).exec(cb); + cb.awaitCompletion(); + + return cb.toString().contains("Received status update TASK_FINISHED for task test-cmd"); }); }