Fix deployer app

Run it, then

$ curl localhost:8080/admin
{"sample":{"name":"sample","id":"81c568e36c7909ec1dd841aa7ee6d3e3","path":"..."}}

undeploy:

$ curl localhost:8080/admin/sample -X DELETE
{"id":"81c568e36c7909ec1dd841aa7ee6d3e3"}

redeploy

$ curl localhost:8080/admin/sample -d path=maven://com.example:function-sample-pojo:1.0.0.BUILD-SNAPSHOT
{"id":"81c568e36c7909ec1dd841aa7ee6d3e3"}

(Takes about 500ms)
This commit is contained in:
Dave Syer
2017-05-19 11:26:29 +02:00
parent 56b9be9b6e
commit 3606e51d78
4 changed files with 96 additions and 66 deletions

View File

@@ -21,7 +21,6 @@ import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -30,24 +29,19 @@ import javax.annotation.PreDestroy;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eclipse.aether.artifact.DefaultArtifact;
import org.eclipse.aether.graph.Dependency;
import org.springframework.boot.Banner.Mode;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.loader.archive.Archive;
import org.springframework.boot.loader.thin.ArchiveUtils;
import org.springframework.boot.loader.thin.DependencyResolver;
import org.springframework.cloud.deployer.thin.ContextRunner;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.core.SpringVersion;
import org.springframework.messaging.Message;
import org.springframework.context.support.LiveBeansView;
import org.springframework.core.io.ClassPathResource;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
import reactor.core.publisher.Flux;
/**
* @author Dave Syer
@@ -56,10 +50,6 @@ import reactor.core.publisher.Flux;
// NOT a @Component (to prevent it from being scanned by the "main" application).
public class ApplicationRunner implements CommandLineRunner {
private static final String DEFAULT_REACTOR_VERSION = "3.0.4.RELEASE";
private static final String DEFAULT_SPRING_VERSION = SpringVersion.getVersion();
private static Log logger = LogFactory.getLog(ApplicationRunner.class);
public static void main(String[] args) {
@@ -84,8 +74,9 @@ public class ApplicationRunner implements CommandLineRunner {
ClassUtils.overrideThreadContextClassLoader(classLoader);
Class<?> cls = classLoader.loadClass(ContextRunner.class.getName());
this.app = cls.newInstance();
runContext(DeployedFunctionApplication.class.getName(),
Collections.emptyMap(), args);
runContext(DeployedFunctionApplication.class.getName(), Collections
.singletonMap(LiveBeansView.MBEAN_DOMAIN_PROPERTY_NAME, "deployer"),
args);
}
catch (Exception e) {
logger.error("Cannot deploy", e);
@@ -93,6 +84,10 @@ public class ApplicationRunner implements CommandLineRunner {
finally {
ClassUtils.overrideThreadContextClassLoader(contextLoader);
}
RuntimeException e = getError();
if (e != null) {
throw e;
}
}
@PreDestroy
@@ -100,6 +95,22 @@ public class ApplicationRunner implements CommandLineRunner {
closeContext();
}
private RuntimeException getError() {
if (this.app == null) {
return null;
}
Method method = ReflectionUtils.findMethod(this.app.getClass(), "getError");
Throwable e;
e = (Throwable) ReflectionUtils.invokeMethod(method, this.app);
if (e==null) {
return null;
}
if (e instanceof RuntimeException) {
return (RuntimeException) e;
}
return new IllegalStateException("Cannot launch", e);
}
private void runContext(String mainClass, Map<String, String> properties,
String... args) {
Method method = ReflectionUtils.findMethod(this.app.getClass(), "run",
@@ -125,14 +136,7 @@ public class ApplicationRunner implements CommandLineRunner {
for (URL url : urls) {
child.add(url);
}
DependencyResolver resolver = DependencyResolver.instance();
String reactor = getReactorCoordinates();
// spring-core is OK, spring-context is not, spring-messaging depends on
// spring-context (so it is not OK)
String spring = getSpringCoordinates();
List<File> resolved = Arrays.asList(
resolver.resolve(new Dependency(new DefaultArtifact(reactor), "runtime")),
resolver.resolve(new Dependency(new DefaultArtifact(spring), "runtime")));
List<File> resolved = resolveParent();
for (File archive : resolved) {
try {
URL url = archive.toURI().toURL();
@@ -151,42 +155,15 @@ public class ApplicationRunner implements CommandLineRunner {
return new URLClassLoader(child.toArray(new URL[0]), base);
}
private String getSpringCoordinates() {
Package pkg = Message.class.getPackage();
String version = null;
version = (pkg != null ? pkg.getImplementationVersion() : DEFAULT_SPRING_VERSION);
return "org.springframework:spring-core:" + version;
}
private String getReactorCoordinates() {
Package pkg = Flux.class.getPackage();
String version = null;
version = (pkg != null ? pkg.getImplementationVersion()
: DEFAULT_REACTOR_VERSION);
if (version == null) {
Archive archive = ArchiveUtils.getArchive(Flux.class);
try {
String path = archive.getUrl().toString();
if (path.endsWith("!/")) {
path = path.substring(0, path.length() - 2);
}
path = StringUtils.getFilename(path);
if (path.startsWith("reactor-core-")) {
path = path.substring("reactor-core-".length());
}
if (path.endsWith(".jar")) {
path = path.substring(0, path.length() - ".jar".length());
}
version = path;
}
catch (MalformedURLException e) {
// ignore
}
private List<File> resolveParent() {
DependencyResolver resolver = DependencyResolver.instance();
List<Dependency> dependencies = resolver
.dependencies(new ClassPathResource("core-pom.xml"));
List<File> resolved = new ArrayList<>();
for (Dependency dependency : dependencies) {
resolved.add(resolver.resolve(dependency));
}
if (version == null) {
version = DEFAULT_REACTOR_VERSION;
}
return "io.projectreactor:reactor-core:" + version;
return resolved;
}
}

View File

@@ -23,8 +23,10 @@ import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.loader.thin.ArchiveUtils;
import org.springframework.cloud.deployer.spi.app.AppDeployer;
import org.springframework.cloud.deployer.spi.core.AppDefinition;
import org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;
import org.springframework.context.support.LiveBeansView;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.web.bind.annotation.DeleteMapping;
@@ -62,10 +64,10 @@ public class FunctionAdminController implements CommandLineRunner {
}
@DeleteMapping(path = "/{name}")
public Map<String, Object> undeploy(@PathVariable String name,
@RequestParam String path) throws Exception {
public Map<String, Object> undeploy(@PathVariable String name) throws Exception {
String id = names.get(name);
if (id == null) {
// TODO: Convert to 404
throw new IllegalStateException("No such app");
}
deployer.undeploy(id);
@@ -78,8 +80,8 @@ public class FunctionAdminController implements CommandLineRunner {
public Map<String, Object> deployed() {
Map<String, Object> result = new LinkedHashMap<>();
for (String name : names.keySet()) {
result.put(name, new DeployedArtifact(name, names.get(name),
deployed.get(names.get(name))));
String id = names.get(name);
result.put(name, new DeployedArtifact(name, id, deployed.get(id)));
}
return result;
}
@@ -93,12 +95,14 @@ public class FunctionAdminController implements CommandLineRunner {
Resource resource = new FileSystemResource(
ArchiveUtils.getArchiveRoot(ArchiveUtils.getArchive(path)));
AppDefinition definition = new AppDefinition(resource.getFilename(),
Collections.emptyMap());
Collections.singletonMap(LiveBeansView.MBEAN_DOMAIN_PROPERTY_NAME,
"functions." + name));
AppDeploymentRequest request = new AppDeploymentRequest(definition, resource,
Collections.emptyMap(), Arrays.asList(args));
Collections.singletonMap(AppDeployer.GROUP_PROPERTY_KEY, "functions"),
Arrays.asList(args));
String deployed = deployer.deploy(request);
this.deployed.put(deployed, path);
this.names.put(deployed, name);
this.names.put(name, deployed);
return deployed;
}
}

View File

@@ -0,0 +1,49 @@
<?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-function-deployer</artifactId>
<packaging>jar</packaging>
<name>spring-cloud-function-deployer</name>
<description>Spring Cloud Function Web Support</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.3.RELEASE</version>
</parent>
<properties>
<reactor.version>3.0.7.RELEASE</reactor.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Dalston.BUILD-SNAPSHOT</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>