diff options
Diffstat (limited to 'test/lib/exabgp.py')
-rw-r--r-- | test/lib/exabgp.py | 271 |
1 files changed, 159 insertions, 112 deletions
diff --git a/test/lib/exabgp.py b/test/lib/exabgp.py index 3bb9d207..10406b6b 100644 --- a/test/lib/exabgp.py +++ b/test/lib/exabgp.py @@ -15,66 +15,59 @@ from __future__ import absolute_import -from itertools import chain -import time - from fabric import colors -from fabric.api import local from lib.base import ( BGPContainer, CmdBuffer, try_several_times, + wait_for_completion, ) class ExaBGPContainer(BGPContainer): - SHARED_VOLUME = '/root/shared_volume' + SHARED_VOLUME = '/shared_volume' + PID_FILE = '/var/run/exabgp.pid' - def __init__(self, name, asn, router_id, ctn_image_name='osrg/exabgp', - exabgp_path=''): - super(ExaBGPContainer, self).__init__(name, asn, router_id, - ctn_image_name) + def __init__(self, name, asn, router_id, ctn_image_name='osrg/exabgp:4.0.5'): + super(ExaBGPContainer, self).__init__(name, asn, router_id, ctn_image_name) self.shared_volumes.append((self.config_dir, self.SHARED_VOLUME)) - self.exabgp_path = exabgp_path + + def _pre_start_exabgp(self): + # Create named pipes for "exabgpcli" + named_pipes = '/run/exabgp.in /run/exabgp.out' + self.local('mkfifo {0}'.format(named_pipes), capture=True) + self.local('chmod 777 {0}'.format(named_pipes), capture=True) def _start_exabgp(self): cmd = CmdBuffer(' ') cmd << 'env exabgp.log.destination={0}/exabgpd.log'.format(self.SHARED_VOLUME) - cmd << "exabgp.tcp.bind='0.0.0.0' exabgp.tcp.port=179" - cmd << './exabgp/sbin/exabgp {0}/exabgpd.conf'.format(self.SHARED_VOLUME) + cmd << 'exabgp.daemon.user=root' + cmd << 'exabgp.daemon.pid={0}'.format(self.PID_FILE) + cmd << 'exabgp.tcp.bind="0.0.0.0" exabgp.tcp.port=179' + cmd << 'exabgp {0}/exabgpd.conf'.format(self.SHARED_VOLUME) self.local(str(cmd), detach=True) - def _update_exabgp(self): - if self.exabgp_path == '': - return - c = CmdBuffer() - c << '#!/bin/bash' - - remotepath = '/root/exabgp' - localpath = self.exabgp_path - local('cp -r {0} {1}'.format(localpath, self.config_dir)) - c << 'cp {0}/etc/exabgp/exabgp.env {1}'.format(remotepath, self.SHARED_VOLUME) - c << 'sed -i -e \'s/all = false/all = true/g\' {0}/exabgp.env'.format(self.SHARED_VOLUME) - c << 'cp -r {0}/exabgp {1}'.format(self.SHARED_VOLUME, - remotepath[:-1 * len('exabgp')]) - c << 'cp {0}/exabgp.env {1}/etc/exabgp/'.format(self.SHARED_VOLUME, remotepath) - cmd = 'echo "{0:s}" > {1}/update.sh'.format(c, self.config_dir) - local(cmd, capture=True) - cmd = 'chmod 755 {0}/update.sh'.format(self.config_dir) - local(cmd, capture=True) - cmd = '{0}/update.sh'.format(self.SHARED_VOLUME) - self.local(cmd) + def _wait_for_boot(self): + def _f(): + ret = self.local('exabgpcli version > /dev/null 2>&1; echo $?', capture=True) + return ret == '0' + + return wait_for_completion(_f) def run(self): super(ExaBGPContainer, self).run() - self._update_exabgp() - self._start_exabgp() + self._pre_start_exabgp() + # To start ExaBGP, it is required to configure neighbor settings, so + # here does not start ExaBGP yet. + # self._start_exabgp() return self.WAIT_FOR_BOOT def create_config(self): - cmd = CmdBuffer() + # Manpage of exabgp.conf(5): + # https://github.com/Exa-Networks/exabgp/blob/master/doc/man/exabgp.conf.5 + cmd = CmdBuffer('\n') for peer, info in self.peers.iteritems(): cmd << 'neighbor {0} {{'.format(info['neigh_addr'].split('/')[0]) cmd << ' router-id {0};'.format(self.router_id) @@ -82,78 +75,22 @@ class ExaBGPContainer(BGPContainer): cmd << ' local-as {0};'.format(self.asn) cmd << ' peer-as {0};'.format(peer.asn) + caps = [] if info['as2']: + caps.append(' asn4 disable;') + if info['addpath']: + caps.append(' add-path send/receive;') + if caps: cmd << ' capability {' - cmd << ' asn4 disable;' + for cap in caps: + cmd << cap cmd << ' }' if info['passwd']: - cmd << ' md5 "{0}";'.format(info['passwd']) + cmd << ' md5-password "{0}";'.format(info['passwd']) if info['passive']: cmd << ' passive;' - - if info['addpath']: - cmd << ' add-path send/receive;' - - routes = [r for r in chain.from_iterable(self.routes.itervalues()) if r['rf'] == 'ipv4' or r['rf'] == 'ipv6'] - - if len(routes) > 0: - cmd << ' static {' - for route in routes: - r = CmdBuffer(' ') - nexthop = info['local_addr'].split('/')[0] - if route['next-hop']: - nexthop = route['next-hop'] - r << ' route {0} next-hop {1}'.format(route['prefix'], nexthop) - if route['as-path']: - r << 'as-path [{0}]'.format(' '.join(str(i) for i in route['as-path'])) - if route['community']: - r << 'community [{0}]'.format(' '.join(c for c in route['community'])) - if route['med']: - r << 'med {0}'.format(route['med']) - if route['local-pref']: - r << 'local-preference {0}'.format(route['local-pref']) - if route['extended-community']: - r << 'extended-community [{0}]'.format(route['extended-community']) - if route['attr']: - r << 'attribute [ {0} ]'.format(route['attr']) - if route['identifier']: - r << 'path-information {0}'.format(route['identifier']) - - cmd << '{0};'.format(str(r)) - cmd << ' }' - - routes = self._extract_routes(['ipv4-flowspec', 'ipv6-flowspec']) - for key, routes in routes.items(): - cmd << ' flow {' - for route in routes: - cmd << ' route {0} {{'.format(key) - cmd << ' match {' - for match in route['matchs']: - cmd << ' {0};'.format(match) -# cmd << ' source {0};'.format(route['prefix']) -# cmd << ' destination 192.168.0.1/32;' -# cmd << ' destination-port =3128 >8080&<8088;' -# cmd << ' source-port >1024;' -# cmd << ' port =14 =15 >10&<156;' -# cmd << ' protocol udp;' # how to specify multiple ip protocols -# cmd << ' packet-length >1000&<2000;' -# cmd << ' tcp-flags !syn;' - cmd << ' }' - cmd << ' then {' - for then in route['thens']: - cmd << ' {0};'.format(then) -# cmd << ' accept;' -# cmd << ' discard;' -# cmd << ' rate-limit 9600;' -# cmd << ' redirect 1.2.3.4:100;' -# cmd << ' redirect 100:100;' -# cmd << ' mark 10;' -# cmd << ' action sample-terminal;' - cmd << ' }' - cmd << ' }' - cmd << ' }' cmd << '}' with open('{0}/exabgpd.conf'.format(self.config_dir), 'w') as f: @@ -161,27 +98,137 @@ class ExaBGPContainer(BGPContainer): print colors.yellow(str(cmd)) f.write(str(cmd)) + def _is_running(self): + ret = self.local("test -f {0}; echo $?".format(self.PID_FILE), capture=True) + return ret == '0' + def reload_config(self): - if len(self.peers) == 0: + if not self.peers: return def _reload(): - def _is_running(): - ps = self.local('ps', capture=True) - running = False - for line in ps.split('\n')[1:]: - if 'python' in line: - running = True - return running - if _is_running(): - self.local('/usr/bin/pkill python -SIGUSR1') + if self._is_running(): + self.local('/usr/bin/pkill --pidfile {0} && rm -f {0}'.format(self.PID_FILE), capture=True) else: self._start_exabgp() - time.sleep(1) - if not _is_running(): - raise RuntimeError() + self._wait_for_boot() + + if not self._is_running(): + raise RuntimeError('Could not start ExaBGP') + try_several_times(_reload) + def _construct_ip_unicast(self, path): + cmd = CmdBuffer(' ') + cmd << str(path['prefix']) + if path['next-hop']: + cmd << 'next-hop {0}'.format(path['next-hop']) + else: + cmd << 'next-hop self' + return str(cmd) + + def _construct_flowspec(self, path): + cmd = CmdBuffer(' ') + cmd << '{ match {' + for match in path['matchs']: + cmd << '{0};'.format(match) + cmd << '} then {' + for then in path['thens']: + cmd << '{0};'.format(then) + cmd << '} }' + return str(cmd) + + def _construct_path_attributes(self, path): + cmd = CmdBuffer(' ') + if path['as-path']: + cmd << 'as-path [{0}]'.format(' '.join(str(i) for i in path['as-path'])) + if path['med']: + cmd << 'med {0}'.format(path['med']) + if path['local-pref']: + cmd << 'local-preference {0}'.format(path['local-pref']) + if path['community']: + cmd << 'community [{0}]'.format(' '.join(c for c in path['community'])) + if path['extended-community']: + cmd << 'extended-community [{0}]'.format(path['extended-community']) + if path['attr']: + cmd << 'attribute [ {0} ]'.format(path['attr']) + return str(cmd) + + def _construct_path(self, path, rf='ipv4', is_withdraw=False): + cmd = CmdBuffer(' ') + + if rf in ['ipv4', 'ipv6']: + cmd << 'route' + cmd << self._construct_ip_unicast(path) + elif rf in ['ipv4-flowspec', 'ipv6-flowspec']: + cmd << 'flow route' + cmd << self._construct_flowspec(path) + else: + raise ValueError('unsupported address family: %s' % rf) + + if path['identifier']: + cmd << 'path-information {0}'.format(path['identifier']) + + if not is_withdraw: + # Withdrawal should not require path attributes + cmd << self._construct_path_attributes(path) + + return str(cmd) + + def add_route(self, route, rf='ipv4', attribute=None, aspath=None, + community=None, med=None, extendedcommunity=None, + nexthop=None, matchs=None, thens=None, + local_pref=None, identifier=None, reload_config=False): + if not self._is_running(): + raise RuntimeError('ExaBGP is not yet running') + + self.routes.setdefault(route, []) + path = { + 'prefix': route, + 'rf': rf, + 'attr': attribute, + 'next-hop': nexthop, + 'as-path': aspath, + 'community': community, + 'med': med, + 'local-pref': local_pref, + 'extended-community': extendedcommunity, + 'identifier': identifier, + 'matchs': matchs, + 'thens': thens, + } + + cmd = CmdBuffer(' ') + cmd << "exabgpcli 'announce" + cmd << self._construct_path(path, rf=rf) + cmd << "'" + self.local(str(cmd), capture=True) + + self.routes[route].append(path) + + def del_route(self, route, identifier=None, reload_config=False): + if not self._is_running(): + raise RuntimeError('ExaBGP is not yet running') + + path = None + new_paths = [] + for p in self.routes.get(route, []): + if p['identifier'] != identifier: + new_paths.append(p) + else: + path = p + if not path: + return + + rf = path['rf'] + cmd = CmdBuffer(' ') + cmd << "exabgpcli 'withdraw" + cmd << self._construct_path(path, rf=rf, is_withdraw=True) + cmd << "'" + self.local(str(cmd), capture=True) + + self.routes[route] = new_paths + class RawExaBGPContainer(ExaBGPContainer): def __init__(self, name, config, ctn_image_name='osrg/exabgp', |