github.com/osrg/gobgp@v2.0.0+incompatible/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  from __future__ import absolute_import
    17  
    18  import collections
    19  import json
    20  from itertools import chain
    21  from threading import Thread
    22  import subprocess
    23  import os
    24  
    25  from fabric import colors
    26  from fabric.api import local
    27  from fabric.utils import indent
    28  import netaddr
    29  import toml
    30  import yaml
    31  
    32  from lib.base import (
    33      community_str,
    34      wait_for_completion,
    35      BGPContainer,
    36      CmdBuffer,
    37      BGP_ATTR_TYPE_AS_PATH,
    38      BGP_ATTR_TYPE_NEXT_HOP,
    39      BGP_ATTR_TYPE_MULTI_EXIT_DISC,
    40      BGP_ATTR_TYPE_LOCAL_PREF,
    41      BGP_ATTR_TYPE_COMMUNITIES,
    42      BGP_ATTR_TYPE_MP_REACH_NLRI,
    43      GRACEFUL_RESTART_TIME,
    44      LONG_LIVED_GRACEFUL_RESTART_TIME,
    45      BGP_FSM_IDLE,
    46      BGP_FSM_ACTIVE,
    47      BGP_FSM_ESTABLISHED,
    48  )
    49  
    50  
    51  def extract_path_attribute(path, typ):
    52      for a in path['attrs']:
    53          if a['type'] == typ:
    54              return a
    55      return None
    56  
    57  
    58  class GoBGPContainer(BGPContainer):
    59  
    60      SHARED_VOLUME = '/root/shared_volume'
    61      QUAGGA_VOLUME = '/etc/quagga'
    62  
    63      def __init__(self, name, asn, router_id, ctn_image_name='osrg/gobgp',
    64                   log_level='debug', zebra=False, config_format='toml',
    65                   zapi_version=2, bgp_config=None, ospfd_config=None):
    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.config_format = config_format
    81  
    82          # bgp_config is equivalent to config.BgpConfigSet structure
    83          # Example:
    84          # bgp_config = {
    85          #     'global': {
    86          #         'confederation': {
    87          #             'config': {
    88          #                 'identifier': 10,
    89          #                 'member-as-list': [65001],
    90          #             }
    91          #         },
    92          #     },
    93          # }
    94          self.bgp_config = bgp_config or {}
    95  
    96          # To start OSPFd in GoBGP container, specify 'ospfd_config' as a dict
    97          # type value.
    98          # Example:
    99          # ospfd_config = {
   100          #     'redistributes': [
   101          #         'connected',
   102          #     ],
   103          #     'networks': {
   104          #         '192.168.1.0/24': '0.0.0.0',  # <network>: <area>
   105          #     },
   106          # }
   107          self.ospfd_config = ospfd_config or {}
   108  
   109      def _start_gobgp(self, graceful_restart=False):
   110          c = CmdBuffer()
   111          c << '#!/bin/sh'
   112          c << '/go/bin/gobgpd -f {0}/gobgpd.conf -l {1} -p {2} -t {3} > ' \
   113               '{0}/gobgpd.log 2>&1'.format(self.SHARED_VOLUME, self.log_level, '-r' if graceful_restart else '', self.config_format)
   114  
   115          cmd = 'echo "{0:s}" > {1}/start.sh'.format(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 -INT 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 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'):
   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          ret = [p[0] for p in json.loads(output).itervalues()]
   286          for p in ret:
   287              p["nexthop"] = self._get_nexthop(p)
   288              p["aspath"] = self._get_as_path(p)
   289              p["prefix"] = p['nlri']['prefix']
   290              p["local-pref"] = self._get_local_pref(p)
   291              p["med"] = self._get_med(p)
   292          return ret
   293  
   294      def get_adj_rib_in(self, peer, prefix='', rf='ipv4'):
   295          return self._get_adj_rib('in', peer, prefix, rf)
   296  
   297      def get_adj_rib_out(self, peer, prefix='', rf='ipv4'):
   298          return self._get_adj_rib('out', peer, prefix, rf)
   299  
   300      def get_neighbor(self, peer):
   301          cmd = 'gobgp -j neighbor {0}'.format(self.peer_name(peer))
   302          return json.loads(self.local(cmd, capture=True))
   303  
   304      def get_neighbor_state(self, peer):
   305          s = self.get_neighbor(peer)['state']['session_state']
   306          if s == 1:
   307              return BGP_FSM_IDLE
   308          elif s == 3:
   309              return BGP_FSM_ACTIVE
   310          elif s == 6:
   311              return BGP_FSM_ESTABLISHED
   312          return "unknown"
   313  
   314      def clear_policy(self):
   315          self.policies = {}
   316          for info in self.peers.itervalues():
   317              info['policies'] = {}
   318          self.prefix_set = []
   319          self.neighbor_set = []
   320          self.statements = []
   321  
   322      def set_prefix_set(self, ps):
   323          if not isinstance(ps, list):
   324              ps = [ps]
   325          self.prefix_set = ps
   326  
   327      def add_prefix_set(self, ps):
   328          if self.prefix_set is None:
   329              self.prefix_set = []
   330          self.prefix_set.append(ps)
   331  
   332      def set_neighbor_set(self, ns):
   333          if not isinstance(ns, list):
   334              ns = [ns]
   335          self.neighbor_set = ns
   336  
   337      def add_neighbor_set(self, ns):
   338          if self.neighbor_set is None:
   339              self.neighbor_set = []
   340          self.neighbor_set.append(ns)
   341  
   342      def set_bgp_defined_set(self, bs):
   343          self.bgp_set = bs
   344  
   345      def create_config(self):
   346          self._create_config_bgp()
   347          if self.zebra:
   348              local('mkdir -p {0}'.format(self.quagga_config_dir))
   349              local('chmod 777 {0}'.format(self.quagga_config_dir))
   350              self._create_config_zebra()
   351              if self.ospfd_config:
   352                  self._create_config_ospfd()
   353  
   354      def _merge_dict(self, dct, merge_dct):
   355          for k, v in merge_dct.iteritems():
   356              if (k in dct and isinstance(dct[k], dict)
   357                      and isinstance(merge_dct[k], collections.Mapping)):
   358                  self._merge_dict(dct[k], merge_dct[k])
   359              else:
   360                  dct[k] = merge_dct[k]
   361  
   362      def _create_config_bgp(self):
   363          config = {
   364              'global': {
   365                  'config': {
   366                      'as': self.asn,
   367                      'router-id': self.router_id,
   368                  },
   369                  'route-selection-options': {
   370                      'config': {
   371                          'external-compare-router-id': True,
   372                      },
   373                  },
   374              },
   375              'neighbors': [],
   376          }
   377  
   378          self._merge_dict(config, self.bgp_config)
   379  
   380          if self.zebra and self.zapi_version == 2:
   381              config['global']['use-multiple-paths'] = {'config': {'enabled': True}}
   382  
   383          for peer, info in self.peers.iteritems():
   384              afi_safi_list = []
   385              if info['interface'] != '':
   386                  afi_safi_list.append({'config': {'afi-safi-name': 'ipv4-unicast'}})
   387                  afi_safi_list.append({'config': {'afi-safi-name': 'ipv6-unicast'}})
   388              else:
   389                  version = netaddr.IPNetwork(info['neigh_addr']).version
   390                  if version == 4:
   391                      afi_safi_list.append({'config': {'afi-safi-name': 'ipv4-unicast'}})
   392                  elif version == 6:
   393                      afi_safi_list.append({'config': {'afi-safi-name': 'ipv6-unicast'}})
   394                  else:
   395                      Exception('invalid ip address version. {0}'.format(version))
   396  
   397              if info['vpn']:
   398                  afi_safi_list.append({'config': {'afi-safi-name': 'l3vpn-ipv4-unicast'}})
   399                  afi_safi_list.append({'config': {'afi-safi-name': 'l3vpn-ipv6-unicast'}})
   400                  afi_safi_list.append({'config': {'afi-safi-name': 'l2vpn-evpn'}})
   401                  afi_safi_list.append({'config': {'afi-safi-name': 'rtc'}, 'route-target-membership': {'config': {'deferral-time': 10}}})
   402  
   403              if info['flowspec']:
   404                  afi_safi_list.append({'config': {'afi-safi-name': 'ipv4-flowspec'}})
   405                  afi_safi_list.append({'config': {'afi-safi-name': 'l3vpn-ipv4-flowspec'}})
   406                  afi_safi_list.append({'config': {'afi-safi-name': 'ipv6-flowspec'}})
   407                  afi_safi_list.append({'config': {'afi-safi-name': 'l3vpn-ipv6-flowspec'}})
   408  
   409              neigh_addr = None
   410              interface = None
   411              peer_as = None
   412              if info['interface'] == '':
   413                  neigh_addr = info['neigh_addr'].split('/')[0]
   414                  peer_as = info['remote_as']
   415              else:
   416                  interface = info['interface']
   417              n = {
   418                  'config': {
   419                      'neighbor-address': neigh_addr,
   420                      'neighbor-interface': interface,
   421                      'peer-as': peer_as,
   422                      'auth-password': info['passwd'],
   423                      'vrf': info['vrf'],
   424                      'remove-private-as': info['remove_private_as'],
   425                  },
   426                  'afi-safis': afi_safi_list,
   427                  'timers': {
   428                      'config': {
   429                          'connect-retry': 10,
   430                      },
   431                  },
   432                  'transport': {
   433                      'config': {},
   434                  },
   435              }
   436  
   437              n['as-path-options'] = {'config': {}}
   438              if info['allow_as_in'] > 0:
   439                  n['as-path-options']['config']['allow-own-as'] = info['allow_as_in']
   440              if info['replace_peer_as']:
   441                  n['as-path-options']['config']['replace-peer-as'] = info['replace_peer_as']
   442  
   443              if ':' in info['local_addr']:
   444                  n['transport']['config']['local-address'] = info['local_addr'].split('/')[0]
   445  
   446              if info['passive']:
   447                  n['transport']['config']['passive-mode'] = True
   448  
   449              if info['is_rs_client']:
   450                  n['route-server'] = {'config': {'route-server-client': True}}
   451  
   452              if info['local_as']:
   453                  n['config']['local-as'] = info['local_as']
   454  
   455              if info['prefix_limit']:
   456                  for v in afi_safi_list:
   457                      v['prefix-limit'] = {'config': {'max-prefixes': info['prefix_limit'], 'shutdown-threshold-pct': 80}}
   458  
   459              if info['graceful_restart'] is not None:
   460                  n['graceful-restart'] = {'config': {'enabled': True, 'restart-time': GRACEFUL_RESTART_TIME}}
   461                  for afi_safi in afi_safi_list:
   462                      afi_safi['mp-graceful-restart'] = {'config': {'enabled': True}}
   463  
   464                  if info['llgr'] is not None:
   465                      n['graceful-restart']['config']['restart-time'] = 1
   466                      n['graceful-restart']['config']['long-lived-enabled'] = True
   467                      for afi_safi in afi_safi_list:
   468                          afi_safi['long-lived-graceful-restart'] = {'config': {'enabled': True, 'restart-time': LONG_LIVED_GRACEFUL_RESTART_TIME}}
   469  
   470              if info['is_rr_client']:
   471                  cluster_id = self.router_id
   472                  if 'cluster_id' in info and info['cluster_id'] is not None:
   473                      cluster_id = info['cluster_id']
   474                  n['route-reflector'] = {'config': {'route-reflector-client': True,
   475                                                     'route-reflector-cluster-id': cluster_id}}
   476  
   477              if info['addpath']:
   478                  n['add-paths'] = {'config': {'receive': True,
   479                                               'send-max': 16}}
   480  
   481              if len(info.get('default-policy', [])) + len(info.get('policies', [])) > 0:
   482                  n['apply-policy'] = {'config': {}}
   483  
   484              for typ, p in info.get('policies', {}).iteritems():
   485                  n['apply-policy']['config']['{0}-policy-list'.format(typ)] = [p['name']]
   486  
   487              def _f(v):
   488                  if v == 'reject':
   489                      return 'reject-route'
   490                  elif v == 'accept':
   491                      return 'accept-route'
   492                  raise Exception('invalid default policy type {0}'.format(v))
   493  
   494              for typ, d in info.get('default-policy', {}).iteritems():
   495                  n['apply-policy']['config']['default-{0}-policy'.format(typ)] = _f(d)
   496  
   497              if info['treat_as_withdraw']:
   498                  n['error-handling'] = {'config': {'treat-as-withdraw': True}}
   499  
   500              config['neighbors'].append(n)
   501  
   502          config['defined-sets'] = {}
   503          if self.prefix_set:
   504              config['defined-sets']['prefix-sets'] = self.prefix_set
   505  
   506          if self.neighbor_set:
   507              config['defined-sets']['neighbor-sets'] = self.neighbor_set
   508  
   509          if self.bgp_set:
   510              config['defined-sets']['bgp-defined-sets'] = self.bgp_set
   511  
   512          policy_list = []
   513          for p in self.policies.itervalues():
   514              policy = {'name': p['name']}
   515              if 'statements' in p:
   516                  policy['statements'] = p['statements']
   517              policy_list.append(policy)
   518  
   519          if len(policy_list) > 0:
   520              config['policy-definitions'] = policy_list
   521  
   522          if self.zebra:
   523              config['zebra'] = {'config': {'enabled': True,
   524                                            'redistribute-route-type-list': ['connect'],
   525                                            'version': self.zapi_version}}
   526  
   527          with open('{0}/gobgpd.conf'.format(self.config_dir), 'w') as f:
   528              print colors.yellow('[{0}\'s new gobgpd.conf]'.format(self.name))
   529              if self.config_format is 'toml':
   530                  raw = toml.dumps(config)
   531              elif self.config_format is 'yaml':
   532                  raw = yaml.dump(config)
   533              elif self.config_format is 'json':
   534                  raw = json.dumps(config)
   535              else:
   536                  raise Exception('invalid config_format {0}'.format(self.config_format))
   537              print colors.yellow(indent(raw))
   538              f.write(raw)
   539  
   540      def _create_config_zebra(self):
   541          c = CmdBuffer()
   542          c << 'hostname zebra'
   543          c << 'password zebra'
   544          c << 'log file {0}/zebra.log'.format(self.QUAGGA_VOLUME)
   545          c << 'debug zebra packet'
   546          c << 'debug zebra kernel'
   547          c << 'debug zebra rib'
   548          c << 'ipv6 forwarding'
   549          c << ''
   550  
   551          with open('{0}/zebra.conf'.format(self.quagga_config_dir), 'w') as f:
   552              print colors.yellow('[{0}\'s new zebra.conf]'.format(self.name))
   553              print colors.yellow(indent(str(c)))
   554              f.writelines(str(c))
   555  
   556      def _create_config_ospfd(self):
   557          c = CmdBuffer()
   558          c << 'hostname ospfd'
   559          c << 'password zebra'
   560          c << 'router ospf'
   561          for redistribute in self.ospfd_config.get('redistributes', []):
   562              c << ' redistribute {0}'.format(redistribute)
   563          for network, area in self.ospfd_config.get('networks', {}).items():
   564              c << ' network {0} area {1}'.format(network, area)
   565          c << 'log file {0}/ospfd.log'.format(self.QUAGGA_VOLUME)
   566          c << ''
   567  
   568          with open('{0}/ospfd.conf'.format(self.quagga_config_dir), 'w') as f:
   569              print colors.yellow('[{0}\'s new ospfd.conf]'.format(self.name))
   570              print colors.yellow(indent(str(c)))
   571              f.writelines(str(c))
   572  
   573      def reload_config(self):
   574          for daemon in self._get_enabled_quagga_daemons():
   575              self.local('pkill -SIGHUP {0}'.format(daemon), capture=True)
   576          self.local('pkill -SIGHUP gobgpd', capture=True)
   577          self._wait_for_boot()
   578  
   579      def add_route(self, route, rf='ipv4', attribute=None, aspath=None,
   580                    community=None, med=None, extendedcommunity=None,
   581                    nexthop=None, matchs=None, thens=None,
   582                    local_pref=None, identifier=None, reload_config=False):
   583          if not self._is_running():
   584              raise RuntimeError('GoBGP is not yet running')
   585  
   586          self.routes.setdefault(route, [])
   587          path = {
   588              'prefix': route,
   589              'rf': rf,
   590              'attr': attribute,
   591              'next-hop': nexthop,
   592              'as-path': aspath,
   593              'community': community,
   594              'med': med,
   595              'local-pref': local_pref,
   596              'extended-community': extendedcommunity,
   597              'identifier': identifier,
   598              'matchs': matchs,
   599              'thens': thens,
   600          }
   601  
   602          c = CmdBuffer(' ')
   603          c << 'gobgp global rib -a {0} add'.format(rf)
   604          if rf in ('ipv4', 'ipv6'):
   605              c << route
   606              if path['identifier']:
   607                  c << 'identifier {0}'.format(path['identifier'])
   608              if path['next-hop']:
   609                  c << 'nexthop {0}'.format(path['next-hop'])
   610              if path['local-pref']:
   611                  c << 'local-pref {0}'.format(path['local-pref'])
   612              if path['med']:
   613                  c << 'med {0}'.format(path['med'])
   614              if path['community']:
   615                  comm = str(path['community'])
   616                  if isinstance(path['community'], (list, tuple)):
   617                      comm = ','.join(path['community'])
   618                  c << 'community {0}'.format(comm)
   619          elif rf.endswith('-flowspec'):
   620              c << 'match {0}'.format(' '.join(path['matchs']))
   621              c << 'then {0}'.format(' '.join(path['thens']))
   622          else:
   623              raise Exception('unsupported address family: {0}'.format(rf))
   624          self.local(str(c), capture=True)
   625  
   626          self.routes[route].append(path)
   627  
   628      def del_route(self, route, identifier=None, reload_config=True):
   629          if not self._is_running():
   630              raise RuntimeError('GoBGP is not yet running')
   631  
   632          if route not in self.routes:
   633              return
   634  
   635          new_paths = []
   636          for path in self.routes[route]:
   637              if path['identifier'] != identifier:
   638                  new_paths.append(path)
   639              else:
   640                  r = CmdBuffer(' ')
   641                  r << 'gobgp global -a {0}'.format(path['rf'])
   642                  prefix = path['prefix']
   643                  if path['rf'].endswith('-flowspec'):
   644                      prefix = 'match {0}'.format(' '.join(path['matchs']))
   645                  r << 'rib del {0}'.format(prefix)
   646                  if identifier:
   647                      r << 'identifier {0}'.format(identifier)
   648                  cmd = str(r)
   649                  self.local(cmd, capture=True)
   650          self.routes[route] = new_paths
   651          # no need to reload config
   652  
   653  
   654  class RawGoBGPContainer(GoBGPContainer):
   655      def __init__(self, name, config, ctn_image_name='osrg/gobgp',
   656                   log_level='debug', zebra=False, config_format='yaml'):
   657          if config_format is 'toml':
   658              d = toml.loads(config)
   659          elif config_format is 'yaml':
   660              d = yaml.load(config)
   661          elif config_format is 'json':
   662              d = json.loads(config)
   663          else:
   664              raise Exception('invalid config format {0}'.format(config_format))
   665          asn = d['global']['config']['as']
   666          router_id = d['global']['config']['router-id']
   667          self.config = config
   668          super(RawGoBGPContainer, self).__init__(name, asn, router_id,
   669                                                  ctn_image_name, log_level,
   670                                                  zebra, config_format)
   671  
   672      def create_config(self):
   673          with open('{0}/gobgpd.conf'.format(self.config_dir), 'w') as f:
   674              print colors.yellow('[{0}\'s new gobgpd.conf]'.format(self.name))
   675              print colors.yellow(indent(self.config))
   676              f.write(self.config)