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