]>
git.proxmox.com Git - ceph.git/blob - ceph/src/pybind/mgr/dashboard/tests/test_host.py
3 from typing
import List
, Optional
4 from unittest
import mock
6 from orchestrator
import HostSpec
, InventoryHost
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
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
= []
23 fake_client
.hosts
.list.return_value
= hosts
25 if inventory
is not None:
26 def _list_inventory(hosts
=None, refresh
=False): # pylint: disable=unused-argument
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
))
32 fake_client
.inventory
.list.side_effect
= _list_inventory
34 instance
.return_value
= fake_client
38 class HostControllerTest(ControllerTestCase
):
39 URL_HOST
= '/api/host'
42 def setup_server(cls
):
43 NotificationQueue
.start_queue()
45 # pylint: disable=protected-access
46 Host
._cp
_config
['tools.authenticate.on'] = False
47 cls
.setup_controllers([Host
])
50 def tearDownClass(cls
):
51 NotificationQueue
.stop()
53 @mock.patch('dashboard.controllers.host.get_hosts')
54 def test_host_list(self
, mock_get_hosts
):
75 def _get_hosts(from_ceph
=True, from_orchestrator
=True):
78 _hosts
.append(hosts
[0])
80 _hosts
.append(hosts
[1])
81 _hosts
.append(hosts
[2])
84 mock_get_hosts
.side_effect
= _get_hosts
86 self
._get
(self
.URL_HOST
)
87 self
.assertStatus(200)
88 self
.assertJsonBody(hosts
)
90 self
._get
('{}?sources=ceph'.format(self
.URL_HOST
))
91 self
.assertStatus(200)
92 self
.assertJsonBody([hosts
[0]])
94 self
._get
('{}?sources=orchestrator'.format(self
.URL_HOST
))
95 self
.assertStatus(200)
96 self
.assertJsonBody(hosts
[1:])
98 self
._get
('{}?sources=ceph,orchestrator'.format(self
.URL_HOST
))
99 self
.assertStatus(200)
100 self
.assertJsonBody(hosts
)
102 def test_get_1(self
):
103 mgr
.list_servers
.return_value
= []
105 with
patch_orch(False):
106 self
._get
('{}/node1'.format(self
.URL_HOST
))
107 self
.assertStatus(404)
109 def test_get_2(self
):
110 mgr
.list_servers
.return_value
= [{'hostname': 'node1'}]
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())
119 def test_get_3(self
):
120 mgr
.list_servers
.return_value
= []
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())
129 @mock.patch('dashboard.controllers.host.add_host')
130 def test_add_host(self
, mock_add_host
):
131 with
patch_orch(True):
136 'status': 'maintenance'
138 self
._post
(self
.URL_HOST
, payload
, version
='0.1')
139 self
.assertStatus(201)
140 mock_add_host
.assert_called()
142 def test_set_labels(self
):
143 mgr
.list_servers
.return_value
= []
145 HostSpec('node0', labels
=['aaa', 'bbb'])
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()
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')
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)
164 def test_host_maintenance(self
):
165 mgr
.list_servers
.return_value
= []
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')
177 # force enter maintenance mode
178 self
._put
('{}/node1'.format(self
.URL_HOST
), {'maintenance': True, 'force': True},
180 self
.assertStatus(200)
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)
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)
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
:
198 'device': '/dev/sdz',
201 self
._task
_post
(url
, payload
)
202 self
.assertStatus(200)
203 mock_time
.sleep
.assert_called()
205 mock
.call('host-0', '/dev/sdz', 'ident', True),
206 mock
.call('host-0', '/dev/sdz', 'ident', False),
208 fake_client
.blink_device_light
.assert_has_calls(calls
)
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):
216 'url': inventory_url
,
217 'inventories': [{'a': 'b'}],
222 'url': '{}?refresh=true'.format(inventory_url
),
223 'inventories': [{'a': 'b'}],
228 'url': inventory_url
,
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)
242 # list without orchestrator service
243 with
patch_orch(False):
244 self
._get
(inventory_url
)
245 self
.assertStatus(503)
248 class HostUiControllerTest(ControllerTestCase
):
249 URL_HOST
= '/ui-api/host'
252 def setup_server(cls
):
253 # pylint: disable=protected-access
254 HostUi
._cp
_config
['tools.authenticate.on'] = False
255 cls
.setup_controllers([HostUi
])
257 def test_labels(self
):
259 HostSpec('node1', labels
=['foo']),
260 HostSpec('node2', labels
=['foo', 'bar'])
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()
268 self
.assertListEqual(labels
, ['bar', 'foo'])
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):
276 'url': inventory_url
,
280 'url': '{}?refresh=true'.format(inventory_url
),
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)
292 # list without orchestrator service
293 with
patch_orch(False):
294 self
._get
(inventory_url
)
295 self
.assertStatus(503)
298 class TestHosts(unittest
.TestCase
):
299 def test_get_hosts(self
):
300 mgr
.list_servers
.return_value
= [{
303 'hostname': 'localhost'
306 HostSpec('node1', labels
=['foo', 'bar']),
307 HostSpec('node2', labels
=['bar'])
310 with
patch_orch(True, hosts
=orch_hosts
):
312 self
.assertEqual(len(hosts
), 3)
317 'orchestrator': False
326 'labels': ['bar', 'foo']
337 hostname
= host
['hostname']
338 self
.assertDictEqual(host
['sources'], checks
[hostname
]['sources'])
339 self
.assertListEqual(host
['labels'], checks
[hostname
]['labels'])
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
: {
347 'devices': 'nvme0n1,sdb',
351 'devices': 'nvme0n1,sdc',
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
, {
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
= {
400 {'path': '/dev/sdb'},
401 {'path': '/dev/sdc'},
408 {'path': '/dev/sda'},
414 with
patch_orch(True, inventory
=inventory
) as orch_client
:
415 mock_str_to_bool
.return_value
= True
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])