RESTCONFとかChatOpsでIOS-XEを設定するのってなんかかっこいいよね。

# はじめに
この記事はシスコシステムズ合同会社の社員有志による「[Cisco Systems Japan Advent Calendar 2022](https://qiita.com/advent-calendar/2022/cisco)」の 2枚目の 24日目 として投稿しています。

過去のカレンダーはこちらから
|| [2021年-1枚目](https://qiita.com/advent-calendar/2021/cisco) / [2021年-2枚目](https://qiita.com/advent-calendar/2021/cisco2) || [2020年-1枚目](https://qiita.com/advent-calendar/2020/cisco) / [2020年-2枚目](https://qiita.com/advent-calendar/2020/cisco2) || [2019年](https://qiita.com/advent-calendar/2019/cisco) || [2018年](https://qiita.com/advent-calendar/2018/cisco) || [2017年](https://qiita.com/advent-calendar/2017/cisco) ||

# DevNet Class of 2020
私はシスコに2020年に新卒として入社しましたが、同年シスコではDevnet認定が新しく始まりました。そのおかげもあり、新卒研修中にDevnet AssociateおよびDevnet Professionalのトレーニングを受ける事ができ、DevNetの資格を取得することができました。2020年にDevNet資格を取得したことで、DevNet Class of 2020というような称号もいただきました。そんなDevNetの勉強をしてた頃、個人的に好きだったRESTCONFとそれのChatOpsへの応用を今回紹介したいと思います。

# RESTCONFとは
RESTCONFはシスコの[プログラマビリティ コンフィグレーションガイド](https://www.cisco.com/c/ja_jp/td/docs/ios-xml/ios/prog/configuration/176/b_176_programmability_cg/m_176_prog_restconf.html)によると

>構造化データ（XML または JSON）および YANG を使用して REST ライクな API を提供します。これによりさまざまなネットワーク デバイスにプログラムを使用してアクセスできます。RESTCONF API は HTTPs メソッドを使用します。

らしいです。難しいことは分かりませんが、REST APIみたいな感じでHTTPsメソッドを使ってネットワーク機器を設定できるものだと認識しています。データはYANGを使用しています。YANGの構造については[こちら](https://github.com/YangModels/yang/tree/main/vendor/cisco/xe)を参照してください。

# RESTCONFの準備をする
RESTCONFを使用するにはネットワーク機器にAAAでの認証と、HTTPサービスを有効にしなければなりません。

### AAAの有効化
```
en
conf t
aaa new-model
aaa authentication login default local
aaa authorization exec default local
username cisco privilege 15 secret cisco
enable secret cisco
line vty 0 15
  transport input ssh
end
```

### HTTPサービスとRESTCONFの有効化
```
en
conf t
ip http secure-server
restconf
end
```

準備はこれだけです。簡単ですね。

# RESTCONFを使ってみる

簡単な検証環境を用意しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2325351/d36aafea-04c0-21dd-346a-1648992e6345.png)


### curl
まずcurlを使ってUbuntuからRESTCONF APIを叩きます。試しにI/Fの設定の確認をしてみます。
```terminal
$ curl --insecure --location --request GET 'https://{hostname}:443/restconf/data/native/interface' --header 'Accept: application/yang-data+json' --header 'Authorization: Basic <base64エンコードした"user:password">'

{
  "Cisco-IOS-XE-native:interface": {
    "GigabitEthernet": [
      {
        "name": "1",
        "ip": {
          "address": {
            "primary": {
              "address": "172.16.100.12",
              "mask": "255.255.255.0"
            }
          }
        },
        "Cisco-IOS-XE-ethernet:negotiation": {
          "auto": true
        }
      },
      {
        "name": "2",
        "ip": {
          "address": {
            "primary": {
              "address": "10.0.0.20",
              "mask": "255.255.255.0"
            }
          }
        },
        "Cisco-IOS-XE-ethernet:negotiation": {
          "auto": true
        }
      },
      {
        "name": "3",
        "ip": {
          "address": {
            "primary": {
              "address": "192.168.11.200",
              "mask": "255.255.255.0"
            }
          }
        },
        "Cisco-IOS-XE-ethernet:negotiation": {
          "auto": true
        }
      }
    ]
  }
}
```
YANG形式でC8000vの各I/Fの情報が出力されました。

### Python
もちろんPythonのRequestsモジュールを使用してRESTCONF APIを叩くことも可能です。以下のPythonスクリプトでcurlと同様の結果が得られます。

```python:get_interface.py
import sys, requests, urllib3, json

url = 'https://192.168.11.200:443/restconf/data/native/interface'

payload = {}
headers = {
        'Content-Type': 'application/yang-data+json',
        'Accept': 'application/yang-data+json',
        'Authorization': 'Basic <base64エンコードした"user:password">'
    }

urllib3.disable_warnings()

response = requests.request("GET", url, verify=False, headers=headers, data=payload)

print(response.text)
```

# Webex ChatBotとRESTCONFを組み合わせてChatOpsしてみる
PythonでRESTCONF APIが叩けるのであれば、WebexのChatBotと組み合わせてChatOps的な事をすることもできます。[こちら](https://github.com/juliogomez/netdevops#netdevops-demo-4---chatops-is-the-way)を参考にしてWebex ChatBotを作り、コードを書いていきました。検証環境は以下になります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2325351/108bee13-dc81-5e93-1f54-0a6752ef4b36.png)

## ChatBotのコード
Webex ChatBotに対して「/c8kv」で始まるメッセージを送った場合、特定の操作をネットワーク機器に対してRESTCONF APIを叩いて行います。今回はI/Fの設定確認とIPアドレスを変更するコマンドを用意してみました。

```python:chatbot.py
from flask import Flask, request, json
import requests
import sys, urllib3, json
import c8kv_ops

app = Flask(__name__)
port = 5005
base_url = 'https://webexapis.com/v1/'
api_key = '<bot_token>'
headers = {
    "Authorization": f"Bearer {api_key}",
    "Content-Type": "application/json"
}

@app.route('/', methods=['POST'])
def index():
    data = request.get_json()
    bot_id = requests.get(f'{base_url}/people/me', headers=headers).json().get('id')
    # 自分のメッセージは無視する
    if bot_id == data.get('data').get('personId'):
        return 'Message from self ignored'
    else:
        # メッセージを受け取った部屋の情報を収集する
        message_id = data.get('data').get('id')
        message_url = f'{base_url}/messages/{message_id}'
        message_text = requests.get(message_url, headers=headers).json().get('text')
        room_id = data.get('data').get('roomId')
        # /c8kvで始まるメッセージを解読する
        if message_text.startswith('/c8kv'):
            try:
                action = message_text.split()[1]
            except IndexError:
                action = 'nothing'
            # /c8kv get_int int_name
            if action == 'get_int':
                try:
                    int_name = message_text.split()[2]
                except IndexError:
                    reply = 'Incorrect syntax.'
                reply = c8kv_ops.get_int_conf(int_name)
            # /c8kv set_int_ip int_name ip_addr mask
            elif action == 'set_int_ip':
                try:
                    int_name = msg.message_text.split()[2]
                    ip_addr = msg.message_text.split()[3]
                    mask = msg.message_text.split()[4]
                except IndexError:
                    reply = 'Incorrect syntax.'
                reply = csr1kv_ops.set_int_ip(int_name, ip_addr, mask)
            # /c8kv
            else:
                reply = 'Did nothing.'
        # どの操作にも引っかからなかった場合、メッセージをそのまま返す
        else:
            reply = f'You said: "{message_text}"'

        my_msg_data = {
            "roomId": room_id,
            "text": reply,
            }
        post_message_url = f'{base_url}/messages'
        post_message_data = requests.post(post_message_url,headers=headers,data=json.dumps(my_msg_data))

    return data

app.run(host="0.0.0.0", port=port, debug=True)
```

```python:c8kv_ops.py
import requests, sys, urllib3, json

base_url = 'https://192.168.11.200:443/'
headers = {
    'Content-Type': 'application/yang-data+json',
    'Accept': 'application/yang-data+json',
    'Authorization': 'Basic <base64エンコードした"user:password">'
}

# Gets configuration of specific interface.
def get_int_conf(int_name):
    req_url = base_url + f'restconf/data/Cisco-IOS-XE-native:native/interface/{int_name}'
    response = requests.get(req_url,
                            headers=headers,
                            verify=False)
    return response.text

# Sets ip address of specified interface.
def set_int_ip(int_name, ip_addr, mask):
    req_url = base_url + f'restconf/data/Cisco-IOS-XE-native:native/interface/{int_name}/ip/address/primary/address'
    ip_address = { 'address': f'{ip_addr}', 'mask': f'{mask}' }
    response = requests.put(req_url,
                            headers=headers,
                            verify=False,
                            data = json.dumps(ip_address))
    if response.status_code == 204:
        return 'Successfully configured ip address.'
    else:
        return 'Failed to configure ip address.'
```

## ChatBotの準備と起動
ChatBotを使用するためにはWebhookの設定が必要になります。curlを使用してWebhookを作成します。今回は楽をしてngrokも使用しています。

最後にChatBotのアプリを起動して準備完了です。

```terminal
$ ngrok http -region jp 5005
$ curl -X POST https://webexapis.com/v1/webhooks -H 'Authorization: Bearer <bot_token>' -H 'Content-Type: application/json' -d '{"name": "Webhook to Chatbot", "resource": "all", "event": "all", "targetUrl": "<ngrok_url>"}'
$ python3 chatbot.py
 * Serving Flask app "chatbot" (lazy loading)
 * Environment: production
   WARNING: Do not use the development server in a production environment.
   Use a production WSGI server instead.
 * Debug mode: on
 * Running on http://0.0.0.0:5005/ (Press CTRL+C to quit)
 * Restarting with inotify reloader
 * Debugger is active!
 * Debugger PIN: 319-610-158
```

## 実際に使ってみる

### ChatBotからI/F情報の取得
コマンド：/c8kv get_int [インターフェイス名]
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2325351/79424d20-e01b-f7f6-2343-cd61a457c743.png)

### ChatBotからI/FのIPアドレスを変更
コマンド：/c8kv set_int_ip [IPアドレスを変更したいI/F名] [IPアドレス] [サブネットマスク]
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2325351/f5b8c944-09dc-724a-40b3-68b104d90468.png)
ちゃんとIPアドレスが変わっている事が確認できます！

# 参考情報
[Cisco IOS XE Bengaluru 17.6.x プログラマビリティ コンフィギュレーションガイド: RESTCONFプロトコル](https://www.cisco.com/c/ja_jp/td/docs/ios-xml/ios/prog/configuration/176/b_176_programmability_cg/m_176_prog_restconf.html)
[Using a Bot to Operate Your Network!? – Getting Started with ChatOps, Part 1](https://blogs.cisco.com/developer/363-chatops-01?ccid=networking-page&dtid=devblog&oid=juliogomezfy21-q4-0000-chatops02-ww)
[Defining Your Bot Logic – Getting Started with ChatOps, Part 2](https://blogs.cisco.com/developer/chatops02?ccid=networking-page&dtid=devblog&oid=juliogomez-fy22-q2-0000-363-chatops-01-ww)
[Message Details – Getting Started with ChatOps, Part 3](https://blogs.cisco.com/developer/chatops03?ccid=networking-page&dtid=devblog&oid=juliogomez-fy22-q2-0000-363-chatops-01-ww)
[Integration with Chuck Norris and Meraki APIs – Getting Started with ChatOps, Part 4](https://blogs.cisco.com/developer/chatops04?ccid=networking-page&dtid=devblog&oid=juliogomez-fy22-q2-0000-chatops03-ww)
[上記シスコブログシリーズで参照されているGitHub Repo](https://github.com/juliogomez/netdevops#netdevops-demo-4---chatops-is-the-way)

# 免責事項
本サイトおよび対応するコメントにおいて表明される意見は、投稿者本人の個人的意見であり、シスコの意見ではありません。本サイトの内容は、情報の提供のみを目的として掲載されており、シスコや他の関係者による推奨や表明を目的としたものではありません。各利用者は、本Webサイトへの掲載により、投稿、リンクその他の方法でアップロードした全ての情報の内容に対して全責任を負い、本Web サイトの利用に関するあらゆる責任からシスコを免責することに同意したものとします。