ジュニパーミストモニタリング
最終更新日: 23 年 2023 月 XNUMX 日LogicMonitor は、Juniper Mist 組織、サイト、およびデバイスの監視を提供します。 組織の健全性、サイト、サイトによって生成されたトラフィック、および Juniper Mist アクセス ポイント、EX シリーズ スイッチ、セッション、スマート ルーターなどの Juniper Mist が管理するデバイスを監視できます。 の ジュニパーミスト API サイトのデバイス統計とデータを取得するために使用されます。
要件
- インストールまたはアップデート Juniper Mist ロジックモジュール.
- Collector バージョン 32.400 以降がインストールされていることを確認します。
- Juniper Mist ポータルまたは Juniper Mist API を使用した API トークンと Mist 組織 ID。 詳細については、次を参照してください。 APIトークンを作成する や 組織 ID とサイト ID を見つけるにはどうすればよいですか?
Juniper Mist リソースの追加
Juniper Mist で管理されたデバイスを追加するための推奨される方法は、 強化されたスクリプト ネットスキャン この記事で LogicMonitor が提供する Groovy スクリプトを使用します。 特定の Juniper Mist 組織に対して、拡張スクリプト Netscan は、Mist 組織と各 Mist サイトのグループ、および関連するグループ内の各 Mist 管理デバイスのリソースを作成します。
- Juniper Mist LogicModule をインストールします。 詳細については、「」を参照してください。 Juniper Mist ロジックモジュール.
- LogicMonitorで、次の場所に移動します リソース > Add > 複数のデバイス > 高度なネットスキャン.
[高度な NetScan の追加] ページが表示されます。 - 入力します お名前 この NetScan に関連付けるたとえば、ジュニパー ミスト。
- 現在地に最も近い コレクタ これにより、NetScan が実行されます。
デフォルトでは、NetScan は NetScan を実行するコレクタに新しいリソースを割り当てます。 後で [プロパティ] でこの動作をオーバーライドし、複数のコレクター グループを使用できます (スケールまたは論理接続用)。 グループ化されていないコレクタまたはフェールオーバーのあるコレクタ グループのいずれかを選択します。
注: Juniper Mist では、Auto-balanced Collector Group (ABCG) はサポートされていません。 ABCG を選択すると、最終的にバランシングによって AP、スイッチ、またはゲートウェイのリソースが、Juniper_Mist_Org DataSource を実行しているコレクターとは別のコレクターに割り当てられます。 この DataSource は、他のすべての Juniper_Mist_* DataSource がデータ収集のために参照するコレクターのスクリプト キャッシュにデータを書き込みます。 同じ Juniper Mist サイトの複数のコレクターで Juniper_Mist_Org デバイスを実行することはサポートされていません。これは、Mist API 呼び出しが増加し、監視のパフォーマンス、スケーラビリティ、および信頼性に悪影響を及ぼす可能性があるためです。
- 選択 強化されたスクリプト NetScan 方法 ドロップダウンリスト。
- ノーザンダイバー社の 強化されたスクリプト セクション、選択 デバイスの資格情報 > このスキャンにカスタム資格情報を使用する.
- 次のプロパティを追加します。 プロパティは、必要な Juniper Mist API クレデンシャルを NetScan に提供し、NetScan がリソースを作成および編成する方法と場所を変更します。
プロパティ | 説明 | 必須? |
ミスト.api.org | 霧の組織ID | ○ |
ミスト.api.キー | ミスト API キー。 NetScan は、このプロパティの値をマスクします。 | ○ |
ミスト.api.org.フォルダー | この NetScan が作成または既存の場合に使用する LogicMonitor リソース グループの名前。 値は、ネストされた子フォルダーにすることができます。 例えば、 フォルダ/フォルダ/フォルダ. | いいえ |
ミスト.api.org.名 | あなたのミストの組織名。 これは、Juniper Mist のルート リソース グループの名前になるか、最上位の子リソース グループの名前になります。 mist.api.org.folder プロパティを使用して代替ルート フォルダーを指定します。 | いいえ |
ミスト.api.org.sites | 含めるサイト名をコンマで区切って指定します (その他は除外されます)。 | いいえ |
ミスト.api.org.collector.sites.csv | デフォルトでは、NetScan によって作成された新しいリソースは、NetScan を実行するコレクタに割り当てられます。 このプロパティは、CSV ファイルを使用してデフォルトの動作をオーバーライドし、Mist サイトと LM コレクターの名前に基づいて、Mist デバイスを目的のコレクターに割り当てます。 CSV ファイルは、NetScan を実行するコレクタに保存する必要があります。 Linux: /usr/local/logicmonitor/agent/bin Windowsの場合: C:\Program Files\LogicMonitor\Agent\bin 詳細については、を参照してください。 ミスト サイトを LM Envision コレクターにマッピングする. | いいえ |
ミスト.api.url | 代替地域の URL には次のものがあります。 https://api.eu.mist.com/api/v1/ https://api.ac2.mist.com/api/v1/ https://api.gc1.mist.com/api/v1/ | いいえ |
- Filters セクションに、次のように入力します。 フィルタリングにより、NetScan が作成するサイトとデバイス タイプを制御できます。
プロパティ | 説明 |
ミスト.api.org.サイト.名 | 含めるまたは除外するサイトのカンマ区切りの名前。 デフォルトでは (このフィルタなし)、NetScan はすべてのサイトをリソース グループとしてインポートします。 スペースを含むサイト名には、スペースの前にバックスラッシュを含める必要があります。 例えば: Site\ For\ APs, SiteD, Site\ 23 |
ミスト.デバイス.タイプ | 操作設定に応じて、NetScan が含めたり除外したりするデバイス タイプのカンマ区切りの名前。 デフォルトでは (このフィルタなし)、NetScan はすべてのデバイス タイプをインポートします。 有効なデバイス タイプは次のとおりです。 ap, スイッチ、 ゲートウェイ. |
- 選択 Groovyスクリプトを埋め込む 次のスクリプトを埋め込みます。
警告: スクリプトを編集しないでください。 編集された拡張スクリプト NetScan はサポートされていません。 LogicMonitor 提供のスクリプトが編集された場合、問題が発生した場合、LogicMonitor サポートは (彼らの裁量で) サポートされているスクリプトで編集内容を上書きするように要求する場合があります。
/*******************************************************************************
* © 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
}
- スケジュール セクション、選択 このNetScanをスケジュールに従って実行する. NetScan は検出にレート制限された Mist API を使用するため、必要な頻度でのみ実行するように NetScan をスケジュールします。 推奨される頻度は週に XNUMX 回です。
注: 以降の NetScan の実行では、Juniper Mist ダッシュボードの変更に基づいて、リソースまたはリソース グループが追加または移動されます。 ただし、NetScan にはリソースを削除する機能がありません。
- 選択 Save or 保存して実行.
注: NetScan を実行した後、追加されたリソースの数、または NetScan がリソースを作成しない場合はエラー メッセージの履歴を確認します。
ジュニパーミストの特性
ジュニパー ミストの追加の属性と値。
プロキシ設定
このスイートのモジュールは、プロキシサーバーを使用したHTTP接続をサポートします。 これは、コレクター設定で構成できます。を参照してください。 HTTPプロキシで使用するためのコレクターの構成、または次のデバイスホストプロパティを使用します。 デバイスホストのプロパティは、プロキシ構成のコレクター設定よりも優先されます。
プロパティ | 値 |
プロキシを有効にします | (オプション) このスイートは、ユーザーが構成した HTTP 呼び出しのコレクター プロキシ設定を使用するように記述されています。 有効にするには、値を に設定してこのデバイス プロパティを追加します。 true。 に設定 false 構成されたコレクタ プロキシ設定の使用をオーバーライドし、プロキシなしで接続します。 |
プロキシ.ホスト | (オプション) コレクターの構成とは異なるプロキシ ホストを構成して接続します。 |
プロキシポート | (オプション) コレクターの構成とは異なるプロキシ ポートを構成して接続します。 |
ミスト サイトを LM Envision コレクターにマッピングする
コレクターID | サイト組織のデバイス名 | フォルダ | サイト |
11 | 地域東部サイト | 地域_東部 | NYC |
11 | 地域東部サイト | 地域_東部 | MIA |
11 | 地域東部サイト | 地域_東部 | CLT |
11 | 地域東部サイト | 地域_東部 | ATL |
11 | 地域東部サイト | 地域_東部 | BOS |
11 | 地域東部サイト | 地域_東部 | CHS |
1 | 地域西サイト | 地域_西部 | LAX |
1 | 地域西サイト | 地域_西部 | LAS |
1 | 地域西サイト | 地域_西部 | SEA |
コレクタ ID、サイト組織のデバイス名、フォルダ、およびサイトのカンマ区切り値。
Collector ID,Site Organization Device Name,Folder,Sites
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
ジュニパーミストのトラブルシューティング
- このスイートはに依存しています コレクタースクリプトのキャッシュ 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 レート制限に達しているかどうかを確認します。
Juniper Mist ロジックモジュール
次の LogicModules をインストールまたは更新します。 詳細については、次を参照してください。 モジュールのインストール.
表示名 | 説明 | |
ミスト 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 | トポロジーソース | ミスト オーグのトポロジ。 サイト、および管理対象デバイス。 |
注: LogicModules がインポートされた後、PropertySource addCategory_Juniper_Mist_Organization
自動的に実行され、必要なプロパティが設定されます。 Mist Organization デバイスが DataSource Juniper_Mist_Org でデータの収集を開始すると、すべてのデバイスのデータ収集が開始されます。