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