]> git.proxmox.com Git - ceph.git/blame - ceph/src/pybind/mgr/cephadm/tests/test_scheduling.py
bump version to 15.2.6-pve1
[ceph.git] / ceph / src / pybind / mgr / cephadm / tests / test_scheduling.py
CommitLineData
9f95a23c
TL
1from typing import NamedTuple, List
2import pytest
3
f6b5b4d7 4from ceph.deployment.hostspec import HostSpec
9f95a23c
TL
5from ceph.deployment.service_spec import ServiceSpec, PlacementSpec, ServiceSpecValidationError
6
7from cephadm.module import HostAssignment
f6b5b4d7
TL
8from orchestrator import DaemonDescription, OrchestratorValidationError, OrchestratorError, HostSpec
9
10
11def wrapper(func):
12 # some odd thingy to revert the order or arguments
13 def inner(*args):
14 def inner2(expected):
15 func(expected, *args)
16 return inner2
17 return inner
18
19
20@wrapper
21def none(expected):
22 assert expected == []
23
24
25@wrapper
26def one_of(expected, *hosts):
27 if not isinstance(expected, list):
28 assert False, str(expected)
29 assert len(expected) == 1, f'one_of failed len({expected}) != 1'
30 assert expected[0] in hosts
31
32
33@wrapper
34def two_of(expected, *hosts):
35 if not isinstance(expected, list):
36 assert False, str(expected)
37 assert len(expected) == 2, f'one_of failed len({expected}) != 2'
38 matches = 0
39 for h in hosts:
40 matches += int(h in expected)
41 if matches != 2:
42 assert False, f'two of {hosts} not in {expected}'
43
44
45@wrapper
46def exactly(expected, *hosts):
47 assert expected == list(hosts)
48
49
50@wrapper
51def error(expected, kind, match):
52 assert isinstance(expected, kind), (str(expected), match)
53 assert str(expected) == match, (str(expected), match)
54
55
56@wrapper
57def _or(expected, *inners):
58 def catch(inner):
59 try:
60 inner(expected)
61 except AssertionError as e:
62 return e
63 result = [catch(i) for i in inners]
64 if None not in result:
65 assert False, f"_or failed: {expected}"
66
67
68def _always_true(_): pass
69
70
71def k(s):
72 return [e for e in s.split(' ') if e]
73
74
75def get_result(key, results):
76 def match(one):
77 for o, k in zip(one, key):
78 if o != k and o != '*':
79 return False
80 return True
81 return [v for k, v in results
82 if match(k)][0]
83
84def mk_spec_and_host(spec_section, hosts, explicit_key, explicit, count):
85
86
87 if spec_section == 'hosts':
88 mk_spec = lambda: ServiceSpec('mon', placement=PlacementSpec(
89 hosts=explicit,
90 count=count,
91 ))
92 mk_hosts = lambda _: hosts
93 elif spec_section == 'label':
94 mk_spec = lambda: ServiceSpec('mon', placement=PlacementSpec(
95 label='mylabel',
96 count=count,
97 ))
98 mk_hosts = lambda l: [e for e in explicit if e in hosts] if l == 'mylabel' else hosts
99 elif spec_section == 'host_pattern':
100 pattern = {
101 'e': 'notfound',
102 '1': '1',
103 '12': '[1-2]',
104 '123': '*',
105 }[explicit_key]
106 mk_spec = lambda: ServiceSpec('mon', placement=PlacementSpec(
107 host_pattern=pattern,
108 count=count,
109 ))
110 mk_hosts = lambda _: hosts
111 else:
112 assert False
113 def _get_hosts_wrapper(label=None, as_hostspec=False):
114 hosts = mk_hosts(label)
115 if as_hostspec:
116 return list(map(HostSpec, hosts))
117 return hosts
118
119 return mk_spec, _get_hosts_wrapper
120
121
122def run_scheduler_test(results, mk_spec, get_hosts_func, get_daemons_func, key_elems):
123 key = ' '.join('N' if e is None else str(e) for e in key_elems)
124 try:
125 assert_res = get_result(k(key), results)
126 except IndexError:
127 try:
128 spec = mk_spec()
129 host_res = HostAssignment(
130 spec=spec,
131 get_hosts_func=get_hosts_func,
132 get_daemons_func=get_daemons_func).place()
133 if isinstance(host_res, list):
134 e = ', '.join(repr(h.hostname) for h in host_res)
135 assert False, f'`(k("{key}"), exactly({e})),` not found'
136 assert False, f'`(k("{key}"), ...),` not found'
137 except OrchestratorError as e:
138 assert False, f'`(k("{key}"), error({type(e).__name__}, {repr(str(e))})),` not found'
139
140 for _ in range(10): # scheduler has a random component
141 try:
142 spec = mk_spec()
143 host_res = HostAssignment(
144 spec=spec,
145 get_hosts_func=get_hosts_func,
146 get_daemons_func=get_daemons_func).place()
147
148 assert_res(sorted([h.hostname for h in host_res]))
149 except Exception as e:
150 assert_res(e)
151
152
153# * first match from the top wins
154# * where e=[], *=any
155#
156# + list of known hosts available for scheduling (host_key)
157# | + hosts used for explict placement (explicit_key)
158# | | + count
159# | | | + section (host, label, pattern)
160# | | | | + expected result
161# | | | | |
162test_explicit_scheduler_results = [
163 (k("* * 0 *"), error(ServiceSpecValidationError, 'num/count must be > 1')),
164 (k("* e N l"), error(OrchestratorValidationError, 'Cannot place <ServiceSpec for service_name=mon>: No matching hosts for label mylabel')),
165 (k("* e N p"), error(OrchestratorValidationError, 'Cannot place <ServiceSpec for service_name=mon>: No matching hosts')),
166 (k("* e N h"), error(OrchestratorValidationError, 'placement spec is empty: no hosts, no label, no pattern, no count')),
167 (k("* e * *"), none),
168 (k("1 12 * h"), error(OrchestratorValidationError, "Cannot place <ServiceSpec for service_name=mon> on 2: Unknown hosts")),
169 (k("1 123 * h"), error(OrchestratorValidationError, "Cannot place <ServiceSpec for service_name=mon> on 2, 3: Unknown hosts")),
170 (k("1 * * *"), exactly('1')),
171 (k("12 1 * *"), exactly('1')),
172 (k("12 12 1 *"), one_of('1', '2')),
173 (k("12 12 * *"), exactly('1', '2')),
174 (k("12 123 * h"), error(OrchestratorValidationError, "Cannot place <ServiceSpec for service_name=mon> on 3: Unknown hosts")),
175 (k("12 123 1 *"), one_of('1', '2', '3')),
176 (k("12 123 * *"), two_of('1', '2', '3')),
177 (k("123 1 * *"), exactly('1')),
178 (k("123 12 1 *"), one_of('1', '2')),
179 (k("123 12 * *"), exactly('1', '2')),
180 (k("123 123 1 *"), one_of('1', '2', '3')),
181 (k("123 123 2 *"), two_of('1', '2', '3')),
182 (k("123 123 * *"), exactly('1', '2', '3')),
183]
184
185@pytest.mark.parametrize("spec_section_key,spec_section",
186 [
187 ('h', 'hosts'),
188 ('l', 'label'),
189 ('p', 'host_pattern'),
190 ])
191@pytest.mark.parametrize("count",
192 [
193 None,
194 0,
195 1,
196 2,
197 3,
198 ])
199@pytest.mark.parametrize("explicit_key, explicit",
200 [
201 ('e', []),
202 ('1', ['1']),
203 ('12', ['1', '2']),
204 ('123', ['1', '2', '3']),
205 ])
206@pytest.mark.parametrize("host_key, hosts",
207 [
208 ('1', ['1']),
209 ('12', ['1', '2']),
210 ('123', ['1', '2', '3']),
211 ])
212def test_explicit_scheduler(host_key, hosts,
213 explicit_key, explicit,
214 count,
215 spec_section_key, spec_section):
216
217 mk_spec, mk_hosts = mk_spec_and_host(spec_section, hosts, explicit_key, explicit, count)
218 run_scheduler_test(
219 results=test_explicit_scheduler_results,
220 mk_spec=mk_spec,
221 get_hosts_func=mk_hosts,
222 get_daemons_func=lambda _: [],
223 key_elems=(host_key, explicit_key, count, spec_section_key)
224 )
225
226
227# * first match from the top wins
228# * where e=[], *=any
229#
230# + list of known hosts available for scheduling (host_key)
231# | + hosts used for explict placement (explicit_key)
232# | | + count
233# | | | + existing daemons
234# | | | | + section (host, label, pattern)
235# | | | | | + expected result
236# | | | | | |
237test_scheduler_daemons_results = [
238 (k("* 1 * * *"), exactly('1')),
239 (k("1 123 * * h"), error(OrchestratorValidationError, 'Cannot place <ServiceSpec for service_name=mon> on 2, 3: Unknown hosts')),
240 (k("1 123 * * *"), exactly('1')),
241 (k("12 123 * * h"), error(OrchestratorValidationError, 'Cannot place <ServiceSpec for service_name=mon> on 3: Unknown hosts')),
242 (k("12 123 N * *"), exactly('1', '2')),
243 (k("12 123 1 * *"), one_of('1', '2')),
244 (k("12 123 2 * *"), exactly('1', '2')),
245 (k("12 123 3 * *"), exactly('1', '2')),
246 (k("123 123 N * *"), exactly('1', '2', '3')),
247 (k("123 123 1 e *"), one_of('1', '2', '3')),
248 (k("123 123 1 1 *"), exactly('1')),
249 (k("123 123 1 3 *"), exactly('3')),
250 (k("123 123 1 12 *"), one_of('1', '2')),
251 (k("123 123 1 112 *"), one_of('1', '2')),
252 (k("123 123 1 23 *"), one_of('2', '3')),
253 (k("123 123 1 123 *"), one_of('1', '2', '3')),
254 (k("123 123 2 e *"), two_of('1', '2', '3')),
255 (k("123 123 2 1 *"), _or(exactly('1', '2'), exactly('1', '3'))),
256 (k("123 123 2 3 *"), _or(exactly('1', '3'), exactly('2', '3'))),
257 (k("123 123 2 12 *"), exactly('1', '2')),
258 (k("123 123 2 112 *"), exactly('1', '2')),
259 (k("123 123 2 23 *"), exactly('2', '3')),
260 (k("123 123 2 123 *"), two_of('1', '2', '3')),
261 (k("123 123 3 * *"), exactly('1', '2', '3')),
262]
263
264
265@pytest.mark.parametrize("spec_section_key,spec_section",
266 [
267 ('h', 'hosts'),
268 ('l', 'label'),
269 ('p', 'host_pattern'),
270 ])
271@pytest.mark.parametrize("daemons_key, daemons",
272 [
273 ('e', []),
274 ('1', ['1']),
275 ('3', ['3']),
276 ('12', ['1', '2']),
277 ('112', ['1', '1', '2']), # deal with existing co-located daemons
278 ('23', ['2', '3']),
279 ('123', ['1', '2', '3']),
280 ])
281@pytest.mark.parametrize("count",
282 [
283 None,
284 1,
285 2,
286 3,
287 ])
288@pytest.mark.parametrize("explicit_key, explicit",
289 [
290 ('1', ['1']),
291 ('123', ['1', '2', '3']),
292 ])
293@pytest.mark.parametrize("host_key, hosts",
294 [
295 ('1', ['1']),
296 ('12', ['1', '2']),
297 ('123', ['1', '2', '3']),
298 ])
299def test_scheduler_daemons(host_key, hosts,
300 explicit_key, explicit,
301 count,
302 daemons_key, daemons,
303 spec_section_key, spec_section):
304 mk_spec, mk_hosts = mk_spec_and_host(spec_section, hosts, explicit_key, explicit, count)
305 dds = [
306 DaemonDescription('mon', d, d)
307 for d in daemons
308 ]
309 run_scheduler_test(
310 results=test_scheduler_daemons_results,
311 mk_spec=mk_spec,
312 get_hosts_func=mk_hosts,
313 get_daemons_func=lambda _: dds,
314 key_elems=(host_key, explicit_key, count, daemons_key, spec_section_key)
315 )
316
317
318## =========================
9f95a23c
TL
319
320
321class NodeAssignmentTest(NamedTuple):
322 service_type: str
323 placement: PlacementSpec
324 hosts: List[str]
325 daemons: List[DaemonDescription]
326 expected: List[str]
327
328@pytest.mark.parametrize("service_type,placement,hosts,daemons,expected",
329 [
330 # just hosts
331 NodeAssignmentTest(
332 'mon',
333 PlacementSpec(hosts=['smithi060:[v2:172.21.15.60:3301,v1:172.21.15.60:6790]=c']),
334 ['smithi060'],
335 [],
336 ['smithi060']
337 ),
338 # all_hosts
339 NodeAssignmentTest(
340 'mon',
341 PlacementSpec(host_pattern='*'),
342 'host1 host2 host3'.split(),
343 [
344 DaemonDescription('mon', 'a', 'host1'),
345 DaemonDescription('mon', 'b', 'host2'),
346 ],
347 ['host1', 'host2', 'host3']
348 ),
349 # count that is bigger than the amount of hosts. Truncate to len(hosts)
350 # RGWs should not be co-located to each other.
351 NodeAssignmentTest(
352 'rgw',
353 PlacementSpec(count=4),
354 'host1 host2 host3'.split(),
355 [],
356 ['host1', 'host2', 'host3']
357 ),
358 # count + partial host list
359 NodeAssignmentTest(
360 'mon',
361 PlacementSpec(count=3, hosts=['host3']),
362 'host1 host2 host3'.split(),
363 [
364 DaemonDescription('mon', 'a', 'host1'),
365 DaemonDescription('mon', 'b', 'host2'),
366 ],
f6b5b4d7 367 ['host3']
9f95a23c
TL
368 ),
369 # count 1 + partial host list
370 NodeAssignmentTest(
371 'mon',
372 PlacementSpec(count=1, hosts=['host3']),
373 'host1 host2 host3'.split(),
374 [
375 DaemonDescription('mon', 'a', 'host1'),
376 DaemonDescription('mon', 'b', 'host2'),
377 ],
378 ['host3']
379 ),
380 # count + partial host list + existing
381 NodeAssignmentTest(
382 'mon',
383 PlacementSpec(count=2, hosts=['host3']),
384 'host1 host2 host3'.split(),
385 [
386 DaemonDescription('mon', 'a', 'host1'),
387 ],
f6b5b4d7 388 ['host3']
9f95a23c
TL
389 ),
390 # count + partial host list + existing (deterministic)
391 NodeAssignmentTest(
392 'mon',
393 PlacementSpec(count=2, hosts=['host1']),
394 'host1 host2'.split(),
395 [
396 DaemonDescription('mon', 'a', 'host1'),
397 ],
f6b5b4d7 398 ['host1']
9f95a23c
TL
399 ),
400 # count + partial host list + existing (deterministic)
401 NodeAssignmentTest(
402 'mon',
403 PlacementSpec(count=2, hosts=['host1']),
404 'host1 host2'.split(),
405 [
406 DaemonDescription('mon', 'a', 'host2'),
407 ],
f6b5b4d7 408 ['host1']
9f95a23c
TL
409 ),
410 # label only
411 NodeAssignmentTest(
412 'mon',
413 PlacementSpec(label='foo'),
414 'host1 host2 host3'.split(),
415 [],
416 ['host1', 'host2', 'host3']
417 ),
418 # host_pattern
419 NodeAssignmentTest(
420 'mon',
421 PlacementSpec(host_pattern='mon*'),
422 'monhost1 monhost2 datahost'.split(),
423 [],
424 ['monhost1', 'monhost2']
425 ),
426 ])
427def test_node_assignment(service_type, placement, hosts, daemons, expected):
f6b5b4d7
TL
428 def get_hosts_func(label=None, as_hostspec=False):
429 if as_hostspec:
430 return [HostSpec(h) for h in hosts]
431 return hosts
432
433 service_id = None
434 if service_type == 'rgw':
435 service_id = 'realm.zone'
436
437 spec = ServiceSpec(service_type=service_type,
438 service_id=service_id,
439 placement=placement)
440
9f95a23c 441 hosts = HostAssignment(
f6b5b4d7
TL
442 spec=spec,
443 get_hosts_func=get_hosts_func,
9f95a23c
TL
444 get_daemons_func=lambda _: daemons).place()
445 assert sorted([h.hostname for h in hosts]) == sorted(expected)
446
e306af50 447
9f95a23c
TL
448class NodeAssignmentTest2(NamedTuple):
449 service_type: str
450 placement: PlacementSpec
451 hosts: List[str]
452 daemons: List[DaemonDescription]
453 expected_len: int
454 in_set: List[str]
455
456@pytest.mark.parametrize("service_type,placement,hosts,daemons,expected_len,in_set",
457 [
9f95a23c
TL
458 # just count
459 NodeAssignmentTest2(
460 'mon',
461 PlacementSpec(count=1),
462 'host1 host2 host3'.split(),
463 [],
464 1,
465 ['host1', 'host2', 'host3'],
466 ),
467
468 # hosts + (smaller) count
469 NodeAssignmentTest2(
470 'mon',
471 PlacementSpec(count=1, hosts='host1 host2'.split()),
472 'host1 host2'.split(),
473 [],
474 1,
475 ['host1', 'host2'],
476 ),
477 # hosts + (smaller) count, existing
478 NodeAssignmentTest2(
479 'mon',
480 PlacementSpec(count=1, hosts='host1 host2 host3'.split()),
481 'host1 host2 host3'.split(),
482 [DaemonDescription('mon', 'mon.a', 'host1'),],
483 1,
484 ['host1', 'host2', 'host3'],
485 ),
486 # hosts + (smaller) count, (more) existing
487 NodeAssignmentTest2(
488 'mon',
489 PlacementSpec(count=1, hosts='host1 host2 host3'.split()),
490 'host1 host2 host3'.split(),
491 [
492 DaemonDescription('mon', 'a', 'host1'),
493 DaemonDescription('mon', 'b', 'host2'),
494 ],
495 1,
496 ['host1', 'host2']
497 ),
498 # count + partial host list
499 NodeAssignmentTest2(
500 'mon',
501 PlacementSpec(count=2, hosts=['host3']),
502 'host1 host2 host3'.split(),
503 [],
f6b5b4d7 504 1,
9f95a23c
TL
505 ['host1', 'host2', 'host3']
506 ),
507 # label + count
508 NodeAssignmentTest2(
509 'mon',
510 PlacementSpec(count=1, label='foo'),
511 'host1 host2 host3'.split(),
512 [],
513 1,
514 ['host1', 'host2', 'host3']
515 ),
516 ])
517def test_node_assignment2(service_type, placement, hosts,
518 daemons, expected_len, in_set):
f6b5b4d7
TL
519 def get_hosts_func(label=None, as_hostspec=False):
520 if as_hostspec:
521 return [HostSpec(h) for h in hosts]
522 return hosts
523
9f95a23c
TL
524 hosts = HostAssignment(
525 spec=ServiceSpec(service_type, placement=placement),
f6b5b4d7 526 get_hosts_func=get_hosts_func,
9f95a23c
TL
527 get_daemons_func=lambda _: daemons).place()
528 assert len(hosts) == expected_len
529 for h in [h.hostname for h in hosts]:
530 assert h in in_set
531
532@pytest.mark.parametrize("service_type,placement,hosts,daemons,expected_len,must_have",
533 [
534 # hosts + (smaller) count, (more) existing
535 NodeAssignmentTest2(
536 'mon',
537 PlacementSpec(count=3, hosts='host3'.split()),
538 'host1 host2 host3'.split(),
539 [],
f6b5b4d7 540 1,
9f95a23c
TL
541 ['host3']
542 ),
543 # count + partial host list
544 NodeAssignmentTest2(
545 'mon',
546 PlacementSpec(count=2, hosts=['host3']),
547 'host1 host2 host3'.split(),
548 [],
f6b5b4d7 549 1,
9f95a23c
TL
550 ['host3']
551 ),
552 ])
553def test_node_assignment3(service_type, placement, hosts,
554 daemons, expected_len, must_have):
f6b5b4d7
TL
555 def get_hosts_func(label=None, as_hostspec=False):
556 if as_hostspec:
557 return [HostSpec(h) for h in hosts]
558 return hosts
559
9f95a23c
TL
560 hosts = HostAssignment(
561 spec=ServiceSpec(service_type, placement=placement),
f6b5b4d7 562 get_hosts_func=get_hosts_func,
9f95a23c
TL
563 get_daemons_func=lambda _: daemons).place()
564 assert len(hosts) == expected_len
565 for h in must_have:
566 assert h in [h.hostname for h in hosts]
567
568
569@pytest.mark.parametrize("placement",
570 [
571 ('1 *'),
572 ('* label:foo'),
573 ('* host1 host2'),
574 ('hostname12hostname12hostname12hostname12hostname12hostname12hostname12'), # > 63 chars
575 ])
576def test_bad_placements(placement):
577 try:
578 s = PlacementSpec.from_string(placement.split(' '))
579 assert False
580 except ServiceSpecValidationError as e:
581 pass
582
583
584class NodeAssignmentTestBadSpec(NamedTuple):
585 service_type: str
586 placement: PlacementSpec
587 hosts: List[str]
588 daemons: List[DaemonDescription]
589 expected: str
590@pytest.mark.parametrize("service_type,placement,hosts,daemons,expected",
591 [
592 # unknown host
593 NodeAssignmentTestBadSpec(
594 'mon',
595 PlacementSpec(hosts=['unknownhost']),
596 ['knownhost'],
597 [],
f6b5b4d7 598 "Cannot place <ServiceSpec for service_name=mon> on unknownhost: Unknown hosts"
9f95a23c
TL
599 ),
600 # unknown host pattern
601 NodeAssignmentTestBadSpec(
602 'mon',
603 PlacementSpec(host_pattern='unknownhost'),
604 ['knownhost'],
605 [],
606 "Cannot place <ServiceSpec for service_name=mon>: No matching hosts"
607 ),
608 # unknown label
609 NodeAssignmentTestBadSpec(
610 'mon',
611 PlacementSpec(label='unknownlabel'),
612 [],
613 [],
614 "Cannot place <ServiceSpec for service_name=mon>: No matching hosts for label unknownlabel"
615 ),
616 ])
617def test_bad_specs(service_type, placement, hosts, daemons, expected):
f6b5b4d7
TL
618 def get_hosts_func(label=None, as_hostspec=False):
619 if as_hostspec:
620 return [HostSpec(h) for h in hosts]
621 return hosts
9f95a23c
TL
622 with pytest.raises(OrchestratorValidationError) as e:
623 hosts = HostAssignment(
624 spec=ServiceSpec(service_type, placement=placement),
f6b5b4d7 625 get_hosts_func=get_hosts_func,
9f95a23c
TL
626 get_daemons_func=lambda _: daemons).place()
627 assert str(e.value) == expected
f6b5b4d7
TL
628
629class ActiveAssignmentTest(NamedTuple):
630 service_type: str
631 placement: PlacementSpec
632 hosts: List[str]
633 daemons: List[DaemonDescription]
634 expected: List[List[str]]
635
636
637@pytest.mark.parametrize("service_type,placement,hosts,daemons,expected",
638 [
639 ActiveAssignmentTest(
640 'mgr',
641 PlacementSpec(count=2),
642 'host1 host2 host3'.split(),
643 [
644 DaemonDescription('mgr', 'a', 'host1', is_active=True),
645 DaemonDescription('mgr', 'b', 'host2'),
646 DaemonDescription('mgr', 'c', 'host3'),
647 ],
648 [['host1', 'host2'], ['host1', 'host3']]
649 ),
650 ActiveAssignmentTest(
651 'mgr',
652 PlacementSpec(count=2),
653 'host1 host2 host3'.split(),
654 [
655 DaemonDescription('mgr', 'a', 'host1'),
656 DaemonDescription('mgr', 'b', 'host2'),
657 DaemonDescription('mgr', 'c', 'host3', is_active=True),
658 ],
659 [['host1', 'host3'], ['host2', 'host3']]
660 ),
661 ActiveAssignmentTest(
662 'mgr',
663 PlacementSpec(count=1),
664 'host1 host2 host3'.split(),
665 [
666 DaemonDescription('mgr', 'a', 'host1'),
667 DaemonDescription('mgr', 'b', 'host2', is_active=True),
668 DaemonDescription('mgr', 'c', 'host3'),
669 ],
670 [['host2']]
671 ),
672 ActiveAssignmentTest(
673 'mgr',
674 PlacementSpec(count=1),
675 'host1 host2 host3'.split(),
676 [
677 DaemonDescription('mgr', 'a', 'host1'),
678 DaemonDescription('mgr', 'b', 'host2'),
679 DaemonDescription('mgr', 'c', 'host3', is_active=True),
680 ],
681 [['host3']]
682 ),
683 ActiveAssignmentTest(
684 'mgr',
685 PlacementSpec(count=1),
686 'host1 host2 host3'.split(),
687 [
688 DaemonDescription('mgr', 'a', 'host1', is_active=True),
689 DaemonDescription('mgr', 'b', 'host2'),
690 DaemonDescription('mgr', 'c', 'host3', is_active=True),
691 ],
692 [['host1'], ['host3']]
693 ),
694 ActiveAssignmentTest(
695 'mgr',
696 PlacementSpec(count=2),
697 'host1 host2 host3'.split(),
698 [
699 DaemonDescription('mgr', 'a', 'host1'),
700 DaemonDescription('mgr', 'b', 'host2', is_active=True),
701 DaemonDescription('mgr', 'c', 'host3', is_active=True),
702 ],
703 [['host2', 'host3']]
704 ),
705 ActiveAssignmentTest(
706 'mgr',
707 PlacementSpec(count=1),
708 'host1 host2 host3'.split(),
709 [
710 DaemonDescription('mgr', 'a', 'host1', is_active=True),
711 DaemonDescription('mgr', 'b', 'host2', is_active=True),
712 DaemonDescription('mgr', 'c', 'host3', is_active=True),
713 ],
714 [['host1'], ['host2'], ['host3']]
715 ),
716 ActiveAssignmentTest(
717 'mgr',
718 PlacementSpec(count=1),
719 'host1 host2 host3'.split(),
720 [
721 DaemonDescription('mgr', 'a', 'host1', is_active=True),
722 DaemonDescription('mgr', 'a2', 'host1'),
723 DaemonDescription('mgr', 'b', 'host2'),
724 DaemonDescription('mgr', 'c', 'host3'),
725 ],
726 [['host1']]
727 ),
728 ActiveAssignmentTest(
729 'mgr',
730 PlacementSpec(count=1),
731 'host1 host2 host3'.split(),
732 [
733 DaemonDescription('mgr', 'a', 'host1', is_active=True),
734 DaemonDescription('mgr', 'a2', 'host1', is_active=True),
735 DaemonDescription('mgr', 'b', 'host2'),
736 DaemonDescription('mgr', 'c', 'host3'),
737 ],
738 [['host1']]
739 ),
740 ActiveAssignmentTest(
741 'mgr',
742 PlacementSpec(count=2),
743 'host1 host2 host3'.split(),
744 [
745 DaemonDescription('mgr', 'a', 'host1', is_active=True),
746 DaemonDescription('mgr', 'a2', 'host1'),
747 DaemonDescription('mgr', 'b', 'host2'),
748 DaemonDescription('mgr', 'c', 'host3', is_active=True),
749 ],
750 [['host1', 'host3']]
751 ),
752 # Explicit placement should override preference for active daemon
753 ActiveAssignmentTest(
754 'mgr',
755 PlacementSpec(count=1, hosts=['host1']),
756 'host1 host2 host3'.split(),
757 [
758 DaemonDescription('mgr', 'a', 'host1'),
759 DaemonDescription('mgr', 'b', 'host2'),
760 DaemonDescription('mgr', 'c', 'host3', is_active=True),
761 ],
762 [['host1']]
763 ),
764
765 ])
766def test_active_assignment(service_type, placement, hosts, daemons, expected):
767 def get_hosts_func(label=None, as_hostspec=False):
768 if as_hostspec:
769 return [HostSpec(h) for h in hosts]
770 return hosts
771
772 spec = ServiceSpec(service_type=service_type,
773 service_id=None,
774 placement=placement)
775
776 hosts = HostAssignment(
777 spec=spec,
778 get_hosts_func=get_hosts_func,
779 get_daemons_func=lambda _: daemons).place()
780 assert sorted([h.hostname for h in hosts]) in expected