diff options
Diffstat (limited to '')
-rw-r--r-- | src/pybind/mgr/dashboard/tests/test_rbd_mirroring.py | 318 |
1 files changed, 318 insertions, 0 deletions
diff --git a/src/pybind/mgr/dashboard/tests/test_rbd_mirroring.py b/src/pybind/mgr/dashboard/tests/test_rbd_mirroring.py new file mode 100644 index 000000000..fd36f681b --- /dev/null +++ b/src/pybind/mgr/dashboard/tests/test_rbd_mirroring.py @@ -0,0 +1,318 @@ + +import json +import unittest + +import rbd + +try: + import mock +except ImportError: + import unittest.mock as mock + +from .. import mgr +from ..controllers.orchestrator import Orchestrator +from ..controllers.rbd_mirroring import RbdMirroring, \ + RbdMirroringPoolBootstrap, RbdMirroringStatus, RbdMirroringSummary, \ + get_daemons, get_pools +from ..controllers.summary import Summary +from ..services import progress +from ..tests import ControllerTestCase + +mock_list_servers = [{ + 'hostname': 'ceph-host', + 'services': [{'id': 3, 'type': 'rbd-mirror'}] +}] + +mock_get_metadata = { + 'id': 1, + 'instance_id': 3, + 'ceph_version': 'ceph version 13.0.0-5719 mimic (dev)' +} + +_status = { + 1: { + 'callouts': { + 'image': { + 'level': 'warning', + } + }, + 'image_local_count': 5, + 'image_remote_count': 6, + 'image_error_count': 7, + 'image_warning_count': 8, + 'name': 'rbd' + } +} + +mock_get_daemon_status = { + 'json': json.dumps(_status) +} + +mock_osd_map = { + 'pools': [{ + 'pool_name': 'rbd', + 'application_metadata': {'rbd'} + }] +} + + +class GetDaemonAndPoolsTest(unittest.TestCase): + @classmethod + def setUpClass(cls): + mgr.list_servers.return_value = mock_list_servers + mgr.get_metadata = mock.Mock(return_value=mock_get_metadata) + mgr.get_daemon_status.return_value = mock_get_daemon_status + mgr.get.side_effect = lambda key: { + 'osd_map': mock_osd_map, + 'health': {'json': '{"status": 1}'}, + 'fs_map': {'filesystems': []}, + 'mgr_map': { + 'services': { + 'dashboard': 'https://ceph.dev:11000/' + }, + } + }[key] + mgr.url_prefix = '' + mgr.get_mgr_id.return_value = 0 + mgr.have_mon_connection.return_value = True + mgr.version = 'ceph version 13.1.0-534-g23d3751b89 ' \ + '(23d3751b897b31d2bda57aeaf01acb5ff3c4a9cd) ' \ + 'nautilus (dev)' + + progress.get_progress_tasks = mock.MagicMock() + progress.get_progress_tasks.return_value = ([], []) + + @mock.patch('rbd.RBD') + def test_get_pools_unknown(self, mock_rbd): + mock_rbd_instance = mock_rbd.return_value + mock_rbd_instance.mirror_mode_get.side_effect = Exception + daemons = get_daemons() + res = get_pools(daemons) + self.assertTrue(res['rbd']['mirror_mode'] == "unknown") + + @mock.patch('rbd.RBD') + def test_get_pools_mode(self, mock_rbd): + + daemons = get_daemons() + mock_rbd_instance = mock_rbd.return_value + testcases = [ + (rbd.RBD_MIRROR_MODE_DISABLED, "disabled"), + (rbd.RBD_MIRROR_MODE_IMAGE, "image"), + (rbd.RBD_MIRROR_MODE_POOL, "pool"), + ] + mock_rbd_instance.mirror_peer_list.return_value = [] + for mirror_mode, expected in testcases: + mock_rbd_instance.mirror_mode_get.return_value = mirror_mode + res = get_pools(daemons) + self.assertTrue(res['rbd']['mirror_mode'] == expected) + + @mock.patch('rbd.RBD') + def test_get_pools_health(self, mock_rbd): + + mock_rbd_instance = mock_rbd.return_value + mock_rbd_instance.mirror_peer_list.return_value = [] + test_cases = self._get_pool_test_cases() + for new_status, pool_mirror_mode, images_summary, expected_output in test_cases: + _status[1].update(new_status) + daemon_status = { + 'json': json.dumps(_status) + } + mgr.get_daemon_status.return_value = daemon_status + daemons = get_daemons() + mock_rbd_instance.mirror_mode_get.return_value = pool_mirror_mode + mock_rbd_instance.mirror_image_status_summary.return_value = images_summary + res = get_pools(daemons) + for k, v in expected_output.items(): + self.assertTrue(v == res['rbd'][k]) + mgr.get_daemon_status.return_value = mock_get_daemon_status # reset return value + + def _get_pool_test_cases(self): + test_cases = [ + # 1. daemon status + # 2. Pool mirror mock_get_daemon_status + # 3. Image health summary + # 4. Pool health output + ( + { + 'image_error_count': 7, + }, + rbd.RBD_MIRROR_MODE_IMAGE, + [(rbd.MIRROR_IMAGE_STATUS_STATE_UNKNOWN, None)], + { + 'health_color': 'warning', + 'health': 'Warning' + } + ), + ( + { + 'image_error_count': 7, + }, + rbd.RBD_MIRROR_MODE_POOL, + [(rbd.MIRROR_IMAGE_STATUS_STATE_ERROR, None)], + { + 'health_color': 'error', + 'health': 'Error' + } + ), + ( + { + 'image_error_count': 0, + 'image_warning_count': 0, + 'leader_id': 1 + }, + rbd.RBD_MIRROR_MODE_DISABLED, + [], + { + 'health_color': 'info', + 'health': 'Disabled' + } + ), + ] + return test_cases + + +class RbdMirroringControllerTest(ControllerTestCase): + + @classmethod + def setup_server(cls): + cls.setup_controllers([RbdMirroring]) + + @mock.patch('dashboard.controllers.rbd_mirroring.rbd.RBD') + def test_site_name(self, mock_rbd): + result = {'site_name': 'fsid'} + mock_rbd_instance = mock_rbd.return_value + mock_rbd_instance.mirror_site_name_get.return_value = \ + result['site_name'] + + self._get('/api/block/mirroring/site_name') + self.assertStatus(200) + self.assertJsonBody(result) + + result['site_name'] = 'site-a' + mock_rbd_instance.mirror_site_name_get.return_value = \ + result['site_name'] + self._put('/api/block/mirroring/site_name', result) + self.assertStatus(200) + self.assertJsonBody(result) + mock_rbd_instance.mirror_site_name_set.assert_called_with( + mock.ANY, result['site_name']) + + +class RbdMirroringPoolBootstrapControllerTest(ControllerTestCase): + + @classmethod + def setup_server(cls): + cls.setup_controllers([RbdMirroringPoolBootstrap]) + + @mock.patch('dashboard.controllers.rbd_mirroring.rbd.RBD') + def test_token(self, mock_rbd): + mock_rbd_instance = mock_rbd.return_value + mock_rbd_instance.mirror_peer_bootstrap_create.return_value = "1234" + + self._post('/api/block/mirroring/pool/abc/bootstrap/token') + self.assertStatus(200) + self.assertJsonBody({"token": "1234"}) + mgr.rados.open_ioctx.assert_called_with("abc") + + mock_rbd_instance.mirror_peer_bootstrap_create.assert_called() + + @mock.patch('dashboard.controllers.rbd_mirroring.rbd') + def test_peer(self, mock_rbd_module): + mock_rbd_instance = mock_rbd_module.RBD.return_value + + values = { + "direction": "invalid", + "token": "1234" + } + self._post('/api/block/mirroring/pool/abc/bootstrap/peer', values) + self.assertStatus(500) + mgr.rados.open_ioctx.assert_called_with("abc") + + values["direction"] = "rx" + self._post('/api/block/mirroring/pool/abc/bootstrap/peer', values) + self.assertStatus(200) + self.assertJsonBody({}) + mgr.rados.open_ioctx.assert_called_with("abc") + + mock_rbd_instance.mirror_peer_bootstrap_import.assert_called_with( + mock.ANY, mock_rbd_module.RBD_MIRROR_PEER_DIRECTION_RX, '1234') + + +class RbdMirroringSummaryControllerTest(ControllerTestCase): + + @classmethod + def setup_server(cls): + mgr.list_servers.return_value = mock_list_servers + mgr.get_metadata = mock.Mock(return_value=mock_get_metadata) + mgr.get_daemon_status.return_value = mock_get_daemon_status + mgr.get.side_effect = lambda key: { + 'osd_map': mock_osd_map, + 'health': {'json': '{"status": 1}'}, + 'fs_map': {'filesystems': []}, + 'mgr_map': { + 'services': { + 'dashboard': 'https://ceph.dev:11000/' + }, + } + }[key] + mgr.url_prefix = '' + mgr.get_mgr_id.return_value = 0 + mgr.have_mon_connection.return_value = True + mgr.version = 'ceph version 13.1.0-534-g23d3751b89 ' \ + '(23d3751b897b31d2bda57aeaf01acb5ff3c4a9cd) ' \ + 'nautilus (dev)' + + progress.get_progress_tasks = mock.MagicMock() + progress.get_progress_tasks.return_value = ([], []) + + cls.setup_controllers([RbdMirroringSummary, Summary], '/test') + + @mock.patch('dashboard.controllers.rbd_mirroring.rbd.RBD') + def test_default(self, mock_rbd): + mock_rbd_instance = mock_rbd.return_value + mock_rbd_instance.mirror_site_name_get.return_value = 'site-a' + + self._get('/test/api/block/mirroring/summary') + result = self.json_body() + self.assertStatus(200) + self.assertEqual(result['site_name'], 'site-a') + self.assertEqual(result['status'], 0) + for k in ['daemons', 'pools', 'image_error', 'image_syncing', 'image_ready']: + self.assertIn(k, result['content_data']) + + @mock.patch('dashboard.controllers.BaseController._has_permissions') + @mock.patch('dashboard.controllers.rbd_mirroring.rbd.RBD') + def test_summary(self, mock_rbd, has_perms_mock): + """We're also testing `summary`, as it also uses code from `rbd_mirroring.py`""" + mock_rbd_instance = mock_rbd.return_value + mock_rbd_instance.mirror_site_name_get.return_value = 'site-a' + + has_perms_mock.return_value = True + self._get('/test/api/summary') + self.assertStatus(200) + + summary = self.json_body()['rbd_mirroring'] + # 2 warnings: 1 for the daemon, 1 for the pool + self.assertEqual(summary, {'errors': 0, 'warnings': 2}) + + +class RbdMirroringStatusControllerTest(ControllerTestCase): + + @classmethod + def setup_server(cls): + cls.setup_controllers([RbdMirroringStatus, Orchestrator]) + + @mock.patch('dashboard.controllers.orchestrator.OrchClient.instance') + def test_status(self, instance): + status = {'available': False, 'description': ''} + fake_client = mock.Mock() + fake_client.status.return_value = status + instance.return_value = fake_client + + self._get('/ui-api/block/mirroring/status') + self.assertStatus(200) + self.assertJsonBody({'available': True, 'message': None}) + + def test_configure(self): + self._post('/ui-api/block/mirroring/configure') + self.assertStatus(200) |