From 18dd67b6ce1649a1300b1778643b06efc11261cc Mon Sep 17 00:00:00 2001 From: Ricardo Dias Date: Mon, 11 Mar 2019 15:52:26 +0000 Subject: [PATCH] mgr/dashboard: tests: move helper classes to __init__.py Signed-off-by: Ricardo Dias --- src/pybind/mgr/dashboard/tests/__init__.py | 194 ++++++++++++++++- src/pybind/mgr/dashboard/tests/helper.py | 198 ------------------ .../dashboard/tests/test_access_control.py | 3 +- .../mgr/dashboard/tests/test_api_auditing.py | 2 +- .../mgr/dashboard/tests/test_controllers.py | 2 +- src/pybind/mgr/dashboard/tests/test_docs.py | 2 +- .../tests/test_erasure_code_profile.py | 2 +- .../mgr/dashboard/tests/test_exceptions.py | 3 +- .../dashboard/tests/test_feature_toggles.py | 2 +- .../mgr/dashboard/tests/test_ganesha.py | 2 +- .../mgr/dashboard/tests/test_grafana.py | 2 +- src/pybind/mgr/dashboard/tests/test_iscsi.py | 3 +- .../mgr/dashboard/tests/test_prometheus.py | 3 +- .../mgr/dashboard/tests/test_rbd_mirroring.py | 2 +- .../mgr/dashboard/tests/test_rest_tasks.py | 2 +- src/pybind/mgr/dashboard/tests/test_rgw.py | 2 +- .../mgr/dashboard/tests/test_settings.py | 3 +- src/pybind/mgr/dashboard/tests/test_sso.py | 5 +- .../mgr/dashboard/tests/test_tcmu_iscsi.py | 2 +- src/pybind/mgr/dashboard/tests/test_tools.py | 2 +- 20 files changed, 213 insertions(+), 223 deletions(-) delete mode 100644 src/pybind/mgr/dashboard/tests/helper.py diff --git a/src/pybind/mgr/dashboard/tests/__init__.py b/src/pybind/mgr/dashboard/tests/__init__.py index f6e9609458e1e..ee2c4912d38a9 100644 --- a/src/pybind/mgr/dashboard/tests/__init__.py +++ b/src/pybind/mgr/dashboard/tests/__init__.py @@ -1,10 +1,21 @@ # -*- coding: utf-8 -*- +# pylint: disable=too-many-arguments from __future__ import absolute_import import json +import threading +import time + +import cherrypy +from cherrypy._cptools import HandlerWrapperTool +from cherrypy.test import helper from mgr_module import CLICommand, MgrModule -from .. import mgr + +from .. import logger, mgr +from ..controllers import json_error_page, generate_controller_routes +from ..services.auth import AuthManagerTool +from ..services.exception import dashboard_exception_handler class CmdException(Exception): @@ -33,3 +44,184 @@ def exec_dashboard_cmd(command_handler, cmd, **kwargs): return json.loads(out) except ValueError: return out + + +class KVStoreMockMixin(object): + CONFIG_KEY_DICT = {} + + @classmethod + def mock_set_module_option(cls, attr, val): + cls.CONFIG_KEY_DICT[attr] = val + + @classmethod + def mock_get_module_option(cls, attr, default=None): + return cls.CONFIG_KEY_DICT.get(attr, default) + + @classmethod + def mock_kv_store(cls): + cls.CONFIG_KEY_DICT.clear() + mgr.set_module_option.side_effect = cls.mock_set_module_option + mgr.get_module_option.side_effect = cls.mock_get_module_option + # kludge below + mgr.set_store.side_effect = cls.mock_set_module_option + mgr.get_store.side_effect = cls.mock_get_module_option + + @classmethod + def get_key(cls, key): + return cls.CONFIG_KEY_DICT.get(key, None) + + +class CLICommandTestMixin(KVStoreMockMixin): + @classmethod + def exec_cmd(cls, cmd, **kwargs): + return exec_dashboard_cmd(None, cmd, **kwargs) + + +class ControllerTestCase(helper.CPWebCase): + @classmethod + def setup_controllers(cls, ctrl_classes, base_url=''): + if not isinstance(ctrl_classes, list): + ctrl_classes = [ctrl_classes] + mapper = cherrypy.dispatch.RoutesDispatcher() + endpoint_list = [] + for ctrl in ctrl_classes: + inst = ctrl() + for endpoint in ctrl.endpoints(): + endpoint.inst = inst + endpoint_list.append(endpoint) + endpoint_list = sorted(endpoint_list, key=lambda e: e.url) + for endpoint in endpoint_list: + generate_controller_routes(endpoint, mapper, base_url) + if base_url == '': + base_url = '/' + cherrypy.tree.mount(None, config={ + base_url: {'request.dispatch': mapper}}) + + def __init__(self, *args, **kwargs): + cherrypy.tools.authenticate = AuthManagerTool() + cherrypy.tools.dashboard_exception_handler = HandlerWrapperTool(dashboard_exception_handler, + priority=31) + cherrypy.config.update({ + 'error_page.default': json_error_page, + 'tools.json_in.on': True, + 'tools.json_in.force': False + }) + super(ControllerTestCase, self).__init__(*args, **kwargs) + + def _request(self, url, method, data=None): + if not data: + b = None + h = None + else: + b = json.dumps(data) + h = [('Content-Type', 'application/json'), + ('Content-Length', str(len(b)))] + self.getPage(url, method=method, body=b, headers=h) + + def _get(self, url): + self._request(url, 'GET') + + def _post(self, url, data=None): + self._request(url, 'POST', data) + + def _delete(self, url, data=None): + self._request(url, 'DELETE', data) + + def _put(self, url, data=None): + self._request(url, 'PUT', data) + + def _task_request(self, method, url, data, timeout): + self._request(url, method, data) + if self.status != '202 Accepted': + logger.info("task finished immediately") + return + + res = self.jsonBody() + self.assertIsInstance(res, dict) + self.assertIn('name', res) + self.assertIn('metadata', res) + + task_name = res['name'] + task_metadata = res['metadata'] + + # pylint: disable=protected-access + class Waiter(threading.Thread): + def __init__(self, task_name, task_metadata, tc): + super(Waiter, self).__init__() + self.task_name = task_name + self.task_metadata = task_metadata + self.ev = threading.Event() + self.abort = False + self.res_task = None + self.tc = tc + + def run(self): + running = True + while running and not self.abort: + logger.info("task (%s, %s) is still executing", self.task_name, + self.task_metadata) + time.sleep(1) + self.tc._get('/api/task?name={}'.format(self.task_name)) + res = self.tc.jsonBody() + for task in res['finished_tasks']: + if task['metadata'] == self.task_metadata: + # task finished + running = False + self.res_task = task + self.ev.set() + + thread = Waiter(task_name, task_metadata, self) + thread.start() + status = thread.ev.wait(timeout) + if not status: + # timeout expired + thread.abort = True + thread.join() + raise Exception("Waiting for task ({}, {}) to finish timed out" + .format(task_name, task_metadata)) + logger.info("task (%s, %s) finished", task_name, task_metadata) + if thread.res_task['success']: + self.body = json.dumps(thread.res_task['ret_value']) + if method == 'POST': + self.status = '201 Created' + elif method == 'PUT': + self.status = '200 OK' + elif method == 'DELETE': + self.status = '204 No Content' + return + else: + if 'status' in thread.res_task['exception']: + self.status = thread.res_task['exception']['status'] + else: + self.status = 500 + self.body = json.dumps(thread.res_task['exception']) + return + + def _task_post(self, url, data=None, timeout=60): + self._task_request('POST', url, data, timeout) + + def _task_delete(self, url, timeout=60): + self._task_request('DELETE', url, None, timeout) + + def _task_put(self, url, data=None, timeout=60): + self._task_request('PUT', url, data, timeout) + + def jsonBody(self): + body_str = self.body.decode('utf-8') if isinstance(self.body, bytes) else self.body + return json.loads(body_str) + + def assertJsonBody(self, data, msg=None): + """Fail if value != self.body.""" + json_body = self.jsonBody() + if data != json_body: + if msg is None: + msg = 'expected body:\n%r\n\nactual body:\n%r' % ( + data, json_body) + self._handlewebError(msg) + + def assertInJsonBody(self, data, msg=None): + json_body = self.jsonBody() + if data not in json_body: + if msg is None: + msg = 'expected %r to be in %r' % (data, json_body) + self._handlewebError(msg) diff --git a/src/pybind/mgr/dashboard/tests/helper.py b/src/pybind/mgr/dashboard/tests/helper.py deleted file mode 100644 index fa3ff74e6532c..0000000000000 --- a/src/pybind/mgr/dashboard/tests/helper.py +++ /dev/null @@ -1,198 +0,0 @@ -# -*- coding: utf-8 -*- -# pylint: disable=too-many-arguments -from __future__ import absolute_import - -import json -import threading -import time - -import cherrypy -from cherrypy._cptools import HandlerWrapperTool -from cherrypy.test import helper - -from . import exec_dashboard_cmd -from .. import logger, mgr -from ..controllers import json_error_page, generate_controller_routes -from ..services.auth import AuthManagerTool -from ..services.exception import dashboard_exception_handler - - -class KVStoreMockMixin(object): - CONFIG_KEY_DICT = {} - - @classmethod - def mock_set_module_option(cls, attr, val): - cls.CONFIG_KEY_DICT[attr] = val - - @classmethod - def mock_get_module_option(cls, attr, default=None): - return cls.CONFIG_KEY_DICT.get(attr, default) - - @classmethod - def mock_kv_store(cls): - cls.CONFIG_KEY_DICT.clear() - mgr.set_module_option.side_effect = cls.mock_set_module_option - mgr.get_module_option.side_effect = cls.mock_get_module_option - # kludge below - mgr.set_store.side_effect = cls.mock_set_module_option - mgr.get_store.side_effect = cls.mock_get_module_option - - @classmethod - def get_key(cls, key): - return cls.CONFIG_KEY_DICT.get(key, None) - - -class CLICommandTestMixin(KVStoreMockMixin): - @classmethod - def exec_cmd(cls, cmd, **kwargs): - return exec_dashboard_cmd(None, cmd, **kwargs) - - -class ControllerTestCase(helper.CPWebCase): - @classmethod - def setup_controllers(cls, ctrl_classes, base_url=''): - if not isinstance(ctrl_classes, list): - ctrl_classes = [ctrl_classes] - mapper = cherrypy.dispatch.RoutesDispatcher() - endpoint_list = [] - for ctrl in ctrl_classes: - inst = ctrl() - for endpoint in ctrl.endpoints(): - endpoint.inst = inst - endpoint_list.append(endpoint) - endpoint_list = sorted(endpoint_list, key=lambda e: e.url) - for endpoint in endpoint_list: - generate_controller_routes(endpoint, mapper, base_url) - if base_url == '': - base_url = '/' - cherrypy.tree.mount(None, config={ - base_url: {'request.dispatch': mapper}}) - - def __init__(self, *args, **kwargs): - cherrypy.tools.authenticate = AuthManagerTool() - cherrypy.tools.dashboard_exception_handler = HandlerWrapperTool(dashboard_exception_handler, - priority=31) - cherrypy.config.update({ - 'error_page.default': json_error_page, - 'tools.json_in.on': True, - 'tools.json_in.force': False - }) - super(ControllerTestCase, self).__init__(*args, **kwargs) - - def _request(self, url, method, data=None): - if not data: - b = None - h = None - else: - b = json.dumps(data) - h = [('Content-Type', 'application/json'), - ('Content-Length', str(len(b)))] - self.getPage(url, method=method, body=b, headers=h) - - def _get(self, url): - self._request(url, 'GET') - - def _post(self, url, data=None): - self._request(url, 'POST', data) - - def _delete(self, url, data=None): - self._request(url, 'DELETE', data) - - def _put(self, url, data=None): - self._request(url, 'PUT', data) - - def _task_request(self, method, url, data, timeout): - self._request(url, method, data) - if self.status != '202 Accepted': - logger.info("task finished immediately") - return - - res = self.jsonBody() - self.assertIsInstance(res, dict) - self.assertIn('name', res) - self.assertIn('metadata', res) - - task_name = res['name'] - task_metadata = res['metadata'] - - # pylint: disable=protected-access - class Waiter(threading.Thread): - def __init__(self, task_name, task_metadata, tc): - super(Waiter, self).__init__() - self.task_name = task_name - self.task_metadata = task_metadata - self.ev = threading.Event() - self.abort = False - self.res_task = None - self.tc = tc - - def run(self): - running = True - while running and not self.abort: - logger.info("task (%s, %s) is still executing", self.task_name, - self.task_metadata) - time.sleep(1) - self.tc._get('/api/task?name={}'.format(self.task_name)) - res = self.tc.jsonBody() - for task in res['finished_tasks']: - if task['metadata'] == self.task_metadata: - # task finished - running = False - self.res_task = task - self.ev.set() - - thread = Waiter(task_name, task_metadata, self) - thread.start() - status = thread.ev.wait(timeout) - if not status: - # timeout expired - thread.abort = True - thread.join() - raise Exception("Waiting for task ({}, {}) to finish timed out" - .format(task_name, task_metadata)) - logger.info("task (%s, %s) finished", task_name, task_metadata) - if thread.res_task['success']: - self.body = json.dumps(thread.res_task['ret_value']) - if method == 'POST': - self.status = '201 Created' - elif method == 'PUT': - self.status = '200 OK' - elif method == 'DELETE': - self.status = '204 No Content' - return - else: - if 'status' in thread.res_task['exception']: - self.status = thread.res_task['exception']['status'] - else: - self.status = 500 - self.body = json.dumps(thread.res_task['exception']) - return - - def _task_post(self, url, data=None, timeout=60): - self._task_request('POST', url, data, timeout) - - def _task_delete(self, url, timeout=60): - self._task_request('DELETE', url, None, timeout) - - def _task_put(self, url, data=None, timeout=60): - self._task_request('PUT', url, data, timeout) - - def jsonBody(self): - body_str = self.body.decode('utf-8') if isinstance(self.body, bytes) else self.body - return json.loads(body_str) - - def assertJsonBody(self, data, msg=None): - """Fail if value != self.body.""" - json_body = self.jsonBody() - if data != json_body: - if msg is None: - msg = 'expected body:\n%r\n\nactual body:\n%r' % ( - data, json_body) - self._handlewebError(msg) - - def assertInJsonBody(self, data, msg=None): - json_body = self.jsonBody() - if data not in json_body: - if msg is None: - msg = 'expected %r to be in %r' % (data, json_body) - self._handlewebError(msg) diff --git a/src/pybind/mgr/dashboard/tests/test_access_control.py b/src/pybind/mgr/dashboard/tests/test_access_control.py index 5061e9f43f6fa..68bdaf1aa79f7 100644 --- a/src/pybind/mgr/dashboard/tests/test_access_control.py +++ b/src/pybind/mgr/dashboard/tests/test_access_control.py @@ -7,8 +7,7 @@ import json import time import unittest -from . import CmdException -from .helper import CLICommandTestMixin +from . import CmdException, CLICommandTestMixin from .. import mgr from ..security import Scope, Permission from ..services.access_control import load_access_control_db, \ diff --git a/src/pybind/mgr/dashboard/tests/test_api_auditing.py b/src/pybind/mgr/dashboard/tests/test_api_auditing.py index 7021d9a5941f4..ae95e3409067b 100644 --- a/src/pybind/mgr/dashboard/tests/test_api_auditing.py +++ b/src/pybind/mgr/dashboard/tests/test_api_auditing.py @@ -6,7 +6,7 @@ import json import cherrypy import mock -from .helper import ControllerTestCase, KVStoreMockMixin +from . import ControllerTestCase, KVStoreMockMixin from ..controllers import RESTController, Controller from ..tools import RequestLoggingTool from .. import mgr diff --git a/src/pybind/mgr/dashboard/tests/test_controllers.py b/src/pybind/mgr/dashboard/tests/test_controllers.py index c6fcb25b0d0f0..0e880470615e1 100644 --- a/src/pybind/mgr/dashboard/tests/test_controllers.py +++ b/src/pybind/mgr/dashboard/tests/test_controllers.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import absolute_import -from .helper import ControllerTestCase +from . import ControllerTestCase from ..controllers import BaseController, RESTController, Controller, \ ApiController, Endpoint diff --git a/src/pybind/mgr/dashboard/tests/test_docs.py b/src/pybind/mgr/dashboard/tests/test_docs.py index 65da8d345fe6c..cc30a57d3d606 100644 --- a/src/pybind/mgr/dashboard/tests/test_docs.py +++ b/src/pybind/mgr/dashboard/tests/test_docs.py @@ -1,7 +1,7 @@ # # -*- coding: utf-8 -*- from __future__ import absolute_import -from .helper import ControllerTestCase +from . import ControllerTestCase from ..controllers import RESTController, ApiController, Endpoint, EndpointDoc, ControllerDoc from ..controllers.docs import Docs diff --git a/src/pybind/mgr/dashboard/tests/test_erasure_code_profile.py b/src/pybind/mgr/dashboard/tests/test_erasure_code_profile.py index 804a5314d5213..88575c0a35c54 100644 --- a/src/pybind/mgr/dashboard/tests/test_erasure_code_profile.py +++ b/src/pybind/mgr/dashboard/tests/test_erasure_code_profile.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- from .. import mgr -from .helper import ControllerTestCase +from . import ControllerTestCase from ..controllers.erasure_code_profile import ErasureCodeProfile diff --git a/src/pybind/mgr/dashboard/tests/test_exceptions.py b/src/pybind/mgr/dashboard/tests/test_exceptions.py index a6c38d84ef049..5607f1dd02992 100644 --- a/src/pybind/mgr/dashboard/tests/test_exceptions.py +++ b/src/pybind/mgr/dashboard/tests/test_exceptions.py @@ -4,9 +4,10 @@ from __future__ import absolute_import import time import rados + +from . import ControllerTestCase from ..services.ceph_service import SendCommandError from ..controllers import RESTController, Controller, Task, Endpoint -from .helper import ControllerTestCase from ..services.exception import handle_rados_error, handle_send_command_error, \ serialize_dashboard_exception from ..tools import ViewCache, TaskManager, NotificationQueue diff --git a/src/pybind/mgr/dashboard/tests/test_feature_toggles.py b/src/pybind/mgr/dashboard/tests/test_feature_toggles.py index 4d659dc39e05b..5c70c88a1843d 100644 --- a/src/pybind/mgr/dashboard/tests/test_feature_toggles.py +++ b/src/pybind/mgr/dashboard/tests/test_feature_toggles.py @@ -4,7 +4,7 @@ from __future__ import absolute_import import unittest from mock import Mock, patch -from .helper import KVStoreMockMixin +from . import KVStoreMockMixin from ..plugins.feature_toggles import FeatureToggles, Features diff --git a/src/pybind/mgr/dashboard/tests/test_ganesha.py b/src/pybind/mgr/dashboard/tests/test_ganesha.py index 4c9711c5e086a..aacbfcf5c2aef 100644 --- a/src/pybind/mgr/dashboard/tests/test_ganesha.py +++ b/src/pybind/mgr/dashboard/tests/test_ganesha.py @@ -5,7 +5,7 @@ import unittest from mock import MagicMock, Mock -from .helper import KVStoreMockMixin +from . import KVStoreMockMixin from .. import mgr from ..settings import Settings from ..services import ganesha diff --git a/src/pybind/mgr/dashboard/tests/test_grafana.py b/src/pybind/mgr/dashboard/tests/test_grafana.py index da0ce9d9c7c9a..abe5c15eaa4e6 100644 --- a/src/pybind/mgr/dashboard/tests/test_grafana.py +++ b/src/pybind/mgr/dashboard/tests/test_grafana.py @@ -1,4 +1,4 @@ -from .helper import ControllerTestCase +from . import ControllerTestCase from ..controllers.grafana import Grafana from .. import mgr diff --git a/src/pybind/mgr/dashboard/tests/test_iscsi.py b/src/pybind/mgr/dashboard/tests/test_iscsi.py index ae6dc7ea210ae..57f2bb6037dec 100644 --- a/src/pybind/mgr/dashboard/tests/test_iscsi.py +++ b/src/pybind/mgr/dashboard/tests/test_iscsi.py @@ -5,8 +5,7 @@ import errno import json import mock -from . import CmdException -from .helper import ControllerTestCase, CLICommandTestMixin +from . import CmdException, ControllerTestCase, CLICommandTestMixin from .. import mgr from ..controllers.iscsi import Iscsi, IscsiTarget from ..services.iscsi_client import IscsiClient diff --git a/src/pybind/mgr/dashboard/tests/test_prometheus.py b/src/pybind/mgr/dashboard/tests/test_prometheus.py index 5961187c8ce2f..2f3fb99271d95 100644 --- a/src/pybind/mgr/dashboard/tests/test_prometheus.py +++ b/src/pybind/mgr/dashboard/tests/test_prometheus.py @@ -1,10 +1,9 @@ # -*- coding: utf-8 -*- +from . import ControllerTestCase from .. import mgr from ..controllers import BaseController, Controller from ..controllers.prometheus import Prometheus, PrometheusReceiver -from .helper import ControllerTestCase - @Controller('alertmanager/mocked/api/v1/alerts', secure=False) class AlertManagerMockInstance(BaseController): diff --git a/src/pybind/mgr/dashboard/tests/test_rbd_mirroring.py b/src/pybind/mgr/dashboard/tests/test_rbd_mirroring.py index 0d2933afd0780..bad16b746b26c 100644 --- a/src/pybind/mgr/dashboard/tests/test_rbd_mirroring.py +++ b/src/pybind/mgr/dashboard/tests/test_rbd_mirroring.py @@ -3,10 +3,10 @@ from __future__ import absolute_import import json import mock +from . import ControllerTestCase from .. import mgr from ..controllers.summary import Summary from ..controllers.rbd_mirroring import RbdMirroringSummary -from .helper import ControllerTestCase mock_list_servers = [{ diff --git a/src/pybind/mgr/dashboard/tests/test_rest_tasks.py b/src/pybind/mgr/dashboard/tests/test_rest_tasks.py index ad871c9410e9e..191ef812934fb 100644 --- a/src/pybind/mgr/dashboard/tests/test_rest_tasks.py +++ b/src/pybind/mgr/dashboard/tests/test_rest_tasks.py @@ -3,7 +3,7 @@ import time -from .helper import ControllerTestCase +from . import ControllerTestCase from ..controllers import Controller, RESTController, Task from ..controllers.task import Task as TaskController from ..tools import NotificationQueue, TaskManager diff --git a/src/pybind/mgr/dashboard/tests/test_rgw.py b/src/pybind/mgr/dashboard/tests/test_rgw.py index 1dfe901751b45..38c3d0af87b0d 100644 --- a/src/pybind/mgr/dashboard/tests/test_rgw.py +++ b/src/pybind/mgr/dashboard/tests/test_rgw.py @@ -1,6 +1,6 @@ import mock -from .helper import ControllerTestCase +from . import ControllerTestCase from ..controllers.rgw import RgwUser diff --git a/src/pybind/mgr/dashboard/tests/test_settings.py b/src/pybind/mgr/dashboard/tests/test_settings.py index a76779355cbc0..aac785b7ce03b 100644 --- a/src/pybind/mgr/dashboard/tests/test_settings.py +++ b/src/pybind/mgr/dashboard/tests/test_settings.py @@ -3,11 +3,10 @@ from __future__ import absolute_import import errno import unittest -from .helper import KVStoreMockMixin +from . import KVStoreMockMixin, ControllerTestCase from .. import settings from ..controllers.settings import Settings as SettingsController from ..settings import Settings, handle_option_command -from .helper import ControllerTestCase class SettingsTest(unittest.TestCase, KVStoreMockMixin): diff --git a/src/pybind/mgr/dashboard/tests/test_sso.py b/src/pybind/mgr/dashboard/tests/test_sso.py index ee2720a556a30..f8681b89ede8d 100644 --- a/src/pybind/mgr/dashboard/tests/test_sso.py +++ b/src/pybind/mgr/dashboard/tests/test_sso.py @@ -5,12 +5,11 @@ from __future__ import absolute_import import errno import unittest -from . import CmdException, exec_dashboard_cmd -from .helper import CLICommandTestMixin +from . import CmdException, exec_dashboard_cmd, KVStoreMockMixin from ..services.sso import handle_sso_command, load_sso_db -class AccessControlTest(unittest.TestCase, CLICommandTestMixin): +class AccessControlTest(unittest.TestCase, KVStoreMockMixin): IDP_METADATA = '''