add alfred server api

This commit is contained in:
Dominik Heidler 2015-10-07 16:19:07 +02:00
parent 52c548494c
commit fb60d69d51
8 changed files with 316 additions and 38 deletions

0
map/__init__.py Normal file
View File

39
map/api.py Normal file
View File

@ -0,0 +1,39 @@
#!/usr/bin/python
import nodewatcher
from flask import Blueprint, request, make_response
from pymongo import MongoClient
from bson.json_util import dumps as bson2json
import json
api = Blueprint("api", __name__)
client = MongoClient()
db = client.freifunk
@api.route('/get_nearest_router')
def get_nearest_router():
res_router = db.routers.find_one({"position": {"$near": {
"$geometry": {
"type": "Point",
"coordinates": [float(request.args.get("lng")), float(request.args.get("lat"))]
},
}}})
r = make_response(bson2json(res_router))
r.mimetype = 'application/json'
return r
@api.route('/alfred', methods=['GET', 'POST'])
def alfred():
#set_alfred_data = {65: "hallo", 66: "welt"}
set_alfred_data = {}
r = make_response(json.dumps(set_alfred_data))
if request.method == 'POST':
alfred_data = request.get_json()
# load router status xml data
for mac, xml in alfred_data.get("64", {}).items():
nodewatcher.process_router_xml(mac, xml)
r.headers['X-API-STATUS'] = "ALFRED data imported"
r.mimetype = 'application/json'
return r

View File

@ -1,12 +1,18 @@
#!/usr/bin/python
from api import api
from filters import filters
from flask import Flask, render_template, request, make_response
from pymongo import MongoClient
from bson.json_util import dumps as bson2json
from bson.objectid import ObjectId
from dateutil import tz
import json
app = Flask(__name__)
app.register_blueprint(api, url_prefix='/api')
app.register_blueprint(filters)
client = MongoClient()
db = client.freifunk
@ -37,38 +43,5 @@ def router_list():
def router_info(dbid):
return render_template("router.html", router=db.routers.find_one({"_id": ObjectId(dbid)}), tileurls=tileurls)
@app.route('/api/get_nearest_router')
def get_nearest_router():
res_router = db.routers.find_one({"position": {"$near": {
"$geometry": {
"type": "Point",
"coordinates": [float(request.args.get("lng")), float(request.args.get("lat"))]
},
}}})
r = make_response(bson2json(res_router))
r.mimetype = 'application/json'
return r
@app.template_filter('neighbour_color')
def neighbour_color(quality):
color = "#04ff0a"
if quality < 105:
color = "#ff1e1e"
elif quality < 130:
color = "#ff4949"
elif quality < 155:
color = "#ff6a6a"
elif quality < 180:
color = "#ffac53"
elif quality < 205:
color = "#ffeb79"
elif quality < 230:
color = "#79ff7c"
return color
@app.template_filter('utc2local')
def utc2local(dt):
return dt.replace(tzinfo=tz.tzutc()).astimezone(tz.tzlocal())
if __name__ == '__main__':
app.run(host='0.0.0.0', debug=True)

27
map/filters.py Normal file
View File

@ -0,0 +1,27 @@
#!/usr/bin/python
from flask import Blueprint
from dateutil import tz
filters = Blueprint("filters", __name__)
@filters.app_template_filter('neighbour_color')
def neighbour_color(quality):
color = "#04ff0a"
if quality < 105:
color = "#ff1e1e"
elif quality < 130:
color = "#ff4949"
elif quality < 155:
color = "#ff6a6a"
elif quality < 180:
color = "#ffac53"
elif quality < 205:
color = "#ffeb79"
elif quality < 230:
color = "#79ff7c"
return color
@filters.app_template_filter('utc2local')
def utc2local(dt):
return dt.replace(tzinfo=tz.tzutc()).astimezone(tz.tzlocal())

58
map/netmon.py Executable file
View File

