summaryrefslogtreecommitdiffstats
path: root/agents
diff options
context:
space:
mode:
Diffstat (limited to 'agents')
-rw-r--r--agents/Makefile.am12
-rw-r--r--agents/alerts/Makefile.am15
-rw-r--r--agents/alerts/alert_file.sh.sample122
-rw-r--r--agents/alerts/alert_smtp.sh.sample118
-rw-r--r--agents/alerts/alert_snmp.sh.sample193
-rwxr-xr-xagents/ocf/ClusterMon.in276
-rwxr-xr-xagents/ocf/Dummy.in323
-rwxr-xr-xagents/ocf/HealthCPU.in212
-rwxr-xr-xagents/ocf/HealthIOWait.in197
-rwxr-xr-xagents/ocf/HealthSMART.in372
-rw-r--r--agents/ocf/Makefile.am53
-rwxr-xr-xagents/ocf/Stateful.in267
-rwxr-xr-xagents/ocf/SysInfo.in407
-rwxr-xr-xagents/ocf/attribute.in240
-rw-r--r--agents/ocf/controld.in303
-rwxr-xr-xagents/ocf/ifspeed.in553
-rwxr-xr-xagents/ocf/o2cb.in440
-rwxr-xr-xagents/ocf/ping.in436
-rwxr-xr-xagents/ocf/remote.in106
-rw-r--r--agents/stonith/Makefile.am19
-rwxr-xr-xagents/stonith/fence_legacy.in277
-rwxr-xr-xagents/stonith/fence_watchdog.in284
22 files changed, 5225 insertions, 0 deletions
diff --git a/agents/Makefile.am b/agents/Makefile.am
new file mode 100644
index 0000000..3cbd7c6
--- /dev/null
+++ b/agents/Makefile.am
@@ -0,0 +1,12 @@
+#
+# Copyright 2023 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)/mk/common.mk
+
+SUBDIRS = alerts ocf stonith
diff --git a/agents/alerts/Makefile.am b/agents/alerts/Makefile.am
new file mode 100644
index 0000000..fdb294f
--- /dev/null
+++ b/agents/alerts/Makefile.am
@@ -0,0 +1,15 @@
+#
+# Copyright 2016-2023 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)/mk/common.mk
+
+samplesdir = $(datadir)/$(PACKAGE)/alerts/
+dist_samples_DATA = alert_file.sh.sample \
+ alert_smtp.sh.sample \
+ alert_snmp.sh.sample
diff --git a/agents/alerts/alert_file.sh.sample b/agents/alerts/alert_file.sh.sample
new file mode 100644
index 0000000..f6c2211
--- /dev/null
+++ b/agents/alerts/alert_file.sh.sample
@@ -0,0 +1,122 @@
+#!/bin/sh
+#
+# 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 General Public License version 2
+# or later (GPLv2+) WITHOUT ANY WARRANTY.
+#
+##############################################################################
+# Sample configuration (cib fragment in xml notation)
+# ================================
+# <configuration>
+# <alerts>
+# <alert id="alert_sample" path="/path/to/alert_file.sh">
+# <instance_attributes id="config_for_alert_file">
+# <nvpair id="debug_option_1" name="debug_exec_order" value="false"/>
+# </instance_attributes>
+# <meta_attributes id="config_for_timestamp">
+# <nvpair id="ts_fmt" name="timestamp-format" value="%H:%M:%S.%06N"/>
+# </meta_attributes>
+# <recipient id="logfile_destination" value="/path/to/logfile"/>
+# </alert>
+# </alerts>
+# </configuration>
+
+# Explicitly list all environment variables used, to make static analysis happy
+: ${CRM_alert_version:=""}
+: ${CRM_alert_recipient:=""}
+: ${CRM_alert_node_sequence:=""}
+: ${CRM_alert_timestamp:=""}
+: ${CRM_alert_kind:=""}
+: ${CRM_alert_node:=""}
+: ${CRM_alert_desc:=""}
+: ${CRM_alert_task:=""}
+: ${CRM_alert_rsc:=""}
+: ${CRM_alert_attribute_name:=""}
+: ${CRM_alert_attribute_value:=""}
+
+# No one will probably ever see this echo, unless they run the script manually.
+# An alternative would be to log to the system log, or similar. (We can't send
+# this to the configured recipient, because that variable won't be defined in
+# this case either.)
+if [ -z $CRM_alert_version ]; then
+ echo "$0 must be run by Pacemaker version 1.1.15 or later"
+ exit 0
+fi
+
+# Alert agents must always handle the case where no recipients are defined,
+# even if it's a no-op (a recipient might be added to the configuration later).
+if [ -z "${CRM_alert_recipient}" ]; then
+ echo "$0 requires a recipient configured with a full filename path"
+ exit 0
+fi
+
+debug_exec_order_default="false"
+
+# Pacemaker passes instance attributes to alert agents as environment variables.
+# It is completely up to the agent what instance attributes to support.
+# Here, we define an instance attribute "debug_exec_order".
+: ${debug_exec_order=${debug_exec_order_default}}
+
+if [ "${debug_exec_order}" = "true" ]; then
+ tstamp=`printf "%04d. " "$CRM_alert_node_sequence"`
+ if [ ! -z "$CRM_alert_timestamp" ]; then
+ tstamp="${tstamp} $CRM_alert_timestamp (`date "+%H:%M:%S.%06N"`): "
+ fi
+else
+ if [ ! -z "$CRM_alert_timestamp" ]; then
+ tstamp="$CRM_alert_timestamp: "
+ fi
+fi
+
+case $CRM_alert_kind in
+ node)
+ echo "${tstamp}Node '${CRM_alert_node}' is now '${CRM_alert_desc}'" >> "${CRM_alert_recipient}"
+ ;;
+ fencing)
+ # Other keys:
+ #
+ # CRM_alert_node
+ # CRM_alert_task
+ # CRM_alert_rc
+ #
+ echo "${tstamp}Fencing ${CRM_alert_desc}" >> "${CRM_alert_recipient}"
+ ;;
+ resource)
+ # Other keys:
+ #
+ # CRM_alert_target_rc
+ # CRM_alert_status
+ # CRM_alert_rc
+ #
+ if [ ${CRM_alert_interval} = "0" ]; then
+ CRM_alert_interval=""
+ else
+ CRM_alert_interval=" (${CRM_alert_interval})"
+ fi
+
+ if [ ${CRM_alert_target_rc} = "0" ]; then
+ CRM_alert_target_rc=""
+ else
+ CRM_alert_target_rc=" (target: ${CRM_alert_target_rc})"
+ fi
+
+ case ${CRM_alert_desc} in
+ Cancelled) ;;
+ *)
+ echo "${tstamp}Resource operation '${CRM_alert_task}${CRM_alert_interval}' for '${CRM_alert_rsc}' on '${CRM_alert_node}': ${CRM_alert_desc}${CRM_alert_target_rc}" >> "${CRM_alert_recipient}"
+ ;;
+ esac
+ ;;
+ attribute)
+ #
+ echo "${tstamp}Attribute '${CRM_alert_attribute_name}' on node '${CRM_alert_node}' was updated to '${CRM_alert_attribute_value}'" >> "${CRM_alert_recipient}"
+ ;;
+
+ *)
+ echo "${tstamp}Unhandled $CRM_alert_kind alert" >> "${CRM_alert_recipient}"
+ env | grep CRM_alert >> "${CRM_alert_recipient}"
+ ;;
+esac
diff --git a/agents/alerts/alert_smtp.sh.sample b/agents/alerts/alert_smtp.sh.sample
new file mode 100644
index 0000000..62bfc41
--- /dev/null
+++ b/agents/alerts/alert_smtp.sh.sample
@@ -0,0 +1,118 @@
+#!/bin/sh
+#
+# Copyright 2016-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.
+#
+##############################################################################
+#
+# Sample configuration (cib fragment in xml notation)
+# ================================
+# <configuration>
+# <alerts>
+# <alert id="smtp_alert" path="/path/to/alert_smtp">
+# <instance_attributes id="config_for_alert_smtp">
+# <nvpair id="cluster_name" name="cluster_name" value=""/>
+# <nvpair id="email_client" name="email_client" value=""/>
+# <nvpair id="email_sender" name="email_sender" value=""/>
+# </instance_attributes>
+# <recipient id="smtp_destination" value="admin@example.com"/>
+# </alert>
+# </alerts>
+# </configuration>
+
+# Explicitly list all environment variables used, to make static analysis happy
+: ${CRM_alert_version:=""}
+: ${CRM_alert_recipient:=""}
+: ${CRM_alert_timestamp:=""}
+: ${CRM_alert_kind:=""}
+: ${CRM_alert_node:=""}
+: ${CRM_alert_desc:=""}
+: ${CRM_alert_task:=""}
+: ${CRM_alert_rsc:=""}
+: ${CRM_alert_attribute_name:=""}
+: ${CRM_alert_attribute_value:=""}
+
+email_client_default="sendmail"
+email_sender_default="hacluster"
+email_recipient_default="root"
+
+: ${email_client=${email_client_default}}
+: ${email_sender=${email_sender_default}}
+email_recipient="${CRM_alert_recipient-${email_recipient_default}}"
+
+node_name=`uname -n`
+cluster_name=`crm_attribute --query -n cluster-name -q`
+email_body=`env | grep CRM_alert_`
+
+if [ ! -z "${email_sender##*@*}" ]; then
+ email_sender="${email_sender}@${node_name}"
+fi
+
+if [ ! -z "${email_recipient##*@*}" ]; then
+ email_recipient="${email_recipient}@${node_name}"
+fi
+
+if [ -z ${CRM_alert_version} ]; then
+ email_subject="Pacemaker version 1.1.15 or later is required for alerts"
+else
+ case ${CRM_alert_kind} in
+ node)
+ email_subject="${CRM_alert_timestamp} ${cluster_name}: Node '${CRM_alert_node}' is now '${CRM_alert_desc}'"
+ ;;
+ fencing)
+ email_subject="${CRM_alert_timestamp} ${cluster_name}: Fencing ${CRM_alert_desc}"
+ ;;
+ resource)
+ if [ ${CRM_alert_interval} = "0" ]; then
+ CRM_alert_interval=""
+ else
+ CRM_alert_interval=" (${CRM_alert_interval})"
+ fi
+
+ if [ ${CRM_alert_target_rc} = "0" ]; then
+ CRM_alert_target_rc=""
+ else
+ CRM_alert_target_rc=" (target: ${CRM_alert_target_rc})"
+ fi
+
+ case ${CRM_alert_desc} in
+ Cancelled) ;;
+ *)
+ email_subject="${CRM_alert_timestamp} ${cluster_name}: Resource operation '${CRM_alert_task}${CRM_alert_interval}' for '${CRM_alert_rsc}' on '${CRM_alert_node}': ${CRM_alert_desc}${CRM_alert_target_rc}"
+ ;;
+ esac
+ ;;
+ attribute)
+ #
+ email_subject="${CRM_alert_timestamp} ${cluster_name}: The '${CRM_alert_attribute_name}' attribute of the '${CRM_alert_node}' node was updated in '${CRM_alert_attribute_value}'"
+ ;;
+ *)
+ email_subject="${CRM_alert_timestamp} ${cluster_name}: Unhandled $CRM_alert_kind alert"
+ ;;
+
+ esac
+fi
+
+if [ ! -z "${email_subject}" ]; then
+ case $email_client in
+ # This sample script supports only sendmail for sending the email.
+ # Support for additional senders can easily be added by adding
+ # new cases here.
+ sendmail)
+ sendmail -t -r "${email_sender}" <<__EOF__
+From: ${email_sender}
+To: ${email_recipient}
+Return-Path: ${email_sender}
+Subject: ${email_subject}
+
+${email_body}
+__EOF__
+ ;;
+ *)
+ ;;
+ esac
+fi
diff --git a/agents/alerts/alert_snmp.sh.sample b/agents/alerts/alert_snmp.sh.sample
new file mode 100644
index 0000000..a140828
--- /dev/null
+++ b/agents/alerts/alert_snmp.sh.sample
@@ -0,0 +1,193 @@
+#!/bin/sh
+#
+# Copyright 2013 Florian CROUZAT <gentoo@floriancrouzat.net>
+# Later changes copyright 2013-2023 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.
+#
+# Description: Manages a SNMP trap, provided by NTT OSSC as a
+# script under Pacemaker control
+#
+##############################################################################
+# This sample script assumes that only users who already have
+# hacluster-equivalent access to the cluster nodes can edit the CIB. Otherwise,
+# a malicious user could run commands as hacluster by inserting shell code into
+# the trap_options or timestamp-format parameters.
+#
+# Sample configuration (cib fragment in xml notation)
+# ================================
+# <configuration>
+# <alerts>
+# <alert id="snmp_alert" path="/path/to/alert_snmp.sh">
+# <instance_attributes id="config_for_alert_snmp">
+# <nvpair id="trap_node_states" name="trap_node_states" value="all"/>
+# </instance_attributes>
+# <meta_attributes id="config_for_timestamp">
+# <nvpair id="ts_fmt" name="timestamp-format" value="%Y-%m-%d,%H:%M:%S.%01N"/>
+# </meta_attributes>
+# <recipient id="snmp_destination" value="192.168.1.2"/>
+# </alert>
+# </alerts>
+# </configuration>
+# ================================
+#
+# This uses the official Pacemaker MIB.
+# 1.3.6.1.4.1.32723 has been assigned to the project by IANA:
+# http://www.iana.org/assignments/enterprise-numbers
+
+# Defaults for user-configurable values
+trap_binary_default="/usr/bin/snmptrap"
+trap_version_default="2c"
+trap_options_default=""
+trap_community_default="public"
+trap_node_states_default="all"
+trap_fencing_tasks_default="all"
+trap_resource_tasks_default="all"
+trap_monitor_success_default="false"
+trap_add_hires_timestamp_oid_default="true"
+trap_snmp_persistent_dir_default="/var/lib/pacemaker/snmp"
+trap_ignore_int32_default=2147483647 # maximum Integer32 value
+trap_ignore_string_default="n/a" # doesn't conflict with valid XML IDs
+
+# Ensure all user-provided variables have values.
+: ${trap_binary=${trap_binary_default}}
+: ${trap_version=${trap_version_default}}
+: ${trap_options=${trap_options_default}}
+: ${trap_community=${trap_community_default}}
+: ${trap_node_states=${trap_node_states_default}}
+: ${trap_fencing_tasks=${trap_fencing_tasks_default}}
+: ${trap_resource_tasks=${trap_resource_tasks_default}}
+: ${trap_monitor_success=${trap_monitor_success_default}}
+: ${trap_add_hires_timestamp_oid=${trap_add_hires_timestamp_oid_default}}
+: ${trap_snmp_persistent_dir=${trap_snmp_persistent_dir_default}}
+: ${trap_ignore_int32=${trap_ignore_int32_default}}
+: ${trap_ignore_string=${trap_ignore_string_default}}
+
+# Ensure all cluster-provided variables have values, regardless of alert type.
+: ${CRM_alert_node=${trap_ignore_string}}
+: ${CRM_alert_rsc=${trap_ignore_string}}
+: ${CRM_alert_task=${trap_ignore_string}}
+: ${CRM_alert_desc=${trap_ignore_string}}
+: ${CRM_alert_status=${trap_ignore_int32}}
+: ${CRM_alert_rc=${trap_ignore_int32}}
+: ${CRM_alert_target_rc=${trap_ignore_int32}}
+: ${CRM_alert_attribute_name=${trap_ignore_string}}
+: ${CRM_alert_attribute_value=${trap_ignore_string}}
+: ${CRM_alert_version:=""}
+: ${CRM_alert_recipient:=""}
+: ${CRM_alert_kind:=""}
+
+if [ -z "$CRM_alert_version" ]; then
+ echo "$0 must be run by Pacemaker version 1.1.15 or later"
+ exit 0
+fi
+
+# SNMP v3 and above do not use community, which must be empty
+case "$trap_version" in
+ 1|2c) ;;
+ *) trap_community="" ;;
+esac
+
+if [ -z "$CRM_alert_recipient" ]; then
+ echo "$0 requires a recipient configured with the SNMP server IP address"
+ exit 0
+fi
+
+# Echo a high-resolution equivalent of the Pacemaker-provided time values
+# using NetSNMP's DateAndTime specification ("%Y-%m-%d,%H:%M:%S.%01N").
+get_system_date() {
+ : ${CRM_alert_timestamp_epoch=$(date +%s)}
+ : ${CRM_alert_timestamp_usec=0}
+
+ YMDHMS=$(date --date="@${CRM_alert_timestamp_epoch}" +"%Y-%m-%d,%H:%M:%S")
+ USEC=$(echo ${CRM_alert_timestamp_usec} | cut -b 1)
+ echo "${YMDHMS}.${USEC}"
+}
+
+is_in_list() {
+ item_list=`echo "$1" | tr ',' ' '`
+
+ if [ "${item_list}" = "all" ]; then
+ return 0
+ else
+ for act in $item_list
+ do
+ act=`echo "$act" | tr A-Z a-z`
+ [ "$act" != "$2" ] && continue
+ return 0
+ done
+ fi
+ return 1
+}
+
+send_pacemaker_trap() {
+ PREFIX="PACEMAKER-MIB::pacemakerNotification"
+
+ OUTPUT=$("${trap_binary}" -v "${trap_version}" ${trap_options} \
+ ${trap_community:+-c "${trap_community}"} \
+ "${CRM_alert_recipient}" "" \
+ "${PREFIX}Trap" \
+ "${PREFIX}Node" s "${CRM_alert_node}" \
+ "${PREFIX}Resource" s "${CRM_alert_rsc}" \
+ "${PREFIX}Operation" s "${CRM_alert_task}" \
+ "${PREFIX}Description" s "${CRM_alert_desc}" \
+ "${PREFIX}Status" i "${CRM_alert_status}" \
+ "${PREFIX}ReturnCode" i "${CRM_alert_rc}" \
+ "${PREFIX}TargetReturnCode" i "${CRM_alert_target_rc}" \
+ "${PREFIX}AttributeName" s "${CRM_alert_attribute_name}" \
+ "${PREFIX}AttributeValue" s "${CRM_alert_attribute_value}" \
+ ${hires_timestamp} 2>&1)
+
+ if [ $? -ne 0 ]; then
+ echo "${trap_binary} returned error : rc=$? $OUTPUT"
+ fi
+}
+
+if [ "${trap_add_hires_timestamp_oid}" = "true" ]; then
+ hires_timestamp="HOST-RESOURCES-MIB::hrSystemDate s $(get_system_date)"
+fi
+
+if [ -z ${SNMP_PERSISTENT_DIR} ]; then
+ export SNMP_PERSISTENT_DIR="${trap_snmp_persistent_dir}"
+ # mkdir for snmp trap tools.
+ if [ ! -d ${SNMP_PERSISTENT_DIR} ]; then
+ mkdir -p ${SNMP_PERSISTENT_DIR}
+ fi
+fi
+
+case "$CRM_alert_kind" in
+ node)
+ if is_in_list "${trap_node_states}" "${CRM_alert_desc}"; then
+ send_pacemaker_trap
+ fi
+ ;;
+
+ fencing)
+ if is_in_list "${trap_fencing_tasks}" "${CRM_alert_task}"; then
+ send_pacemaker_trap
+ fi
+ ;;
+
+ resource)
+ if is_in_list "${trap_resource_tasks}" "${CRM_alert_task}" && \
+ [ "${CRM_alert_desc}" != "Cancelled" ] ; then
+
+ if [ "${trap_monitor_success}" = "false" ] && \
+ [ "${CRM_alert_rc}" = "${CRM_alert_target_rc}" ] && \
+ [ "${CRM_alert_task}" = "monitor" ]; then
+ exit 0
+ fi
+ send_pacemaker_trap
+ fi
+ ;;
+
+ attribute)
+ send_pacemaker_trap
+ ;;
+
+ *)
+ ;;
+esac
diff --git a/agents/ocf/ClusterMon.in b/agents/ocf/ClusterMon.in
new file mode 100755
index 0000000..722cd32
--- /dev/null
+++ b/agents/ocf/ClusterMon.in
@@ -0,0 +1,276 @@
+#!@BASH_PATH@
+#
+# ocf:pacemaker:ClusterMon resource agent
+#
+# Original copyright 2004 SUSE LINUX AG, Lars Marowsky-Br<E9>e
+# Later changes copyright 2008-2023 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
+# (GPLv2) WITHOUT ANY WARRANTY.
+#
+
+# Starts crm_mon in background which logs cluster status as
+# html to the specified file.
+
+#######################################################################
+# Initialization:
+: ${OCF_FUNCTIONS:="${OCF_ROOT}/resource.d/heartbeat/.ocf-shellfuncs"}
+. "${OCF_FUNCTIONS}"
+: ${__OCF_ACTION:="$1"}
+
+# Explicitly list all environment variables used, to make static analysis happy
+: ${OCF_RESKEY_user:=""}
+: ${OCF_RESKEY_pidfile:="/tmp/ClusterMon_${OCF_RESOURCE_INSTANCE}.pid"}
+: ${OCF_RESKEY_update:="15000"}
+: ${OCF_RESKEY_extra_options:=""}
+: ${OCF_RESKEY_htmlfile:="/tmp/ClusterMon_${OCF_RESOURCE_INSTANCE}.html"}
+
+#######################################################################
+
+meta_data() {
+ cat <<END
+<?xml version="1.0"?>
+<resource-agent name="ClusterMon" version="@VERSION@">
+<version>1.1</version>
+
+<longdesc lang="en">
+This is a ClusterMon Resource Agent.
+It outputs current cluster status to the html.
+</longdesc>
+<shortdesc lang="en">Runs crm_mon in the background, recording the cluster status to an HTML file</shortdesc>
+
+<parameters>
+
+<parameter name="user">
+<longdesc lang="en">
+The user we want to run crm_mon as
+</longdesc>
+<shortdesc lang="en">The user we want to run crm_mon as</shortdesc>
+<content type="string" default="root" />
+</parameter>
+
+<parameter name="update">
+<longdesc lang="en">
+How frequently should we update the cluster status (in milliseconds).
+For compatibility with old documentation, values less than 1000 will be treated
+as seconds.
+</longdesc>
+<shortdesc lang="en">Update interval in milliseconds</shortdesc>
+<content type="integer" default="15000" />
+</parameter>
+
+<parameter name="extra_options">
+<longdesc lang="en">
+Additional options to pass to crm_mon. Eg. -n -r
+</longdesc>
+<shortdesc lang="en">Extra options</shortdesc>
+<content type="string" default="" />
+</parameter>
+
+<parameter name="pidfile" unique-group="pidfile">
+<longdesc lang="en">
+PID file location to ensure only one instance is running
+</longdesc>
+<shortdesc lang="en">PID file</shortdesc>
+<content type="string" default="/tmp/ClusterMon_${OCF_RESOURCE_INSTANCE}.pid" />
+</parameter>
+
+<parameter name="htmlfile" unique-group="htmlfile" required="0">
+<longdesc lang="en">
+Location to write HTML output to.
+</longdesc>
+<shortdesc lang="en">HTML output</shortdesc>
+<content type="string" default="/tmp/ClusterMon_${OCF_RESOURCE_INSTANCE}.html" />
+</parameter>
+</parameters>
+
+<actions>
+<action name="start" timeout="20s" />
+<action name="stop" timeout="20s" />
+<action name="monitor" depth="0" timeout="20s" interval="10s" />
+<action name="meta-data" timeout="5s" />
+<action name="validate-all" timeout="30s" depth="0" />
+</actions>
+</resource-agent>
+END
+}
+
+#######################################################################
+
+ClusterMon_usage() {
+ cat <<END
+usage: $0 {start|stop|monitor|validate-all|meta-data}
+
+Expects to have a fully populated OCF RA-compliant environment set.
+END
+}
+
+ClusterMon_exit() {
+ if [ $1 -ne 0 ]; then
+ exit $OCF_ERR_GENERIC
+ else
+ exit $OCF_SUCCESS
+ fi
+}
+
+ClusterMon_start() {
+ if [ -n "$OCF_RESKEY_user" ]; then
+ su - "$OCF_RESKEY_user" -c "$CMON_CMD"
+ else
+ eval $CMON_CMD
+ fi
+ ClusterMon_exit $?
+}
+
+ClusterMon_stop() {
+ if [ -f "$OCF_RESKEY_pidfile" ]; then
+ pid=$(cat "$OCF_RESKEY_pidfile")
+ if [ -n "$pid" ]; then
+ kill -s 9 $pid
+ rm -f "$OCF_RESKEY_pidfile"
+ fi
+ fi
+ ClusterMon_exit 0
+}
+
+ClusterMon_monitor() {
+ local USERARG=""
+ local header
+ local pid
+
+ if [ -f "$OCF_RESKEY_pidfile" ]; then
+ pid=$(cat "$OCF_RESKEY_pidfile")
+ if [ -n "$pid" ]; then
+ if [ -n "$OCF_RESKEY_user" ]; then
+ USERARG="-u $OCF_RESKEY_user"
+ fi
+
+ # Use column header wide as command, to ensure it's shown in full
+ header=$(echo $CMON_CMD | tr 'crmon, \t' 'xxxxxxxx')
+
+ ps $USERARG -o "args=${header}" -p $pid 2>/dev/null | \
+ grep -qE "[c]rm_mon.*${OCF_RESKEY_pidfile}"
+
+ case $? in
+ 0) exit $OCF_SUCCESS;;
+ 1) exit $OCF_NOT_RUNNING;;
+ *) exit $OCF_ERR_GENERIC;;
+ esac
+ fi
+ fi
+ exit $OCF_NOT_RUNNING
+}
+
+CheckOptions() {
+while getopts Vi:nrh:cdp: OPTION
+do
+ case "$OPTION" in
+ V|n|r|c|d);;
+ i) ocf_log warn "You should not have specified the -i option, since OCF_RESKEY_update is set already!";;
+ h) ocf_log warn "You should not have specified the -h option, since OCF_RESKEY_htmlfile is set already!";;
+ p) ocf_log warn "You should not have specified the -p option, since OCF_RESKEY_pidfile is set already!";;
+ *) return $OCF_ERR_ARGS;;
+ esac
+
+ case "$OCF_RESKEY_extra_options" in
+ *--output-as*) ocf_log warn "You should not have specified the -output-as option, since OCF_RESKEY_htmlfile is set already!";;
+ *--output-to*) ocf_log warn "You should not have specified the -output-to option, since OCF_RESKEY_htmlfile is set already!";;
+ esac
+done
+
+if [ $? -ne 0 ]; then
+ return $OCF_ERR_ARGS
+fi
+
+# We should have eaten all options at this stage
+shift $(($OPTIND -1))
+if [ $# -gt 0 ]; then
+ false
+else
+ true
+fi
+}
+
+ClusterMon_validate() {
+ # Host-specific checks
+ if [ "$OCF_CHECK_LEVEL" = "10" ]; then
+
+ # Existence of the user
+ if [ -n "$OCF_RESKEY_user" ]; then
+ getent passwd "$OCF_RESKEY_user" >/dev/null
+ if [ $? -eq 0 ]; then
+ : Yes, user exists. We can further check his permission on crm_mon if necessary
+ else
+ ocf_log err "The user $OCF_RESKEY_user does not exist!"
+ exit $OCF_ERR_ARGS
+ fi
+ fi
+ fi
+
+ # Pidfile should be an absolute path
+ case "$OCF_RESKEY_pidfile" in
+ /*) ;;
+ *) ocf_log warn "pidfile ($OCF_RESKEY_pidfile) is not an absolute path" ;;
+ esac
+
+ # Check the update interval
+ if ocf_is_decimal "$OCF_RESKEY_update" && [ $OCF_RESKEY_update -gt 0 ]; then
+ :
+ else
+ ocf_log err "Invalid update interval $OCF_RESKEY_update. It should be positive integer!"
+ exit $OCF_ERR_ARGS
+ fi
+
+ if CheckOptions $OCF_RESKEY_extra_options; then
+ :
+ else
+ ocf_log err "Invalid options $OCF_RESKEY_extra_options!"
+ exit $OCF_ERR_ARGS
+ fi
+
+ # Htmlfile should be an absolute path
+ case "$OCF_RESKEY_htmlfile" in
+ /*) ;;
+ *) ocf_log warn "htmlfile ($OCF_RESKEY_htmlfile) is not an absolute path" ;;
+ esac
+
+ echo "Validate OK"
+ return $OCF_SUCCESS
+}
+
+if [ $# -ne 1 ]; then
+ ClusterMon_usage
+ exit $OCF_ERR_ARGS
+fi
+
+if [ ${OCF_RESKEY_update} -ge 1000 ]; then
+ OCF_RESKEY_update=$(( $OCF_RESKEY_update / 1000 ))
+fi
+
+CMON_CMD="${HA_SBIN_DIR}/crm_mon -p \"$OCF_RESKEY_pidfile\" -d -i $OCF_RESKEY_update $OCF_RESKEY_extra_options --output-as=html --output-to=\"$OCF_RESKEY_htmlfile\""
+
+case "$__OCF_ACTION" in
+meta-data) meta_data
+ exit $OCF_SUCCESS
+ ;;
+start) ClusterMon_start
+ ;;
+stop) ClusterMon_stop
+ ;;
+monitor) ClusterMon_monitor
+ ;;
+validate-all) ClusterMon_validate
+ ;;
+usage|help) ClusterMon_usage
+ exit $OCF_SUCCESS
+ ;;
+*) ClusterMon_usage
+ exit $OCF_ERR_UNIMPLEMENTED
+ ;;
+esac
+
+exit $?
+
+# vim: set filetype=sh expandtab tabstop=4 softtabstop=4 shiftwidth=4 textwidth=80:
diff --git a/agents/ocf/Dummy.in b/agents/ocf/Dummy.in
new file mode 100755
index 0000000..ceaafad
--- /dev/null
+++ b/agents/ocf/Dummy.in
@@ -0,0 +1,323 @@
+#!/bin/sh
+#
+# ocf:pacemaker:Dummy resource agent
+#
+# Original copyright 2004 SUSE LINUX AG, Lars Marowsky-Br<E9>e
+# Later changes copyright 2008-2023 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
+# (GPLv2) WITHOUT ANY WARRANTY.
+
+#
+# The Dummy agent is intended primarily for testing, and has various options to
+# make actions intentionally fail or take a long time. It may also be used as a
+# template for resource agent writers, in which case:
+#
+# - Replace all occurrences of "dummy" and "Dummy" with your agent name.
+# - Update the meta-data appropriately for your agent, such as the description
+# and supported options. Pay particular attention to the timeouts specified in
+# the actions section; they should be meaningful for the kind of service the
+# agent manages. They should be the minimum advised timeouts, but shouldn't
+# try to cover _all_ possible instances. So, try to be neither overly generous
+# nor too stingy, but moderate. The minimum timeouts should never be below 10
+# seconds.
+# - Don't copy the stuff here that is just for testing, such as the
+# sigterm_handler() or dump_env().
+# - You don't need the state file stuff here if you have a better way of
+# determining whether your service is running. It's only useful for agents
+# such as health agents that don't actually correspond to a running service.
+# - Implement the actions appropriately for your service. Your monitor action
+# must differentiate correctly between running, not running, and failed (that
+# is THREE states, not just yes/no). The migrate_to, migrate_from, and reload
+# actions are optional and not appropriate to all services.
+#
+
+#######################################################################
+# Initialization:
+
+: ${OCF_FUNCTIONS:="${OCF_ROOT}/resource.d/heartbeat/.ocf-shellfuncs"}
+. "${OCF_FUNCTIONS}"
+: ${__OCF_ACTION:="$1"}
+
+# Explicitly list all environment variables used, to make static analysis happy
+: ${OCF_RESKEY_fake:="dummy"}
+: ${OCF_RESKEY_op_sleep:=0}
+: ${OCF_RESKEY_CRM_meta_interval:=0}
+: ${OCF_RESKEY_CRM_meta_globally_unique:="false"}
+: ${OCF_RESKEY_envfile:=""}
+: ${OCF_RESKEY_fail_start_on:=""}
+: ${OCF_RESKEY_migrate_source:=""}
+: ${OCF_RESKEY_migrate_target:=""}
+: ${OCF_RESKEY_envfile:=""}
+: ${OCF_RESKEY_state:=""}
+
+#######################################################################
+
+meta_data() {
+ cat <<END
+<?xml version="1.0"?>
+<resource-agent name="Dummy" version="@VERSION@">
+<version>1.1</version>
+
+<longdesc lang="en">
+This is a dummy OCF resource agent. It does absolutely nothing except keep track
+of whether it is running or not, and can be configured so that actions fail or
+take a long time. Its purpose is primarily for testing, and to serve as a
+template for resource agent writers.
+</longdesc>
+<shortdesc lang="en">Example stateless resource agent</shortdesc>
+
+<parameters>
+<parameter name="state" unique-group="state">
+<longdesc lang="en">
+Location to store the resource state in.
+</longdesc>
+<shortdesc lang="en">State file</shortdesc>
+<content type="string" default="${HA_VARRUN%%/}/Dummy-${OCF_RESOURCE_INSTANCE}.state" />
+</parameter>
+
+<parameter name="passwd" reloadable="1">
+<longdesc lang="en">
+Fake password field
+</longdesc>
+<shortdesc lang="en">Password</shortdesc>
+<content type="string" default="" />
+</parameter>
+
+<parameter name="fake" reloadable="1">
+<longdesc lang="en">
+Fake attribute that can be changed to cause an agent reload
+</longdesc>
+<shortdesc lang="en">Fake attribute that can be changed to cause an agent reload</shortdesc>
+<content type="string" default="dummy" />
+</parameter>
+
+<parameter name="op_sleep" reloadable="1">
+<longdesc lang="en">
+Number of seconds to sleep during operations. This can be used to test how
+the cluster reacts to operation timeouts.
+</longdesc>
+<shortdesc lang="en">Operation sleep duration in seconds.</shortdesc>
+<content type="string" default="0" />
+</parameter>
+
+<parameter name="fail_start_on" reloadable="1">
+<longdesc lang="en">
+Start, migrate_from, and reload-agent actions will return failure if running on
+the host specified here, but the resource will run successfully anyway (future
+monitor calls will find it running). This can be used to test on-fail=ignore.
+</longdesc>
+<shortdesc lang="en">Report bogus start failure on specified host</shortdesc>
+<content type="string" default="" />
+</parameter>
+
+<parameter name="envfile" reloadable="1">
+<longdesc lang="en">
+If this is set, the environment will be dumped to this file for every call.
+</longdesc>
+<shortdesc lang="en">Environment dump file</shortdesc>
+<content type="string" default="" />
+</parameter>
+
+</parameters>
+
+<actions>
+<action name="start" timeout="20s" />
+<action name="stop" timeout="20s" />
+<action name="monitor" timeout="20s" interval="10s" depth="0"/>
+<action name="reload" timeout="20s" />
+<action name="reload-agent" timeout="20s" />
+<action name="migrate_to" timeout="20s" />
+<action name="migrate_from" timeout="20s" />
+<action name="validate-all" timeout="20s" depth="0" />
+<action name="meta-data" timeout="5s" />
+</actions>
+</resource-agent>
+END
+}
+
+#######################################################################
+
+# don't exit on TERM, to test that pacemaker-execd makes sure that we do exit
+trap sigterm_handler TERM
+sigterm_handler() {
+ ocf_log info "They use TERM to bring us down. No such luck."
+
+ # Since we're likely going to get KILLed, clean up any monitor
+ # serialization in progress, so the next probe doesn't return an error.
+ rm -f "${VERIFY_SERIALIZED_FILE}"
+ return
+}
+
+dummy_usage() {
+ cat <<END
+usage: $0 {start|stop|monitor|reload|reload-agent|migrate_to|migrate_from|validate-all|meta-data}
+
+Expects to have a fully populated OCF RA-compliant environment set.
+END
+}
+
+dump_env() {
+ if [ "${OCF_RESKEY_envfile}" != "" ]; then
+ echo "### ${__OCF_ACTION} @ $(date) ###
+$(env | sort)
+###" >> "${OCF_RESKEY_envfile}"
+ fi
+}
+
+dummy_start() {
+ dummy_monitor
+
+ DS_RETVAL=$?
+ if [ $DS_RETVAL -eq $OCF_SUCCESS ]; then
+ if [ "$(uname -n)" = "${OCF_RESKEY_fail_start_on}" ]; then
+ DS_RETVAL=$OCF_ERR_GENERIC
+ fi
+ return $DS_RETVAL
+ fi
+
+ touch "${OCF_RESKEY_state}"
+ DS_RETVAL=$?
+ if [ "$(uname -n)" = "${OCF_RESKEY_fail_start_on}" ]; then
+ DS_RETVAL=$OCF_ERR_GENERIC
+ fi
+ return $DS_RETVAL
+}
+
+dummy_stop() {
+ dummy_monitor --force
+ if [ $? -eq $OCF_SUCCESS ]; then
+ rm "${OCF_RESKEY_state}"
+ fi
+ rm -f "${VERIFY_SERIALIZED_FILE}"
+ return $OCF_SUCCESS
+}
+
+dummy_monitor() {
+ if [ $OCF_RESKEY_op_sleep -ne 0 ]; then
+ if [ "$1" = "" ] && [ -f "${VERIFY_SERIALIZED_FILE}" ]; then
+ # two monitor ops have occurred at the same time.
+ # This verifies a condition in pacemaker-execd regression tests.
+ ocf_log err "$VERIFY_SERIALIZED_FILE exists already"
+ ocf_exit_reason "alternate universe collision"
+ return $OCF_ERR_GENERIC
+ fi
+
+ touch "${VERIFY_SERIALIZED_FILE}"
+ sleep ${OCF_RESKEY_op_sleep}
+ rm "${VERIFY_SERIALIZED_FILE}"
+ fi
+
+ if [ -f "${OCF_RESKEY_state}" ]; then
+ # Multiple monitor levels are defined to support various tests
+ case "$OCF_CHECK_LEVEL" in
+ 10)
+ # monitor level with delay, useful for testing timeouts
+ sleep 30
+ ;;
+
+ 20)
+ # monitor level that fails intermittently
+ n=$(expr "$(dd if=/dev/urandom bs=1 count=1 2>/dev/null | od | head -1 | cut -f2 -d' ')" % 5)
+ if [ $n -eq 1 ]; then
+ ocf_exit_reason "smoke detected near CPU fan"
+ return $OCF_ERR_GENERIC
+ fi
+ ;;
+
+ 30)
+ # monitor level that always fails
+ ocf_exit_reason "hyperdrive quota reached"
+ return $OCF_ERR_GENERIC
+ ;;
+
+ 40)
+ # monitor level that returns error code from state file
+ rc=$(cat ${OCF_RESKEY_state})
+ [ -n "$rc" ] && ocf_exit_reason "CPU ejected. Observed leaving the Kronosnet galaxy at $rc times the speed of light." && return $rc
+ ;;
+
+ *)
+ ;;
+ esac
+ return $OCF_SUCCESS
+ fi
+ return $OCF_NOT_RUNNING
+}
+
+dummy_validate() {
+ # If specified, is op_sleep an integer?
+ case "$OCF_RESKEY_op_sleep" in
+ ""|*[0-9]*) ;;
+ *) return $OCF_ERR_CONFIGURED ;;
+ esac
+
+ # Host-specific checks
+ if [ "$OCF_CHECK_LEVEL" = "10" ]; then
+
+ # Is the state directory writable?
+ state_dir=$(dirname "$OCF_RESKEY_state")
+ [ -d "$state_dir" ] && [ -w "$state_dir" ] && [ -x "$state_dir" ]
+ if [ $? -ne 0 ]; then
+ return $OCF_ERR_ARGS
+ fi
+
+ # If specified, is the environment file directory writable?
+ if [ -n "$OCF_RESKEY_envfile" ]; then
+ envfile_dir=$(dirname "$OCF_RESKEY_envfile")
+ [ -d "$envfile_dir" ] && [ -w "$envfile_dir" ] && [ -x "$envfile_dir" ]
+ if [ $? -ne 0 ]; then
+ return $OCF_ERR_ARGS
+ fi
+ fi
+
+ fi
+ return $OCF_SUCCESS
+}
+
+if [ -z "$OCF_RESKEY_state" ]; then
+ OCF_RESKEY_state="${HA_VARRUN%%/}/Dummy-${OCF_RESOURCE_INSTANCE}.state"
+
+ if [ "${OCF_RESKEY_CRM_meta_globally_unique}" = "false" ]; then
+ # Strip off the trailing clone marker (note + is not portable in sed)
+ OCF_RESKEY_state=$(echo $OCF_RESKEY_state | sed s/:[0-9][0-9]*\.state/.state/)
+ fi
+fi
+VERIFY_SERIALIZED_FILE="${OCF_RESKEY_state}.serialized"
+
+dump_env
+
+case "$__OCF_ACTION" in
+meta-data) meta_data
+ exit $OCF_SUCCESS
+ ;;
+start) dummy_start;;
+stop) dummy_stop;;
+monitor) dummy_monitor;;
+migrate_to) ocf_log info "Migrating ${OCF_RESOURCE_INSTANCE} to ${OCF_RESKEY_CRM_meta_migrate_target}."
+ dummy_stop
+ ;;
+migrate_from) ocf_log info "Migrating ${OCF_RESOURCE_INSTANCE} from ${OCF_RESKEY_CRM_meta_migrate_source}."
+ dummy_start
+ ;;
+reload) ocf_log debug "Reloading $OCF_RESOURCE_INSTANCE (service)"
+ exit $OCF_SUCCESS
+ ;;
+reload-agent) ocf_log err "Reloading $OCF_RESOURCE_INSTANCE (agent)"
+ dummy_start
+ ;;
+validate-all) dummy_validate;;
+usage|help) dummy_usage
+ exit $OCF_SUCCESS
+ ;;
+*) dummy_usage
+ exit $OCF_ERR_UNIMPLEMENTED
+ ;;
+esac
+rc=$?
+ocf_log debug "${OCF_RESOURCE_INSTANCE} $__OCF_ACTION : $rc"
+exit $rc
+
+# vim: set filetype=sh expandtab tabstop=4 softtabstop=4 shiftwidth=4 textwidth=80:
diff --git a/agents/ocf/HealthCPU.in b/agents/ocf/HealthCPU.in
new file mode 100755
index 0000000..4a8e7c3
--- /dev/null
+++ b/agents/ocf/HealthCPU.in
@@ -0,0 +1,212 @@
+#!/bin/sh
+#
+# ocf:pacemaker:HealthCPU resource agent
+#
+# Copyright 2004-2023 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
+# (GPLv2) WITHOUT ANY WARRANTY.
+#
+
+#
+# Measures CPUs idling and writes #health-cpu status into the CIB
+#
+################################
+#
+# TODO: Enter default values
+# Error handling in getting uptime
+#
+##################################
+
+#######################################################################
+# Initialization:
+
+: ${OCF_FUNCTIONS:="${OCF_ROOT}/resource.d/heartbeat/.ocf-shellfuncs"}
+. "${OCF_FUNCTIONS}"
+: ${__OCF_ACTION:="$1"}
+
+#######################################################################
+
+meta_data() {
+ cat <<END
+<?xml version="1.0"?>
+<resource-agent name="HealthCPU" version="@VERSION@">
+<version>1.1</version>
+
+<longdesc lang="en">
+System health agent that measures the CPU idling and updates the #health-cpu attribute.
+</longdesc>
+<shortdesc lang="en">System health CPU usage</shortdesc>
+
+<parameters>
+<parameter name="state" unique-group="state">
+<longdesc lang="en">
+Location to store the resource state in.
+</longdesc>
+<shortdesc lang="en">State file</shortdesc>
+<content type="string" default="${HA_VARRUN%%/}/health-cpu-${OCF_RESOURCE_INSTANCE}.state" />
+</parameter>
+
+<parameter name="yellow_limit" reloadable="1">
+<longdesc lang="en">
+Lower (!) limit of idle percentage to switch the health attribute to yellow. I.e.
+the #health-cpu will go yellow if the %idle of the CPU falls below 50%.
+</longdesc>
+<shortdesc lang="en">Lower limit for yellow health attribute</shortdesc>
+<content type="string" default="50"/>
+</parameter>
+
+<parameter name="red_limit" reloadable="1">
+<longdesc lang="en">
+Lower (!) limit of idle percentage to switch the health attribute to red. I.e.
+the #health-cpu will go red if the %idle of the CPU falls below 10%.
+</longdesc>
+<shortdesc lang="en">Lower limit for red health attribute</shortdesc>
+<content type="string" default="10"/>
+</parameter>
+
+</parameters>
+
+<actions>
+<action name="start" timeout="10s" />
+<action name="stop" timeout="10s" />
+<action name="monitor" timeout="10s" interval="10s" start-delay="0s" />
+<action name="reload-agent" timeout="20s" />
+<action name="meta-data" timeout="5s" />
+<action name="validate-all" timeout="10s" depth="0" />
+</actions>
+</resource-agent>
+END
+}
+
+#######################################################################
+
+healthcpu_usage() {
+ cat <<END
+usage: $0 {start|stop|monitor|reload-agent|validate-all|meta-data}
+
+Expects to have a fully populated OCF RA-compliant environment set.
+END
+}
+
+healthcpu_start() {
+ healthcpu_monitor
+ if [ $? -eq $OCF_SUCCESS ]; then
+ return $OCF_SUCCESS
+ fi
+ touch "${OCF_RESKEY_state}"
+}
+
+healthcpu_stop() {
+ healthcpu_monitor
+ if [ $? -eq $OCF_SUCCESS ]; then
+ rm "${OCF_RESKEY_state}"
+ fi
+ return $OCF_SUCCESS
+}
+
+healthcpu_monitor() {
+ if [ -f "${OCF_RESKEY_state}" ]; then
+
+ IDLE=$(top -b -n2 | grep Cpu | tail -1 | awk -F",|.[0-9][ %]id" '{ print $4 }')
+ # echo "System idle: " $IDLE
+ # echo "$OCF_RESKEY_red_limit"
+ # echo $OCF_RESKEY_yellow_limit
+
+ if [ $IDLE -lt ${OCF_RESKEY_red_limit} ] ; then
+ # echo "System state RED!"
+ attrd_updater -n "#health-cpu" -U "red" -d "30s"
+ return $OCF_SUCCESS
+ fi
+
+ if [ $IDLE -lt ${OCF_RESKEY_yellow_limit} ] ; then
+ # echo "System state yellow."
+ attrd_updater -n "#health-cpu" -U "yellow" -d "30s"
+ else
+ # echo "System state green."
+ attrd_updater -n "#health-cpu" -U "green" -d "30s"
+ fi
+
+ return $OCF_SUCCESS
+ fi
+
+ return $OCF_NOT_RUNNING
+}
+
+healthcpu_reload_agent() {
+ # No action required
+ :;
+}
+
+healthcpu_validate() {
+ # If specified, is yellow_limit a non-negative integer?
+ case "$OCF_RESKEY_yellow_limit" in
+ 0|[1-9]|[1-9][0-9]|100) ;;
+ *) return $OCF_ERR_CONFIGURED ;;
+ esac
+
+ # If specified, is red_limit a non-negative integer?
+ case "$OCF_RESKEY_red_limit" in
+ 0|[1-9]|[1-9][0-9]|100) ;;
+ *) return $OCF_ERR_CONFIGURED ;;
+ esac
+
+ # Host-specific checks
+ if [ "$OCF_CHECK_LEVEL" = "10" ]; then
+
+ # Is the state directory writable?
+ state_dir=$(dirname "$OCF_RESKEY_state")
+ [ -d "$state_dir" ] && [ -w "$state_dir" ] && [ -x "$state_dir" ]
+ if [ $? -ne 0 ]; then
+ ocf_log err "Invalid location for 'state': $state_dir is not writable"
+ return $OCF_ERR_ARGS
+ fi
+ fi
+
+ return $OCF_SUCCESS
+}
+
+: ${OCF_RESKEY_CRM_meta_interval:=0}
+: ${OCF_RESKEY_CRM_meta_globally_unique:="false"}
+
+if [ -z "$OCF_RESKEY_state" ]; then
+ state="${HA_VARRUN%%/}/HealthCPU-${OCF_RESOURCE_INSTANCE}.state"
+ if [ "${OCF_RESKEY_CRM_meta_globally_unique}" = "false" ]; then
+ # Strip off the trailing clone marker
+ OCF_RESKEY_state=$(echo $state | sed s/:[0-9][0-9]*\.state/.state/)
+ else
+ OCF_RESKEY_state="$state"
+ fi
+fi
+
+if [ -z "${OCF_RESKEY_red_limit}" ] ; then
+ OCF_RESKEY_red_limit=10
+fi
+
+if [ -z "${OCF_RESKEY_yellow_limit}" ] ; then
+ OCF_RESKEY_yellow_limit=50
+fi
+
+case "$__OCF_ACTION" in
+meta-data) meta_data
+ exit $OCF_SUCCESS
+ ;;
+start) healthcpu_start;;
+stop) healthcpu_stop;;
+monitor) healthcpu_monitor;;
+reload-agent) healthcpu_reload_agent;;
+validate-all) healthcpu_validate;;
+usage|help) healthcpu_usage
+ exit $OCF_SUCCESS
+ ;;
+*) healthcpu_usage
+ exit $OCF_ERR_UNIMPLEMENTED
+ ;;
+esac
+rc=$?
+ocf_log debug "${OCF_RESOURCE_INSTANCE} $__OCF_ACTION : $rc"
+exit $rc
+
+# vim: set filetype=sh expandtab tabstop=4 softtabstop=4 shiftwidth=4 textwidth=80:
diff --git a/agents/ocf/HealthIOWait.in b/agents/ocf/HealthIOWait.in
new file mode 100755
index 0000000..ba7a17a
--- /dev/null
+++ b/agents/ocf/HealthIOWait.in
@@ -0,0 +1,197 @@
+#!/bin/sh
+#
+# ocf:pacemaker:HealthIOWait resource agent
+#
+# Copyright 2004-2023 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
+# (GPLv2) WITHOUT ANY WARRANTY.
+#
+
+#
+# Measures CPU iowait % via top and writes #health-iowait status into the CIB
+#
+#######################################################################
+# Initialization:
+
+: ${OCF_FUNCTIONS:="${OCF_ROOT}/resource.d/heartbeat/.ocf-shellfuncs"}
+. "${OCF_FUNCTIONS}"
+: ${__OCF_ACTION:="$1"}
+
+#######################################################################
+
+meta_data() {
+ cat <<END
+<?xml version="1.0"?>
+<resource-agent name="HealthIOWait" version="@VERSION@">
+<version>1.1</version>
+
+<longdesc lang="en">
+System health agent that measures the CPU iowait via top and updates the #health-iowait attribute.
+</longdesc>
+<shortdesc lang="en">System health based on CPU iowait measurement</shortdesc>
+
+<parameters>
+<parameter name="state" unique-group="state">
+<longdesc lang="en">
+Location to store the resource state in.
+</longdesc>
+<shortdesc lang="en">State file</shortdesc>
+<content type="string" default="${HA_VARRUN%%/}/health-iowait-${OCF_RESOURCE_INSTANCE}.state" />
+</parameter>
+
+<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="integer" default="10"/>
+</parameter>
+
+<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="integer" default="15"/>
+</parameter>
+
+</parameters>
+
+<actions>
+<action name="start" timeout="10s" />
+<action name="stop" timeout="10s" />
+<action name="monitor" timeout="10s" interval="10s" start-delay="0s" />
+<action name="meta-data" timeout="5s" />
+<action name="validate-all" timeout="10s" depth="0" />
+</actions>
+</resource-agent>
+END
+}
+
+#######################################################################
+
+agent_usage() {
+ cat <<END
+usage: $0 {start|stop|monitor|validate-all|meta-data}
+
+Expects to have a fully populated OCF RA-compliant environment set.
+END
+}
+
+agent_start() {
+ agent_monitor
+ if [ $? -eq $OCF_SUCCESS ]; then
+ return $OCF_SUCCESS
+ fi
+ touch "${OCF_RESKEY_state}"
+}
+
+agent_stop() {
+ agent_monitor
+ if [ $? -eq $OCF_SUCCESS ]; then
+ rm "${OCF_RESKEY_state}"
+ fi
+ return $OCF_SUCCESS
+}
+
+agent_monitor() {
+ # Monitor _MUST!_ differentiate correctly between running
+ # (SUCCESS), failed (ERROR) or _cleanly_ stopped (NOT RUNNING).
+ # That is THREE states, not just yes/no.
+ if [ -f "${OCF_RESKEY_state}" ]; then
+ WAIT=$(top -b -n2 | grep Cpu | tail -1 | awk -F",|.[0-9][ %]wa" '{ print $5 }')
+ # echo "System iowait: " $WAIT
+ # echo $OCF_RESKEY_yellow_limit
+ if [ $WAIT -gt ${OCF_RESKEY_red_limit} ] ; then
+ # echo "System state RED!"
+ attrd_updater -n "#health-iowait" -U "red" -d "5s"
+ return $OCF_SUCCESS
+ fi
+
+ if [ $WAIT -gt ${OCF_RESKEY_yellow_limit} ] ; then
+ # echo "System state yellow."
+ attrd_updater -n "#health-iowait" -U "yellow" -d "5s"
+ else
+ # echo "System state green."
+ attrd_updater -n "#health-iowait" -U "green" -d "5s"
+ fi
+ return $OCF_SUCCESS
+ fi
+ 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_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
+}
+
+: ${OCF_RESKEY_CRM_meta_interval:=0}
+: ${OCF_RESKEY_CRM_meta_globally_unique:="false"}
+
+if [ -z "$OCF_RESKEY_state" ]; then
+ if [ "${OCF_RESKEY_CRM_meta_globally_unique}" = "false" ]; then
+ state="${HA_VARRUN%%/}/HealthIoWait-${OCF_RESOURCE_INSTANCE}.state"
+ #Strip off the trailing clone marker
+ OCF_RESKEY_state=$(echo $state | sed s/:[0-9][0-9]*\.state/.state/)
+ else
+ OCF_RESKEY_state="${HA_VARRUN%%/}/HealthIoWait-${OCF_RESOURCE_INSTANCE}.state"
+ fi
+fi
+
+if [ -z "${OCF_RESKEY_red_limit}" ] ; then
+ OCF_RESKEY_red_limit=15
+fi
+
+if [ -z "${OCF_RESKEY_yellow_limit}" ] ; then
+ OCF_RESKEY_yellow_limit=10
+fi
+
+case $__OCF_ACTION in
+meta-data) meta_data
+ exit $OCF_SUCCESS
+ ;;
+start) agent_start;;
+stop) agent_stop;;
+monitor) agent_monitor;;
+validate-all) agent_validate;;
+usage|help) agent_usage
+ exit $OCF_SUCCESS
+ ;;
+*) agent_usage
+ exit $OCF_ERR_UNIMPLEMENTED
+ ;;
+esac
+rc=$?
+ocf_log debug "${OCF_RESOURCE_INSTANCE} $__OCF_ACTION : $rc"
+exit $rc
+
+# vim: set filetype=sh expandtab tabstop=4 softtabstop=4 shiftwidth=4 textwidth=80:
diff --git a/agents/ocf/HealthSMART.in b/agents/ocf/HealthSMART.in
new file mode 100755
index 0000000..b6edac2
--- /dev/null
+++ b/agents/ocf/HealthSMART.in
@@ -0,0 +1,372 @@
+#!@BASH_PATH@
+#
+# ocf:pacemaker:HealthSMART resource agent
+#
+# Copyright 2009-2023 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
+# (GPLv2) WITHOUT ANY WARRANTY.
+#
+
+#
+# Checks the S.M.A.R.T. status of all given drives and writes the #health-smart
+# status into the CIB
+#
+#######################################################################
+
+#######################################################################
+# Initialization:
+
+: ${OCF_FUNCTIONS:="${OCF_ROOT}/resource.d/heartbeat/.ocf-shellfuncs"}
+. "${OCF_FUNCTIONS}"
+: ${__OCF_ACTION:="$1"}
+
+# Explicitly list all environment variables used, to make static analysis happy
+: ${OCF_RESKEY_CRM_meta_interval:=0}
+: ${OCF_RESKEY_CRM_meta_globally_unique:="true"}
+: ${OCF_RESKEY_temp_warning:=""}
+: ${OCF_RESKEY_temp_lower_limit:=""}
+: ${OCF_RESKEY_temp_upper_limit:=""}
+: ${OCF_RESKEY_drives:="/dev/sda"}
+: ${OCF_RESKEY_devices:=""}
+: ${OCF_RESKEY_state:=""}
+: ${OCF_RESKEY_smartctl:="/usr/sbin/smartctl"}
+: ${OCF_RESKEY_dampen:="5s"}
+
+# Turn these into arrays so we can iterate them later.
+DRIVES=(${OCF_RESKEY_drives})
+DEVICES=(${OCF_RESKEY_devices})
+
+#######################################################################
+
+meta_data() {
+ cat <<END
+<?xml version="1.0"?>
+<resource-agent name="HealthSMART" version="@VERSION@">
+<version>1.1</version>
+
+<longdesc lang="en">
+System health agent that checks the S.M.A.R.T. status of the given drives and
+updates the #health-smart attribute.
+</longdesc>
+<shortdesc lang="en">SMART health status</shortdesc>
+
+<parameters>
+<parameter name="state" unique-group="state">
+<longdesc lang="en">
+Location to store the resource state in.
+</longdesc>
+<shortdesc lang="en">State file</shortdesc>
+<content type="string" default="${HA_VARRUN%%/}/HealthSMART-${OCF_RESOURCE_INSTANCE}.state" />
+</parameter>
+
+<parameter name="drives" reloadable="1">
+<longdesc lang="en">
+The drive(s) to check as a SPACE separated list. Enter the full path to the device, e.g. "/dev/sda".
+</longdesc>
+<shortdesc lang="en">Drives to check</shortdesc>
+<content type="string" default="/dev/sda" />
+</parameter>
+
+<parameter name="devices" reloadable="1">
+<longdesc lang="en">
+The device type(s) to assume for the drive(s) being tested as a SPACE separated list.
+</longdesc>
+<shortdesc lang="en">Device types</shortdesc>
+<content type="string" />
+</parameter>
+
+<parameter name="temp_lower_limit" reloadable="1">
+<longdesc lang="en">
+Lower limit of the temperature in deg C of the drive(s). Below this limit the status will be red.
+</longdesc>
+<shortdesc lang="en">Lower limit for the red smart attribute</shortdesc>
+<content type="string" default="0"/>
+</parameter>
+
+<parameter name="temp_upper_limit" reloadable="1">
+<longdesc lang="en">
+Upper limit of the temperature if deg C of the drives(s). If the drive reports
+a temperature higher than this value the status of #health-smart will be red.
+</longdesc>
+<shortdesc lang="en">Upper limit for red smart attribute</shortdesc>
+<content type="string" default="60"/>
+</parameter>
+
+<parameter name="temp_warning" reloadable="1">
+<longdesc lang="en">
+Number of deg C below/above the upper/lower temp limits at which point the status of #health-smart will change to yellow.
+</longdesc>
+<shortdesc lang="en">Deg C below/above the upper limits for yellow smart attribute</shortdesc>
+<content type="string" default="5"/>
+</parameter>
+
+<parameter name="smartctl" reloadable="1">
+<longdesc lang="en">
+The path to the smartctl program, used for querying device health.
+</longdesc>
+<shortdesc lang="en">The path to the smartctl program</shortdesc>
+<content type="string" default="/usr/sbin/smartctl"/>
+</parameter>
+
+<parameter name="dampen" reloadable="1">
+<longdesc lang="en">
+The time to wait (dampening) for further changes to occur
+</longdesc>
+<shortdesc lang="en">Dampening interval</shortdesc>
+<content type="integer" default="5s"/>
+</parameter>
+
+</parameters>
+
+<actions>
+<action name="start" timeout="10s" />
+<action name="stop" timeout="10s" />
+<action name="monitor" timeout="10s" interval="10s" start-delay="0s" />
+<action name="meta-data" timeout="5s" />
+<action name="validate-all" timeout="10s" depth="0" />
+<action name="reload-agent" timeout="20s" />
+</actions>
+</resource-agent>
+END
+}
+
+#######################################################################
+
+check_temperature() {
+
+ if [ $1 -lt ${lower_red_limit} ] ; then
+ ocf_log info "Drive ${DRIVE} ${DEVICE} too cold: ${1} C"
+ attrd_updater -n "#health-smart" -U "red" -d "${OCF_RESKEY_dampen}"
+ return 1
+ fi
+
+ if [ $1 -gt ${upper_red_limit} ] ; then
+ ocf_log info "Drive ${DRIVE} ${DEVICE} too hot: ${1} C"
+ attrd_updater -n "#health-smart" -U "red" -d "${OCF_RESKEY_dampen}"
+ return 1
+ fi
+
+ if [ $1 -lt ${lower_yellow_limit} ] ; then
+ ocf_log info "Drive ${DRIVE} ${DEVICE} quite cold: ${1} C"
+ attrd_updater -n "#health-smart" -U "yellow" -d "${OCF_RESKEY_dampen}"
+ return 1
+ fi
+
+ if [ $1 -gt ${upper_yellow_limit} ] ; then
+ ocf_log info "Drive ${DRIVE} ${DEVICE} quite hot: ${1} C"
+ attrd_updater -n "#health-smart" -U "yellow" -d "${OCF_RESKEY_dampen}"
+ return 1
+ fi
+}
+
+common_checks() {
+ # Each item in $OCF_RESKEY_drives must have a corresponding item in
+ # $OCF_RESKEY_devices with the device type. Alternately,
+ # $OCF_RESKEY_devices can be empty.
+ drives_len=${#DRIVES[@]}
+ devices_len=${#DEVICES[@]}
+
+ if [ "${drives_len}" -ne "${devices_len}" ] && [ "${devices_len}" -gt 0 ]; then
+ ocf_log err "OCF_RESKEY_devices must be empty or the same length as OCF_RESKEY_drives."
+ exit $OCF_ERR_ARGS
+ fi
+
+ # Each item in $OCF_RESKEY_drives must look like a device node.
+ for d in "${DRIVES[@]}"; do
+ if [[ "$d" != /dev/* ]]; then
+ ocf_log err "Device in OCF_RESKEY_devices does not look like a device node: $d"
+ exit $OCF_ERR_ARGS
+ fi
+ done
+}
+
+
+init_smart() {
+ #Set temperature defaults
+ if [ -z "${OCF_RESKEY_temp_warning}" ]; then
+ yellow_threshold=5
+ else
+ yellow_threshold=${OCF_RESKEY_temp_warning}
+ fi
+
+ if [ -z "${OCF_RESKEY_temp_lower_limit}" ] ; then
+ lower_red_limit=0
+ else
+ lower_red_limit=${OCF_RESKEY_temp_lower_limit}
+ fi
+ lower_yellow_limit=$((${lower_red_limit}+${yellow_threshold}))
+
+ if [ -z "${OCF_RESKEY_temp_upper_limit}" ] ; then
+ upper_red_limit=60
+ else
+ upper_red_limit=${OCF_RESKEY_temp_upper_limit}
+ fi
+ upper_yellow_limit=$((${upper_red_limit}-${yellow_threshold}))
+
+ for ndx in ${!DRIVES[*]}; do
+ DRIVE=${DRIVES[$ndx]}
+
+ if [ -n "${OCF_RESKEY_devices}" ]; then
+ DEVICE=${DEVICES[$ndx]}
+
+ "${OCF_RESKEY_smartctl}" -d "${DEVICE}" -i "${DRIVE}" | grep -q "SMART support is: Enabled"
+ if [ $? -ne 0 ] ; then
+ ocf_log err "S.M.A.R.T. not enabled for drive "${DRIVE}
+ exit $OCF_ERR_INSTALLED
+ fi
+ else
+ "${OCF_RESKEY_smartctl}" -i "${DRIVE}" | grep -q "SMART support is: Enabled"
+ if [ $? -ne 0 ] ; then
+ ocf_log err "S.M.A.R.T. not enabled for drive "${DRIVE}
+ exit $OCF_ERR_INSTALLED
+ fi
+ fi
+ done
+}
+
+HealthSMART_usage() {
+ cat <<END
+usage: $0 {start|stop|monitor|validate-all|meta-data|reload-agent}
+
+Expects to have a fully populated OCF RA-compliant environment set.
+END
+}
+
+HealthSMART_start() {
+ HealthSMART_monitor
+ if [ $? -eq $OCF_SUCCESS ]; then
+ return $OCF_SUCCESS
+ fi
+ touch "${OCF_RESKEY_state}"
+}
+
+HealthSMART_stop() {
+ attrd_updater -D -n "#health-smart" -d "${OCF_RESKEY_dampen}"
+
+ rm "${OCF_RESKEY_state}"
+
+ if [ $? -eq 0 ]; then
+ return $OCF_SUCCESS
+ else
+ return $OCF_ERR_GENERIC
+ fi
+}
+
+HealthSMART_monitor() {
+ common_checks
+
+ # Test for presence of smartctl
+ check_binary smartctl
+
+ init_smart
+
+ # Monitor _MUST!_ differentiate correctly between running
+ # (SUCCESS), failed (ERROR) or _cleanly_ stopped (NOT RUNNING).
+ # That is THREE states, not just yes/no.
+
+ if [ -f "${OCF_RESKEY_state}" ]; then
+
+ for ndx in ${!DRIVES[*]}; do
+ DRIVE=${DRIVES[$ndx]}
+
+ if [ -n "${OCF_RESKEY_devices}" ]; then
+ DEVICE=${DEVICES[$ndx]}
+
+ # Check overall S.M.A.R.T. status
+ "${OCF_RESKEY_smartctl}" -d "${DEVICE}" -H ${DRIVE} | grep -q "SMART overall-health self-assessment test result: PASSED"
+ if [ $? -ne 0 ]; then
+ attrd_updater -n "#health-smart" -U "red" -d "${OCF_RESKEY_dampen}"
+ return $OCF_SUCCESS
+ fi
+
+ # Check drive temperature(s)
+ check_temperature "$("${OCF_RESKEY_smartctl}" -d "${DEVICE}" -A "${DRIVE}" | awk '/^194/ { print $10 }')"
+ if [ $? -ne 0 ]; then
+ return $OCF_SUCCESS
+ fi
+ else
+ "${OCF_RESKEY_smartctl}" -H "${DRIVE}" | grep -q "SMART overall-health self-assessment test result: PASSED"
+ if [ $? -ne 0 ]; then
+ attrd_updater -n "#health-smart" -U "red" -d "${OCF_RESKEY_dampen}"
+ return $OCF_SUCCESS
+ fi
+
+ check_temperature "$("${OCF_RESKEY_smartctl}" -A "${DRIVE}" | awk '/^194/ { print $10 }')"
+ if [ $? -ne 0 ]; then
+ return $OCF_SUCCESS
+ fi
+ fi
+ done
+
+ attrd_updater -n "#health-smart" -U "green" -d "${OCF_RESKEY_dampen}"
+ return $OCF_SUCCESS
+ fi
+
+ return $OCF_NOT_RUNNING
+
+}
+
+HealthSMART_validate() {
+ common_checks
+
+ # Host-specific checks
+ if [ "$OCF_CHECK_LEVEL" = "10" ]; then
+ # Test for presence of smartctl
+ check_binary smartctl
+
+ init_smart
+
+ # Is the state directory writable?
+ state_dir=$(dirname "$OCF_RESKEY_state")
+ touch "$state_dir/$$"
+ if [ $? -ne 0 ]; then
+ return $OCF_ERR_ARGS
+ fi
+ rm "$state_dir/$$"
+ fi
+
+ return $OCF_SUCCESS
+}
+
+HealthSMART_reload_agent() {
+ return $OCF_SUCCESS
+}
+
+
+if [ -z "$OCF_RESKEY_state" ]; then
+ if [ "${OCF_RESKEY_CRM_meta_globally_unique}" = "false" ]; then
+ state="${HA_VARRUN%%/}/HealthSMART-${OCF_RESOURCE_INSTANCE}.state"
+
+ # Strip off the trailing clone marker
+ OCF_RESKEY_state=$(echo $state | sed s/:[0-9][0-9]*\.state/.state/)
+ else
+ OCF_RESKEY_state="${HA_VARRUN%%/}/HealthSMART-${OCF_RESOURCE_INSTANCE}.state"
+ fi
+fi
+
+case "$__OCF_ACTION" in
+ start) HealthSMART_start;;
+ stop) HealthSMART_stop;;
+ monitor) HealthSMART_monitor;;
+ validate-all) HealthSMART_validate;;
+ reload-agent) HealthSMART_reload_agent;;
+ meta-data)
+ meta_data
+ exit $OCF_SUCCESS
+ ;;
+ usage|help)
+ HealthSMART_usage
+ exit $OCF_SUCCESS
+ ;;
+ *) HealthSMART_usage
+ exit $OCF_ERR_UNIMPLEMENTED
+ ;;
+esac
+rc=$?
+ocf_log debug "${OCF_RESOURCE_INSTANCE} $__OCF_ACTION : $rc"
+exit $rc
+
+# vim: set filetype=sh expandtab tabstop=4 softtabstop=4 shiftwidth=4 textwidth=80:
diff --git a/agents/ocf/Makefile.am b/agents/ocf/Makefile.am
new file mode 100644
index 0000000..823e67e
--- /dev/null
+++ b/agents/ocf/Makefile.am
@@ -0,0 +1,53 @@
+#
+# Copyright 2008-2023 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)/mk/common.mk
+include $(top_srcdir)/mk/man.mk
+
+ocfdir = @OCF_RA_INSTALL_DIR@/pacemaker
+dist_ocf_SCRIPTS = attribute \
+ controld \
+ Dummy \
+ HealthCPU \
+ HealthIOWait \
+ ping \
+ remote \
+ Stateful
+ocf_SCRIPTS = ClusterMon \
+ HealthSMART \
+ ifspeed \
+ o2cb \
+ SysInfo
+
+if BUILD_XML_HELP
+
+man7_MANS = $(ocf_SCRIPTS:%=ocf_pacemaker_%.7) $(dist_ocf_SCRIPTS:%=ocf_pacemaker_%.7)
+DBOOK_OPTS = --stringparam command.prefix ocf_pacemaker_ --stringparam variable.prefix OCF_RESKEY_ --param man.vol 7
+
+ocf_pacemaker_%.xml: %
+ $(AM_V_GEN)OCF_FUNCTIONS=/dev/null OCF_ROOT=$(OCF_ROOT_DIR) $(abs_builddir)/$< meta-data > $@
+
+endif
+
+
+# Pass correct local values to validate target manually
+RNG := /dev/null
+OCF_ROOT := /usr/lib/ocf
+
+.PHONY: validate
+validate: all
+ @cat "$(RNG)" >/dev/null 2>/dev/null || { echo "Must specify valid RNG" && false; }
+ @[ -x "$(OCF_ROOT)/lib/heartbeat" ] || { echo "Must specify valid OCF_ROOT" && false; }
+ @for AGENT in $(dist_ocf_SCRIPTS) $(ocf_SCRIPTS); do \
+ echo -e "\n\n## $$AGENT:"; \
+ OCF_ROOT="$(OCF_ROOT)" "$(builddir)/$$AGENT" meta-data \
+ | xmllint --noout --relaxng $(RNG) - || break; \
+ done
+
+CLEANFILES = $(man7_MANS) $(ocf_SCRIPTS:%=%.xml) $(dist_ocf_SCRIPTS:%=%.xml)
diff --git a/agents/ocf/Stateful.in b/agents/ocf/Stateful.in
new file mode 100755
index 0000000..c7c5d75
--- /dev/null
+++ b/agents/ocf/Stateful.in
@@ -0,0 +1,267 @@
+#!/bin/sh
+#
+# ocf:pacemaker:Stateful resource agent
+#
+# Copyright 2006-2023 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.
+#
+
+#
+# Example of a stateful OCF Resource Agent
+#
+
+#######################################################################
+# Initialization:
+
+: ${OCF_FUNCTIONS:="${OCF_ROOT}/resource.d/heartbeat/.ocf-shellfuncs"}
+. "${OCF_FUNCTIONS}"
+: ${__OCF_ACTION:="$1"}
+
+# Explicitly list all environment variables used, to make static analysis happy
+: ${OCF_RESKEY_CRM_meta_interval:=0}
+: ${OCF_RESKEY_CRM_meta_globally_unique:="false"}
+: ${OCF_RESKEY_notify_delay:=0}
+: ${OCF_RESKEY_envfile:=""}
+: ${OCF_RESKEY_state:=""}
+
+# Until we can assume the new name is available ...
+: ${OCF_RUNNING_PROMOTED:=$OCF_RUNNING_MASTER}
+
+SCORE_UNPROMOTED=5
+SCORE_PROMOTED=10
+
+#######################################################################
+
+meta_data() {
+ cat <<END
+<?xml version="1.0"?>
+<resource-agent name="Stateful" version="@VERSION@">
+<version>1.1</version>
+
+<longdesc lang="en">
+This is an example resource agent that implements Promoted and Unpromoted roles
+</longdesc>
+<shortdesc lang="en">Example stateful resource agent</shortdesc>
+
+<parameters>
+
+<parameter name="state" unique-group="state">
+<longdesc lang="en">
+Location to store the resource state in
+</longdesc>
+<shortdesc lang="en">State file</shortdesc>
+<content type="string" default="${HA_VARRUN%%/}/Stateful-${OCF_RESOURCE_INSTANCE}.state" />
+</parameter>
+
+<parameter name="envfile" reloadable="1">
+<longdesc lang="en">
+If this is set, the environment will be dumped to this file for every call.
+</longdesc>
+<shortdesc lang="en">Environment dump file</shortdesc>
+<content type="string" default="" />
+</parameter>
+
+<parameter name="notify_delay" reloadable="1">
+<longdesc lang="en">
+The notify action will sleep for this many seconds before returning,
+to simulate a long-running notify.
+</longdesc>
+<shortdesc lang="en">Notify delay in seconds</shortdesc>
+<content type="string" default="" />
+</parameter>
+
+</parameters>
+
+<actions>
+<action name="start" timeout="20s" />
+<action name="stop" timeout="20s" />
+<action name="monitor" depth="0" timeout="20s" interval="10s" role="Promoted"/>
+<action name="monitor" depth="0" timeout="20s" interval="11s" role="Unpromoted"/>
+<action name="promote" timeout="10s" />
+<action name="demote" timeout="10s" />
+<action name="notify" timeout="5s" />
+<action name="meta-data" timeout="5s" />
+<action name="reload-agent" timeout="10s" />
+<action name="validate-all" timeout="30s" depth="0" />
+</actions>
+</resource-agent>
+END
+ exit $OCF_SUCCESS
+}
+
+#######################################################################
+
+stateful_usage() {
+ cat <<END
+Usage: $0 <action>
+
+where <action> is one of: meta-data validate-all start stop monitor
+ promote demote notify reload-agent
+
+This conforms to the OCF Resource Agent API version 1.1, and expects
+to have OCF-compliant environment variables provided.
+END
+ exit $1
+}
+
+stateful_update() {
+ echo $1 > "${OCF_RESKEY_state}"
+}
+
+stateful_check_state() {
+ target="$1"
+ if [ -f "${OCF_RESKEY_state}" ]; then
+ state=$(cat "${OCF_RESKEY_state}")
+ if [ "$target" = "$state" ]; then
+ return 0
+ fi
+
+ else
+ if [ -z "$target" ]; then
+ return 0
+ fi
+ fi
+
+ return 1
+}
+
+dump_env() {
+ if [ "${OCF_RESKEY_envfile}" != "" ]; then
+ echo "### ${__OCF_ACTION} @ $(date) ###
+$(env | sort)
+###" >> "${OCF_RESKEY_envfile}"
+ fi
+}
+
+set_promotion_score() {
+ "${HA_SBIN_DIR}/crm_attribute" --promotion -v "$1"
+}
+
+stateful_start() {
+ stateful_check_state Promoted
+ if [ $? -eq 0 ]; then
+ # CRM Error - Should never happen
+ return $OCF_RUNNING_PROMOTED
+ fi
+ stateful_update Unpromoted
+ set_promotion_score $SCORE_UNPROMOTED
+ return 0
+}
+
+stateful_demote() {
+ stateful_check_state
+ if [ $? -eq 0 ]; then
+ # CRM Error - Should never happen
+ return $OCF_NOT_RUNNING
+ fi
+ stateful_update Unpromoted
+ set_promotion_score $SCORE_UNPROMOTED
+ return 0
+}
+
+stateful_promote() {
+ stateful_check_state
+ if [ $? -eq 0 ]; then
+ return $OCF_NOT_RUNNING
+ fi
+ stateful_update Promoted
+ set_promotion_score $SCORE_PROMOTED
+ return 0
+}
+
+stateful_stop() {
+ "${HA_SBIN_DIR}/crm_attribute" --promotion -D
+ stateful_check_state Promoted
+ if [ $? -eq 0 ]; then
+ # CRM Error - Should never happen
+ return $OCF_RUNNING_PROMOTED
+ fi
+ if [ -f "${OCF_RESKEY_state}" ]; then
+ rm "${OCF_RESKEY_state}"
+ fi
+ return 0
+}
+
+stateful_monitor() {
+ # for testing
+ if [ -f "${OCF_RESKEY_state}.rc" ]; then
+ rc=$(cat "${OCF_RESKEY_state}.rc")
+ ocf_exit_reason "$rc GB redirected to /dev/null"
+ exit $rc
+ fi
+
+ stateful_check_state Promoted
+ if [ $? -eq 0 ]; then
+ if [ $OCF_RESKEY_CRM_meta_interval -eq 0 ]; then
+ # Restore the promotion score during probes
+ set_promotion_score $SCORE_PROMOTED
+ fi
+ return $OCF_RUNNING_PROMOTED
+ fi
+
+ stateful_check_state Unpromoted
+ if [ $? -eq 0 ]; then
+ if [ $OCF_RESKEY_CRM_meta_interval -eq 0 ]; then
+ # Restore the promotion score during probes
+ set_promotion_score $SCORE_UNPROMOTED
+ fi
+ return $OCF_SUCCESS
+ fi
+
+ if [ -f "${OCF_RESKEY_state}" ]; then
+ echo "File '${OCF_RESKEY_state}' exists but contains unexpected contents"
+ cat "${OCF_RESKEY_state}"
+ return $OCF_ERR_GENERIC
+ fi
+ return 7
+}
+
+stateful_notify() {
+ if [ "${OCF_RESKEY_notify_delay}" != "0" ]; then
+ sleep "${OCF_RESKEY_notify_delay}"
+ fi
+ return $OCF_SUCCESS
+}
+
+stateful_validate() {
+ exit $OCF_SUCCESS
+}
+
+stateful_reload_agent() {
+ return $OCF_SUCCESS
+}
+
+if [ -z "$OCF_RESKEY_state" ]; then
+ if [ "${OCF_RESKEY_CRM_meta_globally_unique}" = "false" ]; then
+ state="${HA_VARRUN%%/}/Stateful-${OCF_RESOURCE_INSTANCE}.state"
+
+ # Strip off the trailing clone marker
+ OCF_RESKEY_state=$(echo $state | sed s/:[0-9][0-9]*\.state/.state/)
+ else
+ OCF_RESKEY_state="${HA_VARRUN%%/}/Stateful-${OCF_RESOURCE_INSTANCE}.state"
+ fi
+fi
+
+dump_env
+
+case "$__OCF_ACTION" in
+meta-data) meta_data;;
+start) stateful_start;;
+promote) stateful_promote;;
+demote) stateful_demote;;
+notify) stateful_notify ;;
+stop) stateful_stop;;
+monitor) stateful_monitor;;
+validate-all) stateful_validate;;
+reload-agent) stateful_reload_agent;;
+usage|help) stateful_usage $OCF_SUCCESS;;
+*) stateful_usage $OCF_ERR_UNIMPLEMENTED;;
+esac
+
+exit $?
+
+# vim: set filetype=sh expandtab tabstop=4 softtabstop=4 shiftwidth=4 textwidth=80:
diff --git a/agents/ocf/SysInfo.in b/agents/ocf/SysInfo.in
new file mode 100755
index 0000000..5c2c7c7
--- /dev/null
+++ b/agents/ocf/SysInfo.in
@@ -0,0 +1,407 @@
+#!@BASH_PATH@
+#
+# ocf:pacemaker:SysInfo resource agent
+#
+# Original copyright 2004 SUSE LINUX AG, Lars Marowsky-Br<E9>e
+# Later changes copyright 2008-2023 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
+# (GPLv2) WITHOUT ANY WARRANTY.
+#
+
+#
+# This agent records (in the CIB) various attributes of a node
+#
+#######################################################################
+# Initialization:
+
+: ${OCF_FUNCTIONS:="${OCF_ROOT}/resource.d/heartbeat/.ocf-shellfuncs"}
+. "${OCF_FUNCTIONS}"
+: ${__OCF_ACTION:="$1"}
+
+# Explicitly list all environment variables used, to make static analysis happy
+: ${OCF_RESKEY_CRM_meta_globally_unique:="false"}
+: ${OCF_RESKEY_pidfile:="${HA_VARRUN%%/}/SysInfo-${OCF_RESOURCE_INSTANCE}"}
+: ${OCF_RESKEY_disk_unit:="G"}
+: ${OCF_RESKEY_clone:="0"}
+: ${OCF_RESKEY_disks:=""}
+: ${OCF_RESKEY_delay:=""}
+: ${OCF_RESKEY_min_disk_free:=""}
+
+#######################################################################
+
+meta_data() {
+ cat <<END
+<?xml version="1.0"?>
+<resource-agent name="SysInfo" version="@VERSION@">
+<version>1.1</version>
+
+<longdesc lang="en">
+This is a SysInfo Resource Agent.
+It records (in the CIB) various attributes of a node
+Sample Linux output:
+ arch: i686
+ os: Linux-2.4.26-gentoo-r14
+ free_swap: 1999
+ cpu_info: Intel(R) Celeron(R) CPU 2.40GHz
+ cpu_speed: 4771.02
+ cpu_cores: 1
+ cpu_load: 0.00
+ ram_total: 513
+ ram_free: 117
+ root_free: 2.4
+ #health_disk: red
+
+Sample Darwin output:
+ arch: i386
+ os: Darwin-8.6.2
+ cpu_info: Intel Core Duo
+ cpu_speed: 2.16
+ cpu_cores: 2
+ cpu_load: 0.18
+ ram_total: 2016
+ ram_free: 787
+ root_free: 13
+ #health_disk: green
+
+Units:
+ free_swap: MB
+ ram_*: MB
+ cpu_speed (Linux): bogomips
+ cpu_speed (Darwin): GHz
+ *_free: GB (or user-defined: disk_unit)
+
+</longdesc>
+<shortdesc lang="en">SysInfo resource agent</shortdesc>
+
+<parameters>
+
+<parameter name="pidfile" unique-group="pidfile">
+<longdesc lang="en">PID file</longdesc>
+<shortdesc lang="en">PID file</shortdesc>
+<content type="string" default="$OCF_RESKEY_pidfile" />
+</parameter>
+
+<parameter name="delay">
+<longdesc lang="en">Interval to allow values to stabilize</longdesc>
+<shortdesc lang="en">Dampening Delay</shortdesc>
+<content type="string" default="0s" />
+</parameter>
+
+<parameter name="disks" reloadable="1">
+<longdesc lang="en">
+Filesystems or Paths to be queried for free disk space as a SPACE
+separated list - e.g "/dev/sda1 /tmp".
+Results will be written to an attribute with leading slashes
+removed, and other slashes replaced with underscore, and the word
+'free' appended - e.g for /dev/sda1 it would be 'dev_sda1_free'.
+Note: The root filesystem '/' is always queried to an attribute
+named 'root_free'
+</longdesc>
+<shortdesc lang="en">List of Filesytems/Paths to query for free disk space</shortdesc>
+<content type="string" />
+</parameter>
+
+<parameter name="disk_unit" reloadable="1">
+<longdesc lang="en">
+Unit to report disk free space in.
+Can be one of: B, K, M, G, T, P (case-insensitive)
+</longdesc>
+<shortdesc lang="en">Unit to report disk free space in</shortdesc>
+<content type="select" default="G">
+ <option value="B" />
+ <option value="b" />
+ <option value="K" />
+ <option value="k" />
+ <option value="M" />
+ <option value="m" />
+ <option value="G" />
+ <option value="g" />
+ <option value="T" />
+ <option value="t" />
+ <option value="P" />
+ <option value="p" />
+</content>
+</parameter>
+
+<parameter name="min_disk_free" reloadable="1">
+<longdesc lang="en">
+The amount of free space required in monitored disks. If any
+of the monitored disks has less than this amount of free space,
+, with the node attribute "#health_disk" changing to "red",
+all resources will move away from the node. Set the node-health-strategy
+property appropriately for this to take effect.
+If the unit is not specified, it defaults to disk_unit.
+</longdesc>
+<shortdesc lang="en">minimum disk free space required</shortdesc>
+<content type="string" default=""/>
+</parameter>
+
+
+</parameters>
+<actions>
+<action name="start" timeout="20s" />
+<action name="stop" timeout="20s" />
+<action name="monitor" timeout="20s" interval="60s"/>
+<action name="reload-agent" timeout="20s" />
+<action name="meta-data" timeout="5s" />
+<action name="validate-all" timeout="30s" depth="0" />
+</actions>
+</resource-agent>
+END
+}
+
+#######################################################################
+
+UpdateStat() {
+ name="$1"; shift
+ value="$*"
+ printf "%s:\t%s\n" "$name" "$value"
+ if [ "$__OCF_ACTION" = "start" ] ; then
+ "${HA_SBIN_DIR}/attrd_updater" ${OCF_RESKEY_delay} -S status -n $name -B "$value"
+ else
+ "${HA_SBIN_DIR}/attrd_updater" ${OCF_RESKEY_delay} -S status -n $name -v "$value"
+ fi
+}
+
+SysInfoStats() {
+ local DISK_STATUS="green"
+
+ UpdateStat arch "$(uname -m)"
+ UpdateStat os "$(uname -s)-$(uname -r)"
+
+ case $(uname -s) in
+ "Darwin")
+ mem=$(top -l 1 | grep Mem: | awk '{print $10}')
+ mem_used=$(top -l 1 | grep Mem: | awk '{print $8}')
+ mem=$(SysInfo_mem_units "$mem")
+ mem_used=$(SysInfo_mem_units "$mem_used")
+ mem_total=$(expr $mem_used + $mem)
+ cpu_type=$(system_profiler SPHardwareDataType | awk -F': ' '/^CPU Type/ {print $2; exit}')
+ cpu_speed=$(system_profiler SPHardwareDataType | awk -F': ' '/^CPU Speed/ {print $2; exit}')
+ cpu_cores=$(system_profiler SPHardwareDataType | awk -F': ' '/^Number Of/ {print $2; exit}')
+ cpu_load=$(uptime | awk -F 'load average: ' '{ print $2 }' | awk -F ', ' '{ print $2 }')
+ ;;
+ "FreeBSD")
+ cpu_type=$(sysctl -in hw.model)
+ cpu_speed=$(sysctl -in dev.cpu.0.freq)
+ cpu_cores=$(sysctl -in hw.ncpu)
+ cpu_load=$(sysctl -in vm.loadavg | awk '{ print $4 }')
+
+ free_pages=$(sysctl -in vm.stats.vm.v_free_count)
+ page_count=$(sysctl -in vm.stats.vm.v_page_count)
+ page_size=$(sysctl -in vm.stats.vm.v_page_size)
+
+ mem=$(expr $free_pages \* $page_size / 1024 / 1024)M
+ mem_total=$(expr $page_count \* $page_size / 1024 / 1024)M
+ ;;
+ "Linux")
+ if [ -f /proc/cpuinfo ]; then
+ cpu_type=$(awk -F': ' '/model name/ {print $2; exit}' /proc/cpuinfo)
+ cpu_speed=$(awk -F': ' '/bogomips/ {print $2; exit}' /proc/cpuinfo)
+ cpu_cores=$(grep "^processor" /proc/cpuinfo | wc -l)
+ fi
+ cpu_load=$(uptime | awk -F 'load average: ' '{ print $2 }' | awk -F ', ' '{ print $2 }')
+
+ if [ -f /proc/meminfo ]; then
+ # meminfo results are in kB
+ mem=$(grep "SwapFree" /proc/meminfo | awk '{print $2"k"}')
+ if [ -n "$mem" ]; then
+ UpdateStat free_swap "$(SysInfo_mem_units "$mem")"
+ fi
+ mem=$(grep "Inactive" /proc/meminfo | awk '{print $2"k"}')
+ mem_total=$(grep "MemTotal" /proc/meminfo | awk '{print $2"k"}')
+ else
+ mem=$(top -n 1 | grep Mem: | awk '{print $7}')
+ fi
+ ;;
+ *)
+ esac
+
+ if [ -n "$cpu_type" ]; then
+ UpdateStat cpu_info "$cpu_type"
+ fi
+
+ if [ -n "$cpu_speed" ]; then
+ UpdateStat cpu_speed "$cpu_speed"
+ fi
+
+ if [ -n "$cpu_cores" ]; then
+ UpdateStat cpu_cores "$cpu_cores"
+ fi
+
+ if [ -n "$cpu_load" ]; then
+ UpdateStat cpu_load "$cpu_load"
+ fi
+
+ if [ -n "$mem" ]; then
+ # Massage the memory values
+ UpdateStat ram_total "$(SysInfo_mem_units "$mem_total")"
+ UpdateStat ram_free "$(SysInfo_mem_units "$mem")"
+ fi
+
+ # Portability notes:
+ # o tail: explicit "-n" not available in Solaris; instead simplify
+ # 'tail -n <c>' to the equivalent 'tail -<c>'.
+ for disk in "/" ${OCF_RESKEY_disks}; do
+ unset disk_free disk_label
+ disk_free=$(df -h "${disk}" | tail -1 | awk '{print $4}')
+ if [ -n "$disk_free" ]; then
+ disk_label=$(echo $disk | sed -e 's#^/$#root#;s#^/*##;s#/#_#g')
+ disk_free=$(SysInfo_hdd_units "$disk_free")
+ UpdateStat "${disk_label}_free" $disk_free
+ if [ -n "$MIN_FREE" ] && [ $disk_free -le $MIN_FREE ]; then
+ DISK_STATUS="red"
+ fi
+ fi
+ done
+ UpdateStat "#health_disk" "$DISK_STATUS"
+}
+
+SysInfo_megabytes() {
+ # Size in megabytes
+ echo $1 | awk '{ n = $0;
+ sub( /[0-9]+(.[0-9]+)?/, "" );
+ if ( $0 == "" ) { $0 = "G" }; # Do not change previous behavior `if ($0 == "G" || $0 == "") { n *= 1024 };`
+ split( n, a, $0 );
+ n = a[1];
+ if ( /^[pP]i?[bB]?/ ) { n *= 1024 * 1024 * 1024 };
+ if ( /^[tT]i?[bB]?/ ) { n *= 1024 * 1024 };
+ if ( /^[gG]i?[bB]?/ ) { n *= 1024 };
+ if ( /^[mM]i?[bB]?/ ) { n *= 1 };
+ if ( /^[kK]i?[bB]?/ ) { n /= 1024 };
+ if ( /^[bB]i?/ ) { n /= 1024 * 1024 };
+ printf "%d\n", n }' # Intentionally round to an integer
+}
+
+SysInfo_mem_units() {
+ mem="$1"
+
+ if [ -z "$1" ]; then
+ return
+ fi
+
+ mem=$(SysInfo_megabytes "$1")
+ # Round to the next multiple of 50
+ r=$(($mem % 50))
+ if [ $r -ne 0 ]; then
+ mem=$(($mem + 50 - $r))
+ fi
+
+ echo $mem
+}
+
+SysInfo_hdd_units() {
+ # Defauts to size in gigabytes
+
+ case "$OCF_RESKEY_disk_unit" in
+ [Pp]) echo $(($(SysInfo_megabytes "$1") / 1024 / 1024 / 1024));;
+ [Tt]) echo $(($(SysInfo_megabytes "$1") / 1024 / 1024));;
+ [Gg]) echo $(($(SysInfo_megabytes "$1") / 1024));;
+ [Mm]) echo "$(SysInfo_megabytes "$1")" ;;
+ [Kk]) echo $(($(SysInfo_megabytes "$1") * 1024));;
+ [Bb]) echo $(($(SysInfo_megabytes "$1") * 1024 * 1024));;
+ *)
+ ocf_log err "Invalid value for disk_unit: $OCF_RESKEY_disk_unit"
+ echo $(($(SysInfo_megabytes "$1") / 1024));;
+ esac
+}
+
+SysInfo_usage() {
+ cat <<END
+usage: $0 {start|stop|monitor|reload-agent|validate-all|meta-data}
+
+Expects to have a fully populated OCF RA-compliant environment set.
+END
+}
+
+SysInfo_start() {
+ echo $OCF_RESKEY_clone > "$OCF_RESKEY_pidfile"
+ SysInfoStats
+ exit $OCF_SUCCESS
+}
+
+SysInfo_stop() {
+ rm "$OCF_RESKEY_pidfile"
+ exit $OCF_SUCCESS
+}
+
+SysInfo_monitor() {
+ if [ -f "$OCF_RESKEY_pidfile" ]; then
+ clone=$(cat "$OCF_RESKEY_pidfile")
+ fi
+
+ if [ -z "$clone" ]; then
+ rm "$OCF_RESKEY_pidfile"
+ exit $OCF_NOT_RUNNING
+
+ elif [ "$clone" = "$OCF_RESKEY_clone" ]; then
+ SysInfoStats
+ exit $OCF_SUCCESS
+
+ elif ocf_is_true "$OCF_RESKEY_CRM_meta_globally_unique"; then
+ SysInfoStats
+ exit $OCF_SUCCESS
+ fi
+ exit $OCF_NOT_RUNNING
+}
+
+SysInfo_reload_agent() {
+ # No action required
+ :;
+}
+
+SysInfo_validate() {
+ # If specified, is min_disk_free a non-negative integer followed by a disk unit?
+
+ pat="[1-9][0-9]*[KkBbMmGgTtPp]?"
+ if [[ ! $OCF_RESKEY_min_disk_free =~ $pat ]]; then
+ return $OCF_ERR_CONFIGURED
+ fi
+
+}
+
+if [ $# -ne 1 ]; then
+ SysInfo_usage
+ exit $OCF_ERR_ARGS
+fi
+
+if [ -n "${OCF_RESKEY_delay}" ]; then
+ OCF_RESKEY_delay="-d ${OCF_RESKEY_delay}"
+else
+ OCF_RESKEY_delay="-d 0"
+fi
+MIN_FREE=""
+if [ -n "$OCF_RESKEY_min_disk_free" ]; then
+ ocf_is_decimal "$OCF_RESKEY_min_disk_free" &&
+ OCF_RESKEY_min_disk_free="$OCF_RESKEY_min_disk_free$OCF_RESKEY_disk_unit"
+ MIN_FREE=$(SysInfo_hdd_units $OCF_RESKEY_min_disk_free)
+fi
+
+case "$__OCF_ACTION" in
+meta-data) meta_data
+ exit $OCF_SUCCESS
+ ;;
+start) SysInfo_start
+ ;;
+stop) SysInfo_stop
+ ;;
+monitor) SysInfo_monitor
+ ;;
+reload-agent) SysInfo_reload_agent
+ ;;
+validate-all) SysInfo_validate
+ ;;
+usage|help) SysInfo_usage
+ exit $OCF_SUCCESS
+ ;;
+*) SysInfo_usage
+ exit $OCF_ERR_UNIMPLEMENTED
+ ;;
+esac
+
+exit $?
+
+# vim: set filetype=sh expandtab tabstop=4 softtabstop=4 shiftwidth=4 textwidth=80:
diff --git a/agents/ocf/attribute.in b/agents/ocf/attribute.in
new file mode 100755
index 0000000..ade3a1c
--- /dev/null
+++ b/agents/ocf/attribute.in
@@ -0,0 +1,240 @@
+#!/bin/sh
+#
+# ocf:pacemaker:attribute resource agent
+#
+# Copyright 2016-2023 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.
+#
+
+USAGE="Usage: $0 {start|stop|monitor|migrate_to|migrate_from|validate-all|meta-data}
+
+Expects to have a fully populated OCF RA-compliant environment set."
+
+# Load OCF helper functions
+: ${OCF_FUNCTIONS:="${OCF_ROOT}/resource.d/heartbeat/.ocf-shellfuncs"}
+. "${OCF_FUNCTIONS}"
+: ${__OCF_ACTION:="$1"}
+
+# Ensure certain variables are set and not empty
+: ${HA_VARRUN:="/var/run"}
+: ${OCF_RESKEY_CRM_meta_globally_unique:="false"}
+: ${OCF_RESOURCE_INSTANCE:="undef"}
+
+DEFAULT_STATE_FILE="${HA_VARRUN%%/}/opa-${OCF_RESOURCE_INSTANCE}.state"
+if [ "${OCF_RESKEY_CRM_meta_globally_unique}" = "false" ]; then
+ # Strip off any trailing clone marker (note + is not portable in sed)
+ DEFAULT_STATE_FILE=$(echo "$DEFAULT_STATE_FILE" | sed s/:[0-9][0-9]*\.state/.state/)
+fi
+
+DEFAULT_ATTR_NAME="opa-${OCF_RESOURCE_INSTANCE}"
+DEFAULT_ACTIVE_VALUE="1"
+DEFAULT_INACTIVE_VALUE="0"
+
+: ${OCF_RESKEY_state:="$DEFAULT_STATE_FILE"}
+: ${OCF_RESKEY_name:="$DEFAULT_ATTR_NAME"}
+
+# If the user did not set a value, use the default. If the user explicitly set
+# a value to the empty string, use that (-z "${V+x}" tests whether $V was set).
+if [ -z "${OCF_RESKEY_active_value+x}" ]; then
+ OCF_RESKEY_active_value="$DEFAULT_ACTIVE_VALUE"
+fi
+if [ -z "${OCF_RESKEY_inactive_value+x}" ]; then
+ OCF_RESKEY_inactive_value="$DEFAULT_INACTIVE_VALUE"
+fi
+
+usage() {
+ USAGE_RC=$1
+ cat <<END
+$USAGE
+END
+ return $USAGE_RC
+}
+
+meta_data() {
+ cat <<END
+<?xml version="1.0"?>
+<resource-agent name="attribute" version="@VERSION@">
+ <version>1.1</version>
+ <longdesc lang="en">
+This resource agent controls a node attribute for the node it's running on.
+It sets the attribute one way when started, and another way when stopped,
+according to the configuration parameters.
+ </longdesc>
+ <shortdesc lang="en">Manages a node attribute</shortdesc>
+ <parameters>
+
+ <parameter name="state" unique-group="state">
+ <longdesc lang="en">
+Full path of a temporary file to store the resource state in
+ </longdesc>
+ <shortdesc lang="en">State file</shortdesc>
+ <content type="string" default="${DEFAULT_STATE_FILE}" />
+ </parameter>
+
+ <parameter name="name" unique-group="name">
+ <longdesc lang="en">
+Name of node attribute to manage
+ </longdesc>
+ <shortdesc lang="en">Attribute name</shortdesc>
+ <content type="string" default="${DEFAULT_ATTR_NAME}" />
+ </parameter>
+
+ <parameter name="active_value">
+ <longdesc lang="en">
+Value to use for node attribute when resource becomes active (empty string is
+discouraged, because monitor cannot distinguish it from a query error)
+ </longdesc>
+ <shortdesc lang="en">Attribute value when active</shortdesc>
+ <content type="string" default="$DEFAULT_ACTIVE_VALUE" />
+ </parameter>
+
+ <parameter name="inactive_value">
+ <longdesc lang="en">
+Value to use for node attribute when resource becomes inactive
+ </longdesc>
+ <shortdesc lang="en">Attribute value when inactive</shortdesc>
+ <content type="string" default="$DEFAULT_INACTIVE_VALUE" />
+ </parameter>
+
+ </parameters>
+ <actions>
+ <action name="start" timeout="20s" />
+ <action name="stop" timeout="20s" />
+ <action name="monitor" timeout="20s" interval="10s" depth="0"/>
+ <action name="reload" timeout="20s" />
+ <action name="migrate_to" timeout="20s" />
+ <action name="migrate_from" timeout="20s" />
+ <action name="validate-all" timeout="20s" depth="0" />
+ <action name="meta-data" timeout="5s" />
+ </actions>
+</resource-agent>
+END
+ return $OCF_SUCCESS
+}
+
+validate() {
+ # Host-specific checks
+ if [ "$OCF_CHECK_LEVEL" = "10" ]; then
+ VALIDATE_DIR=$(dirname "${OCF_RESKEY_state}")
+
+ if [ ! -d "$VALIDATE_DIR" ]; then
+ ocf_exit_reason "state file '$OCF_RESKEY_state' does not have a valid directory"
+ return $OCF_ERR_PERM
+ fi
+
+ if [ ! -w "$VALIDATE_DIR" ] || [ ! -x "$VALIDATE_DIR" ]; then
+ ocf_exit_reason "insufficient privileges on directory of state file '$OCF_RESKEY_state'"
+ return $OCF_ERR_PERM
+ fi
+ fi
+
+ if [ "$OCF_RESKEY_active_value" = "$OCF_RESKEY_inactive_value" ]; then
+ ocf_exit_reason "active value '%s' must be different from inactive value '%s'" \
+ "$OCF_RESKEY_active_value" "$OCF_RESKEY_inactive_value"
+ return $OCF_ERR_CONFIGURED
+ fi
+
+ return $OCF_SUCCESS
+}
+
+get_attribute() {
+ GET_LINE=$(attrd_updater -n "$OCF_RESKEY_name" -Q 2>/dev/null)
+ if [ $? -ne 0 ]; then
+ echo ""
+ else
+ echo "$GET_LINE" | sed -e "s/.* value=\"\(.*\)\"$/\1/"
+ fi
+}
+
+set_attribute() {
+ attrd_updater -n "$OCF_RESKEY_name" -U "$1" 2>/dev/null
+ # TODO if above call is async, loop until get_attribute returns expected value
+}
+
+check_attribute() {
+ CHECK_VALUE=$(get_attribute)
+ CHECK_REASON=""
+ if [ ! -f "$OCF_RESKEY_state" ]; then
+ if [ "$CHECK_VALUE" != "" ] && [ "$CHECK_VALUE" != "$OCF_RESKEY_inactive_value" ]; then
+ CHECK_REASON="Node attribute $OCF_RESKEY_name='$CHECK_VALUE' differs from expected value '$OCF_RESKEY_inactive_value'"
+ return $OCF_ERR_GENERIC
+ fi
+ return $OCF_NOT_RUNNING
+ fi
+ if [ "$CHECK_VALUE" != "$OCF_RESKEY_active_value" ]; then
+ CHECK_REASON="Node attribute $OCF_RESKEY_name='$CHECK_VALUE' differs from expected value '$OCF_RESKEY_active_value'"
+ return $OCF_ERR_GENERIC
+ fi
+ return $OCF_SUCCESS
+}
+
+monitor() {
+ check_attribute
+ MONITOR_RC=$?
+ if [ $MONITOR_RC -eq $OCF_ERR_GENERIC ]; then
+ ocf_exit_reason "$CHECK_REASON"
+ fi
+ return $MONITOR_RC
+}
+
+start() {
+ check_attribute
+ if [ $? -eq $OCF_SUCCESS ]; then
+ return $OCF_SUCCESS
+ fi
+
+ touch "${OCF_RESKEY_state}" 2>/dev/null
+ if [ $? -ne 0 ]; then
+ ocf_exit_reason "Unable to manage state file $OCF_RESKEY_state"
+ return $OCF_ERR_GENERIC
+ fi
+
+ set_attribute "${OCF_RESKEY_active_value}"
+ if [ $? -ne 0 ]; then
+ rm -f "${OCF_RESKEY_state}"
+ ocf_exit_reason "Unable to set node attribute $OCF_RESKEY_name='$OCF_RESKEY_active_value'"
+ return $OCF_ERR_GENERIC
+ fi
+
+ return $OCF_SUCCESS
+}
+
+stop() {
+ check_attribute
+ if [ $? -eq $OCF_NOT_RUNNING ]; then
+ return $OCF_SUCCESS
+ fi
+
+ rm -f ${OCF_RESKEY_state}
+
+ set_attribute "${OCF_RESKEY_inactive_value}"
+ if [ $? -ne 0 ]; then
+ ocf_exit_reason "Unable to set node attribute $OCF_RESKEY_name='$OCF_RESKEY_inactive_value'"
+ return $OCF_ERR_GENERIC
+ fi
+
+ return $OCF_SUCCESS
+}
+
+case $__OCF_ACTION in
+meta-data) meta_data ;;
+start) start ;;
+stop) stop ;;
+monitor) monitor ;;
+# We don't do anything special for live migration, but we support it so that
+# other resources that live migrate can depend on this one.
+migrate_to) stop ;;
+migrate_from) start ;;
+reload) start ;;
+validate-all) validate ;;
+usage|help) usage $OCF_SUCCESS ;;
+*) usage $OCF_ERR_UNIMPLEMENTED ;;
+esac
+
+exit $?
+
+# vim: set filetype=sh expandtab tabstop=4 softtabstop=4 shiftwidth=4 textwidth=80:
diff --git a/agents/ocf/controld.in b/agents/ocf/controld.in
new file mode 100644
index 0000000..ace4933
--- /dev/null
+++ b/agents/ocf/controld.in
@@ -0,0 +1,303 @@
+#!/bin/sh
+#
+# ocf:pacemaker:controld resource agent
+#
+# Copyright 2008-2023 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
+# (GPLv2) WITHOUT ANY WARRANTY.
+
+#
+# Manages the DLM controld process
+#
+
+#######################################################################
+# Initialization:
+
+: ${OCF_FUNCTIONS:="${OCF_ROOT}/resource.d/heartbeat/.ocf-shellfuncs"}
+. "${OCF_FUNCTIONS}"
+: ${__OCF_ACTION:="$1"}
+
+# Explicitly list all environment variables used, to make static analysis happy
+: ${OCF_RESKEY_CRM_meta_globally_unique:="false"}
+: ${OCF_RESKEY_allow_stonith_disabled:="false"}
+: ${OCF_RESKEY_sctp:="false"}
+: ${OCF_RESOURCE_INSTANCE:=""}
+
+case "$OCF_RESOURCE_INSTANCE" in
+ *[gG][fF][sS]*)
+ : ${OCF_RESKEY_args=-g 0}
+ : ${OCF_RESKEY_daemon:=gfs_controld}
+ ;;
+ *[dD][lL][mM]*)
+ : ${OCF_RESKEY_args=-s 0}
+ : ${OCF_RESKEY_daemon:=dlm_controld}
+ ;;
+ *)
+ : ${OCF_RESKEY_args=-s 0}
+ : ${OCF_RESKEY_daemon:=dlm_controld}
+esac
+
+
+#######################################################################
+
+if [ -e "$OCF_ROOT/resource.d/heartbeat/controld" ]; then
+ ocf_log info "Using heartbeat controld agent"
+ "$OCF_ROOT/resource.d/heartbeat/controld" "$1"
+ exit $?
+fi
+
+meta_data() {
+ cat <<END
+<?xml version="1.0"?>
+<resource-agent name="controld" version="@VERSION@">
+<version>1.1</version>
+
+<longdesc lang="en">
+This Resource Agent can control the dlm_controld services needed by cluster-aware file systems.
+It assumes that dlm_controld is in your default PATH.
+In most cases, it should be run as an anonymous clone.
+</longdesc>
+<shortdesc lang="en">DLM Agent for cluster file systems</shortdesc>
+
+<parameters>
+
+<parameter name="args">
+<longdesc lang="en">
+Any additional options to start the dlm_controld service with
+</longdesc>
+<shortdesc lang="en">DLM Options</shortdesc>
+<content type="string" default="-s 0" />
+</parameter>
+
+<parameter name="daemon" unique-group="daemon">
+<longdesc lang="en">
+The daemon to start - supports gfs_controld and dlm_controld
+</longdesc>
+<shortdesc lang="en">The daemon to start</shortdesc>
+<content type="string" default="dlm_controld" />
+</parameter>
+
+<parameter name="allow_stonith_disabled">
+<longdesc lang="en">
+Allow DLM start-up even if STONITH/fencing is disabled in the cluster.
+
+Setting this option to true will cause cluster malfunction and hangs on
+fail-over for DLM clients that require fencing (such as GFS2, OCFS2, and
+cLVM2).
+
+This option is advanced use only.
+</longdesc>
+<shortdesc lang="en">Allow start-up even without STONITH/fencing</shortdesc>
+<content type="string" default="false" />
+</parameter>
+
+</parameters>
+
+<actions>
+<action name="start" timeout="90s" />
+<action name="stop" timeout="100s" />
+<action name="monitor" timeout="20s" interval="10s" depth="0" start-delay="0s" />
+<action name="meta-data" timeout="5s" />
+<action name="validate-all" timeout="30s" depth="0" />
+</actions>
+</resource-agent>
+END
+}
+
+#######################################################################
+
+CONFIGFS_DIR="/sys/kernel/config"
+DLM_CONFIGFS_DIR="${CONFIGFS_DIR}/dlm"
+DLM_SYSFS_DIR="/sys/kernel/dlm"
+
+controld_usage() {
+ cat <<END
+usage: $0 {start|stop|monitor|validate-all|meta-data}
+
+Expects to have a fully populated OCF RA-compliant environment set.
+END
+}
+
+check_uncontrolled_locks()
+{
+ CUL_TMP=$(ls "$DLM_SYSFS_DIR" 2>&1)
+ if [ $? -eq 0 ]; then
+ if [ -n "$CUL_TMP" ]; then
+
+ ocf_log err "Uncontrolled lockspace exists, system must reboot. Executing suicide fencing"
+ stonith_admin --reboot="$(crm_node -n)" --tag controld
+
+ exit $OCF_ERR_GENERIC
+ fi
+ fi
+}
+
+controld_start() {
+ controld_monitor; rc=$?
+
+ case $rc in
+ "$OCF_SUCCESS") return $OCF_SUCCESS;;
+ "$OCF_NOT_RUNNING") ;;
+ *) return $OCF_ERR_GENERIC;;
+ esac
+
+ # Ensure @runstatedir@/cluster exists
+ [ -d "@runstatedir@/cluster" ] || mkdir "@runstatedir@/cluster"
+
+ # Ensure configfs is mounted
+ if [ ! -e "$CONFIGFS_DIR" ]; then
+ modprobe configfs
+ if [ ! -e "$CONFIGFS_DIR" ]; then
+ ocf_log err "$CONFIGFS_DIR not available"
+ return $OCF_ERR_INSTALLED
+ fi
+ fi
+ mount -t configfs | grep " $CONFIGFS_DIR " >/dev/null 2>/dev/null
+ if [ $? -ne 0 ]; then
+ mount -t configfs none "$CONFIGFS_DIR"
+ fi
+
+ # Ensure DLM is available
+ if [ ! -e "$DLM_CONFIGFS_DIR" ]; then
+ modprobe dlm
+ if [ ! -e "$DLM_CONFIGFS_DIR" ]; then
+ ocf_log err "$DLM_CONFIGFS_DIR not available"
+ return $OCF_ERR_INSTALLED
+ fi
+ fi
+
+ if ! ocf_is_true "$OCF_RESKEY_allow_stonith_disabled" && \
+ ! ocf_is_true "$(crm_attribute --type=crm_config --name=stonith-enabled --query --quiet --default=true)"; then
+ ocf_log err "The cluster property stonith-enabled may not be deactivated to use the DLM"
+ return $OCF_ERR_CONFIGURED
+ fi
+
+ # If no-quorum-policy not set, or not set as freeze, give a warning
+ crm_attribute --type=crm_config --name=no-quorum-policy --query|grep value=freeze >/dev/null 2>/dev/null
+ if [ $? -ne 0 ]; then
+ ocf_log warn "The DLM cluster best practice suggests to set the cluster property \"no-quorum-policy=freeze\""
+ fi
+
+ "${OCF_RESKEY_daemon}" $OCF_RESKEY_args
+
+ while true
+ do
+ sleep 1
+
+ controld_monitor; rc=$?
+ case $rc in
+ "$OCF_SUCCESS")
+ CS_ADDR_LIST="$(cat "${DLM_CONFIGFS_DIR}"/cluster/comms/*/addr_list 2>/dev/null)"
+ if [ $? -eq 0 ] && [ -n "$CS_ADDR_LIST" ]; then
+ return $OCF_SUCCESS
+ fi
+ ;;
+ "$OCF_NOT_RUNNING")
+ return $OCF_NOT_RUNNING
+ ;;
+ *)
+ return $OCF_ERR_GENERIC
+ ;;
+ esac
+
+ ocf_log debug "Waiting for ${OCF_RESKEY_daemon} to be ready"
+ done
+}
+
+controld_stop() {
+ controld_monitor; rc=$?
+
+ if [ $rc -eq $OCF_NOT_RUNNING ]; then
+ return $OCF_SUCCESS
+ fi
+
+ killall -TERM "${OCF_RESKEY_daemon}"; rc=$?
+
+ if [ $rc -ne 0 ]; then
+ return $OCF_ERR_GENERIC
+ fi
+
+ rc=$OCF_SUCCESS
+ while [ $rc -eq $OCF_SUCCESS ]; do
+ controld_monitor; rc=$?
+ sleep 1
+ done
+
+ if [ $rc -eq $OCF_NOT_RUNNING ]; then
+ rc=$OCF_SUCCESS
+ fi
+
+ return $rc
+}
+
+controld_monitor() {
+ killall -0 ${OCF_RESKEY_daemon} >/dev/null 2>&1 ; CM_RC=$?
+
+ case $CM_RC in
+ 0) smw=$(dlm_tool status -v | grep "stateful_merge_wait=" | cut -d= -f2)
+ if [ -n "$smw" ] && [ $smw -eq 1 ]; then
+ ocf_log err "DLM status is: stateful_merge_wait"
+ CM_RC=$OCF_ERR_GENERIC
+ elif [ -z "$smw" ] && dlm_tool ls | grep -q "wait fencing" && \
+ ! stonith_admin -H '*' --output-as xml | grep -q "extended-status=\"pending\""; then
+ ocf_log err "DLM status is: wait fencing"
+ CM_RC=$OCF_ERR_GENERIC
+ else
+ CM_RC=$OCF_SUCCESS
+ fi
+ ;;
+ 1) CM_RC=$OCF_NOT_RUNNING;;
+ *) CM_RC=$OCF_ERR_GENERIC;;
+ esac
+
+ # if the dlm is not successfully running, but
+ # dlm lockspace bits are left over, we self must fence.
+ if [ $CM_RC -ne $OCF_SUCCESS ]; then
+ check_uncontrolled_locks
+ fi
+
+ return $CM_RC
+}
+
+controld_validate() {
+ case "${OCF_RESKEY_CRM_meta_globally_unique}" in
+ [Tt][Rr][Uu][Ee] | [Oo][Nn] | [Yy][Ee][Ss] | [Yy] | 1)
+ msg="The globally-unique meta attribute must not be enabled for"
+ msg="$msg $OCF_RESOURCE_INSTANCE"
+ ocf_log err "$msg"
+ exit $OCF_ERR_CONFIGURED
+ ;;
+ esac
+
+ # Host-specific checks
+ if [ "$OCF_CHECK_LEVEL" = "10" ]; then
+ check_binary killall
+ check_binary "${OCF_RESKEY_daemon}"
+ fi
+
+ return $OCF_SUCCESS
+}
+
+case "$__OCF_ACTION" in
+meta-data) meta_data
+ exit $OCF_SUCCESS
+ ;;
+start) controld_validate; controld_start;;
+stop) controld_stop;;
+monitor) controld_validate; controld_monitor;;
+validate-all) controld_validate;;
+usage|help) controld_usage
+ exit $OCF_SUCCESS
+ ;;
+*) controld_usage
+ exit $OCF_ERR_UNIMPLEMENTED
+ ;;
+esac
+rc=$?
+
+exit $rc
+
+# vim: set filetype=sh expandtab tabstop=4 softtabstop=4 shiftwidth=4 textwidth=80:
diff --git a/agents/ocf/ifspeed.in b/agents/ocf/ifspeed.in
new file mode 100755
index 0000000..5fbaf89
--- /dev/null
+++ b/agents/ocf/ifspeed.in
@@ -0,0 +1,553 @@
+#!@BASH_PATH@
+#
+# ocf:pacemaker:ifspeed resource agent
+#
+# Copyright 2011-2023 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.
+#
+
+#
+# Record speed of a network interface as a node attribute, based on the sum of
+# speeds of its active (up, link detected, not blocked) underlying interfaces.
+#
+# Originally based on ocf:pacemaker:ping agent
+#
+
+: ${OCF_FUNCTIONS:="${OCF_ROOT}/resource.d/heartbeat/.ocf-shellfuncs"}
+
+# If these aren't available, we can still show help,
+# which is all that is needed to build the man pages.
+[ -r "${OCF_FUNCTIONS}" ] && . "${OCF_FUNCTIONS}"
+[ -r "${OCF_FUNCTIONS_DIR}/findif.sh" ] && . "${OCF_FUNCTIONS_DIR}/findif.sh"
+: ${OCF_SUCCESS:=0}
+
+: ${__OCF_ACTION:=$1}
+
+FINDIF=findif
+
+# Defaults
+OCF_RESKEY_name_default="ifspeed"
+OCF_RESKEY_bridge_ports_default="detect"
+OCF_RESKEY_weight_base_default=1000
+OCF_RESKEY_dampen_default=5
+
+# Explicitly list all environment variables used, to make static analysis happy
+: ${OCF_RESKEY_name:=${OCF_RESKEY_name_default}}
+: ${OCF_RESKEY_bridge_ports:=${OCF_RESKEY_bridge_ports_default}}
+: ${OCF_RESKEY_weight_base:=${OCF_RESKEY_weight_base_default}}
+: ${OCF_RESKEY_dampen:=${OCF_RESKEY_dampen_default}}
+: ${OCF_RESKEY_iface:=""}
+: ${OCF_RESKEY_ip:=""}
+: ${OCF_RESKEY_debug:="false"}
+
+meta_data() {
+ cat <<END
+<?xml version="1.0"?>
+<resource-agent name="ifspeed" version="@VERSION@">
+<version>1.1</version>
+
+<longdesc lang="en">
+This agent's monitor action records the speed of a specified network interface
+as a node attribute. The attribute can be used in rules to prefer nodes based
+on network speeds.
+
+This agent can monitor physical interfaces, bonded interfaces, bridges, VLANs,
+or any combination thereof. For example:
+
+*) Bridge on top of one 10Gbps interface (eth2) and 802.3ad bonding (bond0) built
+ on two 1Gbps interfaces (eth0 and eth1).
+*) Active-backup bonding built on top of one physical interface and one VLAN on
+ another interface.
+
+For STP-enabled bridges, this agent tries to determine the network topology, and
+by default looks only on ports which are connected to an upstream switch. This
+can be overridden by 'bridge_ports' parameter. Active interfaces in this case
+are those in "forwarding" state.
+
+For balancing bonded interfaces, this agent uses 80% of the sum of the speeds of
+underlying "up" ports.
+
+For non-balancing bonded interfaces ("active-backup" and probably "broadcast"),
+only the speed of the currently active port is considered.
+</longdesc>
+<shortdesc lang="en">Network interface speed monitor</shortdesc>
+
+<parameters>
+
+<parameter name="name" unique-group="name">
+<longdesc lang="en">
+Name of the node attribute to set
+</longdesc>
+<shortdesc lang="en">Attribute name</shortdesc>
+<content type="string" default="${OCF_RESKEY_name_default}"/>
+</parameter>
+
+<parameter name="iface" unique-group="iface">
+<longdesc lang="en">
+If this is set, monitor this network interface. One of iface or ip must be set.
+</longdesc>
+<shortdesc lang="en">Network interface</shortdesc>
+<content type="string" default=""/>
+</parameter>
+
+<parameter name="ip" unique-group="ip">
+<longdesc lang="en">
+If this is set instead of iface, monitor the interface that holds this IP
+address. The address may be specified in dotted-quad notation for IPv4 (for
+example, 192.168.1.1) or hexadecimal notation for IPv6 (for example,
+2001:db8:DC28:0:0:FC57:D4C8:1FFF). One of iface or ip must be set.
+</longdesc>
+<shortdesc lang="en">IPv4 or IPv6 address</shortdesc>
+<content type="string" default="" />
+</parameter>
+
+<parameter name="bridge_ports" unique-group="iface">
+<longdesc lang="en">
+If set and iface is a bridge, consider these bridge ports (by default, all ports
+which have designated_bridge=root_id)
+</longdesc>
+<shortdesc lang="en">Bridge ports</shortdesc>
+<content type="string" default="${OCF_RESKEY_bridge_ports_default}"/>
+</parameter>
+
+<parameter name="weight_base">
+<longdesc lang="en">
+Relative weight of 1Gbps in interface speed.
+Can be used to tune how big attribute value will be.
+</longdesc>
+<shortdesc lang="en">Weight of 1Gbps</shortdesc>
+<content type="integer" default="${OCF_RESKEY_weight_base_default}"/>
+</parameter>
+
+<parameter name="dampen">
+<longdesc lang="en">
+The time to wait (dampening) for further changes to occur.
+</longdesc>
+<shortdesc lang="en">Dampening interval</shortdesc>
+<content type="integer" default="${OCF_RESKEY_dampen_default}"/>
+</parameter>
+
+<parameter name="debug">
+<longdesc lang="en">
+Log more verbosely.
+</longdesc>
+<shortdesc lang="en">Verbose logging</shortdesc>
+<content type="string" default="false"/>
+</parameter>
+
+</parameters>
+
+<actions>
+<action name="start" timeout="30s" />
+<action name="stop" timeout="30s" />
+<action name="monitor" depth="0" timeout="30s" interval="10s"/>
+<action name="meta-data" timeout="5s" />
+<action name="validate-all" timeout="30s" depth="0" />
+</actions>
+</resource-agent>
+END
+}
+
+usage() {
+ cat <<END
+Usage: $0 {start|stop|monitor|validate-all|meta-data}
+
+Expects to have a fully populated OCF RA-compliant environment set.
+END
+}
+
+start() {
+ monitor
+ if [ $? -eq $OCF_SUCCESS ]; then
+ return $OCF_SUCCESS
+ fi
+ ha_pseudo_resource ${ha_pseudo_resource_name} start
+ update
+ return $?
+}
+
+stop() {
+ ha_pseudo_resource "${ha_pseudo_resource_name}" stop
+ attrd_updater -D -n "${OCF_RESKEY_name}" -d "${OCF_RESKEY_dampen}" ${attrd_options}
+ return $OCF_SUCCESS
+}
+
+monitor() {
+ local ret
+
+ ha_pseudo_resource "${ha_pseudo_resource_name}" monitor
+ ret=$?
+ if [ ${ret} -eq $OCF_SUCCESS ] ; then
+ update
+ fi
+ return ${ret}
+}
+
+get_nic_name_by_ip() {
+ # $FINDIF takes its parameters from the environment.
+ # Its output is as follows:
+ # [NIC_NAME] netmask [NETMASK] broadcast [BROADCAST}
+ NICINFO=$( "${FINDIF}" )
+ rc=$?
+ if [ $rc -eq 0 ];then
+ # Get NIC_NAME part of findif function output.
+ echo "${NICINFO%% *}"
+ else
+ echo ""
+ fi
+}
+
+validate() {
+ if [ -z "${OCF_RESKEY_iface}" ]; then
+ if [ -z "${OCF_RESKEY_ip}" ]; then
+ ocf_log err "Must specify either an interface name or valid IP address"
+ ocf_exit_reason "no interface or IP address specified"
+ exit $OCF_ERR_CONFIGURED
+ else
+ ipcheck_ipv4 "${OCF_RESKEY_ip}"
+ if [ $? -eq 1 ] ; then
+ ipcheck_ipv6 "${OCF_RESKEY_ip}"
+ if [ $? -eq 1 ] ; then
+ ocf_exit_reason "'${OCF_RESKEY_ip}' is not a valid IP"
+ exit $OCF_ERR_CONFIGURED
+ fi
+ fi
+ fi
+ fi
+
+ # Host-specific checks
+ if [ "$1" = "10" ]; then
+ if [ "$(uname)" != "Linux" ] ; then
+ ocf_log err "This resource agent works only on Linux"
+ ocf_exit_reason "not Linux"
+ exit $OCF_ERR_INSTALLED
+ fi
+ fi
+ return $OCF_SUCCESS
+}
+
+iface_get_speed() {
+ local iface="$1"
+ local operstate
+ local carrier
+ local speed
+
+ if [ ! -e "/sys/class/net/${iface}" ] ; then
+ echo "0"
+ elif iface_is_bridge "${iface}" ; then # bridges do not have operstate
+ read carrier < "/sys/class/net/${iface}/carrier"
+
+ if [ "${carrier}" != "1" ] ; then
+ echo "0"
+ else
+ bridge_get_speed "${iface}"
+ fi
+ else
+ read operstate < "/sys/class/net/${iface}/operstate"
+ read carrier < "/sys/class/net/${iface}/carrier"
+
+ if [ "${operstate}" != "up" ] || [ "${carrier}" != "1" ] ; then
+ echo "0"
+ elif iface_is_bond "${iface}" ; then
+ bond_get_speed "${iface}"
+ elif iface_is_vlan "${iface}" ; then
+ iface_get_speed "$(vlan_get_phy "${iface}")"
+ elif iface_is_hfi1 "${iface}" ; then
+ hfi1_get_speed "${iface}"
+ else
+ read speed < "/sys/class/net/${iface}/speed"
+ echo "${speed}"
+ fi
+ fi
+}
+
+iface_is_vlan() {
+ local iface="$1"
+
+ [ -e "/proc/net/vlan/${iface}" ] && return 0 || return 1
+}
+
+iface_is_bridge() {
+ local iface="$1"
+
+ [ -e "/sys/class/net/${iface}/bridge" ] && return 0 || return 1
+}
+
+iface_is_bond() {
+ local iface="$1"
+
+ [ -e "/sys/class/net/${iface}/bonding" ] && return 0 || return 1
+}
+
+iface_is_hfi1() {
+ local iface="$1"
+
+ driver=$(readlink "/sys/class/net/${iface}/device/driver")
+ [[ $(basename "${driver}") =~ "hfi1" ]] && return 0 || return 1
+}
+
+vlan_get_phy() {
+ local iface="$1"
+
+ sed -ne "s/^${iface} .*| *//p" < /proc/net/vlan/config
+}
+
+bridge_is_stp_enabled() {
+ local iface="$1"
+ local stp
+
+ read stp < "/sys/class/net/${iface}/bridge/stp_state"
+ [ "${stp}" = "1" ] && return 0 || return 1
+}
+
+bridge_get_root_ports() {
+ local bridge="$1"
+ local root_id
+ local root_ports=""
+ local bridge_id
+
+ read root_id < "/sys/class/net/${bridge}/bridge/root_id"
+
+ for port in /sys/class/net/${bridge}/brif/* ; do
+ read bridge_id < "${port}/designated_bridge"
+ if [ "${bridge_id}" = "${root_id}" ] ; then
+ root_ports="${root_ports} ${port##*/}"
+ fi
+ done
+
+ root_ports=${root_ports# }
+
+ if [ -n "$2" ] ; then # Record value in specified var. This expects we were called not in a sub-shell.
+ eval "$2=\${root_ports}"
+ else # Expect sub-shell
+ echo ${root_ports}
+ fi
+}
+
+# From /usr/include/linux/if_bridge.h:
+#define BR_STATE_DISABLED 0
+#define BR_STATE_LISTENING 1
+#define BR_STATE_LEARNING 2
+#define BR_STATE_FORWARDING 3
+#define BR_STATE_BLOCKING 4
+
+bridge_get_active_ports() {
+ local bridge="$1"
+ shift 1
+ local ports="$*"
+ local active_ports=""
+ local port_state
+ local stp_state
+ local warn=0
+
+ bridge_is_stp_enabled "${bridge}"
+ stp_state=$?
+
+ if [ -z "${ports}" ] || [ "${ports}" = "detect" ] ; then
+ bridge_get_root_ports "${bridge}" ports
+ fi
+
+ for port in $ports ; do
+ if [ ! -e "/sys/class/net/${bridge}/brif/${port}" ] ; then
+ ocf_log warning "Port ${port} doesn't belong to bridge ${bridge}"
+ continue
+ fi
+ read port_state < "/sys/class/net/${bridge}/brif/${port}/state"
+ if [ "${port_state}" = "3" ] ; then
+ if [ -n "${active_ports}" ] && ${stp_state} ; then
+ warn=1
+ fi
+ active_ports="${active_ports} ${port}"
+ fi
+ done
+ if [ ${warn} -eq 1 ] ; then
+ ocf_log warning "More then one upstream port in bridge '${bridge}' is in forwarding state while STP is enabled: ${active_ports}"
+ fi
+ echo "${active_ports# }"
+}
+
+bridge_get_speed() {
+ local iface="$1"
+ local aggregate_speed=0
+
+ if ! iface_is_bridge "${iface}" ; then
+ echo 0
+ return
+ fi
+
+ BGS_PORTS=$( bridge_get_active_ports "${iface}" "${OCF_RESKEY_bridge_ports}" )
+ for port in ${BGS_PORTS} ; do
+ : $(( aggregate_speed += $( iface_get_speed "${port}" ) ))
+ done
+ if [ -n "$2" ] ; then # Record value in specified var. This expects we were called not in a sub-shell.
+ eval "$2=\${aggregate_speed}"
+ else # Expect sub-shell
+ echo ${aggregate_speed}
+ fi
+}
+
+hfi1_get_speed() {
+ local iface="$1"
+ local hfi1_speed
+ local hfi1_value
+ local hfi1_desc
+
+ # At least as of 9/14/2017 Intel Omni Path v10.5.0.0.155, Intel doesn't have
+ # dual- or multiple-port Host Channel Adapters, and it's safe to use this
+ # method to get the speed. Example output:
+ # [root@es-host0 ~]# cat /sys/class/net/ib0/device/infiniband/*/ports/*/rate
+ # 100 Gb/sec (4X EDR)
+ read hfi1_speed hfi1_value hfi1_desc < "/sys/class/net/${iface}/device/infiniband"/*/ports/*/rate
+ ocf_is_true "${OCF_RESKEY_debug}" && ocf_log debug "Detected speed $hfi1_speed $hfi1_value $hfi1_desc"
+
+ # hfi1_value always in Gb/sec, so we need to convert hfi1_speed in Mb/sec
+ echo $(( hfi1_speed * 1000 ))
+}
+
+bond_get_ports() {
+ local iface="$1"
+ local ports
+
+ read ports < "/sys/class/net/${iface}/bonding/slaves"
+ if [ -n "$2" ] ; then # Record value in specified var. This expects we were called not in a sub-shell.
+ eval "$2=\${ports}"
+ else # Expect sub-shell
+ echo ${ports}
+ fi
+}
+
+bond_get_active_iface() {
+ local iface="$1"
+ local active
+
+ read active < "/sys/class/net/${iface}/bonding/active_slave"
+ if [ -n "$2" ] ; then # Record value in specified var. This expects we were called not in a sub-shell.
+ eval "$2=\${active}"
+ else # Expect sub-shell
+ echo ${active}
+ fi
+}
+
+bond_is_balancing() {
+ local iface="$1"
+ read mode mode_index < "/sys/class/net/${iface}/bonding/mode"
+ ocf_is_true "${OCF_RESKEY_debug}" && ocf_log debug "Detected balancing $mode $mode_index"
+ case "${mode}" in
+ "balance-rr"|"balance-xor"|"802.3ad"|"balance-tlb"|"balance-alb")
+ return 0
+ ;;
+ *)
+ return 1
+ ;;
+ esac
+}
+
+bond_get_speed() {
+ local iface="$1"
+ local aggregate_speed=0
+ local active_iface
+ local bond_ports
+
+ if ! iface_is_bond "${iface}" ; then
+ echo 0
+ return
+ fi
+
+ bond_get_ports "${iface}" bond_ports
+
+ if bond_is_balancing "${iface}" ; then
+ for port in ${bond_ports} ; do
+ : $(( aggregate_speed += $( iface_get_speed "${port}" ) ))
+ done
+ # Bonding is unable to get speed*n
+ : $(( aggregate_speed = aggregate_speed * 8 / 10 ))
+ else
+ bond_get_active_iface "${iface}" "active_iface"
+ aggregate_speed=$( iface_get_speed "$active_iface" )
+ fi
+ if [ -n "$2" ] ; then # Record value in specified var. This expects we were called not in a sub-shell.
+ eval "$2=\${aggregate_speed}"
+ else # Expect sub-shell
+ echo ${aggregate_speed}
+ fi
+}
+
+update() {
+ local speed;
+ local nic="${OCF_RESKEY_iface}";
+
+ if [ -z "${OCF_RESKEY_iface}" ]; then
+ nic=$( get_nic_name_by_ip )
+ if [ -z "${nic}" ];then
+ ocf_log err "Could not determine network interface name from IP address (${OCF_RESKEY_ip})"
+ ocf_exit_reason "unable to determine interface name"
+ exit $OCF_ERR_GENERIC
+ fi
+ fi
+ speed=$( iface_get_speed "${nic}" )
+
+ : $(( score = speed * ${OCF_RESKEY_weight_base} / 1000 ))
+ if [ "$__OCF_ACTION" = "start" ] ; then
+ attrd_updater -n "${OCF_RESKEY_name}" -B "${score}" -d "${OCF_RESKEY_dampen}" ${attrd_options}
+ else
+ attrd_updater -n "${OCF_RESKEY_name}" -v "${score}" -d "${OCF_RESKEY_dampen}" ${attrd_options}
+ fi
+ rc=$?
+ case ${rc} in
+ 0)
+ ocf_is_true "${OCF_RESKEY_debug}" && ocf_log debug "Updated ${OCF_RESKEY_name} = ${score}"
+ ;;
+ *)
+ ocf_log warn "Could not update ${OCF_RESKEY_name} = ${score}: rc=${rc}"
+ ;;
+ esac
+ return ${rc}
+}
+
+case $__OCF_ACTION in
+ meta-data)
+ meta_data
+ exit $OCF_SUCCESS
+ ;;
+ usage|help)
+ usage
+ exit $OCF_SUCCESS
+ ;;
+esac
+
+: ${ha_pseudo_resource_name:="ifspeed-${OCF_RESOURCE_INSTANCE}"}
+
+attrd_options=''
+if ocf_is_true "${OCF_RESKEY_debug}" ; then
+ attrd_options='-VV'
+fi
+
+case "$__OCF_ACTION" in
+ start)
+ validate 10
+ start
+ ;;
+ stop)
+ validate 10
+ stop
+ ;;
+ monitor)
+ validate 10
+ monitor
+ ;;
+ validate-all)
+ validate "$OCF_CHECK_LEVEL"
+ ;;
+ *)
+ usage
+ exit $OCF_ERR_UNIMPLEMENTED
+ ;;
+esac
+
+exit $?
+
+# vim: set filetype=sh expandtab tabstop=4 softtabstop=4 shiftwidth=4 textwidth=80:
diff --git a/agents/ocf/o2cb.in b/agents/ocf/o2cb.in
new file mode 100755
index 0000000..f85d2f4
--- /dev/null
+++ b/agents/ocf/o2cb.in
@@ -0,0 +1,440 @@
+#!@BASH_PATH@
+#
+# ocf:pacemaker:o2cb resource agent
+#
+# Original copyright 2005-2008 Oracle
+# Later changes copyright 2008-2023 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
+# (GPLv2) WITHOUT ANY WARRANTY.
+#
+
+#######################################################################
+
+: ${OCF_FUNCTIONS:="${OCF_ROOT}/resource.d/heartbeat/.ocf-shellfuncs"}
+. "${OCF_FUNCTIONS}"
+: ${__OCF_ACTION:="$1"}
+
+: ${OCF_RESKEY_stack:="pcmk"}
+: ${OCF_RESKEY_sysfs:="/sys/fs"}
+: ${OCF_RESKEY_configfs:="/sys/kernel/config"}
+: ${OCF_RESKEY_daemon_timeout:="10"} # How long to wait for things to start
+: ${OCF_RESKEY_CRM_meta_globally_unique:="false"}
+
+DAEMON="/usr/sbin/ocfs2_controld.${OCF_RESKEY_stack}"
+CLUSTER_STACK_FILE="${OCF_RESKEY_sysfs}/ocfs2/cluster_stack"
+LOADED_PLUGINS_FILE="${OCF_RESKEY_sysfs}/ocfs2/loaded_cluster_plugins"
+
+#
+# Check to see if a filesystem driver is loaded.
+# 0 is loaded, 1 is not.
+#
+driver_filesystem() {
+ if [ -z "$1" ]
+ then
+ ocf_log err "driver_filesystem(): Missing an argument"
+ exit 1
+ fi
+ FSNAME="$1"
+
+ FSOUT="$(awk '(NF == 1 && $1 ~ /^'$FSNAME'$/) || $2 ~ /^'$FSNAME'$/{
+ print $1;exit
+ }' /proc/filesystems 2>/dev/null)"
+
+ test -n "$FSOUT"
+ return $?
+}
+
+#
+# Check to see if a filesystem of type $1 is mounted at $2.
+#
+# 0 is mounted, 1 is not.
+#
+check_filesystem() {
+ if [ $# -ne 2 ] || [ -z "$1" ] || [ -z "$2" ]
+ then
+ ocf_log err "check_filesystem(): Missing arguments"
+ exit 4
+ fi
+ FSNAME="$1"
+ MOUNTPOINT="$2"
+
+ FULL_MOUNTSEARCH=$(echo "$MOUNTPOINT" | sed -e 's/\//\\\\\//g')
+ MOUNTOUT=$(awk '$2 ~ /^'$FULL_MOUNTSEARCH'$/ && $3 ~ /^'$FSNAME'$/{print $2; exit}' < /proc/mounts 2>/dev/null)
+ test -n "$MOUNTOUT"
+ return $?
+}
+
+#
+# Unload a filesystem driver.
+# Be careful to notice if the driver is built-in and do nothing.
+#
+# 0 is success, 1 is error, 2 is already unloaded.
+#
+unload_filesystem() {
+ if [ $# -ne 1 ] || [ -z "$1" ]
+ then
+ ocf_log err "unload_filesystem(): Missing an argument"
+ return 1
+ fi
+ FSNAME="$1"
+
+ driver_filesystem "$FSNAME" || return 2
+
+ MODOUT=$(awk '$1 ~ /^'$FSNAME'$/{print $1,$3;exit}' < /proc/modules 2>/dev/null)
+ if [ -z "$MODOUT" ]; then
+ # The driver is built in, we can't unload it.
+ return 0
+ fi
+
+ case "$MODOUT" in
+ "$FSNAME 0")
+ ;;
+ "$FSNAME "*)
+ # The driver is busy, leave it alone
+ ocf_log err "Module $FSNAME is still in use"
+ return 1
+ ;;
+ *)
+ ocf_log err "Invalid module parsing! "
+ return 1
+ ;;
+ esac
+
+ modprobe -rs "$FSNAME"
+ if [ $? -ne 0 ]; then
+ ocf_log err "Unable to unload module: $FSNAME"
+ return 1
+ fi
+
+ return 0
+}
+
+status_daemon() {
+ PID=$(pidof "$DAEMON")
+ if [ -n "$PID" ]; then
+ return $OCF_SUCCESS
+ fi
+ return $OCF_NOT_RUNNING
+}
+
+bringup_daemon() {
+ if [ ! -e "$DAEMON" ]; then
+ ocf_log err "Required binary not found: $DAEMON"
+ return $OCF_ERR_INSTALLED
+ fi
+
+ "$DAEMON"; rc=$?
+ if [ $rc -ne 0 ]; then
+ ocf_log err "Could not start $DAEMON"
+ return $OCF_ERR_GENERIC
+ fi
+
+ sleep 1
+ COUNT=0
+ rc=$OCF_NOT_RUNNING
+
+ while [ $rc -eq $OCF_NOT_RUNNING ]; do
+ COUNT=$(expr $COUNT + 1)
+ if [ $COUNT -gt $OCF_RESKEY_daemon_timeout ]; then
+ ocf_log err "$(basename $DAEMON) did not come up"
+ return $OCF_ERR_GENERIC
+ fi
+ status_daemon; rc=$?
+ sleep 1
+ done
+
+ return $rc
+}
+
+kill_daemon() {
+ status_daemon; rc=$?
+ if [ $rc -ne $OCF_SUCCESS ]; then
+ return $rc
+ fi
+
+ ocf_log info "Stopping $(basename "$DAEMON")"
+ killproc "$DAEMON"
+
+ while [ $rc -eq $OCF_NOT_RUNNING ]; do
+ sleep 1
+ status_daemon; rc=$?
+ done
+
+ return $OCF_SUCCESS
+}
+
+#
+# Unload a module
+# 0 is success, 1 is error, 2 is not loaded
+#
+unload_module() {
+ if [ $# -lt 1 ] || [ -z "$1" ]
+ then
+ ocf_log err "unload_module(): Requires an argument"
+ return 1
+ fi
+ MODNAME="$1"
+
+ MODOUT=$(awk '$1 ~ /^'$MODNAME'$/{print $1,$3;exit}' < /proc/modules 2>/dev/null)
+ if [ -z "$MODOUT" ]
+ then
+ return 2
+ fi
+
+ case "$MODOUT" in
+ "$MODNAME 0")
+ ;;
+ "$MODNAME "*)
+ return 2
+ ;;
+ *)
+ ocf_log err "Invalid module parsing!"
+ return 1
+ ;;
+ esac
+
+ modprobe -rs "$MODNAME"
+ if [ $? -ne 0 ]; then
+ ocf_log err "Unable to unload module \"$MODNAME\""
+ return 1
+ fi
+
+ return 0
+}
+
+o2cb_start() {
+
+ o2cb_monitor; rc=$?
+ if [ $rc -ne $OCF_NOT_RUNNING ]; then
+ return $rc
+ fi
+
+ ocf_log info "Starting $OCF_RESOURCE_INSTANCE"
+
+ if [ ! -e "$CLUSTER_STACK_FILE" ]; then
+ modprobe -s ocfs2_stackglue
+ if [ $? -ne 0 ]; then
+ ocf_log err "Could not load ocfs2_stackglue"
+ return $OCF_ERR_INSTALLED
+ fi
+ fi
+
+ SP_OUT="$(awk '/^'user'$/{print; exit}' "$LOADED_PLUGINS_FILE" 2>/dev/null)"
+ if [ -z "$SP_OUT" ]
+ then
+ modprobe -s ocfs2_stack_user
+ if [ $? -ne 0 ]; then
+ ocf_log err "Could not load ocfs2_stack_user"
+ return $OCF_ERR_INSTALLED
+ fi
+ fi
+
+ SP_OUT="$(awk '/^'user'$/{print; exit}' "$LOADED_PLUGINS_FILE" 2>/dev/null)"
+ if [ -z "$SP_OUT" ]; then
+ ocf_log err "Switch to userspace stack unsuccessful"
+ return $OCF_ERR_INSTALLED
+ fi
+
+ if [ -f "$CLUSTER_STACK_FILE" ]; then
+ echo "$OCF_RESKEY_stack" >"$CLUSTER_STACK_FILE"
+ if [ $? -ne 0 ]; then
+ ocf_log err "Userspace stack '$OCF_RESKEY_stack' not supported"
+ return $OCF_ERR_INSTALLED
+ fi
+ else
+ ocf_log err "Switch to userspace stack not supported"
+ return $OCF_ERR_INSTALLED
+ fi
+
+ driver_filesystem ocfs2; rc=$?
+ if [ $rc -ne 0 ]; then
+ modprobe -s ocfs2
+ if [ $? -ne 0 ]; then
+ ocf_log err "Unable to load ocfs2 module"
+ return $OCF_ERR_INSTALLED
+ fi
+ fi
+
+ bringup_daemon
+ return $?
+}
+
+o2cb_stop() {
+ o2cb_monitor; rc=$?
+ case $rc in
+ "$OCF_NOT_RUNNING") return $OCF_SUCCESS;;
+ esac
+
+ ocf_log info "Stopping $OCF_RESOURCE_INSTANCE"
+
+ kill_daemon
+ if [ $? -ne 0 ]; then
+ ocf_log err "Unable to unload modules: the cluster is still online"
+ return $OCF_ERR_GENERIC
+ fi
+
+ unload_filesystem ocfs2
+ if [ $? -eq 1 ]; then
+ ocf_log err "Unable to unload ocfs2 module"
+ return $OCF_ERR_GENERIC
+ fi
+
+ # If we can't find the stack glue, we have nothing to do.
+ [ ! -e "$LOADED_PLUGINS_FILE" ] && return $OCF_SUCCESS
+
+ while read plugin
+ do
+ unload_module "ocfs2_stack_${plugin}"
+ if [ $? -eq 1 ]; then
+ ocf_log err "Unable to unload ocfs2_stack_${plugin}"
+ return $OCF_ERR_GENERIC
+ fi
+ done <"$LOADED_PLUGINS_FILE"
+
+ unload_module "ocfs2_stackglue"
+ if [ $? -eq 1 ]; then
+ ocf_log err "Unable to unload ocfs2_stackglue"
+ return $OCF_ERR_GENERIC
+ fi
+
+ # Don't unmount configfs - it's always in use by libdlm
+}
+
+o2cb_monitor() {
+ o2cb_validate
+
+ # Assume that ocfs2_controld will terminate if any of the conditions below are met
+
+ driver_filesystem configfs; rc=$?
+ if [ $rc -ne 0 ]; then
+ ocf_log info "configfs not loaded"
+ return $OCF_NOT_RUNNING
+ fi
+
+ check_filesystem configfs "${OCF_RESKEY_configfs}"; rc=$?
+ if [ $rc -ne 0 ]; then
+ ocf_log info "configfs not mounted"
+ return $OCF_NOT_RUNNING
+ fi
+
+ if [ ! -e "$LOADED_PLUGINS_FILE" ]; then
+ ocf_log info "Stack glue driver not loaded"
+ return $OCF_NOT_RUNNING
+ fi
+
+ grep user "$LOADED_PLUGINS_FILE" >/dev/null 2>&1; rc=$?
+ if [ $rc -ne 0 ]; then
+ ocf_log err "Wrong stack $(cat $LOADED_PLUGINS_FILE)"
+ return $OCF_ERR_INSTALLED
+ fi
+
+ driver_filesystem ocfs2; rc=$?
+ if [ $rc -ne 0 ]; then
+ ocf_log info "ocfs2 not loaded"
+ return $OCF_NOT_RUNNING
+ fi
+
+ status_daemon
+ return $?
+}
+
+o2cb_usage() {
+ echo "usage: $0 {start|stop|monitor|validate-all|meta-data}"
+ echo " Expects to have a fully populated OCF RA-compliant environment set."
+ echo " In particualr, a value for OCF_ROOT"
+}
+
+o2cb_validate() {
+ check_binary ${DAEMON}
+
+ case "${OCF_RESKEY_CRM_meta_globally_unique}" in
+ yes|Yes|true|True|1)
+ ocf_log err "$OCF_RESOURCE_INSTANCE must be configured with the globally_unique=false meta attribute"
+ exit $OCF_ERR_CONFIGURED
+ ;;
+ esac
+
+ return $OCF_SUCCESS
+}
+
+meta_data() {
+ cat <<END
+<?xml version="1.0"?>
+<resource-agent name="o2cb" version="@VERSION@">
+ <version>1.0</version>
+ <longdesc lang="en">
+This Resource Agent controls the userspace daemon needed by OCFS2.
+ </longdesc>
+ <shortdesc lang="en">OCFS2 daemon resource agent</shortdesc>
+ <parameters>
+
+ <parameter name="sysfs" unique="0">
+ <longdesc lang="en">
+Location where sysfs is mounted
+ </longdesc>
+ <shortdesc lang="en">Sysfs location</shortdesc>
+ <content type="string" default="/sys/fs"/>
+ </parameter>
+
+ <parameter name="configfs" unique="0">
+ <longdesc lang="en">
+Location where configfs is mounted
+ </longdesc>
+ <shortdesc lang="en">Configfs location</shortdesc>
+ <content type="string" default="/sys/kernel/config"/>
+ </parameter>
+
+ <parameter name="stack" unique="0">
+ <longdesc lang="en">
+Which userspace stack to use. Known values: pcmk
+ </longdesc>
+ <shortdesc lang="en">Userspace stack</shortdesc>
+ <content type="string" default="pcmk"/>
+ </parameter>
+
+ <parameter name="daemon_timeout" unique="0">
+ <longdesc lang="en">
+Number of seconds to allow the control daemon to come up
+ </longdesc>
+ <shortdesc lang="en">Daemon Timeout</shortdesc>
+ <content type="string" default="10"/>
+ </parameter>
+
+ </parameters>
+ <actions>
+ <action name="start" timeout="90s" />
+ <action name="stop" timeout="100s" />
+ <action name="monitor" timeout="20s" depth="0"/>
+ <action name="meta-data" timeout="5s" />
+ <action name="validate-all" timeout="30s" />
+ </actions>
+</resource-agent>
+END
+}
+
+case "$__OCF_ACTION" in
+meta-data) meta_data
+ exit $OCF_SUCCESS
+ ;;
+start) o2cb_start
+ ;;
+stop) o2cb_stop
+ ;;
+monitor) o2cb_monitor
+ ;;
+validate-all) o2cb_validate
+ ;;
+usage|help) o2cb_usage
+ exit $OCF_SUCCESS
+ ;;
+*) o2cb_usage
+ exit $OCF_ERR_UNIMPLEMENTED
+ ;;
+esac
+
+exit $?
+
+# vim: set filetype=sh expandtab tabstop=4 softtabstop=4 shiftwidth=4 textwidth=80:
diff --git a/agents/ocf/ping.in b/agents/ocf/ping.in
new file mode 100755
index 0000000..4855e5b
--- /dev/null
+++ b/agents/ocf/ping.in
@@ -0,0 +1,436 @@
+#!/bin/sh
+#
+# ocf:pacemaker:ping resource agent
+#
+# Copyright 2009-2023 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.
+#
+
+#######################################################################
+# Initialization:
+
+: ${OCF_FUNCTIONS:="${OCF_ROOT}/resource.d/heartbeat/.ocf-shellfuncs"}
+. "${OCF_FUNCTIONS}"
+: ${__OCF_ACTION:="$1"}
+
+# Explicitly list all environment variables used, to make static analysis happy
+: ${OCF_RESKEY_CRM_meta_timeout:="20000"}
+: ${OCF_RESKEY_CRM_meta_globally_unique:="false"}
+: ${OCF_RESKEY_name:="pingd"}
+: ${OCF_RESKEY_dampen:="5s"}
+: ${OCF_RESKEY_attempts:="3"}
+: ${OCF_RESKEY_multiplier:="1"}
+: ${OCF_RESKEY_debug:="0"}
+: ${OCF_RESKEY_failure_score:="0"}
+: ${OCF_RESKEY_use_fping:="1"}
+: ${OCF_RESKEY_host_list:=""}
+: ${OCF_RESKEY_options:=""}
+: ${OCF_RESKEY_timeout:=""}
+
+#######################################################################
+
+meta_data() {
+ cat <<END
+<?xml version="1.0"?>
+<resource-agent name="ping" version="@VERSION@">
+<version>1.1</version>
+
+<longdesc lang="en">
+Every time the monitor action is run, this resource agent records (in the CIB) the current number of nodes the host can connect to using the system fping (preferred) or ping tool.
+</longdesc>
+<shortdesc lang="en">node connectivity</shortdesc>
+
+<parameters>
+
+<parameter name="pidfile" unique-group="pidfile">
+<longdesc lang="en">PID file</longdesc>
+<shortdesc lang="en">PID file</shortdesc>
+<content type="string" default="${HA_VARRUN%%/}/ping-${OCF_RESOURCE_INSTANCE}" />
+</parameter>
+
+<parameter name="dampen" reloadable="1">
+<longdesc lang="en">
+The time to wait (dampening) further changes occur
+</longdesc>
+<shortdesc lang="en">Dampening interval</shortdesc>
+<content type="integer" default="5s"/>
+</parameter>
+
+<parameter name="name" unique-group="name">
+<longdesc lang="en">
+The name of the attributes to set. This is the name to be used in the constraints.
+</longdesc>
+<shortdesc lang="en">Attribute name</shortdesc>
+<content type="string" default="pingd"/>
+</parameter>
+
+<parameter name="multiplier">
+<longdesc lang="en">
+The number by which to multiply the number of connected ping nodes by
+</longdesc>
+<shortdesc lang="en">Value multiplier</shortdesc>
+<content type="integer" default="1"/>
+</parameter>
+
+<parameter name="host_list" required="1">
+<longdesc lang="en">
+A space separated list of ping nodes to count.
+</longdesc>
+<shortdesc lang="en">Host list</shortdesc>
+<content type="string" default=""/>
+</parameter>
+
+<parameter name="attempts" reloadable="1">
+<longdesc lang="en">
+Number of ping attempts, per host, before declaring it dead
+</longdesc>
+<shortdesc lang="en">no. of ping attempts</shortdesc>
+<content type="integer" default="3"/>
+</parameter>
+
+<parameter name="timeout" reloadable="1">
+<longdesc lang="en">
+How long, in seconds, to wait before declaring a ping lost
+</longdesc>
+<shortdesc lang="en">ping timeout in seconds</shortdesc>
+<content type="integer" default="2"/>
+</parameter>
+
+<parameter name="options">
+<longdesc lang="en">
+A catch all for any other options that need to be passed to ping.
+</longdesc>
+<shortdesc lang="en">Extra Options</shortdesc>
+<content type="string" default=""/>
+</parameter>
+
+<parameter name="failure_score">
+<longdesc lang="en">
+Resource is failed if the score is less than failure_score.
+Default never fails.
+</longdesc>
+<shortdesc lang="en">failure_score</shortdesc>
+<content type="integer" default=""/>
+</parameter>
+
+<parameter name="use_fping">
+<longdesc lang="en">
+Use fping rather than ping, if found. If set to 0, fping
+will not be used even if present.
+</longdesc>
+<shortdesc lang="en">Use fping if available</shortdesc>
+<content type="boolean" default="1"/>
+</parameter>
+
+<parameter name="debug" reloadable="1">
+<longdesc lang="en">
+Enables to use default attrd_updater verbose logging on every call.
+</longdesc>
+<shortdesc lang="en">Verbose logging</shortdesc>
+<content type="string" default="false"/>
+</parameter>
+
+</parameters>
+
+<actions>
+<action name="start" timeout="60s" />
+<action name="stop" timeout="20s" />
+<action name="monitor" depth="0" timeout="60s" interval="10s"/>
+<action name="meta-data" timeout="5s" />
+<action name="validate-all" timeout="30s" />
+<action name="reload-agent" timeout="20s" />
+</actions>
+</resource-agent>
+END
+}
+
+#######################################################################
+
+ping_conditional_log() {
+ level="$1"; shift
+ if [ $OCF_RESKEY_debug -gt 0 ]; then
+ ocf_log "$level" "$*"
+ fi
+}
+
+ping_usage() {
+ cat <<END
+usage: $0 {start|stop|monitor|validate-all|reload-agent|meta-data}
+
+Expects to have a fully populated OCF RA-compliant environment set.
+END
+}
+
+ping_start() {
+ ping_monitor
+ if [ $? -eq $OCF_SUCCESS ]; then
+ return $OCF_SUCCESS
+ fi
+ touch "${OCF_RESKEY_pidfile}"
+ ping_update
+}
+
+ping_stop() {
+
+ rm -f "${OCF_RESKEY_pidfile}"
+
+ attrd_updater -D -n "$OCF_RESKEY_name" -d "$OCF_RESKEY_dampen"
+
+ return $OCF_SUCCESS
+}
+
+ping_monitor() {
+ if [ -f "${OCF_RESKEY_pidfile}" ]; then
+ ping_update
+ if [ $? -eq 0 ]; then
+ return $OCF_SUCCESS
+ fi
+ return $OCF_ERR_GENERIC
+ fi
+ return $OCF_NOT_RUNNING
+}
+
+ping_validate() {
+ # Host-specific checks
+ if [ "$OCF_CHECK_LEVEL" = "10" ]; then
+
+ # Is the state directory writable?
+ state_dir=$(dirname "$OCF_RESKEY_pidfile")
+ touch "$state_dir/$$"
+ if [ $? -ne 0 ]; then
+ ocf_log err "Invalid location for 'state': $state_dir is not writable"
+ return $OCF_ERR_ARGS
+ fi
+ rm "$state_dir/$$"
+
+ # Does the ping binary exist?
+ check_binary ping
+
+ fi
+
+# Pidfile better be an absolute path
+ case "$OCF_RESKEY_pidfile" in
+ /*) ;;
+ *) ocf_log warn "You should use an absolute path for pidfile not: $OCF_RESKEY_pidfile" ;;
+ esac
+
+# Check the host list
+ if [ -z "$OCF_RESKEY_host_list" ]; then
+ ocf_log err "Empty host_list. Please specify some nodes to ping"
+ exit $OCF_ERR_CONFIGURED
+ fi
+
+ # For fping allow only same IP versions or hostnames
+ if use_fping; then
+ hosts_family
+ if [ $? -eq 99 ]; then
+ ocf_log err "host_list can contain only host with same IP versions for fping"
+ exit $OCF_ERR_CONFIGURED
+ fi
+ fi
+
+ return $OCF_SUCCESS
+}
+
+ping_reload_agent() {
+ # No action required
+ :;
+}
+
+fping_check() {
+ p_exe=fping
+ hosts_family
+ case $? in
+ 6) p_exe=fping6 ;;
+ 99) ocf_log err "Ambiguous IP versions in host_list: '$OCF_RESKEY_host_list'"; exit $OCF_ERR_CONFIGURED;;
+ esac
+
+ active=0
+
+ timeout=$(expr $OCF_RESKEY_timeout \* 1000 / $OCF_RESKEY_attempts)
+
+ cmd="$p_exe -r $OCF_RESKEY_attempts -t $timeout -B 1.0 $OCF_RESKEY_options $OCF_RESKEY_host_list"
+ fping_output=$($cmd 2>&1); rc=$?
+ active=$(echo "$fping_output" | grep "is alive" | wc -l)
+
+ case $rc in
+ 0)
+ if [ $OCF_RESKEY_debug -gt 1 ]; then
+ ping_conditional_log info "$fping_output"
+ fi
+ ;;
+ 1)
+ for h in $(echo "$fping_output" | grep "is unreachable" | awk '{print $1}'); do
+ ping_conditional_log warn "$h is inactive: $fping_output"
+ done
+ ;;
+ *)
+ ocf_log err "Unexpected result for '$cmd' $rc: $(echo "$fping_output" | tr '\n' ';')"
+ ;;
+ esac
+
+ return $active
+}
+
+ping_check() {
+ active=0
+ for host in $OCF_RESKEY_host_list; do
+ p_exe=ping
+
+ case $(uname) in
+ Linux) p_args="-n -q -W $OCF_RESKEY_timeout -c $OCF_RESKEY_attempts";;
+ Darwin) p_args="-n -q -t $OCF_RESKEY_timeout -c $OCF_RESKEY_attempts -o";;
+ FreeBSD) p_args="-n -q -t $OCF_RESKEY_timeout -c $OCF_RESKEY_attempts -o";;
+ *) ocf_log err "Unknown host type: $(uname)"; exit $OCF_ERR_INSTALLED;;
+ esac
+
+ case "$host" in
+ *:*) p_exe=ping6
+ esac
+
+ ping_output=$($p_exe $p_args $OCF_RESKEY_options $host 2>&1); rc=$?
+
+ case $rc in
+ 0)
+ active=$(expr $active + 1)
+ if [ $OCF_RESKEY_debug -gt 1 ]; then
+ ping_conditional_log info "$ping_output"
+ fi
+ ;;
+ 1) ping_conditional_log warn "$host is inactive: $ping_output";;
+ *) ocf_log err "Unexpected result for '$p_exe $p_args $OCF_RESKEY_options $host' $rc: $ping_output";;
+ esac
+ done
+ return $active
+}
+
+ping_update() {
+
+ if use_fping; then
+ fping_check
+ active=$?
+ else
+ ping_check
+ active=$?
+ fi
+
+ score=$(expr $active \* $OCF_RESKEY_multiplier)
+ if [ "$__OCF_ACTION" = "start" ] ; then
+ attrd_updater -n "$OCF_RESKEY_name" -B "$score" -d "$OCF_RESKEY_dampen"
+ else
+ attrd_updater -n "$OCF_RESKEY_name" -v "$score" -d "$OCF_RESKEY_dampen"
+ fi
+ rc=$?
+ case $rc in
+ 0) ping_conditional_log debug "Updated $OCF_RESKEY_name = $score" ;;
+ *) ocf_log warn "Could not update $OCF_RESKEY_name = $score: rc=$rc";;
+ esac
+ if [ $rc -ne 0 ]; then
+ return $rc
+ fi
+
+ if [ -n "$OCF_RESKEY_failure_score" ] && [ "$score" -lt "$OCF_RESKEY_failure_score" ]; then
+ ocf_log warn "$OCF_RESKEY_name is less than failure_score($OCF_RESKEY_failure_score)"
+ return 1
+ fi
+ return 0
+}
+
+use_fping() {
+ ocf_is_true "$OCF_RESKEY_use_fping" && have_binary fping;
+}
+
+# return values:
+# 4 IPv4
+# 6 IPv6
+# 0 indefinite (i.e. hostname)
+host_family() {
+ case $1 in
+ *[0-9].*[0-9].*[0-9].*[0-9]) return 4 ;;
+ *:*) return 6 ;;
+ *) return 0 ;;
+ esac
+}
+
+# return values same as host_family plus
+# 99 ambiguous families
+hosts_family() {
+ # For fping allow only same IP versions or hostnames
+ family=0
+ for host in $OCF_RESKEY_host_list; do
+ host_family "$host"
+ f=$?
+ if [ $family -ne 0 ] && [ $f -ne 0 ] && [ $f -ne $family ] ; then
+ family=99
+ break
+ fi
+ [ $f -ne 0 ] && family=$f
+ done
+ return $family
+}
+
+integer=$(echo ${OCF_RESKEY_timeout} | egrep -o '[0-9]*')
+case "${OCF_RESKEY_timeout}" in
+ *[0-9]ms|*[0-9]msec) OCF_RESKEY_timeout=$(expr $integer / 1000);;
+ *[0-9]m|*[0-9]min) OCF_RESKEY_timeout=$(expr $integer \* 60);;
+ *[0-9]h|*[0-9]hr) OCF_RESKEY_timeout=$(expr $integer \* 60 \* 60);;
+ *) OCF_RESKEY_timeout=$integer;;
+esac
+
+if [ -z "${OCF_RESKEY_timeout}" ]; then
+ if [ -n "$OCF_RESKEY_host_list" ]; then
+ host_count=$(echo $OCF_RESKEY_host_list | awk '{print NF}')
+ OCF_RESKEY_timeout=$(expr $OCF_RESKEY_CRM_meta_timeout / $host_count / $OCF_RESKEY_attempts)
+ OCF_RESKEY_timeout=$(expr $OCF_RESKEY_timeout / 1100) # Convert to seconds and finish 10% early
+ else
+ OCF_RESKEY_timeout=5
+ fi
+fi
+
+if [ ${OCF_RESKEY_timeout} -lt 1 ]; then
+ OCF_RESKEY_timeout=5
+elif [ ${OCF_RESKEY_timeout} -gt 1000 ]; then
+ # ping actually complains if this value is too high, 5 minutes is plenty
+ OCF_RESKEY_timeout=300
+fi
+
+if [ "${OCF_RESKEY_CRM_meta_globally_unique}" = "false" ]; then
+ : ${OCF_RESKEY_pidfile:="${HA_VARRUN%%/}/ping-${OCF_RESKEY_name}"}
+else
+ : ${OCF_RESKEY_pidfile:="${HA_VARRUN%%/}/ping-${OCF_RESOURCE_INSTANCE}"}
+fi
+
+# Check the debug option
+case "${OCF_RESKEY_debug}" in
+ true|True|TRUE|1) OCF_RESKEY_debug=1;;
+ false|False|FALSE|0) OCF_RESKEY_debug=0;;
+ verbose|Verbose|VERBOSE|2) OCF_RESKEY_debug=2;;
+ *)
+ ocf_log warn "Value for 'debug' is incorrect. Please specify 'true', 'false', or 'verbose', not: ${OCF_RESKEY_debug}"
+ OCF_RESKEY_debug=false
+ ;;
+esac
+
+case "$__OCF_ACTION" in
+meta-data) meta_data
+ exit $OCF_SUCCESS
+ ;;
+start) ping_start;;
+stop) ping_stop;;
+monitor) ping_monitor;;
+validate-all) ping_validate;;
+reload-agent) ping_reload_agent;;
+usage|help) ping_usage
+ exit $OCF_SUCCESS
+ ;;
+*) ping_usage
+ exit $OCF_ERR_UNIMPLEMENTED
+ ;;
+esac
+exit $?
+
+# vim: set filetype=sh expandtab tabstop=4 softtabstop=4 shiftwidth=4 textwidth=80:
diff --git a/agents/ocf/remote.in b/agents/ocf/remote.in
new file mode 100755
index 0000000..048b504
--- /dev/null
+++ b/agents/ocf/remote.in
@@ -0,0 +1,106 @@
+#!/bin/sh
+#
+# ocf:pacemaker:remote OCF resource agent
+#
+# Copyright 2013-2023 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
+# (GPLv2) WITHOUT ANY WARRANTY.
+#
+
+# This script provides metadata for Pacemaker's internal remote agent.
+# Outside of acting as a placeholder so the agent can be indexed, and
+# providing metadata, this script should never be invoked. The actual
+# functionality behind the remote connection lives within Pacemaker's
+# controller daemon.
+#
+
+: ${OCF_FUNCTIONS:="${OCF_ROOT}/resource.d/heartbeat/.ocf-shellfuncs"}
+. "${OCF_FUNCTIONS}"
+: ${__OCF_ACTION:="$1"}
+
+meta_data() {
+ cat <<END
+<?xml version="1.0"?>
+<resource-agent name="remote" version="@VERSION@">
+ <version>1.1</version>
+ <shortdesc lang="en">Pacemaker Remote connection</shortdesc>
+ <parameters>
+ <parameter name="server" unique-group="address">
+ <longdesc lang="en">
+ Server location to connect to (IP address or resolvable host name)
+ </longdesc>
+ <shortdesc lang="en">Remote hostname</shortdesc>
+ <content type="string"/>
+ </parameter>
+ <parameter name="port" unique-group="address">
+ <longdesc lang="en">
+ TCP port at which to contact Pacemaker Remote executor
+ </longdesc>
+ <shortdesc lang="en">Remote port</shortdesc>
+ <content type="integer" default="3121"/>
+ </parameter>
+ <parameter name="reconnect_interval" reloadable="1">
+ <longdesc lang="en">
+ If this is a positive time interval, the cluster will attempt to
+ reconnect to a remote node after an active connection has been
+ lost at this interval. Otherwise, the cluster will attempt to
+ reconnect immediately (after any fencing needed).
+ </longdesc>
+ <shortdesc lang="en">reconnect interval</shortdesc>
+ <content type="string" default="0"/>
+ </parameter>
+ </parameters>
+ <actions>
+ <action name="start" timeout="60s" />
+ <action name="stop" timeout="60s" />
+ <action name="monitor" timeout="30s" />
+ <action name="migrate_to" timeout="60s" />
+ <action name="migrate_from" timeout="60s" />
+ <action name="reload" timeout="60s" />
+ <action name="reload-agent" timeout="60s" />
+ <action name="meta-data" timeout="5s" />
+ </actions>
+</resource-agent>
+END
+ return $OCF_SUCCESS
+}
+
+remote_usage() {
+ EXITSTATUS="$1"
+
+ cat <<END
+Usage: $0 <action>
+
+This conforms to the OCF Resource Agent API version 1.1, and expects
+to have OCF-compliant environment variables provided.
+END
+ return $EXITSTATUS
+}
+
+remote_unsupported() {
+ ocf_log info "The ocf:pacemaker:remote agent should not be directly invoked except for meta-data action"
+ return $OCF_ERR_GENERIC
+}
+
+case $__OCF_ACTION in
+ meta-data) meta_data ;;
+ start) remote_unsupported ;;
+ stop) remote_unsupported ;;
+ monitor) remote_unsupported ;;
+ migrate_to) remote_unsupported ;;
+ migrate_from) remote_unsupported ;;
+ reload) remote_unsupported ;;
+ reload-agent) remote_unsupported ;;
+ validate-all) remote_unsupported ;;
+ usage|help) remote_usage $OCF_SUCCESS ;;
+ *) remote_usage $OCF_ERR_UNIMPLEMENTED ;;
+esac
+
+rc=$?
+ocf_log debug "${OCF_RESOURCE_INSTANCE} $__OCF_ACTION : $rc"
+exit $rc
+
+# vim: set filetype=sh expandtab tabstop=4 softtabstop=4 shiftwidth=4 textwidth=80:
diff --git a/agents/stonith/Makefile.am b/agents/stonith/Makefile.am
new file mode 100644
index 0000000..e231775
--- /dev/null
+++ b/agents/stonith/Makefile.am
@@ -0,0 +1,19 @@
+#
+# Copyright 2003-2023 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)/mk/common.mk
+include $(top_srcdir)/mk/man.mk
+
+sbin_SCRIPTS = fence_watchdog
+
+if BUILD_LHA_SUPPORT
+sbin_SCRIPTS += fence_legacy
+endif
+
+CLEANFILES = $(man7_MANS) $(man8_MANS)
diff --git a/agents/stonith/fence_legacy.in b/agents/stonith/fence_legacy.in
new file mode 100755
index 0000000..c0eeca1
--- /dev/null
+++ b/agents/stonith/fence_legacy.in
@@ -0,0 +1,277 @@
+#!@PYTHON@
+
+__copyright__ = "Copyright 2018-2023 the Pacemaker project contributors"
+__license__ = "GNU General Public License version 2 or later (GPLv2+) WITHOUT ANY WARRANTY"
+
+import os
+import sys
+import argparse
+import subprocess
+
+# These imports allow running from a source checkout after running `make`.
+# Note that while this doesn't necessarily mean it will successfully run tests,
+# but being able to see --help output can be useful.
+if os.path.exists("@abs_top_srcdir@/python"):
+ sys.path.insert(0, "@abs_top_srcdir@/python")
+
+if os.path.exists("@abs_top_builddir@/python") and "@abs_top_builddir@" != "@abs_top_srcdir@":
+ sys.path.insert(0, "@abs_top_builddir@/python")
+
+from pacemaker.exitstatus import ExitStatus
+
+VERSION = "1.1.0"
+
+USAGE = """Helper that presents a Pacemaker-style interface for Linux-HA stonith plugins
+
+Should never be invoked by the user directly
+
+
+Usage: fence_legacy [options]
+
+Options:
+ -h usage
+ -t <sub agent> sub agent
+ -n <name> nodename
+ -o <string> Action: on | off | reset (default) | stat | hostlist
+ -s <stonith> stonith command
+ -q quiet mode
+ -V version"""
+
+META_DATA = """<?xml version="1.0" ?>
+<resource-agent name="fence_pcmk" shortdesc="Helper that presents a Pacemaker-style interface for Linux-HA stonith plugins">
+<longdesc>
+This agent should never be invoked by the user directly.
+</longdesc>
+<vendor-url>https://www.clusterlabs.org/</vendor-url>
+<parameters>
+ <parameter name="action" unique="1" required="1">
+ <getopt mixed="-o &lt;action&gt;" />
+ <content type="string" default="disable" />
+ <shortdesc lang="en">Fencing Action</shortdesc>
+ </parameter>
+ <parameter name="port" unique="1" required="1">
+ <getopt mixed="-n &lt;id&gt;" />
+ <content type="string" />
+ <shortdesc lang="en">Physical plug number or name of virtual machine</shortdesc>
+ </parameter>
+ <parameter name="help" unique="1" required="0">
+ <getopt mixed="-h" />
+ <content type="string" />
+ <shortdesc lang="en">Display help and exit</shortdesc>
+ </parameter>
+</parameters>
+<actions>
+ <action name="enable" />
+ <action name="disable" />
+ <action name="reboot" />
+ <action name="off" />
+ <action name="on" />
+ <action name="status" />
+ <action name="list" />
+ <action name="metadata" />
+</actions>
+</resource-agent>"""
+
+ACTIONS = [
+ "on",
+ "off",
+ "reset",
+ "reboot",
+ "stat",
+ "status",
+ "metadata",
+ "monitor",
+ "list",
+ "hostlist",
+ "poweroff",
+ "poweron"
+]
+
+
+def parse_cli_options():
+ """ Return parsed command-line options (as argparse namespace) """
+
+
+ # Don't add standard help option, so we can format it how we want
+ parser = argparse.ArgumentParser(add_help=False)
+
+ parser.add_argument("-t", metavar="SUBAGENT", dest="subagent",
+ nargs=1, default="none", help="sub-agent")
+
+ parser.add_argument("-n", metavar="NODE", dest="node",
+ nargs=1, default="", help="name of target node")
+
+ # The help text here is consistent with the original version, though
+ # perhaps all actions should be listed.
+ parser.add_argument("-o", metavar="ACTION", dest="action",
+ nargs=1, choices=ACTIONS, default="reset",
+ help="action: on | off | reset (default) | stat | hostlist")
+
+ parser.add_argument("-s", metavar="COMMAND", dest="command",
+ nargs=1, default="stonith", help="stonith command")
+
+ parser.add_argument("-q", dest="quiet", action="store_true",
+ help="quiet mode")
+
+ parser.add_argument("-h", "--help", action="store_true",
+ help="show usage and exit")
+
+ # Don't use action="version", because that printed to stderr before
+ # Python 3.4, and help2man doesn't like that.
+ parser.add_argument("-V", "--version", action="store_true",
+ help="show version and exit")
+
+ return parser.parse_args()
+
+
+def parse_stdin_options(options):
+ """ Update options namespace with options parsed from stdin """
+
+ nlines = 0
+ for line in sys.stdin:
+ # Remove leading and trailing whitespace
+ line = line.strip()
+
+ # Skip blank lines and comments
+ if line == "" or line[0] == "#":
+ continue
+
+ nlines = nlines + 1
+
+ # Parse option name and value (allow whitespace around equals sign)
+ try:
+ (name, value) = line.split("=", 1)
+ name = name.rstrip()
+ if name == "":
+ raise ValueError
+ except ValueError:
+ print("parse error: illegal name in option %d" % nlines,
+ file=sys.stderr)
+ sys.exit(ExitStatus.INVALID_PARAM)
+ value = value.lstrip()
+
+ if name == "plugin":
+ options.subagent = value
+
+ elif name in [ "option", "action" ]:
+ options.action = value
+
+ elif name == "nodename":
+ options.node = value
+ os.environ[name] = value
+
+ elif name == "stonith":
+ options.command = value
+
+ elif name != "agent": # agent is used by fenced
+ os.environ[name] = value
+
+
+def normalize_options(options):
+ """ Use string rather than list of one string """
+
+ if not hasattr(options.subagent, "strip"):
+ options.subagent = options.subagent[0]
+
+ if not hasattr(options.node, "strip"):
+ options.node = options.node[0]
+
+ if not hasattr(options.action, "strip"):
+ options.action = options.action[0]
+
+ if not hasattr(options.command, "strip"):
+ options.command = options.command[0]
+
+
+def build_command(options):
+ """ Return command to execute (as list of arguments) """
+
+ if options.action in [ "hostlist", "list" ]:
+ extra_args = [ "-l" ]
+
+ elif options.action in [ "monitor", "stat", "status" ]:
+ extra_args = [ "-S" ]
+
+ else:
+ if options.node == "":
+ if not options.quiet:
+ print("failed: no plug number")
+ sys.exit(ExitStatus.ERROR)
+ extra_args = [ "-T", options.action, options.node ]
+
+ return [ options.command, "-t", options.subagent, "-E" ] + extra_args
+
+
+def handle_local_options(options):
+ """ Handle options that don't require the fence agent """
+
+ if options.help:
+ print(USAGE)
+ sys.exit(ExitStatus.OK)
+
+ if options.version:
+ print(VERSION)
+ sys.exit(ExitStatus.OK)
+
+
+def remap_action(options):
+ """ Pre-process requested action """
+
+ options.action = options.action.lower()
+
+ if options.action == "metadata":
+ print(META_DATA)
+ sys.exit(ExitStatus.OK)
+
+ elif options.action in [ "hostlist", "list" ]:
+ options.quiet = True
+
+ # Remap accepted aliases to their actual commands
+
+ elif options.action == "reboot":
+ options.action = "reset"
+
+ elif options.action == "poweron":
+ options.action = "on"
+
+ elif options.action == "poweroff":
+ options.action = "off"
+
+
+def execute_command(options, cmd):
+ """ Execute command and return its exit status """
+
+ if not options.quiet:
+ print("Performing: " + " ".join(cmd))
+ return subprocess.call(cmd)
+
+
+def handle_result(options, status):
+ """ Process fence agent result """
+
+ if status == 0:
+ message = "success"
+ exitcode = ExitStatus.OK
+ else:
+ message = "failed"
+ exitcode = ExitStatus.ERROR
+ if not options.quiet:
+ print("%s: %s %d" % (message, options.node, status))
+ sys.exit(exitcode)
+
+
+def main():
+ """ Execute an LHA-style fence agent """
+
+ options = parse_cli_options()
+ handle_local_options(options)
+ normalize_options(options)
+ parse_stdin_options(options)
+ remap_action(options)
+ cmd = build_command(options)
+ status = execute_command(options, cmd)
+ handle_result(options, status)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/agents/stonith/fence_watchdog.in b/agents/stonith/fence_watchdog.in
new file mode 100755
index 0000000..f43ab87
--- /dev/null
+++ b/agents/stonith/fence_watchdog.in
@@ -0,0 +1,284 @@
+#!@PYTHON@
+"""Dummy watchdog fence agent for providing meta-data for the pacemaker internal agent
+"""
+
+__copyright__ = "Copyright 2012-2022 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 target (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()