├── .gitarchivever ├── .gitattributes ├── .github └── CODEOWNERS ├── .gitignore ├── .gitlab-ci.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── COPYING ├── MANIFEST.in ├── Makefile.am ├── README.md ├── RELEASE.md ├── SECURITY.md ├── autogen.sh ├── configure.ac ├── data ├── Makefile.am ├── ocf-1.0.rng └── ocf-1.1.rng ├── dev_requirements.txt ├── m4 ├── ac_compare_versions.m4 ├── ac_pip_module.m4 ├── ac_ruby_gem.m4 └── ax_prog_date.m4 ├── make ├── git-version-gen ├── gitlog-to-changelog └── release.mk ├── mypy.ini ├── pcs.pc.in ├── pcs ├── COPYING ├── Makefile.am ├── __init__.py ├── acl.py ├── alert.py ├── app.py ├── bash_completion │ └── pcs ├── cli │ ├── __init__.py │ ├── alert │ │ ├── __init__.py │ │ ├── command.py │ │ └── output.py │ ├── booth │ │ ├── __init__.py │ │ ├── command.py │ │ └── env.py │ ├── cluster │ │ ├── __init__.py │ │ └── command.py │ ├── cluster_property │ │ ├── __init__.py │ │ ├── command.py │ │ └── output.py │ ├── common │ │ ├── __init__.py │ │ ├── completion.py │ │ ├── env_cli.py │ │ ├── errors.py │ │ ├── lib_wrapper.py │ │ ├── middleware.py │ │ ├── output.py │ │ ├── parse_args.py │ │ ├── printable_tree.py │ │ ├── routing.py │ │ └── tools.py │ ├── constraint │ │ ├── __init__.py │ │ ├── command.py │ │ ├── location │ │ │ ├── __init__.py │ │ │ └── command.py │ │ ├── output │ │ │ ├── __init__.py │ │ │ ├── all.py │ │ │ ├── colocation.py │ │ │ ├── location.py │ │ │ ├── order.py │ │ │ ├── set.py │ │ │ └── ticket.py │ │ └── parse_args.py │ ├── constraint_colocation │ │ ├── __init__.py │ │ └── command.py │ ├── constraint_order │ │ ├── __init__.py │ │ └── command.py │ ├── constraint_ticket │ │ ├── __init__.py │ │ ├── command.py │ │ └── parse_args.py │ ├── dr.py │ ├── fencing_topology.py │ ├── file │ │ ├── __init__.py │ │ └── metadata.py │ ├── node │ │ ├── __init__.py │ │ ├── command.py │ │ └── output.py │ ├── nvset.py │ ├── query │ │ ├── __init__.py │ │ └── resource.py │ ├── reports │ │ ├── __init__.py │ │ ├── messages.py │ │ ├── output.py │ │ ├── preprocessor.py │ │ └── processor.py │ ├── resource │ │ ├── __init__.py │ │ ├── command.py │ │ ├── common.py │ │ ├── output.py │ │ ├── parse_args.py │ │ └── relations.py │ ├── resource_agent.py │ ├── routing │ │ ├── __init__.py │ │ ├── acl.py │ │ ├── alert.py │ │ ├── booth.py │ │ ├── client.py │ │ ├── cluster.py │ │ ├── config.py │ │ ├── constraint.py │ │ ├── dr.py │ │ ├── host.py │ │ ├── node.py │ │ ├── pcsd.py │ │ ├── prop.py │ │ ├── qdevice.py │ │ ├── quorum.py │ │ ├── resource.py │ │ ├── resource_stonith_common.py │ │ ├── status.py │ │ ├── stonith.py │ │ └── tag.py │ ├── rule.py │ ├── status │ │ ├── __init__.py │ │ └── command.py │ ├── stonith │ │ ├── __init__.py │ │ ├── command.py │ │ ├── common.py │ │ └── levels │ │ │ ├── __init__.py │ │ │ ├── command.py │ │ │ └── output.py │ └── tag │ │ ├── __init__.py │ │ ├── command.py │ │ └── output.py ├── client.py ├── cluster.py ├── common │ ├── __init__.py │ ├── async_tasks │ │ ├── __init__.py │ │ ├── dto.py │ │ └── types.py │ ├── capabilities.py │ ├── cfgsync_dto.py │ ├── communication │ │ ├── __init__.py │ │ ├── const.py │ │ ├── dto.py │ │ ├── logger.py │ │ └── types.py │ ├── const.py │ ├── corosync_conf.py │ ├── dr.py │ ├── fencing_topology.py │ ├── file.py │ ├── file_type_codes.py │ ├── host.py │ ├── interface │ │ ├── __init__.py │ │ └── dto.py │ ├── node_communicator.py │ ├── pacemaker │ │ ├── __init__.py │ │ ├── alert.py │ │ ├── cluster_property.py │ │ ├── constraint │ │ │ ├── __init__.py │ │ │ ├── all.py │ │ │ ├── colocation.py │ │ │ ├── location.py │ │ │ ├── order.py │ │ │ ├── set.py │ │ │ └── ticket.py │ │ ├── defaults.py │ │ ├── fencing_topology.py │ │ ├── node.py │ │ ├── nvset.py │ │ ├── resource │ │ │ ├── __init__.py │ │ │ ├── bundle.py │ │ │ ├── clone.py │ │ │ ├── group.py │ │ │ ├── list.py │ │ │ ├── operations.py │ │ │ ├── primitive.py │ │ │ └── relations.py │ │ ├── role.py │ │ ├── rule.py │ │ ├── tag.py │ │ ├── tools.py │ │ └── types.py │ ├── pcs_pycurl.py │ ├── permissions │ │ ├── __init__.py │ │ └── types.py │ ├── reports │ │ ├── __init__.py │ │ ├── codes.py │ │ ├── const.py │ │ ├── conversions.py │ │ ├── deprecated_codes.py │ │ ├── dto.py │ │ ├── item.py │ │ ├── messages.py │ │ ├── processor.py │ │ ├── types.py │ │ └── utils.py │ ├── resource_agent │ │ ├── __init__.py │ │ ├── const.py │ │ └── dto.py │ ├── resource_status.py │ ├── services │ │ ├── __init__.py │ │ ├── common.py │ │ ├── drivers │ │ │ ├── __init__.py │ │ │ ├── systemd.py │ │ │ └── sysvinit_rhel.py │ │ ├── errors.py │ │ ├── interfaces │ │ │ ├── __init__.py │ │ │ ├── executor.py │ │ │ └── manager.py │ │ └── types.py │ ├── services_dto.py │ ├── ssl.py │ ├── status_dto.py │ ├── str_tools.py │ ├── tools.py │ ├── types.py │ └── validate.py ├── config.py ├── constraint.py ├── daemon │ ├── __init__.py │ ├── app │ │ ├── __init__.py │ │ ├── api_v0.py │ │ ├── api_v1.py │ │ ├── api_v2.py │ │ ├── auth.py │ │ ├── capabilities.py │ │ ├── common.py │ │ ├── sinatra_common.py │ │ ├── sinatra_remote.py │ │ ├── sinatra_ui.py │ │ ├── ui_common.py │ │ └── webui │ │ │ ├── __init__.py │ │ │ ├── auth.py │ │ │ ├── core.py │ │ │ ├── session.py │ │ │ └── sinatra_ui.py │ ├── async_tasks │ │ ├── __init__.py │ │ ├── scheduler.py │ │ ├── task.py │ │ ├── types.py │ │ └── worker │ │ │ ├── __init__.py │ │ │ ├── command_mapping.py │ │ │ ├── communicator.py │ │ │ ├── executor.py │ │ │ ├── logging.py │ │ │ ├── report_processor.py │ │ │ └── types.py │ ├── cfgsync.py │ ├── env.py │ ├── http_server.py │ ├── log.py │ ├── ruby_pcsd.py │ ├── run.py │ ├── ssl.py │ └── systemd.py ├── entry_points │ ├── __init__.py │ ├── cli.py │ ├── common.py │ ├── daemon.py │ ├── internal.py │ └── snmp_agent.py ├── host.py ├── lib │ ├── __init__.py │ ├── auth │ │ ├── __init__.py │ │ ├── config │ │ │ ├── __init__.py │ │ │ ├── exporter.py │ │ │ ├── facade.py │ │ │ ├── parser.py │ │ │ └── types.py │ │ ├── const.py │ │ ├── pam.py │ │ ├── provider.py │ │ ├── tools.py │ │ └── types.py │ ├── booth │ │ ├── __init__.py │ │ ├── cib.py │ │ ├── config_facade.py │ │ ├── config_files.py │ │ ├── config_parser.py │ │ ├── config_validators.py │ │ ├── constants.py │ │ ├── env.py │ │ ├── resource.py │ │ ├── status.py │ │ └── sync.py │ ├── cfgsync │ │ ├── __init__.py │ │ ├── config │ │ │ ├── __init__.py │ │ │ └── facade.py │ │ ├── const.py │ │ └── fetcher.py │ ├── cib │ │ ├── __init__.py │ │ ├── acl.py │ │ ├── alert.py │ │ ├── const.py │ │ ├── constraint │ │ │ ├── __init__.py │ │ │ ├── colocation.py │ │ │ ├── common.py │ │ │ ├── constraint.py │ │ │ ├── location.py │ │ │ ├── order.py │ │ │ ├── resource_set.py │ │ │ └── ticket.py │ │ ├── fencing_topology.py │ │ ├── node.py │ │ ├── nvpair.py │ │ ├── nvpair_multi.py │ │ ├── remove_elements.py │ │ ├── resource │ │ │ ├── __init__.py │ │ │ ├── agent.py │ │ │ ├── bundle.py │ │ │ ├── clone.py │ │ │ ├── common.py │ │ │ ├── const.py │ │ │ ├── group.py │ │ │ ├── guest_node.py │ │ │ ├── hierarchy.py │ │ │ ├── operations.py │ │ │ ├── primitive.py │ │ │ ├── relations.py │ │ │ ├── remote_node.py │ │ │ ├── stonith.py │ │ │ ├── types.py │ │ │ └── validations.py │ │ ├── rule │ │ │ ├── __init__.py │ │ │ ├── cib_to_dto.py │ │ │ ├── cib_to_str.py │ │ │ ├── expression_part.py │ │ │ ├── in_effect.py │ │ │ ├── parsed_to_cib.py │ │ │ ├── parser.py │ │ │ └── validator.py │ │ ├── sections.py │ │ ├── status.py │ │ ├── tag.py │ │ └── tools.py │ ├── cluster_property.py │ ├── commands │ │ ├── __init__.py │ │ ├── acl.py │ │ ├── alert.py │ │ ├── booth.py │ │ ├── cfgsync.py │ │ ├── cib.py │ │ ├── cib_options.py │ │ ├── cluster.py │ │ ├── cluster_property.py │ │ ├── constraint │ │ │ ├── __init__.py │ │ │ ├── colocation.py │ │ │ ├── common.py │ │ │ ├── location.py │ │ │ ├── order.py │ │ │ └── ticket.py │ │ ├── dr.py │ │ ├── fencing_topology.py │ │ ├── node.py │ │ ├── pcsd.py │ │ ├── qdevice.py │ │ ├── quorum.py │ │ ├── remote_node.py │ │ ├── resource.py │ │ ├── resource_agent.py │ │ ├── sbd.py │ │ ├── scsi.py │ │ ├── services.py │ │ ├── status.py │ │ ├── stonith.py │ │ ├── stonith_agent.py │ │ └── tag.py │ ├── communication │ │ ├── __init__.py │ │ ├── booth.py │ │ ├── cfgsync.py │ │ ├── cluster.py │ │ ├── corosync.py │ │ ├── nodes.py │ │ ├── qdevice.py │ │ ├── qdevice_net.py │ │ ├── sbd.py │ │ ├── scsi.py │ │ ├── status.py │ │ └── tools.py │ ├── corosync │ │ ├── __init__.py │ │ ├── config_facade.py │ │ ├── config_parser.py │ │ ├── config_validators.py │ │ ├── constants.py │ │ ├── live.py │ │ ├── node.py │ │ ├── qdevice_client.py │ │ └── qdevice_net.py │ ├── dr │ │ ├── __init__.py │ │ ├── config │ │ │ ├── __init__.py │ │ │ └── facade.py │ │ └── env.py │ ├── env.py │ ├── errors.py │ ├── exchange_formats.md │ ├── external.py │ ├── file │ │ ├── __init__.py │ │ ├── instance.py │ │ ├── json.py │ │ ├── metadata.py │ │ ├── raw_file.py │ │ └── toolbox.py │ ├── host │ │ ├── __init__.py │ │ └── config │ │ │ ├── __init__.py │ │ │ ├── exporter.py │ │ │ ├── facade.py │ │ │ ├── parser.py │ │ │ └── types.py │ ├── interface │ │ ├── __init__.py │ │ └── config.py │ ├── node.py │ ├── node_communication.py │ ├── node_communication_format.py │ ├── pacemaker │ │ ├── __init__.py │ │ ├── api_result.py │ │ ├── live.py │ │ ├── simulate.py │ │ ├── state.py │ │ ├── status.py │ │ └── values.py │ ├── permissions │ │ ├── __init__.py │ │ ├── checker.py │ │ └── config │ │ │ ├── __init__.py │ │ │ ├── exporter.py │ │ │ ├── facade.py │ │ │ ├── parser.py │ │ │ └── types.py │ ├── resource_agent │ │ ├── __init__.py │ │ ├── const.py │ │ ├── error.py │ │ ├── facade.py │ │ ├── list.py │ │ ├── name.py │ │ ├── ocf_transform.py │ │ ├── pcs_transform.py │ │ ├── types.py │ │ └── xml.py │ ├── sbd.py │ ├── sbd_stonith.py │ ├── services.py │ ├── tools.py │ ├── validate.py │ └── xml_tools.py ├── node.py ├── pcs.8.in ├── pcs.in ├── pcs_internal.in ├── pcs_internal.py ├── pcsd.py ├── py.typed ├── qdevice.py ├── quorum.py ├── resource.py ├── settings.py.in ├── snmp │ ├── __init__.py │ ├── agentx │ │ ├── __init__.py │ │ ├── types.py │ │ └── updater.py │ ├── conf │ │ └── pcs_snmp_agent │ ├── mibs │ │ ├── PCMK-PCS-MIB.txt │ │ └── PCMK-PCS-V1-MIB.txt │ ├── pcs_snmp_agent.8.in │ ├── pcs_snmp_agent.in │ ├── pcs_snmp_agent.py │ ├── pcs_snmp_agent.service.in │ ├── settings.py.in │ └── updaters │ │ ├── __init__.py │ │ └── v1.py ├── status.py ├── stonith.py ├── usage.py └── utils.py ├── pcs_test ├── Makefile.am ├── __init__.py ├── api_v2_client.in ├── api_v2_client.py ├── curl_test.py ├── pcs_for_tests.in ├── resources │ ├── capabilities.xml │ ├── cib-all.xml │ ├── cib-empty-1.2.xml │ ├── cib-empty-3.1.xml │ ├── cib-empty-3.2.xml │ ├── cib-empty-3.3.xml │ ├── cib-empty-3.4.xml │ ├── cib-empty-3.5.xml │ ├── cib-empty-3.7.xml │ ├── cib-empty-3.9.xml │ ├── cib-empty-with3nodes.xml │ ├── cib-empty-withnodes.xml │ ├── cib-empty.xml │ ├── cib-fencing-levels.xml │ ├── cib-large.xml │ ├── cib-largefile.xml │ ├── cib-property.xml │ ├── cib-remote.xml │ ├── cib-resources.xml │ ├── cib-rule-several-times-in-constraint.xml │ ├── cib-rule-with-spaces-in-date.xml │ ├── cib-status.xml │ ├── cib-tags.xml │ ├── cib-unexportable-constraints.xml │ ├── cib-unsupported-stonith-config.xml │ ├── cluster-options_metadata.xml │ ├── commands-alert │ ├── constraint-commands │ ├── corosync-3nodes-qdevice-heuristics.conf │ ├── corosync-3nodes-qdevice.conf │ ├── corosync-3nodes.conf │ ├── corosync-no-node-names.conf │ ├── corosync-qdevice.conf │ ├── corosync-some-node-names.conf │ ├── corosync.conf │ ├── crm_mon.all_resources.xml │ ├── crm_mon.minimal.xml │ ├── fenced_metadata.xml │ ├── known-hosts │ ├── node-commands │ ├── pcmk_api_rng │ │ ├── api-result.rng │ │ ├── crm_attribute-2.36.rng │ │ ├── crm_mon-2.29.rng │ │ ├── digests-2.9.rng │ │ ├── failure-2.8.rng │ │ ├── fence-event-2.15.rng │ │ ├── node-attrs-2.8.rng │ │ ├── node-history-2.12.rng │ │ ├── nodes-2.29.rng │ │ ├── ocf-ra-1.1.rng │ │ ├── options-2.36.rng │ │ ├── pacemakerd-health-2.25.rng │ │ ├── resources-2.29.rng │ │ └── status-2.0.rng │ ├── resource-commands │ ├── resource_agent_nagios_check_fping.xml │ ├── resource_agent_ocf_heartbeat_dummy.xml │ ├── resource_agent_ocf_heartbeat_dummy_insane_action.xml │ ├── resource_agent_ocf_heartbeat_dummy_utf8.xml │ ├── resource_agent_ocf_heartbeat_ipaddr2.xml │ ├── resource_agent_ocf_pacemaker_booth-site.xml │ ├── resource_agent_ocf_pacemaker_dummy.xml │ ├── resource_agent_ocf_pacemaker_remote.xml │ ├── resource_agent_ocf_pacemaker_stateful_ocf_1.0.xml │ ├── resource_agent_ocf_pacemaker_stateful_ocf_1.1.xml │ ├── resource_agent_systemd_chronyd.xml │ ├── stonith_agent_fence_custom_actions.xml │ ├── stonith_agent_fence_simple.xml │ ├── stonith_agent_fence_unfencing.xml │ ├── transitions01.xml │ └── transitions02.xml ├── settings.py.in ├── smoke.sh.in ├── suite.in ├── suite.py ├── tier0 │ ├── __init__.py │ ├── cli │ │ ├── __init__.py │ │ ├── alert │ │ │ ├── __init__.py │ │ │ └── test_output.py │ │ ├── booth │ │ │ ├── __init__.py │ │ │ └── test_env.py │ │ ├── cluster │ │ │ ├── __init__.py │ │ │ └── test_command.py │ │ ├── cluster_property │ │ │ ├── __init__.py │ │ │ ├── test_command.py │ │ │ └── test_output.py │ │ ├── common │ │ │ ├── __init__.py │ │ │ ├── test_completion.py │ │ │ ├── test_lib_wrapper.py │ │ │ ├── test_middleware.py │ │ │ ├── test_parse_args.py │ │ │ ├── test_printable_tree.py │ │ │ └── test_tools.py │ │ ├── constraint │ │ │ ├── __init__.py │ │ │ ├── location │ │ │ │ ├── __init__.py │ │ │ │ └── test_command.py │ │ │ ├── output │ │ │ │ └── test_all.py │ │ │ ├── test_command.py │ │ │ └── test_parse_args.py │ │ ├── constraint_colocation │ │ │ ├── __init__.py │ │ │ └── test_command.py │ │ ├── constraint_ticket │ │ │ ├── __init__.py │ │ │ ├── test_command.py │ │ │ └── test_parse_args.py │ │ ├── node │ │ │ ├── __init__.py │ │ │ ├── test_command.py │ │ │ └── test_output.py │ │ ├── query │ │ │ ├── __init__.py │ │ │ └── test_resource.py │ │ ├── reports │ │ │ ├── __init__.py │ │ │ ├── test_messages.py │ │ │ └── test_preprocessor.py │ │ ├── resource │ │ │ ├── __init__.py │ │ │ ├── test_common.py │ │ │ ├── test_defaults.py │ │ │ ├── test_parse_args.py │ │ │ ├── test_relations.py │ │ │ ├── test_remove.py │ │ │ └── test_update.py │ │ ├── stonith │ │ │ ├── __init__.py │ │ │ ├── levels │ │ │ │ ├── __init__.py │ │ │ │ └── test_output.py │ │ │ └── test_update.py │ │ ├── tag │ │ │ ├── __init__.py │ │ │ └── test_command.py │ │ ├── test_booth.py │ │ ├── test_cluster.py │ │ ├── test_dr.py │ │ ├── test_nvset.py │ │ ├── test_quorum.py │ │ ├── test_resource.py │ │ ├── test_rule.py │ │ ├── test_status.py │ │ └── test_stonith.py │ ├── common │ │ ├── __init__.py │ │ ├── communication │ │ │ ├── __init__.py │ │ │ └── test_logger.py │ │ ├── interface │ │ │ ├── __init__.py │ │ │ └── test_dto.py │ │ ├── pacemaker │ │ │ ├── constraint │ │ │ │ ├── __init__.py │ │ │ │ └── all.py │ │ │ └── resource │ │ │ │ ├── __init__.py │ │ │ │ └── list.py │ │ ├── reports │ │ │ ├── __init__.py │ │ │ ├── test_item.py │ │ │ ├── test_messages.py │ │ │ └── test_processor.py │ │ ├── services │ │ │ ├── __init__.py │ │ │ └── drivers │ │ │ │ ├── __init__.py │ │ │ │ ├── test_systemd.py │ │ │ │ └── test_sysvinit_rhel.py │ │ ├── test_capabilities.py │ │ ├── test_file.py │ │ ├── test_host.py │ │ ├── test_node_communicator.py │ │ ├── test_resource_status.py │ │ ├── test_str_tools.py │ │ ├── test_tools.py │ │ ├── test_tools_xml_fromstring.py │ │ └── test_validate.py │ ├── daemon │ │ ├── __init__.py │ │ ├── app │ │ │ ├── __init__.py │ │ │ ├── fixtures_app.py │ │ │ ├── fixtures_app_webui.py │ │ │ ├── test_api_v0.py │ │ │ ├── test_api_v1.py │ │ │ ├── test_app_auth.py │ │ │ ├── test_app_gui.py │ │ │ ├── test_app_redirect.py │ │ │ ├── test_app_remote.py │ │ │ └── test_app_spa.py │ │ ├── async_tasks │ │ │ ├── __init__.py │ │ │ ├── dummy_commands.py │ │ │ ├── helpers.py │ │ │ ├── test_command_mapping.py │ │ │ ├── test_integration.py │ │ │ ├── test_scheduler.py │ │ │ ├── test_task.py │ │ │ └── test_worker.py │ │ ├── test_cfgsync.py │ │ ├── test_env.py │ │ ├── test_http_server.py │ │ ├── test_ruby_pcsd.py │ │ ├── test_session.py │ │ └── test_ssl.py │ ├── lib │ │ ├── __init__.py │ │ ├── auth │ │ │ ├── __init__.py │ │ │ ├── config │ │ │ │ ├── __init__.py │ │ │ │ ├── test_facade.py │ │ │ │ └── test_parser.py │ │ │ └── test_provider.py │ │ ├── booth │ │ │ ├── __init__.py │ │ │ ├── test_cib.py │ │ │ ├── test_config_facade.py │ │ │ ├── test_config_files.py │ │ │ ├── test_config_parser.py │ │ │ ├── test_config_validators.py │ │ │ ├── test_env.py │ │ │ ├── test_resource.py │ │ │ ├── test_status.py │ │ │ └── test_sync.py │ │ ├── cfgsync │ │ │ ├── __init__.py │ │ │ ├── config │ │ │ │ ├── __init__.py │ │ │ │ └── test_facade.py │ │ │ └── test_fetcher.py │ │ ├── cib │ │ │ ├── __init__.py │ │ │ ├── constraint │ │ │ │ ├── __init__.py │ │ │ │ └── test_common.py │ │ │ ├── resource │ │ │ │ ├── __init__.py │ │ │ │ ├── test_agent.py │ │ │ │ ├── test_hierarchy.py │ │ │ │ ├── test_primitive_validate.py │ │ │ │ ├── test_relations.py │ │ │ │ ├── test_stonith.py │ │ │ │ └── test_validations.py │ │ │ ├── rule │ │ │ │ ├── __init__.py │ │ │ │ ├── test_cib_to_dto.py │ │ │ │ ├── test_cib_to_str.py │ │ │ │ ├── test_parsed_to_cib.py │ │ │ │ ├── test_parser.py │ │ │ │ └── test_validator.py │ │ │ ├── test_acl.py │ │ │ ├── test_alert.py │ │ │ ├── test_constraint.py │ │ │ ├── test_constraint_colocation.py │ │ │ ├── test_constraint_location.py │ │ │ ├── test_constraint_order.py │ │ │ ├── test_constraint_ticket.py │ │ │ ├── test_fencing_topology.py │ │ │ ├── test_node.py │ │ │ ├── test_nvpair.py │ │ │ ├── test_nvpair_multi.py │ │ │ ├── test_remove_elements.py │ │ │ ├── test_resource_bundle.py │ │ │ ├── test_resource_clone.py │ │ │ ├── test_resource_common.py │ │ │ ├── test_resource_group.py │ │ │ ├── test_resource_guest_node.py │ │ │ ├── test_resource_operations.py │ │ │ ├── test_resource_primitive.py │ │ │ ├── test_resource_remote_node.py │ │ │ ├── test_resource_set.py │ │ │ ├── test_sections.py │ │ │ ├── test_status.py │ │ │ ├── test_tag.py │ │ │ └── test_tools.py │ │ ├── commands │ │ │ ├── __init__.py │ │ │ ├── cluster │ │ │ │ ├── __init__.py │ │ │ │ ├── common.py │ │ │ │ ├── test_add_link.py │ │ │ │ ├── test_add_nodes.py │ │ │ │ ├── test_add_nodes_validation.py │ │ │ │ ├── test_authkey_corosync.py │ │ │ │ ├── test_config_update.py │ │ │ │ ├── test_config_uuid.py │ │ │ │ ├── test_get_corosync_conf_struct.py │ │ │ │ ├── test_node_clear.py │ │ │ │ ├── test_remove_links.py │ │ │ │ ├── test_remove_nodes.py │ │ │ │ ├── test_remove_nodes_from_cib.py │ │ │ │ ├── test_rename.py │ │ │ │ ├── test_setup.py │ │ │ │ ├── test_update_link.py │ │ │ │ └── test_verify.py │ │ │ ├── constraint │ │ │ │ ├── __init__.py │ │ │ │ └── test_location.py │ │ │ ├── dr │ │ │ │ ├── __init__.py │ │ │ │ ├── test_destroy.py │ │ │ │ ├── test_get_config.py │ │ │ │ ├── test_set_recovery_site.py │ │ │ │ └── test_status.py │ │ │ ├── remote_node │ │ │ │ ├── __init__.py │ │ │ │ ├── fixtures_add.py │ │ │ │ ├── fixtures_remove.py │ │ │ │ ├── test_node_add_guest.py │ │ │ │ ├── test_node_add_remote.py │ │ │ │ ├── test_node_remove_guest.py │ │ │ │ └── test_node_remove_remote.py │ │ │ ├── resource │ │ │ │ ├── __init__.py │ │ │ │ ├── bundle_common.py │ │ │ │ ├── test_bundle_create.py │ │ │ │ ├── test_bundle_reset.py │ │ │ │ ├── test_bundle_update.py │ │ │ │ ├── test_failcounts.py │ │ │ │ ├── test_get_configured_resources.py │ │ │ │ ├── test_group_add.py │ │ │ │ ├── test_resource_create.py │ │ │ │ ├── test_resource_enable_disable.py │ │ │ │ ├── test_resource_manage_unmanage.py │ │ │ │ ├── test_resource_move_autoclean.py │ │ │ │ ├── test_resource_move_ban.py │ │ │ │ ├── test_resource_relations.py │ │ │ │ ├── test_resource_update.py │ │ │ │ └── test_restart.py │ │ │ ├── sbd │ │ │ │ ├── __init__.py │ │ │ │ ├── test_disable_sbd.py │ │ │ │ ├── test_enable_sbd.py │ │ │ │ ├── test_get_cluster_sbd_config.py │ │ │ │ ├── test_get_cluster_sbd_status.py │ │ │ │ └── test_watchdog_list.py │ │ │ ├── tag │ │ │ │ ├── __init__.py │ │ │ │ ├── tag_common.py │ │ │ │ ├── test_tag_config.py │ │ │ │ ├── test_tag_create.py │ │ │ │ ├── test_tag_remove.py │ │ │ │ └── test_tag_update.py │ │ │ ├── test_acl.py │ │ │ ├── test_alert.py │ │ │ ├── test_booth.py │ │ │ ├── test_cfgsync.py │ │ │ ├── test_cib.py │ │ │ ├── test_cib_options.py │ │ │ ├── test_cluster_property.py │ │ │ ├── test_constraint_common.py │ │ │ ├── test_constraint_order.py │ │ │ ├── test_fencing_topology.py │ │ │ ├── test_node.py │ │ │ ├── test_pcsd.py │ │ │ ├── test_qdevice.py │ │ │ ├── test_quorum.py │ │ │ ├── test_resource_agent.py │ │ │ ├── test_sbd.py │ │ │ ├── test_scsi.py │ │ │ ├── test_status.py │ │ │ ├── test_stonith.py │ │ │ ├── test_stonith_agent.py │ │ │ ├── test_stonith_history.py │ │ │ ├── test_stonith_update_scsi_devices.py │ │ │ └── test_ticket.py │ │ ├── communication │ │ │ ├── __init__.py │ │ │ ├── test_booth.py │ │ │ ├── test_cfgsync.py │ │ │ ├── test_cluster.py │ │ │ ├── test_corosync.py │ │ │ ├── test_nodes.py │ │ │ ├── test_qdevice.py │ │ │ ├── test_qdevice_net.py │ │ │ ├── test_sbd.py │ │ │ ├── test_scsi.py │ │ │ └── test_status.py │ │ ├── corosync │ │ │ ├── __init__.py │ │ │ ├── test_config_facade_links.py │ │ │ ├── test_config_facade_misc.py │ │ │ ├── test_config_facade_nodes.py │ │ │ ├── test_config_facade_quorum.py │ │ │ ├── test_config_facade_quorum_device.py │ │ │ ├── test_config_facade_transport.py │ │ │ ├── test_config_parser.py │ │ │ ├── test_config_validators_common.py │ │ │ ├── test_config_validators_create.py │ │ │ ├── test_config_validators_links.py │ │ │ ├── test_config_validators_nodes.py │ │ │ ├── test_config_validators_quorum.py │ │ │ ├── test_config_validators_rename_cluster.py │ │ │ ├── test_config_validators_update.py │ │ │ ├── test_live.py │ │ │ ├── test_node.py │ │ │ ├── test_qdevice_client.py │ │ │ └── test_qdevice_net.py │ │ ├── dr │ │ │ ├── __init__.py │ │ │ └── test_facade.py │ │ ├── file │ │ │ ├── test_instance.py │ │ │ ├── test_raw_file.py │ │ │ └── test_toolbox.py │ │ ├── host │ │ │ ├── __init__.py │ │ │ └── config │ │ │ │ ├── __init__.py │ │ │ │ ├── test_facade.py │ │ │ │ └── test_parser.py │ │ ├── misc.py │ │ ├── pacemaker │ │ │ ├── __init__.py │ │ │ ├── test_api_result.py │ │ │ ├── test_live.py │ │ │ ├── test_simulate.py │ │ │ ├── test_state.py │ │ │ ├── test_status.py │ │ │ └── test_values.py │ │ ├── permissions │ │ │ ├── __init__.py │ │ │ ├── config │ │ │ │ ├── __init__.py │ │ │ │ ├── test_facade.py │ │ │ │ └── test_parser.py │ │ │ └── test_checker.py │ │ ├── resource_agent │ │ │ ├── __init__.py │ │ │ ├── test_facade.py │ │ │ ├── test_list.py │ │ │ ├── test_name.py │ │ │ ├── test_ocf_transform.py │ │ │ ├── test_pcs_transform.py │ │ │ ├── test_types.py │ │ │ └── test_xml.py │ │ ├── test_cluster_property.py │ │ ├── test_env.py │ │ ├── test_env_cib.py │ │ ├── test_env_corosync.py │ │ ├── test_external.py │ │ ├── test_node_communication.py │ │ ├── test_node_communication_format.py │ │ ├── test_sbd.py │ │ ├── test_tools.py │ │ ├── test_validate.py │ │ └── test_xml_tools.py │ ├── test_capabilities.py │ ├── test_host.py │ └── test_lint.py ├── tier1 │ ├── __init__.py │ ├── cib_resource │ │ ├── __init__.py │ │ ├── common.py │ │ ├── test_bundle.py │ │ ├── test_clone_unclone.py │ │ ├── test_create.py │ │ ├── test_enable_disable.py │ │ ├── test_group_ungroup.py │ │ ├── test_manage_unmanage.py │ │ ├── test_move.py │ │ ├── test_operation_add.py │ │ ├── test_resource_stonith_is_forbidden.py │ │ ├── test_stonith_create.py │ │ ├── test_stonith_enable_disable.py │ │ ├── test_stonith_resource_is_forbidden.py │ │ └── test_update.py │ ├── cluster │ │ ├── __init__.py │ │ ├── common.py │ │ ├── test_cib_push.py │ │ ├── test_cluster_rename.py │ │ ├── test_config_show.py │ │ ├── test_config_update.py │ │ ├── test_config_uuid.py │ │ └── test_setup_local.py │ ├── constraint │ │ ├── __init__.py │ │ ├── test_config.py │ │ └── test_location.py │ ├── legacy │ │ ├── __init__.py │ │ ├── common.py │ │ ├── test_acl.py │ │ ├── test_alert.py │ │ ├── test_cluster.py │ │ ├── test_constraints.py │ │ ├── test_node.py │ │ ├── test_resource.py │ │ ├── test_stonith.py │ │ └── test_utils.py │ ├── resource │ │ ├── __init__.py │ │ ├── test_config.py │ │ └── test_remove.py │ ├── stonith │ │ ├── __init__.py │ │ ├── levels │ │ │ ├── __init__.py │ │ │ └── test_config.py │ │ ├── test_config.py │ │ └── test_remove.py │ ├── test_alert.py │ ├── test_booth.py │ ├── test_cib_options.py │ ├── test_cluster_pcmk_remote.py │ ├── test_cluster_property.py │ ├── test_misc.py │ ├── test_node.py │ ├── test_quorum.py │ ├── test_status.py │ ├── test_status_query_resource.py │ └── test_tag.py └── tools │ ├── __init__.py │ ├── assertions.py │ ├── bin_mock │ ├── __init__.py │ └── pcmk │ │ ├── crm_resource.d │ │ ├── list_agents_ocf__heartbeat │ │ ├── list_agents_ocf__pacemaker │ │ ├── list_agents_ocf__pcsmock │ │ ├── list_ocf_providers │ │ ├── list_standards │ │ ├── lsb__pcsmock_metadata.xml │ │ ├── ocf__heartbeat__pcsMock_metadata.xml │ │ ├── ocf__pacemaker__pcsMock_metadata.xml │ │ ├── ocf__pacemaker__remote_metadata.xml │ │ ├── ocf__pcsmock__CamelCase_metadata.xml │ │ ├── ocf__pcsmock__action_method_metadata.xml │ │ ├── ocf__pcsmock__duplicate_monitor_metadata.xml │ │ ├── ocf__pcsmock__minimal_metadata.xml │ │ ├── ocf__pcsmock__params_metadata.xml │ │ ├── ocf__pcsmock__stateful_metadata.xml │ │ ├── ocf__pcsmock__unique_metadata.xml │ │ ├── stonith__fence_pcsmock_action_metadata.xml │ │ ├── stonith__fence_pcsmock_method_metadata.xml │ │ ├── stonith__fence_pcsmock_minimal_metadata.xml │ │ ├── stonith__fence_pcsmock_params_metadata.xml │ │ ├── stonith__fence_pcsmock_unfencing_metadata.xml │ │ ├── stonith__fence_sbd_metadata.xml │ │ ├── systemd__pcsmock@a__b_metadata.xml │ │ └── systemd__pcsmock_metadata.xml │ │ ├── crm_resource.in │ │ ├── crm_resource_mock.py │ │ ├── pacemaker-fenced.in │ │ ├── pacemaker_metadata.d │ │ ├── cluster_options.xml │ │ └── pacemaker_fenced.xml │ │ ├── pacemaker_metadata.py │ │ ├── stonith_admin.in │ │ └── stonith_admin_mock.py │ ├── case_analysis.py │ ├── check │ ├── __init__.py │ └── test_misc.py │ ├── cib.py │ ├── color_text_runner │ ├── __init__.py │ ├── format.py │ ├── result.py │ └── writer.py │ ├── command_env │ ├── __init__.py │ ├── assistant.py │ ├── calls.py │ ├── config.py │ ├── config_corosync_conf.py │ ├── config_env.py │ ├── config_fs.py │ ├── config_http.py │ ├── config_http_booth.py │ ├── config_http_corosync.py │ ├── config_http_files.py │ ├── config_http_host.py │ ├── config_http_pcmk.py │ ├── config_http_sbd.py │ ├── config_http_scsi.py │ ├── config_http_status.py │ ├── config_raw_file.py │ ├── config_runner.py │ ├── config_runner_booth.py │ ├── config_runner_cib.py │ ├── config_runner_corosync.py │ ├── config_runner_pcmk.py │ ├── config_runner_sbd.py │ ├── config_runner_scsi.py │ ├── config_services.py │ ├── mock_fs.py │ ├── mock_get_local_corosync_conf.py │ ├── mock_node_communicator.py │ ├── mock_push_cib.py │ ├── mock_push_corosync_conf.py │ ├── mock_raw_file.py │ ├── mock_runner.py │ ├── mock_service_manager.py │ ├── spy.py │ └── tools.py │ ├── constraints_dto.py │ ├── custom_mock.py │ ├── fixture.py │ ├── fixture_cib.py │ ├── fixture_crm_mon.py │ ├── misc.py │ ├── nodes_dto.py │ ├── parallel_test_runner.py │ ├── pcs_runner.py │ ├── resources_dto.py │ └── xml.py ├── pcsd ├── Makefile.am ├── auth.rb ├── bootstrap.rb ├── capabilities.rng ├── capabilities.xml.in ├── cfgsync.rb ├── cluster.rb ├── cluster_entity.rb ├── conf │ └── pcsd ├── config.rb ├── corosyncconf.rb ├── logrotate │ └── pcsd.in ├── pam │ ├── pcsd.debian │ ├── pcsd.fedora │ └── pcsd.opencloudos ├── pcs.rb ├── pcsd-cli-main.rb ├── pcsd-cli.rb.in ├── pcsd-ruby.service.in ├── pcsd.8.in ├── pcsd.in ├── pcsd.rb ├── pcsd.service.in ├── pcsd_action_command.rb ├── pcsd_exchange_format.rb ├── pcsd_file.rb ├── pcsd_remove_file.rb ├── permissions.rb ├── public │ └── ui_instructions.html ├── remote.rb ├── resource.rb ├── rserver.rb.in ├── settings.rb.in └── test │ ├── .gitignore │ ├── cib1.xml │ ├── corosync.conf │ ├── crm1.xml │ ├── crm2.xml │ ├── crm2_new_roles.xml │ ├── known-hosts │ ├── pcs_settings.conf │ ├── pcsd_test_utils.rb │ ├── test_all_suite.rb │ ├── test_cfgsync.rb │ ├── test_cluster.rb │ ├── test_cluster_entity.rb │ ├── test_config.rb │ ├── test_corosyncconf.rb │ ├── test_pcs.rb │ ├── test_permissions.rb │ └── test_resource.rb ├── pyproject.toml.in ├── rpm └── pcs.spec.in ├── scripts ├── pcsd.sh.in └── pre-commit │ ├── README.md │ ├── check-all.sh │ ├── check-format.sh │ ├── check-lint.sh │ ├── check-makefile-file-listing.sh │ ├── extract-extra-dist.sh │ └── pre-commit.sh ├── typos.toml └── typos_known /.gitarchivever: -------------------------------------------------------------------------------- 1 | ref names: (HEAD -> main, refs/pull/981/head) 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | configure.ac export-subst 2 | .gitarchivever export-subst 3 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # https://help.github.com/en/articles/about-code-owners 2 | # Default reviewers for everything 3 | * @tomjelinek 4 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include Makefile 2 | include COPYING 3 | graft pcsd 4 | prune pcsd/test 5 | -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | ## How to release new pcs version 2 | 3 | ### Bump changelog version 4 | 5 | * Run `make -f make/release.mk bump-changelog version=`. 6 | * This will create commit with updated CHANGELOGE.md 7 | * Merge commit to upstream (via PR or push it directly) 8 | 9 | ### Create tarballs with new release version 10 | 11 | * Run 12 | `make -f make/release.mk tarballs version= 13 | "configure_options=--enable-local-build"` 14 | * The should be next pcs version (e.g. version=0.10.9) 15 | * Test generated tarballs 16 | 17 | ### Create annotated tag 18 | 19 | * Run 20 | `make -f make/release.mk tag version= 21 | "configure_options=--enable-local-build" release=yes` 22 | * If your upstream remote branch is origin, run 23 | `make -f make/release.mk publish release=yes` 24 | or `git push ` 25 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | | Version | Supported | 6 | | ------- | ----------| 7 | | 0.12.x | yes | 8 | | 0.11.x | yes | 9 | | 0.10.x | no | 10 | | 0.9.x | no | 11 | 12 | ## Reporting a Vulnerability 13 | 14 | If you wish to report a security vulnerability, please send an email to lead 15 | developers tojeline@redhat.com and omular@redhat.com. 16 | 17 | Include following information in the email to help us fix the 18 | vulnerability: 19 | * Detailed description and / or a reproducer / exploit 20 | * Pcs version(s) where the vulnerability was discovered, upstream or 21 | downstream version 22 | * Setup details, configuration or conditions required to trigger the 23 | vulnerability, if any 24 | * Whether the vulnerability has been disclosed, where and when 25 | * The name and affiliation of security researchers who discovered the 26 | vulnerability 27 | 28 | Thank you 29 | -------------------------------------------------------------------------------- /autogen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Copyright (C) 2020 Red Hat, Inc. All rights reserved. 4 | # 5 | 6 | # Run this to generate all the initial makefiles, etc. 7 | autoreconf -i -v && echo Now run ./configure and make 8 | -------------------------------------------------------------------------------- /data/Makefile.am: -------------------------------------------------------------------------------- 1 | MAINTAINERCLEANFILES = Makefile.in 2 | 3 | pcsdatadir = $(LIB_DIR)/pcs/data 4 | dist_pcsdata_DATA = \ 5 | ocf-1.0.rng \ 6 | ocf-1.1.rng 7 | 8 | uninstall-local: 9 | rmdir $(DESTDIR)/$(pcsdatadir) 2>/dev/null || : 10 | -------------------------------------------------------------------------------- /dev_requirements.txt: -------------------------------------------------------------------------------- 1 | lxml-stubs 2 | mypy==1.13.0 3 | ruff==0.9.1 4 | types-cryptography 5 | types-dataclasses 6 | # later versions remove type annotations from a few functions causing 7 | # error: Call to untyped function "getinfo" in typed context [no-untyped-call] 8 | # so we are stuck with this version until there's a fix 9 | types-pycurl==7.45.2.20240311 10 | types-python-dateutil 11 | -------------------------------------------------------------------------------- /pcs.pc.in: -------------------------------------------------------------------------------- 1 | webui_dir=@PCSD_WEBUI_DIR@ 2 | pcsd_unix_socket=@PCSD_UNIX_SOCKET@ 3 | 4 | Name: pcs 5 | Description: Pacemaker/Corosync Configuration System 6 | Version: @VERSION@ 7 | -------------------------------------------------------------------------------- /pcs/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs/__init__.py -------------------------------------------------------------------------------- /pcs/bash_completion/pcs: -------------------------------------------------------------------------------- 1 | # bash completion for pcs 2 | _pcs_completion(){ 3 | 4 | LENGTHS=() 5 | for WORD in "${COMP_WORDS[@]}"; do 6 | LENGTHS+=(${#WORD}) 7 | done 8 | 9 | 10 | COMPREPLY=( $( \ 11 | env COMP_WORDS="${COMP_WORDS[*]}" \ 12 | COMP_LENGTHS="${LENGTHS[*]}" \ 13 | COMP_CWORD=$COMP_CWORD \ 14 | PCS_AUTO_COMPLETE=1 pcs \ 15 | ) ) 16 | 17 | #examples what we get: 18 | #pcs 19 | #COMP_WORDS: pcs COMP_LENGTHS: 3 20 | #pcs co 21 | #COMP_WORDS: pcs co COMP_LENGTHS: 3 2 22 | # pcs config 23 | #COMP_WORDS: pcs config COMP_LENGTHS: 3 6 24 | # pcs config " 25 | #COMP_WORDS: pcs config " COMP_LENGTHS: 3 6 4 26 | # pcs config "'\\n 27 | #COMP_WORDS: pcs config "'\\n COMP_LENGTHS: 3 6 5'" 28 | } 29 | 30 | # -o default 31 | # Use readline's default filename completion if the compspec generates no 32 | # matches. 33 | # -F function 34 | # The shell function function is executed in the current shell environment. 35 | # When it finishes, the possible completions are retrieved from the value of 36 | # the COMPREPLY array variable. 37 | 38 | complete -o default -F _pcs_completion pcs 39 | -------------------------------------------------------------------------------- /pcs/cli/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs/cli/__init__.py -------------------------------------------------------------------------------- /pcs/cli/alert/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs/cli/alert/__init__.py -------------------------------------------------------------------------------- /pcs/cli/alert/command.py: -------------------------------------------------------------------------------- 1 | import json 2 | from typing import Any 3 | 4 | from pcs.cli.common.errors import CmdLineInputError 5 | from pcs.cli.common.output import lines_to_str 6 | from pcs.cli.common.parse_args import ( 7 | OUTPUT_FORMAT_VALUE_CMD, 8 | OUTPUT_FORMAT_VALUE_JSON, 9 | Argv, 10 | InputModifiers, 11 | ) 12 | from pcs.common.interface.dto import to_dict 13 | 14 | from .output import config_dto_to_cmd, config_dto_to_lines 15 | 16 | 17 | def alert_config(lib: Any, argv: Argv, modifiers: InputModifiers) -> None: 18 | """ 19 | Options: 20 | * -f - CIB file 21 | * --output-format - supported formats: text, cmd, json 22 | """ 23 | modifiers.ensure_only_supported("-f", output_format_supported=True) 24 | output_format = modifiers.get_output_format() 25 | if argv: 26 | raise CmdLineInputError 27 | 28 | config_dto = lib.alert.get_config_dto() 29 | 30 | if output_format == OUTPUT_FORMAT_VALUE_JSON: 31 | print(json.dumps(to_dict(config_dto), indent=2)) 32 | return 33 | 34 | if output_format == OUTPUT_FORMAT_VALUE_CMD: 35 | result_cmd = config_dto_to_cmd(config_dto) 36 | if result_cmd: 37 | print(";\n".join(result_cmd)) 38 | return 39 | 40 | result_text = lines_to_str(config_dto_to_lines(config_dto)) 41 | if result_text: 42 | print(result_text) 43 | -------------------------------------------------------------------------------- /pcs/cli/booth/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs/cli/booth/__init__.py -------------------------------------------------------------------------------- /pcs/cli/cluster/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs/cli/cluster/__init__.py -------------------------------------------------------------------------------- /pcs/cli/cluster_property/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs/cli/cluster_property/__init__.py -------------------------------------------------------------------------------- /pcs/cli/common/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs/cli/common/__init__.py -------------------------------------------------------------------------------- /pcs/cli/common/env_cli.py: -------------------------------------------------------------------------------- 1 | class Env: 2 | # pylint: disable=too-many-instance-attributes 3 | def __init__(self): 4 | self.cib_data = None 5 | self.user = None 6 | self.groups = None 7 | self.corosync_conf_data = None 8 | self.booth = None 9 | self.pacemaker = None 10 | self.known_hosts_getter = None 11 | self.debug = False 12 | self.request_timeout = None 13 | self.report_processor = None 14 | -------------------------------------------------------------------------------- /pcs/cli/common/routing.py: -------------------------------------------------------------------------------- 1 | from typing import ( 2 | Any, 3 | Callable, 4 | List, 5 | Mapping, 6 | Optional, 7 | ) 8 | 9 | from pcs import utils 10 | from pcs.cli.common.errors import CmdLineInputError 11 | from pcs.cli.common.parse_args import InputModifiers 12 | 13 | CliCmdInterface = Callable[[Any, List[str], InputModifiers], None] 14 | 15 | 16 | def create_router( 17 | cmd_map: Mapping[str, CliCmdInterface], 18 | usage_sub_cmd: List[str], 19 | default_cmd: Optional[str] = None, 20 | ) -> CliCmdInterface: 21 | def _router(lib: Any, argv: List[str], modifiers: InputModifiers) -> None: 22 | if argv: 23 | sub_cmd, *argv_next = argv 24 | else: 25 | if default_cmd is None: 26 | raise CmdLineInputError() 27 | sub_cmd, argv_next = default_cmd, [] 28 | 29 | try: 30 | if sub_cmd not in cmd_map: 31 | sub_cmd = "" 32 | raise CmdLineInputError() 33 | return cmd_map[sub_cmd](lib, argv_next, modifiers) 34 | except CmdLineInputError as e: 35 | if not usage_sub_cmd: 36 | raise 37 | return utils.exit_on_cmdline_input_error( 38 | e, usage_sub_cmd[0], (usage_sub_cmd[1:] + [sub_cmd]) 39 | ) 40 | 41 | return _router 42 | -------------------------------------------------------------------------------- /pcs/cli/common/tools.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from typing import Union 3 | 4 | from pcs.common.tools import timeout_to_seconds 5 | 6 | 7 | def timeout_to_seconds_legacy( 8 | timeout: Union[int, str], 9 | ) -> Union[int, str, None]: 10 | """ 11 | Transform pacemaker style timeout to number of seconds. If timeout is not 12 | valid then `timeout` is returned. 13 | 14 | timeout -- timeout string 15 | """ 16 | parsed_timeout = timeout_to_seconds(timeout) 17 | if parsed_timeout is None: 18 | return timeout 19 | return parsed_timeout 20 | 21 | 22 | def print_to_stderr(output: str, end: str = "\n") -> None: 23 | """ 24 | Prints output to stderr and flushes 25 | 26 | str output -- a string that is printed to stderr 27 | str end -- an optional ending, newline by default as Python's print 28 | """ 29 | sys.stderr.write(f"{output}{end}") 30 | sys.stderr.flush() 31 | -------------------------------------------------------------------------------- /pcs/cli/constraint/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs/cli/constraint/__init__.py -------------------------------------------------------------------------------- /pcs/cli/constraint/location/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs/cli/constraint/location/__init__.py -------------------------------------------------------------------------------- /pcs/cli/constraint/output/__init__.py: -------------------------------------------------------------------------------- 1 | from . import ( 2 | colocation, 3 | location, 4 | order, 5 | ticket, 6 | ) 7 | from .all import ( 8 | CibConstraintLocationAnyDto, 9 | constraints_to_cmd, 10 | constraints_to_text, 11 | filter_constraints_by_rule_expired_status, 12 | print_config, 13 | ) 14 | -------------------------------------------------------------------------------- /pcs/cli/constraint_colocation/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs/cli/constraint_colocation/__init__.py -------------------------------------------------------------------------------- /pcs/cli/constraint_order/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs/cli/constraint_order/__init__.py -------------------------------------------------------------------------------- /pcs/cli/constraint_ticket/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs/cli/constraint_ticket/__init__.py -------------------------------------------------------------------------------- /pcs/cli/constraint_ticket/parse_args.py: -------------------------------------------------------------------------------- 1 | from pcs.cli.common.errors import CmdLineInputError 2 | from pcs.cli.common.parse_args import ( 3 | Argv, 4 | KeyValueParser, 5 | ) 6 | 7 | 8 | def separate_tail_option_candidates( 9 | arg_list: Argv, 10 | ) -> tuple[list[str], list[str]]: 11 | for i, arg in enumerate(arg_list): 12 | if "=" in arg: 13 | return arg_list[:i], arg_list[i:] 14 | 15 | return arg_list, [] 16 | 17 | 18 | def parse_add(arg_list: Argv) -> tuple[str, str, str, dict[str, str]]: 19 | info, option_candidates = separate_tail_option_candidates(arg_list) 20 | 21 | if not info: 22 | raise CmdLineInputError("Ticket not specified") 23 | 24 | ticket, resource_specification = info[0], info[1:] 25 | 26 | if len(resource_specification) not in (1, 2): 27 | raise CmdLineInputError( 28 | "invalid resource specification: '{0}'".format( 29 | " ".join(resource_specification) 30 | ) 31 | ) 32 | 33 | if len(resource_specification) == 2: 34 | resource_role, resource_id = resource_specification 35 | else: 36 | resource_role = "" 37 | resource_id = resource_specification[0] 38 | 39 | return ( 40 | ticket, 41 | resource_id, 42 | resource_role, 43 | KeyValueParser(option_candidates).get_unique(), 44 | ) 45 | -------------------------------------------------------------------------------- /pcs/cli/fencing_topology.py: -------------------------------------------------------------------------------- 1 | from pcs.common.fencing_topology import ( 2 | TARGET_TYPE_ATTRIBUTE, 3 | TARGET_TYPE_NODE, 4 | TARGET_TYPE_REGEXP, 5 | ) 6 | 7 | __target_type_map = { 8 | "attrib": TARGET_TYPE_ATTRIBUTE, 9 | "node": TARGET_TYPE_NODE, 10 | "regexp": TARGET_TYPE_REGEXP, 11 | } 12 | 13 | target_type_map_cli_to_lib = __target_type_map 14 | 15 | target_type_map_lib_to_cli = { 16 | value: key for key, value in __target_type_map.items() 17 | } 18 | -------------------------------------------------------------------------------- /pcs/cli/file/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs/cli/file/__init__.py -------------------------------------------------------------------------------- /pcs/cli/file/metadata.py: -------------------------------------------------------------------------------- 1 | import os.path 2 | 3 | from pcs.common import file_type_codes as code 4 | from pcs.common.file import FileMetadata 5 | 6 | _metadata = { 7 | code.BOOTH_CONFIG: lambda path: FileMetadata( 8 | file_type_code=code.BOOTH_CONFIG, 9 | path=path, 10 | owner_user_name=None, 11 | owner_group_name=None, 12 | permissions=None, 13 | is_binary=False, 14 | ), 15 | code.BOOTH_KEY: lambda path: FileMetadata( 16 | file_type_code=code.BOOTH_KEY, 17 | path=path, 18 | owner_user_name=None, 19 | owner_group_name=None, 20 | permissions=0o600, 21 | is_binary=True, 22 | ), 23 | code.COROSYNC_CONF: lambda path: FileMetadata( 24 | file_type_code=code.COROSYNC_CONF, 25 | path=path, 26 | owner_user_name=None, 27 | owner_group_name=None, 28 | permissions=0o644, 29 | is_binary=False, 30 | ), 31 | code.PCS_KNOWN_HOSTS: lambda: FileMetadata( 32 | file_type_code=code.PCS_KNOWN_HOSTS, 33 | path=os.path.join(os.path.expanduser("~/.pcs"), "known-hosts"), 34 | owner_user_name=None, 35 | owner_group_name=None, 36 | permissions=0o600, 37 | is_binary=False, 38 | ), 39 | } 40 | 41 | 42 | def for_file_type(file_type_code, *args, **kwargs): 43 | return _metadata[file_type_code](*args, **kwargs) 44 | -------------------------------------------------------------------------------- /pcs/cli/node/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs/cli/node/__init__.py -------------------------------------------------------------------------------- /pcs/cli/query/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs/cli/query/__init__.py -------------------------------------------------------------------------------- /pcs/cli/reports/__init__.py: -------------------------------------------------------------------------------- 1 | from . import ( 2 | messages, 3 | output, 4 | ) 5 | from .output import process_library_reports 6 | from .processor import ReportProcessorToConsole 7 | -------------------------------------------------------------------------------- /pcs/cli/resource/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs/cli/resource/__init__.py -------------------------------------------------------------------------------- /pcs/cli/resource_agent.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | from pcs.cli.common.errors import CmdLineInputError 4 | from pcs.cli.common.tools import print_to_stderr 5 | from pcs.common import reports 6 | from pcs.common.resource_agent.dto import ( 7 | ResourceAgentNameDto, 8 | get_resource_agent_full_name, 9 | ) 10 | 11 | 12 | def is_stonith(agent_name: ResourceAgentNameDto) -> bool: 13 | return agent_name.standard == "stonith" 14 | 15 | 16 | def find_single_agent( 17 | agent_names: List[ResourceAgentNameDto], to_find: str 18 | ) -> ResourceAgentNameDto: 19 | to_find_normalized = to_find.lower() 20 | matches = [ 21 | agent_name 22 | for agent_name in agent_names 23 | if agent_name.type.lower() == to_find_normalized 24 | ] 25 | if len(matches) == 1: 26 | print_to_stderr( 27 | reports.messages.AgentNameGuessed( 28 | to_find, get_resource_agent_full_name(matches[0]) 29 | ).message 30 | ) 31 | return matches[0] 32 | 33 | report_msg: reports.item.ReportItemMessage 34 | if matches: 35 | report_msg = reports.messages.AgentNameGuessFoundMoreThanOne( 36 | to_find, sorted(map(get_resource_agent_full_name, matches)) 37 | ) 38 | else: 39 | report_msg = reports.messages.AgentNameGuessFoundNone(to_find) 40 | raise CmdLineInputError(report_msg.message) 41 | -------------------------------------------------------------------------------- /pcs/cli/routing/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs/cli/routing/__init__.py -------------------------------------------------------------------------------- /pcs/cli/routing/alert.py: -------------------------------------------------------------------------------- 1 | from pcs import ( 2 | alert, 3 | usage, 4 | ) 5 | from pcs.cli.alert import command as alert_command 6 | from pcs.cli.common.errors import raise_command_replaced 7 | from pcs.cli.common.routing import create_router 8 | 9 | alert_cmd = create_router( 10 | { 11 | "help": lambda lib, argv, modifiers: print(usage.alert(argv)), 12 | "create": alert.alert_add, 13 | "update": alert.alert_update, 14 | "delete": alert.alert_remove, 15 | "remove": alert.alert_remove, 16 | "config": alert_command.alert_config, 17 | "show": lambda lib, argv, modifiers: raise_command_replaced( 18 | ["pcs alert config"], pcs_version="0.12" 19 | ), 20 | "recipient": create_router( 21 | { 22 | "help": lambda lib, argv, modifiers: print( 23 | usage.alert(["recipient"]) 24 | ), 25 | "add": alert.recipient_add, 26 | "update": alert.recipient_update, 27 | "delete": alert.recipient_remove, 28 | "remove": alert.recipient_remove, 29 | }, 30 | ["alert", "recipient"], 31 | ), 32 | "get_all_alerts": alert.print_alerts_in_json, 33 | }, 34 | ["alert"], 35 | default_cmd="config", 36 | ) 37 | -------------------------------------------------------------------------------- /pcs/cli/routing/client.py: -------------------------------------------------------------------------------- 1 | from pcs import ( 2 | client, 3 | usage, 4 | ) 5 | from pcs.cli.common.routing import create_router 6 | 7 | client_cmd = create_router( 8 | { 9 | "help": lambda lib, argv, modifiers: print(usage.client(argv)), 10 | "local-auth": client.local_auth_cmd, 11 | }, 12 | ["client"], 13 | ) 14 | -------------------------------------------------------------------------------- /pcs/cli/routing/config.py: -------------------------------------------------------------------------------- 1 | from pcs import ( 2 | config, 3 | usage, 4 | ) 5 | from pcs.cli.common.routing import create_router 6 | 7 | config_cmd = create_router( 8 | { 9 | "help": lambda lib, argv, modifiers: print(usage.config(argv)), 10 | "show": config.config_show, 11 | "backup": config.config_backup, 12 | "restore": config.config_restore, 13 | "checkpoint": create_router( 14 | { 15 | "list": config.config_checkpoint_list, 16 | "view": config.config_checkpoint_view, 17 | "restore": config.config_checkpoint_restore, 18 | "diff": config.config_checkpoint_diff, 19 | }, 20 | ["config", "checkpoint"], 21 | default_cmd="list", 22 | ), 23 | }, 24 | ["config"], 25 | default_cmd="show", 26 | ) 27 | -------------------------------------------------------------------------------- /pcs/cli/routing/dr.py: -------------------------------------------------------------------------------- 1 | from pcs import usage 2 | from pcs.cli import dr 3 | from pcs.cli.common.routing import create_router 4 | 5 | dr_cmd = create_router( 6 | { 7 | "help": lambda lib, argv, modifiers: print(usage.dr(argv)), 8 | "config": dr.config, 9 | "destroy": dr.destroy, 10 | "set-recovery-site": dr.set_recovery_site, 11 | "status": dr.status, 12 | }, 13 | ["dr"], 14 | ) 15 | -------------------------------------------------------------------------------- /pcs/cli/routing/host.py: -------------------------------------------------------------------------------- 1 | from pcs import ( 2 | host, 3 | usage, 4 | ) 5 | from pcs.cli.common.routing import create_router 6 | 7 | host_cmd = create_router( 8 | { 9 | "help": lambda lib, argv, modifiers: print(usage.host(argv)), 10 | "auth": host.auth_cmd, 11 | "deauth": host.deauth_cmd, 12 | }, 13 | ["host"], 14 | ) 15 | -------------------------------------------------------------------------------- /pcs/cli/routing/pcsd.py: -------------------------------------------------------------------------------- 1 | from pcs import ( 2 | pcsd, 3 | usage, 4 | ) 5 | from pcs.cli.common.routing import create_router 6 | 7 | pcsd_cmd = create_router( 8 | { 9 | "help": lambda lib, argv, modifiers: print(usage.pcsd(argv)), 10 | "accept_token": pcsd.accept_token_cmd, 11 | "deauth": pcsd.pcsd_deauth, 12 | "certkey": pcsd.pcsd_certkey_cmd, 13 | "status": pcsd.pcsd_status_cmd, 14 | "sync-certificates": pcsd.pcsd_sync_certs, 15 | }, 16 | ["pcsd"], 17 | ) 18 | -------------------------------------------------------------------------------- /pcs/cli/routing/prop.py: -------------------------------------------------------------------------------- 1 | from pcs import usage 2 | from pcs.cli.cluster_property import command as cluster_property 3 | from pcs.cli.common.errors import raise_command_replaced 4 | from pcs.cli.common.routing import create_router 5 | 6 | property_cmd = create_router( 7 | { 8 | "help": lambda _lib, _argv, _modifiers: print( 9 | usage.property_usage(_argv) 10 | ), 11 | "set": cluster_property.set_property, 12 | "unset": cluster_property.unset_property, 13 | "list": lambda lib, argv, modifiers: raise_command_replaced( 14 | ["pcs property config"], pcs_version="0.12" 15 | ), 16 | "show": lambda lib, argv, modifiers: raise_command_replaced( 17 | ["pcs property config"], pcs_version="0.12" 18 | ), 19 | "config": cluster_property.config, 20 | "defaults": cluster_property.defaults, 21 | "describe": cluster_property.describe, 22 | "get_cluster_properties_definition": ( 23 | cluster_property.print_cluster_properties_definition_legacy 24 | ), 25 | }, 26 | ["property"], 27 | default_cmd="config", 28 | ) 29 | -------------------------------------------------------------------------------- /pcs/cli/routing/qdevice.py: -------------------------------------------------------------------------------- 1 | from pcs import ( 2 | qdevice, 3 | usage, 4 | ) 5 | from pcs.cli.common.routing import create_router 6 | 7 | qdevice_cmd = create_router( 8 | { 9 | "help": lambda lib, argv, modifiers: print(usage.qdevice(argv)), 10 | "status": qdevice.qdevice_status_cmd, 11 | "setup": qdevice.qdevice_setup_cmd, 12 | "destroy": qdevice.qdevice_destroy_cmd, 13 | "start": qdevice.qdevice_start_cmd, 14 | "stop": qdevice.qdevice_stop_cmd, 15 | "kill": qdevice.qdevice_kill_cmd, 16 | "enable": qdevice.qdevice_enable_cmd, 17 | "disable": qdevice.qdevice_disable_cmd, 18 | }, 19 | ["qdevice"], 20 | ) 21 | -------------------------------------------------------------------------------- /pcs/cli/routing/tag.py: -------------------------------------------------------------------------------- 1 | from pcs import usage 2 | from pcs.cli.common.errors import raise_command_replaced 3 | from pcs.cli.common.routing import create_router 4 | from pcs.cli.tag import command as tag 5 | 6 | tag_cmd = create_router( 7 | { 8 | "config": tag.tag_config, 9 | "create": tag.tag_create, 10 | "delete": tag.tag_remove, 11 | "help": lambda lib, argv, modifiers: print(usage.tag(argv)), 12 | "list": lambda lib, argv, modifiers: raise_command_replaced( 13 | ["pcs tag config"], pcs_version="0.12" 14 | ), 15 | "remove": tag.tag_remove, 16 | "update": tag.tag_update, 17 | }, 18 | ["tag"], 19 | default_cmd="config", 20 | ) 21 | -------------------------------------------------------------------------------- /pcs/cli/status/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs/cli/status/__init__.py -------------------------------------------------------------------------------- /pcs/cli/status/command.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | from pcs.cli.common.errors import CmdLineInputError 4 | from pcs.cli.common.parse_args import ( 5 | Argv, 6 | InputModifiers, 7 | ) 8 | 9 | 10 | def wait_for_pcmk_idle(lib: Any, argv: Argv, modifiers: InputModifiers) -> None: 11 | """ 12 | Options: no options 13 | """ 14 | modifiers.ensure_only_supported() 15 | 16 | if len(argv) > 1: 17 | raise CmdLineInputError() 18 | 19 | lib.cluster.wait_for_pcmk_idle(argv[0] if argv else None) 20 | -------------------------------------------------------------------------------- /pcs/cli/stonith/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs/cli/stonith/__init__.py -------------------------------------------------------------------------------- /pcs/cli/stonith/common.py: -------------------------------------------------------------------------------- 1 | from typing import ( 2 | Any, 3 | Optional, 4 | ) 5 | 6 | from pcs.cli.reports.output import error 7 | from pcs.common import reports 8 | from pcs.common.str_tools import format_optional 9 | 10 | 11 | def check_is_stonith( 12 | lib: Any, 13 | resource_id_list: list[str], 14 | cmd_to_use: Optional[str] = None, 15 | ) -> None: 16 | if lib.resource.is_any_resource_except_stonith(resource_id_list): 17 | raise error( 18 | reports.messages.CommandArgumentTypeMismatch("resources").message 19 | + format_optional(cmd_to_use, " Please use '{}' instead.") 20 | ) 21 | -------------------------------------------------------------------------------- /pcs/cli/stonith/levels/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs/cli/stonith/levels/__init__.py -------------------------------------------------------------------------------- /pcs/cli/tag/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs/cli/tag/__init__.py -------------------------------------------------------------------------------- /pcs/client.py: -------------------------------------------------------------------------------- 1 | from pcs import ( 2 | settings, 3 | utils, 4 | ) 5 | from pcs.cli.common.errors import CmdLineInputError 6 | 7 | 8 | def local_auth_cmd(lib, argv, modifiers): 9 | """ 10 | Options: 11 | * -u - username 12 | * -p - password 13 | * --request-timeout - timeout for HTTP requests 14 | """ 15 | del lib 16 | modifiers.ensure_only_supported("-u", "-p", "--request-timeout") 17 | if len(argv) > 1: 18 | raise CmdLineInputError() 19 | port = argv[0] if argv else settings.pcsd_default_port 20 | username, password = utils.get_user_and_pass() 21 | utils.auth_hosts( 22 | { 23 | "localhost": { 24 | "username": username, 25 | "password": password, 26 | "dest_list": [{"addr": "localhost", "port": port}], 27 | } 28 | } 29 | ) 30 | -------------------------------------------------------------------------------- /pcs/common/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs/common/__init__.py -------------------------------------------------------------------------------- /pcs/common/async_tasks/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs/common/async_tasks/__init__.py -------------------------------------------------------------------------------- /pcs/common/async_tasks/dto.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import ( 3 | Any, 4 | Dict, 5 | List, 6 | Optional, 7 | ) 8 | 9 | from pcs.common.interface.dto import DataTransferObject 10 | from pcs.common.reports.dto import ReportItemDto 11 | 12 | from .types import ( 13 | TaskFinishType, 14 | TaskKillReason, 15 | TaskState, 16 | ) 17 | 18 | 19 | @dataclass(frozen=True) 20 | class CommandOptionsDto(DataTransferObject): 21 | request_timeout: Optional[int] = None 22 | effective_username: Optional[str] = None 23 | effective_groups: Optional[List[str]] = None 24 | 25 | 26 | @dataclass(frozen=True) 27 | class CommandDto(DataTransferObject): 28 | command_name: str 29 | params: Dict[str, Any] 30 | options: CommandOptionsDto 31 | 32 | 33 | @dataclass(frozen=True) 34 | class TaskIdentDto(DataTransferObject): 35 | task_ident: str 36 | 37 | 38 | @dataclass(frozen=True) 39 | class TaskResultDto(DataTransferObject): 40 | task_ident: str 41 | command: CommandDto 42 | reports: List[ReportItemDto] 43 | state: TaskState 44 | task_finish_type: TaskFinishType 45 | kill_reason: Optional[TaskKillReason] 46 | result: Any 47 | -------------------------------------------------------------------------------- /pcs/common/async_tasks/types.py: -------------------------------------------------------------------------------- 1 | from enum import auto 2 | 3 | from pcs.common.types import AutoNameEnum 4 | 5 | 6 | class TaskFinishType(AutoNameEnum): 7 | UNFINISHED = auto() 8 | UNHANDLED_EXCEPTION = auto() 9 | FAIL = auto() 10 | SUCCESS = auto() 11 | KILL = auto() 12 | 13 | 14 | class TaskState(AutoNameEnum): 15 | CREATED = auto() 16 | QUEUED = auto() 17 | EXECUTED = auto() 18 | FINISHED = auto() 19 | 20 | 21 | class TaskKillReason(AutoNameEnum): 22 | USER = auto() 23 | COMPLETION_TIMEOUT = auto() 24 | INTERNAL_MESSAGING_ERROR = auto() 25 | -------------------------------------------------------------------------------- /pcs/common/cfgsync_dto.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from pcs.common.file_type_codes import FileTypeCode 4 | from pcs.common.interface.dto import DataTransferObject 5 | 6 | 7 | @dataclass(frozen=True) 8 | class SyncConfigsDto(DataTransferObject): 9 | cluster_name: str 10 | configs: dict[FileTypeCode, str] 11 | -------------------------------------------------------------------------------- /pcs/common/communication/__init__.py: -------------------------------------------------------------------------------- 1 | from . import ( 2 | const, 3 | dto, 4 | types, 5 | ) 6 | -------------------------------------------------------------------------------- /pcs/common/communication/const.py: -------------------------------------------------------------------------------- 1 | from .types import CommunicationResultStatus as Status 2 | 3 | COM_STATUS_SUCCESS = Status("success") 4 | COM_STATUS_INPUT_ERROR = Status("input_error") 5 | COM_STATUS_UNKNOWN_CMD = Status("unknown_cmd") 6 | COM_STATUS_ERROR = Status("error") 7 | COM_STATUS_EXCEPTION = Status("exception") 8 | COM_STATUS_NOT_AUTHORIZED = Status("not_authorized") 9 | -------------------------------------------------------------------------------- /pcs/common/communication/dto.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import ( 3 | Any, 4 | List, 5 | Mapping, 6 | Optional, 7 | ) 8 | 9 | from pcs.common.interface.dto import DataTransferObject 10 | from pcs.common.reports.dto import ReportItemDto 11 | 12 | from .types import CommunicationResultStatus as StatusType 13 | 14 | 15 | @dataclass(frozen=True) 16 | class InternalCommunicationResultDto(DataTransferObject): 17 | status: StatusType 18 | status_msg: Optional[str] 19 | report_list: List[ReportItemDto] 20 | data: Any 21 | 22 | 23 | @dataclass(frozen=True) 24 | class InternalCommunicationRequestOptionsDto(DataTransferObject): 25 | request_timeout: Optional[int] 26 | 27 | 28 | @dataclass(frozen=True) 29 | class InternalCommunicationRequestDto(DataTransferObject): 30 | options: InternalCommunicationRequestOptionsDto 31 | cmd: str 32 | cmd_data: Mapping[str, Any] 33 | -------------------------------------------------------------------------------- /pcs/common/communication/types.py: -------------------------------------------------------------------------------- 1 | from typing import NewType 2 | 3 | CommunicationResultStatus = NewType("CommunicationResultStatus", str) 4 | -------------------------------------------------------------------------------- /pcs/common/dr.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import Sequence 3 | 4 | from pcs.common.interface.dto import DataTransferObject 5 | from pcs.common.types import DrRole 6 | 7 | 8 | @dataclass(frozen=True) 9 | class DrConfigNodeDto(DataTransferObject): 10 | name: str 11 | 12 | 13 | @dataclass(frozen=True) 14 | class DrConfigSiteDto(DataTransferObject): 15 | site_role: DrRole 16 | node_list: Sequence[DrConfigNodeDto] 17 | 18 | 19 | @dataclass(frozen=True) 20 | class DrConfigDto(DataTransferObject): 21 | local_site: DrConfigSiteDto 22 | remote_site_list: Sequence[DrConfigSiteDto] 23 | 24 | 25 | @dataclass(frozen=True) 26 | class DrSiteStatusDto(DataTransferObject): 27 | local_site: bool 28 | site_role: DrRole 29 | status_plaintext: str 30 | status_successfully_obtained: bool 31 | -------------------------------------------------------------------------------- /pcs/common/fencing_topology.py: -------------------------------------------------------------------------------- 1 | from typing import ( 2 | Final, 3 | NewType, 4 | Union, 5 | ) 6 | 7 | FencingTargetType = NewType("FencingTargetType", str) 8 | FencingTargetValue = Union[str, tuple[str, str]] 9 | 10 | TARGET_TYPE_NODE: Final = FencingTargetType("node") 11 | TARGET_TYPE_REGEXP: Final = FencingTargetType("regexp") 12 | TARGET_TYPE_ATTRIBUTE: Final = FencingTargetType("attribute") 13 | -------------------------------------------------------------------------------- /pcs/common/file_type_codes.py: -------------------------------------------------------------------------------- 1 | from typing import NewType 2 | 3 | FileTypeCode = NewType("FileTypeCode", str) 4 | 5 | BOOTH_CONFIG = FileTypeCode("BOOTH_CONFIG") 6 | BOOTH_KEY = FileTypeCode("BOOTH_KEY") 7 | CFGSYNC_CTL = FileTypeCode("CFGSYNC_CTL") 8 | CIB = FileTypeCode("CIB") 9 | COROSYNC_AUTHKEY = FileTypeCode("COROSYNC_AUTHKEY") 10 | COROSYNC_CONF = FileTypeCode("COROSYNC_CONF") 11 | COROSYNC_QDEVICE_NSSDB = FileTypeCode("COROSYNC_QDEVICE_NSSDB") 12 | COROSYNC_QNETD_NSSDB = FileTypeCode("COROSYNC_QNETD_NSSDB") 13 | COROSYNC_QNETD_CA_CERT = FileTypeCode("COROSYNC_QNETD_CA_CERT") 14 | PACEMAKER_AUTHKEY = FileTypeCode("PACEMAKER_AUTHKEY") 15 | PCSD_ENVIRONMENT_CONFIG = FileTypeCode("PCSD_ENVIRONMENT_CONFIG") 16 | PCSD_SSL_CERT = FileTypeCode("PCSD_SSL_CERT") 17 | PCSD_SSL_KEY = FileTypeCode("PCSD_SSL_KEY") 18 | PCS_KNOWN_HOSTS = FileTypeCode("PCS_KNOWN_HOSTS") 19 | PCS_SETTINGS_CONF = FileTypeCode("PCS_SETTINGS_CONF") 20 | PCS_DR_CONFIG = FileTypeCode("PCS_DR_CONFIG") 21 | PCS_USERS_CONF = FileTypeCode("PCS_USERS_CONF") 22 | -------------------------------------------------------------------------------- /pcs/common/interface/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs/common/interface/__init__.py -------------------------------------------------------------------------------- /pcs/common/pacemaker/__init__.py: -------------------------------------------------------------------------------- 1 | from . import ( 2 | nvset, 3 | resource, 4 | role, 5 | rule, 6 | ) 7 | -------------------------------------------------------------------------------- /pcs/common/pacemaker/alert.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import Optional, Sequence 3 | 4 | from pcs.common.interface.dto import DataTransferObject 5 | from pcs.common.pacemaker.nvset import CibNvsetDto 6 | 7 | 8 | @dataclass(frozen=True) 9 | class CibAlertRecipientDto(DataTransferObject): 10 | id: str 11 | value: str 12 | description: Optional[str] 13 | meta_attributes: Sequence[CibNvsetDto] 14 | instance_attributes: Sequence[CibNvsetDto] 15 | 16 | 17 | @dataclass(frozen=True) 18 | class CibAlertSelectAttributeDto(DataTransferObject): 19 | id: str 20 | name: str 21 | 22 | 23 | @dataclass(frozen=True) 24 | class CibAlertSelectDto(DataTransferObject): 25 | nodes: bool 26 | fencing: bool 27 | resources: bool 28 | attributes: bool 29 | attributes_select: Sequence[CibAlertSelectAttributeDto] 30 | 31 | 32 | @dataclass(frozen=True) 33 | class CibAlertDto(DataTransferObject): 34 | id: str 35 | path: str 36 | description: Optional[str] 37 | recipients: Sequence[CibAlertRecipientDto] 38 | select: Optional[CibAlertSelectDto] 39 | meta_attributes: Sequence[CibNvsetDto] 40 | instance_attributes: Sequence[CibNvsetDto] 41 | 42 | 43 | @dataclass(frozen=True) 44 | class CibAlertListDto(DataTransferObject): 45 | alerts: Sequence[CibAlertDto] 46 | -------------------------------------------------------------------------------- /pcs/common/pacemaker/cluster_property.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import Sequence 3 | 4 | from pcs.common.interface.dto import DataTransferObject 5 | from pcs.common.resource_agent.dto import ResourceAgentParameterDto 6 | from pcs.common.types import StringCollection 7 | 8 | 9 | @dataclass(frozen=True) 10 | class ClusterPropertyMetadataDto(DataTransferObject): 11 | properties_metadata: Sequence[ResourceAgentParameterDto] 12 | readonly_properties: StringCollection 13 | -------------------------------------------------------------------------------- /pcs/common/pacemaker/constraint/__init__.py: -------------------------------------------------------------------------------- 1 | from .all import ( 2 | CibConstraintsDto, 3 | get_all_constraints_ids, 4 | get_all_location_constraints_ids, 5 | get_all_location_rules_ids, 6 | ) 7 | from .colocation import ( 8 | CibConstraintColocationAttributesDto, 9 | CibConstraintColocationDto, 10 | CibConstraintColocationSetDto, 11 | ) 12 | from .location import ( 13 | CibConstraintLocationAttributesDto, 14 | CibConstraintLocationDto, 15 | CibConstraintLocationSetDto, 16 | ) 17 | from .order import ( 18 | CibConstraintOrderAttributesDto, 19 | CibConstraintOrderDto, 20 | CibConstraintOrderSetDto, 21 | ) 22 | from .set import CibResourceSetDto 23 | from .ticket import ( 24 | CibConstraintTicketAttributesDto, 25 | CibConstraintTicketDto, 26 | CibConstraintTicketSetDto, 27 | ) 28 | -------------------------------------------------------------------------------- /pcs/common/pacemaker/constraint/colocation.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import ( 3 | Optional, 4 | Sequence, 5 | ) 6 | 7 | from pcs.common.const import PcmkRoleType 8 | from pcs.common.interface.dto import DataTransferObject 9 | 10 | from ..rule import CibRuleExpressionDto 11 | from .set import CibResourceSetDto 12 | 13 | 14 | @dataclass(frozen=True) 15 | class CibConstraintColocationAttributesDto(DataTransferObject): 16 | constraint_id: str 17 | score: Optional[str] 18 | influence: Optional[str] 19 | lifetime: Sequence[CibRuleExpressionDto] 20 | 21 | 22 | @dataclass(frozen=True) 23 | class CibConstraintColocationDto(DataTransferObject): 24 | # pylint: disable=too-many-instance-attributes 25 | resource_id: str 26 | with_resource_id: str 27 | node_attribute: Optional[str] 28 | resource_role: Optional[PcmkRoleType] 29 | with_resource_role: Optional[PcmkRoleType] 30 | resource_instance: Optional[int] 31 | with_resource_instance: Optional[int] 32 | attributes: CibConstraintColocationAttributesDto 33 | 34 | 35 | @dataclass(frozen=True) 36 | class CibConstraintColocationSetDto(DataTransferObject): 37 | resource_sets: Sequence[CibResourceSetDto] 38 | attributes: CibConstraintColocationAttributesDto 39 | -------------------------------------------------------------------------------- /pcs/common/pacemaker/constraint/location.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import ( 3 | Optional, 4 | Sequence, 5 | ) 6 | 7 | from pcs.common.const import PcmkRoleType 8 | from pcs.common.interface.dto import DataTransferObject 9 | from pcs.common.pacemaker.types import CibResourceDiscovery 10 | 11 | from ..rule import CibRuleExpressionDto 12 | from .set import CibResourceSetDto 13 | 14 | 15 | @dataclass(frozen=True) 16 | class CibConstraintLocationAttributesDto(DataTransferObject): 17 | constraint_id: str 18 | score: Optional[str] 19 | node: Optional[str] 20 | rules: Sequence[CibRuleExpressionDto] 21 | lifetime: Sequence[CibRuleExpressionDto] 22 | resource_discovery: Optional[CibResourceDiscovery] 23 | 24 | 25 | @dataclass(frozen=True) 26 | class CibConstraintLocationDto(DataTransferObject): 27 | resource_id: Optional[str] 28 | resource_pattern: Optional[str] 29 | role: Optional[PcmkRoleType] 30 | attributes: CibConstraintLocationAttributesDto 31 | 32 | 33 | @dataclass(frozen=True) 34 | class CibConstraintLocationSetDto(DataTransferObject): 35 | resource_sets: Sequence[CibResourceSetDto] 36 | attributes: CibConstraintLocationAttributesDto 37 | -------------------------------------------------------------------------------- /pcs/common/pacemaker/constraint/order.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import ( 3 | Optional, 4 | Sequence, 5 | ) 6 | 7 | from pcs.common.const import PcmkAction 8 | from pcs.common.interface.dto import DataTransferObject 9 | from pcs.common.pacemaker.types import CibResourceSetOrderType 10 | 11 | from .set import CibResourceSetDto 12 | 13 | 14 | @dataclass(frozen=True) 15 | class CibConstraintOrderAttributesDto(DataTransferObject): 16 | constraint_id: str 17 | symmetrical: Optional[bool] 18 | require_all: Optional[bool] 19 | score: Optional[str] 20 | kind: Optional[CibResourceSetOrderType] 21 | 22 | 23 | @dataclass(frozen=True) 24 | class CibConstraintOrderDto(DataTransferObject): 25 | first_resource_id: str 26 | then_resource_id: str 27 | first_action: Optional[PcmkAction] 28 | then_action: Optional[PcmkAction] 29 | first_resource_instance: Optional[int] 30 | then_resource_instance: Optional[int] 31 | attributes: CibConstraintOrderAttributesDto 32 | 33 | 34 | @dataclass(frozen=True) 35 | class CibConstraintOrderSetDto(DataTransferObject): 36 | resource_sets: Sequence[CibResourceSetDto] 37 | attributes: CibConstraintOrderAttributesDto 38 | -------------------------------------------------------------------------------- /pcs/common/pacemaker/constraint/set.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import Optional 3 | 4 | from pcs.common.const import ( 5 | PcmkAction, 6 | PcmkRoleType, 7 | ) 8 | from pcs.common.interface.dto import DataTransferObject 9 | from pcs.common.pacemaker.types import ( 10 | CibResourceSetOrdering, 11 | CibResourceSetOrderType, 12 | ) 13 | from pcs.common.types import StringSequence 14 | 15 | 16 | @dataclass(frozen=True) 17 | class CibResourceSetDto(DataTransferObject): 18 | # pylint: disable=too-many-instance-attributes 19 | set_id: str 20 | sequential: Optional[bool] 21 | require_all: Optional[bool] 22 | ordering: Optional[CibResourceSetOrdering] 23 | action: Optional[PcmkAction] 24 | role: Optional[PcmkRoleType] 25 | score: Optional[str] 26 | kind: Optional[CibResourceSetOrderType] 27 | resources_ids: StringSequence 28 | -------------------------------------------------------------------------------- /pcs/common/pacemaker/constraint/ticket.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import ( 3 | Optional, 4 | Sequence, 5 | ) 6 | 7 | from pcs.common.const import PcmkRoleType 8 | from pcs.common.interface.dto import DataTransferObject 9 | from pcs.common.pacemaker.types import CibTicketLossPolicy 10 | 11 | from .set import CibResourceSetDto 12 | 13 | 14 | @dataclass(frozen=True) 15 | class CibConstraintTicketAttributesDto(DataTransferObject): 16 | constraint_id: str 17 | ticket: str 18 | loss_policy: Optional[CibTicketLossPolicy] 19 | 20 | 21 | @dataclass(frozen=True) 22 | class CibConstraintTicketDto(DataTransferObject): 23 | resource_id: str 24 | role: Optional[PcmkRoleType] 25 | attributes: CibConstraintTicketAttributesDto 26 | 27 | 28 | @dataclass(frozen=True) 29 | class CibConstraintTicketSetDto(DataTransferObject): 30 | resource_sets: Sequence[CibResourceSetDto] 31 | attributes: CibConstraintTicketAttributesDto 32 | -------------------------------------------------------------------------------- /pcs/common/pacemaker/defaults.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import Sequence 3 | 4 | from pcs.common.interface.dto import DataTransferObject 5 | 6 | from .nvset import CibNvsetDto 7 | 8 | 9 | @dataclass(frozen=True) 10 | class CibDefaultsDto(DataTransferObject): 11 | instance_attributes: Sequence[CibNvsetDto] 12 | meta_attributes: Sequence[CibNvsetDto] 13 | -------------------------------------------------------------------------------- /pcs/common/pacemaker/fencing_topology.py: -------------------------------------------------------------------------------- 1 | from collections.abc import Sequence 2 | from dataclasses import dataclass 3 | from typing import Union 4 | 5 | from pcs.common.interface.dto import DataTransferObject 6 | 7 | 8 | @dataclass(frozen=True) 9 | class CibFencingLevelNodeDto(DataTransferObject): 10 | id: str 11 | target: str 12 | index: int 13 | devices: list[str] 14 | 15 | 16 | @dataclass(frozen=True) 17 | class CibFencingLevelRegexDto(DataTransferObject): 18 | id: str 19 | target_pattern: str 20 | index: int 21 | devices: list[str] 22 | 23 | 24 | @dataclass(frozen=True) 25 | class CibFencingLevelAttributeDto(DataTransferObject): 26 | id: str 27 | target_attribute: str 28 | target_value: str 29 | index: int 30 | devices: list[str] 31 | 32 | 33 | CibFencingLevel = Union[ 34 | CibFencingLevelNodeDto, CibFencingLevelRegexDto, CibFencingLevelAttributeDto 35 | ] 36 | 37 | 38 | @dataclass(frozen=True) 39 | class CibFencingTopologyDto(DataTransferObject): 40 | target_node: Sequence[CibFencingLevelNodeDto] 41 | target_regex: Sequence[CibFencingLevelRegexDto] 42 | target_attribute: Sequence[CibFencingLevelAttributeDto] 43 | -------------------------------------------------------------------------------- /pcs/common/pacemaker/node.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import Optional, Sequence 3 | 4 | from pcs.common.interface.dto import DataTransferObject 5 | from pcs.common.pacemaker.nvset import CibNvsetDto 6 | 7 | 8 | @dataclass(frozen=True) 9 | class CibNodeDto(DataTransferObject): 10 | id: str 11 | uname: str 12 | description: Optional[str] 13 | score: Optional[str] 14 | type: Optional[str] 15 | instance_attributes: Sequence[CibNvsetDto] 16 | utilization: Sequence[CibNvsetDto] 17 | 18 | 19 | @dataclass(frozen=True) 20 | class CibNodeListDto(DataTransferObject): 21 | nodes: Sequence[CibNodeDto] 22 | -------------------------------------------------------------------------------- /pcs/common/pacemaker/nvset.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import ( 3 | Mapping, 4 | Optional, 5 | Sequence, 6 | ) 7 | 8 | from pcs.common.interface.dto import DataTransferObject 9 | from pcs.common.pacemaker.rule import CibRuleExpressionDto 10 | 11 | 12 | @dataclass(frozen=True) 13 | class CibNvpairDto(DataTransferObject): 14 | id: str # pylint: disable=invalid-name 15 | name: str 16 | value: str 17 | 18 | 19 | @dataclass(frozen=True) 20 | class CibNvsetDto(DataTransferObject): 21 | id: str # pylint: disable=invalid-name 22 | options: Mapping[str, str] 23 | rule: Optional[CibRuleExpressionDto] 24 | nvpairs: Sequence[CibNvpairDto] 25 | 26 | 27 | @dataclass(frozen=True) 28 | class ListCibNvsetDto(DataTransferObject): 29 | nvsets: Sequence[CibNvsetDto] 30 | -------------------------------------------------------------------------------- /pcs/common/pacemaker/resource/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs/common/pacemaker/resource/__init__.py -------------------------------------------------------------------------------- /pcs/common/pacemaker/resource/clone.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import ( 3 | Optional, 4 | Sequence, 5 | ) 6 | 7 | from pcs.common.interface.dto import DataTransferObject 8 | from pcs.common.pacemaker.nvset import CibNvsetDto 9 | 10 | 11 | @dataclass(frozen=True) 12 | class CibResourceCloneDto(DataTransferObject): 13 | id: str # pylint: disable=invalid-name 14 | description: Optional[str] 15 | member_id: str 16 | meta_attributes: Sequence[CibNvsetDto] 17 | instance_attributes: Sequence[CibNvsetDto] 18 | -------------------------------------------------------------------------------- /pcs/common/pacemaker/resource/group.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import ( 3 | Optional, 4 | Sequence, 5 | ) 6 | 7 | from pcs.common.interface.dto import DataTransferObject 8 | from pcs.common.pacemaker.nvset import CibNvsetDto 9 | from pcs.common.types import StringSequence 10 | 11 | 12 | @dataclass(frozen=True) 13 | class CibResourceGroupDto(DataTransferObject): 14 | id: str # pylint: disable=invalid-name 15 | description: Optional[str] 16 | member_ids: StringSequence 17 | meta_attributes: Sequence[CibNvsetDto] 18 | instance_attributes: Sequence[CibNvsetDto] 19 | -------------------------------------------------------------------------------- /pcs/common/pacemaker/resource/list.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from itertools import chain 3 | from typing import Sequence 4 | 5 | from pcs.common.interface.dto import DataTransferObject 6 | 7 | from .bundle import CibResourceBundleDto 8 | from .clone import CibResourceCloneDto 9 | from .group import CibResourceGroupDto 10 | from .primitive import CibResourcePrimitiveDto 11 | 12 | 13 | @dataclass(frozen=True) 14 | class CibResourcesDto(DataTransferObject): 15 | primitives: Sequence[CibResourcePrimitiveDto] 16 | clones: Sequence[CibResourceCloneDto] 17 | groups: Sequence[CibResourceGroupDto] 18 | bundles: Sequence[CibResourceBundleDto] 19 | 20 | 21 | def get_all_resources_ids(resources_dto: CibResourcesDto) -> set[str]: 22 | return set( 23 | chain( 24 | (primitive.id for primitive in resources_dto.primitives), 25 | (group.id for group in resources_dto.groups), 26 | (clone.id for clone in resources_dto.clones), 27 | (bundle.id for bundle in resources_dto.bundles), 28 | ) 29 | ) 30 | 31 | 32 | def get_stonith_resources_ids(resources_dto: CibResourcesDto) -> set[str]: 33 | return { 34 | primitive.id 35 | for primitive in resources_dto.primitives 36 | if primitive.agent_name.standard == "stonith" 37 | } 38 | -------------------------------------------------------------------------------- /pcs/common/pacemaker/resource/operations.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import ( 3 | Optional, 4 | Sequence, 5 | ) 6 | 7 | from pcs.common.const import ( 8 | PcmkOnFailAction, 9 | PcmkRoleType, 10 | ) 11 | from pcs.common.interface.dto import DataTransferObject 12 | from pcs.common.pacemaker.nvset import CibNvsetDto 13 | 14 | OCF_CHECK_LEVEL_INSTANCE_ATTRIBUTE_NAME = "OCF_CHECK_LEVEL" 15 | 16 | 17 | @dataclass(frozen=True) 18 | class CibResourceOperationDto(DataTransferObject): 19 | # pylint: disable=too-many-instance-attributes 20 | id: str # pylint: disable=invalid-name 21 | name: str 22 | interval: str 23 | description: Optional[str] 24 | # exactly one of start_delay and interval_origin should be defined 25 | start_delay: Optional[str] 26 | interval_origin: Optional[str] 27 | timeout: Optional[str] 28 | enabled: Optional[bool] 29 | record_pending: Optional[bool] 30 | role: Optional[PcmkRoleType] 31 | on_fail: Optional[PcmkOnFailAction] 32 | meta_attributes: Sequence[CibNvsetDto] 33 | instance_attributes: Sequence[CibNvsetDto] 34 | 35 | 36 | @dataclass(frozen=True) 37 | class ListCibResourceOperationDto(DataTransferObject): 38 | operations: Sequence[CibResourceOperationDto] 39 | -------------------------------------------------------------------------------- /pcs/common/pacemaker/resource/primitive.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import ( 3 | Optional, 4 | Sequence, 5 | ) 6 | 7 | from pcs.common.interface.dto import DataTransferObject 8 | from pcs.common.pacemaker.nvset import CibNvsetDto 9 | from pcs.common.resource_agent.dto import ResourceAgentNameDto 10 | 11 | from .operations import CibResourceOperationDto 12 | 13 | 14 | @dataclass(frozen=True) 15 | class CibResourcePrimitiveDto(DataTransferObject): 16 | id: str # pylint: disable=invalid-name 17 | agent_name: ResourceAgentNameDto 18 | description: Optional[str] 19 | operations: Sequence[CibResourceOperationDto] 20 | meta_attributes: Sequence[CibNvsetDto] 21 | instance_attributes: Sequence[CibNvsetDto] 22 | utilization: Sequence[CibNvsetDto] 23 | -------------------------------------------------------------------------------- /pcs/common/pacemaker/resource/relations.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import ( 3 | Any, 4 | Mapping, 5 | Sequence, 6 | ) 7 | 8 | from pcs.common.interface.dto import DataTransferObject 9 | from pcs.common.types import ( 10 | ResourceRelationType, 11 | StringSequence, 12 | ) 13 | 14 | 15 | @dataclass(frozen=True) 16 | class RelationEntityDto(DataTransferObject): 17 | id: str # pylint: disable=invalid-name 18 | type: ResourceRelationType 19 | members: StringSequence 20 | metadata: Mapping[str, Any] 21 | 22 | 23 | @dataclass(frozen=True) 24 | class ResourceRelationDto(DataTransferObject): 25 | relation_entity: RelationEntityDto 26 | members: Sequence["ResourceRelationDto"] 27 | is_leaf: bool 28 | -------------------------------------------------------------------------------- /pcs/common/pacemaker/role.py: -------------------------------------------------------------------------------- 1 | from .. import const 2 | 3 | 4 | def get_value_for_cib( 5 | role: const.PcmkRoleType, is_latest_supported: bool 6 | ) -> const.PcmkRoleType: 7 | if is_latest_supported: 8 | return get_value_primary(role) 9 | if role == const.PCMK_ROLE_PROMOTED: 10 | return const.PCMK_ROLE_PROMOTED_LEGACY 11 | if role == const.PCMK_ROLE_UNPROMOTED: 12 | return const.PCMK_ROLE_UNPROMOTED_LEGACY 13 | return role 14 | 15 | 16 | def get_value_primary(role: const.PcmkRoleType) -> const.PcmkRoleType: 17 | if role == const.PCMK_ROLE_PROMOTED_LEGACY: 18 | return const.PCMK_ROLE_PROMOTED 19 | if role == const.PCMK_ROLE_UNPROMOTED_LEGACY: 20 | return const.PCMK_ROLE_UNPROMOTED 21 | return role 22 | -------------------------------------------------------------------------------- /pcs/common/pacemaker/rule.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import ( 3 | Mapping, 4 | Optional, 5 | Sequence, 6 | ) 7 | 8 | from pcs.common.interface.dto import DataTransferObject 9 | from pcs.common.types import ( 10 | CibRuleExpressionType, 11 | CibRuleInEffectStatus, 12 | ) 13 | 14 | 15 | @dataclass(frozen=True) 16 | class CibRuleDateCommonDto(DataTransferObject): 17 | id: str # pylint: disable=invalid-name 18 | options: Mapping[str, str] 19 | 20 | 21 | @dataclass(frozen=True) 22 | class CibRuleExpressionDto(DataTransferObject): 23 | # pylint: disable=too-many-instance-attributes 24 | id: str # pylint: disable=invalid-name 25 | type: CibRuleExpressionType 26 | in_effect: CibRuleInEffectStatus # only valid for type==rule 27 | options: Mapping[str, str] 28 | date_spec: Optional[CibRuleDateCommonDto] 29 | duration: Optional[CibRuleDateCommonDto] 30 | expressions: Sequence["CibRuleExpressionDto"] 31 | as_string: str 32 | -------------------------------------------------------------------------------- /pcs/common/pacemaker/tag.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import Sequence 3 | 4 | from pcs.common.interface.dto import DataTransferObject 5 | from pcs.common.types import StringSequence 6 | 7 | 8 | @dataclass(frozen=True) 9 | class CibTagDto(DataTransferObject): 10 | id: str 11 | idref_list: StringSequence 12 | 13 | 14 | @dataclass(frozen=True) 15 | class CibTagListDto(DataTransferObject): 16 | tags: Sequence[CibTagDto] 17 | -------------------------------------------------------------------------------- /pcs/common/pacemaker/tools.py: -------------------------------------------------------------------------------- 1 | def is_negative_score(score: str) -> bool: 2 | return score.startswith("-") 3 | 4 | 5 | def abs_score(score: str) -> str: 6 | """ 7 | return absolute value of score 8 | """ 9 | if is_negative_score(score): 10 | return score[1:] 11 | return score 12 | -------------------------------------------------------------------------------- /pcs/common/pacemaker/types.py: -------------------------------------------------------------------------------- 1 | from typing import cast 2 | 3 | 4 | class CibResourceDiscovery(str): 5 | __slots__ = () 6 | ALWAYS = cast("CibResourceDiscovery", "always") 7 | NEVER = cast("CibResourceDiscovery", "never") 8 | EXCLUSIVE = cast("CibResourceDiscovery", "exclusive") 9 | 10 | 11 | class CibResourceSetOrdering(str): 12 | __slots__ = () 13 | GROUP = cast("CibResourceSetOrdering", "group") 14 | LISTED = cast("CibResourceSetOrdering", "listed") 15 | 16 | 17 | class CibResourceSetOrderType(str): 18 | __slots__ = () 19 | OPTIONAL = cast("CibResourceSetOrderType", "Optional") 20 | MANDATORY = cast("CibResourceSetOrderType", "Mandatory") 21 | SERIALIZE = cast("CibResourceSetOrderType", "Serialize") 22 | 23 | 24 | class CibTicketLossPolicy(str): 25 | __slots__ = () 26 | STOP = cast("CibTicketLossPolicy", "stop") 27 | DEMOTE = cast("CibTicketLossPolicy", "demote") 28 | FENCE = cast("CibTicketLossPolicy", "fence") 29 | FREEZE = cast("CibTicketLossPolicy", "freeze") 30 | -------------------------------------------------------------------------------- /pcs/common/pcs_pycurl.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | # pylint: disable=unused-wildcard-import 4 | # pylint: disable=wildcard-import 5 | from pycurl import * # noqa: F403 6 | 7 | # This package defines constants which are not present in some older versions 8 | # of pycurl but pcs needs to use them 9 | 10 | required_constants = { 11 | "PROTOCOLS": 181, 12 | "PROTO_HTTPS": 2, 13 | "E_OPERATION_TIMEDOUT": 28, 14 | # these are types of debug messages 15 | # see https://curl.haxx.se/libcurl/c/CURLOPT_DEBUGFUNCTION.html 16 | "DEBUG_TEXT": 0, 17 | "DEBUG_HEADER_IN": 1, 18 | "DEBUG_HEADER_OUT": 2, 19 | "DEBUG_DATA_IN": 3, 20 | "DEBUG_DATA_OUT": 4, 21 | "DEBUG_SSL_DATA_IN": 5, 22 | "DEBUG_SSL_DATA_OUT": 6, 23 | "DEBUG_END": 7, 24 | } 25 | 26 | __current_module = sys.modules[__name__] 27 | 28 | for constant, value in required_constants.items(): 29 | if not hasattr(__current_module, constant): 30 | setattr(__current_module, constant, value) 31 | -------------------------------------------------------------------------------- /pcs/common/permissions/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs/common/permissions/__init__.py -------------------------------------------------------------------------------- /pcs/common/permissions/types.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class PermissionTargetType(str, Enum): 5 | USER = "user" 6 | GROUP = "group" 7 | 8 | 9 | class PermissionAccessType(str, Enum): 10 | READ = "read" 11 | WRITE = "write" 12 | GRANT = "grant" 13 | FULL = "full" 14 | SUPERUSER = "superuser" 15 | -------------------------------------------------------------------------------- /pcs/common/reports/__init__.py: -------------------------------------------------------------------------------- 1 | from . import ( 2 | codes, 3 | const, 4 | item, 5 | messages, 6 | types, 7 | ) 8 | from .conversions import report_dto_to_item 9 | from .dto import ReportItemDto 10 | from .item import ( 11 | ReportItem, 12 | ReportItemContext, 13 | ReportItemList, 14 | ReportItemMessage, 15 | ReportItemSeverity, 16 | get_severity, 17 | get_severity_from_flags, 18 | ) 19 | from .processor import ( 20 | ReportProcessor, 21 | has_errors, 22 | ) 23 | -------------------------------------------------------------------------------- /pcs/common/reports/dto.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import ( 3 | Any, 4 | Mapping, 5 | Optional, 6 | ) 7 | 8 | from pcs.common.interface.dto import DataTransferObject 9 | 10 | from .types import ( 11 | ForceCode, 12 | MessageCode, 13 | SeverityLevel, 14 | ) 15 | 16 | 17 | @dataclass(frozen=True) 18 | class ReportItemSeverityDto(DataTransferObject): 19 | level: SeverityLevel 20 | force_code: Optional[ForceCode] 21 | 22 | 23 | @dataclass(frozen=True) 24 | class ReportItemMessageDto(DataTransferObject): 25 | code: MessageCode 26 | message: str 27 | payload: Mapping[str, Any] 28 | 29 | 30 | @dataclass(frozen=True) 31 | class ReportItemContextDto(DataTransferObject): 32 | node: str 33 | 34 | 35 | @dataclass(frozen=True) 36 | class ReportItemDto(DataTransferObject): 37 | severity: ReportItemSeverityDto 38 | message: ReportItemMessageDto 39 | context: Optional[ReportItemContextDto] 40 | -------------------------------------------------------------------------------- /pcs/common/reports/types.py: -------------------------------------------------------------------------------- 1 | from typing import Collection, NewType 2 | 3 | AddRemoveContainerType = NewType("AddRemoveContainerType", str) 4 | AddRemoveItemType = NewType("AddRemoveItemType", str) 5 | BoothConfigUsedWhere = NewType("BoothConfigUsedWhere", str) 6 | DefaultAddressSource = NewType("DefaultAddressSource", str) 7 | FenceHistoryCommandType = NewType("FenceHistoryCommandType", str) 8 | ForceCode = NewType("ForceCode", str) 9 | # Container would suffice. However, this type is used in interfaces of many 10 | # library commands. When calling the commands via API, their input comes in 11 | # JSON and is transformed by dacite. In JSON, force flags are held in a list, 12 | # there is no other way. And dacite is unable to deserialize a list to 13 | # attributes annotated as Iterable or Container. 14 | ForceFlags = Collection[ForceCode] 15 | MessageCode = NewType("MessageCode", str) 16 | DeprecatedMessageCode = NewType("DeprecatedMessageCode", str) 17 | PcsCommand = NewType("PcsCommand", str) 18 | ReasonType = NewType("ReasonType", str) 19 | ServiceAction = NewType("ServiceAction", str) 20 | SeverityLevel = NewType("SeverityLevel", str) 21 | StonithRestartlessUpdateUnableToPerformReason = NewType( 22 | "StonithRestartlessUpdateUnableToPerformReason", str 23 | ) 24 | StonithWatchdogTimeoutCannotBeSetReason = NewType( 25 | "StonithWatchdogTimeoutCannotBeSetReason", str 26 | ) 27 | -------------------------------------------------------------------------------- /pcs/common/resource_agent/__init__.py: -------------------------------------------------------------------------------- 1 | from . import ( 2 | const, 3 | dto, 4 | ) 5 | -------------------------------------------------------------------------------- /pcs/common/resource_agent/const.py: -------------------------------------------------------------------------------- 1 | # OCF 1.0 doesn't define unique groups, they are defined since OCF 1.1. Pcs 2 | # transforms OCF 1.0 agents to OCF 1.1 structure and therefore needs to create 3 | # a group name for OCF 1.0 unique attrs. The name is: {this_prefix}{attr_name} 4 | DEFAULT_UNIQUE_GROUP_PREFIX = "_pcs_unique_group_" 5 | -------------------------------------------------------------------------------- /pcs/common/services/__init__.py: -------------------------------------------------------------------------------- 1 | from . import ( 2 | drivers, 3 | errors, 4 | interfaces, 5 | types, 6 | ) 7 | -------------------------------------------------------------------------------- /pcs/common/services/common.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=unused-import 2 | from pcs.common.str_tools import join_multilines # noqa: F401 3 | -------------------------------------------------------------------------------- /pcs/common/services/drivers/__init__.py: -------------------------------------------------------------------------------- 1 | from .systemd import SystemdDriver 2 | from .sysvinit_rhel import SysVInitRhelDriver 3 | -------------------------------------------------------------------------------- /pcs/common/services/errors.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | 4 | class ManageServiceError(Exception): 5 | def __init__( 6 | self, 7 | service: str, 8 | message: str, 9 | instance: Optional[str] = None, 10 | ): 11 | self.service = service 12 | self.message = message 13 | self.instance = instance 14 | 15 | 16 | class DisableServiceError(ManageServiceError): 17 | pass 18 | 19 | 20 | class EnableServiceError(ManageServiceError): 21 | pass 22 | 23 | 24 | class StartServiceError(ManageServiceError): 25 | pass 26 | 27 | 28 | class StopServiceError(ManageServiceError): 29 | pass 30 | -------------------------------------------------------------------------------- /pcs/common/services/interfaces/__init__.py: -------------------------------------------------------------------------------- 1 | from .executor import ExecutorInterface 2 | from .manager import ServiceManagerInterface 3 | -------------------------------------------------------------------------------- /pcs/common/services/interfaces/executor.py: -------------------------------------------------------------------------------- 1 | from pcs.common.types import StringSequence 2 | 3 | from ..types import ExecutorResult 4 | 5 | 6 | class ExecutorInterface: 7 | """ 8 | Simple interface for executing external programs. 9 | """ 10 | 11 | def run(self, args: StringSequence) -> ExecutorResult: 12 | """ 13 | args -- Program and its arguments to execute. First item is path to a 14 | executable and rest of the items are arguments which will be provided 15 | to the executable. 16 | 17 | Execute a specified program synchronously and return its result after 18 | it's finished. 19 | """ 20 | raise NotImplementedError() 21 | -------------------------------------------------------------------------------- /pcs/common/services/types.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from .common import join_multilines 4 | 5 | 6 | @dataclass(frozen=True) 7 | class ExecutorResult: 8 | retval: int 9 | stdout: str 10 | stderr: str 11 | 12 | @property 13 | def joined_output(self) -> str: 14 | return join_multilines([self.stderr, self.stdout]) 15 | -------------------------------------------------------------------------------- /pcs/common/services_dto.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import Optional 3 | 4 | from pcs.common.interface.dto import DataTransferObject 5 | 6 | 7 | @dataclass(frozen=True) 8 | class ServiceStatusDto(DataTransferObject): 9 | service: str 10 | installed: Optional[bool] 11 | enabled: Optional[bool] 12 | running: Optional[bool] 13 | 14 | 15 | @dataclass(frozen=True) 16 | class ServicesInfoResultDto(DataTransferObject): 17 | services: list[ServiceStatusDto] 18 | -------------------------------------------------------------------------------- /pcs/daemon/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs/daemon/__init__.py -------------------------------------------------------------------------------- /pcs/daemon/app/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs/daemon/app/__init__.py -------------------------------------------------------------------------------- /pcs/daemon/app/sinatra_common.py: -------------------------------------------------------------------------------- 1 | from pcs.daemon import ruby_pcsd 2 | 3 | 4 | class SinatraMixin: 5 | """ 6 | Sinatra is base class for handlers which calls the Sinatra via wrapper. 7 | It accept ruby wrapper during initialization. It also provides method for 8 | transformation result from sinatra to http response. 9 | """ 10 | 11 | __ruby_pcsd_wrapper: ruby_pcsd.Wrapper 12 | 13 | def initialize_sinatra(self, ruby_pcsd_wrapper: ruby_pcsd.Wrapper): 14 | self.__ruby_pcsd_wrapper = ruby_pcsd_wrapper 15 | 16 | def send_sinatra_result(self, result: ruby_pcsd.SinatraResult): 17 | for name, value in result.headers.items(): 18 | self.set_header(name, value) 19 | # make sure that security related headers, which need to be present in 20 | # all responses, are not overridden by sinatra 21 | self.set_default_headers() 22 | self.set_status(result.status) 23 | self.write(result.body) 24 | 25 | @property 26 | def ruby_pcsd_wrapper(self): 27 | return self.__ruby_pcsd_wrapper 28 | -------------------------------------------------------------------------------- /pcs/daemon/app/ui_common.py: -------------------------------------------------------------------------------- 1 | from tornado.web import StaticFileHandler 2 | 3 | from pcs.daemon.app.common import EnhanceHeadersMixin 4 | 5 | 6 | class AjaxMixin: 7 | @property 8 | def is_ajax(self): 9 | return ( 10 | self.request.headers.get("X-Requested-With", default=None) 11 | == "XMLHttpRequest" 12 | ) 13 | 14 | 15 | class StaticFile(EnhanceHeadersMixin, StaticFileHandler): 16 | # abstract method `data_received` does need to be overridden. This 17 | # method should be implemented to handle streamed request data. 18 | # BUT static files are not streamed SO: 19 | # pylint: disable=abstract-method 20 | def initialize(self, path, default_filename=None): 21 | super().initialize(path, default_filename) 22 | # allow static files to be cached 23 | self.clear_header_cache_control() 24 | -------------------------------------------------------------------------------- /pcs/daemon/app/webui/__init__.py: -------------------------------------------------------------------------------- 1 | from . import ( 2 | core, 3 | session, 4 | sinatra_ui, 5 | ) 6 | -------------------------------------------------------------------------------- /pcs/daemon/async_tasks/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs/daemon/async_tasks/__init__.py -------------------------------------------------------------------------------- /pcs/daemon/async_tasks/types.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from pcs.common.async_tasks.dto import CommandDto 4 | 5 | 6 | @dataclass(frozen=True) 7 | class Command: 8 | command_dto: CommandDto 9 | is_legacy_command: bool = False 10 | -------------------------------------------------------------------------------- /pcs/daemon/async_tasks/worker/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs/daemon/async_tasks/worker/__init__.py -------------------------------------------------------------------------------- /pcs/daemon/async_tasks/worker/communicator.py: -------------------------------------------------------------------------------- 1 | import multiprocessing as mp 2 | from threading import Lock 3 | 4 | from .types import Message 5 | 6 | 7 | class WorkerCommunicator: 8 | def __init__(self, queue: mp.Queue): 9 | self._queue = queue 10 | self._lock = Lock() 11 | self._terminate = False 12 | 13 | def set_terminate(self) -> None: 14 | self._terminate = True 15 | 16 | @property 17 | def is_locked(self) -> bool: 18 | return self._lock.locked() 19 | 20 | def put(self, msg: Message) -> None: 21 | with self._lock: 22 | self._queue.put(msg) 23 | if self._terminate: 24 | raise SystemExit(0) 25 | -------------------------------------------------------------------------------- /pcs/daemon/async_tasks/worker/report_processor.py: -------------------------------------------------------------------------------- 1 | from pcs.common import reports as pcs_reports 2 | 3 | from .communicator import WorkerCommunicator 4 | from .types import Message 5 | 6 | 7 | class WorkerReportProcessor(pcs_reports.ReportProcessor): 8 | """ 9 | Report processor for tasks running inside of the worker pool 10 | """ 11 | 12 | def __init__( 13 | self, 14 | worker_com: WorkerCommunicator, 15 | task_ident: str, 16 | enable_debug: bool = False, 17 | ) -> None: 18 | super().__init__() 19 | self._worker_communicator = worker_com 20 | self._task_ident: str = task_ident 21 | self._debug_enabled = enable_debug 22 | 23 | def _do_report(self, report_item: pcs_reports.item.ReportItem) -> None: 24 | if ( 25 | self._debug_enabled 26 | or report_item.severity.level 27 | != pcs_reports.ReportItemSeverity.DEBUG 28 | ): 29 | self._worker_communicator.put( 30 | Message(self._task_ident, report_item.to_dto()) 31 | ) 32 | -------------------------------------------------------------------------------- /pcs/daemon/async_tasks/worker/types.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import ( 3 | Any, 4 | Union, 5 | ) 6 | 7 | from pcs.common.async_tasks.types import TaskFinishType 8 | from pcs.common.reports import ReportItemDto 9 | from pcs.daemon.async_tasks.types import Command 10 | from pcs.lib.auth.types import AuthUser 11 | 12 | 13 | @dataclass(frozen=True) 14 | class TaskExecuted: 15 | worker_pid: int 16 | 17 | 18 | @dataclass(frozen=True) 19 | class TaskFinished: 20 | task_finish_type: TaskFinishType 21 | result: Any 22 | 23 | 24 | @dataclass(frozen=True) 25 | class Message: 26 | task_ident: str 27 | payload: Union[ 28 | ReportItemDto, 29 | TaskExecuted, 30 | TaskFinished, 31 | ] 32 | 33 | 34 | @dataclass(frozen=True) 35 | class WorkerCommand: 36 | task_ident: str 37 | command: Command 38 | auth_user: AuthUser 39 | -------------------------------------------------------------------------------- /pcs/daemon/systemd.py: -------------------------------------------------------------------------------- 1 | import os.path 2 | import socket 3 | from functools import lru_cache 4 | 5 | from tornado.iostream import IOStream 6 | 7 | from pcs import settings 8 | from pcs.daemon import log 9 | 10 | 11 | async def notify(socket_name): 12 | if socket_name[0] == "@": 13 | # abstract namespace socket 14 | socket_name = "\0" + socket_name[1:] 15 | 16 | log.pcsd.info("Notifying systemd we are running (socket '%s')", socket_name) 17 | try: 18 | stream = IOStream(socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)) 19 | await stream.connect(socket_name) 20 | await stream.write(b"READY=1") 21 | stream.close() 22 | # pylint: disable=broad-except 23 | except Exception as e: 24 | log.pcsd.error("Unable to notify systemd on '%s': %s", socket_name, e) 25 | 26 | 27 | @lru_cache() 28 | def is_systemd(): 29 | return any(os.path.isdir(path) for path in settings.systemd_unit_path) 30 | -------------------------------------------------------------------------------- /pcs/entry_points/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Intention of this package is to provide functions called from 3 | entry points created by setuptools (see setup.py). 4 | """ 5 | -------------------------------------------------------------------------------- /pcs/entry_points/cli.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=unused-import 2 | # pylint: disable=wrong-import-position 3 | 4 | from .common import add_bundled_packages_to_path 5 | 6 | add_bundled_packages_to_path() 7 | 8 | from pcs.app import main # noqa: E402 9 | -------------------------------------------------------------------------------- /pcs/entry_points/common.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from pcs import settings 4 | 5 | 6 | def add_bundled_packages_to_path() -> None: 7 | """ 8 | This function deals with some bundled python dependencies that are 9 | installed in a pcs-specific location rather than in a standard system 10 | location for the python packages. 11 | """ 12 | if settings.pcs_bundled_packages_dir not in sys.path: 13 | sys.path.insert(0, settings.pcs_bundled_packages_dir) 14 | -------------------------------------------------------------------------------- /pcs/entry_points/daemon.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=unused-import 2 | # pylint: disable=wrong-import-position 3 | 4 | from .common import add_bundled_packages_to_path 5 | 6 | add_bundled_packages_to_path() 7 | 8 | from pcs.daemon.run import main # noqa: E402 9 | -------------------------------------------------------------------------------- /pcs/entry_points/internal.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=unused-import 2 | # pylint: disable=wrong-import-position 3 | 4 | from .common import add_bundled_packages_to_path 5 | 6 | add_bundled_packages_to_path() 7 | 8 | from pcs.pcs_internal import main # noqa: E402 9 | -------------------------------------------------------------------------------- /pcs/entry_points/snmp_agent.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=unused-import 2 | # pylint: disable=wrong-import-position 3 | 4 | from .common import add_bundled_packages_to_path 5 | 6 | add_bundled_packages_to_path() 7 | 8 | from pcs.snmp.pcs_snmp_agent import main # noqa: E402 9 | -------------------------------------------------------------------------------- /pcs/lib/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs/lib/__init__.py -------------------------------------------------------------------------------- /pcs/lib/auth/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs/lib/auth/__init__.py -------------------------------------------------------------------------------- /pcs/lib/auth/config/__init__.py: -------------------------------------------------------------------------------- 1 | from . import ( 2 | exporter, 3 | facade, 4 | parser, 5 | types, 6 | ) 7 | -------------------------------------------------------------------------------- /pcs/lib/auth/config/exporter.py: -------------------------------------------------------------------------------- 1 | import dataclasses 2 | from typing import List 3 | 4 | from pcs.lib.file.json import JsonExporter 5 | from pcs.lib.interface.config import ExporterInterface 6 | 7 | from .types import TokenEntry 8 | 9 | 10 | class Exporter(ExporterInterface): 11 | @staticmethod 12 | def export(config_structure: List[TokenEntry]) -> bytes: 13 | return JsonExporter.export( 14 | [dataclasses.asdict(entry) for entry in config_structure] 15 | ) 16 | -------------------------------------------------------------------------------- /pcs/lib/auth/config/types.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | TOKEN_ENTRY_DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S %z" 4 | 5 | 6 | @dataclass(frozen=True) 7 | class TokenEntry: 8 | token: str 9 | username: str 10 | creation_date: str 11 | -------------------------------------------------------------------------------- /pcs/lib/auth/const.py: -------------------------------------------------------------------------------- 1 | from pcs import settings 2 | 3 | SUPERUSER = settings.pacemaker_uname 4 | ADMIN_GROUP = settings.pacemaker_gname 5 | -------------------------------------------------------------------------------- /pcs/lib/auth/types.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import Optional 3 | 4 | from pcs.common.tools import StringCollection 5 | 6 | from . import const 7 | 8 | 9 | @dataclass(frozen=True) 10 | class AuthUser: 11 | username: str 12 | groups: StringCollection 13 | 14 | @property 15 | def is_superuser(self) -> bool: 16 | return self.username == const.SUPERUSER 17 | 18 | 19 | @dataclass(frozen=True) 20 | class DesiredUser: 21 | username: Optional[str] 22 | groups: Optional[StringCollection] 23 | -------------------------------------------------------------------------------- /pcs/lib/booth/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs/lib/booth/__init__.py -------------------------------------------------------------------------------- /pcs/lib/booth/cib.py: -------------------------------------------------------------------------------- 1 | from typing import ( 2 | Optional, 3 | cast, 4 | ) 5 | 6 | from lxml.etree import _Element 7 | 8 | from pcs.lib.booth.constants import DEFAULT_INSTANCE_NAME 9 | 10 | _BOOTH_ATTRIBUTE = "booth-cfg-name" 11 | 12 | 13 | def get_booth_ticket_names( 14 | cib: _Element, booth_instance: Optional[str] = None 15 | ) -> list[str]: 16 | """ 17 | Return names of booth tickets present in CIB for the specified booth 18 | instance 19 | 20 | cib -- element representing the CIB 21 | booth_instance -- name of the booth instance, default instance name is used 22 | if name is not provided 23 | """ 24 | return cast( 25 | list[str], 26 | cib.xpath( 27 | f"status/tickets/ticket_state[@{_BOOTH_ATTRIBUTE}=$instance]/@id", 28 | instance=booth_instance or DEFAULT_INSTANCE_NAME, 29 | ), 30 | ) 31 | 32 | 33 | def get_ticket_names(cib: _Element) -> list[str]: 34 | """ 35 | Return names of all tickets present in CIB 36 | 37 | cib -- element representing the CIB 38 | """ 39 | return cast(list[str], cib.xpath("status/tickets/ticket_state/@id")) 40 | -------------------------------------------------------------------------------- /pcs/lib/booth/constants.py: -------------------------------------------------------------------------------- 1 | from pcs import settings 2 | from pcs.common.types import StringCollection 3 | 4 | DEFAULT_INSTANCE_NAME = "booth" 5 | AUTHFILE_FIX_OPTION = "enable-authfile" 6 | 7 | GLOBAL_KEYS: StringCollection = ( 8 | "transport", 9 | "port", 10 | "name", 11 | "authfile", 12 | "maxtimeskew", 13 | "site", 14 | "arbitrator", 15 | "site-user", 16 | "site-group", 17 | "arbitrator-user", 18 | "arbitrator-group", 19 | "debug", 20 | "ticket", 21 | ) + ( 22 | (AUTHFILE_FIX_OPTION,) 23 | if settings.booth_enable_authfile_set_enabled 24 | or settings.booth_enable_authfile_unset_enabled 25 | else tuple() 26 | ) 27 | 28 | TICKET_KEYS = ( 29 | "acquire-after", 30 | "attr-prereq", 31 | "before-acquire-handler", 32 | "expire", 33 | "mode", 34 | "renewal-freq", 35 | "retries", 36 | "timeout", 37 | "weights", 38 | ) 39 | -------------------------------------------------------------------------------- /pcs/lib/cfgsync/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs/lib/cfgsync/__init__.py -------------------------------------------------------------------------------- /pcs/lib/cfgsync/config/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs/lib/cfgsync/config/__init__.py -------------------------------------------------------------------------------- /pcs/lib/cfgsync/const.py: -------------------------------------------------------------------------------- 1 | from pcs.common.file_type_codes import PCS_KNOWN_HOSTS, PCS_SETTINGS_CONF 2 | 3 | SYNCED_CONFIGS = [PCS_KNOWN_HOSTS, PCS_SETTINGS_CONF] 4 | -------------------------------------------------------------------------------- /pcs/lib/cib/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs/lib/cib/__init__.py -------------------------------------------------------------------------------- /pcs/lib/cib/constraint/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs/lib/cib/constraint/__init__.py -------------------------------------------------------------------------------- /pcs/lib/cib/resource/__init__.py: -------------------------------------------------------------------------------- 1 | from . import ( 2 | bundle, 3 | clone, 4 | common, 5 | group, 6 | guest_node, 7 | hierarchy, 8 | operations, 9 | primitive, 10 | relations, 11 | remote_node, 12 | stonith, 13 | validations, 14 | ) 15 | -------------------------------------------------------------------------------- /pcs/lib/cib/resource/const.py: -------------------------------------------------------------------------------- 1 | from pcs.common.pacemaker.resource.operations import ( 2 | OCF_CHECK_LEVEL_INSTANCE_ATTRIBUTE_NAME, 3 | ) 4 | 5 | OPERATION_ATTRIBUTES = [ 6 | "id", 7 | "description", 8 | "enabled", 9 | "interval", 10 | "interval-origin", 11 | "name", 12 | "on-fail", 13 | "record-pending", 14 | "role", 15 | "start-delay", 16 | "timeout", 17 | OCF_CHECK_LEVEL_INSTANCE_ATTRIBUTE_NAME, 18 | ] 19 | -------------------------------------------------------------------------------- /pcs/lib/cib/resource/types.py: -------------------------------------------------------------------------------- 1 | from typing import ( 2 | Dict, 3 | Mapping, 4 | Optional, 5 | ) 6 | 7 | # TODO temporary definition, we want to overhaul the whole 'resource create' 8 | # command using this and implement a proper dataclass for resource operations 9 | ResourceOperationIn = Mapping[str, Optional[str]] 10 | ResourceOperationOut = Dict[str, Optional[str]] 11 | ResourceOperationFilteredIn = Mapping[str, str] 12 | ResourceOperationFilteredOut = Dict[str, str] 13 | -------------------------------------------------------------------------------- /pcs/lib/cib/rule/__init__.py: -------------------------------------------------------------------------------- 1 | from .cib_to_dto import rule_element_to_dto 2 | from .cib_to_str import RuleToStr 3 | from .expression_part import BoolExpr as RuleRoot 4 | from .in_effect import ( 5 | RuleInEffectEval, 6 | RuleInEffectEvalDummy, 7 | RuleInEffectEvalOneByOne, 8 | ) 9 | from .parsed_to_cib import export as rule_to_cib 10 | from .parser import ( 11 | RuleParseError, 12 | parse_rule, 13 | ) 14 | from .validator import Validator as RuleValidator 15 | -------------------------------------------------------------------------------- /pcs/lib/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs/lib/commands/__init__.py -------------------------------------------------------------------------------- /pcs/lib/commands/constraint/__init__.py: -------------------------------------------------------------------------------- 1 | from . import ( 2 | colocation, 3 | common, 4 | location, 5 | order, 6 | ticket, 7 | ) 8 | -------------------------------------------------------------------------------- /pcs/lib/commands/constraint/colocation.py: -------------------------------------------------------------------------------- 1 | from functools import partial 2 | 3 | from pcs.lib.cib.constraint import colocation 4 | 5 | from . import common 6 | 7 | # configure common constraint command 8 | create_with_set = partial( 9 | common.create_with_set, 10 | colocation.TAG, 11 | colocation.prepare_options_with_set, 12 | ) 13 | -------------------------------------------------------------------------------- /pcs/lib/commands/constraint/order.py: -------------------------------------------------------------------------------- 1 | from functools import partial 2 | 3 | from pcs.lib.cib.constraint import order 4 | 5 | from . import common 6 | 7 | # configure common constraint command 8 | create_with_set = partial( 9 | common.create_with_set, 10 | order.TAG, 11 | order.prepare_options_with_set, 12 | ) 13 | -------------------------------------------------------------------------------- /pcs/lib/communication/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs/lib/communication/__init__.py -------------------------------------------------------------------------------- /pcs/lib/corosync/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs/lib/corosync/__init__.py -------------------------------------------------------------------------------- /pcs/lib/corosync/qdevice_client.py: -------------------------------------------------------------------------------- 1 | from pcs import settings 2 | from pcs.common import reports 3 | from pcs.common.reports.item import ReportItem 4 | from pcs.common.str_tools import join_multilines 5 | from pcs.lib.errors import LibraryError 6 | from pcs.lib.external import CommandRunner 7 | 8 | 9 | def get_status_text(runner: CommandRunner, verbose: bool = False) -> str: 10 | """ 11 | Get quorum device client runtime status in plain text 12 | 13 | verbose -- get more detailed output 14 | """ 15 | cmd = [settings.corosync_qdevice_tool_exec, "-s"] 16 | if verbose: 17 | cmd.append("-v") 18 | stdout, stderr, retval = runner.run(cmd) 19 | if retval != 0: 20 | raise LibraryError( 21 | ReportItem.error( 22 | reports.messages.CorosyncQuorumGetStatusError( 23 | join_multilines([stderr, stdout]) 24 | ) 25 | ) 26 | ) 27 | return stdout 28 | -------------------------------------------------------------------------------- /pcs/lib/dr/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs/lib/dr/__init__.py -------------------------------------------------------------------------------- /pcs/lib/dr/config/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs/lib/dr/config/__init__.py -------------------------------------------------------------------------------- /pcs/lib/dr/env.py: -------------------------------------------------------------------------------- 1 | from pcs.common import file_type_codes 2 | from pcs.lib.file.instance import FileInstance 3 | from pcs.lib.file.toolbox import FileToolbox 4 | from pcs.lib.file.toolbox import for_file_type as get_file_toolbox 5 | 6 | from .config.facade import ( 7 | DrRole, 8 | Facade, 9 | ) 10 | 11 | 12 | class DrEnv: 13 | def __init__(self) -> None: 14 | self._config_file = FileInstance.for_dr_config() 15 | 16 | @staticmethod 17 | def create_facade(role: DrRole) -> Facade: 18 | return Facade.create(role) 19 | 20 | @property 21 | def config(self) -> FileInstance: 22 | return self._config_file 23 | 24 | @staticmethod 25 | def get_config_toolbox() -> FileToolbox: 26 | return get_file_toolbox(file_type_codes.PCS_DR_CONFIG) 27 | -------------------------------------------------------------------------------- /pcs/lib/errors.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from pcs.common.reports import ReportItem 4 | 5 | 6 | class LibraryError(Exception): 7 | def __init__(self, *args: ReportItem, output: Optional[str] = None): 8 | super().__init__(*args) 9 | self._output = output 10 | 11 | @property 12 | def output(self): 13 | return self._output 14 | -------------------------------------------------------------------------------- /pcs/lib/exchange_formats.md: -------------------------------------------------------------------------------- 1 | Exchange formats 2 | ================ 3 | Library exchanges with the client data in the formats described below. 4 | 5 | Resource operation interval duplication 6 | --------------------------------------- 7 | Dictionary. Key is operation name. Value is list of list of interval. 8 | ```python 9 | { 10 | "monitor": [ 11 | ["3600s", "60m", "1h"], 12 | ["60s", "1m"], 13 | ], 14 | }, 15 | ``` 16 | -------------------------------------------------------------------------------- /pcs/lib/file/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs/lib/file/__init__.py -------------------------------------------------------------------------------- /pcs/lib/host/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs/lib/host/__init__.py -------------------------------------------------------------------------------- /pcs/lib/host/config/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs/lib/host/config/__init__.py -------------------------------------------------------------------------------- /pcs/lib/host/config/exporter.py: -------------------------------------------------------------------------------- 1 | import dataclasses 2 | 3 | from pcs.lib.file.json import JsonExporter 4 | from pcs.lib.interface.config import ExporterInterface 5 | 6 | from .types import KnownHosts 7 | 8 | 9 | class Exporter(ExporterInterface): 10 | @staticmethod 11 | def export(config_structure: KnownHosts) -> bytes: 12 | data = dataclasses.asdict(config_structure) 13 | 14 | # remove attribute not stored in file 15 | for host in data["known_hosts"].values(): 16 | del host["name"] 17 | 18 | return JsonExporter.export(data) 19 | -------------------------------------------------------------------------------- /pcs/lib/host/config/facade.py: -------------------------------------------------------------------------------- 1 | from pcs.common.host import PcsKnownHost 2 | from pcs.lib.interface.config import SyncVersionFacadeInterface 3 | 4 | from .types import KnownHosts 5 | 6 | 7 | class Facade(SyncVersionFacadeInterface): 8 | def __init__(self, parsed_config: KnownHosts): 9 | super().__init__(parsed_config) 10 | 11 | @classmethod 12 | def create(cls) -> "Facade": 13 | return cls( 14 | KnownHosts(format_version=1, data_version=1, known_hosts=dict()) 15 | ) 16 | 17 | @property 18 | def config(self) -> KnownHosts: 19 | return self._config 20 | 21 | @property 22 | def data_version(self) -> int: 23 | return self.config.data_version 24 | 25 | @property 26 | def known_hosts(self) -> dict[str, PcsKnownHost]: 27 | return dict(self.config.known_hosts) 28 | -------------------------------------------------------------------------------- /pcs/lib/host/config/types.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import Mapping 3 | 4 | from pcs.common.host import PcsKnownHost 5 | 6 | 7 | @dataclass(frozen=True) 8 | class KnownHosts: 9 | format_version: int 10 | data_version: int 11 | known_hosts: Mapping[str, PcsKnownHost] 12 | -------------------------------------------------------------------------------- /pcs/lib/interface/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs/lib/interface/__init__.py -------------------------------------------------------------------------------- /pcs/lib/pacemaker/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs/lib/pacemaker/__init__.py -------------------------------------------------------------------------------- /pcs/lib/pacemaker/api_result.py: -------------------------------------------------------------------------------- 1 | import os.path 2 | from dataclasses import dataclass 3 | from typing import cast 4 | 5 | from lxml import etree 6 | from lxml.etree import _Element 7 | 8 | from pcs import settings 9 | from pcs.common.tools import xml_fromstring 10 | from pcs.common.types import StringSequence 11 | 12 | 13 | @dataclass(frozen=True) 14 | class Status: 15 | code: int 16 | message: str 17 | errors: StringSequence 18 | 19 | 20 | def get_api_result_dom(xml: str) -> _Element: 21 | # raises etree.XMLSyntaxError and etree.DocumentInvalid 22 | rng = settings.pacemaker_api_result_schema 23 | dom = xml_fromstring(xml) 24 | if os.path.isfile(rng): 25 | etree.RelaxNG(file=rng).assertValid(dom) 26 | return dom 27 | 28 | 29 | def get_status_from_api_result(dom: _Element) -> Status: 30 | errors = [] 31 | status_el = cast(_Element, dom.find("./status")) 32 | errors_el = status_el.find("errors") 33 | if errors_el is not None: 34 | errors = [ 35 | str((error_el.text or "")).strip() 36 | for error_el in errors_el.iterfind("error") 37 | ] 38 | return Status( 39 | code=int(str(status_el.get("code"))), 40 | message=str(status_el.get("message")), 41 | errors=errors, 42 | ) 43 | -------------------------------------------------------------------------------- /pcs/lib/permissions/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs/lib/permissions/__init__.py -------------------------------------------------------------------------------- /pcs/lib/permissions/config/__init__.py: -------------------------------------------------------------------------------- 1 | from . import ( 2 | exporter, 3 | facade, 4 | parser, 5 | types, 6 | ) 7 | -------------------------------------------------------------------------------- /pcs/lib/permissions/config/exporter.py: -------------------------------------------------------------------------------- 1 | import dataclasses 2 | 3 | from pcs.lib.file.json import JsonExporter 4 | from pcs.lib.interface.config import ExporterInterface 5 | 6 | from .types import ConfigV2 7 | 8 | 9 | class ExporterV2(ExporterInterface): 10 | @staticmethod 11 | def export(config_structure: ConfigV2) -> bytes: 12 | data = dataclasses.asdict(config_structure) 13 | data["format_version"] = 2 14 | return JsonExporter.export(data) 15 | -------------------------------------------------------------------------------- /pcs/lib/permissions/config/types.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import ( 3 | Collection, 4 | Sequence, 5 | ) 6 | 7 | from pcs.common.permissions.types import ( 8 | PermissionAccessType, 9 | PermissionTargetType, 10 | ) 11 | from pcs.common.types import StringSequence 12 | 13 | 14 | @dataclass(frozen=True) 15 | class ClusterEntry: 16 | name: str 17 | nodes: StringSequence 18 | 19 | 20 | @dataclass(frozen=True) 21 | class PermissionEntry: 22 | name: str 23 | type: PermissionTargetType 24 | allow: Collection[PermissionAccessType] 25 | 26 | 27 | @dataclass(frozen=True) 28 | class ClusterPermissions: 29 | local_cluster: Sequence[PermissionEntry] 30 | 31 | 32 | @dataclass(frozen=True) 33 | class ConfigV2: 34 | data_version: int 35 | clusters: Sequence[ClusterEntry] 36 | permissions: ClusterPermissions 37 | -------------------------------------------------------------------------------- /pcs/lib/resource_agent/__init__.py: -------------------------------------------------------------------------------- 1 | from pcs.common.resource_agent.dto import ( 2 | ListResourceAgentNameDto, 3 | ResourceAgentActionDto, 4 | ResourceAgentMetadataDto, 5 | ResourceAgentNameDto, 6 | ResourceAgentParameterDto, 7 | ) 8 | 9 | from .error import ( 10 | AgentNameGuessFoundMoreThanOne, 11 | AgentNameGuessFoundNone, 12 | InvalidResourceAgentName, 13 | ResourceAgentError, 14 | UnableToGetAgentMetadata, 15 | UnsupportedOcfVersion, 16 | resource_agent_error_to_report_item, 17 | ) 18 | from .facade import ( 19 | ResourceAgentFacade, 20 | ResourceAgentFacadeFactory, 21 | ) 22 | from .list import ( 23 | find_one_resource_agent_by_type, 24 | list_resource_agents, 25 | list_resource_agents_ocf_providers, 26 | list_resource_agents_standards, 27 | list_resource_agents_standards_and_providers, 28 | ) 29 | from .name import split_resource_agent_name 30 | from .types import ( 31 | ResourceAgentAction, 32 | ResourceAgentMetadata, 33 | ResourceAgentName, 34 | ResourceAgentParameter, 35 | StandardProviderTuple, 36 | ) 37 | -------------------------------------------------------------------------------- /pcs/lib/resource_agent/const.py: -------------------------------------------------------------------------------- 1 | from pcs.common.resource_agent import const 2 | 3 | from .types import ( 4 | _FAKE_AGENT_STANDARD, 5 | CrmAttrAgent, 6 | FakeAgentName, 7 | OcfVersion, 8 | ) 9 | 10 | OCF_1_0 = OcfVersion("1.0") 11 | OCF_1_1 = OcfVersion("1.1") 12 | SUPPORTED_OCF_VERSIONS = [OCF_1_0, OCF_1_1] 13 | 14 | FAKE_AGENT_STANDARD = _FAKE_AGENT_STANDARD 15 | CLUSTER_OPTIONS = CrmAttrAgent("cluster-options") 16 | PACEMAKER_FENCED = FakeAgentName("pacemaker-fenced") 17 | 18 | 19 | STONITH_ACTION_REPLACED_BY = ["pcmk_off_action", "pcmk_reboot_action"] 20 | DEFAULT_UNIQUE_GROUP_PREFIX = const.DEFAULT_UNIQUE_GROUP_PREFIX 21 | -------------------------------------------------------------------------------- /pcs/pcs.in: -------------------------------------------------------------------------------- 1 | #!@PYTHON@ 2 | import os.path 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | CURRENT_DIR = os.path.dirname(os.path.abspath(__file__)) 7 | 8 | # We prevent to import some module from this dir instead of e.g. standard module. 9 | # There is no reason to import anything from this module. 10 | sys.path.remove(CURRENT_DIR) 11 | 12 | # Add pcs package. 13 | PACKAGE_DIR = os.path.dirname(CURRENT_DIR) 14 | BUNDLED_PACKAGES_DIR = os.path.join(PACKAGE_DIR, "@PCS_BUNDLED_DIR_LOCAL@", "packages") 15 | sys.path.insert(0, BUNDLED_PACKAGES_DIR) 16 | sys.path.insert(0, PACKAGE_DIR) 17 | 18 | # pylint: disable=wrong-import-position 19 | # we need settings to be imported from a path defined above 20 | from pcs import settings 21 | 22 | settings.pcsd_exec_location = os.path.join(PACKAGE_DIR, "pcsd") 23 | settings.pcsd_gem_path = os.path.join(PACKAGE_DIR, "@PCSD_BUNDLED_DIR_ROOT_LOCAL@") 24 | settings.pcs_data_dir = os.path.join(PACKAGE_DIR, "data") 25 | 26 | if "-d" in sys.argv: 27 | from pcs.daemon.run import main 28 | argv = sys.argv[:] 29 | argv.remove("-d") 30 | main(argv[1:]) 31 | else: 32 | from pcs import app 33 | app.main(sys.argv[1:]) 34 | -------------------------------------------------------------------------------- /pcs/pcs_internal.in: -------------------------------------------------------------------------------- 1 | #!@PYTHON@ 2 | import os.path 3 | import sys 4 | 5 | CURRENT_DIR = os.path.dirname(os.path.abspath(__file__)) 6 | 7 | # We prevent to import some module from this dir instead of e.g. standard module. 8 | # There is no reason to import anything from this module. 9 | sys.path.remove(CURRENT_DIR) 10 | 11 | # Add pcs package. 12 | PACKAGE_DIR = os.path.dirname(CURRENT_DIR) 13 | BUNDLED_PACKAGES_DIR = os.path.join(PACKAGE_DIR, "@PCS_BUNDLED_DIR_LOCAL@", "packages") 14 | sys.path.insert(0, BUNDLED_PACKAGES_DIR) 15 | sys.path.insert(0, PACKAGE_DIR) 16 | 17 | 18 | from pcs import ( 19 | pcs_internal, 20 | settings, 21 | ) 22 | 23 | settings.pcsd_exec_location = os.path.join(PACKAGE_DIR, "pcsd") 24 | settings.pcsd_gem_path = os.path.join(PACKAGE_DIR, "@PCSD_BUNDLED_DIR_ROOT_LOCAL@") 25 | settings.pcs_data_dir = os.path.join(PACKAGE_DIR, "data") 26 | pcs_internal.main() 27 | -------------------------------------------------------------------------------- /pcs/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs/py.typed -------------------------------------------------------------------------------- /pcs/snmp/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs/snmp/__init__.py -------------------------------------------------------------------------------- /pcs/snmp/agentx/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs/snmp/agentx/__init__.py -------------------------------------------------------------------------------- /pcs/snmp/conf/pcs_snmp_agent: -------------------------------------------------------------------------------- 1 | # pcs snmp agent configuration file 2 | 3 | # Enable logging of dubugging messages 4 | #PCS_SNMP_AGENT_DEBUG=false 5 | 6 | # Data update interval in seconds 7 | #PCS_SNMP_AGENT_UPDATE_INTERVAL=30 8 | -------------------------------------------------------------------------------- /pcs/snmp/mibs/PCMK-PCS-MIB.txt: -------------------------------------------------------------------------------- 1 | PACEMAKER-PCS-MIB DEFINITIONS ::= BEGIN 2 | 3 | IMPORTS 4 | pacemaker FROM PACEMAKER-MIB 5 | MODULE-IDENTITY FROM SNMPv2-SMI; 6 | 7 | pcmkPcs MODULE-IDENTITY 8 | LAST-UPDATED "201709260000Z" 9 | ORGANIZATION "www.clusterlabs.org" 10 | CONTACT-INFO "email: users@clusterlabs.org" 11 | DESCRIPTION "Pacemaker/corosync cluster MIB" 12 | REVISION "201709260000Z" 13 | DESCRIPTION "initial MIB version" 14 | ::= { pacemaker 100 } 15 | END 16 | 17 | -------------------------------------------------------------------------------- /pcs/snmp/pcs_snmp_agent.in: -------------------------------------------------------------------------------- 1 | #!@PYTHON@ 2 | import os.path 3 | import sys 4 | 5 | PACKAGE_DIR = os.path.dirname(os.path.dirname(os.path.dirname( 6 | os.path.abspath(__file__) 7 | ))) 8 | sys.path.insert(0, PACKAGE_DIR) 9 | 10 | import pcs 11 | 12 | pcs.settings.pcs_bundled_packages_dir = os.path.join( 13 | PACKAGE_DIR, "pcs/bundled/packages" 14 | ) 15 | pcs.settings.pcsd_exec_location = os.path.join(PACKAGE_DIR, "pcsd") 16 | pcs.snmp.pcs_snmp_agent.main(sys.argv) 17 | -------------------------------------------------------------------------------- /pcs/snmp/pcs_snmp_agent.service.in: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=SNMP agent for pacemaker cluster 3 | Documentation=man:pcs_snmp_agent(8) 4 | Requires=snmpd.service 5 | 6 | [Service] 7 | EnvironmentFile=@CONF_DIR@/pcs_snmp_agent 8 | ExecStart=@LIB_DIR@/pcs/pcs_snmp_agent 9 | Type=simple 10 | TimeoutSec=500 11 | 12 | [Install] 13 | WantedBy=multi-user.target 14 | -------------------------------------------------------------------------------- /pcs/snmp/settings.py.in: -------------------------------------------------------------------------------- 1 | LOG_FILE = "@LOCALSTATEDIR@/log/pcsd/pcs_snmp_agent.log" 2 | ENTERPRISES_OID = "1.3.6.1.4.1" 3 | PACEMAKER_OID = ENTERPRISES_OID + ".32723" 4 | PCS_OID = PACEMAKER_OID + ".100" 5 | DEFAULT_UPDATE_INTERVAL = 30 6 | -------------------------------------------------------------------------------- /pcs/snmp/updaters/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs/snmp/updaters/__init__.py -------------------------------------------------------------------------------- /pcs_test/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | TEST_ROOT = os.path.realpath(os.path.dirname(os.path.abspath(__file__))) 4 | PROJECT_ROOT = os.path.dirname(TEST_ROOT) 5 | -------------------------------------------------------------------------------- /pcs_test/api_v2_client.in: -------------------------------------------------------------------------------- 1 | #!@PYTHON@ 2 | import os.path 3 | import sys 4 | 5 | CURRENT_DIR = os.path.dirname(os.path.abspath(__file__)) 6 | 7 | TEST_INSTALLED = os.environ.get("PCS_TEST.TEST_INSTALLED", "0") == "1" 8 | 9 | if TEST_INSTALLED: 10 | BUNDLED_PACKAGES_DIR = os.path.join("@PCS_BUNDLED_DIR@", "packages") 11 | else: 12 | PACKAGE_DIR = os.path.dirname(CURRENT_DIR) 13 | sys.path.insert(0, PACKAGE_DIR) 14 | BUNDLED_PACKAGES_DIR = os.path.join(PACKAGE_DIR, "@PCS_BUNDLED_DIR_LOCAL@", "packages") 15 | 16 | sys.path.insert(0, BUNDLED_PACKAGES_DIR) 17 | 18 | from api_v2_client import main 19 | 20 | main() 21 | -------------------------------------------------------------------------------- /pcs_test/resources/capabilities.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | This capability is available in pcs. 7 | 8 | 9 | 10 | 11 | 12 | This capability is available in pcsd. 13 | 14 | 15 | 16 | 17 | 18 | This capability is available in both pcs and pcsd. 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /pcs_test/resources/cib-empty-1.2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /pcs_test/resources/cib-empty-3.1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /pcs_test/resources/cib-empty-3.2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /pcs_test/resources/cib-empty-3.3.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /pcs_test/resources/cib-empty-3.4.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /pcs_test/resources/cib-empty-3.5.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /pcs_test/resources/cib-empty-3.7.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /pcs_test/resources/cib-empty-3.9.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /pcs_test/resources/cib-empty-with3nodes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /pcs_test/resources/cib-empty-withnodes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /pcs_test/resources/cib-empty.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /pcs_test/resources/cib-property.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /pcs_test/resources/cib-resources.xml: -------------------------------------------------------------------------------- 1 | cib-all.xml -------------------------------------------------------------------------------- /pcs_test/resources/cluster-options_metadata.xml: -------------------------------------------------------------------------------- 1 | ../tools/bin_mock/pcmk/pacemaker_metadata.d/cluster_options.xml -------------------------------------------------------------------------------- /pcs_test/resources/commands-alert: -------------------------------------------------------------------------------- 1 | # set of commands to generate various alerts configuration 2 | pcs -- alert create path=/path/1 id=alert1; 3 | pcs -- alert create path=/path/2 id=alert2; 4 | pcs -- alert recipient add alert2 value=test_value_1 id=alert2-recipient1; 5 | pcs -- alert recipient add alert2 value=test_value_2 id=alert2-recipient2 description='alert2 recipient2 description'; 6 | pcs -- alert create path=/path/all id=alert-all description='alert all options' options aai1n=aai1v aai2n=aai2v meta aam1n=aam1v aam2n=aam2v; 7 | pcs -- alert recipient add alert-all value=value-all id=alert-all-recipient description='all options recipient' options aar1i1n=aar1i1v aar1i2n=aar1i2v meta aar1m1n=aar1m1v aar1m2n=aar1m2v 8 | -------------------------------------------------------------------------------- /pcs_test/resources/corosync-3nodes-qdevice-heuristics.conf: -------------------------------------------------------------------------------- 1 | totem { 2 | version: 2 3 | secauth: off 4 | cluster_name: test99 5 | transport: udpu 6 | } 7 | 8 | nodelist { 9 | node { 10 | ring0_addr: rh7-1 11 | nodeid: 1 12 | name: rh7-1 13 | } 14 | 15 | node { 16 | ring0_addr: rh7-2 17 | nodeid: 2 18 | name: rh7-2 19 | } 20 | 21 | node { 22 | ring0_addr: rh7-3 23 | nodeid: 3 24 | name: rh7-3 25 | } 26 | } 27 | 28 | quorum { 29 | provider: corosync_votequorum 30 | 31 | device { 32 | model: net 33 | 34 | net { 35 | host: 127.0.0.1 36 | } 37 | 38 | heuristics { 39 | mode: on 40 | exec_ls: /usr/bin/test -f /tmp/test 41 | } 42 | } 43 | } 44 | 45 | logging { 46 | to_syslog: yes 47 | } 48 | -------------------------------------------------------------------------------- /pcs_test/resources/corosync-3nodes-qdevice.conf: -------------------------------------------------------------------------------- 1 | totem { 2 | version: 2 3 | secauth: off 4 | cluster_name: test99 5 | transport: udpu 6 | } 7 | 8 | nodelist { 9 | node { 10 | ring0_addr: rh7-1 11 | nodeid: 1 12 | name: rh7-1 13 | } 14 | 15 | node { 16 | ring0_addr: rh7-2 17 | nodeid: 2 18 | name: rh7-2 19 | } 20 | 21 | node { 22 | ring0_addr: rh7-3 23 | nodeid: 3 24 | name: rh7-3 25 | } 26 | } 27 | 28 | quorum { 29 | provider: corosync_votequorum 30 | 31 | device { 32 | model: net 33 | 34 | net { 35 | host: 127.0.0.1 36 | } 37 | } 38 | } 39 | 40 | logging { 41 | to_syslog: yes 42 | } 43 | -------------------------------------------------------------------------------- /pcs_test/resources/corosync-3nodes.conf: -------------------------------------------------------------------------------- 1 | totem { 2 | version: 2 3 | secauth: off 4 | cluster_name: test99 5 | transport: udpu 6 | } 7 | 8 | nodelist { 9 | node { 10 | ring0_addr: rh7-1 11 | nodeid: 1 12 | name: rh7-1 13 | } 14 | 15 | node { 16 | ring0_addr: rh7-2 17 | nodeid: 2 18 | name: rh7-2 19 | } 20 | 21 | node { 22 | ring0_addr: rh7-3 23 | nodeid: 3 24 | name: rh7-3 25 | } 26 | } 27 | 28 | quorum { 29 | provider: corosync_votequorum 30 | } 31 | 32 | logging { 33 | to_syslog: yes 34 | } 35 | -------------------------------------------------------------------------------- /pcs_test/resources/corosync-no-node-names.conf: -------------------------------------------------------------------------------- 1 | totem { 2 | version: 2 3 | secauth: off 4 | cluster_name: test99 5 | transport: udpu 6 | } 7 | 8 | nodelist { 9 | node { 10 | ring0_addr: rh7-1 11 | nodeid: 1 12 | } 13 | 14 | node { 15 | ring0_addr: rh7-2 16 | nodeid: 2 17 | } 18 | } 19 | 20 | quorum { 21 | provider: corosync_votequorum 22 | two_node: 1 23 | } 24 | 25 | logging { 26 | to_syslog: yes 27 | } 28 | -------------------------------------------------------------------------------- /pcs_test/resources/corosync-qdevice.conf: -------------------------------------------------------------------------------- 1 | totem { 2 | version: 2 3 | secauth: off 4 | cluster_name: test99 5 | transport: udpu 6 | } 7 | 8 | nodelist { 9 | node { 10 | ring0_addr: rh7-1 11 | nodeid: 1 12 | name: rh7-1 13 | } 14 | 15 | node { 16 | ring0_addr: rh7-2 17 | nodeid: 2 18 | name: rh7-2 19 | } 20 | } 21 | 22 | quorum { 23 | provider: corosync_votequorum 24 | 25 | device { 26 | model: net 27 | 28 | net { 29 | host: 127.0.0.1 30 | } 31 | } 32 | } 33 | 34 | logging { 35 | to_syslog: yes 36 | } 37 | -------------------------------------------------------------------------------- /pcs_test/resources/corosync-some-node-names.conf: -------------------------------------------------------------------------------- 1 | totem { 2 | version: 2 3 | secauth: off 4 | cluster_name: test99 5 | transport: udpu 6 | } 7 | 8 | nodelist { 9 | node { 10 | ring0_addr: rh7-1 11 | nodeid: 1 12 | } 13 | 14 | node { 15 | ring0_addr: rh7-2 16 | nodeid: 2 17 | name: rh7-2 18 | } 19 | } 20 | 21 | quorum { 22 | provider: corosync_votequorum 23 | two_node: 1 24 | } 25 | 26 | logging { 27 | to_syslog: yes 28 | } 29 | -------------------------------------------------------------------------------- /pcs_test/resources/corosync.conf: -------------------------------------------------------------------------------- 1 | totem { 2 | version: 2 3 | secauth: off 4 | cluster_name: test99 5 | transport: udpu 6 | } 7 | 8 | nodelist { 9 | node { 10 | ring0_addr: rh7-1 11 | nodeid: 1 12 | name: rh7-1 13 | } 14 | 15 | node { 16 | ring0_addr: rh7-2 17 | nodeid: 2 18 | name: rh7-2 19 | } 20 | } 21 | 22 | quorum { 23 | provider: corosync_votequorum 24 | two_node: 1 25 | } 26 | 27 | logging { 28 | to_syslog: yes 29 | } 30 | -------------------------------------------------------------------------------- /pcs_test/resources/crm_mon.minimal.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /pcs_test/resources/fenced_metadata.xml: -------------------------------------------------------------------------------- 1 | ../tools/bin_mock/pcmk/pacemaker_metadata.d/pacemaker_fenced.xml -------------------------------------------------------------------------------- /pcs_test/resources/known-hosts: -------------------------------------------------------------------------------- 1 | { 2 | "format_version": 1, 3 | "data_version": 5, 4 | "known_hosts": { 5 | "node1": { 6 | "dest_list": [ 7 | { 8 | "addr": "10.0.1.1", 9 | "port": 2224 10 | }, 11 | { 12 | "addr": "10.0.2.1", 13 | "port": 2225 14 | } 15 | ], 16 | "token": "abcde" 17 | }, 18 | "node2": { 19 | "dest_list": [ 20 | { 21 | "addr": "10.0.1.2", 22 | "port": 2234 23 | }, 24 | { 25 | "addr": "10.0.2.2", 26 | "port": 2235 27 | } 28 | ], 29 | "token": "fghij" 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /pcs_test/resources/node-commands: -------------------------------------------------------------------------------- 1 | pcs node attribute rh7-1 a=1 b=1 2 | pcs node attribute rh7-2 a=1 b=1 3 | pcs node utilization rh7-1 cpu=4 ram=32 4 | pcs node utilization rh7-2 cpu=8 ram=64 5 | -------------------------------------------------------------------------------- /pcs_test/resources/pcmk_api_rng/api-result.rng: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /pcs_test/resources/pcmk_api_rng/crm_attribute-2.36.rng: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /pcs_test/resources/pcmk_api_rng/digests-2.9.rng: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /pcs_test/resources/pcmk_api_rng/node-attrs-2.8.rng: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /pcs_test/resources/pcmk_api_rng/pacemakerd-health-2.25.rng: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /pcs_test/resources/pcmk_api_rng/status-2.0.rng: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /pcs_test/resources/resource_agent_ocf_heartbeat_dummy_utf8.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | This is a Dummy Resource Agent for testing utf-8 in metadata: ® 8 | 9 | 1.0 10 | 11 | 12 | 13 | 14 | Location to store the resource state in: ® 15 | 16 | State file: ® 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /pcs_test/resources/resource_agent_ocf_pacemaker_remote.xml: -------------------------------------------------------------------------------- 1 | ../tools/bin_mock/pcmk/crm_resource.d/ocf__pacemaker__remote_metadata.xml -------------------------------------------------------------------------------- /pcs_test/resources/resource_agent_systemd_chronyd.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 1.1 5 | 6 | NTP client/server 7 | 8 | systemd unit file for chronyd 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /pcs_test/resources/stonith_agent_fence_custom_actions.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | This is a testing fence agent. Its purpose is to provide a mock of a fence 8 | agent which is always available no matter what is the configuration of a 9 | system pcs test suite runs on. 10 | 11 | https://github.com/ClusterLabs/pcs 12 | 13 | 14 | 15 | An example of an optional attribute 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /pcs_test/resources/stonith_agent_fence_unfencing.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | This is a testing fence agent. Its purpose is to provide a mock of a fence 8 | agent which is always available no matter what is the configuration of a 9 | system pcs test suite runs on. 10 | 11 | https://github.com/ClusterLabs/pcs 12 | 13 | 14 | 15 | An example of an optional attribute 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /pcs_test/settings.py.in: -------------------------------------------------------------------------------- 1 | pacemaker_version_rng = "@PCMK_SCHEMA_DIR@/versions.rng" 2 | -------------------------------------------------------------------------------- /pcs_test/suite.in: -------------------------------------------------------------------------------- 1 | #!@PYTHON@ 2 | import os.path 3 | import sys 4 | 5 | from suite import main 6 | 7 | CURRENT_DIR = os.path.dirname(os.path.abspath(__file__)) 8 | 9 | # We prevent to import some module from this dir instead of e.g. standard module. 10 | # There is no reason to import anything from this module. 11 | 12 | # Add pcs package. 13 | PACKAGE_DIR = os.path.dirname(CURRENT_DIR) 14 | BUNDLED_PACKAGES_DIR = os.path.join(PACKAGE_DIR, "@PCS_BUNDLED_DIR_LOCAL@", "packages") 15 | 16 | sys.path.remove(CURRENT_DIR) 17 | sys.path.insert(0, BUNDLED_PACKAGES_DIR) 18 | main() 19 | -------------------------------------------------------------------------------- /pcs_test/tier0/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs_test/tier0/__init__.py -------------------------------------------------------------------------------- /pcs_test/tier0/cli/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs_test/tier0/cli/__init__.py -------------------------------------------------------------------------------- /pcs_test/tier0/cli/alert/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs_test/tier0/cli/alert/__init__.py -------------------------------------------------------------------------------- /pcs_test/tier0/cli/booth/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs_test/tier0/cli/booth/__init__.py -------------------------------------------------------------------------------- /pcs_test/tier0/cli/cluster/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs_test/tier0/cli/cluster/__init__.py -------------------------------------------------------------------------------- /pcs_test/tier0/cli/cluster_property/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs_test/tier0/cli/cluster_property/__init__.py -------------------------------------------------------------------------------- /pcs_test/tier0/cli/common/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs_test/tier0/cli/common/__init__.py -------------------------------------------------------------------------------- /pcs_test/tier0/cli/common/test_middleware.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | 3 | from pcs.cli.common import middleware 4 | 5 | 6 | class MiddlewareBuildTest(TestCase): 7 | def test_run_middleware_correctly_chained(self): 8 | log = [] 9 | 10 | def command(lib, argv, modifiers): 11 | log.append("command: {0}, {1}, {2}".format(lib, argv, modifiers)) 12 | 13 | def mdw1(_next, lib, argv, modifiers): 14 | log.append("mdw1 start: {0}, {1}, {2}".format(lib, argv, modifiers)) 15 | _next(lib, argv, modifiers) 16 | log.append("mdw1 done") 17 | 18 | def mdw2(_next, lib, argv, modifiers): 19 | log.append("mdw2 start: {0}, {1}, {2}".format(lib, argv, modifiers)) 20 | _next(lib, argv, modifiers) 21 | log.append("mdw2 done") 22 | 23 | run_with_middleware = middleware.build(mdw1, mdw2) 24 | run_with_middleware(command, "1", "2", "3") 25 | self.assertEqual( 26 | log, 27 | [ 28 | "mdw1 start: 1, 2, 3", 29 | "mdw2 start: 1, 2, 3", 30 | "command: 1, 2, 3", 31 | "mdw2 done", 32 | "mdw1 done", 33 | ], 34 | ) 35 | -------------------------------------------------------------------------------- /pcs_test/tier0/cli/common/test_tools.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | 3 | from pcs.cli.common import tools 4 | 5 | 6 | class TimeoutToSecondsLegacyTest(TestCase): 7 | def test_valid(self): 8 | self.assertEqual(10, tools.timeout_to_seconds_legacy(10)) 9 | self.assertEqual(10, tools.timeout_to_seconds_legacy("10")) 10 | self.assertEqual(10, tools.timeout_to_seconds_legacy("10s")) 11 | self.assertEqual(10, tools.timeout_to_seconds_legacy("10sec")) 12 | self.assertEqual(600, tools.timeout_to_seconds_legacy("10m")) 13 | self.assertEqual(600, tools.timeout_to_seconds_legacy("10min")) 14 | self.assertEqual(36000, tools.timeout_to_seconds_legacy("10h")) 15 | self.assertEqual(36000, tools.timeout_to_seconds_legacy("10hr")) 16 | 17 | def test_invalid(self): 18 | self.assertEqual(-10, tools.timeout_to_seconds_legacy(-10)) 19 | self.assertEqual("1a1s", tools.timeout_to_seconds_legacy("1a1s")) 20 | self.assertEqual("10mm", tools.timeout_to_seconds_legacy("10mm")) 21 | self.assertEqual("10mim", tools.timeout_to_seconds_legacy("10mim")) 22 | self.assertEqual("aaa", tools.timeout_to_seconds_legacy("aaa")) 23 | self.assertEqual("", tools.timeout_to_seconds_legacy("")) 24 | -------------------------------------------------------------------------------- /pcs_test/tier0/cli/constraint/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs_test/tier0/cli/constraint/__init__.py -------------------------------------------------------------------------------- /pcs_test/tier0/cli/constraint/location/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs_test/tier0/cli/constraint/location/__init__.py -------------------------------------------------------------------------------- /pcs_test/tier0/cli/constraint/output/test_all.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | 3 | from pcs.cli.constraint.output import filter_constraints_by_rule_expired_status 4 | 5 | from pcs_test.tools.constraints_dto import get_all_constraints 6 | from pcs_test.tools.custom_mock import RuleInEffectEvalMock 7 | 8 | 9 | class FilterConstraintsByRuleExpiredStatus(TestCase): 10 | def setUp(self): 11 | self.constraint_dtos_with_expired = get_all_constraints( 12 | RuleInEffectEvalMock({}), include_expired=True 13 | ) 14 | self.constraint_dtos_without_expired = get_all_constraints( 15 | RuleInEffectEvalMock({}), include_expired=False 16 | ) 17 | 18 | def test_include_expired(self): 19 | self.assertEqual( 20 | filter_constraints_by_rule_expired_status( 21 | self.constraint_dtos_with_expired, include_expired=True 22 | ), 23 | self.constraint_dtos_with_expired, 24 | ) 25 | 26 | def test_do_not_include_expired(self): 27 | self.assertEqual( 28 | filter_constraints_by_rule_expired_status( 29 | self.constraint_dtos_with_expired, include_expired=False 30 | ), 31 | self.constraint_dtos_without_expired, 32 | ) 33 | -------------------------------------------------------------------------------- /pcs_test/tier0/cli/constraint_colocation/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs_test/tier0/cli/constraint_colocation/__init__.py -------------------------------------------------------------------------------- /pcs_test/tier0/cli/constraint_ticket/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs_test/tier0/cli/constraint_ticket/__init__.py -------------------------------------------------------------------------------- /pcs_test/tier0/cli/node/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs_test/tier0/cli/node/__init__.py -------------------------------------------------------------------------------- /pcs_test/tier0/cli/query/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs_test/tier0/cli/query/__init__.py -------------------------------------------------------------------------------- /pcs_test/tier0/cli/reports/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs_test/tier0/cli/reports/__init__.py -------------------------------------------------------------------------------- /pcs_test/tier0/cli/resource/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs_test/tier0/cli/resource/__init__.py -------------------------------------------------------------------------------- /pcs_test/tier0/cli/stonith/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs_test/tier0/cli/stonith/__init__.py -------------------------------------------------------------------------------- /pcs_test/tier0/cli/stonith/levels/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs_test/tier0/cli/stonith/levels/__init__.py -------------------------------------------------------------------------------- /pcs_test/tier0/cli/tag/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs_test/tier0/cli/tag/__init__.py -------------------------------------------------------------------------------- /pcs_test/tier0/common/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs_test/tier0/common/__init__.py -------------------------------------------------------------------------------- /pcs_test/tier0/common/communication/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs_test/tier0/common/communication/__init__.py -------------------------------------------------------------------------------- /pcs_test/tier0/common/interface/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs_test/tier0/common/interface/__init__.py -------------------------------------------------------------------------------- /pcs_test/tier0/common/pacemaker/constraint/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs_test/tier0/common/pacemaker/constraint/__init__.py -------------------------------------------------------------------------------- /pcs_test/tier0/common/pacemaker/resource/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs_test/tier0/common/pacemaker/resource/__init__.py -------------------------------------------------------------------------------- /pcs_test/tier0/common/reports/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs_test/tier0/common/reports/__init__.py -------------------------------------------------------------------------------- /pcs_test/tier0/common/services/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs_test/tier0/common/services/__init__.py -------------------------------------------------------------------------------- /pcs_test/tier0/common/services/drivers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs_test/tier0/common/services/drivers/__init__.py -------------------------------------------------------------------------------- /pcs_test/tier0/daemon/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs_test/tier0/daemon/__init__.py -------------------------------------------------------------------------------- /pcs_test/tier0/daemon/app/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs_test/tier0/daemon/app/__init__.py -------------------------------------------------------------------------------- /pcs_test/tier0/daemon/app/fixtures_app_webui.py: -------------------------------------------------------------------------------- 1 | try: 2 | from pcs.daemon.app import webui 3 | except ImportError: 4 | # You need to skip tests in tests that uses AppTest in the case webui 5 | # is not there. 6 | webui = None 7 | 8 | from pcs_test.tier0.daemon.app import fixtures_app 9 | 10 | 11 | class AppTest(fixtures_app.AppTest): 12 | def setUp(self): 13 | self.session_storage = webui.session.Storage(lifetime_seconds=10) 14 | super().setUp() 15 | 16 | def fetch(self, path, raise_error=False, **kwargs): 17 | if "sid" in kwargs: 18 | if "headers" not in kwargs: 19 | kwargs["headers"] = {} 20 | kwargs["headers"]["Cookie"] = ( 21 | f"{webui.auth.PCSD_SESSION}={kwargs['sid']}" 22 | ) 23 | del kwargs["sid"] 24 | 25 | return super().fetch(path, raise_error=raise_error, **kwargs) 26 | 27 | def create_login_session(self): 28 | return self.session_storage.login(fixtures_app.USER) 29 | -------------------------------------------------------------------------------- /pcs_test/tier0/daemon/app/test_api_v1.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | 3 | from pcs.daemon.app.api_v1 import API_V1_MAP 4 | from pcs.daemon.async_tasks.worker.command_mapping import COMMAND_MAP 5 | 6 | 7 | class ApiV1MapTest(TestCase): 8 | def test_all_commands_exist(self): 9 | missing_commands = set(API_V1_MAP.values()) - set(COMMAND_MAP.keys()) 10 | self.assertEqual( 11 | 0, 12 | len(missing_commands), 13 | f"Commands missing in COMMAND_MAP: {missing_commands}", 14 | ) 15 | -------------------------------------------------------------------------------- /pcs_test/tier0/daemon/app/test_app_redirect.py: -------------------------------------------------------------------------------- 1 | from pcs.daemon.app.common import RedirectHandler 2 | 3 | from pcs_test.tier0.daemon.app.fixtures_app import AppTest 4 | 5 | MANAGE = "/manage" 6 | 7 | 8 | class RedirectTest(AppTest): 9 | def get_routes(self): 10 | return [(r"/", RedirectHandler, dict(url=MANAGE))] 11 | 12 | def test_redirects_to_given_location(self): 13 | response = self.get("/") 14 | self.assert_headers_contains(response.headers, {"Location": MANAGE}) 15 | self.assertEqual(response.code, 301) 16 | -------------------------------------------------------------------------------- /pcs_test/tier0/daemon/async_tasks/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs_test/tier0/daemon/async_tasks/__init__.py -------------------------------------------------------------------------------- /pcs_test/tier0/lib/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs_test/tier0/lib/__init__.py -------------------------------------------------------------------------------- /pcs_test/tier0/lib/auth/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs_test/tier0/lib/auth/__init__.py -------------------------------------------------------------------------------- /pcs_test/tier0/lib/auth/config/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs_test/tier0/lib/auth/config/__init__.py -------------------------------------------------------------------------------- /pcs_test/tier0/lib/booth/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs_test/tier0/lib/booth/__init__.py -------------------------------------------------------------------------------- /pcs_test/tier0/lib/booth/test_sync.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | 3 | 4 | class SendAllConfigToNodeTest(TestCase): 5 | """ 6 | tested in: 7 | pcs_test.tier0.lib.commands.cluster.test_add_nodes.AddNodeFull 8 | pcs_test.tier0.lib.commands.cluster.test_add_nodes 9 | .FailureBoothConfigsDistribution 10 | """ 11 | -------------------------------------------------------------------------------- /pcs_test/tier0/lib/cfgsync/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs_test/tier0/lib/cfgsync/__init__.py -------------------------------------------------------------------------------- /pcs_test/tier0/lib/cfgsync/config/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs_test/tier0/lib/cfgsync/config/__init__.py -------------------------------------------------------------------------------- /pcs_test/tier0/lib/cib/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs_test/tier0/lib/cib/__init__.py -------------------------------------------------------------------------------- /pcs_test/tier0/lib/cib/constraint/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs_test/tier0/lib/cib/constraint/__init__.py -------------------------------------------------------------------------------- /pcs_test/tier0/lib/cib/resource/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs_test/tier0/lib/cib/resource/__init__.py -------------------------------------------------------------------------------- /pcs_test/tier0/lib/cib/rule/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs_test/tier0/lib/cib/rule/__init__.py -------------------------------------------------------------------------------- /pcs_test/tier0/lib/cib/test_resource_bundle.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | 3 | from lxml import etree 4 | 5 | from pcs.lib.cib.resource import bundle 6 | 7 | # pcs.lib.cib.resource.bundle is covered by: 8 | # - pcs_test.tier0.lib.commands.resource.test_bundle_create 9 | # - pcs_test.tier0.lib.commands.resource.test_bundle_update 10 | # - pcs_test.tier0.lib.commands.resource.test_resource_create 11 | 12 | 13 | class IsBundle(TestCase): 14 | def test_is_bundle(self): 15 | self.assertTrue(bundle.is_bundle(etree.fromstring(""))) 16 | self.assertFalse(bundle.is_bundle(etree.fromstring(""))) 17 | self.assertFalse(bundle.is_bundle(etree.fromstring(""))) 18 | 19 | 20 | class GetInnerResource(TestCase): 21 | def assert_inner_resource(self, resource_id, xml): 22 | self.assertEqual( 23 | resource_id, 24 | bundle.get_inner_resource(etree.fromstring(xml)).get("id", ""), 25 | ) 26 | 27 | def test_primitive(self): 28 | self.assert_inner_resource( 29 | "A", 30 | """ 31 | 32 | 33 | 34 | 35 | 36 | """, 37 | ) 38 | -------------------------------------------------------------------------------- /pcs_test/tier0/lib/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs_test/tier0/lib/commands/__init__.py -------------------------------------------------------------------------------- /pcs_test/tier0/lib/commands/cluster/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs_test/tier0/lib/commands/cluster/__init__.py -------------------------------------------------------------------------------- /pcs_test/tier0/lib/commands/constraint/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs_test/tier0/lib/commands/constraint/__init__.py -------------------------------------------------------------------------------- /pcs_test/tier0/lib/commands/dr/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs_test/tier0/lib/commands/dr/__init__.py -------------------------------------------------------------------------------- /pcs_test/tier0/lib/commands/remote_node/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs_test/tier0/lib/commands/remote_node/__init__.py -------------------------------------------------------------------------------- /pcs_test/tier0/lib/commands/resource/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs_test/tier0/lib/commands/resource/__init__.py -------------------------------------------------------------------------------- /pcs_test/tier0/lib/commands/sbd/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs_test/tier0/lib/commands/sbd/__init__.py -------------------------------------------------------------------------------- /pcs_test/tier0/lib/commands/tag/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs_test/tier0/lib/commands/tag/__init__.py -------------------------------------------------------------------------------- /pcs_test/tier0/lib/commands/tag/tag_common.py: -------------------------------------------------------------------------------- 1 | def fixture_resources_for_ids(): 2 | return """ 3 | 4 | 5 | 6 | 7 | """ 8 | 9 | 10 | def fixture_tags_xml(tag_with_ids): 11 | return ( 12 | "" 13 | + "".join( 14 | ''.format(tag_id=tag_id) 15 | + "".join(f'' for id_ in id_list) 16 | + "" 17 | for tag_id, id_list in tag_with_ids 18 | ) 19 | + "" 20 | ) 21 | 22 | 23 | def fixture_constraints_for_tags(tag_list): 24 | return ( 25 | "" 26 | + "".join( 27 | f"" 29 | for tag in tag_list 30 | ) 31 | + "" 32 | ) 33 | 34 | 35 | def fixture_resources_for_reference_ids(id_list): 36 | return ( 37 | "" 38 | + "".join( 39 | ( 40 | f"" 42 | ) 43 | for _id in id_list 44 | ) 45 | + "" 46 | ) 47 | -------------------------------------------------------------------------------- /pcs_test/tier0/lib/communication/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs_test/tier0/lib/communication/__init__.py -------------------------------------------------------------------------------- /pcs_test/tier0/lib/communication/test_booth.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | 3 | 4 | class BoothSendConfig(TestCase): 5 | """ 6 | tested in: 7 | pcs_test.tier0.lib.commands.test_booth.ConfigSyncTest 8 | """ 9 | 10 | 11 | class BoothGetConfig(TestCase): 12 | """ 13 | tested in: 14 | pcs_test.tier0.lib.commands.test_booth.PullConfigSuccess 15 | pcs_test.tier0.lib.commands.test_booth.PullConfigFailure 16 | pcs_test.tier0.lib.commands.test_booth.PullConfigWithAuthfileSuccess 17 | pcs_test.tier0.lib.commands.test_booth.PullConfigWithAuthfileFailure 18 | """ 19 | 20 | 21 | class BoothSaveFiles(TestCase): 22 | """ 23 | tested in: 24 | pcs_test.tier0.lib.commands.cluster.test_add_nodes.AddNodeFull 25 | pcs_test.tier0.lib.commands.cluster.test_add_nodes 26 | .FailureBoothConfigsDistribution 27 | """ 28 | -------------------------------------------------------------------------------- /pcs_test/tier0/lib/communication/test_cluster.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | 3 | 4 | class Destroy(TestCase): 5 | """ 6 | tested in: 7 | pcs_test.tier0.lib.commands.cluster.test_setup.SetupSuccessMinimal 8 | pcs_test.tier0.lib.commands.cluster.test_setup.SetupSuccessAddresses 9 | pcs_test.tier0.lib.commands.cluster.test_setup.Setup2NodeSuccessMinimal 10 | pcs_test.tier0.lib.commands.cluster.test_setup.SetupWithWait 11 | pcs_test.tier0.lib.commands.cluster.test_setup.Failures 12 | .test_cluster_destroy_failure 13 | """ 14 | 15 | 16 | class DestroyWarnOnFailure(TestCase): 17 | """ 18 | tested in: 19 | pcs_test.tier0.lib.commands.cluster.test_remove_nodes.SuccessMinimal 20 | pcs_test.tier0.lib.commands.cluster.test_remove_nodes 21 | .FailureClusterDestroy 22 | """ 23 | 24 | 25 | class GetQuorumStatus(TestCase): 26 | """ 27 | tested in: 28 | pcs_test.tier0.lib.commands.cluster.test_remove_nodes.SuccessMinimal 29 | pcs_test.tier0.lib.commands.cluster.test_remove_nodes.QuorumCheck 30 | pcs_test.tier0.lib.commands.cluster.test_remove_nodes.FailureQuorumLoss 31 | """ 32 | -------------------------------------------------------------------------------- /pcs_test/tier0/lib/communication/test_corosync.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | 3 | 4 | class CheckCorosyncOffline(TestCase): 5 | """ 6 | tested in: 7 | pcs_test.tier0.lib.test_env.PushCorosyncConfLiveNoQdeviceTest 8 | """ 9 | 10 | 11 | class DistributeCorosyncConf(TestCase): 12 | """ 13 | tested in: 14 | pcs_test.tier0.lib.test_env.PushCorosyncConfLiveNoQdeviceTest 15 | """ 16 | 17 | 18 | class ReloadCorosyncConf(TestCase): 19 | """ 20 | tested in: 21 | pcs_test.tier0.lib.commands.cluster.test_add_nodes.{ 22 | AddNodesSuccessMinimal 23 | AddNodeFull 24 | } FailureReloadCorosyncConf 25 | pcs_test.tier0.lib.commands.cluster.test_remove_nodes 26 | .FailureCorosyncReload 27 | pcs_test.tier0.lib.test_env 28 | """ 29 | -------------------------------------------------------------------------------- /pcs_test/tier0/lib/communication/test_nodes.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | 3 | 4 | class CheckReachability(TestCase): 5 | """ 6 | tested in: 7 | pcs_test.tier0.lib.command.status 8 | """ 9 | 10 | 11 | class GetOnlineTargets(TestCase): 12 | """ 13 | tested in: 14 | pcs_test.tier0.lib.commands.sbd.test_enable_sbd 15 | """ 16 | 17 | 18 | class SendPcsdSslCertAndKey(TestCase): 19 | """ 20 | tested in: 21 | pcs_test.tier0.lib.commands.cluster.test_setup 22 | pcs_test.tier0.lib.commands.test_pcsd 23 | """ 24 | 25 | 26 | class RemoveNodesFromCib(TestCase): 27 | """ 28 | tested in: 29 | pcs_test.tier0.lib.commands.cluster.test_remove_nodes.{ 30 | RemoveNodesFailureFromCib 31 | RemoveNodesSuccessMinimal 32 | } 33 | """ 34 | -------------------------------------------------------------------------------- /pcs_test/tier0/lib/communication/test_qdevice.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | 3 | 4 | class Stop(TestCase): 5 | """ 6 | tested in: 7 | pcs_test.tier0.lib.commands.test_quorum.RemoveDeviceNetTest 8 | pcs_test.tier0.lib.test_env.PushCorosyncConfLiveWithQdeviceTest 9 | """ 10 | 11 | 12 | class Start(TestCase): 13 | """ 14 | tested in: 15 | pcs_test.tier0.lib.commands.test_quorum.AddDeviceNetTest 16 | pcs_test.tier0.lib.test_env.PushCorosyncConfLiveWithQdeviceTest 17 | """ 18 | 19 | 20 | class Enable(TestCase): 21 | """ 22 | tested in: 23 | pcs_test.tier0.lib.commands.test_quorum.AddDeviceNetTest 24 | """ 25 | 26 | 27 | class Disable(TestCase): 28 | """ 29 | tested in: 30 | pcs_test.tier0.lib.commands.test_quorum.RemoveDeviceNetTest 31 | """ 32 | -------------------------------------------------------------------------------- /pcs_test/tier0/lib/communication/test_qdevice_net.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | 3 | 4 | class GetCaCert(TestCase): 5 | """ 6 | tested in: 7 | pcs_test.tier0.lib.commands.test_quorum.AddDeviceNetTest 8 | """ 9 | 10 | 11 | class ClientSetup(TestCase): 12 | """ 13 | tested in: 14 | pcs_test.tier0.lib.commands.test_quorum.AddDeviceNetTest 15 | """ 16 | 17 | 18 | class SignCertificate(TestCase): 19 | """ 20 | tested in: 21 | pcs_test.tier0.lib.commands.test_quorum.AddDeviceNetTest 22 | """ 23 | 24 | 25 | class ClientImportCertificateAndKey(TestCase): 26 | """ 27 | tested in: 28 | pcs_test.tier0.lib.commands.test_quorum.AddDeviceNetTest 29 | """ 30 | 31 | 32 | class ClientDestroy(TestCase): 33 | """ 34 | tested in: 35 | pcs_test.tier0.lib.commands.test_quorum.RemoveDeviceNetTest 36 | """ 37 | -------------------------------------------------------------------------------- /pcs_test/tier0/lib/communication/test_scsi.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | 3 | 4 | class Unfence(TestCase): 5 | """ 6 | tested in: 7 | pcs_test.tier0.lib.commands.test_stonith_update_scsi_devices.TestUpdateScsiDevicesFailures 8 | """ 9 | -------------------------------------------------------------------------------- /pcs_test/tier0/lib/communication/test_status.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | 3 | 4 | class GetFullClusterStatusPlaintext(TestCase): 5 | """ 6 | tested in: 7 | pcs_test.tier0.lib.commands.dr.test_status 8 | """ 9 | -------------------------------------------------------------------------------- /pcs_test/tier0/lib/corosync/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs_test/tier0/lib/corosync/__init__.py -------------------------------------------------------------------------------- /pcs_test/tier0/lib/dr/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs_test/tier0/lib/dr/__init__.py -------------------------------------------------------------------------------- /pcs_test/tier0/lib/file/test_instance.py: -------------------------------------------------------------------------------- 1 | # Tested in pcs.lib.commands.booth 2 | -------------------------------------------------------------------------------- /pcs_test/tier0/lib/file/test_raw_file.py: -------------------------------------------------------------------------------- 1 | # Tested in pcs.lib.commands.booth 2 | -------------------------------------------------------------------------------- /pcs_test/tier0/lib/host/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs_test/tier0/lib/host/__init__.py -------------------------------------------------------------------------------- /pcs_test/tier0/lib/host/config/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs_test/tier0/lib/host/config/__init__.py -------------------------------------------------------------------------------- /pcs_test/tier0/lib/host/config/test_facade.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | 3 | from pcs.common.host import Destination, PcsKnownHost 4 | from pcs.lib.host.config.facade import Facade as KnownHostsFacade 5 | from pcs.lib.host.config.types import KnownHosts 6 | 7 | 8 | class Facade(TestCase): 9 | def test_create(self): 10 | facade = KnownHostsFacade.create() 11 | 12 | self.assertEqual(1, facade.data_version) 13 | self.assertEqual(dict(), facade.known_hosts) 14 | 15 | def test_ok(self): 16 | facade = KnownHostsFacade( 17 | KnownHosts( 18 | format_version=1, 19 | data_version=10, 20 | known_hosts={ 21 | "a": PcsKnownHost( 22 | name="a", 23 | token="abcd", 24 | dest_list=[Destination("10.0.0.1", 2224)], 25 | ) 26 | }, 27 | ) 28 | ) 29 | 30 | self.assertEqual(10, facade.data_version) 31 | self.assertEqual( 32 | { 33 | "a": PcsKnownHost( 34 | name="a", 35 | token="abcd", 36 | dest_list=[Destination("10.0.0.1", 2224)], 37 | ) 38 | }, 39 | facade.known_hosts, 40 | ) 41 | -------------------------------------------------------------------------------- /pcs_test/tier0/lib/misc.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from unittest import mock 3 | 4 | from pcs.lib.env import LibraryEnvironment as Env 5 | 6 | from pcs_test.tools.custom_mock import MockLibraryReportProcessor 7 | 8 | 9 | def get_mocked_env(**kwargs): 10 | return Env( 11 | logger=mock.MagicMock(logging.Logger), 12 | report_processor=MockLibraryReportProcessor(), 13 | **kwargs, 14 | ) 15 | -------------------------------------------------------------------------------- /pcs_test/tier0/lib/pacemaker/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs_test/tier0/lib/pacemaker/__init__.py -------------------------------------------------------------------------------- /pcs_test/tier0/lib/permissions/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs_test/tier0/lib/permissions/__init__.py -------------------------------------------------------------------------------- /pcs_test/tier0/lib/permissions/config/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs_test/tier0/lib/permissions/config/__init__.py -------------------------------------------------------------------------------- /pcs_test/tier0/lib/resource_agent/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs_test/tier0/lib/resource_agent/__init__.py -------------------------------------------------------------------------------- /pcs_test/tier0/lib/test_tools.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | 3 | from pcs.lib import tools 4 | 5 | 6 | class EnvironmentFileToDictTest(TestCase): 7 | def test_success(self): 8 | data = """ 9 | # ignored comment 10 | IgnoredOptionNoValue 11 | NUMBER=10 12 | OPTION=value # this is also value 13 | ANOTHER ONE="complex value" 14 | ; another comment 15 | 16 | """ 17 | expected = { 18 | "NUMBER": "10", 19 | "OPTION": "value # this is also value", 20 | "ANOTHER ONE": '"complex value"', 21 | } 22 | self.assertEqual(expected, tools.environment_file_to_dict(data)) 23 | 24 | def test_empty_string(self): 25 | self.assertEqual({}, tools.environment_file_to_dict("")) 26 | 27 | 28 | class DictToEnvironmentFileTest(TestCase): 29 | def test_success(self): 30 | cfg_dict = { 31 | "OPTION": "value", 32 | "ANOTHER": "option value", 33 | "ANOTHER ONE": '"complex value"', 34 | } 35 | expected = """# This file has been generated by pcs. 36 | ANOTHER=option value 37 | ANOTHER ONE="complex value" 38 | OPTION=value 39 | """ 40 | self.assertEqual(expected, tools.dict_to_environment_file(cfg_dict)) 41 | -------------------------------------------------------------------------------- /pcs_test/tier0/test_capabilities.py: -------------------------------------------------------------------------------- 1 | import os.path 2 | from unittest import TestCase 3 | 4 | from lxml import etree 5 | 6 | from pcs_test import PROJECT_ROOT 7 | 8 | 9 | class TestCapabilities(TestCase): 10 | def test_parsable(self): 11 | # pylint: disable=no-self-use 12 | capabilities_dir = os.path.join(PROJECT_ROOT, "pcsd") 13 | dom = etree.parse(os.path.join(capabilities_dir, "capabilities.xml")) 14 | etree.RelaxNG( 15 | file=os.path.join(capabilities_dir, "capabilities.rng") 16 | ).assertValid(dom) 17 | -------------------------------------------------------------------------------- /pcs_test/tier1/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs_test/tier1/__init__.py -------------------------------------------------------------------------------- /pcs_test/tier1/cib_resource/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs_test/tier1/cib_resource/__init__.py -------------------------------------------------------------------------------- /pcs_test/tier1/cib_resource/common.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | 3 | from lxml import etree 4 | 5 | from pcs_test.tools.cib import get_assert_pcs_effect_mixin 6 | from pcs_test.tools.misc import get_test_resource as rc 7 | from pcs_test.tools.misc import ( 8 | get_tmp_file, 9 | write_file_to_tmpfile, 10 | ) 11 | from pcs_test.tools.pcs_runner import PcsRunner 12 | 13 | 14 | def get_cib_resources(cib): 15 | return etree.tostring(etree.parse(cib).findall(".//resources")[0]) 16 | 17 | 18 | class ResourceTest(TestCase, get_assert_pcs_effect_mixin(get_cib_resources)): 19 | empty_cib = rc("cib-empty.xml") 20 | 21 | def setUp(self): 22 | self.temp_cib = get_tmp_file("tier1_test_resource_common") 23 | write_file_to_tmpfile(self.empty_cib, self.temp_cib) 24 | self.pcs_runner = PcsRunner(self.temp_cib.name) 25 | 26 | def tearDown(self): 27 | self.temp_cib.close() 28 | -------------------------------------------------------------------------------- /pcs_test/tier1/cluster/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs_test/tier1/cluster/__init__.py -------------------------------------------------------------------------------- /pcs_test/tier1/cluster/common.py: -------------------------------------------------------------------------------- 1 | from textwrap import dedent 2 | 3 | 4 | def fixture_corosync_conf_minimal(no_cluster_uuid=False): 5 | corosync_conf = """\ 6 | totem { 7 | version: 2 8 | cluster_name: cluster_name 9 | """ 10 | if not no_cluster_uuid: 11 | corosync_conf += """\ 12 | cluster_uuid: cluster_uuid 13 | """ 14 | 15 | corosync_conf += """\ 16 | transport: knet 17 | ip_version: ipv6 18 | crypto_cipher: aes256 19 | crypto_hash: sha256 20 | } 21 | 22 | nodelist { 23 | node { 24 | ring0_addr: node1_addr 25 | name: node1 26 | nodeid: 1 27 | } 28 | 29 | node { 30 | ring0_addr: node2_addr 31 | name: node2 32 | nodeid: 2 33 | } 34 | } 35 | 36 | quorum { 37 | provider: corosync_votequorum 38 | two_node: 1 39 | } 40 | 41 | logging { 42 | to_logfile: yes 43 | logfile: /var/log/cluster/corosync.log 44 | to_syslog: yes 45 | timestamp: on 46 | } 47 | """ 48 | 49 | return dedent(corosync_conf) 50 | -------------------------------------------------------------------------------- /pcs_test/tier1/constraint/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs_test/tier1/constraint/__init__.py -------------------------------------------------------------------------------- /pcs_test/tier1/legacy/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs_test/tier1/legacy/__init__.py -------------------------------------------------------------------------------- /pcs_test/tier1/legacy/common.py: -------------------------------------------------------------------------------- 1 | FIXTURE_UTILIZATION_WARNING = ( 2 | "Warning: Utilization attributes configuration has no effect until cluster " 3 | "property option 'placement-strategy' is set to one of the values: " 4 | "'balanced', 'minimal', 'utilization'\n" 5 | ) 6 | -------------------------------------------------------------------------------- /pcs_test/tier1/resource/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs_test/tier1/resource/__init__.py -------------------------------------------------------------------------------- /pcs_test/tier1/stonith/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs_test/tier1/stonith/__init__.py -------------------------------------------------------------------------------- /pcs_test/tier1/stonith/levels/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs_test/tier1/stonith/levels/__init__.py -------------------------------------------------------------------------------- /pcs_test/tools/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs_test/tools/__init__.py -------------------------------------------------------------------------------- /pcs_test/tools/bin_mock/__init__.py: -------------------------------------------------------------------------------- 1 | import os.path 2 | 3 | BIN_MOCK_DIR = os.path.dirname(os.path.abspath(__file__)) 4 | 5 | CRM_RESOURCE_BIN = os.path.abspath( 6 | os.path.join(BIN_MOCK_DIR, "pcmk/crm_resource") 7 | ) 8 | PACEMAKER_FENCED_BIN = os.path.abspath( 9 | os.path.join(BIN_MOCK_DIR, "pcmk/pacemaker-fenced") 10 | ) 11 | STONITH_ADMIN_BIN = os.path.abspath( 12 | os.path.join(BIN_MOCK_DIR, "pcmk/stonith_admin") 13 | ) 14 | 15 | MOCK_SETTINGS = { 16 | "crm_resource_exec": CRM_RESOURCE_BIN, 17 | "pacemaker_fenced_exec": PACEMAKER_FENCED_BIN, 18 | "stonith_admin_exec": STONITH_ADMIN_BIN, 19 | } 20 | 21 | 22 | def get_mock_settings(*required_settings): 23 | if not required_settings: 24 | required_settings = MOCK_SETTINGS.keys() 25 | return { 26 | key: value 27 | for key, value in MOCK_SETTINGS.items() 28 | if key in required_settings 29 | } 30 | -------------------------------------------------------------------------------- /pcs_test/tools/bin_mock/pcmk/crm_resource.d/list_agents_ocf__heartbeat: -------------------------------------------------------------------------------- 1 | pcsMock 2 | -------------------------------------------------------------------------------- /pcs_test/tools/bin_mock/pcmk/crm_resource.d/list_agents_ocf__pacemaker: -------------------------------------------------------------------------------- 1 | pcsMock 2 | remote 3 | -------------------------------------------------------------------------------- /pcs_test/tools/bin_mock/pcmk/crm_resource.d/list_agents_ocf__pcsmock: -------------------------------------------------------------------------------- 1 | action_method 2 | CamelCase 3 | duplicate_monitor 4 | minimal 5 | params 6 | stateful 7 | unique 8 | -------------------------------------------------------------------------------- /pcs_test/tools/bin_mock/pcmk/crm_resource.d/list_ocf_providers: -------------------------------------------------------------------------------- 1 | heartbeat 2 | pacemaker 3 | pcsmock 4 | -------------------------------------------------------------------------------- /pcs_test/tools/bin_mock/pcmk/crm_resource.d/list_standards: -------------------------------------------------------------------------------- 1 | ocf 2 | lsb 3 | service 4 | systemd 5 | stonith 6 | -------------------------------------------------------------------------------- /pcs_test/tools/bin_mock/pcmk/crm_resource.d/lsb__pcsmock_metadata.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 1.0 5 | 6 | This is a mock agent for pcs test - lsb agent 7 | 8 | pcs test mock lsb agent 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | pcsmock 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /pcs_test/tools/bin_mock/pcmk/crm_resource.d/ocf__heartbeat__pcsMock_metadata.xml: -------------------------------------------------------------------------------- 1 | ocf__pacemaker__pcsMock_metadata.xml -------------------------------------------------------------------------------- /pcs_test/tools/bin_mock/pcmk/crm_resource.d/ocf__pacemaker__pcsMock_metadata.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 1.1 4 | 5 | 6 | This is a mock agent for pcs test - minimal agent. It is placed in a real 7 | provider to cover those test cases where we need a real provider. It is also 8 | useful for test cases where two providers ship an agent with the same name. 9 | 10 | Mock agent for pcs tests - minimal agent 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /pcs_test/tools/bin_mock/pcmk/crm_resource.d/ocf__pcsmock__CamelCase_metadata.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 1.1 4 | 5 | 6 | This is a mock agent for pcs test - minimal agent with CamelCase name 7 | 8 | Mock agent for pcs tests - minimal agent 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /pcs_test/tools/bin_mock/pcmk/crm_resource.d/ocf__pcsmock__duplicate_monitor_metadata.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 1.1 4 | 5 | 6 | This is a mock agent for pcs test - promotable agent with both monitor 7 | operations having the same interval 8 | 9 | Mock agent for pcs tests - promotable agent with duplicate monitors 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /pcs_test/tools/bin_mock/pcmk/crm_resource.d/ocf__pcsmock__minimal_metadata.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 1.1 4 | 5 | 6 | This is a mock agent for pcs test - minimal agent 7 | 8 | Mock agent for pcs tests - minimal agent 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /pcs_test/tools/bin_mock/pcmk/crm_resource.d/ocf__pcsmock__stateful_metadata.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 1.1 4 | 5 | 6 | This is a mock agent for pcs test - minimal promotable agent 7 | 8 | Mock agent for pcs tests - promotable agent 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /pcs_test/tools/bin_mock/pcmk/crm_resource.d/ocf__pcsmock__unique_metadata.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 1.1 4 | 5 | 6 | This is a mock agent for pcs test - agent with unique-group parameters 7 | 8 | Mock agent for pcs tests - unique parameters 9 | 10 | 11 | 12 | 13 | Location to store the resource state in. 14 | 15 | State file 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /pcs_test/tools/bin_mock/pcmk/crm_resource.d/stonith__fence_pcsmock_action_metadata.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | This is an agent with action parameter for pcs tests 7 | 8 | 9 | 10 | 11 | 12 | 13 | Fencing action (null, off, on, [reboot], status, list, list-status, monitor, validate-all, metadata) 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /pcs_test/tools/bin_mock/pcmk/crm_resource.d/stonith__fence_pcsmock_method_metadata.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | This is an agent with method parameter for pcs tests 7 | 8 | 9 | 10 | 11 | 12 | 15 | Method to fence 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /pcs_test/tools/bin_mock/pcmk/crm_resource.d/stonith__fence_pcsmock_minimal_metadata.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | This is a minimalistic agent for pcs tests 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /pcs_test/tools/bin_mock/pcmk/crm_resource.d/stonith__fence_pcsmock_unfencing_metadata.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | This is an agent which provides unfencing for pcs tests 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /pcs_test/tools/bin_mock/pcmk/crm_resource.d/systemd__pcsmock@a__b_metadata.xml: -------------------------------------------------------------------------------- 1 | systemd__pcsmock_metadata.xml -------------------------------------------------------------------------------- /pcs_test/tools/bin_mock/pcmk/crm_resource.d/systemd__pcsmock_metadata.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 1.1 4 | 5 | This is a mock agent for pcs test - systemd agent 6 | 7 | systemd unit file for pcsmock 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /pcs_test/tools/bin_mock/pcmk/crm_resource.in: -------------------------------------------------------------------------------- 1 | #!@PYTHON@ 2 | 3 | from crm_resource_mock import main 4 | 5 | main() 6 | -------------------------------------------------------------------------------- /pcs_test/tools/bin_mock/pcmk/pacemaker-fenced.in: -------------------------------------------------------------------------------- 1 | #!@PYTHON@ 2 | 3 | from pacemaker_metadata import main 4 | 5 | main() 6 | -------------------------------------------------------------------------------- /pcs_test/tools/bin_mock/pcmk/pacemaker_metadata.py: -------------------------------------------------------------------------------- 1 | import os.path 2 | import sys 3 | 4 | CURRENT_DIR = os.path.dirname(os.path.abspath(__file__)) 5 | DATA_DIR = os.path.join(CURRENT_DIR, "pacemaker_metadata.d") 6 | 7 | 8 | def write_file_to_stdout(file_name): 9 | with open(file_name) as file: 10 | sys.stdout.write(file.read()) 11 | 12 | 13 | def write_local_file_to_stdout(file_name): 14 | write_file_to_stdout(os.path.join(DATA_DIR, file_name)) 15 | 16 | 17 | def main(): 18 | pcmk_binary = sys.argv[0].rsplit("/", 1)[-1] 19 | argv = sys.argv[1:] 20 | if pcmk_binary == "pacemaker-fenced": 21 | return pacemaker_fenced(argv) 22 | raise AssertionError() 23 | 24 | 25 | def pacemaker_fenced(argv): 26 | if len(argv) != 1: 27 | raise AssertionError() 28 | if argv[0] != "metadata": 29 | raise AssertionError() 30 | write_local_file_to_stdout("pacemaker_fenced.xml") 31 | 32 | 33 | if __name__ == "__main__": 34 | main() 35 | -------------------------------------------------------------------------------- /pcs_test/tools/bin_mock/pcmk/stonith_admin.in: -------------------------------------------------------------------------------- 1 | #!@PYTHON@ 2 | 3 | from stonith_admin_mock import main 4 | 5 | main() 6 | -------------------------------------------------------------------------------- /pcs_test/tools/case_analysis.py: -------------------------------------------------------------------------------- 1 | def _list2reason(test, exc_list): 2 | if exc_list and exc_list[-1][0] is test: 3 | return exc_list[-1][1] 4 | return None 5 | 6 | 7 | def test_failed(test): 8 | # Borrowed from 9 | # https://stackoverflow.com/questions/4414234/getting-pythons-unittest-results-in-a-teardown-method/39606065#39606065 10 | # for Python versions 3.4 to 3.11 11 | # pylint: disable=protected-access 12 | if hasattr(test._outcome, "errors"): 13 | # Python 3.4 - 3.10 (These 2 methods have no side effects) 14 | result = test.defaultTestResult() 15 | test._feedErrorsToResult(result, test._outcome.errors) 16 | else: 17 | # Python 3.11+ 18 | result = test._outcome.result 19 | if not hasattr(result, "errors") or not hasattr(result, "failures"): 20 | return None 21 | 22 | return _list2reason(test, result.errors) or _list2reason( 23 | test, result.failures 24 | ) 25 | -------------------------------------------------------------------------------- /pcs_test/tools/check/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ClusterLabs/pcs/102aafbca762f0c2ed5ccf6c2188859c11dcfcc7/pcs_test/tools/check/__init__.py -------------------------------------------------------------------------------- /pcs_test/tools/check/test_misc.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | 3 | from pcs_test.tools.misc import outdent 4 | 5 | 6 | class OutdentTest(TestCase): 7 | def test_returns_the_same_text_when_not_indented(self): 8 | text = "\n".join( 9 | [ 10 | "first line", 11 | " second line", 12 | " third line", 13 | ] 14 | ) 15 | self.assertEqual(text, outdent(text)) 16 | 17 | def test_remove_the_smallest_indentation(self): 18 | self.assertEqual( 19 | "\n".join( 20 | [ 21 | " first line", 22 | "second line", 23 | " third line", 24 | ] 25 | ), 26 | outdent( 27 | "\n".join( 28 | [ 29 | " first line", 30 | " second line", 31 | " third line", 32 | ] 33 | ) 34 | ), 35 | ) 36 | 37 | def test_very_ugly_indented_text(self): 38 | self.assertEqual( 39 | """\ 40 | Cluster Name: test99 41 | Options: 42 | """, 43 | outdent( 44 | """\ 45 | Cluster Name: test99 46 | Options: 47 | """ 48 | ), 49 | ) 50 | -------------------------------------------------------------------------------- /pcs_test/tools/color_text_runner/__init__.py: -------------------------------------------------------------------------------- 1 | from pcs_test.tools.color_text_runner.result import get_text_test_result_class 2 | -------------------------------------------------------------------------------- /pcsd/capabilities.rng: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 0 40 | 1 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /pcsd/cluster.rb: -------------------------------------------------------------------------------- 1 | class Cluster 2 | attr_accessor :name 3 | attr_reader :nodes 4 | 5 | def initialize(name, node_list) 6 | @name = name 7 | self.nodes = node_list 8 | end 9 | 10 | def nodes=(node_list) 11 | @nodes = [] 12 | node_list.each { |n| 13 | @nodes << n if n.is_a?(String) 14 | } 15 | @nodes = @nodes.uniq.sort 16 | return self 17 | end 18 | 19 | def num_nodes 20 | @nodes.length 21 | end 22 | 23 | def ui_address 24 | return "/managec/" + @name + "/main" 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /pcsd/logrotate/pcsd.in: -------------------------------------------------------------------------------- 1 | @localstatedir@/log/pcsd/*.log { 2 | rotate 5 3 | weekly 4 | missingok 5 | notifempty 6 | compress 7 | delaycompress 8 | copytruncate 9 | create 0600 root root 10 | } 11 | -------------------------------------------------------------------------------- /pcsd/pam/pcsd.debian: -------------------------------------------------------------------------------- 1 | #%PAM-1.0 2 | @include common-auth 3 | @include common-account 4 | @include common-password 5 | @include common-session 6 | -------------------------------------------------------------------------------- /pcsd/pam/pcsd.fedora: -------------------------------------------------------------------------------- 1 | #%PAM-1.0 2 | auth include system-auth 3 | account include system-auth 4 | password include system-auth 5 | session include system-auth 6 | -------------------------------------------------------------------------------- /pcsd/pam/pcsd.opencloudos: -------------------------------------------------------------------------------- 1 | #%PAM-1.0 2 | auth include system-auth 3 | account include system-auth 4 | password include system-auth 5 | session include system-auth 6 | -------------------------------------------------------------------------------- /pcsd/pcsd-cli.rb.in: -------------------------------------------------------------------------------- 1 | #!@RUBY@ 2 | 3 | require 'pcsd-cli-main.rb' 4 | 5 | pcsd_cli_main() 6 | -------------------------------------------------------------------------------- /pcsd/pcsd-ruby.service.in: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=PCS GUI and remote configuration interface (Ruby) 3 | Documentation=man:pcsd(8) 4 | Documentation=man:pcs(8) 5 | Requires=network-online.target 6 | After=network-online.target 7 | # Stop the service automatically if nothing that depends on it is running 8 | StopWhenUnneeded=true 9 | # When stopping or restarting pcsd, stop or restart pcsd-ruby as well 10 | PartOf=pcsd.service 11 | 12 | [Service] 13 | EnvironmentFile=@CONF_DIR@/pcsd 14 | @SYSTEMD_GEM_HOME@ 15 | # This file holds the selinux context 16 | ExecStart=@LIB_DIR@/pcsd/pcsd 17 | Type=notify 18 | 19 | [Install] 20 | WantedBy=multi-user.target 21 | -------------------------------------------------------------------------------- /pcsd/pcsd.in: -------------------------------------------------------------------------------- 1 | #!@RUBY@ 2 | # This file is a runner for ruby part of pcsd callable from a systemd unit. 3 | # It also serves as a holder of a selinux context. 4 | 5 | begin 6 | # add pcsd to the load path (ruby -I) 7 | libdir = File.dirname(__FILE__) 8 | $LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir) 9 | 10 | # change current directory (ruby -C) 11 | Dir.chdir('@LOCALSTATEDIR@/lib/pcsd') 12 | 13 | # import and run ruby daemon 14 | require 'rserver.rb' 15 | rescue SignalException => e 16 | if [Signal.list['INT'], Signal.list['TERM']].include?(e.signo) 17 | # gracefully exit on SIGINT and SIGTERM 18 | # pcsd sets up signal handlers later, this catches exceptions which occur 19 | # by receiving signals before the handlers have been set up. 20 | exit 21 | else 22 | raise 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /pcsd/pcsd.service.in: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=PCS GUI and remote configuration interface 3 | Documentation=man:pcsd(8) 4 | Documentation=man:pcs(8) 5 | Requires=network-online.target 6 | After=network-online.target 7 | Requires=pcsd-ruby.service 8 | After=pcsd-ruby.service 9 | 10 | [Service] 11 | EnvironmentFile=@CONF_DIR@/pcsd 12 | ExecStart=@SBINDIR@/pcsd 13 | Type=notify 14 | KillMode=mixed 15 | 16 | [Install] 17 | WantedBy=multi-user.target 18 | -------------------------------------------------------------------------------- /pcsd/public/ui_instructions.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Pcs WebUI instructions 5 | 6 | 7 | 8 |

