Forum Discussion

SteveBamford's avatar
SteveBamford
Icon for Neophyte rankNeophyte
21 days ago

Adding Arista AP's to LogicMonitor

There was a previous request about this, and I have been working on this my self, so I thought I would share what I have delivered.

Required Custom Parameters.

The Discovery and collection scripts are expecting the following custom properties to be present for each device I have added these at the folder (in our case "Networks") so they cascade down.

AristaWIFI.api.endpoint - This is the endpoint within CV-Cue under advanced setting for configuration and management API integrations.

AristaWIFI.api.key - The API Key for CV-CUE

AristaWIFI.api.token - The API Token for CV-CUE

If these are not provided then the collector and discovery scripts will fail.

NetScan:

I had issues getting the netscan to work in Groovy so wrote a pyhton script to do this which links to Arista CloudVision and LogicMonitor, I have something in my backlog now to migrate this to groovy, the biggest challenge I have is that the HTTP DELETE call in Groovy is expecting data which is not needed for the delete call to close the session.   This also uses the Python Core Library we use for all of our LogicMonitor integrations, so sharing is a tad difficult as I would have share the script and our Core Library.   If people think this is worth While I would add it in the future.

I set a system category of "AristaWifi" to easily identify Arista AP's for the collection and discovery scripts.

Finally I also include the AP's ID on discovery as this is used to pull the specific AP from CV-CUE as the custom Property "CVcueId".

What Metrics can be collected.

Currently I am collecting CPU and Memory which I have created a Arista WIFI General Metrics Data source, and Arista WIFI SSID Metrics which is collecting the Associated Clients and the SSID State which is whether it is Enabled (1) or Disabled (0), I also have unknown (3) if the information can not be retrieved from the JSON.

Arista Wifi General Metrics:

This data source is configured to use Script as the collection source, below is the script.

Note: The logout is commented out until I can result the http delete issue mentioned above.

/*******************************************************************************
*  Arista WIFI Integration with CUE for discovery of the AP's
 ******************************************************************************/

import com.santaba.agent.groovyapi.http.*
import com.santaba.agent.groovy.utils.GroovyScriptHelper as GSH
import com.logicmonitor.mod.Snippets
import com.santaba.agent.AgentVersion
import java.text.DecimalFormat
import com.santaba.agent.util.Settings
import groovy.json.JsonOutput
import groovy.json.JsonSlurper
import groovy.json.JsonBuilder
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit

// To run in debug mode, set to true
Boolean debug = false
// To enable logging, set to true
Boolean log = false

// Set props object based on whether or not we are running inside a netscan or debug console
def props
try {
    hostProps.get("system.hostname")
    props = hostProps
    debug = true  // set debug to true so that we can ensure we do not print sensitive properties
}
catch (MissingPropertyException) {
    props = netscanProps
}

String key = props.get("AristaWIFI.api.key")
String token = props.get("AristaWIFI.api.token")
String AristaEndPoint = props.get("AristaWIFI.api.endpoint")

//Get the Arista CV WIFI Node Node Id as discovered in the scanning script and submitted to the device..
String nodeId = props.get("CVcueId")
String clientId = "logicmonitor"
String wifiUrl = "https://"+AristaEndPoint+"/wifi/api/"
if (!key) {
    throw new Exception("Must provide AristaWIFI.api.key to run this script.  Verify necessary credentials have been provided in Netscan properties.")
}
if (!token) {
    throw new Exception("Must provide AristaWIFI.api.token credentials to run this script.  Verify necessary credentials have been provided in Netscan properties.")
}
if (!clientId) {
    throw new Exception("Must provide AristaWIFI.api.clientId credentials to run this script.  Verify necessary credentials have been provided in Netscan properties.")
}
//def logCacheContext = "${org}::arista-wifi-cloud"
//Boolean skipDeviceDedupe = props.get("skip.device.dedupe", "false").toBoolean()
String hostnameSource    = props.get("hostname.source", "")?.toLowerCase()?.trim()

Integer collectorVersion = AgentVersion.AGENT_VERSION.toInteger()
 
