[Python] 生まれて初めてのPython

事の経緯

これまで何度かSDNに手を出しては、Ryu/Lagopusをウンウン悩みながら触ってきたんですが、やっぱコードが読めないのは辛い(Windowsバッチやシェルスクリプトはナンボか書いてるので、全く読めないわけじゃないけど)ので、触ってみないといけないのかなー・・・逃れられないのかな~・・・と思いつつ、十数年ぶりにプログラム言語なるものに触れてみることにしました。

スクリプトでもやってやれないことは多分ないんですが、コアL3をちょっとだけ弄るものを作ってみました。

筆者のレベル

ぶっちゃけコード書いたりしてたのは2003年の秋頃が最後、VBScriptを最後に触ったのが2005年頃だったと思うので、十年以上プログラムをちゃんと書いたことはないっす。シェルスクリプトもめったに書きませんし、書くのが怖い派です。

環境の構築

取り敢えず、Pythonを書いて実行するための環境を作ってみることにしました。

OSインストール直後のPythonのバージョン

CentOS7を導入し、yum updateをかけた直後の状態はこんな感じです。

# python --version
Python 2.7.5

Pythonは今3系が主流らしく、2系は記法が異なるとのことで、3系の導入が勧められてるようです。

IUSリポジトリの追加

というわけで、今回別途Python3系を導入します。標準リポジトリにはないようなので、IUSと言うリポジトリを追加しました。

# yum install -y https://centos7.iuscommunity.org/ius-release.rpm

Python3.5のインストール

今回、Python3.5系を導入しました。yumでIUSリポジトリを使って導入します。「まずはじめにこれ入れとけ」と言うやつに「pip」「setuptools」というのがあるらしく、取り敢えず言われるがままに導入することにしました。

# yum -y install python35u python35u-libs python35u-devel python35u-pip python35u-setuptools

Python3コマンドの確認・リンク貼り

Python実行環境だと「python3」コマンドでプログラムを実行させるケースが多いんですが、実際パスは通ってるんでしょうかって確認した所

# python3 --version
bash: python3: コマンドが見つかりませんでした...
よく似たコマンドは: 'python'
# python3.5 --version
Python 3.5.4

ということで、「python3.5」と打ち込まないと動かないようです。なので、シンボリックリンク張ります。

# ln -s /usr/bin/python3.5 /usr/bin/python3

pipも同様でした。ので、「pip3」で実行できるように同様にシンボリックリンクを張ります。

# ln -s /usr/bin/pip3.5 /usr/bin/pip3

導入済みモジュールの確認

pip3コマンドを使用して、導入済みモジュールの確認が行えるようなので打ち込んでみます。

# pip3 list
DEPRECATION: The default format will switch to columns in the future. You can use --format=(legacy|columns) (or define a format=(legacy|columns) in your pip.conf under the [list] section) to disable this warning.
pip (9.0.1)
setuptools (36.6.0)

おりょ、結果は出たのですが、Warningらしきメッセージが出てます。どうやら出力フォーマットが近い将来変わるよん!と言われてるようです。取り敢えず無害っぽいんですが、毎度毎度このメッセージが出るのもアレなので対処します。

対処としては、~/.config/pip/pip.conf を作成し、設定を入れる必要があるみたいです。

# cd .config
# mkdir pip
# cd pip
# vi pip.conf
-----以下設定を記述して保存
[list]
format = columns
-----

再度pip3コマンドを叩いて、導入済みモジュールを確認します。

# pip3 list
Package Version
---------- -------
pip 9.0.1
setuptools 36.6.0

おお、警告がでなくなりました。表形式になり読みやすくなりましたね。

何かモジュールをインストールしてみる

pip3を使用することで、追加モジュールを導入し、コレを使用することでより抽象化されたプログラム記述が可能になるとのことで、様々なモジュールが用意されてるようです。ライブラリみたいなもんかな?試しにjinja2モジュールを導入してみます。

# pip3 install jinja2
Collecting jinja2
 Downloading Jinja2-2.10-py2.py3-none-any.whl (126kB)
 100% |????????????????????????????????| 133kB 1.5MB/s
Collecting MarkupSafe>=0.23 (from jinja2)
 Downloading MarkupSafe-1.0.tar.gz
Installing collected packages: MarkupSafe, jinja2
 Running setup.py install for MarkupSafe ... done
Successfully installed MarkupSafe-1.0 jinja2-2.10
#

