Browse Source

batman.py: call 'batctl vd json' and 'batctl gwl' to get knowledge of gateways

how stupid that batadv-vis does not output these -.-
Helge Jung 9 years ago
parent
commit
bd3208d0c0
4 changed files with 94 additions and 5 deletions
  1. 4 0
      batcave.py
  2. 7 2
      ffstatus/basestorage.py
  3. 80 3
      ffstatus/batman.py
  4. 3 0
      ffstatus/server.py

+ 4 - 0
batcave.py

@@ -39,6 +39,8 @@ def get_args():
                         help='executable path for alfred-json')
     parser.add_argument('-B', '--batadv-vis',
                         help='executable path for batadv-vis')
+    parser.add_argument('-C', '--batctl',
+                        help='executable path for batctl')
     parser.add_argument('-G', '--graphite-host',
                         help='Graphite host')
     parser.add_argument('--graphite-port', type=int, default=2003,
@@ -123,6 +125,8 @@ def main():
         alfred.alfred_json = args.alfred_json
     if not args.batadv_vis is None:
         batman.batadv_vis = args.batadv_vis
+    if not args.batctl is None:
+        batman.batctl = args.batctl
 
     logger.debug('Configured A.L.F.R.E.D. source: %s', alfred)
     logger.debug('Configured B.A.T.M.A.N. source: %s', batman)

+ 7 - 2
ffstatus/basestorage.py

@@ -102,6 +102,7 @@ class BaseStorage(object):
             current[item_id]['aliases'] = []
             current[item_id]['clients'] = []
             current[item_id]['neighbours'] = {}
+            current[item_id]['type'] = 'node'
 
             if not item_id in newdata:
                 continue
@@ -230,11 +231,15 @@ class BaseStorage(object):
 
         # check that the last batadv update is noted in the data
         updated = node.get(self.FIELDKEY_UPDATED, None)
-        if updated is None or not 'batadv' in updated:
+        if updated is None:
+            return 'unknown'
+
+        u = updated.get('batadv', updated.get('batctl'))
+        if u is None:
             return 'unknown'
 
         # make decision based on time of last batadv update
-        diff = time.time() - updated['batadv']
+        diff = time.time() - u
         if diff < 150:
             return 'active'
         elif diff < 300:

+ 80 - 3
ffstatus/batman.py

@@ -4,6 +4,7 @@
 from __future__ import print_function
 import io
 import json
+import re
 import string
 import subprocess
 import time
@@ -14,13 +15,15 @@ from .exceptions import SanityCheckError
 
 class BatmanParser:
     batadv_vis = 'batadv-vis'
+    batctl = 'batctl'
     mactranslation = string.maketrans('2367abef', '014589cd')
 
     def __str__(self):
-        return 'BatmanParser \'{0}\''.format(self.batadv_vis)
+        return 'BatmanParser using \'{0}\' and \'{1}\''.format(
+            self.batadv_vis, self.batctl)
 
     def sanitycheck(self):
-        """Checks that batadv-vis is executable and gives sane output."""
+        """Checks that programs are executable and give sane output."""
 
         testdata = None
         try:
@@ -36,6 +39,24 @@ class BatmanParser:
             raise SanityCheckError(
                 self, "batadv-vis does not return valid JSON data", err)
 
+        try:
+            cmd = [self.batctl, 'vd', 'json', '-n']
+            testdata = subprocess.check_output(cmd)
+        except Exception as err:
+            raise SanityCheckError(
+                self, "batctl not found or incompatible", err)
+
+        lineno = 0
+        try:
+            for line in testdata.splitlines():
+                lineno += 1
+                json.loads(line)
+        except Exception as err:
+            raise SanityCheckError(
+                self,
+                "batctl does not return valid JSON data (line " + lineno + ")",
+                err)
+
         return True
 
     def fetch(self):
@@ -44,11 +65,61 @@ class BatmanParser:
         data = {}
         timestamp = int(time.time())
 
+        # call 'batctl vd json -n' and parse output line-wise as JSON
+        rawvisdata = subprocess.check_output([self.batctl, 'vd', 'json', '-n'])
+        visdata = [json.loads(line) for line in rawvisdata.splitlines()]
+
+        # call 'batctl gwl' and parse output lines
+        rawgwdata = subprocess.check_output([self.batctl, 'gwl'])
+        gateways = []
+        for l in rawgwdata.splitlines()[2:]:
+            m = re.search("^\s*(([a-fA-F0-9]{2}:){5}[a-fA-F0-9]{2})\s+", l)
+            if m is not None:
+                gateways.append(ffstatus.mac2id(m.group(1)))
+
         # call batadv-vis and parse output as unicode JSON
         rawdata = subprocess.check_output([self.batadv_vis, '-f', 'jsondoc'])
         rawdata = unicode(rawdata, encoding='latin1', errors='replace')
         batmandata = json.loads(rawdata)
 
+        # parse vis data
+        for item in visdata:
+            itemid = ffstatus.mac2id(item.get('primary', item.get('router')))
+            content = {
+                'aliases': [],
+                'neighbours': {},
+                '__UPDATED__': {'batctl': timestamp},
+            }
+
+            if itemid in gateways:
+                content['type'] = 'gateway'
+
+            neigh_id = ffstatus.mac2id(item.get('neighbor'))
+            if neigh_id is not None:
+                neigh_q = item.get('label', '0')
+                if neigh_q != 'TT':
+                    neigh_q = float(neigh_q)
+                else:
+                    neigh_q = None
+                content['neighbours'][neigh_id] = neigh_q
+
+            if itemid is None:
+                # assumption: this is a secondary
+                secid = ffstatus.mac2id(item.get('secondary'))
+                ofid = ffstatus.mac2id(item.get('of'))
+                if secid is None or ofid is None:
+                    print('BATCTL-VD threw garbage: ' + json.dumps(item))
+                    continue
+
+                itemid = ofid
+                content['aliases'].append(secid)
+
+            if itemid in data:
+                data[itemid] = ffstatus.dict_merge(
+                    data[itemid], content, overwrite_lists=False)
+            else:
+                data[itemid] = content
+
         # parse raw data, convert all MAC into nodeid
         for item in batmandata['vis']:
             itemid = ffstatus.mac2id(item['primary'])
@@ -67,7 +138,7 @@ class BatmanParser:
                     neighbours[neighbour['neighbor']] = metric
 
             # construct dict entry as expected by BATCAVE
-            data[itemid] = {
+            content = {
                 'aliases': aliases,
                 'neighbours': neighbours,
                 'clients': [x for x in item.get('clients', [])],
@@ -75,6 +146,12 @@ class BatmanParser:
                 '__RAW__': {'batadv': {itemid: item}},
             }
 
+            if itemid in data:
+                data[itemid] = ffstatus.dict_merge(
+                    data[itemid], content, overwrite_lists=False)
+            else:
+                data[itemid] = content
+
         return data
 
 # standalone test mode

+ 3 - 0
ffstatus/server.py

@@ -295,6 +295,9 @@ angesprochen und sollte aus einer Mehrzahl von Gr&uuml;nden nicht
                 'neighbours': node.get('neighbours', {}),
                 'status': self.server.storage.get_nodestatus(node=node),
             }
+            nodetype = node.get('type', 'node')
+            if nodetype != 'node':
+                entry['type'] = nodetype
             geo = node.get('location', None)
             if geo is not None:
                 entry['geo'] = [geo['latitude'], geo['longitude']]