Skip to content
This repository has been archived by the owner on Dec 15, 2021. It is now read-only.

Commit

Permalink
JVM-Runtime
Browse files Browse the repository at this point in the history
  • Loading branch information
caraboides committed Jul 11, 2018
1 parent 7de2447 commit 78ab79f
Show file tree
Hide file tree
Showing 27 changed files with 580 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .gitignore
Expand Up @@ -95,6 +95,11 @@ bld/
[Oo]bj/
[Ll]og/

# JVM
.classpath
.project
.settings

# Visual Studio 2015 cache/options directory
.vs/

Expand Down
3 changes: 3 additions & 0 deletions docker/runtime/jvm/Dockerfile
@@ -0,0 +1,3 @@
FROM openjdk:8-jre-alpine

CMD ["java", "-cp", "/kubeless/*", "io.kubeless.Handler"]
33 changes: 33 additions & 0 deletions docker/runtime/jvm/Dockerfile.init
@@ -0,0 +1,33 @@
FROM openjdk:8-jdk-alpine

COPY ./runtime /usr/src/myapp
WORKDIR /usr/src/myapp
ENV GRADLE_HOME /opt/gradle
ENV GRADLE_VERSION 4.8

ARG GRADLE_DOWNLOAD_SHA256=f3e29692a8faa94eb0b02ebf36fa263a642b3ae8694ef806c45c345b8683f1ba
RUN set -o errexit -o nounset \
&& echo "Installing build dependencies" \
&& apk add --no-cache --virtual .build-deps \
ca-certificates \
openssl \
unzip \
\
&& echo "Downloading Gradle" \
&& wget -O gradle.zip "https://services.gradle.org/distributions/gradle-${GRADLE_VERSION}-bin.zip" \
\
&& echo "Checking download hash" \
&& echo "${GRADLE_DOWNLOAD_SHA256} *gradle.zip" | sha256sum -c - \
\
&& echo "Installing Gradle" \
&& unzip gradle.zip \
&& rm gradle.zip \
&& mkdir /opt \
&& mv "gradle-${GRADLE_VERSION}" "${GRADLE_HOME}/" \
&& ln -s "${GRADLE_HOME}/bin/gradle" /usr/bin/gradle \
\
&& apk del .build-deps \
&& gradle shadowJar && cp build/libs/runtime-0.1-all.jar /opt \
&& rm -rf /opt/gradle && rm -rf /usr/src/myapp


15 changes: 15 additions & 0 deletions docker/runtime/jvm/Makefile
@@ -0,0 +1,15 @@
.PHONY: build-all
init-image:
docker build -f Dockerfile.init -t kubeless/jvm-init:1.8 .

runtime-image:
docker build -f Dockerfile -t kubeless/jvm:1.8 .

push-init:
docker push kubeless/jvm-init:1.8

push-runtime:
docker push kubeless/jvm:1.8

build-all: init-image runtime-image
push-all: push-init push-runtime
1 change: 1 addition & 0 deletions docker/runtime/jvm/Readme.md
@@ -0,0 +1 @@
# Generic Runtime for JVM base languages
2 changes: 2 additions & 0 deletions docker/runtime/jvm/runtime/.gitignore
@@ -0,0 +1,2 @@
.gradle
/build/
33 changes: 33 additions & 0 deletions docker/runtime/jvm/runtime/build.gradle
@@ -0,0 +1,33 @@
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.github.jengelman.gradle.plugins:shadow:2.0.4'
}
}

apply plugin: 'java'
apply plugin: 'com.github.johnrengelman.shadow'
version = '0.1'
jar {
manifest {
attributes 'Implementation-Title': 'JVM-Runtime',
'Implementation-Version': version
}
}


repositories {
mavenCentral()
}

