netmikoとipywidgetでJupyter NotebookからCisco機器を操作する

netmikoを使うとpython経由でCisco IOSなどのネットワーク機器を操作できるようになります。perlで言うところのNet::Telnet::Ciscoに相当するものです。

今回はipywidgetでJupyter Notebook上にリストボックスを作成し、リストボックスで選択したホスト（Ciscoルータ）に対して「show ip int brief | exc unass」と「show int status | exc disa」を実行するスクリプトを作成します。

まずは、condaやpipで「netmiko」と「ipywidget」をインストールします。私の環境下では以下のとおりインストールしました。

```bash:bash
$ pip install netmiko
$ conda install -c conda-forge ipywidgets
```

次に、Ciscoルータのホスト名とIPアドレスを紐付けるために、sqliteでデータベースを作成します。ここでは「detwork_database」というデータベース名で以下の形式で「router_hosts」テーブルを作成します。

|  ホスト名   | IP Address    |
|:----------:|:-------------:|
| Rtokyo001  | 192.168.1.110 |
| Rosaka001  | 192.168.1.111 |
| Rnagoya001 | 192.168.1.112 |

```bash:bash
$ sqlite3 network_database.sqlite3

SQLite version 3.13.0 2016-05-18 10:57:30
Enter ".help" for usage hints.

sqlite> create table router_hosts(hostname text primary key, ip_address text);
sqlite> insert into router_hosts values('Rtokyo001', '192.168.1.110');
sqlite> insert into router_hosts values('Rosaka001', '192.168.1.111');
sqlite> insert into router_hosts values('Rnagoya001', '192.168.1.112');

sqlite> select * from router_hosts;
Rtokyo001|192.168.1.110
Rosaka001|192.168.1.111
Rnagoya001|192.168.1.112
```
アドレス解決できれば良いので、sqliteではなくCSVやhostsを使っても構いません。

次にJupyter Notebook上で以下のようなスクリプトを入力します（ルータのユーザ名、ログインパスワード、イネーブルパスワードはすべて"cisco"としています）。

```python:python
from IPython.display import display
from IPython.display import clear_output
from netmiko import ConnectHandler
import ipywidgets
import sqlite3

# sqliteに接続
sql_connection = sqlite3.connect('/home/${your_dir}/jupyter_workdir/network_database.sqlite3')
sql_cursor = sql_connection.cursor()

# router_hostsテーブルに登録されているホスト名を読み込む
sql_cursor.execute("""select hostname from router_hosts order by hostname asc;""")

# 読み込んだホスト名をドロップダウンリストに登録する
dropdown_lists=['-']

for sql_fetch_result in sql_cursor.fetchall():
    dropdown_lists.append(sql_fetch_result[0])

dropdown_item = ipywidgets.Dropdown(options=dropdown_lists, value='-')

# ドロップダウンリストの選択肢が変更されるたびに以下の処理を行う
def on_value_change(change):
    # 出力画面をクリアする
    clear_output()
    
    # router_hostsテーブルから選択されたホストのIPアドレスを取得する
    sql_hostname = (change['new'],)
    sql_cursor.execute('select ip_address from router_hosts where hostname=?', sql_hostname)
    ip_address = sql_cursor.fetchone()[0]

    # ciscoルータにアクセスするためのパラメータを設定する
    dev_definition = {
        "device_type": "cisco_ios",
        "ip": ip_address,
        "username": "cisco",
        "password": "cisco",
        "secret": "cisco"
    }
    
    # ciscoルータにアクセスし、"sh ip int brief | exc unass", "show int status | exc disa"を実行する
    connection = ConnectHandler(**dev_definition)
    connection.enable()
    
    output1 = "\n-----\nshow ip int brief | exc unass\n-----\n"
    output1 += connection.send_command("show ip int brief | exc unass")
    print(output1)
    
    output2 = "\n-----\nshow int status | exc disa\n-----\n"
    output2 += connection.send_command("show int status | exc disa")
    print(output2)
    
    connection.disconnect()

# ドロップダウンリストを観測し、選択肢が変わった時にon_value_change関数を実行する
dropdown_item.observe(on_value_change, names='value')

# ドロップダウンリストを出力する
display(dropdown_item)
```

実行すると以下のようなドロップダウンリストが表示され、「router_hosts」テーブルに登録したホストが選択肢として現れます。

![Screenshot from 2017-07-23 16-15-17.png](https://qiita-image-store.s3.amazonaws.com/0/180164/49fc538a-a88d-efa5-bd6e-5cce36a1911f.png "スクリプト実行直後の画面")

ドロップダウンリストからホストを選択すると、その機器のIPアドレスを「route_hosts」テーブルから求めます。求めたIPアドレスのCiscoルータに対してnetmikoを介してログインし、

* show ip int brief | exc unass
* show int status | exc disa

の結果を出力します。

![Screenshot from 2017-07-23 16-16-24.png](https://qiita-image-store.s3.amazonaws.com/0/180164/1d410dd1-6469-0cd6-7620-008bc27af640.png "ホスト選択後の画面")