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 ##############################################################################
12 from tornado.web import RequestHandler, asynchronous, HTTPError
13 from tornado import gen
14 from datetime import datetime, timedelta
16 from models import Pod, TestProject, TestCase, TestResult
17 from common.constants import DEFAULT_REPRESENTATION, HTTP_BAD_REQUEST, \
18 HTTP_NOT_FOUND, HTTP_FORBIDDEN
19 from common.config import prepare_put_request
21 from dashboard.dashboard_utils import get_dashboard_cases, \
22 check_dashboard_ready_project, check_dashboard_ready_case, \
26 class GenericApiHandler(RequestHandler):
28 The purpose of this class is to take benefit of inheritance and prepare
29 a set of common functions for
34 """ Prepares the database for the entire class """
35 self.db = self.settings["db"]
38 if not (self.request.method == "GET"):
39 if self.request.headers.get("Content-Type") is not None:
40 if self.request.headers["Content-Type"].startswith(
41 DEFAULT_REPRESENTATION):
43 self.json_args = json.loads(self.request.body)
44 except (ValueError, KeyError, TypeError) as error:
45 raise HTTPError(HTTP_BAD_REQUEST,
46 "Bad Json format [{}]".
51 def finish_request(self, json_object):
52 self.write(json.dumps(json_object))
53 self.set_header("Content-Type", DEFAULT_REPRESENTATION)
57 class VersionHandler(RequestHandler):
58 """ Display a message for the API version """
60 self.write("Collection of test result API, v1")
63 class PodHandler(GenericApiHandler):
64 """ Handle the requests about the POD Platforms
72 """ Prepares the database for the entire class """
73 super(PodHandler, self).initialize()
77 def get(self, pod_name=None):
79 Get all pods or a single pod
84 if pod_name is not None:
85 get_request["name"] = pod_name
88 cursor = self.db.pod.find(get_request)
89 while (yield cursor.fetch_next):
90 pod = Pod.pod_from_dict(cursor.next_object())
91 res.append(pod.format())
94 meta["total"] = len(res)
95 meta["success"] = True if len(res) > 0 else False
101 self.finish_request(answer)
108 if self.json_args is None:
109 raise HTTPError(HTTP_BAD_REQUEST)
111 query = {"name": self.json_args.get("name")}
113 # check for existing name in db
114 mongo_dict = yield self.db.pod.find_one(query)
115 if mongo_dict is not None:
116 raise HTTPError(HTTP_FORBIDDEN,
117 "{} already exists as a pod".format(
118 self.json_args.get("name")))
120 pod = Pod.pod_from_dict(self.json_args)
121 pod.creation_date = datetime.now()
123 future = self.db.pod.insert(pod.format())
124 result = yield future
128 meta["success"] = True
129 meta["uri"] = "/pods/{}".format(pod.name)
132 answer["pod"] = pod.format_http()
133 answer["meta"] = meta
135 self.finish_request(answer)
139 def delete(self, pod_name):
142 # check for an existing pod to be deleted
143 mongo_dict = yield self.db.pod.find_one(
145 pod = TestProject.pod(mongo_dict)
147 raise HTTPError(HTTP_NOT_FOUND,
148 "{} could not be found as a pod to be deleted"
151 # just delete it, or maybe save it elsewhere in a future
152 res = yield self.db.test_projects.remove(
156 meta["success"] = True
157 meta["deletion-data"] = res
160 answer["meta"] = meta
162 self.finish_request(answer)
167 class TestProjectHandler(GenericApiHandler):
169 TestProjectHandler Class
170 Handle the requests about the Test projects
172 - GET : Get all test projects and details about a specific one
173 - POST : Add a test project
174 - PUT : Edit test projects information (name and/or description)
175 - DELETE : Remove a test project
178 def initialize(self):
179 """ Prepares the database for the entire class """
180 super(TestProjectHandler, self).initialize()
184 def get(self, project_name=None):
190 if project_name is None:
195 if len(project_name) > 0:
196 get_request["name"] = project_name
199 cursor = self.db.test_projects.find(get_request)
200 while (yield cursor.fetch_next):
201 test_project = TestProject.testproject_from_dict(
202 cursor.next_object())
203 res.append(test_project.format_http())
206 meta["total"] = len(res)
207 meta["success"] = True if len(res) > 0 else False
210 answer["test_projects"] = res
211 answer["meta"] = meta
213 self.finish_request(answer)
218 """ Create a test project"""
220 if self.json_args is None:
221 raise HTTPError(HTTP_BAD_REQUEST)
223 query = {"name": self.json_args.get("name")}
225 # check for name in db
226 mongo_dict = yield self.db.test_projects.find_one(query)
227 if mongo_dict is not None:
228 raise HTTPError(HTTP_FORBIDDEN,
229 "{} already exists as a project".format(
230 self.json_args.get("name")))
232 test_project = TestProject.testproject_from_dict(self.json_args)
233 test_project.creation_date = datetime.now()
235 future = self.db.test_projects.insert(test_project.format())
236 result = yield future
237 test_project._id = result
239 self.finish_request(test_project.format_http())
243 def put(self, project_name):
244 """ Updates the name and description of a test project"""
246 print "PUT request for : {}".format(project_name)
248 query = {'name': project_name}
249 mongo_dict = yield self.db.test_projects.find_one(query)
250 test_project = TestProject.testproject_from_dict(mongo_dict)
251 if test_project is None:
252 raise HTTPError(HTTP_NOT_FOUND,
253 "{} could not be found".format(project_name))
255 new_name = self.json_args.get("name")
256 new_description = self.json_args.get("description")
258 # check for payload name parameter in db
259 # avoid a request if the project name has not changed in the payload
260 if new_name != test_project.name:
261 mongo_dict = yield self.db.test_projects.find_one(
263 if mongo_dict is not None:
264 raise HTTPError(HTTP_FORBIDDEN,
265 "{} already exists as a project"
268 # new dict for changes
270 request = prepare_put_request(request,
274 request = prepare_put_request(request,
277 test_project.description)
279 """ raise exception if there isn't a change """
281 raise HTTPError(HTTP_FORBIDDEN,
284 """ we merge the whole document """
285 edit_request = test_project.format()
286 edit_request.update(request)
288 """ Updating the DB """
289 res = yield self.db.test_projects.update({'name': project_name},
292 edit_request["_id"] = str(test_project._id)
294 self.finish_request({"message": "success", "content": edit_request})
298 def delete(self, project_name):
299 """ Remove a test project"""
301 print "DELETE request for : {}".format(project_name)
303 # check for an existing project to be deleted
304 mongo_dict = yield self.db.test_projects.find_one(
305 {'name': project_name})
306 test_project = TestProject.testproject_from_dict(mongo_dict)
307 if test_project is None:
308 raise HTTPError(HTTP_NOT_FOUND,
309 "{} could not be found as a project to be deleted"
310 .format(project_name))
312 # just delete it, or maybe save it elsewhere in a future
313 res = yield self.db.test_projects.remove(
314 {'name': project_name})
317 self.finish_request({"message": "success"})
320 class TestCasesHandler(GenericApiHandler):
322 TestCasesHandler Class
323 Handle the requests about the Test cases for test projects
325 - GET : Get all test cases and details about a specific one
326 - POST : Add a test project
327 - PUT : Edit test projects information (name and/or description)
330 def initialize(self):
331 """ Prepares the database for the entire class """
332 super(TestCasesHandler, self).initialize()
336 def get(self, project_name, case_name=None):
338 Get testcases(s) info
343 if case_name is None:
347 get_request["project_name"] = project_name
349 if len(case_name) > 0:
350 get_request["name"] = case_name
353 cursor = self.db.test_cases.find(get_request)
355 while (yield cursor.fetch_next):
356 test_case = TestCase.test_case_from_dict(cursor.next_object())
357 res.append(test_case.format_http())
360 meta["total"] = len(res)
361 meta["success"] = True if len(res) > 0 else False
364 answer["test_cases"] = res
365 answer["meta"] = meta
367 self.finish_request(answer)
371 def post(self, project_name):
372 """ Create a test case"""
374 print "POST Request for {}".format(project_name)
376 if self.json_args is None:
377 raise HTTPError(HTTP_BAD_REQUEST,
378 "Check your request payload")
380 # retrieve test project
381 mongo_dict = yield self.db.test_projects.find_one(
382 {"name": project_name})
383 if mongo_dict is None:
384 raise HTTPError(HTTP_FORBIDDEN,
385 "Could not find project {}"
386 .format(project_name))
388 # test_project = TestProject.testproject_from_dict(self.json_args)
390 case = TestCase.test_case_from_dict(self.json_args)
391 case.project_name = project_name
392 case.creation_date = datetime.now()
394 future = self.db.test_cases.insert(case.format())
395 result = yield future
397 self.finish_request(case.format_http())
401 def put(self, project_name, case_name):
403 Updates the name and description of a test case
404 :raises HTTPError (HTTP_NOT_FOUND, HTTP_FORBIDDEN)
407 print "PUT request for : {}/{}".format(project_name, case_name)
408 case_request = {'project_name': project_name, 'name': case_name}
410 # check if there is a case for the project in url parameters
411 mongo_dict = yield self.db.test_cases.find_one(case_request)
412 test_case = TestCase.test_case_from_dict(mongo_dict)
413 if test_case is None:
414 raise HTTPError(HTTP_NOT_FOUND,
415 "{} could not be found as a {} case to be updated"
416 .format(case_name, project_name))
418 new_name = self.json_args.get("name")
419 new_project_name = self.json_args.get("project_name")
420 new_description = self.json_args.get("description")
422 # check if there is not an existing test case
423 # with the name provided in the json payload
424 mongo_dict = yield self.db.test_cases.find_one(
425 {'project_name': new_project_name, 'name': new_name})
426 if mongo_dict is not None:
427 raise HTTPError(HTTP_FORBIDDEN,
428 "{} already exists as a project"
431 # new dict for changes
433 request = prepare_put_request(request,
437 request = prepare_put_request(request,
440 test_case.project_name)
441 request = prepare_put_request(request,
444 test_case.description)
446 # we raise an exception if there isn't a change
448 raise HTTPError(HTTP_FORBIDDEN,
451 # we merge the whole document """
452 edit_request = test_case.format()
453 edit_request.update(request)
455 """ Updating the DB """
456 res = yield self.db.test_cases.update(case_request, edit_request)
458 edit_request["_id"] = str(test_case._id)
460 self.finish_request({"message": "success", "content": edit_request})
464 def delete(self, project_name, case_name):
465 """ Remove a test case"""
467 print "DELETE request for : {}/{}".format(project_name, case_name)
468 case_request = {'project_name': project_name, 'name': case_name}
470 # check for an existing case to be deleted
471 mongo_dict = yield self.db.test_cases.find_one(case_request)
472 test_project = TestProject.testproject_from_dict(mongo_dict)
473 if test_project is None:
474 raise HTTPError(HTTP_NOT_FOUND,
475 "{}/{} could not be found as a case to be deleted"
476 .format(project_name, case_name))
478 # just delete it, or maybe save it elsewhere in a future
479 res = yield self.db.test_projects.remove(case_request)
482 self.finish_request({"message": "success"})
485 class TestResultsHandler(GenericApiHandler):
487 TestResultsHandler Class
488 Handle the requests about the Test project's results
490 - GET : Get all test results and details about a specific one
491 - POST : Add a test results
492 - DELETE : Remove a test result
495 def initialize(self):
496 """ Prepares the database for the entire class """
497 super(TestResultsHandler, self).initialize()
498 self.name = "test_result"
502 def get(self, result_id=None):
504 Retrieve result(s) for a test project on a specific POD.
505 Available filters for this request are :
506 - project : project name
509 - version : platform version (Arno-R1, ...)
510 - installer (fuel, ...)
511 - build_tag : Jenkins build tag name
512 - period : x (x last days)
513 - scenario : the test scenario (previously version)
514 - criteria : the global criteria status passed or failed
517 :param result_id: Get a result by ID
520 GET /results/project=functest&case=vPing&version=Arno-R1 \
521 &pod=pod_name&period=15
522 => get results with optional filters
525 project_arg = self.get_query_argument("project", None)
526 case_arg = self.get_query_argument("case", None)
527 pod_arg = self.get_query_argument("pod", None)
528 version_arg = self.get_query_argument("version", None)
529 installer_arg = self.get_query_argument("installer", None)
530 build_tag_arg = self.get_query_argument("build_tag", None)
531 scenario_arg = self.get_query_argument("scenario", None)
532 criteria_arg = self.get_query_argument("criteria", None)
533 period_arg = self.get_query_argument("period", None)
537 if result_id is None:
538 if project_arg is not None:
539 get_request["project_name"] = project_arg
541 if case_arg is not None:
542 get_request["case_name"] = case_arg
544 if pod_arg is not None:
545 get_request["pod_name"] = pod_arg
547 if version_arg is not None:
548 get_request["version"] = version_arg
550 if installer_arg is not None:
551 get_request["installer"] = installer_arg
553 if build_tag_arg is not None:
554 get_request["build_tag"] = build_tag_arg
556 if scenario_arg is not None:
557 get_request["scenario"] = scenario_arg
559 if criteria_arg is not None:
560 get_request["criteria_tag"] = criteria_arg
562 if period_arg is not None:
564 period_arg = int(period_arg)
566 raise HTTPError(HTTP_BAD_REQUEST)
569 period = datetime.now() - timedelta(days=period_arg)
570 obj = {"$gte": str(period)}
571 get_request["creation_date"] = obj
573 get_request["_id"] = result_id
578 cursor = self.db.test_results.find(get_request)
579 while (yield cursor.fetch_next):
580 test_result = TestResult.test_result_from_dict(
581 cursor.next_object())
582 res.append(test_result.format_http())
584 # building meta object
586 meta["total"] = len(res)
588 # final response object
590 answer["test_results"] = res
591 answer["meta"] = meta
592 self.finish_request(answer)
598 Create a new test result
599 :return: status of the request
603 # check for request payload
604 if self.json_args is None:
605 raise HTTPError(HTTP_BAD_REQUEST)
607 # check for missing parameters in the request payload
608 if self.json_args.get("project_name") is None:
609 raise HTTPError(HTTP_BAD_REQUEST)
610 if self.json_args.get("case_name") is None:
611 raise HTTPError(HTTP_BAD_REQUEST)
612 # check for pod_name instead of id,
613 # keeping id for current implementations
614 if self.json_args.get("pod_name") is None:
615 raise HTTPError(HTTP_BAD_REQUEST)
617 # TODO : replace checks with jsonschema
619 mongo_dict = yield self.db.test_projects.find_one(
620 {"name": self.json_args.get("project_name")})
621 if mongo_dict is None:
622 raise HTTPError(HTTP_NOT_FOUND,
623 "Could not find project [{}] "
624 .format(self.json_args.get("project_name")))
627 mongo_dict = yield self.db.test_cases.find_one(
628 {"name": self.json_args.get("case_name")})
629 if mongo_dict is None:
630 raise HTTPError(HTTP_NOT_FOUND,
631 "Could not find case [{}] "
632 .format(self.json_args.get("case_name")))
635 mongo_dict = yield self.db.pod.find_one(
636 {"name": self.json_args.get("pod_name")})
637 if mongo_dict is None:
638 raise HTTPError(HTTP_NOT_FOUND,
639 "Could not find POD [{}] "
640 .format(self.json_args.get("pod_name")))
642 # convert payload to object
643 test_result = TestResult.test_result_from_dict(self.json_args)
644 test_result.creation_date = datetime.now()
646 future = self.db.test_results.insert(test_result.format(),
648 result = yield future
649 test_result._id = result
651 self.finish_request(test_result.format_http())
654 class DashboardHandler(GenericApiHandler):
656 DashboardHandler Class
657 Handle the requests about the Test project's results
658 in a dahboard ready format
660 - GET : Get all test results and details about a specific one
662 def initialize(self):
663 """ Prepares the database for the entire class """
664 super(DashboardHandler, self).initialize()
665 self.name = "dashboard"
669 def get(self, result_id=None):
671 Retrieve dashboard ready result(s) for a test project
672 Available filters for this request are :
673 - project : project name
676 - version : platform version (Arno-R1, ...)
677 - installer (fuel, ...)
678 - period : x (x last days)
681 :param result_id: Get a result by ID
684 GET /dashboard?project=functest&case=vPing&version=Arno-R1 \
685 &pod=pod_name&period=15
686 => get results with optional filters
689 project_arg = self.get_query_argument("project", None)
690 case_arg = self.get_query_argument("case", None)
691 pod_arg = self.get_query_argument("pod", None)
692 version_arg = self.get_query_argument("version", None)
693 installer_arg = self.get_query_argument("installer", None)
694 period_arg = self.get_query_argument("period", None)
699 # /dashboard?project=<>&pod=<>...
700 if (result_id is None):
701 if project_arg is not None:
702 get_request["project_name"] = project_arg
704 if case_arg is not None:
705 get_request["case_name"] = case_arg
707 if pod_arg is not None:
708 get_request["pod_name"] = pod_arg
710 if version_arg is not None:
711 get_request["version"] = version_arg
713 if installer_arg is not None:
714 get_request["installer"] = installer_arg
716 if period_arg is not None:
718 period_arg = int(period_arg)
720 raise HTTPError(HTTP_BAD_REQUEST)
722 period = datetime.now() - timedelta(days=period_arg)
723 obj = {"$gte": str(period)}
724 get_request["creation_date"] = obj
726 get_request["_id"] = result_id
730 # on /dashboard retrieve the list of projects and testcases
731 # ready for dashboard
732 if project_arg is None:
733 raise HTTPError(HTTP_NOT_FOUND,
734 "error:Project name missing")
735 elif check_dashboard_ready_project(project_arg, "./dashboard"):
741 "error:Test case missing for project " + project_arg)
743 # special case of status for project
744 if case_arg == "status":
745 del get_request["case_name"]
746 # retention time to be agreed
747 # last five days by default?
749 period = datetime.now() - timedelta(days=5)
750 get_request["creation_date"] = {"$gte": period}
753 cursor = self.db.test_results.find(get_request)
754 while (yield cursor.fetch_next):
755 test_result = TestResult.test_result_from_dict(
756 cursor.next_object())
757 res.append(test_result.format_http())
759 if check_dashboard_ready_case(project_arg, case_arg):
760 dashboard = get_dashboard_result(project_arg, case_arg, res)
764 "error:" + case_arg +
765 " test case not case dashboard ready on project " +
770 {"error": "Project not recognized or not dashboard ready"})
772 {"Dashboard-ready-projects":
773 get_dashboard_cases("./dashboard")})
776 "error: no dashboard ready data for this project")
779 # cursor = self.db.test_results.find(get_request)
780 # while (yield cursor.fetch_next):
781 # test_result = TestResult.test_result_from_dict(
782 # cursor.next_object())
783 # res.append(test_result.format_http())
785 # building meta object
788 # final response object
790 answer["dashboard"] = dashboard
791 answer["meta"] = meta
792 self.finish_request(answer)