From e59c7c11c873f38fa86220becb75517351b37fa2 Mon Sep 17 00:00:00 2001 From: Ricardo Dias Date: Thu, 8 Feb 2018 08:24:01 +0000 Subject: [PATCH] mgr/dashboard_v2: removed mock classes, unit tests now run against real cluster We changed tox.ini to run a vstart.sh cluster and run the unit tests against that cluster. All unit tests code was adapted to make requests to the real dashboard_v2 backend using the `requests` python library Signed-off-by: Ricardo Dias --- src/pybind/mgr/dashboard_v2/.gitignore | 1 + src/pybind/mgr/dashboard_v2/__init__.py | 16 +- .../mgr/dashboard_v2/cephmock/__init__.py | 0 .../dashboard_v2/cephmock/ceph_module_mock.py | 41 ----- .../mgr/dashboard_v2/cephmock/rados_mock.py | 12 -- .../mgr/dashboard_v2/cephmock/rbd_mock.py | 31 ---- .../mgr/dashboard_v2/controllers/ping.py | 27 ---- src/pybind/mgr/dashboard_v2/module.py | 22 ++- src/pybind/mgr/dashboard_v2/requirements.txt | 1 + src/pybind/mgr/dashboard_v2/tests/helper.py | 107 +++++++++---- .../mgr/dashboard_v2/tests/test_auth.py | 141 ++++++------------ .../mgr/dashboard_v2/tests/test_host.py | 26 ++++ .../mgr/dashboard_v2/tests/test_module.py | 32 ---- .../mgr/dashboard_v2/tests/test_ping.py | 27 ---- src/pybind/mgr/dashboard_v2/tests/test_rgw.py | 79 +++------- .../mgr/dashboard_v2/tests/test_tools.py | 37 ++++- src/pybind/mgr/dashboard_v2/tox.ini | 43 +++++- 17 files changed, 265 insertions(+), 378 deletions(-) delete mode 100644 src/pybind/mgr/dashboard_v2/cephmock/__init__.py delete mode 100644 src/pybind/mgr/dashboard_v2/cephmock/ceph_module_mock.py delete mode 100644 src/pybind/mgr/dashboard_v2/cephmock/rados_mock.py delete mode 100644 src/pybind/mgr/dashboard_v2/cephmock/rbd_mock.py delete mode 100644 src/pybind/mgr/dashboard_v2/controllers/ping.py create mode 100644 src/pybind/mgr/dashboard_v2/tests/test_host.py delete mode 100644 src/pybind/mgr/dashboard_v2/tests/test_module.py delete mode 100644 src/pybind/mgr/dashboard_v2/tests/test_ping.py diff --git a/src/pybind/mgr/dashboard_v2/.gitignore b/src/pybind/mgr/dashboard_v2/.gitignore index 15922e4b09fd3..16815d17d1b50 100644 --- a/src/pybind/mgr/dashboard_v2/.gitignore +++ b/src/pybind/mgr/dashboard_v2/.gitignore @@ -5,6 +5,7 @@ coverage.xml junit*xml __pycache__ .cache +ceph.conf # IDE .vscode diff --git a/src/pybind/mgr/dashboard_v2/__init__.py b/src/pybind/mgr/dashboard_v2/__init__.py index 51e53764ee801..248abaabc8389 100644 --- a/src/pybind/mgr/dashboard_v2/__init__.py +++ b/src/pybind/mgr/dashboard_v2/__init__.py @@ -6,8 +6,8 @@ from __future__ import absolute_import import os -if 'UNITTEST' not in os.environ: +if 'UNITTEST' not in os.environ: class _LoggerProxy(object): def __init__(self): self.logger = None @@ -18,18 +18,10 @@ if 'UNITTEST' not in os.environ: return getattr(self.logger, item) logger = _LoggerProxy() - - # pylint: disable=W0403,W0401 - + # pylint: disable=wildcard-import, wrong-import-position from .module import * # NOQA else: import logging - import sys - # pylint: disable=W0403 - from .cephmock import ceph_module_mock, rados_mock, rbd_mock - sys.modules['ceph_module'] = ceph_module_mock - sys.modules['rados'] = rados_mock - sys.modules['rbd'] = rbd_mock - logging.basicConfig(level=logging.WARNING) + logging.basicConfig(level=logging.DEBUG) logger = logging.getLogger(__name__) - logging.root.handlers[0].setLevel(logging.WARNING) + logging.root.handlers[0].setLevel(logging.DEBUG) diff --git a/src/pybind/mgr/dashboard_v2/cephmock/__init__.py b/src/pybind/mgr/dashboard_v2/cephmock/__init__.py deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/src/pybind/mgr/dashboard_v2/cephmock/ceph_module_mock.py b/src/pybind/mgr/dashboard_v2/cephmock/ceph_module_mock.py deleted file mode 100644 index edb3c341cf773..0000000000000 --- a/src/pybind/mgr/dashboard_v2/cephmock/ceph_module_mock.py +++ /dev/null @@ -1,41 +0,0 @@ -# -*- coding: utf-8 -*- - - -class BasePyOSDMap(object): - pass - - -class BasePyOSDMapIncremental(object): - pass - - -class BasePyCRUSH(object): - pass - - -class BaseMgrStandbyModule(object): - pass - - -class BaseMgrModule(object): - # pylint: disable=W0613 - def __init__(self, py_modules_ptr, this_ptr): - self.config_key_map = {} - - def _ceph_get_version(self): - return 'ceph-13.0.0' - - def _ceph_get_mgr_id(self): - return 'x' - - def _ceph_set_config(self, key, value): - self.config_key_map[key] = value - - def _ceph_get_config(self, key): - return self.config_key_map.get(key, None) - - def _ceph_log(self, *args): - pass - - def _ceph_get_context(self): - return None diff --git a/src/pybind/mgr/dashboard_v2/cephmock/rados_mock.py b/src/pybind/mgr/dashboard_v2/cephmock/rados_mock.py deleted file mode 100644 index 3e51f4038cd3d..0000000000000 --- a/src/pybind/mgr/dashboard_v2/cephmock/rados_mock.py +++ /dev/null @@ -1,12 +0,0 @@ -# -*- coding: utf-8 -*- - - -class Rados(object): - def __init__(self, *args, **kwargs): - pass - - def connect(self): - pass - - def open_ioctx(self, pool_name): - pass diff --git a/src/pybind/mgr/dashboard_v2/cephmock/rbd_mock.py b/src/pybind/mgr/dashboard_v2/cephmock/rbd_mock.py deleted file mode 100644 index 3b433ed5d74d2..0000000000000 --- a/src/pybind/mgr/dashboard_v2/cephmock/rbd_mock.py +++ /dev/null @@ -1,31 +0,0 @@ -# -*- coding: utf-8 -*- - - -RBD_FEATURE_LAYERING = "RBD_FEATURE_LAYERING" -RBD_FEATURE_STRIPINGV2 = "RBD_FEATURE_STRIPINGV2" -RBD_FEATURE_EXCLUSIVE_LOCK = "RBD_FEATURE_EXCLUSIVE_LOCK" -RBD_FEATURE_OBJECT_MAP = "RBD_FEATURE_OBJECT_MAP" -RBD_FEATURE_FAST_DIFF = "RBD_FEATURE_FAST_DIFF" -RBD_FEATURE_DEEP_FLATTEN = "RBD_FEATURE_DEEP_FLATTEN" -RBD_FEATURE_JOURNALING = "RBD_FEATURE_JOURNALING" -RBD_FEATURE_DATA_POOL = "RBD_FEATURE_DATA_POOL" -RBD_FEATURE_OPERATIONS = "RBD_FEATURE_OPERATIONS" - - -class RBD(object): - def __init__(self, *args, **kwargs): - pass - - def list(self, ioctx): - pass - - -class Image(object): - def __init__(self, *args, **kwargs): - pass - - def stat(self): - pass - - def features(self): - pass diff --git a/src/pybind/mgr/dashboard_v2/controllers/ping.py b/src/pybind/mgr/dashboard_v2/controllers/ping.py deleted file mode 100644 index d4a419a259ee0..0000000000000 --- a/src/pybind/mgr/dashboard_v2/controllers/ping.py +++ /dev/null @@ -1,27 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import - -import cherrypy - -from ..tools import ApiController, AuthRequired, RESTController, BaseController - - -@ApiController('ping') -@AuthRequired() -class Ping(BaseController): - @cherrypy.expose - def default(self): - return 'pong' - - -@ApiController('echo1') -class EchoArgs(RESTController): - @RESTController.args_from_json - def create(self, msg): - return {'echo': msg} - - -@ApiController('echo2') -class Echo(RESTController): - def create(self, data): - return {'echo': data['msg']} diff --git a/src/pybind/mgr/dashboard_v2/module.py b/src/pybind/mgr/dashboard_v2/module.py index 37cd99323a40d..6c48e37cb6515 100644 --- a/src/pybind/mgr/dashboard_v2/module.py +++ b/src/pybind/mgr/dashboard_v2/module.py @@ -48,6 +48,12 @@ class Module(MgrModule): 'name=password,type=CephString', 'desc': 'Set the login credentials', 'perm': 'w' + }, + { + 'cmd': 'dashboard set-session-expire ' + 'name=seconds,type=CephInt', + 'desc': 'Set the session expire timeout', + 'perm': 'w' } ] @@ -60,7 +66,7 @@ class Module(MgrModule): logger.logger = self._logger self._url_prefix = '' - def configure_cherrypy(self, in_unittest=False): + def configure_cherrypy(self): server_addr = self.get_localized_config('server_addr', '::') server_port = self.get_localized_config('server_port', '8080') if server_addr is None: @@ -87,14 +93,11 @@ class Module(MgrModule): # Apply the 'global' CherryPy configuration. config = { - 'engine.autoreload.on': False + 'engine.autoreload.on': False, + 'server.socket_host': server_addr, + 'server.socket_port': int(server_port), + 'error_page.default': json_error_page } - if not in_unittest: - config.update({ - 'server.socket_host': server_addr, - 'server.socket_port': int(server_port), - 'error_page.default': json_error_page - }) cherrypy.config.update(config) current_dir = os.path.dirname(os.path.abspath(__file__)) @@ -135,6 +138,9 @@ class Module(MgrModule): if cmd['prefix'] == 'dashboard set-login-credentials': Auth.set_login_credentials(cmd['username'], cmd['password']) return 0, 'Username and password updated', '' + elif cmd['prefix'] == 'dashboard set-session-expire': + self.set_config('session-expire', str(cmd['seconds'])) + return 0, 'Session expiration timeout updated', '' return (-errno.EINVAL, '', 'Command not found \'{0}\'' .format(cmd['prefix'])) diff --git a/src/pybind/mgr/dashboard_v2/requirements.txt b/src/pybind/mgr/dashboard_v2/requirements.txt index 04704c0751ead..f6191ea385725 100644 --- a/src/pybind/mgr/dashboard_v2/requirements.txt +++ b/src/pybind/mgr/dashboard_v2/requirements.txt @@ -23,6 +23,7 @@ pytest==3.3.2 pytest-cov==2.5.1 python-bcrypt==0.3.2 pytz==2017.3 +requests==2.18.4 singledispatch==3.4.0.3 six==1.11.0 tempora==1.10 diff --git a/src/pybind/mgr/dashboard_v2/tests/helper.py b/src/pybind/mgr/dashboard_v2/tests/helper.py index 2686022133751..53b9068f39af5 100644 --- a/src/pybind/mgr/dashboard_v2/tests/helper.py +++ b/src/pybind/mgr/dashboard_v2/tests/helper.py @@ -2,27 +2,48 @@ # pylint: disable=W0212 from __future__ import absolute_import -import json +import os +import subprocess +import sys +import unittest -from cherrypy.test import helper -from more_itertools import always_iterable +import requests -from ..module import Module +def authenticate(func): + def decorate(self, *args, **kwargs): + self._ceph_cmd(['dashboard', 'set-login-credentials', 'admin', 'admin']) + self._post('/api/auth', {'username': 'admin', 'password': 'admin'}) + self.assertStatus(201) + return func(self, *args, **kwargs) + return decorate + + +class ControllerTestCase(unittest.TestCase): + DASHBOARD_HOST = os.environ.get('DASHBOARD_V2_HOST', "localhost") + DASHBOARD_PORT = os.environ.get('DASHBOARD_V2_PORT', 8080) + + def __init__(self, *args, **kwargs): + super(ControllerTestCase, self).__init__(*args, **kwargs) + self._session = requests.Session() + self._resp = None -class RequestHelper(object): 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) + url = "http://{}:{}{}".format(self.DASHBOARD_HOST, self.DASHBOARD_PORT, + url) + if method == 'GET': + self._resp = self._session.get(url) + return self._resp.json() + elif method == 'POST': + self._resp = self._session.post(url, json=data) + elif method == 'DELETE': + self._resp = self._session.delete(url, json=data) + elif method == 'PUT': + self._resp = self._session.put(url, json=data) + return None def _get(self, url): - self._request(url, 'GET') + return self._request(url, 'GET') def _post(self, url, data=None): self._request(url, 'POST', data) @@ -33,25 +54,49 @@ class RequestHelper(object): def _put(self, url, data=None): self._request(url, 'PUT', data) - def assertJsonBody(self, data, msg=None): - """Fail if value != self.body.""" - body_str = self.body.decode('utf-8') if isinstance(self.body, bytes) else self.body - json_body = json.loads(body_str) - 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 cookies(self): + return self._resp.cookies + + def jsonBody(self): + return self._resp.json() + + def reset_session(self): + self._session = requests.Session() + + def assertJsonBody(self, data): + body = self._resp.json() + self.assertEqual(body, data) + + def assertBody(self, body): + self.assertEqual(self._resp.text, body) + def assertStatus(self, status): + self.assertEqual(self._resp.status_code, status) + + @classmethod + def _cmd(cls, cmd): + if sys.version_info > (3, 0): + res = subprocess.run(cmd, stdout=subprocess.PIPE).stdout + else: + res = subprocess.Popen(cmd, stdout=subprocess.PIPE).communicate()[0] + return res.decode('utf-8').strip() + + @classmethod + def _ceph_cmd(cls, cmd): + _cmd = ['ceph'] + _cmd.extend(cmd) + return cls._cmd(_cmd) + + @classmethod + def set_config_key(cls, key, value): + cls._ceph_cmd(['config-key', 'set', key, value]) -class ControllerTestCase(helper.CPWebCase, RequestHelper): @classmethod - def setup_server(cls): - module = Module('dashboard', None, None) - cls._mgr_module = module - module.configure_cherrypy(True) - cls.setup_test() + def get_config_key(cls, key): + return cls._ceph_cmd(['config-key', 'get', key]) @classmethod - def setup_test(cls): - pass + def _rbd_cmd(cls, cmd): + _cmd = ['rbd'] + _cmd.extend(cmd) + return cls._cmd(_cmd) diff --git a/src/pybind/mgr/dashboard_v2/tests/test_auth.py b/src/pybind/mgr/dashboard_v2/tests/test_auth.py index 88eacc44a2fed..63000ac206077 100644 --- a/src/pybind/mgr/dashboard_v2/tests/test_auth.py +++ b/src/pybind/mgr/dashboard_v2/tests/test_auth.py @@ -4,120 +4,71 @@ from __future__ import absolute_import import time -import cherrypy -from cherrypy.lib.sessions import RamSession -from mock import patch - from .helper import ControllerTestCase -from ..controllers.auth import Auth from ..tools import Session -class Ping(object): - @cherrypy.expose - @cherrypy.tools.allow(methods=['POST']) - def ping(self): - pass - - class AuthTest(ControllerTestCase): - @classmethod - def setup_test(cls): - cherrypy.tree.mount(Ping(), "/api/test", - config={'/': {'tools.authenticate.on': True}}) - cls._mgr_module.set_config('session-expire', '2') - cls._mgr_module.set_config('username', 'admin') - cls._mgr_module.set_config('password', Auth.password_hash('admin')) - def setUp(self): - self._mgr_module.set_config('session-expire', '2') - self._mgr_module.set_config('username', 'admin') - self._mgr_module.set_config('password', Auth.password_hash('admin')) + self.reset_session() + self._ceph_cmd(['dashboard', 'set-session-expire', '2']) + self._ceph_cmd(['dashboard', 'set-login-credentials', 'admin', 'admin']) def test_a_set_login_credentials(self): - Auth.set_login_credentials('admin2', 'admin2') - user = self._mgr_module.get_config('username') - passwd = self._mgr_module.get_config('password') - self.assertEqual(user, 'admin2') - self.assertEqual(passwd, Auth.password_hash('admin2', passwd)) + self._ceph_cmd(['dashboard', 'set-login-credentials', 'admin2', 'admin2']) + self._post("/api/auth", {'username': 'admin2', 'password': 'admin2'}) + self.assertStatus(201) + self.assertJsonBody({"username": "admin2"}) def test_login_valid(self): - sess_mock = RamSession() - with patch('cherrypy.session', sess_mock, create=True): - self._post("/api/auth", {'username': 'admin', 'password': 'admin'}) - self.assertStatus('201 Created') - self.assertJsonBody({"username": "admin"}) - self.assertEqual(sess_mock.get(Session.USERNAME), 'admin') + self._post("/api/auth", {'username': 'admin', 'password': 'admin'}) + self.assertStatus(201) + self.assertJsonBody({"username": "admin"}) def test_login_stay_signed_in(self): - sess_mock = RamSession() - with patch('cherrypy.session', sess_mock, create=True): - self._post("/api/auth", { - 'username': 'admin', - 'password': 'admin', - 'stay_signed_in': True}) - self.assertStatus('201 Created') - self.assertEqual(sess_mock.get( - Session.EXPIRE_AT_BROWSER_CLOSE), False) - for _, content in self.cookies: - parts = map(str.strip, content.split(';')) - parts = {k: v for k, v in (part.split('=') for part in parts)} - if Session.NAME in parts: - self.assertIn('expires', parts) - self.assertIn('Max-Age', parts) + self._post("/api/auth", { + 'username': 'admin', + 'password': 'admin', + 'stay_signed_in': True}) + self.assertStatus(201) + self.assertIn(Session.NAME, self.cookies()) + for cookie in self.cookies(): + if cookie.name == Session.NAME: + self.assertIsNotNone(cookie.expires) def test_login_not_stay_signed_in(self): - sess_mock = RamSession() - with patch('cherrypy.session', sess_mock, create=True): - self._post("/api/auth", { - 'username': 'admin', - 'password': 'admin', - 'stay_signed_in': False}) - self.assertStatus('201 Created') - self.assertEqual(sess_mock.get( - Session.EXPIRE_AT_BROWSER_CLOSE), True) - for _, content in self.cookies: - parts = map(str.strip, content.split(';')) - parts = {k: v for k, v in (part.split('=') for part in parts)} - if Session.NAME in parts: - self.assertNotIn('expires', parts) - self.assertNotIn('Max-Age', parts) + self._post("/api/auth", { + 'username': 'admin', + 'password': 'admin', + 'stay_signed_in': False}) + self.assertStatus(201) + self.assertIn(Session.NAME, self.cookies()) + for cookie in self.cookies(): + if cookie.name == Session.NAME: + self.assertIsNone(cookie.expires) def test_login_invalid(self): - sess_mock = RamSession() - with patch('cherrypy.session', sess_mock, create=True): - self._post("/api/auth", {'username': 'admin', 'password': 'inval'}) - self.assertStatus('403 Forbidden') - self.assertJsonBody({"detail": "Invalid credentials"}) - self.assertEqual(sess_mock.get(Session.USERNAME), None) + self._post("/api/auth", {'username': 'admin', 'password': 'inval'}) + self.assertStatus(403) + self.assertJsonBody({"detail": "Invalid credentials"}) def test_logout(self): - sess_mock = RamSession() - with patch('cherrypy.session', sess_mock, create=True): - self._post("/api/auth", {'username': 'admin', 'password': 'admin'}) - self.assertEqual(sess_mock.get(Session.USERNAME), 'admin') - self._delete("/api/auth") - self.assertStatus('204 No Content') - self.assertBody('') - self.assertEqual(sess_mock.get(Session.USERNAME), None) + self._post("/api/auth", {'username': 'admin', 'password': 'admin'}) + self._delete("/api/auth") + self.assertStatus(204) + self.assertBody('') + self._get("/api/host") + self.assertStatus(401) def test_session_expire(self): - sess_mock = RamSession() - with patch('cherrypy.session', sess_mock, create=True): - self._post("/api/auth", {'username': 'admin', 'password': 'admin'}) - self.assertStatus('201 Created') - self.assertEqual(sess_mock.get(Session.USERNAME), 'admin') - self._post("/api/test/ping") - self.assertStatus('200 OK') - self.assertEqual(sess_mock.get(Session.USERNAME), 'admin') - time.sleep(3) - self._post("/api/test/ping") - self.assertStatus('401 Unauthorized') - self.assertEqual(sess_mock.get(Session.USERNAME), None) + self._post("/api/auth", {'username': 'admin', 'password': 'admin'}) + self.assertStatus(201) + self._get("/api/host") + self.assertStatus(200) + time.sleep(3) + self._get("/api/host") + self.assertStatus(401) def test_unauthorized(self): - sess_mock = RamSession() - with patch('cherrypy.session', sess_mock, create=True): - self._post("/api/test/ping") - self.assertStatus('401 Unauthorized') - self.assertEqual(sess_mock.get(Session.USERNAME), None) + self._get("/api/host") + self.assertStatus(401) diff --git a/src/pybind/mgr/dashboard_v2/tests/test_host.py b/src/pybind/mgr/dashboard_v2/tests/test_host.py new file mode 100644 index 0000000000000..572643502c3b5 --- /dev/null +++ b/src/pybind/mgr/dashboard_v2/tests/test_host.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +from __future__ import absolute_import + +from .helper import ControllerTestCase, authenticate + + +class HostControllerTest(ControllerTestCase): + + @authenticate + def test_host_list(self): + data = self._get('/api/host') + self.assertStatus(200) + + self.assertEqual(len(data), 1) + data = data[0] + self.assertIn('services', data) + self.assertIn('hostname', data) + self.assertIn('ceph_version', data) + self.assertIsNotNone(data['hostname']) + self.assertIsNotNone(data['ceph_version']) + self.assertGreaterEqual(len(data['services']), 1) + for service in data['services']: + self.assertIn('type', service) + self.assertIn('id', service) + self.assertIsNotNone(service['type']) + self.assertIsNotNone(service['id']) diff --git a/src/pybind/mgr/dashboard_v2/tests/test_module.py b/src/pybind/mgr/dashboard_v2/tests/test_module.py deleted file mode 100644 index 3edc3a5f7d2e5..0000000000000 --- a/src/pybind/mgr/dashboard_v2/tests/test_module.py +++ /dev/null @@ -1,32 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import - -import errno - -from .helper import ControllerTestCase -from ..controllers.auth import Auth - - -class ModuleTest(ControllerTestCase): - - def test_ping(self): - self._get("/api/ping") - self.assertStatus("401 Unauthorized") - - def test_set_login_credentials_cmd(self): - ret, _, _ = self._mgr_module.handle_command({ - 'prefix': 'dashboard set-login-credentials', - 'username': 'admin', - 'password': 'admin' - }) - self.assertEqual(ret, 0) - user = self._mgr_module.get_localized_config('username') - passwd = self._mgr_module.get_localized_config('password') - self.assertEqual(user, 'admin') - self.assertEqual(passwd, Auth.password_hash('admin', passwd)) - - def test_cmd_not_found(self): - ret, _, _ = self._mgr_module.handle_command({ - 'prefix': 'dashboard non-command' - }) - self.assertEqual(ret, -errno.EINVAL) diff --git a/src/pybind/mgr/dashboard_v2/tests/test_ping.py b/src/pybind/mgr/dashboard_v2/tests/test_ping.py deleted file mode 100644 index c382b0232f306..0000000000000 --- a/src/pybind/mgr/dashboard_v2/tests/test_ping.py +++ /dev/null @@ -1,27 +0,0 @@ -# -*- coding: utf-8 -*- -# pylint: disable=W0212 - -from __future__ import absolute_import - -from .helper import ControllerTestCase -from ..controllers.ping import Ping - - -class PingTest(ControllerTestCase): - @classmethod - def setup_test(cls): - Ping._cp_config['tools.authenticate.on'] = False - - def test_ping(self): - self.getPage("/api/ping") - self.assertStatus('200 OK') - - def test_echo(self): - self._post("/api/echo2", {'msg': 'Hello World'}) - self.assertStatus('201 Created') - self.assertJsonBody({'echo': 'Hello World'}) - - def test_echo_args(self): - self._post("/api/echo1", {'msg': 'Hello World'}) - self.assertStatus('201 Created') - self.assertJsonBody({'echo': 'Hello World'}) diff --git a/src/pybind/mgr/dashboard_v2/tests/test_rgw.py b/src/pybind/mgr/dashboard_v2/tests/test_rgw.py index aac4798c74e08..cc84de37572da 100644 --- a/src/pybind/mgr/dashboard_v2/tests/test_rgw.py +++ b/src/pybind/mgr/dashboard_v2/tests/test_rgw.py @@ -1,65 +1,30 @@ -from mock import Mock -import cherrypy -from ..controllers.rgw import RgwDaemon -from .helper import ControllerTestCase +# -*- coding: utf-8 -*- +from __future__ import absolute_import -mocked_servers = [{ - 'ceph_version': 'ceph version 13.0.0-5083-g8d1965af24 ' + - '(8d1965af241a5a5487e1b2e3684c676c47392be9) ' + - 'mimic (dev)', - 'hostname': 'ceph-dev', - 'services': [{'id': 'a', 'type': 'mds'}, - {'id': 'b', 'type': 'mds'}, - {'id': 'c', 'type': 'mds'}, - {'id': 'a', 'type': 'mon'}, - {'id': 'b', 'type': 'mon'}, - {'id': 'c', 'type': 'mon'}, - {'id': '0', 'type': 'osd'}, - {'id': '1', 'type': 'osd'}, - {'id': '2', 'type': 'osd'}, - {'id': 'rgw', 'type': 'rgw'}]}] - -mocked_metadata = { - 'arch': 'x86_64', - 'ceph_version': 'ceph version 13.0.0-5083-g8d1965af24 ' + - '(8d1965af241a5a5487e1b2e3684c676c47392be9) mimic (dev)', - 'cpu': 'Intel(R) Core(TM)2 Quad CPU Q9550 @ 2.83GHz', - 'distro': 'opensuse', - 'distro_description': 'openSUSE Tumbleweed', - 'distro_version': '20180202', - 'frontend_config#0': 'civetweb port=8000', - 'frontend_type#0': 'civetweb', - 'hostname': 'ceph-dev', - 'kernel_description': '#135-Ubuntu SMP Fri Jan 19 11:48:36 UTC 2018', - 'kernel_version': '4.4.0-112-generic', - 'mem_swap_kb': '6287356', - 'mem_total_kb': '8173856', - 'num_handles': '1', - 'os': 'Linux', - 'pid': '979', - 'zone_id': '850fbec4-9238-481d-9044-c8048f4d582e', - 'zone_name': 'default', - 'zonegroup_id': '47e10f8e-5801-4d4d-802c-fc32a252c0ba', - 'zonegroup_name': 'default'} +from .helper import ControllerTestCase, authenticate class RgwControllerTest(ControllerTestCase): - @classmethod - def setup_test(cls): - mgr_mock = Mock() - mgr_mock.list_servers.return_value = mocked_servers - mgr_mock.get_metadata.return_value = mocked_metadata - mgr_mock.get_daemon_status.return_value = {'current_sync': []} - mgr_mock.url_prefix = '' + @authenticate + def test_rgw_daemon_list(self): + data = self._get('/api/rgw/daemon') + self.assertStatus(200) - cherrypy.tree.mount(RgwDaemon(mgr_mock), "/api/test/rgw") + self.assertEqual(len(data), 1) + data = data[0] + self.assertIn('id', data) + self.assertIn('version', data) + self.assertIn('server_hostname', data) - def test_list(self): - self._get('/api/test/rgw') + @authenticate + def test_rgw_daemon_get(self): + data = self._get('/api/rgw/daemon') self.assertStatus(200) - self.assertJsonBody([{ - 'id': 'rgw', - 'version': mocked_servers[0]['ceph_version'], - 'server_hostname': mocked_servers[0]['hostname'] - }]) + data = self._get('/api/rgw/daemon/{}'.format(data[0]['id'])) + self.assertStatus(200) + + self.assertIn('rgw_metadata', data) + self.assertIn('rgw_id', data) + self.assertIn('rgw_status', data) + self.assertTrue(data['rgw_metadata']) diff --git a/src/pybind/mgr/dashboard_v2/tests/test_tools.py b/src/pybind/mgr/dashboard_v2/tests/test_tools.py index d108c58fa0d37..8a868b95611c3 100644 --- a/src/pybind/mgr/dashboard_v2/tests/test_tools.py +++ b/src/pybind/mgr/dashboard_v2/tests/test_tools.py @@ -1,12 +1,13 @@ # -*- coding: utf-8 -*- from __future__ import absolute_import +import json + import cherrypy from cherrypy.lib.sessions import RamSession from cherrypy.test import helper from mock import patch -from .helper import RequestHelper from ..tools import RESTController, detail_route @@ -51,7 +52,39 @@ class Root(object): fooargs = FooArgs() -class RESTControllerTest(helper.CPWebCase, RequestHelper): +class RESTControllerTest(helper.CPWebCase): + 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 assertJsonBody(self, data, msg=None): + """Fail if value != self.body.""" + body_str = self.body.decode('utf-8') if isinstance(self.body, bytes) else self.body + json_body = json.loads(body_str) + if data != json_body: + if msg is None: + msg = 'expected body:\n%r\n\nactual body:\n%r' % ( + data, json_body) + self._handlewebError(msg) + @classmethod def setup_server(cls): cherrypy.tree.mount(Root()) diff --git a/src/pybind/mgr/dashboard_v2/tox.ini b/src/pybind/mgr/dashboard_v2/tox.ini index 6fcfcdacef732..3d1ad27e68c14 100644 --- a/src/pybind/mgr/dashboard_v2/tox.ini +++ b/src/pybind/mgr/dashboard_v2/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = cov-init,py27,py3,cov-report,lint +envlist = cov-init,ceph-cluster-start,py27,py3,ceph-cluster-stop,cov-report,lint skipsdist = true [testenv] @@ -10,6 +10,8 @@ setenv= COVERAGE_FILE= .coverage.{envname} PYTHONPATH = {toxinidir}/../../../../build/lib/cython_modules/lib.3:{toxinidir}/../../../../build/lib/cython_modules/lib.2 LD_LIBRARY_PATH = {toxinidir}/../../../../build/lib + PATH = {toxinidir}/../../../../build/bin:$PATH + DASHBOARD_V2_PORT=9865 commands= {envbindir}/py.test --cov=. --cov-report= --junitxml=junit.{envname}.xml tests/ @@ -20,6 +22,41 @@ deps = coverage commands = coverage erase +[testenv:ceph-cluster-start] +deps=coverage +changedir={toxinidir}/../../../../build +whitelist_externals= + bash + cp + sleep +setenv= + COVERAGE_ENABLED=true + COVERAGE_FILE=.coverage.mgr + RGW=1 +commands = + bash ../src/vstart.sh -n + sleep 10 + python ./bin/ceph config-key set mgr/dashboard_v2/x/server_port 9865 + python ./bin/ceph mgr module enable dashboard_v2 + cp ceph.conf {toxinidir}/ + sleep 20 + +[testenv:ceph-cluster-stop] +deps= +changedir={toxinidir}/../../../../build +whitelist_externals= + bash + rm + mv + killall + sleep +commands = + killall ceph-mgr + sleep 5 + bash ../src/stop.sh + rm {toxinidir}/ceph.conf + mv {toxinidir}/../../../../build/.coverage.mgr {toxinidir}/ + [testenv:cov-report] setenv = COVERAGE_FILE = .coverage @@ -35,5 +72,5 @@ setenv = LD_LIBRARY_PATH = {toxinidir}/../../../../build/lib deps=-r{toxinidir}/requirements.txt commands= - pylint --rcfile=.pylintrc --jobs=5 . module.py tools.py controllers tests cephmock - pycodestyle --max-line-length=100 --exclude=python2.7,.tox,venv,frontend . + pylint --rcfile=.pylintrc --jobs=5 . module.py tools.py controllers tests + pycodestyle --max-line-length=100 --exclude=python2.7,.tox,venv,frontend --ignore=E402,E121,E123,E126,E226,E24,E704,W503 . -- 2.39.5