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

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

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

要件

Juniper Mist リソースの追加

Juniper Mist で管理されたデバイスを追加するための推奨される方法は、 強化されたスクリプト ネットスキャン この記事で LogicMonitor が提供する Groovy スクリプトを使用します。 特定の Juniper Mist 組織に対して、拡張スクリプト Netscan は、Mist 組織と各 Mist サイトのグループ、および関連するグループ内の各 Mist 管理デバイスのリソースを作成します。

  1. Juniper Mist LogicModule をインストールします。 詳細については、「」を参照してください。 Juniper Mist ロジックモジュール.
  2. LogicMonitorで、次の場所に移動します 関連情報 > Add > 複数のデバイス > 高度なネットスキャン.
    [高度な NetScan の追加] ページが表示されます。
  3. 入力します お名前 この NetScan に関連付けるたとえば、ジュニパー ミスト。
  4. 現在地に最も近い コレクタ これにより、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 呼び出しが増加し、監視のパフォーマンス、スケーラビリティ、および信頼性に悪影響を及ぼす可能性があるためです。  

  1. 選択 強化されたスクリプト NetScan 方法 ドロップダウンリスト。
  2. ノーザンダイバー社の 強化されたスクリプト セクション、選択 デバイスの資格情報 > このスキャンにカスタム資格情報を使用する.
  3. 次のプロパティを追加します。 プロパティは、必要な 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/
いいえ
  1. フィルタ セクションに、次のように入力します。 フィルタリングにより、NetScan が作成するサイトとデバイス タイプを制御できます。
プロパティ説明
ミスト.api.org.サイト.名含めるまたは除外するサイトのカンマ区切りの名前。 デフォルトでは (このフィルタなし)、NetScan はすべてのサイトをリソース グループとしてインポートします。 スペースを含むサイト名には、スペースの前にバックスラッシュを含める必要があります。 例えば: Site\ For\ APs, SiteD, Site\ 23
ミスト.デバイス.タイプ操作設定に応じて、NetScan が含めたり除外したりするデバイス タイプのカンマ区切りの名前。 デフォルトでは (このフィルタなし)、NetScan はすべてのデバイス タイプをインポートします。 有効なデバイス タイプは次のとおりです。 ap, スイッチ, ゲートウェイ.
  1. 選択 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
}
  1.   スケジュール セクション、選択 このNetScanをスケジュールに従って実行する. NetScan は検出にレート制限された Mist API を使用するため、必要な頻度でのみ実行するように NetScan をスケジュールします。 推奨される頻度は週に XNUMX 回です。

注: 以降の NetScan の実行では、Juniper Mist ダッシュボードの変更に基づいて、リソースまたはリソース グループが追加または移動されます。 ただし、NetScan にはリソースを削除する機能がありません。

  1. 選択 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 でデータの収集を開始すると、すべてのデバイスのデータ収集が開始されます。

記事上で