Juniper Mist Monitoring

Last updated on 03 November, 2022

LogicMonitor offers out-of-the-box monitoring for Juniper Mist organizations, sites, and devices. It is designed to monitor organization health, sites and traffic generated by the sites, Juniper Mist-managed devices (including Juniper Mist Access Points, EX-Series Switches, Session Smart Routers, and the status of these devices), and uses the Juniper Mist API to retrieve site device stats and data.

Requirements

Adding Resources into Monitoring

You can add Juniper Mist resources by running a NetScan or you can add them manually. We recommend running a NetScan. NetScan creates devices under device groups for each Mist organization site, and adds an organization device. For more information, see What is NetScan.

You can automatically add devices using an Advanced NetScan; specifically, an Enhanced Script Netscan. For more information, see Enhanced Script Netscan. Ensure the following configurations are in place:

  1. Under Device Credentials > Use custom credentials for this scan, use the following properties: 
PropertyValue
mist.api.keyMist API token
mist.api.orgMist Organization ID
mist.api.org.nameDesired display name of your Mist Organization device and device group folder, defaults to Mist Organization
  1. When configuring the Enhanced Script Netscan, use Embed Groovy Script and use the following script:
/*******************************************************************************
 * © 2007-2022 - LogicMonitor, Inc. All rights reserved.
 ******************************************************************************/
 
import com.logicmonitor.common.sse.utils.GroovyScriptHelper as GSH
import com.logicmonitor.mod.Snippets
import com.santaba.agent.util.Settings
import com.santaba.agent.AgentVersion
import groovy.json.JsonSlurper
import java.text.DecimalFormat
import javax.net.ssl.HttpsURLConnection

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}.")
}

modLoader = GSH.getInstance()._getScript("Snippets", Snippets.getLoader()).withBinding(getBinding())
def emit = modLoader.load("lm.emit", "0")

String organizationDisplayname = netscanProps.get("mist.api.org.name") ?: "Mist Organization"
String organization = netscanProps.get("mist.api.org")
String token = netscanProps.get("mist.api.key")
if (!organization) {
    throw new Exception("Must provide mist.api.org to run this script.  Verify necessary credentials have been provided in Netscan properties.")
}
if (!token) {
    throw new Exception("Must provide mist.api.key credentials to run this script.  Verify necessary credentials have been provided in Netscan properties.")
}

Map headers = ["Authorization":"token ${token}", "Accept":"application/json"]

List<Map> resources = []

def facility = "siteMetrics"
def organizationSites = httpGet("orgs/${organization}/sites", headers)
if (organizationSites.statusCode == 200 && organizationSites.data) {
    organizationSites = organizationSites.data
    organizationSites.each { organizationSite ->
        def site = organizationSite.get("id")
        def siteName = organizationSite.get("name")        
        def siteDeviceStats = httpGet("sites/${site}/stats/devices?type=all", headers)
        if (siteDeviceStats.statusCode == 200) {
            siteDeviceStats = siteDeviceStats.data
            siteDeviceStats.each { siteDeviceStat ->
                def ip = siteDeviceStat.get("ip")
                def name = siteDeviceStat.get("name")
                if (ip && name && siteName) {
                    Map resource = [
                        "hostname"    : ip,
                        "displayname" : name,
                        "hostProps"   : ["mist.api.org": organization, "mist.api.site": site],
                        "groupName"   : ["${organizationDisplayname}/${siteName}"]
                    ]
                    resources.add(resource)
                }
            }
        } else {
            throw new Exception("Error occurred during sites/${site}/stats/devices?type=all HTTP GET: ${siteDeviceStats}.")
        }
    }
    Map resource = [
        "hostname"    : "${organizationDisplayname}.invalid",
        "displayname" : organizationDisplayname,
        "hostProps"   : ["mist.api.org": organization, "mist.api.key": token],
        "groupName"   : ["${organizationDisplayname}"]
    ]
    resources.add(resource)
    emit.resource(resources)
} else {
    throw new Exception("Error occurred during orgs/${organization}/sites HTTP GET: ${organizationSites}.")
}

return 0

def httpGet(String endpoint, Map headers, Integer retryLen = 5) {
    Random random = new Random()
    Integer waitPeriod = 5000 + Math.round((3000 * random.nextDouble())) // adding randomness to wait time
    Double waitTime = 0
    Map returnItem = [:]
    Integer retryCount = 0
    ArrayList validRetries = [429, 500]
    HttpsURLConnection response = null
    while (retryCount <= retryLen) {
        retryCount++
        response = rawHttpGet("https://api.mist.com/api/v1/${endpoint}", headers)
        returnItem["statusCode"] = response.getResponseCode()
        if (!validRetries.contains(returnItem["statusCode"])) {
            if (returnItem["statusCode"] == 200) {
                returnItem["rawData"] = response.inputStream.text
                returnItem["data"] = new JsonSlurper().parseText(returnItem["rawData"])
                sleep(200)
            } else {
                returnItem["data"] = null
            }
            returnItem["waitTime"] = waitTime
            return returnItem
        }
        sleep(waitPeriod)
        waitTime = waitTime + waitPeriod
    }
    returnItem["statusCode"] = -1  // unknown status code
    returnItem["data"] = null
    returnItem["waitTime"] = waitTime
    returnItem["rawData"] = null
    returnItem["errMsg"] = response.getErrorStream()
    return returnItem
}

