pybatfish で対象コンフィグが更新されている場合のみ、スナップショットを再作成する
batfish に同梱されているサンプルスクリプトや、インターネット上で見かけるスクリプト例は「スナップショットの有無に関わらず、スナップショットを生成する」ものが多いと思います ("例" なので、そういうものなのだと思います)。 仮に何度もスクリプトを実行し直す場合は、「コンフィグに変化が無いのであれば、毎回のスナップショット再作成は不要」という場合もあると思います。 そういった場合の実装例をメモしておきます。
例として Question には ipOwners を利用していますが、どの Question を利用してもスナップショット初期化部分の考え方は同じです。
前提条件
今回は Ubuntu 20.04LTS 上で、予め batfish を Docker で動作させています。 また、ディレクトリ構造は以下としています。 r1.cfg
や r2.cfg
が batfish の解析対象となるネットワーク機器のコンフィグです。
| ├── example
│ └── configs
│ ├── r1.cfg
│ └── r2.cfg
└── ipOwners.py
|
基本的な使い方
pybatfish を使った基本的なサンプルは以下です。 batfish に同梱されたサンプルと、ほぼ同じ内容です。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 | #!/usr/bin/env python3
# -*- coding:utf-8 -*-
from pybatfish.client.commands import *
from pybatfish.question import bfq
from pybatfish.question.question import load_questions
BASENAME = "example"
SNAPSHOT_PATH = "./" + BASENAME
SNAPSHOT_NAME = BASENAME + "_snapshot"
NETWORK_NAME = BASENAME + "_network"
bf_set_network(NETWORK_NAME)
bf_init_snapshot(SNAPSHOT_PATH, name=SNAPSHOT_NAME, overwrite=True)
load_questions()
result = bfq.ipOwners().answer().frame()
print(result.sort_values(by=["Node", "VRF", "Interface"]))
|
実行例は以下の通りです。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 | # ./ipOwners.py
Successfully loaded 68 questions from remote
Successfully loaded 68 questions from remote
status: TRYINGTOASSIGN
.... no task information
status: CHECKINGSTATUS
.... no task information
status: TERMINATEDNORMALLY
.... 2022-03-13 03:25:39.045000+00:00 Deserializing objects of type 'org.batfish.datamodel.Configuration' from files 2 / 2.
Default snapshot is now set to example_snapshot
status: TRYINGTOASSIGN
.... no task information
status: TERMINATEDNORMALLY
.... 2022-03-13 03:25:39.341000+00:00 Parse environment BGP tables.
status: TRYINGTOASSIGN
.... no task information
status: TERMINATEDNORMALLY
.... 2022-03-13 03:25:39.473000+00:00 Begin job.
Node VRF Interface IP Mask Active
1 r1 default GigabitEthernet0/0 10.0.0.1 24 True
0 r2 default GigabitEthernet0/0 10.0.0.2 24 True
|
ロギングを無効化する
pybatfish のロギングを無効化し、結果だけを表示させるには logging
の設定を行います。 下記の例では ERROR
にしていますが、ロギングレベルは必要に応じて調整します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 | #!/usr/bin/env python3
# -*- coding:utf-8 -*-
import logging
from pybatfish.client.commands import *
from pybatfish.question import bfq
from pybatfish.question.question import load_questions
logging.getLogger("pybatfish").setLevel(logging.ERROR)
BASENAME = "example"
SNAPSHOT_PATH = "./" + BASENAME
SNAPSHOT_NAME = BASENAME + "_snapshot"
NETWORK_NAME = BASENAME + "_network"
bf_set_network(NETWORK_NAME)
bf_init_snapshot(SNAPSHOT_PATH, name=SNAPSHOT_NAME, overwrite=True)
load_questions()
result = bfq.ipOwners().answer().frame()
print(result.sort_values(by=["Node", "VRF", "Interface"]))
|
実行例は以下の通りです。 ログ表示が省略され、結果だけ表示されていることが分かります。
| # ./ipOwners.py
Node VRF Interface IP Mask Active
1 r1 default GigabitEthernet0/0 10.0.0.1 24 True
0 r2 default GigabitEthernet0/0 10.0.0.2 24 True
|
インデックスを表示しない
結果表示の際、インデックスを無効化するには Pandas の DataFrame を文字列へ変換して表示する際に .to_string(index=False)
を指定します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 | #!/usr/bin/env python3
# -*- coding:utf-8 -*-
import logging
from pybatfish.client.commands import *
from pybatfish.question import bfq
from pybatfish.question.question import load_questions
logging.getLogger("pybatfish").setLevel(logging.ERROR)
BASENAME = "example"
SNAPSHOT_PATH = "./" + BASENAME
SNAPSHOT_NAME = BASENAME + "_snapshot"
NETWORK_NAME = BASENAME + "_network"
bf_set_network(NETWORK_NAME)
bf_init_snapshot(SNAPSHOT_PATH, name=SNAPSHOT_NAME, overwrite=True)
load_questions()
result = bfq.ipOwners().answer().frame()
print(result.sort_values(by=["Node", "VRF", "Interface"]).to_string(index=False))
|
実行結果は以下の通りです。 これまでのサンプルでは左側に表示されていたインデックスが表示されなくなりました。
| # ./ipOwners.py
Node VRF Interface IP Mask Active
r1 default GigabitEthernet0/0 10.0.0.1 24 True
r2 default GigabitEthernet0/0 10.0.0.2 24 True
|
必要時だけ初期化を実行する
スクリプトを呼び出す度にスナップショットを初期化すると毎回、実行時間が長くなってしまいます。 コンフィグ全体のハッシュ値を計算し、コンフィグに変更があった場合にのみスナップショットを更新 (再作成) するには、例えば以下のようにします。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67 | #!/usr/bin/env python3
# -*- coding:utf-8 -*-
import hashlib
import logging
import os
from pybatfish.client.commands import *
from pybatfish.question import bfq
from pybatfish.question.question import load_questions
def get_dir_hash(directory: str) -> str:
if not os.path.exists(directory):
return -1
hash = hashlib.sha1()
for root, dirs, files in os.walk(directory):
for file in files:
with open(os.path.join(root, file), "rb") as f:
while True:
buf = f.read(hash.block_size * 0x800)
if not buf:
break
hash.update(buf)
return hash.hexdigest()
def read_hash(file: str) -> str:
with open(file) as f:
return f.read()
def write_hash(file: str, path: str):
with open(file, mode="w") as f:
f.write(get_dir_hash(path))
def init_bfq(base: str):
BASENAME = base
SNAPSHOT_PATH = "./" + BASENAME
SNAPSHOT_NAME = BASENAME + "_snapshot"
NETWORK_NAME = BASENAME + "_network"
CONFIGS = SNAPSHOT_PATH + "/configs"
HASH = SNAPSHOT_PATH + "/hash"
bf_set_network(NETWORK_NAME)
if os.path.exists(HASH):
hash1 = read_hash(HASH)
hash2 = get_dir_hash(CONFIGS)
if hash1 == hash2:
try:
bf_init_snapshot(SNAPSHOT_PATH, name=SNAPSHOT_NAME, overwrite=False)
except:
bf_set_snapshot(name=SNAPSHOT_NAME)
else:
write_hash(HASH, CONFIGS)
bf_init_snapshot(SNAPSHOT_PATH, name=SNAPSHOT_NAME, overwrite=True)
else:
write_hash(HASH, CONFIGS)
bf_init_snapshot(SNAPSHOT_PATH, name=SNAPSHOT_NAME, overwrite=True)
load_questions()
logging.getLogger("pybatfish").setLevel(logging.ERROR)
BASENAME = "example"
init_bfq(BASENAME)
result = bfq.ipOwners().answer().frame()
print(result.sort_values(by=["Node", "VRF", "Interface"]).to_string(index=False))
|
実行結果は以下の通りです。 初回はやや時間がかかっていますが、2 回目以降は実行時間が短縮されていることが分かります。 また、途中でコンフィグを変更した場合は自動的にスナップショットが再作成されますが、その場合はやはり再計算分の時間がかかります。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 | # time ./ipOwners.py
Node VRF Interface IP Mask Active
r1 default GigabitEthernet0/0 10.0.0.1 24 True
r2 default GigabitEthernet0/0 10.0.0.2 24 True
real 0m1.579s
user 0m0.800s
sys 0m0.703s
# time ./ipOwners.py
Node VRF Interface IP Mask Active
r1 default GigabitEthernet0/0 10.0.0.1 24 True
r2 default GigabitEthernet0/0 10.0.0.2 24 True
real 0m0.844s
user 0m0.719s
sys 0m0.735s
# time ./ipOwners.py
Node VRF Interface IP Mask Active
r1 default GigabitEthernet0/0 10.0.0.1 24 True
r2 default GigabitEthernet0/0 10.0.0.2 24 True
real 0m0.842s
user 0m0.730s
sys 0m0.718s
|