Skip to content

Commit

Permalink
Added lambda to index events from s3 data lake to metrics cluster
Browse files Browse the repository at this point in the history
Signed-off-by: Brandon Shien <[email protected]>
  • Loading branch information
bshien committed Oct 24, 2024
1 parent 740daba commit d6b592c
Show file tree
Hide file tree
Showing 21 changed files with 1,126 additions and 46 deletions.
2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ dependencies {

implementation 'com.amazonaws:aws-java-sdk-secretsmanager:1.12.671'

implementation 'software.amazon.awssdk:s3:2.28.17'

testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'

Expand Down
23 changes: 20 additions & 3 deletions infrastructure/lib/infrastructure-stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { OpenSearchMetricsSecretsStack } from "./stacks/secrets";
import { GitHubAutomationApp } from "./stacks/gitHubAutomationApp";
import { OpenSearchS3 } from "./stacks/s3";
import { GitHubWorkflowMonitorAlarms } from "./stacks/gitHubWorkflowMonitorAlarms";
import {OpenSearchS3EventIndexWorkflowStack} from "./stacks/s3EventIndexWorkflow";

export class InfrastructureStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
Expand Down Expand Up @@ -75,15 +76,28 @@ export class InfrastructureStack extends Stack {
new ArnPrincipal(Project.JENKINS_AGENT_ROLE)
]
},
githubAutomationAppAccess: gitHubAutomationApp.githubAppRole.roleArn
githubAutomationAppAccess: gitHubAutomationApp.githubAppRole.roleArn,
githubEventsBucket: openSearchEventsS3Bucket.bucket,
});

// Create OpenSearch Metrics Lambda setup
const openSearchMetricsWorkflowStack = new OpenSearchMetricsWorkflowStack(app, 'OpenSearchMetrics-Workflow', {
opensearchDomainStack: openSearchDomainStack, vpcStack: vpcStack, lambdaPackage: Project.LAMBDA_PACKAGE
opensearchDomainStack: openSearchDomainStack,
vpcStack: vpcStack,
lambdaPackage: Project.LAMBDA_PACKAGE,
})
openSearchMetricsWorkflowStack.node.addDependency(vpcStack, openSearchDomainStack);

// Create OpenSearch S3 Event Index Lambda setup
const openSearchS3EventIndexWorkflowStack = new OpenSearchS3EventIndexWorkflowStack(app, 'OpenSearchS3EventIndex-Workflow', {
region: Project.REGION,
opensearchDomainStack: openSearchDomainStack,
vpcStack: vpcStack,
lambdaPackage: Project.LAMBDA_PACKAGE,
githubEventsBucket: openSearchEventsS3Bucket.bucket
})
openSearchS3EventIndexWorkflowStack.node.addDependency(vpcStack, openSearchDomainStack);

