initial commit of module launcher

This commit is contained in:
Mark Fisher
2015-07-22 08:26:25 -04:00
parent 1a51c32f59
commit 69fda684f8
8 changed files with 315 additions and 0 deletions

View File

@@ -29,6 +29,7 @@
<module>spring-cloud-stream</module>
<module>spring-cloud-stream-binders</module>
<module>spring-cloud-stream-codec</module>
<module>spring-cloud-stream-module-launcher</module>
<module>spring-cloud-stream-samples</module>
<module>docs</module>
</modules>

View File

@@ -0,0 +1 @@
artifacts

View File

@@ -0,0 +1,7 @@
FROM java:8
VOLUME /tmp
RUN mkdir -p /opt/spring/modules
ADD artifacts/modules /opt/spring/modules
ADD target/spring-cloud-stream-module-launcher-1.0.0.BUILD-SNAPSHOT-exec.jar module-launcher.jar
RUN bash -c 'touch /module-launcher.jar'
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/module-launcher.jar"]

View File

@@ -0,0 +1,91 @@
# Module Launcher
The Module Launcher provides a single entry point that bootstraps module JARs located in a home directory. A single Docker image that contains such a directory of module JARs can then be used to launch any of those JARs based on an environment variable. When running standalone, a system property may be used instead of an environment variable, so that multiple instances of the Module Launcher may run on a single machine. The following examples demonstrate running the modules for the *ticktock* stream (`time | log`).
## Prerequisites
1: clone and build the spring-cloud-stream project:
````
git clone https://github.com/spring-cloud/spring-cloud-stream.git
cd spring-cloud-stream
mvn -s .settings.xml package
cd ..
````
2: copy the spring-cloud-stream source and sink sample JARs to `/opt/spring/modules`:
````
mkdir -p /opt/spring/modules
cp spring-cloud-stream/spring-cloud-stream-samples/source/target/spring-cloud-stream-sample-source-1.0.0.BUILD-SNAPSHOT-exec.jar /opt/spring/modules/time.jar
cp spring-cloud-stream/spring-cloud-stream-samples/sink/target/spring-cloud-stream-sample-sink-1.0.0.BUILD-SNAPSHOT-exec.jar /opt/spring/modules/log.jar
````
3: start redis locally via `redis-server` (optionally start `redis-cli` and use the `MONITOR` command to watch activity)
## Running Standalone
From the `spring-cloud-stream/spring-cloud-stream-module-launcher` directory:
````
java -Dmodules=time -jar target/spring-cloud-stream-module-launcher-1.0.0.BUILD-SNAPSHOT-exec.jar
java -Dmodules=log -jar target/spring-cloud-stream-module-launcher-1.0.0.BUILD-SNAPSHOT-exec.jar
````
The time messages will be emitted every 5 seconds. The console for the log module will display each:
````
2015-06-05 12:39:58.896 INFO 51078 --- [hannel-adapter1] config.ModuleDefinition : Received: 2015-06-05 12:39:58
2015-06-05 12:40:02.699 INFO 51078 --- [hannel-adapter1] config.ModuleDefinition : Received: 2015-06-05 16:39:52
2015-06-05 12:40:03.897 INFO 51078 --- [hannel-adapter1] config.ModuleDefinition : Received: 2015-06-05 12:40:03
````
*NOTE:* the two modules will be launched within a single process if both are specified: `-Dmodules=time,log`
## Running with Docker
1: build the module-launcher Docker image, including a copy of the module directory:
From the `spring-cloud-stream/spring-cloud-stream-module-launcher` directory:
````
mkdir artifacts
cp -r /opt/spring/modules artifacts/
./dockerize.sh
````
2: run each module as a docker process by passing environment variables for the module name as well as the host machine's IP address for the redis connection to be established within the container:
````
docker run -p 8080:8080 -e MODULES=time -e SPRING_REDIS_HOST=<host.ip> 192.168.59.103:5000/module-launcher
docker run -p 8081:8081 -e MODULES=log -e SPRING_REDIS_HOST=<host.ip> 192.168.59.103:5000/module-launcher
````
## Running on Lattice
### Initial Setup (if necessary)
1: Launch lattice with vagrant as described [here](http://lattice.cf/docs/getting-started/).
2: Run a private Docker registry, and configure Lattice to use that as described [here](http://lattice.cf/docs/private-docker-registry/).
### Deploying Modules
1: Push the Docker image to the private registry (if necessary, run `$(boot2docker shellinit)` first):
````
$ docker push 192.168.59.103:5000/module-launcher
````
2: Create a Redis instance on Lattice (running as root):
````
$ ltc create redis redis -r
````
3: Run the modules as long-running processes (LRPs) on Lattice:
````
$ ltc create time 192.168.59.103:5000/module-launcher -e MODULES=time -e SPRING_PROFILES_ACTIVE=cloud
$ ltc create log 192.168.59.103:5000/module-launcher -e MODULES=log -e SPRING_PROFILES_ACTIVE=cloud
````

View File

@@ -0,0 +1,13 @@
#!/bin/bash
if [ ! -f "Dockerfile" ]; then
echo "Dockerfile not found"
exit 1
fi
if [ ! -f "target/spring-cloud-stream-module-launcher-1.0.0.BUILD-SNAPSHOT-exec.jar" ]; then
echo "JAR not available; run mvn package first"
exit 1
fi
docker build -t 192.168.59.103:5000/module-launcher .

View File

@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-cloud-stream-module-launcher</artifactId>
<packaging>jar</packaging>
<name>spring-cloud-stream-module-launcher</name>
<description>application for launching spring-cloud-stream modules</description>
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-parent</artifactId>
<version>1.0.0.BUILD-SNAPSHOT</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<start-class>org.springframework.cloud.stream.module.launcher.ModuleLauncher</start-class>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-loader</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<classifier>exec</classifier>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.loader;
import java.util.List;
import org.springframework.boot.loader.archive.Archive;
import org.springframework.boot.loader.util.AsciiBytes;
public class ModuleJarLauncher extends ExecutableArchiveLauncher {
private static final AsciiBytes LIB = new AsciiBytes("lib/");
public ModuleJarLauncher(Archive archive) {
super(archive);
}
@Override
protected boolean isNestedArchive(Archive.Entry entry) {
return !entry.isDirectory() && entry.getName().startsWith(LIB);
}
@Override
protected void postProcessClassPathArchives(List<Archive> archives) throws Exception {
archives.add(0, getArchive());
}
@Override
public void launch(String[] args) {
super.launch(args);
}
}

View File

@@ -0,0 +1,111 @@
/*
* Copyright 2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.stream.module.launcher;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import org.springframework.boot.loader.ModuleJarLauncher;
import org.springframework.boot.loader.archive.JarFileArchive;
/**
* Bootstrap for launching one or more modules. The module path(s), relative to the module home, must be provided via
* the "modules" system property or "MODULES" environment variable as a comma-delimited list. The module home directory
* itself may be provided via the "module.home" system property or "MODULE_HOME" environment variable. The default
* module home directory is: /opt/spring/modules
* <p>
* To pass args to a module, prefix with the module name and a dot. The arg name will be de-qualified and passed along.
* For example: <code>--foo.bar=123</code> becomes <code>--bar=123</code> and is only passed to the 'foo' module.
*
* @author Mark Fisher
* @author Ilayaperumal Gopinathan
*/
public class ModuleLauncher {
private static final String DEFAULT_MODULE_HOME = "/opt/spring/modules";
private final File moduleHome;
public ModuleLauncher(File moduleHome) {
this.moduleHome = moduleHome;
}
public void launch(String[] modules, String[] args) {
Executor executor = Executors.newFixedThreadPool(modules.length);
for (String module : modules) {
List<String> moduleArgs = new ArrayList<>();
for (String arg : args) {
if (arg.startsWith("--" + module + ".")) {
moduleArgs.add("--" + arg.substring(module.length() + 3));
}
}
moduleArgs.add("--spring.jmx.default-domain=" + module.replace("/", "."));
executor.execute(new ModuleLaunchTask(moduleHome, module + ".jar", moduleArgs.toArray(new String[moduleArgs.size()])));
}
}
public static void main(String[] args) throws Exception {
String modules = System.getProperty("modules");
if (modules == null) {
modules = System.getenv("MODULES");
}
if (modules == null) {
System.err.println("Either the 'modules' system property or 'MODULES' environment variable is required.");
System.exit(1);
}
String moduleHome = System.getProperty("module.home");
if (moduleHome == null) {
moduleHome = System.getenv("MODULE_HOME");
}
if (moduleHome == null) {
moduleHome = DEFAULT_MODULE_HOME;
}
ModuleLauncher launcher = new ModuleLauncher(new File(moduleHome));
launcher.launch(modules.split(","), args);
}
private static class ModuleLaunchTask implements Runnable {
private final File directory;
private final String module;
private final String[] args;
ModuleLaunchTask(File directory, String module, String[] args) {
this.directory = directory;
this.module = module;
this.args = args;
}
@Override
public void run() {
try {
JarFileArchive jarFileArchive = new JarFileArchive(new File(directory, module));
ModuleJarLauncher jarLauncher = new ModuleJarLauncher(jarFileArchive);
jarLauncher.launch(args);
}
catch (Exception e) {
e.printStackTrace();
}
}
}
}