diff options
Diffstat (limited to 'test/lib/gobgp.py')
-rw-r--r-- | test/lib/gobgp.py | 331 |
1 files changed, 331 insertions, 0 deletions
diff --git a/test/lib/gobgp.py b/test/lib/gobgp.py new file mode 100644 index 00000000..042d6696 --- /dev/null +++ b/test/lib/gobgp.py @@ -0,0 +1,331 @@ +# Copyright (C) 2015 Nippon Telegraph and Telephone Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from base import * +import json +import toml +from itertools import chain + +def extract_path_attribute(path, typ): + for a in path['attrs']: + if a['type'] == typ: + return a + return None + +class GoBGPContainer(BGPContainer): + + SHARED_VOLUME = '/root/shared_volume' + QUAGGA_VOLUME = '/etc/quagga' + + def __init__(self, name, asn, router_id, ctn_image_name='gobgp', + log_level='debug', zebra=False): + super(GoBGPContainer, self).__init__(name, asn, router_id, + ctn_image_name) + self.shared_volumes.append((self.config_dir, self.SHARED_VOLUME)) + + self.log_level = log_level + self.prefix_set = None + self.neighbor_set = None + self.bgp_set = None + self.default_policy = None + self.zebra = zebra + + def _start_gobgp(self): + zebra_op = '' + c = CmdBuffer() + c << '#!/bin/bash' + c << '/go/bin/gobgpd -f {0}/gobgpd.conf -l {1} -p {2} > ' \ + '{0}/gobgpd.log 2>&1'.format(self.SHARED_VOLUME, self.log_level, zebra_op) + + cmd = 'echo "{0:s}" > {1}/start.sh'.format(c, self.config_dir) + local(cmd, capture=True) + cmd = "chmod 755 {0}/start.sh".format(self.config_dir) + local(cmd, capture=True) + self.local("{0}/start.sh".format(self.SHARED_VOLUME), flag='-d') + + def _start_zebra(self): + cmd = 'cp {0}/zebra.conf {1}/'.format(self.SHARED_VOLUME, self.QUAGGA_VOLUME) + self.local(cmd) + cmd = '/usr/lib/quagga/zebra -f {0}/zebra.conf'.format(self.QUAGGA_VOLUME) + self.local(cmd, flag='-d') + + def run(self): + super(GoBGPContainer, self).run() + if self.zebra: + self._start_zebra() + self._start_gobgp() + return self.WAIT_FOR_BOOT + + def _get_as_path(self, path): + asps = (p['as_paths'] for p in path['attrs'] if + p['type'] == BGP_ATTR_TYPE_AS_PATH and 'as_paths' in p + and p['as_paths'] != None) + asps = chain.from_iterable(asps) + asns = (asp['asns'] for asp in asps) + return list(chain.from_iterable(asns)) + + def _get_nexthop(self, path): + for p in path['attrs']: + if p['type'] == BGP_ATTR_TYPE_NEXT_HOP or p['type'] == BGP_ATTR_TYPE_MP_REACH_NLRI: + return p['nexthop'] + + def _trigger_peer_cmd(self, cmd, peer): + if peer not in self.peers: + raise Exception('not found peer {0}'.format(peer.router_id)) + peer_addr = self.peers[peer]['neigh_addr'].split('/')[0] + cmd = 'gobgp neighbor {0} {1}'.format(peer_addr, cmd) + self.local(cmd) + + def disable_peer(self, peer): + self._trigger_peer_cmd('disable', peer) + + def enable_peer(self, peer): + self._trigger_peer_cmd('enable', peer) + + def reset(self, peer): + self._trigger_peer_cmd('reset', peer) + + def softreset(self, peer, rf='ipv4', type='in'): + self._trigger_peer_cmd('softreset{0} -a {1}'.format(type, rf), peer) + + def get_local_rib(self, peer, prefix='', rf='ipv4'): + if peer not in self.peers: + raise Exception('not found peer {0}'.format(peer.router_id)) + peer_addr = self.peers[peer]['neigh_addr'].split('/')[0] + cmd = 'gobgp -j neighbor {0} local {1} -a {2}'.format(peer_addr, prefix, rf) + output = self.local(cmd, capture=True) + ret = json.loads(output) + for d in ret: + for p in d["paths"]: + p["nexthop"] = self._get_nexthop(p) + p["aspath"] = self._get_as_path(p) + return ret + + def get_global_rib(self, prefix='', rf='ipv4'): + cmd = 'gobgp -j global rib {0} -a {1}'.format(prefix, rf) + output = self.local(cmd, capture=True) + ret = json.loads(output) + for d in ret: + for p in d["paths"]: + p["nexthop"] = self._get_nexthop(p) + p["aspath"] = self._get_as_path(p) + return ret + + def _get_adj_rib(self, adj_type, peer, prefix='', rf='ipv4'): + if peer not in self.peers: + raise Exception('not found peer {0}'.format(peer.router_id)) + peer_addr = self.peers[peer]['neigh_addr'].split('/')[0] + cmd = 'gobgp neighbor {0} adj-{1} {2} -a {3} -j'.format(peer_addr, + adj_type, + prefix, rf) + output = self.local(cmd, capture=True) + ret = [p["paths"][0] for p in json.loads(output)] + for p in ret: + p["nexthop"] = self._get_nexthop(p) + p["aspath"] = self._get_as_path(p) + return ret + + def get_adj_rib_in(self, peer, prefix='', rf='ipv4'): + return self._get_adj_rib('in', peer, prefix, rf) + + def get_adj_rib_out(self, peer, prefix='', rf='ipv4'): + return self._get_adj_rib('out', peer, prefix, rf) + + def get_neighbor_state(self, peer): + if peer not in self.peers: + raise Exception('not found peer {0}'.format(peer.router_id)) + peer_addr = self.peers[peer]['neigh_addr'].split('/')[0] + cmd = 'gobgp -j neighbor {0}'.format(peer_addr) + output = self.local(cmd, capture=True) + return json.loads(output)['info']['bgp_state'] + + def clear_policy(self): + self.policies = {} + for info in self.peers.itervalues(): + info['policies'] = {} + self.prefix_set = [] + self.neighbor_set = [] + self.statements = [] + + def set_prefix_set(self, ps): + self.prefix_set = ps + + def set_neighbor_set(self, ns): + self.neighbor_set = ns + + def set_bgp_defined_set(self, bs): + self.bgp_set = bs + + def create_config(self): + self._create_config_bgp() + if self.zebra: + self._create_config_zebra() + + def _create_config_bgp(self): + config = {'Global': {'GlobalConfig': {'As': self.asn, 'RouterId': self.router_id}}} + for peer, info in self.peers.iteritems(): + afi_safi_list = [] + version = netaddr.IPNetwork(info['neigh_addr']).version + if version == 4: + afi_safi_list.append({'AfiSafiName': 'ipv4-unicast'}) + elif version == 6: + afi_safi_list.append({'AfiSafiName': 'ipv6-unicast'}) + else: + Exception('invalid ip address version. {0}'.format(version)) + + if info['evpn']: + afi_safi_list.append({'AfiSafiName': 'l2vpn-evpn'}) + afi_safi_list.append({'AfiSafiName': 'encap'}) + afi_safi_list.append({'AfiSafiName': 'rtc'}) + + if info['flowspec']: + afi_safi_list.append({'AfiSafiName': 'ipv4-flowspec'}) + afi_safi_list.append({'AfiSafiName': 'l3vpn-ipv4-flowspec'}) + afi_safi_list.append({'AfiSafiName': 'ipv6-flowspec'}) + afi_safi_list.append({'AfiSafiName': 'l3vpn-ipv6-flowspec'}) + + n = {'NeighborConfig': + {'NeighborAddress': info['neigh_addr'].split('/')[0], + 'PeerAs': peer.asn, + 'AuthPassword': info['passwd'], + }, + 'AfiSafis': {'AfiSafiList': afi_safi_list} + } + + if info['passive']: + n['Transport'] = {'TransportConfig': {'PassiveMode': True}} + + if info['is_rs_client']: + n['RouteServer'] = {'RouteServerConfig': {'RouteServerClient': True}} + + if info['is_rr_client']: + clusterId = self.router_id + if 'cluster_id' in info and info['cluster_id'] is not None: + clusterId = info['cluster_id'] + n['RouteReflector'] = {'RouteReflectorConfig' : {'RouteReflectorClient': True, + 'RouteReflectorClusterId': clusterId}} + + f = lambda typ: [p for p in info['policies'].itervalues() if p['type'] == typ] + import_policies = f('import') + export_policies = f('export') + in_policies = f('in') + f = lambda typ: [p['default'] for p in info['policies'].itervalues() if p['type'] == typ and 'default' in p] + default_import_policy = f('import') + default_export_policy = f('export') + default_in_policy = f('in') + + if len(import_policies) + len(export_policies) + len(in_policies) + len(default_import_policy) \ + + len(default_export_policy) + len(default_in_policy) > 0: + n['ApplyPolicy'] = {'ApplyPolicyConfig': {}} + + if len(import_policies) > 0: + n['ApplyPolicy']['ApplyPolicyConfig']['ImportPolicy'] = [p['name'] for p in import_policies] + + if len(export_policies) > 0: + n['ApplyPolicy']['ApplyPolicyConfig']['ExportPolicy'] = [p['name'] for p in export_policies] + + if len(in_policies) > 0: + n['ApplyPolicy']['ApplyPolicyConfig']['InPolicy'] = [p['name'] for p in in_policies] + + def f(v): + if v == 'reject': + return 1 + elif v == 'accept': + return 0 + raise Exception('invalid default policy type {0}'.format(v)) + + if len(default_import_policy) > 0: + n['ApplyPolicy']['ApplyPolicyConfig']['DefaultImportPolicy'] = f(default_import_policy[0]) + + if len(default_export_policy) > 0: + n['ApplyPolicy']['ApplyPolicyConfig']['DefaultExportPolicy'] = f(default_export_policy[0]) + + if len(default_in_policy) > 0: + n['ApplyPolicy']['ApplyPolicyConfig']['DefaultInPolicy'] = f(default_in_policy[0]) + + if 'Neighbors' not in config: + config['Neighbors'] = {'NeighborList': []} + + config['Neighbors']['NeighborList'].append(n) + + config['DefinedSets'] = {} + if self.prefix_set: + config['DefinedSets']['PrefixSets'] = {'PrefixSetList': [self.prefix_set]} + + if self.neighbor_set: + config['DefinedSets']['NeighborSets'] = {'NeighborSetList': [self.neighbor_set]} + + if self.bgp_set: + config['DefinedSets']['BgpDefinedSets'] = self.bgp_set + + policy_list = [] + for p in self.policies.itervalues(): + policy = {'Name': p['name'], + 'Statements':{'StatementList': p['statements']}} + policy_list.append(policy) + + if len(policy_list) > 0: + config['PolicyDefinitions'] = {'PolicyDefinitionList': policy_list} + + if self.zebra: + config['Global']['Zebra'] = {'Enabled': True, + 'RedistributeRouteTypeList':[{'RouteType': 'connect'}],} + + with open('{0}/gobgpd.conf'.format(self.config_dir), 'w') as f: + print colors.yellow('[{0}\'s new config]'.format(self.name)) + print colors.yellow(indent(toml.dumps(config))) + f.write(toml.dumps(config)) + + def _create_config_zebra(self): + c = CmdBuffer() + c << 'hostname zebra' + c << 'password zebra' + c << 'log file {0}/zebra.log'.format(self.QUAGGA_VOLUME) + c << 'debug zebra packet' + c << 'debug zebra kernel' + c << 'debug zebra rib' + c << '' + + with open('{0}/zebra.conf'.format(self.config_dir), 'w') as f: + print colors.yellow('[{0}\'s new config]'.format(self.name)) + print colors.yellow(indent(str(c))) + f.writelines(str(c)) + + def reload_config(self): + daemon = [] + daemon.append('gobgpd') + if self.zebra: + daemon.append('zebra') + for d in daemon: + cmd = '/usr/bin/pkill {0} -SIGHUP'.format(d) + self.local(cmd) + for v in self.routes.itervalues(): + if v['rf'] == 'ipv4' or v['rf'] == 'ipv6': + r = CmdBuffer(' ') + r << 'gobgp global -a {0}'.format(v['rf']) + r << 'rib add {0}'.format(v['prefix']) + if v['next-hop']: + r << 'nexthop {0}'.format(v['next-hop']) + if v['local-pref']: + r << 'local-pref {0}'.format(v['local-pref']) + if v['med']: + r << 'med {0}'.format(v['med']) + cmd = str(r) + elif v['rf'] == 'ipv4-flowspec' or v['rf'] == 'ipv6-flowspec': + cmd = 'gobgp global '\ + 'rib add match {0} then {1} -a {2}'.format(' '.join(v['matchs']), ' '.join(v['thens']), v['rf']) + else: + raise Exception('unsupported route faily: {0}'.format(rf)) + self.local(cmd) |