1 from textwrap import dedent
2 from unittest import TestCase, skip
3
4 from lxml import etree
5
6 from pcs import resource, utils
7 from pcs.common import const
8 from pcs.common.str_tools import format_list, format_plural
9 from pcs.constraint import LOCATION_NODE_VALIDATION_SKIP_MSG
10 from pcs.lib.resource_agent import const as ra_const
11
12 from pcs_test.tier1.cib_resource.common import (
13 ResourceTest,
14 fixture_meta_attributes_not_validated_warning,
15 fixture_meta_attributes_warning,
16 fixture_use_meta_command_instead_warning,
17 )
18 from pcs_test.tier1.legacy.common import FIXTURE_UTILIZATION_WARNING
19 from pcs_test.tools.assertions import AssertPcsMixin, assert_pcs_status
20 from pcs_test.tools.bin_mock import get_mock_settings
21 from pcs_test.tools.cib import get_assert_pcs_effect_mixin
22 from pcs_test.tools.fixture_cib import (
23 CachedCibFixture,
24 fixture_master_xml,
25 fixture_to_cib,
26 wrap_element_by_master,
27 wrap_element_by_master_file,
28 )
29 from pcs_test.tools.misc import get_test_resource as rc
30 from pcs_test.tools.misc import (
31 get_tmp_file,
32 is_minimum_pacemaker_version,
33 is_pacemaker_21_without_20_compatibility,
34 outdent,
35 skip_unless_crm_rule,
36 skip_unless_pacemaker_supports_op_onfail_demote,
37 write_data_to_tmpfile,
38 write_file_to_tmpfile,
39 )
40 from pcs_test.tools.pcs_runner import PcsRunner
41 from pcs_test.tools.xml import XmlManipulation, etree_to_str
42
43 PCMK_2_0_3_PLUS = is_minimum_pacemaker_version(2, 0, 3)
44
45 LOCATION_NODE_VALIDATION_SKIP_WARNING = (
46 f"Warning: {LOCATION_NODE_VALIDATION_SKIP_MSG}\n"
47 )
48 ERRORS_HAVE_OCCURRED = (
49 "Error: Errors have occurred, therefore pcs is unable to continue\n"
50 )
51 DEPRECATED_DASH_DASH_GROUP = (
52 "Deprecation Warning: Using '--group' is deprecated and will be replaced "
53 "with 'group' in a future release. Specify --future to switch to the future "
54 "behavior.\n"
55 )
56
57
58 def fixture_message_not_deleting_resources_not_live(resource_ids):
59 resources = format_plural(resource_ids, "resource")
60 are = format_plural(resource_ids, "is")
61 return (
62 "Warning: Resources are not going to be stopped before deletion "
63 "because the command does not run on a live cluster\n"
64 f"Warning: Not checking if {resources} {format_list(resource_ids)} "
65 f"{are} stopped before deletion because the command does not run on a "
66 "live cluster. Deleting unstopped resources may result in orphaned "
67 "resources being present in the cluster.\n"
68 )
69
70
71 empty_cib = rc("cib-empty.xml")
72 large_cib = rc("cib-large.xml")
73
74
75 class ResourceDescribe(TestCase, AssertPcsMixin):
76 def setUp(self):
77 self.pcs_runner = PcsRunner(None)
78 self.pcs_runner.mock_settings = get_mock_settings()
79
80 @staticmethod
81 def fixture_description(advanced=False):
82 advanced_params = """
83 advanced (advanced use only)
84 Description: This parameter should not be set usually
85 Type: string"""
86 return dedent(
87 """\
88 ocf:pcsmock:params - Mock agent for pcs tests - agent with various parameters
89
90 This is a mock agent for pcs test - agent with parameters
91
92 Resource options:
93 mandatory (required)
94 Description: A generic mandatory string parameter
95 Type: string
96 optional
97 Description: A generic optional string parameter
98 Type: string
99 Default: if not specified
100 enum
101 Description: An optional enum parameter
102 Allowed values: 'value1', 'value2', 'value3'
103 Default: value1{0}
104 unique1 (unique group: group-A)
105 Description: First parameter in a unique group
106 Type: string
107 unique2 (unique group: group-A)
108 Description: Second parameter in a unique group
109 Type: string
110
111 Default operations:
112 start:
113 interval=0s timeout=20s
114 stop:
115 interval=0s timeout=20s
116 monitor:
117 interval=10s timeout=20s
118 reload:
119 interval=0s timeout=20s
120 reload-agent:
121 interval=0s timeout=20s
122 migrate_to:
123 interval=0s timeout=20s
124 migrate_from:
125 interval=0s timeout=20s
126 """.format(advanced_params if advanced else "")
127 )
128
129 def test_success(self):
130 self.assert_pcs_success(
131 "resource describe ocf:pcsmock:params".split(),
132 self.fixture_description(),
133 )
134
135 def test_full(self):
136 self.assert_pcs_success(
137 "resource describe ocf:pcsmock:params --full".split(),
138 self.fixture_description(True),
139 )
140
141 def test_success_guess_name(self):
142 self.assert_pcs_success(
143 "resource describe params".split(),
144 stdout_full=self.fixture_description(),
145 stderr_full=(
146 "Assumed agent name 'ocf:pcsmock:params' (deduced from 'params')\n"
147 ),
148 )
149
150 def test_nonextisting_agent(self):
151 self.assert_pcs_fail(
152 "resource describe ocf:pcsmock:nonexistent".split(),
153 (
154 "Error: Agent 'ocf:pcsmock:nonexistent' is not installed or does "
155 "not provide valid metadata: "
156 "pcs mock error message: unable to load agent metadata\n"
157 + ERRORS_HAVE_OCCURRED
158 ),
159 )
160
161 def test_nonextisting_agent_guess_name(self):
162 self.assert_pcs_fail(
163 "resource describe nonexistent".split(),
164 (
165 "Error: Unable to find agent 'nonexistent', try specifying"
166 " its full name\n"
167 ),
168 )
169
170 def test_more_agents_guess_name(self):
171 self.assert_pcs_fail(
172 "resource describe pcsmock".split(),
173 (
174 "Error: Multiple agents match 'pcsmock', please specify full"
175 " name: 'ocf:heartbeat:pcsMock' or 'ocf:pacemaker:pcsMock'\n"
176 ),
177 )
178
179 def test_not_enough_params(self):
180 self.assert_pcs_fail(
181 "resource describe".split(),
182 stderr_start="\nUsage: pcs resource describe...\n",
183 )
184
185 def test_too_many_params(self):
186 self.assert_pcs_fail(
187 "resource describe agent1 agent2".split(),
188 stderr_start="\nUsage: pcs resource describe...\n",
189 )
190
191
192 class ResourceTestCibFixture(CachedCibFixture):
193 def _setup_cib(self):
194 self.assert_pcs_success_ignore_output(
195 (
196 "resource create --no-default-ops ClusterIP ocf:pcsmock:minimal"
197 ).split()
198 )
199 self.assert_pcs_success_ignore_output(
200 (
201 "resource create --no-default-ops ClusterIP2 ocf:pcsmock:minimal"
202 ).split()
203 )
204 self.assert_pcs_success_ignore_output(
205 (
206 "resource create --no-default-ops ClusterIP3 ocf:pcsmock:minimal"
207 ).split()
208 )
209 self.assert_pcs_success_ignore_output(
210 (
211 "resource create --no-default-ops ClusterIP4 ocf:pcsmock:minimal"
212 ).split()
213 )
214 self.assert_pcs_success_ignore_output(
215 (
216 "resource create --no-default-ops ClusterIP5 ocf:pcsmock:minimal"
217 ).split()
218 )
219 self.assert_pcs_success_ignore_output(
220 (
221 "resource create --no-default-ops ClusterIP6 ocf:pcsmock:minimal"
222 ).split()
223 )
224 self.assert_pcs_success(
225 "resource group add TestGroup1 ClusterIP".split()
226 )
227 self.assert_pcs_success(
228 "resource group add TestGroup2 ClusterIP2 ClusterIP3".split()
229 )
230 self.assert_pcs_success("resource clone ClusterIP4".split())
231 # pcs no longer allows turning resources into masters but supports
232 # existing ones. In order to test it, we need to put a master in the
233 # CIB without pcs.
234 wrap_element_by_master_file(
235 self.cache_path, "ClusterIP5", master_id="Master"
236 )
237
238
239 CIB_FIXTURE = ResourceTestCibFixture("fixture_tier1_resource", empty_cib)
240
241
242 class Resource(TestCase, AssertPcsMixin):
243 def setUp(self):
244 self.temp_cib = get_tmp_file("tier1_resource")
245 self.temp_large_cib = get_tmp_file("tier1_resource_large")
246 write_file_to_tmpfile(empty_cib, self.temp_cib)
247 write_file_to_tmpfile(large_cib, self.temp_large_cib)
248 self.pcs_runner = PcsRunner(self.temp_cib.name)
249 self.pcs_runner.mock_settings = get_mock_settings()
250
251 def tearDown(self):
252 self.temp_cib.close()
253 self.temp_large_cib.close()
254
255 # Setups up a cluster with Resources, groups, master/slave resource & clones
256 def setup_cluster_a(self):
257 write_file_to_tmpfile(CIB_FIXTURE.cache_path, self.temp_cib)
258
259 def test_case_insensitive(self):
260 self.assert_pcs_fail(
261 "resource create --no-default-ops D0 pcsmock".split(),
262 (
263 "Error: Multiple agents match 'pcsmock', please specify full name: "
264 "'ocf:heartbeat:pcsMock' or 'ocf:pacemaker:pcsMock'\n"
265 + ERRORS_HAVE_OCCURRED
266 ),
267 )
268
269 self.assert_pcs_success(
270 "resource create --no-default-ops D1 camelcase".split(),
271 stderr_full=(
272 "Assumed agent name 'ocf:pcsmock:CamelCase'"
273 " (deduced from 'camelcase')\n"
274 ),
275 )
276
277 self.assert_pcs_success(
278 "resource create --no-default-ops D2 CAMELCASE".split(),
279 stderr_full=(
280 "Assumed agent name 'ocf:pcsmock:CamelCase'"
281 " (deduced from 'CAMELCASE')\n"
282 ),
283 )
284
285 self.assert_pcs_fail(
286 "resource create --no-default-ops D4 camel_case".split(),
287 (
288 "Error: Unable to find agent 'camel_case', try specifying its full name\n"
289 + ERRORS_HAVE_OCCURRED
290 ),
291 )
292
293 def test_empty(self):
294 self.assert_pcs_success(["resource"], "NO resources configured\n")
295
296 def test_add_resources_large_cib(self):
297 self.pcs_runner = PcsRunner(self.temp_large_cib.name)
298 self.pcs_runner.mock_settings = get_mock_settings()
299 self.assert_pcs_success(
300 "resource create dummy0 ocf:pcsmock:minimal --no-default-ops".split(),
301 )
302 self.assert_pcs_success(
303 "resource config dummy0".split(),
304 dedent(
305 """\
306 Resource: dummy0 (class=ocf provider=pcsmock type=minimal)
307 Operations:
308 monitor: dummy0-monitor-interval-10s
309 interval=10s timeout=20s
310 """
311 ),
312 )
313
314 def test_resource_show(self):
315 self.assert_pcs_success(
316 (
317 "resource create --no-default-ops ClusterIP ocf:pcsmock:params"
318 " mandatory=mandat optional=opti op monitor interval=30s"
319 ).split()
320 )
321 self.assert_pcs_success(
322 "resource config ClusterIP".split(),
323 dedent(
324 """\
325 Resource: ClusterIP (class=ocf provider=pcsmock type=params)
326 Attributes: ClusterIP-instance_attributes
327 mandatory=mandat
328 optional=opti
329 Operations:
330 monitor: ClusterIP-monitor-interval-30s
331 interval=30s
332 """
333 ),
334 )
335
336 def test_add_operation(self):
337 # see also BundleMiscCommands
338 self.assert_pcs_success(
339 (
340 "resource create --no-default-ops ClusterIP ocf:pcsmock:minimal"
341 " op monitor interval=30s"
342 ).split()
343 )
344
345 self.assert_pcs_fail(
346 "resource op add".split(),
347 stderr_start="\nUsage: pcs resource op add...",
348 )
349 self.assert_pcs_fail(
350 "resource op remove".split(),
351 stderr_start="\nUsage: pcs resource op remove...",
352 )
353
354 self.assert_pcs_fail(
355 "resource op add ClusterIP monitor interval=31s".split(),
356 (
357 "Error: operation monitor already specified for ClusterIP, "
358 "use --force to override:\n"
359 "monitor interval=30s (ClusterIP-monitor-interval-30s)\n"
360 ),
361 )
362
363 self.assert_pcs_success(
364 "resource op add ClusterIP monitor interval=31s --force".split(),
365 )
366
367 self.assert_pcs_fail(
368 "resource op add ClusterIP monitor interval=31s".split(),
369 (
370 "Error: operation monitor with interval 31s already specified "
371 "for ClusterIP:\n"
372 "monitor interval=31s (ClusterIP-monitor-interval-31s)\n"
373 ),
374 )
375
376 self.assert_pcs_fail(
377 "resource op add ClusterIP monitor interval=31".split(),
378 (
379 "Error: operation monitor with interval 31s already specified "
380 "for ClusterIP:\n"
381 "monitor interval=31s (ClusterIP-monitor-interval-31s)\n"
382 ),
383 )
384
385 self.assert_pcs_fail(
386 "resource op add ClusterIP moni=tor interval=60".split(),
387 "Error: moni=tor does not appear to be a valid operation action\n",
388 )
389
390 self.assert_pcs_success(
391 "resource config ClusterIP".split(),
392 dedent(
393 """\
394 Resource: ClusterIP (class=ocf provider=pcsmock type=minimal)
395 Operations:
396 monitor: ClusterIP-monitor-interval-30s
397 interval=30s
398 monitor: ClusterIP-monitor-interval-31s
399 interval=31s
400 """
401 ),
402 )
403
404 self.assert_pcs_success(
405 (
406 "resource create --no-default-ops OPTest ocf:pcsmock:minimal "
407 "op monitor interval=30s OCF_CHECK_LEVEL=1 "
408 "op monitor interval=25s OCF_CHECK_LEVEL=1 enabled=0"
409 ).split(),
410 )
411
412 self.assert_pcs_success(
413 "resource config OPTest".split(),
414 dedent(
415 """\
416 Resource: OPTest (class=ocf provider=pcsmock type=minimal)
417 Operations:
418 monitor: OPTest-monitor-interval-30s
419 interval=30s OCF_CHECK_LEVEL=1
420 monitor: OPTest-monitor-interval-25s
421 interval=25s enabled=0 OCF_CHECK_LEVEL=1
422 """
423 ),
424 )
425
426 self.assert_pcs_success(
427 (
428 "resource create --no-default-ops OPTest2 ocf:pcsmock:minimal "
429 "op monitor interval=30s OCF_CHECK_LEVEL=1 "
430 "op monitor interval=25s OCF_CHECK_LEVEL=2 "
431 "op start timeout=30s"
432 ).split(),
433 )
434
435 self.assert_pcs_fail(
436 "resource op add OPTest2 start timeout=1800s".split(),
437 (
438 "Error: operation start with interval 0s already specified for OPTest2:\n"
439 "start interval=0s timeout=30s (OPTest2-start-interval-0s)\n"
440 ),
441 )
442
443 self.assert_pcs_fail(
444 "resource op add OPTest2 start interval=100".split(),
445 (
446 "Error: operation start already specified for OPTest2, use --force to override:\n"
447 "start interval=0s timeout=30s (OPTest2-start-interval-0s)\n"
448 ),
449 )
450
451 self.assert_pcs_success(
452 "resource op add OPTest2 monitor timeout=1800s".split(),
453 )
454
455 self.assert_pcs_success(
456 "resource config OPTest2".split(),
457 dedent(
458 """\
459 Resource: OPTest2 (class=ocf provider=pcsmock type=minimal)
460 Operations:
461 monitor: OPTest2-monitor-interval-30s
462 interval=30s OCF_CHECK_LEVEL=1
463 monitor: OPTest2-monitor-interval-25s
464 interval=25s OCF_CHECK_LEVEL=2
465 start: OPTest2-start-interval-0s
466 interval=0s timeout=30s
467 monitor: OPTest2-monitor-interval-60s
468 interval=60s timeout=1800s
469 """
470 ),
471 )
472
473 self.assert_pcs_success(
474 (
475 "resource create --no-default-ops OPTest3 ocf:pcsmock:minimal "
476 "op monitor OCF_CHECK_LEVEL=1"
477 ).split(),
478 )
479
480 self.assert_pcs_success(
481 "resource config OPTest3".split(),
482 dedent(
483 """\
484 Resource: OPTest3 (class=ocf provider=pcsmock type=minimal)
485 Operations:
486 monitor: OPTest3-monitor-interval-60s
487 interval=60s OCF_CHECK_LEVEL=1
488 """
489 ),
490 )
491
492 self.assert_pcs_success(
493 (
494 "resource create --no-default-ops OPTest4 ocf:pcsmock:minimal "
495 "op monitor interval=30s"
496 ).split(),
497 )
498
499 self.assert_pcs_success(
500 "resource update OPTest4 op monitor OCF_CHECK_LEVEL=1".split(),
501 )
502
503 self.assert_pcs_success(
504 "resource config OPTest4".split(),
505 dedent(
506 """\
507 Resource: OPTest4 (class=ocf provider=pcsmock type=minimal)
508 Operations:
509 monitor: OPTest4-monitor-interval-60s
510 interval=60s OCF_CHECK_LEVEL=1
511 """
512 ),
513 )
514
515 self.assert_pcs_success(
516 "resource create --no-default-ops OPTest5 ocf:pcsmock:minimal".split(),
517 )
518
519 self.assert_pcs_success(
520 "resource update OPTest5 op monitor OCF_CHECK_LEVEL=1".split(),
521 )
522
523 self.assert_pcs_success(
524 "resource config OPTest5".split(),
525 dedent(
526 """\
527 Resource: OPTest5 (class=ocf provider=pcsmock type=minimal)
528 Operations:
529 monitor: OPTest5-monitor-interval-60s
530 interval=60s OCF_CHECK_LEVEL=1
531 """
532 ),
533 )
534
535 self.assert_pcs_success(
536 "resource create --no-default-ops OPTest6 ocf:pcsmock:minimal".split(),
537 )
538
539 self.assert_pcs_success(
540 "resource op add OPTest6 monitor interval=30s OCF_CHECK_LEVEL=1".split(),
541 )
542
543 self.assert_pcs_success(
544 "resource config OPTest6".split(),
545 dedent(
546 """\
547 Resource: OPTest6 (class=ocf provider=pcsmock type=minimal)
548 Operations:
549 monitor: OPTest6-monitor-interval-10s
550 interval=10s timeout=20s
551 monitor: OPTest6-monitor-interval-30s
552 interval=30s OCF_CHECK_LEVEL=1
553 """
554 ),
555 )
556
557 self.assert_pcs_success(
558 "resource create --no-default-ops OPTest7 ocf:pcsmock:minimal".split(),
559 )
560
561 self.assert_pcs_success(
562 "resource update OPTest7 op monitor interval=60s OCF_CHECK_LEVEL=1".split(),
563 )
564
565 self.assert_pcs_fail(
566 "resource op add OPTest7 monitor interval=61s OCF_CHECK_LEVEL=1".split(),
567 (
568 "Error: operation monitor already specified for OPTest7, use --force to override:\n"
569 "monitor interval=60s OCF_CHECK_LEVEL=1 (OPTest7-monitor-interval-60s)\n"
570 ),
571 )
572
573 self.assert_pcs_success(
574 "resource op add OPTest7 monitor interval=61s OCF_CHECK_LEVEL=1 --force".split(),
575 )
576
577 self.assert_pcs_success(
578 "resource config OPTest7".split(),
579 dedent(
580 """\
581 Resource: OPTest7 (class=ocf provider=pcsmock type=minimal)
582 Operations:
583 monitor: OPTest7-monitor-interval-60s
584 interval=60s OCF_CHECK_LEVEL=1
585 monitor: OPTest7-monitor-interval-61s
586 interval=61s OCF_CHECK_LEVEL=1
587 """
588 ),
589 )
590
591 self.assert_pcs_fail(
592 "resource op add OPTest7 monitor interval=60s OCF_CHECK_LEVEL=1".split(),
593 (
594 "Error: operation monitor with interval 60s already specified for OPTest7:\n"
595 "monitor interval=60s OCF_CHECK_LEVEL=1 (OPTest7-monitor-interval-60s)\n"
596 ),
597 )
598
599 self.assert_pcs_success(
600 "resource create --no-default-ops OCFTest1 ocf:pcsmock:minimal".split(),
601 )
602
603 self.assert_pcs_fail(
604 "resource op add OCFTest1 monitor interval=31s".split(),
605 (
606 "Error: operation monitor already specified for OCFTest1, use --force to override:\n"
607 "monitor interval=10s timeout=20s (OCFTest1-monitor-interval-10s)\n"
608 ),
609 )
610
611 self.assert_pcs_success(
612 "resource op add OCFTest1 monitor interval=31s --force".split(),
613 )
614
615 self.assert_pcs_success(
616 "resource op add OCFTest1 monitor interval=30s OCF_CHECK_LEVEL=15".split(),
617 )
618
619 self.assert_pcs_success(
620 "resource config OCFTest1".split(),
621 dedent(
622 """\
623 Resource: OCFTest1 (class=ocf provider=pcsmock type=minimal)
624 Operations:
625 monitor: OCFTest1-monitor-interval-10s
626 interval=10s timeout=20s
627 monitor: OCFTest1-monitor-interval-31s
628 interval=31s
629 monitor: OCFTest1-monitor-interval-30s
630 interval=30s OCF_CHECK_LEVEL=15
631 """
632 ),
633 )
634
635 self.assert_pcs_success(
636 "resource update OCFTest1 op monitor interval=61s OCF_CHECK_LEVEL=5".split(),
637 )
638
639 self.assert_pcs_success(
640 "resource config OCFTest1".split(),
641 dedent(
642 """\
643 Resource: OCFTest1 (class=ocf provider=pcsmock type=minimal)
644 Operations:
645 monitor: OCFTest1-monitor-interval-61s
646 interval=61s OCF_CHECK_LEVEL=5
647 monitor: OCFTest1-monitor-interval-31s
648 interval=31s
649 monitor: OCFTest1-monitor-interval-30s
650 interval=30s OCF_CHECK_LEVEL=15
651 """
652 ),
653 )
654
655 self.assert_pcs_success(
656 "resource update OCFTest1 op monitor OCF_CHECK_LEVEL=4".split(),
657 )
658
659 self.assert_pcs_success(
660 "resource config OCFTest1".split(),
661 dedent(
662 """\
663 Resource: OCFTest1 (class=ocf provider=pcsmock type=minimal)
664 Operations:
665 monitor: OCFTest1-monitor-interval-60s
666 interval=60s OCF_CHECK_LEVEL=4
667 monitor: OCFTest1-monitor-interval-31s
668 interval=31s
669 monitor: OCFTest1-monitor-interval-30s
670 interval=30s OCF_CHECK_LEVEL=15
671 """
672 ),
673 )
674
675 self.assert_pcs_success(
676 "resource update OCFTest1 op monitor OCF_CHECK_LEVEL=4 interval=35s".split(),
677 )
678
679 self.assert_pcs_success(
680 "resource config OCFTest1".split(),
681 dedent(
682 """\
683 Resource: OCFTest1 (class=ocf provider=pcsmock type=minimal)
684 Operations:
685 monitor: OCFTest1-monitor-interval-35s
686 interval=35s OCF_CHECK_LEVEL=4
687 monitor: OCFTest1-monitor-interval-31s
688 interval=31s
689 monitor: OCFTest1-monitor-interval-30s
690 interval=30s OCF_CHECK_LEVEL=15
691 """
692 ),
693 )
694
695 self.assert_pcs_success(
696 "resource create --no-default-ops state ocf:pcsmock:stateful".split(),
697 )
698
699 self.assert_pcs_fail(
700 "resource op add state monitor interval=10".split(),
701 (
702 "Error: operation monitor with interval 10s already specified for state:\n"
703 "monitor interval=10s role=Master timeout=20s (state-monitor-interval-10s)\n"
704 ),
705 )
706
707 self.assert_pcs_fail(
708 "resource op add state monitor interval=10 role=Started".split(),
709 (
710 "Error: operation monitor with interval 10s already specified for state:\n"
711 "monitor interval=10s role=Master timeout=20s (state-monitor-interval-10s)\n"
712 ),
713 )
714
715 self.assert_pcs_success(
716 "resource config state".split(),
717 dedent(
718 f"""\
719 Resource: state (class=ocf provider=pcsmock type=stateful)
720 Operations:
721 monitor: state-monitor-interval-10s
722 interval=10s timeout=20s role={const.PCMK_ROLE_PROMOTED}
723 monitor: state-monitor-interval-11s
724 interval=11s timeout=20s role={const.PCMK_ROLE_UNPROMOTED}
725 """
726 ),
727 )
728
729 @skip_unless_pacemaker_supports_op_onfail_demote()
730 def test_add_operation_onfail_demote_upgrade_cib(self):
731 write_file_to_tmpfile(rc("cib-empty-3.3.xml"), self.temp_cib)
732 self.assert_pcs_success(
733 "resource create --no-default-ops R ocf:pcsmock:minimal".split()
734 )
735 self.assert_pcs_success(
736 "resource op add R start on-fail=demote".split(),
737 stderr_full="Cluster CIB has been upgraded to latest version\n",
738 )
739
740 @skip_unless_pacemaker_supports_op_onfail_demote()
741 def test_update_add_operation_onfail_demote_upgrade_cib(self):
742 write_file_to_tmpfile(rc("cib-empty-3.3.xml"), self.temp_cib)
743 self.assert_pcs_success(
744 "resource create --no-default-ops R ocf:pcsmock:minimal".split()
745 )
746 self.assert_pcs_success(
747 "resource update R op start on-fail=demote".split(),
748 stderr_full="Cluster CIB has been upgraded to latest version\n",
749 )
750
751 def _test_delete_remove_operation(self, command):
752 assert command in {"delete", "remove"}
753
754 self.assert_pcs_success(
755 (
756 "resource create --no-default-ops ClusterIP ocf:pcsmock:minimal"
757 " op monitor interval=30s"
758 ).split()
759 )
760
761 self.assert_pcs_success(
762 "resource op add ClusterIP monitor interval=31s --force".split()
763 )
764
765 self.assert_pcs_success(
766 "resource op add ClusterIP monitor interval=32s --force".split()
767 )
768
769 self.assert_pcs_fail(
770 f"resource op {command} ClusterIP-monitor-interval-32s-xxxxx".split(),
771 (
772 "Error: unable to find operation id: "
773 "ClusterIP-monitor-interval-32s-xxxxx\n"
774 ),
775 )
776
777 self.assert_pcs_success(
778 f"resource op {command} ClusterIP-monitor-interval-32s".split()
779 )
780
781 self.assert_pcs_success(
782 f"resource op {command} ClusterIP monitor interval=30s".split()
783 )
784
785 self.assert_pcs_fail(
786 f"resource op {command} ClusterIP monitor interval=30s".split(),
787 "Error: Unable to find operation matching: monitor interval=30s\n",
788 )
789
790 self.assert_pcs_success(
791 "resource config ClusterIP".split(),
792 dedent(
793 """\
794 Resource: ClusterIP (class=ocf provider=pcsmock type=minimal)
795 Operations:
796 monitor: ClusterIP-monitor-interval-31s
797 interval=31s
798 """
799 ),
800 )
801
802 self.assert_pcs_success(
803 f"resource op {command} ClusterIP monitor interval=31s".split()
804 )
805
806 self.assert_pcs_success(
807 "resource config ClusterIP".split(),
808 dedent(
809 """\
810 Resource: ClusterIP (class=ocf provider=pcsmock type=minimal)
811 """
812 ),
813 )
814
815 self.assert_pcs_success(
816 "resource op add ClusterIP monitor interval=31s".split()
817 )
818
819 self.assert_pcs_success(
820 "resource op add ClusterIP monitor interval=32s --force".split()
821 )
822
823 self.assert_pcs_success(
824 "resource op add ClusterIP stop timeout=34s".split()
825 )
826
827 self.assert_pcs_success(
828 "resource op add ClusterIP start timeout=33s".split()
829 )
830
831 self.assert_pcs_success(
832 f"resource op {command} ClusterIP monitor".split()
833 )
834
835 self.assert_pcs_success(
836 "resource config ClusterIP".split(),
837 dedent(
838 """\
839 Resource: ClusterIP (class=ocf provider=pcsmock type=minimal)
840 Operations:
841 stop: ClusterIP-stop-interval-0s
842 interval=0s timeout=34s
843 start: ClusterIP-start-interval-0s
844 interval=0s timeout=33s
845 """
846 ),
847 )
848
849 def test_delete_operation(self):
850 # see also BundleMiscCommands
851 self.assert_pcs_fail(
852 "resource op delete".split(),
853 stderr_start="\nUsage: pcs resource op delete...",
854 )
855
856 self._test_delete_remove_operation("delete")
857
858 def test_remove_operation(self):
859 # see also BundleMiscCommands
860 self.assert_pcs_fail(
861 "resource op remove".split(),
862 stderr_start="\nUsage: pcs resource op remove...",
863 )
864
865 self._test_delete_remove_operation("remove")
866
867 def test_update_operation(self):
868 self.assert_pcs_success(
869 (
870 "resource create --no-default-ops ClusterIP ocf:pcsmock:params"
871 " mandatory=value op monitor interval=30s"
872 ).split()
873 )
874 self.assert_pcs_success(
875 "resource config ClusterIP".split(),
876 dedent(
877 """\
878 Resource: ClusterIP (class=ocf provider=pcsmock type=params)
879 Attributes: ClusterIP-instance_attributes
880 mandatory=value
881 Operations:
882 monitor: ClusterIP-monitor-interval-30s
883 interval=30s
884 """
885 ),
886 )
887
888 self.assert_pcs_success(
889 "resource update ClusterIP op monitor interval=32s".split()
890 )
891 self.assert_pcs_success(
892 "resource config ClusterIP".split(),
893 dedent(
894 """\
895 Resource: ClusterIP (class=ocf provider=pcsmock type=params)
896 Attributes: ClusterIP-instance_attributes
897 mandatory=value
898 Operations:
899 monitor: ClusterIP-monitor-interval-32s
900 interval=32s
901 """
902 ),
903 )
904
905 show_clusterip = dedent(
906 """\
907 Resource: ClusterIP (class=ocf provider=pcsmock type=params)
908 Attributes: ClusterIP-instance_attributes
909 mandatory=value
910 Operations:
911 monitor: ClusterIP-monitor-interval-33s
912 interval=33s
913 start: ClusterIP-start-interval-30s
914 interval=30s timeout=180s
915 """
916 )
917 self.assert_pcs_success(
918 "resource update ClusterIP op monitor interval=33s start interval=30s timeout=180s".split()
919 )
920 self.assert_pcs_success(
921 "resource config ClusterIP".split(), show_clusterip
922 )
923
924 self.assert_pcs_success(
925 "resource update ClusterIP op monitor interval=33s start interval=30s timeout=180s".split()
926 )
927 self.assert_pcs_success(
928 "resource config ClusterIP".split(), show_clusterip
929 )
930
931 self.assert_pcs_success("resource update ClusterIP op".split())
932 self.assert_pcs_success(
933 "resource config ClusterIP".split(), show_clusterip
934 )
935
936 self.assert_pcs_success("resource update ClusterIP op monitor".split())
937 self.assert_pcs_success(
938 "resource config ClusterIP".split(), show_clusterip
939 )
940
941 # test invalid id
942 self.assert_pcs_fail_regardless_of_force(
943 "resource update ClusterIP op monitor interval=30 id=ab#cd".split(),
944 (
945 "Error: invalid operation id 'ab#cd', '#' is not a valid character"
946 " for a operation id\n"
947 ),
948 )
949 self.assert_pcs_success(
950 "resource config ClusterIP".split(), show_clusterip
951 )
952
953 # test existing id
954 self.assert_pcs_fail_regardless_of_force(
955 "resource update ClusterIP op monitor interval=30 id=ClusterIP".split(),
956 (
957 "Error: id 'ClusterIP' is already in use, please specify another"
958 " one\n"
959 ),
960 )
961 self.assert_pcs_success(
962 "resource config ClusterIP".split(), show_clusterip
963 )
964
965 # test id change
966 # there is a bug:
967 # - first an existing operation is removed
968 # - then a new operation is created at the same place
969 # - therefore options not specified for in the command are removed
970 # instead of them being kept from the old operation
971 # This needs to be fixed. However it's not my task currently.
972 # Moreover it is documented behavior.
973 self.assert_pcs_success(
974 "resource update ClusterIP op monitor id=abcd".split()
975 )
976 self.assert_pcs_success(
977 "resource config ClusterIP".split(),
978 dedent(
979 """\
980 Resource: ClusterIP (class=ocf provider=pcsmock type=params)
981 Attributes: ClusterIP-instance_attributes
982 mandatory=value
983 Operations:
984 monitor: abcd
985 interval=60s
986 start: ClusterIP-start-interval-30s
987 interval=30s timeout=180s
988 """
989 ),
990 )
991
992 # test two monitor operations:
993 # - the first one is updated
994 # - operation duplicity detection test
995 self.assert_pcs_success(
996 "resource create A ocf:pcsmock:minimal op monitor interval=10 op monitor interval=20".split()
997 )
998 self.assert_pcs_success(
999 "resource config A".split(),
1000 dedent(
1001 """\
1002 Resource: A (class=ocf provider=pcsmock type=minimal)
1003 Operations:
1004 migrate_from: A-migrate_from-interval-0s
1005 interval=0s timeout=20s
1006 migrate_to: A-migrate_to-interval-0s
1007 interval=0s timeout=20s
1008 monitor: A-monitor-interval-10
1009 interval=10
1010 monitor: A-monitor-interval-20
1011 interval=20
1012 reload: A-reload-interval-0s
1013 interval=0s timeout=20s
1014 reload-agent: A-reload-agent-interval-0s
1015 interval=0s timeout=20s
1016 start: A-start-interval-0s
1017 interval=0s timeout=20s
1018 stop: A-stop-interval-0s
1019 interval=0s timeout=20s
1020 """
1021 ),
1022 )
1023
1024 self.assert_pcs_fail(
1025 "resource update A op monitor interval=20".split(),
1026 (
1027 "Error: operation monitor with interval 20s already specified for A:\n"
1028 "monitor interval=20 (A-monitor-interval-20)\n"
1029 ),
1030 )
1031
1032 self.assert_pcs_success(
1033 "resource update A op monitor interval=11".split(),
1034 )
1035
1036 self.assert_pcs_success(
1037 "resource config A".split(),
1038 dedent(
1039 """\
1040 Resource: A (class=ocf provider=pcsmock type=minimal)
1041 Operations:
1042 migrate_from: A-migrate_from-interval-0s
1043 interval=0s timeout=20s
1044 migrate_to: A-migrate_to-interval-0s
1045 interval=0s timeout=20s
1046 monitor: A-monitor-interval-11
1047 interval=11
1048 monitor: A-monitor-interval-20
1049 interval=20
1050 reload: A-reload-interval-0s
1051 interval=0s timeout=20s
1052 reload-agent: A-reload-agent-interval-0s
1053 interval=0s timeout=20s
1054 start: A-start-interval-0s
1055 interval=0s timeout=20s
1056 stop: A-stop-interval-0s
1057 interval=0s timeout=20s
1058 """
1059 ),
1060 )
1061
1062 self.assert_pcs_success(
1063 "resource create B ocf:pcsmock:minimal --no-default-ops".split(),
1064 )
1065
1066 self.assert_pcs_success(
1067 "resource op remove B-monitor-interval-10s".split()
1068 )
1069
1070 self.assert_pcs_success(
1071 "resource config B".split(),
1072 "Resource: B (class=ocf provider=pcsmock type=minimal)\n",
1073 )
1074
1075 self.assert_pcs_success(
1076 "resource update B op monitor interval=60s".split(),
1077 )
1078
1079 self.assert_pcs_success(
1080 "resource config B".split(),
1081 dedent(
1082 """\
1083 Resource: B (class=ocf provider=pcsmock type=minimal)
1084 Operations:
1085 monitor: B-monitor-interval-60s
1086 interval=60s
1087 """
1088 ),
1089 )
1090
1091 self.assert_pcs_success(
1092 "resource update B op monitor interval=30".split(),
1093 )
1094
1095 self.assert_pcs_success(
1096 "resource config B".split(),
1097 dedent(
1098 """\
1099 Resource: B (class=ocf provider=pcsmock type=minimal)
1100 Operations:
1101 monitor: B-monitor-interval-30
1102 interval=30
1103 """
1104 ),
1105 )
1106
1107 self.assert_pcs_success(
1108 "resource update B op start interval=0 timeout=10".split(),
1109 )
1110
1111 self.assert_pcs_success(
1112 "resource config B".split(),
1113 dedent(
1114 """\
1115 Resource: B (class=ocf provider=pcsmock type=minimal)
1116 Operations:
1117 monitor: B-monitor-interval-30
1118 interval=30
1119 start: B-start-interval-0
1120 interval=0 timeout=10
1121 """
1122 ),
1123 )
1124
1125 self.assert_pcs_success(
1126 "resource update B op start interval=0 timeout=20".split(),
1127 )
1128
1129 self.assert_pcs_success(
1130 "resource config B".split(),
1131 dedent(
1132 """\
1133 Resource: B (class=ocf provider=pcsmock type=minimal)
1134 Operations:
1135 monitor: B-monitor-interval-30
1136 interval=30
1137 start: B-start-interval-0
1138 interval=0 timeout=20
1139 """
1140 ),
1141 )
1142
1143 self.assert_pcs_success(
1144 "resource update B op monitor interval=33".split(),
1145 )
1146
1147 self.assert_pcs_success(
1148 "resource config B".split(),
1149 dedent(
1150 """\
1151 Resource: B (class=ocf provider=pcsmock type=minimal)
1152 Operations:
1153 monitor: B-monitor-interval-33
1154 interval=33
1155 start: B-start-interval-0
1156 interval=0 timeout=20
1157 """
1158 ),
1159 )
1160
1161 self.assert_pcs_fail(
1162 "resource update B op monitor interval=100 role=Master".split(),
1163 "Error: 'Master' is not a valid role value, use {}\n".format(
1164 format_list(const.PCMK_ROLES)
1165 ),
1166 )
1167
1168 self.assert_pcs_success(
1169 "resource update B op monitor interval=100 role=Promoted".split(),
1170 )
1171
1172 self.assert_pcs_success(
1173 "resource config B".split(),
1174 dedent(
1175 f"""\
1176 Resource: B (class=ocf provider=pcsmock type=minimal)
1177 Operations:
1178 monitor: B-monitor-interval-33
1179 interval=33
1180 start: B-start-interval-0
1181 interval=0 timeout=20
1182 monitor: B-monitor-interval-100
1183 interval=100 role={const.PCMK_ROLE_PROMOTED}
1184 """
1185 ),
1186 )
1187
1188 self.assert_pcs_success(
1189 "resource update B op start interval=0 timeout=22".split(),
1190 )
1191
1192 self.assert_pcs_success(
1193 "resource config B".split(),
1194 dedent(
1195 f"""\
1196 Resource: B (class=ocf provider=pcsmock type=minimal)
1197 Operations:
1198 monitor: B-monitor-interval-33
1199 interval=33
1200 start: B-start-interval-0
1201 interval=0 timeout=22
1202 monitor: B-monitor-interval-100
1203 interval=100 role={const.PCMK_ROLE_PROMOTED}
1204 """
1205 ),
1206 )
1207
1208 @skip_unless_crm_rule()
1209 def test_group_ungroup(self):
1210 self.assert_pcs_success(
1211 "resource create --no-default-ops A1 ocf:pcsmock:minimal".split(),
1212 )
1213 self.assert_pcs_success(
1214 "resource create --no-default-ops A2 ocf:pcsmock:minimal".split(),
1215 )
1216 self.assert_pcs_success(
1217 "resource create --no-default-ops A3 ocf:pcsmock:minimal".split(),
1218 )
1219 self.assert_pcs_success(
1220 "resource create --no-default-ops A4 ocf:pcsmock:minimal".split(),
1221 )
1222 self.assert_pcs_success(
1223 "resource create --no-default-ops A5 ocf:pcsmock:minimal".split(),
1224 )
1225
1226 self.assert_pcs_success(
1227 "resource group add AGroup A1 A2 A3 A4 A5".split(),
1228 )
1229
1230 self.assert_pcs_success(
1231 "resource config AGroup".split(),
1232 dedent(
1233 """\
1234 Group: AGroup
1235 Resource: A1 (class=ocf provider=pcsmock type=minimal)
1236 Operations:
1237 monitor: A1-monitor-interval-10s
1238 interval=10s timeout=20s
1239 Resource: A2 (class=ocf provider=pcsmock type=minimal)
1240 Operations:
1241 monitor: A2-monitor-interval-10s
1242 interval=10s timeout=20s
1243 Resource: A3 (class=ocf provider=pcsmock type=minimal)
1244 Operations:
1245 monitor: A3-monitor-interval-10s
1246 interval=10s timeout=20s
1247 Resource: A4 (class=ocf provider=pcsmock type=minimal)
1248 Operations:
1249 monitor: A4-monitor-interval-10s
1250 interval=10s timeout=20s
1251 Resource: A5 (class=ocf provider=pcsmock type=minimal)
1252 Operations:
1253 monitor: A5-monitor-interval-10s
1254 interval=10s timeout=20s
1255 """
1256 ),
1257 )
1258
1259 def test_group_order(self):
1260 # This was considered for removing during 'resource group add' command
1261 # and tests overhaul. However, this is the only test where "resource
1262 # group list" is called. Due to that this test was not deleted.
1263 self.assert_pcs_success(
1264 "resource create --no-default-ops A ocf:pcsmock:minimal".split(),
1265 )
1266 self.assert_pcs_success(
1267 "resource create --no-default-ops B ocf:pcsmock:minimal".split(),
1268 )
1269 self.assert_pcs_success(
1270 "resource create --no-default-ops C ocf:pcsmock:minimal".split(),
1271 )
1272 self.assert_pcs_success(
1273 "resource create --no-default-ops D ocf:pcsmock:minimal".split(),
1274 )
1275 self.assert_pcs_success(
1276 "resource create --no-default-ops E ocf:pcsmock:minimal".split(),
1277 )
1278 self.assert_pcs_success(
1279 "resource create --no-default-ops F ocf:pcsmock:minimal".split(),
1280 )
1281 self.assert_pcs_success(
1282 "resource create --no-default-ops G ocf:pcsmock:minimal".split(),
1283 )
1284 self.assert_pcs_success(
1285 "resource create --no-default-ops H ocf:pcsmock:minimal".split(),
1286 )
1287 self.assert_pcs_success(
1288 "resource create --no-default-ops I ocf:pcsmock:minimal".split(),
1289 )
1290 self.assert_pcs_success(
1291 "resource create --no-default-ops J ocf:pcsmock:minimal".split(),
1292 )
1293 self.assert_pcs_success(
1294 "resource create --no-default-ops K ocf:pcsmock:minimal".split(),
1295 )
1296
1297 self.assert_pcs_success(
1298 "resource group add RGA A B C E D K J I".split()
1299 )
1300
1301 stdout, stderr, returncode = self.pcs_runner.run(["resource"])
1302 self.assertEqual(stderr, "")
1303 self.assertEqual(returncode, 0)
1304 if is_pacemaker_21_without_20_compatibility():
1305 self.assertEqual(
1306 stdout,
1307 outdent(
1308 """\
1309 * F\t(ocf:pcsmock:minimal):\t Stopped
1310 * G\t(ocf:pcsmock:minimal):\t Stopped
1311 * H\t(ocf:pcsmock:minimal):\t Stopped
1312 * Resource Group: RGA:
1313 * A\t(ocf:pcsmock:minimal):\t Stopped
1314 * B\t(ocf:pcsmock:minimal):\t Stopped
1315 * C\t(ocf:pcsmock:minimal):\t Stopped
1316 * E\t(ocf:pcsmock:minimal):\t Stopped
1317 * D\t(ocf:pcsmock:minimal):\t Stopped
1318 * K\t(ocf:pcsmock:minimal):\t Stopped
1319 * J\t(ocf:pcsmock:minimal):\t Stopped
1320 * I\t(ocf:pcsmock:minimal):\t Stopped
1321 """
1322 ),
1323 )
1324 elif PCMK_2_0_3_PLUS:
1325 assert_pcs_status(
1326 stdout,
1327 """\
1328 * F\t(ocf::pcsmock:minimal):\tStopped
1329 * G\t(ocf::pcsmock:minimal):\tStopped
1330 * H\t(ocf::pcsmock:minimal):\tStopped
1331 * Resource Group: RGA:
1332 * A\t(ocf::pcsmock:minimal):\tStopped
1333 * B\t(ocf::pcsmock:minimal):\tStopped
1334 * C\t(ocf::pcsmock:minimal):\tStopped
1335 * E\t(ocf::pcsmock:minimal):\tStopped
1336 * D\t(ocf::pcsmock:minimal):\tStopped
1337 * K\t(ocf::pcsmock:minimal):\tStopped
1338 * J\t(ocf::pcsmock:minimal):\tStopped
1339 * I\t(ocf::pcsmock:minimal):\tStopped
1340 """,
1341 )
1342 else:
1343 self.assertEqual(
1344 stdout,
1345 """\
1346 F\t(ocf::pcsmock:minimal):\tStopped
1347 G\t(ocf::pcsmock:minimal):\tStopped
1348 H\t(ocf::pcsmock:minimal):\tStopped
1349 Resource Group: RGA
1350 A\t(ocf::pcsmock:minimal):\tStopped
1351 B\t(ocf::pcsmock:minimal):\tStopped
1352 C\t(ocf::pcsmock:minimal):\tStopped
1353 E\t(ocf::pcsmock:minimal):\tStopped
1354 D\t(ocf::pcsmock:minimal):\tStopped
1355 K\t(ocf::pcsmock:minimal):\tStopped
1356 J\t(ocf::pcsmock:minimal):\tStopped
1357 I\t(ocf::pcsmock:minimal):\tStopped
1358 """,
1359 )
1360
1361 self.assert_pcs_success(
1362 "resource group list".split(), "RGA: A B C E D K J I\n"
1363 )
1364
1365 @skip_unless_crm_rule()
1366 def test_cluster_config(self):
1367 self.setup_cluster_a()
1368
1369 self.pcs_runner.mock_settings = {
1370 "corosync_conf_file": rc("corosync.conf"),
1371 }
1372 self.assert_pcs_success(
1373 ["config"],
1374 dedent(
1375 """\
1376 Cluster Name: test99
1377 Corosync Nodes:
1378 rh7-1 rh7-2
1379 Pacemaker Nodes:
1380
1381 Resources:
1382 Resource: ClusterIP6 (class=ocf provider=pcsmock type=minimal)
1383 Operations:
1384 monitor: ClusterIP6-monitor-interval-10s
1385 interval=10s timeout=20s
1386 Group: TestGroup1
1387 Resource: ClusterIP (class=ocf provider=pcsmock type=minimal)
1388 Operations:
1389 monitor: ClusterIP-monitor-interval-10s
1390 interval=10s timeout=20s
1391 Group: TestGroup2
1392 Resource: ClusterIP2 (class=ocf provider=pcsmock type=minimal)
1393 Operations:
1394 monitor: ClusterIP2-monitor-interval-10s
1395 interval=10s timeout=20s
1396 Resource: ClusterIP3 (class=ocf provider=pcsmock type=minimal)
1397 Operations:
1398 monitor: ClusterIP3-monitor-interval-10s
1399 interval=10s timeout=20s
1400 Clone: ClusterIP4-clone
1401 Resource: ClusterIP4 (class=ocf provider=pcsmock type=minimal)
1402 Operations:
1403 monitor: ClusterIP4-monitor-interval-10s
1404 interval=10s timeout=20s
1405 Clone: Master
1406 Meta Attributes:
1407 promotable=true
1408 Resource: ClusterIP5 (class=ocf provider=pcsmock type=minimal)
1409 Operations:
1410 monitor: ClusterIP5-monitor-interval-10s
1411 interval=10s timeout=20s
1412 """
1413 ),
1414 )
1415
1416 def test_ms_group(self):
1417 self.assert_pcs_success(
1418 "resource create --no-default-ops D0 ocf:pcsmock:minimal".split(),
1419 )
1420 self.assert_pcs_success(
1421 "resource create --no-default-ops D1 ocf:pcsmock:minimal".split(),
1422 )
1423 self.assert_pcs_success("resource group add Group D0 D1".split())
1424
1425 # pcs no longer allows turning resources into masters but supports
1426 # existing ones. In order to test it, we need to put a master in the
1427 # CIB without pcs.
1428 wrap_element_by_master(self.temp_cib, "Group", master_id="GroupMaster")
1429
1430 self.assert_pcs_success(
1431 "resource config".split(),
1432 dedent(
1433 """\
1434 Clone: GroupMaster
1435 Meta Attributes:
1436 promotable=true
1437 Group: Group
1438 Resource: D0 (class=ocf provider=pcsmock type=minimal)
1439 Operations:
1440 monitor: D0-monitor-interval-10s
1441 interval=10s timeout=20s
1442 Resource: D1 (class=ocf provider=pcsmock type=minimal)
1443 Operations:
1444 monitor: D1-monitor-interval-10s
1445 interval=10s timeout=20s
1446 """
1447 ),
1448 )
1449
1450 def test_unclone(self):
1451 # see also BundleClone
1452 self.assert_pcs_success(
1453 "resource create --no-default-ops dummy1 ocf:pcsmock:minimal".split()
1454 )
1455 self.assert_pcs_success(
1456 "resource create --no-default-ops dummy2 ocf:pcsmock:minimal".split(),
1457 )
1458 self.assert_pcs_success("resource group add gr dummy1".split())
1459
1460 self.assert_pcs_fail(
1461 "resource unclone gr".split(),
1462 "Error: 'gr' is not a clone resource\n",
1463 )
1464
1465 # unclone with a clone itself specified
1466 self.assert_pcs_success("resource group add gr dummy2".split())
1467 self.assert_pcs_success("resource clone gr".split())
1468 self.assert_pcs_success(
1469 "resource config".split(),
1470 dedent(
1471 """\
1472 Clone: gr-clone
1473 Group: gr
1474 Resource: dummy1 (class=ocf provider=pcsmock type=minimal)
1475 Operations:
1476 monitor: dummy1-monitor-interval-10s
1477 interval=10s timeout=20s
1478 Resource: dummy2 (class=ocf provider=pcsmock type=minimal)
1479 Operations:
1480 monitor: dummy2-monitor-interval-10s
1481 interval=10s timeout=20s
1482 """
1483 ),
1484 )
1485
1486 self.assert_pcs_success("resource unclone gr-clone".split())
1487 self.assert_pcs_success(
1488 "resource config".split(),
1489 dedent(
1490 """\
1491 Group: gr
1492 Resource: dummy1 (class=ocf provider=pcsmock type=minimal)
1493 Operations:
1494 monitor: dummy1-monitor-interval-10s
1495 interval=10s timeout=20s
1496 Resource: dummy2 (class=ocf provider=pcsmock type=minimal)
1497 Operations:
1498 monitor: dummy2-monitor-interval-10s
1499 interval=10s timeout=20s
1500 """
1501 ),
1502 )
1503
1504 # unclone with a cloned group specified
1505 self.assert_pcs_success("resource clone gr".split())
1506 self.assert_pcs_success(
1507 "resource config".split(),
1508 dedent(
1509 """\
1510 Clone: gr-clone
1511 Group: gr
1512 Resource: dummy1 (class=ocf provider=pcsmock type=minimal)
1513 Operations:
1514 monitor: dummy1-monitor-interval-10s
1515 interval=10s timeout=20s
1516 Resource: dummy2 (class=ocf provider=pcsmock type=minimal)
1517 Operations:
1518 monitor: dummy2-monitor-interval-10s
1519 interval=10s timeout=20s
1520 """
1521 ),
1522 )
1523
1524 self.assert_pcs_success("resource unclone gr".split())
1525 self.assert_pcs_success(
1526 "resource config".split(),
1527 dedent(
1528 """\
1529 Group: gr
1530 Resource: dummy1 (class=ocf provider=pcsmock type=minimal)
1531 Operations:
1532 monitor: dummy1-monitor-interval-10s
1533 interval=10s timeout=20s
1534 Resource: dummy2 (class=ocf provider=pcsmock type=minimal)
1535 Operations:
1536 monitor: dummy2-monitor-interval-10s
1537 interval=10s timeout=20s
1538 """
1539 ),
1540 )
1541
1542 # unclone with a cloned grouped resource specified
1543 self.assert_pcs_success("resource clone gr".split())
1544 self.assert_pcs_success(
1545 "resource config".split(),
1546 dedent(
1547 """\
1548 Clone: gr-clone
1549 Group: gr
1550 Resource: dummy1 (class=ocf provider=pcsmock type=minimal)
1551 Operations:
1552 monitor: dummy1-monitor-interval-10s
1553 interval=10s timeout=20s
1554 Resource: dummy2 (class=ocf provider=pcsmock type=minimal)
1555 Operations:
1556 monitor: dummy2-monitor-interval-10s
1557 interval=10s timeout=20s
1558 """
1559 ),
1560 )
1561
1562 self.assert_pcs_success("resource unclone dummy1".split())
1563 self.assert_pcs_success(
1564 "resource config".split(),
1565 dedent(
1566 """\
1567 Resource: dummy1 (class=ocf provider=pcsmock type=minimal)
1568 Operations:
1569 monitor: dummy1-monitor-interval-10s
1570 interval=10s timeout=20s
1571 Clone: gr-clone
1572 Group: gr
1573 Resource: dummy2 (class=ocf provider=pcsmock type=minimal)
1574 Operations:
1575 monitor: dummy2-monitor-interval-10s
1576 interval=10s timeout=20s
1577 """
1578 ),
1579 )
1580
1581 self.assert_pcs_success("resource unclone dummy2".split())
1582 self.assert_pcs_success(
1583 "resource config".split(),
1584 dedent(
1585 """\
1586 Resource: dummy1 (class=ocf provider=pcsmock type=minimal)
1587 Operations:
1588 monitor: dummy1-monitor-interval-10s
1589 interval=10s timeout=20s
1590 Resource: dummy2 (class=ocf provider=pcsmock type=minimal)
1591 Operations:
1592 monitor: dummy2-monitor-interval-10s
1593 interval=10s timeout=20s
1594 """
1595 ),
1596 )
1597
1598 def test_unclone_master(self):
1599 # see also BundleClone
1600 self.assert_pcs_success(
1601 "resource create --no-default-ops dummy1 ocf:pcsmock:stateful".split(),
1602 )
1603 self.assert_pcs_success(
1604 "resource create --no-default-ops dummy2 ocf:pcsmock:stateful".split(),
1605 )
1606
1607 # try to unclone a non-cloned resource
1608 self.assert_pcs_fail(
1609 "resource unclone dummy1".split(),
1610 "Error: 'dummy1' is not a clone resource\n",
1611 )
1612
1613 self.assert_pcs_success("resource group add gr dummy1".split())
1614
1615 self.assert_pcs_fail(
1616 "resource unclone gr".split(),
1617 "Error: 'gr' is not a clone resource\n",
1618 )
1619
1620 # unclone with a cloned primitive specified
1621 # pcs no longer allows turning resources into masters but supports
1622 # existing ones. In order to test it, we need to put a master in the
1623 # CIB without pcs.
1624 wrap_element_by_master(self.temp_cib, "dummy2")
1625 self.assert_pcs_success(
1626 "resource config".split(),
1627 dedent(
1628 f"""\
1629 Group: gr
1630 Resource: dummy1 (class=ocf provider=pcsmock type=stateful)
1631 Operations:
1632 monitor: dummy1-monitor-interval-10s
1633 interval=10s timeout=20s role={const.PCMK_ROLE_PROMOTED}
1634 monitor: dummy1-monitor-interval-11s
1635 interval=11s timeout=20s role={const.PCMK_ROLE_UNPROMOTED}
1636 Clone: dummy2-master
1637 Meta Attributes:
1638 promotable=true
1639 Resource: dummy2 (class=ocf provider=pcsmock type=stateful)
1640 Operations:
1641 monitor: dummy2-monitor-interval-10s
1642 interval=10s timeout=20s role={const.PCMK_ROLE_PROMOTED}
1643 monitor: dummy2-monitor-interval-11s
1644 interval=11s timeout=20s role={const.PCMK_ROLE_UNPROMOTED}
1645 """
1646 ),
1647 )
1648
1649 self.assert_pcs_success("resource unclone dummy2".split())
1650 self.assert_pcs_success(
1651 "resource config".split(),
1652 dedent(
1653 f"""\
1654 Resource: dummy2 (class=ocf provider=pcsmock type=stateful)
1655 Operations:
1656 monitor: dummy2-monitor-interval-10s
1657 interval=10s timeout=20s role={const.PCMK_ROLE_PROMOTED}
1658 monitor: dummy2-monitor-interval-11s
1659 interval=11s timeout=20s role={const.PCMK_ROLE_UNPROMOTED}
1660 Group: gr
1661 Resource: dummy1 (class=ocf provider=pcsmock type=stateful)
1662 Operations:
1663 monitor: dummy1-monitor-interval-10s
1664 interval=10s timeout=20s role={const.PCMK_ROLE_PROMOTED}
1665 monitor: dummy1-monitor-interval-11s
1666 interval=11s timeout=20s role={const.PCMK_ROLE_UNPROMOTED}
1667 """
1668 ),
1669 )
1670
1671 # unclone with a clone itself specified
1672 self.assert_pcs_success("resource group add gr dummy2".split())
1673 # pcs no longer allows turning resources into masters but supports
1674 # existing ones. In order to test it, we need to put a master in the
1675 # CIB without pcs.
1676 wrap_element_by_master(self.temp_cib, "gr")
1677 self.assert_pcs_success(
1678 "resource config".split(),
1679 dedent(
1680 f"""\
1681 Clone: gr-master
1682 Meta Attributes:
1683 promotable=true
1684 Group: gr
1685 Resource: dummy1 (class=ocf provider=pcsmock type=stateful)
1686 Operations:
1687 monitor: dummy1-monitor-interval-10s
1688 interval=10s timeout=20s role={const.PCMK_ROLE_PROMOTED}
1689 monitor: dummy1-monitor-interval-11s
1690 interval=11s timeout=20s role={const.PCMK_ROLE_UNPROMOTED}
1691 Resource: dummy2 (class=ocf provider=pcsmock type=stateful)
1692 Operations:
1693 monitor: dummy2-monitor-interval-10s
1694 interval=10s timeout=20s role={const.PCMK_ROLE_PROMOTED}
1695 monitor: dummy2-monitor-interval-11s
1696 interval=11s timeout=20s role={const.PCMK_ROLE_UNPROMOTED}
1697 """
1698 ),
1699 )
1700
1701 self.assert_pcs_success("resource unclone gr-master".split())
1702 self.assert_pcs_success(
1703 "resource config".split(),
1704 dedent(
1705 f"""\
1706 Group: gr
1707 Resource: dummy1 (class=ocf provider=pcsmock type=stateful)
1708 Operations:
1709 monitor: dummy1-monitor-interval-10s
1710 interval=10s timeout=20s role={const.PCMK_ROLE_PROMOTED}
1711 monitor: dummy1-monitor-interval-11s
1712 interval=11s timeout=20s role={const.PCMK_ROLE_UNPROMOTED}
1713 Resource: dummy2 (class=ocf provider=pcsmock type=stateful)
1714 Operations:
1715 monitor: dummy2-monitor-interval-10s
1716 interval=10s timeout=20s role={const.PCMK_ROLE_PROMOTED}
1717 monitor: dummy2-monitor-interval-11s
1718 interval=11s timeout=20s role={const.PCMK_ROLE_UNPROMOTED}
1719 """
1720 ),
1721 )
1722
1723 # unclone with a cloned group specified
1724 # pcs no longer allows turning resources into masters but supports
1725 # existing ones. In order to test it, we need to put a master in the
1726 # CIB without pcs.
1727 wrap_element_by_master(self.temp_cib, "gr")
1728 self.assert_pcs_success(
1729 "resource config".split(),
1730 dedent(
1731 f"""\
1732 Clone: gr-master
1733 Meta Attributes:
1734 promotable=true
1735 Group: gr
1736 Resource: dummy1 (class=ocf provider=pcsmock type=stateful)
1737 Operations:
1738 monitor: dummy1-monitor-interval-10s
1739 interval=10s timeout=20s role={const.PCMK_ROLE_PROMOTED}
1740 monitor: dummy1-monitor-interval-11s
1741 interval=11s timeout=20s role={const.PCMK_ROLE_UNPROMOTED}
1742 Resource: dummy2 (class=ocf provider=pcsmock type=stateful)
1743 Operations:
1744 monitor: dummy2-monitor-interval-10s
1745 interval=10s timeout=20s role={const.PCMK_ROLE_PROMOTED}
1746 monitor: dummy2-monitor-interval-11s
1747 interval=11s timeout=20s role={const.PCMK_ROLE_UNPROMOTED}
1748 """
1749 ),
1750 )
1751
1752 self.assert_pcs_success("resource unclone gr".split())
1753 self.assert_pcs_success(
1754 "resource config".split(),
1755 dedent(
1756 f"""\
1757 Group: gr
1758 Resource: dummy1 (class=ocf provider=pcsmock type=stateful)
1759 Operations:
1760 monitor: dummy1-monitor-interval-10s
1761 interval=10s timeout=20s role={const.PCMK_ROLE_PROMOTED}
1762 monitor: dummy1-monitor-interval-11s
1763 interval=11s timeout=20s role={const.PCMK_ROLE_UNPROMOTED}
1764 Resource: dummy2 (class=ocf provider=pcsmock type=stateful)
1765 Operations:
1766 monitor: dummy2-monitor-interval-10s
1767 interval=10s timeout=20s role={const.PCMK_ROLE_PROMOTED}
1768 monitor: dummy2-monitor-interval-11s
1769 interval=11s timeout=20s role={const.PCMK_ROLE_UNPROMOTED}
1770 """
1771 ),
1772 )
1773
1774 # unclone with a cloned grouped resource specified
1775 self.assert_pcs_success("resource ungroup gr dummy2".split())
1776 # pcs no longer allows turning resources into masters but supports
1777 # existing ones. In order to test it, we need to put a master in the
1778 # CIB without pcs.
1779 wrap_element_by_master(self.temp_cib, "gr")
1780 self.assert_pcs_success(
1781 "resource config".split(),
1782 dedent(
1783 f"""\
1784 Resource: dummy2 (class=ocf provider=pcsmock type=stateful)
1785 Operations:
1786 monitor: dummy2-monitor-interval-10s
1787 interval=10s timeout=20s role={const.PCMK_ROLE_PROMOTED}
1788 monitor: dummy2-monitor-interval-11s
1789 interval=11s timeout=20s role={const.PCMK_ROLE_UNPROMOTED}
1790 Clone: gr-master
1791 Meta Attributes:
1792 promotable=true
1793 Group: gr
1794 Resource: dummy1 (class=ocf provider=pcsmock type=stateful)
1795 Operations:
1796 monitor: dummy1-monitor-interval-10s
1797 interval=10s timeout=20s role={const.PCMK_ROLE_PROMOTED}
1798 monitor: dummy1-monitor-interval-11s
1799 interval=11s timeout=20s role={const.PCMK_ROLE_UNPROMOTED}
1800 """
1801 ),
1802 )
1803
1804 self.assert_pcs_success("resource unclone dummy1".split())
1805 self.assert_pcs_success(
1806 "resource config".split(),
1807 dedent(
1808 f"""\
1809 Resource: dummy2 (class=ocf provider=pcsmock type=stateful)
1810 Operations:
1811 monitor: dummy2-monitor-interval-10s
1812 interval=10s timeout=20s role={const.PCMK_ROLE_PROMOTED}
1813 monitor: dummy2-monitor-interval-11s
1814 interval=11s timeout=20s role={const.PCMK_ROLE_UNPROMOTED}
1815 Resource: dummy1 (class=ocf provider=pcsmock type=stateful)
1816 Operations:
1817 monitor: dummy1-monitor-interval-10s
1818 interval=10s timeout=20s role={const.PCMK_ROLE_PROMOTED}
1819 monitor: dummy1-monitor-interval-11s
1820 interval=11s timeout=20s role={const.PCMK_ROLE_UNPROMOTED}
1821 """
1822 ),
1823 )
1824
1825 self.assert_pcs_success("resource group add gr dummy1 dummy2".split())
1826
1827 # pcs no longer allows turning resources into masters but supports
1828 # existing ones. In order to test it, we need to put a master in the
1829 # CIB without pcs.
1830 wrap_element_by_master(self.temp_cib, "gr")
1831 self.assert_pcs_success(
1832 "resource config".split(),
1833 dedent(
1834 f"""\
1835 Clone: gr-master
1836 Meta Attributes:
1837 promotable=true
1838 Group: gr
1839 Resource: dummy1 (class=ocf provider=pcsmock type=stateful)
1840 Operations:
1841 monitor: dummy1-monitor-interval-10s
1842 interval=10s timeout=20s role={const.PCMK_ROLE_PROMOTED}
1843 monitor: dummy1-monitor-interval-11s
1844 interval=11s timeout=20s role={const.PCMK_ROLE_UNPROMOTED}
1845 Resource: dummy2 (class=ocf provider=pcsmock type=stateful)
1846 Operations:
1847 monitor: dummy2-monitor-interval-10s
1848 interval=10s timeout=20s role={const.PCMK_ROLE_PROMOTED}
1849 monitor: dummy2-monitor-interval-11s
1850 interval=11s timeout=20s role={const.PCMK_ROLE_UNPROMOTED}
1851 """
1852 ),
1853 )
1854
1855 self.assert_pcs_success("resource unclone dummy2".split())
1856
1857 self.assert_pcs_success(
1858 "resource config".split(),
1859 dedent(
1860 f"""\
1861 Resource: dummy2 (class=ocf provider=pcsmock type=stateful)
1862 Operations:
1863 monitor: dummy2-monitor-interval-10s
1864 interval=10s timeout=20s role={const.PCMK_ROLE_PROMOTED}
1865 monitor: dummy2-monitor-interval-11s
1866 interval=11s timeout=20s role={const.PCMK_ROLE_UNPROMOTED}
1867 Clone: gr-master
1868 Meta Attributes:
1869 promotable=true
1870 Group: gr
1871 Resource: dummy1 (class=ocf provider=pcsmock type=stateful)
1872 Operations:
1873 monitor: dummy1-monitor-interval-10s
1874 interval=10s timeout=20s role={const.PCMK_ROLE_PROMOTED}
1875 monitor: dummy1-monitor-interval-11s
1876 interval=11s timeout=20s role={const.PCMK_ROLE_UNPROMOTED}
1877 """
1878 ),
1879 )
1880
1881 def test_clone_group_member(self):
1882 self.assert_pcs_success(
1883 "resource create --no-default-ops D0 ocf:pcsmock:minimal --group AG".split(),
1884 stderr_full=DEPRECATED_DASH_DASH_GROUP,
1885 )
1886 self.assert_pcs_success(
1887 "resource create --no-default-ops D1 ocf:pcsmock:minimal --group AG".split(),
1888 stderr_full=DEPRECATED_DASH_DASH_GROUP,
1889 )
1890
1891 self.assert_pcs_success("resource clone D0".split())
1892 self.assert_pcs_success(
1893 ["resource", "config"],
1894 dedent(
1895 """\
1896 Group: AG
1897 Resource: D1 (class=ocf provider=pcsmock type=minimal)
1898 Operations:
1899 monitor: D1-monitor-interval-10s
1900 interval=10s timeout=20s
1901 Clone: D0-clone
1902 Resource: D0 (class=ocf provider=pcsmock type=minimal)
1903 Operations:
1904 monitor: D0-monitor-interval-10s
1905 interval=10s timeout=20s
1906 """
1907 ),
1908 )
1909
1910 self.assert_pcs_success("resource clone D1".split())
1911 self.assert_pcs_success(
1912 ["resource", "config"],
1913 dedent(
1914 """\
1915 Clone: D0-clone
1916 Resource: D0 (class=ocf provider=pcsmock type=minimal)
1917 Operations:
1918 monitor: D0-monitor-interval-10s
1919 interval=10s timeout=20s
1920 Clone: D1-clone
1921 Resource: D1 (class=ocf provider=pcsmock type=minimal)
1922 Operations:
1923 monitor: D1-monitor-interval-10s
1924 interval=10s timeout=20s
1925 """
1926 ),
1927 )
1928
1929 def test_promotable_group_member(self):
1930 self.assert_pcs_success(
1931 "resource create --no-default-ops D0 ocf:pcsmock:stateful --group AG".split(),
1932 stderr_full=DEPRECATED_DASH_DASH_GROUP,
1933 )
1934 self.assert_pcs_success(
1935 "resource create --no-default-ops D1 ocf:pcsmock:stateful --group AG".split(),
1936 stderr_full=DEPRECATED_DASH_DASH_GROUP,
1937 )
1938
1939 self.assert_pcs_success("resource promotable D0".split())
1940 self.assert_pcs_success(
1941 ["resource", "config"],
1942 dedent(
1943 """\
1944 Group: AG
1945 Resource: D1 (class=ocf provider=pcsmock type=stateful)
1946 Operations:
1947 monitor: D1-monitor-interval-10s
1948 interval=10s timeout=20s role=Promoted
1949 monitor: D1-monitor-interval-11s
1950 interval=11s timeout=20s role=Unpromoted
1951 Clone: D0-clone
1952 Meta Attributes: D0-clone-meta_attributes
1953 promotable=true
1954 Resource: D0 (class=ocf provider=pcsmock type=stateful)
1955 Operations:
1956 monitor: D0-monitor-interval-10s
1957 interval=10s timeout=20s role=Promoted
1958 monitor: D0-monitor-interval-11s
1959 interval=11s timeout=20s role=Unpromoted
1960 """
1961 ),
1962 )
1963
1964 self.assert_pcs_success("resource promotable D1".split())
1965 self.assert_pcs_success(
1966 ["resource", "config"],
1967 dedent(
1968 """\
1969 Clone: D0-clone
1970 Meta Attributes: D0-clone-meta_attributes
1971 promotable=true
1972 Resource: D0 (class=ocf provider=pcsmock type=stateful)
1973 Operations:
1974 monitor: D0-monitor-interval-10s
1975 interval=10s timeout=20s role=Promoted
1976 monitor: D0-monitor-interval-11s
1977 interval=11s timeout=20s role=Unpromoted
1978 Clone: D1-clone
1979 Meta Attributes: D1-clone-meta_attributes
1980 promotable=true
1981 Resource: D1 (class=ocf provider=pcsmock type=stateful)
1982 Operations:
1983 monitor: D1-monitor-interval-10s
1984 interval=10s timeout=20s role=Promoted
1985 monitor: D1-monitor-interval-11s
1986 interval=11s timeout=20s role=Unpromoted
1987 """
1988 ),
1989 )
1990
1991 def test_clone_master(self):
1992 # see also BundleClone
1993 self.assert_pcs_success(
1994 "resource create --no-default-ops D0 ocf:pcsmock:stateful".split(),
1995 )
1996 self.assert_pcs_success(
1997 "resource create --no-default-ops D1 ocf:pcsmock:stateful".split(),
1998 )
1999 self.assert_pcs_success(
2000 "resource create --no-default-ops D2 ocf:pcsmock:stateful".split(),
2001 )
2002 self.assert_pcs_success(
2003 "resource create --no-default-ops D3 ocf:pcsmock:stateful".split(),
2004 )
2005 self.assert_pcs_success("resource clone D0".split())
2006
2007 self.assert_pcs_fail(
2008 "resource promotable D3 meta promotable=false".split(),
2009 "Error: you cannot specify both promotable option and promotable keyword\n",
2010 )
2011
2012 self.assert_pcs_success("resource promotable D3".split())
2013
2014 # pcs no longer allows turning resources into masters but supports
2015 # existing ones. In order to test it, we need to put a master in the
2016 # CIB without pcs.
2017 wrap_element_by_master(
2018 self.temp_cib, "D1", master_id="D1-master-custom"
2019 )
2020
2021 # pcs no longer allows turning resources into masters but supports
2022 # existing ones. In order to test it, we need to put a master in the
2023 # CIB without pcs.
2024 wrap_element_by_master(self.temp_cib, "D2")
2025
2026 self.assert_pcs_success(
2027 "resource config".split(),
2028 dedent(
2029 """\
2030 Clone: D0-clone
2031 Resource: D0 (class=ocf provider=pcsmock type=stateful)
2032 Operations:
2033 monitor: D0-monitor-interval-10s
2034 interval=10s timeout=20s role=Promoted
2035 monitor: D0-monitor-interval-11s
2036 interval=11s timeout=20s role=Unpromoted
2037 Clone: D3-clone
2038 Meta Attributes: D3-clone-meta_attributes
2039 promotable=true
2040 Resource: D3 (class=ocf provider=pcsmock type=stateful)
2041 Operations:
2042 monitor: D3-monitor-interval-10s
2043 interval=10s timeout=20s role=Promoted
2044 monitor: D3-monitor-interval-11s
2045 interval=11s timeout=20s role=Unpromoted
2046 Clone: D1-master-custom
2047 Meta Attributes:
2048 promotable=true
2049 Resource: D1 (class=ocf provider=pcsmock type=stateful)
2050 Operations:
2051 monitor: D1-monitor-interval-10s
2052 interval=10s timeout=20s role=Promoted
2053 monitor: D1-monitor-interval-11s
2054 interval=11s timeout=20s role=Unpromoted
2055 Clone: D2-master
2056 Meta Attributes:
2057 promotable=true
2058 Resource: D2 (class=ocf provider=pcsmock type=stateful)
2059 Operations:
2060 monitor: D2-monitor-interval-10s
2061 interval=10s timeout=20s role=Promoted
2062 monitor: D2-monitor-interval-11s
2063 interval=11s timeout=20s role=Unpromoted
2064 """
2065 ),
2066 )
2067
2068 def test_lsb_resource(self):
2069 self.assert_pcs_fail(
2070 "resource create --no-default-ops D2 lsb:pcsmock foo=bar".split(),
2071 (
2072 "Error: invalid resource option 'foo', there are no options"
2073 " allowed, use --force to override\n" + ERRORS_HAVE_OCCURRED
2074 ),
2075 )
2076 self.assert_pcs_success(
2077 "resource create --no-default-ops D2 lsb:pcsmock foo=bar --force".split(),
2078 stderr_full=(
2079 "Warning: invalid resource option 'foo', there are no options"
2080 " allowed\n"
2081 ),
2082 )
2083 self.assert_pcs_success(
2084 "resource config".split(),
2085 dedent(
2086 """\
2087 Resource: D2 (class=lsb type=pcsmock)
2088 Attributes: D2-instance_attributes
2089 foo=bar
2090 Operations:
2091 monitor: D2-monitor-interval-15
2092 interval=15 timeout=15
2093 """
2094 ),
2095 )
2096
2097 self.assert_pcs_fail(
2098 "resource update D2 bar=baz".split(),
2099 (
2100 "Error: invalid resource option 'bar', there are no options"
2101 " allowed, use --force to override\n"
2102 ),
2103 )
2104 self.assert_pcs_success(
2105 "resource update D2 bar=baz --force".split(),
2106 stderr_full=(
2107 "Warning: invalid resource option 'bar', there are no options"
2108 " allowed\n"
2109 ),
2110 )
2111 self.assert_pcs_success(
2112 "resource config".split(),
2113 dedent(
2114 """\
2115 Resource: D2 (class=lsb type=pcsmock)
2116 Attributes: D2-instance_attributes
2117 bar=baz
2118 foo=bar
2119 Operations:
2120 monitor: D2-monitor-interval-15
2121 interval=15 timeout=15
2122 """
2123 ),
2124 )
2125
2126 @skip(
2127 "test of 'pcs resource debug-*' to be moved to pcs.lib with the "
2128 "command itself"
2129 )
2130 def test_debug_start_clone_group(self):
2131 self.assert_pcs_success(
2132 "resource create D0 ocf:pcsmock:minimal --group DGroup".split(),
2133 stderr_full=DEPRECATED_DASH_DASH_GROUP,
2134 )
2135 self.assert_pcs_success(
2136 "resource create D1 ocf:pcsmock:minimal --group DGroup".split(),
2137 stderr_full=DEPRECATED_DASH_DASH_GROUP,
2138 )
2139 self.assert_pcs_success(
2140 "resource create D2 ocf:pcsmock:minimal clone".split(),
2141 )
2142
2143 # pcs no longer allows creating masters but supports existing ones. In
2144 # order to test it, we need to put a master in the CIB without pcs.
2145 fixture_to_cib(self.temp_cib.name, fixture_master_xml("D3"))
2146
2147 self.assert_pcs_fail(
2148 "resource debug-start DGroup".split(),
2149 "Error: unable to debug-start a group, try one of the group's resource(s) (D0,D1)\n",
2150 )
2151 self.assert_pcs_fail(
2152 "resource debug-start D2-clone".split(),
2153 "Error: unable to debug-start a clone, try the clone's resource: D2\n",
2154 )
2155 self.assert_pcs_fail(
2156 "resource debug-start D3-master".split(),
2157 "Error: unable to debug-start a master, try the master's resource: D3\n",
2158 )
2159
2160 def test_group_clone_creation(self):
2161 self.assert_pcs_success(
2162 "resource create --no-default-ops D1 ocf:pcsmock:minimal --group DGroup".split(),
2163 stderr_full=DEPRECATED_DASH_DASH_GROUP,
2164 )
2165
2166 self.assert_pcs_fail(
2167 "resource clone DGroup1".split(),
2168 "Error: unable to find group or resource: DGroup1\n",
2169 )
2170
2171 self.assert_pcs_success("resource clone DGroup".split())
2172 self.assert_pcs_success(
2173 "resource config".split(),
2174 dedent(
2175 """\
2176 Clone: DGroup-clone
2177 Group: DGroup
2178 Resource: D1 (class=ocf provider=pcsmock type=minimal)
2179 Operations:
2180 monitor: D1-monitor-interval-10s
2181 interval=10s timeout=20s
2182 """
2183 ),
2184 )
2185
2186 self.assert_pcs_fail(
2187 "resource clone DGroup".split(),
2188 "Error: cannot clone a group that has already been cloned\n",
2189 )
2190
2191 def test_group_promotable_creation(self):
2192 self.assert_pcs_success(
2193 "resource create --no-default-ops D1 ocf:pcsmock:stateful --group DGroup".split(),
2194 stderr_full=DEPRECATED_DASH_DASH_GROUP,
2195 )
2196
2197 self.assert_pcs_fail(
2198 "resource promotable DGroup1".split(),
2199 "Error: unable to find group or resource: DGroup1\n",
2200 )
2201
2202 self.assert_pcs_success("resource promotable DGroup".split())
2203 self.assert_pcs_success(
2204 "resource config".split(),
2205 dedent(
2206 """\
2207 Clone: DGroup-clone
2208 Meta Attributes: DGroup-clone-meta_attributes
2209 promotable=true
2210 Group: DGroup
2211 Resource: D1 (class=ocf provider=pcsmock type=stateful)
2212 Operations:
2213 monitor: D1-monitor-interval-10s
2214 interval=10s timeout=20s role=Promoted
2215 monitor: D1-monitor-interval-11s
2216 interval=11s timeout=20s role=Unpromoted
2217 """
2218 ),
2219 )
2220
2221 self.assert_pcs_fail(
2222 "resource promotable DGroup".split(),
2223 "Error: cannot clone a group that has already been cloned\n",
2224 )
2225
2226 def test_resource_clone_creation(self):
2227 self.pcs_runner = PcsRunner(self.temp_large_cib.name)
2228 self.pcs_runner.mock_settings = get_mock_settings()
2229 # resource "dummy1" is already in "temp_large_cib
2230 self.assert_pcs_success("resource clone dummy1".split())
2231
2232 def test_resource_clone_id_clone_command(self):
2233 self.assert_pcs_success(
2234 "resource create --no-default-ops dummy-clone ocf:pcsmock:minimal".split(),
2235 )
2236 self.assert_pcs_success(
2237 "resource create --no-default-ops dummy ocf:pcsmock:minimal".split(),
2238 )
2239 self.assert_pcs_success("resource clone dummy".split())
2240 self.assert_pcs_success(
2241 "resource config".split(),
2242 dedent(
2243 """\
2244 Resource: dummy-clone (class=ocf provider=pcsmock type=minimal)
2245 Operations:
2246 monitor: dummy-clone-monitor-interval-10s
2247 interval=10s timeout=20s
2248 Clone: dummy-clone-1
2249 Resource: dummy (class=ocf provider=pcsmock type=minimal)
2250 Operations:
2251 monitor: dummy-monitor-interval-10s
2252 interval=10s timeout=20s
2253 """
2254 ),
2255 )
2256
2257 def test_resource_clone_id_create_command(self):
2258 self.assert_pcs_success(
2259 "resource create --no-default-ops dummy-clone ocf:pcsmock:minimal".split(),
2260 )
2261 self.assert_pcs_success(
2262 "resource create --no-default-ops dummy ocf:pcsmock:minimal clone".split(),
2263 )
2264 self.assert_pcs_success(
2265 "resource config".split(),
2266 dedent(
2267 """\
2268 Resource: dummy-clone (class=ocf provider=pcsmock type=minimal)
2269 Operations:
2270 monitor: dummy-clone-monitor-interval-10s
2271 interval=10s timeout=20s
2272 Clone: dummy-clone-1
2273 Resource: dummy (class=ocf provider=pcsmock type=minimal)
2274 Operations:
2275 monitor: dummy-monitor-interval-10s
2276 interval=10s timeout=20s
2277 """
2278 ),
2279 )
2280
2281 def test_resource_promotable_id_promotable_command(self):
2282 self.assert_pcs_success(
2283 "resource create --no-default-ops dummy-clone ocf:pcsmock:stateful".split(),
2284 )
2285 self.assert_pcs_success(
2286 "resource create --no-default-ops dummy ocf:pcsmock:stateful".split(),
2287 )
2288 self.assert_pcs_success("resource promotable dummy".split())
2289 self.assert_pcs_success(
2290 "resource config".split(),
2291 dedent(
2292 """\
2293 Resource: dummy-clone (class=ocf provider=pcsmock type=stateful)
2294 Operations:
2295 monitor: dummy-clone-monitor-interval-10s
2296 interval=10s timeout=20s role=Promoted
2297 monitor: dummy-clone-monitor-interval-11s
2298 interval=11s timeout=20s role=Unpromoted
2299 Clone: dummy-clone-1
2300 Meta Attributes: dummy-clone-1-meta_attributes
2301 promotable=true
2302 Resource: dummy (class=ocf provider=pcsmock type=stateful)
2303 Operations:
2304 monitor: dummy-monitor-interval-10s
2305 interval=10s timeout=20s role=Promoted
2306 monitor: dummy-monitor-interval-11s
2307 interval=11s timeout=20s role=Unpromoted
2308 """
2309 ),
2310 )
2311
2312 def test_resource_promotable_id_create_command(self):
2313 self.assert_pcs_success(
2314 "resource create --no-default-ops dummy-clone ocf:pcsmock:stateful".split(),
2315 )
2316 self.assert_pcs_success(
2317 "resource create --no-default-ops dummy ocf:pcsmock:stateful promotable".split(),
2318 stderr_full=fixture_meta_attributes_not_validated_warning(
2319 ["clone"]
2320 ),
2321 )
2322 self.assert_pcs_success(
2323 "resource config".split(),
2324 dedent(
2325 """\
2326 Resource: dummy-clone (class=ocf provider=pcsmock type=stateful)
2327 Operations:
2328 monitor: dummy-clone-monitor-interval-10s
2329 interval=10s timeout=20s role=Promoted
2330 monitor: dummy-clone-monitor-interval-11s
2331 interval=11s timeout=20s role=Unpromoted
2332 Clone: dummy-clone-1
2333 Meta Attributes: dummy-clone-1-meta_attributes
2334 promotable=true
2335 Resource: dummy (class=ocf provider=pcsmock type=stateful)
2336 Operations:
2337 monitor: dummy-monitor-interval-10s
2338 interval=10s timeout=20s role=Promoted
2339 monitor: dummy-monitor-interval-11s
2340 interval=11s timeout=20s role=Unpromoted
2341 """
2342 ),
2343 )
2344
2345 def test_resource_clone_update(self):
2346 self.assert_pcs_success(
2347 "resource create --no-default-ops D1 ocf:pcsmock:minimal clone".split(),
2348 )
2349 self.assert_pcs_success(
2350 "resource config".split(),
2351 dedent(
2352 """\
2353 Clone: D1-clone
2354 Resource: D1 (class=ocf provider=pcsmock type=minimal)
2355 Operations:
2356 monitor: D1-monitor-interval-10s
2357 interval=10s timeout=20s
2358 """
2359 ),
2360 )
2361
2362 self.assert_pcs_success(
2363 "resource update D1-clone foo=bar".split(),
2364 stderr_full=fixture_meta_attributes_not_validated_warning(
2365 ["clone"]
2366 ),
2367 )
2368 self.assert_pcs_success(
2369 "resource config".split(),
2370 dedent(
2371 """\
2372 Clone: D1-clone
2373 Meta Attributes: D1-clone-meta_attributes
2374 foo=bar
2375 Resource: D1 (class=ocf provider=pcsmock type=minimal)
2376 Operations:
2377 monitor: D1-monitor-interval-10s
2378 interval=10s timeout=20s
2379 """
2380 ),
2381 )
2382
2383 self.assert_pcs_success(
2384 "resource update D1-clone bar=baz".split(),
2385 stderr_full=fixture_meta_attributes_not_validated_warning(
2386 ["clone"]
2387 ),
2388 )
2389 self.assert_pcs_success(
2390 "resource config".split(),
2391 dedent(
2392 """\
2393 Clone: D1-clone
2394 Meta Attributes: D1-clone-meta_attributes
2395 bar=baz
2396 foo=bar
2397 Resource: D1 (class=ocf provider=pcsmock type=minimal)
2398 Operations:
2399 monitor: D1-monitor-interval-10s
2400 interval=10s timeout=20s
2401 """
2402 ),
2403 )
2404
2405 self.assert_pcs_success("resource update D1-clone foo=".split())
2406 self.assert_pcs_success(
2407 "resource config".split(),
2408 dedent(
2409 """\
2410 Clone: D1-clone
2411 Meta Attributes: D1-clone-meta_attributes
2412 bar=baz
2413 Resource: D1 (class=ocf provider=pcsmock type=minimal)
2414 Operations:
2415 monitor: D1-monitor-interval-10s
2416 interval=10s timeout=20s
2417 """
2418 ),
2419 )
2420
2421 def test_mastered_group(self):
2422 self.assert_pcs_success(
2423 "resource create --no-default-ops A ocf:pcsmock:minimal --group AG".split(),
2424 stderr_full=DEPRECATED_DASH_DASH_GROUP,
2425 )
2426 self.assert_pcs_success(
2427 "resource create --no-default-ops B ocf:pcsmock:minimal --group AG".split(),
2428 stderr_full=DEPRECATED_DASH_DASH_GROUP,
2429 )
2430 self.assert_pcs_success(
2431 "resource create --no-default-ops C ocf:pcsmock:minimal --group AG".split(),
2432 stderr_full=DEPRECATED_DASH_DASH_GROUP,
2433 )
2434 # pcs no longer allows turning resources into masters but supports
2435 # existing ones. In order to test it, we need to put a master in the
2436 # CIB without pcs.
2437 wrap_element_by_master(self.temp_cib, "AG", master_id="AGMaster")
2438
2439 self.assert_pcs_fail(
2440 "resource create --no-default-ops A ocf:pcsmock:minimal".split(),
2441 "Error: 'A' already exists\n",
2442 )
2443 self.assert_pcs_fail(
2444 "resource create --no-default-ops AG ocf:pcsmock:minimal".split(),
2445 "Error: 'AG' already exists\n",
2446 )
2447 self.assert_pcs_fail(
2448 "resource create --no-default-ops AGMaster ocf:pcsmock:minimal".split(),
2449 "Error: 'AGMaster' already exists\n",
2450 )
2451
2452 self.assert_pcs_fail(
2453 "resource ungroup AG".split(),
2454 "Error: Cannot remove all resources from a cloned group\n",
2455 )
2456
2457 self.assert_pcs_success(
2458 "resource delete B".split(),
2459 stderr_full=fixture_message_not_deleting_resources_not_live(["B"])
2460 + dedent(
2461 """\
2462 Removing references:
2463 Resource 'B' from:
2464 Group: 'AG'
2465 """
2466 ),
2467 )
2468 self.assert_pcs_success(
2469 "resource delete C".split(),
2470 stderr_full=fixture_message_not_deleting_resources_not_live(["C"])
2471 + dedent(
2472 """\
2473 Removing references:
2474 Resource 'C' from:
2475 Group: 'AG'
2476 """
2477 ),
2478 )
2479 self.assert_pcs_success("resource ungroup AG".split())
2480 self.assert_pcs_success(
2481 "resource config".split(),
2482 dedent(
2483 """\
2484 Clone: AGMaster
2485 Meta Attributes:
2486 promotable=true
2487 Resource: A (class=ocf provider=pcsmock type=minimal)
2488 Operations:
2489 monitor: A-monitor-interval-10s
2490 interval=10s timeout=20s
2491 """
2492 ),
2493 )
2494
2495 def test_cloned_group(self):
2496 self.assert_pcs_success(
2497 "resource create --no-default-ops D1 ocf:pcsmock:minimal --group DG".split(),
2498 stderr_full=DEPRECATED_DASH_DASH_GROUP,
2499 )
2500 self.assert_pcs_success(
2501 "resource create --no-default-ops D2 ocf:pcsmock:minimal --group DG".split(),
2502 stderr_full=DEPRECATED_DASH_DASH_GROUP,
2503 )
2504 self.assert_pcs_success("resource clone DG".split())
2505 self.assert_pcs_success(
2506 "resource config".split(),
2507 dedent(
2508 """\
2509 Clone: DG-clone
2510 Group: DG
2511 Resource: D1 (class=ocf provider=pcsmock type=minimal)
2512 Operations:
2513 monitor: D1-monitor-interval-10s
2514 interval=10s timeout=20s
2515 Resource: D2 (class=ocf provider=pcsmock type=minimal)
2516 Operations:
2517 monitor: D2-monitor-interval-10s
2518 interval=10s timeout=20s
2519 """
2520 ),
2521 )
2522
2523 self.assert_pcs_fail(
2524 "resource create --no-default-ops D1 ocf:pcsmock:minimal".split(),
2525 "Error: 'D1' already exists\n",
2526 )
2527 self.assert_pcs_fail(
2528 "resource create --no-default-ops DG ocf:pcsmock:minimal".split(),
2529 "Error: 'DG' already exists\n",
2530 )
2531 self.assert_pcs_fail(
2532 "resource create --no-default-ops DG-clone ocf:pcsmock:minimal".split(),
2533 "Error: 'DG-clone' already exists\n",
2534 )
2535
2536 def test_op_option(self):
2537 self.assert_pcs_success(
2538 "resource create --no-default-ops B ocf:pcsmock:minimal".split(),
2539 )
2540
2541 self.assert_pcs_fail(
2542 "resource update B ocf:pcsmock:minimal op monitor interval=30s blah=blah".split(),
2543 (
2544 "Error: invalid resource operation option 'blah', allowed "
2545 "options are: 'OCF_CHECK_LEVEL', 'description', 'enabled', "
2546 "'id', 'interval', 'interval-origin', 'name', 'on-fail', "
2547 "'record-pending', 'role', 'start-delay', 'timeout'\n"
2548 ),
2549 )
2550
2551 self.assert_pcs_success(
2552 "resource create --no-default-ops C ocf:pcsmock:minimal".split(),
2553 )
2554
2555 self.assert_pcs_fail(
2556 "resource op add C monitor interval=30s blah=blah".split(),
2557 (
2558 "Error: invalid resource operation option 'blah', allowed "
2559 "options are: 'OCF_CHECK_LEVEL', 'description', 'enabled', "
2560 "'id', 'interval', 'interval-origin', 'name', 'on-fail', "
2561 "'record-pending', 'role', 'start-delay', 'timeout'\n"
2562 ),
2563 )
2564
2565 self.assert_pcs_fail(
2566 "resource op add C monitor interval=60 role=role".split(),
2567 "Error: 'role' is not a valid role value, use {}\n".format(
2568 format_list(const.PCMK_ROLES)
2569 ),
2570 )
2571
2572 self.assert_pcs_success(
2573 "resource config".split(),
2574 dedent(
2575 """\
2576 Resource: B (class=ocf provider=pcsmock type=minimal)
2577 Operations:
2578 monitor: B-monitor-interval-10s
2579 interval=10s timeout=20s
2580 Resource: C (class=ocf provider=pcsmock type=minimal)
2581 Operations:
2582 monitor: C-monitor-interval-10s
2583 interval=10s timeout=20s
2584 """
2585 ),
2586 )
2587
2588 self.assert_pcs_fail(
2589 "resource update B op monitor interval=30s monitor interval=31s role=Master".split(),
2590 "Error: 'Master' is not a valid role value, use {}\n".format(
2591 format_list(const.PCMK_ROLES)
2592 ),
2593 )
2594
2595 self.assert_pcs_success(
2596 "resource update B op monitor interval=30s monitor interval=31s role=promoted".split(),
2597 )
2598
2599 self.assert_pcs_success(
2600 "resource update B op monitor interval=30s monitor interval=31s role=Promoted".split(),
2601 )
2602
2603 self.assert_pcs_success(
2604 "resource config".split(),
2605 dedent(
2606 f"""\
2607 Resource: B (class=ocf provider=pcsmock type=minimal)
2608 Operations:
2609 monitor: B-monitor-interval-30s
2610 interval=30s
2611 monitor: B-monitor-interval-31s
2612 interval=31s role={const.PCMK_ROLE_PROMOTED}
2613 Resource: C (class=ocf provider=pcsmock type=minimal)
2614 Operations:
2615 monitor: C-monitor-interval-10s
2616 interval=10s timeout=20s
2617 """
2618 ),
2619 )
2620
2621 self.assert_pcs_fail(
2622 "resource update B op interval=5s".split(),
2623 "Error: interval=5s does not appear to be a valid operation action\n",
2624 )
2625
2626 def test_clone_bad_resources(self):
2627 self.setup_cluster_a()
2628 self.assert_pcs_fail(
2629 "resource clone ClusterIP4".split(),
2630 "Error: ClusterIP4 is already a clone resource\n",
2631 )
2632 self.assert_pcs_fail(
2633 "resource clone ClusterIP5".split(),
2634 "Error: ClusterIP5 is already a clone resource\n",
2635 )
2636 self.assert_pcs_fail(
2637 "resource promotable ClusterIP4".split(),
2638 "Error: ClusterIP4 is already a clone resource\n",
2639 )
2640 self.assert_pcs_fail(
2641 "resource promotable ClusterIP5".split(),
2642 "Error: ClusterIP5 is already a clone resource\n",
2643 )
2644
2645 def test_group_ms_and_clone(self):
2646 self.assert_pcs_fail(
2647 "resource create --no-default-ops D3 ocf:pcsmock:minimal promotable --group xxx clone".split(),
2648 DEPRECATED_DASH_DASH_GROUP
2649 + "Error: you can specify only one of clone, promotable, bundle or --group\n",
2650 )
2651 self.assert_pcs_fail(
2652 "resource create --no-default-ops D4 ocf:pcsmock:minimal promotable --group xxx".split(),
2653 DEPRECATED_DASH_DASH_GROUP
2654 + "Error: you can specify only one of clone, promotable, bundle or --group\n",
2655 )
2656
2657 def test_resource_missing_values(self):
2658 self.assert_pcs_success(
2659 "resource create --no-default-ops myip params --force".split(),
2660 stderr_full=(
2661 "Assumed agent name 'ocf:pcsmock:params' (deduced from 'params')\n"
2662 "Warning: required resource option 'mandatory' is missing\n"
2663 ),
2664 )
2665 self.assert_pcs_success(
2666 "resource create --no-default-ops myip2 params mandatory=value".split(),
2667 stderr_full=(
2668 "Assumed agent name 'ocf:pcsmock:params' (deduced from 'params')\n"
2669 ),
2670 )
2671 self.assert_pcs_success(
2672 "resource config".split(),
2673 dedent(
2674 """\
2675 Resource: myip (class=ocf provider=pcsmock type=params)
2676 Operations:
2677 monitor: myip-monitor-interval-10s
2678 interval=10s timeout=20s
2679 Resource: myip2 (class=ocf provider=pcsmock type=params)
2680 Attributes: myip2-instance_attributes
2681 mandatory=value
2682 Operations:
2683 monitor: myip2-monitor-interval-10s
2684 interval=10s timeout=20s
2685 """
2686 ),
2687 )
2688
2689 def test_cloned_mastered_group(self):
2690 self.assert_pcs_success(
2691 "resource create dummy1 ocf:pcsmock:minimal --no-default-ops --group dummies".split(),
2692 stderr_full=DEPRECATED_DASH_DASH_GROUP,
2693 )
2694 self.assert_pcs_success(
2695 "resource create dummy2 ocf:pcsmock:minimal --no-default-ops --group dummies".split(),
2696 stderr_full=DEPRECATED_DASH_DASH_GROUP,
2697 )
2698 self.assert_pcs_success(
2699 "resource create dummy3 ocf:pcsmock:minimal --no-default-ops --group dummies".split(),
2700 stderr_full=DEPRECATED_DASH_DASH_GROUP,
2701 )
2702 self.assert_pcs_success("resource clone dummies".split())
2703 self.assert_pcs_success(
2704 "resource config dummies-clone".split(),
2705 dedent(
2706 """\
2707 Clone: dummies-clone
2708 Group: dummies
2709 Resource: dummy1 (class=ocf provider=pcsmock type=minimal)
2710 Operations:
2711 monitor: dummy1-monitor-interval-10s
2712 interval=10s timeout=20s
2713 Resource: dummy2 (class=ocf provider=pcsmock type=minimal)
2714 Operations:
2715 monitor: dummy2-monitor-interval-10s
2716 interval=10s timeout=20s
2717 Resource: dummy3 (class=ocf provider=pcsmock type=minimal)
2718 Operations:
2719 monitor: dummy3-monitor-interval-10s
2720 interval=10s timeout=20s
2721 """
2722 ),
2723 )
2724
2725 self.assert_pcs_success("resource unclone dummies-clone".split())
2726 stdout, stderr, returncode = self.pcs_runner.run(
2727 "resource status".split()
2728 )
2729 self.assertEqual(stderr, "")
2730 self.assertEqual(returncode, 0)
2731 if is_pacemaker_21_without_20_compatibility():
2732 self.assertEqual(
2733 stdout,
2734 outdent(
2735 """\
2736 * Resource Group: dummies:
2737 * dummy1\t(ocf:pcsmock:minimal):\t Stopped
2738 * dummy2\t(ocf:pcsmock:minimal):\t Stopped
2739 * dummy3\t(ocf:pcsmock:minimal):\t Stopped
2740 """
2741 ),
2742 )
2743 elif PCMK_2_0_3_PLUS:
2744 assert_pcs_status(
2745 stdout,
2746 outdent(
2747 """\
2748 * Resource Group: dummies:
2749 * dummy1\t(ocf::pcsmock:minimal):\tStopped
2750 * dummy2\t(ocf::pcsmock:minimal):\tStopped
2751 * dummy3\t(ocf::pcsmock:minimal):\tStopped
2752 """
2753 ),
2754 )
2755 else:
2756 self.assertEqual(
2757 stdout,
2758 outdent(
2759 """\
2760 Resource Group: dummies
2761 dummy1\t(ocf::pcsmock:minimal):\tStopped
2762 dummy2\t(ocf::pcsmock:minimal):\tStopped
2763 dummy3\t(ocf::pcsmock:minimal):\tStopped
2764 """
2765 ),
2766 )
2767
2768 self.assert_pcs_success("resource clone dummies".split())
2769 self.assert_pcs_success(
2770 "resource config dummies-clone".split(),
2771 dedent(
2772 """\
2773 Clone: dummies-clone
2774 Group: dummies
2775 Resource: dummy1 (class=ocf provider=pcsmock type=minimal)
2776 Operations:
2777 monitor: dummy1-monitor-interval-10s
2778 interval=10s timeout=20s
2779 Resource: dummy2 (class=ocf provider=pcsmock type=minimal)
2780 Operations:
2781 monitor: dummy2-monitor-interval-10s
2782 interval=10s timeout=20s
2783 Resource: dummy3 (class=ocf provider=pcsmock type=minimal)
2784 Operations:
2785 monitor: dummy3-monitor-interval-10s
2786 interval=10s timeout=20s
2787 """
2788 ),
2789 )
2790
2791 self.assert_pcs_success(
2792 "resource delete dummies-clone".split(),
2793 stderr_full=fixture_message_not_deleting_resources_not_live(
2794 ["dummy1", "dummy2", "dummy3", "dummies", "dummies-clone"]
2795 )
2796 + dedent(
2797 """\
2798 Removing dependant elements:
2799 Group: 'dummies'
2800 Resources: 'dummy1', 'dummy2', 'dummy3'
2801 """
2802 ),
2803 )
2804 self.assert_pcs_success(
2805 "resource status".split(), "NO resources configured\n"
2806 )
2807
2808 self.assert_pcs_success(
2809 "resource create dummy1 ocf:pcsmock:minimal --no-default-ops --group dummies".split(),
2810 stderr_full=DEPRECATED_DASH_DASH_GROUP,
2811 )
2812 self.assert_pcs_success(
2813 "resource create dummy2 ocf:pcsmock:minimal --no-default-ops --group dummies".split(),
2814 stderr_full=DEPRECATED_DASH_DASH_GROUP,
2815 )
2816 self.assert_pcs_success(
2817 "resource create dummy3 ocf:pcsmock:minimal --no-default-ops --group dummies".split(),
2818 stderr_full=DEPRECATED_DASH_DASH_GROUP,
2819 )
2820 # pcs no longer allows turning resources into masters but supports
2821 # existing ones. In order to test it, we need to put a master in the
2822 # CIB without pcs.
2823 wrap_element_by_master(self.temp_cib, "dummies")
2824
2825 self.assert_pcs_success(
2826 "resource config dummies-master".split(),
2827 dedent(
2828 """\
2829 Clone: dummies-master
2830 Meta Attributes:
2831 promotable=true
2832 Group: dummies
2833 Resource: dummy1 (class=ocf provider=pcsmock type=minimal)
2834 Operations:
2835 monitor: dummy1-monitor-interval-10s
2836 interval=10s timeout=20s
2837 Resource: dummy2 (class=ocf provider=pcsmock type=minimal)
2838 Operations:
2839 monitor: dummy2-monitor-interval-10s
2840 interval=10s timeout=20s
2841 Resource: dummy3 (class=ocf provider=pcsmock type=minimal)
2842 Operations:
2843 monitor: dummy3-monitor-interval-10s
2844 interval=10s timeout=20s
2845 """
2846 ),
2847 )
2848
2849 self.assert_pcs_success("resource unclone dummies-master".split())
2850 stdout, stderr, returncode = self.pcs_runner.run(
2851 "resource status".split()
2852 )
2853 self.assertEqual(stderr, "")
2854 self.assertEqual(returncode, 0)
2855 if is_pacemaker_21_without_20_compatibility():
2856 self.assertEqual(
2857 stdout,
2858 outdent(
2859 """\
2860 * Resource Group: dummies:
2861 * dummy1\t(ocf:pcsmock:minimal):\t Stopped
2862 * dummy2\t(ocf:pcsmock:minimal):\t Stopped
2863 * dummy3\t(ocf:pcsmock:minimal):\t Stopped
2864 """
2865 ),
2866 )
2867 elif PCMK_2_0_3_PLUS:
2868 assert_pcs_status(
2869 stdout,
2870 outdent(
2871 """\
2872 * Resource Group: dummies:
2873 * dummy1\t(ocf::pcsmock:minimal):\tStopped
2874 * dummy2\t(ocf::pcsmock:minimal):\tStopped
2875 * dummy3\t(ocf::pcsmock:minimal):\tStopped
2876 """
2877 ),
2878 )
2879 else:
2880 self.assertEqual(
2881 stdout,
2882 outdent(
2883 """\
2884 Resource Group: dummies
2885 dummy1\t(ocf::pcsmock:minimal):\tStopped
2886 dummy2\t(ocf::pcsmock:minimal):\tStopped
2887 dummy3\t(ocf::pcsmock:minimal):\tStopped
2888 """
2889 ),
2890 )
2891
2892 # pcs no longer allows turning resources into masters but supports
2893 # existing ones. In order to test it, we need to put a master in the
2894 # CIB without pcs.
2895 wrap_element_by_master(self.temp_cib, "dummies")
2896 self.assert_pcs_success(
2897 "resource config dummies-master".split(),
2898 dedent(
2899 """\
2900 Clone: dummies-master
2901 Meta Attributes:
2902 promotable=true
2903 Group: dummies
2904 Resource: dummy1 (class=ocf provider=pcsmock type=minimal)
2905 Operations:
2906 monitor: dummy1-monitor-interval-10s
2907 interval=10s timeout=20s
2908 Resource: dummy2 (class=ocf provider=pcsmock type=minimal)
2909 Operations:
2910 monitor: dummy2-monitor-interval-10s
2911 interval=10s timeout=20s
2912 Resource: dummy3 (class=ocf provider=pcsmock type=minimal)
2913 Operations:
2914 monitor: dummy3-monitor-interval-10s
2915 interval=10s timeout=20s
2916 """
2917 ),
2918 )
2919
2920 self.assert_pcs_success(
2921 "resource delete dummies-master".split(),
2922 stderr_full=fixture_message_not_deleting_resources_not_live(
2923 ["dummy1", "dummy2", "dummy3", "dummies", "dummies-master"]
2924 )
2925 + dedent(
2926 """\
2927 Removing dependant elements:
2928 Group: 'dummies'
2929 Resources: 'dummy1', 'dummy2', 'dummy3'
2930 """
2931 ),
2932 )
2933 self.assert_pcs_success(
2934 "resource status".split(), "NO resources configured\n"
2935 )
2936
2937 def test_relocate_stickiness(self):
2938 self.assert_pcs_success(
2939 "resource create D1 ocf:pcsmock:minimal --no-default-ops".split()
2940 )
2941 self.assert_pcs_success(
2942 "resource create DG1 ocf:pcsmock:minimal --no-default-ops --group GR".split(),
2943 stderr_full=DEPRECATED_DASH_DASH_GROUP,
2944 )
2945 self.assert_pcs_success(
2946 "resource create DG2 ocf:pcsmock:minimal --no-default-ops --group GR".split(),
2947 stderr_full=DEPRECATED_DASH_DASH_GROUP,
2948 )
2949 self.assert_pcs_success(
2950 "resource create DC ocf:pcsmock:minimal --no-default-ops clone".split()
2951 )
2952 self.assert_pcs_success(
2953 "resource create DGC1 ocf:pcsmock:minimal --no-default-ops --group GRC".split(),
2954 stderr_full=DEPRECATED_DASH_DASH_GROUP,
2955 )
2956 self.assert_pcs_success(
2957 "resource create DGC2 ocf:pcsmock:minimal --no-default-ops --group GRC".split(),
2958 stderr_full=DEPRECATED_DASH_DASH_GROUP,
2959 )
2960 self.assert_pcs_success("resource clone GRC".split())
2961
2962 status = dedent(
2963 """\
2964 Resource: D1 (class=ocf provider=pcsmock type=minimal)
2965 Operations:
2966 monitor: D1-monitor-interval-10s
2967 interval=10s timeout=20s
2968 Group: GR
2969 Resource: DG1 (class=ocf provider=pcsmock type=minimal)
2970 Operations:
2971 monitor: DG1-monitor-interval-10s
2972 interval=10s timeout=20s
2973 Resource: DG2 (class=ocf provider=pcsmock type=minimal)
2974 Operations:
2975 monitor: DG2-monitor-interval-10s
2976 interval=10s timeout=20s
2977 Clone: DC-clone
2978 Resource: DC (class=ocf provider=pcsmock type=minimal)
2979 Operations:
2980 monitor: DC-monitor-interval-10s
2981 interval=10s timeout=20s
2982 Clone: GRC-clone
2983 Group: GRC
2984 Resource: DGC1 (class=ocf provider=pcsmock type=minimal)
2985 Operations:
2986 monitor: DGC1-monitor-interval-10s
2987 interval=10s timeout=20s
2988 Resource: DGC2 (class=ocf provider=pcsmock type=minimal)
2989 Operations:
2990 monitor: DGC2-monitor-interval-10s
2991 interval=10s timeout=20s
2992 """
2993 )
2994
2995 cib_original, stderr, returncode = self.pcs_runner.run(
2996 "cluster cib".split()
2997 )
2998 self.assertEqual(stderr, "")
2999 self.assertEqual(returncode, 0)
3000
3001 resources = {
3002 "D1",
3003 "DG1",
3004 "DG2",
3005 "GR",
3006 "DC",
3007 "DC-clone",
3008 "DGC1",
3009 "DGC2",
3010 "GRC",
3011 "GRC-clone",
3012 }
3013 self.assert_pcs_success("resource config".split(), status)
3014 cib_in = utils.parseString(cib_original)
3015 cib_out, updated_resources = resource.resource_relocate_set_stickiness(
3016 cib_in
3017 )
3018 self.assertFalse(cib_in is cib_out)
3019 self.assertEqual(resources, updated_resources)
3020 self.assert_pcs_success("resource config".split(), status)
3021 write_data_to_tmpfile(cib_out.toxml(), self.temp_cib)
3022
3023 self.assert_pcs_success(
3024 "resource config".split(),
3025 dedent(
3026 """\
3027 Resource: D1 (class=ocf provider=pcsmock type=minimal)
3028 Meta Attributes: D1-meta_attributes
3029 resource-stickiness=0
3030 Operations:
3031 monitor: D1-monitor-interval-10s
3032 interval=10s timeout=20s
3033 Group: GR
3034 Meta Attributes: GR-meta_attributes
3035 resource-stickiness=0
3036 Resource: DG1 (class=ocf provider=pcsmock type=minimal)
3037 Meta Attributes: DG1-meta_attributes
3038 resource-stickiness=0
3039 Operations:
3040 monitor: DG1-monitor-interval-10s
3041 interval=10s timeout=20s
3042 Resource: DG2 (class=ocf provider=pcsmock type=minimal)
3043 Meta Attributes: DG2-meta_attributes
3044 resource-stickiness=0
3045 Operations:
3046 monitor: DG2-monitor-interval-10s
3047 interval=10s timeout=20s
3048 Clone: DC-clone
3049 Meta Attributes: DC-clone-meta_attributes
3050 resource-stickiness=0
3051 Resource: DC (class=ocf provider=pcsmock type=minimal)
3052 Meta Attributes: DC-meta_attributes
3053 resource-stickiness=0
3054 Operations:
3055 monitor: DC-monitor-interval-10s
3056 interval=10s timeout=20s
3057 Clone: GRC-clone
3058 Meta Attributes: GRC-clone-meta_attributes
3059 resource-stickiness=0
3060 Group: GRC
3061 Meta Attributes: GRC-meta_attributes
3062 resource-stickiness=0
3063 Resource: DGC1 (class=ocf provider=pcsmock type=minimal)
3064 Meta Attributes: DGC1-meta_attributes
3065 resource-stickiness=0
3066 Operations:
3067 monitor: DGC1-monitor-interval-10s
3068 interval=10s timeout=20s
3069 Resource: DGC2 (class=ocf provider=pcsmock type=minimal)
3070 Meta Attributes: DGC2-meta_attributes
3071 resource-stickiness=0
3072 Operations:
3073 monitor: DGC2-monitor-interval-10s
3074 interval=10s timeout=20s
3075 """
3076 ),
3077 )
3078
3079 resources = {"D1", "DG1", "DC", "DGC1"}
3080 write_data_to_tmpfile(cib_original, self.temp_cib)
3081 self.assert_pcs_success("resource config".split(), status)
3082 cib_in = utils.parseString(cib_original)
3083 cib_out, updated_resources = resource.resource_relocate_set_stickiness(
3084 cib_in, resources
3085 )
3086 self.assertFalse(cib_in is cib_out)
3087 self.assertEqual(resources, updated_resources)
3088 self.assert_pcs_success("resource config".split(), status)
3089 write_data_to_tmpfile(cib_out.toxml(), self.temp_cib)
3090 self.assert_pcs_success(
3091 "resource config".split(),
3092 dedent(
3093 """\
3094 Resource: D1 (class=ocf provider=pcsmock type=minimal)
3095 Meta Attributes: D1-meta_attributes
3096 resource-stickiness=0
3097 Operations:
3098 monitor: D1-monitor-interval-10s
3099 interval=10s timeout=20s
3100 Group: GR
3101 Resource: DG1 (class=ocf provider=pcsmock type=minimal)
3102 Meta Attributes: DG1-meta_attributes
3103 resource-stickiness=0
3104 Operations:
3105 monitor: DG1-monitor-interval-10s
3106 interval=10s timeout=20s
3107 Resource: DG2 (class=ocf provider=pcsmock type=minimal)
3108 Operations:
3109 monitor: DG2-monitor-interval-10s
3110 interval=10s timeout=20s
3111 Clone: DC-clone
3112 Resource: DC (class=ocf provider=pcsmock type=minimal)
3113 Meta Attributes: DC-meta_attributes
3114 resource-stickiness=0
3115 Operations:
3116 monitor: DC-monitor-interval-10s
3117 interval=10s timeout=20s
3118 Clone: GRC-clone
3119 Group: GRC
3120 Resource: DGC1 (class=ocf provider=pcsmock type=minimal)
3121 Meta Attributes: DGC1-meta_attributes
3122 resource-stickiness=0
3123 Operations:
3124 monitor: DGC1-monitor-interval-10s
3125 interval=10s timeout=20s
3126 Resource: DGC2 (class=ocf provider=pcsmock type=minimal)
3127 Operations:
3128 monitor: DGC2-monitor-interval-10s
3129 interval=10s timeout=20s
3130 """
3131 ),
3132 )
3133
3134 resources = {"GRC-clone", "GRC", "DGC1", "DGC2"}
3135 write_data_to_tmpfile(cib_original, self.temp_cib)
3136 self.assert_pcs_success("resource config".split(), status)
3137 cib_in = utils.parseString(cib_original)
3138 cib_out, updated_resources = resource.resource_relocate_set_stickiness(
3139 cib_in, ["GRC-clone"]
3140 )
3141 self.assertFalse(cib_in is cib_out)
3142 self.assertEqual(resources, updated_resources)
3143 self.assert_pcs_success("resource config".split(), status)
3144 write_data_to_tmpfile(cib_out.toxml(), self.temp_cib)
3145 self.assert_pcs_success(
3146 "resource config".split(),
3147 dedent(
3148 """\
3149 Resource: D1 (class=ocf provider=pcsmock type=minimal)
3150 Operations:
3151 monitor: D1-monitor-interval-10s
3152 interval=10s timeout=20s
3153 Group: GR
3154 Resource: DG1 (class=ocf provider=pcsmock type=minimal)
3155 Operations:
3156 monitor: DG1-monitor-interval-10s
3157 interval=10s timeout=20s
3158 Resource: DG2 (class=ocf provider=pcsmock type=minimal)
3159 Operations:
3160 monitor: DG2-monitor-interval-10s
3161 interval=10s timeout=20s
3162 Clone: DC-clone
3163 Resource: DC (class=ocf provider=pcsmock type=minimal)
3164 Operations:
3165 monitor: DC-monitor-interval-10s
3166 interval=10s timeout=20s
3167 Clone: GRC-clone
3168 Meta Attributes: GRC-clone-meta_attributes
3169 resource-stickiness=0
3170 Group: GRC
3171 Meta Attributes: GRC-meta_attributes
3172 resource-stickiness=0
3173 Resource: DGC1 (class=ocf provider=pcsmock type=minimal)
3174 Meta Attributes: DGC1-meta_attributes
3175 resource-stickiness=0
3176 Operations:
3177 monitor: DGC1-monitor-interval-10s
3178 interval=10s timeout=20s
3179 Resource: DGC2 (class=ocf provider=pcsmock type=minimal)
3180 Meta Attributes: DGC2-meta_attributes
3181 resource-stickiness=0
3182 Operations:
3183 monitor: DGC2-monitor-interval-10s
3184 interval=10s timeout=20s
3185 """
3186 ),
3187 )
3188
3189 resources = {"GR", "DG1", "DG2", "DC-clone", "DC"}
3190 write_data_to_tmpfile(cib_original, self.temp_cib)
3191 self.assert_pcs_success("resource config".split(), status)
3192 cib_in = utils.parseString(cib_original)
3193 cib_out, updated_resources = resource.resource_relocate_set_stickiness(
3194 cib_in, ["GR", "DC-clone"]
3195 )
3196 self.assertFalse(cib_in is cib_out)
3197 self.assertEqual(resources, updated_resources)
3198 self.assert_pcs_success("resource config".split(), status)
3199 write_data_to_tmpfile(cib_out.toxml(), self.temp_cib)
3200 self.assert_pcs_success(
3201 "resource config".split(),
3202 dedent(
3203 """\
3204 Resource: D1 (class=ocf provider=pcsmock type=minimal)
3205 Operations:
3206 monitor: D1-monitor-interval-10s
3207 interval=10s timeout=20s
3208 Group: GR
3209 Meta Attributes: GR-meta_attributes
3210 resource-stickiness=0
3211 Resource: DG1 (class=ocf provider=pcsmock type=minimal)
3212 Meta Attributes: DG1-meta_attributes
3213 resource-stickiness=0
3214 Operations:
3215 monitor: DG1-monitor-interval-10s
3216 interval=10s timeout=20s
3217 Resource: DG2 (class=ocf provider=pcsmock type=minimal)
3218 Meta Attributes: DG2-meta_attributes
3219 resource-stickiness=0
3220 Operations:
3221 monitor: DG2-monitor-interval-10s
3222 interval=10s timeout=20s
3223 Clone: DC-clone
3224 Meta Attributes: DC-clone-meta_attributes
3225 resource-stickiness=0
3226 Resource: DC (class=ocf provider=pcsmock type=minimal)
3227 Meta Attributes: DC-meta_attributes
3228 resource-stickiness=0
3229 Operations:
3230 monitor: DC-monitor-interval-10s
3231 interval=10s timeout=20s
3232 Clone: GRC-clone
3233 Group: GRC
3234 Resource: DGC1 (class=ocf provider=pcsmock type=minimal)
3235 Operations:
3236 monitor: DGC1-monitor-interval-10s
3237 interval=10s timeout=20s
3238 Resource: DGC2 (class=ocf provider=pcsmock type=minimal)
3239 Operations:
3240 monitor: DGC2-monitor-interval-10s
3241 interval=10s timeout=20s
3242 """
3243 ),
3244 )
3245
3246
3247 class OperationDeleteRemoveMixin(
3248 get_assert_pcs_effect_mixin(
3249 lambda cib: etree.tostring(etree.parse(cib).findall(".//resources")[0])
3250 )
3251 ):
3252 # see also BundleMiscCommands
3253
3254 def setUp(self):
3255 self.empty_cib = empty_cib
3256 self.temp_cib = get_tmp_file("tier1_resource_operation_delete")
3257 self.temp_large_cib = get_tmp_file(
3258 "tier1_resource_large_operation_delete"
3259 )
3260 write_file_to_tmpfile(empty_cib, self.temp_cib)
3261 write_file_to_tmpfile(large_cib, self.temp_large_cib)
3262 self.pcs_runner = PcsRunner(self.temp_cib.name)
3263 self.pcs_runner.mock_settings = get_mock_settings()
3264 self.command = "to-be-overridden"
3265
3266 def tearDown(self):
3267 self.temp_cib.close()
3268 self.temp_large_cib.close()
3269
3270 fixture_xml_1_monitor = """
3271 <resources>
3272 <primitive class="ocf" id="R" provider="pcsmock" type="minimal">
3273 <operations>
3274 <op id="R-monitor-interval-10s" interval="10s"
3275 name="monitor" timeout="20s"
3276 />
3277 </operations>
3278 </primitive>
3279 </resources>
3280 """
3281
3282 fixture_xml_empty_operations = """
3283 <resources>
3284 <primitive class="ocf" id="R" provider="pcsmock" type="minimal">
3285 <operations>
3286 </operations>
3287 </primitive>
3288 </resources>
3289 """
3290
3291 def fixture_resource(self):
3292 self.assert_effect(
3293 "resource create --no-default-ops R ocf:pcsmock:minimal".split(),
3294 self.fixture_xml_1_monitor,
3295 )
3296
3297 def fixture_monitor_20(self):
3298 self.assert_effect(
3299 "resource op add R monitor interval=20s timeout=20s --force".split(),
3300 """
3301 <resources>
3302 <primitive class="ocf" id="R" provider="pcsmock"
3303 type="minimal"
3304 >
3305 <operations>
3306 <op id="R-monitor-interval-10s" interval="10s"
3307 name="monitor" timeout="20s"
3308 />
3309 <op id="R-monitor-interval-20s" interval="20s"
3310 name="monitor" timeout="20s"
3311 />
3312 </operations>
3313 </primitive>
3314 </resources>
3315 """,
3316 )
3317
3318 def fixture_start(self):
3319 self.assert_effect(
3320 "resource op add R start timeout=20s".split(),
3321 """
3322 <resources>
3323 <primitive class="ocf" id="R" provider="pcsmock"
3324 type="minimal"
3325 >
3326 <operations>
3327 <op id="R-monitor-interval-10s" interval="10s"
3328 name="monitor" timeout="20s"
3329 />
3330 <op id="R-monitor-interval-20s" interval="20s"
3331 name="monitor" timeout="20s"
3332 />
3333 <op id="R-start-interval-0s" interval="0s"
3334 name="start" timeout="20s"
3335 />
3336 </operations>
3337 </primitive>
3338 </resources>
3339 """,
3340 )
3341
3342 def test_remove_missing_op(self):
3343 assert self.command in {"delete", "remove"}
3344 self.fixture_resource()
3345 self.assert_pcs_fail(
3346 f"resource op {self.command} R-monitor-interval-30s".split(),
3347 "Error: unable to find operation id: R-monitor-interval-30s\n",
3348 )
3349
3350 def test_keep_empty_operations(self):
3351 assert self.command in {"delete", "remove"}
3352 self.fixture_resource()
3353 self.assert_effect(
3354 f"resource op {self.command} R-monitor-interval-10s".split(),
3355 self.fixture_xml_empty_operations,
3356 )
3357
3358 def test_remove_by_id_success(self):
3359 assert self.command in {"delete", "remove"}
3360 self.fixture_resource()
3361 self.fixture_monitor_20()
3362 self.assert_effect(
3363 f"resource op {self.command} R-monitor-interval-20s".split(),
3364 self.fixture_xml_1_monitor,
3365 )
3366
3367 def test_remove_all_monitors(self):
3368 assert self.command in {"delete", "remove"}
3369 self.fixture_resource()
3370 self.fixture_monitor_20()
3371 self.fixture_start()
3372 self.assert_effect(
3373 f"resource op {self.command} R monitor".split(),
3374 """
3375 <resources>
3376 <primitive class="ocf" id="R" provider="pcsmock"
3377 type="minimal"
3378 >
3379 <operations>
3380 <op id="R-start-interval-0s" interval="0s"
3381 name="start" timeout="20s"
3382 />
3383 </operations>
3384 </primitive>
3385 </resources>
3386 """,
3387 )
3388
3389
3390 class OperationDelete(OperationDeleteRemoveMixin, TestCase):
3391 def setUp(self):
3392 super().setUp()
3393 self.command = "delete"
3394
3395 def test_usage(self):
3396 self.assert_pcs_fail(
3397 "resource op delete".split(),
3398 stderr_start="\nUsage: pcs resource op delete...",
3399 )
3400
3401
3402 class OperationRemove(OperationDeleteRemoveMixin, TestCase):
3403 def setUp(self):
3404 super().setUp()
3405 self.command = "remove"
3406
3407 def test_usage(self):
3408 self.assert_pcs_fail(
3409 "resource op remove".split(),
3410 stderr_start="\nUsage: pcs resource op remove...",
3411 )
3412
3413
3414 class Utilization(
3415 TestCase,
3416 get_assert_pcs_effect_mixin(
3417 lambda cib: etree.tostring(etree.parse(cib).findall(".//resources")[0])
3418 ),
3419 ):
3420 def setUp(self):
3421 self.empty_cib = empty_cib
3422 self.temp_cib = get_tmp_file("tier1_resource_utilization")
3423 self.temp_large_cib = get_tmp_file("tier1_resource_utilization_large")
3424 write_file_to_tmpfile(empty_cib, self.temp_cib)
3425 write_file_to_tmpfile(large_cib, self.temp_large_cib)
3426 self.pcs_runner = PcsRunner(self.temp_cib.name)
3427 self.pcs_runner.mock_settings = get_mock_settings()
3428
3429 def tearDown(self):
3430 self.temp_cib.close()
3431 self.temp_large_cib.close()
3432
3433 @staticmethod
3434 def fixture_xml_resource_no_utilization():
3435 return """
3436 <resources>
3437 <primitive class="ocf" id="R" provider="pcsmock" type="minimal">
3438 <operations>
3439 <op id="R-monitor-interval-10s" interval="10s"
3440 name="monitor" timeout="20s"
3441 />
3442 </operations>
3443 </primitive>
3444 </resources>
3445 """
3446
3447 @staticmethod
3448 def fixture_xml_resource_empty_utilization():
3449 return """
3450 <resources>
3451 <primitive class="ocf" id="R" provider="pcsmock" type="minimal">
3452 <operations>
3453 <op id="R-monitor-interval-10s" interval="10s"
3454 name="monitor" timeout="20s"
3455 />
3456 </operations>
3457 <utilization id="R-utilization" />
3458 </primitive>
3459 </resources>
3460 """
3461
3462 @staticmethod
3463 def fixture_xml_resource_with_utilization():
3464 return """
3465 <resources>
3466 <primitive class="ocf" id="R" provider="pcsmock" type="minimal">
3467 <operations>
3468 <op id="R-monitor-interval-10s" interval="10s"
3469 name="monitor" timeout="20s"
3470 />
3471 </operations>
3472 <utilization id="R-utilization">
3473 <nvpair id="R-utilization-test" name="test"
3474 value="100"
3475 />
3476 </utilization>
3477 </primitive>
3478 </resources>
3479 """
3480
3481 def fixture_resource(self):
3482 self.assert_effect(
3483 "resource create --no-default-ops R ocf:pcsmock:minimal".split(),
3484 self.fixture_xml_resource_no_utilization(),
3485 )
3486
3487 def fixture_resource_utilization(self):
3488 self.fixture_resource()
3489 self.assert_effect(
3490 "resource utilization R test=100".split(),
3491 self.fixture_xml_resource_with_utilization(),
3492 stderr_full=FIXTURE_UTILIZATION_WARNING,
3493 )
3494
3495 def test_resource_utilization_set(self):
3496 # see also BundleMiscCommands
3497 self.pcs_runner = PcsRunner(self.temp_large_cib.name)
3498 self.pcs_runner.mock_settings = get_mock_settings()
3499
3500 self.assert_pcs_success(
3501 "resource utilization dummy test1=10".split(),
3502 stderr_full=FIXTURE_UTILIZATION_WARNING,
3503 )
3504 self.assert_pcs_success(
3505 "resource utilization dummy1".split(),
3506 dedent(
3507 """\
3508 Resource Utilization:
3509 dummy1:
3510 """
3511 ),
3512 stderr_full=FIXTURE_UTILIZATION_WARNING,
3513 )
3514 self.assert_pcs_success(
3515 "resource utilization dummy".split(),
3516 dedent(
3517 """\
3518 Resource Utilization:
3519 dummy: test1=10
3520 """
3521 ),
3522 stderr_full=FIXTURE_UTILIZATION_WARNING,
3523 )
3524 self.assert_pcs_success(
3525 "resource utilization dummy test1=-10 test4=1234".split(),
3526 stderr_full=FIXTURE_UTILIZATION_WARNING,
3527 )
3528 self.assert_pcs_success(
3529 "resource utilization dummy".split(),
3530 dedent(
3531 """\
3532 Resource Utilization:
3533 dummy: test1=-10 test4=1234
3534 """
3535 ),
3536 stderr_full=FIXTURE_UTILIZATION_WARNING,
3537 )
3538 self.assert_pcs_success(
3539 "resource utilization dummy1 test2=321 empty=".split(),
3540 stderr_full=FIXTURE_UTILIZATION_WARNING,
3541 )
3542 self.assert_pcs_success(
3543 "resource utilization dummy1".split(),
3544 dedent(
3545 """\
3546 Resource Utilization:
3547 dummy1: test2=321
3548 """
3549 ),
3550 stderr_full=FIXTURE_UTILIZATION_WARNING,
3551 )
3552 self.assert_pcs_success(
3553 "resource utilization".split(),
3554 dedent(
3555 """\
3556 Resource Utilization:
3557 dummy: test1=-10 test4=1234
3558 dummy1: test2=321
3559 """
3560 ),
3561 stderr_full=FIXTURE_UTILIZATION_WARNING,
3562 )
3563
3564 def test_no_warning_printed_placement_strategy_is_set(self):
3565 self.fixture_resource()
3566 self.assert_effect(
3567 "property set placement-strategy=minimal".split(),
3568 self.fixture_xml_resource_no_utilization(),
3569 )
3570 self.assert_resources_xml_in_cib(
3571 """
3572 <crm_config>
3573 <cluster_property_set id="cib-bootstrap-options">
3574 <nvpair id="cib-bootstrap-options-placement-strategy"
3575 name="placement-strategy" value="minimal"
3576 />
3577 </cluster_property_set>
3578 </crm_config>
3579 """,
3580 get_cib_part_func=lambda cib: etree.tostring(
3581 etree.parse(cib).findall(".//crm_config")[0],
3582 ),
3583 )
3584 self.assert_effect(
3585 "resource utilization R test=100".split(),
3586 self.fixture_xml_resource_with_utilization(),
3587 )
3588 self.assert_pcs_success(
3589 "resource utilization".split(),
3590 dedent(
3591 """\
3592 Resource Utilization:
3593 R: test=100
3594 """
3595 ),
3596 )
3597
3598 def test_resource_utilization_set_invalid(self):
3599 self.pcs_runner = PcsRunner(self.temp_large_cib.name)
3600 self.pcs_runner.mock_settings = get_mock_settings()
3601 self.assert_pcs_fail(
3602 "resource utilization dummy test".split(),
3603 (
3604 f"{FIXTURE_UTILIZATION_WARNING}"
3605 "Error: missing value of 'test' option\n"
3606 ),
3607 )
3608 self.assert_pcs_fail(
3609 "resource utilization dummy =10".split(),
3610 f"{FIXTURE_UTILIZATION_WARNING}Error: missing key in '=10' option\n",
3611 )
3612 self.assert_pcs_fail(
3613 "resource utilization dummy0".split(),
3614 (
3615 f"{FIXTURE_UTILIZATION_WARNING}"
3616 "Error: Unable to find a resource: dummy0\n"
3617 ),
3618 )
3619 self.assert_pcs_fail(
3620 "resource utilization dummy0 test=10".split(),
3621 (
3622 f"{FIXTURE_UTILIZATION_WARNING}"
3623 "Error: Unable to find a resource: dummy0\n"
3624 ),
3625 )
3626 self.assert_pcs_fail(
3627 "resource utilization dummy1 test1=10 test=int".split(),
3628 (
3629 f"{FIXTURE_UTILIZATION_WARNING}"
3630 "Error: Value of utilization attribute must be integer: "
3631 "'test=int'\n"
3632 ),
3633 )
3634
3635 def test_keep_empty_nvset(self):
3636 self.fixture_resource_utilization()
3637 self.assert_effect(
3638 "resource utilization R test=".split(),
3639 self.fixture_xml_resource_empty_utilization(),
3640 stderr_full=FIXTURE_UTILIZATION_WARNING,
3641 )
3642
3643 def test_dont_create_nvset_on_removal(self):
3644 self.fixture_resource()
3645 self.assert_effect(
3646 "resource utilization R test=".split(),
3647 self.fixture_xml_resource_no_utilization(),
3648 stderr_full=FIXTURE_UTILIZATION_WARNING,
3649 )
3650
3651
3652 class MetaAttrs(
3653 TestCase,
3654 get_assert_pcs_effect_mixin(
3655 lambda cib: etree.tostring(etree.parse(cib).findall(".//resources")[0])
3656 ),
3657 ):
3658 def setUp(self):
3659 self.empty_cib = empty_cib
3660 self.temp_cib = get_tmp_file("tier1_resource_meta")
3661 write_file_to_tmpfile(empty_cib, self.temp_cib)
3662 self.pcs_runner = PcsRunner(self.temp_cib.name)
3663 self.pcs_runner.mock_settings = get_mock_settings()
3664
3665 def tearDown(self):
3666 self.temp_cib.close()
3667
3668 def set_cib_file(self, *xml_string_list):
3669 xml_manip = XmlManipulation.from_file(self.empty_cib)
3670 xml_manip.append_to_first_tag_name("resources", *xml_string_list)
3671 write_data_to_tmpfile(str(xml_manip), self.temp_cib)
3672
3673 @staticmethod
3674 def _fixture_xml_resource_no_meta():
3675 return """
3676 <primitive class="ocf" id="R" provider="pcsmock" type="minimal">
3677 <operations>
3678 <op id="R-monitor-interval-10s" interval="10s"
3679 name="monitor" timeout="20s"
3680 />
3681 </operations>
3682 </primitive>
3683 """
3684
3685 @staticmethod
3686 def fixture_xml_resource_no_meta():
3687 return f"""
3688 <resources>
3689 {MetaAttrs._fixture_xml_resource_no_meta()}
3690 </resources>
3691 """
3692
3693 @staticmethod
3694 def fixture_xml_resource_empty_meta():
3695 return """
3696 <resources>
3697 <primitive class="ocf" id="R" provider="pcsmock" type="minimal">
3698 <meta_attributes id="R-meta_attributes" />
3699 <operations>
3700 <op id="R-monitor-interval-10s" interval="10s"
3701 name="monitor" timeout="20s"
3702 />
3703 </operations>
3704 </primitive>
3705 </resources>
3706 """
3707
3708 @staticmethod
3709 def fixture_xml_resource_with_meta():
3710 return """
3711 <resources>
3712 <primitive class="ocf" id="R" provider="pcsmock" type="minimal">
3713 <meta_attributes id="R-meta_attributes">
3714 <nvpair id="R-meta_attributes-a" name="a" value="b"/>
3715 </meta_attributes>
3716 <operations>
3717 <op id="R-monitor-interval-10s" interval="10s"
3718 name="monitor" timeout="20s"
3719 />
3720 </operations>
3721 </primitive>
3722 </resources>
3723 """
3724
3725 def fixture_resource(self):
3726 self.assert_effect(
3727 "resource create --no-default-ops R ocf:pcsmock:minimal".split(),
3728 self.fixture_xml_resource_no_meta(),
3729 )
3730
3731 def fixture_resource_meta(self):
3732 self.assert_effect(
3733 "resource create --no-default-ops R ocf:pcsmock:minimal meta a=b".split(),
3734 self.fixture_xml_resource_with_meta(),
3735 stderr_full=fixture_meta_attributes_warning(
3736 ["a"], ra_const.PRIMITIVE_META
3737 ),
3738 )
3739
3740 def test_meta_attrs(self):
3741 # see also BundleMiscCommands
3742 self.assert_pcs_success(
3743 (
3744 "resource create --no-default-ops D0 ocf:pcsmock:params"
3745 " mandatory=test1a optional=test2a op monitor interval=30 meta"
3746 " test5=test5a test6=test6a"
3747 ).split(),
3748 stderr_full=fixture_meta_attributes_warning(
3749 ["test5", "test6"], ra_const.PRIMITIVE_META
3750 ),
3751 )
3752 self.assert_pcs_success(
3753 (
3754 "resource create --no-default-ops D1 ocf:pcsmock:params"
3755 " mandatory=test1a optional=test2a op monitor interval=30"
3756 ).split(),
3757 )
3758 self.assert_pcs_success(
3759 (
3760 "resource update D0 mandatory=test1b optional=test2a op monitor "
3761 "interval=35 meta test7=test7a test6="
3762 ).split(),
3763 stderr_full=fixture_use_meta_command_instead_warning(),
3764 )
3765 self.assert_pcs_success(
3766 "resource meta D1 d1meta=superd1meta".split(),
3767 stderr_full=fixture_meta_attributes_warning(
3768 ["d1meta"], ra_const.PRIMITIVE_META
3769 ),
3770 )
3771 self.assert_pcs_success("resource group add TestRG D1".split())
3772 self.assert_pcs_success(
3773 "resource meta TestRG testrgmeta=mymeta testrgmeta2=mymeta2".split(),
3774 stderr_full=fixture_meta_attributes_warning(
3775 ["testrgmeta", "testrgmeta2"], ra_const.PRIMITIVE_META
3776 ),
3777 )
3778 self.assert_pcs_success(
3779 "resource config".split(),
3780 dedent(
3781 """\
3782 Resource: D0 (class=ocf provider=pcsmock type=params)
3783 Attributes: D0-instance_attributes
3784 mandatory=test1b
3785 optional=test2a
3786 Meta Attributes: D0-meta_attributes
3787 test5=test5a
3788 test7=test7a
3789 Operations:
3790 monitor: D0-monitor-interval-35
3791 interval=35
3792 Group: TestRG
3793 Meta Attributes: TestRG-meta_attributes
3794 testrgmeta=mymeta
3795 testrgmeta2=mymeta2
3796 Resource: D1 (class=ocf provider=pcsmock type=params)
3797 Attributes: D1-instance_attributes
3798 mandatory=test1a
3799 optional=test2a
3800 Meta Attributes: D1-meta_attributes
3801 d1meta=superd1meta
3802 Operations:
3803 monitor: D1-monitor-interval-30
3804 interval=30
3805 """
3806 ),
3807 )
3808
3809 def test_resource_update_keep_empty_meta(self):
3810 self.fixture_resource_meta()
3811 self.assert_effect(
3812 "resource update R meta a=".split(),
3813 self.fixture_xml_resource_empty_meta(),
3814 )
3815
3816 def test_resource_update_dont_create_meta_on_removal(self):
3817 self.fixture_resource()
3818 self.assert_effect(
3819 "resource update R meta a=".split(),
3820 self.fixture_xml_resource_no_meta(),
3821 )
3822
3823 @staticmethod
3824 def fixture_not_ocf_clone():
3825 return """
3826 <clone id="clone-R">
3827 <primitive class="systemd" id="R" type="pcsmock">
3828 <instance_attributes id="R-instance_attributes" />
3829 <operations>
3830 <op id="R-monitor-interval-10s" interval="10s"
3831 name="monitor" timeout="20s"
3832 />
3833 </operations>
3834 </primitive>
3835 </clone>
3836 """
3837
3838
3839 class UpdateInstanceAttrs(
3840 TestCase,
3841 get_assert_pcs_effect_mixin(
3842 lambda cib: etree.tostring(etree.parse(cib).findall(".//resources")[0])
3843 ),
3844 ):
3845 # The idempotency with remote-node is tested in
3846 # pcs_test/tier1/legacy/test_cluster_pcmk_remote.py in
3847 # NodeAddGuest.test_success_when_guest_node_matches_with_existing_guest
3848
3849 # see also BundleMiscCommands
3850
3851 def setUp(self):
3852 self.empty_cib = empty_cib
3853 self.temp_cib = get_tmp_file("tier1_resource_update_instance_attrs")
3854 write_file_to_tmpfile(empty_cib, self.temp_cib)
3855 self.pcs_runner = PcsRunner(self.temp_cib.name)
3856 self.pcs_runner.mock_settings = get_mock_settings()
3857
3858 def tearDown(self):
3859 self.temp_cib.close()
3860
3861 def set_cib_file(self, *xml_string_list):
3862 xml_manip = XmlManipulation.from_file(self.empty_cib)
3863 xml_manip.append_to_first_tag_name("resources", *xml_string_list)
3864 write_data_to_tmpfile(str(xml_manip), self.temp_cib)
3865
3866 @staticmethod
3867 def fixture_xml_resource_no_attrs():
3868 return """
3869 <resources>
3870 <primitive class="ocf" id="R" provider="pcsmock" type="params">
3871 <operations>
3872 <op id="R-monitor-interval-10s" interval="10s"
3873 name="monitor" timeout="20s"
3874 />
3875 </operations>
3876 </primitive>
3877 </resources>
3878 """
3879
3880 @staticmethod
3881 def _fixture_xml_resource_empty_attrs():
3882 return """
3883 <primitive class="ocf" id="R" provider="pcsmock" type="params">
3884 <instance_attributes id="R-instance_attributes" />
3885 <operations>
3886 <op id="R-monitor-interval-10s" interval="10s"
3887 name="monitor" timeout="20s"
3888 />
3889 </operations>
3890 </primitive>
3891 """
3892
3893 @staticmethod
3894 def fixture_xml_resource_empty_attrs():
3895 return f"""
3896 <resources>
3897 {UpdateInstanceAttrs._fixture_xml_resource_empty_attrs()}
3898 </resources>
3899 """
3900
3901 @staticmethod
3902 def fixture_xml_resource_with_attrs():
3903 return """
3904 <resources>
3905 <primitive class="ocf" id="R" provider="pcsmock" type="params">
3906 <instance_attributes id="R-instance_attributes">
3907 <nvpair id="R-instance_attributes-mandatory"
3908 name="mandatory" value="F"
3909 />
3910 </instance_attributes>
3911 <operations>
3912 <op id="R-monitor-interval-10s" interval="10s"
3913 name="monitor" timeout="20s"
3914 />
3915 </operations>
3916 </primitive>
3917 </resources>
3918 """
3919
3920 def fixture_resource(self):
3921 self.assert_effect(
3922 "resource create --no-default-ops R ocf:pcsmock:params --force".split(),
3923 self.fixture_xml_resource_no_attrs(),
3924 stderr_full="Warning: required resource option 'mandatory' is missing\n",
3925 )
3926
3927 def fixture_resource_attrs(self):
3928 self.assert_effect(
3929 "resource create --no-default-ops R ocf:pcsmock:params mandatory=F".split(),
3930 self.fixture_xml_resource_with_attrs(),
3931 )
3932
3933 def test_usage(self):
3934 self.assert_pcs_fail(
3935 "resource update".split(),
3936 stderr_start="\nUsage: pcs resource update...\n",
3937 )
3938
3939 def test_bad_instance_variables(self):
3940 self.assert_pcs_fail(
3941 (
3942 "resource create --no-default-ops D0 ocf:pcsmock:params"
3943 " test=testC test2=test2a test4=test4A op monitor interval=35"
3944 " meta test7=test7a test6="
3945 ).split(),
3946 (
3947 "Error: invalid resource options: 'test', 'test2', 'test4', "
3948 "allowed options are: 'advanced', 'enum', 'mandatory', "
3949 "'optional', 'unique1', 'unique2', use --force to override\n"
3950 "Error: required resource option 'mandatory' is missing, "
3951 "use --force to override\n"
3952 + fixture_meta_attributes_warning(
3953 ["test7"], ra_const.PRIMITIVE_META
3954 )
3955 + ERRORS_HAVE_OCCURRED
3956 ),
3957 )
3958
3959 self.assert_pcs_success(
3960 (
3961 "resource create --no-default-ops --force D0 ocf:pcsmock:params"
3962 " test=testC test2=test2a test4=test4A op monitor interval=35"
3963 " meta test7=test7a test6="
3964 ).split(),
3965 stderr_full=(
3966 "Warning: invalid resource options: 'test', 'test2', 'test4',"
3967 " allowed options are: 'advanced', 'enum', 'mandatory', "
3968 "'optional', 'unique1', 'unique2'\n"
3969 "Warning: required resource option 'mandatory' is missing\n"
3970 + fixture_meta_attributes_warning(
3971 ["test7"], ra_const.PRIMITIVE_META
3972 )
3973 ),
3974 )
3975
3976 self.assert_pcs_fail(
3977 "resource update D0 test=testA test2=testB test3=testD".split(),
3978 (
3979 "Error: invalid resource option 'test3', allowed options"
3980 " are: 'advanced', 'enum', 'mandatory', 'optional', 'unique1', "
3981 "'unique2', use --force to override\n"
3982 ),
3983 )
3984
3985 self.assert_pcs_success(
3986 "resource update D0 test=testB test2=testC test3=testD --force".split(),
3987 stderr_full=(
3988 "Warning: invalid resource option 'test3',"
3989 " allowed options are: 'advanced', 'enum', 'mandatory', "
3990 "'optional', 'unique1', 'unique2'\n"
3991 ),
3992 )
3993
3994 self.assert_pcs_success(
3995 "resource config D0".split(),
3996 dedent(
3997 """\
3998 Resource: D0 (class=ocf provider=pcsmock type=params)
3999 Attributes: D0-instance_attributes
4000 test=testB
4001 test2=testC
4002 test3=testD
4003 test4=test4A
4004 Meta Attributes: D0-meta_attributes
4005 test6=
4006 test7=test7a
4007 Operations:
4008 monitor: D0-monitor-interval-35
4009 interval=35
4010 """
4011 ),
4012 )
4013
4014 def test_nonexisting_agent(self):
4015 agent = "ocf:pcsmock:nonexistent"
4016 message = (
4017 f"Agent '{agent}' is not installed or does "
4018 "not provide valid metadata: "
4019 "pcs mock error message: unable to load agent metadata"
4020 )
4021 self.assert_pcs_success(
4022 f"resource create --force D0 {agent}".split(),
4023 stderr_full=f"Warning: {message}\n",
4024 )
4025
4026 self.assert_pcs_fail(
4027 "resource update D0 test=testA".split(),
4028 f"Error: {message}, use --force to override\n",
4029 )
4030 self.assert_pcs_success(
4031 "resource update --force D0 test=testA".split(),
4032 stderr_full=f"Warning: {message}\n",
4033 )
4034
4035 def test_update_existing(self):
4036 xml = """
4037 <resources>
4038 <primitive class="ocf" id="Dummy" provider="pcsmock"
4039 type="params"
4040 >
4041 <instance_attributes id="Dummy-instance_attributes">
4042 <nvpair id="Dummy-instance_attributes-mandatory"
4043 name="mandatory" value="manda"
4044 />
4045 <nvpair id="Dummy-instance_attributes-optional"
4046 name="optional" value="{optional}"
4047 />
4048 </instance_attributes>
4049 <operations>
4050 <op id="Dummy-monitor-interval-30s" interval="30s"
4051 name="monitor"
4052 />
4053 </operations>
4054 </primitive>
4055 </resources>
4056 """
4057 self.assert_effect(
4058 (
4059 "resource create --no-default-ops Dummy ocf:pcsmock:params"
4060 " mandatory=manda optional=opti1 op monitor interval=30s"
4061 ).split(),
4062 xml.format(optional="opti1"),
4063 )
4064
4065 self.assert_effect(
4066 "resource update Dummy optional=opti2".split(),
4067 xml.format(optional="opti2"),
4068 )
4069
4070 def test_keep_empty_nvset(self):
4071 self.fixture_resource_attrs()
4072 self.assert_effect(
4073 "resource update R mandatory= --force".split(),
4074 self.fixture_xml_resource_empty_attrs(),
4075 stderr_full="Warning: required resource option 'mandatory' is missing\n",
4076 )
4077
4078 def test_dont_create_nvset_on_removal(self):
4079 self.fixture_resource()
4080 self.assert_effect(
4081 "resource update R mandatory= --force".split(),
4082 self.fixture_xml_resource_no_attrs(),
4083 stderr_full="Warning: required resource option 'mandatory' is missing\n",
4084 )
4085
4086 def test_agent_self_validation_failure(self):
4087 self.fixture_resource()
4088 self.assert_pcs_fail(
4089 [
4090 "resource",
4091 "update",
4092 "R",
4093 "mandatory=is_invalid=True",
4094 "--agent-validation",
4095 ],
4096 stderr_full=(
4097 "Error: Validation result from agent (use --force to override):\n"
4098 " pcsmock validation failure\n"
4099 ),
4100 )
4101
4102 @staticmethod
4103 def fixture_not_ocf_clone():
4104 return """
4105 <clone id="clone-R">
4106 <primitive class="systemd" id="R" type="pcsmock">
4107 <instance_attributes id="R-instance_attributes" />
4108 <operations>
4109 <op id="R-monitor-interval-10s" interval="10s"
4110 name="monitor" timeout="20s"
4111 />
4112 </operations>
4113 </primitive>
4114 </clone>
4115 """
4116
4117 def test_clone_promotable_not_ocf(self):
4118 self.set_cib_file(self.fixture_not_ocf_clone())
4119 self.assert_pcs_fail_regardless_of_force(
4120 "resource update clone-R promotable=1".split(),
4121 (
4122 "Error: Clone option 'promotable' is not compatible with "
4123 "'systemd:pcsmock' resource agent of resource 'R'\n"
4124 ),
4125 )
4126
4127 def test_clone_globally_unique_not_ocf(self):
4128 self.set_cib_file(self.fixture_not_ocf_clone())
4129 self.assert_pcs_fail_regardless_of_force(
4130 "resource update clone-R globally-unique=1".split(),
4131 (
4132 "Error: Clone option 'globally-unique' is not compatible with "
4133 "'systemd:pcsmock' resource agent of resource 'R'\n"
4134 ),
4135 )
4136
4137 def test_clone_promotable_unsupported(self):
4138 self.set_cib_file(
4139 f"""
4140 <clone id="clone-R">
4141 {self._fixture_xml_resource_empty_attrs()}
4142 </clone>
4143 """
4144 )
4145 self.assert_pcs_fail(
4146 "resource update clone-R promotable=1".split(),
4147 (
4148 "Error: Clone option 'promotable' is not compatible with "
4149 "'ocf:pcsmock:params' resource agent of resource 'R', "
4150 "use --force to override\n"
4151 ),
4152 )
4153
4154
4155 class CloneMasterUpdate(TestCase, AssertPcsMixin):
4156 def setUp(self):
4157 self.temp_cib = get_tmp_file("tier1_resource_clone_master_update")
4158 write_file_to_tmpfile(empty_cib, self.temp_cib)
4159 self.pcs_runner = PcsRunner(self.temp_cib.name)
4160 self.pcs_runner.mock_settings = get_mock_settings()
4161
4162 def tearDown(self):
4163 self.temp_cib.close()
4164
4165 def test_no_op_allowed_in_clone_update(self):
4166 self.assert_pcs_success(
4167 "resource create dummy ocf:pcsmock:minimal clone".split()
4168 )
4169 self.assert_pcs_success(
4170 "resource config dummy-clone".split(),
4171 dedent(
4172 """\
4173 Clone: dummy-clone
4174 Resource: dummy (class=ocf provider=pcsmock type=minimal)
4175 Operations:
4176 migrate_from: dummy-migrate_from-interval-0s
4177 interval=0s timeout=20s
4178 migrate_to: dummy-migrate_to-interval-0s
4179 interval=0s timeout=20s
4180 monitor: dummy-monitor-interval-10s
4181 interval=10s timeout=20s
4182 reload: dummy-reload-interval-0s
4183 interval=0s timeout=20s
4184 reload-agent: dummy-reload-agent-interval-0s
4185 interval=0s timeout=20s
4186 start: dummy-start-interval-0s
4187 interval=0s timeout=20s
4188 stop: dummy-stop-interval-0s
4189 interval=0s timeout=20s
4190 """
4191 ),
4192 )
4193 self.assert_pcs_fail(
4194 "resource update dummy-clone op stop timeout=300".split(),
4195 "Error: op settings must be changed on base resource, not the clone\n",
4196 )
4197 self.assert_pcs_fail(
4198 "resource update dummy-clone foo=bar op stop timeout=300".split(),
4199 "Error: op settings must be changed on base resource, not the clone\n",
4200 )
4201 self.assert_pcs_success(
4202 "resource config dummy-clone".split(),
4203 dedent(
4204 """\
4205 Clone: dummy-clone
4206 Resource: dummy (class=ocf provider=pcsmock type=minimal)
4207 Operations:
4208 migrate_from: dummy-migrate_from-interval-0s
4209 interval=0s timeout=20s
4210 migrate_to: dummy-migrate_to-interval-0s
4211 interval=0s timeout=20s
4212 monitor: dummy-monitor-interval-10s
4213 interval=10s timeout=20s
4214 reload: dummy-reload-interval-0s
4215 interval=0s timeout=20s
4216 reload-agent: dummy-reload-agent-interval-0s
4217 interval=0s timeout=20s
4218 start: dummy-start-interval-0s
4219 interval=0s timeout=20s
4220 stop: dummy-stop-interval-0s
4221 interval=0s timeout=20s
4222 """
4223 ),
4224 )
4225
4226 def test_no_op_allowed_in_master_update(self):
4227 # pcs no longer allows creating masters but supports existing ones. In
4228 # order to test it, we need to put a master in the CIB without pcs.
4229 fixture_to_cib(self.temp_cib.name, fixture_master_xml("dummy"))
4230 show = dedent(
4231 f"""\
4232 Clone: dummy-master
4233 Meta Attributes:
4234 promotable=true
4235 Resource: dummy (class=ocf provider=pcsmock type=stateful)
4236 Operations:
4237 monitor: dummy-monitor-interval-10
4238 interval=10 timeout=20 role={const.PCMK_ROLE_PROMOTED}
4239 monitor: dummy-monitor-interval-11
4240 interval=11 timeout=20 role={const.PCMK_ROLE_UNPROMOTED}
4241 notify: dummy-notify-interval-0s
4242 interval=0s timeout=5
4243 start: dummy-start-interval-0s
4244 interval=0s timeout=20
4245 stop: dummy-stop-interval-0s
4246 interval=0s timeout=20
4247 """
4248 )
4249 self.assert_pcs_success("resource config dummy-master".split(), show)
4250 self.assert_pcs_fail(
4251 "resource update dummy-master op stop timeout=300".split(),
4252 "Error: op settings must be changed on base resource, not the clone\n",
4253 )
4254 self.assert_pcs_fail(
4255 "resource update dummy-master foo=bar op stop timeout=300".split(),
4256 "Error: op settings must be changed on base resource, not the clone\n",
4257 )
4258 self.assert_pcs_success("resource config dummy-master".split(), show)
4259
4260
4261 class TransformMasterToClone(ResourceTest):
4262 def setUp(self):
4263 super().setUp()
4264 self.pcs_runner.mock_settings = get_mock_settings()
4265
4266 def test_transform_master_without_meta_on_update(self):
4267 # pcs no longer allows creating masters but supports existing ones. In
4268 # order to test it, we need to put a master in the CIB without pcs.
4269 fixture_to_cib(self.temp_cib.name, fixture_master_xml("dummy"))
4270 self.assert_effect(
4271 "resource update dummy-master meta a=b".split(),
4272 """<resources>
4273 <clone id="dummy-master">
4274 <primitive class="ocf" id="dummy" provider="pcsmock"
4275 type="stateful"
4276 >
4277 <operations>
4278 <op id="dummy-monitor-interval-10" interval="10"
4279 name="monitor" role="Master" timeout="20"
4280 />
4281 <op id="dummy-monitor-interval-11" interval="11"
4282 name="monitor" role="Slave" timeout="20"
4283 />
4284 <op id="dummy-notify-interval-0s" interval="0s"
4285 name="notify" timeout="5"
4286 />
4287 <op id="dummy-start-interval-0s" interval="0s"
4288 name="start" timeout="20"
4289 />
4290 <op id="dummy-stop-interval-0s" interval="0s"
4291 name="stop" timeout="20"
4292 />
4293 </operations>
4294 </primitive>
4295 <meta_attributes id="dummy-master-meta_attributes">
4296 <nvpair id="dummy-master-meta_attributes-promotable"
4297 name="promotable" value="true"
4298 />
4299 <nvpair id="dummy-master-meta_attributes-a" name="a"
4300 value="b"
4301 />
4302 </meta_attributes>
4303 </clone>
4304 </resources>""",
4305 stderr_full=fixture_meta_attributes_not_validated_warning(
4306 ["clone"]
4307 ),
4308 )
4309
4310 def test_transform_master_with_meta_on_update(self):
4311 # pcs no longer allows creating masters but supports existing ones. In
4312 # order to test it, we need to put a master in the CIB without pcs.
4313 fixture_to_cib(
4314 self.temp_cib.name,
4315 fixture_master_xml("dummy", meta_dict=dict(a="A", b="B", c="C")),
4316 )
4317 self.assert_effect(
4318 "resource update dummy-master meta a=AA b= d=D promotable=".split(),
4319 """<resources>
4320 <clone id="dummy-master">
4321 <primitive class="ocf" id="dummy" provider="pcsmock"
4322 type="stateful"
4323 >
4324 <operations>
4325 <op id="dummy-monitor-interval-10" interval="10"
4326 name="monitor" role="Master" timeout="20"
4327 />
4328 <op id="dummy-monitor-interval-11" interval="11"
4329 name="monitor" role="Slave" timeout="20"
4330 />
4331 <op id="dummy-notify-interval-0s" interval="0s"
4332 name="notify" timeout="5"
4333 />
4334 <op id="dummy-start-interval-0s" interval="0s"
4335 name="start" timeout="20"
4336 />
4337 <op id="dummy-stop-interval-0s" interval="0s"
4338 name="stop" timeout="20"
4339 />
4340 </operations>
4341 </primitive>
4342 <meta_attributes id="dummy-master-meta_attributes">
4343 <nvpair id="dummy-master-meta_attributes-a" name="a"
4344 value="AA"
4345 />
4346 <nvpair id="dummy-master-meta_attributes-c" name="c"
4347 value="C"
4348 />
4349 <nvpair id="dummy-master-meta_attributes-d" name="d"
4350 value="D"
4351 />
4352 </meta_attributes>
4353 </clone>
4354 </resources>""",
4355 stderr_full=fixture_meta_attributes_not_validated_warning(
4356 ["clone"]
4357 ),
4358 )
4359
4360
4361 class BundleCommon(
4362 TestCase,
4363 get_assert_pcs_effect_mixin(
4364 lambda cib: etree.tostring(etree.parse(cib).findall(".//resources")[0])
4365 ),
4366 ):
4367 def setUp(self):
4368 self.temp_cib = get_tmp_file("tier1_resource_bundle")
4369 write_file_to_tmpfile(empty_cib, self.temp_cib)
4370 self.pcs_runner = PcsRunner(self.temp_cib.name)
4371 self.pcs_runner.mock_settings = get_mock_settings()
4372
4373 def tearDown(self):
4374 self.temp_cib.close()
4375
4376 def fixture_primitive(self, name, bundle):
4377 self.assert_pcs_success(
4378 [
4379 "resource",
4380 "create",
4381 name,
4382 "ocf:pcsmock:minimal",
4383 "bundle",
4384 bundle,
4385 ]
4386 )
4387
4388 def fixture_bundle(self, name, container="docker"):
4389 self.assert_pcs_success(
4390 [
4391 "resource",
4392 "bundle",
4393 "create",
4394 name,
4395 "container",
4396 container,
4397 "image=pcs:test",
4398 "network",
4399 "control-port=1234",
4400 ],
4401 )
4402
4403
4404 class BundleShow(BundleCommon):
4405 empty_cib = rc("cib-empty.xml")
4406
4407 def test_docker(self):
4408 self.fixture_bundle("B1", "docker")
4409 self.assert_pcs_success(
4410 "resource config B1".split(),
4411 dedent(
4412 """\
4413 Bundle: B1
4414 Docker: image=pcs:test
4415 Network: control-port=1234
4416 """
4417 ),
4418 )
4419
4420 def test_podman(self):
4421 write_file_to_tmpfile(rc("cib-empty-3.2.xml"), self.temp_cib)
4422 self.fixture_bundle("B1", "podman")
4423 self.assert_pcs_success(
4424 "resource config B1".split(),
4425 dedent(
4426 """\
4427 Bundle: B1
4428 Podman: image=pcs:test
4429 Network: control-port=1234
4430 """
4431 ),
4432 )
4433
4434 def test_unknown_container(self):
4435 write_file_to_tmpfile(rc("cib-empty-3.7.xml"), self.temp_cib)
4436 self.fixture_bundle("B1")
4437
4438 # pcs no longer allows creating rkt bundles
4439 cib_tree = etree.parse(
|
CID (unavailable; MK=4240aff4e9e57313345f81041f87585b) (#1 of 1): XML entity processing enabled (SIGMA.xml_entity_enabled): |
|
(1) Event Sigma main event: |
The Python application enables entity expansion by setting the `lxml.etree.XMLParser` value `resolve_entities` to `true` or `internal` (the default value). If untrusted XML is parsed with entity expansion enabled, a malicious attacker could submit a document that contains very deeply nested entity definitions (known as a Billion Laughs Attack), causing the parser to use large amounts of memory and processing power resulting in a denial of service (DoS) condition. |
|
(2) Event remediation: |
Explicitly set `resolve_entities` argument to `False`. |
4440 self.temp_cib.name, etree.XMLParser(huge_tree=True)
4441 ).getroot()
4442 element = cib_tree.find('.//*[@id="B1"]/docker')
4443 element.tag = "rkt"
4444 write_data_to_tmpfile(etree_to_str(cib_tree), self.temp_cib)
4445
4446 self.assert_pcs_success(
4447 "resource config B1".split(),
4448 dedent(
4449 """\
4450 Bundle: B1
4451 Network: control-port=1234
4452 """
4453 ),
4454 stderr_full=(
4455 "Warning: Bundle 'B1' uses unsupported container type. "
4456 "Supported container types are: 'docker', 'podman'\n"
4457 ),
4458 )
4459
4460
4461 class BundleGroup(BundleCommon):
4462 def test_group_delete_primitive(self):
4463 self.fixture_bundle("B")
4464 self.fixture_primitive("R", "B")
4465 self.assert_pcs_fail(
4466 "resource group delete B R".split(),
4467 "Error: Group 'B' does not exist\n",
4468 )
4469
4470 def test_group_remove_primitive(self):
4471 self.fixture_bundle("B")
4472 self.fixture_primitive("R", "B")
4473 self.assert_pcs_fail(
4474 "resource group remove B R".split(),
4475 "Error: Group 'B' does not exist\n",
4476 )
4477
4478 def test_ungroup_bundle(self):
4479 self.fixture_bundle("B")
4480 self.assert_pcs_fail(
4481 "resource ungroup B".split(), "Error: Group 'B' does not exist\n"
4482 )
4483
4484
4485 class BundleClone(BundleCommon):
4486 def test_clone_bundle(self):
4487 self.fixture_bundle("B")
4488 self.assert_pcs_fail(
4489 "resource clone B".split(),
4490 "Error: unable to find group or resource: B\n",
4491 )
4492
4493 def test_clone_primitive(self):
4494 self.fixture_bundle("B")
4495 self.fixture_primitive("R", "B")
4496 self.assert_pcs_fail(
4497 "resource clone R".split(), "Error: cannot clone bundle resource\n"
4498 )
4499
4500 def test_unclone_bundle(self):
4501 self.fixture_bundle("B")
4502 self.assert_pcs_fail(
4503 "resource unclone B".split(), "Error: could not find resource: B\n"
4504 )
4505
4506
4507 class BundleMiscCommands(BundleCommon):
4508 def test_resource_enable_bundle(self):
4509 self.fixture_bundle("B")
4510 self.assert_pcs_success("resource enable B".split())
4511
4512 def test_resource_disable_bundle(self):
4513 self.fixture_bundle("B")
4514 self.assert_pcs_success("resource disable B".split())
4515
4516 def test_resource_manage_bundle(self):
4517 self.fixture_bundle("B")
4518 self.assert_pcs_success("resource manage B".split())
4519
4520 def test_resource_unmanage_bundle(self):
4521 self.fixture_bundle("B")
4522 self.assert_pcs_success("resource unmanage B".split())
4523
4524 def test_op_add(self):
4525 self.fixture_bundle("B")
4526 self.assert_pcs_fail_regardless_of_force(
4527 "resource op add B monitor interval=30".split(),
4528 "Error: Unable to find resource: B\n",
4529 )
4530
4531 def test_op_remove(self):
4532 self.fixture_bundle("B")
4533 self.assert_pcs_fail(
4534 "resource op remove B monitor interval=30".split(),
4535 "Error: Unable to find resource: B\n",
4536 )
4537
4538 def test_update(self):
4539 self.fixture_bundle("B")
4540 self.assert_pcs_fail_regardless_of_force(
4541 "resource update B meta aaa=bbb".split(),
4542 "Error: Unable to find resource: B\n",
4543 )
4544
4545 def test_utilization(self):
4546 self.fixture_bundle("B")
4547 self.assert_pcs_fail(
4548 "resource utilization B aaa=10".split(),
4549 (
4550 f"{FIXTURE_UTILIZATION_WARNING}"
4551 "Error: Unable to find a resource: B\n"
4552 ),
4553 )
4554
4555 @skip(
4556 "test of 'pcs resource debug-*' to be moved to pcs.lib with the "
4557 "command itself"
4558 )
4559 def test_debug_start_bundle(self):
4560 self.fixture_bundle("B")
4561 self.assert_pcs_fail_regardless_of_force(
4562 "resource debug-start B".split(),
4563 "Error: unable to debug-start a bundle\n",
4564 )
4565
4566 @skip(
4567 "test of 'pcs resource debug-*' to be moved to pcs.lib with the "
4568 "command itself"
4569 )
4570 def test_debug_start_with_resource(self):
4571 self.fixture_bundle("B")
4572 self.fixture_primitive("R", "B")
4573 self.assert_pcs_fail_regardless_of_force(
4574 "resource debug-start B".split(),
4575 "Error: unable to debug-start a bundle, try the bundle's resource: R\n",
4576 )
4577
4578 @skip(
4579 "test of 'pcs resource debug-*' to be moved to pcs.lib with the "
4580 "command itself"
4581 )
4582 def test_debug_stop_bundle(self):
4583 self.fixture_bundle("B")
4584 self.assert_pcs_fail_regardless_of_force(
4585 "resource debug-stop B".split(),
4586 "Error: unable to debug-stop a bundle\n",
4587 )
4588
4589 @skip(
4590 "test of 'pcs resource debug-*' to be moved to pcs.lib with the "
4591 "command itself"
4592 )
4593 def test_debug_stop_with_resource(self):
4594 self.fixture_bundle("B")
4595 self.fixture_primitive("R", "B")
4596 self.assert_pcs_fail_regardless_of_force(
4597 "resource debug-stop B".split(),
4598 "Error: unable to debug-stop a bundle, try the bundle's resource: R\n",
4599 )
4600
4601 @skip(
4602 "test of 'pcs resource debug-*' to be moved to pcs.lib with the "
4603 "command itself"
4604 )
4605 def test_debug_monitor_bundle(self):
4606 self.fixture_bundle("B")
4607 self.assert_pcs_fail_regardless_of_force(
4608 "resource debug-monitor B".split(),
4609 "Error: unable to debug-monitor a bundle\n",
4610 )
4611
4612 @skip(
4613 "test of 'pcs resource debug-*' to be moved to pcs.lib with the "
4614 "command itself"
4615 )
4616 def test_debug_monitor_with_resource(self):
4617 self.fixture_bundle("B")
4618 self.fixture_primitive("R", "B")
4619 self.assert_pcs_fail_regardless_of_force(
4620 "resource debug-monitor B".split(),
4621 "Error: unable to debug-monitor a bundle, try the bundle's resource: R\n",
4622 )
4623
4624 @skip(
4625 "test of 'pcs resource debug-*' to be moved to pcs.lib with the "
4626 "command itself"
4627 )
4628 def test_debug_promote_bundle(self):
4629 self.fixture_bundle("B")
4630 self.assert_pcs_fail_regardless_of_force(
4631 "resource debug-promote B".split(),
4632 "Error: unable to debug-promote a bundle\n",
4633 )
4634
4635 @skip(
4636 "test of 'pcs resource debug-*' to be moved to pcs.lib with the "
4637 "command itself"
4638 )
4639 def test_debug_promote_with_resource(self):
4640 self.fixture_bundle("B")
4641 self.fixture_primitive("R", "B")
4642 self.assert_pcs_fail_regardless_of_force(
4643 "resource debug-promote B".split(),
4644 "Error: unable to debug-promote a bundle, try the bundle's resource: R\n",
4645 )
4646
4647 @skip(
4648 "test of 'pcs resource debug-*' to be moved to pcs.lib with the "
4649 "command itself"
4650 )
4651 def test_debug_demote_bundle(self):
4652 self.fixture_bundle("B")
4653 self.assert_pcs_fail_regardless_of_force(
4654 "resource debug-demote B".split(),
4655 "Error: unable to debug-demote a bundle\n",
4656 )
4657
4658 @skip(
4659 "test of 'pcs resource debug-*' to be moved to pcs.lib with the "
4660 "command itself"
4661 )
4662 def test_debug_demote_with_resource(self):
4663 self.fixture_bundle("B")
4664 self.fixture_primitive("R", "B")
4665 self.assert_pcs_fail_regardless_of_force(
4666 "resource debug-demote B".split(),
4667 "Error: unable to debug-demote a bundle, try the bundle's resource: R\n",
4668 )
4669
4670
4671 class ResourceUpdateRemoteAndGuestChecks(TestCase, AssertPcsMixin):
4672 def setUp(self):
4673 self.temp_cib = get_tmp_file("tier1_resource_update_remote_guest")
4674 write_file_to_tmpfile(empty_cib, self.temp_cib)
4675 self.pcs_runner = PcsRunner(self.temp_cib.name)
4676 self.pcs_runner.mock_settings = get_mock_settings()
4677
4678 def tearDown(self):
4679 self.temp_cib.close()
4680
4681 def test_update_fail_on_pacemaker_guest_attempt(self):
4682 self.assert_pcs_success("resource create R ocf:pcsmock:minimal".split())
4683 self.assert_pcs_fail(
4684 "resource update R meta remote-node=HOST".split(),
4685 (
4686 "Error: this command is not sufficient for creating a guest "
4687 "node, use 'pcs cluster node add-guest', use --force to "
4688 "override\n" + ERRORS_HAVE_OCCURRED
4689 ),
4690 )
4691
4692 def test_update_warn_on_pacemaker_guest_attempt(self):
4693 self.assert_pcs_success("resource create R ocf:pcsmock:minimal".split())
4694 self.assert_pcs_success(
4695 "resource update R meta remote-node=HOST --force".split(),
4696 stderr_full=(
4697 "Warning: this command is not sufficient for creating a guest node,"
4698 " use 'pcs cluster node add-guest'\n"
4699 + fixture_use_meta_command_instead_warning()
4700 ),
4701 )
4702
4703 def test_update_fail_on_pacemaker_guest_attempt_remove(self):
4704 self.assert_pcs_success(
4705 (
4706 "resource create R ocf:pcsmock:minimal meta remote-node=HOST"
4707 " --force"
4708 ).split(),
4709 stderr_full=(
4710 "Warning: this command is not sufficient for creating a guest node,"
4711 " use 'pcs cluster node add-guest'\n"
4712 ),
4713 )
4714 self.assert_pcs_fail(
4715 "resource update R meta remote-node=".split(),
4716 (
4717 "Error: this command is not sufficient for removing a guest "
4718 "node, use 'pcs cluster node remove-guest', use --force "
4719 "to override\n" + ERRORS_HAVE_OCCURRED
4720 ),
4721 )
4722
4723 def test_update_warn_on_pacemaker_guest_attempt_remove(self):
4724 self.assert_pcs_success(
4725 (
4726 "resource create R ocf:pcsmock:minimal meta remote-node=HOST"
4727 " --force"
4728 ).split(),
4729 stderr_full=(
4730 "Warning: this command is not sufficient for creating a guest node,"
4731 " use 'pcs cluster node add-guest'\n"
4732 ),
4733 )
4734 self.assert_pcs_success(
4735 "resource update R meta remote-node= --force".split(),
4736 stderr_full=(
4737 "Warning: this command is not sufficient for removing a guest node,"
4738 " use 'pcs cluster node remove-guest'\n"
4739 ),
4740 )
4741
4742
4743 class ResourceUpdateUniqueAttrChecks(TestCase, AssertPcsMixin):
4744 def setUp(self):
4745 self.temp_cib = get_tmp_file("tier1_resource_update_unique_attr")
4746 write_file_to_tmpfile(empty_cib, self.temp_cib)
4747 self.pcs_runner = PcsRunner(self.temp_cib.name)
4748 self.pcs_runner.mock_settings = get_mock_settings()
4749
4750 def tearDown(self):
4751 self.temp_cib.close()
4752
4753 def test_unique_err(self):
4754 self.assert_pcs_success(
4755 "resource create R1 ocf:pcsmock:unique state=1".split()
4756 )
4757 self.assert_pcs_success("resource create R2 ocf:pcsmock:unique".split())
4758 self.assert_pcs_fail(
4759 "resource update R2 state=1".split(),
4760 (
4761 "Error: Value '1' of option 'state' is not unique across "
4762 "'ocf:pcsmock:unique' resources. Following resources are "
4763 "configured with the same value of the instance attribute: 'R1', "
4764 "use --force to override\n"
4765 ),
4766 )
4767
4768 def test_unique_setting_same_value(self):
4769 self.assert_pcs_success(
4770 "resource create R1 ocf:pcsmock:unique state=1 --no-default-ops".split()
4771 )
4772 self.assert_pcs_success(
4773 "resource create R2 ocf:pcsmock:unique --no-default-ops".split()
4774 )
4775 self.assert_pcs_success(
4776 "resource update R2 state=1 --force".split(),
4777 stderr_full=(
4778 "Warning: Value '1' of option 'state' is not unique across "
4779 "'ocf:pcsmock:unique' resources. Following resources are "
4780 "configured with the same value of the instance attribute: 'R1'\n"
4781 ),
4782 )
4783 res_config = dedent(
4784 """\
4785 Resource: R1 (class=ocf provider=pcsmock type=unique)
4786 Attributes: R1-instance_attributes
4787 state=1
4788 Operations:
4789 monitor: R1-monitor-interval-10s
4790 interval=10s timeout=20s
4791 Resource: R2 (class=ocf provider=pcsmock type=unique)
4792 Attributes: R2-instance_attributes
4793 state=1
4794 Operations:
4795 monitor: R2-monitor-interval-10s
4796 interval=10s timeout=20s
4797 """
4798 )
4799 self.assert_pcs_success("resource config".split(), res_config)
4800 # make sure that it doesn't check against resource itself
4801 self.assert_pcs_success(
4802 "resource update R2 state=1 --force".split(),
4803 stderr_full=(
4804 "Warning: Value '1' of option 'state' is not unique across "
4805 "'ocf:pcsmock:unique' resources. Following resources are "
4806 "configured with the same value of the instance attribute: 'R1'\n"
4807 ),
4808 )
4809 self.assert_pcs_success("resource config".split(), res_config)
4810 res_config = dedent(
4811 """\
4812 Resource: R1 (class=ocf provider=pcsmock type=unique)
4813 Attributes: R1-instance_attributes
4814 state=1
4815 Operations:
4816 monitor: R1-monitor-interval-10s
4817 interval=10s timeout=20s
4818 Resource: R2 (class=ocf provider=pcsmock type=unique)
4819 Attributes: R2-instance_attributes
4820 state=2
4821 Operations:
4822 monitor: R2-monitor-interval-10s
4823 interval=10s timeout=20s
4824 """
4825 )
4826 self.assert_pcs_success("resource update R2 state=2".split())
4827 self.assert_pcs_success("resource config".split(), res_config)
4828 self.assert_pcs_success("resource update R2 state=2".split())
4829 self.assert_pcs_success("resource config".split(), res_config)
4830
4831 def test_unique_warn(self):
4832 self.assert_pcs_success(
4833 "resource create R1 ocf:pcsmock:unique state=1 --no-default-ops".split()
4834 )
4835 self.assert_pcs_success(
4836 "resource create R2 ocf:pcsmock:unique --no-default-ops".split()
4837 )
4838 self.assert_pcs_success(
4839 "resource create R3 ocf:pcsmock:unique --no-default-ops".split()
4840 )
4841 self.assert_pcs_success(
4842 "resource update R2 state=1 --force".split(),
4843 stderr_full=(
4844 "Warning: Value '1' of option 'state' is not unique across "
4845 "'ocf:pcsmock:unique' resources. Following resources are "
4846 "configured with the same value of the instance attribute: 'R1'\n"
4847 ),
4848 )
4849 self.assert_pcs_success(
4850 "resource config".split(),
4851 dedent(
4852 """\
4853 Resource: R1 (class=ocf provider=pcsmock type=unique)
4854 Attributes: R1-instance_attributes
4855 state=1
4856 Operations:
4857 monitor: R1-monitor-interval-10s
4858 interval=10s timeout=20s
4859 Resource: R2 (class=ocf provider=pcsmock type=unique)
4860 Attributes: R2-instance_attributes
4861 state=1
4862 Operations:
4863 monitor: R2-monitor-interval-10s
4864 interval=10s timeout=20s
4865 Resource: R3 (class=ocf provider=pcsmock type=unique)
4866 Operations:
4867 monitor: R3-monitor-interval-10s
4868 interval=10s timeout=20s
4869 """
4870 ),
4871 )
4872 self.assert_pcs_success(
4873 "resource update R3 state=1 --force".split(),
4874 stderr_full=(
4875 "Warning: Value '1' of option 'state' is not unique across "
4876 "'ocf:pcsmock:unique' resources. Following resources are "
4877 "configured with the same value of the instance attribute: 'R1', "
4878 "'R2'\n"
4879 ),
4880 )
4881 self.assert_pcs_success(
4882 "resource config".split(),
4883 dedent(
4884 """\
4885 Resource: R1 (class=ocf provider=pcsmock type=unique)
4886 Attributes: R1-instance_attributes
4887 state=1
4888 Operations:
4889 monitor: R1-monitor-interval-10s
4890 interval=10s timeout=20s
4891 Resource: R2 (class=ocf provider=pcsmock type=unique)
4892 Attributes: R2-instance_attributes
4893 state=1
4894 Operations:
4895 monitor: R2-monitor-interval-10s
4896 interval=10s timeout=20s
4897 Resource: R3 (class=ocf provider=pcsmock type=unique)
4898 Attributes: R3-instance_attributes
4899 state=1
4900 Operations:
4901 monitor: R3-monitor-interval-10s
4902 interval=10s timeout=20s
4903 """
4904 ),
4905 )
4906