From 69fda684f890924502f14f96f10a99a2dd45a311 Mon Sep 17 00:00:00 2001 From: Mark Fisher Date: Wed, 22 Jul 2015 08:26:25 -0400 Subject: [PATCH] initial commit of module launcher --- pom.xml | 1 + .../.gitignore | 1 + .../Dockerfile | 7 ++ spring-cloud-stream-module-launcher/README.md | 91 ++++++++++++++ .../dockerize.sh | 13 ++ spring-cloud-stream-module-launcher/pom.xml | 45 +++++++ .../boot/loader/ModuleJarLauncher.java | 46 ++++++++ .../module/launcher/ModuleLauncher.java | 111 ++++++++++++++++++ 8 files changed, 315 insertions(+) create mode 100644 spring-cloud-stream-module-launcher/.gitignore create mode 100644 spring-cloud-stream-module-launcher/Dockerfile create mode 100644 spring-cloud-stream-module-launcher/README.md create mode 100755 spring-cloud-stream-module-launcher/dockerize.sh create mode 100644 spring-cloud-stream-module-launcher/pom.xml create mode 100644 spring-cloud-stream-module-launcher/src/main/java/org/springframework/boot/loader/ModuleJarLauncher.java create mode 100644 spring-cloud-stream-module-launcher/src/main/java/org/springframework/cloud/stream/module/launcher/ModuleLauncher.java diff --git a/pom.xml b/pom.xml index a498b69d0..f2cdd82b2 100644 --- a/pom.xml +++ b/pom.xml @@ -29,6 +29,7 @@ spring-cloud-stream spring-cloud-stream-binders spring-cloud-stream-codec + spring-cloud-stream-module-launcher spring-cloud-stream-samples docs diff --git a/spring-cloud-stream-module-launcher/.gitignore b/spring-cloud-stream-module-launcher/.gitignore new file mode 100644 index 000000000..44898dfcf --- /dev/null +++ b/spring-cloud-stream-module-launcher/.gitignore @@ -0,0 +1 @@ +artifacts \ No newline at end of file diff --git a/spring-cloud-stream-module-launcher/Dockerfile b/spring-cloud-stream-module-launcher/Dockerfile new file mode 100644 index 000000000..22c3a0b25 --- /dev/null +++ b/spring-cloud-stream-module-launcher/Dockerfile @@ -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"] diff --git a/spring-cloud-stream-module-launcher/README.md b/spring-cloud-stream-module-launcher/README.md new file mode 100644 index 000000000..804e9f8a7 --- /dev/null +++ b/spring-cloud-stream-module-launcher/README.md @@ -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= 192.168.59.103:5000/module-launcher +docker run -p 8081:8081 -e MODULES=log -e SPRING_REDIS_HOST= 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 +```` diff --git a/spring-cloud-stream-module-launcher/dockerize.sh b/spring-cloud-stream-module-launcher/dockerize.sh new file mode 100755 index 000000000..eeda75c4a --- /dev/null +++ b/spring-cloud-stream-module-launcher/dockerize.sh @@ -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 . diff --git a/spring-cloud-stream-module-launcher/pom.xml b/spring-cloud-stream-module-launcher/pom.xml new file mode 100644 index 000000000..22a6c1362 --- /dev/null +++ b/spring-cloud-stream-module-launcher/pom.xml @@ -0,0 +1,45 @@ + + + 4.0.0 + + spring-cloud-stream-module-launcher + jar + spring-cloud-stream-module-launcher + application for launching spring-cloud-stream modules + + + org.springframework.cloud + spring-cloud-stream-parent + 1.0.0.BUILD-SNAPSHOT + + + + UTF-8 + org.springframework.cloud.stream.module.launcher.ModuleLauncher + + + + + org.springframework.boot + spring-boot-loader + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + exec + + + + + diff --git a/spring-cloud-stream-module-launcher/src/main/java/org/springframework/boot/loader/ModuleJarLauncher.java b/spring-cloud-stream-module-launcher/src/main/java/org/springframework/boot/loader/ModuleJarLauncher.java new file mode 100644 index 000000000..26abec996 --- /dev/null +++ b/spring-cloud-stream-module-launcher/src/main/java/org/springframework/boot/loader/ModuleJarLauncher.java @@ -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 archives) throws Exception { + archives.add(0, getArchive()); + } + + @Override + public void launch(String[] args) { + super.launch(args); + } +} diff --git a/spring-cloud-stream-module-launcher/src/main/java/org/springframework/cloud/stream/module/launcher/ModuleLauncher.java b/spring-cloud-stream-module-launcher/src/main/java/org/springframework/cloud/stream/module/launcher/ModuleLauncher.java new file mode 100644 index 000000000..b35ae2ade --- /dev/null +++ b/spring-cloud-stream-module-launcher/src/main/java/org/springframework/cloud/stream/module/launcher/ModuleLauncher.java @@ -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 + *

+ * 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: --foo.bar=123 becomes --bar=123 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 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(); + } + } + } +}