]> git.proxmox.com Git - ceph.git/blob - ceph/src/pybind/mgr/cephadm/tests/test_scheduling.py
54402bad469c0f07d4c2dac1c5750403a5e9257e
[ceph.git] / ceph / src / pybind / mgr / cephadm / tests / test_scheduling.py
1 from typing import NamedTuple, List
2 import pytest
3
4 from ceph.deployment.service_spec import ServiceSpec, PlacementSpec, ServiceSpecValidationError
5
6 from cephadm.module import HostAssignment
7 from orchestrator import DaemonDescription, OrchestratorValidationError
8
9
10 class 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 ])
116 def test_node_assignment(service_type, placement, hosts, daemons, expected):
117 hosts = HostAssignment(
118 spec=ServiceSpec(service_type, placement=placement),
119 get_hosts_func=lambda _: hosts,
120 get_daemons_func=lambda _: daemons).place()
121 assert sorted([h.hostname for h in hosts]) == sorted(expected)
122
123 class NodeAssignmentTest2(NamedTuple):
124 service_type: str
125 placement: PlacementSpec
126 hosts: List[str]
127 daemons: List[DaemonDescription]
128 expected_len: int
129 in_set: List[str]
130
131 @pytest.mark.parametrize("service_type,placement,hosts,daemons,expected_len,in_set",
132 [
133 # empty
134 NodeAssignmentTest2(
135 'mon',
136 PlacementSpec(),
137 'host1 host2 host3'.split(),
138 [],
139 1,
140 ['host1', 'host2', 'host3'],
141 ),
142
143 # just count
144 NodeAssignmentTest2(
145 'mon',
146 PlacementSpec(count=1),
147 'host1 host2 host3'.split(),
148 [],
149 1,
150 ['host1', 'host2', 'host3'],
151 ),
152
153 # hosts + (smaller) count
154 NodeAssignmentTest2(
155 'mon',
156 PlacementSpec(count=1, hosts='host1 host2'.split()),
157 'host1 host2'.split(),
158 [],
159 1,
160 ['host1', 'host2'],
161 ),
162 # hosts + (smaller) count, existing
163 NodeAssignmentTest2(
164 'mon',
165 PlacementSpec(count=1, hosts='host1 host2 host3'.split()),
166 'host1 host2 host3'.split(),
167 [DaemonDescription('mon', 'mon.a', 'host1'),],
168 1,
169 ['host1', 'host2', 'host3'],
170 ),
171 # hosts + (smaller) count, (more) existing
172 NodeAssignmentTest2(
173 'mon',
174 PlacementSpec(count=1, hosts='host1 host2 host3'.split()),
175 'host1 host2 host3'.split(),
176 [
177 DaemonDescription('mon', 'a', 'host1'),
178 DaemonDescription('mon', 'b', 'host2'),
179 ],
180 1,
181 ['host1', 'host2']
182 ),
183 # count + partial host list
184 NodeAssignmentTest2(
185 'mon',
186 PlacementSpec(count=2, hosts=['host3']),
187 'host1 host2 host3'.split(),
188 [],
189 2,
190 ['host1', 'host2', 'host3']
191 ),
192 # label + count
193 NodeAssignmentTest2(
194 'mon',
195 PlacementSpec(count=1, label='foo'),
196 'host1 host2 host3'.split(),
197 [],
198 1,
199 ['host1', 'host2', 'host3']
200 ),
201 ])
202 def test_node_assignment2(service_type, placement, hosts,
203 daemons, expected_len, in_set):
204 hosts = HostAssignment(
205 spec=ServiceSpec(service_type, placement=placement),
206 get_hosts_func=lambda _: hosts,
207 get_daemons_func=lambda _: daemons).place()
208 assert len(hosts) == expected_len
209 for h in [h.hostname for h in hosts]:
210 assert h in in_set
211
212 @pytest.mark.parametrize("service_type,placement,hosts,daemons,expected_len,must_have",
213 [
214 # hosts + (smaller) count, (more) existing
215 NodeAssignmentTest2(
216 'mon',
217 PlacementSpec(count=3, hosts='host3'.split()),
218 'host1 host2 host3'.split(),
219 [],
220 3,
221 ['host3']
222 ),
223 # count + partial host list
224 NodeAssignmentTest2(
225 'mon',
226 PlacementSpec(count=2, hosts=['host3']),
227 'host1 host2 host3'.split(),
228 [],
229 2,
230 ['host3']
231 ),
232 ])
233 def test_node_assignment3(service_type, placement, hosts,
234 daemons, expected_len, must_have):
235 hosts = HostAssignment(
236 spec=ServiceSpec(service_type, placement=placement),
237 get_hosts_func=lambda _: hosts,
238 get_daemons_func=lambda _: daemons).place()
239 assert len(hosts) == expected_len
240 for h in must_have:
241 assert h in [h.hostname for h in hosts]
242
243
244 @pytest.mark.parametrize("placement",
245 [
246 ('1 *'),
247 ('* label:foo'),
248 ('* host1 host2'),
249 ('hostname12hostname12hostname12hostname12hostname12hostname12hostname12'), # > 63 chars
250 ])
251 def test_bad_placements(placement):
252 try:
253 s = PlacementSpec.from_string(placement.split(' '))
254 assert False
255 except ServiceSpecValidationError as e:
256 pass
257
258
259 class NodeAssignmentTestBadSpec(NamedTuple):
260 service_type: str
261 placement: PlacementSpec
262 hosts: List[str]
263 daemons: List[DaemonDescription]
264 expected: str
265 @pytest.mark.parametrize("service_type,placement,hosts,daemons,expected",
266 [
267 # unknown host
268 NodeAssignmentTestBadSpec(
269 'mon',
270 PlacementSpec(hosts=['unknownhost']),
271 ['knownhost'],
272 [],
273 "Cannot place <ServiceSpec for service_name=mon> on {'unknownhost'}: Unknown hosts"
274 ),
275 # unknown host pattern
276 NodeAssignmentTestBadSpec(
277 'mon',
278 PlacementSpec(host_pattern='unknownhost'),
279 ['knownhost'],
280 [],
281 "Cannot place <ServiceSpec for service_name=mon>: No matching hosts"
282 ),
283 # unknown label
284 NodeAssignmentTestBadSpec(
285 'mon',
286 PlacementSpec(label='unknownlabel'),
287 [],
288 [],
289 "Cannot place <ServiceSpec for service_name=mon>: No matching hosts for label unknownlabel"
290 ),
291 ])
292 def test_bad_specs(service_type, placement, hosts, daemons, expected):
293 with pytest.raises(OrchestratorValidationError) as e:
294 hosts = HostAssignment(
295 spec=ServiceSpec(service_type, placement=placement),
296 get_hosts_func=lambda _: hosts,
297 get_daemons_func=lambda _: daemons).place()
298 assert str(e.value) == expected