blob: 7f7e53aa5d506513e4fd0ce09cce4199f584d0b5 [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:
Alexbfa947c2021-11-11 18:14:28 -0600103 _m = req.stream.read()
104 _m = json.loads(_m)
105 except json.JSONDecodeError:
Alex2a7657c2021-11-10 20:51:34 -0600106 _msg = "Incorrect input data"
107 logger.error(_msg)
108 _resp(resp, falcon.HTTP_400, _msg)
109 return
Alexb78191f2021-11-02 16:35:46 -0500110 if _m:
Alex2a7657c2021-11-10 20:51:34 -0600111 logger.debug("got media object:\n{}".format(json.dumps(_m)))
Alexb78191f2021-11-02 16:35:46 -0500112 # Validate action structure
113 _module = _m.get('module', "")
114 _action = _m.get('action', "")
115 _options = _m.get('options', {})
116
117 if not _module or _module not in list(_modules.keys()):
Alex2a7657c2021-11-10 20:51:34 -0600118 logger.error("invalid module '{}'".format(_module))
Alexb78191f2021-11-02 16:35:46 -0500119 _resp(
120 resp,
121 falcon.HTTP_400,
122 "Invalid module '{}'".format(_module)
123 )
124 return
125 elif not _action or _action not in _modules[_module]['actions']:
Alex2a7657c2021-11-10 20:51:34 -0600126 logger.error("invalid action '{}'".format(_action))
Alexb78191f2021-11-02 16:35:46 -0500127 _resp(
128 resp,
129 falcon.HTTP_400,
130 "Invalid action '{}'".format(_action)
131 )
132 return
133 else:
134 # Handle command
135 _a = _fio.actions[_action]
Alex2a7657c2021-11-10 20:51:34 -0600136 try:
137 if _action == 'get_options':
138 logger.info("returning options")
139 resp.status = falcon.HTTP_200
140 resp.content_type = "application/json"
141 resp.text = json.dumps({"options": _a()})
142 elif _action == 'get_resultlist':
143 logger.info("getting results")
144 resp.status = falcon.HTTP_200
145 resp.content_type = "application/json"
146 resp.text = json.dumps({"resultlist": _a()})
147 elif _action == 'get_result':
148 if 'time' not in _options:
149 _msg = "No 'time' found for '{}'".format(_action)
150 logger.error(_msg)
151 _resp(
152 resp,
153 falcon.HTTP_400,
154 _msg
155 )
156 return
157 _time = _options['time']
158 logger.info("getting results for '{}'".format(_time))
159 resp.status = falcon.HTTP_200
160 resp.content_type = "application/json"
Alex3034ba52021-11-13 17:06:45 -0600161 # TODO: get timeline too?
Alex2a7657c2021-11-10 20:51:34 -0600162 resp.text = json.dumps({_time: _a(_time)})
163 elif _action == 'do_singlerun':
164 logger.info("executing single run")
165 _a(_options)
166 resp.status = falcon.HTTP_200
167 resp.content_type = "application/json"
168 resp.text = json.dumps({"ok": True})
169 elif _action == 'do_scheduledrun':
170 logger.info("executing scheduled run")
171 # prepare scheduled run
Alexb78191f2021-11-02 16:35:46 -0500172
Alex2a7657c2021-11-10 20:51:34 -0600173 # Run it
174 _a(_options)
175 resp.status = falcon.HTTP_200
176 resp.content_type = "application/json"
177 resp.text = json.dumps({"ok": True})
178 else:
179 _msg = "Unknown error happened for '{}/{}/{}'".format(
180 _module,
181 _action,
182 _options
183 )
184 logger.error(_msg)
185 _resp(resp, falcon.HTTP_500, _msg)
186 except CheckerException as e:
187 _msg = "Error for '{}/{}':\n{}".format(
188 _module,
189 _action,
190 e
191 )
192 logger.error(_msg)
193 _resp(resp, falcon.HTTP_500, _msg, opt=_options)
Alexb78191f2021-11-02 16:35:46 -0500194 return
195 else:
Alex2a7657c2021-11-10 20:51:34 -0600196 _msg = "Empty request body"
197 logger.error(_msg)
198 _resp(resp, falcon.HTTP_400, _msg)
Alexb78191f2021-11-02 16:35:46 -0500199
200
201class Index:
202 @template.render("agent_index_html.j2")
203 def on_get(self, request, response):
204 # prepare template context
205 _context = {
206 "gen_date": get_time(),
207 "system": system(),
208 "release": release(),
209 "hostname": node()
210 }
211 _context.update(_status)
212 # creating response
213 response.status = falcon.HTTP_200
214 response.content_type = "text/html"
215 response.context = _context
216
217
218def agent_server(host="0.0.0.0", port=8765):
219 # init api
220 api = falcon.API()
221 # populate pages
222 api.add_route("/", Index())
223 api.add_route("/api/", Api())
224
225 # Populate modules list
226 _active_modules = [FioStatus]
227 # init modules
228 for mod in _active_modules:
229 _init_status(mod._name)
230 _update_status(mod._name)
231
232 api.add_route("/api/"+mod._name, mod())
233
234 # init and start server
235 server = pywsgi.WSGIServer((host, port), api)
236 server.serve_forever()