VMware SD-WAN Monitoring

Last updated on 26 April, 2024

LogicMonitor’s VMware SD-WAN monitoring package leverages VMware SD-WAN Orchestration API (formally known as VMware SD-WAN™ by VeloCloud®) monitor and alert on vital edge health and performance metrics.

For more information about VMware SD-WAN Orchestration API, see the following documentation from VMware:

Compatibility

As of February 2024, LogicMonitor’s VMware SD-WAN package is known to be compatible with VCO version 4.2–5.4.

Requirements

  • Collector version 32.400 or later
  • VMware orchestrator portal name and API Token
    For more information on setting up a VMware SD-WAN API Token see VMware SD-WAN Administration Guide
  • LogicMonitor_Collector_Snippets DataSource imported to ensure that your collector supports the code in this monitoring suite, or update to EA Collector 32.100 or later.

Add Resources into Monitoring

The following methods are available for on-boarding VMware SD-WAN resources into monitoring:

Note:The NetScan will look at system.sysname and system.displayname to attempt to identify duplicate devices. SNMP must be enabled or system.displayname must be set to the device name to prevent duplicate resources from being created.

  1. In your LogicMonitor Portal > Modules > Exchange, install the VMware SD-WAN modules. 
  2. Navigate to Resources > Add > Several Devices > Advanced NetScan.
    The Add Advanced NetScan page is displayed.
  3. Enter a name that you want to associate with this NetScan. For example, “VMware SD-WAN Devices”.
  4. Select the Collector to execute the NetScan.
    For more information, see Collector Assignment in Enhanced Script Netscan.
  5. Select Enhanced Script NetScan from the Method drop-down list.
  6. From the Enhanced Script section, select Device Credentials > Use custom credentials for this scan.
  7. Add the following properties that provide the NetScan with the required VMware SD-WAN credentials and change how and where the NetScan creates and organizes resources:
PropertyValueRequired
vmware.sdwan.orchestrator.hostVMware orchestrator respective portal, should be formatted vcoX.velocloud.net.Yes
vmware.sdwan.keyVMware orchestrator API token. For more information, see to API tokens.Yes
vmware.sdwan.enterprise.idVMware enterprise ID.Yes
vmware.sdwan.folder.nameThe name of the LogicMonitor Resource Group that this NetScan creates or uses if already existing. The value can be a nested child folder (for example, folder/folder/folder).This defaults to “VMware SDWAN”.This should not be assigned to a dynamic device group as those are not added by NetScans. For more information, see Device Groups OverviewNo
vmware.sdwan.collectorCollector ID to be used for newly added devices.No
skip.device.dedupeAllows you to skip device deduplication checks, making LogicMonitor API credentials not required.No
lmaccess.id 
or
logicmonitor.access.id
A LogicMonitor API access id to search for duplicate resources in the NetScan prior to adding them to the portal. For more information on portal monitoring, see LogicMonitor Portal Monitoring.Yes
Note: Only required if skip.device.dedupe set to true
lmaccess.key
or
logicmonitor.access.key
A LogicMonitor API key to search for duplicate resources in the NetScan prior to adding them to the portal. For more information on portal monitoring, see LogicMonitor Portal Monitoring.Yes
Note: Only required if skip.device.dedupe set to true
hostname.sourceAllows for selection of the source of the hostname that is used by the NetScan. This is useful in preventing duplicate device creation when conflicts are found for existing resources. For more information, see NetScan Troubleshooting.No

Note: Additional filters can be set to omit specific VMs from discovery. These filters behave the same way as Active Discovery filters. The following device level properties will be automatically discovered and can be filtered on. To exclude a device from being added set up a filter with any of the following properties and select “Not Equal.”

PropertyValue
vmware.sdwan.edge.logical.idVMware Edge Logical ID
vmware.sdwan.edge.nameVMware Edge Name
vmware.sdwan.serial.numberVMware Edge Serial Number
  1. Select Embed a Groovy script and embed the following script:

Warning: Do not edit the script. Edited Enhanced Script NetScans are not supported. If the LogicMonitor-provided script is edited, LogicMonitor Support can require you to overwrite your edits with the supported script if problems arise.

/*******************************************************************************
 * © 2007-2024 - LogicMonitor, Inc. All rights reserved.
 ******************************************************************************/

import com.logicmonitor.common.sse.utils.GroovyScriptHelper as GSH
import com.logicmonitor.mod.Snippets
import com.santaba.agent.AgentVersion
import java.text.DecimalFormat
import groovy.json.JsonOutput
import groovy.json.JsonSlurper


// To run in debug mode, set to true
Boolean debug = 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
}

