Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implemented SequenceRunManager app deployment #155

Merged
merged 2 commits into from
Mar 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions config/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
FilemanagerDependencies,
OrcaBusStatelessConfig,
} from '../lib/workload/orcabus-stateless-stack';
import { Duration, aws_lambda, RemovalPolicy } from 'aws-cdk-lib';
import { Duration, RemovalPolicy } from 'aws-cdk-lib';
import { EventSourceProps } from '../lib/workload/stateful/event_source/component';
import { DbAuthType } from '../lib/workload/stateless/postgres_manager/function/type';

Expand Down Expand Up @@ -68,8 +68,6 @@ const orcaBusStatelessConfig = {
},
eventBusName: eventBusName,
lambdaSecurityGroupName: lambdaSecurityGroupName,
lambdaRuntimePythonVersion: aws_lambda.Runtime.PYTHON_3_10,
bclConvertFunctionName: 'orcabus_bcl_convert',
rdsMasterSecretName: rdsMasterSecretName,
postgresManagerConfig: {
masterSecretName: rdsMasterSecretName,
Expand Down
2 changes: 1 addition & 1 deletion lib/workload/orcabus-stateful-stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { getVpc } from './stateful/vpc/component';
import { EventBusConstruct, EventBusProps } from './stateful/eventbridge/component';
import { Database, ConfigurableDatabaseProps } from './stateful/database/component';
import { ConfigurableDatabaseProps, Database } from './stateful/database/component';
import { SecurityGroupConstruct, SecurityGroupProps } from './stateful/securitygroup/component';
import { SchemaRegistryConstruct, SchemaRegistryProps } from './stateful/schemaregistry/component';
import { EventSource, EventSourceProps } from './stateful/event_source/component';
Expand Down
26 changes: 16 additions & 10 deletions lib/workload/orcabus-stateless-stack.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as cdk from 'aws-cdk-lib';
import { Arn, aws_lambda } from 'aws-cdk-lib';
import { Arn } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { getVpc } from './stateful/vpc/component';
import { MultiSchemaConstructProps } from './stateless/schema/component';
Expand All @@ -11,13 +11,13 @@ import {
PostgresManagerStack,
PostgresManagerConfig,
} from './stateless/postgres_manager/deploy/postgres-manager-stack';
import { SequenceRunManagerStack } from './stateless/sequence_run_manager/deploy/component';
import { EventBus, IEventBus } from 'aws-cdk-lib/aws-events';

export interface OrcaBusStatelessConfig {
multiSchemaConstructProps: MultiSchemaConstructProps;
eventBusName: string;
lambdaSecurityGroupName: string;
lambdaRuntimePythonVersion: aws_lambda.Runtime;
bclConvertFunctionName: string;
rdsMasterSecretName: string;
postgresManagerConfig: PostgresManagerConfig;
filemanagerDependencies?: FilemanagerDependencies;
Expand All @@ -39,8 +39,9 @@ export interface FilemanagerDependencies {
}

export class OrcaBusStatelessStack extends cdk.Stack {
private vpc: IVpc;
private lambdaSecurityGroup: ISecurityGroup;
private readonly vpc: IVpc;
private readonly lambdaSecurityGroup: ISecurityGroup;
private readonly mainBus: IEventBus;

// microservice stacks
microserviceStackArray: cdk.Stack[] = [];
Expand All @@ -59,14 +60,15 @@ export class OrcaBusStatelessStack extends cdk.Stack {
this.vpc
);

// const mainBus = EventBus.fromEventBusName(this, 'OrcaBusMain', props.eventBusName);
this.mainBus = EventBus.fromEventBusName(this, 'OrcaBusMain', props.eventBusName);

// --- Create Stateless resources

// new MultiSchemaConstruct(this, 'MultiSchema', props.multiSchemaConstructProps);

// hook microservice construct components here
this.createSequenceRunManager();

this.microserviceStackArray.push(this.createSequenceRunManager(props));
this.microserviceStackArray.push(this.createPostgresManager(props.postgresManagerConfig));

if (props.filemanagerDependencies) {
Expand All @@ -77,9 +79,13 @@ export class OrcaBusStatelessStack extends cdk.Stack {
}
}

private createSequenceRunManager() {
// TODO new SequenceRunManagerConstruct() from lib/workload/stateless/sequence_run_manager/deploy/component.ts
// However, the implementation is still incomplete...
private createSequenceRunManager(props: cdk.StackProps) {
return new SequenceRunManagerStack(this, 'SequenceRunManager', {
securityGroups: [this.lambdaSecurityGroup],
vpc: this.vpc,
mainBus: this.mainBus,
...props,
});
}

private createPostgresManager(config: PostgresManagerConfig) {
Expand Down
6 changes: 3 additions & 3 deletions lib/workload/stateless/sequence_run_manager/Makefile
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
.PHONY: test suite

install:
@pip install -r requirements-dev.txt
@pip install -r deps/requirements-dev.txt

check: lint

lint:
@black -t py312 --check ./src --exclude skel --exclude .venv
@black -t py312 --check . --exclude .venv

lint-fix:
@black -t py312 ./src --exclude skel --exclude .venv
@black -t py312 . --exclude .venv

# full mock suite test pipeline - install deps, bring up compose stack, run suite, bring down compose stack
test: install up suite down
Expand Down
4 changes: 2 additions & 2 deletions lib/workload/stateless/sequence_run_manager/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ _*If you are PyCharmer and opening the whole `orcabus` project (i.e. not doing s

- Setup Python environment (conda or venv)
```
conda create -n orcabus python=3.12
conda activate orcabus
conda create -n sequence_run_manager python=3.12
conda activate sequence_run_manager
```

### Make
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ This directory contains CDK code constructs that will be called and assembled by

You may have multi-level directory structure under this folder as see fit to arrange your CDK constructs.

However. Collectively, all CDK constructs created under this deploy directory will form as **one deployable component unit** for the higher level CDK Stack. Hence, just single `component.ts` file might be sufficed if your app deployment is a simpler CDK deployment construction.
However. Collectively, all CDK constructs created under this deploy directory will form as **one deployable stack** for the higher level CDK Stack. Hence, just single `stack-name.ts` file might be sufficed if your app deployment is a simpler CDK deployment construction.
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { Construct } from 'constructs';
import { aws_ssm, Duration } from 'aws-cdk-lib';
import * as cognito from 'aws-cdk-lib/aws-cognito';
import { HttpUserPoolAuthorizer } from 'aws-cdk-lib/aws-apigatewayv2-authorizers';
import { CorsHttpMethod, HttpApi } from 'aws-cdk-lib/aws-apigatewayv2';
import { IStringParameter } from 'aws-cdk-lib/aws-ssm';

export class SRMApiGatewayConstruct extends Construct {
private readonly SSM_USER_POOL_ID: string = '/data_portal/client/cog_user_pool_id'; // FIXME one fine day in future
private readonly _httpApi: HttpApi;

constructor(scope: Construct, id: string) {
super(scope, id);

const userPoolParam: IStringParameter = aws_ssm.StringParameter.fromStringParameterName(this, id + 'SSMStringParameter', this.SSM_USER_POOL_ID);
const userPool = cognito.UserPool.fromUserPoolId(scope, id + 'UserPool', userPoolParam.stringValue);

this._httpApi = new HttpApi(this, id + 'HttpApi', {
apiName: 'OrcaBus SequenceRunManager API',
corsPreflight: {
allowHeaders: ['Authorization'],
allowMethods: [
CorsHttpMethod.GET,
CorsHttpMethod.HEAD,
CorsHttpMethod.OPTIONS,
CorsHttpMethod.POST,
],
allowOrigins: ['*'], // FIXME allowed origins from config constant
maxAge: Duration.days(10),
},
defaultAuthorizer: new HttpUserPoolAuthorizer(id + 'HttpUserPoolAuthorizer', userPool),
// defaultDomainMapping: ... TODO
});

// TODO setup cloud map service discovery perhaps
}

get httpApi(): HttpApi {
return this._httpApi;
}
}
166 changes: 75 additions & 91 deletions lib/workload/stateless/sequence_run_manager/deploy/component.ts
Original file line number Diff line number Diff line change
@@ -1,145 +1,129 @@
// FIXME complete the implementation

import path from 'path';
import * as cdk from 'aws-cdk-lib';
import { aws_lambda, aws_secretsmanager, Stack } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { ILayerVersion } from 'aws-cdk-lib/aws-lambda';
import { ISecurityGroup, IVpc } from 'aws-cdk-lib/aws-ec2';
import { IEventBus, Rule } from 'aws-cdk-lib/aws-events';
import { aws_events_targets, aws_lambda, Duration } from 'aws-cdk-lib';
import { IEventBus } from 'aws-cdk-lib/aws-events';
import { PythonFunction, PythonLayerVersion } from '@aws-cdk/aws-lambda-python-alpha';
import { HttpLambdaIntegration } from '@aws-cdk/aws-apigatewayv2-integrations-alpha';
import { CorsHttpMethod, HttpApi, HttpMethod, HttpStage } from '@aws-cdk/aws-apigatewayv2-alpha';
import { HttpLambdaIntegration } from 'aws-cdk-lib/aws-apigatewayv2-integrations';
import { HttpMethod, HttpRoute, HttpRouteKey, HttpStage } from 'aws-cdk-lib/aws-apigatewayv2';
import { ManagedPolicy, Role, ServicePrincipal } from 'aws-cdk-lib/aws-iam';
import { SRMApiGatewayConstruct } from './apigw/component';

export interface SequenceRunManagerProps {
// FIXME change prop interface name
layers: ILayerVersion[];
securityGroups: ISecurityGroup[];
vpc: IVpc;
mainBus: IEventBus;
functionName: string;
lambdaRuntimePythonVersion: aws_lambda.Runtime;
}

export class SequenceRunManagerConstruct extends Construct {
// FIXME change construct name
private scope: Construct;
export class SequenceRunManagerStack extends Stack {
// Follow by naming convention. See https://github.com/umccr/orcabus/pull/149
private readonly secretId: string = 'orcabus/sequence_run_manager/rdsLoginCredential';
private readonly apiNamespace: string = '/srm/v1';
private readonly id: string;
private props: SequenceRunManagerProps;
private baseLayer: PythonLayerVersion;
private readonly props: SequenceRunManagerProps;
private readonly baseLayer: PythonLayerVersion;
private readonly lambdaEnv;
private readonly lambdaRuntimePythonVersion: aws_lambda.Runtime = aws_lambda.Runtime.PYTHON_3_12;
private readonly lambdaRole: Role;

constructor(scope: Construct, id: string, props: SequenceRunManagerProps) {
constructor(scope: Construct, id: string, props: cdk.StackProps & SequenceRunManagerProps) {
super(scope, id);

this.scope = scope;
this.id = id;
this.props = props;

this.lambdaRole = new Role(this, this.id + 'Role', {
assumedBy: new ServicePrincipal('lambda.amazonaws.com'),
description: 'Lambda execution role for ' + this.id,
});
this.lambdaRole.addManagedPolicy(
ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'),
);
this.lambdaRole.addManagedPolicy(
ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaVPCAccessExecutionRole'),
);

const dbSecret = aws_secretsmanager.Secret.fromSecretNameV2(this, this.id + 'dbSecret', this.secretId);
dbSecret.grantRead(this.lambdaRole);

this.lambdaEnv = {
DJANGO_SETTINGS_MODULE: 'sequence_run_manager.settings.aws', // FIXME project name
DJANGO_SETTINGS_MODULE: 'sequence_run_manager.settings.aws',
EVENT_BUS_NAME: this.props.mainBus.eventBusName,
SECRET_ID: this.secretId,
};

this.createLambdaLayer();
this.baseLayer = new PythonLayerVersion(this, this.id + 'Layer', {
entry: path.join(__dirname, '../deps'),
compatibleRuntimes: [this.lambdaRuntimePythonVersion],
});

this.createMigrationHandler();
this.createApiHandler();
this.createProcHandler();
this.createApiHandlerAndIntegration();
this.createProcSqsHandler();
}

private createLambdaLayer() {
this.baseLayer = new PythonLayerVersion(this, this.id + 'Layer', {
entry: path.join(__dirname, '../'), // FIXME haven't tried whether dot dot slash here is working like this
private createPythonFunction(name: string, props: object): PythonFunction {
return new PythonFunction(this, this.id + name, {
entry: path.join(__dirname, '../'),
runtime: this.lambdaRuntimePythonVersion,
layers: [this.baseLayer],
environment: this.lambdaEnv,
securityGroups: this.props.securityGroups,
vpc: this.props.vpc,
vpcSubnets: { subnets: this.props.vpc.privateSubnets },
role: this.lambdaRole,
...props,
});
}

private createMigrationHandler() {
new PythonFunction(this, this.id + 'Migration', {
entry: path.join(__dirname, '../'),
runtime: this.props.lambdaRuntimePythonVersion,
layers: [this.baseLayer],
this.createPythonFunction('Migration', {
index: 'migrate.py',
handler: 'handler',
environment: this.lambdaEnv,
});
}

private createApiHandler() {
const apiFn = new PythonFunction(this, this.id + 'Api', {
entry: path.join(__dirname, '../'),
runtime: this.props.lambdaRuntimePythonVersion,
layers: [this.baseLayer],
private createApiHandlerAndIntegration() {
const apiFn: PythonFunction = this.createPythonFunction('Api', {
index: 'api.py',
handler: 'handler',
environment: this.lambdaEnv,
});

const apiIntegration = new HttpLambdaIntegration(this.id + 'ApiIntegration', apiFn);
const httpApi = new SRMApiGatewayConstruct(this, this.id + 'SRMApiGatewayConstruct').httpApi;

const httpApi = new HttpApi(this, this.id + 'HttpApi', {
corsPreflight: {
allowHeaders: ['Authorization'],
allowMethods: [
CorsHttpMethod.GET,
CorsHttpMethod.HEAD,
CorsHttpMethod.OPTIONS,
CorsHttpMethod.POST,
],
allowOrigins: ['*'], // TODO to get this allowed origins from config constant
maxAge: Duration.days(10),
},
});
new HttpStage(this, this.id + 'HttpStage', { httpApi: httpApi });

httpApi.addRoutes({
path: '/{proxy+}',
methods: [HttpMethod.ANY],
integration: apiIntegration,
});
const apiIntegration = new HttpLambdaIntegration(this.id + 'ApiIntegration', apiFn);

new HttpStage(this, this.id + 'ApiStage', {
new HttpRoute(this, this.id + 'HttpRoute', {
httpApi: httpApi,
integration: apiIntegration,
routeKey: HttpRouteKey.with(this.apiNamespace + '/{proxy+}', HttpMethod.ANY),
});
}

private createProcHandler() {
const procFn = new PythonFunction(this, this.id + 'ProcHandler', {
entry: path.join(__dirname, '../'),
runtime: this.props.lambdaRuntimePythonVersion,
layers: [this.baseLayer],
index: '{{project_name}}_proc/lambdas/hello_proc.py', // FIXME update appropriate path to Lambda entry point
handler: 'handler',
environment: this.lambdaEnv,
});

this.props.mainBus.grantPutEventsTo(procFn);
this.setupEventRule(procFn);
}

private createProcSqsHandler() {
const procSqsFn = new PythonFunction(this, this.id + 'ProcHandler', {
entry: path.join(__dirname, '../'),
runtime: this.props.lambdaRuntimePythonVersion,
layers: [this.baseLayer],
index: '{{project_name}}_proc/lambdas/hello_proc.py', // FIXME update appropriate path to Lambda entry point
const procSqsFn = this.createPythonFunction('ProcHandler', {
index: 'sequence_run_manager_proc/lambdas/bssh_event.py',
handler: 'sqs_handler',
environment: this.lambdaEnv,
});

this.props.mainBus.grantPutEventsTo(procSqsFn); // FIXME remove this if no use
this.setupEventRule(procSqsFn); // FIXME remove this if no use
this.props.mainBus.grantPutEventsTo(procSqsFn);
// this.setupEventRule(procSqsFn); // TODO comment this out for now
}

private setupEventRule(fn: aws_lambda.Function) {
const eventRule = new Rule(this, this.id + 'EventRule', {
ruleName: this.id + 'EventRule',
description: 'Rule to send {event_type.value} events to the {handler.function_name} Lambda',
eventBus: this.props.mainBus,
});

eventRule.addTarget(new aws_events_targets.LambdaFunction(fn));
eventRule.addEventPattern({
source: ['ORCHESTRATOR'], // FIXME complete source to destination event mapping
detailType: ['SequenceRunStateChange'],
});
}
// private setupEventRule(fn: aws_lambda.Function) {
// const eventRule = new Rule(this, this.id + 'EventRule', {
// ruleName: this.id + 'EventRule',
// description: 'Rule to send {event_type.value} events to the {handler.function_name} Lambda',
// eventBus: this.props.mainBus,
// });
//
// eventRule.addTarget(new aws_events_targets.LambdaFunction(fn));
// eventRule.addEventPattern({
// source: ['ORCHESTRATOR'], // FIXME complete source to destination event mapping
// detailType: ['SequenceRunStateChange'],
// });
// }
}
Loading
Loading