diff --git a/daemons/attrd/attrd_alerts.c b/daemons/attrd/attrd_alerts.c index 3e4fd42127..55cb477c22 100644 --- a/daemons/attrd/attrd_alerts.c +++ b/daemons/attrd/attrd_alerts.c @@ -1,135 +1,135 @@ /* * Copyright 2015-2024 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 #include #include #include #include #include #include #include #include #include "pacemaker-attrd.h" static GList *attrd_alert_list = NULL; static void attrd_lrmd_callback(lrmd_event_data_t * op) { CRM_CHECK(op != NULL, return); switch (op->type) { case lrmd_event_disconnect: crm_info("Lost connection to executor"); attrd_lrmd_disconnect(); break; default: break; } } static lrmd_t * attrd_lrmd_connect(void) { if (the_lrmd == NULL) { the_lrmd = lrmd_api_new(); the_lrmd->cmds->set_callback(the_lrmd, attrd_lrmd_callback); } if (!the_lrmd->cmds->is_connected(the_lrmd)) { const unsigned int max_attempts = 10; int ret = -ENOTCONN; for (int fails = 0; fails < max_attempts; ++fails) { ret = the_lrmd->cmds->connect(the_lrmd, PCMK__VALUE_ATTRD, NULL); if (ret == pcmk_ok) { break; } crm_debug("Could not connect to executor, %d tries remaining", (max_attempts - fails)); /* @TODO We don't want to block here with sleep, but we should wait * some time between connection attempts. We could possibly add a * timer with a callback, but then we'd likely need an alert queue. */ } if (ret != pcmk_ok) { attrd_lrmd_disconnect(); } } return the_lrmd; } void attrd_lrmd_disconnect(void) { if (the_lrmd) { lrmd_t *conn = the_lrmd; the_lrmd = NULL; /* in case we're called recursively */ lrmd_api_delete(conn); /* will disconnect if necessary */ } } static void config_query_callback(xmlNode * msg, int call_id, int rc, xmlNode * output, void *user_data) { xmlNode *crmalerts = NULL; if (rc == -ENXIO) { crm_debug("Local CIB has no alerts section"); return; } else if (rc != pcmk_ok) { crm_notice("Could not query local CIB: %s", pcmk_strerror(rc)); return; } crmalerts = output; if ((crmalerts != NULL) && !pcmk__xe_is(crmalerts, PCMK_XE_ALERTS)) { crmalerts = pcmk__xe_first_child(crmalerts, PCMK_XE_ALERTS, NULL, NULL); } if (!crmalerts) { crm_notice("CIB query result has no " PCMK_XE_ALERTS " section"); return; } - pe_free_alert_list(attrd_alert_list); + pcmk__free_alerts(attrd_alert_list); attrd_alert_list = pcmk__unpack_alerts(crmalerts); } gboolean attrd_read_options(gpointer user_data) { int call_id; CRM_CHECK(the_cib != NULL, return TRUE); call_id = the_cib->cmds->query(the_cib, pcmk__cib_abs_xpath_for(PCMK_XE_ALERTS), NULL, cib_xpath); the_cib->cmds->register_callback_full(the_cib, call_id, 120, FALSE, NULL, "config_query_callback", config_query_callback, free); crm_trace("Querying the CIB... call %d", call_id); return TRUE; } int attrd_send_attribute_alert(const char *node, int nodeid, const char *attr, const char *value) { if (attrd_alert_list == NULL) { return pcmk_ok; } return lrmd_send_attribute_alert(attrd_lrmd_connect(), attrd_alert_list, node, nodeid, attr, value); } diff --git a/daemons/controld/controld_alerts.c b/daemons/controld/controld_alerts.c index 35531fc464..2d364fb619 100644 --- a/daemons/controld/controld_alerts.c +++ b/daemons/controld/controld_alerts.c @@ -1,88 +1,88 @@ /* * Copyright 2012-2024 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 #include #include #include #include #include #include #include #include #include static GList *crmd_alert_list = NULL; void crmd_unpack_alerts(xmlNode *alerts) { - pe_free_alert_list(crmd_alert_list); + pcmk__free_alerts(crmd_alert_list); crmd_alert_list = pcmk__unpack_alerts(alerts); } void crmd_alert_node_event(pcmk__node_status_t *node) { lrm_state_t *lrm_state; if (crmd_alert_list == NULL) { return; } lrm_state = controld_get_executor_state(NULL, false); if (lrm_state == NULL) { return; } lrmd_send_node_alert((lrmd_t *) lrm_state->conn, crmd_alert_list, node->name, node->cluster_layer_id, node->state); } void crmd_alert_fencing_op(stonith_event_t * e) { char *desc; lrm_state_t *lrm_state; if (crmd_alert_list == NULL) { return; } lrm_state = controld_get_executor_state(NULL, false); if (lrm_state == NULL) { return; } desc = stonith__event_description(e); lrmd_send_fencing_alert((lrmd_t *) lrm_state->conn, crmd_alert_list, e->target, e->operation, desc, e->result); free(desc); } void crmd_alert_resource_op(const char *node, lrmd_event_data_t * op) { lrm_state_t *lrm_state; if (crmd_alert_list == NULL) { return; } lrm_state = controld_get_executor_state(NULL, false); if (lrm_state == NULL) { return; } lrmd_send_resource_alert((lrmd_t *) lrm_state->conn, crmd_alert_list, node, op); } diff --git a/include/crm/common/alerts_internal.h b/include/crm/common/alerts_internal.h index 0c987a67fa..359fca2086 100644 --- a/include/crm/common/alerts_internal.h +++ b/include/crm/common/alerts_internal.h @@ -1,105 +1,105 @@ /* * Copyright 2015-2024 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #ifndef PCMK__CRM_COMMON_ALERTS_INTERNAL__H #define PCMK__CRM_COMMON_ALERTS_INTERNAL__H #include #include // uint32_t #include // GList, GHashTable #include // xmlNode #ifdef __cplusplus extern "C" { #endif /* Default-Timeout to use before killing a alerts script (in milliseconds) */ #define PCMK__ALERT_DEFAULT_TIMEOUT_MS (30000) /* Default-Format-String used to pass timestamps to the alerts scripts */ #define PCMK__ALERT_DEFAULT_TSTAMP_FORMAT "%H:%M:%S.%06N" enum pcmk__alert_flags { pcmk__alert_none = 0, pcmk__alert_node = (1 << 0), pcmk__alert_fencing = (1 << 1), pcmk__alert_resource = (1 << 2), pcmk__alert_attribute = (1 << 3), pcmk__alert_default = pcmk__alert_node|pcmk__alert_fencing| pcmk__alert_resource, }; typedef struct { char *id; char *path; char *tstamp_format; char *recipient; char **select_attribute_name; GHashTable *envvars; int timeout; uint32_t flags; } pcmk__alert_t; enum pcmk__alert_keys_e { PCMK__alert_key_recipient = 0, PCMK__alert_key_node, PCMK__alert_key_nodeid, PCMK__alert_key_rsc, PCMK__alert_key_task, PCMK__alert_key_interval, PCMK__alert_key_desc, PCMK__alert_key_status, PCMK__alert_key_target_rc, PCMK__alert_key_rc, PCMK__alert_key_kind, PCMK__alert_key_version, PCMK__alert_key_node_sequence, PCMK__alert_key_timestamp, PCMK__alert_key_attribute_name, PCMK__alert_key_attribute_value, PCMK__alert_key_timestamp_epoch, PCMK__alert_key_timestamp_usec, PCMK__alert_key_exec_time, PCMK__alert_key_select_kind, PCMK__alert_key_select_attribute_name }; #define PCMK__ALERT_INTERNAL_KEY_MAX 19 #define PCMK__ALERT_NODE_SEQUENCE "CRM_alert_node_sequence" extern const char *pcmk__alert_keys[PCMK__ALERT_INTERNAL_KEY_MAX]; pcmk__alert_t *pcmk__dup_alert(const pcmk__alert_t *entry); pcmk__alert_t *pcmk__alert_new(const char *id, const char *path); void pcmk__free_alert(pcmk__alert_t *entry); void pcmk__add_alert_key(GHashTable *table, enum pcmk__alert_keys_e name, const char *value); void pcmk__add_alert_key_int(GHashTable *table, enum pcmk__alert_keys_e name, int value); GList *pcmk__unpack_alerts(const xmlNode *alerts); -void pe_free_alert_list(GList *alert_list); +void pcmk__free_alerts(GList *alert_list); static inline const char * pcmk__alert_flag2text(enum pcmk__alert_flags flag) { switch (flag) { case pcmk__alert_node: return "node"; case pcmk__alert_fencing: return "fencing"; case pcmk__alert_resource: return "resource"; case pcmk__alert_attribute: return "attribute"; default: return "unknown"; } } #ifdef __cplusplus } #endif #endif // PCMK__CRM_COMMON_ALERTS_INTERNAL__H diff --git a/lib/common/alerts.c b/lib/common/alerts.c index c3b264c6f4..d9e18f4d09 100644 --- a/lib/common/alerts.c +++ b/lib/common/alerts.c @@ -1,437 +1,437 @@ /* * Copyright 2015-2024 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #include #include #include #include #include #include #include const char *pcmk__alert_keys[PCMK__ALERT_INTERNAL_KEY_MAX] = { [PCMK__alert_key_recipient] = "CRM_alert_recipient", [PCMK__alert_key_node] = "CRM_alert_node", [PCMK__alert_key_nodeid] = "CRM_alert_nodeid", [PCMK__alert_key_rsc] = "CRM_alert_rsc", [PCMK__alert_key_task] = "CRM_alert_task", [PCMK__alert_key_interval] = "CRM_alert_interval", [PCMK__alert_key_desc] = "CRM_alert_desc", [PCMK__alert_key_status] = "CRM_alert_status", [PCMK__alert_key_target_rc] = "CRM_alert_target_rc", [PCMK__alert_key_rc] = "CRM_alert_rc", [PCMK__alert_key_kind] = "CRM_alert_kind", [PCMK__alert_key_version] = "CRM_alert_version", [PCMK__alert_key_node_sequence] = PCMK__ALERT_NODE_SEQUENCE, [PCMK__alert_key_timestamp] = "CRM_alert_timestamp", [PCMK__alert_key_attribute_name] = "CRM_alert_attribute_name", [PCMK__alert_key_attribute_value] = "CRM_alert_attribute_value", [PCMK__alert_key_timestamp_epoch] = "CRM_alert_timestamp_epoch", [PCMK__alert_key_timestamp_usec] = "CRM_alert_timestamp_usec", [PCMK__alert_key_exec_time] = "CRM_alert_exec_time", }; /*! * \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 pcmk__free_alert(). */ pcmk__alert_t * pcmk__alert_new(const char *id, const char *path) { pcmk__alert_t *entry = pcmk__assert_alloc(1, sizeof(pcmk__alert_t)); pcmk__assert((id != NULL) && (path != NULL)); entry->id = pcmk__str_copy(id); entry->path = pcmk__str_copy(path); entry->timeout = PCMK__ALERT_DEFAULT_TIMEOUT_MS; entry->flags = pcmk__alert_default; return entry; } void pcmk__free_alert(pcmk__alert_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); } } /*! * \internal * \brief Duplicate an alert entry * * \param[in] entry Alert entry to duplicate * * \return Duplicate of alert entry */ pcmk__alert_t * pcmk__dup_alert(const pcmk__alert_t *entry) { pcmk__alert_t *new_entry = pcmk__alert_new(entry->id, entry->path); new_entry->timeout = entry->timeout; new_entry->flags = entry->flags; new_entry->envvars = pcmk__str_table_dup(entry->envvars); new_entry->tstamp_format = pcmk__str_copy(entry->tstamp_format); new_entry->recipient = pcmk__str_copy(entry->recipient); if (entry->select_attribute_name) { new_entry->select_attribute_name = g_strdupv(entry->select_attribute_name); } return new_entry; } void pcmk__add_alert_key(GHashTable *table, enum pcmk__alert_keys_e name, const char *value) { pcmk__assert((table != NULL) && (name >= 0) && (name < PCMK__ALERT_INTERNAL_KEY_MAX)); if (value == NULL) { crm_trace("Removing alert key %s", pcmk__alert_keys[name]); g_hash_table_remove(table, pcmk__alert_keys[name]); } else { crm_trace("Inserting alert key %s = '%s'", pcmk__alert_keys[name], value); pcmk__insert_dup(table, pcmk__alert_keys[name], value); } } void pcmk__add_alert_key_int(GHashTable *table, enum pcmk__alert_keys_e name, int value) { pcmk__assert((table != NULL) && (name >= 0) && (name < PCMK__ALERT_INTERNAL_KEY_MAX)); crm_trace("Inserting alert key %s = %d", pcmk__alert_keys[name], value); g_hash_table_insert(table, pcmk__str_copy(pcmk__alert_keys[name]), pcmk__itoa(value)); } #define READABLE_DEFAULT pcmk__readable_interval(PCMK__ALERT_DEFAULT_TIMEOUT_MS) /*! * \internal * \brief Unpack options for an alert or alert recipient from its * meta-attributes in the CIB XML configuration * * \param[in,out] xml Alert or recipient XML * \param[in,out] entry Where to store unpacked values * \param[in,out] max_timeout Max timeout of all alerts and recipients thus far * * \return Standard Pacemaker return code */ static int unpack_alert_options(xmlNode *xml, pcmk__alert_t *entry, guint *max_timeout) { GHashTable *config_hash = pcmk__strkey_table(free, free); crm_time_t *now = crm_time_new(NULL); const char *value = NULL; int rc = pcmk_rc_ok; pcmk_rule_input_t rule_input = { .now = now, }; pcmk_unpack_nvpair_blocks(xml, PCMK_XE_META_ATTRIBUTES, NULL, &rule_input, config_hash, NULL); crm_time_free(now); value = g_hash_table_lookup(config_hash, PCMK_META_ENABLED); if ((value != NULL) && !crm_is_true(value)) { // No need to continue unpacking rc = pcmk_rc_disabled; goto done; } value = g_hash_table_lookup(config_hash, PCMK_META_TIMEOUT); if (value != NULL) { long long timeout_ms = crm_get_msec(value); entry->timeout = (int) QB_MIN(timeout_ms, INT_MAX); if (entry->timeout <= 0) { if (entry->timeout == 0) { crm_trace("Alert %s uses default timeout (%s)", entry->id, READABLE_DEFAULT); } else { pcmk__config_warn("Using default timeout (%s) for alert %s " "because '%s' is not a valid timeout", entry->id, value, READABLE_DEFAULT); } entry->timeout = PCMK__ALERT_DEFAULT_TIMEOUT_MS; } else { crm_trace("Alert %s uses timeout of %s", entry->id, pcmk__readable_interval(entry->timeout)); } if (entry->timeout > *max_timeout) { *max_timeout = entry->timeout; } } value = g_hash_table_lookup(config_hash, PCMK_META_TIMESTAMP_FORMAT); if (value != NULL) { /* hard to do any checks here as merely anything can * can be a valid time-format-string */ entry->tstamp_format = strdup(value); crm_trace("Alert %s uses timestamp format '%s'", entry->id, entry->tstamp_format); } done: g_hash_table_destroy(config_hash); return rc; } /*! * \internal * \brief Unpack agent parameters for an alert or alert recipient into an * environment variable list based on its CIB XML configuration * * \param[in] xml Alert or recipient XML * \param[in,out] entry Alert entry to create environment variables for */ static void unpack_alert_parameters(const xmlNode *xml, pcmk__alert_t *entry) { xmlNode *child; if ((xml == NULL) || (entry == NULL)) { return; } child = pcmk__xe_first_child(xml, PCMK_XE_INSTANCE_ATTRIBUTES, NULL, NULL); if (child == NULL) { return; } if (entry->envvars == NULL) { entry->envvars = pcmk__strkey_table(free, free); } for (child = pcmk__xe_first_child(child, PCMK_XE_NVPAIR, NULL, NULL); child != NULL; child = pcmk__xe_next(child, PCMK_XE_NVPAIR)) { const char *name = crm_element_value(child, PCMK_XA_NAME); const char *value = crm_element_value(child, PCMK_XA_VALUE); if (value == NULL) { value = ""; } pcmk__insert_dup(entry->envvars, name, value); crm_trace("Alert %s: added environment variable %s='%s'", entry->id, name, value); } } /*! * \internal * \brief Create filters for an alert or alert recipient based on its * configuration in CIB XML * * \param[in] xml Alert or recipient XML * \param[in,out] entry Alert entry to create filters for */ static void unpack_alert_filter(xmlNode *xml, pcmk__alert_t *entry) { xmlNode *select = pcmk__xe_first_child(xml, PCMK_XE_SELECT, NULL, NULL); xmlNode *event_type = NULL; uint32_t flags = pcmk__alert_none; for (event_type = pcmk__xe_first_child(select, NULL, NULL, NULL); event_type != NULL; event_type = pcmk__xe_next(event_type, NULL)) { if (pcmk__xe_is(event_type, PCMK_XE_SELECT_FENCING)) { flags |= pcmk__alert_fencing; } else if (pcmk__xe_is(event_type, PCMK_XE_SELECT_NODES)) { flags |= pcmk__alert_node; } else if (pcmk__xe_is(event_type, PCMK_XE_SELECT_RESOURCES)) { flags |= pcmk__alert_resource; } else if (pcmk__xe_is(event_type, PCMK_XE_SELECT_ATTRIBUTES)) { xmlNode *attr; const char *attr_name; int nattrs = 0; flags |= pcmk__alert_attribute; for (attr = pcmk__xe_first_child(event_type, PCMK_XE_ATTRIBUTE, NULL, NULL); attr != NULL; attr = pcmk__xe_next(attr, PCMK_XE_ATTRIBUTE)) { attr_name = crm_element_value(attr, PCMK_XA_NAME); if (attr_name) { if (nattrs == 0) { g_strfreev(entry->select_attribute_name); entry->select_attribute_name = NULL; } ++nattrs; entry->select_attribute_name = pcmk__realloc(entry->select_attribute_name, (nattrs + 1) * sizeof(char*)); entry->select_attribute_name[nattrs - 1] = strdup(attr_name); entry->select_attribute_name[nattrs] = NULL; } } } } if (flags != pcmk__alert_none) { entry->flags = flags; crm_debug("Alert %s receives events: attributes:%s%s%s%s", entry->id, (pcmk_is_set(flags, pcmk__alert_attribute)? (entry->select_attribute_name? "some" : "all") : "none"), (pcmk_is_set(flags, pcmk__alert_fencing)? " fencing" : ""), (pcmk_is_set(flags, pcmk__alert_node)? " nodes" : ""), (pcmk_is_set(flags, pcmk__alert_resource)? " resources" : "")); } } /*! * \internal * \brief Unpack an alert or an alert recipient * * \param[in,out] alert Alert or recipient XML * \param[in,out] entry Where to store unpacked values * \param[in,out] max_timeout Max timeout of all alerts and recipients thus far * * \return Standard Pacemaker return code */ static int unpack_alert(xmlNode *alert, pcmk__alert_t *entry, guint *max_timeout) { int rc = pcmk_rc_ok; unpack_alert_parameters(alert, entry); rc = unpack_alert_options(alert, entry, max_timeout); if (rc == pcmk_rc_ok) { unpack_alert_filter(alert, entry); } return rc; } /*! * \internal * \brief Unpack a CIB alerts section into a list of alert entries * * \param[in] alerts XML of CIB alerts section * * \return List of unpacked alert entries */ GList * pcmk__unpack_alerts(const xmlNode *alerts) { xmlNode *alert; pcmk__alert_t *entry; guint max_timeout = 0U; GList *alert_list = NULL; for (alert = pcmk__xe_first_child(alerts, PCMK_XE_ALERT, NULL, NULL); alert != NULL; alert = pcmk__xe_next(alert, PCMK_XE_ALERT)) { xmlNode *recipient = NULL; int recipients = 0; const char *alert_id = pcmk__xe_id(alert); const char *alert_path = crm_element_value(alert, PCMK_XA_PATH); // Not possible with schema validation enabled if (alert_id == NULL) { pcmk__config_err("Ignoring invalid alert without " PCMK_XA_ID); continue; } if (alert_path == NULL) { pcmk__config_err("Ignoring invalid alert %s without " PCMK_XA_PATH, alert_id); continue; } entry = pcmk__alert_new(alert_id, alert_path); if (unpack_alert(alert, entry, &max_timeout) != pcmk_rc_ok) { // Don't allow recipients to override if entire alert is disabled crm_debug("Alert %s is disabled", entry->id); pcmk__free_alert(entry); continue; } if (entry->tstamp_format == NULL) { entry->tstamp_format = pcmk__str_copy(PCMK__ALERT_DEFAULT_TSTAMP_FORMAT); } crm_debug("Alert %s: path=%s timeout=%s tstamp-format='%s'", entry->id, entry->path, pcmk__readable_interval(entry->timeout), entry->tstamp_format); for (recipient = pcmk__xe_first_child(alert, PCMK_XE_RECIPIENT, NULL, NULL); recipient != NULL; recipient = pcmk__xe_next(recipient, PCMK_XE_RECIPIENT)) { pcmk__alert_t *recipient_entry = pcmk__dup_alert(entry); recipients++; recipient_entry->recipient = crm_element_value_copy(recipient, PCMK_XA_VALUE); if (unpack_alert(recipient, recipient_entry, &max_timeout) != pcmk_rc_ok) { crm_debug("Alert %s: recipient %s is disabled", entry->id, recipient_entry->id); pcmk__free_alert(recipient_entry); continue; } alert_list = g_list_prepend(alert_list, recipient_entry); crm_debug("Alert %s has recipient %s with value %s and %d envvars", entry->id, pcmk__xe_id(recipient), recipient_entry->recipient, (recipient_entry->envvars? g_hash_table_size(recipient_entry->envvars) : 0)); } if (recipients == 0) { alert_list = g_list_prepend(alert_list, entry); } else { // Recipients were prepended individually above pcmk__free_alert(entry); } } return alert_list; } /*! * \internal * \brief Free an alert list generated by pcmk__unpack_alerts() * * \param[in,out] alert_list Alert list to free */ void -pe_free_alert_list(GList *alert_list) +pcmk__free_alerts(GList *alert_list) { - if (alert_list) { + if (alert_list != NULL) { g_list_free_full(alert_list, (GDestroyNotify) pcmk__free_alert); } }