Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Feature: crmd: Implement reliable event notifications

  • Loading branch information...
commit 0cd1b8f02b403976afe106e0ca3a8a8a16864c6c 1 parent 00a97f2
@beekhof beekhof authored
View
2  crmd/Makefile.am
@@ -28,7 +28,7 @@ noinst_HEADERS = crmd.h crmd_fsa.h crmd_messages.h fsa_defines.h \
fsa_matrix.h fsa_proto.h crmd_utils.h crmd_callbacks.h \
crmd_lrm.h te_callbacks.h tengine.h
-crmd_SOURCES = main.c crmd.c corosync.c \
+crmd_SOURCES = main.c crmd.c corosync.c notify.c \
fsa.c control.c messages.c membership.c callbacks.c \
election.c join_client.c join_dc.c subsystems.c throttle.c \
cib.c pengine.c tengine.c lrm.c lrm_state.c remote_lrmd_ra.c \
View
4 crmd/callbacks.c
@@ -126,6 +126,7 @@ peer_update_callback(enum crm_status_type type, crm_node_t * node, const void *d
case crm_status_nstate:
crm_info("%s is now %s (was %s)",
node->uname, state_text(node->state), state_text(data));
+
if (safe_str_eq(data, node->state)) {
/* State did not change */
return;
@@ -147,7 +148,10 @@ peer_update_callback(enum crm_status_type type, crm_node_t * node, const void *d
}
}
}
+
+ crmd_notify_node_event(node);
break;
+
case crm_status_processes:
if (data) {
old = *(const uint32_t *)data;
View
67 crmd/control.c
@@ -873,28 +873,64 @@ do_recover(long long action,
/* *INDENT-OFF* */
pe_cluster_option crmd_opts[] = {
- /* name, old-name, validate, default, description */
- { "dc-version", NULL, "string", NULL, "none", NULL, "Version of Pacemaker on the cluster's DC.", "Includes the hash which identifies the exact Mercurial changeset it was built from. Used for diagnostic purposes." },
- { "cluster-infrastructure", NULL, "string", NULL, "heartbeat", NULL, "The messaging stack on which Pacemaker is currently running.", "Used for informational and diagnostic purposes." },
- { XML_CONFIG_ATTR_DC_DEADTIME, "dc_deadtime", "time", NULL, "20s", &check_time, "How long to wait for a response from other nodes during startup.", "The \"correct\" value will depend on the speed/load of your network and the type of switches used." },
+ /* name, old-name, validate, values, default, short description, long description */
+ { "dc-version", NULL, "string", NULL, "none", NULL,
+ "Version of Pacemaker on the cluster's DC.",
+ "Includes the hash which identifies the exact changeset it was built from. Used for diagnostic purposes."
+ },
+ { "cluster-infrastructure", NULL, "string", NULL, "heartbeat", NULL,
+ "The messaging stack on which Pacemaker is currently running.",
+ "Used for informational and diagnostic purposes." },
+ { XML_CONFIG_ATTR_DC_DEADTIME, "dc_deadtime", "time", NULL, "20s", &check_time,
+ "How long to wait for a response from other nodes during startup.",
+ "The \"correct\" value will depend on the speed/load of your network and the type of switches used."
+ },
{ XML_CONFIG_ATTR_RECHECK, "cluster_recheck_interval", "time",
- "Zero disables polling. Positive values are an interval in seconds (unless other SI units are specified. eg. 5min)", "15min", &check_timer,
+ "Zero disables polling. Positive values are an interval in seconds (unless other SI units are specified. eg. 5min)",
+ "15min", &check_timer,
"Polling interval for time based changes to options, resource parameters and constraints.",
"The Cluster is primarily event driven, however the configuration can have elements that change based on time."
- " To ensure these changes take effect, we can optionally poll the cluster's status for changes." },
+ " To ensure these changes take effect, we can optionally poll the cluster's status for changes."
+ },
+
+ { "notification-script", NULL, "string", NULL, "/dev/null", &check_script,
+ "Notification script to be called after significant cluster events",
+ "Full path to a script that will be invoked when resources start/stop/fail, fencing occurs or nodes join/leave the cluster.\n"
+ "Must exist on all nodes in the cluster."
+ },
+ { "notification-target", NULL, "string", NULL, "", NULL,
+ "Destination for notifications (Optional)",
+ "Where should the supplied script send notifications to. Useful to avoid hard-coding this in the script."
+ },
+
{ "load-threshold", NULL, "percentage", NULL, "80%", &check_utilization,
"The maximum amount of system resources that should be used by nodes in the cluster",
"The cluster will slow down its recovery process when the amount of system resources used"
- " (currently CPU) approaches this limit", },
+ " (currently CPU) approaches this limit",
+ },
{ "node-action-limit", NULL, "integer", NULL, "0", &check_number,
"The maximum number of jobs that can be scheduled per node. Defaults to 2x cores"},
- { XML_CONFIG_ATTR_ELECTION_FAIL, "election_timeout", "time", NULL, "2min", &check_timer, "*** Advanced Use Only ***.", "If need to adjust this value, it probably indicates the presence of a bug." },
- { XML_CONFIG_ATTR_FORCE_QUIT, "shutdown_escalation", "time", NULL, "20min", &check_timer, "*** Advanced Use Only ***.", "If need to adjust this value, it probably indicates the presence of a bug." },
- { "crmd-integration-timeout", NULL, "time", NULL, "3min", &check_timer, "*** Advanced Use Only ***.", "If need to adjust this value, it probably indicates the presence of a bug." },
- { "crmd-finalization-timeout", NULL, "time", NULL, "30min", &check_timer, "*** Advanced Use Only ***.", "If you need to adjust this value, it probably indicates the presence of a bug." },
- { "crmd-transition-delay", NULL, "time", NULL, "0s", &check_timer, "*** Advanced Use Only ***\nEnabling this option will slow down cluster recovery under all conditions", "Delay cluster recovery for the configured interval to allow for additional/related events to occur.\nUseful if your configuration is sensitive to the order in which ping updates arrive." },
+ { XML_CONFIG_ATTR_ELECTION_FAIL, "election_timeout", "time", NULL, "2min", &check_timer,
+ "*** Advanced Use Only ***.", "If need to adjust this value, it probably indicates the presence of a bug."
+ },
+ { XML_CONFIG_ATTR_FORCE_QUIT, "shutdown_escalation", "time", NULL, "20min", &check_timer,
+ "*** Advanced Use Only ***.", "If need to adjust this value, it probably indicates the presence of a bug."
+ },
+ { "crmd-integration-timeout", NULL, "time", NULL, "3min", &check_timer,
+ "*** Advanced Use Only ***.", "If need to adjust this value, it probably indicates the presence of a bug."
+ },
+ { "crmd-finalization-timeout", NULL, "time", NULL, "30min", &check_timer,
+ "*** Advanced Use Only ***.", "If you need to adjust this value, it probably indicates the presence of a bug."
+ },
+ { "crmd-transition-delay", NULL, "time", NULL, "0s", &check_timer,
+ "*** Advanced Use Only ***\n"
+ "Enabling this option will slow down cluster recovery under all conditions",
+ "Delay cluster recovery for the configured interval to allow for additional/related events to occur.\n"
+ "Useful if your configuration is sensitive to the order in which ping updates arrive."
+ },
{ "stonith-watchdog-timeout", NULL, "time", NULL, NULL, &check_timer,
- "How long to wait before we can assume nodes are safely down", NULL },
+ "How long to wait before we can assume nodes are safely down", NULL
+ },
{ "no-quorum-policy", "no_quorum_policy", "enum", "stop, freeze, ignore, suicide", "stop", &check_quorum, NULL, NULL },
#if SUPPORT_PLUGIN
@@ -927,6 +963,7 @@ crmd_pref(GHashTable * options, const char *name)
static void
config_query_callback(xmlNode * msg, int call_id, int rc, xmlNode * output, void *user_data)
{
+ const char *script = NULL;
const char *value = NULL;
GHashTable *config_hash = NULL;
crm_time_t *now = crm_time_new(NULL);
@@ -955,6 +992,10 @@ config_query_callback(xmlNode * msg, int call_id, int rc, xmlNode * output, void
verify_crmd_options(config_hash);
+ script = crmd_pref(config_hash, "notification-script");
+ value = crmd_pref(config_hash, "notification-target");
+ crmd_enable_notifications(script, value);
+
value = crmd_pref(config_hash, XML_CONFIG_ATTR_DC_DEADTIME);
election_trigger->period_ms = crm_get_msec(value);
View
1  crmd/crmd_utils.h
@@ -21,6 +21,7 @@
# include <crm/crm.h>
# include <crm/common/xml.h>
# include <crm/cib/internal.h> /* For CIB_OP_MODIFY */
+# include "notify.h"
# define CLIENT_EXIT_WAIT 30
# define FAKE_TE_ID "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
View
2  crmd/lrm.c
@@ -2415,6 +2415,8 @@ process_lrm_event(lrm_state_t * lrm_state, lrmd_event_data_t * op, struct recurr
free(prefix);
}
+ crmd_notify_resource_op(lrm_state->node_name, op);
+
if (op->rsc_deleted) {
crm_info("Deletion of resource '%s' complete after %s", op->rsc_id, op_key);
delete_rsc_entry(lrm_state, NULL, op->rsc_id, NULL, pcmk_ok, NULL);
View
188 crmd/notify.c
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2015 Andrew Beekhof <andrew@beekhof.net>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <crm_internal.h>
+#include <crm/crm.h>
+#include <crm/msg_xml.h>
+#include "notify.h"
+
+char *notify_script = NULL;
+char *notify_target = NULL;
+
+
+static const char *notify_keys[] =
+{
+ "CRM_notify_recipient",
+ "CRM_notify_node",
+ "CRM_notify_rsc",
+ "CRM_notify_task",
+ "CRM_notify_interval",
+ "CRM_notify_desc",
+ "CRM_notify_status",
+ "CRM_notify_target_rc",
+ "CRM_notify_rc",
+ "CRM_notify_kind",
+ "CRM_notify_version",
+};
+
+
+void
+crmd_enable_notifications(const char *script, const char *target)
+{
+ free(notify_script);
+ notify_script = NULL;
+
+ free(notify_target);
+ notify_target = NULL;
+
+ if(safe_str_eq(script, "/dev/null")) {
+ crm_notice("Notifications disabled");
+ return;
+ }
+
+ notify_script = strdup(script);
+ notify_target = strdup(target);
+ crm_notice("Notifications enabled");
+}
+
+static void
+set_notify_key(const char *name, const char *cvalue, char *value)
+{
+ int lpc;
+ bool found = 0;
+
+ if(cvalue == NULL) {
+ cvalue = value;
+ }
+
+ for(lpc = 0; lpc < DIMOF(notify_keys); lpc++) {
+ if(safe_str_eq(name, notify_keys[lpc])) {
+ found = 1;
+ crm_trace("Setting notify key %s = '%s'", name, cvalue);
+ setenv(name, cvalue, 1);
+ break;
+ }
+ }
+
+ CRM_ASSERT(found != 0);
+ free(value);
+}
+
+
+static void
+send_notification(const char *kind)
+{
+ int lpc;
+ pid_t pid;
+
+ crm_debug("Sending '%s' notification to '%s' via '%s'", kind, notify_target, notify_script);
+
+ set_notify_key("CRM_notify_recipient", notify_target, NULL);
+ set_notify_key("CRM_notify_kind", kind, NULL);
+ set_notify_key("CRM_notify_version", VERSION, NULL);
+
+ pid = fork();
+ if (pid == -1) {
+ crm_perror(LOG_ERR, "notification failed");
+ }
+
+ if (pid == 0) {
+ /* crm_debug("notification: I am the child. Executing the nofitication program."); */
+ execl(notify_script, notify_script, NULL);
+ exit(EXIT_FAILURE);
+
+ } else {
+ for(lpc = 0; lpc < DIMOF(notify_keys); lpc++) {
+ unsetenv(notify_keys[lpc]);
+ }
+ }
+}
+
+void crmd_notify_node_event(crm_node_t *node)
+{
+ if(notify_script == NULL) {
+ return;
+ }
+
+ set_notify_key("CRM_notify_node", node->uname, NULL);
+ set_notify_key("CRM_notify_desc", node->state, NULL);
+
+ send_notification("node");
+}
+
+void
+crmd_notify_fencing_op(stonith_event_t * e)
+{
+ char *desc = NULL;
+
+ if(notify_script) {
+ return;
+ }
+
+ desc = crm_strdup_printf("Operation %s requested by %s for peer %s: %s (ref=%s)",
+ e->operation, e->origin, e->target, pcmk_strerror(e->result),
+ e->id);
+
+ set_notify_key("CRM_notify_node", e->target, NULL);
+ set_notify_key("CRM_notify_task", e->operation, NULL);
+ set_notify_key("CRM_notify_desc", NULL, desc);
+ set_notify_key("CRM_notify_rc", NULL, crm_itoa(e->result));
+
+ send_notification("fencing");
+}
+
+void
+crmd_notify_resource_op(const char *node, lrmd_event_data_t * op)
+{
+ int target_rc = 0;
+
+ if(notify_script == NULL) {
+ return;
+ }
+
+ target_rc = rsc_op_expected_rc(op);
+ if(op->interval == 0 && target_rc == op->rc && safe_str_eq(op->op_type, RSC_STATUS)) {
+ /* Leave it up to the script if they want to notify for
+ * 'failed' probes, only swallow ones for which the result was
+ * unexpected.
+ *
+ * Even if we find a resource running, it was probably because
+ * someone erased the status section.
+ */
+ return;
+ }
+
+ set_notify_key("CRM_notify_node", node, NULL);
+
+ set_notify_key("CRM_notify_rsc", op->rsc_id, NULL);
+ set_notify_key("CRM_notify_task", op->op_type, NULL);
+ set_notify_key("CRM_notify_interval", NULL, crm_itoa(op->interval));
+
+ set_notify_key("CRM_notify_target_rc", NULL, crm_itoa(target_rc));
+ set_notify_key("CRM_notify_status", NULL, crm_itoa(op->op_status));
+ set_notify_key("CRM_notify_rc", NULL, crm_itoa(op->rc));
+
+ if(op->op_status == PCMK_LRM_OP_DONE) {
+ set_notify_key("CRM_notify_desc", services_ocf_exitcode_str(op->rc), NULL);
+ } else {
+ set_notify_key("CRM_notify_desc", services_lrm_status_str(op->op_status), NULL);
+ }
+
+ send_notification("resource");
+}
+
View
30 crmd/notify.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2015 Andrew Beekhof <andrew@beekhof.net>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+#ifndef CRMD_NOTIFY__H
+# define CRMD_NOTIFY__H
+
+# include <crm/crm.h>
+# include <crm/cluster.h>
+# include <crm/stonith-ng.h>
+
+void crmd_enable_notifications(const char *script, const char *target);
+void crmd_notify_node_event(crm_node_t *node);
+void crmd_notify_fencing_op(stonith_event_t * e);
+void crmd_notify_resource_op(const char *node, lrmd_event_data_t * op);
+
+#endif
View
2  crmd/te_utils.c
@@ -124,6 +124,8 @@ tengine_stonith_notify(stonith_t * st, stonith_event_t * st_event)
return;
}
+ crmd_notify_fencing_op(st_event);
+
if (st_event->result == pcmk_ok && safe_str_eq("on", st_event->action)) {
crm_notice("%s was successfully unfenced by %s (at the request of %s)",
st_event->target, st_event->executioner ? st_event->executioner : "<anyone>", st_event->origin);
View
2  cts/CIB.py
@@ -239,6 +239,8 @@ def contents(self, target=None):
o["dc-deadtime"] = "5s"
o["no-quorum-policy"] = no_quorum
o["expected-quorum-votes"] = self.num_nodes
+ o["notification-script"] = "/var/lib/pacemaker/notify.sh"
+ o["notification-target"] = "/var/lib/pacemaker/notify.log"
if self.CM.Env["DoBSC"] == 1:
o["ident-string"] = "Linux-HA TEST configuration file - REMOVEME!!"
View
68 extra/pcmk_notify_sample.sh
@@ -0,0 +1,68 @@
+#!/bin/bash
+#
+# Copyright (C) 2015 Andrew Beekhof <andrew@beekhof.net>
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This software is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+if [ -z $CRM_notify_version ]; then
+ echo "Pacemaker version 1.1.14 is required" >> ${CRM_notify_recipient}
+ exit 0
+fi
+
+case $CRM_notify_kind in
+ node)
+ echo "Node '${CRM_notify_node}' is now '${CRM_notify_desc}'" >> ${CRM_notify_recipient}
+ ;;
+ fencing)
+ # Other keys:
+ #
+ # CRM_notify_node
+ # CRM_notify_task
+ # CRM_notify_rc
+ #
+ echo "Fencing ${CRM_notify_desc}" >> ${CRM_notify_recipient}
+ ;;
+ resource)
+ # Other keys:
+ #
+ # CRM_notify_target_rc
+ # CRM_notify_status
+ # CRM_notify_rc
+ #
+ if [ ${CRM_notify_interval} = "0" ]; then
+ CRM_notify_interval=""
+ else
+ CRM_notify_interval=" (${CRM_notify_interval})"
+ fi
+
+ if [ ${CRM_notify_target_rc} = "0" ]; then
+ CRM_notify_target_rc=""
+ else
+ CRM_notify_target_rc=" (target: ${CRM_notify_target_rc})"
+ fi
+
+ case ${CRM_notify_desc} in
+ Cancelled) ;;
+ *)
+ echo "Resource operation '${CRM_notify_task}${CRM_notify_interval}' for '${CRM_notify_rsc}' on '${CRM_notify_node}': ${CRM_notify_desc}${CRM_notify_target_rc}" >> ${CRM_notify_recipient}
+ ;;
+ esac
+ ;;
+ *)
+ echo "Unhandled $CRM_notify_kind notification" >> ${CRM_notify_recipient}
+ env | grep CRM_notify >> ${CRM_notify_recipient}
+ ;;
+
+esac
View
1  include/crm_internal.h
@@ -127,6 +127,7 @@ gboolean check_timer(const char *value);
gboolean check_boolean(const char *value);
gboolean check_number(const char *value);
gboolean check_quorum(const char *value);
+gboolean check_script(const char *value);
gboolean check_utilization(const char *value);
/* Shared PE/crmd functionality */
View
27 lib/common/utils.c
@@ -180,6 +180,33 @@ check_quorum(const char *value)
}
gboolean
+check_script(const char *value)
+{
+ struct stat st;
+
+ if(safe_str_eq(value, "/dev/null")) {
+ return TRUE;
+ }
+
+ if(stat(value, &st) != 0) {
+ crm_err("Script %s does not exist", value);
+ return FALSE;
+ }
+
+ if(S_ISREG(st.st_mode) == 0) {
+ crm_err("Script %s is not a regular file", value);
+ return FALSE;
+ }
+
+ if( (st.st_mode & (S_IXUSR | S_IXGRP )) == 0) {
+ crm_err("Script %s is not executable", value);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+gboolean
check_utilization(const char *value)
{
char *end = NULL;
Please sign in to comment.
Something went wrong with that request. Please try again.