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"をダウンロードします。
◆ 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」を入力して、オペレーションモードに移行してみます。
以上で、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状態遷移の様子になります。
実際、VRRPパケットダンプも貼っておきます。
今回、はじめて気がついたのですが、VRRP Advertisementパケットには、仮想MACアドレス情報は設定されないんですよね。
Ryu VRRPモジュールでの仮想MACアドレスの扱いについて、もう少し、リバースエンジニアリングが必要かもしれません。
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ルータを準備します。
この段階で、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のフィールドに設定させている。
をチェックしておいてください。
(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 ?
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をいろいろと試してみたいと思います。
◆既存ネットワーク構築
まず、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が動作している様子がわかります。
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"が作成されました。
▪️TestCaseの実行結果の振り返り
Robot FrameworkでのTestCaes実行結果は、後日、ログ結果として確認することができます。
今回、サンプル版TestCase実行結果としてのログ結果は、こんな感じです。
まったく手間を掛けずに、Robot Frameworkの基本機能として、詳細なテスト実行履歴を保存できます。
http://ttsubo.github.io/robotframework_for_openstack/log.html
▪️おわりに
Robot Framework活用により、プロダクトレベルのシステム結合テストを完全に自動化することが理解できました。
TestCase作成には、独特なテストコード記述方法を習得する必要がありますが、慣れれば、そんなに苦にならないと思います。