]> git.proxmox.com Git - ceph.git/blame - ceph/src/pybind/mgr/cephadm/tests/test_osd_removal.py
import ceph 16.2.7
[ceph.git] / ceph / src / pybind / mgr / cephadm / tests / test_osd_removal.py
CommitLineData
f91f0fd5
TL
1import json
2
f67539c2 3from cephadm.services.osd import OSDRemovalQueue, OSD
f6b5b4d7 4import pytest
f6b5b4d7 5from tests import mock
a4b75251 6from .fixtures import with_cephadm_module
f6b5b4d7
TL
7from datetime import datetime
8
9
10class MockOSD:
11
12 def __init__(self, osd_id):
13 self.osd_id = osd_id
14
f91f0fd5 15
f6b5b4d7
TL
16class TestOSDRemoval:
17
18 @pytest.mark.parametrize(
19 "osd_id, osd_df, expected",
20 [
21 # missing 'nodes' key
22 (1, dict(nodes=[]), -1),
23 # missing 'pgs' key
24 (1, dict(nodes=[dict(id=1)]), -1),
25 # id != osd_id
26 (1, dict(nodes=[dict(id=999, pgs=1)]), -1),
27 # valid
28 (1, dict(nodes=[dict(id=1, pgs=1)]), 1),
29 ]
30 )
31 def test_get_pg_count(self, rm_util, osd_id, osd_df, expected):
32 with mock.patch("cephadm.services.osd.RemoveUtil.osd_df", return_value=osd_df):
33 assert rm_util.get_pg_count(osd_id) == expected
34
35 @pytest.mark.parametrize(
36 "osds, ok_to_stop, expected",
37 [
38 # no osd_ids provided
39 ([], [False], []),
40 # all osds are ok_to_stop
41 ([1, 2], [True], [1, 2]),
42 # osds are ok_to_stop after the second iteration
43 ([1, 2], [False, True], [2]),
44 # osds are never ok_to_stop, (taking the sample size `(len(osd_ids))` into account),
45 # expected to get False
46 ([1, 2], [False, False], []),
47 ]
48 )
49 def test_find_stop_threshold(self, rm_util, osds, ok_to_stop, expected):
50 with mock.patch("cephadm.services.osd.RemoveUtil.ok_to_stop", side_effect=ok_to_stop):
51 assert rm_util.find_osd_stop_threshold(osds) == expected
52
53 def test_process_removal_queue(self, rm_util):
54 # TODO: !
55 # rm_util.process_removal_queue()
56 pass
57
a4b75251
TL
58 @pytest.mark.parametrize(
59 "max_osd_draining_count, draining_osds, idling_osds, ok_to_stop, expected",
60 [
61 # drain one at a time, one already draining
62 (1, [1], [1], [True], 0),
63 # drain one at a time, none draining yet
64 (1, [], [1, 2, 3], [True, True, True], 1),
65 # drain one at a time, one already draining, none ok-to-stop
66 (1, [1], [1], [False], 0),
67 # drain one at a time, none draining, one ok-to-stop
68 (1, [], [1, 2, 3], [False, False, True], 1),
69 # drain three at a time, one already draining, all ok-to-stop
70 (3, [1], [1, 2, 3], [True, True, True], 2),
71 # drain two at a time, none already draining, none ok-to-stop
72 (2, [], [1, 2, 3], [False, False, False], 0),
73 # drain two at a time, none already draining, none idling
74 (2, [], [], [], 0),
75 ]
76 )
77 def test_ready_to_drain_osds(self, max_osd_draining_count, draining_osds, idling_osds, ok_to_stop, expected):
78 with with_cephadm_module({'max_osd_draining_count': max_osd_draining_count}) as m:
79 with mock.patch("cephadm.services.osd.OSDRemovalQueue.draining_osds", return_value=draining_osds):
80 with mock.patch("cephadm.services.osd.OSDRemovalQueue.idling_osds", return_value=idling_osds):
81 with mock.patch("cephadm.services.osd.RemoveUtil.ok_to_stop", side_effect=ok_to_stop):
82 removal_queue = OSDRemovalQueue(m)
83 assert len(removal_queue._ready_to_drain_osds()) == expected
84
f6b5b4d7
TL
85 def test_ok_to_stop(self, rm_util):
86 rm_util.ok_to_stop([MockOSD(1)])
a4b75251
TL
87 rm_util._run_mon_cmd.assert_called_with({'prefix': 'osd ok-to-stop', 'ids': ['1']},
88 error_ok=True)
f6b5b4d7
TL
89
90 def test_safe_to_destroy(self, rm_util):
91 rm_util.safe_to_destroy([1])
a4b75251
TL
92 rm_util._run_mon_cmd.assert_called_with({'prefix': 'osd safe-to-destroy',
93 'ids': ['1']}, error_ok=True)
f6b5b4d7
TL
94
95 def test_destroy_osd(self, rm_util):
96 rm_util.destroy_osd(1)
f91f0fd5
TL
97 rm_util._run_mon_cmd.assert_called_with(
98 {'prefix': 'osd destroy-actual', 'id': 1, 'yes_i_really_mean_it': True})
f6b5b4d7
TL
99
100 def test_purge_osd(self, rm_util):
101 rm_util.purge_osd(1)
f91f0fd5
TL
102 rm_util._run_mon_cmd.assert_called_with(
103 {'prefix': 'osd purge-actual', 'id': 1, 'yes_i_really_mean_it': True})
104
adb31ebb 105 def test_load(self, cephadm_module, rm_util):
f91f0fd5
TL
106 data = json.dumps([
107 {
108 "osd_id": 35,
109 "started": True,
110 "draining": True,
111 "stopped": False,
112 "replace": False,
113 "force": False,
a4b75251 114 "zap": False,
f91f0fd5
TL
115 "nodename": "node2",
116 "drain_started_at": "2020-09-14T11:41:53.960463",
117 "drain_stopped_at": None,
118 "drain_done_at": None,
119 "process_started_at": "2020-09-14T11:41:52.245832"
120 }
121 ])
122 cephadm_module.set_store('osd_remove_queue', data)
adb31ebb 123 cephadm_module.to_remove_osds.load_from_store()
f91f0fd5 124
adb31ebb
TL
125 expected = OSDRemovalQueue(cephadm_module)
126 expected.osds.add(OSD(osd_id=35, remove_util=rm_util, draining=True))
127 assert cephadm_module.to_remove_osds == expected
f6b5b4d7
TL
128
129
130class TestOSD:
131
132 def test_start(self, osd_obj):
133 assert osd_obj.started is False
134 osd_obj.start()
135 assert osd_obj.started is True
136 assert osd_obj.stopped is False
137
f67539c2 138 def test_start_draining_purge(self, osd_obj):
f6b5b4d7
TL
139 assert osd_obj.draining is False
140 assert osd_obj.drain_started_at is None
141 ret = osd_obj.start_draining()
f67539c2
TL
142 osd_obj.rm_util.reweight_osd.assert_called_with(osd_obj, 0.0)
143 assert isinstance(osd_obj.drain_started_at, datetime)
144 assert osd_obj.draining is True
145 assert osd_obj.replace is False
146 assert ret is True
147
148 def test_start_draining_replace(self, osd_obj):
149 assert osd_obj.draining is False
150 assert osd_obj.drain_started_at is None
151 osd_obj.replace = True
152 ret = osd_obj.start_draining()
f6b5b4d7
TL
153 osd_obj.rm_util.set_osd_flag.assert_called_with([osd_obj], 'out')
154 assert isinstance(osd_obj.drain_started_at, datetime)
155 assert osd_obj.draining is True
f67539c2 156 assert osd_obj.replace is True
f6b5b4d7
TL
157 assert ret is True
158
159 def test_start_draining_stopped(self, osd_obj):
160 osd_obj.stopped = True
161 ret = osd_obj.start_draining()
162 assert osd_obj.drain_started_at is None
163 assert ret is False
164 assert osd_obj.draining is False
165
f67539c2
TL
166 def test_stop_draining_replace(self, osd_obj):
167 osd_obj.replace = True
f6b5b4d7
TL
168 ret = osd_obj.stop_draining()
169 osd_obj.rm_util.set_osd_flag.assert_called_with([osd_obj], 'in')
170 assert isinstance(osd_obj.drain_stopped_at, datetime)
171 assert osd_obj.draining is False
172 assert ret is True
173
f67539c2
TL
174 def test_stop_draining_purge(self, osd_obj):
175 osd_obj.original_weight = 1.0
176 ret = osd_obj.stop_draining()
177 osd_obj.rm_util.reweight_osd.assert_called_with(osd_obj, 1.0)
178 assert isinstance(osd_obj.drain_stopped_at, datetime)
179 assert osd_obj.draining is False
180 assert ret is True
181
f6b5b4d7
TL
182 @mock.patch('cephadm.services.osd.OSD.stop_draining')
183 def test_stop(self, stop_draining_mock, osd_obj):
f67539c2 184 osd_obj.stop()
f6b5b4d7
TL
185 assert osd_obj.started is False
186 assert osd_obj.stopped is True
187 stop_draining_mock.assert_called_once()
188
189 @pytest.mark.parametrize(
190 "draining, empty, expected",
191 [
192 # must be !draining! and !not empty! to yield True
193 (True, not True, True),
194 # not draining and not empty
195 (False, not True, False),
196 # not draining and empty
197 (False, True, False),
198 # draining and empty
199 (True, True, False),
200 ]
201 )
202 def test_is_draining(self, osd_obj, draining, empty, expected):
203 with mock.patch("cephadm.services.osd.OSD.is_empty", new_callable=mock.PropertyMock(return_value=empty)):
204 osd_obj.draining = draining
205 assert osd_obj.is_draining is expected
206
207 @mock.patch("cephadm.services.osd.RemoveUtil.ok_to_stop")
208 def test_is_ok_to_stop(self, _, osd_obj):
f67539c2 209 osd_obj.is_ok_to_stop
f6b5b4d7
TL
210 osd_obj.rm_util.ok_to_stop.assert_called_once()
211
212 @pytest.mark.parametrize(
213 "pg_count, expected",
214 [
215 (0, True),
216 (1, False),
217 (9999, False),
218 (-1, False),
219 ]
220 )
221 def test_is_empty(self, osd_obj, pg_count, expected):
222 with mock.patch("cephadm.services.osd.OSD.get_pg_count", return_value=pg_count):
223 assert osd_obj.is_empty is expected
224
225 @mock.patch("cephadm.services.osd.RemoveUtil.safe_to_destroy")
226 def test_safe_to_destroy(self, _, osd_obj):
f67539c2 227 osd_obj.safe_to_destroy()
f6b5b4d7
TL
228 osd_obj.rm_util.safe_to_destroy.assert_called_once()
229
230 @mock.patch("cephadm.services.osd.RemoveUtil.set_osd_flag")
231 def test_down(self, _, osd_obj):
f67539c2 232 osd_obj.down()
f6b5b4d7
TL
233 osd_obj.rm_util.set_osd_flag.assert_called_with([osd_obj], 'down')
234
235 @mock.patch("cephadm.services.osd.RemoveUtil.destroy_osd")
236 def test_destroy_osd(self, _, osd_obj):
f67539c2 237 osd_obj.destroy()
f6b5b4d7
TL
238 osd_obj.rm_util.destroy_osd.assert_called_once()
239
240 @mock.patch("cephadm.services.osd.RemoveUtil.purge_osd")
241 def test_purge(self, _, osd_obj):
f67539c2 242 osd_obj.purge()
f6b5b4d7
TL
243 osd_obj.rm_util.purge_osd.assert_called_once()
244
245 @mock.patch("cephadm.services.osd.RemoveUtil.get_pg_count")
246 def test_pg_count(self, _, osd_obj):
f67539c2 247 osd_obj.get_pg_count()
f6b5b4d7
TL
248 osd_obj.rm_util.get_pg_count.assert_called_once()
249
250 def test_drain_status_human_not_started(self, osd_obj):
251 assert osd_obj.drain_status_human() == 'not started'
252
253 def test_drain_status_human_started(self, osd_obj):
254 osd_obj.started = True
255 assert osd_obj.drain_status_human() == 'started'
256
257 def test_drain_status_human_draining(self, osd_obj):
258 osd_obj.started = True
259 osd_obj.draining = True
260 assert osd_obj.drain_status_human() == 'draining'
261
262 def test_drain_status_human_done(self, osd_obj):
263 osd_obj.started = True
264 osd_obj.draining = False
265 osd_obj.drain_done_at = datetime.utcnow()
266 assert osd_obj.drain_status_human() == 'done, waiting for purge'
267
268
adb31ebb 269class TestOSDRemovalQueue:
f6b5b4d7
TL
270
271 def test_queue_size(self, osd_obj):
adb31ebb 272 q = OSDRemovalQueue(mock.Mock())
f6b5b4d7 273 assert q.queue_size() == 0
adb31ebb 274 q.osds.add(osd_obj)
f6b5b4d7
TL
275 assert q.queue_size() == 1
276
277 @mock.patch("cephadm.services.osd.OSD.start")
278 @mock.patch("cephadm.services.osd.OSD.exists")
279 def test_enqueue(self, exist, start, osd_obj):
adb31ebb 280 q = OSDRemovalQueue(mock.Mock())
f6b5b4d7
TL
281 q.enqueue(osd_obj)
282 osd_obj.start.assert_called_once()
283
284 @mock.patch("cephadm.services.osd.OSD.stop")
285 @mock.patch("cephadm.services.osd.OSD.exists")
286 def test_rm_raise(self, exist, stop, osd_obj):
adb31ebb 287 q = OSDRemovalQueue(mock.Mock())
f6b5b4d7
TL
288 with pytest.raises(KeyError):
289 q.rm(osd_obj)
290 osd_obj.stop.assert_called_once()
291
292 @mock.patch("cephadm.services.osd.OSD.stop")
293 @mock.patch("cephadm.services.osd.OSD.exists")
294 def test_rm(self, exist, stop, osd_obj):
adb31ebb
TL
295 q = OSDRemovalQueue(mock.Mock())
296 q.osds.add(osd_obj)
f6b5b4d7
TL
297 q.rm(osd_obj)
298 osd_obj.stop.assert_called_once()