constructor()

in packages/aws-rfdk/lib/deadline/lib/usage-based-licensing.ts [509:630]


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

    const usageBasedLicenses = new Array();

    props.licenses.forEach(license => {
      usageBasedLicenses.push(`${license.licenseName}:${license.limit ? license.limit : UsageBasedLicense.UNLIMITED}`);
    });

    if (usageBasedLicenses.length < 1) {
      throw new Error('Should be specified at least one license with defined limit.');
    }

    this.cluster = new Cluster(this, 'Cluster', { vpc: props.vpc });

    if (!props.vpcSubnets && props.renderQueue.repository.secretsManagementSettings.enabled) {
      Annotations.of(this).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',
      );
    }

    const vpcSubnets = props.vpcSubnets ?? { subnetType: SubnetType.PRIVATE_WITH_EGRESS };

    this.asg = this.cluster.addCapacity('ASG', {
      vpcSubnets,
      instanceType: props.instanceType ? props.instanceType : InstanceType.of(InstanceClass.C5, InstanceSize.LARGE),
      minCapacity: props.desiredCount ?? 1,
      maxCapacity: props.desiredCount ?? 1,
      blockDevices: [ {
        deviceName: '/dev/xvda',
        volume: BlockDeviceVolume.ebs( 30, {encrypted: true}),
      }],
      // addCapacity doesn't specifically take a securityGroup, but it passes on its properties to the ASG it creates,
      // so this security group will get applied there
      // @ts-ignore
      securityGroup: props.securityGroup,
      machineImage: EcsOptimizedImage.amazonLinux2023(),
    });

    const taskDefinition = new TaskDefinition(this, 'TaskDefinition', {
      compatibility: Compatibility.EC2,
      networkMode: NetworkMode.HOST,
    });

    this.grantPrincipal = taskDefinition.taskRole;

    const containerEnv = {
      UBL_CERTIFICATES_URI: '',
      UBL_LIMITS: usageBasedLicenses.join(';'),
      ...props.renderQueue.configureClientECS({
        hosts: [this.asg],
        grantee: this,
      }),
    };

    containerEnv.UBL_CERTIFICATES_URI = props.certificateSecret.secretArn;
    props.certificateSecret.grantRead(taskDefinition.taskRole);

    const prefix = props.logGroupProps?.logGroupPrefix ?? UsageBasedLicensing.DEFAULT_LOG_GROUP_PREFIX;
    const defaultedLogGroupProps: LogGroupFactoryProps = {
      ...props.logGroupProps,
      logGroupPrefix: prefix,
    };
    const logGroup = LogGroupFactory.createOrFetch(this, 'LogGroupWrapper', id, defaultedLogGroupProps);
    logGroup.grantWrite(this.asg);

    const container = taskDefinition.addContainer('LicenseForwarderContainer', {
      image: props.images.licenseForwarder,
      environment: containerEnv,
      memoryReservationMiB: 1024,
      logging: LogDriver.awsLogs({
        logGroup,
        streamPrefix: 'LicenseForwarder',
      }),
    });

    // Increase ulimits
    container.addUlimits({
      name: UlimitName.NOFILE,
      softLimit: 200000,
      hardLimit: 200000,
    }, {
      name: UlimitName.NPROC,
      softLimit: 64000,
      hardLimit: 64000,
    });

    this.service = new Ec2Service(this, 'Service', {
      cluster: this.cluster,
      taskDefinition,
      desiredCount: props.desiredCount ?? 1,
      placementConstraints: [PlacementConstraint.distinctInstances()],
      // This is required to right-size our host capacity and not have the ECS service block on updates. We set a memory
      // reservation, but no memory limit on the container. This allows the container's memory usage to grow unbounded.
      // We want 1:1 container to container instances to not over-spend, but this comes at the price of down-time during
      // cloudformation updates.
      minHealthyPercent: 0,
      maxHealthyPercent: 100,
    });

    // An explicit dependency is required from the service to the ASG providing its capacity.
    // See: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-attribute-dependson.html
    this.service.node.addDependency(this.asg);

    this.node.defaultChild = this.service;

    if (props.renderQueue.repository.secretsManagementSettings.enabled) {
      props.renderQueue.configureSecretsManagementAutoRegistration({
        dependent: this.service.node.defaultChild as CfnService,
        registrationStatus: SecretsManagementRegistrationStatus.REGISTERED,
        role: SecretsManagementRole.CLIENT,
        vpc: props.vpc,
        vpcSubnets,
      });
    }

    // Grant the render queue the ability to connect to the license forwarder to register workers
    this.asg.connections.allowFrom(props.renderQueue.backendConnections, Port.tcp(UsageBasedLicensing.LF_PORT));

    // Tag deployed resources with RFDK meta-data
    tagConstruct(this);
  }