// Create Secret Manager for the metrics project
const openSearchMetricsSecretsStack = new OpenSearchMetricsSecretsStack(app, "OpenSearchMetrics-Secrets", {
secretName: 'metrics-creds'
Expand All @@ -94,7 +108,10 @@ export class InfrastructureStack extends Stack {
const openSearchMetricsMonitoringStack = new OpenSearchMetricsMonitoringStack(app, "OpenSearchMetrics-Monitoring", {
region: Project.REGION,
account: Project.AWS_ACCOUNT,
workflowComponent: openSearchMetricsWorkflowStack.workflowComponent,
workflowComponent: {
opensearchMetricsWorkflowStateMachineName: openSearchMetricsWorkflowStack.workflowComponent.opensearchMetricsWorkflowStateMachineName,
opensearchS3EventIndexWorkflowStateMachineName: openSearchS3EventIndexWorkflowStack.workflowComponent.opensearchS3EventIndexWorkflowStateMachineName
},
lambdaPackage: Project.LAMBDA_PACKAGE,
secrets: openSearchMetricsSecretsStack.secret,
vpcStack: vpcStack
Expand Down
4 changes: 2 additions & 2 deletions infrastructure/lib/stacks/metricsWorkflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,15 +53,15 @@ export class OpenSearchMetricsWorkflowStack extends Stack {
}

private createMetricsTask(scope: Construct, opensearchDomainStack: OpenSearchDomainStack,
vpcStack: VpcStack, lambdaPackage: string) {
vpcStack: VpcStack, lambdaPackage: string) {
const openSearchDomain = opensearchDomainStack.domain;
const metricsLambda = new OpenSearchLambda(scope, "OpenSearchMetricsLambdaFunction", {
lambdaNameBase: "OpenSearchMetricsDashboards",
handler: "org.opensearchmetrics.lambda.MetricsLambda",
lambdaZipPath: `../../../build/distributions/${lambdaPackage}`,
vpc: vpcStack.vpc,
securityGroup: vpcStack.securityGroup,
role: opensearchDomainStack.openSearchLambdaRole,
role: opensearchDomainStack.openSearchMetricsLambdaRole,
environment: {
OPENSEARCH_DOMAIN_ENDPOINT: openSearchDomain.domainEndpoint,
OPENSEARCH_DOMAIN_REGION: openSearchDomain.env.region,
Expand Down
4 changes: 2 additions & 2 deletions infrastructure/lib/stacks/monitoringDashboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,13 @@ import { canarySns } from "../constructs/canarySns";
import { OpenSearchLambda } from "../constructs/lambda";
import { StepFunctionSns } from "../constructs/stepFunctionSns";
import Project from "../enums/project";
import { WorkflowComponent } from "./metricsWorkflow";
import { VpcStack } from "./vpc";


interface OpenSearchMetricsMonitoringStackProps extends StackProps {
readonly region: string;
readonly account: string;
readonly workflowComponent: WorkflowComponent;
readonly workflowComponent: {[component: string]: string};
readonly lambdaPackage: string;
readonly secrets: Secret;
readonly vpcStack: VpcStack;
Expand Down Expand Up @@ -70,6 +69,7 @@ export class OpenSearchMetricsMonitoringStack extends Stack {
private snsMonitorStepFunctionExecutionsFailed(): void {
const stepFunctionSnsAlarms = [
{ alertName: 'StepFunction_execution_errors_MetricsWorkflow', stateMachineName: this.props.workflowComponent.opensearchMetricsWorkflowStateMachineName },
{ alertName: 'StepFunction_execution_errors_S3EventIndexWorkflow', stateMachineName: this.props.workflowComponent.opensearchS3EventIndexWorkflowStateMachineName },
];

new StepFunctionSns(this, "SnsMonitors-StepFunctionExecutionsFailed", {
Expand Down
44 changes: 41 additions & 3 deletions infrastructure/lib/stacks/opensearch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { OpenSearchMetricsNginxCognito } from "../constructs/opensearchNginxProx
import Project from "../enums/project";
import { OpenSearchHealthRoute53 } from "./route53";
import { VpcStack } from "./vpc";
import {Bucket} from "aws-cdk-lib/aws-s3";


export interface OpenSearchStackProps {
Expand All @@ -24,6 +25,7 @@ export interface OpenSearchStackProps {
readonly enableNginxCognito: boolean;
readonly jenkinsAccess?: jenkinsAccess;
readonly githubAutomationAppAccess?: string;
readonly githubEventsBucket: Bucket;
}


Expand All @@ -43,15 +45,16 @@ export class OpenSearchDomainStack extends Stack {
public readonly domain: Domain;
public readonly props: OpenSearchStackProps;
public readonly fullAccessRole: IRole;
public readonly openSearchLambdaRole: IRole;
public readonly openSearchMetricsLambdaRole: IRole;
public readonly openSearchS3EventsIndexLambdaRole: IRole;
public readonly opensearchDomainConfig: OpenSearchDomainConfig;

constructor(scope: Construct, id: string, props: OpenSearchStackProps) {
super(scope, id);
this.props = props;


this.openSearchLambdaRole = new Role(this, 'OpenSearchDomainLambdaRole', {
this.openSearchMetricsLambdaRole = new Role(this, 'OpenSearchDomainLambdaRole', {
assumedBy: new ServicePrincipal('lambda.amazonaws.com'),
description: "OpenSearch Metrics Lambda Execution Role",
roleName: "OpenSearchLambdaRole",
Expand All @@ -76,6 +79,41 @@ export class OpenSearchDomainStack extends Stack {
]
});

this.openSearchS3EventsIndexLambdaRole = new Role(this, 'OpenSearchS3EventIndexLambdaRole', {
assumedBy: new ServicePrincipal('lambda.amazonaws.com'),
description: "OpenSearch Metrics S3 Event Index Lambda Execution Role",
roleName: "OpenSearchS3EventIndexLambdaRole",
inlinePolicies: {
"opensearchAssumeRolePolicy": new PolicyDocument({
statements: [
new PolicyStatement({
effect: Effect.ALLOW,
actions: ["sts:AssumeRole"],
resources: [`arn:aws:iam::${props.account}:role/OpenSearchFullAccessRole`],
conditions: {
StringEquals: { 'aws:PrincipalAccount': props.account, 'aws:RequestedRegion': props.region, },
}
})
]
}),
"opensearchReadS3EventsPolicy": new PolicyDocument({
statements: [
new PolicyStatement({
effect: Effect.ALLOW,
actions: ["s3:GetObject",
"s3:ListBucket"],
resources: [props.githubEventsBucket.bucketArn,
`${props.githubEventsBucket.bucketArn}/*`],
})
]
})
},
managedPolicies: [
ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'),
ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaVPCAccessExecutionRole'),
]
});

this.opensearchDomainConfig = {
domainName: 'opensearch-health',
ebsOptions: {
Expand All @@ -85,7 +123,7 @@ export class OpenSearchDomainStack extends Stack {

const domainArn = `arn:aws:es:${props.region}:${props.account}:domain/${this.opensearchDomainConfig.domainName}/*`;

const secureRolesList = [this.openSearchLambdaRole]
const secureRolesList = [this.openSearchMetricsLambdaRole, this.openSearchS3EventsIndexLambdaRole]
this.fullAccessRole = new Role(this, 'OpenSearchFullAccessRole', {
assumedBy: new CompositePrincipal(...secureRolesList.map((role) => new ArnPrincipal(role.roleArn))),
description: "Master role for OpenSearch full access",
Expand Down
83 changes: 83 additions & 0 deletions infrastructure/lib/stacks/s3EventIndexWorkflow.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/**
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/

import { Duration, Stack, StackProps } from "aws-cdk-lib";
import { Rule, Schedule } from "aws-cdk-lib/aws-events";
import { SfnStateMachine } from "aws-cdk-lib/aws-events-targets";
import {JsonPath, StateMachine, TaskInput} from "aws-cdk-lib/aws-stepfunctions";
import { LambdaInvoke } from "aws-cdk-lib/aws-stepfunctions-tasks";
import { Construct } from 'constructs';
import { OpenSearchLambda } from "../constructs/lambda";
import { OpenSearchDomainStack } from "./opensearch";
import { VpcStack } from "./vpc";
import {Bucket} from "aws-cdk-lib/aws-s3";

export interface OpenSearchS3EventIndexWorkflowStackProps extends StackProps {
readonly region: string;
readonly opensearchDomainStack: OpenSearchDomainStack;
readonly vpcStack: VpcStack;
readonly lambdaPackage: string;
readonly githubEventsBucket: Bucket;
}

export interface WorkflowComponent {
opensearchS3EventIndexWorkflowStateMachineName: string
}
export class OpenSearchS3EventIndexWorkflowStack extends Stack {
public readonly workflowComponent: WorkflowComponent;
constructor(scope: Construct, id: string, props: OpenSearchS3EventIndexWorkflowStackProps) {
super(scope, id, props);

const s3EventIndexTask = this.createS3EventIndexTask(this,
props.region,
props.opensearchDomainStack,
props.vpcStack,
props.lambdaPackage,
props.githubEventsBucket);

const opensearchS3EventIndexWorkflow = new StateMachine(this, 'OpenSearchS3EventIndexWorkflow', {
definition: s3EventIndexTask,
timeout: Duration.minutes(15),
stateMachineName: 'OpenSearchS3EventIndexWorkflow'
})

new Rule(this, 'OpenSearchS3EventIndexWorkflow-Every-Day', {
schedule: Schedule.expression('cron(0 0 * * ? *)'),
targets: [new SfnStateMachine(opensearchS3EventIndexWorkflow)],
});

this.workflowComponent = {
opensearchS3EventIndexWorkflowStateMachineName: opensearchS3EventIndexWorkflow.stateMachineName
}
}

private createS3EventIndexTask(scope: Construct, region: string, opensearchDomainStack: OpenSearchDomainStack, vpcStack: VpcStack, lambdaPackage: string, githubEventsBucket: Bucket) {
const openSearchDomain = opensearchDomainStack.domain;
const s3EventIndexLambda = new OpenSearchLambda(this, "OpenSearchMetricsS3EventIndexLambdaFunction", {
lambdaNameBase: "OpenSearchMetricsS3EventIndex",
handler: "org.opensearchmetrics.lambda.GithubEventsLambda",
lambdaZipPath: `../../../build/distributions/${lambdaPackage}`,
vpc: vpcStack.vpc,
securityGroup: vpcStack.securityGroup,
role: opensearchDomainStack.openSearchS3EventsIndexLambdaRole,
environment: {
S3_BUCKET_REGION: region,
EVENT_BUCKET_NAME: githubEventsBucket.bucketName,
OPENSEARCH_DOMAIN_ENDPOINT: openSearchDomain.domainEndpoint,
OPENSEARCH_DOMAIN_REGION: openSearchDomain.env.region,
OPENSEARCH_DOMAIN_ROLE: opensearchDomainStack.fullAccessRole.roleArn,
}
}).lambda;
return new LambdaInvoke(scope, 'S3 Event Index Lambda', {
lambdaFunction: s3EventIndexLambda,
resultPath: JsonPath.DISCARD,
payload: TaskInput.fromJsonPathAt("$"),
timeout: Duration.minutes(15)
}).addRetry();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,25 +13,30 @@ import Project from "../lib/enums/project";
import { OpenSearchDomainStack } from "../lib/stacks/opensearch";
import { VpcStack } from "../lib/stacks/vpc";
import { ArnPrincipal } from "aws-cdk-lib/aws-iam";
import {OpenSearchS3} from "../lib/stacks/s3";

test('Workflow Stack Test', () => {
test('Metrics Workflow Stack Test', () => {
const app = new App();
const vpcStack = new VpcStack(app, 'Test-OpenSearchHealth-VPC', {})
const vpcStack = new VpcStack(app, 'Test-OpenSearchHealth-VPC', {});
const s3Stack = new OpenSearchS3(app, "Test-OpenSearchMetrics-GitHubAutomationAppEvents-S3");
const openSearchDomainStack = new OpenSearchDomainStack(app, 'OpenSearchHealth-OpenSearch', {
region: "us-east-1",
account: "test-account",
vpcStack: new VpcStack(app, 'OpenSearchHealth-VPC', {}),
enableNginxCognito: true,
jenkinsAccess: {
jenkinsAccountRoles: [
new ArnPrincipal(Project.JENKINS_MASTER_ROLE),
new ArnPrincipal(Project.JENKINS_AGENT_ROLE)
]
},
githubAutomationAppAccess: "sample-role-arn",
githubEventsBucket: s3Stack.bucket,
});
const OpenSearchMetricsWorkflow = new OpenSearchMetricsWorkflowStack(app, 'Test-OpenSearchMetrics-Workflow', {
opensearchDomainStack: new OpenSearchDomainStack(app, 'Test-OpenSearchHealth-OpenSearch', {
region: "us-east-1",
account: "test-account",
vpcStack: vpcStack,
enableNginxCognito: true,
jenkinsAccess: {
jenkinsAccountRoles: [
new ArnPrincipal(Project.JENKINS_MASTER_ROLE),
new ArnPrincipal(Project.JENKINS_AGENT_ROLE)
]
}
}),
opensearchDomainStack: openSearchDomainStack,
vpcStack: vpcStack,
lambdaPackage: Project.LAMBDA_PACKAGE
lambdaPackage: Project.LAMBDA_PACKAGE,
});
const template = Template.fromStack(OpenSearchMetricsWorkflow);
template.resourceCountIs('AWS::IAM::Role', 2);
Expand Down Expand Up @@ -69,4 +74,4 @@ test('Workflow Stack Test', () => {
},
"StateMachineName": "OpenSearchMetricsWorkflow"
});
});
});
Loading

0 comments on commit d6b592c

Please sign in to comment.