// Bail out early if we don't have the correct minimum collector version to ensure netscan runs properly
if (collectorVersion < 32400) {
    def formattedVer = new DecimalFormat("00.000").format(collectorVersion / 1000)
    throw new Exception("Upgrade collector running netscan to 32.400 or higher to run full featured enhanced netscan. Currently running version ${formattedVer}.")
}

httpClient = HTTP.open(AristaEndPoint,443);
httpClient.setHTTPProxy('[YOUR Proxy]',8080);
// Log in to arista
loginurl = "https://"+AristaEndPoint+"/wifi/api/session"

payloadstring = '{"type":"apiKeycredentials","keyId":"'+key+'","keyValue":"'+token+'","timeout":129,"clientIdentifier": "'+clientId+'"}';


def payload = payloadstring;
//println payload;
def loginResponse = httpClient.post(loginurl,payload,["Content-Type":"application/json"]);
if ( !(httpClient.getStatusCode() =~ /20/) )
{
    // Error has occured
    println "Authentication Failure";
    println httpClient.getStatusCode();
    println loginResponse;
    return(1);
}

String RawCookie = httpClient.getHeader("Set-Cookie")
//Have Kept but as yet not needed will remove if not needed..
String httpResponseBody = httpClient.getResponseBody()
//Retrieve the Cookie to use from the header.
AristaCookie = RawCookie.split(';')[0]

//Retrieve AP data.

deviceURL = 'manageddevices/aps?startindex=0&pagesize=1000&locationid=0&nodeid=0&boxid='+nodeId
SendUrl = wifiUrl+deviceURL

def header = ["Content-Type":"application/json","Cookie":AristaCookie]

String deviceResponse = httpClient.get(SendUrl,header)

if ( !(httpClient.getStatusCode() =~ /200/))
{
  println "Failed to retrieve data "+httpClient.getStatusCode
  return 1
}

String deviceBody = httpClient.getResponseBody()

def deviceJson = new JsonSlurper().parseText(deviceBody)

def healthStats = deviceJson.managedDevices.healthStats

def wildvalue = ""
Integer rawminCPU = healthStats[0].minCpuUtilization
Integer minCpu = rawminCPU/100
Integer rawavgCPU = healthStats[0].avgCpuUtilization
Integer avgCpu = rawavgCPU/100
Integer rawmaxCPU = healthStats[0].maxCpuUtilization
Integer maxCpu = rawmaxCPU/100


Integer rawminMem = healthStats[0].minMemoryUtilization
Integer minMem = rawminCPU/100
Integer rawAvgMem = healthStats[0].avgMemoryUtilization
Integer avgMem = rawAvgMem/100
Integer rawmaxMem = healthStats[0].maxMemoryUtilization
Integer maxMem = rawmaxCPU/100


println "${wildvalue}.mincpu=${minCpu}"
println "${wildvalue}.avgcpu=${avgCpu}"
println "${wildvalue}.maxcpu=${maxCpu}"
println "${wildvalue}.minMem=${minMem}"
println "${wildvalue}.avgMem=${avgMem}"
println "${wildvalue}.maxMem=${maxMem}"

// Commented out log out process for now as will need to revisit currently reducing Timeout to 5 minutes for the session.
/* String cookieString = '"'+AristaCookie+'"'
def logoutheaders = ["Content-Type":"application/json","Cookie":AristaCookie]
println logoutheaders
def logoutResponse = httpClient.delete(loginurl,"{}",logoutheaders)
logoutResult = httpClient.getStatusCode()
if ( !(httpClient.getStatusCode() =~ /20/) )
{
    //Error has occured
    println "Logout Failure";
    println httpClient.getStatusCode();
    println loginResponse;
    return(1);
}

println logoutResponse;*/

def LMDebugPrint(message) {
    if (debug) {
        println(message.toString()) 
    }
}

return 0

Arista WIFI SSID Metrics:

This will collect the number of associated clients by Band (IE 2.4ghz) and SSID I have the created graphs to aggregate the data together for total number of associated clients.

Discovery Script:

/*******************************************************************************
*  Arista WIFI Integration with CUE for discovery of the AP's
 ******************************************************************************/

