PowerShell and the Nimble Storage REST API

If you read my previous blog article on PowerShell Function to Determine the Installed VSS Providers then you're already aware that I recently migrated one of my customers to a Nimble Storage Area Network.

While Nimble does have a PowerShell module and it's decent, I wanted to see how difficult it is to work with their REST API directly with PowerShell. Their REST API documentation also seems to be decent.

Note: All of the functions shown in this blog article are a proof of concept and should be thoroughly tested before attempting to use them in a production environment.

First of all, you need to connect to the Nimble SAN group so I wrote a function for that:

 1#Requires -Version 4.0 -Module TunableSSLValidator
 2function Connect-MrNSGroup {
 3
 4<#
 5.SYNOPSIS
 6    Connects to a Nimble SAN.
 7
 8.DESCRIPTION
 9    Connect-MrNSGroup is an advanced function that provides the initial connection to a Nimble SAN
10    so that other subsequent commands can be run without having to each authenticate individually.
11    The TunableSSLValidator module is required since Nimble uses an untrusted SSL certificate. It
12    can be found on GitHub: https://github.com/Jaykul/Tunable-SSL-Validator
13
14.PARAMETER Group
15    The DNS name or IP address of the Nimble group.
16
17.PARAMETER Port
18    The port number for the Nimble REST API. The default value is 5392. This parameter is hidden and
19    provided only as a means to easily change the port number if the API ever changes.
20
21.PARAMETER Credential
22    Specifies a user account that has permission to perform this action. Type a user name, such as User01
23     or enter a PSCredential object, such as one generated by the Get-Credential cmdlet. If you type a
24     user name, this function prompts you for a password.
25
26.EXAMPLE
27     Connect-MrNSGroup -Group nimblegroup.yourdns.local -Credential (Get-Credential)
28
29.EXAMPLE
30     Connect-MrNSGroup -Group 192.168.1.50 -Credential (Get-Credential)
31
32.INPUTS
33    None
34
35.OUTPUTS
36    None
37
38.NOTES
39    Author:  Mike F Robbins
40    Website: http://mikefrobbins.com
41    Twitter: @mikefrobbins
42#>
43
44    [CmdletBinding()]
45    param (
46        [Parameter(Mandatory)]
47        [string]$Group,
48
49        [Parameter(DontShow)]
50        [ValidateNotNullOrEmpty()]
51        [int]$Port = 5392,
52
53        [Parameter(Mandatory)]
54        [System.Management.Automation.Credential()]$Credential
55    )
56
57    $Uri = "https://$($Group):$($Port)"
58
59    $Script:tokenData = TunableSSLValidator\Invoke-RestMethod -Uri "$Uri/v1/tokens" -InSecure -Method Post -Body ((@{data = @{username = $Credential.UserName;password = $Credential.GetNetworkCredential().password}}) | ConvertTo-Json)
60    $Script:RestVersion = (TunableSSLValidator\Invoke-RestMethod -Uri "$Uri/versions" -Insecure).data.name
61    $Script:session_token = $tokenData.data.session_token
62    $Script:array = $Group
63}

I'm not a big fan of globally scoping variables unless absolutely necessary, but in this scenario unless you want to authenticate with every command, it does seem to be necessary. Their SAN uses a self-signed SSL certificate and I found that fellow Microsoft MVP Joel Bennett had written a PowerShell module named TunableSSLValidator that adds an Insecure parameter to Invoke-RestMethod which kept me from having to worry about writing the code to handle it myself.

Update: Based on feedback from Joel Bennett, I changed the variable scoping from Global to Script to limit their access to only the module.

While many examples you might find store things like the value for Body parameter in a variable, the password is decrypted and then transmitted using the SSL encryption so I didn't want the plain text password hanging around in a variable for however long I leave my current PowerShell session open so I decided to perform the decryption inline when that line of code is executed.

This next function gets the session tokens for users who are connected to the Nimble SAN group:

 1#Requires -Version 4.0 -Module TunableSSLValidator
 2function Get-MrNSToken {
 3
 4<#
 5.SYNOPSIS
 6    List user session tokens.
 7
 8.DESCRIPTION
 9    Get-MrNSToken is an advanced function that lists user session tokens for the specified Nimble SAN.
10    The TunableSSLValidator module is required since Nimble uses an untrusted SSL certificate. It can
11    be found on GitHub: https://github.com/Jaykul/Tunable-SSL-Validator
12
13.PARAMETER Group
14    The DNS name or IP address of the Nimble group. The default is the group that you've already connected
15    to using the Connect-MrNSGroup function.
16
17.PARAMETER Port
18    The port number for the Nimble REST API. The default value is 5392. This parameter is hidden and
19    provided only as a means to easily change the port number if the API ever changes.
20
21.EXAMPLE
22     Get-NStoken
23
24.INPUTS
25    None
26
27.OUTPUTS
28    MrNS.Token
29
30.NOTES
31    Author:  Mike F Robbins
32    Website: http://mikefrobbins.com
33    Twitter: @mikefrobbins
34#>
35
36    [CmdletBinding()]
37    param (
38        [Parameter(DontShow)]
39        [ValidateNotNullOrEmpty()]
40        [string]$Group = $array,
41
42        [Parameter(DontShow)]
43        [ValidateNotNullOrEmpty()]
44        [int]$Port = 5392
45    )
46
47    $Uri = "https://$($Group):$($Port)"
48
49    $Header = @{'X-Auth-Token' = $session_token}
50
51    $Tokens = TunableSSLValidator\Invoke-RestMethod -Uri "$Uri/v1/tokens" -Method Get -Header $Header -Insecure
52
53    foreach ($Token in $Tokens.data.id){
54
55        $TokenUri = "$Uri/v1/tokens/$Token"
56
57        $Result = (TunableSSLValidator\Invoke-RestMethod -Uri $TokenUri -Method Get -Header $header -Insecure).data
58        $Result.PSTypeNames.Insert(0,'MrNS.Token')
59
60        Write-Output $Result
61    }
62
63}

