Skip to content

Commit

Permalink
work to facilitate generic json command
Browse files Browse the repository at this point in the history
  • Loading branch information
thomas-mangin committed Oct 17, 2024
1 parent ec4f83e commit 965abdc
Show file tree
Hide file tree
Showing 13 changed files with 490 additions and 428 deletions.
31 changes: 21 additions & 10 deletions src/exabgp/application/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,12 @@


class AnswerStream:
done = '\n%s\n' % Answer.done
error = '\n%s\n' % Answer.error
shutdown = '\n%s\n' % Answer.error
text_done = '\n%s\n' % Answer.text_done
text_error = '\n%s\n' % Answer.text_error
text_shutdown = '\n%s\n' % Answer.text_error
json_done = '\n%s\n' % Answer.json_done
json_error = '\n%s\n' % Answer.json_error
json_shutdown = '\n%s\n' % Answer.json_error
buffer_size = Answer.buffer_size + 2


Expand Down Expand Up @@ -174,13 +177,21 @@ def cmdline(cmdarg):
break

# we read some data but it is not ending by a new line (ie: not a command completion)
if rbuffer[-1] != 10: # \n
if rbuffer[-1] != ord('\n'):
continue
if AnswerStream.done.endswith(rbuffer.decode()[-len(AnswerStream.done) :]):

if AnswerStream.done.endswith(rbuffer.decode()[-len(AnswerStream.text_done) :]):
break
if AnswerStream.error.endswith(rbuffer.decode()[-len(AnswerStream.text_error) :]):
break
if AnswerStream.shutdown.endswith(rbuffer.decode()[-len(AnswerStream.text_shutdown) :]):
break

if AnswerStream.done.endswith(rbuffer.decode()[-len(AnswerStream.json_done) :]):
break
if AnswerStream.error.endswith(rbuffer.decode()[-len(AnswerStream.error) :]):
if AnswerStream.error.endswith(rbuffer.decode()[-len(AnswerStream.json_error) :]):
break
if AnswerStream.shutdown.endswith(rbuffer.decode()[-len(AnswerStream.shutdown) :]):
if AnswerStream.shutdown.endswith(rbuffer.decode()[-len(AnswerStream.json_shutdown) :]):
break

renamed = ['']
Expand Down Expand Up @@ -293,15 +304,15 @@ def cmdline(cmdarg):
while b'\n' in buf:
line, buf = buf.split(b'\n', 1)
string = line.decode()
if string == Answer.done:
if string == Answer.text_done or string == Answer.json_done:
done = True
break
if string == Answer.shutdown:
if string == Answer.text_shutdown or string == Answer.json_shutdown:
sys.stderr.write('ExaBGP is shutting down, command aborted\n')
sys.stderr.flush()
done = True
break
if string == Answer.error:
if string == Answer.text_error or string == Answer.json_error:
done = True
sys.stderr.write('ExaBGP returns an error (see ExaBGP\'s logs for more information)\n')
sys.stderr.write('use help for a list of available commands\n')
Expand Down
3 changes: 2 additions & 1 deletion src/exabgp/application/validate.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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')

Expand Down
179 changes: 2 additions & 177 deletions src/exabgp/bgp/neighbor.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

from exabgp.rib import RIB

from exabgp.reactor.api.command.neighbor import NeighborTemplate

# class Section(dict):
# name = ''
Expand Down Expand Up @@ -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']
Expand All @@ -490,4 +315,4 @@ def remove_self(self, changes):
return change

def __str__(self):
return self.string(False)
return NeighborTemplate.configuration(self, False)
22 changes: 20 additions & 2 deletions src/exabgp/reactor/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,29 @@ 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.split(' '):
return self.json(reactor, service, command)
if 'text' in command.split(' '):
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)
reactor.processes.answer_error(service)
return self.callback['text'][registered](self, reactor, service, command, False)
reactor.processes.answer_text_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_json_error(service)
log.warning('command from process not understood : %s' % command, 'api')
return False

Expand Down
Loading

0 comments on commit 965abdc

Please sign in to comment.