Resources

Arrow Image

Blog & News

Arrow Image

Using the Frame Admin API to Schedule Elasticity Settings

Using the Frame Admin API to Schedule Elasticity Settings

Cloud-based infrastructure has empowered IT managers with the ability to provide their users with virtually unlimited on-demand compute and storage resources from datacenters across the world. Successful capacity management is key to balancing the cost versus user experience equation. Running and pre-provisioned machines make for quick connections for users, but also consume infrastructure costs by keeping the cloud meter running.

News & Blog

WRITTEN BY

David Horvath

Senior Solutions Architect

November 24, 2020

TABLE OF CONTENT

Active Capacity Configuration
Active Capacity Configuration

It isn't surprising that some customers are interested in more dynamic schedules and the ability to schedule the provisioning and deprovisioning of production instances to minimize cloud storage costs. These storage costs are incurred even if machines are not in use. Although the cost may seem low on an individual server basis, having hundreds or thousands of provisioned machines can add up quickly.

Fortunately, the Frame platform also provides a REST-based Admin API that can be used to give the administrator more flexibility in managing their cloud infrastructure storage costs. These API functions in combination with a scheduler (in our example we will use the built-in Windows Server Task Scheduler) gives the Frame administrator maximum flexibility and control over an account's elasticity settings. This blog shows you how to set up this capability on a Frame utility server in your own Frame account.

Prerequisites

For this tutorial, we will be running the Frame API calls from a Frame utility server. This is not a requirement for the API to work, but it is a simple way to get up on running quickly. If you don't already have a Frame Windows utility server in one of your Frame accounts, you can deploy one by following our instructions. An Air 4GB instance type should be sufficient for this.

In order to use the Frame Admin API, you will need to gather a few values from your Frame tenant:

  • Client ID and Client Secret: These are the credentials your script will use to call the Frame Admin API. They can be generated by following these instructions. The role you assign should be "Account Administrator" on the Frame Account that you will be dynamically managing capacity on.
Choose the Account Administrator Role
Choose the Account Administrator Role
  • Account ID: The Account ID is the ID of the Frame Account that you will be updating the elasticity settings. You can find that value by going to the Nutanix Frame Admin UI and choosing “Update” on the account that you plan to change the elasticity parameters on.
Choose “Update” after clicking on the kebab on the far right
Choose “Update” after clicking on the kebab on the far right

In your browser's Location bar you will see something like:

https://frame.nutanix.com/frame/account/1f86e290-8cd2-4950-9c5a-9d3f7ed332e7/basic-info

The information between account and basic-info (in this example, 1f86e290-8cd2-4950-9c5a-9d3f7ed332e7) is the Account ID.

  • Pool ID: The last piece of information that you will need is the Pool ID. The easiest way to obtain this value is to call the Frame Admin API. To do this, you will have to execute a PowerShell command on the utility server. To do so, start a session on the utility server and run the following script from the PowerShell prompt. Note: You will have to set the variables to the values you collect above.
#Requires -Version 5

$clnt_id = "<Client ID>"
$clnt_secret = "<Client Secret>"
$acct_id = "<Account ID>"
$api="https://api-gateway-prod.frame.nutanix.com/v1/accounts/"+$acct_id+"/pools"

$timestamp = [Math]::Floor([decimal](Get-Date -UFormat "%s"))
$to_sign = "$($timestamp)$($clnt_id)"
$hmacsha = New-Object System.Security.Cryptography.HMACSHA256
$hmacsha.key = [Text.Encoding]::ASCII.GetBytes($clnt_secret)
$signature = $hmacsha.ComputeHash([Text.Encoding]::ASCII.GetBytes($to_sign))
$signature = [System.BitConverter]::ToString($signature).Replace('-', '').ToLower()
$headers = @{
            'X-Frame-ClientId'  = $clnt_id
            'X-Frame-Timestamp' = $timestamp
            'X-Frame-Signature' = $signature
            'Content-Type'      = 'application/json'
}
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$response = Invoke-RestMethod -Method Get -Uri $api -Headers $headers
Write-Output $response

