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

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

Mac OS X環境のVMware Fusion上で、vSRXを動かす

これまで、JUNOSを扱った経験がなかったので、vSRXを用いてJUNOSの勉強環境を作成したいと思います。
こちらが、参考にさせて頂いたslideshareです。

www.slideshare.net

◆ vSRXのイメージを、ダウンロードする

まずは、Juniper.netでアカウント登録しておきます。
そして、Juniperのダウンロードサイトから、vSRXのイメージ"vSRX VMware Appliance with IDE virtual disk"をダウンロードします。

Download Software - Support - Juniper Networks
f:id:ttsubo:20151115061516p:plain

◆ ovfTool を使って、VMX フォーマットに変換する

VMware Fusionがインストールされている環境には、すでに、ovfToolも配備されているはずです。
(なお、私の場合、VMware Fusion バージョン 7.1.3 に最新化してから、変換作業に着手しました。)
以下のようにして、vSRXイメージを変換します。

$ ls -l Downloads | grep ova
-rw-r--r--@  1 ttsubo  staff  2156820480  8  4 13:23 media-srx-ffp-vsrx-vmdisk-15.1X49-D15.4.ide.ova

$ mkdir vSRX
$ cd /Applications/VMware\ Fusion.app/Contents/Library/VMware\ OVF\ Tool
$ ./ovftool --acceptAllEulas /Users/ttsubo/Downloads/media-srx-ffp-vsrx-vmdisk-15.1X49-D15.4.ide.ova /Users/ttsubo/vSRX/vSRX.vmx
... (snip)
Completed successfully

$ cd
$ ls -l /Users/ttsubo/vSRX/
total 5156104
-rw-------  1 ttsubo  staff  2639921152 11 15 05:53 vSRX-disk1.vmdk
-rwxr-xr--  1 ttsubo  staff        1631 11 15 05:53 vSRX.vmx

VMware FusionにvSRXイメージを取り込む

VMware Fusionから、vSRX.vmxファイルを指定して、vSRXイメージを取り込みます。
そして、vSRXイメージを起動してみます。
loginプロンプトが出力されたら、rootユーザでログインします。
その後、「cli」を入力して、オペレーションモードに移行してみます。

f:id:ttsubo:20151115070707p:plain

以上で、vSRXイメージをVMware Fusionに取り込むことができました。

◆ vSRXイメージをVMBundle化する

vSRXイメージも、他の仮想マシンのイメージファイルと同じように扱いたいので、最後に、VMbundle化してみます。

ひとまず、仮想マシン"vSRX"をシャットダウンしてから、VMware Fusion上の仮想マシン"vSRX"を削除します。
この時、”ファイルを保持”を選んで、仮想マシンを削除してください。

つづいて、仮想マシンのイメージファイルを移動させます。

$ mv vSRX Documents/Virtual\ Machines.localized/
$ cd Documents/Virtual\ Machines.localized/
$ mv vSRX vSRX.vmwarevm

Finderから"書類"->"仮想マシン"に移動して、先ほどのvSRXイメージのファイル名を"vSRX.vmwarevm"から"vSRX"に変更します。
再度、VMware Fusionから、vSRXイメージを取り込んでください。

以上です。

Ryu SDN Frameworkで、VRRP動作を試してみる

これまで、あまり知られておりませんでしたが、Ryu SDN Frameworkでは、VRRP機能を動作させることができます。
VRRP機能を自由に操れるようになれば、柔軟なネットワーク冗長構成も構築できそうです。
今回の元ネタは、こちらになります。
http://ryu.readthedocs.org/en/latest/test-vrrp.html

◆ まずは、VRRP動作環境の準備

Ryu VRRPを動作させるLinux環境としては、Ubuntu Server版を使用しました。

$ cat /etc/lsb-release 
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=14.04
DISTRIB_CODENAME=trusty
DISTRIB_DESCRIPTION="Ubuntu 14.04.3 LTS"

VRRP動作環境としては、2台のLinuxサーバ間でVRRPを動作させるイメージです。
ただし、今回のブログ記事では、あくまでもVRRPプロトコルによるコントロールプレーンの挙動に特化しております。
VRRPによるネットワーク冗長切り替えに関わるデータプレーンは、スコープ外になります。

 --------------                                           --------------
 |  Ryu       | eth1                                 eth1 |  Ryu       |
 |  VRRP1     | ------------------------------------------|  VRRP2     |
 |            | 192.168.0.2                   192.168.0.3 |            |
 --------------                                           --------------
                              VIP: 192.168.0.1

VRRPを動作させるインタフェースは、"eth1"とします。
必要なアドレス設定を事前に行っておきます。

$ ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default 
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 00:0c:29:1c:78:07 brd ff:ff:ff:ff:ff:ff
    inet 192.168.195.153/24 brd 192.168.195.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::20c:29ff:fe1c:7807/64 scope link 
       valid_lft forever preferred_lft forever
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 00:0c:29:1c:78:11 brd ff:ff:ff:ff:ff:ff
    inet 192.168.0.3/24 brd 192.168.0.255 scope global eth1
       valid_lft forever preferred_lft forever
    inet6 fe80::20c:29ff:fe1c:7811/64 scope link 
       valid_lft forever preferred_ft forever

Ryu SDN Frameworkをインストールします。

$ sudo apt-get update
$ sudo apt-get install python-dev
$ sudo apt-get install python-pip
$ sudo apt-get -y install libxml2-dev
$ sudo apt-get -y install python-lxml
$ sudo pip install --upgrade six
$ sudo apt-get install git
$ git clone https://github.com/osrg/ryu.git
$ cd ryu/tools/
$ sudo pip install -r pip-requires
$ cd ..
$ sudo python ./setup.py install

正しく、Ryuがインストールされたことを確認しておきます。

$ ryu-manager --version
rya-manager 3.26

◆ Ryu VRRPサンプルアプリ

Linuxサーバに、おのおのRyu SDN Frameworkをインストールしたら、次のVRRPサンプルアプリを保存します。

(1) RyuVRRP1側のサンプルアプリ

import logging
import datetime
import time
import netaddr

from ryu.base import app_manager
from ryu.lib import hub
from ryu.controller import handler
from ryu.services.protocols.vrrp import api as vrrp_api
from ryu.services.protocols.vrrp import event as vrrp_event


_VRRP_VERSION_V3 = 3
_VRID = 1
_VIRTUAL_IP_ADDRESS = '192.168.0.1'
_PRIMARY_IP_ADDRESS = '192.168.0.2'
_VIRTUAL_MAC_ADDRESS = '00:00:5e:00:01:01'
_PRIORITY = 250
_IFNAME = 'eth1'
_PREEMPT_DELAY = 10

LOG = logging.getLogger('SampleVrrp')
LOG.setLevel(logging.DEBUG)
logging.basicConfig()

