Java env alpha (#656)
This change adds support for JVM based environment for running Java functions. This is alpha release of JVM environment and might undergo changes.
This commit is contained in:
@@ -0,0 +1,7 @@
|
|||||||
|
.springBeans
|
||||||
|
.project
|
||||||
|
.mvn
|
||||||
|
.settings
|
||||||
|
.classpath
|
||||||
|
target/
|
||||||
|
bin/
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
FROM maven:3.5-jdk-8 as BUILD
|
||||||
|
COPY src /usr/src/myapp/src
|
||||||
|
COPY pom.xml /usr/src/myapp
|
||||||
|
RUN mvn -f /usr/src/myapp/pom.xml clean package
|
||||||
|
|
||||||
|
FROM openjdk:8-jdk-alpine
|
||||||
|
VOLUME /tmp
|
||||||
|
COPY --from=BUILD /usr/src/myapp/target/env-java-0.0.1-SNAPSHOT.jar /app.jar
|
||||||
|
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar","--server.port=8888"]
|
||||||
|
EXPOSE 8888
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
<?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>
|
||||||
|
|
||||||
|
<groupId>io.fission</groupId>
|
||||||
|
<artifactId>env-java</artifactId>
|
||||||
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-parent</artifactId>
|
||||||
|
<version>2.0.1.RELEASE</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.fission</groupId>
|
||||||
|
<artifactId>fission-java-core</artifactId>
|
||||||
|
<version>0.0.2-SNAPSHOT</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
<!-- Adding Sonatype repository to pull snapshots -->
|
||||||
|
<repositories>
|
||||||
|
<repository>
|
||||||
|
<id>fission-java-core</id>
|
||||||
|
<name>fission-java-core-snapshot</name>
|
||||||
|
<url>https://oss.sonatype.org/content/repositories/snapshots/</url>
|
||||||
|
</repository>
|
||||||
|
</repositories>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package io.fission;
|
||||||
|
|
||||||
|
public class FunctionLoadRequest {
|
||||||
|
|
||||||
|
private String filepath;
|
||||||
|
private String functionName;
|
||||||
|
private String url;
|
||||||
|
|
||||||
|
String getFilepath() {
|
||||||
|
return filepath;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setFilepath(String filepath) {
|
||||||
|
this.filepath = filepath;
|
||||||
|
}
|
||||||
|
|
||||||
|
String getUrl() {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setUrl(String url) {
|
||||||
|
this.url = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFunctionName() {
|
||||||
|
return functionName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFunctionName(String functionName) {
|
||||||
|
this.functionName = functionName;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
package io.fission;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.net.URLClassLoader;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
import java.util.jar.JarEntry;
|
||||||
|
import java.util.jar.JarFile;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import org.springframework.boot.*;
|
||||||
|
import org.springframework.boot.autoconfigure.*;
|
||||||
|
import org.springframework.http.RequestEntity;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import io.fission.Function;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@EnableAutoConfiguration
|
||||||
|
public class Server {
|
||||||
|
|
||||||
|
private Function fn;
|
||||||
|
|
||||||
|
private static final int CLASS_LENGTH = 6;
|
||||||
|
|
||||||
|
private static Logger logger = Logger.getGlobal();
|
||||||
|
|
||||||
|
@RequestMapping(value = "/", method = { RequestMethod.GET, RequestMethod.POST, RequestMethod.DELETE,
|
||||||
|
RequestMethod.PUT })
|
||||||
|
ResponseEntity<Object> home(RequestEntity<?> req) {
|
||||||
|
if (fn == null) {
|
||||||
|
return ResponseEntity.badRequest().body("Container not specialized");
|
||||||
|
} else {
|
||||||
|
return ((ResponseEntity<Object>) ((Function) fn).call(req, null));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping(path = "/v2/specialize", consumes = "application/json")
|
||||||
|
ResponseEntity<String> specialize(@RequestBody FunctionLoadRequest req) {
|
||||||
|
long startTime = System.nanoTime();
|
||||||
|
File file = new File(req.getFilepath());
|
||||||
|
if (!file.exists()) {
|
||||||
|
return ResponseEntity.badRequest().body("/userfunc/user not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
String entryPoint = req.getFunctionName();
|
||||||
|
logger.log(Level.INFO, "Entrypoint class:" + entryPoint);
|
||||||
|
if (entryPoint == null) {
|
||||||
|
return ResponseEntity.badRequest().body("Entrypoint class is missing in the function");
|
||||||
|
}
|
||||||
|
|
||||||
|
JarFile jarFile = null;
|
||||||
|
ClassLoader cl = null;
|
||||||
|
try {
|
||||||
|
|
||||||
|
jarFile = new JarFile(file);
|
||||||
|
Enumeration<JarEntry> e = jarFile.entries();
|
||||||
|
URL[] urls = { new URL("jar:file:" + file + "!/") };
|
||||||
|
|
||||||
|
// TODO Check if the classloading can be improved for ex. use something like:
|
||||||
|
// Thread.currentThread().setContextClassLoader(cl);
|
||||||
|
if (this.getClass().getClassLoader() == null) {
|
||||||
|
cl = URLClassLoader.newInstance(urls);
|
||||||
|
} else {
|
||||||
|
cl = URLClassLoader.newInstance(urls, this.getClass().getClassLoader());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cl == null) {
|
||||||
|
return ResponseEntity.status(500).body("Failed to initialize the classloader");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load all dependent classes from libraries etc.
|
||||||
|
while (e.hasMoreElements()) {
|
||||||
|
JarEntry je = e.nextElement();
|
||||||
|
if (je.isDirectory() || !je.getName().endsWith(".class")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String className = je.getName().substring(0, je.getName().length() - CLASS_LENGTH);
|
||||||
|
className = className.replace('/', '.');
|
||||||
|
cl.loadClass(className);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Instantiate the function class
|
||||||
|
fn = (Function) cl.loadClass(entryPoint).newInstance();
|
||||||
|
|
||||||
|
} catch (MalformedURLException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return ResponseEntity.badRequest().body("Error loading the Function class file");
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return ResponseEntity.badRequest().body("Error loading Function or dependent class");
|
||||||
|
} catch (InstantiationException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return ResponseEntity.badRequest().body("Error creating a new instance of function class");
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return ResponseEntity.badRequest().body("Error creating a new instance of function class");
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return ResponseEntity.badRequest().body("Error reading the JAR file");
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
// cl.close();
|
||||||
|
jarFile.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return ResponseEntity.badRequest().body("Error closing the file while loading the class");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
long elapsedTime = System.nanoTime() - startTime;
|
||||||
|
logger.log(Level.INFO, "Specialize call done in: " + elapsedTime / 1000000 + " ms");
|
||||||
|
return ResponseEntity.ok("Done");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
SpringApplication.run(Server.class, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
.project
|
||||||
|
.settings
|
||||||
|
.classpath
|
||||||
|
target/
|
||||||
Executable
+5
@@ -0,0 +1,5 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# This script allows you to build the jar without needing Maven & JDK installed locally.
|
||||||
|
# You need docker, as it uses a Docker image to build source code
|
||||||
|
|
||||||
|
docker run -it --rm -v "$(pwd)":/usr/src/mymaven -w /usr/src/mymaven maven:3.5-jdk-8 mvn clean package
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
<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>
|
||||||
|
<groupId>io.fission</groupId>
|
||||||
|
<artifactId>hello-world</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<name>hello-world</name>
|
||||||
|
<url>http://maven.apache.org</url>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
<version>2.0.1.RELEASE</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.fission</groupId>
|
||||||
|
<artifactId>fission-java-core</artifactId>
|
||||||
|
<version>0.0.2-SNAPSHOT</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-assembly-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<descriptorRefs>
|
||||||
|
<descriptorRef>jar-with-dependencies</descriptorRef>
|
||||||
|
</descriptorRefs>
|
||||||
|
</configuration>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>make-assembly</id> <!-- this is used for inheritance merges -->
|
||||||
|
<phase>package</phase> <!-- bind to the packaging phase -->
|
||||||
|
<goals>
|
||||||
|
<goal>single</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
<!-- Adding Sonatype repository to pull snapshots -->
|
||||||
|
<repositories>
|
||||||
|
<repository>
|
||||||
|
<id>fission-java-core</id>
|
||||||
|
<name>fission-java-core-snapshot</name>
|
||||||
|
<url>https://oss.sonatype.org/content/repositories/snapshots/</url>
|
||||||
|
</repository>
|
||||||
|
</repositories>
|
||||||
|
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package io.fission;
|
||||||
|
|
||||||
|
import org.springframework.http.RequestEntity;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
|
||||||
|
import io.fission.Function;
|
||||||
|
import io.fission.Context;
|
||||||
|
|
||||||
|
public class HelloWorld implements Function {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResponseEntity<?> call(RequestEntity req, Context context) {
|
||||||
|
return ResponseEntity.ok("Hello World!");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package io.fission;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.RequestEntity;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
public class HelloWorldTest {
|
||||||
|
|
||||||
|
public void testResponse() {
|
||||||
|
HelloWorld hw = new HelloWorld();
|
||||||
|
RequestEntity request = null;
|
||||||
|
try {
|
||||||
|
request = RequestEntity.get(new URI("http://example.com/bar")).build();
|
||||||
|
} catch (URISyntaxException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
ResponseEntity resp = hw.call(request, null);
|
||||||
|
Assert.hasText(resp.getBody().toString(), "Hello World!");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -41,6 +41,7 @@ build_and_push_builder $BUILDER_IMAGE:$TAG
|
|||||||
ENV='python'
|
ENV='python'
|
||||||
|
|
||||||
build_and_push_env_runtime $ENV $REPO/$ENV-env:$TAG
|
build_and_push_env_runtime $ENV $REPO/$ENV-env:$TAG
|
||||||
|
build_and_push_env_runtime jvm $REPO/jvm-env:$TAG
|
||||||
|
|
||||||
build_and_push_env_builder $ENV $REPO/$ENV-env-builder:$TAG $BUILDER_IMAGE:$TAG
|
build_and_push_env_builder $ENV $REPO/$ENV-env-builder:$TAG $BUILDER_IMAGE:$TAG
|
||||||
|
|
||||||
|
|||||||
+55
@@ -0,0 +1,55 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ROOT=$(dirname $0)/../../..
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
fission fn delete --name hellon
|
||||||
|
fission fn delete --name hellop
|
||||||
|
fission env delete --name jvm
|
||||||
|
}
|
||||||
|
|
||||||
|
test_fn() {
|
||||||
|
echo "Checking for valid response"
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
response0=$(curl http://$FISSION_ROUTER/$1)
|
||||||
|
echo $response0 | grep -i $2
|
||||||
|
if [[ $? -eq 0 ]]; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
export -f test_fn
|
||||||
|
|
||||||
|
cd $ROOT/examples/jvm/java
|
||||||
|
|
||||||
|
log "Creating the jar from application"
|
||||||
|
#Using Docker to build Jar so that maven & other Java dependencies are not needed on CI server
|
||||||
|
docker run -it --rm -v "$(pwd)":/usr/src/mymaven -w /usr/src/mymaven maven:3.5-jdk-8 mvn clean package
|
||||||
|
|
||||||
|
log "Creating environment for Java"
|
||||||
|
fission env create --name jvm --image gcr.io/fission-ci/jvm-env:test --version 2 --extract=false
|
||||||
|
|
||||||
|
log "Creating pool manager & new deployment function for Java"
|
||||||
|
fission fn create --name hellop --deploy target/hello-world-1.0-SNAPSHOT-jar-with-dependencies.jar --env jvm --entrypoint io.fission.HelloWorld
|
||||||
|
fission fn create --name hellon --deploy target/hello-world-1.0-SNAPSHOT-jar-with-dependencies.jar --env jvm --executortype newdeploy --entrypoint io.fission.HelloWorld
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
log "Creating route for pool manager function"
|
||||||
|
fission route create --function hellop --url /hellop --method GET
|
||||||
|
|
||||||
|
log "Creating route for new deployment function"
|
||||||
|
fission route create --function hellon --url /hellon --method GET
|
||||||
|
|
||||||
|
log "Waiting for router & pools to catch up"
|
||||||
|
sleep 5
|
||||||
|
|
||||||
|
log "Testing pool manager function"
|
||||||
|
timeout 60 bash -c "test_fn hellop 'Hello'"
|
||||||
|
|
||||||
|
log "Testing new deployment function"
|
||||||
|
timeout 60 bash -c "test_fn hellon 'Hello'"
|
||||||
Reference in New Issue
Block a user