]> git.proxmox.com Git - ceph.git/blame - ceph/src/pybind/mgr/cephadm/tests/test_scheduling.py
import 15.2.4
[ceph.git] / ceph / src / pybind / mgr / cephadm / tests / test_scheduling.py
CommitLineData
9f95a23c
TL
1from typing import NamedTuple, List
2import pytest
3
4from ceph.deployment.service_spec import ServiceSpec, PlacementSpec, ServiceSpecValidationError
5
6from cephadm.module import HostAssignment
7from orchestrator import DaemonDescription, OrchestratorValidationError
8
9
10class NodeAssignmentTest(NamedTuple):
11 service_type: str
12 placement: PlacementSpec
13 hosts: List[str]
14 daemons: List[DaemonDescription]
15 expected: List[str]
16
17@pytest.mark.parametrize("service_type,placement,hosts,daemons,expected",
18 [
19 # just hosts
20 NodeAssignmentTest(
21 'mon',
22 PlacementSpec(hosts=['smithi060:[v2:172.21.15.60:3301,v1:172.21.15.60:6790]=c']),
23 ['smithi060'],
24 [],
25 ['smithi060']
26 ),
27 # all_hosts
28 NodeAssignmentTest(
29 'mon',
30 PlacementSpec(host_pattern='*'),
31 'host1 host2 host3'.split(),
32 [
33 DaemonDescription('mon', 'a', 'host1'),
34 DaemonDescription('mon', 'b', 'host2'),
35 ],
36 ['host1', 'host2', 'host3']
37 ),
38 # count that is bigger than the amount of hosts. Truncate to len(hosts)
39 # RGWs should not be co-located to each other.
40 NodeAssignmentTest(
41 'rgw',
42 PlacementSpec(count=4),
43 'host1 host2 host3'.split(),
44 [],
45 ['host1', 'host2', 'host3']
46 ),
47 # count + partial host list
48 NodeAssignmentTest(
49 'mon',
50 PlacementSpec(count=3, hosts=['host3']),
51 'host1 host2 host3'.split(),
52 [
53 DaemonDescription('mon', 'a', 'host1'),
54 DaemonDescription('mon', 'b', 'host2'),
55 ],
56 ['host1', 'host2', 'host3']
57 ),
58 # count 1 + partial host list
59 NodeAssignmentTest(
60 'mon',
61 PlacementSpec(count=1, hosts=['host3']),
62 'host1 host2 host3'.split(),
63 [
64 DaemonDescription('mon', 'a', 'host1'),
65 DaemonDescription('mon', 'b', 'host2'),
66 ],
67 ['host3']
68 ),
69 # count + partial host list + existing
70 NodeAssignmentTest(
71 'mon',
72 PlacementSpec(count=2, hosts=['host3']),
73 'host1 host2 host3'.split(),
74 [
75 DaemonDescription('mon', 'a', 'host1'),
76 ],
77 ['host1', 'host3']
78 ),
79 # count + partial host list + existing (deterministic)
80 NodeAssignmentTest(
81 'mon',
82 PlacementSpec(count=2, hosts=['host1']),
83 'host1 host2'.split(),
84 [
85 DaemonDescription('mon', 'a', 'host1'),
86 ],
87 ['host1', 'host2']
88 ),
89 # count + partial host list + existing (deterministic)
90 NodeAssignmentTest(
91 'mon',
92 PlacementSpec(count=2, hosts=['host1']),
93 'host1 host2'.split(),
94 [
95 DaemonDescription('mon', 'a', 'host2'),
96 ],
97 ['host1', 'host2']
98 ),
99 # label only
100 NodeAssignmentTest(
101 'mon',
102 PlacementSpec(label='foo'),
103 'host1 host2 host3'.split(),
104 [],
105 ['host1', 'host2', 'host3']
106 ),
107 # host_pattern
108 NodeAssignmentTest(
109 'mon',
110 PlacementSpec(host_pattern='mon*'),
111 'monhost1 monhost2 datahost'.split(),
112 [],
113 ['monhost1', 'monhost2']
114 ),
115 ])
116def test_node_assignment(service_type, placement, hosts, daemons, expected):
117 hosts = HostAssignment(
118 spec=ServiceSpec(service_type, placement=placement),
e306af50 119 get_hosts_func=lambda label=None, as_hostspec=False: hosts,
9f95a23c
TL
120 get_daemons_func=lambda _: daemons).place()
121 assert sorted([h.hostname for h in hosts]) == sorted(expected)
122
e306af50 123
9f95a23c
TL
124class NodeAssignmentTest2(NamedTuple):
125 service_type: str
126 placement: PlacementSpec
127 hosts: List[str]
128 daemons: List[DaemonDescription]
129 expected_len: int
130 in_set: List[str]
131
132@pytest.mark.parametrize("service_type,placement,hosts,daemons,expected_len,in_set",
133 [
134 # empty
135 NodeAssignmentTest2(
136 'mon',
137 PlacementSpec(),
138 'host1 host2 host3'.split(),
139 [],
140 1,
141 ['host1', 'host2', 'host3'],
142 ),
143
144 # just count
145 NodeAssignmentTest2(
146 'mon',
147 PlacementSpec(count=1),
148 'host1 host2 host3'.split(),
149 [],
150 1,
151 ['host1', 'host2', 'host3'],
152 ),
153
154 # hosts + (smaller) count
155 NodeAssignmentTest2(
156 'mon',
157 PlacementSpec(count=1, hosts='host1 host2'.split()),
158 'host1 host2'.split(),
159 [],
160 1,
161 ['host1', 'host2'],
162 ),
163 # hosts + (smaller) count, existing
164 NodeAssignmentTest2(
165 'mon',
166 PlacementSpec(count=1, hosts='host1 host2 host3'.split()),
167 'host1 host2 host3'.split(),
168 [DaemonDescription('mon', 'mon.a', 'host1'),],
169 1,
170 ['host1', 'host2', 'host3'],
171 ),
172 # hosts + (smaller) count, (more) existing
173 NodeAssignmentTest2(
174 'mon',
175 PlacementSpec(count=1, hosts='host1 host2 host3'.split()),
176 'host1 host2 host3'.split(),
177 [
178 DaemonDescription('mon', 'a', 'host1'),
179 DaemonDescription('mon', 'b', 'host2'),
180 ],
181 1,
182 ['host1', 'host2']
183 ),
184 # count + partial host list
185 NodeAssignmentTest2(
186 'mon',
187 PlacementSpec(count=2, hosts=['host3']),
188 'host1 host2 host3'.split(),
189 [],
190 2,
191 ['host1', 'host2', 'host3']
192 ),
193 # label + count
194 NodeAssignmentTest2(
195 'mon',
196 PlacementSpec(count=1, label='foo'),
197 'host1 host2 host3'.split(),
198 [],
199 1,
200 ['host1', 'host2', 'host3']
201 ),
202 ])
203def test_node_assignment2(service_type, placement, hosts,
204 daemons, expected_len, in_set):
205 hosts = HostAssignment(
206 spec=ServiceSpec(service_type, placement=placement),
e306af50 207 get_hosts_func=lambda label=None, as_hostspec=False: hosts,
9f95a23c
TL
208 get_daemons_func=lambda _: daemons).place()
209 assert len(hosts) == expected_len
210 for h in [h.hostname for h in hosts]:
211 assert h in in_set
212
213@pytest.mark.parametrize("service_type,placement,hosts,daemons,expected_len,must_have",
214 [
215 # hosts + (smaller) count, (more) existing
216 NodeAssignmentTest2(
217 'mon',
218 PlacementSpec(count=3, hosts='host3'.split()),
219 'host1 host2 host3'.split(),
220 [],
221 3,
222 ['host3']
223 ),
224 # count + partial host list
225 NodeAssignmentTest2(
226 'mon',
227 PlacementSpec(count=2, hosts=['host3']),
228 'host1 host2 host3'.split(),
229 [],
230 2,
231 ['host3']
232 ),
233 ])
234def test_node_assignment3(service_type, placement, hosts,
235 daemons, expected_len, must_have):
236 hosts = HostAssignment(
237 spec=ServiceSpec(service_type, placement=placement),
e306af50 238 get_hosts_func=lambda label=None, as_hostspec=False: hosts,
9f95a23c
TL
239 get_daemons_func=lambda _: daemons).place()
240 assert len(hosts) == expected_len
241 for h in must_have:
242 assert h in [h.hostname for h in hosts]
243
244
245@pytest.mark.parametrize("placement",
246 [
247 ('1 *'),
248 ('* label:foo'),
249 ('* host1 host2'),
250 ('hostname12hostname12hostname12hostname12hostname12hostname12hostname12'), # > 63 chars
251 ])
252def test_bad_placements(placement):
253 try:
254 s = PlacementSpec.from_string(placement.split(' '))
255 assert False
256 except ServiceSpecValidationError as e:
257 pass
258
259
260class NodeAssignmentTestBadSpec(NamedTuple):
261 service_type: str
262 placement: PlacementSpec
263 hosts: List[str]
264 daemons: List[DaemonDescription]
265 expected: str
266@pytest.mark.parametrize("service_type,placement,hosts,daemons,expected",
267 [
268 # unknown host
269 NodeAssignmentTestBadSpec(
270 'mon',
271 PlacementSpec(hosts=['unknownhost']),
272 ['knownhost'],
273 [],
274 "Cannot place <ServiceSpec for service_name=mon> on {'unknownhost'}: Unknown hosts"
275 ),
276 # unknown host pattern
277 NodeAssignmentTestBadSpec(
278 'mon',
279 PlacementSpec(host_pattern='unknownhost'),
280 ['knownhost'],
281 [],
282 "Cannot place <ServiceSpec for service_name=mon>: No matching hosts"
283 ),
284 # unknown label
285 NodeAssignmentTestBadSpec(
286 'mon',
287 PlacementSpec(label='unknownlabel'),
288 [],
289 [],
290 "Cannot place <ServiceSpec for service_name=mon>: No matching hosts for label unknownlabel"
291 ),
292 ])
293def test_bad_specs(service_type, placement, hosts, daemons, expected):
294 with pytest.raises(OrchestratorValidationError) as e:
295 hosts = HostAssignment(
296 spec=ServiceSpec(service_type, placement=placement),
e306af50 297 get_hosts_func=lambda label=None, as_hostspec=False: hosts,
9f95a23c
TL
298 get_daemons_func=lambda _: daemons).place()
299 assert str(e.value) == expected