JunosのREST APIを使ってみる

# 概要

[NetOpsCoding Advent Calendar 2015](http://qiita.com/advent-calendar/2015/netopscoding)の16日目の記事です。

今回は、Junos 14.2から搭載されたREST APIを使って、Junos機器の状態を取得したり、設定を変更したりしてみたいと思います。

# Junos REST APIについて

Junos REST APIは、JuniperのM/T/(v)MX/PTXルータでJunos 14.2からサポートされた、HTTP(S)経由で使用できるAPIです。

APIについてのドキュメントは以下にまとまっています。
[REST API Guide](http://www.juniper.net/documentation/en_US/junos14.2/information-products/pathway-pages/rest-api/rest-api.html)

また、APIで使用するRPCについては基本的にNETCONFと同様であり、以下のドキュメントに記載されています。
[NETCONF XML Management Protocol Developer Guide](http://www.juniper.net/documentation/en_US/junos14.2/information-products/pathway-pages/netconf-guide/netconf.html)

# 環境

今回は、以下の環境を使用して検証を行いました。

* Ruby 1.9.3-p484
* ライブラリ
    * rest-client 1.8.0
* ルータ
    * Juniper vMX 15.1F3.11

# 事前準備

## ルータ側

リモートの環境からアクセスできるよう、IPアドレスやユーザ等の設定を行っておきます。
REST APIは以下の設定を投入することによって有効化されます。

```
set system services rest http port 3000
```

## PC側

Rubyでrest-clientライブラリが使用できるようにします。(`gem install --no-ri --no-rdoc rest-client` 等、お好みに応じてインストールしてください。)

# 情報取得(GET)

まずはじめに、機器の情報を取得してみましょう。Junosでは、各コマンドに対してそれぞれRPC methodが定義されています。各コマンドに対応するRPCの情報は以下のようにして確認することが可能です。

```xml
root@vmx> show version | display xml rpc
<rpc-reply xmlns:junos="http://xml.juniper.net/junos/15.1F3/junos">
    <rpc>
        <get-software-information>
        </get-software-information>
    </rpc>
    <cli>
        <banner></banner>
    </cli>
</rpc-reply>
```

この例では、RPCとして`get-software-information` methodを発行することにより、Junos CLIでいう`show version`と同一の出力が得られることがわかります。

早速、APIを叩いてみましょう。
以下にサンプルコードを提示します。HTTP GETにて、`get-software-information` RPC methodを叩き、結果を出力します。

```rb:get-software-information.rb
#!/usr/bin/env ruby

require 'restclient'

auth = "user:password"
host = '192.168.0.1'
port = '3000'

rpc = "http://#{auth}@#{host}:#{port}/rpc"

puts RestClient.get rpc + '/get-software-information'
```

出力結果:

```xml:get-software-information-result.xml
<software-information>
<host-name>vmx</host-name>
<product-model>vmx</product-model>
<product-name>vmx</product-name>
<junos-version>15.1F3.11</junos-version>
<package-information>
<name>junos</name>
<comment>JUNOS Base OS boot [15.1F3.11]</comment>
</package-information>
<package-information>
<name>jbase</name>
<comment>JUNOS Base OS Software Suite [15.1F3.11]</comment>
</package-information>
<!-- ...(中略)... -->
<package-information>
<name>jsim-vmx</name>
<comment>JUNOS Packet Forwarding Engine Trio Simulation Package [15.1F3.11]</comment>
</package-information>
</software-information>
```

出力結果を見るとわかるように、デフォルトでは応答形式はXMLになっていますので、適切なライブラリを使用する事により、プログラム内で特定の箇所を抽出する事も可能になってきます。

ここで、「えっ、今時XMLかよ…」などと思われた読者の方も多いんじゃないかと思いますが、JSONやPlain textでの出力も可能です。例えば、JSONで結果を取得したい場合、HTTPのAcceptヘッダに'application/json'を指定するか、URLの末尾に`@format=json`と追記することでも出力フォーマットを指定可能です。

例として、Junos CLIでいう`show interfaces terse ge-0/0/1`の結果をJSONで出力してみます。

```rb:get-software-information_json.rb
#!/usr/bin/env ruby

require 'restclient'

auth = "user:password"
host = '192.168.0.1'
port = '3000'

rpc = "http://#{auth}@#{host}:#{port}/rpc"

puts RestClient.get rpc + '/get-interface-information?terse=&interface-name=ge-0/0/1', :accept => 'application/json'
# alternative:
# puts RestClient.get rpc + '/get-interface-information@format=json?terse=&interface-name=ge-0/0/1'
```

出力結果:

```json:get-software-information-result.json
{
 "interface-information" : [
 {
    "attributes" : {"xmlns" : "http://xml.juniper.net/junos/15.1F3/junos-interface",
                    "junos:style" : "terse"
                   },
    "physical-interface" : [
    {
        "name" : [
        {
            "data" : "ge-0/0/1"
        }
        ],
        "admin-status" : [
        {
            "data" : "up"
        }
        ],
        "oper-status" : [
        {
            "data" : "down"
        }
        ],
        "logical-interface" : [
        {
            "name" : [
            {
                "data" : "ge-0/0/1.0"
            }
            ],
            "admin-status" : [
            {
                "data" : "up"
            }
            ],
            "oper-status" : [
            {
                "data" : "down"
            }
            ],
            "filter-information" : [
            {
            }
            ],
            "address-family" : [
            {
                "address-family-name" : [
                {
                    "data" : "inet"
                }
                ],
                "interface-address" : [
                {
                    "ifa-local" : [
                    {
                        "data" : "10.0.0.2/24",
                        "attributes" : {"junos:emit" : "emit"}
                    }
                    ]
                }
                ]
            },
            {
                "address-family-name" : [
                {
                    "data" : "multiservice"
                }
                ]
            }
            ]
        }
        ]
    }
    ]
 }
 ]
}

```

REST APIを使用するとこのように、簡単にJunos機器の状態を取得することが可能です。

# 設定の変更

さて、続いては設定の変更を行ってみたいと思います。
設定の変更は、HTTP POSTで`load-configuration` RPCを叩くことにより可能です。

例えば、`ge-0/0/1`のDescriptionに`to-customer-A`という文字列を指定するコードは以下のようになります。Candidate Configurationの変更を行うのみですので、Commitは行われません。

```rb:set-description.rb
#!/usr/bin/env ruby

require 'restclient'

auth = "user:password"
host = '192.168.0.1'
port = '3000'

rpc = "http://#{auth}@#{host}:#{port}/rpc"

puts RestClient.post rpc + '/load-configuration@format=text',
  '<configuration-text>
  interfaces {
    ge-0/0/1 {
      description to-customer-A
    }
  }
</configuration-text>'
```

結果:

```xml
<load-configuration-results>
<load-success/>
</load-configuration-results>
```

ここで、Junos上で`show | compare` を実行してみると、上記の設定が正しくCandidate Configurationに投入されていることがわかります。

```
root@vmx# show | compare
[edit interfaces ge-0/0/1]
+   description to-customer-A;
```

この変更を適用するには、投入した設定をCommitする必要があります。先ほどのコードに、Commitする操作を追加してみましょう。

```rb:set-description-and-commit.rb
#!/usr/bin/env ruby

require 'restclient'

auth = "user:password"
host = '192.168.0.1'
port = '3000'

rpc = "http://#{auth}@#{host}:#{port}/rpc"

puts RestClient.post rpc + '/load-configuration@format=text',
  '<configuration-text>
  interfaces {
    ge-0/0/1 {
      description to-customer-A
    }
  }
</configuration-text>'

puts RestClient.get rpc + '/commit-configuration'
```

結果:

```xml
<load-configuration-results>
<load-success/>
</load-configuration-results>
<commit-results xmlns:junos="http://xml.juniper.net/junos/*/junos">
<routing-engine junos:style="normal">
<name>re0</name>
<commit-success/>
</routing-engine>
</commit-results>
```

2つのRPCリクエストが行われ、設定の変更が反映されたようです。Junos上で確認してみましょう。

```
[edit]
root@vmx# show | compare rollback 1
[edit interfaces ge-0/0/1]
+   description to-customer-A;
```

# Multiple RPC call per one HTTP request

ここまでは、1つのHTTPリクエストで1つのRPCコールを行ってきました。
しかし、実際にコンフィグレーションを変更したりする場合、1つのリクエストの中で複数の変更を行ったり、Commit(設定の反映)を行ないたいケースが多々出てくるかと思います。
Junos REST APIでは、rpc endpointに複数のRPC methodを含むXMLをPostすることにより、1回のHTTPリクエストの中で複数のRPCコールを発行することが可能です。

例えば、Configurationをロックした後、ge-0/0/0のdescriptionを追加し、ge-0/0/1のdescriptionを削除してCommitしてからConfigurationのロックを解除するコードは以下のようになります。

```rb:multiple-rpc.rb
#!/usr/bin/env ruby

require 'restclient'

auth = "user:password"
host = '192.168.0.1'
port = '3000'

rpc = "http://#{auth}@#{host}:#{port}/rpc"

puts RestClient.post rpc+'?stop-on-error=1' ,
  '<lock-configuration />
<load-configuration format="text">
  <configuration-text>
    interfaces {
      ge-0/0/0 {
        description to-core-router;
      }
      ge-0/0/1 {
        delete: description;
      }
    }
  </configuration-text>
</load-configuration>
<commit-configuration />
<unlock-configuration />'

```

結果: 

```xml

--nwlrbbmqbhcdarz

--nwlrbbmqbhcdarz
Content-Type: text/plain; charset=utf-8

<load-configuration-results>
<load-success/>
</load-configuration-results>
--nwlrbbmqbhcdarz
Content-Type: application/xml; charset=utf-8

<commit-results xmlns:junos="http://xml.juniper.net/junos/*/junos">
<routing-engine junos:style="normal">
<name>re0</name>
<commit-success/>
</routing-engine>
</commit-results>
--nwlrbbmqbhcdarz

--nwlrbbmqbhcdarz--
```

複数のRPC methodを1度のリクエストで実行した場合、multipart/mixedの形でそれぞれのRPCに対応する応答が返されるようです。

# さいごに

今回はJunosのREST APIを使用して、ルータをプログラムから操作してみました。REST APIによって実現できること自体は特にNetconfと変わりませんが、汎用のHTTPライブラリのみで操作できるため、比較的とっつきやすいのではないかと思います。
現在はルータ系のJunos 14.2以降でのみサポートされていますが、スイッチではJunos 15.1以降でサポートされる予定です。今後の流行がどういった方向に進むのかはわかりませんが、Junos機器を自動化するにあたっての選択肢の一つとして考えられるかと思います。