

# Connect service-managed fleets to a custom license server
<a name="smf-byol"></a>

You can bring your own license server to use with a Deadline Cloud service-managed fleet. To bring your own license, you can configure a license server using a queue environment in your farm. To configure your license server, you should already have a farm and queue set up.

Service-managed fleet workers run in infrastructure that AWS manages, outside your VPC, so they reach a license server in one of three ways:
+ Through a VPC resource endpoint. Workers connect to a license server in your VPC through VPC Lattice using a private domain name, without a queue environment or port forwarding. For setup steps, see [Connect VPC resources to your SMF with VPC resource endpoints](smf-vpc.md). Set the application's license environment variable to the resource endpoint's domain name the same way as for a server address in [Step 3: Connect a rendering application to an endpoint](cmf-ubl.md).
+ Through Amazon EC2 Systems Manager (SSM) port forwarding. A queue environment forwards license ports from each worker to an Amazon EC2 instance in your account that hosts the license server, or that proxies traffic to it. The rest of this page covers this option.
+ Directly over the internet. Your workers obtain a license from the software vendor's license server using the internet.

With the resource endpoint and port forwarding options, the license server doesn't have to be in the same VPC or account as the resource gateway or proxy instance. The gateway or proxy only needs network access to the server, for example through VPC peering, a transit gateway, or a VPN connection.

With the following instructions, you use SSM to forward ports from a worker instance to your license server or proxy instance. In this example, if your license server is unable to provide a license, Deadline Cloud usage-based licensing is used as a fallback. Remove the sections that don't apply to your pipeline or products for which you don't want to use usage based licensing after exhausting your licenses.

