fix POD id type in API
[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
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     """
64
65     def initialize(self):
66         """ Prepares the database for the entire class """
67         super(PodHandler, self).initialize()
68
69     @asynchronous
70     @gen.coroutine
71     def get(self, pod_id=None):
72         """
73         Get all pods or a single pod
74         :param pod_id:
75         """
76
77         if pod_id is None:
78             pod_id = ""
79
80         get_request = dict()
81
82         if len(pod_id) > 0:
83             get_request["_id"] = int(pod_id)
84
85         res = []
86         cursor = self.db.pod.find(get_request)
87         while (yield cursor.fetch_next):
88             pod = Pod.pod_from_dict(cursor.next_object())
89             res.append(pod.format())
90
91         meta = dict()
92         meta["total"] = len(res)
93         meta["success"] = True if len(res) > 0 else False
94
95         answer = dict()
96         answer["pods"] = res
97         answer["meta"] = meta
98
99         self.finish_request(answer)
100
101
102 class TestProjectHandler(GenericApiHandler):
103     """
104     TestProjectHandler Class
105     Handle the requests about the Test projects
106     HTTP Methdods :
107         - GET : Get all test projects and details about a specific one
108         - POST : Add a test project
109         - PUT : Edit test projects information (name and/or description)
110         - DELETE : Remove a test project
111     """
112
113     def initialize(self):
114         """ Prepares the database for the entire class """
115         super(TestProjectHandler, self).initialize()
116
117     @asynchronous
118     @gen.coroutine
119     def get(self, project_name=None):
120         """
121         Get Project(s) info
122         :param project_name:
123         """
124
125         if project_name is None:
126             project_name = ""
127
128         get_request = dict()
129
130         if len(project_name) > 0:
131             get_request["name"] = project_name
132
133         res = []
134         cursor = self.db.test_projects.find(get_request)
135         while (yield cursor.fetch_next):
136             test_project = TestProject.testproject_from_dict(
137                 cursor.next_object())
138             res.append(test_project.format_http())
139
140         meta = dict()
141         meta["total"] = len(res)
142         meta["success"] = True if len(res) > 0 else False
143
144         answer = dict()
145         answer["test_projects"] = res
146         answer["meta"] = meta
147
148         self.finish_request(answer)
149
150     @asynchronous
151     @gen.coroutine
152     def post(self):
153         """ Create a test project"""
154
155         if self.json_args is None:
156             raise HTTPError(HTTP_BAD_REQUEST)
157
158         query = {"name": self.json_args.get("name")}
159
160         # check for name in db
161         mongo_dict = yield self.db.test_projects.find_one(query)
162         if mongo_dict is not None:
163             raise HTTPError(HTTP_FORBIDDEN,
164                             "{} already exists as a project".format(
165                                 self.json_args.get("name")))
166
167         test_project = TestProject.testproject_from_dict(self.json_args)
168         test_project.creation_date = datetime.now()
169
170         future = self.db.test_projects.insert(test_project.format())
171         result = yield future
172         test_project._id = result
173
174         self.finish_request(test_project.format_http())
175
176     @asynchronous
177     @gen.coroutine
178     def put(self, project_name):
179         """ Updates the name and description of a test project"""
180
181         print "PUT request for : {}".format(project_name)
182
183         query = {'name': project_name}
184         mongo_dict = yield self.db.test_projects.find_one(query)
185         test_project = TestProject.testproject_from_dict(mongo_dict)
186         if test_project is None:
187             raise HTTPError(HTTP_NOT_FOUND,
188                             "{} could not be found".format(project_name))
189
190         new_name = self.json_args.get("name")
191         new_description = self.json_args.get("description")
192
193         # check for payload name parameter in db
194         # avoid a request if the project name has not changed in the payload
195         if new_name != test_project.name:
196             mongo_dict = yield self.db.test_projects.find_one(
197                 {"name": new_name})
198             if mongo_dict is not None:
199                 raise HTTPError(HTTP_FORBIDDEN,
200                                 "{} already exists as a project"
201                                 .format(new_name))
202
203         # new dict for changes
204         request = dict()
205         request = prepare_put_request(request,
206                                       "name",
207                                       new_name,
208                                       test_project.name)
209         request = prepare_put_request(request,
210                                       "description",
211                                       new_description,
212                                       test_project.description)
213
214         """ raise exception if there isn't a change """
215         if not request:
216             raise HTTPError(HTTP_FORBIDDEN,
217                             "Nothing to update")
218
219         """ we merge the whole document """
220         edit_request = test_project.format()
221         edit_request.update(request)
222
223         """ Updating the DB """
224         res = yield self.db.test_projects.update({'name': project_name},
225                                                  edit_request)
226         print res
227         edit_request["_id"] = str(test_project._id)
228
229         self.finish_request({"message": "success", "content": edit_request})
230
231     @asynchronous
232     @gen.coroutine
233     def delete(self, project_name):
234         """ Remove a test project"""
235
236         print "DELETE request for : {}".format(project_name)
237
238         # check for an existing project to be deleted
239         mongo_dict = yield self.db.test_projects.find_one(
240             {'name': project_name})
241         test_project = TestProject.testproject_from_dict(mongo_dict)
242         if test_project is None:
243             raise HTTPError(HTTP_NOT_FOUND,
244                             "{} could not be found as a project to be deleted"
245                             .format(project_name))
246
247         # just delete it, or maybe save it elsewhere in a future
248         res = yield self.db.test_projects.remove(
249             {'name': project_name})
250         print res
251
252         self.finish_request({"message": "success"})
253
254
255 class TestCasesHandler(GenericApiHandler):
256     """
257     TestCasesHandler Class
258     Handle the requests about the Test cases for test projects
259     HTTP Methdods :
260         - GET : Get all test cases and details about a specific one
261         - POST : Add a test project
262         - PUT : Edit test projects information (name and/or description)
263     """
264
265     def initialize(self):
266         """ Prepares the database for the entire class """
267         super(TestCasesHandler, self).initialize()
268
269     @asynchronous
270     @gen.coroutine
271     def get(self, project_name, case_name=None):
272         """
273         Get testcases(s) info
274         :param project_name:
275         :param case_name:
276         """
277
278         if case_name is None:
279             case_name = ""
280
281         get_request = dict()
282         get_request["project_name"] = project_name
283
284         if len(case_name) > 0:
285             get_request["name"] = case_name
286
287         res = []
288         cursor = self.db.test_cases.find(get_request)
289         print get_request
290         while (yield cursor.fetch_next):
291                 test_case = TestCase.test_case_from_dict(cursor.next_object())
292                 res.append(test_case.format_http())
293
294         meta = dict()
295         meta["total"] = len(res)
296         meta["success"] = True if len(res) > 0 else False
297
298         answer = dict()
299         answer["test_cases"] = res
300         answer["meta"] = meta
301
302         self.finish_request(answer)
303
304     @asynchronous
305     @gen.coroutine
306     def post(self, project_name):
307         """ Create a test case"""
308
309         print "POST Request for {}".format(project_name)
310
311         if self.json_args is None:
312             raise HTTPError(HTTP_BAD_REQUEST,
313                             "Check your request payload")
314
315         # retrieve test project
316         mongo_dict = yield self.db.test_projects.find_one(
317             {"name": project_name})
318         if mongo_dict is None:
319             raise HTTPError(HTTP_FORBIDDEN,
320                             "Could not find project {}"
321                             .format(project_name))
322
323         # test_project = TestProject.testproject_from_dict(self.json_args)
324
325         case = TestCase.test_case_from_dict(self.json_args)
326         case.project_name = project_name
327         case.creation_date = datetime.now()
328
329         future = self.db.test_cases.insert(case.format())
330         result = yield future
331         case._id = result
332         self.finish_request(case.format_http())
333
334     @asynchronous
335     @gen.coroutine
336     def put(self, project_name, case_name):
337         """
338         Updates the name and description of a test case
339         :raises HTTPError (HTTP_NOT_FOUND, HTTP_FORBIDDEN)
340         """
341
342         print "PUT request for : {}/{}".format(project_name, case_name)
343         case_request = {'project_name': project_name, 'name': case_name}
344
345         # check if there is a case for the project in url parameters
346         mongo_dict = yield self.db.test_cases.find_one(case_request)
347         test_case = TestCase.test_case_from_dict(mongo_dict)
348         if test_case is None:
349             raise HTTPError(HTTP_NOT_FOUND,
350                             "{} could not be found as a {} case to be updated"
351                             .format(case_name, project_name))
352
353         new_name = self.json_args.get("name")
354         new_project_name = self.json_args.get("project_name")
355         new_description = self.json_args.get("description")
356
357         # check if there is not an existing test case
358         # with the name provided in the json payload
359         mongo_dict = yield self.db.test_cases.find_one(
360             {'project_name': new_project_name, 'name': new_name})
361         if mongo_dict is not None:
362             raise HTTPError(HTTP_FORBIDDEN,
363                             "{} already exists as a project"
364                             .format(new_name))
365
366         # new dict for changes
367         request = dict()
368         request = prepare_put_request(request,
369                                       "name",
370                                       new_name,
371                                       test_case.name)
372         request = prepare_put_request(request,
373                                       "project_name",
374                                       new_project_name,
375                                       test_case.project_name)
376         request = prepare_put_request(request,
377                                       "description",
378                                       new_description,
379                                       test_case.description)
380
381         # we raise an exception if there isn't a change
382         if not request:
383             raise HTTPError(HTTP_FORBIDDEN,
384                             "Nothing to update")
385
386         # we merge the whole document """
387         edit_request = test_case.format()
388         edit_request.update(request)
389
390         """ Updating the DB """
391         res = yield self.db.test_cases.update(case_request, edit_request)
392         print res
393         edit_request["_id"] = str(test_case._id)
394
395         self.finish_request({"message": "success", "content": edit_request})
396
397     @asynchronous
398     @gen.coroutine
399     def delete(self, project_name, case_name):
400         """ Remove a test case"""
401
402         print "DELETE request for : {}/{}".format(project_name, case_name)
403         case_request = {'project_name': project_name, 'name': case_name}
404
405         # check for an existing case to be deleted
406         mongo_dict = yield self.db.test_cases.find_one(case_request)
407         test_project = TestProject.testproject_from_dict(mongo_dict)
408         if test_project is None:
409             raise HTTPError(HTTP_NOT_FOUND,
410                             "{}/{} could not be found as a case to be deleted"
411                             .format(project_name, case_name))
412
413         # just delete it, or maybe save it elsewhere in a future
414         res = yield self.db.test_projects.remove(case_request)
415         print res
416
417         self.finish_request({"message": "success"})
418
419
420 class TestResultsHandler(GenericApiHandler):
421     """
422     TestResultsHandler Class
423     Handle the requests about the Test project's results
424     HTTP Methdods :
425         - GET : Get all test results and details about a specific one
426         - POST : Add a test results
427         - DELETE : Remove a test result
428     """
429
430     def initialize(self):
431         """ Prepares the database for the entire class """
432         super(TestResultsHandler, self).initialize()
433         self.name = "test_result"
434
435     @asynchronous
436     @gen.coroutine
437     def get(self, result_id=None):
438         """
439         Retrieve result(s) for a test project on a specific POD.
440         Available filters for this request are :
441          - project : project name
442          - case : case name
443          - pod : pod ID
444
445         :param result_id: Get a result by ID
446         :raise HTTPError
447
448         GET /results/project=functest&case=keystone.catalog&pod=1
449         => get results with optional filters
450         """
451
452         project_arg = self.get_query_argument("project", None)
453         case_arg = self.get_query_argument("case", None)
454         pod_arg = self.get_query_argument("pod", None)
455
456         # prepare request
457         get_request = dict()
458         if result_id is None:
459             if project_arg is not None:
460                 get_request["project_name"] = project_arg
461
462             if case_arg is not None:
463                 get_request["case_name"] = case_arg
464
465             if pod_arg is not None:
466                 get_request["pod_id"] = int(pod_arg)
467         else:
468             get_request["_id"] = result_id
469
470         res = []
471         # fetching results
472         cursor = self.db.test_results.find(get_request)
473         while (yield cursor.fetch_next):
474             test_result = TestResult.test_result_from_dict(cursor.next_object())
475             res.append(test_result.format_http())
476
477         # building meta object
478         meta = dict()
479         meta["total"] = len(res)
480
481         # final response object
482         answer = dict()
483         answer["test_results"] = res
484         answer["meta"] = meta
485         self.finish_request(answer)
486
487     @asynchronous
488     @gen.coroutine
489     def post(self):
490         """
491         Create a new test result
492         :return: status of the request
493         :raise HTTPError
494         """
495
496         # check for request payload
497         if self.json_args is None:
498             raise HTTPError(HTTP_BAD_REQUEST)
499
500         # check for missing parameters in the request payload
501         if self.json_args.get("project_name") is None:
502             raise HTTPError(HTTP_BAD_REQUEST)
503         if self.json_args.get("case_name") is None:
504             raise HTTPError(HTTP_BAD_REQUEST)
505         if self.json_args.get("pod_id") is None:
506             raise HTTPError(HTTP_BAD_REQUEST)
507
508         # TODO : replace checks with jsonschema
509         # check for project
510         mongo_dict = yield self.db.test_projects.find_one(
511             {"name": self.json_args.get("project_name")})
512         if mongo_dict is None:
513             raise HTTPError(HTTP_NOT_FOUND,
514                             "Could not find project [{}] "
515                             .format(self.json_args.get("project_name")))
516
517         # check for case
518         mongo_dict = yield self.db.test_cases.find_one(
519             {"name": self.json_args.get("case_name")})
520         if mongo_dict is None:
521             raise HTTPError(HTTP_NOT_FOUND,
522                             "Could not find case [{}] "
523                             .format(self.json_args.get("case_name")))
524
525         # check for pod
526         mongo_dict = yield self.db.pod.find_one(
527             {"_id": self.json_args.get("pod_id")})
528         if mongo_dict is None:
529             raise HTTPError(HTTP_NOT_FOUND,
530                             "Could not find POD [{}] "
531                             .format(self.json_args.get("pod_id")))
532
533         # convert payload to object
534         test_result = TestResult.test_result_from_dict(self.json_args)
535         test_result.creation_date = datetime.now()
536
537         future = self.db.test_results.insert(test_result.format())
538         result = yield future
539         test_result._id = result
540
541         self.finish_request(test_result.format_http())