Skip to content

Commit

Permalink
fix(deadline): User Data execution on Worker Nodes
Browse files Browse the repository at this point in the history
  • Loading branch information
Eugene Kozlov authored and root committed Nov 26, 2020
1 parent 9387f60 commit 64960a1
Show file tree
Hide file tree
Showing 9 changed files with 212 additions and 46 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -43,6 +48,30 @@ class ComputeTierProps(StackProps):
# The bastion host to allow connection to Worker nodes.
bastion: Optional[BastionHostLinux] = None

class UserDataProvider(InstanceUserDataProvider):
def __init__(self, scope: Construct, stack_id: str):
super().__init__(scope, stack_id)
self.test_script=Asset(scope, "SampleAsset",
path=os.path.join(os.getcwd(), "..", "scripts", "configure_worker.sh")
)

def pre_cloud_watch_agent(self, host) -> None:
host.user_data.add_commands("echo preCloudWatchAgent")

def pre_render_queue_configuration(self, host) -> None:
host.user_data.add_commands("echo preRenderQueueConfiguration")

def pre_worker_configuration(self, host) -> None:
host.user_data.add_commands("echo preWorkerConfiguration")

def post_worker_launch(self, host) -> None:
host.user_data.add_commands("echo postWorkerLaunch")
self.test_script.grant_read(host)
local_path = host.user_data.add_s3_download_command(
bucket=self.test_script.bucket,
bucket_key=self.test_script.s3_object_key
)
host.user_data.add_execute_file_command(file_path=local_path)

