diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..e0f15db --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "java.configuration.updateBuildConfiguration": "automatic" +} \ No newline at end of file diff --git a/src/services/examples/backend.json b/src/services/examples/backend.json new file mode 100644 index 0000000..b35e42f --- /dev/null +++ b/src/services/examples/backend.json @@ -0,0 +1,25 @@ +{ + "type": "java", + "endpoints": { + "http": { + "/cart/checkout": ["sleep,1000", "sql://backend-db/?query=SELECT * from carts", ["http://ext-payment-1/pay", "http://ext-payment-2/pay"]], + "/cart/add": ["sql://backend-db/?query=SELECT * from carts"], + "error": [{"call": "error,500,Cart not found", "probability": 0.5}], + "/data": ["sleep,50", "log,warn,do some logging", "log,more logging", { + "call": "data", + "id": "price", + "type": "double", + "value": [1,2,3] + }, { + "call": "data", + "id": "randomThingy", + "chance": "integer,min:5,max:13" + }, { + "call": "data", + "id": "stuff", + "chance": "letter,casing:upper" + }] + } + }, + "name": "backend" +} diff --git a/src/services/examples/database.json b/src/services/examples/database.json new file mode 100644 index 0000000..0b7dcbd --- /dev/null +++ b/src/services/examples/database.json @@ -0,0 +1 @@ +{"type":"mysql","databases":{"shop":{"carts":["id","name","value"],"customers":["id","name","email"]}},"name":"backend-db"} diff --git a/src/services/examples/frontend.json b/src/services/examples/frontend.json new file mode 100644 index 0000000..ff506d4 --- /dev/null +++ b/src/services/examples/frontend.json @@ -0,0 +1,41 @@ +{ + "type": "nodejs", + "options": { + "connectionDelay": 500, + "httpLibrary": "request-promise" + }, + "endpoints": { + "http": { + "/logo.png": ["sleep,1000"], + "/script.js": ["sleep,5000"], + "/checkout": ["http://backend/cart/checkout", "image,logo.png", "script,script.js","ajax,cache"], + "/addtocart": ["http://backend/cart/add", "slow,1500"], + "/cache": [["sleep,5000","sleep,5"], "cache,1024"], + "/schedule": ["sleep,50", { + "call": "error,500,oops", + "schedule": "0-30 * * * * * *" + }], + "/test": [{ + "call": "http://localhost:9876/cache", + "catchExceptions": false, + "remoteTimeout": 2000 + }], + "/data": ["sleep,50", "log,warn,do some logging", "log,more logging", { + "call": "data", + "id": "price", + "type": "double", + "value": [1,2,3] + }, { + "call": "data", + "id": "randomThingy", + "chance": "integer,min:5,max:13" + }, { + "call": "data", + "id": "stuff", + "chance": "letter,casing:upper" + }], + "/script": ["code,sample.js"] + } + }, + "name": "frontend" +} diff --git a/src/services/java/.gitignore b/src/services/java/.gitignore new file mode 100644 index 0000000..8a33059 --- /dev/null +++ b/src/services/java/.gitignore @@ -0,0 +1,7 @@ +.idea +*.class +target/maven-* +extras/*.jar +target/ +node.log +javanode.iml \ No newline at end of file diff --git a/src/services/java/Dockerfile b/src/services/java/Dockerfile new file mode 100644 index 0000000..8aeb48d --- /dev/null +++ b/src/services/java/Dockerfile @@ -0,0 +1,15 @@ +FROM maven:3-jdk-11 AS build +WORKDIR /home/app +COPY src /home/app/src +COPY pom.xml /home/app +RUN mvn -f /home/app/pom.xml clean package + +FROM openjdk:11-jre +RUN mkdir -p /app/dependency-jars +WORKDIR /app +COPY --from=build /home/app/target /app/ +COPY entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypointk8s.sh +COPY extras/* /app/ +EXPOSE 8080 +CMD ["/entrypoint.sh"] diff --git a/src/services/java/entrypoint.sh b/src/services/java/entrypoint.sh new file mode 100755 index 0000000..08bb671 --- /dev/null +++ b/src/services/java/entrypoint.sh @@ -0,0 +1,4 @@ +#!/bin/bash +echo java -jar "javanode-1.0-SNAPSHOT.jar" 8080 +APP_CONFIG="$( + + 4.0.0 + com.cisco.open.appsimulator.java + javanode + jar + 1.0-SNAPSHOT + javanode + + 11.0.0.beta3 + + + + jakarta.servlet + jakarta.servlet-api + 5.0.0 + + + mysql + mysql-connector-java + 5.1.49 + + + org.mongodb + mongo-java-driver + 3.12.7 + + + org.eclipse.jetty + jetty-server + ${jettyVersion} + + + org.eclipse.jetty + jetty-servlet + ${jettyVersion} + + + org.eclipse.jetty + jetty-client + ${jettyVersion} + + + javax.json + javax.json-api + 1.1 + + + org.glassfish + javax.json + 1.1 + + + net.sf.ehcache + ehcache + 2.10.5 + + + org.slf4j + slf4j-api + 2.0.16 + + + ch.qos.logback + logback-classic + 1.5.12 + + + + + + org.codehaus.mojo + exec-maven-plugin + 1.1 + + + + java + + + + + com.cisco.open.appsimulator.JavaNode + + 8080 + + + + + org.apache.maven.plugins + maven-jar-plugin + 3.1.0 + + + + true + com.cisco.open.appsimulator.JavaNode + dependency-jars/ + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + 2.5.1 + + + copy-dependencies + package + + copy-dependencies + + + + runtime + ${project.build.directory}/dependency-jars/ + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.0 + + 11 + + + + + diff --git a/src/services/java/run.sh b/src/services/java/run.sh new file mode 100755 index 0000000..5bbba48 --- /dev/null +++ b/src/services/java/run.sh @@ -0,0 +1,2 @@ +#!/bin/bash +env APP_CONFIG="$(<../examples/backend.json)" mvn -e compile exec:java diff --git a/src/services/java/src/main/java/com/cisco/open/appsimulator/HttpException.java b/src/services/java/src/main/java/com/cisco/open/appsimulator/HttpException.java new file mode 100644 index 0000000..14d4cbc --- /dev/null +++ b/src/services/java/src/main/java/com/cisco/open/appsimulator/HttpException.java @@ -0,0 +1,17 @@ +package com.cisco.open.appsimulator; + +import java.io.IOException; + +public class HttpException extends IOException +{ + protected int code; + + public HttpException(int code, String message) { + super(message); + this.code = code; + } + + public int getCode() { + return this.code; + } +} diff --git a/src/services/java/src/main/java/com/cisco/open/appsimulator/JavaNode.java b/src/services/java/src/main/java/com/cisco/open/appsimulator/JavaNode.java new file mode 100644 index 0000000..60ed9b7 --- /dev/null +++ b/src/services/java/src/main/java/com/cisco/open/appsimulator/JavaNode.java @@ -0,0 +1,337 @@ +package com.cisco.open.appsimulator; + +import net.sf.ehcache.Element; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.servlet.ServletHandler; + +import javax.json.*; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.StringReader; +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.net.URL; +import java.net.HttpURLConnection; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.concurrent.ThreadLocalRandom; + +import net.sf.ehcache.Cache; +import net.sf.ehcache.CacheManager; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.core.util.StatusPrinter; + +public class JavaNode { + + protected static Cache cache; + + private static final Logger logger = LoggerFactory.getLogger(JavaNode.class); + + public static void main(String[] args) throws Exception { + + int port = 8080; + //CacheManager cacheManager; + + if (args.length > 0) { + port = Integer.parseInt(args[0]); + } + + LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory(); + StatusPrinter.print(loggerContext); + + logger.info("Reading AppConfig"); + String envAppConfig = System.getenv("APP_CONFIG"); + logger.info("Using config {}", envAppConfig); + if (envAppConfig == null) { + envAppConfig = "{\"endpoints\":{\"http\":{}}}"; + } + + JsonReader jsonReader = Json.createReader(new StringReader(envAppConfig)); + JsonObject config = jsonReader.readObject(); + + CacheManager cm = CacheManager.getInstance(); + + cm.addCache("cache1"); + + cache = cm.getCache("cache1"); + + logger.info("Starting Server on port {}", port); + Server server = new Server(port); + ServletHandler handler = new ServletHandler(); + server.setHandler(handler); + logger.info("NodeServlet setConfig"); + NodeServlet.setConfig(config); + logger.info("add Servlet mapping"); + handler.addServletWithMapping(NodeServlet.class, "/*"); + + server.start(); + logger.info("Join server"); + server.join(); + } + + // @SuppressWarnings("serial") + public static class NodeServlet extends HttpServlet { + protected static JsonObject config; + protected static JsonObject endpoints; + + public static void setConfig(JsonObject config) { + NodeServlet.config = config; + NodeServlet.endpoints = config.getJsonObject("endpoints").getJsonObject("http"); + logger.info("Config: {}", config.toString()); + logger.info("Config: {}", config.getJsonObject("endpoints").getJsonObject("http").toString()); + } + + protected String buildResponse(int timeout) { + long start = System.currentTimeMillis(); + long finish = start; + String response = ""; + while (finish - start < timeout) { + response += " "; + finish = System.currentTimeMillis(); + } + return response.length() + "slow response"; + } + + protected String loadFromCache(int timeout) { + long start = System.currentTimeMillis(); + long finish = start; + int i = 0; + Integer element = Integer.valueOf(0); + while (finish - start < timeout) { + i++; + element = Integer.valueOf(i); + cache.putIfAbsent(new Element(element, i)); + finish = System.currentTimeMillis(); + } + return "Cache result: " + cache.get(element).toString(); + } + + protected String queryDatabase(String call, boolean catchExceptions, int remoteTimeout) + throws IOException { + try { + String url = "jdbc:my" + call.split("\\?")[0] + "?useSSL=false"; + Connection connection = DriverManager.getConnection(url, "root", "root"); + + PreparedStatement stmt = connection.prepareStatement(call.split("\\?query=")[1]); + stmt.execute(); + + connection.close(); + } catch (SQLException e) { + if (catchExceptions) { + return e.getMessage(); + } + throw new IOException(e.getMessage()); + } + return "Database query executed: " + call; + } + + protected String callRemote(String call, boolean catchExceptions, int remoteTimeout) + throws IOException { + try { + URL url = new URL(call); + // return new Scanner( url.openStream() ).useDelimiter( "\\Z" ).next(); + HttpURLConnection con = (HttpURLConnection) url.openConnection(); + + con.setConnectTimeout(remoteTimeout); + con.setReadTimeout(remoteTimeout); + con.setRequestProperty("Content-Type", "application/json"); + + BufferedReader in = new BufferedReader( + new InputStreamReader(con.getInputStream())); + String inputLine; + StringBuffer response = new StringBuffer(); + + while ((inputLine = in.readLine()) != null) { + response.append(inputLine); + } + in.close(); + + return response.toString(); + + } catch (IOException e) { + if (catchExceptions) { + return e.getMessage(); + } + throw e; + } + } + + protected String logMessage(String level, String msg) { + switch (level) { + case "warn": + logger.warn(msg); + break; + case "error": + logger.error(msg); + break; + case "debug": + logger.debug(msg); + break; + case "trace": + logger.trace(msg); + break; + default: + logger.info(msg); + } + return "Logged (" + level + "): " + msg; + } + + protected String processCall(String call, boolean catchExceptions, int remoteTimeout) + throws IOException { + logger.info("Processing call: {}", call); + if (call.startsWith("sleep")) { + int timeout = Integer.parseInt(call.split(",")[1].trim()); + try { + Thread.sleep(timeout); + } catch (InterruptedException e) { + + } + return "Slept for " + timeout; + } + + if (call.startsWith("log")) { + String[] log = call.split(","); + if (log.length > 2) { + return this.logMessage(log[1], log[2]); + } + return this.logMessage("info", log[1]); + } + + if (call.startsWith("slow")) { + int timeout = Integer.parseInt(call.split(",")[1]); + return this.buildResponse(timeout); + } + + if (call.startsWith("cache")) { + int timeout = Integer.parseInt(call.split(",")[1]); + return this.loadFromCache(timeout); + } + + if (call.startsWith("http://")) { + return this.callRemote(call, catchExceptions, remoteTimeout); + } + if (call.startsWith("sql://")) { + return this.queryDatabase(call, catchExceptions, remoteTimeout); + } + if (call.startsWith("error")) { + throw new HttpException(500, "error"); + } + if (call.startsWith("image")) { + String src = call.split(",")[1]; + return ""; + } + if (call.startsWith("script")) { + String src = call.split(",")[1]; + return ""; + } + if (call.startsWith("ajax")) { + String src = call.split(",")[1]; + return ""; + } + return ":" + call + " is not supported"; + } + + protected String processData(JsonObject data) { + return "Data not processed: not implemented"; + } + + protected String preProcessCall(JsonValue call) throws IOException { + + boolean catchExceptions = true; + int remoteTimeout = Integer.MAX_VALUE; + + if (call.getValueType() == JsonValue.ValueType.ARRAY) { + JsonArray arr = (JsonArray) call; + int index = ThreadLocalRandom.current().nextInt(arr.size()); + call = arr.get(index); + } + if (call.getValueType() == JsonValue.ValueType.OBJECT) { + JsonObject obj = (JsonObject) call; + call = obj.getJsonString("call"); + + if (((JsonString) call).getString().startsWith("data")) { + return this.processData(obj); + } + + if (obj.containsKey("probability")) { + double probability = obj.getJsonNumber("probability").doubleValue(); + if (probability * 100 < ThreadLocalRandom.current().nextInt(100)) { + return call + " was not probable"; + } + } + if (obj.containsKey("catchExceptions")) { + catchExceptions = obj.getBoolean("catchExceptions"); + } + if (obj.containsKey("remoteTimeout")) { + remoteTimeout = obj.getInt("remoteTimeout"); + } + } + return this.processCall(((JsonString) call).getString(), catchExceptions, remoteTimeout); + } + + public void handleEndpoint(HttpServletResponse response, JsonArray endpoint) throws IOException { + response.setStatus(HttpServletResponse.SC_OK); + + StringBuilder result = new StringBuilder(); + + for (JsonValue entry : endpoint) { + result.append(this.preProcessCall(entry)); + } + + response.getWriter().println(result); + } + + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + String endpoint = request.getRequestURI(); + logger.info("Endpoint: {}", endpoint); + + String contentType = request.getContentType(); + + if ("application/json".equals(contentType) + || "javascript".equals(request.getParameter("output"))) { + response.setContentType(contentType); + } else if (contentType == null) { + response.setContentType("text/html;charset=utf-8"); + } + + if (request.getParameter("uniqueSessionId") != null) { + // not implemented + } + + try { + if (NodeServlet.endpoints.containsKey(endpoint)) { + logger.info("handling endpoint {} now containsKey", endpoint); + this.handleEndpoint(response, NodeServlet.endpoints.getJsonArray(endpoint)); + } else if (NodeServlet.endpoints.containsKey(endpoint.substring(1))) { + logger.info("handling endpoint {} now substring", endpoint); + this.handleEndpoint(response, + NodeServlet.endpoints.getJsonArray(endpoint.substring(1))); + } else { + logger.info("handling endpoint {} notfound", endpoint); + response.setStatus(HttpServletResponse.SC_NOT_FOUND); + response.getWriter().println(404); + } + } catch (HttpException e) { + response.setStatus(e.getCode()); + response.getWriter().println(e.getMessage()); + } catch (IOException e) { + response.setStatus(500); + response.getWriter().println(e.getMessage()); + } + } + + } + +} diff --git a/src/services/java/src/main/resources/logback.xml b/src/services/java/src/main/resources/logback.xml new file mode 100644 index 0000000..686aa46 --- /dev/null +++ b/src/services/java/src/main/resources/logback.xml @@ -0,0 +1,27 @@ + + + + + + + + + %date{ISO8601} [%thread] %-5level %logger - %msg%n + + + + + + ${LOG_DIRECTORY:-.}/node.log + + + %date{ISO8601} [%thread] %-5level %logger - %msg%n + + + + + + + + + \ No newline at end of file