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 - period : x (x last days)
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 period_arg = self.get_query_argument("period", None)
531 if result_id is None:
532 if project_arg is not None:
533 get_request["project_name"] = project_arg
535 if case_arg is not None:
536 get_request["case_name"] = case_arg
538 if pod_arg is not None:
539 get_request["pod_name"] = pod_arg
541 if version_arg is not None:
542 get_request["version"] = version_arg
544 if installer_arg is not None:
545 get_request["installer"] = installer_arg
547 if period_arg is not None:
549 period_arg = int(period_arg)
551 raise HTTPError(HTTP_BAD_REQUEST)
554 period = datetime.now() - timedelta(days=period_arg)
555 obj = {"$gte": period}
556 get_request["creation_date"] = obj
558 get_request["_id"] = result_id
563 cursor = self.db.test_results.find(get_request)
564 while (yield cursor.fetch_next):
565 test_result = TestResult.test_result_from_dict(
566 cursor.next_object())
567 res.append(test_result.format_http())
569 # building meta object
571 meta["total"] = len(res)
573 # final response object
575 answer["test_results"] = res
576 answer["meta"] = meta
577 self.finish_request(answer)
583 Create a new test result
584 :return: status of the request
588 # check for request payload
589 if self.json_args is None:
590 raise HTTPError(HTTP_BAD_REQUEST)
592 # check for missing parameters in the request payload
593 if self.json_args.get("project_name") is None:
594 raise HTTPError(HTTP_BAD_REQUEST)
595 if self.json_args.get("case_name") is None:
596 raise HTTPError(HTTP_BAD_REQUEST)
597 # check for pod_name instead of id,
598 # keeping id for current implementations
599 if self.json_args.get("pod_name") is None:
600 raise HTTPError(HTTP_BAD_REQUEST)
602 # TODO : replace checks with jsonschema
604 mongo_dict = yield self.db.test_projects.find_one(
605 {"name": self.json_args.get("project_name")})
606 if mongo_dict is None:
607 raise HTTPError(HTTP_NOT_FOUND,
608 "Could not find project [{}] "
609 .format(self.json_args.get("project_name")))
612 mongo_dict = yield self.db.test_cases.find_one(
613 {"name": self.json_args.get("case_name")})
614 if mongo_dict is None:
615 raise HTTPError(HTTP_NOT_FOUND,
616 "Could not find case [{}] "
617 .format(self.json_args.get("case_name")))
620 mongo_dict = yield self.db.pod.find_one(
621 {"name": self.json_args.get("pod_name")})
622 if mongo_dict is None:
623 raise HTTPError(HTTP_NOT_FOUND,
624 "Could not find POD [{}] "
625 .format(self.json_args.get("pod_name")))
627 # convert payload to object
628 test_result = TestResult.test_result_from_dict(self.json_args)
629 test_result.creation_date = datetime.now()
631 future = self.db.test_results.insert(test_result.format(),
633 result = yield future
634 test_result._id = result
636 self.finish_request(test_result.format_http())
639 class DashboardHandler(GenericApiHandler):
641 DashboardHandler Class
642 Handle the requests about the Test project's results
643 in a dahboard ready format
645 - GET : Get all test results and details about a specific one
647 def initialize(self):
648 """ Prepares the database for the entire class """
649 super(DashboardHandler, self).initialize()
650 self.name = "dashboard"
654 def get(self, result_id=None):
656 Retrieve dashboard ready result(s) for a test project
657 Available filters for this request are :
658 - project : project name
661 - version : platform version (Arno-R1, ...)
662 - installer (fuel, ...)
663 - period : x (x last days)
666 :param result_id: Get a result by ID
669 GET /dashboard?project=functest&case=vPing&version=Arno-R1 \
670 &pod=pod_name&period=15
671 => get results with optional filters
674 project_arg = self.get_query_argument("project", None)
675 case_arg = self.get_query_argument("case", None)
676 pod_arg = self.get_query_argument("pod", None)
677 version_arg = self.get_query_argument("version", None)
678 installer_arg = self.get_query_argument("installer", None)
679 period_arg = self.get_query_argument("period", None)
684 # /dashboard?project=<>&pod=<>...
685 if (result_id is None):
686 if project_arg is not None:
687 get_request["project_name"] = project_arg
689 if case_arg is not None:
690 get_request["case_name"] = case_arg
692 if pod_arg is not None:
693 get_request["pod_name"] = pod_arg
695 if version_arg is not None:
696 get_request["version"] = version_arg
698 if installer_arg is not None:
699 get_request["installer"] = installer_arg
701 if period_arg is not None:
703 period_arg = int(period_arg)
705 raise HTTPError(HTTP_BAD_REQUEST)
707 period = datetime.now() - timedelta(days=period_arg)
708 obj = {"$gte": period}
709 get_request["creation_date"] = obj
711 get_request["_id"] = result_id
715 # on /dashboard retrieve the list of projects and testcases
716 # ready for dashboard
717 if project_arg is None:
718 raise HTTPError(HTTP_NOT_FOUND,
719 "error:Project name missing")
720 elif check_dashboard_ready_project(project_arg, "./dashboard"):
726 "error:Test case missing for project " + project_arg)
728 # special case of status for project
729 if case_arg == "status":
730 del get_request["case_name"]
731 # retention time to be agreed
732 # last five days by default?
734 period = datetime.now() - timedelta(days=5)
735 get_request["creation_date"] = {"$gte": period}
738 cursor = self.db.test_results.find(get_request)
739 while (yield cursor.fetch_next):
740 test_result = TestResult.test_result_from_dict(
741 cursor.next_object())
742 res.append(test_result.format_http())
744 if check_dashboard_ready_case(project_arg, case_arg):
745 dashboard = get_dashboard_result(project_arg, case_arg, res)
749 "error:" + case_arg +
750 " test case not case dashboard ready on project " +
755 {"error": "Project not recognized or not dashboard ready"})
757 {"Dashboard-ready-projects":
758 get_dashboard_cases("./dashboard")})
761 "error: no dashboard ready data for this project")
764 # cursor = self.db.test_results.find(get_request)
765 # while (yield cursor.fetch_next):
766 # test_result = TestResult.test_result_from_dict(
767 # cursor.next_object())
768 # res.append(test_result.format_http())
770 # building meta object
773 # final response object
775 answer["dashboard"] = dashboard
776 answer["meta"] = meta
777 self.finish_request(answer)