Commit a847fc8403b958a19160a9d063c244a9f260ce4a

Authored by Alex Mukha
1 parent 62242e0611
Exists in master

Refactored the code to use a valid CF template. Added docs.

... ... @@ -0,0 +1,28 @@
  1 +# Serverless Heartbeat
  2 +
  3 +
  4 +## Overview
  5 +
  6 +The project contain a lambda (HeartbeatRequestHandler) which writes the received data to DynamoDB.
  7 +The request and response objects are build according to the docs:
  8 +https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-set-up-simple-proxy.html#api-gateway-simple-proxy-for-lambda-input-format
  9 +
  10 +## Usage
  11 +
  12 +Use the following steps to deploy a stack (aws cli should be installed and configured):
  13 +* build the code using maven:
  14 +~~~
  15 +mvn clean nstall
  16 +~~~
  17 +* create S3 bucket:
  18 +~~~
  19 +aws s3 mb s3://<bucket-name>
  20 +~~~
  21 +* package the template and upload the jar to the bucket:
  22 +~~~
  23 +aws cloudformation package --template-file heartbeat-template.yaml --output-template-file heartbeat-template-output.yaml --s3-bucket <bucket-name>
  24 +~~~
  25 +* deploy the stack in CF:
  26 +~~~
  27 +aws cloudformation deploy --stack-name <stack-name> --template heartbeat-template.yaml --capabilities CAPABILITY_IAM
  28 +~~~
... ...
heartbeat-template.yaml
1 1 index fd8f2bac0b6895e34562aa295970cba481e807a9..629721117c25260c4a9dfa8c8c29d17e478c516b 100644
2   --- a/src/main/resources/heartbeat-template.yaml
  2 +++ b/heartbeat-template.yaml
... ... @@ -2,18 +2,21 @@
2 2 Transform: 'AWS::Serverless-2016-10-31'
3 3 Description: An AWS Serverless Heartbeat receiver
4 4 Resources:
5   - Heartbeat:
6   - Type: 'AWS::Serverless::Function'
  5 + HeartbeatLambda:
  6 + Type: AWS::Serverless::Function
7 7 Properties:
8 8 Handler: org.alfresco.heartbeat.handler.HeartbeatRequestHandler
9 9 Runtime: java8
10   - Description: 'Heartbeat receiver'
  10 + Description: 'Heartbeat lambda'
11 11 MemorySize: 512
12 12 Timeout: 15
13   - CodeUri: s3://alfresco-heartbeat/heartbeat-2.0-SNAPSHOT.jar
  13 + CodeUri: ./target/heartbeat-2.0-SNAPSHOT.jar
14 14 Policies:
15 15 - AmazonDynamoDBFullAccess
16   - - AmazonS3ReadOnlyAccess
  16 + Environment:
  17 + Variables:
  18 + TABLE_NAME:
  19 + Ref: Table
17 20 Events:
18 21 PostRequest:
19 22 Type: Api
... ... @@ -38,7 +41,6 @@ Resources:
38 41 -
39 42 AttributeName: 'repositoryVersion'
40 43 AttributeType: 'S'
41   - TableName: 'heartbeat'
42 44 ProvisionedThroughput:
43 45 ReadCapacityUnits: "5"
44 46 WriteCapacityUnits: "5"
... ...
... ... @@ -16,6 +16,7 @@
16 16 <properties>
17 17 <aws.lambda.core.version>1.1.0</aws.lambda.core.version>
18 18 <aws.dynamodb.sdk.version>1.11.68</aws.dynamodb.sdk.version>
  19 + <jackson.core.version>2.8.5</jackson.core.version>
19 20 </properties>
20 21  
21 22 <dependencies>
... ... @@ -29,6 +30,11 @@
29 30 <artifactId>aws-java-sdk-dynamodb</artifactId>
30 31 <version>${aws.dynamodb.sdk.version}</version>
31 32 </dependency>
  33 + <dependency>
  34 + <groupId>com.fasterxml.jackson.core</groupId>
  35 + <artifactId>jackson-core</artifactId>
  36 + <version>${jackson.core.version}</version>
  37 + </dependency>
32 38 </dependencies>
33 39  
34 40 <build>
... ...
src/main/java/org/alfresco/heartbeat/HeartbeatRequest.java
1 1 package org.alfresco.heartbeat;
2 2  
  3 +import java.io.Serializable;
  4 +
3 5 /**
4 6 * Represents the request object
5 7 *
6 8 * @author amukha
7 9 */
8   -public class HeartbeatRequest
  10 +public class HeartbeatRequest implements Serializable
