From 534ca019931d48a2a5f4075c0ca36b501d8f793d Mon Sep 17 00:00:00 2001 From: SerenaFeng Date: Sun, 29 May 2016 01:12:54 +0800 Subject: swagger-ize result-apis of testAPI JIRA: FUNCTEST-270 Change-Id: I82b1e3acee82d9b4931531c9404e13a663ff32de Signed-off-by: SerenaFeng --- .../result_collection_api/resources/handlers.py | 188 ++------------------- .../resources/pod_handlers.py | 7 +- .../resources/project_handlers.py | 7 +- .../resources/result_handlers.py | 134 +++++++++++++++ .../resources/result_models.py | 12 +- .../resources/testcase_handlers.py | 5 +- .../resources/testcase_models.py | 2 - .../result_collection_api/result_collection_api.py | 9 +- .../tests/unit/fake_pymongo.py | 2 + .../result_collection_api/tests/unit/test_base.py | 9 +- .../result_collection_api/tests/unit/test_pod.py | 4 +- .../tests/unit/test_project.py | 4 +- .../tests/unit/test_result.py | 10 +- .../tests/unit/test_testcase.py | 4 +- 14 files changed, 189 insertions(+), 208 deletions(-) create mode 100644 utils/test/result_collection_api/resources/result_handlers.py (limited to 'utils/test/result_collection_api') diff --git a/utils/test/result_collection_api/resources/handlers.py b/utils/test/result_collection_api/resources/handlers.py index 7d2e8161e..b5f71450c 100644 --- a/utils/test/result_collection_api/resources/handlers.py +++ b/utils/test/result_collection_api/resources/handlers.py @@ -67,13 +67,14 @@ class GenericApiHandler(RequestHandler): self.finish() def _create_response(self, resource): - href = self.request.full_url() + '/' + resource + href = self.request.full_url() + '/' + str(resource) return CreateResponse(href=href).format() @asynchronous @gen.coroutine - def _create(self, db_checks, **kwargs): + 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) @@ -83,10 +84,11 @@ class GenericApiHandler(RequestHandler): raise HTTPError(HTTP_BAD_REQUEST, "no body") data = self.table_cls.from_dict(self.json_args) - name = data.name - if name is None or name == '': - raise HTTPError(HTTP_BAD_REQUEST, - '{} name missing'.format(self.table[:-1])) + for miss in miss_checks: + miss_data = data.__getattribute__(miss) + if miss_data is None or miss_data == '': + raise HTTPError(HTTP_BAD_REQUEST, + '{} missing'.format(miss)) for k, v in kwargs.iteritems(): data.__setattr__(k, v) @@ -98,8 +100,12 @@ class GenericApiHandler(RequestHandler): raise HTTPError(code, message) data.creation_date = datetime.now() - yield self._eval_db(self.table, 'insert', data.format()) - self.finish_request(self._create_response(name)) + _id = yield self._eval_db(self.table, 'insert', data.format()) + if 'name' in self.json_args: + resource = data.name + else: + resource = _id + self.finish_request(self._create_response(resource)) @asynchronous @gen.coroutine @@ -198,172 +204,6 @@ class VersionHandler(GenericApiHandler): self.finish_request([{'v1': 'basics'}]) -class TestResultsHandler(GenericApiHandler): - """ - TestResultsHandler Class - Handle the requests about the Test project's results - HTTP Methdods : - - GET : Get all test results and details about a specific one - - POST : Add a test results - - DELETE : Remove a test result - """ - - def initialize(self): - """ Prepares the database for the entire class """ - super(TestResultsHandler, self).initialize() - self.name = "test_result" - - @asynchronous - @gen.coroutine - def get(self, result_id=None): - """ - 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 - - - :param result_id: Get a result by ID - :raise HTTPError - - GET /results/project=functest&case=vPing&version=Arno-R1 \ - &pod=pod_name&period=15 - => get results with optional filters - """ - - # prepare request - query = dict() - if result_id is not None: - query["_id"] = result_id - answer = yield self.db.results.find_one(query) - if answer is None: - raise HTTPError(HTTP_NOT_FOUND, - "test result {} Not Exist".format(result_id)) - else: - answer = format_data(answer, TestResult) - else: - 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 - - res = [] - cursor = self.db.results.find(query) - while (yield cursor.fetch_next): - res.append(format_data(cursor.next_object(), TestResult)) - answer = {'results': res} - - self.finish_request(answer) - - @asynchronous - @gen.coroutine - def post(self): - """ - Create a new test result - :return: status of the request - :raise HTTPError - """ - - # check for request payload - if self.json_args is None: - raise HTTPError(HTTP_BAD_REQUEST, 'no payload') - - result = TestResult.from_dict(self.json_args) - - # check for pod_name instead of id, - # keeping id for current implementations - if result.pod_name is None: - raise HTTPError(HTTP_BAD_REQUEST, 'pod is not provided') - - # check for missing parameters in the request payload - if result.project_name is None: - raise HTTPError(HTTP_BAD_REQUEST, 'project is not provided') - - if result.case_name is None: - raise HTTPError(HTTP_BAD_REQUEST, 'testcase is not provided') - - # TODO : replace checks with jsonschema - # check for pod - the_pod = yield self.db.pods.find_one({"name": result.pod_name}) - if the_pod is None: - raise HTTPError(HTTP_NOT_FOUND, - "Could not find POD [{}] " - .format(self.json_args.get("pod_name"))) - - # check for project - the_project = yield self.db.projects.find_one( - {"name": result.project_name}) - if the_project is None: - raise HTTPError(HTTP_NOT_FOUND, "Could not find project [{}] " - .format(result.project_name)) - - # check for testcase - the_testcase = yield self.db.testcases.find_one( - {"name": result.case_name}) - if the_testcase is None: - raise HTTPError(HTTP_NOT_FOUND, - "Could not find testcase [{}] " - .format(result.case_name)) - - _id = yield self.db.results.insert(result.format(), check_keys=False) - - self.finish_request(self._create_response(_id)) - - class DashboardHandler(GenericApiHandler): """ DashboardHandler Class diff --git a/utils/test/result_collection_api/resources/pod_handlers.py b/utils/test/result_collection_api/resources/pod_handlers.py index c50ec51e3..9f583bbfc 100644 --- a/utils/test/result_collection_api/resources/pod_handlers.py +++ b/utils/test/result_collection_api/resources/pod_handlers.py @@ -31,7 +31,7 @@ class PodCLHandler(GenericPodHandler): @rtype: L{Pod} @return 200: pod is created. @raise 403: pod already exists - @raise 400: post without body + @raise 400: body or name not provided """ def query(data): return {'name': data.name} @@ -40,8 +40,9 @@ class PodCLHandler(GenericPodHandler): message = '{} already exists as a pod'.format(data.name) return HTTP_FORBIDDEN, message - db_check = [(self.table, False, query, error)] - self._create(db_check) + miss_checks = ['name'] + db_checks = [(self.table, False, query, error)] + self._create(miss_checks, db_checks) class PodGURHandler(GenericPodHandler): diff --git a/utils/test/result_collection_api/resources/project_handlers.py b/utils/test/result_collection_api/resources/project_handlers.py index e56c01c36..0bc1a61bd 100644 --- a/utils/test/result_collection_api/resources/project_handlers.py +++ b/utils/test/result_collection_api/resources/project_handlers.py @@ -33,7 +33,7 @@ class ProjectCLHandler(GenericProjectHandler): @rtype: L{Project} @return 200: project is created. @raise 403: project already exists - @raise 400: post without body + @raise 400: body or name not provided """ def query(data): return {'name': data.name} @@ -42,8 +42,9 @@ class ProjectCLHandler(GenericProjectHandler): message = '{} already exists as a project'.format(data.name) return HTTP_FORBIDDEN, message - db_check = [(self.table, False, query, error)] - self._create(db_check) + miss_checks = ['name'] + db_checks = [(self.table, False, query, error)] + self._create(miss_checks, db_checks) class ProjectGURHandler(GenericProjectHandler): diff --git a/utils/test/result_collection_api/resources/result_handlers.py b/utils/test/result_collection_api/resources/result_handlers.py new file mode 100644 index 000000000..d3fea1df7 --- /dev/null +++ b/utils/test/result_collection_api/resources/result_handlers.py @@ -0,0 +1,134 @@ +from datetime import datetime, timedelta + +from bson.objectid import ObjectId +from tornado.web import HTTPError + +from common.constants import HTTP_BAD_REQUEST, HTTP_NOT_FOUND +from resources.handlers import GenericApiHandler +from resources.result_models import TestResult +from tornado_swagger_ui.tornado_swagger import swagger + + +class GenericResultHandler(GenericApiHandler): + def __init__(self, application, request, **kwargs): + super(GenericResultHandler, self).__init__(application, + request, + **kwargs) + self.table = self.db_results + self.table_cls = TestResult + + +class ResultsCLHandler(GenericResultHandler): + @swagger.operation(nickname="list-all") + def get(self): + """ + @description: list all test results consist with query + @return 200: all test results consist with query, + empty list if no result is found + @rtype: L{TestResults} + """ + 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) + + @swagger.operation(nickname="create") + def post(self): + """ + @description: create a test result + @param body: result to be created + @type body: L{ResultCreateRequest} + @in body: body + @rtype: L{TestResult} + @return 200: result is created. + @raise 404: pod/project/testcase not exist + @raise 400: body/pod_name/project_name/case_name not provided + """ + def pod_query(data): + return {'name': data.pod_name} + + def pod_error(data): + message = 'Could not find pod [{}]'.format(data.pod_name) + return HTTP_NOT_FOUND, message + + def project_query(data): + return {'name': data.project_name} + + def project_error(data): + message = 'Could not find project [{}]'.format(data.project_name) + return HTTP_NOT_FOUND, message + + def testcase_query(data): + return {'project_name': data.project_name, 'name': data.case_name} + + def testcase_error(data): + message = 'Could not find testcase [{}] in project [{}]'\ + .format(data.case_name, data.project_name) + return HTTP_NOT_FOUND, message + + miss_checks = ['pod_name', 'project_name', 'case_name'] + db_checks = [('pods', True, pod_query, pod_error), + ('projects', True, project_query, project_error), + ('testcases', True, testcase_query, testcase_error)] + self._create(miss_checks, db_checks) + + +class ResultsGURHandler(GenericResultHandler): + @swagger.operation(nickname='get-one') + def get(self, result_id): + """ + @description: get a single result by result_id + @rtype: L{TestResult} + @return 200: test result exist + @raise 404: test result not exist + """ + query = dict() + query["_id"] = ObjectId(result_id) + self._get_one(query) diff --git a/utils/test/result_collection_api/resources/result_models.py b/utils/test/result_collection_api/resources/result_models.py index 15684e229..7faac16d4 100644 --- a/utils/test/result_collection_api/resources/result_models.py +++ b/utils/test/result_collection_api/resources/result_models.py @@ -1,4 +1,7 @@ +from tornado_swagger_ui.tornado_swagger import swagger + +@swagger.model() class ResultCreateRequest(object): def __init__(self, pod_name=None, @@ -43,9 +46,8 @@ class ResultCreateRequest(object): } -class TestResult: - """ Describes a test result""" - +@swagger.model() +class TestResult(object): def __init__(self): self._id = None self.case_name = None @@ -132,7 +134,11 @@ class TestResult: } +@swagger.model() class TestResults(object): + """ + @ptype testcases: C{list} of L{TestResult} + """ def __init__(self, results=list()): self.results = results diff --git a/utils/test/result_collection_api/resources/testcase_handlers.py b/utils/test/result_collection_api/resources/testcase_handlers.py index 9c0eb6363..8f3bea617 100644 --- a/utils/test/result_collection_api/resources/testcase_handlers.py +++ b/utils/test/result_collection_api/resources/testcase_handlers.py @@ -37,7 +37,7 @@ class TestcaseCLHandler(GenericTestcaseHandler): @return 200: testcase is created in this project. @raise 403: project not exist or testcase already exists in this project - @raise 400: post without body + @raise 400: body or name not provided """ def p_query(data): return {'name': data.project_name} @@ -57,9 +57,10 @@ class TestcaseCLHandler(GenericTestcaseHandler): .format(data.name, data.project_name) return HTTP_FORBIDDEN, message + miss_checks = ['name'] db_checks = [(self.db_projects, True, p_query, p_error), (self.db_testcases, False, tc_query, tc_error)] - self._create(db_checks, project_name=project_name) + self._create(miss_checks, db_checks, project_name=project_name) class TestcaseGURHandler(GenericTestcaseHandler): diff --git a/utils/test/result_collection_api/resources/testcase_models.py b/utils/test/result_collection_api/resources/testcase_models.py index f3867649f..90b3f75e9 100644 --- a/utils/test/result_collection_api/resources/testcase_models.py +++ b/utils/test/result_collection_api/resources/testcase_models.py @@ -35,8 +35,6 @@ class TestcaseUpdateRequest(object): @swagger.model() class Testcase(object): - """ Describes a test case""" - def __init__(self): self._id = None self.name = None diff --git a/utils/test/result_collection_api/result_collection_api.py b/utils/test/result_collection_api/result_collection_api.py index 25a670c32..652aa58af 100644 --- a/utils/test/result_collection_api/result_collection_api.py +++ b/utils/test/result_collection_api/result_collection_api.py @@ -34,11 +34,11 @@ import argparse import tornado.ioloop import motor -from resources.handlers import VersionHandler, \ - TestResultsHandler, DashboardHandler +from resources.handlers import VersionHandler, DashboardHandler 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 common.config import APIConfig from tornado_swagger_ui.tornado_swagger import swagger @@ -83,9 +83,8 @@ def make_app(): # POST /results => # Push results with mandatory request payload parameters # (project, case, and pod) - (r"/api/v1/results", TestResultsHandler), - (r"/api/v1/results([^/]*)", TestResultsHandler), - (r"/api/v1/results/([^/]*)", TestResultsHandler), + (r"/api/v1/results", ResultsCLHandler), + (r"/api/v1/results/([^/]+)", ResultsGURHandler), # Method to manage Dashboard ready results # GET /dashboard?project=functest&case=vPing&pod=opnfv-jump2 diff --git a/utils/test/result_collection_api/tests/unit/fake_pymongo.py b/utils/test/result_collection_api/tests/unit/fake_pymongo.py index 40eb164db..3494280fa 100644 --- a/utils/test/result_collection_api/tests/unit/fake_pymongo.py +++ b/utils/test/result_collection_api/tests/unit/fake_pymongo.py @@ -33,6 +33,8 @@ class MemDb(object): def _find_one(self, spec_or_id=None, *args): if spec_or_id is not None and not isinstance(spec_or_id, dict): spec_or_id = {"_id": spec_or_id} + if '_id' in spec_or_id: + spec_or_id['_id'] = str(spec_or_id['_id']) cursor = self._find(spec_or_id, *args) for result in cursor: return result diff --git a/utils/test/result_collection_api/tests/unit/test_base.py b/utils/test/result_collection_api/tests/unit/test_base.py index dfb2070c4..036c6cf6d 100644 --- a/utils/test/result_collection_api/tests/unit/test_base.py +++ b/utils/test/result_collection_api/tests/unit/test_base.py @@ -4,9 +4,9 @@ from tornado.testing import AsyncHTTPTestCase from resources.pod_handlers import PodCLHandler, PodGURHandler from resources.project_handlers import ProjectCLHandler, ProjectGURHandler -from resources.handlers import VersionHandler, \ - TestResultsHandler, DashboardHandler +from resources.handlers import VersionHandler, DashboardHandler from resources.testcase_handlers import TestcaseCLHandler, TestcaseGURHandler +from resources.result_handlers import ResultsCLHandler, ResultsGURHandler from resources.models import CreateResponse import fake_pymongo @@ -36,9 +36,8 @@ class TestBase(AsyncHTTPTestCase): (r"/api/v1/projects/([^/]+)/cases", TestcaseCLHandler), (r"/api/v1/projects/([^/]+)/cases/([^/]+)", TestcaseGURHandler), - (r"/api/v1/results", TestResultsHandler), - (r"/api/v1/results([^/]*)", TestResultsHandler), - (r"/api/v1/results/([^/]*)", TestResultsHandler), + (r"/api/v1/results", ResultsCLHandler), + (r"/api/v1/results/([^/]+)", ResultsGURHandler), (r"/dashboard/v1/results", DashboardHandler), (r"/dashboard/v1/results([^/]*)", DashboardHandler), ], diff --git a/utils/test/result_collection_api/tests/unit/test_pod.py b/utils/test/result_collection_api/tests/unit/test_pod.py index 8a9302738..1a43c0563 100644 --- a/utils/test/result_collection_api/tests/unit/test_pod.py +++ b/utils/test/result_collection_api/tests/unit/test_pod.py @@ -36,13 +36,13 @@ class TestPodCreate(TestPodBase): req_empty = PodCreateRequest('') (code, body) = self.create(req_empty) self.assertEqual(code, HTTP_BAD_REQUEST) - self.assertIn('pod name missing', body) + self.assertIn('name missing', body) def test_noneName(self): req_none = PodCreateRequest(None) (code, body) = self.create(req_none) self.assertEqual(code, HTTP_BAD_REQUEST) - self.assertIn('pod name missing', body) + self.assertIn('name missing', body) def test_success(self): code, body = self.create_d() diff --git a/utils/test/result_collection_api/tests/unit/test_project.py b/utils/test/result_collection_api/tests/unit/test_project.py index b07cb7ad6..4f5bd9d5e 100644 --- a/utils/test/result_collection_api/tests/unit/test_project.py +++ b/utils/test/result_collection_api/tests/unit/test_project.py @@ -34,13 +34,13 @@ class TestProjectCreate(TestProjectBase): req_empty = ProjectCreateRequest('') (code, body) = self.create(req_empty) self.assertEqual(code, HTTP_BAD_REQUEST) - self.assertIn('project name missing', body) + self.assertIn('name missing', body) def test_noneName(self): req_none = ProjectCreateRequest(None) (code, body) = self.create(req_none) self.assertEqual(code, HTTP_BAD_REQUEST) - self.assertIn('project name missing', body) + self.assertIn('name missing', body) def test_success(self): (code, body) = self.create_d() diff --git a/utils/test/result_collection_api/tests/unit/test_result.py b/utils/test/result_collection_api/tests/unit/test_result.py index 7dd07efa8..5757df014 100644 --- a/utils/test/result_collection_api/tests/unit/test_result.py +++ b/utils/test/result_collection_api/tests/unit/test_result.py @@ -105,35 +105,35 @@ class TestResultCreate(TestResultBase): def test_nobody(self): (code, body) = self.create(None) self.assertEqual(code, HTTP_BAD_REQUEST) - self.assertIn('no payload', body) + self.assertIn('no body', body) def test_podNotProvided(self): req = self.req_d req.pod_name = None (code, body) = self.create(req) self.assertEqual(code, HTTP_BAD_REQUEST) - self.assertIn('pod is not provided', body) + self.assertIn('pod_name missing', body) def test_projectNotProvided(self): req = self.req_d req.project_name = None (code, body) = self.create(req) self.assertEqual(code, HTTP_BAD_REQUEST) - self.assertIn('project is not provided', body) + self.assertIn('project_name missing', body) def test_testcaseNotProvided(self): req = self.req_d req.case_name = None (code, body) = self.create(req) self.assertEqual(code, HTTP_BAD_REQUEST) - self.assertIn('testcase is not provided', body) + self.assertIn('case_name missing', body) def test_noPod(self): req = self.req_d req.pod_name = 'notExistPod' (code, body) = self.create(req) self.assertEqual(code, HTTP_NOT_FOUND) - self.assertIn('Could not find POD', body) + self.assertIn('Could not find pod', body) def test_noProject(self): req = self.req_d diff --git a/utils/test/result_collection_api/tests/unit/test_testcase.py b/utils/test/result_collection_api/tests/unit/test_testcase.py index c6c060802..237147205 100644 --- a/utils/test/result_collection_api/tests/unit/test_testcase.py +++ b/utils/test/result_collection_api/tests/unit/test_testcase.py @@ -85,13 +85,13 @@ class TestCaseCreate(TestCaseBase): req_empty = TestcaseCreateRequest('') (code, body) = self.create(req_empty, self.project) self.assertEqual(code, HTTP_BAD_REQUEST) - self.assertIn('testcase name missing', body) + self.assertIn('name missing', body) def test_noneName(self): req_none = TestcaseCreateRequest(None) (code, body) = self.create(req_none, self.project) self.assertEqual(code, HTTP_BAD_REQUEST) - self.assertIn('testcase name missing', body) + self.assertIn('name missing', body) def test_success(self): code, body = self.create_d() -- cgit 1.2.3-korg