netmikoで複数機器にコマンドを流し込む（OS自動検出、csv読み込み）

# （追記）この記事を読む前に
[clogin, jloginやろうとしていたことができる](https://codeout.hatenablog.com/entry/2015/12/18/110745)でことを発見したのでLinuxユーザーの方はrancidをインストールしてもっと手軽にネットワーク自動化をしましょう！


# 概要
netmikoを使って複数の機器に複数のコマンドを実行してログ取得できるプログラムを書きました。[ソースコードはこちら](https://github.com/tomato15/netmiko-multiple-connections-with-csv)です。

今後自分で拡張することや、git cloneなどして活用していただくことを想定してコードを書いています。サンプルのcsvファイルもあるのcloneして使ってください。
```
git clone https://github.com/tomato15/netmiko-multiple-connections-with-csv
```



# 実行環境
- python3.9.4（python3.6でも動くことを確認済み）

- 接続先としてはjunos, cisco_iosはテスト済み
- netmikoとping3が必要。`pip install netmiko ping3`
- VSCodeで作業する方は.csvの編集に[Rainbow CSV](https://marketplace.visualstudio.com/items?itemName=mechatroner.rainbow-csv)という拡張機能があると見やすくなります。


# 実用的にするための工夫
### OSの自動検出
- 接続先のOSは自動検出します。

- 自動検出が可能かはnetmikoのバージョンに依存する部分はあるが、[autodetectのソースコード](https://github.com/ktbyers/netmiko/blob/develop/netmiko/ssh_autodetect.py)を見る限りCisco系、Juniper系は問題ないです。

### ログ出力
192.168.0.1にSSH成功、192.168.0.2はSSH失敗だった場合は以下のような形で.logができます。

``` 
├── commandlist.csv
├── hostlist.csv
├── netmiko-multiple-connections.py
└── log-20220824-135851
    ├── 192.168.0.1-20220824-135851-JST.log
    └── 192.168.0.2-20220824-135851-JST-SSHTimeoutError.log
```
- .pyが実行されたディレクトリ上に、log-yyyymmdd-hhmmssというディレクトリを作成します。

- 作成されたディレクトリ上に、.logファイルを保存します。
- SSHに失敗した場合はエラーを示す文字列が.logのファイル名末尾に追加されます。
- hostlist.csvに書いたsecretは漏洩防止のためnetmikoが.log上で********に置換してくれます。secretがenとかだとログ上の全ての'en'が置換されてしまうのはちょっと難点ですが仕様なので許容。。




### コンソールでの確認
192.168.0.1でpingを実行し、192.168.0.2はSSH失敗だった場合は以下のような形で出力されます。
```
============================== ping 8.8.8.8 @192.168.0.1 ==============================
cisco>ping 8.8.8.8
Type escape sequence to abort.
Sending 5, 100-byte ICMP Echos to 8.8.8.8, timeout is 2 seconds:
!!!!!
Success rate is 100 percent (5/5), round-trip min/avg/max = 4/7/8 ms

================================================================================

2022-08-27 11:51:11 - INFO - multi_connections - SuccessfullyDone: 192.168.0.1

2022-08-27 11:51:32 - ERROR - ping_check - PingTimeout: 192.168.0.2
2022-08-27 11:51:32 - ERROR - multi_connections - SSHTimeoutError: 192.168.0.2
```


- ちゃんと実行できているかを.logを都度開いて確認するのは手間なので、コンソール上で実行結果を出力します。

- 可読性を上げるためにホスト名と実行内容を示す区切りが表示されます。
- 一通りのコマンドが問題なく実行できた場合、INFOとしてログが表示されます。
- SSHが失敗している場合、ERRORとしてpingの成功/失敗とSSHのエラーログが表示されます。
- SSHがうまく行かないときに最初に疑うのは疎通可否だと思うので、自動でpingをチェックしてくれるようにしています。
- ただし、ping3はICMPメッセージの送受信にroot権限が必要なOSでは実行できません。Ubuntu 16.04では権限が必要でした。その場合は、PermissionErrorというエラーが表示されるだけで、その他の処理ができないなどの影響はありません。


# 使い方
### 0. pip insatllで環境を整える
- requirement.txtを使って必要なパッケージをダウロードします。
`pip install -r requirement.txt`

- pipの環境を分けたい場合は、venvなどを利用しましょう。初学者の方はとりあえず気にしなくても動作に問題はありません。


### 1. hostlist.csvに接続先の情報を書く
- hostの列はIPアドレスを記入します。名前解決できるならホスト名でも大丈夫なようです。

- usernameとpasswordは接続先にログインするための認証情報を記入します。
- secretはenable時に求められるパスワードです。Junosでは不要。
- カンマの後にスペースを入れないよう注意してください。' cisco'という文字列で認証しようとするため。

### 2. commandlist.csvに実行したいコマンドを書く
- ter len 0やenableは処理はnetmikoで実行してくれるので書く必要はないです。

### 3. pythonを実行する
- 二つのcsvファイルがあるディレクトリで、`python3 netmiko-multiple-connections.py`を実行する。

- 他のディレクトリから実行したい場合は、.pyの中のHOSTLISTとCOMMANDLISTでパスを書き換える必要があります。


# 応用例
- hostlist.csvやcommandlist.csvの列を増やして読みませるパラメータの拡張ができます。

- genie parserやtextfsmでパースして必要な情報の抽出や正誤の判定。genieでパースできるコマンドは[こちら](https://pubhub.devnetcloud.com/media/genie-feature-browser/docs/#/parsers)で調べられます。
- 複数機器で同じconfigを変更することもできます。（netmikoにメソッドあり）


# そもそもの動機

私はNWのSIerなので、案件ごとに検証環境を作ったり、お客さんごとにルールがあったりします。

そうすると自動化ツールを継続的に使うことが難しく、結果的にレガシーなteratermマクロに皆さん落ち着いていくようです。

そんな中、案件でこれまで使われていたteratermマクロが設計の変更でうまく動かない部分が出てきてしまいました。

ただ、今後使われなくなっていくであろうteratermを勉強するのは嫌だ。。という気持ちが拭い切れず自分でpythonを書く事にしました。

手始めに、事前や事後のログ取得や実環境の調査でよくやる「複数機器に対して同じコマンドを流し込む」作業のpython化に取り組んだのが今回のコードです。

簡単な要件としては、

- まとめてのログ取得やちょっとした検証で手軽に使いたい＆使ってほしいので、都度.pyを書き換えるのではなく、csvでホスト情報やコマンドをいじるだけにしたい。

- .yamlもあるが馴染みのありそうな.csvを採用。（ansibleやpyatsなどの良いツールは既に存在しているものの、ややオーバースペックに感じた。）

という感じです。


# 今回勉強したことメモ

### netmiko関連
[公式ドキュメント](https://ktbyers.github.io/netmiko/docs/netmiko/index.html)を読むのに慣れてきた頃に「これnetmiko側で準備してくれてるから自分で書く必要なかったのか、、」と気付く事があったので備忘として残します。
- ディレクトリの存在を確かめて、なければ作る、というメソッドが[utilities](https://ktbyers.github.io/netmiko/docs/netmiko/utilities.html)にある。logフォルダを作るときに使っています。その他にも役に立つものが揃っているようです。

- ConnectHandlerに渡すdictで[session_log](https://ktbyers.github.io/netmiko/docs/netmiko/index.html#netmiko.BaseConnection.serial_login)を使えばログを自動で保存してくれる。（わざわざ.logをopen()する関数を書いていた。。）
- [send_command()](https://ktbyers.github.io/netmiko/docs/netmiko/index.html#netmiko.BaseConnection.send_command)のデフォルト出力にはプロンプトとコマンドが含まれないが、strip_prompt = Falseにすれば返り値の文字列に入れてくれる。strip_commandも同じ。（わざわざfind_prompt()と組み合わせて書いていた。。）


### 「python 脱初心者」
などと検索してyoutubeやブログをソースに勉強しました。今回は最終的に使わなかったけれど、デコレータ、内包表記、lambdaは使い方を勉強できて楽しかったです。勉強して取り入れているものは以下です。
- f-string：文字列を+で繋げて見づらいコードになっていた。。

- tyr-exceptのエラーハンドリング：SSHが失敗した原因が認証なのかタイムアウトかなのか一目でわかるようにしておくのは大切。

- loggerでのログ出力：printですべきでないということで使い方を調べて実装した。

- 綺麗な書き方（スタイルガイド）：flake8やhackingを活用して読みやすいコードにしました。

- アノテーション：必須ではないけど今後のために関数には書くようにしました。


NWの自動化は利用環境によって色々エラーを踏みやすいと思うので特にエラーハンドリングは大切だなと思いました。






# 参考
ここに載せられていないものも多数ありますが、先人達からたくさん学ばせていただきました。

https://qiita.com/_masa_u/items/b21c493a82e8aba8993f

https://qiita.com/inetcpl/items/af42b8221d9447637449

https://qiita.com/akira6592/items/feff470e0093d0b7ca43

https://qiita.com/sourjp/items/b247fc5d4386b9d30e3d

https://ktbyers.github.io/netmiko/docs/netmiko/index.html#netmiko.BaseConnection.find_prompt