zabbixのマップをapiから作ってみる

Zabbixで監視を自動化したりしているのですが、マップも監視対象が追加された時点で自動的に作成できれば便利だなと思ったのでZabbix API経由からマップを作成する手順について簡単にですがまとめてみます。

# 環境

## サーバ情報

| 項目   | 内容・バージョン |
|:-------|:-----------------|
| OS     | CentOS 7.4       |
| Zabbix | 3.0.13-2         |

## 開発情報

| 項目   | 内容・バージョン |
|:-------|:-----------------|
| Python | 3.4.4            |

# Zabbix Map APIドキュメント

https://www.zabbix.com/documentation/3.0/manual/api/reference/map

# テンプレート

今回は、以下のテンプレートを元に処理を書いていきます。

```python
#!/usr/bin/env python3
from collections import defaultdict
import requests
import json
import atexit

class zbxapi:
    """
    Zabbix APIを処理するクラス
    """
    def __init__(self):
        self.url = ''
        self.user = ''
        self.passwd = ''
        self.token = ''

    def login(self):
        """
        ログインメソッド
        ログインに成功したらTokenを取得する

        :rtype: str
        :return: Zabbix API Auth Token
        """
        params = multi_dimension_dict(1)
        params['user'] = self.user
        params['password'] = self.passwd

        # Tokenを取得
        r = self.post('user.login', params)
        self.token = json.loads(r)['result']

    def logout(self):
        """
        ログアウトメソッド

        :rtype: str
        :return: ログアウトの成功(True)・失敗(False)
        """
        params = {}
        r = self.post('user.logout', params)
        return json.loads(r)['result']

    def get(self, method, params):
        """
        Zabbixから情報を取得するメソッド

        :type method: str
        :param method: Zabbix APIメソッド

        :type params: dict
        :param params: 取得したい情報のパラメーター

        :rtype: json
        :return: レスポンスボディ
        """
        data = multi_dimension_dict(1)
        data['jsonrpc'] = '2.0'
        data['method'] = method
        data['params'] = params
        data['id'] = 1
        data['auth'] = self.token

        r = requests.get(self.url,
                         data=json.dumps(data),
                         headers=get_headers(),
                         verify=False)

        if (r.status_code == 200):
            return r.text

    def post(self, method, params):
        """
        Zabbixへ情報を送るメソッド

        :type method: str
        :param method: Zabbix APIメソッド

        :type params: dict
        :param params: 送信する情報のパラメーター

        :rtype: json
        :return: レスポンスボディ
        """
        data = multi_dimension_dict(1)
        data['jsonrpc'] = '2.0'
        data['method'] = method
        data['params'] = params
        data['id'] = 1
        if(self.token): data['auth'] = self.token

        r = requests.post(self.url,
                          data=json.dumps(data),
                          headers=get_headers(),
                          verify=False)

        if(r.status_code == 200):
            return(r.text)

def multi_dimension_dict(dimension, callable_obj=int):
    """
    pythonで多次元連想配列を使う関数
    参照元: http://materia.jp/blog/20121119.html
    """
    nodes = defaultdict(callable_obj)
    for i in range(dimension-1):
        p = nodes.copy()
        nodes = defaultdict(lambda : defaultdict(p.default_factory))
    return nodes

def get_headers():
    """
    ヘッダー情報

    :rtype: dict
    :return: Zabbix APIを実行するためのヘッダー
    """
    headers = {
        'Content-Type':'application/json-rpc'
    }
    return headers

if __name__ == "__main__":
    # Login
    zapi = zbxapi()
    zapi.url = 'http://IP or FQDN/zabbix/api_jsonrpc.php'
    zapi.user = 'admin'
    zapi.passwd = 'zabbix'
    zapi.login()
    atexit.register(zapi.logout)

    # ここから処理を書いていく
```

以下からは `# ここから処理を書いていく` 以降を書いていきます。

# マップ自動生成をやってみた

## マップ作成編

まずは、シンプルなマップを作ってみます。

```python
    # ここから処理を書いていく
    # Create Map.
    params = {}
    params['name'] = "Map"
    params['width'] = 600
    params['height'] = 600
    r = zapi.post('map.create', params)
    print(json.dumps(json.loads(r), indent=2))
```

| パラメーター | 説明               |
|:-------------|:-------------------|
| name         | マップの名前       |
| width        | マップ幅のサイズ   |
| height       | マップ高さのサイズ |

実行します。
以下はレスポンスボディです。

```json
{
  "jsonrpc": "2.0",
  "result": {
    "sysmapids": [
      "12"
    ]
  },
  "id": 1
}
```

マップが作成されているか確認します。

