Completed
Push — master ( 0dc7c6...640170 )
by Thomas
15:16
created

exabgp.configuration.neighbor   D

Complexity

Total Complexity 58

Size/Duplication

Total Lines 328
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 253
dl 0
loc 328
rs 4.5599
c 0
b 0
f 0
wmc 58

4 Methods

Rating   Name   Duplication   Size   Complexity  
A ParseNeighbor.__init__() 0 4 1
A ParseNeighbor.clear() 0 3 1
A ParseNeighbor.pre() 0 2 1
F ParseNeighbor.post() 0 192 55

How to fix   Complexity   

Complexity

Complex classes like exabgp.configuration.neighbor often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
# encoding: utf-8
2
"""
3
neighbor/__init__.py
4
5
Created by Thomas Mangin on 2015-06-04.
6
Copyright (c) 2009-2017 Exa Networks. All rights reserved.
7
License: 3-clause BSD. (See the COPYRIGHT file)
8
"""
9
10
# import sys
11
import base64
12
from copy import deepcopy
13
14
from exabgp.util.dns import host,domain
15
16
from exabgp.protocol.family import AFI
17
from exabgp.protocol.family import SAFI
18
19
from exabgp.bgp.neighbor import Neighbor
20
21
from exabgp.bgp.message import OUT
22
# from exabgp.bgp.message.open.asn import ASN
23
from exabgp.bgp.message.open.holdtime import HoldTime
24
25
from exabgp.bgp.message.update.nlri.flow import NLRI
26
27
from exabgp.configuration.core import Section
28
from exabgp.configuration.neighbor.api import ParseAPI
29
from exabgp.configuration.neighbor.family import ParseFamily
30
from exabgp.configuration.neighbor.nexthop import ParseNextHop
31
from exabgp.configuration.neighbor.family import ParseAddPath
32
33
from exabgp.configuration.parser import boolean
34
from exabgp.configuration.parser import auto_boolean
35
from exabgp.configuration.parser import ip
36
from exabgp.configuration.parser import peer_ip
37
# from exabgp.configuration.parser import asn
38
from exabgp.configuration.parser import auto_asn
39
from exabgp.configuration.parser import port
40
from exabgp.configuration.neighbor.parser import ttl
41
from exabgp.configuration.neighbor.parser import md5
42
from exabgp.configuration.neighbor.parser import hold_time
43
from exabgp.configuration.neighbor.parser import router_id
44
from exabgp.configuration.neighbor.parser import local_address
45
from exabgp.configuration.neighbor.parser import hostname
46
from exabgp.configuration.neighbor.parser import domainname
47
from exabgp.configuration.neighbor.parser import description
48
from exabgp.configuration.neighbor.parser import inherit
49
from exabgp.configuration.neighbor.parser import rate_limit
50
51
52
class ParseNeighbor (Section):
53
	TTL_SECURITY = 255
54
55
	syntax = ''
56
57
	known = {
58
		'inherit':       inherit,
59
		'description':   description,
60
		'host-name':     hostname,
61
		'domain-name':   domainname,
62
		'router-id':     router_id,
63
		'hold-time':     hold_time,
64
		'rate-limit':    rate_limit,
65
		'local-address': local_address,
66
		'peer-address':  peer_ip,
67
		'local-as':      auto_asn,
68
		'peer-as':       auto_asn,
69
		'passive':       boolean,
70
		'listen':        port,
71
		'connect':       port,
72
		'outgoing-ttl':  ttl,
73
		'incoming-ttl':  ttl,
74
		'md5-password':  md5,
75
		'md5-base64':    auto_boolean,
76
		'md5-ip':        ip,
77
		'group-updates': boolean,
78
		'auto-flush':    boolean,
79
		'adj-rib-out':   boolean,
80
		'adj-rib-in':    boolean,
81
		'manual-eor':    boolean,
82
	}
83
84
	action = {
85
		'inherit':       'extend-command',
86
		'description':   'set-command',
87
		'host-name':     'set-command',
88
		'domain-name':   'set-command',
89
		'router-id':     'set-command',
90
		'hold-time':     'set-command',
91
		'rate-limit':    'set-command',
92
		'local-address': 'set-command',
93
		'peer-address':  'set-command',
94
		'local-as':      'set-command',
95
		'peer-as':       'set-command',
96
		'passive':       'set-command',
97
		'listen':        'set-command',
98
		'connect':       'set-command',
99
		'outgoing-ttl':  'set-command',
100
		'incoming-ttl':  'set-command',
101
		'md5-password':  'set-command',
102
		'md5-base64':    'set-command',
103
		'md5-ip':        'set-command',
104
		'group-updates': 'set-command',
105
		'auto-flush':    'set-command',
106
		'adj-rib-out':   'set-command',
107
		'adj-rib-in':    'set-command',
108
		'manual-eor':    'set-command',
109
		'route':         'append-name',
110
	}
