github.com/osrg/gobgp/v3@v3.30.0/test/lib/gobgp.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  
    17  import collections
    18  import json
    19  from itertools import chain
    20  from threading import Thread
    21  import subprocess
    22  import os
    23  
    24  import netaddr
    25  import toml
    26  import yaml
    27  
    28  from lib.base import (
    29      community_str,
    30      wait_for_completion,
    31      BGPContainer,
    32      CmdBuffer,
    33      BGP_ATTR_TYPE_AS_PATH,
    34      BGP_ATTR_TYPE_NEXT_HOP,
    35      BGP_ATTR_TYPE_MULTI_EXIT_DISC,
    36      BGP_ATTR_TYPE_LOCAL_PREF,
    37      BGP_ATTR_TYPE_COMMUNITIES,
    38      BGP_ATTR_TYPE_MP_REACH_NLRI,
    39      GRACEFUL_RESTART_TIME,
    40      LONG_LIVED_GRACEFUL_RESTART_TIME,
    41      BGP_FSM_IDLE,
    42      BGP_FSM_ACTIVE,
    43      BGP_FSM_ESTABLISHED,
    44      yellow,
    45      indent,
    46      local,
    47  )
    48  
    49  
    50  def extract_path_attribute(path, typ):
    51      for a in path['attrs']:
    52          if a['type'] == typ:
    53              return a
    54      return None
    55  
    56  
    57  class GoBGPContainer(BGPContainer):
    58  
    59      SHARED_VOLUME = '/root/shared_volume'
    60      QUAGGA_VOLUME = '/etc/quagga'
    61  
    62      def __init__(self, name, asn, router_id, ctn_image_name='osrg/gobgp',
    63                   log_level='debug', zebra=False, config_format='toml',
    64                   zapi_version=2, bgp_config=None, ospfd_config=None,
    65                   zebra_multipath_enabled=False):
    66          super(GoBGPContainer, self).__init__(name, asn, router_id,
    67                                               ctn_image_name)
    68          self.shared_volumes.append((self.config_dir, self.SHARED_VOLUME))
    69          self.quagga_config_dir = '{0}/quagga'.format(self.config_dir)
    70          self.shared_volumes.append((self.quagga_config_dir, self.QUAGGA_VOLUME))
    71  
    72          self.log_level = log_level
    73          self.prefix_set = None
    74          self.neighbor_set = None
    75          self.bgp_set = None
    76          self.statements = None
    77          self.default_policy = None
    78          self.zebra = zebra
    79          self.zapi_version = zapi_version
    80          self.zebra_multipath_enabled = zebra_multipath_enabled
    81          self.config_format = config_format
    82  
    83          # bgp_config is equivalent to config.BgpConfigSet structure
    84          # Example:
    85          # bgp_config = {
    86          #     'global': {
    87          #         'confederation': {
    88          #             'config': {
    89          #                 'identifier': 10,
    90          #                 'member-as-list': [65001],
    91          #             }
    92          #         },
    93          #     },
    94          # }
    95          self.bgp_config = bgp_config or {}
    96  
    97          # To start OSPFd in GoBGP container, specify 'ospfd_config' as a dict
    98          # type value.
    99          # Example:
   100          # ospfd_config = {
   101          #     'redistributes': [
   102          #         'connected',
   103          #     ],
   104          #     'networks': {
   105          #         '192.168.1.0/24': '0.0.0.0',  # <network>: <area>
   106          #     },
   107          # }
   108          self.ospfd_config = ospfd_config or {}
   109  
   110      def _start_gobgp(self, graceful_restart=False):
   111          c = CmdBuffer()
   112          c << '#!/bin/sh'
   113          c << '/go/bin/gobgpd -f {0}/gobgpd.conf -l {1} -p {2} -t {3} > ' \
   114               '{0}/gobgpd.log 2>&1'.format(self.SHARED_VOLUME, self.log_level, '-r' if graceful_restart else '', self.config_format)
   115          cmd = 'echo "{0:s}" > {1}/start.sh'.format(str(c), self.config_dir)
   116          local(cmd, capture=True)
   117          cmd = "chmod 755 {0}/start.sh".format(self.config_dir)
   118          local(cmd, capture=True)
   119          self.local("{0}/start.sh".format(self.SHARED_VOLUME), detach=True)
   120  
   121      def start_gobgp(self, graceful_restart=False):
   122          if self._is_running():
   123              raise RuntimeError('GoBGP is already running')
   124          self._start_gobgp(graceful_restart=graceful_restart)
   125          self._wait_for_boot()
   126  
   127      def stop_gobgp(self):
   128          self.local("pkill -KILL gobgpd")
   129  
   130      def _start_zebra(self):
   131          if self.zapi_version == 2:
   132              daemon_bin = '/usr/lib/quagga/zebra'
   133          else:
   134              daemon_bin = 'zebra'
   135          cmd = '{0} -f {1}/zebra.conf'.format(daemon_bin, self.QUAGGA_VOLUME)
   136          self.local(cmd, detach=True)
   137  
   138      def _start_ospfd(self):
   139          if self.zapi_version == 2:
   140              daemon_bin = '/usr/lib/quagga/ospfd'
   141          else:
   142              daemon_bin = 'ospfd'
   143          cmd = '{0} -f {1}/ospfd.conf'.format(daemon_bin, self.QUAGGA_VOLUME)
   144          self.local(cmd, detach=True)
   145  
   146      def _get_enabled_quagga_daemons(self):
   147          daemons = []
   148          if self.zebra:
   149              daemons.append('zebra')
   150              if self.ospfd_config:
   151                  daemons.append('ospfd')
   152          return daemons
   153  
   154      def _is_running(self):
   155          return self.local('gobgp global'
   156                            ' > /dev/null 2>&1; echo $?', capture=True) == '0'
   157  
   158      def _wait_for_boot(self):
   159          for daemon in self._get_enabled_quagga_daemons():
   160              def _f_quagga():
   161                  ret = self.local("vtysh -d {0} -c 'show run' > /dev/null 2>&1; echo $?".format(daemon), capture=True)
   162                  return ret == '0'
   163  
   164              wait_for_completion(_f_quagga)
   165  
   166          wait_for_completion(self._is_running)
   167  
   168      def run(self):
   169          super(GoBGPContainer, self).run()
   170          if self.zebra:
   171              self._start_zebra()
   172              if self.ospfd_config:
   173                  self._start_ospfd()
   174          self._start_gobgp()
   175          self._wait_for_boot()
   176          return self.WAIT_FOR_BOOT
   177  
   178      @staticmethod
   179      def _get_as_path(path):
   180          asps = (p['as_paths'] for p in path['attrs']
   181                  if p['type'] == BGP_ATTR_TYPE_AS_PATH and 'as_paths' in p and p['as_paths'] is not None)
   182          asps = chain.from_iterable(asps)
   183          asns = (asp['asns'] for asp in asps)
   184          return list(chain.from_iterable(asns))
   185  
   186      @staticmethod
   187      def _get_nexthop(path):
   188          for p in path['attrs']:
   189              if p['type'] == BGP_ATTR_TYPE_NEXT_HOP or p['type'] == BGP_ATTR_TYPE_MP_REACH_NLRI:
   190                  return p['nexthop']
   191  
   192      @staticmethod
   193      def _get_local_pref(path):
   194          for p in path['attrs']:
   195              if p['type'] == BGP_ATTR_TYPE_LOCAL_PREF:
   196                  return p['value']
   197          return None
   198  
   199      @staticmethod
   200      def _get_med(path):
   201          for p in path['attrs']:
   202              if p['type'] == BGP_ATTR_TYPE_MULTI_EXIT_DISC:
   203                  return p['metric']
   204          return None
   205  
   206      @staticmethod
   207      def _get_community(path):
   208          for p in path['attrs']:
   209              if p['type'] == BGP_ATTR_TYPE_COMMUNITIES:
   210                  return [community_str(c) for c in p['communities']]
   211          return None
   212  
   213      def _get_rib(self, dests_dict):
   214          dests = []
   215          for k, v in list(dests_dict.items()):
   216              for p in v:
   217                  p["nexthop"] = self._get_nexthop(p)
   218                  p["aspath"] = self._get_as_path(p)
   219                  p["local-pref"] = self._get_local_pref(p)
   220                  p["community"] = self._get_community(p)
   221                  p["med"] = self._get_med(p)
   222                  p["prefix"] = k
   223                  path_id = p.get("id", None)
   224                  if path_id:
   225                      p["identifier"] = p["id"]
   226              dests.append({'paths': v, 'prefix': k})
   227          return dests
   228  
   229      def _trigger_peer_cmd(self, cmd, peer):
   230          peer_addr = self.peer_name(peer)
   231          cmd = 'gobgp neighbor {0} {1}'.format(peer_addr, cmd)
   232          self.local(cmd)
   233  
   234      def disable_peer(self, peer):
   235          self._trigger_peer_cmd('disable', peer)
   236  
   237      def enable_peer(self, peer):
   238          self._trigger_peer_cmd('enable', peer)
   239  
   240      def reset(self, peer):
   241          self._trigger_peer_cmd('reset', peer)
   242  
   243      def softreset(self, peer, rf='ipv4', type='in'):
   244          self._trigger_peer_cmd('softreset{0} -a {1}'.format(type, rf), peer)
   245  
   246      def get_local_rib(self, peer, prefix='', rf='ipv4'):
   247          peer_addr = self.peer_name(peer)
   248          cmd = 'gobgp -j neighbor {0} local {1} -a {2}'.format(peer_addr, prefix, rf)
   249          output = self.local(cmd, capture=True)
   250          return self._get_rib(json.loads(output))
   251  
   252      def get_global_rib(self, prefix='', rf='ipv4'):
   253          cmd = 'gobgp -j global rib {0} -a {1}'.format(prefix, rf)
   254          output = self.local(cmd, capture=True)
   255          return self._get_rib(json.loads(output))
   256  
   257      def monitor_global_rib(self, queue, rf='ipv4'):
   258          host = self.ip_addrs[0][1].split('/')[0]
   259  
   260          if not os.path.exists('{0}/gobgp'.format(self.config_dir)):
   261              self.local('cp /go/bin/gobgp {0}/'.format(self.SHARED_VOLUME))
   262  
   263          args = '{0}/gobgp -u {1} -j monitor global rib -a {2}'.format(self.config_dir, host, rf).split(' ')
   264  
   265          def monitor():
   266              process = subprocess.Popen(args, stdout=subprocess.PIPE)
   267              for line in iter(process.stdout.readline, ''):
   268                  p = json.loads(line)[0]
   269                  p["nexthop"] = self._get_nexthop(p)
   270                  p["aspath"] = self._get_as_path(p)
   271                  p["local-pref"] = self._get_local_pref(p)
   272                  p["med"] = self._get_med(p)
   273                  queue.put(p)
   274  
   275          t = Thread(target=monitor)
   276          t.daemon = True
   277          t.start()
   278  
   279      def _get_adj_rib(self, adj_type, peer, prefix='', rf='ipv4', add_path_enabled=False):
   280          peer_addr = self.peer_name(peer)
   281          cmd = 'gobgp neighbor {0} adj-{1} {2} -a {3} -j'.format(peer_addr,
   282                                                                  adj_type,
   283                                                                  prefix, rf)
   284          output = self.local(cmd, capture=True)
   285          if add_path_enabled:
   286              return self._get_rib(json.loads(output))
   287  
   288          ret = [p[0] for p in json.loads(output).values()]
   289          for p in ret:
   290              p["nexthop"] = self._get_nexthop(p)
   291              p["aspath"] = self._get_as_path(p)
   292              p["prefix"] = p['nlri']['prefix']
   293              p["local-pref"] = self._get_local_pref(p)
   294              p["med"] = self._get_med(p)
   295          return ret
   296  
   297      def get_adj_rib_in(self, peer, prefix='', rf='ipv4', add_path_enabled=False):
   298          return self._get_adj_rib('in', peer, prefix, rf, add_path_enabled)
   299  
   300      def get_adj_rib_out(self, peer, prefix='', rf='ipv4', add_path_enabled=False):
   301          return self._get_adj_rib('out', peer, prefix, rf, add_path_enabled)
   302  
   303      def get_neighbor(self, peer):
   304          cmd = 'gobgp -j neighbor {0}'.format(self.peer_name(peer))
   305          return json.loads(self.local(cmd, capture=True))
   306  
   307      def get_neighbor_state(self, peer):
   308          s = self.get_neighbor(peer)['state']['session_state']
   309          if s == 1:
   310              return BGP_FSM_IDLE
   311          elif s == 3:
   312              return BGP_FSM_ACTIVE
   313          elif s == 6:
   314              return BGP_FSM_ESTABLISHED
   315          return "unknown"
   316  
   317      def clear_policy(self):
   318          self.policies = {}
   319          for info in self.peers.values():
   320              info['policies'] = {}
   321          self.prefix_set = []
   322          self.neighbor_set = []
   323          self.statements = []
   324  
   325      def set_prefix_set(self, ps):
   326          if not isinstance(ps, list):
   327              ps = [ps]
   328          self.prefix_set = ps
   329  
   330      def add_prefix_set(self, ps):
   331          if self.prefix_set is None:
   332              self.prefix_set = []
   333          self.prefix_set.append(ps)
   334  
   335      def set_neighbor_set(self, ns):
   336          if not isinstance(ns, list):
   337              ns = [ns]
   338          self.neighbor_set = ns
   339  
   340      def add_neighbor_set(self, ns):
   341          if self.neighbor_set is None:
   342              self.neighbor_set = []
   343          self.neighbor_set.append(ns)
   344  
   345      def set_bgp_defined_set(self, bs):
   346          self.bgp_set = bs
   347  
   348      def create_config(self):
   349          self._create_config_bgp()
   350          if self.zebra:
   351              local('mkdir -p {0}'.format(self.quagga_config_dir))
   352              local('chmod 777 {0}'.format(self.quagga_config_dir))
   353              self._create_config_zebra()
   354              if self.ospfd_config:
   355                  self._create_config_ospfd()
   356  
   357      def _merge_dict(self, dct, merge_dct):
   358          for k, v in merge_dct.items():
   359              if (k in dct and isinstance(dct[k], dict)
   360                      and isinstance(merge_dct[k], collections.abc.Mapping)):
   361                  self._merge_dict(dct[k], merge_dct[k])
   362              else:
   363                  dct[k] = merge_dct[k]
   364  
   365      def _create_config_bgp(self):
   366          config = {
   367              'global': {
   368                  'config': {
   369                      'as': self.asn,
   370                      'router-id': self.router_id,
   371                  },
   372                  'route-selection-options': {
   373                      'config': {
   374                          'external-compare-router-id': True,
   375                      },
   376                  },
   377              },
   378              'neighbors': [],
   379          }
   380  
   381          self._merge_dict(config, self.bgp_config)
   382  
   383          if self.zebra and self.zapi_version == 2:
   384              config['global']['use-multiple-paths'] = {'config': {'enabled': True}}
   385          else:
   386              config['global']['use-multiple-paths'] = {'config': {'enabled': self.zebra_multipath_enabled}}
   387  
   388          for peer, info in self.peers.items():
   389              afi_safi_list = []
   390              if info['interface'] != '':
   391                  afi_safi_list.append({'config': {'afi-safi-name': 'ipv4-unicast'}})
   392                  afi_safi_list.append({'config': {'afi-safi-name': 'ipv6-unicast'}})
   393              else:
   394                  version = netaddr.IPNetwork(info['neigh_addr']).version
   395                  if version == 4:
   396                      afi_safi_list.append({'config': {'afi-safi-name': 'ipv4-unicast'}})
   397                  elif version == 6:
   398                      afi_safi_list.append({'config': {'afi-safi-name': 'ipv6-unicast'}})
   399                  else:
   400                      Exception('invalid ip address version. {0}'.format(version))
   401  
   402              if info['vpn']:
   403                  afi_safi_list.append({'config': {'afi-safi-name': 'l3vpn-ipv4-unicast'}})
   404                  afi_safi_list.append({'config': {'afi-safi-name': 'l3vpn-ipv6-unicast'}})
   405                  afi_safi_list.append({'config': {'afi-safi-name': 'l2vpn-evpn'}})
   406                  afi_safi_list.append({'config': {'afi-safi-name': 'rtc'}, 'route-target-membership': {'config': {'deferral-time': 10}}})
   407  
   408              if info['flowspec']:
   409                  afi_safi_list.append({'config': {'afi-safi-name': 'ipv4-flowspec'}})
   410                  afi_safi_list.append({'config': {'afi-safi-name': 'l3vpn-ipv4-flowspec'}})
   411                  afi_safi_list.append({'config': {'afi-safi-name': 'ipv6-flowspec'}})
   412                  afi_safi_list.append({'config': {'afi-safi-name': 'l3vpn-ipv6-flowspec'}})
   413  
   414              if info['mup']:
   415                  afi_safi_list.append({'config': {'afi-safi-name': 'ipv4-mup'}})
   416                  afi_safi_list.append({'config': {'afi-safi-name': 'ipv6-mup'}})
   417  
   418              neigh_addr = None
   419              interface = None
   420              peer_as = None
   421              if info['interface'] == '':
   422                  neigh_addr = info['neigh_addr'].split('/')[0]
   423                  peer_as = info['remote_as']
   424              else:
   425                  interface = info['interface']
   426              n = {
   427                  'config': {
   428                      'neighbor-address': neigh_addr,
   429                      'neighbor-interface': interface,
   430                      'peer-as': peer_as,
   431                      'auth-password': info['passwd'],
   432                      'vrf': info['vrf'],
   433                      'remove-private-as': info['remove_private_as'],
   434                  },
   435                  'afi-safis': afi_safi_list,
   436                  'timers': {
   437                      'config': {
   438                          'connect-retry': 10,
   439                      },
   440                  },
   441                  'transport': {
   442                      'config': {},
   443                  },
   444              }
   445  
   446              n['as-path-options'] = {'config': {}}
   447              if info['allow_as_in'] > 0:
   448                  n['as-path-options']['config']['allow-own-as'] = info['allow_as_in']
   449              if info['replace_peer_as']:
   450                  n['as-path-options']['config']['replace-peer-as'] = info['replace_peer_as']
   451  
   452              if ':' in info['local_addr']:
   453                  n['transport']['config']['local-address'] = info['local_addr'].split('/')[0]
   454  
   455              if info['passive']:
   456                  n['transport']['config']['passive-mode'] = True
   457  
   458              if info['is_rs_client']:
   459                  n['route-server'] = {'config': {'route-server-client': True}}
   460  
   461              if info['local_as']:
   462                  n['config']['local-as'] = info['local_as']
   463  
   464              if info['prefix_limit']:
   465                  for v in afi_safi_list:
   466                      v['prefix-limit'] = {'config': {'max-prefixes': info['prefix_limit'], 'shutdown-threshold-pct': 80}}
   467  
   468              if info['graceful_restart'] is not None:
   469                  n['graceful-restart'] = {'config': {'enabled': True, 'restart-time': GRACEFUL_RESTART_TIME}}
   470                  for afi_safi in afi_safi_list:
   471                      afi_safi['mp-graceful-restart'] = {'config': {'enabled': True}}
   472  
   473                  if info['llgr'] is not None:
   474                      n['graceful-restart']['config']['restart-time'] = 1
   475                      n['graceful-restart']['config']['long-lived-enabled'] = True
   476                      for afi_safi in afi_safi_list:
   477                          afi_safi['long-lived-graceful-restart'] = {'config': {'enabled': True, 'restart-time': LONG_LIVED_GRACEFUL_RESTART_TIME}}
   478  
   479              if info['is_rr_client']:
   480                  cluster_id = self.router_id
   481                  if 'cluster_id' in info and info['cluster_id'] is not None:
   482                      cluster_id = info['cluster_id']
   483                  n['route-reflector'] = {'config': {'route-reflector-client': True,
   484                                                     'route-reflector-cluster-id': cluster_id}}
   485  
   486              if info["addpath"] > 0:
   487                  n["add-paths"] = {
   488                      "config": {"receive": True, "send-max": info["addpath"]}
   489                  }
   490  
   491              if len(info.get('default-policy', [])) + len(info.get('policies', [])) > 0:
   492                  n['apply-policy'] = {'config': {}}
   493  
   494              for typ, p in info.get('policies', {}).items():
   495                  n['apply-policy']['config']['{0}-policy-list'.format(typ)] = [p['name']]
   496  
   497              def _f(v):
   498                  if v == 'reject':
   499                      return 'reject-route'
   500                  elif v == 'accept':
   501                      return 'accept-route'
   502                  raise Exception('invalid default policy type {0}'.format(v))
   503  
   504              for typ, d in info.get('default-policy', {}).items():
   505                  n['apply-policy']['config']['default-{0}-policy'.format(typ)] = _f(d)
   506  
   507              if info['treat_as_withdraw']:
   508                  n['error-handling'] = {'config': {'treat-as-withdraw': True}}
   509  
   510              config['neighbors'].append(n)
   511  
   512          config['defined-sets'] = {}
   513          if self.prefix_set:
   514              config['defined-sets']['prefix-sets'] = self.prefix_set
   515  
   516          if self.neighbor_set:
   517              config['defined-sets']['neighbor-sets'] = self.neighbor_set
   518  
   519          if self.bgp_set:
   520              config['defined-sets']['bgp-defined-sets'] = self.bgp_set
   521  
   522          policy_list = []
   523          for p in self.policies.values():
   524              policy = {'name': p['name']}
   525              if 'statements' in p:
   526                  policy['statements'] = p['statements']
   527              policy_list.append(policy)
   528  
   529          if len(policy_list) > 0:
   530              config['policy-definitions'] = policy_list
   531  
   532          if self.zebra:
   533              config['zebra'] = {'config': {'enabled': True,
   534                                            'redistribute-route-type-list': ['connect'],
   535                                            'version': self.zapi_version}}
   536  
   537          with open('{0}/gobgpd.conf'.format(self.config_dir), 'w') as f:
   538              print(yellow('[{0}\'s new gobgpd.conf]'.format(self.name)))
   539              if self.config_format == "toml":
   540                  raw = toml.dumps(config)
   541              elif self.config_format == "yaml":
   542                  raw = yaml.dump(config)
   543              elif self.config_format == "json":
   544                  raw = json.dumps(config)
   545              else:
   546                  raise Exception('invalid config_format {0}'.format(self.config_format))
   547              raw = raw.strip()
   548              print(yellow(indent(raw)))
   549              f.write(raw)
   550  
   551      def _create_config_zebra(self):
   552          c = CmdBuffer()
   553          c << 'hostname zebra'
   554          c << 'password zebra'
   555          c << 'log file {0}/zebra.log'.format(self.QUAGGA_VOLUME)
   556          c << 'debug zebra packet'
   557          c << 'debug zebra kernel'
   558          c << 'debug zebra rib'
   559          c << 'ipv6 forwarding'
   560          c << ''
   561  
   562          with open('{0}/zebra.conf'.format(self.quagga_config_dir), 'w') as f:
   563              print(yellow('[{0}\'s new zebra.conf]'.format(self.name)))
   564              c = str(c).strip()
   565              print(yellow(indent(c)))
   566              f.writelines(c)
   567  
   568      def _create_config_ospfd(self):
   569          c = CmdBuffer()
   570          c << 'hostname ospfd'
   571          c << 'password zebra'
   572          c << 'router ospf'
   573          for redistribute in self.ospfd_config.get('redistributes', []):
   574              c << ' redistribute {0}'.format(redistribute)
   575          for network, area in list(self.ospfd_config.get('networks', {}).items()):
   576              c << ' network {0} area {1}'.format(network, area)
   577          c << 'log file {0}/ospfd.log'.format(self.QUAGGA_VOLUME)
   578          c << ''
   579  
   580          with open('{0}/ospfd.conf'.format(self.quagga_config_dir), 'w') as f:
   581              print(yellow('[{0}\'s new ospfd.conf]'.format(self.name)))
   582              print(yellow(indent(str(c))))
   583              f.writelines(str(c))
   584  
   585      def reload_config(self):
   586          for daemon in self._get_enabled_quagga_daemons():
   587              self.local('pkill -SIGHUP {0}'.format(daemon), capture=True)
   588          self.local('pkill -SIGHUP gobgpd', capture=True)
   589          self._wait_for_boot()
   590  
   591      def add_route(self, route, rf='ipv4', attribute=None, aspath=None,
   592                    community=None, med=None, extendedcommunity=None,
   593                    nexthop=None, matchs=None, thens=None,
   594                    local_pref=None, identifier=None, reload_config=False):
   595          if not self._is_running():
   596              raise RuntimeError('GoBGP is not yet running')
   597  
   598          self.routes.setdefault(route, [])
   599          path = {
   600              'prefix': route,
   601              'rf': rf,
   602              'attr': attribute,
   603              'next-hop': nexthop,
   604              'as-path': aspath,
   605              'community': community,
   606              'med': med,
   607              'local-pref': local_pref,
   608              'extended-community': extendedcommunity,
   609              'identifier': identifier,
   610              'matchs': matchs,
   611              'thens': thens,
   612          }
   613  
   614          c = CmdBuffer(' ')
   615          c << 'gobgp global rib -a {0} add'.format(rf)
   616          if rf in ('ipv4', 'ipv6'):
   617              c << route
   618              if path['identifier']:
   619                  c << 'identifier {0}'.format(path['identifier'])
   620              if path['next-hop']:
   621                  c << 'nexthop {0}'.format(path['next-hop'])
   622              if path['local-pref']:
   623                  c << 'local-pref {0}'.format(path['local-pref'])
   624              if path['med']:
   625                  c << 'med {0}'.format(path['med'])
   626              if path['community']:
   627                  comm = str(path['community'])
   628                  if isinstance(path['community'], (list, tuple)):
   629                      comm = ','.join(path['community'])
   630                  c << 'community {0}'.format(comm)
   631          elif rf.endswith('-flowspec'):
   632              c << 'match {0}'.format(' '.join(path['matchs']))
   633              c << 'then {0}'.format(' '.join(path['thens']))
   634          else:
   635              raise Exception('unsupported address family: {0}'.format(rf))
   636          self.local(str(c), capture=True)
   637  
   638          self.routes[route].append(path)
   639  
   640      def del_route(self, route, identifier=None, reload_config=True):
   641          if not self._is_running():
   642              raise RuntimeError('GoBGP is not yet running')
   643  
   644          if route not in self.routes:
   645              return
   646  
   647          new_paths = []
   648          for path in self.routes[route]:
   649              if path['identifier'] != identifier:
   650                  new_paths.append(path)
   651              else:
   652                  r = CmdBuffer(' ')
   653                  r << 'gobgp global -a {0}'.format(path['rf'])
   654                  prefix = path['prefix']
   655                  if path['rf'].endswith('-flowspec'):
   656                      prefix = 'match {0}'.format(' '.join(path['matchs']))
   657                  r << 'rib del {0}'.format(prefix)
   658                  if identifier:
   659                      r << 'identifier {0}'.format(identifier)
   660                  cmd = str(r)
   661                  self.local(cmd, capture=True)
   662          self.routes[route] = new_paths
   663          # no need to reload config
   664  
   665  
   666  class RawGoBGPContainer(GoBGPContainer):
   667      def __init__(self, name, config, ctn_image_name='osrg/gobgp',
   668                   log_level='debug', zebra=False, config_format='yaml'):
   669          if config_format == "toml":
   670              d = toml.loads(config)
   671          elif config_format == "yaml":
   672              d = yaml.load(config)
   673          elif config_format == "json":
   674              d = json.loads(config)
   675          else:
   676              raise Exception('invalid config format {0}'.format(config_format))
   677          asn = d['global']['config']['as']
   678          router_id = d['global']['config']['router-id']
   679          self.config = config
   680          super(RawGoBGPContainer, self).__init__(name, asn, router_id,
   681                                                  ctn_image_name, log_level,
   682                                                  zebra, config_format)
   683  
   684      def create_config(self):
   685          with open('{0}/gobgpd.conf'.format(self.config_dir), 'w') as f:
   686              print(yellow('[{0}\'s new gobgpd.conf]'.format(self.name)))
   687              print(yellow(indent(self.config)))
   688              f.write(self.config)