Icinga Web REST API を使ってみた


Icingaでの操作(監視の開始・停止)や条件に合った監視ホスト・サービスを取得する[Icinga Web REST API](http://docs.icinga.org/latest/en/icinga-web-api.html)というものが提供されています。
つまり、Icingaサーバ以外のサーバからIcingaの操作や情報の取得が可能になるものです。

操作を自動化したいと思っていたので、「これだ！」と思って調べていたのですが、あまり情報がなかったので数少ないドキュメントと数々の試行錯誤の結果得た知見を紹介します。

Icinga Web REST APIを使うためには、Icinga のバージョンが1.5以上であることが必須です。

## 事前準備

`auth_enabled`を`true`に変更します。

```xml
# /usr/share/icinga-web/app/modules/AppKit/config/auth.xml
<ae:parameter name="auth_key">
    <ae:parameter name="auth_module">AppKit</ae:parameter>
    <ae:parameter name="auth_provider">Auth.Provider.AuthKey</ae:parameter>
    <ae:parameter name="auth_enable">true</ae:parameter>
    <ae:parameter name="auth_authoritative">true</ae:parameter>
</ae:parameter>
```

キャッシュクリア

```bash
$ sudo rm -f /usr/share/icinga-web/app/config/*.php
```

## APIキー発行

API操作するためにAPIキーを発行します。

1. Icinga-Webにログイン後、Admin -> Users をクリック
2. 「Add new user」をクリックして、APIアクセス用のユーザを作成
3. 「Auth via」で「auth_key」を選択
4. 「Authkey for Api (optional)」 の更新ボタンをクリックしてAPIキーを発行する
5. Rightタブ -> Credentialsタブ を選択し、`appkit.api.access`と`icinga.user`にチェックを入れる
6. 「Create user」をクリック

### テスト

以下のAPIが使えるかテストします。

#### CriticalかWarningの状態であるサービスすべてを取得するAPI

```bash
# XML
$ curl -s --globoff "http://icinga.example.com/icinga-web/web/api/service/filter[AND(HOST_CURRENT_STATE|=|0;OR(SERVICE_CURRENT_STATE|=|1;SERVICE_CURRENT_STATE|=|2))]/columns[SERVICE_NAME|HOST_NAME|SERVICE_CURRENT_STATE|HOST_NAME|HOST_CURRENT_STATE|HOSTGROUP_NAME]/order(SERVICE_CURRENT_STATE;DESC)/countColumn=SERVICE_ID/authkey=<APIキー>/xml"
```

```xml
<?xml version="1.0" encoding="UTF-8"?>
<results><result><column name="SERVICE_NAME">HTTPD</column><column name="HOST_NAME">host1</column><column name="SERVICE_CURRENT_STATE">1</column><column name="HOST_CURRENT_STATE">0</column><column name="HOST_IS_PENDING">0</column><column name="SERVICE_IS_PENDING">0</column>...</result><total>1</total></results>
```

```bash
# JSON (jqで整形)
curl -s --globoff "http://icinga.example.com/icinga-web/web/api/service/filter[AND(HOST_CURRENT_STATE|=|0;OR(SERVICE_CURRENT_STATE|=|1;SERVICE_CURRENT_STATE|=|2))]/columns[SERVICE_NAME|HOST_NAME|SERVICE_CURRENT_STATE|HOST_NAME|HOST_CURRENT_STATE|HOSTGROUP_NAME]/order(SERVICE_CURRENT_STATE;DESC)/countColumn=SERVICE_ID/authkey=<APIキー>/json" | jq .
```

```json
{
  "total": 1,
  "success": "true",
  "result": [
    {
      "SERVICE_IS_PENDING": "0",
      "HOST_IS_PENDING": "0",
      "HOST_CURRENT_STATE": "0",
      "SERVICE_CURRENT_STATE": "1",
      "HOST_NAME": "host1",
      "SERVICE_NAME": "HTTPD"
    },
    ...
  ]
}
```

#### 通知がオフになっているサービスすべてを取得するAPI

```bash
curl -s --globoff "http://icinga.example.com/icinga-web/web/api/service/filter[OR(SERVICE_NOTIFICATIONS_ENABLED|=|0;HOST_NOTIFICATIONS_ENABLED|=|0)]/columns[HOST_NAME|SERVICE_NAME]/order(HOST_NAME;asc)/countColumn=SERVICE_ID/authkey=<APIキー>/json" | jq .
```

```json
{
  "total": 135,
  "success": "true",
  "result": [
    {
      "SERVICE_NAME": "CPU Usage System",
      "HOST_NAME": "host1"
    },
    ...
  ]
}
```

## URLの構成

ひな形 : `http://icinga.example.com/icinga-web/web/api/TARGET/COLUMNS/FILTER/ORDER/GROUPING/LIMIT/COUNTFIELD/auth=AUTH_KEY/OUTPUT_TYPE`

| パラメータ | 説明 | 例 |
|:----------|:-----------|:------------|
| TARGET | service or host | service |
| COLUMNS | 取得したいカラム名 | `columns[COL1!COL2!COL3!...]` |
| FILTER | SQLで言うと`where` | `and(カラム!=!条件);or(カラム!=!条件;カラム=条件)･･･`で検索、`!like!`でLIKE検索 |
| ORDER | SQLで言うと`order by` | `order(カラム;ASC or DESC)` |
| GROUPING | SQLで言うと`group by` | `group(カラム)` |
| LIMIT | SQLで言うと`limit` | `limit[START;END]` |
| COUNTFIELD | SQLで言うと`count(*)`、カウントしたいカラム名(idなど) | `countColumn=カラム名` |
| AUTH_KEY | APIキー | 先ほど発行したキー |
| OUTPUT | 出力フォーマット | json or xml |

FILTERについて

* `()`, `and()`, `or()`で1つのグループ
* 演算子は`|=|`のようにする
    * 上記例では`!`を`|`に置き換えてください
* `;`がセパレータ

例

* A=1 => `(A|=|1)`
* A=1 and B=2 => `and(A|=|1;B|=|2)`
* A=1 or B=2 => `or(A|=|1;B|=|2)`
* A=1 and (B=2 or C=3) => `and(A|=|1;or(B|=|2;C|=|3))`

## 使用できるカラム名

`/usr/share/icinga-web/app/modules/Cronks/lib/js/Icinga/Cronks/Tackle/Information/Head.js`に取得できるカラム一覧があるのでそちらをご参照ください。

## ホスト

大体同じものやあまり使わなそうなものは説明を省略しています。

| カラム名 | 説明 | 例 |
|:----------|:-----------|:------------|
| HOST_ID| ホストID | - |
| HOST_OBJECT_ID | ホストオブジェクトID | - |
| HOST_INSTANCE_ID | ホストインスタンスID | - |
| HOST_NAME | ホスト名 | host1 |
| HOST_ALIAS | ホストエイリアス名 | - |
| HOST_DISPLAY_NAME | ホストのディスプレイ表示名 | - |
| HOST_ADDRESS | ホストのアドレス名(IPアドレス) | 192.168.192.5 |
| HOST_ADDRESS6 | ホストのアドレス名(IPアドレスv6?) | - |
| HOST_ACTIVE_CHECKS_ENABLED | ホストのアクティブチェックが有効になっているか | 0:無効, 1:有効 |
| HOST_CONFIG_TYPE |
| HOST_FLAP_DETECTION_ENABLED |
| HOST_PROCESS_PERFORMANCE_DATA |
| HOST_FRESHNESS_CHECKS_ENABLED |
| HOST_FRESHNESS_THRESHOLD |
| HOST_PASSIVE_CHECKS_ENABLED |
| HOST_EVENT_HANDLER_ENABLED |
| HOST_RETAIN_STATUS_INFORMATION |
| HOST_RETAIN_NONSTATUS_INFORMATION |
| HOST_NOTIFICATIONS_ENABLED | 通知が有効になっているか | 0:無効, 1:有効 |
| HOST_OBSESS_OVER_HOST |
| HOST_FAILURE_PREDICTION_ENABLED |
| HOST_NOTES |
| HOST_NOTES_URL |
| HOST_ACTION_URL |
| HOST_ICON_IMAGE | 
| HOST_ICON_IMAGE_ALT |
| HOST_IS_ACTIVE |
| HOST_OUTPUT |
| HOST_LONG_OUTPUT |
| HOST_PERFDATA |
| HOST_CURRENT_STATE | ホストの状態 | 0:UP, 1:DOWN, 2:UNREACHABLE |
| HOST_CURRENT_CHECK_ATTEMPT |
| HOST_MAX_CHECK_ATTEMPTS |
| HOST_LAST_CHECK | 最後に監視チェックした時刻 | 2015-06-10 18:46:43 |
| HOST_LAST_STATE_CHANGE | 最後に状態が変わった時刻 | 2015-03-04 19:45:16 |
| HOST_CHECK_TYPE|
| HOST_LATENCY |
| HOST_EXECUTION_TIME |
| HOST_NEXT_CHECK | 次に監視チェックする時刻 | 2015-06-10 18:49:53 |
| HOST_HAS_BEEN_CHECKED|
| HOST_LAST_HARD_STATE_CHANGE|
| HOST_LAST_NOTIFICATION |
| HOST_PROCESS_PERFORMANCE_DATA |
| HOST_STATE_TYPE　|
| HOST_IS_FLAPPING |
| HOST_PROBLEM_HAS_BEEN_ACKNOWLEDGED |
| HOST_SCHEDULED_DOWNTIME_DEPTH |
| HOST_SHOULD_BE_SCHEDULED |
| HOST_STATUS_UPDATE_TIME |
| HOST_CHECK_SOURCE |

## サービス

| カラム名 | 説明 | 例 |
|:----------|:-----------|:------------|
| SERVICE_ID | サービスID |
| SERVICE_INSTANCE_ID | サービスインスタンスID |
| SERVICE_CONFIG_TYPE |
| SERVICE_IS_ACTIVE |
| SERVICE_OBJECT_ID | サービスオブジェクトID |
| SERVICE_NAME | サービス名 |
| SERVICE_DISPLAY_NAME | サービスのディスプレイ表示名 |
| SERVICE_NOTIFICATIONS_ENABLED | 通知が有効になっているか | 0:無効, 1:有効 |
| SERVICE_FLAP_DETECTION_ENABLED |
| SERVICE_PASSIVE_CHECKS_ENABLED |
| SERVICE_EVENT_HANDLER_ENABLED |
| SERVICE_ACTIVE_CHECKS_ENABLED |
| SERVICE_RETAIN_STATUS_INFORMATION |
| SERVICE_RETAIN_NONSTATUS_INFORMATION |
| SERVICE_OBSESS_OVER_SERVICE |
| SERVICE_FAILURE_PREDICTION_ENABLED |
| SERVICE_NOTES |
| SERVICE_NOTES_URL |
| SERVICE_ACTION_URL |
| SERVICE_ICON_IMAGE |
| SERVICE_ICON_IMAGE_ALT |
| SERVICE_OUTPUT |
| SERVICE_LONG_OUTPUT |
| SERVICE_PERFDATA |
| SERVICE_PROCESS_PERFORMANCE_DATA |
| SERVICE_CURRENT_STATE | サービスの状態 | 0:OK, 1:WARNING, 2:CRITICAL, 3:UNKNOWN |
| SERVICE_CURRENT_CHECK_ATTEMPT |
| SERVICE_MAX_CHECK_ATTEMPTS |
| SERVICE_LAST_CHECK |
| SERVICE_LAST_STATE_CHANGE |
| SERVICE_CHECK_TYPE |
| SERVICE_LATENCY |
| SERVICE_EXECUTION_TIME |
| SERVICE_NEXT_CHECK |
| SERVICE_HAS_BEEN_CHECKED |
| SERVICE_LAST_HARD_STATE |
| SERVICE_LAST_HARD_STATE_CHANGE |
| SERVICE_LAST_NOTIFICATION |
| SERVICE_STATE_TYPE |
| SERVICE_IS_FLAPPING |
| SERVICE_PROBLEM_HAS_BEEN_ACKNOWLEDGED |
| SERVICE_SCHEDULED_DOWNTIME_DEPTH |
| SERVICE_SHOULD_BE_SCHEDULED |
| SERVICE_STATUS_UPDATE_TIME |
| SERVICE_CHECK_SOURCE |

## POSTメソッドでAPIコール

POSTリクエストでもGETリクエストと同じことができます。
POSTと聞いてデータを更新できるかと思ったら違って残念な気持ちになりました。

```bash
curl \
-d target=service \
-d 'filters_json={"type":"AND","field":[{"type":"atom","field":["HOST_CURRENT_STATE"],"method":["="],"value":[0]},{"type":"OR","field":[{"type":"atom","field":["SERVICE_CURRENT_STATE"],"method":["="],"value":[1]},{"type":"atom","field":["SERVICE_CURRENT_STATE"],"method":["="],"value" : [2] }]}]}' \
-d columns[0]=SERVICE_NAME \
-d columns[1]=HOST_NAME \
-d columns[2]=SERVICE_CURRENT_STATE \
-d columns[3]=HOST_NAME \
-d columns[4]=HOST_CURRENT_STATE \
-d columns[5]=HOSTGROUP_NAME \
-d 'order=SERVICE_CURRENT_STATE;DESC' \
-d countColumn=SERVICE_ID \
-d 'authkey=<APIキー>' \
http://icinga.example.com/icinga-web/web/api/xml
```

`fileter_json`を整形するとこのようになっています。

```json
{
    "field": [
        {
            "field": [
                "HOST_CURRENT_STATE"
            ], 
            "method": [
                "="
            ], 
            "type": "atom", 
            "value": [
                0
            ]
        }, 
        {
            "field": [
                {
                    "field": [
                        "SERVICE_CURRENT_STATE"
                    ], 
                    "method": [
                        "="
                    ], 
                    "type": "atom", 
                    "value": [
                        1
                    ]
                }, 
                {
                    "field": [
                        "SERVICE_CURRENT_STATE"
                    ], 
                    "method": [
                        "="
                    ], 
                    "type": "atom", 
                    "value": [
                        2
                    ]
                }
            ], 
            "type": "OR"
        }
    ], 
    "type": "AND"
}
```


## APIからIcingaの外部コマンドを実行する

Nagiosに[外部コマンドファイル](http://nagios.x-trans.jp/Nagios-doc/JAPANESE/extcommands.html)が用意されているようにIcingaも同じ外部コマンドファイルが用意されています。  
なんと、その外部コマンドをAPIで操作できるのです。
これで外部サーバから通知のオン/オフなどの操作ができるようになります。

### URL構成

`http://icinga.example.com/icinga-web/web/api/cmd/cmd=COMMAND_NAME/authkey=AUTH_KEY/target=TARGET/data=DATA`

| パラメータ | 説明 | 例 |
|:----------|:-----------|:------------|
| COMMAND_NAME | コマンド名 | DISABLE_HOST_NOTIFICATIONS: 通知をオフにする, ENABLE_HOST_NOTIFICATIONS: 通知をオンにする |
| AUTH_KEY | APIキー | 先ほど発行したキー |
| TARGET | コマンド実行の対象 | JSON形式でURLエンコードする |
| DATA | コマンド実行のデータ | JSON形式でURLエンコードする |

### 例

* 監視対象サーバ(host1)の通知をオフにする

```bash
# ホスト
$ curl  http://icinga.example.com/icinga-web/web/api/cmd/cmd=DISABLE_HOST_NOTIFICATIONS/authkey=<APIキー>/target=%5B%7B%22instance%22%3A%22default%22%2C%22host%22%3A%22host1%22%7D%5D/data=%7B%22host%22%3A%22host1%22%7D
{"success":true}

# サービス
curl http://icinga.example.com/icinga-web/web/api/cmd/cmd=DISABLE_HOST_SVC_NOTIFICATIONS/authkey=<APIキー>/target=%5B%7B%22instance%22%3A%22default%22%2C%22host%22%3A%22host1%22%7D%5D/data=%7B%22host%22%3A%22host1%22%7D
{"success":true}
```

`target`パラメータはURLエンコードされており、デコードするとこのようになります。

```json
[{"instance":"default","host":"host1"}]
```

`data`パラメータはこのようになっています。

```json
{"host":"host1"}
```

APIリクエストによる詳細ログを見ることができます。

```
# /var/log/icinga-web/debug.log

[Thu Jun 11 16:18:30 2015] [debug] Write session update: hf349klcer3llm5k4du413uar7 (AppKitDoctrineSessionStorage::sessionWrite(), line 143)
[Thu Jun 11 16:18:30 2015] [debug] Auth.Dispatch: Starting authenticate (username=user1)
[Thu Jun 11 16:18:30 2015] [info] Auth.Dispatch: Converting username to lowercase
[Thu Jun 11 16:18:30 2015] [debug] Auth.Dispatch: Userdata found in db (uid=2)
[Thu Jun 11 16:18:30 2015] [debug] Auth.Provider: Object (name=auth_key) initialized
[Thu Jun 11 16:18:30 2015] [debug] Auth.Dispatch: Authoritative provider found (provider=auth_key, authid=api test)
[Thu Jun 11 16:18:30 2015] [debug] Auth.Dispatch: Successful authentication (provder=auth_key)
[Thu Jun 11 16:18:30 2015] [info] User user1 (test, api) logged in! (ip=::1)
[Thu Jun 11 16:18:30 2015] [info] Array
(
    [0] => Array
        (
            [instance] => default
            [host] => host1
        )

)

[Thu Jun 11 16:18:30 2015] [info] Array
(
    [0] => Array
        (
            [instance] => default
            [host] => host1
        )

)

[Thu Jun 11 16:18:30 2015] [debug] Trying to send commands, targets: [{"instance":"default","host":"host1"}] , data: {"host":"host1"}  (Cronks_System_CommandSenderModel::dispatchCommands(), line 68)
[Thu Jun 11 16:18:30 2015] [debug] Setting up console for instance default  (Cronks_System_CommandSenderModel::getConsoleInstance(), line 79)
[Thu Jun 11 16:18:30 2015] [debug] Submitting command DISABLE_HOST_NOTIFICATIONS to {"instance":"default","host":"host1"} (Cronks_System_CommandSenderModel::dispatchCommands(), line 68)
[Thu Jun 11 16:18:30 2015] [debug] Sending icinga-command [1434007110] DISABLE_HOST_NOTIFICATIONS;host1 (Api_Commands_CommandDispatcherModel::submitCommand(), line 83)
[Thu Jun 11 16:18:30 2015] [debug] ["Finished submitting command"] (Cronks_System_CommandSenderModel::dispatchCommands(), line 68)
[Thu Jun 11 16:18:30 2015] [info] User api test (test, api) logged out! (ip=::1)

[Thu Jun 11 16:41:13 2015] [debug] Write session update: user2 (AppKitDoctrineSessionStorage::sessionWrite(), line 143)
[Thu Jun 11 16:41:13 2015] [debug] Auth.Dispatch: Starting authenticate (username=user1)
[Thu Jun 11 16:41:13 2015] [info] Auth.Dispatch: Converting username to lowercase
[Thu Jun 11 16:41:13 2015] [debug] Auth.Dispatch: Userdata found in db (uid=2)
[Thu Jun 11 16:41:13 2015] [debug] Auth.Provider: Object (name=auth_key) initialized
[Thu Jun 11 16:41:13 2015] [debug] Auth.Dispatch: Authoritative provider found (provider=auth_key, authid=api test)
[Thu Jun 11 16:41:13 2015] [debug] Auth.Dispatch: Successful authentication (provder=auth_key)
[Thu Jun 11 16:41:13 2015] [info] User user1 (test, api) logged in! (ip=::1)
[Thu Jun 11 16:41:13 2015] [info] Array
(
    [0] => Array
        (
            [instance] => default
            [host] => host1
        )

)

[Thu Jun 11 16:41:13 2015] [info] Array
(
    [0] => Array
        (
            [instance] => default
            [host] => host1
        )

)

[Thu Jun 11 16:41:13 2015] [debug] Trying to send commands, targets: [{"instance":"default","host":"host1"}] , data: {"host":"host1"}  (Cronks_System_CommandSenderModel::dispatchCommands(), line 68)
[Thu Jun 11 16:41:13 2015] [debug] Setting up console for instance default  (Cronks_System_CommandSenderModel::getConsoleInstance(), line 79)
[Thu Jun 11 16:41:13 2015] [debug] Submitting command DISABLE_HOST_SVC_NOTIFICATIONS to {"instance":"default","host":"host1"} (Cronks_System_CommandSenderModel::dispatchCommands(), line 68)
[Thu Jun 11 16:41:13 2015] [debug] Sending icinga-command [1434008473] DISABLE_HOST_SVC_NOTIFICATIONS;host1 (Api_Commands_CommandDispatcherModel::submitCommand(), line 83)
[Thu Jun 11 16:41:13 2015] [debug] ["Finished submitting command"] (Cronks_System_CommandSenderModel::dispatchCommands(), line 68)
[Thu Jun 11 16:41:13 2015] [info] User api test (test, api) logged out! (ip=::1)
```

### ソース改変

連続でAPIを叩くとたまにエラーになる場合があるので、ソースを改変する。

```php
# /usr/share/icinga-web/app/modules/Api/models/Commands/CommandDispatcherModel.class.php

 77             AppKitLogger::debug("Sending icinga-command %s",$string);
 78             $cmd = $this->getContext()->getModel($commandClass[0],$commandClass[1],
 79                                                  array(
 80                                                          //"command" => "printf",
 81                                                          "command" => "echo",   // <-こちらに修正
 82                                                          "arguments" => array($string)
 83                                                  )
 84                                                 );
```

何をしているかというと、外部コマンドファイル(`/var/spool/icinga/cmd/icinga.cmd`)にパイプで命令を渡すコマンドを修正しています。  
`printf`から`echo`に変更することで、意図的に改行を挟むようにし、連続して実行してもエラーにならなくなりました。

```bash
# 修正前
printf '[1434600538] ENABLE_HOST_NOTIFICATIONS;host1'  > /var/spool/icinga/cmd/icinga.cmd

# 修正後
echo '[1434600538] ENABLE_HOST_NOTIFICATIONS;host1'  > /var/spool/icinga/cmd/icinga.cmd
```

## 参考URL

* [11.4. The Icinga Web REST API](http://docs.icinga.org/latest/en/icinga-web-api.html)
* [Icinga-Web REST API - Development - Icinga Wiki](https://wiki.icinga.org/display/Dev/Icinga-Web+REST+API)


元記事は[こちら](http://blog.jicoman.info/2015/07/icinga_web_restful_api/)