Init REST API
This patch adds REST API for next operations: - getting of list plugins (GET /v1/plugins), - getting of suites from plugin (GET /v1/plugins/<name>/suites) - getting of tests from plugin (GET /v1/plugins/<name>/suites/tests) - getting of tests from suites (GET /v1/plugins/<name>/suites/<suite>/tests) - executing of suites in plugin (POST /v1/plugins/<name>/suites) - executing of suite in plugin (POST /v1/plugins/<name>/suites/<suite>) - executing of test (POST /v1/plugins/<name>/suites/tests/<test>) Change-Id: I8a5c7a14a791d62fc856526dbd5585430ec2d555
This commit is contained in:
parent
cd3e966d0a
commit
8e32dc6c0f
46
README.rst
46
README.rst
|
@ -123,3 +123,49 @@ Links
|
|||
|
||||
* OSTF contributor's guide - http://docs.mirantis.com/fuel-dev/develop/ostf_contributors_guide.html)
|
||||
* OSTF source code - https://github.com/stackforge/fuel-ostf
|
||||
|
||||
========
|
||||
REST API
|
||||
========
|
||||
|
||||
|
||||
Run server
|
||||
----------
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ cloudvalidation-server --config-file=path_to_config
|
||||
* Running on http://127.0.0.1:8777/ (Press CTRL+C to quit)
|
||||
|
||||
Example of config
|
||||
-----------------
|
||||
|
||||
[rest]
|
||||
server_host=127.0.0.1
|
||||
server_port=8777
|
||||
log_file=/var/log/ostf.log
|
||||
debug=False
|
||||
|
||||
List of supported operations
|
||||
----------------------------
|
||||
- get list of supported plugins
|
||||
GET /v1/plugins?load_tests=True/False
|
||||
In load_tests=True case tests for plugin will be shown.
|
||||
|
||||
- get suites in plugin
|
||||
GET /v1/plugins/<plugin_name>/suites
|
||||
|
||||
- get tests for all suites in plugin
|
||||
GET /v1/plugins/<plugin_name>/suites/tests
|
||||
|
||||
- get tests per suite in plugin
|
||||
GET /v1/plugins/<plugin_name>/suites/<suite>/tests
|
||||
|
||||
- run suites for plugin
|
||||
POST /v1/plugins/<plugin_name>/suites
|
||||
|
||||
- run suite for plugin
|
||||
POST /v1/plugins/<plugin_name>/suites/<suite>
|
||||
|
||||
- run test for plugin
|
||||
/v1/plugins/<plugin_name>/suites/tests/<test>
|
||||
|
|
|
@ -30,6 +30,7 @@ sanity_group = cfg.OptGroup("sanity", "Sanity configuration group.")
|
|||
smoke_group = cfg.OptGroup("smoke", "Smoke configuration group.")
|
||||
platform_group = cfg.OptGroup("platform",
|
||||
"Platform functional configuration group.")
|
||||
rest_group = cfg.OptGroup("rest", "Cloudvalidation ReST API service options.")
|
||||
ha_group = cfg.OptGroup("high_availability", "HA configuration group.")
|
||||
|
||||
|
||||
|
@ -61,6 +62,22 @@ ha_opts = [
|
|||
cfg.MultiStrOpt("enabled_tests", default=[]),
|
||||
]
|
||||
|
||||
rest_opts = [
|
||||
cfg.StrOpt('server_host',
|
||||
default='127.0.0.1',
|
||||
help="adapter host"),
|
||||
cfg.IntOpt('server_port',
|
||||
default=8777,
|
||||
help="Port number"),
|
||||
cfg.StrOpt('log_file',
|
||||
default='/var/log/ostf.log',
|
||||
help=""),
|
||||
cfg.StrOpt('debug',
|
||||
default=False,
|
||||
help="Debug for REST API."),
|
||||
]
|
||||
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.register_opts(common_opts)
|
||||
|
||||
|
@ -68,11 +85,13 @@ CONF.register_group(sanity_group)
|
|||
CONF.register_group(smoke_group)
|
||||
CONF.register_group(platform_group)
|
||||
CONF.register_group(ha_group)
|
||||
CONF.register_group(rest_group)
|
||||
|
||||
CONF.register_opts(sanity_opts, sanity_group)
|
||||
CONF.register_opts(smoke_opts, smoke_group)
|
||||
CONF.register_opts(platform_opts, platform_group)
|
||||
CONF.register_opts(ha_opts, ha_group)
|
||||
CONF.register_opts(rest_opts, rest_group)
|
||||
|
||||
|
||||
def parse_args(argv, default_config_files=None):
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
# Copyright 2015 Mirantis, Inc
|
||||
#
|
||||
# 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.
|
||||
|
||||
import signal
|
||||
import sys
|
||||
|
||||
import flask
|
||||
from flask.ext import restful
|
||||
from oslo_config import cfg
|
||||
|
||||
from cloudv_ostf_adapter.common import cfg as config
|
||||
from cloudv_ostf_adapter import wsgi
|
||||
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
app = flask.Flask('cloudv_ostf_adapter')
|
||||
api = restful.Api(app)
|
||||
|
||||
api.add_resource(wsgi.Plugins, '/v1/plugins')
|
||||
api.add_resource(wsgi.PluginSuite,
|
||||
'/v1/plugins/<plugin>/suites')
|
||||
api.add_resource(wsgi.PluginTests,
|
||||
'/v1/plugins/<plugin>/suites/tests')
|
||||
api.add_resource(wsgi.Suites,
|
||||
'/v1/plugins/<plugin>/suites/<suite>',
|
||||
'/v1/plugins/<plugin>/suites/<suite>/tests')
|
||||
api.add_resource(wsgi.Tests,
|
||||
'/v1/plugins/<plugin>/suites/tests/<test>')
|
||||
|
||||
|
||||
def main():
|
||||
config.parse_args(sys.argv)
|
||||
|
||||
host, port = CONF.rest.server_host, CONF.rest.server_port
|
||||
try:
|
||||
signal.signal(signal.SIGCHLD, signal.SIG_IGN)
|
||||
signal.signal(signal.SIGHUP, signal.SIG_IGN)
|
||||
app.run(host=host, port=port, debug=CONF.rest.debug)
|
||||
except KeyboardInterrupt:
|
||||
pass
|
|
@ -23,9 +23,29 @@ CONF = cfg.CONF
|
|||
SUITES = [fake_plugin_tests]
|
||||
|
||||
|
||||
class FakeTest(object):
|
||||
def __init__(self):
|
||||
self.description = {
|
||||
"test": 'fake_test',
|
||||
"report": "",
|
||||
"result": "passed",
|
||||
"duration": "0.1"
|
||||
}
|
||||
|
||||
|
||||
class FakeValidationPlugin(base.ValidationPlugin):
|
||||
|
||||
def __init__(self, load_tests=True):
|
||||
name = 'fake'
|
||||
self.test = FakeTest()
|
||||
super(FakeValidationPlugin, self).__init__(
|
||||
name, SUITES, load_tests=load_tests)
|
||||
|
||||
def run_suites(self):
|
||||
return [self.test]
|
||||
|
||||
def run_suite(self, suite):
|
||||
return [self.test]
|
||||
|
||||
def run_test(self, test):
|
||||
return [self.test]
|
||||
|
|
|
@ -0,0 +1,176 @@
|
|||
# Copyright 2015 Mirantis, Inc
|
||||
#
|
||||
# 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.
|
||||
import json
|
||||
|
||||
import testtools
|
||||
|
||||
from cloudv_ostf_adapter import server
|
||||
from cloudv_ostf_adapter.tests.unittests.fakes.fake_plugin import health_plugin
|
||||
from cloudv_ostf_adapter import wsgi
|
||||
|
||||
|
||||
class TestServer(testtools.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.plugin = health_plugin.FakeValidationPlugin()
|
||||
server.app.config['TESTING'] = True
|
||||
self.app = server.app.test_client()
|
||||
self.actual_plugins = wsgi.validation_plugin.VALIDATION_PLUGINS
|
||||
wsgi.validation_plugin.VALIDATION_PLUGINS = [self.plugin.__class__]
|
||||
super(TestServer, self).setUp()
|
||||
|
||||
def tearDown(self):
|
||||
wsgi.validation_plugin.VALIDATION_PLUGINS = self.actual_plugins
|
||||
super(TestServer, self).tearDown()
|
||||
|
||||
def test_urlmap(self):
|
||||
links = []
|
||||
check_list = [
|
||||
'/v1/plugins',
|
||||
'/v1/plugins/<plugin>/suites/tests/<test>',
|
||||
'/v1/plugins/<plugin>/suites/<suite>/tests',
|
||||
'/v1/plugins/<plugin>/suites/tests',
|
||||
'/v1/plugins/<plugin>/suites/<suite>',
|
||||
'/v1/plugins/<plugin>/suites'
|
||||
]
|
||||
for rule in server.app.url_map.iter_rules():
|
||||
links.append(str(rule))
|
||||
self.assertEqual(set(check_list) & set(links), set(check_list))
|
||||
|
||||
def _resp_to_dict(self, data):
|
||||
if type(data) == bytes:
|
||||
data = data.decode('utf-8')
|
||||
return json.loads(data)
|
||||
|
||||
def test_plugins_no_load_tests(self):
|
||||
rv = self.app.get('/v1/plugins').data
|
||||
check = {
|
||||
'plugins': [{'name': self.plugin.name,
|
||||
'suites': self.plugin.suites,
|
||||
'tests': []}]
|
||||
}
|
||||
self.assertEqual(self._resp_to_dict(rv), check)
|
||||
|
||||
def test_plugins_load_tests(self):
|
||||
rv = self.app.get('/v1/plugins?load_tests=True').data
|
||||
check = {
|
||||
'plugins': [{'name': self.plugin.name,
|
||||
'suites': self.plugin.suites,
|
||||
'tests': self.plugin.tests}]
|
||||
}
|
||||
self.assertEqual(self._resp_to_dict(rv), check)
|
||||
|
||||
def test_plugin_not_found(self):
|
||||
rv = self.app.get('/v1/plugins/fake2/suites').data
|
||||
check = {"message": "Unsupported plugin fake2."}
|
||||
self.assertEqual(self._resp_to_dict(rv), check)
|
||||
|
||||
def test_plugin_suites(self):
|
||||
rv = self.app.get('/v1/plugins/fake/suites').data
|
||||
check = {"plugin": {"name": self.plugin.name,
|
||||
"suites": self.plugin.suites}}
|
||||
self.assertEqual(self._resp_to_dict(rv), check)
|
||||
|
||||
def test_suite_plugin_not_found(self):
|
||||
rv = self.app.get(
|
||||
'/v1/plugins/fake2/suites/fake/tests').data
|
||||
check = {u'message': u'Unsupported plugin fake2.'}
|
||||
self.assertEqual(self._resp_to_dict(rv), check)
|
||||
|
||||
def test_suite_tests(self):
|
||||
suite = self.plugin.suites[0]
|
||||
url = '/v1/plugins/fake/suites/%s/tests' % suite
|
||||
rv = self.app.get(url).data
|
||||
tests = self.plugin.get_tests_by_suite(suite)
|
||||
check = {
|
||||
"plugin": {"name": self.plugin.name,
|
||||
"suite": {"name": suite,
|
||||
"tests": tests}}}
|
||||
self.assertEqual(self._resp_to_dict(rv), check)
|
||||
|
||||
def test_suite_not_found(self):
|
||||
rv = self.app.get(
|
||||
'/v1/plugins/fake/suites/fake/tests').data
|
||||
check = {u'message': u'Unknown suite fake.'}
|
||||
self.assertEqual(self._resp_to_dict(rv), check)
|
||||
|
||||
def test_plugin_tests_not_found(self):
|
||||
rv = self.app.get(
|
||||
'/v1/plugins/fake2/suites/tests').data
|
||||
check = {u'message': u'Unsupported plugin fake2.'}
|
||||
self.assertEqual(self._resp_to_dict(rv), check)
|
||||
|
||||
def test_plugin_tests(self):
|
||||
rv = self.app.get(
|
||||
'/v1/plugins/fake/suites/tests').data
|
||||
check = {"plugin": {"name": self.plugin.name,
|
||||
"tests": self.plugin.tests}}
|
||||
self.assertEqual(self._resp_to_dict(rv), check)
|
||||
|
||||
def test_run_suites(self):
|
||||
rv = self.app.post(
|
||||
'/v1/plugins/fake/suites').data
|
||||
check = {
|
||||
u'plugin': {u'name': self.plugin.name,
|
||||
u'report': [self.plugin.test.description]}}
|
||||
self.assertEqual(self._resp_to_dict(rv), check)
|
||||
|
||||
def test_run_suites_plugin_not_found(self):
|
||||
rv = self.app.post(
|
||||
'/v1/plugins/fake2/suites').data
|
||||
check = {u'message': u'Unsupported plugin fake2.'}
|
||||
self.assertEqual(self._resp_to_dict(rv), check)
|
||||
|
||||
def test_run_suite(self):
|
||||
suite = self.plugin.suites[0]
|
||||
rv = self.app.post(
|
||||
'/v1/plugins/fake/suites/%s' % suite).data
|
||||
check = {
|
||||
u'suite': {u'name': suite,
|
||||
u'report': [self.plugin.test.description]}}
|
||||
self.assertEqual(self._resp_to_dict(rv), check)
|
||||
|
||||
def test_run_suite_plugin_not_found(self):
|
||||
rv = self.app.post(
|
||||
'/v1/plugins/fake2/suites/fake_suite').data
|
||||
check = {u'message': u'Unsupported plugin fake2.'}
|
||||
self.assertEqual(self._resp_to_dict(rv), check)
|
||||
|
||||
def test_run_suite_suite_not_found(self):
|
||||
rv = self.app.post(
|
||||
'/v1/plugins/fake/suites/fake_suite').data
|
||||
check = {u'message': u'Unknown suite fake_suite.'}
|
||||
self.assertEqual(self._resp_to_dict(rv), check)
|
||||
|
||||
def test_run_test(self):
|
||||
test = self.plugin.tests[0]
|
||||
rv = self.app.post(
|
||||
'/v1/plugins/fake/suites/tests/%s' % test).data
|
||||
check = {
|
||||
u'plugin': {u'name': self.plugin.name,
|
||||
u'test': test,
|
||||
u'report': [self.plugin.test.description]}}
|
||||
self.assertEqual(self._resp_to_dict(rv), check)
|
||||
|
||||
def test_run_test_plugin_not_found(self):
|
||||
rv = self.app.post(
|
||||
'/v1/plugins/fake2/suites/tests/fake_test').data
|
||||
check = {u'message': u'Unsupported plugin fake2.'}
|
||||
self.assertEqual(self._resp_to_dict(rv), check)
|
||||
|
||||
def test_run_test_not_found(self):
|
||||
rv = self.app.post(
|
||||
'/v1/plugins/fake/suites/tests/fake_test').data
|
||||
check = {u'message': u'Test fake_test not found.'}
|
||||
self.assertEqual(self._resp_to_dict(rv), check)
|
|
@ -60,10 +60,10 @@ class ValidationPlugin(object):
|
|||
self.name = name
|
||||
self.suites = __suites
|
||||
self._suites = suites
|
||||
self.tests = (self._get_tests()
|
||||
self.tests = (self.get_tests()
|
||||
if load_tests else [])
|
||||
|
||||
def _get_tests(self):
|
||||
def get_tests(self):
|
||||
"""
|
||||
Test collector
|
||||
"""
|
||||
|
@ -94,7 +94,7 @@ class ValidationPlugin(object):
|
|||
})
|
||||
return test_suites_paths
|
||||
|
||||
def _get_tests_by_suite(self, suite):
|
||||
def get_tests_by_suite(self, suite):
|
||||
tests = []
|
||||
for test in self.tests:
|
||||
if suite in test:
|
||||
|
|
|
@ -79,9 +79,9 @@ class FuelHealthPlugin(base.ValidationPlugin):
|
|||
super(FuelHealthPlugin, self).__init__(
|
||||
'fuel_health', SUITES, load_tests=load_tests)
|
||||
|
||||
def _get_tests(self):
|
||||
def get_tests(self):
|
||||
try:
|
||||
return super(FuelHealthPlugin, self)._get_tests()
|
||||
return super(FuelHealthPlugin, self).get_tests()
|
||||
except Exception:
|
||||
print("fuel_health is not installed.")
|
||||
|
||||
|
@ -134,7 +134,7 @@ class FuelHealthPlugin(base.ValidationPlugin):
|
|||
raise Exception(
|
||||
"%s is a test case, but not test suite." % suite)
|
||||
else:
|
||||
tests = self._get_tests_by_suite(suite)
|
||||
tests = self.get_tests_by_suite(suite)
|
||||
test_suites_paths = self.setup_execution(tests)
|
||||
reports = self._execute_and_report(test_suites_paths)
|
||||
sys.stderr = safe_stderr
|
||||
|
|
|
@ -0,0 +1,137 @@
|
|||
# Copyright 2015 Mirantis, Inc
|
||||
#
|
||||
# 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.
|
||||
|
||||
from flask.ext import restful
|
||||
from flask.ext.restful import abort
|
||||
from flask.ext.restful import reqparse
|
||||
from oslo_config import cfg
|
||||
|
||||
from cloudv_ostf_adapter import validation_plugin
|
||||
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
class BaseTests(restful.Resource):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(BaseTests, self).__init__(*args, **kwargs)
|
||||
self.plugins = {}
|
||||
for plugin in validation_plugin.VALIDATION_PLUGINS:
|
||||
_plugin = plugin(load_tests=False)
|
||||
self.plugins[_plugin.name] = _plugin
|
||||
|
||||
def load_tests(self):
|
||||
for plugin in self.plugins.values():
|
||||
plugin.tests = plugin.get_tests()
|
||||
|
||||
def get_plugin(self, **kwargs):
|
||||
plugin = kwargs.pop('plugin', None)
|
||||
if plugin is None or plugin not in self.plugins:
|
||||
abort(404,
|
||||
message='Unsupported plugin %s.' % plugin)
|
||||
return self.plugins[plugin]
|
||||
|
||||
def get_suite(self, plugin, suite=None):
|
||||
if suite not in plugin.suites:
|
||||
abort(404,
|
||||
message='Unknown suite %s.' % suite)
|
||||
return suite
|
||||
|
||||
|
||||
class Plugins(BaseTests):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(Plugins, self).__init__(*args, **kwargs)
|
||||
self.parser = reqparse.RequestParser()
|
||||
self.parser.add_argument('load_tests',
|
||||
type=str,
|
||||
location='args',
|
||||
required=False)
|
||||
|
||||
def get(self, **kwargs):
|
||||
args = self.parser.parse_args()
|
||||
load_tests = args.pop('load_tests', False)
|
||||
if load_tests in ['True', 'true', '1']:
|
||||
self.load_tests()
|
||||
plugins = [
|
||||
{'name': p.name,
|
||||
'suites': p.suites,
|
||||
'tests': p.tests}
|
||||
for p in self.plugins.values()
|
||||
]
|
||||
return {'plugins': plugins}
|
||||
|
||||
|
||||
class PluginSuite(BaseTests):
|
||||
|
||||
def get(self, **kwargs):
|
||||
plugin = self.get_plugin(**kwargs)
|
||||
return {'plugin': {'name': plugin.name,
|
||||
'suites': plugin.suites}}
|
||||
|
||||
def post(self, **kwargs):
|
||||
plugin = self.get_plugin(**kwargs)
|
||||
self.load_tests()
|
||||
reports = plugin.run_suites()
|
||||
report = [r.description for r in reports]
|
||||
return {"plugin": {"name": plugin.name,
|
||||
"report": report}}
|
||||
|
||||
|
||||
class PluginTests(BaseTests):
|
||||
|
||||
def get(self, **kwargs):
|
||||
plugin = self.get_plugin(**kwargs)
|
||||
self.load_tests()
|
||||
return {'plugin': {'name': plugin.name,
|
||||
'tests': plugin.tests}}
|
||||
|
||||
|
||||
class Suites(BaseTests):
|
||||
|
||||
def get(self, **kwargs):
|
||||
plugin = self.get_plugin(**kwargs)
|
||||
_suite = kwargs.pop('suite', None)
|
||||
suite = self.get_suite(plugin, suite=_suite)
|
||||
self.load_tests()
|
||||
tests = plugin.get_tests_by_suite(suite)
|
||||
return {'plugin': {'name': plugin.name,
|
||||
'suite': {'name': suite,
|
||||
'tests': tests}}}
|
||||
|
||||
def post(self, **kwargs):
|
||||
plugin = self.get_plugin(**kwargs)
|
||||
_suite = kwargs.pop('suite', None)
|
||||
suite = self.get_suite(plugin, suite=_suite)
|
||||
self.load_tests()
|
||||
reports = plugin.run_suite(suite)
|
||||
report = [r.description for r in reports]
|
||||
return {"suite": {"name": suite,
|
||||
"report": report}}
|
||||
|
||||
|
||||
class Tests(BaseTests):
|
||||
|
||||
def post(self, **kwargs):
|
||||
plugin = self.get_plugin(**kwargs)
|
||||
self.load_tests()
|
||||
test = kwargs.pop('test', None)
|
||||
if test is None or test not in plugin.tests:
|
||||
abort(404,
|
||||
message="Test %s not found." % test)
|
||||
reports = plugin.run_test(test)
|
||||
report = [r.description for r in reports]
|
||||
return {"plugin": {"name": plugin.name,
|
||||
"test": test,
|
||||
"report": report}}
|
|
@ -1,6 +1,12 @@
|
|||
[DEFAULT]
|
||||
health_check_config_path = /home/dmakogon/Documents/MCV/repo/cloudv-ostf-adapter/etc/cloudv_ostf_adapter/test.conf
|
||||
|
||||
[rest]
|
||||
server_host=127.0.0.1
|
||||
server_port=8777
|
||||
log_file=/var/log/ostf.log
|
||||
debug=False
|
||||
|
||||
[sanity]
|
||||
|
||||
[smoke]
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
# of appearance. Changing the order has an impact on the overall integration
|
||||
# process, which may cause wedges in the gate later.
|
||||
|
||||
flask
|
||||
flask-restful
|
||||
nose
|
||||
oslo.config>=1.6.0 # Apache-2.0
|
||||
pbr>=0.6,!=0.7,<1.0
|
||||
|
|
Loading…
Reference in New Issue