diff --git a/examples/deadline/All-In-AWS-Infrastructure-Basic/python/package/lib/compute_tier.py b/examples/deadline/All-In-AWS-Infrastructure-Basic/python/package/lib/compute_tier.py index 39ceb8144..0f41b9a73 100644 --- a/examples/deadline/All-In-AWS-Infrastructure-Basic/python/package/lib/compute_tier.py +++ b/examples/deadline/All-In-AWS-Infrastructure-Basic/python/package/lib/compute_tier.py @@ -17,15 +17,20 @@ IVpc, Port ) +from aws_cdk.aws_s3_assets import ( + Asset +) from aws_rfdk import ( HealthMonitor, ) from aws_rfdk.deadline import ( + InstanceUserDataProvider, IRenderQueue, WorkerInstanceFleet, ) +import os @dataclass class ComputeTierProps(StackProps): @@ -43,6 +48,22 @@ class ComputeTierProps(StackProps): # The bastion host to allow connection to Worker nodes. bastion: Optional[BastionHostLinux] = None +class UserDataProvider(InstanceUserDataProvider): + def pre_cloud_watch_agent(self, host) -> None: + host.user_data.add_commands("echo preCloudWatchAgent") + def pre_deadline_configuration(self, host) -> None: + host.user_data.add_commands("echo preDeadlineConfiguration") + def post_worker_launch(self, host) -> None: + host.user_data.add_commands("echo postWorkerLaunch") + test_script=Asset(host.node.scope, "SampleAsset", + path=os.path.join(os.getcwd(), "..", "scripts", "configure_worker.sh") + ) + test_script.grant_read(host) + local_path = host.user_data.add_s3_download_command( + bucket=test_script.bucket, + bucket_key=test_script.s3_object_key + ) + host.user_data.add_execute_file_command(file_path=local_path) class ComputeTier(Stack): """ @@ -78,6 +99,7 @@ def __init__(self, scope: Construct, stack_id: str, *, props: ComputeTierProps, worker_machine_image=props.worker_machine_image, health_monitor=self.health_monitor, key_name=props.key_pair_name, + user_data_provider=UserDataProvider() ) if props.bastion: diff --git a/examples/deadline/All-In-AWS-Infrastructure-Basic/scripts/configure_worker.sh b/examples/deadline/All-In-AWS-Infrastructure-Basic/scripts/configure_worker.sh new file mode 100644 index 000000000..9a9cf8b4f --- /dev/null +++ b/examples/deadline/All-In-AWS-Infrastructure-Basic/scripts/configure_worker.sh @@ -0,0 +1 @@ +mkdir test_dir diff --git a/examples/deadline/All-In-AWS-Infrastructure-Basic/ts/lib/compute-tier.ts b/examples/deadline/All-In-AWS-Infrastructure-Basic/ts/lib/compute-tier.ts index eb0bab6ad..73bea405d 100644 --- a/examples/deadline/All-In-AWS-Infrastructure-Basic/ts/lib/compute-tier.ts +++ b/examples/deadline/All-In-AWS-Infrastructure-Basic/ts/lib/compute-tier.ts @@ -11,6 +11,8 @@ import { } from '@aws-cdk/aws-ec2'; import * as cdk from '@aws-cdk/core'; import { + IHost, + InstanceUserDataProvider, IRenderQueue, IWorkerFleet, WorkerInstanceFleet, @@ -19,6 +21,8 @@ import { HealthMonitor, IHealthMonitor, } from 'aws-rfdk'; +import { Asset } from '@aws-cdk/aws-s3-assets'; +import * as path from 'path' /** * Properties for {@link ComputeTier}. @@ -50,6 +54,33 @@ export interface ComputeTierProps extends cdk.StackProps { readonly bastion?: BastionHostLinux; } +class UserDataProvider extends InstanceUserDataProvider { + preCloudWatchAgent(host: IHost): void { + host.userData.addCommands('echo preCloudWatchAgent'); + } + preDeadlineConfiguration(host: IHost): void { + host.userData.addCommands('echo preDeadlineConfiguration'); + } + postWorkerLaunch(host: IHost): void { + host.userData.addCommands('echo postWorkerLaunch'); + if (host.node.scope != undefined) { + const testScript = new Asset( + host.node.scope as cdk.Construct, + 'SampleAsset', + {path: path.join(__dirname, '..', 'configure_worker.sh')}, + ); + testScript.grantRead(host); + const localPath = host.userData.addS3DownloadCommand({ + bucket: testScript.bucket, + bucketKey: testScript.s3ObjectKey, + }); + host.userData.addExecuteFileCommand({ + filePath: localPath, + }) + } + } +} + /** * The computer tier consists of raw compute power. For a Deadline render farm, * this will be the fleet of Worker nodes that render Deadline jobs. @@ -88,6 +119,7 @@ export class ComputeTier extends cdk.Stack { workerMachineImage: props.workerMachineImage, healthMonitor: this.healthMonitor, keyName: props.keyPairName, + userDataProvider: new UserDataProvider(), }); if (props.bastion) { diff --git a/packages/aws-rfdk/lib/deadline/lib/worker-configuration.ts b/packages/aws-rfdk/lib/deadline/lib/worker-configuration.ts index 6203f7956..84c3c3319 100644 --- a/packages/aws-rfdk/lib/deadline/lib/worker-configuration.ts +++ b/packages/aws-rfdk/lib/deadline/lib/worker-configuration.ts @@ -29,6 +29,26 @@ import { Version, } from './version'; +/** + * Provider for adding user data scripts + */ +export interface IInstanceUserDataProvider { + // Method that is invoked before configuration Cloud Watch Agent. + preCloudWatchAgent(host: IHost): void; + // Method that is invoked before Deadline worker and renderqueue configuration . + preDeadlineConfiguration(host: IHost): void; + // Method that is invoked after all configuration is done and worker started. + postWorkerLaunch(host: IHost): void; +} + +export class InstanceUserDataProvider implements IInstanceUserDataProvider{ + preCloudWatchAgent(_host: IHost): void { + } + preDeadlineConfiguration(_host: IHost): void { + } + postWorkerLaunch(_host: IHost): void { + } +} /** * Configuration settings for Deadline Workers */ @@ -86,6 +106,11 @@ export interface WorkerInstanceConfigurationProps { * @default The Worker is assigned the default settings as outlined in the WorkerSettings interface. */ readonly workerSettings?: WorkerSettings; + + /** + * The provider for User data scripts that will be invoke during worker configuration. + */ + readonly userDataProvider?: IInstanceUserDataProvider; } /** @@ -105,12 +130,14 @@ export interface WorkerInstanceConfigurationProps { export class WorkerInstanceConfiguration extends Construct { constructor(scope: Construct, id: string, props: WorkerInstanceConfigurationProps) { super(scope, id); - + props.userDataProvider?.preCloudWatchAgent(props.worker); if (props.cloudwatchLogSettings) { this.configureCloudWatchLogStream(props.worker, id, props.cloudwatchLogSettings); } + props.userDataProvider?.preDeadlineConfiguration(props.worker); props.renderQueue?.configureClientInstance({ host: props.worker}); this.configureWorkerSettings(props.worker, id, props.workerSettings); + props.userDataProvider?.postWorkerLaunch(props.worker); } /** diff --git a/packages/aws-rfdk/lib/deadline/lib/worker-fleet.ts b/packages/aws-rfdk/lib/deadline/lib/worker-fleet.ts index 3d24082db..4ffc8a008 100644 --- a/packages/aws-rfdk/lib/deadline/lib/worker-fleet.ts +++ b/packages/aws-rfdk/lib/deadline/lib/worker-fleet.ts @@ -55,6 +55,7 @@ import { } from './render-queue'; import { Version } from './version'; import { + IInstanceUserDataProvider, WorkerInstanceConfiguration, WorkerSettings, } from './worker-configuration'; @@ -189,6 +190,11 @@ export interface WorkerInstanceFleetProps extends WorkerSettings { * @default The default devices of the provided ami will be used. */ readonly blockDevices?: BlockDevice[]; + + /** + * The provider for User data scripts that will be invoke during worker configuration. + */ + readonly userDataProvider?: IInstanceUserDataProvider; } /** @@ -449,6 +455,7 @@ export class WorkerInstanceFleet extends WorkerInstanceFleetBase { }, renderQueue: props.renderQueue, workerSettings: props, + userDataProvider: props.userDataProvider, }); // Updating the user data with successful cfn-signal commands. diff --git a/packages/aws-rfdk/lib/deadline/test/asset-constants.ts b/packages/aws-rfdk/lib/deadline/test/asset-constants.ts index bbab5acbe..66f1344eb 100644 --- a/packages/aws-rfdk/lib/deadline/test/asset-constants.ts +++ b/packages/aws-rfdk/lib/deadline/test/asset-constants.ts @@ -47,8 +47,3 @@ export const RQ_CONNECTION_ASSET = { Bucket: 'AssetParameters89a29e05a2a88ec4d4a02e847e3c3c9461d0154b326492f4cad655d4ca0bda98S3BucketC22E185C', Key: 'AssetParameters89a29e05a2a88ec4d4a02e847e3c3c9461d0154b326492f4cad655d4ca0bda98S3VersionKey0833D670', }; - -export const VERSION_QUERY_ASSET = { - Bucket: stringLike('AssetParameters*S3Bucket6ABF873D'), - Key: stringLike('AssetParameters*S3VersionKey5A5FE29C'), -}; diff --git a/packages/aws-rfdk/lib/deadline/test/version-query.test.ts b/packages/aws-rfdk/lib/deadline/test/version-query.test.ts index 845a1b71f..3be13f53e 100644 --- a/packages/aws-rfdk/lib/deadline/test/version-query.test.ts +++ b/packages/aws-rfdk/lib/deadline/test/version-query.test.ts @@ -18,8 +18,6 @@ import { VersionQuery, } from '../lib'; -import { VERSION_QUERY_ASSET } from './asset-constants'; - test('VersionQuery constructor full version', () => { const app = new App(); const stack = new Stack(app, 'Stack'); @@ -57,44 +55,6 @@ test('VersionQuery constructor full version', () => { ], })); expectCDK(stack).to(haveResourceLike('AWS::Lambda::Function', { - Code: { - S3Bucket: { - Ref: VERSION_QUERY_ASSET.Bucket, - }, - S3Key: { - 'Fn::Join': [ - '', - [ - { - 'Fn::Select': [ - 0, - { - 'Fn::Split': [ - '||', - { - Ref: VERSION_QUERY_ASSET.Key, - }, - ], - }, - ], - }, - { - 'Fn::Select': [ - 1, - { - 'Fn::Split': [ - '||', - { - Ref: VERSION_QUERY_ASSET.Key, - }, - ], - }, - ], - }, - ], - ], - }, - }, Handler: 'version-provider.handler', Role: { 'Fn::GetAtt': [ diff --git a/packages/aws-rfdk/lib/deadline/test/worker-fleet.test.ts b/packages/aws-rfdk/lib/deadline/test/worker-fleet.test.ts index deb23ccf3..c166bcaa5 100644 --- a/packages/aws-rfdk/lib/deadline/test/worker-fleet.test.ts +++ b/packages/aws-rfdk/lib/deadline/test/worker-fleet.test.ts @@ -44,6 +44,8 @@ import { escapeTokenRegex, } from '../../core/test/token-regex-helpers'; import { + IHost, + InstanceUserDataProvider, IRenderQueue, RenderQueue, Repository, @@ -92,12 +94,18 @@ beforeEach(() => { test('default worker fleet is created correctly', () => { // WHEN + class userDataProvider extends InstanceUserDataProvider{ + preCloudWatchAgent(host: IHost) { + host.userData.addCommands('mkdir test_dir'); + } + } const fleet = new WorkerInstanceFleet(wfstack, 'workerFleet', { vpc, workerMachineImage: new GenericWindowsImage({ 'us-east-1': 'ami-any', }), renderQueue, + userDataProvider: new userDataProvider(), }); // THEN @@ -1461,6 +1469,36 @@ describe('HealthMonitor Tests', () => { TargetType: 'instance', })); }); + + test('UserData is added', () => { + // WHEN + class UserDataProvider extends InstanceUserDataProvider { + preCloudWatchAgent(host: IHost): void { + host.userData.addCommands('echo preCloudWatchAgent'); + } + preDeadlineConfiguration(host: IHost): void { + host.userData.addCommands('echo preDeadlineConfiguration'); + } + postWorkerLaunch(host: IHost): void { + host.userData.addCommands('echo postWorkerLaunch'); + } + } + const fleet = new WorkerInstanceFleet(wfstack, 'workerFleet', { + vpc, + workerMachineImage: new GenericWindowsImage({ + 'us-east-1': 'ami-any', + }), + renderQueue, + healthMonitor, + userDataProvider: new UserDataProvider(), + }); + const userData = fleet.fleet.userData.render(); + + // THEN + expect(userData).toContain('echo preCloudWatchAgent'); + expect(userData).toContain('echo preDeadlineConfiguration'); + expect(userData).toContain('echo postWorkerLaunch'); + }); }); describe('tagging', () => {