class SampleVrrp(app_manager.RyuApp):
    def __init__(self, *args, **kwargs):
        super(SampleVrrp, self).__init__(*args, **kwargs)
        hub.spawn(self._test_senario)

    def _test_senario(self):
        LOG.info("")
        LOG.info("////// 1. Create Vrrp Router  //////")
        vrrp_mgr = self._configure_vrrp_router(_VRRP_VERSION_V3, _PRIORITY,
                      _PRIMARY_IP_ADDRESS, _VIRTUAL_IP_ADDRESS, _IFNAME,
                      _VRID, _PREEMPT_DELAY)
        time.sleep(30)

        LOG.info("")
        LOG.info("////// 2. Change Priority [250] -> [50]  //////")
        self._configure_vrrp_change(_VRID, 50)
        time.sleep(30)

        LOG.info("")
        LOG.info("////// 3. Change Priority [50] -> [250]  //////")
        self._configure_vrrp_change(_VRID, _PRIORITY)
        time.sleep(30)

        LOG.info("")
        LOG.info("////// 4. Shutdown Vrrp Router  //////")
        self._shutdown_vrrp_router(_VRID)

    def _configure_vrrp_change(self, vrid, priority):
        instance_name = self._lookup_instance(vrid)
        if not instance_name:
            raise RPCError('vrid %d is not found' % (vrid))
        vrrp_api.vrrp_config_change(self, instance_name, priority=priority)

    def _shutdown_vrrp_router(self, vrid):
        instance_name = self._lookup_instance(vrid)
        if not instance_name:
            raise RPCError('vrid %d is not found' % (vrid))
        vrrp_api.vrrp_shutdown(self, instance_name)

    def _lookup_instance(self, vrid):
        for instance in vrrp_api.vrrp_list(self).instance_list:
            if vrid == instance.config.vrid:
                return instance.instance_name
        return None

    def _configure_vrrp_router(self, vrrp_version, vrrp_priority,
                               primary_ip_address, virtual_ip_address,
                               ifname, vrid, preempt_delay):
        interface = vrrp_event.VRRPInterfaceNetworkDevice(
            _VIRTUAL_MAC_ADDRESS, primary_ip_address, None, ifname)

        ip_addresses = [virtual_ip_address]
        config = vrrp_event.VRRPConfig(
            version=vrrp_version, vrid=vrid, priority=vrrp_priority,
            ip_addresses=ip_addresses, preempt_delay=preempt_delay)
        config_result = vrrp_api.vrrp_config(self, interface, config)
        return config_result


    @handler.set_ev_cls(vrrp_event.EventVRRPStateChanged)
    def vrrp_state_changed_handler(self, ev):
        old_state = ev.old_state
        new_state = ev.new_state
        now = datetime.datetime.now()
        now_time = now.strftime("%H:%M:%S")
        micro_time = "%03d" % (now.microsecond // 1000)
        LOG.debug("%s.%s: State Changed [%s] -> [%s]"
                 % (now_time, micro_time, old_state, new_state))

(2) RyuVRRP2側のサンプルアプリ

import logging
import datetime

from ryu.base import app_manager
from ryu.lib import hub
from ryu.controller import handler
from ryu.services.protocols.vrrp import api as vrrp_api
from ryu.services.protocols.vrrp import event as vrrp_event


_VRRP_VERSION_V3 = 3
_VRID = 1
_VIRTUAL_IP_ADDRESS = '192.168.0.1'
_PRIMARY_IP_ADDRESS = '192.168.0.3'
_VIRTUAL_MAC_ADDRESS = '00:00:5e:00:01:01'
_PRIORITY = 100
_IFNAME = 'eth1'
_PREEMPT_DELAY = 10

LOG = logging.getLogger('SampleVrrp')
LOG.setLevel(logging.DEBUG)
logging.basicConfig()

class SampleVrrp(app_manager.RyuApp):
    def __init__(self, *args, **kwargs):
        super(SampleVrrp, self).__init__(*args, **kwargs)
        hub.spawn(self._test_senario)

    def _test_senario(self):
        LOG.info("")
        LOG.info("////// 1. Create Vrrp Router  //////")
        vrrp_mgr = self._configure_vrrp_router(_VRRP_VERSION_V3, _PRIORITY,
                      _PRIMARY_IP_ADDRESS, _VIRTUAL_IP_ADDRESS, _IFNAME,
                      _VRID, _PREEMPT_DELAY)


    def _configure_vrrp_router(self, vrrp_version, vrrp_priority,
                               primary_ip_address, virtual_ip_address,
                               ifname, vrid, preempt_delay):
        interface = vrrp_event.VRRPInterfaceNetworkDevice(
            _VIRTUAL_MAC_ADDRESS, primary_ip_address, None, ifname)

        ip_addresses = [virtual_ip_address]
        config = vrrp_event.VRRPConfig(
            version=vrrp_version, vrid=vrid, priority=vrrp_priority,
            ip_addresses=ip_addresses, preempt_delay=preempt_delay)
        rep = vrrp_api.vrrp_config(self, interface, config)
        return rep


    @handler.set_ev_cls(vrrp_event.EventVRRPStateChanged)
    def vrrp_state_changed_handler(self, ev):
        old_state = ev.old_state
        new_state = ev.new_state
        now = datetime.datetime.now()
        now_time = now.strftime("%H:%M:%S")
        micro_time = "%03d" % (now.microsecond // 1000)
        LOG.debug("%s.%s: State Changed [%s] -> [%s]"
                 % (now_time, micro_time, old_state, new_state))

◆ 早速、VRRPサンプルアプリを動かしてみる

まずは、Ryu VRRP2側サンプルアプリを起動したのちに、Ryu VRRP1側サンプルアプリを起動します。
すると、Ryu VRRPサンプルアプリ動作として、以下のテストシナリオが自動で開始されます。
(1) Ryu VRRP1サンプルアプリ動作テストシナリオ
1. VRRP Router起動
2. Priority値を"250"から"50"に変更
3. Priority値を"50"から"250"に変更
4. VRRP Router停止

(2) Ryu VRRP2サンプルアプリ動作テストシナリオ
1. VRRP Router起動

ちなみに、Backup -> Masterへの状態遷移に関わるPreempt modeは有効になっております。[preempt delay: 10秒]
Ryu VRRPサンプルアプリを動作させると、コマンド結果として、いろいろとデバック情報が表示されます。
Ryu VRRP1起動結果

$ sudo ryu-manager sample_vrrp1.py
loading app sample_vrrp1.py
loading app ryu.services.protocols.vrrp.manager
instantiating app sample_vrrp.py of SampleVrrp
instantiating app ryu.services.protocols.vrrp.manager of VRRPManager

////// 1. Create Vrrp Router  //////
instantiating app None of VRRPInterfaceMonitorNetworkDevice
instantiating app None of VRRPRouterV3
14:12:05.593: State Changed [None] -> [Initialize]
14:12:05.594: State Changed [Initialize] -> [Backup]
VRRPV3StateBackup preempt_delay
14:12:15.989: State Changed [Backup] -> [Master]

////// 2. Change Priority [250] -> [50]  //////
VRRPV3StateMaster vrrp_config_change_request
14:12:45.603: State Changed [Master] -> [Backup]

////// 3. Change Priority [50] -> [250]  //////
VRRPV3StateBackup vrrp_config_change_request
VRRPV3StateBackup preempt_delay
14:13:15.645: State Changed [Backup] -> [Master]

////// 4. Shutdown Vrrp Router  //////
14:13:35.600: State Changed [Master] -> [Initialize]

Ryu VRRP2起動結果

$ sudo ryu-manager sample_vrrp2.py
loading app sample_vrrp2.py
loading app ryu.services.protocols.vrrp.manager
instantiating app sample_vrrp.py of SampleVrrp
instantiating app ryu.services.protocols.vrrp.manager of VRRPManager

////// 1. Create Vrrp Router  //////
instantiating app None of VRRPInterfaceMonitorNetworkDevice
instantiating app None of VRRPRouterV3
14:11:58.294: State Changed [None] -> [Initialize]
14:11:58.295: State Changed [Initialize] -> [Backup]
14:12:01.908: State Changed [Backup] -> [Master]
14:12:15.922: State Changed [Master] -> [Backup]
VRRPV3StateBackup preempt_delay
14:12:45.533: State Changed [Backup] -> [Master]
14:13:15.578: State Changed [Master] -> [Backup]
14:13:36.144: State Changed [Backup] -> [Master]

Ryu VRRPサンプルアプリのデバック情報のみだと、本当に、VRRPが正しく動作しているのかを確認するのも、困難だと思います。
そこで、今回の動作テスト時に、Linuxサーバ間でPacketキャプチャしておいて、デバック情報とPacketキャプチャ結果を目視で紐付けてみました。
おかげで、Backup -> Masterへの状態遷移に関わるPreempt delayの挙動と、実際のVRRP Advertisementパケットの送出タイミングの因果関係を理解できるようになりました。以下に、VRRP状態遷移の様子になります。
f:id:ttsubo:20151024160435j:plain

実際、VRRPパケットダンプも貼っておきます。
今回、はじめて気がついたのですが、VRRP Advertisementパケットには、仮想MACアドレス情報は設定されないんですよね。
Ryu VRRPモジュールでの仮想MACアドレスの扱いについて、もう少し、リバースエンジニアリングが必要かもしれません。
f:id:ttsubo:20151024160938p:plain

◆ 終わりに

とりあえず、Ryu SDN FrameworkによるVRRPプロトコル制御の有用性を確認することができました。
ただし、このままでは、VRRPプロトコル知識修得くらいしか活用シーンが思い浮かびません。
今後は、もう一歩、踏み込んで、C/Dプレーン連携を見据えた技術検討を行っていく必要がありそうです。

Pythonベースで、GoBGPを制御してみる 〜gRPC活用編〜

[ 2016.4.30修正:GoBGP最新版への対応 ]
GoBGPは、golangベースのBGPエンジンであり、経路追加などの設定コマンド等も、golangで実装されております。
今回は、Ryu SDN Frameworkとの連携を見据えて、PythonベースでGoBGPを制御する手法を試してみたいと思います。
元ネタは、こちらになります。
github.com

◆ まずは、構築準備から ...

(1) GoBGP環境の準備
前回のブログ記事に従って、PythonからgRPCを扱えるように環境を準備します。
ttsubo.hatenablog.com

(2) BGPルータ環境の準備
GoBGPと相互接続するBGPルータを準備します。

f:id:ttsubo:20151018165000j:plain

この段階で、BGPルータ側のBGPテーブルの状態を確認しておきます。

R1#show bgp ipv4 unicast
BGP table version is 42, local router ID is 192.168.0.1
Status codes: s suppressed, d damped, h history, * valid, > best, i - internal,
              r RIB-failure, S Stale, m multipath, b backup-path, f RT-Filter,
              x best-external, a additional-path, c RIB-compressed,
Origin codes: i - IGP, e - EGP, ? - incomplete
RPKI validation codes: V valid, I invalid, N Not found

     Network          Next Hop            Metric LocPrf Weight Path
 *>  20.1.0.0/24      0.0.0.0                  0         32768 i

(3) GoBGP動作開始
GoBGPを起動します。

$ cd $HOME
$ cd golang/bin/
$ cat gobgpd.conf 
[global]
 [global.config]
   as = 65002
   router-id = "192.168.0.2"

[[neighbors]]
 [neighbors.config]
   peer-type = "external"
   neighbor-address = "192.168.0.1"
   peer-as = 65001
   local-as = 65002
[[neighbors.afi-safis]]
 [neighbors.afi-safis.config]
   afi-safi-name = "ipv4-unicast"

$ sudo ./gobgpd -f gobgpd.conf 
... (snip)

この時、BGPルータとGoBGPとの間のリンクで、BGP UPDATEメッセージにより"Prefix情報"が伝搬される様子を確認しておきます。
ここでの確認ポイントとしては、

  • "20.1.0.0/24"は、Network Layer reachability information(通称、nlri)のフィールドに設定されている。
  • "Nexthop"や、"ORIGIN"などのBGPアトリビュートは、Path attributesのフィールドに設定させている。

をチェックしておいてください。
f:id:ttsubo:20151018172216p:plain

(4) GoBGP側でのBGPテーブル確認
先ほどのBGP UPDATEメッセージが、GoBGP側で反映できたことを確認しておきます。

$ gobgp global rib
    Network             Next Hop             AS_PATH              Age        Attrs
*>  20.1.0.0/24         192.168.0.1          65001                00:03:22   [{Origin: i} {Med: 0}]

以上で、下準備が完了しました。

◆ gRPC基本動作の概要を理解する

GoBGPの内部構造として活用しているgRPCのインタフェース条件として、IDLファイルの内容を確認しておきます。

$ cd $GOPATH/src/github.com/osrg/gobgp/api
$ cat gobgp.proto 

syntax = "proto3";

package gobgpapi;

// Interface exported by the server.

service GobgpApi {
  rpc GetGlobalConfig(Arguments) returns (Global) {}
  rpc ModGlobalConfig(ModGlobalConfigArguments) returns (Error) {}
  rpc GetNeighbors(Arguments) returns (stream Peer) {}
  rpc GetNeighbor(Arguments) returns (Peer) {}
  rpc ModNeighbor(ModNeighborArguments) returns(Error) {}
  rpc GetRib(Table) returns (Table) {}
  rpc Reset(Arguments) returns (Error) {}
  rpc SoftReset(Arguments) returns (Error) {}
  rpc SoftResetIn(Arguments) returns (Error) {}
  rpc SoftResetOut(Arguments) returns (Error) {}
  rpc Shutdown(Arguments) returns (Error) {}
  rpc Enable(Arguments) returns (Error) {}
  rpc Disable(Arguments) returns (Error) {}
  rpc ModPath(ModPathArguments) returns (ModPathResponse) {}
  rpc ModPaths(stream ModPathsArguments) returns (Error) {}
  rpc MonitorRib(Table) returns (stream Destination) {}
  rpc MonitorBestChanged(Arguments) returns (stream Destination) {}
  rpc MonitorPeerState(Arguments) returns (stream Peer) {}
  rpc GetMrt(MrtArguments) returns (stream MrtMessage) {}
  rpc ModMrt(ModMrtArguments) returns (Error) {}
  rpc ModBmp(ModBmpArguments) returns (Error) {}
  rpc GetRPKI(Arguments) returns (stream RPKI) {}
  rpc ModRPKI(ModRpkiArguments) returns (Error) {}
  rpc GetROA(Arguments) returns (stream ROA) {}
  rpc GetVrfs(Arguments) returns (stream Vrf) {}
  rpc ModVrf(ModVrfArguments) returns (Error) {}
  rpc GetDefinedSet(DefinedSet) returns (DefinedSet) {}
  rpc GetDefinedSets(DefinedSet) returns (stream DefinedSet) {}
  rpc ModDefinedSet(ModDefinedSetArguments) returns (Error) {}
  rpc GetStatement(Statement) returns (Statement) {}
  rpc GetStatements(Statement) returns (stream Statement) {}
  rpc ModStatement(ModStatementArguments) returns (Error) {}
  rpc GetPolicy(Policy) returns (Policy) {}
  rpc GetPolicies(Policy) returns (stream Policy) {}
  rpc ModPolicy(ModPolicyArguments) returns (Error) {}
  rpc GetPolicyAssignment(PolicyAssignment) returns (PolicyAssignment) {}
  rpc ModPolicyAssignment(ModPolicyAssignmentArguments) returns (Error) {}
}

… (snip)

今回のブログ記事では、二つのAPIの挙動に着目します。

  • "MonitorBestChanged" : GoBGPで保持しているBGPテーブルの経路情報の変化の有無を監視する
  • "ModPath" : GoBGPへの経路情報の設定を行う


ちなみに、これらのAPIを動作させる際には、事前に、引数/戻り値パラメータ定義を紐解く必要があります。
引数/戻り値パラメータとして入れ子定義されているパラメータを追っかけていくと、”Path"フィールドにたどり着きます。

message Path {
    bytes nlri = 1;
    repeated bytes pattrs = 2;
    int64 age = 3;
    bool best = 4;
    bool is_withdraw = 5;
    int32 validation = 6;
    bool no_implicit_withdraw = 7;
    uint32 family = 8;
    uint32 source_asn = 9;
    string source_id = 10;
    bool filtered = 11;
    bool stale = 12;
    bool is_from_external = 13;
    string neighbor_ip = 14;
}

ここでの確認ポイントは、

  • Pathフィールドのメンバとして、"nlri"および、"pattrs"が定義されている。
  • "nlri"および、"pattrsの型は、"bytes"と定義されている。

すなわち、Pathフィールドの"nlri"および、"pattrs"は、BGP UPDATEメッセージのBGPアトリビュートに対応しております。
よって、"pattrs"は、ORIGIN等のBGPアトリビュートによって構成されることになります。
また、BGPアトリビュートに対応する各パラメータに値を設定する際には、"bytes"型に対応したバイナリ変換が必要になります。
今回は、RyuBGPのPacketパーサを活用してバイナリ変換に対応しました。

◆ BGPテーブル監視用サンプルアプリを試してみる

まずは、Pythonスクリプトから、RPCメソッド:”MonitorBestChanged”を動作させてみます。
任意のディレクトリを作成して、次のサンプルアプリを配備してください。

$ cd $HOME
$ mkdir sample
$ cd sample/
$ vi monitor_BestPathChange.py
-----------------
import gobgp_pb2
import sys
import signal
import time
import os
from threading import Thread
from ryu.lib.packet.bgp import IPAddrPrefix
from ryu.lib.packet.bgp import _PathAttribute
from ryu.lib.packet.bgp import BGPPathAttributeOrigin
from ryu.lib.packet.bgp import BGPPathAttributeAsPath
from ryu.lib.packet.bgp import BGPPathAttributeMultiExitDisc
from ryu.lib.packet.bgp import BGPPathAttributeNextHop
from ryu.lib.packet.bgp import BGPPathAttributeCommunities
from grpc.beta import implementations

_TIMEOUT_SECONDS = 1000

AFI_IP = 1
SAFI_UNICAST = 1
RF_IPv4_UC = AFI_IP<<16 | SAFI_UNICAST

def run(gobgpd_addr, routefamily):
    channel = implementations.insecure_channel(gobgpd_addr, 50051)
    with gobgp_pb2.beta_create_GobgpApi_stub(channel) as stub:

        ribs = stub.MonitorBestChanged(gobgp_pb2.Arguments(family=routefamily),
                                                           _TIMEOUT_SECONDS)

        for rib in ribs:
            paths_target = rib.paths
            for path_target in paths_target:
                nlri = IPAddrPrefix.parser(path_target.nlri)
                print "----------------------------"
                print (" Rib.prefix     : %s" % nlri[0].prefix)
                for pattr in path_target.pattrs:
                    path_attr = _PathAttribute.parser(pattr)
                    if isinstance(path_attr[0], BGPPathAttributeOrigin):
                        print (" Rib.origin     : %s" % path_attr[0].value)
                    elif isinstance(path_attr[0], BGPPathAttributeAsPath):
                        if path_attr[0].type == 2:
                            print(" Rib.aspath     : %s" % path_attr[0].value)
                        else:
                            print(" Rib.aspath     : ???")
                    elif isinstance(path_attr[0], BGPPathAttributeMultiExitDisc):
                        print (" Rib.med        : %s" % path_attr[0].value)
                    elif isinstance(path_attr[0], BGPPathAttributeNextHop):
                        print (" Rib.nexthop    : %s" % path_attr[0].value)
                    elif isinstance(path_attr[0], BGPPathAttributeCommunities):
                        for community in path_attr[0].communities:
                            print(" Rib.community  : %s" % community)

                print (" Rib.is_withdraw : %s" % path_target.is_withdraw)


def receive_signal(signum, stack):
    print('signal received:%d' % signum)
    print('exit')
    os._exit(0)

if __name__ == '__main__':
    gobgp = sys.argv[1]
    family = sys.argv[2]
    if family == "ipv4":
        routefamily = RF_IPv4_UC 
    else:
        exit(1)

    signal.signal(signal.SIGINT, receive_signal)

    t = Thread(target=run, args=(gobgp, routefamily))
    t.daemon = True
    t.start()

    # sleep 1 sec forever to keep main thread alive
    while True:
        time.sleep(1)

続いて、IDLファイルをコンパイルします。
すると、"gobgp_pb2.py"が自動生成されます。

$ GOBGP_API=$GOPATH/src/github.com/osrg/gobgp/api
$ protoc -I=$GOPATH/src/github.com/osrg/gobgp/api --python_out=. --grpc_out=. --plugin=protoc-gen-grpc=`which grpc_python_plugin` $GOPATH/src/github.com/osrg/gobgp/api/gobgp.proto
$ ls -l
total 184
-rw-rw-r-- 1 tsubo tsubo 180906 Apr 30 04:34 gobgp_pb2.py
-rw-rw-r-- 1 tsubo tsubo   2768 Apr 30 04:34 monitor_BestPathChange.py

いよいよ、サンプルアプリを動かしてみます。
(1) 対向BGPルータ側で、Peerを切断しておく。
ここでは、物理インタフェースを停止されることで、Peerを切断しております。

R1(config)#int f1/0
R1(config-if)#shutdown

(2) GoBGPを起動する。
GoBGPを起動します。

$ sudo ./gobgpd -f gobgpd.conf 
{"level":"info","msg":"gobgpd started","time":"2016-04-30T05:17:37+09:00"}
{"level":"info","msg":"finished reading the config file","time":"2016-04-30T05:17:37+09:00"}
{"level":"info","msg":"Peer 192.168.0.1 is added","time":"2016-04-30T05:17:37+09:00"}
{"level":"info","msg":"Add a peer configuration for 192.168.0.1","time":"2016-04-30T05:17:37+09:00"}

(3) サンプルアプリを起動する。
ちなみに、最後の引数"ipv4"は、AddressFamily: ipv4のみを抽出することを意図します。

$ python ./monitor_BestPathChange.py 192.168.0.2 ipv4

(4) 対向BGPルータ側で、Peerを開設する。
ここでは、物理インタフェースを起動されることで、Peerを開設しております。

R1(config-if)#no shutdown

(5) サンプルアプリ起動結果を確認する。
すると、BGP UPDATEメッセージの受信した様子が確認できます。

$ python ./monitor_BestPathChange.py 192.168.0.2 ipv4
----------------------------
 Rib.prefix     : 20.1.0.0/24
 Rib.origin     : 0
 Rib.aspath     : [[65001]]
 Rib.nexthop    : 192.168.0.1
 Rib.med        : 0
 Rib.is_withdraw : False

(6) GoBGPのBGPテーブル確認する。
BGP UPDATEメッセージの受信に伴い、BGPテーブルが更新された様子が確認できます。

$ gobgp global rib
    Network             Next Hop             AS_PATH              Age        Attrs
*>  20.1.0.0/24         192.168.0.1          65001                00:00:27   [{Origin: i} {Med: 0}]

◆ BGPテーブルへの経路追加用サンプルアプリを試してみる

続いて、Pythonスクリプトから、RPCメソッド:”ModPath”を動作させてみます。
先ほどのディレクトリに移動して、次のサンプルアプリを配備してください。

$ cd $HOME
$ cd sample/
$ vi add_prefix.py
-----------------
import gobgp_pb2
import sys
from netaddr.ip import IPNetwork
from ryu.lib.packet.bgp import BGPPathAttributeOrigin
from ryu.lib.packet.bgp import IPAddrPrefix
from ryu.lib.packet.bgp import BGPPathAttributeNextHop
from grpc.beta import implementations


_TIMEOUT_SECONDS = 10
Resource_GLOBAL  = 0

def run(gobgpd_addr, prefix, nexthop):
    channel = implementations.insecure_channel(gobgpd_addr, 50051)
    with gobgp_pb2.beta_create_GobgpApi_stub(channel) as stub:

        subnet = IPNetwork(prefix)
        ipaddr = subnet.ip
        masklen = subnet.prefixlen

        nlri = IPAddrPrefix(addr=ipaddr, length=masklen)
        bin_nlri = nlri.serialize()

        nexthop = BGPPathAttributeNextHop(value=nexthop)
        bin_nexthop = nexthop.serialize()

        origin = BGPPathAttributeOrigin(value=2)
        bin_origin = origin.serialize()

        pattrs = []
        pattrs.append(str(bin_nexthop))
        pattrs.append(str(bin_origin))

        path = {}
        path['nlri'] = str(bin_nlri)
        path['pattrs'] = pattrs

        uuid = stub.ModPath(gobgp_pb2.ModPathArguments(resource=Resource_GLOBAL, path=path)                           ,_TIMEOUT_SECONDS)

        if uuid:
            print "Success!"
        else:
            print "Error!"


if __name__ == '__main__':
    gobgp = sys.argv[1]
    prefix = sys.argv[2]
    nexthop = sys.argv[3]
    run(gobgp, prefix, nexthop)

早速、サンプルアプリを起動してみます。
(1) GoBGPを起動する。

$ sudo ./gobgpd -f gobgpd.conf 
{"level":"info","msg":"gobgpd started","time":"2016-04-30T07:33:50+09:00"}
{"level":"info","msg":"finished reading the config file","time":"2016-04-30T07:33:50+09:00"}
{"level":"info","msg":"Peer 192.168.0.1 is added","time":"2016-04-30T07:33:50+09:00"}
{"level":"info","msg":"Add a peer configuration for 192.168.0.1","time":"2016-04-30T07:33:50+09:00"}
{"Key":"192.168.0.1","State":"BGP_FSM_OPENCONFIRM","Topic":"Peer","level":"info","msg":"Peer Up","time":"2016-04-30T07:33:54+09:00"}

(2) サンプルアプリを起動する。
うまく動作すれば、"Success!"と表示されます。

$ python ./add_prefix.py 192.168.0.2 30.1.0.0/24 0.0.0.0
Success!

(3) GoBGPのBGPテーブルを確認する。
先ほど、追加した経路"30.1.0.0/24"がエントリされているはずです。

$ gobgp global rib
    Network             Next Hop             AS_PATH              Age        Attrs
*>  20.1.0.0/24         192.168.0.1          65001                00:03:05   [{Origin: i} {Med: 0}]
*>  30.1.0.0/24         0.0.0.0                                   00:00:25   [{Origin: ?}]

(4) 対向BGPルータのBGPテーブルを確認する。

R1#show bgp ipv4 unicast
BGP table version is 9, local router ID is 192.168.0.1
Status codes: s suppressed, d damped, h history, * valid, > best, i - internal, 
              r RIB-failure, S Stale, m multipath, b backup-path, f RT-Filter, 
              x best-external, a additional-path, c RIB-compressed, 
Origin codes: i - IGP, e - EGP, ? - incomplete
RPKI validation codes: V valid, I invalid, N Not found

     Network          Next Hop            Metric LocPrf Weight Path
 *>  20.1.0.0/24      0.0.0.0                  0         32768 i
 *>  30.1.0.0/24      192.168.0.2                            0 65002 ?

pythonスクリプトのサンプルアプリから、GoBGPを制御することができました。

◆ その他、留意事項

今回のサンプルアプリは、RyuBGPで保有しているBGPパケットライブラリを活用することが必須になります。
GoBGPでは、"EVPN", "Mrt", "FlowSpec"など最新BGP機能が利用できますが、これらの機能に関わるBGPメッセージ拡張に対応したPython版パケットライブラリは、RyuBGPには存在しておりません。よって、PythonによるGoBGP制御の適用範囲を、事前に吟味することが必要になります。

◆ 終わりに

今後も、大規模ネットワークにおけるBGPの役割は、重要になってまいります。
そこで、スケール性や耐障害性を担保するためには、BGPエンジンでの円滑な挙動が必要になってくるわけですが、従来のPythonベースのRyuBGPだと、Python特有な課題(GILなど)が顕在化してしまいます。その課題解決のアプローチとして、golangベースでBGPエンジンに焼き直したものが"GoBGP"だと理解しております。
GoBGPの特徴を活かしつつ、既存SDN基盤との連携を実現する手段として、PythonによるgRPC経由でのGoBGP制御は、様々な局面で有用だと思います。

GoBGPでの内部構造で活用されている"gRPC"の仕組みを体験してみる

[ 2020.5.24修正:gRPC最新版への対応 ]
最近、GoBGPでの内部構造として活用されている"gRPC"に興味を持ち始めました。
GoBGP実践活用として、gRPC技術の修得が必要不可欠になりそうなので、まずは、gRPCを体験するところから始めたいと思います。

◆ gRPCとは?

googleが開発したオープンソースベースのRPCフレームワークだそうです。各種プログラミング言語に対応しているみたいで、データ通信層には、HTTP/2が活用されるらしいです。
なお、サーバ/クライアント間でのシリアライズ化などの通信処理は、特に意識することなく、IDLのプリコンパイルによって自動生成されるスタブが担うことになります。
さらに、IDLの記述仕様は、以下のサイトが参考になります。
developers.google.com

このあたりの開発作業スタイルは、その昔の、"DCE/RPCプログラミング"による分散処理システム構築時の実装スタイルとそっくりな感じです。
... まず、rpcgenで、IDLファイルをプリコンパイルしてスタブを自動生成して、サーバ・クライアントアプリに組み込むあたりが...

◆ gRPC環境準備

今回は、Golang/Python版でのgRPC動作環境を作成してみます。

0. Ubuntu環境準備
Ubuntuサーバ環境を準備します。

$ cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=14.04
DISTRIB_CODENAME=trusty
DISTRIB_DESCRIPTION="Ubuntu 14.04.5 LTS"

1. GoBGPのインストール
今回は、gRPCサンプルアプリをサーバ側で動作させる際には、golangが必要になります。
GoBGP環境も、同時に作成しておきます。

$ vi $HOME/.profile
—————————
... (snip)
export GOPATH=$HOME/golang
export PATH=$GOPATH/bin:/usr/local/go/bin:$PATH

$ wget --no-check-certificate https://storage.googleapis.com/golang/go1.13.5.linux-amd64.tar.gz
$ sudo tar -C /usr/local -xzf go1.13.5.linux-amd64.tar.gz
$ mkdir $HOME/golang
$ source .profile
$ go version
go version go1.13.5 linux/amd64

$ sudo apt-get update
$ sudo apt-get install git
$ go get github.com/osrg/gobgp/cmd/gobgpd
$ go get github.com/osrg/gobgp/cmd/gobgp
$ go get github.com/golang/protobuf/protoc-gen-go

2. Protocol Buffersのインストール
Protocol Buffersをインストールします。

$ cd $HOME
$ sudo apt-get install unzip
$ wget https://github.com/google/protobuf/releases/download/v3.12.1/protoc-3.12.1-linux-x86_64.zip
$ unzip protoc-3.12.1-linux-x86_64.zip -d protoc3
$ sudo mv protoc3/bin/* /usr/local/bin/
$ sudo mv protoc3/include/* /usr/local/include/
$ protoc --version
libprotoc 3.12.1

3. gRPC for Pythonのインストール
python版gRPCをインストールします。

$ sudo apt-get install python-dev
$ wget https://bootstrap.pypa.io/get-pip.py
$ sudo python get-pip.py
$ sudo pip install pip==9.0.3
$ sudo pip install grpcio
$ sudo pip install grpcio-tools

◆ gRPCのサンプルアプリ"hello world"を動かしてみる

ここでのサンプルアプリの動作環境の特徴としては、サーバ/クライアント間で異なるプログラミング環境でも、gRPCサーバ/クライアント間での相互運用が可能になっております。
すなわち、サーバ側をgolangで実行し、クライアント側をpythonで実行させています。

1. IDLファイル準備
以下のIDLファイル”helloworld.proto”を、任意ディレクトリ(ここでは、"helloworld"とします)に保存します。

$ cd $HOME
$ mkdir helloworld
$ cd helloworld
$ vi helloworld.proto
------------
syntax = "proto3";

package helloworld;

// The greeting service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings
message HelloReply {
  string message = 1;
}

2. クライアントアプリ準備
まずは、クライアント作業ディレクトリ"client"に移動して、先ほどのIDLファイルをコンパイルします。
すると、"helloworld_pb2.py"が自動生成されます。

$ cd $HOME/helloworld
$ mkdir client
$ cd client/
$ python -m grpc_tools.protoc -I .. --python_out=. --grpc_python_out=. ../helloworld.proto
$ ls -l
total 8
-rw-r--r-- 1 root root 3733 May 24 00:19 helloworld_pb2.py
-rw-r--r-- 1 root root 2196 May 24 00:19 helloworld_pb2_grpc.py

さらに、"greeter_client.py"を同じディレクトリに配備します。

$ vi greeter_client.py
----------------------------
import grpc
import helloworld_pb2
import helloworld_pb2_grpc


def run():
    # NOTE(gRPC Python Team): .close() is possible on a channel and should be
    # used in circumstances in which the with statement does not fit the needs
    # of the code.
    with grpc.insecure_channel('localhost:50051') as channel:
        stub = helloworld_pb2_grpc.GreeterStub(channel)
        response = stub.SayHello(helloworld_pb2.HelloRequest(name='you'))
    print("Greeter client received: " + response.message)


if __name__ == '__main__':
    run()

3. サーバアプリ準備
つぎに、サーバ作業ディレクトリ"server"に移動して、先ほどのIDLファイルをコンパイルします。
すると、"helloworld.pb.go"が自動生成されます。

$ cd $HOME/helloworld
$ mkdir server
$ cd server/
$ protoc -I .. --go_out=plugins=grpc:. ../helloworld.proto
$ ls -l
total 12
-rw-r--r-- 1 root root 9579 May 24 00:27 helloworld.pb.go
$ mkdir helloworld
$ mv helloworld.pb.go helloworld

さらに、"greeter_server.go"を配備します。

$ vi greeter_server.go
----------------------------
package main

import (
        "log"
        "net"

        pb "./helloworld"
        "golang.org/x/net/context"
        "google.golang.org/grpc"
)

const (
        port = ":50051"
)

// server is used to implement hellowrld.GreeterServer.
type server struct{}

// SayHello implements helloworld.GreeterServer
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
        return &pb.HelloReply{Message: "Hello " + in.Name}, nil
}

func main() {
        lis, err := net.Listen("tcp", port)
        if err != nil {
                log.Fatalf("failed to listen: %v", err)
        }
        s := grpc.NewServer()
        pb.RegisterGreeterServer(s, &server{})
        s.Serve(lis)
}

4. サンプルアプリ起動
まずは、サーバ側サンプルアプリを起動します。

$ go run greeter_server.go

続いて、別ターミナルから、クライアントアプリを起動します。
"Greeter client received: Hello you"と出力されるとサンプル起動は、成功です。

$ python greeter_client.py 
Greeter client received: Hello you

◆ 終わりに

GoBGP活用として、Ryu SDN Framework連携を目指した初めの一歩として、gRPCによるサンプルアプリを動作させてみました。
次回は、GoBGP実践活用の前段として、Pythonアプリ連携にチャレンジしてみます。

GoBGPを活用して、BGP動作を体験してみる 〜環境準備編〜

[ 2016.4.29修正:gobgp最新版への対応 ]
これまで、RyuBGPSpeakerを活用して、SDN分野におけるBGP技術の有用性を試してきました。
SDN分野におけるBGP新技術を活用したユースケースが、IETF等でいろいろ議論されておりますので、今後も、
SDN分野でBGP新技術を試してみて、いろいろ知見を貯めたいところです。
ただ、Ryu開発コミュニティでは、BGP新機能の議論は、活発に行われていないようです。

いっぽう、golangベースで実装されたオープンソース"GoBGP v1.0"がリリースされました。
GoBGPの提供機能は、SDN分野での活用を見据えて、BGP新機能が実装されているようです。
そこで、GoBGPをいろいろと試してみたいと思います。

github.com


◆既存ネットワーク構築

まず、GoBGPを試すには、既存ネットワークを事前に準備する必要があります。
ただし、既存ネットワーク構築準備に、あまり稼働を裂きたくないので、"docker-pseudo-wan"なる構築ツールを活用することとします。
github.com

既存ネットワーク構築は、以下のREADME.mdの記述とおり実行すると、MPLS-VPNが簡単に構築することができます。
https://github.com/ttsubo/docker-pseudo-wan/blob/master/README.md

◆GoBGP検証構成

既存ネットワークが構築できたら、GoBGP側も構築していきます。
今回の検証構成は、こんな感じです。

                                +-----------+               +----------+
host_001_101 container -------+ |  RyuBGP   | +-----------+ |  GoBGP   | +---- ...
host_002_102 container -------+ | container |               |          |
     :                          +-----------+               +----------+
                     < AS65001 >   192.168.0.1             192.168.0.2  < AS65002 >

           <-- 既存ネットワークを事前準備 --->               <-- 今回の構築対象 --->

ただし、今回は、BGPによるルーティング情報の伝播に関わる挙動に特化しますので、エンドエンド通信に関わるD-planeは想定外です。

◆GoBGP構築手順

GoBGPの環境構築は、ライブラリ依存関係を意識したりする必要がなく、至ってシンプルにインストールすることができます。
ちなみに、今回の動作確認では、Ubuntu Server版に、GoBGPをインストールしました。

$ cat /etc/lsb-release 
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=14.04
DISTRIB_CODENAME=trusty
DISTRIB_DESCRIPTION="Ubuntu 14.04.3 LTS"

1. Linuxネットワーク設定

$ sudo vi /etc/network/interfaces
—————————
..(snip)

auto eth1
iface eth1 inet static
address 192.168.0.2
netmask 255.255.255.0

2. golangのインストール

$ vi $HOME/.profile
—————————
export GOPATH=$HOME/golang
export PATH=$GOPATH/bin:/usr/local/go/bin:$PATH

$ wget --no-check-certificate https://storage.googleapis.com/golang/go1.6.2.linux-amd64.tar.gz
$ sudo tar -C /usr/local -xzf go1.6.2.linux-amd64.tar.gz
$ mkdir $HOME/golang
$ source .profile

$ go version
go version go1.6.2 linux/amd64

3. goBGPのインストール

$ sudo apt-get update
$ sudo apt-get install git
$ go get -u github.com/osrg/gobgp/gobgpd
$ go get -u github.com/osrg/gobgp/gobgp

4. gobgpd設定ファイル

% vi $HOME/golang/bin/gobgpd.conf 
—————————
[global]
 [global.config]
   as = 65002
   router-id = "192.168.0.2"
 [global.mpls-label-range]
   min-label = 1000
   max-label = 2000

[[neighbors]]
 [neighbors.config]
   peer-type = "external"
   neighbor-address = "192.168.0.1"
   peer-as = 65001
   local-as = 65002
[[neighbors.afi-safis]]
 [neighbors.afi-safis.config]
   afi-safi-name = "ipv4-unicast"
[[neighbors.afi-safis]]
 [neighbors.afi-safis.config]
   afi-safi-name = "l3vpn-ipv4-unicast"

◆動作確認

1. gobgpdの起動

$ cd $HOME/golang/bin
$ sudo ./gobgpd -f gobgpd.conf 
{"level":"info","msg":"gobgpd started","time":"2016-04-29T15:37:54+09:00"}
{"level":"info","msg":"finished reading the config file","time":"2016-04-29T15:37:54+09:00"}
{"level":"info","msg":"Peer 192.168.0.1 is added","time":"2016-04-29T15:37:54+09:00"}
{"level":"info","msg":"Add a peer configuration for 192.168.0.1","time":"2016-04-29T15:37:54+09:00"}

2. BGP Peer確認

$ gobgp neighbor 192.168.0.1
BGP neighbor is 192.168.0.1, remote AS 65001
  BGP version 4, remote router ID 10.10.10.1
  BGP state = BGP_FSM_ESTABLISHED, up for 16920d 06:38:02
  BGP OutQ = 0, Flops = 0
  Hold time is 40, keepalive interval is 30 seconds
  Configured hold time is 90, keepalive interval is 30 seconds
  Neighbor capabilities:
    BGP_CAP_MULTIPROTOCOL:
        ipv4-unicast:	advertised and received
        l3vpn-ipv4-unicast:	advertised and received
    BGP_CAP_ROUTE_REFRESH:	advertised and received
    BGP_CAP_FOUR_OCTET_AS_NUMBER:	advertised
    BGP_CAP_ENHANCED_ROUTE_REFRESH:	received
  Message statistics:
                         Sent       Rcvd
    Opens:                  1          1
    Notifications:          0          0
    Updates:                0         33
    Keepalives:             1          1
    Route Refesh:           0          0
    Discarded:              0          0
    Total:                  2         35
  Route statistics:
    Advertised:             0
    Received:              33
    Accepted:              33

3. vrfユーザの登録

$ gobgp vrf add customerA rd 65001:101 rt both 65001:101

vrfユーザを登録すると、RD, RTに値が正しく設定されている様子が確認できます。

$ gobgp vrf
  Name                 RD                   Import RT            Export RT           
  customerA            65001:101            65001:101            65001:101      

4. ルーティング情報の登録

$ gobgp vrf customerA rib add 30.1.0.0/24

ルーティング情報を登録すると、対向BGPルータで保持しているルーティング情報を受信することになります。
その様子を確認してみます。

$ gobgp vrf customerA rib
    Network               Labels     Next Hop             AS_PATH              Age        Attrs
    65001:101:110.1.9.0/24[1001]     192.168.0.1          65001                00:01:30   [{Origin: ?} {Med: 100} {Extcomms: [65001:101]}]
    65001:101:110.1.4.0/24[1001]     192.168.0.1          65001                00:01:30   [{Origin: ?} {Med: 100} {Extcomms: [65001:101]}]
    65001:101:110.1.6.0/24[1001]     192.168.0.1          65001                00:01:30   [{Origin: ?} {Med: 100} {Extcomms: [65001:101]}]
    65001:101:110.1.2.0/24[1001]     192.168.0.1          65001                00:01:30   [{Origin: ?} {Med: 100} {Extcomms: [65001:101]}]
    65001:101:110.1.5.0/24[1001]     192.168.0.1          65001                00:01:30   [{Origin: ?} {Med: 100} {Extcomms: [65001:101]}]
    65001:101:30.1.0.0/24[1000]     0.0.0.0                                   00:00:06   [{Origin: ?} {Extcomms: [65001:101]}]
    65001:101:110.1.8.0/24[1001]     192.168.0.1          65001                00:01:30   [{Origin: ?} {Med: 100} {Extcomms: [65001:101]}]
    65001:101:110.1.1.0/24[1001]     192.168.0.1          65001                00:01:30   [{Origin: ?} {Med: 100} {Extcomms: [65001:101]}]
    65001:101:110.1.0.0/24[1001]     192.168.0.1          65001                00:01:30   [{Origin: ?} {Med: 100} {Extcomms: [65001:101]}]
    65001:101:110.1.7.0/24[1001]     192.168.0.1          65001                00:01:30   [{Origin: ?} {Med: 100} {Extcomms: [65001:101]}]
    65001:101:110.1.3.0/24[1001]     192.168.0.1          65001                00:01:30   [{Origin: ?} {Med: 100} {Extcomms: [65001:101]}]
    65001:101:10.1.0.2/32[1000]     192.168.0.1          65001                00:01:30   [{Origin: ?} {Med: 100} {Extcomms: [65001:101]}]

◆終わりに

GoBGPを動作させて、MPLS-VPNの挙動を確認しました。
次回より、GoBGPの動作原理に、DeepDiveしてみたいです。

golangで開発アプリのコマンドインタフェースを拡張するには ...

今回は、golangでコマンドインタフェースの定義する際の自分用メモです。
以下の外部アプリを組み込むと、簡単にコマンドインタフェースが拡張できるみたいです。github.com

OMGが定めた分散オブジェクト技術の仕様"CORBA"とは、何の関係ありません。
あしからず。

spf13/cobra環境構築

golangのインストールは、ここでは割愛します。

$ export GOPATH=/Users/ttsubo/gocode
$ go get github.com/spf13/cobra
$ cd gocode/src/github.com/spf13

$ ls -l
total 0
drwxr-xr-x  17 ttsubo  staff   578  8 17 17:03 cobra
drwxr-xr-x  38 ttsubo  staff  1292  8 17 17:03 pflag

サンプルアプリ作成

Githubリポジトリに記載されたREADME.mdのサンプルアプリをそのまま動作させてみて、その挙動を確認します。
まずは、サンプルアプリを準備します。

package main

import(
    "github.com/spf13/cobra"
    "fmt"
    "strings"
)

func main() {

    var echoTimes int

    var cmdPrint = &cobra.Command{
        Use:   "print [string to print]",
        Short: "Print anything to the screen",
        Long:  `print is for printing anything back to the screen.
        For many years people have printed back to the screen.
        `,
        Run: func(cmd *cobra.Command, args []string) {
            fmt.Println("Print: " + strings.Join(args, " "))
        },
    }

    var cmdEcho = &cobra.Command{
        Use:   "echo [string to echo]",
        Short: "Echo anything to the screen",
        Long:  `echo is for echoing anything back.
        Echo works a lot like print, except it has a child command.
        `,
        Run: func(cmd *cobra.Command, args []string) {
            fmt.Println("Print: " + strings.Join(args, " "))
        },
    }

    var cmdTimes = &cobra.Command{
        Use:   "times [# times] [string to echo]",
        Short: "Echo anything to the screen more times",
        Long:  `echo things multiple times back to the user by providing
        a count and a string.`,
        Run: func(cmd *cobra.Command, args []string) {
            for i:=0; i < echoTimes; i++ {
                fmt.Println("Echo: " + strings.Join(args, " "))
            }
        },
    }

    cmdTimes.Flags().IntVarP(&echoTimes, "times", "t", 1, "times to echo the input")

    var rootCmd = &cobra.Command{Use: "app"}
    rootCmd.AddCommand(cmdPrint, cmdEcho)
    cmdEcho.AddCommand(cmdTimes)
    rootCmd.Execute()
}

そして、サンプルアプリをコンパイルします。

$ go build app.go

サンプルアプリを動作させてみる

以降、サンプルアプリを動作させてみます。コマンド結果と、実際のコードを比較してみると、コマンドライン拡張できる様子が理解できると思います。

(1) helpコマンドで起動してみる。

$ ./app help


Usage: 
  app [command]

Available Commands: 
  print       Print anything to the screen
  echo        Echo anything to the screen
  help        Help about any command

Flags:
  -h, --help[=false]: help for app


Use "app [command] --help" for more information about a command.

(2) printコマンドを起動してみる。

まずは、オプション"-h"を付与して起動してみます。

$ ./app print -h
print is for printing anything back to the screen.
        For many years people have printed back to the screen.

Usage: 
  app print [string to print] [flags]

Flags:
  -h, --help[=false]: help for print

つづいて、printコマンドで任意文字列"aaa"を出力してみます。

$ ./app print aaa
Print: aaa

(2) echoコマンドを起動してみる。

まずは、オプション"-h"を付与して起動してみます。

$ ./app echo -h
echo is for echoing anything back.
        Echo works a lot like print, except it has a child command.

Usage: 
  app echo [string to echo] [flags]
  app echo [command]

Available Commands: 
  times       Echo anything to the screen more times

Flags:
  -h, --help[=false]: help for echo


Use "app echo [command] --help" for more information about a command.

つづいて、echoコマンドで任意文字列"aaa"を出力してみます。

$ ./app echo aaa
Print: aaa

(2) echo timesコマンドを起動してみる。

まずは、オプション"-h"を付与して起動してみます。

$ ./app echo times -h
echo things multiple times back to the user by providing
        a count and a string.

Usage: 
  app echo times [# times] [string to echo] [flags]

Flags:
  -h, --help[=false]: help for times
  -t, --times=1: times to echo the input

つづいて、echo timesコマンドで任意文字列"aaa"を出力してみます。

$ ./app echo times -t 2 aaa
Echo: aaa
Echo: aaa

echoのサブコマンドとしてtimesが動作している様子がわかります。

おわりに

spf13/cobraを活用すれば、簡易にgolangで作成するアプリのコマンドインタフェースを拡張することがわかりました。
今回は、ほんの入り口にすぎませんので、ご容赦ください。

SDN Lab環境でのRobot Framework実践活用

最近、オープンソース系ソフトウェアを独自カスタマイズしてプロダクト化する事例が増えている気がします。
これからのSDN/NFV分野では、ホワイトボックス化されたネットワーク機器にオープンソース系ソフトウェアを搭載して仮想アプライアンスとして導入するユースケースが増えていくものと推測できます。
オープンソース系ソフトウェアを独自カスタマイズして実ネットワークに導入する場会、カスタマイズ部分のソフトウェア品質担保が必須条件となるので、ソフトウェア開発フェーズでのテスト網羅性が極めて重要になります。

従来のソフトウェア開発でもユニットテストレベルでのテスト網羅性&自動化は、一般的に着手されておりました。
ただ、これからのクラウドコンピューティング技術革新のスピード感が、どんどん増している状況下においては、プロダクトレベルでのテスト網羅性&自動化を念頭に置く必要があると感じております。

そこで、今回は、Robot Frameworkによるプロダクトテスト自動化に着目してみたいと思います。

▪️Robot Frameworkで何ができる?

テスト自動化を目指したRobot Framework活用事例をSlideShareのスライドにまとめましたので参考にしてください。

www.slideshare.net

▪️OpenStack環境で、Robot Frameworkを試してみる

SDN/NFV分野では、OpenStackが担う役割/分担がどんどん増加していくと思います。
そこで、応用編として、OpenStack環境でのRobot Framework活用によるテスト自動化を試してみます。

(1) テスト環境として、OpenStackを配備

今回ためしてみるTestCaesは、Netronの基本動作になります。
Neutronにて、Network -> SubNet -> Portを新規作成します。
OpenStackテスト環境としては、devstackでも構いません。
(adminユーザのパスワードは、"secrete"としてください。)

(2) Robot Frameworkインストール

まずは、Robot Frameworkパッケージをインストールします。
今回は、Ubuntu Server版で試してみました。

$ sudo pip install robotframework
$ sudo pip install robotframework-requests
$ sudo pip install robotframework-sshlibrary
$ sudo pip install requests

(3) サンプルTestCaseの取得

続いて、Robot Framework用サンプルTestCaseを取得します。

$ git clone https://github.com/ttsubo/robotframework_for_openstack.git
$ cd robotframework_for_openstack

(4) サンプルTestCaseの実行

Neutronにて、Network -> SubNet -> Portを新規作成するTest Caseを実行します。

$ pybot --variable OPENSTACK:127.0.0.1 tests
==============================================================================
Tests :: Test suite for Neutron                                               
==============================================================================
Tests.Networks :: Checking Network created in OpenStack                       
==============================================================================
Check OpenStack Networks :: Checking OpenStack Neutron for known n... | PASS |
------------------------------------------------------------------------------
Create Network :: Create new network in OpenStack                     | PASS |
------------------------------------------------------------------------------
Check Network :: Check Network created in OpenStack                   | PASS |
------------------------------------------------------------------------------
Tests.Networks :: Checking Network created in OpenStack               | PASS |
3 critical tests, 3 passed, 0 failed
3 tests total, 3 passed, 0 failed
==============================================================================
Tests.Subnets :: Checking Subnets created in OpenStack                        
==============================================================================
Check OpenStack Subnets :: Checking OpenStack Neutron for known Su... | PASS |
------------------------------------------------------------------------------
Create New subnet :: Create new subnet in OpenStack                   | PASS |
------------------------------------------------------------------------------
Check New subnet :: Check new subnet created in OpenStack             | PASS |
------------------------------------------------------------------------------
Tests.Subnets :: Checking Subnets created in OpenStack                | PASS |
3 critical tests, 3 passed, 0 failed
3 tests total, 3 passed, 0 failed
==============================================================================
Tests.Ports :: Checking Port created in OpenStack                             
==============================================================================
Check OpenStack ports :: Checking OpenStack Neutron for known ports   | PASS |
------------------------------------------------------------------------------
Create New Port :: Create new port in OpenStack                       | PASS |
------------------------------------------------------------------------------
Check New Port :: Check new subnet created in OpenStack               | PASS |
------------------------------------------------------------------------------
Tests.Ports :: Checking Port created in OpenStack                     | PASS |
3 critical tests, 3 passed, 0 failed
3 tests total, 3 passed, 0 failed
==============================================================================
Tests :: Test suite for Neutron                                       | PASS |
9 critical tests, 9 passed, 0 failed
9 tests total, 9 passed, 0 failed
==============================================================================
Output:  /home/tsubo/devel/robotframework_for_openstack/output.xml
Log:     /home/tsubo/devel/robotframework_for_openstack/log.html
Report:  /home/tsubo/devel/robotframework_for_openstack/report.html

TestCaseを実行したことにより、"test-network"が作成されました。
f:id:ttsubo:20150531180751p:plain

▪️TestCaseの実行結果の振り返り

Robot FrameworkでのTestCaes実行結果は、後日、ログ結果として確認することができます。
今回、サンプル版TestCase実行結果としてのログ結果は、こんな感じです。
まったく手間を掛けずに、Robot Frameworkの基本機能として、詳細なテスト実行履歴を保存できます。

http://ttsubo.github.io/robotframework_for_openstack/log.html

▪️おわりに

Robot Framework活用により、プロダクトレベルのシステム結合テストを完全に自動化することが理解できました。
TestCase作成には、独特なテストコード記述方法を習得する必要がありますが、慣れれば、そんなに苦にならないと思います。