Loading

The Futures so bright, gotta wear shades.

Sometimes to move forward you need to look back, and I mean WAYYYY back. All legacy management platforms own their existence to SNMP, and really to be manageable, the first protocol you commonly support is SNMP. This lets your product be integrated directly with even the most ancient platform.

SSSSSSNMP. Oh Yeah

As such, and as an example, the Nimble Storage array supports publishing performance information directly via SNMP. This information is not currently available via RestAPI. So as an experiment I decided to write a extension to the toolkit to expand its coverage not only to the entire RestAPI but also to expose all of the SNMP gather-able material.

What you need to know to gather SNMP data, you need to roadmap in this case called the MIB (Maganagement Information Base). By default the SNMP exists as a single name space, where everything can be mananged.

In this case, we need to follow the tree down to get to a Nimble Specific location; We traverse as follows and note that the words are optional. (1)iso.(3)org.(6)dod.(1)internet.(4)private.(1)enterprise.(37447)nimble

Now the nimble specific paths for the volume specific counters are as follows; (1)nimbleVariables.(2)volTable.(y)featureNumber.(x)volNumber

Now the nimble specific paths for the global counters are as follows; (1)nimbleVariables.(2)volTable.(y)featureNumber.(0)value

There exists in the MIB file (downloadable from Infosight) a list of the featurenames. i.e. featureNumber 3 is the “volume name”, while featureNumber 23 may be volIoSeqReads.

So if I make a request to the following SNMP location (called an OID); 1.3.6.1.4.1.37447.1.2.3.41 it would return the name associated with the 41st volume on the array. If on the other hand if I make a call to the OID 1.3.6.1.4.1.37447.1.3.2.0 it will return the global ioReads on the Nimble array.

So the first thing I learned from the MIB is that there exist up to 55 Volume based feature names, and an additional 16 Global based feature names. As you know when we return the data, we want to match up the data to the hash table and use the proper feature names with the proper values.

The following function returns the correct featurename for each.

