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])