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