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