Help us understand the problem. What is going on with this article?

Genie ConfによるCisco機器のConfig自動生成

はじめに

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.yaml
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した例です。

genie6-1.py
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文で出力した結果です。

output6-1-1
$ 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の実行が行われています。

output6-1-2
[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 consoleline console 0exec-timeout 0が実行されていますが、これは本設定が自動化の妨げになるためです。

output6-1-3
[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=[])

最後に設定変更が行われ、期待通りのコマンドが実行されました。

output6-1-4
[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()を用います。

genie6-2.py
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アドレス)のように指定し、それ以上の場合は、辞書形式で指定します。

genie6-3.py
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の追加・削除も行っています。

genie6-4.py
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インターフェースの設定

genie6-5.py
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インターフェースの初期化

genie6-6.py
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にも対応しているようです。

genie6-7.py
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-8.py
# 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生成も可能です。

genie6-9.py
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
# 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になれるかも知れませんね。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした