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..d6f9c03db --- /dev/null +++ b/examples/deadline/All-In-AWS-Infrastructure-Basic/scripts/configure_worker.sh @@ -0,0 +1 @@ +mkdir test_dir \ No newline at end of file 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..e3308149b 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,8 @@ export interface WorkerInstanceConfigurationProps { * @default The Worker is assigned the default settings as outlined in the WorkerSettings interface. */ readonly workerSettings?: WorkerSettings; + + readonly userDataProvider?: IInstanceUserDataProvider; } /** @@ -105,12 +127,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..0bdebc53f 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,8 @@ export interface WorkerInstanceFleetProps extends WorkerSettings { * @default The default devices of the provided ami will be used. */ readonly blockDevices?: BlockDevice[]; + + readonly userDataProvider?: IInstanceUserDataProvider; } /** @@ -449,6 +452,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..64524a2e5 100644 --- a/packages/aws-rfdk/lib/deadline/test/asset-constants.ts +++ b/packages/aws-rfdk/lib/deadline/test/asset-constants.ts @@ -49,6 +49,6 @@ export const RQ_CONNECTION_ASSET = { }; export const VERSION_QUERY_ASSET = { - Bucket: stringLike('AssetParameters*S3Bucket6ABF873D'), - Key: stringLike('AssetParameters*S3VersionKey5A5FE29C'), + Bucket: stringLike('AssetParameters*S3Bucket978C4A04'), + Key: stringLike('AssetParameters*S3VersionKeyFFE15274'), }; 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', () => { diff --git a/yarn.lock b/yarn.lock index 8b446869b..c3764ce54 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10460,6 +10460,17 @@ ts-node@^8.0.2: source-map-support "^0.5.17" yn "3.1.1" +ts-node@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-9.0.0.tgz#e7699d2a110cc8c0d3b831715e417688683460b3" + integrity sha512-/TqB4SnererCDR/vb4S/QvSZvzQMJN8daAslg7MeaiHvD8rDZsSfXmNeNumyZZzMned72Xoq/isQljYSt8Ynfg== + dependencies: + arg "^4.1.0" + diff "^4.0.1" + make-error "^1.1.1" + source-map-support "^0.5.17" + yn "3.1.1" + tsame@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/tsame/-/tsame-2.0.1.tgz#70410ddbefcd29c61e2d68549b3347b0444d613f"