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