import com.santaba.agent.groovyapi.http.*
import com.santaba.agent.groovy.utils.GroovyScriptHelper as GSH
import com.logicmonitor.mod.Snippets
import com.santaba.agent.AgentVersion
import java.text.DecimalFormat
import com.santaba.agent.util.Settings
import groovy.json.JsonOutput
import groovy.json.JsonSlurper
import groovy.json.JsonBuilder
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit


// To run in debug mode, set to true
Boolean debug = false
// To enable logging, set to true
Boolean log = false

// Set props object based on whether or not we are running inside a netscan or debug console
def props
try {
    hostProps.get("system.hostname")
    props = hostProps
    debug = true  // set debug to true so that we can ensure we do not print sensitive properties
}
catch (MissingPropertyException) {
    props = netscanProps
}

String key = props.get("AristaWIFI.api.key")
String token = props.get("AristaWIFI.api.token")
String AristaEndPoint = props.get("AristaWIFI.api.endpoint")
//Get the Arista CV WIFI Node Node Id as discovered in the scanning script and submitted to the device..
String nodeId = props.get("CVcueId")
String clientId = "logicmonitor"

String wifiUrl = "https://"+AristaEndPoint+"/wifi/api/"
if (!key) {
    throw new Exception("Must provide AristaWIFI.api.key to run this script.  Verify necessary credentials have been provided in Netscan properties.")
}
if (!token) {
    throw new Exception("Must provide AristaWIFI.api.token credentials to run this script.  Verify necessary credentials have been provided in Netscan properties.")
}
if (!clientId) {
    throw new Exception("Must provide AristaWIFI.api.clientId credentials to run this script.  Verify necessary credentials have been provided in Netscan properties.")
}
//def logCacheContext = "${org}::arista-wifi-cloud"
//Boolean skipDeviceDedupe = props.get("skip.device.dedupe", "false").toBoolean()
String hostnameSource    = props.get("hostname.source", "")?.toLowerCase()?.trim()

Integer collectorVersion = AgentVersion.AGENT_VERSION.toInteger()
 
// Bail out early if we don't have the correct minimum collector version to ensure netscan runs properly
if (collectorVersion < 32400) {
    def formattedVer = new DecimalFormat("00.000").format(collectorVersion / 1000)
    throw new Exception("Upgrade collector running netscan to 32.400 or higher to run full featured enhanced netscan. Currently running version ${formattedVer}.")
}

httpClient = HTTP.open(AristaEndPoint,443);
httpClient.setHTTPProxy('[Your Proxy]',8080);
// Log in to arista
loginurl = "https://"+AristaEndPoint+"/wifi/api/session"

payloadstring = '{"type":"apiKeycredentials","keyId":"'+key+'","keyValue":"'+token+'","timeout":129,"clientIdentifier": "'+clientId+'"}';


def payload = payloadstring;
//println payload;
def loginResponse = httpClient.post(loginurl,payload,["Content-Type":"application/json"]);
if ( !(httpClient.getStatusCode() =~ /20/) )
{
    // Error has occured
    println "Authentication Failure";
    println httpClient.getStatusCode();
    println loginResponse;
    return(1);
}

String RawCookie = httpClient.getHeader("Set-Cookie")
//Have Kept but as yet not needed will remove if not needed..
String httpResponseBody = httpClient.getResponseBody()
//Retrieve the Cookie to use from the header.
AristaCookie = RawCookie.split(';')[0]

//Retrieve AP data.

deviceURL = 'manageddevices/aps?startindex=0&pagesize=1000&locationid=0&nodeid=0&boxid='+nodeId
SendUrl = wifiUrl+deviceURL

def header = ["Content-Type":"application/json","Cookie":AristaCookie]

String deviceResponse = httpClient.get(SendUrl,header)

if ( !(httpClient.getStatusCode() =~ /200/))
{
  println "Failed to retrieve data "+httpClient.getStatusCode
  return 1
}

String deviceBody = httpClient.getResponseBody()
def encoded_instance_props_array = []

def deviceJson = new JsonSlurper().parseText(deviceBody)

def wildvalue = ""
def wildalias = ""
def description = ""
def getOperatingBand = ""
def getSSID = ""
def radios = deviceJson.managedDevices.radios

