SDN開発エンジニアを目指した活動ブログ

〜SDNなオープンソース製品を実際に使って試してみる〜

Python / JUNOS環境で、NETCONF動作を試してみる

SDN時代のインフラ構築には、APIベースの設定作業が標準になりつつあります。
通常、仮想VMであれば、OpenStackなどが活用されるところでしょう。
SDNインフラ環境でも既存ネットワークとの相互運用は必須になりますが、NW機器での諸設定が従来通りの手作業によるコンフィグ設定が行なわれている事例がまだ多いと思います。
昨今のSDN技術の台頭により、作業効率の向上、および、オペミス防止の観点から、従来のNW機器もSDNオーケストレーション的なアプローチでAPIベースで制御してしまおうという風潮が高まりつつあります。
そこで、 NETCONFというプロトコルが注目されるわけですね。

◼️ NETCONF確認用のSRXトポロジ環境

今回は、NETCONFを活用したSRX実機でのBGP設定にチャレンジしたいと思います。
なお、BGP構成は、以前の記事と全く同じ構成にします。
なお、NETCONFサーバ動作環境の構築方法は、以前のブログ記事「vSRXを活用した、JUNOSのお勉強まとめ【JUNOS初期設定 + BGP基本編】」と同じです。
SRX実機も、vSRXも同じJUNOSなので、環境構築上の違いはないです。詳しくは、こちらの参照ください。
ttsubo.hatenablog.com

今回は、さらに、NETCONF動作環境を追加しているイメージです。

                   +----------+
                   | -MacOS X-|
                   | NETCONF  |
                   | (Client) |
                   +----------+
                        +
                        |
                        | 192.168.100.0/24
                        |
                        + .101
                   +----------+                          +----------+
                .1 | -SRX100- | .1      i-BGP         .2 | -BHR4GRV-| .1
... -------------+ | NETCONF  | +----------------------+ | OpenWRT  | +------------- ...
    172.16.0.0/24  | (Server) |      192.168.0.0/30      | (Quagga) |  172.16.1.0/24
                   +----------+                          +----------+
                   < AS65000 >                           < AS65000 >

SRXトポロジj構成として、SRX実機に加えて、対向BGPルータを用意する必要があります。
ただ、環境構築になるべくお金を掛けたくなかったので、対向BGPには、Buffalo"BHR-4GRV"に、OpenWRT環境をセットアップした上で、Quaggaをインストールして代用しました。
ttsubo.hatenablog.com

◼️ NETCONF環境を設定する

1. NETCONFクライアント側の環境設定

SDNオーケストレーションなどの自動制御を担う動作環境で、NETCONFを動作させるには、
オープンソース"ncclient: Python library for NETCONF clients"が活用されることが多いようです。
github.com

ncclientは、次のような多種多様なNETCONF装置に対応しているようです。

Supported device handlers

  • Juniper: device_params={'name':'junos'}
  • Cisco CSR: device_params={'name':'csr'}
  • Cisco Nexus: device_params={'name':'nexus'}
  • Huawei: device_params={'name':'huawei'}
  • Alcatel Lucent: device_params={'name':'alu'}
  • H3C: device_params={'name':'h3c'}
  • HP Comware: device_params={'name':'hpcomware'}

ncclient/README.rst at master · ncclient/ncclient · GitHub

(1) 事前準備

まず、ncclientをインストールします。

$ pip install ncclient
(2) BGP設定サンプルアプリ[create-bgp.py]の配置

NETCONFを活用して、BGPコンフィグ設定を行うサンプルアプリを配置します。

from ncclient import manager
from ncclient.xml_ import *
import time

