diff --git a/daemons/attrd/attrd_attributes.c b/daemons/attrd/attrd_attributes.c index fdc238375e7..a18076f7a37 100644 --- a/daemons/attrd/attrd_attributes.c +++ b/daemons/attrd/attrd_attributes.c @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the Pacemaker project contributors + * Copyright 2013-2025 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -282,3 +282,195 @@ attrd_nvpair_id(const attribute_t *attr, const char *node_state_id) pcmk__xml_sanitize_id(nvpair_id); return nvpair_id; } + +/*! + * \internal + * \brief Check whether an attribute is one that must be written to the CIB + * + * \param[in] a Attribute to check + * + * \return false if we are in standalone mode or \p a is private, otherwise true + */ +bool +attrd_for_cib(const attribute_t *a) +{ + return !stand_alone && (a != NULL) + && !pcmk_is_set(a->flags, attrd_attr_is_private); +} + +/*! + * \internal + * \brief Drop NULL attribute values as indicated by given function + * + * Drop all NULL node attribute values that a given function indicates should + * be, based on the XML ID of an element that was removed from the CIB. + * + * \param[in] cib_id ID of XML element that was removed from CIB + * (a name/value pair, an attribute set, or a node state) + * \param[in] set_type If not NULL, drop only attributes with this set type + * \param[in] func Call this function for every attribute/value + * combination; keep the value if it returns 0, drop + * the value and keep checking the attribute's other + * values if it returns 1, or drop value and stop checking + * the attribute's further values if it returns 2 + */ +static void +drop_removed_values(const char *cib_id, const char *set_type, + int (*func)(const attribute_t *, const attribute_value_t *, + const char *)) +{ + attribute_t *a = NULL; + GHashTableIter attr_iter; + const char *entry_type = pcmk__s(set_type, "status entry"); // for log + + CRM_CHECK((cib_id != NULL) && (func != NULL), return); + + // Check every attribute ... + g_hash_table_iter_init(&attr_iter, attributes); + while (g_hash_table_iter_next(&attr_iter, NULL, (gpointer *) &a)) { + attribute_value_t *v = NULL; + GHashTableIter value_iter; + + if (!attrd_for_cib(a) + || ((set_type != NULL) + && !pcmk__str_eq(a->set_type, set_type, pcmk__str_none))) { + continue; + } + + // Check every value of the attribute ... + g_hash_table_iter_init(&value_iter, a->values); + while (g_hash_table_iter_next(&value_iter, NULL, (gpointer *) &v)) { + int rc = 0; + + if (v->current != NULL) { + continue; + } + + if (attrd_get_node_xml_id(v->nodename) == NULL) { + /* This shouldn't be a significant issue, since we will know the + * XML ID if *any* attribute for the node has ever been written. + */ + crm_trace("Ignoring %s[%s] after CIB erasure of %s %s because " + "its node XML ID is unknown (possibly attribute was " + "never written to CIB)", + a->id, v->nodename, entry_type, cib_id); + continue; + } + + rc = func(a, v, cib_id); + if (rc > 0) { + crm_debug("Dropping %s[%s] after CIB erasure of %s %s", + a->id, v->nodename, entry_type, cib_id); + g_hash_table_iter_remove(&value_iter); + if (rc > 1) { + return; + } + } + } + } +} + +/*! + * \internal + * \brief Check whether an attribute value has a given XML ID + * + * \param[in] a Attribute being checked + * \param[in] v Attribute value being checked + * \param[in] cib_id ID of name/value pair element that was removed from CIB + * + * \return 2 if value matches XML ID, otherwise 0 + */ +static int +nvpair_matches(const attribute_t *a, const attribute_value_t *v, + const char *cib_id) +{ + char *id = attrd_nvpair_id(a, attrd_get_node_xml_id(v->nodename)); + + /* The attribute manager doesn't enforce uniqueness for value XML IDs + * (schema validation could be disabled), but in practice they should be, + * so we can stop looping if we find a match. + */ + int rc = pcmk__str_eq(id, cib_id, pcmk__str_none)? 2 : 0; + + free(id); + return rc; +} + +/*! + * \internal + * \brief Drop attribute value corresponding to given removed CIB entry + * + * \param[in] cib_id ID of name/value pair element that was removed from CIB + */ +void +attrd_drop_removed_value(const char *cib_id) +{ + drop_removed_values(cib_id, NULL, nvpair_matches); +} + +/*! + * \internal + * \brief Check whether an attribute value has a given attribute set ID + * + * \param[in] a Attribute being checked + * \param[in] v Attribute value being checked + * \param[in] cib_id ID of attribute set that was removed from CIB + * + * \return 1 if value matches XML ID, otherwise 0 + */ +static int +set_id_matches(const attribute_t *a, const attribute_value_t *v, + const char *cib_id) +{ + char *id = attrd_set_id(a, attrd_get_node_xml_id(v->nodename)); + int rc = pcmk__str_eq(id, cib_id, pcmk__str_none)? 1: 0; + + free(id); + return rc; +} + +/*! + * \internal + * \brief Drop all removed attribute values for an attribute set + * + * \param[in] set_type XML element name of set that was removed + * \param[in] cib_id ID of attribute set that was removed from CIB + */ +void +attrd_drop_removed_set(const char *set_type, const char *cib_id) +{ + drop_removed_values(cib_id, set_type, set_id_matches); +} + +/*! + * \internal + * \brief Check whether an attribute value has a given node state XML ID + * + * \param[in] a Attribute being checked + * \param[in] v Attribute value being checked + * \param[in] cib_id ID of node state that was removed from CIB + * + * \return 1 if value matches removed ID, otherwise 0 + */ +static int +node_matches(const attribute_t *a, const attribute_value_t *v, + const char *cib_id) +{ + if (pcmk__str_eq(cib_id, attrd_get_node_xml_id(v->nodename), + pcmk__str_none)) { + return 1; + } + return 0; +} + +/*! + * \internal + * \brief Drop all removed attribute values for a node + * + * \param[in] cib_id ID of node state that was removed from CIB + */ +void +attrd_drop_removed_values(const char *cib_id) +{ + drop_removed_values(cib_id, NULL, node_matches); +} diff --git a/daemons/attrd/attrd_cib.c b/daemons/attrd/attrd_cib.c index 4231e4a668a..588e7793483 100644 --- a/daemons/attrd/attrd_cib.c +++ b/daemons/attrd/attrd_cib.c @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the Pacemaker project contributors + * Copyright 2013-2025 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -9,7 +9,10 @@ #include <crm_internal.h> +#include <sys/types.h> // for regex.h #include <errno.h> +#include <string.h> // strndup() +#include <regex.h> // regcomp(), regexec(), regex_t, regmatch_t, regoff_t #include <stdbool.h> #include <stdlib.h> #include <glib.h> @@ -34,7 +37,7 @@ attrd_cib_destroy_cb(gpointer user_data) cib->cmds->signoff(cib); - if (attrd_shutting_down(false)) { + if (attrd_shutting_down()) { crm_info("Disconnected from the CIB manager"); } else { @@ -45,6 +48,141 @@ attrd_cib_destroy_cb(gpointer user_data) } } +/* In a CIB patchset, deletions have the XPath to the deleted element, like: + * + * /cib/status/node_state[@id='X'] + * + * This regular expression checks whether a node state was deleted, or transient + * attributes beneath that, or an attribute set beneath that, or a name/value + * pair beneath that. + */ +#define ID_REGEX "\\[@" PCMK_XA_ID "='([^']+)'\\]" +#define DELETION_REGEX "/" PCMK_XE_CIB "/" PCMK_XE_STATUS \ + "/" PCMK__XE_NODE_STATE ID_REGEX \ + "(/" PCMK__XE_TRANSIENT_ATTRIBUTES ID_REGEX ")?" \ + "(/([^[/]+)" ID_REGEX ")?" "(/" PCMK_XE_NVPAIR ID_REGEX ")?$" + +// Number of parenthesized submatches in DELETION_REGEX plus 1 for entire match +#define DELETION_NMATCH 9 + +/*! + * \internal + * \brief Duplicate a regular expression submatch + * + * \param[in] string String being matched against a regular expression + * \param[in] matches Submatches, as determined by regexec() + * \param[in] submatch Desired index into \p matches + * + * \return Newly allocated string with desired submatch + * \note This asserts on allocation failure, so the result is guaranteed to be + * non-NULL. + */ +static char * +re_submatch(const char *string, regmatch_t matches[], size_t submatch) +{ + const regoff_t start = matches[submatch].rm_so; + char *match = strndup(string + start, matches[submatch].rm_eo - start); + + pcmk__mem_assert(match); + return match; +} + +/*! + * \internal + * \brief Check a patchset change for deletion of node attribute values + * + * \param[in] xml Patchset change element + * \param[in] data Ignored + * + * \return pcmk_rc_ok (to always continue to next patchset change) + */ +static int +drop_values_in_deletion(xmlNode *xml, void *data) +{ + const char *value = NULL; + char *id = NULL; + regmatch_t matches[DELETION_NMATCH]; + + static regex_t re; + static bool re_compiled = false; + + // Skip this change if it does not look like a deletion + value = crm_element_value(xml, PCMK_XA_OPERATION); + if (!pcmk__str_eq(value, "delete", pcmk__str_none)) { + return pcmk_rc_ok; + } + value = crm_element_value(xml, PCMK_XA_PATH); + if (value == NULL) { + crm_warn("Ignoring malformed deletion in " + "CIB change notification: No " PCMK_XA_PATH); + return pcmk_rc_ok; + } + + // Check whether deleted XPath could contain node attribute values + if (!re_compiled) { + CRM_CHECK(regcomp(&re, DELETION_REGEX, REG_EXTENDED) == 0, + return pcmk_rc_ok); + re_compiled = true; + } + if (regexec(&re, value, DELETION_NMATCH, matches, 0) != 0) { + return pcmk_rc_ok; // This is not an attribute deletion + } + + /* matches[0] = entire node state match + * matches[1] = node state ID + * + * Optional: + * matches[2] = transient attributes element with ID + * matches[3] = transient attributes ID + * matches[4] = attribute set element with ID + * matches[5] = attribute set element name + * matches[6] = attribute set ID + * matches[7] = name/value pair element with ID + * matches[8] = name/value pair ID + */ + + // If we get here, we must have matched at least node state and its ID + CRM_CHECK((matches[0].rm_so == 0) && (matches[1].rm_so > 0), + return pcmk_rc_ok); + + /* Check whether all of node's attributes were deleted (if no matches[2], + * entire node state was deleted; if no matches[4], entire + * transient attributes section was deleted) + */ + if ((matches[2].rm_so < 0) || (matches[4].rm_so < 0)) { + id = re_submatch(value, matches, 1); + attrd_drop_removed_values(id); + free(id); + return pcmk_rc_ok; + } + + /* We matched transient attributes, so we must have its ID, as well as an + * attribute set, so we must have its element name and ID + */ + CRM_CHECK((matches[3].rm_so > 0) && (matches[5].rm_so > 0) + && (matches[6].rm_so > 0), return pcmk_rc_ok); + + // Check whether the entire set was deleted + if (matches[7].rm_so < 0) { + char *set_type = re_submatch(value, matches, 5); + + id = re_submatch(value, matches, 6); + attrd_drop_removed_set(set_type, id); + free(id); + free(set_type); + return pcmk_rc_ok; + } + + // We matched a single name/value pair, so we must have its ID + CRM_CHECK(matches[8].rm_so > 0, return pcmk_rc_ok); + + // Drop the one value + id = re_submatch(value, matches, 8); + attrd_drop_removed_value(id); + free(id); + return pcmk_rc_ok; +} + static void attrd_cib_updated_cb(const char *event, xmlNode *msg) { @@ -57,7 +195,7 @@ attrd_cib_updated_cb(const char *event, xmlNode *msg) } if (pcmk__cib_element_in_patchset(patchset, PCMK_XE_ALERTS)) { - if (attrd_shutting_down(true)) { + if (attrd_shutting_down()) { crm_debug("Ignoring alerts change in CIB during shutdown"); } else { mainloop_set_trigger(attrd_config_read); @@ -70,7 +208,21 @@ attrd_cib_updated_cb(const char *event, xmlNode *msg) if (!cib__client_triggers_refresh(client_name)) { /* This change came from a source that ensured the CIB is consistent * with our attributes table, so we don't need to write anything out. + * If a removed attribute has been erased, we can forget it now. */ + int format = 1; + + if ((crm_element_value_int(patchset, PCMK_XA_FORMAT, &format) != 0) + || (format != 2)) { + crm_warn("Can't handle CIB patch format %d", format); + return; + } + + /* This won't modify patchset, but we need to break const to match the + * function signature. + */ + pcmk__xe_foreach_child((xmlNode *) patchset, PCMK_XE_CHANGE, + drop_values_in_deletion, NULL); return; } @@ -82,7 +234,7 @@ attrd_cib_updated_cb(const char *event, xmlNode *msg) if (status_changed || pcmk__cib_element_in_patchset(patchset, PCMK_XE_NODES)) { - if (attrd_shutting_down(true)) { + if (attrd_shutting_down()) { crm_debug("Ignoring node change in CIB during shutdown"); return; } @@ -216,8 +368,8 @@ void attrd_cib_init(void) { /* We have no attribute values in memory, so wipe the CIB to match. This is - * normally done by the DC's controller when this node leaves the cluster, but - * this handles the case where the node restarted so quickly that the + * normally done by the writer when this node leaves the cluster, but this + * handles the case where the node restarted so quickly that the * cluster layer didn't notice. * * \todo If the attribute manager respawns after crashing (see @@ -494,18 +646,12 @@ write_attribute(attribute_t *a, bool ignore_delay) GHashTableIter iter; GHashTable *alert_attribute_value = NULL; int rc = pcmk_ok; - bool should_write = true; + bool should_write = attrd_for_cib(a); if (a == NULL) { return; } - // Private attributes (or any in standalone mode) are not written to the CIB - if (stand_alone || pcmk_is_set(a->flags, attrd_attr_is_private)) { - should_write = false; - } - - /* If this attribute will be written to the CIB ... */ if (should_write) { /* Defer the write if now's not a good time */ if (a->update && (a->update < last_cib_op_done)) { @@ -557,6 +703,7 @@ write_attribute(attribute_t *a, bool ignore_delay) while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &v)) { const char *node_xml_id = NULL; const char *prev_xml_id = NULL; + pcmk__node_status_t *peer = NULL; if (!should_write) { private_updates++; @@ -575,12 +722,23 @@ write_attribute(attribute_t *a, bool ignore_delay) // A Pacemaker Remote node's XML ID is the same as its name node_xml_id = v->nodename; + } else if (v->current == NULL) { + /* If a value was removed, check the caches for the node XML ID, + * but don't create a new cache entry. We don't want to re-create a + * purged node. + */ + peer = pcmk__search_node_caches(0, v->nodename, prev_xml_id, + pcmk__node_search_any + |pcmk__node_search_cluster_cib); + node_xml_id = pcmk__cluster_get_xml_id(peer); + if (node_xml_id == NULL) { + node_xml_id = prev_xml_id; + } + } else { // This creates a cluster node cache entry if none exists - pcmk__node_status_t *peer = pcmk__get_node(0, v->nodename, - prev_xml_id, - pcmk__node_search_any); - + peer = pcmk__get_node(0, v->nodename, prev_xml_id, + pcmk__node_search_any); node_xml_id = pcmk__cluster_get_xml_id(peer); if (node_xml_id == NULL) { node_xml_id = prev_xml_id; @@ -608,8 +766,8 @@ write_attribute(attribute_t *a, bool ignore_delay) if (rc != pcmk_rc_ok) { crm_err("Couldn't add %s[%s]='%s' to CIB transaction: %s " QB_XS " node XML ID %s", - a->id, v->nodename, v->current, pcmk_rc_str(rc), - node_xml_id); + a->id, v->nodename, pcmk__s(v->current, "(unset)"), + pcmk_rc_str(rc), node_xml_id); continue; } @@ -645,6 +803,7 @@ write_attribute(attribute_t *a, bool ignore_delay) "attrd_cib_callback", attrd_cib_callback, free)) { // Transmit alert of the attribute + // @TODO Do this in callback only if write was successful send_alert_attributes_value(a, alert_attribute_value); } } diff --git a/daemons/attrd/attrd_corosync.c b/daemons/attrd/attrd_corosync.c index e97e09cb865..8d52ab8c8a3 100644 --- a/daemons/attrd/attrd_corosync.c +++ b/daemons/attrd/attrd_corosync.c @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the Pacemaker project contributors + * Copyright 2013-2025 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -46,7 +46,7 @@ attrd_peer_message(pcmk__node_status_t *peer, xmlNode *xml) return; } - if (attrd_shutting_down(false)) { + if (attrd_shutting_down()) { /* If we're shutting down, we want to continue responding to election * ops as long as we're a cluster member (because our vote may be * needed). Ignore all other messages. @@ -129,7 +129,7 @@ attrd_cpg_dispatch(cpg_handle_t handle, static void attrd_cpg_destroy(gpointer unused) { - if (attrd_shutting_down(false)) { + if (attrd_shutting_down()) { crm_info("Disconnected from Corosync process group"); } else { @@ -276,17 +276,6 @@ update_attr_on_host(attribute_t *a, const pcmk__node_status_t *peer, pcmk__str_update(&v->current, value); attrd_set_attr_flags(a, attrd_attr_changed); - if (pcmk__str_eq(host, attrd_cluster->priv->node_name, pcmk__str_casei) - && pcmk__str_eq(attr, PCMK__NODE_ATTR_SHUTDOWN, pcmk__str_none)) { - - if (!pcmk__str_eq(value, "0", pcmk__str_null_matches)) { - attrd_set_requesting_shutdown(); - - } else { - attrd_clear_requesting_shutdown(); - } - } - // Write out new value or start dampening timer if (a->timeout_ms && a->timer) { crm_trace("Delaying write of %s %s for dampening", @@ -534,12 +523,35 @@ attrd_peer_remove(const char *host, bool uncache, const char *source) host, source, (uncache? "and" : "without")); g_hash_table_iter_init(&aIter, attributes); - while (g_hash_table_iter_next(&aIter, NULL, (gpointer *) & a)) { - if(g_hash_table_remove(a->values, host)) { - crm_debug("Removed %s[%s] for peer %s", a->id, host, source); + while (g_hash_table_iter_next(&aIter, NULL, (gpointer *) &a)) { + /* If the attribute won't be written to the CIB, we can drop the value + * now. Otherwise we need to set it NULL and wait for a notification + * that it was erased, because if there's no writer or the current + * writer fails to write it then leaves, we may become the writer and + * need to do it. + */ + if (attrd_for_cib(a)) { + attribute_value_t *v = g_hash_table_lookup(a->values, host); + + if ((v != NULL) && (v->current != NULL)) { + crm_debug("Removed %s[%s] (by setting NULL) for %s", + a->id, host, source); + pcmk__str_update(&(v->current), NULL); + attrd_set_attr_flags(a, attrd_attr_changed); + } + } else if (g_hash_table_remove(a->values, host)) { + crm_debug("Removed %s[%s] immediately for %s", + a->id, host, source); } } + if (attrd_election_won()) { + attrd_cib_erase_transient_attrs(host); // Wipe from CIB + } else { + attrd_start_election_if_needed(); // Make sure CIB gets updated + } + + // Remove node from caches if requested if (uncache) { pcmk__purge_node_from_cache(host, 0); attrd_forget_node_xml_id(host); diff --git a/daemons/attrd/attrd_elections.c b/daemons/attrd/attrd_elections.c index 281ec12c2fd..1a79a93df21 100644 --- a/daemons/attrd/attrd_elections.c +++ b/daemons/attrd/attrd_elections.c @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the Pacemaker project contributors + * Copyright 2013-2025 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -41,7 +41,7 @@ attrd_start_election_if_needed(void) { if ((peer_writer == NULL) && (election_state(attrd_cluster) != election_in_progress) - && !attrd_shutting_down(false)) { + && !attrd_shutting_down()) { crm_info("Starting an election to determine the writer"); election_vote(attrd_cluster); @@ -63,7 +63,7 @@ attrd_handle_election_op(const pcmk__node_status_t *peer, xmlNode *xml) crm_xml_add(xml, PCMK__XA_SRC, peer->name); // Don't become writer if we're shutting down - rc = election_count_vote(attrd_cluster, xml, !attrd_shutting_down(false)); + rc = election_count_vote(attrd_cluster, xml, !attrd_shutting_down()); switch(rc) { case election_start: diff --git a/daemons/attrd/attrd_ipc.c b/daemons/attrd/attrd_ipc.c index fd917a37bb5..e030c125e7c 100644 --- a/daemons/attrd/attrd_ipc.c +++ b/daemons/attrd/attrd_ipc.c @@ -1,5 +1,5 @@ /* - * Copyright 2004-2024 the Pacemaker project contributors + * Copyright 2004-2025 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -492,7 +492,7 @@ static int32_t attrd_ipc_accept(qb_ipcs_connection_t *c, uid_t uid, gid_t gid) { crm_trace("New client connection %p", c); - if (attrd_shutting_down(false)) { + if (attrd_shutting_down()) { crm_info("Ignoring new connection from pid %d during shutdown", pcmk__client_pid(c)); return -ECONNREFUSED; diff --git a/daemons/attrd/attrd_utils.c b/daemons/attrd/attrd_utils.c index f219b8862d3..f6c6f0ac52a 100644 --- a/daemons/attrd/attrd_utils.c +++ b/daemons/attrd/attrd_utils.c @@ -1,5 +1,5 @@ /* - * Copyright 2004-2024 the Pacemaker project contributors + * Copyright 2004-2025 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -25,7 +25,6 @@ cib_t *the_cib = NULL; -static bool requesting_shutdown = false; static bool shutting_down = false; static GMainLoop *mloop = NULL; @@ -34,45 +33,17 @@ static GMainLoop *mloop = NULL; */ GHashTable *peer_protocol_vers = NULL; -/*! - * \internal - * \brief Set requesting_shutdown state - */ -void -attrd_set_requesting_shutdown(void) -{ - requesting_shutdown = true; -} - -/*! - * \internal - * \brief Clear requesting_shutdown state - */ -void -attrd_clear_requesting_shutdown(void) -{ - requesting_shutdown = false; -} - /*! * \internal * \brief Check whether local attribute manager is shutting down * - * \param[in] if_requested If \c true, also consider presence of - * \c PCMK__NODE_ATTR_SHUTDOWN attribute - * - * \return \c true if local attribute manager has begun shutdown sequence - * or (if \p if_requested is \c true) whether local node has a nonzero - * \c PCMK__NODE_ATTR_SHUTDOWN attribute set, otherwise \c false - * \note Most callers should pass \c false for \p if_requested, because the - * attribute manager needs to continue performing while the controller is - * shutting down, and even needs to be eligible for election in case all - * nodes are shutting down. + * \return \c true if local attribute manager has begun shutdown sequence, + * otherwise \c false */ bool -attrd_shutting_down(bool if_requested) +attrd_shutting_down(void) { - return shutting_down || (if_requested && requesting_shutdown); + return shutting_down; } /*! diff --git a/daemons/attrd/pacemaker-attrd.h b/daemons/attrd/pacemaker-attrd.h index cc0dcf29ee4..193964e41cb 100644 --- a/daemons/attrd/pacemaker-attrd.h +++ b/daemons/attrd/pacemaker-attrd.h @@ -56,10 +56,8 @@ void attrd_init_mainloop(void); void attrd_run_mainloop(void); -void attrd_set_requesting_shutdown(void); -void attrd_clear_requesting_shutdown(void); void attrd_free_waitlist(void); -bool attrd_shutting_down(bool if_requested); +bool attrd_shutting_down(void); void attrd_shutdown(int nsig); void attrd_init_ipc(void); void attrd_ipc_fini(void); @@ -211,6 +209,10 @@ void attrd_free_attribute_value(gpointer data); attribute_t *attrd_populate_attribute(xmlNode *xml, const char *attr); char *attrd_set_id(const attribute_t *attr, const char *node_state_id); char *attrd_nvpair_id(const attribute_t *attr, const char *node_state_id); +bool attrd_for_cib(const attribute_t *a); +void attrd_drop_removed_value(const char *cib_id); +void attrd_drop_removed_set(const char *set_type, const char *cib_id); +void attrd_drop_removed_values(const char *cib_id); enum attrd_write_options { attrd_write_changed = 0, diff --git a/daemons/controld/controld_attrd.c b/daemons/controld/controld_attrd.c index eff8070818d..32fcbe2568d 100644 --- a/daemons/controld/controld_attrd.c +++ b/daemons/controld/controld_attrd.c @@ -1,5 +1,5 @@ /* - * Copyright 2006-2024 the Pacemaker project contributors + * Copyright 2006-2025 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -106,8 +106,15 @@ update_attrd_list(GList *attrs, uint32_t opts) } } +/*! + * \internal + * \brief Ask attribute manager to purge a node and its transient attributes + * + * \param[in] node_name Node to purge + * \param[in] from_cache If true, purge from node caches as well + */ void -update_attrd_remote_node_removed(const char *host, const char *user_name) +controld_purge_node_attrs(const char *node_name, bool from_cache) { int rc = pcmk_rc_ok; @@ -115,14 +122,14 @@ update_attrd_remote_node_removed(const char *host, const char *user_name) rc = pcmk_new_ipc_api(&attrd_api, pcmk_ipc_attrd); } if (rc == pcmk_rc_ok) { - crm_trace("Asking attribute manager to purge Pacemaker Remote node %s", - host); - rc = pcmk__attrd_api_purge(attrd_api, host, true); + crm_debug("Asking %s to purge transient attributes%s for %s ", + pcmk_ipc_name(attrd_api, true), + (from_cache? " and node cache" : ""), node_name); + rc = pcmk__attrd_api_purge(attrd_api, node_name, from_cache); } if (rc != pcmk_rc_ok) { - crm_err("Could not purge Pacemaker Remote node %s " - "in attribute manager%s: %s " QB_XS " rc=%d", - host, when(), pcmk_rc_str(rc), rc); + crm_err("Could not purge node %s from attribute manager%s: %s " + QB_XS " rc=%d", node_name, when(), pcmk_rc_str(rc), rc); } } diff --git a/daemons/controld/controld_callbacks.c b/daemons/controld/controld_callbacks.c index fd70d85dcc1..3a326ef53db 100644 --- a/daemons/controld/controld_callbacks.c +++ b/daemons/controld/controld_callbacks.c @@ -1,5 +1,5 @@ /* - * Copyright 2004-2024 the Pacemaker project contributors + * Copyright 2004-2025 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -232,20 +232,10 @@ peer_update_callback(enum pcmk__node_update type, pcmk__node_status_t *node, pcmk__str_casei) && !pcmk__cluster_is_node_active(node)) { - /* The DC has left, so delete its transient attributes and - * trigger a new election. - * - * A DC sends its shutdown request to all peers, who update the - * DC's expected state to down. This avoids fencing upon - * deletion of its transient attributes. - */ + // The DC has left, so trigger a new election crm_notice("Our peer on the DC (%s) is dead", controld_globals.dc_name); - register_fsa_input(C_CRMD_STATUS_CALLBACK, I_ELECTION, NULL); - controld_delete_node_state(node->name, controld_section_attrs, - cib_none); - } else if (AM_I_DC || pcmk_is_set(controld_globals.flags, controld_dc_left) || (controld_globals.dc_name == NULL)) { @@ -255,10 +245,6 @@ peer_update_callback(enum pcmk__node_update type, pcmk__node_status_t *node, */ if (appeared) { te_trigger_stonith_history_sync(FALSE); - } else { - controld_delete_node_state(node->name, - controld_section_attrs, - cib_none); } } break; diff --git a/daemons/controld/controld_cib.c b/daemons/controld/controld_cib.c index 122e5d81446..6032e120d3e 100644 --- a/daemons/controld/controld_cib.c +++ b/daemons/controld/controld_cib.c @@ -1,5 +1,5 @@ /* - * Copyright 2004-2024 the Pacemaker project contributors + * Copyright 2004-2025 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -278,30 +278,18 @@ cib_delete_callback(xmlNode *msg, int call_id, int rc, xmlNode *output, "[not(@" PCMK_OPT_SHUTDOWN_LOCK ") " \ "or " PCMK_OPT_SHUTDOWN_LOCK "<%lld]" -// Node's PCMK__XE_TRANSIENT_ATTRIBUTES section (name 1x) -#define XPATH_NODE_ATTRS XPATH_NODE_STATE "/" PCMK__XE_TRANSIENT_ATTRIBUTES - -// Everything under PCMK__XE_NODE_STATE (name 1x) -#define XPATH_NODE_ALL XPATH_NODE_STATE "/*" - -/* Unlocked history + transient attributes - * (name 2x, (seconds_since_epoch - PCMK_OPT_SHUTDOWN_LOCK_LIMIT) 1x, name 1x) - */ -#define XPATH_NODE_ALL_UNLOCKED XPATH_NODE_LRM_UNLOCKED "|" XPATH_NODE_ATTRS - /*! * \internal - * \brief Get the XPath and description of a node state section to be deleted + * \brief Get the XPath and description of resource history to be deleted * - * \param[in] uname Desired node - * \param[in] section Subsection of \c PCMK__XE_NODE_STATE to be deleted - * \param[out] xpath Where to store XPath of \p section - * \param[out] desc If not \c NULL, where to store description of \p section + * \param[in] uname Name of node to delete resource history for + * \param[in] unlocked_only If true, delete history of only unlocked resources + * \param[out] xpath Where to store XPath for history deletion + * \param[out] desc If not NULL, where to store loggable description */ void -controld_node_state_deletion_strings(const char *uname, - enum controld_section_e section, - char **xpath, char **desc) +controld_node_history_deletion_strings(const char *uname, bool unlocked_only, + char **xpath, char **desc) { const char *desc_pre = NULL; @@ -309,33 +297,13 @@ controld_node_state_deletion_strings(const char *uname, long long expire = (long long) time(NULL) - controld_globals.shutdown_lock_limit; - switch (section) { - case controld_section_lrm: - *xpath = crm_strdup_printf(XPATH_NODE_LRM, uname); - desc_pre = "resource history"; - break; - case controld_section_lrm_unlocked: - *xpath = crm_strdup_printf(XPATH_NODE_LRM_UNLOCKED, - uname, uname, expire); - desc_pre = "resource history (other than shutdown locks)"; - break; - case controld_section_attrs: - *xpath = crm_strdup_printf(XPATH_NODE_ATTRS, uname); - desc_pre = "transient attributes"; - break; - case controld_section_all: - *xpath = crm_strdup_printf(XPATH_NODE_ALL, uname); - desc_pre = "all state"; - break; - case controld_section_all_unlocked: - *xpath = crm_strdup_printf(XPATH_NODE_ALL_UNLOCKED, - uname, uname, expire, uname); - desc_pre = "all state (other than shutdown locks)"; - break; - default: - // We called this function incorrectly - pcmk__assert(false); - break; + if (unlocked_only) { + *xpath = crm_strdup_printf(XPATH_NODE_LRM_UNLOCKED, + uname, uname, expire); + desc_pre = "resource history (other than shutdown locks)"; + } else { + *xpath = crm_strdup_printf(XPATH_NODE_LRM, uname); + desc_pre = "resource history"; } if (desc != NULL) { @@ -343,17 +311,17 @@ controld_node_state_deletion_strings(const char *uname, } } + /*! * \internal - * \brief Delete subsection of a node's CIB \c PCMK__XE_NODE_STATE + * \brief Delete a node's resource history from the CIB * - * \param[in] uname Desired node - * \param[in] section Subsection of \c PCMK__XE_NODE_STATE to delete - * \param[in] options CIB call options to use + * \param[in] uname Desired node + * \param[in] unlocked_only If true, delete history of only unlocked resources + * \param[in] options CIB call options to use */ void -controld_delete_node_state(const char *uname, enum controld_section_e section, - int options) +controld_delete_node_history(const char *uname, bool unlocked_only, int options) { cib_t *cib = controld_globals.cib_conn; char *xpath = NULL; @@ -362,8 +330,7 @@ controld_delete_node_state(const char *uname, enum controld_section_e section, pcmk__assert((uname != NULL) && (cib != NULL)); - controld_node_state_deletion_strings(uname, section, &xpath, &desc); - + controld_node_history_deletion_strings(uname, unlocked_only, &xpath, &desc); cib__set_call_options(options, "node state deletion", cib_xpath|cib_multiple); cib_rc = cib->cmds->remove(cib, xpath, NULL, options); diff --git a/daemons/controld/controld_cib.h b/daemons/controld/controld_cib.h index b8622d52280..116db64924f 100644 --- a/daemons/controld/controld_cib.h +++ b/daemons/controld/controld_cib.h @@ -1,5 +1,5 @@ /* - * Copyright 2004-2024 the Pacemaker project contributors + * Copyright 2004-2025 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -46,20 +46,11 @@ int controld_update_cib(const char *section, xmlNode *data, int options, void *)); unsigned int cib_op_timeout(void); -// Subsections of PCMK__XE_NODE_STATE -enum controld_section_e { - controld_section_lrm, - controld_section_lrm_unlocked, - controld_section_attrs, - controld_section_all, - controld_section_all_unlocked -}; - -void controld_node_state_deletion_strings(const char *uname, - enum controld_section_e section, - char **xpath, char **desc); -void controld_delete_node_state(const char *uname, - enum controld_section_e section, int options); +void controld_node_history_deletion_strings(const char *uname, + bool unlocked_only, + char **xpath, char **desc); +void controld_delete_node_history(const char *uname, bool unlocked_only, + int options); int controld_delete_resource_history(const char *rsc_id, const char *node, const char *user_name, int call_options); diff --git a/daemons/controld/controld_execd.c b/daemons/controld/controld_execd.c index f16b1c61664..8e97e77204d 100644 --- a/daemons/controld/controld_execd.c +++ b/daemons/controld/controld_execd.c @@ -1,5 +1,5 @@ /* - * Copyright 2004-2024 the Pacemaker project contributors + * Copyright 2004-2025 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -1073,8 +1073,7 @@ force_reprobe(lrm_state_t *lrm_state, const char *from_sys, } /* Now delete the copy in the CIB */ - controld_delete_node_state(lrm_state->node_name, controld_section_lrm, - cib_none); + controld_delete_node_history(lrm_state->node_name, false, cib_none); } /*! diff --git a/daemons/controld/controld_fencing.c b/daemons/controld/controld_fencing.c index 49d1142cb36..9b0678fc79f 100644 --- a/daemons/controld/controld_fencing.c +++ b/daemons/controld/controld_fencing.c @@ -1,5 +1,5 @@ /* - * Copyright 2004-2024 the Pacemaker project contributors + * Copyright 2004-2025 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -266,7 +266,11 @@ update_node_state_after_fencing(const char *target, const char *target_xml_id) crm_debug("Updating node state for %s after fencing (call %d)", target, rc); fsa_register_cib_callback(rc, pcmk__str_copy(target), cib_fencing_updated); - controld_delete_node_state(peer->name, controld_section_all, cib_none); + // Delete node's resource history from CIB + controld_delete_node_history(peer->name, false, cib_none); + + // Ask attribute manager to delete node's transient attributes + controld_purge_node_attrs(peer->name, false); } /*! diff --git a/daemons/controld/controld_join_dc.c b/daemons/controld/controld_join_dc.c index 9960966c6ba..52b8bd59ac9 100644 --- a/daemons/controld/controld_join_dc.c +++ b/daemons/controld/controld_join_dc.c @@ -1,5 +1,5 @@ /* - * Copyright 2004-2024 the Pacemaker project contributors + * Copyright 2004-2025 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -770,7 +770,8 @@ do_dc_join_ack(long long action, pcmk__node_status_t *peer = NULL; enum controld_join_phase phase = controld_join_none; - enum controld_section_e section = controld_section_lrm; + const bool unlocked_only = pcmk_is_set(controld_globals.flags, + controld_shutdown_lock_enabled); char *xpath = NULL; xmlNode *state = join_ack->xml; xmlNode *execd_state = NULL; @@ -831,10 +832,8 @@ do_dc_join_ack(long long action, } // Delete relevant parts of node's current executor state from CIB - if (pcmk_is_set(controld_globals.flags, controld_shutdown_lock_enabled)) { - section = controld_section_lrm_unlocked; - } - controld_node_state_deletion_strings(join_from, section, &xpath, NULL); + controld_node_history_deletion_strings(join_from, unlocked_only, &xpath, + NULL); rc = cib->cmds->remove(cib, xpath, NULL, cib_xpath|cib_multiple|cib_transaction); diff --git a/daemons/controld/controld_remote_ra.c b/daemons/controld/controld_remote_ra.c index 5ff48e5b14d..5ef4f809888 100644 --- a/daemons/controld/controld_remote_ra.c +++ b/daemons/controld/controld_remote_ra.c @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the Pacemaker project contributors + * Copyright 2013-2025 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -235,36 +235,18 @@ should_purge_attributes(pcmk__node_status_t *node) return true; } -static enum controld_section_e -section_to_delete(bool purge) -{ - if (pcmk_is_set(controld_globals.flags, controld_shutdown_lock_enabled)) { - if (purge) { - return controld_section_all_unlocked; - } else { - return controld_section_lrm_unlocked; - } - } else { - if (purge) { - return controld_section_all; - } else { - return controld_section_lrm; - } - } -} - static void purge_remote_node_attrs(int call_opt, pcmk__node_status_t *node) { - bool purge = should_purge_attributes(node); - enum controld_section_e section = section_to_delete(purge); + const bool unlocked_only = pcmk_is_set(controld_globals.flags, + controld_shutdown_lock_enabled); - /* Purge node from attrd's memory */ - if (purge) { - update_attrd_remote_node_removed(node->name, NULL); + // Purge node's transient attributes (from attribute manager and CIB) + if (should_purge_attributes(node)) { + controld_purge_node_attrs(node->name, true); } - controld_delete_node_state(node->name, section, call_opt); + controld_delete_node_history(node->name, unlocked_only, call_opt); } /*! @@ -365,18 +347,15 @@ remote_node_down(const char *node_name, const enum down_opts opts) int call_opt = crmd_cib_smart_opt(); pcmk__node_status_t *node = NULL; - /* Purge node from attrd's memory */ - update_attrd_remote_node_removed(node_name, NULL); + // Purge node's transient attributes (from attribute manager and CIB) + controld_purge_node_attrs(node_name, true); - /* Normally, only node attributes should be erased, and the resource history - * should be kept until the node comes back up. However, after a successful - * fence, we want to clear the history as well, so we don't think resources - * are still running on the node. + /* Normally, the resource history should be kept until the node comes back + * up. However, after a successful fence, clear the history so we don't + * think resources are still running on the node. */ if (opts == DOWN_ERASE_LRM) { - controld_delete_node_state(node_name, controld_section_all, call_opt); - } else { - controld_delete_node_state(node_name, controld_section_attrs, call_opt); + controld_delete_node_history(node_name, false, call_opt); } /* Ensure node is in the remote peer cache with lost state */ diff --git a/daemons/controld/controld_utils.h b/daemons/controld/controld_utils.h index 84e6a19ea06..1658a83b8ee 100644 --- a/daemons/controld/controld_utils.h +++ b/daemons/controld/controld_utils.h @@ -1,5 +1,5 @@ /* - * Copyright 2004-2024 the Pacemaker project contributors + * Copyright 2004-2025 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -50,7 +50,7 @@ void crm_update_quorum(gboolean quorum, gboolean force_update); void controld_close_attrd_ipc(void); void update_attrd(const char *host, const char *name, const char *value, const char *user_name, gboolean is_remote_node); void update_attrd_list(GList *attrs, uint32_t opts); -void update_attrd_remote_node_removed(const char *host, const char *user_name); +void controld_purge_node_attrs(const char *node_name, bool from_cache); void update_attrd_clear_failures(const char *host, const char *rsc, const char *op, const char *interval_spec, gboolean is_remote_node);