On-Box Pythonで設定変更後のログ取得＆転送を自動化してみた

# はじめに
Cisco IOS-XEの16.5から、ネットワーク機器内(On-Box)でPythonプログラミングができるようになりました。
本記事では、設定変更をトリガーにして、指定したshowコマンド一式を取得し、TFTPサーバに転送する例をご紹介します。


# 用意した環境
Cisco DevNet Sandbox環境 [IOS XE on Catalyst 9000 Lab](https://devnetsandbox.cisco.com/RM/Diagram/Index/f301b80a-c25c-4650-a6fa-0d7f1440a7ac) を使用しました。
![無題1111.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/310082/2ed08ec6-f16f-e142-18db-eae3dc69c298.png)

今回はCatalyst9300を使用していますが、バージョン要件を満たすIOS-XEであれば問題ないと思います。
TFTPサーバは、Sandbox内に用意されていたCentOSを使いました。ほんと至れり尽くせりですね！

| 用途       | 機種    | バージョン   | 備考 |
|:-----------------|:------------------|:------------------|:------------------|
| 検証対象機器(IOS-XE)   | Catalyst9300-24UX  | 16.8.1a ||
| 検証対象機器(ゲストシェル)   | CentOS  | 7.6.1810 | Python=2.7.5   |
| TFTPサーバ   | CentOS | 7.3.1611||


# TFTPサーバのセットアップ
以下の記事を参考にさせて頂きました。
[TFTPサーバの構築(CentOS7)](https://ac-5.net/linux/tftpserver)

TFTPサーバのアップロード先のディレクトリパスは、デフォルトで`/var/lib/tftpboot`になっています。
今回はログ専用の保管先として、配下にディレクトリ`cisco_log`を作成しました。

```
[root@localhost /]# mkdir /var/lib/tftpboot/cisco_log
```

# ゲストシェル(仮想Linux環境)のセットアップ
以前投稿した記事と同じ手順でセットアップしました。

[[ネタ投稿]Cisco On-Box Pythonを使ってログインバナーをどうぶつの森風にしてみた](https://qiita.com/tech_kitara/items/ebf16eccc70f353421ab#%E3%82%B2%E3%82%B9%E3%83%88%E3%82%B7%E3%82%A7%E3%83%AB%E4%BB%AE%E6%83%B3linux%E7%92%B0%E5%A2%83%E3%81%AE%E3%82%BB%E3%83%83%E3%83%88%E3%82%A2%E3%83%83%E3%83%97)

作成したログファイルは`flash:`に保存されるようにします。ゲストシェルからは、ルートディレクトリ直下の`/flash`として見えます。

```text:IOS-XE
cat9300-1#dir
Directory of flash:/

639009  -rw-            24197  May 11 2019 12:47:51 +00:00  cat9300-1_20190511.log
```

```text:ゲストシェル
cat9300-1#guestshell
[guestshell@guestshell ~]$ pwd
/home/guestshell
[guestshell@guestshell ~]$ cd /
[guestshell@guestshell /]$ ls
bin        data         flash  local       opt   sbin     tmp
boot       dev          home   lost+found  proc  selinux  unfix_sshd.sh
bootflash  etc          lib    media       root  srv      usr
cisco      fix_sshd.sh  lib64  mnt         run   sys      var
[guestshell@guestshell /]$ ls -l flash
lrwxrwxrwx 1 root root 10 Feb 15  2018 flash -> /bootflash
[guestshell@guestshell /]$ cd /flash
[guestshell@guestshell flash]$ ls -l cat9300-1_20190511.log
-rw-rw-r-- 1 65534 network-admin 24197 May 11 12:47 cat9300-1_20190511.log
```


# Pythonスクリプトの作成
おおまかな手順は以下の通りです。

- `get_hostname`で、Configからホスト名を抽出。(ログファイル名で使用。)
- `get_log`でshowコマンドを取得し、ローカルファイル保存。
- `file_transfer`でCatalyst9300からTFTPサーバへファイル転送。  
※冒頭で`file prompt quiet`コマンドを実行していますが、これは後続の`copy`コマンド実行時、宛先IPアドレスやファイル名を聞かれないようにするためです。  
※TFTPサーバ接続IFがVRF`Mgmt-vrf`に属しているため、`copy`コマンドでvrf指定しています。 


```python:log_transfer2.py
from cli import configure, cli
import datetime

# Set Global variables to be used in later functions
tftp_path = '10.10.20.20/cisco_log'
command_list = ['show clock', 'show version', 'show running-config', 'show environment all']


def get_hostname():
    sh_run_host = cli('show run | include hostname')
    host_strip = sh_run_host.strip()
    hostname = host_strip.lstrip('hostname ')
    print 'Hostname of this device is %s' % (hostname)
    return hostname


def get_log(hostname, command_list):
    filename = hostname + '_' + datetime.date.today().strftime('%Y%m%d') + '.log'
    filepath = '/flash/' + filename
    with open(filepath, 'w') as f:
        for command in command_list:
            f.write('\n-------- ' + command + ' --------\n')
            output = cli(command)
            f.write(output)
    return filename


def file_transfer(tftp_path, file, file_system='flash:/'):
    source = file_system + file
    # Set commands to prepare for file transfer
    tftp_commands = ['file prompt quiet', 'ip tftp blocksize 8192']
    results = configure(tftp_commands)
    print '*** Successfully configure "file prompt quiet" on switch ***'
    transfer_file = "copy %s tftp://%s/%s vrf Mgmt-vrf" % (source, tftp_path, file)
    print 'Transferring %s to %s' % (file, tftp_path)
    transfer_results = cli(transfer_file)
    if 'copied' in transfer_results:
        print '*** %s was transferred successfully!!! ***' % (file)
    elif 'Error opening' in transfer_results:
        raise ValueError("XXX Failed Xfer XXX")


def main():
    print '###### STARTING LOG TRANSFER SCRIPT ######'
    print '\n*** Obtaining hostname of device.. ***'
    hostname = get_hostname()
    print '*** Getting logs.. ***'
    filename = get_log(hostname, command_list)
    print '*** Transferring log file to TFTP server.. ***'
    file_transfer(tftp_path, filename)
    print '###### FINISHED LOG TRANSFER SCRIPT ######'


if __name__ in "__main__":
    main()
```

# NW機器側のCLI設定
Pythonスクリプト実行のための条件をEEMで記述します。今回はSyslog発生をトリガーにしています。
具体的には、グローバルコンフィグレーションモードから特権モードに戻る時に発生する`%SYS-5-CONFIG_I:～`を検知した場合に、`log_transfer2.py`を実行するようにしています。

```text:設定Config
event manager applet LOG_TRANSFER
 event syslog pattern "%SYS-5-CONFIG_I: Configured from"
 action 1.0 cli command "enable"
 action 2.0 cli command "guestshell run python /flash/log_transfer2.py --apply"
```

なお、EEM実行時、Pythonスクリプト内の`print`コマンドは標準出力されません。
出力結果を見ながら、スクリプト単体の動作確認をしたい場合は、以下コマンドを実行します。

```
cat9300-1#guestshell run python /flash/log_transfer2.py --apply
```


# 実行後の確認
適当に設定変更を行い、以下のshowコマンドで問題なく動作していること(Triggeredのカウントアップがみられること)を確認します。

```text:確認コマンド
cat9300-1#sh event manager statistics policy

                                       Average        Maximum
No.  Class     Triggered   Suppressed  Run Time       Run Time       Name
-------------------------------------------------------------------------------
1    applet    2           0           0.406          0.456          intShut
 event {} syslog

2    applet    13          0           6.404          20.042         LOG_TRANSFER
 event {} syslog
```

TFTPサーバ側で、ファイル転送されていることを確認します。

```text:TFTPサーバ出力結果
(python2) [root@localhost /]# cd /var/lib/tftpboot/cisco_log
(python2) [root@localhost cisco_log]# ls -l
total 24
-rw-rw-rw-. 1 root root 24197 May 11 14:38 cat9300-1_20190511.log
(python2) [root@localhost cisco_log]# cat cat9300-1_20190511.log

-------- show clock --------

*12:47:51.815 UTC Sat May 11 2019

-------- show version --------

Cisco IOS XE Software, Version 16.08.01a
Cisco IOS Software [Fuji], Catalyst L3 Switch Software (CAT9K_IOSXE), Version 16.8.1a, RELEASE SOFTWARE (fc1)
～～省略～～

-------- show running-config --------

Building configuration...
Current configuration : 19512 bytes
!
! Last configuration change at 12:47:49 UTC Sat May 11 2019 by admin
!
version 16.8
～～省略～～
```

# 最後に
プログラミングの勉強と、日々もっと楽をしたい思いから作ってみました。
ただこのスクリプトだと、グローバルコンフィグレーションモードに入ったものの、設定変更しなかった場合もログ取得が走ってしまうので、もうちょっと考慮が必要かも知れません。