def connect(host, port, user, password, config):
    conn = manager.connect(host=host,
            port=port,
            username=user,
            password=password,
            timeout=30,
            device_params = {'name':'junos'},
            hostkey_verify=False)

    print "------------"
    print "1. conn.lock"
    print "------------"
    try:
        lock = conn.lock('candidate')
        print lock.tostring
    except Exception as e:
        print ("Error in 'conn.lock': type=[%s], message=[%s]"%(type(e), e.message))
        raise

    print "--------------------------"
    print "2. conn.load_configuration"
    print "--------------------------"
    try:
        load_conf = conn.load_configuration(target="candidate",
                                            config=config["routing_options"],
                                            format='xml')
        print load_conf.tostring
        load_conf = conn.load_configuration(target="candidate",
                                            config=config["protocols"],
                                            format='xml')
        print load_conf.tostring
    except Exception as e:
        print ("Error in 'conn.load_configuration': type=[%s], message=[%s]"%(type(e), e.message))
        raise

    print "----------------"
    print "3. conn.validate"
    print "----------------"
    try:
        validate = conn.validate()
        print validate.tostring
    except Exception as e:
        print ("Error in 'conn.validate': type=[%s], message=[%s]"%(type(e), e.message))
        raise

    print "-----------------------------"
    print "4. conn.compare_configuration"
    print "-----------------------------"
    try:
        compare = conn.compare_configuration()
        print compare.tostring
    except Exception as e:
        print ("Error in 'conn.compare_configuration': type=[%s], message=[%s]"%(type(e), e.message))
        raise

    print "---------------"
    print "5. conn.commit"
    print "---------------"
    try:
        commit = conn.commit()
        print commit.tostring
    except Exception as e:
        print ("Error in 'conn.commit': type=[%s], message=[%s]"%(type(e), e.message))
        raise

    print "---------------"
    print "6. conn.unlock"
    print "---------------"
    try:
        unlock = conn.unlock()
        print unlock.tostring
    except Exception as e:
        print ("Error in 'conn.unlock': type=[%s], message=[%s]"%(type(e), e.message))
        raise


if __name__ == '__main__':

    protocols = new_ele('protocols')
    bgp = sub_ele(protocols, 'bgp')
    group = sub_ele(bgp, 'group')
    group_name = sub_ele(group, 'name').text = 'INTERNAL'
    sub_ele(group, 'type').text = 'internal'
    sub_ele(group, 'export').text = 'export-bgp'
    sub_ele(group, 'neighbor').text = '192.168.0.2'

    routing_options = new_ele('routing-options')
    sub_ele(routing_options, 'router-id').text = '10.0.0.1'
    sub_ele(routing_options, 'autonomous-system').text = '65000'

    config = {}
    config["routing_options"] = routing_options
    config["protocols"] = protocols

    connect('192.168.100.101', 830, 'tsubo', 'xxxxxxx', config)
(3) BGP確認サンプルアプリ[show-bgp.py]の配置

NETCONFを活用して、BGP動作確認を行うサンプルアプリを配置します。

from ncclient import manager
from ncclient.xml_ import *
import time

def connect(host, port, user, password):
    conn = manager.connect(host=host,
            port=port,
            username=user,
            password=password,
            timeout=30,
            device_params = {'name':'junos'},
            hostkey_verify=False)

    print "-------------------------------------"
    print "1. conn.get_configuration 'protocols'"
    print "-------------------------------------"
    config_filter = new_ele('configuration')
    sub_ele(config_filter, 'routing-options')
    sub_ele(config_filter, 'protocols')
    get_conf = conn.get_configuration(format='text', filter=config_filter)
    print get_conf.tostring

    print "----------------------------------"
    print "2. conn.command 'show bgp summary'"
    print "----------------------------------"
    time.sleep(10)
    print "root@SRX> show bgp summary"
    result = conn.command(command='show bgp summary', format='text')
    print result.xpath('output')[0].text

    print "-------------------------------------------------------------"
    print "3. conn.command 'show route receive-protocol bgp 192.168.0.2'"
    print "-------------------------------------------------------------"
    print "root@SRX> show route receive-protocol bgp 192.168.0.2"
    result = conn.command(command='show route receive-protocol bgp 192.168.0.2', format='text')
    print result.xpath('output')[0].text


if __name__ == '__main__':

    connect('192.168.100.101', 830, 'tsubo', 'xxxxxxx')

2. NETCONFサーバ側の環境設定

NETCONFサーバ環境として、JUNOS搭載のNW機器が活用されることが多いと思います、
そこで、今回は、JUNOS搭載のNETCONFサーバ環境として、SRX実機を採用しました。
f:id:ttsubo:20160416174416j:plain

