1 ##############################################################################
2 # Copyright (c) 2015 Orange
3 # guyrodrigue.koffi@orange.com / koffirodrigue@gmail.com
4 # All rights reserved. This program and the accompanying materials
5 # are made available under the terms of the Apache License, Version 2.0
6 # which accompanies this distribution, and is available at
7 # http://www.apache.org/licenses/LICENSE-2.0
8 # feng.xiaowei@zte.com.cn refactor db.pod to db.pods 5-19-2016
9 # feng.xiaowei@zte.com.cn refactor test_project to project 5-19-2016
10 # feng.xiaowei@zte.com.cn refactor response body 5-19-2016
11 # feng.xiaowei@zte.com.cn refactor pod/project response info 5-19-2016
12 # feng.xiaowei@zte.com.cn refactor testcase related handler 5-20-2016
13 # feng.xiaowei@zte.com.cn refactor result related handler 5-23-2016
14 # feng.xiaowei@zte.com.cn refactor dashboard related handler 5-24-2016
15 # feng.xiaowei@zte.com.cn add methods to GenericApiHandler 5-26-2016
16 # feng.xiaowei@zte.com.cn remove PodHandler 5-26-2016
17 ##############################################################################
20 from datetime import datetime, timedelta
22 from tornado.web import RequestHandler, asynchronous, HTTPError
23 from tornado import gen
25 from models import CreateResponse
26 from resources.result_models import TestResult
27 from common.constants import DEFAULT_REPRESENTATION, HTTP_BAD_REQUEST, \
28 HTTP_NOT_FOUND, HTTP_FORBIDDEN
29 from common.config import prepare_put_request
30 from dashboard.dashboard_utils import check_dashboard_ready_project, \
31 check_dashboard_ready_case, get_dashboard_result
34 def format_data(data, cls):
35 cls_data = cls.from_dict(data)
36 return cls_data.format_http()
39 class GenericApiHandler(RequestHandler):
40 def __init__(self, application, request, **kwargs):
41 super(GenericApiHandler, self).__init__(application, request, **kwargs)
42 self.db = self.settings["db"]
46 self.db_projects = 'projects'
48 self.db_testcases = 'testcases'
49 self.db_results = 'results'
52 if self.request.method != "GET" and self.request.method != "DELETE":
53 if self.request.headers.get("Content-Type") is not None:
54 if self.request.headers["Content-Type"].startswith(
55 DEFAULT_REPRESENTATION):
57 self.json_args = json.loads(self.request.body)
58 except (ValueError, KeyError, TypeError) as error:
59 raise HTTPError(HTTP_BAD_REQUEST,
60 "Bad Json format [{}]".
63 def finish_request(self, json_object=None):
65 self.write(json.dumps(json_object))
66 self.set_header("Content-Type", DEFAULT_REPRESENTATION)
69 def _create_response(self, resource):
70 href = self.request.full_url() + '/' + resource
71 return CreateResponse(href=href).format()
75 def _create(self, db_checks, **kwargs):
77 :param db_checks: [(table, exist, query, (error, message))]
78 :param db_op: (insert/remove)
79 :param res_op: (_create_response/None)
82 if self.json_args is None:
83 raise HTTPError(HTTP_BAD_REQUEST, "no body")
85 data = self.table_cls.from_dict(self.json_args)
87 if name is None or name == '':
88 raise HTTPError(HTTP_BAD_REQUEST,
89 '{} name missing'.format(self.table[:-1]))
91 for k, v in kwargs.iteritems():
92 data.__setattr__(k, v)
94 for table, exist, query, error in db_checks:
95 check = yield self._eval_db(table, 'find_one', query(data))
96 if (exist and not check) or (not exist and check):
97 code, message = error(data)
98 raise HTTPError(code, message)
100 data.creation_date = datetime.now()
101 yield self._eval_db(self.table, 'insert', data.format())
102 self.finish_request(self._create_response(name))
106 def _list(self, query=None):
110 cursor = self._eval_db(self.table, 'find', query)
111 while (yield cursor.fetch_next):
112 res.append(format_data(cursor.next_object(), self.table_cls))
113 self.finish_request({self.table: res})
117 def _get_one(self, query):
118 data = yield self._eval_db(self.table, 'find_one', query)
120 raise HTTPError(HTTP_NOT_FOUND,
121 "[{}] not exist in table [{}]"
122 .format(query, self.table))
123 self.finish_request(format_data(data, self.table_cls))
127 def _delete(self, query):
128 data = yield self._eval_db(self.table, 'find_one', query)
130 raise HTTPError(HTTP_NOT_FOUND,
131 "[{}] not exit in table [{}]"
132 .format(query, self.table))
134 yield self._eval_db(self.table, 'remove', query)
135 self.finish_request()
139 def _update(self, query, db_keys):
140 if self.json_args is None:
141 raise HTTPError(HTTP_BAD_REQUEST, "No payload")
143 # check old data exist
144 from_data = yield self._eval_db(self.table, 'find_one', query)
145 if from_data is None:
146 raise HTTPError(HTTP_NOT_FOUND,
147 "{} could not be found in table [{}]"
148 .format(query, self.table))
150 data = self.table_cls.from_dict(from_data)
151 # check new data exist
152 equal, new_query = self._update_query(db_keys, data)
154 to_data = yield self._eval_db(self.table, 'find_one', new_query)
155 if to_data is not None:
156 raise HTTPError(HTTP_FORBIDDEN,
157 "{} already exists in table [{}]"
158 .format(new_query, self.table))
160 # we merge the whole document """
161 edit_request = data.format()
162 edit_request.update(self._update_request(data))
164 """ Updating the DB """
165 yield self._eval_db(self.table, 'update', query, edit_request)
166 edit_request['_id'] = str(data._id)
167 self.finish_request(edit_request)
169 def _update_request(self, data):
171 for k, v in self.json_args.iteritems():
172 request = prepare_put_request(request, k, v,
173 data.__getattribute__(k))
175 raise HTTPError(HTTP_FORBIDDEN, "Nothing to update")
178 def _update_query(self, keys, data):
182 new = self.json_args.get(key)
183 old = data.__getattribute__(key)
191 def _eval_db(self, table, method, *args):
192 return eval('self.db.%s.%s(*args)' % (table, method))
195 class VersionHandler(GenericApiHandler):
196 """ Display a message for the API version """
198 self.finish_request([{'v1': 'basics'}])
201 class TestResultsHandler(GenericApiHandler):
203 TestResultsHandler Class
204 Handle the requests about the Test project's results
206 - GET : Get all test results and details about a specific one
207 - POST : Add a test results
208 - DELETE : Remove a test result
211 def initialize(self):
212 """ Prepares the database for the entire class """
213 super(TestResultsHandler, self).initialize()
214 self.name = "test_result"
218 def get(self, result_id=None):
220 Retrieve result(s) for a test project on a specific POD.
221 Available filters for this request are :
222 - project : project name
225 - version : platform version (Arno-R1, ...)
226 - installer (fuel, ...)
227 - build_tag : Jenkins build tag name
228 - period : x (x last days)
229 - scenario : the test scenario (previously version)
230 - criteria : the global criteria status passed or failed
231 - trust_indicator : evaluate the stability of the test case to avoid
232 running systematically long and stable test case
235 :param result_id: Get a result by ID
238 GET /results/project=functest&case=vPing&version=Arno-R1 \
239 &pod=pod_name&period=15
240 => get results with optional filters
245 if result_id is not None:
246 query["_id"] = result_id
247 answer = yield self.db.results.find_one(query)
249 raise HTTPError(HTTP_NOT_FOUND,
250 "test result {} Not Exist".format(result_id))
252 answer = format_data(answer, TestResult)
254 pod_arg = self.get_query_argument("pod", None)
255 project_arg = self.get_query_argument("project", None)
256 case_arg = self.get_query_argument("case", None)
257 version_arg = self.get_query_argument("version", None)
258 installer_arg = self.get_query_argument("installer", None)
259 build_tag_arg = self.get_query_argument("build_tag", None)
260 scenario_arg = self.get_query_argument("scenario", None)
261 criteria_arg = self.get_query_argument("criteria", None)
262 period_arg = self.get_query_argument("period", None)
263 trust_indicator_arg = self.get_query_argument("trust_indicator",
266 if project_arg is not None:
267 query["project_name"] = project_arg
269 if case_arg is not None:
270 query["case_name"] = case_arg
272 if pod_arg is not None:
273 query["pod_name"] = pod_arg
275 if version_arg is not None:
276 query["version"] = version_arg
278 if installer_arg is not None:
279 query["installer"] = installer_arg
281 if build_tag_arg is not None:
282 query["build_tag"] = build_tag_arg
284 if scenario_arg is not None:
285 query["scenario"] = scenario_arg
287 if criteria_arg is not None:
288 query["criteria_tag"] = criteria_arg
290 if trust_indicator_arg is not None:
291 query["trust_indicator_arg"] = trust_indicator_arg
293 if period_arg is not None:
295 period_arg = int(period_arg)
297 raise HTTPError(HTTP_BAD_REQUEST)
300 period = datetime.now() - timedelta(days=period_arg)
301 obj = {"$gte": str(period)}
302 query["creation_date"] = obj
305 cursor = self.db.results.find(query)
306 while (yield cursor.fetch_next):
307 res.append(format_data(cursor.next_object(), TestResult))
308 answer = {'results': res}
310 self.finish_request(answer)
316 Create a new test result
317 :return: status of the request
321 # check for request payload
322 if self.json_args is None:
323 raise HTTPError(HTTP_BAD_REQUEST, 'no payload')
325 result = TestResult.from_dict(self.json_args)
327 # check for pod_name instead of id,
328 # keeping id for current implementations
329 if result.pod_name is None:
330 raise HTTPError(HTTP_BAD_REQUEST, 'pod is not provided')
332 # check for missing parameters in the request payload
333 if result.project_name is None:
334 raise HTTPError(HTTP_BAD_REQUEST, 'project is not provided')
336 if result.case_name is None:
337 raise HTTPError(HTTP_BAD_REQUEST, 'testcase is not provided')
339 # TODO : replace checks with jsonschema
341 the_pod = yield self.db.pods.find_one({"name": result.pod_name})
343 raise HTTPError(HTTP_NOT_FOUND,
344 "Could not find POD [{}] "
345 .format(self.json_args.get("pod_name")))
348 the_project = yield self.db.projects.find_one(
349 {"name": result.project_name})
350 if the_project is None:
351 raise HTTPError(HTTP_NOT_FOUND, "Could not find project [{}] "
352 .format(result.project_name))
355 the_testcase = yield self.db.testcases.find_one(
356 {"name": result.case_name})
357 if the_testcase is None:
358 raise HTTPError(HTTP_NOT_FOUND,
359 "Could not find testcase [{}] "
360 .format(result.case_name))
362 _id = yield self.db.results.insert(result.format(), check_keys=False)
364 self.finish_request(self._create_response(_id))
367 class DashboardHandler(GenericApiHandler):
369 DashboardHandler Class
370 Handle the requests about the Test project's results
371 in a dahboard ready format
373 - GET : Get all test results and details about a specific one
375 def initialize(self):
376 """ Prepares the database for the entire class """
377 super(DashboardHandler, self).initialize()
378 self.name = "dashboard"
384 Retrieve dashboard ready result(s) for a test project
385 Available filters for this request are :
386 - project : project name
389 - version : platform version (Arno-R1, ...)
390 - installer (fuel, ...)
391 - period : x (x last days)
394 :param result_id: Get a result by ID
397 GET /dashboard?project=functest&case=vPing&version=Arno-R1 \
398 &pod=pod_name&period=15
399 => get results with optional filters
402 project_arg = self.get_query_argument("project", None)
403 case_arg = self.get_query_argument("case", None)
404 pod_arg = self.get_query_argument("pod", None)
405 version_arg = self.get_query_argument("version", None)
406 installer_arg = self.get_query_argument("installer", None)
407 period_arg = self.get_query_argument("period", None)
412 if project_arg is not None:
413 query["project_name"] = project_arg
415 if case_arg is not None:
416 query["case_name"] = case_arg
418 if pod_arg is not None:
419 query["pod_name"] = pod_arg
421 if version_arg is not None:
422 query["version"] = version_arg
424 if installer_arg is not None:
425 query["installer"] = installer_arg
427 if period_arg is not None:
429 period_arg = int(period_arg)
431 raise HTTPError(HTTP_BAD_REQUEST)
433 period = datetime.now() - timedelta(days=period_arg)
434 obj = {"$gte": str(period)}
435 query["creation_date"] = obj
437 # on /dashboard retrieve the list of projects and testcases
438 # ready for dashboard
439 if project_arg is None:
440 raise HTTPError(HTTP_NOT_FOUND, "Project name missing")
442 if not check_dashboard_ready_project(project_arg):
443 raise HTTPError(HTTP_NOT_FOUND,
444 'Project [{}] not dashboard ready'
445 .format(project_arg))
450 'Test case missing for project [{}]'.format(project_arg))
452 if not check_dashboard_ready_case(project_arg, case_arg):
455 'Test case [{}] not dashboard ready for project [{}]'
456 .format(case_arg, project_arg))
458 # special case of status for project
460 if case_arg != "status":
461 cursor = self.db.results.find(query)
462 while (yield cursor.fetch_next):
463 result = TestResult.from_dict(cursor.next_object())
464 res.append(result.format_http())
466 # final response object
467 self.finish_request(get_dashboard_result(project_arg, case_arg, res))