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