From 7a7ba871dc1250765b1e1804d9abf642497ee6b6 Mon Sep 17 00:00:00 2001 From: Dheeraj Mummareddy Date: Thu, 8 Feb 2018 12:03:00 -0500 Subject: [PATCH] serveless implementation using aws compute engine and serverless framework --- pom.xml | 4 + serverless/README.md | 122 +++++++++++++ serverless/etc/aws-black.png | Bin 0 -> 9262 bytes serverless/etc/azure-black.png | Bin 0 -> 4707 bytes serverless/etc/gcf-black.png | Bin 0 -> 7651 bytes serverless/etc/kubeless-logos-black.png | Bin 0 -> 8939 bytes serverless/etc/openwhisk-black.png | Bin 0 -> 5722 bytes serverless/etc/spotinst-logos-black-small.png | Bin 0 -> 5412 bytes serverless/etc/webtask-small-grayscale.png | Bin 0 -> 8420 bytes serverless/pom.xml | 113 ++++++++++++ serverless/serverless.yml | 49 ++++++ .../serverless/ApiGatewayResponse.java | 166 ++++++++++++++++++ .../com/iluwatar/serverless/LambdaInfo.java | 140 +++++++++++++++ .../serverless/api/LambdaInfoApiHandler.java | 83 +++++++++ .../src/main/resources/log4j.properties | 29 +++ .../api/LambdaInfoApiHandlerTest.java | 49 ++++++ 16 files changed, 755 insertions(+) create mode 100644 serverless/README.md create mode 100644 serverless/etc/aws-black.png create mode 100644 serverless/etc/azure-black.png create mode 100644 serverless/etc/gcf-black.png create mode 100644 serverless/etc/kubeless-logos-black.png create mode 100644 serverless/etc/openwhisk-black.png create mode 100644 serverless/etc/spotinst-logos-black-small.png create mode 100644 serverless/etc/webtask-small-grayscale.png create mode 100644 serverless/pom.xml create mode 100644 serverless/serverless.yml create mode 100644 serverless/src/main/java/com/iluwatar/serverless/ApiGatewayResponse.java create mode 100644 serverless/src/main/java/com/iluwatar/serverless/LambdaInfo.java create mode 100644 serverless/src/main/java/com/iluwatar/serverless/api/LambdaInfoApiHandler.java create mode 100644 serverless/src/main/resources/log4j.properties create mode 100644 serverless/src/test/java/com/iluwatar/serverless/api/LambdaInfoApiHandlerTest.java diff --git a/pom.xml b/pom.xml index 66c5fa850..3d1c5a18f 100644 --- a/pom.xml +++ b/pom.xml @@ -48,6 +48,9 @@ 3.3.0 1.7.21 1.1.7 + 1.1.0 + 1.0.0 + 2.8.5 abstract-factory @@ -154,6 +157,7 @@ eip-splitter eip-aggregator retry + serverless diff --git a/serverless/README.md b/serverless/README.md new file mode 100644 index 000000000..75fc40ac0 --- /dev/null +++ b/serverless/README.md @@ -0,0 +1,122 @@ +--- +layout: pattern +title: serverless +folder: serverless +permalink: /patterns/serverless/ +categories: Architectural +tags: + - Java + - Difficulty-Intermittent +--- + +## Serverless + +Serverless eliminates the need to plan for infrastructure and let's you focus on your +application. + +## Intent + +Whether to reduce your infrastructure costs, shrink the time you spend on ops tasks, +simplify your deployment processes, reach infinite scalability, serverless cuts time +to market in half. + +## Explanation + +Serverless computing is a cloud computing execution model in which the cloud provider +dynamically manages the allocation of machine resources. Pricing is based on the +actual amount of resources consumed by an application, rather than on pre-purchased +units of capacity. + +## Serverless framework + +[Serverless](https://serverless.com/) is a toolkit for deploying and operating serverless architectures. + +## (Function as a Service or "FaaS") + +The term ‘Serverless’ is confusing since with such applications there are both server +hardware and server processes running somewhere, but the difference to normal +approaches is that the organization building and supporting a ‘Serverless’ application + is not looking after the hardware or the processes - they are outsourcing this to a vendor. + +Some of the Serverless Cloud Providers are + +![https://serverless.com/framework/docs/providers/aws/](./etc/aws-black.png "aws") +![https://serverless.com/framework/docs/providers/azure/](./etc/azure-black.png "azure") +![https://serverless.com/framework/docs/providers/openwhisk/](./etc/openwhisk-black.png "openwhisk") +![https://serverless.com/framework/docs/providers/google/](./etc/gcf-black.png "google") +![https://serverless.com/framework/docs/providers/kubeless/](./etc/kubeless-logos-black.png "kubeless") +![https://serverless.com/framework/docs/providers/spotinst/](./etc/spotinst-logos-black-small.png "spotinst") +![https://serverless.com/framework/docs/providers/webtasks/](./etc/webtask-small-grayscale.png "webtask") +... + +Anything that triggers an Lambda Function to execute is regarded by the Framework as +an Event. Most of the Serverless Cloud Providers support following Events +- Http +- PubSub Events +- scheduled + +AWS supports processing event generated from AWS Services (S3/Cloudwatch/etc) and +using aws as a compute engine is our first choice. + +## AWS lambda function implementation + +AWS lambda SDK provides pre-defined interface `com.amazonaws.services.lambda.runtime +.RequestHandler` to implement our lambda function. + +```java +public class LambdaInfoApiHandler implements RequestHandler, ApiGatewayResponse> { + + private static final Logger LOG = Logger.getLogger(LambdaInfoApiHandler.class); + private static final Integer SUCCESS_STATUS_CODE = 200; + + + @Override + public ApiGatewayResponse handleRequest(Map input, Context context) { + + } +} +``` +handleRequest method is where the function code is implemented. Context provides +useful information about Lambda execution environment. AWS Lambda function needs a +deployment package. This package is either a .zip or .jar file that contains all the +dependencies of the function. + +`serverless.yml` contains configuration to manage deployments for your functions. + +## Run example in local + +# Pre-requisites +* Node.js v6.5.0 or later. +* Serverless CLI v1.9.0 or later. You can run npm install -g serverless to install it. +* An AWS account. If you don't already have one, you can sign up for a free trial that includes 1 million free Lambda requests per month. +* [Set-up](https://serverless.com/framework/docs/providers/aws/guide/credentials/) your Provider Credentials. Watch the video on setting up credentials + +# build and deploy + +* `cd serverless` +* `mvn clean package` +* `serverless deploy --stage=dev --verbose` + +Based on the configuration in `serverless.yml` serverless framework creates a +cloud formation stack for S3 (ServerlessDeploymentBucket), IAM Role +(IamRoleLambdaExecution), cloud watch (log groups), API Gateway (ApiGatewayRestApi) +and the Lambda function. + +The command will print out Stack Outputs which looks something like this + +```yaml +endpoints: + GET - https://xxxxxxxxx.execute-api.us-east-1.amazonaws.com/dev/info +``` + +```yaml +CurrentTimeLambdaFunctionQualifiedArn: arn:aws:lambda:us-east-1:xxxxxxxxxxx:function:lambda-info-http-endpoint-dev-currentTime:4 +ServiceEndpoint: https://xxxxxxxxx.execute-api.us-east-1.amazonaws.com/dev +ServerlessDeploymentBucketName: lambda-info-http-endpoin-serverlessdeploymentbuck-2u8uz2i7cap2 +``` +access the endpoint to invoke the function. + +## Credits + +* [serverless docs](https://serverless.com/framework/docs/) +* [Serverless Architectures](https://martinfowler.com/articles/serverless.html) \ No newline at end of file diff --git a/serverless/etc/aws-black.png b/serverless/etc/aws-black.png new file mode 100644 index 0000000000000000000000000000000000000000..7b5ba317856f8f1b05fb7c02c0b24f4bb955cd42 GIT binary patch literal 9262 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