ほほう、どうやらモジュールがダウンロードされ、適用されたみたい。導入済みモジュールの確認をしてみます。

# pip3 list
Package Version
---------- -------
Jinja2 2.10
MarkupSafe 1.0
pip 9.0.1
setuptools 36.6.0

依存関係にあるモジュールも併せて導入してくれるみたいですね。ありがたや~。

取り敢えずコレでPythonの実行環境は出来上がったっぽいです。

コアL3のデフォルトゲートウェイをPythonでイジらせてみる

実行環境は出来上がったので、取り敢えずやりたい事を考えてみたのですが、簡単な操作として以下の様なことを思いつきました。

やりたい事

こんなことをやらせてみたいと思います。

元々、自宅の本番環境にはゲートウェイが複数あります。通常、IPv4に関しては赤線の経路でインターネットに抜けるようにしています。が、時々UTM側のウイルススキャン機能の影響なのか、上手くダウンロード等が行えないケースが有り、その場合にVPNルーターを代替経路として使用することがたまーにあります。

この時、いつもはスイッチへTELNETでログインし、コマンド叩いてたんですが、これをPythonで自動化させたらどうなるのかなーと思って、コードを書いてみました。参考にしたのは以下の情報です。

まずはモジュールの追加

今回、参考にしたコードではpexpectモジュールが使われています。これをpip3でインストールしておきます。

# pip3 install pexpect
Collecting pexpect
 Downloading pexpect-4.3.1-py2.py3-none-any.whl (55kB)
 100% |????????????????????????????????| 61kB 740kB/s
Collecting ptyprocess>=0.5 (from pexpect)
 Downloading ptyprocess-0.5.2-py2.py3-none-any.whl
Installing collected packages: ptyprocess, pexpect
Successfully installed pexpect-4.3.1 ptyprocess-0.5.2
# 

# pip3 list
Package Version
---------- -------
Jinja2 2.10
MarkupSafe 1.0
pexpect 4.3.1
pip 9.0.1
ptyprocess 0.5.2
setuptools 36.6.0

実際のコード

こんなんなりました。まずは本体であるpc6224_autocmd.pyっす。

import pexpect
import sys
import time
from datetime import datetime

def login(ipaddr, passwd):
    child = pexpect.spawn("telnet " + ipaddr)
    logname = "./log/" + "log_" + ipaddr + \
    "_" + datetime.now().strftime("%s") + ".log"
    wb = open(logname, 'wb')
    child.logfile_read = wb

    expect_list = [u"#",
        u">",
        u"User:",
        u"Password:",
        u"Connection closed by foreign host.",
        u"Login incorrect"]

    index = child.expect(expect_list)

    while True:
        if index == 0: # success to login.
            return child
        elif index == 1: # need to promoted to enable mode.
            child.sendline("enable")
            index = child.expect(expect_list)
        elif index == 2 : # need to input "admin".
            child.sendline("admin")
            index = child.expect(expect_list)
        elif index == 3: # need to input password.
            child.sendline(passwd)
            index = child.expect(expect_list)
        elif index == 4: # Connection is closed.
            print("Unmatched password, or connection is closed.")
            return -1
        elif index == 5: # incorrect password.
            print("\nFault: incorrect password.")
            return -1

def exec_command(commands, child):
    expect_list = [u"Connection closed by foreign host.",
                   u"#"]
    for c in commands:
        time.sleep(0.1)
        child.sendline(c)
        index=child.expect(expect_list)
        if index == 0:
            print("\nCommand execute completed.")
            return -1

def main():
    (acc_ip, acc_pwd, acc_filename) = (sys.argv[1], sys.argv[2], sys.argv[3])
    conn=login(acc_ip, acc_pwd)
    acc_cmdfile=open(acc_filename,"r")
    acc_cmd=acc_cmdfile.readlines()
    exec_command(acc_cmd,conn)
    time.sleep(0.1)
    acc_cmdfile.close()

if __name__== "__main__":
    main()

第一引数にIPアドレス、第二引数に管理者アカウントパスワード、第三引数にコマンドを羅列したテキストファイルを指定します。テキストファイルは以下の2つを用意しました。

pc6224_routechange.txt

conf
no ip route 0.0.0.0 0.0.0.0 192.168.100.4
exit
quit

pc6224_routereverse.txt

conf
ip route 0.0.0.0 0.0.0.0 192.168.100.4
exit
quit

