diff --git a/src/exabgp/bgp/neighbor.py b/src/exabgp/bgp/neighbor.py index 429ccb9ad..ede2a2cd1 100644 --- a/src/exabgp/bgp/neighbor.py +++ b/src/exabgp/bgp/neighbor.py @@ -173,19 +173,31 @@ def clear_rib(self): self.messages = deque() self.refresh = deque() - def name(self): + def name(self, json=False): if self['capability']['multi-session']: session = '/'.join("%s-%s" % (afi.name(), safi.name()) for (afi, safi) in self.families()) else: session = 'in-open' - return "neighbor %s local-ip %s local-as %s peer-as %s router-id %s family-allowed %s" % ( - self['peer-address'], - self['local-address'] if self['peer-address'] is not None else 'auto', - self['local-as'] if self['local-as'] is not None else 'auto', - self['peer-as'] if self['peer-as'] is not None else 'auto', - self['router-id'], - session, - ) + data = { + "neighbor": self.peer_address, + "local-ip": self.local_address if self.peer_address is not None else 'auto', + "local-as": self.local_as if self.local_as is not None else 'auto', + "peer-as": self.peer_as if self.peer_as is not None else 'auto', + "router-id": self.router_id, + "family-allowed": session + } + + if json: + return data + else: + return "neighbor %s local-ip %s local-as %s peer-as %s router-id %s family-allowed %s" % ( + data["neighbor"], + data["local-ip"], + data["local-as"], + data["peer-as"], + data["router-id"], + data["family-allowed"] + ) def families(self): # this list() is important .. as we use the function to modify self._families diff --git a/src/exabgp/reactor/api/command/neighbor.py b/src/exabgp/reactor/api/command/neighbor.py index 3a4e4685f..d0b402862 100644 --- a/src/exabgp/reactor/api/command/neighbor.py +++ b/src/exabgp/reactor/api/command/neighbor.py @@ -32,38 +32,28 @@ def _pr(value): return '%s' % value -def _addpath(send, receive): - if send and receive: - return "send/receive" - if send: - return "send" - if receive: - return "receive" - return "disabled" - - class Neighbor(object): extensive_kv = ' %-20s %15s %15s %15s' extensive_template = """\ Neighbor %(peer-address)s - Session Local + Session Local %(local-address)s %(state)s %(duration)s - Setup Local Remote + Setup Local Remote %(as)s %(id)s %(hold)s - Capability Local Remote + Capability Local Remote %(capabilities)s - Families Local Remote Add-Path + Families Local Remote Add-Path %(families)s - Message Statistic Sent Received + Message Statistic Sent Received %(messages)s """.replace( '\t', ' ' @@ -73,99 +63,98 @@ class Neighbor(object): summary_template = '%-15s %-7s %9s %-12s %10d %10d' @classmethod - def as_dict(cls, answer): - up = answer['duration'] - - formated = { - 'state': 'up' if up else 'down', - 'duration': answer['duration'] if up else answer['down'], - 'fsm': answer['state'], - 'local': { - 'capabilities': {}, - 'families': {}, - 'add-path': {}, - }, - 'peer': { - 'capabilities': {}, - 'families': {}, - 'add-path': {}, - }, - 'messages': {'sent': {}, 'received': {}}, - 'capabilities': [], - 'families': [], - 'add-path': {}, - } - - for (a, s), (l, p, aps, apr) in answer['families'].items(): - k = '%s %s' % (a, s) - formated['local']['families'][k] = l - formated['peer']['families'][k] = p - formated['local']['add-path'][k] = aps - formated['peer']['add-path'][k] = apr - if l and p: - formated['families'].append(k) - formated['add-path'][k] = _addpath(aps, apr) - - for k, (l, p) in answer['capabilities'].items(): - formated['local']['capabilities'][k] = l - formated['peer']['capabilities'][k] = p - if l and p: - formated['capabilities'].append(k) - - for k, (s, r) in answer['messages'].items(): - formated['messages']['sent'][k] = s - formated['messages']['received'][k] = r - - formated['local']['address'] = answer['local-address'] - formated['local']['as'] = answer['local-as'] - formated['local']['id'] = answer['local-id'] - formated['local']['hold'] = answer['local-hold'] - - formated['peer']['address'] = answer['peer-address'] - formated['peer']['as'] = answer['peer-as'] - formated['peer']['id'] = answer['peer-id'] - formated['peer']['hold'] = answer['peer-hold'] - - return formated - - @classmethod - def extensive(cls, answer): + def extensive(cls, answer, output_format='text'): if answer['duration']: duration = cls.extensive_kv % ('up for', timedelta(seconds=answer['duration']), '', '') else: duration = cls.extensive_kv % ('down for', timedelta(seconds=answer['down']), '', '') - formated = { - 'peer-address': answer['peer-address'], - 'local-address': cls.extensive_kv % ('local', answer['local-address'], '', ''), - 'state': cls.extensive_kv % ('state', answer['state'], '', ''), - 'duration': duration, - 'as': cls.extensive_kv % ('AS', answer['local-as'], _pr(answer['peer-as']), ''), - 'id': cls.extensive_kv % ('ID', answer['local-id'], _pr(answer['peer-id']), ''), - 'hold': cls.extensive_kv % ('hold-time', answer['local-hold'], _pr(answer['peer-hold']), ''), - 'capabilities': '\n'.join( - cls.extensive_kv % ('%s:' % k, _en(l), _en(p), '') for k, (l, p) in answer['capabilities'].items() - ), - 'families': '\n'.join( - cls.extensive_kv % ('%s %s:' % (a, s), _en(l), _en(r), _addpath(aps, apr)) - for (a, s), (l, r, apr, aps) in answer['families'].items() - ), - 'messages': '\n'.join( - cls.extensive_kv % ('%s:' % k, str(s), str(r), '') for k, (s, r) in answer['messages'].items() - ), - } - - return cls.extensive_template % formated + + if output_format == 'text': + formated = { + 'peer-address': answer['peer-address'], + 'local-address': cls.extensive_kv % ('local', answer['local-address'], '', ''), + 'state': cls.extensive_kv % ('state', answer['state'], '', ''), + 'duration': duration, + 'as': cls.extensive_kv % ('AS', answer['local-as'], _pr(answer['peer-as']), ''), + 'id': cls.extensive_kv % ('ID', answer['local-id'], _pr(answer['peer-id']), ''), + 'hold': cls.extensive_kv % ('hold-time', answer['local-hold'], _pr(answer['peer-hold']), ''), + 'capabilities': '\n'.join( + cls.extensive_kv % ('%s:' % k, _en(l), _en(p), '') for k, (l, p) in answer['capabilities'].items() + ), + 'families': '\n'.join( + cls.extensive_kv % ('%s %s:' % (a, s), _en(l), _en(r), _en(p)) + for (a, s), (l, r, p) in answer['families'].items() + ), + 'messages': '\n'.join( + cls.extensive_kv % ('%s:' % k, str(s), str(r), '') for k, (s, r) in answer['messages'].items() + ), + } + return cls.extensive_template % formated + else: + json_output = { + "Neighbor": answer['peer-address'], + "Session": { + "Local": answer['local-address'], + "State": answer['state'], + "Up For": str(timedelta(seconds=answer['duration'])) + }, + "Setup": { + "AS": { + "Local": answer['local-as'], + "Remote": answer['peer-as'] + }, + "ID": { + "Local": answer['local-id'], + "Remote": answer['peer-id'] + }, + "hold-time": { + "Local": answer['local-hold'], + "Remote": answer['peer-hold'] + } + }, + "Capability": { + k: { + "Local": _en(l), + "Remote": _en(p) + } for k, (l, p) in answer['capabilities'].items() + }, + "Families": [ + { + "Family": f"{a} {s}", + "Local": _en(l), + "Remote": _en(r), + "Add-Path": _en(p) + } for (a, s), (l, r, p) in answer['families'].items() + ], + "Message Statistic": { + k: { + "Sent": s, + "Received": r + } for k, (s, r) in answer['messages'].items() + } + } + return json_output @classmethod - def summary(cls, answer): - return cls.summary_template % ( - answer['peer-address'], - _pr(answer['peer-as']), - timedelta(seconds=answer['duration']) if answer['duration'] else 'down', - answer['state'].lower(), - answer['messages']['update'][0], - answer['messages']['update'][1], - ) + def summary(cls, answer, output_format='text'): + if output_format == 'text': + return cls.summary_template % ( + answer['peer-address'], + _pr(answer['peer-as']), + timedelta(seconds=answer['duration']) if answer['duration'] else 'down', + answer['state'].lower(), + answer['messages']['update'][0], + answer['messages']['update'][1], + ) + else : + return { + "Peer": answer['peer-address'], + "AS": answer['peer-as'], + "Up/Down": str(timedelta(seconds=answer['duration'])) if answer['duration'] else 'down', + "State": answer['state'].lower(), + "Sent": answer['messages']['update'][0], + "Recvd": answer['messages']['update'][1], + } @Command.register('text', 'teardown', True) @@ -194,14 +183,15 @@ def teardown(self, reactor, service, line): return False -@Command.register('text', 'show neighbor', False, ['summary', 'extensive', 'configuration', 'json']) +@Command.register('text', 'show neighbor', False, ['summary', 'extensive', 'configuration']) +@Command.register('text', 'show neighbor json', False, ['summary', 'extensive']) def show_neighbor(self, reactor, service, command): words = command.split() extensive = 'extensive' in words configuration = 'configuration' in words summary = 'summary' in words - jason = 'json' in words + json_output = 'json' in words if summary: words.remove('summary') @@ -209,7 +199,7 @@ def show_neighbor(self, reactor, service, command): words.remove('extensive') if configuration: words.remove('configuration') - if jason: + if json_output: words.remove('json') limit = words[-1] if words[-1] != 'neighbor' else '' @@ -226,37 +216,44 @@ def callback_configuration(): yield True reactor.processes.answer_done(service) - def callback_json(): - p = [] - for peer_name in reactor.peers(): - p.append(Neighbor.as_dict(reactor.neighbor_cli_data(peer_name))) - for line in json.dumps(p).split('\n'): - reactor.processes.write(service, line) - yield True - reactor.processes.answer_done(service) - def callback_extensive(): + peers_extensive_info = [] + output_format = 'json' if json_output else 'text' for peer_name in reactor.peers(): if limit and limit not in reactor.neighbor_name(peer_name): continue - for line in Neighbor.extensive(reactor.neighbor_cli_data(peer_name)).split('\n'): - reactor.processes.write(service, line) - yield True + extensive_info = Neighbor.extensive(reactor.neighbor_cli_data(peer_name), output_format) + if output_format == 'text': + for line in extensive_info.split('\n'): + reactor.processes.write(service, line) + else : + peers_extensive_info.append(extensive_info) + yield True + if output_format == 'json': + reactor.processes.write(service, json.dumps(peers_extensive_info, indent=4) ) + reactor.processes.answer_done(service) def callback_summary(): - reactor.processes.write(service, Neighbor.summary_header) - for peer_name in reactor.peers(): + peers_summary = [] + output_format = 'json' if json_output else 'text' + + if output_format == 'text': + reactor.processes.write(service, Neighbor.summary_header) + for peer_name in reactor.established_peers(): if limit and limit != reactor.neighbor_ip(peer_name): continue - for line in Neighbor.summary(reactor.neighbor_cli_data(peer_name)).split('\n'): - reactor.processes.write(service, line) - yield True - reactor.processes.answer_done(service) + summary_info = Neighbor.summary(reactor.neighbor_cli_data(peer_name), output_format) + if output_format == 'text': + for line in summary_info.split('\n'): + reactor.processes.write(service, line) + else : + peers_summary.append(summary_info) + yield True - if jason: - reactor.asynchronous.schedule(service, command, callback_json()) - return True + if output_format == 'json': + reactor.processes.write(service, json.dumps(peers_summary, indent=4) ) + reactor.processes.answer_done(service) if summary: reactor.asynchronous.schedule(service, command, callback_summary()) diff --git a/src/exabgp/reactor/api/command/rib.py b/src/exabgp/reactor/api/command/rib.py index 8306a6b3a..b03623da5 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 @@ -23,70 +25,88 @@ 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, json_output): def callback(): lines_per_yield = getenv().api.chunk if last in ('routes', 'extensive', 'static', 'flow', 'l2vpn'): peers = reactor.peers() else: peers = [n for n in reactor.peers() if 'neighbor %s' % last in n] + + output = {} for key in peers: 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(), - ), - ) + neighbor_ip = reactor.neighbor_ip(key) + if json_output: + if neighbor_ip not in output: + output[neighbor_ip] = { + "routes": [] + } + if extensive: + neighbor_data = reactor.neighbor_name(key, json=True) + output[neighbor_ip].update({ + "local-ip": str(neighbor_data['local-ip']), + "local-as": neighbor_data['local-as'], + "peer-as": neighbor_data['peer-as'], + "router-id": str(neighbor_data['router-id']), + "family-allowed": neighbor_data['family-allowed'], + }) + + route_entry = { + "prefix": str(change.nlri.cidr.prefix()), + "family": str(change.nlri.family()).strip("()").replace(",", "") + } + output[neighbor_ip]["routes"].append(route_entry) else: - reactor.processes.write( - service, - 'neighbor %s %s %s' - % ( - reactor.neighbor_ip(key), - '%s %s' % change.nlri.family().afi_safi(), - str(change.nlri), - ), - ) + if extensive: + reactor.processes.write( + service, + '%s %s %s' + % (reactor.neighbor_name(key), '%s %s' % change.nlri.family(), change.extensive()), + ) + else: + reactor.processes.write( + service, + 'neighbor %s %s %s' + % (reactor.neighbor_ip(key), '%s %s' % change.nlri.family(), str(change.nlri)), + ) yield True + + if json_output: + reactor.processes.write(service, json.dumps(output, indent=4)) + 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', - ], -) +@Command.register('text', 'show adj-rib out', False, ['extensive',]) +@Command.register('text', 'show adj-rib in', False, ['extensive',]) +@Command.register('text', 'show adj-rib out json', False, ['extensive',]) +@Command.register('text', 'show adj-rib in json', False, ['extensive',]) def show_adj_rib(self, reactor, service, line): words = line.split() extensive = line.endswith(' extensive') + json_output = True if 'json' in words else False try: rib = words[2] - if not rib in ('in', 'out'): + if not rib in ('in', 'out', 'json'): reactor.processes.answer_error(service) return False + # Accept 'adj-rib-out json' and 'adj-rib-in json' commands + if rib =='json': + if words[1] == 'adj-rib-in': + rib = 'in' + elif words[1] == 'adj-rib-out': + rib = 'out' + else: + reactor.processes.answer_error(service) + return False + except IndexError: if words[1] == 'adj-rib-in': rib = 'in' @@ -109,11 +129,11 @@ def show_adj_rib(self, reactor, service, line): elif 'l2vpn' in words: klass = (VPLS, EVPN) - for remove in ('show', 'adj-rib', 'adj-rib-in', 'adj-rib-out', 'in', 'out', 'extensive'): + for remove in ('show', 'adj-rib', 'adj-rib-in', 'adj-rib-out', 'in', 'out', 'extensive', 'json'): 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, json_output) reactor.asynchronous.schedule(service, line, callback()) return True diff --git a/src/exabgp/reactor/loop.py b/src/exabgp/reactor/loop.py index af83b5491..7342ac500 100644 --- a/src/exabgp/reactor/loop.py +++ b/src/exabgp/reactor/loop.py @@ -172,12 +172,16 @@ def neighbor(self, peer_name): return return peer.neighbor - def neighbor_name(self, peer_name): + def neighbor_name(self, peer_name, json=False): peer = self._peers.get(peer_name, None) if not peer: log.critical('could not find referenced peer', 'reactor') return "" - return peer.neighbor.name() + if json: + return peer.neighbor.name(json=True) + else: + return peer.neighbor.name() + def neighbor_ip(self, peer_name): peer = self._peers.get(peer_name, None)