swagger-ize result-apis of testAPI
[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 # 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 # feng.xiaowei@zte.com.cn refactor testcase related handler  5-20-2016
13 # feng.xiaowei@zte.com.cn refactor result related handler    5-23-2016
14 # feng.xiaowei@zte.com.cn refactor dashboard related handler 5-24-2016
15 # feng.xiaowei@zte.com.cn add methods to GenericApiHandler   5-26-2016
16 # feng.xiaowei@zte.com.cn remove PodHandler                  5-26-2016
17 ##############################################################################
18
19 import json
20 from datetime import datetime, timedelta
21
22 from tornado.web import RequestHandler, asynchronous, HTTPError
23 from tornado import gen
24
25 from models import CreateResponse
26 from resources.result_models import TestResult
27 from common.constants import DEFAULT_REPRESENTATION, HTTP_BAD_REQUEST, \
28     HTTP_NOT_FOUND, HTTP_FORBIDDEN
29 from common.config import prepare_put_request
30 from dashboard.dashboard_utils import check_dashboard_ready_project, \
31     check_dashboard_ready_case, get_dashboard_result
32
33
34 def format_data(data, cls):
35     cls_data = cls.from_dict(data)
36     return cls_data.format_http()
37
38
39 class GenericApiHandler(RequestHandler):
40     def __init__(self, application, request, **kwargs):
41         super(GenericApiHandler, self).__init__(application, request, **kwargs)
42         self.db = self.settings["db"]
43         self.json_args = None
44         self.table = None
45         self.table_cls = None
46         self.db_projects = 'projects'
47         self.db_pods = 'pods'
48         self.db_testcases = 'testcases'
49         self.db_results = 'results'
50
51     def prepare(self):
52         if self.request.method != "GET" and self.request.method != "DELETE":
53             if self.request.headers.get("Content-Type") is not None:
54                 if self.request.headers["Content-Type"].startswith(
55                         DEFAULT_REPRESENTATION):
56                     try:
57                         self.json_args = json.loads(self.request.body)
58                     except (ValueError, KeyError, TypeError) as error:
59                         raise HTTPError(HTTP_BAD_REQUEST,
60                                         "Bad Json format [{}]".
61                                         format(error))
62
63     def finish_request(self, json_object=None):
64         if json_object:
65             self.write(json.dumps(json_object))
66         self.set_header("Content-Type", DEFAULT_REPRESENTATION)
67         self.finish()
68
69     def _create_response(self, resource):
70         href = self.request.full_url() + '/' + str(resource)
71         return CreateResponse(href=href).format()
72
73     @asynchronous
74     @gen.coroutine
75     def _create(self, miss_checks, db_checks, **kwargs):
76         """
77         :param miss_checks: [miss1, miss2]
78         :param db_checks: [(table, exist, query, (error, message))]
79         :param db_op: (insert/remove)
80         :param res_op: (_create_response/None)
81         :return:
82         """
83         if self.json_args is None:
84             raise HTTPError(HTTP_BAD_REQUEST, "no body")
85
86         data = self.table_cls.from_dict(self.json_args)
87         for miss in miss_checks:
88             miss_data = data.__getattribute__(miss)
89             if miss_data is None or miss_data == '':
90                 raise HTTPError(HTTP_BAD_REQUEST,
91                                 '{} missing'.format(miss))
92
93         for k, v in kwargs.iteritems():
94             data.__setattr__(k, v)
95
96         for table, exist, query, error in db_checks:
97             check = yield self._eval_db(table, 'find_one', query(data))
98             if (exist and not check) or (not exist and check):
99                 code, message = error(data)
100                 raise HTTPError(code, message)
101
102         data.creation_date = datetime.now()
103         _id = yield self._eval_db(self.table, 'insert', data.format())
104         if 'name' in self.json_args:
105             resource = data.name
106         else:
107             resource = _id
108         self.finish_request(self._create_response(resource))
109
110     @asynchronous
111     @gen.coroutine
112     def _list(self, query=None):
113         if query is None:
114             query = {}
115         res = []
116         cursor = self._eval_db(self.table, 'find', query)
117         while (yield cursor.fetch_next):
118             res.append(format_data(cursor.next_object(), self.table_cls))
119         self.finish_request({self.table: res})
120
121     @asynchronous
122     @gen.coroutine
123     def _get_one(self, query):
124         data = yield self._eval_db(self.table, 'find_one', query)
125         if data is None:
126             raise HTTPError(HTTP_NOT_FOUND,
127                             "[{}] not exist in table [{}]"
128                             .format(query, self.table))
129         self.finish_request(format_data(data, self.table_cls))
130
131     @asynchronous
132     @gen.coroutine
133     def _delete(self, query):
134         data = yield self._eval_db(self.table, 'find_one', query)
135         if data is None:
136             raise HTTPError(HTTP_NOT_FOUND,
137                             "[{}] not exit in table [{}]"
138                             .format(query, self.table))
139
140         yield self._eval_db(self.table, 'remove', query)
141         self.finish_request()
142
143     @asynchronous
144     @gen.coroutine
145     def _update(self, query, db_keys):
146         if self.json_args is None:
147             raise HTTPError(HTTP_BAD_REQUEST, "No payload")
148
149         # check old data exist
150         from_data = yield self._eval_db(self.table, 'find_one', query)
151         if from_data is None:
152             raise HTTPError(HTTP_NOT_FOUND,
153                             "{} could not be found in table [{}]"
154                             .format(query, self.table))
155
156         data = self.table_cls.from_dict(from_data)
157         # check new data exist
158         equal, new_query = self._update_query(db_keys, data)
159         if not equal:
160             to_data = yield self._eval_db(self.table, 'find_one', new_query)
161             if to_data is not None:
162                 raise HTTPError(HTTP_FORBIDDEN,
163                                 "{} already exists in table [{}]"
164                                 .format(new_query, self.table))
165
166         # we merge the whole document """
167         edit_request = data.format()
168         edit_request.update(self._update_request(data))
169
170         """ Updating the DB """
171         yield self._eval_db(self.table, 'update', query, edit_request)
172         edit_request['_id'] = str(data._id)
173         self.finish_request(edit_request)
174
175     def _update_request(self, data):
176         request = dict()
177         for k, v in self.json_args.iteritems():
178             request = prepare_put_request(request, k, v,
179                                           data.__getattribute__(k))
180         if not request:
181             raise HTTPError(HTTP_FORBIDDEN, "Nothing to update")
182         return request
183
184     def _update_query(self, keys, data):
185         query = dict()
186         equal = True
187         for key in keys:
188             new = self.json_args.get(key)
189             old = data.__getattribute__(key)
190             if new is None:
191                 new = old
192             elif new != old:
193                 equal = False
194             query[key] = new
195         return equal, query
196
197     def _eval_db(self, table, method, *args):
198         return eval('self.db.%s.%s(*args)' % (table, method))
199
200
201 class VersionHandler(GenericApiHandler):
202     """ Display a message for the API version """
203     def get(self):
204         self.finish_request([{'v1': 'basics'}])
205
206
207 class DashboardHandler(GenericApiHandler):
208     """
209     DashboardHandler Class
210     Handle the requests about the Test project's results
211     in a dahboard ready format
212     HTTP Methdods :
213         - GET : Get all test results and details about a specific one
214     """
215     def initialize(self):
216         """ Prepares the database for the entire class """
217         super(DashboardHandler, self).initialize()
218         self.name = "dashboard"
219
220     @asynchronous
221     @gen.coroutine
222     def get(self):
223         """
224         Retrieve dashboard ready result(s) for a test project
225         Available filters for this request are :
226          - project : project name
227          - case : case name
228          - pod : pod name
229          - version : platform version (Arno-R1, ...)
230          - installer (fuel, ...)
231          - period : x (x last days)
232
233
234         :param result_id: Get a result by ID
235         :raise HTTPError
236
237         GET /dashboard?project=functest&case=vPing&version=Arno-R1 \
238         &pod=pod_name&period=15
239         => get results with optional filters
240         """
241
242         project_arg = self.get_query_argument("project", None)
243         case_arg = self.get_query_argument("case", None)
244         pod_arg = self.get_query_argument("pod", None)
245         version_arg = self.get_query_argument("version", None)
246         installer_arg = self.get_query_argument("installer", None)
247         period_arg = self.get_query_argument("period", None)
248
249         # prepare request
250         query = dict()
251
252         if project_arg is not None:
253             query["project_name"] = project_arg
254
255         if case_arg is not None:
256             query["case_name"] = case_arg
257
258         if pod_arg is not None:
259             query["pod_name"] = pod_arg
260
261         if version_arg is not None:
262             query["version"] = version_arg
263
264         if installer_arg is not None:
265             query["installer"] = installer_arg
266
267         if period_arg is not None:
268             try:
269                 period_arg = int(period_arg)
270             except:
271                 raise HTTPError(HTTP_BAD_REQUEST)
272             if period_arg > 0:
273                 period = datetime.now() - timedelta(days=period_arg)
274                 obj = {"$gte": str(period)}
275                 query["creation_date"] = obj
276
277         # on /dashboard retrieve the list of projects and testcases
278         # ready for dashboard
279         if project_arg is None:
280             raise HTTPError(HTTP_NOT_FOUND, "Project name missing")
281
282         if not check_dashboard_ready_project(project_arg):
283             raise HTTPError(HTTP_NOT_FOUND,
284                             'Project [{}] not dashboard ready'
285                             .format(project_arg))
286
287         if case_arg is None:
288             raise HTTPError(
289                 HTTP_NOT_FOUND,
290                 'Test case missing for project [{}]'.format(project_arg))
291
292         if not check_dashboard_ready_case(project_arg, case_arg):
293             raise HTTPError(
294                 HTTP_NOT_FOUND,
295                 'Test case [{}] not dashboard ready for project [{}]'
296                 .format(case_arg, project_arg))
297
298         # special case of status for project
299         res = []
300         if case_arg != "status":
301             cursor = self.db.results.find(query)
302             while (yield cursor.fetch_next):
303                 result = TestResult.from_dict(cursor.next_object())
304                 res.append(result.format_http())
305
306         # final response object
307         self.finish_request(get_dashboard_result(project_arg, case_arg, res))