Do not check key in json file
[releng.git] / utils / test / result_collection_api / resources / handlers.py
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 ##############################################################################
9
10 import json
11
12 from tornado.web import RequestHandler, asynchronous, HTTPError
13 from tornado import gen
14 from datetime import datetime, timedelta
15
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
20
21
22 class GenericApiHandler(RequestHandler):
23     """
24     The purpose of this class is to take benefit of inheritance and prepare
25     a set of common functions for
26     the handlers
27     """
28
29     def initialize(self):
30         """ Prepares the database for the entire class """
31         self.db = self.settings["db"]
32
33     def prepare(self):
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):
38                     try:
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 [{}]".
43                                         format(error))
44                 else:
45                     self.json_args = None
46
47     def finish_request(self, json_object):
48         self.write(json.dumps(json_object))
49         self.set_header("Content-Type", DEFAULT_REPRESENTATION)
50         self.finish()
51
52
53 class VersionHandler(RequestHandler):
54     """ Display a message for the API version """
55     def get(self):
56         self.write("Collection of test result API, v1")
57
58
59 class PodHandler(GenericApiHandler):
60     """ Handle the requests about the POD Platforms
61     HTTP Methdods :
62         - GET : Get PODS
63         - POST : Create a pod
64         - DELETE : DELETE POD
65     """
66
67     def initialize(self):
68         """ Prepares the database for the entire class """
69         super(PodHandler, self).initialize()
70
71     @asynchronous
72     @gen.coroutine
73     def get(self, pod_name=None):
74         """
75         Get all pods or a single pod
76         :param pod_id:
77         """
78         get_request = dict()
79
80         if pod_name is not None:
81             get_request["name"] = pod_name
82
83         res = []
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())
88
89         meta = dict()
90         meta["total"] = len(res)
91         meta["success"] = True if len(res) > 0 else False
92
93         answer = dict()
94         answer["pods"] = res
95         answer["meta"] = meta
96
97         self.finish_request(answer)
98
99     @asynchronous
100     @gen.coroutine
101     def post(self):
102         """ Create a POD"""
103
104         if self.json_args is None:
105             raise HTTPError(HTTP_BAD_REQUEST)
106
107         query = {"name": self.json_args.get("name")}
108
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")))
115
116         pod = Pod.pod_from_dict(self.json_args)
117         pod.creation_date = datetime.now()
118
119         future = self.db.pod.insert(pod.format())
120         result = yield future
121         pod._id = result
122
123         meta = dict()
124         meta["success"] = True
125         meta["uri"] = "/pods/{}".format(pod.name)
126
127         answer = dict()
128         answer["pod"] = pod.format_http()
129         answer["meta"] = meta
130
131         self.finish_request(answer)
132
133     @asynchronous
134     @gen.coroutine
135     def delete(self, pod_name):
136         """ Remove a POD
137
138         # check for an existing pod to be deleted
139         mongo_dict = yield self.db.pod.find_one(
140             {'name': pod_name})
141         pod = TestProject.pod(mongo_dict)
142         if pod is None:
143             raise HTTPError(HTTP_NOT_FOUND,
144                             "{} could not be found as a pod to be deleted"
145                             .format(pod_name))
146
147         # just delete it, or maybe save it elsewhere in a future
148         res = yield self.db.test_projects.remove(
149             {'name': pod_name})
150
151         meta = dict()
152         meta["success"] = True
153         meta["deletion-data"] = res
154
155         answer = dict()
156         answer["meta"] = meta
157
158         self.finish_request(answer)
159         """
160         pass
161
162
163 class TestProjectHandler(GenericApiHandler):
164     """
165     TestProjectHandler Class
166     Handle the requests about the Test projects
167     HTTP Methdods :
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
172     """
173
174     def initialize(self):
175         """ Prepares the database for the entire class """
176         super(TestProjectHandler, self).initialize()
177
178     @asynchronous
179     @gen.coroutine
180     def get(self, project_name=None):
181         """
182         Get Project(s) info
183         :param project_name:
184         """
185
186         if project_name is None:
187             project_name = ""
188
189         get_request = dict()
190
191         if len(project_name) > 0:
192             get_request["name"] = project_name
193
194         res = []
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())
200
201         meta = dict()
202         meta["total"] = len(res)
203         meta["success"] = True if len(res) > 0 else False
204
205         answer = dict()
206         answer["test_projects"] = res
207         answer["meta"] = meta
208
209         self.finish_request(answer)
210
211     @asynchronous
212     @gen.coroutine
213     def post(self):
214         """ Create a test project"""
215
216         if self.json_args is None:
217             raise HTTPError(HTTP_BAD_REQUEST)
218
219         query = {"name": self.json_args.get("name")}
220
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")))
227
228         test_project = TestProject.testproject_from_dict(self.json_args)
229         test_project.creation_date = datetime.now()
230
231         future = self.db.test_projects.insert(test_project.format())
232         result = yield future
233         test_project._id = result
234
235         self.finish_request(test_project.format_http())
236
237     @asynchronous
238     @gen.coroutine
239     def put(self, project_name):
240         """ Updates the name and description of a test project"""
241
242         print "PUT request for : {}".format(project_name)
243
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))
250
251         new_name = self.json_args.get("name")
252         new_description = self.json_args.get("description")
253
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(
258                 {"name": new_name})
259             if mongo_dict is not None:
260                 raise HTTPError(HTTP_FORBIDDEN,
261                                 "{} already exists as a project"
262                                 .format(new_name))
263
264         # new dict for changes
265         request = dict()
266         request = prepare_put_request(request,
267                                       "name",
268                                       new_name,
269                                       test_project.name)
270         request = prepare_put_request(request,
271                                       "description",
272                                       new_description,
273                                       test_project.description)
274
275         """ raise exception if there isn't a change """
276         if not request:
277             raise HTTPError(HTTP_FORBIDDEN,
278                             "Nothing to update")
279
280         """ we merge the whole document """
281         edit_request = test_project.format()
282         edit_request.update(request)
283
284         """ Updating the DB """
285         res = yield self.db.test_projects.update({'name': project_name},
286                                                  edit_request)
287         print res
288         edit_request["_id"] = str(test_project._id)
289
290         self.finish_request({"message": "success", "content": edit_request})
291
292     @asynchronous
293     @gen.coroutine
294     def delete(self, project_name):
295         """ Remove a test project"""
296
297         print "DELETE request for : {}".format(project_name)
298
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))
307
308         # just delete it, or maybe save it elsewhere in a future
309         res = yield self.db.test_projects.remove(
310             {'name': project_name})
311         print res
312
313         self.finish_request({"message": "success"})
314
315
316 class TestCasesHandler(GenericApiHandler):
317     """
318     TestCasesHandler Class
319     Handle the requests about the Test cases for test projects
320     HTTP Methdods :
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)
324     """
325
326     def initialize(self):
327         """ Prepares the database for the entire class """
328         super(TestCasesHandler, self).initialize()
329
330     @asynchronous
331     @gen.coroutine
332     def get(self, project_name, case_name=None):
333         """
334         Get testcases(s) info
335         :param project_name:
336         :param case_name:
337         """
338
339         if case_name is None:
340             case_name = ""
341
342         get_request = dict()
343         get_request["project_name"] = project_name
344
345         if len(case_name) > 0:
346             get_request["name"] = case_name
347
348         res = []
349         cursor = self.db.test_cases.find(get_request)
350         print 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())
354
355         meta = dict()
356         meta["total"] = len(res)
357         meta["success"] = True if len(res) > 0 else False
358
359         answer = dict()
360         answer["test_cases"] = res
361         answer["meta"] = meta
362
363         self.finish_request(answer)
364
365     @asynchronous
366     @gen.coroutine
367     def post(self, project_name):
368         """ Create a test case"""
369
370         print "POST Request for {}".format(project_name)
371
372         if self.json_args is None:
373             raise HTTPError(HTTP_BAD_REQUEST,
374                             "Check your request payload")
375
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))
383
384         # test_project = TestProject.testproject_from_dict(self.json_args)
385
386         case = TestCase.test_case_from_dict(self.json_args)
387         case.project_name = project_name
388         case.creation_date = datetime.now()
389
390         future = self.db.test_cases.insert(case.format())
391         result = yield future
392         case._id = result
393         self.finish_request(case.format_http())
394
395     @asynchronous
396     @gen.coroutine
397     def put(self, project_name, case_name):
398         """
399         Updates the name and description of a test case
400         :raises HTTPError (HTTP_NOT_FOUND, HTTP_FORBIDDEN)
401         """
402
403         print "PUT request for : {}/{}".format(project_name, case_name)
404         case_request = {'project_name': project_name, 'name': case_name}
405
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))
413
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")
417
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"
425                             .format(new_name))
426
427         # new dict for changes
428         request = dict()
429         request = prepare_put_request(request,
430                                       "name",
431                                       new_name,
432                                       test_case.name)
433         request = prepare_put_request(request,
434                                       "project_name",
435                                       new_project_name,
436                                       test_case.project_name)
437         request = prepare_put_request(request,
438                                       "description",
439                                       new_description,
440                                       test_case.description)
441
442         # we raise an exception if there isn't a change
443         if not request:
444             raise HTTPError(HTTP_FORBIDDEN,
445                             "Nothing to update")
446
447         # we merge the whole document """
448         edit_request = test_case.format()
449         edit_request.update(request)
450
451         """ Updating the DB """
452         res = yield self.db.test_cases.update(case_request, edit_request)
453         print res
454         edit_request["_id"] = str(test_case._id)
455
456         self.finish_request({"message": "success", "content": edit_request})
457
458     @asynchronous
459     @gen.coroutine
460     def delete(self, project_name, case_name):
461         """ Remove a test case"""
462
463         print "DELETE request for : {}/{}".format(project_name, case_name)
464         case_request = {'project_name': project_name, 'name': case_name}
465
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))
473
474         # just delete it, or maybe save it elsewhere in a future
475         res = yield self.db.test_projects.remove(case_request)
476         print res
477
478         self.finish_request({"message": "success"})
479
480
481 class TestResultsHandler(GenericApiHandler):
482     """
483     TestResultsHandler Class
484     Handle the requests about the Test project's results
485     HTTP Methdods :
486         - GET : Get all test results and details about a specific one
487         - POST : Add a test results
488         - DELETE : Remove a test result
489     """
490
491     def initialize(self):
492         """ Prepares the database for the entire class """
493         super(TestResultsHandler, self).initialize()
494         self.name = "test_result"
495
496     @asynchronous
497     @gen.coroutine
498     def get(self, result_id=None):
499         """
500         Retrieve result(s) for a test project on a specific POD.
501         Available filters for this request are :
502          - project : project name
503          - case : case name
504          - pod : pod name
505          - version : platform version (Arno-R1, ...)
506          - installer (fuel, ...)
507          - period : x (x last days)
508
509
510         :param result_id: Get a result by ID
511         :raise HTTPError
512
513         GET /results/project=functest&case=vPing&version=Arno-R1 \
514         &pod=pod_name&period=15
515         => get results with optional filters
516         """
517
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)
524
525         # prepare request
526         get_request = dict()
527         if result_id is None:
528             if project_arg is not None:
529                 get_request["project_name"] = project_arg
530
531             if case_arg is not None:
532                 get_request["case_name"] = case_arg
533
534             if pod_arg is not None:
535                 get_request["pod_name"] = pod_arg
536
537             if version_arg is not None:
538                 get_request["version"] = version_arg
539
540             if installer_arg is not None:
541                 get_request["installer"] = installer_arg
542
543             if period_arg is not None:
544                 try:
545                     period_arg = int(period_arg)
546                 except:
547                     raise HTTPError(HTTP_BAD_REQUEST)
548
549                 if period_arg > 0:
550                     period = datetime.now() - timedelta(days=period_arg)
551                     obj = {"$gte": period}
552                     get_request["creation_date"] = obj
553         else:
554             get_request["_id"] = result_id
555
556         print get_request
557         res = []
558         # fetching results
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())
564
565         # building meta object
566         meta = dict()
567         meta["total"] = len(res)
568
569         # final response object
570         answer = dict()
571         answer["test_results"] = res
572         answer["meta"] = meta
573         self.finish_request(answer)
574
575     @asynchronous
576     @gen.coroutine
577     def post(self):
578         """
579         Create a new test result
580         :return: status of the request
581         :raise HTTPError
582         """
583
584         # check for request payload
585         if self.json_args is None:
586             raise HTTPError(HTTP_BAD_REQUEST)
587
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)
597
598         # TODO : replace checks with jsonschema
599         # check for project
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")))
606
607         # check for case
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")))
614
615         # check for pod
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")))
622
623         # convert payload to object
624         test_result = TestResult.test_result_from_dict(self.json_args)
625         test_result.creation_date = datetime.now()
626
627         future = self.db.test_results.insert(test_result.format(),
628                                              check_keys=False)
629         result = yield future
630         test_result._id = result
631
632         self.finish_request(test_result.format_http())