Skip to content

Commit

Permalink
Reindex GitHub Events into maintainer inactivity data
Browse files Browse the repository at this point in the history
Signed-off-by: Brandon Shien <bshien@amazon.com>
  • Loading branch information
bshien committed Oct 31, 2024
1 parent 157464b commit a6d5c23
Show file tree
Hide file tree
Showing 17 changed files with 1,195 additions and 14 deletions.
12 changes: 11 additions & 1 deletion infrastructure/lib/infrastructure-stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { GitHubAutomationApp } from "./stacks/gitHubAutomationApp";
import { OpenSearchS3 } from "./stacks/s3";
import { GitHubWorkflowMonitorAlarms } from "./stacks/gitHubWorkflowMonitorAlarms";
import {OpenSearchS3EventIndexWorkflowStack} from "./stacks/s3EventIndexWorkflow";
import {OpenSearchMaintainerInactivityWorkflowStack} from "./stacks/maintainerInactivityWorkflow";

export class InfrastructureStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
Expand Down Expand Up @@ -98,6 +99,14 @@ export class InfrastructureStack extends Stack {
})
openSearchS3EventIndexWorkflowStack.node.addDependency(vpcStack, openSearchDomainStack);

// Create OpenSearch Maintainer Inactivity Lambda setup
const openSearchMaintainerInactivityWorkflowStack = new OpenSearchMaintainerInactivityWorkflowStack(app, 'OpenSearchMaintainerInactivity-Workflow', {
opensearchDomainStack: openSearchDomainStack,
vpcStack: vpcStack,
lambdaPackage: Project.LAMBDA_PACKAGE,
})
openSearchMaintainerInactivityWorkflowStack.node.addDependency(vpcStack, openSearchDomainStack);

