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 TestProject, TestCase, TestResult
17 from resources.pod_models import Pod
18 from common.constants import DEFAULT_REPRESENTATION, HTTP_BAD_REQUEST, \
19 HTTP_NOT_FOUND, HTTP_FORBIDDEN
20 from common.config import prepare_put_request
22 from dashboard.dashboard_utils import get_dashboard_cases, \
23 check_dashboard_ready_project, check_dashboard_ready_case, \
27 class GenericApiHandler(RequestHandler):
29 The purpose of this class is to take benefit of inheritance and prepare
30 a set of common functions for
35 """ Prepares the database for the entire class """
36 self.db = self.settings["db"]
39 if not (self.request.method == "GET"):
40 if self.request.headers.get("Content-Type") is not None:
41 if self.request.headers["Content-Type"].startswith(
42 DEFAULT_REPRESENTATION):
44 self.json_args = json.loads(self.request.body)
45 except (ValueError, KeyError, TypeError) as error:
46 raise HTTPError(HTTP_BAD_REQUEST,
47 "Bad Json format [{}]".
52 def finish_request(self, json_object):
53 self.write(json.dumps(json_object))
54 self.set_header("Content-Type", DEFAULT_REPRESENTATION)
58 class VersionHandler(RequestHandler):
59 """ Display a message for the API version """
61 self.write("Collection of test result API, v1")
64 class PodHandler(GenericApiHandler):
65 """ Handle the requests about the POD Platforms
73 """ Prepares the database for the entire class """
74 super(PodHandler, self).initialize()
78 def get(self, pod_name=None):
80 Get all pods or a single pod
85 if pod_name is not None:
86 get_request["name"] = pod_name
89 cursor = self.db.pod.find(get_request)
90 while (yield cursor.fetch_next):
91 pod = Pod.pod_from_dict(cursor.next_object())
92 res.append(pod.format())
95 meta["total"] = len(res)
96 meta["success"] = True if len(res) > 0 else False
100 answer["meta"] = meta
102 self.finish_request(answer)
109 if self.json_args is None:
110 raise HTTPError(HTTP_BAD_REQUEST)
112 query = {"name": self.json_args.get("name")}
114 # check for existing name in db
115 mongo_dict = yield self.db.pod.find_one(query)
116 if mongo_dict is not None:
117 raise HTTPError(HTTP_FORBIDDEN,
118 "{} already exists as a pod".format(
119 self.json_args.get("name")))
121 pod = Pod.pod_from_dict(self.json_args)
122 pod.creation_date = datetime.now()
124 future = self.db.pod.insert(pod.format())
125 result = yield future
129 meta["success"] = True
130 meta["uri"] = "/pods/{}".format(pod.name)
133 answer["pod"] = pod.format_http()
134 answer["meta"] = meta
136 self.finish_request(answer)
140 def delete(self, pod_name):
143 # check for an existing pod to be deleted
144 mongo_dict = yield self.db.pod.find_one(
146 pod = TestProject.pod(mongo_dict)
148 raise HTTPError(HTTP_NOT_FOUND,
149 "{} could not be found as a pod to be deleted"
152 # just delete it, or maybe save it elsewhere in a future
153 res = yield self.db.test_projects.remove(
157 meta["success"] = True
158 meta["deletion-data"] = res
161 answer["meta"] = meta
163 self.finish_request(answer)
168 class TestProjectHandler(GenericApiHandler):
170 TestProjectHandler Class
171 Handle the requests about the Test projects
173 - GET : Get all test projects and details about a specific one
174 - POST : Add a test project
175 - PUT : Edit test projects information (name and/or description)
176 - DELETE : Remove a test project
179 def initialize(self):
180 """ Prepares the database for the entire class """
181 super(TestProjectHandler, self).initialize()
185 def get(self, project_name=None):
191 if project_name is None:
196 if len(project_name) > 0:
197 get_request["name"] = project_name
200 cursor = self.db.test_projects.find(get_request)
201 while (yield cursor.fetch_next):
202 test_project = TestProject.testproject_from_dict(
203 cursor.next_object())
204 res.append(test_project.format_http())
207 meta["total"] = len(res)
208 meta["success"] = True if len(res) > 0 else False
211 answer["test_projects"] = res
212 answer["meta"] = meta
214 self.finish_request(answer)
219 """ Create a test project"""
221 if self.json_args is None:
222 raise HTTPError(HTTP_BAD_REQUEST)
224 query = {"name": self.json_args.get("name")}
226 # check for name in db
227 mongo_dict = yield self.db.test_projects.find_one(query)
228 if mongo_dict is not None:
229 raise HTTPError(HTTP_FORBIDDEN,
230 "{} already exists as a project".format(
231 self.json_args.get("name")))
233 test_project = TestProject.testproject_from_dict(self.json_args)
234 test_project.creation_date = datetime.now()
236 future = self.db.test_projects.insert(test_project.format())
237 result = yield future
238 test_project._id = result
240 self.finish_request(test_project.format_http())
244 def put(self, project_name):
245 """ Updates the name and description of a test project"""
247 print "PUT request for : {}".format(project_name)
249 query = {'name': project_name}
250 mongo_dict = yield self.db.test_projects.find_one(query)
251 test_project = TestProject.testproject_from_dict(mongo_dict)
252 if test_project is None:
253 raise HTTPError(HTTP_NOT_FOUND,
254 "{} could not be found".format(project_name))
256 new_name = self.json_args.get("name")
257 new_description = self.json_args.get("description")
259 # check for payload name parameter in db
260 # avoid a request if the project name has not changed in the payload
261 if new_name != test_project.name:
262 mongo_dict = yield self.db.test_projects.find_one(
264 if mongo_dict is not None:
265 raise HTTPError(HTTP_FORBIDDEN,
266 "{} already exists as a project"
269 # new dict for changes
271 request = prepare_put_request(request,
275 request = prepare_put_request(request,
278 test_project.description)
280 """ raise exception if there isn't a change """
282 raise HTTPError(HTTP_FORBIDDEN,
285 """ we merge the whole document """
286 edit_request = test_project.format()
287 edit_request.update(request)
289 """ Updating the DB """
290 res = yield self.db.test_projects.update({'name': project_name},
293 edit_request["_id"] = str(test_project._id)
295 self.finish_request({"message": "success", "content": edit_request})
299 def delete(self, project_name):
300 """ Remove a test project"""
302 print "DELETE request for : {}".format(project_name)
304 # check for an existing project to be deleted
305 mongo_dict = yield self.db.test_projects.find_one(
306 {'name': project_name})
307 test_project = TestProject.testproject_from_dict(mongo_dict)
308 if test_project is None:
309 raise HTTPError(HTTP_NOT_FOUND,
310 "{} could not be found as a project to be deleted"
311 .format(project_name))
313 # just delete it, or maybe save it elsewhere in a future
314 res = yield self.db.test_projects.remove(
315 {'name': project_name})
318 self.finish_request({"message": "success"})
321 class TestCasesHandler(GenericApiHandler):
323 TestCasesHandler Class
324 Handle the requests about the Test cases for test projects
326 - GET : Get all test cases and details about a specific one
327 - POST : Add a test project
328 - PUT : Edit test projects information (name and/or description)
331 def initialize(self):
332 """ Prepares the database for the entire class """
333 super(TestCasesHandler, self).initialize()
337 def get(self, project_name, case_name=None):
339 Get testcases(s) info
344 if case_name is None:
348 get_request["project_name"] = project_name
350 if len(case_name) > 0:
351 get_request["name"] = case_name
354 cursor = self.db.test_cases.find(get_request)
356 while (yield cursor.fetch_next):
357 test_case = TestCase.test_case_from_dict(cursor.next_object())
358 res.append(test_case.format_http())
361 meta["total"] = len(res)
362 meta["success"] = True if len(res) > 0 else False
365 answer["test_cases"] = res
366 answer["meta"] = meta
368 self.finish_request(answer)
372 def post(self, project_name):
373 """ Create a test case"""
375 print "POST Request for {}".format(project_name)
377 if self.json_args is None:
378 raise HTTPError(HTTP_BAD_REQUEST,
379 "Check your request payload")
381 # retrieve test project
382 mongo_dict = yield self.db.test_projects.find_one(
383 {"name": project_name})
384 if mongo_dict is None:
385 raise HTTPError(HTTP_FORBIDDEN,
386 "Could not find project {}"
387 .format(project_name))
389 # test_project = TestProject.testproject_from_dict(self.json_args)
391 case = TestCase.test_case_from_dict(self.json_args)
392 case.project_name = project_name
393 case.creation_date = datetime.now()
395 future = self.db.test_cases.insert(case.format())
396 result = yield future
398 self.finish_request(case.format_http())
402 def put(self, project_name, case_name):
404 Updates the name and description of a test case
405 :raises HTTPError (HTTP_NOT_FOUND, HTTP_FORBIDDEN)
408 print "PUT request for : {}/{}".format(project_name, case_name)
409 case_request = {'project_name': project_name, 'name': case_name}
411 # check if there is a case for the project in url parameters
412 mongo_dict = yield self.db.test_cases.find_one(case_request)
413 test_case = TestCase.test_case_from_dict(mongo_dict)
414 if test_case is None:
415 raise HTTPError(HTTP_NOT_FOUND,
416 "{} could not be found as a {} case to be updated"
417 .format(case_name, project_name))
419 new_name = self.json_args.get("name")
420 new_project_name = self.json_args.get("project_name")
421 new_description = self.json_args.get("description")
423 # check if there is not an existing test case
424 # with the name provided in the json payload
425 mongo_dict = yield self.db.test_cases.find_one(
426 {'project_name': new_project_name, 'name': new_name})
427 if mongo_dict is not None:
428 raise HTTPError(HTTP_FORBIDDEN,
429 "{} already exists as a project"
432 # new dict for changes
434 request = prepare_put_request(request,
438 request = prepare_put_request(request,
441 test_case.project_name)
442 request = prepare_put_request(request,
445 test_case.description)
447 # we raise an exception if there isn't a change
449 raise HTTPError(HTTP_FORBIDDEN,
452 # we merge the whole document """
453 edit_request = test_case.format()
454 edit_request.update(request)
456 """ Updating the DB """
457 res = yield self.db.test_cases.update(case_request, edit_request)
459 edit_request["_id"] = str(test_case._id)
461 self.finish_request({"message": "success", "content": edit_request})
465 def delete(self, project_name, case_name):
466 """ Remove a test case"""
468 print "DELETE request for : {}/{}".format(project_name, case_name)
469 case_request = {'project_name': project_name, 'name': case_name}
471 # check for an existing case to be deleted
472 mongo_dict = yield self.db.test_cases.find_one(case_request)
473 test_project = TestProject.testproject_from_dict(mongo_dict)
474 if test_project is None:
475 raise HTTPError(HTTP_NOT_FOUND,
476 "{}/{} could not be found as a case to be deleted"
477 .format(project_name, case_name))
479 # just delete it, or maybe save it elsewhere in a future
480 res = yield self.db.test_projects.remove(case_request)
483 self.finish_request({"message": "success"})
486 class TestResultsHandler(GenericApiHandler):
488 TestResultsHandler Class
489 Handle the requests about the Test project's results
491 - GET : Get all test results and details about a specific one
492 - POST : Add a test results
493 - DELETE : Remove a test result
496 def initialize(self):
497 """ Prepares the database for the entire class """
498 super(TestResultsHandler, self).initialize()
499 self.name = "test_result"
503 def get(self, result_id=None):
505 Retrieve result(s) for a test project on a specific POD.
506 Available filters for this request are :
507 - project : project name
510 - version : platform version (Arno-R1, ...)
511 - installer (fuel, ...)
512 - build_tag : Jenkins build tag name
513 - period : x (x last days)
514 - scenario : the test scenario (previously version)
515 - criteria : the global criteria status passed or failed
516 - trust_indicator : evaluate the stability of the test case to avoid
517 running systematically long and stable test case
520 :param result_id: Get a result by ID
523 GET /results/project=functest&case=vPing&version=Arno-R1 \
524 &pod=pod_name&period=15
525 => get results with optional filters
528 project_arg = self.get_query_argument("project", None)
529 case_arg = self.get_query_argument("case", None)
530 pod_arg = self.get_query_argument("pod", None)
531 version_arg = self.get_query_argument("version", None)
532 installer_arg = self.get_query_argument("installer", None)
533 build_tag_arg = self.get_query_argument("build_tag", None)
534 scenario_arg = self.get_query_argument("scenario", None)
535 criteria_arg = self.get_query_argument("criteria", None)
536 period_arg = self.get_query_argument("period", None)
537 trust_indicator_arg = self.get_query_argument("trust_indicator", None)
541 if result_id is None:
542 if project_arg is not None:
543 get_request["project_name"] = project_arg
545 if case_arg is not None:
546 get_request["case_name"] = case_arg
548 if pod_arg is not None:
549 get_request["pod_name"] = pod_arg
551 if version_arg is not None:
552 get_request["version"] = version_arg
554 if installer_arg is not None:
555 get_request["installer"] = installer_arg
557 if build_tag_arg is not None:
558 get_request["build_tag"] = build_tag_arg
560 if scenario_arg is not None:
561 get_request["scenario"] = scenario_arg
563 if criteria_arg is not None:
564 get_request["criteria_tag"] = criteria_arg
566 if trust_indicator_arg is not None:
567 get_request["trust_indicator_arg"] = trust_indicator_arg
569 if period_arg is not None:
571 period_arg = int(period_arg)
573 raise HTTPError(HTTP_BAD_REQUEST)
576 period = datetime.now() - timedelta(days=period_arg)
577 obj = {"$gte": str(period)}
578 get_request["creation_date"] = obj
580 get_request["_id"] = result_id
585 cursor = self.db.test_results.find(get_request)
586 while (yield cursor.fetch_next):
587 test_result = TestResult.test_result_from_dict(
588 cursor.next_object())
589 res.append(test_result.format_http())
591 # building meta object
593 meta["total"] = len(res)
595 # final response object
597 answer["test_results"] = res
598 answer["meta"] = meta
599 self.finish_request(answer)
605 Create a new test result
606 :return: status of the request
610 # check for request payload
611 if self.json_args is None:
612 raise HTTPError(HTTP_BAD_REQUEST)
614 # check for missing parameters in the request payload
615 if self.json_args.get("project_name") is None:
616 raise HTTPError(HTTP_BAD_REQUEST)
617 if self.json_args.get("case_name") is None:
618 raise HTTPError(HTTP_BAD_REQUEST)
619 # check for pod_name instead of id,
620 # keeping id for current implementations
621 if self.json_args.get("pod_name") is None:
622 raise HTTPError(HTTP_BAD_REQUEST)
624 # TODO : replace checks with jsonschema
626 mongo_dict = yield self.db.test_projects.find_one(
627 {"name": self.json_args.get("project_name")})
628 if mongo_dict is None:
629 raise HTTPError(HTTP_NOT_FOUND,
630 "Could not find project [{}] "
631 .format(self.json_args.get("project_name")))
634 mongo_dict = yield self.db.test_cases.find_one(
635 {"name": self.json_args.get("case_name")})
636 if mongo_dict is None:
637 raise HTTPError(HTTP_NOT_FOUND,
638 "Could not find case [{}] "
639 .format(self.json_args.get("case_name")))
642 mongo_dict = yield self.db.pod.find_one(
643 {"name": self.json_args.get("pod_name")})
644 if mongo_dict is None:
645 raise HTTPError(HTTP_NOT_FOUND,
646 "Could not find POD [{}] "
647 .format(self.json_args.get("pod_name")))
649 # convert payload to object
650 test_result = TestResult.test_result_from_dict(self.json_args)
651 test_result.creation_date = datetime.now()
653 future = self.db.test_results.insert(test_result.format(),
655 result = yield future
656 test_result._id = result
658 self.finish_request(test_result.format_http())
661 class DashboardHandler(GenericApiHandler):
663 DashboardHandler Class
664 Handle the requests about the Test project's results
665 in a dahboard ready format
667 - GET : Get all test results and details about a specific one
669 def initialize(self):
670 """ Prepares the database for the entire class """
671 super(DashboardHandler, self).initialize()
672 self.name = "dashboard"
676 def get(self, result_id=None):
678 Retrieve dashboard ready result(s) for a test project
679 Available filters for this request are :
680 - project : project name
683 - version : platform version (Arno-R1, ...)
684 - installer (fuel, ...)
685 - period : x (x last days)
688 :param result_id: Get a result by ID
691 GET /dashboard?project=functest&case=vPing&version=Arno-R1 \
692 &pod=pod_name&period=15
693 => get results with optional filters
696 project_arg = self.get_query_argument("project", None)
697 case_arg = self.get_query_argument("case", None)
698 pod_arg = self.get_query_argument("pod", None)
699 version_arg = self.get_query_argument("version", None)
700 installer_arg = self.get_query_argument("installer", None)
701 period_arg = self.get_query_argument("period", None)
706 # /dashboard?project=<>&pod=<>...
707 if (result_id is None):
708 if project_arg is not None:
709 get_request["project_name"] = project_arg
711 if case_arg is not None:
712 get_request["case_name"] = case_arg
714 if pod_arg is not None:
715 get_request["pod_name"] = pod_arg
717 if version_arg is not None:
718 get_request["version"] = version_arg
720 if installer_arg is not None:
721 get_request["installer"] = installer_arg
723 if period_arg is not None:
725 period_arg = int(period_arg)
727 raise HTTPError(HTTP_BAD_REQUEST)
729 period = datetime.now() - timedelta(days=period_arg)
730 obj = {"$gte": str(period)}
731 get_request["creation_date"] = obj
733 get_request["_id"] = result_id
737 # on /dashboard retrieve the list of projects and testcases
738 # ready for dashboard
739 if project_arg is None:
740 raise HTTPError(HTTP_NOT_FOUND,
741 "error:Project name missing")
742 elif check_dashboard_ready_project(project_arg, "./dashboard"):
748 "error:Test case missing for project " + project_arg)
750 # special case of status for project
751 if case_arg == "status":
752 del get_request["case_name"]
753 # retention time to be agreed
754 # last five days by default?
756 period = datetime.now() - timedelta(days=5)
757 get_request["creation_date"] = {"$gte": period}
760 cursor = self.db.test_results.find(get_request)
761 while (yield cursor.fetch_next):
762 test_result = TestResult.test_result_from_dict(
763 cursor.next_object())
764 res.append(test_result.format_http())
766 if check_dashboard_ready_case(project_arg, case_arg):
767 dashboard = get_dashboard_result(project_arg, case_arg, res)
771 "error:" + case_arg +
772 " test case not case dashboard ready on project " +
777 {"error": "Project not recognized or not dashboard ready"})
779 {"Dashboard-ready-projects":
780 get_dashboard_cases("./dashboard")})
783 "error: no dashboard ready data for this project")
786 # cursor = self.db.test_results.find(get_request)
787 # while (yield cursor.fetch_next):
788 # test_result = TestResult.test_result_from_dict(
789 # cursor.next_object())
790 # res.append(test_result.format_http())
792 # building meta object
795 # final response object
797 answer["dashboard"] = dashboard
798 answer["meta"] = meta
799 self.finish_request(answer)