Initial commit.
- qa-tools public release which includes:
- trace-based coverage tool
- quality metrics measurement and tracking setup
- associated in-source documentation.
Signed-off-by: Basil Eljuse <basil.eljuse@arm.com>
diff --git a/quality-metrics/broker-component/quality_metrics_server.py b/quality-metrics/broker-component/quality_metrics_server.py
new file mode 100644
index 0000000..f68f4b2
--- /dev/null
+++ b/quality-metrics/broker-component/quality_metrics_server.py
@@ -0,0 +1,154 @@
+#!/usr/bin/env python3
+
+from __future__ import print_function
+from data_validator import DataValidator
+import credentials
+
+__copyright__ = """
+/*
+ * Copyright (c) 2020, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ */
+ """
+
+""" quality_metrics_server.py:
+
+ This is the broker component which accepts the data from data
+ generator scripts, and performs basic sanity check and pushes
+ the data to Influx-DB for visualisation with Grafana component.
+ It is not mandatory to push data via data generator scripts.
+ The request to push data to database, in this case - InfluxDB,
+ is expected to be be a POST request with right credentials and
+ should be in agreed upon format.
+
+"""
+
+from pprint import pprint
+from pprint import pformat
+from db_manager import dbManager
+from flask_jwt import JWT, jwt_required
+from flask import Flask, jsonify, request
+from werkzeug.security import safe_str_cmp
+from logging.handlers import RotatingFileHandler
+
+import sys
+import json
+import random
+import logging
+import argparse
+import datetime
+
+import constants
+""" It is suggested to keep credentials.py is kept locally in the
+ system where server is running. This file has been provided
+ for reference.
+"""
+
+username_table = {u.username: u for u in credentials.users}
+userid_table = {u.id: u for u in credentials.users}
+
+
+def authenticate(username, password):
+ user = username_table.get(username, None)
+ if user and safe_str_cmp(
+ user.password.encode('utf-8'),
+ password.encode('utf-8')):
+ return user
+
+
+def identity(payload):
+ user_id = payload['identity']
+ return userid_table.get(user_id, None)
+
+
+def setup_logging(app):
+ # maxBytes and backupCount values to allow the file to rollover at a predetermined size.
+ # When the size is about to be exceeded, the file is closed and a new file is silently
+ # opened for output. Rollover occurs whenever the current log file is nearly maxBytes in length.
+ # When backupCount is non-zero, the system will save old log files by appending the extensions
+ # ‘.1’, ‘.2’ etc., to the filename.
+ file_handler = RotatingFileHandler(
+ "./flask.log",
+ maxBytes=1024 * 1024 * 1024 * 5,
+ backupCount=5)
+ file_handler.setFormatter(
+ logging.Formatter(
+ '[%(asctime)s][PID:%(process)d][%(levelname)s]'
+ '[%(lineno)s][%(name)s.%(funcName)s()] %(message)s'))
+ file_handler.setLevel(logging.INFO)
+ loggers = [app.logger]
+ for logger in loggers:
+ logger.addHandler(file_handler)
+ app.logger.setLevel(logging.INFO)
+
+
+app = Flask(__name__)
+
+setup_logging(app)
+
+logger = logging.getLogger(__name__)
+
+app.debug = True
+app.config['SECRET_KEY'] = credentials.SECRET_KEY
+app.config['JWT_EXPIRATION_DELTA'] = datetime.timedelta(
+ days=constants.JWT_EXPIRATION_DAYS)
+
+dbm = dbManager(app=app).start_daemon()
+
+jwt = JWT(app, authenticate, identity)
+
+# ----------------------- Database Methods ----------------------------------#
+
+
+def store_to_db(data_dict):
+ """
+ Use the database manager to asynchronously update the database
+
+ :param: data_dict: Dictionary containing data to be stored
+ """
+ validation, err_code = dbm.store(data_dict)
+ return validation, err_code
+
+# ----------------------- FLASK API Methods ---------------------------------- #
+
+
+@app.route('/', methods=['POST'])
+@jwt_required()
+def add_db_entry():
+ """
+ Store received data to database if validation is okay
+
+ :return: validation information and error code
+ """
+
+ data = request.get_json()
+ app.logger.debug("Received Data (POST)")
+ app.logger.debug(pformat(data))
+ # Make sure the data is valid
+ validation, err_code = DataValidator.validate_request_sanity(data)
+ if validation == "OK":
+ app.logger.info("<<<<VALIDATION OK>>>>")
+ validation, err_code = store_to_db(data)
+ else:
+ app.logger.error("<<<<VALIDATION NOT OK>>>>")
+ app.logger.error(pformat({"data": validation, "error_code": err_code}))
+ info_json = jsonify({"data": validation, "error_code": err_code})
+ return info_json, err_code
+
+
+@app.route("/")
+def home():
+ info_json = jsonify({"type": "INFO", "data": "Quality Metrics"})
+ return info_json, 200
+
+
+if __name__ == '__main__':
+ try:
+ app.run(host=constants.LISTEN_ALL_IPS, port=5000)
+ except Exception as ex:
+ template = "An exception of type {0} occurred. Arguments:\n{1!r}"
+ message = template.format(type(ex).__name__, ex.args)
+ app.logger.error("message")
+ dbm.stop()