@ -0,0 +1,58 @@
#!/usr/bin/python
import lxml.etree
import requests
import datetime
from pymongo import MongoClient
client = MongoClient()
db = client.freifunk
def fetch_router_info(mac):
mac = mac.replace(":", "").lower()
tree = lxml.etree.fromstring(requests.get("https://netmon.freifunk-franken.de/api/rest/router/%s" % mac, params={"limit": 5000}).content)
for r in tree.xpath("/netmon_response/router"):
user_netmon_id = int(r.xpath("user_id/text()")[0])
user = db.users.find_one({"netmon_id": user_netmon_id})
if user:
user_id = user["_id"]
else:
user_id = db.users.insert({
"netmon_id": user_netmon_id,
"nickname": r.xpath("user/nickname/text()")[0]
})
user = db.users.find_one({"_id": user_id})
router = {
"netmon_id": int(r.xpath("router_id/text()")[0]),
"user": {"nickname": user["nickname"], "_id": user["_id"]}
}
try:
lng = float(r.xpath("longitude/text()")[0])
lat = float(r.xpath("latitude/text()")[0])
assert lng != 0
assert lat != 0
router["position"] = {
"type": "Point",
"coordinates": [lng, lat]
}
# try to get comment
position_comment = r.xpath("location/text()")[0]
if position_comment != "undefined":
router["position"]["comment"] = position_comment
except (IndexError, AssertionError):
pass
try:
router["description"] = r.xpath("description/text()")[0]
except IndexError:
pass
router["last_contact"] = datetime.datetime.utcnow()
router["created"] = datetime.datetime.utcnow()
return router

171
map/nodewatcher.py Normal file
View File

@ -0,0 +1,171 @@
#!/usr/bin/python
import netmon
import lxml.etree
import datetime
from pymongo import MongoClient
client = MongoClient()
db = client.freifunk
CONFIG = {
"vpn_netif": "fffVPN",
}
def process_router_xml(mac, xml):
try:
router = db.routers.find_one({"netifs.mac": mac.lower()})
if router:
router_id = router["_id"]
assert xml != ""
tree = lxml.etree.fromstring(xml)
router_update = {
"status": tree.xpath("/data/system_data/status/text()")[0],
"hostname": tree.xpath("/data/system_data/hostname/text()")[0],
"neighbours": [],
"netifs": [],
"system": {
"time": datetime.datetime.fromtimestamp(int(tree.xpath("/data/system_data/local_time/text()")[0])),
"uptime": int(float(tree.xpath("/data/system_data/uptime/text()")[0])),
"memory": {
"free": int(tree.xpath("/data/system_data/memory_free/text()")[0]),
"buffering": int(tree.xpath("/data/system_data/memory_buffering/text()")[0]),
"caching": int(tree.xpath("/data/system_data/memory_caching/text()")[0]),
},
"loadavg": float(tree.xpath("/data/system_data/loadavg/text()")[0]),
"processes": {
"runnable": int(tree.xpath("/data/system_data/processes/text()")[0].split("/")[0]),
"total": int(tree.xpath("/data/system_data/processes/text()")[0].split("/")[1]),
},
"clients": int(tree.xpath("/data/client_count/text()")[0]),
"has_wan_uplink": len(tree.xpath("/data/interface_data/fffVPN")) > 0,
},
"hardware": {
"chipset": tree.xpath("/data/system_data/chipset/text()")[0],
"cpu": tree.xpath("/data/system_data/cpu/text()")[0]
},
"software": {
"os": "%s (%s)" % (tree.xpath("/data/system_data/distname/text()")[0],
tree.xpath("/data/system_data/distversion/text()")[0]),
"batman_adv": tree.xpath("/data/system_data/batman_advanced_version/text()")[0],
"kernel": tree.xpath("/data/system_data/kernel_version/text()")[0],
"nodewatcher": tree.xpath("/data/system_data/nodewatcher_version/text()")[0],
#"fastd": tree.xpath("/data/system_data/fastd_version/text()")[0],
"firmware": tree.xpath("/data/system_data/firmware_version/text()")[0],
"firmware_rev": tree.xpath("/data/system_data/firmware_revision/text()")[0],
}
}
# get hardware.name by chipset - FIXME: this should be found out by nodewatcher
chipset = db.chipsets.find_one({"name": router_update["hardware"]["chipset"]})
if chipset:
router_update["hardware"]["name"] = chipset["hardware"]
for netif in tree.xpath("/data/interface_data/*"):
interface = {
"name": netif.xpath("name/text()")[0],
"mtu": int(netif.xpath("mtu/text()")[0]),
"mac": netif.xpath("mac_addr/text()")[0].lower(),
"traffic": {
"rx": int(netif.xpath("traffic_rx/text()")[0]),
"tx": int(netif.xpath("traffic_tx/text()")[0]),
},
}
if len(netif.xpath("ipv6_link_local_addr/text()")) > 0:
interface["ipv6_fe80_addr"] = netif.xpath("ipv6_link_local_addr/text()")[0].lower().split("/")[0]
if len(netif.xpath("ipv4_addr/text()")) > 0:
interface["ipv4_addr"] = netif.xpath("ipv4_addr/text()")[0]
router_update["netifs"].append(interface)
visible_neighbours = 0
for originator in tree.xpath("/data/batman_adv_originators/*"):
visible_neighbours += 1
o_mac = originator.xpath("originator/text()")[0]
o_nexthop = originator.xpath("nexthop/text()")[0]
# mac is the mac of the neighbour w2/5mesh if
# (which might also be called wlan0-1)
o_link_quality = originator.xpath("link_quality/text()")[0]
o_out_if = originator.xpath("outgoing_interface/text()")[0]
if o_mac.upper() == o_nexthop.upper():
# skip vpn server
if o_out_if == CONFIG["vpn_netif"]:
continue
neighbour = {
"mac": o_mac.lower(),
"quality": int(o_link_quality),
"net_if": o_out_if,
}
try:
neighbour_router = db.routers.find_one({"netifs.mac": neighbour["mac"]})
neighbour["_id"] = neighbour_router["_id"]
neighbour["hostname"] = neighbour_router["hostname"]
assert "coordinates" in neighbour_router["position"]
assert neighbour_router["position"]["coordinates"][0] != 0
assert neighbour_router["position"]["coordinates"][1] != 0
if "comment" in neighbour_router["position"]:
del neighbour_router["position"]["comment"]
neighbour["position"] = neighbour_router["position"]
except:
pass
router_update["neighbours"].append(neighbour)
router_update["system"]["visible_neighbours"] = visible_neighbours
if router:
# keep hood up to date
router_update["hood"] = db.hoods.find_one({"position": {"$near": {"$geometry": router["position"]}}})["name"]
db.routers.update_one({"netifs.mac": mac.lower()}, {"$set": router_update, "$currentDate": {"last_contact": True}})
else:
# new router
# fetch additional information from netmon as it is not yet contained in xml
router_info = netmon.fetch_router_info(mac)
if router_info:
# keep hood up to date
router_update["hood"] = db.hoods.find_one({"position": {"$near": {"$geometry": router_info["position"]}}})["name"]
router_update.update(router_info)
router_id = db.routers.insert_one(router_update).inserted_id
status = router_update["status"]
except (AssertionError, lxml.etree.XMLSyntaxError):
if router:
db.routers.update_one({"_id": router_id}, {"$set": {"status": "unknown"}})
status = "unknown"
finally:
# fire events
events = []
try:
if not router:
events.append({
"time": datetime.datetime.utcnow(),
"type": "created",
})
except:
pass
try:
if router["system"]["uptime"] > router_update["system"]["uptime"]:
events.append({
"time": datetime.datetime.utcnow(),
"type": "reboot",
})
except:
pass
try:
if router["status"] != status:
events.append({
"time": datetime.datetime.utcnow(),
"type": status,
})
except:
pass
db.routers.update_one({"_id": router_id}, {"$push": {"events": {
"$each": events,
"$slice": -10,
}}})
if status == "online":
# calculate RRD statistics (rrdcache?)
#FIXME: implementation
pass

