]> git.proxmox.com Git - ceph.git/blob - ceph/src/pybind/mgr/dashboard/tests/test_rbd_mirroring.py
60571d8e5543f2ec4a9be4c62a516babff2702d2
[ceph.git] / ceph / src / pybind / mgr / dashboard / tests / test_rbd_mirroring.py
1
2 import json
3 import unittest
4
5 import rbd
6
7 try:
8 import mock
9 except ImportError:
10 import unittest.mock as mock
11
12 from .. import mgr
13 from ..controllers.orchestrator import Orchestrator
14 from ..controllers.rbd_mirroring import RbdMirroring, \
15 RbdMirroringPoolBootstrap, RbdMirroringStatus, RbdMirroringSummary, \
16 get_daemons, get_pools
17 from ..controllers.summary import Summary
18 from ..services import progress
19 from ..tests import ControllerTestCase
20
21 mock_list_servers = [{
22 'hostname': 'ceph-host',
23 'services': [{'id': 3, 'type': 'rbd-mirror'}]
24 }]
25
26 mock_get_metadata = {
27 'id': 1,
28 'instance_id': 3,
29 'ceph_version': 'ceph version 13.0.0-5719 mimic (dev)'
30 }
31
32 _status = {
33 1: {
34 'callouts': {},
35 'image_local_count': 5,
36 'image_remote_count': 6,
37 'image_error_count': 7,
38 'image_warning_count': 8,
39 'name': 'rbd'
40 }
41 }
42
43 mock_get_daemon_status = {
44 'json': json.dumps(_status)
45 }
46
47 mock_osd_map = {
48 'pools': [{
49 'pool_name': 'rbd',
50 'application_metadata': {'rbd'}
51 }]
52 }
53
54
55 class GetDaemonAndPoolsTest(unittest.TestCase):
56 @classmethod
57 def setUpClass(cls):
58 mgr.list_servers.return_value = mock_list_servers
59 mgr.get_metadata = mock.Mock(return_value=mock_get_metadata)
60 mgr.get_daemon_status.return_value = mock_get_daemon_status
61 mgr.get.side_effect = lambda key: {
62 'osd_map': mock_osd_map,
63 'health': {'json': '{"status": 1}'},
64 'fs_map': {'filesystems': []},
65 'mgr_map': {
66 'services': {
67 'dashboard': 'https://ceph.dev:11000/'
68 },
69 }
70 }[key]
71 mgr.url_prefix = ''
72 mgr.get_mgr_id.return_value = 0
73 mgr.have_mon_connection.return_value = True
74 mgr.version = 'ceph version 13.1.0-534-g23d3751b89 ' \
75 '(23d3751b897b31d2bda57aeaf01acb5ff3c4a9cd) ' \
76 'nautilus (dev)'
77
78 progress.get_progress_tasks = mock.MagicMock()
79 progress.get_progress_tasks.return_value = ([], [])
80
81 @mock.patch('rbd.RBD')
82 def test_get_pools_unknown(self, mock_rbd):
83 mock_rbd_instance = mock_rbd.return_value
84 mock_rbd_instance.mirror_mode_get.side_effect = Exception
85 daemons = get_daemons()
86 res = get_pools(daemons)
87 self.assertTrue(res['rbd']['mirror_mode'] == "unknown")
88
89 @mock.patch('rbd.RBD')
90 def test_get_pools_mode(self, mock_rbd):
91
92 daemons = get_daemons()
93 mock_rbd_instance = mock_rbd.return_value
94 testcases = [
95 (rbd.RBD_MIRROR_MODE_DISABLED, "disabled"),
96 (rbd.RBD_MIRROR_MODE_IMAGE, "image"),
97 (rbd.RBD_MIRROR_MODE_POOL, "pool"),
98 ]
99 mock_rbd_instance.mirror_peer_list.return_value = []
100 for mirror_mode, expected in testcases:
101 mock_rbd_instance.mirror_mode_get.return_value = mirror_mode
102 res = get_pools(daemons)
103 self.assertTrue(res['rbd']['mirror_mode'] == expected)
104
105 @mock.patch('rbd.RBD')
106 def test_get_pools_health(self, mock_rbd):
107
108 mock_rbd_instance = mock_rbd.return_value
109 mock_rbd_instance.mirror_peer_list.return_value = []
110 test_cases = self._get_pool_test_cases()
111 for new_status, mirror_mode, expected_output in test_cases:
112 _status[1].update(new_status)
113 daemon_status = {
114 'json': json.dumps(_status)
115 }
116 mgr.get_daemon_status.return_value = daemon_status
117 daemons = get_daemons()
118 mock_rbd_instance.mirror_mode_get.return_value = mirror_mode
119 res = get_pools(daemons)
120 for k, v in expected_output.items():
121 self.assertTrue(v == res['rbd'][k])
122 mgr.get_daemon_status.return_value = mock_get_daemon_status # reset return value
123
124 def _get_pool_test_cases(self):
125 test_cases = [
126 (
127 {
128 'image_error_count': 7,
129 },
130 rbd.RBD_MIRROR_MODE_IMAGE,
131 {
132 'health_color': 'warning',
133 'health': 'Warning'
134 }
135 ),
136 (
137 {
138 'image_error_count': 7,
139 },
140 rbd.RBD_MIRROR_MODE_DISABLED,
141 {
142 'health_color': 'error',
143 'health': 'Error'
144 }
145 ),
146 (
147 {
148 'image_error_count': 0,
149 'image_warning_count': 0,
150 'leader_id': 1
151 },
152 rbd.RBD_MIRROR_MODE_DISABLED,
153 {
154 'health_color': 'info',
155 'health': 'Disabled'
156 }
157 ),
158 ]
159 return test_cases
160
161
162 class RbdMirroringControllerTest(ControllerTestCase):
163
164 @classmethod
165 def setup_server(cls):
166 cls.setup_controllers([RbdMirroring])
167
168 @mock.patch('dashboard.controllers.rbd_mirroring.rbd.RBD')
169 def test_site_name(self, mock_rbd):
170 result = {'site_name': 'fsid'}
171 mock_rbd_instance = mock_rbd.return_value
172 mock_rbd_instance.mirror_site_name_get.return_value = \
173 result['site_name']
174
175 self._get('/api/block/mirroring/site_name')
176 self.assertStatus(200)
177 self.assertJsonBody(result)
178
179 result['site_name'] = 'site-a'
180 mock_rbd_instance.mirror_site_name_get.return_value = \
181 result['site_name']
182 self._put('/api/block/mirroring/site_name', result)
183 self.assertStatus(200)
184 self.assertJsonBody(result)
185 mock_rbd_instance.mirror_site_name_set.assert_called_with(
186 mock.ANY, result['site_name'])
187
188
189 class RbdMirroringPoolBootstrapControllerTest(ControllerTestCase):
190
191 @classmethod
192 def setup_server(cls):
193 cls.setup_controllers([RbdMirroringPoolBootstrap])
194
195 @mock.patch('dashboard.controllers.rbd_mirroring.rbd.RBD')
196 def test_token(self, mock_rbd):
197 mock_rbd_instance = mock_rbd.return_value
198 mock_rbd_instance.mirror_peer_bootstrap_create.return_value = "1234"
199
200 self._post('/api/block/mirroring/pool/abc/bootstrap/token')
201 self.assertStatus(200)
202 self.assertJsonBody({"token": "1234"})
203 mgr.rados.open_ioctx.assert_called_with("abc")
204
205 mock_rbd_instance.mirror_peer_bootstrap_create.assert_called()
206
207 @mock.patch('dashboard.controllers.rbd_mirroring.rbd')
208 def test_peer(self, mock_rbd_module):
209 mock_rbd_instance = mock_rbd_module.RBD.return_value
210
211 values = {
212 "direction": "invalid",
213 "token": "1234"
214 }
215 self._post('/api/block/mirroring/pool/abc/bootstrap/peer', values)
216 self.assertStatus(500)
217 mgr.rados.open_ioctx.assert_called_with("abc")
218
219 values["direction"] = "rx"
220 self._post('/api/block/mirroring/pool/abc/bootstrap/peer', values)
221 self.assertStatus(200)
222 self.assertJsonBody({})
223 mgr.rados.open_ioctx.assert_called_with("abc")
224
225 mock_rbd_instance.mirror_peer_bootstrap_import.assert_called_with(
226 mock.ANY, mock_rbd_module.RBD_MIRROR_PEER_DIRECTION_RX, '1234')
227
228
229 class RbdMirroringSummaryControllerTest(ControllerTestCase):
230
231 @classmethod
232 def setup_server(cls):
233 mgr.list_servers.return_value = mock_list_servers
234 mgr.get_metadata = mock.Mock(return_value=mock_get_metadata)
235 mgr.get_daemon_status.return_value = mock_get_daemon_status
236 mgr.get.side_effect = lambda key: {
237 'osd_map': mock_osd_map,
238 'health': {'json': '{"status": 1}'},
239 'fs_map': {'filesystems': []},
240 'mgr_map': {
241 'services': {
242 'dashboard': 'https://ceph.dev:11000/'
243 },
244 }
245 }[key]
246 mgr.url_prefix = ''
247 mgr.get_mgr_id.return_value = 0
248 mgr.have_mon_connection.return_value = True
249 mgr.version = 'ceph version 13.1.0-534-g23d3751b89 ' \
250 '(23d3751b897b31d2bda57aeaf01acb5ff3c4a9cd) ' \
251 'nautilus (dev)'
252
253 progress.get_progress_tasks = mock.MagicMock()
254 progress.get_progress_tasks.return_value = ([], [])
255
256 cls.setup_controllers([RbdMirroringSummary, Summary], '/test')
257
258 @mock.patch('dashboard.controllers.rbd_mirroring.rbd.RBD')
259 def test_default(self, mock_rbd):
260 mock_rbd_instance = mock_rbd.return_value
261 mock_rbd_instance.mirror_site_name_get.return_value = 'site-a'
262
263 self._get('/test/api/block/mirroring/summary')
264 result = self.json_body()
265 self.assertStatus(200)
266 self.assertEqual(result['site_name'], 'site-a')
267 self.assertEqual(result['status'], 0)
268 for k in ['daemons', 'pools', 'image_error', 'image_syncing', 'image_ready']:
269 self.assertIn(k, result['content_data'])
270
271 @mock.patch('dashboard.controllers.BaseController._has_permissions')
272 @mock.patch('dashboard.controllers.rbd_mirroring.rbd.RBD')
273 def test_summary(self, mock_rbd, has_perms_mock):
274 """We're also testing `summary`, as it also uses code from `rbd_mirroring.py`"""
275 mock_rbd_instance = mock_rbd.return_value
276 mock_rbd_instance.mirror_site_name_get.return_value = 'site-a'
277
278 has_perms_mock.return_value = True
279 self._get('/test/api/summary')
280 self.assertStatus(200)
281
282 summary = self.json_body()['rbd_mirroring']
283 self.assertEqual(summary, {'errors': 0, 'warnings': 1})
284
285
286 class RbdMirroringStatusControllerTest(ControllerTestCase):
287
288 @classmethod
289 def setup_server(cls):
290 cls.setup_controllers([RbdMirroringStatus, Orchestrator])
291
292 @mock.patch('dashboard.controllers.orchestrator.OrchClient.instance')
293 def test_status(self, instance):
294 status = {'available': False, 'description': ''}
295 fake_client = mock.Mock()
296 fake_client.status.return_value = status
297 instance.return_value = fake_client
298
299 self._get('/ui-api/block/mirroring/status')
300 self.assertStatus(200)
301 self.assertJsonBody({'available': True, 'message': None})
302
303 def test_configure(self):
304 self._post('/ui-api/block/mirroring/configure')
305 self.assertStatus(200)