はじめに
Ciscoが公開しているPythonベースのテストフレームワーク/フィーチャーライブラリー「pyATS/Genie」の内、Genie Confを使ってみた結果をメモしておきます。
Genie Confとは
インターフェース、スパニングツリー、ACL、BGP、OSPF、VLAN、HSRPなどの設定項目に対し、アトリビュート(パラメーター)を指定すると、Configを自動で生成してくれるPythonオブジェクトです。
IOS、IOS-XE、IOS-XR、NX-OS等に対応しており、OSによるコマンドの違いを意識することなく、設定変更が可能です。
参考URL:
Genie Documentation - Getting Started - Genie.Conf
GitHub - genielibs - conf
セットアップ
pyATS/Genieインストール
CCIEおじさんのブログ記事を参考にさせて頂きました。
pyATS/Genie : ネットワークテスト自動化ツール pyATS & Genie についてとインストール方法
検証環境
Cisco DevNet SandboxのCatalyst9300(16.6.5)×1台を使用しました。
Testbed
対象機器のホスト名、OSタイプ、ログイン情報などをYAML形式で記載します。
(Ansibleで言うところのInventoryファイル)
testbed:
name: 'testbed'
devices:
leaf1: # ホスト名と同じにする必要あり
type: catalyst9300
os: "iosxe" # プラットフォーム名 ios、iosxe、iosxr、nxos等を指定
alias: 'helper'
tacacs:
login_prompt: 'login:'
password_prompt: 'Password:'
username: admin # ログインユーザ名
passwords:
tacacs: cisco # ログインパスワード 認証方式に関係なくtacacsをキーとして指定
enable: cisco # enableパスワード
line: cisco # lineパスワード
connections:
ssh:
protocol: ssh # telnet、ポート番号も指定可能
ip: "10.10.20.81" # ログインIPアドレス
今回の対象機器は、ホスト名leaf1
の1台ですが、例えばleaf2
も変更する場合は、下にleaf2:
を追記し、同様にOSタイプ、ログイン情報を記載します。aliasは同じ値が使えないので注意が必要です。
Pythonコードと出力結果
設定例1: L3インターフェースの設定
Catalyst9300のマネジメントIFにDescriptionとIPアドレス設定を行い、no shutdownした例です。
from genie.conf import Genie
from genie.conf.base import Interface
testbed = Genie.init('testbed.yaml')
device = testbed.devices['leaf1']
# create an interface object
intf = Interface(device=device, name='GigabitEthernet0/0')
# configuration the interface by setting various object level attributes
intf.description = '< For Test >'
intf.ipv4 = '10.1.1.1'
intf.ipv4.netmask = '255.255.255.0'
intf.shutdown = False
# view the generated configuration
print(intf.build_config(apply=False))
# apply configuration on device
device.connect()
intf.build_config()
出力結果
testbed.yamlと同じディレクトリにPythonコードを格納し、実行した結果です。
冒頭のinterface GigabitEthernet0/0
コマンドは、設定Configをprint文で出力した結果です。
$ python genie6-1.py
interface GigabitEthernet0/0
description < For Test >
ip address 10.1.1.1 255.255.255.0
no shutdown
exit
その後、ログイン処理とshow version
の実行が行われています。
[2019-07-06 09:16:13,088] +++ leaf1 logfile /tmp/leaf1-cli-20190706T091613087.log +++
[2019-07-06 09:16:13,089] +++ Unicon plugin iosxe +++
[2019-07-06 09:16:13,095] +++ connection to spawn: ssh -l admin 10.10.20.81, id: 140124079080168 +++
[2019-07-06 09:16:13,096] connection to leaf1
[2019-07-06 09:16:15,117] Timeout occurred : Timeout value : 2
Target: leaf1
Command sent: ssh -l admin 10.10.20.81
Pattern: '.+$'
Got: ''
[2019-07-06 09:16:15,117] Timeout occured, sending '\r'
Password:
leaf1#
[2019-07-06 09:16:16,704] +++ initializing handle +++
[2019-07-06 09:16:16,705] +++ leaf1: executing command 'term length 0' +++
term length 0
leaf1#
[2019-07-06 09:16:17,096] +++ leaf1: executing command 'term width 0' +++
term width 0
leaf1#
[2019-07-06 09:16:17,455] +++ leaf1: executing command 'show version' +++
show version
Cisco IOS XE Software, Version 16.06.05
Cisco IOS Software [Everest], Catalyst L3 Switch Software (CAT9K_IOSXE), Version 16.6.5, RELEASE SOFTWARE (fc3)
(省略: show versionの出力結果)
続いて、no logging console
、line console 0
、exec-timeout 0
が実行されていますが、これは本設定が自動化の妨げになるためです。
[2019-07-06 09:16:17,813] +++ leaf1: config +++
config term
Enter configuration commands, one per line. End with CNTL/Z.
leaf1(config)#no logging console
leaf1(config)#line console 0
leaf1(config-line)#exec-timeout 0
leaf1(config-line)#end
leaf1#
無効化したい場合は、Pythonコードを以下の通り書き換えます。
# 変更前
device.connect()
# 変更後
device.connect(init_config_commands=[])
最後に設定変更が行われ、期待通りのコマンドが実行されました。
[2019-07-06 09:16:19,643] +++ leaf1: config +++
config term
Enter configuration commands, one per line. End with CNTL/Z.
leaf1(config)#interface GigabitEthernet0/0
leaf1(config-if)# description < For Test >
leaf1(config-if)# ip address 10.1.1.1 255.255.255.0
leaf1(config-if)# no shutdown
leaf1(config-if)# exit
leaf1(config)#end
leaf1#
設定例2: L3インターフェースの初期化
設定例1で設定した内容を削除(初期化)します。設定時はintf.build_config()
を指定していましたが、削除時はintf.build_unconfig()
を用います。
from genie.conf import Genie
from genie.conf.base import Interface
testbed = Genie.init('testbed.yaml')
device = testbed.devices['leaf1']
# create an interface object
intf = Interface(device=device, name='GigabitEthernet0/0')
# remove configuration from device
device.connect()
intf.build_unconfig()
出力結果
default interface GigabitEthernet0/0
コマンドで設定が初期化され、最後に閉塞が行われているのが分かります。
[2019-07-06 09:28:08,824] +++ leaf1: config +++
config term
Enter configuration commands, one per line. End with CNTL/Z.
leaf1(config)#default interface GigabitEthernet0/0
% Management interface VRF can not be changed.
% Error(s) seen setting interface GigabitEthernet0/0 to it's default config
leaf1(config)#interface GigabitEthernet0/0
leaf1(config-if)#shutdown
leaf1(config-if)#end
leaf1#
設定例3: L3インターフェースの一部設定削除
intf.build_unconfig()
の引数で、削除したいアトリビュートを指定できます。一つだけの場合は、attributes='ipv4'
(IPアドレス)のように指定し、それ以上の場合は、辞書形式で指定します。
from genie.conf import Genie
from genie.conf.base import Interface
testbed = Genie.init('testbed.yaml')
device = testbed.devices['leaf1']
# create an interface object
intf = Interface(device=device, name='GigabitEthernet0/0')
# configuration the interface by setting various object level attributes
intf.description = '< For Test >'
intf.ipv4 = '10.1.1.1'
intf.ipv4.netmask = '255.255.255.0'
intf.shutdown = False
# remove configuration from device
device.connect()
intf.build_unconfig(attributes='ipv4')
intf.build_unconfig(attributes={'description': None, 'shutdown': None})
出力結果
2つに分かれていますが、削除できています。
[2019-07-06 09:49:31,747] +++ leaf1: config +++
config term
Enter configuration commands, one per line. End with CNTL/Z.
leaf1(config)#interface GigabitEthernet0/0
leaf1(config-if)# no ip address 10.1.1.1 255.255.255.0
leaf1(config-if)# exit
leaf1(config)#end
leaf1#
[2019-07-06 09:49:34,314] +++ leaf1: config +++
config term
Enter configuration commands, one per line. End with CNTL/Z.
leaf1(config)#interface GigabitEthernet0/0
leaf1(config-if)# no description < For Test >
leaf1(config-if)# shutdown
leaf1(config-if)# exit
leaf1(config)#end
leaf1#
設定例4: スイッチポートの設定
Trunkポートの例です。allowed vlanの追加・削除も行っています。
from genie.conf import Genie
from genie.conf.base import Interface
testbed = Genie.init('testbed.yaml')
device = testbed.devices['leaf1']
# create an interface object
intf = Interface(device=device, name='TenGigabitEthernet1/0/24')
# configuration the interface by setting various object level attributes
intf.description = '< Test Switchport >'
intf.switchport_enable = True
intf.switchport_mode = "trunk"
intf.trunk_vlans = "100-200,250"
intf.trunk_add_vlans = "300"
intf.trunk_remove_vlans = "250,300"
intf.native_vlan = "1"
intf.shutdown = False
# apply configuration on device
device.connect()
intf.build_config()
出力結果
[2019-07-06 10:02:17,725] +++ leaf1: config +++
config term
Enter configuration commands, one per line. End with CNTL/Z.
leaf1(config)#interface TenGigabitEthernet1/0/24
leaf1(config-if)# description < Test Switchport >
leaf1(config-if)# no shutdown
leaf1(config-if)# switchport
leaf1(config-if)# switchport mode trunk
leaf1(config-if)# switchport trunk allowed vlan 100-200,250
leaf1(config-if)# switchport trunk native vlan 1
leaf1(config-if)# switchport trunk allowed vlan add 300
leaf1(config-if)# switchport trunk allowed vlan remove 250,300
leaf1(config-if)# exit
leaf1(config)#end
leaf1#
設定例5: Loopbackインターフェースの設定
from genie.conf import Genie
from genie.conf.base import Interface
testbed = Genie.init('testbed.yaml')
device = testbed.devices['leaf1']
# create an interface object
intf = Interface(device = device, name = 'Loopback100')
# configuration the interface by setting various object level attributes
intf.description = '< Test Loopback IF >'
intf.ipv4 = '10.2.2.2'
intf.ipv4.netmask = '255.255.255.0'
# apply configuration on device
device.connect()
intf.build_config()
出力結果
[2019-07-06 10:38:21,797] +++ leaf1: config +++
config term
Enter configuration commands, one per line. End with CNTL/Z.
leaf1(config)#interface Loopback100
leaf1(config-if)# description < Test Loopback IF >
leaf1(config-if)# ip address 10.2.2.2 255.255.255.0
leaf1(config-if)# exit
leaf1(config)#end
leaf1#
設定例6: Loopbackインターフェースの初期化
from genie.conf import Genie
from genie.conf.base import Interface
testbed = Genie.init('testbed.yaml')
device = testbed.devices['leaf1']
# create an interface object
intf = Interface(device = device, name = 'Loopback100')
# remove configuration from device
device.connect()
intf.build_unconfig()
出力結果
物理IFの場合、default~コマンドで初期化していましたが、Loopbackのような論理IFは、IFごと削除してしまえばいいです。
Genie Confはこの違いもロジックに組み込まれているようです。
[2019-07-06 10:42:45,002] +++ leaf1: config +++
config term
Enter configuration commands, one per line. End with CNTL/Z.
leaf1(config)#no interface Loopback100
leaf1(config)#end
leaf1#
設定例7: スパニングツリーの設定
今回はPVST+の例です。Rapid PVST+やMSTPにも対応しているようです。
from genie.conf import Genie
from genie.libs.conf.stp import Stp
testbed = Genie.init('testbed.yaml')
device = testbed.devices['leaf1']
# create an STP instance
stp = Stp()
device.add_feature(stp)
# bridge assurance configuration
stp.device_attr[device].etherchannel_misconfig_guard = True
stp.device_attr[device].loop_guard = True
stp.device_attr[device].bpdu_guard = True
# pvst global configuration
stp.device_attr[device].mode_attr['pvst'].pvst_attr['default'].\
vlan_attr['100'].v_bridge_priority = 4096
stp.device_attr[device].mode_attr['pvst'].pvst_attr['default'].\
vlan_attr['100'].v_hello_time = 5
# pvst interface configuration
stp.device_attr[device].mode_attr['pvst'].pvst_attr['default'].\
vlan_attr['100'].interface_attr['TenGigabitEthernet1/0/24'].v_if_cost = '123'
stp.device_attr[device].mode_attr['pvst'].pvst_attr['default'].\
vlan_attr['100'].interface_attr['TenGigabitEthernet1/0/24'].v_if_port_priority = 16
stp.device_attr[device].mode_attr['pvst'].pvst_attr['default'].\
interface_attr['TenGigabitEthernet1/0/23'].p_if_edge_port = 'edge_enable'
stp.device_attr[device].mode_attr['pvst'].pvst_attr['default'].\
interface_attr['TenGigabitEthernet1/0/23'].p_if_link_type = 'shared'
stp.device_attr[device].mode_attr['pvst'].pvst_attr['default'].\
interface_attr['TenGigabitEthernet1/0/23'].p_if_guard = 'root'
stp.device_attr[device].mode_attr['pvst'].pvst_attr['default'].\
interface_attr['TenGigabitEthernet1/0/23'].p_if_bpdu_guard = True
stp.device_attr[device].mode_attr['pvst'].pvst_attr['default'].\
interface_attr['TenGigabitEthernet1/0/23'].p_if_bpdu_filter = True
# apply configuration on device
device.connect()
stp.build_config()
出力結果
[2019-07-06 11:09:54,835] +++ leaf1: config +++
config term
Enter configuration commands, one per line. End with CNTL/Z.
leaf1(config)#spanning-tree etherchannel guard misconfig
leaf1(config)#spanning-tree loopguard default
leaf1(config)#spanning-tree portfast bpduguard default
leaf1(config)#spanning-tree mode pvst
leaf1(config)#spanning-tree vlan 100 hello-time 5
leaf1(config)#spanning-tree vlan 100 priority 4096
leaf1(config)#interface TenGigabitEthernet1/0/24
leaf1(config-if)# spanning-tree vlan 100 cost 123
leaf1(config-if)# spanning-tree vlan 100 port-priority 16
leaf1(config-if)# exit
leaf1(config)#interface TenGigabitEthernet1/0/23
leaf1(config-if)# spanning-tree portfast
%Warning: portfast should only be enabled on ports connected to a single
host. Connecting hubs, concentrators, switches, bridges, etc... to this
interface when portfast is enabled, can cause temporary bridging loops.
Use with CAUTION
%Portfast has been configured on TenGigabitEthernet1/0/23 but will only
have effect when the interface is in a non-trunking mode.
leaf1(config-if)# spanning-tree link-type shared
leaf1(config-if)# spanning-tree guard root
leaf1(config-if)# spanning-tree bpduguard enable
leaf1(config-if)# spanning-tree bpdufilter enable
leaf1(config-if)# exit
leaf1(config)#end
leaf1#
設定例8: スパニングツリーの削除
# genie6-7.pyと異なる点
stp.build_unconfig()
出力結果
[2019-07-06 11:16:57,490] +++ leaf1: config +++
config term
Enter configuration commands, one per line. End with CNTL/Z.
leaf1(config)#no spanning-tree etherchannel guard misconfig
leaf1(config)#no spanning-tree loopguard default
leaf1(config)#no spanning-tree portfast bpduguard default
leaf1(config)#no spanning-tree mode pvst
leaf1(config)#no spanning-tree vlan 100 hello-time 5
leaf1(config)#no spanning-tree vlan 100 priority 4096
leaf1(config)#interface TenGigabitEthernet1/0/24
leaf1(config-if)# no spanning-tree vlan 100 cost 123
leaf1(config-if)# no spanning-tree vlan 100 port-priority 16
leaf1(config-if)# exit
leaf1(config)#interface TenGigabitEthernet1/0/23
leaf1(config-if)# no spanning-tree portfast
leaf1(config-if)# no spanning-tree link-type shared
leaf1(config-if)# no spanning-tree guard root
leaf1(config-if)# no spanning-tree bpduguard enable
leaf1(config-if)# no spanning-tree bpdufilter enable
leaf1(config-if)# exit
leaf1(config)#end
leaf1#
設定例9: ACLの設定
名前付き拡張ACLの設定例です。ACLのエントリ追加だけでなく、IF適用のためのConfig生成も可能です。
from genie.conf import Genie
from genie.libs.conf.acl import Acl
testbed = Genie.init('testbed.yaml')
device = testbed.devices['leaf1']
# create an acl object
acl = Acl()
device.add_feature(acl)
# extended acl configuration
acl.device_attr[device].acl_attr['ipv4_acl'].acl_type = 'ipv4-acl-type'
acl.device_attr[device].acl_attr['ipv4_acl'].ace_attr['10'].actions_forwarding = 'permit'
acl.device_attr[device].acl_attr['ipv4_acl'].ace_attr['10'].protocol = 'ip'
acl.device_attr[device].acl_attr['ipv4_acl'].ace_attr['10'].src = 'host 10.1.1.1'
acl.device_attr[device].acl_attr['ipv4_acl'].ace_attr['10'].dst = 'any'
# apply acl to vlan interface
acl.device_attr[device].acl_attr['ipv4_acl'].interface_attr['Vlan100'].if_in = True
# apply configuration on device
device.connect()
acl.build_config()
出力結果
[2019-07-06 11:23:07,742] +++ leaf1: config +++
config term
Enter configuration commands, one per line. End with CNTL/Z.
leaf1(config)#ip access-list extended ipv4_acl
leaf1(config-ext-nacl)# 10 permit ip host 10.1.1.1 any
leaf1(config-ext-nacl)# exit
leaf1(config)#interface Vlan100
leaf1(config-if)# ip access-group ipv4_acl in
leaf1(config-if)# exit
leaf1(config)#end
leaf1#
設定例10: ACLの削除
# genie6-9.pyと異なる点
acl.build_unconfig()
出力結果
[2019-07-06 11:25:31,697] +++ leaf1: config +++
config term
Enter configuration commands, one per line. End with CNTL/Z.
leaf1(config)#no ip access-list extended ipv4_acl
leaf1(config)#interface Vlan100
leaf1(config-if)# no ip access-group ipv4_acl in
leaf1(config-if)# exit
leaf1(config)#end
leaf1#
所感
今回ご紹介した方法以外に、Jinja2テンプレートを使ってConfig自動生成する方法もあります。Genie Diffのメリットは、テンプレートファイルを自作しなくて良い点と、OSやIFタイプの違いを意識せずに済む点かと思います。
すべての設定項目には対応していないようですが、サポートしているものは今後も使っていきたいと思います。
行く行くはAnsibleのモジュールとして組み込まれたら、より多くの人がHappyになれるかも知れませんね。