#!/usr/bin/env python3
# Licensed under GPLv3
#
# Simple http server to allow user control of n2n edge nodes
#
# Currently only for demonstration
# - needs nicer looking html written
# - needs more json interfaces in edge
#
# Try it out with
# http://localhost:8080/
# http://localhost:8080/edge/edges
# http://localhost:8080/edge/supernodes
import argparse
import socket
import json
import socketserver
import http.server
import signal
import functools
import base64
from http import HTTPStatus
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)
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"""
# 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
# and be either an "error" or a "begin"
assert(data['_tag'] == tagstr)
if data['_type'] == 'error':
raise ValueError('Error: {}'.format(data['error']))
assert(data['_type'] == 'begin')
# 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)
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)
pages = {
"/script.js": {
"content_type": "text/javascript",
"content": """
var verbose=-1;
function rows2verbose(id, unused, data) {
row0 = data[0]
verbose = row0['traceLevel']
let div = document.getElementById(id);
div.innerHTML=verbose
}
function rows2keyvalue(id, keys, data) {
let s = "
"
data.forEach((row) => {
keys.forEach((key) => {
if (key in row) {
s += "
" + key + "
" + row[key];
}
});
});
s += "
"
let div = document.getElementById(id);
div.innerHTML=s
}
function rows2keyvalueall(id, unused, data) {
let s = "
"
data.forEach((row) => {
Object.keys(row).forEach((key) => {
s += "
" + key + "
" + row[key];
});
});
s += "
"
let div = document.getElementById(id);
div.innerHTML=s
}
function rows2table(id, columns, data) {
let s = "
"
s += "
"
columns.forEach((col) => {
s += "
" + col
});
data.forEach((row) => {
s += "
"
columns.forEach((col) => {
val = row[col]
if (typeof val === "undefined") {
val = ''
}
s += "
" + val
});
});
s += "
"
let div = document.getElementById(id);
div.innerHTML=s
}
function do_get(url, id, handler, handler_param) {
fetch(url)
.then(function (response) {
return response.json();
})
.then(function (data) {
handler(id,handler_param,data);
// update the timestamp on success
let now = Math.round(new Date().getTime() / 1000);
let time = document.getElementById('time');
time.innerHTML=now;
})
.catch(function (err) {
console.log('error: ' + err);
});
}
function do_post(url, body, id, handler, handler_param) {
fetch(url, {method:'POST', body: body})
.then(function (response) {
return response.json();
})
.then(function (data) {
handler(id,handler_param,data);
})
.catch(function (err) {
console.log('error: ' + err);
});
}
function do_stop(tracelevel) {
// FIXME: uses global in script library
fetch(nodetype + '/stop', {method:'POST'})
}
function setverbose(tracelevel) {
if (tracelevel < 0) {
tracelevel = 0;
}
// FIXME: uses global in script library
do_post(
nodetype + '/verbose', tracelevel, 'verbose',
rows2verbose, null
);
}
function refresh_setup(interval) {
var timer = setInterval(refresh_job, interval);
}
""",
},
"/": {
"content_type": "text/html; charset=utf-8",
"content": """
n2n edge management