Meraki APIで無線APにPingを打たせて結果をMackerelに投稿してみる

これは [Mackerel Advent Calendar 2022](https://qiita.com/advent-calendar/2022/mackerel) の6日目の記事です。

1年ほど前から、GoでMackerelのカスタムプラグインを作ることにハマっているものです。

私はネットワークエンジニアであり、ソフトウェアエンジニアとしては未熟者なのですが、以前からこのようなアドベントカレンダーに投稿することに憧れを抱いておりました。

そこで私なりのテーマを考え、今回、好きなネットワーク製品であるMerakiと好きな監視サービスであるMackerel、この2つの好きなサービスを連携させる記事を書いてみようと思いました。

## Merakiとは
無線LAN製品を中心とした、クラウド管理型のNW機器製品及びサービスで、2012年にCiscoに買収され[Cisco Meraki](https://meraki.cisco.com/ja-jp/)というブランドになっています。
従来型のNW機器と異なり、NW機器を全てクラウドで管理するという革新的なコンセプトを世に広めたサービスです。

## MerakiのAPI
Merakiはクラウドサービスということもあって、[API](https://developer.cisco.com/meraki/api-latest/)が用意されております。
クラウドダッシュボードの情報取得や無線APの設定など色々なことが出来ますが、
最近機能追加されたと思われる、「無線APにPingを打たせてその結果を取得する」というAPIがあったので、これをMackerelのサービスメトリックとして投稿して可視化してみようと思います。

## MerakiのLive Tool機能
「無線APにPingを打たせてその結果を取得する」という機能はMerakiのクラウドダッシュボード上の機能として以前から存在し、NWのトラブルシューティング用途としてよく利用されるものです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/477254/b1b98322-fef9-5dcd-e426-a8a0a1ecaa37.png)

## Pingの実行予約と結果の取得
[APIリファレンス](https://developer.cisco.com/meraki/api-latest/#!get-device-live-tools-ping)を見ると、
①Pingの実行予約とPing実行ID取得(POST)
②Ping実行結果の取得(GET)
という2つのAPIを実行する仕組みのようです。

イメージは以下の通りと理解しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/477254/8b26c7e7-f8ed-51be-0c1a-ebc6498b3eb8.png)

## Pingを予約
APIリファレンスにはMeraki Pythonライブラリ用のテンプレートがあり、こちらを使うと簡単に試せるので、今回はPythonを使います。
```ping-create.py
import meraki
import json

API_KEY = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
dashboard = meraki.DashboardAPI(
    API_KEY,
    print_console=False, #コンソールログ出力抑制
    output_log=False, #ログ出力抑制
)

serial = 'xxxx-xxxx-xxxx' # 無線APのシリアル
target = '1.1.1.1'        # Pingターゲット

response = dashboard.devices.createDeviceLiveToolsPing(
    serial, target, 
    count=3 #3回実施
)

print(json.dumps(response, indent=2))
```

API実行結果
```
{
  "pingId": "602356450276192497", 
  "url": "/devices/xxxx-xxxx-xxxx/liveTools/ping/602356450276192497",
  "request": {
    "serial": "xxxx-xxxx-xxxx",
    "target": "1.1.1.1",
    "count": "3"
  },
  "status": "new"
}
```
pingId 602356450276192497としてPing Jobが予約されました。

## Ping結果を取得
少し待ったあと、pingIdを用いてPing結果を取得してみます。
```ping-get.py
import meraki
import json

API_KEY = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
dashboard = meraki.DashboardAPI(
    API_KEY,
    print_console=False, #コンソールログ出力抑制
    output_log=False, #ログ出力抑制
)

serial = 'xxxx-xxxx-xxxx'  # 無線APのシリアル
id_ = '602356450276192497' # ping-create.pyで取得したpingId

response = dashboard.devices.getDeviceLiveToolsPing(
    serial, id_
)

print(json.dumps(response, indent=2))
```
API実行結果。Ping送信回数と応答回数、応答遅延(最小/最大/平均)が返ってきました。
```
{
  "pingId": "602356450276192497",
  "url": "/devices/xxxx-xxxx-xxxx/liveTools/ping/602356450276192497",
  "request": {
    "serial": "xxxx-xxxx-xxxx",
    "target": "1.1.1.1",
    "count": "3"
  },
  "status": "complete",
  "results": {
    "sent": 3,
    "received": 3,
    "loss": {
      "percentage": 0
    },
    "latencies": {
      "minimum": 3.03,
      "average": 3.18,
      "maximum": 3.31
    },
    "replies": [
      {
        "sequenceId": 0,
        "size": 64,
        "latency": 3.31
      },
      {
        "sequenceId": 1,
        "size": 64,
        "latency": 3.03
      },
      {
        "sequenceId": 2,
        "size": 64,
        "latency": 3.22
      }
    ]
  }
}
```

## Mackerelへ投稿
上記のPing予約とPing結果を続けて実行し、その結果をMackerelのサービスメトリックに投稿してみます。ターゲットはwww.google.comです。
```ping_get_and_mackerelpost.py
import requests
import json
import time
import meraki
import sys

t = int(time.time())

# mackerel post用list
postdata = []

# Mackerel API POST用関数
def MackerelPost(postdata):
    headers = {
    "Content-Type": "application/json",
    "X-Api-Key": "xxxxxxxxxxxxxxxxxxxx" # Mackerel API KEY
    }
    json_data = json.dumps(postdata).encode("utf-8")
    response = requests.post("https://api.mackerelio.com/api/v0/services/Meraki/tsdb", headers=headers, data=json_data)
    print(response.text)


MERAKI_API_KEY = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

dashboard = meraki.DashboardAPI(
    MERAKI_API_KEY,
    print_console=False,
    output_log=False,
)

apname = "MerakiAP1"
serial = "xxxx-xxxx-xxxx" # 無線APのシリアル
target = "www.google.com" # Pingターゲット

# Ping Jobをリクエスト
try:
    response_ping_create = dashboard.devices.createDeviceLiveToolsPing(
        serial, target, 
        count=3
    )
    # 応答のあったpingIdを取得
    ping_id = response_ping_create["pingId"]

except meraki.APIError as e:
    print("リクエストエラー",e.status, e.reason)
    sys.exit(1)

# Pingが完了するまで50秒待ってみる
time.sleep(50)

# 実行されたPing結果をリクエスト
try:
    response_ping_get = dashboard.devices.getDeviceLiveToolsPing(
        serial, ping_id
    )

    status = response_ping_get["status"]

    # Pingリクエストが成功していたらデータを取得
    if status == "complete":
        # Ping送信数と成功数を取得
        sent = response_ping_get["results"]["sent"]
        received = response_ping_get["results"]["received"]
        loss = sent - received

        # Ping統計を取得
        min = response_ping_get["results"]["latencies"]["minimum"]
        avg = response_ping_get["results"]["latencies"]["average"]
        max = response_ping_get["results"]["latencies"]["maximum"]

        # メトリック用辞書作成
        d_min = {"name":"PingRttFrom_" + apname +"_To_" + target + ".min_msec","time":t,"value":min}
        d_avg = {"name":"PingRttFrom_" + apname +"_To_" + target + ".avg_msec","time":t,"value":avg}
        d_max = {"name":"PingRttFrom_" + apname +"_To_" + target + ".max_msec","time":t,"value":max}
        
        d_received = {"name":"PingFrom_" + apname +"_To_" + target + ".Received","time":t,"value":received}
        d_loss = {"name":"PingFrom_" + apname +"_To_" + target + ".Loss","time":t,"value":loss}

        # POST用リストに格納
        postdata += [d_min,d_avg,d_max,d_received,d_loss]

        # Mackerel API POST実行
        MackerelPost(postdata)
    else:
        print("Ping Status",status)

except meraki.APIError as e:
    print("Ping結果取得エラー",e.status, e.reason)
```
Mackerelの応答
```
{"success":true}
```

## Mackerel側で見てみる
複数回繰り返した結果、サービスメトリックとして投稿され、可視化できました!

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/477254/2baadd2c-be34-153f-6a19-d0e25d491e3f.png"　 width="300"><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/477254/ce3070d2-d210-8971-0557-83d50ba309c0.png" width="300">

無線AP設置拠点視点のNW調査、定点観測などに使用したら面白そうです。

※Ping予約からPing結果取得可能までの間の時間は、最初20秒や30秒で試していましたが、たまにReadyと返ってきて失敗することがありました。実行までの時間は十分とった方が良さそうです。