API v310.64 Documentation Available


The v310.64 API documentation is now available on Tintri’s GitHub site. So what’s new:

  • Four new systemProperty APIs which allows customization of the following system properties:
    • used percent alert low threshold
    • used percent aler high threshold
    • reserves used percent alert threshold
    • use percent disable snapshots threshold
    • use percent disable replication threshold
  • more Datastore statistics
    • snapshot space saving factor
    • space savings factor including snapshot savings

For more explanations on system properties, go to the systemProperites Data Transfer Object (DTO) documentation; and for more information on the new snaphot factors, go to the DatastoreStat DTO documentation.

Currently the PowerShell Toolkit and the Python SDK do not support the system property APIs except in raw API form.  See SysPropertyManger.ps1 for an example. However, the new information in the Datastore DTO is available to the Python SDK and the PowerShell Toolkit.

Happy coding,

– Rick –


Invoke-RestMethod Exception Handling with Tintri APIs


Recently, I’ve been working in PowerShell.  Tintri has a great PowerShell Toolkit, but unfortunately some APIs cannot be invoked via the Toolkit.  So to invoke APIs that are not in the Toolkit, you can use Microsoft’s Invoke-RestMethod.  In this blog, I’m only going to discuss exception handling.

Try {
    $resp = Invoke-RestMethod -Uri $url -Method Delete -WebSession $session -ContentType $JSON_CONTENT
Catch {
    # Obtain some information
    $expMessage = $_.Exception.Message
    $failedItem = $_.Exception.Source
    $line = $_.InvocationInfo.ScriptLineNumber

    # Check if there is a response.
    if ($_.Exception.Response -eq $null) {
        Write-Host "At $($line):`r`n$expMessage" -ForeGroundColor Red
    else {
        # Get the response body with more error detail.
        $respStream = $_.Exception.Response.GetResponseStream()
        $reader = New-Object System.IO.StreamReader($respStream)
        $respBody = $reader.ReadToEnd() | ConvertFrom-Json
        $errorCode = $respBody.code
        $errorMessage = $respBody.message
        $causeDetails = $respBody.causeDetails
       Write-Host "At line $($line):`r`n$expMessage`r`n$($errorCode): $errorMessage`n`r $causeDetails" -ForegroundColor Red

Take the example code above where we invoking a GET API.  The variable $session is the session ID and $url is of course the API URL.

If there is an Invoke-RestMethod() error, the exception is thrown and the Catch block is executed.  When a Tintri API error occurs Invoke-RestMethod does not return any data, and there is more information in the API response.  See the Error Handling section in the API documentation where the error response is documented:

 "message":"Failed to find requested resource of type VirtualMachine with id: 'xxxxddd'",
 "title":"Resource not found"

So how do you get that error information in the response?  Fortunately Chris Wahl shared some great information on his blog.  As you can see, the first three bold lines in the second part of  the Catch block is directly from Chris.  The italicized part is what I added for Tintri errors.

The code now checks Exception.response; therefore, this Catch block can now handle both non-Tintri and Tintri errors.  I changed Write-Error to Write-Host with a red foreground color for simpler output.

The response stream returns a string.  To get the error code and the error message, the split function would need to be used twice within a loop; or just call ConvertFrom-JSON which which gives us a nice dictionary to easily pluck out errorCode, errorMessage, and causeDetails.

Until next time,

– Rick –


VM Scale-out Code Examples

There is a new blog about automating VM scale-out migration recommendations on the corporate Tintri site. Along with that script I wrote another script to set VM affinity for VM scale-out migration rules. These rules allow you to exclude VMs from being considered for migration.

Let’s look at a use case. Suppose there is one Flash VMstore, and 2 hybrid VMstores in a VMstore pool. VDI is running in your Flash VMstore, and you don’t what the VDI VMs to migrate to the hybrid VMstores, therefore, the VDI VMs are put into a Service Group by name pattern. Unfortunately, the script would have to be executed periodically because affinity is set at the VM level.  When a VM is added to the Service Group, the script needs to be executed.

VMs are excluded with --affinity set to ‘never‘.  VMs are specified by a service group, --sg, by a list of VM name, --vms, or by pattern matching with --name. To clear the affinity set --affinity to ‘clear‘.

So let’s look at the script,

# Let's get to work
    if (len(vms) > 0):
        print("Collecting VMs from list")
        vm_uuids += get_vms_in_list(server_name, session_id, vms)

    if (service_group != ""):
        print("Collecting VMs from service group")
        sg_uuid = get_sg_by_name(server_name, session_id, service_group)
        if (sg_uuid == ""):
            raise tintri.TintriRequestsException("Can't find service group " + service_group)

        vm_uuids += get_vms_by_sg(server_name, session_id, sg_uuid)

    if (vm_contains_name != ""):
        print("Collecting VMs from name")
        vm_uuids += get_vms_by_name(server_name, session_id, vm_contains_name)

    if (len(vm_uuids) == 0):
        raise tintri.TintriRequestsException("No VMs to set rules")

    if debug_mode:
        count = 1
        for uuid in vm_uuids:
            print(str(count) + ": " + uuid)
            count += 1

    # Process according to affinity
    if (affinity == "never"):
        set_vm_affinity_never(server_name, session_id, vm_uuids)
    elif (affinity == "clear"):
        clear_vm_affinity(server_name, session_id, vm_uuids)
        raise tintri.TintriRequestsException("Bad affinity rule: " + affinity)

except tintri.TintriRequestsException as tre:
except tintri.TintriApiException as tae:

In the above snippet, the code figures out the list of VMs and processes for the specified affinity.

The crux of this script is how to set the affinity:

# A helper function  that sets the VM affinity rule for migration recommendations.
def set_vm_affinity(server_name, session_id, vm_uuids, affinity_rule):
    url = "/v310/vm/"

    for vm_uuid in vm_uuids:
        rule_url = url + vm_uuid + "/affinity"

        r = tintri.api_put(server_name, rule_url, affinity_rule, session_id)
        if r.status_code != 204:
            tintri.api_logout(server_name, session_id)
            message = "The HTTP response for put affinity rule to the server is not 204."
            raise tintri.TintriApiException(message, r.status_code,
                                            rule_url, str(affinity_rule), r.text)

# Set the VM affinity rule to never for a list of VMs.
def set_vm_affinity_never(server_name, session_id, vm_uuids):
    print("Setting " + str(len(vm_uuids)) + " VMs to never migrate")

    affinity_rule = \
        {"typeId" : beans + "vm.VirtualMachineAffinityRule",
         "ruleType" : "NEVER"

    set_vm_affinity(server_name, session_id, vm_uuids, affinity_rule)

# Clear the VM affinity rule for a list of VMs
def clear_vm_affinity(server_name, session_id, vm_uuids):
    print("Clearing " + str(len(vm_uuids)) + " VMs affinity rules")

    affinity_rule = \
        {"typeId" : beans + "vm.VirtualMachineAffinityRule",

    set_vm_affinity(server_name, session_id, vm_uuids, affinity_rule)

In the function set_vm_affinity(), we see the API invoke /v310/vm/{vm_uuid}/affinity. For each VM UUID in the input list, the API is invoked. There are two helper functions set_vm_affinity() and clear_vm_affinity to facilitate.

Although it has been discussed before, you can review how to get the VMs in a service group with this snippet of code.

# Return a list of VM UUIDs based on a service group name.
def get_vms_by_sg(server_name, session_id, sg_uuid):
    vm_uuids = []
# Get a list of VMs, but return a page size at a time
vm_filter = {"includeFields" : ["uuid", "vmware"],
             "serviceGroupIds" : sg_uuid
vm_uuids = get_vms(server_name, session_id, vm_filter)

return vm_uuids

# Return VM items constrained by a filter.
def get_vms(server_name, session_id, vm_filter):
vm_uuids = []

# Get a list of VMs, but return a page size at a time
get_vm_url = "/v310/vm"
vm_paginated_result = {'next' : "offset=0&limit=" + str(page_size)}

# While there are more VMs, go get them, and build a dictionary
# of name to UUID.
while 'next' in vm_paginated_result:
    url = get_vm_url + "?" + vm_paginated_result['next']

    r = tintri.api_get_query(server_name, url, vm_filter, session_id)
    print_debug("The JSON response of the VM get invoke to the server " +
                server_name + " is: " + r.text)

    # For each VM in the page, print the VM name and UUID.
    vm_paginated_result = r.json()
    print_debug("VMs:\n" + format_json(vm_paginated_result))

    # Get the VMs
    items = vm_paginated_result["items"]
    for vm in items:

return vm_uuids

Before I close this post, I would like to mention that you can post API questions on Tintri’s Hub. Until next time,

– Rick –

GitHub API Python Examples Updated


The API python examples on the Tintri GitHub site have been updated.

The library, has been updated to disable SSL warnings. To facilitate this, a new method, api_version(), has been added to the library. It replaces the /info GET invoke.

All the API python client examples have been updated to use the library and api_version() method. Since api_version() returns the same values as /info, the change is minimal. Here is an example diff:

 - r = tintri.api_get(server_name, '/info')
 + r = tintri.api_version(server_name)
      json_info = r.json()
      product_name = json_info['productName']

A new API client example, is now available. This example shows how to use the new QoS TGC Service Group APIs. With the new APIs, configuring QoS for Service Groups has been simplified. The blog, TGC Service Groups and QoS Redux discusses this new QoS example.


– Rick –

Some API differences with Virtually Aware Flash Storage


Tintri has just announce the T5000 All-Flash series.  This means that customers that desire all-flash storage now can have it all: flash, and virtually aware storage (VAS).  I recently blogged about some API and UI changes.

I wanted to go into a little more detail about the API changes.  The All-Flash code snippet in the blog is from an updated All the python files in the  Tintri API GitHub repo were updated with a more common python location for easy script running.

As for dedupeFactor and cloneDedupeFactor in the DatastoreStat object discussion, I hope the tables below clarify.  The first table shows the responses when invoking the following APIs from a VMstore.

  • GET /v310/datastore
  • GET /v310/datastore/{uuid}/statsHistoric
  • GET /v310/datastore/{uuid}/statsRealtime
  • GET /v310/datastore/{uuid}/statsSummary
Responses from VMstore
Field All-Flash Hybrid (4.0) Hybrid (3.2)
dedupFactor Valid Valid Not Available
cloneDedupFactor 1.0 Valid Valid

As you know, Tintri Global Center (TGC) and VMstore have the share some above API endpoints, but the responses can be different. Here is the table for the TGC responses:

Responses from TGC
Field All-Flash Hybrid (4.0) Hybrid (3.2)
dedupFactor Valid Valid 1.0
cloneDedupFactor 1.0 Valid Valid

As you can see, invoking the APIs via a TGC masks the “Not Available” for a consistent return for all fields.

Until next time,

– Rick –

Orchestration and Tintri APIs


I’d like to point out two orchestration examples that are using Tintri APIs.

The first one is by Nick Colyer on vRrealize orchestration. Nick created vRealize workflows that use Tintri APIs.  The series of posts show workflows that create a Tintri session, obtain a Tintri VM UUID, and perform a Tintri snapshot.

The second by is by a company called Emitrom, which create an orchestrator call Integra.  David LaMotta of Emitrom created a workflow that backs-up a VM to the AWS.  He also did a workflow that restores the VM from the AWS.  These workflows use the Tintri PowerShell Toolkit which is imported into Integra which allows each PowerShell commandlet to be a command in Integra.

I hope you can make use of these examples in your admin environments,

– Rick –

Setting DNS primary at Scale


Another “Ask Rick” was posted on Tintri’s corporate blog.  This post covers setting the VMstore’s primary DNS for 100s of VMstores.  This was done for our IT department.

The main thrust of this article, is how to modify VMstore configuration information with the Appliance API.  The Data Transfer Objects (DTOs) are hierarchical.  Let’s start with the Request DTO, because the Appliance API is PUT with it in the HTTP body.

# Create the Request object with the Appliance DTO.
Request = \
{'typeId': '',
 'objectsWithNewValues': newAppliance,
 'propertiesToBeUpdated': ['dnsConfig']

As you can see, the objectsWithNewValues field is an Appliance object, and the propertiesToBeUpdated is the dnsConfig field in the Appliance DTO since DNS configuration is desired to be changed.

# Create the Appliance object wit the new ApplianceDns DTO.
newAppliance = \
{'typeId': '',
 'dnsConfig': newDnsInfo

Above we see the newAppliance object with the dnsConfig field only.  We don’t need the other Appliance fields since we are not modifying them.  The dnsConfig field contains the new DNS values we want to set.

# Create the ApplianceDns DTO.
newDnsInfo = \
{'typeId': '',
 'dnsPrimary': new_dns_primary,
 'dnsSecondary': new_dns_secondary

Both primary and secondary DNS values need to be present because the API is modifying dnsConfig as a whole.

After the DTOs are created and filled with the appropriate values, the Appliance API is invoked.

APPLIANCE_URL = "/v310/appliance/default"

r = tintri.api_put(server_name, url, Request, session_id)

This template will be useful for setting other values in the Appliance.

Please check out this week’s “Ask Rick” for more details.  As always, you can leave comments and questions here.


– Rick –

QoS and TGC Storage Groups


My latest blog post on “Setting QoS at Scale” was posted on the Tintri corporate site.  This post discusses how to use TGC Service Groups to set storage QoS on multiple VMstores.  TGC Service Groups allow you to organize VMstores according to your needs.  For example, one approach might be for all your VMstores associated with VDI VMs, and another approach could be for VMstores associated with a specific customer.

For more information on storage QoS, check-out these two posts:

You’ll note that Tintri’s QoS satisfies Mr Crump’s three requirements:

  1. storage QoS at the VM level
  2. expressing IOPs the same for each VM independent of block sizes
  3. storage resource visibility

Combining TGC Service groups and setting storage QoS provides an great way to manage your VMstores.

– Rick –

Measuring Storage Usage the Easy Way


I posted a blog entry on the Tintri cooperate blog.  This is a change in the corporate blog style to allow more technical blog posts.  Please feel free to comment.

The blog entry discusses how to collect statistics for chargeback and/or showback.  A new collection statistics code example posted on our Tintri GitHub site is discussed in the blog post as well.

So check it out.,

– Rick –

Delete the Oldest Snapshot on a VMstore

Happy New Year,

For the beginning of the New Year, I have coded an REST API example in Python.  This example queries for the oldest user generated snapshot and removes it.  This example shows how to use a query string with the GET snapshot API invoke, and a DELETE snapshot.  What is cool about this example is that there are functions that encapsulate the requests Python modules calls that hopefully will be reusable.  I have attached the example, and will go through some of the code below.

First off, you will need these Python modules:

import requests
import json
import sys
from datetime import datetime

Let’s look at the login function.  A user name and password is all that is needed over HTTPS.

def api_login(server_name, user_name, password):
    # Payload, header and URL for login call
    headers = {'content-type': 'application/json'}
    payload = {'username': user_name,   # payload needed for post.
               'password': password,
               'typeId': ''}   # 'typeId' is required.
    url_login = 'https://'+ server_name + '/api/v310/session/login'

        # Invoke the login API.
        r =, data=json.dumps(payload),
        headers=headers, verify=False)
    except requests.ConnectionError:
        print_error("Login: API Connection error occurred")
    except requests.HTTPError:
        print_error("Login: HTTP error occurred")
    except requests.Timeout:
        print_error("Login: Request timed out")
        print_error("Login: An unexpected error " + sys.exc_info()[0] +
                    " occurred")

    # if HTTP Response is not 200 then raise an exception
    if r.status_code != 200:
        print_error("The HTTP response for login call to the server " +
                    server_name + " is not 200, but is: " + str(r.status_code))
        print_error("url = " + url_login)
        print_error("payload = " + str(payload))
        print_error("response: " + r.text)

    session_id = r.cookies['JSESSIONID']   # Session ID is return for subsequent invokes.
    return session_id

The info API does not require user name, password, or session ID.

r = api_get(server_name, '/api/info')
json_info = r.json()

The query below requests that the response returns the oldest user generated snapshot.

# Create filter to get the oldest user generated snapshot
q_filter = {'queryType': 'TOP_DOCS_BY_TIME',
'limit': '1', # There can only be one

# Get the oldest user generated snapshot
r = api_get_query(server_name, '/api/v310/snapshot', q_filter, session_id)

This example could be used for the basis of a snapshot delete program.  Additions could be added to the query string, like VM name or before this time.

The code example is here: delete_snapshot.


– Rick –