From 2cea85b0b4b18af73ed6afd7d837d443da43fd2b Mon Sep 17 00:00:00 2001 From: SerenaFeng Date: Tue, 18 Oct 2016 17:30:31 +0800 Subject: rename result_collection_api to testapi Change-Id: Iec4e3db23cd44f30831e17c127eda74e9d9b5d14 Signed-off-by: SerenaFeng --- testapi/opnfv_testapi/resources/__init__.py | 8 + testapi/opnfv_testapi/resources/handlers.py | 237 +++++++++++++++++++++ testapi/opnfv_testapi/resources/models.py | 71 ++++++ testapi/opnfv_testapi/resources/pod_handlers.py | 87 ++++++++ testapi/opnfv_testapi/resources/pod_models.py | 85 ++++++++ .../opnfv_testapi/resources/project_handlers.py | 92 ++++++++ testapi/opnfv_testapi/resources/project_models.py | 94 ++++++++ testapi/opnfv_testapi/resources/result_handlers.py | 198 +++++++++++++++++ testapi/opnfv_testapi/resources/result_models.py | 231 ++++++++++++++++++++ .../opnfv_testapi/resources/testcase_handlers.py | 115 ++++++++++ testapi/opnfv_testapi/resources/testcase_models.py | 105 +++++++++ 11 files changed, 1323 insertions(+) create mode 100644 testapi/opnfv_testapi/resources/__init__.py create mode 100644 testapi/opnfv_testapi/resources/handlers.py create mode 100644 testapi/opnfv_testapi/resources/models.py create mode 100644 testapi/opnfv_testapi/resources/pod_handlers.py create mode 100644 testapi/opnfv_testapi/resources/pod_models.py create mode 100644 testapi/opnfv_testapi/resources/project_handlers.py create mode 100644 testapi/opnfv_testapi/resources/project_models.py create mode 100644 testapi/opnfv_testapi/resources/result_handlers.py create mode 100644 testapi/opnfv_testapi/resources/result_models.py create mode 100644 testapi/opnfv_testapi/resources/testcase_handlers.py create mode 100644 testapi/opnfv_testapi/resources/testcase_models.py (limited to 'testapi/opnfv_testapi/resources') diff --git a/testapi/opnfv_testapi/resources/__init__.py b/testapi/opnfv_testapi/resources/__init__.py new file mode 100644 index 0000000..05c0c93 --- /dev/null +++ b/testapi/opnfv_testapi/resources/__init__.py @@ -0,0 +1,8 @@ +############################################################################## +# Copyright (c) 2015 Orange +# guyrodrigue.koffi@orange.com / koffirodrigue@gmail.com +# All rights reserved. This program and the accompanying materials +# 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 +############################################################################## diff --git a/testapi/opnfv_testapi/resources/handlers.py b/testapi/opnfv_testapi/resources/handlers.py new file mode 100644 index 0000000..5059f5d --- /dev/null +++ b/testapi/opnfv_testapi/resources/handlers.py @@ -0,0 +1,237 @@ +############################################################################## +# Copyright (c) 2015 Orange +# guyrodrigue.koffi@orange.com / koffirodrigue@gmail.com +# All rights reserved. This program and the accompanying materials +# 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 refactor db.pod to db.pods 5-19-2016 +# feng.xiaowei@zte.com.cn refactor test_project to project 5-19-2016 +# feng.xiaowei@zte.com.cn refactor response body 5-19-2016 +# feng.xiaowei@zte.com.cn refactor pod/project response info 5-19-2016 +# feng.xiaowei@zte.com.cn refactor testcase related handler 5-20-2016 +# feng.xiaowei@zte.com.cn refactor result related handler 5-23-2016 +# 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 + +from tornado import gen +from tornado.web import RequestHandler, asynchronous, HTTPError + +from models import CreateResponse +from opnfv_testapi.common.constants import DEFAULT_REPRESENTATION, \ + HTTP_BAD_REQUEST, HTTP_NOT_FOUND, HTTP_FORBIDDEN +from opnfv_testapi.tornado_swagger import swagger + + +class GenericApiHandler(RequestHandler): + def __init__(self, application, request, **kwargs): + super(GenericApiHandler, self).__init__(application, request, **kwargs) + self.db = self.settings["db"] + self.json_args = None + self.table = None + self.table_cls = None + self.db_projects = 'projects' + self.db_pods = 'pods' + self.db_testcases = 'testcases' + self.db_results = 'results' + + def prepare(self): + if self.request.method != "GET" and self.request.method != "DELETE": + if self.request.headers.get("Content-Type") is not None: + if self.request.headers["Content-Type"].startswith( + DEFAULT_REPRESENTATION): + try: + self.json_args = json.loads(self.request.body) + except (ValueError, KeyError, TypeError) as error: + raise HTTPError(HTTP_BAD_REQUEST, + "Bad Json format [{}]". + format(error)) + + def finish_request(self, json_object=None): + if json_object: + self.write(json.dumps(json_object)) + self.set_header("Content-Type", DEFAULT_REPRESENTATION) + self.finish() + + def _create_response(self, resource): + 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)] + """ + if self.json_args is None: + raise HTTPError(HTTP_BAD_REQUEST, "no body") + + data = self.table_cls.from_dict(self.json_args) + 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) + + for table, exist, query, error in db_checks: + 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) + + if self.table != 'results': + data.creation_date = datetime.now() + _id = yield self._eval_db(self.table, 'insert', data.format(), + check_keys=False) + if 'name' in self.json_args: + resource = data.name + else: + resource = _id + self.finish_request(self._create_response(resource)) + + @asynchronous + @gen.coroutine + def _list(self, query=None, res_op=None, *args, **kwargs): + if query is None: + query = {} + data = [] + cursor = self._eval_db(self.table, 'find', query) + if 'sort' in kwargs: + cursor = cursor.sort(kwargs.get('sort')) + if 'last' in kwargs: + cursor = cursor.limit(kwargs.get('last')) + while (yield cursor.fetch_next): + 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_find_one(query) + if data is None: + raise HTTPError(HTTP_NOT_FOUND, + "[{}] not exist in table [{}]" + .format(query, self.table)) + self.finish_request(self.format_data(data)) + + @asynchronous + @gen.coroutine + def _delete(self, query): + data = yield self._eval_db_find_one(query) + if data is None: + raise HTTPError(HTTP_NOT_FOUND, + "[{}] not exit in table [{}]" + .format(query, self.table)) + + yield self._eval_db(self.table, 'remove', query) + self.finish_request() + + @asynchronous + @gen.coroutine + def _update(self, query, db_keys): + if self.json_args is None: + raise HTTPError(HTTP_BAD_REQUEST, "No payload") + + # check old data exist + 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 [{}]" + .format(query, self.table)) + + data = self.table_cls.from_dict(from_data) + # check new data exist + equal, new_query = self._update_query(db_keys, data) + if not equal: + to_data = yield self._eval_db_find_one(new_query) + if to_data is not None: + raise HTTPError(HTTP_FORBIDDEN, + "{} already exists in table [{}]" + .format(new_query, self.table)) + + # we merge the whole document """ + edit_request = data.format() + edit_request.update(self._update_requests(data)) + + """ Updating the DB """ + yield self._eval_db(self.table, 'update', query, edit_request, + check_keys=False) + edit_request['_id'] = str(data._id) + self.finish_request(edit_request) + + def _update_requests(self, data): + request = dict() + for k, v in self.json_args.iteritems(): + 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 new_value != old_value: + edit_request[key] = new_value + + return edit_request + + def _update_query(self, keys, data): + query = dict() + equal = True + for key in keys: + new = self.json_args.get(key) + old = data.__getattribute__(key) + if new is None: + new = old + elif new != old: + equal = False + query[key] = new + return equal, query + + def _eval_db(self, table, method, *args, **kwargs): + exec_collection = self.db.__getattr__(table) + return exec_collection.__getattribute__(method)(*args, **kwargs) + + 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 VersionHandler(GenericApiHandler): + @swagger.operation(nickname='list') + def get(self): + """ + @description: list all supported versions + @rtype: L{Versions} + """ + versions = [{'version': 'v1.0', 'description': 'basics'}] + self.finish_request({'versions': versions}) diff --git a/testapi/opnfv_testapi/resources/models.py b/testapi/opnfv_testapi/resources/models.py new file mode 100644 index 0000000..e79308b --- /dev/null +++ b/testapi/opnfv_testapi/resources/models.py @@ -0,0 +1,71 @@ +############################################################################## +# Copyright (c) 2015 Orange +# guyrodrigue.koffi@orange.com / koffirodrigue@gmail.com +# All rights reserved. This program and the accompanying materials +# 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 mv Pod to pod_models.py 5-18-2016 +# feng.xiaowei@zte.com.cn add MetaCreateResponse/MetaGetResponse 5-18-2016 +# feng.xiaowei@zte.com.cn mv TestProject to project_models.py 5-19-2016 +# feng.xiaowei@zte.com.cn delete meta class 5-19-2016 +# feng.xiaowei@zte.com.cn add CreateResponse 5-19-2016 +# 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 opnfv_testapi.tornado_swagger import swagger + + +@swagger.model() +class CreateResponse(object): + def __init__(self, href=''): + self.href = href + + @staticmethod + def from_dict(res_dict): + if res_dict is None: + return None + + res = CreateResponse() + res.href = res_dict.get('href') + return res + + def format(self): + return {'href': self.href} + + +@swagger.model() +class Versions(object): + """ + @property versions: + @ptype versions: C{list} of L{Version} + """ + def __init__(self): + self.versions = list() + + @staticmethod + def from_dict(res_dict): + if res_dict is None: + return None + + res = Versions() + for version in res_dict.get('versions'): + res.versions.append(Version.from_dict(version)) + return res + + +@swagger.model() +class Version(object): + def __init__(self, version=None, description=None): + self.version = version + self.description = description + + @staticmethod + def from_dict(a_dict): + if a_dict is None: + return None + + ver = Version() + ver.version = a_dict.get('version') + ver.description = str(a_dict.get('description')) + return ver diff --git a/testapi/opnfv_testapi/resources/pod_handlers.py b/testapi/opnfv_testapi/resources/pod_handlers.py new file mode 100644 index 0000000..8f44439 --- /dev/null +++ b/testapi/opnfv_testapi/resources/pod_handlers.py @@ -0,0 +1,87 @@ +############################################################################## +# Copyright (c) 2015 Orange +# guyrodrigue.koffi@orange.com / koffirodrigue@gmail.com +# All rights reserved. This program and the accompanying materials +# 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 +############################################################################## +from opnfv_testapi.tornado_swagger import swagger +from handlers import GenericApiHandler +from pod_models import Pod +from opnfv_testapi.common.constants import HTTP_FORBIDDEN + + +class GenericPodHandler(GenericApiHandler): + def __init__(self, application, request, **kwargs): + super(GenericPodHandler, self).__init__(application, request, **kwargs) + self.table = 'pods' + self.table_cls = Pod + + +class PodCLHandler(GenericPodHandler): + @swagger.operation(nickname='list-all') + def get(self): + """ + @description: list all pods + @return 200: list all pods, empty list is no pod exist + @rtype: L{Pods} + """ + self._list() + + @swagger.operation(nickname='create') + def post(self): + """ + @description: create a pod + @param body: pod to be created + @type body: L{PodCreateRequest} + @in body: body + @rtype: L{CreateResponse} + @return 200: pod is created. + @raise 403: pod already exists + @raise 400: body or name not provided + """ + def query(data): + return {'name': data.name} + + def error(data): + message = '{} already exists as a pod'.format(data.name) + return HTTP_FORBIDDEN, message + + miss_checks = ['name'] + db_checks = [(self.table, False, query, error)] + self._create(miss_checks, db_checks) + + +class PodGURHandler(GenericPodHandler): + @swagger.operation(nickname='get-one') + def get(self, pod_name): + """ + @description: get a single pod by pod_name + @rtype: L{Pod} + @return 200: pod exist + @raise 404: pod not exist + """ + query = dict() + query['name'] = pod_name + self._get_one(query) + + def delete(self, pod_name): + """ Remove a POD + + # check for an existing pod to be deleted + mongo_dict = yield self.db.pods.find_one( + {'name': pod_name}) + pod = TestProject.pod(mongo_dict) + if pod is None: + raise HTTPError(HTTP_NOT_FOUND, + "{} could not be found as a pod to be deleted" + .format(pod_name)) + + # just delete it, or maybe save it elsewhere in a future + res = yield self.db.projects.remove( + {'name': pod_name}) + + self.finish_request(answer) + """ + pass diff --git a/testapi/opnfv_testapi/resources/pod_models.py b/testapi/opnfv_testapi/resources/pod_models.py new file mode 100644 index 0000000..7231806 --- /dev/null +++ b/testapi/opnfv_testapi/resources/pod_models.py @@ -0,0 +1,85 @@ +############################################################################## +# Copyright (c) 2015 Orange +# guyrodrigue.koffi@orange.com / koffirodrigue@gmail.com +# All rights reserved. This program and the accompanying materials +# 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 +############################################################################## +from opnfv_testapi.tornado_swagger import swagger + +# name: name of the POD e.g. zte-1 +# mode: metal or virtual +# details: any detail +# role: ci-pod or community-pod or single-node + + +@swagger.model() +class PodCreateRequest(object): + def __init__(self, name, mode='', details='', role=""): + self.name = name + self.mode = mode + self.details = details + self.role = role + + def format(self): + return { + "name": self.name, + "mode": self.mode, + "details": self.details, + "role": self.role, + } + + +@swagger.model() +class Pod(PodCreateRequest): + def __init__(self, + name='', mode='', details='', + role="", _id='', create_date=''): + super(Pod, self).__init__(name, mode, details, role) + self._id = _id + self.creation_date = create_date + + @staticmethod + def from_dict(pod_dict): + if pod_dict is None: + return None + + p = Pod() + p._id = pod_dict.get('_id') + p.creation_date = str(pod_dict.get('creation_date')) + p.name = pod_dict.get('name') + p.mode = pod_dict.get('mode') + p.details = pod_dict.get('details') + p.role = pod_dict.get('role') + return p + + def format(self): + f = super(Pod, self).format() + f['creation_date'] = str(self.creation_date) + return f + + def format_http(self): + f = self.format() + f['_id'] = str(self._id) + return f + + +@swagger.model() +class Pods(object): + """ + @property pods: + @ptype pods: C{list} of L{Pod} + """ + def __init__(self): + self.pods = list() + + @staticmethod + def from_dict(res_dict): + if res_dict is None: + return None + + res = Pods() + for pod in res_dict.get('pods'): + res.pods.append(Pod.from_dict(pod)) + return res diff --git a/testapi/opnfv_testapi/resources/project_handlers.py b/testapi/opnfv_testapi/resources/project_handlers.py new file mode 100644 index 0000000..1e9a972 --- /dev/null +++ b/testapi/opnfv_testapi/resources/project_handlers.py @@ -0,0 +1,92 @@ +############################################################################## +# Copyright (c) 2015 Orange +# guyrodrigue.koffi@orange.com / koffirodrigue@gmail.com +# All rights reserved. This program and the accompanying materials +# 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 +############################################################################## +from opnfv_testapi.tornado_swagger import swagger +from handlers import GenericApiHandler +from opnfv_testapi.common.constants import HTTP_FORBIDDEN +from project_models import Project + + +class GenericProjectHandler(GenericApiHandler): + def __init__(self, application, request, **kwargs): + super(GenericProjectHandler, self).__init__(application, + request, + **kwargs) + self.table = 'projects' + self.table_cls = Project + + +class ProjectCLHandler(GenericProjectHandler): + @swagger.operation(nickname="list-all") + def get(self): + """ + @description: list all projects + @return 200: return all projects, empty list is no project exist + @rtype: L{Projects} + """ + self._list() + + @swagger.operation(nickname="create") + def post(self): + """ + @description: create a project + @param body: project to be created + @type body: L{ProjectCreateRequest} + @in body: body + @rtype: L{CreateResponse} + @return 200: project is created. + @raise 403: project already exists + @raise 400: body or name not provided + """ + def query(data): + return {'name': data.name} + + def error(data): + message = '{} already exists as a project'.format(data.name) + return HTTP_FORBIDDEN, message + + miss_checks = ['name'] + db_checks = [(self.table, False, query, error)] + self._create(miss_checks, db_checks) + + +class ProjectGURHandler(GenericProjectHandler): + @swagger.operation(nickname='get-one') + def get(self, project_name): + """ + @description: get a single project by project_name + @rtype: L{Project} + @return 200: project exist + @raise 404: project not exist + """ + self._get_one({'name': project_name}) + + @swagger.operation(nickname="update") + def put(self, project_name): + """ + @description: update a single project by project_name + @param body: project to be updated + @type body: L{ProjectUpdateRequest} + @in body: body + @rtype: L{Project} + @return 200: update success + @raise 404: project not exist + @raise 403: new project name already exist or nothing to update + """ + query = {'name': project_name} + db_keys = ['name'] + self._update(query, db_keys) + + @swagger.operation(nickname='delete') + def delete(self, project_name): + """ + @description: delete a project by project_name + @return 200: delete success + @raise 404: project not exist + """ + self._delete({'name': project_name}) diff --git a/testapi/opnfv_testapi/resources/project_models.py b/testapi/opnfv_testapi/resources/project_models.py new file mode 100644 index 0000000..f70630c --- /dev/null +++ b/testapi/opnfv_testapi/resources/project_models.py @@ -0,0 +1,94 @@ +############################################################################## +# Copyright (c) 2015 Orange +# guyrodrigue.koffi@orange.com / koffirodrigue@gmail.com +# All rights reserved. This program and the accompanying materials +# 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 +############################################################################## +from opnfv_testapi.tornado_swagger import swagger + + +@swagger.model() +class ProjectCreateRequest(object): + def __init__(self, name, description=''): + self.name = name + self.description = description + + def format(self): + return { + "name": self.name, + "description": self.description, + } + + +@swagger.model() +class ProjectUpdateRequest(object): + def __init__(self, name='', description=''): + self.name = name + self.description = description + + def format(self): + return { + "name": self.name, + "description": self.description, + } + + +@swagger.model() +class Project(object): + def __init__(self, + name=None, _id=None, description=None, create_date=None): + self._id = _id + self.name = name + self.description = description + self.creation_date = create_date + + @staticmethod + def from_dict(res_dict): + + if res_dict is None: + return None + + t = Project() + t._id = res_dict.get('_id') + t.creation_date = res_dict.get('creation_date') + t.name = res_dict.get('name') + t.description = res_dict.get('description') + + return t + + def format(self): + return { + "name": self.name, + "description": self.description, + "creation_date": str(self.creation_date) + } + + def format_http(self): + return { + "_id": str(self._id), + "name": self.name, + "description": self.description, + "creation_date": str(self.creation_date), + } + + +@swagger.model() +class Projects(object): + """ + @property projects: + @ptype projects: C{list} of L{Project} + """ + def __init__(self): + self.projects = list() + + @staticmethod + def from_dict(res_dict): + if res_dict is None: + return None + + res = Projects() + for project in res_dict.get('projects'): + res.projects.append(Project.from_dict(project)) + return res diff --git a/testapi/opnfv_testapi/resources/result_handlers.py b/testapi/opnfv_testapi/resources/result_handlers.py new file mode 100644 index 0000000..400b84a --- /dev/null +++ b/testapi/opnfv_testapi/resources/result_handlers.py @@ -0,0 +1,198 @@ +############################################################################## +# Copyright (c) 2015 Orange +# guyrodrigue.koffi@orange.com / koffirodrigue@gmail.com +# All rights reserved. This program and the accompanying materials +# 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 +############################################################################## +from datetime import datetime, timedelta + +from bson.objectid import ObjectId +from tornado.web import HTTPError + +from opnfv_testapi.common.constants import HTTP_BAD_REQUEST, HTTP_NOT_FOUND +from opnfv_testapi.resources.handlers import GenericApiHandler +from opnfv_testapi.resources.result_models import TestResult +from opnfv_testapi.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 + + def get_int(self, key, value): + try: + value = int(value) + except: + raise HTTPError(HTTP_BAD_REQUEST, '{} must be int'.format(key)) + return value + + 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': + v = self.get_int(k, v) + if v > 0: + period = datetime.now() - timedelta(days=v) + obj = {"$gte": str(period)} + query['start_date'] = obj + elif k == 'trust_indicator': + query[k + '.current'] = float(v) + elif k != 'last': + query[k] = v + return query + + +class ResultsCLHandler(GenericResultHandler): + @swagger.operation(nickname="list-all") + def get(self): + """ + @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: False + @param case: case name + @type case: L{string} + @in case: query + @required case: False + @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 last: last records stored until now + @type last: L{string} + @in last: query + @required last: False + @param trust_indicator: must be float + @type trust_indicator: L{float} + @in trust_indicator: query + @required trust_indicator: False + """ + last = self.get_query_argument('last', 0) + if last is not None: + last = self.get_int('last', last) + + self._list(self.set_query(), sort=[('start_date', -1)], last=last) + + @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{CreateResponse} + @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) + + @swagger.operation(nickname="update") + def put(self, result_id): + """ + @description: update a single result by _id + @param body: fields to be updated + @type body: L{ResultUpdateRequest} + @in body: body + @rtype: L{Result} + @return 200: update success + @raise 404: result not exist + @raise 403: nothing to update + """ + query = {'_id': ObjectId(result_id)} + db_keys = [] + self._update(query, db_keys) diff --git a/testapi/opnfv_testapi/resources/result_models.py b/testapi/opnfv_testapi/resources/result_models.py new file mode 100644 index 0000000..f73f5c6 --- /dev/null +++ b/testapi/opnfv_testapi/resources/result_models.py @@ -0,0 +1,231 @@ +############################################################################## +# Copyright (c) 2015 Orange +# guyrodrigue.koffi@orange.com / koffirodrigue@gmail.com +# All rights reserved. This program and the accompanying materials +# 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 +############################################################################## +from opnfv_testapi.tornado_swagger import swagger + + +@swagger.model() +class TIHistory(object): + """ + @ptype step: L{float} + """ + def __init__(self, date=None, step=0): + self.date = date + self.step = step + + def format(self): + return { + "date": self.date, + "step": self.step + } + + @staticmethod + def from_dict(a_dict): + if a_dict is None: + return None + + return TIHistory(a_dict.get('date'), a_dict.get('step')) + + +@swagger.model() +class TI(object): + """ + @property histories: trust_indicator update histories + @ptype histories: C{list} of L{TIHistory} + @ptype current: L{float} + """ + def __init__(self, current=0): + self.current = current + self.histories = list() + + def format(self): + hs = [] + for h in self.histories: + hs.append(h.format()) + + return { + "current": self.current, + "histories": hs + } + + @staticmethod + def from_dict(a_dict): + t = TI() + if a_dict: + t.current = a_dict.get('current') + if 'histories' in a_dict.keys(): + for history in a_dict.get('histories', None): + t.histories.append(TIHistory.from_dict(history)) + else: + t.histories = [] + return t + + +@swagger.model() +class ResultCreateRequest(object): + """ + @property trust_indicator: + @ptype trust_indicator: L{TI} + """ + def __init__(self, + pod_name=None, + project_name=None, + case_name=None, + installer=None, + version=None, + start_date=None, + stop_date=None, + details=None, + build_tag=None, + scenario=None, + criteria=None, + trust_indicator=None): + self.pod_name = pod_name + self.project_name = project_name + self.case_name = case_name + self.installer = installer + self.version = version + self.start_date = start_date + self.stop_date = stop_date + self.details = details + self.build_tag = build_tag + self.scenario = scenario + self.criteria = criteria + self.trust_indicator = trust_indicator if trust_indicator else TI(0) + + def format(self): + return { + "pod_name": self.pod_name, + "project_name": self.project_name, + "case_name": self.case_name, + "installer": self.installer, + "version": self.version, + "start_date": self.start_date, + "stop_date": self.stop_date, + "details": self.details, + "build_tag": self.build_tag, + "scenario": self.scenario, + "criteria": self.criteria, + "trust_indicator": self.trust_indicator.format() + } + + +@swagger.model() +class ResultUpdateRequest(object): + """ + @property trust_indicator: + @ptype trust_indicator: L{TI} + """ + def __init__(self, trust_indicator=None): + self.trust_indicator = trust_indicator + + def format(self): + return { + "trust_indicator": self.trust_indicator.format(), + } + + +@swagger.model() +class TestResult(object): + """ + @property trust_indicator: used for long duration test case + @ptype trust_indicator: L{TI} + """ + def __init__(self, _id=None, case_name=None, project_name=None, + pod_name=None, installer=None, version=None, + start_date=None, stop_date=None, details=None, + build_tag=None, scenario=None, criteria=None, + trust_indicator=None): + self._id = _id + self.case_name = case_name + self.project_name = project_name + self.pod_name = pod_name + self.installer = installer + self.version = version + self.start_date = start_date + self.stop_date = stop_date + self.details = details + self.build_tag = build_tag + self.scenario = scenario + self.criteria = criteria + self.trust_indicator = trust_indicator + + @staticmethod + def from_dict(a_dict): + + if a_dict is None: + return None + + t = TestResult() + t._id = a_dict.get('_id') + t.case_name = a_dict.get('case_name') + t.pod_name = a_dict.get('pod_name') + t.project_name = a_dict.get('project_name') + t.start_date = str(a_dict.get('start_date')) + t.stop_date = str(a_dict.get('stop_date')) + t.details = a_dict.get('details') + t.version = a_dict.get('version') + t.installer = a_dict.get('installer') + t.build_tag = a_dict.get('build_tag') + t.scenario = a_dict.get('scenario') + t.criteria = a_dict.get('criteria') + t.trust_indicator = TI.from_dict(a_dict.get('trust_indicator')) + return t + + def format(self): + return { + "case_name": self.case_name, + "project_name": self.project_name, + "pod_name": self.pod_name, + "start_date": str(self.start_date), + "stop_date": str(self.stop_date), + "version": self.version, + "installer": self.installer, + "details": self.details, + "build_tag": self.build_tag, + "scenario": self.scenario, + "criteria": self.criteria, + "trust_indicator": self.trust_indicator.format() + } + + def format_http(self): + return { + "_id": str(self._id), + "case_name": self.case_name, + "project_name": self.project_name, + "pod_name": self.pod_name, + "start_date": str(self.start_date), + "stop_date": str(self.stop_date), + "version": self.version, + "installer": self.installer, + "details": self.details, + "build_tag": self.build_tag, + "scenario": self.scenario, + "criteria": self.criteria, + "trust_indicator": self.trust_indicator.format() + } + + +@swagger.model() +class TestResults(object): + """ + @property results: + @ptype results: C{list} of L{TestResult} + """ + def __init__(self): + self.results = list() + + @staticmethod + def from_dict(a_dict): + if a_dict is None: + return None + + res = TestResults() + for result in a_dict.get('results'): + res.results.append(TestResult.from_dict(result)) + return res diff --git a/testapi/opnfv_testapi/resources/testcase_handlers.py b/testapi/opnfv_testapi/resources/testcase_handlers.py new file mode 100644 index 0000000..253aa66 --- /dev/null +++ b/testapi/opnfv_testapi/resources/testcase_handlers.py @@ -0,0 +1,115 @@ +############################################################################## +# Copyright (c) 2015 Orange +# guyrodrigue.koffi@orange.com / koffirodrigue@gmail.com +# All rights reserved. This program and the accompanying materials +# 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 +############################################################################## +from opnfv_testapi.common.constants import HTTP_FORBIDDEN +from opnfv_testapi.resources.handlers import GenericApiHandler +from opnfv_testapi.resources.testcase_models import Testcase +from opnfv_testapi.tornado_swagger import swagger + + +class GenericTestcaseHandler(GenericApiHandler): + def __init__(self, application, request, **kwargs): + super(GenericTestcaseHandler, self).__init__(application, + request, + **kwargs) + self.table = self.db_testcases + self.table_cls = Testcase + + +class TestcaseCLHandler(GenericTestcaseHandler): + @swagger.operation(nickname="list-all") + def get(self, project_name): + """ + @description: list all testcases of a project by project_name + @return 200: return all testcases of this project, + empty list is no testcase exist in this project + @rtype: L{TestCases} + """ + query = dict() + query['project_name'] = project_name + self._list(query) + + @swagger.operation(nickname="create") + def post(self, project_name): + """ + @description: create a testcase of a project by project_name + @param body: testcase to be created + @type body: L{TestcaseCreateRequest} + @in body: body + @rtype: L{CreateResponse} + @return 200: testcase is created in this project. + @raise 403: project not exist + or testcase already exists in this project + @raise 400: body or name not provided + """ + def p_query(data): + return {'name': data.project_name} + + def tc_query(data): + return { + 'project_name': data.project_name, + 'name': data.name + } + + def p_error(data): + message = 'Could not find project [{}]'.format(data.project_name) + return HTTP_FORBIDDEN, message + + def tc_error(data): + message = '{} already exists as a testcase in project {}'\ + .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(miss_checks, db_checks, project_name=project_name) + + +class TestcaseGURHandler(GenericTestcaseHandler): + @swagger.operation(nickname='get-one') + def get(self, project_name, case_name): + """ + @description: get a single testcase + by case_name and project_name + @rtype: L{Testcase} + @return 200: testcase exist + @raise 404: testcase not exist + """ + query = dict() + query['project_name'] = project_name + query["name"] = case_name + self._get_one(query) + + @swagger.operation(nickname="update") + def put(self, project_name, case_name): + """ + @description: update a single testcase + by project_name and case_name + @param body: testcase to be updated + @type body: L{TestcaseUpdateRequest} + @in body: body + @rtype: L{Project} + @return 200: update success + @raise 404: testcase or project not exist + @raise 403: new testcase name already exist in project + or nothing to update + """ + query = {'project_name': project_name, 'name': case_name} + db_keys = ['name', 'project_name'] + self._update(query, db_keys) + + @swagger.operation(nickname='delete') + def delete(self, project_name, case_name): + """ + @description: delete a testcase by project_name and case_name + @return 200: delete success + @raise 404: testcase not exist + """ + query = {'project_name': project_name, 'name': case_name} + self._delete(query) diff --git a/testapi/opnfv_testapi/resources/testcase_models.py b/testapi/opnfv_testapi/resources/testcase_models.py new file mode 100644 index 0000000..c9dce60 --- /dev/null +++ b/testapi/opnfv_testapi/resources/testcase_models.py @@ -0,0 +1,105 @@ +############################################################################## +# Copyright (c) 2015 Orange +# guyrodrigue.koffi@orange.com / koffirodrigue@gmail.com +# All rights reserved. This program and the accompanying materials +# 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 +############################################################################## +from opnfv_testapi.tornado_swagger import swagger + + +@swagger.model() +class TestcaseCreateRequest(object): + def __init__(self, name, url=None, description=None): + self.name = name + self.url = url + self.description = description + + def format(self): + return { + "name": self.name, + "description": self.description, + "url": self.url, + } + + +@swagger.model() +class TestcaseUpdateRequest(object): + def __init__(self, name=None, description=None, project_name=None): + self.name = name + self.description = description + self.project_name = project_name + + def format(self): + return { + "name": self.name, + "description": self.description, + "project_name": self.project_name, + } + + +@swagger.model() +class Testcase(object): + def __init__(self): + self._id = None + self.name = None + self.project_name = None + self.description = None + self.url = None + self.creation_date = None + + @staticmethod + def from_dict(a_dict): + + if a_dict is None: + return None + + t = Testcase() + t._id = a_dict.get('_id') + t.project_name = a_dict.get('project_name') + t.creation_date = a_dict.get('creation_date') + t.name = a_dict.get('name') + t.description = a_dict.get('description') + t.url = a_dict.get('url') + + return t + + def format(self): + return { + "name": self.name, + "description": self.description, + "project_name": self.project_name, + "creation_date": str(self.creation_date), + "url": self.url + } + + def format_http(self): + return { + "_id": str(self._id), + "name": self.name, + "project_name": self.project_name, + "description": self.description, + "creation_date": str(self.creation_date), + "url": self.url, + } + + +@swagger.model() +class Testcases(object): + """ + @property testcases: + @ptype testcases: C{list} of L{Testcase} + """ + def __init__(self): + self.testcases = list() + + @staticmethod + def from_dict(res_dict): + if res_dict is None: + return None + + res = Testcases() + for testcase in res_dict.get('testcases'): + res.testcases.append(Testcase.from_dict(testcase)) + return res -- cgit 1.2.3-korg