]> git.proxmox.com Git - ceph.git/blame - ceph/src/pybind/mgr/dashboard/tests/test_host.py
update source to Ceph Pacific 16.2.2
[ceph.git] / ceph / src / pybind / mgr / dashboard / tests / test_host.py
CommitLineData
f67539c2 1import contextlib
9f95a23c 2import unittest
f67539c2
TL
3from typing import List, Optional
4from unittest import mock
9f95a23c 5
f67539c2 6from orchestrator import HostSpec, InventoryHost
9f95a23c 7
9f95a23c 8from .. import mgr
f67539c2
TL
9from ..controllers.host import Host, HostUi, get_device_osd_map, get_hosts, get_inventories
10from ..tools import NotificationQueue, TaskManager
11from . import ControllerTestCase # pylint: disable=no-name-in-module
12
13
14@contextlib.contextmanager
15def patch_orch(available: bool, hosts: Optional[List[HostSpec]] = None,
16 inventory: Optional[List[dict]] = None):
17 with mock.patch('dashboard.controllers.orchestrator.OrchClient.instance') as instance:
18 fake_client = mock.Mock()
19 fake_client.available.return_value = available
20 fake_client.get_missing_features.return_value = []
21
22 if hosts is not None:
23 fake_client.hosts.list.return_value = hosts
24
25 if inventory is not None:
26 def _list_inventory(hosts=None, refresh=False): # pylint: disable=unused-argument
27 inv_hosts = []
28 for inv_host in inventory:
29 if hosts is None or inv_host['name'] in hosts:
30 inv_hosts.append(InventoryHost.from_json(inv_host))
31 return inv_hosts
32 fake_client.inventory.list.side_effect = _list_inventory
33
34 instance.return_value = fake_client
35 yield fake_client
9f95a23c
TL
36
37
38class HostControllerTest(ControllerTestCase):
39 URL_HOST = '/api/host'
40
41 @classmethod
42 def setup_server(cls):
f67539c2
TL
43 NotificationQueue.start_queue()
44 TaskManager.init()
9f95a23c
TL
45 # pylint: disable=protected-access
46 Host._cp_config['tools.authenticate.on'] = False
47 cls.setup_controllers([Host])
48
f67539c2
TL
49 @classmethod
50 def tearDownClass(cls):
51 NotificationQueue.stop()
52
9f95a23c
TL
53 @mock.patch('dashboard.controllers.host.get_hosts')
54 def test_host_list(self, mock_get_hosts):
f6b5b4d7
TL
55 hosts = [{
56 'hostname': 'host-0',
57 'sources': {
58 'ceph': True,
59 'orchestrator': False
9f95a23c 60 }
f6b5b4d7
TL
61 }, {
62 'hostname': 'host-1',
63 'sources': {
64 'ceph': False,
65 'orchestrator': True
66 }
67 }, {
68 'hostname': 'host-2',
69 'sources': {
70 'ceph': True,
71 'orchestrator': True
72 }
73 }]
9f95a23c
TL
74
75 def _get_hosts(from_ceph=True, from_orchestrator=True):
76 _hosts = []
77 if from_ceph:
78 _hosts.append(hosts[0])
79 if from_orchestrator:
80 _hosts.append(hosts[1])
81 _hosts.append(hosts[2])
82 return _hosts
f6b5b4d7 83
9f95a23c
TL
84 mock_get_hosts.side_effect = _get_hosts
85
86 self._get(self.URL_HOST)
87 self.assertStatus(200)
88 self.assertJsonBody(hosts)
89
90 self._get('{}?sources=ceph'.format(self.URL_HOST))
91 self.assertStatus(200)
92 self.assertJsonBody([hosts[0]])
93
94 self._get('{}?sources=orchestrator'.format(self.URL_HOST))
95 self.assertStatus(200)
96 self.assertJsonBody(hosts[1:])
97
98 self._get('{}?sources=ceph,orchestrator'.format(self.URL_HOST))
99 self.assertStatus(200)
100 self.assertJsonBody(hosts)
101
f67539c2 102 def test_get_1(self):
f6b5b4d7 103 mgr.list_servers.return_value = []
9f95a23c 104
f67539c2
TL
105 with patch_orch(False):
106 self._get('{}/node1'.format(self.URL_HOST))
107 self.assertStatus(404)
f6b5b4d7 108
f67539c2 109 def test_get_2(self):
f6b5b4d7
TL
110 mgr.list_servers.return_value = [{'hostname': 'node1'}]
111
f67539c2
TL
112 with patch_orch(False):
113 self._get('{}/node1'.format(self.URL_HOST))
114 self.assertStatus(200)
115 self.assertIn('labels', self.json_body())
f6b5b4d7 116
f67539c2 117 def test_get_3(self):
f6b5b4d7
TL
118 mgr.list_servers.return_value = []
119
f67539c2
TL
120 with patch_orch(True, hosts=[HostSpec('node1')]):
121 self._get('{}/node1'.format(self.URL_HOST))
122 self.assertStatus(200)
123 self.assertIn('labels', self.json_body())
9f95a23c 124
f67539c2 125 def test_set_labels(self):
f6b5b4d7 126 mgr.list_servers.return_value = []
f67539c2 127 orch_hosts = [
f6b5b4d7
TL
128 HostSpec('node0', labels=['aaa', 'bbb'])
129 ]
f67539c2
TL
130 with patch_orch(True, hosts=orch_hosts) as fake_client:
131 fake_client.hosts.remove_label = mock.Mock()
132 fake_client.hosts.add_label = mock.Mock()
133
134 payload = {'update_labels': True, 'labels': ['bbb', 'ccc']}
135 self._put('{}/node0'.format(self.URL_HOST), payload)
136 self.assertStatus(200)
137 fake_client.hosts.remove_label.assert_called_once_with('node0', 'aaa')
138 fake_client.hosts.add_label.assert_called_once_with('node0', 'ccc')
139
140 # return 400 if type other than List[str]
141 self._put('{}/node0'.format(self.URL_HOST), {'update_labels': True,
142 'labels': 'ddd'})
143 self.assertStatus(400)
144
145 def test_host_maintenance(self):
146 mgr.list_servers.return_value = []
147 orch_hosts = [
148 HostSpec('node0'),
149 HostSpec('node1')
150 ]
151 with patch_orch(True, hosts=orch_hosts):
152 # enter maintenance mode
153 self._put('{}/node0'.format(self.URL_HOST), {'maintenance': True})
154 self.assertStatus(200)
155
156 # force enter maintenance mode
157 self._put('{}/node1'.format(self.URL_HOST), {'maintenance': True, 'force': True})
158 self.assertStatus(200)
159
160 # exit maintenance mode
161 self._put('{}/node0'.format(self.URL_HOST), {'maintenance': True})
162 self.assertStatus(200)
163 self._put('{}/node1'.format(self.URL_HOST), {'maintenance': True})
164 self.assertStatus(200)
165
166 # maintenance without orchestrator service
167 with patch_orch(False):
168 self._put('{}/node0'.format(self.URL_HOST), {'maintenance': True})
169 self.assertStatus(503)
170
171 @mock.patch('dashboard.controllers.host.time')
172 def test_identify_device(self, mock_time):
173 url = '{}/host-0/identify_device'.format(self.URL_HOST)
174 with patch_orch(True) as fake_client:
175 payload = {
176 'device': '/dev/sdz',
177 'duration': '1'
178 }
179 self._task_post(url, payload)
180 self.assertStatus(200)
181 mock_time.sleep.assert_called()
182 calls = [
183 mock.call('host-0', '/dev/sdz', 'ident', True),
184 mock.call('host-0', '/dev/sdz', 'ident', False),
185 ]
186 fake_client.blink_device_light.assert_has_calls(calls)
187
188 @mock.patch('dashboard.controllers.host.get_inventories')
189 def test_inventory(self, mock_get_inventories):
190 inventory_url = '{}/host-0/inventory'.format(self.URL_HOST)
191 with patch_orch(True):
192 tests = [
193 {
194 'url': inventory_url,
195 'inventories': [{'a': 'b'}],
196 'refresh': None,
197 'resp': {'a': 'b'}
198 },
199 {
200 'url': '{}?refresh=true'.format(inventory_url),
201 'inventories': [{'a': 'b'}],
202 'refresh': "true",
203 'resp': {'a': 'b'}
204 },
205 {
206 'url': inventory_url,
207 'inventories': [],
208 'refresh': None,
209 'resp': {}
210 },
211 ]
212 for test in tests:
213 mock_get_inventories.reset_mock()
214 mock_get_inventories.return_value = test['inventories']
215 self._get(test['url'])
216 mock_get_inventories.assert_called_once_with(['host-0'], test['refresh'])
217 self.assertEqual(self.json_body(), test['resp'])
218 self.assertStatus(200)
f6b5b4d7 219
f67539c2
TL
220 # list without orchestrator service
221 with patch_orch(False):
222 self._get(inventory_url)
223 self.assertStatus(503)
f6b5b4d7
TL
224
225
226class HostUiControllerTest(ControllerTestCase):
227 URL_HOST = '/ui-api/host'
228
229 @classmethod
230 def setup_server(cls):
231 # pylint: disable=protected-access
232 HostUi._cp_config['tools.authenticate.on'] = False
233 cls.setup_controllers([HostUi])
234
f67539c2
TL
235 def test_labels(self):
236 orch_hosts = [
f6b5b4d7
TL
237 HostSpec('node1', labels=['foo']),
238 HostSpec('node2', labels=['foo', 'bar'])
239 ]
f6b5b4d7 240
f67539c2
TL
241 with patch_orch(True, hosts=orch_hosts):
242 self._get('{}/labels'.format(self.URL_HOST))
243 self.assertStatus(200)
244 labels = self.json_body()
245 labels.sort()
246 self.assertListEqual(labels, ['bar', 'foo'])
247
248 @mock.patch('dashboard.controllers.host.get_inventories')
249 def test_inventory(self, mock_get_inventories):
250 inventory_url = '{}/inventory'.format(self.URL_HOST)
251 with patch_orch(True):
252 tests = [
253 {
254 'url': inventory_url,
255 'refresh': None
256 },
257 {
258 'url': '{}?refresh=true'.format(inventory_url),
259 'refresh': "true"
260 },
261 ]
262 for test in tests:
263 mock_get_inventories.reset_mock()
264 mock_get_inventories.return_value = [{'a': 'b'}]
265 self._get(test['url'])
266 mock_get_inventories.assert_called_once_with(None, test['refresh'])
267 self.assertEqual(self.json_body(), [{'a': 'b'}])
268 self.assertStatus(200)
269
270 # list without orchestrator service
271 with patch_orch(False):
272 self._get(inventory_url)
273 self.assertStatus(503)
f6b5b4d7
TL
274
275
276class TestHosts(unittest.TestCase):
f67539c2 277 def test_get_hosts(self):
f6b5b4d7
TL
278 mgr.list_servers.return_value = [{
279 'hostname': 'node1'
280 }, {
281 'hostname': 'localhost'
282 }]
f67539c2 283 orch_hosts = [
f6b5b4d7
TL
284 HostSpec('node1', labels=['foo', 'bar']),
285 HostSpec('node2', labels=['bar'])
286 ]
9f95a23c 287
f67539c2
TL
288 with patch_orch(True, hosts=orch_hosts):
289 hosts = get_hosts()
290 self.assertEqual(len(hosts), 3)
291 checks = {
292 'localhost': {
293 'sources': {
294 'ceph': True,
295 'orchestrator': False
296 },
297 'labels': []
f6b5b4d7 298 },
f67539c2
TL
299 'node1': {
300 'sources': {
301 'ceph': True,
302 'orchestrator': True
303 },
304 'labels': ['bar', 'foo']
305 },
306 'node2': {
307 'sources': {
308 'ceph': False,
309 'orchestrator': True
310 },
311 'labels': ['bar']
312 }
313 }
314 for host in hosts:
315 hostname = host['hostname']
316 self.assertDictEqual(host['sources'], checks[hostname]['sources'])
317 self.assertListEqual(host['labels'], checks[hostname]['labels'])
318
319 @mock.patch('dashboard.controllers.host.mgr.get')
320 def test_get_device_osd_map(self, mgr_get):
321 mgr_get.side_effect = lambda key: {
322 'osd_metadata': {
323 '0': {
324 'hostname': 'node0',
325 'devices': 'nvme0n1,sdb',
326 },
327 '1': {
328 'hostname': 'node0',
329 'devices': 'nvme0n1,sdc',
330 },
331 '2': {
332 'hostname': 'node1',
333 'devices': 'sda',
334 },
335 '3': {
336 'hostname': 'node2',
337 'devices': '',
338 }
339 }
340 }[key]
341
342 device_osd_map = get_device_osd_map()
343 mgr.get.assert_called_with('osd_metadata')
344 # sort OSD IDs to make assertDictEqual work
345 for devices in device_osd_map.values():
346 for host in devices.keys():
347 devices[host] = sorted(devices[host])
348 self.assertDictEqual(device_osd_map, {
349 'node0': {
350 'nvme0n1': [0, 1],
351 'sdb': [0],
352 'sdc': [1],
f6b5b4d7
TL
353 },
354 'node1': {
f67539c2
TL
355 'sda': [2]
356 }
357 })
358
359 @mock.patch('dashboard.controllers.host.str_to_bool')
360 @mock.patch('dashboard.controllers.host.get_device_osd_map')
361 def test_get_inventories(self, mock_get_device_osd_map, mock_str_to_bool):
362 mock_get_device_osd_map.return_value = {
363 'host-0': {
364 'nvme0n1': [1, 2],
365 'sdb': [1],
366 'sdc': [2]
f6b5b4d7 367 },
f67539c2
TL
368 'host-1': {
369 'sdb': [3]
f6b5b4d7 370 }
9f95a23c 371 }
f67539c2
TL
372 inventory = [
373 {
374 'name': 'host-0',
375 'addr': '1.2.3.4',
376 'devices': [
377 {'path': 'nvme0n1'},
378 {'path': '/dev/sdb'},
379 {'path': '/dev/sdc'},
380 ]
381 },
382 {
383 'name': 'host-1',
384 'addr': '1.2.3.5',
385 'devices': [
386 {'path': '/dev/sda'},
387 {'path': 'sdb'},
388 ]
389 }
390 ]
391
392 with patch_orch(True, inventory=inventory) as orch_client:
393 mock_str_to_bool.return_value = True
394
395 hosts = ['host-0', 'host-1']
396 inventories = get_inventories(hosts, 'true')
397 mock_str_to_bool.assert_called_with('true')
398 orch_client.inventory.list.assert_called_once_with(hosts=hosts, refresh=True)
399 self.assertEqual(len(inventories), 2)
400 host0 = inventories[0]
401 self.assertEqual(host0['name'], 'host-0')
402 self.assertEqual(host0['addr'], '1.2.3.4')
403 self.assertEqual(host0['devices'][0]['osd_ids'], [1, 2])
404 self.assertEqual(host0['devices'][1]['osd_ids'], [1])
405 self.assertEqual(host0['devices'][2]['osd_ids'], [2])
406 host1 = inventories[1]
407 self.assertEqual(host1['name'], 'host-1')
408 self.assertEqual(host1['addr'], '1.2.3.5')
409 self.assertEqual(host1['devices'][0]['osd_ids'], [])
410 self.assertEqual(host1['devices'][1]['osd_ids'], [3])