ジュニパーミストモニタリング

最終更新日: 17 年 2023 月 XNUMX 日

LogicMonitor は、Juniper Mist 組織、サイト、およびデバイスの監視を提供します。 組織の健全性、サイトとサイトによって生成されたトラフィック、および Juniper Mist が管理するデバイス (Juniper Mist アクセス ポイント、EX シリーズ スイッチ、セッション スマート ルーター) を監視するように設計されています。 それは ジュニパーミスト API サイトのデバイス統計とデータを取得します。

Requirements

モニタリングへのリソースの追加

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.folderNetScan によって作成されたルート フォルダ
mist.api.org.sites含めるサイト名をコンマで区切って指定します (その他は除外されます)。
ミスト.api.org.collector.sites.csvミスト.csv
Mist サイトの LM Envision コレクターへのマッピング。 詳細については、次を参照してください。 ミスト サイトを LM Envision コレクターにマッピングする.

ミスト サイトを LM Envision コレクターにマッピングする

コレクターIDサイト組織のデバイス名フォルダサイト
11Region_East_SitesRegion_EastNYC
11Region_East_SitesRegion_EastMIA
11Region_East_SitesRegion_EastCLT
11Region_East_SitesRegion_EastATL
11Region_East_SitesRegion_EastBOS
11Region_East_SitesRegion_EastCHS
1Region_West_SitesRegion_WestLAX
1Region_West_SitesRegion_WestLAS
1Region_West_SitesRegion_WestSEA

強化されたネットスキャン属性

属性操作コメント
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 の推奨事項に従います。 必要に応じて、これらの定義済みのしきい値を調整して、環境固有のニーズに合わせてください。 データポイントのしきい値の調整の詳細については、次を参照してください。 データポイントの静的しきい値の調整.

記事上で