Out with the old and in with the new and honestly I couldn't be happier with the new that's coming in. What is the new that I'm talking about? The Nexus 1000V REST API of course.
I just finished writing scripts to manage (create,modify,delete) vlans and port-profiles on a Nexus 1000V using expect. The scripts work fine, I'm using PowerShell as the main script and it calls out to expect and ssh running in a Cygwin environment, however it would be nice to use the REST API, and do everything from PowerShell or the language of your choice.
The customer I did the work for has multiple 1000V deployments and wanted to automate some aspects of the 1000V administration. Vlan provisioning and port-profile creation seemed to be obvious choices.
I wrote an expect script to check for the existence of one or more vlans and an expect script to check for the existence of a specific port-profile. Here's the important part of that code;
1 2 3 4 5 6 7 8 910111213 | setvlans [split$vlanIDs","]foreach vlan$vlans {setchkVlanCmd"show vlan brief | egrep '^| egrep '^$vlan ' | count | sed 's/^/| count | sed 's/^/$vlan:/' | no-more"| no-more"send"$chkVlanCmd\r"expect {":0"{ puts"$vlan:0"}":1"{ puts"$vlan:1"} }} |
$vlanIDs is set from the command line it is a commas separated list of vlans to check for, the script loops through them one by one, using the "show vlan brief" command to display the vlan. Additionally the script uses the Nexus command line capabilities to run egrep, count and sed on the output of "show vlan brief"
If the input vlan list was "100,101,200,201" and vlan 100 and 201 exist then output of the script will be
100:1
101:0
200:0
201:1
Similarly the input to the expect script that checks for the existence of the port-profile will output the port-profile name with either a :0 or :1 appended to it, :0 for not found and :1 for found.
I call the expect scripts from a PowerShell and use the formatted output from expect to make logic decisions in the PowerShell script. With the new Nexus 1000V REST API I can run everything from PowerShell and get the results I need to make the correct decisions to either add the vlan or not, or to create the port-profile or just update it.
To retrieve Vlans via the Nexus 1000V REST API for the VMware version of the 1000V this code does the trick.
1234 | $n1kUser="admin"$n1kPass=ConvertTo-SecureString-String"admin"-AsPlainText -Force$n1kCred=New-Object-TypeName System.Management.Automation.PSCredential -ArgumentList$n1kUser, $n1kPass$n1kVlans=Invoke-RestMethod-Uri http://172.21.67.89/api/vlan -Credential$n1kCred |
The command will return either XML or JSON, by default XML is returned. The retuned XML looks like this
1 2 3 4 5 6 7 8 910111213141516171819 | <?xml version="1.0" encoding="utf-8"?><set name="vlan_set"> <instance name="208" url="/api/vlan/208"> <properties> <id>208</id> <state>active</state> <name>VLAN0208</name> <shutdown>false</shutdown> </properties> </instance> <instance name="610" url="/api/vlan/610"> <properties> <id>610</id> <state>active</state> <name>VLAN0610</name> <shutdown>false</shutdown> </properties> </instance></set> |
Then I could do the following to list the returned Vlans
$n1kVlans.set.instance.properties.id
208
610
You could specify -Headers @{"Accept"="application/json"} and get back JSON instead. Unfortunately there is a bug in PowerShell 3.0 that does not allow for the manipulation of the some web request headers, Accept being one of them. The bug has been reported as fixed for PowerShell 4.0. That does not mean that PowerShell 3.0 cannot be used to retrieve JSON output, it can, just not via the Invoke-RestMethod cmdlet.
This code will produce JSON output, using basic web authentication.
1 2 3 4 5 6 7 8 9101112131415161718192021222324252627 | functionreqREST(){param([string] $inUri=$null,[string] $inPath=$null,[string] $inCreds=$null,[string] $inMethod=$null); $encoded=[System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($inCreds))$webRequest="" $webRequest=[System.Net.WebRequest]::Create("$inUri/$inPath")$webRequest.PreAuthenticate =$true $webRequest.Headers.Add('Authorization', "Basic "+$encoded)$webRequest.Accept ='application/json' $webRequest.Method =$inMethod $webRequest.GetResponse()[Net.HttpWebResponse] $result=$webRequest.GetResponse()[IO.Stream] $stream=$result.GetResponseStream()[IO.StreamReader] $reader=New-ObjectIO.StreamReader($stream)[string] $output=$reader.ReadToEnd()$stream.Flush()$stream.Close()$output}reqREST"http://172.21.67.89" "api/vlan" "admin:admin" "GET" |
Produces this output
1234567 | {"208":{"url":"/api/vlan/208","properties":{"id":208,"state":"active","name":"VLAN0208","shutdown":false}},"610":{"url":"/api/vlan/610","properties":{"id":610,"state":"active","name":"VLAN0610","shutdown":false}},"613":{"url":"/api/vlan/613","properties":{"id":613,"state":"active","name":"VLAN0613","shutdown":false}},"1":{"url":"/api/vlan/1","properties":{"id":1,"state":"active","name":"default","shutdown":false}},"611":{"url":"/api/vlan/611","properties":{"id":611,"state":"active","name":"VLAN0611","shutdown":false}},"33":{"url":"/api/vlan/33","properties":{"id":33,"state":"active","name":"VLAN0033","shutdown":false}},"614":{"url":"/api/vlan/614","properties":{"id":614,"state":"active","name":"VLAN0614","shutdown":false}}} |
A port-profile request and it's output
1 | reqREST "http://172.21.67.89" "api/port-profile" "admin:admin" "GET" |
1234 | { "Unused_Or_Quarantine_Uplink":{"url":"/api/port-profile/Unused_Or_Quarantine_Uplink","properties":{"minPorts":1,"description":"Port-group created for Nexus1000V internal usage. Do not use.","capability":"","state":false,"name":"Unused_Or_Quarantine_Uplink","shutdown":true,"portGroupName":"Unused_Or_Quarantine_Uplink","portBinding":"static","maxPorts":32,"type":"Ethernet"}},"prd-clnt-uplink":{"url":"/api/port-profile/prd-clnt-uplink","properties":{"minPorts":1,"description":"","switchportMode":"trunk","capability":"","state":false,"type":"Ethernet","name":"prd-clnt-uplink","shutdown":false,"portGroupName":"prd-clnt-uplink","portBinding":"static","maxPorts":32,"switchportTrunkVLANs":[10]}},"Unused_Or_Quarantine_Veth":{"url":"/api/port-profile/Unused_Or_Quarantine_Veth","properties":{"minPorts":1,"description":"Port-group created for Nexus1000V internalusage. Do not use.","capability":"","state":false,"name":"Unused_Or_Quarantine_Veth","shutdown":true,"portGroupName":"Unused_Or_Quarantine_Veth","portBinding":"static","maxPorts":32,"type":"Vethernet"}},"profile-02":{"url":"/api/port-profile/profile-02","properties":{"minPorts":1,"description":"","switchportMode":"access","state":false,"name":"profile-02","capability":"","portGroupName":"","portBinding":"static","maxPorts":32,"type":"Vethernet"}}} |
These two examples should show you how to eliminate the need for expect and ssh to query information on the Nexus 1000V. Additionally there is a CLI capability in the REST API. The Nexus 1000V REST API user guide, for the VMware version of the Nexus 1000V REST API has an example call to the CLI to save the running configuration.
curl -u username:password 10.10.10.2/api/cli -d '{"cmd": "copy r s"}' -i
This command will copy the running-config to the startup-config. The example uses curl but it easily translates to the Invoke-RESTMethod
Invoke-RestMethod -Uri http://172.21.67.89/api/cli -Credential$n1kCred -Method POST -Body '{"cmd": "copy r s"}'
But it seems as if any cli command will work, e.g. "show vlan brief"
PS C:\Windows\system32> Invoke-RestMethod -Uri http://172.21.67.89/api/cli -Credential$n1kCred -Method POST -Body '{"cmd": "show vlan brief"}'VLAN Name Status Ports---- -------------------------------- --------- -------------------------------1 default active33 VLAN0033 active208 VLAN0208 active610 VLAN0610 active611 VLAN0611 active613 VLAN0613 active614 VLAN0614 active
So you can still sort of have your expect, if you were thinking you would miss it. I showed how to query vlans and port-profiles and get back XML or JSON, but what about creating them? The process is outlined in the user guide using curl for a port-profile. Showing a creation, an update and a deletion.
curl -X POST -u admin:password 10.10.10.2/api/port-profile -d '{ "name" : "profile1", "switchportMode" : "access", "shutdown" : "false"}'
Successfully created "port-profile profile1"
curl -X POST -u admin:password 10.10.10.2/api/port-profile/profile1 -d '{ "switchportMode" : "trunk", "shutdown" : "false"}'
Successfully modified "port-profile profile1"
curl -u admin:password -X DELETE 10.10.10.2/api/port-profile/profile1
Successfully deleted "port-profile profile1"
In PowerShell it is an easy translation.
PS C:\Windows\system32> Invoke-RestMethod -Uri http://172.21.67.89/api/port-profile -Credential$n1kCred -Method POST -Body '{ "name" : "profile1", "switchportMode" : "access", "shutdown" : "false"}'
Successfully created "port-profile profile1"PS C:\Windows\system32> Invoke-RestMethod -Uri http://172.21.67.89/api/port-profile/profile1 -Credential$n1kCred -Method POST -Body '{ "switchportMode" : "trunk", "shutdown" : "false"}'
Successfully modified "port-profile profile1