// Import any needed credentials or properties that have been set in the UI
// Required properties
def key = props.get("vmware.sdwan.key")
def orchestratorHost = props.get("vmware.sdwan.orchestrator.host")
def enterpriseId = props.get("vmware.sdwan.enterprise.id")

// Optional properties
def rootFolder = props.get("vmware.sdwan.folder.name") ?: "VMware SD-WAN"
def collectorId = props.get("vmware.sdwan.collector")

Boolean skipDeviceDedupe = props.get("skip.device.dedupe", "false").toBoolean()
String hostnameSource = props.get("hostname.source", "")?.toLowerCase()?.trim()

// Retrieve the collector version
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 script netscan. Currently running version ${formattedVer}.")
}

// Bail out early if we don't have the necessary credentials
if (!key) {
    throw new Exception("Must provide credentials to run this script.  Verify necessary credentials have been provided in NetScan properties.")
}

// Bail out early if we don't have the necessary properties
if (!orchestratorHost || !enterpriseId) {
    throw new Exception("Must provide vmware.sdwan.orchestrator.host and vmware.sdwan.enterprise.id to run this script.  Verify necessary properties have been provided in NetScan properties.")
}

def logCacheContext = "vmware-sdwan"
def modLoader = GSH.getInstance()._getScript("Snippets", Snippets.getLoader()).withBinding(getBinding())
def debugSnip = modLoader.load("lm.debug", "1").debugSnippetFactory(out, debug, false, logCacheContext)
def lmEmit = modLoader.load("lm.emit", "1")
def http = modLoader.load("proto.http", "0").httpSnippetFactory(props)
def VMwareSnippet = modLoader.load("vmware.sdwan", "0").vmwareSdwanSnippetFactory(props, debugSnip, http)

// Only initialize lmApi snippet class if customer has not opted out
def lmApi
if (!skipDeviceDedupe) {
    def lmApiSnippet = modLoader.load("lm.api", "0")
    lmApi = lmApiSnippet.lmApiSnippetFactory(props, http, debugSnip)
}

Map sensitiveProps = [
        "vmware.sdwan.key": key,
]

// Get information about devices that already exist in LM portal
List fields = ["name", "currentCollectorId", "displayName", "systemProperties"]
Map args = ["size": 1000, "fields": fields.join(",")]
def lmDevices
// But first determine if the portal size is within a range that allows us to get all devices at once
def pathFlag, portalInfo, timeLimitSec, timeLimitMs
if (!skipDeviceDedupe) {
    portalInfo = lmApi.apiCallInfo("Devices", args)
    timeLimitSec = props.get("lmapi.timelimit.sec", "60").toInteger()
    timeLimitMs = (timeLimitSec) ? Math.min(Math.max(timeLimitSec, 30), 120) * 1000 : 60000
    // Allow range 30-120 sec if configured; default to 60 sec
    if (portalInfo.timeEstimateMs > timeLimitMs) {
        debugSnip.LMDebugPrint("Estimate indicates LM API calls would take longer than time limit configured.  Proceeding with individual queries by display name for each device to add.")
        debugSnip.LMDebugPrint("\t${portalInfo}\n\tNOTE:  Time limit is set to ${timeLimitSec} seconds.  Adjust this limit by setting the property lmapi.timelimit.sec.  Max 120 seconds, min 30 seconds.")
        pathFlag = "ind"
    } else {
        debugSnip.LMDebugPrint("Response time indicates LM API calls will complete in a reasonable time range.  Proceeding to collect info on all devices to cross reference and prevent duplicate device creation.\n\t${portalInfo}")
        pathFlag = "all"
        lmDevices = lmApi.getPortalDevices(args)
    }
}

Map lmDevicesByName
if (pathFlag == "all" && !skipDeviceDedupe) {
    // VMware does not provide individual IP addresses, check dedupe by device name
    lmDevicesByName = lmDevices?.collectEntries { device ->
        def sysName = device?.systemProperties?.find { it -> it?.name == "system.sysname" }?.value
        def displayName = device?.systemProperties?.find { it -> it?.name == "system.displayname" }?.value
        [
                ("${sysName ?: displayName}".toString().toLowerCase()): [
                        "name"              : "${device.name}",
                        "displayName"       : (device.displayName),
                        "currentCollectorId": (device.currentCollectorId),
                        "sysName"           : (sysName)
                ]
        ]
    }
}

// Get your data and build your list of resources
List<Map> resources = []