dependencies {
compile group: 'io.prometheus', name: 'simpleclient', version: '0.3.0'
compile group: 'io.prometheus', name: 'simpleclient_hotspot', version: '0.3.0'
compile group: 'io.prometheus', name: 'simpleclient_httpserver', version: '0.3.0'
compile group: 'io.prometheus', name: 'simpleclient_pushgateway', version: '0.3.0'
compile group: 'log4j', name: 'log4j', version: '1.2.17'

testCompile group: 'junit', name: 'junit', version: '4.+'
}
34 changes: 34 additions & 0 deletions docker/runtime/jvm/runtime/src/main/java/io/kubeless/Context.java
@@ -0,0 +1,34 @@
/*
Copyright (c) 2016-2017 Bitnami
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 io.kubeless;

/**
* Context includes information about the function environment
*/
public class Context {
String functionName;
String timeout;
String runtime;
String memoryLimit;

public Context(String functionName, String timeout, String runtime, String memoryLimit) {
this.functionName = functionName;
this.timeout = timeout;
this.runtime = runtime;
this.memoryLimit = memoryLimit;
}
}
36 changes: 36 additions & 0 deletions docker/runtime/jvm/runtime/src/main/java/io/kubeless/Event.java
@@ -0,0 +1,36 @@
/*
Copyright (c) 2016-2017 Bitnami
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 io.kubeless;

/**
* Event includes information about the event source
*/
public class Event {
String Data;
String EventID;
String EventType;
String EventTime;
String EventNamespace;

public Event(String data, String eventId, String eventType, String eventTime, String eventNamespace) {
this.Data = data;
this.EventID = eventId;
this.EventType = eventType;
this.EventTime = eventTime;
this.EventNamespace = eventNamespace;
}
}
182 changes: 182 additions & 0 deletions docker/runtime/jvm/runtime/src/main/java/io/kubeless/Handler.java
@@ -0,0 +1,182 @@
/*
Copyright (c) 2016-2017 Bitnami
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 io.kubeless;

import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import com.sun.net.httpserver.Headers;

import java.io.IOException;
import java.io.OutputStream;
import java.io.InputStreamReader;
import java.io.BufferedReader;
import java.lang.reflect.Method;
import java.lang.reflect.InvocationTargetException;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.stream.Collectors;
import io.prometheus.client.Counter;
import io.prometheus.client.Histogram;
import io.kubeless.Event;
import io.kubeless.Context;
import org.apache.log4j.Logger;
import org.apache.log4j.BasicConfigurator;

public class Handler {

static String className = System.getenv("MOD_NAME").replaceAll("_",".");
static String methodName = System.getenv("FUNC_HANDLER");
static String timeout = System.getenv("FUNC_TIMEOUT");
static String runtime = System.getenv("FUNC_RUNTIME");
static String memoryLimit = System.getenv("FUNC_MEMORY_LIMIT");
static Method method;
static Object obj;
static Logger logger = Logger.getLogger(Handler.class.getName());

static final Counter requests = Counter.build().name("function_calls_total").help("Total function calls.").register();
static final Counter failures = Counter.build().name("function_failures_total").help("Total function call failuress.").register();
static final Histogram requestLatency = Histogram.build().name("function_duration_seconds").help("Duration of time user function ran in seconds.").register();

public static void main(String[] args) {
logger.info("Start Function Handler: ");
logger.info("className:"+className);
logger.info("methodName:"+methodName);
logger.info("timeout:"+timeout);
logger.info("memoryLimit:"+memoryLimit);



BasicConfigurator.configure();

String funcPort = System.getenv("FUNC_PORT");
if(funcPort == null || funcPort.isEmpty()) {
funcPort = "8080";
}
int port = Integer.parseInt(funcPort);
try {
HttpServer server = HttpServer.create(new InetSocketAddress(port), 0);
server.createContext("/", new FunctionHandler());
server.createContext("/healthz", new HealthHandler());
server.setExecutor(java.util.concurrent.Executors.newFixedThreadPool(50));
server.start();

Class<?> c = Class.forName(className);
obj = c.newInstance();
method = c.getMethod(methodName, io.kubeless.Event.class, io.kubeless.Context.class);
} catch (Exception e) {
failures.inc();
if (e instanceof ClassNotFoundException) {
logger.error("Class: " + className + " not found");
} else if (e instanceof NoSuchMethodException) {
logger.error("Method: " + methodName + " not found");
} else if (e instanceof java.io.IOException) {
logger.error("Failed to starting listener.");
} else {
logger.error("An exception occured running Class: " + className + " method: " + methodName);
e.printStackTrace();
}
}
}

static class FunctionHandler implements HttpHandler {

@Override
public void handle(HttpExchange he) throws IOException {
Histogram.Timer requestTimer = requestLatency.startTimer();
try {
requests.inc();

InputStreamReader reader = new InputStreamReader(he.getRequestBody(), StandardCharsets.UTF_8.name());
BufferedReader br = new BufferedReader(reader);
String requestBody = br.lines().collect(Collectors.joining());
br.close();
reader.close();

Headers headers = he.getRequestHeaders();
String eventId = getEventId(headers);
String eventType = getEventType(headers);
String eventTime = getEventTime(headers);
String eventNamespace = getEventNamespace(headers);

Event event = new Event(requestBody, eventId, eventType, eventTime, eventNamespace);
Context context = new Context(methodName, timeout, runtime, memoryLimit);

Object returnValue = Handler.method.invoke(Handler.obj, event, context);
String response = (String)returnValue;
logger.info("Response: " + response);
he.sendResponseHeaders(200, response.length());
OutputStream os = he.getResponseBody();
os.write(response.getBytes());
os.close();
} catch (Exception e) {
failures.inc();
if (e instanceof ClassNotFoundException) {
logger.error("Class: " + className + " not found");
} else if (e instanceof NoSuchMethodException) {
logger.error("Method: " + methodName + " not found");
} else if (e instanceof InvocationTargetException) {
logger.error("Failed to Invoke Method: " + methodName);
} else if (e instanceof InstantiationException) {
logger.error("Failed to instantiate method: " + methodName);
} else {
logger.error("An exception occured running Class: " + className + " method: " + methodName);
e.printStackTrace();
}
} finally {
requestTimer.observeDuration();
}
}

private String getEventType(Headers headers) {
return getByName(headers,"event-type");
}

private String getEventTime(Headers headers) {
return getByName(headers,"event-time");
}

private String getEventNamespace(Headers headers) {
return getByName(headers,"event-namespace");
}

private String getEventId(Headers headers) {
return getByName(headers,"event-id");
}

private String getByName(Headers headers, String name) {
if (headers.containsKey(name)) {
List<String> values = headers.get(name);
if (values != null) {
return values.get(0);
}
}
return "";
}
}

static class HealthHandler implements HttpHandler {
@Override
public void handle(HttpExchange t) throws IOException {
String response = "OK";
t.sendResponseHeaders(200, response.length());
OutputStream os = t.getResponseBody();
os.write(response.getBytes());
os.close();
}
}
}
8 changes: 8 additions & 0 deletions docker/runtime/jvm/runtime/src/resources/log4j.properties
@@ -0,0 +1,8 @@
# Root logger option
log4j.rootLogger=INFO, stdout

# Redirect log messages to console
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n
6 changes: 6 additions & 0 deletions examples/Makefile
Expand Up @@ -800,6 +800,12 @@ get-java-deps-verify:
kubeless function call get-java-deps --data '{"hello": "world"}'
kubectl logs -l function=get-java-deps | grep -q '.*Hello.*world! Current local time is:'

get-jvm-java:
kubeless function deploy get-jvm-java --runtime jvm1.8 --from-file jvm/java/build/libs/jvm-test-0.1-all.jar --handler io_ino_Handler.sayHello

get-jvm-java-verify:
kubeless function call get-jvm-java | grep "Hello world"

get-nodejs-distroless:
kubeless function deploy get-nodejs-distroless --runtime nodejs_distroless8 --handler helloget.foo --from-file nodejs/helloget.js

Expand Down
5 changes: 5 additions & 0 deletions examples/jvm/java/Readme.md
@@ -0,0 +1,5 @@
# Java on runtime JVM

`gradle shadowJar` Build the jar with all deps

`kubeless function deploy test --runtime jvm1.8 --from-file build/libs/jvm-test-0.1-all.jar --handler io_ino_Handler.sayHello` The package name use `_` instead of `.` for the path.

0 comments on commit 78ab79f

Please sign in to comment.