Skip to content

Commit

Permalink
feat(deadline): Adds WorkerConfiguration construct
Browse files Browse the repository at this point in the history
This is a construct that provides helper methods for configuring a Deadline Worker
in the RFDK context.
  • Loading branch information
Daniel Neilson committed Nov 4, 2020
1 parent 0c1b37c commit 0e036b4
Show file tree
Hide file tree
Showing 11 changed files with 915 additions and 133 deletions.
5 changes: 5 additions & 0 deletions packages/aws-rfdk/lib/core/lib/script-assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ export interface ConventionalScriptPathParams {
*/
function getConventionalScriptPath(params: ConventionalScriptPathParams): string {
const { rootDir: scriptDir, baseName: scriptName, osType } = params;
// Make sure we have a known osType. The error message is pretty obtuse if we don't:
// The "path" argument must be of type string. Received undefined
if (ScriptPathPrefix[osType] === undefined || ScriptExtension[osType] == undefined) {
throw Error(`Unknown osType: ${osType}`);
}
return path.join(
scriptDir,
ScriptPathPrefix[osType],
Expand Down
1 change: 1 addition & 0 deletions packages/aws-rfdk/lib/deadline/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ export * from './stage';
export * from './thinkbox-docker-recipes';
export * from './version';
export * from './version-ref';
export * from './worker-configuration';
162 changes: 162 additions & 0 deletions packages/aws-rfdk/lib/deadline/lib/worker-configuration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
/**
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

import * as path from 'path';

import {
OperatingSystemType,
} from '@aws-cdk/aws-ec2';
import {
IGrantable,
} from '@aws-cdk/aws-iam';
import {
Construct,
Duration,
} from '@aws-cdk/core';
import {
CloudWatchAgent,
CloudWatchConfigBuilder,
IScriptHost,
LogGroupFactory,
LogGroupFactoryProps,
ScriptAsset,
} from '../../core';
import { Version } from './version';

/**
* Interface for Deadline clients that can be configured via the ClientConfiguration
* helper class.
*/
export interface IConfigurableWorker extends IScriptHost, IGrantable {
}

/**
* The Deadline Worker settings that can be configured via the configureWorkerSettings method
* of the WorkerConfiguration helper class.
*/
export interface WorkerSettings {
/**
* Deadline groups these workers needs to be assigned to. The group is
* created if it does not already exist.
*
* @default - Worker is not assigned to any group
*/
readonly groups?: string[];

/**
* Deadline pools these workers needs to be assigned to. The pool is created
* if it does not already exist.
*
* @default - Worker is not assigned to any pool.
*/
readonly pools?: string[];

/**
* Deadline region these workers needs to be assigned to.
*
* @default - Worker is not assigned to any region
*/
readonly region?: string;
}

/**
* This is a helper construct for configuring Deadline Workers to connect to a RenderQueue, send their
* log files to CloudWatch, and similar common actions.
*
* Security Considerations
* ------------------------
* - The instances configured by this construct will download and run scripts from your CDK bootstrap bucket when that instance
* is launched. You must limit write access to your CDK bootstrap bucket to prevent an attacker from modifying the actions
* performed by these scripts. We strongly recommend that you either enable Amazon S3 server access logging on your CDK
* bootstrap bucket, or enable AWS CloudTrail on your account to assist in post-incident analysis of compromised production
* environments.
*/
export class WorkerConfiguration extends Construct {
constructor(scope: Construct, id: string) {
super(scope, id);
}

/**
* This method can be used to configure a Deadline Worker instance to stream its logs to the AWS CloudWatch
* service. The logs that this configures to stream are:
* - EC2 Instance UserData execution; this is the startup scripting that is run when the instance launches
* for the first time.
* - Deadline Worker logs.
* - Deadline Launcher logs.
*
* @param worker The worker to configure. This can be an instance, auto scaling group, launch template, etc.
* @param id Identifier to disambiguate the resources that are created.
* @param logGroupProps Configuration for the log group in CloudWatch.
*/
public configureCloudWatchLogStream(worker: IConfigurableWorker, id: string, logGroupProps?: LogGroupFactoryProps): void {
const logGroup = LogGroupFactory.createOrFetch(this, `${id}LogGroupWrapper`, `${id}`, logGroupProps);

logGroup.grantWrite(worker);

const cloudWatchConfigurationBuilder = new CloudWatchConfigBuilder(Duration.seconds(15));

if (worker.osType === OperatingSystemType.WINDOWS) {
cloudWatchConfigurationBuilder.addLogsCollectList(logGroup.logGroupName,
'UserdataExecution',
'C:\\ProgramData\\Amazon\\EC2-Windows\\Launch\\Log\\UserdataExecution.log');
cloudWatchConfigurationBuilder.addLogsCollectList(logGroup.logGroupName,
'WorkerLogs',
'C:\\ProgramData\\Thinkbox\\Deadline10\\logs\\deadlineslave*.log');
cloudWatchConfigurationBuilder.addLogsCollectList(logGroup.logGroupName,
'LauncherLogs',
'C:\\ProgramData\\Thinkbox\\Deadline10\\logs\\deadlinelauncher*.log');
} else {
cloudWatchConfigurationBuilder.addLogsCollectList(logGroup.logGroupName,
'cloud-init-output',
'/var/log/cloud-init-output.log');
cloudWatchConfigurationBuilder.addLogsCollectList(logGroup.logGroupName,
'WorkerLogs',
'/var/log/Thinkbox/Deadline10/deadlineslave*.log');
cloudWatchConfigurationBuilder.addLogsCollectList(logGroup.logGroupName,
'LauncherLogs',
'/var/log/Thinkbox/Deadline10/deadlinelauncher*.log');
}

new CloudWatchAgent(this, `${id}WorkerFleetLogsConfig`, {
cloudWatchConfig: cloudWatchConfigurationBuilder.generateCloudWatchConfiguration(),
host: worker,
});
}

/**
* This method can be used to set up the Deadline Worker application on an EC2 instance. From a practical
* perspective, this is executing the script found in aws-rfdk/lib/deadline/scripts/[bash,powershell]/configureWorker.[sh,ps1]
* to configure the Deadline Worker application.
*
* @param worker The worker to configure. This can be an instance, auto scaling group, launch template, etc.
* @param id Identifier to disambiguate the resources that are created.
* @param settings The Deadline Worker settings to apply.
*/
public configureWorkerSettings(worker: IConfigurableWorker, id: string, settings?: WorkerSettings): void {
const configureWorkerScriptAsset = ScriptAsset.fromPathConvention(this, `${id}WorkerConfigurationScript`, {
osType: worker.osType,
baseName: 'configureWorker',
rootDir: path.join(
__dirname,
'..',
'scripts/',
),
});

// Converting to lower case, as groups and pools are all stored in lower case in deadline.
const groups = settings?.groups?.map(val => val.toLowerCase()).join(',') ?? ''; // props.groups ? props.groups.map(val => val.toLowerCase()).join(',') : '';
const pools = settings?.pools?.map(val => val.toLowerCase()).join(',') ?? ''; // props.pools ? props.pools.map(val => val.toLowerCase()).join(',') : '';

configureWorkerScriptAsset.executeOn({
host: worker,
args: [
`'${groups}'`,
`'${pools}'`,
`'${settings?.region ?? ''}'`,
`'${Version.MINIMUM_SUPPORTED_DEADLINE_VERSION.toString()}'`,
],
});
}
}
136 changes: 34 additions & 102 deletions packages/aws-rfdk/lib/deadline/lib/worker-fleet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,10 @@ import {
Stack,
} from '@aws-cdk/core';
import {
CloudWatchAgent,
CloudWatchConfigBuilder,
HealthCheckConfig,
HealthMonitor,
IHealthMonitor,
IMonitorableFleet,
LogGroupFactory,
LogGroupFactoryProps,
ScriptAsset,
} from '../../core';
Expand All @@ -57,6 +54,10 @@ import {
IRenderQueue,
} from './render-queue';
import { Version } from './version';
import {
WorkerConfiguration,
WorkerSettings,
} from './worker-configuration';

/**
* Interface for Deadline Worker Fleet.
Expand All @@ -67,7 +68,7 @@ export interface IWorkerFleet extends IResource, IConnectable, IGrantable {
/**
* Properties for the Deadline Worker Fleet.
*/
export interface WorkerInstanceFleetProps {
export interface WorkerInstanceFleetProps extends WorkerSettings {
/**
* VPC to launch the worker fleet in.
*/
Expand Down Expand Up @@ -150,29 +151,6 @@ export interface WorkerInstanceFleetProps {
*/
readonly renderQueue: IRenderQueue;

/**
* Deadline groups these workers needs to be assigned to. The group is
* created if it does not already exist.
*
* @default - Worker is not assigned to any group
*/
readonly groups?: string[];

/**
* Deadline pools these workers needs to be assigned to. The pool is created
* if it does not already exist.
*
* @default - Worker is not assigned to any pool.
*/
readonly pools?: string[];

/**
* Deadline region these workers needs to be assigned to.
*
* @default - Worker is not assigned to any region
*/
readonly region?: string;

/**
* Properties for setting up the Deadline Worker's LogGroup
* @default - LogGroup will be created with all properties' default values and a prefix of "/renderfarm/".
Expand Down Expand Up @@ -458,25 +436,23 @@ export class WorkerInstanceFleet extends WorkerInstanceFleetBase {
this.grantPrincipal = this.fleet.grantPrincipal;
this.connections = this.fleet.connections;

this.connections.allowToDefaultPort(props.renderQueue);

let healthCheckPort = HealthMonitor.DEFAULT_HEALTH_CHECK_PORT;
if (props.healthCheckConfig && props.healthCheckConfig.port) {
healthCheckPort = props.healthCheckConfig.port;
}

// Configure the health monitoring if provided
this.configureHealthMonitor(props, healthCheckPort);
this.configureHealthMonitor(props);

const workerConfig = new WorkerConfiguration(this, 'Config');

// Updating the user data with installation logs stream.
this.configureCloudWatchLogStream(this.fleet, id, props.logGroupProps);
workerConfig.configureCloudWatchLogStream(this.fleet, id, {
logGroupPrefix: WorkerInstanceFleet.DEFAULT_LOG_GROUP_PREFIX,
...props.logGroupProps,
});

props.renderQueue.configureClientInstance({
host: this.fleet,
});

// Updating the user data with deadline repository installation commands.
this.configureWorkerScript(this.fleet, props, healthCheckPort);
workerConfig.configureWorkerSettings(this.fleet, id, props);

// Updating the user data with successful cfn-signal commands.
this.fleet.userData.addSignalOnExitCommand(this.fleet);
Expand All @@ -494,70 +470,6 @@ export class WorkerInstanceFleet extends WorkerInstanceFleetBase {
this.fleet.addSecurityGroup(securityGroup);
}

private configureCloudWatchLogStream(fleetInstance: AutoScalingGroup, id: string, logGroupProps?: LogGroupFactoryProps) {
const prefix = logGroupProps?.logGroupPrefix ?? WorkerInstanceFleet.DEFAULT_LOG_GROUP_PREFIX;
const defaultedLogGroupProps = {
...logGroupProps,
logGroupPrefix: prefix,
};
const logGroup = LogGroupFactory.createOrFetch(this, `${id}LogGroupWrapper`, `${id}`, defaultedLogGroupProps);

logGroup.grantWrite(fleetInstance);

const cloudWatchConfigurationBuilder = new CloudWatchConfigBuilder(Duration.seconds(15));

cloudWatchConfigurationBuilder.addLogsCollectList(logGroup.logGroupName,
'UserdataExecution',
'C:\\ProgramData\\Amazon\\EC2-Windows\\Launch\\Log\\UserdataExecution.log');
cloudWatchConfigurationBuilder.addLogsCollectList(logGroup.logGroupName,
'WorkerLogs',
'C:\\ProgramData\\Thinkbox\\Deadline10\\logs\\deadlineslave*.log');
cloudWatchConfigurationBuilder.addLogsCollectList(logGroup.logGroupName,
'LauncherLogs',
'C:\\ProgramData\\Thinkbox\\Deadline10\\logs\\deadlinelauncher*.log');
cloudWatchConfigurationBuilder.addLogsCollectList(logGroup.logGroupName,
'cloud-init-output',
'/var/log/cloud-init-output.log');
cloudWatchConfigurationBuilder.addLogsCollectList(logGroup.logGroupName,
'WorkerLogs',
'/var/log/Thinkbox/Deadline10/deadlineslave*.log');
cloudWatchConfigurationBuilder.addLogsCollectList(logGroup.logGroupName,
'LauncherLogs',
'/var/log/Thinkbox/Deadline10/deadlinelauncher*.log');

new CloudWatchAgent(this, 'WorkerFleetLogsConfig', {
cloudWatchConfig: cloudWatchConfigurationBuilder.generateCloudWatchConfiguration(),
host: fleetInstance,
});
}

private configureWorkerScript(fleetInstance: AutoScalingGroup, props: WorkerInstanceFleetProps, healthCheckPort: number) {
const configureWorkerScriptAsset = ScriptAsset.fromPathConvention(this, 'WorkerConfigurationScript', {
osType: fleetInstance.osType,
baseName: 'configureWorker',
rootDir: path.join(
__dirname,
'..',
'scripts/',
),
});

// Converting to lower case, as groups and pools are all stored in lower case in deadline.
const groups = props.groups ? props.groups.map(val => val.toLowerCase()).join(',') : '';
const pools = props.pools ? props.pools.map(val => val.toLowerCase()).join(',') : '';

configureWorkerScriptAsset.executeOn({
host: fleetInstance,
args: [
`'${healthCheckPort}'`,
`'${groups}'`,
`'${pools}'`,
`'${props.region || ''}'`,
`'${Version.MINIMUM_SUPPORTED_DEADLINE_VERSION.toString()}'`,
],
});
}

private validateProps(props: WorkerInstanceFleetProps) {
this.validateSpotPrice(props.spotPrice);
this.validateArrayGroupsPoolsSyntax(props.groups, /^(?!none$)[a-zA-Z0-9-_]+$/i, 'groups');
Expand Down Expand Up @@ -609,8 +521,28 @@ export class WorkerInstanceFleet extends WorkerInstanceFleetBase {
}
}

private configureHealthMonitor(props: WorkerInstanceFleetProps, healthCheckPort: number) {
private configureHealthMonitor(props: WorkerInstanceFleetProps) {
if (props.healthMonitor) {
const healthCheckPort = props.healthCheckConfig?.port ?? HealthMonitor.DEFAULT_HEALTH_CHECK_PORT;

const configureHealthMonitorScriptAsset = ScriptAsset.fromPathConvention(this, 'WorkerConfigurationScript', {
osType: this.fleet.osType,
baseName: 'configureWorkerHealthCheck',
rootDir: path.join(
__dirname,
'..',
'scripts/',
),
});

configureHealthMonitorScriptAsset.executeOn({
host: this.fleet,
args: [
`'${healthCheckPort}'`,
`'${Version.MINIMUM_SUPPORTED_DEADLINE_VERSION.toString()}'`,
],
});

props.healthMonitor.registerFleet(this, props.healthCheckConfig || {
port: healthCheckPort,
});
Expand Down
Loading

0 comments on commit 0e036b4

Please sign in to comment.