def now = new Date()
def dateFormat = "yyyy-MM-dd'T'HH:mm:ss.s z"
TimeZone tz = TimeZone.getDefault()
Map duplicateResources = [
        "date"     : now.format(dateFormat, tz),
        "message"  : "Duplicate display names found within LogicMonitor portal wherein hostname in LM does not match hostname in Netscan output.  Refer to documentation for how to resolve name collisions using 'hostname.source' netscan property.",
        "total"    : 0,
        "resources": []
]

def edges = VMwareSnippet.setupRequestV1("enterprise/getEnterpriseEdges", enterpriseId)
edges?.each { device ->
    String hostName = device.name
    String displayName = device.name
    String displayNameToCheck = "${displayName}".toString().toLowerCase()

    // Check for existing device in LM portal with this displayName; set to false initially and update to true when dupe found
    def deviceMatch = false
    //  If customer has opted out of device deduplication checks, we skip the lookups where we determine if a match exists and proceed as false
    if (!skipDeviceDedupe) {
        if (pathFlag == "ind") {
            deviceMatch = lmApi.findPortalDeviceByProperty(hostName, args)
        } else if (pathFlag == "all") {
            deviceMatch = lmDevicesByName?.get(displayNameToCheck)
        }
    }

    if (deviceMatch) {
        // Log duplicates that would cause additional devices to be created; unless these entries are resolved, they will not be added to resources for netscan output
        if (deviceMatch.name.toString().toLowerCase() != displayNameToCheck) {
            def collisionInfo = [
                    (hostName): [
                            "Netscan" : [
                                    "hostname": hostName
                            ],
                            "LM"      : [
                                    "hostname"   : deviceMatch.name,
                                    "collectorId": deviceMatch.currentCollectorId
                            ],
                            "Resolved": false
                    ]
            ]

            // If user specified to use LM hostname on display name match, update hostname variable accordingly
            // and flag it as no longer a match since we have resolved the collision with user's input
            if (hostnameSource == "lm" || hostnameSource == "logicmonitor") {
                hostName = deviceMatch.name
                collectorId = deviceMatch.currentCollectorId
                displayName = deviceMatch.displayName
                deviceMatch = false
                collisionInfo[hostName]["Resolved"] = true
            }
            // If user specified to use netscan data for hostname, update the display name to make it unique
            // and flag it as no longer a match since we have resolved the collision with user's input
            else if (hostnameSource == "netscan") {
                // Update the resolved status before we change the displayName
                collisionInfo[hostName]["Resolved"] = true
                displayName = "${displayName - deviceMatch.name}"
                deviceMatch = false
            }

            duplicateResources["resources"].add(collisionInfo)
        }
        // Don't worry about matches where the hostname values are the same
        // These will update via normal NetScan processing and should be ignored
        else {
            deviceMatch = false
        }
    }

    List groupName = ["${rootFolder}"]

    def deviceProps = [
            "vmware.sdwan.orchestrator.host": orchestratorHost,
            "vmware.sdwan.edge.logical.id"  : device.logicalId,
            "vmware.sdwan.edge.name"        : device.name,
            "vmware.sdwan.serial.number"    : device.serialNumber,
            "vmware.sdwan.enterprise.id"    : enterpriseId
    ]

    deviceProps.putAll(sensitiveProps)

    Map resource = [
            "hostname"   : hostName,    // String
            "displayname": displayName,    // String
            "hostProps"  : deviceProps, // Map<String, String>
            "groupName"  : groupName,   // List<String>
    ]

    // Only add the collectorId field to resource map if we found a collector ID above
    if (collectorId) {
        resource["collectorId"] = collectorId
        if (duplicateResources["resources"][hostName]["Netscan"]) {
            duplicateResources["resources"][hostName]["Netscan"][0]["collectorId"] = collectorId
        }
    }

    if (!deviceMatch) {
        resources.add(resource)
    }
}

// Output validated data in JSON format
// If errors have been made in acceptable/required keys, lm.emit will throw and inform the user
lmEmit.sanitizeResourceSensitiveProperties(resources, debug)
lmEmit.resource(resources)

// Report devices that already exist in LM via log file named after root folder
if (duplicateResources["resources"].size() > 0) {
    def netscanDupLog = new File("../logs/NetscanDuplicates/${rootFolder.replaceAll(" ", "_")}.json")
    new File(netscanDupLog.getParent()).mkdirs()
    duplicateResources["total"] = duplicateResources["resources"].size()
    def json = JsonOutput.prettyPrint(JsonOutput.toJson(duplicateResources))
    netscanDupLog.write(json)
    if (hostnameSource) {
        debugSnip.LMDebug("${duplicateResources["resources"].size()} devices found that were resolved with hostname.source=${hostnameSource} in netscan output.  See LogicMonitor/Agent/logs/NetscanDuplicates/${rootFolder.replaceAll(" ", "_")}.json for details.")
    } else {
        debugSnip.LMDebug("${duplicateResources["resources"].size()} devices found that were not reported in netscan output.  See LogicMonitor/Agent/logs/NetscanDuplicates/${rootFolder.replaceAll(" ", "_")}.json for details.")
    }
}