(1) 事前準備

NETCONFクライアント環境からの制御を受信できるように準備しておきます。
具体的には、NETCONFプロトコル(830ポート)を有効にしておきます。

root@SRX> configure 
Entering configuration mode

[edit]
root@SRX# set system services netconf ssh port 830
(2) その他、事前に行っておくコンフィグ設定

今回のNETCONF動作として、"protocols"と"routing-options "を設定することにします。
従って、それ以外の各種コンフィグ設定は、事前に設定しておきます。

root@SRX> show configuration |display set    
set version 12.1X44-D35.5
set system host-name SRX
set system time-zone Asia/Tokyo
set system root-authentication encrypted-password "$1$kAd32lTR$F7o4dwAbW9vY1CIV.a9kt."
set system login user tsubo uid 2000
set system login user tsubo class super-user
set system login user tsubo authentication encrypted-password "$1$eT7nqVkt$uCwT9W8WuBhrEhBcyKoQ5/"
set system services ssh root-login allow
set system services netconf ssh port 830
set system syslog archive size 100k
set system syslog archive files 3
set system syslog user * any emergency
set system syslog file messages any any
set system syslog file messages authorization info
set system syslog file interactive-commands interactive-commands any
set system max-configurations-on-flash 5
set system max-configuration-rollbacks 5
set system license autoupdate url https://ae1.juniper.net/junos/key_retrieval
set system ntp
set interfaces fe-0/0/0 unit 0 family inet address 192.168.100.101/24
set interfaces fe-0/0/1 unit 0 family inet address 192.168.0.1/24
set interfaces fe-0/0/2 unit 0 family inet address 172.16.0.1/24
set interfaces lo0 unit 0 family inet address 10.0.0.1/32
set policy-options policy-statement export-bgp term 1 from route-filter 172.16.0.0/24 exact
set policy-options policy-statement export-bgp term 1 then accept
set security forwarding-options family inet6 mode packet-based
set security forwarding-options family mpls mode packet-based

◼️ いよいよ、NETCONF動作を試してみる

1. BGP設定を行う

まずは、NETCONFを活用して、"routing-options"と"protocols"のコンフィグ設定を行います。

$ python create-bgp.py 
------------
1. conn.lock
------------
<rpc-reply message-id="urn:uuid:06ced48c-03d4-11e6-81aa-a45e60ba8c55">
  <ok/>
</rpc-reply>

--------------------------
2. conn.load_configuration
--------------------------
<rpc-reply message-id="urn:uuid:06e4c133-03d4-11e6-ab26-a45e60ba8c55">
  <load-configuration-results>
    <ok/>
  </load-configuration-results>
</rpc-reply>

<rpc-reply message-id="urn:uuid:06f798fd-03d4-11e6-afa3-a45e60ba8c55">
  <load-configuration-results>
    <ok/>
  </load-configuration-results>
</rpc-reply>

----------------
3. conn.validate
----------------
<rpc-reply message-id="urn:uuid:070b022b-03d4-11e6-bca1-a45e60ba8c55">
  <commit-results>
</commit-results>
  <ok/>
</rpc-reply>

-----------------------------
4. conn.compare_configuration
-----------------------------
<rpc-reply message-id="urn:uuid:0a753c47-03d4-11e6-8b4f-a45e60ba8c55">
  <configuration-information>
    <configuration-output>
[edit]
+  routing-options {
+      router-id 10.0.0.1;
+      autonomous-system 65000;
+  }
+  protocols {
+      bgp {
+          group INTERNAL {
+              type internal;
+              export export-bgp;
+              neighbor 192.168.0.2;
+          }
+      }
+  }
</configuration-output>
  </configuration-information>
</rpc-reply>

---------------
5. conn.commit
---------------
<rpc-reply message-id="urn:uuid:0b0273cc-03d4-11e6-a844-a45e60ba8c55">
  <ok/>
</rpc-reply>

---------------
6. conn.unlock
---------------
<rpc-reply message-id="urn:uuid:1663174f-03d4-11e6-bf75-a45e60ba8c55">
  <ok/>