The response should be a list of the production pools associated to that Frame Account where the entries look like:

id            : d2eb96d7-780e-42cb-a4ed-b7bb7ffa64b3
kind          : production
name          : Air 8GB
instance_type : n1-standard-2-Windows
disk_size     : 50
external_id   : gateway-prod.370588

You will need to find the production pool that you want to set capacity on (in this case, the Air 8GB) and extract the Pool ID value. This will be the Pool ID that you need in your actual PowerShell script.

Server Setup

Once you have the information you need, you can configure your Frame Utility Server to set the capacity dynamically. On the utility server, create a directory to hold your code (e.g., C:\FrameAPI). In that directory, place the following PowerShell script. Note: you will need to replace the variables with the information you gathered above.

SetElasticity.ps1

#Requires -Version 5
<#
  .NOTES
    =======================================================================
     Created on:   10/21/2020
     Organization: Nutanix Frame
     Filename:      SetElasticity.ps1
    =======================================================================
    .DESCRIPTION
    Script to set the max_server setting for a Frame Pool

Client credentials:
Client ID, Client Secret, and Account ID
Pool_ID for the appropriate pool

#>
Param
(
    # the number to set the max servers to.
    [parameter(Mandatory = $true)]
    [int]
    $maxnum
)
$clnt_id = "<Client ID>"
$clnt_secret = "<Client Secret>"
$acct_id = "<Account ID>"
$pool_id = "<Pool ID>"

function Get-FrameAPICall {

    Param
    (
        [parameter(Mandatory = $true)]
        [String]
        $client_id,
        [parameter(Mandatory = $true)]
        [String]
        $client_secret,
        [parameter(Mandatory = $true)]
        [String]
        $api
    )
    try {
        # Create signature
        $timestamp = [Math]::Floor([decimal](Get-Date -UFormat "%s"))
        $to_sign = "$($timestamp)$($client_id)"
        $hmacsha = New-Object System.Security.Cryptography.HMACSHA256
        $hmacsha.key = [Text.Encoding]::ASCII.GetBytes($client_secret)
        $signature = $hmacsha.ComputeHash([Text.Encoding]::ASCII.GetBytes($to_sign))
        $signature = [System.BitConverter]::ToString($signature).Replace('-', '').ToLower()
        # Set HTTP request headers
        $headers = @{
            'X-Frame-ClientId'  = $client_id
            'X-Frame-Timestamp' = $timestamp
            'X-Frame-Signature' = $signature
            'Content-Type'      = 'application/json'
        }
        # Set TLS1.2 (PS uses 1.0 by default) and make HTTP request
        [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
        $response = Invoke-RestMethod -Method Get -Uri $api -Headers $headers
    }
    catch {
        # HTTP 401
        if ($_.Exception.Response.StatusCode -eq "Unauthorized") {
            Write-Error "Check your ClientId and ClientSecret - Unauthorized"
        }
        # HTTP 404
        elseif ($_.Exception.Response.StatusCode -eq "NotFound") {
            Write-Error "Check your ClientID - Not Found"
        }
        # HTTP 403
        elseif ($_.Exception.Response.StatusCode -eq "BadRequest") {
            Write-Error "Check your AccountId - BadRequest"
        }
        # Unhandled exception
        else {
            Write-Error "Unknown error: $($_). For more info contact Frame Support"
        }
    }
    return $response

}
function Post-FrameAPICall {

    Param
    (
        [parameter(Mandatory = $true)]
        [String]
        $client_id,
        [parameter(Mandatory = $true)]
        [String]
        $client_secret,
        [parameter(Mandatory = $true)]
        [PSCustomObject]
        $post_data,
        [parameter(Mandatory = $true)]
        [String]
        $api
    )

    try {
        $post_data = $post_data | ConvertTo-Json

        # Create signature
        $timestamp = [Math]::Floor([decimal](Get-Date -UFormat "%s"))
        $to_sign = "$($timestamp)$($client_id)"
        $hmacsha = New-Object System.Security.Cryptography.HMACSHA256
        $hmacsha.key = [Text.Encoding]::ASCII.GetBytes($client_secret)
        $signature = $hmacsha.ComputeHash([Text.Encoding]::ASCII.GetBytes($to_sign))
        $signature = [System.BitConverter]::ToString($signature).Replace('-', '').ToLower()
        # Set HTTP request headers
        $headers = @{
            'X-Frame-ClientId'  = $client_id
            'X-Frame-Timestamp' = $timestamp
            'X-Frame-Signature' = $signature
            'Content-Type'      = 'application/json'
        }

        # Set TLS1.2 (PS uses 1.0 by default) and make HTTP request
        [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
        $post_response = Invoke-RestMethod -Method Post -Uri $api -Headers $headers -Body $post_data
    }

    catch {
        # HTTP 401
        if ($_.Exception.Response.StatusCode -eq "Unauthorized") {
            Write-Error "Check your ClientId and ClientSecret - Unauthorized"
        }

        # HTTP 404
        elseif ($_.Exception.Response.StatusCode -eq "NotFound") {
            Write-Error "Check your ClientID - Not Found"
        }
        # HTTP 403
        elseif ($_.Exception.Response.StatusCode -eq "BadRequest") {
            Write-Error "Check your AccountId - BadRequest"
        }

        # Unhandled exception
        else {
            Write-Error "Unknown error: $($_). For more info contact Frame Support"
        }
    }
    return $post_response
}


# Get Elasticity Settings
$req_string = "https://api-gateway-prod.frame.nutanix.com/v1/pools/" + $pool_id + "/elasticity_settings"

$res = Get-FrameAPICall -client_id $clnt_id -client_secret $clnt_secret -api $req_string

if ($res.max_servers -ne $maxnum) {
    $currentMax = $res.max_servers
    Write-Output "The Number is $currentMax and it should be $maxnum"
    $res.max_servers = $maxnum
    $res1 = Post-FrameAPICall -client_id $clnt_id -client_secret $clnt_secret -post_data $res -api $req_string
    if ($res1 -ne $null)
        {
            Write-Output "Task created with id: " $res1.id
            While ($res1.stage -ne "done")
            {
                Write-Output "Waiting 30 seconds"
                Start-Sleep -Seconds 30
                $req_string = "https://api-gateway-prod.frame.nutanix.com/v1/accounts/" + $acct_id + "/task/" + $res1.id
                $res1 = Get-FrameAPICall -client_id $clnt_id -client_secret $clnt_secret -api $req_string
            }
            Write-Output "Duration: " $res1.duration_sec
        }
}
else {

    Write-Output "Nothing to do."
}

This script is set to change "max_servers" to the value passed as an argument to the script. The script can be run interactively via the PowerShell shell to confirm the script works properly.

It is easier to run cmd files via task scheduler, so create two files in that same directory (e.g., C:\FrameAPI). These scripts will send their output to a log file by default, so if you create a C:\FrameAPI\Log directory you can collect the logs from those commands.

SetHigh.cmd

powershell -noprofile -executionpolicy bypass -file C:\FrameAPI\SetElasticity.ps1 5 > %date:~7,2%-%date:~4,2%-%date:~10,4%_%time:~0,2%_%time:~3,2%_%time:~6,2%.log

SetLow.cmd

powershell -noprofile -executionpolicy bypass -file C:\FrameAPI\SetElasticity.ps1 0 > %date:~7,2%-%date:~4,2%-%date:~10,4%_%time:~0,2%_%time:~3,2%_%time:~6,2%.log

For this example, the SetHigh.cmd file will set the selected pool's max capacity to 5 and SetLow.cmd will set the selected pool's max capacity to zero. The '>' redirects the output to a log file that will be named with the current date and time. You can check these files periodically to confirm the proper execution of the cmd.

You can also run the cmd files interactively to confirm their operation. Finally, you are not limited to just the two command files, you can create as many command files as you need to adjust the settings on your pool to the correct values.

Scheduling

Now that the pieces are in place, all that is left for you to do is to set up the schedule for when your scripts run. To keep things separate, create a local Windows admin user called "FrameAPI" and make it a local administrator. Now open up "Task Scheduler" and choose "Create Task".

Create Task Dialog Window
Create Task Dialog Window

Give your task a name and then "Change User or Group" to have the task use the Windows user you created and choose "Run whether user is logged in or not".

On the "Triggers" Tab, create a New Trigger. This will allow you to schedule when you want your script to run.

Create Trigger Dialog Window
Create Trigger Dialog Window

Set the schedule, then click on "OK". Now bring up the "Actions" tab and create a new Action.

Browse to the cmd file and "Start" it in the log directory you created.

Create Action Dialog Window
Create Action Dialog Window

Click on "OK". You can look on the Conditions and Settings tabs for other options that you might be interested in, but the defaults should work.

Press "OK" and you should be prompted to enter the credentials for the "FrameAPI" user. Enter those credentials and if accepted, the task will now show up in the Task Scheduler Library.

You can now follow the above process and add another task to reset the max number of servers via the "SetLow.cmd".

You can check back in the Task Scheduler to see if your commands run successfully or check the log files created to confirm task completion. You can also monitor the results on your Analytics - Elasticity chart.

Elasticity Chart showing the &quot;Max Setting&quot; being adjusted
Elasticity Chart showing the "Max Setting" being adjusted

Conclusion

That is it! You have now learned how to use Frame Admin API to increase and decrease the max number of provisioned servers in a production pool to save storage costs. The other capacity settings (min servers and buffer servers) are also a part of the same API call and can be adjusted by relatively simple modifications to the API calls to the main script.

Cloud-based infrastructure has empowered IT managers with the ability to provide their users with virtually unlimited on-demand compute and storage resources from datacenters across the world. Successful capacity management is key to balancing the cost versus user experience equation. Running and pre-provisioned machines make for quick connections for users, but also consume infrastructure costs by keeping the cloud meter running.As a cloud-based service, Frame has always provided the ability to analyze and manage active capacity usage via the Frame Admin dashboard.

About the Author

Dizzion

Dizzion was founded in 2011 with a visionary mission to redefine the way the world works.

In an era of legacy Virtual Desktop Infrastructure (VDI), Dizzion set out to challenge the status quo by making it simple for all customers to transform their workspace experience. By building a powerful automation and services platform on top of the VMware stack, Dizzion delivered virtual desktops as a service before Desktop as a Service (DaaS) even existed.

David Horvath

Senior Solutions Architect

William Wong is the VP of Service Delivery for Dizzion, responsible for service delivery (professional and managed services), solutions architecture, and support. He works actively with customers to transform their business and operations leveraging DaaS in a hybrid and multi-cloud world. Before joining Dizzion as part of the Frame spinout from Nutanix, William was Head of Enterprise Solutions at Frame and following Nutanix's acquisition of Frame in 2018, Director of Solutions Architecture (Frame) at Nutanix. Prior to his work in DaaS, William led the development and adoption of innovative Internet software solutions and services, including Internet-based credit card and check processing and eCommerce platforms. William spent over 30 years at Cancer Commons, NetDeposit, Hewlett-Packard, VeriFone, and multiple Internet, payment, and eCommerce startups in executive management, program management, engineering management, and executive advisory positions. William received his B.S., M.S., and Ph.D. in Electrical Engineering from Stanford University.

More about the author

Subscribe to our newsletter

Register for our newsletter now to unlock the full potential of Dizzion's Resource Library. Don't miss out on the latest industry insights – sign up today!