0AXR 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 LOQT8y&)EM0Zf;=R literal 0 HcmV?d00001 diff --git a/serverless/etc/gcf-black.png b/serverless/etc/gcf-black.png new file mode 100644 index 0000000000000000000000000000000000000000..b4e22982c8e25e4b7cac03e82909b8047a7910ec GIT binary patch literal 7651 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 A_5c6? literal 0 HcmV?d00001 diff --git a/serverless/etc/openwhisk-black.png b/serverless/etc/openwhisk-black.png new file mode 100644 index 0000000000000000000000000000000000000000..e70139088b7d1175a01efbddc0942b2fa71e74d3 GIT binary patch literal 5722 zcmcIoc{r49+gFi6NJYppMwZ4lV_(KLV@)!aL1e6h2{V?#40@C;Yf{hfq>Kn9YsgO7 zLyV=GlzkV*_SnUzXM5j2-sAm_<2#P;{^P!{>pp+y?_93)`sWviyl%+O%FoKcz`$;7 zq-Vjva6P}JOT0TF*x4V$pzDTr!2peYoINlW7-u(=FmX@XYj-hzNwH%*X@ zk}1U07lHA_8in{{EJLnaxrTVTs=0x*wSX|9`Z0kIhTse&`gr34)QOs)KX}!T*FS&D zfq;KN2ws|?Ka;XGMFJ6ce+*Db79!&cQHB7Op|Vf~MHLklX`nm=3Xy}z%R%L3pepJL z^6C%>@UI7S9L?X&UEM;@;ICN6J57)$f#9nyCl?$XEE}vKi}&}CgQ}^i{p66BmpMkr z1dwn9XQB))K=f}0JxqYBKh~Fk#p8fK8J%75fdoy^ai)Kk;Nxp*`VTQK;IBp?;SAg~<8%{H*H_bO6Bu^RG1iBRar}+0RP^Ql_BF-@5S($Yn7`v< zkK_L3i}Ii>~GXd{!g~xmUJpo8hJOLlziT4E}ER}$!&aPP8&;8Hi`LkL*j6XIA z<7VKG_W}OVUv=z%h!0Ww-|GI;*X{q(Gr8krRvc`&%+ z8{>a`HNJ)$lNlJ!#T)DCS`oi4WibDWYUAthScsK;&srAGgfu^U1+1LI$K57rj&Ny> z7dUT%>~m_8jeRX(y5y1wWmP__r)YHEJRW2!=E9_J&b1IIvaPZIc8FB2uss^w-CTS$ zrZzTodu2ppWwb`&Xm|F>I%}oRw>*GI4hX5!1d2kI0z{%B^Z{8;TyOwW!dbYaZPZy- z9+Cf7LsPtnEfW)yM%QNM#83exaH}xc!(M*pzJ}(L?rgZxR@CN8JMWLVCUuT!{5mFlAXdlCs3M94O!LI(QU%DMZyUa`;W?@i%)jY{J! z>o3CJb6mFd(fk%0ul1=M%(9Xz=7Mt020f2fg6+dZ`_pG{pm=29;54Ts1@N94%gPKn zHex*q{CIX%gFK>mkIl9MkPnLpOwWB7K071}U74DOF<~IRp;DqvdjsWr( wu%>e> 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 + + + 4.0.0 + serverless + + com.iluwatar + java-design-patterns + 1.19.0-SNAPSHOT + + + + + com.amazonaws + aws-lambda-java-core + ${aws-lambda-core.version} + + + com.amazonaws + aws-lambda-java-log4j + ${aws-lambda-log4j.version} + + + com.fasterxml.jackson.core + jackson-core + ${jackson.version} + + + com.fasterxml.jackson.core + jackson-databind + ${jackson.version} + + + com.fasterxml.jackson.core + jackson-annotations + ${jackson.version} + + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.mockito + mockito-core + test + + + junit + junit + test + + + + + + + + org.apache.maven.plugins + maven-shade-plugin + 2.3 + + false + + + + package + + shade + + + ${project.artifactId} + + + + + + + + \ No newline at end of file diff --git a/serverless/serverless.yml b/serverless/serverless.yml new file mode 100644 index 000000000..8f2199d26 --- /dev/null +++ b/serverless/serverless.yml @@ -0,0 +1,49 @@ +# +# The MIT License +# Copyright (c) 2014 Ilkka Seppälä +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# + +service: lambda-info-http-endpoint + +frameworkVersion: ">=1.2.0 <2.0.0" + +provider: + name: aws + runtime: java8 + usagePlan: + quota: + limit: 500 + offset: 2 + period: MONTH + throttle: + burstLimit: 20 + rateLimit: 10 + +package: + artifact: target/serverless.jar + +functions: + currentTime: + handler: com.iluwatar.serverless.api.LambdaInfoApiHandler + events: + - http: + path: info + method: get diff --git a/serverless/src/main/java/com/iluwatar/serverless/ApiGatewayResponse.java b/serverless/src/main/java/com/iluwatar/serverless/ApiGatewayResponse.java new file mode 100644 index 000000000..f20cc13c8 --- /dev/null +++ b/serverless/src/main/java/com/iluwatar/serverless/ApiGatewayResponse.java @@ -0,0 +1,166 @@ +/** + * The MIT License + * Copyright (c) 2014 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.serverless; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.io.Serializable; +import java.util.Map; + +/** + * Api gateway response + * + * @param serializable object + */ +public class ApiGatewayResponse implements Serializable { + + private static final long serialVersionUID = 1181159426782844892L; + + private final Integer statusCode; + private final String body; + private final Map headers; + private final Boolean isBase64Encoded; + + /** + * api gateway response + * + * @param statusCode - http status code + * @param body - response body + * @param headers - response headers + * @param isBase64Encoded - base64Encoded flag + */ + public ApiGatewayResponse(Integer statusCode, String body, Map headers, + Boolean isBase64Encoded) { + this.statusCode = statusCode; + this.body = body; + this.headers = headers; + this.isBase64Encoded = isBase64Encoded; + } + + /** + * http status code + * + * @return statusCode - http status code + */ + public Integer getStatusCode() { + return statusCode; + } + + /** + * response body + * + * @return string body + */ + public String getBody() { + return body; + } + + /** + * response headers + * + * @return response headers + */ + public Map getHeaders() { + return headers; + } + + /** + * base64Encoded flag, API Gateway expects the property to be called "isBase64Encoded" + * + * @return base64Encoded flag + */ + public Boolean isBase64Encoded() { + return isBase64Encoded; + } + + /** + * ApiGatewayResponse Builder class + * @param Serializable object + */ + public static class ApiGatewayResponseBuilder { + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + private Integer statusCode; + private T body; + private Map headers; + private Boolean isBase64Encoded; + + /** + * http status code + * @param statusCode - http status code + * @return ApiGatewayResponseBuilder + */ + public ApiGatewayResponseBuilder statusCode(Integer statusCode) { + this.statusCode = statusCode; + return this; + } + + /** + * Serializable body + * @param body - Serializable object + * @return ApiGatewayResponseBuilder + */ + public ApiGatewayResponseBuilder body(T body) { + this.body = body; + return this; + } + + /** + * response headers + * @param headers - response headers + * @return ApiGatewayResponseBuilder + */ + public ApiGatewayResponseBuilder headers(Map headers) { + this.headers = headers; + return this; + } + + /** + * base64Encoded glag + * @param isBase64Encoded - base64Encoded flag + * @return ApiGatewayResponseBuilder + */ + public ApiGatewayResponseBuilder base64Encoded(Boolean isBase64Encoded) { + this.isBase64Encoded = isBase64Encoded; + return this; + } + + /** + * build ApiGatewayResponse + * + * @return ApiGatewayResponse + */ + public ApiGatewayResponse build() { + String strBody = null; + if (this.body != null) { + try { + strBody = OBJECT_MAPPER.writeValueAsString(body); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } + + return new ApiGatewayResponse(this.statusCode, strBody, this.headers, this.isBase64Encoded); + } + } +} diff --git a/serverless/src/main/java/com/iluwatar/serverless/LambdaInfo.java b/serverless/src/main/java/com/iluwatar/serverless/LambdaInfo.java new file mode 100644 index 000000000..aedb6f72f --- /dev/null +++ b/serverless/src/main/java/com/iluwatar/serverless/LambdaInfo.java @@ -0,0 +1,140 @@ +/** + * The MIT License + * Copyright (c) 2014 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.serverless; + +import java.io.Serializable; + +/** + * Lambda context information + */ +public class LambdaInfo implements Serializable { + + private static final long serialVersionUID = 3936130599040848923L; + + private String awsRequestId; + private String logGroupName; + private String logStreamName; + private String functionName; + private String functionVersion; + private Integer memoryLimitInMb; + + public String getAwsRequestId() { + return awsRequestId; + } + + public void setAwsRequestId(String awsRequestId) { + this.awsRequestId = awsRequestId; + } + + public String getLogGroupName() { + return logGroupName; + } + + public void setLogGroupName(String logGroupName) { + this.logGroupName = logGroupName; + } + + public String getLogStreamName() { + return logStreamName; + } + + public void setLogStreamName(String logStreamName) { + this.logStreamName = logStreamName; + } + + public String getFunctionName() { + return functionName; + } + + public void setFunctionName(String functionName) { + this.functionName = functionName; + } + + public String getFunctionVersion() { + return functionVersion; + } + + public void setFunctionVersion(String functionVersion) { + this.functionVersion = functionVersion; + } + + public Integer getMemoryLimitInMb() { + return memoryLimitInMb; + } + + public void setMemoryLimitInMb(Integer memoryLimitInMb) { + this.memoryLimitInMb = memoryLimitInMb; + } + + @Override + public String toString() { + return "LambdaInfo{" + + "awsRequestId='" + awsRequestId + '\'' + + ", logGroupName='" + logGroupName + '\'' + + ", logStreamName='" + logStreamName + '\'' + + ", functionName='" + functionName + '\'' + + ", functionVersion='" + functionVersion + '\'' + + ", memoryLimitInMb=" + memoryLimitInMb + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + LambdaInfo that = (LambdaInfo) o; + + if (awsRequestId != null ? !awsRequestId.equals(that.awsRequestId) : that.awsRequestId != null) { + return false; + } + if (logGroupName != null ? !logGroupName.equals(that.logGroupName) : that.logGroupName != null) { + return false; + } + if (logStreamName != null ? !logStreamName.equals(that.logStreamName) : that.logStreamName != null) { + return false; + } + if (functionName != null ? !functionName.equals(that.functionName) : that.functionName != null) { + return false; + } + if (functionVersion != null ? !functionVersion.equals(that.functionVersion) : that.functionVersion != null) { + return false; + } + return memoryLimitInMb != null ? memoryLimitInMb.equals(that.memoryLimitInMb) : that.memoryLimitInMb == null; + } + + @Override + public int hashCode() { + int result = awsRequestId != null ? awsRequestId.hashCode() : 0; + result = 31 * result + (logGroupName != null ? logGroupName.hashCode() : 0); + result = 31 * result + (logStreamName != null ? logStreamName.hashCode() : 0); + result = 31 * result + (functionName != null ? functionName.hashCode() : 0); + result = 31 * result + (functionVersion != null ? functionVersion.hashCode() : 0); + result = 31 * result + (memoryLimitInMb != null ? memoryLimitInMb.hashCode() : 0); + return result; + } +} diff --git a/serverless/src/main/java/com/iluwatar/serverless/api/LambdaInfoApiHandler.java b/serverless/src/main/java/com/iluwatar/serverless/api/LambdaInfoApiHandler.java new file mode 100644 index 000000000..669601a23 --- /dev/null +++ b/serverless/src/main/java/com/iluwatar/serverless/api/LambdaInfoApiHandler.java @@ -0,0 +1,83 @@ +/** + * The MIT License + * Copyright (c) 2014 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.serverless.api; + +import com.iluwatar.serverless.ApiGatewayResponse; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import com.iluwatar.serverless.LambdaInfo; +import org.apache.log4j.BasicConfigurator; +import org.apache.log4j.Logger; + +import java.util.HashMap; +import java.util.Map; + +/** + * LambdaInfoApiHandler - simple api to get lambda context + * Created by dheeraj.mummar on 2/5/18. + */ +public class LambdaInfoApiHandler implements RequestHandler, ApiGatewayResponse> { + + private static final Logger LOG = Logger.getLogger(LambdaInfoApiHandler.class); + private static final Integer SUCCESS_STATUS_CODE = 200; + + + @Override + public ApiGatewayResponse handleRequest(Map input, Context context) { + BasicConfigurator.configure(); + LOG.info("received: " + input); + + return new ApiGatewayResponse + .ApiGatewayResponseBuilder() + .headers(headers()) + .statusCode(SUCCESS_STATUS_CODE) + .body(lambdaInfo(context)) + .build(); + + } + + /** + * lambdaInfo + * @param context - Lambda context + * @return LambdaInfo + */ + private LambdaInfo lambdaInfo(Context context) { + LambdaInfo lambdaInfo = new LambdaInfo(); + lambdaInfo.setAwsRequestId(context.getAwsRequestId()); + lambdaInfo.setFunctionName(context.getFunctionName()); + lambdaInfo.setFunctionVersion(context.getFunctionVersion()); + lambdaInfo.setLogGroupName(context.getLogGroupName()); + lambdaInfo.setLogStreamName(context.getLogStreamName()); + lambdaInfo.setMemoryLimitInMb(context.getMemoryLimitInMB()); + + return lambdaInfo; + } + + private Map headers() { + Map headers = new HashMap<>(); + headers.put("Content-Type", "application/json"); + + return headers; + } +} diff --git a/serverless/src/main/resources/log4j.properties b/serverless/src/main/resources/log4j.properties new file mode 100644 index 000000000..15a25e61b --- /dev/null +++ b/serverless/src/main/resources/log4j.properties @@ -0,0 +1,29 @@ +# +# The MIT License +# Copyright (c) 2014 Ilkka Seppälä +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# + +log = . +log4j.rootLogger = DEBUG, LAMBDA + +log4j.appender.LAMBDA=com.amazonaws.services.lambda.runtime.log4j.LambdaAppender +log4j.appender.LAMBDA.layout=org.apache.log4j.PatternLayout +log4j.appender.LAMBDA.layout.conversionPattern=%d{yyyy-MM-dd HH:mm:ss} <%X{AWSRequestId}> %-5p %c:%L - %m%n diff --git a/serverless/src/test/java/com/iluwatar/serverless/api/LambdaInfoApiHandlerTest.java b/serverless/src/test/java/com/iluwatar/serverless/api/LambdaInfoApiHandlerTest.java new file mode 100644 index 000000000..6c7a03386 --- /dev/null +++ b/serverless/src/test/java/com/iluwatar/serverless/api/LambdaInfoApiHandlerTest.java @@ -0,0 +1,49 @@ +/** + * The MIT License + * Copyright (c) 2014 Ilkka Seppälä + *

+ * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + *

+ * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + *

+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.serverless.api; + +import com.amazonaws.services.lambda.runtime.Context; +import org.junit.jupiter.api.Test; +import org.junit.runner.RunWith; +import org.mockito.runners.MockitoJUnitRunner; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.IsNull.notNullValue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * LambdaInfoApiHandlerTest + */ +@RunWith(MockitoJUnitRunner.class) +public class LambdaInfoApiHandlerTest { + + @Test + public void handleRequestWithMockContext() { + LambdaInfoApiHandler lambdaInfoApiHandler = new LambdaInfoApiHandler(); + Context context = mock(Context.class); + when(context.getAwsRequestId()).thenReturn("mock aws request id"); + + assertThat(lambdaInfoApiHandler.handleRequest(null, context), notNullValue()); + } +} \ No newline at end of file