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 --- result_collection_api/common/config.py | 16 +- result_collection_api/dashboard/dashboard_utils.py | 2 +- .../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 +++++++++------ result_collection_api/result_collection_api.py | 4 +- result_collection_api/tests/unit/test_base.py | 5 +- result_collection_api/tests/unit/test_dashboard.py | 1 + result_collection_api/tests/unit/test_result.py | 23 ++- 10 files changed, 276 insertions(+), 203 deletions(-) create mode 100644 result_collection_api/resources/dashboard_handlers.py (limited to 'result_collection_api') diff --git a/result_collection_api/common/config.py b/result_collection_api/common/config.py index a62e805..369bdd2 100644 --- a/result_collection_api/common/config.py +++ b/result_collection_api/common/config.py @@ -5,27 +5,13 @@ # are made available under the terms of the Apache License, Version 2.0 # which accompanies this distribution, and is available at # http://www.apache.org/licenses/LICENSE-2.0 +# feng.xiaowei@zte.com.cn remove prepare_put_request 5-30-2016 ############################################################################## from ConfigParser import SafeConfigParser, NoOptionError -def prepare_put_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 - - class ParseError(Exception): """ Custom exception class for config file diff --git a/result_collection_api/dashboard/dashboard_utils.py b/result_collection_api/dashboard/dashboard_utils.py index bfa7432..9de5b19 100644 --- a/result_collection_api/dashboard/dashboard_utils.py +++ b/result_collection_api/dashboard/dashboard_utils.py @@ -67,7 +67,7 @@ def get_dashboard_cases(): return modules -def get_dashboard_result(project, case, results): +def get_dashboard_result(project, case, results=None): # get the dashboard ready results # paramters are: # project: project name 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): diff --git a/result_collection_api/result_collection_api.py b/result_collection_api/result_collection_api.py index 652aa58..7d29c9d 100644 --- a/result_collection_api/result_collection_api.py +++ b/result_collection_api/result_collection_api.py @@ -34,11 +34,12 @@ import argparse import tornado.ioloop import motor -from resources.handlers import VersionHandler, DashboardHandler +from resources.handlers import VersionHandler from resources.testcase_handlers import TestcaseCLHandler, TestcaseGURHandler from resources.pod_handlers import PodCLHandler, PodGURHandler from resources.project_handlers import ProjectCLHandler, ProjectGURHandler from resources.result_handlers import ResultsCLHandler, ResultsGURHandler +from resources.dashboard_handlers import DashboardHandler from common.config import APIConfig from tornado_swagger_ui.tornado_swagger import swagger @@ -92,7 +93,6 @@ def make_app(): # get /dashboard # => get the list of project with dashboard ready results (r"/dashboard/v1/results", DashboardHandler), - (r"/dashboard/v1/results([^/]*)", DashboardHandler), ], db=db, debug=CONF.api_debug_on, diff --git a/result_collection_api/tests/unit/test_base.py b/result_collection_api/tests/unit/test_base.py index 036c6cf..ef711ae 100644 --- a/result_collection_api/tests/unit/test_base.py +++ b/result_collection_api/tests/unit/test_base.py @@ -1,12 +1,14 @@ import json + from tornado.web import Application from tornado.testing import AsyncHTTPTestCase from resources.pod_handlers import PodCLHandler, PodGURHandler from resources.project_handlers import ProjectCLHandler, ProjectGURHandler -from resources.handlers import VersionHandler, DashboardHandler +from resources.handlers import VersionHandler from resources.testcase_handlers import TestcaseCLHandler, TestcaseGURHandler from resources.result_handlers import ResultsCLHandler, ResultsGURHandler +from resources.dashboard_handlers import DashboardHandler from resources.models import CreateResponse import fake_pymongo @@ -39,7 +41,6 @@ class TestBase(AsyncHTTPTestCase): (r"/api/v1/results", ResultsCLHandler), (r"/api/v1/results/([^/]+)", ResultsGURHandler), (r"/dashboard/v1/results", DashboardHandler), - (r"/dashboard/v1/results([^/]*)", DashboardHandler), ], db=fake_pymongo, debug=True, diff --git a/result_collection_api/tests/unit/test_dashboard.py b/result_collection_api/tests/unit/test_dashboard.py index 5c3f924..3e71333 100644 --- a/result_collection_api/tests/unit/test_dashboard.py +++ b/result_collection_api/tests/unit/test_dashboard.py @@ -44,6 +44,7 @@ class TestDashboardQuery(TestDashboardBase): def test_success(self): code, body = self.query(self._set_query()) self.assertEqual(code, HTTP_OK) + print(body) self.assertIn('{"description": "vPing results for Dashboard"}', body) def test_caseIsStatus(self): diff --git a/result_collection_api/tests/unit/test_result.py b/result_collection_api/tests/unit/test_result.py index 5757df0..a7541b3 100644 --- a/result_collection_api/tests/unit/test_result.py +++ b/result_collection_api/tests/unit/test_result.py @@ -1,4 +1,5 @@ import unittest +import copy from test_base import TestBase from resources.pod_models import PodCreateRequest @@ -8,7 +9,6 @@ from resources.result_models import ResultCreateRequest, \ TestResult, TestResults from common.constants import HTTP_OK, HTTP_BAD_REQUEST, HTTP_NOT_FOUND - __author__ = '__serena__' @@ -154,6 +154,27 @@ class TestResultCreate(TestResultBase): self.assertEqual(code, HTTP_OK) self.assert_href(body) + def test_createSameResults(self): + # req_again = ResultCreateRequest(pod_name=self.pod, + # project_name=self.project, + # case_name=self.case, + # installer=self.installer, + # version=self.version, + # start_date="2016-05-23 08:16:09.477097", + # stop_date=self.stop_date, + # details=self.details.format(), + # build_tag=self.build_tag, + # scenario=self.scenario, + # criteria=self.criteria, + # trust_indicator=self.trust_indicator) + req_again = copy.deepcopy(self.req_d) + req_again.start_date = "2016-05-23 08:16:09.477097" + req_again.stop_date = "2016-05-23 08:16:19.477097" + + (code, body) = self.create(req_again) + self.assertEqual(code, HTTP_OK) + self.assert_href(body) + class TestResultGet(TestResultBase): def test_getOne(self): -- cgit 1.2.3-korg