| # author: Alex Savatieiev (osavatieiev@mirantis.com) |
| from gevent import monkey, pywsgi |
| monkey.patch_all() |
| import falcon # noqa E402 |
| import os # noqa E402 |
| import json # noqa E402 |
| |
| from copy import deepcopy # noqa E402 |
| from platform import system, release, node # noqa E402 |
| |
| from cfg_checker.common.log import logger # noqa E402 |
| from cfg_checker.common.settings import pkg_dir # noqa E402 |
| from cfg_checker.common.exception import CheckerException # noqa E402 |
| from cfg_checker.helpers.falcon_jinja2 import FalconTemplate # noqa E402 |
| from .fio_runner import FioProcessShellRun, get_time # noqa E402 |
| |
| template = FalconTemplate( |
| path=os.path.join(pkg_dir, "templates") |
| ) |
| |
| _module_status = { |
| "status": "unknown", |
| "healthcheck": {}, |
| "actions": [], |
| "options": {}, |
| "uri": "<agent_uri>/api/<modile_name>", |
| } |
| |
| _action = { |
| "module": "<name>", |
| "action": "<action>", |
| "options": "<options_dict>" |
| } |
| |
| _modules = { |
| "fio": deepcopy(_module_status) |
| } |
| |
| _status = { |
| "agent": { |
| "started": get_time() |
| }, |
| "modules": list(_modules.keys()), |
| "help": { |
| ".../api": { |
| "GET": "<this_status>", |
| "POST": json.dumps(_action) |
| }, |
| ".../api/<module_name>": { |
| "GET": "returns healthcheck and module help" |
| } |
| } |
| } |
| |
| # Populate modules |
| _fio = FioProcessShellRun() |
| |
| |
| def _init_status(mod): |
| _modules[mod]["uri"] = "<agent_uri>/api/fio" |
| _modules[mod]["actions"] = list(_fio.actions.keys()) |
| |
| |
| def _update_status(mod): |
| _modules[mod]["healthcheck"] = _fio.hchk |
| _modules[mod]["options"] = _fio.get_options() |
| _modules[mod].update(_fio.status()) |
| |
| |
| class FioStatus: |
| _name = "fio" |
| |
| def on_get(self, req, resp): |
| # Hacky way to handle empty request |
| _m = req.get_media(default_when_empty={}) |
| if "fast" in _m and _m["fast"]: |
| resp.status = falcon.HTTP_200 |
| resp.content_type = "application/json" |
| resp.text = json.dumps(_fio.status()) |
| else: |
| _update_status(self._name) |
| |
| resp.status = falcon.HTTP_200 |
| resp.content_type = "application/json" |
| resp.text = json.dumps(_modules[self._name]) |
| |
| |
| class Api: |
| def on_get(self, req, resp): |
| # return api status |
| resp.status = falcon.HTTP_200 |
| resp.content_type = "application/json" |
| resp.text = json.dumps(_status) |
| |
| def on_post(self, req, resp): |
| def _resp(resp, code, msg, opt={}): |
| resp.status = code |
| resp.content_type = "application/json" |
| resp.text = json.dumps({"error": msg, "options": opt}) |
| # Handle actions |
| logger.info("Getting media") |
| try: |
| _m = req.stream.read() |
| _m = json.loads(_m) |
| except json.JSONDecodeError: |
| _msg = "Incorrect input data" |
| logger.error(_msg) |
| _resp(resp, falcon.HTTP_400, _msg) |
| return |
| if _m: |
| logger.debug("got media object:\n{}".format(json.dumps(_m))) |
| # Validate action structure |
| _module = _m.get('module', "") |
| _action = _m.get('action', "") |
| _options = _m.get('options', {}) |
| |
| if not _module or _module not in list(_modules.keys()): |
| logger.error("invalid module '{}'".format(_module)) |
| _resp( |
| resp, |
| falcon.HTTP_400, |
| "Invalid module '{}'".format(_module) |
| ) |
| return |
| elif not _action or _action not in _modules[_module]['actions']: |
| logger.error("invalid action '{}'".format(_action)) |
| _resp( |
| resp, |
| falcon.HTTP_400, |
| "Invalid action '{}'".format(_action) |
| ) |
| return |
| else: |
| # Handle command |
| _a = _fio.actions[_action] |
| try: |
| if _action == 'get_options': |
| logger.info("returning options") |
| resp.status = falcon.HTTP_200 |
| resp.content_type = "application/json" |
| resp.text = json.dumps({"options": _a()}) |
| elif _action == 'get_resultlist': |
| logger.info("getting results") |
| resp.status = falcon.HTTP_200 |
| resp.content_type = "application/json" |
| resp.text = json.dumps({"resultlist": _a()}) |
| elif _action == 'get_result': |
| if 'time' not in _options: |
| _msg = "No 'time' found for '{}'".format(_action) |
| logger.error(_msg) |
| _resp( |
| resp, |
| falcon.HTTP_400, |
| _msg |
| ) |
| return |
| _time = _options['time'] |
| logger.info("getting results for '{}'".format(_time)) |
| resp.status = falcon.HTTP_200 |
| resp.content_type = "application/json" |
| resp.text = json.dumps({_time: _a(_time)}) |
| elif _action == 'do_singlerun': |
| logger.info("executing single run") |
| _a(_options) |
| resp.status = falcon.HTTP_200 |
| resp.content_type = "application/json" |
| resp.text = json.dumps({"ok": True}) |
| elif _action == 'do_scheduledrun': |
| logger.info("executing scheduled run") |
| # prepare scheduled run |
| |
| # Run it |
| _a(_options) |
| resp.status = falcon.HTTP_200 |
| resp.content_type = "application/json" |
| resp.text = json.dumps({"ok": True}) |
| else: |
| _msg = "Unknown error happened for '{}/{}/{}'".format( |
| _module, |
| _action, |
| _options |
| ) |
| logger.error(_msg) |
| _resp(resp, falcon.HTTP_500, _msg) |
| except CheckerException as e: |
| _msg = "Error for '{}/{}':\n{}".format( |
| _module, |
| _action, |
| e |
| ) |
| logger.error(_msg) |
| _resp(resp, falcon.HTTP_500, _msg, opt=_options) |
| return |
| else: |
| _msg = "Empty request body" |
| logger.error(_msg) |
| _resp(resp, falcon.HTTP_400, _msg) |
| |
| |
| class Index: |
| @template.render("agent_index_html.j2") |
| def on_get(self, request, response): |
| # prepare template context |
| _context = { |
| "gen_date": get_time(), |
| "system": system(), |
| "release": release(), |
| "hostname": node() |
| } |
| _context.update(_status) |
| # creating response |
| response.status = falcon.HTTP_200 |
| response.content_type = "text/html" |
| response.context = _context |
| |
| |
| def agent_server(host="0.0.0.0", port=8765): |
| # init api |
| api = falcon.API() |
| # populate pages |
| api.add_route("/", Index()) |
| api.add_route("/api/", Api()) |
| |
| # Populate modules list |
| _active_modules = [FioStatus] |
| # init modules |
| for mod in _active_modules: |
| _init_status(mod._name) |
| _update_status(mod._name) |
| |
| api.add_route("/api/"+mod._name, mod()) |
| |
| # init and start server |
| server = pywsgi.WSGIServer((host, port), api) |
| server.serve_forever() |