[ネタ投稿]Cisco On-Box Pythonを使ってログインバナーをどうぶつの森風にしてみた

# はじめに
Cisco IOS-XEの16点台の途中から、ネットワーク機器内(On-Box)でPythonプログラミングができるようになりました。これまでもEEMやTCLを使って、条件に応じたアクションを定義することはできましたが、汎用言語が使えることで外部システムとの連携など、応用の幅が広がりました。
本記事では、ログイン時に表示されるバナーを、時間に従って自動的に変更させるPythonスクリプトを作成してみました。

# 本記事の背景
街に出ればクリスマス一色。片や自分は、休日に家に籠ってQiita投稿。これはこれで楽しいんですが、だんだん気持ちが枯れていくんです。癒しが欲しい！せめて日々の業務の中で、ネットワーク機器にちょっとだけ癒されたい。なんか最近出回ってきた、On-Box Pythonで何かできないかな？

あっ、これだ！！
![8-2.png](https://qiita-image-store.s3.amazonaws.com/0/310082/cafe309a-5524-609a-8c5f-2f95a7029323.png)

ニンテンドー3DS「どびだせ どうぶつの森」のログイン画面で、毎回しずえさんに癒されてたっけなー。
Pythonでシステム時刻を読み取って、時間帯ごとに挨拶の内容を変えれば行ける！
あとはしずえさんのAAを挿し込めば...!

[残念なお知らせ]
Ciscoログインバナーの文字数制限、半角文字縛り、自分の画才の無さで、AAが酷いことになりました。しずえさんLOVE LOVEの皆さんは、ここで**いいね!**だけ押して画面を閉じて下さい。

# 用意した環境

### Cisco 1000V (16.08.01)
- Cisco DevNetにCisco 1000Vのサンドボックスがあり、開発者やNWエンジニアが、無償でスクリプト等の動作を確認できます。いつでも使える「Always-on」と、予約が必要な「Reservable」があります。https://developer.cisco.com/site/ios-xe/
- 「Always-on」はTeraTerm等の通信ソフトがあればすぐに使えるので、ちょっと動作を確認したい時に便利だと思います。ただし、他のユーザと共有していますので、お互いの設定変更内容が影響したり、セキュリティリスクがあったりします。あとレスポンスも良くなかったです。
- 「Reservable」は専用のCisco VPNソフト(Cisco AnyConnect Secure Mobility Client)を使い、VPN上でSSHログインします。もちろん環境を占有でき、1回の予約で7時間使えました。画面例は以下の通りです。
![2-3.png](https://qiita-image-store.s3.amazonaws.com/0/310082/8255020f-7fa9-c97e-d891-f30dcb27025b.png)

# ゲストシェル(仮想Linux環境)のセットアップ

以下のURLを参考にしました。
https://developer.cisco.com/docs/ios-xe/#!on-box-python-and-guestshell-quick-start-guide/onbox-python
大まかな手順は以下の通りです。

- IOxアプリケーションフレームワークの有効化
- ゲストシェルバーチャルインターフェースの設定
- ゲストシェルの有効化
- ゲストシェルへのログイン

```text:
csr1000v#conf t
Enter configuration commands, one per line.  End with CNTL/Z.
csr1000v(config)#iox
csr1000v(config)#app-hosting appid guestshell
csr1000v(config-app-hosting)#vnic management guest-interface 0
csr1000v(config-app-hosting)#end
csr1000v#guestshell enable
Interface will be selected if configured in app-hosting
Please wait for completion
guestshell installed successfully
Current state is: DEPLOYED
guestshell activated successfully
Current state is: ACTIVATED
guestshell started successfully
Current state is: RUNNING
Guestshell enabled successfully
 
csr1000v#guestshell
[guestshell@guestshell ~]$
```

# Pythonスクリプトの作成

`/bootflash`ディレクトリ(NW機器側のbootflash:に対応)配下に、`shizue.py`というファイルを作成します。

```bash:
[guestshell@guestshell ~]$ cd /bootflash
[guestshell@guestshell bootflash]$ touch shizue.py
[guestshell@guestshell bootflash]$ ls
core                                        packages.conf
csr1000v-mono-universalk9.16.08.01.SPA.pkg  running-config.bak
csr1000v-rpboot.16.08.01.SPA.pkg            sbx_iosxe_baseline.txt
csrlxc-cfg.log                              shizue.py
cvac.log                                    throughput_monitor_params
iox                                         tracelogs
lost+found                                  virtual-instance
onep                                        vman_fdb
ovf-env.xml.md5
[guestshell@guestshell bootflash]$ vi shizue.py
"shizue.py" 0L, 0C~
~
```
vimで挿入したPythonスクリプトは以下の通りです。大まかには以下の3つをやっています。

- システム内の現在時刻を取得。DevNetはUTC(標準時間)表記だったので、JST(+9時間)に変更。時刻から`date`(曜日、年月日)、`time`(時間、分、AM/PM)を取得して変数に格納。
- 3つの時間帯毎に、挨拶文`greeting_word`としずえさんの「目」`eyes`を定義。具体的には、午前4~11時台は`morning`と`* *`(寝起き?の目)、午後12～17時台は`afternoon`と`^ ^`(機嫌良さげな目)、それ以外の時間帯は`evening`と`= =`(眠そうな目)にしました。
- `conf`にログインバナーコマンドを格納。上記の変数もコマンド内に埋め込みます。これを`configurep`でコマンド実行します。(補足：通常、複数行にわたる設定は`,`(カンマ)で区切ったリスト構造で定義しますが、バナーの場合扱いが異なり、区切らずに改行コード`\r`(\015)を使う必要があります。またAAで使う文字のエスケープ処理も必要です。)

```python:shizue.py
from cli import cli, configurep
from datetime import datetime, timedelta

# Get time information from system and convert to JST
jpn_time = datetime.now() + timedelta(hours=9)
date = jpn_time.strftime('%a %b %d %Y')
time = jpn_time.strftime('%I:%M, %p.')

# Generate greeting word
if jpn_time.hour >= 4 and jpn_time.hour <= 11:
    greeting_word = 'morning'
    eyes = '* *'
elif jpn_time.hour >= 12 and jpn_time.hour <= 17:
    greeting_word = 'afternoon'
    eyes = '^ ^'
else:
    greeting_word = 'evening'
    eyes = '= ='

# Execute login banner command
conf = ['banner login #\015      ___\015     /   |\015     |  /\015     @~~|\015 / ^^^^^^^^|\015|  | '+ eyes + ' |  |\015|__/  @  |__/\015     \'-\'\015 ________\015| Shizue |\015| Good ' + greeting_word + ', Mayor!\015| It\'s ' + date + ', and the\015| current time is ' + time + '\015| Shall we get started?\015#']
configurep(conf)
```

# NW機器側のCLI設定

Pythonスクリプト実行のための条件はEEMで記述します。今回は180秒毎にPythonスクリプトを実行するための設定を行います。

```text:設定config
event manager applet MOTD
 event timer watchdog time 180
 action 1.0 cli command "enable"
 action 1.1 cli command "guestshell run python /bootflash/shizue.py --apply"
```
設定後、showコマンドで問題なく動作していること(Triggeredのカウントアップがみられること)を確認します。

```text:確認コマンド
csr1000v#sh event manager statistics policy

                                       Average        Maximum
No.  Class     Triggered   Suppressed  Run Time       Run Time       Name
-------------------------------------------------------------------------------
1    applet    53          0           2.437          2.641          MOTD
 event {} timer watchdog (Time Remaining: 55.601)
```

# 実行結果

実際にログインした結果は以下の通りです。

```text:午後の場合
      ___
     /   |
     |  /
     @~~|
 / ^^^^^^^^|
|  | * * |  |
|__/  @  |__/
     '-'
 ________
| Shizue |
| Good morning, Mayor!
| It's Sun Dec 09 2018, and the
| current time is 11:51, AM.
| Shall we get started?
```

```text:午後の場合
      ___
     /   |
     |  /
     @~~|
 / ^^^^^^^^|
|  | ^ ^ |  |
|__/  @  |__/
     '-'
 ________
| Shizue |
| Good afternoon, Mayor!
| It's Sun Dec 09 2018, and the
| current time is 12:27, PM.
| Shall we get started?
```

夜まで待っているのもバカらしいので、夜の場合は時刻を操作して確認しました(リセットさんに怒られそうww)

```text:夜の場合
      ___
     /   |
     |  /
     @~~|
 / ^^^^^^^^|
|  | = = |  |
|__/  @  |__/
     '-'
 ________
| Shizue |
| Good evening, Mayor!
| It's Sun Dec 09 2018, and the
| current time is 19:30, PM.
| Shall we get started?
```

# 最後に

一応動くものが作れました。(癒されるかどうかは別として。。) 時間帯で全く別のキャラクターにすることもできますし、可能性は無限大です!
みなさんも好きなキャラクターで試してみてはいかがでしょうか？
![8-4.jpg](https://qiita-image-store.s3.amazonaws.com/0/310082/5a4c1d2b-843a-2f62-886e-fcd3010c4bbb.jpeg)