diff options
34 files changed, 852 insertions, 195 deletions
diff --git a/reporting/3rd_party/html/gambia.html b/reporting/3rd_party/html/gambia.html new file mode 100644 index 0000000..d5f6fcd --- /dev/null +++ b/reporting/3rd_party/html/gambia.html @@ -0,0 +1,141 @@ +<!DOCTYPE HTML> +<!-- + Phantom by HTML5 UP + html5up.net | @ajlkn + Free for personal and commercial use under the CCA 3.0 license (html5up.net/license) +--> +<html> + <head> + <title>Phantom by HTML5 UP</title> + <meta charset="utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1" /> + <!--[if lte IE 8]><script src="3rd_party/js/ie/html5shiv.js"></script><![endif]--> + <link rel="stylesheet" href="3rd_party/css/main.css" /> + <!--[if lte IE 9]><link rel="stylesheet" href="3rd_party/css/ie9.css" /><![endif]--> + <!--[if lte IE 8]><link rel="stylesheet" href="3rd_party/css/ie8.css" /><![endif]--> + </head> + <body> + <!-- Wrapper --> + <div id="wrapper"> + + <!-- Header --> + <header id="header"> + <div class="inner"> + + <!-- Logo --> + <a href="index.html" class="logo"> + <span class="symbol"><img src="img/logo.svg" alt="" /></span><span class="title">Phantom</span> + </a> + + </div> + </header> + + <!-- Menu --> + <!-- Main --> + <div id="main"> + <div class="inner"> + <header> + <h1>Gambia reporting</h1> + </header> + <section class="tiles"> + <article class="style3"> + <span class="image"> + <img src="img/projectIcon_functest_250x250.png" alt="" /> + </span> + <a href="gambia/functest/functest.html"> + <h2>Functest</h2> + <div class="content"> + <p>Functional testing</p> + </div> + </a> + </article> + <article class="style2"> + <span class="image"> + <img src="img/projectIcon_yardstick_250x250.png" alt="" /> + </span> + <a href="gambia/yardstick/status-apex.html"> + <h2>Yardstick</h2> + <div class="content"> + <p>Qualification and performance testing</p> + </div> + </a> + </article> + <article class="style4"> + <span class="image"> + <img src="img/projectIcon_storperf_250x250.png" alt="" /> + </span> + <a href="gambia/storperf/status-apex.html"> + <h2>Storperf</h2> + <div class="content"> + <p>Storage testing</p> + </div> + </a> + </article> + <article class="style5"> + <span class="image"> + <img src="img/projectIcon_vsperf_250x250.png" alt="" /> + </span> + <a href="gambia/vsperf/reporting.html"> + <h2>Vsperf</h2> + <div class="content"> + <p>Virtual switch testing</p> + </div> + </a> + </article> + <article class="style1"> + <span class="image"> + <img src="img/projectIcon_qtip_250x250.png" alt="" /> + </span> + <a href="gambia/qtip/status-apex.html"> + <h2>Qtip</h2> + <div class="content"> + <p>Benchmark as a service</p> + </div> + </a> + </article> + <article class="style6"> + <span class="image"> + <img src="img/projectIcon_bottlenecks_250x250.png" alt="" /> + </span> + <a href="gambia/bottlenecks/status-apex.html"> + <h2>Bottlenecks</h2> + <div class="content"> + <p>Bottleneck finder</p> + </div> + </a> + </article> + </section> + </div> + </div> + + <!-- Footer --> + <footer id="footer"> + <div class="inner"> + <section> + <h2>OPNFV Testing Working group</h2> + </section> + <section> + <h2>Follow</h2> + <ul class="icons"> + <li><a href="https://twitter.com/opnfv" class="icon style2 fa-twitter"><span class="label">Twitter</span></a></li> + <li><a href="http://git.opnfv.org" class="icon style2 fa-github"><span class="label">GitHub</span></a></li> + <li><a href="mailto:test-wg@list.opnfv.org" class="icon style2 fa-envelope-o"><span class="label">Email</span></a></li> + </ul> + </section> + <ul class="copyright"> + <li>© Untitled. All rights reserved</li><li>Design: <a href="http://html5up.net">HTML5 UP</a></li> + </ul> + </div> + </footer> + + </div> + + <!-- Scripts --> + <script src="3rd_party/js/jquery.min.js"></script> + <script src="3rd_party/js/skel.min.js"></script> + <script src="3rd_party/js/util.js"></script> + <!--[if lte IE 8]><script src="3rd_party/js/ie/respond.min.js"></script><![endif]--> + <script src="3rd_party/js/main.js"></script> + + </body> +</html> diff --git a/reporting/3rd_party/html/index.html b/reporting/3rd_party/html/index.html index 858ac47..9165250 100644 --- a/reporting/3rd_party/html/index.html +++ b/reporting/3rd_party/html/index.html @@ -45,23 +45,23 @@ <section class="tiles"> <article class="style4"> <span class="image"> - <img src="img/euphrates.jpg" alt="" /> + <img src="img/fraser.jpg" alt="" /> </span> - <a href="euphrates.html"> - <h2>Euphrates</h2> + <a href="fraser.html"> + <h2>Fraser</h2> <div class="content"> - <p>Euphrates (ETA 10/2017)</p> + <p>Fraser (ETA 04/2018)</p> </div> </a> </article> <article class="style6"> <span class="image"> - <img src="img/fraser.jpg" alt="" /> + <img src="img/gambia.jpg" alt="" /> </span> - <a href="fraser.html"> - <h2>Fraser</h2> + <a href="gambia.html"> + <h2>Gambia</h2> <div class="content"> - <p>Fraser (ETA 04/2018)</p> + <p>Gambia (ETA 11/2018)</p> </div> </a> </article> diff --git a/reporting/api/conf.py b/reporting/api/conf.py index 5897d4f..d0023e0 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', 'fraser', 'gambia'] + +period = 10 diff --git a/reporting/api/extension/__init__.py b/reporting/api/extension/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/reporting/api/extension/__init__.py 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 --- /dev/null +++ b/reporting/api/service/__init__.py 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) ] diff --git a/reporting/css/default.css b/reporting/css/default.css index e32fa5f..686e886 100644 --- a/reporting/css/default.css +++ b/reporting/css/default.css @@ -26,6 +26,7 @@ width: 100%; background-color: #0095a2 } + .panel-default > .panel-heading h4 { color: white; } @@ -55,7 +56,7 @@ td{ background-color: #0095a2; } -h1 { +h1 { display: block; font-size: 2em; margin-top: 0.67em; @@ -191,4 +192,3 @@ h2 { position:relative; top:1px; } - diff --git a/reporting/docker/reporting.sh b/reporting/docker/reporting.sh index 8ca7eac..f3f3d50 100755 --- a/reporting/docker/reporting.sh +++ b/reporting/docker/reporting.sh @@ -3,7 +3,7 @@ export PYTHONPATH="${PYTHONPATH}:./reporting" export CONFIG_REPORTING_YAML=./reporting/reporting.yaml -declare -a versions=(fraser euphrates master) +declare -a versions=(gambia fraser master) declare -a projects=(functest storperf yardstick qtip vsperf bottlenecks) project=$1 @@ -25,11 +25,6 @@ cp -Rf 3rd_party/html/* display cp -Rf img display cp -Rf js display -for i in "${versions[@]}" -do - cp -Rf html/functest.html display/$i/functest -done - # if nothing is precised run all the reporting generation # projet | option # $1 | $2 diff --git a/reporting/img/gambia.jpg b/reporting/img/gambia.jpg Binary files differnew file mode 100644 index 0000000..fd91d11 --- /dev/null +++ b/reporting/img/gambia.jpg diff --git a/reporting/pages/app/index.html b/reporting/pages/app/index.html index 843a623..6ef1d02 100644 --- a/reporting/pages/app/index.html +++ b/reporting/pages/app/index.html @@ -87,6 +87,7 @@ <script src="scripts/controllers/auth.controller.js"></script> <script src="scripts/controllers/admin.controller.js"></script> <script src="scripts/controllers/main.controller.js"></script> + <script src="scripts/controllers/gating.controller.js"></script> <script src="scripts/controllers/testvisual.controller.js"></script> <!-- endbuild --> diff --git a/reporting/pages/app/scripts/config.router.js b/reporting/pages/app/scripts/config.router.js index d3724b7..45a6997 100644 --- a/reporting/pages/app/scripts/config.router.js +++ b/reporting/pages/app/scripts/config.router.js @@ -17,11 +17,11 @@ angular.module('opnfvApp') ]).config(['$stateProvider', '$urlRouterProvider', function($stateProvider, $urlRouterProvider) { - $urlRouterProvider.otherwise('/landingpage/table'); + $urlRouterProvider.otherwise('/home/gating'); $stateProvider - .state('landingpage', { - url: "/landingpage", + .state('home', { + url: "/home", controller: 'MainController', templateUrl: "views/main.html", data: { pageTitle: '首页', specialClass: 'landing-page' }, @@ -33,7 +33,7 @@ angular.module('opnfvApp') }] } }) - .state('landingpage.table', { + .state('home.table', { url: "/table", controller: 'TableController', templateUrl: "views/commons/table.html", @@ -47,6 +47,19 @@ angular.module('opnfvApp') }] } }) + .state('home.gating', { + url: "/gating", + controller: 'GatingController', + templateUrl: "views/gating.html", + data: { pageTitle: 'OPNFV Release Gating Page', specialClass: 'landing-page' }, + resolve: { + controller: ['$ocLazyLoad', function($ocLazyLoad) { + return $ocLazyLoad.load([ + + ]) + }] + } + }) .state('select', { url: '/select', templateUrl: "views/testcase.html", diff --git a/reporting/pages/app/scripts/controllers/gating.controller.js b/reporting/pages/app/scripts/controllers/gating.controller.js new file mode 100644 index 0000000..52b381d --- /dev/null +++ b/reporting/pages/app/scripts/controllers/gating.controller.js @@ -0,0 +1,119 @@ +'use strict'; + +angular.module('opnfvApp') + .controller('GatingController', ['$scope', '$state', '$stateParams', 'TableFactory', function($scope, $state, $stateParams, TableFactory) { + + init(); + + function init() { + $scope.goTest = goTest; + $scope.goLogin = goLogin; + + $scope.scenarios = {}; + + $scope.scenarioList = _toSelectList(['all']); + $scope.versionList = _toSelectList(['master', 'fraser', 'gambia']); + $scope.installerList = _toSelectList(['all', 'apex', 'compass', 'daisy', 'fuel', 'joid']); + $scope.iterationList = _toSelectList([10, 20, 30]); + + $scope.selectScenario = 'all'; + $scope.selectVersion = 'master'; + $scope.selectInstaller = 'all'; + $scope.selectIteration = 10; + + $scope.ScenarioConfig = { + create: true, + valueField: 'title', + labelField: 'title', + delimiter: '|', + maxItems: 1, + placeholder: 'Scenario', + onChange: function(value) { + $scope.selectScenario = value; + } + } + + $scope.VersionConfig = { + create: true, + valueField: 'title', + labelField: 'title', + delimiter: '|', + maxItems: 1, + placeholder: 'Version', + onChange: function(value) { + $scope.selectVersion = value; + getScenarioResult(); + } + } + + $scope.InstallerConfig = { + create: true, + valueField: 'title', + labelField: 'title', + delimiter: '|', + maxItems: 1, + placeholder: 'Installer', + onChange: function(value) { + $scope.selectInstaller = value; + getScenarioResult(); + } + } + + $scope.IterationConfig = { + create: true, + valueField: 'title', + labelField: 'title', + delimiter: '|', + maxItems: 1, + placeholder: 'Iteration', + onChange: function(value) { + $scope.selectIteration = value; + getScenarioResult(); + } + } + getScenarioResult(); + } + + function getScenarioResult(){ + _getScenarioResult($scope.selectVersion, $scope.selectInstaller, $scope.selectIteration); + } + + function _getScenarioResult(version, installer, iteration){ + var data = { + 'version': version, + 'iteration': iteration + } + + if(installer != 'all'){ + data['installer'] = installer; + } + + TableFactory.getScenarioResult().get(data).$promise.then(function(resp){ + $scope.scenarios = resp; + _concat($scope.scenarioList, _toSelectList(Object.keys(resp))); + }, function(err){ + }); + } + + function _concat(aList, bList){ + angular.forEach(bList, function(ele){ + aList.push(ele); + }); + } + + function _toSelectList(arr){ + var tempList = []; + angular.forEach(arr, function(ele){ + tempList.push({'title': ele}); + }); + return tempList; + } + + function goTest() { + $state.go("select.selectTestCase"); + } + + function goLogin() { + $state.go("login"); + } + }]); diff --git a/reporting/pages/app/scripts/factory/table.factory.js b/reporting/pages/app/scripts/factory/table.factory.js index e715c5c..a2c5c76 100644 --- a/reporting/pages/app/scripts/factory/table.factory.js +++ b/reporting/pages/app/scripts/factory/table.factory.js @@ -6,7 +6,7 @@ angular.module('opnfvApp') .factory('TableFactory', function($resource, $rootScope, $http) { - var BASE_URL = 'http://testresults.opnfv.org/reporting2'; + var BASE_URL = ' http://testresults.opnfv.org/testing'; $.ajax({ url: 'config.json', async: false, @@ -15,11 +15,19 @@ angular.module('opnfvApp') BASE_URL = response.url; }, error: function (response){ - alert('fail to get api url, using default: http://testresults.opnfv.org/reporting2') + alert('fail to get api url, using default: http://testresults.opnfv.org/testing') } }); return { + getScenarioResult: function() { + return $resource(BASE_URL + '/scenarios/results', {'scenario': '@scenario', 'version': '@version', 'installer': '@installer', 'iteration': '@iteration'}, { + 'get': { + method: 'GET', + + } + }); + }, getFilter: function() { return $resource(BASE_URL + '/landing-page/filters', {}, { 'get': { diff --git a/reporting/pages/app/views/gating.html b/reporting/pages/app/views/gating.html new file mode 100644 index 0000000..0215c87 --- /dev/null +++ b/reporting/pages/app/views/gating.html @@ -0,0 +1,68 @@ +<section class="container-tablesize"> + <div class="row border-bottom white-bg dashboard-header"> + <div class="row"> + + <div class="ibox float-e-margins"> + <div class="ibox-title"> + <h5>OPNFV Release Gating Reporting </h5> + </div> + + <div class="ibox-content row"> + <div class="col-md-2" style="margin-top:5px;margin-right: 5px;"> + <selectize options="scenarioList" ng-model="selectScenario" config="ScenarioConfig"></selectize> + </div> + <div class="col-md-2" style="margin-top:5px;margin-right: 5px;"> + <selectize options="versionList" ng-model="selectVersion" config="VersionConfig"></selectize> + </div> + + <div class="col-md-2" style="margin-top:5px;margin-right: 5px;"> + <selectize options="installerList" ng-model="selectInstaller" config="InstallerConfig"></selectize> + + </div> + + <div class="col-md-2" style="margin-top:5px;margin-right: 5px;"> + <selectize options="iterationList" ng-model="selectIteration" config="IterationConfig"></selectize> + </div> + </div> + + <div class="table-responsive"> + <table class="table table-bordered" id="table"> + <thead class="thead"> + <tr> + <th>Scenario</th> + <th>Date</th> + <th>ID</th> + <th>Version</th> + <th>Installer</th> + <th>Deployment</th> + <th>Functest</th> + <th>Yardstick</th> + </tr> + </thead> + <tbody class="tbody" ng-show="selectScenario == 'all' || scenario == selectScenario" ng-repeat="(scenario,value) in scenarios"> + <tr ng-repeat="record in value"> + <td ng-if="$index == 0">{{ scenario }}</td> + <td ng-if="$index != 0"></td> + <td>{{ record.date }}</td> + <td>{{ record.id }}</td> + <td>{{ record.version }}</td> + <td>{{ record.installer }}</td> + <td>{{ record.projects.deployment.gating }}</td> + <td>{{ record.projects.functest.gating }}</td> + <td>{{ record.projects.yardstick.gating }}</td> + </tr> + </tbody> + </table> + + </div> + + </div> + </div> + </div> + +</section> +<style> +.selectize-input { + width: 200px !important; +} +</style> diff --git a/reporting/reporting/functest/reporting-status.py b/reporting/reporting/functest/reporting-status.py index 552080d..e36aede 100755 --- a/reporting/reporting/functest/reporting-status.py +++ b/reporting/reporting/functest/reporting-status.py @@ -25,8 +25,6 @@ Functest reporting status LOGGER = rp_utils.getLogger("Functest-Status") # Initialization -testValid = [] -otherTestCases = [] reportingDate = datetime.datetime.now().strftime("%Y-%m-%d %H:%M") # init just connection_check to get the list of scenarios @@ -43,8 +41,7 @@ blacklist = rp_utils.get_config('functest.blacklist') log_level = rp_utils.get_config('general.log.log_level') exclude_noha = rp_utils.get_config('functest.exclude_noha') exclude_virtual = rp_utils.get_config('functest.exclude_virtual') - -functest_yaml_config = rp_utils.getFunctestConfig() +tiers_for_scoring = {'healthcheck', 'smoke', 'vnf', 'features'} LOGGER.info("*******************************************") LOGGER.info("* *") @@ -57,38 +54,36 @@ LOGGER.info("* NOHA scenarios excluded: %s *", exclude_noha) LOGGER.info("* *") LOGGER.info("*******************************************") -# Retrieve test cases of Tier 1 (smoke) -config_tiers = functest_yaml_config.get("tiers") +# For all the versions +for version in versions: + testValid = [] + # Retrieve test cases of Tier 1 (smoke) + version_config = "" + if (version != "master" and version != "latest"): + version_config = "?h=stable/" + version + functest_yaml_config = rp_utils.getFunctestConfig(version_config) + config_tiers = functest_yaml_config.get("tiers") + + # we consider Tier 0 (Healthcheck), Tier 1 (smoke),2 (features) + # to validate scenarios + # Tier > 2 are not used to validate scenarios but we display + # the results anyway + # tricky thing for the API as some tests are Functest tests + # other tests are declared directly in the feature projects + for tier in config_tiers: -# we consider Tier 0 (Healthcheck), Tier 1 (smoke),2 (features) -# to validate scenarios -# Tier > 2 are not used to validate scenarios but we display the results anyway -# tricky thing for the API as some tests are Functest tests -# other tests are declared directly in the feature projects -for tier in config_tiers: - if tier['order'] >= 0 and tier['order'] < 2: for case in tier['testcases']: + try: + dependencies = case['dependencies'] + except KeyError: + dependencies = "" if case['case_name'] not in blacklist: testValid.append(tc.TestCase(case['case_name'], "functest", - case['dependencies'])) - elif tier['order'] == 2: - for case in tier['testcases']: - if case['case_name'] not in blacklist: - otherTestCases.append(tc.TestCase(case['case_name'], - case['case_name'], - case['dependencies'])) - elif tier['order'] > 2: - for case in tier['testcases']: - if case['case_name'] not in blacklist: - otherTestCases.append(tc.TestCase(case['case_name'], - "functest", - case['dependencies'])) - -LOGGER.debug("Functest reporting start") + dependencies, + tier=tier['name'])) + LOGGER.debug("Functest reporting start") -# For all the versions -for version in versions: # For all the installers scenario_directory = "./display/" + version + "/functest/" scenario_file_name = scenario_directory + "scenario_history.txt" @@ -155,10 +150,8 @@ for version in versions: # Check if test case is runnable / installer, scenario # for the test case used for Scenario validation try: - # 1) Manage the test cases for the scenario validation - # concretely Tiers 0-3 for test_case in testValid: - test_case.checkRunnable(installer, s, + test_case.checkRunnable(installer, s, architecture, test_case.getConstraints()) LOGGER.debug("testcase %s (%s) is %s", test_case.getDisplayName(), @@ -169,7 +162,8 @@ for version in versions: name = test_case.getName() displayName = test_case.getDisplayName() project = test_case.getProject() - nb_test_runnable_for_this_scenario += 1 + if test_case.getTier() in tiers_for_scoring: + nb_test_runnable_for_this_scenario += 1 LOGGER.info(" Searching results for case %s ", displayName) if "fuel" in installer: @@ -185,53 +179,18 @@ for version in versions: LOGGER.info(" >>>> Test score = " + str(result)) test_case.setCriteria(result) test_case.setIsRunnable(True) - testCases2BeDisplayed.append(tc.TestCase(name, - project, - "", - result, - True, - 1)) - scenario_score = scenario_score + result - - # 2) Manage the test cases for the scenario qualification - # concretely Tiers > 3 - for test_case in otherTestCases: - test_case.checkRunnable(installer, s, - test_case.getConstraints()) - LOGGER.debug("testcase %s (%s) is %s", - test_case.getDisplayName(), - test_case.getName(), - test_case.isRunnable) - time.sleep(1) - if test_case.isRunnable: - name = test_case.getName() - displayName = test_case.getDisplayName() - project = test_case.getProject() - LOGGER.info(" Searching results for case %s ", - displayName) - if "fuel" in installer: - result = rp_utils.getCaseScoreFromBuildTag( - name, - s_result) - else: - result = rp_utils.getCaseScore(name, installer, - s, version) - # at least 1 result for the test - if result > -1: - test_case.setCriteria(result) - test_case.setIsRunnable(True) - testCases2BeDisplayed.append(tc.TestCase( - name, - project, - "", - result, - True, - 4)) - else: - LOGGER.debug("No results found") + testCases2BeDisplayed.append( + tc.TestCase(name, + project, + "", + result, + True, + tier=test_case.getTier())) + if test_case.getTier() in tiers_for_scoring: + scenario_score = scenario_score + result items[s] = testCases2BeDisplayed - except Exception: # pylint: disable=broad-except + except KeyError: # pylint: disable=broad-except LOGGER.error("Error installer %s, version %s, scenario %s", installer, version, s) LOGGER.error("No data available: %s", sys.exc_info()[0]) @@ -259,6 +218,12 @@ for version in versions: else: k_score = 2 + # TODO for the scoring we should consider 3 tiers + # - Healthcheck + # - Smoke + # - Vnf + # components + scenario_criteria = nb_test_runnable_for_this_scenario*k_score # score for reporting diff --git a/reporting/reporting/functest/template/index-status-tmpl.html b/reporting/reporting/functest/template/index-status-tmpl.html index 50fc648..48b5a2d 100644 --- a/reporting/reporting/functest/template/index-status-tmpl.html +++ b/reporting/reporting/functest/template/index-status-tmpl.html @@ -144,33 +144,32 @@ $(document).ready(function (){ <span class="panel-header-item"> </span> </div> + {% for tier in ['healthcheck', 'smoke', 'vnf', 'features'] -%} <table class="table"> <tr> + <h2>{{tier}}</h2> {% for test in items[scenario] -%} - <th> - {% if test.getCriteria() > -1 -%} - {{test.getDisplayName() }} + {% if test.getCriteria() > -1 and test.getTier() == tier -%} + <th>{{test.getDisplayName() }}</th> {%- endif %} - {% if test.getTier() > 3 -%} - * - {%- endif %} - </th> - {%- endfor %} + {%- endfor %} </tr> <tr class="tr-weather-weather"> - {% for test in items[scenario] -%} - {% if test.getCriteria() > 2 -%} + {% for test in items[scenario] -%} + {% if test.getCriteria() > 2 and test.getTier() == tier -%} <td><img src="../../img/weather-clear.png"></td> - {%- elif test.getCriteria() > 1 -%} + {%- elif test.getCriteria() > 1 and test.getTier() == tier -%} <td><img src="../../img/weather-few-clouds.png"></td> - {%- elif test.getCriteria() > 0 -%} + {%- elif test.getCriteria() > 0 and test.getTier() == tier -%} <td><img src="../../img/weather-overcast.png"></td> - {%- elif test.getCriteria() > -1 -%} + {%- elif test.getCriteria() > -1 and test.getTier() == tier -%} <td><img src="../../img/weather-storm.png"></td> {%- endif %} {%- endfor %} </tr> </table> + <br><hr> + {%- endfor %} </div> </div> {%- endfor %} diff --git a/reporting/reporting/functest/testCase.py b/reporting/reporting/functest/testCase.py index d114f8a..fba3216 100644 --- a/reporting/reporting/functest/testCase.py +++ b/reporting/reporting/functest/testCase.py @@ -26,7 +26,9 @@ class TestCase(object): 'onos': 'ONOS', 'ocl': 'OCL', 'tempest_smoke_serial': 'Tempest (smoke)', + 'tempest_smoke': 'Tempest (smoke)', 'tempest_full_parallel': 'Tempest (full)', + 'tempest_full': 'Tempest (full)', 'tempest_defcore': 'Tempest (Defcore)', 'refstack_defcore': 'Refstack', 'rally_sanity': 'Rally (smoke)', @@ -43,13 +45,14 @@ class TestCase(object): 'functest-odl-sfc': 'SFC', 'onos_sfc': 'SFC', 'parser-basics': 'Parser', - 'connection_check': 'Health (connection)', - 'api_check': 'Health (api)', + 'connection_check': 'connectivity', + 'api_check': 'api', 'snaps_smoke': 'SNAPS', - 'snaps_health_check': 'Health (dhcp)', + 'snaps_health_check': 'dhcp', 'gluon_vping': 'Netready', 'fds': 'FDS', 'cloudify_ims': 'vIMS (Cloudify)', + 'cloudify': 'Cloudify', 'orchestra_openims': 'OpenIMS (OpenBaton)', 'orchestra_clearwaterims': 'vIMS (OpenBaton)', 'opera_ims': 'vIMS (Open-O)', @@ -58,8 +61,29 @@ class TestCase(object): 'odl_netvirt': 'Netvirt', 'security_scan': 'Security', 'patrole': 'Patrole', + 'tenantnetwork1': 'tenant network 1', + 'tenantnetwork2': 'tenant network 2', + 'vmready1': 'vm ready 1', + 'vmready2': 'vm ready 2', + 'singlevm1': 'single vm 1', + 'singlevm2': 'single vm 2', + 'cinder_test': 'cinder tests', + 'barbican': 'barbican', + 'vmtp': 'vmtp', 'juju_epc': 'vEPC (Juju)', - 'neutron_trunk': 'Neutron trunk'} + 'shaker': 'shaker', + 'neutron_trunk': 'Neutron trunk', + 'tempest_scenario': 'tempest_scenario', + 'networking-bgpvpn': 'networking-bgpvpn', + 'networking-sfc': 'networking-sfc', + 'tempest_full': 'Tempest (full)', + 'cloudify': 'cloudify', + 'heat_ims': 'vIMS (Heat)', + 'vmtp': 'vmtp', + 'tempest_smoke': 'Tempest (smoke)', + 'neutron-tempest-plugin-api': 'Neutron API', + 'vgpu': 'vgpu', + 'stor4nfv_os': 'stor4nfv_os'} try: self.displayName = display_name_matrix[self.name] except: @@ -71,22 +95,22 @@ class TestCase(object): def getProject(self): return self.project - def getConstraints(self): - return self.constraints - def getCriteria(self): return self.criteria def getTier(self): return self.tier + def getConstraints(self): + return self.constraints + def setCriteria(self, criteria): self.criteria = criteria def setIsRunnable(self, isRunnable): self.isRunnable = isRunnable - def checkRunnable(self, installer, scenario, config): + def checkRunnable(self, installer, scenario, arch, config): # Re-use Functest declaration # Retrieve Functest configuration file functest_config.yaml is_runnable = True @@ -101,27 +125,36 @@ class TestCase(object): # Retrieve test constraints # Retrieve test execution param - test_execution_context = {"installer": installer, - "scenario": scenario} + test_execution_context = {"INSTALLER_TYPE": installer, + "DEPLOY_SCENARIO": scenario, + "POD_ARCH": arch} + + # 3 types of constraints + # INSTALLER_TYPE + # DEPLOY_SCENARIO + # POD_ARCH # By default we assume that all the tests are always runnable... # if test_env not empty => dependencies to be checked - if config_test is not None and len(config_test) > 0: - # possible criteria = ["installer", "scenario"] - # consider test criteria from config file - # compare towards CI env through CI en variable - for criteria in config_test: - if re.search(config_test[criteria], - test_execution_context[criteria]) is None: - # print "Test "+ test + " cannot be run on the environment" - is_runnable = False + try: + if config_test is not None and len(config_test) > 0: + # possible criteria = ["installer", "scenario"] + # consider test criteria from config file + # compare towards CI env through CI en variable + for criterias in config_test: + for criteria_key, criteria_value in criterias.iteritems(): + if re.search( + criteria_value, + test_execution_context[criteria_key]) is None: + is_runnable = False + except AttributeError: + is_runnable = False # print is_runnable self.isRunnable = is_runnable def toString(self): testcase = ("Name=" + self.name + ";Criteria=" + str(self.criteria) + ";Project=" + self.project + - ";Constraints=" + str(self.constraints) + ";IsRunnable" + str(self.isRunnable)) return testcase diff --git a/reporting/reporting/reporting.yaml b/reporting/reporting/reporting.yaml index b2f7d07..6a59a44 100644 --- a/reporting/reporting/reporting.yaml +++ b/reporting/reporting/reporting.yaml @@ -9,7 +9,7 @@ general: versions: - master - - fraser + - gambia log: log_file: reporting.log @@ -36,9 +36,11 @@ testapi: functest: blacklist: - odl_netvirt - - juju_epc - tempest_full_parallel + - tempest_full - rally_full + - heat_ims + - tempest_scenario max_scenario_criteria: 50 test_conf: https://git.opnfv.org/cgit/functest/plain/functest/ci/testcases.yaml log_level: ERROR diff --git a/reporting/reporting/vsperf/reporting-status.py b/reporting/reporting/vsperf/reporting-status.py index f1a437f..377c315 100644 --- a/reporting/reporting/vsperf/reporting-status.py +++ b/reporting/reporting/vsperf/reporting-status.py @@ -127,7 +127,7 @@ def main(): period=50) version_data = _get_version_data(data['vsperf']) - for version in {'master', 'danube', 'euphrates', 'fraser'}: + for version in {'master', 'fraser', 'gambia'}: _generate_reporting(version, version_data.get(version, [])) LOG.info("End") diff --git a/testapi/docker/Dockerfile b/testapi/docker/Dockerfile index 86999d9..03b15e8 100644 --- a/testapi/docker/Dockerfile +++ b/testapi/docker/Dockerfile @@ -23,30 +23,31 @@ # http://www.apache.org/licenses/LICENSE-2.0 # -FROM ubuntu:14.04 +FROM ubuntu:18.04 MAINTAINER SerenaFeng <feng.xiaowei@zte.com.cn> LABEL version="v1" description="OPNFV TestAPI Docker container" -ENV HOME /home +ARG user=ubuntu +ARG group=ubuntu -# Packaged dependencies RUN apt-get update && apt-get install -y \ -curl \ -git \ -gcc \ -wget \ -python-dev \ -python-pip \ -crudini \ ---no-install-recommends - -RUN pip install --upgrade requests - -RUN git config --global http.sslVerify false -RUN git clone https://gerrit.opnfv.org/gerrit/releng-testresults /home/releng-testresults - -WORKDIR /home/releng-testresults/testapi -RUN pip install -r requirements.txt - -RUN python setup.py install + curl git gcc wget python-dev python-pip python-wheel python-setuptools \ + crudini libxslt-dev zlib1g-dev --no-install-recommends && \ + groupadd -r $group && useradd -ms /bin/bash $user -g $group && \ + git clone https://gerrit.opnfv.org/gerrit/releng-testresults \ + /home/ubuntu/releng-testresults && \ + pip install -r /home/ubuntu/releng-testresults/testapi/requirements.txt \ + -c /home/ubuntu/releng-testresults/testapi/upper-constraints.txt \ + -c https://raw.githubusercontent.com/openstack/requirements/stable/ussuri/upper-constraints.txt && \ + sed -i '152,152s/)/,\ verify=False)/g' \ + /usr/local/lib/python2.7/dist-packages/cas.py && \ + cd /home/ubuntu/releng-testresults/testapi/ && python setup.py install && \ + for i in /home/ubuntu/releng-testresults /etc/opnfv_testapi /usr/local/share/opnfv_testapi; do \ + mkdir -p $i && chown -R $user:$group $i && \ + find $i -type d |xargs chmod 777 && \ + find $i -type f |xargs chmod 666 ; done && \ + apt-get remove --purge -y python-dev libxslt-dev zlib1g-dev && \ + apt-get autoremove --purge -y && apt-get clean && rm -rf /var/lib/apt/lists/* +WORKDIR /home/ubuntu/releng-testresults/testapi +USER ubuntu CMD ["bash", "docker/start-server.sh"] diff --git a/testapi/docker/prepare-env.sh b/testapi/docker/prepare-env.sh index 9086e77..3b061d2 100755 --- a/testapi/docker/prepare-env.sh +++ b/testapi/docker/prepare-env.sh @@ -2,18 +2,20 @@ FILE=/etc/opnfv_testapi/config.ini -if [ "$mongodb_url" != "" ]; then - sudo crudini --set --existing $FILE mongo url $mongodb_url -fi +[[ "${mongodb_url}" == "" ]] && mongodb_url=mongodb://mongo:27017/ +[[ "${base_url}" == "" ]] && base_url=http://localhost:8000 +[[ ! "${auth}" =~ [f|F]alse ]] && auth=true -if [ "$base_url" != "" ]; then - sudo crudini --set --existing $FILE api url $base_url/api/v1 - sudo crudini --set --existing $FILE ui url $base_url - sudo cat > /usr/local/share/opnfv_testapi/testapi-ui/config.json << EOF +auth_server=`echo ${auth:0:1} | tr '[:lower:]' '[:upper:]'``echo ${auth:1} | tr '[:upper:]' '[:lower:]'` +auth_web=`echo ${auth} | tr '[:upper:]' '[:lower:]'` +crudini --set --existing ${FILE} mongo url ${mongodb_url} +crudini --set --existing ${FILE} api url ${base_url}/api/v1 +crudini --set --existing ${FILE} ui url ${base_url} +crudini --set --existing ${FILE} api authenticate ${auth_server} + +cat > /usr/local/share/opnfv_testapi/testapi-ui/config.json << EOF { - "testapiApiUrl": "$base_url/api/v1", - "authenticate": true + "testapiApiUrl": "${base_url}/api/v1", + "authenticate": ${auth_web} } EOF - -fi diff --git a/testapi/opnfv_testapi/tests/unit/executor.py b/testapi/opnfv_testapi/tests/unit/executor.py index 5a8d688..7c8cb8a 100644 --- a/testapi/opnfv_testapi/tests/unit/executor.py +++ b/testapi/opnfv_testapi/tests/unit/executor.py @@ -18,9 +18,9 @@ O_get_secure_cookie = ( def thread_execute(method, *args, **kwargs): - with ThreadPoolExecutor(max_workers=2) as executor: - result = executor.submit(method, *args, **kwargs) - return result + with ThreadPoolExecutor(max_workers=2) as executor: + result = executor.submit(method, *args, **kwargs) + return result def mock_invalid_lfid(): diff --git a/testapi/opnfv_testapi/tests/unit/fake_pymongo.py b/testapi/opnfv_testapi/tests/unit/fake_pymongo.py index 041e6e8..631e9ac 100644 --- a/testapi/opnfv_testapi/tests/unit/fake_pymongo.py +++ b/testapi/opnfv_testapi/tests/unit/fake_pymongo.py @@ -15,9 +15,9 @@ from concurrent.futures import ThreadPoolExecutor def thread_execute(method, *args, **kwargs): - with ThreadPoolExecutor(max_workers=2) as executor: - result = executor.submit(method, *args, **kwargs) - return result + with ThreadPoolExecutor(max_workers=2) as executor: + result = executor.submit(method, *args, **kwargs) + return result class MemCursor(object): diff --git a/testapi/opnfv_testapi/ui/Gruntfile.js b/testapi/opnfv_testapi/ui/Gruntfile.js index ab59475..d6a2f47 100644 --- a/testapi/opnfv_testapi/ui/Gruntfile.js +++ b/testapi/opnfv_testapi/ui/Gruntfile.js @@ -152,6 +152,7 @@ module.exports = function (grunt) { makeReport: { src: '../tests/UI/coverage/*.json', options: { + type: 'cobertura', print: 'detail' } } diff --git a/testapi/requirements.txt b/testapi/requirements.txt index 7f50fd7..9dba746 100644 --- a/testapi/requirements.txt +++ b/testapi/requirements.txt @@ -2,12 +2,13 @@ # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. -pbr>=2.0.0,!=2.1.0 # Apache-2.0 -setuptools>=16.0,!=24.0.0,!=34.0.0,!=34.0.1,!=34.0.2,!=34.0.3,!=34.1.0,!=34.1.1,!=34.2.0,!=34.3.0,!=34.3.1,!=34.3.2 # PSF/ZPL -tornado>=3.1,<=4.3 # Apache-2.0 -epydoc>=0.3.1 -six>=1.9.0 # MIT -motor # Apache-2.0 +pbr!=2.1.0 # Apache-2.0 +setuptools!=24.0.0,!=34.0.0,!=34.0.1,!=34.0.2,!=34.0.3,!=34.1.0,!=34.1.1,!=34.2.0,!=34.3.0,!=34.3.1,!=34.3.2,!=36.2.0;python_version>='3.5' # PSF/ZPL +setuptools!=24.0.0,!=34.0.0,!=34.0.1,!=34.0.2,!=34.0.3,!=34.1.0,!=34.1.1,!=34.2.0,!=34.3.0,!=34.3.1,!=34.3.2,!=36.2.0,<45.0.0;python_version<='2.7' # PSF/ZPL +tornado<=4.3,>=3.1 # Apache-2.0 +epydoc +six # MIT +motor # Apache-2.0 python-cas -requests[security] -futures
\ No newline at end of file +requests[security]!=2.20.0 # Apache-2.0 +futures!=0.17.0;python_version=='2.7' or python_version=='2.6' # PSF diff --git a/testapi/setup.py b/testapi/setup.py index f9d95a3..566d844 100644 --- a/testapi/setup.py +++ b/testapi/setup.py @@ -1,13 +1,29 @@ -import setuptools +# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. -__author__ = 'serena' +# THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT +import setuptools +# In python < 2.7.4, a lazy loading of package `pbr` will break +# setuptools if some other modules registered functions in `atexit`. +# solution from: http://bugs.python.org/issue15881#msg170215 try: import multiprocessing # noqa except ImportError: pass - setuptools.setup( setup_requires=['pbr>=2.0.0'], pbr=True) diff --git a/testapi/test-requirements.txt b/testapi/test-requirements.txt index 233f465..67fd8f0 100644 --- a/testapi/test-requirements.txt +++ b/testapi/test-requirements.txt @@ -2,9 +2,9 @@ # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. -coverage>=4.0,!=4.4 # Apache-2.0 -mock>=2.0 # BSD -nose # LGPL -pytest # MIT -pytest-cov # MIT -pytest-mock # MIT +coverage!=4.4 # Apache-2.0 +mock!=4.0.0,!=4.0.1 # BSD +nose # LGPL +pytest # MIT +pytest-cov +pytest-mock diff --git a/testapi/tox.ini b/testapi/tox.ini index e15deea..0478e23 100644 --- a/testapi/tox.ini +++ b/testapi/tox.ini @@ -14,6 +14,8 @@ install_command = pip install -U {opts} {packages} deps = -rrequirements.txt -rtest-requirements.txt + -chttps://raw.githubusercontent.com/openstack/requirements/stable/ussuri/upper-constraints.txt + -cupper-constraints.txt commands= py.test \ --basetemp={envtmpdir} \ @@ -30,18 +32,21 @@ basepython=python2.7 commands = sphinx-build -W -b html docs/ docs/_build [testenv:pep8] +basepython=python2.7 deps = flake8 commands = flake8 {toxinidir} [flake8] +basepython=python2.7 # H803 skipped on purpose per list discussion. # E123, E125 skipped as they are invalid PEP-8. show-source = True -ignore = E123,E125,H803 +ignore = E123,E125,H803,W503,W504 builtins = _ exclude = build,dist,doc,legacy,.eggs,.git,.tox,.venv,testapi_venv,venv [pytest] +basepython=python2.7 testpaths = opnfv_testapi/tests python_functions = test_* diff --git a/testapi/upper-constraints.txt b/testapi/upper-constraints.txt new file mode 100644 index 0000000..5226f48 --- /dev/null +++ b/testapi/upper-constraints.txt @@ -0,0 +1,9 @@ +epydoc===3.0.1 +motor===1.2.2 +python-cas===1.2.0 +argparse===1.2.1 +backports-abc===0.5 +backports.ssl-match-hostname===3.5.0.1 +html5lib===0.999 +singledispatch===3.4.0.3 +wsgiref===0.1.2 |