Configuration._enter()   B
last analyzed

Complexity

Conditions 7

Size

Total Lines 31
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

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