function Get-NSPrivateSNMPFeatureLookup
{ Param(
    $FeatureNum,
    $GlobalLocal
)
$VReturn=''
if ($GlobalLocal -like '2')
    {   Switch($FeatureNum)
        {   2   {   $VReturn="volID"          }
            3   {   $VReturn="volName"        } 
            4   {   $VReturn="volSizeLow"     }
            5   {   $VReturn="volSizeHigh"    }
            6   {   $VReturn="volUsageLow"    }
            7   {   $VReturn="volUsageHigh"   }
            8   {   $VReturn="volReserveLow"  }
            9   {   $VReturn="volReserveHigh" }
            10  {   $VReturn="volOnline" }
            11  {   $VReturn="volNumConnections" }
            12  {   $VReturn="volStatTimeEpochSeconds" }
            13  {   $VReturn="volIoReads" }
            14  {   $VReturn="volIoReadTimeMicrosec" }
            15  {   $VReturn="volIoReadBytes" }
            16  {   $VReturn="volIoSeqReads" }
            17  {   $VReturn="volIoSeqReadBytes" }
            18  {   $VReturn="volIoNonseqReadTotalHits" }
            19  {   $VReturn="volIoNonseqReadMemHits" }
            20  {   $VReturn="volIoNonseqReadSSDHits" }
            21  {   $VReturn="volIoReadLatency0uTo100u" }
            22  {   $VReturn="volIoReadLatency100uTo200u" }
            23  {   $VReturn="volIoReadLatency200uTo500u" }
            24  {   $VReturn="volIoReadLatency500uTo1m" }
            25  {   $VReturn="volIoReadLatency1mTo2m" }
            26  {   $VReturn="volIoReadLatency2mTo5m" }
            27  {   $VReturn="volIoReadLatency5mTo10m" }
            28  {   $VReturn="volIoReadLatency10mTo20m" }
            29  {   $VReturn="volIoReadLatency20mTo50m" }
            30  {   $VReturn="volIoReadLatency50mTo100m" }
            31  {   $VReturn="volIoReadLatency100mTo200m" }
            32  {   $VReturn="volIoReadLatency200mTo500m" }
            33  {   $VReturn="volIoReadLatency500mTomax" }
            34  {   $VReturn="volIoWrites" }
            35  {   $VReturn="volIoWriteTimeMicrosec" }
            36  {   $VReturn="volIoWriteBytes" }
            37  {   $VReturn="volIoSeqWrites" }
            38  {   $VReturn="volIoSeqWriteBytes" }
            39  {   $VReturn="volIoWriteLatency0uTo100u" }
            40  {   $VReturn="volIoWriteLatency100uTo200u" }
            41  {   $VReturn="volIoWriteLatency200uTo500u" }
            42  {   $VReturn="volIoWriteLatency500uTo1m" }
            43  {   $VReturn="volIoWriteLatency1mTo2m" }
            44  {   $VReturn="volIoWriteLatency2mTo5m" }
            45  {   $VReturn="volIoWriteLatency5mTo10m" }
            46  {   $VReturn="volIoWriteLatency10mTo20m" }
            47  {   $VReturn="volIoWriteLatency20mTo50m" }
            48  {   $VReturn="volIoWriteLatency50mTo100m" }
            49  {   $VReturn="volIoWriteLatency100mTo200m" }
            50  {   $VReturn="volIoWriteLatency200mTo500m" }
            51  {   $VReturn="volIoWriteLatency500mTomax" }
            52  {   $VReturn="volDiskVolBytesUsedLow" }
            53  {   $VReturn="volDiskVolBytesUsedHigh" }
            54  {   $VReturn="volDiskSnapBytesUsedLow" }
            55  {   $VReturn="volDiskSnapBytesUsedHigh" }
        }
    } else 
    {   Switch($FeatureNum)
        {   1    { $VReturn="statTimeEpochSeconds"}
            2    { $VReturn="ioReads"}
            3    { $VReturn="ioSeqReads"}
            4    { $VReturn="ioWrites"}
            5    { $VReturn="ioSeqWrites"}
            6    { $VReturn="ioReadTimeMicrosec"}
            7    { $VReturn="ioWriteTimeMicrosec"}
            8    { $VReturn="ioReadBytes"}
            9    { $VReturn="ioSeqReadBytes"}
            10   { $VReturn="ioWriteBytes"}
            11   { $VReturn="ioSeqWriteBytes"}
            12   { $VReturn="diskVolBytesUsedLow"}
            13   { $VReturn="diskVolBytesUsedHigh"}
            14   { $VReturn="diskSnapBytesUsedLow"}
            15   { $VReturn="diskSnapBytesUsedHigh"}
            16   { $VReturn="ioNonseqReadHits"}
        }
    }
return $VReturn
}

Now to make code that actually querys the SNMP protocol…well, that is an ugly problem, and a problem that has already been solved much better by other people. In this case, I tried to make it self-supporting by using the Microsoft iSNMP code which it built into the printer subsystem called oleprn. located here; https://docs.microsoft.com/en-us/windows-hardware/drivers/print/isnmp-methods

The problem with the existing Microsoft code is that it doesn’t support many data types, and in our case, doesn’t return the SNMP standard xCounter64 data type. I however found that someone has written a SNMP module on the PSGallery that works reliably, and I tied my code into this dependency. The other option was to use a tool such as SNMPWALK which is an executable, but there exist other problems with bundling something like that with a toolkit.

The low hanging fruit is to bring in the Global settings since I know that there will be exactly 16, and the address is well known. The values such as the IP Adress to the Array can be deduced directly from the variables set by the connect-nsgroup command. The version of the protocol is well known, and the community string (needed to retrieve the data), and the SNMP Port can be queried from the array directly.

