From 6ffbb81ec808aa615c7dad95fe6328ea5cc9c7b5 Mon Sep 17 00:00:00 2001 From: chenjiankun Date: Sat, 21 Apr 2018 08:53:03 +0000 Subject: Add testing gating reporting page Change-Id: I9f46d684a11b7999defffe983fc3224ef1a50412 Signed-off-by: chenjiankun --- reporting/api/conf.py | 4 ++ reporting/api/extension/__init__.py | 0 reporting/api/extension/client.py | 15 ++++ reporting/api/handlers/scenarios.py | 27 ++++++++ reporting/api/server.py | 8 ++- reporting/api/service/__init__.py | 0 reporting/api/service/result.py | 90 ++++++++++++++++++++++++ reporting/api/service/scenario.py | 133 ++++++++++++++++++++++++++++++++++++ reporting/api/urls.py | 5 +- 9 files changed, 280 insertions(+), 2 deletions(-) create mode 100644 reporting/api/extension/__init__.py create mode 100644 reporting/api/extension/client.py create mode 100644 reporting/api/handlers/scenarios.py create mode 100644 reporting/api/service/__init__.py create mode 100644 reporting/api/service/result.py create mode 100644 reporting/api/service/scenario.py (limited to 'reporting/api') diff --git a/reporting/api/conf.py b/reporting/api/conf.py index 5897d4f..e05c1fd 100644 --- a/reporting/api/conf.py +++ b/reporting/api/conf.py @@ -1 +1,5 @@ base_url = 'http://testresults.opnfv.org/test/api/v1' + +versions = ['master', 'euphrates', 'fraser'] + +period = 10 diff --git a/reporting/api/extension/__init__.py b/reporting/api/extension/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/reporting/api/extension/client.py b/reporting/api/extension/client.py new file mode 100644 index 0000000..03371fd --- /dev/null +++ b/reporting/api/extension/client.py @@ -0,0 +1,15 @@ +from tornado.simple_httpclient import SimpleAsyncHTTPClient +from tornado.log import gen_log + + +class NoQueueTimeoutHTTPClient(SimpleAsyncHTTPClient): + def fetch_impl(self, request, callback): + key = object() + + self.queue.append((key, request, callback)) + self.waiting[key] = (request, callback, None) + + self._process_queue() + + if self.queue: + gen_log.debug("max_clients limit reached, request queued.") diff --git a/reporting/api/handlers/scenarios.py b/reporting/api/handlers/scenarios.py new file mode 100644 index 0000000..70447c7 --- /dev/null +++ b/reporting/api/handlers/scenarios.py @@ -0,0 +1,27 @@ +from tornado.web import asynchronous +from tornado.gen import coroutine +from tornado.escape import json_encode + +from api.handlers import BaseHandler +from api.service.scenario import ScenarioTableResult + + +class Result(BaseHandler): + @asynchronous + @coroutine + def get(self): + self._set_header() + + scenario = self.get_argument('scenario', None) + version = self.get_argument('version', 'master') + installer = self.get_argument('installer', None) + iteration = int(self.get_argument('iteration', 10)) + + yield self._get_scenario_data(scenario, version, installer, iteration) + + @coroutine + def _get_scenario_data(self, scenario, version, installer, iteration): + results = ScenarioTableResult(scenario, version, installer, iteration) + result = yield results.get() + self.write(json_encode(result)) + self.finish() diff --git a/reporting/api/server.py b/reporting/api/server.py index e340b01..461e6d5 100644 --- a/reporting/api/server.py +++ b/reporting/api/server.py @@ -12,6 +12,7 @@ from tornado.options import define from tornado.options import options from api.urls import mappings +from api.service.result import ResultCache define("port", default=8000, help="run on the given port", type=int) @@ -20,7 +21,12 @@ def main(): tornado.options.parse_command_line() application = tornado.web.Application(mappings) application.listen(options.port) - tornado.ioloop.IOLoop.current().start() + + tornado.ioloop.PeriodicCallback(ResultCache.update, 1800000).start() + + ioloop = tornado.ioloop.IOLoop.current() + ioloop.call_later(1000, ResultCache.update()) + ioloop.start() if __name__ == "__main__": diff --git a/reporting/api/service/__init__.py b/reporting/api/service/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/reporting/api/service/result.py b/reporting/api/service/result.py new file mode 100644 index 0000000..fa6553c --- /dev/null +++ b/reporting/api/service/result.py @@ -0,0 +1,90 @@ +import json +import logging +from collections import defaultdict + +from tornado.gen import coroutine +from tornado.gen import Return +from tornado.httpclient import AsyncHTTPClient + +from api import conf as consts +from api.extension.client import NoQueueTimeoutHTTPClient + +LOG = logging.getLogger(__name__) +AsyncHTTPClient.configure(NoQueueTimeoutHTTPClient) + + +class Result(object): + + def __init__(self): + self._url = '{}/results?period={}&version={}&page={}' + self._client = AsyncHTTPClient() + self._result = defaultdict(list) + + @property + @coroutine + def result(self): + if not self._result: + yield self.update_results() + raise Return(self._result) + + @coroutine + def update_results(self): + LOG.info('start update results') + + for version in consts.versions: + yield self._update_version_result(version) + + LOG.info('results update finished') + + @coroutine + def _update_version_result(self, version): + total_page = yield self._get_total_page(version) + + responses = yield self._fetch_results(version, total_page) + + self._update_version_dict(version, responses) + + @coroutine + def _fetch_results(self, version, total_page): + urls = [self._url.format(consts.base_url, consts.period, version, i) + for i in range(1, total_page + 1)] + responses = yield [self._client.fetch(url) for url in urls] + raise Return(responses) + + @coroutine + def _get_total_page(self, version): + url = self._url.format(consts.base_url, consts.period, version, 1) + response = yield self._client.fetch(url) + raise Return(json.loads(response.body)['pagination']['total_pages']) + + def _update_version_dict(self, version, responses): + for response in responses: + results = json.loads(response.body)['results'] + for result in results: + data = {k: v for k, v in result.items() if k != 'details'} + self._result[version].append(data) + + +class ResultCache(Result): + + @classmethod + @coroutine + def update(cls): + cls._check() + + yield cls.cache.update_results() + + @classmethod + @coroutine + def get(cls, version): + cls._check() + + result = yield cls.cache.result + raise Return(result[version]) + + @classmethod + def _check(cls): + try: + cls.cache + except AttributeError: + cls.cache = cls() diff --git a/reporting/api/service/scenario.py b/reporting/api/service/scenario.py new file mode 100644 index 0000000..f9e2a91 --- /dev/null +++ b/reporting/api/service/scenario.py @@ -0,0 +1,133 @@ +import abc +from collections import defaultdict + +import six +from tornado.gen import coroutine +from tornado.gen import Return + +from api.service.result import ResultCache + +PROJECTS = ['fastdatastacks', 'barometer', 'sfc', 'sdnvpn', 'doctor', 'parser'] + + +def _set(key, llist): + return set(x[key] for x in llist) + + +def _filter(key, value, llist): + return filter(lambda x: x[key] == value, llist) + + +class ScenarioTableResult(object): + + def __init__(self, scenario, version, installer, iteration): + self.scenario = scenario + self.version = version + self.installer = installer + self.iteration = iteration + + @coroutine + def get(self): + results = yield ResultCache.get(self.version) + results = self._filter_result(results) + results = self._struct_result(results) + + raise Return(results) + + def _filter_result(self, results): + results = [x for x in results if x['build_tag']] + if self.installer: + results = _filter('installer', self.installer, results) + if self.scenario: + results = _filter('scenario', self.scenario, results) + return results + + def _struct_result(self, results): + + return { + s: self._struct_scenario(_filter('scenario', s, results)) + for s in _set('scenario', results) + } + + def _struct_scenario(self, data): + return sorted([ + HandlerFacade.get_result(b, _filter('build_tag', b, data)) + for b in _set('build_tag', data) + ], key=lambda x: x['date'], reverse=True)[:self.iteration] + + +class HandlerFacade(object): + + @classmethod + def get_result(cls, index, data): + if not data: + return {} + + cls._change_name_to_functest(data) + data = cls._sort_by_start_date(data) + + return { + 'id': index, + 'date': data[0]['start_date'], + 'version': data[0]['version'], + 'installer': data[0]['installer'], + 'projects': cls._get_projects_data(data) + } + + @classmethod + def _sort_by_start_date(cls, data): + return sorted(data, key=lambda x: x['start_date']) + + @classmethod + def _get_projects_data(cls, data): + + return { + p: HANDLER_MAP[p](_filter('project_name', p, data)).struct() + for p in _set('project_name', data) + } + + @classmethod + def _change_name_to_functest(cls, data): + for ele in data: + if ele['project_name'] in PROJECTS: + ele['project_name'] = 'functest' + + +@six.add_metaclass(abc.ABCMeta) +class ProjectHandler(object): + + def __init__(self, data): + self.data = data + + def struct(self): + return self._struct_project_data() + + def _struct_project_data(self): + return { + 'gating': self._gating() + } + + @abc.abstractmethod + def _gating(self): + pass + + +class DefaultHandler(ProjectHandler): + + def _struct_project_data(self): + return {} + + def _gating(self): + pass + + +class FunctestHandler(ProjectHandler): + + def _gating(self): + if all([x['criteria'] == 'PASS' for x in self.data]): + return 'PASS' + else: + return 'FAIL' + + +HANDLER_MAP = defaultdict(lambda: DefaultHandler, functest=FunctestHandler) diff --git a/reporting/api/urls.py b/reporting/api/urls.py index a5228b2..34f61d8 100644 --- a/reporting/api/urls.py +++ b/reporting/api/urls.py @@ -9,6 +9,7 @@ from api.handlers import landing from api.handlers import projects from api.handlers import testcases +from api.handlers import scenarios mappings = [ (r"/landing-page/filters", landing.FiltersHandler), @@ -16,5 +17,7 @@ mappings = [ (r"/projects-page/projects", projects.Projects), (r"/projects/([^/]+)/cases", testcases.TestCases), - (r"/projects/([^/]+)/cases/([^/]+)", testcases.TestCase) + (r"/projects/([^/]+)/cases/([^/]+)", testcases.TestCase), + + (r"/scenarios/results", scenarios.Result) ] -- cgit 1.2.3-korg