return 0
  1. In the Schedule section, select Run this NetScan on a schedule. For dynamic environments, you can schedule the NetScan to run as frequently as hourly.
  2. Select Save or Save & Run.

After running the NetScan, review the history for the number of resources added, or for error messages if the NetScan does not create any resources.

Manually Adding Resources

  1. Create an VMware SD-WAN Organization device group. For more information, see Adding Device Groups.
  2. Add the devices to the VMware Organization group.
  3. Add or verify that the following properties are set on the resources or under the VMware SD-WAN Organization Resource Group. For more information on setting properties, see Resource and Instance Properties.
PropertyValue
vmware.sdwan.orchestrator.hostVMware orchestrator respective portal, should be formatted vcoX.velocloud.net.
vmware.sdwan.keyVMware orchestrator API token. For more information, refer to API tokens.
system.sysname OR
vmware.sdwan.edge.name OR
vmware.sdwan.edge.logical.id OR
system.displayname
If system.displayname is set to the device name defined in VMware or SNMP is enabled we will be able to determine the device. In the case that SNMP is not enabled or system.display name is not set to the device name defined in VMware add the edge logical ID or the device name.
vmware.sdwan.enterprise.idVMware enterprise ID.

Importing LogicModules

From the LogicMonitor public repository, import all VMware SD-WAN LogicModules. If these LogicModules are already present, ensure you have the most recent versions.

After the LogicModules are imported (assuming all previous setup requirements have been met), data collection will automatically commence.For more information on importing modules, see LM Exchange.

NetScan Troubleshooting

The NetScan for this suite can update existing devices in the portal to add relevant information retrieved from WMware’s API. The NetScan can also create duplicate devices in the portal when a conflict exists where the display name is the same, but the value for system.hostname differs. To ensure devices are updated properly and duplicate devices are not created, this NetScan uses LogicMonitor’s API to query existing devices and report name conflicts discovered. You can access the file in the collector logs. For more information, see Collector logging – Sending logs to LogicMonitor.

The devices in the report are not reported to LogicMonitor as part of the NetScan output unless the NetScan has been configured with the property hostname.source. This property allows a user to resolve the name conflicts discovered by selecting the source of the hostname that is used in the NetScan output. Two possible hostname sources are determined by the following values:

  • “lm” or “logicmonitor” Devices with name conflicts use the existing system.hostname in LogicMonitor to ensure a device in the portal is updated using the NetScan. This setting does not create a new device.
  • “netscan” Devices with name conflicts maintain system.hostname determined in the NetScan script and update the display name reported to append - <system.hostname> to ensure it is unique and can be added to the portal. This option is beneficial if you do not have strict naming conventions and have multiple devices with the same display name.

Note:Though NetScans are not able to update the value of system.hostname, they may update display name, custom properties, and group assignments.

LogicModules in Package

LogicMonitor’s package for VMware SD-WAN consists of the following LogicModules. For full coverage, ensure all of the following LogicModules are imported into your LogicMonitor platform:

Display NameTypeDescription
VMware_SDWAN_BGP_SessionsDataSourceMonitors the BGP sessions of VMware SD-WAN Edge.
VMware SDWAN HealthDataSourceMonitors the VMware SDWAN Health.
VMware SDWAN InterfacesDataSourceMonitors the WAN interfaces of VMware SD-WAN Edge.
VMware SDWAN PerformanceDataSourceMonitors the VMware SDWAN Performance
VMware SDWAN TunnelsDataSourceMonitors the WAN tunnels of VMware SD-WAN Edge.
VMware_SDWAN_TopologyTopologySourceGenerate VMware SD-WAN topologies.
VMware SDWAN AlertsLogSourceProvides logs for VMware SD-WAN Devices.
addCategory_VMwareSDWANPropertySourceAdds system category VMwareSDWANEdge and auto properties with relevant device information.
addERI_VMware_SDWANPropertySourceDiscover and add ERI’s for VMware SDWAN resources.

When setting static datapoint thresholds on the various metrics tracked by this package’s DataSources, LogicMonitor follows the technology owner’s best practice KPI recommendations.

Recommendation: If necessary, adjust these predefined thresholds to meet the unique needs of your environment. For more information on tuning datapoint thresholds, see Tuning Static Thresholds for Datapoints.

In This Article