function Get-NSSnmpGlobalValue
{   if ( -not (Get-Module -listavailable snmp) )
    {   throw "The Required SNMP Module is not available. Please install the SNMP module from the PowerShell gallery at the following location HTTPS://www.powershellgallery/packages/SNMP"    
    } else
    {   $GLOBAL:SNMPArrayIPAddress = (($BaseURI -split "//")[1] -split ":")[0] # this pulls out the IP Address
        $SNMPPort = (Get-NSGroup).snmp_get_port
        $GLOBAL:CommunityString = (get-NSGroup).snmp_community
        $GlobalRootOID = ".1.3.6.1.4.1.37447.1.3."
        $StartVal = 1
        $ParentArray = @{}
        while ( $StartVal -le 16 )
        {   $TempOID = $GlobalRootOID + $StartVal + ".0"
            $OIDValue = (Get-SNMPdata -ip $SNMPArrayIPAddress -oid $TempOid -Community $CommunityString -version V2 -timeout 3000).data
            $FeatureName = Get-NSPrivateSNMPFeatureLookup $StartVal 3
            $ParentArray+=[ordered]@{$FeatureName = $OIDValue}
            $StartVal=$StartVal+1
        }
        $PSObjGlobal = [PSCustomObject]$ParentArray
        Return $PSObjGlobal
    }
}

Now for the more complex function, I need to query the names or each volume, and I only know that I have encountered the last volume when the query result doesn’t exist. I can create a hash table for each volume with all of its results, Each of these hash tables (representing a single volume) exists as a nested value for an overarching array collection. In this way the results are given to the user as a collection of volumes and each volume is a hash table of many values.

function Get-NSSnmpVolumeValue
{   param   (  [string] $name
            )
    if ( -not (Get-Module -listavailable snmp) )
    {   throw "The Required SNMP Module is not available. Please install the SNMP module from the PowerShell gallery at the following location HTTPS://www.powershellgallery/packages/SNMP"    
    } else
    {   $GLOBAL:SNMPArrayIPAddress = (($BaseURI -split "//")[1] -split ":")[0] 
        # this pulls out the IP Address
        $SNMPPort = (Get-NSGroup).snmp_get_port
        $GLOBAL:CommunityString = (get-NSGroup).snmp_community
        $VolRootOID = ".1.3.6.1.4.1.37447.1.2.1."
        $StartVol = 0
        $ParentArray = @()
        while ( ((Get-SNMPdata -ip $SNMPArrayIPAddress -oid ($VolRootOID + "2." + "$StartVol") -Community $CommunityString -version V2 -timeout 3000).data  ) -like $StartVol)
        {   $FeatureNum = 3
            $TempOID = $VolRootOID + $FeatureNum + "." + "$StartVol"
            $OIDValue = ((Get-SNMPdata -ip $SNMPArrayIPAddress -oid $TempOid -Community $CommunityString -version V2 -timeout 3000).data)
            $FeatureName = Get-NSPrivateSNMPFeatureLookup $FeatureNum 2
            $ParentArray+=[ordered]@{$FeatureName = $OIDValue}
            while ($FeatureNum -le 54)
            {   $FeatureNum = $FeatureNum + 1
                $TempOID = $VolRootOID + $FeatureNum + "." + "$StartVol"
                $OIDValue = ((Get-SNMPdata -ip $SNMPArrayIPAddress -oid $TempOid -Community $CommunityString -version V2 -timeout 3000).data)
                $FeatureName = Get-NSPrivateSNMPFeatureLookup $FeatureNum 2
                $ParentArray[$StartVol].add($FeatureName,$OIDValue)
            }
            $StartVol=$StartVol+1
        }
        [System.Collections.ArrayList]$PerfData=@()
        foreach ( $PSVolume in $ParentArray )
        {   $PSObjVolume = [PSCustomObject]$PSVolume
                $PSObjVolume.PSObject.TypeNames.Insert(0,$DataSetType)
            if ( ($PSVolume.volName -eq $name) -or (-not ($name)) )
                {   $supress=$PerfData.add($PSObjVolume)
                }
        }
        Return $PerfData
    } 
}

The best part about this is that SNMP data can be returned to the user in the same method that restAPI method commands work, this allows the users to not have to understand any of the ugly details, and instead focus on the data they want, not the underlying protocol used to get it.

So neat to see PowerShell being used to glue together 1987 technology with current 2019 technology seamlessly.