**Topics**
+ [Step 1: Configure the queue environment](#configure-queue-environment)
+ [Step 2: (Optional) License proxy instance setup](#license-proxy)
+ [Step 3: CloudFormation template setup](#byol-cfn-template)

## Step 1: Configure the queue environment
<a name="configure-queue-environment"></a>



You can configure a queue environment in your queue to access your license server. First, ensure that you have an AWS instance configured with license server access using one of the following methods:
+ License server – The instance hosts the license servers directly.
+ License proxy – The instance has network access to the license server, and forwards license server ports to the license server. For details on how to configure a license proxy instance, see [Step 2: (Optional) License proxy instance setup](#license-proxy).

For information about configuring license environment variables, see [Step 3: Connect a rendering application to an endpoint](cmf-ubl.md). For a custom license server setup, the license server address remains localhost instead of the Amazon VPC endpoint.

**To add required permissions to the queue role**

1. From the [Deadline Cloud console](https://console.aws.amazon.com/deadlinecloud/home), choose **Go to Dashboard**.

1. From the dashboard, select the farm, and then the queue you want to configure.

1. From queue details > service role, select the role.

1. Choose **Add permission**, and then choose **Create inline policy**.

1. Select the JSON policy editor, and then copy and paste the following text into the editor.

------
#### [ JSON ]

****  

   ```
   {
       "Version":"2012-10-17",		 	 	 
       "Statement": [
           {
               "Sid": "",
               "Effect": "Allow",
               "Action": [
                   "ssm:StartSession"
               ],
               "Resource": [
                   "arn:aws:ssm:{{us-east-1}}::document/AWS-StartPortForwardingSession",
                   "arn:aws:ec2:{{us-east-1}}:{{111122223333}}:instance/{{instance_id}}"
               ]
           }
       ]
   }
   ```

------

1. Before saving the new policy, replace the following values in the policy text:
   + Replace `region` with the AWS Region where your farm is located
   + Replace `instance_id` with the instance ID for the license server or proxy instance you're using
   + Replace `account_id` with the AWS account number containing your farm

1. Choose **Next**.

1. For the Policy name, enter **LicenseForwarding**.

1. Choose **Create policy** to save your changes and create the policy with the required permissions.

**To add a new queue environment to the queue**

1. From the [Deadline Cloud console](https://console.aws.amazon.com/deadlinecloud/home), choose **Go to Dashboard** if you haven't already.

1. From the dashboard, select the farm, and then the queue you want to configure.

1. Choose **Queue Environments** > **Actions** > **Create new with YAML**.

1. Copy and paste the following text into the YAML script editor.

------
#### [ Windows ]

   ```
   specificationVersion: "environment-2023-09"
   parameterDefinitions:
    - name: LicenseInstanceId
      type: STRING
      description: >
       The Instance ID of the license server/proxy instance
      default: ""
    - name: LicenseInstanceRegion
      type: STRING
      description: >
       The region containing this farm
      default: ""
    - name: LicensePorts
      type: STRING
      description: >
       Comma-separated list of ports to be forwarded to the license server/proxy
       instance. Example: "2701,2702,7075,2703,6101,1715,1716,1717,7054,7055,30304"
      default: "2701,2702,7075,2703,6101,1715,1716,1717,7054,7055,30304"
   environment:
    name: BYOL License Forwarding
    variables:
     example_LICENSE: 2701@localhost
    script:
     actions:
      onEnter:
       command: bash
       args: [ "{{Env.File.Enter}}" ]
      onExit:
       command: bash
       args: [ "{{Env.File.Exit}}" ]
     embeddedFiles:
      - name: Enter
        type: TEXT
        runnable: True
        data: |
         curl "https://s3.amazonaws.com/session-manager-downloads/plugin/latest/windows/SessionManagerPlugin.zip" -o "{{Session.WorkingDirectory}}/ssm-plugin.zip"
         powershell -Command "Expand-Archive -Path '{{Session.WorkingDirectory}}/ssm-plugin.zip' -DestinationPath '{{Session.WorkingDirectory}}/ssm-plugin' -Force; Expand-Archive -Path '{{Session.WorkingDirectory}}/ssm-plugin/package.zip' -DestinationPath '{{Session.WorkingDirectory}}/ssm-plugin/package' -Force"
         conda activate
         python "{{Env.File.StartSession}}" "{{Session.WorkingDirectory}}/ssm-plugin/package/bin/session-manager-plugin.exe"
      - name: Exit
        type: TEXT
        runnable: True
        data: |
         echo Killing SSM Manager Plugin PIDs: $BYOL_SSM_PIDS
         for pid in ${BYOL_SSM_PIDS//,/ }; do kill $pid; done
      - name: StartSession
        type: TEXT
        data: |
         import boto3
         import json
         import subprocess
         import sys
         import os
         import tempfile
   
         instance_id = "{{Param.LicenseInstanceId}}"
         region = "{{Param.LicenseInstanceRegion}}"
         license_ports_list = "{{Param.LicensePorts}}".split(",")
   
         ssm_client = boto3.client("ssm", region_name=region)
         pids = []
   
         for port in license_ports_list:
           session_response = ssm_client.start_session(
             Target=instance_id,
             DocumentName="AWS-StartPortForwardingSession",
             Parameters={"portNumber": [port], "localPortNumber": [port]}
           )
   
           cmd = [
             sys.argv[1],
             json.dumps(session_response),
             region,
             "StartSession",
             "",
             json.dumps({"Target": instance_id}),
             f"https://ssm.{region}.amazonaws.com"
           ]
   
           process = subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
           pids.append(process.pid)
           print(f"SSM Port Forwarding Session started for port {port}")
   
         print(f"openjd_env: BYOL_SSM_PIDS={','.join(str(pid) for pid in pids)}")
   
         # Enabling UBL after the BYOL has run out requires prepending the BYOL configuration to the existing license setup
         # Remove the sections that do not apply to your pipeline, or you do not want to use UBL after exhausting the BYOL licenses.
         # The port numbers used may not match what your license server is serving.
   
         # Arnold
         os.environ["ADSKFLEX_LICENSE_FILE"] = f"2701@localhost;{os.environ.get('ADSKFLEX_LICENSE_FILE', '')}"
         print(f"openjd_env: ADSKFLEX_LICENSE_FILE={os.environ['ADSKFLEX_LICENSE_FILE']}")
   
         # Cinema4D
         os.environ["g_licenseServerRLM"] = f"localhost:7057;{os.environ.get('g_licenseServerRLM', '')}"
         print(f"openjd_env: g_licenseServerRLM={os.environ['g_licenseServerRLM']}")
   
         # Nuke
         os.environ["foundry_LICENSE"] = f"6101@localhost;{os.environ.get('foundry_LICENSE', '')}"
         print(f"openjd_env: foundry_LICENSE={os.environ['foundry_LICENSE']}")
   
         # SideFX
         os.environ["SESI_LMHOST"] = f"localhost:1715;{os.environ.get('SESI_LMHOST', '')}"
         print(f"openjd_env: SESI_LMHOST={os.environ['SESI_LMHOST']}")
   
         # Redshift and Red Giant
         os.environ["redshift_LICENSE"] = f"7054@localhost;7055@localhost;{os.environ.get('redshift_LICENSE', '')}"
         print(f"openjd_env: redshift_LICENSE={os.environ['redshift_LICENSE']}")
   
         # V-Ray doesn't support multiple license servers in a single environment variable
         # See https://documentation.chaos.com/space/LIC5/125050770/Sharing+a+License+Configuration+in+a+Network
         vray_license = os.environ.get('VRAY_AUTH_CLIENT_SETTINGS', '')
         xml_content = """<VRLClient>
           <LicServer>
             <Host>localhost</Host>
             <Port>30304</Port>"""
   
         if vray_license and vray_license.startswith('licset://'):
             server_parts = vray_license.removeprefix('licset://').split(':')
             if len(server_parts) >= 2:
                 xml_content += f"""
             <Host1>{server_parts[0]}</Host1>
             <Port1>{server_parts[1]}</Port1>"""
   
         xml_content += """
             <User></User>
             <Pass></Pass>
           </LicServer>
         </VRLClient>"""
   
         temp_dir = tempfile.gettempdir()
         xml_path = os.path.join(temp_dir, 'vrlclient.xml')
         
         with open(xml_path, 'w') as f:
             f.write(xml_content)
   
         os.environ["VRAY_AUTH_CLIENT_FILE_PATH"] = temp_dir
         print(f"openjd_env: VRAY_AUTH_CLIENT_FILE_PATH={os.environ['VRAY_AUTH_CLIENT_FILE_PATH']}")
   
         # Clear the existing VRAY_AUTH_CLIENT_SETTINGS so only the vrlclient.xml file is used.
         os.environ["VRAY_AUTH_CLIENT_SETTINGS"] = ''
         print(f"openjd_env: VRAY_AUTH_CLIENT_SETTINGS={os.environ['VRAY_AUTH_CLIENT_SETTINGS']}")
   
         # Print out the created xml file's contents
         print(f"V-Ray configuration file: {xml_path}")
         with open(xml_path, 'r') as f:
             print(f"{f.read()}")
   ```

------
#### [ Linux ]

   ```
   specificationVersion: "environment-2023-09"
   parameterDefinitions:
    - name: LicenseInstanceId
      type: STRING
      description: >
       The Instance ID of the license server/proxy instance
      default: ""
    - name: LicenseInstanceRegion
      type: STRING
      description: >
       The region containing this farm
      default: ""
    - name: LicensePorts
      type: STRING
      description: >
       Comma-separated list of ports to be forwarded to the license server/proxy
       instance. Example: "2701,2702,7075,2703,6101,1715,1716,1717,7054,7055,30304"
      default: "2701,2702,7075,2703,6101,1715,1716,1717,7054,7055,30304"
   environment:
    name: BYOL License Forwarding
    variables:
     example_LICENSE: 2701@localhost
    script:
     actions:
      onEnter:
       command: bash
       args: [ "{{Env.File.Enter}}" ]
      onExit:
       command: bash
       args: [ "{{Env.File.Exit}}" ]
     embeddedFiles:
      - name: Enter
        type: TEXT
        runnable: True
        data: |
         curl https://s3.amazonaws.com/session-manager-downloads/plugin/latest/linux_64bit/session-manager-plugin.rpm -Ls | rpm2cpio - | cpio -iv --to-stdout ./usr/local/sessionmanagerplugin/bin/session-manager-plugin > {{Session.WorkingDirectory}}/session-manager-plugin
         chmod +x {{Session.WorkingDirectory}}/session-manager-plugin
         conda activate
         python {{Env.File.StartSession}} {{Session.WorkingDirectory}}/session-manager-plugin
      - name: Exit
        type: TEXT
        runnable: True
        data: |
         echo Killing SSM Manager Plugin PIDs: $BYOL_SSM_PIDS
         for pid in ${BYOL_SSM_PIDS//,/ }; do kill $pid; done
      - name: StartSession
        type: TEXT
        data: |
         import boto3
         import json
         import subprocess
         import sys
         import os
         import tempfile
   
         instance_id = "{{Param.LicenseInstanceId}}"
         region = "{{Param.LicenseInstanceRegion}}"
         license_ports_list = "{{Param.LicensePorts}}".split(",")
   
         ssm_client = boto3.client("ssm", region_name=region)
         pids = []
   
         for port in license_ports_list:
           session_response = ssm_client.start_session(
             Target=instance_id,
             DocumentName="AWS-StartPortForwardingSession",
             Parameters={"portNumber": [port], "localPortNumber": [port]}
           )
   
           cmd = [
             sys.argv[1],
             json.dumps(session_response),
             region,
             "StartSession",
             "",
             json.dumps({"Target": instance_id}),
             f"https://ssm.{region}.amazonaws.com"
           ]
   
           process = subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
           pids.append(process.pid)
           print(f"SSM Port Forwarding Session started for port {port}")
   
         print(f"openjd_env: BYOL_SSM_PIDS={','.join(str(pid) for pid in pids)}")
   
         # Enabling UBL after the BYOL has run out requires prepending the BYOL configuration to the existing license setup
         # Remove the sections that do not apply to your pipeline, or you do not want to use UBL after exhausting the BYOL licenses.
         # The port numbers used may not match what your license server is serving.
   
         # Arnold
         os.environ["ADSKFLEX_LICENSE_FILE"] = f"2701@localhost:{os.environ.get('ADSKFLEX_LICENSE_FILE', '')}"
         print(f"openjd_env: ADSKFLEX_LICENSE_FILE={os.environ['ADSKFLEX_LICENSE_FILE']}")
   
         # Nuke
         os.environ["foundry_LICENSE"] = f"6101@localhost:{os.environ.get('foundry_LICENSE', '')}"
         print(f"openjd_env: foundry_LICENSE={os.environ['foundry_LICENSE']}")
   
         # SideFX
         os.environ["SESI_LMHOST"] = f"localhost:1715;{os.environ.get('SESI_LMHOST', '')}"
         print(f"openjd_env: SESI_LMHOST={os.environ['SESI_LMHOST']}")
   
         # Redshift and Red Giant
         os.environ["redshift_LICENSE"] = f"7054@localhost:7055@localhost:{os.environ.get('redshift_LICENSE', '')}"
         print(f"openjd_env: redshift_LICENSE={os.environ['redshift_LICENSE']}")
   
         # V-Ray doesn't support multiple license servers in a single environment variable
         # See https://documentation.chaos.com/space/LIC5/125050770/Sharing+a+License+Configuration+in+a+Network
         vray_license = os.environ.get('VRAY_AUTH_CLIENT_SETTINGS', '')
         xml_content = """<VRLClient>
           <LicServer>
             <Host>localhost</Host>
             <Port>30304</Port>"""
   
         if vray_license and vray_license.startswith('licset://'):
             server_parts = vray_license.removeprefix('licset://').split(':')
             if len(server_parts) >= 2:
                 xml_content += f"""
             <Host1>{server_parts[0]}</Host1>
             <Port1>{server_parts[1]}</Port1>"""
   
         xml_content += """
             <User></User>
             <Pass></Pass>
           </LicServer>
         </VRLClient>"""
   
         temp_dir = tempfile.gettempdir()
         xml_path = os.path.join(temp_dir, 'vrlclient.xml')
         
         with open(xml_path, 'w') as f:
             f.write(xml_content)
   
         os.environ["VRAY_AUTH_CLIENT_FILE_PATH"] = temp_dir
         print(f"openjd_env: VRAY_AUTH_CLIENT_FILE_PATH={os.environ['VRAY_AUTH_CLIENT_FILE_PATH']}")
   
         # Clear the existing VRAY_AUTH_CLIENT_SETTINGS so only the vrlclient.xml file is used.
         os.environ["VRAY_AUTH_CLIENT_SETTINGS"] = ''
         print(f"openjd_env: VRAY_AUTH_CLIENT_SETTINGS={os.environ['VRAY_AUTH_CLIENT_SETTINGS']}")
   
         # Print out the created xml file's contents
         print(f"V-Ray configuration file: {xml_path}")
         with open(xml_path, 'r') as f:
             print(f"{f.read()}")
   ```

------

1. Before saving the queue environment, make the following changes to the environment text as needed:
   + Update the default values for the following parameters to reflect your environment:
     + **LicenseInstanceID** – The Amazon EC2 instance ID of your license server or proxy instance
     + **LicenseInstanceRegion** – The AWS Region containing your farm
     + **LicensePorts** – A comma-separated list of ports to be forwarded to the license server or proxy instance (for example 2700,2701)
   + If you want to use usage based licensing (UBL) after Bring your own license (BYOL) is exhausted be sure the port is correct for your license server. If you do not want to use UBL after running out of BYOL, add any required licensing environment variables to the variables section.

      These variables should direct the DCCs to localhost on the license server port. For example, if your Foundry license server is listening on port 6101, you would add the variable as **foundry\_LICENSE: 6101@localhost**.

1. (Optional) You can leave **Priority** set to **0**, or you can change it to order the priority differently among multiple queue environments.

1. Choose **Create queue environment** to save the new environment.

   With the queue environment set, jobs submitted to this queue will retrieve licenses from the configured license server.

## Step 2: (Optional) License proxy instance setup
<a name="license-proxy"></a>

As an alternative to using a license server, you can use a license proxy. To create a license proxy, create a new Amazon Linux 2023 instance that has network access to the license server. If needed, you can configure this access using a VPN connection. For more information, see [VPN connections](https://docs.aws.amazon.com/vpc/latest/userguide/vpn-connections.html) in the *Amazon VPC User Guide*.

To set up a license proxy instance for Deadline Cloud, follow the steps in this procedure. Perform the following configuration steps on this new instance to enable forwarding of license traffic to your license server

1. To install the HAProxy package, enter

   ```
   sudo yum install haproxy
   ```

1. Update the listen license-server section of the **/etc/haproxy/haproxy.cfg** configuration file with the following:

   1. Replace **LicensePort1** and **LicensePort2** with the port numbers to be forwarded to the license server. Add or remove comma-separated values to accommodate the required number of ports.

   1. Replace **LicenseServerHost** with the host name or IP address of the license server.

   ```
   global
       log         127.0.0.1 local2
       chroot      /var/lib/haproxy
       user        haproxy
       group       haproxy
       daemon
   
   defaults
       timeout queue           1m
       timeout connect         10s
       timeout client          1m
       timeout server          1m
       timeout http-keep-alive 10s
       timeout check           10s
   
   listen license-server
        bind *:{{LicensePort1}},*:{{LicensePort2}}
        server license-server {{LicenseServerHost}}
   ```

1. To enable and start the HAProxy service, run the following commands:

   ```
   sudo systemctl enable haproxy
   sudo service haproxy start
   ```

After completing the steps, license requests sent to localhost from the forwarding queue environment should be forwarded to the specified license server.

## Step 3: CloudFormation template setup
<a name="byol-cfn-template"></a>

You can use a CloudFormation template to configure an entire farm to use your own licensing.

1. Modify the template provided in the next step to add any required licensing environment variables to the **variables** section under **BYOLQueueEnvironment**.

1. Use the following CloudFormation template.

   ```
   AWSTemplateFormatVersion: 2010-09-09
   Description: "Create Deadline Cloud resources for BYOL"
   
   Parameters:
     LicenseInstanceId:
       Type: AWS::EC2::Instance::Id
       Description: Instance ID for the license server/proxy instance
     LicensePorts:
       Type: String
       Description: Comma-separated list of ports to forward to the license instance
   
   Resources:
     JobAttachmentBucket:
       Type: AWS::S3::Bucket
       Properties:
         BucketName: !Sub byol-example-ja-bucket-${AWS::AccountId}-${AWS::Region}
         BucketEncryption:
           ServerSideEncryptionConfiguration:
             - ServerSideEncryptionByDefault:
                 SSEAlgorithm: AES256
   
     Farm:
       Type: AWS::Deadline::Farm
       Properties:
         DisplayName: BYOLFarm
   
     QueuePolicy:
       Type: AWS::IAM::ManagedPolicy
       Properties:
         ManagedPolicyName: BYOLQueuePolicy
         PolicyDocument:
           Version: 2012-10-17
           Statement:
             - Effect: Allow
               Action:
                 - s3:GetObject
                 - s3:PutObject
                 - s3:ListBucket
                 - s3:GetBucketLocation
               Resource:
                 - !Sub ${JobAttachmentBucket.Arn}
                 - !Sub ${JobAttachmentBucket.Arn}/job-attachments/*
               Condition:
                 StringEquals:
                   aws:ResourceAccount: !Sub ${AWS::AccountId}
             - Effect: Allow
               Action: logs:GetLogEvents
               Resource: !Sub arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/deadline/${Farm.FarmId}/*
             - Effect: Allow
               Action:
                 - s3:ListBucket
                 - s3:GetObject
               Resource:
                 - "*"
               Condition:
                 ArnLike:
                   s3:DataAccessPointArn:
                     - arn:aws:s3:*:*:accesspoint/deadline-software-*
                 StringEquals:
                   s3:AccessPointNetworkOrigin: VPC
     
     BYOLSSMPolicy:
       Type: AWS::IAM::ManagedPolicy
       Properties:
         ManagedPolicyName: BYOLSSMPolicy
         PolicyDocument:
           Version: 2012-10-17
           Statement:
             - Effect: Allow
               Action:
                 - ssm:StartSession
               Resource:
                 - !Sub arn:aws:ssm:${AWS::Region}::document/AWS-StartPortForwardingSession
                 - !Sub arn:aws:ec2:${AWS::Region}:${AWS::AccountId}:instance/${LicenseInstanceId}
   
   
     WorkerPolicy:
       Type: AWS::IAM::ManagedPolicy
       Properties:
         ManagedPolicyName: BYOLWorkerPolicy
         PolicyDocument:
           Version: 2012-10-17
           Statement:
             - Effect: Allow
               Action:
                 - logs:CreateLogStream
               Resource: !Sub arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/deadline/${Farm.FarmId}/*
               Condition:
                 ForAnyValue:StringEquals:
                   aws:CalledVia:
                     - deadline.amazonaws.com
             - Effect: Allow
               Action:
                 - logs:PutLogEvents
                 - logs:GetLogEvents
               Resource: !Sub arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/deadline/${Farm.FarmId}/*
               
   
     QueueRole:
       Type: AWS::IAM::Role
       Properties:
         RoleName: BYOLQueueRole
         ManagedPolicyArns:
           - !Ref QueuePolicy
           - !Ref BYOLSSMPolicy
         AssumeRolePolicyDocument:
           Version: 2012-10-17
           Statement:
             - Effect: Allow
               Action:
                 - sts:AssumeRole
               Principal:
                 Service:
                   - credentials.deadline.amazonaws.com
                   - deadline.amazonaws.com
               Condition:
                 StringEquals:
                   aws:SourceAccount: !Sub ${AWS::AccountId}
                 ArnEquals:
                   aws:SourceArn: !Ref Farm
   
     WorkerRole:
       Type: AWS::IAM::Role
       Properties:
         RoleName: BYOLWorkerRole
         ManagedPolicyArns:
           - arn:aws:iam::aws:policy/AWSDeadlineCloud-FleetWorker
           - !Ref WorkerPolicy
         AssumeRolePolicyDocument:
           Version: 2012-10-17
           Statement:
             - Effect: Allow
               Action:
                 - sts:AssumeRole
               Principal:
                 Service: credentials.deadline.amazonaws.com
   
   
     Queue:
       Type: AWS::Deadline::Queue
       Properties:
         DisplayName: BYOLQueue
         FarmId: !GetAtt Farm.FarmId
         RoleArn: !GetAtt QueueRole.Arn
         JobRunAsUser:
           Posix:
             Group: ""
             User: ""
           RunAs: WORKER_AGENT_USER
         JobAttachmentSettings:
           RootPrefix: job-attachments
           S3BucketName: !Ref JobAttachmentBucket
     
     Fleet:
       Type: AWS::Deadline::Fleet
       Properties:
         DisplayName: BYOLFleet
         FarmId: !GetAtt Farm.FarmId
         MinWorkerCount: 1
         MaxWorkerCount: 2
         Configuration:
           ServiceManagedEc2:
             InstanceCapabilities:
               VCpuCount:
                 Min: 4
                 Max: 16
               MemoryMiB:
                 Min: 4096
                 Max: 16384
               OsFamily: LINUX
               CpuArchitectureType: x86_64
             InstanceMarketOptions:
               Type: on-demand
         RoleArn: !GetAtt WorkerRole.Arn
   
     QFA:
       Type: AWS::Deadline::QueueFleetAssociation
       Properties:
         FarmId: !GetAtt Farm.FarmId
         FleetId: !GetAtt Fleet.FleetId
         QueueId: !GetAtt Queue.QueueId
     
     CondaQueueEnvironment:
       Type: AWS::Deadline::QueueEnvironment
       Properties:
         FarmId: !GetAtt Farm.FarmId
         Priority: 5
         QueueId: !GetAtt Queue.QueueId
         TemplateType: YAML
         Template: |
           specificationVersion: 'environment-2023-09'
           parameterDefinitions:
           - name: CondaPackages
             type: STRING
             description: >
               This is a space-separated list of conda package match specifications to install for the job.
               E.g. "blender=3.6" for a job that renders frames in Blender 3.6.
   
               See https://docs.conda.io/projects/conda/en/latest/user-guide/concepts/pkg-specs.html#package-match-specifications
             default: ""
             userInterface:
               control: LINE_EDIT
               label: Conda Packages
           - name: CondaChannels
             type: STRING
             description: >
               This is a space-separated list of conda channels from which to install packages. Deadline Cloud SMF packages are
               installed from the "deadline-cloud" channel that is configured by Deadline Cloud.
   
               Add "conda-forge" to get packages from the https://conda-forge.org/ community, and "defaults" to get packages
               from Anaconda Inc (make sure your usage complies with https://www.anaconda.com/terms-of-use).
             default: "deadline-cloud"
             userInterface:
               control: LINE_EDIT
               label: Conda Channels
           environment:
             name: Conda
             script:
               actions:
                 onEnter:
                   command: "conda-queue-env-enter"
                   args: ["{{Session.WorkingDirectory}}/.env", "--packages", "{{Param.CondaPackages}}", "--channels", "{{Param.CondaChannels}}"]
                 onExit:
                   command: "conda-queue-env-exit"
     
     BYOLQueueEnvironment:
       Type: AWS::Deadline::QueueEnvironment
       Properties:
         FarmId: !GetAtt Farm.FarmId
         Priority: 10
         QueueId: !GetAtt Queue.QueueId
         TemplateType: YAML
         Template: |
           specificationVersion: "environment-2023-09"
           parameterDefinitions:
           - name: LicenseInstanceId
             type: STRING
             description: >
               The Instance ID of the license server/proxy instance
             default: ""
           - name: LicenseInstanceRegion
             type: STRING
             description: >
               The region containing this farm
             default: ""
           - name: LicensePorts
             type: STRING
             description: >
               Comma-separated list of ports to be forwarded to the license server/proxy
               instance. Example: "2701,2702,7075,2703,6101,1715,1716,1717,7054,7055,30304"
             default: "2701,2702,7075,2703,6101,1715,1716,1717,7054,7055,30304"
           environment:
             name: BYOL License Forwarding
             variables:
               example_LICENSE: 2701@localhost
             script:
               actions:
                 onEnter:
                   command: bash
                   args: [ "{{Env.File.Enter}}" ]
                 onExit:
                   command: bash
                   args: [ "{{Env.File.Exit}}" ]
               embeddedFiles:
               - name: Enter
                 type: TEXT
                 runnable: True
                 data: |
                   curl https://s3.amazonaws.com/session-manager-downloads/plugin/latest/linux_64bit/session-manager-plugin.rpm -Ls | rpm2cpio - | cpio -iv --to-stdout ./usr/local/sessionmanagerplugin/bin/session-manager-plugin > {{Session.WorkingDirectory}}/session-manager-plugin
                   chmod +x {{Session.WorkingDirectory}}/session-manager-plugin
                   conda activate
                   python {{Env.File.StartSession}} {{Session.WorkingDirectory}}/session-manager-plugin
               - name: Exit
                 type: TEXT
                 runnable: True
                 data: |
                   echo Killing SSM Manager Plugin PIDs: $BYOL_SSM_PIDS
                   for pid in ${BYOL_SSM_PIDS//,/ }; do kill $pid; done
               - name: StartSession
                 type: TEXT
                 data: |
                   import boto3
                   import json
                   import subprocess
                   import sys
                   import os
                   import tempfile
   
                   instance_id = "{{Param.LicenseInstanceId}}"
                   region = "{{Param.LicenseInstanceRegion}}"
                   license_ports_list = "{{Param.LicensePorts}}".split(",")
   
                   ssm_client = boto3.client("ssm", region_name=region)
                   pids = []
   
                   for port in license_ports_list:
                     session_response = ssm_client.start_session(
                       Target=instance_id,
                       DocumentName="AWS-StartPortForwardingSession",
                       Parameters={"portNumber": [port], "localPortNumber": [port]}
                     )
   
                     cmd = [
                       sys.argv[1],
                       json.dumps(session_response),
                       region,
                       "StartSession",
                       "",
                       json.dumps({"Target": instance_id}),
                       f"https://ssm.{region}.amazonaws.com"
                     ]
   
                     process = subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
                     pids.append(process.pid)
                     print(f"SSM Port Forwarding Session started for port {port}")
   
                   print(f"openjd_env: BYOL_SSM_PIDS={','.join(str(pid) for pid in pids)}")
   
                   # Enabling UBL after the "bring your own license" (BYOL) has run out requires prepending the BYOL configuration to the existing license setup
                   # Remove the sections that do not apply to your pipeline, or you do not want to use UBL after exhausting the BYOL licenses.
                   # The port numbers used may not match what your license server is serving.
   
                   # Arnold
                   os.environ["ADSKFLEX_LICENSE_FILE"] = f"2701@localhost:{os.environ.get('ADSKFLEX_LICENSE_FILE', '')}"
                   print(f"openjd_env: ADSKFLEX_LICENSE_FILE={os.environ['ADSKFLEX_LICENSE_FILE']}")
   
                   # Nuke
                   os.environ["foundry_LICENSE"] = f"6101@localhost:{os.environ.get('foundry_LICENSE', '')}"
                   print(f"openjd_env: foundry_LICENSE={os.environ['foundry_LICENSE']}")
   
                   # SideFX
                   os.environ["SESI_LMHOST"] = f"localhost:1715;{os.environ.get('SESI_LMHOST', '')}"
                   print(f"openjd_env: SESI_LMHOST={os.environ['SESI_LMHOST']}")
   
                   # Redshift and Red Giant
                   os.environ["redshift_LICENSE"] = f"7054@localhost:7055@localhost:{os.environ.get('redshift_LICENSE', '')}"
                   print(f"openjd_env: redshift_LICENSE={os.environ['redshift_LICENSE']}")
   
                   # V-Ray doesn't support multiple license servers in a single environment variable
                   # See https://documentation.chaos.com/space/LIC5/125050770/Sharing+a+License+Configuration+in+a+Network
                   vray_license = os.environ.get('VRAY_AUTH_CLIENT_SETTINGS', '')
                   xml_content = """<VRLClient>
                     <LicServer>
                       <Host>localhost</Host>
                       <Port>30304</Port>"""
   
                   if vray_license and vray_license.startswith('licset://'):
                       server_parts = vray_license.removeprefix('licset://').split(':')
                       if len(server_parts) >= 2:
                           xml_content += f"""
                       <Host1>{server_parts[0]}</Host1>
                       <Port1>{server_parts[1]}</Port1>"""
   
                   xml_content += """
                       <User></User>
                       <Pass></Pass>
                     </LicServer>
                   </VRLClient>"""
   
                   temp_dir = tempfile.gettempdir()
                   xml_path = os.path.join(temp_dir, 'vrlclient.xml')
                 
                   with open(xml_path, 'w') as f:
                       f.write(xml_content)
   
                   os.environ["VRAY_AUTH_CLIENT_FILE_PATH"] = temp_dir
                   print(f"openjd_env: VRAY_AUTH_CLIENT_FILE_PATH={os.environ['VRAY_AUTH_CLIENT_FILE_PATH']}")
   
                   # Clear the existing VRAY_AUTH_CLIENT_SETTINGS so only the vrlclient.xml file is used.
                   os.environ["VRAY_AUTH_CLIENT_SETTINGS"] = ''
                   print(f"openjd_env: VRAY_AUTH_CLIENT_SETTINGS={os.environ['VRAY_AUTH_CLIENT_SETTINGS']}")
   
                   # Print out the created xml file's contents
                   print(f"V-Ray configuration file: {xml_path}")
                   with open(xml_path, 'r') as f:
                       print(f"{f.read()}")
   ```

1. When deploying the CloudFormation template, provide the following parameters:
   + Update the **LicenseInstanceID** with the Amazon EC2 Instance ID of your license server or proxy instance
   + Update the **LicensePorts** with a comma-separated list of ports to be forwarded to the license server or proxy instance (for example 2700,2701)
   + Add the license environment variables by replacing **example\_LICENSE: 2700@localhost** in the template

1. Deploy the template to setup your farm with bring your own license capability.