radios[0].each { RadioEntry ->
    // Build out wireless entries.
    getOperatingBand = RadioEntry.operatingBand
    RadioEntry.wirelessInterfaces.each { ssidEntry ->
       
        getssid = ssidEntry.ssid
        description = ssidEntry.bssid
        wildvalue = getOperatingBand+"_"+getssid
        wildalias = wildvalue
        def instance_props = [
            "auto.opertating.band": RadioEntry.operatingBand,
            "auto.ssid": ssidEntry.ssid,
            "auto.bssid":ssidEntry.bssid,
            "auto.ssid.profileId":ssidEntry.ssidProfileId
        ]
        encoded_instance_props_array = instance_props.collect() { property, value ->
                URLEncoder.encode(property.toString()) + "=" + 
URLEncoder.encode(value.toString())
            }
        println "${wildvalue}##${wildalias}##${description}####${encoded_instance_props_array.join("&")}"
    }

}


// Commented out log out process for now as will need to revisit currently reducing Timeout to 5 minutes for the session.
/* String cookieString = '"'+AristaCookie+'"'
def logoutheaders = ["Content-Type":"application/json","Cookie":AristaCookie]
println logoutheaders
def logoutResponse = httpClient.delete(loginurl,"{}",logoutheaders)
logoutResult = httpClient.getStatusCode()
if ( !(httpClient.getStatusCode() =~ /20/) )
{
    //Error has occured
    println "Logout Failure";
    println httpClient.getStatusCode();
    println loginResponse;
    return(1);
}

println logoutResponse;*/

def LMDebugPrint(message) {
    if (debug) {
        println(message.toString()) 
    }
}

return 0

Collection Script:

/*******************************************************************************
*  Arista WIFI Integration with CUE for discovery of the AP's
 ******************************************************************************/

import com.santaba.agent.groovyapi.http.*
import com.santaba.agent.groovy.utils.GroovyScriptHelper as GSH
import com.logicmonitor.mod.Snippets
import com.santaba.agent.AgentVersion
import java.text.DecimalFormat
import com.santaba.agent.util.Settings
import groovy.json.JsonOutput
import groovy.json.JsonSlurper
import groovy.json.JsonBuilder
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit


// To run in debug mode, set to true
Boolean debug = false
// To enable logging, set to true
Boolean log = false

// Set props object based on whether or not we are running inside a netscan or debug console
def props
try {
    hostProps.get("system.hostname")
    props = hostProps
    debug = true  // set debug to true so that we can ensure we do not print sensitive properties
}
catch (MissingPropertyException) {
    props = netscanProps
}

String key = props.get("AristaWIFI.api.key")
String token = props.get("AristaWIFI.api.token")
String AristaEndPont = props.get("AristaWIFI.api.endpoint")

//Get the Arista CV WIFI Node Node Id as discovered in the scanning script and submitted to the device..
String nodeId = props.get("CVcueId")
String clientId = "logicmonitor"
String wifiUrl = "https://"+AristaEndPont+"/wifi/api/"
if (!key) {
    throw new Exception("Must provide AristaWIFI.api.key to run this script.  Verify necessary credentials have been provided in Netscan properties.")
}
if (!token) {
    throw new Exception("Must provide AristaWIFI.api.token credentials to run this script.  Verify necessary credentials have been provided in Netscan properties.")
}
if (!clientId) {
    throw new Exception("Must provide AristaWIFI.api.clientId credentials to run this script.  Verify necessary credentials have been provided in Netscan properties.")
}
if (!AristaEndPont) {
    throw new Exception("Must provide AristaWIFI.api.endpoint  to run this script.  Verify necessary variable is within your LogicMonitor instance before continuing.")
}
//def logCacheContext = "${org}::arista-wifi-cloud"
//Boolean skipDeviceDedupe = props.get("skip.device.dedupe", "false").toBoolean()
String hostnameSource    = props.get("hostname.source", "")?.toLowerCase()?.trim()

Integer collectorVersion = AgentVersion.AGENT_VERSION.toInteger()
 
