Come join our live training webinar every other Wednesday at 11am PST and hear LogicMonitor experts explain best practices and answer common questions. We understand these are uncertain times, and we are here to help!
LogicMonitor’s out-of-the-box monitoring for Cisco Meraki monitors:
You must enable access to the Meraki Dashboard API. In your Meraki environment, navigate to Organization > Settings > Dashboard API access, and select the Enable access to the Cisco Meraki Dashboard API checkbox.
After enabling the API access, navigate to the My Profile page and generate an API key. The API key will be assigned to a property within LogicMonitor. Be sure to copy the API key in a secure location once it is generated.
See Cisco’s Meraki Dashboard API documentation for instructions on enabling the API access and generating an API key.
SNMP v2c or v3 must be enabled on the Meraki Cloud Controller. See Cisco’s SNMP Overview and Configuration documentation for instructions.
From the Resources page, create a Meraki device group and assign it the following properties:
For more information on creating device groups, see Adding Device Groups.
Add your Cisco Meraki hosts into monitoring. You can either use NetScan to automatically add all relevant devices into monitoring (and auto-assign several required device properties), or you can manually add the devices into monitoring.
You can automatically add all Meraki networks, organizations, and a main api.meraki.com device via an advanced NetScan. For instructions on creating and running an advanced NetScan, see Creating NetScans.
Ensure the following configurations are in place for the NetScan:
Note: Be sure to substitute the INSERT API TOKEN HERE placeholder text located at the top of this script with your actual API token.
INSERT API TOKEN HERE
/******************************************************************************* * © 2007-2021 - LogicMonitor, Inc. All rights reserved. ******************************************************************************/ import com.santaba.agent.util.Settings import groovy.json.JsonOutput import groovy.json.JsonSlurper // Set this variable to meraki API token. def token = "" // Optionally, create a list of organizations (e.g. ["1243","2343","3564","4355"]) // Setting this variable will only discover networks on those orgs. def orgsAllowed = null collectorCache = null try { collectorCache = this.class.classLoader.loadClass("com.santaba.agent.util.script.ScriptCache").getCache() } catch (ClassNotFoundException ignored) {} debug = false orgs = cachedHttpGet(token, "/organizations") orgs.each { org -> def orgId = org.id // Filter in only allowed orgs if it's defined. if (orgsAllowed != null) { if (!orgsAllowed.contains(orgId)) { return } } def orgName = org.name def networks = [] def devices = [] try { networks = cachedHttpGet(token, "/organizations/${orgId}/networks") devices = cachedHttpGet(token, "/organizations/${orgId}/devices") } catch (Exception error) { if (debug) println error.message return } networks.each { network -> def networkId = network.id // Check if this network has any devices and avoid reporting deviceless networks. def networkDevices = devices.findAll{it.networkId == networkId} if (networkDevices.size() == 0) { return } def networkName = network.name def networkTags = network.tags def hostName = "${orgName.replaceAll('\\W', '')}.${networkName.replaceAll('\\W', '')}.invalid" def displayName = "Meraki Network: ${networkName}" println "${hostName}##${displayName}##meraki.org.name=${orgName}##meraki.org.id=${orgId}##meraki.api.key=${token}##meraki.network.id=${networkId}##meraki.network.name=${networkName}##meraki.network.tags=${networkTags}##meraki.network.type=${network.type}" } // Only add orgs if they have networks. if (networks.size() > 0) { println "${orgName.replaceAll('\\W', '')}.invalid##Meraki Org: ${orgName}##meraki.org.name=${orgName}##meraki.org.id=${orgId}##meraki.api.key=${token}" } } println "api.meraki.com##api.meraki.com##meraki.api.key=${token}" return 0 def cachedHttpGet(String token, String endpoint, Map args=[:], String serviceUrl="https://api.meraki.com/api/v0", Integer expirySecs=600) { if (collectorCache) { def cacheKey = "CiscoMeraki.APICache:${token}-${composeUrl(serviceUrl, endpoint, args)}" LMDebugPrint("Trying cache for a recent answer to ${endpoint}") def cachedResponse = collectorCache.get(cacheKey) if (cachedResponse) { LMDebugPrint("Cached response found, returning ${cachedResponse.size()} bytes") return (new JsonSlurper()).parseText(cachedResponse) } else{ LMDebugPrint("No cached response found.") Integer retryCount = 0 while (retryCount < 5) { retryCount++ // We are in the lock, lets get the data, update the cache and if(_cacheAcquireLock(cacheKey, 30)) { try { expirySecs = expirySecs * 1000 // Convert to ms. cachedResponse = httpGet(token, endpoint, args, serviceUrl) collectorCache.set(cacheKey, JsonOutput.toJson(cachedResponse), expirySecs) return cachedResponse } finally { _cacheReleaseLock(cacheKey) } } else { cachedResponse = collectorCache.get(cacheKey) if (cachedResponse) { return (new JsonSlurper()).parseText(cachedResponse) } } sleep(1000) // Wait for the other thread to finish or time out. } } } else { return httpGet(token, endpoint, args, serviceUrl) } } boolean _cacheAcquireLock(String cacheKey, Integer timeOutSecs) { String lockKey = "${cacheKey}:Lock" String lockValue = collectorCache.get(lockKey) def now = (new Date()).getTime() if (lockValue == null) { lockValue = (now + timeOutSecs).toString() collectorCache.set(lockKey, lockValue, (timeOutSecs * 1000)) return true } else { return false } } def _cacheReleaseLock(String cacheKey) { String lockKey = "${cacheKey}:Lock" collectorCache.remove(lockKey) } def httpGet(token, endpoint, Map args=[:], serviceUrl="https://api.meraki.com/api/v0") { def retryCount = 0 while (true) { if (retryCount > 10) throw new Exception("Unable to get ${endpoint} after ${retryCount} retries, giving up") def request = rawHttpGet(token, endpoint, args, serviceUrl) def responseCode = request.getResponseCode() LMDebugPrint("Status code: ${responseCode}") if (responseCode == 200) { String content = request.content.text LMDebugPrint("Content length: ${content.size()} bytes") def payload = new JsonSlurper().parseText(content) String links = request.getHeaderField('link') if (links) { /* Unpack a header in this form: <https://nXXX.meraki.com/api/v1/organizations/00000000/devices?perPage=100&startingAfter=0000-0000-0000>; rel=first, <https://nXXX.meraki.com/api/v1/organizations/00000000/devices?endingBefore=XYZ-XYXX-XYZZ&perPage=100>; rel=prev, <https://nXX.meraki.com/api/v1/organizations/000000000/devices?endingBefore=ZZZZ-ZZZZ-ZZZZ&perPage=100>; rel=last */ links.split(',').each { link -> def rel = link.split(';')[1].split('=')[1] if (rel == 'next') { def nextEndpoint = link.split(';')[0].strip()[1..-2] sleep(200) // Sleep 1/6th of a second to avoid triggering the throttling. payload += httpGet(token, "", [:], nextEndpoint) } } } return payload } else if (responseCode == 429) { def retryIn = request.getHeaderField('Retry-After') if (retryIn == null) retryIn = 5 else retryIn = retryIn.toInteger() LMDebugPrint("Sleeping and retrying in ${retryIn} ms after recieving a 429 response") sleep(retryIn * 1000) } else { throw new Exception("Server return HTTP code ${responseCode} from ${endpoint}") } retryCount++ } } static String composeUrl(String serviceUrl, String endpoint, Map args) { def url = serviceUrl+endpoint if (args) { def encodedArgs = [] args.each{ k,v -> encodedArgs << "${k}=${URLEncoder.encode(v.toString(), "UTF-8")}" } url += "?${encodedArgs.join('&')}" } return url } def rawHttpGet(String token, String endpoint, Map args=[:],serviceUrl="https://api.meraki.com/api/v0") { def headers = ["X-Cisco-Meraki-API-Key": token.toString(), "Accept": "application/json"] url = composeUrl(serviceUrl, endpoint, args) def request = null def proxyInfo = getProxyInfo() if (proxyInfo) { request = url.toURL().openConnection(proxyInfo) } else { request = url.toURL().openConnection() } headers.each { request.addRequestProperty(it.key, it.value)} LMDebugPrint("GET ${url}") return request } def getProxyInfo() { if (Settings.getSetting("proxy.enable").toBoolean()) { return new Proxy(Proxy.Type.HTTP, new InetSocketAddress(Settings.getSetting("proxy.host"), Settings.getSetting("proxy.port")?.toInteger())) } return null } def LMDebugPrint(message) { if (debug) { println(message.toString()) } }
Note: Ensure your SNMP credentials and Meraki key are set as properties on this group before running the NetScan. For more information, see Create a Meraki Device Group
You can manually add your Meraki devices into monitoring. There are three types of Meraki devices that can be added:
When adding these devices, assign them to the device group you created previously. For instructions on manually adding resources into monitoring, see Adding Devices.
The network device reports per-device and per-interface data for a specific network.
The organization device reports per-network device counts and API Usage statistics for a specific organization.
The Meraki API device reports per-network device counts and API Usage statistics for all organizations on the account.
In addition to the SNMP and meraki.api.key properties (which we recommend assigning at the group level, as discussed in the Create a Meraki Device Group section of this support article), the following properties must also be set on the Meraki devices within LogicMonitor.
meraki.api.key
Note: If you added resources into monitoring via NetScan, these properties will already be assigned. If you added resources into monitoring manually, you’ll need to manually set these properties. For instructions on manually setting properties, see Resource and Instance Properties.
*Ensure that you set the X-Cisco-Meraki-API-Key header to your API key when running any API requests from https://developer.cisco.com/meraki/api.
X-Cisco-Meraki-API-Key
Some Meraki configurations can encompass multiple organizations with different SNMP credentials. In such instances, resources belonging to an organization can be grouped by creating a dynamic group for each organization and assigning the specific properties there.
From the LogicMonitor public repository, import all Cisco Meraki LogicModules, which are 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 suite of Meraki DataSources will automatically begin collecting data.
In June of 2020, LogicMonitor released a new suite of Cisco Meraki DataSources. The new DataSources offer several advantages, including expanded monitoring coverage and improved efficiency for future scalability and support.
The release of these new DataSources serves to deprecate the following legacy Meraki DataSources:
If you are currently monitoring Meraki devices using any of these legacy DataSources, you will not experience data loss upon importing the new DataSources. This is because DataSource names have been changed to eliminate module overwriting.
However, you will collect duplicate data and receive duplicate alerts for as long as both sets of DataSources are active. For this reason, we recommend that you disable the above-listed DataSources after importing the new set of DataSources and confirming that they are working as intended in your environment.
When a DataSource is disabled, it stops querying the host and generating alerts, but maintains all historical data. At some point in time, you may want to delete the legacy DataSources altogether, but consider this move carefully as all historical data will be lost upon deletion. For more information on disabling DataSources, see Disabling Monitoring for a DataSource or Instance.
LogicMonitor’s package for Cisco Meraki consists of the following LogicModules. For full coverage, please ensure that all of these LogicModules are imported into your LogicMonitor platform.
system.categories
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