def rawHttpGet(String url, Map headers) {
    HttpsURLConnection request = null
    Map proxyInfo = getProxyInfo()
    if (proxyInfo){
        request = url.toURL().openConnection(proxyInfo.proxy)
    } else {
        request = url.toURL().openConnection()
    }
    headers.each { request.addRequestProperty(it.key, it.value) }
    return request
}

Map getProxyInfo() {
    Boolean deviceProxy    =  this.netscanProps.get("proxy.enable")?.toBoolean() ?: true  // default to true in absence of property to use collectorProxy as determinant
    Boolean collectorProxy = Settings.getSetting("proxy.enable")?.toBoolean() ?: false // if settings are not present, value should be false
    Map proxyInfo = [:]

    if (deviceProxy && collectorProxy) {
        proxyInfo = [
            enabled : true,
            host : this.netscanProps.get("proxy.host") ?: Settings.getSetting("proxy.host"),
            port : this.netscanProps.get("proxy.port") ?: Settings.getSetting("proxy.port") ?: 3128,
            user : Settings.getSetting("proxy.user"),
            pass : Settings.getSetting("proxy.pass")
        ]

        proxyInfo["proxy"] = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyInfo.host, proxyInfo.port.toInteger()))
    }

    return proxyInfo
}

Manual Setup

We recommend using the Enhanced Script Netscan, however, you can add Juniper Mist resources manually.

  1. Create a Juniper Mist Organization device group. For more information, see Adding Device Groups.
    • Property: mist.api.org
    • Value: Mist Organization ID
  2. Create a Juniper Mist Organization device. For more information, see Adding Devices.
    • Property: mist.api.key
    • Value: API key
  3. Add the devices to the Mist Organization device group under a Site device group with the following property added. For more information, refer to the Mist documentation for How do I find my Org and Site ID?
    • Property: mist.api.site
    • Value: Mist Site ID

Import the Juniper Mist LogicModules

Import all Juniper Mist LogicModules listed in the LogicModules in Package section of this support article. If these LogicModules are already present, ensure you have the most recent versions.  

Once the LogicModules are imported (assuming all previous setup requirements have been met), the addCategory_Juniper_Mist_Organization PropertySource must run to set the required properties to begin monitoring. Data collection will automatically begin for all devices after the Mist Organization device has started collecting data successfully in the Juniper_Mist_Org DataSource. For more information on importing modules, see LM Exchange.

Troubleshooting

  • This suite relies on Collector Script Caching to continuously retrieve and store data from the Juniper Mist API to minimize rate limiting constraints. Continuous data collection is maintained on the Mist Organization device through the Juniper_Mist_Org DataSource, which maintains a background thread that writes API responses to the collector script cache. addCategory_Juniper_Mist_Organization PropertySource must run first to set the proper category on this device. Juniper_Mist_Org DataSource must be running successfully for all other modules in this package to be successful.
  • During onboarding, you may wish to run Active Discovery manually on additional PropertySources in this package after Juniper_Mist_Org begins collecting data to expedite monitoring and topology mapping.
  • If data gaps are seen, verify Juniper_Mist_Org is functioning successfully and check script cache health in the LogicMonitor_Collector_ScriptCache DataSource.

Note: The API used to pull data has rate limits. Check Juniper_Mist_Org on your Mist Organization device in order to check if the API is unreachable or monitoring has hit the API rate limit.

LogicModules in Package

LogicMonitor’s package for Juniper Mist consists of the following LogicModules. For full coverage, please ensure that all of these LogicModules are installed on your LogicMonitor platform.

Display NameTypeDescription
Mist AP BeaconsDataSourceHealth and performance of Mist-managed AP Beacons.
Mist AP HealthDataSourceHealth and availability of Juniper Mist-managed Wireless Access Points.
Mist AP InterfacesDataSourceStatus and network transmission and receive statistics for Juniper Mist-managed AP interfaces.
Mist AP PerformanceDataSourcePerformance of Juniper Mist-managed Wireless Access Points.
Mist AP RadiosDataSourceMist-managed AP Radio power and usage.
Mist ClientsDataSourceAn Org’s Juniper Mist Clients by Type.
Mist DevicesDataSourceAn Org’s Juniper Mist Devices by Type and state.
Mist OrganizationDataSourceHealth of the monitored Mist Organization and the Mist API connection.
Mist SitesDataSourceAn Org’s Juniper Mist Sites by Client count and network transmission.
Mist Switch PerformanceDataSourceHealth and Performance of Mist-managed Switches.
Mist WAN Edge PerformanceDataSourceHealth and Performance of Mist-managed Session Smart Routers.
addCategory_Juniper_Mist_DevicePropertySourceAdds the JuniperMist<device type> system.category to devices managed by Juniper Mist Requires Juniper_Mist_Org DataSource (watchdog) in order to collect data.
addCategory_Juniper_Mist_OrganizationPropertySourceAdds the JuniperMistOrg system.category to devices responsible for collecting data for the Juniper Mist organization.
addERI_Juniper_Mist_DevicePropertySourceSets Juniper Mist device MAC address ERI and device type ERT. Requires Juniper_Mist_Org DataSource (watchdog) in order to collect data.
addERI_Juniper_Mist_OrganizationPropertySourceSets Juniper Mist organization UUID ERI and Cluster ERT.
Juniper_Mist_TopologyTopologySourceTopology of a Mist Org. Sites, and managed-devices.

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. If necessary, we encourage you to 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