View File

@ -25,7 +25,7 @@
{% block content %}
<div id="map"></div>
<script type="text/javascript">
var url_get_nearest_router = "{{ url_for('get_nearest_router') }}";
var url_get_nearest_router = "{{ url_for('api.get_nearest_router') }}";
var url_router_info = "{{ url_for('router_info', dbid='') }}";
var tileurls = {{ tileurls|tojson|safe }};
</script>

View File

@ -10,6 +10,9 @@
.navbar, .table-condensed {
margin-bottom: 0;
}
.table-condensed tr:last-child td, th {
border-bottom: 1px solid #ddd;
}
</style>
{% endblock %}
@ -21,7 +24,7 @@
<div class="panel-body" style="padding: 0;">
<div id="map"></div>
<script type="text/javascript">
var url_get_nearest_router = "{{ url_for('get_nearest_router') }}";
var url_get_nearest_router = "{{ url_for('api.get_nearest_router') }}";
var url_router_info = "{{ url_for('router_info', dbid='') }}";
var tileurls = {{ tileurls|tojson|safe }};
</script>
@ -49,7 +52,7 @@
<div class="panel panel-default" style="flex: 0 1 auto;">
<div class="panel-heading">Neighbours</div>
<div class="panel-body">
<table class="neighbours">
<table class="neighbours" style="width: 100%;">
<tr>
<th>Hostname</th>
<th>MAC Address</th>
@ -71,7 +74,14 @@
<div class="panel panel-default" style="flex: 1 1 auto;">
<div class="panel-heading">Events</div>
<div class="panel-body">
Foobar
<table class="table table-condensed">
{%- for event in router.events[:5] %}
<tr>
<td>{{ event.time|utc2local }}</td>
<td>{{ event.type }}</td>
</tr>
{%- endfor %}
</table>
</div>
</div>
</div>