実際の流れとしては

  • 第一引数・第二引数を元にコアL3スイッチへTELNET接続をする
  • ファイルを読み出し、リスト変数として格納する
  • 事前に接続した情報を使用して、リスト変数の内容を1行ずつ実行する
  • 接続断を検知したら処理を終了する(このあたりがちょいと怪しい)

と言う感じです。手探りで書いたので、内容がしっかり理解できてるわけじゃーござーせん。

特徴的だなーと感じた所

Python書いてみて特徴的だなーと感じたところを記載します。

  • 入れ子はインデントで表現する(4文字以上の空白、もしくはタブ文字が1セット)
  • 型宣言はしなくていい
  • 配列すら特に宣言いらない、逆に言えば型の把握は気をつけたほうが良いかもしれない
  • case文がない。なので、if文を連発する必要があるみたい
  • インタプリタ実行である(頭から順に読み出すことを考慮する必要がありそう)

特にインデントの表現は「?」と言う感じで、それなりに苦労しました。また、importの表現ですが、C言語で言う「#include」相当なのは分かるんですけど、どうやらfromと言う文字列と組み合わせてクラス書式の簡略化が出来るみたいですね。色々工夫されているんだなーと感じました。

実行してみる

というわけで、出来上がったコードを実行してみます。経路切替を実行。

# python3 pc6224_autocmd.py 192.168.100.254 ********* pc6224_routechange.txt

TELNETでL3に接続し、show ip routeを叩くとデフォルトゲートウェイがOSPFベースのものに変わってました。

O E2 0.0.0.0/0 [110/1] via 192.168.100.1, vlan 10

経路切り戻しを実行。

# python3 pc6224_autocmd.py 192.168.100.254 ********* pc6224_routereverse.txt

同様にL3に接続して確認。スタティックルートが復活しました。

S 0.0.0.0/0 [1/0] via 192.168.100.4, vlan 10

ログも出力してます。

# ls log -al
drwxr-xr-x 2 root root 4096 1月 15 15:23 .
dr-xr-x---. 17 root root 4096 1月 15 15:13 ..
-rw-r--r-- 1 root root 149 1月 15 14:54 log_192.168.100.254_1515995666.log
-rw-r--r-- 1 root root 347 1月 15 14:54 log_192.168.100.254_1515995680.log
-rw-r--r-- 1 root root 344 1月 15 14:56 log_192.168.100.254_1515995791.log
-rw-r--r-- 1 root root 347 1月 15 15:23 log_192.168.100.254_1515997387.log
-rw-r--r-- 1 root root 344 1月 15 15:23 log_192.168.100.254_1515997398.log

ログの内容はこんな感じです。

# cat log/log_192.168.100.254_1515997398.log
Trying 192.168.100.254...
Connected to 192.168.100.254.
Escape character is '^]'.

User:admin
Password:********
hatomugi>enable

hatomugi#conf

hatomugi(config)#
hatomugi(config)#ip route 0.0.0.0 0.0.0.0 192.168.100.4

hatomugi(config)#
hatomugi(config)#exit

hatomugi#
hatomugi#quitConnection closed by foreign host.

これをHinemosのジョブ管理機能と組み合わせたりすると色々便利なのかなーとかおもったり。実装が下手くそなのはさておき、こういうところで自動化出来るのは本当にありがたい話だなーと感じます。

こんな風に色々やったんですが

んまぁ、自分の書いたコードは即席かつ手探りで作ったようなコードなので、あまり参考にはならないと思います・・・・このコードを発展させるのもちょいと難しい(PowerConnectに特化している)のでなんともなぁ・・・と言う感じで。あれですよ、単なる自己満足というやつです。

ただ、ネットワーク設定の自動化にPythonというプログラミング言語がどういう形で寄与しているのか?と言うところはぼんやりと理解することができました。

実際には、NAPALM等のような外部モジュールを使ったほうがより現実的なアプリケーションが組めるようで、そうした使用例もたくさん挙がってます。こうした技術を駆使して、ネットワーク設定を自動化することにより、運用負荷の軽減や設定ミスの低減を図ったりしているのかな・・?と言う印象です。

SDNコントローラフレームワークであるryuは、そんなPythonを使ってアプリケーションを組んでいきますので、サンプルコードを引き続きチラチラ見ながら、何がどういう風に動いてるのかと言うのは追いかけてみたいなーと思います。