9 11 {
10   - private String repositoryId;
11   - private String repositoryVersion;
  12 + private String httpMethod;
  13 + private String body;
12 14  
13 15 public HeartbeatRequest()
14 16 {
15 17  
16 18 }
17 19  
18   - public String getRepositoryId()
19   - {
20   - return repositoryId;
21   - }
22   -
23   - public void setRepositoryId(String repositoryId)
  20 + public String getHttpMethod()
24 21 {
25   - this.repositoryId = repositoryId;
  22 + return httpMethod;
26 23 }
27 24  
28   - public String getRepositoryVersion()
  25 + public void setHttpMethod(String httpMethod)
29 26 {
30   - return repositoryVersion;
  27 + this.httpMethod = httpMethod;
31 28 }
32 29  
33   - public void setRepositoryVersion(String repositoryVersion)
  30 + public String getBody()
34 31 {
35   - this.repositoryVersion = repositoryVersion;
  32 + return body;
36 33 }
37 34  
38   - @Override
39   - public boolean equals(Object o)
40   - {
41   - if (this == o) return true;
42   - if (o == null || getClass() != o.getClass()) return false;
43   -
44   - HeartbeatRequest that = (HeartbeatRequest) o;
45   -
46   - if (repositoryId != null ? !repositoryId.equals(that.repositoryId) : that.repositoryId != null) return false;
47   - return repositoryVersion != null ? repositoryVersion.equals(that.repositoryVersion) : that.repositoryVersion == null;
48   - }
49   -
50   - @Override
51   - public int hashCode()
  35 + public void setBody(String body)
52 36 {
53   - int result = repositoryId != null ? repositoryId.hashCode() : 0;
54   - result = 31 * result + (repositoryVersion != null ? repositoryVersion.hashCode() : 0);
55   - return result;
  37 + this.body = body;
56 38 }
57 39  
58 40 @Override
59 41 public String toString()
60 42 {
61 43 return "HeartbeatRequest{" +
62   - "repositoryId='" + repositoryId + '\'' +
63   - ", repositoryVersion='" + repositoryVersion + '\'' +
  44 + "httpMethod='" + httpMethod + '\'' +
  45 + ", body='" + body + '\'' +
64 46 '}';
65 47 }
66 48 }
... ...
src/main/java/org/alfresco/heartbeat/HeartbeatResponse.java
1 1 package org.alfresco.heartbeat;
2 2  
  3 +import java.io.Serializable;
  4 +import java.util.List;
  5 +
3 6 /**
4 7 * Represents the response object
5 8 *
6 9 * @author amukha
7 10 */
8   -public class HeartbeatResponse
  11 +public class HeartbeatResponse implements Serializable
9 12 {
10   - private boolean success;
  13 + private int statusCode;
  14 + private List<String> headers;
  15 + private String body;
11 16  
12 17 public HeartbeatResponse()
13 18 {
14 19  
15 20 }
16 21  
17   - public void setSuccess(boolean success)
  22 + public int getStatusCode()
18 23 {
19   - this.success = success;
  24 + return statusCode;
20 25 }
21 26  
22   - public boolean isSuccess()
  27 + public void setStatusCode(int statusCode)
23 28 {
24   - return success;
  29 + this.statusCode = statusCode;
25 30 }
26 31  
27   - @Override
28   - public boolean equals(Object o)
  32 + public List<String> getHeaders()
29 33 {
30   - if (this == o) return true;
31   - if (o == null || getClass() != o.getClass()) return false;
32   -
33   - HeartbeatResponse response = (HeartbeatResponse) o;
  34 + return headers;
  35 + }
34 36  
35   - return success == response.success;
  37 + public void setHeaders(List<String> headers)
  38 + {
  39 + this.headers = headers;
36 40 }
37 41  
38   - @Override
39   - public int hashCode()
  42 + public String getBody()
40 43 {
41   - return (success ? 1 : 0);
  44 + return body;
42 45 }
43 46  
44   - @Override
45   - public String toString()
  47 + public void setBody(String body)
46 48 {
47   - return "HeartbeatResponse{" +
48   - "success=" + success +
49   - '}';
  49 + this.body = body;
50 50 }
51 51 }
... ...
src/main/java/org/alfresco/heartbeat/handler/HeartbeatRequestHandler.java
1 1 package org.alfresco.heartbeat.handler;
2 2  
3   -
4   -import com.amazonaws.auth.profile.ProfileCredentialsProvider;
5 3 import com.amazonaws.regions.Region;
6 4 import com.amazonaws.regions.Regions;
7 5 import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClient;
... ... @@ -9,11 +7,18 @@ import com.amazonaws.services.dynamodbv2.document.DynamoDB;
9 7 import com.amazonaws.services.dynamodbv2.document.Item;
10 8 import com.amazonaws.services.dynamodbv2.document.PutItemOutcome;
11 9 import com.amazonaws.services.dynamodbv2.document.Table;
  10 +import com.amazonaws.services.lambda.runtime.Context;
