github.com/osrg/gobgp@v2.0.0+incompatible/test/lib/exabgp.py (about)

     1  # Copyright (C) 2015 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  from __future__ import absolute_import
    17  
    18  from fabric import colors
    19  
    20  from lib.base import (
    21      BGPContainer,
    22      CmdBuffer,
    23      try_several_times,
    24      wait_for_completion,
    25  )
    26  
    27  
    28  class ExaBGPContainer(BGPContainer):
    29  
    30      SHARED_VOLUME = '/shared_volume'
    31      PID_FILE = '/var/run/exabgp.pid'
    32  
    33      def __init__(self, name, asn, router_id, ctn_image_name='osrg/exabgp:4.0.5'):
    34          super(ExaBGPContainer, self).__init__(name, asn, router_id, ctn_image_name)
    35          self.shared_volumes.append((self.config_dir, self.SHARED_VOLUME))
    36  
    37      def _pre_start_exabgp(self):
    38          # Create named pipes for "exabgpcli"
    39          named_pipes = '/run/exabgp.in /run/exabgp.out'
    40          self.local('mkfifo {0}'.format(named_pipes), capture=True)
    41          self.local('chmod 777 {0}'.format(named_pipes), capture=True)
    42  
    43      def _start_exabgp(self):
    44          cmd = CmdBuffer(' ')
    45          cmd << 'env exabgp.log.destination={0}/exabgpd.log'.format(self.SHARED_VOLUME)
    46          cmd << 'exabgp.daemon.user=root'
    47          cmd << 'exabgp.daemon.pid={0}'.format(self.PID_FILE)
    48          cmd << 'exabgp.tcp.bind="0.0.0.0" exabgp.tcp.port=179'
    49          cmd << 'exabgp {0}/exabgpd.conf'.format(self.SHARED_VOLUME)
    50          self.local(str(cmd), detach=True)
    51  
    52      def _wait_for_boot(self):
    53          def _f():
    54              ret = self.local('exabgpcli version > /dev/null 2>&1; echo $?', capture=True)
    55              return ret == '0'
    56  
    57          return wait_for_completion(_f)
    58  
    59      def run(self):
    60          super(ExaBGPContainer, self).run()
    61          self._pre_start_exabgp()
    62          # To start ExaBGP, it is required to configure neighbor settings, so
    63          # here does not start ExaBGP yet.
    64          # self._start_exabgp()
    65          return self.WAIT_FOR_BOOT
    66  
    67      def create_config(self):
    68          # Manpage of exabgp.conf(5):
    69          # https://github.com/Exa-Networks/exabgp/blob/master/doc/man/exabgp.conf.5
    70          cmd = CmdBuffer('\n')
    71          for peer, info in self.peers.iteritems():
    72              cmd << 'neighbor {0} {{'.format(info['neigh_addr'].split('/')[0])
    73              cmd << '    router-id {0};'.format(self.router_id)
    74              cmd << '    local-address {0};'.format(info['local_addr'].split('/')[0])
    75              cmd << '    local-as {0};'.format(self.asn)
    76              cmd << '    peer-as {0};'.format(peer.asn)
    77  
    78              caps = []
    79              if info['as2']:
    80                  caps.append('        asn4 disable;')
    81              if info['addpath']:
    82                  caps.append('        add-path send/receive;')
    83              if caps:
    84                  cmd << '    capability {'
    85                  for cap in caps:
    86                      cmd << cap
    87                  cmd << '    }'
    88  
    89              if info['passwd']:
    90                  cmd << '    md5-password "{0}";'.format(info['passwd'])
    91  
    92              if info['passive']:
    93                  cmd << '    passive;'
    94              cmd << '}'
    95  
    96          with open('{0}/exabgpd.conf'.format(self.config_dir), 'w') as f:
    97              print colors.yellow('[{0}\'s new exabgpd.conf]'.format(self.name))
    98              print colors.yellow(str(cmd))
    99              f.write(str(cmd))
   100  
   101      def _is_running(self):
   102          ret = self.local("test -f {0}; echo $?".format(self.PID_FILE), capture=True)
   103          return ret == '0'
   104  
   105      def reload_config(self):
   106          if not self.peers:
   107              return
   108  
   109          def _reload():
   110              if self._is_running():
   111                  self.local('/usr/bin/pkill --pidfile {0} && rm -f {0}'.format(self.PID_FILE), capture=True)
   112              else:
   113                  self._start_exabgp()
   114                  self._wait_for_boot()
   115  
   116              if not self._is_running():
   117                  raise RuntimeError('Could not start ExaBGP')
   118  
   119          try_several_times(_reload)
   120  
   121      def _construct_ip_unicast(self, path):
   122          cmd = CmdBuffer(' ')
   123          cmd << str(path['prefix'])
   124          if path['next-hop']:
   125              cmd << 'next-hop {0}'.format(path['next-hop'])
   126          else:
   127              cmd << 'next-hop self'
   128          return str(cmd)
   129  
   130      def _construct_flowspec(self, path):
   131          cmd = CmdBuffer(' ')
   132          cmd << '{ match {'
   133          for match in path['matchs']:
   134              cmd << '{0};'.format(match)
   135          cmd << '} then {'
   136          for then in path['thens']:
   137              cmd << '{0};'.format(then)
   138          cmd << '} }'
   139          return str(cmd)
   140  
   141      def _construct_path_attributes(self, path):
   142          cmd = CmdBuffer(' ')
   143          if path['as-path']:
   144              cmd << 'as-path [{0}]'.format(' '.join(str(i) for i in path['as-path']))
   145          if path['med']:
   146              cmd << 'med {0}'.format(path['med'])
   147          if path['local-pref']:
   148              cmd << 'local-preference {0}'.format(path['local-pref'])
   149          if path['community']:
   150              cmd << 'community [{0}]'.format(' '.join(c for c in path['community']))
   151          if path['extended-community']:
   152              cmd << 'extended-community [{0}]'.format(path['extended-community'])
   153          if path['attr']:
   154              cmd << 'attribute [ {0} ]'.format(path['attr'])
   155          return str(cmd)
   156  
   157      def _construct_path(self, path, rf='ipv4', is_withdraw=False):
   158          cmd = CmdBuffer(' ')
   159  
   160          if rf in ['ipv4', 'ipv6']:
   161              cmd << 'route'
   162              cmd << self._construct_ip_unicast(path)
   163          elif rf in ['ipv4-flowspec', 'ipv6-flowspec']:
   164              cmd << 'flow route'
   165              cmd << self._construct_flowspec(path)
   166          else:
   167              raise ValueError('unsupported address family: %s' % rf)
   168  
   169          if path['identifier']:
   170              cmd << 'path-information {0}'.format(path['identifier'])
   171  
   172          if not is_withdraw:
   173              # Withdrawal should not require path attributes
   174              cmd << self._construct_path_attributes(path)
   175  
   176          return str(cmd)
   177  
   178      def add_route(self, route, rf='ipv4', attribute=None, aspath=None,
   179                    community=None, med=None, extendedcommunity=None,
   180                    nexthop=None, matchs=None, thens=None,
   181                    local_pref=None, identifier=None, reload_config=False):
   182          if not self._is_running():
   183              raise RuntimeError('ExaBGP is not yet running')
   184  
   185          self.routes.setdefault(route, [])
   186          path = {
   187              'prefix': route,
   188              'rf': rf,
   189              'attr': attribute,
   190              'next-hop': nexthop,
   191              'as-path': aspath,
   192              'community': community,
   193              'med': med,
   194              'local-pref': local_pref,
   195              'extended-community': extendedcommunity,
   196              'identifier': identifier,
   197              'matchs': matchs,
   198              'thens': thens,
   199          }
   200  
   201          cmd = CmdBuffer(' ')
   202          cmd << "exabgpcli 'announce"
   203          cmd << self._construct_path(path, rf=rf)
   204          cmd << "'"
   205          self.local(str(cmd), capture=True)
   206  
   207          self.routes[route].append(path)
   208  
   209      def del_route(self, route, identifier=None, reload_config=False):
   210          if not self._is_running():
   211              raise RuntimeError('ExaBGP is not yet running')
   212  
   213          path = None
   214          new_paths = []
   215          for p in self.routes.get(route, []):
   216              if p['identifier'] != identifier:
   217                  new_paths.append(p)
   218              else:
   219                  path = p
   220          if not path:
   221              return
   222  
   223          rf = path['rf']
   224          cmd = CmdBuffer(' ')
   225          cmd << "exabgpcli 'withdraw"
   226          cmd << self._construct_path(path, rf=rf, is_withdraw=True)
   227          cmd << "'"
   228          self.local(str(cmd), capture=True)
   229  
   230          self.routes[route] = new_paths
   231  
   232      def _get_adj_rib(self, peer, rf, in_out='in'):
   233          # IPv4 Unicast:
   234          # neighbor 172.17.0.2 ipv4 unicast 192.168.100.0/24 path-information 0.0.0.20 next-hop self
   235          # IPv6 FlowSpec:
   236          # neighbor 172.17.0.2 ipv6 flow flow destination-ipv6 2002:1::/64/0 source-ipv6 2002:2::/64/0 next-header =udp flow-label >100
   237          rf_map = {
   238              'ipv4': ['ipv4', 'unicast'],
   239              'ipv6': ['ipv6', 'unicast'],
   240              'ipv4-flowspec': ['ipv4', 'flow'],
   241              'ipv6-flowspec': ['ipv6', 'flow'],
   242          }
   243          assert rf in rf_map
   244          assert in_out in ('in', 'out')
   245          peer_addr = self.peer_name(peer)
   246          lines = self.local('exabgpcli show adj-rib {0}'.format(in_out), capture=True).split('\n')
   247          # rib = {
   248          #     <nlri>: [
   249          #         {
   250          #             'nlri': <nlri>,
   251          #             'next-hop': <next-hop>,
   252          #             ...
   253          #         },
   254          #         ...
   255          #     ],
   256          # }
   257          rib = {}
   258          for line in lines:
   259              if not line:
   260                  continue
   261              values = line.split()
   262              if peer_addr != values[1]:
   263                  continue
   264              elif rf is not None and rf_map[rf] != values[2:4]:
   265                  continue
   266              if rf in ('ipv4', 'ipv6'):
   267                  nlri = values[4]
   268                  rib.setdefault(nlri, [])
   269                  path = {k: v for k, v in zip(*[iter(values[5:])] * 2)}
   270                  path['nlri'] = nlri
   271                  rib[nlri].append(path)
   272              elif rf in ('ipv4-flowspec', 'ipv6-flowspec'):
   273                  # XXX: Missing path attributes?
   274                  nlri = ' '.join(values[5:])
   275                  rib.setdefault(nlri, [])
   276                  path = {'nlri': nlri}
   277                  rib[nlri].append(path)
   278          return rib
   279  
   280      def get_adj_rib_in(self, peer, rf='ipv4'):
   281          return self._get_adj_rib(peer, rf, 'in')
   282  
   283      def get_adj_rib_out(self, peer, rf='ipv4'):
   284          return self._get_adj_rib(peer, rf, 'out')
   285  
   286  
   287  class RawExaBGPContainer(ExaBGPContainer):
   288      def __init__(self, name, config, ctn_image_name='osrg/exabgp',
   289                   exabgp_path=''):
   290          asn = None
   291          router_id = None
   292          for line in config.split('\n'):
   293              line = line.strip()
   294              if line.startswith('local-as'):
   295                  asn = int(line[len('local-as'):].strip('; '))
   296              if line.startswith('router-id'):
   297                  router_id = line[len('router-id'):].strip('; ')
   298          if not asn:
   299              raise Exception('asn not in exabgp config')
   300          if not router_id:
   301              raise Exception('router-id not in exabgp config')
   302          self.config = config
   303  
   304          super(RawExaBGPContainer, self).__init__(name, asn, router_id,
   305                                                   ctn_image_name, exabgp_path)
   306  
   307      def create_config(self):
   308          with open('{0}/exabgpd.conf'.format(self.config_dir), 'w') as f:
   309              print colors.yellow('[{0}\'s new exabgpd.conf]'.format(self.name))
   310              print colors.yellow(self.config)
   311              f.write(self.config)