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 pcsd SSL certificate '/ssl/cert/path': Failed",
3657 reports.FileIoError(
3658 file_type_codes.PCSD_SSL_CERT,
3659 RawFileError.ACTION_READ,
3660 "Failed",
3661 file_path="/ssl/cert/path",
3662 ),
3663 )
3664
3665 def test_role_translation_a(self):
3666 self.assert_message_from_report(
3667 "Unable to write pcsd SSL key '/ssl/key/path': Failed",
3668 reports.FileIoError(
3669 file_type_codes.PCSD_SSL_KEY,
3670 RawFileError.ACTION_WRITE,
3671 "Failed",
3672 file_path="/ssl/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 PcsdSslCertAndKeyDistributionStarted(NameBuildTest):
4370 def test_multiple_nodes(self):
4371 self.assert_message_from_report(
4372 "Synchronizing pcsd SSL certificates on node(s) 'node1', 'node2', "
4373 "'node3'...",
4374 reports.PcsdSslCertAndKeyDistributionStarted(
4375 ["node1", "node3", "node2"]
4376 ),
4377 )
4378
4379 def test_single_node(self):
4380 self.assert_message_from_report(
4381 "Synchronizing pcsd SSL certificates on node(s) 'node3'...",
4382 reports.PcsdSslCertAndKeyDistributionStarted(["node3"]),
4383 )
4384
4385
4386 class PcsdSslCertAndKeySetSuccess(NameBuildTest):
4387 def test_success(self):
4388 self.assert_message_from_report(
4389 "node1: Success", reports.PcsdSslCertAndKeySetSuccess("node1")
4390 )
4391
4392
4393 class ClusterWillBeDestroyed(NameBuildTest):
4394 def test_all(self):
4395 self.assert_message_from_report(
4396 (
4397 "Some nodes are already in a cluster. Enforcing this will "
4398 "destroy existing cluster on those nodes. You should remove "
4399 "the nodes from their clusters instead to keep the clusters "
4400 "working properly"
4401 ),
4402 reports.ClusterWillBeDestroyed(),
4403 )
4404
4405
4406 class ClusterSetupSuccess(NameBuildTest):
4407 def test_all(self):
4408 self.assert_message_from_report(
4409 "Cluster has been successfully set up.",
4410 reports.ClusterSetupSuccess(),
4411 )
4412
4413
4414 class UsingDefaultAddressForHost(NameBuildTest):
4415 def test_success(self):
4416 self.assert_message_from_report(
4417 "No addresses specified for host 'node-name', using 'node-addr'",
4418 reports.UsingDefaultAddressForHost(
4419 "node-name",
4420 "node-addr",
4421 const.DEFAULT_ADDRESS_SOURCE_KNOWN_HOSTS,
4422 ),
4423 )
4424
4425
4426 class ResourceInBundleNotAccessible(NameBuildTest):
4427 def test_success(self):
4428 self.assert_message_from_report(
4429 (
4430 "Resource 'resourceA' will not be accessible by the cluster "
4431 "inside bundle 'bundleA', at least one of bundle options "
4432 "'control-port' or 'ip-range-start' has to be specified"
4433 ),
4434 reports.ResourceInBundleNotAccessible("bundleA", "resourceA"),
4435 )
4436
4437
4438 class UsingDefaultWatchdog(NameBuildTest):
4439 def test_success(self):
4440 self.assert_message_from_report(
4441 (
4442 "No watchdog has been specified for node 'node1'. Using "
4443 "default watchdog '/dev/watchdog'"
4444 ),
4445 reports.UsingDefaultWatchdog("/dev/watchdog", "node1"),
4446 )
4447
4448
4449 class CannotRemoveAllClusterNodes(NameBuildTest):
4450 def test_success(self):
4451 self.assert_message_from_report(
4452 "No nodes would be left in the cluster",
4453 reports.CannotRemoveAllClusterNodes(),
4454 )
4455
4456
4457 class UnableToConnectToAnyRemainingNode(NameBuildTest):
4458 def test_all(self):
4459 self.assert_message_from_report(
4460 "Unable to connect to any remaining cluster node",
4461 reports.UnableToConnectToAnyRemainingNode(),
4462 )
4463
4464
4465 class UnableToConnectToAllRemainingNodes(NameBuildTest):
4466 def test_single_node(self):
4467 self.assert_message_from_report(
4468 ("Remaining cluster node 'node1' could not be reached"),
4469 reports.UnableToConnectToAllRemainingNodes(["node1"]),
4470 )
4471
4472 def test_multiple_nodes(self):
4473 self.assert_message_from_report(
4474 (
4475 "Remaining cluster nodes 'node0', 'node1', 'node2' could not "
4476 "be reached"
4477 ),
4478 reports.UnableToConnectToAllRemainingNodes(
4479 ["node1", "node0", "node2"]
4480 ),
4481 )
4482
4483
4484 class NodesToRemoveUnreachable(NameBuildTest):
4485 def test_single_node(self):
4486 self.assert_message_from_report(
4487 (
4488 "Removed node 'node0' could not be reached and subsequently "
4489 "deconfigured"
4490 ),
4491 reports.NodesToRemoveUnreachable(["node0"]),
4492 )
4493
4494 def test_multiple_nodes(self):
4495 self.assert_message_from_report(
4496 (
4497 "Removed nodes 'node0', 'node1', 'node2' could not be reached "
4498 "and subsequently deconfigured"
4499 ),
4500 reports.NodesToRemoveUnreachable(["node1", "node0", "node2"]),
4501 )
4502
4503
4504 class NodeUsedAsTieBreaker(NameBuildTest):
4505 def test_success(self):
4506 self.assert_message_from_report(
4507 (
4508 "Node 'node2' with id '2' is used as a tie breaker for a "
4509 "qdevice and therefore cannot be removed"
4510 ),
4511 reports.NodeUsedAsTieBreaker("node2", 2),
4512 )
4513
4514
4515 class CorosyncQuorumWillBeLost(NameBuildTest):
4516 def test_all(self):
4517 self.assert_message_from_report(
4518 "This action will cause a loss of the quorum",
4519 reports.CorosyncQuorumWillBeLost(),
4520 )
4521
4522
4523 class CorosyncQuorumLossUnableToCheck(NameBuildTest):
4524 def test_all(self):
4525 self.assert_message_from_report(
4526 (
4527 "Unable to determine whether this action will cause "
4528 "a loss of the quorum"
4529 ),
4530 reports.CorosyncQuorumLossUnableToCheck(),
4531 )
4532
4533
4534 class CorosyncConfigDisablingEncryptionDeprecated(NameBuildTest):
4535 def test_success(self):
4536 self.assert_message_from_report(
4537 (
4538 "Disabling cluster traffic encryption is deprecated and will "
4539 "not be possible in a future version"
4540 ),
4541 reports.CorosyncConfigDisablingEncryptionDeprecated(),
4542 )
4543
4544
4545 class SbdListWatchdogError(NameBuildTest):
4546 def test_success(self):
4547 self.assert_message_from_report(
4548 "Unable to query available watchdogs from sbd: this is a reason",
4549 reports.SbdListWatchdogError("this is a reason"),
4550 )
4551
4552
4553 class SbdWatchdogNotSupported(NameBuildTest):
4554 def test_success(self):
4555 self.assert_message_from_report(
4556 (
4557 "node1: Watchdog '/dev/watchdog' is not supported (it may be a "
4558 "software watchdog)"
4559 ),
4560 reports.SbdWatchdogNotSupported("node1", "/dev/watchdog"),
4561 )
4562
4563
4564 class SbdWatchdogValidationInactive(NameBuildTest):
4565 def test_all(self):
4566 self.assert_message_from_report(
4567 "Not validating the watchdog",
4568 reports.SbdWatchdogValidationInactive(),
4569 )
4570
4571
4572 class SbdWatchdogTestError(NameBuildTest):
4573 def test_success(self):
4574 self.assert_message_from_report(
4575 "Unable to initialize test of the watchdog: some reason",
4576 reports.SbdWatchdogTestError("some reason"),
4577 )
4578
4579
4580 class SbdWatchdogTestMultipleDevices(NameBuildTest):
4581 def test_all(self):
4582 self.assert_message_from_report(
4583 (
4584 "Multiple watchdog devices available, therefore, watchdog "
4585 "which should be tested has to be specified."
4586 ),
4587 reports.SbdWatchdogTestMultipleDevices(),
4588 )
4589
4590
4591 class SbdWatchdogTestFailed(NameBuildTest):
4592 def test_all(self):
4593 self.assert_message_from_report(
4594 "System should have been reset already",
4595 reports.SbdWatchdogTestFailed(),
4596 )
4597
4598
4599 class SystemWillReset(NameBuildTest):
4600 def test_all(self):
4601 self.assert_message_from_report(
4602 "System will reset shortly", reports.SystemWillReset()
4603 )
4604
4605
4606 class ResourceBundleUnsupportedContainerType(NameBuildTest):
4607 def test_single_type(self):
4608 self.assert_message_from_report(
4609 (
4610 "Bundle 'bundle id' uses unsupported container type, therefore "
4611 "it is not possible to set its container options. Supported "
4612 "container types are: 'b'"
4613 ),
4614 reports.ResourceBundleUnsupportedContainerType("bundle id", ["b"]),
4615 )
4616
4617 def test_multiple_types(self):
4618 self.assert_message_from_report(
4619 (
4620 "Bundle 'bundle id' uses unsupported container type, therefore "
4621 "it is not possible to set its container options. Supported "
4622 "container types are: 'a', 'b', 'c'"
4623 ),
4624 reports.ResourceBundleUnsupportedContainerType(
4625 "bundle id", ["b", "a", "c"]
4626 ),
4627 )
4628
4629 def test_no_update(self):
4630 self.assert_message_from_report(
4631 (
4632 "Bundle 'bundle id' uses unsupported container type. Supported "
4633 "container types are: 'a', 'b', 'c'"
4634 ),
4635 reports.ResourceBundleUnsupportedContainerType(
4636 "bundle id", ["b", "a", "c"], updating_options=False
4637 ),
4638 )
4639
4640
4641 class FenceHistoryCommandError(NameBuildTest):
4642 def test_success(self):
4643 self.assert_message_from_report(
4644 "Unable to show fence history: reason",
4645 reports.FenceHistoryCommandError(
4646 "reason", reports.const.FENCE_HISTORY_COMMAND_SHOW
4647 ),
4648 )
4649
4650
4651 class FenceHistoryNotSupported(NameBuildTest):
4652 def test_success(self):
4653 self.assert_message_from_report(
4654 "Fence history is not supported, please upgrade pacemaker",
4655 reports.FenceHistoryNotSupported(),
4656 )
4657
4658
4659 class ResourceInstanceAttrValueNotUnique(NameBuildTest):
4660 def test_one_resource(self):
4661 self.assert_message_from_report(
4662 (
4663 "Value 'val' of option 'attr' is not unique across 'agent' "
4664 "resources. Following resources are configured with the same "
4665 "value of the instance attribute: 'A'"
4666 ),
4667 reports.ResourceInstanceAttrValueNotUnique(
4668 "attr", "val", "agent", ["A"]
4669 ),
4670 )
4671
4672 def test_multiple_resources(self):
4673 self.assert_message_from_report(
4674 (
4675 "Value 'val' of option 'attr' is not unique across 'agent' "
4676 "resources. Following resources are configured with the same "
4677 "value of the instance attribute: 'A', 'B', 'C'"
4678 ),
4679 reports.ResourceInstanceAttrValueNotUnique(
4680 "attr", "val", "agent", ["B", "C", "A"]
4681 ),
4682 )
4683
4684
4685 class ResourceInstanceAttrGroupValueNotUnique(NameBuildTest):
4686 def test_message(self):
4687 self.assert_message_from_report(
4688 (
4689 "Value '127.0.0.1', '12345' of options 'ip', 'port' (group "
4690 "'address') is not unique across 'agent' resources. Following "
4691 "resources are configured with the same values of the instance "
4692 "attributes: 'A', 'B'"
4693 ),
4694 reports.ResourceInstanceAttrGroupValueNotUnique(
4695 "address",
4696 {
4697 "port": "12345",
4698 "ip": "127.0.0.1",
4699 },
4700 "agent",
4701 ["B", "A"],
4702 ),
4703 )
4704
4705
4706 class CannotLeaveGroupEmptyAfterMove(NameBuildTest):
4707 def test_single_resource(self):
4708 self.assert_message_from_report(
4709 "Unable to move resource 'R' as it would leave group 'gr1' empty.",
4710 reports.CannotLeaveGroupEmptyAfterMove("gr1", ["R"]),
4711 )
4712
4713 def test_multiple_resources(self):
4714 self.assert_message_from_report(
4715 "Unable to move resources 'R1', 'R2', 'R3' as it would leave "
4716 "group 'gr1' empty.",
4717 reports.CannotLeaveGroupEmptyAfterMove("gr1", ["R3", "R1", "R2"]),
4718 )
4719
4720
4721 class CannotMoveResourceBundleInner(NameBuildTest):
4722 def test_success(self):
4723 self.assert_message_from_report(
4724 (
4725 "Resources cannot be moved out of their bundles. If you want "
4726 "to move a bundle, use the bundle id (B)"
4727 ),
4728 reports.CannotMoveResourceBundleInner("R", "B"),
4729 )
4730
4731
4732 class CannotMoveResourceCloneInner(NameBuildTest):
4733 def test_success(self):
4734 self.assert_message_from_report(
4735 "to move clone resources you must use the clone id (C)",
4736 reports.CannotMoveResourceCloneInner("R", "C"),
4737 )
4738
4739
4740 class CannotMoveResourceMultipleInstances(NameBuildTest):
4741 def test_success(self):
4742 self.assert_message_from_report(
4743 (
4744 "more than one instance of resource 'R' is running, "
4745 "thus the resource cannot be moved"
4746 ),
4747 reports.CannotMoveResourceMultipleInstances("R"),
4748 )
4749
4750
4751 class CannotMoveResourceMultipleInstancesNoNodeSpecified(NameBuildTest):
4752 def test_success(self):
4753 self.assert_message_from_report(
4754 (
4755 "more than one instance of resource 'R' is running, "
4756 "thus the resource cannot be moved, "
4757 "unless a destination node is specified"
4758 ),
4759 reports.CannotMoveResourceMultipleInstancesNoNodeSpecified("R"),
4760 )
4761
4762
4763 class CannotMoveResourcePromotableInner(NameBuildTest):
4764 def test_success(self):
4765 self.assert_message_from_report(
4766 (
4767 "to move promotable clone resources you must use "
4768 "the promotable clone id (P)"
4769 ),
4770 reports.CannotMoveResourcePromotableInner("R", "P"),
4771 )
4772
4773
4774 class CannotMoveResourceMasterResourceNotPromotable(NameBuildTest):
4775 def test_without_promotable(self):
4776 self.assert_message_from_report(
4777 "when specifying promoted you must use the promotable clone id",
4778 reports.CannotMoveResourceMasterResourceNotPromotable("R"),
4779 )
4780
4781 def test_with_promotable(self):
4782 self.assert_message_from_report(
4783 "when specifying promoted you must use the promotable clone id (P)",
4784 reports.CannotMoveResourceMasterResourceNotPromotable(
4785 "R", promotable_id="P"
4786 ),
4787 )
4788
4789
4790 class CannotMoveResourceNotRunning(NameBuildTest):
4791 def test_success(self):
4792 self.assert_message_from_report(
4793 (
4794 "It is not possible to move resource 'R' as it is not running "
4795 "at the moment"
4796 ),
4797 reports.CannotMoveResourceNotRunning("R"),
4798 )
4799
4800
4801 class CannotMoveResourceStoppedNoNodeSpecified(NameBuildTest):
4802 def test_success(self):
4803 self.assert_message_from_report(
4804 "You must specify a node when moving/banning a stopped resource",
4805 reports.CannotMoveResourceStoppedNoNodeSpecified("R"),
4806 )
4807
4808
4809 class ResourceMovePcmkError(NameBuildTest):
4810 def test_success(self):
4811 self.assert_message_from_report(
4812 "cannot move resource 'R'\nstdout1\n stdout2\nstderr1\n stderr2",
4813 reports.ResourceMovePcmkError(
4814 "R", "stdout1\n\n stdout2\n", "stderr1\n\n stderr2\n"
4815 ),
4816 )
4817
4818
4819 class ResourceMovePcmkSuccess(NameBuildTest):
4820 def test_success(self):
4821 self.assert_message_from_report(
4822 "stdout1\n stdout2\nstderr1\n stderr2",
4823 reports.ResourceMovePcmkSuccess(
4824 "R", "stdout1\n\n stdout2\n", "stderr1\n\n stderr2\n"
4825 ),
4826 )
4827
4828 def test_translate(self):
4829 self.assert_message_from_report(
4830 (
4831 "Warning: Creating location constraint "
4832 "'cli-ban-dummy-on-node1' with a score of -INFINITY "
4833 "for resource dummy on node1.\n"
4834 " This will prevent dummy from running on node1 until the "
4835 "constraint is removed\n"
4836 " This will be the case even if node1 is the last node in "
4837 "the cluster"
4838 ),
4839 reports.ResourceMovePcmkSuccess(
4840 "dummy",
4841 "",
4842 (
4843 "WARNING: Creating rsc_location constraint "
4844 "'cli-ban-dummy-on-node1' with a score of -INFINITY "
4845 "for resource dummy on node1.\n"
4846 " This will prevent dummy from running on node1 until "
4847 "the constraint is removed using the clear option or "
4848 "by editing the CIB with an appropriate tool\n"
4849 " This will be the case even if node1 is the last node "
4850 "in the cluster\n"
4851 ),
4852 ),
4853 )
4854
4855
4856 class CannotBanResourceBundleInner(NameBuildTest):
4857 def test_success(self):
4858 self.assert_message_from_report(
4859 (
4860 "Resource 'R' is in a bundle and cannot be banned. If you want "
4861 "to ban the bundle, use the bundle id (B)"
4862 ),
4863 reports.CannotBanResourceBundleInner("R", "B"),
4864 )
4865
4866
4867 class CannotBanResourceMasterResourceNotPromotable(NameBuildTest):
4868 def test_without_promotable(self):
4869 self.assert_message_from_report(
4870 "when specifying promoted you must use the promotable clone id",
4871 reports.CannotBanResourceMasterResourceNotPromotable("R"),
4872 )
4873
4874 def test_with_promotable(self):
4875 self.assert_message_from_report(
4876 "when specifying promoted you must use the promotable clone id (P)",
4877 reports.CannotBanResourceMasterResourceNotPromotable(
4878 "R", promotable_id="P"
4879 ),
4880 )
4881
4882
4883 class CannotBanResourceMultipleInstancesNoNodeSpecified(NameBuildTest):
4884 def test_success(self):
4885 self.assert_message_from_report(
4886 (
4887 "more than one instance of resource 'R' is running, "
4888 "thus the resource cannot be banned, "
4889 "unless a destination node is specified"
4890 ),
4891 reports.CannotBanResourceMultipleInstancesNoNodeSpecified("R"),
4892 )
4893
4894
4895 class CannotBanResourceStoppedNoNodeSpecified(NameBuildTest):
4896 def test_success(self):
4897 self.assert_message_from_report(
4898 "You must specify a node when moving/banning a stopped resource",
4899 reports.CannotBanResourceStoppedNoNodeSpecified("R"),
4900 )
4901
4902
4903 class ResourceBanPcmkError(NameBuildTest):
4904 def test_success(self):
4905 self.assert_message_from_report(
4906 "cannot ban resource 'R'\nstdout1\n stdout2\nstderr1\n stderr2",
4907 reports.ResourceBanPcmkError(
4908 "R", "stdout1\n\n stdout2\n", "stderr1\n\n stderr2\n"
4909 ),
4910 )
4911
4912
4913 class ResourceBanPcmkSuccess(NameBuildTest):
4914 def test_success(self):
4915 self.assert_message_from_report(
4916 "stdout1\n stdout2\nstderr1\n stderr2",
4917 reports.ResourceBanPcmkSuccess(
4918 "R", "stdout1\n\n stdout2\n", "stderr1\n\n stderr2\n"
4919 ),
4920 )
4921
4922 def test_translate(self):
4923 self.assert_message_from_report(
4924 (
4925 "Warning: Creating location constraint "
4926 "'cli-ban-dummy-on-node1' with a score of -INFINITY "
4927 "for resource dummy on node1.\n"
4928 " This will prevent dummy from running on node1 until the "
4929 "constraint is removed\n"
4930 " This will be the case even if node1 is the last node in "
4931 "the cluster"
4932 ),
4933 reports.ResourceBanPcmkSuccess(
4934 "dummy",
4935 "",
4936 (
4937 "WARNING: Creating rsc_location constraint "
4938 "'cli-ban-dummy-on-node1' with a score of -INFINITY "
4939 "for resource dummy on node1.\n"
4940 " This will prevent dummy from running on node1 until "
4941 "the constraint is removed using the clear option or "
4942 "by editing the CIB with an appropriate tool\n"
4943 " This will be the case even if node1 is the last node "
4944 "in the cluster\n"
4945 ),
4946 ),
4947 )
4948
4949
4950 class CannotUnmoveUnbanResourceMasterResourceNotPromotable(NameBuildTest):
4951 def test_without_promotable(self):
4952 self.assert_message_from_report(
4953 "when specifying promoted you must use the promotable clone id",
4954 reports.CannotUnmoveUnbanResourceMasterResourceNotPromotable("R"),
4955 )
4956
4957 def test_with_promotable(self):
4958 self.assert_message_from_report(
4959 "when specifying promoted you must use the promotable clone id (P)",
4960 reports.CannotUnmoveUnbanResourceMasterResourceNotPromotable(
4961 "R", promotable_id="P"
4962 ),
4963 )
4964
4965
4966 class ResourceUnmoveUnbanPcmkExpiredNotSupported(NameBuildTest):
4967 def test_success(self):
4968 self.assert_message_from_report(
4969 "expired is not supported, please upgrade pacemaker",
4970 reports.ResourceUnmoveUnbanPcmkExpiredNotSupported(),
4971 )
4972
4973
4974 class ResourceUnmoveUnbanPcmkError(NameBuildTest):
4975 def test_success(self):
4976 self.assert_message_from_report(
4977 "cannot clear resource 'R'\nstdout1\n stdout2\nstderr1\n stderr2",
4978 reports.ResourceUnmoveUnbanPcmkError(
4979 "R", "stdout1\n\n stdout2\n", "stderr1\n\n stderr2\n"
4980 ),
4981 )
4982
4983
4984 class ResourceUnmoveUnbanPcmkSuccess(NameBuildTest):
4985 def test_success(self):
4986 self.assert_message_from_report(
4987 "stdout1\n stdout2\nstderr1\n stderr2",
4988 reports.ResourceUnmoveUnbanPcmkSuccess(
4989 "R", "stdout1\n\n stdout2\n", "stderr1\n\n stderr2\n"
4990 ),
4991 )
4992
4993
4994 class ResourceMoveConstraintCreated(NameBuildTest):
4995 def test_success(self):
4996 self.assert_message_from_report(
4997 "Location constraint to move resource 'R1' has been created",
4998 reports.ResourceMoveConstraintCreated("R1"),
4999 )
5000
5001
5002 class ResourceMoveConstraintRemoved(NameBuildTest):
5003 def test_success(self):
5004 self.assert_message_from_report(
5005 (
5006 "Location constraint created to move resource 'R1' has "
5007 "been removed"
5008 ),
5009 reports.ResourceMoveConstraintRemoved("R1"),
5010 )
5011
5012
5013 class ResourceMoveNotAffectingResource(NameBuildTest):
5014 def test_success(self):
5015 self.assert_message_from_report(
5016 (
5017 "Unable to move resource 'R1' using a location constraint. "
5018 "Current location of the resource may be affected by some "
5019 "other constraint."
5020 ),
5021 reports.ResourceMoveNotAffectingResource("R1"),
5022 )
5023
5024
5025 class ResourceMoveAffectsOtherResources(NameBuildTest):
5026 def test_multiple(self):
5027 self.assert_message_from_report(
5028 "Moving resource 'R1' affects resources: 'p0', 'p1', 'p2'",
5029 reports.ResourceMoveAffectsOtherResources("R1", ["p2", "p0", "p1"]),
5030 )
5031
5032 def test_single(self):
5033 self.assert_message_from_report(
5034 "Moving resource 'R1' affects resource: 'R2'",
5035 reports.ResourceMoveAffectsOtherResources("R1", ["R2"]),
5036 )
5037
5038
5039 class ResourceMoveAutocleanSimulationFailure(NameBuildTest):
5040 def test_simulation(self):
5041 self.assert_message_from_report(
5042 (
5043 "Unable to ensure that moved resource 'R1' will stay on the "
5044 "same node after a constraint used for moving it is removed."
5045 ),
5046 reports.ResourceMoveAutocleanSimulationFailure(
5047 "R1", others_affected=False
5048 ),
5049 )
5050
5051 def test_simulation_others_affected(self):
5052 self.assert_message_from_report(
5053 (
5054 "Unable to ensure that moved resource 'R1' or other resources "
5055 "will stay on the same node after a constraint used for moving "
5056 "it is removed."
5057 ),
5058 reports.ResourceMoveAutocleanSimulationFailure(
5059 "R1", others_affected=True
5060 ),
5061 )
5062
5063 def test_live(self):
5064 self.assert_message_from_report(
5065 (
5066 "Unable to ensure that moved resource 'R1' will stay on the "
5067 "same node after a constraint used for moving it is removed."
5068 " The constraint to move the resource has not been removed "
5069 "from configuration. Consider removing it manually. Be aware "
5070 "that removing the constraint may cause resources to move to "
5071 "other nodes."
5072 ),
5073 reports.ResourceMoveAutocleanSimulationFailure(
5074 "R1", others_affected=False, move_constraint_left_in_cib=True
5075 ),
5076 )
5077
5078 def test_live_others_affected(self):
5079 self.assert_message_from_report(
5080 (
5081 "Unable to ensure that moved resource 'R1' or other resources "
5082 "will stay on the same node after a constraint used for moving "
5083 "it is removed."
5084 " The constraint to move the resource has not been removed "
5085 "from configuration. Consider removing it manually. Be aware "
5086 "that removing the constraint may cause resources to move to "
5087 "other nodes."
5088 ),
5089 reports.ResourceMoveAutocleanSimulationFailure(
5090 "R1", others_affected=True, move_constraint_left_in_cib=True
5091 ),
5092 )
5093
5094
5095 class ResourceMayOrMayNotMove(NameBuildTest):
5096 def test_build_message(self):
5097 self.assert_message_from_report(
5098 (
5099 "A move constraint has been created and the resource 'id' may "
5100 "or may not move depending on other configuration"
5101 ),
5102 reports.ResourceMayOrMayNotMove("id"),
5103 )
5104
5105
5106 class ParseErrorJsonFile(NameBuildTest):
5107 def test_success(self):
5108 self.assert_message_from_report(
5109 "Unable to parse known-hosts file '/tmp/known-hosts': "
5110 "some reason: line 15 column 5 (char 100)",
5111 reports.ParseErrorJsonFile(
5112 file_type_codes.PCS_KNOWN_HOSTS,
5113 15,
5114 5,
5115 100,
5116 "some reason",
5117 "some reason: line 15 column 5 (char 100)",
5118 file_path="/tmp/known-hosts",
5119 ),
5120 )
5121
5122
5123 class ResourceDisableAffectsOtherResources(NameBuildTest):
5124 def test_multiple_disabled(self):
5125 self.assert_message_from_report(
5126 (
5127 "Disabling specified resource would have an effect on these "
5128 "resources: 'O1', 'O2'"
5129 ),
5130 reports.ResourceDisableAffectsOtherResources(
5131 ["D1"],
5132 ["O2", "O1"],
5133 ),
5134 )
5135
5136 def test_multiple_affected(self):
5137 self.assert_message_from_report(
5138 (
5139 "Disabling specified resources would have an effect on this "
5140 "resource: 'O1'"
5141 ),
5142 reports.ResourceDisableAffectsOtherResources(
5143 ["D2", "D1"],
5144 ["O1"],
5145 ),
5146 )
5147
5148
5149 class DrConfigAlreadyExist(NameBuildTest):
5150 def test_success(self):
5151 self.assert_message_from_report(
5152 "Disaster-recovery already configured",
5153 reports.DrConfigAlreadyExist(),
5154 )
5155
5156
5157 class DrConfigDoesNotExist(NameBuildTest):
5158 def test_success(self):
5159 self.assert_message_from_report(
5160 "Disaster-recovery is not configured",
5161 reports.DrConfigDoesNotExist(),
5162 )
5163
5164
5165 class NodeInLocalCluster(NameBuildTest):
5166 def test_success(self):
5167 self.assert_message_from_report(
5168 "Node 'node-name' is part of local cluster",
5169 reports.NodeInLocalCluster("node-name"),
5170 )
5171
5172
5173 class BoothLackOfSites(NameBuildTest):
5174 def test_no_site(self):
5175 self.assert_message_from_report(
5176 (
5177 "lack of sites for booth configuration (need 2 at least): "
5178 "sites missing"
5179 ),
5180 reports.BoothLackOfSites([]),
5181 )
5182
5183 def test_single_site(self):
5184 self.assert_message_from_report(
5185 (
5186 "lack of sites for booth configuration (need 2 at least): "
5187 "sites 'site1'"
5188 ),
5189 reports.BoothLackOfSites(["site1"]),
5190 )
5191
5192 def test_multiple_sites(self):
5193 self.assert_message_from_report(
5194 (
5195 "lack of sites for booth configuration (need 2 at least): "
5196 "sites 'site1', 'site2'"
5197 ),
5198 reports.BoothLackOfSites(["site1", "site2"]),
5199 )
5200
5201
5202 class BoothEvenPeersNumber(NameBuildTest):
5203 def test_success(self):
5204 self.assert_message_from_report(
5205 "odd number of peers is required (entered 4 peers)",
5206 reports.BoothEvenPeersNumber(4),
5207 )
5208
5209
5210 class BoothAddressDuplication(NameBuildTest):
5211 def test_single_address(self):
5212 self.assert_message_from_report(
5213 "duplicate address for booth configuration: 'addr1'",
5214 reports.BoothAddressDuplication(["addr1"]),
5215 )
5216
5217 def test_multiple_addresses(self):
5218 self.assert_message_from_report(
5219 (
5220 "duplicate address for booth configuration: 'addr1', 'addr2', "
5221 "'addr3'"
5222 ),
5223 reports.BoothAddressDuplication(
5224 sorted(["addr2", "addr1", "addr3"])
5225 ),
5226 )
5227
5228
5229 class BoothConfigUnexpectedLines(NameBuildTest):
5230 def test_single_line(self):
5231 self.assert_message_from_report(
5232 "unexpected line in booth config:\nline",
5233 reports.BoothConfigUnexpectedLines(["line"]),
5234 )
5235
5236 def test_multiple_lines(self):
5237 self.assert_message_from_report(
5238 "unexpected lines in booth config:\nline\nline2",
5239 reports.BoothConfigUnexpectedLines(["line", "line2"]),
5240 )
5241
5242 def test_file_path(self):
5243 self.assert_message_from_report(
5244 "unexpected line in booth config 'PATH':\nline",
5245 reports.BoothConfigUnexpectedLines(["line"], file_path="PATH"),
5246 )
5247
5248
5249 class BoothInvalidName(NameBuildTest):
5250 def test_success(self):
5251 self.assert_message_from_report(
5252 "booth name '/name' is not valid, it cannot contain /{} characters",
5253 reports.BoothInvalidName("/name", "/{}"),
5254 )
5255
5256
5257 class BoothTicketNameInvalid(NameBuildTest):
5258 def test_success(self):
5259 self.assert_message_from_report(
5260 (
5261 "booth ticket name 'ticket&' is not valid, use up to 63 "
5262 "alphanumeric characters or dash"
5263 ),
5264 reports.BoothTicketNameInvalid("ticket&"),
5265 )
5266
5267
5268 class BoothTicketDuplicate(NameBuildTest):
5269 def test_success(self):
5270 self.assert_message_from_report(
5271 "booth ticket name 'ticket_name' already exists in configuration",
5272 reports.BoothTicketDuplicate("ticket_name"),
5273 )
5274
5275
5276 class BoothTicketDoesNotExist(NameBuildTest):
5277 def test_success(self):
5278 self.assert_message_from_report(
5279 "booth ticket name 'ticket_name' does not exist",
5280 reports.BoothTicketDoesNotExist("ticket_name"),
5281 )
5282
5283
5284 class BoothTicketNotInCib(NameBuildTest):
5285 def test_success(self):
5286 self.assert_message_from_report(
5287 "Unable to find ticket 'name' in CIB",
5288 reports.BoothTicketNotInCib("name"),
5289 )
5290
5291
5292 class BoothAlreadyInCib(NameBuildTest):
5293 def test_success(self):
5294 self.assert_message_from_report(
5295 "booth instance 'name' is already created as cluster resource",
5296 reports.BoothAlreadyInCib("name"),
5297 )
5298
5299
5300 class BoothPathNotExists(NameBuildTest):
5301 def test_success(self):
5302 self.assert_message_from_report(
5303 (
5304 "Configuration directory for booth 'path' is missing. Is booth "
5305 "installed?"
5306 ),
5307 reports.BoothPathNotExists("path"),
5308 )
5309
5310
5311 class BoothNotExistsInCib(NameBuildTest):
5312 def test_success(self):
5313 self.assert_message_from_report(
5314 "booth instance 'name' not found in cib",
5315 reports.BoothNotExistsInCib("name"),
5316 )
5317
5318
5319 class BoothConfigIsUsed(NameBuildTest):
5320 def test_cluster(self):
5321 self.assert_message_from_report(
5322 "booth instance 'name' is used in a cluster resource",
5323 reports.BoothConfigIsUsed(
5324 "name", reports.const.BOOTH_CONFIG_USED_IN_CLUSTER_RESOURCE
5325 ),
5326 )
5327
5328 def test_cluster_resource(self):
5329 self.assert_message_from_report(
5330 "booth instance 'name' is used in cluster resource 'R'",
5331 reports.BoothConfigIsUsed(
5332 "name", reports.const.BOOTH_CONFIG_USED_IN_CLUSTER_RESOURCE, "R"
5333 ),
5334 )
5335
5336 def test_service_manager_enabled(self):
5337 self.assert_message_from_report(
5338 "booth instance 'name' is used - it is enabled in service manager",
5339 reports.BoothConfigIsUsed(
5340 "name",
5341 reports.const.BOOTH_CONFIG_USED_ENABLED_IN_SERVICE_MANAGER,
5342 ),
5343 )
5344
5345 def test_service_manager_running(self):
5346 self.assert_message_from_report(
5347 "booth instance 'name' is used - it is running in service manager",
5348 reports.BoothConfigIsUsed(
5349 "name",
5350 reports.const.BOOTH_CONFIG_USED_RUNNING_IN_SERVICE_MANAGER,
5351 ),
5352 )
5353
5354 def test_systemd_enabled(self):
5355 self.assert_message_from_report(
5356 "booth instance 'name' is used - it is enabled in systemd",
5357 reports.BoothConfigIsUsed(
5358 "name",
5359 reports.const.BOOTH_CONFIG_USED_ENABLED_IN_SYSTEMD,
5360 ),
5361 )
5362
5363 def test_systemd_running(self):
5364 self.assert_message_from_report(
5365 "booth instance 'name' is used - it is running in systemd",
5366 reports.BoothConfigIsUsed(
5367 "name",
5368 reports.const.BOOTH_CONFIG_USED_RUNNING_IN_SYSTEMD,
5369 ),
5370 )
5371
5372
5373 class BoothMultipleTimesInCib(NameBuildTest):
5374 def test_success(self):
5375 self.assert_message_from_report(
5376 "found more than one booth instance 'name' in cib",
5377 reports.BoothMultipleTimesInCib("name"),
5378 )
5379
5380
5381 class BoothConfigDistributionStarted(NameBuildTest):
5382 def test_success(self):
5383 self.assert_message_from_report(
5384 "Sending booth configuration to cluster nodes...",
5385 reports.BoothConfigDistributionStarted(),
5386 )
5387
5388
5389 class BoothConfigAcceptedByNode(NameBuildTest):
5390 def test_defaults(self):
5391 self.assert_message_from_report(
5392 "Booth config saved",
5393 reports.BoothConfigAcceptedByNode(),
5394 )
5395
5396 def test_empty_name_list(self):
5397 self.assert_message_from_report(
5398 "Booth config saved",
5399 reports.BoothConfigAcceptedByNode(name_list=[]),
5400 )
5401
5402 def test_node_and_empty_name_list(self):
5403 self.assert_message_from_report(
5404 "node1: Booth config saved",
5405 reports.BoothConfigAcceptedByNode(node="node1", name_list=[]),
5406 )
5407
5408 def test_name_booth_only(self):
5409 self.assert_message_from_report(
5410 "Booth config saved",
5411 reports.BoothConfigAcceptedByNode(name_list=["booth"]),
5412 )
5413
5414 def test_name_booth_and_node(self):
5415 self.assert_message_from_report(
5416 "node1: Booth config saved",
5417 reports.BoothConfigAcceptedByNode(
5418 node="node1",
5419 name_list=["booth"],
5420 ),
5421 )
5422
5423 def test_single_name(self):
5424 self.assert_message_from_report(
5425 "Booth config 'some' saved",
5426 reports.BoothConfigAcceptedByNode(name_list=["some"]),
5427 )
5428
5429 def test_multiple_names(self):
5430 self.assert_message_from_report(
5431 "Booth configs 'another', 'some' saved",
5432 reports.BoothConfigAcceptedByNode(name_list=["another", "some"]),
5433 )
5434
5435 def test_node(self):
5436 self.assert_message_from_report(
5437 "node1: Booth configs 'another', 'some' saved",
5438 reports.BoothConfigAcceptedByNode(
5439 node="node1",
5440 name_list=["some", "another"],
5441 ),
5442 )
5443
5444
5445 class BoothConfigDistributionNodeError(NameBuildTest):
5446 def test_empty_name(self):
5447 self.assert_message_from_report(
5448 "Unable to save booth config on node 'node1': reason1",
5449 reports.BoothConfigDistributionNodeError("node1", "reason1"),
5450 )
5451
5452 def test_booth_name(self):
5453 self.assert_message_from_report(
5454 "Unable to save booth config on node 'node1': reason1",
5455 reports.BoothConfigDistributionNodeError(
5456 "node1",
5457 "reason1",
5458 name="booth",
5459 ),
5460 )
5461
5462 def test_another_name(self):
5463 self.assert_message_from_report(
5464 "Unable to save booth config 'another' on node 'node1': reason1",
5465 reports.BoothConfigDistributionNodeError(
5466 "node1",
5467 "reason1",
5468 name="another",
5469 ),
5470 )
5471
5472
5473 class BoothFetchingConfigFromNode(NameBuildTest):
5474 def test_empty_name(self):
5475 self.assert_message_from_report(
5476 "Fetching booth config from node 'node1'...",
5477 reports.BoothFetchingConfigFromNode("node1"),
5478 )
5479
5480 def test_booth_name(self):
5481 self.assert_message_from_report(
5482 "Fetching booth config from node 'node1'...",
5483 reports.BoothFetchingConfigFromNode("node1", config="booth"),
5484 )
5485
5486 def test_another_name(self):
5487 self.assert_message_from_report(
5488 "Fetching booth config 'another' from node 'node1'...",
5489 reports.BoothFetchingConfigFromNode("node1", config="another"),
5490 )
5491
5492
5493 class BoothUnsupportedFileLocation(NameBuildTest):
5494 def test_success(self):
5495 self.assert_message_from_report(
5496 (
5497 "Booth configuration '/some/file' is outside of supported "
5498 "booth config directory '/booth/conf/dir/', ignoring the file"
5499 ),
5500 reports.BoothUnsupportedFileLocation(
5501 "/some/file",
5502 "/booth/conf/dir/",
5503 file_type_codes.BOOTH_CONFIG,
5504 ),
5505 )
5506
5507
5508 class BoothDaemonStatusError(NameBuildTest):
5509 def test_success(self):
5510 self.assert_message_from_report(
5511 "unable to get status of booth daemon: some reason",
5512 reports.BoothDaemonStatusError("some reason"),
5513 )
5514
5515
5516 class BoothTicketStatusError(NameBuildTest):
5517 def test_minimal(self):
5518 self.assert_message_from_report(
5519 "unable to get status of booth tickets",
5520 reports.BoothTicketStatusError(),
5521 )
5522
5523 def test_all(self):
5524 self.assert_message_from_report(
5525 "unable to get status of booth tickets: some reason",
5526 reports.BoothTicketStatusError(reason="some reason"),
5527 )
5528
5529
5530 class BoothPeersStatusError(NameBuildTest):
5531 def test_minimal(self):
5532 self.assert_message_from_report(
5533 "unable to get status of booth peers",
5534 reports.BoothPeersStatusError(),
5535 )
5536
5537 def test_all(self):
5538 self.assert_message_from_report(
5539 "unable to get status of booth peers: some reason",
5540 reports.BoothPeersStatusError(reason="some reason"),
5541 )
5542
5543
5544 class BoothCannotDetermineLocalSiteIp(NameBuildTest):
5545 def test_success(self):
5546 self.assert_message_from_report(
5547 "cannot determine local site ip, please specify site parameter",
5548 reports.BoothCannotDetermineLocalSiteIp(),
5549 )
5550
5551
5552 class BoothTicketOperationFailed(NameBuildTest):
5553 def test_success(self):
5554 self.assert_message_from_report(
5555 (
5556 "unable to operation booth ticket 'ticket_name'"
5557 " for site 'site_ip', reason: reason"
5558 ),
5559 reports.BoothTicketOperationFailed(
5560 "operation", "reason", "site_ip", "ticket_name"
5561 ),
5562 )
5563
5564 def test_no_site_ip(self):
5565 self.assert_message_from_report(
5566 ("unable to operation booth ticket 'ticket_name', reason: reason"),
5567 reports.BoothTicketOperationFailed(
5568 "operation", "reason", None, "ticket_name"
5569 ),
5570 )
5571
5572
5573 class BoothTicketChangingState(NameBuildTest):
5574 def test_success(self):
5575 self.assert_message_from_report(
5576 "Changing state of ticket 'name' to standby",
5577 reports.BoothTicketChangingState("name", "standby"),
5578 )
5579
5580
5581 class BoothTicketCleanup(NameBuildTest):
5582 def test_success(self):
5583 self.assert_message_from_report(
5584 "Cleaning up ticket 'name' from CIB",
5585 reports.BoothTicketCleanup("name"),
5586 )
5587
5588
5589 # TODO: remove, use ADD_REMOVE reports
5590 class TagAddRemoveIdsDuplication(NameBuildTest):
5591 def test_message_add(self):
5592 self.assert_message_from_report(
5593 "Ids to add must be unique, duplicate ids: 'dup1', 'dup2'",
5594 reports.TagAddRemoveIdsDuplication(
5595 duplicate_ids_list=["dup2", "dup1"],
5596 ),
5597 )
5598
5599 def test_message_remove(self):
5600 self.assert_message_from_report(
5601 "Ids to remove must be unique, duplicate ids: 'dup1', 'dup2'",
5602 reports.TagAddRemoveIdsDuplication(
5603 duplicate_ids_list=["dup2", "dup1"],
5604 add_or_not_remove=False,
5605 ),
5606 )
5607
5608
5609 # TODO: remove, use ADD_REMOVE reports
5610 class TagAdjacentReferenceIdNotInTheTag(NameBuildTest):
5611 def test_message(self):
5612 self.assert_message_from_report(
5613 (
5614 "There is no reference id 'adj_id' in the tag 'tag_id', cannot "
5615 "put reference ids next to it in the tag"
5616 ),
5617 reports.TagAdjacentReferenceIdNotInTheTag("adj_id", "tag_id"),
5618 )
5619
5620
5621 # TODO: remove, use ADD_REMOVE reports
5622 class TagCannotAddAndRemoveIdsAtTheSameTime(NameBuildTest):
5623 def test_message_one_item(self):
5624 self.assert_message_from_report(
5625 "Ids cannot be added and removed at the same time: 'id1'",
5626 reports.TagCannotAddAndRemoveIdsAtTheSameTime(["id1"]),
5627 )
5628
5629 def test_message_more_items(self):
5630 self.assert_message_from_report(
5631 (
5632 "Ids cannot be added and removed at the same time: 'id1', "
5633 "'id2', 'id3'"
5634 ),
5635 reports.TagCannotAddAndRemoveIdsAtTheSameTime(
5636 ["id3", "id2", "id1"],
5637 ),
5638 )
5639
5640
5641 # TODO: remove, use ADD_REMOVE reports
5642 class TagCannotAddReferenceIdsAlreadyInTheTag(NameBuildTest):
5643 def test_message_singular(self):
5644 self.assert_message_from_report(
5645 "Cannot add reference id already in the tag 'tag_id': 'id1'",
5646 reports.TagCannotAddReferenceIdsAlreadyInTheTag(
5647 "tag_id",
5648 ["id1"],
5649 ),
5650 )
5651
5652 def test_message_plural(self):
5653 self.assert_message_from_report(
5654 "Cannot add reference ids already in the tag 'TAG': 'id1', 'id2'",
5655 reports.TagCannotAddReferenceIdsAlreadyInTheTag(
5656 "TAG",
5657 ["id2", "id1"],
5658 ),
5659 )
5660
5661
5662 class TagCannotContainItself(NameBuildTest):
5663 def test_message(self):
5664 self.assert_message_from_report(
5665 "Tag cannot contain itself", reports.TagCannotContainItself()
5666 )
5667
5668
5669 class TagCannotCreateEmptyTagNoIdsSpecified(NameBuildTest):
5670 def test_message(self):
5671 self.assert_message_from_report(
5672 "Cannot create empty tag, no resource ids specified",
5673 reports.TagCannotCreateEmptyTagNoIdsSpecified(),
5674 )
5675
5676
5677 # TODO: remove, use ADD_REMOVE reports
5678 class TagCannotPutIdNextToItself(NameBuildTest):
5679 def test_message(self):
5680 self.assert_message_from_report(
5681 "Cannot put id 'some_id' next to itself.",
5682 reports.TagCannotPutIdNextToItself("some_id"),
5683 )
5684
5685
5686 # TODO: remove, use ADD_REMOVE reports
5687 class TagCannotRemoveAdjacentId(NameBuildTest):
5688 def test_message(self):
5689 self.assert_message_from_report(
5690 "Cannot remove id 'some_id' next to which ids are being added",
5691 reports.TagCannotRemoveAdjacentId("some_id"),
5692 )
5693
5694
5695 # TODO: remove, use ADD_REMOVE reports
5696 class TagCannotRemoveReferencesWithoutRemovingTag(NameBuildTest):
5697 def test_message(self):
5698 self.assert_message_from_report(
5699 "There would be no references left in the tag 'tag-id'",
5700 reports.TagCannotRemoveReferencesWithoutRemovingTag("tag-id"),
5701 )
5702
5703
5704 class TagCannotRemoveTagReferencedInConstraints(NameBuildTest):
5705 def test_message_singular(self):
5706 self.assert_message_from_report(
5707 "Tag 'tag1' cannot be removed because it is referenced in "
5708 "constraint 'constraint-id-1'",
5709 reports.TagCannotRemoveTagReferencedInConstraints(
5710 "tag1",
5711 ["constraint-id-1"],
5712 ),
5713 )
5714
5715 def test_message_plural(self):
5716 self.assert_message_from_report(
5717 "Tag 'tag2' cannot be removed because it is referenced in "
5718 "constraints 'constraint-id-1', 'constraint-id-2'",
5719 reports.TagCannotRemoveTagReferencedInConstraints(
5720 "tag2",
5721 ["constraint-id-2", "constraint-id-1"],
5722 ),
5723 )
5724
5725
5726 class TagCannotRemoveTagsNoTagsSpecified(NameBuildTest):
5727 def test_message(self):
5728 self.assert_message_from_report(
5729 "Cannot remove tags, no tags to remove specified",
5730 reports.TagCannotRemoveTagsNoTagsSpecified(),
5731 )
5732
5733
5734 # TODO: remove, use ADD_REMOVE reports
5735 class TagCannotSpecifyAdjacentIdWithoutIdsToAdd(NameBuildTest):
5736 def test_message(self):
5737 self.assert_message_from_report(
5738 "Cannot specify adjacent id 'some-id' without ids to add",
5739 reports.TagCannotSpecifyAdjacentIdWithoutIdsToAdd("some-id"),
5740 )
5741
5742
5743 # TODO: remove, use ADD_REMOVE reports
5744 class TagCannotUpdateTagNoIdsSpecified(NameBuildTest):
5745 def test_message(self):
5746 self.assert_message_from_report(
5747 "Cannot update tag, no ids to be added or removed specified",
5748 reports.TagCannotUpdateTagNoIdsSpecified(),
5749 )
5750
5751
5752 # TODO: remove, use ADD_REMOVE reports
5753 class TagIdsNotInTheTag(NameBuildTest):
5754 def test_message_singular(self):
5755 self.assert_message_from_report(
5756 "Tag 'tag-id' does not contain id: 'a'",
5757 reports.TagIdsNotInTheTag("tag-id", ["a"]),
5758 )
5759
5760 def test_message_plural(self):
5761 self.assert_message_from_report(
5762 "Tag 'tag-id' does not contain ids: 'a', 'b'",
5763 reports.TagIdsNotInTheTag("tag-id", ["b", "a"]),
5764 )
5765
5766
5767 class RuleInEffectStatusDetectionNotSupported(NameBuildTest):
5768 def test_success(self):
5769 self.assert_message_from_report(
5770 (
5771 "crm_rule is not available, therefore expired parts of "
5772 "configuration may not be detected. Consider upgrading pacemaker."
5773 ),
5774 reports.RuleInEffectStatusDetectionNotSupported(),
5775 )
5776
5777
5778 class RuleExpressionOptionsDuplication(NameBuildTest):
5779 def test_success(self):
5780 self.assert_message_from_report(
5781 "Duplicate options in a single (sub)expression: 'key', 'name'",
5782 reports.RuleExpressionOptionsDuplication(["name", "key"]),
5783 )
5784
5785
5786 class RuleExpressionSinceGreaterThanUntil(NameBuildTest):
5787 def test_success(self):
5788 self.assert_message_from_report(
5789 "Since '987' is not sooner than until '654'",
5790 reports.RuleExpressionSinceGreaterThanUntil("987", "654"),
5791 )
5792
5793
5794 class RuleExpressionParseError(NameBuildTest):
5795 def test_success(self):
5796 self.assert_message_from_report(
5797 "'resource dummy op monitor' is not a valid rule expression, "
5798 "parse error near or after line 1 column 16",
5799 reports.RuleExpressionParseError(
5800 "resource dummy op monitor",
5801 "Expected end of text",
5802 "resource dummy op monitor",
5803 1,
5804 16,
5805 15,
5806 ),
5807 )
5808
5809
5810 class RuleExpressionNotAllowed(NameBuildTest):
5811 def test_op(self):
5812 self.assert_message_from_report(
5813 "Keyword 'op' cannot be used in a rule in this command",
5814 reports.RuleExpressionNotAllowed(
5815 CibRuleExpressionType.OP_EXPRESSION
5816 ),
5817 )
5818
5819 def test_rsc(self):
5820 self.assert_message_from_report(
5821 "Keyword 'resource' cannot be used in a rule in this command",
5822 reports.RuleExpressionNotAllowed(
5823 CibRuleExpressionType.RSC_EXPRESSION
5824 ),
5825 )
5826
5827 def test_node_attr(self):
5828 self.assert_message_from_report(
5829 "Keywords 'defined', 'not_defined', 'eq', 'ne', 'gte', 'gt', "
5830 "'lte' and 'lt' cannot be used in a rule in this command",
5831 reports.RuleExpressionNotAllowed(CibRuleExpressionType.EXPRESSION),
5832 )
5833
5834
5835 class RuleNoExpressionSpecified(NameBuildTest):
5836 def test_success(self):
5837 self.assert_message_from_report(
5838 "No rule expression was specified",
5839 reports.RuleNoExpressionSpecified(),
5840 )
5841
5842
5843 class CibNvsetAmbiguousProvideNvsetId(NameBuildTest):
5844 def test_success(self):
5845 self.assert_message_from_report(
5846 "Several options sets exist, please specify an option set ID",
5847 reports.CibNvsetAmbiguousProvideNvsetId(
5848 const.PCS_COMMAND_RESOURCE_DEFAULTS_UPDATE
5849 ),
5850 )
5851
5852
5853 class AddRemoveItemsNotSpecified(NameBuildTest):
5854 def test_message(self):
5855 self.assert_message_from_report(
5856 (
5857 "Cannot modify stonith resource 'container-id', no devices to "
5858 "add or remove specified"
5859 ),
5860 reports.AddRemoveItemsNotSpecified(
5861 const.ADD_REMOVE_CONTAINER_TYPE_STONITH_RESOURCE,
5862 const.ADD_REMOVE_ITEM_TYPE_DEVICE,
5863 "container-id",
5864 ),
5865 )
5866
5867 def test_message_without_container(self):
5868 self.assert_message_from_report(
5869 "No devices to add or remove specified",
5870 reports.AddRemoveItemsNotSpecified(
5871 container_type=None,
5872 item_type=const.ADD_REMOVE_ITEM_TYPE_DEVICE,
5873 container_id=None,
5874 ),
5875 )
5876
5877
5878 class AddRemoveItemsDuplication(NameBuildTest):
5879 def test_message(self):
5880 self.assert_message_from_report(
5881 (
5882 "Devices to add or remove must be unique, duplicate devices: "
5883 "'dup1', 'dup2'"
5884 ),
5885 reports.AddRemoveItemsDuplication(
5886 const.ADD_REMOVE_CONTAINER_TYPE_STONITH_RESOURCE,
5887 const.ADD_REMOVE_ITEM_TYPE_DEVICE,
5888 "container-id",
5889 ["dup2", "dup1"],
5890 ),
5891 )
5892
5893
5894 class AddRemoveCannotAddItemsAlreadyInTheContainer(NameBuildTest):
5895 def test_message_plural(self):
5896 self.assert_message_from_report(
5897 "Cannot add devices 'i1', 'i2', they are already present in stonith"
5898 " resource 'container-id'",
5899 reports.AddRemoveCannotAddItemsAlreadyInTheContainer(
5900 const.ADD_REMOVE_CONTAINER_TYPE_STONITH_RESOURCE,
5901 const.ADD_REMOVE_ITEM_TYPE_DEVICE,
5902 "container-id",
5903 ["i2", "i1"],
5904 ),
5905 )
5906
5907 def test_message_singular(self):
5908 self.assert_message_from_report(
5909 "Cannot add device 'i1', it is already present in stonith resource "
5910 "'container-id'",
5911 reports.AddRemoveCannotAddItemsAlreadyInTheContainer(
5912 const.ADD_REMOVE_CONTAINER_TYPE_STONITH_RESOURCE,
5913 const.ADD_REMOVE_ITEM_TYPE_DEVICE,
5914 "container-id",
5915 ["i1"],
5916 ),
5917 )
5918
5919
5920 class AddRemoveCannotRemoveItemsNotInTheContainer(NameBuildTest):
5921 def test_message_plural(self):
5922 self.assert_message_from_report(
5923 (
5924 "Cannot remove devices 'i1', 'i2', they are not present in "
5925 "stonith resource 'container-id'"
5926 ),
5927 reports.AddRemoveCannotRemoveItemsNotInTheContainer(
5928 const.ADD_REMOVE_CONTAINER_TYPE_STONITH_RESOURCE,
5929 const.ADD_REMOVE_ITEM_TYPE_DEVICE,
5930 "container-id",
5931 ["i2", "i1"],
5932 ),
5933 )
5934
5935 def test_message_singular(self):
5936 self.assert_message_from_report(
5937 (
5938 "Cannot remove device 'i1', it is not present in "
5939 "stonith resource 'container-id'"
5940 ),
5941 reports.AddRemoveCannotRemoveItemsNotInTheContainer(
5942 const.ADD_REMOVE_CONTAINER_TYPE_STONITH_RESOURCE,
5943 const.ADD_REMOVE_ITEM_TYPE_DEVICE,
5944 "container-id",
5945 ["i1"],
5946 ),
5947 )
5948
5949
5950 class AddRemoveCannotAddAndRemoveItemsAtTheSameTime(NameBuildTest):
5951 def test_message_plural(self):
5952 self.assert_message_from_report(
5953 "Devices cannot be added and removed at the same time: 'i1', 'i2'",
5954 reports.AddRemoveCannotAddAndRemoveItemsAtTheSameTime(
5955 const.ADD_REMOVE_CONTAINER_TYPE_STONITH_RESOURCE,
5956 const.ADD_REMOVE_ITEM_TYPE_DEVICE,
5957 "container-id",
5958 ["i2", "i1"],
5959 ),
5960 )
5961
5962 def test_message_singular(self):
5963 self.assert_message_from_report(
5964 "Device cannot be added and removed at the same time: 'i1'",
5965 reports.AddRemoveCannotAddAndRemoveItemsAtTheSameTime(
5966 const.ADD_REMOVE_CONTAINER_TYPE_STONITH_RESOURCE,
5967 const.ADD_REMOVE_ITEM_TYPE_DEVICE,
5968 "container-id",
5969 ["i1"],
5970 ),
5971 )
5972
5973
5974 class AddRemoveCannotRemoveAllItemsFromTheContainer(NameBuildTest):
5975 def test_message(self):
5976 self.assert_message_from_report(
5977 "Cannot remove all devices from stonith resource 'container-id'",
5978 reports.AddRemoveCannotRemoveAllItemsFromTheContainer(
5979 const.ADD_REMOVE_CONTAINER_TYPE_STONITH_RESOURCE,
5980 const.ADD_REMOVE_ITEM_TYPE_DEVICE,
5981 "container-id",
5982 ["i1", "i2"],
5983 ),
5984 )
5985
5986
5987 class AddRemoveAdjacentItemNotInTheContainer(NameBuildTest):
5988 def test_message(self):
5989 self.assert_message_from_report(
5990 (
5991 "There is no device 'adjacent-item-id' in the stonith resource "
5992 "'container-id', cannot add devices next to it"
5993 ),
5994 reports.AddRemoveAdjacentItemNotInTheContainer(
5995 const.ADD_REMOVE_CONTAINER_TYPE_STONITH_RESOURCE,
5996 const.ADD_REMOVE_ITEM_TYPE_DEVICE,
5997 "container-id",
5998 "adjacent-item-id",
5999 ),
6000 )
6001
6002
6003 class AddRemoveCannotPutItemNextToItself(NameBuildTest):
6004 def test_message(self):
6005 self.assert_message_from_report(
6006 "Cannot put device 'adjacent-item-id' next to itself",
6007 reports.AddRemoveCannotPutItemNextToItself(
6008 const.ADD_REMOVE_CONTAINER_TYPE_STONITH_RESOURCE,
6009 const.ADD_REMOVE_ITEM_TYPE_DEVICE,
6010 "container-id",
6011 "adjacent-item-id",
6012 ),
6013 )
6014
6015
6016 class AddRemoveCannotSpecifyAdjacentItemWithoutItemsToAdd(NameBuildTest):
6017 def test_message(self):
6018 self.assert_message_from_report(
6019 (
6020 "Cannot specify adjacent device 'adjacent-item-id' without "
6021 "devices to add"
6022 ),
6023 reports.AddRemoveCannotSpecifyAdjacentItemWithoutItemsToAdd(
6024 const.ADD_REMOVE_CONTAINER_TYPE_STONITH_RESOURCE,
6025 const.ADD_REMOVE_ITEM_TYPE_DEVICE,
6026 "container-id",
6027 "adjacent-item-id",
6028 ),
6029 )
6030
6031
6032 class CloningStonithResourcesHasNoEffect(NameBuildTest):
6033 def test_singular_without_group_id(self):
6034 self.assert_message_from_report(
6035 (
6036 "No need to clone stonith resource 'fence1', any node can use "
6037 "a stonith resource (unless specifically banned) regardless of "
6038 "whether the stonith resource is running on that node or not"
6039 ),
6040 reports.CloningStonithResourcesHasNoEffect(["fence1"]),
6041 )
6042
6043 def test_plural_with_group_id(self):
6044 self.assert_message_from_report(
6045 (
6046 "Group 'StonithGroup' contains stonith resources. No need to "
6047 "clone stonith resources 'fence1', 'fence2', any node can use "
6048 "a stonith resource (unless specifically banned) regardless of "
6049 "whether the stonith resource is running on that node or not"
6050 ),
6051 reports.CloningStonithResourcesHasNoEffect(
6052 ["fence1", "fence2"], "StonithGroup"
6053 ),
6054 )
6055
6056
6057 class CommandInvalidPayload(NameBuildTest):
6058 def test_all(self):
6059 reason = "a reason"
6060 self.assert_message_from_report(
6061 f"Invalid command payload: {reason}",
6062 reports.CommandInvalidPayload(reason),
6063 )
6064
6065
6066 class CommandUnknown(NameBuildTest):
6067 def test_all(self):
6068 cmd = "a cmd"
6069 self.assert_message_from_report(
6070 f"Unknown command '{cmd}'",
6071 reports.CommandUnknown(cmd),
6072 )
6073
6074
6075 class NotAuthorized(NameBuildTest):
6076 def test_all(self):
6077 self.assert_message_from_report(
6078 "Current user is not authorized for this operation",
6079 reports.NotAuthorized(),
6080 )
6081
6082
6083 class AgentSelfValidationResult(NameBuildTest):
6084 def test_message(self):
6085 lines = [f"line #{i}" for i in range(3)]
6086 self.assert_message_from_report(
6087 "Validation result from agent:\n {}".format("\n ".join(lines)),
6088 reports.AgentSelfValidationResult("\n".join(lines)),
6089 )
6090
6091
6092 class AgentSelfValidationInvalidData(NameBuildTest):
6093 def test_message(self):
6094 reason = "not xml"
6095 self.assert_message_from_report(
6096 f"Invalid validation data from agent: {reason}",
6097 reports.AgentSelfValidationInvalidData(reason),
6098 )
6099
6100
6101 class AgentSelfValidationSkippedUpdatedResourceMisconfigured(NameBuildTest):
6102 def test_message(self):
6103 lines = [f"line #{i}" for i in range(3)]
6104 self.assert_message_from_report(
6105 (
6106 "The resource was misconfigured before the update, therefore "
6107 "agent self-validation will not be run for the updated "
6108 "configuration. Validation output of the original "
6109 "configuration:\n {}"
6110 ).format("\n ".join(lines)),
6111 reports.AgentSelfValidationSkippedUpdatedResourceMisconfigured(
6112 "\n".join(lines)
6113 ),
6114 )
6115
6116
6117 class AgentSelfValidationAutoOnWithWarnings(NameBuildTest):
6118 def test_message(self):
6119 self.assert_message_from_report(
6120 (
6121 "Validating resource options using the resource agent itself "
6122 "is enabled by default and produces warnings. In a future "
6123 "version, this might be changed to errors. Enable "
6124 "agent validation to switch to the future behavior."
6125 ),
6126 reports.AgentSelfValidationAutoOnWithWarnings(),
6127 )
6128
6129
6130 class ResourceCloneIncompatibleMetaAttributes(NameBuildTest):
6131 def test_with_provider(self):
6132 attr = "attr_name"
6133 self.assert_message_from_report(
6134 f"Clone option '{attr}' is not compatible with 'standard:provider:type' resource agent",
6135 reports.ResourceCloneIncompatibleMetaAttributes(
6136 attr, ResourceAgentNameDto("standard", "provider", "type")
6137 ),
6138 )
6139
6140 def test_without_provider(self):
6141 attr = "attr_name"
6142 self.assert_message_from_report(
6143 f"Clone option '{attr}' is not compatible with 'standard:type' resource agent",
6144 reports.ResourceCloneIncompatibleMetaAttributes(
6145 attr, ResourceAgentNameDto("standard", None, "type")
6146 ),
6147 )
6148
6149 def test_resource_id(self):
6150 attr = "attr_name"
6151 res_id = "resource_id"
6152 self.assert_message_from_report(
6153 (
6154 f"Clone option '{attr}' is not compatible with 'standard:type' "
6155 f"resource agent of resource '{res_id}'"
6156 ),
6157 reports.ResourceCloneIncompatibleMetaAttributes(
6158 attr,
6159 ResourceAgentNameDto("standard", None, "type"),
6160 resource_id=res_id,
6161 ),
6162 )
6163
6164 def test_group_id(self):
6165 attr = "attr_name"
6166 res_id = "resource id"
6167 group_id = "group id"
6168 self.assert_message_from_report(
6169 (
6170 f"Clone option '{attr}' is not compatible with 'standard:type' "
6171 f"resource agent of resource '{res_id}' in group '{group_id}'"
6172 ),
6173 reports.ResourceCloneIncompatibleMetaAttributes(
6174 attr,
6175 ResourceAgentNameDto("standard", None, "type"),
6176 resource_id=res_id,
6177 group_id=group_id,
6178 ),
6179 )
6180
6181
6182 class BoothAuthfileNotUsed(NameBuildTest):
6183 def test_message(self):
6184 self.assert_message_from_report(
6185 "Booth authfile is not enabled",
6186 reports.BoothAuthfileNotUsed("instance name"),
6187 )
6188
6189
6190 class BoothUnsupportedOptionEnableAuthfile(NameBuildTest):
6191 def test_message(self):
6192 self.assert_message_from_report(
6193 "Unsupported option 'enable-authfile' is set in booth configuration",
6194 reports.BoothUnsupportedOptionEnableAuthfile("instance name"),
6195 )
6196
6197
6198 class CannotCreateDefaultClusterPropertySet(NameBuildTest):
6199 def test_all(self):
6200 self.assert_message_from_report(
6201 (
6202 "Cannot create default cluster_property_set element, ID "
6203 "'cib-bootstrap-options' already exists. Find elements with the"
6204 " ID and remove them from cluster configuration."
6205 ),
6206 reports.CannotCreateDefaultClusterPropertySet(
6207 "cib-bootstrap-options"
6208 ),
6209 )
6210
6211
6212 class ClusterStatusBundleMemberIdAsImplicit(NameBuildTest):
6213 def test_one(self):
6214 self.assert_message_from_report(
6215 (
6216 "Skipping bundle 'resource-bundle': resource 'resource' has "
6217 "the same id as some of the implicit bundle resources"
6218 ),
6219 reports.ClusterStatusBundleMemberIdAsImplicit(
6220 "resource-bundle", ["resource"]
6221 ),
6222 )
6223
6224 def test_multiple(self):
6225 self.assert_message_from_report(
6226 (
6227 "Skipping bundle 'resource-bundle': resources 'resource-0', "
6228 "'resource-1' have the same id as some of the implicit bundle "
6229 "resources"
6230 ),
6231 reports.ClusterStatusBundleMemberIdAsImplicit(
6232 "resource-bundle", ["resource-0", "resource-1"]
6233 ),
6234 )
6235
6236
6237 class ResourceWaitDeprecated(NameBuildTest):
6238 def test_success(self):
6239 self.assert_message_from_report(
6240 (
6241 "Ability of this command to accept 'wait' argument is "
6242 "deprecated and will be removed in a future release."
6243 ),
6244 reports.ResourceWaitDeprecated(),
6245 )
6246
6247
6248 class CommandArgumentTypeMismatch(NameBuildTest):
6249 def test_message(self) -> str:
6250 self.assert_message_from_report(
6251 "This command does not accept entity type.",
6252 reports.CommandArgumentTypeMismatch(
6253 "entity type", "pcs stonith create"
6254 ),
6255 )
6256
6257
6258 class ResourceRestartError(NameBuildTest):
6259 def test_message(self) -> str:
6260 self.assert_message_from_report(
6261 "Unable to restart resource 'resourceId':\nerror description",
6262 reports.ResourceRestartError("error description", "resourceId"),
6263 )
6264
6265
6266 class ResourceRestartNodeIsForMultiinstanceOnly(NameBuildTest):
6267 def test_message(self) -> str:
6268 self.assert_message_from_report(
6269 (
6270 "Can only restart on a specific node for a clone or bundle, "
6271 "'resourceId' is a resource"
6272 ),
6273 reports.ResourceRestartNodeIsForMultiinstanceOnly(
6274 "resourceId", "primitive", "node01"
6275 ),
6276 )
6277
6278
6279 class ResourceRestartUsingParentRersource(NameBuildTest):
6280 def test_message(self) -> str:
6281 self.assert_message_from_report(
6282 (
6283 "Restarting 'parentId' instead...\n"
6284 "(If a resource is a clone or bundle, you must use the clone "
6285 "or bundle instead)"
6286 ),
6287 reports.ResourceRestartUsingParentRersource(
6288 "resourceId", "parentId"
6289 ),
6290 )
6291
6292
6293 class ClusterOptionsMetadataNotSupported(NameBuildTest):
6294 def test_success(self):
6295 self.assert_message_from_report(
6296 (
6297 "Cluster options metadata are not supported, please upgrade "
6298 "pacemaker"
6299 ),
6300 reports.ClusterOptionsMetadataNotSupported(),
6301 )
6302
6303
6304 class StoppingResources(NameBuildTest):
6305 def test_one_resource(self):
6306 self.assert_message_from_report(
6307 "Stopping resource 'resourceId'",
6308 reports.StoppingResources(["resourceId"]),
6309 )
6310
6311 def test_multiple_resources(self):
6312 self.assert_message_from_report(
6313 "Stopping resources 'resourceId1', 'resourceId2'",
6314 reports.StoppingResources(["resourceId1", "resourceId2"]),
6315 )
6316
6317
6318 class StoppedResourcesBeforeDeleteCheckSkipped(NameBuildTest):
6319 def test_one_resource(self):
6320 self.assert_message_from_report(
6321 (
6322 "Not checking if resource 'A' is stopped before deletion. "
6323 "Deleting unstopped resources may result in orphaned resources "
6324 "being present in the cluster."
6325 ),
6326 reports.StoppedResourcesBeforeDeleteCheckSkipped(["A"]),
6327 )
6328
6329 def test_multiple_resources(self):
6330 self.assert_message_from_report(
6331 (
6332 "Not checking if resources 'A', 'B' are stopped before "
6333 "deletion. Deleting unstopped resources may result in orphaned "
6334 "resources being present in the cluster."
6335 ),
6336 reports.StoppedResourcesBeforeDeleteCheckSkipped(["A", "B"]),
6337 )
6338
6339 def test_with_reason(self):
6340 self.assert_message_from_report(
6341 (
6342 "Not checking if resource 'A' is stopped before deletion "
6343 "because the command does not run on a live cluster. Deleting "
6344 "unstopped resources may result in orphaned resources being "
6345 "present in the cluster."
6346 ),
6347 reports.StoppedResourcesBeforeDeleteCheckSkipped(
6348 ["A"], reports.const.REASON_NOT_LIVE_CIB
6349 ),
6350 )
6351
6352
6353 class CannotRemoveResourcesNotStopped(NameBuildTest):
6354 def test_one_resource(self) -> str:
6355 self.assert_message_from_report(
6356 (
6357 "Resource 'resourceId' is not stopped, removing unstopped "
6358 "resources can lead to orphaned resources being present in the "
6359 "cluster."
6360 ),
6361 reports.CannotRemoveResourcesNotStopped(["resourceId"]),
6362 )
6363
6364 def test_multiple_resources(self) -> str:
6365 self.assert_message_from_report(
6366 (
6367 "Resources 'resourceId1', 'resourceId2' are not stopped, "
6368 "removing unstopped resources can lead to orphaned resources "
6369 "being present in the cluster."
6370 ),
6371 reports.CannotRemoveResourcesNotStopped(
6372 ["resourceId1", "resourceId2"]
6373 ),
6374 )
6375
6376
6377 class DlmClusterRenameNeeded(NameBuildTest):
6378 def test_success(self):
6379 self.assert_message_from_report(
6380 (
6381 "The DLM cluster name in the shared volume groups metadata "
6382 "must be updated to reflect the name of the cluster so that "
6383 "the volume groups can start"
6384 ),
6385 reports.DlmClusterRenameNeeded(),
6386 )
6387
6388
6389 class Gfs2LockTableRenameNeeded(NameBuildTest):
6390 def test_success(self):
6391 self.assert_message_from_report(
6392 (
6393 "The lock table name on each GFS2 filesystem must be updated "
6394 "to reflect the name of the cluster so that the filesystems "
6395 "can be mounted"
6396 ),
6397 reports.Gfs2LockTableRenameNeeded(),
6398 )
6399
6400
6401 class CibClusterNameRemovalStarted(NameBuildTest):
6402 def test_success(self):
6403 self.assert_message_from_report(
6404 "Removing CIB cluster name property on nodes...",
6405 reports.CibClusterNameRemovalStarted(),
6406 )
6407
6408
6409 class CibClusterNameRemoved(NameBuildTest):
6410 def test_success(self):
6411 self.assert_message_from_report(
6412 "node: Succeeded", reports.CibClusterNameRemoved("node")
6413 )
6414
6415
6416 class CibClusterNameRemovalFailed(NameBuildTest):
6417 def test_success(self):
6418 self.assert_message_from_report(
6419 "CIB cluster name property removal failed: reason",
6420 reports.CibClusterNameRemovalFailed("reason"),
6421 )
6422
6423
6424 class PacemakerRunning(NameBuildTest):
6425 def test_success(self):
6426 self.assert_message_from_report(
6427 "Pacemaker is running", reports.PacemakerRunning()
6428 )
6429
6430
6431 class CibXmlMissing(NameBuildTest):
6432 def test_success(self):
6433 self.assert_message_from_report(
6434 "CIB XML file cannot be found", reports.CibXmlMissing()
6435 )
6436
6437
6438 class CibNodeRenameElementUpdated(NameBuildTest):
6439 def test_location_constraint(self):
6440 self.assert_message_from_report(
6441 "Location constraint 'loc-1': node updated from 'node1' to 'node2'",
6442 reports.CibNodeRenameElementUpdated(
6443 "Location constraint", "loc-1", "node", "node1", "node2"
6444 ),
6445 )
6446
6447 def test_rule_expression(self):
6448 self.assert_message_from_report(
6449 "Rule 'rule-1': #uname expression updated from 'node1' to 'node2'",
6450 reports.CibNodeRenameElementUpdated(
6451 "Rule", "rule-1", "#uname expression", "node1", "node2"
6452 ),
6453 )
6454
6455 def test_fencing_level(self):
6456 self.assert_message_from_report(
6457 "Fencing level '1': target updated from 'node1' to 'node2'",
6458 reports.CibNodeRenameElementUpdated(
6459 "Fencing level", "1", "target", "node1", "node2"
6460 ),
6461 )
6462
6463 def test_fence_device(self):
6464 self.assert_message_from_report(
6465 "Fence device 'fence_xvm': attribute 'pcmk_host_list' "
6466 "updated from 'node1,node2' to 'node3,node2'",
6467 reports.CibNodeRenameElementUpdated(
6468 "Fence device",
6469 "fence_xvm",
6470 "attribute 'pcmk_host_list'",
6471 "node1,node2",
6472 "node3,node2",
6473 ),
6474 )
6475
6476
6477 class CibNodeRenameFencingLevelPatternExists(NameBuildTest):
6478 def test_success(self):
6479 self.assert_message_from_report(
6480 "Fencing level '1' uses target-pattern 'node.*', "
6481 "which may match the renamed node, check the pattern and adjust the"
6482 " configuration if necessary",
6483 reports.CibNodeRenameFencingLevelPatternExists("1", "node.*"),
6484 )
6485
6486
6487 class CibNodeRenameAclsExist(NameBuildTest):
6488 def test_success(self):
6489 self.assert_message_from_report(
6490 "ACL rules exist in CIB and may contain references to node "
6491 "names, check the ACL configuration and adjust it if necessary",
6492 reports.CibNodeRenameAclsExist(),
6493 )
6494
6495
6496 class CibNodeRenameOldNodeInCorosync(NameBuildTest):
6497 def test_success(self):
6498 self.assert_message_from_report(
6499 "Node 'old_name' is still known to corosync, "
6500 "the node may not have been renamed in corosync.conf yet",
6501 reports.CibNodeRenameOldNodeInCorosync(
6502 old_name="old_name",
6503 ),
6504 )
6505
6506
6507 class CibNodeRenameNewNodeNotInCorosync(NameBuildTest):
6508 def test_success(self):
6509 self.assert_message_from_report(
6510 "Node 'new_name' is not known to corosync, "
6511 "the node name may be incorrect",
6512 reports.CibNodeRenameNewNodeNotInCorosync(
6513 new_name="new_name",
6514 ),
6515 )
6516
6517
6518 class CibNodeRenameNoChange(NameBuildTest):
6519 def test_success(self):
6520 self.assert_message_from_report(
6521 "No CIB configuration changes needed for node rename",
6522 reports.CibNodeRenameNoChange(),
6523 )
6524
6525
6526 class ConfiguredResourceMissingInStatus(NameBuildTest):
6527 def test_only_resource_id(self):
6528 self.assert_message_from_report(
6529 (
6530 "Cannot check if the resource 'id' is in expected state, "
6531 "since the resource is missing in cluster status"
6532 ),
6533 reports.ConfiguredResourceMissingInStatus("id"),
6534 )
6535
6536 def test_with_expected_state(self):
6537 self.assert_message_from_report(
6538 (
6539 "Cannot check if the resource 'id' is in expected state "
6540 "(stopped), since the resource is missing in cluster status"
6541 ),
6542 reports.ConfiguredResourceMissingInStatus(
6543 "id", ResourceState.STOPPED
6544 ),
6545 )
6546
6547
6548 class NoStonithMeansWouldBeLeft(NameBuildTest):
6549 def test_success(self):
6550 self.assert_message_from_report(
6551 (
6552 "Requested action leaves the cluster with no enabled means "
6553 "to fence nodes, resulting in the cluster not being able to "
6554 "recover from certain failure conditions"
6555 ),
6556 reports.NoStonithMeansWouldBeLeft(),
6557 )
6558
6559
6560 class NoStonithMeansWouldBeLeftDueToProperties(NameBuildTest):
6561 def test_success(self):
6562 self.assert_message_from_report(
6563 (
6564 "Setting property stonith-enabled to false or fencing-enabled"
6565 " to 0 leaves the cluster with no enabled means to fence nodes,"
6566 " resulting in the cluster not being able to recover from"
6567 " certain failure conditions"
6568 ),
6569 reports.NoStonithMeansWouldBeLeftDueToProperties(
6570 {"stonith-enabled": "false", "fencing-enabled": "0"}
6571 ),
6572 )
6573
6574
6575 class ParseErrorInvalidFileStructure(NameBuildTest):
6576 def test_no_path(self):
6577 self.assert_message_from_report(
6578 "Unable to parse known-hosts file: reason",
6579 reports.ParseErrorInvalidFileStructure(
6580 "reason", file_type_codes.PCS_KNOWN_HOSTS, None
6581 ),
6582 )
6583
6584 def test_path(self):
6585 self.assert_message_from_report(
6586 "Unable to parse known-hosts file '/foo/bar': reason",
6587 reports.ParseErrorInvalidFileStructure(
6588 "reason", file_type_codes.PCS_KNOWN_HOSTS, "/foo/bar"
6589 ),
6590 )
6591
6592
6593 class NodeReportsUnexpectedClusterName(NameBuildTest):
6594 def test_success(self):
6595 self.assert_message_from_report(
6596 "The node is not in the cluster named 'name'",
6597 reports.NodeReportsUnexpectedClusterName("name"),
6598 )
6599
6600
6601 class PcsCfgsyncSendingConfigsToNodes(NameBuildTest):
6602 def test_one_node(self):
6603 self.assert_message_from_report(
6604 "Sending file 'known-hosts' to node 'node1'",
6605 reports.PcsCfgsyncSendingConfigsToNodes(
6606 [file_type_codes.PCS_KNOWN_HOSTS], ["node1"]
6607 ),
6608 )
6609
6610 def test_multiple_nodes(self):
6611 self.assert_message_from_report(
6612 "Sending file 'known-hosts' to nodes 'node1', 'node2'",
6613 reports.PcsCfgsyncSendingConfigsToNodes(
6614 [file_type_codes.PCS_KNOWN_HOSTS], ["node1", "node2"]
6615 ),
6616 )
6617
6618 def test_multiple_files(self):
6619 self.assert_message_from_report(
6620 "Sending files 'known-hosts', 'pcs configuration' to node 'node1'",
6621 reports.PcsCfgsyncSendingConfigsToNodes(
6622 [
6623 file_type_codes.PCS_KNOWN_HOSTS,
6624 file_type_codes.PCS_SETTINGS_CONF,
6625 ],
6626 ["node1"],
6627 ),
6628 )
6629
6630
6631 class PcsCfgsyncSendingConfigsToNodesFailed(NameBuildTest):
6632 def test_one_node(self):
6633 self.assert_message_from_report(
6634 "Unable to save file 'known-hosts' on node 'node1'",
6635 reports.PcsCfgsyncSendingConfigsToNodesFailed(
6636 [file_type_codes.PCS_KNOWN_HOSTS], ["node1"]
6637 ),
6638 )
6639
6640 def test_multiple_nodes(self):
6641 self.assert_message_from_report(
6642 "Unable to save file 'known-hosts' on nodes 'node1', 'node2'",
6643 reports.PcsCfgsyncSendingConfigsToNodesFailed(
6644 [file_type_codes.PCS_KNOWN_HOSTS], ["node1", "node2"]
6645 ),
6646 )
6647
6648 def test_multiple_files(self):
6649 self.assert_message_from_report(
6650 "Unable to save files 'known-hosts', 'pcs configuration' on node 'node1'",
6651 reports.PcsCfgsyncSendingConfigsToNodesFailed(
6652 [
6653 file_type_codes.PCS_KNOWN_HOSTS,
6654 file_type_codes.PCS_SETTINGS_CONF,
6655 ],
6656 ["node1"],
6657 ),
6658 )
6659
6660
6661 class PcsCfgsyncConfigAccepted(NameBuildTest):
6662 def test_success(self):
6663 self.assert_message_from_report(
6664 "The known-hosts file saved successfully",
6665 reports.PcsCfgsyncConfigAccepted(file_type_codes.PCS_KNOWN_HOSTS),
6666 )
6667
6668
6669 class PcsCfgsyncConfigRejected(NameBuildTest):
6670 def test_success(self):
6671 self.assert_message_from_report(
6672 (
6673 "The known-hosts file not saved, a newer version of the file "
6674 "exists on the node"
6675 ),
6676 reports.PcsCfgsyncConfigRejected(file_type_codes.PCS_KNOWN_HOSTS),
6677 )
6678
6679
6680 class PcsCfgsyncConfigSaveError(NameBuildTest):
6681 def test_success(self):
6682 self.assert_message_from_report(
6683 "The known-hosts file not saved",
6684 reports.PcsCfgsyncConfigSaveError(file_type_codes.PCS_KNOWN_HOSTS),
6685 )
6686
6687
6688 class PcsCfgsyncConfigUnsupported(NameBuildTest):
6689 def test_success(self):
6690 self.assert_message_from_report(
6691 (
6692 "The known-hosts file synchronization is not supported on this "
6693 "node"
6694 ),
6695 reports.PcsCfgsyncConfigUnsupported(
6696 file_type_codes.PCS_KNOWN_HOSTS
6697 ),
6698 )
6699
6700
6701 class PcsCfgsyncFetchingNewestConfig(NameBuildTest):
6702 def test_one_node(self):
6703 self.assert_message_from_report(
6704 (
6705 "Fetching the newest version of file 'known-hosts' from node "
6706 "'node1'"
6707 ),
6708 reports.PcsCfgsyncFetchingNewestConfig(
6709 [file_type_codes.PCS_KNOWN_HOSTS], ["node1"]
6710 ),
6711 )
6712
6713 def test_multiple_nodes(self):
6714 self.assert_message_from_report(
6715 (
6716 "Fetching the newest version of file 'known-hosts' from nodes "
6717 "'node1', 'node2'"
6718 ),
6719 reports.PcsCfgsyncFetchingNewestConfig(
6720 [file_type_codes.PCS_KNOWN_HOSTS], ["node1", "node2"]
6721 ),
6722 )
6723
6724 def test_multiple_files(self):
6725 self.assert_message_from_report(
6726 (
6727 "Fetching the newest version of files 'known-hosts', "
6728 "'pcs configuration' from node 'node1'"
6729 ),
6730 reports.PcsCfgsyncFetchingNewestConfig(
6731 [
6732 file_type_codes.PCS_KNOWN_HOSTS,
6733 file_type_codes.PCS_SETTINGS_CONF,
6734 ],
6735 ["node1"],
6736 ),
6737 )
6738
6739
6740 class PcsCfgsyncConflictRepeatAction(NameBuildTest):
6741 def test_success(self):
6742 self.assert_message_from_report(
6743 (
6744 "Configuration conflict detected. Some nodes had a newer "
6745 "configuration than the local node. Local node's configuration "
6746 "was updated. Please repeat the last action if appropriate."
6747 ),
6748 reports.PcsCfgsyncConflictRepeatAction(),
6749 )
6750
6751
6752 class MetaAttrsUnknownToPcmk(NameBuildTest):
6753 def test_single_option(self):
6754 self.assert_message_from_report(
6755 (
6756 "Resource meta attribute 'unknown' has no effect on cluster "
6757 "resource handling, meta attribute with effect: 'known'"
6758 ),
6759 reports.MetaAttrsUnknownToPcmk(
6760 ["unknown"], ["known"], ["primitive-meta"]
6761 ),
6762 )
6763
6764 def test_multiple_options(self):
6765 self.assert_message_from_report(
6766 (
6767 "Resource / stonith meta attributes 'unknown1', 'unknown2' "
6768 "have no effect on cluster resource handling, meta attributes "
6769 "with effect: 'known1', 'known2'"
6770 ),
6771 reports.MetaAttrsUnknownToPcmk(
6772 ["unknown1", "unknown2"],
6773 ["known1", "known2"],
6774 ["primitive-meta", "stonith-meta"],
6775 ),
6776 )
6777
6778
6779 class MetaAttrsNotValidatedUnsupportedType(NameBuildTest):
6780 def test_empty_options(self):
6781 self.assert_message_from_report(
6782 "Meta attributes are not validated",
6783 reports.MetaAttrsNotValidatedUnsupportedType([]),
6784 )
6785
6786 def test_single_option(self):
6787 self.assert_message_from_report(
6788 "Meta attributes of clone are not validated",
6789 reports.MetaAttrsNotValidatedUnsupportedType(["clone"]),
6790 )
6791
6792 def test_multiple_options(self):
6793 self.assert_message_from_report(
6794 (
6795 "Meta attributes of bundle / clone / group / resource are not "
6796 "validated"
6797 ),
6798 reports.MetaAttrsNotValidatedUnsupportedType(
6799 ["clone", "bundle", "group", "primitive"]
6800 ),
6801 )
6802
6803
6804 class MetaAttrsNotValidatedLoadingError(NameBuildTest):
6805 def test_success(self):
6806 self.assert_message_from_report(
6807 (
6808 "Meta attribute validation is skipped due to an error loading "
6809 "meta attributes definition."
6810 ),
6811 reports.MetaAttrsNotValidatedLoadingError(),
6812 )
6813
6814
6815 class NodeNotInCluster(NameBuildTest):
6816 def test_success(self):
6817 self.assert_message_from_report(
6818 "The node does not currently have a cluster configured",
6819 reports.NodeNotInCluster(),
6820 )
6821
6822
6823 class ClusterNameAlreadyInUse(NameBuildTest):
6824 def test_success(self):
6825 self.assert_message_from_report(
6826 "The cluster name 'foo' is already used",
6827 reports.ClusterNameAlreadyInUse("foo"),
6828 )
6829
6830
6831 class UnableToGetClusterInfoFromStatus(NameBuildTest):
6832 def test_success(self):
6833 self.assert_message_from_report(
6834 "Unable to retrieve cluster information from node status",
6835 reports.UnableToGetClusterInfoFromStatus(),
6836 )
6837
6838
6839 class UnableToGetClusterKnownHosts(NameBuildTest):
6840 def test_success(self):
6841 self.assert_message_from_report(
6842 "Unable to get known hosts from cluster 'foo'",
6843 reports.UnableToGetClusterKnownHosts("foo"),
6844 )
6845
6846
6847 class CibResourceSecretUnableToGet(NameBuildTest):
6848 def test_success(self):
6849 self.assert_message_from_report(
6850 "Unable to get secret 'secret_name' for resource 'resource_id'",
6851 reports.CibResourceSecretUnableToGet(
|
CID (unavailable; MK=3c1b63e281c648382217e184ef14dad2) (#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. |
6852 "resource_id", "secret_name", "reason"
6853 ),
6854 )
6855
6856
6857 class PermissionDuplication(NameBuildTest):
6858 def test_success(self):
6859 self.assert_message_from_report(
6860 (
6861 "Permissions must be unique, duplicate permissions for "
6862 "user: 'john', group: 'haclient'"
6863 ),
6864 reports.PermissionDuplication(
6865 [
6866 ("john", PermissionTargetType.USER),
6867 ("haclient", PermissionTargetType.GROUP),
6868 ]
6869 ),
6870 )
6871
6872
6873 class NotAuthorizedToChangeFullPermission(NameBuildTest):
6874 def test_success(self):
6875 self.assert_message_from_report(
6876 (
6877 "Current user is not authorized for this operation.\n"
6878 "Only hacluster and users with Full permission can grant or "
6879 "revoke Full permission."
6880 ),
6881 reports.NotAuthorizedToChangeFullPermission(),
6882 )
6883
6884
6885 class UseCommandClusterRename(NameBuildTest):
6886 def test_success(self):
6887 self.assert_message_from_report(
6888 "This command cannot be used for renaming a cluster",
6889 reports.UseCommandClusterRename(),
6890 )
6891