12 11 import com.amazonaws.services.lambda.runtime.RequestHandler;
  12 +import com.fasterxml.jackson.core.JsonFactory;
  13 +import com.fasterxml.jackson.core.JsonParser;
  14 +import com.fasterxml.jackson.databind.JsonNode;
  15 +import com.fasterxml.jackson.databind.ObjectMapper;
  16 +import com.fasterxml.jackson.databind.node.ObjectNode;
13 17 import org.alfresco.heartbeat.HeartbeatRequest;
14 18 import org.alfresco.heartbeat.HeartbeatResponse;
  19 +import org.apache.http.HttpStatus;
15 20  
16   -import com.amazonaws.services.lambda.runtime.Context;
  21 +import java.io.IOException;
17 22  
18 23 /**
19 24 * Represents the receiver of the heart beat
... ... @@ -22,45 +27,68 @@ import com.amazonaws.services.lambda.runtime.Context;
22 27 */
23 28 public class HeartbeatRequestHandler implements RequestHandler<HeartbeatRequest, HeartbeatResponse>
24 29 {
25   - private static final String TABLE_NAME = "heartbeat";
  30 + private static final String TABLE_NAME = "TABLE_NAME";
26 31 private static final String PRIMARY_KEY = "repositoryId";
27 32 private static final String REPOSITORY_VERSION_KEY = "repositoryVersion";
28 33  
29 34 @Override
30 35 public HeartbeatResponse handleRequest(HeartbeatRequest request, Context context)
31 36 {
  37 + String repositoryId = null;
  38 + String repositoryVersion = null;
32 39 context.getLogger().log("Received object: " + request);
33   - checkRequest(request);
  40 + ObjectMapper mapper = new ObjectMapper();
  41 + if (request.getBody() == null)
  42 + {
  43 + return returnError(mapper);
  44 + }
  45 + JsonFactory factory = mapper.getFactory();
  46 + try
  47 + {
  48 + JsonParser jp = factory.createParser(request.getBody());
  49 + JsonNode actualObj = mapper.readTree(jp);
  50 + repositoryId = actualObj.get(PRIMARY_KEY) == null ?
  51 + null : actualObj.get(PRIMARY_KEY).asText();
  52 + repositoryVersion = actualObj.get(REPOSITORY_VERSION_KEY) == null ?
  53 + null : actualObj.get(REPOSITORY_VERSION_KEY).asText();
  54 + }
  55 + catch (IOException ioe)
  56 + {
  57 + context.getLogger().log("Could not parse request body.");
  58 + return returnError(mapper);
  59 + }
  60 +
  61 + if (repositoryId == null || repositoryVersion == null)
  62 + {
  63 + context.getLogger().log("Request is not valid.");
  64 + return returnError(mapper);
  65 + }
  66 +
  67 + String tableName = System.getenv(TABLE_NAME);
34 68 AmazonDynamoDBClient client = new AmazonDynamoDBClient();
35 69 client.setRegion(Region.getRegion(Regions.EU_WEST_1));
36 70 DynamoDB dynamoDB = new DynamoDB(client);
37   - Table table = dynamoDB.getTable(TABLE_NAME);
38   - context.getLogger().log("Table description " + table.getDescription());
  71 + Table table = dynamoDB.getTable(tableName);
39 72 Item item = new Item()
40   - .withPrimaryKey(PRIMARY_KEY, request.getRepositoryId())
41   - .withString(REPOSITORY_VERSION_KEY, request.getRepositoryVersion());
  73 + .withPrimaryKey(PRIMARY_KEY, repositoryId)
  74 + .withString(REPOSITORY_VERSION_KEY, repositoryVersion);
42 75 PutItemOutcome outcome = table.putItem(item);
43 76 // TODO incorporate outcome in the response
44 77 HeartbeatResponse response = new HeartbeatResponse();
45   - response.setSuccess(true);
  78 + response.setStatusCode(HttpStatus.SC_CREATED);
  79 + ObjectNode responseNode = mapper.createObjectNode();
  80 + responseNode.put("success", true);
  81 + response.setBody(responseNode.toString());
46 82 return response;
47 83 }
48 84  
49   - /**
50   - * @return <code>true</code> if the request has at least minimum set of parameters present
51   - */
52   - private boolean checkRequest(HeartbeatRequest request)
  85 + private HeartbeatResponse returnError(ObjectMapper mapper)
53 86 {
54   - if (request.getRepositoryId() == null ||
55   - request.getRepositoryId().isEmpty() ||
56   - request.getRepositoryVersion() == null ||
57   - request.getRepositoryVersion().isEmpty())
58   - {
59   - return false;
60   - }
61   - else
62   - {
63   - return true;
64   - }
  87 + HeartbeatResponse response = new HeartbeatResponse();
  88 + response.setStatusCode(HttpStatus.SC_BAD_REQUEST);
  89 + ObjectNode responseNode = mapper.createObjectNode();
  90 + responseNode.put("success", true);
  91 + response.setBody(responseNode.toString());
  92 + return response;
65 93 }
66 94 }
... ...