github.com/osrg/gobgp@v2.0.0+incompatible/test/scenario_test/bgp_zebra_nht_test.py (about) 1 # Copyright (C) 2017 Nippon Telegraph and Telephone Corporation. 2 # 3 # Licensed under the Apache License, Version 2.0 (the "License"); 4 # you may not use this file except in compliance with the License. 5 # You may obtain a copy of the License at 6 # 7 # http://www.apache.org/licenses/LICENSE-2.0 8 # 9 # Unless required by applicable law or agreed to in writing, software 10 # distributed under the License is distributed on an "AS IS" BASIS, 11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 12 # implied. 13 # See the License for the specific language governing permissions and 14 # limitations under the License. 15 16 import sys 17 import time 18 import unittest 19 20 from fabric.api import local 21 import nose 22 23 from lib.noseplugin import OptionParser, parser_option 24 25 from lib import base 26 from lib.base import ( 27 assert_several_times, 28 Bridge, 29 BGP_FSM_ESTABLISHED, 30 ) 31 from lib.gobgp import GoBGPContainer 32 from lib.quagga import QuaggaOSPFContainer 33 34 35 def get_ifname_with_prefix(prefix, f=local): 36 command = ( 37 "ip addr show to %s" 38 " | head -n1 | cut -d'@' -f1 | cut -d' ' -f2") % prefix 39 40 return f(command, capture=True) 41 42 43 class ZebraNHTTest(unittest.TestCase): 44 """ 45 Test case for Next-Hop Tracking with Zebra integration. 46 """ 47 48 def _assert_med_equal(self, rt, prefix, med): 49 rib = rt.get_global_rib(prefix=prefix) 50 self.assertEqual(len(rib), 1) 51 self.assertEqual(len(rib[0]['paths']), 1) 52 self.assertEqual(rib[0]['paths'][0]['med'], med) 53 54 # R1: GoBGP 55 # R2: GoBGP + Zebra + OSPFd 56 # R3: Zebra + OSPFd 57 # R4: Zebra + OSPFd 58 # 59 # +----+ 60 # | R3 |... has loopback 10.3.1.1/32 61 # +----+ 62 # / | 63 # / | 64 # / +----+ 65 # / | R4 | 66 # / +----+ 67 # +----+ | 68 # | R2 |------+ 69 # +----+ 70 # | 192.168.0.2/24 71 # | 72 # | 192.168.0.0/24 73 # | 74 # | 192.168.0.1/24 75 # +----+ 76 # | R1 | 77 # +----+ 78 79 @classmethod 80 def setUpClass(cls): 81 gobgp_ctn_image_name = parser_option.gobgp_image 82 base.TEST_PREFIX = parser_option.test_prefix 83 cls.r1 = GoBGPContainer( 84 name='r1', asn=65000, router_id='192.168.0.1', 85 ctn_image_name=gobgp_ctn_image_name, 86 log_level=parser_option.gobgp_log_level, 87 zebra=False) 88 89 cls.r2 = GoBGPContainer( 90 name='r2', asn=65000, router_id='192.168.0.2', 91 ctn_image_name=gobgp_ctn_image_name, 92 log_level=parser_option.gobgp_log_level, 93 zebra=True, 94 zapi_version=3, 95 ospfd_config={ 96 'networks': { 97 '192.168.23.0/24': '0.0.0.0', 98 '192.168.24.0/24': '0.0.0.0', 99 }, 100 }) 101 102 cls.r3 = QuaggaOSPFContainer( 103 name='r3', 104 zebra_config={ 105 'interfaces': { 106 'lo': [ 107 'ip address 10.3.1.1/32', 108 ], 109 }, 110 }, 111 ospfd_config={ 112 'networks': { 113 '10.3.1.1/32': '0.0.0.0', 114 '192.168.23.0/24': '0.0.0.0', 115 '192.168.34.0/24': '0.0.0.0', 116 }, 117 }) 118 119 cls.r4 = QuaggaOSPFContainer( 120 name='r4', 121 ospfd_config={ 122 'networks': { 123 '192.168.34.0/24': '0.0.0.0', 124 '192.168.24.0/24': '0.0.0.0', 125 }, 126 }) 127 128 wait_time = max(ctn.run() for ctn in [cls.r1, cls.r2, cls.r3, cls.r4]) 129 time.sleep(wait_time) 130 131 cls.br_r1_r2 = Bridge(name='br_r1_r2', subnet='192.168.12.0/24') 132 for ctn in (cls.r1, cls.r2): 133 cls.br_r1_r2.addif(ctn) 134 135 cls.br_r2_r3 = Bridge(name='br_r2_r3', subnet='192.168.23.0/24') 136 for ctn in (cls.r2, cls.r3): 137 cls.br_r2_r3.addif(ctn) 138 139 cls.br_r2_r4 = Bridge(name='br_r2_r4', subnet='192.168.24.0/24') 140 for ctn in (cls.r2, cls.r4): 141 cls.br_r2_r4.addif(ctn) 142 143 cls.br_r3_r4 = Bridge(name='br_r3_r4', subnet='192.168.34.0/24') 144 for ctn in (cls.r3, cls.r4): 145 cls.br_r3_r4.addif(ctn) 146 147 def test_01_BGP_neighbor_established(self): 148 # Test to start BGP connection up between r1-r2. 149 150 self.r1.add_peer(self.r2, bridge=self.br_r1_r2.name) 151 self.r2.add_peer(self.r1, bridge=self.br_r1_r2.name) 152 153 self.r1.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=self.r2) 154 155 def test_02_OSPF_established(self): 156 # Test to start OSPF connection up between r2-r3 and receive the route 157 # to r3's loopback '10.3.1.1'. 158 def _f(): 159 self.assertEqual(self.r2.local( 160 "vtysh -c 'show ip ospf route'" 161 " | grep '10.3.1.1/32' > /dev/null" 162 " && echo OK || echo NG", 163 capture=True), 'OK') 164 165 assert_several_times(f=_f, t=120) 166 167 def test_03_add_ipv4_route(self): 168 # Test to add IPv4 route to '10.3.1.0/24' whose nexthop is r3's 169 # loopback '10.3.1.1'. Also, test to receive the initial MED/Metric. 170 171 # MED/Metric = 10(r2 to r3) + 10(r3-ethX to r3-lo) 172 med = 20 173 174 self.r2.local( 175 'gobgp global rib add -a ipv4 10.3.1.0/24 nexthop 10.3.1.1') 176 177 assert_several_times( 178 f=lambda: self._assert_med_equal(self.r2, '10.3.1.0/24', med)) 179 assert_several_times( 180 f=lambda: self._assert_med_equal(self.r1, '10.3.1.0/24', med)) 181 182 # Test if the path, which came after the NEXTHOP_UPDATE message was 183 # received from Zebra, is updated by reflecting the nexthop cache. 184 self.r2.local( 185 'gobgp global rib add -a ipv4 10.3.2.0/24 nexthop 10.3.1.1') 186 187 assert_several_times( 188 f=lambda: self._assert_med_equal(self.r2, '10.3.2.0/24', med)) 189 assert_several_times( 190 f=lambda: self._assert_med_equal(self.r1, '10.3.2.0/24', med)) 191 192 self.r2.local( 193 'gobgp global rib del -a ipv4 10.3.2.0/24') 194 195 def test_04_link_r2_r3_down(self): 196 # Test to update MED to the nexthop if the Metric to that nexthop is 197 # changed by the link down. If the link r2-r3 goes down, MED/Metric 198 # should be increased. 199 200 # MED/Metric = 10(r2 to r4) + 10(r4 to r3) + 10(r3-ethX to r3-lo) 201 med = 30 202 203 ifname = get_ifname_with_prefix('192.168.23.3/24', f=self.r3.local) 204 self.r3.local('ip link set %s down' % ifname) 205 206 assert_several_times( 207 f=lambda: self._assert_med_equal(self.r2, '10.3.1.0/24', med)) 208 assert_several_times( 209 f=lambda: self._assert_med_equal(self.r1, '10.3.1.0/24', med)) 210 211 def test_05_nexthop_unreachable(self): 212 # Test to update the nexthop state if nexthop become unreachable by 213 # link down. If the link r2-r3 and r2-r4 goes down, there is no route 214 # to r3. 215 216 def _f_r2(prefix): 217 self.assertEqual(self.r2.local( 218 "gobgp global rib -a ipv4 %s" 219 " | grep '^* ' > /dev/null" # not best "*>" 220 " && echo OK || echo NG" % prefix, 221 capture=True), 'OK') 222 223 def _f_r1(prefix): 224 self.assertEqual(self.r1.local( 225 "gobgp global rib -a ipv4 %s" 226 "| grep 'Network not in table' > /dev/null" 227 " && echo OK || echo NG" % prefix, 228 capture=True), 'OK') 229 230 ifname = get_ifname_with_prefix('192.168.24.4/24', f=self.r4.local) 231 self.r4.local('ip link set %s down' % ifname) 232 233 assert_several_times(f=lambda: _f_r2("10.3.1.0/24"), t=120) 234 assert_several_times(f=lambda: _f_r1("10.3.1.0/24"), t=120) 235 236 # Test if the path, which came after the NEXTHOP_UPDATE message was 237 # received from Zebra, is updated by reflecting the nexthop cache. 238 self.r2.local( 239 'gobgp global rib add -a ipv4 10.3.2.0/24 nexthop 10.3.1.1') 240 241 assert_several_times(f=lambda: _f_r2("10.3.2.0/24"), t=120) 242 assert_several_times(f=lambda: _f_r1("10.3.2.0/24"), t=120) 243 244 # Confirm the stability of the nexthop state 245 for _ in range(10): 246 time.sleep(1) 247 _f_r2("10.3.1.0/24") 248 _f_r1("10.3.1.0/24") 249 _f_r2("10.3.2.0/24") 250 _f_r1("10.3.2.0/24") 251 252 def test_06_link_r2_r4_restore(self): 253 # Test to update the nexthop state if nexthop become reachable again. 254 # If the link r2-r4 goes up again, MED/Metric should be the value of 255 # the path going through r4. 256 257 # MED/Metric = 10(r2 to r4) + 10(r4 to r3) + 10(r3-ethX to r3-lo) 258 med = 30 259 260 ifname = get_ifname_with_prefix('192.168.24.4/24', f=self.r4.local) 261 self.r4.local('ip link set %s up' % ifname) 262 263 assert_several_times( 264 f=lambda: self._assert_med_equal(self.r2, '10.3.1.0/24', med)) 265 assert_several_times( 266 f=lambda: self._assert_med_equal(self.r1, '10.3.1.0/24', med)) 267 268 def test_07_nexthop_restore(self): 269 # Test to update the nexthop state if the Metric to that nexthop is 270 # changed. If the link r2-r3 goes up again, MED/Metric should be update 271 # with the initial value. 272 273 # MED/Metric = 10(r2 to r3) + 10(r3-ethX to r3-lo) 274 med = 20 275 276 ifname = get_ifname_with_prefix('192.168.23.3/24', f=self.r3.local) 277 self.r3.local('ip link set %s up' % ifname) 278 279 assert_several_times( 280 f=lambda: self._assert_med_equal(self.r2, '10.3.1.0/24', med)) 281 assert_several_times( 282 f=lambda: self._assert_med_equal(self.r1, '10.3.1.0/24', med)) 283 284 285 if __name__ == '__main__': 286 output = local("which docker 2>&1 > /dev/null ; echo $?", capture=True) 287 if int(output) is not 0: 288 print "docker not found" 289 sys.exit(1) 290 291 nose.main(argv=sys.argv, addplugins=[OptionParser()], 292 defaultTest=sys.argv[0])