1 import inspect
2 from unittest import TestCase
3
4 from pcs.common import file_type_codes
5 from pcs.common.fencing_topology import (
6 TARGET_TYPE_ATTRIBUTE,
7 TARGET_TYPE_NODE,
8 TARGET_TYPE_REGEXP,
9 )
10 from pcs.common.file import RawFileError
11 from pcs.common.reports import const
12 from pcs.common.reports import messages as reports
13 from pcs.common.resource_agent.dto import ResourceAgentNameDto
14 from pcs.common.resource_status import ResourceState
15 from pcs.common.types import CibRuleExpressionType
16
17 # pylint: disable=too-many-lines
18
19
20 class AllClassesTested(TestCase):
21 def test_success(self):
22 self.maxDiff = None
23 message_classes = frozenset(
24 name
25 for name, member in inspect.getmembers(reports, inspect.isclass)
26 if issubclass(member, reports.ReportItemMessage)
27 and member
28 not in {reports.ReportItemMessage, reports.LegacyCommonMessage}
29 )
30 test_classes = frozenset(
31 name
32 for name, member in inspect.getmembers(
33 inspect.getmodule(self), inspect.isclass
34 )
35 if issubclass(member, NameBuildTest)
36 )
37 untested = sorted(message_classes - test_classes)
38 self.assertEqual(
39 untested,
40 [],
41 f"It seems {len(untested)} subclass(es) of 'ReportItemMessage' are "
42 "missing tests. Make sure the test classes have the same name as "
43 "the code classes.",
44 )
45
46
47 class NameBuildTest(TestCase):
48 """
49 Base class for the testing of message building.
50 """
51
52 def assert_message_from_report(self, message, report):
53 self.maxDiff = None
54 self.assertEqual(message, report.message)
55
56
57 class ResourceForConstraintIsMultiinstance(NameBuildTest):
58 def test_success(self):
59 self.assertEqual(
60 (
61 "resource1 is a bundle resource, you should use the "
62 "bundle id: parent1 when adding constraints"
63 ),
64 reports.ResourceForConstraintIsMultiinstance(
65 "resource1", "bundle", "parent1"
66 ).message,
67 )
68
69
70 class DuplicateConstraintsExist(NameBuildTest):
71 def test_build_singular(self):
72 self.assert_message_from_report(
73 "Duplicate constraint already exists",
74 reports.DuplicateConstraintsExist(["c1"]),
75 )
76
77 def test_build_plural(self):
78 self.assert_message_from_report(
79 "Duplicate constraints already exist",
80 reports.DuplicateConstraintsExist(["c1", "c3", "c0"]),
81 )
82
83
84 class EmptyResourceSet(NameBuildTest):
85 def test_success(self):
86 self.assert_message_from_report(
87 "Resource set is empty",
88 reports.EmptyResourceSet(),
89 )
90
91
92 class EmptyResourceSetList(NameBuildTest):
93 def test_success(self):
94 self.assert_message_from_report(
95 "Resource set list is empty",
96 reports.EmptyResourceSetList(),
97 )
98
99
100 class CannotSetOrderConstraintsForResourcesInTheSameGroup(NameBuildTest):
101 def test_success(self):
102 self.assert_message_from_report(
103 "Cannot create an order constraint for resources in the same group",
104 reports.CannotSetOrderConstraintsForResourcesInTheSameGroup(),
105 )
106
107
108 class RequiredOptionsAreMissing(NameBuildTest):
109 def test_build_message_with_type(self):
110 self.assert_message_from_report(
111 "required TYPE option 'NAME' is missing",
112 reports.RequiredOptionsAreMissing(["NAME"], option_type="TYPE"),
113 )
114
115 def test_build_message_without_type(self):
116 self.assert_message_from_report(
117 "required option 'NAME' is missing",
118 reports.RequiredOptionsAreMissing(["NAME"]),
119 )
120
121 def test_build_message_with_multiple_names(self):
122 self.assert_message_from_report(
123 "required options 'ANOTHER', 'NAME' are missing",
124 reports.RequiredOptionsAreMissing(["NAME", "ANOTHER"]),
125 )
126
127
128 class PrerequisiteOptionIsMissing(NameBuildTest):
129 def test_without_type(self):
130 self.assert_message_from_report(
131 "If option 'a' is specified, option 'b' must be specified as well",
132 reports.PrerequisiteOptionIsMissing("a", "b"),
133 )
134
135 def test_with_type(self):
136 self.assert_message_from_report(
137 "If some option 'a' is specified, "
138 "other option 'b' must be specified as well",
139 reports.PrerequisiteOptionIsMissing("a", "b", "some", "other"),
140 )
141
142
143 class PrerequisiteOptionMustBeEnabledAsWell(NameBuildTest):
144 def test_without_type(self):
145 self.assert_message_from_report(
146 "If option 'a' is enabled, option 'b' must be enabled as well",
147 reports.PrerequisiteOptionMustBeEnabledAsWell("a", "b"),
148 )
149
150 def test_with_type(self):
151 self.assert_message_from_report(
152 "If some option 'a' is enabled, "
153 "other option 'b' must be enabled as well",
154 reports.PrerequisiteOptionMustBeEnabledAsWell(
155 "a", "b", "some", "other"
156 ),
157 )
158
159
160 class PrerequisiteOptionMustBeDisabled(NameBuildTest):
161 def test_without_type(self):
162 self.assert_message_from_report(
163 "If option 'a' is enabled, option 'b' must be disabled",
164 reports.PrerequisiteOptionMustBeDisabled("a", "b"),
165 )
166
167 def test_with_type(self):
168 self.assert_message_from_report(
169 "If some option 'a' is enabled, other option 'b' must be disabled",
170 reports.PrerequisiteOptionMustBeDisabled("a", "b", "some", "other"),
171 )
172
173
174 class PrerequisiteOptionMustNotBeSet(NameBuildTest):
175 def test_without_type(self):
176 self.assert_message_from_report(
177 "Cannot set option 'a' because option 'b' is already set",
178 reports.PrerequisiteOptionMustNotBeSet(
179 "a",
180 "b",
181 ),
182 )
183
184 def test_with_type(self):
185 self.assert_message_from_report(
186 "Cannot set some option 'a' because other option 'b' is "
187 "already set",
188 reports.PrerequisiteOptionMustNotBeSet(
189 "a",
190 "b",
191 option_type="some",
192 prerequisite_type="other",
193 ),
194 )
195
196
197 class RequiredOptionOfAlternativesIsMissing(NameBuildTest):
198 def test_minimal(self):
199 self.assert_message_from_report(
200 "option 'aAa', 'bBb' or 'cCc' has to be specified",
201 reports.RequiredOptionOfAlternativesIsMissing(
202 ["aAa", "cCc", "bBb"]
203 ),
204 )
205
206 def test_with_type(self):
207 self.assert_message_from_report(
208 "test option 'aAa' has to be specified",
209 reports.RequiredOptionOfAlternativesIsMissing(
210 ["aAa"], option_type="test"
211 ),
212 )
213
214 def test_with_deprecated(self):
215 self.assert_message_from_report(
216 (
217 "option 'bBb', 'aAa' (deprecated) or 'cCc' (deprecated) has "
218 "to be specified"
219 ),
220 reports.RequiredOptionOfAlternativesIsMissing(
221 ["aAa", "cCc", "bBb"], deprecated_names=["cCc", "aAa"]
222 ),
223 )
224
225
226 class InvalidOptions(NameBuildTest):
227 def test_build_message_with_type(self):
228 self.assert_message_from_report(
229 "invalid TYPE option 'NAME', allowed options are: 'FIRST', "
230 "'SECOND'",
231 reports.InvalidOptions(["NAME"], ["SECOND", "FIRST"], "TYPE"),
232 )
233
234 def test_build_message_without_type(self):
235 self.assert_message_from_report(
236 "invalid option 'NAME', allowed options are: 'FIRST', 'SECOND'",
237 reports.InvalidOptions(["NAME"], ["FIRST", "SECOND"], ""),
238 )
239
240 def test_build_message_with_multiple_names(self):
241 self.assert_message_from_report(
242 "invalid options: 'ANOTHER', 'NAME', allowed option is 'FIRST'",
243 reports.InvalidOptions(["NAME", "ANOTHER"], ["FIRST"], ""),
244 )
245
246 def test_pattern(self):
247 self.assert_message_from_report(
248 (
249 "invalid option 'NAME', allowed are options matching patterns: "
250 "'exec_<name>'"
251 ),
252 reports.InvalidOptions(["NAME"], [], "", ["exec_<name>"]),
253 )
254
255 def test_allowed_and_patterns(self):
256 self.assert_message_from_report(
257 (
258 "invalid option 'NAME', allowed option is 'FIRST' and options "
259 "matching patterns: 'exec_<name>'"
260 ),
261 reports.InvalidOptions(
262 ["NAME"], ["FIRST"], "", allowed_patterns=["exec_<name>"]
263 ),
264 )
265
266 def test_no_allowed_options(self):
267 self.assert_message_from_report(
268 "invalid options: 'ANOTHER', 'NAME', there are no options allowed",
269 reports.InvalidOptions(["NAME", "ANOTHER"], [], ""),
270 )
271
272
273 class InvalidUserdefinedOptions(NameBuildTest):
274 def test_without_type(self):
275 self.assert_message_from_report(
276 (
277 "invalid option 'exec_NAME', options may contain "
278 "a-z A-Z 0-9 /_- characters only"
279 ),
280 reports.InvalidUserdefinedOptions(["exec_NAME"], "a-z A-Z 0-9 /_-"),
281 )
282
283 def test_with_type(self):
284 self.assert_message_from_report(
285 (
286 "invalid heuristics option 'exec_NAME', heuristics options may "
287 "contain a-z A-Z 0-9 /_- characters only"
288 ),
289 reports.InvalidUserdefinedOptions(
290 ["exec_NAME"], "a-z A-Z 0-9 /_-", "heuristics"
291 ),
292 )
293
294 def test_more_options(self):
295 self.assert_message_from_report(
296 (
297 "invalid TYPE options: 'ANOTHER', 'NAME', TYPE options may "
298 "contain a-z A-Z 0-9 /_- characters only"
299 ),
300 reports.InvalidUserdefinedOptions(
301 ["NAME", "ANOTHER"], "a-z A-Z 0-9 /_-", "TYPE"
302 ),
303 )
304
305
306 class InvalidOptionType(NameBuildTest):
307 def test_allowed_string(self):
308 self.assert_message_from_report(
309 "specified option name is not valid, use allowed types",
310 reports.InvalidOptionType("option name", "allowed types"),
311 )
312
313 def test_allowed_list(self):
314 self.assert_message_from_report(
315 "specified option name is not valid, use 'allowed', 'types'",
316 reports.InvalidOptionType("option name", ["types", "allowed"]),
317 )
318
319
320 class InvalidOptionValue(NameBuildTest):
321 def test_multiple_allowed_values(self):
322 self.assert_message_from_report(
323 "'VALUE' is not a valid NAME value, use 'FIRST', 'SECOND'",
324 reports.InvalidOptionValue("NAME", "VALUE", ["SECOND", "FIRST"]),
325 )
326
327 def test_textual_hint(self):
328 self.assert_message_from_report(
329 "'VALUE' is not a valid NAME value, use some hint",
330 reports.InvalidOptionValue("NAME", "VALUE", "some hint"),
331 )
332
333 def test_cannot_be_empty(self):
334 self.assert_message_from_report(
335 "NAME cannot be empty",
336 reports.InvalidOptionValue(
337 "NAME", "VALUE", allowed_values=None, cannot_be_empty=True
338 ),
339 )
340
341 def test_cannot_be_empty_with_hint(self):
342 self.assert_message_from_report(
343 "NAME cannot be empty, use 'FIRST', 'SECOND'",
344 reports.InvalidOptionValue(
345 "NAME", "VALUE", ["SECOND", "FIRST"], cannot_be_empty=True
346 ),
347 )
348
349 def test_forbidden_characters(self):
350 self.assert_message_from_report(
351 r"NAME cannot contain }{\r\n characters",
352 reports.InvalidOptionValue(
353 "NAME",
354 "VALUE",
355 allowed_values=None,
356 forbidden_characters="}{\\r\\n",
357 ),
358 )
359
360 def test_forbidden_characters_with_hint(self):
361 self.assert_message_from_report(
362 r"NAME cannot contain }{\r\n characters, use 'FIRST', 'SECOND'",
363 reports.InvalidOptionValue(
364 "NAME",
365 "VALUE",
366 ["SECOND", "FIRST"],
367 forbidden_characters="}{\\r\\n",
368 ),
369 )
370
371 def test_cannot_be_empty_and_forbidden_characters(self):
372 self.assert_message_from_report(
373 "NAME cannot be empty, use 'FIRST', 'SECOND'",
374 reports.InvalidOptionValue(
375 "NAME", "VALUE", ["SECOND", "FIRST"], True
376 ),
377 )
378
379
380 class DeprecatedOption(NameBuildTest):
381 def test_no_desc_hint_array(self):
382 self.assert_message_from_report(
383 (
384 "option 'option name' is deprecated and might be removed in a "
385 "future release, therefore it should not be used, use 'new_a', "
386 "'new_b' instead"
387 ),
388 reports.DeprecatedOption("option name", ["new_b", "new_a"], ""),
389 )
390
391 def test_desc_hint_string(self):
392 self.assert_message_from_report(
393 (
394 "option type option 'option name' is deprecated and might be "
395 "removed in a future release, therefore it should not be used, "
396 "use 'new option' instead"
397 ),
398 reports.DeprecatedOption(
399 "option name", ["new option"], "option type"
400 ),
401 )
402
403 def test_empty_hint(self):
404 self.assert_message_from_report(
405 (
406 "option 'option name' is deprecated and might be removed in a "
407 "future release, therefore it should not be used"
408 ),
409 reports.DeprecatedOption("option name", [], ""),
410 )
411
412
413 class DeprecatedOptionValue(NameBuildTest):
414 def test_replaced_by(self):
415 self.assert_message_from_report(
416 (
417 "Value 'deprecatedValue' of option optionA is deprecated and "
418 "might be removed in a future release, therefore it should not "
419 "be used, use 'newValue' value instead"
420 ),
421 reports.DeprecatedOptionValue(
422 "optionA", "deprecatedValue", "newValue"
423 ),
424 )
425
426 def test_no_replacement(self):
427 self.assert_message_from_report(
428 (
429 "Value 'deprecatedValue' of option optionA is deprecated and "
430 "might be removed in a future release, therefore it should not "
431 "be used"
432 ),
433 reports.DeprecatedOptionValue("optionA", "deprecatedValue"),
434 )
435
436
437 class MutuallyExclusiveOptions(NameBuildTest):
438 def test_build_message(self):
439 self.assert_message_from_report(
440 "Only one of some options 'a' and 'b' can be used",
441 reports.MutuallyExclusiveOptions(["b", "a"], "some"),
442 )
443
444
445 class InvalidCibContent(NameBuildTest):
446 def test_message_can_be_more_verbose(self):
447 report = "no verbose\noutput\n"
448 self.assert_message_from_report(
449 "invalid cib:\n{0}".format(report),
450 reports.InvalidCibContent(report, True),
451 )
452
453 def test_message_cannot_be_more_verbose(self):
454 report = "some verbose\noutput"
455 self.assert_message_from_report(
456 "invalid cib:\n{0}".format(report),
457 reports.InvalidCibContent(report, False),
458 )
459
460
461 class InvalidIdIsEmpty(NameBuildTest):
462 def test_all(self):
463 self.assert_message_from_report(
464 "description cannot be empty",
465 reports.InvalidIdIsEmpty("description"),
466 )
467
468
469 class InvalidIdBadChar(NameBuildTest):
470 def test_build_message_with_first_char_invalid(self):
471 self.assert_message_from_report(
472 (
473 "invalid ID_DESCRIPTION 'ID', 'INVALID_CHARACTER' is not a"
474 " valid first character for a ID_DESCRIPTION"
475 ),
476 reports.InvalidIdBadChar(
477 "ID", "ID_DESCRIPTION", "INVALID_CHARACTER", is_first_char=True
478 ),
479 )
480
481 def test_build_message_with_non_first_char_invalid(self):
482 self.assert_message_from_report(
483 (
484 "invalid ID_DESCRIPTION 'ID', 'INVALID_CHARACTER' is not a"
485 " valid character for a ID_DESCRIPTION"
486 ),
487 reports.InvalidIdBadChar(
488 "ID", "ID_DESCRIPTION", "INVALID_CHARACTER", is_first_char=False
489 ),
490 )
491
492
493 class InvalidIdType(NameBuildTest):
494 def test_success(self):
495 self.assert_message_from_report(
496 (
497 "'entered' is not a valid type of ID specification, "
498 "use 'expected1', 'expected2'"
499 ),
500 reports.InvalidIdType("entered", ["expected1", "expected2"]),
501 )
502
503
504 class InvalidTimeoutValue(NameBuildTest):
505 def test_all(self):
506 self.assert_message_from_report(
507 "'24h' is not a valid number of seconds to wait",
508 reports.InvalidTimeoutValue("24h"),
509 )
510
511
512 class InvalidScore(NameBuildTest):
513 def test_all(self):
514 self.assert_message_from_report(
515 "invalid score '1M', use integer or INFINITY or -INFINITY",
516 reports.InvalidScore("1M"),
517 )
518
519
520 class RunExternalProcessStarted(NameBuildTest):
521 def test_build_message_minimal(self):
522 self.assert_message_from_report(
523 "Running: COMMAND\nEnvironment:\n",
524 reports.RunExternalProcessStarted("COMMAND", "", {}),
525 )
526
527 def test_build_message_with_stdin(self):
528 self.assert_message_from_report(
529 (
530 "Running: COMMAND\nEnvironment:\n"
531 "--Debug Input Start--\n"
532 "STDIN\n"
533 "--Debug Input End--\n"
534 ),
535 reports.RunExternalProcessStarted("COMMAND", "STDIN", {}),
536 )
537
538 def test_build_message_with_env(self):
539 self.assert_message_from_report(
540 ("Running: COMMAND\nEnvironment:\n env_a=A\n env_b=B\n"),
541 reports.RunExternalProcessStarted(
542 "COMMAND",
543 "",
544 {
545 "env_a": "A",
546 "env_b": "B",
547 },
548 ),
549 )
550
551 def test_build_message_maximal(self):
552 self.assert_message_from_report(
553 (
554 "Running: COMMAND\nEnvironment:\n"
555 " env_a=A\n"
556 " env_b=B\n"
557 "--Debug Input Start--\n"
558 "STDIN\n"
559 "--Debug Input End--\n"
560 ),
561 reports.RunExternalProcessStarted(
562 "COMMAND",
563 "STDIN",
564 {
565 "env_a": "A",
566 "env_b": "B",
567 },
568 ),
569 )
570
571 def test_insidious_environment(self):
572 self.assert_message_from_report(
573 (
574 "Running: COMMAND\nEnvironment:\n"
575 " test=a:{green},b:{red}\n"
576 "--Debug Input Start--\n"
577 "STDIN\n"
578 "--Debug Input End--\n"
579 ),
580 reports.RunExternalProcessStarted(
581 "COMMAND",
582 "STDIN",
583 {
584 "test": "a:{green},b:{red}",
585 },
586 ),
587 )
588
589
590 class RunExternalProcessFinished(NameBuildTest):
591 def test_all(self):
592 self.assert_message_from_report(
593 (
594 "Finished running: com-mand\n"
595 "Return value: 0\n"
596 "--Debug Stdout Start--\n"
597 "STDOUT\n"
598 "--Debug Stdout End--\n"
599 "--Debug Stderr Start--\n"
600 "STDERR\n"
601 "--Debug Stderr End--\n"
602 ),
603 reports.RunExternalProcessFinished(
604 "com-mand", 0, "STDOUT", "STDERR"
605 ),
606 )
607
608
609 class RunExternalProcessError(NameBuildTest):
610 def test_all(self):
611 self.assert_message_from_report(
612 "unable to run command com-mand: reason",
613 reports.RunExternalProcessError("com-mand", "reason"),
614 )
615
616
617 class NoActionNecessary(NameBuildTest):
618 def test_all(self):
619 self.assert_message_from_report(
620 "No action necessary, requested change would have no effect",
621 reports.NoActionNecessary(),
622 )
623
624
625 class NodeCommunicationStarted(NameBuildTest):
626 def test_build_message_with_data(self):
627 self.assert_message_from_report(
628 (
629 "Sending HTTP Request to: TARGET\n"
630 "--Debug Input Start--\n"
631 "DATA\n"
632 "--Debug Input End--\n"
633 ),
634 reports.NodeCommunicationStarted("TARGET", "DATA"),
635 )
636
637 def test_build_message_without_data(self):
638 self.assert_message_from_report(
639 "Sending HTTP Request to: TARGET",
640 reports.NodeCommunicationStarted("TARGET", ""),
641 )
642
643
644 class NodeCommunicationFinished(NameBuildTest):
645 def test_all(self):
646 self.assert_message_from_report(
647 (
648 "Finished calling: node1\n"
649 "Response Code: 0\n"
650 "--Debug Response Start--\n"
651 "DATA\n"
652 "--Debug Response End--\n"
653 ),
654 reports.NodeCommunicationFinished("node1", 0, "DATA"),
655 )
656
657
658 class NodeCommunicationDebugInfo(NameBuildTest):
659 def test_all(self):
660 self.assert_message_from_report(
661 (
662 "Communication debug info for calling: node1\n"
663 "--Debug Communication Info Start--\n"
664 "DATA\n"
665 "--Debug Communication Info End--\n"
666 ),
667 reports.NodeCommunicationDebugInfo("node1", "DATA"),
668 )
669
670
671 class NodeCommunicationNotConnected(NameBuildTest):
672 def test_all(self):
673 self.assert_message_from_report(
674 "Unable to connect to node2 (this is reason)",
675 reports.NodeCommunicationNotConnected("node2", "this is reason"),
676 )
677
678
679 class NodeCommunicationNoMoreAddresses(NameBuildTest):
680 def test_success(self):
681 self.assert_message_from_report(
682 "Unable to connect to 'node_name' via any of its addresses",
683 reports.NodeCommunicationNoMoreAddresses(
684 "node_name",
685 "my/request",
686 ),
687 )
688
689
690 class NodeCommunicationErrorNotAuthorized(NameBuildTest):
691 def test_success(self):
692 self.assert_message_from_report(
693 "Unable to authenticate to node1 (some error)",
694 reports.NodeCommunicationErrorNotAuthorized(
695 "node1", "some-command", "some error"
696 ),
697 )
698
699
700 class NodeCommunicationErrorPermissionDenied(NameBuildTest):
701 def test_all(self):
702 self.assert_message_from_report(
703 "node3: Permission denied (reason)",
704 reports.NodeCommunicationErrorPermissionDenied(
705 "node3", "com-mand", "reason"
706 ),
707 )
708
709
710 class NodeCommunicationErrorUnsupportedCommand(NameBuildTest):
711 def test_all(self):
712 self.assert_message_from_report(
713 "node1: Unsupported command (reason), try upgrading pcsd",
714 reports.NodeCommunicationErrorUnsupportedCommand(
715 "node1", "com-mand", "reason"
716 ),
717 )
718
719
720 class NodeCommunicationCommandUnsuccessful(NameBuildTest):
721 def test_all(self):
722 self.assert_message_from_report(
723 "node1: reason",
724 reports.NodeCommunicationCommandUnsuccessful(
725 "node1", "com-mand", "reason"
726 ),
727 )
728
729
730 class NodeCommunicationError(NameBuildTest):
731 def test_all(self):
732 self.assert_message_from_report(
733 "Error connecting to node1 (reason)",
734 reports.NodeCommunicationError("node1", "com-mand", "reason"),
735 )
736
737
738 class NodeCommunicationErrorUnableToConnect(NameBuildTest):
739 def test_all(self):
740 self.assert_message_from_report(
741 "Unable to connect to node1 (reason)",
742 reports.NodeCommunicationErrorUnableToConnect(
743 "node1", "com-mand", "reason"
744 ),
745 )
746
747
748 class NodeCommunicationErrorTimedOut(NameBuildTest):
749 def test_success(self):
750 self.assert_message_from_report(
751 (
752 "node-1: Connection timeout (Connection timed out after 60049 "
753 "milliseconds)"
754 ),
755 reports.NodeCommunicationErrorTimedOut(
756 "node-1",
757 "/remote/command",
758 "Connection timed out after 60049 milliseconds",
759 ),
760 )
761
762
763 class NodeCommunicationProxyIsSet(NameBuildTest):
764 def test_minimal(self):
765 self.assert_message_from_report(
766 "Proxy is set in environment variables, try disabling it",
767 reports.NodeCommunicationProxyIsSet(),
768 )
769
770 def test_with_node(self):
771 self.assert_message_from_report(
772 "Proxy is set in environment variables, try disabling it",
773 reports.NodeCommunicationProxyIsSet(node="node1"),
774 )
775
776 def test_with_address(self):
777 self.assert_message_from_report(
778 "Proxy is set in environment variables, try disabling it",
779 reports.NodeCommunicationProxyIsSet(address="aaa"),
780 )
781
782 def test_all(self):
783 self.assert_message_from_report(
784 "Proxy is set in environment variables, try disabling it",
785 reports.NodeCommunicationProxyIsSet(node="node1", address="aaa"),
786 )
787
788
789 class NodeCommunicationRetrying(NameBuildTest):
790 def test_success(self):
791 self.assert_message_from_report(
792 (
793 "Unable to connect to 'node_name' via address 'failed.address' "
794 "and port '2224'. Retrying request 'my/request' via address "
795 "'next.address' and port '2225'"
796 ),
797 reports.NodeCommunicationRetrying(
798 "node_name",
799 "failed.address",
800 "2224",
801 "next.address",
802 "2225",
803 "my/request",
804 ),
805 )
806
807
808 class DefaultsCanBeOverridden(NameBuildTest):
809 def test_message(self):
810 self.assert_message_from_report(
811 (
812 "Defaults do not apply to resources which override them with "
813 "their own defined values"
814 ),
815 reports.DefaultsCanBeOverridden(),
816 )
817
818
819 class CorosyncAuthkeyWrongLength(NameBuildTest):
820 def test_at_most_allowed_singular_provided_plural(self):
821 self.assert_message_from_report(
822 (
823 "At least 0 and at most 1 byte key must be provided for "
824 "a corosync authkey, 2 bytes key provided"
825 ),
826 reports.CorosyncAuthkeyWrongLength(2, 0, 1),
827 )
828
829 def test_at_most_allowed_plural_provided_singular(self):
830 self.assert_message_from_report(
831 (
832 "At least 2 and at most 3 bytes key must be provided for "
833 "a corosync authkey, 1 byte key provided"
834 ),
835 reports.CorosyncAuthkeyWrongLength(1, 2, 3),
836 )
837
838 def test_exactly_allowed_singular_provided_plural(self):
839 self.assert_message_from_report(
840 (
841 "1 byte key must be provided for a corosync authkey, 2 bytes "
842 "key provided"
843 ),
844 reports.CorosyncAuthkeyWrongLength(2, 1, 1),
845 )
846
847 def test_exactly_allowed_plural_provided_singular(self):
848 self.assert_message_from_report(
849 (
850 "2 bytes key must be provided for a corosync authkey, 1 byte "
851 "key provided"
852 ),
853 reports.CorosyncAuthkeyWrongLength(1, 2, 2),
854 )
855
856
857 class CorosyncConfigDistributionStarted(NameBuildTest):
858 def test_all(self):
859 self.assert_message_from_report(
860 "Sending updated corosync.conf to nodes...",
861 reports.CorosyncConfigDistributionStarted(),
862 )
863
864
865 # TODO: consider generalizing
866 class CorosyncConfigAcceptedByNode(NameBuildTest):
867 def test_all(self):
868 self.assert_message_from_report(
869 "node1: Succeeded", reports.CorosyncConfigAcceptedByNode("node1")
870 )
871
872
873 class CorosyncConfigDistributionNodeError(NameBuildTest):
874 def test_all(self):
875 self.assert_message_from_report(
876 "node1: Unable to set corosync config",
877 reports.CorosyncConfigDistributionNodeError("node1"),
878 )
879
880
881 class CorosyncNotRunningCheckStarted(NameBuildTest):
882 def test_all(self):
883 self.assert_message_from_report(
884 "Checking that corosync is not running on nodes...",
885 reports.CorosyncNotRunningCheckStarted(),
886 )
887
888
889 class CorosyncNotRunningCheckFinishedRunning(NameBuildTest):
890 def test_one_node(self):
891 self.assert_message_from_report(
892 (
893 "Corosync is running on node 'node1'. Requested change can "
894 "only be made if the cluster is stopped. In order to proceed, "
895 "stop the cluster."
896 ),
897 reports.CorosyncNotRunningCheckFinishedRunning(["node1"]),
898 )
899
900 def test_more_nodes(self):
901 self.assert_message_from_report(
902 (
903 "Corosync is running on nodes 'node1', 'node2', 'node3'. "
904 "Requested change can only be made if the cluster is stopped. "
905 "In order to proceed, stop the cluster."
906 ),
907 reports.CorosyncNotRunningCheckFinishedRunning(
908 ["node2", "node1", "node3"]
909 ),
910 )
911
912
913 class CorosyncNotRunningCheckNodeError(NameBuildTest):
914 def test_all(self):
915 self.assert_message_from_report(
916 "Unable to check if corosync is not running on node 'node1'",
917 reports.CorosyncNotRunningCheckNodeError("node1"),
918 )
919
920
921 class CorosyncNotRunningCheckNodeStopped(NameBuildTest):
922 def test_all(self):
923 self.assert_message_from_report(
924 "Corosync is not running on node 'node2'",
925 reports.CorosyncNotRunningCheckNodeStopped("node2"),
926 )
927
928
929 class CorosyncNotRunningCheckNodeRunning(NameBuildTest):
930 def test_all(self):
931 self.assert_message_from_report(
932 "Corosync is running on node 'node3'",
933 reports.CorosyncNotRunningCheckNodeRunning("node3"),
934 )
935
936
937 class CorosyncQuorumGetStatusError(NameBuildTest):
938 def test_success(self):
939 self.assert_message_from_report(
940 "Unable to get quorum status: a reason",
941 reports.CorosyncQuorumGetStatusError("a reason"),
942 )
943
944 def test_success_with_node(self):
945 self.assert_message_from_report(
946 "node1: Unable to get quorum status: a reason",
947 reports.CorosyncQuorumGetStatusError("a reason", "node1"),
948 )
949
950
951 class CorosyncQuorumHeuristicsEnabledWithNoExec(NameBuildTest):
952 def test_message(self):
953 self.assert_message_from_report(
954 (
955 "No exec_NAME options are specified, so heuristics are "
956 "effectively disabled"
957 ),
958 reports.CorosyncQuorumHeuristicsEnabledWithNoExec(),
959 )
960
961
962 class CorosyncQuorumSetExpectedVotesError(NameBuildTest):
963 def test_all(self):
964 self.assert_message_from_report(
965 "Unable to set expected votes: reason",
966 reports.CorosyncQuorumSetExpectedVotesError("reason"),
967 )
968
969
970 class CorosyncConfigReloaded(NameBuildTest):
971 def test_with_node(self):
972 self.assert_message_from_report(
973 "node1: Corosync configuration reloaded",
974 reports.CorosyncConfigReloaded("node1"),
975 )
976
977 def test_without_node(self):
978 self.assert_message_from_report(
979 "Corosync configuration reloaded",
980 reports.CorosyncConfigReloaded(),
981 )
982
983
984 class CorosyncConfigReloadError(NameBuildTest):
985 def test_with_node(self):
986 self.assert_message_from_report(
987 "node1: Unable to reload corosync configuration: a reason",
988 reports.CorosyncConfigReloadError("a reason", "node1"),
989 )
990
991 def test_without_node(self):
992 self.assert_message_from_report(
993 "Unable to reload corosync configuration: different reason",
994 reports.CorosyncConfigReloadError("different reason"),
995 )
996
997
998 class CorosyncConfigReloadNotPossible(NameBuildTest):
999 def test_success(self):
1000 self.assert_message_from_report(
1001 (
1002 "node1: Corosync is not running, therefore reload of the "
1003 "corosync configuration is not possible"
1004 ),
1005 reports.CorosyncConfigReloadNotPossible("node1"),
1006 )
1007
1008
1009 class CorosyncConfigInvalidPreventsClusterJoin(NameBuildTest):
1010 def test_success(self):
1011 self.assert_message_from_report(
1012 (
1013 "One or more nodes failed to reload the Corosync configuration "
1014 "and are currently running with the previous configuration. If "
1015 "these nodes are restarted or fenced, they will fail to rejoin "
1016 "the cluster. Update the configuration and fix the issues as "
1017 "soon as possible."
1018 ),
1019 reports.CorosyncConfigInvalidPreventsClusterJoin(),
1020 )
1021
1022
1023 class CorosyncConfigUnsupportedTransport(NameBuildTest):
1024 def test_success(self):
1025 self.assert_message_from_report(
1026 (
1027 "Transport 'netk' currently configured in corosync.conf is "
1028 "unsupported. Supported transport types are: 'knet', 'udp', "
1029 "'udpu'"
1030 ),
1031 reports.CorosyncConfigUnsupportedTransport(
1032 "netk", ["udp", "knet", "udpu"]
1033 ),
1034 )
1035
1036
1037 class ParseErrorCorosyncConfMissingClosingBrace(NameBuildTest):
1038 def test_all(self):
1039 self.assert_message_from_report(
1040 "Unable to parse corosync config: missing closing brace",
1041 reports.ParseErrorCorosyncConfMissingClosingBrace(),
1042 )
1043
1044
1045 class ParseErrorCorosyncConfUnexpectedClosingBrace(NameBuildTest):
1046 def test_all(self):
1047 self.assert_message_from_report(
1048 "Unable to parse corosync config: unexpected closing brace",
1049 reports.ParseErrorCorosyncConfUnexpectedClosingBrace(),
1050 )
1051
1052
1053 class ParseErrorCorosyncConfMissingSectionNameBeforeOpeningBrace(NameBuildTest):
1054 def test_all(self):
1055 self.assert_message_from_report(
1056 "Unable to parse corosync config: missing a section name before {",
1057 reports.ParseErrorCorosyncConfMissingSectionNameBeforeOpeningBrace(),
1058 )
1059
1060
1061 class ParseErrorCorosyncConfExtraCharactersAfterOpeningBrace(NameBuildTest):
1062 def test_all(self):
1063 self.assert_message_from_report(
1064 "Unable to parse corosync config: extra characters after {",
1065 reports.ParseErrorCorosyncConfExtraCharactersAfterOpeningBrace(),
1066 )
1067
1068
1069 class ParseErrorCorosyncConfExtraCharactersBeforeOrAfterClosingBrace(
1070 NameBuildTest
1071 ):
1072 def test_all(self):
1073 self.assert_message_from_report(
1074 (
1075 "Unable to parse corosync config: extra characters before "
1076 "or after }"
1077 ),
1078 reports.ParseErrorCorosyncConfExtraCharactersBeforeOrAfterClosingBrace(),
1079 )
1080
1081
1082 class ParseErrorCorosyncConfLineIsNotSectionNorKeyValue(NameBuildTest):
1083 def test_all(self):
1084 self.assert_message_from_report(
1085 "Unable to parse corosync config: a line is not opening or closing "
1086 "a section or key: value",
1087 reports.ParseErrorCorosyncConfLineIsNotSectionNorKeyValue(),
1088 )
1089
1090
1091 class ParseErrorCorosyncConf(NameBuildTest):
1092 def test_all(self):
1093 self.assert_message_from_report(
1094 "Unable to parse corosync config", reports.ParseErrorCorosyncConf()
1095 )
1096
1097
1098 class CorosyncConfigCannotSaveInvalidNamesValues(NameBuildTest):
1099 def test_empty(self):
1100 self.assert_message_from_report(
1101 "Cannot save corosync.conf containing invalid section names, "
1102 "option names or option values",
1103 reports.CorosyncConfigCannotSaveInvalidNamesValues([], [], []),
1104 )
1105
1106 def test_one_section(self):
1107 self.assert_message_from_report(
1108 "Cannot save corosync.conf containing "
1109 "invalid section name(s): 'SECTION'",
1110 reports.CorosyncConfigCannotSaveInvalidNamesValues(
1111 ["SECTION"], [], []
1112 ),
1113 )
1114
1115 def test_more_sections(self):
1116 self.assert_message_from_report(
1117 "Cannot save corosync.conf containing "
1118 "invalid section name(s): 'SECTION1', 'SECTION2'",
1119 reports.CorosyncConfigCannotSaveInvalidNamesValues(
1120 ["SECTION1", "SECTION2"], [], []
1121 ),
1122 )
1123
1124 def test_one_attr_name(self):
1125 self.assert_message_from_report(
1126 "Cannot save corosync.conf containing "
1127 "invalid option name(s): 'ATTR'",
1128 reports.CorosyncConfigCannotSaveInvalidNamesValues(
1129 [], ["ATTR"], []
1130 ),
1131 )
1132
1133 def test_more_attr_names(self):
1134 self.assert_message_from_report(
1135 "Cannot save corosync.conf containing "
1136 "invalid option name(s): 'ATTR1', 'ATTR2'",
1137 reports.CorosyncConfigCannotSaveInvalidNamesValues(
1138 [], ["ATTR1", "ATTR2"], []
1139 ),
1140 )
1141
1142 def test_one_attr_value(self):
1143 self.assert_message_from_report(
1144 "Cannot save corosync.conf containing "
1145 "invalid option value(s): 'VALUE' (option 'ATTR')",
1146 reports.CorosyncConfigCannotSaveInvalidNamesValues(
1147 [], [], [("ATTR", "VALUE")]
1148 ),
1149 )
1150
1151 def test_more_attr_values(self):
1152 self.assert_message_from_report(
1153 "Cannot save corosync.conf containing "
1154 "invalid option value(s): 'VALUE1' (option 'ATTR1'), "
1155 "'VALUE2' (option 'ATTR2')",
1156 reports.CorosyncConfigCannotSaveInvalidNamesValues(
1157 [], [], [("ATTR1", "VALUE1"), ("ATTR2", "VALUE2")]
1158 ),
1159 )
1160
1161 def test_all(self):
1162 self.assert_message_from_report(
1163 "Cannot save corosync.conf containing "
1164 "invalid section name(s): 'SECTION1', 'SECTION2'; "
1165 "invalid option name(s): 'ATTR1', 'ATTR2'; "
1166 "invalid option value(s): 'VALUE3' (option 'ATTR3'), "
1167 "'VALUE4' (option 'ATTR4')",
1168 reports.CorosyncConfigCannotSaveInvalidNamesValues(
1169 ["SECTION1", "SECTION2"],
1170 ["ATTR1", "ATTR2"],
1171 [("ATTR3", "VALUE3"), ("ATTR4", "VALUE4")],
1172 ),
1173 )
1174
1175
1176 class CorosyncConfigMissingNamesOfNodes(NameBuildTest):
1177 def test_non_fatal(self):
1178 self.assert_message_from_report(
1179 "Some nodes are missing names in corosync.conf, "
1180 "those nodes were omitted. "
1181 "Edit corosync.conf and make sure all nodes have their name set.",
1182 reports.CorosyncConfigMissingNamesOfNodes(),
1183 )
1184
1185 def test_fatal(self):
1186 self.assert_message_from_report(
1187 "Some nodes are missing names in corosync.conf, "
1188 "unable to continue. "
1189 "Edit corosync.conf and make sure all nodes have their name set.",
1190 reports.CorosyncConfigMissingNamesOfNodes(fatal=True),
1191 )
1192
1193
1194 class CorosyncConfigMissingIdsOfNodes(NameBuildTest):
1195 def test_success(self):
1196 self.assert_message_from_report(
1197 "Some nodes are missing IDs in corosync.conf. "
1198 "Edit corosync.conf and make sure all nodes have their nodeid set.",
1199 reports.CorosyncConfigMissingIdsOfNodes(),
1200 )
1201
1202
1203 class CorosyncConfigNoNodesDefined(NameBuildTest):
1204 def test_success(self):
1205 self.assert_message_from_report(
1206 "No nodes found in corosync.conf",
1207 reports.CorosyncConfigNoNodesDefined(),
1208 )
1209
1210
1211 class CorosyncOptionsIncompatibleWithQdevice(NameBuildTest):
1212 def test_single_option(self):
1213 self.assert_message_from_report(
1214 "These options cannot be set when the cluster uses a quorum "
1215 "device: 'option1'",
1216 reports.CorosyncOptionsIncompatibleWithQdevice(["option1"]),
1217 )
1218
1219 def test_multiple_options(self):
1220 self.assert_message_from_report(
1221 "These options cannot be set when the cluster uses a quorum "
1222 "device: 'option1', 'option2', 'option3'",
1223 reports.CorosyncOptionsIncompatibleWithQdevice(
1224 ["option3", "option1", "option2"]
1225 ),
1226 )
1227
1228
1229 class CorosyncClusterNameInvalidForGfs2(NameBuildTest):
1230 def test_success(self):
1231 self.assert_message_from_report(
1232 "Chosen cluster name 'cluster name' will prevent mounting GFS2 "
1233 "volumes in the cluster, use at most 16 of a-z A-Z characters; "
1234 "you may safely override this if you do not intend to use GFS2",
1235 reports.CorosyncClusterNameInvalidForGfs2(
1236 cluster_name="cluster name",
1237 max_length=16,
1238 allowed_characters="a-z A-Z",
1239 ),
1240 )
1241
1242
1243 class CorosyncBadNodeAddressesCount(NameBuildTest):
1244 def test_no_node_info(self):
1245 self.assert_message_from_report(
1246 "At least 1 and at most 4 addresses must be specified for a node, "
1247 "5 addresses specified",
1248 reports.CorosyncBadNodeAddressesCount(5, 1, 4),
1249 )
1250
1251 def test_node_name(self):
1252 self.assert_message_from_report(
1253 "At least 1 and at most 4 addresses must be specified for a node, "
1254 "5 addresses specified for node 'node1'",
1255 reports.CorosyncBadNodeAddressesCount(5, 1, 4, "node1"),
1256 )
1257
1258 def test_node_id(self):
1259 self.assert_message_from_report(
1260 "At least 1 and at most 4 addresses must be specified for a node, "
1261 "5 addresses specified for node '2'",
1262 reports.CorosyncBadNodeAddressesCount(5, 1, 4, node_index=2),
1263 )
1264
1265 def test_node_name_and_id(self):
1266 self.assert_message_from_report(
1267 "At least 1 and at most 4 addresses must be specified for a node, "
1268 "5 addresses specified for node 'node2'",
1269 reports.CorosyncBadNodeAddressesCount(5, 1, 4, "node2", 2),
1270 )
1271
1272 def test_one_address_allowed(self):
1273 self.assert_message_from_report(
1274 "At least 0 and at most 1 address must be specified for a node, "
1275 "2 addresses specified for node 'node2'",
1276 reports.CorosyncBadNodeAddressesCount(2, 0, 1, "node2", 2),
1277 )
1278
1279 def test_one_address_specified(self):
1280 self.assert_message_from_report(
1281 "At least 2 and at most 4 addresses must be specified for a node, "
1282 "1 address specified for node 'node2'",
1283 reports.CorosyncBadNodeAddressesCount(1, 2, 4, "node2", 2),
1284 )
1285
1286 def test_exactly_one_address_allowed(self):
1287 self.assert_message_from_report(
1288 "1 address must be specified for a node, "
1289 "2 addresses specified for node 'node2'",
1290 reports.CorosyncBadNodeAddressesCount(2, 1, 1, "node2", 2),
1291 )
1292
1293 def test_exactly_two_addresses_allowed(self):
1294 self.assert_message_from_report(
1295 "2 addresses must be specified for a node, "
1296 "1 address specified for node 'node2'",
1297 reports.CorosyncBadNodeAddressesCount(1, 2, 2, "node2", 2),
1298 )
1299
1300
1301 class CorosyncIpVersionMismatchInLinks(NameBuildTest):
1302 def test_without_links(self):
1303 self.assert_message_from_report(
1304 "Using both IPv4 and IPv6 on one link is not allowed; please, use "
1305 "either IPv4 or IPv6",
1306 reports.CorosyncIpVersionMismatchInLinks(),
1307 )
1308
1309 def test_with_single_link(self):
1310 self.assert_message_from_report(
1311 "Using both IPv4 and IPv6 on one link is not allowed; please, use "
1312 "either IPv4 or IPv6 on link(s): '3'",
1313 reports.CorosyncIpVersionMismatchInLinks(["3"]),
1314 )
1315
1316 def test_with_links(self):
1317 self.assert_message_from_report(
1318 "Using both IPv4 and IPv6 on one link is not allowed; please, use "
1319 "either IPv4 or IPv6 on link(s): '0', '3', '4'",
1320 reports.CorosyncIpVersionMismatchInLinks(["3", "0", "4"]),
1321 )
1322
1323
1324 class CorosyncAddressIpVersionWrongForLink(NameBuildTest):
1325 def test_without_links(self):
1326 self.assert_message_from_report(
1327 "Address '192.168.100.42' cannot be used in the link because "
1328 "the link uses IPv6 addresses",
1329 reports.CorosyncAddressIpVersionWrongForLink(
1330 "192.168.100.42",
1331 "IPv6",
1332 ),
1333 )
1334
1335 def test_with_links(self):
1336 self.assert_message_from_report(
1337 "Address '192.168.100.42' cannot be used in link '3' because "
1338 "the link uses IPv6 addresses",
1339 reports.CorosyncAddressIpVersionWrongForLink(
1340 "192.168.100.42",
1341 "IPv6",
1342 3,
1343 ),
1344 )
1345
1346
1347 class CorosyncLinkNumberDuplication(NameBuildTest):
1348 _template = "Link numbers must be unique, duplicate link numbers: {values}"
1349
1350 def test_message(self):
1351 self.assert_message_from_report(
1352 self._template.format(values="'1', '3'"),
1353 reports.CorosyncLinkNumberDuplication(["1", "3"]),
1354 )
1355
1356 def test_sort(self):
1357 self.assert_message_from_report(
1358 self._template.format(values="'1', '3'"),
1359 reports.CorosyncLinkNumberDuplication(["3", "1"]),
1360 )
1361
1362 def test_sort_not_int(self):
1363 self.assert_message_from_report(
1364 self._template.format(values="'-5', 'x3', '1', '3'"),
1365 reports.CorosyncLinkNumberDuplication(["3", "1", "x3", "-5"]),
1366 )
1367
1368
1369 class CorosyncNodeAddressCountMismatch(NameBuildTest):
1370 def test_message(self):
1371 self.assert_message_from_report(
1372 "All nodes must have the same number of addresses; "
1373 "nodes 'node3', 'node4', 'node6' have 1 address; "
1374 "nodes 'node2', 'node5' have 3 addresses; "
1375 "node 'node1' has 2 addresses",
1376 reports.CorosyncNodeAddressCountMismatch(
1377 {
1378 "node1": 2,
1379 "node2": 3,
1380 "node3": 1,
1381 "node4": 1,
1382 "node5": 3,
1383 "node6": 1,
1384 }
1385 ),
1386 )
1387
1388
1389 class NodeAddressesAlreadyExist(NameBuildTest):
1390 def test_one_address(self):
1391 self.assert_message_from_report(
1392 "Node address 'node1' is already used by existing nodes; please, "
1393 "use other address",
1394 reports.NodeAddressesAlreadyExist(["node1"]),
1395 )
1396
1397 def test_more_addresses(self):
1398 self.assert_message_from_report(
1399 "Node addresses 'node1', 'node3' are already used by existing "
1400 "nodes; please, use other addresses",
1401 reports.NodeAddressesAlreadyExist(["node1", "node3"]),
1402 )
1403
1404
1405 class NodeAddressesCannotBeEmpty(NameBuildTest):
1406 def test_one_node(self):
1407 self.assert_message_from_report(
1408 ("Empty address set for node 'node2', an address cannot be empty"),
1409 reports.NodeAddressesCannotBeEmpty(["node2"]),
1410 )
1411
1412 def test_more_nodes(self):
1413 self.assert_message_from_report(
1414 (
1415 "Empty address set for nodes 'node1', 'node2', "
1416 "an address cannot be empty"
1417 ),
1418 reports.NodeAddressesCannotBeEmpty(["node2", "node1"]),
1419 )
1420
1421
1422 class NodeAddressesDuplication(NameBuildTest):
1423 def test_message(self):
1424 self.assert_message_from_report(
1425 "Node addresses must be unique, duplicate addresses: "
1426 "'node1', 'node3'",
1427 reports.NodeAddressesDuplication(["node1", "node3"]),
1428 )
1429
1430
1431 class NodeNamesAlreadyExist(NameBuildTest):
1432 def test_one_address(self):
1433 self.assert_message_from_report(
1434 "Node name 'node1' is already used by existing nodes; please, "
1435 "use other name",
1436 reports.NodeNamesAlreadyExist(["node1"]),
1437 )
1438
1439 def test_more_addresses(self):
1440 self.assert_message_from_report(
1441 "Node names 'node1', 'node3' are already used by existing "
1442 "nodes; please, use other names",
1443 reports.NodeNamesAlreadyExist(["node1", "node3"]),
1444 )
1445
1446
1447 class NodeNamesDuplication(NameBuildTest):
1448 def test_message(self):
1449 self.assert_message_from_report(
1450 "Node names must be unique, duplicate names: 'node1', 'node3'",
1451 reports.NodeNamesDuplication(["node1", "node3"]),
1452 )
1453
1454
1455 class CorosyncNodesMissing(NameBuildTest):
1456 def test_message(self):
1457 self.assert_message_from_report(
1458 "No nodes have been specified", reports.CorosyncNodesMissing()
1459 )
1460
1461
1462 class CorosyncNodeRenameOldNodeNotFound(NameBuildTest):
1463 def test_message(self):
1464 self.assert_message_from_report(
1465 ("Node 'node1' was not found in corosync.conf, unable to rename"),
1466 reports.CorosyncNodeRenameOldNodeNotFound("node1"),
1467 )
1468
1469
1470 class CorosyncNodeRenameNewNodeAlreadyExists(NameBuildTest):
1471 def test_message(self):
1472 self.assert_message_from_report(
1473 ("Node 'node2' already exists in corosync.conf, unable to rename"),
1474 reports.CorosyncNodeRenameNewNodeAlreadyExists("node2"),
1475 )
1476
1477
1478 class CorosyncNodeRenameAddrsMatchOldName(NameBuildTest):
1479 def test_message(self):
1480 self.assert_message_from_report(
1481 (
1482 "Node 'node1' has been renamed to 'node1-new', but the"
1483 " following addresses still reference the old name:"
1484 " node1-new: ring1_addr; node2: ring0_addr"
1485 ),
1486 reports.CorosyncNodeRenameAddrsMatchOldName(
1487 "node1",
1488 "node1-new",
1489 {"node1-new": ["ring1_addr"], "node2": ["ring0_addr"]},
1490 ),
1491 )
1492
1493
1494 class CorosyncTooManyLinksOptions(NameBuildTest):
1495 def test_message(self):
1496 self.assert_message_from_report(
1497 (
1498 "Cannot specify options for more links (7) than how many is "
1499 "defined by number of addresses per node (3)"
1500 ),
1501 reports.CorosyncTooManyLinksOptions(7, 3),
1502 )
1503
1504
1505 class CorosyncCannotAddRemoveLinksBadTransport(NameBuildTest):
1506 def test_add(self):
1507 self.assert_message_from_report(
1508 (
1509 "Cluster is using udp transport which does not support "
1510 "adding links"
1511 ),
1512 reports.CorosyncCannotAddRemoveLinksBadTransport(
1513 "udp", ["knet1", "knet2"], add_or_not_remove=True
1514 ),
1515 )
1516
1517 def test_remove(self):
1518 self.assert_message_from_report(
1519 (
1520 "Cluster is using udpu transport which does not support "
1521 "removing links"
1522 ),
1523 reports.CorosyncCannotAddRemoveLinksBadTransport(
1524 "udpu", ["knet"], add_or_not_remove=False
1525 ),
1526 )
1527
1528
1529 class CorosyncCannotAddRemoveLinksNoLinksSpecified(NameBuildTest):
1530 def test_add(self):
1531 self.assert_message_from_report(
1532 "Cannot add links, no links to add specified",
1533 reports.CorosyncCannotAddRemoveLinksNoLinksSpecified(
1534 add_or_not_remove=True
1535 ),
1536 )
1537
1538 def test_remove(self):
1539 self.assert_message_from_report(
1540 "Cannot remove links, no links to remove specified",
1541 reports.CorosyncCannotAddRemoveLinksNoLinksSpecified(
1542 add_or_not_remove=False
1543 ),
1544 )
1545
1546
1547 class CorosyncCannotAddRemoveLinksTooManyFewLinks(NameBuildTest):
1548 def test_add(self):
1549 self.assert_message_from_report(
1550 (
1551 "Cannot add 1 link, there would be 1 link defined which is "
1552 "more than allowed number of 1 link"
1553 ),
1554 reports.CorosyncCannotAddRemoveLinksTooManyFewLinks(
1555 1, 1, 1, add_or_not_remove=True
1556 ),
1557 )
1558
1559 def test_add_s(self):
1560 self.assert_message_from_report(
1561 (
1562 "Cannot add 2 links, there would be 4 links defined which is "
1563 "more than allowed number of 3 links"
1564 ),
1565 reports.CorosyncCannotAddRemoveLinksTooManyFewLinks(
1566 2, 4, 3, add_or_not_remove=True
1567 ),
1568 )
1569
1570 def test_remove(self):
1571 self.assert_message_from_report(
1572 (
1573 "Cannot remove 1 link, there would be 1 link defined which is "
1574 "less than allowed number of 1 link"
1575 ),
1576 reports.CorosyncCannotAddRemoveLinksTooManyFewLinks(
1577 1, 1, 1, add_or_not_remove=False
1578 ),
1579 )
1580
1581 def test_remove_s(self):
1582 self.assert_message_from_report(
1583 (
1584 "Cannot remove 3 links, there would be 0 links defined which "
1585 "is less than allowed number of 2 links"
1586 ),
1587 reports.CorosyncCannotAddRemoveLinksTooManyFewLinks(
1588 3, 0, 2, add_or_not_remove=False
1589 ),
1590 )
1591
1592
1593 class CorosyncLinkAlreadyExistsCannotAdd(NameBuildTest):
1594 def test_message(self):
1595 self.assert_message_from_report(
1596 "Cannot add link '2', it already exists",
1597 reports.CorosyncLinkAlreadyExistsCannotAdd("2"),
1598 )
1599
1600
1601 class CorosyncLinkDoesNotExistCannotRemove(NameBuildTest):
1602 def test_single_link(self):
1603 self.assert_message_from_report(
1604 ("Cannot remove non-existent link 'abc', existing links: '5'"),
1605 reports.CorosyncLinkDoesNotExistCannotRemove(["abc"], ["5"]),
1606 )
1607
1608 def test_multiple_links(self):
1609 self.assert_message_from_report(
1610 (
1611 "Cannot remove non-existent links '0', '1', 'abc', existing "
1612 "links: '2', '3', '5'"
1613 ),
1614 reports.CorosyncLinkDoesNotExistCannotRemove(
1615 ["1", "0", "abc"], ["3", "2", "5"]
1616 ),
1617 )
1618
1619
1620 class CorosyncLinkDoesNotExistCannotUpdate(NameBuildTest):
1621 def test_link_list_several(self):
1622 self.assert_message_from_report(
1623 (
1624 "Cannot set options for non-existent link '3'"
1625 ", existing links: '0', '1', '2', '6', '7'"
1626 ),
1627 reports.CorosyncLinkDoesNotExistCannotUpdate(
1628 3, ["6", "7", "0", "1", "2"]
1629 ),
1630 )
1631
1632 def test_link_list_one(self):
1633 self.assert_message_from_report(
1634 (
1635 "Cannot set options for non-existent link '3'"
1636 ", existing links: '0'"
1637 ),
1638 reports.CorosyncLinkDoesNotExistCannotUpdate(3, ["0"]),
1639 )
1640
1641
1642 class CorosyncTransportUnsupportedOptions(NameBuildTest):
1643 def test_udp(self):
1644 self.assert_message_from_report(
1645 "The udp/udpu transport does not support 'crypto' options, use "
1646 "'knet' transport",
1647 reports.CorosyncTransportUnsupportedOptions(
1648 "crypto", "udp/udpu", ["knet"]
1649 ),
1650 )
1651
1652 def test_multiple_supported_transports(self):
1653 self.assert_message_from_report(
1654 "The udp/udpu transport does not support 'crypto' options, use "
1655 "'knet', 'knet2' transport",
1656 reports.CorosyncTransportUnsupportedOptions(
1657 "crypto", "udp/udpu", ["knet", "knet2"]
1658 ),
1659 )
1660
1661
1662 class ClusterUuidAlreadySet(NameBuildTest):
1663 def test_all(self):
1664 self.assert_message_from_report(
1665 "Cluster UUID has already been set", reports.ClusterUuidAlreadySet()
1666 )
1667
1668
1669 class QdeviceAlreadyDefined(NameBuildTest):
1670 def test_all(self):
1671 self.assert_message_from_report(
1672 "quorum device is already defined", reports.QdeviceAlreadyDefined()
1673 )
1674
1675
1676 class QdeviceNotDefined(NameBuildTest):
1677 def test_all(self):
1678 self.assert_message_from_report(
1679 "no quorum device is defined in this cluster",
1680 reports.QdeviceNotDefined(),
1681 )
1682
1683
1684 class QdeviceClientReloadStarted(NameBuildTest):
1685 def test_all(self):
1686 self.assert_message_from_report(
1687 "Reloading qdevice configuration on nodes...",
1688 reports.QdeviceClientReloadStarted(),
1689 )
1690
1691
1692 class QdeviceAlreadyInitialized(NameBuildTest):
1693 def test_all(self):
1694 self.assert_message_from_report(
1695 "Quorum device 'model' has been already initialized",
1696 reports.QdeviceAlreadyInitialized("model"),
1697 )
1698
1699
1700 class QdeviceNotInitialized(NameBuildTest):
1701 def test_all(self):
1702 self.assert_message_from_report(
1703 "Quorum device 'model' has not been initialized yet",
1704 reports.QdeviceNotInitialized("model"),
1705 )
1706
1707
1708 class QdeviceInitializationSuccess(NameBuildTest):
1709 def test_all(self):
1710 self.assert_message_from_report(
1711 "Quorum device 'model' initialized",
1712 reports.QdeviceInitializationSuccess("model"),
1713 )
1714
1715
1716 class QdeviceInitializationError(NameBuildTest):
1717 def test_all(self):
1718 self.assert_message_from_report(
1719 "Unable to initialize quorum device 'model': reason",
1720 reports.QdeviceInitializationError("model", "reason"),
1721 )
1722
1723
1724 class QdeviceCertificateDistributionStarted(NameBuildTest):
1725 def test_all(self):
1726 self.assert_message_from_report(
1727 "Setting up qdevice certificates on nodes...",
1728 reports.QdeviceCertificateDistributionStarted(),
1729 )
1730
1731
1732 class QdeviceCertificateAcceptedByNode(NameBuildTest):
1733 def test_all(self):
1734 self.assert_message_from_report(
1735 "node1: Succeeded",
1736 reports.QdeviceCertificateAcceptedByNode("node1"),
1737 )
1738
1739
1740 class QdeviceCertificateRemovalStarted(NameBuildTest):
1741 def test_all(self):
1742 self.assert_message_from_report(
1743 "Removing qdevice certificates from nodes...",
1744 reports.QdeviceCertificateRemovalStarted(),
1745 )
1746
1747
1748 class QdeviceCertificateRemovedFromNode(NameBuildTest):
1749 def test_all(self):
1750 self.assert_message_from_report(
1751 "node2: Succeeded",
1752 reports.QdeviceCertificateRemovedFromNode("node2"),
1753 )
1754
1755
1756 class QdeviceCertificateImportError(NameBuildTest):
1757 def test_all(self):
1758 self.assert_message_from_report(
1759 "Unable to import quorum device certificate: reason",
1760 reports.QdeviceCertificateImportError("reason"),
1761 )
1762
1763
1764 class QdeviceCertificateSignError(NameBuildTest):
1765 def test_all(self):
1766 self.assert_message_from_report(
1767 "Unable to sign quorum device certificate: reason",
1768 reports.QdeviceCertificateSignError("reason"),
1769 )
1770
1771
1772 class QdeviceCertificateBadFormat(NameBuildTest):
1773 def test_all(self):
1774 self.assert_message_from_report(
1775 "Unable to parse quorum device certificate",
1776 reports.QdeviceCertificateBadFormat(),
1777 )
1778
1779
1780 class QdeviceCertificateReadError(NameBuildTest):
1781 def test_all(self):
1782 self.assert_message_from_report(
1783 "Unable to read quorum device certificate: reason",
1784 reports.QdeviceCertificateReadError("reason"),
1785 )
1786
1787
1788 class QdeviceDestroySuccess(NameBuildTest):
1789 def test_all(self):
1790 self.assert_message_from_report(
1791 "Quorum device 'model' configuration files removed",
1792 reports.QdeviceDestroySuccess("model"),
1793 )
1794
1795
1796 class QdeviceDestroyError(NameBuildTest):
1797 def test_all(self):
1798 self.assert_message_from_report(
1799 "Unable to destroy quorum device 'model': reason",
1800 reports.QdeviceDestroyError("model", "reason"),
1801 )
1802
1803
1804 class QdeviceNotRunning(NameBuildTest):
1805 def test_all(self):
1806 self.assert_message_from_report(
1807 "Quorum device 'model' is not running",
1808 reports.QdeviceNotRunning("model"),
1809 )
1810
1811
1812 class QdeviceGetStatusError(NameBuildTest):
1813 def test_all(self):
1814 self.assert_message_from_report(
1815 "Unable to get status of quorum device 'model': reason",
1816 reports.QdeviceGetStatusError("model", "reason"),
1817 )
1818
1819
1820 class QdeviceUsedByClusters(NameBuildTest):
1821 def test_single_cluster(self):
1822 self.assert_message_from_report(
1823 "Quorum device is currently being used by cluster(s): 'c1'",
1824 reports.QdeviceUsedByClusters(["c1"]),
1825 )
1826
1827 def test_multiple_clusters(self):
1828 self.assert_message_from_report(
1829 "Quorum device is currently being used by cluster(s): 'c1', 'c2'",
1830 reports.QdeviceUsedByClusters(["c1", "c2"]),
1831 )
1832
1833
1834 class IdAlreadyExists(NameBuildTest):
1835 def test_all(self):
1836 self.assert_message_from_report(
1837 "'id' already exists", reports.IdAlreadyExists("id")
1838 )
1839
1840
1841 class IdBelongsToUnexpectedType(NameBuildTest):
1842 def test_build_message_with_single_type(self):
1843 self.assert_message_from_report(
1844 "'ID' is not an ACL permission",
1845 reports.IdBelongsToUnexpectedType("ID", ["acl_permission"], "op"),
1846 )
1847
1848 def test_build_message_with_data(self):
1849 self.assert_message_from_report(
1850 "'ID' is not a clone / resource",
1851 reports.IdBelongsToUnexpectedType(
1852 "ID", ["primitive", "clone"], "op"
1853 ),
1854 )
1855
1856 def test_build_message_with_transformation_and_article(self):
1857 self.assert_message_from_report(
1858 "'ID' is not an ACL group / ACL user",
1859 reports.IdBelongsToUnexpectedType(
1860 "ID",
1861 ["acl_target", "acl_group"],
1862 "op",
1863 ),
1864 )
1865
1866
1867 class IdDoesNotSupportElementDescriptions(NameBuildTest):
1868 def test_success(self):
1869 self.assert_message_from_report(
1870 (
1871 "'ID' is a location constraint, descriptions are only "
1872 "supported for clone / resource"
1873 ),
1874 reports.IdDoesNotSupportElementDescriptions(
1875 "ID",
1876 "rsc_location",
1877 ["primitive", "clone"],
1878 ),
1879 )
1880
1881
1882 class ObjectWithIdInUnexpectedContext(NameBuildTest):
1883 def test_with_context_id(self):
1884 self.assert_message_from_report(
1885 "resource 'R' exists but does not belong to group 'G'",
1886 reports.ObjectWithIdInUnexpectedContext(
1887 "primitive", "R", "group", "G"
1888 ),
1889 )
1890
1891 def test_without_context_id(self):
1892 self.assert_message_from_report(
1893 "group 'G' exists but does not belong to 'resource'",
1894 reports.ObjectWithIdInUnexpectedContext(
1895 "group", "G", "primitive", ""
1896 ),
1897 )
1898
1899
1900 class IdNotFound(NameBuildTest):
1901 def test_id(self):
1902 self.assert_message_from_report(
1903 "'ID' does not exist", reports.IdNotFound("ID", [])
1904 )
1905
1906 def test_id_and_type(self):
1907 self.assert_message_from_report(
1908 "clone / resource 'ID' does not exist",
1909 reports.IdNotFound("ID", ["primitive", "clone"]),
1910 )
1911
1912 def test_context(self):
1913 self.assert_message_from_report(
1914 "there is no 'ID' in the C_TYPE 'C_ID'",
1915 reports.IdNotFound(
1916 "ID", [], context_type="C_TYPE", context_id="C_ID"
1917 ),
1918 )
1919
1920 def test_type_and_context(self):
1921 self.assert_message_from_report(
1922 "there is no ACL user 'ID' in the C_TYPE 'C_ID'",
1923 reports.IdNotFound(
1924 "ID", ["acl_target"], context_type="C_TYPE", context_id="C_ID"
1925 ),
1926 )
1927
1928
1929 class ResourceBundleAlreadyContainsAResource(NameBuildTest):
1930 def test_build_message_with_data(self):
1931 self.assert_message_from_report(
1932 (
1933 "bundle 'test_bundle' already contains resource "
1934 "'test_resource', a bundle may contain at most one resource"
1935 ),
1936 reports.ResourceBundleAlreadyContainsAResource(
1937 "test_bundle", "test_resource"
1938 ),
1939 )
1940
1941
1942 class CannotGroupResourceWrongType(NameBuildTest):
1943 def test_without_parent(self):
1944 self.assert_message_from_report(
1945 (
1946 "'R' is a clone resource, clone resources cannot be put into "
1947 "a group"
1948 ),
1949 reports.CannotGroupResourceWrongType("R", "master", None, None),
1950 )
1951
1952 def test_with_parent(self):
1953 self.assert_message_from_report(
1954 (
1955 "'R' cannot be put into a group because its parent 'B' "
1956 "is a bundle resource"
1957 ),
1958 reports.CannotGroupResourceWrongType(
1959 "R", "primitive", "B", "bundle"
1960 ),
1961 )
1962
1963
1964 class UnableToGetResourceOperationDigests(NameBuildTest):
1965 def test_success(self):
1966 self.assert_message_from_report(
1967 "unable to get resource operation digests:\ncrm_resource output",
1968 reports.UnableToGetResourceOperationDigests("crm_resource output"),
1969 )
1970
1971
1972 class StonithResourcesDoNotExist(NameBuildTest):
1973 def test_success(self):
1974 self.assert_message_from_report(
1975 "Stonith resource(s) 'device1', 'device2' do not exist",
1976 reports.StonithResourcesDoNotExist(["device2", "device1"]),
1977 )
1978
1979
1980 class StonithRestartlessUpdateOfScsiDevicesNotSupported(NameBuildTest):
1981 def test_success(self):
1982 self.assert_message_from_report(
1983 (
1984 "Restartless update of scsi devices is not supported, please "
1985 "upgrade pacemaker"
1986 ),
1987 reports.StonithRestartlessUpdateOfScsiDevicesNotSupported(),
1988 )
1989
1990
1991 class StonithRestartlessUpdateUnsupportedAgent(NameBuildTest):
1992 def test_plural(self):
1993 self.assert_message_from_report(
1994 (
1995 "Resource 'fence_sbd' is not a stonith resource or its type "
1996 "'wrong_type' is not supported for devices update. Supported "
1997 "types: 'fence_mpath', 'fence_scsi'"
1998 ),
1999 reports.StonithRestartlessUpdateUnsupportedAgent(
2000 "fence_sbd", "wrong_type", ["fence_scsi", "fence_mpath"]
2001 ),
2002 )
2003
2004 def test_singular(self):
2005 self.assert_message_from_report(
2006 (
2007 "Resource 'fence_sbd' is not a stonith resource or its type "
2008 "'wrong_type' is not supported for devices update. Supported "
2009 "type: 'fence_scsi'"
2010 ),
2011 reports.StonithRestartlessUpdateUnsupportedAgent(
2012 "fence_sbd", "wrong_type", ["fence_scsi"]
2013 ),
2014 )
2015
2016
2017 class StonithUnfencingFailed(NameBuildTest):
2018 def test_build_message(self):
2019 self.assert_message_from_report(
2020 ("Unfencing failed:\nreason"),
2021 reports.StonithUnfencingFailed("reason"),
2022 )
2023
2024
2025 class StonithUnfencingDeviceStatusFailed(NameBuildTest):
2026 def test_build_message(self):
2027 self.assert_message_from_report(
2028 "Unfencing failed, unable to check status of device 'dev1': reason",
2029 reports.StonithUnfencingDeviceStatusFailed("dev1", "reason"),
2030 )
2031
2032
2033 class StonithUnfencingSkippedDevicesFenced(NameBuildTest):
2034 def test_one_device(self):
2035 self.assert_message_from_report(
2036 "Unfencing skipped, device 'dev1' is fenced",
2037 reports.StonithUnfencingSkippedDevicesFenced(["dev1"]),
2038 )
2039
2040 def test_multiple_devices(self):
2041 self.assert_message_from_report(
2042 "Unfencing skipped, devices 'dev1', 'dev2', 'dev3' are fenced",
2043 reports.StonithUnfencingSkippedDevicesFenced(
2044 ["dev2", "dev1", "dev3"]
2045 ),
2046 )
2047
2048
2049 class StonithRestartlessUpdateUnableToPerform(NameBuildTest):
2050 def test_build_message(self):
2051 self.assert_message_from_report(
2052 "Unable to perform restartless update of scsi devices: reason",
2053 reports.StonithRestartlessUpdateUnableToPerform("reason"),
2054 )
2055
2056 def test_build_message_reason_type_specified(self):
2057 self.assert_message_from_report(
2058 "Unable to perform restartless update of scsi devices: reason",
2059 reports.StonithRestartlessUpdateUnableToPerform(
2060 "reason",
2061 const.STONITH_RESTARTLESS_UPDATE_UNABLE_TO_PERFORM_REASON_NOT_RUNNING,
2062 ),
2063 )
2064
2065
2066 class StonithRestartlessUpdateMissingMpathKeys(NameBuildTest):
2067 def test_plural(self):
2068 self.assert_message_from_report(
2069 (
2070 "Missing mpath reservation keys for nodes: 'rh9-2', 'rh9-3', "
2071 "in 'pcmk_host_map' value: 'rh9-1:1'"
2072 ),
2073 reports.StonithRestartlessUpdateMissingMpathKeys(
2074 "rh9-1:1", ["rh9-2", "rh9-3"]
2075 ),
2076 )
2077
2078 def test_singular(self):
2079 self.assert_message_from_report(
2080 (
2081 "Missing mpath reservation key for node: 'rh9-2', "
2082 "in 'pcmk_host_map' value: 'rh9-1:1'"
2083 ),
2084 reports.StonithRestartlessUpdateMissingMpathKeys(
2085 "rh9-1:1", ["rh9-2"]
2086 ),
2087 )
2088
2089 def test_missing_map_and_empty_nodes(self):
2090 self.assert_message_from_report(
2091 "Missing mpath reservation keys, 'pcmk_host_map' not set",
2092 reports.StonithRestartlessUpdateMissingMpathKeys(None, []),
2093 )
2094
2095 def test_missing_map_non_empty_nodes(self):
2096 self.assert_message_from_report(
2097 "Missing mpath reservation keys, 'pcmk_host_map' not set",
2098 reports.StonithRestartlessUpdateMissingMpathKeys(
2099 None, ["rh9-1", "rh9-2"]
2100 ),
2101 )
2102
2103 def test_non_empty_map_empty_nodes(self):
2104 self.assert_message_from_report(
2105 (
2106 "Missing mpath reservation keys for nodes in 'pcmk_host_map' "
2107 "value: 'rh-1:1'"
2108 ),
2109 reports.StonithRestartlessUpdateMissingMpathKeys("rh-1:1", []),
2110 )
2111
2112
2113 class ResourceRunningOnNodes(NameBuildTest):
2114 def test_one_node(self):
2115 self.assert_message_from_report(
2116 "resource 'R' is running on node 'node1'",
2117 reports.ResourceRunningOnNodes("R", {"Started": ["node1"]}),
2118 )
2119
2120 def test_multiple_nodes(self):
2121 self.assert_message_from_report(
2122 "resource 'R' is running on nodes 'node1', 'node2'",
2123 reports.ResourceRunningOnNodes(
2124 "R", {"Started": ["node1", "node2"]}
2125 ),
2126 )
2127
2128 def test_multiple_role_multiple_nodes(self):
2129 self.assert_message_from_report(
2130 "resource 'R' is promoted on node 'node3'"
2131 "; running on nodes 'node1', 'node2'",
2132 reports.ResourceRunningOnNodes(
2133 "R",
2134 {
2135 "Started": ["node1", "node2"],
2136 "Promoted": ["node3"],
2137 },
2138 ),
2139 )
2140
2141
2142 class ResourceDoesNotRun(NameBuildTest):
2143 def test_build_message(self):
2144 self.assert_message_from_report(
2145 "resource 'R' is not running on any node",
2146 reports.ResourceDoesNotRun("R"),
2147 )
2148
2149
2150 class ResourceIsGuestNodeAlready(NameBuildTest):
2151 def test_build_messages(self):
2152 self.assert_message_from_report(
2153 "the resource 'some-resource' is already a guest node",
2154 reports.ResourceIsGuestNodeAlready("some-resource"),
2155 )
2156
2157
2158 class ResourceIsUnmanaged(NameBuildTest):
2159 def test_build_message(self):
2160 self.assert_message_from_report(
2161 "'R' is unmanaged", reports.ResourceIsUnmanaged("R")
2162 )
2163
2164
2165 class ResourceManagedNoMonitorEnabled(NameBuildTest):
2166 def test_build_message(self):
2167 self.assert_message_from_report(
2168 "Resource 'R' has no enabled monitor operations",
2169 reports.ResourceManagedNoMonitorEnabled("R"),
2170 )
2171
2172
2173 class CibLoadError(NameBuildTest):
2174 def test_all(self):
2175 self.assert_message_from_report(
2176 "unable to get cib", reports.CibLoadError("reason")
2177 )
2178
2179
2180 class CibLoadErrorGetNodesForValidation(NameBuildTest):
2181 def test_all(self):
2182 self.assert_message_from_report(
2183 (
2184 "Unable to load CIB to get guest and remote nodes from it, "
2185 "those nodes cannot be considered in configuration validation"
2186 ),
2187 reports.CibLoadErrorGetNodesForValidation(),
2188 )
2189
2190
2191 class CibLoadErrorScopeMissing(NameBuildTest):
2192 def test_all(self):
2193 self.assert_message_from_report(
2194 "unable to get cib, scope 'scope-name' not present in cib",
2195 reports.CibLoadErrorScopeMissing("scope-name", "reason"),
2196 )
2197
2198
2199 class CibLoadErrorBadFormat(NameBuildTest):
2200 def test_message(self):
2201 self.assert_message_from_report(
2202 "unable to get cib, something wrong",
2203 reports.CibLoadErrorBadFormat("something wrong"),
2204 )
2205
2206
2207 class CibCannotFindMandatorySection(NameBuildTest):
2208 def test_all(self):
2209 self.assert_message_from_report(
2210 "Unable to get 'section-name' section of cib",
2211 reports.CibCannotFindMandatorySection("section-name"),
2212 )
2213
2214
2215 class CibPushError(NameBuildTest):
2216 def test_all(self):
2217 self.assert_message_from_report(
2218 "Unable to update cib\nreason\npushed-cib",
2219 reports.CibPushError("reason", "pushed-cib"),
2220 )
2221
2222
2223 class CibSaveTmpError(NameBuildTest):
2224 def test_all(self):
2225 self.assert_message_from_report(
2226 "Unable to save CIB to a temporary file: reason",
2227 reports.CibSaveTmpError("reason"),
2228 )
2229
2230
2231 class CibDiffError(NameBuildTest):
2232 def test_success(self):
2233 self.assert_message_from_report(
2234 "Unable to diff CIB: error message\n<cib-new />",
2235 reports.CibDiffError("error message", "<cib-old />", "<cib-new />"),
2236 )
2237
2238
2239 class CibSimulateError(NameBuildTest):
2240 def test_success(self):
2241 self.assert_message_from_report(
2242 "Unable to simulate changes in CIB: error message",
2243 reports.CibSimulateError("error message"),
2244 )
2245
2246 def test_empty_reason(self):
2247 self.assert_message_from_report(
2248 "Unable to simulate changes in CIB",
2249 reports.CibSimulateError(""),
2250 )
2251
2252
2253 class CrmMonError(NameBuildTest):
2254 def test_without_reason(self):
2255 self.assert_message_from_report(
2256 "error running crm_mon, is pacemaker running?",
2257 reports.CrmMonError(""),
2258 )
2259
2260 def test_with_reason(self):
2261 self.assert_message_from_report(
2262 (
2263 "error running crm_mon, is pacemaker running?"
2264 "\n reason\n spans several lines"
2265 ),
2266 reports.CrmMonError("reason\nspans several lines"),
2267 )
2268
2269
2270 class BadPcmkApiResponseFormat(NameBuildTest):
2271 def test_all(self):
2272 self.assert_message_from_report(
2273 (
2274 "Cannot process pacemaker response due to a parse error: "
2275 "detailed parse or xml error\n"
2276 "pacemaker tool output"
2277 ),
2278 reports.BadPcmkApiResponseFormat(
2279 "detailed parse or xml error", "pacemaker tool output"
2280 ),
2281 )
2282
2283
2284 class BadClusterStateFormat(NameBuildTest):
2285 def test_all(self):
2286 self.assert_message_from_report(
2287 "cannot load cluster status, xml does not conform to the schema",
2288 reports.BadClusterStateFormat(),
2289 )
2290
2291
2292 class BadClusterStateData(NameBuildTest):
2293 def test_no_reason(self):
2294 self.assert_message_from_report(
2295 (
2296 "Cannot load cluster status, xml does not describe "
2297 "valid cluster status"
2298 ),
2299 reports.BadClusterStateData(),
2300 )
2301
2302 def test_reason(self):
2303 self.assert_message_from_report(
2304 (
2305 "Cannot load cluster status, xml does not describe "
2306 "valid cluster status: sample reason"
2307 ),
2308 reports.BadClusterStateData("sample reason"),
2309 )
2310
2311
2312 class WaitForIdleStarted(NameBuildTest):
2313 def test_timeout(self):
2314 timeout = 20
2315 self.assert_message_from_report(
2316 (
2317 "Waiting for the cluster to apply configuration changes "
2318 f"(timeout: {timeout} seconds)..."
2319 ),
2320 reports.WaitForIdleStarted(timeout),
2321 )
2322
2323 def test_timeout_singular(self):
2324 timeout = 1
2325 self.assert_message_from_report(
2326 (
2327 "Waiting for the cluster to apply configuration changes "
2328 f"(timeout: {timeout} second)..."
2329 ),
2330 reports.WaitForIdleStarted(timeout),
2331 )
2332
2333 def test_timeout_0(self):
2334 self.assert_message_from_report(
2335 "Waiting for the cluster to apply configuration changes...",
2336 reports.WaitForIdleStarted(0),
2337 )
2338
2339 def test_timeout_negative(self):
2340 self.assert_message_from_report(
2341 "Waiting for the cluster to apply configuration changes...",
2342 reports.WaitForIdleStarted(-1),
2343 )
2344
2345
2346 class WaitForIdleTimedOut(NameBuildTest):
2347 def test_all(self):
2348 self.assert_message_from_report(
2349 "waiting timeout\n\nreason", reports.WaitForIdleTimedOut("reason")
2350 )
2351
2352
2353 class WaitForIdleError(NameBuildTest):
2354 def test_all(self):
2355 self.assert_message_from_report(
2356 "reason", reports.WaitForIdleError("reason")
2357 )
2358
2359
2360 class WaitForIdleNotLiveCluster(NameBuildTest):
2361 def test_all(self):
2362 self.assert_message_from_report(
2363 "Cannot pass CIB together with 'wait'",
2364 reports.WaitForIdleNotLiveCluster(),
2365 )
2366
2367
2368 class ResourceCleanupError(NameBuildTest):
2369 def test_minimal(self):
2370 self.assert_message_from_report(
2371 "Unable to forget failed operations of resources\nsomething wrong",
2372 reports.ResourceCleanupError("something wrong"),
2373 )
2374
2375 def test_node(self):
2376 self.assert_message_from_report(
2377 "Unable to forget failed operations of resources\nsomething wrong",
2378 reports.ResourceCleanupError("something wrong", node="N1"),
2379 )
2380
2381 def test_resource(self):
2382 self.assert_message_from_report(
2383 "Unable to forget failed operations of resource: R1\n"
2384 "something wrong",
2385 reports.ResourceCleanupError("something wrong", "R1"),
2386 )
2387
2388 def test_resource_and_node(self):
2389 self.assert_message_from_report(
2390 "Unable to forget failed operations of resource: R1\n"
2391 "something wrong",
2392 reports.ResourceCleanupError("something wrong", "R1", "N1"),
2393 )
2394
2395
2396 class ResourceRefreshError(NameBuildTest):
2397 def test_minimal(self):
2398 self.assert_message_from_report(
2399 "Unable to delete history of resources\nsomething wrong",
2400 reports.ResourceRefreshError("something wrong"),
2401 )
2402
2403 def test_node(self):
2404 self.assert_message_from_report(
2405 "Unable to delete history of resources\nsomething wrong",
2406 reports.ResourceRefreshError(
2407 "something wrong",
2408 node="N1",
2409 ),
2410 )
2411
2412 def test_resource(self):
2413 self.assert_message_from_report(
2414 "Unable to delete history of resource: R1\nsomething wrong",
2415 reports.ResourceRefreshError("something wrong", "R1"),
2416 )
2417
2418 def test_resource_and_node(self):
2419 self.assert_message_from_report(
2420 "Unable to delete history of resource: R1\nsomething wrong",
2421 reports.ResourceRefreshError("something wrong", "R1", "N1"),
2422 )
2423
2424
2425 class ResourceRefreshTooTimeConsuming(NameBuildTest):
2426 def test_success(self):
2427 self.assert_message_from_report(
2428 "Deleting history of all resources on all nodes will execute more "
2429 "than 25 operations in the cluster, which may negatively "
2430 "impact the responsiveness of the cluster. Consider specifying "
2431 "resource and/or node",
2432 reports.ResourceRefreshTooTimeConsuming(25),
2433 )
2434
2435
2436 class ResourceOperationIntervalDuplication(NameBuildTest):
2437 def test_build_message_with_data(self):
2438 self.assert_message_from_report(
2439 "multiple specification of the same operation with the same"
2440 " interval:"
2441 "\nmonitor with intervals 3600s, 60m, 1h"
2442 "\nmonitor with intervals 60s, 1m",
2443 reports.ResourceOperationIntervalDuplication(
2444 {
2445 "monitor": [
2446 ["3600s", "60m", "1h"],
2447 ["60s", "1m"],
2448 ],
2449 }
2450 ),
2451 )
2452
2453
2454 class ResourceOperationIntervalAdapted(NameBuildTest):
2455 def test_build_message_with_data(self):
2456 self.assert_message_from_report(
2457 "changing a monitor operation interval from 10 to 11 to make the"
2458 " operation unique",
2459 reports.ResourceOperationIntervalAdapted("monitor", "10", "11"),
2460 )
2461
2462
2463 class NodeNotFound(NameBuildTest):
2464 def test_build_messages(self):
2465 self.assert_message_from_report(
2466 "Node 'SOME_NODE' does not appear to exist in configuration",
2467 reports.NodeNotFound("SOME_NODE"),
2468 )
2469
2470 def test_build_messages_with_one_search_types(self):
2471 self.assert_message_from_report(
2472 "remote node 'SOME_NODE' does not appear to exist in configuration",
2473 reports.NodeNotFound("SOME_NODE", ["remote"]),
2474 )
2475
2476 def test_build_messages_with_multiple_search_types(self):
2477 self.assert_message_from_report(
2478 "nor remote node or guest node 'SOME_NODE' does not appear to exist"
2479 " in configuration",
2480 reports.NodeNotFound("SOME_NODE", ["remote", "guest"]),
2481 )
2482
2483
2484 class NodeRenameNamesEqual(NameBuildTest):
2485 def test_message(self):
2486 self.assert_message_from_report(
2487 (
2488 "Unable to rename node 'node1': "
2489 "new name is the same as the current name"
2490 ),
2491 reports.NodeRenameNamesEqual("node1"),
2492 )
2493
2494
2495 class NodeToClearIsStillInCluster(NameBuildTest):
2496 def test_build_messages(self):
2497 self.assert_message_from_report(
2498 "node 'node1' seems to be still in the cluster"
2499 "; this command should be used only with nodes that have been"
2500 " removed from the cluster",
2501 reports.NodeToClearIsStillInCluster("node1"),
2502 )
2503
2504
2505 class NodeRemoveInPacemakerFailed(NameBuildTest):
2506 def test_minimal(self):
2507 self.assert_message_from_report(
2508 ("Unable to remove node(s) 'NODE1', 'NODE2' from pacemaker"),
2509 reports.NodeRemoveInPacemakerFailed(["NODE2", "NODE1"]),
2510 )
2511
2512 def test_without_node(self):
2513 self.assert_message_from_report(
2514 "Unable to remove node(s) 'NODE' from pacemaker: reason",
2515 reports.NodeRemoveInPacemakerFailed(["NODE"], reason="reason"),
2516 )
2517
2518 def test_with_node(self):
2519 self.assert_message_from_report(
2520 (
2521 "node-a: Unable to remove node(s) 'NODE1', 'NODE2' from "
2522 "pacemaker: reason"
2523 ),
2524 reports.NodeRemoveInPacemakerFailed(
2525 ["NODE1", "NODE2"], node="node-a", reason="reason"
2526 ),
2527 )
2528
2529
2530 class NodeRemoveInPacemakerSkipped(NameBuildTest):
2531 def test_one_node(self):
2532 self.assert_message_from_report(
2533 (
2534 "Skipping removal of node 'NODE1' from pacemaker because the "
2535 "command does not run on a live cluster"
2536 ),
2537 reports.NodeRemoveInPacemakerSkipped(
2538 const.REASON_NOT_LIVE_CIB, ["NODE1"]
2539 ),
2540 )
2541
2542 def test_multiple_nodes(self):
2543 self.assert_message_from_report(
2544 (
2545 "Skipping removal of nodes 'NODE1', 'NODE2' from pacemaker "
2546 "because the command does not run on a live cluster"
2547 ),
2548 reports.NodeRemoveInPacemakerSkipped(
2549 const.REASON_NOT_LIVE_CIB, ["NODE2", "NODE1"]
2550 ),
2551 )
2552
2553 def test_with_node(self):
2554 self.assert_message_from_report(
2555 (
2556 "node-a: Unable to remove node(s) 'NODE1', 'NODE2' from "
2557 "pacemaker: reason"
2558 ),
2559 reports.NodeRemoveInPacemakerFailed(
2560 ["NODE1", "NODE2"], node="node-a", reason="reason"
2561 ),
2562 )
2563
2564
2565 class MultipleResultsFound(NameBuildTest):
2566 def test_minimal(self):
2567 self.assert_message_from_report(
2568 "more than one resource found: 'ID1', 'ID2'",
2569 reports.MultipleResultsFound("resource", ["ID2", "ID1"]),
2570 )
2571
2572 def test_build_messages(self):
2573 self.assert_message_from_report(
2574 "more than one resource for 'NODE-NAME' found: 'ID1', 'ID2'",
2575 reports.MultipleResultsFound(
2576 "resource", ["ID2", "ID1"], "NODE-NAME"
2577 ),
2578 )
2579
2580
2581 class PacemakerSimulationResult(NameBuildTest):
2582 def test_default(self):
2583 self.assert_message_from_report(
2584 "\nSimulation result:\ncrm_simulate output",
2585 reports.PacemakerSimulationResult("crm_simulate output"),
2586 )
2587
2588
2589 class PacemakerLocalNodeNameNotFound(NameBuildTest):
2590 def test_all(self):
2591 self.assert_message_from_report(
2592 "unable to get local node name from pacemaker: reason",
2593 reports.PacemakerLocalNodeNameNotFound("reason"),
2594 )
2595
2596
2597 class ServiceActionStarted(NameBuildTest):
2598 def test_start(self):
2599 self.assert_message_from_report(
2600 "Starting a_service...",
2601 reports.ServiceActionStarted(
2602 const.SERVICE_ACTION_START, "a_service"
2603 ),
2604 )
2605
2606 def test_start_instance(self):
2607 self.assert_message_from_report(
2608 "Starting a_service@an_instance...",
2609 reports.ServiceActionStarted(
2610 const.SERVICE_ACTION_START, "a_service", "an_instance"
2611 ),
2612 )
2613
2614 def test_stop(self):
2615 self.assert_message_from_report(
2616 "Stopping a_service...",
2617 reports.ServiceActionStarted(
2618 const.SERVICE_ACTION_STOP, "a_service"
2619 ),
2620 )
2621
2622 def test_stop_instance(self):
2623 self.assert_message_from_report(
2624 "Stopping a_service@an_instance...",
2625 reports.ServiceActionStarted(
2626 const.SERVICE_ACTION_STOP, "a_service", "an_instance"
2627 ),
2628 )
2629
2630 def test_enable(self):
2631 self.assert_message_from_report(
2632 "Enabling a_service...",
2633 reports.ServiceActionStarted(
2634 const.SERVICE_ACTION_ENABLE, "a_service"
2635 ),
2636 )
2637
2638 def test_enable_instance(self):
2639 self.assert_message_from_report(
2640 "Enabling a_service@an_instance...",
2641 reports.ServiceActionStarted(
2642 const.SERVICE_ACTION_ENABLE, "a_service", "an_instance"
2643 ),
2644 )
2645
2646 def test_disable(self):
2647 self.assert_message_from_report(
2648 "Disabling a_service...",
2649 reports.ServiceActionStarted(
2650 const.SERVICE_ACTION_DISABLE, "a_service"
2651 ),
2652 )
2653
2654 def test_disable_instance(self):
2655 self.assert_message_from_report(
2656 "Disabling a_service@an_instance...",
2657 reports.ServiceActionStarted(
2658 const.SERVICE_ACTION_DISABLE, "a_service", "an_instance"
2659 ),
2660 )
2661
2662 def test_kill(self):
2663 self.assert_message_from_report(
2664 "Killing a_service...",
2665 reports.ServiceActionStarted(
2666 const.SERVICE_ACTION_KILL, "a_service"
2667 ),
2668 )
2669
2670 def test_kill_instance(self):
2671 self.assert_message_from_report(
2672 "Killing a_service@an_instance...",
2673 reports.ServiceActionStarted(
2674 const.SERVICE_ACTION_KILL, "a_service", "an_instance"
2675 ),
2676 )
2677
2678
2679 # TODO: add tests for node if needed
2680 class ServiceActionFailed(NameBuildTest):
2681 def test_start(self):
2682 self.assert_message_from_report(
2683 "Unable to start a_service: a_reason",
2684 reports.ServiceActionFailed(
2685 const.SERVICE_ACTION_START, "a_service", "a_reason"
2686 ),
2687 )
2688
2689 def test_start_instance(self):
2690 self.assert_message_from_report(
2691 "Unable to start a_service@an_instance: a_reason",
2692 reports.ServiceActionFailed(
2693 const.SERVICE_ACTION_START,
2694 "a_service",
2695 "a_reason",
2696 instance="an_instance",
2697 ),
2698 )
2699
2700 def test_stop(self):
2701 self.assert_message_from_report(
2702 "Unable to stop a_service: a_reason",
2703 reports.ServiceActionFailed(
2704 const.SERVICE_ACTION_STOP, "a_service", "a_reason"
2705 ),
2706 )
2707
2708 def test_stop_instance(self):
2709 self.assert_message_from_report(
2710 "Unable to stop a_service@an_instance: a_reason",
2711 reports.ServiceActionFailed(
2712 const.SERVICE_ACTION_STOP,
2713 "a_service",
2714 "a_reason",
2715 instance="an_instance",
2716 ),
2717 )
2718
2719 def test_enable(self):
2720 self.assert_message_from_report(
2721 "Unable to enable a_service: a_reason",
2722 reports.ServiceActionFailed(
2723 const.SERVICE_ACTION_ENABLE, "a_service", "a_reason"
2724 ),
2725 )
2726
2727 def test_enable_instance(self):
2728 self.assert_message_from_report(
2729 "Unable to enable a_service@an_instance: a_reason",
2730 reports.ServiceActionFailed(
2731 const.SERVICE_ACTION_ENABLE,
2732 "a_service",
2733 "a_reason",
2734 instance="an_instance",
2735 ),
2736 )
2737
2738 def test_disable(self):
2739 self.assert_message_from_report(
2740 "Unable to disable a_service: a_reason",
2741 reports.ServiceActionFailed(
2742 const.SERVICE_ACTION_DISABLE, "a_service", "a_reason"
2743 ),
2744 )
2745
2746 def test_disable_instance(self):
2747 self.assert_message_from_report(
2748 "Unable to disable a_service@an_instance: a_reason",
2749 reports.ServiceActionFailed(
2750 const.SERVICE_ACTION_DISABLE,
2751 "a_service",
2752 "a_reason",
2753 instance="an_instance",
2754 ),
2755 )
2756
2757 def test_kill(self):
2758 self.assert_message_from_report(
2759 "Unable to kill a_service: a_reason",
2760 reports.ServiceActionFailed(
2761 const.SERVICE_ACTION_KILL, "a_service", "a_reason"
2762 ),
2763 )
2764
2765 def test_kill_instance(self):
2766 self.assert_message_from_report(
2767 "Unable to kill a_service@an_instance: a_reason",
2768 reports.ServiceActionFailed(
2769 const.SERVICE_ACTION_KILL,
2770 "a_service",
2771 "a_reason",
2772 instance="an_instance",
2773 ),
2774 )
2775
2776
2777 # TODO: add tests for node if needed
2778 class ServiceActionSucceeded(NameBuildTest):
2779 def test_start(self):
2780 self.assert_message_from_report(
2781 "a_service started",
2782 reports.ServiceActionSucceeded(
2783 const.SERVICE_ACTION_START, "a_service"
2784 ),
2785 )
2786
2787 def test_start_instance(self):
2788 self.assert_message_from_report(
2789 "a_service@an_instance started",
2790 reports.ServiceActionSucceeded(
2791 const.SERVICE_ACTION_START, "a_service", instance="an_instance"
2792 ),
2793 )
2794
2795 def test_stop(self):
2796 self.assert_message_from_report(
2797 "a_service stopped",
2798 reports.ServiceActionSucceeded(
2799 const.SERVICE_ACTION_STOP, "a_service"
2800 ),
2801 )
2802
2803 def test_stop_instance(self):
2804 self.assert_message_from_report(
2805 "a_service@an_instance stopped",
2806 reports.ServiceActionSucceeded(
2807 const.SERVICE_ACTION_STOP, "a_service", instance="an_instance"
2808 ),
2809 )
2810
2811 def test_enable(self):
2812 self.assert_message_from_report(
2813 "a_service enabled",
2814 reports.ServiceActionSucceeded(
2815 const.SERVICE_ACTION_ENABLE, "a_service"
2816 ),
2817 )
2818
2819 def test_enable_instance(self):
2820 self.assert_message_from_report(
2821 "a_service@an_instance enabled",
2822 reports.ServiceActionSucceeded(
2823 const.SERVICE_ACTION_ENABLE, "a_service", instance="an_instance"
2824 ),
2825 )
2826
2827 def test_disable(self):
2828 self.assert_message_from_report(
2829 "a_service disabled",
2830 reports.ServiceActionSucceeded(
2831 const.SERVICE_ACTION_DISABLE, "a_service"
2832 ),
2833 )
2834
2835 def test_disable_instance(self):
2836 self.assert_message_from_report(
2837 "a_service@an_instance disabled",
2838 reports.ServiceActionSucceeded(
2839 const.SERVICE_ACTION_DISABLE,
2840 "a_service",
2841 instance="an_instance",
2842 ),
2843 )
2844
2845 def test_kill(self):
2846 self.assert_message_from_report(
2847 "a_service killed",
2848 reports.ServiceActionSucceeded(
2849 const.SERVICE_ACTION_KILL, "a_service"
2850 ),
2851 )
2852
2853 def test_kill_instance(self):
2854 self.assert_message_from_report(
2855 "a_service@an_instance killed",
2856 reports.ServiceActionSucceeded(
2857 const.SERVICE_ACTION_KILL, "a_service", instance="an_instance"
2858 ),
2859 )
2860
2861
2862 class ServiceActionSkipped(NameBuildTest):
2863 def test_start(self):
2864 self.assert_message_from_report(
2865 "not starting a_service: a_reason",
2866 reports.ServiceActionSkipped(
2867 const.SERVICE_ACTION_START, "a_service", "a_reason"
2868 ),
2869 )
2870
2871 def test_start_instance(self):
2872 self.assert_message_from_report(
2873 "not starting a_service@an_instance: a_reason",
2874 reports.ServiceActionSkipped(
2875 const.SERVICE_ACTION_START,
2876 "a_service",
2877 "a_reason",
2878 instance="an_instance",
2879 ),
2880 )
2881
2882 def test_stop(self):
2883 self.assert_message_from_report(
2884 "not stopping a_service: a_reason",
2885 reports.ServiceActionSkipped(
2886 const.SERVICE_ACTION_STOP, "a_service", "a_reason"
2887 ),
2888 )
2889
2890 def test_stop_instance(self):
2891 self.assert_message_from_report(
2892 "not stopping a_service@an_instance: a_reason",
2893 reports.ServiceActionSkipped(
2894 const.SERVICE_ACTION_STOP,
2895 "a_service",
2896 "a_reason",
2897 instance="an_instance",
2898 ),
2899 )
2900
2901 def test_enable(self):
2902 self.assert_message_from_report(
2903 "not enabling a_service: a_reason",
2904 reports.ServiceActionSkipped(
2905 const.SERVICE_ACTION_ENABLE, "a_service", "a_reason"
2906 ),
2907 )
2908
2909 def test_enable_instance(self):
2910 self.assert_message_from_report(
2911 "not enabling a_service@an_instance: a_reason",
2912 reports.ServiceActionSkipped(
2913 const.SERVICE_ACTION_ENABLE,
2914 "a_service",
2915 "a_reason",
2916 instance="an_instance",
2917 ),
2918 )
2919
2920 def test_disable(self):
2921 self.assert_message_from_report(
2922 "not disabling a_service: a_reason",
2923 reports.ServiceActionSkipped(
2924 const.SERVICE_ACTION_DISABLE, "a_service", "a_reason"
2925 ),
2926 )
2927
2928 def test_disable_instance(self):
2929 self.assert_message_from_report(
2930 "not disabling a_service@an_instance: a_reason",
2931 reports.ServiceActionSkipped(
2932 const.SERVICE_ACTION_DISABLE,
2933 "a_service",
2934 "a_reason",
2935 instance="an_instance",
2936 ),
2937 )
2938
2939 def test_kill(self):
2940 self.assert_message_from_report(
2941 "not killing a_service: a_reason",
2942 reports.ServiceActionSkipped(
2943 const.SERVICE_ACTION_KILL, "a_service", "a_reason"
2944 ),
2945 )
2946
2947 def test_kill_instance(self):
2948 self.assert_message_from_report(
2949 "not killing a_service@an_instance: a_reason",
2950 reports.ServiceActionSkipped(
2951 const.SERVICE_ACTION_KILL,
2952 "a_service",
2953 "a_reason",
2954 instance="an_instance",
2955 ),
2956 )
2957
2958
2959 class ServiceUnableToDetectInitSystem(NameBuildTest):
2960 def test_success(self):
2961 self.assert_message_from_report(
2962 (
2963 "Unable to detect init system. All actions related to system "
2964 "services will be skipped."
2965 ),
2966 reports.ServiceUnableToDetectInitSystem(),
2967 )
2968
2969
2970 class UnableToGetAgentMetadata(NameBuildTest):
2971 def test_all(self):
2972 self.assert_message_from_report(
2973 (
2974 "Agent 'agent-name' is not installed or does not provide valid "
2975 "metadata: reason"
2976 ),
2977 reports.UnableToGetAgentMetadata("agent-name", "reason"),
2978 )
2979
2980
2981 class InvalidResourceAgentName(NameBuildTest):
2982 def test_build_message_with_data(self):
2983 self.assert_message_from_report(
2984 "Invalid resource agent name ':name'. Use standard:provider:type "
2985 "when standard is 'ocf' or standard:type otherwise.",
2986 reports.InvalidResourceAgentName(":name"),
2987 )
2988
2989
2990 class InvalidStonithAgentName(NameBuildTest):
2991 def test_build_message_with_data(self):
2992 self.assert_message_from_report(
2993 "Invalid stonith agent name 'fence:name'. Agent name cannot contain "
2994 "the ':' character, do not use the 'stonith:' prefix.",
2995 reports.InvalidStonithAgentName("fence:name"),
2996 )
2997
2998
2999 class AgentNameGuessed(NameBuildTest):
3000 def test_build_message_with_data(self):
3001 self.assert_message_from_report(
3002 "Assumed agent name 'ocf:heartbeat:Delay' (deduced from 'Delay')",
3003 reports.AgentNameGuessed("Delay", "ocf:heartbeat:Delay"),
3004 )
3005
3006
3007 class AgentNameGuessFoundMoreThanOne(NameBuildTest):
3008 def test_all(self):
3009 self.assert_message_from_report(
3010 (
3011 "Multiple agents match 'agent', please specify full name: "
3012 "'agent1', 'agent2' or 'agent3'"
3013 ),
3014 reports.AgentNameGuessFoundMoreThanOne(
3015 "agent", ["agent2", "agent1", "agent3"]
3016 ),
3017 )
3018
3019
3020 class AgentNameGuessFoundNone(NameBuildTest):
3021 def test_all(self):
3022 self.assert_message_from_report(
3023 "Unable to find agent 'agent-name', try specifying its full name",
3024 reports.AgentNameGuessFoundNone("agent-name"),
3025 )
3026
3027
3028 class AgentImplementsUnsupportedOcfVersion(NameBuildTest):
3029 def test_singular(self):
3030 self.assert_message_from_report(
3031 "Unable to process agent 'agent-name' as it implements unsupported "
3032 "OCF version 'ocf-2.3', supported version is: 'v1'",
3033 reports.AgentImplementsUnsupportedOcfVersion(
3034 "agent-name", "ocf-2.3", ["v1"]
3035 ),
3036 )
3037
3038 def test_plural(self):
3039 self.assert_message_from_report(
3040 "Unable to process agent 'agent-name' as it implements unsupported "
3041 "OCF version 'ocf-2.3', supported versions are: 'v1', 'v2', 'v3'",
3042 reports.AgentImplementsUnsupportedOcfVersion(
3043 "agent-name", "ocf-2.3", ["v1", "v2", "v3"]
3044 ),
3045 )
3046
3047
3048 class AgentGenericError(NameBuildTest):
3049 def test_success(self):
3050 self.assert_message_from_report(
3051 "Unable to load agent 'agent-name'",
3052 reports.AgentGenericError("agent-name"),
3053 )
3054
3055
3056 class OmittingNode(NameBuildTest):
3057 def test_all(self):
3058 self.assert_message_from_report(
3059 "Omitting node 'node1'", reports.OmittingNode("node1")
3060 )
3061
3062
3063 class SbdCheckStarted(NameBuildTest):
3064 def test_all(self):
3065 self.assert_message_from_report(
3066 "Running SBD pre-enabling checks...", reports.SbdCheckStarted()
3067 )
3068
3069
3070 class SbdCheckSuccess(NameBuildTest):
3071 def test_all(self):
3072 self.assert_message_from_report(
3073 "node1: SBD pre-enabling checks done",
3074 reports.SbdCheckSuccess("node1"),
3075 )
3076
3077
3078 class SbdConfigDistributionStarted(NameBuildTest):
3079 def test_all(self):
3080 self.assert_message_from_report(
3081 "Distributing SBD config...", reports.SbdConfigDistributionStarted()
3082 )
3083
3084
3085 class SbdConfigAcceptedByNode(NameBuildTest):
3086 def test_all(self):
3087 self.assert_message_from_report(
3088 "node1: SBD config saved", reports.SbdConfigAcceptedByNode("node1")
3089 )
3090
3091
3092 class UnableToGetSbdConfig(NameBuildTest):
3093 def test_no_reason(self):
3094 self.assert_message_from_report(
3095 "Unable to get SBD configuration from node 'node1'",
3096 reports.UnableToGetSbdConfig("node1", ""),
3097 )
3098
3099 def test_all(self):
3100 self.assert_message_from_report(
3101 "Unable to get SBD configuration from node 'node2': reason",
3102 reports.UnableToGetSbdConfig("node2", "reason"),
3103 )
3104
3105
3106 class SbdDeviceInitializationStarted(NameBuildTest):
3107 def test_more_devices(self):
3108 self.assert_message_from_report(
3109 "Initializing devices '/dev1', '/dev2', '/dev3'...",
3110 reports.SbdDeviceInitializationStarted(["/dev3", "/dev2", "/dev1"]),
3111 )
3112
3113 def test_one_device(self):
3114 self.assert_message_from_report(
3115 "Initializing device '/dev1'...",
3116 reports.SbdDeviceInitializationStarted(["/dev1"]),
3117 )
3118
3119
3120 class SbdDeviceInitializationSuccess(NameBuildTest):
3121 def test_more_devices(self):
3122 self.assert_message_from_report(
3123 "Devices initialized successfully",
3124 reports.SbdDeviceInitializationSuccess(["/dev2", "/dev1"]),
3125 )
3126
3127 def test_one_device(self):
3128 self.assert_message_from_report(
3129 "Device initialized successfully",
3130 reports.SbdDeviceInitializationSuccess(["/dev1"]),
3131 )
3132
3133
3134 class SbdDeviceInitializationError(NameBuildTest):
3135 def test_more_devices(self):
3136 self.assert_message_from_report(
3137 "Initialization of devices '/dev1', '/dev2' failed: this is reason",
3138 reports.SbdDeviceInitializationError(
3139 ["/dev2", "/dev1"], "this is reason"
3140 ),
3141 )
3142
3143 def test_one_device(self):
3144 self.assert_message_from_report(
3145 "Initialization of device '/dev2' failed: this is reason",
3146 reports.SbdDeviceInitializationError(["/dev2"], "this is reason"),
3147 )
3148
3149
3150 class SbdDeviceListError(NameBuildTest):
3151 def test_build_message(self):
3152 self.assert_message_from_report(
3153 "Unable to get list of messages from device '/dev': this is reason",
3154 reports.SbdDeviceListError("/dev", "this is reason"),
3155 )
3156
3157
3158 class SbdDeviceMessageError(NameBuildTest):
3159 def test_build_message(self):
3160 self.assert_message_from_report(
3161 (
3162 "Unable to set message 'test' for node 'node1' on device "
3163 "'/dev1': this is reason"
3164 ),
3165 reports.SbdDeviceMessageError(
3166 "/dev1", "node1", "test", "this is reason"
3167 ),
3168 )
3169
3170
3171 class SbdDeviceDumpError(NameBuildTest):
3172 def test_build_message(self):
3173 self.assert_message_from_report(
3174 "Unable to get SBD headers from device '/dev1': this is reason",
3175 reports.SbdDeviceDumpError("/dev1", "this is reason"),
3176 )
3177
3178
3179 class FilesDistributionStarted(NameBuildTest):
3180 def test_build_messages(self):
3181 self.assert_message_from_report(
3182 "Sending 'first', 'second'",
3183 reports.FilesDistributionStarted(["first", "second"]),
3184 )
3185
3186 def test_build_messages_with_single_node(self):
3187 self.assert_message_from_report(
3188 "Sending 'first' to 'node1'",
3189 reports.FilesDistributionStarted(["first"], ["node1"]),
3190 )
3191
3192 def test_build_messages_with_nodes(self):
3193 self.assert_message_from_report(
3194 "Sending 'first', 'second' to 'node1', 'node2'",
3195 reports.FilesDistributionStarted(
3196 ["first", "second"], ["node1", "node2"]
3197 ),
3198 )
3199
3200
3201 class FilesDistributionSkipped(NameBuildTest):
3202 def test_not_live(self):
3203 self.assert_message_from_report(
3204 "Distribution of 'file1' to 'nodeA', 'nodeB' was skipped because "
3205 "the command does not run on a live cluster. "
3206 "Please, distribute the file(s) manually.",
3207 reports.FilesDistributionSkipped(
3208 const.REASON_NOT_LIVE_CIB, ["file1"], ["nodeA", "nodeB"]
3209 ),
3210 )
3211
3212 def test_unreachable(self):
3213 self.assert_message_from_report(
3214 "Distribution of 'file1', 'file2' to 'nodeA' was skipped because "
3215 "pcs is unable to connect to the node(s). Please, distribute "
3216 "the file(s) manually.",
3217 reports.FilesDistributionSkipped(
3218 const.REASON_UNREACHABLE, ["file1", "file2"], ["nodeA"]
3219 ),
3220 )
3221
3222 def test_unknown_reason(self):
3223 self.assert_message_from_report(
3224 "Distribution of 'file1', 'file2' to 'nodeA', 'nodeB' was skipped "
3225 "because some undefined reason. Please, distribute the file(s) "
3226 "manually.",
3227 reports.FilesDistributionSkipped(
3228 "some undefined reason", ["file1", "file2"], ["nodeA", "nodeB"]
3229 ),
3230 )
3231
3232
3233 class FileDistributionSuccess(NameBuildTest):
3234 def test_build_messages(self):
3235 self.assert_message_from_report(
3236 "node1: successful distribution of the file 'some authfile'",
3237 reports.FileDistributionSuccess("node1", "some authfile"),
3238 )
3239
3240
3241 class FileDistributionError(NameBuildTest):
3242 def test_build_messages(self):
3243 self.assert_message_from_report(
3244 "node1: unable to distribute file 'file1': permission denied",
3245 reports.FileDistributionError(
3246 "node1", "file1", "permission denied"
3247 ),
3248 )
3249
3250
3251 class FilesRemoveFromNodesStarted(NameBuildTest):
3252 def test_minimal(self):
3253 self.assert_message_from_report(
3254 "Requesting remove 'file'",
3255 reports.FilesRemoveFromNodesStarted(["file"]),
3256 )
3257
3258 def test_with_single_node(self):
3259 self.assert_message_from_report(
3260 "Requesting remove 'first' from 'node1'",
3261 reports.FilesRemoveFromNodesStarted(["first"], ["node1"]),
3262 )
3263
3264 def test_with_multiple_nodes(self):
3265 self.assert_message_from_report(
3266 "Requesting remove 'first', 'second' from 'node1', 'node2'",
3267 reports.FilesRemoveFromNodesStarted(
3268 ["first", "second"],
3269 ["node1", "node2"],
3270 ),
3271 )
3272
3273
3274 class FilesRemoveFromNodesSkipped(NameBuildTest):
3275 def test_not_live(self):
3276 self.assert_message_from_report(
3277 "Removing 'file1' from 'nodeA', 'nodeB' was skipped because the "
3278 "command does not run on a live cluster. "
3279 "Please, remove the file(s) manually.",
3280 reports.FilesRemoveFromNodesSkipped(
3281 const.REASON_NOT_LIVE_CIB, ["file1"], ["nodeA", "nodeB"]
3282 ),
3283 )
3284
3285 def test_unreachable(self):
3286 self.assert_message_from_report(
3287 "Removing 'file1', 'file2' from 'nodeA' was skipped because pcs is "
3288 "unable to connect to the node(s). Please, remove the file(s) "
3289 "manually.",
3290 reports.FilesRemoveFromNodesSkipped(
3291 const.REASON_UNREACHABLE, ["file1", "file2"], ["nodeA"]
3292 ),
3293 )
3294
3295 def test_unknown_reason(self):
3296 self.assert_message_from_report(
3297 "Removing 'file1', 'file2' from 'nodeA', 'nodeB' was skipped "
3298 "because some undefined reason. Please, remove the file(s) "
3299 "manually.",
3300 reports.FilesRemoveFromNodesSkipped(
3301 "some undefined reason", ["file1", "file2"], ["nodeA", "nodeB"]
3302 ),
3303 )
3304
3305
3306 class FileRemoveFromNodeSuccess(NameBuildTest):
3307 def test_build_messages(self):
3308 self.assert_message_from_report(
3309 "node1: successful removal of the file 'some authfile'",
3310 reports.FileRemoveFromNodeSuccess("node1", "some authfile"),
3311 )
3312
3313
3314 class FileRemoveFromNodeError(NameBuildTest):
3315 def test_build_messages(self):
3316 self.assert_message_from_report(
3317 "node1: unable to remove file 'file1': permission denied",
3318 reports.FileRemoveFromNodeError(
3319 "node1", "file1", "permission denied"
3320 ),
3321 )
3322
3323
3324 class ServiceCommandsOnNodesStarted(NameBuildTest):
3325 def test_build_messages(self):
3326 self.assert_message_from_report(
3327 "Requesting 'action1', 'action2'",
3328 reports.ServiceCommandsOnNodesStarted(["action1", "action2"]),
3329 )
3330
3331 def test_build_messages_with_single_node(self):
3332 self.assert_message_from_report(
3333 "Requesting 'action1' on 'node1'",
3334 reports.ServiceCommandsOnNodesStarted(
3335 ["action1"],
3336 ["node1"],
3337 ),
3338 )
3339
3340 def test_build_messages_with_nodes(self):
3341 self.assert_message_from_report(
3342 "Requesting 'action1', 'action2' on 'node1', 'node2'",
3343 reports.ServiceCommandsOnNodesStarted(
3344 ["action1", "action2"],
3345 ["node1", "node2"],
3346 ),
3347 )
3348
3349
3350 class ServiceCommandsOnNodesSkipped(NameBuildTest):
3351 def test_not_live(self):
3352 self.assert_message_from_report(
3353 "Running action(s) 'pacemaker_remote enable', 'pacemaker_remote "
3354 "start' on 'nodeA', 'nodeB' was skipped because the command "
3355 "does not run on a live cluster. Please, "
3356 "run the action(s) manually.",
3357 reports.ServiceCommandsOnNodesSkipped(
3358 const.REASON_NOT_LIVE_CIB,
3359 ["pacemaker_remote enable", "pacemaker_remote start"],
3360 ["nodeA", "nodeB"],
3361 ),
3362 )
3363
3364 def test_unreachable(self):
3365 self.assert_message_from_report(
3366 "Running action(s) 'pacemaker_remote enable', 'pacemaker_remote "
3367 "start' on 'nodeA', 'nodeB' was skipped because pcs is unable "
3368 "to connect to the node(s). Please, run the action(s) manually.",
3369 reports.ServiceCommandsOnNodesSkipped(
3370 const.REASON_UNREACHABLE,
3371 ["pacemaker_remote enable", "pacemaker_remote start"],
3372 ["nodeA", "nodeB"],
3373 ),
3374 )
3375
3376 def test_unknown_reason(self):
3377 self.assert_message_from_report(
3378 "Running action(s) 'pacemaker_remote enable', 'pacemaker_remote "
3379 "start' on 'nodeA', 'nodeB' was skipped because some undefined "
3380 "reason. Please, run the action(s) manually.",
3381 reports.ServiceCommandsOnNodesSkipped(
3382 "some undefined reason",
3383 ["pacemaker_remote enable", "pacemaker_remote start"],
3384 ["nodeA", "nodeB"],
3385 ),
3386 )
3387
3388
3389 class ServiceCommandOnNodeSuccess(NameBuildTest):
3390 def test_build_messages(self):
3391 self.assert_message_from_report(
3392 "node1: successful run of 'service enable'",
3393 reports.ServiceCommandOnNodeSuccess("node1", "service enable"),
3394 )
3395
3396
3397 class ServiceCommandOnNodeError(NameBuildTest):
3398 def test_build_messages(self):
3399 self.assert_message_from_report(
3400 "node1: service command failed: service1 start: permission denied",
3401 reports.ServiceCommandOnNodeError(
3402 "node1", "service1 start", "permission denied"
3403 ),
3404 )
3405
3406
3407 class InvalidResponseFormat(NameBuildTest):
3408 def test_all(self):
3409 self.assert_message_from_report(
3410 "node1: Invalid format of response",
3411 reports.InvalidResponseFormat("node1"),
3412 )
3413
3414
3415 class SbdNotUsedCannotSetSbdOptions(NameBuildTest):
3416 def test_single_option(self):
3417 self.assert_message_from_report(
3418 "Cluster is not configured to use SBD, cannot specify SBD option(s)"
3419 " 'device' for node 'node1'",
3420 reports.SbdNotUsedCannotSetSbdOptions(["device"], "node1"),
3421 )
3422
3423 def test_multiple_options(self):
3424 self.assert_message_from_report(
3425 "Cluster is not configured to use SBD, cannot specify SBD option(s)"
3426 " 'device', 'watchdog' for node 'node1'",
3427 reports.SbdNotUsedCannotSetSbdOptions(
3428 ["device", "watchdog"], "node1"
3429 ),
3430 )
3431
3432
3433 class SbdWithDevicesNotUsedCannotSetDevice(NameBuildTest):
3434 def test_success(self):
3435 self.assert_message_from_report(
3436 "Cluster is not configured to use SBD with shared storage, cannot "
3437 "specify SBD devices for node 'node1'",
3438 reports.SbdWithDevicesNotUsedCannotSetDevice("node1"),
3439 )
3440
3441
3442 class SbdNoDeviceForNode(NameBuildTest):
3443 def test_not_enabled(self):
3444 self.assert_message_from_report(
3445 "No SBD device specified for node 'node1'",
3446 reports.SbdNoDeviceForNode("node1"),
3447 )
3448
3449 def test_enabled(self):
3450 self.assert_message_from_report(
3451 "Cluster uses SBD with shared storage so SBD devices must be "
3452 "specified for all nodes, no device specified for node 'node1'",
3453 reports.SbdNoDeviceForNode("node1", sbd_enabled_in_cluster=True),
3454 )
3455
3456
3457 class SbdTooManyDevicesForNode(NameBuildTest):
3458 def test_build_messages(self):
3459 self.assert_message_from_report(
3460 "At most 3 SBD devices can be specified for a node, '/dev1', "
3461 "'/dev2', '/dev3' specified for node 'node1'",
3462 reports.SbdTooManyDevicesForNode(
3463 "node1", ["/dev1", "/dev3", "/dev2"], 3
3464 ),
3465 )
3466
3467
3468 class SbdDevicePathNotAbsolute(NameBuildTest):
3469 def test_build_message(self):
3470 self.assert_message_from_report(
3471 "Device path '/dev' on node 'node1' is not absolute",
3472 reports.SbdDevicePathNotAbsolute("/dev", "node1"),
3473 )
3474
3475
3476 class SbdDeviceDoesNotExist(NameBuildTest):
3477 def test_build_message(self):
3478 self.assert_message_from_report(
3479 "node1: device '/dev' not found",
3480 reports.SbdDeviceDoesNotExist("/dev", "node1"),
3481 )
3482
3483
3484 class SbdDeviceIsNotBlockDevice(NameBuildTest):
3485 def test_build_message(self):
3486 self.assert_message_from_report(
3487 "node1: device '/dev' is not a block device",
3488 reports.SbdDeviceIsNotBlockDevice("/dev", "node1"),
3489 )
3490
3491
3492 class StonithWatchdogTimeoutCannotBeSet(NameBuildTest):
3493 def test_sbd_not_enabled(self):
3494 self.assert_message_from_report(
3495 "fencing-watchdog-timeout / stonith-watchdog-timeout can only be "
3496 "unset or set to 0 while SBD is disabled",
3497 reports.StonithWatchdogTimeoutCannotBeSet(
3498 reports.const.SBD_NOT_SET_UP
3499 ),
3500 )
3501
3502 def test_sbd_with_devices(self):
3503 self.assert_message_from_report(
3504 "fencing-watchdog-timeout / stonith-watchdog-timeout can only be "
3505 "unset or set to 0 while SBD is enabled with devices",
3506 reports.StonithWatchdogTimeoutCannotBeSet(
3507 reports.const.SBD_SET_UP_WITH_DEVICES
3508 ),
3509 )
3510
3511
3512 class StonithWatchdogTimeoutCannotBeUnset(NameBuildTest):
3513 def test_sbd_without_devices(self):
3514 self.assert_message_from_report(
3515 "fencing-watchdog-timeout / stonith-watchdog-timeout cannot be "
3516 "unset or set to 0 while SBD is enabled without devices",
3517 reports.StonithWatchdogTimeoutCannotBeUnset(
3518 reports.const.SBD_SET_UP_WITHOUT_DEVICES
3519 ),
3520 )
3521
3522
3523 class StonithWatchdogTimeoutTooSmall(NameBuildTest):
3524 def test_all(self):
3525 self.assert_message_from_report(
3526 "The fencing-watchdog-timeout / stonith-watchdog-timeout must be "
3527 "greater than SBD watchdog timeout '5', entered '4'",
3528 reports.StonithWatchdogTimeoutTooSmall(5, "4"),
3529 )
3530
3531
3532 class WatchdogNotFound(NameBuildTest):
3533 def test_all(self):
3534 self.assert_message_from_report(
3535 "Watchdog 'watchdog-name' does not exist on node 'node1'",
3536 reports.WatchdogNotFound("node1", "watchdog-name"),
3537 )
3538
3539
3540 class WatchdogInvalid(NameBuildTest):
3541 def test_all(self):
3542 self.assert_message_from_report(
3543 "Watchdog path '/dev/wdog' is invalid.",
3544 reports.WatchdogInvalid("/dev/wdog"),
3545 )
3546
3547
3548 class UnableToGetSbdStatus(NameBuildTest):
3549 def test_no_reason(self):
3550 self.assert_message_from_report(
3551 "Unable to get status of SBD from node 'node1'",
3552 reports.UnableToGetSbdStatus("node1", ""),
3553 )
3554
3555 def test_all(self):
3556 self.assert_message_from_report(
3557 "Unable to get status of SBD from node 'node2': reason",
3558 reports.UnableToGetSbdStatus("node2", "reason"),
3559 )
3560
3561
3562 class ClusterRestartRequiredToApplyChanges(NameBuildTest):
3563 def test_all(self):
3564 self.assert_message_from_report(
3565 "Cluster restart is required in order to apply these changes.",
3566 reports.ClusterRestartRequiredToApplyChanges(),
3567 )
3568
3569
3570 class CibAlertRecipientAlreadyExists(NameBuildTest):
3571 def test_all(self):
3572 self.assert_message_from_report(
3573 "Recipient 'recipient' in alert 'alert-id' already exists",
3574 reports.CibAlertRecipientAlreadyExists("alert-id", "recipient"),
3575 )
3576
3577
3578 class CibAlertRecipientValueInvalid(NameBuildTest):
3579 def test_all(self):
3580 self.assert_message_from_report(
3581 "Recipient value 'recipient' is not valid.",
3582 reports.CibAlertRecipientValueInvalid("recipient"),
3583 )
3584
3585
3586 class CibUpgradeSuccessful(NameBuildTest):
3587 def test_all(self):
3588 self.assert_message_from_report(
3589 "CIB has been upgraded to the latest schema version.",
3590 reports.CibUpgradeSuccessful(),
3591 )
3592
3593
3594 class CibUpgradeFailed(NameBuildTest):
3595 def test_all(self):
3596 self.assert_message_from_report(
3597 "Upgrading of CIB to the latest schema failed: reason",
3598 reports.CibUpgradeFailed("reason"),
3599 )
3600
3601
3602 class CibUpgradeFailedToMinimalRequiredVersion(NameBuildTest):
3603 def test_all(self):
3604 self.assert_message_from_report(
3605 (
3606 "Unable to upgrade CIB to required schema version"
3607 " 1.1 or higher. Current version is"
3608 " 0.8. Newer version of pacemaker is needed."
3609 ),
3610 reports.CibUpgradeFailedToMinimalRequiredVersion("0.8", "1.1"),
3611 )
3612
3613
3614 class FileAlreadyExists(NameBuildTest):
3615 def test_minimal(self):
3616 self.assert_message_from_report(
3617 "Corosync authkey file '/corosync_conf/path' already exists",
3618 reports.FileAlreadyExists(
3619 "COROSYNC_AUTHKEY", "/corosync_conf/path"
3620 ),
3621 )
3622
3623 def test_with_node(self):
3624 self.assert_message_from_report(
3625 "node1: pcs configuration file '/pcs/conf/file' already exists",
3626 reports.FileAlreadyExists(
3627 "PCS_SETTINGS_CONF", "/pcs/conf/file", node="node1"
3628 ),
3629 )
3630
3631
3632 class FileIoError(NameBuildTest):
3633 def test_minimal(self):
3634 self.assert_message_from_report(
3635 "Unable to read Booth configuration: ",
3636 reports.FileIoError(
3637 file_type_codes.BOOTH_CONFIG, RawFileError.ACTION_READ, ""
3638 ),
3639 )
3640
3641 def test_all(self):
3642 self.assert_message_from_report(
3643 "Unable to read pcsd SSL certificate '/ssl/cert/path': Failed",
3644 reports.FileIoError(
3645 file_type_codes.PCSD_SSL_CERT,
3646 RawFileError.ACTION_READ,
3647 "Failed",
3648 file_path="/ssl/cert/path",
3649 ),
3650 )
3651
3652 def test_role_translation_a(self):
3653 self.assert_message_from_report(
3654 "Unable to write pcsd SSL key '/ssl/key/path': Failed",
3655 reports.FileIoError(
3656 file_type_codes.PCSD_SSL_KEY,
3657 RawFileError.ACTION_WRITE,
3658 "Failed",
3659 file_path="/ssl/key/path",
3660 ),
3661 )
3662
3663 def test_role_translation_b(self):
3664 self.assert_message_from_report(
3665 (
3666 "Unable to change ownership of pcsd configuration "
3667 "'/pcsd/conf/path': Failed"
3668 ),
3669 reports.FileIoError(
3670 file_type_codes.PCSD_ENVIRONMENT_CONFIG,
3671 RawFileError.ACTION_CHOWN,
3672 "Failed",
3673 file_path="/pcsd/conf/path",
3674 ),
3675 )
3676
3677 def test_role_translation_c(self):
3678 self.assert_message_from_report(
3679 "Unable to change permissions of Corosync authkey: Failed",
3680 reports.FileIoError(
3681 file_type_codes.COROSYNC_AUTHKEY,
3682 RawFileError.ACTION_CHMOD,
3683 "Failed",
3684 ),
3685 )
3686
3687 def test_role_translation_d(self):
3688 self.assert_message_from_report(
3689 (
3690 "Unable to change ownership of pcs configuration: "
3691 "Permission denied"
3692 ),
3693 reports.FileIoError(
3694 file_type_codes.PCS_SETTINGS_CONF,
3695 RawFileError.ACTION_CHOWN,
3696 "Permission denied",
3697 ),
3698 )
3699
3700
3701 class UnsupportedOperationOnNonSystemdSystems(NameBuildTest):
3702 def test_all(self):
3703 self.assert_message_from_report(
3704 "unsupported operation on non systemd systems",
3705 reports.UnsupportedOperationOnNonSystemdSystems(),
3706 )
3707
3708
3709 class LiveEnvironmentRequired(NameBuildTest):
3710 def test_build_messages_transformable_codes(self):
3711 self.assert_message_from_report(
3712 "This command does not support passing '{}', '{}'".format(
3713 str(file_type_codes.CIB),
3714 str(file_type_codes.COROSYNC_CONF),
3715 ),
3716 reports.LiveEnvironmentRequired(
3717 [file_type_codes.COROSYNC_CONF, file_type_codes.CIB]
3718 ),
3719 )
3720
3721
3722 class LiveEnvironmentRequiredForLocalNode(NameBuildTest):
3723 def test_all(self):
3724 self.assert_message_from_report(
3725 "Node(s) must be specified if mocked CIB is used",
3726 reports.LiveEnvironmentRequiredForLocalNode(),
3727 )
3728
3729
3730 class LiveEnvironmentNotConsistent(NameBuildTest):
3731 def test_one_one(self):
3732 self.assert_message_from_report(
3733 "When '{}' is specified, '{}' must be specified as well".format(
3734 str(file_type_codes.BOOTH_CONFIG),
3735 str(file_type_codes.BOOTH_KEY),
3736 ),
3737 reports.LiveEnvironmentNotConsistent(
3738 [file_type_codes.BOOTH_CONFIG],
3739 [file_type_codes.BOOTH_KEY],
3740 ),
3741 )
3742
3743 def test_many_many(self):
3744 self.assert_message_from_report(
3745 (
3746 "When '{}', '{}' are specified, '{}', '{}' must be specified "
3747 "as well"
3748 ).format(
3749 str(file_type_codes.BOOTH_CONFIG),
3750 str(file_type_codes.CIB),
3751 str(file_type_codes.BOOTH_KEY),
3752 str(file_type_codes.COROSYNC_CONF),
3753 ),
3754 reports.LiveEnvironmentNotConsistent(
3755 [file_type_codes.CIB, file_type_codes.BOOTH_CONFIG],
3756 [file_type_codes.COROSYNC_CONF, file_type_codes.BOOTH_KEY],
3757 ),
3758 )
3759
3760
3761 class CorosyncNodeConflictCheckSkipped(NameBuildTest):
3762 def test_success(self):
3763 self.assert_message_from_report(
3764 "Unable to check if there is a conflict with nodes set in corosync "
3765 "because the command does not run on a live cluster",
3766 reports.CorosyncNodeConflictCheckSkipped(const.REASON_NOT_LIVE_CIB),
3767 )
3768
3769
3770 class CorosyncQuorumAtbCannotBeDisabledDueToSbd(NameBuildTest):
3771 def test_success(self):
3772 self.assert_message_from_report(
3773 (
3774 "Unable to disable auto_tie_breaker, SBD fencing would have no "
3775 "effect"
3776 ),
3777 reports.CorosyncQuorumAtbCannotBeDisabledDueToSbd(),
3778 )
3779
3780
3781 class CorosyncQuorumAtbWillBeEnabledDueToSbd(NameBuildTest):
3782 def test_success(self):
3783 self.assert_message_from_report(
3784 (
3785 "SBD fencing is enabled in the cluster. To keep it effective, "
3786 "auto_tie_breaker quorum option will be enabled."
3787 ),
3788 reports.CorosyncQuorumAtbWillBeEnabledDueToSbd(),
3789 )
3790
3791
3792 class CorosyncQuorumAtbWillBeEnabledDueToSbdClusterIsRunning(NameBuildTest):
3793 def test_success(self):
3794 self.assert_message_from_report(
3795 (
3796 "SBD fencing is enabled in the cluster. To keep it effective, "
3797 "auto_tie_breaker quorum option needs to be enabled. This can "
3798 "only be done when the cluster is stopped. To proceed, stop the "
3799 "cluster, enable auto_tie_breaker, and start the cluster. Then, "
3800 "repeat the requested action."
3801 ),
3802 reports.CorosyncQuorumAtbWillBeEnabledDueToSbdClusterIsRunning(),
3803 )
3804
3805
3806 class CibAclRoleIsAlreadyAssignedToTarget(NameBuildTest):
3807 def test_all(self):
3808 self.assert_message_from_report(
3809 "Role 'role_id' is already assigned to 'target_id'",
3810 reports.CibAclRoleIsAlreadyAssignedToTarget("role_id", "target_id"),
3811 )
3812
3813
3814 class CibAclRoleIsNotAssignedToTarget(NameBuildTest):
3815 def test_all(self):
3816 self.assert_message_from_report(
3817 "Role 'role_id' is not assigned to 'target_id'",
3818 reports.CibAclRoleIsNotAssignedToTarget("role_id", "target_id"),
3819 )
3820
3821
3822 class CibAclTargetAlreadyExists(NameBuildTest):
3823 def test_all(self):
3824 self.assert_message_from_report(
3825 "'target_id' already exists",
3826 reports.CibAclTargetAlreadyExists("target_id"),
3827 )
3828
3829
3830 class CibFencingLevelAlreadyExists(NameBuildTest):
3831 def test_target_node(self):
3832 self.assert_message_from_report(
3833 "Fencing level for 'nodeA' at level '1' with device(s) "
3834 "'device1', 'device2' already exists",
3835 reports.CibFencingLevelAlreadyExists(
3836 "1", TARGET_TYPE_NODE, "nodeA", ["device2", "device1"]
3837 ),
3838 )
3839
3840 def test_target_pattern(self):
3841 self.assert_message_from_report(
3842 "Fencing level for 'node-\\d+' at level '1' with device(s) "
3843 "'device1', 'device2' already exists",
3844 reports.CibFencingLevelAlreadyExists(
3845 "1", TARGET_TYPE_REGEXP, "node-\\d+", ["device1", "device2"]
3846 ),
3847 )
3848
3849 def test_target_attribute(self):
3850 self.assert_message_from_report(
3851 "Fencing level for 'name=value' at level '1' with device(s) "
3852 "'device2' already exists",
3853 reports.CibFencingLevelAlreadyExists(
3854 "1", TARGET_TYPE_ATTRIBUTE, ("name", "value"), ["device2"]
3855 ),
3856 )
3857
3858
3859 class CibFencingLevelDoesNotExist(NameBuildTest):
3860 def test_full_info(self):
3861 self.assert_message_from_report(
3862 "Fencing level for 'nodeA' at level '1' with device(s) "
3863 "'device1', 'device2' does not exist",
3864 reports.CibFencingLevelDoesNotExist(
3865 "1", TARGET_TYPE_NODE, "nodeA", ["device2", "device1"]
3866 ),
3867 )
3868
3869 def test_only_level(self):
3870 self.assert_message_from_report(
3871 "Fencing level at level '1' does not exist",
3872 reports.CibFencingLevelDoesNotExist("1"),
3873 )
3874
3875 def test_only_target(self):
3876 self.assert_message_from_report(
3877 "Fencing level for 'name=value' does not exist",
3878 reports.CibFencingLevelDoesNotExist(
3879 target_type=TARGET_TYPE_ATTRIBUTE,
3880 target_value=("name", "value"),
3881 ),
3882 )
3883
3884 def test_only_devices(self):
3885 self.assert_message_from_report(
3886 "Fencing level with device(s) 'device1' does not exist",
3887 reports.CibFencingLevelDoesNotExist(devices=["device1"]),
3888 )
3889
3890 def test_no_info(self):
3891 self.assert_message_from_report(
3892 "Fencing level does not exist",
3893 reports.CibFencingLevelDoesNotExist(),
3894 )
3895
3896
3897 class CibRemoveResources(NameBuildTest):
3898 def test_single_id(self):
3899 self.assert_message_from_report(
3900 "Removing resource: 'id1'", reports.CibRemoveResources(["id1"])
3901 )
3902
3903 def test_multiple_ids(self):
3904 self.assert_message_from_report(
3905 "Removing resources: 'id1', 'id2', 'id3'",
3906 reports.CibRemoveResources(["id1", "id2", "id3"]),
3907 )
3908
3909
3910 class CibRemoveDependantElements(NameBuildTest):
3911 def test_single_element_type_with_single_id(self):
3912 self.assert_message_from_report(
3913 "Removing dependant element:\n Location constraint: 'id1'",
3914 reports.CibRemoveDependantElements({"id1": "rsc_location"}),
3915 )
3916
3917 def test_single_element_type_with_multiple_ids(self):
3918 self.assert_message_from_report(
3919 (
3920 "Removing dependant elements:\n"
3921 " Location constraints: 'id1', 'id2'"
3922 ),
3923 reports.CibRemoveDependantElements(
3924 {"id1": "rsc_location", "id2": "rsc_location"}
3925 ),
3926 )
3927
3928 def test_multiple_element_types_with_single_id(self):
3929 self.assert_message_from_report(
3930 (
3931 "Removing dependant elements:\n"
3932 " Clone: 'id2'\n"
3933 " Location constraint: 'id1'"
3934 ),
3935 reports.CibRemoveDependantElements(
3936 {"id1": "rsc_location", "id2": "clone"}
3937 ),
3938 )
3939
3940 def test_multiple_element_types_with_multiple_ids(self):
3941 self.assert_message_from_report(
3942 (
3943 "Removing dependant elements:\n"
3944 " Another_elements: 'id5', 'id6'\n"
3945 " Clones: 'id3', 'id4'\n"
3946 " Location constraints: 'id1', 'id2'"
3947 ),
3948 reports.CibRemoveDependantElements(
3949 {
3950 "id1": "rsc_location",
3951 "id2": "rsc_location",
3952 "id3": "clone",
3953 "id4": "clone",
3954 "id5": "another_element",
3955 "id6": "another_element",
3956 }
3957 ),
3958 )
3959
3960
3961 class CibRemoveReferences(NameBuildTest):
3962 def test_one_element_single_reference(self):
3963 self.assert_message_from_report(
3964 ("Removing references:\n Resource 'id1' from:\n Tag: 'id2'"),
3965 reports.CibRemoveReferences(
3966 {"id1": "primitive", "id2": "tag"}, {"id1": ["id2"]}
3967 ),
3968 )
3969
3970 def test_missing_tag_mapping(self):
3971 self.assert_message_from_report(
3972 ("Removing references:\n Element 'id1' from:\n Element: 'id2'"),
3973 reports.CibRemoveReferences({}, {"id1": ["id2"]}),
3974 )
3975
3976 def test_one_element_multiple_references_same_type(self):
3977 self.assert_message_from_report(
3978 (
3979 "Removing references:\n"
3980 " Resource 'id1' from:\n"
3981 " Tags: 'id2', 'id3'"
3982 ),
3983 reports.CibRemoveReferences(
3984 {"id1": "primitive", "id2": "tag", "id3": "tag"},
3985 {"id1": ["id2", "id3"]},
3986 ),
3987 )
3988
3989 def test_one_element_multiple_references_multiple_types(self):
3990 self.assert_message_from_report(
3991 (
3992 "Removing references:\n"
3993 " Resource 'id1' from:\n"
3994 " Group: 'id3'\n"
3995 " Tag: 'id2'"
3996 ),
3997 reports.CibRemoveReferences(
3998 {"id1": "primitive", "id2": "tag", "id3": "group"},
3999 {"id1": ["id2", "id3"]},
4000 ),
4001 )
4002
4003 def test_multiple_elements_single_reference(self):
4004 self.assert_message_from_report(
4005 (
4006 "Removing references:\n"
4007 " Resource 'id1' from:\n"
4008 " Tag: 'id2'\n"
4009 " Resource 'id3' from:\n"
4010 " Tag: 'id4'"
4011 ),
4012 reports.CibRemoveReferences(
4013 {
4014 "id1": "primitive",
4015 "id2": "tag",
4016 "id3": "primitive",
4017 "id4": "tag",
4018 },
4019 {"id1": ["id2"], "id3": ["id4"]},
4020 ),
4021 )
4022
4023
4024 class UseCommandNodeAddRemote(NameBuildTest):
4025 def test_build_messages(self):
4026 self.assert_message_from_report(
4027 "this command is not sufficient for creating a remote connection",
4028 reports.UseCommandNodeAddRemote(),
4029 )
4030
4031
4032 class UseCommandNodeAddGuest(NameBuildTest):
4033 def test_build_messages(self):
4034 self.assert_message_from_report(
4035 "this command is not sufficient for creating a guest node",
4036 reports.UseCommandNodeAddGuest(),
4037 )
4038
4039
4040 class UseCommandNodeRemoveRemote(NameBuildTest):
4041 def test_build_messages(self):
4042 self.assert_message_from_report(
4043 "this command is not sufficient for removing a remote node",
4044 reports.UseCommandNodeRemoveRemote(),
4045 )
4046
4047
4048 class UseCommandNodeRemoveGuest(NameBuildTest):
4049 def test_build_messages(self):
4050 self.assert_message_from_report(
4051 "this command is not sufficient for removing a guest node",
4052 reports.UseCommandNodeRemoveGuest(),
4053 )
4054
4055
4056 class UseCommandRemoveAndAddGuestNode(NameBuildTest):
4057 def test_message(self):
4058 self.assert_message_from_report(
4059 "Changing connection parameters of an existing guest node is not "
4060 "sufficient for connecting to a different guest node, remove the "
4061 "existing guest node and add a new one instead",
4062 reports.UseCommandRemoveAndAddGuestNode(),
4063 )
4064
4065
4066 class GuestNodeNameAlreadyExists(NameBuildTest):
4067 def test_message(self):
4068 self.assert_message_from_report(
4069 "Cannot set name of the guest node to 'N' because that ID already "
4070 "exists in the cluster configuration.",
4071 reports.GuestNodeNameAlreadyExists("N"),
4072 )
4073
4074
4075 class TmpFileWrite(NameBuildTest):
4076 def test_success(self):
4077 self.assert_message_from_report(
4078 (
4079 "Writing to a temporary file /tmp/pcs/test.tmp:\n"
4080 "--Debug Content Start--\n"
4081 "test file\ncontent\n\n"
4082 "--Debug Content End--\n"
4083 ),
4084 reports.TmpFileWrite("/tmp/pcs/test.tmp", "test file\ncontent\n"),
4085 )
4086
4087
4088 class NodeAddressesUnresolvable(NameBuildTest):
4089 def test_one_address(self):
4090 self.assert_message_from_report(
4091 "Unable to resolve addresses: 'node1'",
4092 reports.NodeAddressesUnresolvable(["node1"]),
4093 )
4094
4095 def test_more_address(self):
4096 self.assert_message_from_report(
4097 "Unable to resolve addresses: 'node1', 'node2', 'node3'",
4098 reports.NodeAddressesUnresolvable(["node2", "node1", "node3"]),
4099 )
4100
4101
4102 class UnableToPerformOperationOnAnyNode(NameBuildTest):
4103 def test_all(self):
4104 self.assert_message_from_report(
4105 (
4106 "Unable to perform operation on any available node/host, "
4107 "therefore it is not possible to continue"
4108 ),
4109 reports.UnableToPerformOperationOnAnyNode(),
4110 )
4111
4112
4113 class HostNotFound(NameBuildTest):
4114 def test_single_host(self):
4115 self.assert_message_from_report(
4116 "Host 'unknown_host' is not known to pcs",
4117 reports.HostNotFound(["unknown_host"]),
4118 )
4119
4120 def test_multiple_hosts(self):
4121 self.assert_message_from_report(
4122 "Hosts 'another_one', 'unknown_host' are not known to pcs",
4123 reports.HostNotFound(["unknown_host", "another_one"]),
4124 )
4125
4126
4127 class NoneHostFound(NameBuildTest):
4128 def test_all(self):
4129 self.assert_message_from_report(
4130 "None of hosts is known to pcs.", reports.NoneHostFound()
4131 )
4132
4133
4134 class HostAlreadyAuthorized(NameBuildTest):
4135 def test_success(self):
4136 self.assert_message_from_report(
4137 "host: Already authorized", reports.HostAlreadyAuthorized("host")
4138 )
4139
4140
4141 class AuthorizationSuccessful(NameBuildTest):
4142 def test_success(self):
4143 self.assert_message_from_report(
4144 "Authorized", reports.AuthorizationSuccessful()
4145 )
4146
4147
4148 class IncorrectCredentials(NameBuildTest):
4149 def test_success(self):
4150 self.assert_message_from_report(
4151 "Username and/or password is incorrect",
4152 reports.IncorrectCredentials(),
4153 )
4154
4155
4156 class NoHostSpecified(NameBuildTest):
4157 def test_success(self):
4158 self.assert_message_from_report(
4159 "No host specified", reports.NoHostSpecified()
4160 )
4161
4162
4163 class ClusterDestroyStarted(NameBuildTest):
4164 def test_multiple_hosts(self):
4165 self.assert_message_from_report(
4166 "Destroying cluster on hosts: 'node1', 'node2', 'node3'...",
4167 reports.ClusterDestroyStarted(["node1", "node3", "node2"]),
4168 )
4169
4170 def test_single_host(self):
4171 self.assert_message_from_report(
4172 "Destroying cluster on hosts: 'node1'...",
4173 reports.ClusterDestroyStarted(["node1"]),
4174 )
4175
4176
4177 class ClusterDestroySuccess(NameBuildTest):
4178 def test_success(self):
4179 self.assert_message_from_report(
4180 "node1: Successfully destroyed cluster",
4181 reports.ClusterDestroySuccess("node1"),
4182 )
4183
4184
4185 class ClusterEnableStarted(NameBuildTest):
4186 def test_multiple_hosts(self):
4187 self.assert_message_from_report(
4188 "Enabling cluster on hosts: 'node1', 'node2', 'node3'...",
4189 reports.ClusterEnableStarted(["node1", "node3", "node2"]),
4190 )
4191
4192 def test_single_host(self):
4193 self.assert_message_from_report(
4194 "Enabling cluster on hosts: 'node1'...",
4195 reports.ClusterEnableStarted(["node1"]),
4196 )
4197
4198
4199 class ClusterEnableSuccess(NameBuildTest):
4200 def test_success(self):
4201 self.assert_message_from_report(
4202 "node1: Cluster enabled", reports.ClusterEnableSuccess("node1")
4203 )
4204
4205
4206 class ClusterStartStarted(NameBuildTest):
4207 def test_multiple_hosts(self):
4208 self.assert_message_from_report(
4209 "Starting cluster on hosts: 'node1', 'node2', 'node3'...",
4210 reports.ClusterStartStarted(["node1", "node3", "node2"]),
4211 )
4212
4213 def test_single_host(self):
4214 self.assert_message_from_report(
4215 "Starting cluster on hosts: 'node1'...",
4216 reports.ClusterStartStarted(["node1"]),
4217 )
4218
4219
4220 class ClusterStartSuccess(NameBuildTest):
4221 def test_all(self):
4222 self.assert_message_from_report(
4223 "node1: Cluster started", reports.ClusterStartSuccess("node1")
4224 )
4225
4226
4227 class ServiceNotInstalled(NameBuildTest):
4228 def test_multiple_services(self):
4229 self.assert_message_from_report(
4230 "node1: Required cluster services not installed: 'service1', "
4231 "'service2', 'service3'",
4232 reports.ServiceNotInstalled(
4233 "node1", ["service1", "service3", "service2"]
4234 ),
4235 )
4236
4237 def test_single_service(self):
4238 self.assert_message_from_report(
4239 "node1: Required cluster services not installed: 'service'",
4240 reports.ServiceNotInstalled("node1", ["service"]),
4241 )
4242
4243
4244 class HostAlreadyInClusterConfig(NameBuildTest):
4245 def test_success(self):
4246 self.assert_message_from_report(
4247 "host: The host seems to be in a cluster already as cluster "
4248 "configuration files have been found on the host",
4249 reports.HostAlreadyInClusterConfig("host"),
4250 )
4251
4252
4253 class HostAlreadyInClusterServices(NameBuildTest):
4254 def test_multiple_services(self):
4255 self.assert_message_from_report(
4256 "node1: The host seems to be in a cluster already as the following "
4257 "services are found to be running: 'service1', 'service2', "
4258 "'service3'. If the host is not part of a cluster, stop the "
4259 "services and retry",
4260 reports.HostAlreadyInClusterServices(
4261 "node1", ["service1", "service3", "service2"]
4262 ),
4263 )
4264
4265 def test_single_service(self):
4266 self.assert_message_from_report(
4267 "node1: The host seems to be in a cluster already as the following "
4268 "service is found to be running: 'service'. If the host is not "
4269 "part of a cluster, stop the service and retry",
4270 reports.HostAlreadyInClusterServices("node1", ["service"]),
4271 )
4272
4273
4274 class ServiceVersionMismatch(NameBuildTest):
4275 def test_success(self):
4276 self.assert_message_from_report(
4277 "Hosts do not have the same version of 'service'; "
4278 "hosts 'host4', 'host5', 'host6' have version 2.0; "
4279 "hosts 'host1', 'host3' have version 1.0; "
4280 "host 'host2' has version 1.2",
4281 reports.ServiceVersionMismatch(
4282 "service",
4283 {
4284 "host1": "1.0",
4285 "host2": "1.2",
4286 "host3": "1.0",
4287 "host4": "2.0",
4288 "host5": "2.0",
4289 "host6": "2.0",
4290 },
4291 ),
4292 )
4293
4294
4295 class WaitForNodeStartupStarted(NameBuildTest):
4296 def test_single_node(self):
4297 self.assert_message_from_report(
4298 "Waiting for node(s) to start: 'node1'...",
4299 reports.WaitForNodeStartupStarted(["node1"]),
4300 )
4301
4302 def test_multiple_nodes(self):
4303 self.assert_message_from_report(
4304 "Waiting for node(s) to start: 'node1', 'node2', 'node3'...",
4305 reports.WaitForNodeStartupStarted(["node3", "node2", "node1"]),
4306 )
4307
4308
4309 class WaitForNodeStartupTimedOut(NameBuildTest):
4310 def test_all(self):
4311 self.assert_message_from_report(
4312 "Node(s) startup timed out", reports.WaitForNodeStartupTimedOut()
4313 )
4314
4315
4316 class WaitForNodeStartupError(NameBuildTest):
4317 def test_all(self):
4318 self.assert_message_from_report(
4319 "Unable to verify all nodes have started",
4320 reports.WaitForNodeStartupError(),
4321 )
4322
4323
4324 class WaitForNodeStartupWithoutStart(NameBuildTest):
4325 def test_all(self):
4326 self.assert_message_from_report(
4327 "Cannot specify 'wait' without specifying 'start'",
4328 reports.WaitForNodeStartupWithoutStart(),
4329 )
4330
4331
4332 class PcsdVersionTooOld(NameBuildTest):
4333 def test_success(self):
4334 self.assert_message_from_report(
4335 (
4336 "node1: Old version of pcsd is running on the node, therefore "
4337 "it is unable to perform the action"
4338 ),
4339 reports.PcsdVersionTooOld("node1"),
4340 )
4341
4342
4343 class PcsdSslCertAndKeyDistributionStarted(NameBuildTest):
4344 def test_multiple_nodes(self):
4345 self.assert_message_from_report(
4346 "Synchronizing pcsd SSL certificates on node(s) 'node1', 'node2', "
4347 "'node3'...",
4348 reports.PcsdSslCertAndKeyDistributionStarted(
4349 ["node1", "node3", "node2"]
4350 ),
4351 )
4352
4353 def test_single_node(self):
4354 self.assert_message_from_report(
4355 "Synchronizing pcsd SSL certificates on node(s) 'node3'...",
4356 reports.PcsdSslCertAndKeyDistributionStarted(["node3"]),
4357 )
4358
4359
4360 class PcsdSslCertAndKeySetSuccess(NameBuildTest):
4361 def test_success(self):
4362 self.assert_message_from_report(
4363 "node1: Success", reports.PcsdSslCertAndKeySetSuccess("node1")
4364 )
4365
4366
4367 class ClusterWillBeDestroyed(NameBuildTest):
4368 def test_all(self):
4369 self.assert_message_from_report(
4370 (
4371 "Some nodes are already in a cluster. Enforcing this will "
4372 "destroy existing cluster on those nodes. You should remove "
4373 "the nodes from their clusters instead to keep the clusters "
4374 "working properly"
4375 ),
4376 reports.ClusterWillBeDestroyed(),
4377 )
4378
4379
4380 class ClusterSetupSuccess(NameBuildTest):
4381 def test_all(self):
4382 self.assert_message_from_report(
4383 "Cluster has been successfully set up.",
4384 reports.ClusterSetupSuccess(),
4385 )
4386
4387
4388 class UsingDefaultAddressForHost(NameBuildTest):
4389 def test_success(self):
4390 self.assert_message_from_report(
4391 "No addresses specified for host 'node-name', using 'node-addr'",
4392 reports.UsingDefaultAddressForHost(
4393 "node-name",
4394 "node-addr",
4395 const.DEFAULT_ADDRESS_SOURCE_KNOWN_HOSTS,
4396 ),
4397 )
4398
4399
4400 class ResourceInBundleNotAccessible(NameBuildTest):
4401 def test_success(self):
4402 self.assert_message_from_report(
4403 (
4404 "Resource 'resourceA' will not be accessible by the cluster "
4405 "inside bundle 'bundleA', at least one of bundle options "
4406 "'control-port' or 'ip-range-start' has to be specified"
4407 ),
4408 reports.ResourceInBundleNotAccessible("bundleA", "resourceA"),
4409 )
4410
4411
4412 class UsingDefaultWatchdog(NameBuildTest):
4413 def test_success(self):
4414 self.assert_message_from_report(
4415 (
4416 "No watchdog has been specified for node 'node1'. Using "
4417 "default watchdog '/dev/watchdog'"
4418 ),
4419 reports.UsingDefaultWatchdog("/dev/watchdog", "node1"),
4420 )
4421
4422
4423 class CannotRemoveAllClusterNodes(NameBuildTest):
4424 def test_success(self):
4425 self.assert_message_from_report(
4426 "No nodes would be left in the cluster",
4427 reports.CannotRemoveAllClusterNodes(),
4428 )
4429
4430
4431 class UnableToConnectToAnyRemainingNode(NameBuildTest):
4432 def test_all(self):
4433 self.assert_message_from_report(
4434 "Unable to connect to any remaining cluster node",
4435 reports.UnableToConnectToAnyRemainingNode(),
4436 )
4437
4438
4439 class UnableToConnectToAllRemainingNodes(NameBuildTest):
4440 def test_single_node(self):
4441 self.assert_message_from_report(
4442 ("Remaining cluster node 'node1' could not be reached"),
4443 reports.UnableToConnectToAllRemainingNodes(["node1"]),
4444 )
4445
4446 def test_multiple_nodes(self):
4447 self.assert_message_from_report(
4448 (
4449 "Remaining cluster nodes 'node0', 'node1', 'node2' could not "
4450 "be reached"
4451 ),
4452 reports.UnableToConnectToAllRemainingNodes(
4453 ["node1", "node0", "node2"]
4454 ),
4455 )
4456
4457
4458 class NodesToRemoveUnreachable(NameBuildTest):
4459 def test_single_node(self):
4460 self.assert_message_from_report(
4461 (
4462 "Removed node 'node0' could not be reached and subsequently "
4463 "deconfigured"
4464 ),
4465 reports.NodesToRemoveUnreachable(["node0"]),
4466 )
4467
4468 def test_multiple_nodes(self):
4469 self.assert_message_from_report(
4470 (
4471 "Removed nodes 'node0', 'node1', 'node2' could not be reached "
4472 "and subsequently deconfigured"
4473 ),
4474 reports.NodesToRemoveUnreachable(["node1", "node0", "node2"]),
4475 )
4476
4477
4478 class NodeUsedAsTieBreaker(NameBuildTest):
4479 def test_success(self):
4480 self.assert_message_from_report(
4481 (
4482 "Node 'node2' with id '2' is used as a tie breaker for a "
4483 "qdevice and therefore cannot be removed"
4484 ),
4485 reports.NodeUsedAsTieBreaker("node2", 2),
4486 )
4487
4488
4489 class CorosyncQuorumWillBeLost(NameBuildTest):
4490 def test_all(self):
4491 self.assert_message_from_report(
4492 "This action will cause a loss of the quorum",
4493 reports.CorosyncQuorumWillBeLost(),
4494 )
4495
4496
4497 class CorosyncQuorumLossUnableToCheck(NameBuildTest):
4498 def test_all(self):
4499 self.assert_message_from_report(
4500 (
4501 "Unable to determine whether this action will cause "
4502 "a loss of the quorum"
4503 ),
4504 reports.CorosyncQuorumLossUnableToCheck(),
4505 )
4506
4507
4508 class SbdListWatchdogError(NameBuildTest):
4509 def test_success(self):
4510 self.assert_message_from_report(
4511 "Unable to query available watchdogs from sbd: this is a reason",
4512 reports.SbdListWatchdogError("this is a reason"),
4513 )
4514
4515
4516 class SbdWatchdogNotSupported(NameBuildTest):
4517 def test_success(self):
4518 self.assert_message_from_report(
4519 (
4520 "node1: Watchdog '/dev/watchdog' is not supported (it may be a "
4521 "software watchdog)"
4522 ),
4523 reports.SbdWatchdogNotSupported("node1", "/dev/watchdog"),
4524 )
4525
4526
4527 class SbdWatchdogValidationInactive(NameBuildTest):
4528 def test_all(self):
4529 self.assert_message_from_report(
4530 "Not validating the watchdog",
4531 reports.SbdWatchdogValidationInactive(),
4532 )
4533
4534
4535 class SbdWatchdogTestError(NameBuildTest):
4536 def test_success(self):
4537 self.assert_message_from_report(
4538 "Unable to initialize test of the watchdog: some reason",
4539 reports.SbdWatchdogTestError("some reason"),
4540 )
4541
4542
4543 class SbdWatchdogTestMultipleDevices(NameBuildTest):
4544 def test_all(self):
4545 self.assert_message_from_report(
4546 (
4547 "Multiple watchdog devices available, therefore, watchdog "
4548 "which should be tested has to be specified."
4549 ),
4550 reports.SbdWatchdogTestMultipleDevices(),
4551 )
4552
4553
4554 class SbdWatchdogTestFailed(NameBuildTest):
4555 def test_all(self):
4556 self.assert_message_from_report(
4557 "System should have been reset already",
4558 reports.SbdWatchdogTestFailed(),
4559 )
4560
4561
4562 class SystemWillReset(NameBuildTest):
4563 def test_all(self):
4564 self.assert_message_from_report(
4565 "System will reset shortly", reports.SystemWillReset()
4566 )
4567
4568
4569 class ResourceBundleUnsupportedContainerType(NameBuildTest):
4570 def test_single_type(self):
4571 self.assert_message_from_report(
4572 (
4573 "Bundle 'bundle id' uses unsupported container type, therefore "
4574 "it is not possible to set its container options. Supported "
4575 "container types are: 'b'"
4576 ),
4577 reports.ResourceBundleUnsupportedContainerType("bundle id", ["b"]),
4578 )
4579
4580 def test_multiple_types(self):
4581 self.assert_message_from_report(
4582 (
4583 "Bundle 'bundle id' uses unsupported container type, therefore "
4584 "it is not possible to set its container options. Supported "
4585 "container types are: 'a', 'b', 'c'"
4586 ),
4587 reports.ResourceBundleUnsupportedContainerType(
4588 "bundle id", ["b", "a", "c"]
4589 ),
4590 )
4591
4592 def test_no_update(self):
4593 self.assert_message_from_report(
4594 (
4595 "Bundle 'bundle id' uses unsupported container type. Supported "
4596 "container types are: 'a', 'b', 'c'"
4597 ),
4598 reports.ResourceBundleUnsupportedContainerType(
4599 "bundle id", ["b", "a", "c"], updating_options=False
4600 ),
4601 )
4602
4603
4604 class FenceHistoryCommandError(NameBuildTest):
4605 def test_success(self):
4606 self.assert_message_from_report(
4607 "Unable to show fence history: reason",
4608 reports.FenceHistoryCommandError(
4609 "reason", reports.const.FENCE_HISTORY_COMMAND_SHOW
4610 ),
4611 )
4612
4613
4614 class FenceHistoryNotSupported(NameBuildTest):
4615 def test_success(self):
4616 self.assert_message_from_report(
4617 "Fence history is not supported, please upgrade pacemaker",
4618 reports.FenceHistoryNotSupported(),
4619 )
4620
4621
4622 class ResourceInstanceAttrValueNotUnique(NameBuildTest):
4623 def test_one_resource(self):
4624 self.assert_message_from_report(
4625 (
4626 "Value 'val' of option 'attr' is not unique across 'agent' "
4627 "resources. Following resources are configured with the same "
4628 "value of the instance attribute: 'A'"
4629 ),
4630 reports.ResourceInstanceAttrValueNotUnique(
4631 "attr", "val", "agent", ["A"]
4632 ),
4633 )
4634
4635 def test_multiple_resources(self):
4636 self.assert_message_from_report(
4637 (
4638 "Value 'val' of option 'attr' is not unique across 'agent' "
4639 "resources. Following resources are configured with the same "
4640 "value of the instance attribute: 'A', 'B', 'C'"
4641 ),
4642 reports.ResourceInstanceAttrValueNotUnique(
4643 "attr", "val", "agent", ["B", "C", "A"]
4644 ),
4645 )
4646
4647
4648 class ResourceInstanceAttrGroupValueNotUnique(NameBuildTest):
4649 def test_message(self):
4650 self.assert_message_from_report(
4651 (
4652 "Value '127.0.0.1', '12345' of options 'ip', 'port' (group "
4653 "'address') is not unique across 'agent' resources. Following "
4654 "resources are configured with the same values of the instance "
4655 "attributes: 'A', 'B'"
4656 ),
4657 reports.ResourceInstanceAttrGroupValueNotUnique(
4658 "address",
4659 {
4660 "port": "12345",
4661 "ip": "127.0.0.1",
4662 },
4663 "agent",
4664 ["B", "A"],
4665 ),
4666 )
4667
4668
4669 class CannotLeaveGroupEmptyAfterMove(NameBuildTest):
4670 def test_single_resource(self):
4671 self.assert_message_from_report(
4672 "Unable to move resource 'R' as it would leave group 'gr1' empty.",
4673 reports.CannotLeaveGroupEmptyAfterMove("gr1", ["R"]),
4674 )
4675
4676 def test_multiple_resources(self):
4677 self.assert_message_from_report(
4678 "Unable to move resources 'R1', 'R2', 'R3' as it would leave "
4679 "group 'gr1' empty.",
4680 reports.CannotLeaveGroupEmptyAfterMove("gr1", ["R3", "R1", "R2"]),
4681 )
4682
4683
4684 class CannotMoveResourceBundleInner(NameBuildTest):
4685 def test_success(self):
4686 self.assert_message_from_report(
4687 (
4688 "Resources cannot be moved out of their bundles. If you want "
4689 "to move a bundle, use the bundle id (B)"
4690 ),
4691 reports.CannotMoveResourceBundleInner("R", "B"),
4692 )
4693
4694
4695 class CannotMoveResourceCloneInner(NameBuildTest):
4696 def test_success(self):
4697 self.assert_message_from_report(
4698 "to move clone resources you must use the clone id (C)",
4699 reports.CannotMoveResourceCloneInner("R", "C"),
4700 )
4701
4702
4703 class CannotMoveResourceMultipleInstances(NameBuildTest):
4704 def test_success(self):
4705 self.assert_message_from_report(
4706 (
4707 "more than one instance of resource 'R' is running, "
4708 "thus the resource cannot be moved"
4709 ),
4710 reports.CannotMoveResourceMultipleInstances("R"),
4711 )
4712
4713
4714 class CannotMoveResourceMultipleInstancesNoNodeSpecified(NameBuildTest):
4715 def test_success(self):
4716 self.assert_message_from_report(
4717 (
4718 "more than one instance of resource 'R' is running, "
4719 "thus the resource cannot be moved, "
4720 "unless a destination node is specified"
4721 ),
4722 reports.CannotMoveResourceMultipleInstancesNoNodeSpecified("R"),
4723 )
4724
4725
4726 class CannotMoveResourcePromotableInner(NameBuildTest):
4727 def test_success(self):
4728 self.assert_message_from_report(
4729 (
4730 "to move promotable clone resources you must use "
4731 "the promotable clone id (P)"
4732 ),
4733 reports.CannotMoveResourcePromotableInner("R", "P"),
4734 )
4735
4736
4737 class CannotMoveResourceMasterResourceNotPromotable(NameBuildTest):
4738 def test_without_promotable(self):
4739 self.assert_message_from_report(
4740 "when specifying promoted you must use the promotable clone id",
4741 reports.CannotMoveResourceMasterResourceNotPromotable("R"),
4742 )
4743
4744 def test_with_promotable(self):
4745 self.assert_message_from_report(
4746 "when specifying promoted you must use the promotable clone id (P)",
4747 reports.CannotMoveResourceMasterResourceNotPromotable(
4748 "R", promotable_id="P"
4749 ),
4750 )
4751
4752
4753 class CannotMoveResourceNotRunning(NameBuildTest):
4754 def test_success(self):
4755 self.assert_message_from_report(
4756 (
4757 "It is not possible to move resource 'R' as it is not running "
4758 "at the moment"
4759 ),
4760 reports.CannotMoveResourceNotRunning("R"),
4761 )
4762
4763
4764 class CannotMoveResourceStoppedNoNodeSpecified(NameBuildTest):
4765 def test_success(self):
4766 self.assert_message_from_report(
4767 "You must specify a node when moving/banning a stopped resource",
4768 reports.CannotMoveResourceStoppedNoNodeSpecified("R"),
4769 )
4770
4771
4772 class ResourceMovePcmkError(NameBuildTest):
4773 def test_success(self):
4774 self.assert_message_from_report(
4775 "cannot move resource 'R'\nstdout1\n stdout2\nstderr1\n stderr2",
4776 reports.ResourceMovePcmkError(
4777 "R", "stdout1\n\n stdout2\n", "stderr1\n\n stderr2\n"
4778 ),
4779 )
4780
4781
4782 class ResourceMovePcmkSuccess(NameBuildTest):
4783 def test_success(self):
4784 self.assert_message_from_report(
4785 "stdout1\n stdout2\nstderr1\n stderr2",
4786 reports.ResourceMovePcmkSuccess(
4787 "R", "stdout1\n\n stdout2\n", "stderr1\n\n stderr2\n"
4788 ),
4789 )
4790
4791 def test_translate(self):
4792 self.assert_message_from_report(
4793 (
4794 "Warning: Creating location constraint "
4795 "'cli-ban-dummy-on-node1' with a score of -INFINITY "
4796 "for resource dummy on node1.\n"
4797 " This will prevent dummy from running on node1 until the "
4798 "constraint is removed\n"
4799 " This will be the case even if node1 is the last node in "
4800 "the cluster"
4801 ),
4802 reports.ResourceMovePcmkSuccess(
4803 "dummy",
4804 "",
4805 (
4806 "WARNING: Creating rsc_location constraint "
4807 "'cli-ban-dummy-on-node1' with a score of -INFINITY "
4808 "for resource dummy on node1.\n"
4809 " This will prevent dummy from running on node1 until "
4810 "the constraint is removed using the clear option or "
4811 "by editing the CIB with an appropriate tool\n"
4812 " This will be the case even if node1 is the last node "
4813 "in the cluster\n"
4814 ),
4815 ),
4816 )
4817
4818
4819 class CannotBanResourceBundleInner(NameBuildTest):
4820 def test_success(self):
4821 self.assert_message_from_report(
4822 (
4823 "Resource 'R' is in a bundle and cannot be banned. If you want "
4824 "to ban the bundle, use the bundle id (B)"
4825 ),
4826 reports.CannotBanResourceBundleInner("R", "B"),
4827 )
4828
4829
4830 class CannotBanResourceMasterResourceNotPromotable(NameBuildTest):
4831 def test_without_promotable(self):
4832 self.assert_message_from_report(
4833 "when specifying promoted you must use the promotable clone id",
4834 reports.CannotBanResourceMasterResourceNotPromotable("R"),
4835 )
4836
4837 def test_with_promotable(self):
4838 self.assert_message_from_report(
4839 "when specifying promoted you must use the promotable clone id (P)",
4840 reports.CannotBanResourceMasterResourceNotPromotable(
4841 "R", promotable_id="P"
4842 ),
4843 )
4844
4845
4846 class CannotBanResourceMultipleInstancesNoNodeSpecified(NameBuildTest):
4847 def test_success(self):
4848 self.assert_message_from_report(
4849 (
4850 "more than one instance of resource 'R' is running, "
4851 "thus the resource cannot be banned, "
4852 "unless a destination node is specified"
4853 ),
4854 reports.CannotBanResourceMultipleInstancesNoNodeSpecified("R"),
4855 )
4856
4857
4858 class CannotBanResourceStoppedNoNodeSpecified(NameBuildTest):
4859 def test_success(self):
4860 self.assert_message_from_report(
4861 "You must specify a node when moving/banning a stopped resource",
4862 reports.CannotBanResourceStoppedNoNodeSpecified("R"),
4863 )
4864
4865
4866 class ResourceBanPcmkError(NameBuildTest):
4867 def test_success(self):
4868 self.assert_message_from_report(
4869 "cannot ban resource 'R'\nstdout1\n stdout2\nstderr1\n stderr2",
4870 reports.ResourceBanPcmkError(
4871 "R", "stdout1\n\n stdout2\n", "stderr1\n\n stderr2\n"
4872 ),
4873 )
4874
4875
4876 class ResourceBanPcmkSuccess(NameBuildTest):
4877 def test_success(self):
4878 self.assert_message_from_report(
4879 "stdout1\n stdout2\nstderr1\n stderr2",
4880 reports.ResourceBanPcmkSuccess(
4881 "R", "stdout1\n\n stdout2\n", "stderr1\n\n stderr2\n"
4882 ),
4883 )
4884
4885 def test_translate(self):
4886 self.assert_message_from_report(
4887 (
4888 "Warning: Creating location constraint "
4889 "'cli-ban-dummy-on-node1' with a score of -INFINITY "
4890 "for resource dummy on node1.\n"
4891 " This will prevent dummy from running on node1 until the "
4892 "constraint is removed\n"
4893 " This will be the case even if node1 is the last node in "
4894 "the cluster"
4895 ),
4896 reports.ResourceBanPcmkSuccess(
4897 "dummy",
4898 "",
4899 (
4900 "WARNING: Creating rsc_location constraint "
4901 "'cli-ban-dummy-on-node1' with a score of -INFINITY "
4902 "for resource dummy on node1.\n"
4903 " This will prevent dummy from running on node1 until "
4904 "the constraint is removed using the clear option or "
4905 "by editing the CIB with an appropriate tool\n"
4906 " This will be the case even if node1 is the last node "
4907 "in the cluster\n"
4908 ),
4909 ),
4910 )
4911
4912
4913 class CannotUnmoveUnbanResourceMasterResourceNotPromotable(NameBuildTest):
4914 def test_without_promotable(self):
4915 self.assert_message_from_report(
4916 "when specifying promoted you must use the promotable clone id",
4917 reports.CannotUnmoveUnbanResourceMasterResourceNotPromotable("R"),
4918 )
4919
4920 def test_with_promotable(self):
4921 self.assert_message_from_report(
4922 "when specifying promoted you must use the promotable clone id (P)",
4923 reports.CannotUnmoveUnbanResourceMasterResourceNotPromotable(
4924 "R", promotable_id="P"
4925 ),
4926 )
4927
4928
4929 class ResourceUnmoveUnbanPcmkExpiredNotSupported(NameBuildTest):
4930 def test_success(self):
4931 self.assert_message_from_report(
4932 "expired is not supported, please upgrade pacemaker",
4933 reports.ResourceUnmoveUnbanPcmkExpiredNotSupported(),
4934 )
4935
4936
4937 class ResourceUnmoveUnbanPcmkError(NameBuildTest):
4938 def test_success(self):
4939 self.assert_message_from_report(
4940 "cannot clear resource 'R'\nstdout1\n stdout2\nstderr1\n stderr2",
4941 reports.ResourceUnmoveUnbanPcmkError(
4942 "R", "stdout1\n\n stdout2\n", "stderr1\n\n stderr2\n"
4943 ),
4944 )
4945
4946
4947 class ResourceUnmoveUnbanPcmkSuccess(NameBuildTest):
4948 def test_success(self):
4949 self.assert_message_from_report(
4950 "stdout1\n stdout2\nstderr1\n stderr2",
4951 reports.ResourceUnmoveUnbanPcmkSuccess(
4952 "R", "stdout1\n\n stdout2\n", "stderr1\n\n stderr2\n"
4953 ),
4954 )
4955
4956
4957 class ResourceMoveConstraintCreated(NameBuildTest):
4958 def test_success(self):
4959 self.assert_message_from_report(
4960 "Location constraint to move resource 'R1' has been created",
4961 reports.ResourceMoveConstraintCreated("R1"),
4962 )
4963
4964
4965 class ResourceMoveConstraintRemoved(NameBuildTest):
4966 def test_success(self):
4967 self.assert_message_from_report(
4968 (
4969 "Location constraint created to move resource 'R1' has "
4970 "been removed"
4971 ),
4972 reports.ResourceMoveConstraintRemoved("R1"),
4973 )
4974
4975
4976 class ResourceMoveNotAffectingResource(NameBuildTest):
4977 def test_success(self):
4978 self.assert_message_from_report(
4979 (
4980 "Unable to move resource 'R1' using a location constraint. "
4981 "Current location of the resource may be affected by some "
4982 "other constraint."
4983 ),
4984 reports.ResourceMoveNotAffectingResource("R1"),
4985 )
4986
4987
4988 class ResourceMoveAffectsOtherResources(NameBuildTest):
4989 def test_multiple(self):
4990 self.assert_message_from_report(
4991 "Moving resource 'R1' affects resources: 'p0', 'p1', 'p2'",
4992 reports.ResourceMoveAffectsOtherResources("R1", ["p2", "p0", "p1"]),
4993 )
4994
4995 def test_single(self):
4996 self.assert_message_from_report(
4997 "Moving resource 'R1' affects resource: 'R2'",
4998 reports.ResourceMoveAffectsOtherResources("R1", ["R2"]),
4999 )
5000
5001
5002 class ResourceMoveAutocleanSimulationFailure(NameBuildTest):
5003 def test_simulation(self):
5004 self.assert_message_from_report(
5005 (
5006 "Unable to ensure that moved resource 'R1' will stay on the "
5007 "same node after a constraint used for moving it is removed."
5008 ),
5009 reports.ResourceMoveAutocleanSimulationFailure(
5010 "R1", others_affected=False
5011 ),
5012 )
5013
5014 def test_simulation_others_affected(self):
5015 self.assert_message_from_report(
5016 (
5017 "Unable to ensure that moved resource 'R1' or other resources "
5018 "will stay on the same node after a constraint used for moving "
5019 "it is removed."
5020 ),
5021 reports.ResourceMoveAutocleanSimulationFailure(
5022 "R1", others_affected=True
5023 ),
5024 )
5025
5026 def test_live(self):
5027 self.assert_message_from_report(
5028 (
5029 "Unable to ensure that moved resource 'R1' will stay on the "
5030 "same node after a constraint used for moving it is removed."
5031 " The constraint to move the resource has not been removed "
5032 "from configuration. Consider removing it manually. Be aware "
5033 "that removing the constraint may cause resources to move to "
5034 "other nodes."
5035 ),
5036 reports.ResourceMoveAutocleanSimulationFailure(
5037 "R1", others_affected=False, move_constraint_left_in_cib=True
5038 ),
5039 )
5040
5041 def test_live_others_affected(self):
5042 self.assert_message_from_report(
5043 (
5044 "Unable to ensure that moved resource 'R1' or other resources "
5045 "will stay on the same node after a constraint used for moving "
5046 "it is removed."
5047 " The constraint to move the resource has not been removed "
5048 "from configuration. Consider removing it manually. Be aware "
5049 "that removing the constraint may cause resources to move to "
5050 "other nodes."
5051 ),
5052 reports.ResourceMoveAutocleanSimulationFailure(
5053 "R1", others_affected=True, move_constraint_left_in_cib=True
5054 ),
5055 )
5056
5057
5058 class ResourceMayOrMayNotMove(NameBuildTest):
5059 def test_build_message(self):
5060 self.assert_message_from_report(
5061 (
5062 "A move constraint has been created and the resource 'id' may "
5063 "or may not move depending on other configuration"
5064 ),
5065 reports.ResourceMayOrMayNotMove("id"),
5066 )
5067
5068
5069 class ParseErrorJsonFile(NameBuildTest):
5070 def test_success(self):
5071 self.assert_message_from_report(
5072 "Unable to parse known-hosts file '/tmp/known-hosts': "
5073 "some reason: line 15 column 5 (char 100)",
5074 reports.ParseErrorJsonFile(
5075 file_type_codes.PCS_KNOWN_HOSTS,
5076 15,
5077 5,
5078 100,
5079 "some reason",
5080 "some reason: line 15 column 5 (char 100)",
5081 file_path="/tmp/known-hosts",
5082 ),
5083 )
5084
5085
5086 class ResourceDisableAffectsOtherResources(NameBuildTest):
5087 def test_multiple_disabled(self):
5088 self.assert_message_from_report(
5089 (
5090 "Disabling specified resource would have an effect on these "
5091 "resources: 'O1', 'O2'"
5092 ),
5093 reports.ResourceDisableAffectsOtherResources(
5094 ["D1"],
5095 ["O2", "O1"],
5096 ),
5097 )
5098
5099 def test_multiple_affected(self):
5100 self.assert_message_from_report(
5101 (
5102 "Disabling specified resources would have an effect on this "
5103 "resource: 'O1'"
5104 ),
5105 reports.ResourceDisableAffectsOtherResources(
5106 ["D2", "D1"],
5107 ["O1"],
5108 ),
5109 )
5110
5111
5112 class DrConfigAlreadyExist(NameBuildTest):
5113 def test_success(self):
5114 self.assert_message_from_report(
5115 "Disaster-recovery already configured",
5116 reports.DrConfigAlreadyExist(),
5117 )
5118
5119
5120 class DrConfigDoesNotExist(NameBuildTest):
5121 def test_success(self):
5122 self.assert_message_from_report(
5123 "Disaster-recovery is not configured",
5124 reports.DrConfigDoesNotExist(),
5125 )
5126
5127
5128 class NodeInLocalCluster(NameBuildTest):
5129 def test_success(self):
5130 self.assert_message_from_report(
5131 "Node 'node-name' is part of local cluster",
5132 reports.NodeInLocalCluster("node-name"),
5133 )
5134
5135
5136 class BoothLackOfSites(NameBuildTest):
5137 def test_no_site(self):
5138 self.assert_message_from_report(
5139 (
5140 "lack of sites for booth configuration (need 2 at least): "
5141 "sites missing"
5142 ),
5143 reports.BoothLackOfSites([]),
5144 )
5145
5146 def test_single_site(self):
5147 self.assert_message_from_report(
5148 (
5149 "lack of sites for booth configuration (need 2 at least): "
5150 "sites 'site1'"
5151 ),
5152 reports.BoothLackOfSites(["site1"]),
5153 )
5154
5155 def test_multiple_sites(self):
5156 self.assert_message_from_report(
5157 (
5158 "lack of sites for booth configuration (need 2 at least): "
5159 "sites 'site1', 'site2'"
5160 ),
5161 reports.BoothLackOfSites(["site1", "site2"]),
5162 )
5163
5164
5165 class BoothEvenPeersNumber(NameBuildTest):
5166 def test_success(self):
5167 self.assert_message_from_report(
5168 "odd number of peers is required (entered 4 peers)",
5169 reports.BoothEvenPeersNumber(4),
5170 )
5171
5172
5173 class BoothAddressDuplication(NameBuildTest):
5174 def test_single_address(self):
5175 self.assert_message_from_report(
5176 "duplicate address for booth configuration: 'addr1'",
5177 reports.BoothAddressDuplication(["addr1"]),
5178 )
5179
5180 def test_multiple_addresses(self):
5181 self.assert_message_from_report(
5182 (
5183 "duplicate address for booth configuration: 'addr1', 'addr2', "
5184 "'addr3'"
5185 ),
5186 reports.BoothAddressDuplication(
5187 sorted(["addr2", "addr1", "addr3"])
5188 ),
5189 )
5190
5191
5192 class BoothConfigUnexpectedLines(NameBuildTest):
5193 def test_single_line(self):
5194 self.assert_message_from_report(
5195 "unexpected line in booth config:\nline",
5196 reports.BoothConfigUnexpectedLines(["line"]),
5197 )
5198
5199 def test_multiple_lines(self):
5200 self.assert_message_from_report(
5201 "unexpected lines in booth config:\nline\nline2",
5202 reports.BoothConfigUnexpectedLines(["line", "line2"]),
5203 )
5204
5205 def test_file_path(self):
5206 self.assert_message_from_report(
5207 "unexpected line in booth config 'PATH':\nline",
5208 reports.BoothConfigUnexpectedLines(["line"], file_path="PATH"),
5209 )
5210
5211
5212 class BoothInvalidName(NameBuildTest):
5213 def test_success(self):
5214 self.assert_message_from_report(
5215 "booth name '/name' is not valid, it cannot contain /{} characters",
5216 reports.BoothInvalidName("/name", "/{}"),
5217 )
5218
5219
5220 class BoothTicketNameInvalid(NameBuildTest):
5221 def test_success(self):
5222 self.assert_message_from_report(
5223 (
5224 "booth ticket name 'ticket&' is not valid, use up to 63 "
5225 "alphanumeric characters or dash"
5226 ),
5227 reports.BoothTicketNameInvalid("ticket&"),
5228 )
5229
5230
5231 class BoothTicketDuplicate(NameBuildTest):
5232 def test_success(self):
5233 self.assert_message_from_report(
5234 "booth ticket name 'ticket_name' already exists in configuration",
5235 reports.BoothTicketDuplicate("ticket_name"),
5236 )
5237
5238
5239 class BoothTicketDoesNotExist(NameBuildTest):
5240 def test_success(self):
5241 self.assert_message_from_report(
5242 "booth ticket name 'ticket_name' does not exist",
5243 reports.BoothTicketDoesNotExist("ticket_name"),
5244 )
5245
5246
5247 class BoothTicketNotInCib(NameBuildTest):
5248 def test_success(self):
5249 self.assert_message_from_report(
5250 "Unable to find ticket 'name' in CIB",
5251 reports.BoothTicketNotInCib("name"),
5252 )
5253
5254
5255 class BoothAlreadyInCib(NameBuildTest):
5256 def test_success(self):
5257 self.assert_message_from_report(
5258 "booth instance 'name' is already created as cluster resource",
5259 reports.BoothAlreadyInCib("name"),
5260 )
5261
5262
5263 class BoothPathNotExists(NameBuildTest):
5264 def test_success(self):
5265 self.assert_message_from_report(
5266 (
5267 "Configuration directory for booth 'path' is missing. Is booth "
5268 "installed?"
5269 ),
5270 reports.BoothPathNotExists("path"),
5271 )
5272
5273
5274 class BoothNotExistsInCib(NameBuildTest):
5275 def test_success(self):
5276 self.assert_message_from_report(
5277 "booth instance 'name' not found in cib",
5278 reports.BoothNotExistsInCib("name"),
5279 )
5280
5281
5282 class BoothConfigIsUsed(NameBuildTest):
5283 def test_cluster(self):
5284 self.assert_message_from_report(
5285 "booth instance 'name' is used in a cluster resource",
5286 reports.BoothConfigIsUsed(
5287 "name", reports.const.BOOTH_CONFIG_USED_IN_CLUSTER_RESOURCE
5288 ),
5289 )
5290
5291 def test_cluster_resource(self):
5292 self.assert_message_from_report(
5293 "booth instance 'name' is used in cluster resource 'R'",
5294 reports.BoothConfigIsUsed(
5295 "name", reports.const.BOOTH_CONFIG_USED_IN_CLUSTER_RESOURCE, "R"
5296 ),
5297 )
5298
5299 def test_systemd_enabled(self):
5300 self.assert_message_from_report(
5301 "booth instance 'name' is used - it is enabled in systemd",
5302 reports.BoothConfigIsUsed(
5303 "name", reports.const.BOOTH_CONFIG_USED_ENABLED_IN_SYSTEMD
5304 ),
5305 )
5306
5307 def test_systemd_running(self):
5308 self.assert_message_from_report(
5309 "booth instance 'name' is used - it is running by systemd",
5310 reports.BoothConfigIsUsed(
5311 "name", reports.const.BOOTH_CONFIG_USED_RUNNING_IN_SYSTEMD
5312 ),
5313 )
5314
5315
5316 class BoothMultipleTimesInCib(NameBuildTest):
5317 def test_success(self):
5318 self.assert_message_from_report(
5319 "found more than one booth instance 'name' in cib",
5320 reports.BoothMultipleTimesInCib("name"),
5321 )
5322
5323
5324 class BoothConfigDistributionStarted(NameBuildTest):
5325 def test_success(self):
5326 self.assert_message_from_report(
5327 "Sending booth configuration to cluster nodes...",
5328 reports.BoothConfigDistributionStarted(),
5329 )
5330
5331
5332 class BoothConfigAcceptedByNode(NameBuildTest):
5333 def test_defaults(self):
5334 self.assert_message_from_report(
5335 "Booth config saved",
5336 reports.BoothConfigAcceptedByNode(),
5337 )
5338
5339 def test_empty_name_list(self):
5340 self.assert_message_from_report(
5341 "Booth config saved",
5342 reports.BoothConfigAcceptedByNode(name_list=[]),
5343 )
5344
5345 def test_node_and_empty_name_list(self):
5346 self.assert_message_from_report(
5347 "node1: Booth config saved",
5348 reports.BoothConfigAcceptedByNode(node="node1", name_list=[]),
5349 )
5350
5351 def test_name_booth_only(self):
5352 self.assert_message_from_report(
5353 "Booth config saved",
5354 reports.BoothConfigAcceptedByNode(name_list=["booth"]),
5355 )
5356
5357 def test_name_booth_and_node(self):
5358 self.assert_message_from_report(
5359 "node1: Booth config saved",
5360 reports.BoothConfigAcceptedByNode(
5361 node="node1",
5362 name_list=["booth"],
5363 ),
5364 )
5365
5366 def test_single_name(self):
5367 self.assert_message_from_report(
5368 "Booth config 'some' saved",
5369 reports.BoothConfigAcceptedByNode(name_list=["some"]),
5370 )
5371
5372 def test_multiple_names(self):
5373 self.assert_message_from_report(
5374 "Booth configs 'another', 'some' saved",
5375 reports.BoothConfigAcceptedByNode(name_list=["another", "some"]),
5376 )
5377
5378 def test_node(self):
5379 self.assert_message_from_report(
5380 "node1: Booth configs 'another', 'some' saved",
5381 reports.BoothConfigAcceptedByNode(
5382 node="node1",
5383 name_list=["some", "another"],
5384 ),
5385 )
5386
5387
5388 class BoothConfigDistributionNodeError(NameBuildTest):
5389 def test_empty_name(self):
5390 self.assert_message_from_report(
5391 "Unable to save booth config on node 'node1': reason1",
5392 reports.BoothConfigDistributionNodeError("node1", "reason1"),
5393 )
5394
5395 def test_booth_name(self):
5396 self.assert_message_from_report(
5397 "Unable to save booth config on node 'node1': reason1",
5398 reports.BoothConfigDistributionNodeError(
5399 "node1",
5400 "reason1",
5401 name="booth",
5402 ),
5403 )
5404
5405 def test_another_name(self):
5406 self.assert_message_from_report(
5407 "Unable to save booth config 'another' on node 'node1': reason1",
5408 reports.BoothConfigDistributionNodeError(
5409 "node1",
5410 "reason1",
5411 name="another",
5412 ),
5413 )
5414
5415
5416 class BoothFetchingConfigFromNode(NameBuildTest):
5417 def test_empty_name(self):
5418 self.assert_message_from_report(
5419 "Fetching booth config from node 'node1'...",
5420 reports.BoothFetchingConfigFromNode("node1"),
5421 )
5422
5423 def test_booth_name(self):
5424 self.assert_message_from_report(
5425 "Fetching booth config from node 'node1'...",
5426 reports.BoothFetchingConfigFromNode("node1", config="booth"),
5427 )
5428
5429 def test_another_name(self):
5430 self.assert_message_from_report(
5431 "Fetching booth config 'another' from node 'node1'...",
5432 reports.BoothFetchingConfigFromNode("node1", config="another"),
5433 )
5434
5435
5436 class BoothUnsupportedFileLocation(NameBuildTest):
5437 def test_success(self):
5438 self.assert_message_from_report(
5439 (
5440 "Booth configuration '/some/file' is outside of supported "
5441 "booth config directory '/booth/conf/dir/', ignoring the file"
5442 ),
5443 reports.BoothUnsupportedFileLocation(
5444 "/some/file",
5445 "/booth/conf/dir/",
5446 file_type_codes.BOOTH_CONFIG,
5447 ),
5448 )
5449
5450
5451 class BoothDaemonStatusError(NameBuildTest):
5452 def test_success(self):
5453 self.assert_message_from_report(
5454 "unable to get status of booth daemon: some reason",
5455 reports.BoothDaemonStatusError("some reason"),
5456 )
5457
5458
5459 class BoothTicketStatusError(NameBuildTest):
5460 def test_minimal(self):
5461 self.assert_message_from_report(
5462 "unable to get status of booth tickets",
5463 reports.BoothTicketStatusError(),
5464 )
5465
5466 def test_all(self):
5467 self.assert_message_from_report(
5468 "unable to get status of booth tickets: some reason",
5469 reports.BoothTicketStatusError(reason="some reason"),
5470 )
5471
5472
5473 class BoothPeersStatusError(NameBuildTest):
5474 def test_minimal(self):
5475 self.assert_message_from_report(
5476 "unable to get status of booth peers",
5477 reports.BoothPeersStatusError(),
5478 )
5479
5480 def test_all(self):
5481 self.assert_message_from_report(
5482 "unable to get status of booth peers: some reason",
5483 reports.BoothPeersStatusError(reason="some reason"),
5484 )
5485
5486
5487 class BoothCannotDetermineLocalSiteIp(NameBuildTest):
5488 def test_success(self):
5489 self.assert_message_from_report(
5490 "cannot determine local site ip, please specify site parameter",
5491 reports.BoothCannotDetermineLocalSiteIp(),
5492 )
5493
5494
5495 class BoothTicketOperationFailed(NameBuildTest):
5496 def test_success(self):
5497 self.assert_message_from_report(
5498 (
5499 "unable to operation booth ticket 'ticket_name'"
5500 " for site 'site_ip', reason: reason"
5501 ),
5502 reports.BoothTicketOperationFailed(
5503 "operation", "reason", "site_ip", "ticket_name"
5504 ),
5505 )
5506
5507 def test_no_site_ip(self):
5508 self.assert_message_from_report(
5509 ("unable to operation booth ticket 'ticket_name', reason: reason"),
5510 reports.BoothTicketOperationFailed(
5511 "operation", "reason", None, "ticket_name"
5512 ),
5513 )
5514
5515
5516 class BoothTicketChangingState(NameBuildTest):
5517 def test_success(self):
5518 self.assert_message_from_report(
5519 "Changing state of ticket 'name' to standby",
5520 reports.BoothTicketChangingState("name", "standby"),
5521 )
5522
5523
5524 class BoothTicketCleanup(NameBuildTest):
5525 def test_success(self):
5526 self.assert_message_from_report(
5527 "Cleaning up ticket 'name' from CIB",
5528 reports.BoothTicketCleanup("name"),
5529 )
5530
5531
5532 # TODO: remove, use ADD_REMOVE reports
5533 class TagAddRemoveIdsDuplication(NameBuildTest):
5534 def test_message_add(self):
5535 self.assert_message_from_report(
5536 "Ids to add must be unique, duplicate ids: 'dup1', 'dup2'",
5537 reports.TagAddRemoveIdsDuplication(
5538 duplicate_ids_list=["dup2", "dup1"],
5539 ),
5540 )
5541
5542 def test_message_remove(self):
5543 self.assert_message_from_report(
5544 "Ids to remove must be unique, duplicate ids: 'dup1', 'dup2'",
5545 reports.TagAddRemoveIdsDuplication(
5546 duplicate_ids_list=["dup2", "dup1"],
5547 add_or_not_remove=False,
5548 ),
5549 )
5550
5551
5552 # TODO: remove, use ADD_REMOVE reports
5553 class TagAdjacentReferenceIdNotInTheTag(NameBuildTest):
5554 def test_message(self):
5555 self.assert_message_from_report(
5556 (
5557 "There is no reference id 'adj_id' in the tag 'tag_id', cannot "
5558 "put reference ids next to it in the tag"
5559 ),
5560 reports.TagAdjacentReferenceIdNotInTheTag("adj_id", "tag_id"),
5561 )
5562
5563
5564 # TODO: remove, use ADD_REMOVE reports
5565 class TagCannotAddAndRemoveIdsAtTheSameTime(NameBuildTest):
5566 def test_message_one_item(self):
5567 self.assert_message_from_report(
5568 "Ids cannot be added and removed at the same time: 'id1'",
5569 reports.TagCannotAddAndRemoveIdsAtTheSameTime(["id1"]),
5570 )
5571
5572 def test_message_more_items(self):
5573 self.assert_message_from_report(
5574 (
5575 "Ids cannot be added and removed at the same time: 'id1', "
5576 "'id2', 'id3'"
5577 ),
5578 reports.TagCannotAddAndRemoveIdsAtTheSameTime(
5579 ["id3", "id2", "id1"],
5580 ),
5581 )
5582
5583
5584 # TODO: remove, use ADD_REMOVE reports
5585 class TagCannotAddReferenceIdsAlreadyInTheTag(NameBuildTest):
5586 def test_message_singular(self):
5587 self.assert_message_from_report(
5588 "Cannot add reference id already in the tag 'tag_id': 'id1'",
5589 reports.TagCannotAddReferenceIdsAlreadyInTheTag(
5590 "tag_id",
5591 ["id1"],
5592 ),
5593 )
5594
5595 def test_message_plural(self):
5596 self.assert_message_from_report(
5597 "Cannot add reference ids already in the tag 'TAG': 'id1', 'id2'",
5598 reports.TagCannotAddReferenceIdsAlreadyInTheTag(
5599 "TAG",
5600 ["id2", "id1"],
5601 ),
5602 )
5603
5604
5605 class TagCannotContainItself(NameBuildTest):
5606 def test_message(self):
5607 self.assert_message_from_report(
5608 "Tag cannot contain itself", reports.TagCannotContainItself()
5609 )
5610
5611
5612 class TagCannotCreateEmptyTagNoIdsSpecified(NameBuildTest):
5613 def test_message(self):
5614 self.assert_message_from_report(
5615 "Cannot create empty tag, no resource ids specified",
5616 reports.TagCannotCreateEmptyTagNoIdsSpecified(),
5617 )
5618
5619
5620 # TODO: remove, use ADD_REMOVE reports
5621 class TagCannotPutIdNextToItself(NameBuildTest):
5622 def test_message(self):
5623 self.assert_message_from_report(
5624 "Cannot put id 'some_id' next to itself.",
5625 reports.TagCannotPutIdNextToItself("some_id"),
5626 )
5627
5628
5629 # TODO: remove, use ADD_REMOVE reports
5630 class TagCannotRemoveAdjacentId(NameBuildTest):
5631 def test_message(self):
5632 self.assert_message_from_report(
5633 "Cannot remove id 'some_id' next to which ids are being added",
5634 reports.TagCannotRemoveAdjacentId("some_id"),
5635 )
5636
5637
5638 # TODO: remove, use ADD_REMOVE reports
5639 class TagCannotRemoveReferencesWithoutRemovingTag(NameBuildTest):
5640 def test_message(self):
5641 self.assert_message_from_report(
5642 "There would be no references left in the tag 'tag-id'",
5643 reports.TagCannotRemoveReferencesWithoutRemovingTag("tag-id"),
5644 )
5645
5646
5647 class TagCannotRemoveTagReferencedInConstraints(NameBuildTest):
5648 def test_message_singular(self):
5649 self.assert_message_from_report(
5650 "Tag 'tag1' cannot be removed because it is referenced in "
5651 "constraint 'constraint-id-1'",
5652 reports.TagCannotRemoveTagReferencedInConstraints(
5653 "tag1",
5654 ["constraint-id-1"],
5655 ),
5656 )
5657
5658 def test_message_plural(self):
5659 self.assert_message_from_report(
5660 "Tag 'tag2' cannot be removed because it is referenced in "
5661 "constraints 'constraint-id-1', 'constraint-id-2'",
5662 reports.TagCannotRemoveTagReferencedInConstraints(
5663 "tag2",
5664 ["constraint-id-2", "constraint-id-1"],
5665 ),
5666 )
5667
5668
5669 class TagCannotRemoveTagsNoTagsSpecified(NameBuildTest):
5670 def test_message(self):
5671 self.assert_message_from_report(
5672 "Cannot remove tags, no tags to remove specified",
5673 reports.TagCannotRemoveTagsNoTagsSpecified(),
5674 )
5675
5676
5677 # TODO: remove, use ADD_REMOVE reports
5678 class TagCannotSpecifyAdjacentIdWithoutIdsToAdd(NameBuildTest):
5679 def test_message(self):
5680 self.assert_message_from_report(
5681 "Cannot specify adjacent id 'some-id' without ids to add",
5682 reports.TagCannotSpecifyAdjacentIdWithoutIdsToAdd("some-id"),
5683 )
5684
5685
5686 # TODO: remove, use ADD_REMOVE reports
5687 class TagCannotUpdateTagNoIdsSpecified(NameBuildTest):
5688 def test_message(self):
5689 self.assert_message_from_report(
5690 "Cannot update tag, no ids to be added or removed specified",
5691 reports.TagCannotUpdateTagNoIdsSpecified(),
5692 )
5693
5694
5695 # TODO: remove, use ADD_REMOVE reports
5696 class TagIdsNotInTheTag(NameBuildTest):
5697 def test_message_singular(self):
5698 self.assert_message_from_report(
5699 "Tag 'tag-id' does not contain id: 'a'",
5700 reports.TagIdsNotInTheTag("tag-id", ["a"]),
5701 )
5702
5703 def test_message_plural(self):
5704 self.assert_message_from_report(
5705 "Tag 'tag-id' does not contain ids: 'a', 'b'",
5706 reports.TagIdsNotInTheTag("tag-id", ["b", "a"]),
5707 )
5708
5709
5710 class RuleInEffectStatusDetectionNotSupported(NameBuildTest):
5711 def test_success(self):
5712 self.assert_message_from_report(
5713 (
5714 "crm_rule is not available, therefore expired parts of "
5715 "configuration may not be detected. Consider upgrading pacemaker."
5716 ),
5717 reports.RuleInEffectStatusDetectionNotSupported(),
5718 )
5719
5720
5721 class RuleExpressionOptionsDuplication(NameBuildTest):
5722 def test_success(self):
5723 self.assert_message_from_report(
5724 "Duplicate options in a single (sub)expression: 'key', 'name'",
5725 reports.RuleExpressionOptionsDuplication(["name", "key"]),
5726 )
5727
5728
5729 class RuleExpressionSinceGreaterThanUntil(NameBuildTest):
5730 def test_success(self):
5731 self.assert_message_from_report(
5732 "Since '987' is not sooner than until '654'",
5733 reports.RuleExpressionSinceGreaterThanUntil("987", "654"),
5734 )
5735
5736
5737 class RuleExpressionParseError(NameBuildTest):
5738 def test_success(self):
5739 self.assert_message_from_report(
5740 "'resource dummy op monitor' is not a valid rule expression, "
5741 "parse error near or after line 1 column 16",
5742 reports.RuleExpressionParseError(
5743 "resource dummy op monitor",
5744 "Expected end of text",
5745 "resource dummy op monitor",
5746 1,
5747 16,
5748 15,
5749 ),
5750 )
5751
5752
5753 class RuleExpressionNotAllowed(NameBuildTest):
5754 def test_op(self):
5755 self.assert_message_from_report(
5756 "Keyword 'op' cannot be used in a rule in this command",
5757 reports.RuleExpressionNotAllowed(
5758 CibRuleExpressionType.OP_EXPRESSION
5759 ),
5760 )
5761
5762 def test_rsc(self):
5763 self.assert_message_from_report(
5764 "Keyword 'resource' cannot be used in a rule in this command",
5765 reports.RuleExpressionNotAllowed(
5766 CibRuleExpressionType.RSC_EXPRESSION
5767 ),
5768 )
5769
5770 def test_node_attr(self):
5771 self.assert_message_from_report(
5772 "Keywords 'defined', 'not_defined', 'eq', 'ne', 'gte', 'gt', "
5773 "'lte' and 'lt' cannot be used in a rule in this command",
5774 reports.RuleExpressionNotAllowed(CibRuleExpressionType.EXPRESSION),
5775 )
5776
5777
5778 class RuleNoExpressionSpecified(NameBuildTest):
5779 def test_success(self):
5780 self.assert_message_from_report(
5781 "No rule expression was specified",
5782 reports.RuleNoExpressionSpecified(),
5783 )
5784
5785
5786 class CibNvsetAmbiguousProvideNvsetId(NameBuildTest):
5787 def test_success(self):
5788 self.assert_message_from_report(
5789 "Several options sets exist, please specify an option set ID",
5790 reports.CibNvsetAmbiguousProvideNvsetId(
5791 const.PCS_COMMAND_RESOURCE_DEFAULTS_UPDATE
5792 ),
5793 )
5794
5795
5796 class AddRemoveItemsNotSpecified(NameBuildTest):
5797 def test_message(self):
5798 self.assert_message_from_report(
5799 (
5800 "Cannot modify stonith resource 'container-id', no devices to "
5801 "add or remove specified"
5802 ),
5803 reports.AddRemoveItemsNotSpecified(
5804 const.ADD_REMOVE_CONTAINER_TYPE_STONITH_RESOURCE,
5805 const.ADD_REMOVE_ITEM_TYPE_DEVICE,
5806 "container-id",
5807 ),
5808 )
5809
5810
5811 class AddRemoveItemsDuplication(NameBuildTest):
5812 def test_message(self):
5813 self.assert_message_from_report(
5814 (
5815 "Devices to add or remove must be unique, duplicate devices: "
5816 "'dup1', 'dup2'"
5817 ),
5818 reports.AddRemoveItemsDuplication(
5819 const.ADD_REMOVE_CONTAINER_TYPE_STONITH_RESOURCE,
5820 const.ADD_REMOVE_ITEM_TYPE_DEVICE,
5821 "container-id",
5822 ["dup2", "dup1"],
5823 ),
5824 )
5825
5826
5827 class AddRemoveCannotAddItemsAlreadyInTheContainer(NameBuildTest):
5828 def test_message_plural(self):
5829 self.assert_message_from_report(
5830 "Cannot add devices 'i1', 'i2', they are already present in stonith"
5831 " resource 'container-id'",
5832 reports.AddRemoveCannotAddItemsAlreadyInTheContainer(
5833 const.ADD_REMOVE_CONTAINER_TYPE_STONITH_RESOURCE,
5834 const.ADD_REMOVE_ITEM_TYPE_DEVICE,
5835 "container-id",
5836 ["i2", "i1"],
5837 ),
5838 )
5839
5840 def test_message_singular(self):
5841 self.assert_message_from_report(
5842 "Cannot add device 'i1', it is already present in stonith resource "
5843 "'container-id'",
5844 reports.AddRemoveCannotAddItemsAlreadyInTheContainer(
5845 const.ADD_REMOVE_CONTAINER_TYPE_STONITH_RESOURCE,
5846 const.ADD_REMOVE_ITEM_TYPE_DEVICE,
5847 "container-id",
5848 ["i1"],
5849 ),
5850 )
5851
5852
5853 class AddRemoveCannotRemoveItemsNotInTheContainer(NameBuildTest):
5854 def test_message_plural(self):
5855 self.assert_message_from_report(
5856 (
5857 "Cannot remove devices 'i1', 'i2', they are not present in "
5858 "stonith resource 'container-id'"
5859 ),
5860 reports.AddRemoveCannotRemoveItemsNotInTheContainer(
5861 const.ADD_REMOVE_CONTAINER_TYPE_STONITH_RESOURCE,
5862 const.ADD_REMOVE_ITEM_TYPE_DEVICE,
5863 "container-id",
5864 ["i2", "i1"],
5865 ),
5866 )
5867
5868 def test_message_singular(self):
5869 self.assert_message_from_report(
5870 (
5871 "Cannot remove device 'i1', it is not present in "
5872 "stonith resource 'container-id'"
5873 ),
5874 reports.AddRemoveCannotRemoveItemsNotInTheContainer(
5875 const.ADD_REMOVE_CONTAINER_TYPE_STONITH_RESOURCE,
5876 const.ADD_REMOVE_ITEM_TYPE_DEVICE,
5877 "container-id",
5878 ["i1"],
5879 ),
5880 )
5881
5882
5883 class AddRemoveCannotAddAndRemoveItemsAtTheSameTime(NameBuildTest):
5884 def test_message_plural(self):
5885 self.assert_message_from_report(
5886 "Devices cannot be added and removed at the same time: 'i1', 'i2'",
5887 reports.AddRemoveCannotAddAndRemoveItemsAtTheSameTime(
5888 const.ADD_REMOVE_CONTAINER_TYPE_STONITH_RESOURCE,
5889 const.ADD_REMOVE_ITEM_TYPE_DEVICE,
5890 "container-id",
5891 ["i2", "i1"],
5892 ),
5893 )
5894
5895 def test_message_singular(self):
5896 self.assert_message_from_report(
5897 "Device cannot be added and removed at the same time: 'i1'",
5898 reports.AddRemoveCannotAddAndRemoveItemsAtTheSameTime(
5899 const.ADD_REMOVE_CONTAINER_TYPE_STONITH_RESOURCE,
5900 const.ADD_REMOVE_ITEM_TYPE_DEVICE,
5901 "container-id",
5902 ["i1"],
5903 ),
5904 )
5905
5906
5907 class AddRemoveCannotRemoveAllItemsFromTheContainer(NameBuildTest):
5908 def test_message(self):
5909 self.assert_message_from_report(
5910 "Cannot remove all devices from stonith resource 'container-id'",
5911 reports.AddRemoveCannotRemoveAllItemsFromTheContainer(
5912 const.ADD_REMOVE_CONTAINER_TYPE_STONITH_RESOURCE,
5913 const.ADD_REMOVE_ITEM_TYPE_DEVICE,
5914 "container-id",
5915 ["i1", "i2"],
5916 ),
5917 )
5918
5919
5920 class AddRemoveAdjacentItemNotInTheContainer(NameBuildTest):
5921 def test_message(self):
5922 self.assert_message_from_report(
5923 (
5924 "There is no device 'adjacent-item-id' in the stonith resource "
5925 "'container-id', cannot add devices next to it"
5926 ),
5927 reports.AddRemoveAdjacentItemNotInTheContainer(
5928 const.ADD_REMOVE_CONTAINER_TYPE_STONITH_RESOURCE,
5929 const.ADD_REMOVE_ITEM_TYPE_DEVICE,
5930 "container-id",
5931 "adjacent-item-id",
5932 ),
5933 )
5934
5935
5936 class AddRemoveCannotPutItemNextToItself(NameBuildTest):
5937 def test_message(self):
5938 self.assert_message_from_report(
5939 "Cannot put device 'adjacent-item-id' next to itself",
5940 reports.AddRemoveCannotPutItemNextToItself(
5941 const.ADD_REMOVE_CONTAINER_TYPE_STONITH_RESOURCE,
5942 const.ADD_REMOVE_ITEM_TYPE_DEVICE,
5943 "container-id",
5944 "adjacent-item-id",
5945 ),
5946 )
5947
5948
5949 class AddRemoveCannotSpecifyAdjacentItemWithoutItemsToAdd(NameBuildTest):
5950 def test_message(self):
5951 self.assert_message_from_report(
5952 (
5953 "Cannot specify adjacent device 'adjacent-item-id' without "
5954 "devices to add"
5955 ),
5956 reports.AddRemoveCannotSpecifyAdjacentItemWithoutItemsToAdd(
5957 const.ADD_REMOVE_CONTAINER_TYPE_STONITH_RESOURCE,
5958 const.ADD_REMOVE_ITEM_TYPE_DEVICE,
5959 "container-id",
5960 "adjacent-item-id",
5961 ),
5962 )
5963
5964
5965 class CloningStonithResourcesHasNoEffect(NameBuildTest):
5966 def test_singular_without_group_id(self):
5967 self.assert_message_from_report(
5968 (
5969 "No need to clone stonith resource 'fence1', any node can use "
5970 "a stonith resource (unless specifically banned) regardless of "
5971 "whether the stonith resource is running on that node or not"
5972 ),
5973 reports.CloningStonithResourcesHasNoEffect(["fence1"]),
5974 )
5975
5976 def test_plural_with_group_id(self):
5977 self.assert_message_from_report(
5978 (
5979 "Group 'StonithGroup' contains stonith resources. No need to "
5980 "clone stonith resources 'fence1', 'fence2', any node can use "
5981 "a stonith resource (unless specifically banned) regardless of "
5982 "whether the stonith resource is running on that node or not"
5983 ),
5984 reports.CloningStonithResourcesHasNoEffect(
5985 ["fence1", "fence2"], "StonithGroup"
5986 ),
5987 )
5988
5989
5990 class CommandInvalidPayload(NameBuildTest):
5991 def test_all(self):
5992 reason = "a reason"
5993 self.assert_message_from_report(
5994 f"Invalid command payload: {reason}",
5995 reports.CommandInvalidPayload(reason),
5996 )
5997
5998
5999 class CommandUnknown(NameBuildTest):
6000 def test_all(self):
6001 cmd = "a cmd"
6002 self.assert_message_from_report(
6003 f"Unknown command '{cmd}'",
6004 reports.CommandUnknown(cmd),
6005 )
6006
6007
6008 class NotAuthorized(NameBuildTest):
6009 def test_all(self):
6010 self.assert_message_from_report(
6011 "Current user is not authorized for this operation",
6012 reports.NotAuthorized(),
6013 )
6014
6015
6016 class AgentSelfValidationResult(NameBuildTest):
6017 def test_message(self):
6018 lines = [f"line #{i}" for i in range(3)]
6019 self.assert_message_from_report(
6020 "Validation result from agent:\n {}".format("\n ".join(lines)),
6021 reports.AgentSelfValidationResult("\n".join(lines)),
6022 )
6023
6024
6025 class AgentSelfValidationInvalidData(NameBuildTest):
6026 def test_message(self):
6027 reason = "not xml"
6028 self.assert_message_from_report(
6029 f"Invalid validation data from agent: {reason}",
6030 reports.AgentSelfValidationInvalidData(reason),
6031 )
6032
6033
6034 class AgentSelfValidationSkippedUpdatedResourceMisconfigured(NameBuildTest):
6035 def test_message(self):
6036 lines = [f"line #{i}" for i in range(3)]
6037 self.assert_message_from_report(
6038 (
6039 "The resource was misconfigured before the update, therefore "
6040 "agent self-validation will not be run for the updated "
6041 "configuration. Validation output of the original "
6042 "configuration:\n {}"
6043 ).format("\n ".join(lines)),
6044 reports.AgentSelfValidationSkippedUpdatedResourceMisconfigured(
6045 "\n".join(lines)
6046 ),
6047 )
6048
6049
6050 class AgentSelfValidationAutoOnWithWarnings(NameBuildTest):
6051 def test_message(self):
6052 self.assert_message_from_report(
6053 (
6054 "Validating resource options using the resource agent itself "
6055 "is enabled by default and produces warnings. In a future "
6056 "version, this might be changed to errors. Enable "
6057 "agent validation to switch to the future behavior."
6058 ),
6059 reports.AgentSelfValidationAutoOnWithWarnings(),
6060 )
6061
6062
6063 class ResourceCloneIncompatibleMetaAttributes(NameBuildTest):
6064 def test_with_provider(self):
6065 attr = "attr_name"
6066 self.assert_message_from_report(
6067 f"Clone option '{attr}' is not compatible with 'standard:provider:type' resource agent",
6068 reports.ResourceCloneIncompatibleMetaAttributes(
6069 attr, ResourceAgentNameDto("standard", "provider", "type")
6070 ),
6071 )
6072
6073 def test_without_provider(self):
6074 attr = "attr_name"
6075 self.assert_message_from_report(
6076 f"Clone option '{attr}' is not compatible with 'standard:type' resource agent",
6077 reports.ResourceCloneIncompatibleMetaAttributes(
6078 attr, ResourceAgentNameDto("standard", None, "type")
6079 ),
6080 )
6081
6082 def test_resource_id(self):
6083 attr = "attr_name"
6084 res_id = "resource_id"
6085 self.assert_message_from_report(
6086 (
6087 f"Clone option '{attr}' is not compatible with 'standard:type' "
6088 f"resource agent of resource '{res_id}'"
6089 ),
6090 reports.ResourceCloneIncompatibleMetaAttributes(
6091 attr,
6092 ResourceAgentNameDto("standard", None, "type"),
6093 resource_id=res_id,
6094 ),
6095 )
6096
6097 def test_group_id(self):
6098 attr = "attr_name"
6099 res_id = "resource id"
6100 group_id = "group id"
6101 self.assert_message_from_report(
6102 (
6103 f"Clone option '{attr}' is not compatible with 'standard:type' "
6104 f"resource agent of resource '{res_id}' in group '{group_id}'"
6105 ),
6106 reports.ResourceCloneIncompatibleMetaAttributes(
6107 attr,
6108 ResourceAgentNameDto("standard", None, "type"),
6109 resource_id=res_id,
6110 group_id=group_id,
6111 ),
6112 )
6113
6114
6115 class BoothAuthfileNotUsed(NameBuildTest):
6116 def test_message(self):
6117 self.assert_message_from_report(
6118 "Booth authfile is not enabled",
6119 reports.BoothAuthfileNotUsed("instance name"),
6120 )
6121
6122
6123 class BoothUnsupportedOptionEnableAuthfile(NameBuildTest):
6124 def test_message(self):
6125 self.assert_message_from_report(
6126 "Unsupported option 'enable-authfile' is set in booth configuration",
6127 reports.BoothUnsupportedOptionEnableAuthfile("instance name"),
6128 )
6129
6130
6131 class CannotCreateDefaultClusterPropertySet(NameBuildTest):
6132 def test_all(self):
6133 self.assert_message_from_report(
6134 (
6135 "Cannot create default cluster_property_set element, ID "
6136 "'cib-bootstrap-options' already exists. Find elements with the"
6137 " ID and remove them from cluster configuration."
6138 ),
6139 reports.CannotCreateDefaultClusterPropertySet(
6140 "cib-bootstrap-options"
6141 ),
6142 )
6143
6144
6145 class ClusterStatusBundleMemberIdAsImplicit(NameBuildTest):
6146 def test_one(self):
6147 self.assert_message_from_report(
6148 (
6149 "Skipping bundle 'resource-bundle': resource 'resource' has "
6150 "the same id as some of the implicit bundle resources"
6151 ),
6152 reports.ClusterStatusBundleMemberIdAsImplicit(
6153 "resource-bundle", ["resource"]
6154 ),
6155 )
6156
6157 def test_multiple(self):
6158 self.assert_message_from_report(
6159 (
6160 "Skipping bundle 'resource-bundle': resources 'resource-0', "
6161 "'resource-1' have the same id as some of the implicit bundle "
6162 "resources"
6163 ),
6164 reports.ClusterStatusBundleMemberIdAsImplicit(
6165 "resource-bundle", ["resource-0", "resource-1"]
6166 ),
6167 )
6168
6169
6170 class ResourceWaitDeprecated(NameBuildTest):
6171 def test_success(self):
6172 self.assert_message_from_report(
6173 (
6174 "Ability of this command to accept 'wait' argument is "
6175 "deprecated and will be removed in a future release."
6176 ),
6177 reports.ResourceWaitDeprecated(),
6178 )
6179
6180
6181 class CommandArgumentTypeMismatch(NameBuildTest):
6182 def test_message(self) -> str:
6183 self.assert_message_from_report(
6184 "This command does not accept entity type.",
6185 reports.CommandArgumentTypeMismatch(
6186 "entity type", "pcs stonith create"
6187 ),
6188 )
6189
6190
6191 class ResourceRestartError(NameBuildTest):
6192 def test_message(self) -> str:
6193 self.assert_message_from_report(
6194 "Unable to restart resource 'resourceId':\nerror description",
6195 reports.ResourceRestartError("error description", "resourceId"),
6196 )
6197
6198
6199 class ResourceRestartNodeIsForMultiinstanceOnly(NameBuildTest):
6200 def test_message(self) -> str:
6201 self.assert_message_from_report(
6202 (
6203 "Can only restart on a specific node for a clone or bundle, "
6204 "'resourceId' is a resource"
6205 ),
6206 reports.ResourceRestartNodeIsForMultiinstanceOnly(
6207 "resourceId", "primitive", "node01"
6208 ),
6209 )
6210
6211
6212 class ResourceRestartUsingParentRersource(NameBuildTest):
6213 def test_message(self) -> str:
6214 self.assert_message_from_report(
6215 (
6216 "Restarting 'parentId' instead...\n"
6217 "(If a resource is a clone or bundle, you must use the clone "
6218 "or bundle instead)"
6219 ),
6220 reports.ResourceRestartUsingParentRersource(
6221 "resourceId", "parentId"
6222 ),
6223 )
6224
6225
6226 class ClusterOptionsMetadataNotSupported(NameBuildTest):
6227 def test_success(self):
6228 self.assert_message_from_report(
6229 (
6230 "Cluster options metadata are not supported, please upgrade "
6231 "pacemaker"
6232 ),
6233 reports.ClusterOptionsMetadataNotSupported(),
6234 )
6235
6236
6237 class StoppingResources(NameBuildTest):
6238 def test_one_resource(self):
6239 self.assert_message_from_report(
6240 "Stopping resource 'resourceId'",
6241 reports.StoppingResources(["resourceId"]),
6242 )
6243
6244 def test_multiple_resources(self):
6245 self.assert_message_from_report(
6246 "Stopping resources 'resourceId1', 'resourceId2'",
6247 reports.StoppingResources(["resourceId1", "resourceId2"]),
6248 )
6249
6250
6251 class StoppedResourcesBeforeDeleteCheckSkipped(NameBuildTest):
6252 def test_one_resource(self):
6253 self.assert_message_from_report(
6254 (
6255 "Not checking if resource 'A' is stopped before deletion. "
6256 "Deleting unstopped resources may result in orphaned resources "
6257 "being present in the cluster."
6258 ),
6259 reports.StoppedResourcesBeforeDeleteCheckSkipped(["A"]),
6260 )
6261
6262 def test_multiple_resources(self):
6263 self.assert_message_from_report(
6264 (
6265 "Not checking if resources 'A', 'B' are stopped before "
6266 "deletion. Deleting unstopped resources may result in orphaned "
6267 "resources being present in the cluster."
6268 ),
6269 reports.StoppedResourcesBeforeDeleteCheckSkipped(["A", "B"]),
6270 )
6271
6272 def test_with_reason(self):
6273 self.assert_message_from_report(
6274 (
6275 "Not checking if resource 'A' is stopped before deletion "
6276 "because the command does not run on a live cluster. Deleting "
6277 "unstopped resources may result in orphaned resources being "
6278 "present in the cluster."
6279 ),
6280 reports.StoppedResourcesBeforeDeleteCheckSkipped(
6281 ["A"], reports.const.REASON_NOT_LIVE_CIB
6282 ),
6283 )
6284
6285
6286 class CannotRemoveResourcesNotStopped(NameBuildTest):
6287 def test_one_resource(self) -> str:
6288 self.assert_message_from_report(
6289 (
6290 "Resource 'resourceId' is not stopped, removing unstopped "
6291 "resources can lead to orphaned resources being present in the "
6292 "cluster."
6293 ),
6294 reports.CannotRemoveResourcesNotStopped(["resourceId"]),
6295 )
6296
6297 def test_multiple_resources(self) -> str:
6298 self.assert_message_from_report(
6299 (
6300 "Resources 'resourceId1', 'resourceId2' are not stopped, "
6301 "removing unstopped resources can lead to orphaned resources "
6302 "being present in the cluster."
6303 ),
6304 reports.CannotRemoveResourcesNotStopped(
6305 ["resourceId1", "resourceId2"]
6306 ),
6307 )
6308
6309
6310 class DlmClusterRenameNeeded(NameBuildTest):
6311 def test_success(self):
6312 self.assert_message_from_report(
6313 (
6314 "The DLM cluster name in the shared volume groups metadata "
6315 "must be updated to reflect the name of the cluster so that "
6316 "the volume groups can start"
6317 ),
6318 reports.DlmClusterRenameNeeded(),
6319 )
6320
6321
6322 class Gfs2LockTableRenameNeeded(NameBuildTest):
6323 def test_success(self):
6324 self.assert_message_from_report(
6325 (
6326 "The lock table name on each GFS2 filesystem must be updated "
6327 "to reflect the name of the cluster so that the filesystems "
6328 "can be mounted"
6329 ),
6330 reports.Gfs2LockTableRenameNeeded(),
6331 )
6332
6333
6334 class CibClusterNameRemovalStarted(NameBuildTest):
6335 def test_success(self):
6336 self.assert_message_from_report(
6337 "Removing CIB cluster name property on nodes...",
6338 reports.CibClusterNameRemovalStarted(),
6339 )
6340
6341
6342 class CibClusterNameRemoved(NameBuildTest):
6343 def test_success(self):
6344 self.assert_message_from_report(
6345 "node: Succeeded", reports.CibClusterNameRemoved("node")
6346 )
6347
6348
6349 class CibClusterNameRemovalFailed(NameBuildTest):
6350 def test_success(self):
6351 self.assert_message_from_report(
6352 "CIB cluster name property removal failed: reason",
6353 reports.CibClusterNameRemovalFailed("reason"),
6354 )
6355
6356
6357 class PacemakerRunning(NameBuildTest):
6358 def test_success(self):
6359 self.assert_message_from_report(
6360 "Pacemaker is running", reports.PacemakerRunning()
6361 )
6362
6363
6364 class CibXmlMissing(NameBuildTest):
6365 def test_success(self):
6366 self.assert_message_from_report(
6367 "CIB XML file cannot be found", reports.CibXmlMissing()
6368 )
6369
6370
6371 class CibNodeRenameElementUpdated(NameBuildTest):
6372 def test_location_constraint(self):
6373 self.assert_message_from_report(
6374 "Location constraint 'loc-1': node updated from 'node1' to 'node2'",
6375 reports.CibNodeRenameElementUpdated(
6376 "Location constraint", "loc-1", "node", "node1", "node2"
6377 ),
6378 )
6379
6380 def test_rule_expression(self):
6381 self.assert_message_from_report(
6382 "Rule 'rule-1': #uname expression updated from 'node1' to 'node2'",
6383 reports.CibNodeRenameElementUpdated(
6384 "Rule", "rule-1", "#uname expression", "node1", "node2"
6385 ),
6386 )
6387
6388 def test_fencing_level(self):
6389 self.assert_message_from_report(
6390 "Fencing level '1': target updated from 'node1' to 'node2'",
6391 reports.CibNodeRenameElementUpdated(
6392 "Fencing level", "1", "target", "node1", "node2"
6393 ),
6394 )
6395
6396 def test_fence_device(self):
6397 self.assert_message_from_report(
6398 "Fence device 'fence_xvm': attribute 'pcmk_host_list' "
6399 "updated from 'node1,node2' to 'node3,node2'",
6400 reports.CibNodeRenameElementUpdated(
6401 "Fence device",
6402 "fence_xvm",
6403 "attribute 'pcmk_host_list'",
6404 "node1,node2",
6405 "node3,node2",
6406 ),
6407 )
6408
6409
6410 class CibNodeRenameFencingLevelPatternExists(NameBuildTest):
6411 def test_success(self):
6412 self.assert_message_from_report(
6413 "Fencing level '1' uses target-pattern 'node.*', "
6414 "which may match the renamed node, check the pattern and adjust the"
6415 " configuration if necessary",
6416 reports.CibNodeRenameFencingLevelPatternExists("1", "node.*"),
6417 )
6418
6419
6420 class CibNodeRenameAclsExist(NameBuildTest):
6421 def test_success(self):
6422 self.assert_message_from_report(
6423 "ACL rules exist in CIB and may contain references to node "
6424 "names, check the ACL configuration and adjust it if necessary",
6425 reports.CibNodeRenameAclsExist(),
6426 )
6427
6428
6429 class CibNodeRenameOldNodeInCorosync(NameBuildTest):
6430 def test_success(self):
6431 self.assert_message_from_report(
6432 "Node 'old_name' is still known to corosync, "
6433 "the node may not have been renamed in corosync.conf yet",
6434 reports.CibNodeRenameOldNodeInCorosync(
6435 old_name="old_name",
6436 ),
6437 )
6438
6439
6440 class CibNodeRenameNewNodeNotInCorosync(NameBuildTest):
6441 def test_success(self):
6442 self.assert_message_from_report(
6443 "Node 'new_name' is not known to corosync, "
6444 "the node name may be incorrect",
6445 reports.CibNodeRenameNewNodeNotInCorosync(
6446 new_name="new_name",
6447 ),
6448 )
6449
6450
6451 class CibNodeRenameNoChange(NameBuildTest):
6452 def test_success(self):
6453 self.assert_message_from_report(
6454 "No CIB configuration changes needed for node rename",
6455 reports.CibNodeRenameNoChange(),
6456 )
6457
6458
6459 class ConfiguredResourceMissingInStatus(NameBuildTest):
6460 def test_only_resource_id(self):
6461 self.assert_message_from_report(
6462 (
6463 "Cannot check if the resource 'id' is in expected state, "
6464 "since the resource is missing in cluster status"
6465 ),
6466 reports.ConfiguredResourceMissingInStatus("id"),
6467 )
6468
6469 def test_with_expected_state(self):
6470 self.assert_message_from_report(
6471 (
6472 "Cannot check if the resource 'id' is in expected state "
6473 "(stopped), since the resource is missing in cluster status"
6474 ),
6475 reports.ConfiguredResourceMissingInStatus(
6476 "id", ResourceState.STOPPED
6477 ),
6478 )
6479
6480
6481 class NoStonithMeansWouldBeLeft(NameBuildTest):
6482 def test_success(self):
6483 self.assert_message_from_report(
6484 (
6485 "Requested action leaves the cluster with no enabled means "
6486 "to fence nodes, resulting in the cluster not being able to "
6487 "recover from certain failure conditions"
6488 ),
6489 reports.NoStonithMeansWouldBeLeft(),
6490 )
6491
6492
6493 class NoStonithMeansWouldBeLeftDueToProperties(NameBuildTest):
6494 def test_success(self):
6495 self.assert_message_from_report(
6496 (
6497 "Setting property stonith-enabled to false or fencing-enabled"
6498 " to 0 leaves the cluster with no enabled means to fence nodes,"
6499 " resulting in the cluster not being able to recover from"
6500 " certain failure conditions"
6501 ),
6502 reports.NoStonithMeansWouldBeLeftDueToProperties(
6503 {"stonith-enabled": "false", "fencing-enabled": "0"}
6504 ),
6505 )
6506
6507
6508 class ParseErrorInvalidFileStructure(NameBuildTest):
6509 def test_no_path(self):
6510 self.assert_message_from_report(
6511 "Unable to parse known-hosts file: reason",
6512 reports.ParseErrorInvalidFileStructure(
6513 "reason", file_type_codes.PCS_KNOWN_HOSTS, None
6514 ),
6515 )
6516
6517 def test_path(self):
6518 self.assert_message_from_report(
6519 "Unable to parse known-hosts file '/foo/bar': reason",
6520 reports.ParseErrorInvalidFileStructure(
6521 "reason", file_type_codes.PCS_KNOWN_HOSTS, "/foo/bar"
6522 ),
6523 )
6524
6525
6526 class NodeReportsUnexpectedClusterName(NameBuildTest):
6527 def test_success(self):
6528 self.assert_message_from_report(
6529 "The node is not in the cluster named 'name'",
6530 reports.NodeReportsUnexpectedClusterName("name"),
6531 )
6532
6533
6534 class PcsCfgsyncSendingConfigsToNodes(NameBuildTest):
6535 def test_one_node(self):
6536 self.assert_message_from_report(
6537 "Sending file 'known-hosts' to node 'node1'",
6538 reports.PcsCfgsyncSendingConfigsToNodes(
6539 [file_type_codes.PCS_KNOWN_HOSTS], ["node1"]
6540 ),
6541 )
6542
6543 def test_multiple_nodes(self):
6544 self.assert_message_from_report(
6545 "Sending file 'known-hosts' to nodes 'node1', 'node2'",
6546 reports.PcsCfgsyncSendingConfigsToNodes(
6547 [file_type_codes.PCS_KNOWN_HOSTS], ["node1", "node2"]
6548 ),
6549 )
6550
6551 def test_multiple_files(self):
6552 self.assert_message_from_report(
6553 "Sending files 'known-hosts', 'pcs configuration' to node 'node1'",
6554 reports.PcsCfgsyncSendingConfigsToNodes(
6555 [
6556 file_type_codes.PCS_KNOWN_HOSTS,
6557 file_type_codes.PCS_SETTINGS_CONF,
6558 ],
6559 ["node1"],
6560 ),
6561 )
6562
6563
6564 class PcsCfgsyncSendingConfigsToNodesFailed(NameBuildTest):
6565 def test_one_node(self):
6566 self.assert_message_from_report(
6567 "Unable to save file 'known-hosts' on node 'node1'",
6568 reports.PcsCfgsyncSendingConfigsToNodesFailed(
6569 [file_type_codes.PCS_KNOWN_HOSTS], ["node1"]
6570 ),
6571 )
6572
6573 def test_multiple_nodes(self):
6574 self.assert_message_from_report(
6575 "Unable to save file 'known-hosts' on nodes 'node1', 'node2'",
6576 reports.PcsCfgsyncSendingConfigsToNodesFailed(
6577 [file_type_codes.PCS_KNOWN_HOSTS], ["node1", "node2"]
6578 ),
6579 )
6580
6581 def test_multiple_files(self):
6582 self.assert_message_from_report(
6583 "Unable to save files 'known-hosts', 'pcs configuration' on node 'node1'",
6584 reports.PcsCfgsyncSendingConfigsToNodesFailed(
6585 [
6586 file_type_codes.PCS_KNOWN_HOSTS,
6587 file_type_codes.PCS_SETTINGS_CONF,
6588 ],
6589 ["node1"],
6590 ),
6591 )
6592
6593
6594 class PcsCfgsyncConfigAccepted(NameBuildTest):
6595 def test_success(self):
6596 self.assert_message_from_report(
6597 "The known-hosts file saved successfully",
6598 reports.PcsCfgsyncConfigAccepted(file_type_codes.PCS_KNOWN_HOSTS),
6599 )
6600
6601
6602 class PcsCfgsyncConfigRejected(NameBuildTest):
6603 def test_success(self):
6604 self.assert_message_from_report(
6605 (
6606 "The known-hosts file not saved, a newer version of the file "
6607 "exists on the node"
6608 ),
6609 reports.PcsCfgsyncConfigRejected(file_type_codes.PCS_KNOWN_HOSTS),
6610 )
6611
6612
6613 class PcsCfgsyncConfigSaveError(NameBuildTest):
6614 def test_success(self):
6615 self.assert_message_from_report(
6616 "The known-hosts file not saved",
6617 reports.PcsCfgsyncConfigSaveError(file_type_codes.PCS_KNOWN_HOSTS),
6618 )
6619
6620
6621 class PcsCfgsyncConfigUnsupported(NameBuildTest):
6622 def test_success(self):
6623 self.assert_message_from_report(
6624 (
6625 "The known-hosts file synchronization is not supported on this "
6626 "node"
6627 ),
6628 reports.PcsCfgsyncConfigUnsupported(
6629 file_type_codes.PCS_KNOWN_HOSTS
6630 ),
6631 )
6632
6633
6634 class PcsCfgsyncFetchingNewestConfig(NameBuildTest):
6635 def test_one_node(self):
6636 self.assert_message_from_report(
6637 (
6638 "Fetching the newest version of file 'known-hosts' from node "
6639 "'node1'"
6640 ),
6641 reports.PcsCfgsyncFetchingNewestConfig(
6642 [file_type_codes.PCS_KNOWN_HOSTS], ["node1"]
6643 ),
6644 )
6645
6646 def test_multiple_nodes(self):
6647 self.assert_message_from_report(
6648 (
6649 "Fetching the newest version of file 'known-hosts' from nodes "
6650 "'node1', 'node2'"
6651 ),
6652 reports.PcsCfgsyncFetchingNewestConfig(
6653 [file_type_codes.PCS_KNOWN_HOSTS], ["node1", "node2"]
6654 ),
6655 )
6656
6657 def test_multiple_files(self):
6658 self.assert_message_from_report(
6659 (
6660 "Fetching the newest version of files 'known-hosts', "
6661 "'pcs configuration' from node 'node1'"
6662 ),
6663 reports.PcsCfgsyncFetchingNewestConfig(
6664 [
6665 file_type_codes.PCS_KNOWN_HOSTS,
6666 file_type_codes.PCS_SETTINGS_CONF,
6667 ],
6668 ["node1"],
6669 ),
6670 )
6671
6672
6673 class PcsCfgsyncConflictRepeatAction(NameBuildTest):
6674 def test_success(self):
6675 self.assert_message_from_report(
6676 (
6677 "Configuration conflict detected. Some nodes had a newer "
6678 "configuration than the local node. Local node's configuration "
6679 "was updated. Please repeat the last action if appropriate."
6680 ),
6681 reports.PcsCfgsyncConflictRepeatAction(),
6682 )
6683
6684
6685 class MetaAttrsUnknownToPcmk(NameBuildTest):
6686 def test_single_option(self):
6687 self.assert_message_from_report(
6688 (
6689 "Resource meta attribute 'unknown' has no effect on cluster "
6690 "resource handling, meta attribute with effect: 'known'"
6691 ),
6692 reports.MetaAttrsUnknownToPcmk(
6693 ["unknown"], ["known"], ["primitive-meta"]
6694 ),
6695 )
6696
6697 def test_multiple_options(self):
6698 self.assert_message_from_report(
6699 (
6700 "Resource / stonith meta attributes 'unknown1', 'unknown2' "
6701 "have no effect on cluster resource handling, meta attributes "
6702 "with effect: 'known1', 'known2'"
6703 ),
6704 reports.MetaAttrsUnknownToPcmk(
6705 ["unknown1", "unknown2"],
6706 ["known1", "known2"],
6707 ["primitive-meta", "stonith-meta"],
6708 ),
6709 )
6710
6711
6712 class MetaAttrsNotValidatedUnsupportedType(NameBuildTest):
6713 def test_empty_options(self):
6714 self.assert_message_from_report(
6715 "Meta attributes are not validated",
6716 reports.MetaAttrsNotValidatedUnsupportedType([]),
6717 )
6718
6719 def test_single_option(self):
6720 self.assert_message_from_report(
6721 "Meta attributes of clone are not validated",
6722 reports.MetaAttrsNotValidatedUnsupportedType(["clone"]),
6723 )
6724
6725 def test_multiple_options(self):
6726 self.assert_message_from_report(
6727 (
6728 "Meta attributes of bundle / clone / group / resource are not "
6729 "validated"
6730 ),
6731 reports.MetaAttrsNotValidatedUnsupportedType(
6732 ["clone", "bundle", "group", "primitive"]
6733 ),
6734 )
6735
6736
6737 class MetaAttrsNotValidatedLoadingError(NameBuildTest):
6738 def test_success(self):
6739 self.assert_message_from_report(
6740 (
6741 "Meta attribute validation is skipped due to an error loading "
6742 "meta attributes definition."
6743 ),
6744 reports.MetaAttrsNotValidatedLoadingError(),
6745 )
6746
6747
6748 class NodeNotInCluster(NameBuildTest):
6749 def test_success(self):
6750 self.assert_message_from_report(
6751 "The node does not currently have a cluster configured",
6752 reports.NodeNotInCluster(),
6753 )
6754
6755
6756 class ClusterNameAlreadyInUse(NameBuildTest):
6757 def test_success(self):
6758 self.assert_message_from_report(
6759 "The cluster name 'foo' is already used",
6760 reports.ClusterNameAlreadyInUse("foo"),
6761 )
6762
6763
6764 class UnableToGetClusterInfoFromStatus(NameBuildTest):
6765 def test_success(self):
6766 self.assert_message_from_report(
6767 "Unable to retrieve cluster information from node status",
6768 reports.UnableToGetClusterInfoFromStatus(),
6769 )
6770
6771
6772 class UnableToGetClusterKnownHosts(NameBuildTest):
6773 def test_success(self):
6774 self.assert_message_from_report(
6775 "Unable to get known hosts from cluster 'foo'",
6776 reports.UnableToGetClusterKnownHosts("foo"),
6777 )
6778
6779
6780 class CibResourceSecretUnableToGet(NameBuildTest):
6781 def test_success(self):
6782 self.assert_message_from_report(
6783 "Unable to get secret 'secret_name' for resource 'resource_id'",
6784 reports.CibResourceSecretUnableToGet(
|
(1) Event Sigma main event: |
A secret, such as a password, cryptographic key, or token is stored in plaintext directly in the source code, in an application's properties, or configuration file. Users with access to the secret may then use the secret to access resources that they otherwise would not have access to. Secret type: Secret (generic). |
|
(2) Event remediation: |
Avoid setting sensitive configuration values as string literals. Instead, these values should be set using variables with the sensitive data loaded from an encrypted file or a secret store. |
6785 "resource_id", "secret_name", "reason"
6786 ),
6787 )
6788