I decided to go ahead and turn these functions into a module so it would be easier to add both custom formatting and custom types. One thing I've found is most SAN vendors simply give you everything back from the API. That ends up being an overwhelming amount of information so I decided to only return a few key properties in a table by default without having to hard code anything or pipe to format table. That's where the custom formatting comes into play. This .format.ps1xml file also includes custom formatting for the next function we'll look at.

  1<?xml version="1.0" encoding="utf-8" ?>
  2<Configuration>
  3    <ViewDefinitions>
  4        <View>
  5            <Name>MrNS.Token</Name>
  6            <ViewSelectedBy>
  7                <TypeName>MrNS.Token</TypeName>
  8            </ViewSelectedBy>
  9            <TableControl>
 10                <TableHeaders>
 11                     <TableColumnHeader>
 12                        <Width>12</Width>
 13                    </TableColumnHeader>
 14                    <TableColumnHeader>
 15                        <Width>15</Width>
 16                    </TableColumnHeader>
 17                    <TableColumnHeader>
 18                        <Width>35</Width>
 19                    </TableColumnHeader>
 20                    <TableColumnHeader>
 21                        <Width>24</Width>
 22                    </TableColumnHeader>
 23                    <TableColumnHeader>
 24                        <Width>24</Width>
 25                    </TableColumnHeader>
 26                </TableHeaders>
 27                <TableRowEntries>
 28                    <TableRowEntry>
 29                        <TableColumnItems>
 30                            <TableColumnItem>
 31                                <PropertyName>UserName</PropertyName>
 32                            </TableColumnItem>
 33                            <TableColumnItem>
 34                                <PropertyName>Source_IP</PropertyName>
 35                            </TableColumnItem>
 36                            <TableColumnItem>
 37                                <PropertyName>Session_Token</PropertyName>
 38                            </TableColumnItem>
 39                            <TableColumnItem>
 40                                <PropertyName>CreateTime</PropertyName>
 41                            </TableColumnItem>
 42                            <TableColumnItem>
 43                                <PropertyName>ModifiedTime</PropertyName>
 44                            </TableColumnItem>
 45                        </TableColumnItems>
 46                    </TableRowEntry>
 47                 </TableRowEntries>
 48            </TableControl>
 49        </View>
 50        <View>
 51            <Name>MrNS.Volume</Name>
 52            <ViewSelectedBy>
 53                <TypeName>MrNS.Volume</TypeName>
 54            </ViewSelectedBy>
 55            <TableControl>
 56                <TableHeaders>
 57                     <TableColumnHeader>
 58                        <Width>25</Width>
 59                    </TableColumnHeader>
 60                    <TableColumnHeader>
 61                        <Width>10</Width>
 62                    </TableColumnHeader>
 63                    <TableColumnHeader>
 64                        <Width>10</Width>
 65                    </TableColumnHeader>
 66                    <TableColumnHeader>
 67                        <Width>26</Width>
 68                    </TableColumnHeader>
 69                    <TableColumnHeader>
 70                        <Width>18</Width>
 71                    </TableColumnHeader>
 72                    <TableColumnHeader>
 73                        <Width>10</Width>
 74                    </TableColumnHeader>
 75                </TableHeaders>
 76                <TableRowEntries>
 77                    <TableRowEntry>
 78                        <TableColumnItems>
 79                            <TableColumnItem>
 80                                <PropertyName>Name</PropertyName>
 81                            </TableColumnItem>
 82                            <TableColumnItem>
 83                                <PropertyName>Size</PropertyName>
 84                            </TableColumnItem>
 85                            <TableColumnItem>
 86                                <PropertyName>vol_state</PropertyName>
 87                            </TableColumnItem>
 88                            <TableColumnItem>
 89                                <PropertyName>perfpolicy_name</PropertyName>
 90                            </TableColumnItem>
 91                            <TableColumnItem>
 92                                <PropertyName>thinly_provisioned</PropertyName>
 93                            </TableColumnItem>
 94                            <TableColumnItem>
 95                                <PropertyName>block_size</PropertyName>
 96                            </TableColumnItem>
 97                        </TableColumnItems>
 98                    </TableRowEntry>
 99                 </TableRowEntries>