class ComputeTier(Stack):
"""
Expand Down Expand Up @@ -78,6 +107,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(self, 'UserDataProvider')
)

if props.bastion:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/bin/sh

# This script is only here to illustrate how to add custom user data when using our WorkerInstanceFleet and WorkerInstanceConfiguration constructs
mkdir test_dir
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import {
} from '@aws-cdk/aws-ec2';
import * as cdk from '@aws-cdk/core';
import {
IHost,
InstanceUserDataProvider,
IRenderQueue,
IWorkerFleet,
WorkerInstanceFleet,
Expand All @@ -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}.
Expand Down Expand Up @@ -50,6 +54,36 @@ export interface ComputeTierProps extends cdk.StackProps {
readonly bastion?: BastionHostLinux;
}

class UserDataProvider extends InstanceUserDataProvider {
preCloudWatchAgent(host: IHost): void {
host.userData.addCommands('echo preCloudWatchAgent');
}
preRenderQueueConfiguration(host: IHost): void {
host.userData.addCommands('echo preRenderQueueConfiguration');
}
preWorkerConfiguration(host: IHost): void {
host.userData.addCommands('echo preWorkerConfiguration');
}
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, '..', '..', 'scripts', '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.
Expand Down Expand Up @@ -88,6 +122,7 @@ export class ComputeTier extends cdk.Stack {
workerMachineImage: props.workerMachineImage,
healthMonitor: this.healthMonitor,
keyName: props.keyPairName,
userDataProvider: new UserDataProvider(this, 'UserDataProvider'),
});

if (props.bastion) {
Expand Down
20 changes: 20 additions & 0 deletions packages/aws-rfdk/lib/deadline/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -319,3 +319,23 @@ const workerFleet = new WorkerInstanceFleet(stack, 'WorkerFleet', {
}
});
```

### User data scripts for the Worker configuration

You have possibility to run user data scripts at various points during the Worker configuration lifecycle.

To do this, subclass `InstanceUserDataProvider` and override desired methods:
```ts
class UserDataProvider extends InstanceUserDataProvider {
preCloudWatchAgent(host: IHost): void {
host.userData.addCommands('echo preCloudWatchAgent');
}
}
const fleet = new WorkerInstanceFleet(stack, 'WorkerFleet', {
vpc,
renderQueue,
workerMachineImage: /* ... */,
userDataProvider: new UserDataProvider(stack, 'UserDataProvider'),
});

```
80 changes: 79 additions & 1 deletion packages/aws-rfdk/lib/deadline/lib/worker-configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,67 @@ import {
Version,
} from './version';

/**
* Provider for adding user data scripts
* Methods of this interface will be invoked in WorkerInstanceConfiguration
* on different stages of worker configuration
*/
export interface IInstanceUserDataProvider {
/**
* Method that is invoked before configuring the Cloud Watch Agent.
*/
preCloudWatchAgent(host: IHost): void;

/**
* Method that is invoked before the render queue configuration.
*/
preRenderQueueConfiguration(host: IHost): void;

/**
* Method that is invoked after configuring the connection to the render queue and before configuring the Deadline Worker.
*/
preWorkerConfiguration(host: IHost): void;

/**
* Method that is invoked after all configuration is done and worker started.
*/
postWorkerLaunch(host: IHost): void;
}

/**
* Implementation of {@link IInstanceUserDataProvider}.
* Can be used as sub-class with override the desired methods
* to add custom user data commands for WorkerInstanceFleet or WorkerInstanceConfiguration.
*/
export class InstanceUserDataProvider extends Construct implements IInstanceUserDataProvider{
constructor(scope: Construct, id: string) {
super(scope, id);
}

/**
* @inheritdoc
*/
preCloudWatchAgent(_host: IHost): void {
}

/**
* @inheritdoc
*/
preRenderQueueConfiguration(_host: IHost): void {
}

/**
* @inheritdoc
*/
preWorkerConfiguration(_host: IHost): void {
}

/**
* @inheritdoc
*/
postWorkerLaunch(_host: IHost): void {
}
}
/**
* Configuration settings for Deadline Workers
*/
Expand Down Expand Up @@ -86,6 +147,12 @@ export interface WorkerInstanceConfigurationProps {
* @default The Worker is assigned the default settings as outlined in the WorkerSettings interface.
*/
readonly workerSettings?: WorkerSettings;

/**
* An optional provider of user data commands to be injected at various points during the Worker configuration lifecycle.
* You can provide a subclass of InstanceUserDataProvider with the methods overridden as desired.
*/
readonly userDataProvider?: IInstanceUserDataProvider;
}

/**
Expand All @@ -94,6 +161,14 @@ export interface WorkerInstanceConfigurationProps {
*
* The configuration happens on instance start-up using user data scripting.
*
* This configuration performs the following steps in order:
* 1) Configure Cloud Watch Agent
* 2) Configure Deadline Worker RenderQueue connection
* 3) Configure Deadline Worker settings
*
* A `userDataProvider` can be specified that defines callback functions.
* These callbacks can be used to inject user data commands at different points during the Worker instance configuration.
*
* Security Considerations
* ------------------------
* - The instances configured by this construct will download and run scripts from your CDK bootstrap bucket when that instance
Expand All @@ -105,12 +180,15 @@ 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?.preRenderQueueConfiguration(props.worker);
props.renderQueue?.configureClientInstance({ host: props.worker});
props.userDataProvider?.preWorkerConfiguration(props.worker);
this.configureWorkerSettings(props.worker, id, props.workerSettings);
props.userDataProvider?.postWorkerLaunch(props.worker);
}

/**
Expand Down
8 changes: 8 additions & 0 deletions packages/aws-rfdk/lib/deadline/lib/worker-fleet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ import {
} from './render-queue';
import { Version } from './version';
import {
IInstanceUserDataProvider,
WorkerInstanceConfiguration,
WorkerSettings,
} from './worker-configuration';
Expand Down Expand Up @@ -189,6 +190,12 @@ export interface WorkerInstanceFleetProps extends WorkerSettings {
* @default The default devices of the provided ami will be used.
*/
readonly blockDevices?: BlockDevice[];

/**
* An optional provider of user data commands to be injected at various points during the Worker configuration lifecycle.
* You can provide a subclass of InstanceUserDataProvider with the methods overridden as desired.
*/
readonly userDataProvider?: IInstanceUserDataProvider;
}

/**
Expand Down Expand Up @@ -449,6 +456,7 @@ export class WorkerInstanceFleet extends WorkerInstanceFleetBase {
},
renderQueue: props.renderQueue,
workerSettings: props,
userDataProvider: props.userDataProvider,
});

// Updating the user data with successful cfn-signal commands.
Expand Down
5 changes: 0 additions & 5 deletions packages/aws-rfdk/lib/deadline/test/asset-constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
};
40 changes: 0 additions & 40 deletions packages/aws-rfdk/lib/deadline/test/version-query.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down Expand Up @@ -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': [
Expand Down
Loading

0 comments on commit 64960a1

Please sign in to comment.