![](https://sky-joker.tech/wp-content/uploads/2017/11/7301317f70235010225af623ed6750e4.png)

## マップ作成と同時にアイコンを追加

マップ作成と作成したマップにアイコンを追加する処理を一度にやってみようと思います。

```python
    # ここから処理を書いていく
    # Element.
    element = {}
    element['elementid'] = 10105
    element['selementid'] = 1
    element['elementtype'] = 0
    element['iconid_off'] = 101

    # Create Map.
    params = {}
    params['name'] = "Map"
    params['width'] = 600
    params['height'] = 600
    params['selements'] = [element]
    r = zapi.post('map.create', params)
    print(json.dumps(json.loads(r), indent=2))
```

| パラメーター | 説明                                                                         |
|:-------------|:-----------------------------------------------------------------------------|
| elementid    | エレメント(アイコン)に紐づけるID(ホストやグループ)                           |
| selementid   | マップ内で判別するエレメントのID、これを元にエレメント同士を紐付けたりします |
| elementtype  | マップエレメントのタイプ、エレメントはhostなのかmapなのかなど                |
| iconid_off   | アイコンの種類                                                               |
| selements    | エレメントを配列で渡す                                                       |

注意点としては、アイコンに紐づけるホストなどがZabbixに登録されている必要があります。
`elementid` は省略できません。
ここでは `example01` というホストを手動で登録してURLから `hostid` を取得しています。
~~決して自動で取得する処理を書くのがめんどくさかった訳では...~~
ただし `elementtype` が `4(イメージ)` の場合は `elementid` が実在していないものでも登録出来ちゃいます。

実行するとMapにアイコンが出来ています。

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "sysmapids": [
      "13"
    ]
  }
}
```

![](https://sky-joker.tech/wp-content/uploads/2017/11/006157549f0f33b15fdd01e443173355.png)

### アイコンの種類について

参考までに `iconid_off` の情報については以下に保存してあります。
`imageid` を `iconid_off` に指定します。
https://gist.github.com/sky-joker/727f319e0f0601f2b08004de05ce0f17

ちなみに取得の仕方は以下のようにします。

```python
    # ここから処理を書いていく
    params = {}
    params['output'] = ["imageid", "name"]
    r = zapi.get('image.get', params)
    print(json.dumps(json.loads(r), indent=2))
```

## アイコンの座標を指定する

座標を指定しない場合は、全てのアイコンは画面左上に出来てしまうので座標を指定してみます。

```python
    # ここから処理を書いていく
    # Element.
    element = {}
    element['elementid'] = 10105
    element['selementid'] = 1
    element['elementtype'] = 0
    element['iconid_off'] = 101
    element['x'] = 100
    element['y'] = 100
    (snip)
```

| パラメーター | 説明                        |
|:-------------|:----------------------------|
| x            | アイコンのx座標を指定します |
| y            | アイコンのy座標を指定します |

実行するとアイコンが指定した座標に配置されています。

```json
{
  "jsonrpc": "2.0",
  "result": {
    "sysmapids": [
      "14"
    ]
  },
  "id": 1
}
```

![](https://sky-joker.tech/wp-content/uploads/2017/11/33d076a6604216d4f4eafbf0ce21b2b3.png)

## アイコンを紐づけてみる

マップにアイコンを2つ登録してお互いを紐づけてみます。

```python
    # ここから処理を書いていく
    # Element1.
    element1 = {}
    element1['elementid'] = 10105
    element1['selementid'] = 1
    element1['elementtype'] = 0
    element1['iconid_off'] = 101
    element1['x'] = 100
    element1['y'] = 100

    # Element2.
    element2 = {}
    element2['elementid'] = 10106
    element2['selementid'] = 2
    element2['elementtype'] = 0
    element2['iconid_off'] = 101
    element2['x'] = 350
    element2['y'] = 100

    # Element Link.
    elementLink = {
        'selementid1': element1['selementid'],
        'selementid2': element2['selementid']
    }

    # Create Map.
    params = {}
    params['name'] = "Map"
    params['width'] = 600
    params['height'] = 600
    params['selements'] = [element1, element2]
    params['links'] = [elementLink]
    r = zapi.post('map.create', params)
    print(json.dumps(json.loads(r), indent=2))
```

| パラメーター | 説明                                           |
|:-------------|:-----------------------------------------------|
| links        | 紐づけたいエレメントの `selementid` を渡します |

実行するとアイコンが２つ出来て紐づけられています。

```json
{
  "id": 1,
  "jsonrpc": "2.0",
  "result": {
    "sysmapids": [
      "16"
    ]
  }
}
```

![](https://sky-joker.tech/wp-content/uploads/2017/11/af3f52409959a7a1e88ac2dffc7b40fc.png)

## マップをアップデートする

既に作成したマップをアップデート(アイコンを追加したり)してみます。

```python
    # ここから処理を書いていく
    # Element1.
    element1 = {}
    element1['elementid'] = 10105
    element1['selementid'] = 1
    element1['elementtype'] = 0
    element1['iconid_off'] = 101
    element1['x'] = 100
    element1['y'] = 100

    # Element2.
    element2 = {}
    element2['elementid'] = 10106
    element2['selementid'] = 2
    element2['elementtype'] = 0
    element2['iconid_off'] = 101
    element2['x'] = 350
    element2['y'] = 100

    # Element Link.
    elementLink = {
        'selementid1': element1['selementid'],
        'selementid2': element2['selementid']
    }

    # Update Map.
    params = {}
    params['sysmapid'] = 18
    params['selements'] = [element1, element2]
    params['links'] = [elementLink]
    r = zapi.post('map.update', params)
    print(json.dumps(json.loads(r), indent=2))
```

| パラメーター | 説明                       |
|:-------------|:---------------------------|
| sysmapid     | アップデート対象のsysmapid |

作成時と違うのは、対象の `sysmapid` を指定します。
sysmapidはマップのURLとか作成時のレスポンスボディから確認できます。

# 独り言

今回は、シンプルな処理しかしなかったから多次元連想配列(multi_dimension_dict)は使わなかった。。。