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