New upstream version 3.1.1
This commit is contained in:
parent
4e9934e5ec
commit
e7b41df57b
229 changed files with 57000 additions and 12055 deletions
301
scripts/n2n-ctl
Executable file
301
scripts/n2n-ctl
Executable file
|
@ -0,0 +1,301 @@
|
|||
#!/usr/bin/env python3
|
||||
# Licensed under GPLv3
|
||||
#
|
||||
# Simple script to query the management interface of a running n2n edge node
|
||||
|
||||
import argparse
|
||||
import socket
|
||||
import json
|
||||
import collections
|
||||
|
||||
|
||||
class JsonUDP():
|
||||
"""encapsulate communication with the edge"""
|
||||
|
||||
def __init__(self, port):
|
||||
self.address = "127.0.0.1"
|
||||
self.port = port
|
||||
self.tag = 0
|
||||
self.key = None
|
||||
self.debug = False
|
||||
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
self.sock.settimeout(1)
|
||||
|
||||
def _next_tag(self):
|
||||
tagstr = str(self.tag)
|
||||
self.tag = (self.tag + 1) % 1000
|
||||
return tagstr
|
||||
|
||||
def _cmdstr(self, msgtype, cmdline):
|
||||
"""Create the full command string to send"""
|
||||
tagstr = self._next_tag()
|
||||
|
||||
options = [tagstr]
|
||||
if self.key is not None:
|
||||
options += ['1'] # Flags set for auth key field
|
||||
options += [self.key]
|
||||
optionsstr = ':'.join(options)
|
||||
|
||||
return tagstr, ' '.join((msgtype, optionsstr, cmdline))
|
||||
|
||||
def _rx(self, tagstr):
|
||||
"""Wait for rx packets"""
|
||||
|
||||
seen_begin = False
|
||||
while not seen_begin:
|
||||
# TODO: there are no timeouts with any of the recv calls
|
||||
data, _ = self.sock.recvfrom(1024)
|
||||
data = json.loads(data.decode('utf8'))
|
||||
|
||||
# TODO: We assume the first packet we get will be tagged for us
|
||||
assert(data['_tag'] == tagstr)
|
||||
|
||||
if data['_type'] == 'error':
|
||||
raise ValueError('Error: {}'.format(data['error']))
|
||||
|
||||
if data['_type'] == 'replacing':
|
||||
# a signal that we have evicted an earlier subscribe
|
||||
continue
|
||||
|
||||
if data['_type'] == 'subscribe':
|
||||
return True
|
||||
|
||||
if data['_type'] == 'begin':
|
||||
seen_begin = True
|
||||
|
||||
# Ideally, we would confirm that this is our "begin", but that
|
||||
# would need the cmd passed into this method, and that would
|
||||
# probably require parsing the cmdline passed to us :-(
|
||||
# assert(data['cmd'] == cmd)
|
||||
|
||||
continue
|
||||
|
||||
raise ValueError('Unknown data type {} from '
|
||||
'edge'.format(data['_type']))
|
||||
|
||||
result = list()
|
||||
error = None
|
||||
|
||||
while True:
|
||||
data, _ = self.sock.recvfrom(1024)
|
||||
data = json.loads(data.decode('utf8'))
|
||||
|
||||
if data['_tag'] != tagstr:
|
||||
# this packet is not for us, ignore it
|
||||
continue
|
||||
|
||||
if data['_type'] == 'error':
|
||||
# we still expect an end packet, so save the error
|
||||
error = ValueError('Error: {}'.format(data['error']))
|
||||
continue
|
||||
|
||||
if data['_type'] == 'end':
|
||||
if error:
|
||||
raise error
|
||||
return result
|
||||
|
||||
if data['_type'] != 'row':
|
||||
raise ValueError('Unknown data type {} from '
|
||||
'edge'.format(data['_type']))
|
||||
|
||||
# remove our boring metadata
|
||||
del data['_tag']
|
||||
del data['_type']
|
||||
|
||||
if self.debug:
|
||||
print(data)
|
||||
|
||||
result.append(data)
|
||||
|
||||
def _call(self, msgtype, cmdline):
|
||||
"""Perform a rpc call"""
|
||||
tagstr, msgstr = self._cmdstr(msgtype, cmdline)
|
||||
self.sock.sendto(msgstr.encode('utf8'), (self.address, self.port))
|
||||
return self._rx(tagstr)
|
||||
|
||||
def read(self, cmdline):
|
||||
return self._call('r', cmdline)
|
||||
|
||||
def write(self, cmdline):
|
||||
return self._call('w', cmdline)
|
||||
|
||||
def sub(self, cmdline):
|
||||
return self._call('s', cmdline)
|
||||
|
||||
def readevent(self):
|
||||
self.sock.settimeout(3600)
|
||||
|
||||
data, _ = self.sock.recvfrom(1024)
|
||||
data = json.loads(data.decode('utf8'))
|
||||
# assert(data['_tag'] == tagstr)
|
||||
assert(data['_type'] == 'event')
|
||||
|
||||
del data['_tag']
|
||||
del data['_type']
|
||||
return data
|
||||
|
||||
|
||||
def str_table(rows, columns, orderby):
|
||||
"""Given an array of dicts, do a simple table print"""
|
||||
result = list()
|
||||
widths = collections.defaultdict(lambda: 0)
|
||||
|
||||
if len(rows) == 0:
|
||||
# No data to show, be sure not to truncate the column headings
|
||||
for col in columns:
|
||||
widths[col] = len(col)
|
||||
else:
|
||||
for row in rows:
|
||||
for col in columns:
|
||||
if col in row:
|
||||
widths[col] = max(widths[col], len(str(row[col])))
|
||||
|
||||
for col in columns:
|
||||
if widths[col] == 0:
|
||||
widths[col] = 1
|
||||
result += "{:{}.{}} ".format(col, widths[col], widths[col])
|
||||
result += "\n"
|
||||
|
||||
if orderby is not None:
|
||||
rows = sorted(rows, key=lambda row: row.get(orderby, 0))
|
||||
|
||||
for row in rows:
|
||||
for col in columns:
|
||||
if col in row:
|
||||
data = row[col]
|
||||
else:
|
||||
data = ''
|
||||
result += "{:{}} ".format(data, widths[col])
|
||||
result += "\n"
|
||||
|
||||
return ''.join(result)
|
||||
|
||||
|
||||
def subcmd_show_supernodes(rpc, args):
|
||||
rows = rpc.read('supernodes')
|
||||
columns = [
|
||||
'version',
|
||||
'current',
|
||||
'macaddr',
|
||||
'sockaddr',
|
||||
'uptime',
|
||||
]
|
||||
|
||||
return str_table(rows, columns, args.orderby)
|
||||
|
||||
|
||||
def subcmd_show_edges(rpc, args):
|
||||
rows = rpc.read('edges')
|
||||
columns = [
|
||||
'mode',
|
||||
'ip4addr',
|
||||
'macaddr',
|
||||
'sockaddr',
|
||||
'desc',
|
||||
]
|
||||
|
||||
return str_table(rows, columns, args.orderby)
|
||||
|
||||
|
||||
def subcmd_show_help(rpc, args):
|
||||
result = 'Commands with pretty-printed output:\n\n'
|
||||
for name, cmd in subcmds.items():
|
||||
result += "{:12} {}\n".format(name, cmd['help'])
|
||||
|
||||
result += "\n"
|
||||
result += "Possble remote commands:\n"
|
||||
result += "(those without a pretty-printer will pass-through)\n\n"
|
||||
rows = rpc.read('help')
|
||||
for row in rows:
|
||||
result += "{:12} {}\n".format(row['cmd'], row['help'])
|
||||
return result
|
||||
|
||||
|
||||
subcmds = {
|
||||
'help': {
|
||||
'func': subcmd_show_help,
|
||||
'help': 'Show available commands',
|
||||
},
|
||||
'supernodes': {
|
||||
'func': subcmd_show_supernodes,
|
||||
'help': 'Show the list of supernodes',
|
||||
},
|
||||
'edges': {
|
||||
'func': subcmd_show_edges,
|
||||
'help': 'Show the list of edges/peers',
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def subcmd_default(rpc, args):
|
||||
"""Just pass command through to edge"""
|
||||
cmdline = ' '.join([args.cmd] + args.args)
|
||||
if args.write:
|
||||
rows = rpc.write(cmdline)
|
||||
elif args.read:
|
||||
rows = rpc.read(cmdline)
|
||||
elif args.sub:
|
||||
if not rpc.sub(cmdline):
|
||||
raise ValueError('Could not subscribe')
|
||||
while True:
|
||||
event = rpc.readevent()
|
||||
# FIXME: violates layering..
|
||||
print(json.dumps(event, sort_keys=True, indent=4))
|
||||
else:
|
||||
raise ValueError('Unknown request type')
|
||||
return json.dumps(rows, sort_keys=True, indent=4)
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser(
|
||||
description='Query the running local n2n edge')
|
||||
ap.add_argument('-t', '--mgmtport', action='store', default=5644,
|
||||
help='Management Port (default=5644)', type=int)
|
||||
ap.add_argument('-k', '--key', action='store',
|
||||
help='Password for mgmt commands')
|
||||
ap.add_argument('-d', '--debug', action='store_true',
|
||||
help='Also show raw internal data')
|
||||
ap.add_argument('--raw', action='store_true',
|
||||
help='Force cmd to avoid any pretty printing')
|
||||
ap.add_argument('--orderby', action='store',
|
||||
help='Hint to a pretty printer on how to sort')
|
||||
|
||||
group = ap.add_mutually_exclusive_group()
|
||||
group.add_argument('--read', action='store_true',
|
||||
help='Make a read request (default)')
|
||||
group.add_argument('--write', action='store_true',
|
||||
help='Make a write request (only to non pretty'
|
||||
'printed cmds)')
|
||||
group.add_argument('--sub', action='store_true',
|
||||
help='Make a subscribe request')
|
||||
|
||||
ap.add_argument('cmd', action='store',
|
||||
help='Command to run (try "help" for list)')
|
||||
ap.add_argument('args', action='store', nargs="*",
|
||||
help='Optional args for the command')
|
||||
|
||||
args = ap.parse_args()
|
||||
|
||||
if not args.read and not args.write and not args.sub:
|
||||
args.read = True
|
||||
|
||||
if args.raw or (args.cmd not in subcmds):
|
||||
func = subcmd_default
|
||||
else:
|
||||
func = subcmds[args.cmd]['func']
|
||||
|
||||
rpc = JsonUDP(args.mgmtport)
|
||||
rpc.debug = args.debug
|
||||
rpc.key = args.key
|
||||
|
||||
try:
|
||||
result = func(rpc, args)
|
||||
except socket.timeout as e:
|
||||
print(e)
|
||||
exit(1)
|
||||
|
||||
print(result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
Loading…
Add table
Add a link
Reference in a new issue