// Bail out early if we don't have the correct minimum collector version to ensure netscan runs properly
if (collectorVersion < 32400) {
    def formattedVer = new DecimalFormat("00.000").format(collectorVersion / 1000)
    throw new Exception("Upgrade collector running netscan to 32.400 or higher to run full featured enhanced netscan. Currently running version ${formattedVer}.")
}

httpClient = HTTP.open(AristaEndPont,443);
httpClient.setHTTPProxy('[Your Proxy]',8080);
// Log in to arista
loginurl = "https://"+AristaEndPont+"/wifi/api/session"

payloadstring = '{"type":"apiKeycredentials","keyId":"'+key+'","keyValue":"'+token+'","timeout":129,"clientIdentifier": "'+clientId+'"}';


def payload = payloadstring;
//println payload;
def loginResponse = httpClient.post(loginurl,payload,["Content-Type":"application/json"]);
if ( !(httpClient.getStatusCode() =~ /20/) )
{
    // Error has occured
    println "Authentication Failure";
    println httpClient.getStatusCode();
    println loginResponse;
    return(1);
}

String RawCookie = httpClient.getHeader("Set-Cookie")
//Have Kept but as yet not needed will remove if not needed..
String httpResponseBody = httpClient.getResponseBody()
//Retrieve the Cookie to use from the header.
AristaCookie = RawCookie.split(';')[0]

//Retrieve AP data.

deviceURL = 'manageddevices/aps?startindex=0&pagesize=1000&locationid=0&nodeid=0&boxid='+nodeId
SendUrl = wifiUrl+deviceURL

def header = ["Content-Type":"application/json","Cookie":AristaCookie]

String deviceResponse = httpClient.get(SendUrl,header)

if ( !(httpClient.getStatusCode() =~ /200/))
{
  println "Failed to retrieve data "+httpClient.getStatusCode
  return 1
}

String deviceBody = httpClient.getResponseBody()
def encoded_instance_props_array = []

def deviceJson = new JsonSlurper().parseText(deviceBody)

def wildvalue = ""
def wildalias = ""
def description = ""
def getOperatingBand = ""
def getSSID = ""
def radios = deviceJson.managedDevices.radios
def ssidActive = 3

radios[0].each { RadioEntry ->
    // Build out wireless entries.
    getOperatingBand = RadioEntry.operatingBand
    RadioEntry.wirelessInterfaces.each { ssidEntry ->
       
        getssid = ssidEntry.ssid
        description = ssidEntry.bssid
        wildvalue = getOperatingBand+"_"+getssid
        wildalias = wildvalue
        
        if (ssidEntry.active == true) {
            ssidActive = 1
        } else {
            ssidActive = 0
        }
        
        println "${wildvalue}.numAssocClients=${ssidEntry.numAssocClients}"
        println "${wildvalue}.active=${ssidActive}"
    }

}


// Commented out log out process for now as will need to revisit currently reducing Timeout to 5 minutes for the session.
/* String cookieString = '"'+AristaCookie+'"'
def logoutheaders = ["Content-Type":"application/json","Cookie":AristaCookie]
println logoutheaders
def logoutResponse = httpClient.delete(loginurl,"{}",logoutheaders)
logoutResult = httpClient.getStatusCode()
if ( !(httpClient.getStatusCode() =~ /20/) )
{
    //Error has occured
    println "Logout Failure";
    println httpClient.getStatusCode();
    println loginResponse;
    return(1);
}

println logoutResponse;*/

def LMDebugPrint(message) {
    if (debug) {
        println(message.toString()) 
    }
}

return 0

 

3 Replies

  • This is a very thorough and enlightening post - GREAT JOB, SteveBamford​ !!!

    We don't use Arista, but what I love best about this post is it is a great example on how to do discovery and data collection in LogicMonitor.

    I know there's lots of code examples over on github and within the LM Docs, and it is great seeing posts like this in the LM Community.

  • FAQs

    Where are the interface stats?   They are not available from the API at this time, the only ones that is possibly interface stats.

    Are there more metrics that can be retrieved?  Yes specific Band (IE 2.4GHZ) metrics which if we add I will include here.

    Have you submitted to LM Exchange?  No Not yet waiting for feedback internally and feel free to drop a comment in below as I only work in Groovy when doing stuff in LogicMonitor, so any feedback is greatly appreciated.