blob: eacce8f58b260a46c25a8a894a5004cffcf221b1 [file] [log] [blame]
Alexb78191f2021-11-02 16:35:46 -05001# author: Alex Savatieiev (osavatieiev@mirantis.com)
2from gevent import monkey, pywsgi
3monkey.patch_all()
4import falcon # noqa E402
5import os # noqa E402
6import json # noqa E402
7
8from copy import deepcopy # noqa E402
9from platform import system, release, node # noqa E402
10
Alex2a7657c2021-11-10 20:51:34 -060011from cfg_checker.common.log import logger # noqa E402
Alexb78191f2021-11-02 16:35:46 -050012from cfg_checker.common.settings import pkg_dir # noqa E402
Alex2a7657c2021-11-10 20:51:34 -060013from cfg_checker.common.exception import CheckerException # noqa E402
Alexb78191f2021-11-02 16:35:46 -050014from cfg_checker.helpers.falcon_jinja2 import FalconTemplate # noqa E402
15from .fio_runner import FioProcessShellRun, get_time # noqa E402
16
17template = FalconTemplate(
18 path=os.path.join(pkg_dir, "templates")
19)
20
21_module_status = {
22 "status": "unknown",
23 "healthcheck": {},
24 "actions": [],
25 "options": {},
26 "uri": "<agent_uri>/api/<modile_name>",
27}
28
29_action = {
30 "module": "<name>",
31 "action": "<action>",
32 "options": "<options_dict>"
33}
34
35_modules = {
36 "fio": deepcopy(_module_status)
37}
38
39_status = {
40 "agent": {
41 "started": get_time()
42 },
43 "modules": list(_modules.keys()),
44 "help": {
45 ".../api": {
46 "GET": "<this_status>",
47 "POST": json.dumps(_action)
48 },
49 ".../api/<module_name>": {
50 "GET": "returns healthcheck and module help"
51 }
52 }
53}
54
55# Populate modules
56_fio = FioProcessShellRun()
57
58
59def _init_status(mod):
60 _modules[mod]["uri"] = "<agent_uri>/api/fio"
61 _modules[mod]["actions"] = list(_fio.actions.keys())
62
63
64def _update_status(mod):
65 _modules[mod]["healthcheck"] = _fio.hchk
66 _modules[mod]["options"] = _fio.get_options()
67 _modules[mod].update(_fio.status())
68
69
70class FioStatus:
71 _name = "fio"
72
73 def on_get(self, req, resp):
74 # Hacky way to handle empty request
75 _m = req.get_media(default_when_empty={})
76 if "fast" in _m and _m["fast"]:
77 resp.status = falcon.HTTP_200
78 resp.content_type = "application/json"
79 resp.text = json.dumps(_fio.status())
80 else:
81 _update_status(self._name)
82
83 resp.status = falcon.HTTP_200
84 resp.content_type = "application/json"
85 resp.text = json.dumps(_modules[self._name])
86
87
88class Api:
89 def on_get(self, req, resp):
90 # return api status
91 resp.status = falcon.HTTP_200
92 resp.content_type = "application/json"
93 resp.text = json.dumps(_status)
94
95 def on_post(self, req, resp):
Alex2a7657c2021-11-10 20:51:34 -060096 def _resp(resp, code, msg, opt={}):
Alexb78191f2021-11-02 16:35:46 -050097 resp.status = code
98 resp.content_type = "application/json"
Alex2a7657c2021-11-10 20:51:34 -060099 resp.text = json.dumps({"error": msg, "options": opt})
Alexb78191f2021-11-02 16:35:46 -0500100 # Handle actions
Alex2a7657c2021-11-10 20:51:34 -0600101 logger.info("Getting media")
102 try:
103 _m = req.get_media(default_when_empty={})
104 except falcon.MediaMalformedError:
105 _msg = "Incorrect input data"
106 logger.error(_msg)
107 _resp(resp, falcon.HTTP_400, _msg)
108 return
Alexb78191f2021-11-02 16:35:46 -0500109 if _m:
Alex2a7657c2021-11-10 20:51:34 -0600110 logger.debug("got media object:\n{}".format(json.dumps(_m)))
Alexb78191f2021-11-02 16:35:46 -0500111 # Validate action structure
112 _module = _m.get('module', "")
113 _action = _m.get('action', "")
114 _options = _m.get('options', {})
115
116 if not _module or _module not in list(_modules.keys()):
Alex2a7657c2021-11-10 20:51:34 -0600117 logger.error("invalid module '{}'".format(_module))
Alexb78191f2021-11-02 16:35:46 -0500118 _resp(
119 resp,
120 falcon.HTTP_400,
121 "Invalid module '{}'".format(_module)
122 )
123 return
124 elif not _action or _action not in _modules[_module]['actions']:
Alex2a7657c2021-11-10 20:51:34 -0600125 logger.error("invalid action '{}'".format(_action))
Alexb78191f2021-11-02 16:35:46 -0500126 _resp(
127 resp,
128 falcon.HTTP_400,
129 "Invalid action '{}'".format(_action)
130 )
131 return
132 else:
133 # Handle command
134 _a = _fio.actions[_action]
Alex2a7657c2021-11-10 20:51:34 -0600135 try:
136 if _action == 'get_options':
137 logger.info("returning options")
138 resp.status = falcon.HTTP_200
139 resp.content_type = "application/json"
140 resp.text = json.dumps({"options": _a()})
141 elif _action == 'get_resultlist':
142 logger.info("getting results")
143 resp.status = falcon.HTTP_200
144 resp.content_type = "application/json"
145 resp.text = json.dumps({"resultlist": _a()})
146 elif _action == 'get_result':
147 if 'time' not in _options:
148 _msg = "No 'time' found for '{}'".format(_action)
149 logger.error(_msg)
150 _resp(
151 resp,
152 falcon.HTTP_400,
153 _msg
154 )
155 return
156 _time = _options['time']
157 logger.info("getting results for '{}'".format(_time))
158 resp.status = falcon.HTTP_200
159 resp.content_type = "application/json"
160 resp.text = json.dumps({_time: _a(_time)})
161 elif _action == 'do_singlerun':
162 logger.info("executing single run")
163 _a(_options)
164 resp.status = falcon.HTTP_200
165 resp.content_type = "application/json"
166 resp.text = json.dumps({"ok": True})
167 elif _action == 'do_scheduledrun':
168 logger.info("executing scheduled run")
169 # prepare scheduled run
Alexb78191f2021-11-02 16:35:46 -0500170
Alex2a7657c2021-11-10 20:51:34 -0600171 # Run it
172 _a(_options)
173 resp.status = falcon.HTTP_200
174 resp.content_type = "application/json"
175 resp.text = json.dumps({"ok": True})
176 else:
177 _msg = "Unknown error happened for '{}/{}/{}'".format(
178 _module,
179 _action,
180 _options
181 )
182 logger.error(_msg)
183 _resp(resp, falcon.HTTP_500, _msg)
184 except CheckerException as e:
185 _msg = "Error for '{}/{}':\n{}".format(
186 _module,
187 _action,
188 e
189 )
190 logger.error(_msg)
191 _resp(resp, falcon.HTTP_500, _msg, opt=_options)
Alexb78191f2021-11-02 16:35:46 -0500192 return
193 else:
Alex2a7657c2021-11-10 20:51:34 -0600194 _msg = "Empty request body"
195 logger.error(_msg)
196 _resp(resp, falcon.HTTP_400, _msg)
Alexb78191f2021-11-02 16:35:46 -0500197
198
199class Index:
200 @template.render("agent_index_html.j2")
201 def on_get(self, request, response):
202 # prepare template context
203 _context = {
204 "gen_date": get_time(),
205 "system": system(),
206 "release": release(),
207 "hostname": node()
208 }
209 _context.update(_status)
210 # creating response
211 response.status = falcon.HTTP_200
212 response.content_type = "text/html"
213 response.context = _context
214
215
216def agent_server(host="0.0.0.0", port=8765):
217 # init api
218 api = falcon.API()
219 # populate pages
220 api.add_route("/", Index())
221 api.add_route("/api/", Api())
222
223 # Populate modules list
224 _active_modules = [FioStatus]
225 # init modules
226 for mod in _active_modules:
227 _init_status(mod._name)
228 _update_status(mod._name)
229
230 api.add_route("/api/"+mod._name, mod())
231
232 # init and start server
233 server = pywsgi.WSGIServer((host, port), api)
234 server.serve_forever()