Blame SOURCES/0012-Feature-crmd-Implement-reliable-event-notifications.patch

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