</rpc-reply>

特に、エラーも発生せずに、BGP設定を行うことができたようです。

2. BGP動作を確認してみる

それでは、NETCONFを活用して、"routing-options"と"protocols"のコンフィグ設定が正しく行われたことを確認してみます。

$ python show-bgp.py 
-------------------------------------
1. conn.get_configuration 'protocols'
-------------------------------------
<rpc-reply message-id="urn:uuid:2119510a-03d4-11e6-9a47-a45e60ba8c55">
  <configuration-text>
## Last changed: 2016-04-16 22:06:42 JST
routing-options {
    router-id 10.0.0.1;
    autonomous-system 65000;
}
## Last changed: 2016-04-16 22:06:42 JST
protocols {
    bgp {
        group INTERNAL {
            type internal;
            export export-bgp;
            neighbor 192.168.0.2;
        }
    }
}
</configuration-text>
</rpc-reply>

----------------------------------
2. conn.command 'show bgp summary'
----------------------------------
root@SRX> show bgp summary

Groups: 1 Peers: 1 Down peers: 0
Table          Tot Paths  Act Paths Suppressed    History Damp State    Pending
inet.0                 1          1          0          0          0          0
Peer                     AS      InPkt     OutPkt    OutQ   Flaps Last Up/Dwn State|#Active/Received/Accepted/Damped...
192.168.0.2           65000          3          4       0       0          18 1/1/1/0              0/0/0/0

-------------------------------------------------------------
3. conn.command 'show route receive-protocol bgp 192.168.0.2'
-------------------------------------------------------------
root@SRX> show route receive-protocol bgp 192.168.0.2


inet.0: 8 destinations, 8 routes (8 active, 0 holddown, 0 hidden)
  Prefix		  Nexthop	       MED     Lclpref    AS path
* 172.16.1.0/24           192.168.0.2          0       100        I

さらに、対向BGPルータにて、SRXから配布されたBGP経路が受信できたことを確認してみます。

Quagga-3# show ip bgp
BGP table version is 0, local router ID is 10.0.1.3
Status codes: s suppressed, d damped, h history, * valid, > best, i - internal,
              r RIB-failure, S Stale, R Removed
Origin codes: i - IGP, e - EGP, ? - incomplete

   Network          Next Hop            Metric LocPrf Weight Path
*>i172.16.0.0/24    192.168.0.1                   100      0 i
*> 172.16.1.0/24    0.0.0.0                  0         32768 i

Total number of prefixes 2

以上より、NETCONFを活用することにより、NW機器への各種コンフィグ設定/確認が自動的に実施できるよう、Pythonスクリプト化することができました。

◼️ NETCONFエラー判定動作を確認する

ncclient側でのAPI制御結果として、エラー判定された時のNETCONF動作を確認しておきます。

事例1「他ユーザが、コンフィグ処理中のとき」

例えば、lo0インタフェースのDescriptionの設定に関わるコンフィグ設定が完了しないときに、NETCONFを活用して、BGP設定を行ってみます。

(1) 他ユーザによるDescriptionの設定

Description設定後、commitを行わず、放置しておきます。

root@SRX# set interfaces lo0 description lo0_description 

[edit]
root@SRX# show |compare 
[edit interfaces lo0]
+   description lo0_description;

[edit]
(2) NETCONFによるBGP設定

このとき、NETCONFのlock処理がエラー判定となるので、BGP設定サンプルアプリ[create-bgp.py]が異常終了することを確認します。

$ python create-bgp.py 
------------
1. conn.lock
------------
Error in 'conn.lock': type=[<class 'ncclient.operations.rpc.RPCError'>], message=[
configuration database modified
]
Traceback (most recent call last):
  File "create-bgp.py", line 99, in <module>
    connect('192.168.100.101', 830, 'tsubo', 'xxxxxxxx', config)
  File "create-bgp.py", line 18, in connect
    lock = conn.lock('candidate')
  File "/usr/local/lib/python2.7/site-packages/ncclient/manager.py", line 158, in wrapper
    return self.execute(op_cls, *args, **kwds)
  File "/usr/local/lib/python2.7/site-packages/ncclient/manager.py", line 228, in execute
    raise_mode=self._raise_mode).request(*args, **kwds)
  File "/usr/local/lib/python2.7/site-packages/ncclient/operations/lock.py", line 35, in request
    return self._request(node)
  File "/usr/local/lib/python2.7/site-packages/ncclient/operations/rpc.py", line 336, in _request
    raise self._reply.error
