Alex | b78191f | 2021-11-02 16:35:46 -0500 | [diff] [blame] | 1 | # author: Alex Savatieiev (osavatieiev@mirantis.com) |
| 2 | from gevent import monkey, pywsgi |
| 3 | monkey.patch_all() |
| 4 | import falcon # noqa E402 |
| 5 | import os # noqa E402 |
| 6 | import json # noqa E402 |
| 7 | |
| 8 | from copy import deepcopy # noqa E402 |
| 9 | from platform import system, release, node # noqa E402 |
| 10 | |
| 11 | from cfg_checker.common.settings import pkg_dir # noqa E402 |
| 12 | from cfg_checker.helpers.falcon_jinja2 import FalconTemplate # noqa E402 |
| 13 | from .fio_runner import FioProcessShellRun, get_time # noqa E402 |
| 14 | |
| 15 | template = FalconTemplate( |
| 16 | path=os.path.join(pkg_dir, "templates") |
| 17 | ) |
| 18 | |
| 19 | _module_status = { |
| 20 | "status": "unknown", |
| 21 | "healthcheck": {}, |
| 22 | "actions": [], |
| 23 | "options": {}, |
| 24 | "uri": "<agent_uri>/api/<modile_name>", |
| 25 | } |
| 26 | |
| 27 | _action = { |
| 28 | "module": "<name>", |
| 29 | "action": "<action>", |
| 30 | "options": "<options_dict>" |
| 31 | } |
| 32 | |
| 33 | _modules = { |
| 34 | "fio": deepcopy(_module_status) |
| 35 | } |
| 36 | |
| 37 | _status = { |
| 38 | "agent": { |
| 39 | "started": get_time() |
| 40 | }, |
| 41 | "modules": list(_modules.keys()), |
| 42 | "help": { |
| 43 | ".../api": { |
| 44 | "GET": "<this_status>", |
| 45 | "POST": json.dumps(_action) |
| 46 | }, |
| 47 | ".../api/<module_name>": { |
| 48 | "GET": "returns healthcheck and module help" |
| 49 | } |
| 50 | } |
| 51 | } |
| 52 | |
| 53 | # Populate modules |
| 54 | _fio = FioProcessShellRun() |
| 55 | |
| 56 | |
| 57 | def _init_status(mod): |
| 58 | _modules[mod]["uri"] = "<agent_uri>/api/fio" |
| 59 | _modules[mod]["actions"] = list(_fio.actions.keys()) |
| 60 | |
| 61 | |
| 62 | def _update_status(mod): |
| 63 | _modules[mod]["healthcheck"] = _fio.hchk |
| 64 | _modules[mod]["options"] = _fio.get_options() |
| 65 | _modules[mod].update(_fio.status()) |
| 66 | |
| 67 | |
| 68 | class FioStatus: |
| 69 | _name = "fio" |
| 70 | |
| 71 | def on_get(self, req, resp): |
| 72 | # Hacky way to handle empty request |
| 73 | _m = req.get_media(default_when_empty={}) |
| 74 | if "fast" in _m and _m["fast"]: |
| 75 | resp.status = falcon.HTTP_200 |
| 76 | resp.content_type = "application/json" |
| 77 | resp.text = json.dumps(_fio.status()) |
| 78 | else: |
| 79 | _update_status(self._name) |
| 80 | |
| 81 | resp.status = falcon.HTTP_200 |
| 82 | resp.content_type = "application/json" |
| 83 | resp.text = json.dumps(_modules[self._name]) |
| 84 | |
| 85 | |
| 86 | class Api: |
| 87 | def on_get(self, req, resp): |
| 88 | # return api status |
| 89 | resp.status = falcon.HTTP_200 |
| 90 | resp.content_type = "application/json" |
| 91 | resp.text = json.dumps(_status) |
| 92 | |
| 93 | def on_post(self, req, resp): |
| 94 | def _resp(resp, code, msg): |
| 95 | resp.status = code |
| 96 | resp.content_type = "application/json" |
| 97 | resp.text = json.dumps({"error": msg}) |
| 98 | # Handle actions |
| 99 | _m = req.get_media(default_when_empty={}) |
| 100 | if _m: |
| 101 | # Validate action structure |
| 102 | _module = _m.get('module', "") |
| 103 | _action = _m.get('action', "") |
| 104 | _options = _m.get('options', {}) |
| 105 | |
| 106 | if not _module or _module not in list(_modules.keys()): |
| 107 | _resp( |
| 108 | resp, |
| 109 | falcon.HTTP_400, |
| 110 | "Invalid module '{}'".format(_module) |
| 111 | ) |
| 112 | return |
| 113 | elif not _action or _action not in _modules[_module]['actions']: |
| 114 | _resp( |
| 115 | resp, |
| 116 | falcon.HTTP_400, |
| 117 | "Invalid action '{}'".format(_action) |
| 118 | ) |
| 119 | return |
| 120 | else: |
| 121 | # Handle command |
| 122 | _a = _fio.actions[_action] |
| 123 | if _action == 'get_options': |
| 124 | resp.status = falcon.HTTP_200 |
| 125 | resp.content_type = "application/json" |
| 126 | resp.text = json.dumps({"options": _a()}) |
| 127 | elif _action == 'get_resultlist': |
| 128 | resp.status = falcon.HTTP_200 |
| 129 | resp.content_type = "application/json" |
| 130 | resp.text = json.dumps({"resultlist": _a()}) |
| 131 | elif _action == 'get_result': |
| 132 | if 'time' not in _options: |
| 133 | _resp( |
| 134 | resp, |
| 135 | falcon.HTTP_400, |
| 136 | "No 'time' found for '{}'".format(_action) |
| 137 | ) |
| 138 | return |
| 139 | _time = _options['time'] |
| 140 | resp.status = falcon.HTTP_200 |
| 141 | resp.content_type = "application/json" |
| 142 | resp.text = json.dumps({_time: _a(_time)}) |
| 143 | elif _action == 'do_singlerun': |
| 144 | _a(_options) |
| 145 | resp.status = falcon.HTTP_200 |
| 146 | resp.content_type = "application/json" |
| 147 | resp.text = json.dumps({"ok": True}) |
| 148 | elif _action == 'do_scheduledrun': |
| 149 | # prepare scheduled run |
| 150 | |
| 151 | # Run it |
| 152 | _a(_options) |
| 153 | resp.status = falcon.HTTP_200 |
| 154 | resp.content_type = "application/json" |
| 155 | resp.text = json.dumps({"ok": True}) |
| 156 | else: |
| 157 | _resp( |
| 158 | resp, |
| 159 | falcon.HTTP_500, |
| 160 | "Unknown error happened for '{}/{}/{}'".format( |
| 161 | _module, |
| 162 | _action, |
| 163 | _options |
| 164 | ) |
| 165 | ) |
| 166 | return |
| 167 | else: |
Alex | 5cace3b | 2021-11-10 16:40:37 -0600 | [diff] [blame^] | 168 | _resp(resp, falcon.HTTP_400, "Empty request body") |
Alex | b78191f | 2021-11-02 16:35:46 -0500 | [diff] [blame] | 169 | |
| 170 | |
| 171 | class Index: |
| 172 | @template.render("agent_index_html.j2") |
| 173 | def on_get(self, request, response): |
| 174 | # prepare template context |
| 175 | _context = { |
| 176 | "gen_date": get_time(), |
| 177 | "system": system(), |
| 178 | "release": release(), |
| 179 | "hostname": node() |
| 180 | } |
| 181 | _context.update(_status) |
| 182 | # creating response |
| 183 | response.status = falcon.HTTP_200 |
| 184 | response.content_type = "text/html" |
| 185 | response.context = _context |
| 186 | |
| 187 | |
| 188 | def agent_server(host="0.0.0.0", port=8765): |
| 189 | # init api |
| 190 | api = falcon.API() |
| 191 | # populate pages |
| 192 | api.add_route("/", Index()) |
| 193 | api.add_route("/api/", Api()) |
| 194 | |
| 195 | # Populate modules list |
| 196 | _active_modules = [FioStatus] |
| 197 | # init modules |
| 198 | for mod in _active_modules: |
| 199 | _init_status(mod._name) |
| 200 | _update_status(mod._name) |
| 201 | |
| 202 | api.add_route("/api/"+mod._name, mod()) |
| 203 | |
| 204 | # init and start server |
| 205 | server = pywsgi.WSGIServer((host, port), api) |
| 206 | server.serve_forever() |