Automating Cisco ACI with PowerCLI (part I)

This blog describes a way how to leverage PowerClI (from VMware) to automate tasks within Cisco ACI, the SDN solution from Cisco.
PowerCLI is based on Windows PowerShell.
The purpose of PowerCLI is to be able to automate tasks within VMware vSphere, which on first sight has little to nothing to do with Cisco ACI, but:
As Cisco ACI and VMware vSphere are connected to each other and they also can be integrated with each other, it is not strange to combine those 2 worlds.

With PowerCLI it is possible to manage the DVS directly (through the vCenter server), but it’s not possible to manage the DVS through ACI, as PowerCLI isn’t capable of managing Cisco ACI directly.
There is a Powershell toolkit (called: “Powertool”-kit) available for Cisco ACI, but it’s heavily outdated: the last update is from 2015 ..).
Cisco has stopped the development of the Powertool-kit: which I think is a bad thing, as (cloud-)admins are working more and more with PowerShell).

So I’ve created the functions so U admins haven’t to develop it!

ACI uses a logical policy model, which creates logical networks and (when leveraging VMware integration) creates Port Groups on the VMware DVS.
aci logical model relationship
Endpoint Groups (EPGs), which contain virtual machines with the same security settings, can be connected to VMM domains, which are the representation of the VMware (vCenter server) environment.
VMM Domain policy model
By connecting an EPG to an VMM domain, a portgroup is automatically created by Cisco ACI on the DVS through the vCenter server. The connection between ACI and the DVS is fully controlled from the ACI-side.

For more information about how to setup Cisco ACI and VMware integration: https://www.cisco.com/c/en/us/td/docs/switches/datacenter/aci/apic/sw/1-x/aci-fundamentals/b_ACI-Fundamentals/b_ACI-Fundamentals_chapter_01011.html

So let’s make ACI manageable through Powershell/PowerCLI:

How to automate ACI with PowerCLI?

As with the “Connected-VIServer” cmdlet from PowerClI, there must be a way to connect to ACI with PowerShell (PS).
Cisco ACI leverages the Restfull API based on XML or JSON.
Powershell has cmdlets to convert JSON to PowerShell-CustomObjects (called ‘PSCustomObjects’) and visa-versa through the ‘convertfrom-json’ and ‘convertto-json’ cmdlets.
So, this is most easiest choice to make: JSON it is 🙂

But first things first: how to make an API call to ACI?
This can simply achieved by leveraging the “Invoke-webrequest”-cmdlet, but with ACI a few rules apply:
– REST API calls uses cookies/websessions for the connection.
– A session has a timeout which must be refreshed manually

These rules are solved by the functions below:

function Execute-API-Call
{
[CmdletBinding()] 
Param 
([Parameter(Mandatory=$true)] 
[string]$method,
[Parameter(Mandatory=$true)] 
[string]$ContentType,
[Parameter(Mandatory=$true)] 
[string]$url,
[Parameter(Mandatory=$false)] 
[string]$body)
#define return value
$response = New-Object PsObject -Property @{httpResponse = "";websession = ""}
#execute actual REST-API call
try {
 if (!$global:websession) {
 # if no websession exist, run Invoke-webrequest to retrieve websession details
 Write-Log -Level Info -Message "sending $body to $url with method $method as contentype $ContentType"
 $rawresponse = Invoke-webrequest -Method $method -ContentType $ContentType -Uri $url -Body $body -SessionVariable websession
 #return websession
 $response.websession = $websession
 } else { 
 if (!$body) {
 #if no body is added, execute Invoke-webrequest without the body-parameter
 Write-Log -Level Info -Message "sending $url with method $method as contentype $ContentType"
 $rawresponse = Invoke-webrequest -Method $method -ContentType $ContentType -Uri $url -WebSession $global:websession
 } else {
 Write-Log -Level Info -Message "sending $body to $url with method $method as contentype $ContentType"
 #execute Invoke-webrequest when body exist.
 $rawresponse = Invoke-webrequest -Method $method -ContentType $ContentType -Uri $url -Body $body -WebSession $global:websession
 } 
 }
 Write-Log -Level Info -Message "$rawresponse"
 } catch {
 # if an error occurs during the API call log the returncode
 $rawresponse = $_.Exception.Response.StatusCode.Value__
 Write-Log -Level WARN -Message "HTTP response code is $rawresponse $_"
 }
 $response.HttpResponse = $rawresponse
 return $response
}