100            </TableControl>
101        </View>
102    </ViewDefinitions>
103</Configuration>

You could also add custom formatting so piping to Format-List would only return specific properties unless all properties were explicitly requested either with Format-Table -Property * or Select-Object -Property *. An example of how to write custom formatting for list views can be found in my blog article about My Solution: August 2015 PowerShell Scripting Games Puzzle.

I also added a custom types file which adds a human readable date for the session token creation and modified time stamps. The value that the API returns is the number of seconds since January 1, 1970. The .types.ps1xml file simply adds a couple of script properties without having to manually add them to the function itself which seemed to be a cleaner solution.

 1<Types>
 2    <Type>
 3      <Name>MrNS.Token</Name>
 4      <Members>
 5        <ScriptProperty>
 6          <Name>CreateTime</Name>
 7          <GetScriptBlock>
 8            (Get-Date -Date '1/1/1970').AddSeconds($this.creation_time)
 9          </GetScriptBlock>
10        </ScriptProperty>
11      </Members>
12    </Type>
13    <Type>
14      <Name>MrNS.Token</Name>
15      <Members>
16        <ScriptProperty>
17          <Name>ModifiedTime</Name>
18          <GetScriptBlock>
19            (Get-Date -Date '1/1/1970').AddSeconds($this.last_modified)
20          </GetScriptBlock>
21        </ScriptProperty>
22      </Members>
23    </Type>
24</Types>

To me, the really cool function is the one I wrote to retrieve a list of volumes:

 1#Requires -Version 4.0 -Module TunableSSLValidator
 2function Get-MrNSVolume {
 3
 4<#
 5.SYNOPSIS
 6    Retrieves information for volumes on a Nimble SAN.
 7
 8.DESCRIPTION
 9    Get-MrNSVolume is an advanced function that retrieves information for volumes on a Nimble SAN.
10    The TunableSSLValidator module is required since Nimble uses an untrusted SSL certificate. It can
11    be found on GitHub: https://github.com/Jaykul/Tunable-SSL-Validator
12
13.PARAMETER Group
14    The DNS name or IP address of the Nimble group. The default is the group that you've already connected
15    to using the Connect-MrNSGroup function.
16
17.PARAMETER Port
18    The port number for the Nimble REST API. The default value is 5392. This parameter is hidden and
19    provided only as a means to easily change the port number if the API ever changes.
20
21.PARAMETER Name
22    Name of the volume.
23
24.PARAMETER Id
25    Identifier for the volume.
26
27.EXAMPLE
28     Get-MrNSVolume -Name Volume001
29
30.EXAMPLE
31     Get-MrNSVolume -Id '07204756105a0139c1000000000000000000000009'
32
33.INPUTS
34    None
35
36.OUTPUTS
37    MrNS.Volume
38
39.NOTES
40    Author:  Mike F Robbins
41    Website: http://mikefrobbins.com
42    Twitter: @mikefrobbins
43#>
44
45    [CmdletBinding(DefaultParameterSetName='Name')]
46    param (
47        [Parameter(DontShow)]
48        [ValidateNotNullOrEmpty()]
49        [string]$Group = $array,
50
51        [Parameter(DontShow)]
52        [ValidateNotNullOrEmpty()]
53        [int]$Port = 5392,
54
55        [Parameter(ParameterSetName='Name')]
56        [string]$Name,
57
58        [Parameter(ParameterSetName='Id')]
59        [string]$Id
60    )
61
62    $Uri = "https://$($Group):$($Port)"
63
64    $Params = @{
65        Header = @{'X-Auth-Token' = $session_token}
66        Method = 'Get'
67        Insecure = $true
68    }
69
70    if ($PSBoundParameters.Name){
71
72        $Params.Uri = "$Uri/v1/volumes?name=$Name"
73
74    }
75    elseif ($PSBoundParameters.Id) {
76
77        $Params.Uri = "$Uri/v1/volumes?id=$Id"
78    }
79    else {
80
81        $Params.Uri = "$Uri/v1/volumes"
82
83    }
84
85    $Volumes = (TunableSSLValidator\Invoke-RestMethod @Params).data
86
87    foreach ($Volume in $Volumes.id){
88
89        $Params.Uri = "$Uri/v1/volumes/$Volume"
90
91        $Result = (TunableSSLValidator\Invoke-RestMethod @Params).data
92        $Result.PSTypeNames.Insert(0,'MrNS.Volume')
93
94        Write-Output $Result
95
96    }
97
98}

Do you see how splatting is used to dynamically build the commands and how it's used to reduce the amount of redundancy and minimize the amount of code you have to write?

The functions shown in this blog article are part of my MrNS PowerShell module which can be downloaded from my Nimble repository on GitHub.

µ