Completed
Push — master ( 35f20e...816300 )
by Thomas
13:24
created

exabgp.bgp.neighbor   F

Complexity

Total Complexity 81

Size/Duplication

Total Lines 401
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 281
dl 0
loc 401
rs 2
c 0
b 0
f 0
wmc 81

21 Methods

Rating   Name   Duplication   Size   Complexity  
B Neighbor.__init__() 0 73 1
A Neighbor.id() 0 2 1
A Neighbor.remove_nexthop() 0 3 2
A Neighbor.__str__() 0 2 1
F Neighbor.string() 0 149 39
A Neighbor.add_nexthop() 0 3 2
A Neighbor.__ne__() 0 2 1
A Neighbor.nexthops() 0 3 1
A Neighbor.remove_family() 0 3 2
A Neighbor.add_family() 0 10 3
A Neighbor.__eq__() 0 35 1
A Neighbor.make_rib() 0 2 1
A Neighbor.addpaths() 0 3 1
A Neighbor.clear_rib() 0 4 1
C Neighbor.missing() 0 12 10
A Neighbor.remove_addpath() 0 3 2
A Neighbor.families() 0 3 1
A Neighbor.reset_rib() 0 4 1
A Neighbor.index() 0 4 2
A Neighbor.name() 0 12 5
A Neighbor.add_addpath() 0 10 3

How to fix   Complexity   

Complexity

