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 ##############################################################################
16 from tornado.web import RequestHandler, asynchronous, HTTPError
17 from tornado import gen
18 from datetime import datetime, timedelta
20 from models import TestCase, TestResult, CreateResponse
21 from resources.project_models import Project
22 from resources.pod_models import Pod
23 from common.constants import DEFAULT_REPRESENTATION, HTTP_BAD_REQUEST, \
24 HTTP_NOT_FOUND, HTTP_FORBIDDEN
25 from common.config import prepare_put_request
27 from dashboard.dashboard_utils import get_dashboard_cases, \
28 check_dashboard_ready_project, check_dashboard_ready_case, \
32 def format_data(data, cls):
33 cls_data = cls.from_dict(data)
34 return cls_data.format_http()
37 class GenericApiHandler(RequestHandler):
39 The purpose of this class is to take benefit of inheritance and prepare
40 a set of common functions for
45 """ Prepares the database for the entire class """
46 self.db = self.settings["db"]
49 if not (self.request.method == "GET" or 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 [{}]".
62 def finish_request(self, json_object=None):
64 self.write(json.dumps(json_object))
65 self.set_header("Content-Type", DEFAULT_REPRESENTATION)
69 class VersionHandler(RequestHandler):
70 """ Display a message for the API version """
72 self.write("Collection of test result API, v1")
75 class PodHandler(GenericApiHandler):
76 """ Handle the requests about the POD Platforms
84 """ Prepares the database for the entire class """
85 super(PodHandler, self).initialize()
89 def get(self, pod_name=None):
91 Get all pods or a single pod
96 if pod_name is not None:
97 get_request["name"] = pod_name
98 answer = yield self.db.pods.find_one(get_request)
100 raise HTTPError(HTTP_NOT_FOUND,
101 "{} Not Exist".format(pod_name))
103 answer = format_data(answer, Pod)
106 cursor = self.db.pods.find(get_request)
107 while (yield cursor.fetch_next):
108 res.append(format_data(cursor.next_object(), Pod))
112 self.finish_request(answer)
119 if self.json_args is None:
120 raise HTTPError(HTTP_BAD_REQUEST)
122 query = {"name": self.json_args.get("name")}
124 # check for existing name in db
125 mongo_dict = yield self.db.pods.find_one(query)
126 if mongo_dict is not None:
127 raise HTTPError(HTTP_FORBIDDEN,
128 "{} already exists as a pod".format(
129 self.json_args.get("name")))
131 pod = Pod.from_dict(self.json_args)
132 pod.creation_date = datetime.now()
134 yield self.db.pods.insert(pod.format())
136 res = CreateResponse(self.request.full_url() + '/{}'.format(pod.name))
137 self.finish_request(res.format())
141 def delete(self, pod_name):
144 # check for an existing pod to be deleted
145 mongo_dict = yield self.db.pods.find_one(
147 pod = TestProject.pod(mongo_dict)
149 raise HTTPError(HTTP_NOT_FOUND,
150 "{} could not be found as a pod to be deleted"
153 # just delete it, or maybe save it elsewhere in a future
154 res = yield self.db.projects.remove(
158 meta["success"] = True
159 meta["deletion-data"] = res
162 answer["meta"] = meta
164 self.finish_request(answer)
169 class TestProjectHandler(GenericApiHandler):
171 TestProjectHandler Class
172 Handle the requests about the Test projects
174 - GET : Get all test projects and details about a specific one
175 - POST : Add a test project
176 - PUT : Edit test projects information (name and/or description)
177 - DELETE : Remove a test project
180 def initialize(self):
181 """ Prepares the database for the entire class """
182 super(TestProjectHandler, self).initialize()
186 def get(self, project_name=None):
194 if project_name is not None:
195 get_request["name"] = project_name
196 answer = yield self.db.projects.find_one(get_request)
198 raise HTTPError(HTTP_NOT_FOUND,
199 "{} Not Exist".format(project_name))
201 answer = format_data(answer, Project)
204 cursor = self.db.projects.find(get_request)
205 while (yield cursor.fetch_next):
206 res.append(format_data(cursor.next_object(), Project))
208 answer['projects'] = res
210 self.finish_request(answer)
215 """ Create a test project"""
217 if self.json_args is None:
218 raise HTTPError(HTTP_BAD_REQUEST)
220 query = {"name": self.json_args.get("name")}
222 # check for name in db
223 mongo_dict = yield self.db.projects.find_one(query)
224 if mongo_dict is not None:
225 raise HTTPError(HTTP_FORBIDDEN,
226 "{} already exists as a project".format(
227 self.json_args.get("name")))
229 project = Project.from_dict(self.json_args)
230 project.creation_date = datetime.now()
232 yield self.db.projects.insert(project.format())
234 res = CreateResponse(self.request.full_url() + '/{}'.format(project.name))
235 self.finish_request(res.format())
240 def put(self, project_name):
241 """ Updates the name and description of a test project"""
243 print "PUT request for : {}".format(project_name)
245 if self.json_args is None:
246 raise HTTPError(HTTP_BAD_REQUEST)
248 query = {'name': project_name}
249 mongo_dict = yield self.db.projects.find_one(query)
250 project = Project.from_dict(mongo_dict)
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 != project.name:
261 mongo_dict = yield self.db.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,
279 """ raise exception if there isn't a change """
281 raise HTTPError(HTTP_FORBIDDEN, "Nothing to update")
283 """ we merge the whole document """
284 edit_request = project.format()
285 edit_request.update(request)
287 """ Updating the DB """
288 yield self.db.projects.update({'name': project_name}, edit_request)
289 new_project = yield self.db.projects.find_one({"_id": project._id})
291 self.finish_request(format_data(new_project, Project))
295 def delete(self, project_name):
296 """ Remove a test project"""
298 print "DELETE request for : {}".format(project_name)
300 # check for an existing project to be deleted
301 mongo_dict = yield self.db.projects.find_one(
302 {'name': project_name})
303 test_project = Project.from_dict(mongo_dict)
304 if test_project is None:
305 raise HTTPError(HTTP_NOT_FOUND,
306 "{} could not be found as a project to be deleted"
307 .format(project_name))
309 # just delete it, or maybe save it elsewhere in a future
310 yield self.db.projects.remove({'name': project_name})
312 self.finish_request()
315 class TestCasesHandler(GenericApiHandler):
317 TestCasesHandler Class
318 Handle the requests about the Test cases for test projects
320 - GET : Get all test cases and details about a specific one
321 - POST : Add a test project
322 - PUT : Edit test projects information (name and/or description)
325 def initialize(self):
326 """ Prepares the database for the entire class """
327 super(TestCasesHandler, self).initialize()
331 def get(self, project_name, case_name=None):
333 Get testcases(s) info
338 if case_name is None:
342 get_request["project_name"] = project_name
344 if len(case_name) > 0:
345 get_request["name"] = case_name
348 cursor = self.db.test_cases.find(get_request)
350 while (yield cursor.fetch_next):
351 test_case = TestCase.test_case_from_dict(cursor.next_object())
352 res.append(test_case.format_http())
355 meta["total"] = len(res)
356 meta["success"] = True if len(res) > 0 else False
359 answer["test_cases"] = res
360 answer["meta"] = meta
362 self.finish_request(answer)
366 def post(self, project_name):
367 """ Create a test case"""
369 print "POST Request for {}".format(project_name)
371 if self.json_args is None:
372 raise HTTPError(HTTP_BAD_REQUEST,
373 "Check your request payload")
375 # retrieve test project
376 mongo_dict = yield self.db.projects.find_one(
377 {"name": project_name})
378 if mongo_dict is None:
379 raise HTTPError(HTTP_FORBIDDEN,
380 "Could not find project {}"
381 .format(project_name))
383 # test_project = TestProject.from_dict(self.json_args)
385 case = TestCase.test_case_from_dict(self.json_args)
386 case.project_name = project_name
387 case.creation_date = datetime.now()
389 future = self.db.test_cases.insert(case.format())
390 result = yield future
392 self.finish_request(case.format_http())
396 def put(self, project_name, case_name):
398 Updates the name and description of a test case
399 :raises HTTPError (HTTP_NOT_FOUND, HTTP_FORBIDDEN)
402 print "PUT request for : {}/{}".format(project_name, case_name)
403 case_request = {'project_name': project_name, 'name': case_name}
405 # check if there is a case for the project in url parameters
406 mongo_dict = yield self.db.test_cases.find_one(case_request)
407 test_case = TestCase.test_case_from_dict(mongo_dict)
408 if test_case is None:
409 raise HTTPError(HTTP_NOT_FOUND,
410 "{} could not be found as a {} case to be updated"
411 .format(case_name, project_name))
413 new_name = self.json_args.get("name")
414 new_project_name = self.json_args.get("project_name")
415 new_description = self.json_args.get("description")
417 # check if there is not an existing test case
418 # with the name provided in the json payload
419 mongo_dict = yield self.db.test_cases.find_one(
420 {'project_name': new_project_name, 'name': new_name})
421 if mongo_dict is not None:
422 raise HTTPError(HTTP_FORBIDDEN,
423 "{} already exists as a project"
426 # new dict for changes
428 request = prepare_put_request(request,
432 request = prepare_put_request(request,
435 test_case.project_name)
436 request = prepare_put_request(request,
439 test_case.description)
441 # we raise an exception if there isn't a change
443 raise HTTPError(HTTP_FORBIDDEN,
446 # we merge the whole document """
447 edit_request = test_case.format()
448 edit_request.update(request)
450 """ Updating the DB """
451 res = yield self.db.test_cases.update(case_request, edit_request)
453 edit_request["_id"] = str(test_case._id)
455 self.finish_request({"message": "success", "content": edit_request})
459 def delete(self, project_name, case_name):
460 """ Remove a test case"""
462 print "DELETE request for : {}/{}".format(project_name, case_name)
463 case_request = {'project_name': project_name, 'name': case_name}
465 # check for an existing case to be deleted
466 mongo_dict = yield self.db.test_cases.find_one(case_request)
467 test_project = Project.from_dict(mongo_dict)
468 if test_project is None:
469 raise HTTPError(HTTP_NOT_FOUND,
470 "{}/{} could not be found as a case to be deleted"
471 .format(project_name, case_name))
473 # just delete it, or maybe save it elsewhere in a future
474 res = yield self.db.projects.remove(case_request)
477 self.finish_request({"message": "success"})
480 class TestResultsHandler(GenericApiHandler):
482 TestResultsHandler Class
483 Handle the requests about the Test project's results
485 - GET : Get all test results and details about a specific one
486 - POST : Add a test results
487 - DELETE : Remove a test result
490 def initialize(self):
491 """ Prepares the database for the entire class """
492 super(TestResultsHandler, self).initialize()
493 self.name = "test_result"
497 def get(self, result_id=None):
499 Retrieve result(s) for a test project on a specific POD.
500 Available filters for this request are :
501 - project : project name
504 - version : platform version (Arno-R1, ...)
505 - installer (fuel, ...)
506 - build_tag : Jenkins build tag name
507 - period : x (x last days)
508 - scenario : the test scenario (previously version)
509 - criteria : the global criteria status passed or failed
510 - trust_indicator : evaluate the stability of the test case to avoid
511 running systematically long and stable test case
514 :param result_id: Get a result by ID
517 GET /results/project=functest&case=vPing&version=Arno-R1 \
518 &pod=pod_name&period=15
519 => get results with optional filters
522 project_arg = self.get_query_argument("project", None)
523 case_arg = self.get_query_argument("case", None)
524 pod_arg = self.get_query_argument("pod", None)
525 version_arg = self.get_query_argument("version", None)
526 installer_arg = self.get_query_argument("installer", None)
527 build_tag_arg = self.get_query_argument("build_tag", None)
528 scenario_arg = self.get_query_argument("scenario", None)
529 criteria_arg = self.get_query_argument("criteria", None)
530 period_arg = self.get_query_argument("period", None)
531 trust_indicator_arg = self.get_query_argument("trust_indicator", None)
535 if result_id is None:
536 if project_arg is not None:
537 get_request["project_name"] = project_arg
539 if case_arg is not None:
540 get_request["case_name"] = case_arg
542 if pod_arg is not None:
543 get_request["pod_name"] = pod_arg
545 if version_arg is not None:
546 get_request["version"] = version_arg
548 if installer_arg is not None:
549 get_request["installer"] = installer_arg
551 if build_tag_arg is not None:
552 get_request["build_tag"] = build_tag_arg
554 if scenario_arg is not None:
555 get_request["scenario"] = scenario_arg
557 if criteria_arg is not None:
558 get_request["criteria_tag"] = criteria_arg
560 if trust_indicator_arg is not None:
561 get_request["trust_indicator_arg"] = trust_indicator_arg
563 if period_arg is not None:
565 period_arg = int(period_arg)
567 raise HTTPError(HTTP_BAD_REQUEST)
570 period = datetime.now() - timedelta(days=period_arg)
571 obj = {"$gte": str(period)}
572 get_request["creation_date"] = obj
574 get_request["_id"] = result_id
579 cursor = self.db.test_results.find(get_request)
580 while (yield cursor.fetch_next):
581 test_result = TestResult.test_result_from_dict(
582 cursor.next_object())
583 res.append(test_result.format_http())
585 # building meta object
587 meta["total"] = len(res)
589 # final response object
591 answer["test_results"] = res
592 answer["meta"] = meta
593 self.finish_request(answer)
599 Create a new test result
600 :return: status of the request
604 # check for request payload
605 if self.json_args is None:
606 raise HTTPError(HTTP_BAD_REQUEST)
608 # check for missing parameters in the request payload
609 if self.json_args.get("project_name") is None:
610 raise HTTPError(HTTP_BAD_REQUEST)
611 if self.json_args.get("case_name") is None:
612 raise HTTPError(HTTP_BAD_REQUEST)
613 # check for pod_name instead of id,
614 # keeping id for current implementations
615 if self.json_args.get("pod_name") is None:
616 raise HTTPError(HTTP_BAD_REQUEST)
618 # TODO : replace checks with jsonschema
620 mongo_dict = yield self.db.projects.find_one(
621 {"name": self.json_args.get("project_name")})
622 if mongo_dict is None:
623 raise HTTPError(HTTP_NOT_FOUND,
624 "Could not find project [{}] "
625 .format(self.json_args.get("project_name")))
628 mongo_dict = yield self.db.test_cases.find_one(
629 {"name": self.json_args.get("case_name")})
630 if mongo_dict is None:
631 raise HTTPError(HTTP_NOT_FOUND,
632 "Could not find case [{}] "
633 .format(self.json_args.get("case_name")))
636 mongo_dict = yield self.db.pods.find_one(
637 {"name": self.json_args.get("pod_name")})
638 if mongo_dict is None:
639 raise HTTPError(HTTP_NOT_FOUND,
640 "Could not find POD [{}] "
641 .format(self.json_args.get("pod_name")))
643 # convert payload to object
644 test_result = TestResult.test_result_from_dict(self.json_args)
645 test_result.creation_date = datetime.now()
647 future = self.db.test_results.insert(test_result.format(),
649 result = yield future
650 test_result._id = result
652 self.finish_request(test_result.format_http())
655 class DashboardHandler(GenericApiHandler):
657 DashboardHandler Class
658 Handle the requests about the Test project's results
659 in a dahboard ready format
661 - GET : Get all test results and details about a specific one
663 def initialize(self):
664 """ Prepares the database for the entire class """
665 super(DashboardHandler, self).initialize()
666 self.name = "dashboard"
670 def get(self, result_id=None):
672 Retrieve dashboard ready result(s) for a test project
673 Available filters for this request are :
674 - project : project name
677 - version : platform version (Arno-R1, ...)
678 - installer (fuel, ...)
679 - period : x (x last days)
682 :param result_id: Get a result by ID
685 GET /dashboard?project=functest&case=vPing&version=Arno-R1 \
686 &pod=pod_name&period=15
687 => get results with optional filters
690 project_arg = self.get_query_argument("project", None)
691 case_arg = self.get_query_argument("case", None)
692 pod_arg = self.get_query_argument("pod", None)
693 version_arg = self.get_query_argument("version", None)
694 installer_arg = self.get_query_argument("installer", None)
695 period_arg = self.get_query_argument("period", None)
700 # /dashboard?project=<>&pod=<>...
701 if (result_id is None):
702 if project_arg is not None:
703 get_request["project_name"] = project_arg
705 if case_arg is not None:
706 get_request["case_name"] = case_arg
708 if pod_arg is not None:
709 get_request["pod_name"] = pod_arg
711 if version_arg is not None:
712 get_request["version"] = version_arg
714 if installer_arg is not None:
715 get_request["installer"] = installer_arg
717 if period_arg is not None:
719 period_arg = int(period_arg)
721 raise HTTPError(HTTP_BAD_REQUEST)
723 period = datetime.now() - timedelta(days=period_arg)
724 obj = {"$gte": str(period)}
725 get_request["creation_date"] = obj
727 get_request["_id"] = result_id
731 # on /dashboard retrieve the list of projects and testcases
732 # ready for dashboard
733 if project_arg is None:
734 raise HTTPError(HTTP_NOT_FOUND,
735 "error:Project name missing")
736 elif check_dashboard_ready_project(project_arg, "./dashboard"):
742 "error:Test case missing for project " + project_arg)
744 # special case of status for project
745 if case_arg == "status":
746 del get_request["case_name"]
747 # retention time to be agreed
748 # last five days by default?
750 period = datetime.now() - timedelta(days=5)
751 get_request["creation_date"] = {"$gte": period}
754 cursor = self.db.test_results.find(get_request)
755 while (yield cursor.fetch_next):
756 test_result = TestResult.test_result_from_dict(
757 cursor.next_object())
758 res.append(test_result.format_http())
760 if check_dashboard_ready_case(project_arg, case_arg):
761 dashboard = get_dashboard_result(project_arg, case_arg, res)
765 "error:" + case_arg +
766 " test case not case dashboard ready on project " +
771 {"error": "Project not recognized or not dashboard ready"})
773 {"Dashboard-ready-projects":
774 get_dashboard_cases("./dashboard")})
777 "error: no dashboard ready data for this project")
780 # cursor = self.db.test_results.find(get_request)
781 # while (yield cursor.fetch_next):
782 # test_result = TestResult.test_result_from_dict(
783 # cursor.next_object())
784 # res.append(test_result.format_http())
786 # building meta object
789 # final response object
791 answer["dashboard"] = dashboard
792 answer["meta"] = meta
793 self.finish_request(answer)