ジュニパーミストモニタリング
最終更新日: 17 年 2023 月 XNUMX 日LogicMonitor は、Juniper Mist 組織、サイト、およびデバイスの監視を提供します。 組織の健全性、サイトとサイトによって生成されたトラフィック、および Juniper Mist が管理するデバイス (Juniper Mist アクセス ポイント、EX シリーズ スイッチ、セッション スマート ルーター) を監視するように設計されています。 それは ジュニパーミスト API サイトのデバイス統計とデータを取得します。
Requirements
- コレクター バージョン 32.400 以降
- Juniper Mist ポータルまたは Juniper Mist API を使用した API トークンと Mist 組織 ID。 詳細については、以下の Mist ドキュメントを参照してください。
モニタリングへのリソースの追加
Juniper Mist で管理されたデバイスを追加するための推奨される方法は、 強化されたスクリプト ネットスキャン LogicMonitor が提供するスクリプトを使用します。 拡張スクリプト Netscan は、各 Mist 組織サイトのデバイス グループの下にデバイスを作成し、組織デバイスを追加します。
注意: 編集されたスクリプトはサポートされていません。 デフォルトのスクリプト操作を変更または編集するには、 強化されたスクリプト ネットスキャン プロパティとフィルター。
強化されたスクリプト Netscan を使用した Juniper Mist リソースの追加
を実行すると、Juniper Mist デバイスを自動的に追加できます。 強化されたスクリプト ネットスキャン 次の認証情報と Groovy スクリプトを使用します。
強化されたスクリプト Netscan クレデンシャル
プロパティ | 値 |
ミスト.api.キー | ミスト API トークン |
ミスト.api.org | ミスト組織ID |
ミスト.api.org.名 | Mist Organization デバイスとデバイス グループ フォルダの表示名。 デフォルトはミスト組織です。 |
強化されたスクリプト Netscan Groovy スクリプト
/*******************************************************************************
* © 2007-2023 - 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 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.")
}
String url = netscanProps.get("mist.api.url")?.trim() ?: "https://api.mist.com/api/v1/"
if (!url.endsWith("/")) url += "/"
String organizationDisplayname = netscanProps.get("mist.api.org.name")?.trim() ?: "MistOrganization"
String organizationFolder = netscanProps.get("mist.api.org.folder")?.trim() ? netscanProps.get("mist.api.org.folder") + "/" : ""
def sitesWhitelist = netscanProps.get("mist.api.org.sites")?.tokenize(",")?.collect{ it.trim() }
def collectorSitesCSV = netscanProps.get("mist.api.org.collector.sites.csv")
def collectorSiteInfo
if (collectorSitesCSV) {
collectorSiteInfo = processCollectorSiteInfoCSV(collectorSitesCSV)
}
Map headers = ["Authorization":"token ${token}", "Accept":"application/json"]
List<Map> resources = []
def facility = "siteMetrics"
def organizationSites = httpGet("${url}orgs/${organization}/sites", headers)
if (organizationSites.statusCode == 200 && organizationSites.data) {
if (collectorSiteInfo) {
def seenCollectors = []
collectorSiteInfo.each { collectorSite ->
if (collectorSite.size() == 4) {
def collectorId = collectorSite[0]
if (seenCollectors.contains(collectorId)) return // One org device and folder per collector ID
seenCollectors << collectorId
def name = collectorSite[1]
def folder = collectorSite[2]
def props = ["mist.api.org": organization, "mist.api.key": token, "mist.api.url": url, "system.categories": "JuniperMistOrg",
"mist.api.org.collector.sites": collectorSiteInfo.findAll{ it[0] == collectorId }?.collect{ it[3] }?.toString()?.replace("[", "")?.replace("]", "")]
Map resource = [
"hostname" : "${name}.invalid",
"displayname" : "${name}",
"hostProps" : props,
"groupName" : ["${organizationFolder}${folder}"],
"collectorId" : collectorId
]
resources.add(resource)
}
}
} else {
def props = ["mist.api.org": organization, "mist.api.key": token, "mist.api.url": url, "system.categories": "JuniperMistOrg,NoPing"]
Map resource = [
"hostname" : "${organizationDisplayname}.invalid",
"displayname" : organizationDisplayname,
"hostProps" : props,
"groupName" : ["${organizationFolder}${organizationDisplayname}"]
]
resources.add(resource)
}
organizationSites = organizationSites.data
organizationSites.each { organizationSite ->
def siteName = organizationSite.get("name")
if (sitesWhitelist != null && !sitesWhitelist.contains(siteName)) return
def site = organizationSite.get("id")
def siteDeviceStats = httpGet("${url}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")
def type = siteDeviceStat.get("type")
def props = ["mist.api.org": organization, "mist.api.site": site, "mist.device.type": type, "mist.api.org.site.name": siteName]
if (type == "ap") {
props.put("system.categories", "JuniperMistAP,NoPing")
} else if (type == "switch") {
props.put("system.categories", "JuniperMistSwitch")
} else if (type == "gateway") {
props.put("system.categories", "JuniperMistGateway")
}
if (ip && name && type && siteName) {
if (ip == "127.0.0.1") ip = name
if (collectorSiteInfo) {
def collectorIdEntry = collectorSiteInfo.find{ it.contains(siteName) }
def collectorId = collectorIdEntry[0]
def folder = collectorIdEntry[2]
Map resource = [
"hostname" : ip,
"displayname" : name,
"hostProps" : props,
"groupName" : ["${organizationFolder}${folder}/${siteName}"],
"collectorId" : collectorId
]
resources.add(resource)
} else {
Map resource = [
"hostname" : ip,
"displayname" : name,
"hostProps" : props,
"groupName" : ["${organizationFolder}${organizationDisplayname}/${siteName}"]
]
resources.add(resource)
}
}
}
} else {
throw new Exception("Error occurred during sites/${site}/stats/devices?type=all HTTP GET: ${siteDeviceStats}.")
}
}
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(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
}
// Helper function to process a collector id, site organization device name, folder, and site CSV
def processCollectorSiteInfoCSV(String filename) {
// Ensure relative filepath is complete with extension type
def filepath
if (!filename.contains("./")) {
filepath = "./${filename}"
}
if (!filename.contains(".csv")) {
filepath = "${filename}.csv"
}
// Read file into memory and split into list of lists
def csv = new File(filepath)
def rows = csv.readLines()*.split(",")
def data
// Verify whether headers are present and expected values
// Sanitize for casing and extra whitespaces while gathering headers
def maybeHeaders = rows[0]*.toLowerCase()*.trim()
if (maybeHeaders.contains("collector id") && maybeHeaders.contains("site organization device name") && maybeHeaders.contains("folder") && maybeHeaders.contains("site")) {
// Remove headers from dataset
data = rows[1..-1]
}
// Bail out early if we don't have the expected headers in the provided CSV
else {
throw new Exception(" Required headers not provided in CSV. Please provide \"Collector ID\", \"Site Organization Device Name\", \"Folder\", and \"Site\" (case insensitive). Headers provided: \"${rows[0]}\"")
}
return data
}
Juniper Mist リソースを手動で追加する
次の方法で、Juniper Mist リソースを手動で追加できます。 デバイスグループの追加 および デバイスの追加. 次のプロパティを使用します。
手順 | プロパティ | 値 |
Juniper Mist Organization デバイス グループを作成します。 | ミスト.api.org | 霧の組織ID |
Juniper Mist Organization デバイスを作成します。 | ミスト.api.キー | APIキー |
サイト デバイス グループの下の Mist 組織グループにデバイスを追加します。 | ミスト.api.サイト | ミストサイトID |
注意: 詳細については、Mist のドキュメントを参照してください。 組織 ID とサイト ID を見つける方法?
追加の値と属性
強化された Netscan 値
プロパティ | 値 |
ミスト.api.url | 代替地域の URL には次のものがあります。 https://api.eu.mist.com/api/v1/ https://api.ac2.mist.com/api/v1/ https://api.gc1.mist.com/api/v1/ |
mist.api.org.folder | NetScan によって作成されたルート フォルダ |
mist.api.org.sites | 含めるサイト名をコンマで区切って指定します (その他は除外されます)。 |
ミスト.api.org.collector.sites.csv | ミスト.csv Mist サイトの LM Envision コレクターへのマッピング。 詳細については、次を参照してください。 ミスト サイトを LM Envision コレクターにマッピングする. |
ミスト サイトを LM Envision コレクターにマッピングする
コレクターID | サイト組織のデバイス名 | フォルダ | サイト |
11 | Region_East_Sites | Region_East | NYC |
11 | Region_East_Sites | Region_East | MIA |
11 | Region_East_Sites | Region_East | CLT |
11 | Region_East_Sites | Region_East | ATL |
11 | Region_East_Sites | Region_East | BOS |
11 | Region_East_Sites | Region_East | CHS |
1 | Region_West_Sites | Region_West | LAX |
1 | Region_West_Sites | Region_West | LAS |
1 | Region_West_Sites | Region_West | SEA |
強化されたネットスキャン属性
属性 | 操作 | 値 | コメント |
mist.api.org.site.name | 等しい | 等しくない | ブロックまたは許可するカンマ区切りのサイト名。 例: ATL、BOS、MIA | NotEqual オペレーションは、リストされたサイトをブロックします。 同等の操作には、特定のサイトのみが含まれます。 |
ミスト.デバイス.タイプ | 等しい | 等しくない | インポートまたは除外するカンマ区切りのデバイス タイプ。 例: ap、スイッチ、ゲートウェイ | NotEqual オペレーション ブロックは、デバイス タイプをリストします。 同等の操作には、特定のデバイス タイプのみが含まれます。 |
推奨事項: スクリプト操作を変更するには、NetScan のプロパティとフィルタを使用します。
Juniper Mist LogicModules のインポート
にリストされているすべての Juniper Mist LogicModules をインポートします。 パッケージ内のLogicModules このサポート記事のセクション。 これらの LogicModules が既に存在する場合は、最新バージョンであることを確認してください。 詳細については、次を参照してください。 LogicModuleの更新.
- LogicModules がインポートされた後、PropertySource
addCategory_Juniper_Mist_Organization
自動的に実行され、必要なプロパティが設定されます。 - Mist Organization デバイスが DataSource でデータの収集を開始した後
Juniper_Mist_Org
、すべてのデバイスのデータ収集が開始されます。
トラブルシューティング
- このスイートはに依存しています コレクタースクリプトのキャッシュ Juniper Mist API からデータを継続的に取得して保存し、レート制限の制約を最小限に抑えます。 Mist Organization デバイスでは、継続的なデータ収集が維持されます。
Juniper_Mist_Org
API 応答をコレクター スクリプト キャッシュに書き込むバックグラウンド スレッドを維持する DataSource。addCategory_Juniper_Mist_Organization
このデバイスに適切なカテゴリを設定するには、最初に PropertySource を実行する必要があります。Juniper_Mist_Org
このパッケージ内の他のすべてのモジュールが正常に動作するには、DataSource が正常に実行されている必要があります。 - オンボーディング中に、後でこのパッケージの追加の PropertySources で Active Discovery を手動で実行することをお勧めします。
Juniper_Mist_Org
データの収集を開始して、監視とトポロジ マッピングを促進します。 - データのギャップが見られる場合は、検証します
Juniper_Mist_Org
正常に機能しており、スクリプト キャッシュの正常性をLogicMonitor_Collector_ScriptCache
情報元。
注意: データのプルに使用される API にはレート制限があります。 Mist Organization デバイスで Juniper_Mist_Org をチェックして、API に到達できないか、監視が API レート制限に達しているかどうかを確認します。
パッケージ内のLogicModules
Juniper Mist 用の LogicMonitor のパッケージは、次の LogicModule で構成されています。 完全にカバーするには、これらの LogicModules がすべて LogicMonitor プラットフォームにインストールされていることを確認してください。
表示名 | 種類 | 商品説明 |
ミスト AP ビーコン | データソース | Mist が管理する AP ビーコンの状態とパフォーマンス。 |
ミスト AP ヘルス | データソース | Juniper Mist が管理するワイヤレス アクセス ポイントの正常性と可用性。 |
ミスト AP インターフェイス | データソース | Juniper Mist で管理された AP インターフェイスのステータスとネットワークの送受信統計。 |
ミスト AP パフォーマンス | データソース | Juniper Mist が管理するワイヤレス アクセス ポイントのパフォーマンス。 |
ミスト AP ラジオ | データソース | ミストで管理された AP の無線電力と使用状況。 |
ミストクライアント | データソース | タイプ別の組織の Juniper Mist クライアント。 |
ミスト装置 | データソース | タイプおよび状態別の組織の Juniper Mist デバイス。 |
ミストオーガナイゼーション | データソース | 監視対象の Mist 組織と Mist API 接続の状態。 |
ミストサイト | データソース | クライアント数とネットワーク転送による組織の Juniper Mist サイト。 |
ミストスイッチヘルス | データソース | Mist で管理されたスイッチの状態。 |
ミストスイッチの性能 | データソース | Mist マネージド スイッチのパフォーマンス。 |
ミスト WAN エッジ ヘルス | データソース | Mist で管理されたセッション スマート ルーターの正常性。 |
ミスト WAN エッジ パフォーマンス | データソース | Mist マネージド セッション スマート ルーターのパフォーマンス。 |
addCategory_Juniper_Mist_Device | プロパティソース | ジュニパーミストを追加Juniper Mist によって管理されるデバイスへの system.category データを収集するには、Juniper_Mist_Org DataSource (ウォッチドッグ) が必要です。 |
addCategory_Juniper_Mist_Organization | プロパティソース | Juniper Mist 組織のデータ収集を担当するデバイスに JuniperMistOrg system.category を追加します。 |
addERI_Juniper_Mist_Device | プロパティソース | Juniper Mist デバイスの MAC アドレス ERI とデバイス タイプ ERT を設定します。 データを収集するには、Juniper_Mist_Org DataSource (ウォッチドッグ) が必要です。 |
addERI_Juniper_Mist_Organization | プロパティソース | Juniper Mist 組織の UUID ERI とクラスター ERT を設定します。 |
Juniper_Mist_Topology | トポロジーソース | ミスト オーグのトポロジ。 サイト、および管理対象デバイス。 |
このパッケージの DataSources によって追跡されるさまざまなメトリックに静的なデータポイントのしきい値を設定する場合、LogicMonitor はテクノロジ所有者のベスト プラクティス KPI の推奨事項に従います。 必要に応じて、これらの定義済みのしきい値を調整して、環境固有のニーズに合わせてください。 データポイントのしきい値の調整の詳細については、次を参照してください。 データポイントの静的しきい値の調整.