111
112
	default = {
113
		'md5-base64': False,
114
		'passive': True,
115
		'group-updates': True,
116
		'auto-flush': True,
117
		'adj-rib-out': False,
118
		'adj-rib-in': False,
119
		'manual-eor': False,
120
	}
121
122
	name = 'neighbor'
123
124
	def __init__ (self, tokeniser, scope, error, logger):
125
		Section.__init__(self,tokeniser,scope,error,logger)
126
		self._neighbors = []
127
		self.neighbors = {}
128
129
	def clear (self):
130
		self._neighbors = []
131
		self.neighbors = {}
132
133
	def pre (self):
134
		return self.parse(self.name,'peer-address')
135
136
	def post (self):
137
		for inherit in self.scope.pop('inherit',[]):
138
			data = self.scope.template('neighbor',inherit)
139
			self.scope.inherit(data)
140
		local = self.scope.get()
141
142
		neighbor = Neighbor()
143
144
		# XXX: use the right class for the data type
145
		# XXX: we can use the scope.nlri interface ( and rename it ) to set some values
146
		neighbor.router_id        = local.get('router-id',None)
147
		neighbor.peer_address     = local.get('peer-address',None)
148
		neighbor.local_address    = local.get('local-address',None)
149
		neighbor.local_as         = local.get('local-as',None)
150
		neighbor.peer_as          = local.get('peer-as',None)
151
		neighbor.passive          = local.get('passive',None)
152
		neighbor.listen           = local.get('listen',0)
153
		neighbor.connect          = local.get('connect',0)
154
		neighbor.hold_time        = local.get('hold-time',HoldTime(180))
155
		neighbor.rate_limit       = local.get('rate-limit',0)
156
		neighbor.host_name        = local.get('host-name',host())
157
		neighbor.domain_name      = local.get('domain-name',domain())
158
		neighbor.md5_password     = local.get('md5-password',None)
159
		neighbor.md5_base64       = local.get('md5-base64', None)
160
		neighbor.md5_ip           = local.get('md5-ip',neighbor.local_address)
161
		neighbor.description      = local.get('description','')
162
		neighbor.flush            = local.get('auto-flush',True)
163
		neighbor.adj_rib_out      = local.get('adj-rib-out',True)
164
		neighbor.adj_rib_in       = local.get('adj-rib-in',True)
165
		neighbor.aigp             = local.get('aigp',None)
166
		neighbor.ttl_out          = local.get('outgoing-ttl',None)
167
		neighbor.ttl_in           = local.get('incoming-ttl',None)
168
		neighbor.group_updates    = local.get('group-updates',True)
169
		neighbor.manual_eor       = local.get('manual-eor', False)
170
171
		if neighbor.local_address is None:
172
			return self.error.set('incomplete neighbor, missing local-address')
173
		if neighbor.local_as is None:
174
			return self.error.set('incomplete neighbor, missing local-as')
175
		if neighbor.peer_as is None:
176
			return self.error.set('incomplete neighbor, missing peer-as')
177
178
		if neighbor.passive is None:
179
			neighbor.passive = False
180
181
		capability = local.get('capability',{})
182
		neighbor.nexthop          = capability.get('nexthop',None)
183
		neighbor.add_path         = capability.get('add-path',0)
184
		neighbor.asn4             = capability.get('asn4',True)
185
		neighbor.extended_message = capability.get('extended-message',True)
186
		neighbor.multisession     = capability.get('multi-session',False)
187
		neighbor.operational      = capability.get('operational',False)
188
		neighbor.route_refresh    = capability.get('route-refresh',0)
189
190
		if capability.get('graceful-restart',False) is not False:
191
			neighbor.graceful_restart = capability.get('graceful-restart',0) or int(neighbor.hold_time)
192
193
		neighbor.api              = ParseAPI.flatten(local.pop('api',{}))
194
195
		families = []
196
		for family in ParseFamily.convert:
197
			for pair in local.get('family',{}).get(family,[]):
198
				families.append(pair)
199
200
		families = families or NLRI.known_families()
201
202
		for family in families:
203
			neighbor.add_family(family)
204
205
		if neighbor.add_path:
206
			add_path = local.get('add-path',{})
207
			if add_path:
208
				for family in ParseAddPath.convert:
209
					for pair in add_path.get(family,[]):
210
						if pair not in families:
211
							self.logger.debug('skipping add-path family ' + str(pair) + ' as it is not negotiated','configuration')
212
							continue
213
						neighbor.add_addpath(pair)
214
			else:
215
				for family in families:
216
					neighbor.add_addpath(family)
217
218
		# The default is to auto-detect by the presence of the nexthop block
