import fcntl import hmac import json import logging import logging.config import os from flask import (Flask, request, jsonify, abort, render_template) from ltsdb_json import LTS from dashboard import Dashboard import config logging.config.dictConfig(config.logging) app = Flask(__name__) log = logging.getLogger() @app.route("/") def home(): return jsonify({ "success": None }) @app.route("/report", methods=["POST"]) def report(): data = request.get_json() n_ts = 0 n_dp = 0 for d in data: d["description"]["remote_addr"] = request.remote_addr d["description"]["node"] = verify_node(d) log.info("received %s", json.dumps(d)) ts = LTS(d["description"]) for dp in d["data"]: ts.add(*dp) ts.save() n_dp += 1 n_ts += 1 return jsonify({ "success": True, "timeseries": n_ts, "datapoints": n_dp }) @app.route("/ts/") def get_timeseries(id): try: ts = LTS(id=id) except FileNotFoundError: abort(404) return jsonify({"description": ts.description, "data": ts.data}) @app.route("/dimensions") def list_dimensions(): with open("data/.index") as fh: fcntl.flock(fh, fcntl.LOCK_SH) index = json.load(fh) # Just return the number of timeseries for each dimension/member, not # the timeseries themselves for d in index.keys(): for m in index[d].keys(): index[d][m] = len(index[d][m]) return jsonify(index) @app.route("/search") def search(): log.debug("search: %s", request.args) return jsonify(_search()) def _search(): timeseries = None with open("data/.index") as fh: fcntl.flock(fh, fcntl.LOCK_SH) index = json.load(fh) for k, v in request.args.lists(): log.debug("search: %s -> %s", k, v) if timeseries is None: timeseries = set() log.debug("search: %s: %s", k, index[k]) for m in v: timeseries |= set(index[k][m]) else: filter = set() for m in v: filter |= set(index[k][m]) timeseries &= filter results = list(timeseries) return results def verify_node(d): node = d["auth"]["node"] timestamp = d["auth"]["timestamp"] digest1 = d["auth"]["hmac"] if "/" in node: raise ValueError("invalid node name %s", node) try: log.info("getting client config from %s", "config/" + node) with open("config/" + node) as fh: node_conf = json.load(fh) except Exception as e: log.warning("got %s opening %s", e, "config/" + node) abort(401, "unknown client") last = node_conf["last"] for key in node_conf["keys"]: msg = (node + " " + str(timestamp)).encode("UTF-8") hmac2 = hmac.new(key.encode("UTF-8"), msg, "SHA256") digest2 = hmac2.hexdigest() if hmac.compare_digest(digest1, digest2): if timestamp > node_conf["last"]: node_conf["last"] = timestamp os.replace("config/" + node, "config/" + node + ".old") with open("config/" + node, "w") as fh: json.dump(node_conf, fh) # XXX return node else: abort(409, "timestamp out of sync") abort(401, "auth failed") @app.get("/v") def visualize(): timeseries_ids = request.args.getlist("ts") if not timeseries_ids: timeseries_ids = _search() log.debug("timeseries_ids = %s", timeseries_ids) timeseries_data = [] for id in timeseries_ids: ts = LTS(id=id) timeseries_data.append(ts) return render_template("visualize.html", ts=timeseries_data) @app.get("/dashboard/") def dashboard_index(): d = Dashboard("dashboards/" + "index" + ".json") return d.as_html() @app.get("/dashboard/") def dashboard_file(dashboard): d = Dashboard("dashboards/" + dashboard + ".json") return d.as_html()