diff --git a/src/exabgp/application/validate.py b/src/exabgp/application/validate.py index c9da274d0..3e128ef1a 100644 --- a/src/exabgp/application/validate.py +++ b/src/exabgp/application/validate.py @@ -10,6 +10,7 @@ from exabgp.environment import getconf from exabgp.configuration.configuration import Configuration +from exabgp.reactor.api.command.neighbor import NeighborTemplate from exabgp.debug import trace_interceptor from exabgp.logger import log @@ -61,7 +62,7 @@ def cmdline(cmdarg): if cmdarg.neighbor: log.warning('checking neighbors', 'configuration') for name, neighbor in config.neighbors.items(): - reparsed = neighbor.string() + reparsed = NeighborTemplate.configuration(neighbor) log.debug(reparsed, configuration) log.info(f'\u2713 neighbor {name.split()[1]}', 'configuration') diff --git a/src/exabgp/bgp/neighbor.py b/src/exabgp/bgp/neighbor.py index 429ccb9ad..70719e6d2 100644 --- a/src/exabgp/bgp/neighbor.py +++ b/src/exabgp/bgp/neighbor.py @@ -25,6 +25,7 @@ from exabgp.rib import RIB +from exabgp.reactor.api.command.neighbor import NeighborTemplate # class Section(dict): # name = '' @@ -290,182 +291,6 @@ def __eq__(self, other): def __ne__(self, other): return not self.__eq__(other) - def string(self, with_changes=True): - changes = '' - if with_changes: - changes += '\nstatic { ' - for change in self.rib.outgoing.queued_changes(): - changes += '\n\t\t%s' % change.extensive() - changes += '\n}' - - families = '' - for afi, safi in self.families(): - families += '\n\t\t%s %s;' % (afi.name(), safi.name()) - - nexthops = '' - for afi, safi, nexthop in self.nexthops(): - nexthops += '\n\t\t%s %s %s;' % (afi.name(), safi.name(), nexthop.name()) - - addpaths = '' - for afi, safi in self.addpaths(): - addpaths += '\n\t\t%s %s;' % (afi.name(), safi.name()) - - codes = Message.CODE - - _extension_global = { - 'neighbor-changes': 'neighbor-changes', - 'negotiated': 'negotiated', - 'fsm': 'fsm', - 'signal': 'signal', - } - - _extension_receive = { - 'receive-packets': 'packets', - 'receive-parsed': 'parsed', - 'receive-consolidate': 'consolidate', - 'receive-%s' % codes.NOTIFICATION.SHORT: 'notification', - 'receive-%s' % codes.OPEN.SHORT: 'open', - 'receive-%s' % codes.KEEPALIVE.SHORT: 'keepalive', - 'receive-%s' % codes.UPDATE.SHORT: 'update', - 'receive-%s' % codes.ROUTE_REFRESH.SHORT: 'refresh', - 'receive-%s' % codes.OPERATIONAL.SHORT: 'operational', - } - - _extension_send = { - 'send-packets': 'packets', - 'send-parsed': 'parsed', - 'send-consolidate': 'consolidate', - 'send-%s' % codes.NOTIFICATION.SHORT: 'notification', - 'send-%s' % codes.OPEN.SHORT: 'open', - 'send-%s' % codes.KEEPALIVE.SHORT: 'keepalive', - 'send-%s' % codes.UPDATE.SHORT: 'update', - 'send-%s' % codes.ROUTE_REFRESH.SHORT: 'refresh', - 'send-%s' % codes.OPERATIONAL.SHORT: 'operational', - } - - apis = '' - - for process in self.api.get('processes', []): - _global = [] - _receive = [] - _send = [] - - for api, name in _extension_global.items(): - _global.extend( - [ - '\t\t%s;\n' % name, - ] - if process in self.api[api] - else [] - ) - - for api, name in _extension_receive.items(): - _receive.extend( - [ - '\t\t\t%s;\n' % name, - ] - if process in self.api[api] - else [] - ) - - for api, name in _extension_send.items(): - _send.extend( - [ - '\t\t\t%s;\n' % name, - ] - if process in self.api[api] - else [] - ) - - _api = '\tapi {\n' - _api += '\t\tprocesses [ %s ];\n' % process - _api += ''.join(_global) - if _receive: - _api += '\t\treceive {\n' - _api += ''.join(_receive) - _api += '\t\t}\n' - if _send: - _api += '\t\tsend {\n' - _api += ''.join(_send) - _api += '\t\t}\n' - _api += '\t}\n' - - apis += _api - - returned = ( - 'neighbor %s {\n' - '\tdescription "%s";\n' - '\trouter-id %s;\n' - '\thost-name %s;\n' - '\tdomain-name %s;\n' - '\tlocal-address %s;\n' - '\tsource-interface %s;\n' - '\tlocal-as %s;\n' - '\tpeer-as %s;\n' - '\thold-time %s;\n' - '\trate-limit %s;\n' - '\tmanual-eor %s;\n' - '%s%s%s%s%s%s%s%s%s%s%s\n' - '\tcapability {\n' - '%s%s%s%s%s%s%s%s%s%s\t}\n' - '\tfamily {%s\n' - '\t}\n' - '\tnexthop {%s\n' - '\t}\n' - '\tadd-path {%s\n' - '\t}\n' - '%s' - '%s' - '}' - % ( - self['peer-address'], - self['description'], - self['router-id'], - self['host-name'], - self['domain-name'], - self['local-address'] if not self.auto_discovery else 'auto', - self['source-interface'], - self['local-as'], - self['peer-as'], - self['hold-time'], - 'disable' if self['rate-limit'] == 0 else self['rate-limit'], - 'true' if self['manual-eor'] else 'false', - '\n\tpassive %s;\n' % ('true' if self['passive'] else 'false'), - '\n\tlisten %d;\n' % self['listen'] if self['listen'] else '', - '\n\tconnect %d;\n' % self['connect'] if self['connect'] else '', - '\tgroup-updates %s;\n' % ('true' if self['group-updates'] else 'false'), - '\tauto-flush %s;\n' % ('true' if self['auto-flush'] else 'false'), - '\tadj-rib-in %s;\n' % ('true' if self['adj-rib-in'] else 'false'), - '\tadj-rib-out %s;\n' % ('true' if self['adj-rib-out'] else 'false'), - '\tmd5-password "%s";\n' % self['md5-password'] if self['md5-password'] else '', - '\tmd5-base64 %s;\n' - % ('true' if self['md5-base64'] is True else 'false' if self['md5-base64'] is False else 'auto'), - '\tmd5-ip "%s";\n' % self['md5-ip'] if not self.auto_discovery else '', - '\toutgoing-ttl %s;\n' % self['outgoing-ttl'] if self['outgoing-ttl'] else '', - '\tincoming-ttl %s;\n' % self['incoming-ttl'] if self['incoming-ttl'] else '', - '\t\tasn4 %s;\n' % ('enable' if self['capability']['asn4'] else 'disable'), - '\t\troute-refresh %s;\n' % ('enable' if self['capability']['route-refresh'] else 'disable'), - '\t\tgraceful-restart %s;\n' - % (self['capability']['graceful-restart'] if self['capability']['graceful-restart'] else 'disable'), - '\t\tsoftware-version %s;\n' % ('enable' if self['capability']['software-version'] else 'disable'), - '\t\tnexthop %s;\n' % ('enable' if self['capability']['nexthop'] else 'disable'), - '\t\tadd-path %s;\n' - % (AddPath.string[self['capability']['add-path']] if self['capability']['add-path'] else 'disable'), - '\t\tmulti-session %s;\n' % ('enable' if self['capability']['multi-session'] else 'disable'), - '\t\toperational %s;\n' % ('enable' if self['capability']['operational'] else 'disable'), - '\t\taigp %s;\n' % ('enable' if self['capability']['aigp'] else 'disable'), - families, - nexthops, - addpaths, - apis, - changes, - ) - ) - - # '\t\treceive {\n%s\t\t}\n' % receive if receive else '', - # '\t\tsend {\n%s\t\t}\n' % send if send else '', - return returned.replace('\t', ' ') - def ip_self(self, afi): if afi == self['local-address'].afi: return self['local-address'] @@ -490,4 +315,4 @@ def remove_self(self, changes): return change def __str__(self): - return self.string(False) + return NeighborTemplate.configuration(self, False) diff --git a/src/exabgp/reactor/api/__init__.py b/src/exabgp/reactor/api/__init__.py index ce80b8f17..a67240264 100644 --- a/src/exabgp/reactor/api/__init__.py +++ b/src/exabgp/reactor/api/__init__.py @@ -37,10 +37,28 @@ def log_failure(self, message, level='ERR'): report = '%s\nreason: %s' % (message, error) if error else message log.error(report, 'processes', level) + def process(self, reactor, service, command): + # it to allow a global "set encoding json" + # it to allow a global "set encoding text" + # to not have to set the encoding on each command + if ' json ' in command: + return self.json(reactor, service, command) + if ' text ' in command: + return self.text(reactor, service, command) + return self.text(reactor, service, command) + def text(self, reactor, service, command): for registered in self.functions: if registered == command or command.endswith(' ' + registered) or registered + ' ' in command: - return self.callback['text'][registered](self, reactor, service, command) + return self.callback['text'][registered](self, reactor, service, command, False) + reactor.processes.answer_error(service) + log.warning('command from process not understood : %s' % command, 'api') + return False + + def json(self, reactor, service, command): + for registered in self.functions: + if registered == command or command.endswith(' ' + registered) or registered + ' ' in command: + return self.callback['json'][registered](self, reactor, service, command, True) reactor.processes.answer_error(service) log.warning('command from process not understood : %s' % command, 'api') return False diff --git a/src/exabgp/reactor/api/command/announce.py b/src/exabgp/reactor/api/command/announce.py index 7a4b67dc9..a3781dc74 100644 --- a/src/exabgp/reactor/api/command/announce.py +++ b/src/exabgp/reactor/api/command/announce.py @@ -21,11 +21,11 @@ def register_announce(): pass -# @Command.register('text', 'debug') +# @Command.register('debug') # the command debug is hardcoded in the process code -@Command.register('text', 'announce route') -def announce_route(self, reactor, service, line): +@Command.register('announce route') +def announce_route(self, reactor, service, line, use_json): def callback(): try: descriptions, command = extract_neighbors(line) @@ -70,8 +70,8 @@ def callback(): return True -@Command.register('text', 'withdraw route') -def withdraw_route(self, reactor, service, line): +@Command.register('withdraw route') +def withdraw_route(self, reactor, service, line, use_json): def callback(): try: descriptions, command = extract_neighbors(line) @@ -126,8 +126,8 @@ def callback(): return True -@Command.register('text', 'announce vpls') -def announce_vpls(self, reactor, service, line): +@Command.register('announce vpls') +def announce_vpls(self, reactor, service, line, use_json): def callback(): try: descriptions, command = extract_neighbors(line) @@ -167,8 +167,8 @@ def callback(): return True -@Command.register('text', 'withdraw vpls') -def withdraw_vpls(self, reactor, service, line): +@Command.register('withdraw vpls') +def withdraw_vpls(self, reactor, service, line, use_json): def callback(): try: descriptions, command = extract_neighbors(line) @@ -214,9 +214,9 @@ def callback(): return True -@Command.register('text', 'announce attribute') -@Command.register('text', 'announce attributes') -def announce_attributes(self, reactor, service, line): +@Command.register('announce attribute') +@Command.register('announce attributes') +def announce_attributes(self, reactor, service, line, use_json): def callback(): try: descriptions, command = extract_neighbors(line) @@ -256,9 +256,9 @@ def callback(): return True -@Command.register('text', 'withdraw attribute') -@Command.register('text', 'withdraw attributes') -def withdraw_attribute(self, reactor, service, line): +@Command.register('withdraw attribute') +@Command.register('withdraw attributes') +def withdraw_attribute(self, reactor, service, line, use_json): def callback(): try: descriptions, command = extract_neighbors(line) @@ -303,8 +303,8 @@ def callback(): return True -@Command.register('text', 'announce flow') -def announce_flow(self, reactor, service, line): +@Command.register('announce flow') +def announce_flow(self, reactor, service, line, use_json): def callback(): try: descriptions, command = extract_neighbors(line) @@ -344,8 +344,8 @@ def callback(): return True -@Command.register('text', 'withdraw flow') -def withdraw_flow(self, reactor, service, line): +@Command.register('withdraw flow') +def withdraw_flow(self, reactor, service, line, use_json): def callback(): try: descriptions, command = extract_neighbors(line) @@ -390,8 +390,8 @@ def callback(): return True -@Command.register('text', 'announce eor') -def announce_eor(self, reactor, service, command): +@Command.register('announce eor') +def announce_eor(self, reactor, service, line, use_json): def callback(self, command, peers): family = self.api_eor(command) if not family: @@ -410,7 +410,7 @@ def callback(self, command, peers): reactor.processes.answer_done(service) try: - descriptions, command = extract_neighbors(command) + descriptions, command = extract_neighbors(line) peers = match_neighbors(reactor.established_peers(), descriptions) if not peers: self.log_failure('no neighbor matching the command : %s' % command) @@ -428,8 +428,8 @@ def callback(self, command, peers): return False -@Command.register('text', 'announce route-refresh') -def announce_refresh(self, reactor, service, command): +@Command.register('announce route-refresh') +def announce_refresh(self, reactor, service, line, use_json): def callback(self, command, peers): refreshes = self.api_refresh(command) if not refreshes: @@ -449,7 +449,7 @@ def callback(self, command, peers): reactor.processes.answer_done(service) try: - descriptions, command = extract_neighbors(command) + descriptions, command = extract_neighbors(line) peers = match_neighbors(reactor.established_peers(), descriptions) if not peers: self.log_failure('no neighbor matching the command : %s' % command) @@ -467,8 +467,8 @@ def callback(self, command, peers): return False -@Command.register('text', 'announce operational') -def announce_operational(self, reactor, service, command): +@Command.register('announce operational') +def announce_operational(self, reactor, service, line, use_json): def callback(self, command, peers): operational = self.api_operational(command) if not operational: @@ -499,7 +499,7 @@ def callback(self, command, peers): return False try: - descriptions, command = extract_neighbors(command) + descriptions, command = extract_neighbors(line) peers = match_neighbors(reactor.peers(service), descriptions) if not peers: self.log_failure('no neighbor matching the command : %s' % command) @@ -517,8 +517,8 @@ def callback(self, command, peers): return False -@Command.register('text', 'announce ipv4') -def announce_ipv4(self, reactor, service, line): +@Command.register('announce ipv4') +def announce_ipv4(self, reactor, service, line, use_json): def callback(): try: descriptions, command = extract_neighbors(line) @@ -558,8 +558,8 @@ def callback(): return True -@Command.register('text', 'withdraw ipv4') -def withdraw_ipv4(self, reactor, service, line): +@Command.register('withdraw ipv4') +def withdraw_ipv4(self, reactor, service, line, use_json): def callback(): try: descriptions, command = extract_neighbors(line) @@ -604,8 +604,8 @@ def callback(): return True -@Command.register('text', 'announce ipv6') -def announce_ipv6(self, reactor, service, line): +@Command.register('announce ipv6') +def announce_ipv6(self, reactor, service, line, use_json): def callback(): try: descriptions, command = extract_neighbors(line) @@ -645,8 +645,8 @@ def callback(): return True -@Command.register('text', 'withdraw ipv6') -def withdraw_ipv6(self, reactor, service, line): +@Command.register('withdraw ipv6') +def withdraw_ipv6(self, reactor, service, line, use_json): def callback(): try: descriptions, command = extract_neighbors(line) diff --git a/src/exabgp/reactor/api/command/command.py b/src/exabgp/reactor/api/command/command.py index b51ab86bc..423209198 100644 --- a/src/exabgp/reactor/api/command/command.py +++ b/src/exabgp/reactor/api/command/command.py @@ -14,7 +14,7 @@ class Command(object): functions = [] @classmethod - def register(cls, encoding, name, neighbor=True, options=None): + def register(cls, name, neighbor=True, options=None, json_support=False): if name not in cls.functions: cls.functions.append(name) cls.functions.sort(reverse=True) @@ -22,7 +22,9 @@ def register(cls, encoding, name, neighbor=True, options=None): def register(function): cls.callback['neighbor'][name] = neighbor - cls.callback[encoding][name] = function + cls.callback['text'][name] = function + if json_support: + cls.callback['json'][name] = function function.func_name = name.replace(' ', '_') return function diff --git a/src/exabgp/reactor/api/command/neighbor.py b/src/exabgp/reactor/api/command/neighbor.py index 162439d4b..c83e68893 100644 --- a/src/exabgp/reactor/api/command/neighbor.py +++ b/src/exabgp/reactor/api/command/neighbor.py @@ -15,6 +15,9 @@ from exabgp.reactor.api.command.limit import match_neighbor from exabgp.reactor.api.command.limit import extract_neighbors +from exabgp.bgp.message import Message +from exabgp.bgp.message.open.capability import AddPath + def register_neighbor(): pass @@ -42,7 +45,7 @@ def _addpath(send, receive): return "disabled" -class Neighbor(object): +class NeighborTemplate(object): extensive_kv = ' %-20s %15s %15s %15s' extensive_template = """\ Neighbor %(peer-address)s @@ -72,6 +75,184 @@ class Neighbor(object): summary_header = 'Peer AS up/down state | #sent #recvd' summary_template = '%-15s %-7s %9s %-12s %10d %10d' + @classmethod + def configuration(cls, neighbor, with_changes=True): + changes = '' + if with_changes: + changes += '\nstatic { ' + for change in neighbor.rib.outgoing.queued_changes(): + changes += '\n\t\t%s' % change.extensive() + changes += '\n}' + + families = '' + for afi, safi in neighbor.families(): + families += '\n\t\t%s %s;' % (afi.name(), safi.name()) + + nexthops = '' + for afi, safi, nexthop in neighbor.nexthops(): + nexthops += '\n\t\t%s %s %s;' % (afi.name(), safi.name(), nexthop.name()) + + addpaths = '' + for afi, safi in neighbor.addpaths(): + addpaths += '\n\t\t%s %s;' % (afi.name(), safi.name()) + + codes = Message.CODE + + _extension_global = { + 'neighbor-changes': 'neighbor-changes', + 'negotiated': 'negotiated', + 'fsm': 'fsm', + 'signal': 'signal', + } + + _extension_receive = { + 'receive-packets': 'packets', + 'receive-parsed': 'parsed', + 'receive-consolidate': 'consolidate', + 'receive-%s' % codes.NOTIFICATION.SHORT: 'notification', + 'receive-%s' % codes.OPEN.SHORT: 'open', + 'receive-%s' % codes.KEEPALIVE.SHORT: 'keepalive', + 'receive-%s' % codes.UPDATE.SHORT: 'update', + 'receive-%s' % codes.ROUTE_REFRESH.SHORT: 'refresh', + 'receive-%s' % codes.OPERATIONAL.SHORT: 'operational', + } + + _extension_send = { + 'send-packets': 'packets', + 'send-parsed': 'parsed', + 'send-consolidate': 'consolidate', + 'send-%s' % codes.NOTIFICATION.SHORT: 'notification', + 'send-%s' % codes.OPEN.SHORT: 'open', + 'send-%s' % codes.KEEPALIVE.SHORT: 'keepalive', + 'send-%s' % codes.UPDATE.SHORT: 'update', + 'send-%s' % codes.ROUTE_REFRESH.SHORT: 'refresh', + 'send-%s' % codes.OPERATIONAL.SHORT: 'operational', + } + + apis = '' + + for process in neighbor.api.get('processes', []): + _global = [] + _receive = [] + _send = [] + + for api, name in _extension_global.items(): + _global.extend( + [ + '\t\t%s;\n' % name, + ] + if process in neighbor.api[api] + else [] + ) + + for api, name in _extension_receive.items(): + _receive.extend( + [ + '\t\t\t%s;\n' % name, + ] + if process in neighbor.api[api] + else [] + ) + + for api, name in _extension_send.items(): + _send.extend( + [ + '\t\t\t%s;\n' % name, + ] + if process in neighbor.api[api] + else [] + ) + + _api = '\tapi {\n' + _api += '\t\tprocesses [ %s ];\n' % process + _api += ''.join(_global) + if _receive: + _api += '\t\treceive {\n' + _api += ''.join(_receive) + _api += '\t\t}\n' + if _send: + _api += '\t\tsend {\n' + _api += ''.join(_send) + _api += '\t\t}\n' + _api += '\t}\n' + + apis += _api + + returned = ( + 'neighbor %s {\n' + '\tdescription "%s";\n' + '\trouter-id %s;\n' + '\thost-name %s;\n' + '\tdomain-name %s;\n' + '\tlocal-address %s;\n' + '\tsource-interface %s;\n' + '\tlocal-as %s;\n' + '\tpeer-as %s;\n' + '\thold-time %s;\n' + '\trate-limit %s;\n' + '\tmanual-eor %s;\n' + '%s%s%s%s%s%s%s%s%s%s%s\n' + '\tcapability {\n' + '%s%s%s%s%s%s%s%s%s%s\t}\n' + '\tfamily {%s\n' + '\t}\n' + '\tnexthop {%s\n' + '\t}\n' + '\tadd-path {%s\n' + '\t}\n' + '%s' + '%s' + '}' + % ( + neighbor['peer-address'], + neighbor['description'], + neighbor['router-id'], + neighbor['host-name'], + neighbor['domain-name'], + neighbor['local-address'] if not neighbor.auto_discovery else 'auto', + neighbor['source-interface'], + neighbor['local-as'], + neighbor['peer-as'], + neighbor['hold-time'], + 'disable' if neighbor['rate-limit'] == 0 else neighbor['rate-limit'], + 'true' if neighbor['manual-eor'] else 'false', + '\n\tpassive %s;\n' % ('true' if neighbor['passive'] else 'false'), + '\n\tlisten %d;\n' % neighbor['listen'] if neighbor['listen'] else '', + '\n\tconnect %d;\n' % neighbor['connect'] if neighbor['connect'] else '', + '\tgroup-updates %s;\n' % ('true' if neighbor['group-updates'] else 'false'), + '\tauto-flush %s;\n' % ('true' if neighbor['auto-flush'] else 'false'), + '\tadj-rib-in %s;\n' % ('true' if neighbor['adj-rib-in'] else 'false'), + '\tadj-rib-out %s;\n' % ('true' if neighbor['adj-rib-out'] else 'false'), + '\tmd5-password "%s";\n' % neighbor['md5-password'] if neighbor['md5-password'] else '', + '\tmd5-base64 %s;\n' + % ('true' if neighbor['md5-base64'] is True else 'false' if neighbor['md5-base64'] is False else 'auto'), + '\tmd5-ip "%s";\n' % neighbor['md5-ip'] if not neighbor.auto_discovery else '', + '\toutgoing-ttl %s;\n' % neighbor['outgoing-ttl'] if neighbor['outgoing-ttl'] else '', + '\tincoming-ttl %s;\n' % neighbor['incoming-ttl'] if neighbor['incoming-ttl'] else '', + '\t\tasn4 %s;\n' % ('enable' if neighbor['capability']['asn4'] else 'disable'), + '\t\troute-refresh %s;\n' % ('enable' if neighbor['capability']['route-refresh'] else 'disable'), + '\t\tgraceful-restart %s;\n' + % (neighbor['capability']['graceful-restart'] if neighbor['capability']['graceful-restart'] else 'disable'), + '\t\tsoftware-version %s;\n' % ('enable' if neighbor['capability']['software-version'] else 'disable'), + '\t\tnexthop %s;\n' % ('enable' if neighbor['capability']['nexthop'] else 'disable'), + '\t\tadd-path %s;\n' + % (AddPath.string[neighbor['capability']['add-path']] if neighbor['capability']['add-path'] else 'disable'), + '\t\tmulti-session %s;\n' % ('enable' if neighbor['capability']['multi-session'] else 'disable'), + '\t\toperational %s;\n' % ('enable' if neighbor['capability']['operational'] else 'disable'), + '\t\taigp %s;\n' % ('enable' if neighbor['capability']['aigp'] else 'disable'), + families, + nexthops, + addpaths, + apis, + changes, + ) + ) + + # '\t\treceive {\n%s\t\t}\n' % receive if receive else '', + # '\t\tsend {\n%s\t\t}\n' % send if send else '', + return returned.replace('\t', ' ') + + @classmethod def as_dict(cls, answer): up = answer['duration'] @@ -173,8 +354,8 @@ def summary(cls, answer): ) -@Command.register('text', 'teardown', True) -def teardown(self, reactor, service, line): +@Command.register('teardown', True) +def teardown(self, reactor, service, line, use_json): try: descriptions, line = extract_neighbors(line) if ' ' not in line: @@ -199,11 +380,9 @@ def teardown(self, reactor, service, line): return False -@Command.register('text', 'show neighbor', False, ['summary', 'extensive', 'configuration']) -@Command.register('text', 'show neighbor text', False, ['summary', 'extensive', 'configuration']) -@Command.register('json', 'show neighbor json', False, ['summary', 'extensive', 'configuration']) -def show_neighbor(self, reactor, service, command): - words = command.split() +@Command.register('show neighbor', False, ['summary', 'extensive', 'configuration'], True) +def show_neighbor(self, reactor, service, line, use_json): + words = line.split() extensive = 'extensive' in words configuration = 'configuration' in words @@ -222,8 +401,6 @@ def show_neighbor(self, reactor, service, command): if text: words.remove('text') - use_json = json and not text - limit = words[-1] if words[-1] != 'neighbor' else '' def callback_configuration(): @@ -267,19 +444,19 @@ def callback_summary(): reactor.processes.answer_done(service) if use_json: - reactor.asynchronous.schedule(service, command, callback_json()) + reactor.asynchronous.schedule(service, line, callback_json()) return True if summary: - reactor.asynchronous.schedule(service, command, callback_summary()) + reactor.asynchronous.schedule(service, line, callback_summary()) return True if extensive: - reactor.asynchronous.schedule(service, command, callback_extensive()) + reactor.asynchronous.schedule(service, line, callback_extensive()) return True if configuration: - reactor.asynchronous.schedule(service, command, callback_configuration()) + reactor.asynchronous.schedule(service, line, callback_configuration()) return True reactor.processes.write(service, 'please specify summary, extensive or configuration') diff --git a/src/exabgp/reactor/api/command/reactor.py b/src/exabgp/reactor/api/command/reactor.py index c1611bd73..b1f980dd6 100644 --- a/src/exabgp/reactor/api/command/reactor.py +++ b/src/exabgp/reactor/api/command/reactor.py @@ -17,10 +17,11 @@ def register_reactor(): pass -@Command.register('text', 'help', False) -def manual(self, reactor, service, _): +@Command.register('help', False) +def manual(self, reactor, service, line, use_json): lines = [] - for command in sorted(self.callback['text']): + encoding = 'json' if use_json else 'text' + for command in sorted(self.callback[encoding]): if self.callback['options'][command]: extended = '%s [ %s ]' % (command, ' | '.join(self.callback['options'][command])) else: @@ -45,51 +46,51 @@ def manual(self, reactor, service, _): return True -@Command.register('text', 'shutdown', False) -def shutdown(self, reactor, service, _): +@Command.register('shutdown', False) +def shutdown(self, reactor, service, line, use_json): reactor.signal.received = reactor.signal.SHUTDOWN reactor.processes.write(service, 'shutdown in progress') reactor.processes.answer_done(service) return True -@Command.register('text', 'reload', False) -def reload(self, reactor, service, _): +@Command.register('reload', False) +def reload(self, reactor, service, line, use_json): reactor.signal.received = reactor.signal.RELOAD reactor.processes.write(service, 'reload in progress') reactor.processes.answer_done(service) return True -@Command.register('text', 'restart', False) -def restart(self, reactor, service, _): +@Command.register('restart', False) +def restart(self, reactor, service, line, use_json): reactor.signal.received = reactor.signal.RESTART reactor.processes.write(service, 'restart in progress') reactor.processes.answer_done(service) return True -@Command.register('text', 'version', False) -def version(self, reactor, service, _): +@Command.register('version', False) +def version(self, reactor, service, line, use_json): reactor.processes.write(service, 'exabgp %s' % _version) reactor.processes.answer_done(service) return True -@Command.register('text', '#', False) -def comment(self, reactor, service, line): +@Command.register('#', False) +def comment(self, reactor, service, line, use_json): log.debug(line.lstrip().lstrip('#').strip(), 'process') reactor.processes.answer_done(service) return True -@Command.register('text', 'reset', False) -def reset(self, reactor, service, line): +@Command.register('reset', False) +def reset(self, reactor, service, line, use_json): reactor.asynchronous.clear(service) -@Command.register('text', 'crash') -def crash(self, reactor, service, line): +@Command.register('crash') +def crash(self, reactor, service, line, use_json): def callback(): raise ValueError('crash test of the API') yield None diff --git a/src/exabgp/reactor/api/command/rib.py b/src/exabgp/reactor/api/command/rib.py index 8306a6b3a..d31fc1794 100644 --- a/src/exabgp/reactor/api/command/rib.py +++ b/src/exabgp/reactor/api/command/rib.py @@ -7,6 +7,8 @@ License: 3-clause BSD. (See the COPYRIGHT file) """ +import json + from exabgp.reactor.api.command.command import Command from exabgp.reactor.api.command.limit import match_neighbors from exabgp.reactor.api.command.limit import extract_neighbors @@ -24,7 +26,43 @@ def register_rib(): pass -def _show_adjrib_callback(reactor, service, last, route_type, advertised, rib_name, extensive): +def _show_adjrib_callback(reactor, service, last, route_type, advertised, rib_name, extensive, use_json): + def to_text(key, changes): + for change in changes: + if not isinstance(change.nlri, route_type): + # log something about this drop? + continue + + msg = '%s %s %s' % ( + reactor.neighbor_name(key) if extensive else reactor.neighbor_ip(key), + '%s %s' % change.nlri.family().afi_safi(), + change.extensive() if extensive else str(change.nlri), + ) + reactor.processes.write(service, msg) + + def to_json(key, changes): + jason = {} + neighbor = reactor.neighbor(key) + neighbor_ip = reactor.neighbor_ip(key) + routes = jason.setdefault(neighbor_ip, {'routes': []})['routes'] + + if extensive: + jason[neighbor_ip].update(neighbor.to_json()) + + for change in changes: + if not isinstance(change.nlri, route_type): + # log something about this drop? + continue + + routes.append({ + "prefix": str(change.nlri.cidr.prefix()), + "family": str(change.nlri.family()).strip("()").replace(",", "") + }) + + for line in json.dumps(jason).split('\n'): + reactor.processes.write(service, line) + + def callback(): lines_per_yield = getenv().api.chunk if last in ('routes', 'extensive', 'static', 'flow', 'l2vpn'): @@ -35,51 +73,19 @@ def callback(): routes = reactor.neighor_rib(key, rib_name, advertised) while routes: changes, routes = routes[:lines_per_yield], routes[lines_per_yield:] - for change in changes: - if isinstance(change.nlri, route_type): - if extensive: - reactor.processes.write( - service, - '%s %s %s' - % ( - reactor.neighbor_name(key), - '%s %s' % change.nlri.family().afi_safi(), - change.extensive(), - ), - ) - else: - reactor.processes.write( - service, - 'neighbor %s %s %s' - % ( - reactor.neighbor_ip(key), - '%s %s' % change.nlri.family().afi_safi(), - str(change.nlri), - ), - ) + if use_json: + to_json(key, changes) + else: + to_text(key, changes) yield True reactor.processes.answer_done(service) return callback -@Command.register( - 'text', - 'show adj-rib out', - False, - [ - 'extensive', - ], -) -@Command.register( - 'text', - 'show adj-rib in', - False, - [ - 'extensive', - ], -) -def show_adj_rib(self, reactor, service, line): +@Command.register('show adj-rib out', False, ['extensive',], True) +@Command.register('show adj-rib in', False, ['extensive',], True) +def show_adj_rib(self, reactor, service, line, use_json): words = line.split() extensive = line.endswith(' extensive') try: @@ -109,17 +115,22 @@ def show_adj_rib(self, reactor, service, line): elif 'l2vpn' in words: klass = (VPLS, EVPN) + use_json = False + if 'json' in words: + words.remove('json') + use_json = True + for remove in ('show', 'adj-rib', 'adj-rib-in', 'adj-rib-out', 'in', 'out', 'extensive'): if remove in words: words.remove(remove) last = '' if not words else words[0] - callback = _show_adjrib_callback(reactor, service, last, klass, False, rib, extensive) + callback = _show_adjrib_callback(reactor, service, last, klass, False, rib, extensive, use_json) reactor.asynchronous.schedule(service, line, callback()) return True -@Command.register('text', 'flush adj-rib out') -def flush_adj_rib_out(self, reactor, service, line): +@Command.register('flush adj-rib out') +def flush_adj_rib_out(self, reactor, service, line, use_json): def callback(self, peers): self.log_message( "flushing adjb-rib out for %s" % ', '.join(peers if peers else []) if peers is not None else 'all peers' @@ -149,8 +160,8 @@ def callback(self, peers): return False -@Command.register('text', 'clear adj-rib') -def clear_adj_rib(self, reactor, service, line): +@Command.register('clear adj-rib') +def clear_adj_rib(self, reactor, service, line, use_json): def callback(self, peers, direction): self.log_message( "clearing adjb-rib-%s for %s" @@ -172,7 +183,7 @@ def callback(self, peers, direction): self.log_failure('no neighbor matching the command : %s' % command, 'warning') reactor.processes.answer_error(service) return False - words = line.split() + words = command.split() direction = 'in' if 'adj-rib-in' in words or 'in' in words else 'out' reactor.asynchronous.schedule(service, command, callback(self, peers, direction)) return True diff --git a/src/exabgp/reactor/api/command/watchdog.py b/src/exabgp/reactor/api/command/watchdog.py index 859ef2da2..268e549f4 100644 --- a/src/exabgp/reactor/api/command/watchdog.py +++ b/src/exabgp/reactor/api/command/watchdog.py @@ -14,8 +14,8 @@ def register_watchdog(): pass -@Command.register('text', 'announce watchdog') -def announce_watchdog(self, reactor, service, line): +@Command.register('announce watchdog') +def announce_watchdog(self, reactor, service, line, use_json): def callback(name): # XXX: move into Action for neighbor_name in reactor.configuration.neighbors.keys(): @@ -35,8 +35,8 @@ def callback(name): return True -@Command.register('text', 'withdraw watchdog') -def withdraw_watchdog(self, reactor, service, line): +@Command.register('withdraw watchdog') +def withdraw_watchdog(self, reactor, service, line, use_json): def callback(name): # XXX: move into Action for neighbor_name in reactor.configuration.neighbors.keys(): diff --git a/src/exabgp/reactor/loop.py b/src/exabgp/reactor/loop.py index af83b5491..289e067e2 100644 --- a/src/exabgp/reactor/loop.py +++ b/src/exabgp/reactor/loop.py @@ -418,7 +418,7 @@ def run(self): # read at least on message per process if there is some and parse it for service, command in self.processes.received(): - self.api.text(self, service, command) + self.api.process(self, service, command) sleep = 0 self.asynchronous.run()