From 7358b5733d9e25f68b44b366ebe7714544b24c6c Mon Sep 17 00:00:00 2001 From: SerenaFeng Date: Mon, 30 May 2016 19:14:52 +0800 Subject: swagger-ize dashboard and version apis of testAPI JIRA: FUNCTEST-273 Change-Id: I6f2b1de5488ba684d0c00e9f40daee2487a011cc Signed-off-by: SerenaFeng --- .../resources/dashboard_handlers.py | 99 +++++++++++ result_collection_api/resources/handlers.py | 182 ++++++--------------- result_collection_api/resources/models.py | 17 ++ result_collection_api/resources/result_handlers.py | 130 +++++++++------ 4 files changed, 246 insertions(+), 182 deletions(-) create mode 100644 result_collection_api/resources/dashboard_handlers.py (limited to 'result_collection_api/resources') diff --git a/result_collection_api/resources/dashboard_handlers.py b/result_collection_api/resources/dashboard_handlers.py new file mode 100644 index 0000000..320fcc2 --- /dev/null +++ b/result_collection_api/resources/dashboard_handlers.py @@ -0,0 +1,99 @@ +from tornado.web import HTTPError + +from common.constants import HTTP_NOT_FOUND +from dashboard.dashboard_utils import check_dashboard_ready_project, \ + check_dashboard_ready_case, get_dashboard_result +from resources.result_handlers import GenericResultHandler +from resources.result_models import TestResult +from tornado_swagger_ui.tornado_swagger import swagger + + +class GenericDashboardHandler(GenericResultHandler): + def __init__(self, application, request, **kwargs): + super(GenericDashboardHandler, self).__init__(application, + request, + **kwargs) + self.table = self.db_results + self.table_cls = TestResult + + +class DashboardHandler(GenericDashboardHandler): + @swagger.operation(nickname='query') + def get(self): + """ + @description: Retrieve dashboard ready result(s) + for a test project + @notes: Retrieve dashboard ready result(s) for a test project + Available filters for this request are : + - project : project name + - case : case name + - pod : pod name + - version : platform version (Arno-R1, ...) + - installer (fuel, ...) + - period : x (x last days) + + GET /dashboard?project=functest&case=vPing&version=Colorado \ + &pod=pod_name&period=15 + @rtype: L{string} + @param pod: pod name + @type pod: L{string} + @in pod: query + @required pod: False + @param project: project name + @type project: L{string} + @in project: query + @required project: True + @param case: case name + @type case: L{string} + @in case: query + @required case: True + @param version: i.e. Colorado + @type version: L{string} + @in version: query + @required version: False + @param installer: fuel/apex/joid/compass + @type installer: L{string} + @in installer: query + @required installer: False + @param period: last days + @type period: L{string} + @in period: query + @required period: False + @return 200: test result exist + @raise 400: period is not in + @raise 404: project or case name missing, + or project or case is not dashboard ready + """ + + project_arg = self.get_query_argument("project", None) + case_arg = self.get_query_argument("case", None) + + # on /dashboard retrieve the list of projects and testcases + # ready for dashboard + if project_arg is None: + raise HTTPError(HTTP_NOT_FOUND, "Project name missing") + + if not check_dashboard_ready_project(project_arg): + raise HTTPError(HTTP_NOT_FOUND, + 'Project [{}] not dashboard ready' + .format(project_arg)) + + if case_arg is None: + raise HTTPError( + HTTP_NOT_FOUND, + 'Test case missing for project [{}]'.format(project_arg)) + + if not check_dashboard_ready_case(project_arg, case_arg): + raise HTTPError( + HTTP_NOT_FOUND, + 'Test case [{}] not dashboard ready for project [{}]' + .format(case_arg, project_arg)) + + # special case of status for project + if case_arg == 'status': + self.finish_request(get_dashboard_result(project_arg, case_arg)) + else: + def get_result(res, project, case): + return get_dashboard_result(project, case, res) + + self._list(self.set_query(), get_result, project_arg, case_arg) diff --git a/result_collection_api/resources/handlers.py b/result_collection_api/resources/handlers.py index b5f7145..b4f7117 100644 --- a/result_collection_api/resources/handlers.py +++ b/result_collection_api/resources/handlers.py @@ -14,26 +14,22 @@ # feng.xiaowei@zte.com.cn refactor dashboard related handler 5-24-2016 # feng.xiaowei@zte.com.cn add methods to GenericApiHandler 5-26-2016 # feng.xiaowei@zte.com.cn remove PodHandler 5-26-2016 +# feng.xiaowei@zte.com.cn remove ProjectHandler 5-26-2016 +# feng.xiaowei@zte.com.cn remove TestcaseHandler 5-27-2016 +# feng.xiaowei@zte.com.cn remove ResultHandler 5-29-2016 +# feng.xiaowei@zte.com.cn remove DashboardHandler 5-30-2016 ############################################################################## import json -from datetime import datetime, timedelta +from datetime import datetime from tornado.web import RequestHandler, asynchronous, HTTPError from tornado import gen from models import CreateResponse -from resources.result_models import TestResult from common.constants import DEFAULT_REPRESENTATION, HTTP_BAD_REQUEST, \ HTTP_NOT_FOUND, HTTP_FORBIDDEN -from common.config import prepare_put_request -from dashboard.dashboard_utils import check_dashboard_ready_project, \ - check_dashboard_ready_case, get_dashboard_result - - -def format_data(data, cls): - cls_data = cls.from_dict(data) - return cls_data.format_http() +from tornado_swagger_ui.tornado_swagger import swagger class GenericApiHandler(RequestHandler): @@ -70,15 +66,16 @@ class GenericApiHandler(RequestHandler): href = self.request.full_url() + '/' + str(resource) return CreateResponse(href=href).format() + def format_data(self, data): + cls_data = self.table_cls.from_dict(data) + return cls_data.format_http() + @asynchronous @gen.coroutine def _create(self, miss_checks, db_checks, **kwargs): """ :param miss_checks: [miss1, miss2] - :param db_checks: [(table, exist, query, (error, message))] - :param db_op: (insert/remove) - :param res_op: (_create_response/None) - :return: + :param db_checks: [(table, exist, query, error)] """ if self.json_args is None: raise HTTPError(HTTP_BAD_REQUEST, "no body") @@ -94,7 +91,7 @@ class GenericApiHandler(RequestHandler): data.__setattr__(k, v) for table, exist, query, error in db_checks: - check = yield self._eval_db(table, 'find_one', query(data)) + check = yield self._eval_db_find_one(query(data), table) if (exist and not check) or (not exist and check): code, message = error(data) raise HTTPError(code, message) @@ -109,29 +106,33 @@ class GenericApiHandler(RequestHandler): @asynchronous @gen.coroutine - def _list(self, query=None): + def _list(self, query=None, res_op=None, *args): if query is None: query = {} - res = [] + data = [] cursor = self._eval_db(self.table, 'find', query) while (yield cursor.fetch_next): - res.append(format_data(cursor.next_object(), self.table_cls)) - self.finish_request({self.table: res}) + data.append(self.format_data(cursor.next_object())) + if res_op is None: + res = {self.table: data} + else: + res = res_op(data, *args) + self.finish_request(res) @asynchronous @gen.coroutine def _get_one(self, query): - data = yield self._eval_db(self.table, 'find_one', query) + data = yield self._eval_db_find_one(query) if data is None: raise HTTPError(HTTP_NOT_FOUND, "[{}] not exist in table [{}]" .format(query, self.table)) - self.finish_request(format_data(data, self.table_cls)) + self.finish_request(self.format_data(data)) @asynchronous @gen.coroutine def _delete(self, query): - data = yield self._eval_db(self.table, 'find_one', query) + data = yield self._eval_db_find_one(query) if data is None: raise HTTPError(HTTP_NOT_FOUND, "[{}] not exit in table [{}]" @@ -147,7 +148,7 @@ class GenericApiHandler(RequestHandler): raise HTTPError(HTTP_BAD_REQUEST, "No payload") # check old data exist - from_data = yield self._eval_db(self.table, 'find_one', query) + from_data = yield self._eval_db_find_one(query) if from_data is None: raise HTTPError(HTTP_NOT_FOUND, "{} could not be found in table [{}]" @@ -157,7 +158,7 @@ class GenericApiHandler(RequestHandler): # check new data exist equal, new_query = self._update_query(db_keys, data) if not equal: - to_data = yield self._eval_db(self.table, 'find_one', new_query) + to_data = yield self._eval_db_find_one(new_query) if to_data is not None: raise HTTPError(HTTP_FORBIDDEN, "{} already exists in table [{}]" @@ -165,22 +166,37 @@ class GenericApiHandler(RequestHandler): # we merge the whole document """ edit_request = data.format() - edit_request.update(self._update_request(data)) + edit_request.update(self._update_requests(data)) """ Updating the DB """ yield self._eval_db(self.table, 'update', query, edit_request) edit_request['_id'] = str(data._id) self.finish_request(edit_request) - def _update_request(self, data): + def _update_requests(self, data): request = dict() for k, v in self.json_args.iteritems(): - request = prepare_put_request(request, k, v, + request = self._update_request(request, k, v, data.__getattribute__(k)) if not request: raise HTTPError(HTTP_FORBIDDEN, "Nothing to update") return request + @staticmethod + def _update_request(edit_request, key, new_value, old_value): + """ + This function serves to prepare the elements in the update request. + We try to avoid replace the exact values in the db + edit_request should be a dict in which we add an entry (key) after + comparing values + """ + if not (new_value is None): + if len(new_value) > 0: + if new_value != old_value: + edit_request[key] = new_value + + return edit_request + def _update_query(self, keys, data): query = dict() equal = True @@ -197,111 +213,17 @@ class GenericApiHandler(RequestHandler): def _eval_db(self, table, method, *args): return eval('self.db.%s.%s(*args)' % (table, method)) - -class VersionHandler(GenericApiHandler): - """ Display a message for the API version """ - def get(self): - self.finish_request([{'v1': 'basics'}]) + def _eval_db_find_one(self, query, table=None): + if table is None: + table = self.table + return self._eval_db(table, 'find_one', query) -class DashboardHandler(GenericApiHandler): - """ - DashboardHandler Class - Handle the requests about the Test project's results - in a dahboard ready format - HTTP Methdods : - - GET : Get all test results and details about a specific one - """ - def initialize(self): - """ Prepares the database for the entire class """ - super(DashboardHandler, self).initialize() - self.name = "dashboard" - - @asynchronous - @gen.coroutine +class VersionHandler(GenericApiHandler): + @swagger.operation(nickname='list') def get(self): """ - Retrieve dashboard ready result(s) for a test project - Available filters for this request are : - - project : project name - - case : case name - - pod : pod name - - version : platform version (Arno-R1, ...) - - installer (fuel, ...) - - period : x (x last days) - - - :param result_id: Get a result by ID - :raise HTTPError - - GET /dashboard?project=functest&case=vPing&version=Arno-R1 \ - &pod=pod_name&period=15 - => get results with optional filters + @description: Display a message for the API version + @rtype: L{Versions} """ - - project_arg = self.get_query_argument("project", None) - case_arg = self.get_query_argument("case", None) - pod_arg = self.get_query_argument("pod", None) - version_arg = self.get_query_argument("version", None) - installer_arg = self.get_query_argument("installer", None) - period_arg = self.get_query_argument("period", None) - - # prepare request - query = dict() - - if project_arg is not None: - query["project_name"] = project_arg - - if case_arg is not None: - query["case_name"] = case_arg - - if pod_arg is not None: - query["pod_name"] = pod_arg - - if version_arg is not None: - query["version"] = version_arg - - if installer_arg is not None: - query["installer"] = installer_arg - - if period_arg is not None: - try: - period_arg = int(period_arg) - except: - raise HTTPError(HTTP_BAD_REQUEST) - if period_arg > 0: - period = datetime.now() - timedelta(days=period_arg) - obj = {"$gte": str(period)} - query["creation_date"] = obj - - # on /dashboard retrieve the list of projects and testcases - # ready for dashboard - if project_arg is None: - raise HTTPError(HTTP_NOT_FOUND, "Project name missing") - - if not check_dashboard_ready_project(project_arg): - raise HTTPError(HTTP_NOT_FOUND, - 'Project [{}] not dashboard ready' - .format(project_arg)) - - if case_arg is None: - raise HTTPError( - HTTP_NOT_FOUND, - 'Test case missing for project [{}]'.format(project_arg)) - - if not check_dashboard_ready_case(project_arg, case_arg): - raise HTTPError( - HTTP_NOT_FOUND, - 'Test case [{}] not dashboard ready for project [{}]' - .format(case_arg, project_arg)) - - # special case of status for project - res = [] - if case_arg != "status": - cursor = self.db.results.find(query) - while (yield cursor.fetch_next): - result = TestResult.from_dict(cursor.next_object()) - res.append(result.format_http()) - - # final response object - self.finish_request(get_dashboard_result(project_arg, case_arg, res)) + self.finish_request([{'v1': 'basics'}]) diff --git a/result_collection_api/resources/models.py b/result_collection_api/resources/models.py index b4094dd..3c834fd 100644 --- a/result_collection_api/resources/models.py +++ b/result_collection_api/resources/models.py @@ -13,6 +13,7 @@ # feng.xiaowei@zte.com.cn mv TestCase to testcase_models.py 5-20-2016 # feng.xiaowei@zte.com.cn mv TestResut to result_models.py 5-23-2016 ############################################################################## +from tornado_swagger_ui.tornado_swagger import swagger class CreateResponse(object): @@ -30,3 +31,19 @@ class CreateResponse(object): def format(self): return {'href': self.href} + + +@swagger.model() +class Versions(object): + """ + @ptype versions: C{list} of L{Version} + """ + def __init__(self, versions): + self.versions = versions + + +@swagger.model() +class Version(object): + def __init__(self, version=None, description=None): + self.version = version + self.description = description diff --git a/result_collection_api/resources/result_handlers.py b/result_collection_api/resources/result_handlers.py index d3fea1d..d344f46 100644 --- a/result_collection_api/resources/result_handlers.py +++ b/result_collection_api/resources/result_handlers.py @@ -17,67 +17,93 @@ class GenericResultHandler(GenericApiHandler): self.table = self.db_results self.table_cls = TestResult + def set_query(self): + query = dict() + for k in self.request.query_arguments.keys(): + v = self.get_query_argument(k) + if k == 'project' or k == 'pod' or k == 'case': + query[k + '_name'] = v + elif k == 'period': + try: + v = int(v) + except: + raise HTTPError(HTTP_BAD_REQUEST, 'period must be int') + if v > 0: + period = datetime.now() - timedelta(days=v) + obj = {"$gte": str(period)} + query['creation_date'] = obj + else: + query[k] = v + return query + class ResultsCLHandler(GenericResultHandler): @swagger.operation(nickname="list-all") def get(self): """ - @description: list all test results consist with query + @description: Retrieve result(s) for a test project + on a specific pod. + @notes: Retrieve result(s) for a test project on a specific pod. + Available filters for this request are : + - project : project name + - case : case name + - pod : pod name + - version : platform version (Arno-R1, ...) + - installer (fuel, ...) + - build_tag : Jenkins build tag name + - period : x (x last days) + - scenario : the test scenario (previously version) + - criteria : the global criteria status passed or failed + - trust_indicator : evaluate the stability of the test case to avoid + running systematically long and stable test case + + GET /results/project=functest&case=vPing&version=Arno-R1 \ + &pod=pod_name&period=15 @return 200: all test results consist with query, empty list if no result is found @rtype: L{TestResults} + @param pod: pod name + @type pod: L{string} + @in pod: query + @required pod: False + @param project: project name + @type project: L{string} + @in project: query + @required project: True + @param case: case name + @type case: L{string} + @in case: query + @required case: True + @param version: i.e. Colorado + @type version: L{string} + @in version: query + @required version: False + @param installer: fuel/apex/joid/compass + @type installer: L{string} + @in installer: query + @required installer: False + @param build_tag: i.e. v3.0 + @type build_tag: L{string} + @in build_tag: query + @required build_tag: False + @param scenario: i.e. odl + @type scenario: L{string} + @in scenario: query + @required scenario: False + @param criteria: i.e. passed + @type criteria: L{string} + @in criteria: query + @required criteria: False + @param period: last days + @type period: L{string} + @in period: query + @required period: False + @param trust_indicator: must be integer + @type trust_indicator: L{string} + @in trust_indicator: query + @required trust_indicator: False """ - query = dict() - pod_arg = self.get_query_argument("pod", None) - project_arg = self.get_query_argument("project", None) - case_arg = self.get_query_argument("case", None) - version_arg = self.get_query_argument("version", None) - installer_arg = self.get_query_argument("installer", None) - build_tag_arg = self.get_query_argument("build_tag", None) - scenario_arg = self.get_query_argument("scenario", None) - criteria_arg = self.get_query_argument("criteria", None) - period_arg = self.get_query_argument("period", None) - trust_indicator_arg = self.get_query_argument("trust_indicator", None) - - if project_arg is not None: - query["project_name"] = project_arg - - if case_arg is not None: - query["case_name"] = case_arg - - if pod_arg is not None: - query["pod_name"] = pod_arg - - if version_arg is not None: - query["version"] = version_arg - - if installer_arg is not None: - query["installer"] = installer_arg - - if build_tag_arg is not None: - query["build_tag"] = build_tag_arg - - if scenario_arg is not None: - query["scenario"] = scenario_arg - - if criteria_arg is not None: - query["criteria_tag"] = criteria_arg - - if trust_indicator_arg is not None: - query["trust_indicator_arg"] = trust_indicator_arg - - if period_arg is not None: - try: - period_arg = int(period_arg) - except: - raise HTTPError(HTTP_BAD_REQUEST) - - if period_arg > 0: - period = datetime.now() - timedelta(days=period_arg) - obj = {"$gte": str(period)} - query["creation_date"] = obj - - self._list(query) + self._list(self.set_query()) @swagger.operation(nickname="create") def post(self): -- cgit 1.2.3-korg