summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2021-12-01 09:44:33 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2021-12-01 09:44:33 +0000
commit4a6b0b753a98dad7922c2f00397126a10fdb7828 (patch)
tree75602f575d059347217ad759f6ec6e0f21e37d95
parentAdding upstream version 2.1.1. (diff)
downloadpacemaker-upstream.tar.xz
pacemaker-upstream.zip
Adding upstream version 2.1.2.upstream/2.1.2upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
-rw-r--r--ChangeLog61
-rw-r--r--GNUmakefile292
-rw-r--r--INSTALL.md7
-rw-r--r--Makefile.am8
-rw-r--r--configure.ac184
-rw-r--r--cts/cli/regression.tools.exp680
-rw-r--r--cts/cli/regression.validity.exp1
-rwxr-xr-xcts/cts-cli.in54
-rw-r--r--cts/cts-exec.in66
-rw-r--r--cts/cts-fencing.in60
-rwxr-xr-xcts/cts-regression.in5
-rw-r--r--cts/cts-scheduler.in19
-rw-r--r--cts/lab/CIB.py6
-rw-r--r--cts/lab/CTStests.py2
-rw-r--r--cts/lab/CTSvars.py.in1
-rw-r--r--cts/lab/patterns.py1
-rw-r--r--cts/scheduler/summary/remote-connection-unrecoverable.summary2
-rw-r--r--cts/scheduler/summary/remote-recover-all.summary4
-rw-r--r--cts/scheduler/summary/remote-recover-no-resources.summary2
-rw-r--r--cts/scheduler/summary/remote-recover-unknown.summary4
-rw-r--r--cts/scheduler/summary/stonith-4.summary2
-rw-r--r--daemons/attrd/attrd_elections.c8
-rw-r--r--daemons/based/based_io.c23
-rw-r--r--daemons/controld/Makefile.am3
-rw-r--r--daemons/controld/controld_based.c33
-rw-r--r--daemons/controld/controld_callbacks.c8
-rw-r--r--daemons/controld/controld_control.c16
-rw-r--r--daemons/controld/controld_execd.c355
-rw-r--r--daemons/controld/controld_execd_state.c151
-rw-r--r--daemons/controld/controld_fencing.c32
-rw-r--r--daemons/controld/controld_fencing.h1
-rw-r--r--daemons/controld/controld_fsa.h2
-rw-r--r--daemons/controld/controld_join_client.c2
-rw-r--r--daemons/controld/controld_lrm.h26
-rw-r--r--daemons/controld/controld_messages.c28
-rw-r--r--daemons/controld/controld_remote_ra.c185
-rw-r--r--daemons/controld/controld_te_actions.c28
-rw-r--r--daemons/controld/controld_te_callbacks.c12
-rw-r--r--daemons/controld/controld_te_events.c28
-rw-r--r--daemons/controld/controld_te_utils.c61
-rw-r--r--daemons/controld/controld_transition.c17
-rw-r--r--daemons/controld/controld_transition.h2
-rw-r--r--daemons/controld/pacemaker-controld.h1
-rw-r--r--daemons/execd/Makefile.am7
-rw-r--r--daemons/execd/cts-exec-helper.c32
-rw-r--r--daemons/execd/execd_alerts.c21
-rw-r--r--daemons/execd/execd_commands.c354
-rw-r--r--daemons/execd/pacemaker-execd.c6
-rw-r--r--daemons/execd/remoted_pidone.c4
-rw-r--r--daemons/fenced/Makefile.am5
-rwxr-xr-xdaemons/fenced/fence_watchdog.in284
-rw-r--r--daemons/fenced/fenced_commands.c522
-rw-r--r--daemons/fenced/fenced_history.c12
-rw-r--r--daemons/fenced/fenced_remote.c108
-rw-r--r--daemons/fenced/pacemaker-fenced.c138
-rw-r--r--daemons/fenced/pacemaker-fenced.h33
-rw-r--r--daemons/pacemakerd/Makefile.am1
-rw-r--r--daemons/pacemakerd/pacemakerd.c4
-rw-r--r--daemons/pacemakerd/pacemakerd.h16
-rw-r--r--daemons/pacemakerd/pcmkd_corosync.c38
-rw-r--r--daemons/pacemakerd/pcmkd_subdaemons.c293
-rw-r--r--daemons/schedulerd/Makefile.am3
-rw-r--r--devel/Makefile.am2
-rw-r--r--devel/coccinelle/rename-var.cocci29
-rw-r--r--doc/Makefile.am12
-rw-r--r--doc/sphinx/Makefile.am7
-rw-r--r--doc/sphinx/Pacemaker_Administration/tools.rst4
-rw-r--r--doc/sphinx/Pacemaker_Development/c.rst224
-rw-r--r--doc/sphinx/Pacemaker_Development/components.rst11
-rw-r--r--doc/sphinx/Pacemaker_Development/evolution.rst47
-rw-r--r--doc/sphinx/Pacemaker_Development/hacking.rst69
-rw-r--r--doc/sphinx/Pacemaker_Development/helpers.rst209
-rw-r--r--doc/sphinx/Pacemaker_Development/index.rst1
-rw-r--r--doc/sphinx/Pacemaker_Explained/advanced-resources.rst15
-rw-r--r--doc/sphinx/Pacemaker_Explained/fencing.rst16
-rw-r--r--doc/sphinx/Pacemaker_Explained/resources.rst47
-rw-r--r--etc/Makefile.am2
-rwxr-xr-xextra/resources/HealthIOWait44
-rw-r--r--extra/resources/Makefile.am3
-rw-r--r--include/crm/Makefile.am3
-rw-r--r--include/crm/cib/internal.h47
-rw-r--r--include/crm/common/Makefile.am2
-rw-r--r--include/crm/common/curses_internal.h47
-rw-r--r--include/crm/common/internal.h11
-rw-r--r--include/crm/common/iso8601_internal.h3
-rw-r--r--include/crm/common/logging.h7
-rw-r--r--include/crm/common/options_internal.h17
-rw-r--r--include/crm/common/output.h12
-rw-r--r--include/crm/common/output_internal.h2
-rw-r--r--include/crm/common/results.h231
-rw-r--r--include/crm/common/results_internal.h42
-rw-r--r--include/crm/common/strings_internal.h5
-rw-r--r--include/crm/common/xml.h5
-rw-r--r--include/crm/common/xml_internal.h2
-rw-r--r--include/crm/crm.h4
-rw-r--r--include/crm/fencing/internal.h35
-rw-r--r--include/crm/lrmd.h19
-rw-r--r--include/crm/lrmd_internal.h7
-rw-r--r--include/crm/pengine/internal.h9
-rw-r--r--include/crm/pengine/pe_types.h5
-rw-r--r--include/crm/services.h217
-rw-r--r--include/crm/services_compat.h95
-rw-r--r--include/crm/services_internal.h11
-rw-r--r--include/crm/stonith-ng.h57
-rw-r--r--include/pacemaker-internal.h1
-rw-r--r--include/pacemaker.h92
-rw-r--r--include/pcmki/Makefile.am1
-rw-r--r--include/pcmki/pcmki_sched_allocate.h51
-rw-r--r--include/pcmki/pcmki_sched_utils.h38
-rw-r--r--include/pcmki/pcmki_scheduler.h39
-rw-r--r--include/pcmki/pcmki_simulate.h117
-rw-r--r--include/pcmki/pcmki_transition.h85
-rw-r--r--lib/cib/Makefile.am2
-rw-r--r--lib/cib/cib_client.c17
-rw-r--r--lib/cib/cib_file.c10
-rw-r--r--lib/cib/cib_native.c9
-rw-r--r--lib/cib/cib_remote.c9
-rw-r--r--lib/cib/cib_utils.c44
-rw-r--r--lib/cluster/Makefile.am2
-rw-r--r--lib/cluster/cluster.c2
-rw-r--r--lib/cluster/corosync.c8
-rw-r--r--lib/cluster/cpg.c126
-rw-r--r--lib/cluster/membership.c2
-rw-r--r--lib/common/Makefile.am19
-rw-r--r--lib/common/acl.c50
-rw-r--r--lib/common/agents.c2
-rw-r--r--lib/common/cmdline.c6
-rw-r--r--lib/common/crmcommon_private.h42
-rw-r--r--lib/common/io.c29
-rw-r--r--lib/common/iso8601.c59
-rw-r--r--lib/common/logging.c73
-rw-r--r--lib/common/mainloop.c6
-rw-r--r--lib/common/mock.c52
-rw-r--r--lib/common/mock.mk2
-rw-r--r--lib/common/mock_private.h23
-rw-r--r--lib/common/nvpair.c7
-rw-r--r--lib/common/operations.c16
-rw-r--r--lib/common/options.c42
-rw-r--r--lib/common/patchset.c39
-rw-r--r--lib/common/results.c134
-rw-r--r--lib/common/strings.c90
-rw-r--r--lib/common/tests/Makefile.am21
-rw-r--r--lib/common/tests/agents/Makefile.am20
-rw-r--r--lib/common/tests/agents/pcmk_stonith_param_test.c65
-rw-r--r--lib/common/tests/cmdline/Makefile.am22
-rw-r--r--lib/common/tests/cmdline/pcmk__cmdline_preproc_test.c69
-rw-r--r--lib/common/tests/flags/Makefile.am29
-rw-r--r--lib/common/tests/flags/pcmk__clear_flags_as_test.c50
-rw-r--r--lib/common/tests/flags/pcmk__set_flags_as_test.c30
-rw-r--r--lib/common/tests/flags/pcmk_all_flags_set_test.c40
-rw-r--r--lib/common/tests/flags/pcmk_any_flags_set_test.c34
-rw-r--r--lib/common/tests/io/Makefile.am26
-rw-r--r--lib/common/tests/io/pcmk__full_path_test.c47
-rw-r--r--lib/common/tests/io/pcmk__get_tmpdir_test.c93
-rw-r--r--lib/common/tests/iso8601/Makefile.am17
-rw-r--r--lib/common/tests/iso8601/pcmk__readable_interval_test.c38
-rw-r--r--lib/common/tests/operations/Makefile.am20
-rw-r--r--lib/common/tests/operations/parse_op_key_test.c206
-rw-r--r--lib/common/tests/results/Makefile.am20
-rw-r--r--lib/common/tests/results/pcmk__results_test.c66
-rw-r--r--lib/common/tests/strings/Makefile.am26
-rw-r--r--lib/common/tests/strings/crm_get_msec_test.c62
-rw-r--r--lib/common/tests/strings/crm_is_true_test.c69
-rw-r--r--lib/common/tests/strings/crm_str_to_boolean_test.c104
-rw-r--r--lib/common/tests/strings/pcmk__add_word_test.c58
-rw-r--r--lib/common/tests/strings/pcmk__btoa_test.c24
-rw-r--r--lib/common/tests/strings/pcmk__char_in_any_str_test.c49
-rw-r--r--lib/common/tests/strings/pcmk__ends_with_test.c69
-rw-r--r--lib/common/tests/strings/pcmk__parse_ll_range_test.c99
-rw-r--r--lib/common/tests/strings/pcmk__scan_double_test.c145
-rw-r--r--lib/common/tests/strings/pcmk__starts_with_test.c47
-rw-r--r--lib/common/tests/strings/pcmk__str_any_of_test.c61
-rw-r--r--lib/common/tests/strings/pcmk__str_in_list_test.c98
-rw-r--r--lib/common/tests/strings/pcmk__strcmp_test.c89
-rw-r--r--lib/common/tests/utils/Makefile.am35
-rw-r--r--lib/common/tests/utils/pcmk_hostname_test.c71
-rw-r--r--lib/common/tests/utils/pcmk_str_is_infinity_test.c61
-rw-r--r--lib/common/tests/utils/pcmk_str_is_minus_infinity_test.c55
-rw-r--r--lib/common/tests/xpath/Makefile.am20
-rw-r--r--lib/common/tests/xpath/pcmk__xpath_node_id_test.c46
-rw-r--r--lib/common/xml.c205
-rw-r--r--lib/fencing/Makefile.am6
-rw-r--r--lib/fencing/fencing_private.h51
-rw-r--r--lib/fencing/st_client.c158
-rw-r--r--lib/fencing/st_lha.c2
-rw-r--r--lib/fencing/st_output.c10
-rw-r--r--lib/fencing/st_rhcs.c2
-rw-r--r--lib/lrmd/Makefile.am2
-rw-r--r--lib/lrmd/lrmd_alerts.c7
-rw-r--r--lib/lrmd/lrmd_client.c251
-rw-r--r--lib/pacemaker-pe_status.pc.in2
-rw-r--r--lib/pacemaker-service.pc.in2
-rw-r--r--lib/pacemaker.pc.in2
-rw-r--r--lib/pacemaker/Makefile.am19
-rw-r--r--lib/pacemaker/libpacemaker_private.h201
-rw-r--r--lib/pacemaker/pcmk_cluster_queries.c20
-rw-r--r--lib/pacemaker/pcmk_graph_consumer.c854
-rw-r--r--lib/pacemaker/pcmk_graph_logging.c (renamed from lib/pacemaker/pcmk_trans_utils.c)173
-rw-r--r--lib/pacemaker/pcmk_graph_producer.c (renamed from lib/pacemaker/pcmk_sched_graph.c)613
-rw-r--r--lib/pacemaker/pcmk_output.c122
-rw-r--r--lib/pacemaker/pcmk_sched_allocate.c994
-rw-r--r--lib/pacemaker/pcmk_sched_bundle.c234
-rw-r--r--lib/pacemaker/pcmk_sched_clone.c221
-rw-r--r--lib/pacemaker/pcmk_sched_colocation.c1059
-rw-r--r--lib/pacemaker/pcmk_sched_constraints.c2998
-rw-r--r--lib/pacemaker/pcmk_sched_fencing.c486
-rw-r--r--lib/pacemaker/pcmk_sched_group.c197
-rw-r--r--lib/pacemaker/pcmk_sched_location.c672
-rw-r--r--lib/pacemaker/pcmk_sched_messages.c36
-rw-r--r--lib/pacemaker/pcmk_sched_native.c1102
-rw-r--r--lib/pacemaker/pcmk_sched_ordering.c1535
-rw-r--r--lib/pacemaker/pcmk_sched_promotable.c155
-rw-r--r--lib/pacemaker/pcmk_sched_remote.c737
-rw-r--r--lib/pacemaker/pcmk_sched_resource.c78
-rw-r--r--lib/pacemaker/pcmk_sched_tickets.c486
-rw-r--r--lib/pacemaker/pcmk_sched_transition.c84
-rw-r--r--lib/pacemaker/pcmk_sched_utilization.c101
-rw-r--r--lib/pacemaker/pcmk_sched_utils.c151
-rw-r--r--lib/pacemaker/pcmk_simulate.c540
-rw-r--r--lib/pacemaker/pcmk_trans_graph.c335
-rw-r--r--lib/pacemaker/pcmk_trans_unpack.c347
-rw-r--r--lib/pengine/Makefile.am4
-rw-r--r--lib/pengine/bundle.c19
-rw-r--r--lib/pengine/clone.c21
-rw-r--r--lib/pengine/common.c10
-rw-r--r--lib/pengine/group.c14
-rw-r--r--lib/pengine/native.c15
-rw-r--r--lib/pengine/pe_output.c286
-rw-r--r--lib/pengine/rules.c2
-rw-r--r--lib/pengine/tests/rules/Makefile.am23
-rw-r--r--lib/pengine/tests/rules/pe_cron_range_satisfied_test.c88
-rw-r--r--lib/pengine/unpack.c190
-rw-r--r--lib/pengine/utils.c638
-rw-r--r--lib/services/Makefile.am4
-rw-r--r--lib/services/services.c536
-rw-r--r--lib/services/services_linux.c659
-rw-r--r--lib/services/services_lsb.c83
-rw-r--r--lib/services/services_lsb.h5
-rw-r--r--lib/services/services_nagios.c93
-rw-r--r--lib/services/services_nagios.h8
-rw-r--r--lib/services/services_ocf.c179
-rw-r--r--lib/services/services_ocf.h31
-rw-r--r--lib/services/services_private.h25
-rw-r--r--lib/services/systemd.c591
-rw-r--r--lib/services/systemd.h11
-rw-r--r--lib/services/upstart.c490
-rw-r--r--lib/services/upstart.h11
-rw-r--r--m4/glibtests.m431
-rw-r--r--m4/version.m42
-rw-r--r--maint/mocked/Makefile44
-rw-r--r--maint/mocked/based-notifyfenced.c245
-rw-r--r--maint/mocked/based.c330
-rw-r--r--maint/mocked/based.h49
-rw-r--r--mk/common.mk66
-rw-r--r--mk/glib-tap.mk136
-rw-r--r--mk/man.mk74
-rw-r--r--mk/release.mk57
-rw-r--r--mk/tap.mk16
-rw-r--r--rpm/Makefile.am268
-rw-r--r--rpm/pacemaker.spec.in16
-rw-r--r--rpm/rpmlintrc27
-rw-r--r--tools/Makefile.am1
-rw-r--r--tools/crm_mon.c73
-rw-r--r--tools/crm_mon.h51
-rw-r--r--tools/crm_mon_curses.c43
-rw-r--r--tools/crm_resource.c35
-rw-r--r--tools/crm_resource.h3
-rw-r--r--tools/crm_resource_print.c97
-rw-r--r--tools/crm_resource_runtime.c203
-rw-r--r--tools/crm_rule.c28
-rw-r--r--tools/crm_simulate.c752
-rw-r--r--tools/crm_ticket.c4
-rw-r--r--tools/crm_verify.c29
-rw-r--r--tools/report.common.in6
-rw-r--r--xml/api/crm_resource-2.14.rng297
275 files changed, 18511 insertions, 13306 deletions
diff --git a/ChangeLog b/ChangeLog
index f706fe1..8c4fe01 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,62 @@
+* Tue Nov 23 2021 Ken Gaillot <kgaillot@redhat.com> Pacemaker-2.1.2
+- Changesets: 462
+- Diff: 223 files changed, 16518 insertions(+), 11743 deletions(-)
+
+- Features added since Pacemaker-2.1.1
+ + build: when built with --with-initdir, Pacemaker uses the value to find
+ LSB resources (in addition to being where Pacemaker's own
+ init scripts are installed)
+ + build: cmocka is new dependency for unit tests ("make check")
+ + rpm: fence_watchdog now comes with pacemaker package (not pacemaker-cli)
+ + daemons: metadata for cluster options supports OCF 1.1 standard
+ + executor: nagios warning results now map to OCF "degraded" result code
+ + fencing: pcmk_delay_base can optionally specify different delays per node
+ + fencing: pcmk_host_map supports escaped characters such as spaces in values
+ + resources: HealthIOWait agent supports OCF 1.1 standard, and validate works
+ + tools: crm_mon shows exit reasons for actions failed due to internal errors
+ + tools: crm_mon failed action display is more human-friendly by default
+ + tools: crm_resource --force-* now outputs exit reasons if available
+
+- Fixes since Pacemaker-2.1.1
+ + pkg-config: return correct value for ocfdir (regression introduced in 2.1.0)
+ + tools: fix crm_mon --hide-headers and related options
+ (regression introduced in 2.0.4)
+ + attrd: check election status upon node loss to avoid election timeout
+ + controller: improved handling of executor connection failures
+ + executor: properly detect systemd unit existence
+ + pacemakerd: recover properly from Corosync crash
+ + fencing: fencing results are now sorted with sub-second resolution
+ + fencing: fix fence_watchdog version output, metadata output, and man page
+ + fencing: mark state as done if remapped "on" times out
+ + tools: map LSB status to OCF correctly with crm_resource --force-check
+
+- Public API changes since Pacemaker-2.1.1
+ + libcrmcommon: deprecate PCMK_OCF_EXEC_ERROR
+ + libcrmcommon: deprecate PCMK_OCF_PENDING
+ + libcrmcommon: deprecate PCMK_OCF_SIGNAL
+ + libcrmcommon: add CRM_EX_DEGRADED and CRM_EX_DEGRADED_PROMOTED
+ + libcrmcommon: add enum pcmk_exec_status
+ + libcrmcommon: add PCMK_EXEC_MAX
+ + libcrmcommon: add PCMK_EXEC_NO_FENCE_DEVICE
+ + libcrmcommon: add PCMK_EXEC_NO_SECRETS
+ + libcrmcommon: add pcmk_exec_status_str()
+ + libcrmcommon: add pcmk_rc2ocf()
+ + libcrmcommon: deprecate PCMK_OCF_TIMEOUT
+ + libcrmservice: add services_result2ocf()
+ + libcrmservice: deprecate enum op_status
+ + libcrmservice: deprecate LSB_ROOT_DIR
+ + libcrmservice: deprecate NAGIOS_NOT_INSTALLED
+ + libcrmservice: deprecate NAGIOS_STATE_DEPENDENT
+ + libcrmservice: deprecate services_get_ocf_exitcode()
+ + libcrmservice: deprecate services_list() and services_action_create()
+ + libcrmservice: deprecate services_lrm_status_str()
+ + libpacemaker: add enum pcmk_sim_flags
+ + libpacemaker: add pcmk_injections_t
+ + libpacemaker: add pcmk_free_injections()
+ + libpacemaker: add pcmk_simulate()
+ + libstonithd: add opaque member to stonith_event_t
+ + libstonithd: add opaque member to stonith_callback_data_t
+
* Thu Sep 09 2021 Ken Gaillot <kgaillot@redhat.com> Pacemaker-2.1.1
- Changesets: 231
- Diff:
@@ -755,7 +814,7 @@
compare a node attribute against a resource parameter
+ New "stonith-max-attempts" cluster option to specify how many times
fencing can fail for a target before the cluster will no longer
- immediately re-attempt it (previously hard-coded at 10)
+ immediately re-attempt it (previously hard-coded at 10)
+ New "cluster-ipc-limit" cluster option to avoid IPC client eviction in
large clusters
+ Failures are now tracked per operation type, as well as per node and
diff --git a/GNUmakefile b/GNUmakefile
index e3e03c0..6375378 100644
--- a/GNUmakefile
+++ b/GNUmakefile
@@ -28,76 +28,19 @@ EXTRA_CLEAN_TARGETS = ancillary-clean
abs_srcdir ?= $(shell pwd)
abs_builddir ?= $(shell pwd)
+# Define release-related variables
+include $(abs_srcdir)/mk/release.mk
+
GLIB_CFLAGS ?= $(pkg-config --cflags glib-2.0)
PACKAGE ?= pacemaker
-
-# Definitions that specify what various targets will apply to
-
-COMMIT ?= HEAD
-
-# TAG defaults to DIST when not in a git checkout (e.g. from a distribution),
-# the tag name if COMMIT is tagged, and the full commit ID otherwise.
-TAG ?= $(shell T=$$(git describe --tags --exact-match '$(COMMIT)' 2>/dev/null); \
- test -n "$${T}" && echo "$${T}" \
- || git log --pretty=format:%H -n 1 '$(COMMIT)' 2>/dev/null || echo DIST)
-lparen = (
-rparen = )
-
-# SPEC_COMMIT is identical to TAG for DIST and tagged releases, otherwise it is
-# the short commit ID (which must be used in order for "make export" to use the
-# same archive name as "make dist")
-SPEC_COMMIT ?= $(shell \
- case $(TAG) in \
- Pacemaker-*|DIST$(rparen) \
- echo '$(TAG)' ;; \
- *$(rparen) \
- git log --pretty=format:%h -n 1 '$(TAG)';; \
- esac)$(shell \
- if [ x$(DIRTY) != x ]; then echo ".mod"; fi)
-SPEC_ABBREV = $(shell printf %s '$(SPEC_COMMIT)' | wc -c)
-
-LAST_RC ?= $(shell test -e /Volumes || git tag -l | grep Pacemaker | sort -Vr | grep rc | head -n 1)
-ifneq ($(origin VERSION), undefined)
-LAST_RELEASE ?= Pacemaker-$(VERSION)
-else
-LAST_RELEASE ?= $(shell git tag -l | grep Pacemaker | sort -Vr | grep -v rc | head -n 1)
-endif
-NEXT_RELEASE ?= $(shell echo $(LAST_RELEASE) | awk -F. '/[0-9]+\./{$$3+=1;OFS=".";print $$1,$$2,$$3}')
-
# indent target: Limit indent to these directories
INDENT_DIRS ?= .
# indent target: Extra options to pass to indent
INDENT_OPTS ?=
-# This Makefile can create 2 types of distributions:
-#
-# - "make dist" is automake's native functionality, based on the various
-# dist/nodist make variables; it always uses the current sources
-#
-# - "make export" is a custom target based on git archive and relevant entries
-# from .gitattributes; it defaults to current sources but can use any git tag
-#
-# Both types use the TARFILE name for the result, though they generate
-# different contents.
-#
-# The directory is named pacemaker-DIST when not in a git checkout (e.g.
-# from a distribution itself), pacemaker-<version_part_of_tag> for tagged
-# commits, and pacemaker-<short_commit> otherwise.
-distdir = $(PACKAGE)-$(shell \
- case $(TAG) in \
- DIST$(rparen) \
- echo DIST;; \
- Pacemaker-*$(rparen) \
- echo '$(TAG)' | cut -c11-;; \
- *$(rparen) \
- git log --pretty=format:%h -n 1 '$(TAG)';; \
- esac)$(shell \
- if [ x$(DIRTY) != x ]; then echo ".mod"; fi)
-TARFILE = $(abs_builddir)/$(distdir).tar.gz
-
.PHONY: init
init:
test -e $(top_srcdir)/configure || ./autogen.sh
@@ -107,220 +50,18 @@ init:
build: init
$(MAKE) $(AM_MAKEFLAGS) core
-export:
- if [ ! -f "$(TARFILE)" ]; then \
- if [ x$(DIRTY) != x ]; then \
- git commit -m "DO-NOT-PUSH" -a; \
- git archive --prefix=$(distdir)/ -o "$(TARFILE)" HEAD^{tree}; \
- git reset --mixed HEAD^; \
- else \
- git archive --prefix=$(distdir)/ -o "$(TARFILE)" $(TAG)^{tree}; \
- fi; \
- echo "`date`: Rebuilt $(TARFILE)"; \
- else \
- echo "`date`: Using existing tarball: $(TARFILE)"; \
- fi
-
-## RPM-related targets
-
-# Where to put RPM artifacts; possible values:
-#
-# - subtree (default): RPM sources (i.e. TARFILE) in top-level build directory,
-# everything else in dedicated "rpm" subdirectory of build tree
-#
-# - toplevel (deprecated): RPM sources, spec, and source rpm in top-level build
-# directory, everything else uses the usual rpmbuild defaults
-RPMDEST ?= subtree
-
-RPM_SPEC_DIR_toplevel = $(abs_builddir)
-RPM_SRCRPM_DIR_toplevel = $(abs_builddir)
-RPM_OPTS_toplevel = --define "_sourcedir $(abs_builddir)" \
- --define "_specdir $(RPM_SPEC_DIR_toplevel)" \
- --define "_srcrpmdir $(RPM_SRCRPM_DIR_toplevel)"
-
-RPM_SPEC_DIR_subtree = $(abs_builddir)/rpm/SPECS
-RPM_SRCRPM_DIR_subtree = $(abs_builddir)/rpm/SRPMS
-RPM_OPTS_subtree = --define "_sourcedir $(abs_builddir)" \
- --define "_topdir $(abs_builddir)/rpm"
-
-RPM_SPEC_DIR = $(RPM_SPEC_DIR_$(RPMDEST))
-RPM_SRCRPM_DIR = $(RPM_SRCRPM_DIR_$(RPMDEST))
-RPM_OPTS = $(RPM_OPTS_$(RPMDEST))
-
-WITH ?= --without doc
-BUILD_COUNTER ?= build.counter
-LAST_COUNT = $(shell test ! -e $(BUILD_COUNTER) && echo 0; test -e $(BUILD_COUNTER) && cat $(BUILD_COUNTER))
-COUNT = $(shell expr 1 + $(LAST_COUNT))
-SPECVERSION ?= $(COUNT)
-
-MOCK_DIR = $(abs_builddir)/mock
-MOCK_OPTIONS ?= --resultdir=$(MOCK_DIR) --no-cleanup-after
-
-F ?= $(shell test ! -e /etc/fedora-release && echo 0; test -e /etc/fedora-release && rpm --eval %{fedora})
-ARCH ?= $(shell test ! -e /etc/fedora-release && uname -m; test -e /etc/fedora-release && rpm --eval %{_arch})
-MOCK_CFG ?= $(shell test -e /etc/fedora-release && echo fedora-$(F)-$(ARCH))
-
-# rpmbuild wrapper that translates "--with[out] FEATURE" into RPM macros
-#
-# Unfortunately, at least recent versions of rpm do not support mentioned
-# switch. To work this around, we can emulate mechanism that rpm uses
-# internally: unfold the flags into respective macro definitions:
-#
-# --with[out] FOO -> --define "_with[out]_FOO --with[out]-FOO"
-#
-# $(1) ... WITH string (e.g., --with pre_release --without doc)
-# $(2) ... options following the initial "rpmbuild" in the command
-# $(3) ... final arguments determined with $2 (e.g., pacemaker.spec)
-#
-# Note that if $(3) is a specfile, extra case is taken so as to reflect
-# pcmkversion correctly (using in-place modification).
-#
-# Also note that both ways to specify long option with an argument
-# (i.e., what getopt and, importantly, rpm itself support) can be used:
-#
-# --with FOO
-# --with=FOO
-rpmbuild-with = \
- WITH=$$(getopt -o "" -l with:,without: -- $(1)) || exit 1; \
- CMD='rpmbuild $(2)'; PREREL=0; \
- eval set -- "$${WITH}"; \
- while true; do \
- case "$$1" in \
- --with) CMD="$${CMD} --define \"_with_$$2 --with-$$2\""; \
- [ "$$2" != pre_release ] || PREREL=1; shift 2;; \
- --without) CMD="$${CMD} --define \"_without_$$2 --without-$$2\""; \
- [ "$$2" != pre_release ] || PREREL=0; shift 2;; \
- --) shift ; break ;; \
- *) echo "cannot parse WITH: $$1"; exit 1;; \
- esac; \
- done; \
- case "$(3)" in \
- *.spec) { [ $${PREREL} -eq 0 ] || [ $(LAST_RELEASE) = $(TAG) ]; } \
- && sed -i "s/^\(%global pcmkversion \).*/\1$$(echo $(LAST_RELEASE) | sed -e s:Pacemaker-:: -e s:-.*::)/" $(3) \
- || sed -i "s/^\(%global pcmkversion \).*/\1$$(echo $(NEXT_RELEASE) | sed -e s:Pacemaker-:: -e s:-.*::)/" $(3);; \
- esac; \
- CMD="$${CMD} $(3)"; \
- eval "$${CMD}"
-
-# Depend on spec-clean so it gets rebuilt every time
-$(RPM_SPEC_DIR)/$(PACKAGE).spec: spec-clean rpm/pacemaker.spec.in
- $(AM_V_at)$(MKDIR_P) $(RPM_SPEC_DIR) # might not exist in VPATH build
- $(AM_V_GEN)if [ x != x"`git ls-files -m rpm/pacemaker.spec.in 2>/dev/null`" ]; then \
- cat $(abs_srcdir)/rpm/pacemaker.spec.in; \
- elif git cat-file -e $(TAG):rpm/pacemaker.spec.in 2>/dev/null; then \
- git show $(TAG):rpm/pacemaker.spec.in; \
- elif git cat-file -e $(TAG):pacemaker.spec.in 2>/dev/null; then \
- git show $(TAG):pacemaker.spec.in; \
- else \
- cat $(abs_srcdir)/rpm/pacemaker.spec.in; \
- fi | sed \
- -e "s/^\(%global pcmkversion \).*/\1$$(echo $(LAST_RELEASE) | sed -e s:Pacemaker-:: -e s:-.*::)/" \
- -e 's/global\ specversion\ .*/global\ specversion\ $(SPECVERSION)/' \
- -e 's/global\ commit\ .*/global\ commit\ $(SPEC_COMMIT)/' \
- -e 's/global\ commit_abbrev\ .*/global\ commit_abbrev\ $(SPEC_ABBREV)/' \
- -e "s/PACKAGE_DATE/$$(date +'%a %b %d %Y')/" \
- -e "s/PACKAGE_VERSION/$$(git describe --tags $(TAG) | sed -e s:Pacemaker-:: -e s:-.*::)/" \
- > "$@"
-
-.PHONY: $(PACKAGE).spec
-$(PACKAGE).spec: $(RPM_SPEC_DIR)/$(PACKAGE).spec
-
-.PHONY: spec-clean
-spec-clean:
- -rm -f $(RPM_SPEC_DIR)/$(PACKAGE).spec
-
-.PHONY: srpm
-srpm: export srpm-clean $(RPM_SPEC_DIR)/$(PACKAGE).spec
- if [ -e $(BUILD_COUNTER) ]; then \
- echo $(COUNT) > $(BUILD_COUNTER); \
- fi
- $(call rpmbuild-with,$(WITH),-bs $(RPM_OPTS),$(RPM_SPEC_DIR)/$(PACKAGE).spec)
-
-.PHONY: srpm-clean
-srpm-clean:
- -rm -f $(RPM_SRCRPM_DIR)/*.src.rpm
-
-.PHONY: chroot
-chroot: mock-$(MOCK_CFG) mock-install-$(MOCK_CFG) mock-sh-$(MOCK_CFG)
- @echo "Done"
-
-.PHONY: mock-next
-mock-next:
- $(MAKE) $(AM_MAKEFLAGS) F=$(shell expr 1 + $(F)) mock
-
-.PHONY: mock-rawhide
-mock-rawhide:
- $(MAKE) $(AM_MAKEFLAGS) F=rawhide mock
-
-mock-install-%:
- @echo "Installing packages"
- mock --root=$* $(MOCK_OPTIONS) --install $(MOCK_DIR)/*.rpm \
- vi sudo valgrind lcov gdb fence-agents psmisc
-
-.PHONY: mock-install
-mock-install: mock-install-$(MOCK_CFG)
- @echo "Done"
-
-.PHONY: mock-sh
-mock-sh: mock-sh-$(MOCK_CFG)
- @echo "Done"
-
-mock-sh-%:
- @echo "Connecting"
- mock --root=$* $(MOCK_OPTIONS) --shell
- @echo "Done"
-
-mock-%: srpm mock-clean
- mock $(MOCK_OPTIONS) --root=$* --no-cleanup-after --rebuild \
- $(WITH) $(RPM_SRCRPM_DIR)/*.src.rpm
-
-.PHONY: mock
-mock: mock-$(MOCK_CFG)
- @echo "Done"
-
-.PHONY: dirty
-dirty:
- $(MAKE) $(AM_MAKEFLAGS) DIRTY=yes mock
-
-.PHONY: mock-clean
-mock-clean:
- -rm -rf $(MOCK_DIR)
-
-.PHONY: rpm-dep
-rpm-dep: $(RPM_SPEC_DIR)/$(PACKAGE).spec
- sudo yum-builddep $(PACKAGE).spec
-
-# e.g. make WITH="--with pre_release" rpm
-.PHONY: rpm
-rpm: srpm
- @echo To create custom builds, edit the flags and options in $(PACKAGE).spec first
- $(call rpmbuild-with,$(WITH),$(RPM_OPTS),--rebuild $(RPM_SRCRPM_DIR)/*.src.rpm)
-
-.PHONY: rpm-clean
-rpm-clean:
- -if [ "$(RPMDEST)" = "subtree" ]; then \
- rm -rf "$(abs_builddir)/rpm/BUILD" \
- "$(abs_builddir)/rpm/BUILDROOT" \
- "$(abs_builddir)/rpm/RPMS" \
- "$(abs_builddir)/rpm/SPECS" \
- "$(abs_builddir)/rpm/SRPMS"; \
- else \
- rm -f $(abs_builddir)/$(PACKAGE).spec \
- $(abs_builddir)/*.src.rpm; \
- fi
-
-.PHONY: rpmlint
-rpmlint: $(RPM_SPEC_DIR)/$(PACKAGE).spec
- rpmlint -f rpm/rpmlintrc "$<"
-
-.PHONY: release
-release:
- $(MAKE) $(AM_MAKEFLAGS) TAG=$(LAST_RELEASE) rpm
-
-.PHONY: rc
-rc:
- $(MAKE) $(AM_MAKEFLAGS) TAG=$(LAST_RC) rpm
+## RPM-related targets (deprecated; use targets in rpm subdirectory instead)
+
+# Pass option depending on whether automake has been run or not
+USE_FILE = $(shell test -z "$(srcdir)" && echo "-f Makefile.am")
+
+.PHONY: $(PACKAGE).spec chroot dirty export mock rc release rpm rpmlint srpm
+$(PACKAGE).spec chroot dirty export mock rc release rpm rpmlint srpm:
+ $(MAKE) $(AM_MAKEFLAGS) -C rpm $(USE_FILE) "$@"
+.PHONY: mock-% rpm-% spec-% srpm-%
+mock-% rpm-% spec-% srpm-%:
+ $(MAKE) $(AM_MAKEFLAGS) -C rpm $(USE_FILE) "$@"
## Static analysis via coverity
@@ -506,6 +247,8 @@ gnulib-update:
maint/gnulib/gnulib-tool \
--source-base=lib/gnu --lgpl=2 --no-vc-files --no-conditional-dependencies \
$(GNU_MODS_AVOID:%=--avoid %) --import $(GNU_MODS)
+ sed -i -e "s/bundled(gnulib).*/bundled(gnulib) = $(date +'%Y%m%d')/" \
+ rpm/pacemaker.spec.in
## Coverage/profiling
@@ -524,5 +267,4 @@ coverage-clean:
-rm -rf coverage
-find . \( -name "*.gcno" -o -name "*.gcda" \) -exec rm -f \{\} \;
-ancillary-clean: rpm-clean mock-clean coverity-clean coverage-clean
- -rm -f $(TARFILE)
+ancillary-clean: mock-clean coverity-clean coverage-clean
diff --git a/INSTALL.md b/INSTALL.md
index 3592d8c..dbdf79a 100644
--- a/INSTALL.md
+++ b/INSTALL.md
@@ -17,7 +17,10 @@
| 0.17.0 or later | libqb-devel | libqb-devel | libqb-dev |
| 3.4 or later | python3 | python3 | python3 |
-Also: GNU make
+Also:
+* make must be GNU (or compatible) (setting MAKE=gmake might also work but is
+ untested)
+* GNU (or compatible) getopt must be somewhere on the PATH
### Cluster Stack Dependencies
@@ -48,7 +51,9 @@ Also: GNU make
| documentation | | docbook-style-xsl | docbook-xsl-stylesheets | docbook-xsl |
| documentation | | python3-sphinx | python3-sphinx | python3-sphinx |
| documentation (PDF) | | latexmk texlive texlive-capt-of texlive-collection-xetex texlive-fncychap texlive-framed texlive-multirow texlive-needspace texlive-tabulary texlive-titlesec texlive-threeparttable texlive-upquote texlive-wrapfig texlive-xetex | texlive texlive-latex | texlive texlive-latex-extra |
+| annotated source code as HTML via "make global" | | global | global | global |
| RPM packages via "make rpm" | 4.11 or later | rpm | rpm | (n/a) |
+| unit tests | | libcmocka-devel | libcmocka-devel | libcmocka-dev |
## Optional testing dependencies
* procps and psmisc (if running cts-exec, cts-fencing, or CTS)
diff --git a/Makefile.am b/Makefile.am
index f6ad23e..6e841dc 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -10,7 +10,6 @@
# This directory must be same as in configure.ac's AC_CONFIG_MACRO_DIR
ACLOCAL_AMFLAGS = -I m4
-# m4/glibtests.m4 is copied from https://gitlab.gnome.org/GNOME/glib/blob/master/m4macros/glibtests.m4.
EXTRA_DIST = CONTRIBUTING.md \
GNUmakefile \
INSTALL.md \
@@ -18,14 +17,11 @@ EXTRA_DIST = CONTRIBUTING.md \
autogen.sh \
m4/CC_CHECK_LDFLAGS.m4 \
m4/CHECK_ENUM_VALUE.m4 \
- m4/glibtests.m4 \
m4/gnulib-cache.m4 \
m4/gnulib-tool.m4 \
m4/PKG_CHECK_VAR.m4 \
m4/REQUIRE_HEADER.m4 \
- m4/version.m4 \
- rpm/rpmlintrc \
- rpm/pacemaker.spec.in
+ m4/version.m4
DISTCLEANFILES = config.status
@@ -52,7 +48,7 @@ AM_DISTCHECK_CONFIGURE_FLAGS = --prefix="$$dc_install_base/usr" \
CORE_INSTALL = replace include lib daemons tools xml
# Only these will get built with a plain "make" or "make clean"
-CORE = $(CORE_INSTALL) cts
+CORE = $(CORE_INSTALL) cts rpm
SUBDIRS = $(CORE) devel doc etc extra maint tests
diff --git a/configure.ac b/configure.ac
index eaa1658..e0b8037 100644
--- a/configure.ac
+++ b/configure.ac
@@ -84,8 +84,6 @@ export CC_IN_CONFIGURE
LDD=ldd
-GLIB_TESTS
-
dnl ========================================================================
dnl Compiler characteristics
dnl ========================================================================
@@ -153,6 +151,34 @@ cc_restore_flags() {
CFLAGS=$ac_save_CFLAGS
}
+# expand_path_option $path_variable_name $default
+expand_path_option() {
+ # The first argument is the variable *name* (not value)
+ ac_path_varname="$1"
+
+ # Get the original value of the variable
+ ac_path_value=$(eval echo "\${${ac_path_varname}}")
+
+ # Expand any literal variable expressions in the value so that we don't
+ # end up with something like '${prefix}' in #defines etc.
+ #
+ # Autoconf deliberately leaves values unexpanded to allow overriding
+ # the configure script choices in make commands (for example,
+ # "make exec_prefix=/foo install"). No longer being able to do this seems
+ # like no great loss.
+ eval ac_path_value=$(eval echo "${ac_path_value}")
+
+ # Use (expanded) default if necessary
+ AS_IF([test x"${ac_path_value}" = x""],
+ [eval ac_path_value=$(eval echo "$2")])
+
+ # Require a full path
+ AS_CASE(["$ac_path_value"],
+ [/*], [eval ${ac_path_varname}="$ac_path_value"],
+ [*], [AC_MSG_ERROR([$ac_path_varname value "$ac_path_value" is not a full path])]
+ )
+}
+
# yes_no_try $user_response $default
DISABLED=0
REQUIRED=1
@@ -183,6 +209,16 @@ dnl ===============================================
dnl Actual library checks come later, but pkg-config can be used here to grab
dnl external values to use as defaults for configure options
+dnl Per the autoconf docs, --enable-*/--disable-* options should control
+dnl features inherent to Pacemaker, while --with-*/--without-* options should
+dnl control the use of external software. However, --enable-*/--disable-* may
+dnl implicitly require additional external dependencies, and
+dnl --with-*/--without-* may implicitly enable or disable features, so the
+dnl line is blurry.
+dnl
+dnl We also use --with-* options for custom file, directory, and path
+dnl locations, since autoconf does not provide an option type for those.
+
dnl --enable-* options: build process
AC_ARG_ENABLE([quiet],
@@ -222,7 +258,7 @@ AC_ARG_ENABLE([upstart],
yes_no_try "$enable_upstart" "try"
enable_upstart=$?
-dnl --enable-* options: compatibility
+dnl --enable-* options: features inherent to Pacemaker
AC_ARG_ENABLE([compat-2.0],
[AS_HELP_STRING([--enable-compat-2.0], m4_normalize([
@@ -250,7 +286,7 @@ yes_no_try "$enable_legacy_links" "no"
enable_legacy_links=$?
AM_CONDITIONAL([BUILD_LEGACY_LINKS], [test $enable_legacy_links -ne $DISABLED])
-dnl --with-* options: basic parameters
+dnl --with-* options: external software support, and custom locations
dnl This argument is defined via an M4 macro so default can be a variable
AC_DEFUN([VERSION_ARG],
@@ -364,6 +400,15 @@ AC_ARG_WITH([corosync],
yes_no_try "$with_corosync" "try"
with_corosync=$?
+dnl @TODO This should be a corosync pkgconfig variable
+PCMK__COROSYNC_CONF=""
+AC_ARG_WITH([corosync-conf],
+ [AS_HELP_STRING([--with-corosync-conf], m4_normalize([
+ location of Corosync configuration file
+ @<:@SYSCONFDIR/corosync/corosync.conf@:>@]))],
+ [ PCMK__COROSYNC_CONF="$withval" ]
+)
+
AC_ARG_WITH([nagios],
[AS_HELP_STRING([--with-nagios], [support nagios resources])]
)
@@ -457,8 +502,6 @@ AC_ARG_WITH([ocfrapath],
[ OCF_RA_PATH="$withval" ]
)
AC_SUBST(OCF_RA_PATH)
-AC_DEFINE_UNQUOTED([OCF_RA_PATH], ["$OCF_RA_PATH"],
- [OCF directories to search for resource agents ])
OCF_RA_INSTALL_DIR="$OCF_ROOT_DIR/resource.d"
AC_ARG_WITH([ocfrainstalldir],
@@ -503,6 +546,14 @@ AC_ARG_WITH([sanitizers],
[ SANITIZERS="$withval" ],
[ SANITIZERS="" ])
+dnl Environment variable options
+
+AC_ARG_VAR([CFLAGS_HARDENED_LIB], [extra C compiler flags for hardened libraries])
+AC_ARG_VAR([LDFLAGS_HARDENED_LIB], [extra linker flags for hardened libraries])
+
+AC_ARG_VAR([CFLAGS_HARDENED_EXE], [extra C compiler flags for hardened executables])
+AC_ARG_VAR([LDFLAGS_HARDENED_EXE], [extra linker flags for hardened executables])
+
dnl ===============================================
dnl General Processing
@@ -586,68 +637,59 @@ case $libdir in
;;
esac
-dnl Expand autoconf variables so that we don't end up with '${prefix}'
-dnl in #defines and python scripts
-dnl NOTE: Autoconf deliberately leaves them unexpanded to allow
-dnl make exec_prefix=/foo install
-dnl No longer being able to do this seems like no great loss to me...
-
-eval prefix="`eval echo ${prefix}`"
-eval exec_prefix="`eval echo ${exec_prefix}`"
-eval bindir="`eval echo ${bindir}`"
-eval sbindir="`eval echo ${sbindir}`"
-eval libexecdir="`eval echo ${libexecdir}`"
-eval datadir="`eval echo ${datadir}`"
-eval sysconfdir="`eval echo ${sysconfdir}`"
-eval sharedstatedir="`eval echo ${sharedstatedir}`"
-eval localstatedir="`eval echo ${localstatedir}`"
-eval libdir="`eval echo ${libdir}`"
-eval includedir="`eval echo ${includedir}`"
-eval oldincludedir="`eval echo ${oldincludedir}`"
-eval infodir="`eval echo ${infodir}`"
-eval mandir="`eval echo ${mandir}`"
+dnl Expand values of autoconf-provided directory options
+expand_path_option prefix
+expand_path_option exec_prefix
+expand_path_option bindir
+expand_path_option sbindir
+expand_path_option libexecdir
+expand_path_option datadir
+expand_path_option sysconfdir
+expand_path_option sharedstatedir
+expand_path_option localstatedir
+expand_path_option libdir
+expand_path_option includedir
+expand_path_option oldincludedir
+expand_path_option infodir
+expand_path_option mandir
dnl Home-grown variables
-if [ test "x${runstatedir}" = "x" ]; then
- if [ test "x${pcmk_runstatedir}" = "x" ]; then
- runstatedir="${localstatedir}/run"
- else
- runstatedir="${pcmk_runstatedir}"
- fi
-fi
-eval runstatedir="$(eval echo ${runstatedir})"
+AS_IF([test x"${runstatedir}" = x""], [runstatedir="${pcmk_runstatedir}"])
+expand_path_option runstatedir "${localstatedir}/run"
AC_DEFINE_UNQUOTED([PCMK_RUN_DIR], ["$runstatedir"],
[Location for modifiable per-process data])
AC_SUBST(runstatedir)
-eval INITDIR="${INITDIR}"
-eval docdir="`eval echo ${docdir}`"
-if test x"${docdir}" = x""; then
- docdir=${datadir}/doc/${PACKAGE}-${VERSION}
-fi
+expand_path_option INITDIR
+AC_DEFINE_UNQUOTED([PCMK__LSB_INIT_DIR], ["$INITDIR"],
+ [Location for LSB init scripts])
+
+expand_path_option docdir "${datadir}/doc/${PACKAGE}-${VERSION}"
AC_SUBST(docdir)
-if test x"${CONFIGDIR}" = x""; then
- CONFIGDIR="${sysconfdir}/sysconfig"
-fi
+
+expand_path_option CONFIGDIR "${sysconfdir}/sysconfig"
AC_SUBST(CONFIGDIR)
-if test x"${CRM_LOG_DIR}" = x""; then
- CRM_LOG_DIR="${localstatedir}/log/pacemaker"
-fi
+expand_path_option PCMK__COROSYNC_CONF "${sysconfdir}/corosync/corosync.conf"
+AC_SUBST(PCMK__COROSYNC_CONF)
+
+expand_path_option CRM_LOG_DIR "${localstatedir}/log/pacemaker"
AC_DEFINE_UNQUOTED(CRM_LOG_DIR,"$CRM_LOG_DIR", Location for Pacemaker log file)
AC_SUBST(CRM_LOG_DIR)
-if test x"${CRM_BUNDLE_DIR}" = x""; then
- CRM_BUNDLE_DIR="${localstatedir}/log/pacemaker/bundles"
-fi
+expand_path_option CRM_BUNDLE_DIR "${localstatedir}/log/pacemaker/bundles"
AC_DEFINE_UNQUOTED(CRM_BUNDLE_DIR,"$CRM_BUNDLE_DIR", Location for Pacemaker bundle logs)
AC_SUBST(CRM_BUNDLE_DIR)
-eval PCMK__FENCE_BINDIR="`eval echo ${PCMK__FENCE_BINDIR}`"
+expand_path_option PCMK__FENCE_BINDIR
AC_DEFINE_UNQUOTED(PCMK__FENCE_BINDIR,"$PCMK__FENCE_BINDIR",
[Location for executable fence agents])
+expand_path_option OCF_RA_PATH
+AC_DEFINE_UNQUOTED([OCF_RA_PATH], ["$OCF_RA_PATH"],
+ [OCF directories to search for resource agents ])
+
AS_IF([test x"${PCMK_GNUTLS_PRIORITIES}" != x""], [],
[AC_MSG_ERROR([--with-gnutls-priorities value must not be empty])])
AC_DEFINE_UNQUOTED([PCMK_GNUTLS_PRIORITIES], ["$PCMK_GNUTLS_PRIORITIES"],
@@ -1041,6 +1083,38 @@ AC_CHECK_DECLS([CLOCK_MONOTONIC], [PCMK_FEATURES="$PCMK_FEATURES monotonic"], []
]])
dnl ========================================================================
+dnl Unit test declarations
+dnl ========================================================================
+
+AC_CHECK_DECLS([assert_float_equal], [], [], [[
+ #include <stdarg.h>
+ #include <stddef.h>
+ #include <setjmp.h>
+ #include <cmocka.h>
+]])
+
+cc_temp_flags "$CFLAGS -Wl,--wrap=uname"
+
+WRAPPABLE_UNAME="no"
+
+AC_MSG_CHECKING([if uname() can be wrapped])
+AC_RUN_IFELSE([AC_LANG_SOURCE([[
+#include <sys/utsname.h>
+int __wrap_uname(struct utsname *buf) {
+return 100;
+}
+int main(int argc, char **argv) {
+struct utsname x;
+return uname(&x) == 100 ? 0 : 1;
+}
+]])],
+ [ WRAPPABLE_UNAME="yes" ], [ WRAPPABLE_UNAME="no"])
+AC_MSG_RESULT([$WRAPPABLE_UNAME])
+AM_CONDITIONAL([WRAPPABLE_UNAME], [test "$WRAPPABLE_UNAME" = "yes"])
+
+cc_restore_flags
+
+dnl ========================================================================
dnl Structures
dnl ========================================================================
@@ -1321,7 +1395,7 @@ AC_DEFINE_UNQUOTED(SBIN_DIR,"$sbindir",[Location for system binaries])
AC_PATH_PROGS(GIT, git false)
AC_MSG_CHECKING([build version])
-BUILD_VERSION=77db578727
+BUILD_VERSION=ada5c3b36e2
if test $BUILD_VERSION != ":%h$"; then
AC_MSG_RESULT([$BUILD_VERSION (archive hash)])
elif test -x $GIT && test -d .git; then
@@ -1659,12 +1733,6 @@ if export | fgrep " CFLAGS=" > /dev/null; then
unset SAVED_CFLAGS
fi
-AC_ARG_VAR([CFLAGS_HARDENED_LIB], [extra C compiler flags for hardened libraries])
-AC_ARG_VAR([LDFLAGS_HARDENED_LIB], [extra linker flags for hardened libraries])
-
-AC_ARG_VAR([CFLAGS_HARDENED_EXE], [extra C compiler flags for hardened executables])
-AC_ARG_VAR([LDFLAGS_HARDENED_EXE], [extra linker flags for hardened executables])
-
CC_EXTRAS=""
AS_IF([test "$GCC" != yes], [CFLAGS="$CFLAGS -g"], [
@@ -1972,6 +2040,7 @@ CONFIG_FILES_EXEC([cts/cts-cli],
[cts/support/fence_dummy],
[cts/support/pacemaker-cts-dummyd],
[daemons/fenced/fence_legacy],
+ [daemons/fenced/fence_watchdog],
[doc/abi-check],
[extra/resources/ClusterMon],
[extra/resources/HealthSMART],
@@ -2048,6 +2117,8 @@ AC_CONFIG_FILES(Makefile \
lib/common/tests/agents/Makefile \
lib/common/tests/cmdline/Makefile \
lib/common/tests/flags/Makefile \
+ lib/common/tests/io/Makefile \
+ lib/common/tests/iso8601/Makefile \
lib/common/tests/operations/Makefile \
lib/common/tests/results/Makefile \
lib/common/tests/strings/Makefile \
@@ -2064,6 +2135,7 @@ AC_CONFIG_FILES(Makefile \
lib/lrmd/Makefile \
lib/services/Makefile \
maint/Makefile \
+ rpm/Makefile \
tests/Makefile \
tools/Makefile \
tools/report.collector \
diff --git a/cts/cli/regression.tools.exp b/cts/cli/regression.tools.exp
index 6c46bf1..150b507 100644
--- a/cts/cli/regression.tools.exp
+++ b/cts/cli/regression.tools.exp
@@ -324,6 +324,8 @@ Deleted crm_config option: id=(null) name=cluster-delay
* Passed: crm_attribute - Delete cluster option with -i
=#=#=#= Begin test: Create node1 and bring it online =#=#=#=
Current cluster status:
+ * Full List of Resources:
+ * No resources
Performing Requested Modifications:
* Bringing node node1 online
@@ -336,6 +338,8 @@ Revised Cluster Status:
* Node List:
* Online: [ node1 ]
+ * Full List of Resources:
+ * No resources
=#=#=#= Current cib after: Create node1 and bring it online =#=#=#=
<cib epoch="9" num_updates="2" admin_epoch="0">
<configuration>
@@ -485,6 +489,19 @@ scope=status name=fail-count-foo value=3
</cib>
=#=#=#= End test: Query a fail count - OK (0) =#=#=#=
* Passed: crm_failcount - Query a fail count
+=#=#=#= Begin test: Show node attributes with crm_simulate =#=#=#=
+Current cluster status:
+ * Node List:
+ * Online: [ node1 ]
+
+ * Full List of Resources:
+ * No resources
+
+ * Node Attributes:
+ * Node: node1:
+ * ram : 1024M
+=#=#=#= End test: Show node attributes with crm_simulate - OK (0) =#=#=#=
+* Passed: crm_simulate - Show node attributes with crm_simulate
=#=#=#= Begin test: Delete a transient (fail-count) node attribute =#=#=#=
Deleted status attribute: id=status-node1-fail-count-foo name=fail-count-foo
@@ -4095,6 +4112,669 @@ Resources colocated with clone:
5
=#=#=#= End test: List guest,remote nodes - OK (0) =#=#=#=
* Passed: crmadmin - List guest,remote nodes
+=#=#=#= Begin test: Show allocation scores with crm_simulate =#=#=#=
+<pacemaker-result api-version="X" request="crm_mon.xml --show-scores --output-as=xml">
+ <cluster_status>
+ <nodes>
+ <node name="cluster01" id="1" online="true" standby="false" standby_onfail="false" maintenance="false" pending="false" unclean="false" shutdown="false" expected_up="true" is_dc="false" resources_running="7" type="member"/>
+ <node name="cluster02" id="2" online="true" standby="false" standby_onfail="false" maintenance="false" pending="false" unclean="false" shutdown="false" expected_up="true" is_dc="true" resources_running="9" type="member"/>
+ <node name="httpd-bundle-0" id="httpd-bundle-0" online="true" standby="false" standby_onfail="false" maintenance="false" pending="false" unclean="false" shutdown="false" expected_up="false" is_dc="false" resources_running="1" type="remote" id_as_resource="httpd-bundle-docker-0"/>
+ <node name="httpd-bundle-1" id="httpd-bundle-1" online="true" standby="false" standby_onfail="false" maintenance="false" pending="false" unclean="false" shutdown="false" expected_up="false" is_dc="false" resources_running="1" type="remote" id_as_resource="httpd-bundle-docker-1"/>
+ <node name="httpd-bundle-2" id="httpd-bundle-2" online="false" standby="false" standby_onfail="false" maintenance="false" pending="false" unclean="false" shutdown="false" expected_up="false" is_dc="false" resources_running="0" type="remote" id_as_resource="httpd-bundle-docker-2"/>
+ </nodes>
+ <resources>
+ <clone id="ping-clone" multi_state="false" unique="false" managed="true" disabled="false" failed="false" failure_ignored="false">
+ <resource id="ping" resource_agent="ocf:pacemaker:ping" role="Started" active="true" orphaned="false" blocked="false" managed="true" failed="false" failure_ignored="false" nodes_running_on="1">
+ <node name="cluster02" id="2" cached="true"/>
+ </resource>
+ <resource id="ping" resource_agent="ocf:pacemaker:ping" role="Started" active="true" orphaned="false" blocked="false" managed="true" failed="false" failure_ignored="false" nodes_running_on="1">
+ <node name="cluster01" id="1" cached="true"/>
+ </resource>
+ </clone>
+ <resource id="Fencing" resource_agent="stonith:fence_xvm" role="Started" active="true" orphaned="false" blocked="false" managed="true" failed="false" failure_ignored="false" nodes_running_on="1">
+ <node name="cluster01" id="1" cached="true"/>
+ </resource>
+ <resource id="dummy" resource_agent="ocf:pacemaker:Dummy" role="Started" active="true" orphaned="false" blocked="false" managed="true" failed="false" failure_ignored="false" nodes_running_on="1">
+ <node name="cluster02" id="2" cached="true"/>
+ </resource>
+ <clone id="inactive-clone" multi_state="false" unique="false" managed="true" disabled="true" failed="false" failure_ignored="false" target_role="stopped">
+ <resource id="inactive-dhcpd" resource_agent="lsb:dhcpd" role="Stopped" target_role="stopped" active="false" orphaned="false" blocked="false" managed="true" failed="false" failure_ignored="false" nodes_running_on="0"/>
+ <resource id="inactive-dhcpd" resource_agent="lsb:dhcpd" role="Stopped" target_role="stopped" active="false" orphaned="false" blocked="false" managed="true" failed="false" failure_ignored="false" nodes_running_on="0"/>
+ </clone>
+ <group id="inactive-group" number_resources="2" managed="true" disabled="true">
+ <resource id="inactive-dummy-1" resource_agent="ocf:pacemaker:Dummy" role="Stopped" target_role="stopped" active="false" orphaned="false" blocked="false" managed="true" failed="false" failure_ignored="false" nodes_running_on="0"/>
+ <resource id="inactive-dummy-2" resource_agent="ocf:pacemaker:Dummy" role="Stopped" target_role="stopped" active="false" orphaned="false" blocked="false" managed="true" failed="false" failure_ignored="false" nodes_running_on="0"/>
+ </group>
+ <bundle id="httpd-bundle" type="docker" image="pcmk:http" unique="false" managed="true" failed="false">
+ <replica id="0">
+ <resource id="httpd-bundle-ip-192.168.122.131" resource_agent="ocf:heartbeat:IPaddr2" role="Started" target_role="Started" active="true" orphaned="false" blocked="false" managed="true" failed="false" failure_ignored="false" nodes_running_on="1">
+ <node name="cluster01" id="1" cached="true"/>
+ </resource>
+ <resource id="httpd" resource_agent="ocf:heartbeat:apache" role="Started" target_role="Started" active="true" orphaned="false" blocked="false" managed="true" failed="false" failure_ignored="false" nodes_running_on="1">
+ <node name="httpd-bundle-0" id="httpd-bundle-0" cached="true"/>
+ </resource>
+ <resource id="httpd-bundle-docker-0" resource_agent="ocf:heartbeat:docker" role="Started" target_role="Started" active="true" orphaned="false" blocked="false" managed="true" failed="false" failure_ignored="false" nodes_running_on="1">
+ <node name="cluster01" id="1" cached="true"/>
+ </resource>
+ <resource id="httpd-bundle-0" resource_agent="ocf:pacemaker:remote" role="Started" target_role="Started" active="true" orphaned="false" blocked="false" managed="true" failed="false" failure_ignored="false" nodes_running_on="1">
+ <node name="cluster01" id="1" cached="true"/>
+ </resource>
+ </replica>
+ <replica id="1">
+ <resource id="httpd-bundle-ip-192.168.122.132" resource_agent="ocf:heartbeat:IPaddr2" role="Started" target_role="Started" active="true" orphaned="false" blocked="false" managed="true" failed="false" failure_ignored="false" nodes_running_on="1">
+ <node name="cluster02" id="2" cached="true"/>
+ </resource>
+ <resource id="httpd" resource_agent="ocf:heartbeat:apache" role="Started" target_role="Started" active="true" orphaned="false" blocked="false" managed="true" failed="false" failure_ignored="false" nodes_running_on="1">
+ <node name="httpd-bundle-1" id="httpd-bundle-1" cached="true"/>
+ </resource>
+ <resource id="httpd-bundle-docker-1" resource_agent="ocf:heartbeat:docker" role="Started" target_role="Started" active="true" orphaned="false" blocked="false" managed="true" failed="false" failure_ignored="false" nodes_running_on="1">
+ <node name="cluster02" id="2" cached="true"/>
+ </resource>
+ <resource id="httpd-bundle-1" resource_agent="ocf:pacemaker:remote" role="Started" target_role="Started" active="true" orphaned="false" blocked="false" managed="true" failed="false" failure_ignored="false" nodes_running_on="1">
+ <node name="cluster02" id="2" cached="true"/>
+ </resource>
+ </replica>
+ <replica id="2">
+ <resource id="httpd-bundle-ip-192.168.122.133" resource_agent="ocf:heartbeat:IPaddr2" role="Stopped" target_role="Started" active="false" orphaned="false" blocked="false" managed="true" failed="false" failure_ignored="false" nodes_running_on="0"/>
+ <resource id="httpd" resource_agent="ocf:heartbeat:apache" role="Stopped" target_role="Started" active="false" orphaned="false" blocked="false" managed="true" failed="false" failure_ignored="false" nodes_running_on="0"/>
+ <resource id="httpd-bundle-docker-2" resource_agent="ocf:heartbeat:docker" role="Stopped" target_role="Started" active="false" orphaned="false" blocked="false" managed="true" failed="false" failure_ignored="false" nodes_running_on="0"/>
+ <resource id="httpd-bundle-2" resource_agent="ocf:pacemaker:remote" role="Stopped" target_role="Started" active="false" orphaned="false" blocked="false" managed="true" failed="false" failure_ignored="false" nodes_running_on="0"/>
+ </replica>
+ </bundle>
+ <group id="exim-group" number_resources="2" managed="true" disabled="false">
+ <resource id="Public-IP" resource_agent="ocf:heartbeat:IPaddr" role="Started" active="true" orphaned="false" blocked="false" managed="true" failed="false" failure_ignored="false" nodes_running_on="1">
+ <node name="cluster02" id="2" cached="true"/>
+ </resource>
+ <resource id="Email" resource_agent="lsb:exim" role="Started" active="true" orphaned="false" blocked="false" managed="true" failed="false" failure_ignored="false" nodes_running_on="1">
+ <node name="cluster02" id="2" cached="true"/>
+ </resource>
+ </group>
+ <clone id="mysql-clone-group" multi_state="false" unique="false" managed="true" disabled="false" failed="false" failure_ignored="false">
+ <group id="mysql-group:0" number_resources="1" managed="true" disabled="false">
+ <resource id="mysql-proxy" resource_agent="lsb:mysql-proxy" role="Started" active="true" orphaned="false" blocked="false" managed="true" failed="false" failure_ignored="false" nodes_running_on="1">
+ <node name="cluster02" id="2" cached="true"/>
+ </resource>
+ </group>
+ <group id="mysql-group:1" number_resources="1" managed="true" disabled="false">
+ <resource id="mysql-proxy" resource_agent="lsb:mysql-proxy" role="Started" active="true" orphaned="false" blocked="false" managed="true" failed="false" failure_ignored="false" nodes_running_on="1">
+ <node name="cluster01" id="1" cached="true"/>
+ </resource>
+ </group>
+ <group id="mysql-group:2" number_resources="1" managed="true" disabled="false">
+ <resource id="mysql-proxy" resource_agent="lsb:mysql-proxy" role="Stopped" active="false" orphaned="false" blocked="false" managed="true" failed="false" failure_ignored="false" nodes_running_on="0"/>
+ </group>
+ <group id="mysql-group:3" number_resources="1" managed="true" disabled="false">
+ <resource id="mysql-proxy" resource_agent="lsb:mysql-proxy" role="Stopped" active="false" orphaned="false" blocked="false" managed="true" failed="false" failure_ignored="false" nodes_running_on="0"/>
+ </group>
+ <group id="mysql-group:4" number_resources="1" managed="true" disabled="false">
+ <resource id="mysql-proxy" resource_agent="lsb:mysql-proxy" role="Stopped" active="false" orphaned="false" blocked="false" managed="true" failed="false" failure_ignored="false" nodes_running_on="0"/>
+ </group>
+ </clone>
+ <clone id="promotable-clone" multi_state="true" unique="false" managed="true" disabled="false" failed="false" failure_ignored="false">
+ <resource id="promotable-rsc" resource_agent="ocf:pacemaker:Stateful" role="Promoted" active="true" orphaned="false" blocked="false" managed="true" failed="false" failure_ignored="false" nodes_running_on="1">
+ <node name="cluster02" id="2" cached="true"/>
+ </resource>
+ <resource id="promotable-rsc" resource_agent="ocf:pacemaker:Stateful" role="Unpromoted" active="true" orphaned="false" blocked="false" managed="true" failed="false" failure_ignored="false" nodes_running_on="1">
+ <node name="cluster01" id="1" cached="true"/>
+ </resource>
+ <resource id="promotable-rsc" resource_agent="ocf:pacemaker:Stateful" role="Stopped" active="false" orphaned="false" blocked="false" managed="true" failed="false" failure_ignored="false" nodes_running_on="0"/>
+ <resource id="promotable-rsc" resource_agent="ocf:pacemaker:Stateful" role="Stopped" active="false" orphaned="false" blocked="false" managed="true" failed="false" failure_ignored="false" nodes_running_on="0"/>
+ <resource id="promotable-rsc" resource_agent="ocf:pacemaker:Stateful" role="Stopped" active="false" orphaned="false" blocked="false" managed="true" failed="false" failure_ignored="false" nodes_running_on="0"/>
+ </clone>
+ </resources>
+ </cluster_status>
+ <allocations>
+ <node_weight function="pcmk__clone_allocate" node="cluster01" score="0" id="ping-clone"/>
+ <node_weight function="pcmk__clone_allocate" node="cluster02" score="0" id="ping-clone"/>
+ <node_weight function="pcmk__clone_allocate" node="cluster01" score="0" id="ping:0"/>
+ <node_weight function="pcmk__clone_allocate" node="cluster02" score="1" id="ping:0"/>
+ <node_weight function="pcmk__clone_allocate" node="cluster01" score="1" id="ping:1"/>
+ <node_weight function="pcmk__clone_allocate" node="cluster02" score="0" id="ping:1"/>
+ <node_weight function="pcmk__native_allocate" node="cluster01" score="0" id="ping:0"/>
+ <node_weight function="pcmk__native_allocate" node="cluster02" score="1" id="ping:0"/>
+ <node_weight function="pcmk__native_allocate" node="cluster01" score="1" id="ping:1"/>
+ <node_weight function="pcmk__native_allocate" node="cluster02" score="-INFINITY" id="ping:1"/>
+ <node_weight function="pcmk__native_allocate" node="cluster01" score="0" id="Fencing"/>
+ <node_weight function="pcmk__native_allocate" node="cluster02" score="0" id="Fencing"/>
+ <node_weight function="pcmk__native_allocate" node="cluster01" score="-INFINITY" id="dummy"/>
+ <node_weight function="pcmk__native_allocate" node="cluster02" score="0" id="dummy"/>
+ <node_weight function="pcmk__clone_allocate" node="cluster01" score="0" id="inactive-clone"/>
+ <node_weight function="pcmk__clone_allocate" node="cluster02" score="0" id="inactive-clone"/>
+ <node_weight function="pcmk__clone_allocate" node="cluster01" score="0" id="inactive-dhcpd:0"/>
+ <node_weight function="pcmk__clone_allocate" node="cluster02" score="0" id="inactive-dhcpd:0"/>
+ <node_weight function="pcmk__clone_allocate" node="cluster01" score="0" id="inactive-dhcpd:1"/>
+ <node_weight function="pcmk__clone_allocate" node="cluster02" score="0" id="inactive-dhcpd:1"/>
+ <node_weight function="pcmk__native_allocate" node="cluster01" score="-INFINITY" id="inactive-dhcpd:0"/>
+ <node_weight function="pcmk__native_allocate" node="cluster02" score="-INFINITY" id="inactive-dhcpd:0"/>
+ <node_weight function="pcmk__native_allocate" node="httpd-bundle-0" score="-INFINITY" id="inactive-dhcpd:0"/>
+ <node_weight function="pcmk__native_allocate" node="httpd-bundle-1" score="-INFINITY" id="inactive-dhcpd:0"/>
+ <node_weight function="pcmk__native_allocate" node="httpd-bundle-2" score="-INFINITY" id="inactive-dhcpd:0"/>
+ <node_weight function="pcmk__native_allocate" node="cluster01" score="-INFINITY" id="inactive-dhcpd:1"/>
+ <node_weight function="pcmk__native_allocate" node="cluster02" score="-INFINITY" id="inactive-dhcpd:1"/>
+ <node_weight function="pcmk__native_allocate" node="httpd-bundle-0" score="-INFINITY" id="inactive-dhcpd:1"/>
+ <node_weight function="pcmk__native_allocate" node="httpd-bundle-1" score="-INFINITY" id="inactive-dhcpd:1"/>
+ <node_weight function="pcmk__native_allocate" node="httpd-bundle-2" score="-INFINITY" id="inactive-dhcpd:1"/>
+ <node_weight function="pcmk__group_allocate" node="cluster01" score="0" id="inactive-group"/>
+ <node_weight function="pcmk__group_allocate" node="cluster02" score="0" id="inactive-group"/>
+ <node_weight function="pcmk__group_allocate" node="cluster01" score="0" id="inactive-dummy-1"/>
+ <node_weight function="pcmk__group_allocate" node="cluster02" score="0" id="inactive-dummy-1"/>
+ <node_weight function="pcmk__group_allocate" node="cluster01" score="0" id="inactive-dummy-2"/>
+ <node_weight function="pcmk__group_allocate" node="cluster02" score="0" id="inactive-dummy-2"/>
+ <node_weight function="pcmk__native_allocate" node="cluster01" score="-INFINITY" id="inactive-dummy-1"/>
+ <node_weight function="pcmk__native_allocate" node="cluster02" score="-INFINITY" id="inactive-dummy-1"/>
+ <node_weight function="pcmk__native_allocate" node="httpd-bundle-0" score="-INFINITY" id="inactive-dummy-1"/>
+ <node_weight function="pcmk__native_allocate" node="httpd-bundle-1" score="-INFINITY" id="inactive-dummy-1"/>
+ <node_weight function="pcmk__native_allocate" node="httpd-bundle-2" score="-INFINITY" id="inactive-dummy-1"/>
+ <node_weight function="pcmk__native_allocate" node="cluster01" score="-INFINITY" id="inactive-dummy-2"/>
+ <node_weight function="pcmk__native_allocate" node="cluster02" score="-INFINITY" id="inactive-dummy-2"/>
+ <node_weight function="pcmk__native_allocate" node="httpd-bundle-0" score="-INFINITY" id="inactive-dummy-2"/>
+ <node_weight function="pcmk__native_allocate" node="httpd-bundle-1" score="-INFINITY" id="inactive-dummy-2"/>
+ <node_weight function="pcmk__native_allocate" node="httpd-bundle-2" score="-INFINITY" id="inactive-dummy-2"/>
+ <node_weight function="pcmk__bundle_allocate" node="cluster01" score="0" id="httpd-bundle"/>
+ <node_weight function="pcmk__bundle_allocate" node="cluster02" score="0" id="httpd-bundle"/>
+ <node_weight function="pcmk__bundle_allocate" node="cluster01" score="0" id="httpd-bundle-docker-0"/>
+ <node_weight function="pcmk__bundle_allocate" node="cluster02" score="0" id="httpd-bundle-docker-0"/>
+ <node_weight function="pcmk__bundle_allocate" node="cluster01" score="0" id="httpd-bundle-ip-192.168.122.131"/>
+ <node_weight function="pcmk__bundle_allocate" node="cluster02" score="0" id="httpd-bundle-ip-192.168.122.131"/>
+ <node_weight function="pcmk__bundle_allocate" node="cluster01" score="0" id="httpd-bundle-0"/>
+ <node_weight function="pcmk__bundle_allocate" node="cluster02" score="0" id="httpd-bundle-0"/>
+ <node_weight function="pcmk__bundle_allocate" node="cluster01" score="0" id="httpd-bundle-docker-1"/>
+ <node_weight function="pcmk__bundle_allocate" node="cluster02" score="0" id="httpd-bundle-docker-1"/>
+ <node_weight function="pcmk__bundle_allocate" node="cluster01" score="0" id="httpd-bundle-ip-192.168.122.132"/>
+ <node_weight function="pcmk__bundle_allocate" node="cluster02" score="0" id="httpd-bundle-ip-192.168.122.132"/>
+ <node_weight function="pcmk__bundle_allocate" node="cluster01" score="0" id="httpd-bundle-1"/>
+ <node_weight function="pcmk__bundle_allocate" node="cluster02" score="0" id="httpd-bundle-1"/>
+ <node_weight function="pcmk__bundle_allocate" node="cluster01" score="0" id="httpd-bundle-docker-2"/>
+ <node_weight function="pcmk__bundle_allocate" node="cluster02" score="0" id="httpd-bundle-docker-2"/>
+ <node_weight function="pcmk__bundle_allocate" node="cluster01" score="0" id="httpd-bundle-ip-192.168.122.133"/>
+ <node_weight function="pcmk__bundle_allocate" node="cluster02" score="0" id="httpd-bundle-ip-192.168.122.133"/>
+ <node_weight function="pcmk__bundle_allocate" node="cluster01" score="0" id="httpd-bundle-2"/>
+ <node_weight function="pcmk__bundle_allocate" node="cluster02" score="0" id="httpd-bundle-2"/>
+ <node_weight function="pcmk__bundle_allocate" node="cluster01" score="0" id="httpd-bundle-clone"/>
+ <node_weight function="pcmk__bundle_allocate" node="cluster02" score="0" id="httpd-bundle-clone"/>
+ <node_weight function="pcmk__bundle_allocate" node="httpd-bundle-0" score="-INFINITY" id="httpd-bundle-clone"/>
+ <node_weight function="pcmk__bundle_allocate" node="httpd-bundle-1" score="-INFINITY" id="httpd-bundle-clone"/>
+ <node_weight function="pcmk__bundle_allocate" node="httpd-bundle-2" score="-INFINITY" id="httpd-bundle-clone"/>
+ <node_weight function="pcmk__bundle_allocate" node="httpd-bundle-0" score="501" id="httpd:0"/>
+ <node_weight function="pcmk__bundle_allocate" node="httpd-bundle-1" score="501" id="httpd:1"/>
+ <node_weight function="pcmk__bundle_allocate" node="httpd-bundle-2" score="500" id="httpd:2"/>
+ <node_weight function="pcmk__native_allocate" node="cluster01" score="0" id="httpd-bundle-docker-0"/>
+ <node_weight function="pcmk__native_allocate" node="cluster02" score="0" id="httpd-bundle-docker-0"/>
+ <node_weight function="pcmk__native_allocate" node="cluster01" score="-INFINITY" id="httpd-bundle-docker-1"/>
+ <node_weight function="pcmk__native_allocate" node="cluster02" score="0" id="httpd-bundle-docker-1"/>
+ <node_weight function="pcmk__native_allocate" node="cluster01" score="-INFINITY" id="httpd-bundle-docker-2"/>
+ <node_weight function="pcmk__native_allocate" node="cluster02" score="-INFINITY" id="httpd-bundle-docker-2"/>
+ <node_weight function="pcmk__native_allocate" node="cluster01" score="0" id="httpd-bundle-ip-192.168.122.131"/>
+ <node_weight function="pcmk__native_allocate" node="cluster02" score="-INFINITY" id="httpd-bundle-ip-192.168.122.131"/>
+ <node_weight function="pcmk__native_allocate" node="cluster01" score="10000" id="httpd-bundle-0"/>
+ <node_weight function="pcmk__native_allocate" node="cluster02" score="0" id="httpd-bundle-0"/>
+ <node_weight function="pcmk__native_allocate" node="httpd-bundle-0" score="INFINITY" id="httpd:0"/>
+ <node_weight function="pcmk__native_allocate" node="cluster01" score="-INFINITY" id="httpd-bundle-ip-192.168.122.132"/>
+ <node_weight function="pcmk__native_allocate" node="cluster02" score="0" id="httpd-bundle-ip-192.168.122.132"/>
+ <node_weight function="pcmk__native_allocate" node="cluster01" score="0" id="httpd-bundle-1"/>
+ <node_weight function="pcmk__native_allocate" node="cluster02" score="10000" id="httpd-bundle-1"/>
+ <node_weight function="pcmk__native_allocate" node="httpd-bundle-1" score="INFINITY" id="httpd:1"/>
+ <node_weight function="pcmk__native_allocate" node="cluster01" score="-INFINITY" id="httpd-bundle-ip-192.168.122.133"/>
+ <node_weight function="pcmk__native_allocate" node="cluster02" score="-INFINITY" id="httpd-bundle-ip-192.168.122.133"/>
+ <node_weight function="pcmk__native_allocate" node="cluster01" score="0" id="httpd-bundle-2"/>
+ <node_weight function="pcmk__native_allocate" node="cluster02" score="0" id="httpd-bundle-2"/>
+ <node_weight function="pcmk__native_allocate" node="httpd-bundle-2" score="INFINITY" id="httpd:2"/>
+ <node_weight function="pcmk__clone_allocate" node="cluster01" score="-INFINITY" id="httpd-bundle-clone"/>
+ <node_weight function="pcmk__clone_allocate" node="cluster02" score="-INFINITY" id="httpd-bundle-clone"/>
+ <node_weight function="pcmk__clone_allocate" node="httpd-bundle-0" score="0" id="httpd-bundle-clone"/>
+ <node_weight function="pcmk__clone_allocate" node="httpd-bundle-1" score="0" id="httpd-bundle-clone"/>
+ <node_weight function="pcmk__clone_allocate" node="httpd-bundle-2" score="0" id="httpd-bundle-clone"/>
+ <node_weight function="pcmk__clone_allocate" node="httpd-bundle-0" score="INFINITY" id="httpd:0"/>
+ <node_weight function="pcmk__clone_allocate" node="httpd-bundle-1" score="INFINITY" id="httpd:1"/>
+ <node_weight function="pcmk__clone_allocate" node="httpd-bundle-2" score="INFINITY" id="httpd:2"/>
+ <node_weight function="pcmk__group_allocate" node="cluster01" score="0" id="exim-group"/>
+ <node_weight function="pcmk__group_allocate" node="cluster02" score="0" id="exim-group"/>
+ <node_weight function="pcmk__group_allocate" node="cluster01" score="0" id="Public-IP"/>
+ <node_weight function="pcmk__group_allocate" node="cluster02" score="0" id="Public-IP"/>
+ <node_weight function="pcmk__group_allocate" node="cluster01" score="0" id="Email"/>
+ <node_weight function="pcmk__group_allocate" node="cluster02" score="0" id="Email"/>
+ <node_weight function="pcmk__native_allocate" node="cluster01" score="0" id="Public-IP"/>
+ <node_weight function="pcmk__native_allocate" node="cluster02" score="0" id="Public-IP"/>
+ <node_weight function="pcmk__native_allocate" node="cluster01" score="-INFINITY" id="Email"/>
+ <node_weight function="pcmk__native_allocate" node="cluster02" score="0" id="Email"/>
+ <node_weight function="pcmk__clone_allocate" node="cluster01" score="0" id="mysql-clone-group"/>
+ <node_weight function="pcmk__clone_allocate" node="cluster02" score="0" id="mysql-clone-group"/>
+ <node_weight function="pcmk__clone_allocate" node="cluster01" score="0" id="mysql-group:0"/>
+ <node_weight function="pcmk__clone_allocate" node="cluster02" score="0" id="mysql-group:0"/>
+ <node_weight function="pcmk__clone_allocate" node="cluster01" score="0" id="mysql-proxy:0"/>
+ <node_weight function="pcmk__clone_allocate" node="cluster02" score="1" id="mysql-proxy:0"/>
+ <node_weight function="pcmk__clone_allocate" node="cluster01" score="0" id="mysql-group:1"/>
+ <node_weight function="pcmk__clone_allocate" node="cluster02" score="0" id="mysql-group:1"/>
+ <node_weight function="pcmk__clone_allocate" node="cluster01" score="1" id="mysql-proxy:1"/>
+ <node_weight function="pcmk__clone_allocate" node="cluster02" score="0" id="mysql-proxy:1"/>
+ <node_weight function="pcmk__clone_allocate" node="cluster01" score="0" id="mysql-group:2"/>
+ <node_weight function="pcmk__clone_allocate" node="cluster02" score="0" id="mysql-group:2"/>
+ <node_weight function="pcmk__clone_allocate" node="cluster01" score="0" id="mysql-proxy:2"/>
+ <node_weight function="pcmk__clone_allocate" node="cluster02" score="0" id="mysql-proxy:2"/>
+ <node_weight function="pcmk__clone_allocate" node="cluster01" score="0" id="mysql-group:3"/>
+ <node_weight function="pcmk__clone_allocate" node="cluster02" score="0" id="mysql-group:3"/>
+ <node_weight function="pcmk__clone_allocate" node="cluster01" score="0" id="mysql-proxy:3"/>
+ <node_weight function="pcmk__clone_allocate" node="cluster02" score="0" id="mysql-proxy:3"/>
+ <node_weight function="pcmk__clone_allocate" node="cluster01" score="0" id="mysql-group:4"/>
+ <node_weight function="pcmk__clone_allocate" node="cluster02" score="0" id="mysql-group:4"/>
+ <node_weight function="pcmk__clone_allocate" node="cluster01" score="0" id="mysql-proxy:4"/>
+ <node_weight function="pcmk__clone_allocate" node="cluster02" score="0" id="mysql-proxy:4"/>
+ <node_weight function="pcmk__group_allocate" node="cluster01" score="0" id="mysql-group:0"/>
+ <node_weight function="pcmk__group_allocate" node="cluster02" score="0" id="mysql-group:0"/>
+ <node_weight function="pcmk__group_allocate" node="cluster01" score="0" id="mysql-proxy:0"/>
+ <node_weight function="pcmk__group_allocate" node="cluster02" score="1" id="mysql-proxy:0"/>
+ <node_weight function="pcmk__native_allocate" node="cluster01" score="0" id="mysql-proxy:0"/>
+ <node_weight function="pcmk__native_allocate" node="cluster02" score="1" id="mysql-proxy:0"/>
+ <node_weight function="pcmk__group_allocate" node="cluster01" score="0" id="mysql-group:1"/>
+ <node_weight function="pcmk__group_allocate" node="cluster02" score="-INFINITY" id="mysql-group:1"/>
+ <node_weight function="pcmk__group_allocate" node="cluster01" score="1" id="mysql-proxy:1"/>
+ <node_weight function="pcmk__group_allocate" node="cluster02" score="-INFINITY" id="mysql-proxy:1"/>
+ <node_weight function="pcmk__native_allocate" node="cluster01" score="1" id="mysql-proxy:1"/>
+ <node_weight function="pcmk__native_allocate" node="cluster02" score="-INFINITY" id="mysql-proxy:1"/>
+ <node_weight function="pcmk__group_allocate" node="cluster01" score="-INFINITY" id="mysql-group:2"/>
+ <node_weight function="pcmk__group_allocate" node="cluster02" score="-INFINITY" id="mysql-group:2"/>
+ <node_weight function="pcmk__group_allocate" node="cluster01" score="-INFINITY" id="mysql-proxy:2"/>
+ <node_weight function="pcmk__group_allocate" node="cluster02" score="-INFINITY" id="mysql-proxy:2"/>
+ <node_weight function="pcmk__native_allocate" node="cluster01" score="-INFINITY" id="mysql-proxy:2"/>
+ <node_weight function="pcmk__native_allocate" node="cluster02" score="-INFINITY" id="mysql-proxy:2"/>
+ <node_weight function="pcmk__group_allocate" node="cluster01" score="-INFINITY" id="mysql-group:3"/>
+ <node_weight function="pcmk__group_allocate" node="cluster02" score="-INFINITY" id="mysql-group:3"/>
+ <node_weight function="pcmk__group_allocate" node="cluster01" score="-INFINITY" id="mysql-proxy:3"/>
+ <node_weight function="pcmk__group_allocate" node="cluster02" score="-INFINITY" id="mysql-proxy:3"/>
+ <node_weight function="pcmk__native_allocate" node="cluster01" score="-INFINITY" id="mysql-proxy:3"/>
+ <node_weight function="pcmk__native_allocate" node="cluster02" score="-INFINITY" id="mysql-proxy:3"/>
+ <node_weight function="pcmk__group_allocate" node="cluster01" score="-INFINITY" id="mysql-group:4"/>
+ <node_weight function="pcmk__group_allocate" node="cluster02" score="-INFINITY" id="mysql-group:4"/>
+ <node_weight function="pcmk__group_allocate" node="cluster01" score="-INFINITY" id="mysql-proxy:4"/>
+ <node_weight function="pcmk__group_allocate" node="cluster02" score="-INFINITY" id="mysql-proxy:4"/>
+ <node_weight function="pcmk__native_allocate" node="cluster01" score="-INFINITY" id="mysql-proxy:4"/>
+ <node_weight function="pcmk__native_allocate" node="cluster02" score="-INFINITY" id="mysql-proxy:4"/>
+ <node_weight function="pcmk__clone_allocate" node="cluster01" score="0" id="promotable-clone"/>
+ <node_weight function="pcmk__clone_allocate" node="cluster02" score="0" id="promotable-clone"/>
+ <node_weight function="pcmk__clone_allocate" node="cluster01" score="0" id="promotable-rsc:0"/>
+ <node_weight function="pcmk__clone_allocate" node="cluster02" score="1" id="promotable-rsc:0"/>
+ <node_weight function="pcmk__clone_allocate" node="cluster01" score="1" id="promotable-rsc:1"/>
+ <node_weight function="pcmk__clone_allocate" node="cluster02" score="0" id="promotable-rsc:1"/>
+ <node_weight function="pcmk__clone_allocate" node="cluster01" score="0" id="promotable-rsc:2"/>
+ <node_weight function="pcmk__clone_allocate" node="cluster02" score="0" id="promotable-rsc:2"/>
+ <node_weight function="pcmk__clone_allocate" node="cluster01" score="0" id="promotable-rsc:3"/>
+ <node_weight function="pcmk__clone_allocate" node="cluster02" score="0" id="promotable-rsc:3"/>
+ <node_weight function="pcmk__clone_allocate" node="cluster01" score="0" id="promotable-rsc:4"/>
+ <node_weight function="pcmk__clone_allocate" node="cluster02" score="0" id="promotable-rsc:4"/>
+ <node_weight function="pcmk__native_allocate" node="cluster01" score="0" id="promotable-rsc:0"/>
+ <node_weight function="pcmk__native_allocate" node="cluster02" score="1" id="promotable-rsc:0"/>
+ <node_weight function="pcmk__native_allocate" node="cluster01" score="1" id="promotable-rsc:1"/>
+ <node_weight function="pcmk__native_allocate" node="cluster02" score="-INFINITY" id="promotable-rsc:1"/>
+ <node_weight function="pcmk__native_allocate" node="cluster01" score="-INFINITY" id="promotable-rsc:2"/>
+ <node_weight function="pcmk__native_allocate" node="cluster02" score="-INFINITY" id="promotable-rsc:2"/>
+ <node_weight function="pcmk__native_allocate" node="cluster01" score="-INFINITY" id="promotable-rsc:3"/>
+ <node_weight function="pcmk__native_allocate" node="cluster02" score="-INFINITY" id="promotable-rsc:3"/>
+ <node_weight function="pcmk__native_allocate" node="cluster01" score="-INFINITY" id="promotable-rsc:4"/>
+ <node_weight function="pcmk__native_allocate" node="cluster02" score="-INFINITY" id="promotable-rsc:4"/>
+ <promotion_score id="promotable-rsc:0" score="9" node="cluster02"/>
+ <promotion_score id="promotable-rsc:2" score="0"/>
+ <promotion_score id="promotable-rsc:3" score="0"/>
+ <promotion_score id="promotable-rsc:4" score="0"/>
+ <promotion_score id="promotable-rsc:1" score="-1" node="cluster01"/>
+ </allocations>
+ <actions>
+ <rsc_action action="start" resource="httpd-bundle-2" node="cluster01" reason="unrunnable httpd-bundle-docker-2 start" blocked="true"/>
+ <rsc_action action="start" resource="httpd:2" node="httpd-bundle-2" reason="unrunnable httpd-bundle-docker-2 start" blocked="true"/>
+ </actions>
+ <status code="0" message="OK"/>
+</pacemaker-result>
+=#=#=#= End test: Show allocation scores with crm_simulate - OK (0) =#=#=#=
+* Passed: crm_simulate - Show allocation scores with crm_simulate
+=#=#=#= Begin test: Show utilization with crm_simulate =#=#=#=
+4 of 32 resource instances DISABLED and 0 BLOCKED from further action due to failure
+
+[ cluster01 cluster02 ]
+[ httpd-bundle-0@cluster01 httpd-bundle-1@cluster02 ]
+
+Started: [ cluster01 cluster02 ]
+Fencing (stonith:fence_xvm): Started cluster01
+dummy (ocf:pacemaker:Dummy): Started cluster02
+Stopped (disabled): [ cluster01 cluster02 ]
+inactive-dummy-1 (ocf:pacemaker:Dummy): Stopped (disabled)
+inactive-dummy-2 (ocf:pacemaker:Dummy): Stopped (disabled)
+httpd-bundle-0 (192.168.122.131) (ocf:heartbeat:apache): Started cluster01
+httpd-bundle-1 (192.168.122.132) (ocf:heartbeat:apache): Started cluster02
+httpd-bundle-2 (192.168.122.133) (ocf:heartbeat:apache): Stopped
+Public-IP (ocf:heartbeat:IPaddr): Started cluster02
+Email (lsb:exim): Started cluster02
+Started: [ cluster01 cluster02 ]
+Promoted: [ cluster02 ]
+Unpromoted: [ cluster01 ]
+
+Only 'private' parameters to dummy_monitor_60000 on cluster02 changed: 0:0;16:2:0:4a9e64d6-e1dd-4395-917c-1596312eafe4
+Original: cluster01 capacity:
+Original: cluster02 capacity:
+Original: httpd-bundle-0 capacity:
+Original: httpd-bundle-1 capacity:
+Original: httpd-bundle-2 capacity:
+native_assign_node: ping:0 utilization on cluster02:
+native_assign_node: ping:1 utilization on cluster01:
+native_assign_node: Fencing utilization on cluster01:
+native_assign_node: dummy utilization on cluster02:
+native_assign_node: httpd-bundle-docker-0 utilization on cluster01:
+native_assign_node: httpd-bundle-docker-1 utilization on cluster02:
+native_assign_node: httpd-bundle-ip-192.168.122.131 utilization on cluster01:
+native_assign_node: httpd-bundle-0 utilization on cluster01:
+native_assign_node: httpd:0 utilization on httpd-bundle-0:
+native_assign_node: httpd-bundle-ip-192.168.122.132 utilization on cluster02:
+native_assign_node: httpd-bundle-1 utilization on cluster02:
+native_assign_node: httpd:1 utilization on httpd-bundle-1:
+native_assign_node: httpd-bundle-2 utilization on cluster01:
+native_assign_node: httpd:2 utilization on httpd-bundle-2:
+native_assign_node: Public-IP utilization on cluster02:
+native_assign_node: Email utilization on cluster02:
+native_assign_node: mysql-proxy:0 utilization on cluster02:
+native_assign_node: mysql-proxy:1 utilization on cluster01:
+native_assign_node: promotable-rsc:0 utilization on cluster02:
+native_assign_node: promotable-rsc:1 utilization on cluster01:
+Remaining: cluster01 capacity:
+Remaining: cluster02 capacity:
+Remaining: httpd-bundle-0 capacity:
+Remaining: httpd-bundle-1 capacity:
+Remaining: httpd-bundle-2 capacity:
+
+Start httpd-bundle-2 ( cluster01 ) due to unrunnable httpd-bundle-docker-2 start (blocked)
+Start httpd:2 ( httpd-bundle-2 ) due to unrunnable httpd-bundle-docker-2 start (blocked)
+=#=#=#= End test: Show utilization with crm_simulate - OK (0) =#=#=#=
+* Passed: crm_simulate - Show utilization with crm_simulate
+=#=#=#= Begin test: Simulate injecting a failure =#=#=#=
+4 of 32 resource instances DISABLED and 0 BLOCKED from further action due to failure
+
+Current cluster status:
+ * Node List:
+ * Online: [ cluster01 cluster02 ]
+ * GuestOnline: [ httpd-bundle-0@cluster01 httpd-bundle-1@cluster02 ]
+
+ * Full List of Resources:
+ * Clone Set: ping-clone [ping]:
+ * Started: [ cluster01 cluster02 ]
+ * Fencing (stonith:fence_xvm): Started cluster01
+ * dummy (ocf:pacemaker:Dummy): Started cluster02
+ * Clone Set: inactive-clone [inactive-dhcpd] (disabled):
+ * Stopped (disabled): [ cluster01 cluster02 ]
+ * Resource Group: inactive-group (disabled):
+ * inactive-dummy-1 (ocf:pacemaker:Dummy): Stopped (disabled)
+ * inactive-dummy-2 (ocf:pacemaker:Dummy): Stopped (disabled)
+ * Container bundle set: httpd-bundle [pcmk:http]:
+ * httpd-bundle-0 (192.168.122.131) (ocf:heartbeat:apache): Started cluster01
+ * httpd-bundle-1 (192.168.122.132) (ocf:heartbeat:apache): Started cluster02
+ * httpd-bundle-2 (192.168.122.133) (ocf:heartbeat:apache): Stopped
+ * Resource Group: exim-group:
+ * Public-IP (ocf:heartbeat:IPaddr): Started cluster02
+ * Email (lsb:exim): Started cluster02
+ * Clone Set: mysql-clone-group [mysql-group]:
+ * Started: [ cluster01 cluster02 ]
+ * Clone Set: promotable-clone [promotable-rsc] (promotable):
+ * Promoted: [ cluster02 ]
+ * Unpromoted: [ cluster01 ]
+
+Performing Requested Modifications:
+ * Injecting ping_monitor_10000@cluster02=1 into the configuration
+ * Injecting attribute fail-count-ping#monitor_10000=value++ into /node_state '2'
+ * Injecting attribute last-failure-ping#monitor_10000= into /node_state '2'
+
+Transition Summary:
+ * Recover ping:0 ( cluster02 )
+ * Start httpd-bundle-2 ( cluster01 ) due to unrunnable httpd-bundle-docker-2 start (blocked)
+ * Start httpd:2 ( httpd-bundle-2 ) due to unrunnable httpd-bundle-docker-2 start (blocked)
+
+Executing Cluster Transition:
+ * Cluster action: clear_failcount for ping on cluster02
+ * Pseudo action: ping-clone_stop_0
+ * Pseudo action: httpd-bundle_start_0
+ * Resource action: ping stop on cluster02
+ * Pseudo action: ping-clone_stopped_0
+ * Pseudo action: ping-clone_start_0
+ * Pseudo action: httpd-bundle-clone_start_0
+ * Resource action: ping start on cluster02
+ * Resource action: ping monitor=10000 on cluster02
+ * Pseudo action: ping-clone_running_0
+ * Pseudo action: httpd-bundle-clone_running_0
+ * Pseudo action: httpd-bundle_running_0
+
+Revised Cluster Status:
+ * Node List:
+ * Online: [ cluster01 cluster02 ]
+ * GuestOnline: [ httpd-bundle-0@cluster01 httpd-bundle-1@cluster02 ]
+
+ * Full List of Resources:
+ * Clone Set: ping-clone [ping]:
+ * Started: [ cluster01 cluster02 ]
+ * Fencing (stonith:fence_xvm): Started cluster01
+ * dummy (ocf:pacemaker:Dummy): Started cluster02
+ * Clone Set: inactive-clone [inactive-dhcpd] (disabled):
+ * Stopped (disabled): [ cluster01 cluster02 ]
+ * Resource Group: inactive-group (disabled):
+ * inactive-dummy-1 (ocf:pacemaker:Dummy): Stopped (disabled)
+ * inactive-dummy-2 (ocf:pacemaker:Dummy): Stopped (disabled)
+ * Container bundle set: httpd-bundle [pcmk:http]:
+ * httpd-bundle-0 (192.168.122.131) (ocf:heartbeat:apache): Started cluster01
+ * httpd-bundle-1 (192.168.122.132) (ocf:heartbeat:apache): Started cluster02
+ * httpd-bundle-2 (192.168.122.133) (ocf:heartbeat:apache): Stopped
+ * Resource Group: exim-group:
+ * Public-IP (ocf:heartbeat:IPaddr): Started cluster02
+ * Email (lsb:exim): Started cluster02
+ * Clone Set: mysql-clone-group [mysql-group]:
+ * Started: [ cluster01 cluster02 ]
+ * Clone Set: promotable-clone [promotable-rsc] (promotable):
+ * Promoted: [ cluster02 ]
+ * Unpromoted: [ cluster01 ]
+=#=#=#= End test: Simulate injecting a failure - OK (0) =#=#=#=
+* Passed: crm_simulate - Simulate injecting a failure
+=#=#=#= Begin test: Simulate bringing a node down =#=#=#=
+4 of 32 resource instances DISABLED and 0 BLOCKED from further action due to failure
+
+Current cluster status:
+ * Node List:
+ * Online: [ cluster01 cluster02 ]
+ * GuestOnline: [ httpd-bundle-0@cluster01 httpd-bundle-1@cluster02 ]
+
+ * Full List of Resources:
+ * Clone Set: ping-clone [ping]:
+ * Started: [ cluster01 cluster02 ]
+ * Fencing (stonith:fence_xvm): Started cluster01
+ * dummy (ocf:pacemaker:Dummy): Started cluster02
+ * Clone Set: inactive-clone [inactive-dhcpd] (disabled):
+ * Stopped (disabled): [ cluster01 cluster02 ]
+ * Resource Group: inactive-group (disabled):
+ * inactive-dummy-1 (ocf:pacemaker:Dummy): Stopped (disabled)
+ * inactive-dummy-2 (ocf:pacemaker:Dummy): Stopped (disabled)
+ * Container bundle set: httpd-bundle [pcmk:http]:
+ * httpd-bundle-0 (192.168.122.131) (ocf:heartbeat:apache): Started cluster01
+ * httpd-bundle-1 (192.168.122.132) (ocf:heartbeat:apache): Started cluster02
+ * httpd-bundle-2 (192.168.122.133) (ocf:heartbeat:apache): Stopped
+ * Resource Group: exim-group:
+ * Public-IP (ocf:heartbeat:IPaddr): Started cluster02
+ * Email (lsb:exim): Started cluster02
+ * Clone Set: mysql-clone-group [mysql-group]:
+ * Started: [ cluster01 cluster02 ]
+ * Clone Set: promotable-clone [promotable-rsc] (promotable):
+ * Promoted: [ cluster02 ]
+ * Unpromoted: [ cluster01 ]
+
+Performing Requested Modifications:
+ * Taking node cluster01 offline
+
+Transition Summary:
+ * Fence (off) httpd-bundle-0 (resource: httpd-bundle-docker-0) 'guest is unclean'
+ * Start Fencing ( cluster02 )
+ * Start httpd-bundle-0 ( cluster02 ) due to unrunnable httpd-bundle-docker-0 start (blocked)
+ * Stop httpd:0 ( httpd-bundle-0 ) due to unrunnable httpd-bundle-docker-0 start
+ * Start httpd-bundle-2 ( cluster02 ) due to unrunnable httpd-bundle-docker-2 start (blocked)
+ * Start httpd:2 ( httpd-bundle-2 ) due to unrunnable httpd-bundle-docker-2 start (blocked)
+
+Executing Cluster Transition:
+ * Resource action: Fencing start on cluster02
+ * Pseudo action: stonith-httpd-bundle-0-off on httpd-bundle-0
+ * Pseudo action: httpd-bundle_stop_0
+ * Pseudo action: httpd-bundle_start_0
+ * Resource action: Fencing monitor=60000 on cluster02
+ * Pseudo action: httpd-bundle-clone_stop_0
+ * Pseudo action: httpd_stop_0
+ * Pseudo action: httpd-bundle-clone_stopped_0
+ * Pseudo action: httpd-bundle-clone_start_0
+ * Pseudo action: httpd-bundle_stopped_0
+ * Pseudo action: httpd-bundle-clone_running_0
+ * Pseudo action: httpd-bundle_running_0
+
+Revised Cluster Status:
+ * Node List:
+ * Online: [ cluster02 ]
+ * OFFLINE: [ cluster01 ]
+ * GuestOnline: [ httpd-bundle-1@cluster02 ]
+
+ * Full List of Resources:
+ * Clone Set: ping-clone [ping]:
+ * Started: [ cluster02 ]
+ * Stopped: [ cluster01 ]
+ * Fencing (stonith:fence_xvm): Started cluster02
+ * dummy (ocf:pacemaker:Dummy): Started cluster02
+ * Clone Set: inactive-clone [inactive-dhcpd] (disabled):
+ * Stopped (disabled): [ cluster01 cluster02 ]
+ * Resource Group: inactive-group (disabled):
+ * inactive-dummy-1 (ocf:pacemaker:Dummy): Stopped (disabled)
+ * inactive-dummy-2 (ocf:pacemaker:Dummy): Stopped (disabled)
+ * Container bundle set: httpd-bundle [pcmk:http]:
+ * httpd-bundle-0 (192.168.122.131) (ocf:heartbeat:apache): FAILED
+ * httpd-bundle-1 (192.168.122.132) (ocf:heartbeat:apache): Started cluster02
+ * httpd-bundle-2 (192.168.122.133) (ocf:heartbeat:apache): Stopped
+ * Resource Group: exim-group:
+ * Public-IP (ocf:heartbeat:IPaddr): Started cluster02
+ * Email (lsb:exim): Started cluster02
+ * Clone Set: mysql-clone-group [mysql-group]:
+ * Started: [ cluster02 ]
+ * Stopped: [ cluster01 ]
+ * Clone Set: promotable-clone [promotable-rsc] (promotable):
+ * Promoted: [ cluster02 ]
+ * Stopped: [ cluster01 ]
+=#=#=#= End test: Simulate bringing a node down - OK (0) =#=#=#=
+* Passed: crm_simulate - Simulate bringing a node down
+=#=#=#= Begin test: Simulate a node failing =#=#=#=
+4 of 32 resource instances DISABLED and 0 BLOCKED from further action due to failure
+
+Current cluster status:
+ * Node List:
+ * Online: [ cluster01 cluster02 ]
+ * GuestOnline: [ httpd-bundle-0@cluster01 httpd-bundle-1@cluster02 ]
+
+ * Full List of Resources:
+ * Clone Set: ping-clone [ping]:
+ * Started: [ cluster01 cluster02 ]
+ * Fencing (stonith:fence_xvm): Started cluster01
+ * dummy (ocf:pacemaker:Dummy): Started cluster02
+ * Clone Set: inactive-clone [inactive-dhcpd] (disabled):
+ * Stopped (disabled): [ cluster01 cluster02 ]
+ * Resource Group: inactive-group (disabled):
+ * inactive-dummy-1 (ocf:pacemaker:Dummy): Stopped (disabled)
+ * inactive-dummy-2 (ocf:pacemaker:Dummy): Stopped (disabled)
+ * Container bundle set: httpd-bundle [pcmk:http]:
+ * httpd-bundle-0 (192.168.122.131) (ocf:heartbeat:apache): Started cluster01
+ * httpd-bundle-1 (192.168.122.132) (ocf:heartbeat:apache): Started cluster02
+ * httpd-bundle-2 (192.168.122.133) (ocf:heartbeat:apache): Stopped
+ * Resource Group: exim-group:
+ * Public-IP (ocf:heartbeat:IPaddr): Started cluster02
+ * Email (lsb:exim): Started cluster02
+ * Clone Set: mysql-clone-group [mysql-group]:
+ * Started: [ cluster01 cluster02 ]
+ * Clone Set: promotable-clone [promotable-rsc] (promotable):
+ * Promoted: [ cluster02 ]
+ * Unpromoted: [ cluster01 ]
+
+Performing Requested Modifications:
+ * Failing node cluster02
+
+Transition Summary:
+ * Fence (off) httpd-bundle-1 (resource: httpd-bundle-docker-1) 'guest is unclean'
+ * Fence (reboot) cluster02 'peer is no longer part of the cluster'
+ * Stop ping:0 ( cluster02 ) due to node availability
+ * Stop dummy ( cluster02 ) due to node availability
+ * Stop httpd-bundle-ip-192.168.122.132 ( cluster02 ) due to node availability
+ * Stop httpd-bundle-docker-1 ( cluster02 ) due to node availability
+ * Stop httpd-bundle-1 ( cluster02 ) due to unrunnable httpd-bundle-docker-1 start
+ * Stop httpd:1 ( httpd-bundle-1 ) due to unrunnable httpd-bundle-docker-1 start
+ * Start httpd-bundle-2 ( cluster01 ) due to unrunnable httpd-bundle-docker-2 start (blocked)
+ * Start httpd:2 ( httpd-bundle-2 ) due to unrunnable httpd-bundle-docker-2 start (blocked)
+ * Move Public-IP ( cluster02 -> cluster01 )
+ * Move Email ( cluster02 -> cluster01 )
+ * Stop mysql-proxy:0 ( cluster02 ) due to node availability
+ * Stop promotable-rsc:0 ( Promoted cluster02 ) due to node availability
+
+Executing Cluster Transition:
+ * Pseudo action: httpd-bundle-1_stop_0
+ * Pseudo action: promotable-clone_demote_0
+ * Pseudo action: httpd-bundle_stop_0
+ * Pseudo action: httpd-bundle_start_0
+ * Fencing cluster02 (reboot)
+ * Pseudo action: ping-clone_stop_0
+ * Pseudo action: dummy_stop_0
+ * Pseudo action: httpd-bundle-docker-1_stop_0
+ * Pseudo action: exim-group_stop_0
+ * Pseudo action: Email_stop_0
+ * Pseudo action: mysql-clone-group_stop_0
+ * Pseudo action: promotable-rsc_demote_0
+ * Pseudo action: promotable-clone_demoted_0
+ * Pseudo action: promotable-clone_stop_0
+ * Pseudo action: stonith-httpd-bundle-1-off on httpd-bundle-1
+ * Pseudo action: ping_stop_0
+ * Pseudo action: ping-clone_stopped_0
+ * Pseudo action: httpd-bundle-clone_stop_0
+ * Pseudo action: httpd-bundle-ip-192.168.122.132_stop_0
+ * Pseudo action: Public-IP_stop_0
+ * Pseudo action: mysql-group:0_stop_0
+ * Pseudo action: mysql-proxy_stop_0
+ * Pseudo action: promotable-rsc_stop_0
+ * Pseudo action: promotable-clone_stopped_0
+ * Pseudo action: httpd_stop_0
+ * Pseudo action: httpd-bundle-clone_stopped_0
+ * Pseudo action: httpd-bundle-clone_start_0
+ * Pseudo action: exim-group_stopped_0
+ * Pseudo action: exim-group_start_0
+ * Resource action: Public-IP start on cluster01
+ * Resource action: Email start on cluster01
+ * Pseudo action: mysql-group:0_stopped_0
+ * Pseudo action: mysql-clone-group_stopped_0
+ * Pseudo action: httpd-bundle_stopped_0
+ * Pseudo action: httpd-bundle-clone_running_0
+ * Pseudo action: exim-group_running_0
+ * Pseudo action: httpd-bundle_running_0
+
+Revised Cluster Status:
+ * Node List:
+ * Online: [ cluster01 ]
+ * OFFLINE: [ cluster02 ]
+ * GuestOnline: [ httpd-bundle-0@cluster01 ]
+
+ * Full List of Resources:
+ * Clone Set: ping-clone [ping]:
+ * Started: [ cluster01 ]
+ * Stopped: [ cluster02 ]
+ * Fencing (stonith:fence_xvm): Started cluster01
+ * dummy (ocf:pacemaker:Dummy): Stopped
+ * Clone Set: inactive-clone [inactive-dhcpd] (disabled):
+ * Stopped (disabled): [ cluster01 cluster02 ]
+ * Resource Group: inactive-group (disabled):
+ * inactive-dummy-1 (ocf:pacemaker:Dummy): Stopped (disabled)
+ * inactive-dummy-2 (ocf:pacemaker:Dummy): Stopped (disabled)
+ * Container bundle set: httpd-bundle [pcmk:http]:
+ * httpd-bundle-0 (192.168.122.131) (ocf:heartbeat:apache): Started cluster01
+ * httpd-bundle-1 (192.168.122.132) (ocf:heartbeat:apache): FAILED
+ * httpd-bundle-2 (192.168.122.133) (ocf:heartbeat:apache): Stopped
+ * Resource Group: exim-group:
+ * Public-IP (ocf:heartbeat:IPaddr): Started cluster01
+ * Email (lsb:exim): Started cluster01
+ * Clone Set: mysql-clone-group [mysql-group]:
+ * Started: [ cluster01 ]
+ * Stopped: [ cluster02 ]
+ * Clone Set: promotable-clone [promotable-rsc] (promotable):
+ * Unpromoted: [ cluster01 ]
+ * Stopped: [ cluster02 ]
+=#=#=#= End test: Simulate a node failing - OK (0) =#=#=#=
+* Passed: crm_simulate - Simulate a node failing
=#=#=#= Begin test: List a promotable clone resource =#=#=#=
resource promotable-clone is running on: cluster01
resource promotable-clone is running on: cluster02 Promoted
diff --git a/cts/cli/regression.validity.exp b/cts/cli/regression.validity.exp
index 345151d..5ace430 100644
--- a/cts/cli/regression.validity.exp
+++ b/cts/cli/regression.validity.exp
@@ -509,7 +509,6 @@ Current cluster status:
* Full List of Resources:
* dummy1 (ocf:pacemaker:Dummy): Stopped
* dummy2 (ocf:pacemaker:Dummy): Stopped
-unpack_simple_rsc_order error: Cannot invert constraint 'ord_1-2' (please specify inverse manually)
unpack_resources error: Resource start-up disabled since no STONITH resources have been defined
unpack_resources error: Either configure some or disable STONITH with the stonith-enabled option
unpack_resources error: NOTE: Clusters with shared data need STONITH to ensure data integrity
diff --git a/cts/cts-cli.in b/cts/cts-cli.in
index e23aa50..d32bfb7 100755
--- a/cts/cts-cli.in
+++ b/cts/cts-cli.in
@@ -603,6 +603,10 @@ function test_tools() {
cmd="crm_failcount --query -r foo -N node1"
test_assert $CRM_EX_OK
+ desc="Show node attributes with crm_simulate"
+ cmd="crm_simulate --live-check --show-attrs"
+ test_assert $CRM_EX_OK 0
+
desc="Delete a transient (fail-count) node attribute"
cmd="crm_attribute -n fail-count-foo -D -N node1 -t status"
test_assert $CRM_EX_OK
@@ -955,6 +959,29 @@ function test_tools() {
unset CIB_file
export CIB_file="$test_home/cli/crm_mon.xml"
+ export CIB_shadow_dir="${shadow_dir}"
+
+ desc="Show allocation scores with crm_simulate"
+ cmd="crm_simulate -x $CIB_file --show-scores --output-as=xml"
+ test_assert_validate $CRM_EX_OK 0
+
+ desc="Show utilization with crm_simulate"
+ cmd="crm_simulate -x $CIB_file --show-utilization"
+ test_assert $CRM_EX_OK 0
+
+ desc="Simulate injecting a failure"
+ cmd="crm_simulate -x $CIB_file -S -i ping_monitor_10000@cluster02=1"
+ test_assert $CRM_EX_OK 0
+
+ desc="Simulate bringing a node down"
+ cmd="crm_simulate -x $CIB_file -S --node-down=cluster01"
+ test_assert $CRM_EX_OK 0
+
+ desc="Simulate a node failing"
+ cmd="crm_simulate -x $CIB_file -S --node-fail=cluster02"
+ test_assert $CRM_EX_OK 0
+
+ unset CIB_shadow_dir
desc="List a promotable clone resource"
cmd="crm_resource --locate -r promotable-clone"
@@ -1851,6 +1878,7 @@ for t in $tests; do
-e 's/ end=\"[0-9][-+: 0-9]*Z*\"/ end=\"\"/' \
-e 's/ start=\"[0-9][-+: 0-9]*Z*\"/ start=\"\"/' \
-e 's/^Error checking rule: Device not configured/Error checking rule: No such device or address/' \
+ -e 's/\(Injecting attribute last-failure-ping#monitor_10000=\)[0-9]*/\1/' \
-e 's/^lt-//' \
-e 's/ocf::/ocf:/' \
-e 's/Masters:/Promoted:/' \
@@ -1890,22 +1918,34 @@ for t in $tests; do
grep -e '^\* \(Passed\|Failed\)' "$TMPFILE"
done
-if [ $num_errors -ne 0 ]; then
+function print_or_remove_file() {
+
+ eval TMPFILE="\$TMPFILE_$1"
+ if [[ ! $(diff -wq $test_home/cli/regression.$1.exp "$TMPFILE") ]]; then
+ rm -f "$TMPFILE"
+ else
+ echo " $TMPFILE"
+ fi
+}
+
+if [ $num_errors -ne 0 ] && [ $failed -ne 0 ]; then
echo "$num_errors tests failed; see output in:"
for t in $tests; do
- eval TMPFILE="\$TMPFILE_$t"
- echo " $TMPFILE"
+ print_or_remove_file "$t"
+ done
+ exit $CRM_EX_ERROR
+elif [ $num_errors -ne 0 ]; then
+ echo "$num_errors tests failed"
+ for t in $tests; do
+ print_or_remove_file "$t"
done
exit $CRM_EX_ERROR
-
elif [ $failed -eq 1 ]; then
echo "$num_passed tests passed but output was unexpected; see output in:"
for t in $tests; do
- eval TMPFILE="\$TMPFILE_$t"
- echo " $TMPFILE"
+ print_or_remove_file "$t"
done
exit $CRM_EX_DIGEST
-
else
echo $num_passed tests passed
for t in $tests; do
diff --git a/cts/cts-exec.in b/cts/cts-exec.in
index 9557017..96704ec 100644
--- a/cts/cts-exec.in
+++ b/cts/cts-exec.in
@@ -402,6 +402,12 @@ class Tests(object):
self.action_timeout = " -t 9000 "
if self.tls:
self.rsc_classes.remove("stonith")
+
+ try:
+ self.rsc_classes.remove("nagios")
+ except ValueError: # Not found
+ pass
+
if "systemd" in self.rsc_classes:
try:
# This code doesn't need this import, but pacemaker-cts-dummyd
@@ -684,7 +690,7 @@ class Tests(object):
'-l "NEW_EVENT event_type:register rsc_id:test_rsc action:none rc:ok op_status:complete"')
test.add_cmd('-c exec -r test_rsc -a start -k monitor_delay -v 30 ' +
'-t 1000 -w') # -t must be less than self.action_timeout
- test.add_cmd('-l "NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:start rc:OCF_TIMEOUT op_status:Timed Out" '
+ test.add_cmd('-l "NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:start rc:error op_status:Timed Out" '
+ self.action_timeout)
test.add_cmd("-c exec -r test_rsc -a stop " + self.action_timeout +
"-l \"NEW_EVENT event_type:exec_complete rsc_id:test_rsc action:stop rc:ok op_status:complete\" ")
@@ -1193,28 +1199,28 @@ class TestOptions(object):
self.options['run-only-pattern'] = args[i+1]
skip = 1
- def show_usage(self):
- """ Show command usage """
-
- print("usage: " + sys.argv[0] + " [options]")
- print("If no options are provided, all tests will run")
- print("Options:")
- print("\t [--help | -h] Show usage")
- print("\t [--list-tests | -l] Print out all registered tests.")
- print("\t [--run-only | -r 'testname'] Run a specific test")
- print("\t [--verbose | -V] Verbose output")
- print("\t [--timeout | -t 'floating point number']"
- "\n\t\tUp to how many seconds each test case waits for the daemon to be initialized."
- "\n\t\tDefaults to 2. The value 0 means no limit.")
- print("\t [--force-wait | -w]"
- "\n\t\tEach test case waits the default/specified --timeout for the daemon without tracking the log.")
- if REMOTE_ENABLED:
- print("\t [--pacemaker-remote | -R Test pacemaker-remoted binary instead of pacemaker-execd")
- print("\t [--run-only-pattern | -p 'string'] Run only tests containing the string value")
- print("\n\tExample: Run only the test 'start_stop'")
- print("\t\t " + sys.argv[0] + " --run-only start_stop")
- print("\n\tExample: Run only the tests with the string 'systemd' present in them")
- print("\t\t " + sys.argv[0] + " --run-only-pattern systemd")
+def show_usage():
+ """ Show command usage """
+
+ print("usage: " + sys.argv[0] + " [options]")
+ print("If no options are provided, all tests will run")
+ print("Options:")
+ print("\t [--help | -h] Show usage")
+ print("\t [--list-tests | -l] Print out all registered tests.")
+ print("\t [--run-only | -r 'testname'] Run a specific test")
+ print("\t [--verbose | -V] Verbose output")
+ print("\t [--timeout | -t 'floating point number']"
+ "\n\t\tUp to how many seconds each test case waits for the daemon to be initialized."
+ "\n\t\tDefaults to 2. The value 0 means no limit.")
+ print("\t [--force-wait | -w]"
+ "\n\t\tEach test case waits the default/specified --timeout for the daemon without tracking the log.")
+ if REMOTE_ENABLED:
+ print("\t [--pacemaker-remote | -R Test pacemaker-remoted binary instead of pacemaker-execd")
+ print("\t [--run-only-pattern | -p 'string'] Run only tests containing the string value")
+ print("\n\tExample: Run only the test 'start_stop'")
+ print("\t\t " + sys.argv[0] + " --run-only start_stop")
+ print("\n\tExample: Run only the tests with the string 'systemd' present in them")
+ print("\t\t " + sys.argv[0] + " --run-only-pattern systemd")
def main(argv):
@@ -1225,6 +1231,10 @@ def main(argv):
opts = TestOptions()
opts.build_options(argv)
+ if opts.options['show-usage']:
+ show_usage()
+ sys.exit(CrmExit.OK)
+
# Create a temporary directory for log files (the directory will
# automatically be erased when done)
with tempfile.TemporaryDirectory(prefix="cts-exec-") as logdir:
@@ -1238,15 +1248,15 @@ def main(argv):
tests.build_custom_tests()
tests.build_stress_tests()
+ if opts.options['list-tests']:
+ tests.print_list()
+ sys.exit(CrmExit.OK)
+
tests.setup_test_environment()
print("Starting ...")
- if opts.options['list-tests']:
- tests.print_list()
- elif opts.options['show-usage']:
- opts.show_usage()
- elif opts.options['run-only-pattern'] != "":
+ if opts.options['run-only-pattern'] != "":
tests.run_tests_matching(opts.options['run-only-pattern'])
tests.print_results()
elif opts.options['run-only'] != "":
diff --git a/cts/cts-fencing.in b/cts/cts-fencing.in
index 8dc5ec8..babfb63 100644
--- a/cts/cts-fencing.in
+++ b/cts/cts-fencing.in
@@ -19,6 +19,7 @@ import signal
# Prefer the source tree if available
BUILD_DIR = "@abs_top_builddir@"
SCHEMA_DIR = "@CRM_SCHEMA_DIRECTORY@"
+COROSYNC_CONF = "@PCMK__COROSYNC_CONF@"
TEST_DIR = sys.path[0]
AUTOGEN_COROSYNC_TEMPLATE = """
@@ -109,7 +110,10 @@ def update_path():
def find_validator(rng_file):
if os.access("/usr/bin/xmllint", os.X_OK):
- return ["xmllint", "--relaxng", rng_file, "-"]
+ if rng_file == None:
+ return ["xmllint", "-"]
+ else:
+ return ["xmllint", "--relaxng", rng_file, "-"]
else:
return None
@@ -123,7 +127,7 @@ def rng_directory():
return SCHEMA_DIR
-def pipe_communicate(pipes, stderr=False, stdin=None):
+def pipe_communicate(pipes, check_stderr=False, stdin=None):
""" Get text output from pipes """
if stdin is not None:
@@ -132,7 +136,7 @@ def pipe_communicate(pipes, stderr=False, stdin=None):
pipe_outputs = pipes.communicate()
output = pipe_outputs[0].decode(sys.stdout.encoding)
- if stderr:
+ if check_stderr:
output = output + pipe_outputs[1].decode(sys.stderr.encoding)
return output
@@ -240,7 +244,7 @@ class Test(object):
self.executed = 0
- def __new_cmd(self, cmd, args, exitcode, stdout_match="", no_wait=0, stdout_negative_match="", kill=None, validate=True):
+ def __new_cmd(self, cmd, args, exitcode, stdout_match="", no_wait=0, stdout_negative_match="", kill=None, validate=True, check_rng=True, check_stderr=True):
""" Add a command to be executed as part of this test """
self.cmds.append(
@@ -253,6 +257,8 @@ class Test(object):
"stdout_negative_match" : stdout_negative_match,
"no_wait" : no_wait,
"validate" : validate,
+ "check_rng" : check_rng,
+ "check_stderr" : check_stderr,
}
)
@@ -347,10 +353,10 @@ class Test(object):
self.negative_stonith_patterns.append(pattern)
- def add_cmd(self, cmd, args, validate=True):
+ def add_cmd(self, cmd, args, validate=True, check_rng=True, check_stderr=True):
""" Add a simple command to be executed as part of this test """
- self.__new_cmd(cmd, args, CrmExit.OK, "", validate=validate)
+ self.__new_cmd(cmd, args, CrmExit.OK, "", validate=validate, check_rng=check_rng, check_stderr=check_stderr)
def add_cmd_no_wait(self, cmd, args):
""" Add a simple command to be executed (without waiting) as part of this test """
@@ -397,7 +403,7 @@ class Test(object):
else:
return CrmExit.OK
- output = pipe_communicate(test, stderr=True)
+ output = pipe_communicate(test, check_stderr=args['check_stderr'])
if self.verbose:
print(output)
@@ -413,7 +419,10 @@ class Test(object):
raise OutputFoundError(output)
if args['validate']:
- rng_file = rng_directory() + "/api/api-result.rng"
+ if args['check_rng']:
+ rng_file = rng_directory() + "/api/api-result.rng"
+ else:
+ rng_file = None
cmd = find_validator(rng_file)
if not cmd:
@@ -423,7 +432,7 @@ class Test(object):
print("\nRunning: "+" ".join(cmd))
validator = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
- output = pipe_communicate(validator, stderr=True, stdin=output)
+ output = pipe_communicate(validator, check_stderr=True, stdin=output)
if self.verbose:
print(output)
@@ -508,6 +517,10 @@ class Test(object):
print("Step %d FAILED - '%s' was found in command output: %s" % (i, cmd['stdout_negative_match'], e))
self.set_error(i, cmd);
break
+ except XmlValidationError as e:
+ print("Step %d FAILED - xmllint failed: %s" % (i, e))
+ self.set_error(i, cmd);
+ break
if self.verbose:
print("Step %d SUCCESS" % (i))
i = i + 1
@@ -532,7 +545,7 @@ class Tests(object):
self.timeout = timeout
self.force_wait = force_wait
self.logdir = logdir
- self.autogen_corosync_cfg = not os.path.exists("/etc/corosync/corosync.conf")
+ self.autogen_corosync_cfg = not os.path.exists(COROSYNC_CONF)
def new_test(self, name, description, with_cpg=0):
""" Create a named test """
@@ -1133,7 +1146,7 @@ class Tests(object):
test.add_cmd("stonith_admin",
"--output-as=xml -R true1 -a fence_dummy_no_reboot -o \"mode=pass\" -o \"pcmk_host_list=node1 node2 node3\"")
test.add_cmd("stonith_admin", "--output-as=xml -B node1 -t 5 -V")
- test.add_stonith_log_pattern("does not advertise support for 'reboot', performing 'off'")
+ test.add_stonith_log_pattern("does not support reboot")
test.add_stonith_log_pattern("using true1 returned 0 (OK)")
# make sure reboot is used when reboot action is advertised
@@ -1423,13 +1436,32 @@ class Tests(object):
test.add_stonith_log_pattern("perform 'reboot' action targeting node_fake using true2")
test.add_stonith_neg_log_pattern("node_fake with true3")
+ def build_query_tests(self):
+ """ run stonith_admin --metadata for the fence_dummy agent and check command output """
+
+ test = self.new_test("get_metadata",
+ "Run stonith_admin --metadata for the fence_dummy agent", 1)
+ test.add_cmd_check_stdout("stonith_admin", "--output-as=xml -a fence_dummy --metadata", '<shortdesc lang')
+
+ def build_metadata_tests(self):
+ """ run fence-agents coming with pacemaker with -o metadata and check for valid xml """
+
+ test = self.new_test("check_metadata_dummy",
+ "Run fence_dummy -o metadata and check for valid xml", 0)
+ test.add_cmd("fence_dummy", "-o metadata", check_rng=False, check_stderr=False)
+ # fence_dummy prints on stderr to check that tools just listen on stdout
+
+ test = self.new_test("check_metadata_watchdog",
+ "Run fence_watchdog -o metadata and check for valid xml", 0)
+ test.add_cmd("fence_watchdog", "-o metadata", check_rng=False)
+
def setup_environment(self, use_corosync):
""" Prepare the host before executing any tests """
if use_corosync:
if self.autogen_corosync_cfg:
logname = os.path.join(self.logdir, "corosync.log")
- corosync_cfg = io.open("/etc/corosync/corosync.conf", "w")
+ corosync_cfg = io.open(COROSYNC_CONF, "w")
corosync_cfg.write(AUTOGEN_COROSYNC_TEMPLATE % (localname(), logname))
corosync_cfg.close()
@@ -1452,7 +1484,7 @@ class Tests(object):
for line in logfile.readlines():
print(line.strip())
logfile.close()
- os.remove("/etc/corosync/corosync.conf")
+ os.remove(COROSYNC_CONF)
subprocess.call(["cts-support", "uninstall"])
@@ -1551,6 +1583,8 @@ def main(argv):
tests.build_unfence_on_target_tests()
tests.build_nodeid_tests()
tests.build_remap_tests()
+ tests.build_query_tests()
+ tests.build_metadata_tests()
if opts.options['list-tests']:
tests.print_list()
diff --git a/cts/cts-regression.in b/cts/cts-regression.in
index 817e867..f2ed2c2 100755
--- a/cts/cts-regression.in
+++ b/cts/cts-regression.in
@@ -75,11 +75,6 @@ function run_as_root() {
if [ $EUID -eq 0 ]; then
$CMD
- elif [ -z $TRAVIS ]; then
- # sudo doesn't work in buildbot, su doesn't work in travis
- echo "Enter the root password..."
- su root -c "$CMD"
-
else
echo "Enter the root password if prompted..."
sudo -- $CMD
diff --git a/cts/cts-scheduler.in b/cts/cts-scheduler.in
index 23bcc17..17fd6ce 100644
--- a/cts/cts-scheduler.in
+++ b/cts/cts-scheduler.in
@@ -15,6 +15,7 @@ import shutil
import argparse
import subprocess
import platform
+import tempfile
DESC = """Regression tests for Pacemaker's scheduler"""
@@ -1250,13 +1251,17 @@ class CtsScheduler(object):
self.summary_dir = os.path.join(self.args.io_dir, "summary")
self.stderr_expected_dir = os.path.join(self.args.io_dir, "stderr")
+ # Create a temporary directory to store diff file
+ self.failed_dir = tempfile.mkdtemp(prefix='cts-scheduler_')
+
# Where to store generated files
if self.args.out_dir is None:
self.args.out_dir = self.args.io_dir
- self.failed_filename = os.path.join(self.test_home, ".regression.failed.diff")
+ self.failed_filename = os.path.join(self.failed_dir, "test-output.diff")
else:
- self.failed_filename = os.path.join(self.args.out_dir, ".regression.failed.diff")
+ self.failed_filename = os.path.join(self.args.out_dir, "test-output.diff")
os.environ['CIB_shadow_dir'] = self.args.out_dir
+
self.failed_file = None
self.outfile_out_dir = os.path.join(self.args.out_dir, "out")
@@ -1314,7 +1319,7 @@ class CtsScheduler(object):
if diff(filename1, filename2, stdout=subprocess.DEVNULL) != 0:
diff(filename1, filename2, stdout=self.failed_file, stderr=subprocess.DEVNULL)
- self.failed_file.write("\n");
+ self.failed_file.write("\n")
return True
return False
@@ -1542,6 +1547,7 @@ class CtsScheduler(object):
def _test_results(self):
if self.num_failed == 0:
+ shutil.rmtree(self.failed_dir)
return CrmExit.OK
if os.path.isfile(self.failed_filename) and os.stat(self.failed_filename).st_size != 0:
@@ -1557,7 +1563,7 @@ class CtsScheduler(object):
self._error("%d (of %d) tests failed (no diff results)" %
(self.num_failed, self.num_tests))
if os.path.isfile(self.failed_filename):
- os.remove(self.failed_filename)
+ shutil.rmtree(self.failed_dir)
return CrmExit.ERROR
def run(self):
@@ -1578,7 +1584,10 @@ class CtsScheduler(object):
else:
rc = self.run_one(self.args.run, "Single shot", self.single_test_args)
self.failed_file.close()
- cat(self.failed_filename)
+ if self.num_failed > 0:
+ print("\nFailures:\nThese have also been written to: " + self.failed_filename + "\n")
+ cat(self.failed_filename)
+ shutil.rmtree(self.failed_dir)
return rc
diff --git a/cts/lab/CIB.py b/cts/lab/CIB.py
index c59976d..3011993 100644
--- a/cts/lab/CIB.py
+++ b/cts/lab/CIB.py
@@ -1,7 +1,7 @@
""" CIB generator for Pacemaker's Cluster Test Suite (CTS)
"""
-__copyright__ = "Copyright 2008-2020 the Pacemaker project contributors"
+__copyright__ = "Copyright 2008-2021 the Pacemaker project contributors"
__license__ = "GNU General Public License version 2 or later (GPLv2+) WITHOUT ANY WARRANTY"
import os
@@ -127,8 +127,8 @@ class CIB12(ConfigBase):
(rc, output) = self.Factory.rsh(self.Factory.target,
r"""awk -v RS="}" """
r"""'/^(\s*nodelist\s*{)?\s*node\s*{.*(ring0_addr|name):\s*%s(\s+|$)/"""
- r"""{gsub(/.*nodeid:\s*/,"");gsub(/\s+.*$/,"");print}'"""
- r""" /etc/corosync/corosync.conf""" % node_name, None)
+ r"""{gsub(/.*nodeid:\s*/,"");gsub(/\s+.*$/,"");print}'%s"""
+ % (node_name, CTSvars.COROSYNC_CONF), None)
if rc == 0 and len(output) == 1:
try:
diff --git a/cts/lab/CTStests.py b/cts/lab/CTStests.py
index c5c7393..649489c 100644
--- a/cts/lab/CTStests.py
+++ b/cts/lab/CTStests.py
@@ -2350,7 +2350,7 @@ class SimulStopLite(CTSTest):
if did_fail:
return self.failure("Active nodes exist: " + repr(up_nodes))
- self.logger.log("Warn: All nodes stopped but CTS didnt detect: "
+ self.logger.log("Warn: All nodes stopped but CTS didn't detect: "
+ repr(watch.unmatched))
return self.failure("Missing log message: "+repr(watch.unmatched))
diff --git a/cts/lab/CTSvars.py.in b/cts/lab/CTSvars.py.in
index a590a2b..4702542 100644
--- a/cts/lab/CTSvars.py.in
+++ b/cts/lab/CTSvars.py.in
@@ -13,3 +13,4 @@ class CTSvars(object):
CRM_DAEMON_USER="@CRM_DAEMON_USER@"
CRM_DAEMON_DIR="@CRM_DAEMON_DIR@"
OCF_ROOT_DIR="@OCF_ROOT_DIR@"
+ COROSYNC_CONF="@PCMK__COROSYNC_CONF@"
diff --git a/cts/lab/patterns.py b/cts/lab/patterns.py
index 400fd3d..4655e44 100644
--- a/cts/lab/patterns.py
+++ b/cts/lab/patterns.py
@@ -219,6 +219,7 @@ class crm_corosync(BasePatterns):
]
self.components["corosync-ignore"] = [
+ r"Could not connect to Corosync CFG: CS_ERR_LIBRARY",
r"error:.*Connection to the CPG API failed: Library error",
r"\[[0-9]+\] exited with status [0-9]+ \(",
r"\[[0-9]+\] terminated with signal 15",
diff --git a/cts/scheduler/summary/remote-connection-unrecoverable.summary b/cts/scheduler/summary/remote-connection-unrecoverable.summary
index 3cfb645..727dad2 100644
--- a/cts/scheduler/summary/remote-connection-unrecoverable.summary
+++ b/cts/scheduler/summary/remote-connection-unrecoverable.summary
@@ -14,7 +14,7 @@ Current cluster status:
* Stopped: [ remote1 ]
Transition Summary:
- * Fence (reboot) remote1 'resources are active and the connection is unrecoverable'
+ * Fence (reboot) remote1 'resources are active but connection is unrecoverable'
* Fence (reboot) node1 'peer is no longer part of the cluster'
* Stop remote1 ( node1 ) due to node availability
* Restart killer ( node2 ) due to resource definition change
diff --git a/cts/scheduler/summary/remote-recover-all.summary b/cts/scheduler/summary/remote-recover-all.summary
index 18d1073..5a7d3ce 100644
--- a/cts/scheduler/summary/remote-recover-all.summary
+++ b/cts/scheduler/summary/remote-recover-all.summary
@@ -39,8 +39,8 @@ Current cluster status:
* stonith-fence_ipmilan-5254005bdbb5 (stonith:fence_ipmilan): Started controller-1 (UNCLEAN)
Transition Summary:
- * Fence (reboot) messaging-1 'resources are active and the connection is unrecoverable'
- * Fence (reboot) galera-2 'resources are active and the connection is unrecoverable'
+ * Fence (reboot) messaging-1 'resources are active but connection is unrecoverable'
+ * Fence (reboot) galera-2 'resources are active but connection is unrecoverable'
* Fence (reboot) controller-1 'peer is no longer part of the cluster'
* Stop messaging-1 ( controller-1 ) due to node availability
* Move galera-0 ( controller-1 -> controller-2 )
diff --git a/cts/scheduler/summary/remote-recover-no-resources.summary b/cts/scheduler/summary/remote-recover-no-resources.summary
index d7d9ef9..eb94266 100644
--- a/cts/scheduler/summary/remote-recover-no-resources.summary
+++ b/cts/scheduler/summary/remote-recover-no-resources.summary
@@ -39,7 +39,7 @@ Current cluster status:
* stonith-fence_ipmilan-5254005bdbb5 (stonith:fence_ipmilan): Started controller-1 (UNCLEAN)
Transition Summary:
- * Fence (reboot) messaging-1 'resources are active and the connection is unrecoverable'
+ * Fence (reboot) messaging-1 'resources are active but connection is unrecoverable'
* Fence (reboot) controller-1 'peer is no longer part of the cluster'
* Stop messaging-1 ( controller-1 ) due to node availability
* Move galera-0 ( controller-1 -> controller-2 )
diff --git a/cts/scheduler/summary/remote-recover-unknown.summary b/cts/scheduler/summary/remote-recover-unknown.summary
index 4f3d045..e04e969 100644
--- a/cts/scheduler/summary/remote-recover-unknown.summary
+++ b/cts/scheduler/summary/remote-recover-unknown.summary
@@ -39,8 +39,8 @@ Current cluster status:
* stonith-fence_ipmilan-5254005bdbb5 (stonith:fence_ipmilan): Started controller-1 (UNCLEAN)
Transition Summary:
- * Fence (reboot) galera-2 'resources are in an unknown state and the connection is unrecoverable'
- * Fence (reboot) messaging-1 'resources are active and the connection is unrecoverable'
+ * Fence (reboot) galera-2 'resources are in unknown state and connection is unrecoverable'
+ * Fence (reboot) messaging-1 'resources are active but connection is unrecoverable'
* Fence (reboot) controller-1 'peer is no longer part of the cluster'
* Stop messaging-1 ( controller-1 ) due to node availability
* Move galera-0 ( controller-1 -> controller-2 )
diff --git a/cts/scheduler/summary/stonith-4.summary b/cts/scheduler/summary/stonith-4.summary
index ef1449c..6aa0f4d 100644
--- a/cts/scheduler/summary/stonith-4.summary
+++ b/cts/scheduler/summary/stonith-4.summary
@@ -17,7 +17,7 @@ Current cluster status:
Transition Summary:
* Fence (reboot) pcmk-10 'peer process is no longer available'
* Fence (reboot) pcmk-8 'peer has not been seen by the cluster'
- * Fence (reboot) pcmk-7 'peer failed the pacemaker membership criteria'
+ * Fence (reboot) pcmk-7 'peer failed Pacemaker membership criteria'
* Fence (reboot) pcmk-5 'peer has not been seen by the cluster'
* Start Fencing ( pcmk-1 ) blocked
diff --git a/daemons/attrd/attrd_elections.c b/daemons/attrd/attrd_elections.c
index ae4392f..3bf18e0 100644
--- a/daemons/attrd/attrd_elections.c
+++ b/daemons/attrd/attrd_elections.c
@@ -151,6 +151,14 @@ attrd_remove_voter(const crm_node_t *peer)
* attributes.
*/
attrd_start_election_if_needed();
+
+ /* If an election is in progress, we need to call election_check(), in case
+ * this lost peer is the only one that hasn't voted, otherwise the election
+ * would be pending until it's timed out.
+ */
+ } else if (election_state(writer) == election_in_progress) {
+ crm_debug("Checking election status upon loss of voter %s", peer->uname);
+ election_check(writer);
}
}
diff --git a/daemons/based/based_io.c b/daemons/based/based_io.c
index 7815b38..6d069ac 100644
--- a/daemons/based/based_io.c
+++ b/daemons/based/based_io.c
@@ -370,18 +370,23 @@ activateCibXml(xmlNode * new_cib, gboolean to_disk, const char *op)
static void
cib_diskwrite_complete(mainloop_child_t * p, pid_t pid, int core, int signo, int exitcode)
{
- if (signo) {
- crm_notice("Disk write process terminated with signal %d (pid=%d, core=%d)", signo, pid,
- core);
+ const char *errmsg = "Could not write CIB to disk";
- } else {
- do_crm_log(exitcode == 0 ? LOG_TRACE : LOG_ERR, "Disk write process exited (pid=%d, rc=%d)",
- pid, exitcode);
+ if ((exitcode != 0) && cib_writes_enabled) {
+ cib_writes_enabled = FALSE;
+ errmsg = "Disabling CIB disk writes after failure";
}
- if (exitcode != 0 && cib_writes_enabled) {
- crm_err("Disabling disk writes after write failure");
- cib_writes_enabled = FALSE;
+ if ((signo == 0) && (exitcode == 0)) {
+ crm_trace("Disk write [%d] succeeded", (int) pid);
+
+ } else if (signo == 0) {
+ crm_err("%s: process %d exited %d", errmsg, (int) pid, exitcode);
+
+ } else {
+ crm_err("%s: process %d terminated with signal %d (%s)%s",
+ errmsg, (int) pid, signo, strsignal(signo),
+ (core? " and dumped core" : ""));
}
mainloop_trigger_complete(cib_writer);
diff --git a/daemons/controld/Makefile.am b/daemons/controld/Makefile.am
index 7fef7f3..db45bcb 100644
--- a/daemons/controld/Makefile.am
+++ b/daemons/controld/Makefile.am
@@ -1,5 +1,5 @@
#
-# Copyright 2018-2019 the Pacemaker project contributors
+# Copyright 2018-2021 the Pacemaker project contributors
#
# The version control history for this file may have further details.
#
@@ -8,6 +8,7 @@
#
include $(top_srcdir)/mk/common.mk
+include $(top_srcdir)/mk/man.mk
halibdir = $(CRM_DAEMON_DIR)
diff --git a/daemons/controld/controld_based.c b/daemons/controld/controld_based.c
index 163d65d..8cf884e 100644
--- a/daemons/controld/controld_based.c
+++ b/daemons/controld/controld_based.c
@@ -46,6 +46,27 @@ do_cib_replaced(const char *event, xmlNode * msg)
register_fsa_input(C_FSA_INTERNAL, I_ELECTION, NULL);
}
+void
+controld_disconnect_cib_manager(void)
+{
+ CRM_ASSERT(fsa_cib_conn != NULL);
+
+ crm_info("Disconnecting from the CIB manager");
+
+ controld_clear_fsa_input_flags(R_CIB_CONNECTED);
+
+ fsa_cib_conn->cmds->del_notify_callback(fsa_cib_conn, T_CIB_REPLACE_NOTIFY, do_cib_replaced);
+ fsa_cib_conn->cmds->del_notify_callback(fsa_cib_conn, T_CIB_DIFF_NOTIFY, do_cib_updated);
+ cib_free_callbacks(fsa_cib_conn);
+ if (fsa_cib_conn->state != cib_disconnected) {
+ /* Does not require a set_slave() reply to sign out from based. */
+ fsa_cib_conn->cmds->set_slave(fsa_cib_conn, cib_scope_local | cib_discard_reply);
+ fsa_cib_conn->cmds->signoff(fsa_cib_conn);
+ }
+
+ crm_notice("Disconnected from the CIB manager");
+}
+
/* A_CIB_STOP, A_CIB_START, O_CIB_RESTART */
void
do_cib_control(long long action,
@@ -63,18 +84,8 @@ do_cib_control(long long action,
return;
}
- crm_info("Disconnecting from the CIB manager");
- controld_clear_fsa_input_flags(R_CIB_CONNECTED);
-
- fsa_cib_conn->cmds->del_notify_callback(fsa_cib_conn, T_CIB_REPLACE_NOTIFY, do_cib_replaced);
- fsa_cib_conn->cmds->del_notify_callback(fsa_cib_conn, T_CIB_DIFF_NOTIFY, do_cib_updated);
+ controld_disconnect_cib_manager();
- if (fsa_cib_conn->state != cib_disconnected) {
- /* Does not require a set_slave() reply to sign out from based. */
- fsa_cib_conn->cmds->set_slave(fsa_cib_conn, cib_scope_local | cib_discard_reply);
- fsa_cib_conn->cmds->signoff(fsa_cib_conn);
- }
- crm_notice("Disconnected from the CIB manager");
}
if (action & A_CIB_START) {
diff --git a/daemons/controld/controld_callbacks.c b/daemons/controld/controld_callbacks.c
index e9dbbdc..b6eca75 100644
--- a/daemons/controld/controld_callbacks.c
+++ b/daemons/controld/controld_callbacks.c
@@ -1,5 +1,5 @@
/*
- * Copyright 2004-2020 the Pacemaker project contributors
+ * Copyright 2004-2021 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
@@ -260,7 +260,7 @@ peer_update_callback(enum crm_status_type type, crm_node_t * node, const void *d
/* tengine_stonith_callback() confirms fence actions */
crm_trace("Updating CIB %s fencer reported fencing of %s complete",
- (down->confirmed? "after" : "before"), node->uname);
+ (pcmk_is_set(down->flags, pcmk__graph_action_confirmed)? "after" : "before"), node->uname);
} else if (!appeared && pcmk__str_eq(task, CRM_OP_SHUTDOWN, pcmk__str_casei)) {
@@ -276,7 +276,7 @@ peer_update_callback(enum crm_status_type type, crm_node_t * node, const void *d
} else {
crm_notice("%s of peer %s is complete " CRM_XS " action=%d",
task, node->uname, down->id);
- update_graph(transition_graph, down);
+ pcmk__update_graph(transition_graph, down);
trigger_graph();
}
@@ -312,7 +312,7 @@ peer_update_callback(enum crm_status_type type, crm_node_t * node, const void *d
/* Trigger resource placement on newly integrated nodes */
if (appeared) {
abort_transition(INFINITY, tg_restart,
- "pacemaker_remote node integrated", NULL);
+ "Pacemaker Remote node integrated", NULL);
}
}
diff --git a/daemons/controld/controld_control.c b/daemons/controld/controld_control.c
index 45a70bb..3c59192 100644
--- a/daemons/controld/controld_control.c
+++ b/daemons/controld/controld_control.c
@@ -225,10 +225,8 @@ crmd_exit(crm_exit_t exit_code)
/* Tear down the CIB manager connection, but don't free it yet -- it could
* be used when we drain the mainloop later.
*/
- fsa_cib_conn->cmds->del_notify_callback(fsa_cib_conn, T_CIB_REPLACE_NOTIFY, do_cib_replaced);
- fsa_cib_conn->cmds->del_notify_callback(fsa_cib_conn, T_CIB_DIFF_NOTIFY, do_cib_updated);
- cib_free_callbacks(fsa_cib_conn);
- fsa_cib_conn->cmds->signoff(fsa_cib_conn);
+
+ controld_disconnect_cib_manager();
verify_stopped(fsa_state, LOG_WARNING);
controld_clear_fsa_input_flags(R_LRM_CONNECTED);
@@ -571,7 +569,7 @@ static pcmk__cluster_option_t crmd_opts[] = {
"A cluster node may receive notification of its own fencing if fencing "
"is misconfigured, or if fabric fencing is in use that doesn't cut "
"cluster communication. Allowed values are \"stop\" to attempt to "
- "immediately stop pacemaker and stay stopped, or \"panic\" to attempt "
+ "immediately stop Pacemaker and stay stopped, or \"panic\" to attempt "
"to immediately reboot the local node, falling back to stop on failure."
},
{
@@ -615,7 +613,7 @@ static pcmk__cluster_option_t crmd_opts[] = {
},
{
"stonith-watchdog-timeout", NULL, "time", NULL,
- "0", pcmk__valid_sbd_timeout,
+ "0", controld_verify_stonith_watchdog_timeout,
"How long to wait before we can assume nodes are safely down "
"when watchdog-based self-fencing via SBD is in use",
"If nonzero, along with `have-watchdog=true` automatically set by the "
@@ -633,7 +631,7 @@ static pcmk__cluster_option_t crmd_opts[] = {
"If `stonith-watchdog-timeout` is set to a negative value, and "
"`SBD_WATCHDOG_TIMEOUT` is set, twice that value will be used. "
"+WARNING:+ In this case, it's essential (currently not verified by "
- "pacemaker) that `SBD_WATCHDOG_TIMEOUT` is set to the same value on "
+ "Pacemaker) that `SBD_WATCHDOG_TIMEOUT` is set to the same value on "
"all nodes."
},
{
@@ -645,7 +643,7 @@ static pcmk__cluster_option_t crmd_opts[] = {
// Already documented in libpe_status (other values must be kept identical)
{
- "no-quorum-policy", NULL, "enum", "stop, freeze, ignore, demote, suicide",
+ "no-quorum-policy", NULL, "select", "stop, freeze, ignore, demote, suicide",
"stop", pcmk__valid_quorum, NULL, NULL
},
{
@@ -657,7 +655,7 @@ static pcmk__cluster_option_t crmd_opts[] = {
void
crmd_metadata(void)
{
- pcmk__print_option_metadata("pacemaker-controld", "1.0",
+ pcmk__print_option_metadata("pacemaker-controld",
"Pacemaker controller options",
"Cluster options used by Pacemaker's "
"controller (formerly called crmd)",
diff --git a/daemons/controld/controld_execd.c b/daemons/controld/controld_execd.c
index 90261a3..bded6e6 100644
--- a/daemons/controld/controld_execd.c
+++ b/daemons/controld/controld_execd.c
@@ -20,6 +20,7 @@
#include <crm/msg_xml.h>
#include <crm/common/xml.h>
#include <crm/pengine/rules.h>
+#include <crm/lrmd_internal.h>
#include <pacemaker-internal.h>
#include <pacemaker-controld.h>
@@ -198,7 +199,7 @@ update_history_cache(lrm_state_t * lrm_state, lrmd_rsc_info_t * rsc, lrmd_event_
entry->last_callid = op->call_id;
target_rc = rsc_op_expected_rc(op);
- if (op->op_status == PCMK_LRM_OP_CANCELLED) {
+ if (op->op_status == PCMK_EXEC_CANCELLED) {
if (op->interval_ms > 0) {
crm_trace("Removing cancelled recurring op: " PCMK__OP_FMT,
op->rsc_id, op->op_type, op->interval_ms);
@@ -273,8 +274,7 @@ send_task_ok_ack(lrm_state_t *lrm_state, ha_msg_input_t *input,
{
lrmd_event_data_t *op = construct_op(lrm_state, input->xml, rsc_id, task);
- op->rc = PCMK_OCF_OK;
- op->op_status = PCMK_LRM_OP_DONE;
+ lrmd__set_result(op, PCMK_OCF_OK, PCMK_EXEC_DONE, NULL);
controld_ack_event_directly(ack_host, ack_sys, rsc, op, rsc_id);
lrmd_free_event(op);
}
@@ -313,6 +313,41 @@ lrm_op_callback(lrmd_event_data_t * op)
}
}
+static void
+try_local_executor_connect(long long action, fsa_data_t *msg_data,
+ lrm_state_t *lrm_state)
+{
+ int rc = pcmk_rc_ok;
+
+ crm_debug("Connecting to the local executor");
+
+ // If we can connect, great
+ rc = controld_connect_local_executor(lrm_state);
+ if (rc == pcmk_rc_ok) {
+ controld_set_fsa_input_flags(R_LRM_CONNECTED);
+ crm_info("Connection to the local executor established");
+ return;
+ }
+
+ // Otherwise, if we can try again, set a timer to do so
+ if (lrm_state->num_lrm_register_fails < MAX_LRM_REG_FAILS) {
+ crm_warn("Failed to connect to the local executor %d time%s "
+ "(%d max): %s", lrm_state->num_lrm_register_fails,
+ pcmk__plural_s(lrm_state->num_lrm_register_fails),
+ MAX_LRM_REG_FAILS, pcmk_rc_str(rc));
+ controld_start_timer(wait_timer);
+ crmd_fsa_stall(FALSE);
+ return;
+ }
+
+ // Otherwise give up
+ crm_err("Failed to connect to the executor the max allowed "
+ "%d time%s: %s", lrm_state->num_lrm_register_fails,
+ pcmk__plural_s(lrm_state->num_lrm_register_fails),
+ pcmk_rc_str(rc));
+ register_fsa_error(C_FSA_INTERNAL, I_ERROR, NULL);
+}
+
/* A_LRM_CONNECT */
void
do_lrm_control(long long action,
@@ -352,34 +387,7 @@ do_lrm_control(long long action,
}
if (action & A_LRM_CONNECT) {
- int ret = pcmk_ok;
-
- crm_debug("Connecting to the executor");
- ret = lrm_state_ipc_connect(lrm_state);
-
- if (ret != pcmk_ok) {
- if (lrm_state->num_lrm_register_fails < MAX_LRM_REG_FAILS) {
- crm_warn("Failed to connect to the executor %d time%s (%d max)",
- lrm_state->num_lrm_register_fails,
- pcmk__plural_s(lrm_state->num_lrm_register_fails),
- MAX_LRM_REG_FAILS);
-
- controld_start_timer(wait_timer);
- crmd_fsa_stall(FALSE);
- return;
- }
- }
-
- if (ret != pcmk_ok) {
- crm_err("Failed to connect to the executor the max allowed %d time%s",
- lrm_state->num_lrm_register_fails,
- pcmk__plural_s(lrm_state->num_lrm_register_fails));
- register_fsa_error(C_FSA_INTERNAL, I_ERROR, NULL);
- return;
- }
-
- controld_set_fsa_input_flags(R_LRM_CONNECTED);
- crm_info("Connection to the executor established");
+ try_local_executor_connect(action, msg_data, lrm_state);
}
if (action & ~(A_LRM_CONNECT | A_LRM_DISCONNECT)) {
@@ -716,7 +724,7 @@ build_operation_update(xmlNode * parent, lrmd_rsc_info_t * rsc, lrmd_event_data_
* changed (using something like inotify, or a hash or modification time of
* the agent executable).
*/
- if ((op->op_status != PCMK_LRM_OP_DONE) || (op->rc != target_rc)
+ if ((op->op_status != PCMK_EXEC_DONE) || (op->rc != target_rc)
|| !pcmk__str_eq(op->op_type, CRMD_ACTION_START, pcmk__str_none)) {
metadata_source |= controld_metadata_from_cache;
}
@@ -858,18 +866,20 @@ controld_query_executor_state(const char *node_name)
void
controld_rc2event(lrmd_event_data_t *event, int rc)
{
+ /* This is called for cleanup requests from controller peers/clients, not
+ * for resource actions, so no exit reason is needed.
+ */
switch (rc) {
case pcmk_rc_ok:
- event->rc = PCMK_OCF_OK;
- event->op_status = PCMK_LRM_OP_DONE;
+ lrmd__set_result(event, PCMK_OCF_OK, PCMK_EXEC_DONE, NULL);
break;
case EACCES:
- event->rc = PCMK_OCF_INSUFFICIENT_PRIV;
- event->op_status = PCMK_LRM_OP_ERROR;
+ lrmd__set_result(event, PCMK_OCF_INSUFFICIENT_PRIV,
+ PCMK_EXEC_ERROR, NULL);
break;
default:
- event->rc = PCMK_OCF_UNKNOWN_ERROR;
- event->op_status = PCMK_LRM_OP_ERROR;
+ lrmd__set_result(event, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_ERROR,
+ NULL);
break;
}
}
@@ -1371,13 +1381,12 @@ get_fake_call_id(lrm_state_t *lrm_state, const char *rsc_id)
static void
fake_op_status(lrm_state_t *lrm_state, lrmd_event_data_t *op, int op_status,
- enum ocf_exitcode op_exitcode)
+ enum ocf_exitcode op_exitcode, const char *exit_reason)
{
op->call_id = get_fake_call_id(lrm_state, op->rsc_id);
op->t_run = time(NULL);
op->t_rcchange = op->t_run;
- op->op_status = op_status;
- op->rc = op_exitcode;
+ lrmd__set_result(op, op_exitcode, op_status, exit_reason);
}
static void
@@ -1432,10 +1441,12 @@ force_reprobe(lrm_state_t *lrm_state, const char *from_sys,
* \param[in] action Action XML from request
* \param[in] rc Desired return code to use
* \param[in] op_status Desired operation status to use
+ * \param[in] exit_reason Human-friendly detail, if error
*/
static void
synthesize_lrmd_failure(lrm_state_t *lrm_state, xmlNode *action,
- int op_status, enum ocf_exitcode rc)
+ int op_status, enum ocf_exitcode rc,
+ const char *exit_reason)
{
lrmd_event_data_t *op = NULL;
const char *operation = crm_element_value(action, XML_LRM_ATTR_TASK);
@@ -1459,9 +1470,9 @@ synthesize_lrmd_failure(lrm_state_t *lrm_state, xmlNode *action,
op = construct_op(lrm_state, action, ID(xml_rsc), operation);
if (pcmk__str_eq(operation, RSC_NOTIFY, pcmk__str_casei)) { // Notifications can't fail
- fake_op_status(lrm_state, op, PCMK_LRM_OP_DONE, PCMK_OCF_OK);
+ fake_op_status(lrm_state, op, PCMK_EXEC_DONE, PCMK_OCF_OK, NULL);
} else {
- fake_op_status(lrm_state, op, op_status, rc);
+ fake_op_status(lrm_state, op, op_status, rc, exit_reason);
}
crm_info("Faking " PCMK__OP_FMT " result (%d) on %s",
@@ -1513,7 +1524,6 @@ fail_lrm_resource(xmlNode *xml, lrm_state_t *lrm_state, const char *user_name,
* processed as if it came from the executor.
*/
op = construct_op(lrm_state, xml, ID(xml_rsc), "asyncmon");
- fake_op_status(lrm_state, op, PCMK_LRM_OP_DONE, PCMK_OCF_UNKNOWN_ERROR);
free((char*) op->user_data);
op->user_data = NULL;
@@ -1521,22 +1531,28 @@ fail_lrm_resource(xmlNode *xml, lrm_state_t *lrm_state, const char *user_name,
if (user_name && !pcmk__is_privileged(user_name)) {
crm_err("%s does not have permission to fail %s", user_name, ID(xml_rsc));
+ fake_op_status(lrm_state, op, PCMK_EXEC_ERROR,
+ PCMK_OCF_INSUFFICIENT_PRIV,
+ "Unprivileged user cannot fail resources");
controld_ack_event_directly(from_host, from_sys, NULL, op, ID(xml_rsc));
lrmd_free_event(op);
return;
}
+
if (get_lrm_resource(lrm_state, xml_rsc, TRUE, &rsc) == pcmk_ok) {
crm_info("Failing resource %s...", rsc->id);
- op->exit_reason = strdup("Simulated failure");
+ fake_op_status(lrm_state, op, PCMK_EXEC_DONE, PCMK_OCF_UNKNOWN_ERROR,
+ "Simulated failure");
process_lrm_event(lrm_state, op, NULL, xml);
- op->op_status = PCMK_LRM_OP_DONE;
- op->rc = PCMK_OCF_OK;
+ op->rc = PCMK_OCF_OK; // The request to fail the resource succeeded
lrmd_free_rsc_info(rsc);
} else {
crm_info("Cannot find/create resource in order to fail it...");
crm_log_xml_warn(xml, "bad input");
+ fake_op_status(lrm_state, op, PCMK_EXEC_ERROR, PCMK_OCF_UNKNOWN_ERROR,
+ "Cannot fail unknown resource");
}
controld_ack_event_directly(from_host, from_sys, NULL, op, ID(xml_rsc));
@@ -1701,13 +1717,11 @@ do_lrm_delete(ha_msg_input_t *input, lrm_state_t *lrm_state,
lrmd_event_data_t *op = NULL;
op = construct_op(lrm_state, input->xml, rsc->id, CRMD_ACTION_DELETE);
- op->op_status = PCMK_LRM_OP_ERROR;
- if (cib_rc == EACCES) {
- op->rc = PCMK_OCF_INSUFFICIENT_PRIV;
- } else {
- op->rc = PCMK_OCF_UNKNOWN_ERROR;
- }
+ /* These are resource clean-ups, not actions, so no exit reason is
+ * needed.
+ */
+ lrmd__set_result(op, pcmk_rc2ocf(cib_rc), PCMK_EXEC_ERROR, NULL);
controld_ack_event_directly(from_host, from_sys, NULL, op, rsc->id);
lrmd_free_event(op);
return;
@@ -1747,8 +1761,9 @@ do_lrm_invoke(long long action,
if ((lrm_state == NULL) && is_remote_node) {
crm_err("Failing action because local node has never had connection to remote node %s",
target_node);
- synthesize_lrmd_failure(NULL, input->xml, PCMK_LRM_OP_NOT_CONNECTED,
- PCMK_OCF_UNKNOWN_ERROR);
+ synthesize_lrmd_failure(NULL, input->xml, PCMK_EXEC_NOT_CONNECTED,
+ PCMK_OCF_UNKNOWN_ERROR,
+ "Local node has no connection to remote");
return;
}
CRM_ASSERT(lrm_state != NULL);
@@ -1805,8 +1820,9 @@ do_lrm_invoke(long long action,
rc = get_lrm_resource(lrm_state, xml_rsc, create_rsc, &rsc);
if (rc == -ENOTCONN) {
synthesize_lrmd_failure(lrm_state, input->xml,
- PCMK_LRM_OP_NOT_CONNECTED,
- PCMK_OCF_UNKNOWN_ERROR);
+ PCMK_EXEC_NOT_CONNECTED,
+ PCMK_OCF_UNKNOWN_ERROR,
+ "Not connected to remote executor");
return;
} else if ((rc < 0) && !create_rsc) {
@@ -1825,8 +1841,9 @@ do_lrm_invoke(long long action,
// Resource operation on malformed resource
crm_err("Invalid resource definition for %s", ID(xml_rsc));
crm_log_xml_warn(input->msg, "invalid resource");
- synthesize_lrmd_failure(lrm_state, input->xml, PCMK_LRM_OP_ERROR,
- PCMK_OCF_NOT_CONFIGURED); // fatal error
+ synthesize_lrmd_failure(lrm_state, input->xml, PCMK_EXEC_ERROR,
+ PCMK_OCF_NOT_CONFIGURED, // fatal error
+ "Invalid resource definition");
return;
} else if (rc < 0) {
@@ -1835,8 +1852,9 @@ do_lrm_invoke(long long action,
CRM_XS " rc=%d",
ID(xml_rsc), pcmk_strerror(rc), rc);
crm_log_xml_warn(input->msg, "failed registration");
- synthesize_lrmd_failure(lrm_state, input->xml, PCMK_LRM_OP_ERROR,
- PCMK_OCF_INVALID_PARAM); // hard error
+ synthesize_lrmd_failure(lrm_state, input->xml, PCMK_EXEC_ERROR,
+ PCMK_OCF_INVALID_PARAM, // hard error
+ "Could not register resource with executor");
return;
}
@@ -1953,10 +1971,9 @@ construct_op(lrm_state_t *lrm_state, xmlNode *rsc_op, const char *rsc_id,
op = lrmd_new_event(rsc_id, operation, 0);
op->type = lrmd_event_exec_complete;
- op->op_status = PCMK_LRM_OP_PENDING;
- op->rc = -1;
op->timeout = 0;
op->start_delay = 0;
+ lrmd__set_result(op, PCMK_OCF_UNKNOWN, PCMK_EXEC_PENDING, NULL);
if (rsc_op == NULL) {
CRM_LOG_ASSERT(pcmk__str_eq(CRMD_ACTION_STOP, operation, pcmk__str_casei));
@@ -2204,8 +2221,7 @@ record_pending_op(const char *node_name, lrmd_rsc_info_t *rsc, lrmd_event_data_t
}
op->call_id = -1;
- op->op_status = PCMK_LRM_OP_PENDING;
- op->rc = PCMK_OCF_UNKNOWN;
+ lrmd__set_result(op, PCMK_OCF_UNKNOWN, PCMK_EXEC_PENDING, NULL);
op->t_run = time(NULL);
op->t_rcchange = op->t_run;
@@ -2221,6 +2237,7 @@ static void
do_lrm_rsc_op(lrm_state_t *lrm_state, lrmd_rsc_info_t *rsc,
const char *operation, xmlNode *msg)
{
+ int rc;
int call_id = 0;
char *op_id = NULL;
lrmd_event_data_t *op = NULL;
@@ -2228,7 +2245,7 @@ do_lrm_rsc_op(lrm_state_t *lrm_state, lrmd_rsc_info_t *rsc,
fsa_data_t *msg_data = NULL;
const char *transition = NULL;
gboolean stop_recurring = FALSE;
- bool send_nack = FALSE;
+ const char *nack_reason = NULL;
CRM_CHECK(rsc != NULL, return);
CRM_CHECK(operation != NULL, return);
@@ -2287,22 +2304,22 @@ do_lrm_rsc_op(lrm_state_t *lrm_state, lrmd_rsc_info_t *rsc,
&& pcmk__str_eq(operation, RSC_START, pcmk__str_casei)) {
register_fsa_input(C_SHUTDOWN, I_SHUTDOWN, NULL);
- send_nack = TRUE;
+ nack_reason = "Not attempting start due to shutdown in progress";
} else if (fsa_state != S_NOT_DC
&& fsa_state != S_POLICY_ENGINE /* Recalculating */
&& fsa_state != S_TRANSITION_ENGINE
&& !pcmk__str_eq(operation, CRMD_ACTION_STOP, pcmk__str_casei)) {
- send_nack = TRUE;
+ nack_reason = "Controller cannot attempt actions at this time";
}
- if(send_nack) {
+ if (nack_reason != NULL) {
crm_notice("Discarding attempt to perform action %s on %s in state %s (shutdown=%s)",
operation, rsc->id, fsa_state2string(fsa_state),
pcmk__btoa(pcmk_is_set(fsa_input_register, R_SHUTDOWN)));
- op->rc = PCMK_OCF_UNKNOWN_ERROR;
- op->op_status = PCMK_LRM_OP_INVALID;
+ lrmd__set_result(op, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_INVALID,
+ nack_reason);
controld_ack_event_directly(NULL, NULL, rsc, op, rsc->id);
lrmd_free_event(op);
free(op_id);
@@ -2329,21 +2346,11 @@ do_lrm_rsc_op(lrm_state_t *lrm_state, lrmd_rsc_info_t *rsc,
}
}
- call_id = lrm_state_exec(lrm_state, rsc->id, op->op_type, op->user_data,
- op->interval_ms, op->timeout, op->start_delay,
- params);
-
- if (call_id <= 0 && lrm_state_is_local(lrm_state)) {
- crm_err("Operation %s on %s failed: %d", operation, rsc->id, call_id);
- register_fsa_error(C_FSA_INTERNAL, I_FAIL, NULL);
-
- } else if (call_id <= 0) {
- crm_err("Operation %s on resource %s failed to execute on remote node %s: %d",
- operation, rsc->id, lrm_state->node_name, call_id);
- fake_op_status(lrm_state, op, PCMK_LRM_OP_DONE, PCMK_OCF_UNKNOWN_ERROR);
- process_lrm_event(lrm_state, op, NULL, NULL);
-
- } else {
+ rc = controld_execute_resource_agent(lrm_state, rsc->id, op->op_type,
+ op->user_data, op->interval_ms,
+ op->timeout, op->start_delay, params,
+ &call_id);
+ if (rc == pcmk_rc_ok) {
/* record all operations so we can wait
* for them to complete during shutdown
*/
@@ -2368,22 +2375,36 @@ do_lrm_rsc_op(lrm_state_t *lrm_state, lrmd_rsc_info_t *rsc,
if ((op->interval_ms > 0)
&& (op->start_delay > START_DELAY_THRESHOLD)) {
- int target_rc = 0;
+ int target_rc = PCMK_OCF_OK;
crm_info("Faking confirmation of %s: execution postponed for over 5 minutes", op_id);
decode_transition_key(op->user_data, NULL, NULL, NULL, &target_rc);
- op->rc = target_rc;
- op->op_status = PCMK_LRM_OP_DONE;
+ lrmd__set_result(op, target_rc, PCMK_EXEC_DONE, NULL);
controld_ack_event_directly(NULL, NULL, rsc, op, rsc->id);
}
pending->params = op->params;
op->params = NULL;
+
+ } else if (lrm_state_is_local(lrm_state)) {
+ crm_err("Could not initiate %s action for resource %s locally: %s "
+ CRM_XS " rc=%d", operation, rsc->id, pcmk_rc_str(rc), rc);
+ fake_op_status(lrm_state, op, PCMK_EXEC_NOT_CONNECTED,
+ PCMK_OCF_UNKNOWN_ERROR, pcmk_rc_str(rc));
+ process_lrm_event(lrm_state, op, NULL, NULL);
+ register_fsa_error(C_FSA_INTERNAL, I_FAIL, NULL);
+
+ } else {
+ crm_err("Could not initiate %s action for resource %s remotely on %s: "
+ "%s " CRM_XS " rc=%d",
+ operation, rsc->id, lrm_state->node_name, pcmk_rc_str(rc), rc);
+ fake_op_status(lrm_state, op, PCMK_EXEC_NOT_CONNECTED,
+ PCMK_OCF_UNKNOWN_ERROR, pcmk_rc_str(rc));
+ process_lrm_event(lrm_state, op, NULL, NULL);
}
free(op_id);
lrmd_free_event(op);
- return;
}
int last_resource_update = 0;
@@ -2600,6 +2621,83 @@ did_lrm_rsc_op_fail(lrm_state_t *lrm_state, const char * rsc_id,
return FALSE;
}
+/*!
+ * \internal
+ * \brief Log the result of an executor action (actual or synthesized)
+ *
+ * \param[in] op Executor action to log result for
+ * \param[in] op_key Operation key for action
+ * \param[in] node_name Name of node action was performed on, if known
+ * \param[in] update_id Call ID for CIB update (or 0 if none)
+ * \param[in] confirmed Whether to log that graph action was confirmed
+ */
+static void
+log_executor_event(lrmd_event_data_t *op, const char *op_key,
+ const char *node_name, int update_id, gboolean confirmed)
+{
+ int log_level = LOG_ERR;
+ GString *str = g_string_sized_new(100); // reasonable starting size
+
+ g_string_printf(str, "Result of %s operation for %s",
+ crm_action_str(op->op_type, op->interval_ms), op->rsc_id);
+
+ if (node_name != NULL) {
+ g_string_append_printf(str, " on %s", node_name);
+ }
+
+ switch (op->op_status) {
+ case PCMK_EXEC_DONE:
+ log_level = LOG_NOTICE;
+ g_string_append_printf(str, ": %s",
+ services_ocf_exitcode_str(op->rc));
+ break;
+
+ case PCMK_EXEC_TIMEOUT:
+ g_string_append_printf(str, ": %s after %s",
+ pcmk_exec_status_str(op->op_status),
+ pcmk__readable_interval(op->timeout));
+ break;
+
+ case PCMK_EXEC_CANCELLED:
+ log_level = LOG_INFO;
+ // Fall through
+ default:
+ g_string_append_printf(str, ": %s",
+ pcmk_exec_status_str(op->op_status));
+ }
+
+ if ((op->exit_reason != NULL)
+ && ((op->op_status != PCMK_EXEC_DONE) || (op->rc != PCMK_OCF_OK))) {
+ g_string_append_printf(str, " (%s)", op->exit_reason);
+ }
+
+ g_string_append(str, " " CRM_XS);
+ if (update_id != 0) {
+ g_string_append_printf(str, " CIB update %d,", update_id);
+ }
+ g_string_append_printf(str, " graph action %sconfirmed; call=%d key=%s",
+ (confirmed? "" : "un"), op->call_id, op_key);
+ if (op->op_status == PCMK_EXEC_DONE) {
+ g_string_append_printf(str, " rc=%d", op->rc);
+ }
+
+ do_crm_log(log_level, "%s", str->str);
+ g_string_free(str, TRUE);
+
+ if (op->output != NULL) {
+ char *prefix = crm_strdup_printf("%s-" PCMK__OP_FMT ":%d", node_name,
+ op->rsc_id, op->op_type,
+ op->interval_ms, op->call_id);
+
+ if (op->rc) {
+ crm_log_output(LOG_NOTICE, prefix, op->output);
+ } else {
+ crm_log_output(LOG_DEBUG, prefix, op->output);
+ }
+ free(prefix);
+ }
+}
+
void
process_lrm_event(lrm_state_t *lrm_state, lrmd_event_data_t *op,
active_op_t *pending, xmlNode *action_xml)
@@ -2620,13 +2718,13 @@ process_lrm_event(lrm_state_t *lrm_state, lrmd_event_data_t *op,
// Remap new status codes for older DCs
if (compare_version(fsa_our_dc_version, "3.2.0") < 0) {
switch (op->op_status) {
- case PCMK_LRM_OP_NOT_CONNECTED:
- op->op_status = PCMK_LRM_OP_ERROR;
- op->rc = PCMK_OCF_CONNECTION_DIED;
+ case PCMK_EXEC_NOT_CONNECTED:
+ lrmd__set_result(op, PCMK_OCF_CONNECTION_DIED,
+ PCMK_EXEC_ERROR, op->exit_reason);
break;
- case PCMK_LRM_OP_INVALID:
- op->op_status = PCMK_LRM_OP_ERROR;
- op->rc = CRM_DIRECT_NACK_RC;
+ case PCMK_EXEC_INVALID:
+ lrmd__set_result(op, CRM_DIRECT_NACK_RC, PCMK_EXEC_ERROR,
+ op->exit_reason);
break;
default:
break;
@@ -2672,14 +2770,14 @@ process_lrm_event(lrm_state_t *lrm_state, lrmd_event_data_t *op,
}
}
- if (op->op_status == PCMK_LRM_OP_ERROR) {
+ if (op->op_status == PCMK_EXEC_ERROR) {
switch(op->rc) {
case PCMK_OCF_NOT_RUNNING:
case PCMK_OCF_RUNNING_PROMOTED:
case PCMK_OCF_DEGRADED:
case PCMK_OCF_DEGRADED_PROMOTED:
// Leave it to the TE/scheduler to decide if this is an error
- op->op_status = PCMK_LRM_OP_DONE;
+ op->op_status = PCMK_EXEC_DONE;
break;
default:
/* Nothing to do */
@@ -2687,7 +2785,7 @@ process_lrm_event(lrm_state_t *lrm_state, lrmd_event_data_t *op,
}
}
- if (op->op_status != PCMK_LRM_OP_CANCELLED) {
+ if (op->op_status != PCMK_EXEC_CANCELLED) {
/* We might not record the result, so directly acknowledge it to the
* originator instead, so it doesn't time out waiting for the result
* (especially important if part of a transition).
@@ -2785,7 +2883,7 @@ process_lrm_event(lrm_state_t *lrm_state, lrmd_event_data_t *op,
removed = TRUE;
} else if (lrm_state && ((op->interval_ms == 0)
- || (op->op_status == PCMK_LRM_OP_CANCELLED))) {
+ || (op->op_status == PCMK_EXEC_CANCELLED))) {
gboolean found = g_hash_table_remove(lrm_state->pending_ops, op_id);
@@ -2799,60 +2897,7 @@ process_lrm_event(lrm_state_t *lrm_state, lrmd_event_data_t *op,
}
}
- if (node_name == NULL) {
- node_name = "unknown node"; // for logging
- }
-
- switch (op->op_status) {
- case PCMK_LRM_OP_CANCELLED:
- crm_info("Result of %s operation for %s on %s: %s "
- CRM_XS " call=%d key=%s confirmed=%s",
- crm_action_str(op->op_type, op->interval_ms),
- op->rsc_id, node_name,
- services_lrm_status_str(op->op_status),
- op->call_id, op_key, pcmk__btoa(removed));
- break;
-
- case PCMK_LRM_OP_DONE:
- crm_notice("Result of %s operation for %s on %s: %s "
- CRM_XS " rc=%d call=%d key=%s confirmed=%s cib-update=%d",
- crm_action_str(op->op_type, op->interval_ms),
- op->rsc_id, node_name,
- services_ocf_exitcode_str(op->rc), op->rc,
- op->call_id, op_key, pcmk__btoa(removed), update_id);
- break;
-
- case PCMK_LRM_OP_TIMEOUT:
- crm_err("Result of %s operation for %s on %s: %s "
- CRM_XS " call=%d key=%s timeout=%dms",
- crm_action_str(op->op_type, op->interval_ms),
- op->rsc_id, node_name,
- services_lrm_status_str(op->op_status),
- op->call_id, op_key, op->timeout);
- break;
-
- default:
- crm_err("Result of %s operation for %s on %s: %s "
- CRM_XS " call=%d key=%s confirmed=%s status=%d cib-update=%d",
- crm_action_str(op->op_type, op->interval_ms),
- op->rsc_id, node_name,
- services_lrm_status_str(op->op_status), op->call_id, op_key,
- pcmk__btoa(removed), op->op_status, update_id);
- }
-
- if (op->output) {
- char *prefix =
- crm_strdup_printf("%s-" PCMK__OP_FMT ":%d", node_name,
- op->rsc_id, op->op_type, op->interval_ms,
- op->call_id);
-
- if (op->rc) {
- crm_log_output(LOG_NOTICE, prefix, op->output);
- } else {
- crm_log_output(LOG_DEBUG, prefix, op->output);
- }
- free(prefix);
- }
+ log_executor_event(op, op_key, node_name, update_id, removed);
if (lrm_state) {
if (!pcmk__str_eq(op->op_type, RSC_METADATA, pcmk__str_casei)) {
diff --git a/daemons/controld/controld_execd_state.c b/daemons/controld/controld_execd_state.c
index e1e34c3..67c376a 100644
--- a/daemons/controld/controld_execd_state.c
+++ b/daemons/controld/controld_execd_state.c
@@ -8,6 +8,9 @@
*/
#include <crm_internal.h>
+
+#include <errno.h>
+
#include <crm/crm.h>
#include <crm/msg_xml.h>
#include <crm/common/iso8601.h>
@@ -73,8 +76,8 @@ fail_pending_op(gpointer key, gpointer value, gpointer user_data)
event.user_data = op->user_data;
event.timeout = 0;
event.interval_ms = op->interval_ms;
- event.rc = PCMK_OCF_UNKNOWN_ERROR;
- event.op_status = PCMK_LRM_OP_NOT_CONNECTED;
+ lrmd__set_result(&event, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_NOT_CONNECTED,
+ "Action was pending when executor connection was dropped");
event.t_run = (unsigned int) op->start_time;
event.t_rcchange = (unsigned int) op->start_time;
@@ -83,6 +86,7 @@ fail_pending_op(gpointer key, gpointer value, gpointer user_data)
event.params = op->params;
process_lrm_event(lrm_state, &event, op, NULL);
+ lrmd__reset_result(&event);
return TRUE;
}
@@ -359,30 +363,38 @@ lrm_state_poke_connection(lrm_state_t * lrm_state)
{
if (!lrm_state->conn) {
- return -1;
+ return -ENOTCONN;
}
return ((lrmd_t *) lrm_state->conn)->cmds->poke_connection(lrm_state->conn);
}
+// \return Standard Pacemaker return code
int
-lrm_state_ipc_connect(lrm_state_t * lrm_state)
+controld_connect_local_executor(lrm_state_t *lrm_state)
{
- int ret;
+ int rc = pcmk_rc_ok;
- if (!lrm_state->conn) {
- lrm_state->conn = lrmd_api_new();
- ((lrmd_t *) lrm_state->conn)->cmds->set_callback(lrm_state->conn, lrm_op_callback);
+ if (lrm_state->conn == NULL) {
+ lrmd_t *api = NULL;
+
+ rc = lrmd__new(&api, NULL, NULL, 0);
+ if (rc != pcmk_rc_ok) {
+ return rc;
+ }
+ api->cmds->set_callback(api, lrm_op_callback);
+ lrm_state->conn = api;
}
- ret = ((lrmd_t *) lrm_state->conn)->cmds->connect(lrm_state->conn, CRM_SYSTEM_CRMD, NULL);
+ rc = ((lrmd_t *) lrm_state->conn)->cmds->connect(lrm_state->conn,
+ CRM_SYSTEM_CRMD, NULL);
+ rc = pcmk_legacy2rc(rc);
- if (ret != pcmk_ok) {
- lrm_state->num_lrm_register_fails++;
- } else {
+ if (rc == pcmk_rc_ok) {
lrm_state->num_lrm_register_fails = 0;
+ } else {
+ lrm_state->num_lrm_register_fails++;
}
-
- return ret;
+ return rc;
}
static remote_proxy_t *
@@ -555,33 +567,39 @@ crmd_remote_proxy_cb(lrmd_t *lrmd, void *userdata, xmlNode *msg)
}
+// \return Standard Pacemaker return code
int
-lrm_state_remote_connect_async(lrm_state_t * lrm_state, const char *server, int port,
- int timeout_ms)
+controld_connect_remote_executor(lrm_state_t *lrm_state, const char *server,
+ int port, int timeout_ms)
{
- int ret;
+ int rc = pcmk_rc_ok;
- if (!lrm_state->conn) {
- lrm_state->conn = lrmd_remote_api_new(lrm_state->node_name, server, port);
- if (!lrm_state->conn) {
- return -1;
+ if (lrm_state->conn == NULL) {
+ lrmd_t *api = NULL;
+
+ rc = lrmd__new(&api, lrm_state->node_name, server, port);
+ if (rc != pcmk_rc_ok) {
+ crm_warn("Pacemaker Remote connection to %s:%s failed: %s "
+ CRM_XS " rc=%d", server, port, pcmk_rc_str(rc), rc);
+
+ return rc;
}
- ((lrmd_t *) lrm_state->conn)->cmds->set_callback(lrm_state->conn, remote_lrm_op_callback);
- lrmd_internal_set_proxy_callback(lrm_state->conn, lrm_state, crmd_remote_proxy_cb);
+ lrm_state->conn = api;
+ api->cmds->set_callback(api, remote_lrm_op_callback);
+ lrmd_internal_set_proxy_callback(api, lrm_state, crmd_remote_proxy_cb);
}
- crm_trace("initiating remote connection to %s at %d with timeout %d", server, port, timeout_ms);
- ret =
- ((lrmd_t *) lrm_state->conn)->cmds->connect_async(lrm_state->conn, lrm_state->node_name,
- timeout_ms);
-
- if (ret != pcmk_ok) {
- lrm_state->num_lrm_register_fails++;
- } else {
+ crm_trace("Initiating remote connection to %s:%d with timeout %dms",
+ server, port, timeout_ms);
+ rc = ((lrmd_t *) lrm_state->conn)->cmds->connect_async(lrm_state->conn,
+ lrm_state->node_name,
+ timeout_ms);
+ if (rc == pcmk_ok) {
lrm_state->num_lrm_register_fails = 0;
+ } else {
+ lrm_state->num_lrm_register_fails++; // Ignored for remote connections
}
-
- return ret;
+ return pcmk_legacy2rc(rc);
}
int
@@ -661,32 +679,57 @@ lrm_state_get_rsc_info(lrm_state_t * lrm_state, const char *rsc_id, enum lrmd_ca
}
+/*!
+ * \internal
+ * \brief Initiate a resource agent action
+ *
+ * \param[in] lrm_state Executor state object
+ * \param[in] rsc_id ID of resource for action
+ * \param[in] action Action to execute
+ * \param[in] userdata String to copy and pass to execution callback
+ * \param[in] interval_ms Action interval (in milliseconds)
+ * \param[in] timeout_ms Action timeout (in milliseconds)
+ * \param[in] start_delay_ms Delay (in milliseconds) before initiating action
+ * \param[in] params Resource parameters
+ * \param[out] call_id Where to store call ID on success
+ *
+ * \return Standard Pacemaker return code
+ * \note This takes ownership of \p params, which should not be used or freed
+ * after calling this function.
+ */
int
-lrm_state_exec(lrm_state_t *lrm_state, const char *rsc_id, const char *action,
- const char *userdata, guint interval_ms,
- int timeout, /* ms */
- int start_delay, /* ms */
- lrmd_key_value_t * params)
+controld_execute_resource_agent(lrm_state_t *lrm_state, const char *rsc_id,
+ const char *action, const char *userdata,
+ guint interval_ms, int timeout_ms,
+ int start_delay_ms, lrmd_key_value_t *params,
+ int *call_id)
{
+ int rc = pcmk_rc_ok;
- if (!lrm_state->conn) {
+ if (lrm_state->conn == NULL) {
lrmd_key_value_freeall(params);
- return -ENOTCONN;
- }
+ rc = ENOTCONN;
- if (is_remote_lrmd_ra(NULL, NULL, rsc_id)) {
- return remote_ra_exec(lrm_state, rsc_id, action, userdata, interval_ms,
- timeout, start_delay, params);
- }
-
- return ((lrmd_t *) lrm_state->conn)->cmds->exec(lrm_state->conn,
- rsc_id,
- action,
- userdata,
- interval_ms,
- timeout,
- start_delay,
- lrmd_opt_notify_changes_only, params);
+ } else if (is_remote_lrmd_ra(NULL, NULL, rsc_id)) {
+ rc = controld_execute_remote_agent(lrm_state, rsc_id, action,
+ userdata, interval_ms, timeout_ms,
+ start_delay_ms, params, call_id);
+
+ } else {
+ rc = ((lrmd_t *) lrm_state->conn)->cmds->exec(lrm_state->conn, rsc_id,
+ action, userdata,
+ interval_ms, timeout_ms,
+ start_delay_ms,
+ lrmd_opt_notify_changes_only,
+ params);
+ if (rc < 0) {
+ rc = pcmk_legacy2rc(rc);
+ } else {
+ *call_id = rc;
+ rc = pcmk_rc_ok;
+ }
+ }
+ return rc;
}
int
diff --git a/daemons/controld/controld_fencing.c b/daemons/controld/controld_fencing.c
index 0fba661..f5a252c 100644
--- a/daemons/controld/controld_fencing.c
+++ b/daemons/controld/controld_fencing.c
@@ -11,6 +11,7 @@
#include <crm/crm.h>
#include <crm/msg_xml.h>
#include <crm/common/xml.h>
+#include <crm/stonith-ng.h>
#include <crm/fencing/internal.h>
#include <pacemaker-controld.h>
@@ -372,22 +373,22 @@ fail_incompletable_stonith(crm_graph_t *graph)
GList *lpc2 = NULL;
synapse_t *synapse = (synapse_t *) lpc->data;
- if (synapse->confirmed) {
+ if (pcmk_is_set(synapse->flags, pcmk__synapse_confirmed)) {
continue;
}
for (lpc2 = synapse->actions; lpc2 != NULL; lpc2 = lpc2->next) {
crm_action_t *action = (crm_action_t *) lpc2->data;
- if (action->type != action_type_crm || action->confirmed) {
+ if (action->type != action_type_crm || pcmk_is_set(action->flags, pcmk__graph_action_confirmed)) {
continue;
}
task = crm_element_value(action->xml, XML_LRM_ATTR_TASK);
if (task && pcmk__str_eq(task, CRM_OP_FENCE, pcmk__str_casei)) {
- action->failed = TRUE;
+ crm__set_graph_action_flags(action, pcmk__graph_action_failed);
last_action = action->xml;
- update_graph(graph, action);
+ pcmk__update_graph(graph, action);
crm_notice("Failing action %d (%s): fencer terminated",
action->id, ID(action->xml));
}
@@ -752,7 +753,7 @@ tengine_stonith_callback(stonith_t *stonith, stonith_callback_data_t *data)
const char *op = crm_meta_value(action->params, "stonith_action");
crm_info("Stonith operation %d for %s passed", call_id, target);
- if (action->confirmed == FALSE) {
+ if (!(pcmk_is_set(action->flags, pcmk__graph_action_confirmed))) {
te_action_confirmed(action, NULL);
if (pcmk__str_eq("on", op, pcmk__str_casei)) {
const char *value = NULL;
@@ -782,9 +783,9 @@ tengine_stonith_callback(stonith_t *stonith, stonith_callback_data_t *data)
update_attrd(target, CRM_ATTR_DIGESTS_SECURE, value, NULL,
is_remote_node);
- } else if (action->sent_update == FALSE) {
+ } else if (!(pcmk_is_set(action->flags, pcmk__graph_action_sent_update))) {
send_stonith_update(action, target, uuid);
- action->sent_update = TRUE;
+ crm__set_graph_action_flags(action, pcmk__graph_action_sent_update);
}
}
st_fail_count_reset(target);
@@ -793,7 +794,7 @@ tengine_stonith_callback(stonith_t *stonith, stonith_callback_data_t *data)
const char *target = crm_element_value(action->xml, XML_LRM_ATTR_TARGET);
enum transition_action abort_action = tg_restart;
- action->failed = TRUE;
+ crm__set_graph_action_flags(action, pcmk__graph_action_failed);
crm_notice("Stonith operation %d for %s failed (%s): aborting transition.",
call_id, target, pcmk_strerror(rc));
@@ -813,7 +814,7 @@ tengine_stonith_callback(stonith_t *stonith, stonith_callback_data_t *data)
abort_for_stonith_failure(abort_action, target, NULL);
}
- update_graph(transition_graph, action);
+ pcmk__update_graph(transition_graph, action);
trigger_graph();
bail:
@@ -886,6 +887,19 @@ te_fence_node(crm_graph_t *graph, crm_action_t *action)
return TRUE;
}
+bool
+controld_verify_stonith_watchdog_timeout(const char *value)
+{
+ gboolean rv = TRUE;
+
+ if (stonith_api && (stonith_api->state != stonith_disconnected) &&
+ stonith__watchdog_fencing_enabled_for_node_api(stonith_api,
+ fsa_our_uname)) {
+ rv = pcmk__valid_sbd_timeout(value);
+ }
+ return rv;
+}
+
/* end stonith API client functions */
diff --git a/daemons/controld/controld_fencing.h b/daemons/controld/controld_fencing.h
index d0ecc82..ef68a0c 100644
--- a/daemons/controld/controld_fencing.h
+++ b/daemons/controld/controld_fencing.h
@@ -24,6 +24,7 @@ void update_stonith_max_attempts(const char* value);
void controld_trigger_fencer_connect(void);
void controld_disconnect_fencer(bool destroy);
gboolean te_fence_node(crm_graph_t *graph, crm_action_t *action);
+bool controld_verify_stonith_watchdog_timeout(const char *value);
// stonith cleanup list
void add_stonith_cleanup(const char *target);
diff --git a/daemons/controld/controld_fsa.h b/daemons/controld/controld_fsa.h
index c63880a..296232f 100644
--- a/daemons/controld/controld_fsa.h
+++ b/daemons/controld/controld_fsa.h
@@ -426,7 +426,7 @@ enum crmd_fsa_input {
# define R_IN_RECOVERY 0x80000000ULL
-#define CRM_DIRECT_NACK_RC (99) // Deprecated (see PCMK_LRM_OP_INVALID)
+#define CRM_DIRECT_NACK_RC (99) // Deprecated (see PCMK_EXEC_INVALID)
enum crmd_fsa_cause {
C_UNKNOWN = 0,
diff --git a/daemons/controld/controld_join_client.c b/daemons/controld/controld_join_client.c
index c62170d..51947a4 100644
--- a/daemons/controld/controld_join_client.c
+++ b/daemons/controld/controld_join_client.c
@@ -225,7 +225,7 @@ do_cl_join_finalize_respond(long long action,
gboolean was_nack = TRUE;
static gboolean first_join = TRUE;
ha_msg_input_t *input = fsa_typed_data(fsa_dt_ha_msg);
- const char *start_state = pcmk__env_option("node_start_state");
+ const char *start_state = pcmk__env_option(PCMK__ENV_NODE_START_STATE);
int join_id = -1;
const char *op = crm_element_value(input->msg, F_CRM_TASK);
diff --git a/daemons/controld/controld_lrm.h b/daemons/controld/controld_lrm.h
index 8bc1610..028984b 100644
--- a/daemons/controld/controld_lrm.h
+++ b/daemons/controld/controld_lrm.h
@@ -137,9 +137,9 @@ lrm_state_t *lrm_state_find_or_create(const char *node_name);
*/
void lrm_state_disconnect_only(lrm_state_t * lrm_state);
void lrm_state_disconnect(lrm_state_t * lrm_state);
-int lrm_state_ipc_connect(lrm_state_t * lrm_state);
-int lrm_state_remote_connect_async(lrm_state_t * lrm_state, const char *server, int port,
- int timeout);
+int controld_connect_local_executor(lrm_state_t *lrm_state);
+int controld_connect_remote_executor(lrm_state_t *lrm_state, const char *server,
+ int port, int timeout);
int lrm_state_is_connected(lrm_state_t * lrm_state);
int lrm_state_poke_connection(lrm_state_t * lrm_state);
@@ -149,11 +149,11 @@ int lrm_state_get_metadata(lrm_state_t * lrm_state,
const char *agent, char **output, enum lrmd_call_options options);
int lrm_state_cancel(lrm_state_t *lrm_state, const char *rsc_id,
const char *action, guint interval_ms);
-int lrm_state_exec(lrm_state_t *lrm_state, const char *rsc_id,
- const char *action, const char *userdata, guint interval_ms,
- int timeout, /* ms */
- int start_delay, /* ms */
- lrmd_key_value_t * params);
+int controld_execute_resource_agent(lrm_state_t *lrm_state, const char *rsc_id,
+ const char *action, const char *userdata,
+ guint interval_ms, int timeout_ms,
+ int start_delay_ms,
+ lrmd_key_value_t *params, int *call_id);
lrmd_rsc_info_t *lrm_state_get_rsc_info(lrm_state_t * lrm_state,
const char *rsc_id, enum lrmd_call_options options);
int lrm_state_register_rsc(lrm_state_t * lrm_state,
@@ -169,11 +169,11 @@ gboolean is_remote_lrmd_ra(const char *agent, const char *provider, const char *
lrmd_rsc_info_t *remote_ra_get_rsc_info(lrm_state_t * lrm_state, const char *rsc_id);
int remote_ra_cancel(lrm_state_t *lrm_state, const char *rsc_id,
const char *action, guint interval_ms);
-int remote_ra_exec(lrm_state_t *lrm_state, const char *rsc_id,
- const char *action, const char *userdata, guint interval_ms,
- int timeout, /* ms */
- int start_delay, /* ms */
- lrmd_key_value_t * params);
+int controld_execute_remote_agent(lrm_state_t *lrm_state, const char *rsc_id,
+ const char *action, const char *userdata,
+ guint interval_ms, int timeout_ms,
+ int start_delay_ms, lrmd_key_value_t *params,
+ int *call_id);
void remote_ra_cleanup(lrm_state_t * lrm_state);
void remote_ra_fail(const char *node_name);
void remote_ra_process_pseudo(xmlNode *xml);
diff --git a/daemons/controld/controld_messages.c b/daemons/controld/controld_messages.c
index 7e3a036..7ffb056 100644
--- a/daemons/controld/controld_messages.c
+++ b/daemons/controld/controld_messages.c
@@ -326,12 +326,12 @@ gboolean
relay_message(xmlNode * msg, gboolean originated_locally)
{
int dest = 1;
- int is_for_dc = 0;
- int is_for_dcib = 0;
- int is_for_te = 0;
- int is_for_crm = 0;
- int is_for_cib = 0;
- int is_local = 0;
+ bool is_for_dc = false;
+ bool is_for_dcib = false;
+ bool is_for_te = false;
+ bool is_for_crm = false;
+ bool is_for_cib = false;
+ bool is_local = false;
const char *host_to = crm_element_value(msg, F_CRM_HOST_TO);
const char *sys_to = crm_element_value(msg, F_CRM_SYS_TO);
const char *sys_from = crm_element_value(msg, F_CRM_SYS_FROM);
@@ -370,10 +370,10 @@ relay_message(xmlNode * msg, gboolean originated_locally)
is_for_cib = (strcasecmp(CRM_SYSTEM_CIB, sys_to) == 0);
is_for_crm = (strcasecmp(CRM_SYSTEM_CRMD, sys_to) == 0);
- is_local = 0;
+ is_local = false;
if (pcmk__str_empty(host_to)) {
if (is_for_dc || is_for_te) {
- is_local = 0;
+ is_local = false;
} else if (is_for_crm) {
if (pcmk__strcase_any_of(task, CRM_OP_NODE_INFO,
@@ -383,24 +383,24 @@ relay_message(xmlNode * msg, gboolean originated_locally)
* client may not know the local node name. Always handle these
* requests locally.
*/
- is_local = 1;
+ is_local = true;
} else {
is_local = !originated_locally;
}
} else {
- is_local = 1;
+ is_local = true;
}
} else if (pcmk__str_eq(fsa_our_uname, host_to, pcmk__str_casei)) {
- is_local = 1;
+ is_local = true;
} else if (is_for_crm && pcmk__str_eq(task, CRM_OP_LRM_DELETE, pcmk__str_casei)) {
xmlNode *msg_data = get_message_xml(msg, F_CRM_DATA);
const char *mode = crm_element_value(msg_data, PCMK__XA_MODE);
if (pcmk__str_eq(mode, XML_TAG_CIB, pcmk__str_casei)) {
// Local delete of an offline node's resource history
- is_local = 1;
+ is_local = true;
}
}
@@ -1268,7 +1268,7 @@ send_remote_state_message(const char *node_name, gboolean node_up)
xmlNode *msg = create_request(CRM_OP_REMOTE_STATE, NULL, fsa_our_dc,
CRM_SYSTEM_DC, CRM_SYSTEM_CRMD, NULL);
- crm_info("Notifying DC %s of pacemaker_remote node %s %s",
+ crm_info("Notifying DC %s of Pacemaker Remote node %s %s",
fsa_our_dc, node_name, (node_up? "coming up" : "going down"));
crm_xml_add(msg, XML_ATTR_ID, node_name);
crm_xml_add_boolean(msg, XML_NODE_IN_CLUSTER, node_up);
@@ -1276,7 +1276,7 @@ send_remote_state_message(const char *node_name, gboolean node_up)
TRUE);
free_xml(msg);
} else {
- crm_debug("No DC to notify of pacemaker_remote node %s %s",
+ crm_debug("No DC to notify of Pacemaker Remote node %s %s",
node_name, (node_up? "coming up" : "going down"));
}
}
diff --git a/daemons/controld/controld_remote_ra.c b/daemons/controld/controld_remote_ra.c
index 8faeead..d1562fa 100644
--- a/daemons/controld/controld_remote_ra.c
+++ b/daemons/controld/controld_remote_ra.c
@@ -13,6 +13,7 @@
#include <crm/msg_xml.h>
#include <crm/common/xml_internal.h>
#include <crm/lrmd.h>
+#include <crm/lrmd_internal.h>
#include <crm/services.h>
#include <pacemaker-controld.h>
@@ -31,7 +32,6 @@ typedef struct remote_ra_cmd_s {
char *action;
/*! some string the client wants us to give it back */
char *userdata;
- char *exit_reason; // descriptive text on error
/*! start delay in ms */
int start_delay;
/*! timer id used for start delay. */
@@ -48,9 +48,7 @@ typedef struct remote_ra_cmd_s {
int takeover_timeout_id;
/*! action parameters */
lrmd_key_value_t *params;
- /*! executed rc */
- int rc;
- int op_status;
+ pcmk__action_result_t result;
int call_id;
time_t start_time;
gboolean cancel;
@@ -113,7 +111,7 @@ free_cmd(gpointer user_data)
free(cmd->rsc_id);
free(cmd->action);
free(cmd->userdata);
- free(cmd->exit_reason);
+ pcmk__reset_result(&(cmd->result));
lrmd_key_value_freeall(cmd->params);
free(cmd);
}
@@ -181,7 +179,7 @@ remote_node_up(const char *node_name)
enum controld_section_e section = controld_section_all;
CRM_CHECK(node_name != NULL, return);
- crm_info("Announcing pacemaker_remote node %s", node_name);
+ crm_info("Announcing Pacemaker Remote node %s", node_name);
/* Clear node's entire state (resource history and transient attributes)
* other than shutdown locks. The transient attributes should and normally
@@ -299,7 +297,7 @@ static void
check_remote_node_state(remote_ra_cmd_t *cmd)
{
/* Only successful actions can change node state */
- if (cmd->rc != PCMK_OCF_OK) {
+ if (cmd->result.exit_status != PCMK_OCF_OK) {
return;
}
@@ -359,14 +357,15 @@ report_remote_ra_result(remote_ra_cmd_t * cmd)
op.rsc_id = cmd->rsc_id;
op.op_type = cmd->action;
op.user_data = cmd->userdata;
- op.exit_reason = cmd->exit_reason;
op.timeout = cmd->timeout;
op.interval_ms = cmd->interval_ms;
- op.rc = cmd->rc;
- op.op_status = cmd->op_status;
op.t_run = (unsigned int) cmd->start_time;
op.t_rcchange = (unsigned int) cmd->start_time;
- if (cmd->reported_success && cmd->rc != PCMK_OCF_OK) {
+
+ lrmd__set_result(&op, cmd->result.exit_status, cmd->result.execution_status,
+ cmd->result.exit_reason);
+
+ if (cmd->reported_success && (cmd->result.exit_status != PCMK_OCF_OK)) {
op.t_rcchange = (unsigned int) time(NULL);
/* This edge case will likely never ever occur, but if it does the
* result is that a failure will not be processed correctly. This is only
@@ -399,6 +398,7 @@ report_remote_ra_result(remote_ra_cmd_t * cmd)
if (op.params) {
g_hash_table_destroy(op.params);
}
+ lrmd__reset_result(&op);
}
static void
@@ -413,7 +413,7 @@ retry_start_cmd_cb(gpointer data)
lrm_state_t *lrm_state = data;
remote_ra_data_t *ra_data = lrm_state->remote_ra_data;
remote_ra_cmd_t *cmd = NULL;
- int rc = -1;
+ int rc = ETIME;
if (!ra_data || !ra_data->cur_cmd) {
return FALSE;
@@ -426,11 +426,13 @@ retry_start_cmd_cb(gpointer data)
if (cmd->remaining_timeout > 0) {
rc = handle_remote_ra_start(lrm_state, cmd, cmd->remaining_timeout);
+ } else {
+ pcmk__set_result(&(cmd->result), PCMK_OCF_UNKNOWN_ERROR,
+ PCMK_EXEC_TIMEOUT,
+ "Not enough time remains to retry remote connection");
}
- if (rc != 0) {
- cmd->rc = PCMK_OCF_UNKNOWN_ERROR;
- cmd->op_status = PCMK_LRM_OP_ERROR;
+ if (rc != pcmk_rc_ok) {
report_remote_ra_result(cmd);
if (ra_data->cmds) {
@@ -474,8 +476,8 @@ monitor_timeout_cb(gpointer data)
crm_info("Timed out waiting for remote poke response from %s%s",
cmd->rsc_id, (lrm_state? "" : " (no LRM state)"));
cmd->monitor_timeout_id = 0;
- cmd->op_status = PCMK_LRM_OP_TIMEOUT;
- cmd->rc = PCMK_OCF_UNKNOWN_ERROR;
+ pcmk__set_result(&(cmd->result), PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_TIMEOUT,
+ "Remote executor did not respond");
if (lrm_state && lrm_state->remote_ra_data) {
remote_ra_data_t *ra_data = lrm_state->remote_ra_data;
@@ -511,11 +513,10 @@ synthesize_lrmd_success(lrm_state_t *lrm_state, const char *rsc_id, const char *
op.type = lrmd_event_exec_complete;
op.rsc_id = rsc_id;
op.op_type = op_type;
- op.rc = PCMK_OCF_OK;
- op.op_status = PCMK_LRM_OP_DONE;
op.t_run = (unsigned int) time(NULL);
op.t_rcchange = op.t_run;
op.call_id = generate_callid();
+ lrmd__set_result(&op, PCMK_OCF_OK, PCMK_EXEC_DONE, NULL);
process_lrm_event(lrm_state, &op, NULL, NULL);
}
@@ -532,7 +533,7 @@ remote_lrm_op_callback(lrmd_event_data_t * op)
(op->op_type? op->op_type : ""), (op->op_type? " " : ""),
lrmd_event_type2str(op->type), op->remote_nodename,
services_ocf_exitcode_str(op->rc), op->rc,
- services_lrm_status_str(op->op_status), op->op_status);
+ pcmk_exec_status_str(op->op_status), op->op_status);
lrm_state = lrm_state_find(op->remote_nodename);
if (!lrm_state || !lrm_state->remote_ra_data) {
@@ -549,7 +550,8 @@ remote_lrm_op_callback(lrmd_event_data_t * op)
ra_data->migrate_status = takeover_complete;
} else {
- crm_err("Unexpected pacemaker_remote client takeover for %s. Disconnecting", op->remote_nodename);
+ crm_err("Disconnecting from Pacemaker Remote node %s due to "
+ "unexpected client takeover", op->remote_nodename);
/* In this case, lrmd_tls_connection_destroy() will be called under the control of mainloop. */
/* Do not free lrm_state->conn yet. */
/* It'll be freed in the following stop action. */
@@ -608,9 +610,9 @@ remote_lrm_op_callback(lrmd_event_data_t * op)
if (op->connection_rc == -ENOKEY) {
// Hard error, don't retry
- cmd->op_status = PCMK_LRM_OP_ERROR;
- cmd->rc = PCMK_OCF_INVALID_PARAM;
- cmd->exit_reason = strdup("Authentication key not readable");
+ pcmk__set_result(&(cmd->result), PCMK_OCF_INVALID_PARAM,
+ PCMK_EXEC_ERROR,
+ "Authentication key not readable");
} else if (cmd->remaining_timeout > 3000) {
crm_trace("rescheduling start, remaining timeout %d", cmd->remaining_timeout);
@@ -620,14 +622,14 @@ remote_lrm_op_callback(lrmd_event_data_t * op)
} else {
crm_trace("can't reschedule start, remaining timeout too small %d",
cmd->remaining_timeout);
- cmd->op_status = PCMK_LRM_OP_TIMEOUT;
- cmd->rc = PCMK_OCF_UNKNOWN_ERROR;
+ pcmk__set_result(&(cmd->result), PCMK_OCF_UNKNOWN_ERROR,
+ PCMK_EXEC_TIMEOUT,
+ pcmk_strerror(op->connection_rc));
}
} else {
lrm_state_reset_tables(lrm_state, TRUE);
- cmd->rc = PCMK_OCF_OK;
- cmd->op_status = PCMK_LRM_OP_DONE;
+ pcmk__set_result(&(cmd->result), PCMK_OCF_OK, PCMK_EXEC_DONE, NULL);
ra_data->active = TRUE;
}
@@ -646,8 +648,7 @@ remote_lrm_op_callback(lrmd_event_data_t * op)
* For this function, if we get the poke pack, it is always a success. Pokes
* only fail if the send fails, or the response times out. */
if (!cmd->reported_success) {
- cmd->rc = PCMK_OCF_OK;
- cmd->op_status = PCMK_LRM_OP_DONE;
+ pcmk__set_result(&(cmd->result), PCMK_OCF_OK, PCMK_EXEC_DONE, NULL);
report_remote_ra_result(cmd);
cmd->reported_success = 1;
}
@@ -665,8 +666,10 @@ remote_lrm_op_callback(lrmd_event_data_t * op)
} else if (op->type == lrmd_event_disconnect && pcmk__str_eq(cmd->action, "monitor", pcmk__str_casei)) {
if (ra_data->active == TRUE && (cmd->cancel == FALSE)) {
- cmd->rc = PCMK_OCF_UNKNOWN_ERROR;
- cmd->op_status = PCMK_LRM_OP_ERROR;
+ pcmk__set_result(&(cmd->result), PCMK_OCF_UNKNOWN_ERROR,
+ PCMK_EXEC_ERROR,
+ "Remote connection unexpectedly dropped "
+ "during monitor");
report_remote_ra_result(cmd);
crm_err("Remote connection to %s unexpectedly dropped during monitor",
lrm_state->node_name);
@@ -722,13 +725,12 @@ handle_remote_ra_stop(lrm_state_t * lrm_state, remote_ra_cmd_t * cmd)
ra_data->cur_cmd = NULL;
if (cmd) {
- cmd->rc = PCMK_OCF_OK;
- cmd->op_status = PCMK_LRM_OP_DONE;
-
+ pcmk__set_result(&(cmd->result), PCMK_OCF_OK, PCMK_EXEC_DONE, NULL);
report_remote_ra_result(cmd);
}
}
+// \return Standard Pacemaker return code
static int
handle_remote_ra_start(lrm_state_t * lrm_state, remote_ra_cmd_t * cmd, int timeout_ms)
{
@@ -737,6 +739,7 @@ handle_remote_ra_start(lrm_state_t * lrm_state, remote_ra_cmd_t * cmd, int timeo
int port = 0;
remote_ra_data_t *ra_data = lrm_state->remote_ra_data;
int timeout_used = timeout_ms > MAX_START_TIMEOUT_MS ? MAX_START_TIMEOUT_MS : timeout_ms;
+ int rc = pcmk_rc_ok;
for (tmp = cmd->params; tmp; tmp = tmp->next) {
if (pcmk__strcase_any_of(tmp->key, XML_RSC_ATTR_REMOTE_RA_ADDR,
@@ -749,7 +752,13 @@ handle_remote_ra_start(lrm_state_t * lrm_state, remote_ra_cmd_t * cmd, int timeo
}
}
- return lrm_state_remote_connect_async(lrm_state, server, port, timeout_used);
+ rc = controld_connect_remote_executor(lrm_state, server, port,
+ timeout_used);
+ if (rc != pcmk_rc_ok) {
+ pcmk__set_result(&(cmd->result), PCMK_OCF_UNKNOWN_ERROR,
+ PCMK_EXEC_ERROR, pcmk_rc_str(rc));
+ }
+ return rc;
}
static gboolean
@@ -779,18 +788,13 @@ handle_remote_ra_exec(gpointer user_data)
if (!strcmp(cmd->action, "start") || !strcmp(cmd->action, "migrate_from")) {
ra_data->migrate_status = 0;
- rc = handle_remote_ra_start(lrm_state, cmd, cmd->timeout);
- if (rc == 0) {
+ if (handle_remote_ra_start(lrm_state, cmd,
+ cmd->timeout) == pcmk_rc_ok) {
/* take care of this later when we get async connection result */
crm_debug("Initiated async remote connection, %s action will complete after connect event",
cmd->action);
ra_data->cur_cmd = cmd;
return TRUE;
- } else {
- crm_debug("Could not initiate remote connection for %s action",
- cmd->action);
- cmd->rc = PCMK_OCF_UNKNOWN_ERROR;
- cmd->op_status = PCMK_LRM_OP_ERROR;
}
report_remote_ra_result(cmd);
@@ -799,13 +803,13 @@ handle_remote_ra_exec(gpointer user_data)
if (lrm_state_is_connected(lrm_state) == TRUE) {
rc = lrm_state_poke_connection(lrm_state);
if (rc < 0) {
- cmd->rc = PCMK_OCF_UNKNOWN_ERROR;
- cmd->op_status = PCMK_LRM_OP_ERROR;
+ pcmk__set_result(&(cmd->result), PCMK_OCF_UNKNOWN_ERROR,
+ PCMK_EXEC_ERROR, pcmk_strerror(rc));
}
} else {
rc = -1;
- cmd->op_status = PCMK_LRM_OP_DONE;
- cmd->rc = PCMK_OCF_NOT_RUNNING;
+ pcmk__set_result(&(cmd->result), PCMK_OCF_NOT_RUNNING,
+ PCMK_EXEC_DONE, "Remote connection inactive");
}
if (rc == 0) {
@@ -835,8 +839,7 @@ handle_remote_ra_exec(gpointer user_data)
} else if (!strcmp(cmd->action, "migrate_to")) {
ra_data->migrate_status = expect_takeover;
- cmd->rc = PCMK_OCF_OK;
- cmd->op_status = PCMK_LRM_OP_DONE;
+ pcmk__set_result(&(cmd->result), PCMK_OCF_OK, PCMK_EXEC_DONE, NULL);
report_remote_ra_result(cmd);
} else if (pcmk__str_any_of(cmd->action, CRMD_ACTION_RELOAD,
CRMD_ACTION_RELOAD_AGENT, NULL)) {
@@ -849,8 +852,7 @@ handle_remote_ra_exec(gpointer user_data)
* of "reload-agent". An OCF 1.1 "reload" would be a no-op anyway,
* so this would work for that purpose as well.
*/
- cmd->rc = PCMK_OCF_OK;
- cmd->op_status = PCMK_LRM_OP_DONE;
+ pcmk__set_result(&(cmd->result), PCMK_OCF_OK, PCMK_EXEC_DONE, NULL);
report_remote_ra_result(cmd);
}
@@ -956,8 +958,8 @@ fail_all_monitor_cmds(GList * list)
for (gIter = rm_list; gIter != NULL; gIter = gIter->next) {
cmd = gIter->data;
- cmd->rc = PCMK_OCF_UNKNOWN_ERROR;
- cmd->op_status = PCMK_LRM_OP_ERROR;
+ pcmk__set_result(&(cmd->result), PCMK_OCF_UNKNOWN_ERROR,
+ PCMK_EXEC_ERROR, "Lost connection to remote executor");
crm_trace("Pre-emptively failing %s %s (interval=%u, %s)",
cmd->action, cmd->rsc_id, cmd->interval_ms, cmd->userdata);
report_remote_ra_result(cmd);
@@ -1092,27 +1094,50 @@ handle_dup:
return cmd;
}
+/*!
+ * \internal
+ * \brief Execute an action using the (internal) ocf:pacemaker:remote agent
+ *
+ * \param[in] lrm_state Executor state object for remote connection
+ * \param[in] rsc_id Connection resource ID
+ * \param[in] action Action to execute
+ * \param[in] userdata String to copy and pass to execution callback
+ * \param[in] interval_ms Action interval (in milliseconds)
+ * \param[in] timeout_ms Action timeout (in milliseconds)
+ * \param[in] start_delay_ms Delay (in milliseconds) before initiating action
+ * \param[in] params Connection resource parameters
+ * \param[out] call_id Where to store call ID on success
+ *
+ * \return Standard Pacemaker return code
+ * \note This takes ownership of \p params, which should not be used or freed
+ * after calling this function.
+ */
int
-remote_ra_exec(lrm_state_t *lrm_state, const char *rsc_id, const char *action,
- const char *userdata, guint interval_ms,
- int timeout, /* ms */
- int start_delay, /* ms */
- lrmd_key_value_t * params)
+controld_execute_remote_agent(lrm_state_t *lrm_state, const char *rsc_id,
+ const char *action, const char *userdata,
+ guint interval_ms, int timeout_ms,
+ int start_delay_ms, lrmd_key_value_t *params,
+ int *call_id)
{
- int rc = 0;
lrm_state_t *connection_rsc = NULL;
remote_ra_cmd_t *cmd = NULL;
remote_ra_data_t *ra_data = NULL;
- if (is_remote_ra_supported_action(action) == FALSE) {
- rc = -EINVAL;
- goto exec_done;
+ *call_id = 0;
+
+ CRM_CHECK((lrm_state != NULL) && (rsc_id != NULL) && (action != NULL)
+ && (userdata != NULL) && (call_id != NULL),
+ lrmd_key_value_freeall(params); return EINVAL);
+
+ if (!is_remote_ra_supported_action(action)) {
+ lrmd_key_value_freeall(params);
+ return EOPNOTSUPP;
}
connection_rsc = lrm_state_find(rsc_id);
- if (!connection_rsc) {
- rc = -EINVAL;
- goto exec_done;
+ if (connection_rsc == NULL) {
+ lrmd_key_value_freeall(params);
+ return ENOTCONN;
}
remote_ra_data_init(connection_rsc);
@@ -1120,18 +1145,31 @@ remote_ra_exec(lrm_state_t *lrm_state, const char *rsc_id, const char *action,
cmd = handle_dup_monitor(ra_data, interval_ms, userdata);
if (cmd) {
- rc = cmd->call_id;
- goto exec_done;
+ *call_id = cmd->call_id;
+ lrmd_key_value_freeall(params);
+ return pcmk_rc_ok;
}
cmd = calloc(1, sizeof(remote_ra_cmd_t));
+ if (cmd == NULL) {
+ lrmd_key_value_freeall(params);
+ return ENOMEM;
+ }
+
cmd->owner = strdup(lrm_state->node_name);
cmd->rsc_id = strdup(rsc_id);
cmd->action = strdup(action);
cmd->userdata = strdup(userdata);
+ if ((cmd->owner == NULL) || (cmd->rsc_id == NULL) || (cmd->action == NULL)
+ || (cmd->userdata == NULL)) {
+ free_cmd(cmd);
+ lrmd_key_value_freeall(params);
+ return ENOMEM;
+ }
+
cmd->interval_ms = interval_ms;
- cmd->timeout = timeout;
- cmd->start_delay = start_delay;
+ cmd->timeout = timeout_ms;
+ cmd->start_delay = start_delay_ms;
cmd->params = params;
cmd->start_time = time(NULL);
@@ -1144,11 +1182,8 @@ remote_ra_exec(lrm_state_t *lrm_state, const char *rsc_id, const char *action,
ra_data->cmds = g_list_append(ra_data->cmds, cmd);
mainloop_set_trigger(ra_data->work);
- return cmd->call_id;
- exec_done:
-
- lrmd_key_value_freeall(params);
- return rc;
+ *call_id = cmd->call_id;
+ return pcmk_rc_ok;
}
/*!
@@ -1165,7 +1200,7 @@ remote_ra_fail(const char *node_name)
if (lrm_state && lrm_state_is_connected(lrm_state)) {
remote_ra_data_t *ra_data = lrm_state->remote_ra_data;
- crm_info("Failing monitors on pacemaker_remote node %s", node_name);
+ crm_info("Failing monitors on Pacemaker Remote node %s", node_name);
ra_data->recurring_cmds = fail_all_monitor_cmds(ra_data->recurring_cmds);
ra_data->cmds = fail_all_monitor_cmds(ra_data->cmds);
}
diff --git a/daemons/controld/controld_te_actions.c b/daemons/controld/controld_te_actions.c
index 2204b6e..5599c74 100644
--- a/daemons/controld/controld_te_actions.c
+++ b/daemons/controld/controld_te_actions.c
@@ -236,13 +236,11 @@ controld_record_action_timeout(crm_action_t *action)
* that will be reported via the usual callback. This timeout means that we
* didn't hear from the executor or the controller that relayed the action
* to the executor.
- *
- * @TODO Using PCMK_OCF_UNKNOWN_ERROR instead of PCMK_OCF_TIMEOUT is one way
- * to distinguish those situations, but perhaps PCMK_OCF_TIMEOUT would be
- * preferable anyway.
*/
- op = convert_graph_action(NULL, action, PCMK_LRM_OP_TIMEOUT,
- PCMK_OCF_UNKNOWN_ERROR);
+ op = pcmk__event_from_graph_action(NULL, action, PCMK_EXEC_TIMEOUT,
+ PCMK_OCF_UNKNOWN_ERROR,
+ "Cluster communication timeout "
+ "(no response from executor)");
op->call_id = -1;
op->user_data = pcmk__transition_key(transition_graph->id, action->id,
target_rc, te_uuid);
@@ -259,7 +257,7 @@ controld_record_action_timeout(crm_action_t *action)
crm_trace("Sent CIB update (call ID %d) for timeout of action %d (%s on %s)",
rc, action->id, task_uuid, target);
- action->sent_update = TRUE;
+ crm__set_graph_action_flags(action, pcmk__graph_action_sent_update);
}
static gboolean
@@ -288,7 +286,7 @@ te_rsc_command(crm_graph_t * graph, crm_action_t * action)
CRM_ASSERT(action != NULL);
CRM_ASSERT(action->xml != NULL);
- action->executed = FALSE;
+ crm__clear_graph_action_flags(action, pcmk__graph_action_executed);
on_node = crm_element_value(action->xml, XML_LRM_ATTR_TARGET);
CRM_CHECK(on_node != NULL && strlen(on_node) != 0,
@@ -350,7 +348,7 @@ te_rsc_command(crm_graph_t * graph, crm_action_t * action)
free(counter);
free_xml(cmd);
- action->executed = TRUE;
+ crm__set_graph_action_flags(action, pcmk__graph_action_executed);
if (rc == FALSE) {
crm_err("Action %d failed: send", action->id);
@@ -358,13 +356,13 @@ te_rsc_command(crm_graph_t * graph, crm_action_t * action)
} else if (no_wait) {
crm_info("Action %d confirmed - no wait", action->id);
- action->confirmed = TRUE; /* Just mark confirmed.
+ crm__set_graph_action_flags(action, pcmk__graph_action_confirmed); /* Just mark confirmed.
* Don't bump the job count only to immediately decrement it
*/
- update_graph(transition_graph, action);
+ pcmk__update_graph(transition_graph, action);
trigger_graph();
- } else if (action->confirmed == TRUE) {
+ } else if (pcmk_is_set(action->flags, pcmk__graph_action_confirmed)) {
crm_debug("Action %d: %s %s on %s(timeout %dms) was already confirmed.",
action->id, task, task_uuid, on_node, action->timeout);
} else {
@@ -552,15 +550,15 @@ te_should_perform_action(crm_graph_t * graph, crm_action_t * action)
void
te_action_confirmed(crm_action_t *action, crm_graph_t *graph)
{
- if (action->confirmed == FALSE) {
+ if (!pcmk_is_set(action->flags, pcmk__graph_action_confirmed)) {
if ((action->type == action_type_rsc)
&& (crm_element_value(action->xml, XML_LRM_ATTR_TARGET) != NULL)) {
te_update_job_count(action, -1);
}
- action->confirmed = TRUE;
+ crm__set_graph_action_flags(action, pcmk__graph_action_confirmed);
}
if (graph) {
- update_graph(graph, action);
+ pcmk__update_graph(graph, action);
trigger_graph();
}
}
diff --git a/daemons/controld/controld_te_callbacks.c b/daemons/controld/controld_te_callbacks.c
index 4e3e4e6..228572d 100644
--- a/daemons/controld/controld_te_callbacks.c
+++ b/daemons/controld/controld_te_callbacks.c
@@ -241,13 +241,14 @@ process_resource_updates(const char *node, xmlNode *xml, xmlNode *change,
if (xml == NULL) {
return;
+ }
- } else if (strcmp((const char*)xml->name, XML_CIB_TAG_LRM) == 0) {
+ if (strcmp(TYPE(xml), XML_CIB_TAG_LRM) == 0) {
xml = first_named_child(xml, XML_LRM_TAG_RESOURCES);
- crm_trace("Got %p in %s", xml, XML_CIB_TAG_LRM);
+ CRM_CHECK(xml != NULL, return);
}
- CRM_ASSERT(strcmp((const char*)xml->name, XML_LRM_TAG_RESOURCES) == 0);
+ CRM_CHECK(strcmp(TYPE(xml), XML_LRM_TAG_RESOURCES) == 0, return);
/*
* Updates by, or in response to, TE actions will never contain updates
@@ -683,9 +684,10 @@ action_timer_callback(gpointer data)
(on_node? on_node : ""), (task? task : "unknown action"),
(via_node? via_node : "controller"),
timer->timeout + transition_graph->network_delay);
- print_action(LOG_ERR, "Aborting transition, action lost: ", timer->action);
+ pcmk__log_graph_action(LOG_ERR, timer->action);
+
+ crm__set_graph_action_flags(timer->action, pcmk__graph_action_failed);
- timer->action->failed = TRUE;
te_action_confirmed(timer->action, transition_graph);
abort_transition(INFINITY, tg_restart, "Action lost", NULL);
diff --git a/daemons/controld/controld_te_events.c b/daemons/controld/controld_te_events.c
index 03d84e5..36fd832 100644
--- a/daemons/controld/controld_te_events.c
+++ b/daemons/controld/controld_te_events.c
@@ -39,7 +39,7 @@ fail_incompletable_actions(crm_graph_t * graph, const char *down_node)
for (; gIter != NULL; gIter = gIter->next) {
synapse_t *synapse = (synapse_t *) gIter->data;
- if (synapse->confirmed || synapse->failed) {
+ if (pcmk_any_flags_set(synapse->flags, pcmk__synapse_confirmed|pcmk__synapse_failed)) {
/* We've already been here */
continue;
}
@@ -48,7 +48,7 @@ fail_incompletable_actions(crm_graph_t * graph, const char *down_node)
for (; gIter2 != NULL; gIter2 = gIter2->next) {
crm_action_t *action = (crm_action_t *) gIter2->data;
- if (action->type == action_type_pseudo || action->confirmed) {
+ if (action->type == action_type_pseudo || pcmk_is_set(action->flags, pcmk__graph_action_confirmed)) {
continue;
} else if (action->type == action_type_crm) {
const char *task = crm_element_value(action->xml, XML_LRM_ATTR_TASK);
@@ -68,13 +68,13 @@ fail_incompletable_actions(crm_graph_t * graph, const char *down_node)
}
if (pcmk__str_eq(target_uuid, down_node, pcmk__str_casei) || pcmk__str_eq(router_uuid, down_node, pcmk__str_casei)) {
- action->failed = TRUE;
- synapse->failed = TRUE;
+ crm__set_graph_action_flags(action, pcmk__graph_action_failed);
+ pcmk__set_synapse_flags(synapse, pcmk__synapse_failed);
last_action = action->xml;
stop_te_timer(action->timer);
- update_graph(graph, action);
+ pcmk__update_graph(graph, action);
- if (synapse->executed) {
+ if (pcmk_is_set(synapse->flags, pcmk__synapse_executed)) {
crm_notice("Action %d (%s) was pending on %s (offline)",
action->id, crm_element_value(action->xml, XML_LRM_ATTR_TASK_KEY), down_node);
} else {
@@ -304,7 +304,7 @@ match_down_event(const char *target)
gIter2 = gIter2->next) {
match = (crm_action_t*)gIter2->data;
- if (match->executed) {
+ if (pcmk_is_set(match->flags, pcmk__graph_action_confirmed)) {
xpath_ret = xpath_search(match->xml, xpath);
if (numXpathResults(xpath_ret) < 1) {
match = NULL;
@@ -357,7 +357,7 @@ process_graph_event(xmlNode *event, const char *event_node)
}
crm_element_value_int(event, XML_LRM_ATTR_OPSTATUS, &status);
- if (status == PCMK_LRM_OP_PENDING) {
+ if (status == PCMK_EXEC_PENDING) {
return;
}
@@ -398,7 +398,7 @@ process_graph_event(xmlNode *event, const char *event_node)
* scheduled in.
*/
- if (status == PCMK_LRM_OP_CANCELLED) {
+ if (status == PCMK_EXEC_CANCELLED) {
confirm_cancel_action(id, get_node_id(event));
goto bail;
}
@@ -424,7 +424,7 @@ process_graph_event(xmlNode *event, const char *event_node)
desc = "unknown";
abort_transition(INFINITY, tg_restart, "Unknown event", event);
- } else if (action->confirmed == TRUE) {
+ } else if (pcmk_is_set(action->flags, pcmk__graph_action_confirmed)) {
/* Nothing further needs to be done if the action has already been
* confirmed. This can happen e.g. when processing both an
* "xxx_last_0" or "xxx_last_failure_0" record as well as the main
@@ -443,13 +443,13 @@ process_graph_event(xmlNode *event, const char *event_node)
ignore_failures = TRUE;
} else if (rc != target_rc) {
- action->failed = TRUE;
+ crm__set_graph_action_flags(action, pcmk__graph_action_failed);
}
stop_te_timer(action->timer);
te_action_confirmed(action, transition_graph);
- if (action->failed) {
+ if (pcmk_is_set(action->flags, pcmk__graph_action_failed)) {
abort_transition(action->synapse->priority + 1, tg_restart,
"Event failed", event);
}
@@ -464,11 +464,11 @@ process_graph_event(xmlNode *event, const char *event_node)
uname = "unknown node";
}
- if (status == PCMK_LRM_OP_INVALID) {
+ if (status == PCMK_EXEC_INVALID) {
// We couldn't attempt the action
crm_info("Transition %d action %d (%s on %s): %s",
transition_num, action_num, id, uname,
- services_lrm_status_str(status));
+ pcmk_exec_status_str(status));
} else if (desc && update_failcount(event, event_node, rc, target_rc,
(transition_num == -1), FALSE)) {
diff --git a/daemons/controld/controld_te_utils.c b/daemons/controld/controld_te_utils.c
index 54d8127..de85fd6 100644
--- a/daemons/controld/controld_te_utils.c
+++ b/daemons/controld/controld_te_utils.c
@@ -1,5 +1,5 @@
/*
- * Copyright 2004-2020 the Pacemaker project contributors
+ * Copyright 2004-2021 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
@@ -34,8 +34,6 @@ stop_te_timer(crm_action_timer_t * timer)
gboolean
te_graph_trigger(gpointer user_data)
{
- enum transition_status graph_rc = -1;
-
if (transition_graph == NULL) {
crm_debug("Nothing to do");
return TRUE;
@@ -57,15 +55,13 @@ te_graph_trigger(gpointer user_data)
}
if (transition_graph->complete == FALSE) {
+ enum transition_status graph_rc;
int limit = transition_graph->batch_limit;
transition_graph->batch_limit = throttle_get_total_job_limit(limit);
- graph_rc = run_graph(transition_graph);
+ graph_rc = pcmk__execute_graph(transition_graph);
transition_graph->batch_limit = limit; /* Restore the configured value */
- /* significant overhead... */
- /* print_graph(LOG_TRACE, transition_graph); */
-
if (graph_rc == transition_active) {
crm_trace("Transition not yet complete");
return TRUE;
@@ -76,8 +72,9 @@ te_graph_trigger(gpointer user_data)
}
if (graph_rc != transition_complete) {
- crm_warn("Transition failed: %s", transition_status(graph_rc));
- print_graph(LOG_NOTICE, transition_graph);
+ crm_warn("Transition failed: %s",
+ pcmk__graph_status2text(graph_rc));
+ pcmk__log_graph(LOG_NOTICE, transition_graph);
}
}
@@ -135,6 +132,52 @@ abort_after_delay(int abort_priority, enum transition_action abort_action,
abort_timer.id = g_timeout_add(delay_ms, abort_timer_popped, NULL);
}
+static const char *
+abort2text(enum transition_action abort_action)
+{
+ switch (abort_action) {
+ case tg_done:
+ return "done";
+ case tg_stop:
+ return "stop";
+ case tg_restart:
+ return "restart";
+ case tg_shutdown:
+ return "shutdown";
+ }
+ return "unknown";
+}
+
+static bool
+update_abort_priority(crm_graph_t *graph, int priority,
+ enum transition_action action, const char *abort_reason)
+{
+ bool change = FALSE;
+
+ if (graph == NULL) {
+ return change;
+ }
+
+ if (graph->abort_priority < priority) {
+ crm_debug("Abort priority upgraded from %d to %d", graph->abort_priority, priority);
+ graph->abort_priority = priority;
+ if (graph->abort_reason != NULL) {
+ crm_debug("'%s' abort superseded by %s", graph->abort_reason, abort_reason);
+ }
+ graph->abort_reason = abort_reason;
+ change = TRUE;
+ }
+
+ if (graph->completion_action < action) {
+ crm_debug("Abort action %s superseded by %s: %s",
+ abort2text(graph->completion_action), abort2text(action), abort_reason);
+ graph->completion_action = action;
+ change = TRUE;
+ }
+
+ return change;
+}
+
void
abort_transition_graph(int abort_priority, enum transition_action abort_action,
const char *abort_text, xmlNode * reason, const char *fn, int line)
diff --git a/daemons/controld/controld_transition.c b/daemons/controld/controld_transition.c
index 14764ef..6a49377 100644
--- a/daemons/controld/controld_transition.c
+++ b/daemons/controld/controld_transition.c
@@ -1,5 +1,5 @@
/*
- * Copyright 2004-2020 the Pacemaker project contributors
+ * Copyright 2004-2021 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
@@ -25,7 +25,7 @@ global_cib_callback(const xmlNode * msg, int callid, int rc, xmlNode * output)
static crm_graph_t *
create_blank_graph(void)
{
- crm_graph_t *a_graph = unpack_graph(NULL, NULL);
+ crm_graph_t *a_graph = pcmk__unpack_graph(NULL, NULL);
a_graph->complete = TRUE;
a_graph->abort_reason = "DC Takeover";
@@ -44,7 +44,7 @@ do_te_control(long long action,
if (action & A_TE_STOP) {
if (transition_graph) {
- destroy_graph(transition_graph);
+ pcmk__free_graph(transition_graph);
transition_graph = NULL;
}
@@ -96,10 +96,10 @@ do_te_control(long long action,
}
if (init_ok) {
- set_graph_functions(&te_graph_fns);
+ pcmk__set_graph_functions(&te_graph_fns);
if (transition_graph) {
- destroy_graph(transition_graph);
+ pcmk__free_graph(transition_graph);
}
/* create a blank one */
@@ -132,7 +132,6 @@ do_te_invoke(long long action,
}
} else if (action & A_TE_HALT) {
- crm_debug("Halting the transition: %s", transition_graph->complete ? "inactive" : "active");
abort_transition(INFINITY, tg_stop, "Peer Halt", NULL);
if (transition_graph->complete == FALSE) {
crmd_fsa_stall(FALSE);
@@ -179,8 +178,8 @@ do_te_invoke(long long action,
crm_log_xml_err(input->msg, "Bad command");
return);
- destroy_graph(transition_graph);
- transition_graph = unpack_graph(graph_data, graph_input);
+ pcmk__free_graph(transition_graph);
+ transition_graph = pcmk__unpack_graph(graph_data, graph_input);
if (transition_graph == NULL) {
CRM_CHECK(transition_graph != NULL,);
transition_graph = create_blank_graph();
@@ -208,7 +207,7 @@ do_te_invoke(long long action,
}
trigger_graph();
- print_graph(LOG_TRACE, transition_graph);
+ pcmk__log_graph(LOG_TRACE, transition_graph);
if (graph_data != input->xml) {
free_xml(graph_data);
diff --git a/daemons/controld/controld_transition.h b/daemons/controld/controld_transition.h
index 698c231..cb6e752 100644
--- a/daemons/controld/controld_transition.h
+++ b/daemons/controld/controld_transition.h
@@ -25,7 +25,7 @@ void process_graph_event(xmlNode *event, const char *event_node);
/* utils */
crm_action_t *controld_get_action(int id);
extern gboolean stop_te_timer(crm_action_timer_t * timer);
-extern const char *get_rsc_state(const char *task, enum op_status status);
+const char *get_rsc_state(const char *task, enum pcmk_exec_status status);
/* unpack */
extern gboolean process_te_message(xmlNode * msg, xmlNode * xml_data);
diff --git a/daemons/controld/pacemaker-controld.h b/daemons/controld/pacemaker-controld.h
index 7485d95..3119cb2 100644
--- a/daemons/controld/pacemaker-controld.h
+++ b/daemons/controld/pacemaker-controld.h
@@ -34,5 +34,6 @@ void controld_remove_voter(const char *uname);
void controld_election_fini(void);
void controld_set_election_period(const char *value);
void controld_stop_election_timer(void);
+void controld_disconnect_cib_manager(void);
#endif
diff --git a/daemons/execd/Makefile.am b/daemons/execd/Makefile.am
index 1741468..c851607 100644
--- a/daemons/execd/Makefile.am
+++ b/daemons/execd/Makefile.am
@@ -8,6 +8,7 @@
#
include $(top_srcdir)/mk/common.mk
+include $(top_srcdir)/mk/man.mk
halibdir = $(CRM_DAEMON_DIR)
@@ -23,14 +24,14 @@ pacemaker_execd_SOURCES = pacemaker-execd.c execd_commands.c \
execd_alerts.c
if BUILD_REMOTE
-initdir = $(INITDIR)
-init_SCRIPTS = pacemaker_remote
sbin_PROGRAMS = pacemaker-remoted
if BUILD_SYSTEMD
systemdsystemunit_DATA = pacemaker_remote.service
+else
+initdir = $(INITDIR)
+init_SCRIPTS = pacemaker_remote
endif
-
pacemaker_remoted_CPPFLAGS = -DPCMK__COMPILE_REMOTE $(AM_CPPFLAGS)
pacemaker_remoted_CFLAGS = $(CFLAGS_HARDENED_EXE)
diff --git a/daemons/execd/cts-exec-helper.c b/daemons/execd/cts-exec-helper.c
index d03d36b..ed51b76 100644
--- a/daemons/execd/cts-exec-helper.c
+++ b/daemons/execd/cts-exec-helper.c
@@ -19,6 +19,7 @@
#include <crm/pengine/status.h>
#include <crm/pengine/internal.h>
#include <crm/cib.h>
+#include <crm/cib/internal.h>
#include <crm/lrmd.h>
static pcmk__cli_option_t long_options[] = {
@@ -121,7 +122,6 @@ static pcmk__cli_option_t long_options[] = {
{ 0, 0, 0, 0 }
};
-static cib_t *cib_conn = NULL;
static int exec_call_id = 0;
static int exec_call_opts = 0;
static gboolean start_test(gpointer user_data);
@@ -169,8 +169,8 @@ test_exit(crm_exit_t exit_code)
lrmd_event_type2str(event->type), \
event->rsc_id, \
event->op_type ? event->op_type : "none", \
- services_ocf_exitcode_str(event->rc), \
- services_lrm_status_str(event->op_status)); \
+ services_ocf_exitcode_str(event->rc), \
+ pcmk_exec_status_str(event->op_status)); \
crm_info("%s", event_buf_v0);
static void
@@ -196,7 +196,8 @@ read_events(lrmd_event_data_t * event)
print_result(printf("API-CALL SUCCESSFUL for 'exec'\n"));
} else {
print_result(printf("API-CALL FAILURE for 'exec', rc:%d lrmd_op_status:%s\n",
- event->rc, services_lrm_status_str(event->op_status)));
+ event->rc,
+ pcmk_exec_status_str(event->op_status)));
test_exit(CRM_EX_ERROR);
}
@@ -442,22 +443,10 @@ generate_params(void)
}
pe__set_working_set_flags(data_set, pe_flag_no_counts|pe_flag_no_compat);
- cib_conn = cib_new();
- rc = cib_conn->cmds->signon(cib_conn, crm_system_name, cib_query);
- if (rc != pcmk_ok) {
- crm_err("Could not connect to the CIB manager: %s", pcmk_strerror(rc));
- rc = -1;
- goto param_gen_bail;
- }
+ rc = cib__signon_query(NULL, &cib_xml_copy);
- rc = cib_conn->cmds->query(cib_conn, NULL, &cib_xml_copy, cib_scope_local | cib_sync_call);
- if (rc != pcmk_ok) {
- crm_err("Error retrieving cib copy: %s (%d)", pcmk_strerror(rc), rc);
- goto param_gen_bail;
-
- } else if (cib_xml_copy == NULL) {
- rc = -ENODATA;
- crm_err("Error retrieving cib copy: %s (%d)", pcmk_strerror(rc), rc);
+ if (rc != pcmk_rc_ok) {
+ crm_err("CIB query failed: %s", pcmk_rc_str(rc));
goto param_gen_bail;
}
@@ -680,11 +669,6 @@ main(int argc, char **argv)
mainloop = g_main_loop_new(NULL, FALSE);
g_main_loop_run(mainloop);
- if (cib_conn != NULL) {
- cib_conn->cmds->signoff(cib_conn);
- cib_delete(cib_conn);
- }
-
test_exit(CRM_EX_OK);
return CRM_EX_OK;
}
diff --git a/daemons/execd/execd_alerts.c b/daemons/execd/execd_alerts.c
index baace91..f9ba1f1 100644
--- a/daemons/execd/execd_alerts.c
+++ b/daemons/execd/execd_alerts.c
@@ -110,18 +110,27 @@ process_lrmd_alert_exec(pcmk__client_t *client, uint32_t id, xmlNode *request)
++alert_sequence_no);
cb_data = calloc(1, sizeof(struct alert_cb_s));
- CRM_CHECK(cb_data != NULL,
- rc = -ENOMEM; goto err);
+ if (cb_data == NULL) {
+ rc = -errno;
+ goto err;
+ }
/* coverity[deref_ptr] False Positive */
cb_data->client_id = strdup(client->id);
- CRM_CHECK(cb_data->client_id != NULL,
- rc = -ENOMEM; goto err);
+ if (cb_data->client_id == NULL) {
+ rc = -errno;
+ goto err;
+ }
crm_element_value_int(request, F_LRMD_CALLID, &(cb_data->call_id));
action = services_alert_create(alert_id, alert_path, alert_timeout, params,
alert_sequence_no, cb_data);
+ if (action->rc != PCMK_OCF_UNKNOWN) {
+ rc = -E2BIG;
+ goto err;
+ }
+
rc = services_action_user(action, CRM_DAEMON_USER);
if (rc < 0) {
goto err;
@@ -140,9 +149,7 @@ err:
}
free(cb_data);
}
- if (action) {
- services_action_free(action);
- }
+ services_action_free(action);
return rc;
}
diff --git a/daemons/execd/execd_commands.c b/daemons/execd/execd_commands.c
index aa150d0..999b1be 100644
--- a/daemons/execd/execd_commands.c
+++ b/daemons/execd/execd_commands.c
@@ -30,8 +30,6 @@
#include "pacemaker-execd.h"
-#define EXIT_REASON_MAX_LEN 128
-
GHashTable *rsc_list = NULL;
typedef struct lrmd_cmd_s {
@@ -41,8 +39,6 @@ typedef struct lrmd_cmd_s {
int timeout_orig;
int call_id;
- int exec_rc;
- int lrmd_op_status;
int call_opts;
/* Timer ids, must be removed on cmd destruction. */
@@ -58,10 +54,10 @@ typedef struct lrmd_cmd_s {
char *rsc_id;
char *action;
char *real_action;
- char *exit_reason;
- char *output;
char *userdata_str;
+ pcmk__action_result_t result;
+
/* We can track operation queue time and run time, to be saved with the CIB
* resource history (and displayed in cluster status). We need
* high-resolution monotonic time for this purpose, so we use
@@ -201,30 +197,52 @@ action_matches(lrmd_cmd_t *cmd, const char *action, guint interval_ms)
&& pcmk__str_eq(cmd->action, action, pcmk__str_casei);
}
+/*!
+ * \internal
+ * \brief Log the result of an asynchronous command
+ *
+ * \param[in] cmd Command to log result for
+ * \param[in] exec_time_ms Execution time in milliseconds, if known
+ * \param[in] queue_time_ms Queue time in milliseconds, if known
+ */
static void
-log_finished(lrmd_cmd_t * cmd, int exec_time, int queue_time)
+log_finished(lrmd_cmd_t *cmd, int exec_time_ms, int queue_time_ms)
{
- char pid_str[32] = { 0, };
int log_level = LOG_INFO;
-
- if (cmd->last_pid) {
- snprintf(pid_str, 32, "%d", cmd->last_pid);
- }
+ GString *str = g_string_sized_new(100); // reasonable starting size
if (pcmk__str_eq(cmd->action, "monitor", pcmk__str_casei)) {
log_level = LOG_DEBUG;
}
+
+ g_string_printf(str, "%s %s (call %d",
+ cmd->rsc_id, cmd->action, cmd->call_id);
+ if (cmd->last_pid != 0) {
+ g_string_append_printf(str, ", PID %d", cmd->last_pid);
+ }
+ if (cmd->result.execution_status == PCMK_EXEC_DONE) {
+ g_string_append_printf(str, ") exited with status %d",
+ cmd->result.exit_status);
+ } else {
+ g_string_append_printf(str, ") could not be executed: %s",
+ pcmk_exec_status_str(cmd->result.execution_status));
+ }
+ if (cmd->result.exit_reason != NULL) {
+ g_string_append_printf(str, " (%s)", cmd->result.exit_reason);
+ }
+
#ifdef PCMK__TIME_USE_CGT
- do_crm_log(log_level, "%s %s (call %d%s%s) exited with status %d"
- " (execution time %dms, queue time %dms)",
- cmd->rsc_id, cmd->action, cmd->call_id,
- (cmd->last_pid? ", PID " : ""), pid_str, cmd->exec_rc,
- exec_time, queue_time);
-#else
- do_crm_log(log_level, "%s %s (call %d%s%s) exited with status %d",
- cmd->rsc_id, cmd->action, cmd->call_id,
- (cmd->last_pid? ", PID " : ""), pid_str, cmd->exec_rc);
+ g_string_append_printf(str, " (execution time %s",
+ pcmk__readable_interval(exec_time_ms));
+ if (queue_time_ms > 0) {
+ g_string_append_printf(str, " after being queued %s",
+ pcmk__readable_interval(queue_time_ms));
+ }
+ g_string_append(str, ")");
#endif
+
+ do_crm_log(log_level, "%s", str->str);
+ g_string_free(str, TRUE);
}
static void
@@ -329,13 +347,12 @@ free_lrmd_cmd(lrmd_cmd_t * cmd)
if (cmd->params) {
g_hash_table_destroy(cmd->params);
}
+ pcmk__reset_result(&(cmd->result));
free(cmd->origin);
free(cmd->action);
free(cmd->real_action);
free(cmd->userdata_str);
free(cmd->rsc_id);
- free(cmd->output);
- free(cmd->exit_reason);
free(cmd->client_id);
free(cmd);
}
@@ -435,7 +452,7 @@ merge_recurring_duplicate(lrmd_rsc_t * rsc, lrmd_cmd_t * cmd)
*/
if (pcmk__str_eq(rsc->class, PCMK_RESOURCE_CLASS_STONITH,
pcmk__str_casei)
- && (dup->lrmd_op_status == PCMK_LRM_OP_CANCELLED)) {
+ && (dup->result.execution_status == PCMK_EXEC_CANCELLED)) {
return false;
}
@@ -564,22 +581,21 @@ static void
send_cmd_complete_notify(lrmd_cmd_t * cmd)
{
xmlNode *notify = NULL;
+ int exec_time = 0;
+ int queue_time = 0;
#ifdef PCMK__TIME_USE_CGT
- int exec_time = time_diff_ms(NULL, &(cmd->t_run));
- int queue_time = time_diff_ms(&cmd->t_run, &(cmd->t_queue));
-
- log_finished(cmd, exec_time, queue_time);
-#else
- log_finished(cmd, 0, 0);
+ exec_time = time_diff_ms(NULL, &(cmd->t_run));
+ queue_time = time_diff_ms(&cmd->t_run, &(cmd->t_queue));
#endif
+ log_finished(cmd, exec_time, queue_time);
/* if the first notify result for a cmd has already been sent earlier, and the
* the option to only send notifies on result changes is set. Check to see
* if the last result is the same as the new one. If so, suppress this update */
if (cmd->first_notify_sent && (cmd->call_opts & lrmd_opt_notify_changes_only)) {
- if (cmd->last_notify_rc == cmd->exec_rc &&
- cmd->last_notify_op_status == cmd->lrmd_op_status) {
+ if ((cmd->last_notify_rc == cmd->result.exit_status) &&
+ (cmd->last_notify_op_status == cmd->result.execution_status)) {
/* only send changes */
return;
@@ -588,8 +604,8 @@ send_cmd_complete_notify(lrmd_cmd_t * cmd)
}
cmd->first_notify_sent = true;
- cmd->last_notify_rc = cmd->exec_rc;
- cmd->last_notify_op_status = cmd->lrmd_op_status;
+ cmd->last_notify_rc = cmd->result.exit_status;
+ cmd->last_notify_op_status = cmd->result.execution_status;
notify = create_xml_node(NULL, T_LRMD_NOTIFY);
@@ -597,8 +613,8 @@ send_cmd_complete_notify(lrmd_cmd_t * cmd)
crm_xml_add_int(notify, F_LRMD_TIMEOUT, cmd->timeout);
crm_xml_add_ms(notify, F_LRMD_RSC_INTERVAL, cmd->interval_ms);
crm_xml_add_int(notify, F_LRMD_RSC_START_DELAY, cmd->start_delay);
- crm_xml_add_int(notify, F_LRMD_EXEC_RC, cmd->exec_rc);
- crm_xml_add_int(notify, F_LRMD_OP_STATUS, cmd->lrmd_op_status);
+ crm_xml_add_int(notify, F_LRMD_EXEC_RC, cmd->result.exit_status);
+ crm_xml_add_int(notify, F_LRMD_OP_STATUS, cmd->result.execution_status);
crm_xml_add_int(notify, F_LRMD_CALLID, cmd->call_id);
crm_xml_add_int(notify, F_LRMD_RSC_DELETED, cmd->rsc_deleted);
@@ -619,8 +635,14 @@ send_cmd_complete_notify(lrmd_cmd_t * cmd)
crm_xml_add(notify, F_LRMD_RSC_ACTION, cmd->action);
}
crm_xml_add(notify, F_LRMD_RSC_USERDATA_STR, cmd->userdata_str);
- crm_xml_add(notify, F_LRMD_RSC_OUTPUT, cmd->output);
- crm_xml_add(notify, F_LRMD_RSC_EXIT_REASON, cmd->exit_reason);
+ crm_xml_add(notify, F_LRMD_RSC_EXIT_REASON, cmd->result.exit_reason);
+
+ if (cmd->result.action_stderr != NULL) {
+ crm_xml_add(notify, F_LRMD_RSC_OUTPUT, cmd->result.action_stderr);
+
+ } else if (cmd->result.action_stdout != NULL) {
+ crm_xml_add(notify, F_LRMD_RSC_OUTPUT, cmd->result.action_stdout);
+ }
if (cmd->params) {
char *key = NULL;
@@ -675,17 +697,15 @@ send_generic_notify(int rc, xmlNode * request)
static void
cmd_reset(lrmd_cmd_t * cmd)
{
- cmd->lrmd_op_status = 0;
cmd->last_pid = 0;
#ifdef PCMK__TIME_USE_CGT
memset(&cmd->t_run, 0, sizeof(cmd->t_run));
memset(&cmd->t_queue, 0, sizeof(cmd->t_queue));
#endif
cmd->epoch_last_run = 0;
- free(cmd->exit_reason);
- cmd->exit_reason = NULL;
- free(cmd->output);
- cmd->output = NULL;
+
+ pcmk__reset_result(&(cmd->result));
+ cmd->result.execution_status = PCMK_EXEC_DONE;
}
static void
@@ -708,7 +728,9 @@ cmd_finalize(lrmd_cmd_t * cmd, lrmd_rsc_t * rsc)
send_cmd_complete_notify(cmd);
- if (cmd->interval_ms && (cmd->lrmd_op_status == PCMK_LRM_OP_CANCELLED)) {
+ if ((cmd->interval_ms != 0)
+ && (cmd->result.execution_status == PCMK_EXEC_CANCELLED)) {
+
if (rsc) {
rsc->recurring_ops = g_list_remove(rsc->recurring_ops, cmd);
rsc->pending_ops = g_list_remove(rsc->pending_ops, cmd);
@@ -726,22 +748,6 @@ cmd_finalize(lrmd_cmd_t * cmd, lrmd_rsc_t * rsc)
}
static int
-ocf2uniform_rc(int rc)
-{
- switch (rc) {
- case PCMK_OCF_DEGRADED:
- case PCMK_OCF_DEGRADED_PROMOTED:
- break;
- default:
- if (rc < 0 || rc > PCMK_OCF_FAILED_PROMOTED) {
- return PCMK_OCF_UNKNOWN_ERROR;
- }
- }
-
- return rc;
-}
-
-static int
stonith2uniform_rc(const char *action, int rc)
{
switch (rc) {
@@ -766,11 +772,6 @@ stonith2uniform_rc(const char *action, int rc)
rc = PCMK_OCF_UNIMPLEMENT_FEATURE;
break;
- case -ETIME:
- case -ETIMEDOUT:
- rc = PCMK_OCF_TIMEOUT;
- break;
-
default:
rc = PCMK_OCF_UNKNOWN_ERROR;
break;
@@ -778,58 +779,21 @@ stonith2uniform_rc(const char *action, int rc)
return rc;
}
-#if SUPPORT_NAGIOS
static int
-nagios2uniform_rc(const char *action, int rc)
+action_get_uniform_rc(svc_action_t *action)
{
- if (rc < 0) {
- return PCMK_OCF_UNKNOWN_ERROR;
- }
-
- switch (rc) {
- case NAGIOS_STATE_OK:
- return PCMK_OCF_OK;
- case NAGIOS_INSUFFICIENT_PRIV:
- return PCMK_OCF_INSUFFICIENT_PRIV;
- case NAGIOS_NOT_INSTALLED:
- return PCMK_OCF_NOT_INSTALLED;
- case NAGIOS_STATE_WARNING:
- case NAGIOS_STATE_CRITICAL:
- case NAGIOS_STATE_UNKNOWN:
- case NAGIOS_STATE_DEPENDENT:
- default:
- return PCMK_OCF_UNKNOWN_ERROR;
- }
-
- return PCMK_OCF_UNKNOWN_ERROR;
-}
-#endif
+ lrmd_cmd_t *cmd = action->cb_data;
-static int
-get_uniform_rc(const char *standard, const char *action, int rc)
-{
- if (pcmk__str_eq(standard, PCMK_RESOURCE_CLASS_OCF, pcmk__str_casei)) {
- return ocf2uniform_rc(rc);
- } else if (pcmk__str_eq(standard, PCMK_RESOURCE_CLASS_STONITH, pcmk__str_casei)) {
- return stonith2uniform_rc(action, rc);
- } else if (pcmk__str_eq(standard, PCMK_RESOURCE_CLASS_SYSTEMD, pcmk__str_casei)) {
- return rc;
- } else if (pcmk__str_eq(standard, PCMK_RESOURCE_CLASS_UPSTART, pcmk__str_casei)) {
- return rc;
-#if SUPPORT_NAGIOS
- } else if (pcmk__str_eq(standard, PCMK_RESOURCE_CLASS_NAGIOS, pcmk__str_casei)) {
- return nagios2uniform_rc(action, rc);
-#endif
+ if (pcmk__str_eq(action->standard, PCMK_RESOURCE_CLASS_STONITH,
+ pcmk__str_casei)) {
+ return stonith2uniform_rc(cmd->action, action->rc);
} else {
- return services_get_ocf_exitcode(action, rc);
- }
-}
+ enum ocf_exitcode code = services_result2ocf(action->standard,
+ cmd->action, action->rc);
-static int
-action_get_uniform_rc(svc_action_t * action)
-{
- lrmd_cmd_t *cmd = action->cb_data;
- return get_uniform_rc(action->standard, cmd->action, action->rc);
+ // Cast variable instead of function return to keep compilers happy
+ return (int) code;
+ }
}
struct notify_new_client_data {
@@ -861,41 +825,6 @@ notify_of_new_client(pcmk__client_t *new_client)
free_xml(data.notify);
}
-static char *
-parse_exit_reason(const char *output)
-{
- const char *cur = NULL;
- const char *last = NULL;
- static int cookie_len = 0;
- char *eol = NULL;
- size_t reason_len = EXIT_REASON_MAX_LEN;
-
- if (output == NULL) {
- return NULL;
- }
-
- if (!cookie_len) {
- cookie_len = strlen(PCMK_OCF_REASON_PREFIX);
- }
-
- cur = strstr(output, PCMK_OCF_REASON_PREFIX);
- for (; cur != NULL; cur = strstr(cur, PCMK_OCF_REASON_PREFIX)) {
- /* skip over the cookie delimiter string */
- cur += cookie_len;
- last = cur;
- }
- if (last == NULL) {
- return NULL;
- }
-
- // Truncate everything after a new line, and limit reason string size
- eol = strchr(last, '\n');
- if (eol) {
- reason_len = QB_MIN(reason_len, eol - last);
- }
- return strndup(last, reason_len);
-}
-
void
client_disconnect_cleanup(const char *client_id)
{
@@ -931,14 +860,14 @@ action_complete(svc_action_t * action)
}
#ifdef PCMK__TIME_USE_CGT
- if (cmd->exec_rc != action->rc) {
+ if (cmd->result.exit_status != action->rc) {
cmd->epoch_rcchange = time(NULL);
}
#endif
cmd->last_pid = action->pid;
- cmd->exec_rc = action_get_uniform_rc(action);
- cmd->lrmd_op_status = action->status;
+ pcmk__set_result(&(cmd->result), action_get_uniform_rc(action),
+ action->status, services__exit_reason(action));
rsc = cmd->rsc_id ? g_hash_table_lookup(rsc_list, cmd->rsc_id) : NULL;
#ifdef PCMK__TIME_USE_CGT
@@ -949,7 +878,7 @@ action_complete(svc_action_t * action)
}
if (pcmk__str_eq(rclass, PCMK_RESOURCE_CLASS_SYSTEMD, pcmk__str_casei)) {
- if ((cmd->exec_rc == PCMK_OCF_OK)
+ if ((cmd->result.exit_status == PCMK_OCF_OK)
&& pcmk__strcase_any_of(cmd->action, "start", "stop", NULL)) {
/* systemd returns from start and stop actions after the action
* begins, not after it completes. We have to jump through a few
@@ -962,11 +891,10 @@ action_complete(svc_action_t * action)
} else if (cmd->real_action != NULL) {
// This is follow-up monitor to check whether start/stop completed
- if ((cmd->lrmd_op_status == PCMK_LRM_OP_DONE)
- && (cmd->exec_rc == PCMK_OCF_PENDING)) {
+ if (cmd->result.execution_status == PCMK_EXEC_PENDING) {
goagain = true;
- } else if ((cmd->exec_rc == PCMK_OCF_OK)
+ } else if ((cmd->result.exit_status == PCMK_OCF_OK)
&& pcmk__str_eq(cmd->real_action, "stop", pcmk__str_casei)) {
goagain = true;
@@ -977,18 +905,18 @@ action_complete(svc_action_t * action)
crm_debug("%s systemd %s is now complete (elapsed=%dms, "
"remaining=%dms): %s (%d)",
cmd->rsc_id, cmd->real_action, time_sum, timeout_left,
- services_ocf_exitcode_str(cmd->exec_rc),
- cmd->exec_rc);
+ services_ocf_exitcode_str(cmd->result.exit_status),
+ cmd->result.exit_status);
cmd_original_times(cmd);
// Monitors may return "not running", but start/stop shouldn't
- if ((cmd->lrmd_op_status == PCMK_LRM_OP_DONE)
- && (cmd->exec_rc == PCMK_OCF_NOT_RUNNING)) {
+ if ((cmd->result.execution_status == PCMK_EXEC_DONE)
+ && (cmd->result.exit_status == PCMK_OCF_NOT_RUNNING)) {
if (pcmk__str_eq(cmd->real_action, "start", pcmk__str_casei)) {
- cmd->exec_rc = PCMK_OCF_UNKNOWN_ERROR;
+ cmd->result.exit_status = PCMK_OCF_UNKNOWN_ERROR;
} else if (pcmk__str_eq(cmd->real_action, "stop", pcmk__str_casei)) {
- cmd->exec_rc = PCMK_OCF_OK;
+ cmd->result.exit_status = PCMK_OCF_OK;
}
}
}
@@ -999,11 +927,12 @@ action_complete(svc_action_t * action)
#if SUPPORT_NAGIOS
if (rsc && pcmk__str_eq(rsc->class, PCMK_RESOURCE_CLASS_NAGIOS, pcmk__str_casei)) {
if (action_matches(cmd, "monitor", 0)
- && (cmd->exec_rc == PCMK_OCF_OK)) {
+ && (cmd->result.exit_status == PCMK_OCF_OK)) {
/* Successfully executed --version for the nagios plugin */
- cmd->exec_rc = PCMK_OCF_NOT_RUNNING;
+ cmd->result.exit_status = PCMK_OCF_NOT_RUNNING;
- } else if (pcmk__str_eq(cmd->action, "start", pcmk__str_casei) && cmd->exec_rc != PCMK_OCF_OK) {
+ } else if (pcmk__str_eq(cmd->action, "start", pcmk__str_casei)
+ && (cmd->result.exit_status != PCMK_OCF_OK)) {
#ifdef PCMK__TIME_USE_CGT
goagain = true;
#endif
@@ -1026,17 +955,20 @@ action_complete(svc_action_t * action)
cmd->start_delay = delay;
cmd->timeout = timeout_left;
- if(cmd->exec_rc == PCMK_OCF_OK) {
+ if (cmd->result.exit_status == PCMK_OCF_OK) {
crm_debug("%s %s may still be in progress: re-scheduling (elapsed=%dms, remaining=%dms, start_delay=%dms)",
cmd->rsc_id, cmd->real_action, time_sum, timeout_left, delay);
- } else if(cmd->exec_rc == PCMK_OCF_PENDING) {
+ } else if (cmd->result.execution_status == PCMK_EXEC_PENDING) {
crm_info("%s %s is still in progress: re-scheduling (elapsed=%dms, remaining=%dms, start_delay=%dms)",
cmd->rsc_id, cmd->action, time_sum, timeout_left, delay);
} else {
crm_notice("%s %s failed '%s' (%d): re-scheduling (elapsed=%dms, remaining=%dms, start_delay=%dms)",
- cmd->rsc_id, cmd->action, services_ocf_exitcode_str(cmd->exec_rc), cmd->exec_rc, time_sum, timeout_left, delay);
+ cmd->rsc_id, cmd->action,
+ services_ocf_exitcode_str(cmd->result.exit_status),
+ cmd->result.exit_status, time_sum, timeout_left,
+ delay);
}
cmd_reset(cmd);
@@ -1050,22 +982,20 @@ action_complete(svc_action_t * action)
} else {
crm_notice("Giving up on %s %s (rc=%d): timeout (elapsed=%dms, remaining=%dms)",
- cmd->rsc_id, cmd->real_action?cmd->real_action:cmd->action, cmd->exec_rc, time_sum, timeout_left);
- cmd->lrmd_op_status = PCMK_LRM_OP_TIMEOUT;
- cmd->exec_rc = PCMK_OCF_TIMEOUT;
+ cmd->rsc_id,
+ (cmd->real_action? cmd->real_action : cmd->action),
+ cmd->result.exit_status, time_sum, timeout_left);
+ pcmk__set_result(&(cmd->result), PCMK_OCF_UNKNOWN_ERROR,
+ PCMK_EXEC_TIMEOUT,
+ "Investigate reason for timeout, and adjust "
+ "configured operation timeout if necessary");
cmd_original_times(cmd);
}
}
#endif
- if (action->stderr_data) {
- cmd->output = strdup(action->stderr_data);
- cmd->exit_reason = parse_exit_reason(action->stderr_data);
-
- } else if (action->stdout_data) {
- cmd->output = strdup(action->stdout_data);
- }
-
+ pcmk__set_result_output(&(cmd->result), services__grab_stdout(action),
+ services__grab_stderr(action));
cmd_finalize(cmd, rsc);
}
@@ -1086,7 +1016,7 @@ action_complete(svc_action_t * action)
static int
stonith_rc2status(const char *action, guint interval_ms, int rc)
{
- int status = PCMK_LRM_OP_DONE;
+ int status = PCMK_EXEC_DONE;
switch (rc) {
case pcmk_ok:
@@ -1094,23 +1024,23 @@ stonith_rc2status(const char *action, guint interval_ms, int rc)
case -EOPNOTSUPP:
case -EPROTONOSUPPORT:
- status = PCMK_LRM_OP_NOTSUPPORTED;
+ status = PCMK_EXEC_NOT_SUPPORTED;
break;
case -ETIME:
case -ETIMEDOUT:
- status = PCMK_LRM_OP_TIMEOUT;
+ status = PCMK_EXEC_TIMEOUT;
break;
case -ENOTCONN:
case -ECOMM:
// Couldn't talk to fencer
- status = PCMK_LRM_OP_ERROR;
+ status = PCMK_EXEC_ERROR;
break;
case -ENODEV:
// The device is not registered with the fencer
- status = PCMK_LRM_OP_ERROR;
+ status = PCMK_EXEC_ERROR;
break;
default:
@@ -1125,18 +1055,18 @@ stonith_action_complete(lrmd_cmd_t * cmd, int rc)
// This can be NULL if resource was removed before command completed
lrmd_rsc_t *rsc = g_hash_table_lookup(rsc_list, cmd->rsc_id);
- cmd->exec_rc = stonith2uniform_rc(cmd->action, rc);
+ cmd->result.exit_status = stonith2uniform_rc(cmd->action, rc);
/* This function may be called with status already set to cancelled, if a
* pending action was aborted. Otherwise, we need to determine status from
* the fencer return code.
*/
- if (cmd->lrmd_op_status != PCMK_LRM_OP_CANCELLED) {
- cmd->lrmd_op_status = stonith_rc2status(cmd->action, cmd->interval_ms,
- rc);
+ if (cmd->result.execution_status != PCMK_EXEC_CANCELLED) {
+ cmd->result.execution_status = stonith_rc2status(cmd->action,
+ cmd->interval_ms, rc);
// Certain successful actions change the known state of the resource
- if (rsc && (cmd->exec_rc == PCMK_OCF_OK)) {
+ if ((rsc != NULL) && (cmd->result.exit_status == PCMK_OCF_OK)) {
if (pcmk__str_eq(cmd->action, "start", pcmk__str_casei)) {
rsc->st_probe_rc = pcmk_ok; // maps to PCMK_OCF_OK
} else if (pcmk__str_eq(cmd->action, "stop", pcmk__str_casei)) {
@@ -1145,17 +1075,22 @@ stonith_action_complete(lrmd_cmd_t * cmd, int rc)
}
}
+ // Give the user more detail than an OCF code
+ if (rc != -pcmk_err_generic) {
+ cmd->result.exit_reason = strdup(pcmk_strerror(rc));
+ }
+
/* The recurring timer should not be running at this point in any case, but
* as a failsafe, stop it if it is.
*/
stop_recurring_timer(cmd);
/* Reschedule this command if appropriate. If a recurring command is *not*
- * rescheduled, its status must be PCMK_LRM_OP_CANCELLED, otherwise it will
+ * rescheduled, its status must be PCMK_EXEC_CANCELLED, otherwise it will
* not be removed from recurring_ops by cmd_finalize().
*/
if (rsc && (cmd->interval_ms > 0)
- && (cmd->lrmd_op_status != PCMK_LRM_OP_CANCELLED)) {
+ && (cmd->result.execution_status != PCMK_EXEC_CANCELLED)) {
start_recurring_timer(cmd);
}
@@ -1368,7 +1303,7 @@ lrmd_rsc_execute_service_lib(lrmd_rsc_t * rsc, lrmd_cmd_t * cmd)
if (pcmk__str_eq(rsc->class, PCMK_RESOURCE_CLASS_NAGIOS, pcmk__str_casei)
&& pcmk__str_eq(cmd->action, "stop", pcmk__str_casei)) {
- cmd->exec_rc = PCMK_OCF_OK;
+ cmd->result.exit_status = PCMK_OCF_OK;
goto exec_done;
}
#endif
@@ -1381,38 +1316,31 @@ lrmd_rsc_execute_service_lib(lrmd_rsc_t * rsc, lrmd_cmd_t * cmd)
cmd->interval_ms, cmd->timeout,
params_copy, cmd->service_flags);
- if (!action) {
- crm_err("Failed to create action, action:%s on resource %s", cmd->action, rsc->rsc_id);
- cmd->exec_rc = PCMK_OCF_UNKNOWN_ERROR;
- cmd->lrmd_op_status = PCMK_LRM_OP_ERROR;
+ if (action == NULL) {
+ pcmk__set_result(&(cmd->result), PCMK_OCF_UNKNOWN_ERROR,
+ PCMK_EXEC_ERROR, strerror(ENOMEM));
goto exec_done;
}
- if (action->rc != 0) {
- cmd->exec_rc = action->rc;
- cmd->lrmd_op_status = action->status;
+ if (action->rc != PCMK_OCF_UNKNOWN) {
+ pcmk__set_result(&(cmd->result), action->rc, action->status,
+ services__exit_reason(action));
services_action_free(action);
goto exec_done;
}
action->cb_data = cmd;
- /* 'cmd' may not be valid after this point if
- * services_action_async() returned TRUE
- *
- * Upstart and systemd both synchronously determine monitor/status
- * results and call action_complete (which may free 'cmd') if necessary.
- */
if (services_action_async(action, action_complete)) {
+ /* When services_action_async() returns TRUE, the callback might have
+ * been called -- in this case action_complete(), which might free cmd,
+ * so cmd cannot be used here.
+ */
return TRUE;
}
- cmd->exec_rc = action->rc;
- if(action->status != PCMK_LRM_OP_DONE) {
- cmd->lrmd_op_status = action->status;
- } else {
- cmd->lrmd_op_status = PCMK_LRM_OP_ERROR;
- }
+ pcmk__set_result(&(cmd->result), action->rc, action->status,
+ services__exit_reason(action));
services_action_free(action);
action = NULL;
@@ -1493,7 +1421,7 @@ free_rsc(gpointer data)
lrmd_cmd_t *cmd = gIter->data;
/* command was never executed */
- cmd->lrmd_op_status = PCMK_LRM_OP_CANCELLED;
+ cmd->result.execution_status = PCMK_EXEC_CANCELLED;
cmd_finalize(cmd, NULL);
gIter = next;
@@ -1507,7 +1435,7 @@ free_rsc(gpointer data)
lrmd_cmd_t *cmd = gIter->data;
if (is_stonith) {
- cmd->lrmd_op_status = PCMK_LRM_OP_CANCELLED;
+ cmd->result.execution_status = PCMK_EXEC_CANCELLED;
/* If a stonith command is in-flight, just mark it as cancelled;
* it is not safe to finalize/free the cmd until the stonith api
* says it has either completed or timed out.
@@ -1709,7 +1637,7 @@ cancel_op(const char *rsc_id, const char *action, guint interval_ms)
lrmd_cmd_t *cmd = gIter->data;
if (action_matches(cmd, action, interval_ms)) {
- cmd->lrmd_op_status = PCMK_LRM_OP_CANCELLED;
+ cmd->result.execution_status = PCMK_EXEC_CANCELLED;
cmd_finalize(cmd, rsc);
return pcmk_ok;
}
@@ -1722,7 +1650,7 @@ cancel_op(const char *rsc_id, const char *action, guint interval_ms)
lrmd_cmd_t *cmd = gIter->data;
if (action_matches(cmd, action, interval_ms)) {
- cmd->lrmd_op_status = PCMK_LRM_OP_CANCELLED;
+ cmd->result.execution_status = PCMK_EXEC_CANCELLED;
if (rsc->active != cmd) {
cmd_finalize(cmd, rsc);
}
diff --git a/daemons/execd/pacemaker-execd.c b/daemons/execd/pacemaker-execd.c
index ad838b6..7368dc6 100644
--- a/daemons/execd/pacemaker-execd.c
+++ b/daemons/execd/pacemaker-execd.c
@@ -475,16 +475,16 @@ main(int argc, char **argv, char **envp)
bump_log_num--;
}
- option = pcmk__env_option("logfacility");
+ option = pcmk__env_option(PCMK__ENV_LOGFACILITY);
if (option && !pcmk__strcase_any_of(option, "none", "/dev/null", NULL)) {
setenv("HA_LOGFACILITY", option, 1); /* Used by the ocf_log/ha_log OCF macro */
}
- option = pcmk__env_option("logfile");
+ option = pcmk__env_option(PCMK__ENV_LOGFILE);
if(option && !pcmk__str_eq(option, "none", pcmk__str_casei)) {
setenv("HA_LOGFILE", option, 1); /* Used by the ocf_log/ha_log OCF macro */
- if (pcmk__env_option_enabled(crm_system_name, "debug")) {
+ if (pcmk__env_option_enabled(crm_system_name, PCMK__ENV_DEBUG)) {
setenv("HA_DEBUGLOG", option, 1); /* Used by the ocf_log/ha_debug OCF macro */
}
}
diff --git a/daemons/execd/remoted_pidone.c b/daemons/execd/remoted_pidone.c
index 55b96f5..7204d3b 100644
--- a/daemons/execd/remoted_pidone.c
+++ b/daemons/execd/remoted_pidone.c
@@ -225,8 +225,8 @@ remoted_spawn_pidone(int argc, char **argv, char **envp)
* /var/log/pacemaker, so use a different default if no value has been
* explicitly configured in the container's environment.
*/
- if (pcmk__env_option("logfile") == NULL) {
- pcmk__set_env_option("logfile", "/var/log/pcmk-init.log");
+ if (pcmk__env_option(PCMK__ENV_LOGFILE) == NULL) {
+ pcmk__set_env_option(PCMK__ENV_LOGFILE, "/var/log/pcmk-init.log");
}
sigfillset(&set);
diff --git a/daemons/fenced/Makefile.am b/daemons/fenced/Makefile.am
index 43413e1..9485ca5 100644
--- a/daemons/fenced/Makefile.am
+++ b/daemons/fenced/Makefile.am
@@ -2,7 +2,7 @@
# Original Author: Sun Jiang Dong <sunjd@cn.ibm.com>
# Copyright 2004 International Business Machines
#
-# with later changes copyright 2004-2019 the Pacemaker project contributors.
+# with later changes copyright 2004-2021 the Pacemaker project contributors.
# The version control history for this file may have further details.
#
# This source code is licensed under the GNU General Public License version 2
@@ -10,12 +10,13 @@
#
include $(top_srcdir)/mk/common.mk
+include $(top_srcdir)/mk/man.mk
halibdir = $(CRM_DAEMON_DIR)
halib_PROGRAMS = pacemaker-fenced cts-fence-helper
-sbin_SCRIPTS = fence_legacy
+sbin_SCRIPTS = fence_legacy fence_watchdog
noinst_HEADERS = pacemaker-fenced.h
diff --git a/daemons/fenced/fence_watchdog.in b/daemons/fenced/fence_watchdog.in
new file mode 100755
index 0000000..eefa739
--- /dev/null
+++ b/daemons/fenced/fence_watchdog.in
@@ -0,0 +1,284 @@
+#!@PYTHON@
+"""Dummy watchdog fence agent for providing meta-data for the pacemaker internal agent
+"""
+
+__copyright__ = "Copyright 2012-2021 the Pacemaker project contributors"
+__license__ = "GNU General Public License version 2 or later (GPLv2+) WITHOUT ANY WARRANTY"
+
+import io
+import os
+import re
+import sys
+import atexit
+import getopt
+
+AGENT_VERSION = "1.0.0"
+SHORT_DESC = "Dummy watchdog fence agent"
+LONG_DESC = """fence_watchdog just provides
+meta-data - actual fencing is done by the pacemaker internal watchdog agent."""
+
+ALL_OPT = {
+ "version" : {
+ "getopt" : "V",
+ "longopt" : "version",
+ "help" : "-V, --version Display version information and exit",
+ "required" : "0",
+ "shortdesc" : "Display version information and exit",
+ "order" : 53
+ },
+ "help" : {
+ "getopt" : "h",
+ "longopt" : "help",
+ "help" : "-h, --help Display this help and exit",
+ "required" : "0",
+ "shortdesc" : "Display help and exit",
+ "order" : 54
+ },
+ "action" : {
+ "getopt" : "o:",
+ "longopt" : "action",
+ "help" : "-o, --action=[action] Action: metadata",
+ "required" : "1",
+ "shortdesc" : "Fencing Action",
+ "default" : "metadata",
+ "order" : 1
+ },
+ "nodename" : {
+ "getopt" : "N:",
+ "longopt" : "nodename",
+ "help" : "-N, --nodename Node name of fence victim (ignored)",
+ "required" : "0",
+ "shortdesc" : "Ignored",
+ "order" : 2
+ },
+ "plug" : {
+ "getopt" : "n:",
+ "longopt" : "plug",
+ "help" : "-n, --plug=[id] Physical plug number on device (ignored)",
+ "required" : "1",
+ "shortdesc" : "Ignored",
+ "order" : 4
+ }
+}
+
+
+def agent():
+ """ Return name this file was run as. """
+
+ return os.path.basename(sys.argv[0])
+
+
+def fail_usage(message):
+ """ Print a usage message and exit. """
+
+ sys.exit("%s\nPlease use '-h' for usage" % message)
+
+
+def show_docs(options):
+ """ Handle informational options (display info and exit). """
+
+ device_opt = options["device_opt"]
+
+ if "-h" in options:
+ usage(device_opt)
+ sys.exit(0)
+
+ if "-o" in options and options["-o"].lower() == "metadata":
+ metadata(device_opt, options)
+ sys.exit(0)
+
+ if "-V" in options:
+ print(AGENT_VERSION)
+ sys.exit(0)
+
+
+def sorted_options(avail_opt):
+ """ Return a list of all options, in their internally specified order. """
+
+ sorted_list = [(key, ALL_OPT[key]) for key in avail_opt]
+ sorted_list.sort(key=lambda x: x[1]["order"])
+ return sorted_list
+
+
+def usage(avail_opt):
+ """ Print a usage message. """
+ print(LONG_DESC)
+ print()
+ print("Usage:")
+ print("\t" + agent() + " [options]")
+ print("Options:")
+
+ for dummy, value in sorted_options(avail_opt):
+ if len(value["help"]) != 0:
+ print(" " + value["help"])
+
+
+def metadata(avail_opt, options):
+ """ Print agent metadata. """
+
+ print("""<?xml version="1.0" ?>
+<resource-agent name="%s" shortdesc="%s">
+<longdesc>%s</longdesc>
+<parameters>""" % (agent(), SHORT_DESC, LONG_DESC))
+
+ for option, dummy in sorted_options(avail_opt):
+ if "shortdesc" in ALL_OPT[option]:
+ print(' <parameter name="' + option +
+ '" required="' + ALL_OPT[option]["required"] + '">')
+
+ default = ""
+ default_name_arg = "-" + ALL_OPT[option]["getopt"][:-1]
+ default_name_no_arg = "-" + ALL_OPT[option]["getopt"]
+
+ if "default" in ALL_OPT[option]:
+ default = 'default="%s"' % str(ALL_OPT[option]["default"])
+ elif default_name_arg in options:
+ if options[default_name_arg]:
+ try:
+ default = 'default="%s"' % options[default_name_arg]
+ except TypeError:
+ ## @todo/@note: Currently there is no clean way how to handle lists
+ ## we can create a string from it but we can't set it on command line
+ default = 'default="%s"' % str(options[default_name_arg])
+ elif default_name_no_arg in options:
+ default = 'default="true"'
+
+ mixed = ALL_OPT[option]["help"]
+ ## split it between option and help text
+ res = re.compile(r"^(.*--\S+)\s+", re.IGNORECASE | re.S).search(mixed)
+ if None != res:
+ mixed = res.group(1)
+ mixed = mixed.replace("<", "&lt;").replace(">", "&gt;")
+ print(' <getopt mixed="' + mixed + '" />')
+
+ if ALL_OPT[option]["getopt"].count(":") > 0:
+ print(' <content type="string" ' + default + ' />')
+ else:
+ print(' <content type="boolean" ' + default + ' />')
+
+ print(' <shortdesc lang="en">' + ALL_OPT[option]["shortdesc"] + '</shortdesc>')
+ print(' </parameter>')
+
+ print(' </parameters>\n <actions>')
+ print(' <action name="on" />')
+ print(' <action name="off" />')
+ print(' <action name="reboot" />')
+ print(' <action name="monitor" />')
+ print(' <action name="list" />')
+ print(' <action name="metadata" />')
+ print(' </actions>')
+ print('</resource-agent>')
+
+
+def option_longopt(option):
+ """ Return the getopt-compatible long-option name of the given option. """
+
+ if ALL_OPT[option]["getopt"].endswith(":"):
+ return ALL_OPT[option]["longopt"] + "="
+ else:
+ return ALL_OPT[option]["longopt"]
+
+
+def opts_from_command_line(argv, avail_opt):
+ """ Read options from command-line arguments. """
+
+ # Prepare list of options for getopt
+ getopt_string = ""
+ longopt_list = []
+ for k in avail_opt:
+ if k in ALL_OPT:
+ getopt_string += ALL_OPT[k]["getopt"]
+ else:
+ fail_usage("Parse error: unknown option '" + k + "'")
+
+ if k in ALL_OPT and "longopt" in ALL_OPT[k]:
+ longopt_list.append(option_longopt(k))
+
+ try:
+ opt, dummy = getopt.gnu_getopt(argv, getopt_string, longopt_list)
+ except getopt.GetoptError as error:
+ fail_usage("Parse error: " + error.msg)
+
+ # Transform longopt to short one which are used in fencing agents
+ old_opt = opt
+ opt = {}
+ for old_option in dict(old_opt).keys():
+ if old_option.startswith("--"):
+ for option in ALL_OPT.keys():
+ if "longopt" in ALL_OPT[option] and "--" + ALL_OPT[option]["longopt"] == old_option:
+ opt["-" + ALL_OPT[option]["getopt"].rstrip(":")] = dict(old_opt)[old_option]
+ else:
+ opt[old_option] = dict(old_opt)[old_option]
+
+ return opt
+
+
+def opts_from_stdin(avail_opt):
+ """ Read options from standard input. """
+
+ opt = {}
+ name = ""
+ for line in sys.stdin.readlines():
+ line = line.strip()
+ if line.startswith("#") or (len(line) == 0):
+ continue
+
+ (name, value) = (line + "=").split("=", 1)
+ value = value[:-1]
+
+ if name not in avail_opt:
+ print("Parse error: Ignoring unknown option '%s'" % line,
+ file=sys.stderr)
+ continue
+
+ if ALL_OPT[name]["getopt"].endswith(":"):
+ opt["-"+ALL_OPT[name]["getopt"].rstrip(":")] = value
+ elif value.lower() in ["1", "yes", "on", "true"]:
+ opt["-"+ALL_OPT[name]["getopt"]] = "1"
+
+ return opt
+
+
+def process_input(avail_opt):
+ """ Set standard environment variables, and parse all options. """
+
+ # Set standard environment
+ os.putenv("LANG", "C")
+ os.putenv("LC_ALL", "C")
+
+ # Read options from command line or standard input
+ if len(sys.argv) > 1:
+ return opts_from_command_line(sys.argv[1:], avail_opt)
+ else:
+ return opts_from_stdin(avail_opt)
+
+
+def atexit_handler():
+ """ Close stdout on exit. """
+
+ try:
+ sys.stdout.close()
+ os.close(1)
+ except IOError:
+ sys.exit("%s failed to close standard output" % agent())
+
+
+def main():
+ """ Make it so! """
+
+ device_opt = ALL_OPT.keys()
+
+ ## Defaults for fence agent
+ atexit.register(atexit_handler)
+ options = process_input(device_opt)
+ options["device_opt"] = device_opt
+ show_docs(options)
+
+ print("Watchdog fencing may be initiated only by the cluster, not this agent.",
+ file=sys.stderr)
+
+ sys.exit(1)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/daemons/fenced/fenced_commands.c b/daemons/fenced/fenced_commands.c
index cd9968f..8f1a558 100644
--- a/daemons/fenced/fenced_commands.c
+++ b/daemons/fenced/fenced_commands.c
@@ -62,7 +62,7 @@ struct device_search_s {
};
static gboolean stonith_device_dispatch(gpointer user_data);
-static void st_child_done(GPid pid, int rc, const char *output, gpointer user_data);
+static void st_child_done(int pid, int rc, const char *output, void *user_data);
static void stonith_send_reply(xmlNode * reply, int call_options, const char *remote_peer,
const char *client_id);
@@ -99,7 +99,7 @@ typedef struct async_command_s {
GList *device_next;
void *internal_user_data;
- void (*done_cb) (GPid pid, int rc, const char *output, gpointer user_data);
+ void (*done_cb) (int pid, int rc, const char *output, void *user_data);
guint timer_sigterm;
guint timer_sigkill;
/*! If the operation timed out, this is the last signal
@@ -139,18 +139,45 @@ get_action_delay_max(stonith_device_t * device, const char * action)
}
static int
-get_action_delay_base(stonith_device_t * device, const char * action)
+get_action_delay_base(stonith_device_t *device, const char *action, const char *victim)
{
- const char *value = NULL;
+ char *hash_value = NULL;
int delay_base = 0;
if (!pcmk__strcase_any_of(action, "off", "reboot", NULL)) {
return 0;
}
- value = g_hash_table_lookup(device->params, PCMK_STONITH_DELAY_BASE);
- if (value) {
- delay_base = crm_parse_interval_spec(value) / 1000;
+ hash_value = g_hash_table_lookup(device->params, PCMK_STONITH_DELAY_BASE);
+
+ if (hash_value) {
+ char *value = strdup(hash_value);
+ char *valptr = value;
+
+ CRM_ASSERT(value != NULL);
+
+ if (victim) {
+ for (char *val = strtok(value, "; \t"); val != NULL; val = strtok(NULL, "; \t")) {
+ char *mapval = strchr(val, ':');
+
+ if (mapval == NULL || mapval[1] == 0) {
+ crm_err("pcmk_delay_base: empty value in mapping", val);
+ continue;
+ }
+
+ if (mapval != val && strncasecmp(victim, val, (size_t)(mapval - val)) == 0) {
+ value = mapval + 1;
+ crm_debug("pcmk_delay_base mapped to %s for %s", value, victim);
+ break;
+ }
+ }
+ }
+
+ if (strchr(value, ':') == 0) {
+ delay_base = crm_parse_interval_spec(value) / 1000;
+ }
+
+ free(valptr);
}
return delay_base;
@@ -301,7 +328,7 @@ get_active_cmds(stonith_device_t * device)
}
static void
-fork_cb(GPid pid, gpointer user_data)
+fork_cb(int pid, void *user_data)
{
async_command_t *cmd = (async_command_t *) user_data;
stonith_device_t * device =
@@ -346,6 +373,19 @@ get_agent_metadata_cb(gpointer data) {
}
}
+/*!
+ * \internal
+ * \brief Call a command's action callback for an internal (not library) result
+ *
+ * \param[in] cmd Command to report result for
+ * \param[in] rc Legacy return code to pass to callback
+ */
+static void
+report_internal_result(async_command_t *cmd, int rc)
+{
+ cmd->done_cb(0, rc, NULL, cmd);
+}
+
static gboolean
stonith_device_execute(stonith_device_t * device)
{
@@ -397,34 +437,32 @@ stonith_device_execute(stonith_device_t * device)
return TRUE;
}
- if(pcmk__str_eq(device->agent, STONITH_WATCHDOG_AGENT, pcmk__str_casei)) {
- if(pcmk__str_eq(cmd->action, "reboot", pcmk__str_casei)) {
- pcmk__panic(__func__);
- goto done;
-
- } else if(pcmk__str_eq(cmd->action, "off", pcmk__str_casei)) {
- pcmk__panic(__func__);
- goto done;
-
+ if (pcmk__str_any_of(device->agent, STONITH_WATCHDOG_AGENT,
+ STONITH_WATCHDOG_AGENT_INTERNAL, NULL)) {
+ if (pcmk__strcase_any_of(cmd->action, "reboot", "off", NULL)) {
+ if (node_does_watchdog_fencing(stonith_our_uname)) {
+ pcmk__panic(__func__);
+ goto done;
+ }
} else {
crm_info("Faking success for %s watchdog operation", cmd->action);
- cmd->done_cb(0, 0, NULL, cmd);
+ report_internal_result(cmd, pcmk_ok);
goto done;
}
}
#if SUPPORT_CIBSECRETS
- if (pcmk__substitute_secrets(device->id, device->params) != pcmk_rc_ok) {
- /* replacing secrets failed! */
+ exec_rc = pcmk__substitute_secrets(device->id, device->params);
+ if (exec_rc != pcmk_rc_ok) {
if (pcmk__str_eq(cmd->action, "stop", pcmk__str_casei)) {
- /* don't fail on stop! */
- crm_info("Proceeding with stop operation for %s", device->id);
-
+ crm_info("Proceeding with stop operation for %s "
+ "despite being unable to load CIB secrets (%s)",
+ device->id, pcmk_rc_str(exec_rc));
} else {
- crm_err("Considering %s unconfigured: Failed to get secrets",
- device->id);
- exec_rc = PCMK_OCF_NOT_CONFIGURED;
- cmd->done_cb(0, exec_rc, NULL, cmd);
+ crm_err("Considering %s unconfigured "
+ "because unable to load CIB secrets: %s",
+ device->id, pcmk_rc_str(exec_rc));
+ report_internal_result(cmd, -EACCES);
goto done;
}
}
@@ -434,7 +472,11 @@ stonith_device_execute(stonith_device_t * device)
if (pcmk__str_eq(cmd->action, "reboot", pcmk__str_casei)
&& !pcmk_is_set(device->flags, st_device_supports_reboot)) {
- crm_warn("Agent '%s' does not advertise support for 'reboot', performing 'off' action instead", device->agent);
+ crm_notice("Remapping 'reboot' action%s%s using %s to 'off' "
+ "because agent '%s' does not support reboot",
+ ((cmd->victim == NULL)? "" : " targeting "),
+ ((cmd->victim == NULL)? "" : cmd->victim),
+ device->id, device->agent);
action_str = "off";
}
@@ -457,13 +499,10 @@ stonith_device_execute(stonith_device_t * device)
cmd->activating_on = device;
exec_rc = stonith_action_execute_async(action, (void *)cmd,
cmd->done_cb, fork_cb);
-
if (exec_rc < 0) {
- crm_warn("Operation '%s'%s%s using %s failed: %s " CRM_XS " rc=%d",
- cmd->action, cmd->victim ? " targeting " : "", cmd->victim ? cmd->victim : "",
- device->id, pcmk_strerror(exec_rc), exec_rc);
cmd->activating_on = NULL;
- cmd->done_cb(0, exec_rc, NULL, cmd);
+ report_internal_result(cmd, exec_rc);
+ stonith__destroy_action(action);
}
done:
@@ -543,7 +582,7 @@ schedule_stonith_command(async_command_t * cmd, stonith_device_t * device)
}
delay_max = get_action_delay_max(device, cmd->action);
- delay_base = get_action_delay_base(device, cmd->action);
+ delay_base = get_action_delay_base(device, cmd->action, cmd->victim);
if (delay_max == 0) {
delay_max = delay_base;
}
@@ -586,7 +625,7 @@ free_device(gpointer data)
async_command_t *cmd = gIter->data;
crm_warn("Removal of device '%s' purged operation '%s'", device->id, cmd->action);
- cmd->done_cb(0, -ENODEV, NULL, cmd);
+ report_internal_result(cmd, -ENODEV);
}
g_list_free(device->pending_ops);
@@ -637,6 +676,11 @@ build_port_aliases(const char *hostmap, GList ** targets)
max = strlen(hostmap);
for (; lpc <= max; lpc++) {
switch (hostmap[lpc]) {
+ /* Skip escaped chars */
+ case '\\':
+ lpc++;
+ break;
+
/* Assignment chars */
case '=':
case ':':
@@ -656,10 +700,18 @@ build_port_aliases(const char *hostmap, GList ** targets)
case '\t':
if (name) {
char *value = NULL;
+ int k = 0;
value = calloc(1, 1 + lpc - last);
memcpy(value, hostmap + last, lpc - last);
+ for (int i = 0; value[i] != '\0'; i++) {
+ if (value[i] != '\\') {
+ value[k++] = value[i];
+ }
+ }
+ value[k] = '\0';
+
crm_debug("Adding alias '%s'='%s'", name, value);
g_hash_table_replace(aliases, name, value);
if (targets) {
@@ -716,7 +768,7 @@ get_agent_metadata(const char *agent, xmlNode ** metadata)
return EINVAL;
}
*metadata = NULL;
- if (pcmk__str_eq(agent, STONITH_WATCHDOG_AGENT, pcmk__str_none)) {
+ if (pcmk__str_eq(agent, STONITH_WATCHDOG_AGENT_INTERNAL, pcmk__str_none)) {
return pcmk_rc_ok;
}
init_metadata_cache();
@@ -1027,8 +1079,8 @@ schedule_internal_command(const char *origin,
const char *victim,
int timeout,
void *internal_user_data,
- void (*done_cb) (GPid pid, int rc, const char *output,
- gpointer user_data))
+ void (*done_cb) (int pid, int rc, const char *output,
+ void *user_data))
{
async_command_t *cmd = NULL;
@@ -1050,26 +1102,16 @@ schedule_internal_command(const char *origin,
schedule_stonith_command(cmd, device);
}
-gboolean
-string_in_list(GList *list, const char *item)
-{
- int lpc = 0;
- int max = g_list_length(list);
-
- for (lpc = 0; lpc < max; lpc++) {
- const char *value = g_list_nth_data(list, lpc);
-
- if (pcmk__str_eq(item, value, pcmk__str_casei)) {
- return TRUE;
- } else {
- crm_trace("%d: '%s' != '%s'", lpc, item, value);
- }
- }
- return FALSE;
-}
+// Fence agent status commands use custom exit status codes
+enum fence_status_code {
+ fence_status_invalid = -1,
+ fence_status_active = 0,
+ fence_status_unknown = 1,
+ fence_status_inactive = 2,
+};
static void
-status_search_cb(GPid pid, int rc, const char *output, gpointer user_data)
+status_search_cb(int pid, int rc, const char *output, void *user_data)
{
async_command_t *cmd = user_data;
struct device_search_s *search = cmd->internal_user_data;
@@ -1085,22 +1127,28 @@ status_search_cb(GPid pid, int rc, const char *output, gpointer user_data)
mainloop_set_trigger(dev->work);
- if (rc == 1 /* unknown */ ) {
- crm_trace("Host %s is not known by %s", search->host, dev->id);
+ switch (rc) {
+ case fence_status_unknown:
+ crm_trace("%s reported it cannot fence %s", dev->id, search->host);
+ break;
- } else if (rc == 0 /* active */ || rc == 2 /* inactive */ ) {
- crm_trace("Host %s is known by %s", search->host, dev->id);
- can = TRUE;
+ case fence_status_active:
+ case fence_status_inactive:
+ crm_trace("%s reported it can fence %s", dev->id, search->host);
+ can = TRUE;
+ break;
- } else {
- crm_notice("Unknown result when testing if %s can fence %s: rc=%d", dev->id, search->host,
- rc);
+ default:
+ crm_warn("Assuming %s cannot fence %s "
+ "(status returned unknown code %d)",
+ dev->id, search->host, rc);
+ break;
}
search_devices_record_result(search, dev->id, can);
}
static void
-dynamic_list_search_cb(GPid pid, int rc, const char *output, gpointer user_data)
+dynamic_list_search_cb(int pid, int rc, const char *output, void *user_data)
{
async_command_t *cmd = user_data;
struct device_search_s *search = cmd->internal_user_data;
@@ -1121,21 +1169,30 @@ dynamic_list_search_cb(GPid pid, int rc, const char *output, gpointer user_data)
mainloop_set_trigger(dev->work);
- /* If we successfully got the targets earlier, don't disable. */
- if (rc != 0 && !dev->targets) {
- if (g_hash_table_lookup(dev->params, PCMK_STONITH_HOST_CHECK) == NULL) {
- /*
- If the operation fails if the user does not explicitly specify "dynamic-list", it will fall back to "status".
- */
- crm_notice("Disabling port list queries for %s (%d): %s", dev->id, rc, output);
- g_hash_table_replace(dev->params,
- strdup(PCMK_STONITH_HOST_CHECK), strdup("status"));
- }
- } else if (!rc) {
- crm_info("Refreshing port list for %s", dev->id);
+ if (rc == CRM_EX_OK) {
+ crm_info("Refreshing target list for %s", dev->id);
g_list_free_full(dev->targets, free);
dev->targets = stonith__parse_targets(output);
dev->targets_age = time(NULL);
+
+ } else if (dev->targets != NULL) {
+ crm_info("Reusing most recent target list for %s "
+ "because list returned error code %d",
+ dev->id, rc);
+
+ } else { // We have never successfully executed list
+ crm_warn("Assuming %s cannot fence %s "
+ "because list returned error code %d",
+ dev->id, search->host, rc);
+
+ /* Fall back to pcmk_host_check="status" if the user didn't explicitly
+ * specify "dynamic-list".
+ */
+ if (g_hash_table_lookup(dev->params, PCMK_STONITH_HOST_CHECK) == NULL) {
+ crm_notice("Switching to pcmk_host_check='status' for %s", dev->id);
+ g_hash_table_replace(dev->params, strdup(PCMK_STONITH_HOST_CHECK),
+ strdup("status"));
+ }
}
if (dev->targets) {
@@ -1144,7 +1201,7 @@ dynamic_list_search_cb(GPid pid, int rc, const char *output, gpointer user_data)
if (!alias) {
alias = search->host;
}
- if (string_in_list(dev->targets, alias)) {
+ if (pcmk__str_in_list(alias, dev->targets, pcmk__str_casei)) {
can_fence = TRUE;
}
}
@@ -1215,9 +1272,62 @@ stonith_device_register(xmlNode * msg, const char **desc, gboolean from_cib)
stonith_device_t *dup = NULL;
stonith_device_t *device = build_device_from_xml(msg);
guint ndevices = 0;
+ int rv = pcmk_ok;
CRM_CHECK(device != NULL, return -ENOMEM);
+ /* do we have a watchdog-device? */
+ if (pcmk__str_eq(device->id, STONITH_WATCHDOG_ID, pcmk__str_none) ||
+ pcmk__str_any_of(device->agent, STONITH_WATCHDOG_AGENT,
+ STONITH_WATCHDOG_AGENT_INTERNAL, NULL)) do {
+ if (stonith_watchdog_timeout_ms <= 0) {
+ crm_err("Ignoring watchdog fence device without "
+ "stonith-watchdog-timeout set.");
+ rv = -ENODEV;
+ /* fall through to cleanup & return */
+ } else if (!pcmk__str_any_of(device->agent, STONITH_WATCHDOG_AGENT,
+ STONITH_WATCHDOG_AGENT_INTERNAL, NULL)) {
+ crm_err("Ignoring watchdog fence device with unknown "
+ "agent '%s' unequal '" STONITH_WATCHDOG_AGENT "'.",
+ device->agent?device->agent:"");
+ rv = -ENODEV;
+ /* fall through to cleanup & return */
+ } else if (!pcmk__str_eq(device->id, STONITH_WATCHDOG_ID,
+ pcmk__str_none)) {
+ crm_err("Ignoring watchdog fence device "
+ "named %s !='"STONITH_WATCHDOG_ID"'.",
+ device->id?device->id:"");
+ rv = -ENODEV;
+ /* fall through to cleanup & return */
+ } else {
+ if (pcmk__str_eq(device->agent, STONITH_WATCHDOG_AGENT,
+ pcmk__str_none)) {
+ /* this either has an empty list or the targets
+ configured for watchdog-fencing
+ */
+ g_list_free_full(stonith_watchdog_targets, free);
+ stonith_watchdog_targets = device->targets;
+ device->targets = NULL;
+ }
+ if (node_does_watchdog_fencing(stonith_our_uname)) {
+ g_list_free_full(device->targets, free);
+ device->targets = stonith__parse_targets(stonith_our_uname);
+ g_hash_table_replace(device->params,
+ strdup(PCMK_STONITH_HOST_LIST),
+ strdup(stonith_our_uname));
+ /* proceed as with any other stonith-device */
+ break;
+ }
+
+ crm_debug("Skip registration of watchdog fence device on node not in host-list.");
+ /* cleanup and fall through to more cleanup and return */
+ device->targets = NULL;
+ stonith_device_remove(device->id, from_cib);
+ }
+ free_device(device);
+ return rv;
+ } while (0);
+
dup = device_has_duplicate(device);
if (dup) {
ndevices = g_hash_table_size(device_list);
@@ -1598,6 +1708,39 @@ stonith_level_remove(xmlNode *msg, char **desc)
* (CIB registration is not sufficient), because monitor should not be
* possible unless the device is "started" (API registered).
*/
+
+static char *
+list_to_string(GList *list, const char *delim, gboolean terminate_with_delim)
+{
+ int max = g_list_length(list);
+ size_t delim_len = delim?strlen(delim):0;
+ size_t alloc_size = 1 + (max?((max-1+(terminate_with_delim?1:0))*delim_len):0);
+ char *rv;
+ GList *gIter;
+
+ for (gIter = list; gIter != NULL; gIter = gIter->next) {
+ const char *value = (const char *) gIter->data;
+
+ alloc_size += strlen(value);
+ }
+ rv = calloc(alloc_size, sizeof(char));
+ if (rv) {
+ char *pos = rv;
+ const char *lead_delim = "";
+
+ for (gIter = list; gIter != NULL; gIter = gIter->next) {
+ const char *value = (const char *) gIter->data;
+
+ pos = &pos[sprintf(pos, "%s%s", lead_delim, value)];
+ lead_delim = delim;
+ }
+ if (max && terminate_with_delim) {
+ sprintf(pos, "%s", delim);
+ }
+ }
+ return rv;
+}
+
static int
stonith_device_action(xmlNode * msg, char **output)
{
@@ -1615,6 +1758,19 @@ stonith_device_action(xmlNode * msg, char **output)
return -EPROTO;
}
+ if (pcmk__str_eq(id, STONITH_WATCHDOG_ID, pcmk__str_none)) {
+ if (stonith_watchdog_timeout_ms <= 0) {
+ return -ENODEV;
+ } else {
+ if (pcmk__str_eq(action, "list", pcmk__str_casei)) {
+ *output = list_to_string(stonith_watchdog_targets, "\n", TRUE);
+ return pcmk_ok;
+ } else if (pcmk__str_eq(action, "monitor", pcmk__str_casei)) {
+ return pcmk_ok;
+ }
+ }
+ }
+
device = g_hash_table_lookup(device_list, id);
if ((device == NULL)
|| (!device->api_registered && !strcmp(action, "monitor"))) {
@@ -1742,7 +1898,7 @@ can_fence_host_with_device(stonith_device_t * dev, struct device_search_s *searc
* Only use if all hosts on which the device can be active can always fence all listed hosts
*/
- if (string_in_list(dev->targets, host)) {
+ if (pcmk__str_in_list(host, dev->targets, pcmk__str_casei)) {
can = TRUE;
} else if (g_hash_table_lookup(dev->params, PCMK_STONITH_HOST_MAP)
&& g_hash_table_lookup(dev->aliases, host)) {
@@ -1753,6 +1909,13 @@ can_fence_host_with_device(stonith_device_t * dev, struct device_search_s *searc
time_t now = time(NULL);
if (dev->targets == NULL || dev->targets_age + 60 < now) {
+ int device_timeout = get_action_timeout(dev, "list", search->per_device_timeout);
+
+ if (device_timeout > search->per_device_timeout) {
+ crm_notice("Since the pcmk_list_timeout(%ds) parameter of %s is larger than stonith-timeout(%ds), timeout may occur",
+ device_timeout, dev->id, search->per_device_timeout);
+ }
+
crm_trace("Running '%s' to check whether %s is eligible to fence %s (%s)",
check_type, dev->id, search->host, search->action);
@@ -1763,11 +1926,18 @@ can_fence_host_with_device(stonith_device_t * dev, struct device_search_s *searc
return;
}
- if (string_in_list(dev->targets, alias)) {
+ if (pcmk__str_in_list(alias, dev->targets, pcmk__str_casei)) {
can = TRUE;
}
} else if (pcmk__str_eq(check_type, "status", pcmk__str_casei)) {
+ int device_timeout = get_action_timeout(dev, check_type, search->per_device_timeout);
+
+ if (device_timeout > search->per_device_timeout) {
+ crm_notice("Since the pcmk_status_timeout(%ds) parameter of %s is larger than stonith-timeout(%ds), timeout may occur",
+ device_timeout, dev->id, search->per_device_timeout);
+ }
+
crm_trace("Running '%s' to check whether %s is eligible to fence %s (%s)",
check_type, dev->id, search->host, search->action);
schedule_internal_command(__func__, dev, "status", search->host,
@@ -1808,12 +1978,6 @@ get_capable_devices(const char *host, const char *action, int timeout, bool suic
void (*callback) (GList * devices, void *user_data))
{
struct device_search_s *search;
- int per_device_timeout = DEFAULT_QUERY_TIMEOUT;
- int devices_needing_async_query = 0;
- char *key = NULL;
- const char *check_type = NULL;
- GHashTableIter gIter;
- stonith_device_t *device = NULL;
guint ndevices = g_hash_table_size(device_list);
if (ndevices == 0) {
@@ -1823,47 +1987,23 @@ get_capable_devices(const char *host, const char *action, int timeout, bool suic
search = calloc(1, sizeof(struct device_search_s));
if (!search) {
+ crm_crit("Cannot search for capable fence devices: %s",
+ strerror(ENOMEM));
callback(NULL, user_data);
return;
}
- g_hash_table_iter_init(&gIter, device_list);
- while (g_hash_table_iter_next(&gIter, (void **)&key, (void **)&device)) {
- check_type = target_list_type(device);
- if (pcmk__strcase_any_of(check_type, "status", "dynamic-list", NULL)) {
- devices_needing_async_query++;
- }
- }
-
- /* If we have devices that require an async event in order to know what
- * nodes they can fence, we have to give the events a timeout. The total
- * query timeout is divided among those events. */
- if (devices_needing_async_query) {
- per_device_timeout = timeout / devices_needing_async_query;
- if (!per_device_timeout) {
- crm_err("Fencing timeout %ds is too low; using %ds, "
- "but consider raising to at least %ds",
- timeout, DEFAULT_QUERY_TIMEOUT,
- DEFAULT_QUERY_TIMEOUT * devices_needing_async_query);
- per_device_timeout = DEFAULT_QUERY_TIMEOUT;
- } else if (per_device_timeout < DEFAULT_QUERY_TIMEOUT) {
- crm_notice("Fencing timeout %ds is low for the current "
- "configuration; consider raising to at least %ds",
- timeout, DEFAULT_QUERY_TIMEOUT * devices_needing_async_query);
- }
- }
-
search->host = host ? strdup(host) : NULL;
search->action = action ? strdup(action) : NULL;
- search->per_device_timeout = per_device_timeout;
- /* We are guaranteed this many replies. Even if a device gets
- * unregistered some how during the async search, we will get
- * the correct number of replies. */
- search->replies_needed = ndevices;
+ search->per_device_timeout = timeout;
search->allow_suicide = suicide;
search->callback = callback;
search->user_data = user_data;
- /* kick off the search */
+
+ /* We are guaranteed this many replies, even if a device is
+ * unregistered while the search is in progress.
+ */
+ search->replies_needed = ndevices;
crm_debug("Searching %d device%s to see which can execute '%s' targeting %s",
ndevices, pcmk__plural_s(ndevices),
@@ -1888,10 +2028,11 @@ struct st_query_data {
* \param[in,out] xml XML to add attributes to
* \param[in] action Fence action
* \param[in] device Fence device
+ * \param[in] target Fence target
*/
static void
add_action_specific_attributes(xmlNode *xml, const char *action,
- stonith_device_t *device)
+ stonith_device_t *device, const char *target)
{
int action_specific_timeout;
int delay_max;
@@ -1918,7 +2059,7 @@ add_action_specific_attributes(xmlNode *xml, const char *action,
crm_xml_add_int(xml, F_STONITH_DELAY_MAX, delay_max / 1000);
}
- delay_base = get_action_delay_base(device, action);
+ delay_base = get_action_delay_base(device, action, target);
if (delay_base > 0) {
crm_xml_add_int(xml, F_STONITH_DELAY_BASE, delay_base / 1000);
}
@@ -1974,7 +2115,7 @@ add_action_reply(xmlNode *xml, const char *action, stonith_device_t *device,
xmlNode *child = create_xml_node(xml, F_STONITH_ACTION);
crm_xml_add(child, XML_ATTR_ID, action);
- add_action_specific_attributes(child, action, device);
+ add_action_specific_attributes(child, action, device, target);
add_disallowed(child, action, device, target, allow_suicide);
}
@@ -2019,7 +2160,7 @@ stonith_query_capable_device_cb(GList * devices, void *user_data)
}
/* Add action-specific values if available */
- add_action_specific_attributes(dev, action, device);
+ add_action_specific_attributes(dev, action, device, query->target);
if (pcmk__str_eq(query->action, "reboot", pcmk__str_casei)) {
/* A "reboot" *might* get remapped to "off" then "on", so after
* sending the "reboot"-specific values in the main element, we add
@@ -2109,53 +2250,94 @@ stonith_query(xmlNode * msg, const char *remote_peer, const char *client_id, int
query, stonith_query_capable_device_cb);
}
-#define ST_LOG_OUTPUT_MAX 512
+/*!
+ * \internal
+ * \brief Log the result of an asynchronous command
+ *
+ * \param[in] cmd Command the result is for
+ * \param[in] rc Legacy return code corresponding to result
+ * \param[in] pid Process ID of command, if available
+ * \param[in] next Alternate device that will be tried if command failed
+ * \param[in] output Command output, if any
+ * \param[in] op_merged Whether this command was merged with an earlier one
+ */
static void
-log_operation(async_command_t * cmd, int rc, int pid, const char *next, const char *output, gboolean op_merged)
+log_async_result(async_command_t *cmd, int rc, int pid, const char *next,
+ const char *output, gboolean op_merged)
{
- if (rc == 0) {
+ int log_level = LOG_ERR;
+ int output_log_level = LOG_NEVER;
+ guint devices_remaining = g_list_length(cmd->device_next);
+
+ GString *msg = g_string_sized_new(80); // Reasonable starting size
+
+ // Choose log levels appropriately
+ if (rc == 0) { // Success
+ log_level = (cmd->victim == NULL)? LOG_DEBUG : LOG_NOTICE;
+ if ((output != NULL)
+ && !pcmk__str_eq(cmd->action, "metadata", pcmk__str_casei)) {
+ output_log_level = LOG_DEBUG;
+ }
next = NULL;
+ } else { // Failure
+ log_level = (cmd->victim == NULL)? LOG_NOTICE : LOG_ERR;
+ if ((output != NULL)
+ && !pcmk__str_eq(cmd->action, "metadata", pcmk__str_casei)) {
+ output_log_level = LOG_WARNING;
+ }
}
+ // Build the log message piece by piece
+ g_string_printf(msg, "Operation '%s' ", cmd->action);
+ if (pid != 0) {
+ g_string_append_printf(msg, "[%d] ", pid);
+ }
if (cmd->victim != NULL) {
- do_crm_log(((rc == 0)? LOG_NOTICE : LOG_ERR),
- "Operation '%s' [%d] (%scall %d from %s) targeting %s "
- "using %s returned %d (%s)%s%s",
- cmd->action, pid, (op_merged? "merged " : ""), cmd->id,
- cmd->client_name, cmd->victim,
- cmd->device, rc, pcmk_strerror(rc),
- (next? ", retrying with " : ""), (next ? next : ""));
- } else {
- do_crm_log_unlikely(((rc == 0)? LOG_DEBUG : LOG_NOTICE),
- "Operation '%s' [%d]%s using %s returned %d (%s)%s%s",
- cmd->action, pid, (op_merged? " (merged)" : ""),
- cmd->device, rc, pcmk_strerror(rc),
- (next? ", retrying with " : ""), (next ? next : ""));
+ g_string_append_printf(msg, "targeting %s ", cmd->victim);
+ }
+ g_string_append_printf(msg, "using %s ", cmd->device);
+
+ // Add result
+ g_string_append_printf(msg, "returned %d (%s)", rc, pcmk_strerror(rc));
+
+ // Add next device if appropriate
+ if (next != NULL) {
+ g_string_append_printf(msg, ", retrying with %s", next);
}
+ if (devices_remaining > 0) {
+ g_string_append_printf(msg, " (%u device%s remaining)",
+ (unsigned int) devices_remaining,
+ pcmk__plural_s(devices_remaining));
+ }
+ g_string_append_printf(msg, " " CRM_XS " %scall %d from %s",
+ (op_merged? "merged " : ""), cmd->id,
+ cmd->client_name);
+
+ // Log the result
+ do_crm_log(log_level, "%s", msg->str);
+ g_string_free(msg, TRUE);
- if (output) {
- // Output may have multiple lines
+ // Log the output (which may have multiple lines), if appropriate
+ if (output_log_level != LOG_NEVER) {
char *prefix = crm_strdup_printf("%s[%d]", cmd->device, pid);
- crm_log_output(rc == 0 ? LOG_DEBUG : LOG_WARNING, prefix, output);
+ crm_log_output(output_log_level, prefix, output);
free(prefix);
}
}
static void
-stonith_send_async_reply(async_command_t * cmd, const char *output, int rc, GPid pid, int options)
+stonith_send_async_reply(async_command_t *cmd, const char *output, int rc,
+ int pid, bool merged)
{
xmlNode *reply = NULL;
gboolean bcast = FALSE;
reply = stonith_construct_async_reply(cmd, output, NULL, rc);
- if (pcmk__str_eq(cmd->action, "metadata", pcmk__str_casei)) {
- /* Too verbose to log */
- crm_trace("Metadata query for %s", cmd->device);
- output = NULL;
-
- } else if (pcmk__str_any_of(cmd->action, "monitor", "list", "status", NULL)) {
+ // Only replies for certain actions are broadcast
+ if (pcmk__str_any_of(cmd->action, "metadata", "monitor", "list", "status",
+ NULL)) {
crm_trace("Never broadcast '%s' replies", cmd->action);
} else if (!stand_alone && pcmk__str_eq(cmd->origin, cmd->victim, pcmk__str_casei) && !pcmk__str_eq(cmd->action, "on", pcmk__str_casei)) {
@@ -2164,10 +2346,10 @@ stonith_send_async_reply(async_command_t * cmd, const char *output, int rc, GPid
bcast = TRUE;
}
- log_operation(cmd, rc, pid, NULL, output, (options & st_reply_opt_merged ? TRUE : FALSE));
+ log_async_result(cmd, rc, pid, NULL, output, merged);
crm_log_xml_trace(reply, "Reply");
- if (options & st_reply_opt_merged) {
+ if (merged) {
crm_xml_add(reply, F_STONITH_MERGED, "true");
}
@@ -2225,7 +2407,7 @@ cancel_stonith_command(async_command_t * cmd)
}
static void
-st_child_done(GPid pid, int rc, const char *output, gpointer user_data)
+st_child_done(int pid, int rc, const char *output, void *user_data)
{
stonith_device_t *device = NULL;
stonith_device_t *next_device = NULL;
@@ -2250,9 +2432,6 @@ st_child_done(GPid pid, int rc, const char *output, gpointer user_data)
mainloop_set_trigger(device->work);
}
- crm_debug("Operation '%s' using %s returned %d (%d devices remaining)",
- cmd->action, cmd->device, rc, g_list_length(cmd->device_next));
-
if (rc == 0) {
GList *iter;
/* see if there are any required devices left to execute for this op */
@@ -2275,15 +2454,14 @@ st_child_done(GPid pid, int rc, const char *output, gpointer user_data)
/* this operation requires more fencing, hooray! */
if (next_device) {
- log_operation(cmd, rc, pid, next_device->id, output, FALSE);
-
+ log_async_result(cmd, rc, pid, next_device->id, output, FALSE);
schedule_stonith_command(cmd, next_device);
/* Prevent cmd from being freed */
cmd = NULL;
goto done;
}
- stonith_send_async_reply(cmd, output, rc, pid, st_reply_opt_none);
+ stonith_send_async_reply(cmd, output, rc, pid, false);
if (rc != 0) {
goto done;
@@ -2331,7 +2509,7 @@ st_child_done(GPid pid, int rc, const char *output, gpointer user_data)
cmd_list = g_list_remove_link(cmd_list, gIter);
- stonith_send_async_reply(cmd_other, output, rc, pid, st_reply_opt_merged);
+ stonith_send_async_reply(cmd_other, output, rc, pid, true);
cancel_stonith_command(cmd_other);
free_async_command(cmd_other);
@@ -2386,7 +2564,7 @@ stonith_fence_get_devices_cb(GList * devices, void *user_data)
}
/* no device found! */
- stonith_send_async_reply(cmd, NULL, -ENODEV, 0, st_reply_opt_none);
+ stonith_send_async_reply(cmd, NULL, -ENODEV, 0, false);
free_async_command(cmd);
g_list_free_full(devices, free);
@@ -2529,13 +2707,29 @@ bool fencing_peer_active(crm_node_t *peer)
return FALSE;
}
+void set_fencing_completed(remote_fencing_op_t * op)
+{
+#ifdef CLOCK_MONOTONIC
+ struct timespec tv;
+
+ clock_gettime(CLOCK_MONOTONIC, &tv);
+
+ op->completed = tv.tv_sec;
+ op->completed_nsec = tv.tv_nsec;
+#else
+ op->completed = time(NULL);
+ op->completed_nsec = 0L;
+#endif
+}
+
/*!
* \internal
- * \brief Determine if we need to use an alternate node to
- * fence the target. If so return that node's uname
+ * \brief Look for alternate node needed if local node shouldn't fence target
+ *
+ * \param[in] target Node that must be fenced
*
- * \retval NULL, no alternate host
- * \retval uname, uname of alternate host to use
+ * \return Name of an alternate node that should fence \p target if any,
+ * or NULL otherwise
*/
static const char *
check_alternate_host(const char *target)
@@ -2777,7 +2971,7 @@ handle_request(pcmk__client_t *client, uint32_t id, uint32_t flags,
const char *client_id = NULL;
remote_fencing_op_t *op = NULL;
- crm_notice("Forwarding self-fencing request to peer %s"
+ crm_notice("Forwarding self-fencing request to peer %s "
"due to topology", alternate_host);
if (client->id) {
diff --git a/daemons/fenced/fenced_history.c b/daemons/fenced/fenced_history.c
index 857c6c8..fa8e2d8 100644
--- a/daemons/fenced/fenced_history.c
+++ b/daemons/fenced/fenced_history.c
@@ -149,7 +149,11 @@ op_time_sort(const void *a_voidp, const void *b_voidp)
} else if (b_pending) {
return 1;
} else if ((*b)->completed == (*a)->completed) {
- return 0;
+ if ((*b)->completed_nsec > (*a)->completed_nsec) {
+ return 1;
+ } else if ((*b)->completed_nsec == (*a)->completed_nsec) {
+ return 0;
+ }
} else if ((*b)->completed > (*a)->completed) {
return 1;
}
@@ -230,6 +234,7 @@ stonith_xml_history_to_list(xmlNode *history)
char *id = crm_element_value_copy(xml_op, F_STONITH_REMOTE_OP_ID);
int state;
long long completed;
+ long long completed_nsec = 0L;
if (!id) {
crm_warn("Malformed fencing history received from peer");
@@ -248,6 +253,8 @@ stonith_xml_history_to_list(xmlNode *history)
op->client_name = crm_element_value_copy(xml_op, F_STONITH_CLIENTNAME);
crm_element_value_ll(xml_op, F_STONITH_DATE, &completed);
op->completed = (time_t) completed;
+ crm_element_value_ll(xml_op, F_STONITH_DATE_NSEC, &completed_nsec);
+ op->completed_nsec = completed_nsec;
crm_element_value_int(xml_op, F_STONITH_STATE, &state);
op->state = (enum op_state) state;
@@ -346,6 +353,7 @@ stonith_local_history_diff_and_merge(GHashTable *remote_history,
crm_xml_add(entry, F_STONITH_DELEGATE, op->delegate);
crm_xml_add(entry, F_STONITH_CLIENTNAME, op->client_name);
crm_xml_add_ll(entry, F_STONITH_DATE, op->completed);
+ crm_xml_add_ll(entry, F_STONITH_DATE_NSEC, op->completed_nsec);
crm_xml_add_int(entry, F_STONITH_STATE, op->state);
}
}
@@ -363,7 +371,7 @@ stonith_local_history_diff_and_merge(GHashTable *remote_history,
crm_warn("Failing pending operation %.8s originated by us but "
"known only from peer history", op->id);
op->state = st_failed;
- op->completed = time(NULL);
+ set_fencing_completed(op);
/* use -EHOSTUNREACH to not introduce a new return-code that might
trigger unexpected results at other places and to prevent
remote_op_done from setting the delegate if not present
diff --git a/daemons/fenced/fenced_remote.c b/daemons/fenced/fenced_remote.c
index cf91aca..0541b8a 100644
--- a/daemons/fenced/fenced_remote.c
+++ b/daemons/fenced/fenced_remote.c
@@ -457,6 +457,18 @@ handle_duplicates(remote_fencing_op_t * op, xmlNode * data, int rc)
}
}
+static char *
+delegate_from_xml(xmlNode *xml)
+{
+ xmlNode *match = get_xpath_object("//@" F_STONITH_DELEGATE, xml, LOG_NEVER);
+
+ if (match == NULL) {
+ return crm_element_value_copy(xml, F_ORIG);
+ } else {
+ return crm_element_value_copy(match, F_STONITH_DELEGATE);
+ }
+}
+
/*!
* \internal
* \brief Finalize a remote operation.
@@ -490,7 +502,7 @@ remote_op_done(remote_fencing_op_t * op, xmlNode * data, int rc, int dup)
xmlNode *local_data = NULL;
gboolean op_merged = FALSE;
- op->completed = time(NULL);
+ set_fencing_completed(op);
clear_remote_op_timers(op);
undo_op_remap(op);
@@ -504,19 +516,19 @@ remote_op_done(remote_fencing_op_t * op, xmlNode * data, int rc, int dup)
goto remote_op_done_cleanup;
}
- if (!op->delegate && data && rc != -ENODEV && rc != -EHOSTUNREACH) {
- xmlNode *ndata = get_xpath_object("//@" F_STONITH_DELEGATE, data,
- LOG_NEVER);
- if(ndata) {
- op->delegate = crm_element_value_copy(ndata, F_STONITH_DELEGATE);
- } else {
- op->delegate = crm_element_value_copy(data, F_ORIG);
- }
- }
-
if (data == NULL) {
data = create_xml_node(NULL, "remote-op");
local_data = data;
+
+ } else if (op->delegate == NULL) {
+ switch (rc) {
+ case -ENODEV:
+ case -EHOSTUNREACH:
+ break;
+ default:
+ op->delegate = delegate_from_xml(data);
+ break;
+ }
}
if(dup) {
@@ -622,6 +634,7 @@ remote_op_timeout(gpointer userdata)
* "off" phase completed successfully, so quit trying any further
* devices, and return success.
*/
+ op->state = st_done;
remote_op_done(op, NULL, pcmk_ok, FALSE);
return FALSE;
}
@@ -976,7 +989,7 @@ stonith_manual_ack(xmlNode * msg, remote_fencing_op_t * op)
xmlNode *dev = get_xpath_object("//@" F_STONITH_TARGET, msg, LOG_ERR);
op->state = st_done;
- op->completed = time(NULL);
+ set_fencing_completed(op);
op->delegate = strdup("a human");
crm_notice("Injecting manual confirmation that %s is safely off/down",
@@ -1522,6 +1535,25 @@ advance_topology_device_in_level(remote_fencing_op_t *op, const char *device,
}
}
+static gboolean
+check_watchdog_fencing_and_wait(remote_fencing_op_t * op)
+{
+ if (node_does_watchdog_fencing(op->target)) {
+
+ crm_notice("Waiting %lds for %s to self-fence (%s) for "
+ "client %s " CRM_XS " id=%.8s",
+ (stonith_watchdog_timeout_ms / 1000),
+ op->target, op->action, op->client_name, op->id);
+ op->op_timer_one = g_timeout_add(stonith_watchdog_timeout_ms,
+ remote_op_watchdog_done, op);
+ return TRUE;
+ } else {
+ crm_debug("Skipping fallback to watchdog-fencing as %s is "
+ "not in host-list", op->target);
+ }
+ return FALSE;
+}
+
void
call_remote_stonith(remote_fencing_op_t * op, st_query_result_t * peer, int rc)
{
@@ -1592,26 +1624,33 @@ call_remote_stonith(remote_fencing_op_t * op, st_query_result_t * peer, int rc)
g_source_remove(op->op_timer_one);
}
- if(stonith_watchdog_timeout_ms > 0 && device && pcmk__str_eq(device, "watchdog", pcmk__str_casei)) {
- crm_notice("Waiting %lds for %s to self-fence (%s) for client %s "
- CRM_XS " id=%.8s", (stonith_watchdog_timeout_ms / 1000),
- op->target, op->action, op->client_name, op->id);
- op->op_timer_one = g_timeout_add(stonith_watchdog_timeout_ms, remote_op_watchdog_done, op);
-
- /* TODO check devices to verify watchdog will be in use */
- } else if(stonith_watchdog_timeout_ms > 0
- && pcmk__str_eq(peer->host, op->target, pcmk__str_casei)
- && !pcmk__str_eq(op->action, "on", pcmk__str_casei)) {
- crm_notice("Waiting %lds for %s to self-fence (%s) for client %s "
- CRM_XS " id=%.8s", (stonith_watchdog_timeout_ms / 1000),
- op->target, op->action, op->client_name, op->id);
- op->op_timer_one = g_timeout_add(stonith_watchdog_timeout_ms, remote_op_watchdog_done, op);
-
- } else {
+ if (!(stonith_watchdog_timeout_ms > 0 && (
+ (pcmk__str_eq(device, STONITH_WATCHDOG_ID,
+ pcmk__str_none)) ||
+ (pcmk__str_eq(peer->host, op->target, pcmk__str_casei)
+ && !pcmk__str_eq(op->action, "on", pcmk__str_casei))) &&
+ check_watchdog_fencing_and_wait(op))) {
+
+ /* Some thoughts about self-fencing cases reaching this point:
+ - Actually check in check_watchdog_fencing_and_wait
+ shouldn't fail if STONITH_WATCHDOG_ID is
+ chosen as fencing-device and it being present implies
+ watchdog-fencing is enabled anyway
+ - If watchdog-fencing is disabled either in general or for
+ a specific target - detected in check_watchdog_fencing_and_wait -
+ for some other kind of self-fencing we can't expect
+ a success answer but timeout is fine if the node doesn't
+ come back in between
+ - Delicate might be the case where we have watchdog-fencing
+ enabled for a node but the watchdog-fencing-device isn't
+ explicitly chosen for suicide. Local pe-execution in sbd
+ may detect the node as unclean and lead to timely suicide.
+ Otherwise the selection of stonith-watchdog-timeout at
+ least is questionable.
+ */
op->op_timer_one = g_timeout_add((1000 * timeout_one), remote_op_timeout_one, op);
}
-
send_cluster_message(crm_get_peer(0, peer->host), crm_msg_stonith_ng, remote_op, FALSE);
peer->tried = TRUE;
free_xml(remote_op);
@@ -1645,12 +1684,11 @@ call_remote_stonith(remote_fencing_op_t * op, st_query_result_t * peer, int rc)
* but we have all the expected replies, then no devices
* are available to execute the fencing operation. */
- if(stonith_watchdog_timeout_ms && pcmk__str_eq(device, "watchdog", pcmk__str_null_matches | pcmk__str_casei)) {
- crm_notice("Waiting %lds for %s to self-fence (%s) for client %s "
- CRM_XS " id=%.8s", (stonith_watchdog_timeout_ms / 1000),
- op->target, op->action, op->client_name, op->id);
- op->op_timer_one = g_timeout_add(stonith_watchdog_timeout_ms, remote_op_watchdog_done, op);
- return;
+ if(stonith_watchdog_timeout_ms > 0 && pcmk__str_eq(device,
+ STONITH_WATCHDOG_ID, pcmk__str_null_matches)) {
+ if (check_watchdog_fencing_and_wait(op)) {
+ return;
+ }
}
if (op->state == st_query) {
diff --git a/daemons/fenced/pacemaker-fenced.c b/daemons/fenced/pacemaker-fenced.c
index 39738d8..843c18e 100644
--- a/daemons/fenced/pacemaker-fenced.c
+++ b/daemons/fenced/pacemaker-fenced.c
@@ -42,6 +42,7 @@
char *stonith_our_uname = NULL;
long stonith_watchdog_timeout_ms = 0;
+GList *stonith_watchdog_targets = NULL;
static GMainLoop *mainloop = NULL;
@@ -578,7 +579,44 @@ our_node_allowed_for(pe_resource_t *rsc)
}
static void
-watchdog_device_update(xmlNode *cib)
+watchdog_device_update(void)
+{
+ if (stonith_watchdog_timeout_ms > 0) {
+ if (!g_hash_table_lookup(device_list, STONITH_WATCHDOG_ID) &&
+ !stonith_watchdog_targets) {
+ /* getting here watchdog-fencing enabled, no device there yet
+ and reason isn't stonith_watchdog_targets preventing that
+ */
+ int rc;
+ xmlNode *xml;
+
+ xml = create_device_registration_xml(
+ STONITH_WATCHDOG_ID,
+ st_namespace_internal,
+ STONITH_WATCHDOG_AGENT,
+ NULL, /* stonith_device_register will add our
+ own name as PCMK_STONITH_HOST_LIST param
+ so we can skip that here
+ */
+ NULL);
+ rc = stonith_device_register(xml, NULL, TRUE);
+ free_xml(xml);
+ if (rc != pcmk_ok) {
+ crm_crit("Cannot register watchdog pseudo fence agent");
+ crm_exit(CRM_EX_FATAL);
+ }
+ }
+
+ } else {
+ /* be silent if no device - todo parameter to stonith_device_remove */
+ if (g_hash_table_lookup(device_list, STONITH_WATCHDOG_ID)) {
+ stonith_device_remove(STONITH_WATCHDOG_ID, TRUE);
+ }
+ }
+}
+
+static void
+update_stonith_watchdog_timeout_ms(xmlNode *cib)
{
xmlNode *stonith_enabled_xml = NULL;
const char *stonith_enabled_s = NULL;
@@ -608,33 +646,7 @@ watchdog_device_update(xmlNode *cib)
}
}
- if (timeout_ms != stonith_watchdog_timeout_ms) {
- crm_notice("New watchdog timeout %lds (was %lds)", timeout_ms/1000, stonith_watchdog_timeout_ms/1000);
- stonith_watchdog_timeout_ms = timeout_ms;
-
- if (stonith_watchdog_timeout_ms > 0) {
- int rc;
- xmlNode *xml;
- stonith_key_value_t *params = NULL;
-
- params = stonith_key_value_add(params, PCMK_STONITH_HOST_LIST,
- stonith_our_uname);
-
- xml = create_device_registration_xml("watchdog", st_namespace_internal,
- STONITH_WATCHDOG_AGENT, params,
- NULL);
- stonith_key_value_freeall(params, 1, 1);
- rc = stonith_device_register(xml, NULL, FALSE);
- free_xml(xml);
- if (rc != pcmk_ok) {
- crm_crit("Cannot register watchdog pseudo fence agent");
- crm_exit(CRM_EX_FATAL);
- }
-
- } else {
- stonith_device_remove("watchdog", FALSE);
- }
- }
+ stonith_watchdog_timeout_ms = timeout_ms;
}
/*!
@@ -677,6 +689,16 @@ static void cib_device_update(pe_resource_t *rsc, pe_working_set_t *data_set)
return;
}
+ /* if watchdog-fencing is disabled handle any watchdog-fence
+ resource as if it was disabled
+ */
+ if ((stonith_watchdog_timeout_ms <= 0) &&
+ pcmk__str_eq(rsc->id, STONITH_WATCHDOG_ID, pcmk__str_none)) {
+ crm_info("Watchdog-fencing disabled thus handling "
+ "device %s as disabled", rsc->id);
+ return;
+ }
+
/* Check whether our node is allowed for this resource (and its parent if in a group) */
node = our_node_allowed_for(rsc);
if (rsc->parent && (rsc->parent->variant == pe_group)) {
@@ -747,7 +769,6 @@ static void cib_device_update(pe_resource_t *rsc, pe_working_set_t *data_set)
static void
cib_devices_update(void)
{
- GList *gIter = NULL;
GHashTableIter iter;
stonith_device_t *device = NULL;
@@ -772,9 +793,12 @@ cib_devices_update(void)
}
}
- for (gIter = fenced_data_set->resources; gIter != NULL; gIter = gIter->next) {
- cib_device_update(gIter->data, fenced_data_set);
- }
+ /* have list repopulated if cib has a watchdog-fencing-resource
+ TODO: keep a cached list for queries happening while we are refreshing
+ */
+ g_list_free_full(stonith_watchdog_targets, free);
+ stonith_watchdog_targets = NULL;
+ g_list_foreach(fenced_data_set->resources, (GFunc) cib_device_update, fenced_data_set);
g_hash_table_iter_init(&iter, device_list);
while (g_hash_table_iter_next(&iter, NULL, (void **)&device)) {
@@ -825,6 +849,8 @@ update_cib_stonith_devices_v2(const char *event, xmlNode * msg)
if (search != NULL) {
*search = 0;
stonith_device_remove(rsc_id, TRUE);
+ /* watchdog_device_update called afterwards
+ to fall back to implicit definition if needed */
} else {
crm_warn("Ignoring malformed CIB update (resource deletion)");
}
@@ -968,6 +994,24 @@ node_has_attr(const char *node, const char *name, const char *value)
return (match != NULL);
}
+/*!
+ * \internal
+ * \brief Check whether a node does watchdog-fencing
+ *
+ * \param[in] node Name of node to check
+ *
+ * \return TRUE if node found in stonith_watchdog_targets
+ * or stonith_watchdog_targets is empty indicating
+ * all nodes are doing watchdog-fencing
+ */
+gboolean
+node_does_watchdog_fencing(const char *node)
+{
+ return ((stonith_watchdog_targets == NULL) ||
+ pcmk__str_in_list(node, stonith_watchdog_targets, pcmk__str_casei));
+}
+
+
static void
update_fencing_topology(const char *event, xmlNode * msg)
{
@@ -1073,6 +1117,8 @@ update_cib_cache_cb(const char *event, xmlNode * msg)
xmlNode *stonith_enabled_xml = NULL;
const char *stonith_enabled_s = NULL;
static gboolean stonith_enabled_saved = TRUE;
+ long timeout_ms_saved = stonith_watchdog_timeout_ms;
+ gboolean need_full_refresh = FALSE;
if(!have_cib_devices) {
crm_trace("Skipping updates until we get a full dump");
@@ -1127,6 +1173,7 @@ update_cib_cache_cb(const char *event, xmlNode * msg)
}
pcmk__refresh_node_caches_from_cib(local_cib);
+ update_stonith_watchdog_timeout_ms(local_cib);
stonith_enabled_xml = get_xpath_object("//nvpair[@name='stonith-enabled']",
local_cib, LOG_NEVER);
@@ -1134,23 +1181,30 @@ update_cib_cache_cb(const char *event, xmlNode * msg)
stonith_enabled_s = crm_element_value(stonith_enabled_xml, XML_NVPAIR_ATTR_VALUE);
}
- watchdog_device_update(local_cib);
-
if (stonith_enabled_s && crm_is_true(stonith_enabled_s) == FALSE) {
crm_trace("Ignoring CIB updates while fencing is disabled");
stonith_enabled_saved = FALSE;
- return;
} else if (stonith_enabled_saved == FALSE) {
crm_info("Updating fencing device and topology lists "
"now that fencing is enabled");
stonith_enabled_saved = TRUE;
- fencing_topology_init();
- cib_devices_update();
+ need_full_refresh = TRUE;
} else {
- update_fencing_topology(event, msg);
- update_cib_stonith_devices(event, msg);
+ if (timeout_ms_saved != stonith_watchdog_timeout_ms) {
+ need_full_refresh = TRUE;
+ } else {
+ update_fencing_topology(event, msg);
+ update_cib_stonith_devices(event, msg);
+ watchdog_device_update();
+ }
+ }
+
+ if (need_full_refresh) {
+ fencing_topology_init();
+ cib_devices_update();
+ watchdog_device_update();
}
}
@@ -1162,10 +1216,11 @@ init_cib_cache_cb(xmlNode * msg, int call_id, int rc, xmlNode * output, void *us
local_cib = copy_xml(output);
pcmk__refresh_node_caches_from_cib(local_cib);
+ update_stonith_watchdog_timeout_ms(local_cib);
fencing_topology_init();
- watchdog_device_update(local_cib);
cib_devices_update();
+ watchdog_device_update();
}
static void
@@ -1460,7 +1515,8 @@ main(int argc, char **argv)
"different delays are configured on the nodes.\nUse this to "
"enable a static delay for fencing actions.\nThe overall delay "
"is derived from a random delay value adding this static delay "
- "so that the sum is kept below the maximum delay.</longdesc>\n");
+ "so that the sum is kept below the maximum delay.\nSet to eg. "
+ "node1:1s;node2:5 to set different value per node.</longdesc>\n");
printf(" <content type=\"time\" default=\"0s\"/>\n");
printf(" </parameter>\n");
diff --git a/daemons/fenced/pacemaker-fenced.h b/daemons/fenced/pacemaker-fenced.h
index d330fda..a64b576 100644
--- a/daemons/fenced/pacemaker-fenced.h
+++ b/daemons/fenced/pacemaker-fenced.h
@@ -1,5 +1,5 @@
/*
- * Copyright 2009-2020 the Pacemaker project contributors
+ * Copyright 2009-2021 the Pacemaker project contributors
*
* This source code is licensed under the GNU General Public License version 2
* or later (GPLv2+) WITHOUT ANY WARRANTY.
@@ -10,13 +10,14 @@
/*!
* \internal
- * \brief Check to see if target was fenced in the last few seconds.
- * \param tolerance, The number of seconds to look back in time
- * \param target, The node to search for
- * \param action, The action we want to match.
+ * \brief Check whether target has already been fenced recently
*
- * \retval FALSE, not match
- * \retval TRUE, fencing operation took place in the last 'tolerance' number of seconds.
+ * \param[in] tolerance Number of seconds to look back in time
+ * \param[in] target Name of node to search for
+ * \param[in] action Action we want to match
+ *
+ * \return TRUE if an equivalent fencing operation took place in the last
+ * \p tolerance seconds, FALSE otherwise
*/
gboolean stonith_check_fence_tolerance(int tolerance, const char *target, const char *action);
@@ -66,14 +67,6 @@ enum st_remap_phase {
st_phase_max = 3
};
-/* These values provide additional information for STONITH's asynchronous reply response.
- * The st_reply_opt_merged value indicates an operation that has been merged and completed without being executed.
- */
-enum st_replay_option {
- st_reply_opt_none = 0x00000000,
- st_reply_opt_merged = 0x00000001,
-};
-
typedef struct remote_fencing_op_s {
/* The unique id associated with this operation */
char *id;
@@ -155,6 +148,9 @@ typedef struct remote_fencing_op_s {
* completes, the duplicate operations will be closed out as well. */
GList *duplicates;
+ /*! The point at which the remote operation completed(nsec) */
+ long long completed_nsec;
+
} remote_fencing_op_t;
/*!
@@ -258,16 +254,19 @@ void stonith_fence_history_trim(void);
bool fencing_peer_active(crm_node_t *peer);
-int stonith_manual_ack(xmlNode * msg, remote_fencing_op_t * op);
+void set_fencing_completed(remote_fencing_op_t * op);
-gboolean string_in_list(GList *list, const char *item);
+int stonith_manual_ack(xmlNode * msg, remote_fencing_op_t * op);
gboolean node_has_attr(const char *node, const char *name, const char *value);
+gboolean node_does_watchdog_fencing(const char *node);
+
extern char *stonith_our_uname;
extern gboolean stand_alone;
extern GHashTable *device_list;
extern GHashTable *topology;
extern long stonith_watchdog_timeout_ms;
+extern GList *stonith_watchdog_targets;
extern GHashTable *stonith_remote_op_list;
diff --git a/daemons/pacemakerd/Makefile.am b/daemons/pacemakerd/Makefile.am
index 84517a3..fc0e014 100644
--- a/daemons/pacemakerd/Makefile.am
+++ b/daemons/pacemakerd/Makefile.am
@@ -8,6 +8,7 @@
#
include $(top_srcdir)/mk/common.mk
+include $(top_srcdir)/mk/man.mk
sbin_PROGRAMS = pacemakerd
diff --git a/daemons/pacemakerd/pacemakerd.c b/daemons/pacemakerd/pacemakerd.c
index d52888f..acec357 100644
--- a/daemons/pacemakerd/pacemakerd.c
+++ b/daemons/pacemakerd/pacemakerd.c
@@ -270,7 +270,7 @@ main(int argc, char **argv)
}
rc = pcmk__output_new(&out, args->output_ty, args->output_dest, argv);
- if (rc != pcmk_rc_ok) {
+ if ((rc != pcmk_rc_ok) || (out == NULL)) {
exit_code = CRM_EX_ERROR;
g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Error creating output format %s: %s",
args->output_ty, pcmk_rc_str(rc));
@@ -354,7 +354,7 @@ main(int argc, char **argv)
// OCF shell functions and cluster-glue need facility under different name
{
- const char *facility = pcmk__env_option("logfacility");
+ const char *facility = pcmk__env_option(PCMK__ENV_LOGFACILITY);
if (facility && !pcmk__str_eq(facility, "none", pcmk__str_casei)) {
setenv("HA_LOGFACILITY", facility, 1);
diff --git a/daemons/pacemakerd/pacemakerd.h b/daemons/pacemakerd/pacemakerd.h
index 1f01964..7c541bb 100644
--- a/daemons/pacemakerd/pacemakerd.h
+++ b/daemons/pacemakerd/pacemakerd.h
@@ -11,20 +11,6 @@
#include <stdint.h>
-typedef struct pcmk_child_s {
- pid_t pid;
- int start_seq;
- int respawn_count;
- gboolean respawn;
- const char *name;
- const char *uid;
- const char *command;
- const char *endpoint; /* IPC server name */
-
- gboolean active_before_startup;
-} pcmk_child_t;
-
-#define SIZEOF(a) (sizeof(a) / sizeof(a[0]))
#define MAX_RESPAWN 100
extern GMainLoop *mainloop;
@@ -42,7 +28,9 @@ gboolean cluster_connect_cfg(void);
void cluster_disconnect_cfg(void);
int find_and_track_existing_processes(void);
gboolean init_children_processes(void *user_data);
+void restart_cluster_subdaemons(void);
void pcmk_shutdown(int nsig);
void pcmk_handle_ping_request(pcmk__client_t *c, xmlNode *msg, uint32_t id);
void pcmk_handle_shutdown_request(pcmk__client_t *c, xmlNode *msg, uint32_t id, uint32_t flags);
void pcmkd_shutdown_corosync(void);
+bool pcmkd_corosync_connected(void);
diff --git a/daemons/pacemakerd/pcmkd_corosync.c b/daemons/pacemakerd/pcmkd_corosync.c
index 4aec870..49b206e 100644
--- a/daemons/pacemakerd/pcmkd_corosync.c
+++ b/daemons/pacemakerd/pcmkd_corosync.c
@@ -82,6 +82,9 @@ cluster_reconnect_cb(gpointer data)
mainloop_timer_del(reconnect_timer);
reconnect_timer = NULL;
crm_notice("Cluster reconnect succeeded");
+ mcp_read_config();
+ restart_cluster_subdaemons();
+ return G_SOURCE_REMOVE;
} else {
crm_info("Cluster reconnect failed"
"(connection will be reattempted once per second)");
@@ -90,7 +93,7 @@ cluster_reconnect_cb(gpointer data)
* In theory this will continue forever. In practice the CIB connection from
* attrd will timeout and shut down Pacemaker when it gets bored.
*/
- return TRUE;
+ return G_SOURCE_CONTINUE;
}
@@ -219,6 +222,25 @@ pcmkd_shutdown_corosync(void)
}
}
+bool
+pcmkd_corosync_connected(void)
+{
+ cpg_handle_t local_handle = 0;
+ cpg_model_v1_data_t cpg_model_info = {CPG_MODEL_V1, NULL, NULL, NULL, 0};
+ int fd = -1;
+
+ if (cpg_model_initialize(&local_handle, CPG_MODEL_V1, (cpg_model_data_t *) &cpg_model_info, NULL) != CS_OK) {
+ return false;
+ }
+
+ if (cpg_fd_get(local_handle, &fd) != CS_OK) {
+ return false;
+ }
+
+ cpg_finalize(local_handle);
+
+ return true;
+}
/* =::=::=::= Configuration =::=::=::= */
static int
@@ -301,30 +323,30 @@ mcp_read_config(void)
stack = get_cluster_type();
if (stack != pcmk_cluster_corosync) {
- crm_crit("Expected corosync stack but detected %s " CRM_XS " stack=%d",
- name_for_cluster_type(stack), stack);
+ crm_crit("Expected Corosync cluster layer but detected %s "
+ CRM_XS " stack=%d", name_for_cluster_type(stack), stack);
return FALSE;
}
crm_info("Reading configuration for %s stack",
name_for_cluster_type(stack));
- pcmk__set_env_option("cluster_type", "corosync");
- pcmk__set_env_option("quorum_type", "corosync");
+ pcmk__set_env_option(PCMK__ENV_CLUSTER_TYPE, "corosync");
+ pcmk__set_env_option(PCMK__ENV_QUORUM_TYPE, "corosync");
// If debug logging is not configured, check whether corosync has it
- if (pcmk__env_option("debug") == NULL) {
+ if (pcmk__env_option(PCMK__ENV_DEBUG) == NULL) {
char *debug_enabled = NULL;
get_config_opt(config, local_handle, "logging.debug", &debug_enabled, "off");
if (crm_is_true(debug_enabled)) {
- pcmk__set_env_option("debug", "1");
+ pcmk__set_env_option(PCMK__ENV_DEBUG, "1");
if (get_crm_log_level() < LOG_DEBUG) {
set_crm_log_level(LOG_DEBUG);
}
} else {
- pcmk__set_env_option("debug", "0");
+ pcmk__set_env_option(PCMK__ENV_DEBUG, "0");
}
free(debug_enabled);
diff --git a/daemons/pacemakerd/pcmkd_subdaemons.c b/daemons/pacemakerd/pcmkd_subdaemons.c
index b1f300a..9556d74 100644
--- a/daemons/pacemakerd/pcmkd_subdaemons.c
+++ b/daemons/pacemakerd/pcmkd_subdaemons.c
@@ -23,39 +23,57 @@
#include <crm/cluster.h>
#include <crm/msg_xml.h>
+typedef struct pcmk_child_s {
+ pid_t pid;
+ int respawn_count;
+ bool respawn;
+ const char *name;
+ const char *uid;
+ const char *command;
+ const char *endpoint; /* IPC server name */
+ bool needs_cluster;
+
+ /* Anything below here will be dynamically initialized */
+ bool needs_retry;
+ bool active_before_startup;
+} pcmk_child_t;
+
#define PCMK_PROCESS_CHECK_INTERVAL 5
#define SHUTDOWN_ESCALATION_PERIOD 180000 /* 3m */
/* Index into the array below */
-#define PCMK_CHILD_CONTROLD 3
+#define PCMK_CHILD_CONTROLD 5
static pcmk_child_t pcmk_children[] = {
{
- 0, 0, 0, FALSE, "none", NULL, NULL, NULL
- },
- {
- 0, 3, 0, TRUE, "pacemaker-execd", NULL,
- CRM_DAEMON_DIR "/pacemaker-execd", CRM_SYSTEM_LRMD
+ 0, 0, true, "pacemaker-based", CRM_DAEMON_USER,
+ CRM_DAEMON_DIR "/pacemaker-based", PCMK__SERVER_BASED_RO,
+ true
},
{
- 0, 1, 0, TRUE, "pacemaker-based", CRM_DAEMON_USER,
- CRM_DAEMON_DIR "/pacemaker-based", PCMK__SERVER_BASED_RO
+ 0, 0, true, "pacemaker-fenced", NULL,
+ CRM_DAEMON_DIR "/pacemaker-fenced", "stonith-ng",
+ true
},
{
- 0, 6, 0, TRUE, "pacemaker-controld", CRM_DAEMON_USER,
- CRM_DAEMON_DIR "/pacemaker-controld", CRM_SYSTEM_CRMD
+ 0, 0, true, "pacemaker-execd", NULL,
+ CRM_DAEMON_DIR "/pacemaker-execd", CRM_SYSTEM_LRMD,
+ false
},
{
- 0, 4, 0, TRUE, "pacemaker-attrd", CRM_DAEMON_USER,
- CRM_DAEMON_DIR "/pacemaker-attrd", T_ATTRD
+ 0, 0, true, "pacemaker-attrd", CRM_DAEMON_USER,
+ CRM_DAEMON_DIR "/pacemaker-attrd", T_ATTRD,
+ true
},
{
- 0, 5, 0, TRUE, "pacemaker-schedulerd", CRM_DAEMON_USER,
- CRM_DAEMON_DIR "/pacemaker-schedulerd", CRM_SYSTEM_PENGINE
+ 0, 0, true, "pacemaker-schedulerd", CRM_DAEMON_USER,
+ CRM_DAEMON_DIR "/pacemaker-schedulerd", CRM_SYSTEM_PENGINE,
+ false
},
{
- 0, 2, 0, TRUE, "pacemaker-fenced", NULL,
- CRM_DAEMON_DIR "/pacemaker-fenced", "stonith-ng"
+ 0, 0, true, "pacemaker-controld", CRM_DAEMON_USER,
+ CRM_DAEMON_DIR "/pacemaker-controld", CRM_SYSTEM_CRMD,
+ true
},
};
@@ -90,55 +108,59 @@ static bool global_keep_tracking = false;
static gboolean check_active_before_startup_processes(gpointer user_data);
static int child_liveness(pcmk_child_t *child);
static gboolean escalate_shutdown(gpointer data);
-static gboolean start_child(pcmk_child_t * child);
+static int start_child(pcmk_child_t * child);
static void pcmk_child_exit(mainloop_child_t * p, pid_t pid, int core, int signo, int exitcode);
static void pcmk_process_exit(pcmk_child_t * child);
static gboolean pcmk_shutdown_worker(gpointer user_data);
static gboolean stop_child(pcmk_child_t * child, int signal);
+static bool
+pcmkd_cluster_connected(void)
+{
+#if SUPPORT_COROSYNC
+ return pcmkd_corosync_connected();
+#else
+ return true;
+#endif
+}
+
static gboolean
check_active_before_startup_processes(gpointer user_data)
{
- int start_seq = 1, lpc = 0;
- static int max = SIZEOF(pcmk_children);
gboolean keep_tracking = FALSE;
- for (start_seq = 1; start_seq < max; start_seq++) {
- for (lpc = 0; lpc < max; lpc++) {
- if (pcmk_children[lpc].active_before_startup == FALSE) {
- /* we are already tracking it as a child process. */
- continue;
- } else if (start_seq != pcmk_children[lpc].start_seq) {
- continue;
- } else {
- int rc = child_liveness(&pcmk_children[lpc]);
-
- switch (rc) {
- case pcmk_rc_ok:
- break;
- case pcmk_rc_ipc_unresponsive:
- case pcmk_rc_ipc_pid_only: // This case: it was previously OK
- if (pcmk_children[lpc].respawn == TRUE) {
- crm_err("%s[%lld] terminated%s", pcmk_children[lpc].name,
- (long long) PCMK__SPECIAL_PID_AS_0(pcmk_children[lpc].pid),
- (rc == pcmk_rc_ipc_pid_only)? " as IPC server" : "");
- } else {
- /* orderly shutdown */
- crm_notice("%s[%lld] terminated%s", pcmk_children[lpc].name,
- (long long) PCMK__SPECIAL_PID_AS_0(pcmk_children[lpc].pid),
- (rc == pcmk_rc_ipc_pid_only)? " as IPC server" : "");
- }
- pcmk_process_exit(&(pcmk_children[lpc]));
- continue;
- default:
- crm_exit(CRM_EX_FATAL);
- break; /* static analysis/noreturn */
- }
+ for (int i = 0; i < PCMK__NELEM(pcmk_children); i++) {
+ if (!pcmk_children[i].active_before_startup) {
+ /* we are already tracking it as a child process. */
+ continue;
+ } else {
+ int rc = child_liveness(&pcmk_children[i]);
+
+ switch (rc) {
+ case pcmk_rc_ok:
+ break;
+ case pcmk_rc_ipc_unresponsive:
+ case pcmk_rc_ipc_pid_only: // This case: it was previously OK
+ if (pcmk_children[i].respawn) {
+ crm_err("%s[%lld] terminated%s", pcmk_children[i].name,
+ (long long) PCMK__SPECIAL_PID_AS_0(pcmk_children[i].pid),
+ (rc == pcmk_rc_ipc_pid_only)? " as IPC server" : "");
+ } else {
+ /* orderly shutdown */
+ crm_notice("%s[%lld] terminated%s", pcmk_children[i].name,
+ (long long) PCMK__SPECIAL_PID_AS_0(pcmk_children[i].pid),
+ (rc == pcmk_rc_ipc_pid_only)? " as IPC server" : "");
+ }
+ pcmk_process_exit(&(pcmk_children[i]));
+ continue;
+ default:
+ crm_exit(CRM_EX_FATAL);
+ break; /* static analysis/noreturn */
}
- /* at least one of the processes found at startup
- * is still going, so keep this recurring timer around */
- keep_tracking = TRUE;
}
+ /* at least one of the processes found at startup
+ * is still going, so keep this recurring timer around */
+ keep_tracking = TRUE;
}
global_keep_tracking = keep_tracking;
@@ -169,8 +191,9 @@ pcmk_child_exit(mainloop_child_t * p, pid_t pid, int core, int signo, int exitco
if (signo) {
do_crm_log(((signo == SIGKILL)? LOG_WARNING : LOG_ERR),
- "%s[%d] terminated with signal %d (core=%d)",
- name, pid, signo, core);
+ "%s[%d] terminated with signal %d (%s)%s",
+ name, pid, signo, strsignal(signo),
+ (core? " and dumped core" : ""));
} else {
switch(exitcode) {
@@ -182,14 +205,14 @@ pcmk_child_exit(mainloop_child_t * p, pid_t pid, int core, int signo, int exitco
case CRM_EX_FATAL:
crm_warn("Shutting cluster down because %s[%d] had fatal failure",
name, pid);
- child->respawn = FALSE;
+ child->respawn = false;
fatal_error = TRUE;
pcmk_shutdown(SIGTERM);
break;
case CRM_EX_PANIC:
crm_emerg("%s[%d] instructed the machine to reset", name, pid);
- child->respawn = FALSE;
+ child->respawn = false;
fatal_error = TRUE;
pcmk__panic(__func__);
pcmk_shutdown(SIGTERM);
@@ -209,12 +232,12 @@ static void
pcmk_process_exit(pcmk_child_t * child)
{
child->pid = 0;
- child->active_before_startup = FALSE;
+ child->active_before_startup = false;
child->respawn_count += 1;
if (child->respawn_count > MAX_RESPAWN) {
crm_err("Child respawn count exceeded by %s", child->name);
- child->respawn = FALSE;
+ child->respawn = false;
}
if (shutdown_trigger) {
@@ -233,7 +256,7 @@ pcmk_process_exit(pcmk_child_t * child)
" appears alright per %s IPC end-point",
child->name, child->endpoint);
/* need to monitor how it evolves, and start new process if badly */
- child->active_before_startup = TRUE;
+ child->active_before_startup = true;
if (!global_keep_tracking) {
global_keep_tracking = true;
g_timeout_add_seconds(PCMK_PROCESS_CHECK_INTERVAL,
@@ -241,6 +264,13 @@ pcmk_process_exit(pcmk_child_t * child)
}
} else {
+ if (child->needs_cluster && !pcmkd_cluster_connected()) {
+ crm_notice("Skipping cluster-based subdaemon %s until cluster returns",
+ child->name);
+ child->needs_retry = true;
+ return;
+ }
+
crm_notice("Respawning failed child process: %s", child->name);
start_child(child);
}
@@ -249,64 +279,53 @@ pcmk_process_exit(pcmk_child_t * child)
static gboolean
pcmk_shutdown_worker(gpointer user_data)
{
- static int phase = SIZEOF(pcmk_children);
+ static int phase = PCMK__NELEM(pcmk_children) - 1;
static time_t next_log = 0;
- int lpc = 0;
-
- if (phase == SIZEOF(pcmk_children)) {
+ if (phase == PCMK__NELEM(pcmk_children) - 1) {
crm_notice("Shutting down Pacemaker");
pacemakerd_state = XML_PING_ATTR_PACEMAKERDSTATE_SHUTTINGDOWN;
}
- for (; phase > 0; phase--) {
- /* Don't stop anything with start_seq < 1 */
-
- for (lpc = SIZEOF(pcmk_children) - 1; lpc >= 0; lpc--) {
- pcmk_child_t *child = &(pcmk_children[lpc]);
-
- if (phase != child->start_seq) {
- continue;
- }
-
- if (child->pid != 0) {
- time_t now = time(NULL);
-
- if (child->respawn) {
- if (child->pid == PCMK__SPECIAL_PID) {
- crm_warn("The process behind %s IPC cannot be"
- " terminated, so either wait the graceful"
- " period of %ld s for its native termination"
- " if it vitally depends on some other daemons"
- " going down in a controlled way already,"
- " or locate and kill the correct %s process"
- " on your own; set PCMK_fail_fast=1 to avoid"
- " this altogether next time around",
- child->name, (long) SHUTDOWN_ESCALATION_PERIOD,
- child->command);
- }
- next_log = now + 30;
- child->respawn = FALSE;
- stop_child(child, SIGTERM);
- if (phase < pcmk_children[PCMK_CHILD_CONTROLD].start_seq) {
- g_timeout_add(SHUTDOWN_ESCALATION_PERIOD,
- escalate_shutdown, child);
- }
-
- } else if (now >= next_log) {
- next_log = now + 30;
- crm_notice("Still waiting for %s to terminate "
- CRM_XS " pid=%lld seq=%d",
- child->name, (long long) child->pid,
- child->start_seq);
+ for (; phase >= 0; phase--) {
+ pcmk_child_t *child = &(pcmk_children[phase]);
+
+ if (child->pid != 0) {
+ time_t now = time(NULL);
+
+ if (child->respawn) {
+ if (child->pid == PCMK__SPECIAL_PID) {
+ crm_warn("The process behind %s IPC cannot be"
+ " terminated, so either wait the graceful"
+ " period of %ld s for its native termination"
+ " if it vitally depends on some other daemons"
+ " going down in a controlled way already,"
+ " or locate and kill the correct %s process"
+ " on your own; set PCMK_fail_fast=1 to avoid"
+ " this altogether next time around",
+ child->name, (long) SHUTDOWN_ESCALATION_PERIOD,
+ child->command);
+ }
+ next_log = now + 30;
+ child->respawn = false;
+ stop_child(child, SIGTERM);
+ if (phase < PCMK_CHILD_CONTROLD) {
+ g_timeout_add(SHUTDOWN_ESCALATION_PERIOD,
+ escalate_shutdown, child);
}
- return TRUE;
- }
- /* cleanup */
- crm_debug("%s confirmed stopped", child->name);
- child->pid = 0;
+ } else if (now >= next_log) {
+ next_log = now + 30;
+ crm_notice("Still waiting for %s to terminate "
+ CRM_XS " pid=%lld",
+ child->name, (long long) child->pid);
+ }
+ return TRUE;
}
+
+ /* cleanup */
+ crm_debug("%s confirmed stopped", child->name);
+ child->pid = 0;
}
crm_notice("Shutdown complete");
@@ -319,7 +338,7 @@ pcmk_shutdown_worker(gpointer user_data)
}
{
- const char *delay = pcmk__env_option("shutdown_delay");
+ const char *delay = pcmk__env_option(PCMK__ENV_SHUTDOWN_DELAY);
if(delay) {
sync();
pcmk__sleep_ms(crm_get_msec(delay));
@@ -344,7 +363,8 @@ pcmk_shutdown_worker(gpointer user_data)
it shall hand over these descriptors here if/once they are successfully
pre-opened in (presumably) child_liveness(), to avoid any remaining
room for races */
-static gboolean
+ // \return Standard Pacemaker return code
+static int
start_child(pcmk_child_t * child)
{
uid_t uid = 0;
@@ -354,11 +374,11 @@ start_child(pcmk_child_t * child)
const char *env_valgrind = getenv("PCMK_valgrind_enabled");
const char *env_callgrind = getenv("PCMK_callgrind_enabled");
- child->active_before_startup = FALSE;
+ child->active_before_startup = false;
if (child->command == NULL) {
crm_info("Nothing to do for child \"%s\"", child->name);
- return TRUE;
+ return pcmk_rc_ok;
}
if (env_callgrind != NULL && crm_is_true(env_callgrind)) {
@@ -385,7 +405,7 @@ start_child(pcmk_child_t * child)
if (child->uid) {
if (crm_user_lookup(child->uid, &uid, &gid) < 0) {
crm_err("Invalid user (%s) for %s: not found", child->uid, child->name);
- return FALSE;
+ return EACCES;
}
crm_info("Using uid=%u and group=%u for process %s", uid, gid, child->name);
}
@@ -400,7 +420,7 @@ start_child(pcmk_child_t * child)
crm_info("Forked child %lld for process %s%s",
(long long) child->pid, child->name,
use_valgrind ? " (valgrind enabled: " VALGRIND_BIN ")" : "");
- return TRUE;
+ return pcmk_rc_ok;
} else {
/* Start a new session */
@@ -465,7 +485,7 @@ start_child(pcmk_child_t * child)
crm_crit("Could not execute %s: %s", child->command, strerror(errno));
crm_exit(CRM_EX_FATAL);
}
- return TRUE; /* never reached */
+ return pcmk_rc_ok; /* never reached */
}
/*!
@@ -634,7 +654,7 @@ find_and_track_existing_processes(void)
for (rounds = 1; rounds <= WAIT_TRIES; rounds++) {
wait_in_progress = false;
- for (i = 0; i < SIZEOF(pcmk_children); i++) {
+ for (i = 0; i < PCMK__NELEM(pcmk_children); i++) {
if ((pcmk_children[i].endpoint == NULL)
|| (pcmk_children[i].respawn_count < 0)) {
@@ -694,7 +714,7 @@ find_and_track_existing_processes(void)
(long long) PCMK__SPECIAL_PID_AS_0(
pcmk_children[i].pid));
pcmk_children[i].respawn_count = -1; /* 0~keep watching */
- pcmk_children[i].active_before_startup = TRUE;
+ pcmk_children[i].active_before_startup = true;
tracking = true;
break;
case pcmk_rc_ipc_pid_only:
@@ -726,7 +746,7 @@ find_and_track_existing_processes(void)
}
pcmk__sleep_ms(250); // Wait a bit for changes to possibly happen
}
- for (i = 0; i < SIZEOF(pcmk_children); i++) {
+ for (i = 0; i < PCMK__NELEM(pcmk_children); i++) {
pcmk_children[i].respawn_count = 0; /* restore pristine state */
}
@@ -740,22 +760,14 @@ find_and_track_existing_processes(void)
gboolean
init_children_processes(void *user_data)
{
- int start_seq = 1, lpc = 0;
- static int max = SIZEOF(pcmk_children);
-
/* start any children that have not been detected */
- for (start_seq = 1; start_seq < max; start_seq++) {
- /* don't start anything with start_seq < 1 */
- for (lpc = 0; lpc < max; lpc++) {
- if (pcmk_children[lpc].pid != 0) {
- /* we are already tracking it */
- continue;
- }
-
- if (start_seq == pcmk_children[lpc].start_seq) {
- start_child(&(pcmk_children[lpc]));
- }
+ for (int i = 0; i < PCMK__NELEM(pcmk_children); i++) {
+ if (pcmk_children[i].pid != 0) {
+ /* we are already tracking it */
+ continue;
}
+
+ start_child(&(pcmk_children[i]));
}
/* From this point on, any daemons being started will be due to
@@ -777,6 +789,21 @@ pcmk_shutdown(int nsig)
mainloop_set_trigger(shutdown_trigger);
}
+void
+restart_cluster_subdaemons(void)
+{
+ for (int i = 0; i < PCMK__NELEM(pcmk_children); i++) {
+ if (!pcmk_children[i].needs_retry || pcmk_children[i].pid != 0) {
+ continue;
+ }
+
+ crm_notice("Respawning cluster-based subdaemon: %s", pcmk_children[i].name);
+ if (start_child(&pcmk_children[i])) {
+ pcmk_children[i].needs_retry = false;
+ }
+ }
+}
+
static gboolean
stop_child(pcmk_child_t * child, int signal)
{
diff --git a/daemons/schedulerd/Makefile.am b/daemons/schedulerd/Makefile.am
index c5e97aa..87c84aa 100644
--- a/daemons/schedulerd/Makefile.am
+++ b/daemons/schedulerd/Makefile.am
@@ -1,5 +1,5 @@
#
-# Copyright 2004-2019 the Pacemaker project contributors
+# Copyright 2004-2021 the Pacemaker project contributors
#
# The version control history for this file may have further details.
#
@@ -8,6 +8,7 @@
#
include $(top_srcdir)/mk/common.mk
+include $(top_srcdir)/mk/man.mk
AM_CPPFLAGS += -I$(top_builddir) -I$(top_srcdir)
diff --git a/devel/Makefile.am b/devel/Makefile.am
index 75b8850..347e1c9 100644
--- a/devel/Makefile.am
+++ b/devel/Makefile.am
@@ -41,7 +41,7 @@ EXTRA_DIST = README gdbhelpers $(COCCI_FILES) \
# Any file in this list is allowed to use any of the pcmk__ internal functions.
# Coccinelle can use any transformation that depends on "internal" to rewrite
# code to use the internal functions.
-MAY_USE_INTERNAL_FILES = $(shell find .. -path "../lib/*.c" -o -path "../tools/*.c" -o -path "../daemons/*.c" -o -path '../include/pcmki/*h' -o -name '*internal.h')
+MAY_USE_INTERNAL_FILES = $(shell find .. -path "../lib/*.c" -o -path "../lib/*private.h" -o -path "../tools/*.c" -o -path "../daemons/*.c" -o -path '../include/pcmki/*h' -o -name '*internal.h')
# And then any file in this list is public API, which may not use internal
# functions. Thus, only those transformations that do not depend on "internal"
diff --git a/devel/coccinelle/rename-var.cocci b/devel/coccinelle/rename-var.cocci
new file mode 100644
index 0000000..65bba80
--- /dev/null
+++ b/devel/coccinelle/rename-var.cocci
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2021 the Pacemaker project contributors
+ *
+ * The version control history for this file may have further details.
+ *
+ * This source code is licensed under the GNU General Public License version 2
+ * or later (GPLv2+) WITHOUT ANY WARRANTY.
+ */
+
+/*
+ * Rename a variable. This is here as a template; replace "old" and
+ * "new" below with the old and new names. Here is an example:
+ *
+ * @@ @@
+ * - xml_private_flags
+ * + pcmk__xml_flags
+ *
+ * Run in the devel directory:
+ *
+ * make COCCI_FILES=coccinelle/rename-var.cocci cocci-inplace
+ *
+ * then revert the file before committing.
+ */
+
+virtual internal
+
+@@ @@
+- old
++ new
diff --git a/doc/Makefile.am b/doc/Makefile.am
index 3044b7f..ab65673 100644
--- a/doc/Makefile.am
+++ b/doc/Makefile.am
@@ -1,5 +1,5 @@
#
-# Copyright 2003-2020 the Pacemaker project contributors
+# Copyright 2003-2021 the Pacemaker project contributors
#
# The version control history for this file may have further details.
#
@@ -8,6 +8,9 @@
#
include $(top_srcdir)/mk/common.mk
+# Define release-related variables
+include $(top_srcdir)/mk/release.mk
+
# What formats to use for book uploads (i.e. "make www";
# use BOOK_FORMATS in sphinx subdirectory to change local builds)
BOOK_FORMATS ?= html singlehtml pdf epub
@@ -34,11 +37,6 @@ RSYNC_DEST ?= root@www.clusterlabs.org:/var/www/html
# don't cross filesystems, sparse, show progress
RSYNC_OPTS = -rlptvzxS --progress
-LAST_RELEASE ?= Pacemaker-$(VERSION)
-TAG ?= $(shell [ -n "`git tag --points-at HEAD | head -1`" ] \
- && ( git tag --points-at HEAD | head -1 ) \
- || git log --pretty=format:%H -n 1 HEAD)
-
if IS_ASCIIDOC
ASCIIDOC_HTML_ARGS = --unsafe --backend=xhtml11
ASCIIDOC_DBOOK_ARGS = -b docbook -d book
@@ -70,8 +68,10 @@ deprecated-clean:
# Annotated source code as HTML
+# Cleaning first ensures we don't index unrelated stuff like RPM sources
global:
$(MAKE) $(AM_MAKEFLAGS) -C .. clean-generic
+ $(MAKE) $(AM_MAKEFLAGS) -C ../rpm rpm-clean
cd .. && gtags -q && htags -sanhIT doc
global-upload: global
diff --git a/doc/sphinx/Makefile.am b/doc/sphinx/Makefile.am
index 65ec7f0..5005998 100644
--- a/doc/sphinx/Makefile.am
+++ b/doc/sphinx/Makefile.am
@@ -8,6 +8,9 @@
#
include $(top_srcdir)/mk/common.mk
+# Define release-related variables
+include $(top_srcdir)/mk/release.mk
+
# Things you might want to override on the command line
# Books to generate
@@ -80,10 +83,6 @@ RSYNC_OPTS = -rlptvzxS --progress
BOOK_RSYNC_DEST = $(RSYNC_DEST)/$(PACKAGE)/doc/$(PACKAGE_SERIES)
-TAG ?= $(shell [ -n "`git tag --points-at HEAD | head -1`" ] \
- && ( git tag --points-at HEAD | head -1 ) \
- || git log --pretty=format:Pacemaker-$(VERSION)-%h -n 1 HEAD)
-
BOOK = none
DEPS_intro = shared/pacemaker-intro.rst $(PNGS_GENERATED)
diff --git a/doc/sphinx/Pacemaker_Administration/tools.rst b/doc/sphinx/Pacemaker_Administration/tools.rst
index e85edee..5a6044d 100644
--- a/doc/sphinx/Pacemaker_Administration/tools.rst
+++ b/doc/sphinx/Pacemaker_Administration/tools.rst
@@ -386,14 +386,14 @@ Visualizing the action sequence
_______________________________
Another handy feature is the ability to generate a visual graph of the actions
-needed, using the ``--dot-file`` option. This relies on the separate
+needed, using the ``--save-dotfile`` option. This relies on the separate
Graphviz [#]_ project.
.. topic:: Generate a visual graph of cluster actions from a saved CIB
.. code-block:: none
- # crm_simulate --simulate --xml-file $FILENAME --dot-file $FILENAME.dot
+ # crm_simulate --simulate --xml-file $FILENAME --save-dotfile $FILENAME.dot
# dot $FILENAME.dot -Tsvg > $FILENAME.svg
``$FILENAME.dot`` will contain a GraphViz representation of the cluster's
diff --git a/doc/sphinx/Pacemaker_Development/c.rst b/doc/sphinx/Pacemaker_Development/c.rst
index 0079ddd..7a3a6bf 100644
--- a/doc/sphinx/Pacemaker_Development/c.rst
+++ b/doc/sphinx/Pacemaker_Development/c.rst
@@ -211,6 +211,19 @@ require a prefix, though a unique prefix indicating an executable (controld,
crm_mon, etc.) can be helpful to indicate symbols shared between multiple
source files for the executable.
+Public API Deprecation
+______________________
+
+Public APIs may not be removed in most Pacemaker releases, but they may be
+deprecated.
+
+When a public API is deprecated, it is moved to a header whose name ends in
+``compat.h``. The original header includes the compatibility header only if the
+``PCMK_ALLOW_DEPRECATED`` symbol is undefined or defined to 1. This allows
+external code to continue using the deprecated APIs, but internal code is
+prevented from using them because the ``crm_internal.h`` header defines the
+symbol to 0.
+
.. index::
pair: C; boilerplate
@@ -475,6 +488,9 @@ API compatibility. However, there are exceptions:
Enumerations
############
+* Enumerations should not have a ``typedef``, and do not require any naming
+ convention beyond what applies to all exposed symbols.
+
* New values should usually be added to the end of public API enumerations,
because the compiler will define the values to 0, 1, etc., in the order
given, and inserting a value in the middle would change the numerical values
@@ -540,6 +556,76 @@ readability and logging consistency.
.. index::
+ pair: C; booleans
+ pair: C; bool
+ pair: C; gboolean
+
+Booleans
+########
+
+Boolean Types
+_____________
+
+Booleans in C can be represented by an integer type, ``bool``, or ``gboolean``.
+
+Integers are sometimes useful for storing booleans when they must be converted
+to and from a string, such as an XML attribute value (for which
+``crm_element_value_int()`` can be used). Integer booleans use 0 for false and
+nonzero (usually 1) for true.
+
+``gboolean`` should be used with glib APIs that specify it. ``gboolean`` should
+always be used with glib's ``TRUE`` and ``FALSE`` constants.
+
+Otherwise, ``bool`` should be preferred. ``bool`` should be used with the
+``true`` and ``false`` constants from the ``stdbool.h`` header.
+
+Testing Booleans
+________________
+
+Do not use equality operators when testing booleans. For example:
+
+.. code-block:: c
+
+ // Do this
+ if (bool1) {
+ fn();
+ }
+ if (!bool2) {
+ fn2();
+ }
+
+ // Not this
+ if (bool1 == true) {
+ fn();
+ }
+ if (bool2 == false) {
+ fn2();
+ }
+
+ // Otherwise there's no logical end ...
+ if ((bool1 == false) == true) {
+ fn();
+ }
+
+Conversely, equality operators *should* be used with non-boolean variables,
+even when just testing zero or nonzero:
+
+.. code-block:: c
+
+ int var1 = fn();
+
+ // Prefer this, because it gives a hint to the type when reading it
+ if (var1 == 0) {
+ fn2();
+ }
+
+ // Not this, because a reader could mistakenly assume it is a boolean
+ if (!var1) {
+ fn2();
+ }
+
+
+.. index::
pair: C; function
Functions
@@ -653,24 +739,146 @@ Memory Management
.. index::
pair: C; logging
+ pair: C; output
-Logging
-#######
+Logging and Output
+##################
+
+Logging Vs. Output
+__________________
+
+Log messages and output messages are logically similar but distinct.
+Oversimplifying a bit, daemons log, and tools output.
+
+Log messages are intended to help with troubleshooting and debugging.
+They may have a high level of technical detail, and are usually filtered by
+severity -- for example, the system log by default gets messages of notice
+level and higher.
+
+Output is intended to let the user know what a tool is doing, and is generally
+terser and less technical, and may even be parsed by scripts. Output might have
+"verbose" and "quiet" modes, but it is not filtered by severity.
+
+Common Guidelines for All Messages
+__________________________________
* When format strings are used for derived data types whose implementation may
vary across platforms (``pid_t``, ``time_t``, etc.), the safest approach is
to use ``%lld`` in the format string, and cast the value to ``long long``.
-* Do *not* pass ``NULL`` as an argument to satisfy the ``%s`` format specifier
- in logging (and more generally, ``printf``-style) functions. When the string
- "<null>" is a sufficient output representation in such case, you can use the
- ``crm_str()`` convenience macro; otherwise, the ternary operator is an
- obvious choice.
-
* Do not rely on ``%s`` handling ``NULL`` values properly. While the standard
library functions might, not all Pacemaker API using them does, and it's
safest to get in the habit of always ensuring format values are non-NULL.
+ For debug and trace messages, the ``crm_str()`` macro is sufficient and will
+ map NULL to the string "<null>", but for other messages an understandable
+ string appropriate to the context should be used when the value is NULL.
+
+* The convenience macros ``pcmk__plural_s()`` and ``pcmk__plural_alt()`` are
+ handy when logging a word that may be singular or plural.
+
+Logging
+_______
+
+Pacemaker uses libqb for logging, but wraps it with a higher level of
+functionality (see ``include/crm/common/logging*h``).
+
+A few macros ``crm_err()``, ``crm_warn()``, etc. do most of the heavy lifting.
+
+By default, Pacemaker sends logs at notice level and higher to the system log,
+and logs at info level and higher to the detail log (typically
+``/var/log/pacemaker/pacemaker.log``). The intent is that most users will only
+ever need the system log, but for deeper troubleshooting and developer
+debugging, the detail log may be helpful, at the cost of being more technical
+and difficult to follow.
+
+The same message can have more detail in the detail log than in the system log,
+using libqb's "extended logging" feature:
+
+.. code-block:: c
+
+ /* The following will log a simple message in the system log, like:
+
+ warning: Action failed: Node not found
+
+ with extra detail in the detail log, like:
+
+ warning: Action failed: Node not found | rc=-1005 id=hgjjg-51006
+ */
+ crm_warn("Action failed: %s " CRM_XS " rc=%d id=%s",
+ pcmk_rc_str(rc), rc, id);
+
+
+Output
+______
+
+Pacemaker has a somewhat complicated system for tool output. The main benefit
+is that the user can select the output format with the ``--output-as`` option
+(usually "text" for human-friendly output or "xml" for reliably script-parsable
+output, though ``crm_mon`` additionally supports "console" and "html").
+
+A custom message can be defined with a unique string identifier, plus
+implementation functions for each supported format. The caller invokes the
+message using the identifier. The user selects the output format via
+``--output-as``, and the output code automatically calls the appropriate
+implementation function.
+
+The interface (most importantly ``pcmk__output_t``) is declared in
+``include/crm/common/output*h``. See the API comments and existing tools for
+examples.
+
+
+.. index::
+ pair: C; strings
+
+String Handling
+###############
+
+Define Constants for Magic Strings
+__________________________________
+
+A "magic" string is one used for control purposes rather than human reading,
+and which must be exactly the same every time it is used. Examples would be
+configuration option names, XML attribute names, or environment variable names.
+
+These should always be defined constants, rather than using the string literal
+everywhere. If someone mistypes a defined constant, the code won't compile, but
+if they mistype a literal, it could go unnoticed until a user runs into a
+problem.
+
+
+Library Functions
+_________________
+
+Pacemaker's libcrmcommon has a large number of functions to assist in string
+handling. The most commonly used ones are:
+
+* ``pcmk__str_eq()`` tests string equality (similar to ``strcmp()``), but can
+ handle NULL, and takes options for case-insensitive, whether NULL should be
+ considered a match, etc.
+* ``crm_strdup_printf()`` takes ``printf()``-style arguments and creates a
+ string from them (dynamically allocated, so it must be freed with
+ ``free()``). It asserts on memory failure, so the return value is always
+ non-NULL.
+
+String handling functions should almost always be internal API, since Pacemaker
+isn't intended to be used as a general-purpose library. Most are declared in
+``include/crm/common/strings_internal.h``. ``util.h`` has some older ones that
+are public API (for now, but will eventually be made internal).
+
+
+char*, gchar*, and GString
+__________________________
+
+When using dynamically allocated strings, be careful to always use the
+appropriate free function.
+* ``char*`` strings allocated with something like ``calloc()`` must be freed
+ with ``free()``. Most Pacemaker library functions that allocate strings use
+ this implementation.
+* glib functions often use ``gchar*`` instead, which must be freed with
+ ``g_free()``.
+* Occasionally, it's convenient to use glib's flexible ``GString*`` type, which
+ must be freed with ``g_string_free()``.
.. index::
pair: C; regular expression
diff --git a/doc/sphinx/Pacemaker_Development/components.rst b/doc/sphinx/Pacemaker_Development/components.rst
index bc39197..0d29fa0 100644
--- a/doc/sphinx/Pacemaker_Development/components.rst
+++ b/doc/sphinx/Pacemaker_Development/components.rst
@@ -44,7 +44,7 @@ A fencing request can be initiated by the cluster or externally, using the
libstonithd API.
* The cluster always initiates fencing via
- ``daemons/controld/controld_te_actions.c:te_fence_node()`` (which calls the
+ ``daemons/controld/controld_fencing.c:te_fence_node()`` (which calls the
``fence()`` API method). This occurs when a transition graph synapse contains
a ``CRM_OP_FENCE`` XML operation.
* The main external clients are ``stonith_admin`` and ``cts-fence-helper``.
@@ -100,10 +100,11 @@ or messaging layer callback, which calls:
* ``handle_reply()`` which (for ``STONITH_OP_QUERY``) calls
* ``process_remote_stonith_query()``, which allocates a new query result
- structure, parses device information into it, and adds it to operation
- object. It increments the number of replies received for this operation,
- and compares it against the expected number of replies (i.e. the number
- of active peers), and if this is the last expected reply, calls
+ structure, parses device information into it, and adds it to the
+ operation object. It increments the number of replies received for this
+ operation, and compares it against the expected number of replies (i.e.
+ the number of active peers), and if this is the last expected reply,
+ calls
* ``call_remote_stonith()``, which calculates the timeout and sends
``STONITH_OP_FENCE`` request(s) to carry out the fencing. If the target
diff --git a/doc/sphinx/Pacemaker_Development/evolution.rst b/doc/sphinx/Pacemaker_Development/evolution.rst
index cf4bd4f..31349c3 100644
--- a/doc/sphinx/Pacemaker_Development/evolution.rst
+++ b/doc/sphinx/Pacemaker_Development/evolution.rst
@@ -1,49 +1,32 @@
Evolution of the project
------------------------
-Foreword
-########
+This section will not generally be of interest, but may occasionally
+shed light on why the current code is structured the way it is when
+investigating some thorny issue.
-This chapter is currently not meant as a definite summary of how
-Pacemaker got into where it stands now, but rather to provide few valuable
-pointers an entusiasts (presumably software archeologist type of person)
-may find useful. Moreover, well-intentioned contributors to Pacemaker
-project may want to review them occasionally since, as the famous quote
-has it, "those who do not learn history are doomed to repeat it".
-
-For anything more talkative with less emphasis on actual code, other
-places will serve better for the time being (and if not, should not be
-too hard to volunteer extensions to those writeups):
-
-* `main entry at ClusterLabs community wiki <https://wiki.clusterlabs.org/wiki/Pacemaker>`_
-* `entry at wikipedia.org <https://en.wikipedia.org/wiki/Pacemaker_(software)>`_
-* `brief section dedicated to Pacemaker in Digimer's tutorial regarding setting
- up the cluster with the old Red Hat Cluster Suite like stack
- <https://www.alteeve.com/w/AN!Cluster_Tutorial_2#What_about_Pacemaker.3F>`_
-
-
-Ancestor: Heartbeat project
+Origin in Heartbeat project
###########################
Pacemaker can be considered as a spin-off from Heartbeat, the original
comprehensive high availability suite started by Alan Robertson. Some
portions of code are shared, at least on the conceptual level if not verbatim,
-till today, even if the effective percentage continually declines. Note that
-till Pacemaker 2.0, it also used to stand for one (and initially the only) of
-supported messaging back-ends (removal of this support made for one such
-notable drop of reused code), see also
-`pre-2.0 commit 55ab749bf
-<https://github.com/ClusterLabs/pacemaker/commit/55ab749bf0f0143bd1cd050c1bbe302aecb3898e>`_.
+till today, even if the effective percentage continually declines.
+
+Before Pacemaker 2.0, Pacemaker supported Heartbeat as a cluster layer
+alternative to Corosync. That support was dropped for the 2.0.0 release (see
+`commit 55ab749bf
+<https://github.com/ClusterLabs/pacemaker/commit/55ab749bf0f0143bd1cd050c1bbe302aecb3898e>`_).
An archive of a 2016 checkout of the Heartbeat code base is shared as a
-`dedicated read-only repository
-<https://gitlab.com/poki/archived-heartbeat>`_ , and anchored there, the most
-notable commits are:
+`read-only repository <https://gitlab.com/poki/archived-heartbeat>`_. Notable
+commits include:
-* `initial check-in of what turned up to be the basis for Pacemaker later on
+* `creation of Heartbeat's "new cluster resource manager," which evolved into
+ Pacemaker
<https://gitlab.com/poki/archived-heartbeat/commit/bb48551be418291c46980511aa31c7c2df3a85e4>`_
-* `drop of now-detached Pacemaker code
+* `deletion of the new CRM from Heartbeat after Pacemaker had been split off
<https://gitlab.com/poki/archived-heartbeat/commit/74573ac6182785820d765ec76c5d70086381931a>`_
Regarding Pacemaker's split from heartbeat, it evolved stepwise (as opposed to
diff --git a/doc/sphinx/Pacemaker_Development/hacking.rst b/doc/sphinx/Pacemaker_Development/hacking.rst
deleted file mode 100644
index 4a0e087..0000000
--- a/doc/sphinx/Pacemaker_Development/hacking.rst
+++ /dev/null
@@ -1,69 +0,0 @@
-Advanced Hacking on the Project
--------------------------------
-
-Foreword
-########
-
-This chapter aims to be a gentle introduction (or perhaps, rather a
-summarization of advanced techniques we developed for backreferences) to how
-deal with the Pacemaker internals effectively. For instance, how to:
-
-* debug with an ease
-* verify various interesting interaction-based properties
-
-or simply put, all that is in the interest of the core contributors on the
-project to know, master, and (preferably) also evolve -- way beyond what is in
-the presumed repertoire of a generic contributor role, which is detailed in
-other chapters of this guide.
-
-Therefore, if you think you will not benefit from any such details
-in the scope of this chapter, feel free to skip it.
-
-
-Debugging
-#########
-
-In the GNU userland tradition, preferred way of debugging is based on ``gdb``
-(directly or via specific frontends atop) that is widely available on platforms
-(semi-)supported with Pacemaker itself.
-
-To make some advanced debugging easier, we maintain a script defining some
-useful helpers in ``devel/gdbhelpers`` file, which you can make available
-in the debugging session easily when invoking it as
-``gdb -x <path-to-gdbhelpers> ...``.
-
-From within the debugger, you can then invoke the new ``pcmk`` command that
-will guide you regarding other helper functions available, so we won't
-replicate that here.
-
-
-Working with mocked daemons
-###########################
-
-Since the Pacemaker run-time consists of multiple co-operating daemons
-as detailed elsewhere, tracking down the interaction details amongst
-them can be rather cumbersome. Since rebuilding existing daemons in
-a more modular way as opposed to clusters of mutually dependent
-functions, we elected to grow separate bare-bones counterparts built
-evolutionary as skeletons just to get the basic (long-term stabilized)
-communication with typical daemon clients going, and to add new modules
-in their outer circles (plus minimalistic hook support at those cores)
-on a demand-driven basis.
-
-The code for these is located at ``maint/mocked``; for instance,
-``based-notifyfenced.c`` module of ``based.c`` skeleton mocking
-``pacemaker-based`` daemon was exactly to fulfill investigation helper
-role (the case at hand was also an impulse to kick off this very
-sort of maintenance support material, to begin with).
-
-Non-trivial knowledge of Pacemaker internals and other skills are
-needed to use such devised helpers, but given the other way around,
-some sorts of investigation may be even heftier, it may be the least
-effort choice. And when that's the case, advanced contributors are
-expected to contribute their own extensions they used to validate
-the reproducibility/actual correctness of the fix along the actual
-code modifications. This way, the rest of the development teams is
-not required to deal with elaborate preconditions, be at guess, or
-even forced to use a blind faith regarding the causes, consequences
-and validity regarding the raised issues/fixes, for the greater
-benefit of all.
diff --git a/doc/sphinx/Pacemaker_Development/helpers.rst b/doc/sphinx/Pacemaker_Development/helpers.rst
index 727a53a..3cb8edd 100644
--- a/doc/sphinx/Pacemaker_Development/helpers.rst
+++ b/doc/sphinx/Pacemaker_Development/helpers.rst
@@ -4,6 +4,69 @@ C Development Helpers
.. index::
single: unit testing
+Refactoring
+###########
+
+Pacemaker uses an optional tool called `coccinelle <https://coccinelle.gitlabpages.inria.fr/website/>`_
+to do automatic refactoring. coccinelle is a very complicated tool that can be
+difficult to understand, and the existing documentation makes it pretty tough
+to get started. Much of the documentation is either aimed at kernel developers
+or takes the form of grammars.
+
+However, it can apply very complex transformations across an entire source tree.
+This is useful for tasks like code refactoring, changing APIs (number or type of
+arguments, etc.), catching functions that should not be called, and changing
+existing patterns.
+
+coccinelle is driven by input scripts called `semantic patches <https://coccinelle.gitlabpages.inria.fr/website/docs/index.html>`_
+written in its own language. These scripts bear a passing resemblance to source
+code patches and tell coccinelle how to match and modify a piece of source
+code. They are stored in ``devel/coccinelle`` and each script either contains
+a single source transformation or several related transformations. In general,
+we try to keep these as simple as possible.
+
+In Pacemaker development, we use a couple targets in ``devel/Makefile.am`` to
+control coccinelle. The ``cocci`` target tries to apply each script to every
+Pacemaker source file, printing out any changes it would make to the console.
+The ``cocci-inplace`` target does the same but also makes those changes to the
+source files. A variety of warnings might also be printed. If you aren't working
+on a new script, these can usually be ignored.
+
+If you are working on a new coccinelle script, it can be useful (and faster) to
+skip everything else and only run the new script. The ``COCCI_FILES`` variable
+can be used for this:
+
+.. code-block:: none
+
+ $ make -C devel COCCI_FILES=coccinelle/new-file.cocci cocci
+
+This variable is also used for preventing some coccinelle scripts in the Pacemaker
+source tree from running. Some scripts are disabled because they are not currently
+fully working or because they are there as templates. When adding a new script,
+remember to add it to this variable if it should always be run.
+
+One complication when writing coccinelle scripts is that certain Pacemaker source
+files may not use private functions (those whose name starts with ``pcmk__``).
+Handling this requires work in both the Makefile and in the coccinelle scripts.
+
+The Makefile deals with this by maintaining two lists of source files: those that
+may use private functions and those that may not. For those that may, a special
+argument (``-D internal``) is added to the coccinelle command line. This creates
+a virtual dependency named ``internal``.
+
+In the coccinelle scripts, those transformations that modify source code to use
+a private function also have a dependency on ``internal``. If that dependency
+was given on the command line, the transformation will be run. Otherwise, it will
+be skipped.
+
+This means that not all instances of an older style of code will be changed after
+running a given transformation. Some developer intervention is still necessary
+to know whether a source code block should have been changed or not.
+
+Probably the easiest way to learn how to use coccinelle is by following other
+people's scripts. In addition to the ones in the Pacemaker source directory,
+there's several others on the `coccinelle website <https://coccinelle.gitlabpages.inria.fr/website/rules/>`_.
+
Unit Testing
############
@@ -12,9 +75,11 @@ tests. Much of Pacemaker cannot effectively be unit tested (and there are other
testing systems used for those parts), but the ``lib`` subdirectory is pretty easy
to write tests for.
-Pacemaker uses the `GLib unit testing framework
-<https://developer.gnome.org/glib/stable/glib-Testing.html>`_ which looks a lot
-like other unit testing frameworks for C and should be fairly familiar.
+Pacemaker uses the `cmocka unit testing framework <https://cmocka.org/>`_ which looks
+a lot like other unit testing frameworks for C and should be fairly familiar. In
+addition to regular unit tests, cmocka also gives us the ability to use
+`mock functions <https://en.wikipedia.org/wiki/Mock_object>`_ for unit testing
+functions that would otherwise be difficult to test.
Organization
____________
@@ -60,14 +125,15 @@ you can see, there are already tests for other functions in this same file in
the ``lib/common/tests/strings`` directory.
* cd into ``lib/common/tests/strings``
-* Add the new file to the the ``test_programs`` variable in ``Makefile.am``, making
- it something like this:
+* Add the new file to the the ``check_PROGRAMS`` variable in ``Makefile.am``,
+ making it something like this:
.. code-block:: none
- test_programs = pcmk__add_word_test \
- pcmk__btoa_test \
- pcmk__scan_port_test
+ check_PROGRAMS = \
+ pcmk__add_word_test \
+ pcmk__btoa_test \
+ pcmk__scan_port_test
* Create a new ``pcmk__scan_port_test.c`` file, copying the copyright and include
boilerplate from another file in the same directory.
@@ -108,13 +174,12 @@ is no ``lib/common/tests/acls`` directory.
* Create a new ``acls`` directory, copying the ``Makefile.am`` from some other
directory.
* cd into ``acls``
-* Get rid of any existing values for ``test_programs``, ``dist_test_data``, and
- ``test_data`` in ``Makefile.am``. Set ``test_programs`` to ``pcmk_acl_required_test``,
- like so:
+* Get rid of any existing values for ``check_PROGRAMS`` and set it to
+ ``pcmk_acl_required_test`` like so:
.. code-block:: none
- test_programs = pcmk_acl_required_test
+ check_PROGRAMS = pcmk_acl_required_test
* Follow the steps in `Testing a new function in an already testable source file`_
to create the new ``pcmk_acl_required_test.c`` file.
@@ -161,7 +226,7 @@ may not be a very good function or even library to write actual unit tests for.
SUBDIRS = lrmd_alerts
-* Follow the steps in `Testing a function in a library without tests` to create
+* Follow the steps in `Testing a function in a source file without tests`_ to create
the rest of the new directory structure.
* Follow the steps in `Testing a new function in an already testable source file`_
@@ -185,7 +250,7 @@ here's the basic structure:
.. code-block:: c
/*
- * Copyright 2020-2021 the Pacemaker project contributors
+ * Copyright 2021 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
@@ -195,7 +260,11 @@ here's the basic structure:
#include <crm_internal.h>
- #include <glib.h>
+ #include <stdarg.h>
+ #include <stddef.h>
+ #include <stdint.h>
+ #include <setjmp.h>
+ #include <cmocka.h>
/* Put your test-specific includes here */
@@ -204,11 +273,10 @@ here's the basic structure:
int
main(int argc, char **argv)
{
- g_test_init(&argc, &argv, NULL);
-
/* Register your test functions here */
- return g_test_run();
+ cmocka_set_message_output(CM_OUTPUT_TAP);
+ return cmocka_run_group_tests(tests, NULL, NULL);
}
Each test-specific function should test one aspect of the library function,
@@ -219,39 +287,23 @@ expression matching:
.. code-block:: c
static void
- regex(void) {
+ regex(void **state) {
const char *s1 = "abcd";
const char *s2 = "ABCD";
- g_assert_cmpint(pcmk__strcmp(NULL, "a..d", pcmk__str_regex), ==, 1);
- g_assert_cmpint(pcmk__strcmp(s1, NULL, pcmk__str_regex), ==, 1);
- g_assert_cmpint(pcmk__strcmp(s1, "a..d", pcmk__str_regex), ==, 0);
+ assert_int_equal(pcmk__strcmp(NULL, "a..d", pcmk__str_regex), 1);
+ assert_int_equal(pcmk__strcmp(s1, NULL, pcmk__str_regex), 1);
+ assert_int_equal(pcmk__strcmp(s1, "a..d", pcmk__str_regex), 0);
}
Each test-specific function must also be registered or it will not be called.
-This is done with ``g_test_add_func()``. The first argument is a namespace for
-tests. It's best to look at what is being used elsewhere and try to fit your
-new functions in.
+This is done with ``cmocka_unit_test()`` in the ``main`` function:
.. code-block:: c
- g_test_add_func("/common/strings/strcmp/same_pointer", same_pointer);
- g_test_add_func("/common/strings/strcmp/one_is_null", one_is_null);
- g_test_add_func("/common/strings/strcmp/case_matters", case_matters);
- g_test_add_func("/common/strings/strcmp/case_insensitive", case_insensitive);
- g_test_add_func("/common/strings/strcmp/regex", regex);
-
-Finally, be careful when calling the ``g_assert_`` functions. They are adding
-new functions all the time, but we can't use functions newer than the minimum
-version of glib supported by Pacemaker. Luckily, they do a good job of marking
-when each function was introduced. The minimum glib version can be found in
-``configure.ac``:
-
-.. code-block:: none
-
- $ grep -A 1 "Require minimum glib" configure.ac
- # Require minimum glib version
- PKG_CHECK_MODULES([GLIB], [glib-2.0 >= 2.42.0],
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(regex),
+ };
Running
_______
@@ -270,11 +322,11 @@ following:
.. code-block:: none
- PASS: pcmk__strcmp_test 1 /common/strings/strcmp/same_pointer
- PASS: pcmk__strcmp_test 2 /common/strings/strcmp/one_is_null
- PASS: pcmk__strcmp_test 3 /common/strings/strcmp/case_matters
- PASS: pcmk__strcmp_test 4 /common/strings/strcmp/case_insensitive
- PASS: pcmk__strcmp_test 5 /common/strings/strcmp/regex
+ PASS: pcmk__strcmp_test 1 - same_pointer
+ PASS: pcmk__strcmp_test 2 - one_is_null
+ PASS: pcmk__strcmp_test 3 - case_matters
+ PASS: pcmk__strcmp_test 4 - case_insensitive
+ PASS: pcmk__strcmp_test 5 - regex
============================================================================
Testsuite summary for pacemaker 2.1.0
============================================================================
@@ -293,24 +345,20 @@ like these:
.. code-block:: none
- ERROR: pcmk__scan_double_test - Bail out! ERROR:pcmk__scan_double_test.c:77:trailing_chars: assertion failed (fabs(result - 3.0) < DBL_EPSILON): (1 < 2.22044605e-16)
- PASS: pcmk__str_any_of_test 1 /common/strings/any_of/empty_list
- PASS: pcmk__str_any_of_test 2 /common/strings/any_of/empty_string
- PASS: pcmk__str_any_of_test 3 /common/strings/any_of/in
- PASS: pcmk__str_any_of_test 4 /common/strings/any_of/not_in
- PASS: pcmk__strcmp_test 1 /common/strings/strcmp/same_pointer
- PASS: pcmk__strcmp_test 2 /common/strings/strcmp/one_is_null
- PASS: pcmk__strcmp_test 3 /common/strings/strcmp/case_matters
- PASS: pcmk__strcmp_test 4 /common/strings/strcmp/case_insensitive
- PASS: pcmk__strcmp_test 5 /common/strings/strcmp/regex
+ PASS: pcmk__scan_double_test 3 - trailing_chars
+ FAIL: pcmk__scan_double_test 4 - typical_case
+ PASS: pcmk__scan_double_test 5 - double_overflow
+ PASS: pcmk__scan_double_test 6 - double_underflow
+ ERROR: pcmk__scan_double_test - exited with status 1
+ PASS: pcmk__starts_with_test 1 - bad_input
============================================================================
Testsuite summary for pacemaker 2.1.0
============================================================================
- # TOTAL: 30
- # PASS: 29
+ # TOTAL: 56
+ # PASS: 54
# SKIP: 0
# XFAIL: 0
- # FAIL: 0
+ # FAIL: 1
# XPASS: 0
# ERROR: 1
============================================================================
@@ -320,16 +368,43 @@ like these:
make[7]: *** [Makefile:1218: test-suite.log] Error 1
make[7]: Leaving directory '/home/clumens/src/pacemaker/lib/common/tests/strings'
-The ``ERROR`` line indicates which test failed, the line the failure occurred on,
-and the test result that caused a failure. For this test case, the result is a
-little hard to understand because floating point numbers are involved. It is
-basically saying that it expected ``result`` to be ``3.0``, but this was not the case.
+The failure is in ``lib/common/tests/strings/test-suite.log``:
+
+.. code-block:: none
+
+ ERROR: pcmk__scan_double_test
+ =============================
+
+ 1..6
+ ok 1 - empty_input_string
+ PASS: pcmk__scan_double_test 1 - empty_input_string
+ ok 2 - bad_input_string
+ PASS: pcmk__scan_double_test 2 - bad_input_string
+ ok 3 - trailing_chars
+ PASS: pcmk__scan_double_test 3 - trailing_chars
+ not ok 4 - typical_case
+ FAIL: pcmk__scan_double_test 4 - typical_case
+ # 0.000000 != 3.000000
+ # pcmk__scan_double_test.c:80: error: Failure!
+ ok 5 - double_overflow
+ PASS: pcmk__scan_double_test 5 - double_overflow
+ ok 6 - double_underflow
+ PASS: pcmk__scan_double_test 6 - double_underflow
+ # not ok - tests
+ ERROR: pcmk__scan_double_test - exited with status 1
At this point, you need to determine whether your test case is incorrect or
whether the code being tested is incorrect. Fix whichever is wrong and continue.
-Test case failures are usually much easier to understand, for instance:
-.. code-block:: none
+Debugging
+#########
+
+gdb
+___
+
+If you use ``gdb`` for debugging, some helper functions are defined in
+``devel/gdbhelpers``, which can be given to ``gdb`` using the ``-x`` option.
- ERROR: pcmk__strcmp_test - Bail out! ERROR:pcmk__strcmp_test.c:64:regex: assertion failed (pcmk__strcmp(NULL, "a..d", pcmk__str_regex) == 2): (1 == 2)
+From within the debugger, you can then invoke the ``pcmk`` command that
+will describe the helper functions available.
diff --git a/doc/sphinx/Pacemaker_Development/index.rst b/doc/sphinx/Pacemaker_Development/index.rst
index 19ab926..cbe1499 100644
--- a/doc/sphinx/Pacemaker_Development/index.rst
+++ b/doc/sphinx/Pacemaker_Development/index.rst
@@ -25,7 +25,6 @@ Table of Contents
components
helpers
evolution
- hacking
Index
-----
diff --git a/doc/sphinx/Pacemaker_Explained/advanced-resources.rst b/doc/sphinx/Pacemaker_Explained/advanced-resources.rst
index b65f8c2..320dc39 100644
--- a/doc/sphinx/Pacemaker_Explained/advanced-resources.rst
+++ b/doc/sphinx/Pacemaker_Explained/advanced-resources.rst
@@ -97,6 +97,21 @@ Groups inherit the ``priority``, ``target-role``, and ``is-managed`` properties
from primitive resources. See :ref:`resource_options` for information about
those properties.
+.. table:: **Group-specific configuration options**
+
+ +-------------------+-----------------+-------------------------------------------------------+
+ | Meta-Attribute | Default | Description |
+ +===================+=================+=======================================================+
+ | ordered | true | .. index:: |
+ | | | single: group; option, ordered |
+ | | | single: option; ordered (group) |
+ | | | single: ordered; group option |
+ | | | |
+ | | | If **true**, group members will be started in the |
+ | | | order they are listed in the configuration (and |
+ | | | stopped in the reverse order). |
+ +-------------------+-----------------+-------------------------------------------------------+
+
Group Instance Attributes
_________________________
diff --git a/doc/sphinx/Pacemaker_Explained/fencing.rst b/doc/sphinx/Pacemaker_Explained/fencing.rst
index 7ee9979..680d5d8 100644
--- a/doc/sphinx/Pacemaker_Explained/fencing.rst
+++ b/doc/sphinx/Pacemaker_Explained/fencing.rst
@@ -205,9 +205,9 @@ for ``pacemaker-fenced``.
| pcmk_host_map | string | | .. index:: |
| | | | single: pcmk_host_map |
| | | | |
- | | | | A mapping of host names to ports |
- | | | | numbers for devices that do not |
- | | | | support host names. |
+ | | | | A mapping of node names to ports |
+ | | | | for devices that do not understand |
+ | | | | the node names. |
| | | | |
| | | | Example: ``node1:1;node2:2,3`` tells |
| | | | the cluster to use port 1 for |
@@ -215,7 +215,10 @@ for ``pacemaker-fenced``.
| | | | ``node2``. If ``pcmk_host_check`` is |
| | | | explicitly set to ``static-list``, |
| | | | either this or ``pcmk_host_list`` must |
- | | | | be set. |
+ | | | | be set. The port portion of the map |
+ | | | | may contain special characters such as |
+ | | | | spaces if preceded by a backslash |
+ | | | | *(since 2.1.2)*. |
+----------------------+---------+--------------------+----------------------------------------+
| pcmk_host_list | string | | .. index:: |
| | | | single: pcmk_host_list |
@@ -271,7 +274,10 @@ for ``pacemaker-fenced``.
| | | | overall delay introduced by pacemaker |
| | | | is derived from this value plus a |
| | | | random delay such that the sum is kept |
- | | | | below the maximum delay. |
+ | | | | below the maximum delay. A single |
+ | | | | device can have different delays per |
+ | | | | node using a host map *(since 2.1.2)*, |
+ | | | | for example ``node1:0s;node2:5s.`` |
+----------------------+---------+--------------------+----------------------------------------+
| pcmk_action_limit | integer | 1 | .. index:: |
| | | | single: pcmk_action_limit |
diff --git a/doc/sphinx/Pacemaker_Explained/resources.rst b/doc/sphinx/Pacemaker_Explained/resources.rst
index 773188c..2737354 100644
--- a/doc/sphinx/Pacemaker_Explained/resources.rst
+++ b/doc/sphinx/Pacemaker_Explained/resources.rst
@@ -213,17 +213,44 @@ discussed later in :ref:`fencing`.
Nagios Plugins
______________
-Nagios Plugins [#]_ allow us to monitor services on remote hosts.
+Nagios Plugins [#]_ are a way to monitor services. Pacemaker can use these as
+resources, to react to a change in the service's status.
+
+To use plugins as resources, Pacemaker must have been built with support, and
+OCF-style meta-data for the plugins must be installed on nodes that can run
+them. Meta-data for several common plugins is provided by the
+`nagios-agents-metadata <https://github.com/ClusterLabs/nagios-agents-metadata>`_
+project.
+
+The supported parameters for such a resource are same as the long options of
+the plugin.
+
+Start and monitor actions for plugin resources are implemented as invoking the
+plugin. A plugin result of "OK" (0) is treated as success, a result of "WARN"
+(1) is treated as a successful but degraded service, and any other result is
+considered a failure.
+
+A plugin resource is not going to change its status after recovery by
+restarting the plugin, so using them alone does not make sense with ``on-fail``
+set (or left to default) to ``restart``. Another value could make sense, for
+example, if you want to fence or standby nodes that cannot reach some external
+service.
+
+A more common use case for plugin resources is to configure them with a
+``container`` meta-attribute set to the name of another resource that actually
+makes the service available, such as a virtual machine or container.
+
+With ``container`` set, the plugin resource will automatically be colocated
+with the containing resource and ordered after it, and the containing resource
+will be considered failed if the plugin resource fails. This allows monitoring
+of a service inside a virtual machine or container, with recovery of the
+virtual machine or container if the service fails.
+
+Configuring a virtual machine as a guest node, or a container as a
+:ref:`bundle <s-resource-bundle>`, is the preferred way of monitoring a service
+inside, but plugin resources can be useful when it is not practical to modify
+the virtual machine or container image for this purpose.
-Pacemaker is able to do remote monitoring with the plugins `if they are
-present`.
-
-A common use case is to configure them as resources belonging to a resource
-container (usually a virtual machine), and the container will be restarted
-if any of them has failed. Another use is to configure them as ordinary
-resources to be used for monitoring hosts or services via the network.
-
-The supported parameters are same as the long options of the plugin.
.. _primitive-resource:
diff --git a/etc/Makefile.am b/etc/Makefile.am
index dd69bb6..ad0480b 100644
--- a/etc/Makefile.am
+++ b/etc/Makefile.am
@@ -12,8 +12,10 @@ MAINTAINERCLEANFILES = Makefile.in
configdir = @CONFIGDIR@
CONFIGS = crm_mon pacemaker
+if !BUILD_SYSTEMD
initdir = $(INITDIR)
init_SCRIPTS = init.d/pacemaker
+endif
logrotatedir = $(sysconfdir)/logrotate.d
logrotate_DATA = logrotate.d/pacemaker
diff --git a/extra/resources/HealthIOWait b/extra/resources/HealthIOWait
index e17d8a8..43a8b70 100755
--- a/extra/resources/HealthIOWait
+++ b/extra/resources/HealthIOWait
@@ -2,7 +2,7 @@
#
# ocf:pacemaker:HealthIOWait resource agent
#
-# Copyright 2004-2019 the Pacemaker project contributors
+# Copyright 2004-2021 the Pacemaker project contributors
#
# The version control history for this file may have further details.
#
@@ -26,8 +26,8 @@ meta_data() {
cat <<END
<?xml version="1.0"?>
<!DOCTYPE resource-agent SYSTEM "ra-api-1.dtd">
-<resource-agent name="HealthIOWait" version="1.0">
-<version>1.0</version>
+<resource-agent name="HealthIOWait" version="1.1">
+<version>1.1</version>
<longdesc lang="en">
System health agent that measures the CPU iowait via top and updates the #health-iowait attribute.
@@ -35,7 +35,7 @@ System health agent that measures the CPU iowait via top and updates the #health
<shortdesc lang="en">System health based on CPU iowait measurement</shortdesc>
<parameters>
-<parameter name="state" unique="1">
+<parameter name="state" unique-group="state">
<longdesc lang="en">
Location to store the resource state in.
</longdesc>
@@ -43,22 +43,22 @@ Location to store the resource state in.
<content type="string" default="${HA_VARRUN%%/}/health-iowait-${OCF_RESOURCE_INSTANCE}.state" />
</parameter>
-<parameter name="yellow_limit" unique="0">
+<parameter name="yellow_limit">
<longdesc lang="en">
Upper limit of iowait percentage to switch the health attribute to yellow. I.e.
the #health-iowait will go yellow if the %iowait of the CPU gets higher than 10%.
</longdesc>
<shortdesc lang="en">Upper limit for yellow health attribute</shortdesc>
-<content type="string" default="10"/>
+<content type="integer" default="10"/>
</parameter>
-<parameter name="red_limit" unique="0">
+<parameter name="red_limit">
<longdesc lang="en">
Upper limit of iowait percentage to switch the health attribute to red. I.e.
the #health-iowait will go red if the %iowait of the CPU get higher than 15%.
</longdesc>
<shortdesc lang="en">Upper limit for red health attribute</shortdesc>
-<content type="string" default="15"/>
+<content type="integer" default="15"/>
</parameter>
</parameters>
@@ -126,12 +126,32 @@ agent_monitor() {
return $OCF_NOT_RUNNING
}
+is_integer() {
+ case "$1" in
+ ""|*[0-9]*) return 0 ;;
+ *) return 1 ;;
+ esac
+}
+
+is_writable_dir() {
+ dir=$(dirname "$1")
+ [ -d "$dir" ] && [ -w "$dir" ] && [ -x "$dir" ]
+}
+
agent_validate() {
- # Is the state directory writable?
- state_dir=$(dirname "$OCF_RESKEY_state")
- if [ -d "$state_dir" ] && [ -w "$state_dir" ] && [ -x "$state_dir" ]; then
- return $OCF_ERR_ARGS
+
+ is_integer "$OCF_RESKEY_yellow_limit" && is_integer "$OCF_RESKEY_red_limit"
+ if [ $? -ne 0 ]; then
+ return $OCF_ERR_CONFIGURED
fi
+
+ if [ "$OCF_CHECK_LEVEL" = "10" ]; then
+ is_writable_dir "$OCF_RESKEY_state"
+ if [ $? -ne 0 ]; then
+ return $OCF_ERR_ARGS
+ fi
+ fi
+
return $OCF_SUCCESS
}
diff --git a/extra/resources/Makefile.am b/extra/resources/Makefile.am
index dd31833..fa74b31 100644
--- a/extra/resources/Makefile.am
+++ b/extra/resources/Makefile.am
@@ -1,5 +1,5 @@
#
-# Copyright 2008-2019 the Pacemaker project contributors
+# Copyright 2008-2021 the Pacemaker project contributors
#
# The version control history for this file may have further details.
#
@@ -8,6 +8,7 @@
#
include $(top_srcdir)/mk/common.mk
+include $(top_srcdir)/mk/man.mk
ocfdir = @OCF_RA_INSTALL_DIR@/pacemaker
dist_ocf_SCRIPTS = attribute \
diff --git a/include/crm/Makefile.am b/include/crm/Makefile.am
index f0cf01e..6dd52fd 100644
--- a/include/crm/Makefile.am
+++ b/include/crm/Makefile.am
@@ -14,7 +14,8 @@ headerdir=$(pkgincludedir)/crm
header_HEADERS = cib.h cluster.h compatibility.h crm.h \
lrmd.h msg_xml.h services.h stonith-ng.h \
crm_compat.h \
- msg_xml_compat.h
+ msg_xml_compat.h \
+ services_compat.h
noinst_HEADERS = lrmd_internal.h services_internal.h
diff --git a/include/crm/cib/internal.h b/include/crm/cib/internal.h
index 31d4ed6..cdba501 100644
--- a/include/crm/cib/internal.h
+++ b/include/crm/cib/internal.h
@@ -74,7 +74,6 @@ gboolean cib_read_config(GHashTable * options, xmlNode * current_cib);
void verify_cib_options(GHashTable * options);
gboolean cib_internal_config_changed(xmlNode * diff);
-extern GHashTable *cib_op_callback_table;
typedef struct cib_notify_client_s {
const char *event;
const char *obj_id; /* implement one day */
@@ -178,22 +177,22 @@ int cib_process_upgrade(const char *op, int options, const char *section, xmlNod
/*!
* \internal
- * \brief Core function to manipulate with/query CIB/XML per xpath + arguments
- * \param[in] op, the operation to be performed:
- * <tt>CIB_OP_{CREATE,DELETE,MODIFY,QUERY,REPLACE}</tt>
- * \param[in] options, ORed flags per relevant \c cib_call_options enumeration:
- * <tt>cib_{multiple,no_children,xpath_address}</tt>
- * \param[in] section, xpath defining place of interest in
- * <tt>{existing,result}_cib</tt>
- * \param[in] req, UNUSED
- * \param[in] input, the input operand for
- * <tt>CIB_OP_{CREATE,MODIFY,REPLACE}</tt>
- * \param[in] existing_cib, the input operand (CIB) for \c CIB_OP_QUERY
- * \param[inout] result_cib, the operand and result for
- * <tt>CIB_OP_{CREATE,DELETE,MODIFY,REPLACE}</tt>
- * \param[out] answer, the result for \c CIB_OP_QUERY, structured per \c options
+ * \brief Query or modify a CIB
*
- * \retval \c pcmk_ok (0) for success, different value for failure
+ * \param[in] op CIB_OP_* operation to be performed
+ * \param[in] options Flag set of \c cib_call_options
+ * \param[in] section XPath to query or modify
+ * \param[in] req unused
+ * \param[in] input Portion of CIB to modify (used with
+ * CIB_OP_CREATE, CIB_OP_MODIFY, and
+ * CIB_OP_REPLACE)
+ * \param[in] existing_cib Input CIB (used with CIB_OP_QUERY)
+ * \param[in,out] result_cib CIB copy to make changes in (used with
+ * CIB_OP_CREATE, CIB_OP_MODIFY, CIB_OP_DELETE, and
+ * CIB_OP_REPLACE)
+ * \param[out] answer Query result (used with CIB_OP_QUERY)
+ *
+ * \return Legacy Pacemaker return code
*/
int cib_process_xpath(const char *op, int options, const char *section, xmlNode * req,
xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib,
@@ -215,4 +214,20 @@ int cib_file_write_with_digest(xmlNode *cib_root, const char *cib_dirname,
void cib__set_output(cib_t *cib, pcmk__output_t *out);
+cib_callback_client_t* cib__lookup_id (int call_id);
+
+/*!
+ * \internal
+ * \brief Connect to, query, and optionally disconnect from the CIB, returning
+ * the resulting XML object.
+ *
+ * \param[out] cib If non-NULL, a pointer to where to store the CIB
+ * connection. In this case, it is up to the caller to
+ * disconnect from the CIB when finished.
+ * \param[out] cib_object A pointer to where to store the XML query result.
+ *
+ * \return A standard Pacemaker return code
+ */
+int cib__signon_query(cib_t **cib, xmlNode **cib_object);
+
#endif
diff --git a/include/crm/common/Makefile.am b/include/crm/common/Makefile.am
index 8228de8..02f1686 100644
--- a/include/crm/common/Makefile.am
+++ b/include/crm/common/Makefile.am
@@ -21,5 +21,5 @@ header_HEADERS = xml.h ipc.h util.h iso8601.h mainloop.h logging.h results.h \
noinst_HEADERS = internal.h alerts_internal.h \
iso8601_internal.h remote_internal.h xml_internal.h \
ipc_internal.h output_internal.h cmdline_internal.h \
- curses_internal.h attrd_internal.h options_internal.h \
+ attrd_internal.h options_internal.h results_internal.h \
strings_internal.h lists_internal.h logging_internal.h
diff --git a/include/crm/common/curses_internal.h b/include/crm/common/curses_internal.h
deleted file mode 100644
index 55a645e..0000000
--- a/include/crm/common/curses_internal.h
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright 2015-2021 the Pacemaker project contributors
- *
- * The version control history for this file may have further details.
- *
- * This source code is licensed under the GNU Lesser General Public License
- * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
- */
-
-#ifndef PCMK__CURSES_INTERNAL__H
-# define PCMK__CURSES_INTERNAL__H
-
-# include <stdio.h>
-
-# ifndef PCMK__CONFIG_H
-# define PCMK__CONFIG_H
-# include <config.h>
-# endif
-
-# include <crm/common/logging.h>
-
-/*
- * The man pages for both curses and ncurses suggest inclusion of "curses.h".
- * We believe the following to be acceptable and portable.
- */
-
-# if defined(HAVE_LIBNCURSES) || defined(HAVE_LIBCURSES)
-# if defined(HAVE_NCURSES_H) && !defined(HAVE_INCOMPATIBLE_PRINTW)
-# include <ncurses.h>
-# define CURSES_ENABLED 1
-# elif defined(HAVE_NCURSES_NCURSES_H) && !defined(HAVE_INCOMPATIBLE_PRINTW)
-# include <ncurses/ncurses.h>
-# define CURSES_ENABLED 1
-# elif defined(HAVE_CURSES_H) && !defined(HAVE_INCOMPATIBLE_PRINTW)
-# include <curses.h>
-# define CURSES_ENABLED 1
-# elif defined(HAVE_CURSES_CURSES_H) && !defined(HAVE_INCOMPATIBLE_PRINTW)
-# include <curses/curses.h>
-# define CURSES_ENABLED 1
-# else
-# define CURSES_ENABLED 0
-# endif
-# else
-# define CURSES_ENABLED 0
-# endif
-
-#endif
diff --git a/include/crm/common/internal.h b/include/crm/common/internal.h
index 3904c9c..0b5adcf 100644
--- a/include/crm/common/internal.h
+++ b/include/crm/common/internal.h
@@ -23,6 +23,8 @@
#include <crm/common/util.h> // crm_strdup_printf()
#include <crm/common/logging.h> // do_crm_log_unlikely(), etc.
#include <crm/common/mainloop.h> // mainloop_io_t, struct ipc_client_callbacks
+#include <crm/common/iso8601_internal.h>
+#include <crm/common/results_internal.h>
#include <crm/common/strings_internal.h>
/* This says whether the current application is a Pacemaker daemon or not,
@@ -75,6 +77,7 @@ int pcmk__chown_series_sequence(const char *directory, const char *series,
uid_t uid, gid_t gid);
int pcmk__build_path(const char *path_c, mode_t mode);
+char *pcmk__full_path(const char *filename, const char *dirname);
bool pcmk__daemon_can_write(const char *dir, const char *file);
void pcmk__sync_directory(const char *name);
@@ -285,14 +288,6 @@ pcmk__realloc(void *ptr, size_t size)
}
-/* Error domains for use with g_set_error (from results.c) */
-
-GQuark pcmk__rc_error_quark(void);
-GQuark pcmk__exitc_error_quark(void);
-
-#define PCMK__RC_ERROR pcmk__rc_error_quark()
-#define PCMK__EXITC_ERROR pcmk__exitc_error_quark()
-
static inline char *
pcmk__getpid_s(void)
{
diff --git a/include/crm/common/iso8601_internal.h b/include/crm/common/iso8601_internal.h
index b696452..0360b30 100644
--- a/include/crm/common/iso8601_internal.h
+++ b/include/crm/common/iso8601_internal.h
@@ -1,5 +1,5 @@
/*
- * Copyright 2015-2020 the Pacemaker project contributors
+ * Copyright 2015-2021 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
@@ -25,6 +25,7 @@ pcmk__time_hr_t *pcmk__time_hr_new(const char *date_time);
void pcmk__time_hr_free(pcmk__time_hr_t *hr_dt);
char *pcmk__time_format_hr(const char *format, pcmk__time_hr_t *hr_dt);
const char *pcmk__epoch2str(time_t *when);
+const char *pcmk__readable_interval(guint interval_ms);
struct pcmk__time_us {
int years;
diff --git a/include/crm/common/logging.h b/include/crm/common/logging.h
index 5580edc..9a0e38a 100644
--- a/include/crm/common/logging.h
+++ b/include/crm/common/logging.h
@@ -105,6 +105,13 @@ void crm_update_callsites(void);
void crm_log_deinit(void);
+/*!
+ * \brief Initializes the logging system and defaults to the least verbose output level
+ *
+ * \param[in] entity If not NULL, will be used as the identity for logging purposes
+ * \param[in] argc The number of command line parameters
+ * \param[in] argv The command line parameter values
+ */
void crm_log_preinit(const char *entity, int argc, char **argv);
gboolean crm_log_init(const char *entity, uint8_t level, gboolean daemon,
gboolean to_stderr, int argc, char **argv, gboolean quiet);
diff --git a/include/crm/common/options_internal.h b/include/crm/common/options_internal.h
index fa2ae24..23906be 100644
--- a/include/crm/common/options_internal.h
+++ b/include/crm/common/options_internal.h
@@ -97,8 +97,8 @@ const char *pcmk__cluster_option(GHashTable *options,
pcmk__cluster_option_t *option_list, int len,
const char *name);
-void pcmk__print_option_metadata(const char *name, const char *version,
- const char *desc_short, const char *desc_long,
+void pcmk__print_option_metadata(const char *name, const char *desc_short,
+ const char *desc_long,
pcmk__cluster_option_t *option_list, int len);
void pcmk__validate_cluster_options(GHashTable *options,
@@ -119,4 +119,17 @@ bool pcmk__get_sbd_sync_resource_startup(void);
long pcmk__auto_watchdog_timeout(void);
bool pcmk__valid_sbd_timeout(const char *value);
+// constants for environment variable names
+#define PCMK__ENV_CLUSTER_TYPE "cluster_type"
+#define PCMK__ENV_QUORUM_TYPE "quorum_type"
+#define PCMK__ENV_DEBUG "debug"
+#define PCMK__ENV_LOGFILE "logfile"
+#define PCMK__ENV_LOGFACILITY "logfacility"
+#define PCMK__ENV_SHUTDOWN_DELAY "shutdown_delay"
+#define PCMK__ENV_NODE_START_STATE "node_start_state"
+#define PCMK__ENV_MCP "mcp"
+#define PCMK__ENV_LOGPRIORITY "logpriority"
+#define PCMK__ENV_STDERR "stderr"
+#define PCMK__ENV_BLACKBOX "blackbox"
+
#endif // PCMK__OPTIONS_INTERNAL__H
diff --git a/include/crm/common/output.h b/include/crm/common/output.h
index 989c94f..325e864 100644
--- a/include/crm/common/output.h
+++ b/include/crm/common/output.h
@@ -40,14 +40,16 @@ typedef enum {
pcmk_section_tickets = 1 << 13,
pcmk_section_bans = 1 << 14,
pcmk_section_failures = 1 << 15,
+ pcmk_section_maint_mode = 1 << 16,
} pcmk_section_e;
#define pcmk_section_fencing_all (pcmk_section_fence_failed | pcmk_section_fence_pending | pcmk_section_fence_worked)
-#define pcmk_section_summary (pcmk_section_stack | pcmk_section_dc | pcmk_section_times | pcmk_section_counts)
+#define pcmk_section_summary (pcmk_section_stack | pcmk_section_dc | pcmk_section_times | \
+ pcmk_section_counts | pcmk_section_maint_mode)
#define pcmk_section_all (pcmk_section_summary | pcmk_section_options | pcmk_section_nodes | \
pcmk_section_resources | pcmk_section_attributes | pcmk_section_failcounts | \
pcmk_section_operations | pcmk_section_fencing_all | pcmk_section_tickets | \
- pcmk_section_bans | pcmk_section_failures)
+ pcmk_section_bans | pcmk_section_failures | pcmk_section_maint_mode)
/*!
* \brief Further modify the output of sections
@@ -62,9 +64,13 @@ typedef enum {
pcmk_show_rscs_by_node = 1 << 6,
pcmk_show_pending = 1 << 7,
pcmk_show_rsc_only = 1 << 8,
+ pcmk_show_failed_detail = 1 << 9,
} pcmk_show_opt_e;
-#define pcmk_show_details (pcmk_show_clone_detail | pcmk_show_node_id | pcmk_show_implicit_rscs)
+#define pcmk_show_details (pcmk_show_clone_detail \
+ | pcmk_show_node_id \
+ | pcmk_show_implicit_rscs \
+ | pcmk_show_failed_detail)
#ifdef __cplusplus
}
diff --git a/include/crm/common/output_internal.h b/include/crm/common/output_internal.h
index f3eaf90..479f0e4 100644
--- a/include/crm/common/output_internal.h
+++ b/include/crm/common/output_internal.h
@@ -27,7 +27,7 @@ extern "C" {
# include <glib.h>
# include <crm/common/results.h>
-# define PCMK__API_VERSION "2.13"
+# define PCMK__API_VERSION "2.14"
#if defined(PCMK__WITH_ATTRIBUTE_OUTPUT_ARGS)
# define PCMK__OUTPUT_ARGS(ARGS...) __attribute__((output_args(ARGS)))
diff --git a/include/crm/common/results.h b/include/crm/common/results.h
index 011faa7..873faf5 100644
--- a/include/crm/common/results.h
+++ b/include/crm/common/results.h
@@ -53,10 +53,11 @@ extern "C" {
* alternative interpretations. The legacy interpration is that the absolute
* value of the return code is either a system error number or a custom
* pcmk_err_* number. This is less than ideal because system error numbers are
- * constrained only to the positive int range, so there's the possibility
- * (though not noticed in the wild) that system errors and custom errors could
- * collide. The new intepretation is that negative values are from the pcmk_rc_e
- * enum, and positive values are system error numbers. Both use 0 for success.
+ * constrained only to the positive int range, so there's the possibility that
+ * system errors and custom errors could collide (which did in fact happen
+ * already on one architecture). The new intepretation is that negative values
+ * are from the pcmk_rc_e enum, and positive values are system error numbers.
+ * Both use 0 for success.
*
* For system error codes, see:
* - /usr/include/asm-generic/errno.h
@@ -84,8 +85,8 @@ extern "C" {
# define pcmk_err_already 215
/* On HPPA 215 is ENOSYM (Unknown error 215), which hopefully never happens. */
#ifdef __hppa__
-# define pcmk_err_bad_nvpair 250 /* 216 is ENOTSOCK */
-# define pcmk_err_unknown_format 252 /* 217 is EDESTADDRREQ */
+# define pcmk_err_bad_nvpair 250 /* 216 is ENOTSOCK */
+# define pcmk_err_unknown_format 252 /* 217 is EDESTADDRREQ */
#else
# define pcmk_err_bad_nvpair 216
# define pcmk_err_unknown_format 217
@@ -107,6 +108,9 @@ enum pcmk_rc_e {
/* When adding new values, use consecutively lower numbers, update the array
* in lib/common/results.c, and test with crm_error.
*/
+ pcmk_rc_invalid_transition = -1031,
+ pcmk_rc_graph_error = -1030,
+ pcmk_rc_dot_error = -1029,
pcmk_rc_underflow = -1028,
pcmk_rc_no_input = -1027,
pcmk_rc_no_output = -1026,
@@ -144,38 +148,55 @@ enum pcmk_rc_e {
// Positive values reserved for system error numbers
};
-/* Uniform exit codes
- * Everything is mapped to its OCF equivalent so that Pacemaker only deals with one set of codes
+
+/*!
+ * \enum ocf_exitcode
+ * \brief Exit status codes for resource agents
+ *
+ * The OCF Resource Agent API standard enumerates the possible exit status codes
+ * that agents should return. Besides being used with OCF agents, these values
+ * are also used by the executor as a universal status for all agent standards;
+ * actual results are mapped to these before returning them to clients.
*/
enum ocf_exitcode {
- PCMK_OCF_OK = 0,
- PCMK_OCF_UNKNOWN_ERROR = 1,
- PCMK_OCF_INVALID_PARAM = 2,
- PCMK_OCF_UNIMPLEMENT_FEATURE = 3,
- PCMK_OCF_INSUFFICIENT_PRIV = 4,
- PCMK_OCF_NOT_INSTALLED = 5,
- PCMK_OCF_NOT_CONFIGURED = 6,
- PCMK_OCF_NOT_RUNNING = 7, /* End of overlap with LSB */
- PCMK_OCF_RUNNING_PROMOTED = 8,
- PCMK_OCF_FAILED_PROMOTED = 9,
-
-
- /* 150-199 reserved for application use */
- PCMK_OCF_CONNECTION_DIED = 189, // Deprecated (see PCMK_LRM_OP_NOT_CONNECTED)
-
- PCMK_OCF_DEGRADED = 190, // Resource active but more likely to fail soon
- PCMK_OCF_DEGRADED_PROMOTED = 191, // Resource promoted but more likely to fail soon
+ PCMK_OCF_OK = 0, //!< Success
+ PCMK_OCF_UNKNOWN_ERROR = 1, //!< Unspecified error
+ PCMK_OCF_INVALID_PARAM = 2, //!< Parameter invalid (in local context)
+ PCMK_OCF_UNIMPLEMENT_FEATURE = 3, //!< Requested action not implemented
+ PCMK_OCF_INSUFFICIENT_PRIV = 4, //!< Insufficient privileges
+ PCMK_OCF_NOT_INSTALLED = 5, //!< Dependencies not available locally
+ PCMK_OCF_NOT_CONFIGURED = 6, //!< Parameter invalid (inherently)
+ PCMK_OCF_NOT_RUNNING = 7, //!< Service safely stopped
+ PCMK_OCF_RUNNING_PROMOTED = 8, //!< Service active and promoted
+ PCMK_OCF_FAILED_PROMOTED = 9, //!< Service failed and possibly in promoted role
+ PCMK_OCF_DEGRADED = 190, //!< Service active but more likely to fail soon
+ PCMK_OCF_DEGRADED_PROMOTED = 191, //!< Service promoted but more likely to fail soon
- PCMK_OCF_EXEC_ERROR = 192, /* Generic problem invoking the agent */
- PCMK_OCF_UNKNOWN = 193, /* State of the service is unknown - used for recording in-flight operations */
- PCMK_OCF_SIGNAL = 194,
- PCMK_OCF_NOT_SUPPORTED = 195,
- PCMK_OCF_PENDING = 196,
- PCMK_OCF_CANCELLED = 197,
- PCMK_OCF_TIMEOUT = 198,
- PCMK_OCF_OTHER_ERROR = 199, /* Keep the same codes as PCMK_LSB */
+ /* These two are Pacemaker extensions, not in the OCF standard. The
+ * controller records PCMK_OCF_UNKNOWN for pending actions.
+ * PCMK_OCF_CONNECTION_DIED is used only with older DCs that don't support
+ * PCMK_EXEC_NOT_CONNECTED.
+ *
+ * @TODO PCMK_OCF_UNKNOWN should be deprecated, and an execution status of
+ * PCMK_EXEC_PENDING relied on instead (though it might be worthwhile to
+ * keep PCMK_OCF_UNKNOWN as an invalid value for initializing new action
+ * objects). However, backward compatibility must be considered (processing
+ * old saved CIB files, rolling upgrades with older DCs, older
+ * Pacemaker Remote nodes or connection hosts, and older bundles).
+ */
+ PCMK_OCF_CONNECTION_DIED = 189, //!< \deprecated See PCMK_EXEC_NOT_CONNECTED
+ PCMK_OCF_UNKNOWN = 193, //!< Action is pending
#if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1)
+ // Former Pacemaker extensions
+ PCMK_OCF_EXEC_ERROR = 192, //!< \deprecated (Unused)
+ PCMK_OCF_SIGNAL = 194, //!< \deprecated (Unused)
+ PCMK_OCF_NOT_SUPPORTED = 195, //!< \deprecated (Unused)
+ PCMK_OCF_PENDING = 196, //!< \deprecated (Unused)
+ PCMK_OCF_CANCELLED = 197, //!< \deprecated (Unused)
+ PCMK_OCF_TIMEOUT = 198, //!< \deprecated (Unused)
+ PCMK_OCF_OTHER_ERROR = 199, //!< \deprecated (Unused)
+
//! \deprecated Use PCMK_OCF_RUNNING_PROMOTED instead
PCMK_OCF_RUNNING_MASTER = PCMK_OCF_RUNNING_PROMOTED,
@@ -187,8 +208,9 @@ enum ocf_exitcode {
#endif
};
-/*
- * Exit status codes
+/*!
+ * \enum crm_exit_e
+ * \brief Exit status codes for tools and daemons
*
* We want well-specified (i.e. OS-invariant) exit status codes for our daemons
* and applications so they can be relied on by callers. (Function return codes
@@ -199,7 +221,7 @@ enum ocf_exitcode {
* OSes we don't support -- for example, OpenVMS considers 1 success!).
*
* For init scripts, the LSB gives meaning to 0-7, and sets aside 150-199 for
- * application use. OCF adds 8-9 and 189-199.
+ * application use. OCF adds 8-9 and 190-191.
*
* sysexits.h was an attempt to give additional meanings, but never really
* caught on. It uses 0 and 64-78.
@@ -214,58 +236,96 @@ enum ocf_exitcode {
*/
typedef enum crm_exit_e {
// Common convention
- CRM_EX_OK = 0,
- CRM_EX_ERROR = 1,
+ CRM_EX_OK = 0, //!< Success
+ CRM_EX_ERROR = 1, //!< Unspecified error
// LSB + OCF
- CRM_EX_INVALID_PARAM = 2,
- CRM_EX_UNIMPLEMENT_FEATURE = 3,
- CRM_EX_INSUFFICIENT_PRIV = 4,
- CRM_EX_NOT_INSTALLED = 5,
- CRM_EX_NOT_CONFIGURED = 6,
- CRM_EX_NOT_RUNNING = 7,
+ CRM_EX_INVALID_PARAM = 2, //!< Parameter invalid (in local context)
+ CRM_EX_UNIMPLEMENT_FEATURE = 3, //!< Requested action not implemented
+ CRM_EX_INSUFFICIENT_PRIV = 4, //!< Insufficient privileges
+ CRM_EX_NOT_INSTALLED = 5, //!< Dependencies not available locally
+ CRM_EX_NOT_CONFIGURED = 6, //!< Parameter invalid (inherently)
+ CRM_EX_NOT_RUNNING = 7, //!< Service safely stopped
// sysexits.h
- CRM_EX_USAGE = 64, // command line usage error
- CRM_EX_DATAERR = 65, // user-supplied data incorrect
- CRM_EX_NOINPUT = 66, // input file not available
- CRM_EX_NOUSER = 67, // user does not exist
- CRM_EX_NOHOST = 68, // host unknown
- CRM_EX_UNAVAILABLE = 69, // needed service unavailable
- CRM_EX_SOFTWARE = 70, // internal software bug
- CRM_EX_OSERR = 71, // external (OS/environmental) problem
- CRM_EX_OSFILE = 72, // system file not usable
- CRM_EX_CANTCREAT = 73, // file couldn't be created
- CRM_EX_IOERR = 74, // file I/O error
- CRM_EX_TEMPFAIL = 75, // try again
- CRM_EX_PROTOCOL = 76, // protocol violated
- CRM_EX_NOPERM = 77, // non-file permission issue
- CRM_EX_CONFIG = 78, // misconfiguration
+ CRM_EX_USAGE = 64, //!< Command line usage error
+ CRM_EX_DATAERR = 65, //!< User-supplied data incorrect
+ CRM_EX_NOINPUT = 66, //!< Input file not available
+ CRM_EX_NOUSER = 67, //!< User does not exist
+ CRM_EX_NOHOST = 68, //!< Host unknown
+ CRM_EX_UNAVAILABLE = 69, //!< Needed service unavailable
+ CRM_EX_SOFTWARE = 70, //!< Internal software bug
+ CRM_EX_OSERR = 71, //!< External (OS/environmental) problem
+ CRM_EX_OSFILE = 72, //!< System file not usable
+ CRM_EX_CANTCREAT = 73, //!< File couldn't be created
+ CRM_EX_IOERR = 74, //!< File I/O error
+ CRM_EX_TEMPFAIL = 75, //!< Try again
+ CRM_EX_PROTOCOL = 76, //!< Protocol violated
+ CRM_EX_NOPERM = 77, //!< Non-file permission issue
+ CRM_EX_CONFIG = 78, //!< Misconfiguration
// Custom
- CRM_EX_FATAL = 100, // do not respawn
- CRM_EX_PANIC = 101, // panic the local host
- CRM_EX_DISCONNECT = 102, // lost connection to something
- CRM_EX_OLD = 103, // update older than existing config
- CRM_EX_DIGEST = 104, // digest comparison failed
- CRM_EX_NOSUCH = 105, // requested item does not exist
- CRM_EX_QUORUM = 106, // local partition does not have quorum
- CRM_EX_UNSAFE = 107, // requires --force or new conditions
- CRM_EX_EXISTS = 108, // requested item already exists
- CRM_EX_MULTIPLE = 109, // requested item has multiple matches
- CRM_EX_EXPIRED = 110, // requested item has expired
- CRM_EX_NOT_YET_IN_EFFECT = 111, // requested item is not in effect
- CRM_EX_INDETERMINATE = 112, // could not determine status
- CRM_EX_UNSATISFIED = 113, // requested item does not satisfy constraints
+ CRM_EX_FATAL = 100, //!< Do not respawn
+ CRM_EX_PANIC = 101, //!< Panic the local host
+ CRM_EX_DISCONNECT = 102, //!< Lost connection to something
+ CRM_EX_OLD = 103, //!< Update older than existing config
+ CRM_EX_DIGEST = 104, //!< Digest comparison failed
+ CRM_EX_NOSUCH = 105, //!< Requested item does not exist
+ CRM_EX_QUORUM = 106, //!< Local partition does not have quorum
+ CRM_EX_UNSAFE = 107, //!< Requires --force or new conditions
+ CRM_EX_EXISTS = 108, //!< Requested item already exists
+ CRM_EX_MULTIPLE = 109, //!< Requested item has multiple matches
+ CRM_EX_EXPIRED = 110, //!< Requested item has expired
+ CRM_EX_NOT_YET_IN_EFFECT = 111, //!< Requested item is not in effect
+ CRM_EX_INDETERMINATE = 112, //!< Could not determine status
+ CRM_EX_UNSATISFIED = 113, //!< Requested item does not satisfy constraints
// Other
- CRM_EX_TIMEOUT = 124, // convention from timeout(1)
- CRM_EX_MAX = 255, // ensure crm_exit_t can hold this
+ CRM_EX_TIMEOUT = 124, //!< Convention from timeout(1)
+
+ /* Anything above 128 overlaps with some shells' use of these values for
+ * "interrupted by signal N", and so may be unreliable when detected by
+ * shell scripts.
+ */
+
+ // OCF Resource Agent API 1.1
+ CRM_EX_DEGRADED = 190, //!< Service active but more likely to fail soon
+ CRM_EX_DEGRADED_PROMOTED = 191, //!< Service promoted but more likely to fail soon
+
+ CRM_EX_MAX = 255, //!< Ensure crm_exit_t can hold this
} crm_exit_t;
+/*!
+ * \enum pcmk_exec_status
+ * \brief Execution status
+ *
+ * These codes are used to specify the result of the attempt to execute an
+ * agent, rather than the agent's result itself.
+ */
+enum pcmk_exec_status {
+ PCMK_EXEC_UNKNOWN = -2, //!< Used only to initialize variables
+ PCMK_EXEC_PENDING = -1, //!< Action is in progress
+ PCMK_EXEC_DONE, //!< Action completed, result is known
+ PCMK_EXEC_CANCELLED, //!< Action was cancelled
+ PCMK_EXEC_TIMEOUT, //!< Action did not complete in time
+ PCMK_EXEC_NOT_SUPPORTED, //!< Agent does not implement requested action
+ PCMK_EXEC_ERROR, //!< Execution failed, may be retried
+ PCMK_EXEC_ERROR_HARD, //!< Execution failed, do not retry on node
+ PCMK_EXEC_ERROR_FATAL, //!< Execution failed, do not retry anywhere
+ PCMK_EXEC_NOT_INSTALLED, //!< Agent or dependency not available locally
+ PCMK_EXEC_NOT_CONNECTED, //!< No connection to executor
+ PCMK_EXEC_INVALID, //!< Action cannot be attempted (e.g. shutdown)
+ PCMK_EXEC_NO_FENCE_DEVICE, //!< No fence device is configured for target
+ PCMK_EXEC_NO_SECRETS, //!< Necessary CIB secrets are unavailable
+
+ // Add new values above here then update this one below
+ PCMK_EXEC_MAX = PCMK_EXEC_NO_SECRETS, //!< Maximum value for this enum
+};
+
const char *pcmk_rc_name(int rc);
const char *pcmk_rc_str(int rc);
crm_exit_t pcmk_rc2exitc(int rc);
+enum ocf_exitcode pcmk_rc2ocf(int rc);
int pcmk_rc2legacy(int rc);
int pcmk_legacy2rc(int legacy_rc);
const char *pcmk_strerror(int rc);
@@ -276,6 +336,27 @@ const char *crm_exit_name(crm_exit_t exit_code);
const char *crm_exit_str(crm_exit_t exit_code);
_Noreturn crm_exit_t crm_exit(crm_exit_t rc);
+static inline const char *
+pcmk_exec_status_str(enum pcmk_exec_status status)
+{
+ switch (status) {
+ case PCMK_EXEC_PENDING: return "pending";
+ case PCMK_EXEC_DONE: return "complete";
+ case PCMK_EXEC_CANCELLED: return "Cancelled";
+ case PCMK_EXEC_TIMEOUT: return "Timed Out";
+ case PCMK_EXEC_NOT_SUPPORTED: return "NOT SUPPORTED";
+ case PCMK_EXEC_ERROR: return "Error";
+ case PCMK_EXEC_ERROR_HARD: return "Hard error";
+ case PCMK_EXEC_ERROR_FATAL: return "Fatal error";
+ case PCMK_EXEC_NOT_INSTALLED: return "Not installed";
+ case PCMK_EXEC_NOT_CONNECTED: return "No executor connection";
+ case PCMK_EXEC_INVALID: return "Cannot execute now";
+ case PCMK_EXEC_NO_FENCE_DEVICE: return "No fence device";
+ case PCMK_EXEC_NO_SECRETS: return "CIB secrets unavailable";
+ default: return "UNKNOWN!";
+ }
+}
+
#ifdef __cplusplus
}
#endif
diff --git a/include/crm/common/results_internal.h b/include/crm/common/results_internal.h
new file mode 100644
index 0000000..804bf2a
--- /dev/null
+++ b/include/crm/common/results_internal.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2020-2021 the Pacemaker project contributors
+ *
+ * The version control history for this file may have further details.
+ *
+ * This source code is licensed under the GNU Lesser General Public License
+ * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
+ */
+
+#ifndef PCMK__COMMON_RESULTS_INTERNAL__H
+#define PCMK__COMMON_RESULTS_INTERNAL__H
+
+#include <glib.h> // GQuark
+
+/* Error domains for use with g_set_error */
+
+GQuark pcmk__rc_error_quark(void);
+GQuark pcmk__exitc_error_quark(void);
+
+#define PCMK__RC_ERROR pcmk__rc_error_quark()
+#define PCMK__EXITC_ERROR pcmk__exitc_error_quark()
+
+/* Action results */
+
+typedef struct {
+ int exit_status; // Child exit status
+ enum pcmk_exec_status execution_status; // Execution status
+ char *exit_reason; // Brief, human-friendly explanation
+ char *action_stdout; // Action output
+ char *action_stderr; // Action error output
+} pcmk__action_result_t;
+
+void pcmk__set_result(pcmk__action_result_t *result, int exit_status,
+ enum pcmk_exec_status exec_status,
+ const char *exit_reason);
+
+void pcmk__set_result_output(pcmk__action_result_t *result,
+ char *out, char *err);
+
+void pcmk__reset_result(pcmk__action_result_t *result);
+
+#endif // PCMK__COMMON_RESULTS_INTERNAL__H
diff --git a/include/crm/common/strings_internal.h b/include/crm/common/strings_internal.h
index 6870798..02cb607 100644
--- a/include/crm/common/strings_internal.h
+++ b/include/crm/common/strings_internal.h
@@ -25,7 +25,8 @@ enum pcmk__str_flags {
pcmk__str_none = 0,
pcmk__str_casei = 1 << 0,
pcmk__str_null_matches = 1 << 1,
- pcmk__str_regex = 1 << 2
+ pcmk__str_regex = 1 << 2,
+ pcmk__str_star_matches = 1 << 3,
};
int pcmk__scan_double(const char *text, double *result,
@@ -117,7 +118,7 @@ pcmk__intkey_table_remove(GHashTable *hash_table, int key)
return g_hash_table_remove(hash_table, GINT_TO_POINTER(key));
}
-gboolean pcmk__str_in_list(GList *lst, const gchar *s, uint32_t flags);
+gboolean pcmk__str_in_list(const gchar *s, GList *lst, uint32_t flags);
bool pcmk__strcase_any_of(const char *s, ...) G_GNUC_NULL_TERMINATED;
bool pcmk__str_any_of(const char *s, ...) G_GNUC_NULL_TERMINATED;
diff --git a/include/crm/common/xml.h b/include/crm/common/xml.h
index 8617d04..4fab7da 100644
--- a/include/crm/common/xml.h
+++ b/include/crm/common/xml.h
@@ -237,6 +237,11 @@ const char *get_schema_name(int version);
const char *xml_latest_schema(void);
gboolean cli_config_update(xmlNode ** xml, int *best_version, gboolean to_logs);
+/*!
+ * \brief Initialize the CRM XML subsystem
+ *
+ * This method sets global XML settings and loads pacemaker schemas into the cache.
+ */
void crm_xml_init(void);
void crm_xml_cleanup(void);
diff --git a/include/crm/common/xml_internal.h b/include/crm/common/xml_internal.h
index 2193b50..09a2767 100644
--- a/include/crm/common/xml_internal.h
+++ b/include/crm/common/xml_internal.h
@@ -277,6 +277,8 @@ pcmk__xe_set_propv(xmlNodePtr node, va_list pairs);
*
* \param[in,out] node XML node to add properties to
* \param[in] ... NULL-terminated list of name/value pairs
+ *
+ * \note A NULL name terminates the arguments; a NULL value will be skipped.
*/
void
pcmk__xe_set_props(xmlNodePtr node, ...)
diff --git a/include/crm/crm.h b/include/crm/crm.h
index ee52c36..b661fd9 100644
--- a/include/crm/crm.h
+++ b/include/crm/crm.h
@@ -64,9 +64,9 @@ extern "C" {
* >=3.0.9: DC will send its own shutdown request to all peers
* XML v2 patchsets are created by default
* >=3.0.13: Fail counts include operation name and interval
- * >=3.2.0: DC supports PCMK_LRM_OP_INVALID and PCMK_LRM_OP_NOT_CONNECTED
+ * >=3.2.0: DC supports PCMK_EXEC_INVALID and PCMK_EXEC_NOT_CONNECTED
*/
-# define CRM_FEATURE_SET "3.10.2"
+# define CRM_FEATURE_SET "3.11.0"
/* Pacemaker's CPG protocols use fixed-width binary fields for the sender and
* recipient of a CPG message. This imposes an arbitrary limit on cluster node
diff --git a/include/crm/fencing/internal.h b/include/crm/fencing/internal.h
index 8bcb544..fa9059e 100644
--- a/include/crm/fencing/internal.h
+++ b/include/crm/fencing/internal.h
@@ -64,11 +64,9 @@ void stonith__action_result(stonith_action_t *action, int *rc, char **output,
int
stonith_action_execute_async(stonith_action_t * action,
void *userdata,
- void (*done) (GPid pid, int rc, const char *output,
- gpointer user_data),
- void (*fork_cb) (GPid pid, gpointer user_data));
-
-int stonith__execute(stonith_action_t *action);
+ void (*done) (int pid, int rc, const char *output,
+ void *user_data),
+ void (*fork_cb) (int pid, void *user_data));
xmlNode *create_level_registration_xml(const char *node, const char *pattern,
const char *attr, const char *value,
@@ -135,6 +133,7 @@ void stonith__device_parameter_flags(uint32_t *device_flags,
# define F_STONITH_ORIGIN "st_origin"
# define F_STONITH_HISTORY_LIST "st_history"
# define F_STONITH_DATE "st_date"
+# define F_STONITH_DATE_NSEC "st_date_nsec"
# define F_STONITH_STATE "st_state"
# define F_STONITH_ACTIVE "st_active"
# define F_STONITH_DIFFERENTIAL "st_differential"
@@ -164,25 +163,10 @@ void stonith__device_parameter_flags(uint32_t *device_flags,
# define STONITH_OP_LEVEL_ADD "st_level_add"
# define STONITH_OP_LEVEL_DEL "st_level_remove"
-# define STONITH_WATCHDOG_AGENT "#watchdog"
-
-# ifdef HAVE_STONITH_STONITH_H
-// utilities from st_lha.c
-int stonith__list_lha_agents(stonith_key_value_t **devices);
-int stonith__lha_metadata(const char *agent, int timeout, char **output);
-bool stonith__agent_is_lha(const char *agent);
-int stonith__lha_validate(stonith_t *st, int call_options, const char *target,
- const char *agent, GHashTable *params,
- int timeout, char **output, char **error_output);
-# endif
-
-// utilities from st_rhcs.c
-int stonith__list_rhcs_agents(stonith_key_value_t **devices);
-int stonith__rhcs_metadata(const char *agent, int timeout, char **output);
-bool stonith__agent_is_rhcs(const char *agent);
-int stonith__rhcs_validate(stonith_t *st, int call_options, const char *target,
- const char *agent, GHashTable *params, const char *host_arg,
- int timeout, char **output, char **error_output);
+# define STONITH_WATCHDOG_AGENT "fence_watchdog"
+/* Don't change 2 below as it would break rolling upgrade */
+# define STONITH_WATCHDOG_AGENT_INTERNAL "#watchdog"
+# define STONITH_WATCHDOG_ID "watchdog"
/* Exported for crm_mon to reference */
int stonith__failed_history(pcmk__output_t *out, va_list args);
@@ -211,4 +195,7 @@ stonith__op_state_pending(enum op_state state)
return state != st_failed && state != st_done;
}
+gboolean stonith__watchdog_fencing_enabled_for_node(const char *node);
+gboolean stonith__watchdog_fencing_enabled_for_node_api(stonith_t *st, const char *node);
+
#endif
diff --git a/include/crm/lrmd.h b/include/crm/lrmd.h
index c532f70..f7b2f3b 100644
--- a/include/crm/lrmd.h
+++ b/include/crm/lrmd.h
@@ -154,10 +154,11 @@ bool lrmd_dispatch(lrmd_t * lrmd);
/*!
* \brief Poll for a specified timeout period to determine if a message
- * is ready for dispatch.
- * \retval 1 msg is ready
- * \retval 0 timeout occurred
- * \retval negative error code
+ * is ready for dispatch
+ *
+ * \retval 1 Message is ready
+ * \retval 0 Timeout occurred
+ * \retval negative errno Error occurred
*/
int lrmd_poll(lrmd_t * lrmd, int timeout);
@@ -301,10 +302,9 @@ typedef struct lrmd_api_operations_s {
int (*connect_async) (lrmd_t * lrmd, const char *client_name, int timeout /*ms */ );
/*!
- * \brief Is connected to lrmd daemon?
+ * \brief Check whether connection to executor daemon is (still) active
*
- * \retval 0, false
- * \retval 1, true
+ * \return 1 if the executor connection is active, 0 otherwise
*/
int (*is_connected) (lrmd_t * lrmd);
@@ -337,10 +337,9 @@ typedef struct lrmd_api_operations_s {
const char *provider, const char *agent, enum lrmd_call_options options);
/*!
- * \brief Retrieve registration info for a rsc
+ * \brief Retrieve a resource's registration information
*
- * \retval info on success
- * \retval NULL on failure
+ * \return Resource information on success, otherwise NULL
*/
lrmd_rsc_info_t *(*get_rsc_info) (lrmd_t * lrmd,
const char *rsc_id, enum lrmd_call_options options);
diff --git a/include/crm/lrmd_internal.h b/include/crm/lrmd_internal.h
index de279a9..284c4d6 100644
--- a/include/crm/lrmd_internal.h
+++ b/include/crm/lrmd_internal.h
@@ -19,6 +19,8 @@
#include <crm/common/remote_internal.h> // pcmk__remote_t
#include <crm/lrmd.h> // lrmd_t, lrmd_event_data_t
+int lrmd__new(lrmd_t **api, const char *nodename, const char *server, int port);
+
int lrmd_send_attribute_alert(lrmd_t *lrmd, GList *alert_list,
const char *node, uint32_t nodeid,
const char *attr_name, const char *attr_value);
@@ -33,6 +35,11 @@ int lrmd_send_resource_alert(lrmd_t *lrmd, GList *alert_list,
int lrmd__remote_send_xml(pcmk__remote_t *session, xmlNode *msg, uint32_t id,
const char *msg_type);
+void lrmd__set_result(lrmd_event_data_t *event, enum ocf_exitcode rc,
+ int op_status, const char *exit_reason);
+
+void lrmd__reset_result(lrmd_event_data_t *event);
+
/* Shared functions for IPC proxy back end */
typedef struct remote_proxy_s {
diff --git a/include/crm/pengine/internal.h b/include/crm/pengine/internal.h
index ff4f7ec..8c8fbac 100644
--- a/include/crm/pengine/internal.h
+++ b/include/crm/pengine/internal.h
@@ -499,15 +499,8 @@ pe_action_t *pe_fence_op(pe_node_t * node, const char *op, bool optional, const
void trigger_unfencing(
pe_resource_t * rsc, pe_node_t *node, const char *reason, pe_action_t *dependency, pe_working_set_t * data_set);
+char *pe__action2reason(pe_action_t *action, enum pe_action_flags flag);
void pe_action_set_reason(pe_action_t *action, const char *reason, bool overwrite);
-void pe_action_set_flag_reason(const char *function, long line, pe_action_t *action, pe_action_t *reason, const char *text, enum pe_action_flags flags, bool overwrite);
-
-#define pe_action_required(action, reason, text) \
- pe_action_set_flag_reason(__func__, __LINE__, action, reason, text, \
- pe_action_optional, FALSE)
-#define pe_action_implies(action, reason, flag) \
- pe_action_set_flag_reason(__func__, __LINE__, action, reason, NULL, \
- flag, FALSE)
void pe__set_resource_flags_recursive(pe_resource_t *rsc, uint64_t flags);
void pe__clear_resource_flags_recursive(pe_resource_t *rsc, uint64_t flags);
diff --git a/include/crm/pengine/pe_types.h b/include/crm/pengine/pe_types.h
index 725b184..babc1ca 100644
--- a/include/crm/pengine/pe_types.h
+++ b/include/crm/pengine/pe_types.h
@@ -75,8 +75,8 @@ enum node_type {
//! \deprecated will be removed in a future release
enum pe_restart {
- pe_restart_restart,
- pe_restart_ignore
+ pe_restart_restart, //! \deprecated will be removed in a future release
+ pe_restart_ignore //! \deprecated will be removed in a future release
};
//! Determine behavior of pe_find_resource_with_flags()
@@ -335,7 +335,6 @@ struct pe_resource_s {
enum rsc_recovery_type recovery_type;
- // @TODO only pe_restart_restart is of interest, so merge into flags
enum pe_restart restart_type; //!< \deprecated will be removed in future release
int priority;
diff --git a/include/crm/services.h b/include/crm/services.h
index 947b4f0..25bb66d 100644
--- a/include/crm/services.h
+++ b/include/crm/services.h
@@ -30,10 +30,6 @@ extern "C" {
# include <crm_config.h> // OCF_ROOT_DIR
# include "common/results.h"
-# ifndef LSB_ROOT_DIR
-# define LSB_ROOT_DIR "/etc/init.d"
-# endif
-
/* TODO: Autodetect these two ?*/
# ifndef SYSTEMCTL
# define SYSTEMCTL "/bin/systemctl"
@@ -83,30 +79,22 @@ enum lsb_status_exitcode {
PCMK_LSB_STATUS_INSUFFICIENT_PRIV = 151,
};
-enum op_status {
- PCMK_LRM_OP_UNKNOWN = -2,
- PCMK_LRM_OP_PENDING = -1,
- PCMK_LRM_OP_DONE,
- PCMK_LRM_OP_CANCELLED,
- PCMK_LRM_OP_TIMEOUT,
- PCMK_LRM_OP_NOTSUPPORTED,
- PCMK_LRM_OP_ERROR,
- PCMK_LRM_OP_ERROR_HARD,
- PCMK_LRM_OP_ERROR_FATAL,
- PCMK_LRM_OP_NOT_INSTALLED,
- PCMK_LRM_OP_NOT_CONNECTED,
- PCMK_LRM_OP_INVALID,
-};
-
enum nagios_exitcode {
NAGIOS_STATE_OK = 0,
NAGIOS_STATE_WARNING = 1,
NAGIOS_STATE_CRITICAL = 2,
NAGIOS_STATE_UNKNOWN = 3,
- NAGIOS_STATE_DEPENDENT = 4,
+ /* This is a custom Pacemaker value (not a nagios convention), used as an
+ * intermediate value between the services library and the executor, so the
+ * executor can map it to the corresponding OCF code.
+ */
NAGIOS_INSUFFICIENT_PRIV = 100,
- NAGIOS_NOT_INSTALLED = 101,
+
+#if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1)
+ NAGIOS_STATE_DEPENDENT = 4, //! \deprecated Unused
+ NAGIOS_NOT_INSTALLED = 101, //! \deprecated Unused
+#endif
};
enum svc_action_flags {
@@ -116,42 +104,79 @@ enum svc_action_flags {
};
typedef struct svc_action_private_s svc_action_private_t;
+
+/*!
+ * \brief Object for executing external actions
+ *
+ * \note This object should never be instantiated directly, but instead created
+ * using one of the constructor functions (resources_action_create() for
+ * resource agents, services_alert_create() for alert agents, or
+ * services_action_create_generic() for generic executables). Similarly,
+ * do not use sizeof() on this struct.
+ *
+ * \internal Internally, services__create_resource_action() is preferable to
+ * resources_action_create().
+ */
typedef struct svc_action_s {
+ /*! Operation key (<resource>_<action>_<interval>) for resource actions,
+ * XML ID for alert actions, or NULL for generic actions
+ */
char *id;
+
+ //! XML ID of resource being executed for resource actions, otherwise NULL
char *rsc;
+
+ //! Name of action being executed for resource actions, otherwise NULL
char *action;
+
+ //! Action interval for recurring resource actions, otherwise 0
guint interval_ms;
+ //! Resource standard for resource actions, otherwise NULL
char *standard;
+
+ //! Resource provider for resource actions that require it, otherwise NULL
char *provider;
+
+ //! Resource agent name for resource actions, otherwise NULL
char *agent;
- int timeout;
- GHashTable *params; /* used for setting up environment for ocf-ra &
- alert agents
- and to be sent via stdin for fence-agents
- */
+ int timeout; //!< Action timeout (in milliseconds)
- int rc;
- int pid;
- int cancel;
- int status;
- int sequence;
- int expected_rc;
- int synchronous;
- enum svc_action_flags flags;
-
- char *stderr_data;
- char *stdout_data;
-
- /*!
- * Data stored by the creator of the action.
- *
- * This may be used to hold data that is needed later on by a callback,
- * for example.
+ /*! A hash table of name/value pairs to use as parameters for resource and
+ * alert actions, otherwise NULL. These will be used to set environment
+ * variables for non-fencing resource agents and alert agents, and to send
+ * stdin to fence agents.
*/
- void *cb_data;
+ GHashTable *params;
+
+ int rc; //!< Exit status of action (set by library upon completion)
+
+ //!@{
+ //! This field should be treated as internal to Pacemaker
+ int pid; // Process ID of child
+ int cancel; // Whether this is a cancellation of a recurring action
+ //!@}
+
+ int status; //!< Execution status (enum pcmk_exec_status set by library)
+ /*! Action counter (set by library for resource actions, or by caller
+ * otherwise)
+ */
+ int sequence;
+
+ //!@{
+ //! This field should be treated as internal to Pacemaker
+ int expected_rc; // Unused
+ int synchronous; // Whether execution should be synchronous (blocking)
+ //!@}
+
+ enum svc_action_flags flags; //!< Flag group of enum svc_action_flags
+ char *stderr_data; //!< Action stderr (set by library)
+ char *stdout_data; //!< Action stdout (set by library)
+ void *cb_data; //!< For caller's use (not used by library)
+
+ //! This field should be treated as internal to Pacemaker
svc_action_private_t *opaque;
} svc_action_t;
@@ -168,14 +193,6 @@ typedef struct svc_action_s {
GList *get_directory_list(const char *root, gboolean files, gboolean executable);
/**
- * Get a list of services
- *
- * \return a list of services. The list items are gchar *. This list _must_
- * be destroyed using g_list_free_full(list, free).
- */
- GList *services_list(void);
-
-/**
* \brief Get a list of providers
*
* \param[in] standard list providers of this standard (e.g. ocf, lsb, etc.)
@@ -215,9 +232,6 @@ typedef struct svc_action_s {
*/
gboolean resources_agent_exists(const char *standard, const char *provider, const char *agent);
-svc_action_t *services_action_create(const char *name, const char *action,
- guint interval_ms, int timeout /* ms */);
-
/**
* \brief Create a new resource action
*
@@ -272,14 +286,14 @@ gboolean services_action_kick(const char *name, const char *action,
gboolean services_action_sync(svc_action_t * op);
/**
- * Run an action asynchronously.
+ * \brief Run an action asynchronously
*
- * \param[in] op services action data
- * \param[in] action_callback callback for when the action completes
- * \param[in] action_fork_callback callback for when action forked successfully
+ * \param[in] op Action to run
+ * \param[in] action_callback Function to call when the action completes
+ * \param[in] action_fork_callback Function to call after action process forks
*
- * \retval TRUE succesfully started execution
- * \retval FALSE failed to start execution, no callback will be received
+ * \return TRUE if execution was successfully initiated, FALSE otherwise (in
+ * which case the callback will not be called)
*/
gboolean services_action_async_fork_notify(svc_action_t * op,
void (*action_callback) (svc_action_t *),
@@ -298,21 +312,8 @@ svc_action_t *services_alert_create(const char *id, const char *exec,
gboolean services_alert_async(svc_action_t *action,
void (*cb)(svc_action_t *op));
- static inline const char *services_lrm_status_str(enum op_status status) {
- switch (status) {
- case PCMK_LRM_OP_PENDING:
- return "pending";
- case PCMK_LRM_OP_DONE:return "complete";
- case PCMK_LRM_OP_CANCELLED:return "Cancelled";
- case PCMK_LRM_OP_TIMEOUT:return "Timed Out";
- case PCMK_LRM_OP_NOTSUPPORTED:return "NOT SUPPORTED";
- case PCMK_LRM_OP_ERROR:return "Error";
- case PCMK_LRM_OP_NOT_INSTALLED:return "Not installed";
- case PCMK_LRM_OP_NOT_CONNECTED:return "No executor connection";
- case PCMK_LRM_OP_INVALID:return "Cannot execute now";
- default:return "UNKNOWN!";
- }
- }
+enum ocf_exitcode services_result2ocf(const char *standard, const char *action,
+ int exit_status);
static inline const char *services_ocf_exitcode_str(enum ocf_exitcode code) {
switch (code) {
@@ -336,61 +337,33 @@ gboolean services_alert_async(svc_action_t *action,
return "promoted";
case PCMK_OCF_FAILED_PROMOTED:
return "promoted (failed)";
- case PCMK_OCF_SIGNAL:
- return "OCF_SIGNAL";
- case PCMK_OCF_NOT_SUPPORTED:
- return "OCF_NOT_SUPPORTED";
- case PCMK_OCF_PENDING:
- return "OCF_PENDING";
- case PCMK_OCF_CANCELLED:
- return "OCF_CANCELLED";
- case PCMK_OCF_TIMEOUT:
- return "OCF_TIMEOUT";
- case PCMK_OCF_OTHER_ERROR:
- return "OCF_OTHER_ERROR";
case PCMK_OCF_DEGRADED:
return "OCF_DEGRADED";
case PCMK_OCF_DEGRADED_PROMOTED:
return "promoted (degraded)";
+
+#if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1)
+ case PCMK_OCF_NOT_SUPPORTED:
+ return "not supported (DEPRECATED STATUS)";
+ case PCMK_OCF_CANCELLED:
+ return "cancelled (DEPRECATED STATUS)";
+ case PCMK_OCF_OTHER_ERROR:
+ return "other error (DEPRECATED STATUS)";
+ case PCMK_OCF_SIGNAL:
+ return "interrupted by signal (DEPRECATED STATUS)";
+ case PCMK_OCF_PENDING:
+ return "pending (DEPRECATED STATUS)";
+ case PCMK_OCF_TIMEOUT:
+ return "timeout (DEPRECATED STATUS)";
+#endif
default:
return "unknown";
}
}
- /**
- * \brief Get OCF equivalent of LSB exit code
- *
- * \param[in] action LSB action that produced exit code
- * \param[in] lsb_exitcode Exit code of LSB action
- *
- * \return PCMK_OCF_* constant that corresponds to LSB exit code
- */
- static inline enum ocf_exitcode
- services_get_ocf_exitcode(const char *action, int lsb_exitcode)
- {
- /* For non-status actions, LSB and OCF share error code meaning <= 7 */
- if (action && strcmp(action, "status") && strcmp(action, "monitor")) {
- if ((lsb_exitcode < 0) || (lsb_exitcode > PCMK_LSB_NOT_RUNNING)) {
- return PCMK_OCF_UNKNOWN_ERROR;
- }
- return (enum ocf_exitcode)lsb_exitcode;
- }
-
- /* status has different return codes */
- switch (lsb_exitcode) {
- case PCMK_LSB_STATUS_OK:
- return PCMK_OCF_OK;
- case PCMK_LSB_STATUS_NOT_INSTALLED:
- return PCMK_OCF_NOT_INSTALLED;
- case PCMK_LSB_STATUS_INSUFFICIENT_PRIV:
- return PCMK_OCF_INSUFFICIENT_PRIV;
- case PCMK_LSB_STATUS_VAR_PID:
- case PCMK_LSB_STATUS_VAR_LOCK:
- case PCMK_LSB_STATUS_NOT_RUNNING:
- return PCMK_OCF_NOT_RUNNING;
- }
- return PCMK_OCF_UNKNOWN_ERROR;
- }
+#if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1)
+#include <crm/services_compat.h>
+#endif
# ifdef __cplusplus
}
diff --git a/include/crm/services_compat.h b/include/crm/services_compat.h
new file mode 100644
index 0000000..32b1cac
--- /dev/null
+++ b/include/crm/services_compat.h
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2010-2021 the Pacemaker project contributors
+ *
+ * The version control history for this file may have further details.
+ *
+ * This source code is licensed under the GNU Lesser General Public License
+ * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
+ */
+
+#ifndef PCMK__SERVICES_COMPAT__H
+# define PCMK__SERVICES_COMPAT__H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * \file
+ * \brief Deprecated services API
+ * \ingroup core
+ * \deprecated Do not include this header directly. The service APIs in this
+ * header, and the header itself, will be removed in a future
+ * release.
+ */
+
+#include <crm/common/results.h>
+
+# ifndef LSB_ROOT_DIR
+ //! \deprecated Do not use
+# define LSB_ROOT_DIR "/etc/init.d"
+# endif
+
+//! \deprecated Use enum pcmk_exec_status instead
+enum op_status {
+ PCMK_LRM_OP_UNKNOWN = PCMK_EXEC_UNKNOWN,
+ PCMK_LRM_OP_PENDING = PCMK_EXEC_PENDING,
+ PCMK_LRM_OP_DONE = PCMK_EXEC_DONE,
+ PCMK_LRM_OP_CANCELLED = PCMK_EXEC_CANCELLED,
+ PCMK_LRM_OP_TIMEOUT = PCMK_EXEC_TIMEOUT,
+ PCMK_LRM_OP_NOTSUPPORTED = PCMK_EXEC_NOT_SUPPORTED,
+ PCMK_LRM_OP_ERROR = PCMK_EXEC_ERROR,
+ PCMK_LRM_OP_ERROR_HARD = PCMK_EXEC_ERROR_HARD,
+ PCMK_LRM_OP_ERROR_FATAL = PCMK_EXEC_ERROR_FATAL,
+ PCMK_LRM_OP_NOT_INSTALLED = PCMK_EXEC_NOT_INSTALLED,
+ PCMK_LRM_OP_NOT_CONNECTED = PCMK_EXEC_NOT_CONNECTED,
+ PCMK_LRM_OP_INVALID = PCMK_EXEC_INVALID,
+};
+
+//! \deprecated Use resources_action_create() instead
+svc_action_t *services_action_create(const char *name, const char *action,
+ guint interval_ms, int timeout);
+
+//! \deprecated Use resources_list_agents() instead
+GList *services_list(void);
+
+//! \deprecated Use pcmk_exec_status_str() instead
+static inline const char *
+services_lrm_status_str(enum op_status status)
+{
+ return pcmk_exec_status_str((enum pcmk_exec_status) status);
+}
+
+//! \deprecated Use services_result2ocf() instead
+static inline enum ocf_exitcode
+services_get_ocf_exitcode(const char *action, int lsb_exitcode)
+{
+ /* For non-status actions, LSB and OCF share error code meaning <= 7 */
+ if (action && strcmp(action, "status") && strcmp(action, "monitor")) {
+ if ((lsb_exitcode < 0) || (lsb_exitcode > PCMK_LSB_NOT_RUNNING)) {
+ return PCMK_OCF_UNKNOWN_ERROR;
+ }
+ return (enum ocf_exitcode)lsb_exitcode;
+ }
+
+ /* status has different return codes */
+ switch (lsb_exitcode) {
+ case PCMK_LSB_STATUS_OK:
+ return PCMK_OCF_OK;
+ case PCMK_LSB_STATUS_NOT_INSTALLED:
+ return PCMK_OCF_NOT_INSTALLED;
+ case PCMK_LSB_STATUS_INSUFFICIENT_PRIV:
+ return PCMK_OCF_INSUFFICIENT_PRIV;
+ case PCMK_LSB_STATUS_VAR_PID:
+ case PCMK_LSB_STATUS_VAR_LOCK:
+ case PCMK_LSB_STATUS_NOT_RUNNING:
+ return PCMK_OCF_NOT_RUNNING;
+ }
+ return PCMK_OCF_UNKNOWN_ERROR;
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/crm/services_internal.h b/include/crm/services_internal.h
index afc3cc8..0b4b15e 100644
--- a/include/crm/services_internal.h
+++ b/include/crm/services_internal.h
@@ -26,7 +26,8 @@ extern "C" {
* \param[in] timeout Consider action failed if it does not complete in this many milliseconds
* \param[in] params Action parameters
*
- * \return newly allocated action instance
+ * \return NULL if not enough memory, otherwise newly allocated action instance
+ * (if its rc member is not PCMK_OCF_UNKNOWN, the action is invalid)
*
* \post After the call, 'params' is owned, and later free'd by the svc_action_t result
* \note The caller is responsible for freeing the return value using
@@ -38,6 +39,14 @@ svc_action_t *services__create_resource_action(const char *name, const char *sta
int timeout /* ms */, GHashTable *params,
enum svc_action_flags flags);
+const char *services__exit_reason(svc_action_t *action);
+char *services__grab_stdout(svc_action_t *action);
+char *services__grab_stderr(svc_action_t *action);
+
+void services__set_result(svc_action_t *action, int agent_status,
+ enum pcmk_exec_status exec_status,
+ const char *exit_reason);
+
# ifdef __cplusplus
}
# endif
diff --git a/include/crm/stonith-ng.h b/include/crm/stonith-ng.h
index 6994a0c..248b3ba 100644
--- a/include/crm/stonith-ng.h
+++ b/include/crm/stonith-ng.h
@@ -110,6 +110,7 @@ typedef struct stonith_history_s {
int state;
time_t completed;
struct stonith_history_s *next;
+ long completed_nsec;
} stonith_history_t;
typedef struct stonith_s stonith_t;
@@ -132,13 +133,17 @@ typedef struct stonith_event_s
/*! The name of the client that initiated the action. */
char *client_origin;
+ //! \internal This field should be treated as internal to Pacemaker
+ void *opaque;
} stonith_event_t;
-typedef struct stonith_callback_data_s
-{
+typedef struct stonith_callback_data_s {
int rc;
int call_id;
void *userdata;
+
+ //! \internal This field should be treated as internal to Pacemaker
+ void *opaque;
} stonith_callback_data_t;
typedef struct stonith_api_operations_s
@@ -151,16 +156,14 @@ typedef struct stonith_api_operations_s
/*!
* \brief Connect to the local stonith daemon.
*
- * \retval 0, success
- * \retval negative error code on failure
+ * \return Legacy Pacemaker return code
*/
int (*connect) (stonith_t *st, const char *name, int *stonith_fd);
/*!
* \brief Disconnect from the local stonith daemon.
*
- * \retval 0, success
- * \retval negative error code on failure
+ * \return Legacy Pacemaker return code
*/
int (*disconnect)(stonith_t *st);
@@ -169,8 +172,7 @@ typedef struct stonith_api_operations_s
*
* \note Synchronous, guaranteed to occur in daemon before function returns.
*
- * \retval 0, success
- * \retval negative error code on failure
+ * \return Legacy Pacemaker return code
*/
int (*remove_device)(
stonith_t *st, int options, const char *name);
@@ -180,8 +182,7 @@ typedef struct stonith_api_operations_s
*
* \note Synchronous, guaranteed to occur in daemon before function returns.
*
- * \retval 0, success
- * \retval negative error code on failure
+ * \return Legacy Pacemaker return code
*/
int (*register_device)(
stonith_t *st, int options, const char *id,
@@ -190,8 +191,7 @@ typedef struct stonith_api_operations_s
/*!
* \brief Remove a fencing level for a specific node.
*
- * \retval 0, success
- * \retval negative error code on failure
+ * \return Legacy Pacemaker return code
*/
int (*remove_level)(
stonith_t *st, int options, const char *node, int level);
@@ -200,8 +200,7 @@ typedef struct stonith_api_operations_s
* \brief Register a fencing level containing the fencing devices to be used
* at that level for a specific node.
*
- * \retval 0, success
- * \retval negative error code on failure
+ * \return Legacy Pacemaker return code
*/
int (*register_level)(
stonith_t *st, int options, const char *node, int level, stonith_key_value_t *device_list);
@@ -211,8 +210,7 @@ typedef struct stonith_api_operations_s
*
* \note Value is returned in output. Output must be freed when set.
*
- * \retval 0 success
- * \retval negative error code on failure
+ * \return Legacy Pacemaker return code
*/
int (*metadata)(stonith_t *st, int options,
const char *device, const char *provider, char **output, int timeout);
@@ -224,8 +222,7 @@ typedef struct stonith_api_operations_s
* \note list must be freed using stonith_key_value_freeall()
* \note call_options parameter is not used, it is reserved for future use.
*
- * \retval num items in list on success
- * \retval negative error code on failure
+ * \return Number of items in list on success, or negative errno otherwise
*/
int (*list_agents)(stonith_t *stonith, int call_options, const char *provider,
stonith_key_value_t **devices, int timeout);
@@ -233,24 +230,21 @@ typedef struct stonith_api_operations_s
/*!
* \brief Retrieve string listing hosts and port assignments from a local stonith device.
*
- * \retval 0 on success
- * \retval negative error code on failure
+ * \return Legacy Pacemaker return code
*/
int (*list)(stonith_t *st, int options, const char *id, char **list_output, int timeout);
/*!
* \brief Check to see if a local stonith device is reachable
*
- * \retval 0 on success
- * \retval negative error code on failure
+ * \return Legacy Pacemaker return code
*/
int (*monitor)(stonith_t *st, int options, const char *id, int timeout);
/*!
* \brief Check to see if a local stonith device's port is reachable
*
- * \retval 0 on success
- * \retval negative error code on failure
+ * \return Legacy Pacemaker return code
*/
int (*status)(stonith_t *st, int options, const char *id, const char *port, int timeout);
@@ -260,8 +254,7 @@ typedef struct stonith_api_operations_s
* \note If node is provided, only devices that can fence the node id
* will be returned.
*
- * \retval num items in list on success
- * \retval negative error code on failure
+ * \return Number of items in list on success, or negative errno otherwise
*/
int (*query)(stonith_t *st, int options, const char *node,
stonith_key_value_t **devices, int timeout);
@@ -278,8 +271,7 @@ typedef struct stonith_api_operations_s
* \param timeout, The default per device timeout to use with each device
* capable of fencing the target.
*
- * \retval 0 success
- * \retval negative error code on failure.
+ * \return Legacy Pacemaker return code
*/
int (*fence)(stonith_t *st, int options, const char *node, const char *action,
int timeout, int tolerance);
@@ -287,16 +279,14 @@ typedef struct stonith_api_operations_s
/*!
* \brief Manually confirm that a node is down.
*
- * \retval 0 success
- * \retval negative error code on failure.
+ * \return Legacy Pacemaker return code
*/
int (*confirm)(stonith_t *st, int options, const char *node);
/*!
* \brief Retrieve a list of fencing operations that have occurred for a specific node.
*
- * \retval 0 success
- * \retval negative error code on failure.
+ * \return Legacy Pacemaker return code
*/
int (*history)(stonith_t *st, int options, const char *node, stonith_history_t **output, int timeout);
@@ -412,8 +402,7 @@ typedef struct stonith_api_operations_s
* \param delay, Apply a fencing delay. Value -1 means disable also any
* static/random fencing delays from pcmk_delay_base/max
*
- * \retval 0 success
- * \retval negative error code on failure.
+ * \return Legacy Pacemaker return code
*/
int (*fence_with_delay)(stonith_t *st, int options, const char *node, const char *action,
int timeout, int tolerance, int delay);
diff --git a/include/pacemaker-internal.h b/include/pacemaker-internal.h
index bf33f3e..0e688bd 100644
--- a/include/pacemaker-internal.h
+++ b/include/pacemaker-internal.h
@@ -19,6 +19,7 @@
# include <pcmki/pcmki_sched_notif.h>
# include <pcmki/pcmki_sched_utils.h>
# include <pcmki/pcmki_scheduler.h>
+# include <pcmki/pcmki_simulate.h>
# include <pcmki/pcmki_transition.h>
#endif
diff --git a/include/pacemaker.h b/include/pacemaker.h
index a6a9d13..a8523c9 100644
--- a/include/pacemaker.h
+++ b/include/pacemaker.h
@@ -27,6 +27,56 @@ extern "C" {
# include <crm/stonith-ng.h>
/*!
+ * \brief Modify operation of running a cluster simulation.
+ */
+enum pcmk_sim_flags {
+ pcmk_sim_none = 0,
+ pcmk_sim_all_actions = 1 << 0,
+ pcmk_sim_show_pending = 1 << 1,
+ pcmk_sim_process = 1 << 2,
+ pcmk_sim_show_scores = 1 << 3,
+ pcmk_sim_show_utilization = 1 << 4,
+ pcmk_sim_simulate = 1 << 5,
+ pcmk_sim_sanitized = 1 << 6,
+ pcmk_sim_verbose = 1 << 7,
+};
+
+/*!
+ * \brief Synthetic cluster events that can be injected into the cluster
+ * for running simulations.
+ */
+typedef struct {
+ /*! A list of node names (gchar *) to simulate bringing online */
+ GList *node_up;
+ /*! A list of node names (gchar *) to simulate bringing offline */
+ GList *node_down;
+ /*! A list of node names (gchar *) to simulate failing */
+ GList *node_fail;
+ /*! A list of operations (gchar *) to inject. The format of these strings
+ * is described in the "Operation Specification" section of crm_simulate
+ * help output.
+ */
+ GList *op_inject;
+ /*! A list of operations (gchar *) that should return a given error code
+ * if they fail. The format of these strings is described in the
+ * "Operation Specification" section of crm_simulate help output.
+ */
+ GList *op_fail;
+ /*! A list of tickets (gchar *) to simulate granting */
+ GList *ticket_grant;
+ /*! A list of tickets (gchar *) to simulate revoking */
+ GList *ticket_revoke;
+ /*! A list of tickets (gchar *) to simulate putting on standby */
+ GList *ticket_standby;
+ /*! A list of tickets (gchar *) to simulate activating */
+ GList *ticket_activate;
+ /*! Does the cluster have an active watchdog device? */
+ char *watchdog;
+ /*! Does the cluster have quorum? */
+ char *quorum;
+} pcmk_injections_t;
+
+/*!
* \brief Get controller status
*
* \param[in,out] xml The destination for the result, as an XML tree.
@@ -48,6 +98,13 @@ int pcmk_controller_status(xmlNodePtr *xml, char *dest_node, unsigned int messag
int pcmk_designated_controller(xmlNodePtr *xml, unsigned int message_timeout_ms);
/*!
+ * \brief Free a :pcmk_injections_t structure
+ *
+ * \param[in,out] injections The structure to be freed
+ */
+void pcmk_free_injections(pcmk_injections_t *injections);
+
+/*!
* \brief Get pacemakerd status
*
* \param[in,out] xml The destination for the result, as an XML tree.
@@ -73,6 +130,40 @@ int pcmk_resource_digests(xmlNodePtr *xml, pe_resource_t *rsc,
pe_node_t *node, GHashTable *overrides,
pe_working_set_t *data_set);
+/**
+ * \brief Simulate a cluster's response to events.
+ *
+ * This high-level function essentially implements crm_simulate(8). It operates
+ * on an input CIB file and various lists of events that can be simulated. It
+ * optionally writes out a variety of artifacts to show the results of the
+ * simulation. Output can be modified with various flags.
+ *
+ * \param[in,out] xml The destination for the result, as an XML tree.
+ * \param[in,out] data_set Working set for the cluster.
+ * \param[in] events A structure containing cluster events
+ * (node up/down, tickets, injected operations)
+ * \param[in] flags A bitfield of :pcmk_sim_flags to modify
+ * operation of the simulation.
+ * \param[in] section_opts Which portions of the cluster status output
+ * should be displayed?
+ * \param[in] use_date The date to set the cluster's time to
+ * (may be NULL).
+ * \param[in] input_file The source CIB file, which may be overwritten by
+ * this function (may be NULL).
+ * \param[in] graph_file Where to write the XML-formatted transition graph
+ * (may be NULL, in which case no file will be
+ * written).
+ * \param[in] dot_file Where to write the dot(1) formatted transition
+ * graph (may be NULL, in which case no file will
+ * be written). See \p pcmk__write_sim_dotfile().
+ *
+ * \return Standard Pacemaker return code
+ */
+int pcmk_simulate(xmlNodePtr *xml, pe_working_set_t *data_set,
+ pcmk_injections_t *injections, unsigned int flags,
+ unsigned int section_opts, char *use_date, char *input_file,
+ char *graph_file, char *dot_file);
+
/*!
* \brief Get nodes list
*
@@ -255,7 +346,6 @@ int pcmk_fence_unregister_level(stonith_t *st, char *target, int fence_level);
int pcmk_fence_validate(xmlNodePtr *xml, stonith_t *st, const char *agent,
const char *id, stonith_key_value_t *params,
unsigned int timeout);
-
#endif
#ifdef __cplusplus
diff --git a/include/pcmki/Makefile.am b/include/pcmki/Makefile.am
index 446c801..2c3ec18 100644
--- a/include/pcmki/Makefile.am
+++ b/include/pcmki/Makefile.am
@@ -18,6 +18,7 @@ noinst_HEADERS = pcmki_error.h \
pcmki_sched_notif.h \
pcmki_sched_utils.h \
pcmki_scheduler.h \
+ pcmki_simulate.h \
pcmki_transition.h
.PHONY: $(ARCHIVE_VERSION)
diff --git a/include/pcmki/pcmki_sched_allocate.h b/include/pcmki/pcmki_sched_allocate.h
index 62fbbeb..090f069 100644
--- a/include/pcmki/pcmki_sched_allocate.h
+++ b/include/pcmki/pcmki_sched_allocate.h
@@ -30,6 +30,26 @@ struct resource_alloc_functions_s {
void (*rsc_colocation_rh) (pe_resource_t *, pe_resource_t *,
pcmk__colocation_t *, pe_working_set_t *);
+ /*!
+ * \internal
+ * \brief Create list of all resources in colocations with a given resource
+ *
+ * Given a resource, create a list of all resources involved in mandatory
+ * colocations with it, whether directly or indirectly via chained colocations.
+ *
+ * \param[in] rsc Resource to add to colocated list
+ * \param[in] orig_rsc Resource originally requested
+ * \param[in] colocated_rscs Existing list
+ *
+ * \return List of given resource and all resources involved in colocations
+ *
+ * \note This function is recursive; top-level callers should pass NULL as
+ * \p colocated_rscs and \p orig_rsc, and the desired resource as
+ * \p rsc. The recursive calls will use other values.
+ */
+ GList *(*colocated_resources)(pe_resource_t *rsc, pe_resource_t *orig_rsc,
+ GList *colocated_rscs);
+
void (*rsc_location) (pe_resource_t *, pe__location_t *);
enum pe_action_flags (*action_flags) (pe_action_t *, pe_node_t *);
@@ -55,14 +75,12 @@ pe_node_t *pcmk__native_allocate(pe_resource_t *rsc, pe_node_t *preferred,
pe_working_set_t *data_set);
extern void native_create_actions(pe_resource_t * rsc, pe_working_set_t * data_set);
extern void native_internal_constraints(pe_resource_t * rsc, pe_working_set_t * data_set);
-void native_rsc_colocation_lh(pe_resource_t *lh_rsc, pe_resource_t *rh_rsc,
+void native_rsc_colocation_lh(pe_resource_t *dependent, pe_resource_t *primary,
pcmk__colocation_t *constraint,
pe_working_set_t *data_set);
-void native_rsc_colocation_rh(pe_resource_t *lh_rsc, pe_resource_t *rh_rsc,
+void native_rsc_colocation_rh(pe_resource_t *dependent, pe_resource_t *primary,
pcmk__colocation_t *constraint,
pe_working_set_t *data_set);
-extern void rsc_ticket_constraint(pe_resource_t * lh_rsc, rsc_ticket_t * rsc_ticket,
- pe_working_set_t * data_set);
extern enum pe_action_flags native_action_flags(pe_action_t * action, pe_node_t * node);
void native_rsc_location(pe_resource_t *rsc, pe__location_t *constraint);
@@ -75,10 +93,10 @@ pe_node_t *pcmk__group_allocate(pe_resource_t *rsc, pe_node_t *preferred,
pe_working_set_t *data_set);
extern void group_create_actions(pe_resource_t * rsc, pe_working_set_t * data_set);
extern void group_internal_constraints(pe_resource_t * rsc, pe_working_set_t * data_set);
-void group_rsc_colocation_lh(pe_resource_t *lh_rsc, pe_resource_t *rh_rsc,
+void group_rsc_colocation_lh(pe_resource_t *dependent, pe_resource_t *primary,
pcmk__colocation_t *constraint,
pe_working_set_t *data_set);
-void group_rsc_colocation_rh(pe_resource_t *lh_rsc, pe_resource_t *rh_rsc,
+void group_rsc_colocation_rh(pe_resource_t *dependent, pe_resource_t *primary,
pcmk__colocation_t *constraint,
pe_working_set_t *data_set);
extern enum pe_action_flags group_action_flags(pe_action_t * action, pe_node_t * node);
@@ -95,12 +113,12 @@ gboolean pcmk__bundle_create_probe(pe_resource_t *rsc, pe_node_t *node,
pe_working_set_t *data_set);
void pcmk__bundle_internal_constraints(pe_resource_t *rsc,
pe_working_set_t *data_set);
-void pcmk__bundle_rsc_colocation_lh(pe_resource_t *lh_rsc,
- pe_resource_t *rh_rsc,
+void pcmk__bundle_rsc_colocation_lh(pe_resource_t *dependent,
+ pe_resource_t *primary,
pcmk__colocation_t *constraint,
pe_working_set_t *data_set);
-void pcmk__bundle_rsc_colocation_rh(pe_resource_t *lh_rsc,
- pe_resource_t *rh_rsc,
+void pcmk__bundle_rsc_colocation_rh(pe_resource_t *dependent,
+ pe_resource_t *primary,
pcmk__colocation_t *constraint,
pe_working_set_t *data_set);
void pcmk__bundle_rsc_location(pe_resource_t *rsc, pe__location_t *constraint);
@@ -113,10 +131,10 @@ pe_node_t *pcmk__clone_allocate(pe_resource_t *rsc, pe_node_t *preferred,
pe_working_set_t *data_set);
extern void clone_create_actions(pe_resource_t * rsc, pe_working_set_t * data_set);
extern void clone_internal_constraints(pe_resource_t * rsc, pe_working_set_t * data_set);
-void clone_rsc_colocation_lh(pe_resource_t *lh_rsc, pe_resource_t *rh_rsc,
+void clone_rsc_colocation_lh(pe_resource_t *dependent, pe_resource_t *primary,
pcmk__colocation_t *constraint,
pe_working_set_t *data_set);
-void clone_rsc_colocation_rh(pe_resource_t *lh_rsc, pe_resource_t *rh_rsc,
+void clone_rsc_colocation_rh(pe_resource_t *dependent, pe_resource_t *primary,
pcmk__colocation_t *constraint,
pe_working_set_t *data_set);
void clone_rsc_location(pe_resource_t *rsc, pe__location_t *constraint);
@@ -132,24 +150,17 @@ pe_node_t *pcmk__set_instance_roles(pe_resource_t *rsc,
void create_promotable_actions(pe_resource_t *rsc, pe_working_set_t *data_set);
void promote_demote_constraints(pe_resource_t *rsc, pe_working_set_t *data_set);
void promotable_constraints(pe_resource_t *rsc, pe_working_set_t *data_set);
-void promotable_colocation_rh(pe_resource_t *lh_rsc, pe_resource_t *rh_rsc,
+void promotable_colocation_rh(pe_resource_t *dependent, pe_resource_t *primary,
pcmk__colocation_t *constraint,
pe_working_set_t *data_set);
/* extern resource_object_functions_t resource_variants[]; */
extern resource_alloc_functions_t resource_class_alloc_functions[];
-extern gboolean unpack_rsc_order(xmlNode * xml_obj, pe_working_set_t * data_set);
-
-extern gboolean unpack_rsc_ticket(xmlNode * xml_obj, pe_working_set_t * data_set);
-
void LogNodeActions(pe_working_set_t * data_set);
void LogActions(pe_resource_t * rsc, pe_working_set_t * data_set);
void pcmk__bundle_log_actions(pe_resource_t *rsc, pe_working_set_t *data_set);
-extern void rsc_stonith_ordering(pe_resource_t * rsc, pe_action_t * stonith_op,
- pe_working_set_t * data_set);
-
enum pe_graph_flags native_update_actions(pe_action_t *first, pe_action_t *then,
pe_node_t *node,
enum pe_action_flags flags,
diff --git a/include/pcmki/pcmki_sched_utils.h b/include/pcmki/pcmki_sched_utils.h
index 1de341f..7100d45 100644
--- a/include/pcmki/pcmki_sched_utils.h
+++ b/include/pcmki/pcmki_sched_utils.h
@@ -17,25 +17,14 @@
#include <crm/pengine/pe_types.h>
#include <crm/pengine/internal.h>
#include <pcmki/pcmki_scheduler.h>
+#include <pcmki/pcmki_transition.h>
+#include <pacemaker.h>
/* Constraint helper functions */
pcmk__colocation_t *invert_constraint(pcmk__colocation_t *constraint);
pe__location_t *copy_constraint(pe__location_t *constraint);
-pe__location_t *rsc2node_new(const char *id, pe_resource_t *rsc, int weight,
- const char *discovery_mode, pe_node_t *node,
- pe_working_set_t *data_set);
-
-void pcmk__new_colocation(const char *id, const char *node_attr, int score,
- pe_resource_t *rsc_lh, pe_resource_t *rsc_rh,
- const char *state_lh, const char *state_rh,
- bool influence, pe_working_set_t *data_set);
-
-extern gboolean rsc_ticket_new(const char *id, pe_resource_t * rsc_lh, pe_ticket_t * ticket,
- const char *state_lh, const char *loss_policy,
- pe_working_set_t * data_set);
-
GHashTable *pcmk__copy_node_table(GHashTable *nodes);
GList *pcmk__copy_node_list(const GList *list, bool reset);
GList *sort_nodes_by_weight(GList *nodes, pe_node_t *active_node,
@@ -62,16 +51,6 @@ enum pe_action_flags summary_action_flags(pe_action_t * action, GList *children,
enum action_tasks clone_child_action(pe_action_t * action);
int copies_per_node(pe_resource_t * rsc);
-enum filter_colocation_res {
- influence_nothing = 0,
- influence_rsc_location,
- influence_rsc_priority,
-};
-
-extern enum filter_colocation_res
-filter_colocation_constraint(pe_resource_t * rsc_lh, pe_resource_t * rsc_rh,
- pcmk__colocation_t *constraint, gboolean preview);
-
extern int compare_capacity(const pe_node_t * node1, const pe_node_t * node2);
extern void calculate_utilization(GHashTable * current_utilization,
GHashTable * utilization, gboolean plus);
@@ -90,14 +69,15 @@ xmlNode *pcmk__create_history_xml(xmlNode *parent, lrmd_event_data_t *event,
# define LOAD_STOPPED "load_stopped"
-void modify_configuration(
- pe_working_set_t * data_set, cib_t *cib,
- const char *quorum, const char *watchdog, GList *node_up, GList *node_down, GList *node_fail,
- GList *op_inject, GList *ticket_grant, GList *ticket_revoke,
- GList *ticket_standby, GList *ticket_activate);
+void modify_configuration(pe_working_set_t *data_set, cib_t *cib,
+ pcmk_injections_t *injections);
-int run_simulation(pe_working_set_t * data_set, cib_t *cib, GList *op_fail_list);
+enum transition_status run_simulation(pe_working_set_t * data_set, cib_t *cib, GList *op_fail_list);
pcmk__output_t *pcmk__new_logger(void);
+bool pcmk__threshold_reached(pe_resource_t *rsc, pe_node_t *node,
+ pe_working_set_t *data_set,
+ pe_resource_t **failed);
+
#endif
diff --git a/include/pcmki/pcmki_scheduler.h b/include/pcmki/pcmki_scheduler.h
index b9d0bf7..336a577 100644
--- a/include/pcmki/pcmki_scheduler.h
+++ b/include/pcmki/pcmki_scheduler.h
@@ -39,14 +39,14 @@ enum pe_weights {
typedef struct {
const char *id;
const char *node_attribute;
- pe_resource_t *rsc_lh;
- pe_resource_t *rsc_rh;
+ pe_resource_t *dependent; // The resource being colocated
+ pe_resource_t *primary; // The resource the dependent is colocated with
- int role_lh;
- int role_rh;
+ int dependent_role; // Colocation applies only if dependent has this role
+ int primary_role; // Colocation applies only if primary has this role
int score;
- bool influence; // Whether rsc_lh should influence active rsc_rh placement
+ bool influence; // Whether dependent influences active primary placement
} pcmk__colocation_t;
enum loss_ticket_policy_e {
@@ -68,40 +68,17 @@ struct rsc_ticket_s {
extern gboolean stage0(pe_working_set_t * data_set);
extern gboolean probe_resources(pe_working_set_t * data_set);
extern gboolean stage2(pe_working_set_t * data_set);
-extern gboolean stage3(pe_working_set_t * data_set);
extern gboolean stage4(pe_working_set_t * data_set);
extern gboolean stage5(pe_working_set_t * data_set);
extern gboolean stage6(pe_working_set_t * data_set);
-extern gboolean stage7(pe_working_set_t * data_set);
extern gboolean stage8(pe_working_set_t * data_set);
-extern gboolean summary(GList *resources);
-
-extern gboolean unpack_constraints(xmlNode * xml_constraints, pe_working_set_t * data_set);
-
-extern gboolean shutdown_constraints(pe_node_t * node, pe_action_t * shutdown_op,
- pe_working_set_t * data_set);
-
-void pcmk__order_vs_fence(pe_action_t *stonith_op, pe_working_set_t *data_set);
-
-extern int custom_action_order(pe_resource_t * lh_rsc, char *lh_task, pe_action_t * lh_action,
- pe_resource_t * rh_rsc, char *rh_task, pe_action_t * rh_action,
- enum pe_ordering type, pe_working_set_t * data_set);
-
-extern int new_rsc_order(pe_resource_t * lh_rsc, const char *lh_task,
- pe_resource_t * rh_rsc, const char *rh_task,
- enum pe_ordering type, pe_working_set_t * data_set);
-
-# define order_start_start(rsc1,rsc2, type) \
- new_rsc_order(rsc1, CRMD_ACTION_START, rsc2, CRMD_ACTION_START, type, data_set)
-# define order_stop_stop(rsc1, rsc2, type) \
- new_rsc_order(rsc1, CRMD_ACTION_STOP, rsc2, CRMD_ACTION_STOP, type, data_set)
+void pcmk__unpack_constraints(pe_working_set_t *data_set);
extern void graph_element_from_action(pe_action_t * action, pe_working_set_t * data_set);
extern void add_maintenance_update(pe_working_set_t *data_set);
xmlNode *pcmk__schedule_actions(pe_working_set_t *data_set, xmlNode *xml_input,
crm_time_t *now);
-bool pcmk__ordering_is_invalid(pe_action_t *action, pe_action_wrapper_t *input);
extern const char *transition_idle_timeout;
@@ -111,7 +88,7 @@ extern const char *transition_idle_timeout;
*
* \param[in] colocation Colocation constraint
* \param[in] rsc Right-hand instance (normally this will be
- * colocation->rsc_rh, which NULL will be treated as,
+ * colocation->primary, which NULL will be treated as,
* but for clones or bundles with multiple instances
* this can be a particular instance)
*
@@ -122,7 +99,7 @@ pcmk__colocation_has_influence(const pcmk__colocation_t *colocation,
const pe_resource_t *rsc)
{
if (rsc == NULL) {
- rsc = colocation->rsc_rh;
+ rsc = colocation->primary;
}
/* The left hand of a colocation influences the right hand's location
diff --git a/include/pcmki/pcmki_simulate.h b/include/pcmki/pcmki_simulate.h
new file mode 100644
index 0000000..7985937
--- /dev/null
+++ b/include/pcmki/pcmki_simulate.h
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2021 the Pacemaker project contributors
+ *
+ * The version control history for this file may have further details.
+ *
+ * This source code is licensed under the GNU Lesser General Public License
+ * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
+ */
+
+#ifndef PCMKI_SIMULATE__H
+# define PCMKI_SIMULATE__H
+
+#include <crm/common/output_internal.h>
+#include <crm/pengine/pe_types.h>
+#include <pacemaker.h>
+#include <stdbool.h>
+
+/**
+ * \brief Write out a file in dot(1) format describing the actions that will
+ * be taken by the scheduler in response to an input CIB file.
+ *
+ * \param[in] data_set Working set for the cluster.
+ * \param[in] dot_file The filename to write.
+ * \param[in] all_actions Write all actions, even those that are optional or
+ * are on unmanaged resources.
+ * \param[in] verbose Add extra information, such as action IDs, to the
+ * output.
+ *
+ * \return Standard Pacemaker return code
+ */
+int pcmk__write_sim_dotfile(pe_working_set_t *data_set, const char *dot_file,
+ bool all_actions, bool verbose);
+
+/**
+ * \brief Profile the configuration updates and scheduler actions in a single
+ * CIB file, printing the profiling timings.
+ *
+ * \note \p data_set->priv must have been set to a valid \p pcmk__output_t
+ * object before this function is called.
+ *
+ * \param[in] xml_file The CIB file to profile.
+ * \param[in] repeat Number of times to run.
+ * \param[in] data_set Working set for the cluster.
+ * \param[in] use_date The date to set the cluster's time to (may
+ * be NULL).
+ */
+void pcmk__profile_file(const char *xml_file, long long repeat, pe_working_set_t *data_set,
+ char *use_date);
+
+/**
+ * \brief Profile the configuration updates and scheduler actions in every
+ * CIB file in a given directory, printing the profiling timings for
+ * each.
+ *
+ * \note \p data_set->priv must have been set to a valid \p pcmk__output_t
+ * object before this function is called.
+ *
+ * \param[in] dir A directory full of CIB files to be profiled.
+ * \param[in] repeat Number of times to run on each input file.
+ * \param[in] data_set Working set for the cluster.
+ * \param[in] use_date The date to set the cluster's time to (may
+ * be NULL).
+ */
+void pcmk__profile_dir(const char *dir, long long repeat, pe_working_set_t *data_set,
+ char *use_date);
+
+/**
+ * \brief Set the date of the cluster, either to the value given by
+ * \p use_date, or to the "execution-date" value in the CIB.
+ *
+ * \note \p data_set->priv must have been set to a valid \p pcmk__output_t
+ * object before this function is called.
+ *
+ * \param[in,out] data_set Working set for the cluster.
+ * \param[in] print_original If \p true, the "execution-date" should
+ * also be printed.
+ * \param[in] use_date The date to set the cluster's time to
+ * (may be NULL).
+ */
+void pcmk__set_effective_date(pe_working_set_t *data_set, bool print_original, char *use_date);
+
+/**
+ * \brief Simulate a cluster's response to events.
+ *
+ * This high-level function essentially implements crm_simulate(8). It operates
+ * on an input CIB file and various lists of events that can be simulated. It
+ * optionally writes out a variety of artifacts to show the results of the
+ * simulation. Output can be modified with various flags.
+ *
+ * \param[in,out] data_set Working set for the cluster.
+ * \param[in,out] out The output functions structure.
+ * \param[in] events A structure containing cluster events
+ * (node up/down, tickets, injected operations)
+ * and related data.
+ * \param[in] flags A bitfield of :pcmk_sim_flags to modify
+ * operation of the simulation.
+ * \param[in] section_opts Which portions of the cluster status output
+ * should be displayed?
+ * \param[in] use_date The date to set the cluster's time to
+ * (may be NULL).
+ * \param[in] input_file The source CIB file, which may be overwritten by
+ * this function (may be NULL).
+ * \param[in] graph_file Where to write the XML-formatted transition graph
+ * (may be NULL, in which case no file will be
+ * written).
+ * \param[in] dot_file Where to write the dot(1) formatted transition
+ * graph (may be NULL, in which case no file will
+ * be written). See \p pcmk__write_sim_dotfile().
+ *
+ * \return Standard Pacemaker return code
+ */
+int pcmk__simulate(pe_working_set_t *data_set, pcmk__output_t *out,
+ pcmk_injections_t *injections, unsigned int flags,
+ unsigned int section_opts, char *use_date, char *input_file,
+ char *graph_file, char *dot_file);
+
+#endif
diff --git a/include/pcmki/pcmki_transition.h b/include/pcmki/pcmki_transition.h
index 1b0682b..e0bb07b 100644
--- a/include/pcmki/pcmki_transition.h
+++ b/include/pcmki/pcmki_transition.h
@@ -28,19 +28,47 @@ typedef enum {
typedef struct te_timer_s crm_action_timer_t;
typedef struct crm_graph_s crm_graph_t;
+enum pcmk__synapse_flags {
+ pcmk__synapse_ready = (1 << 0),
+ pcmk__synapse_failed = (1 << 1),
+ pcmk__synapse_executed = (1 << 2),
+ pcmk__synapse_confirmed = (1 << 3),
+};
+
typedef struct synapse_s {
int id;
int priority;
- gboolean ready;
- gboolean failed;
- gboolean executed;
- gboolean confirmed;
+ uint32_t flags; // Group of pcmk__synapse_flags
GList *actions; /* crm_action_t* */
GList *inputs; /* crm_action_t* */
} synapse_t;
+const char *synapse_state_str(synapse_t *synapse);
+
+#define pcmk__set_synapse_flags(synapse, flags_to_set) do { \
+ (synapse)->flags = pcmk__set_flags_as(__func__, __LINE__, \
+ LOG_TRACE, \
+ "Synapse", "synapse", \
+ (synapse)->flags, (flags_to_set), #flags_to_set); \
+ } while (0)
+
+#define pcmk__clear_synapse_flags(synapse, flags_to_clear) do { \
+ (synapse)->flags = pcmk__clear_flags_as(__func__, __LINE__, \
+ LOG_TRACE, \
+ "Synapse", "synapse", \
+ (synapse)->flags, (flags_to_clear), #flags_to_clear); \
+ } while (0)
+
+enum pcmk__graph_action_flags {
+ pcmk__graph_action_sent_update = (1 << 0), /* sent to the CIB */
+ pcmk__graph_action_executed = (1 << 1), /* sent to the CRM */
+ pcmk__graph_action_confirmed = (1 << 2),
+ pcmk__graph_action_failed = (1 << 3),
+ pcmk__graph_action_can_fail = (1 << 4), //! \deprecated Will be removed in a future release
+};
+
typedef struct crm_action_s {
int id;
int timeout;
@@ -51,17 +79,28 @@ typedef struct crm_action_s {
crm_action_timer_t *timer;
synapse_t *synapse;
- gboolean sent_update; /* sent to the CIB */
- gboolean executed; /* sent to the CRM */
- gboolean confirmed;
-
- gboolean failed;
- gboolean can_fail; //! \deprecated Will be removed in a future release
+ uint32_t flags; // Group of pcmk__graph_action_flags
xmlNode *xml;
} crm_action_t;
+const char *action_state_str(crm_action_t *action);
+
+#define crm__set_graph_action_flags(action, flags_to_set) do { \
+ (action)->flags = pcmk__set_flags_as(__func__, __LINE__, \
+ LOG_TRACE, \
+ "Action", "action", \
+ (action)->flags, (flags_to_set), #flags_to_set); \
+ } while (0)
+
+#define crm__clear_graph_action_flags(action, flags_to_clear) do { \
+ (action)->flags = pcmk__clear_flags_as(__func__, __LINE__, \
+ LOG_TRACE, \
+ "Action", "action", \
+ (action)->flags, (flags_to_clear), #flags_to_clear); \
+ } while (0)
+
struct te_timer_s {
int source_id;
int timeout;
@@ -121,20 +160,18 @@ enum transition_status {
transition_failed,
};
-void set_default_graph_functions(void);
-void set_graph_functions(crm_graph_functions_t * fns);
-crm_graph_t *unpack_graph(xmlNode * xml_graph, const char *reference);
-int run_graph(crm_graph_t * graph);
-gboolean update_graph(crm_graph_t * graph, crm_action_t * action);
-void destroy_graph(crm_graph_t * graph);
-const char *transition_status(enum transition_status state);
-void print_graph(unsigned int log_level, crm_graph_t * graph);
-void print_action(int log_level, const char *prefix, crm_action_t * action);
-bool update_abort_priority(crm_graph_t * graph, int priority,
- enum transition_action action, const char *abort_reason);
-const char *actiontype2text(action_type_e type);
-lrmd_event_data_t *convert_graph_action(xmlNode * resource, crm_action_t * action, int status,
- int rc);
+void pcmk__set_graph_functions(crm_graph_functions_t *fns);
+crm_graph_t *pcmk__unpack_graph(xmlNode *xml_graph, const char *reference);
+enum transition_status pcmk__execute_graph(crm_graph_t *graph);
+void pcmk__update_graph(crm_graph_t *graph, crm_action_t *action);
+void pcmk__free_graph(crm_graph_t *graph);
+const char *pcmk__graph_status2text(enum transition_status state);
+void pcmk__log_graph(unsigned int log_level, crm_graph_t *graph);
+void pcmk__log_graph_action(int log_level, crm_action_t *action);
+lrmd_event_data_t *pcmk__event_from_graph_action(xmlNode *resource,
+ crm_action_t *action,
+ int status, int rc,
+ const char *exit_reason);
#ifdef __cplusplus
}
diff --git a/lib/cib/Makefile.am b/lib/cib/Makefile.am
index 584bcf9..029dd0f 100644
--- a/lib/cib/Makefile.am
+++ b/lib/cib/Makefile.am
@@ -15,7 +15,7 @@ lib_LTLIBRARIES = libcib.la
libcib_la_SOURCES = cib_ops.c cib_utils.c cib_client.c cib_native.c cib_attrs.c
libcib_la_SOURCES += cib_file.c cib_remote.c
-libcib_la_LDFLAGS = -version-info 29:0:2
+libcib_la_LDFLAGS = -version-info 29:1:2
libcib_la_CPPFLAGS = -I$(top_srcdir) $(AM_CPPFLAGS)
libcib_la_CFLAGS = $(CFLAGS_HARDENED_LIB)
diff --git a/lib/cib/cib_client.c b/lib/cib/cib_client.c
index 077e3da..a3059bb 100644
--- a/lib/cib/cib_client.c
+++ b/lib/cib/cib_client.c
@@ -25,7 +25,7 @@
#include <crm/msg_xml.h>
#include <crm/common/xml.h>
-GHashTable *cib_op_callback_table = NULL;
+static GHashTable *cib_op_callback_table = NULL;
int cib_client_set_op_callback(cib_t * cib, void (*callback) (const xmlNode * msg, int call_id,
int rc, xmlNode * output));
@@ -355,6 +355,10 @@ cib_new_variant(void)
new_cib = calloc(1, sizeof(cib_t));
+ if (new_cib == NULL) {
+ return NULL;
+ }
+
remove_cib_op_callback(0, TRUE); /* remove all */
new_cib->call_id = 1;
@@ -370,6 +374,11 @@ cib_new_variant(void)
/* the rest will get filled in by the variant constructor */
new_cib->cmds = calloc(1, sizeof(cib_api_operations_t));
+ if (new_cib->cmds == NULL) {
+ free(new_cib);
+ return NULL;
+ }
+
new_cib->cmds->set_op_callback = cib_client_set_op_callback;
new_cib->cmds->add_notify_callback = cib_client_add_notify_callback;
new_cib->cmds->del_notify_callback = cib_client_del_notify_callback;
@@ -690,3 +699,9 @@ cib_dump_pending_callbacks(void)
}
return g_hash_table_foreach(cib_op_callback_table, cib_dump_pending_op, NULL);
}
+
+cib_callback_client_t*
+cib__lookup_id (int call_id)
+{
+ return pcmk__intkey_table_lookup(cib_op_callback_table, call_id);
+} \ No newline at end of file
diff --git a/lib/cib/cib_file.c b/lib/cib/cib_file.c
index cd0ab53..e7160cf 100644
--- a/lib/cib/cib_file.c
+++ b/lib/cib/cib_file.c
@@ -497,8 +497,16 @@ cib_file_new(const char *cib_location)
cib_file_opaque_t *private = NULL;
cib_t *cib = cib_new_variant();
+ if (cib == NULL) {
+ return NULL;
+ }
+
private = calloc(1, sizeof(cib_file_opaque_t));
- CRM_ASSERT((cib != NULL) && (private != NULL));
+
+ if (private == NULL) {
+ free(cib);
+ return NULL;
+ }
cib->variant = cib_file;
cib->variant_opaque = private;
diff --git a/lib/cib/cib_native.c b/lib/cib/cib_native.c
index 5a39180..a21a5e4 100644
--- a/lib/cib/cib_native.c
+++ b/lib/cib/cib_native.c
@@ -58,8 +58,17 @@ cib_native_new(void)
cib_native_opaque_t *native = NULL;
cib_t *cib = cib_new_variant();
+ if (cib == NULL) {
+ return NULL;
+ }
+
native = calloc(1, sizeof(cib_native_opaque_t));
+ if (native == NULL) {
+ free(cib);
+ return NULL;
+ }
+
cib->variant = cib_native;
cib->variant_opaque = native;
diff --git a/lib/cib/cib_remote.c b/lib/cib/cib_remote.c
index 3fe00af..2576fcc 100644
--- a/lib/cib/cib_remote.c
+++ b/lib/cib/cib_remote.c
@@ -109,8 +109,17 @@ cib_remote_new(const char *server, const char *user, const char *passwd, int por
cib_remote_opaque_t *private = NULL;
cib_t *cib = cib_new_variant();
+ if (cib == NULL) {
+ return NULL;
+ }
+
private = calloc(1, sizeof(cib_remote_opaque_t));
+ if (private == NULL) {
+ free(cib);
+ return NULL;
+ }
+
cib->variant = cib_remote;
cib->variant_opaque = private;
diff --git a/lib/cib/cib_utils.c b/lib/cib/cib_utils.c
index 15ab0b8..fbfdc92 100644
--- a/lib/cib/cib_utils.c
+++ b/lib/cib/cib_utils.c
@@ -20,7 +20,6 @@
#include <crm/crm.h>
#include <crm/cib/internal.h>
#include <crm/msg_xml.h>
-#include <crm/common/iso8601_internal.h>
#include <crm/common/xml.h>
#include <crm/common/xml_internal.h>
#include <crm/pengine/rules.h>
@@ -555,7 +554,8 @@ cib_native_callback(cib_t * cib, xmlNode * msg, int call_id, int rc)
output = get_message_xml(msg, F_CIB_CALLDATA);
}
- blob = pcmk__intkey_table_lookup(cib_op_callback_table, call_id);
+ blob = cib__lookup_id(call_id);
+
if (blob == NULL) {
crm_trace("No callback found for call %d", call_id);
}
@@ -647,7 +647,7 @@ static pcmk__cluster_option_t cib_opts[] = {
void
cib_metadata(void)
{
- pcmk__print_option_metadata("pacemaker-based", "1.0",
+ pcmk__print_option_metadata("pacemaker-based",
"Cluster Information Base manager options",
"Cluster options used by Pacemaker's "
"Cluster Information Base manager",
@@ -785,3 +785,41 @@ cib_apply_patch_event(xmlNode *event, xmlNode *input, xmlNode **output,
}
return rc;
}
+
+int
+cib__signon_query(cib_t **cib, xmlNode **cib_object)
+{
+ int rc = pcmk_rc_ok;
+ cib_t *cib_conn = NULL;
+
+ if (cib == NULL) {
+ cib_conn = cib_new();
+ } else {
+ *cib = cib_new();
+ cib_conn = *cib;
+ }
+
+ if (cib_conn == NULL) {
+ return ENOMEM;
+ }
+
+ rc = cib_conn->cmds->signon(cib_conn, crm_system_name, cib_command);
+ rc = pcmk_legacy2rc(rc);
+
+ if (rc == pcmk_rc_ok) {
+ rc = cib_conn->cmds->query(cib_conn, NULL, cib_object, cib_scope_local | cib_sync_call);
+ rc = pcmk_legacy2rc(rc);
+ }
+
+ if (cib == NULL) {
+ cib_conn->cmds->signoff(cib_conn);
+ cib_delete(cib_conn);
+ cib_conn = NULL;
+ }
+
+ if (cib_object == NULL) {
+ return pcmk_rc_no_input;
+ } else {
+ return rc;
+ }
+}
diff --git a/lib/cluster/Makefile.am b/lib/cluster/Makefile.am
index 6538849..f783b56 100644
--- a/lib/cluster/Makefile.am
+++ b/lib/cluster/Makefile.am
@@ -13,7 +13,7 @@ noinst_HEADERS = crmcluster_private.h
## libraries
lib_LTLIBRARIES = libcrmcluster.la
-libcrmcluster_la_LDFLAGS = -version-info 29:7:0
+libcrmcluster_la_LDFLAGS = -version-info 29:8:0
libcrmcluster_la_CFLAGS = $(CFLAGS_HARDENED_LIB)
libcrmcluster_la_LDFLAGS += $(LDFLAGS_HARDENED_LIB)
diff --git a/lib/cluster/cluster.c b/lib/cluster/cluster.c
index ddd8192..0919251 100644
--- a/lib/cluster/cluster.c
+++ b/lib/cluster/cluster.c
@@ -319,7 +319,7 @@ get_cluster_type(void)
return cluster_type;
}
- cluster = pcmk__env_option("cluster_type");
+ cluster = pcmk__env_option(PCMK__ENV_CLUSTER_TYPE);
#if SUPPORT_COROSYNC
/* If nothing is defined in the environment, try corosync (if supported) */
diff --git a/lib/cluster/corosync.c b/lib/cluster/corosync.c
index d7bd453..6e3f742 100644
--- a/lib/cluster/corosync.c
+++ b/lib/cluster/corosync.c
@@ -1,5 +1,5 @@
/*
- * Copyright 2004-2020 the Pacemaker project contributors
+ * Copyright 2004-2021 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
@@ -58,7 +58,7 @@ pcmk__corosync_uuid(crm_node_t *node)
if (node->id > 0) {
return crm_strdup_printf("%u", node->id);
} else {
- crm_info("Node %s is not yet known by corosync", node->uname);
+ crm_info("Node %s is not yet known by Corosync", node->uname);
}
}
return NULL;
@@ -420,7 +420,7 @@ pcmk__corosync_quorum_connect(gboolean (*dispatch)(unsigned long long,
if (quorate) {
crm_notice("Quorum acquired");
} else {
- crm_warn("Quorum lost");
+ crm_warn("No quorum");
}
quorum_app_callback = dispatch;
crm_have_quorum = quorate;
@@ -607,7 +607,7 @@ pcmk__corosync_add_nodes(xmlNode *xml_parent)
}
crm_peer_init();
- crm_trace("Initializing corosync nodelist");
+ crm_trace("Initializing Corosync node list");
for (lpc = 0; TRUE; lpc++) {
uint32_t nodeid = 0;
char *name = NULL;
diff --git a/lib/cluster/cpg.c b/lib/cluster/cpg.c
index 67d26ad..ec2cb19 100644
--- a/lib/cluster/cpg.c
+++ b/lib/cluster/cpg.c
@@ -1,5 +1,5 @@
/*
- * Copyright 2004-2020 the Pacemaker project contributors
+ * Copyright 2004-2021 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
@@ -252,13 +252,10 @@ crm_cs_flush(gpointer data)
}
queue_len -= sent;
- if ((sent > 1) || (cs_message_queue != NULL)) {
- crm_info("Sent %u CPG messages (%d remaining): %s (%d)",
- sent, queue_len, pcmk__cs_err_str(rc), (int) rc);
- } else {
- crm_trace("Sent %u CPG messages (%d remaining): %s (%d)",
- sent, queue_len, pcmk__cs_err_str(rc), (int) rc);
- }
+ do_crm_log((queue_len > 5)? LOG_INFO : LOG_TRACE,
+ "Sent %u CPG message%s (%d still queued): %s (rc=%d)",
+ sent, pcmk__plural_s(sent), queue_len, pcmk__cs_err_str(rc),
+ (int) rc);
if (cs_message_queue) {
uint32_t delay_ms = 100;
@@ -614,6 +611,67 @@ peer_name(crm_node_t *peer)
}
/*!
+ * \internal
+ * \brief Process a CPG peer's leaving the cluster
+ *
+ * \param[in] cpg_group_name CPG group name (for logging)
+ * \param[in] event_counter Event number (for logging)
+ * \param[in] local_nodeid Node ID of local node
+ * \param[in] cpg_peer CPG peer that left
+ * \param[in] sorted_member_list List of remaining members, qsort()-ed by ID
+ * \param[in] member_list_entries Number of entries in \p sorted_member_list
+ */
+static void
+node_left(const char *cpg_group_name, int event_counter,
+ uint32_t local_nodeid, const struct cpg_address *cpg_peer,
+ const struct cpg_address **sorted_member_list,
+ size_t member_list_entries)
+{
+ crm_node_t *peer = pcmk__search_cluster_node_cache(cpg_peer->nodeid,
+ NULL);
+ const struct cpg_address **rival = NULL;
+
+ /* Most CPG-related Pacemaker code assumes that only one process on a node
+ * can be in the process group, but Corosync does not impose this
+ * limitation, and more than one can be a member in practice due to a
+ * daemon attempting to start while another instance is already running.
+ *
+ * Check for any such duplicate instances, because we don't want to process
+ * their leaving as if our actual peer left. If the peer that left still has
+ * an entry in sorted_member_list (with a different PID), we will ignore the
+ * leaving.
+ *
+ * @TODO Track CPG members' PIDs so we can tell exactly who left.
+ */
+ if (peer != NULL) {
+ rival = bsearch(&cpg_peer, sorted_member_list, member_list_entries,
+ sizeof(const struct cpg_address *),
+ cmp_member_list_nodeid);
+ }
+
+ if (rival == NULL) {
+ crm_info("Group %s event %d: %s (node %u pid %u) left%s",
+ cpg_group_name, event_counter, peer_name(peer),
+ cpg_peer->nodeid, cpg_peer->pid,
+ cpgreason2str(cpg_peer->reason));
+ if (peer != NULL) {
+ crm_update_peer_proc(__func__, peer, crm_proc_cpg,
+ OFFLINESTATUS);
+ }
+ } else if (cpg_peer->nodeid == local_nodeid) {
+ crm_warn("Group %s event %d: duplicate local pid %u left%s",
+ cpg_group_name, event_counter,
+ cpg_peer->pid, cpgreason2str(cpg_peer->reason));
+ } else {
+ crm_warn("Group %s event %d: "
+ "%s (node %u) duplicate pid %u left%s (%u remains)",
+ cpg_group_name, event_counter, peer_name(peer),
+ cpg_peer->nodeid, cpg_peer->pid,
+ cpgreason2str(cpg_peer->reason), (*rival)->pid);
+ }
+}
+
+/*!
* \brief Handle a CPG configuration change event
*
* \param[in] handle CPG connection
@@ -636,7 +694,7 @@ pcmk_cpg_membership(cpg_handle_t handle,
gboolean found = FALSE;
static int counter = 0;
uint32_t local_nodeid = get_local_nodeid(handle);
- const struct cpg_address *key, **sorted;
+ const struct cpg_address **sorted;
sorted = malloc(member_list_entries * sizeof(const struct cpg_address *));
CRM_ASSERT(sorted != NULL);
@@ -649,52 +707,8 @@ pcmk_cpg_membership(cpg_handle_t handle,
cmp_member_list_nodeid);
for (i = 0; i < left_list_entries; i++) {
- crm_node_t *peer = pcmk__search_cluster_node_cache(left_list[i].nodeid,
- NULL);
- const struct cpg_address **rival = NULL;
-
- /* in CPG world, NODE:PROCESS-IN-MEMBERSHIP-OF-G is an 1:N relation
- and not playing by this rule may go wild in case of multiple
- residual instances of the same pacemaker daemon at the same node
- -- we must ensure that the possible local rival(s) won't make us
- cry out and bail (e.g. when they quit themselves), since all the
- surrounding logic denies this simple fact that the full membership
- is discriminated also per the PID of the process beside mere node
- ID (and implicitly, group ID); practically, this will be sound in
- terms of not preventing progress, since all the CPG joiners are
- also API end-point carriers, and that's what matters locally
- (who's the winner);
- remotely, we will just compare leave_list and member_list and if
- the left process has its node retained in member_list (under some
- other PID, anyway) we will just ignore it as well
- XXX: long-term fix is to establish in-out PID-aware tracking? */
- if (peer) {
- key = &left_list[i];
- rival = bsearch(&key, sorted, member_list_entries,
- sizeof(const struct cpg_address *),
- cmp_member_list_nodeid);
- }
-
- if (rival == NULL) {
- crm_info("Group %s event %d: %s (node %u pid %u) left%s",
- groupName->value, counter, peer_name(peer),
- left_list[i].nodeid, left_list[i].pid,
- cpgreason2str(left_list[i].reason));
- if (peer) {
- crm_update_peer_proc(__func__, peer, crm_proc_cpg,
- OFFLINESTATUS);
- }
- } else if (left_list[i].nodeid == local_nodeid) {
- crm_warn("Group %s event %d: duplicate local pid %u left%s",
- groupName->value, counter,
- left_list[i].pid, cpgreason2str(left_list[i].reason));
- } else {
- crm_warn("Group %s event %d: "
- "%s (node %u) duplicate pid %u left%s (%u remains)",
- groupName->value, counter, peer_name(peer),
- left_list[i].nodeid, left_list[i].pid,
- cpgreason2str(left_list[i].reason), (*rival)->pid);
- }
+ node_left(groupName->value, counter, local_nodeid, &left_list[i],
+ sorted, member_list_entries);
}
free(sorted);
sorted = NULL;
@@ -710,7 +724,7 @@ pcmk_cpg_membership(cpg_handle_t handle,
if (member_list[i].nodeid == local_nodeid
&& member_list[i].pid != getpid()) {
- /* see the note above */
+ // See the note in node_left()
crm_warn("Group %s event %d: detected duplicate local pid %u",
groupName->value, counter, member_list[i].pid);
continue;
diff --git a/lib/cluster/membership.c b/lib/cluster/membership.c
index 4982b60..c8f1bf0 100644
--- a/lib/cluster/membership.c
+++ b/lib/cluster/membership.c
@@ -1288,6 +1288,7 @@ pcmk__search_known_node_cache(unsigned int id, const char *uname,
// Deprecated functions kept only for backward API compatibility
+// LCOV_EXCL_START
#include <crm/cluster/compat.h>
@@ -1303,4 +1304,5 @@ crm_terminate_member_no_mainloop(int nodeid, const char *uname, int *connection)
return stonith_api_kick(nodeid, uname, 120, TRUE);
}
+// LCOV_EXCL_STOP
// End deprecated API
diff --git a/lib/common/Makefile.am b/lib/common/Makefile.am
index 2fa1c1d..5b0d069 100644
--- a/lib/common/Makefile.am
+++ b/lib/common/Makefile.am
@@ -7,6 +7,7 @@
# or later (GPLv2+) WITHOUT ANY WARRANTY.
#
include $(top_srcdir)/mk/common.mk
+include $(top_srcdir)/lib/common/mock.mk
AM_CPPFLAGS += -I$(top_builddir)/lib/gnu -I$(top_srcdir)/lib/gnu
@@ -14,6 +15,7 @@ MOSTLYCLEANFILES = md5.c
## libraries
lib_LTLIBRARIES = libcrmcommon.la
+check_LTLIBRARIES = libcrmcommon_test.la
# Disable -Wcast-qual if used, because we do some hacky casting,
# and because libxml2 has some signatures that should be const but aren't
@@ -24,11 +26,15 @@ lib_LTLIBRARIES = libcrmcommon.la
CFLAGS = $(CFLAGS_COPY:-Wcast-qual=) -fPIC
-SUBDIRS = tests
+# Without "." here, check-recursive will run through the subdirectories first
+# and then run "make check" here. This will fail, because there's things in
+# the subdirectories that need check_LTLIBRARIES built first. Adding "." here
+# changes the order so the subdirectories are processed afterwards.
+SUBDIRS = . tests
-noinst_HEADERS = crmcommon_private.h
+noinst_HEADERS = crmcommon_private.h mock_private.h
-libcrmcommon_la_LDFLAGS = -version-info 41:0:7
+libcrmcommon_la_LDFLAGS = -version-info 42:0:8
libcrmcommon_la_CFLAGS = $(CFLAGS_HARDENED_LIB)
libcrmcommon_la_LDFLAGS += $(LDFLAGS_HARDENED_LIB)
@@ -92,6 +98,13 @@ libcrmcommon_la_SOURCES += xpath.c
# file, which may have already been cleaned.
nodist_libcrmcommon_la_SOURCES = md5.c
+libcrmcommon_test_la_SOURCES = $(libcrmcommon_la_SOURCES)
+libcrmcommon_test_la_SOURCES += mock.c
+libcrmcommon_test_la_LDFLAGS = $(LDFLAGS_HARDENED_LIB) $(WRAPPED_FLAGS)
+libcrmcommon_test_la_CFLAGS = $(libcrmcommon_la_CFLAGS)
+libcrmcommon_test_la_LIBADD = $(libcrmcommon_la_LIBADD)
+nodist_libcrmcommon_test_la_SOURCES = $(nodist_libcrmcommon_la_SOURCES)
+
md5.c: ../gnu/md5.c
cp "$<" "$@"
diff --git a/lib/common/acl.c b/lib/common/acl.c
index 740312b..90253b0 100644
--- a/lib/common/acl.c
+++ b/lib/common/acl.c
@@ -185,13 +185,13 @@ parse_acl_entry(xmlNode *acl_top, xmlNode *acl_entry, GList *acls)
}
} else if (strcmp(XML_ACL_TAG_READ, tag) == 0) {
- acls = create_acl(child, acls, xpf_acl_read);
+ acls = create_acl(child, acls, pcmk__xf_acl_read);
} else if (strcmp(XML_ACL_TAG_WRITE, tag) == 0) {
- acls = create_acl(child, acls, xpf_acl_write);
+ acls = create_acl(child, acls, pcmk__xf_acl_write);
} else if (strcmp(XML_ACL_TAG_DENY, tag) == 0) {
- acls = create_acl(child, acls, xpf_acl_deny);
+ acls = create_acl(child, acls, pcmk__xf_acl_deny);
} else {
crm_warn("Ignoring unknown ACL %s '%s'",
@@ -227,13 +227,13 @@ parse_acl_entry(xmlNode *acl_top, xmlNode *acl_entry, GList *acls)
static const char *
acl_to_text(enum xml_private_flags flags)
{
- if (pcmk_is_set(flags, xpf_acl_deny)) {
+ if (pcmk_is_set(flags, pcmk__xf_acl_deny)) {
return "deny";
- } else if (pcmk_any_flags_set(flags, xpf_acl_write|xpf_acl_create)) {
+ } else if (pcmk_any_flags_set(flags, pcmk__xf_acl_write|pcmk__xf_acl_create)) {
return "read/write";
- } else if (pcmk_is_set(flags, xpf_acl_read)) {
+ } else if (pcmk_is_set(flags, pcmk__xf_acl_read)) {
return "read";
}
return "none";
@@ -330,18 +330,18 @@ pcmk__unpack_acl(xmlNode *source, xmlNode *target, const char *user)
static inline bool
test_acl_mode(enum xml_private_flags allowed, enum xml_private_flags requested)
{
- if (pcmk_is_set(allowed, xpf_acl_deny)) {
+ if (pcmk_is_set(allowed, pcmk__xf_acl_deny)) {
return false;
} else if (pcmk_all_flags_set(allowed, requested)) {
return true;
- } else if (pcmk_is_set(requested, xpf_acl_read)
- && pcmk_is_set(allowed, xpf_acl_write)) {
+ } else if (pcmk_is_set(requested, pcmk__xf_acl_read)
+ && pcmk_is_set(allowed, pcmk__xf_acl_write)) {
return true;
- } else if (pcmk_is_set(requested, xpf_acl_create)
- && pcmk_any_flags_set(allowed, xpf_acl_write|xpf_created)) {
+ } else if (pcmk_is_set(requested, pcmk__xf_acl_create)
+ && pcmk_any_flags_set(allowed, pcmk__xf_acl_write|pcmk__xf_created)) {
return true;
}
return false;
@@ -355,7 +355,7 @@ purge_xml_attributes(xmlNode *xml)
bool readable_children = false;
xml_private_t *p = xml->_private;
- if (test_acl_mode(p->flags, xpf_acl_read)) {
+ if (test_acl_mode(p->flags, pcmk__xf_acl_read)) {
crm_trace("%s[@id=%s] is readable", crm_element_name(xml), ID(xml));
return true;
}
@@ -421,7 +421,7 @@ xml_acl_filtered_copy(const char *user, xmlNode *acl_source, xmlNode *xml,
}
pcmk__unpack_acl(acl_source, target, user);
- pcmk__set_xml_doc_flag(target, xpf_acl_enabled);
+ pcmk__set_xml_doc_flag(target, pcmk__xf_acl_enabled);
pcmk__apply_acl(target);
doc = target->doc->_private;
@@ -429,7 +429,7 @@ xml_acl_filtered_copy(const char *user, xmlNode *acl_source, xmlNode *xml,
int max = 0;
xml_acl_t *acl = aIter->data;
- if (acl->mode != xpf_acl_deny) {
+ if (acl->mode != pcmk__xf_acl_deny) {
/* Nothing to do */
} else if (acl->xpath) {
@@ -530,13 +530,13 @@ pcmk__apply_creation_acl(xmlNode *xml, bool check_top)
{
xml_private_t *p = xml->_private;
- if (pcmk_is_set(p->flags, xpf_created)) {
+ if (pcmk_is_set(p->flags, pcmk__xf_created)) {
if (implicitly_allowed(xml)) {
crm_trace("Creation of <%s> scaffolding with id=\"%s\""
" is implicitly allowed",
crm_element_name(xml), display_id(xml));
- } else if (pcmk__check_acl(xml, NULL, xpf_acl_write)) {
+ } else if (pcmk__check_acl(xml, NULL, pcmk__xf_acl_write)) {
crm_trace("ACLs allow creation of <%s> with id=\"%s\"",
crm_element_name(xml), display_id(xml));
@@ -566,7 +566,7 @@ xml_acl_denied(xmlNode *xml)
if (xml && xml->doc && xml->doc->_private){
xml_private_t *p = xml->doc->_private;
- return pcmk_is_set(p->flags, xpf_acl_denied);
+ return pcmk_is_set(p->flags, pcmk__xf_acl_denied);
}
return false;
}
@@ -580,7 +580,7 @@ xml_acl_disable(xmlNode *xml)
/* Catch anything that was created but shouldn't have been */
pcmk__apply_acl(xml);
pcmk__apply_creation_acl(xml, false);
- pcmk__clear_xml_flags(p, xpf_acl_enabled);
+ pcmk__clear_xml_flags(p, pcmk__xf_acl_enabled);
}
}
@@ -590,7 +590,7 @@ xml_acl_enabled(xmlNode *xml)
if (xml && xml->doc && xml->doc->_private){
xml_private_t *p = xml->doc->_private;
- return pcmk_is_set(p->flags, xpf_acl_enabled);
+ return pcmk_is_set(p->flags, pcmk__xf_acl_enabled);
}
return false;
}
@@ -619,7 +619,7 @@ pcmk__check_acl(xmlNode *xml, const char *name, enum xml_private_flags mode)
if (docp->acls == NULL) {
crm_trace("User '%s' without ACLs denied %s access to %s",
docp->user, acl_to_text(mode), buffer);
- pcmk__set_xml_doc_flag(xml, xpf_acl_denied);
+ pcmk__set_xml_doc_flag(xml, pcmk__xf_acl_denied);
return false;
}
@@ -631,8 +631,8 @@ pcmk__check_acl(xmlNode *xml, const char *name, enum xml_private_flags mode)
if (name) {
xmlAttr *attr = xmlHasProp(xml, (pcmkXmlStr) name);
- if (attr && mode == xpf_acl_create) {
- mode = xpf_acl_write;
+ if (attr && mode == pcmk__xf_acl_create) {
+ mode = pcmk__xf_acl_write;
}
}
@@ -641,11 +641,11 @@ pcmk__check_acl(xmlNode *xml, const char *name, enum xml_private_flags mode)
if (test_acl_mode(p->flags, mode)) {
return true;
- } else if (pcmk_is_set(p->flags, xpf_acl_deny)) {
+ } else if (pcmk_is_set(p->flags, pcmk__xf_acl_deny)) {
crm_trace("%sACL denies user '%s' %s access to %s",
(parent != xml) ? "Parent " : "", docp->user,
acl_to_text(mode), buffer);
- pcmk__set_xml_doc_flag(xml, xpf_acl_denied);
+ pcmk__set_xml_doc_flag(xml, pcmk__xf_acl_denied);
return false;
}
parent = parent->parent;
@@ -653,7 +653,7 @@ pcmk__check_acl(xmlNode *xml, const char *name, enum xml_private_flags mode)
crm_trace("Default ACL denies user '%s' %s access to %s",
docp->user, acl_to_text(mode), buffer);
- pcmk__set_xml_doc_flag(xml, xpf_acl_denied);
+ pcmk__set_xml_doc_flag(xml, pcmk__xf_acl_denied);
return false;
}
diff --git a/lib/common/agents.c b/lib/common/agents.c
index a68c98f..cde029f 100644
--- a/lib/common/agents.c
+++ b/lib/common/agents.c
@@ -195,6 +195,7 @@ pcmk_stonith_param(const char *param)
}
// Deprecated functions kept only for backward API compatibility
+// LCOV_EXCL_START
#include <crm/common/agents_compat.h>
@@ -204,4 +205,5 @@ crm_provider_required(const char *standard)
return pcmk_is_set(pcmk_get_ra_caps(standard), pcmk_ra_cap_provider);
}
+// LCOV_EXCL_STOP
// End deprecated API
diff --git a/lib/common/cmdline.c b/lib/common/cmdline.c
index 1ca6147..8a42a1e 100644
--- a/lib/common/cmdline.c
+++ b/lib/common/cmdline.c
@@ -31,12 +31,14 @@ pcmk__new_common_args(const char *summary)
args = calloc(1, sizeof(pcmk__common_args_t));
if (args == NULL) {
- crm_exit(crm_errno2exit(-ENOMEM));
+ crm_exit(CRM_EX_OSERR);
}
args->summary = strdup(summary);
if (args->summary == NULL) {
- crm_exit(crm_errno2exit(-ENOMEM));
+ free(args);
+ args = NULL;
+ crm_exit(CRM_EX_OSERR);
}
return args;
diff --git a/lib/common/crmcommon_private.h b/lib/common/crmcommon_private.h
index 2202e1f..ba41983 100644
--- a/lib/common/crmcommon_private.h
+++ b/lib/common/crmcommon_private.h
@@ -1,5 +1,5 @@
/*
- * Copyright 2018-2020 the Pacemaker project contributors
+ * Copyright 2018-2021 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
@@ -29,25 +29,25 @@
*/
enum xml_private_flags {
- xpf_none = 0x0000,
- xpf_dirty = 0x0001,
- xpf_deleted = 0x0002,
- xpf_created = 0x0004,
- xpf_modified = 0x0008,
-
- xpf_tracking = 0x0010,
- xpf_processed = 0x0020,
- xpf_skip = 0x0040,
- xpf_moved = 0x0080,
-
- xpf_acl_enabled = 0x0100,
- xpf_acl_read = 0x0200,
- xpf_acl_write = 0x0400,
- xpf_acl_deny = 0x0800,
-
- xpf_acl_create = 0x1000,
- xpf_acl_denied = 0x2000,
- xpf_lazy = 0x4000,
+ pcmk__xf_none = 0x0000,
+ pcmk__xf_dirty = 0x0001,
+ pcmk__xf_deleted = 0x0002,
+ pcmk__xf_created = 0x0004,
+ pcmk__xf_modified = 0x0008,
+
+ pcmk__xf_tracking = 0x0010,
+ pcmk__xf_processed = 0x0020,
+ pcmk__xf_skip = 0x0040,
+ pcmk__xf_moved = 0x0080,
+
+ pcmk__xf_acl_enabled = 0x0100,
+ pcmk__xf_acl_read = 0x0200,
+ pcmk__xf_acl_write = 0x0400,
+ pcmk__xf_acl_deny = 0x0800,
+
+ pcmk__xf_acl_create = 0x1000,
+ pcmk__xf_acl_denied = 0x2000,
+ pcmk__xf_lazy = 0x4000,
};
/* When deleting portions of an XML tree, we keep a record so we can know later
@@ -197,7 +197,7 @@ typedef struct pcmk__ipc_methods_s {
* \internal
* \brief Check whether an IPC request results in a reply
*
- * \parma[in] api IPC API connection
+ * \param[in] api IPC API connection
* \param[in] request IPC request XML
*
* \return true if request would result in an IPC reply, false otherwise
diff --git a/lib/common/io.c b/lib/common/io.c
index 7aeaec5..ffdcae2 100644
--- a/lib/common/io.c
+++ b/lib/common/io.c
@@ -615,7 +615,35 @@ pcmk__close_fds_in_child(bool all)
}
}
+/*!
+ * \brief Duplicate a file path, inserting a prefix if not absolute
+ *
+ * \param[in] filename File path to duplicate
+ * \param[in] dirname If filename is not absolute, prefix to add
+ *
+ * \return Newly allocated memory with full path (guaranteed non-NULL)
+ */
+char *
+pcmk__full_path(const char *filename, const char *dirname)
+{
+ char *path = NULL;
+
+ CRM_ASSERT(filename != NULL);
+
+ if (filename[0] == '/') {
+ path = strdup(filename);
+ CRM_ASSERT(path != NULL);
+
+ } else {
+ CRM_ASSERT(dirname != NULL);
+ path = crm_strdup_printf("%s/%s", dirname, filename);
+ }
+
+ return path;
+}
+
// Deprecated functions kept only for backward API compatibility
+// LCOV_EXCL_START
#include <crm/common/util_compat.h>
@@ -630,4 +658,5 @@ crm_build_path(const char *path_c, mode_t mode)
}
}
+// LCOV_EXCL_STOP
// End deprecated API
diff --git a/lib/common/iso8601.c b/lib/common/iso8601.c
index 7cefaf0..8475354 100644
--- a/lib/common/iso8601.c
+++ b/lib/common/iso8601.c
@@ -20,7 +20,6 @@
#include <string.h>
#include <stdbool.h>
#include <crm/common/iso8601.h>
-#include <crm/common/iso8601_internal.h>
/*
* Andrew's code was originally written for OSes whose "struct tm" contains:
@@ -1734,3 +1733,61 @@ pcmk__epoch2str(time_t *when)
return pcmk__trim(since_epoch);
}
}
+
+/*!
+ * \internal
+ * \brief Given a millisecond interval, return a log-friendly string
+ *
+ * \param[in] interval_ms Interval in milliseconds
+ *
+ * \return Readable version of \p interval_ms
+ *
+ * \note The return value is a pointer to static memory that will be
+ * overwritten by later calls to this function.
+ */
+const char *
+pcmk__readable_interval(guint interval_ms)
+{
+#define MS_IN_S (1000)
+#define MS_IN_M (MS_IN_S * 60)
+#define MS_IN_H (MS_IN_M * 60)
+#define MS_IN_D (MS_IN_H * 24)
+#define MAXSTR sizeof("..d..h..m..s...ms")
+ static char str[MAXSTR] = { '\0', };
+ int offset = 0;
+
+ if (interval_ms > MS_IN_D) {
+ offset += snprintf(str + offset, MAXSTR - offset, "%ud",
+ interval_ms / MS_IN_D);
+ interval_ms -= (interval_ms / MS_IN_D) * MS_IN_D;
+ }
+ if (interval_ms > MS_IN_H) {
+ offset += snprintf(str + offset, MAXSTR - offset, "%uh",
+ interval_ms / MS_IN_H);
+ interval_ms -= (interval_ms / MS_IN_H) * MS_IN_H;
+ }
+ if (interval_ms > MS_IN_M) {
+ offset += snprintf(str + offset, MAXSTR - offset, "%um",
+ interval_ms / MS_IN_M);
+ interval_ms -= (interval_ms / MS_IN_M) * MS_IN_M;
+ }
+
+ // Ns, N.NNNs, or NNNms
+ if (interval_ms > MS_IN_S) {
+ offset += snprintf(str + offset, MAXSTR - offset, "%u",
+ interval_ms / MS_IN_S);
+ interval_ms -= (interval_ms / MS_IN_S) * MS_IN_S;
+ if (interval_ms > 0) {
+ offset += snprintf(str + offset, MAXSTR - offset, ".%03u",
+ interval_ms);
+ }
+ (void) snprintf(str + offset, MAXSTR - offset, "s");
+
+ } else if (interval_ms > 0) {
+ (void) snprintf(str + offset, MAXSTR - offset, "%ums", interval_ms);
+
+ } else if (str[0] == '\0') {
+ strcpy(str, "0s");
+ }
+ return str;
+}
diff --git a/lib/common/logging.c b/lib/common/logging.c
index 1c71606..37625ab 100644
--- a/lib/common/logging.c
+++ b/lib/common/logging.c
@@ -131,8 +131,21 @@ crm_log_deinit(void)
#define FMT_MAX 256
+/*!
+ * \internal
+ * \brief Set the log format string based on the passed-in method
+ *
+ * \param[in] method The detail level of the log output
+ * \param[in] daemon The daemon ID included in error messages
+ * \param[in] use_pid Cached result of getpid() call, for efficiency
+ * \param[in] use_nodename Cached result of uname() call, for efficiency
+ *
+ */
+
+/* XXX __attribute__((nonnull)) for use_nodename parameter */
static void
-set_format_string(int method, const char *daemon)
+set_format_string(int method, const char *daemon, pid_t use_pid,
+ const char *use_nodename)
{
if (method == QB_LOG_SYSLOG) {
// The system log gets a simplified, user-friendly format
@@ -146,17 +159,10 @@ set_format_string(int method, const char *daemon)
char fmt[FMT_MAX];
if (method > QB_LOG_STDERR) {
- struct utsname res;
- const char *nodename = "localhost";
-
- if (uname(&res) == 0) {
- nodename = res.nodename;
- }
-
// If logging to file, prefix with timestamp, node name, daemon ID
offset += snprintf(fmt + offset, FMT_MAX - offset,
TIMESTAMP_FORMAT_SPEC " %s %-20s[%lu] ",
- nodename, daemon, (unsigned long) getpid());
+ use_nodename, daemon, (unsigned long) use_pid);
}
// Add function name (in parentheses)
@@ -289,8 +295,8 @@ static void
setenv_logfile(const char *filename)
{
// Some resource agents will log only if environment variable is set
- if (pcmk__env_option("logfile") == NULL) {
- pcmk__set_env_option("logfile", filename);
+ if (pcmk__env_option(PCMK__ENV_LOGFILE) == NULL) {
+ pcmk__set_env_option(PCMK__ENV_LOGFILE, filename);
}
}
@@ -710,6 +716,22 @@ crm_priority2int(const char *name)
}
+/*!
+ * \internal
+ * \brief Set the identifier for the current process
+ *
+ * If the identifier crm_system_name is not already set, then it is set as follows:
+ * - it is passed to the function via the "entity" parameter, or
+ * - it is derived from the executable name
+ *
+ * The identifier can be used in logs, IPC, and more.
+ *
+ * This method also sets the PCMK_service environment variable.
+ *
+ * \param[in] entity If not NULL, will be assigned to the identifier
+ * \param[in] argc The number of command line parameters
+ * \param[in] argv The command line parameter values
+ */
static void
set_identity(const char *entity, int argc, char **argv)
{
@@ -744,9 +766,11 @@ crm_log_preinit(const char *entity, int argc, char **argv)
{
/* Configure libqb logging with nothing turned on */
+ struct utsname res;
int lpc = 0;
int32_t qb_facility = 0;
-
+ pid_t pid = getpid();
+ const char *nodename = "localhost";
static bool have_logging = FALSE;
if(have_logging == FALSE) {
@@ -781,6 +805,9 @@ crm_log_preinit(const char *entity, int argc, char **argv)
// Shorter than default, generous for what we *should* send to syslog
qb_log_ctl(QB_LOG_SYSLOG, QB_LOG_CONF_MAX_LINE_LEN, 256);
#endif
+ if (uname(memset(&res, 0, sizeof(res))) == 0 && *res.nodename != '\0') {
+ nodename = res.nodename;
+ }
/* Set format strings and disable threading
* Pacemaker and threads do not mix well (due to the amount of forking)
@@ -792,7 +819,7 @@ crm_log_preinit(const char *entity, int argc, char **argv)
// End truncated lines with '...'
qb_log_ctl(lpc, QB_LOG_CONF_ELLIPSIS, QB_TRUE);
#endif
- set_format_string(lpc, crm_system_name);
+ set_format_string(lpc, crm_system_name, pid, nodename);
}
}
}
@@ -802,7 +829,7 @@ crm_log_init(const char *entity, uint8_t level, gboolean daemon, gboolean to_std
int argc, char **argv, gboolean quiet)
{
const char *syslog_priority = NULL;
- const char *facility = pcmk__env_option("logfacility");
+ const char *facility = pcmk__env_option(PCMK__ENV_LOGFACILITY);
const char *f_copy = facility;
pcmk__is_daemon = daemon;
@@ -822,7 +849,7 @@ crm_log_init(const char *entity, uint8_t level, gboolean daemon, gboolean to_std
} else {
facility = "none";
}
- pcmk__set_env_option("logfacility", facility);
+ pcmk__set_env_option(PCMK__ENV_LOGFACILITY, facility);
}
if (pcmk__str_eq(facility, "none", pcmk__str_casei)) {
@@ -833,13 +860,13 @@ crm_log_init(const char *entity, uint8_t level, gboolean daemon, gboolean to_std
qb_log_ctl(QB_LOG_SYSLOG, QB_LOG_CONF_FACILITY, qb_log_facility2int(facility));
}
- if (pcmk__env_option_enabled(crm_system_name, "debug")) {
+ if (pcmk__env_option_enabled(crm_system_name, PCMK__ENV_DEBUG)) {
/* Override the default setting */
crm_log_level = LOG_DEBUG;
}
/* What lower threshold do we have for sending to syslog */
- syslog_priority = pcmk__env_option("logpriority");
+ syslog_priority = pcmk__env_option(PCMK__ENV_LOGPRIORITY);
if (syslog_priority) {
crm_log_priority = crm_priority2int(syslog_priority);
}
@@ -852,7 +879,7 @@ crm_log_init(const char *entity, uint8_t level, gboolean daemon, gboolean to_std
}
/* Should we log to stderr */
- if (pcmk__env_option_enabled(crm_system_name, "stderr")) {
+ if (pcmk__env_option_enabled(crm_system_name, PCMK__ENV_STDERR)) {
/* Override the default setting */
to_stderr = TRUE;
}
@@ -860,7 +887,7 @@ crm_log_init(const char *entity, uint8_t level, gboolean daemon, gboolean to_std
// Log to a file if we're a daemon or user asked for one
{
- const char *logfile = pcmk__env_option("logfile");
+ const char *logfile = pcmk__env_option(PCMK__ENV_LOGFILE);
if (!pcmk__str_eq("none", logfile, pcmk__str_casei)
&& (pcmk__is_daemon || (logfile != NULL))) {
@@ -870,14 +897,14 @@ crm_log_init(const char *entity, uint8_t level, gboolean daemon, gboolean to_std
}
if (pcmk__is_daemon
- && pcmk__env_option_enabled(crm_system_name, "blackbox")) {
+ && pcmk__env_option_enabled(crm_system_name, PCMK__ENV_BLACKBOX)) {
crm_enable_blackbox(0);
}
/* Summary */
crm_trace("Quiet: %d, facility %s", quiet, f_copy);
- pcmk__env_option("logfile");
- pcmk__env_option("logfacility");
+ pcmk__env_option(PCMK__ENV_LOGFILE);
+ pcmk__env_option(PCMK__ENV_LOGFACILITY);
crm_update_callsites();
@@ -1061,6 +1088,7 @@ pcmk__cli_init_logging(const char *name, unsigned int verbosity)
}
// Deprecated functions kept only for backward API compatibility
+// LCOV_EXCL_START
#include <crm/common/logging_compat.h>
@@ -1077,4 +1105,5 @@ crm_add_logfile(const char *filename)
return pcmk__add_logfile(filename) == pcmk_rc_ok;
}
+// LCOV_EXCL_STOP
// End deprecated API
diff --git a/lib/common/mainloop.c b/lib/common/mainloop.c
index d8e2c3c..fc3e2cb 100644
--- a/lib/common/mainloop.c
+++ b/lib/common/mainloop.c
@@ -1104,7 +1104,7 @@ child_timeout_callback(gpointer p)
child->timerid = 0;
if (child->timeout) {
- crm_crit("%s process (PID %d) will not die!", child->desc, (int)child->pid);
+ crm_warn("%s process (PID %d) will not die!", child->desc, (int)child->pid);
return FALSE;
}
@@ -1115,7 +1115,7 @@ child_timeout_callback(gpointer p)
}
child->timeout = TRUE;
- crm_warn("%s process (PID %d) timed out", child->desc, (int)child->pid);
+ crm_debug("%s process (PID %d) timed out", child->desc, (int)child->pid);
child->timerid = g_timeout_add(5000, child_timeout_callback, child);
return FALSE;
@@ -1488,6 +1488,7 @@ pcmk_drain_main_loop(GMainLoop *mloop, guint timer_ms, bool (*check)(guint))
}
// Deprecated functions kept only for backward API compatibility
+// LCOV_EXCL_START
#include <crm/common/mainloop_compat.h>
@@ -1497,4 +1498,5 @@ crm_signal(int sig, void (*dispatch) (int sig))
return crm_signal_handler(sig, dispatch) != SIG_ERR;
}
+// LCOV_EXCL_STOP
// End deprecated API
diff --git a/lib/common/mock.c b/lib/common/mock.c
new file mode 100644
index 0000000..6a57fa0
--- /dev/null
+++ b/lib/common/mock.c
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2021 the Pacemaker project contributors
+ *
+ * The version control history for this file may have further details.
+ *
+ * This source code is licensed under the GNU Lesser General Public License
+ * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
+ */
+
+#include <stdlib.h>
+#include <sys/utsname.h>
+
+#include "mock_private.h"
+
+/* This file is only used when running "make check". It is built into
+ * libcrmcommon_test.a, not into libcrmcommon.so. It is used to support
+ * constructing mock versions of library functions for unit testing.
+ *
+ * Each unit test will only ever want to use a mocked version of one or two
+ * library functions. However, we need to mark all the mocked functions as
+ * wrapped (with -Wl,--wrap= in the LDFLAGS) in libcrmcommon_test.a so that
+ * all those unit tests can share the same special test library. The unit
+ * test then defines its own wrapped function. Because a unit test won't
+ * define every single wrapped function, there will be undefined references
+ * at link time.
+ *
+ * This file takes care of those undefined references. It defines a
+ * wrapped version of every function that simply calls the real libc
+ * version. These wrapped versions are defined with a weak attribute,
+ * which means the unit tests can define another wrapped version for
+ * unit testing that will override the version defined here.
+ *
+ * IN SUMMARY:
+ *
+ * - Define two functions for each function listed in WRAPPED in mock.mk.
+ * One function is a weakly defined __wrap_X function that just calls
+ * __real_X.
+ * - Add a __real_X and __wrap_X function prototype for each function to
+ * mock_private.h.
+ * - Each unit test defines its own __wrap_X for whatever function it's
+ * mocking that overrides the version here.
+ */
+
+char *__attribute__((weak))
+__wrap_getenv(const char *name) {
+ return __real_getenv(name);
+}
+
+int __attribute__((weak))
+__wrap_uname(struct utsname *buf) {
+ return __real_uname(buf);
+}
diff --git a/lib/common/mock.mk b/lib/common/mock.mk
new file mode 100644
index 0000000..9fcdbcc
--- /dev/null
+++ b/lib/common/mock.mk
@@ -0,0 +1,2 @@
+WRAPPED = getenv uname
+WRAPPED_FLAGS = $(foreach fn,$(WRAPPED),-Wl,--wrap=$(fn))
diff --git a/lib/common/mock_private.h b/lib/common/mock_private.h
new file mode 100644
index 0000000..8b455ec
--- /dev/null
+++ b/lib/common/mock_private.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2021 the Pacemaker project contributors
+ *
+ * The version control history for this file may have further details.
+ *
+ * This source code is licensed under the GNU Lesser General Public License
+ * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
+ */
+
+#ifndef MOCK_PRIVATE__H
+# define MOCK_PRIVATE__H
+
+#include <sys/utsname.h>
+
+/* This header is for the sole use of libcrmcommon_test. */
+
+char *__real_getenv(const char *name);
+char *__wrap_getenv(const char *name);
+
+int __real_uname(struct utsname *buf);
+int __wrap_uname(struct utsname *buf);
+
+#endif // MOCK_PRIVATE__H
diff --git a/lib/common/nvpair.c b/lib/common/nvpair.c
index a7281cc..ae784ab 100644
--- a/lib/common/nvpair.c
+++ b/lib/common/nvpair.c
@@ -20,7 +20,6 @@
#include <crm/msg_xml.h>
#include <crm/common/xml.h>
#include <crm/common/xml_internal.h>
-#include <crm/common/iso8601_internal.h>
#include "crmcommon_private.h"
/*
@@ -353,7 +352,7 @@ crm_xml_add(xmlNode *node, const char *name, const char *value)
}
}
- if (dirty && (pcmk__check_acl(node, name, xpf_acl_create) == FALSE)) {
+ if (dirty && (pcmk__check_acl(node, name, pcmk__xf_acl_create) == FALSE)) {
crm_trace("Cannot add %s=%s to %s", name, value, node->name);
return NULL;
}
@@ -392,7 +391,7 @@ crm_xml_replace(xmlNode *node, const char *name, const char *value)
/* Could be re-setting the same value */
CRM_CHECK(old_value != value, return value);
- if (pcmk__check_acl(node, name, xpf_acl_write) == FALSE) {
+ if (pcmk__check_acl(node, name, pcmk__xf_acl_write) == FALSE) {
/* Create a fake object linked to doc->_private instead? */
crm_trace("Cannot replace %s=%s to %s", name, value, node->name);
return NULL;
@@ -955,6 +954,7 @@ xml2list(xmlNode *parent)
}
// Deprecated functions kept only for backward API compatibility
+// LCOV_EXCL_START
#include <crm/common/util_compat.h>
@@ -977,4 +977,5 @@ pcmk_format_named_time(const char *name, time_t epoch_time)
return pcmk__format_named_time(name, epoch_time);
}
+// LCOV_EXCL_STOP
// End deprecated API
diff --git a/lib/common/operations.c b/lib/common/operations.c
index b7bea4b..aa7106c 100644
--- a/lib/common/operations.c
+++ b/lib/common/operations.c
@@ -437,15 +437,17 @@ gboolean
did_rsc_op_fail(lrmd_event_data_t * op, int target_rc)
{
switch (op->op_status) {
- case PCMK_LRM_OP_CANCELLED:
- case PCMK_LRM_OP_PENDING:
+ case PCMK_EXEC_CANCELLED:
+ case PCMK_EXEC_PENDING:
return FALSE;
- case PCMK_LRM_OP_NOTSUPPORTED:
- case PCMK_LRM_OP_TIMEOUT:
- case PCMK_LRM_OP_ERROR:
- case PCMK_LRM_OP_NOT_CONNECTED:
- case PCMK_LRM_OP_INVALID:
+ case PCMK_EXEC_NOT_SUPPORTED:
+ case PCMK_EXEC_TIMEOUT:
+ case PCMK_EXEC_ERROR:
+ case PCMK_EXEC_NOT_CONNECTED:
+ case PCMK_EXEC_NO_FENCE_DEVICE:
+ case PCMK_EXEC_NO_SECRETS:
+ case PCMK_EXEC_INVALID:
return TRUE;
default:
diff --git a/lib/common/options.c b/lib/common/options.c
index 1321bb6..c11228a 100644
--- a/lib/common/options.c
+++ b/lib/common/options.c
@@ -553,8 +553,8 @@ pcmk__cluster_option(GHashTable *options, pcmk__cluster_option_t *option_list,
}
void
-pcmk__print_option_metadata(const char *name, const char *version,
- const char *desc_short, const char *desc_long,
+pcmk__print_option_metadata(const char *name, const char *desc_short,
+ const char *desc_long,
pcmk__cluster_option_t *option_list, int len)
{
int lpc = 0;
@@ -565,27 +565,51 @@ pcmk__print_option_metadata(const char *name, const char *version,
" <version>%s</version>\n"
" <longdesc lang=\"en\">%s</longdesc>\n"
" <shortdesc lang=\"en\">%s</shortdesc>\n"
- " <parameters>\n", name, version, desc_long, desc_short);
+ " <parameters>\n", name, PCMK_OCF_VERSION, desc_long, desc_short);
for (lpc = 0; lpc < len; lpc++) {
if ((option_list[lpc].description_long == NULL)
&& (option_list[lpc].description_short == NULL)) {
continue;
}
- fprintf(stdout, " <parameter name=\"%s\" unique=\"0\">\n"
+
+ fprintf(stdout, " <parameter name=\"%s\">\n"
" <shortdesc lang=\"en\">%s</shortdesc>\n"
- " <content type=\"%s\" default=\"%s\"/>\n"
- " <longdesc lang=\"en\">%s%s%s</longdesc>\n"
- " </parameter>\n",
+ " <longdesc lang=\"en\">%s%s%s</longdesc>\n",
option_list[lpc].name,
option_list[lpc].description_short,
- option_list[lpc].type,
- option_list[lpc].default_value,
option_list[lpc].description_long?
option_list[lpc].description_long :
option_list[lpc].description_short,
(option_list[lpc].values? " Allowed values: " : ""),
(option_list[lpc].values? option_list[lpc].values : ""));
+
+ if (option_list[lpc].values && !strcmp(option_list[lpc].type, "select")) {
+ char *str = strdup(option_list[lpc].values);
+ char delim[] = ", ";
+ char *ptr = strtok(str, delim);
+
+ fprintf(stdout, " <content type=\"%s\" default=\"%s\">\n",
+ option_list[lpc].type,
+ option_list[lpc].default_value
+ );
+
+ while (ptr != NULL) {
+ fprintf(stdout, " <option value=\"%s\" />\n", ptr);
+ ptr = strtok(NULL, delim);
+ }
+
+ fprintf(stdout, " </content>\n");
+ free(str);
+
+ } else {
+ fprintf(stdout, " <content type=\"%s\" default=\"%s\"/>\n",
+ option_list[lpc].type,
+ option_list[lpc].default_value
+ );
+ }
+
+ fprintf(stdout, " </parameter>\n");
}
fprintf(stdout, " </parameters>\n</resource-agent>\n");
}
diff --git a/lib/common/patchset.c b/lib/common/patchset.c
index 3f4197f..ac94ab1 100644
--- a/lib/common/patchset.c
+++ b/lib/common/patchset.c
@@ -24,7 +24,6 @@
#include <crm/crm.h>
#include <crm/msg_xml.h>
-#include <crm/common/iso8601_internal.h>
#include <crm/common/xml.h>
#include <crm/common/xml_internal.h> // CRM_XML_LOG_BASE, etc.
#include "crmcommon_private.h"
@@ -92,13 +91,13 @@ add_xml_changes_to_patchset(xmlNode *xml, xmlNode *patchset)
const char *value = NULL;
// If this XML node is new, just report that
- if (patchset && pcmk_is_set(p->flags, xpf_created)) {
+ if (patchset && pcmk_is_set(p->flags, pcmk__xf_created)) {
int offset = 0;
char buffer[PCMK__BUFFER_SIZE];
if (pcmk__element_xpath(NULL, xml->parent, buffer, offset,
sizeof(buffer)) > 0) {
- int position = pcmk__xml_position(xml, xpf_deleted);
+ int position = pcmk__xml_position(xml, pcmk__xf_deleted);
change = create_xml_node(patchset, XML_DIFF_CHANGE);
@@ -117,7 +116,7 @@ add_xml_changes_to_patchset(xmlNode *xml, xmlNode *patchset)
xmlNode *attr = NULL;
p = pIter->_private;
- if (!pcmk_any_flags_set(p->flags, xpf_deleted|xpf_dirty)) {
+ if (!pcmk_any_flags_set(p->flags, pcmk__xf_deleted|pcmk__xf_dirty)) {
continue;
}
@@ -139,7 +138,7 @@ add_xml_changes_to_patchset(xmlNode *xml, xmlNode *patchset)
attr = create_xml_node(change, XML_DIFF_ATTR);
crm_xml_add(attr, XML_NVPAIR_ATTR_NAME, (const char *)pIter->name);
- if (p->flags & xpf_deleted) {
+ if (p->flags & pcmk__xf_deleted) {
crm_xml_add(attr, XML_DIFF_OP, "unset");
} else {
@@ -159,7 +158,7 @@ add_xml_changes_to_patchset(xmlNode *xml, xmlNode *patchset)
for (pIter = pcmk__xe_first_attr(xml); pIter != NULL;
pIter = pIter->next) {
p = pIter->_private;
- if (!pcmk_is_set(p->flags, xpf_deleted)) {
+ if (!pcmk_is_set(p->flags, pcmk__xf_deleted)) {
value = crm_element_value(xml, (const char *) pIter->name);
crm_xml_add(result, (const char *)pIter->name, value);
}
@@ -173,12 +172,12 @@ add_xml_changes_to_patchset(xmlNode *xml, xmlNode *patchset)
}
p = xml->_private;
- if (patchset && pcmk_is_set(p->flags, xpf_moved)) {
+ if (patchset && pcmk_is_set(p->flags, pcmk__xf_moved)) {
int offset = 0;
char buffer[PCMK__BUFFER_SIZE];
crm_trace("%s.%s moved to position %d",
- xml->name, ID(xml), pcmk__xml_position(xml, xpf_skip));
+ xml->name, ID(xml), pcmk__xml_position(xml, pcmk__xf_skip));
if (pcmk__element_xpath(NULL, xml, buffer, offset,
sizeof(buffer)) > 0) {
change = create_xml_node(patchset, XML_DIFF_CHANGE);
@@ -186,7 +185,7 @@ add_xml_changes_to_patchset(xmlNode *xml, xmlNode *patchset)
crm_xml_add(change, XML_DIFF_OP, "move");
crm_xml_add(change, XML_DIFF_PATH, buffer);
crm_xml_add_int(change, XML_DIFF_POSITION,
- pcmk__xml_position(xml, xpf_deleted));
+ pcmk__xml_position(xml, pcmk__xf_deleted));
}
}
}
@@ -201,7 +200,7 @@ is_config_change(xmlNode *xml)
if (config) {
p = config->_private;
}
- if ((p != NULL) && pcmk_is_set(p->flags, xpf_dirty)) {
+ if ((p != NULL) && pcmk_is_set(p->flags, pcmk__xf_dirty)) {
return TRUE;
}
@@ -1000,7 +999,7 @@ first_matching_xml_child(xmlNode *parent, const char *name, const char *id,
// "position" makes sense only for XML comments for now
if ((cIter->type == XML_COMMENT_NODE)
&& (position >= 0)
- && (pcmk__xml_position(cIter, xpf_skip) != position)) {
+ && (pcmk__xml_position(cIter, pcmk__xf_skip) != position)) {
continue;
}
@@ -1247,7 +1246,7 @@ apply_v2_patchset(xmlNode *xml, xmlNode *patchset)
crm_element_value_int(change, XML_DIFF_POSITION, &position);
while ((match_child != NULL)
- && (position != pcmk__xml_position(match_child, xpf_skip))) {
+ && (position != pcmk__xml_position(match_child, pcmk__xf_skip))) {
match_child = match_child->next;
}
@@ -1273,11 +1272,11 @@ apply_v2_patchset(xmlNode *xml, xmlNode *patchset)
int position = 0;
crm_element_value_int(change, XML_DIFF_POSITION, &position);
- if (position != pcmk__xml_position(match, xpf_skip)) {
+ if (position != pcmk__xml_position(match, pcmk__xf_skip)) {
xmlNode *match_child = NULL;
int p = position;
- if (p > pcmk__xml_position(match, xpf_skip)) {
+ if (p > pcmk__xml_position(match, pcmk__xf_skip)) {
p++; // Skip ourselves
}
@@ -1285,13 +1284,13 @@ apply_v2_patchset(xmlNode *xml, xmlNode *patchset)
match_child = match->parent->children;
while ((match_child != NULL)
- && (p != pcmk__xml_position(match_child, xpf_skip))) {
+ && (p != pcmk__xml_position(match_child, pcmk__xf_skip))) {
match_child = match_child->next;
}
crm_trace("Moving %s to position %d (was %d, prev %p, %s %p)",
match->name, position,
- pcmk__xml_position(match, xpf_skip),
+ pcmk__xml_position(match, pcmk__xf_skip),
match->prev, (match_child? "next":"last"),
(match_child? match_child : match->parent->last));
@@ -1308,10 +1307,10 @@ apply_v2_patchset(xmlNode *xml, xmlNode *patchset)
match->name, position);
}
- if (position != pcmk__xml_position(match, xpf_skip)) {
+ if (position != pcmk__xml_position(match, pcmk__xf_skip)) {
crm_err("Moved %s.%s to position %d instead of %d (%p)",
match->name, ID(match),
- pcmk__xml_position(match, xpf_skip),
+ pcmk__xml_position(match, pcmk__xf_skip),
position, match->prev);
rc = pcmk_rc_diff_failed;
}
@@ -1576,7 +1575,7 @@ subtract_xml_object(xmlNode *parent, xmlNode *left, xmlNode *right,
}
right_val = crm_element_value(right, prop_name);
- if ((right_val == NULL) || (p && pcmk_is_set(p->flags, xpf_deleted))) {
+ if ((right_val == NULL) || (p && pcmk_is_set(p->flags, pcmk__xf_deleted))) {
/* new */
*changed = TRUE;
if (full) {
@@ -1644,6 +1643,7 @@ subtract_xml_object(xmlNode *parent, xmlNode *left, xmlNode *right,
}
// Deprecated functions kept only for backward API compatibility
+// LCOV_EXCL_START
#include <crm/common/xml_compat.h>
@@ -1737,4 +1737,5 @@ apply_xml_diff(xmlNode *old_xml, xmlNode *diff, xmlNode **new_xml)
return result;
}
+// LCOV_EXCL_STOP
// End deprecated API
diff --git a/lib/common/results.c b/lib/common/results.c
index ea0cf91..6d12069 100644
--- a/lib/common/results.c
+++ b/lib/common/results.c
@@ -1,5 +1,5 @@
/*
- * Copyright 2004-2020 the Pacemaker project contributors
+ * Copyright 2004-2021 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
@@ -250,7 +250,19 @@ static struct pcmk__rc_info {
{ "pcmk_rc_underflow",
"Value too small to be stored in data type",
-pcmk_err_generic,
- }
+ },
+ { "pcmk_rc_dot_error",
+ "Error writing dot(1) file",
+ -pcmk_err_generic,
+ },
+ { "pcmk_rc_graph_error",
+ "Error writing graph file",
+ -pcmk_err_generic,
+ },
+ { "pcmk_rc_invalid_transition",
+ "Cluster simulation produced invalid transition",
+ -pcmk_err_generic,
+ },
};
#define PCMK__N_RC (sizeof(pcmk__rcs) / sizeof(struct pcmk__rc_info))
@@ -517,6 +529,8 @@ crm_exit_name(crm_exit_t exit_code)
case CRM_EX_UNSATISFIED: return "CRM_EX_UNSATISFIED";
case CRM_EX_OLD: return "CRM_EX_OLD";
case CRM_EX_TIMEOUT: return "CRM_EX_TIMEOUT";
+ case CRM_EX_DEGRADED: return "CRM_EX_DEGRADED";
+ case CRM_EX_DEGRADED_PROMOTED: return "CRM_EX_DEGRADED_PROMOTED";
case CRM_EX_MAX: return "CRM_EX_UNKNOWN";
}
return "CRM_EX_UNKNOWN";
@@ -564,6 +578,8 @@ crm_exit_str(crm_exit_t exit_code)
case CRM_EX_UNSATISFIED: return "Not applicable under current conditions";
case CRM_EX_OLD: return "Update was older than existing configuration";
case CRM_EX_TIMEOUT: return "Timeout occurred";
+ case CRM_EX_DEGRADED: return "Service is active but might fail soon";
+ case CRM_EX_DEGRADED_PROMOTED: return "Service is promoted but might fail soon";
case CRM_EX_MAX: return "Error occurred";
}
if ((exit_code > 128) && (exit_code < CRM_EX_MAX)) {
@@ -672,6 +688,8 @@ pcmk_rc2exitc(int rc)
case EIO:
case pcmk_rc_no_output:
+ case pcmk_rc_dot_error:
+ case pcmk_rc_graph_error:
return CRM_EX_IOERR;
case ENOTSUP:
@@ -720,6 +738,38 @@ pcmk_rc2exitc(int rc)
}
}
+/*!
+ * \brief Map a function return code to the most similar OCF exit code
+ *
+ * \param[in] rc Function return code
+ *
+ * \return Most similar OCF exit code
+ */
+enum ocf_exitcode
+pcmk_rc2ocf(int rc)
+{
+ switch (rc) {
+ case pcmk_rc_ok:
+ return PCMK_OCF_OK;
+
+ case pcmk_rc_bad_nvpair:
+ return PCMK_OCF_INVALID_PARAM;
+
+ case EACCES:
+ return PCMK_OCF_INSUFFICIENT_PRIV;
+
+ case ENOTSUP:
+#if EOPNOTSUPP != ENOTSUP
+ case EOPNOTSUPP:
+#endif
+ return PCMK_OCF_UNIMPLEMENT_FEATURE;
+
+ default:
+ return PCMK_OCF_UNKNOWN_ERROR;
+ }
+}
+
+
// Other functions
const char *
@@ -780,3 +830,83 @@ crm_exit(crm_exit_t rc)
exit(rc);
}
+
+/*
+ * External action results
+ */
+
+/*!
+ * \internal
+ * \brief Set the result of an action
+ *
+ * \param[out] result Where to set action result
+ * \param[in] exit_status OCF exit status to set
+ * \param[in] exec_status Execution status to set
+ * \param[in] exit_reason Human-friendly description of event to set
+ */
+void
+pcmk__set_result(pcmk__action_result_t *result, int exit_status,
+ enum pcmk_exec_status exec_status, const char *exit_reason)
+{
+ if (result == NULL) {
+ return;
+ }
+
+ result->exit_status = exit_status;
+ result->execution_status = exec_status;
+
+ if (!pcmk__str_eq(result->exit_reason, exit_reason, pcmk__str_none)) {
+ free(result->exit_reason);
+ result->exit_reason = (exit_reason == NULL)? NULL : strdup(exit_reason);
+ }
+}
+
+/*!
+ * \internal
+ * \brief Set the output of an action
+ *
+ * \param[out] result Action result to set output for
+ * \param[in] out Action output to set (must be dynamically
+ * allocated)
+ * \param[in] err Action error output to set (must be dynamically
+ * allocated)
+ *
+ * \note \p result will take ownership of \p out and \p err, so the caller
+ * should not free them.
+ */
+void
+pcmk__set_result_output(pcmk__action_result_t *result, char *out, char *err)
+{
+ if (result == NULL) {
+ return;
+ }
+
+ free(result->action_stdout);
+ result->action_stdout = out;
+
+ free(result->action_stderr);
+ result->action_stderr = err;
+}
+
+/*!
+ * \internal
+ * \brief Clear a result's exit reason, output, and error output
+ *
+ * \param[in] result Result to reset
+ */
+void
+pcmk__reset_result(pcmk__action_result_t *result)
+{
+ if (result == NULL) {
+ return;
+ }
+
+ free(result->exit_reason);
+ result->exit_reason = NULL;
+
+ free(result->action_stdout);
+ result->action_stdout = NULL;
+
+ free(result->action_stderr);
+ result->action_stderr = NULL;
+}
diff --git a/lib/common/strings.c b/lib/common/strings.c
index e1e9880..c1829b6 100644
--- a/lib/common/strings.c
+++ b/lib/common/strings.c
@@ -353,8 +353,9 @@ pcmk__guint_from_hash(GHashTable *table, const char *key, guint default_val,
/*!
* \brief Parse a time+units string and return milliseconds equivalent
*
- * \param[in] input String with a number and units (optionally with whitespace
- * before and/or after the number)
+ * \param[in] input String with a number and optional unit (optionally
+ * with whitespace before and/or after the number). If
+ * missing, the unit defaults to seconds.
*
* \return Milliseconds corresponding to string expression, or
* PCMK__PARSE_INT_DEFAULT on error
@@ -869,73 +870,48 @@ pcmk__parse_ll_range(const char *srcstring, long long *start, long long *end)
* \internal
* \brief Find a string in a list of strings
*
- * Search \p lst for \p s, taking case into account. As a special case,
- * if "*" is the only element of \p lst, the search is successful.
- *
- * Behavior can be changed with various flags:
- *
- * - pcmk__str_casei - By default, comparisons are done taking case into
- * account. This flag makes comparisons case-insensitive.
- * - pcmk__str_null_matches - If the input string is NULL, return TRUE.
- *
- * \note The special "*" matching rule takes precedence over flags. In
- * particular, "*" will match a NULL input string even without
- * pcmk__str_null_matches being specified.
+ * \note This function takes the same flags and has the same behavior as
+ * pcmk__str_eq().
*
* \note No matter what input string or flags are provided, an empty
* list will always return FALSE.
*
- * \param[in] lst List to search
* \param[in] s String to search for
+ * \param[in] lst List to search
* \param[in] flags A bitfield of pcmk__str_flags to modify operation
*
* \return \c TRUE if \p s is in \p lst, or \c FALSE otherwise
*/
gboolean
-pcmk__str_in_list(GList *lst, const gchar *s, uint32_t flags)
+pcmk__str_in_list(const gchar *s, GList *lst, uint32_t flags)
{
- GCompareFunc fn;
-
- if (lst == NULL) {
- return FALSE;
+ for (GList *ele = lst; ele != NULL; ele = ele->next) {
+ if (pcmk__str_eq(s, ele->data, flags)) {
+ return TRUE;
+ }
}
- if (strcmp(lst->data, "*") == 0 && lst->next == NULL) {
- return TRUE;
- }
+ return FALSE;
+}
+static bool
+str_any_of(const char *s, va_list args, uint32_t flags)
+{
if (s == NULL) {
return pcmk_is_set(flags, pcmk__str_null_matches);
}
- if (pcmk_is_set(flags, pcmk__str_casei)) {
- fn = (GCompareFunc) strcasecmp;
- } else {
- fn = (GCompareFunc) strcmp;
- }
-
- return g_list_find_custom(lst, s, fn) != NULL;
-}
-
-static bool
-str_any_of(bool casei, const char *s, va_list args)
-{
- bool rc = false;
+ while (1) {
+ const char *ele = va_arg(args, const char *);
- if (s != NULL) {
- while (1) {
- const char *ele = va_arg(args, const char *);
-
- if (ele == NULL) {
- break;
- } else if (pcmk__str_eq(s, ele,
- casei? pcmk__str_casei : pcmk__str_none)) {
- rc = true;
- break;
- }
+ if (ele == NULL) {
+ break;
+ } else if (pcmk__str_eq(s, ele, flags)) {
+ return true;
}
}
- return rc;
+
+ return false;
}
/*!
@@ -958,7 +934,7 @@ pcmk__strcase_any_of(const char *s, ...)
bool rc;
va_start(ap, s);
- rc = str_any_of(true, s, ap);
+ rc = str_any_of(s, ap, pcmk__str_casei);
va_end(ap);
return rc;
}
@@ -982,7 +958,7 @@ pcmk__str_any_of(const char *s, ...)
bool rc;
va_start(ap, s);
- rc = str_any_of(false, s, ap);
+ rc = str_any_of(s, ap, pcmk__str_none);
va_end(ap);
return rc;
}
@@ -1106,6 +1082,8 @@ pcmk__numeric_strcasecmp(const char *s1, const char *s2)
* This can be combined with pcmk__str_regex.
* - pcmk__str_null_matches - If one string is NULL and the other is not,
* still return 0.
+ * - pcmk__str_star_matches - If one string is "*" and the other is not, still
+ * return 0.
*
* \param[in] s1 First string to compare
* \param[in] s2 Second string to compare, or a regular expression to
@@ -1178,6 +1156,16 @@ pcmk__strcmp(const char *s1, const char *s2, uint32_t flags)
return 1;
}
+ /* If this flag is set, return 0 if either (or both) of the input strings
+ * are "*". If neither one is, we need to continue and compare them
+ * normally.
+ */
+ if (pcmk_is_set(flags, pcmk__str_star_matches)) {
+ if (strcmp(s1, "*") == 0 || strcmp(s2, "*") == 0) {
+ return 0;
+ }
+ }
+
if (pcmk_is_set(flags, pcmk__str_casei)) {
return strcasecmp(s1, s2);
} else {
@@ -1186,6 +1174,7 @@ pcmk__strcmp(const char *s1, const char *s2, uint32_t flags)
}
// Deprecated functions kept only for backward API compatibility
+// LCOV_EXCL_START
#include <crm/common/util_compat.h>
@@ -1311,4 +1300,5 @@ pcmk_numeric_strcasecmp(const char *s1, const char *s2)
return pcmk__numeric_strcasecmp(s1, s2);
}
+// LCOV_EXCL_END
// End deprecated API
diff --git a/lib/common/tests/Makefile.am b/lib/common/tests/Makefile.am
index 010b3fe..cd97327 100644
--- a/lib/common/tests/Makefile.am
+++ b/lib/common/tests/Makefile.am
@@ -1 +1,20 @@
-SUBDIRS = agents cmdline flags operations strings utils xpath results
+#
+# Copyright 2020-2021 the Pacemaker project contributors
+#
+# The version control history for this file may have further details.
+#
+# This source code is licensed under the GNU General Public License version 2
+# or later (GPLv2+) WITHOUT ANY WARRANTY.
+#
+
+SUBDIRS = \
+ agents \
+ cmdline \
+ flags \
+ io \
+ iso8601 \
+ operations \
+ results \
+ strings \
+ utils \
+ xpath
diff --git a/lib/common/tests/agents/Makefile.am b/lib/common/tests/agents/Makefile.am
index 40cb5f7..e59585c 100644
--- a/lib/common/tests/agents/Makefile.am
+++ b/lib/common/tests/agents/Makefile.am
@@ -7,23 +7,11 @@
# or later (GPLv2+) WITHOUT ANY WARRANTY.
#
AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_builddir)/include
-LDADD = $(top_builddir)/lib/common/libcrmcommon.la
+LDADD = $(top_builddir)/lib/common/libcrmcommon.la -lcmocka
-include $(top_srcdir)/mk/glib-tap.mk
+include $(top_srcdir)/mk/tap.mk
-# Add each test program here. Each test should be written as a little standalone
-# program using the glib unit testing functions. See the documentation for more
-# information.
-#
-# https://developer.gnome.org/glib/unstable/glib-Testing.html
-#
# Add "_test" to the end of all test program names to simplify .gitignore.
-test_programs = pcmk_stonith_param_test
-
-# If any extra data needs to be added to the source distribution, add it to the
-# following list.
-dist_test_data =
+check_PROGRAMS = pcmk_stonith_param_test
-# If any extra data needs to be used by tests but should not be added to the
-# source distribution, add it to the following list.
-test_data =
+TESTS = $(check_PROGRAMS)
diff --git a/lib/common/tests/agents/pcmk_stonith_param_test.c b/lib/common/tests/agents/pcmk_stonith_param_test.c
index ee82b85..f1dce83 100644
--- a/lib/common/tests/agents/pcmk_stonith_param_test.c
+++ b/lib/common/tests/agents/pcmk_stonith_param_test.c
@@ -8,52 +8,55 @@
*/
#include <crm_internal.h>
-
-#include <glib.h>
-
#include <crm/common/agents.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <setjmp.h>
+#include <cmocka.h>
+
static void
-is_stonith_param(void)
+is_stonith_param(void **state)
{
- g_assert_false(pcmk_stonith_param(NULL));
- g_assert_false(pcmk_stonith_param(""));
- g_assert_false(pcmk_stonith_param("unrecognized"));
- g_assert_false(pcmk_stonith_param("pcmk_unrecognized"));
- g_assert_false(pcmk_stonith_param("x" PCMK_STONITH_ACTION_LIMIT));
- g_assert_false(pcmk_stonith_param(PCMK_STONITH_ACTION_LIMIT "x"));
-
- g_assert_true(pcmk_stonith_param(PCMK_STONITH_ACTION_LIMIT));
- g_assert_true(pcmk_stonith_param(PCMK_STONITH_DELAY_BASE));
- g_assert_true(pcmk_stonith_param(PCMK_STONITH_DELAY_MAX));
- g_assert_true(pcmk_stonith_param(PCMK_STONITH_HOST_ARGUMENT));
- g_assert_true(pcmk_stonith_param(PCMK_STONITH_HOST_CHECK));
- g_assert_true(pcmk_stonith_param(PCMK_STONITH_HOST_LIST));
- g_assert_true(pcmk_stonith_param(PCMK_STONITH_HOST_MAP));
- g_assert_true(pcmk_stonith_param(PCMK_STONITH_PROVIDES));
- g_assert_true(pcmk_stonith_param(PCMK_STONITH_STONITH_TIMEOUT));
+ assert_false(pcmk_stonith_param(NULL));
+ assert_false(pcmk_stonith_param(""));
+ assert_false(pcmk_stonith_param("unrecognized"));
+ assert_false(pcmk_stonith_param("pcmk_unrecognized"));
+ assert_false(pcmk_stonith_param("x" PCMK_STONITH_ACTION_LIMIT));
+ assert_false(pcmk_stonith_param(PCMK_STONITH_ACTION_LIMIT "x"));
+
+ assert_true(pcmk_stonith_param(PCMK_STONITH_ACTION_LIMIT));
+ assert_true(pcmk_stonith_param(PCMK_STONITH_DELAY_BASE));
+ assert_true(pcmk_stonith_param(PCMK_STONITH_DELAY_MAX));
+ assert_true(pcmk_stonith_param(PCMK_STONITH_HOST_ARGUMENT));
+ assert_true(pcmk_stonith_param(PCMK_STONITH_HOST_CHECK));
+ assert_true(pcmk_stonith_param(PCMK_STONITH_HOST_LIST));
+ assert_true(pcmk_stonith_param(PCMK_STONITH_HOST_MAP));
+ assert_true(pcmk_stonith_param(PCMK_STONITH_PROVIDES));
+ assert_true(pcmk_stonith_param(PCMK_STONITH_STONITH_TIMEOUT));
}
static void
-is_stonith_action_param(void)
+is_stonith_action_param(void **state)
{
/* Currently, the function accepts any string not containing underbars as
* the action name, so we do not need to verify particular action names.
*/
- g_assert_false(pcmk_stonith_param("pcmk_on_unrecognized"));
- g_assert_true(pcmk_stonith_param("pcmk_on_action"));
- g_assert_true(pcmk_stonith_param("pcmk_on_timeout"));
- g_assert_true(pcmk_stonith_param("pcmk_on_retries"));
+ assert_false(pcmk_stonith_param("pcmk_on_unrecognized"));
+ assert_true(pcmk_stonith_param("pcmk_on_action"));
+ assert_true(pcmk_stonith_param("pcmk_on_timeout"));
+ assert_true(pcmk_stonith_param("pcmk_on_retries"));
}
int
main(int argc, char **argv)
{
- g_test_init(&argc, &argv, NULL);
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(is_stonith_param),
+ cmocka_unit_test(is_stonith_action_param),
+ };
- g_test_add_func("/common/utils/parse_op_key/is_stonith_param",
- is_stonith_param);
- g_test_add_func("/common/utils/parse_op_key/is_stonith_action_param",
- is_stonith_action_param);
- return g_test_run();
+ cmocka_set_message_output(CM_OUTPUT_TAP);
+ return cmocka_run_group_tests(tests, NULL, NULL);
}
diff --git a/lib/common/tests/cmdline/Makefile.am b/lib/common/tests/cmdline/Makefile.am
index e69ef21..bb5e2ee 100644
--- a/lib/common/tests/cmdline/Makefile.am
+++ b/lib/common/tests/cmdline/Makefile.am
@@ -1,5 +1,5 @@
#
-# Copyright 2020 the Pacemaker project contributors
+# Copyright 2020-2021 the Pacemaker project contributors
#
# The version control history for this file may have further details.
#
@@ -7,23 +7,11 @@
# or later (GPLv2+) WITHOUT ANY WARRANTY.
#
AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_builddir)/include
-LDADD = $(top_builddir)/lib/common/libcrmcommon.la
+LDADD = $(top_builddir)/lib/common/libcrmcommon.la -lcmocka
-include $(top_srcdir)/mk/glib-tap.mk
+include $(top_srcdir)/mk/tap.mk
-# Add each test program here. Each test should be written as a little standalone
-# program using the glib unit testing functions. See the documentation for more
-# information.
-#
-# https://developer.gnome.org/glib/unstable/glib-Testing.html
-#
# Add "_test" to the end of all test program names to simplify .gitignore.
-test_programs = pcmk__cmdline_preproc_test
-
-# If any extra data needs to be added to the source distribution, add it to the
-# following list.
-dist_test_data =
+check_PROGRAMS = pcmk__cmdline_preproc_test
-# If any extra data needs to be used by tests but should not be added to the
-# source distribution, add it to the following list.
-test_data =
+TESTS = $(check_PROGRAMS)
diff --git a/lib/common/tests/cmdline/pcmk__cmdline_preproc_test.c b/lib/common/tests/cmdline/pcmk__cmdline_preproc_test.c
index edc5640..d260851 100644
--- a/lib/common/tests/cmdline/pcmk__cmdline_preproc_test.c
+++ b/lib/common/tests/cmdline/pcmk__cmdline_preproc_test.c
@@ -10,20 +10,27 @@
#include <crm_internal.h>
#include <crm/common/cmdline_internal.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <setjmp.h>
+#include <cmocka.h>
+#include <glib.h>
+
#define LISTS_EQ(a, b) { \
- g_assert_cmpint(g_strv_length((gchar **) (a)), ==, g_strv_length((gchar **) (b))); \
+ assert_int_equal(g_strv_length((gchar **) (a)), g_strv_length((gchar **) (b))); \
for (int i = 0; i < g_strv_length((a)); i++) { \
- g_assert_cmpstr((a)[i], ==, (b)[i]); \
+ assert_string_equal((a)[i], (b)[i]); \
} \
}
static void
-empty_input(void) {
- g_assert_null(pcmk__cmdline_preproc(NULL, ""));
+empty_input(void **state) {
+ assert_null(pcmk__cmdline_preproc(NULL, ""));
}
static void
-no_specials(void) {
+no_specials(void **state) {
const char *argv[] = { "-a", "-b", "-c", "-d", NULL };
const gchar *expected[] = { "-a", "-b", "-c", "-d", NULL };
@@ -37,7 +44,7 @@ no_specials(void) {
}
static void
-single_dash(void) {
+single_dash(void **state) {
const char *argv[] = { "-", NULL };
const gchar *expected[] = { "-", NULL };
@@ -47,7 +54,7 @@ single_dash(void) {
}
static void
-double_dash(void) {
+double_dash(void **state) {
const char *argv[] = { "-a", "--", "-bc", NULL };
const gchar *expected[] = { "-a", "--", "-bc", NULL };
@@ -57,7 +64,7 @@ double_dash(void) {
}
static void
-special_args(void) {
+special_args(void **state) {
const char *argv[] = { "-aX", "-Fval", NULL };
const gchar *expected[] = { "-a", "X", "-F", "val", NULL };
@@ -67,7 +74,7 @@ special_args(void) {
}
static void
-special_arg_at_end(void) {
+special_arg_at_end(void **state) {
const char *argv[] = { "-a", NULL };
const gchar *expected[] = { "-a", NULL };
@@ -77,7 +84,7 @@ special_arg_at_end(void) {
}
static void
-long_arg(void) {
+long_arg(void **state) {
const char *argv[] = { "--blah=foo", NULL };
const gchar *expected[] = { "--blah=foo", NULL };
@@ -87,7 +94,7 @@ long_arg(void) {
}
static void
-negative_score(void) {
+negative_score(void **state) {
const char *argv[] = { "-v", "-1000", NULL };
const gchar *expected[] = { "-v", "-1000", NULL };
@@ -97,7 +104,7 @@ negative_score(void) {
}
static void
-negative_score_2(void) {
+negative_score_2(void **state) {
const char *argv[] = { "-1i3", NULL };
const gchar *expected[] = { "-1", "-i", "-3", NULL };
@@ -107,7 +114,7 @@ negative_score_2(void) {
}
static void
-string_arg_with_dash(void) {
+string_arg_with_dash(void **state) {
const char *argv[] = { "-n", "crm_mon_options", "-v", "--opt1 --opt2", NULL };
const gchar *expected[] = { "-n", "crm_mon_options", "-v", "--opt1 --opt2", NULL };
@@ -117,7 +124,7 @@ string_arg_with_dash(void) {
}
static void
-string_arg_with_dash_2(void) {
+string_arg_with_dash_2(void **state) {
const char *argv[] = { "-n", "crm_mon_options", "-v", "-1i3", NULL };
const gchar *expected[] = { "-n", "crm_mon_options", "-v", "-1i3", NULL };
@@ -127,7 +134,7 @@ string_arg_with_dash_2(void) {
}
static void
-string_arg_with_dash_3(void) {
+string_arg_with_dash_3(void **state) {
const char *argv[] = { "-abc", "-1i3", NULL };
const gchar *expected[] = { "-a", "-b", "-c", "-1i3", NULL };
@@ -139,19 +146,21 @@ string_arg_with_dash_3(void) {
int
main(int argc, char **argv)
{
- g_test_init(&argc, &argv, NULL);
-
- g_test_add_func("/common/cmdline/preproc/empty_input", empty_input);
- g_test_add_func("/common/cmdline/preproc/no_specials", no_specials);
- g_test_add_func("/common/cmdline/preproc/single_dash", single_dash);
- g_test_add_func("/common/cmdline/preproc/double_dash", double_dash);
- g_test_add_func("/common/cmdline/preproc/special_args", special_args);
- g_test_add_func("/common/cmdline/preproc/special_arg_at_end", special_arg_at_end);
- g_test_add_func("/common/cmdline/preproc/long_arg", long_arg);
- g_test_add_func("/common/cmdline/preproc/negative_score", negative_score);
- g_test_add_func("/common/cmdline/preproc/negative_score_2", negative_score_2);
- g_test_add_func("/common/cmdline/preproc/string_arg_with_dash", string_arg_with_dash);
- g_test_add_func("/common/cmdline/preproc/string_arg_with_dash_2", string_arg_with_dash_2);
- g_test_add_func("/common/cmdline/preproc/string_arg_with_dash_3", string_arg_with_dash_3);
- return g_test_run();
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(empty_input),
+ cmocka_unit_test(no_specials),
+ cmocka_unit_test(single_dash),
+ cmocka_unit_test(double_dash),
+ cmocka_unit_test(special_args),
+ cmocka_unit_test(special_arg_at_end),
+ cmocka_unit_test(long_arg),
+ cmocka_unit_test(negative_score),
+ cmocka_unit_test(negative_score_2),
+ cmocka_unit_test(string_arg_with_dash),
+ cmocka_unit_test(string_arg_with_dash_2),
+ cmocka_unit_test(string_arg_with_dash_3),
+ };
+
+ cmocka_set_message_output(CM_OUTPUT_TAP);
+ return cmocka_run_group_tests(tests, NULL, NULL);
}
diff --git a/lib/common/tests/flags/Makefile.am b/lib/common/tests/flags/Makefile.am
index 086d4e2..97160bd 100644
--- a/lib/common/tests/flags/Makefile.am
+++ b/lib/common/tests/flags/Makefile.am
@@ -1,5 +1,5 @@
#
-# Copyright 2020 the Pacemaker project contributors
+# Copyright 2020-2021 the Pacemaker project contributors
#
# The version control history for this file may have further details.
#
@@ -7,26 +7,15 @@
# or later (GPLv2+) WITHOUT ANY WARRANTY.
#
AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_builddir)/include
-LDADD = $(top_builddir)/lib/common/libcrmcommon.la
+LDADD = $(top_builddir)/lib/common/libcrmcommon.la -lcmocka
-include $(top_srcdir)/mk/glib-tap.mk
+include $(top_srcdir)/mk/tap.mk
-# Add each test program here. Each test should be written as a little standalone
-# program using the glib unit testing functions. See the documentation for more
-# information.
-#
-# https://developer.gnome.org/glib/unstable/glib-Testing.html
-#
# Add "_test" to the end of all test program names to simplify .gitignore.
-test_programs = pcmk__clear_flags_as_test \
- pcmk__set_flags_as_test \
- pcmk_all_flags_set_test \
- pcmk_any_flags_set_test
-
-# If any extra data needs to be added to the source distribution, add it to the
-# following list.
-dist_test_data =
+check_PROGRAMS = \
+ pcmk__clear_flags_as_test \
+ pcmk__set_flags_as_test \
+ pcmk_all_flags_set_test \
+ pcmk_any_flags_set_test
-# If any extra data needs to be used by tests but should not be added to the
-# source distribution, add it to the following list.
-test_data =
+TESTS = $(check_PROGRAMS)
diff --git a/lib/common/tests/flags/pcmk__clear_flags_as_test.c b/lib/common/tests/flags/pcmk__clear_flags_as_test.c
index a476adf..8a1fa13 100644
--- a/lib/common/tests/flags/pcmk__clear_flags_as_test.c
+++ b/lib/common/tests/flags/pcmk__clear_flags_as_test.c
@@ -1,5 +1,5 @@
/*
- * Copyright 2020 the Pacemaker project contributors
+ * Copyright 2020-2021 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
@@ -9,37 +9,45 @@
#include <crm_internal.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <setjmp.h>
+#include <cmocka.h>
+
static void
-clear_none(void) {
- g_assert_cmphex(pcmk__clear_flags_as(__func__, __LINE__, LOG_TRACE, "Test",
- "test", 0x0f0, 0x00f, NULL), ==, 0x0f0);
- g_assert_cmphex(pcmk__clear_flags_as(__func__, __LINE__, LOG_TRACE, "Test",
- "test", 0x0f0, 0xf0f, NULL), ==, 0x0f0);
+clear_none(void **state) {
+ assert_int_equal(pcmk__clear_flags_as(__func__, __LINE__, LOG_TRACE, "Test",
+ "test", 0x0f0, 0x00f, NULL), 0x0f0);
+ assert_int_equal(pcmk__clear_flags_as(__func__, __LINE__, LOG_TRACE, "Test",
+ "test", 0x0f0, 0xf0f, NULL), 0x0f0);
}
static void
-clear_some(void) {
- g_assert_cmphex(pcmk__clear_flags_as(__func__, __LINE__, LOG_TRACE, "Test",
- "test", 0x0f0, 0x020, NULL), ==, 0x0d0);
- g_assert_cmphex(pcmk__clear_flags_as(__func__, __LINE__, LOG_TRACE, "Test",
- "test", 0x0f0, 0x030, NULL), ==, 0x0c0);
+clear_some(void **state) {
+ assert_int_equal(pcmk__clear_flags_as(__func__, __LINE__, LOG_TRACE, "Test",
+ "test", 0x0f0, 0x020, NULL), 0x0d0);
+ assert_int_equal(pcmk__clear_flags_as(__func__, __LINE__, LOG_TRACE, "Test",
+ "test", 0x0f0, 0x030, NULL), 0x0c0);
}
static void
-clear_all(void) {
- g_assert_cmphex(pcmk__clear_flags_as(__func__, __LINE__, LOG_TRACE, "Test",
- "test", 0x0f0, 0x0f0, NULL), ==, 0x000);
- g_assert_cmphex(pcmk__clear_flags_as(__func__, __LINE__, LOG_TRACE, "Test",
- "test", 0x0f0, 0xfff, NULL), ==, 0x000);
+clear_all(void **state) {
+ assert_int_equal(pcmk__clear_flags_as(__func__, __LINE__, LOG_TRACE, "Test",
+ "test", 0x0f0, 0x0f0, NULL), 0x000);
+ assert_int_equal(pcmk__clear_flags_as(__func__, __LINE__, LOG_TRACE, "Test",
+ "test", 0x0f0, 0xfff, NULL), 0x000);
}
int
main(int argc, char **argv)
{
- g_test_init(&argc, &argv, NULL);
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(clear_none),
+ cmocka_unit_test(clear_some),
+ cmocka_unit_test(clear_all),
+ };
- g_test_add_func("/common/flags/clear/clear_none", clear_none);
- g_test_add_func("/common/flags/clear/clear_some", clear_some);
- g_test_add_func("/common/flags/clear/clear_all", clear_all);
- return g_test_run();
+ cmocka_set_message_output(CM_OUTPUT_TAP);
+ return cmocka_run_group_tests(tests, NULL, NULL);
}
diff --git a/lib/common/tests/flags/pcmk__set_flags_as_test.c b/lib/common/tests/flags/pcmk__set_flags_as_test.c
index 9a8292c..1a927bb 100644
--- a/lib/common/tests/flags/pcmk__set_flags_as_test.c
+++ b/lib/common/tests/flags/pcmk__set_flags_as_test.c
@@ -1,5 +1,5 @@
/*
- * Copyright 2020 the Pacemaker project contributors
+ * Copyright 2020-2021 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
@@ -9,21 +9,29 @@
#include <crm_internal.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <setjmp.h>
+#include <cmocka.h>
+
static void
-set_flags(void) {
- g_assert_cmphex(pcmk__set_flags_as(__func__, __LINE__, LOG_TRACE, "Test",
- "test", 0x0f0, 0x00f, NULL), ==, 0x0ff);
- g_assert_cmphex(pcmk__set_flags_as(__func__, __LINE__, LOG_TRACE, "Test",
- "test", 0x0f0, 0xf0f, NULL), ==, 0xfff);
- g_assert_cmphex(pcmk__set_flags_as(__func__, __LINE__, LOG_TRACE, "Test",
- "test", 0x0f0, 0xfff, NULL), ==, 0xfff);
+set_flags(void **state) {
+ assert_int_equal(pcmk__set_flags_as(__func__, __LINE__, LOG_TRACE, "Test",
+ "test", 0x0f0, 0x00f, NULL), 0x0ff);
+ assert_int_equal(pcmk__set_flags_as(__func__, __LINE__, LOG_TRACE, "Test",
+ "test", 0x0f0, 0xf0f, NULL), 0xfff);
+ assert_int_equal(pcmk__set_flags_as(__func__, __LINE__, LOG_TRACE, "Test",
+ "test", 0x0f0, 0xfff, NULL), 0xfff);
}
int
main(int argc, char **argv)
{
- g_test_init(&argc, &argv, NULL);
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(set_flags),
+ };
- g_test_add_func("/common/flags/set/set_flags", set_flags);
- return g_test_run();
+ cmocka_set_message_output(CM_OUTPUT_TAP);
+ return cmocka_run_group_tests(tests, NULL, NULL);
}
diff --git a/lib/common/tests/flags/pcmk_all_flags_set_test.c b/lib/common/tests/flags/pcmk_all_flags_set_test.c
index ebe175d..4596ad9 100644
--- a/lib/common/tests/flags/pcmk_all_flags_set_test.c
+++ b/lib/common/tests/flags/pcmk_all_flags_set_test.c
@@ -1,5 +1,5 @@
/*
- * Copyright 2020 the Pacemaker project contributors
+ * Copyright 2020-2021 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
@@ -7,33 +7,39 @@
* version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
*/
-#include <stdio.h>
-#include <stdbool.h>
#include <crm_internal.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <setjmp.h>
+#include <cmocka.h>
+
static void
-all_set(void) {
- g_assert_false(pcmk_all_flags_set(0x000, 0x003));
- g_assert_true(pcmk_all_flags_set(0x00f, 0x003));
- g_assert_false(pcmk_all_flags_set(0x00f, 0x010));
- g_assert_false(pcmk_all_flags_set(0x00f, 0x011));
- g_assert_true(pcmk_all_flags_set(0x000, 0x000));
- g_assert_true(pcmk_all_flags_set(0x00f, 0x000));
+all_set(void **state) {
+ assert_false(pcmk_all_flags_set(0x000, 0x003));
+ assert_true(pcmk_all_flags_set(0x00f, 0x003));
+ assert_false(pcmk_all_flags_set(0x00f, 0x010));
+ assert_false(pcmk_all_flags_set(0x00f, 0x011));
+ assert_true(pcmk_all_flags_set(0x000, 0x000));
+ assert_true(pcmk_all_flags_set(0x00f, 0x000));
}
static void
-one_is_set(void) {
+one_is_set(void **state) {
// pcmk_is_set() is a simple macro alias for pcmk_all_flags_set()
- g_assert_true(pcmk_is_set(0x00f, 0x001));
- g_assert_false(pcmk_is_set(0x00f, 0x010));
+ assert_true(pcmk_is_set(0x00f, 0x001));
+ assert_false(pcmk_is_set(0x00f, 0x010));
}
int
main(int argc, char **argv)
{
- g_test_init(&argc, &argv, NULL);
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(all_set),
+ cmocka_unit_test(one_is_set),
+ };
- g_test_add_func("/common/flags/all_set/all_set", all_set);
- g_test_add_func("/common/flags/all_set/is_set", one_is_set);
- return g_test_run();
+ cmocka_set_message_output(CM_OUTPUT_TAP);
+ return cmocka_run_group_tests(tests, NULL, NULL);
}
diff --git a/lib/common/tests/flags/pcmk_any_flags_set_test.c b/lib/common/tests/flags/pcmk_any_flags_set_test.c
index d465d0d..1d3407d 100644
--- a/lib/common/tests/flags/pcmk_any_flags_set_test.c
+++ b/lib/common/tests/flags/pcmk_any_flags_set_test.c
@@ -1,5 +1,5 @@
/*
- * Copyright 2020 the Pacemaker project contributors
+ * Copyright 2020-2021 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
@@ -7,26 +7,32 @@
* version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
*/
-#include <stdio.h>
-#include <stdbool.h>
#include <crm_internal.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <setjmp.h>
+#include <cmocka.h>
+
static void
-any_set(void) {
- g_assert_false(pcmk_any_flags_set(0x000, 0x000));
- g_assert_false(pcmk_any_flags_set(0x000, 0x001));
- g_assert_true(pcmk_any_flags_set(0x00f, 0x001));
- g_assert_false(pcmk_any_flags_set(0x00f, 0x010));
- g_assert_true(pcmk_any_flags_set(0x00f, 0x011));
- g_assert_false(pcmk_any_flags_set(0x000, 0x000));
- g_assert_false(pcmk_any_flags_set(0x00f, 0x000));
+any_set(void **state) {
+ assert_false(pcmk_any_flags_set(0x000, 0x000));
+ assert_false(pcmk_any_flags_set(0x000, 0x001));
+ assert_true(pcmk_any_flags_set(0x00f, 0x001));
+ assert_false(pcmk_any_flags_set(0x00f, 0x010));
+ assert_true(pcmk_any_flags_set(0x00f, 0x011));
+ assert_false(pcmk_any_flags_set(0x000, 0x000));
+ assert_false(pcmk_any_flags_set(0x00f, 0x000));
}
int
main(int argc, char **argv)
{
- g_test_init(&argc, &argv, NULL);
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(any_set),
+ };
- g_test_add_func("/common/flags/any_set/any_set", any_set);
- return g_test_run();
+ cmocka_set_message_output(CM_OUTPUT_TAP);
+ return cmocka_run_group_tests(tests, NULL, NULL);
}
diff --git a/lib/common/tests/io/Makefile.am b/lib/common/tests/io/Makefile.am
new file mode 100644
index 0000000..448de6e
--- /dev/null
+++ b/lib/common/tests/io/Makefile.am
@@ -0,0 +1,26 @@
+#
+# Copyright 2020-2021 the Pacemaker project contributors
+#
+# The version control history for this file may have further details.
+#
+# This source code is licensed under the GNU General Public License version 2
+# or later (GPLv2+) WITHOUT ANY WARRANTY.
+#
+
+include $(top_srcdir)/lib/common/mock.mk
+
+AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_builddir)/include -I$(top_srcdir)/lib/common
+LDADD = $(top_builddir)/lib/common/libcrmcommon.la -lcmocka
+
+pcmk__get_tmpdir_test_LDADD = $(top_builddir)/lib/common/libcrmcommon_test.la -lcmocka
+pcmk__get_tmpdir_test_LDFLAGS = -Wl,--wrap=getenv
+
+include $(top_srcdir)/mk/tap.mk
+
+# Add "_test" to the end of all test program names to simplify .gitignore.
+check_PROGRAMS = \
+ pcmk__full_path_test \
+ pcmk__get_tmpdir_test
+
+TESTS = $(check_PROGRAMS)
+
diff --git a/lib/common/tests/io/pcmk__full_path_test.c b/lib/common/tests/io/pcmk__full_path_test.c
new file mode 100644
index 0000000..47333e4
--- /dev/null
+++ b/lib/common/tests/io/pcmk__full_path_test.c
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2020-2021 the Pacemaker project contributors
+ *
+ * The version control history for this file may have further details.
+ *
+ * This source code is licensed under the GNU Lesser General Public License
+ * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
+ */
+
+#include <crm_internal.h>
+
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+#include <stdlib.h>
+#include <setjmp.h>
+#include <cmocka.h>
+
+static void
+full_path(void **state)
+{
+ char *path = NULL;
+
+ path = pcmk__full_path("file", "/dir");
+ assert_int_equal(strcmp(path, "/dir/file"), 0);
+ free(path);
+
+ path = pcmk__full_path("/full/path", "/dir");
+ assert_int_equal(strcmp(path, "/full/path"), 0);
+ free(path);
+
+ path = pcmk__full_path("../relative/path", "/dir");
+ assert_int_equal(strcmp(path, "/dir/../relative/path"), 0);
+ free(path);
+}
+
+int
+main(int argc, char **argv)
+{
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(full_path),
+ };
+
+ cmocka_set_message_output(CM_OUTPUT_TAP);
+ return cmocka_run_group_tests(tests, NULL, NULL);
+}
diff --git a/lib/common/tests/io/pcmk__get_tmpdir_test.c b/lib/common/tests/io/pcmk__get_tmpdir_test.c
new file mode 100644
index 0000000..7fbad8f
--- /dev/null
+++ b/lib/common/tests/io/pcmk__get_tmpdir_test.c
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2021 the Pacemaker project contributors
+ *
+ * The version control history for this file may have further details.
+ *
+ * This source code is licensed under the GNU Lesser General Public License
+ * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
+ */
+
+#include <crm_internal.h>
+#include "mock_private.h"
+
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <setjmp.h>
+#include <cmocka.h>
+
+static bool use_mocked = false;
+
+char *
+__wrap_getenv(const char *name)
+{
+ /* If coverage is enabled, something in "make check" will want to call
+ * the real getenv(), which will of course fail if the mocked version gets
+ * used. This bool needs to be set and unset around every unit test so
+ * the mocked version is only called in that time.
+ */
+ if (use_mocked) {
+ return mock_ptr_type(char *);
+ } else {
+ return __real_getenv(name);
+ }
+}
+
+static void
+getenv_returns_invalid(void **state)
+{
+ const char *result;
+
+ use_mocked = true;
+
+ will_return(__wrap_getenv, NULL); // getenv("TMPDIR") return value
+ result = pcmk__get_tmpdir();
+ assert_string_equal(result, "/tmp");
+
+ will_return(__wrap_getenv, ""); // getenv("TMPDIR") return value
+ result = pcmk__get_tmpdir();
+ assert_string_equal(result, "/tmp");
+
+ will_return(__wrap_getenv, "subpath"); // getenv("TMPDIR") return value
+ result = pcmk__get_tmpdir();
+ assert_string_equal(result, "/tmp");
+
+ use_mocked = false;
+}
+
+static void
+getenv_returns_valid(void **state)
+{
+ const char *result;
+
+ use_mocked = true;
+
+ will_return(__wrap_getenv, "/var/tmp"); // getenv("TMPDIR") return value
+ result = pcmk__get_tmpdir();
+ assert_string_equal(result, "/var/tmp");
+
+ will_return(__wrap_getenv, "/"); // getenv("TMPDIR") return value
+ result = pcmk__get_tmpdir();
+ assert_string_equal(result, "/");
+
+ will_return(__wrap_getenv, "/tmp/abcd.1234"); // getenv("TMPDIR") return value
+ result = pcmk__get_tmpdir();
+ assert_string_equal(result, "/tmp/abcd.1234");
+
+ use_mocked = false;
+}
+
+int
+main(int argc, char **argv)
+{
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(getenv_returns_invalid),
+ cmocka_unit_test(getenv_returns_valid),
+ };
+
+ cmocka_set_message_output(CM_OUTPUT_TAP);
+ return cmocka_run_group_tests(tests, NULL, NULL);
+}
diff --git a/lib/common/tests/iso8601/Makefile.am b/lib/common/tests/iso8601/Makefile.am
new file mode 100644
index 0000000..7bbc111
--- /dev/null
+++ b/lib/common/tests/iso8601/Makefile.am
@@ -0,0 +1,17 @@
+#
+# Copyright 2020-2021 the Pacemaker project contributors
+#
+# The version control history for this file may have further details.
+#
+# This source code is licensed under the GNU General Public License version 2
+# or later (GPLv2+) WITHOUT ANY WARRANTY.
+#
+AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_builddir)/include
+LDADD = $(top_builddir)/lib/common/libcrmcommon.la -lcmocka
+
+include $(top_srcdir)/mk/tap.mk
+
+# Add "_test" to the end of all test program names to simplify .gitignore.
+check_PROGRAMS = pcmk__readable_interval_test
+
+TESTS = $(check_PROGRAMS)
diff --git a/lib/common/tests/iso8601/pcmk__readable_interval_test.c b/lib/common/tests/iso8601/pcmk__readable_interval_test.c
new file mode 100644
index 0000000..62b74e9
--- /dev/null
+++ b/lib/common/tests/iso8601/pcmk__readable_interval_test.c
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2021 the Pacemaker project contributors
+ *
+ * The version control history for this file may have further details.
+ *
+ * This source code is licensed under the GNU Lesser General Public License
+ * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
+ */
+
+#include <crm_internal.h>
+
+#include <limits.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <setjmp.h>
+#include <cmocka.h>
+
+static void
+readable_interval(void **state)
+{
+ assert_string_equal(pcmk__readable_interval(0), "0s");
+ assert_string_equal(pcmk__readable_interval(30000), "30s");
+ assert_string_equal(pcmk__readable_interval(150000), "2m30s");
+ assert_string_equal(pcmk__readable_interval(3333), "3.333s");
+ assert_string_equal(pcmk__readable_interval(UINT_MAX), "49d17h2m47.295s");
+}
+
+int
+main(int argc, char **argv)
+{
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(readable_interval),
+ };
+
+ cmocka_set_message_output(CM_OUTPUT_TAP);
+ return cmocka_run_group_tests(tests, NULL, NULL);
+}
diff --git a/lib/common/tests/operations/Makefile.am b/lib/common/tests/operations/Makefile.am
index cd5884b..c8814ff 100644
--- a/lib/common/tests/operations/Makefile.am
+++ b/lib/common/tests/operations/Makefile.am
@@ -7,23 +7,11 @@
# or later (GPLv2+) WITHOUT ANY WARRANTY.
#
AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_builddir)/include
-LDADD = $(top_builddir)/lib/common/libcrmcommon.la
+LDADD = $(top_builddir)/lib/common/libcrmcommon.la -lcmocka
-include $(top_srcdir)/mk/glib-tap.mk
+include $(top_srcdir)/mk/tap.mk
-# Add each test program here. Each test should be written as a little standalone
-# program using the glib unit testing functions. See the documentation for more
-# information.
-#
-# https://developer.gnome.org/glib/unstable/glib-Testing.html
-#
# Add "_test" to the end of all test program names to simplify .gitignore.
-test_programs = parse_op_key_test
-
-# If any extra data needs to be added to the source distribution, add it to the
-# following list.
-dist_test_data =
+check_PROGRAMS = parse_op_key_test
-# If any extra data needs to be used by tests but should not be added to the
-# source distribution, add it to the following list.
-test_data =
+TESTS = $(check_PROGRAMS)
diff --git a/lib/common/tests/operations/parse_op_key_test.c b/lib/common/tests/operations/parse_op_key_test.c
index 857431a..daac346 100644
--- a/lib/common/tests/operations/parse_op_key_test.c
+++ b/lib/common/tests/operations/parse_op_key_test.c
@@ -9,220 +9,226 @@
#include <crm_internal.h>
-#include <stdio.h>
#include <glib.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <setjmp.h>
+#include <cmocka.h>
static void
-basic(void)
+basic(void **state)
{
char *rsc = NULL;
char *ty = NULL;
guint ms = 0;
- g_assert_true(parse_op_key("Fencing_monitor_60000", &rsc, &ty, &ms));
- g_assert_cmpstr(rsc, ==, "Fencing");
- g_assert_cmpstr(ty, ==, "monitor");
- g_assert_cmpint(ms, ==, 60000);
+ assert_true(parse_op_key("Fencing_monitor_60000", &rsc, &ty, &ms));
+ assert_string_equal(rsc, "Fencing");
+ assert_string_equal(ty, "monitor");
+ assert_int_equal(ms, 60000);
free(rsc);
free(ty);
}
static void
-colon_in_rsc(void)
+colon_in_rsc(void **state)
{
char *rsc = NULL;
char *ty = NULL;
guint ms = 0;
- g_assert_true(parse_op_key("ClusterIP:0_start_0", &rsc, &ty, &ms));
- g_assert_cmpstr(rsc, ==, "ClusterIP:0");
- g_assert_cmpstr(ty, ==, "start");
- g_assert_cmpint(ms, ==, 0);
+ assert_true(parse_op_key("ClusterIP:0_start_0", &rsc, &ty, &ms));
+ assert_string_equal(rsc, "ClusterIP:0");
+ assert_string_equal(ty, "start");
+ assert_int_equal(ms, 0);
free(rsc);
free(ty);
- g_assert_true(parse_op_key("imagestoreclone:1_post_notify_stop_0", &rsc, &ty, &ms));
- g_assert_cmpstr(rsc, ==, "imagestoreclone:1");
- g_assert_cmpstr(ty, ==, "post_notify_stop");
- g_assert_cmpint(ms, ==, 0);
+ assert_true(parse_op_key("imagestoreclone:1_post_notify_stop_0", &rsc, &ty, &ms));
+ assert_string_equal(rsc, "imagestoreclone:1");
+ assert_string_equal(ty, "post_notify_stop");
+ assert_int_equal(ms, 0);
free(rsc);
free(ty);
}
static void
-dashes_in_rsc(void)
+dashes_in_rsc(void **state)
{
char *rsc = NULL;
char *ty = NULL;
guint ms = 0;
- g_assert_true(parse_op_key("httpd-bundle-0_monitor_30000", &rsc, &ty, &ms));
- g_assert_cmpstr(rsc, ==, "httpd-bundle-0");
- g_assert_cmpstr(ty, ==, "monitor");
- g_assert_cmpint(ms, ==, 30000);
+ assert_true(parse_op_key("httpd-bundle-0_monitor_30000", &rsc, &ty, &ms));
+ assert_string_equal(rsc, "httpd-bundle-0");
+ assert_string_equal(ty, "monitor");
+ assert_int_equal(ms, 30000);
free(rsc);
free(ty);
- g_assert_true(parse_op_key("httpd-bundle-ip-192.168.122.132_start_0", &rsc, &ty, &ms));
- g_assert_cmpstr(rsc, ==, "httpd-bundle-ip-192.168.122.132");
- g_assert_cmpstr(ty, ==, "start");
- g_assert_cmpint(ms, ==, 0);
+ assert_true(parse_op_key("httpd-bundle-ip-192.168.122.132_start_0", &rsc, &ty, &ms));
+ assert_string_equal(rsc, "httpd-bundle-ip-192.168.122.132");
+ assert_string_equal(ty, "start");
+ assert_int_equal(ms, 0);
free(rsc);
free(ty);
}
static void
-migrate_to_from(void)
+migrate_to_from(void **state)
{
char *rsc = NULL;
char *ty = NULL;
guint ms = 0;
- g_assert_true(parse_op_key("vm_migrate_from_0", &rsc, &ty, &ms));
- g_assert_cmpstr(rsc, ==, "vm");
- g_assert_cmpstr(ty, ==, "migrate_from");
- g_assert_cmpint(ms, ==, 0);
+ assert_true(parse_op_key("vm_migrate_from_0", &rsc, &ty, &ms));
+ assert_string_equal(rsc, "vm");
+ assert_string_equal(ty, "migrate_from");
+ assert_int_equal(ms, 0);
free(rsc);
free(ty);
- g_assert_true(par