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