From d13b4bd2d44c8c4df166bedf41d3aac16c56803e Mon Sep 17 00:00:00 2001 From: Markus Tacker Date: Thu, 22 Oct 2020 10:38:27 +0200 Subject: [PATCH] feat(cellgeo): add GET __health endpoint Check if API is ready before testing See #455 --- cdk/httpApiHealth.ts | 10 +++ cdk/prepare-resources.ts | 2 + cdk/resources/CellGeolocationApi.ts | 73 ++++++++++++++++------ cdk/stacks/Bifravst.ts | 11 ++-- features/CellGeolocation.feature | 13 ++++ features/CellGeolocationPublishApi.feature | 3 +- 6 files changed, 87 insertions(+), 25 deletions(-) create mode 100644 cdk/httpApiHealth.ts diff --git a/cdk/httpApiHealth.ts b/cdk/httpApiHealth.ts new file mode 100644 index 00000000..071df5e1 --- /dev/null +++ b/cdk/httpApiHealth.ts @@ -0,0 +1,10 @@ +import { APIGatewayProxyResult } from 'aws-lambda' + +export const handler = (): APIGatewayProxyResult => ({ + statusCode: 200, + headers: { + 'Access-Control-Allow-Origin': '*', + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ status: 'OK' }), +}) diff --git a/cdk/prepare-resources.ts b/cdk/prepare-resources.ts index ff5d773c..4f2c9b6c 100644 --- a/cdk/prepare-resources.ts +++ b/cdk/prepare-resources.ts @@ -16,6 +16,7 @@ import { ConsoleProgressReporter } from '@bifravst/package-layered-lambdas/dist/ export type CDKLambdas = { createThingGroup: string + httpApiHealth: string } export type BifravstLambdas = { @@ -152,6 +153,7 @@ export const prepareCDKLambdas = async ({ Bucket: sourceCodeBucketName, lambdas: { createThingGroup: path.resolve(rootDir, 'cdk', 'createThingGroup.ts'), + httpApiHealth: path.resolve(rootDir, 'cdk', 'httpApiHealth.ts'), }, tsConfig: path.resolve(rootDir, 'tsconfig.json'), }), diff --git a/cdk/resources/CellGeolocationApi.ts b/cdk/resources/CellGeolocationApi.ts index dd57c6c1..4489a7ac 100644 --- a/cdk/resources/CellGeolocationApi.ts +++ b/cdk/resources/CellGeolocationApi.ts @@ -2,7 +2,7 @@ import * as CloudFormation from '@aws-cdk/core' import * as HttpApi from '@aws-cdk/aws-apigatewayv2' import * as IAM from '@aws-cdk/aws-iam' import * as Lambda from '@aws-cdk/aws-lambda' -import { BifravstLambdas } from '../prepare-resources' +import { BifravstLambdas, CDKLambdas } from '../prepare-resources' import { logToCloudWatch } from './logToCloudWatch' import { CellGeolocation } from './CellGeolocation' import { LambdasWithLayer } from './LambdasWithLayer' @@ -24,9 +24,11 @@ export class CellGeolocationApi extends CloudFormation.Resource { { cellgeo, lambdas, + cdkLambdas, }: { cellgeo: CellGeolocation lambdas: LambdasWithLayer + cdkLambdas: LambdasWithLayer }, ) { super(parent, id) @@ -98,7 +100,7 @@ export class CellGeolocationApi extends CloudFormation.Resource { }, ) - this.stage = new HttpApi.CfnStage(this, 'httpApiStage', { + this.stage = new HttpApi.CfnStage(this, 'stage', { apiId: this.api.ref, stageName: 'v1', autoDeploy: true, @@ -123,36 +125,70 @@ export class CellGeolocationApi extends CloudFormation.Resource { }, }) - const geolocateIntegration = new HttpApi.CfnIntegration( + // GET __health + + const healthCheck = new Lambda.Function(this, 'apiHealth', { + handler: 'index.handler', + runtime: Lambda.Runtime.NODEJS_12_X, + timeout: CloudFormation.Duration.seconds(10), + code: cdkLambdas.lambdas.httpApiHealth, + description: 'HTTP API Health Check', + initialPolicy: [logToCloudWatch], + }) + + const healthCheckIntegration = new HttpApi.CfnIntegration( this, - 'httpApiAddCellGeolocateIntegration', + 'healthCheckIntegration', { apiId: this.api.ref, integrationType: 'AWS_PROXY', - integrationUri: `arn:aws:apigateway:${this.stack.region}:lambda:path/2015-03-31/functions/${geolocateCell.functionArn}/invocations`, + integrationUri: `arn:aws:apigateway:${this.stack.region}:lambda:path/2015-03-31/functions/${healthCheck.functionArn}/invocations`, integrationMethod: 'POST', payloadFormatVersion: '1.0', }, ) - const geolocateRoute = new HttpApi.CfnRoute( + const healthCheckRoute = new HttpApi.CfnRoute(this, 'healthCheckRoute', { + apiId: this.api.ref, + routeKey: 'GET /__health', + target: `integrations/${healthCheckIntegration.ref}`, + }) + + healthCheck.addPermission('invokeByHttpApi', { + principal: new IAM.ServicePrincipal('apigateway.amazonaws.com'), + sourceArn: `arn:aws:execute-api:${this.stack.region}:${this.stack.account}:${this.api.ref}/${this.stage.stageName}/GET/__health`, + }) + + // GET /cellgeolocation + + const geolocateIntegration = new HttpApi.CfnIntegration( this, - 'httpApiCellGeolocateRoute', + 'geolocateIntegration', { apiId: this.api.ref, - routeKey: 'GET /cellgeolocation', - target: `integrations/${geolocateIntegration.ref}`, + integrationType: 'AWS_PROXY', + integrationUri: `arn:aws:apigateway:${this.stack.region}:lambda:path/2015-03-31/functions/${geolocateCell.functionArn}/invocations`, + integrationMethod: 'POST', + payloadFormatVersion: '1.0', }, ) + const geolocateRoute = new HttpApi.CfnRoute(this, 'geolocateRoute', { + apiId: this.api.ref, + routeKey: 'GET /cellgeolocation', + target: `integrations/${geolocateIntegration.ref}`, + }) + geolocateCell.addPermission('invokeByHttpApi', { principal: new IAM.ServicePrincipal('apigateway.amazonaws.com'), sourceArn: `arn:aws:execute-api:${this.stack.region}:${this.stack.account}:${this.api.ref}/${this.stage.stageName}/GET/cellgeolocation`, }) + // POST /cellgeolocation + const geolocationIntegration = new HttpApi.CfnIntegration( this, - 'httpApiAddCellGeolocationIntegration', + 'geolocationIntegration', { apiId: this.api.ref, integrationType: 'AWS_PROXY', @@ -162,26 +198,23 @@ export class CellGeolocationApi extends CloudFormation.Resource { }, ) - const geolocationRoute = new HttpApi.CfnRoute( - this, - 'httpApiAddCellGeolocationRoute', - { - apiId: this.api.ref, - routeKey: 'POST /cellgeolocation', - target: `integrations/${geolocationIntegration.ref}`, - }, - ) + const geolocationRoute = new HttpApi.CfnRoute(this, 'geolocationRoute', { + apiId: this.api.ref, + routeKey: 'POST /cellgeolocation', + target: `integrations/${geolocationIntegration.ref}`, + }) addCellGeolocation.addPermission('invokeByHttpApi', { principal: new IAM.ServicePrincipal('apigateway.amazonaws.com'), sourceArn: `arn:aws:execute-api:${this.stack.region}:${this.stack.account}:${this.api.ref}/${this.stage.stageName}/POST/cellgeolocation`, }) - const deployment = new HttpApi.CfnDeployment(this, 'httpApiDeployment', { + const deployment = new HttpApi.CfnDeployment(this, 'deployment', { apiId: this.api.ref, stageName: this.stage.stageName, }) deployment.node.addDependency(this.stage) + deployment.node.addDependency(healthCheckRoute) deployment.node.addDependency(geolocateRoute) deployment.node.addDependency(geolocationRoute) } diff --git a/cdk/stacks/Bifravst.ts b/cdk/stacks/Bifravst.ts index 6f722c4c..e265a5e2 100644 --- a/cdk/stacks/Bifravst.ts +++ b/cdk/stacks/Bifravst.ts @@ -346,11 +346,13 @@ export class BifravstStack extends CloudFormation.Stack { exportName: `${this.stackName}:thingPolicyArn`, }) + const cdkLambdas = { + lambdas: lambasOnBucket(packedCDKLambdas), + layers: [cloudFormationLayer], + } + const thingGroupLambda = new ThingGroupLambda(this, 'thingGroupLambda', { - cdkLambdas: { - lambdas: lambasOnBucket(packedCDKLambdas), - layers: [cloudFormationLayer], - }, + cdkLambdas, }) new CloudFormation.CfnOutput(this, 'thingGroupLambdaArn', { @@ -450,6 +452,7 @@ export class BifravstStack extends CloudFormation.Stack { const cellGeoApi = new CellGeolocationApi(this, 'cellGeolocationApi', { lambdas, + cdkLambdas, cellgeo, }) diff --git a/features/CellGeolocation.feature b/features/CellGeolocation.feature index 3c464cf1..54ed0c29 100644 --- a/features/CellGeolocation.feature +++ b/features/CellGeolocation.feature @@ -10,6 +10,19 @@ Feature: Cell Geolocation API And I am run after the "Device: Update Shadow" feature And the endpoint is "{geolocationApiUrl}" + Scenario: Check API health + + When I GET /__health + Then the response status code should be 200 + And the response Access-Control-Allow-Origin should be "*" + And the response Content-Type should be "application/json" + And the response should equal this JSON + """ + { + "status": "OK" + } + """ + Scenario: Device enters a cell Given I store "$floor($random() * 100000000)" into "cellId" diff --git a/features/CellGeolocationPublishApi.feature b/features/CellGeolocationPublishApi.feature index 064b4d21..304a5cf5 100644 --- a/features/CellGeolocationPublishApi.feature +++ b/features/CellGeolocationPublishApi.feature @@ -5,7 +5,8 @@ Feature: Cell Geolocation Publish API Background: - Given the endpoint is "{geolocationApiUrl}" + Given I am run after the "Cell Geolocation API" feature + And the endpoint is "{geolocationApiUrl}" And I store "$floor($random() * 100000000)" into "cellId" And I store "$floor($random() * 10000) + 10000" into "mccmnc" And I store "$floor($random() * 100) + 100" into "area"