219
		# if this is manually set, then we honor it
220
		nexthop = local.get('nexthop', {})
221
		if neighbor.nexthop is None and nexthop:
222
			neighbor.nexthop = True
223
224
		if neighbor.nexthop:
225
			nexthops = []
226
			for family in nexthop:
227
				nexthops.extend(nexthop[family])
228
			if nexthops:
229
				for afi,safi,nhafi in nexthops:
230
					if (afi,safi) not in neighbor.families():
231
						self.logger.debug('skipping nexthop afi,safi ' + str(afi) + '/' + str(safi) + ' as it is not negotiated', 'configuration')
232
						continue
233
					if (nhafi, safi) not in neighbor.families():
234
						self.logger.debug('skipping nexthop afi ' + str(nhafi) + '/' + str(safi) + ' as it is not negotiated', 'configuration')
235
						continue
236
					neighbor.add_nexthop(afi, safi, nhafi)
237
238
		neighbor.changes = []
239
		neighbor.changes.extend(self.scope.pop_routes())
240
241
		# old format
242
		for section in ('static','l2vpn','flow'):
243
			routes = local.get(section,{}).get('routes',[])
244
			for route in routes:
245
				route.nlri.action = OUT.ANNOUNCE
246
			neighbor.changes.extend(routes)
247
248
		routes = local.get('routes',[])
249
		for route in routes:
250
			route.nlri.action = OUT.ANNOUNCE
251
		neighbor.changes.extend(routes)
252
253
		messages = local.get('operational',{}).get('routes',[])
254
255
		if neighbor.local_address is None:
256
			neighbor.auto_discovery = True
257
			neighbor.local_address = None
258
			neighbor.md5_ip = None
259
260
		if not neighbor.router_id:
261
			if neighbor.peer_address.afi == AFI.ipv4 and not neighbor.auto_discovery:
262
				neighbor.router_id = neighbor.local_address
263
			else:
264
				return self.error.set('missing router-id for the peer, it can not be set using the local-ip')
265
266
		if neighbor.route_refresh:
267
			if neighbor.adj_rib_out:
268
				self.logger.debug('route-refresh requested, enabling adj-rib-out','configuration')
269
270
		missing = neighbor.missing()
271
		if missing:
272
			return self.error.set('incomplete neighbor, missing %s' % missing)
273
274
		if not neighbor.auto_discovery and neighbor.local_address.afi != neighbor.peer_address.afi:
275
			return self.error.set('local-address and peer-address must be of the same family')
276
		neighbor.range_size = neighbor.peer_address.mask.size()
277
278
		if neighbor.range_size > 1 and not neighbor.passive:
279
			return self.error.set('can only use ip ranges for the peer address with passive neighbors')
280
281
		if neighbor.index() in self._neighbors:
282
			return self.error.set('duplicate peer definition %s' % neighbor.peer_address.top())
283
		self._neighbors.append(neighbor.index())
284
285
		if neighbor.md5_password:
286
			try:
287
				md5 = base64.b64decode(neighbor.md5_password) if neighbor.md5_base64 else neighbor.md5_password
288
			except TypeError as e:
289
				return self.error.set("Invalid base64 encoding of MD5 password.")
290
			else:
291
				if len(md5) > 80:
292
					return self.error.set('MD5 password must be no larger than 80 characters')
293
294
		# check we are not trying to announce routes without the right MP announcement
295
		for change in neighbor.changes:
296
			family = change.nlri.family()
297
			if family not in families and family != (AFI.ipv4,SAFI.unicast):
298
				return self.error.set('Trying to announce a route of type %s,%s when we are not announcing the family to our peer' % change.nlri.family())
299
300
		def _init_neighbor (neighbor):
301
			families = neighbor.families()
302
			for change in neighbor.changes:
303
				if change.nlri.family() in families:
304
					# This add the family to neighbor.families()
305
					neighbor.rib.outgoing.add_to_rib_watchdog(change)
306
			for message in messages:
307
				if message.family() in families:
308
					if message.name == 'ASM':
309
						neighbor.asm[message.family()] = message
310
					else:
311
						neighbor.messages.append(message)
312
			self.neighbors[neighbor.name()] = neighbor
313
314
		# create one neighbor object per family for multisession
315
		if neighbor.multisession and len(neighbor.families()) > 1:
316
			for family in neighbor.families():
317
				# XXX: FIXME: Ok, it works but it takes LOTS of memory ..
318
				m_neighbor = deepcopy(neighbor)
319
				m_neighbor.make_rib()
320
				m_neighbor.rib.outgoing.families = [family]
321
				_init_neighbor(m_neighbor)
322
		else:
323
			neighbor.make_rib()
324
			_init_neighbor(neighbor)
325
326
		local.clear()
327
		return True
328