はじめに
先進サービス開発事業部の高橋です。
ブログ初投稿となります、よろしくお願いいたします。
今回はNEEDLEWORK-ScenarioWriterについて書きます。
NEEDLEWORK-ScenarioWriterとは
私が所属する部署はNEEDLEWORKというプロダクトを提供しています。
詳細はNEEDLEWORKの製品サイトよりご確認ください。
NEEDLEWORKを使用するために所定のCSVが必要になります。
ですがCSVをネットワーク機器のコンフィグファイルから手動で作成するのは時間がかかります。
そこで今回はコンフィグファイルから自動でCSVを生成するツールを作成しました。
現在はJuniper SSG5に対応しています。
Juniper SSG5のバックアップ
ブラウザからJuniper SSG5に接続しGUIを表示させます。
Configration→Update→Config file→Download Configuration from Deviceのsave to fileを選択します。
バックアップにてコンフィグファイルを取得後、ツールを使用します。
ツールの使用方法はNEEDLEWORK-ScenarioWriterからご確認ください。
開発で苦労したこと
3つです。
- そもそもコーディング未経験であるということ
- コンフィグファイルがテキスト形式であるということ
- コードの可読性が著しく低いということ
1つずつ掘り下げていきます。
そもそもコーディング未経験であるということ
pythonの基礎どころかコーディング経験がなかったためこのツールがどうあるべきか、
そしてある処理をするためにどういった実装をするべきかを考えることに少し苦労しました。
コンフィグファイルがテキスト形式であるということ
「JSONならばどれほどよかったでしょう」
コンフィグファイルがテキスト形式(.txt)でした。
コンフィグファイルの情報をもとにCSVを生成するため、定型でないデータを扱うために煩雑な処理を行うケースもありました。
例えば以下のコード(1部抜粋)です。
value_noname_key = ['set', 'policy', 'id', 'policy_id', 'from', 'src_zone', 'to', 'dst_zone', 'src_ip', 'dst_ip', 'protocol', 'expect', 'log'] value_noname_key1 = ['set', 'policy', 'id', 'policy_id', 'from', 'src_zone', 'to', 'dst_zone', 'src_ip', 'dst_ip', 'protocol', 'nat', 'src', 'expect', 'log'] value_noname_key2 = ['set', 'policy', 'id', 'policy_id', 'from', 'src_zone', 'to', 'dst_zone', 'src_ip', 'dst_ip', 'protocol', 'nat', 'src', 'dip_id', 'dip_num', 'expect', 'log'] value_noname_key3 = ['set', 'policy', 'id', 'policy_id', 'from', 'src_zone', 'to', 'dst_zone', 'src_ip', 'dst_ip', 'protocol', 'nat', 'src', 'ip', 'src_nat_ip', 'expect', 'log'] value_noname_key4 = ['set', 'policy', 'id', 'policy_id', 'from', 'src_zone', 'to', 'dst_zone', 'src_ip', 'dst_ip', 'protocol', 'nat', 'dst', 'ip', 'dst_nat_ip', 'expect', 'log'] value_noname_key5 = ['set', 'policy', 'id', 'policy_id', 'from', 'src_zone', 'to', 'dst_zone', 'src_ip', 'dst_ip', 'protocol', 'nat', 'src', 'dst', 'ip', 'dst_nat_ip', 'expect', 'log'] value_noname_key6 = ['set', 'policy', 'id', 'policy_id', 'from', 'src_zone', 'to', 'dst_zone', 'src_ip', 'dst_ip', 'protocol', 'nat', 'dst', 'ip', 'dst_nat_ip', 'port', 'dst_nat_port', 'expect', 'log'] policy_dict = [] def convert_list_to_dict(key, value, dictionary): d = {k: v for k, v in zip( key, value)} dictionary.append(d) def append_noname_to_policy_dict(value): dictionary = policy_dict if len(value) == 13 and "log" in value or len(value) == 12 and "log" not in value: key = value_noname_key convert_list_to_dict(key, value, dictionary) elif len(value) == 15 and "log" in value or len(value) == 14 and "log" not in value: key = value_noname_key1 convert_list_to_dict(key, value, dictionary) elif len(value) == 17 and "src" in value and "dip-id" in value and "log" in value or len(value) == 16 and "src" in value and "dip-id" in value and "log" not in value: key = value_noname_key2 convert_list_to_dict(key, value, dictionary) elif len(value) == 17 and "src" in value and "log" in value or len(value) == 16 and "src" in value and "log" not in value: key = value_noname_key3 convert_list_to_dict(key, value, dictionary) elif len(value) == 17 and "dst" in value and "log" in value or len(value) == 16 and "dst" in value and "log" not in value: key = value_noname_key4 convert_list_to_dict(key, value, dictionary) elif len(value) == 18 and "log" in value or len(value) == 17 and "log" not in value: key = value_noname_key5 convert_list_to_dict(key, value, dictionary) elif len(value) == 19 and "log" in value or len(value) == 18 and "log" not in value: key = value_noname_key6 convert_list_to_dict(key, value, dictionary) def absorb_config(): with open(file_name) as f: for line in f: value = line.strip().split() if "manage" in line or "bypass" in line or "proxy-arp-entry" in line or "mtu" in line or "unset" in line or "sharable" in line: continue if "set policy id" in line and "name" in line and "from" in line: dictionary = policy_dict if len(value) == 20 and "log" in value or len(value) == 19 and "log" not in value: key = value_name_key convert_list_to_dict(key, value, dictionary) elif len(value) == 15 and "log" in value or len(value) == 14 and "log" not in value: key = value_name_keyex convert_list_to_dict(key, value, dictionary) elif "set policy id" in line and not "name" in line and "from" in line: append_noname_to_policy_dict(value)
このコードは以下の処理を行っています。
- ツール使用時に指定したコンフィグファイルを読み取る
- 特定の文字列が含まれた時に専用のリストに辞書型で追加する
よりよい方法もあると思うので順次改修予定です。
名前付きのポリシーやNATなどの設定によって「リストのn番目に必ずこの値が入る」と断定できなかったため複雑になってしまいました。
コードの可読性が著しく低いということ
これが1番苦労しました。
自分で書いたコードが2,3時間後にわからなくなる、あるいは理解に時間かかるコードを書いていました。
例えば次のコードです。
for num in range(len(DstZone)): if DstZone[num] == absorb.IntZoneV[0][6]: DstVlan += [absorb.IntZoneV[0][4]]
このコードは「宛先ゾーンと一致するゾーンで使用されているVLANIDをリストに追加する」という処理ですが下記の情報が瞬時に読み取れるでしょうか。
- absorb.pyの変数
IntZoneV
にVLANの設定をしている、Interfaceのゾーン情報が
リストにリストで格納されている(リスト内リストである)こと - 実装時は格納された値のうちから固定で
[0]
の値を取得していたこと [6]
からInterfaceのゾーン名が取得できること[4]
からInterfaceのVLANIDが取得できること
上記のコードはまだ比較的理解しやすいですがこのようなコードや
このコード以上に複雑なコードが複数あったらどうでしょうか。
コーディングどころではなくなってしまいます。
そこで試しにradonで開発中のコードを計測してみました。
$ radon mi -s main/ main/expect.py - A (50.85) main/description.py - A (99.93) main/dstip.py - C (0.00) main/srcvlan.py - A (75.34) main/gencsv.py - A (64.28) main/dstvlan.py - A (75.34) main/dstfw.py - A (40.67) main/protocol.py - A (56.32) main/srcnatip.py - A (50.87) main/multiple.py - C (0.00) main/srcfw.py - A (58.35) main/srcip.py - C (0.00) main/srcport.py - A (81.06) main/dstport.py - A (26.38) main/dstnatport.py - A (62.70) main/dstnatip.py - A (68.87) main/absorbdict.py - A (25.38)
コードの可読性が著しく低いことがわかります。
開発過程で一部構造の変更もありましたがリーダブルコード ―より良いコードを書くためのシンプルで実践的なテクニック (Theory in practice)やflake8をもとにリファクタリングしました。
リファクタリング後の結果はこのようになりました。
$ radon mi -s main/ main/expect.py - A (54.49) main/description.py - A (97.09) main/dstip.py - A (22.13) main/srcvlan.py - A (81.24) main/gencsv.py - A (62.32) main/dstvlan.py - A (81.24) main/dstfw.py - A (74.19) main/protocol.py - A (58.72) main/srcnatip.py - A (50.64) main/multiple.py - A (44.91) main/srcfw.py - A (78.63) main/srcip.py - A (23.77) main/srcport.py - A (86.96) main/dstport.py - A (52.72) main/dstnatport.py - A (67.09) main/dstnatip.py - A (73.95) main/absorbdict.py - B (17.58)
まだ改善の余地はありそうなので時間を見つけてリファクタリング等したいと思います。
以上、ご一読ありがとうございました。