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 resources.testcase_models import Testcase
28 from common.constants import DEFAULT_REPRESENTATION, HTTP_BAD_REQUEST, \
29 HTTP_NOT_FOUND, HTTP_FORBIDDEN
30 from common.config import prepare_put_request
31 from dashboard.dashboard_utils import check_dashboard_ready_project, \
32 check_dashboard_ready_case, get_dashboard_result
35 def format_data(data, cls):
36 cls_data = cls.from_dict(data)
37 return cls_data.format_http()
40 class GenericApiHandler(RequestHandler):
41 def __init__(self, application, request, **kwargs):
42 super(GenericApiHandler, self).__init__(application, request, **kwargs)
43 self.db = self.settings["db"]
49 if self.request.method != "GET" and self.request.method != "DELETE":
50 if self.request.headers.get("Content-Type") is not None:
51 if self.request.headers["Content-Type"].startswith(
52 DEFAULT_REPRESENTATION):
54 self.json_args = json.loads(self.request.body)
55 except (ValueError, KeyError, TypeError) as error:
56 raise HTTPError(HTTP_BAD_REQUEST,
57 "Bad Json format [{}]".
60 def finish_request(self, json_object=None):
62 self.write(json.dumps(json_object))
63 self.set_header("Content-Type", DEFAULT_REPRESENTATION)
66 def _create_response(self, resource):
67 href = self.request.full_url() + '/' + resource
68 return CreateResponse(href=href).format()
72 def _create(self, error):
73 if self.json_args is None:
74 raise HTTPError(HTTP_BAD_REQUEST, 'no body')
76 data = self.table_cls.from_dict(self.json_args)
78 if name is None or name == '':
79 raise HTTPError(HTTP_BAD_REQUEST,
80 '{} name missing'.format(self.table[:-1]))
82 exist_data = yield self._eval_db(self.table, 'find_one',
84 if exist_data is not None:
85 raise HTTPError(HTTP_FORBIDDEN,
86 error.format(name, self.table[:-1]))
87 data.creation_date = datetime.now()
88 yield self._eval_db(self.table, 'insert', data.format())
89 self.finish_request(self._create_response(name))
93 def _list(self, query=None):
97 cursor = self._eval_db(self.table, 'find', query)
98 while (yield cursor.fetch_next):
99 res.append(format_data(cursor.next_object(), self.table_cls))
100 self.finish_request({self.table: res})
104 def _get_one(self, query):
105 data = yield self._eval_db(self.table, 'find_one', query)
107 raise HTTPError(HTTP_NOT_FOUND,
108 "[{}] not exist in table [{}]"
109 .format(query, self.table))
110 self.finish_request(format_data(data, self.table_cls))
114 def _delete(self, query):
115 data = yield self._eval_db(self.table, 'find_one', query)
117 raise HTTPError(HTTP_NOT_FOUND,
118 "[{}] not exit in table [{}]"
119 .format(query, self.table))
121 yield self._eval_db(self.table, 'remove', query)
122 self.finish_request()
124 def _eval_db(self, table, method, param):
125 return eval('self.db.%s.%s(param)' % (table, method))
128 class VersionHandler(GenericApiHandler):
129 """ Display a message for the API version """
131 self.finish_request([{'v1': 'basics'}])
134 class TestcaseHandler(GenericApiHandler):
136 TestCasesHandler Class
137 Handle the requests about the Test cases for test projects
139 - GET : Get all test cases and details about a specific one
140 - POST : Add a test project
141 - PUT : Edit test projects information (name and/or description)
144 def initialize(self):
145 """ Prepares the database for the entire class """
146 super(TestcaseHandler, self).initialize()
150 def get(self, project_name, case_name=None):
152 Get testcases(s) info
157 query = {'project_name': project_name}
159 if case_name is not None:
160 query["name"] = case_name
161 answer = yield self.db.testcases.find_one(query)
163 raise HTTPError(HTTP_NOT_FOUND,
164 "{} Not Exist".format(case_name))
166 answer = format_data(answer, Testcase)
169 cursor = self.db.testcases.find(query)
170 while (yield cursor.fetch_next):
171 res.append(format_data(cursor.next_object(), Testcase))
172 answer = {'testcases': res}
174 self.finish_request(answer)
178 def post(self, project_name):
179 """ Create a test case"""
181 if self.json_args is None:
182 raise HTTPError(HTTP_BAD_REQUEST,
183 "Check your request payload")
185 # retrieve test project
186 project = yield self.db.projects.find_one(
187 {"name": project_name})
189 raise HTTPError(HTTP_FORBIDDEN,
190 "Could not find project {}"
191 .format(project_name))
193 case_name = self.json_args.get('name')
194 the_testcase = yield self.db.testcases.find_one(
195 {"project_name": project_name, 'name': case_name})
197 raise HTTPError(HTTP_FORBIDDEN,
198 "{} already exists as a case in project {}"
199 .format(case_name, project_name))
201 testcase = Testcase.from_dict(self.json_args)
202 testcase.project_name = project_name
203 testcase.creation_date = datetime.now()
205 yield self.db.testcases.insert(testcase.format())
206 self.finish_request(self._create_response(testcase.name))
210 def put(self, project_name, case_name):
212 Updates the name and description of a test case
213 :raises HTTPError (HTTP_NOT_FOUND, HTTP_FORBIDDEN)
216 query = {'project_name': project_name, 'name': case_name}
218 if self.json_args is None:
219 raise HTTPError(HTTP_BAD_REQUEST, "No payload")
221 # check if there is a case for the project in url parameters
222 from_testcase = yield self.db.testcases.find_one(query)
223 if from_testcase is None:
224 raise HTTPError(HTTP_NOT_FOUND,
225 "{} could not be found as a {} case to be updated"
226 .format(case_name, project_name))
228 testcase = Testcase.from_dict(from_testcase)
229 new_name = self.json_args.get("name")
230 new_project_name = self.json_args.get("project_name")
231 if not new_project_name:
232 new_project_name = project_name
233 new_description = self.json_args.get("description")
235 # check if there is not an existing test case
236 # with the name provided in the json payload
237 if new_name != case_name or new_project_name != project_name:
238 to_testcase = yield self.db.testcases.find_one(
239 {'project_name': new_project_name, 'name': new_name})
240 if to_testcase is not None:
241 raise HTTPError(HTTP_FORBIDDEN,
242 "{} already exists as a case in project"
243 .format(new_name, new_project_name))
245 # new dict for changes
247 request = prepare_put_request(request,
251 request = prepare_put_request(request,
254 testcase.project_name)
255 request = prepare_put_request(request,
258 testcase.description)
260 # we raise an exception if there isn't a change
262 raise HTTPError(HTTP_FORBIDDEN,
265 # we merge the whole document """
266 edit_request = testcase.format()
267 edit_request.update(request)
269 """ Updating the DB """
270 yield self.db.testcases.update(query, edit_request)
271 new_case = yield self.db.testcases.find_one({"_id": testcase._id})
272 self.finish_request(format_data(new_case, Testcase))
276 def delete(self, project_name, case_name):
277 """ Remove a test case"""
279 query = {'project_name': project_name, 'name': case_name}
281 # check for an existing case to be deleted
282 testcase = yield self.db.testcases.find_one(query)
284 raise HTTPError(HTTP_NOT_FOUND,
285 "{}/{} could not be found as a case to be deleted"
286 .format(project_name, case_name))
288 # just delete it, or maybe save it elsewhere in a future
289 yield self.db.testcases.remove(query)
290 self.finish_request()
293 class TestResultsHandler(GenericApiHandler):
295 TestResultsHandler Class
296 Handle the requests about the Test project's results
298 - GET : Get all test results and details about a specific one
299 - POST : Add a test results
300 - DELETE : Remove a test result
303 def initialize(self):
304 """ Prepares the database for the entire class """
305 super(TestResultsHandler, self).initialize()
306 self.name = "test_result"
310 def get(self, result_id=None):
312 Retrieve result(s) for a test project on a specific POD.
313 Available filters for this request are :
314 - project : project name
317 - version : platform version (Arno-R1, ...)
318 - installer (fuel, ...)
319 - build_tag : Jenkins build tag name
320 - period : x (x last days)
321 - scenario : the test scenario (previously version)
322 - criteria : the global criteria status passed or failed
323 - trust_indicator : evaluate the stability of the test case to avoid
324 running systematically long and stable test case
327 :param result_id: Get a result by ID
330 GET /results/project=functest&case=vPing&version=Arno-R1 \
331 &pod=pod_name&period=15
332 => get results with optional filters
337 if result_id is not None:
338 query["_id"] = result_id
339 answer = yield self.db.results.find_one(query)
341 raise HTTPError(HTTP_NOT_FOUND,
342 "test result {} Not Exist".format(result_id))
344 answer = format_data(answer, TestResult)
346 pod_arg = self.get_query_argument("pod", None)
347 project_arg = self.get_query_argument("project", None)
348 case_arg = self.get_query_argument("case", None)
349 version_arg = self.get_query_argument("version", None)
350 installer_arg = self.get_query_argument("installer", None)
351 build_tag_arg = self.get_query_argument("build_tag", None)
352 scenario_arg = self.get_query_argument("scenario", None)
353 criteria_arg = self.get_query_argument("criteria", None)
354 period_arg = self.get_query_argument("period", None)
355 trust_indicator_arg = self.get_query_argument("trust_indicator",
358 if project_arg is not None:
359 query["project_name"] = project_arg
361 if case_arg is not None:
362 query["case_name"] = case_arg
364 if pod_arg is not None:
365 query["pod_name"] = pod_arg
367 if version_arg is not None:
368 query["version"] = version_arg
370 if installer_arg is not None:
371 query["installer"] = installer_arg
373 if build_tag_arg is not None:
374 query["build_tag"] = build_tag_arg
376 if scenario_arg is not None:
377 query["scenario"] = scenario_arg
379 if criteria_arg is not None:
380 query["criteria_tag"] = criteria_arg
382 if trust_indicator_arg is not None:
383 query["trust_indicator_arg"] = trust_indicator_arg
385 if period_arg is not None:
387 period_arg = int(period_arg)
389 raise HTTPError(HTTP_BAD_REQUEST)
392 period = datetime.now() - timedelta(days=period_arg)
393 obj = {"$gte": str(period)}
394 query["creation_date"] = obj
397 cursor = self.db.results.find(query)
398 while (yield cursor.fetch_next):
399 res.append(format_data(cursor.next_object(), TestResult))
400 answer = {'results': res}
402 self.finish_request(answer)
408 Create a new test result
409 :return: status of the request
413 # check for request payload
414 if self.json_args is None:
415 raise HTTPError(HTTP_BAD_REQUEST, 'no payload')
417 result = TestResult.from_dict(self.json_args)
419 # check for pod_name instead of id,
420 # keeping id for current implementations
421 if result.pod_name is None:
422 raise HTTPError(HTTP_BAD_REQUEST, 'pod is not provided')
424 # check for missing parameters in the request payload
425 if result.project_name is None:
426 raise HTTPError(HTTP_BAD_REQUEST, 'project is not provided')
428 if result.case_name is None:
429 raise HTTPError(HTTP_BAD_REQUEST, 'testcase is not provided')
431 # TODO : replace checks with jsonschema
433 the_pod = yield self.db.pods.find_one({"name": result.pod_name})
435 raise HTTPError(HTTP_NOT_FOUND,
436 "Could not find POD [{}] "
437 .format(self.json_args.get("pod_name")))
440 the_project = yield self.db.projects.find_one(
441 {"name": result.project_name})
442 if the_project is None:
443 raise HTTPError(HTTP_NOT_FOUND, "Could not find project [{}] "
444 .format(result.project_name))
447 the_testcase = yield self.db.testcases.find_one(
448 {"name": result.case_name})
449 if the_testcase is None:
450 raise HTTPError(HTTP_NOT_FOUND,
451 "Could not find testcase [{}] "
452 .format(result.case_name))
454 _id = yield self.db.results.insert(result.format(), check_keys=False)
456 self.finish_request(self._create_response(_id))
459 class DashboardHandler(GenericApiHandler):
461 DashboardHandler Class
462 Handle the requests about the Test project's results
463 in a dahboard ready format
465 - GET : Get all test results and details about a specific one
467 def initialize(self):
468 """ Prepares the database for the entire class """
469 super(DashboardHandler, self).initialize()
470 self.name = "dashboard"
476 Retrieve dashboard ready result(s) for a test project
477 Available filters for this request are :
478 - project : project name
481 - version : platform version (Arno-R1, ...)
482 - installer (fuel, ...)
483 - period : x (x last days)
486 :param result_id: Get a result by ID
489 GET /dashboard?project=functest&case=vPing&version=Arno-R1 \
490 &pod=pod_name&period=15
491 => get results with optional filters
494 project_arg = self.get_query_argument("project", None)
495 case_arg = self.get_query_argument("case", None)
496 pod_arg = self.get_query_argument("pod", None)
497 version_arg = self.get_query_argument("version", None)
498 installer_arg = self.get_query_argument("installer", None)
499 period_arg = self.get_query_argument("period", None)
504 if project_arg is not None:
505 query["project_name"] = project_arg
507 if case_arg is not None:
508 query["case_name"] = case_arg
510 if pod_arg is not None:
511 query["pod_name"] = pod_arg
513 if version_arg is not None:
514 query["version"] = version_arg
516 if installer_arg is not None:
517 query["installer"] = installer_arg
519 if period_arg is not None:
521 period_arg = int(period_arg)
523 raise HTTPError(HTTP_BAD_REQUEST)
525 period = datetime.now() - timedelta(days=period_arg)
526 obj = {"$gte": str(period)}
527 query["creation_date"] = obj
529 # on /dashboard retrieve the list of projects and testcases
530 # ready for dashboard
531 if project_arg is None:
532 raise HTTPError(HTTP_NOT_FOUND, "Project name missing")
534 if not check_dashboard_ready_project(project_arg):
535 raise HTTPError(HTTP_NOT_FOUND,
536 'Project [{}] not dashboard ready'
537 .format(project_arg))
542 'Test case missing for project [{}]'.format(project_arg))
544 if not check_dashboard_ready_case(project_arg, case_arg):
547 'Test case [{}] not dashboard ready for project [{}]'
548 .format(case_arg, project_arg))
550 # special case of status for project
552 if case_arg != "status":
553 cursor = self.db.results.find(query)
554 while (yield cursor.fetch_next):
555 result = TestResult.from_dict(cursor.next_object())
556 res.append(result.format_http())
558 # final response object
559 self.finish_request(get_dashboard_result(project_arg, case_arg, res))