Completed
Push — master ( a60122...e7d721 )
by Thomas
12:58
created

Configuration._reload()   B

Complexity

Conditions 7

Size

Total Lines 47
Code Lines 31

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 31
nop 1
dl 0
loc 47
rs 7.736
c 0
b 0
f 0
1
# encoding: utf-8
2
"""
3
configuration.py
4
5
Created by Thomas Mangin on 2009-08-25.
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 sys
12
13
from exabgp.vendoring import six
14
15
from exabgp.logger import Logger
16
17
from exabgp.configuration.core import Error
18
from exabgp.configuration.core import Scope
19
from exabgp.configuration.core import Tokeniser
20
from exabgp.configuration.core import Section
21
22
from exabgp.configuration.process import ParseProcess
23
from exabgp.configuration.template import ParseTemplate
24
from exabgp.configuration.template.neighbor import ParseTemplateNeighbor
25
from exabgp.configuration.neighbor import ParseNeighbor
26
from exabgp.configuration.neighbor.api import ParseAPI
27
from exabgp.configuration.neighbor.api import ParseSend
28
from exabgp.configuration.neighbor.api import ParseReceive
29
from exabgp.configuration.neighbor.family import ParseFamily
30
from exabgp.configuration.neighbor.family import ParseAddPath
31
from exabgp.configuration.neighbor.nexthop import ParseNextHop
32
from exabgp.configuration.capability import ParseCapability
33
from exabgp.configuration.announce import SectionAnnounce
34
from exabgp.configuration.announce import AnnounceIPv4
35
from exabgp.configuration.announce import AnnounceIPv6
36
from exabgp.configuration.announce import AnnounceL2VPN
37
from exabgp.configuration.static import ParseStatic
38
from exabgp.configuration.static import ParseStaticRoute
39
from exabgp.configuration.flow import ParseFlow
40
from exabgp.configuration.flow import ParseFlowRoute
41
from exabgp.configuration.flow import ParseFlowThen
42
from exabgp.configuration.flow import ParseFlowMatch
43
from exabgp.configuration.flow import ParseFlowScope
44
from exabgp.configuration.l2vpn import ParseL2VPN
45
from exabgp.configuration.l2vpn import ParseVPLS
46
from exabgp.configuration.operational import ParseOperational
47
48
from exabgp.configuration.environment import environment
49
50
# for registration
51
from exabgp.configuration.announce.ip import AnnounceIP
52
from exabgp.configuration.announce.path import AnnouncePath
53
from exabgp.configuration.announce.label import AnnounceLabel
54
from exabgp.configuration.announce.vpn import AnnounceVPN
55
from exabgp.configuration.announce.flow import AnnounceFlow
56
from exabgp.configuration.announce.vpls import AnnounceVPLS
57
58
59
if sys.version_info[0] >= 3:
60
	StandardError = Exception
61
62
63
class _Configuration (object):
64
	def __init__ (self):
65
		self.processes = {}
66
		self.neighbors = {}
67
		self.logger = Logger ()
68
69
	def inject_change (self, peers, change):
70
		result = True
71
		for neighbor in self.neighbors:
72
			if neighbor in peers:
73
				if change.nlri.family() in self.neighbors[neighbor].families():
74
					self.neighbors[neighbor].rib.outgoing.add_to_rib(change)
75
				else:
76
					self.logger.error('the route family is not configured on neighbor','configuration')
77
					result = False
78
		return result
79
80
	def inject_eor (self, peers, family):
81
		result = False
82
		for neighbor in self.neighbors:
83
			if neighbor in peers:
84
				result = True
85
				self.neighbors[neighbor].eor.append(family)
86
		return result
87
88
	def inject_operational (self, peers, operational):
89
		result = True
90
		for neighbor in self.neighbors:
91
			if neighbor in peers:
92
				if operational.family() in self.neighbors[neighbor].families():
93
					if operational.name == 'ASM':
94
						self.neighbors[neighbor].asm[operational.family()] = operational
95
					self.neighbors[neighbor].messages.append(operational)
96
				else:
97
					self.logger.error('the route family is not configured on neighbor','configuration')
98
					result = False
99
		return result
100
101
	def inject_refresh (self, peers, refreshes):
102
		result = True
103
		for neighbor in self.neighbors:
104
			if neighbor in peers:
105
				for refresh in refreshes:
106
					family = (refresh.afi,refresh.safi)
107
					if family in self.neighbors[neighbor].families():
108
						self.neighbors[neighbor].refresh.append(refresh.__class__(refresh.afi,refresh.safi))
109
					else:
110
						result = False
111
		return result
112
113
114
class Configuration (_Configuration):
115
	def __init__ (self, configurations, text=False):
116
		_Configuration.__init__(self)
117
		self.api_encoder = environment.settings().api.encoder
118
119
		self._configurations = configurations
120
		self._text = text
121
122
		self.error  = Error  ()
123
		self.scope  = Scope  ()
124
125
		self.tokeniser = Tokeniser(self.scope,self.error,self.logger)
126
127
		params = (self.tokeniser,self.scope,self.error,self.logger)
128
		self.section             = Section               (*params)
129
		self.process             = ParseProcess          (*params)
130
		self.template            = ParseTemplate         (*params)
131
		self.template_neighbor   = ParseTemplateNeighbor (*params)
132
		self.neighbor            = ParseNeighbor         (*params)
133
		self.family              = ParseFamily           (*params)
134
		self.addpath             = ParseAddPath          (*params)
135
		self.nexthop             = ParseNextHop          (*params)
136
		self.capability          = ParseCapability       (*params)
137
		self.api                 = ParseAPI              (*params)
138
		self.api_send            = ParseSend             (*params)
139
		self.api_receive         = ParseReceive          (*params)
140
		self.static              = ParseStatic           (*params)
141
		self.static_route        = ParseStaticRoute      (*params)
142
		self.announce            = SectionAnnounce       (*params)
143
		self.announce_ipv4       = AnnounceIPv4          (*params)
144
		self.announce_ipv6       = AnnounceIPv6          (*params)
145
		self.announce_l2vpn      = AnnounceL2VPN         (*params)
146
		self.flow                = ParseFlow             (*params)
147
		self.flow_route          = ParseFlowRoute        (*params)
148
		self.flow_match          = ParseFlowMatch        (*params)
149
		self.flow_then           = ParseFlowThen         (*params)
150
		self.flow_scope          = ParseFlowScope        (*params)
151
		self.l2vpn               = ParseL2VPN            (*params)
152
		self.vpls                = ParseVPLS             (*params)
153
		self.operational         = ParseOperational      (*params)
154
155
		# We should check if name are unique when running Section.__init__
156
157
		self._structure = {
158
			'root': {
159
				'class':    self.section,
160
				'commands': [],
161
				'sections': {
162
					'process': self.process.name,
163
					'neighbor': self.neighbor.name,
164
					'template': self.template.name,
165
				},
166
			},
167
			self.process.name: {
168
				'class':    self.process,
169
				'commands': self.process.known.keys(),
170
				'sections': {},
171
			},
172
			self.template.name: {
173
				'class':    self.template,
174
				'commands': self.template.known.keys(),
175
				'sections': {
176
					'neighbor':    self.template_neighbor.name,
177
				},
178
			},
179
			self.template_neighbor.name: {
180
				'class':    self.template_neighbor,
181
				'commands': self.template_neighbor.known.keys(),
182
				'sections': {
183
					'family':      self.family.name,
184
					'capability':  self.capability.name,
185
					'add-path':    self.addpath.name,
186
					'nexthop':     self.nexthop.name,
187
					'api':         self.api.name,
188
					'static':      self.static.name,
189
					'flow':        self.flow.name,
190
					'l2vpn':       self.l2vpn.name,
191
					'operational': self.operational.name,
192
					'announce':    self.announce.name,
193
				},
194
			},
195
			self.neighbor.name: {
196
				'class':    self.neighbor,
197
				'commands': self.neighbor.known.keys(),
198
				'sections': {
199
					'family':      self.family.name,
200
					'capability':  self.capability.name,
201
					'add-path':    self.addpath.name,
202
					'nexthop':     self.nexthop.name,
203
					'api':         self.api.name,
204
					'static':      self.static.name,
205
					'flow':        self.flow.name,
206
					'l2vpn':       self.l2vpn.name,
207
					'operational': self.operational.name,
208
					'announce':    self.announce.name,
209
				},
210
			},
211
			self.family.name: {
212
				'class':    self.family,
213
				'commands': self.family.known.keys(),
214
				'sections': {
215
				},
216
			},
217
			self.capability.name: {
218
				'class':    self.capability,
219
				'commands': self.capability.known.keys(),
220
				'sections': {
221
				},
222
			},
223
			self.nexthop.name: {
224
				'class':    self.nexthop,
225
				'commands': self.nexthop.known.keys(),
226
				'sections': {
227
				},
228
			},
229
			self.addpath.name: {
230
				'class':    self.addpath,
231
				'commands': self.addpath.known.keys(),
232
				'sections': {
233
				},
234
			},
235
			self.api.name: {
236
				'class':    self.api,
237
				'commands': self.api.known.keys(),
238
				'sections': {
239
					'send':    self.api_send.name,
240
					'receive': self.api_receive.name,
241
				},
242
			},
243
			self.api_send.name: {
244
				'class':    self.api_send,
245
				'commands': self.api_send.known.keys(),
246
				'sections': {
247
				},
248
			},
249
			self.api_receive.name: {
250
				'class':    self.api_receive,
251
				'commands': self.api_receive.known.keys(),
252
				'sections': {
253
				},
254
			},
255
			self.announce.name: {
256
				'class':    self.announce,
257
				'commands': self.announce.known.keys(),
258
				'sections': {
259
					'ipv4': self.announce_ipv4.name,
260
					'ipv6': self.announce_ipv6.name,
261
					'l2vpn': self.announce_l2vpn.name,
262
				},
263
			},
264
			self.announce_ipv4.name: {
265
				'class':    self.announce_ipv4,
266
				'commands': ['unicast', 'multicast', 'nlri-mpls', 'mpls-vpn', 'flow', 'flow-vpn'],
267
				'sections': {
268
				},
269
			},
270
			self.announce_ipv6.name: {
271
				'class':    self.announce_ipv6,
272
				'commands': ['unicast', 'multicast', 'nlri-mpls', 'mpls-vpn', 'flow', 'flow-vpn'],
273
				'sections': {
274
				},
275
			},
276
			self.announce_l2vpn.name: {
277
				'class':    self.announce_l2vpn,
278
				'commands': ['vpls',],
279
				'sections': {
280
				},
281
			},
282
			self.static.name: {
283
				'class':    self.static,
284
				'commands': ['route','attributes'],
285
				'sections': {
286
					'route': self.static_route.name,
287
				},
288
			},
289
			self.static_route.name: {
290
				'class':    self.static_route,
291
				'commands': self.static_route.known.keys(),
292
				'sections': {
293
				},
294
			},
295
			self.flow.name: {
296
				'class':    self.flow,
297
				'commands': self.flow.known.keys(),
298
				'sections': {
299
					'route': self.flow_route.name,
300
				},
301
			},
302
			self.flow_route.name: {
303
				'class':    self.flow_route,
304
				'commands': self.flow_route.known.keys(),
305
				'sections': {
306
					'match': self.flow_match.name,
307
					'then':  self.flow_then.name,
308
					'scope': self.flow_scope.name,
309
				},
310
			},
311
			self.flow_match.name: {
312
				'class':    self.flow_match,
313
				'commands': self.flow_match.known.keys(),
314
				'sections': {
315
				},
316
			},
317
			self.flow_then.name: {
318
				'class':    self.flow_then,
319
				'commands': self.flow_then.known.keys(),
320
				'sections': {
321
				},
322
			},
323
			self.flow_scope.name: {
324
				'class':    self.flow_scope,
325
				'commands': self.flow_scope.known.keys(),
326
				'sections': {
327
				}
328
			},
329
			self.l2vpn.name: {
330
				'class':    self.l2vpn,
331
				'commands': self.l2vpn.known.keys(),
332
				'sections': {
333
					'vpls': self.vpls.name,
334
				},
335
			},
336
			self.vpls.name: {
337
				'class':    self.vpls,
338
				'commands': self.l2vpn.known.keys(),
339
				'sections': {
340
				},
341
			},
342
			self.operational.name: {
343
				'class':    self.operational,
344
				'commands': self.operational.known.keys(),
345
				'sections': {
346
				}
347
			},
348
		}
349
350
		self._neighbors = {}
351
		self._previous_neighbors = {}
352
353
	def _clear (self):
354
		self.processes = {}
355
		self._previous_neighbors = self.neighbors
356
		self.neighbors = {}
357
		self._neighbors = {}
358
359
	# clear the parser data (ie: free memory)
360
	def _cleanup (self):
361
		self.error.clear()
362
		self.tokeniser.clear()
363
		self.scope.clear()
364
365
		self.process.clear()
366
		self.template.clear()
367
		self.template_neighbor.clear()
368
		self.neighbor.clear()
369
		self.family.clear()
370
		self.capability.clear()
371
		self.api.clear()
372
		self.api_send.clear()
373
		self.api_receive.clear()
374
		self.announce_ipv6.clear()
375
		self.announce_ipv4.clear()
376
		self.announce_l2vpn.clear()
377
		self.announce.clear()
378
		self.static.clear()
379
		self.static_route.clear()
380
		self.flow.clear()
381
		self.flow_route.clear()
382
		self.flow_match.clear()
383
		self.flow_then.clear()
384
		self.flow_scope.clear()
385
		self.l2vpn.clear()
386
		self.vpls.clear()
387
		self.operational.clear()
388
389
	def _rollback_reload (self):
390
		self.neighbors = self._previous_neighbors
391
		self.processes = self.process.processes
392
		self._neighbors = {}
393
		self._previous_neighbors = {}
394
395
	def _commit_reload (self):
396
		self.neighbors = self.neighbor.neighbors
397
		# XXX: Yes, we do not detect changes in processes and restart anything ..
398
		# XXX: This is a bug ..
399
		self.processes = self.process.processes
400
		self._neighbors = {}
401
402
		# Add the changes prior to the reload to the neighbor to correct handling of deleted routes
403
		for neighbor in self.neighbors:
404
			if neighbor in self._previous_neighbors:
405
				self.neighbors[neighbor].backup_changes = self._previous_neighbors[neighbor].changes
406
407
		self._previous_neighbors = {}
408
		self._cleanup()
409
410
	def reload (self):
411
		try:
412
			return self._reload()
413
		except KeyboardInterrupt:
414
			return self.error.set('configuration reload aborted by ^C or SIGINT')
415
		except Error as exc:
416
			if environment.settings().debug.configuration:
417
				raise
418
			return self.error.set(
419
				'problem parsing configuration file line %d\n'
420
				'error message: %s' % (self.tokeniser.index_line, exc)
421
			)
422
		except StandardError as exc:
423
			if environment.settings().debug.configuration:
424
				raise
425
			return self.error.set(
426
				'problem parsing configuration file line %d\n'
427
				'error message: %s' % (self.tokeniser.index_line, exc)
428
			)
429
430
	def _reload (self):
431
		# taking the first configuration available (FIFO buffer)
432
		fname = self._configurations.pop(0)
433
		self._configurations.append(fname)
434
435
		# clearing the current configuration to be able to re-parse it
436
		self._clear()
437
438
		if self._text:
439
			if not self.tokeniser.set_text(fname):
440
				return False
441
		else:
442
			# resolve any potential symlink, and check it is a file
443
			target = os.path.realpath(fname)
444
			if not os.path.isfile(target):
445
				return False
446
			if not self.tokeniser.set_file(target):
447
				return False
448
449
		if self.parseSection('root') is not True:
450
			# XXX: Should it be in neighbor ?
451
			self.process.add_api()
452
			self._rollback_reload()
453
454
			return self.error.set(
455
				"\n"
456
				"syntax error in section %s\n"
457
				"line %d: %s\n"
458
				"\n%s" % (
459
					self.scope.location(),
460
					self.tokeniser.number,
461
					' '.join(self.tokeniser.line),
462
					str(self.error)
463
				)
464
			)
465
466
		self.process.add_api()
467
		self._commit_reload()
468
		self._link()
469
470
		check = self.validate()
471
		if check is not None:
472
			return check
473
474
		self.debug_check_route()
475
		self.debug_self_check()
476
		return True
477
478
	def validate (self):
479
		for neighbor in self.neighbors.values():
480
			for notification in neighbor.api:
481
				for api in neighbor.api[notification]:
482
					if not self.processes[api].get('run',''):
483
						return self.error.set(
484
							"\n\nan api called '%s' is used by neighbor '%s' but not defined\n\n" % (api,neighbor.peer_address),
485
						)
486
		return None
487
488
	def _link (self):
489
		for neighbor in six.itervalues(self.neighbors):
490
			api = neighbor.api
491
			for process in api.get('processes',[]):
492
				self.processes.setdefault(process,{})['neighbor-changes'] = api['neighbor-changes']
493
				self.processes.setdefault(process,{})['negotiated'] = api['negotiated']
494
				self.processes.setdefault(process,{})['fsm'] = api['fsm']
495
				self.processes.setdefault(process,{})['signal'] = api['signal']
496
				for way in ('send','receive'):
497
					for name in ('parsed','packets','consolidate'):
498
						key = "%s-%s" % (way,name)
499
						if api[key]:
500
							self.processes[process].setdefault(key,[]).append(neighbor.router_id)
501
					for name in ('open', 'update', 'notification', 'keepalive', 'refresh', 'operational'):
502
						key = "%s-%s" % (way,name)
503
						if api[key]:
504
							self.processes[process].setdefault(key,[]).append(neighbor.router_id)
505
506
	def partial (self, section, text, action='announce'):
507
		self._cleanup()  # this perform a big cleanup (may be able to be smarter)
508
		self._clear()
509
		self.tokeniser.set_api(text if text.endswith(';') or text.endswith('}') else text + ' ;')
510
		self.tokeniser.set_action(action)
511
512
		if self.parseSection(section) is not True:
513
			self._rollback_reload()
514
			self.logger.debug(
515
				"\n"
516
				"syntax error in api command %s\n"
517
				"line %d: %s\n"
518
				"\n%s" % (
519
					self.scope.location(),
520
					self.tokeniser.number,
521
					' '.join(self.tokeniser.line),
522
					str(self.error)
523
				),
524
				'configuration'
525
			)
526
			return False
527
		return True
528
529
	def _enter (self,name):
530
		location = self.tokeniser.iterate()
531
		self.logger.debug("> %-16s | %s" % (location,self.tokeniser.params()),'configuration')
532
533
		if location not in self._structure[name]['sections']:
534
			return self.error.set('section %s is invalid in %s, %s' % (location,name,self.scope.location()))
535
536
		self.scope.enter(location)
537
		self.scope.to_context()
538
539
		class_name = self._structure[name]['sections'][location]
540
		instance = self._structure[class_name].get('class',None)
541
		if not instance:
542
			raise RuntimeError('This should not be happening, debug time !')
543
544
		if not instance.pre():
545
			return False
546
547
		if not self.dispatch(self._structure[name]['sections'][location]):
548
			return False
549
550
		if not instance.post():
551
			return False
552
553
		left = self.scope.leave()
554
		if not left:
555
			return self.error.set('closing too many parenthesis')
556
		self.scope.to_context()
557
558
		self.logger.debug("< %-16s | %s" % (left,self.tokeniser.params()),'configuration')
559
		return True
560
561
	def _run (self,name):
562
		command = self.tokeniser.iterate()
563
		self.logger.debug(". %-16s | %s" % (command,self.tokeniser.params()),'configuration')
564
565
		if not self.run(name,command):
566
			return False
567
		return True
568
569
	def dispatch (self,name):
570
		while True:
571
			self.tokeniser()
572
573
			if self.tokeniser.end == ';':
574
				if self._run(name):
575
					continue
576
				return False
577
578
			if self.tokeniser.end == '{':
579
				if self._enter(name):
580
					continue
581
				return False
582
583
			if self.tokeniser.end == '}':
584
				return True
585
586
			if not self.tokeniser.end:  # finished
587
				return True
588
589
			return self.error.set('invalid syntax line %d' % self.tokeniser.index_line)
590
		return False
591
592
	def parseSection (self, name):
593
		if name not in self._structure:
594
			return self.error.set('option %s is not allowed here' % name)
595
596
		return self.dispatch(name)
597
598
	def run (self, name, command):
599
		# restore 'anounce attribute' to provide backward 3.4 compatibility
600
		if name == 'static' and command == 'attribute':
601
			command = 'attributes'
602
		if command not in self._structure[name]['commands']:
603
			return self.error.set('invalid keyword "%s"' % command)
604
605
		return self._structure[name]['class'].parse(name,command)
606
607
	def debug_check_route (self):
608
		# we are not really running the program, just want to ....
609
		if environment.settings().debug.route:
610
			from exabgp.configuration.check import check_message
611
			if check_message(self.neighbors,environment.settings().debug.route):
612
				sys.exit(0)
613
			sys.exit(1)
614
615
	def debug_self_check (self):
616
		# we are not really running the program, just want check the configuration validity
617
		if environment.settings().debug.selfcheck:
618
			from exabgp.configuration.check import check_neighbor
619
			if check_neighbor(self.neighbors):
620
				sys.exit(0)
621
			sys.exit(1)
622