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

Commit

Permalink
java support with prometheus and dependency management with maven (#738)
Browse files Browse the repository at this point in the history
* basic java support with prometheus and dependency management with maven

* pass the event and context details to called function

* fix NPE when all expected event details are not there

* buid container support to run java functions with deps

* addressing review comments

* review fixes

* docs for java runtime

* update the runtimeImage and initImage for Java
  • Loading branch information
murali-reddy authored May 17, 2018
1 parent baa1457 commit d379421
Show file tree
Hide file tree
Showing 21 changed files with 607 additions and 0 deletions.
5 changes: 5 additions & 0 deletions docker/runtime/java/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
FROM openjdk:8-jdk-alpine

USER 1000

CMD ["java", "-cp", "/kubeless/lib/*:/kubeless/handler/target/handler-1.0-SNAPSHOT.jar:/kubeless/function/target/function-1.0-SNAPSHOT.jar", "io.kubeless.Handler"]
5 changes: 5 additions & 0 deletions docker/runtime/java/Dockerfile.init
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
FROM openjdk:8-jdk-alpine

RUN apk add --no-cache maven

COPY . /usr/src/myapp
14 changes: 14 additions & 0 deletions docker/runtime/java/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
init-image:
docker build -f Dockerfile.init -t kubeless/java-init:1.8 .

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

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

push-runtime:
docker push kubeless/java:1.8

build-all: init-image runtime-image
push-all: push-init push-runtime
18 changes: 18 additions & 0 deletions docker/runtime/java/function/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<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>function</artifactId>
<name>function</name>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>io.kubeless</groupId>
<artifactId>params</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
<parent>
<groupId>io.kubeless</groupId>
<artifactId>kubeless</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package io.kubeless;

public class Example {
public String sayHello(io.kubeless.Event event, io.kubeless.Context context) {
return "Hello world!";
}
}
51 changes: 51 additions & 0 deletions docker/runtime/java/handler/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<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>handler</artifactId>
<name>handler</name>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>io.kubeless</groupId>
<artifactId>kubeless</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<dependencies>
<dependency>
<groupId>io.kubeless</groupId>
<artifactId>function</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>io.kubeless</groupId>
<artifactId>params</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>io.prometheus</groupId>
<artifactId>simpleclient</artifactId>
<version>0.3.0</version>
</dependency>
<!-- Hotspot JVM metrics-->
<dependency>
<groupId>io.prometheus</groupId>
<artifactId>simpleclient_hotspot</artifactId>
<version>0.3.0</version>
</dependency>
<!-- Exposition HTTPServer-->
<dependency>
<groupId>io.prometheus</groupId>
<artifactId>simpleclient_httpserver</artifactId>
<version>0.3.0</version>
</dependency>
<!-- Pushgateway exposition-->
<dependency>
<groupId>io.prometheus</groupId>
<artifactId>simpleclient_pushgateway</artifactId>
<version>0.3.0</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
</dependencies>
</project>
189 changes: 189 additions & 0 deletions docker/runtime/java/handler/src/main/java/io/kubeless/Handler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
/*
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");
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) {

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("io.kubeless."+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) {
if (headers.containsKey("event-type")) {
List<String> values = headers.get("event-type");
if (values != null) {
return values.get(0);
}
}
return "";
}

private String getEventTime(Headers headers) {
if (headers.containsKey("event-time")) {
List<String> values = headers.get("event-time");
if (values != null) {
return values.get(0);
}
}
return "";
}

private String getEventNamespace(Headers headers) {
if (headers.containsKey("event-namespace")) {
List<String> values = headers.get("event-namespace");
if (values != null) {
return values.get(0);
}
}
return "";
}

private String getEventId(Headers headers) {
if (headers.containsKey("event-id")) {
List<String> values = headers.get("event-id");
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/java/handler/src/resources/log4j.properties
Original file line number Diff line number Diff line change
@@ -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
11 changes: 11 additions & 0 deletions docker/runtime/java/params/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<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>params</artifactId>
<name>params</name>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>io.kubeless</groupId>
<artifactId>kubeless</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
</project>
34 changes: 34 additions & 0 deletions docker/runtime/java/params/src/main/java/io/kubeless/Context.java
Original file line number Diff line number Diff line change
@@ -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/java/params/src/main/java/io/kubeless/Event.java
Original file line number Diff line number Diff line change
@@ -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;
}
}
Loading

0 comments on commit d379421

Please sign in to comment.