1 # pylint: disable=too-many-lines #noqa: PLR0915
2 import json
3 import re
4 import sys
5 import textwrap
6 from functools import partial
7 from typing import TYPE_CHECKING, Any, Callable, Mapping, Optional
8 from xml.dom.minidom import parseString
9
10 import pcs.lib.pacemaker.live as lib_pacemaker
11 import pcs.lib.resource_agent as lib_ra
12 from pcs import constraint, utils
13 from pcs.cli.cluster_property.output import PropertyConfigurationFacade
14 from pcs.cli.common.errors import (
15 SEE_MAN_CHANGES,
16 CmdLineInputError,
17 raise_command_replaced,
18 )
19 from pcs.cli.common.parse_args import (
20 FUTURE_OPTION,
21 OUTPUT_FORMAT_VALUE_CMD,
22 OUTPUT_FORMAT_VALUE_JSON,
23 OUTPUT_FORMAT_VALUE_TEXT,
24 Argv,
25 InputModifiers,
26 KeyValueParser,
27 ensure_unique_args,
28 group_by_keywords,
29 wait_to_timeout,
30 )
31 from pcs.cli.common.tools import print_to_stderr, timeout_to_seconds_legacy
32 from pcs.cli.nvset import filter_out_expired_nvset, nvset_dto_list_to_lines
33 from pcs.cli.reports import process_library_reports
34 from pcs.cli.reports.output import deprecation_warning, warn
35 from pcs.cli.resource.common import check_is_not_stonith
36 from pcs.cli.resource.output import (
37 operation_defaults_to_cmd,
38 resource_agent_metadata_to_text,
39 resource_defaults_to_cmd,
40 )
41 from pcs.cli.resource.parse_args import (
42 parse_bundle_create_options,
43 parse_bundle_reset_options,
44 parse_bundle_update_options,
45 parse_clone,
46 parse_create_new,
47 parse_create_old,
48 )
49 from pcs.cli.resource_agent import find_single_agent
50 from pcs.common import const, pacemaker, reports
51 from pcs.common.interface import dto
52 from pcs.common.pacemaker.defaults import CibDefaultsDto
53 from pcs.common.pacemaker.resource.list import (
54 get_all_resources_ids,
55 get_stonith_resources_ids,
56 )
57 from pcs.common.pacemaker.resource.operations import (
58 OCF_CHECK_LEVEL_INSTANCE_ATTRIBUTE_NAME,
59 )
60 from pcs.common.str_tools import (
61 format_list,
62 format_list_custom_last_separator,
63 format_plural,
64 )
65 from pcs.lib.cib.resource import guest_node, primitive
66 from pcs.lib.cib.tools import get_resources
67 from pcs.lib.commands.resource import (
68 _get_nodes_to_validate_against,
69 _validate_guest_change,
70 )
71 from pcs.lib.errors import LibraryError
72 from pcs.lib.pacemaker.values import is_true, validate_id
73 from pcs.settings import (
74 pacemaker_wait_timeout_status as PACEMAKER_WAIT_TIMEOUT_STATUS,
75 )
76
77 if TYPE_CHECKING:
78 from pcs.common.resource_agent.dto import ResourceAgentNameDto
79
80 RESOURCE_RELOCATE_CONSTRAINT_PREFIX = "pcs-relocate-"
81
82
83 def _detect_guest_change(
84 meta_attributes: Mapping[str, str], allow_not_suitable_command: bool
85 ) -> None:
86 """
87 Commandline options:
88 * -f - CIB file
89 """
90 if not guest_node.is_node_name_in_options(meta_attributes):
91 return
92
93 env = utils.get_lib_env()
94 cib = env.get_cib()
95 (
96 existing_nodes_names,
97 existing_nodes_addrs,
98 report_list,
99 ) = _get_nodes_to_validate_against(env, cib)
100 if env.report_processor.report_list(
101 report_list
102 + _validate_guest_change(
103 cib,
104 existing_nodes_names,
105 existing_nodes_addrs,
106 meta_attributes,
107 allow_not_suitable_command,
108 detect_remove=True,
109 )
110 ).has_errors:
111 raise LibraryError()
112
113
114 def resource_utilization_cmd(
115 lib: Any, argv: Argv, modifiers: InputModifiers
116 ) -> None:
117 """
118 Options:
119 * -f - CIB file
120 """
121 modifiers.ensure_only_supported("-f")
122 utils.print_warning_if_utilization_attrs_has_no_effect(
123 PropertyConfigurationFacade.from_properties_dtos(
124 lib.cluster_property.get_properties(),
125 lib.cluster_property.get_properties_metadata(),
126 )
127 )
128 if not argv:
129 print_resources_utilization()
130 return
131 resource_id = argv.pop(0)
132 check_is_not_stonith(lib, [resource_id])
133 if argv:
134 set_resource_utilization(resource_id, argv)
135 else:
136 print_resource_utilization(resource_id)
137
138
139 def _defaults_set_create_cmd(
140 lib_command: Callable[..., Any], argv: Argv, modifiers: InputModifiers
141 ) -> None:
142 modifiers.ensure_only_supported("-f", "--force")
143
144 groups = group_by_keywords(
145 argv, {"meta", "rule"}, implicit_first_keyword="options"
146 )
147 groups.ensure_unique_keywords()
148 force_flags = set()
149 if modifiers.get("--force"):
150 force_flags.add(reports.codes.FORCE)
151
152 lib_command(
153 KeyValueParser(groups.get_args_flat("meta")).get_unique(),
154 KeyValueParser(groups.get_args_flat("options")).get_unique(),
155 nvset_rule=(
156 " ".join(groups.get_args_flat("rule"))
157 if groups.get_args_flat("rule")
158 else None
159 ),
160 force_flags=force_flags,
161 )
162
163
164 def resource_defaults_set_create_cmd(
165 lib: Any, argv: Argv, modifiers: InputModifiers
166 ) -> None:
167 """
168 Options:
169 * -f - CIB file
170 * --force - allow unknown options
171 """
172 return _defaults_set_create_cmd(
173 lib.cib_options.resource_defaults_create, argv, modifiers
174 )
175
176
177 def resource_op_defaults_set_create_cmd(
178 lib: Any, argv: Argv, modifiers: InputModifiers
179 ) -> None:
180 """
181 Options:
182 * -f - CIB file
183 * --force - allow unknown options
184 """
185 return _defaults_set_create_cmd(
186 lib.cib_options.operation_defaults_create, argv, modifiers
187 )
188
189
190 def _filter_defaults(
191 cib_defaults_dto: CibDefaultsDto, include_expired: bool
192 ) -> CibDefaultsDto:
193 return CibDefaultsDto(
194 instance_attributes=(
195 cib_defaults_dto.instance_attributes
196 if include_expired
197 else filter_out_expired_nvset(cib_defaults_dto.instance_attributes)
198 ),
199 meta_attributes=(
200 cib_defaults_dto.meta_attributes
201 if include_expired
202 else filter_out_expired_nvset(cib_defaults_dto.meta_attributes)
203 ),
204 )
205
206
207 def _defaults_config_cmd(
208 lib_command: Callable[[bool], CibDefaultsDto],
209 defaults_to_cmd: Callable[[CibDefaultsDto], list[list[str]]],
210 argv: Argv,
211 modifiers: InputModifiers,
212 ) -> None:
213 """
214 Options:
215 * -f - CIB file
216 * --all - display all nvsets including the ones with expired rules
217 * --full - verbose output
218 * --no-expire-check -- disable evaluating whether rules are expired
219 * --output-format - supported formats: text, cmd, json
220 """
221 if argv:
222 raise CmdLineInputError()
223 modifiers.ensure_only_supported(
224 "-f",
225 "--all",
226 "--full",
227 "--no-expire-check",
228 output_format_supported=True,
229 )
230 modifiers.ensure_not_mutually_exclusive("--all", "--no-expire-check")
231 output_format = modifiers.get_output_format()
232 if (
233 modifiers.is_specified("--full")
234 and output_format != OUTPUT_FORMAT_VALUE_TEXT
235 ):
236 raise CmdLineInputError(
237 f"option '--full' is not compatible with '{output_format}' output "
238 "format."
239 )
240 cib_defaults_dto = _filter_defaults(
241 lib_command(not modifiers.get("--no-expire-check")),
242 bool(modifiers.get("--all")),
243 )
244 if output_format == OUTPUT_FORMAT_VALUE_CMD:
245 output = ";\n".join(
246 " \\\n".join(cmd) for cmd in defaults_to_cmd(cib_defaults_dto)
247 )
248 elif output_format == OUTPUT_FORMAT_VALUE_JSON:
249 output = json.dumps(dto.to_dict(cib_defaults_dto))
250 else:
251 output = "\n".join(
252 nvset_dto_list_to_lines(
253 cib_defaults_dto.meta_attributes,
254 nvset_label="Meta Attrs",
255 with_ids=bool(modifiers.get("--full")),
256 )
257 )
258 if output:
259 print(output)
260
261
262 def resource_defaults_config_cmd(
263 lib: Any, argv: Argv, modifiers: InputModifiers
264 ) -> None:
265 """
266 Options:
267 * -f - CIB file
268 * --full - verbose output
269 """
270 return _defaults_config_cmd(
271 lib.cib_options.resource_defaults_config,
272 resource_defaults_to_cmd,
273 argv,
274 modifiers,
275 )
276
277
278 def resource_op_defaults_config_cmd(
279 lib: Any, argv: Argv, modifiers: InputModifiers
280 ) -> None:
281 """
282 Options:
283 * -f - CIB file
284 * --full - verbose output
285 """
286 return _defaults_config_cmd(
287 lib.cib_options.operation_defaults_config,
288 operation_defaults_to_cmd,
289 argv,
290 modifiers,
291 )
292
293
294 def _defaults_set_remove_cmd(
295 lib_command: Callable[..., Any], argv: Argv, modifiers: InputModifiers
296 ) -> None:
297 """
298 Options:
299 * -f - CIB file
300 """
301 modifiers.ensure_only_supported("-f")
302 lib_command(argv)
303
304
305 def resource_defaults_set_remove_cmd(
306 lib: Any, argv: Argv, modifiers: InputModifiers
307 ) -> None:
308 """
309 Options:
310 * -f - CIB file
311 """
312 return _defaults_set_remove_cmd(
313 lib.cib_options.resource_defaults_remove, argv, modifiers
314 )
315
316
317 def resource_op_defaults_set_remove_cmd(
318 lib: Any, argv: Argv, modifiers: InputModifiers
319 ) -> None:
320 """
321 Options:
322 * -f - CIB file
323 """
324 return _defaults_set_remove_cmd(
325 lib.cib_options.operation_defaults_remove, argv, modifiers
326 )
327
328
329 def _defaults_set_update_cmd(
330 lib_command: Callable[..., Any], argv: Argv, modifiers: InputModifiers
331 ) -> None:
332 """
333 Options:
334 * -f - CIB file
335 """
336 modifiers.ensure_only_supported("-f")
337 if not argv:
338 raise CmdLineInputError()
339
340 set_id = argv[0]
341 groups = group_by_keywords(argv[1:], {"meta"})
342 groups.ensure_unique_keywords()
343 lib_command(
344 set_id, KeyValueParser(groups.get_args_flat("meta")).get_unique()
345 )
346
347
348 def resource_defaults_set_update_cmd(
349 lib: Any, argv: Argv, modifiers: InputModifiers
350 ) -> None:
351 """
352 Options:
353 * -f - CIB file
354 """
355 return _defaults_set_update_cmd(
356 lib.cib_options.resource_defaults_update, argv, modifiers
357 )
358
359
360 def resource_op_defaults_set_update_cmd(
361 lib: Any, argv: Argv, modifiers: InputModifiers
362 ) -> None:
363 """
364 Options:
365 * -f - CIB file
366 """
367 return _defaults_set_update_cmd(
368 lib.cib_options.operation_defaults_update, argv, modifiers
369 )
370
371
372 def resource_defaults_legacy_cmd(
373 lib: Any,
374 argv: Argv,
375 modifiers: InputModifiers,
376 deprecated_syntax_used: bool = False,
377 ) -> None:
378 """
379 Options:
380 * -f - CIB file
381 """
382 del modifiers
383 if deprecated_syntax_used:
384 deprecation_warning(
385 "This command is deprecated and will be removed. "
386 "Please use 'pcs resource defaults update' instead."
387 )
388 return lib.cib_options.resource_defaults_update(
389 None, KeyValueParser(argv).get_unique()
390 )
391
392
393 def resource_op_defaults_legacy_cmd(
394 lib: Any,
395 argv: Argv,
396 modifiers: InputModifiers,
397 deprecated_syntax_used: bool = False,
398 ) -> None:
399 """
400 Options:
401 * -f - CIB file
402 """
403 del modifiers
404 if deprecated_syntax_used:
405 deprecation_warning(
406 "This command is deprecated and will be removed. "
407 "Please use 'pcs resource op defaults update' instead."
408 )
409 return lib.cib_options.operation_defaults_update(
410 None, KeyValueParser(argv).get_unique()
411 )
412
413
414 def op_add_cmd(lib: Any, argv: Argv, modifiers: InputModifiers) -> None:
415 """
416 Options:
417 * -f - CIB file
418 * --force - allow unknown options
419 """
420 if not argv:
421 raise CmdLineInputError()
422 check_is_not_stonith(lib, [argv[0]], "pcs stonith op add")
423 resource_op_add(argv, modifiers)
424
425
426 def resource_op_add(argv: Argv, modifiers: InputModifiers) -> None:
427 """
428 Commandline options:
429 * -f - CIB file
430 * --force - allow unknown options
431 """
432 modifiers.ensure_only_supported("-f", "--force")
433 if not argv:
434 raise CmdLineInputError()
435 res_id = argv.pop(0)
436
437 # Check if we need to upgrade cib schema.
438 # To do that, argv must be parsed, which is duplication of parsing in
439 # resource_operation_add. But we need to upgrade the cib first before
440 # calling that function. Hopefully, this will be fixed in the new pcs
441 # architecture.
442
443 # argv[0] is an operation name
444 dom = None
445 op_properties = utils.convert_args_to_tuples(argv[1:])
446 for key, value in op_properties:
447 if key == "on-fail" and value == "demote":
448 dom = utils.cluster_upgrade_to_version(
449 const.PCMK_ON_FAIL_DEMOTE_CIB_VERSION
450 )
451 break
452 if dom is None:
453 dom = utils.get_cib_dom()
454
455 # add the requested operation
456 utils.replace_cib_configuration(resource_operation_add(dom, res_id, argv))
457
458
459 def op_delete_cmd(lib: Any, argv: Argv, modifiers: InputModifiers) -> None:
460 """
461 Options:
462 * -f - CIB file
463 """
464 modifiers.ensure_only_supported("-f")
465 if not argv:
466 raise CmdLineInputError()
467 resource_id = argv.pop(0)
468 check_is_not_stonith(lib, [resource_id], "pcs stonith op delete")
469 resource_operation_remove(resource_id, argv)
470
471
472 def parse_resource_options(
473 argv: Argv,
474 ) -> tuple[list[str], list[list[str]], list[str]]:
475 """
476 Commandline options: no options
477 """
478 ra_values = []
479 op_values: list[list[str]] = []
480 meta_values = []
481 op_args = False
482 meta_args = False
483 for arg in argv:
484 if arg == "op":
485 op_args = True
486 meta_args = False
487 op_values.append([])
488 elif arg == "meta":
489 meta_args = True
490 op_args = False
491 elif op_args:
492 if arg == "op":
493 op_values.append([])
494 elif "=" not in arg and op_values[-1]:
495 op_values.append([])
496 op_values[-1].append(arg)
497 else:
498 op_values[-1].append(arg)
499 elif meta_args:
500 if "=" in arg:
501 meta_values.append(arg)
502 else:
503 ra_values.append(arg)
504 return ra_values, op_values, meta_values
505
506
507 def resource_list_available(
508 lib: Any, argv: Argv, modifiers: InputModifiers
509 ) -> None:
510 """
511 Options:
512 * --nodesc - don't display description
513 """
514 modifiers.ensure_only_supported("--nodesc")
515 if len(argv) > 1:
516 raise CmdLineInputError()
517
518 search = argv[0] if argv else None
519 agent_list = lib.resource_agent.list_agents(
520 not modifiers.get("--nodesc"), search
521 )
522
523 if not agent_list:
524 if search:
525 utils.err("No resource agents matching the filter.")
526 utils.err(
527 "No resource agents available. "
528 "Do you have resource agents installed?"
529 )
530
531 for agent_info in agent_list:
532 name = agent_info["name"]
533 shortdesc = agent_info["shortdesc"]
534 if shortdesc:
535 print(
536 "{0} - {1}".format(
537 name,
538 _format_desc(
539 len(name + " - "), shortdesc.replace("\n", " ")
540 ),
541 )
542 )
543 else:
544 print(name)
545
546
547 def resource_list_options(
548 lib: Any, argv: Argv, modifiers: InputModifiers
549 ) -> None:
550 """
551 Options:
552 * --full - show advanced
553 """
554 modifiers.ensure_only_supported("--full")
555 if len(argv) != 1:
556 raise CmdLineInputError()
557
558 agent_name_str = argv[0]
559 agent_name: ResourceAgentNameDto
560 if ":" in agent_name_str:
561 agent_name = lib.resource_agent.get_structured_agent_name(
562 agent_name_str
563 )
564 else:
565 agent_name = find_single_agent(
566 lib.resource_agent.get_agents_list().names, agent_name_str
567 )
568 if agent_name.standard == "stonith":
569 deprecation_warning(
570 reports.messages.ResourceStonithCommandsMismatch(
571 "stonith / fence agents"
572 ).message
573 + " Please use 'pcs stonith describe' instead."
574 )
575 print(
576 "\n".join(
577 resource_agent_metadata_to_text(
578 lib.resource_agent.get_agent_metadata(agent_name),
579 lib.resource_agent.get_agent_default_operations(
580 agent_name
581 ).operations,
582 verbose=modifiers.is_specified("--full"),
583 )
584 )
585 )
586
587
588 # Return the string formatted with a line length of terminal width and indented
589 def _format_desc(indentation: int, desc: str) -> str:
590 """
591 Commandline options: no options
592 """
593 desc = " ".join(desc.split())
594 dummy_rows, columns = utils.getTerminalSize()
595 columns = max(int(columns), 40)
596 afterindent = columns - indentation
597 if afterindent < 1:
598 afterindent = columns
599
600 output = ""
601 first = True
602 for line in textwrap.wrap(desc, afterindent):
603 if not first:
604 output += " " * indentation
605 output += line
606 output += "\n"
607 first = False
608
609 return output.rstrip()
610
611
612 def resource_create(lib: Any, argv: Argv, modifiers: InputModifiers) -> None: # noqa: PLR0912
613 """
614 Options:
615 * --agent-validation - use agent self validation of instance attributes
616 * --before - specified resource inside a group before which new resource
617 will be placed inside the group
618 * --after - specified resource inside a group after which new resource
619 will be placed inside the group
620 * --group - specifies group in which resource will be created
621 * --force - allow not existing agent, invalid operations or invalid
622 instance attributes, allow not suitable command
623 * --disabled - created resource will be disabled
624 * --no-default-ops - do not add default operations
625 * --wait
626 * -f - CIB file
627 * --future - enable future cli parser behavior
628 """
629 # pylint: disable=too-many-branches
630 modifiers_deprecated = ["--before", "--after", "--group"]
631 modifiers.ensure_only_supported(
632 *(
633 [
634 "--agent-validation",
635 "--force",
636 "--disabled",
637 "--no-default-ops",
638 "--wait",
639 "-f",
640 FUTURE_OPTION,
641 ]
642 + ([] if modifiers.get(FUTURE_OPTION) else modifiers_deprecated)
643 )
644 )
645 if len(argv) < 2:
646 raise CmdLineInputError()
647
648 ra_id = argv[0]
649 ra_type = argv[1]
650
651 if modifiers.get(FUTURE_OPTION):
652 parts = parse_create_new(argv[2:])
653 else:
654 parts = parse_create_old(
655 argv[2:], modifiers.get_subset(*modifiers_deprecated)
656 )
657
658 defined_options = set()
659 if parts.bundle_id:
660 defined_options.add("bundle")
661 if parts.clone:
662 defined_options.add("clone")
663 if parts.promotable:
664 defined_options.add("promotable")
665 if parts.group:
666 defined_options.add("group")
667 if len(defined_options) > 1:
668 raise CmdLineInputError(
669 "you can specify only one of clone, promotable, bundle or {}group".format(
670 "" if modifiers.get(FUTURE_OPTION) else "--"
671 )
672 )
673
674 if parts.group:
675 if parts.group.after_resource and parts.group.before_resource:
676 raise CmdLineInputError(
677 "you cannot specify both 'before' and 'after'"
678 if modifiers.get(FUTURE_OPTION)
679 else "you cannot specify both --before and --after"
680 )
681
682 if parts.promotable and "promotable" in parts.promotable.meta_attrs:
683 raise CmdLineInputError(
684 "you cannot specify both promotable option and promotable keyword"
685 )
686
687 settings = dict(
688 allow_absent_agent=modifiers.get("--force"),
689 allow_invalid_operation=modifiers.get("--force"),
690 allow_invalid_instance_attributes=modifiers.get("--force"),
691 ensure_disabled=modifiers.get("--disabled"),
692 use_default_operations=not modifiers.get("--no-default-ops"),
693 wait=modifiers.get("--wait"),
694 allow_not_suitable_command=modifiers.get("--force"),
695 enable_agent_self_validation=modifiers.get("--agent-validation"),
696 )
697
698 if parts.clone:
699 lib.resource.create_as_clone(
700 ra_id,
701 ra_type,
702 parts.primitive.operations,
703 parts.primitive.meta_attrs,
704 parts.primitive.instance_attrs,
705 parts.clone.meta_attrs,
706 clone_id=parts.clone.clone_id,
707 allow_incompatible_clone_meta_attributes=modifiers.get("--force"),
708 **settings,
709 )
710 elif parts.promotable:
711 lib.resource.create_as_clone(
712 ra_id,
713 ra_type,
714 parts.primitive.operations,
715 parts.primitive.meta_attrs,
716 parts.primitive.instance_attrs,
717 dict(**parts.promotable.meta_attrs, promotable="true"),
718 clone_id=parts.promotable.clone_id,
719 allow_incompatible_clone_meta_attributes=modifiers.get("--force"),
720 **settings,
721 )
722 elif parts.bundle_id:
723 settings["allow_not_accessible_resource"] = modifiers.get("--force")
724 lib.resource.create_into_bundle(
725 ra_id,
726 ra_type,
727 parts.primitive.operations,
728 parts.primitive.meta_attrs,
729 parts.primitive.instance_attrs,
730 parts.bundle_id,
731 **settings,
732 )
733 elif parts.group:
734 adjacent_resource_id = None
735 put_after_adjacent = False
736 if parts.group.after_resource:
737 adjacent_resource_id = parts.group.after_resource
738 put_after_adjacent = True
739 if parts.group.before_resource:
740 adjacent_resource_id = parts.group.before_resource
741 put_after_adjacent = False
742
743 lib.resource.create_in_group(
744 ra_id,
745 ra_type,
746 parts.group.group_id,
747 parts.primitive.operations,
748 parts.primitive.meta_attrs,
749 parts.primitive.instance_attrs,
750 adjacent_resource_id=adjacent_resource_id,
751 put_after_adjacent=put_after_adjacent,
752 **settings,
753 )
754 else:
755 lib.resource.create(
756 ra_id,
757 ra_type,
758 parts.primitive.operations,
759 parts.primitive.meta_attrs,
760 parts.primitive.instance_attrs,
761 **settings,
762 )
763
764
765 def _parse_resource_move_ban(
766 argv: Argv,
767 ) -> tuple[str, Optional[str], Optional[str]]:
768 resource_id = argv.pop(0)
769 node = None
770 lifetime = None
771 while argv:
772 arg = argv.pop(0)
773 if arg.startswith("lifetime="):
774 if lifetime:
775 raise CmdLineInputError()
776 lifetime = arg.split("=")[1]
777 if lifetime and lifetime[0] in list("0123456789"):
778 lifetime = "P" + lifetime
779 elif not node:
780 node = arg
781 else:
782 raise CmdLineInputError()
783 return resource_id, node, lifetime
784
785
786 def resource_move_with_constraint(
787 lib: Any, argv: Argv, modifiers: InputModifiers
788 ) -> None:
789 """
790 Options:
791 * -f - CIB file
792 * --promoted
793 * --wait
794 """
795 modifiers.ensure_only_supported("-f", "--promoted", "--wait")
796
797 if not argv:
798 raise CmdLineInputError("must specify a resource to move")
799 if len(argv) > 3:
800 raise CmdLineInputError()
801 resource_id, node, lifetime = _parse_resource_move_ban(argv)
802
803 lib.resource.move(
804 resource_id,
805 node=node,
806 master=modifiers.is_specified("--promoted"),
807 lifetime=lifetime,
808 wait=modifiers.get("--wait"),
809 )
810
811
812 def resource_move(lib: Any, argv: Argv, modifiers: InputModifiers) -> None:
813 """
814 Options:
815 * --autodelete - deprecated, not needed anymore
816 * --promoted
817 * --strict
818 * --wait
819 """
820 modifiers.ensure_only_supported(
821 "--autodelete", "--promoted", "--strict", "--wait"
822 )
823
824 if not argv:
825 raise CmdLineInputError("must specify a resource to move")
826 resource_id = argv.pop(0)
827 node = None
828 if argv:
829 node = argv.pop(0)
830 if node.startswith("lifetime="):
831 deprecation_warning(
832 "Option 'lifetime' has been removed. {}".format(
833 SEE_MAN_CHANGES.format("0.11")
834 )
835 )
836 if argv:
837 raise CmdLineInputError()
838
839 if modifiers.is_specified("--autodelete"):
840 deprecation_warning(
841 "Option '--autodelete' is deprecated. There is no need to use it "
842 "as its functionality is default now."
843 )
844
845 lib.resource.move_autoclean(
846 resource_id,
847 node=node,
848 master=modifiers.is_specified("--promoted"),
849 wait_timeout=wait_to_timeout(modifiers.get("--wait")),
850 strict=modifiers.get("--strict"),
851 )
852
853
854 def resource_ban(lib: Any, argv: Argv, modifiers: InputModifiers) -> None:
855 """
856 Options:
857 * -f - CIB file
858 * --promoted
859 * --wait
860 """
861 modifiers.ensure_only_supported("-f", "--promoted", "--wait")
862
863 if not argv:
864 raise CmdLineInputError("must specify a resource to ban")
865 if len(argv) > 3:
866 raise CmdLineInputError()
867 resource_id, node, lifetime = _parse_resource_move_ban(argv)
868
869 lib.resource.ban(
870 resource_id,
871 node=node,
872 master=modifiers.is_specified("--promoted"),
873 lifetime=lifetime,
874 wait=modifiers.get("--wait"),
875 )
876
877
878 def resource_unmove_unban(
879 lib: Any, argv: Argv, modifiers: InputModifiers
880 ) -> None:
881 """
882 Options:
883 * -f - CIB file
884 * --promoted
885 * --wait
886 """
887 modifiers.ensure_only_supported("-f", "--expired", "--promoted", "--wait")
888
889 if not argv:
890 raise CmdLineInputError("must specify a resource to clear")
891 if len(argv) > 2:
892 raise CmdLineInputError()
893 resource_id = argv.pop(0)
894 node = argv.pop(0) if argv else None
895
896 lib.resource.unmove_unban(
897 resource_id,
898 node=node,
899 master=modifiers.is_specified("--promoted"),
900 expired=modifiers.is_specified("--expired"),
901 wait=modifiers.get("--wait"),
902 )
903
904
905 def resource_standards(lib: Any, argv: Argv, modifiers: InputModifiers) -> None:
906 """
907 Options: no options
908 """
909 modifiers.ensure_only_supported()
910 if argv:
911 raise CmdLineInputError()
912
913 standards = lib.resource_agent.list_standards()
914
915 if standards:
916 print("\n".join(standards))
917 else:
918 utils.err("No standards found")
919
920
921 def resource_providers(lib: Any, argv: Argv, modifiers: InputModifiers) -> None:
922 """
923 Options: no options
924 """
925 modifiers.ensure_only_supported()
926 if argv:
927 raise CmdLineInputError()
928
929 providers = lib.resource_agent.list_ocf_providers()
930
931 if providers:
932 print("\n".join(providers))
933 else:
934 utils.err("No OCF providers found")
935
936
937 def resource_agents(lib: Any, argv: Argv, modifiers: InputModifiers) -> None:
938 """
939 Options: no options
940 """
941 modifiers.ensure_only_supported()
942 if len(argv) > 1:
943 raise CmdLineInputError()
944
945 standard = argv[0] if argv else None
946
947 agents = lib.resource_agent.list_agents_for_standard_and_provider(standard)
948
949 if agents:
950 print("\n".join(agents))
951 else:
952 utils.err(
953 "No agents found{0}".format(
954 " for {0}".format(argv[0]) if argv else ""
955 )
956 )
957
958
959 def update_cmd(lib: Any, argv: Argv, modifiers: InputModifiers) -> None:
960 """
961 Options:
962 * -f - CIB file
963 * --agent-validation - use agent self validation of instance attributes
964 * --wait
965 * --force - allow invalid options, do not fail if not possible to get
966 agent metadata, allow not suitable command
967 """
968 if not argv:
969 raise CmdLineInputError()
970 check_is_not_stonith(lib, [argv[0]], "pcs stonith update")
971 resource_update(argv, modifiers)
972
973
974 # Update a resource, removing any args that are empty and adding/updating
975 # args that are not empty
976 def resource_update(args: Argv, modifiers: InputModifiers) -> None: # noqa: PLR0912, PLR0915
977 """
978 Commandline options:
979 * -f - CIB file
980 * --agent-validation - use agent self validation of instance attributes
981 * --wait
982 * --force - allow invalid options, do not fail if not possible to get
983 agent metadata, allow not suitable command
984 """
985 # pylint: disable=too-many-branches
986 # pylint: disable=too-many-locals
987 # pylint: disable=too-many-statements
988 modifiers.ensure_only_supported(
989 "-f", "--wait", "--force", "--agent-validation"
990 )
991 if len(args) < 2:
992 raise CmdLineInputError()
993 res_id = args.pop(0)
994
995 # Extract operation arguments
996 ra_values, op_values, meta_values = parse_resource_options(args)
997
998 wait = False
999 wait_timeout = None
1000 if modifiers.is_specified("--wait"):
1001 wait_timeout = utils.validate_wait_get_timeout()
1002 wait = True
1003
1004 # Check if we need to upgrade cib schema.
1005 # To do that, argv must be parsed, which is duplication of parsing below.
1006 # But we need to upgrade the cib first before calling that function.
1007 # Hopefully, this will be fixed in the new pcs architecture.
1008
1009 cib_upgraded = False
1010 for op_argv in op_values:
1011 if cib_upgraded:
1012 break
1013 if len(op_argv) < 2:
1014 continue
1015 # argv[0] is an operation name
1016 op_vars = utils.convert_args_to_tuples(op_argv[1:])
1017 for key, value in op_vars:
1018 if key == "on-fail" and value == "demote":
1019 utils.cluster_upgrade_to_version(
1020 const.PCMK_ON_FAIL_DEMOTE_CIB_VERSION
1021 )
1022 cib_upgraded = True
1023 break
1024
1025 cib_xml = utils.get_cib()
1026 dom = utils.get_cib_dom(cib_xml=cib_xml)
1027
1028 resource = utils.dom_get_resource(dom, res_id)
1029 if not resource:
1030 clone = utils.dom_get_clone(dom, res_id)
1031 master = utils.dom_get_master(dom, res_id)
1032 if clone or master:
1033 if master:
1034 clone = transform_master_to_clone(master)
1035 clone_child = utils.dom_elem_get_clone_ms_resource(clone)
1036 if clone_child:
1037 child_id = clone_child.getAttribute("id")
1038 new_args = ["meta"] + ra_values + meta_values
1039 for op_args in op_values:
1040 if op_args:
1041 new_args += ["op"] + op_args
1042 return resource_update_clone(
1043 dom, clone, child_id, new_args, wait, wait_timeout
1044 )
1045 utils.err("Unable to find resource: %s" % res_id)
1046
1047 params = utils.convert_args_to_tuples(ra_values)
1048
1049 try:
1050 agent_facade = _get_resource_agent_facade(
1051 _get_resource_agent_name_from_rsc_el(resource)
1052 )
1053 report_list = primitive.validate_resource_instance_attributes_update(
1054 utils.cmd_runner(),
1055 agent_facade,
1056 dict(params),
1057 res_id,
1058 get_resources(lib_pacemaker.get_cib(cib_xml)),
1059 force=bool(modifiers.get("--force")),
1060 enable_agent_self_validation=bool(
1061 modifiers.get("--agent-validation")
1062 ),
1063 )
1064 if report_list:
1065 process_library_reports(report_list)
1066 except lib_ra.ResourceAgentError as e:
1067 process_library_reports(
1068 [
1069 lib_ra.resource_agent_error_to_report_item(
1070 e,
1071 reports.get_severity(
1072 reports.codes.FORCE, bool(modifiers.get("--force"))
1073 ),
1074 )
1075 ]
1076 )
1077
1078 utils.dom_update_instance_attr(resource, params)
1079
1080 remote_node_name = utils.dom_get_resource_remote_node_name(resource)
1081
1082 # The "remote-node" meta attribute makes sense (and causes creation of
1083 # inner pacemaker resource) only for primitive. The meta attribute
1084 # "remote-node" has no special meaning for clone/master. So there is no
1085 # need for checking this attribute in clone/master.
1086 #
1087 # It is ok to not to check it until this point in this function:
1088 # 1) Only master/clone element is updated if the parameter "res_id" is an id
1089 # of the clone/master element. In that case another function is called and
1090 # the code path does not reach this point.
1091 # 2) No persistent changes happened until this line if the parameter
1092 # "res_id" is an id of the primitive.
1093 meta_options = KeyValueParser(meta_values).get_unique()
1094 if remote_node_name != guest_node.get_guest_option_value(meta_options):
1095 _detect_guest_change(
1096 meta_options,
1097 bool(modifiers.get("--force")),
1098 )
1099
1100 utils.dom_update_meta_attr(
1101 resource, utils.convert_args_to_tuples(meta_values)
1102 )
1103
1104 operations = resource.getElementsByTagName("operations")
1105 if not operations:
1106 operations = dom.createElement("operations")
1107 resource.appendChild(operations)
1108 else:
1109 operations = operations[0]
1110
1111 get_role = partial(
1112 pacemaker.role.get_value_for_cib,
1113 is_latest_supported=utils.isCibVersionSatisfied(
1114 dom, const.PCMK_NEW_ROLES_CIB_VERSION
1115 ),
1116 )
1117 for op_argv in op_values:
1118 if not op_argv:
1119 continue
1120
1121 op_name = op_argv[0]
1122 if op_name.find("=") != -1:
1123 utils.err(
1124 "%s does not appear to be a valid operation action" % op_name
1125 )
1126
1127 if len(op_argv) < 2:
1128 continue
1129
1130 op_role = ""
1131 op_vars = utils.convert_args_to_tuples(op_argv[1:])
1132
1133 for key, value in op_vars:
1134 if key == "role":
1135 op_role = get_role(value)
1136 break
1137
1138 updating_op = None
1139 updating_op_before = None
1140 for existing_op in operations.getElementsByTagName("op"):
1141 if updating_op:
1142 updating_op_before = existing_op
1143 break
1144 existing_op_name = existing_op.getAttribute("name")
1145 existing_op_role = get_role(existing_op.getAttribute("role"))
1146 if existing_op_role == op_role and existing_op_name == op_name:
1147 updating_op = existing_op
1148 continue
1149
1150 if updating_op:
1151 updating_op.parentNode.removeChild(updating_op)
1152 dom = resource_operation_add(
1153 dom,
1154 res_id,
1155 op_argv,
1156 validate_strict=False,
1157 before_op=updating_op_before,
1158 )
1159
1160 utils.replace_cib_configuration(dom)
1161
1162 if (
1163 remote_node_name
1164 and remote_node_name
1165 != utils.dom_get_resource_remote_node_name(resource)
1166 ):
1167 # if the resource was a remote node and it is not anymore, (or its name
1168 # changed) we need to tell pacemaker about it
1169 output, retval = utils.run(
1170 ["crm_node", "--force", "--remove", remote_node_name]
1171 )
1172
1173 if modifiers.is_specified("--wait"):
1174 args = ["crm_resource", "--wait"]
1175 if wait_timeout:
1176 args.extend(["--timeout=%s" % wait_timeout])
1177 output, retval = utils.run(args)
1178 running_on = utils.resource_running_on(res_id)
1179 if retval == 0:
1180 print_to_stderr(running_on["message"])
1181 else:
1182 msg = []
1183 if retval == PACEMAKER_WAIT_TIMEOUT_STATUS:
1184 msg.append("waiting timeout")
1185 msg.append(running_on["message"])
1186 if retval != 0 and output:
1187 msg.append("\n" + output)
1188 utils.err("\n".join(msg).strip())
1189 return None
1190
1191
1192 def resource_update_clone(dom, clone, res_id, args, wait, wait_timeout):
1193 """
1194 Commandline options:
1195 * -f - CIB file
1196 """
1197 dom, dummy_clone_id = resource_clone_create(
1198 dom, [res_id] + args, update_existing=True
1199 )
1200
1201 utils.replace_cib_configuration(dom)
1202
1203 if wait:
1204 args = ["crm_resource", "--wait"]
1205 if wait_timeout:
1206 args.extend(["--timeout=%s" % wait_timeout])
1207 output, retval = utils.run(args)
1208 running_on = utils.resource_running_on(clone.getAttribute("id"))
1209 if retval == 0:
1210 print_to_stderr(running_on["message"])
1211 else:
1212 msg = []
1213 if retval == PACEMAKER_WAIT_TIMEOUT_STATUS:
1214 msg.append("waiting timeout")
1215 msg.append(running_on["message"])
1216 if retval != 0 and output:
1217 msg.append("\n" + output)
1218 utils.err("\n".join(msg).strip())
1219
1220 return dom
1221
1222
1223 def transform_master_to_clone(master_element):
1224 # create a new clone element with the same id
1225 dom = master_element.ownerDocument
1226 clone_element = dom.createElement("clone")
1227 clone_element.setAttribute("id", master_element.getAttribute("id"))
1228 # place it next to the master element
1229 master_element.parentNode.insertBefore(clone_element, master_element)
1230 # move all master's children to the clone
1231 while master_element.firstChild:
1232 clone_element.appendChild(master_element.firstChild)
1233 # remove the master
1234 master_element.parentNode.removeChild(master_element)
1235 # set meta to make the clone promotable
1236 utils.dom_update_meta_attr(clone_element, [("promotable", "true")])
1237 return clone_element
1238
1239
1240 def resource_operation_add( # noqa: PLR0912, PLR0915
1241 dom, res_id, argv, validate_strict=True, before_op=None
1242 ):
1243 """
1244 Commandline options:
1245 * --force
1246 """
1247 # pylint: disable=too-many-branches
1248 # pylint: disable=too-many-locals
1249 # pylint: disable=too-many-statements
1250 if not argv:
1251 raise CmdLineInputError()
1252
1253 res_el = utils.dom_get_resource(dom, res_id)
1254 if not res_el:
1255 utils.err("Unable to find resource: %s" % res_id)
1256
1257 op_name = argv.pop(0)
1258 op_properties = utils.convert_args_to_tuples(argv)
1259
1260 if "=" in op_name:
1261 utils.err("%s does not appear to be a valid operation action" % op_name)
1262 if "--force" not in utils.pcs_options:
1263 valid_attrs = [
1264 "id",
1265 "name",
1266 "interval",
1267 "description",
1268 "start-delay",
1269 "interval-origin",
1270 "timeout",
1271 "enabled",
1272 "record-pending",
1273 "role",
1274 "on-fail",
1275 OCF_CHECK_LEVEL_INSTANCE_ATTRIBUTE_NAME,
1276 ]
1277 for key, value in op_properties:
1278 if key not in valid_attrs:
1279 utils.err(
1280 "%s is not a valid op option (use --force to override)"
1281 % key
1282 )
1283 if key == "role":
1284 if value not in const.PCMK_ROLES:
1285 utils.err(
1286 "role must be: {} (use --force to override)".format(
1287 format_list_custom_last_separator(
1288 const.PCMK_ROLES, " or "
1289 )
1290 )
1291 )
1292
1293 interval = None
1294 for key, val in op_properties:
1295 if key == "interval":
1296 interval = val
1297 break
1298 if not interval:
1299 interval = "60s" if op_name == "monitor" else "0s"
1300 op_properties.append(("interval", interval))
1301
1302 op_properties.sort(key=lambda a: a[0])
1303 op_properties.insert(0, ("name", op_name))
1304
1305 generate_id = True
1306 for name, value in op_properties:
1307 if name == "id":
1308 op_id = value
1309 generate_id = False
1310 id_valid, id_error = utils.validate_xml_id(value, "operation id")
1311 if not id_valid:
1312 utils.err(id_error)
1313 if utils.does_id_exist(dom, value):
1314 utils.err(
1315 "id '%s' is already in use, please specify another one"
1316 % value
1317 )
1318 if generate_id:
1319 op_id = "%s-%s-interval-%s" % (res_id, op_name, interval)
1320 op_id = utils.find_unique_id(dom, op_id)
1321
1322 op_el = dom.createElement("op")
1323 op_el.setAttribute("id", op_id)
1324 for key, val in op_properties:
1325 if key == OCF_CHECK_LEVEL_INSTANCE_ATTRIBUTE_NAME:
1326 attrib_el = dom.createElement("instance_attributes")
1327 attrib_el.setAttribute(
1328 "id", utils.find_unique_id(dom, "params-" + op_id)
1329 )
1330 op_el.appendChild(attrib_el)
1331 nvpair_el = dom.createElement("nvpair")
1332 nvpair_el.setAttribute("name", key)
1333 nvpair_el.setAttribute("value", val)
1334 nvpair_el.setAttribute(
1335 "id", utils.find_unique_id(dom, "-".join((op_id, key, val)))
1336 )
1337 attrib_el.appendChild(nvpair_el)
1338 elif key == "role" and "--force" not in utils.pcs_options:
1339 op_el.setAttribute(
1340 key,
1341 pacemaker.role.get_value_for_cib(
1342 val,
1343 utils.isCibVersionSatisfied(
1344 dom, const.PCMK_NEW_ROLES_CIB_VERSION
1345 ),
1346 ),
1347 )
1348 else:
1349 op_el.setAttribute(key, val)
1350
1351 operations = res_el.getElementsByTagName("operations")
1352 if not operations:
1353 operations = dom.createElement("operations")
1354 res_el.appendChild(operations)
1355 else:
1356 operations = operations[0]
1357 duplicate_op_list = utils.operation_exists(operations, op_el)
1358 if duplicate_op_list:
1359 utils.err(
1360 "operation %s with interval %ss already specified for %s:\n%s"
1361 % (
1362 op_el.getAttribute("name"),
1363 timeout_to_seconds_legacy(op_el.getAttribute("interval")),
1364 res_id,
1365 "\n".join(
1366 [operation_to_string(op) for op in duplicate_op_list]
1367 ),
1368 )
1369 )
1370 if validate_strict and "--force" not in utils.pcs_options:
1371 duplicate_op_list = utils.operation_exists_by_name(
1372 operations, op_el
1373 )
1374 if duplicate_op_list:
1375 msg = (
1376 "operation {action} already specified for {res}"
1377 + ", use --force to override:\n{op}"
1378 )
1379 utils.err(
1380 msg.format(
1381 action=op_el.getAttribute("name"),
1382 res=res_id,
1383 op="\n".join(
1384 [
1385 operation_to_string(op)
1386 for op in duplicate_op_list
1387 ]
1388 ),
1389 )
1390 )
1391
1392 operations.insertBefore(op_el, before_op)
1393 return dom
1394
1395
1396 def resource_operation_remove(res_id: str, argv: Argv) -> None: # noqa: PLR0912
1397 """
1398 Commandline options:
1399 * -f - CIB file
1400 """
1401 # pylint: disable=too-many-branches
1402 # if no args, then we're removing an operation id
1403
1404 # Do not ever remove an operations element, even if it is empty. There may
1405 # be ACLs set in pacemaker which allow "write" for op elements (adding,
1406 # changing and removing) but not operations elements. In such a case,
1407 # removing an operations element would cause the whole change to be
1408 # rejected by pacemaker with a "permission denied" message.
1409 # https://bugzilla.redhat.com/show_bug.cgi?id=1642514
1410
1411 dom = utils.get_cib_dom()
1412 if not argv:
1413 for operation in dom.getElementsByTagName("op"):
1414 if operation.getAttribute("id") == res_id:
1415 parent = operation.parentNode
1416 parent.removeChild(operation)
1417 utils.replace_cib_configuration(dom)
1418 return
1419 utils.err("unable to find operation id: %s" % res_id)
1420
1421 original_argv = " ".join(argv)
1422
1423 op_name = argv.pop(0)
1424 resource_el = None
1425
1426 for resource in dom.getElementsByTagName("primitive"):
1427 if resource.getAttribute("id") == res_id:
1428 resource_el = resource
1429 break
1430
1431 if not resource_el:
1432 utils.err("Unable to find resource: %s" % res_id)
1433 # return to let mypy know that resource_el is not None anymore
1434 return
1435
1436 remove_all = False
1437 if not argv:
1438 remove_all = True
1439
1440 op_properties = utils.convert_args_to_tuples(argv)
1441 op_properties.append(("name", op_name))
1442 found_match = False
1443 for op in resource_el.getElementsByTagName("op"):
1444 temp_properties = []
1445 for attr_name in op.attributes.keys(): # noqa: SIM118, attributes is not a dict
1446 if attr_name == "id":
1447 continue
1448 temp_properties.append(
1449 (attr_name, op.attributes.get(attr_name).nodeValue)
1450 )
1451
1452 if remove_all and op.attributes["name"].value == op_name:
1453 found_match = True
1454 parent = op.parentNode
1455 parent.removeChild(op)
1456 elif not set(op_properties) ^ set(temp_properties):
1457 found_match = True
1458 parent = op.parentNode
1459 parent.removeChild(op)
1460 break
1461
1462 if not found_match:
1463 utils.err("Unable to find operation matching: %s" % original_argv)
1464
1465 utils.replace_cib_configuration(dom)
1466
1467
1468 def resource_group_rm_cmd(
1469 lib: Any, argv: Argv, modifiers: InputModifiers
1470 ) -> None:
1471 """
1472 Options:
1473 * --wait
1474 * -f - CIB file
1475 """
1476 del lib
1477 modifiers.ensure_only_supported("--wait", "-f")
1478 if not argv:
1479 raise CmdLineInputError()
1480 group_name = argv.pop(0)
1481 resource_ids = argv
1482
1483 cib_dom = resource_group_rm(utils.get_cib_dom(), group_name, resource_ids)
1484
1485 if modifiers.is_specified("--wait"):
1486 wait_timeout = utils.validate_wait_get_timeout()
1487
1488 utils.replace_cib_configuration(cib_dom)
1489
1490 if modifiers.is_specified("--wait"):
1491 args = ["crm_resource", "--wait"]
1492 if wait_timeout:
1493 args.extend(["--timeout=%s" % wait_timeout])
1494 output, retval = utils.run(args)
1495 if retval != 0:
1496 msg = []
1497 if retval == PACEMAKER_WAIT_TIMEOUT_STATUS:
1498 msg.append("waiting timeout")
1499 if output:
1500 msg.append("\n" + output)
1501 utils.err("\n".join(msg).strip())
1502
1503
1504 def resource_group_add_cmd(
1505 lib: Any, argv: Argv, modifiers: InputModifiers
1506 ) -> None:
1507 """
1508 Options:
1509 * --wait
1510 * -f - CIB file
1511 * --after - place a resource in a group after the specified resource in
1512 the group
1513 * --before - place a resource in a group before the specified resource in
1514 the group
1515 """
1516 modifiers.ensure_only_supported("--wait", "-f", "--after", "--before")
1517 if len(argv) < 2:
1518 raise CmdLineInputError()
1519
1520 group_name = argv.pop(0)
1521 resource_names = argv
1522 adjacent_name = None
1523 after_adjacent = True
1524 if modifiers.is_specified("--after") and modifiers.is_specified("--before"):
1525 raise CmdLineInputError("you cannot specify both --before and --after")
1526 if modifiers.is_specified("--after"):
1527 adjacent_name = modifiers.get("--after")
1528 after_adjacent = True
1529 elif modifiers.is_specified("--before"):
1530 adjacent_name = modifiers.get("--before")
1531 after_adjacent = False
1532
1533 lib.resource.group_add(
1534 group_name,
1535 resource_names,
1536 adjacent_resource_id=adjacent_name,
1537 put_after_adjacent=after_adjacent,
1538 wait=modifiers.get("--wait"),
1539 )
1540
1541
1542 def resource_clone(
1543 lib: Any, argv: Argv, modifiers: InputModifiers, promotable: bool = False
1544 ) -> None:
1545 """
1546 Options:
1547 * --wait
1548 * -f - CIB file
1549 * --force - allow to clone stonith resource
1550 """
1551 modifiers.ensure_only_supported("-f", "--force", "--wait")
1552 if not argv:
1553 raise CmdLineInputError()
1554
1555 res = argv[0]
1556 check_is_not_stonith(lib, [res])
1557 cib_dom = utils.get_cib_dom()
1558
1559 if modifiers.is_specified("--wait"):
1560 wait_timeout = utils.validate_wait_get_timeout()
1561
1562 force_flags = set()
1563 if modifiers.get("--force"):
1564 force_flags.add(reports.codes.FORCE)
1565
1566 cib_dom, clone_id = resource_clone_create(
1567 cib_dom, argv, promotable=promotable, force_flags=force_flags
1568 )
1569 cib_dom = constraint.constraint_resource_update(res, cib_dom)
1570 utils.replace_cib_configuration(cib_dom)
1571
1572 if modifiers.is_specified("--wait"):
1573 args = ["crm_resource", "--wait"]
1574 if wait_timeout:
1575 args.extend(["--timeout=%s" % wait_timeout])
1576 output, retval = utils.run(args)
1577 running_on = utils.resource_running_on(clone_id)
1578 if retval == 0:
1579 print_to_stderr(running_on["message"])
1580 else:
1581 msg = []
1582 if retval == PACEMAKER_WAIT_TIMEOUT_STATUS:
1583 msg.append("waiting timeout")
1584 msg.append(running_on["message"])
1585 if output:
1586 msg.append("\n" + output)
1587 utils.err("\n".join(msg).strip())
1588
1589
1590 def _resource_is_ocf(resource_el) -> bool:
1591 return resource_el.getAttribute("class") == "ocf"
1592
1593
1594 def _get_resource_agent_name_from_rsc_el(
1595 resource_el,
1596 ) -> lib_ra.ResourceAgentName:
1597 return lib_ra.ResourceAgentName(
1598 resource_el.getAttribute("class"),
1599 resource_el.getAttribute("provider"),
1600 resource_el.getAttribute("type"),
1601 )
1602
1603
1604 def _get_resource_agent_facade(
1605 resource_agent: lib_ra.ResourceAgentName,
1606 ) -> lib_ra.ResourceAgentFacade:
1607 return lib_ra.ResourceAgentFacadeFactory(
1608 utils.cmd_runner(), utils.get_report_processor()
1609 ).facade_from_parsed_name(resource_agent)
1610
1611
1612 def resource_clone_create( # noqa: PLR0912
1613 cib_dom, argv, update_existing=False, promotable=False, force_flags=()
1614 ):
1615 """
1616 Commandline options:
1617 * --force - allow to clone stonith resource
1618 """
1619 # pylint: disable=too-many-branches
1620 name = argv.pop(0)
1621
1622 resources_el = cib_dom.getElementsByTagName("resources")[0]
1623 element = utils.dom_get_resource(resources_el, name) or utils.dom_get_group(
1624 resources_el, name
1625 )
1626 if not element:
1627 utils.err("unable to find group or resource: %s" % name)
1628
1629 if element.parentNode.tagName == "bundle":
1630 utils.err("cannot clone bundle resource")
1631
1632 if not update_existing:
1633 if utils.dom_get_resource_clone(
1634 cib_dom, name
1635 ) or utils.dom_get_resource_masterslave(cib_dom, name):
1636 utils.err("%s is already a clone resource" % name)
1637
1638 if utils.dom_get_group_clone(
1639 cib_dom, name
1640 ) or utils.dom_get_group_masterslave(cib_dom, name):
1641 utils.err("cannot clone a group that has already been cloned")
1642 else:
1643 if element.parentNode.tagName != "clone":
1644 utils.err("%s is not currently a clone" % name)
1645 clone = element.parentNode
1646
1647 # If element is currently in a group and it's the last member, we get rid
1648 # of the group
1649 if (
1650 element.parentNode.tagName == "group"
1651 and element.parentNode.getElementsByTagName("primitive").length <= 1
1652 ):
1653 element.parentNode.parentNode.removeChild(element.parentNode)
1654
1655 if element.getAttribute("class") == "stonith":
1656 process_library_reports(
1657 [
1658 reports.ReportItem(
1659 severity=reports.item.get_severity(
1660 reports.codes.FORCE,
1661 is_forced=reports.codes.FORCE in force_flags,
1662 ),
1663 message=reports.messages.CloningStonithResourcesHasNoEffect(
1664 [name]
1665 ),
1666 )
1667 ]
1668 )
1669
1670 parts = parse_clone(argv, promotable=promotable)
1671 _check_clone_incompatible_options_child(
1672 element, parts.meta_attrs, force=reports.codes.FORCE in force_flags
1673 )
1674
1675 if not update_existing:
1676 clone_id = parts.clone_id
1677 if clone_id is not None:
1678 report_list = []
1679 validate_id(clone_id, reporter=report_list)
1680 if report_list:
1681 raise CmdLineInputError("invalid id '{}'".format(clone_id))
1682 if utils.does_id_exist(cib_dom, clone_id):
1683 raise CmdLineInputError(
1684 "id '{}' already exists".format(clone_id),
1685 )
1686 else:
1687 clone_id = utils.find_unique_id(cib_dom, name + "-clone")
1688 clone = cib_dom.createElement("clone")
1689 clone.setAttribute("id", clone_id)
1690 clone.appendChild(element)
1691 resources_el.appendChild(clone)
1692
1693 utils.dom_update_meta_attr(clone, sorted(parts.meta_attrs.items()))
1694
1695 return cib_dom, clone.getAttribute("id")
1696
1697
1698 def _check_clone_incompatible_options_child(
1699 child_el,
1700 clone_meta_attrs: Mapping[str, str],
1701 force: bool = False,
1702 ):
1703 report_list = []
1704 if child_el.tagName == "primitive":
1705 report_list = _check_clone_incompatible_options_primitive(
1706 child_el, clone_meta_attrs, force=force
1707 )
1708 elif child_el.tagName == "group":
1709 group_id = child_el.getAttribute("id")
1710 for primitive_el in utils.get_group_children_el_from_el(child_el):
1711 report_list.extend(
1712 _check_clone_incompatible_options_primitive(
1713 primitive_el,
1714 clone_meta_attrs,
1715 group_id=group_id,
1716 force=force,
1717 )
1718 )
1719 if report_list:
1720 process_library_reports(report_list)
1721
1722
1723 def _check_clone_incompatible_options_primitive(
1724 primitive_el,
1725 clone_meta_attrs: Mapping[str, str],
1726 group_id: Optional[str] = None,
1727 force: bool = False,
1728 ) -> reports.ReportItemList:
1729 resource_agent_name = _get_resource_agent_name_from_rsc_el(primitive_el)
1730 primitive_id = primitive_el.getAttribute("id")
1731 if not _resource_is_ocf(primitive_el):
1732 for incompatible_attribute in ("globally-unique", "promotable"):
1733 if is_true(clone_meta_attrs.get(incompatible_attribute, "0")):
1734 return [
1735 reports.ReportItem.error(
1736 reports.messages.ResourceCloneIncompatibleMetaAttributes(
1737 incompatible_attribute,
1738 resource_agent_name.to_dto(),
1739 resource_id=primitive_id,
1740 group_id=group_id,
1741 )
1742 )
1743 ]
1744 else:
1745 try:
1746 resource_agent_facade = _get_resource_agent_facade(
1747 resource_agent_name
1748 )
1749 except lib_ra.ResourceAgentError as e:
1750 return [
1751 lib_ra.resource_agent_error_to_report_item(
1752 e, reports.get_severity(reports.codes.FORCE, force)
1753 )
1754 ]
1755 if resource_agent_facade.metadata.ocf_version == "1.1":
1756 if (
1757 is_true(clone_meta_attrs.get("promotable", "0"))
1758 and not resource_agent_facade.metadata.provides_promotability
1759 ):
1760 return [
1761 reports.ReportItem(
1762 reports.get_severity(reports.codes.FORCE, force),
1763 reports.messages.ResourceCloneIncompatibleMetaAttributes(
1764 "promotable",
1765 resource_agent_name.to_dto(),
1766 resource_id=primitive_id,
1767 group_id=group_id,
1768 ),
1769 )
1770 ]
1771 return []
1772
1773
1774 def resource_clone_master_remove(
1775 lib: Any, argv: Argv, modifiers: InputModifiers
1776 ) -> None:
1777 """
1778 Options:
1779 * -f - CIB file
1780 * --wait
1781 """
1782 # pylint: disable=too-many-locals
1783 del lib
1784 modifiers.ensure_only_supported("-f", "--wait")
1785 if len(argv) != 1:
1786 raise CmdLineInputError()
1787
1788 name = argv.pop()
1789 dom = utils.get_cib_dom()
1790 resources_el = dom.documentElement.getElementsByTagName("resources")[0]
1791
1792 # get the resource no matter if user entered a clone or a cloned resource
1793 resource = (
1794 utils.dom_get_resource(resources_el, name)
1795 or utils.dom_get_group(resources_el, name)
1796 or utils.dom_get_clone_ms_resource(resources_el, name)
1797 )
1798 if not resource:
1799 utils.err("could not find resource: %s" % name)
1800 resource_id = resource.getAttribute("id")
1801 clone = utils.dom_get_resource_clone_ms_parent(resources_el, resource_id)
1802 if not clone:
1803 utils.err("'%s' is not a clone resource" % name)
1804
1805 if modifiers.is_specified("--wait"):
1806 wait_timeout = utils.validate_wait_get_timeout()
1807
1808 # if user requested uncloning a resource contained in a cloned group
1809 # remove the resource from the group and leave the clone itself alone
1810 # unless the resource is the last one in the group
1811 clone_child = utils.dom_get_clone_ms_resource(
1812 resources_el, clone.getAttribute("id")
1813 )
1814 if (
1815 clone_child.tagName == "group"
1816 and resource.tagName != "group"
1817 and len(clone_child.getElementsByTagName("primitive")) > 1
1818 ):
1819 resource_group_rm(dom, clone_child.getAttribute("id"), [resource_id])
1820 else:
1821 remove_resource_references(dom, clone.getAttribute("id"))
1822 clone.parentNode.appendChild(resource)
1823 clone.parentNode.removeChild(clone)
1824 utils.replace_cib_configuration(dom)
1825
1826 if modifiers.is_specified("--wait"):
1827 args = ["crm_resource", "--wait"]
1828 if wait_timeout:
1829 args.extend(["--timeout=%s" % wait_timeout])
1830 output, retval = utils.run(args)
1831 running_on = utils.resource_running_on(resource_id)
1832 if retval == 0:
1833 print_to_stderr(running_on["message"])
1834 else:
1835 msg = []
1836 if retval == PACEMAKER_WAIT_TIMEOUT_STATUS:
1837 msg.append("waiting timeout")
1838 msg.append(running_on["message"])
1839 if output:
1840 msg.append("\n" + output)
1841 utils.err("\n".join(msg).strip())
1842
1843
1844 def resource_remove_cmd(
1845 lib: Any, argv: Argv, modifiers: InputModifiers
1846 ) -> None:
1847 """
1848 Options:
1849 * -f - CIB file
1850 * --force - don't stop resources before deletion
1851 """
1852 modifiers.ensure_only_supported("-f", "--force")
1853 if not argv:
1854 raise CmdLineInputError()
1855 ensure_unique_args(argv)
1856
1857 resources_to_remove = set(argv)
1858 resources_dto = lib.resource.get_configured_resources()
1859 missing_ids = resources_to_remove - get_all_resources_ids(resources_dto)
1860 if missing_ids:
1861 raise CmdLineInputError(
1862 "Unable to find {resource}: {id_list}".format(
1863 resource=format_plural(missing_ids, "resource"),
1864 id_list=format_list(missing_ids),
1865 )
1866 )
1867
1868 stonith_ids = resources_to_remove & get_stonith_resources_ids(resources_dto)
1869 if stonith_ids:
1870 deprecation_warning(
1871 reports.messages.ResourceStonithCommandsMismatch(
1872 "stonith resources"
1873 ).message
1874 + " Please use 'pcs stonith remove' instead."
1875 )
1876
1877 force_flags = set()
1878 if modifiers.is_specified("--force"):
1879 force_flags.add(reports.codes.FORCE)
1880
1881 lib.cib.remove_elements(resources_to_remove, force_flags)
1882
1883
1884 def stonith_level_rm_device(cib_dom, stn_id):
1885 """
1886 Commandline options: no options
1887 """
1888 topology_el_list = cib_dom.getElementsByTagName("fencing-topology")
1889 if not topology_el_list:
1890 return cib_dom
1891 topology_el = topology_el_list[0]
1892 for level_el in topology_el.getElementsByTagName("fencing-level"):
1893 device_list = level_el.getAttribute("devices").split(",")
1894 if stn_id in device_list:
1895 new_device_list = [dev for dev in device_list if dev != stn_id]
1896 if new_device_list:
1897 level_el.setAttribute("devices", ",".join(new_device_list))
1898 else:
1899 level_el.parentNode.removeChild(level_el)
1900 if not topology_el.getElementsByTagName("fencing-level"):
1901 topology_el.parentNode.removeChild(topology_el)
1902 return cib_dom
1903
1904
1905 def remove_resource_references(
1906 dom, resource_id, output=False, constraints_element=None
1907 ):
1908 """
1909 Commandline options: no options
1910 NOTE: -f - will be used only if dom will be None
1911 """
1912 for obj_ref in dom.getElementsByTagName("obj_ref"):
1913 if obj_ref.getAttribute("id") == resource_id:
1914 tag = obj_ref.parentNode
1915 tag.removeChild(obj_ref)
1916 if tag.getElementsByTagName("obj_ref").length == 0:
1917 remove_resource_references(
1918 dom,
1919 tag.getAttribute("id"),
1920 output=output,
1921 )
1922 tag.parentNode.removeChild(tag)
1923 constraint.remove_constraints_containing(
1924 resource_id, output, constraints_element, dom
1925 )
1926 stonith_level_rm_device(dom, resource_id)
1927
1928 for permission in dom.getElementsByTagName("acl_permission"):
1929 if permission.getAttribute("reference") == resource_id:
1930 permission.parentNode.removeChild(permission)
1931
1932 return dom
1933
1934
1935 # This removes a resource from a group, but keeps it in the config
1936 def resource_group_rm(cib_dom, group_name, resource_ids):
1937 """
1938 Commandline options: no options
1939 """
1940 dom = cib_dom.getElementsByTagName("configuration")[0]
1941
1942 all_resources = len(resource_ids) == 0
1943
1944 group_match = utils.dom_get_group(dom, group_name)
1945 if not group_match:
1946 utils.err("Group '%s' does not exist" % group_name)
1947
1948 resources_to_move = []
1949 if all_resources:
1950 resources_to_move.extend(
1951 list(group_match.getElementsByTagName("primitive"))
1952 )
1953 else:
1954 for resource_id in resource_ids:
1955 resource = utils.dom_get_resource(group_match, resource_id)
1956 if resource:
1957 resources_to_move.append(resource)
1958 else:
1959 utils.err(
1960 "Resource '%s' does not exist in group '%s'"
1961 % (resource_id, group_name)
1962 )
1963
1964 # If the group is in a clone, we don't delete the clone as there may be
1965 # constraints associated with it which the user may want to keep. However,
1966 # there may be several resources in the group. In that case there is no way
1967 # to figure out which one of them should stay in the clone. So we forbid
1968 # removing all resources from a cloned group unless there is just one
1969 # resource.
1970 # This creates an inconsistency:
1971 # - consider a cloned group with two resources
1972 # - move one resource from the group - it becomes a primitive
1973 # - move the last resource from the group - it stays in the clone
1974 # So far there has been no request to change this behavior. Unless there is
1975 # a request / reason to change it, we'll keep it that way.
1976 is_cloned_group = group_match.parentNode.tagName in ["clone", "master"]
1977 res_in_group = len(group_match.getElementsByTagName("primitive"))
1978 if (
1979 is_cloned_group
1980 and res_in_group > 1
1981 and len(resources_to_move) == res_in_group
1982 ):
1983 utils.err("Cannot remove all resources from a cloned group")
1984 target_node = group_match.parentNode
1985 if is_cloned_group and res_in_group > 1:
1986 target_node = dom.getElementsByTagName("resources")[0]
1987 for resource in resources_to_move:
1988 resource.parentNode.removeChild(resource)
1989 target_node.appendChild(resource)
1990
1991 if not group_match.getElementsByTagName("primitive"):
1992 group_match.parentNode.removeChild(group_match)
1993 remove_resource_references(dom, group_name, output=True)
1994
1995 return cib_dom
1996
1997
1998 def resource_group_list(
1999 lib: Any, argv: Argv, modifiers: InputModifiers
2000 ) -> None:
2001 """
2002 Options:
2003 * -f - CIB file
2004 """
2005 del lib
2006 modifiers.ensure_only_supported("-f")
2007 if argv:
2008 raise CmdLineInputError()
2009 group_xpath = "//group"
2010 group_xml = utils.get_cib_xpath(group_xpath)
2011
2012 # If no groups exist, we silently return
2013 if group_xml == "":
2014 return
2015
|
(1) Event Sigma main event: |
The application uses Python's built in `xml` module which does not properly handle erroneous or maliciously constructed data, making the application vulnerable to one or more types of XML attacks. |
|
(2) Event remediation: |
Avoid using the `xml` module. Consider using the `defusedxml` module or similar which safely prevents all XML entity attacks. |
2016 element = parseString(group_xml).documentElement
2017 # If there is more than one group returned it's wrapped in an xpath-query
2018 # element
2019 # Ignoring mypy errors in this very old code. So far, nobody reported a bug
2020 # related to these lines.
2021 if element.tagName == "xpath-query": # type: ignore
2022 elements = element.getElementsByTagName("group") # type: ignore
2023 else:
2024 elements = [element]
2025
2026 for e in elements:
2027 line_parts = [e.getAttribute("id") + ":"]
2028 line_parts.extend(
2029 resource.getAttribute("id")
2030 for resource in e.getElementsByTagName("primitive")
2031 )
2032 print(" ".join(line_parts))
2033
2034
2035 def resource_show(
2036 lib: Any, argv: Argv, modifiers: InputModifiers, stonith: bool = False
2037 ) -> None:
2038 # TODO remove, deprecated command
2039 # replaced with 'resource status' and 'resource config'
2040 """
2041 Options:
2042 * -f - CIB file
2043 * --full - print all configured options
2044 * --groups - print resource groups
2045 * --hide-inactive - print only active resources
2046 """
2047 del lib
2048 modifiers.ensure_only_supported(
2049 "-f", "--full", "--groups", "--hide-inactive"
2050 )
2051 if modifiers.get("--groups"):
2052 raise_command_replaced(["pcs resource group list"], pcs_version="0.11")
2053
2054 keyword = "stonith" if stonith else "resource"
2055 if modifiers.get("--full") or argv:
2056 raise_command_replaced([f"pcs {keyword} config"], pcs_version="0.11")
2057
2058 raise_command_replaced([f"pcs {keyword} status"], pcs_version="0.11")
2059
2060
2061 def resource_status( # noqa: PLR0912, PLR0915
2062 lib: Any, argv: Argv, modifiers: InputModifiers, stonith: bool = False
2063 ) -> None:
2064 """
2065 Options:
2066 * -f - CIB file
2067 * --hide-inactive - print only active resources
2068 """
2069 # pylint: disable=too-many-branches
2070 # pylint: disable=too-many-locals
2071 # pylint: disable=too-many-statements
2072 del lib
2073 modifiers.ensure_only_supported("-f", "--hide-inactive")
2074 if len(argv) > 2:
2075 raise CmdLineInputError()
2076
2077 monitor_command = ["crm_mon", "--one-shot"]
2078 if not modifiers.get("--hide-inactive"):
2079 monitor_command.append("--inactive")
2080
2081 resource_or_tag_id = None
2082 node = None
2083 crm_mon_err_msg = "unable to get cluster status from crm_mon\n"
2084 if argv:
2085 for arg in argv[:]:
2086 if "=" not in arg:
2087 resource_or_tag_id = arg
2088 crm_mon_err_msg = (
2089 f"unable to get status of '{resource_or_tag_id}' from crm_"
2090 "mon\n"
2091 )
2092 monitor_command.extend(
2093 [
2094 "--include",
2095 "none,resources",
2096 "--resource",
2097 resource_or_tag_id,
2098 ]
2099 )
2100 argv.remove(arg)
2101 break
2102 parser = KeyValueParser(argv)
2103 parser.check_allowed_keys({"node"})
2104 node = parser.get_unique().get("node")
2105 if node == "":
2106 utils.err("missing value of 'node' option")
2107 if node:
2108 monitor_command.extend(["--node", node])
2109
2110 output, retval = utils.run(monitor_command)
2111 if retval != 0:
2112 utils.err(crm_mon_err_msg + output.rstrip())
2113 preg = re.compile(r".*(stonith:.*)")
2114 resources_header = False
2115 in_resources = False
2116 has_resources = False
2117 no_resources_line = (
2118 "NO stonith devices configured"
2119 if stonith
2120 else "NO resources configured"
2121 )
2122 no_active_resources_msg = "No active resources"
2123 for line in output.split("\n"):
2124 if line in (
2125 " * No active resources", # pacemaker >= 2.0.3 with --hide-inactive
2126 "No active resources", # pacemaker < 2.0.3 with --hide-inactive
2127 ):
2128 print(no_active_resources_msg)
2129 return
2130 if line in (
2131 " * No resources", # pacemaker >= 2.0.3
2132 "No resources", # pacemaker < 2.0.3
2133 ):
2134 if resource_or_tag_id and not node:
2135 utils.err(
2136 f"resource or tag id '{resource_or_tag_id}' not found"
2137 )
2138 if not node:
2139 print(no_resources_line)
2140 else:
2141 print(no_active_resources_msg)
2142 return
2143 if line in (
2144 "Full List of Resources:", # pacemaker >= 2.0.3
2145 "Active Resources:", # pacemaker >= 2.0.3 with --hide-inactive
2146 ):
2147 in_resources = True
2148 continue
2149 if line in (
2150 "Full list of resources:", # pacemaker < 2.0.3
2151 "Active resources:", # pacemaker < 2.0.3 with --hide-inactive
2152 ):
2153 resources_header = True
2154 continue
2155 if line == "":
2156 if resources_header:
2157 resources_header = False
2158 in_resources = True
2159 elif in_resources:
2160 if not has_resources:
2161 print(no_resources_line)
2162 return
2163 continue
2164 if in_resources:
2165 if resource_or_tag_id:
2166 has_resources = True
2167 print(line)
2168 continue
2169 if (
2170 not preg.match(line)
2171 and not stonith
2172 or preg.match(line)
2173 and stonith
2174 ):
2175 has_resources = True
2176 print(line)
2177
2178
2179 def resource_disable_cmd(
2180 lib: Any, argv: Argv, modifiers: InputModifiers
2181 ) -> None:
2182 """
2183 Options:
2184 * -f - CIB file
2185 * --brief - show brief output of --simulate
2186 * --safe - only disable if no other resource gets stopped or demoted
2187 * --simulate - do not push the CIB, print its effects
2188 * --no-strict - allow disable if other resource is affected
2189 * --wait
2190 """
2191 if not argv:
2192 raise CmdLineInputError("You must specify resource(s) to disable")
2193 check_is_not_stonith(lib, argv, "pcs stonith disable")
2194 resource_disable_common(lib, argv, modifiers)
2195
2196
2197 def resource_disable_common(
2198 lib: Any, argv: Argv, modifiers: InputModifiers
2199 ) -> None:
2200 """
2201 Commandline options:
2202 * -f - CIB file
2203 * --force - allow to disable the last stonith resource in the cluster
2204 * --brief - show brief output of --simulate
2205 * --safe - only disable if no other resource gets stopped or demoted
2206 * --simulate - do not push the CIB, print its effects
2207 * --no-strict - allow disable if other resource is affected
2208 * --wait
2209 """
2210 modifiers.ensure_only_supported(
2211 "-f",
2212 "--force",
2213 "--brief",
2214 "--safe",
2215 "--simulate",
2216 "--no-strict",
2217 "--wait",
2218 )
2219 modifiers.ensure_not_mutually_exclusive("-f", "--simulate", "--wait")
2220 modifiers.ensure_not_incompatible("--simulate", {"-f", "--safe", "--wait"})
2221 modifiers.ensure_not_incompatible("--safe", {"-f", "--simulate"})
2222 modifiers.ensure_not_incompatible("--no-strict", {"-f"})
2223
2224 if not argv:
2225 raise CmdLineInputError("You must specify resource(s) to disable")
2226
2227 if modifiers.get("--simulate"):
2228 result = lib.resource.disable_simulate(
2229 argv, not modifiers.get("--no-strict")
2230 )
2231 if modifiers.get("--brief"):
2232 # if the result is empty, printing it would produce a new line,
2233 # which is not wanted
2234 if result["other_affected_resource_list"]:
2235 print("\n".join(result["other_affected_resource_list"]))
2236 return
2237 print(result["plaintext_simulated_status"])
2238 return
2239 if modifiers.get("--safe") or modifiers.get("--no-strict"):
2240 if modifiers.get("--brief"):
2241 # Brief mode skips simulation output by setting the report processor
2242 # to ignore info reports which contain crm_simulate output and
2243 # resource status in this command
2244 lib.env.report_processor.suppress_reports_of_severity(
2245 [reports.ReportItemSeverity.INFO]
2246 )
2247 lib.resource.disable_safe(
2248 argv,
2249 not modifiers.get("--no-strict"),
2250 modifiers.get("--wait"),
2251 )
2252 return
2253 if modifiers.get("--brief"):
2254 raise CmdLineInputError(
2255 "'--brief' cannot be used without '--simulate' or '--safe'"
2256 )
2257 force_flags = set()
2258 if modifiers.get("--force"):
2259 force_flags.add(reports.codes.FORCE)
2260 lib.resource.disable(argv, modifiers.get("--wait"), force_flags)
2261
2262
2263 def resource_safe_disable_cmd(
2264 lib: Any, argv: Argv, modifiers: InputModifiers
2265 ) -> None:
2266 """
2267 Options:
2268 * --brief - show brief output of --simulate
2269 * --force - skip checks for safe resource disable
2270 * --no-strict - allow disable if other resource is affected
2271 * --simulate - do not push the CIB, print its effects
2272 * --wait
2273 """
2274 modifiers.ensure_only_supported(
2275 "--brief", "--force", "--no-strict", "--simulate", "--wait"
2276 )
2277 modifiers.ensure_not_incompatible("--force", {"--no-strict", "--simulate"})
2278 custom_options = {}
2279 if modifiers.get("--force"):
2280 warn(
2281 "option '--force' is specified therefore checks for disabling "
2282 "resource safely will be skipped"
2283 )
2284 elif not modifiers.get("--simulate"):
2285 custom_options["--safe"] = True
2286 resource_disable_cmd(
2287 lib,
2288 argv,
2289 modifiers.get_subset(
2290 "--wait", "--no-strict", "--simulate", "--brief", **custom_options
2291 ),
2292 )
2293
2294
2295 def resource_enable_cmd(
2296 lib: Any, argv: Argv, modifiers: InputModifiers
2297 ) -> None:
2298 """
2299 Options:
2300 * --wait
2301 * -f - CIB file
2302 """
2303 modifiers.ensure_only_supported("--wait", "-f")
2304 if not argv:
2305 raise CmdLineInputError("You must specify resource(s) to enable")
2306 resources = argv
2307 check_is_not_stonith(lib, resources, "pcs stonith enable")
2308 lib.resource.enable(resources, modifiers.get("--wait"))
2309
2310
2311 def resource_restart_cmd(
2312 lib: Any, argv: Argv, modifiers: InputModifiers
2313 ) -> None:
2314 """
2315 Options:
2316 * --wait
2317 """
2318 modifiers.ensure_only_supported("--wait")
2319 if not argv:
2320 raise CmdLineInputError(
2321 "You must specify a resource to restart",
2322 show_both_usage_and_message=True,
2323 )
2324 resource = argv.pop(0)
2325 node = argv.pop(0) if argv else None
2326 if argv:
2327 raise CmdLineInputError()
2328
2329 check_is_not_stonith(lib, [resource])
2330 timeout = (
2331 modifiers.get("--wait") if modifiers.is_specified("--wait") else None
2332 )
2333
2334 lib.resource.restart(resource, node, timeout)
2335
2336 print_to_stderr(f"{resource} successfully restarted")
2337
2338
2339 def resource_force_action( # noqa: PLR0912
2340 lib: Any, argv: Argv, modifiers: InputModifiers, action: str
2341 ) -> None:
2342 """
2343 Options:
2344 * --force
2345 * --full - more verbose output
2346 """
2347 # pylint: disable=too-many-branches
2348 modifiers.ensure_only_supported("--force", "--full")
2349 action_command = {
2350 "debug-start": "--force-start",
2351 "debug-stop": "--force-stop",
2352 "debug-promote": "--force-promote",
2353 "debug-demote": "--force-demote",
2354 "debug-monitor": "--force-check",
2355 }
2356
2357 if action not in action_command:
2358 raise CmdLineInputError()
2359 if not argv:
2360 utils.err("You must specify a resource to {0}".format(action))
2361 if len(argv) != 1:
2362 raise CmdLineInputError()
2363
2364 resource = argv[0]
2365 check_is_not_stonith(lib, [resource])
2366 dom = utils.get_cib_dom()
2367
2368 if not (
2369 utils.dom_get_any_resource(dom, resource)
2370 or utils.dom_get_bundle(dom, resource)
2371 ):
2372 utils.err(
2373 "unable to find a resource/clone/group/bundle: {0}".format(resource)
2374 )
2375 bundle_el = utils.dom_get_bundle(dom, resource)
2376 if bundle_el:
2377 bundle_resource = utils.dom_get_resource_bundle(bundle_el)
2378 if bundle_resource:
2379 utils.err(
2380 "unable to {0} a bundle, try the bundle's resource: {1}".format(
2381 action, bundle_resource.getAttribute("id")
2382 )
2383 )
2384 else:
2385 utils.err("unable to {0} a bundle".format(action))
2386 if utils.dom_get_group(dom, resource):
2387 group_resources = utils.get_group_children(resource)
2388 utils.err(
2389 (
2390 "unable to {0} a group, try one of the group's resource(s) "
2391 "({1})"
2392 ).format(action, ",".join(group_resources))
2393 )
2394 if utils.dom_get_clone(dom, resource) or utils.dom_get_master(
2395 dom, resource
2396 ):
2397 clone_resource = utils.dom_get_clone_ms_resource(dom, resource)
2398 utils.err(
2399 "unable to {0} a clone, try the clone's resource: {1}".format(
2400 action, clone_resource.getAttribute("id")
2401 )
2402 )
2403
2404 args = ["crm_resource", "-r", resource, action_command[action]]
2405 if modifiers.get("--full"):
2406 # set --verbose twice to get a reasonable amount of debug messages
2407 args.extend(["--verbose"] * 2)
2408 if modifiers.get("--force"):
2409 args.append("--force")
2410 output, retval = utils.run(args)
2411
2412 if "doesn't support group resources" in output:
2413 utils.err("groups are not supported")
2414 sys.exit(retval)
2415 if "doesn't support stonith resources" in output:
2416 utils.err("stonith devices are not supported")
2417 sys.exit(retval)
2418
2419 print(output.rstrip())
2420 sys.exit(retval)
2421
2422
2423 def resource_manage_cmd(
2424 lib: Any, argv: Argv, modifiers: InputModifiers
2425 ) -> None:
2426 """
2427 Options:
2428 * -f - CIB file
2429 * --monitor - enable monitor operation of specified resources
2430 """
2431 modifiers.ensure_only_supported("-f", "--monitor")
2432 if not argv:
2433 raise CmdLineInputError("You must specify resource(s) to manage")
2434 resources = argv
2435 check_is_not_stonith(lib, resources)
2436 lib.resource.manage(resources, with_monitor=modifiers.get("--monitor"))
2437
2438
2439 def resource_unmanage_cmd(
2440 lib: Any, argv: Argv, modifiers: InputModifiers
2441 ) -> None:
2442 """
2443 Options:
2444 * -f - CIB file
2445 * --monitor - bisable monitor operation of specified resources
2446 """
2447 modifiers.ensure_only_supported("-f", "--monitor")
2448 if not argv:
2449 raise CmdLineInputError("You must specify resource(s) to unmanage")
2450 resources = argv
2451 check_is_not_stonith(lib, resources)
2452 lib.resource.unmanage(resources, with_monitor=modifiers.get("--monitor"))
2453
2454
2455 def resource_failcount_show(
2456 lib: Any, argv: Argv, modifiers: InputModifiers
2457 ) -> None:
2458 """
2459 Options:
2460 * --full
2461 * -f - CIB file
2462 """
2463 # pylint: disable=too-many-locals
2464 modifiers.ensure_only_supported("-f", "--full")
2465
2466 resource = argv.pop(0) if argv and "=" not in argv[0] else None
2467 parser = KeyValueParser(argv)
2468 parser.check_allowed_keys({"node", "operation", "interval"})
2469 parsed_options = parser.get_unique()
2470
2471 node = parsed_options.get("node")
2472 operation = parsed_options.get("operation")
2473 interval = parsed_options.get("interval")
2474 result_lines = []
2475 failures_data = lib.resource.get_failcounts(
2476 resource=resource, node=node, operation=operation, interval=interval
2477 )
2478
2479 if not failures_data:
2480 result_lines.append(
2481 __headline_resource_failures(
2482 True, resource, node, operation, interval
2483 )
2484 )
2485 print("\n".join(result_lines))
2486 return
2487
2488 resource_list = sorted({fail["resource"] for fail in failures_data})
2489 for current_resource in resource_list:
2490 result_lines.append(
2491 __headline_resource_failures(
2492 False, current_resource, node, operation, interval
2493 )
2494 )
2495 resource_failures = [
2496 fail
2497 for fail in failures_data
2498 if fail["resource"] == current_resource
2499 ]
2500 node_list = sorted({fail["node"] for fail in resource_failures})
2501 for current_node in node_list:
2502 node_failures = [
2503 fail
2504 for fail in resource_failures
2505 if fail["node"] == current_node
2506 ]
2507 if modifiers.get("--full"):
2508 result_lines.append(f" {current_node}:")
2509 operation_list = sorted(
2510 {fail["operation"] for fail in node_failures}
2511 )
2512 for current_operation in operation_list:
2513 operation_failures = [
2514 fail
2515 for fail in node_failures
2516 if fail["operation"] == current_operation
2517 ]
2518 interval_list = sorted(
2519 {fail["interval"] for fail in operation_failures},
2520 # pacemaker's definition of infinity
2521 key=lambda x: 1000000 if x == "INFINITY" else x,
2522 )
2523 for current_interval in interval_list:
2524 interval_failures = [
2525 fail
2526 for fail in operation_failures
2527 if fail["interval"] == current_interval
2528 ]
2529 failcount, dummy_last_failure = __aggregate_failures(
2530 interval_failures
2531 )
2532 result_lines.append(
2533 f" {current_operation} {current_interval}ms: "
2534 f"{failcount}"
2535 )
2536 else:
2537 failcount, dummy_last_failure = __aggregate_failures(
2538 node_failures
2539 )
2540 result_lines.append(f" {current_node}: {failcount}")
2541 print("\n".join(result_lines))
2542
2543
2544 def __aggregate_failures(failure_list):
2545 """
2546 Commandline options: no options
2547 """
2548 last_failure = 0
2549 fail_count = 0
2550 for failure in failure_list:
2551 # infinity is a maximal value and cannot be increased
2552 if fail_count != "INFINITY":
2553 if failure["fail_count"] == "INFINITY":
2554 fail_count = failure["fail_count"]
2555 else:
2556 fail_count += failure["fail_count"]
2557 last_failure = max(last_failure, failure["last_failure"])
2558 return fail_count, last_failure
2559
2560
2561 def __headline_resource_failures(empty, resource, node, operation, interval):
2562 """
2563 Commandline options: no options
2564 """
2565 headline_parts = []
2566 if empty:
2567 headline_parts.append("No failcounts")
2568 else:
2569 headline_parts.append("Failcounts")
2570 if operation:
2571 headline_parts.append("for operation '{operation}'")
2572 if interval:
2573 headline_parts.append("with interval '{interval}'")
2574 if resource:
2575 headline_parts.append("of" if operation else "for")
2576 headline_parts.append("resource '{resource}'")
2577 if node:
2578 headline_parts.append("on node '{node}'")
2579 return " ".join(headline_parts).format(
2580 node=node, resource=resource, operation=operation, interval=interval
2581 )
2582
2583
2584 def operation_to_string(op_el):
2585 """
2586 Commandline options: no options
2587 """
2588 parts = []
2589 parts.append(op_el.getAttribute("name"))
2590 for name, value in sorted(op_el.attributes.items()):
2591 if name in ["id", "name"]:
2592 continue
2593 parts.append(name + "=" + value)
2594 parts.extend(
2595 f"{nvpair.getAttribute('name')}={nvpair.getAttribute('value')}"
2596 for nvpair in op_el.getElementsByTagName("nvpair")
2597 )
2598 parts.append("(" + op_el.getAttribute("id") + ")")
2599 return " ".join(parts)
2600
2601
2602 def resource_cleanup(lib: Any, argv: Argv, modifiers: InputModifiers) -> None:
2603 """
2604 Options: no options
2605 """
2606 del lib
2607 modifiers.ensure_only_supported("--strict")
2608 resource = argv.pop(0) if argv and "=" not in argv[0] else None
2609 parser = KeyValueParser(argv)
2610 parser.check_allowed_keys({"node", "operation", "interval"})
2611 parsed_options = parser.get_unique()
2612
2613 print_to_stderr(
2614 lib_pacemaker.resource_cleanup(
2615 utils.cmd_runner(),
2616 resource=resource,
2617 node=parsed_options.get("node"),
2618 operation=parsed_options.get("operation"),
2619 interval=parsed_options.get("interval"),
2620 strict=bool(modifiers.get("--strict")),
2621 )
2622 )
2623
2624
2625 def resource_refresh(lib: Any, argv: Argv, modifiers: InputModifiers) -> None:
2626 """
2627 Options:
2628 * --force - do refresh even though it may be time consuming
2629 """
2630 del lib
2631 modifiers.ensure_only_supported(
2632 "--force",
2633 "--strict",
2634 # The hint is defined to print error messages which point users to the
2635 # changes section in pcs manpage.
2636 # To be removed in the next significant version.
2637 hint_syntax_changed=modifiers.is_specified("--full"),
2638 )
2639 resource = argv.pop(0) if argv and "=" not in argv[0] else None
2640 parser = KeyValueParser(argv)
2641 parser.check_allowed_keys({"node"})
2642 parsed_options = parser.get_unique()
2643 print_to_stderr(
2644 lib_pacemaker.resource_refresh(
2645 utils.cmd_runner(),
2646 resource=resource,
2647 node=parsed_options.get("node"),
2648 strict=bool(modifiers.get("--strict")),
2649 force=bool(modifiers.get("--force")),
2650 )
2651 )
2652
2653
2654 def resource_relocate_show_cmd(
2655 lib: Any, argv: Argv, modifiers: InputModifiers
2656 ) -> None:
2657 """
2658 Options: no options
2659 """
2660 del lib
2661 modifiers.ensure_only_supported()
2662 if argv:
2663 raise CmdLineInputError()
2664 resource_relocate_show(utils.get_cib_dom())
2665
2666
2667 def resource_relocate_dry_run_cmd(
2668 lib: Any, argv: Argv, modifiers: InputModifiers
2669 ) -> None:
2670 """
2671 Options:
2672 * -f - CIB file
2673 """
2674 modifiers.ensure_only_supported("-f")
2675 if argv:
2676 check_is_not_stonith(lib, argv)
2677 resource_relocate_run(utils.get_cib_dom(), argv, dry=True)
2678
2679
2680 def resource_relocate_run_cmd(
2681 lib: Any, argv: Argv, modifiers: InputModifiers
2682 ) -> None:
2683 """
2684 Options: no options
2685 """
2686 modifiers.ensure_only_supported()
2687 if argv:
2688 check_is_not_stonith(lib, argv)
2689 resource_relocate_run(utils.get_cib_dom(), argv, dry=False)
2690
2691
2692 def resource_relocate_clear_cmd(
2693 lib: Any, argv: Argv, modifiers: InputModifiers
2694 ) -> None:
2695 """
2696 Options:
2697 * -f - CIB file
2698 """
2699 del lib
2700 modifiers.ensure_only_supported("-f")
2701 if argv:
2702 raise CmdLineInputError()
2703 utils.replace_cib_configuration(
2704 resource_relocate_clear(utils.get_cib_dom())
2705 )
2706
2707
2708 def resource_relocate_set_stickiness(cib_dom, resources=None):
2709 """
2710 Commandline options: no options
2711 """
2712 resources = [] if resources is None else resources
2713 cib_dom = cib_dom.cloneNode(True) # do not change the original cib
2714 resources_found = set()
2715 updated_resources = set()
2716 # set stickiness=0
2717 for tagname in ("master", "clone", "group", "primitive"):
2718 for res_el in cib_dom.getElementsByTagName(tagname):
2719 if resources and res_el.getAttribute("id") not in resources:
2720 continue
2721 resources_found.add(res_el.getAttribute("id"))
2722 res_and_children = (
2723 [res_el]
2724 + res_el.getElementsByTagName("group")
2725 + res_el.getElementsByTagName("primitive")
2726 )
2727 updated_resources.update(
2728 [el.getAttribute("id") for el in res_and_children]
2729 )
2730 for res_or_child in res_and_children:
2731 meta_attributes = utils.dom_prepare_child_element(
2732 res_or_child,
2733 "meta_attributes",
2734 res_or_child.getAttribute("id") + "-meta_attributes",
2735 )
2736 utils.dom_update_nv_pair(
2737 meta_attributes,
2738 "resource-stickiness",
2739 "0",
2740 meta_attributes.getAttribute("id") + "-",
2741 )
2742 # resources don't exist
2743 if resources:
2744 resources_not_found = set(resources) - resources_found
2745 if resources_not_found:
2746 for res_id in resources_not_found:
2747 utils.err(
2748 "unable to find a resource/clone/group: {0}".format(res_id),
2749 False,
2750 )
2751 sys.exit(1)
2752 return cib_dom, updated_resources
2753
2754
2755 def resource_relocate_get_locations(cib_dom, resources=None):
2756 """
2757 Commandline options:
2758 * --force - allow constraint on any resource, may not have any effective
2759 as an invalid constraint is ignored anyway
2760 """
2761 resources = [] if resources is None else resources
2762 updated_cib, updated_resources = resource_relocate_set_stickiness(
2763 cib_dom, resources
2764 )
2765 dummy_simout, transitions, new_cib = utils.simulate_cib(updated_cib)
2766 operation_list = utils.get_operations_from_transitions(transitions)
2767 locations = utils.get_resources_location_from_operations(
2768 new_cib, operation_list
2769 )
2770 # filter out non-requested resources
2771 if not resources:
2772 return list(locations.values())
2773 return [
2774 val
2775 for val in locations.values()
2776 if val["id"] in updated_resources
2777 or val["id_for_constraint"] in updated_resources
2778 ]
2779
2780
2781 def resource_relocate_show(cib_dom):
2782 """
2783 Commandline options: no options
2784 """
2785 updated_cib, dummy_updated_resources = resource_relocate_set_stickiness(
2786 cib_dom
2787 )
2788 simout, dummy_transitions, dummy_new_cib = utils.simulate_cib(updated_cib)
2789 in_status = False
2790 in_status_resources = False
2791 in_transitions = False
2792 for line in simout.split("\n"):
2793 if line.strip() == "Current cluster status:":
2794 in_status = True
2795 in_status_resources = False
2796 in_transitions = False
2797 elif line.strip() == "Transition Summary:":
2798 in_status = False
2799 in_status_resources = False
2800 in_transitions = True
2801 print()
2802 elif line.strip() == "":
2803 if in_status:
2804 in_status = False
2805 in_status_resources = True
2806 in_transitions = False
2807 else:
2808 in_status = False
2809 in_status_resources = False
2810 in_transitions = False
2811 if in_status or in_status_resources or in_transitions:
2812 print(line)
2813
2814
2815 def resource_relocate_location_to_str(location):
2816 """
2817 Commandline options: no options
2818 """
2819 message = (
2820 "Creating location constraint: {res} prefers {node}=INFINITY{role}"
2821 )
2822 if "start_on_node" in location:
2823 return message.format(
2824 res=location["id_for_constraint"],
2825 node=location["start_on_node"],
2826 role="",
2827 )
2828 if "promote_on_node" in location:
2829 return message.format(
2830 res=location["id_for_constraint"],
2831 node=location["promote_on_node"],
2832 role=f" role={const.PCMK_ROLE_PROMOTED_PRIMARY}",
2833 )
2834 return ""
2835
2836
2837 def resource_relocate_run(cib_dom, resources=None, dry=True): # noqa: PLR0912
2838 """
2839 Commandline options:
2840 * -f - CIB file, explicitly forbids -f if dry is False
2841 * --force - allow constraint on any resource, may not have any effective
2842 as an invalid copnstraint is ignored anyway
2843 """
2844 # pylint: disable=too-many-branches
2845 resources = [] if resources is None else resources
2846 was_error = False
2847 anything_changed = False
2848 if not dry and utils.usefile:
2849 utils.err("This command cannot be used with -f")
2850
2851 # create constraints
2852 cib_dom, constraint_el = constraint.getCurrentConstraints(cib_dom)
2853 for location in resource_relocate_get_locations(cib_dom, resources):
2854 if not ("start_on_node" in location or "promote_on_node" in location):
2855 continue
2856 anything_changed = True
2857 print_to_stderr(resource_relocate_location_to_str(location))
2858 constraint_id = utils.find_unique_id(
2859 cib_dom,
2860 RESOURCE_RELOCATE_CONSTRAINT_PREFIX + location["id_for_constraint"],
2861 )
2862 new_constraint = cib_dom.createElement("rsc_location")
2863 new_constraint.setAttribute("id", constraint_id)
2864 new_constraint.setAttribute("rsc", location["id_for_constraint"])
2865 new_constraint.setAttribute("score", "INFINITY")
2866 if "promote_on_node" in location:
2867 new_constraint.setAttribute("node", location["promote_on_node"])
2868 new_constraint.setAttribute(
2869 "role",
2870 pacemaker.role.get_value_for_cib(
2871 const.PCMK_ROLE_PROMOTED_PRIMARY,
2872 utils.isCibVersionSatisfied(
2873 cib_dom, const.PCMK_NEW_ROLES_CIB_VERSION
2874 ),
2875 ),
2876 )
2877 elif "start_on_node" in location:
2878 new_constraint.setAttribute("node", location["start_on_node"])
2879 constraint_el.appendChild(new_constraint)
2880 if not anything_changed:
2881 return
2882 if not dry:
2883 utils.replace_cib_configuration(cib_dom)
2884
2885 # wait for resources to move
2886 print_to_stderr("\nWaiting for resources to move...\n")
2887 if not dry:
2888 output, retval = utils.run(["crm_resource", "--wait"])
2889 if retval != 0:
2890 was_error = True
2891 if retval == PACEMAKER_WAIT_TIMEOUT_STATUS:
2892 utils.err("waiting timeout", False)
2893 else:
2894 utils.err(output, False)
2895
2896 # remove constraints
2897 resource_relocate_clear(cib_dom)
2898 if not dry:
2899 utils.replace_cib_configuration(cib_dom)
2900
2901 if was_error:
2902 sys.exit(1)
2903
2904
2905 def resource_relocate_clear(cib_dom):
2906 """
2907 Commandline options: no options
2908 """
2909 for constraint_el in cib_dom.getElementsByTagName("constraints"):
2910 for location_el in constraint_el.getElementsByTagName("rsc_location"):
2911 location_id = location_el.getAttribute("id")
2912 if location_id.startswith(RESOURCE_RELOCATE_CONSTRAINT_PREFIX):
2913 print_to_stderr("Removing constraint {0}".format(location_id))
2914 location_el.parentNode.removeChild(location_el)
2915 return cib_dom
2916
2917
2918 def set_resource_utilization(resource_id: str, argv: Argv) -> None:
2919 """
2920 Commandline options:
2921 * -f - CIB file
2922 """
2923 cib = utils.get_cib_dom()
2924 resource_el = utils.dom_get_resource(cib, resource_id)
2925 if resource_el is None:
2926 utils.err("Unable to find a resource: {0}".format(resource_id))
2927 utils.dom_update_utilization(resource_el, KeyValueParser(argv).get_unique())
2928 utils.replace_cib_configuration(cib)
2929
2930
2931 def print_resource_utilization(resource_id: str) -> None:
2932 """
2933 Commandline options:
2934 * -f - CIB file
2935 """
2936 cib = utils.get_cib_dom()
2937 resource_el = utils.dom_get_resource(cib, resource_id)
2938 if resource_el is None:
2939 utils.err("Unable to find a resource: {0}".format(resource_id))
2940 utilization = utils.get_utilization_str(resource_el)
2941
2942 print("Resource Utilization:")
2943 print(" {0}: {1}".format(resource_id, utilization))
2944
2945
2946 def print_resources_utilization() -> None:
2947 """
2948 Commandline options:
2949 * -f - CIB file
2950 """
2951 cib = utils.get_cib_dom()
2952 utilization = {}
2953 for resource_el in cib.getElementsByTagName("primitive"):
2954 utilization_str = utils.get_utilization_str(resource_el)
2955 if utilization_str:
2956 utilization[resource_el.getAttribute("id")] = utilization_str
2957
2958 print("Resource Utilization:")
2959 for resource in sorted(utilization):
2960 print(" {0}: {1}".format(resource, utilization[resource]))
2961
2962
2963 # deprecated
2964 # This is used only by pcsd, will be removed in new architecture
2965 def get_resource_agent_info(
2966 lib: Any, argv: Argv, modifiers: InputModifiers
2967 ) -> None:
2968 """
2969 Options: no options
2970 """
2971 modifiers.ensure_only_supported()
2972 if len(argv) != 1:
2973 utils.err("One parameter expected")
2974 print(json.dumps(lib.resource_agent.describe_agent(argv[0])))
2975
2976
2977 def resource_bundle_create_cmd(
2978 lib: Any, argv: Argv, modifiers: InputModifiers
2979 ) -> None:
2980 """
2981 Options:
2982 * --force - allow unknown options
2983 * --disabled - create as a stopped bundle
2984 * --wait
2985 * -f - CIB file
2986 """
2987 modifiers.ensure_only_supported("--force", "--disabled", "--wait", "-f")
2988 if not argv:
2989 raise CmdLineInputError()
2990
2991 bundle_id = argv[0]
2992 parts = parse_bundle_create_options(argv[1:])
2993 lib.resource.bundle_create(
2994 bundle_id,
2995 parts.container_type,
2996 container_options=parts.container,
2997 network_options=parts.network,
2998 port_map=parts.port_map,
2999 storage_map=parts.storage_map,
3000 meta_attributes=parts.meta_attrs,
3001 force_options=modifiers.get("--force"),
3002 ensure_disabled=modifiers.get("--disabled"),
3003 wait=modifiers.get("--wait"),
3004 )
3005
3006
3007 def resource_bundle_reset_cmd(
3008 lib: Any, argv: Argv, modifiers: InputModifiers
3009 ) -> None:
3010 """
3011 Options:
3012 * --force - allow unknown options
3013 * --disabled - create as a stopped bundle
3014 * --wait
3015 * -f - CIB file
3016 """
3017 modifiers.ensure_only_supported("--force", "--disabled", "--wait", "-f")
3018 if not argv:
3019 raise CmdLineInputError()
3020
3021 bundle_id = argv[0]
3022 parts = parse_bundle_reset_options(argv[1:])
3023 lib.resource.bundle_reset(
3024 bundle_id,
3025 container_options=parts.container,
3026 network_options=parts.network,
3027 port_map=parts.port_map,
3028 storage_map=parts.storage_map,
3029 meta_attributes=parts.meta_attrs,
3030 force_options=modifiers.get("--force"),
3031 ensure_disabled=modifiers.get("--disabled"),
3032 wait=modifiers.get("--wait"),
3033 )
3034
3035
3036 def resource_bundle_update_cmd(
3037 lib: Any, argv: Argv, modifiers: InputModifiers
3038 ) -> None:
3039 """
3040 Options:
3041 * --force - allow unknown options
3042 * --wait
3043 * -f - CIB file
3044 """
3045 modifiers.ensure_only_supported("--force", "--wait", "-f")
3046 if not argv:
3047 raise CmdLineInputError()
3048
3049 bundle_id = argv[0]
3050 parts = parse_bundle_update_options(argv[1:])
3051 lib.resource.bundle_update(
3052 bundle_id,
3053 container_options=parts.container,
3054 network_options=parts.network,
3055 port_map_add=parts.port_map_add,
3056 port_map_remove=parts.port_map_remove,
3057 storage_map_add=parts.storage_map_add,
3058 storage_map_remove=parts.storage_map_remove,
3059 meta_attributes=parts.meta_attrs,
3060 force_options=modifiers.get("--force"),
3061 wait=modifiers.get("--wait"),
3062 )
3063