This function is able to execute 3 types of ‘Invoke-WebRequest” cmdlets.
With or without a WebSession (for login purposes), and when a WebSession exists it can be executed with or without a HTML-body.
It returns the WebSession-details and the HttpResponse through the response-variable.
It also creates a log-file including sended and received data.

For the write-log cmdlet I use the following script, which can be download here: https://gallery.technet.microsoft.com/scriptcenter/Write-Log-PowerShell-999c32d0

So now we can make the API call it’s time to connect to ACI:

function Connect-APICController([string]$HostName, [string]$UserName, [string]$PassWord)
{
#example function call: Connect-APIC -HostName 1.2.3.4 -UserName admin -PassWord Password01!

# This is the URL we're going to be logging in to
$loginurl = "https://" + $HostName + "/api/aaaLogin.json"
# Format the JSON body for a login
$creds = '{"aaaUser":{"attributes":{"name":"'+ $UserName+'","pwd":"'+ $PassWord+'"}}}'
# Execute the API Call
$resultjson = Execute-API-Call -method "POST" -ContentType "application/json" -url $loginurl -body $creds
#store websession as global variable

if(!$resultjson -or $resultjson -match "401") { 
Write-log -Level Warn -Message "Authentication to APIC controller $HostName failed with username $username"
} else {
Write-log -Level Info -Message "succesfully authenticated to APIC controller $HostName with username $username"
$global:websession = $resultjson.websession
$global:DefaultAPICController = $HostName
$result = ConvertFrom-Json -InputObject $resultjSON.HttpResponse
#create timer variable
$aaarefreshtimer = New-Object System.Timers.Timer
#set refresh timer (timeout minus 5 seconds)
$aaarefreshtimer.Interval = ($result.imdata.aaalogin.attributes.restTimeoutSeconds - 5)*1000
$aaarefreshtimer.AutoReset = $true
#create ACI login refreshblock scriptblock
$aaarefreshtimeraction = {
 $url = "https://" + $global:DefaultAPICController + "/api/aaaRefresh.json"
 # Execute the API Call
 $resultJSON = Execute-API-Call -method "GET" -ContentType "application/json" -url $url
 #convert JSON output to PSCustomObjects
 $result = ConvertFrom-Json -InputObject $resultJSON.httpResponse
 #create cookie object details
 $cookie = New-Object System.Net.Cookie
 $cookie.HttpOnly = $true
 $cookie.name = "APIC-Cookie"
 $cookie.domain = $apic
 $cookie.value = $result.imdata.aaalogin.attributes.token
 #create websession object
 $websession = New-Object Microsoft.PowerShell.Commands.WebRequestSession
 #store new cookie in websession. 
 $websession.Cookies.Add($cookie)
 #store websession as global variable
 $global:websession = $websession
 write-log -Level Info -Message "successfully updated websession with cookie $cookie)"
}
$aaarefreshstart = Register-ObjectEvent -InputObject $aaarefreshtimer -SourceIdentifier aaarefresh -EventName Elapsed -Action $aaarefreshtimeraction 
Write-Log -Level Warn -Message "Timer-object aaarefresh registered"
$aaarefreshtimer.Start()
Write-Log -Level Warn -Message "refresh AAA timer started"
}
return $result
}

The above function looks complex but it is very straightforward, it executes the  ‘aaalogin’ API-call and stores the websession-details as a global variable which can be used in subsequent API-calls.
This is the same method which the “Connect-VIServer”-cmdlet uses and stores the connection-details also as a global-variable.

But a session, without an manual refresh, will timeout too soon.
The ‘Connect-APICController’ function creates (after a successfull login) a Timer-object that will (repeatedly) elapse 5 seconds before the time-out.
When the timer elapses an ‘Elapsed’-event is being generated, which is being noticed by the “Register-ObjectEvent” process and the $aaarefreshtimeraction scriptblock is executed.
The scriptblock executes the aaarefresh API-call and re-creates the WebSession global-variable.

The basics are set, now we can go ahead and start the real fun.
Stay tuned for a follow-up/part deux.

1 Comment

Leave a Reply

Your email address will not be published. Required fields are marked *

Scroll to top