Pcs WebUI instructions

9 |

10 | WebUI is not a part of pcs repository but it has its own 11 | repository. 12 |

13 |

14 | You can clone WebUI repository 15 | and build the web application into pcs by: 16 |

17 |

18 |     $ npm install
19 |     $ npm run build
20 |     $ mv ./build [/path/to/]pcs/pcsd/public/ui
21 |   
22 |

23 | For more details, see instructions in 24 | README.md. 25 |

26 | 27 | 28 | -------------------------------------------------------------------------------- /pcsd/settings.rb.in: -------------------------------------------------------------------------------- 1 | PCS_VERSION = '@VERSION@' 2 | PCS_EXEC = '@SBINDIR@/pcs' 3 | PCS_INTERNAL_EXEC = '@LIB_DIR@/pcs/pcs_internal' 4 | PCSD_EXEC_LOCATION = '@LIB_DIR@/pcsd' 5 | PCSD_VAR_LOCATION = '@LOCALSTATEDIR@/lib/pcsd' 6 | PCSD_DEFAULT_PORT = 2224 7 | PCSD_RUBY_SOCKET = '@LOCALSTATEDIR@/run/pcsd-ruby.socket' 8 | PCSD_RESTART_AFTER_REQUESTS = 200 9 | PCSD_RESTART_AFTER_REQUESTS_MIN = 50 10 | 11 | CRT_FILE = File.join(PCSD_VAR_LOCATION, 'pcsd.crt') 12 | KEY_FILE = File.join(PCSD_VAR_LOCATION, 'pcsd.key') 13 | KNOWN_HOSTS_FILE_NAME = 'known-hosts' 14 | PCSD_SETTINGS_CONF_LOCATION = File.join(PCSD_VAR_LOCATION, "pcs_settings.conf") 15 | PCSD_DR_CONFIG_LOCATION = File.join(PCSD_VAR_LOCATION, "disaster-recovery") 16 | 17 | CRM_MON = "@PCMKEXECPREFIX@/sbin/crm_mon" 18 | CIBADMIN = "@PCMKEXECPREFIX@/sbin/cibadmin" 19 | PACEMAKERD = "@PCMKEXECPREFIX@/sbin/pacemakerd" 20 | CIB_PATH = '@PCMK_CIB_DIR@/cib.xml' 21 | PACEMAKER_AUTHKEY='@PCMKCONFDIR@/pacemaker/authkey' 22 | 23 | COROSYNC_BINARIES = "@COROEXECPREFIX@/sbin" 24 | COROSYNC_CONF = '@COROCONFDIR@/corosync/corosync.conf' 25 | COROSYNC_LOG_FILE = '@COROLOGDIR@/corosync.log' 26 | 27 | COROSYNC_AUTHKEY = "@COROCONFDIR@/corosync/authkey" 28 | 29 | SBD_CONFIG = '@SBDCONFDIR@/sbd' 30 | 31 | BOOTH_CONFIG_DIR='@BOOTHCONFDIR@' 32 | 33 | SUPERUSER = '@PCMK_USER@' 34 | ADMIN_GROUP = '@PCMK_GROUP@' 35 | 36 | SYSTEMD_UNIT_PATHS = '@SYSTEMD_UNIT_PATH@'.split(':') 37 | -------------------------------------------------------------------------------- /pcsd/test/.gitignore: -------------------------------------------------------------------------------- 1 | *.tmp 2 | -------------------------------------------------------------------------------- /pcsd/test/corosync.conf: -------------------------------------------------------------------------------- 1 | totem { 2 | version: 2 3 | secauth: off 4 | cluster_name: test99 5 | transport: udpu 6 | config_version: 9 7 | } 8 | 9 | nodelist { 10 | node { 11 | ring0_addr: rh7-1 12 | nodeid: 1 13 | name: rh7-1 14 | } 15 | 16 | node { 17 | ring0_addr: rh7-2 18 | nodeid: 2 19 | name: rh7-2 20 | } 21 | } 22 | 23 | quorum { 24 | provider: corosync_votequorum 25 | } 26 | 27 | logging { 28 | to_syslog: yes 29 | } 30 | -------------------------------------------------------------------------------- /pcsd/test/known-hosts: -------------------------------------------------------------------------------- 1 | { 2 | "format_version": 1, 3 | "data_version": 5, 4 | "known_hosts": { 5 | "node1": { 6 | "dest_list": [ 7 | { 8 | "addr": "10.0.1.1", 9 | "port": 2224 10 | }, 11 | { 12 | "addr": "10.0.2.1", 13 | "port": 2225 14 | } 15 | ], 16 | "token": "abcde" 17 | }, 18 | "node2": { 19 | "dest_list": [ 20 | { 21 | "addr": "10.0.1.2", 22 | "port": 2234 23 | }, 24 | { 25 | "addr": "10.0.2.2", 26 | "port": 2235 27 | } 28 | ], 29 | "token": "fghij" 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /pcsd/test/pcs_settings.conf: -------------------------------------------------------------------------------- 1 | { 2 | "format_version": 2, 3 | "data_version": 9, 4 | "clusters": [ 5 | { 6 | "name": "cluster71", 7 | "nodes": [ 8 | "rh71-node1", 9 | "rh71-node2" 10 | ] 11 | }, 12 | { 13 | "name": "cluster67", 14 | "nodes": [ 15 | "rh67-node1", 16 | "rh67-node2", 17 | "rh67-node3" 18 | ] 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /pcsd/test/pcsd_test_utils.rb: -------------------------------------------------------------------------------- 1 | CURRENT_DIR = File.expand_path(File.dirname(__FILE__)) 2 | CFG_COROSYNC_CONF = File.join(CURRENT_DIR, "corosync.conf.tmp") 3 | CFG_PCSD_SETTINGS = File.join(CURRENT_DIR, "pcs_settings.conf.tmp") 4 | CFG_PCSD_KNOWN_HOSTS = File.join(CURRENT_DIR, 'known-hosts.tmp') 5 | 6 | CFG_SYNC_CONTROL = File.join(CURRENT_DIR, 'cfgsync_ctl.tmp') 7 | 8 | class MockLogger 9 | attr_reader :log 10 | 11 | def initialize 12 | @log = [] 13 | end 14 | 15 | def clean 16 | @log = [] 17 | end 18 | 19 | ['fatal', 'error', 'warn', 'info', 'debug'].each { |level| 20 | define_method(level) { |message| 21 | @log << [level, message] 22 | return self 23 | } 24 | } 25 | end 26 | -------------------------------------------------------------------------------- /pcsd/test/test_all_suite.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'test/unit' 3 | 4 | require 'test_corosyncconf.rb' 5 | require 'test_cluster.rb' 6 | require 'test_cluster_entity.rb' 7 | require 'test_permissions.rb' 8 | require 'test_config.rb' 9 | require 'test_cfgsync.rb' 10 | require 'test_pcs.rb' 11 | require 'test_resource.rb' 12 | -------------------------------------------------------------------------------- /scripts/pre-commit/README.md: -------------------------------------------------------------------------------- 1 | # Pre-commit hook 2 | 3 | It makes checks and warns the committer when there is something suspicious. 4 | 5 | ## Install 6 | 7 | Just copy the main file to githooks (assume you are in project root dir): 8 | ``` 9 | cp ./scripts/pre-commit/pre-commit.sh .git/hooks/pre-commit 10 | ``` 11 | -------------------------------------------------------------------------------- /scripts/pre-commit/check-all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Check definition. Multiline string, every line is for one check. 4 | # Format of line is: 5 | # `Check title ./path/relative/to/this/script/check-script.sh`. 6 | # The last space separates title from script path. 7 | check_list=" 8 | RUFF LINT CHECK ./check-lint.sh 9 | RUFF FORMAT CHECK ./check-format.sh 10 | MAKEFILE FILE LISTING CHECK ./check-makefile-file-listing.sh 11 | " 12 | 13 | extract_title() { 14 | echo "$1" | awk '{$NF=""; print $0}' 15 | } 16 | 17 | extract_command() { 18 | realpath "$(dirname "$0")"/"$(echo "$1" | awk '{print $NF}')" 19 | } 20 | 21 | err_report="" 22 | while IFS= read -r line; do 23 | [ -n "$line" ] || continue 24 | 25 | check=$(extract_command "$line") 26 | 27 | if ! output=$("$check" 2>&1); then 28 | err_report="${err_report}$(extract_title "$line") ($check)\n$output\n\n" 29 | fi 30 | done << EOF 31 | $check_list 32 | EOF 33 | 34 | if [ -z "$err_report" ]; then 35 | exit 0 36 | fi 37 | 38 | echo "Warning: some check failed" 39 | printf "%b" "$err_report" 40 | 41 | printf "Checks failed. Continue with commit? (c)Continue (a)Abort: " > /dev/tty 42 | IFS= read -r resolution < /dev/tty 43 | 44 | case "$resolution" in 45 | c | C) exit 0 ;; 46 | a | A) 47 | echo "Commit aborted." 48 | exit 1 49 | ;; 50 | esac 51 | 52 | echo "Unknown resolution '$resolution', aborting commit..." 53 | exit 1 54 | -------------------------------------------------------------------------------- /scripts/pre-commit/check-format.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if make --dry-run ruff_format_check > /dev/null 2>&1; then 4 | make ruff_format_check 5 | else 6 | echo "No 'make ruff_format_check', skipping..." 7 | fi 8 | -------------------------------------------------------------------------------- /scripts/pre-commit/check-lint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if make --dry-run ruff_lint > /dev/null 2>&1; then 4 | make ruff_lint 5 | else 6 | echo "No 'make ruff_lint', skipping..." 7 | fi 8 | -------------------------------------------------------------------------------- /scripts/pre-commit/check-makefile-file-listing.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | makefile_list="\ 4 | Makefile.am 5 | pcs/Makefile.am 6 | pcs_test/Makefile.am 7 | pcsd/Makefile.am 8 | data/Makefile.am 9 | " 10 | 11 | get_mentioned_files() { 12 | for makefile in $1; do 13 | [ -n "$makefile" ] || continue 14 | "$(dirname "$0")"/extract-extra-dist.sh "$makefile" 15 | done 16 | } 17 | 18 | get_unlisted_files() { 19 | makefiles=$1 20 | added_files=$2 21 | 22 | mentioned_files=$(get_mentioned_files "$makefiles") 23 | 24 | for file in $added_files; do 25 | if ! echo "$mentioned_files" | 26 | grep --quiet --fixed-strings --line-regexp "$file" 2> /dev/null; then 27 | echo "$file" 28 | fi 29 | done 30 | } 31 | 32 | git_added="$(git diff --cached --name-only --diff-filter=A)" 33 | 34 | if [ -z "$git_added" ]; then 35 | exit 0 36 | fi 37 | 38 | unlisted_files="$(get_unlisted_files "$makefile_list" "$git_added")" 39 | 40 | if [ -z "$unlisted_files" ]; then 41 | exit 0 42 | fi 43 | 44 | echo "Warning: The following files are not listed in any Makefile.am:" 45 | echo "$unlisted_files" 46 | exit 1 47 | -------------------------------------------------------------------------------- /scripts/pre-commit/extract-extra-dist.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | makefile="$1" 4 | inside_extra_dist=0 5 | files="" 6 | 7 | while IFS= read -r line; do 8 | case "$line" in 9 | *EXTRA_DIST[[:space:]]*=*) 10 | inside_extra_dist=1 11 | files="${line#*=}" 12 | files="${files%\\}" 13 | ;; 14 | *\\) 15 | [ $inside_extra_dist -eq 1 ] && files="$files ${line%\\}" 16 | ;; 17 | *) # the last line in EXTRA_DIST 18 | [ $inside_extra_dist -eq 1 ] && files="$files $line" && break 19 | ;; 20 | esac 21 | done < "$makefile" 22 | 23 | # no globing 24 | set -f 25 | # split to arguments 26 | # shellcheck disable=2086 27 | set -- $files 28 | for file in "$@"; do 29 | printf "%s\n" "${makefile%Makefile.am}$file" 30 | done 31 | -------------------------------------------------------------------------------- /scripts/pre-commit/pre-commit.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | checks=./scripts/pre-commit/check-all.sh 4 | 5 | if [ -x "$checks" ]; then 6 | "$checks" 7 | fi 8 | -------------------------------------------------------------------------------- /typos.toml: -------------------------------------------------------------------------------- 1 | [files] 2 | extend-exclude = ["typos.toml", "typos_known", "typos_new"] 3 | 4 | [default.extend-words] 5 | absic = "basic" 6 | adnvanced = "advanced" 7 | agnet = "agent" 8 | aleready = "already" 9 | alrge = "large" 10 | atuhkey = "authkey" 11 | axists = "exists" 12 | budle = "bundle" 13 | clsuter = "cluster" 14 | cocorync = "corosync" 15 | commes = "comes" 16 | corosnyc = "corosync" 17 | decsribes = "describes" 18 | depraceted = "deprecated" 19 | foce = "force" 20 | iddle = "idle" 21 | inlfuences = "influences" 22 | medatada = "metadata" 23 | monitro = "monitor" 24 | muttualy = "mutually" 25 | operatin = "operation" 26 | oudside = "outside" 27 | pacakamer = "pacemaker" 28 | pacemkaer = "pacemaker" 29 | pririty = "priority" 30 | proprties = "properties" 31 | quourm = "quorum" 32 | relevatnt = "relevant" 33 | resrource = "resource" 34 | resourcs = "resources" 35 | restarless = "restartless" 36 | selimited = "delimited" 37 | simualte = "simulate" 38 | sotnith = "stonith" 39 | startdards = "standards" 40 | stontih = "stonith" 41 | timedouted = "timeouted" 42 | ugrade = "upgrade" 43 | understore = "underscore" 44 | usefull = "useful" 45 | valeus = "values" 46 | webbrick = "webrick" 47 | wpecified = "specified" 48 | digets = "digests" 49 | unexpedted = "unexpected" 50 | --------------------------------------------------------------------------------