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
22 class GenericApiHandler(RequestHandler):
24 The purpose of this class is to take benefit of inheritance and prepare
25 a set of common functions for
30 """ Prepares the database for the entire class """
31 self.db = self.settings["db"]
34 if not (self.request.method == "GET"):
35 if self.request.headers.get("Content-Type") is not None:
36 if self.request.headers["Content-Type"].startswith(
37 DEFAULT_REPRESENTATION):
39 self.json_args = json.loads(self.request.body)
40 except (ValueError, KeyError, TypeError) as error:
41 raise HTTPError(HTTP_BAD_REQUEST,
42 "Bad Json format [{}]".
47 def finish_request(self, json_object):
48 self.write(json.dumps(json_object))
49 self.set_header("Content-Type", DEFAULT_REPRESENTATION)
53 class VersionHandler(RequestHandler):
54 """ Display a message for the API version """
56 self.write("Collection of test result API, v1")
59 class PodHandler(GenericApiHandler):
60 """ Handle the requests about the POD Platforms
68 """ Prepares the database for the entire class """
69 super(PodHandler, self).initialize()
73 def get(self, pod_name=None):
75 Get all pods or a single pod
80 if pod_name is not None:
81 get_request["name"] = pod_name
84 cursor = self.db.pod.find(get_request)
85 while (yield cursor.fetch_next):
86 pod = Pod.pod_from_dict(cursor.next_object())
87 res.append(pod.format())
90 meta["total"] = len(res)
91 meta["success"] = True if len(res) > 0 else False
97 self.finish_request(answer)
104 if self.json_args is None:
105 raise HTTPError(HTTP_BAD_REQUEST)
107 query = {"name": self.json_args.get("name")}
109 # check for existing name in db
110 mongo_dict = yield self.db.pod.find_one(query)
111 if mongo_dict is not None:
112 raise HTTPError(HTTP_FORBIDDEN,
113 "{} already exists as a pod".format(
114 self.json_args.get("name")))
116 pod = Pod.pod_from_dict(self.json_args)
117 pod.creation_date = datetime.now()
119 future = self.db.pod.insert(pod.format())
120 result = yield future
124 meta["success"] = True
125 meta["uri"] = "/pods/{}".format(pod.name)
128 answer["pod"] = pod.format_http()
129 answer["meta"] = meta
131 self.finish_request(answer)
135 def delete(self, pod_name):
138 # check for an existing pod to be deleted
139 mongo_dict = yield self.db.pod.find_one(
141 pod = TestProject.pod(mongo_dict)
143 raise HTTPError(HTTP_NOT_FOUND,
144 "{} could not be found as a pod to be deleted"
147 # just delete it, or maybe save it elsewhere in a future
148 res = yield self.db.test_projects.remove(
152 meta["success"] = True
153 meta["deletion-data"] = res
156 answer["meta"] = meta
158 self.finish_request(answer)
163 class TestProjectHandler(GenericApiHandler):
165 TestProjectHandler Class
166 Handle the requests about the Test projects
168 - GET : Get all test projects and details about a specific one
169 - POST : Add a test project
170 - PUT : Edit test projects information (name and/or description)
171 - DELETE : Remove a test project
174 def initialize(self):
175 """ Prepares the database for the entire class """
176 super(TestProjectHandler, self).initialize()
180 def get(self, project_name=None):
186 if project_name is None:
191 if len(project_name) > 0:
192 get_request["name"] = project_name
195 cursor = self.db.test_projects.find(get_request)
196 while (yield cursor.fetch_next):
197 test_project = TestProject.testproject_from_dict(
198 cursor.next_object())
199 res.append(test_project.format_http())
202 meta["total"] = len(res)
203 meta["success"] = True if len(res) > 0 else False
206 answer["test_projects"] = res
207 answer["meta"] = meta
209 self.finish_request(answer)
214 """ Create a test project"""
216 if self.json_args is None:
217 raise HTTPError(HTTP_BAD_REQUEST)
219 query = {"name": self.json_args.get("name")}
221 # check for name in db
222 mongo_dict = yield self.db.test_projects.find_one(query)
223 if mongo_dict is not None:
224 raise HTTPError(HTTP_FORBIDDEN,
225 "{} already exists as a project".format(
226 self.json_args.get("name")))
228 test_project = TestProject.testproject_from_dict(self.json_args)
229 test_project.creation_date = datetime.now()
231 future = self.db.test_projects.insert(test_project.format())
232 result = yield future
233 test_project._id = result
235 self.finish_request(test_project.format_http())
239 def put(self, project_name):
240 """ Updates the name and description of a test project"""
242 print "PUT request for : {}".format(project_name)
244 query = {'name': project_name}
245 mongo_dict = yield self.db.test_projects.find_one(query)
246 test_project = TestProject.testproject_from_dict(mongo_dict)
247 if test_project is None:
248 raise HTTPError(HTTP_NOT_FOUND,
249 "{} could not be found".format(project_name))
251 new_name = self.json_args.get("name")
252 new_description = self.json_args.get("description")
254 # check for payload name parameter in db
255 # avoid a request if the project name has not changed in the payload
256 if new_name != test_project.name:
257 mongo_dict = yield self.db.test_projects.find_one(
259 if mongo_dict is not None:
260 raise HTTPError(HTTP_FORBIDDEN,
261 "{} already exists as a project"
264 # new dict for changes
266 request = prepare_put_request(request,
270 request = prepare_put_request(request,
273 test_project.description)
275 """ raise exception if there isn't a change """
277 raise HTTPError(HTTP_FORBIDDEN,
280 """ we merge the whole document """
281 edit_request = test_project.format()
282 edit_request.update(request)
284 """ Updating the DB """
285 res = yield self.db.test_projects.update({'name': project_name},
288 edit_request["_id"] = str(test_project._id)
290 self.finish_request({"message": "success", "content": edit_request})
294 def delete(self, project_name):
295 """ Remove a test project"""
297 print "DELETE request for : {}".format(project_name)
299 # check for an existing project to be deleted
300 mongo_dict = yield self.db.test_projects.find_one(
301 {'name': project_name})
302 test_project = TestProject.testproject_from_dict(mongo_dict)
303 if test_project is None:
304 raise HTTPError(HTTP_NOT_FOUND,
305 "{} could not be found as a project to be deleted"
306 .format(project_name))
308 # just delete it, or maybe save it elsewhere in a future
309 res = yield self.db.test_projects.remove(
310 {'name': project_name})
313 self.finish_request({"message": "success"})
316 class TestCasesHandler(GenericApiHandler):
318 TestCasesHandler Class
319 Handle the requests about the Test cases for test projects
321 - GET : Get all test cases and details about a specific one
322 - POST : Add a test project
323 - PUT : Edit test projects information (name and/or description)
326 def initialize(self):
327 """ Prepares the database for the entire class """
328 super(TestCasesHandler, self).initialize()
332 def get(self, project_name, case_name=None):
334 Get testcases(s) info
339 if case_name is None:
343 get_request["project_name"] = project_name
345 if len(case_name) > 0:
346 get_request["name"] = case_name
349 cursor = self.db.test_cases.find(get_request)
351 while (yield cursor.fetch_next):
352 test_case = TestCase.test_case_from_dict(cursor.next_object())
353 res.append(test_case.format_http())
356 meta["total"] = len(res)
357 meta["success"] = True if len(res) > 0 else False
360 answer["test_cases"] = res
361 answer["meta"] = meta
363 self.finish_request(answer)
367 def post(self, project_name):
368 """ Create a test case"""
370 print "POST Request for {}".format(project_name)
372 if self.json_args is None:
373 raise HTTPError(HTTP_BAD_REQUEST,
374 "Check your request payload")
376 # retrieve test project
377 mongo_dict = yield self.db.test_projects.find_one(
378 {"name": project_name})
379 if mongo_dict is None:
380 raise HTTPError(HTTP_FORBIDDEN,
381 "Could not find project {}"
382 .format(project_name))
384 # test_project = TestProject.testproject_from_dict(self.json_args)
386 case = TestCase.test_case_from_dict(self.json_args)
387 case.project_name = project_name
388 case.creation_date = datetime.now()
390 future = self.db.test_cases.insert(case.format())
391 result = yield future
393 self.finish_request(case.format_http())
397 def put(self, project_name, case_name):
399 Updates the name and description of a test case
400 :raises HTTPError (HTTP_NOT_FOUND, HTTP_FORBIDDEN)
403 print "PUT request for : {}/{}".format(project_name, case_name)
404 case_request = {'project_name': project_name, 'name': case_name}
406 # check if there is a case for the project in url parameters
407 mongo_dict = yield self.db.test_cases.find_one(case_request)
408 test_case = TestCase.test_case_from_dict(mongo_dict)
409 if test_case is None:
410 raise HTTPError(HTTP_NOT_FOUND,
411 "{} could not be found as a {} case to be updated"
412 .format(case_name, project_name))
414 new_name = self.json_args.get("name")
415 new_project_name = self.json_args.get("project_name")
416 new_description = self.json_args.get("description")
418 # check if there is not an existing test case
419 # with the name provided in the json payload
420 mongo_dict = yield self.db.test_cases.find_one(
421 {'project_name': new_project_name, 'name': new_name})
422 if mongo_dict is not None:
423 raise HTTPError(HTTP_FORBIDDEN,
424 "{} already exists as a project"
427 # new dict for changes
429 request = prepare_put_request(request,
433 request = prepare_put_request(request,
436 test_case.project_name)
437 request = prepare_put_request(request,
440 test_case.description)
442 # we raise an exception if there isn't a change
444 raise HTTPError(HTTP_FORBIDDEN,
447 # we merge the whole document """
448 edit_request = test_case.format()
449 edit_request.update(request)
451 """ Updating the DB """
452 res = yield self.db.test_cases.update(case_request, edit_request)
454 edit_request["_id"] = str(test_case._id)
456 self.finish_request({"message": "success", "content": edit_request})
460 def delete(self, project_name, case_name):
461 """ Remove a test case"""
463 print "DELETE request for : {}/{}".format(project_name, case_name)
464 case_request = {'project_name': project_name, 'name': case_name}
466 # check for an existing case to be deleted
467 mongo_dict = yield self.db.test_cases.find_one(case_request)
468 test_project = TestProject.testproject_from_dict(mongo_dict)
469 if test_project is None:
470 raise HTTPError(HTTP_NOT_FOUND,
471 "{}/{} could not be found as a case to be deleted"
472 .format(project_name, case_name))
474 # just delete it, or maybe save it elsewhere in a future
475 res = yield self.db.test_projects.remove(case_request)
478 self.finish_request({"message": "success"})
481 class TestResultsHandler(GenericApiHandler):
483 TestResultsHandler Class
484 Handle the requests about the Test project's results
486 - GET : Get all test results and details about a specific one
487 - POST : Add a test results
488 - DELETE : Remove a test result
491 def initialize(self):
492 """ Prepares the database for the entire class """
493 super(TestResultsHandler, self).initialize()
494 self.name = "test_result"
498 def get(self, result_id=None):
500 Retrieve result(s) for a test project on a specific POD.
501 Available filters for this request are :
502 - project : project name
505 - version : platform version (Arno-R1, ...)
506 - installer (fuel, ...)
507 - period : x (x last days)
510 :param result_id: Get a result by ID
513 GET /results/project=functest&case=vPing&version=Arno-R1 \
514 &pod=pod_name&period=15
515 => get results with optional filters
518 project_arg = self.get_query_argument("project", None)
519 case_arg = self.get_query_argument("case", None)
520 pod_arg = self.get_query_argument("pod", None)
521 version_arg = self.get_query_argument("version", None)
522 installer_arg = self.get_query_argument("installer", None)
523 period_arg = self.get_query_argument("period", None)
527 if result_id is None:
528 if project_arg is not None:
529 get_request["project_name"] = project_arg
531 if case_arg is not None:
532 get_request["case_name"] = case_arg
534 if pod_arg is not None:
535 get_request["pod_name"] = pod_arg
537 if version_arg is not None:
538 get_request["version"] = version_arg
540 if installer_arg is not None:
541 get_request["installer"] = installer_arg
543 if period_arg is not None:
545 period_arg = int(period_arg)
547 raise HTTPError(HTTP_BAD_REQUEST)
550 period = datetime.now() - timedelta(days=period_arg)
551 obj = {"$gte": period}
552 get_request["creation_date"] = obj
554 get_request["_id"] = result_id
559 cursor = self.db.test_results.find(get_request)
560 while (yield cursor.fetch_next):
561 test_result = TestResult.test_result_from_dict(
562 cursor.next_object())
563 res.append(test_result.format_http())
565 # building meta object
567 meta["total"] = len(res)
569 # final response object
571 answer["test_results"] = res
572 answer["meta"] = meta
573 self.finish_request(answer)
579 Create a new test result
580 :return: status of the request
584 # check for request payload
585 if self.json_args is None:
586 raise HTTPError(HTTP_BAD_REQUEST)
588 # check for missing parameters in the request payload
589 if self.json_args.get("project_name") is None:
590 raise HTTPError(HTTP_BAD_REQUEST)
591 if self.json_args.get("case_name") is None:
592 raise HTTPError(HTTP_BAD_REQUEST)
593 # check for pod_name instead of id,
594 # keeping id for current implementations
595 if self.json_args.get("pod_name") is None:
596 raise HTTPError(HTTP_BAD_REQUEST)
598 # TODO : replace checks with jsonschema
600 mongo_dict = yield self.db.test_projects.find_one(
601 {"name": self.json_args.get("project_name")})
602 if mongo_dict is None:
603 raise HTTPError(HTTP_NOT_FOUND,
604 "Could not find project [{}] "
605 .format(self.json_args.get("project_name")))
608 mongo_dict = yield self.db.test_cases.find_one(
609 {"name": self.json_args.get("case_name")})
610 if mongo_dict is None:
611 raise HTTPError(HTTP_NOT_FOUND,
612 "Could not find case [{}] "
613 .format(self.json_args.get("case_name")))
616 mongo_dict = yield self.db.pod.find_one(
617 {"name": self.json_args.get("pod_name")})
618 if mongo_dict is None:
619 raise HTTPError(HTTP_NOT_FOUND,
620 "Could not find POD [{}] "
621 .format(self.json_args.get("pod_name")))
623 # convert payload to object
624 test_result = TestResult.test_result_from_dict(self.json_args)
625 test_result.creation_date = datetime.now()
627 future = self.db.test_results.insert(test_result.format(),
629 result = yield future
630 test_result._id = result
632 self.finish_request(test_result.format_http())