constructor()

in packages/aws-rfdk/lib/deadline/lib/configure-spot-event-plugin.ts [377:533]


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

    if (ConfigureSpotEventPlugin.uniqueRenderQueues.has(props.renderQueue)) {
      throw new Error('Only one ConfigureSpotEventPlugin construct is allowed per render queue.');
    }
    else {
      ConfigureSpotEventPlugin.uniqueRenderQueues.add(props.renderQueue);
    }

    if (props.renderQueue instanceof RenderQueue) {
      // We do not check the patch version, so it's set to 0.
      const minimumVersion: Version = new Version([10, 1, 12, 0]);

      if (props.renderQueue.version.isLessThan(minimumVersion)) {
        throw new Error(`Minimum supported Deadline version for ${this.constructor.name} is ` +
        `${minimumVersion.versionString}. ` +
        `Received: ${props.renderQueue.version.versionString}.`);
      }

      if (props.spotFleets && props.spotFleets.length !== 0) {
        // Always add Resource Tracker admin policy, even if props.configuration?.enableResourceTracker is false.
        // This improves usability, as customers won't need to add this policy manually, if they
        // enable Resource Tracker later in the Spot Event Plugin configuration (e.g., using Deadline Monitor and not RFDK).
        props.renderQueue.addSEPPolicies(true);

        const fleetRoles = props.spotFleets.map(sf => sf.fleetRole.roleArn);
        const fleetInstanceRoles = props.spotFleets.map(sf => sf.fleetInstanceRole.roleArn);
        new Policy(this, 'SpotEventPluginPolicy', {
          statements: [
            new PolicyStatement({
              actions: [
                'iam:PassRole',
              ],
              resources: [...fleetRoles, ...fleetInstanceRoles],
              conditions: {
                StringLike: {
                  'iam:PassedToService': 'ec2.amazonaws.com',
                },
              },
            }),
            new PolicyStatement({
              actions: [
                'ec2:CreateTags',
              ],
              resources: [
                'arn:aws:ec2:*:*:spot-fleet-request/*',
                'arn:aws:ec2:*:*:volume/*',
              ],
            }),
          ],
          roles: [
            props.renderQueue.grantPrincipal as Role,
          ],
        });
      }
    }
    else {
      throw new Error('The provided render queue is not an instance of RenderQueue class. Some functionality is not supported.');
    }

    const region = Construct.isConstruct(props.renderQueue) ? Stack.of(props.renderQueue).region : Stack.of(this).region;

    const timeoutMins = 15;
    const configurator = new LambdaFunction(this, 'Configurator', {
      vpc: props.vpc,
      vpcSubnets: props.vpcSubnets ?? { subnetType: SubnetType.PRIVATE_WITH_EGRESS },
      description: `Used by a ConfigureSpotEventPlugin ${this.node.addr} to perform configuration of Deadline Spot Event Plugin`,
      code: Code.fromAsset(path.join(__dirname, '..', '..', 'lambdas', 'nodejs'), {
      }),
      environment: {
        DEBUG: 'false',
        LAMBDA_TIMEOUT_MINS: timeoutMins.toString(),
      },
      runtime: Runtime.NODEJS_18_X,
      handler: 'configure-spot-event-plugin.configureSEP',
      timeout: Duration.minutes(timeoutMins),
      logRetention: RetentionDays.ONE_WEEK,
    });

    configurator.connections.allowToDefaultPort(props.renderQueue);
    props.renderQueue.certChain?.grantRead(configurator.grantPrincipal);

    const pluginConfig: PluginSettings = {
      AWSInstanceStatus: props.configuration?.awsInstanceStatus ?? SpotEventPluginDisplayInstanceStatus.DISABLED,
      DeleteInterruptedSlaves: props.configuration?.deleteEC2SpotInterruptedWorkers ?? false,
      DeleteTerminatedSlaves: props.configuration?.deleteSEPTerminatedWorkers ?? false,
      IdleShutdown: props.configuration?.idleShutdown?.toMinutes({integral: true}) ?? 10,
      Logging: props.configuration?.loggingLevel ?? SpotEventPluginLoggingLevel.STANDARD,
      PreJobTaskMode: props.configuration?.preJobTaskMode ?? SpotEventPluginPreJobTaskMode.CONSERVATIVE,
      Region: props.configuration?.region ?? region,
      ResourceTracker: props.configuration?.enableResourceTracker ?? true,
      StaggerInstances: props.configuration?.maximumInstancesStartedPerCycle ?? 50,
      State: props.configuration?.state ?? SpotEventPluginState.GLOBAL_ENABLED,
      StrictHardCap: props.configuration?.strictHardCap ?? false,
    };
    const spotFleetRequestConfigs = this.mergeSpotFleetRequestConfigs(props.spotFleets);

    const deadlineGroups = Array.from(new Set(props.spotFleets?.map(fleet => fleet.deadlineGroups).reduce((p, c) => p.concat(c), [])));
    const deadlinePools = Array.from(new Set(props.spotFleets?.map(fleet => fleet.deadlinePools).reduce((p, c) => p?.concat(c ?? []), [])));
    const properties: SEPConfiguratorResourceProps = {
      connection: {
        hostname: props.renderQueue.endpoint.hostname,
        port: props.renderQueue.endpoint.portAsString(),
        protocol: props.renderQueue.endpoint.applicationProtocol,
        caCertificateArn: props.renderQueue.certChain?.secretArn,
      },
      spotFleetRequestConfigurations: spotFleetRequestConfigs,
      spotPluginConfigurations: pluginConfig,
      deadlineGroups,
      deadlinePools,
    };

    const resource = new CustomResource(this, 'Default', {
      serviceToken: configurator.functionArn,
      resourceType: 'Custom::RFDK_ConfigureSpotEventPlugin',
      properties,
    });

    // Prevents a race during a stack-update.
    resource.node.addDependency(configurator.role!);

    // We need to add this dependency to avoid failures while deleting a Custom Resource:
    // 'Custom Resource failed to stabilize in expected time. If you are using the Python cfn-response module,
    // you may need to update your Lambda function code so that CloudFormation can attach the updated version.'.
    // This happens, because Route Table Associations are deleted before the Custom Resource and we
    // don't get a response from 'doDelete()'.
    // Ideally, we would only want to add dependency on 'internetConnectivityEstablished' as shown below,
    // but it seems that CDK misses dependencies on Route Table Associations in that case:
    // const { internetConnectivityEstablished } = props.vpc.selectSubnets(props.vpcSubnets);
    // resource.node.addDependency(internetConnectivityEstablished);
    resource.node.addDependency(props.vpc);

    // /* istanbul ignore next */
    // Add a dependency on the render queue to ensure that
    // it is running before we try to send requests to it.
    resource.node.addDependency(props.renderQueue);

    if (props.spotFleets && props.renderQueue.repository.secretsManagementSettings.enabled) {
      props.spotFleets.forEach(spotFleet => {
        if (spotFleet.defaultSubnets) {
          Annotations.of(spotFleet).addWarning(
            'Deadline Secrets Management is enabled on the Repository and VPC subnets have not been supplied. Using dedicated subnets is recommended. See https://github.com/aws/aws-rfdk/blobs/release/packages/aws-rfdk/lib/deadline/README.md#using-dedicated-subnets-for-deadline-components',
          );
        }
        props.renderQueue.configureSecretsManagementAutoRegistration({
          dependent: resource,
          role: SecretsManagementRole.CLIENT,
          registrationStatus: SecretsManagementRegistrationStatus.REGISTERED,
          vpc: props.vpc,
          vpcSubnets: spotFleet.subnets,
        });
      });
    }

    this.node.defaultChild = resource;
  }