UNPKG

aws-rfdk

Version:

Package for core render farm constructs

742 lines 89.5 kB
"use strict"; /** * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */ Object.defineProperty(exports, "__esModule", { value: true }); const aws_cdk_lib_1 = require("aws-cdk-lib"); const assertions_1 = require("aws-cdk-lib/assertions"); const aws_ec2_1 = require("aws-cdk-lib/aws-ec2"); const aws_ecs_1 = require("aws-cdk-lib/aws-ecs"); const aws_iam_1 = require("aws-cdk-lib/aws-iam"); const aws_route53_1 = require("aws-cdk-lib/aws-route53"); const core_1 = require("../../core"); const runtime_info_1 = require("../../core/lib/runtime-info"); const lib_1 = require("../lib"); const spot_event_plugin_fleet_1 = require("../lib/spot-event-plugin-fleet"); const test_helper_1 = require("./test-helper"); describe('ConfigureSpotEventPlugin', () => { let stack; let vpc; let region; let renderQueue; let version; let app; let fleet; let groupName; const workerMachineImage = new aws_ec2_1.GenericWindowsImage({ 'us-east-1': 'ami-any', }); beforeEach(() => { region = 'us-east-1'; app = new aws_cdk_lib_1.App(); stack = new aws_cdk_lib_1.Stack(app, 'stack', { env: { region, }, }); vpc = new aws_ec2_1.Vpc(stack, 'Vpc'); version = new lib_1.VersionQuery(stack, 'Version'); renderQueue = new lib_1.RenderQueue(stack, 'RQ', { vpc, images: { remoteConnectionServer: aws_ecs_1.ContainerImage.fromAsset(__dirname) }, repository: new lib_1.Repository(stack, 'Repository', { vpc, version, secretsManagementSettings: { enabled: false }, }), trafficEncryption: { externalTLS: { enabled: false } }, version, }); groupName = 'group_name1'; fleet = new spot_event_plugin_fleet_1.SpotEventPluginFleet(stack, 'SpotFleet', { vpc, renderQueue: renderQueue, deadlineGroups: [ groupName, ], instanceTypes: [ aws_ec2_1.InstanceType.of(aws_ec2_1.InstanceClass.T2, aws_ec2_1.InstanceSize.SMALL), ], workerMachineImage, maxCapacity: 1, }); }); describe('creates a custom resource', () => { test('with default spot event plugin properties', () => { // WHEN new lib_1.ConfigureSpotEventPlugin(stack, 'ConfigureSpotEventPlugin', { vpc, renderQueue: renderQueue, spotFleets: [ fleet, ], }); // THEN assertions_1.Template.fromStack(stack).hasResourceProperties('Custom::RFDK_ConfigureSpotEventPlugin', assertions_1.Match.objectLike({ spotPluginConfigurations: assertions_1.Match.objectLike({ AWSInstanceStatus: 'Disabled', DeleteInterruptedSlaves: false, DeleteTerminatedSlaves: false, IdleShutdown: 10, Logging: 'Standard', PreJobTaskMode: 'Conservative', Region: aws_cdk_lib_1.Stack.of(renderQueue).region, ResourceTracker: true, StaggerInstances: 50, State: 'Global Enabled', StrictHardCap: false, }), })); }); test('with custom spot event plugin properties', () => { // GIVEN const configuration = { awsInstanceStatus: lib_1.SpotEventPluginDisplayInstanceStatus.EXTRA_INFO_0, deleteEC2SpotInterruptedWorkers: true, deleteSEPTerminatedWorkers: true, idleShutdown: aws_cdk_lib_1.Duration.minutes(20), loggingLevel: lib_1.SpotEventPluginLoggingLevel.VERBOSE, preJobTaskMode: lib_1.SpotEventPluginPreJobTaskMode.NORMAL, region: 'us-west-2', enableResourceTracker: false, maximumInstancesStartedPerCycle: 10, state: lib_1.SpotEventPluginState.DISABLED, strictHardCap: true, }; // WHEN new lib_1.ConfigureSpotEventPlugin(stack, 'ConfigureSpotEventPlugin', { vpc, renderQueue: renderQueue, spotFleets: [ fleet, ], configuration, }); // THEN assertions_1.Template.fromStack(stack).hasResourceProperties('Custom::RFDK_ConfigureSpotEventPlugin', assertions_1.Match.objectLike({ spotPluginConfigurations: assertions_1.Match.objectLike({ AWSInstanceStatus: 'ExtraInfo0', DeleteInterruptedSlaves: true, DeleteTerminatedSlaves: true, IdleShutdown: 20, Logging: 'Verbose', PreJobTaskMode: 'Normal', Region: 'us-west-2', ResourceTracker: false, StaggerInstances: 10, State: 'Disabled', StrictHardCap: true, }), })); }); test('without spot fleets', () => { // WHEN new lib_1.ConfigureSpotEventPlugin(stack, 'ConfigureSpotEventPlugin', { vpc, renderQueue: renderQueue, }); // THEN assertions_1.Template.fromStack(stack).hasResourceProperties('Custom::RFDK_ConfigureSpotEventPlugin', { spotFleetRequestConfigurations: assertions_1.Match.absent(), }); }); test('provides RQ connection parameters to custom resource', () => { // WHEN new lib_1.ConfigureSpotEventPlugin(stack, 'ConfigureSpotEventPlugin', { vpc, renderQueue: renderQueue, spotFleets: [ fleet, ], }); // THEN assertions_1.Template.fromStack(stack).hasResourceProperties('Custom::RFDK_ConfigureSpotEventPlugin', assertions_1.Match.objectLike({ connection: assertions_1.Match.objectLike({ hostname: stack.resolve(renderQueue.endpoint.hostname), port: stack.resolve(renderQueue.endpoint.portAsString()), protocol: stack.resolve(renderQueue.endpoint.applicationProtocol.toString()), }), })); }); test('with default spot fleet request configuration', () => { // WHEN new lib_1.ConfigureSpotEventPlugin(stack, 'ConfigureSpotEventPlugin', { vpc, renderQueue: renderQueue, spotFleets: [ fleet, ], }); const rfdkTag = (0, runtime_info_1.tagFields)(fleet); // THEN assertions_1.Template.fromStack(stack).hasResourceProperties('Custom::RFDK_ConfigureSpotEventPlugin', { spotFleetRequestConfigurations: { [groupName]: { AllocationStrategy: 'lowestPrice', IamFleetRole: stack.resolve(fleet.fleetRole.roleArn), LaunchTemplateConfigs: assertions_1.Match.arrayWith([ assertions_1.Match.objectLike({ LaunchTemplateSpecification: { Version: stack.resolve(fleet.launchTemplate.versionNumber), LaunchTemplateId: stack.resolve(fleet.launchTemplate.launchTemplateId), }, }), ]), TagSpecifications: assertions_1.Match.arrayWith([ assertions_1.Match.objectLike({ ResourceType: 'spot-fleet-request', Tags: assertions_1.Match.arrayWith([ { Key: rfdkTag.name, Value: rfdkTag.value, }, ]), }), ]), }, }, }); }); test('adds policies to the render queue', () => { // WHEN new lib_1.ConfigureSpotEventPlugin(stack, 'ConfigureSpotEventPlugin', { vpc, renderQueue: renderQueue, spotFleets: [ fleet, ], }); // THEN (0, test_helper_1.resourcePropertiesCountIs)(stack, 'AWS::IAM::Role', { ManagedPolicyArns: assertions_1.Match.arrayWith([ stack.resolve(aws_iam_1.ManagedPolicy.fromAwsManagedPolicyName('AWSThinkboxDeadlineSpotEventPluginAdminPolicy').managedPolicyArn), stack.resolve(aws_iam_1.ManagedPolicy.fromAwsManagedPolicyName('AWSThinkboxDeadlineResourceTrackerAdminPolicy').managedPolicyArn), ]), }, 1); assertions_1.Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { Action: 'iam:PassRole', Condition: { StringLike: { 'iam:PassedToService': 'ec2.amazonaws.com', }, }, Effect: 'Allow', Resource: [ stack.resolve(fleet.fleetRole.roleArn), stack.resolve(fleet.fleetInstanceRole.roleArn), ], }, { Action: 'ec2:CreateTags', Effect: 'Allow', Resource: [ 'arn:aws:ec2:*:*:spot-fleet-request/*', 'arn:aws:ec2:*:*:volume/*', ], }, ], }, Roles: [{ Ref: 'RQRCSTaskTaskRole00DC9B43', }], }); }); test('adds resource tracker policy even if rt disabled', () => { // WHEN new lib_1.ConfigureSpotEventPlugin(stack, 'ConfigureSpotEventPlugin', { vpc, renderQueue: renderQueue, spotFleets: [ fleet, ], configuration: { enableResourceTracker: false, }, }); // THEN assertions_1.Template.fromStack(stack).hasResourceProperties('AWS::IAM::Role', { ManagedPolicyArns: assertions_1.Match.arrayWith([ stack.resolve(aws_iam_1.ManagedPolicy.fromAwsManagedPolicyName('AWSThinkboxDeadlineResourceTrackerAdminPolicy').managedPolicyArn), ]), }); }); test.each([ undefined, [], ])('without spot fleet', (noFleets) => { // WHEN new lib_1.ConfigureSpotEventPlugin(stack, 'ConfigureSpotEventPlugin', { vpc, renderQueue: renderQueue, spotFleets: noFleets, }); // THEN assertions_1.Template.fromStack(stack).hasResourceProperties('Custom::RFDK_ConfigureSpotEventPlugin', assertions_1.Match.objectLike({ spotFleetRequestConfigurations: assertions_1.Match.absent(), })); assertions_1.Template.fromStack(stack).hasResourceProperties('AWS::IAM::Role', assertions_1.Match.not({ ManagedPolicyArns: assertions_1.Match.arrayWith([ stack.resolve(aws_iam_1.ManagedPolicy.fromAwsManagedPolicyName('AWSThinkboxDeadlineSpotEventPluginAdminPolicy').managedPolicyArn), stack.resolve(aws_iam_1.ManagedPolicy.fromAwsManagedPolicyName('AWSThinkboxDeadlineResourceTrackerAdminPolicy').managedPolicyArn), ]), })); assertions_1.Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', assertions_1.Match.not({ PolicyDocument: { Statement: [ { Action: 'iam:PassRole', Condition: { StringLike: { 'iam:PassedToService': 'ec2.amazonaws.com', }, }, Effect: 'Allow', Resource: [ stack.resolve(fleet.fleetRole.roleArn), stack.resolve(fleet.fleetInstanceRole.roleArn), ], }, { Action: 'ec2:CreateTags', Effect: 'Allow', Resource: 'arn:aws:ec2:*:*:spot-fleet-request/*', }, ], }, Roles: [{ Ref: 'RQRCSTaskTaskRole00DC9B43', }], })); }); test('fleet with validUntil', () => { // GIVEN const validUntil = aws_cdk_lib_1.Expiration.atDate(new Date(2022, 11, 17)); const fleetWithCustomProps = new spot_event_plugin_fleet_1.SpotEventPluginFleet(stack, 'SpotEventPluginFleet', { vpc, renderQueue, deadlineGroups: [ groupName, ], instanceTypes: [ aws_ec2_1.InstanceType.of(aws_ec2_1.InstanceClass.T3, aws_ec2_1.InstanceSize.LARGE), ], workerMachineImage, maxCapacity: 1, validUntil, }); // WHEN new lib_1.ConfigureSpotEventPlugin(stack, 'ConfigureSpotEventPlugin', { vpc, renderQueue: renderQueue, spotFleets: [ fleetWithCustomProps, ], }); // THEN assertions_1.Template.fromStack(stack).hasResourceProperties('Custom::RFDK_ConfigureSpotEventPlugin', assertions_1.Match.objectLike({ spotFleetRequestConfigurations: assertions_1.Match.objectLike({ [groupName]: assertions_1.Match.objectLike({ ValidUntil: validUntil.date.toISOString(), }), }), })); }); test('fleet with context', () => { // GIVEN const context = 'context-abcdef'; const fleetWithCustomProps = new spot_event_plugin_fleet_1.SpotEventPluginFleet(stack, 'SpotEventPluginFleet', { vpc, renderQueue, deadlineGroups: [ groupName, ], instanceTypes: [ aws_ec2_1.InstanceType.of(aws_ec2_1.InstanceClass.T3, aws_ec2_1.InstanceSize.LARGE), ], workerMachineImage, maxCapacity: 1, context, }); // WHEN new lib_1.ConfigureSpotEventPlugin(stack, 'ConfigureSpotEventPlugin', { vpc, renderQueue: renderQueue, spotFleets: [ fleetWithCustomProps, ], }); // THEN assertions_1.Template.fromStack(stack).hasResourceProperties('Custom::RFDK_ConfigureSpotEventPlugin', assertions_1.Match.objectLike({ spotFleetRequestConfigurations: assertions_1.Match.objectLike({ [groupName]: assertions_1.Match.objectLike({ Context: context, }), }), })); }); }); test('only one object allowed per render queue', () => { // GIVEN new lib_1.ConfigureSpotEventPlugin(stack, 'ConfigureSpotEventPlugin', { vpc, renderQueue: renderQueue, spotFleets: [ fleet, ], }); // WHEN function createConfigureSpotEventPlugin() { new lib_1.ConfigureSpotEventPlugin(stack, 'ConfigureSpotEventPlugin2', { vpc, renderQueue: renderQueue, spotFleets: [ fleet, ], }); } // THEN expect(createConfigureSpotEventPlugin).toThrow(/Only one ConfigureSpotEventPlugin construct is allowed per render queue./); }); test('can create multiple objects with different render queues', () => { // GIVEN const renderQueue2 = new lib_1.RenderQueue(stack, 'RQ2', { vpc, images: { remoteConnectionServer: aws_ecs_1.ContainerImage.fromAsset(__dirname) }, repository: new lib_1.Repository(stack, 'Repository2', { vpc, version, secretsManagementSettings: { enabled: false }, }), trafficEncryption: { externalTLS: { enabled: false } }, version, }); // WHEN new lib_1.ConfigureSpotEventPlugin(stack, 'ConfigureSpotEventPlugin', { vpc, renderQueue: renderQueue, spotFleets: [ fleet, ], }); new lib_1.ConfigureSpotEventPlugin(stack, 'ConfigureSpotEventPlugin2', { vpc, renderQueue: renderQueue2, spotFleets: [ fleet, ], }); // THEN assertions_1.Template.fromStack(stack).resourceCountIs('Custom::RFDK_ConfigureSpotEventPlugin', 2); }); test('throws with not supported render queue', () => { // GIVEN const invalidRenderQueue = {}; // WHEN function createConfigureSpotEventPlugin() { new lib_1.ConfigureSpotEventPlugin(stack, 'ConfigureSpotEventPlugin2', { vpc, renderQueue: invalidRenderQueue, spotFleets: [ fleet, ], }); } // THEN expect(createConfigureSpotEventPlugin).toThrow(/The provided render queue is not an instance of RenderQueue class. Some functionality is not supported./); }); test('tagSpecifications returns undefined if fleet does not have tags', () => { // GIVEN const mockFleet = { tags: { hasTags: jest.fn().mockReturnValue(false), }, }; const mockedFleet = mockFleet; const config = new lib_1.ConfigureSpotEventPlugin(stack, 'ConfigureSpotEventPlugin', { vpc, renderQueue: renderQueue, spotFleets: [ fleet, ], }); // WHEN // eslint-disable-next-line dot-notation const result = stack.resolve(config['tagSpecifications'](mockedFleet, lib_1.SpotFleetResourceType.INSTANCE)); // THEN expect(result).toBeUndefined(); }); describe('with TLS', () => { let renderQueueWithTls; let caCert; beforeEach(() => { const host = 'renderqueue'; const zoneName = 'deadline-test.internal'; caCert = new core_1.X509CertificatePem(stack, 'RootCA', { subject: { cn: 'SampleRootCA', }, }); renderQueueWithTls = new lib_1.RenderQueue(stack, 'RQ with TLS', { vpc, images: { remoteConnectionServer: aws_ecs_1.ContainerImage.fromAsset(__dirname) }, repository: new lib_1.Repository(stack, 'Repository2', { vpc, version, }), version, hostname: { zone: new aws_route53_1.PrivateHostedZone(stack, 'DnsZone', { vpc, zoneName: zoneName, }), hostname: host, }, trafficEncryption: { externalTLS: { rfdkCertificate: new core_1.X509CertificatePem(stack, 'RQCert', { subject: { cn: `${host}.${zoneName}`, }, signingCertificate: caCert, }), }, }, }); }); test('Lambda role can get the ca secret', () => { // WHEN new lib_1.ConfigureSpotEventPlugin(stack, 'ConfigureSpotEventPlugin', { vpc, renderQueue: renderQueueWithTls, spotFleets: [ fleet, ], }); // THEN assertions_1.Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { Action: [ 'secretsmanager:GetSecretValue', 'secretsmanager:DescribeSecret', ], Effect: 'Allow', Resource: stack.resolve(renderQueueWithTls.certChain.secretArn), }, ], }, Roles: [ { Ref: 'ConfigureSpotEventPluginConfiguratorServiceRole341B4735', }, ], }); }); test('creates a custom resource with connection', () => { // WHEN new lib_1.ConfigureSpotEventPlugin(stack, 'ConfigureSpotEventPlugin', { vpc, renderQueue: renderQueueWithTls, spotFleets: [ fleet, ], }); // THEN assertions_1.Template.fromStack(stack).hasResourceProperties('Custom::RFDK_ConfigureSpotEventPlugin', assertions_1.Match.objectLike({ connection: assertions_1.Match.objectLike({ hostname: stack.resolve(renderQueueWithTls.endpoint.hostname), port: stack.resolve(renderQueueWithTls.endpoint.portAsString()), protocol: stack.resolve(renderQueueWithTls.endpoint.applicationProtocol.toString()), caCertificateArn: stack.resolve(renderQueueWithTls.certChain.secretArn), }), })); }); }); test('throws with the same group name', () => { // WHEN function createConfigureSpotEventPlugin() { const duplicateFleet = new spot_event_plugin_fleet_1.SpotEventPluginFleet(stack, 'DuplicateSpotFleet', { vpc, renderQueue, workerMachineImage: fleet.machineImage, instanceTypes: fleet.instanceTypes, maxCapacity: fleet.maxCapacity, deadlineGroups: fleet.deadlineGroups, }); new lib_1.ConfigureSpotEventPlugin(stack, 'ConfigureSpotEventPlugin', { vpc, renderQueue: renderQueue, spotFleets: [ fleet, duplicateFleet, ], }); } // THEN expect(createConfigureSpotEventPlugin).toThrow(`Bad Group Name: ${groupName}. Group names in Spot Fleet Request Configurations should be unique.`); }); test('uses selected subnets', () => { // WHEN new lib_1.ConfigureSpotEventPlugin(stack, 'ConfigureSpotEventPlugin', { vpc, vpcSubnets: { subnets: [vpc.privateSubnets[0]] }, renderQueue: renderQueue, spotFleets: [ fleet, ], }); // THEN assertions_1.Template.fromStack(stack).hasResourceProperties('AWS::Lambda::Function', { Handler: 'configure-spot-event-plugin.configureSEP', VpcConfig: { SubnetIds: [ stack.resolve(vpc.privateSubnets[0].subnetId), ], }, }); }); describe('throws with wrong deadline version', () => { test.each([ ['10.1.9'], ['10.1.10'], ])('%s', (versionString) => { // GIVEN const newStack = new aws_cdk_lib_1.Stack(app, 'NewStack'); version = new lib_1.VersionQuery(newStack, 'OldVersion', { version: versionString, }); renderQueue = new lib_1.RenderQueue(newStack, 'OldRenderQueue', { vpc, images: { remoteConnectionServer: aws_ecs_1.ContainerImage.fromAsset(__dirname) }, repository: new lib_1.Repository(newStack, 'Repository', { vpc, version, secretsManagementSettings: { enabled: false }, }), trafficEncryption: { externalTLS: { enabled: false } }, version, }); // WHEN function createConfigureSpotEventPlugin() { new lib_1.ConfigureSpotEventPlugin(stack, 'ConfigureSpotEventPlugin', { vpc, renderQueue: renderQueue, spotFleets: [ fleet, ], }); } // THEN expect(createConfigureSpotEventPlugin).toThrow(`Minimum supported Deadline version for ConfigureSpotEventPlugin is 10.1.12.0. Received: ${versionString}.`); }); }); test('does not throw with min deadline version', () => { // GIVEN const versionString = '10.1.12'; const newStack = new aws_cdk_lib_1.Stack(app, 'NewStack'); version = new lib_1.VersionQuery(newStack, 'OldVersion', { version: versionString, }); renderQueue = new lib_1.RenderQueue(newStack, 'OldRenderQueue', { vpc, images: { remoteConnectionServer: aws_ecs_1.ContainerImage.fromAsset(__dirname) }, repository: new lib_1.Repository(newStack, 'Repository', { vpc, version, secretsManagementSettings: { enabled: false }, }), trafficEncryption: { externalTLS: { enabled: false } }, version, }); // WHEN function createConfigureSpotEventPlugin() { new lib_1.ConfigureSpotEventPlugin(newStack, 'ConfigureSpotEventPlugin', { vpc, renderQueue: renderQueue, spotFleets: [ fleet, ], }); } // THEN expect(createConfigureSpotEventPlugin).not.toThrow(); }); describe('secrets management enabled', () => { beforeEach(() => { region = 'us-east-1'; app = new aws_cdk_lib_1.App(); stack = new aws_cdk_lib_1.Stack(app, 'stack', { env: { region, }, }); vpc = new aws_ec2_1.Vpc(stack, 'Vpc'); version = new lib_1.VersionQuery(stack, 'Version'); renderQueue = new lib_1.RenderQueue(stack, 'RQ', { vpc, images: { remoteConnectionServer: aws_ecs_1.ContainerImage.fromAsset(__dirname) }, repository: new lib_1.Repository(stack, 'Repository', { vpc, version, }), version, }); groupName = 'group_name1'; }); test('a fleet without vpcSubnets specified => warns about dedicated subnets', () => { // GIVEN fleet = new spot_event_plugin_fleet_1.SpotEventPluginFleet(stack, 'SpotFleet', { vpc, renderQueue: renderQueue, deadlineGroups: [ groupName, ], instanceTypes: [ aws_ec2_1.InstanceType.of(aws_ec2_1.InstanceClass.T2, aws_ec2_1.InstanceSize.SMALL), ], workerMachineImage, maxCapacity: 1, }); // WHEN new lib_1.ConfigureSpotEventPlugin(stack, 'ConfigureSpotEventPlugin', { renderQueue, vpc, spotFleets: [fleet], }); // THEN assertions_1.Annotations.fromStack(stack).hasWarning(`/${fleet.node.path}`, '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'); }); test('a fleet with vpcSubnets specified => does not warn about dedicated subnets', () => { // GIVEN fleet = new spot_event_plugin_fleet_1.SpotEventPluginFleet(stack, 'SpotFleetWithSubnets', { vpc, vpcSubnets: { subnetType: aws_ec2_1.SubnetType.PRIVATE_WITH_EGRESS, }, renderQueue: renderQueue, deadlineGroups: [ groupName, ], instanceTypes: [ aws_ec2_1.InstanceType.of(aws_ec2_1.InstanceClass.T2, aws_ec2_1.InstanceSize.SMALL), ], workerMachineImage, maxCapacity: 1, }); // WHEN new lib_1.ConfigureSpotEventPlugin(stack, 'ConfigureSpotEventPlugin', { renderQueue, vpc, spotFleets: [fleet], }); // THEN assertions_1.Annotations.fromStack(stack).hasNoWarning(`/${fleet.node.path}`, assertions_1.Match.stringLikeRegexp('.*dedicated subnet.*')); }); }); }); //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29uZmlndXJlLXNwb3QtZXZlbnQtcGx1Z2luLnRlc3QuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJjb25maWd1cmUtc3BvdC1ldmVudC1wbHVnaW4udGVzdC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7OztHQUdHOztBQUVILDZDQUtxQjtBQUNyQix1REFJZ0M7QUFDaEMsaURBTzZCO0FBQzdCLGlEQUU2QjtBQUM3QixpREFBb0Q7QUFDcEQseURBQTREO0FBQzVELHFDQUFnRDtBQUNoRCw4REFBd0Q7QUFDeEQsZ0NBYWdCO0FBQ2hCLDRFQUV3QztBQUN4QywrQ0FBMEQ7QUFHMUQsUUFBUSxDQUFDLDBCQUEwQixFQUFFLEdBQUcsRUFBRTtJQUN4QyxJQUFJLEtBQVksQ0FBQztJQUNqQixJQUFJLEdBQVEsQ0FBQztJQUNiLElBQUksTUFBYyxDQUFDO0lBQ25CLElBQUksV0FBeUIsQ0FBQztJQUM5QixJQUFJLE9BQWlCLENBQUM7SUFDdEIsSUFBSSxHQUFRLENBQUM7SUFDYixJQUFJLEtBQTJCLENBQUM7SUFDaEMsSUFBSSxTQUFpQixDQUFDO0lBQ3RCLE1BQU0sa0JBQWtCLEdBQUcsSUFBSSw2QkFBbUIsQ0FBQztRQUNqRCxXQUFXLEVBQUUsU0FBUztLQUN2QixDQUFDLENBQUM7SUFFSCxVQUFVLENBQUMsR0FBRyxFQUFFO1FBQ2QsTUFBTSxHQUFHLFdBQVcsQ0FBQztRQUNyQixHQUFHLEdBQUcsSUFBSSxpQkFBRyxFQUFFLENBQUM7UUFDaEIsS0FBSyxHQUFHLElBQUksbUJBQUssQ0FBQyxHQUFHLEVBQUUsT0FBTyxFQUFFO1lBQzlCLEdBQUcsRUFBRTtnQkFDSCxNQUFNO2FBQ1A7U0FDRixDQUFDLENBQUM7UUFDSCxHQUFHLEdBQUcsSUFBSSxhQUFHLENBQUMsS0FBSyxFQUFFLEtBQUssQ0FBQyxDQUFDO1FBRTVCLE9BQU8sR0FBRyxJQUFJLGtCQUFZLENBQUMsS0FBSyxFQUFFLFNBQVMsQ0FBQyxDQUFDO1FBRTdDLFdBQVcsR0FB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