summaryrefslogtreecommitdiffstats
path: root/src/pybind/mgr/dashboard/tests/test_rbd_mirroring.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/pybind/mgr/dashboard/tests/test_rbd_mirroring.py')
-rw-r--r--src/pybind/mgr/dashboard/tests/test_rbd_mirroring.py318
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)