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