// Create Secret Manager for the metrics project
const openSearchMetricsSecretsStack = new OpenSearchMetricsSecretsStack(app, "OpenSearchMetrics-Secrets", {
secretName: 'metrics-creds'
Expand All @@ -110,7 +119,8 @@ export class InfrastructureStack extends Stack {
account: Project.AWS_ACCOUNT,
workflowComponent: {
opensearchMetricsWorkflowStateMachineName: openSearchMetricsWorkflowStack.workflowComponent.opensearchMetricsWorkflowStateMachineName,
opensearchS3EventIndexWorkflowStateMachineName: openSearchS3EventIndexWorkflowStack.workflowComponent.opensearchS3EventIndexWorkflowStateMachineName
opensearchMaintainerInactivityWorkflowStateMachineName: openSearchMaintainerInactivityWorkflowStack.workflowComponent.opensearchMaintainerInactivityWorkflowStateMachineName,
opensearchS3EventIndexWorkflowStateMachineName: openSearchS3EventIndexWorkflowStack.workflowComponent.opensearchS3EventIndexWorkflowStateMachineName,
},
lambdaPackage: Project.LAMBDA_PACKAGE,
secrets: openSearchMetricsSecretsStack.secret,
Expand Down
78 changes: 78 additions & 0 deletions infrastructure/lib/stacks/maintainerInactivityWorkflow.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/**
* 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 } 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";

export interface OpenSearchMaintainerInactivityWorkflowStackProps extends StackProps {
readonly opensearchDomainStack: OpenSearchDomainStack;
readonly vpcStack: VpcStack;
readonly lambdaPackage: string
}

export interface WorkflowComponent {
opensearchMaintainerInactivityWorkflowStateMachineName: string
}

export class OpenSearchMaintainerInactivityWorkflowStack extends Stack {
public readonly workflowComponent: WorkflowComponent;
constructor(scope: Construct, id: string, props: OpenSearchMaintainerInactivityWorkflowStackProps) {
super(scope, id, props);

const maintainerInactivityTask = this.createMaintainerInactivityTask(
this,
props.opensearchDomainStack,
props.vpcStack,
props.lambdaPackage
);
const opensearchMaintainerInactivityWorkflow = new StateMachine(this, 'OpenSearchMaintainerInactivityWorkflow', {
definition: maintainerInactivityTask,
timeout: Duration.minutes(15),
stateMachineName: 'OpenSearchMaintainerInactivityWorkflow'
})

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

this.workflowComponent = {
opensearchMaintainerInactivityWorkflowStateMachineName: opensearchMaintainerInactivityWorkflow.stateMachineName
}
}

private createMaintainerInactivityTask(scope: Construct, opensearchDomainStack: OpenSearchDomainStack,
vpcStack: VpcStack, lambdaPackage: string) {
const openSearchDomain = opensearchDomainStack.domain;
const maintainerInactivityLambda = new OpenSearchLambda(scope, "OpenSearchMetricsMaintainerInactivityLambdaFunction", {
lambdaNameBase: "OpenSearchMetricsMaintainerInactivity",
handler: "org.opensearchmetrics.lambda.MaintainerInactivityLambda",
lambdaZipPath: `../../../build/distributions/${lambdaPackage}`,
vpc: vpcStack.vpc,
securityGroup: vpcStack.securityGroup,
role: opensearchDomainStack.openSearchMetricsLambdaRole,
environment: {
OPENSEARCH_DOMAIN_ENDPOINT: openSearchDomain.domainEndpoint,
OPENSEARCH_DOMAIN_REGION: openSearchDomain.env.region,
OPENSEARCH_DOMAIN_ROLE: opensearchDomainStack.fullAccessRole.roleArn,
},
}).lambda;
return new LambdaInvoke(scope, 'Maintainer Inactivity Lambda', {
lambdaFunction: maintainerInactivityLambda,
resultPath: JsonPath.DISCARD,
timeout: Duration.minutes(15)
}).addRetry();
}
}
1 change: 1 addition & 0 deletions infrastructure/lib/stacks/monitoringDashboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,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_MaintainerInactivityWorkflow', stateMachineName: this.props.workflowComponent.opensearchMaintainerInactivityWorkflowStateMachineName },
{ alertName: 'StepFunction_execution_errors_S3EventIndexWorkflow', stateMachineName: this.props.workflowComponent.opensearchS3EventIndexWorkflowStateMachineName },
];

Expand Down
78 changes: 78 additions & 0 deletions infrastructure/test/maintainer-inactivity-workflow-stack.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/**
* 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 { App } from "aws-cdk-lib";
import { Template } from "aws-cdk-lib/assertions";
import { OpenSearchMetricsWorkflowStack } from "../lib/stacks/metricsWorkflow";
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";
import {OpenSearchMaintainerInactivityWorkflowStack} from "../lib/stacks/maintainerInactivityWorkflow";

test('Maintainer Inactivity Workflow Stack Test', () => {
const app = new App();
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 openSearchMaintainerInactivityWorkflowStack = new OpenSearchMaintainerInactivityWorkflowStack(app, 'Test-OpenSearchMaintainerInactivity-Workflow', {
opensearchDomainStack: openSearchDomainStack,
vpcStack: vpcStack,
lambdaPackage: Project.LAMBDA_PACKAGE,
});
const template = Template.fromStack(openSearchMaintainerInactivityWorkflowStack);
template.resourceCountIs('AWS::IAM::Role', 2);
template.resourceCountIs('AWS::Lambda::Function', 1);
template.hasResourceProperties('AWS::Lambda::Function', {
"FunctionName": "OpenSearchMetricsMaintainerInactivityLambda",
"Handler": "org.opensearchmetrics.lambda.MaintainerInactivityLambda"
});
template.resourceCountIs('AWS::StepFunctions::StateMachine', 1);
template.hasResourceProperties('AWS::StepFunctions::StateMachine', {
"DefinitionString": {
"Fn::Join": [
"",
[
"{\"StartAt\":\"Maintainer Inactivity Lambda\",\"States\":{\"Maintainer Inactivity Lambda\":{\"End\":true,\"Retry\":[{\"ErrorEquals\":[\"Lambda.ClientExecutionTimeoutException\",\"Lambda.ServiceException\",\"Lambda.AWSLambdaException\",\"Lambda.SdkClientException\"],\"IntervalSeconds\":2,\"MaxAttempts\":6,\"BackoffRate\":2},{\"ErrorEquals\":[\"States.ALL\"]}],\"Type\":\"Task\",\"TimeoutSeconds\":900,\"ResultPath\":null,\"Resource\":\"arn:",
{
"Ref": "AWS::Partition"
},
":states:::lambda:invoke\",\"Parameters\":{\"FunctionName\":\"",
{
"Fn::GetAtt": [
"OpenSearchMetricsMaintainerInactivityLambdaCB6D4475",
"Arn"
]
},
"\",\"Payload.$\":\"$\"}}},\"TimeoutSeconds\":900}"
]
]
},
"RoleArn": {
"Fn::GetAtt": [
"OpenSearchMaintainerInactivityWorkflowRoleF9A5E625",
"Arn"
]
},
"StateMachineName": "OpenSearchMaintainerInactivityWorkflow"
});
});
45 changes: 44 additions & 1 deletion infrastructure/test/monitoring-stack.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { OpenSearchMetricsMonitoringStack } from "../lib/stacks/monitoringDashbo
import { OpenSearchMetricsSecretsStack } from "../lib/stacks/secrets";
import {OpenSearchS3} from "../lib/stacks/s3";
import {OpenSearchS3EventIndexWorkflowStack} from "../lib/stacks/s3EventIndexWorkflow";
import {OpenSearchMaintainerInactivityWorkflowStack} from "../lib/stacks/maintainerInactivityWorkflow";

test('Monitoring Stack Test', () => {
const app = new App();
Expand All @@ -40,6 +41,11 @@ test('Monitoring Stack Test', () => {
vpcStack: vpcStack,
lambdaPackage: Project.LAMBDA_PACKAGE,
});
const openSearchMaintainerInactivityWorkflowStack = new OpenSearchMaintainerInactivityWorkflowStack(app, 'Test-OpenSearchMaintainerInactivity-Workflow', {
opensearchDomainStack: opensearchDomainStack,
vpcStack: vpcStack,
lambdaPackage: Project.LAMBDA_PACKAGE,
});
const openSearchS3EventIndexWorkflowStack = new OpenSearchS3EventIndexWorkflowStack(app, 'Test-OpenSearchS3EventIndex-Workflow', {
region: Project.REGION,
opensearchDomainStack: opensearchDomainStack,
Expand All @@ -55,6 +61,7 @@ test('Monitoring Stack Test', () => {
account: Project.AWS_ACCOUNT,
workflowComponent: {
opensearchMetricsWorkflowStateMachineName: openSearchMetricsWorkflowStack.workflowComponent.opensearchMetricsWorkflowStateMachineName,
opensearchMaintainerInactivityWorkflowStateMachineName: openSearchMaintainerInactivityWorkflowStack.workflowComponent.opensearchMaintainerInactivityWorkflowStateMachineName,
opensearchS3EventIndexWorkflowStateMachineName: openSearchS3EventIndexWorkflowStack.workflowComponent.opensearchS3EventIndexWorkflowStateMachineName
},
lambdaPackage: Project.LAMBDA_PACKAGE,
Expand All @@ -64,7 +71,7 @@ test('Monitoring Stack Test', () => {
const template = Template.fromStack(openSearchMetricsMonitoringStack);
template.resourceCountIs('AWS::IAM::Role', 2);
template.resourceCountIs('AWS::IAM::Policy', 1);
template.resourceCountIs('AWS::CloudWatch::Alarm', 3);
template.resourceCountIs('AWS::CloudWatch::Alarm', 4);
template.resourceCountIs('AWS::SNS::Topic', 2);
template.resourceCountIs('AWS::Synthetics::Canary', 1);
template.hasResourceProperties('AWS::IAM::Role', {
Expand Down Expand Up @@ -171,6 +178,42 @@ test('Monitoring Stack Test', () => {
"Threshold": 1,
"TreatMissingData": "notBreaching"
});

template.hasResourceProperties('AWS::CloudWatch::Alarm', {
"AlarmActions": [
{
"Ref": "SnsMonitorsStepFunctionExecutionsFailedOpenSearchMetricsAlarmStepFunctionExecutionsFailed0B259DBC"
}
],
"AlarmDescription": "Detect SF execution failure",
"AlarmName": "StepFunction_execution_errors_MaintainerInactivityWorkflow",
"ComparisonOperator": "GreaterThanOrEqualToThreshold",
"DatapointsToAlarm": 1,
"Dimensions": [
{
"Name": "StateMachineArn",
"Value": {
"Fn::Join": [
"",
[
"arn:aws:states:::stateMachine:",
{
"Fn::ImportValue": "Test-OpenSearchMaintainerInactivity-Workflow:ExportsOutputFnGetAttOpenSearchMaintainerInactivityWorkflowE07E380BName0C54300B"
}
]
]
}
}
],
"EvaluationPeriods": 1,
"MetricName": "ExecutionsFailed",
"Namespace": "AWS/States",
"Period": 300,
"Statistic": "Sum",
"Threshold": 1,
"TreatMissingData": "notBreaching"
});

template.hasResourceProperties('AWS::CloudWatch::Alarm', {
"AlarmActions": [
{
Expand Down
5 changes: 3 additions & 2 deletions src/main/java/org/opensearchmetrics/dagger/CommonModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import org.opensearchmetrics.metrics.MetricsCalculation;
import org.opensearchmetrics.metrics.general.*;
import org.opensearchmetrics.metrics.label.LabelMetrics;
import org.opensearchmetrics.metrics.maintainer.MaintainerMetrics;
import org.opensearchmetrics.metrics.release.ReleaseMetrics;
import org.opensearchmetrics.util.OpenSearchUtil;
import com.amazonaws.services.secretsmanager.AWSSecretsManager;
Expand Down Expand Up @@ -107,15 +108,15 @@ public MetricsCalculation getMetricsCalculation(OpenSearchUtil openSearchUtil, O
CreatedIssues createdIssues, IssueComments issueComments,
PullComments pullComments, IssuePositiveReactions issuePositiveReactions,
IssueNegativeReactions issueNegativeReactions, LabelMetrics labelMetrics,
ReleaseMetrics releaseMetrics) {
ReleaseMetrics releaseMetrics, MaintainerMetrics maintainerMetrics) {
return new MetricsCalculation(openSearchUtil, objectMapper,
untriagedIssues, uncommentedPullRequests,
unlabelledPullRequests, unlabelledIssues,
mergedPullRequests, openPullRequests,
openIssues, closedIssues, createdIssues,
issueComments, pullComments,
issuePositiveReactions, issueNegativeReactions,
labelMetrics, releaseMetrics);
labelMetrics, releaseMetrics, maintainerMetrics);
}

@Provides
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package org.opensearchmetrics.lambda;

import com.amazonaws.services.lambda.runtime.Context;
import com.google.common.annotations.VisibleForTesting;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.opensearch.action.search.SearchRequest;
import org.opensearch.action.search.SearchResponse;
import org.opensearch.search.aggregations.AggregationBuilders;
import org.opensearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.opensearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.opensearch.search.builder.SearchSourceBuilder;
import org.opensearchmetrics.dagger.DaggerServiceComponent;
import org.opensearchmetrics.dagger.ServiceComponent;
import org.opensearchmetrics.metrics.MetricsCalculation;
import org.opensearchmetrics.util.OpenSearchUtil;

import java.util.List;
import java.util.stream.Collectors;

@Slf4j
public class MaintainerInactivityLambda extends AbstractBaseLambda {
private static final ServiceComponent COMPONENT = DaggerServiceComponent.create();
private final OpenSearchUtil openSearchUtil;

private final MetricsCalculation metricsCalculation;

public MaintainerInactivityLambda() {
this(COMPONENT.getOpenSearchUtil(), COMPONENT.getMetricsCalculation());
}

Check warning on line 30 in src/main/java/org/opensearchmetrics/lambda/MaintainerInactivityLambda.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/opensearchmetrics/lambda/MaintainerInactivityLambda.java#L29-L30

Added lines #L29 - L30 were not covered by tests

@VisibleForTesting
MaintainerInactivityLambda(@NonNull OpenSearchUtil openSearchUtil, @NonNull MetricsCalculation metricsCalculation) {
this.openSearchUtil = openSearchUtil;
this.metricsCalculation = metricsCalculation;
}

@Override
public Void handleRequest(Void input, Context context) {
SearchRequest searchRequest = new SearchRequest("github_repos");
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.size(0);
TermsAggregationBuilder aggregation = AggregationBuilders.terms("repos")
.field("repository.keyword").size(500);
searchSourceBuilder.aggregation(aggregation);
searchRequest.source(searchSourceBuilder);
SearchResponse searchResponse = null;
searchResponse = openSearchUtil.search(searchRequest);
ParsedStringTerms termsAggregation = searchResponse.getAggregations().get("repos");
List<String> keys = termsAggregation.getBuckets().stream()
.map(bucket -> bucket.getKeyAsString())
.collect(Collectors.toList());
try {
metricsCalculation.generateMaintainerMetrics(keys);
} catch (Exception e) {
throw new RuntimeException("Error running Maintainer Inactivity Calculation", e);

Check warning on line 56 in src/main/java/org/opensearchmetrics/lambda/MaintainerInactivityLambda.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/opensearchmetrics/lambda/MaintainerInactivityLambda.java#L55-L56

Added lines #L55 - L56 were not covered by tests
}
return input;
}
}

Loading

0 comments on commit a6d5c23

Please sign in to comment.