Complex classes like exabgp.bgp.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.py
4
5
Created by Thomas Mangin on 2009-11-05.
6
Copyright (c) 2009-2017 Exa Networks. All rights reserved.
7
License: 3-clause BSD. (See the COPYRIGHT file)
8
"""
9
10
import os
11
import uuid
12
13
from collections import deque
14
15
# collections.counter is python2.7 only ..
16
try:
17
	from collections import Counter
18
except ImportError:
19
	from exabgp.vendoring.counter import Counter
20
21
from exabgp.protocol.family import AFI
22
23
from exabgp.bgp.message import Message
24
from exabgp.bgp.message.open.capability import NextHop
25
from exabgp.bgp.message.open.capability import AddPath
26
27
from exabgp.rib import RIB
28
29
30
# The definition of a neighbor (from reading the configuration)
31
class Neighbor (object):
32
	_GLOBAL = {'uid': 1}
33
34
	def __init__ (self):
35
		# self.logger should not be used here as long as we do use deepcopy as it contains a Lock
36
		self.description = None
37
		self.router_id = None
38
		self.host_name = None
39
		self.domain_name = None
40
		self.local_address = None
41
		self.range_size = 1
42
		# local_address uses auto discovery
43
		self.auto_discovery = False
44
		self.peer_address = None
45
		self.peer_as = None
46
		self.local_as = None
47
		self.hold_time = None
48
		self.rate_limit = None
49
		self.asn4 = None
50
		self.nexthop = None
51
		self.add_path = None
52
		self.md5_password = None
53
		self.md5_base64 = False
54
		self.md5_ip = None
55
		self.ttl_in = None
56
		self.ttl_out = None
57
		self.group_updates = None
58
		self.flush = None
59
		self.adj_rib_in = None
60
		self.adj_rib_out = None
61
62
		self.manual_eor = False
63
64
		self.api = None  # XXX: not scriptable - is replaced outside the class
65
		# passive indicate that we do not establish outgoing connections
66
		self.passive = False
67
		# the port to listen on ( zero mean that we do not listen )
68
		self.listen = 0
69
		# the port to connect to
70
		self.connect = 0
71
72
		# was this Neighbor generated from a range
73
		self.generated = False
74
75
		# capability
76
		self.route_refresh = False
77
		self.graceful_restart = False
78
		self.multisession = None
79
		self.nexthop = None
80
		self.add_path = None
81
		self.aigp = None
82
83
		self._families = []
84
		self._nexthop = []
85
		self._addpath = []
86
		self.rib = None
87
88
		# The routes we have parsed from the configuration
89
		self.changes = []
90
		# On signal update, the previous routes so we can compare what changed
91
		self.backup_changes = []
92
93
		self.operational = None
94
		self.eor = deque()
95
		self.asm = dict()
96
97
		self.messages = deque()
98
		self.refresh = deque()
99
100
		self.counter = Counter()
101
		# It is possible to :
102
		# - have multiple exabgp toward one peer on the same host ( use of pid )
103
		# - have more than once connection toward a peer
104
		# - each connection has it own neihgbor (hence why identificator is not in Protocol)
105
		self.uid = '%d' % self._GLOBAL['uid']
106
		self._GLOBAL['uid'] += 1
107
108
	def id (self):
109
		return 'neighbor-%s' % self.uid
110
111
	# This set must be unique between peer, not full draft-ietf-idr-bgp-multisession-07
112
	def index (self):
113
		if self.listen != 0:
114
			return 'peer-ip %s listen %d' % (self.peer_address, self.listen)
115
		return self.name()
116
117
	def make_rib (self):
118
		self.rib = RIB(self.name(),self.adj_rib_in,self.adj_rib_out,self._families)
119
120
	# will resend all the routes once we reconnect
121
	def reset_rib (self):
122
		self.rib.reset()
123
		self.messages = deque()
124
		self.refresh = deque()
125
126
	# back to square one, all the routes are removed
127
	def clear_rib (self):
128
		self.rib.clear()
129
		self.messages = deque()
130
		self.refresh = deque()
131
132
	def name (self):
133
		if self.multisession:
134
			session = '/'.join("%s-%s" % (afi.name(),safi.name()) for (afi,safi) in self.families())
135
		else:
136
			session = 'in-open'
137
		return "neighbor %s local-ip %s local-as %s peer-as %s router-id %s family-allowed %s" % (
138
			self.peer_address,
139
			self.local_address if self.peer_address is not None else 'auto',
140
			self.local_as if self.local_as is not None else 'auto',
141
			self.peer_as if self.peer_as is not None else 'auto',
142
			self.router_id,
143
			session
144
		)
145
146
	def families (self):
147
		# this list() is important .. as we use the function to modify self._families
148
		return list(self._families)
149
150
	def nexthops (self):
151
		# this list() is important .. as we use the function to modify self._nexthop
152
		return list(self._nexthop)
153
154
	def addpaths (self):
155
		# this list() is important .. as we use the function to modify self._add_path
156
		return list(self._addpath)
157
158
	def add_family (self, family):
159
		# the families MUST be sorted for neighbor indexing name to be predictable for API users
160
		# this list() is important .. as we use the function to modify self._families
161
		if family not in self.families():
162
			afi,safi = family
163
			d = dict()
164
			d[afi] = [safi,]
165
			for afi,safi in self._families:
166
				d.setdefault(afi,[]).append(safi)
167
			self._families = [(afi,safi) for afi in sorted(d) for safi in sorted(d[afi])]
168
169
	def add_nexthop (self, afi, safi, nhafi):
170
		if (afi,safi,nhafi) not in self._nexthop:
171
			self._nexthop.append((afi,safi,nhafi))
172
173
	def add_addpath (self, family):
174
		# the families MUST be sorted for neighbor indexing name to be predictable for API users
175
		# this list() is important .. as we use the function to modify self._add_path
176
		if family not in self.addpaths():
177
			afi,safi = family
178
			d = dict()
179
			d[afi] = [safi,]
180
			for afi,safi in self._addpath:
181
				d.setdefault(afi,[]).append(safi)
182
			self._addpath = [(afi,safi) for afi in sorted(d) for safi in sorted(d[afi])]
183
184
	def remove_family (self, family):
185
		if family in self.families():
186
			self._families.remove(family)
187
188
	def remove_nexthop (self, afi, safi, nhafi):
189
		if (afi,safi,nhafi) in self.nexthops():
190
			self._nexthop.remove((afi,safi,nhafi))
191
192
	def remove_addpath (self, family):
193
		if family in self.addpaths():
194
			self._addpath.remove(family)
195
196
	def missing (self):
197
		if self.local_address is None and not self.auto_discovery:
198
			return 'local-address'
199
		if self.listen > 0 and self.auto_discovery:
200
			return 'local-address'
201
		if self.peer_address is None:
202
			return 'peer-address'
203
		if self.auto_discovery and not self.router_id:
204
			return 'router-id'
205
		if self.peer_address.afi == AFI.ipv6 and not self.router_id:
206
			return 'router-id'
207
		return ''
208
209
	# This function only compares the neighbor BUT NOT ITS ROUTES
210
	def __eq__ (self, other):
211
		# Comparing local_address is skipped in the case where either
212
		# peer is configured to auto discover its local address. In
213
		# this case it can happen that one local_address is None and
214
		# the other one will be set to the auto disocvered IP address.
215
		auto_discovery = self.auto_discovery or other.auto_discovery
216
		return \
217
			self.router_id == other.router_id and \
218
			(auto_discovery or self.local_address == other.local_address) and \
219
			self.auto_discovery == other.auto_discovery and \
220
			self.local_as == other.local_as and \
221
			self.peer_address == other.peer_address and \
222
			self.peer_as == other.peer_as and \
223
			self.passive == other.passive and \
224
			self.listen == other.listen and \
225
			self.connect == other.connect and \
226
			self.hold_time == other.hold_time and \
227
			self.rate_limit == other.rate_limit and \
228
			self.host_name == other.host_name and \
229
			self.domain_name == other.domain_name and \
230
			self.md5_password == other.md5_password and \
231
			self.md5_ip == other.md5_ip and \
232
			self.ttl_in == other.ttl_in and \
233
			self.ttl_out == other.ttl_out and \
234
			self.route_refresh == other.route_refresh and \
235
			self.graceful_restart == other.graceful_restart and \
236
			self.multisession == other.multisession and \
237
			self.nexthop == other.nexthop and \
238
			self.add_path == other.add_path and \
239
			self.operational == other.operational and \
240
			self.group_updates == other.group_updates and \
241
			self.flush == other.flush and \
242
			self.adj_rib_in == other.adj_rib_in and \
243
			self.adj_rib_out == other.adj_rib_out and \
244
			self.families() == other.families()
245
246
	def __ne__ (self, other):
247
		return not self.__eq__(other)
248
249
	def string (self, with_changes=True):
250
		changes = ''
251
		if with_changes:
252
			changes += '\nstatic { '
253
			for changes in self.rib.incoming.queued_changes():
254
				changes += '\n\t\t%s' % changes.extensive()
255
			changes += '\n}'
256
257
		families = ''
258
		for afi,safi in self.families():
259
			families += '\n\t\t%s %s;' % (afi.name(),safi.name())
260
261
		nexthops = ''
262
		for afi, safi, nexthop in self.nexthops():
263
			nexthops += '\n\t\t%s %s %s;' % (afi.name(), safi.name(), nexthop.name())
264
265
		addpaths = ''
266
		for afi,safi in self.addpaths():
267
			addpaths += '\n\t\t%s %s;' % (afi.name(),safi.name())
268
269
		codes = Message.CODE
270
271
		_extension_global = {
272
			'neighbor-changes': 'neighbor-changes',
273
			'negotiated':       'negotiated',
274
			'fsm':              'fsm',
275
			'signal':           'signal',
276
		}
277
278
		_extension_receive = {
279
			'receive-packets':                        'packets',
280
			'receive-parsed':                         'parsed',
281
			'receive-consolidate':                    'consolidate',
282
			'receive-%s' % codes.NOTIFICATION.SHORT:  'notification',
283
			'receive-%s' % codes.OPEN.SHORT:          'open',
284
			'receive-%s' % codes.KEEPALIVE.SHORT:     'keepalive',
285
			'receive-%s' % codes.UPDATE.SHORT:        'update',
286
			'receive-%s' % codes.ROUTE_REFRESH.SHORT: 'refresh',
287
			'receive-%s' % codes.OPERATIONAL.SHORT:   'operational',
288
		}
289
290
		_extension_send = {
291
			'send-packets':                        'packets',
292
			'send-parsed':                         'parsed',
293
			'send-consolidate':                    'consolidate',
294
			'send-%s' % codes.NOTIFICATION.SHORT:  'notification',
295
			'send-%s' % codes.OPEN.SHORT:          'open',
296
			'send-%s' % codes.KEEPALIVE.SHORT:     'keepalive',
297
			'send-%s' % codes.UPDATE.SHORT:        'update',
298
			'send-%s' % codes.ROUTE_REFRESH.SHORT: 'refresh',
299
			'send-%s' % codes.OPERATIONAL.SHORT:   'operational',
300
		}
301
302
		apis = ''
303
304
		for process in self.api.get('processes',[]):
305
			_global = []
306
			_receive = []
307
			_send = []
308
309
			for api,name in _extension_global.items():
310
				_global.extend(['\t\t%s;\n' % name,] if process in self.api[api] else [])
311
312
			for api,name in _extension_receive.items():
313
				_receive.extend(['\t\t\t%s;\n' % name,] if process in self.api[api] else [])
314
315
			for api,name in _extension_send.items():
316
				_send.extend(['\t\t\t%s;\n' % name,] if process in self.api[api] else [])
317
318
			_api  = '\tapi {\n'
319
			_api += '\t\tprocesses [ %s ];\n' % process
320
			_api += ''.join(_global)
321
			if _receive:
322
				_api += '\t\treceive {\n'
323
				_api += ''.join(_receive)
324
				_api += '\t\t}\n'
325
			if _send:
326
				_api += '\t\tsend {\n'
327
				_api += ''.join(_send)
328
				_api += '\t\t}\n'
329
			_api += '\t}\n'
330
331
			apis += _api
332
333
		returned = \
334
			'neighbor %s {\n' \
335
			'\tdescription "%s";\n' \
336
			'\trouter-id %s;\n' \
337
			'\thost-name %s;\n' \
338
			'\tdomain-name %s;\n' \
339
			'\tlocal-address %s;\n' \
340
			'\tlocal-as %s;\n' \
341
			'\tpeer-as %s;\n' \
342
			'\thold-time %s;\n' \
343
			'\trate-limit %s;\n' \
344
			'\tmanual-eor %s;\n' \
345
			'%s%s%s%s%s%s%s%s%s%s%s\n' \
346
			'\tcapability {\n' \
347
			'%s%s%s%s%s%s%s%s%s\t}\n' \
348
			'\tfamily {%s\n' \
349
			'\t}\n' \
350
			'\tnexthop {%s\n' \
351
			'\t}\n' \
352
			'\tadd-path {%s\n' \
353
			'\t}\n' \
354
			'%s' \
355
			'%s' \
356
			'}' % (
357
				self.peer_address,
358
				self.description,
359
				self.router_id,
360
				self.host_name,
361
				self.domain_name,
362
				self.local_address if not self.auto_discovery else 'auto',
363
				self.local_as,
364
				self.peer_as,
365
				self.hold_time,
366
				'disable' if self.rate_limit == 0 else self.rate_limit,
367
				'true' if self.manual_eor else 'false',
368
				'\n\tpassive %s;\n' % ('true' if self.passive else 'false'),
369
				'\n\tlisten %d;\n' % self.listen if self.listen else '',
370
				'\n\tconnect %d;\n' % self.connect if self.connect else '',
371
				'\tgroup-updates %s;\n' % ('true' if self.group_updates else 'false'),
372
				'\tauto-flush %s;\n' % ('true' if self.flush else 'false'),
373
				'\tadj-rib-in %s;\n' % ('true' if self.adj_rib_in else 'false'),
374
				'\tadj-rib-out %s;\n' % ('true' if self.adj_rib_out else 'false'),
375
				'\tmd5-password "%s";\n' % self.md5_password if self.md5_password else '',
376
				'\tmd5-base64 %s;\n' % ('true' if self.md5_base64 is True else 'false' if self.md5_base64 is False else 'auto'),
377
				'\tmd5-ip "%s";\n' % self.md5_ip if not self.auto_discovery else '',
378
				'\toutgoing-ttl %s;\n' % self.ttl_out if self.ttl_out else '',
379
				'\tincoming-ttl %s;\n' % self.ttl_in if self.ttl_in else '',
380
				'\t\tasn4 %s;\n' % ('enable' if self.asn4 else 'disable'),
381
				'\t\troute-refresh %s;\n' % ('enable' if self.route_refresh else 'disable'),
382
				'\t\tgraceful-restart %s;\n' % (self.graceful_restart if self.graceful_restart else 'disable'),
383
				'\t\tnexthop %s;\n' % ('enable' if self.nexthop else 'disable'),
384
				'\t\tadd-path %s;\n' % (AddPath.string[self.add_path] if self.add_path else 'disable'),
385
				'\t\tmulti-session %s;\n' % ('enable' if self.multisession else 'disable'),
386
				'\t\toperational %s;\n' % ('enable' if self.operational else 'disable'),
387
				'\t\taigp %s;\n' % ('enable' if self.aigp else 'disable'),
388
				families,
389
				nexthops,
390
				addpaths,
391
				apis,
392
				changes
393
			)
394
395
		# '\t\treceive {\n%s\t\t}\n' % receive if receive else '',
396
		# '\t\tsend {\n%s\t\t}\n' % send if send else '',
397
		return returned.replace('\t','  ')
398
399
	def __str__ (self):
400
		return self.string(False)
401