Merge pull request #42 from mweinelt/master
pep8, batadv-vis socket support, batman gateway handling refactoredinfluxdb
commit
30670feb31
|
@ -3,4 +3,4 @@ language: python
|
||||||
python:
|
python:
|
||||||
- "3.4"
|
- "3.4"
|
||||||
install: "pip install pep8"
|
install: "pip install pep8"
|
||||||
script: "pep8 *.py"
|
script: "pep8 *.py lib/*.py"
|
||||||
|
|
|
@ -50,7 +50,7 @@ def main(params):
|
||||||
|
|
||||||
bm = list(map(lambda d:
|
bm = list(map(lambda d:
|
||||||
(d.vis_data(True), d.gateway_list()),
|
(d.vis_data(True), d.gateway_list()),
|
||||||
map(Batman, params['mesh'])))
|
map(Batman, params['mesh'], params['alfred_sock'])))
|
||||||
for vis_data, gateway_list in bm:
|
for vis_data, gateway_list in bm:
|
||||||
nodes.import_mesh_ifs_vis_data(nodedb['nodes'], vis_data)
|
nodes.import_mesh_ifs_vis_data(nodedb['nodes'], vis_data)
|
||||||
nodes.import_vis_clientcount(nodedb['nodes'], vis_data)
|
nodes.import_vis_clientcount(nodedb['nodes'], vis_data)
|
||||||
|
|
|
@ -32,11 +32,13 @@ class NodeRRD(RRD):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def imagename(self):
|
def imagename(self):
|
||||||
return "{basename}.png".format(basename=os.path.basename(self.filename).rsplit('.', 2)[0])
|
return "{basename}.png".format(
|
||||||
|
basename=os.path.basename(self.filename).rsplit('.', 2)[0])
|
||||||
|
|
||||||
# TODO: fix this, python does not support function overloading
|
# TODO: fix this, python does not support function overloading
|
||||||
def update(self):
|
def update(self):
|
||||||
super().update({'upstate': int(self.node['flags']['online']), 'clients': self.node['statistics']['clients']})
|
super().update({'upstate': int(self.node['flags']['online']),
|
||||||
|
'clients': self.node['statistics']['clients']})
|
||||||
|
|
||||||
def graph(self, directory, timeframe):
|
def graph(self, directory, timeframe):
|
||||||
"""
|
"""
|
||||||
|
|
23
lib/RRD.py
23
lib/RRD.py
|
@ -83,9 +83,11 @@ class RRD(object):
|
||||||
info = self.info()
|
info = self.info()
|
||||||
if set(ds_list) - set(info['ds'].values()) != set():
|
if set(ds_list) - set(info['ds'].values()) != set():
|
||||||
for ds in ds_list:
|
for ds in ds_list:
|
||||||
if ds.name in info['ds'] and ds.type != info['ds'][ds.name].type:
|
if ds.name in info['ds'] and\
|
||||||
raise RRDIncompatibleException("%s is %s but should be %s" %
|
ds.type != info['ds'][ds.name].type:
|
||||||
(ds.name, ds.type, info['ds'][ds.name].type))
|
raise RRDIncompatibleException(
|
||||||
|
"{} is {} but should be {}".format(
|
||||||
|
ds.name, ds.type, info['ds'][ds.name].type))
|
||||||
else:
|
else:
|
||||||
raise RRDOutdatedException()
|
raise RRDOutdatedException()
|
||||||
|
|
||||||
|
@ -108,8 +110,10 @@ class RRD(object):
|
||||||
if ds.name in info['ds']:
|
if ds.name in info['ds']:
|
||||||
old_ds = info['ds'][ds.name]
|
old_ds = info['ds'][ds.name]
|
||||||
if info['ds'][ds.name].type != ds.type:
|
if info['ds'][ds.name].type != ds.type:
|
||||||
raise RuntimeError('Cannot convert existing DS "%s" from type "%s" to "%s"' %
|
raise RuntimeError(
|
||||||
(ds.name, old_ds.type, ds.type))
|
"Cannot convert existing DS '{}'"
|
||||||
|
"from type '{}' to '{}'".format(
|
||||||
|
ds.name, old_ds.type, ds.type))
|
||||||
ds.index = old_ds.index
|
ds.index = old_ds.index
|
||||||
new_ds[ds.index] = ds
|
new_ds[ds.index] = ds
|
||||||
else:
|
else:
|
||||||
|
@ -237,7 +241,8 @@ class RRD(object):
|
||||||
for line in out.splitlines():
|
for line in out.splitlines():
|
||||||
base = info
|
base = info
|
||||||
for match in self._info_regex.finditer(line):
|
for match in self._info_regex.finditer(line):
|
||||||
section, key, name, value = match.group("section", "key", "name", "value")
|
section, key, name, value = match.group(
|
||||||
|
"section", "key", "name", "value")
|
||||||
if section and key:
|
if section and key:
|
||||||
try:
|
try:
|
||||||
key = int(key)
|
key = int(key)
|
||||||
|
@ -258,7 +263,8 @@ class RRD(object):
|
||||||
base[name] = value
|
base[name] = value
|
||||||
dss = {}
|
dss = {}
|
||||||
for name, ds in info['ds'].items():
|
for name, ds in info['ds'].items():
|
||||||
ds_obj = DS(name, ds['type'], ds['minimal_heartbeat'], ds['min'], ds['max'])
|
ds_obj = DS(name, ds['type'], ds['minimal_heartbeat'],
|
||||||
|
ds['min'], ds['max'])
|
||||||
ds_obj.index = ds['index']
|
ds_obj.index = ds['index']
|
||||||
ds_obj.last_ds = ds['last_ds']
|
ds_obj.last_ds = ds['last_ds']
|
||||||
ds_obj.value = ds['value']
|
ds_obj.value = ds['value']
|
||||||
|
@ -267,7 +273,8 @@ class RRD(object):
|
||||||
info['ds'] = dss
|
info['ds'] = dss
|
||||||
rras = []
|
rras = []
|
||||||
for rra in info['rra'].values():
|
for rra in info['rra'].values():
|
||||||
rras.append(RRA(rra['cf'], rra['xff'], rra['pdp_per_row'], rra['rows']))
|
rras.append(RRA(rra['cf'], rra['xff'],
|
||||||
|
rra['pdp_per_row'], rra['rows']))
|
||||||
info['rra'] = rras
|
info['rra'] = rras
|
||||||
self._cached_info = info
|
self._cached_info = info
|
||||||
return info
|
return info
|
||||||
|
|
|
@ -8,8 +8,12 @@ class Batman(object):
|
||||||
Bindings for B.A.T.M.A.N. Advanced
|
Bindings for B.A.T.M.A.N. Advanced
|
||||||
commandline interface "batctl"
|
commandline interface "batctl"
|
||||||
"""
|
"""
|
||||||
def __init__(self, mesh_interface='bat0'):
|
def __init__(self, mesh_interface='bat0', alfred_sockpath=None):
|
||||||
self.mesh_interface = mesh_interface
|
self.mesh_interface = mesh_interface
|
||||||
|
self.alfred_sock = alfred_sockpath
|
||||||
|
|
||||||
|
# compile regular expressions only once on startup
|
||||||
|
self.mac_addr_pattern = re.compile(r'(([a-z0-9]{2}:){5}[a-z0-9]{2})')
|
||||||
|
|
||||||
def vis_data(self, batadv_vis=False):
|
def vis_data(self, batadv_vis=False):
|
||||||
vds = self.vis_data_batctl_legacy()
|
vds = self.vis_data_batctl_legacy()
|
||||||
|
@ -44,8 +48,10 @@ class Batman(object):
|
||||||
Parse "batadv-vis -i <mesh_interface> -f json"
|
Parse "batadv-vis -i <mesh_interface> -f json"
|
||||||
into an array of dictionaries.
|
into an array of dictionaries.
|
||||||
"""
|
"""
|
||||||
output = subprocess.check_output(
|
cmd = ['batadv-vis', '-i', self.mesh_interface, '-f', 'json']
|
||||||
['batadv-vis', '-i', self.mesh_interface, '-f', 'json'])
|
if self.alfred_sock:
|
||||||
|
cmd.extend(['-u', self.alfred_sock])
|
||||||
|
output = subprocess.check_output(cmd)
|
||||||
lines = output.splitlines()
|
lines = output.splitlines()
|
||||||
return self.vis_data_helper(lines)
|
return self.vis_data_helper(lines)
|
||||||
|
|
||||||
|
@ -57,36 +63,35 @@ class Batman(object):
|
||||||
output = subprocess.check_output(
|
output = subprocess.check_output(
|
||||||
['batctl', '-m', self.mesh_interface, 'gwl', '-n'])
|
['batctl', '-m', self.mesh_interface, 'gwl', '-n'])
|
||||||
output_utf8 = output.decode('utf-8')
|
output_utf8 = output.decode('utf-8')
|
||||||
lines = output_utf8.splitlines()
|
rows = output_utf8.splitlines()
|
||||||
|
|
||||||
own_mac = re.match(r"^.*MainIF/MAC: [^/]+/([0-9a-f:]+).*$",
|
|
||||||
lines[0]).group(1)
|
|
||||||
|
|
||||||
gateways = []
|
gateways = []
|
||||||
gw_mode = self.gateway_mode()
|
|
||||||
if gw_mode['mode'] == 'server':
|
|
||||||
gateways.append(own_mac)
|
|
||||||
|
|
||||||
for line in lines:
|
# local gateway
|
||||||
gw_line = re.match(r"^(?:=>)? +([0-9a-f:]+) ", line)
|
header = rows.pop(0)
|
||||||
if gw_line:
|
mode, bandwidth = self.gateway_mode()
|
||||||
gateways.append(gw_line.group(1))
|
if mode == 'server':
|
||||||
|
local_gw_mac = self.mac_addr_pattern.search(header).group(0)
|
||||||
|
gateways.append(local_gw_mac)
|
||||||
|
|
||||||
|
# remote gateway(s)
|
||||||
|
for row in rows:
|
||||||
|
match = self.mac_addr_pattern.search(row)
|
||||||
|
if match:
|
||||||
|
gateways.append(match.group(1))
|
||||||
|
|
||||||
return gateways
|
return gateways
|
||||||
|
|
||||||
def gateway_mode(self):
|
def gateway_mode(self):
|
||||||
"""
|
"""
|
||||||
Parse "batctl -m <mesh_interface> gw"
|
Parse "batctl -m <mesh_interface> gw"
|
||||||
|
return: tuple mode, bandwidth, if mode != server then bandwidth is None
|
||||||
"""
|
"""
|
||||||
output = subprocess.check_output(
|
output = subprocess.check_output(
|
||||||
['batctl', '-m', self.mesh_interface, 'gw'])
|
['batctl', '-m', self.mesh_interface, 'gw'])
|
||||||
elements = output.decode("utf-8").split()
|
chunks = output.decode("utf-8").split()
|
||||||
mode = elements[0]
|
|
||||||
if mode == 'server':
|
return chunks[0], chunks[3] if 3 in chunks else None
|
||||||
return {'mode': 'server',
|
|
||||||
'bandwidth': elements[3]}
|
|
||||||
else:
|
|
||||||
return {'mode': mode}
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
bc = Batman()
|
bc = Batman()
|
||||||
|
|
25
lib/graph.py
25
lib/graph.py
|
@ -8,12 +8,17 @@ from lib.nodes import build_mac_table
|
||||||
|
|
||||||
def import_vis_data(graph, nodes, vis_data):
|
def import_vis_data(graph, nodes, vis_data):
|
||||||
macs = build_mac_table(nodes)
|
macs = build_mac_table(nodes)
|
||||||
nodes_a = map(lambda d: 2*[d['primary']], filter(lambda d: 'primary' in d, vis_data))
|
nodes_a = map(lambda d: 2*[d['primary']],
|
||||||
nodes_b = map(lambda d: [d['secondary'], d['of']], filter(lambda d: 'secondary' in d, vis_data))
|
filter(lambda d: 'primary' in d, vis_data))
|
||||||
graph.add_nodes_from(map(lambda a, b: (a, dict(primary=b, node_id=macs.get(b))), *zip(*chain(nodes_a, nodes_b))))
|
nodes_b = map(lambda d: [d['secondary'], d['of']],
|
||||||
|
filter(lambda d: 'secondary' in d, vis_data))
|
||||||
|
graph.add_nodes_from(map(lambda a, b:
|
||||||
|
(a, dict(primary=b, node_id=macs.get(b))),
|
||||||
|
*zip(*chain(nodes_a, nodes_b))))
|
||||||
|
|
||||||
edges = filter(lambda d: 'neighbor' in d, vis_data)
|
edges = filter(lambda d: 'neighbor' in d, vis_data)
|
||||||
graph.add_edges_from(map(lambda d: (d['router'], d['neighbor'], dict(tq=float(d['label']))), edges))
|
graph.add_edges_from(map(lambda d: (d['router'], d['neighbor'],
|
||||||
|
dict(tq=float(d['label']))), edges))
|
||||||
|
|
||||||
|
|
||||||
def mark_vpn(graph, vpn_macs):
|
def mark_vpn(graph, vpn_macs):
|
||||||
|
@ -32,11 +37,13 @@ def to_multigraph(graph):
|
||||||
return node['primary'] if node else a
|
return node['primary'] if node else a
|
||||||
|
|
||||||
def map_node(node, data):
|
def map_node(node, data):
|
||||||
return (data['primary'], dict(node_id=data['node_id'])) if data else (node, dict())
|
return (data['primary'],
|
||||||
|
dict(node_id=data['node_id'])) if data else (node, dict())
|
||||||
|
|
||||||
digraph = nx.MultiDiGraph()
|
digraph = nx.MultiDiGraph()
|
||||||
digraph.add_nodes_from(map(map_node, *zip(*graph.nodes_iter(data=True))))
|
digraph.add_nodes_from(map(map_node, *zip(*graph.nodes_iter(data=True))))
|
||||||
digraph.add_edges_from(map(lambda a, b, data: (f(a), f(b), data), *zip(*graph.edges_iter(data=True))))
|
digraph.add_edges_from(map(lambda a, b, data: (f(a), f(b), data),
|
||||||
|
*zip(*graph.edges_iter(data=True))))
|
||||||
|
|
||||||
return digraph
|
return digraph
|
||||||
|
|
||||||
|
@ -50,7 +57,8 @@ def merge_nodes(graph):
|
||||||
multigraph = to_multigraph(graph)
|
multigraph = to_multigraph(graph)
|
||||||
digraph = nx.DiGraph()
|
digraph = nx.DiGraph()
|
||||||
digraph.add_nodes_from(multigraph.nodes_iter(data=True))
|
digraph.add_nodes_from(multigraph.nodes_iter(data=True))
|
||||||
edges = chain.from_iterable([[(e, d, merge_edges(multigraph[e][d].values()))
|
edges = chain.from_iterable([[(e, d, merge_edges(
|
||||||
|
multigraph[e][d].values()))
|
||||||
for d in multigraph[e]] for e in multigraph])
|
for d in multigraph[e]] for e in multigraph])
|
||||||
digraph.add_edges_from(edges)
|
digraph.add_edges_from(edges)
|
||||||
|
|
||||||
|
@ -69,7 +77,8 @@ def to_undirected(graph):
|
||||||
|
|
||||||
graph = nx.Graph()
|
graph = nx.Graph()
|
||||||
graph.add_nodes_from(multigraph.nodes_iter(data=True))
|
graph.add_nodes_from(multigraph.nodes_iter(data=True))
|
||||||
edges = chain.from_iterable([[(e, d, merge_edges(multigraph[e][d].values()))
|
edges = chain.from_iterable([[(e, d, merge_edges(
|
||||||
|
multigraph[e][d].values()))
|
||||||
for d in multigraph[e]] for e in multigraph])
|
for d in multigraph[e]] for e in multigraph])
|
||||||
graph.add_edges_from(edges)
|
graph.add_edges_from(edges)
|
||||||
|
|
||||||
|
|
25
lib/nodes.py
25
lib/nodes.py
|
@ -53,23 +53,26 @@ def reset_statistics(nodes):
|
||||||
node['statistics'] = {'clients': 0}
|
node['statistics'] = {'clients': 0}
|
||||||
|
|
||||||
|
|
||||||
def import_statistics(nodes, statistics):
|
def import_statistics(nodes, stats):
|
||||||
def add(node, statistics, target, source, f=lambda d: d):
|
def add(node, statistics, target, source, f=lambda d: d):
|
||||||
try:
|
try:
|
||||||
node['statistics'][target] = f(reduce(dict.__getitem__, source, statistics))
|
node['statistics'][target] = f(reduce(dict.__getitem__,
|
||||||
|
source,
|
||||||
|
statistics))
|
||||||
except (KeyError, TypeError):
|
except (KeyError, TypeError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
macs = build_mac_table(nodes)
|
macs = build_mac_table(nodes)
|
||||||
statistics = filter(lambda d: 'node_id' in d, statistics)
|
stats = filter(lambda d: 'node_id' in d, stats)
|
||||||
statistics = filter(lambda d: d['node_id'] in nodes, statistics)
|
stats = filter(lambda d: d['node_id'] in nodes, stats)
|
||||||
for node, statistics in map(lambda d: (nodes[d['node_id']], d), statistics):
|
for node, stats in map(lambda d: (nodes[d['node_id']], d), stats):
|
||||||
add(node, statistics, 'clients', ['clients', 'total'])
|
add(node, stats, 'clients', ['clients', 'total'])
|
||||||
add(node, statistics, 'gateway', ['gateway'], lambda d: macs.get(d, d))
|
add(node, stats, 'gateway', ['gateway'], lambda d: macs.get(d, d))
|
||||||
add(node, statistics, 'uptime', ['uptime'])
|
add(node, stats, 'uptime', ['uptime'])
|
||||||
add(node, statistics, 'loadavg', ['loadavg'])
|
add(node, stats, 'loadavg', ['loadavg'])
|
||||||
add(node, statistics, 'memory_usage', ['memory'], lambda d: 1 - d['free'] / d['total'])
|
add(node, stats, 'memory_usage', ['memory'],
|
||||||
add(node, statistics, 'rootfs_usage', ['rootfs_usage'])
|
lambda d: 1 - d['free'] / d['total'])
|
||||||
|
add(node, stats, 'rootfs_usage', ['rootfs_usage'])
|
||||||
|
|
||||||
|
|
||||||
def import_mesh_ifs_vis_data(nodes, vis_data):
|
def import_mesh_ifs_vis_data(nodes, vis_data):
|
||||||
|
|
|
@ -28,8 +28,10 @@ class RRD(object):
|
||||||
os.mkdir(self.imagePath)
|
os.mkdir(self.imagePath)
|
||||||
|
|
||||||
def update_database(self, nodes):
|
def update_database(self, nodes):
|
||||||
online_nodes = dict(filter(lambda d: d[1]['flags']['online'], nodes.items()))
|
online_nodes = dict(filter(
|
||||||
client_count = sum(map(lambda d: d['statistics']['clients'], online_nodes.values()))
|
lambda d: d[1]['flags']['online'], nodes.items()))
|
||||||
|
client_count = sum(map(
|
||||||
|
lambda d: d['statistics']['clients'], online_nodes.values()))
|
||||||
|
|
||||||
self.globalDb.update(len(online_nodes), client_count)
|
self.globalDb.update(len(online_nodes), client_count)
|
||||||
for node_id, node in online_nodes.items():
|
for node_id, node in online_nodes.items():
|
||||||
|
@ -37,7 +39,8 @@ class RRD(object):
|
||||||
rrd.update()
|
rrd.update()
|
||||||
|
|
||||||
def update_images(self):
|
def update_images(self):
|
||||||
self.globalDb.graph(os.path.join(self.imagePath, "globalGraph.png"), self.displayTimeGlobal)
|
self.globalDb.graph(os.path.join(self.imagePath, "globalGraph.png"),
|
||||||
|
self.displayTimeGlobal)
|
||||||
|
|
||||||
nodedb_files = os.listdir(self.dbPath)
|
nodedb_files = os.listdir(self.dbPath)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue