diff --git a/include/crm/common/alerts_internal.h b/include/crm/common/alerts_internal.h index 8bfcb8c01d..714366ba3d 100644 --- a/include/crm/common/alerts_internal.h +++ b/include/crm/common/alerts_internal.h @@ -1,114 +1,115 @@ /* * Copyright (C) 2015 Andrew Beekhof * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser 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 Lesser 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 ALERT_INTERNAL_H #define ALERT_INTERNAL_H #include #include /* Default-Timeout to use before killing a alerts script (in milliseconds) */ # define CRM_ALERT_DEFAULT_TIMEOUT_MS (30000) /* Default-Format-String used to pass timestamps to the alerts scripts */ # define CRM_ALERT_DEFAULT_TSTAMP_FORMAT "%H:%M:%S.%06N" typedef struct { char *name; char *value; } crm_alert_envvar_t; enum crm_alert_flags { crm_alert_none = 0x0000, crm_alert_node = 0x0001, crm_alert_fencing = 0x0002, crm_alert_resource = 0x0004, crm_alert_attribute = 0x0008, crm_alert_default = crm_alert_node|crm_alert_fencing|crm_alert_resource }; typedef struct { char *id; char *path; char *tstamp_format; char *recipient; char **select_attribute_name; GHashTable *envvars; int timeout; uint32_t flags; } crm_alert_entry_t; enum crm_alert_keys_e { CRM_alert_recipient = 0, CRM_alert_node, CRM_alert_nodeid, CRM_alert_rsc, CRM_alert_task, CRM_alert_interval, CRM_alert_desc, CRM_alert_status, CRM_alert_target_rc, CRM_alert_rc, CRM_alert_kind, CRM_alert_version, CRM_alert_node_sequence, CRM_alert_timestamp, CRM_alert_attribute_name, CRM_alert_attribute_value, CRM_alert_timestamp_epoch, CRM_alert_timestamp_usec, + CRM_alert_exec_time, CRM_alert_select_kind, CRM_alert_select_attribute_name }; -#define CRM_ALERT_INTERNAL_KEY_MAX 18 +#define CRM_ALERT_INTERNAL_KEY_MAX 19 #define CRM_ALERT_NODE_SEQUENCE "CRM_alert_node_sequence" extern const char *crm_alert_keys[CRM_ALERT_INTERNAL_KEY_MAX][3]; crm_alert_entry_t *crm_dup_alert_entry(crm_alert_entry_t *entry); crm_alert_envvar_t *crm_dup_alert_envvar(crm_alert_envvar_t *src); crm_alert_entry_t *crm_alert_entry_new(const char *id, const char *path); void crm_free_alert_entry(crm_alert_entry_t *entry); void crm_free_alert_envvar(crm_alert_envvar_t *entry); void crm_insert_alert_key(GHashTable *table, enum crm_alert_keys_e name, const char *value); void crm_insert_alert_key_int(GHashTable *table, enum crm_alert_keys_e name, int value); void crm_unset_alert_keys(void); void crm_set_envvar_list(crm_alert_entry_t *entry); void crm_unset_envvar_list(crm_alert_entry_t *entry); bool crm_patchset_contains_alert(xmlNode *msg, bool config); static inline const char * crm_alert_flag2text(enum crm_alert_flags flag) { switch (flag) { case crm_alert_node: return "node"; case crm_alert_fencing: return "fencing"; case crm_alert_resource: return "resource"; case crm_alert_attribute: return "attribute"; default: return "unknown"; } } #endif diff --git a/lib/common/alerts.c b/lib/common/alerts.c index 5da87dd391..902fdb32c5 100644 --- a/lib/common/alerts.c +++ b/lib/common/alerts.c @@ -1,281 +1,282 @@ /* * Copyright 2015-2018 Andrew Beekhof * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #include #include #include #include #include #include /* for F_CIB_UPDATE_RESULT */ /* * to allow script compatibility we can have more than one * set of environment variables */ const char *crm_alert_keys[CRM_ALERT_INTERNAL_KEY_MAX][3] = { [CRM_alert_recipient] = {"CRM_notify_recipient", "CRM_alert_recipient", NULL}, [CRM_alert_node] = {"CRM_notify_node", "CRM_alert_node", NULL}, [CRM_alert_nodeid] = {"CRM_notify_nodeid", "CRM_alert_nodeid", NULL}, [CRM_alert_rsc] = {"CRM_notify_rsc", "CRM_alert_rsc", NULL}, [CRM_alert_task] = {"CRM_notify_task", "CRM_alert_task", NULL}, [CRM_alert_interval] = {"CRM_notify_interval", "CRM_alert_interval", NULL}, [CRM_alert_desc] = {"CRM_notify_desc", "CRM_alert_desc", NULL}, [CRM_alert_status] = {"CRM_notify_status", "CRM_alert_status", NULL}, [CRM_alert_target_rc] = {"CRM_notify_target_rc", "CRM_alert_target_rc", NULL}, [CRM_alert_rc] = {"CRM_notify_rc", "CRM_alert_rc", NULL}, [CRM_alert_kind] = {"CRM_notify_kind", "CRM_alert_kind", NULL}, [CRM_alert_version] = {"CRM_notify_version", "CRM_alert_version", NULL}, [CRM_alert_node_sequence] = {"CRM_notify_node_sequence", CRM_ALERT_NODE_SEQUENCE, NULL}, [CRM_alert_timestamp] = {"CRM_notify_timestamp", "CRM_alert_timestamp", NULL}, [CRM_alert_attribute_name] = {"CRM_notify_attribute_name", "CRM_alert_attribute_name", NULL}, [CRM_alert_attribute_value] = {"CRM_notify_attribute_value", "CRM_alert_attribute_value", NULL}, [CRM_alert_timestamp_epoch] = {"CRM_notify_timestamp_epoch", "CRM_alert_timestamp_epoch", NULL}, - [CRM_alert_timestamp_usec] = {"CRM_notify_timestamp_usec", "CRM_alert_timestamp_usec", NULL} + [CRM_alert_timestamp_usec] = {"CRM_notify_timestamp_usec", "CRM_alert_timestamp_usec", NULL}, + [CRM_alert_exec_time] = {"CRM_notify_exec_time", "CRM_alert_exec_time", NULL} }; void crm_free_alert_envvar(crm_alert_envvar_t *entry) { free(entry->name); free(entry->value); free(entry); } /*! * \brief Create a new alert entry structure * * \param[in] id ID to use * \param[in] path Path to alert agent executable * * \return Pointer to newly allocated alert entry * \note Non-string fields will be filled in with defaults. * It is the caller's responsibility to free the result, * using crm_free_alert_entry(). */ crm_alert_entry_t * crm_alert_entry_new(const char *id, const char *path) { crm_alert_entry_t *entry = calloc(1, sizeof(crm_alert_entry_t)); CRM_ASSERT(entry && id && path); entry->id = strdup(id); entry->path = strdup(path); entry->timeout = CRM_ALERT_DEFAULT_TIMEOUT_MS; entry->flags = crm_alert_default; return entry; } void crm_free_alert_entry(crm_alert_entry_t *entry) { if (entry) { free(entry->id); free(entry->path); free(entry->tstamp_format); free(entry->recipient); g_strfreev(entry->select_attribute_name); if (entry->envvars) { g_hash_table_destroy(entry->envvars); } free(entry); } } crm_alert_envvar_t * crm_dup_alert_envvar(crm_alert_envvar_t *src) { crm_alert_envvar_t *dst = calloc(1, sizeof(crm_alert_envvar_t)); CRM_ASSERT(dst); dst->name = strdup(src->name); dst->value = src->value?strdup(src->value):NULL; return dst; } /*! * \internal * \brief Duplicate an alert entry * * \param[in] entry Alert entry to duplicate * * \return Duplicate of alert entry */ crm_alert_entry_t * crm_dup_alert_entry(crm_alert_entry_t *entry) { crm_alert_entry_t *new_entry = crm_alert_entry_new(entry->id, entry->path); new_entry->timeout = entry->timeout; new_entry->flags = entry->flags; new_entry->envvars = crm_str_table_dup(entry->envvars); if (entry->tstamp_format) { new_entry->tstamp_format = strdup(entry->tstamp_format); } if (entry->recipient) { new_entry->recipient = strdup(entry->recipient); } if (entry->select_attribute_name) { new_entry->select_attribute_name = g_strdupv(entry->select_attribute_name); } return new_entry; } void crm_unset_alert_keys() { const char **key; enum crm_alert_keys_e name; for(name = 0; name < DIMOF(crm_alert_keys); name++) { for(key = crm_alert_keys[name]; *key; key++) { crm_trace("Unsetting alert key %s", *key); unsetenv(*key); } } } void crm_insert_alert_key(GHashTable *table, enum crm_alert_keys_e name, const char *value) { for (const char **key = crm_alert_keys[name]; *key; key++) { crm_trace("Inserting alert key %s = '%s'", *key, value); if (value) { g_hash_table_insert(table, strdup(*key), strdup(value)); } else { g_hash_table_remove(table, *key); } } } void crm_insert_alert_key_int(GHashTable *table, enum crm_alert_keys_e name, int value) { for (const char **key = crm_alert_keys[name]; *key; key++) { crm_trace("Inserting alert key %s = %d", *key, value); g_hash_table_insert(table, strdup(*key), crm_itoa(value)); } } static void set_envvar(gpointer key, gpointer value, gpointer user_data) { gboolean always_unset = GPOINTER_TO_INT(user_data); crm_trace("%s environment variable %s='%s'", (value? "Setting" : "Unsetting"), (char*)key, (value? (char*)value : "")); if (value && !always_unset) { setenv(key, value, 1); } else { unsetenv(key); } } void crm_set_envvar_list(crm_alert_entry_t *entry) { if (entry->envvars) { g_hash_table_foreach(entry->envvars, set_envvar, GINT_TO_POINTER(FALSE)); } } /* * \note We have no way of restoring a previous value if one was set. */ void crm_unset_envvar_list(crm_alert_entry_t *entry) { if (entry->envvars) { g_hash_table_foreach(entry->envvars, set_envvar, GINT_TO_POINTER(TRUE)); } } #define XPATH_PATCHSET1_DIFF "//" F_CIB_UPDATE_RESULT "//" XML_TAG_DIFF_ADDED #define XPATH_PATCHSET1_CRMCONFIG XPATH_PATCHSET1_DIFF "//" XML_CIB_TAG_CRMCONFIG #define XPATH_PATCHSET1_ALERTS XPATH_PATCHSET1_DIFF "//" XML_CIB_TAG_ALERTS #define XPATH_PATCHSET1_EITHER \ XPATH_PATCHSET1_CRMCONFIG " | " XPATH_PATCHSET1_ALERTS #define XPATH_CONFIG "/" XML_TAG_CIB "/" XML_CIB_TAG_CONFIGURATION #define XPATH_CRMCONFIG XPATH_CONFIG "/" XML_CIB_TAG_CRMCONFIG "/" #define XPATH_ALERTS XPATH_CONFIG "/" XML_CIB_TAG_ALERTS /*! * \internal * \brief Check whether a CIB update affects alerts * * \param[in] msg XML containing CIB update * \param[in] config Whether to check for crmconfig change as well * * \return TRUE if update affects alerts, FALSE otherwise */ bool crm_patchset_contains_alert(xmlNode *msg, bool config) { int rc = -1; int format= 1; xmlNode *patchset = get_message_xml(msg, F_CIB_UPDATE_RESULT); xmlNode *change = NULL; xmlXPathObject *xpathObj = NULL; CRM_CHECK(msg != NULL, return FALSE); crm_element_value_int(msg, F_CIB_RC, &rc); if (rc < pcmk_ok) { crm_trace("Ignore failed CIB update: %s (%d)", pcmk_strerror(rc), rc); return FALSE; } crm_element_value_int(patchset, "format", &format); if (format == 1) { const char *diff = (config? XPATH_PATCHSET1_EITHER : XPATH_PATCHSET1_ALERTS); if ((xpathObj = xpath_search(msg, diff)) != NULL) { freeXpathObject(xpathObj); return TRUE; } } else if (format == 2) { for (change = __xml_first_child(patchset); change != NULL; change = __xml_next(change)) { const char *xpath = crm_element_value(change, XML_DIFF_PATH); if (xpath == NULL) { continue; } if ((!config || !strstr(xpath, XPATH_CRMCONFIG)) && !strstr(xpath, XPATH_ALERTS)) { /* this is not a change to an existing section ... */ xmlNode *section = NULL; const char *name = NULL; if ((strcmp(xpath, XPATH_CONFIG) != 0) || ((section = __xml_first_child(change)) == NULL) || ((name = crm_element_name(section)) == NULL) || (strcmp(name, XML_CIB_TAG_ALERTS) != 0)) { /* ... nor is it a newly added alerts section */ continue; } } return TRUE; } } else { crm_warn("Unknown patch format: %d", format); } return FALSE; } diff --git a/lib/lrmd/lrmd_alerts.c b/lib/lrmd/lrmd_alerts.c index d22e10ff5c..34dac8276d 100644 --- a/lib/lrmd/lrmd_alerts.c +++ b/lib/lrmd/lrmd_alerts.c @@ -1,377 +1,386 @@ /* * Copyright 2015-2018 David Vossel * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #include #include #include #include #include #include #include #include #include #include #include #include #include static lrmd_key_value_t * alert_key2param(lrmd_key_value_t *head, enum crm_alert_keys_e name, const char *value) { const char **key; if (value == NULL) { value = ""; } for (key = crm_alert_keys[name]; *key; key++) { crm_trace("Setting alert key %s = '%s'", *key, value); head = lrmd_key_value_add(head, *key, value); } return head; } static lrmd_key_value_t * alert_key2param_int(lrmd_key_value_t *head, enum crm_alert_keys_e name, int value) { char *value_s = crm_itoa(value); head = alert_key2param(head, name, value_s); free(value_s); return head; } static lrmd_key_value_t * alert_key2param_ms(lrmd_key_value_t *head, enum crm_alert_keys_e name, guint value) { char *value_s = crm_strdup_printf("%u", value); head = alert_key2param(head, name, value_s); free(value_s); return head; } static void set_ev_kv(gpointer key, gpointer value, gpointer user_data) { lrmd_key_value_t **head = (lrmd_key_value_t **) user_data; if (value) { crm_trace("Setting environment variable %s='%s'", (char*)key, (char*)value); *head = lrmd_key_value_add(*head, key, value); } } static lrmd_key_value_t * alert_envvar2params(lrmd_key_value_t *head, crm_alert_entry_t *entry) { if (entry->envvars) { g_hash_table_foreach(entry->envvars, set_ev_kv, &head); } return head; } /* * We could use g_strv_contains() instead of this function, * but that has only been available since glib 2.43.2. */ static gboolean is_target_alert(char **list, const char *value) { int target_list_num = 0; gboolean rc = FALSE; CRM_CHECK(value != NULL, return FALSE); if (list == NULL) { return TRUE; } target_list_num = g_strv_length(list); for (int cnt = 0; cnt < target_list_num; cnt++) { if (strcmp(list[cnt], value) == 0) { rc = TRUE; break; } } return rc; } /*! * \internal * \brief Execute alert agents for an event * * \param[in] lrmd Executor connection to use * \param[in] alert_list Alerts to execute * \param[in] kind Type of event that is being alerted for * \param[in] attr_name If crm_alert_attribute, the attribute name * \param[in,out] params Environment variables to pass to agents * * \retval pcmk_ok on success * \retval -1 if some alerts failed * \retval -2 if all alerts failed */ static int exec_alert_list(lrmd_t *lrmd, GList *alert_list, enum crm_alert_flags kind, const char *attr_name, lrmd_key_value_t *params) { bool any_success = FALSE, any_failure = FALSE; const char *kind_s = crm_alert_flag2text(kind); crm_time_hr_t *now = NULL; struct timeval tv_now; char timestamp_epoch[20]; char timestamp_usec[7]; params = alert_key2param(params, CRM_alert_kind, kind_s); params = alert_key2param(params, CRM_alert_version, VERSION); for (GList *iter = g_list_first(alert_list); iter; iter = g_list_next(iter)) { crm_alert_entry_t *entry = (crm_alert_entry_t *)(iter->data); lrmd_key_value_t *copy_params = NULL; lrmd_key_value_t *head = NULL; int rc; if (is_not_set(entry->flags, kind)) { crm_trace("Filtering unwanted %s alert to %s via %s", kind_s, entry->recipient, entry->id); continue; } if ((kind == crm_alert_attribute) && !is_target_alert(entry->select_attribute_name, attr_name)) { crm_trace("Filtering unwanted attribute '%s' alert to %s via %s", attr_name, entry->recipient, entry->id); continue; } if (now == NULL) { if (gettimeofday(&tv_now, NULL) == 0) { now = crm_time_timeval_hr_convert(NULL, &tv_now); } } crm_info("Sending %s alert via %s to %s", kind_s, entry->id, entry->recipient); /* Make a copy of the parameters, because each alert will be unique */ for (head = params; head != NULL; head = head->next) { copy_params = lrmd_key_value_add(copy_params, head->key, head->value); } copy_params = alert_key2param(copy_params, CRM_alert_recipient, entry->recipient); if (now) { char *timestamp = crm_time_format_hr(entry->tstamp_format, now); if (timestamp) { copy_params = alert_key2param(copy_params, CRM_alert_timestamp, timestamp); free(timestamp); } snprintf(timestamp_epoch, sizeof(timestamp_epoch), "%lld", (long long) tv_now.tv_sec); copy_params = alert_key2param(copy_params, CRM_alert_timestamp_epoch, timestamp_epoch); snprintf(timestamp_usec, sizeof(timestamp_usec), "%06d", now->useconds); copy_params = alert_key2param(copy_params, CRM_alert_timestamp_usec, timestamp_usec); } copy_params = alert_envvar2params(copy_params, entry); rc = lrmd->cmds->exec_alert(lrmd, entry->id, entry->path, entry->timeout, copy_params); if (rc < 0) { crm_err("Could not execute alert %s: %s " CRM_XS " rc=%d", entry->id, pcmk_strerror(rc), rc); any_failure = TRUE; } else { any_success = TRUE; } } if (now) { free(now); } if (any_failure) { return (any_success? -1 : -2); } return pcmk_ok; } /*! * \internal * \brief Send an alert for a node attribute change * * \param[in] lrmd Executor connection to use * \param[in] alert_list List of alert agents to execute * \param[in] node Name of node with attribute change * \param[in] nodeid Node ID of node with attribute change * \param[in] attr_name Name of attribute that changed * \param[in] attr_value New value of attribute that changed * * \retval pcmk_ok on success * \retval -1 if some alert agents failed * \retval -2 if all alert agents failed */ int lrmd_send_attribute_alert(lrmd_t *lrmd, GList *alert_list, const char *node, uint32_t nodeid, const char *attr_name, const char *attr_value) { int rc = pcmk_ok; lrmd_key_value_t *params = NULL; if (lrmd == NULL) { return -2; } params = alert_key2param(params, CRM_alert_node, node); params = alert_key2param_int(params, CRM_alert_nodeid, nodeid); params = alert_key2param(params, CRM_alert_attribute_name, attr_name); params = alert_key2param(params, CRM_alert_attribute_value, attr_value); rc = exec_alert_list(lrmd, alert_list, crm_alert_attribute, attr_name, params); lrmd_key_value_freeall(params); return rc; } /*! * \internal * \brief Send an alert for a node membership event * * \param[in] lrmd Executor connection to use * \param[in] alert_list List of alert agents to execute * \param[in] node Name of node with change * \param[in] nodeid Node ID of node with change * \param[in] state New state of node with change * * \retval pcmk_ok on success * \retval -1 if some alert agents failed * \retval -2 if all alert agents failed */ int lrmd_send_node_alert(lrmd_t *lrmd, GList *alert_list, const char *node, uint32_t nodeid, const char *state) { int rc = pcmk_ok; lrmd_key_value_t *params = NULL; if (lrmd == NULL) { return -2; } params = alert_key2param(params, CRM_alert_node, node); params = alert_key2param(params, CRM_alert_desc, state); params = alert_key2param_int(params, CRM_alert_nodeid, nodeid); rc = exec_alert_list(lrmd, alert_list, crm_alert_node, NULL, params); lrmd_key_value_freeall(params); return rc; } /*! * \internal * \brief Send an alert for a fencing event * * \param[in] lrmd Executor connection to use * \param[in] alert_list List of alert agents to execute * \param[in] target Name of fence target node * \param[in] task Type of fencing event that occurred * \param[in] desc Readable description of event * \param[in] op_rc Result of fence action * * \retval pcmk_ok on success * \retval -1 if some alert agents failed * \retval -2 if all alert agents failed */ int lrmd_send_fencing_alert(lrmd_t *lrmd, GList *alert_list, const char *target, const char *task, const char *desc, int op_rc) { int rc = pcmk_ok; lrmd_key_value_t *params = NULL; if (lrmd == NULL) { return -2; } params = alert_key2param(params, CRM_alert_node, target); params = alert_key2param(params, CRM_alert_task, task); params = alert_key2param(params, CRM_alert_desc, desc); params = alert_key2param_int(params, CRM_alert_rc, op_rc); rc = exec_alert_list(lrmd, alert_list, crm_alert_fencing, NULL, params); lrmd_key_value_freeall(params); return rc; } /*! * \internal * \brief Send an alert for a resource operation * * \param[in] lrmd Executor connection to use * \param[in] alert_list List of alert agents to execute * \param[in] node Name of node that executed operation * \param[in] op Resource operation * * \retval pcmk_ok on success * \retval -1 if some alert agents failed * \retval -2 if all alert agents failed */ int lrmd_send_resource_alert(lrmd_t *lrmd, GList *alert_list, const char *node, lrmd_event_data_t *op) { int rc = pcmk_ok; int target_rc = pcmk_ok; lrmd_key_value_t *params = NULL; if (lrmd == NULL) { return -2; } target_rc = rsc_op_expected_rc(op); if ((op->interval_ms == 0) && (target_rc == op->rc) && safe_str_eq(op->op_type, RSC_STATUS)) { /* Don't send alerts for probes with the expected result. Leave it up to * the agent whether to alert for 'failed' probes. (Even if we find a * resource running, it was probably because someone did a clean-up of * the status section.) */ return pcmk_ok; } params = alert_key2param(params, CRM_alert_node, node); params = alert_key2param(params, CRM_alert_rsc, op->rsc_id); params = alert_key2param(params, CRM_alert_task, op->op_type); params = alert_key2param_ms(params, CRM_alert_interval, op->interval_ms); params = alert_key2param_int(params, CRM_alert_target_rc, target_rc); params = alert_key2param_int(params, CRM_alert_status, op->op_status); params = alert_key2param_int(params, CRM_alert_rc, op->rc); + /* Reoccurring operations do not set exec_time, so on timeout, set it + * to the operation timeout since that's closer to the actual value. + */ + if (op->op_status == PCMK_LRM_OP_TIMEOUT && op->exec_time == 0) { + params = alert_key2param_int(params, CRM_alert_exec_time, op->timeout); + } else { + params = alert_key2param_int(params, CRM_alert_exec_time, op->exec_time); + } + if (op->op_status == PCMK_LRM_OP_DONE) { params = alert_key2param(params, CRM_alert_desc, services_ocf_exitcode_str(op->rc)); } else { params = alert_key2param(params, CRM_alert_desc, services_lrm_status_str(op->op_status)); } rc = exec_alert_list(lrmd, alert_list, crm_alert_resource, NULL, params); lrmd_key_value_freeall(params); return rc; }