ncclient.operations.rpc.RPCError: 
configuration database modified

期待どおりに、ncclient側でのNETCONF処理でエラー判定された場合には、Python処理がException終了してくれました。

事例2「事前設定のコンフィグ不備のとき」

BGP設定サンプルアプリ[create-bgp.py]では、事前にpolicy-statement "export-bgp"を設定しておく必要があります。
ここでは、あえて、事前に、policy-statementを削除してみます。

(1) policy-statement "export-bgp"のコンフィグ設定不備

policy-statementを削除しておきます。

root@SRX# delete policy-options 

[edit]
root@SRX# show |compare 
[edit]
-  policy-options {
-      policy-statement export-bgp {
-          term 1 {
-              from {
-                  route-filter 172.16.0.0/24 exact;
-              }
-              then accept;
-          }
-      }
-  }

[edit]
root@SRX# commit 
commit complete

[edit]
(2) NETCONFによるBGP設定

このとき、NETCONFのvalidate処理がエラー判定となるので、BGP設定サンプルアプリ[create-bgp.py]が異常終了することを確認します。

$ python create-bgp.py 
------------
1. conn.lock
------------
<rpc-reply message-id="urn:uuid:8853948f-0454-11e6-975b-a45e60ba8c55">
  <ok/>
</rpc-reply>

--------------------------
2. conn.load_configuration
--------------------------
<rpc-reply message-id="urn:uuid:88685466-0454-11e6-a6e1-a45e60ba8c55">
  <load-configuration-results>
    <ok/>
  </load-configuration-results>
</rpc-reply>

<rpc-reply message-id="urn:uuid:887ab21c-0454-11e6-bdd1-a45e60ba8c55">
  <load-configuration-results>
    <ok/>
  </load-configuration-results>
</rpc-reply>

----------------
3. conn.validate
----------------
Error in 'conn.validate': type=[<class 'ncclient.operations.rpc.RPCError'>], message=[error: Policy error: Policy export-bgp referenced but not defined
error: BGP: export list not applied
error: configuration check-out failed]
Traceback (most recent call last):
  File "create-bgp.py", line 99, in <module>
    connect('192.168.100.101', 830, 'tsubo', 'xxxxxxxx', config)
  File "create-bgp.py", line 44, in connect
    validate = conn.validate()
  File "/usr/local/lib/python2.7/site-packages/ncclient/manager.py", line 158, in wrapper
    return self.execute(op_cls, *args, **kwds)
  File "/usr/local/lib/python2.7/site-packages/ncclient/manager.py", line 228, in execute
    raise_mode=self._raise_mode).request(*args, **kwds)
  File "/usr/local/lib/python2.7/site-packages/ncclient/operations/edit.py", line 123, in request
    return self._request(node)
  File "/usr/local/lib/python2.7/site-packages/ncclient/operations/rpc.py", line 334, in _request
    raise RPCError(to_ele(self._reply._raw), errs=errors)
ncclient.operations.rpc.RPCError: error: Policy error: Policy export-bgp referenced but not defined
error: BGP: export list not applied
error: configuration check-out failed

期待どおりに、ncclient側でのNETCONF処理でエラー判定された場合には、Python処理がException終了してくれました。

◼️ 最後に、

SDNインフラと相互接続しているNW機器を、SDNオーケストレーション層から順序制御できる仕組みとして、NETCONFの活用は、とても有効であると思います。
今後も、クラウド基盤として、OpenStackが活用される事例は、ますます増えていくと思います。OpenStackとNETCONFの動作連携を担う仕組みとして、heat プロジェクトの役割りの重要性が、どんどん増していくのでしょう。