1 # Copyright (C) 2015 Nippon Telegraph and Telephone Corporation.
3 # This is based on the following
4 # https://github.com/osrg/gobgp/test/lib/quagga.py
6 # Licensed under the Apache License, Version 2.0 (the "License");
7 # you may not use this file except in compliance with the License.
8 # You may obtain a copy of the License at
10 # http://www.apache.org/licenses/LICENSE-2.0
12 # Unless required by applicable law or agreed to in writing, software
13 # distributed under the License is distributed on an "AS IS" BASIS,
14 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
16 # See the License for the specific language governing permissions and
17 # limitations under the License.
19 from __future__ import absolute_import
26 from . import docker_base as base
28 LOG = logging.getLogger(__name__)
31 class QuaggaBGPContainer(base.BGPContainer):
34 SHARED_VOLUME = '/etc/quagga'
36 def __init__(self, name, asn, router_id, ctn_image_name, zebra=False):
37 super(QuaggaBGPContainer, self).__init__(name, asn, router_id,
39 self.shared_volumes.append((self.config_dir, self.SHARED_VOLUME))
41 self._create_config_debian()
43 def run(self, wait=False, w_time=WAIT_FOR_BOOT):
44 w_time = super(QuaggaBGPContainer,
45 self).run(wait=wait, w_time=self.WAIT_FOR_BOOT)
48 def get_global_rib(self, prefix='', rf='ipv4'):
51 return self.get_global_rib_with_prefix(prefix, rf)
53 out = self.vtysh('show bgp {0} unicast'.format(rf), config=False)
54 if out.startswith('No BGP network exists'):
59 for line in out.split('\n'):
82 rib.append({'prefix': prefix, 'nexthop': nexthop,
87 def get_global_rib_with_prefix(self, prefix, rf):
90 lines = [line.strip() for line in self.vtysh(
91 'show bgp {0} unicast {1}'.format(rf, prefix),
92 config=False).split('\n')]
94 if lines[0] == '% Network not in table':
99 if lines[0].startswith('Not advertised'):
100 lines.pop(0) # another useless line
101 elif lines[0].startswith('Advertised to non peer-group peers:'):
102 lines = lines[2:] # other useless lines
104 raise Exception('unknown output format {0}'.format(lines))
106 if lines[0] == 'Local':
109 aspath = [int(asn) for asn in lines[0].split()]
111 nexthop = lines[1].split()[0].strip()
112 info = [s.strip(',') for s in lines[2].split()]
115 med = info[info.index('metric') + 1]
116 attrs.append({'type': base.BGP_ATTR_TYPE_MULTI_EXIT_DISC,
118 if 'localpref' in info:
119 localpref = info[info.index('localpref') + 1]
120 attrs.append({'type': base.BGP_ATTR_TYPE_LOCAL_PREF,
121 'value': int(localpref)})
123 rib.append({'prefix': prefix, 'nexthop': nexthop,
124 'aspath': aspath, 'attrs': attrs})
128 def get_neighbor_state(self, peer):
129 if peer not in self.peers:
130 raise Exception('not found peer {0}'.format(peer.router_id))
132 neigh_addr = self.peers[peer]['neigh_addr'].split('/')[0]
134 info = [l.strip() for l in self.vtysh(
135 'show bgp neighbors {0}'.format(neigh_addr),
136 config=False).split('\n')]
138 if not info[0].startswith('BGP neighbor is'):
139 raise Exception('unknown format')
141 idx1 = info[0].index('BGP neighbor is ')
142 idx2 = info[0].index(',')
143 n_addr = info[0][idx1 + len('BGP neighbor is '):idx2]
144 if n_addr == neigh_addr:
145 idx1 = info[2].index('= ')
146 state = info[2][idx1 + len('= '):]
147 if state.startswith('Idle'):
148 return base.BGP_FSM_IDLE
149 elif state.startswith('Active'):
150 return base.BGP_FSM_ACTIVE
151 elif state.startswith('Established'):
152 return base.BGP_FSM_ESTABLISHED
156 raise Exception('not found peer {0}'.format(peer.router_id))
158 def send_route_refresh(self):
159 self.vtysh('clear ip bgp * soft', config=False)
161 def create_config(self):
163 self._create_config_bgp()
166 self._create_config_zebra()
167 self._create_config_daemons(zebra)
169 def _create_config_debian(self):
171 c << 'vtysh_enable=yes'
172 c << 'zebra_options=" --daemon -A 127.0.0.1"'
173 c << 'bgpd_options=" --daemon -A 127.0.0.1"'
174 c << 'ospfd_options=" --daemon -A 127.0.0.1"'
175 c << 'ospf6d_options=" --daemon -A ::1"'
176 c << 'ripd_options=" --daemon -A 127.0.0.1"'
177 c << 'ripngd_options=" --daemon -A ::1"'
178 c << 'isisd_options=" --daemon -A 127.0.0.1"'
179 c << 'babeld_options=" --daemon -A 127.0.0.1"'
180 c << 'watchquagga_enable=yes'
181 c << 'watchquagga_options=(--daemon)'
182 with open('{0}/debian.conf'.format(self.config_dir), 'w') as f:
183 LOG.info("[%s's new config]", self.name)
187 def _create_config_daemons(self, zebra='no'):
189 c << 'zebra=%s' % zebra
197 with open('{0}/daemons'.format(self.config_dir), 'w') as f:
198 LOG.info("[%s's new config]", self.name)
202 def _create_config_bgp(self):
206 c << 'password zebra'
207 c << 'router bgp {0}'.format(self.asn)
208 c << 'bgp router-id {0}'.format(self.router_id)
209 if any(info['graceful_restart'] for info in self.peers.values()):
210 c << 'bgp graceful-restart'
213 for peer, info in self.peers.items():
214 version = netaddr.IPNetwork(info['neigh_addr']).version
215 n_addr = info['neigh_addr'].split('/')[0]
217 c << 'no bgp default ipv4-unicast'
219 c << 'neighbor {0} remote-as {1}'.format(n_addr, peer.asn)
220 if info['is_rs_client']:
221 c << 'neighbor {0} route-server-client'.format(n_addr)
222 for typ, p in info['policies'].items():
223 c << 'neighbor {0} route-map {1} {2}'.format(n_addr, p['name'],
226 c << 'neighbor {0} password {1}'.format(n_addr, info['passwd'])
228 c << 'neighbor {0} passive'.format(n_addr)
230 c << 'address-family ipv6 unicast'
231 c << 'neighbor {0} activate'.format(n_addr)
232 c << 'exit-address-family'
234 for route in self.routes.values():
235 if route['rf'] == 'ipv4':
236 c << 'network {0}'.format(route['prefix'])
237 elif route['rf'] == 'ipv6':
238 c << 'address-family ipv6 unicast'
239 c << 'network {0}'.format(route['prefix'])
240 c << 'exit-address-family'
243 'unsupported route faily: {0}'.format(route['rf']))
247 c << 'address-family ipv6 unicast'
248 c << 'redistribute connected'
249 c << 'exit-address-family'
251 c << 'redistribute connected'
253 for name, policy in self.policies.items():
254 c << 'access-list {0} {1} {2}'.format(name, policy['type'],
256 c << 'route-map {0} permit 10'.format(name)
257 c << 'match ip address {0}'.format(name)
258 c << 'set metric {0}'.format(policy['med'])
262 c << 'debug bgp updates'
263 c << 'debug bgp events'
264 c << 'log file {0}/bgpd.log'.format(self.SHARED_VOLUME)
266 with open('{0}/bgpd.conf'.format(self.config_dir), 'w') as f:
267 LOG.info("[%s's new config]", self.name)
271 def _create_config_zebra(self):
273 c << 'hostname zebra'
274 c << 'password zebra'
275 c << 'log file {0}/zebra.log'.format(self.SHARED_VOLUME)
276 c << 'debug zebra packet'
277 c << 'debug zebra kernel'
278 c << 'debug zebra rib'
281 with open('{0}/zebra.conf'.format(self.config_dir), 'w') as f:
282 LOG.info("[%s's new config]", self.name)
286 def vtysh(self, cmd, config=True):
287 if not isinstance(cmd, list):
289 cmd = ' '.join("-c '{0}'".format(c) for c in cmd)
291 return self.exec_on_ctn(
292 "vtysh -d bgpd -c 'en' -c 'conf t' -c "
293 "'router bgp {0}' {1}".format(self.asn, cmd),
296 return self.exec_on_ctn("vtysh -d bgpd {0}".format(cmd),
299 def reload_config(self):
301 daemon.append('bgpd')
303 daemon.append('zebra')
305 cmd = '/usr/bin/pkill {0} -SIGHUP'.format(d)
306 self.exec_on_ctn(cmd, capture=True)
309 class RawQuaggaBGPContainer(QuaggaBGPContainer):
310 def __init__(self, name, config, ctn_image_name,
314 for line in config.split('\n'):
316 if line.startswith('router bgp'):
317 asn = int(line[len('router bgp'):].strip())
318 if line.startswith('bgp router-id'):
319 router_id = line[len('bgp router-id'):].strip()
321 raise Exception('asn not in quagga config')
323 raise Exception('router-id not in quagga config')
325 super(RawQuaggaBGPContainer, self).__init__(name, asn, router_id,
326 ctn_image_name, zebra)
328 def create_config(self):
329 with open(os.path.join(self.config_dir, 'bgpd.conf'), 'w') as f:
330 LOG.info("[%s's new config]", self.name)
331 LOG.info(self.config)
332 f.writelines(self.config)