Page Menu
Home
ClusterLabs Projects
Search
Configure Global Search
Log In
Files
F2825182
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
98 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/daemons/attrd/attrd_cib.c b/daemons/attrd/attrd_cib.c
index 17d86eed3e..588e779348 100644
--- a/daemons/attrd/attrd_cib.c
+++ b/daemons/attrd/attrd_cib.c
@@ -1,871 +1,871 @@
/*
* Copyright 2013-2025 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 <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>
#include <crm/cib/internal.h> // cib__*
#include <crm/common/logging.h>
#include <crm/common/results.h>
#include <crm/common/strings_internal.h>
#include <crm/common/xml.h>
#include <crm/cluster/internal.h> // pcmk__get_node()
#include "pacemaker-attrd.h"
static int last_cib_op_done = 0;
static void write_attribute(attribute_t *a, bool ignore_delay);
static void
attrd_cib_destroy_cb(gpointer user_data)
{
cib_t *cib = user_data;
cib->cmds->signoff(cib);
- if (attrd_shutting_down(false)) {
+ if (attrd_shutting_down()) {
crm_info("Disconnected from the CIB manager");
} else {
// @TODO This should trigger a reconnect, not a shutdown
crm_crit("Lost connection to the CIB manager, shutting down");
attrd_exit_status = CRM_EX_DISCONNECT;
attrd_shutdown(0);
}
}
/* 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)
{
const xmlNode *patchset = NULL;
const char *client_name = NULL;
bool status_changed = false;
if (cib__get_notify_patchset(msg, &patchset) != pcmk_rc_ok) {
return;
}
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);
}
}
status_changed = pcmk__cib_element_in_patchset(patchset, PCMK_XE_STATUS);
client_name = crm_element_value(msg, PCMK__XA_CIB_CLIENTNAME);
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;
}
if (!attrd_election_won()) {
// Don't write attributes if we're not the writer
return;
}
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;
}
/* An unsafe client modified the PCMK_XE_NODES or PCMK_XE_STATUS
* section. Write transient attributes to ensure they're up-to-date in
* the CIB.
*/
if (client_name == NULL) {
client_name = crm_element_value(msg, PCMK__XA_CIB_CLIENTID);
}
crm_notice("Updating all attributes after %s event triggered by %s",
event, pcmk__s(client_name, "unidentified client"));
attrd_write_attributes(attrd_write_all);
}
}
int
attrd_cib_connect(int max_retry)
{
static int attempts = 0;
int rc = -ENOTCONN;
the_cib = cib_new();
if (the_cib == NULL) {
return -ENOTCONN;
}
do {
if (attempts > 0) {
sleep(attempts);
}
attempts++;
crm_debug("Connection attempt %d to the CIB manager", attempts);
rc = the_cib->cmds->signon(the_cib, crm_system_name, cib_command);
} while ((rc != pcmk_ok) && (attempts < max_retry));
if (rc != pcmk_ok) {
crm_err("Connection to the CIB manager failed: %s " QB_XS " rc=%d",
pcmk_strerror(rc), rc);
goto cleanup;
}
crm_debug("Connected to the CIB manager after %d attempts", attempts);
rc = the_cib->cmds->set_connection_dnotify(the_cib, attrd_cib_destroy_cb);
if (rc != pcmk_ok) {
crm_err("Could not set disconnection callback");
goto cleanup;
}
rc = the_cib->cmds->add_notify_callback(the_cib,
PCMK__VALUE_CIB_DIFF_NOTIFY,
attrd_cib_updated_cb);
if (rc != pcmk_ok) {
crm_err("Could not set CIB notification callback");
goto cleanup;
}
return pcmk_ok;
cleanup:
cib__clean_up_connection(&the_cib);
return -ENOTCONN;
}
void
attrd_cib_disconnect(void)
{
CRM_CHECK(the_cib != NULL, return);
the_cib->cmds->del_notify_callback(the_cib, PCMK__VALUE_CIB_DIFF_NOTIFY,
attrd_cib_updated_cb);
cib__clean_up_connection(&the_cib);
mainloop_destroy_trigger(attrd_config_read);
}
static void
attrd_erase_cb(xmlNode *msg, int call_id, int rc, xmlNode *output,
void *user_data)
{
const char *node = pcmk__s((const char *) user_data, "a node");
if (rc == pcmk_ok) {
crm_info("Cleared transient node attributes for %s from CIB", node);
} else {
crm_err("Unable to clear transient node attributes for %s from CIB: %s",
node, pcmk_strerror(rc));
}
}
#define XPATH_TRANSIENT "//" PCMK__XE_NODE_STATE \
"[@" PCMK_XA_UNAME "='%s']" \
"/" PCMK__XE_TRANSIENT_ATTRIBUTES
/*!
* \internal
* \brief Wipe all transient node attributes for a node from the CIB
*
* \param[in] node Node to clear attributes for
*/
void
attrd_cib_erase_transient_attrs(const char *node)
{
int call_id = 0;
char *xpath = NULL;
CRM_CHECK(node != NULL, return);
xpath = crm_strdup_printf(XPATH_TRANSIENT, node);
crm_debug("Clearing transient node attributes for %s from CIB using %s",
node, xpath);
call_id = the_cib->cmds->remove(the_cib, xpath, NULL, cib_xpath);
free(xpath);
the_cib->cmds->register_callback_full(the_cib, call_id, 120, FALSE,
pcmk__str_copy(node),
"attrd_erase_cb", attrd_erase_cb,
free);
}
/*!
* \internal
* \brief Prepare the CIB after cluster is connected
*/
void
attrd_cib_init(void)
{
/* We have no attribute values in memory, so wipe the CIB to match. This is
* 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
* PCMK_ENV_RESPAWNED), ideally we'd skip this and sync our attributes
* from the writer. However, currently we reject any values for us
* that the writer has, in attrd_peer_update().
*/
attrd_cib_erase_transient_attrs(attrd_cluster->priv->node_name);
// Set a trigger for reading the CIB (for the alerts section)
attrd_config_read = mainloop_add_trigger(G_PRIORITY_HIGH, attrd_read_options, NULL);
// Always read the CIB at start-up
mainloop_set_trigger(attrd_config_read);
}
static gboolean
attribute_timer_cb(gpointer data)
{
attribute_t *a = data;
crm_trace("Dampen interval expired for %s", a->id);
attrd_write_or_elect_attribute(a);
return FALSE;
}
static void
attrd_cib_callback(xmlNode *msg, int call_id, int rc, xmlNode *output, void *user_data)
{
int level = LOG_ERR;
GHashTableIter iter;
const char *peer = NULL;
attribute_value_t *v = NULL;
char *name = user_data;
attribute_t *a = g_hash_table_lookup(attributes, name);
if(a == NULL) {
crm_info("Attribute %s no longer exists", name);
return;
}
a->update = 0;
if (rc == pcmk_ok && call_id < 0) {
rc = call_id;
}
switch (rc) {
case pcmk_ok:
level = LOG_INFO;
last_cib_op_done = call_id;
if (a->timer && !a->timeout_ms) {
// Remove temporary dampening for failed writes
mainloop_timer_del(a->timer);
a->timer = NULL;
}
break;
case -pcmk_err_diff_failed: /* When an attr changes while the CIB is syncing */
case -ETIME: /* When an attr changes while there is a DC election */
case -ENXIO: /* When an attr changes while the CIB is syncing a
* newer config from a node that just came up
*/
level = LOG_WARNING;
break;
}
do_crm_log(level, "CIB update %d result for %s: %s " QB_XS " rc=%d",
call_id, a->id, pcmk_strerror(rc), rc);
g_hash_table_iter_init(&iter, a->values);
while (g_hash_table_iter_next(&iter, (gpointer *) & peer, (gpointer *) & v)) {
if (rc == pcmk_ok) {
crm_info("* Wrote %s[%s]=%s",
a->id, peer, pcmk__s(v->requested, "(unset)"));
pcmk__str_update(&(v->requested), NULL);
} else {
do_crm_log(level, "* Could not write %s[%s]=%s",
a->id, peer, pcmk__s(v->requested, "(unset)"));
/* Reattempt write below if we are still the writer */
attrd_set_attr_flags(a, attrd_attr_changed);
}
}
if (pcmk_is_set(a->flags, attrd_attr_changed) && attrd_election_won()) {
if (rc == pcmk_ok) {
/* We deferred a write of a new update because this update was in
* progress. Write out the new value without additional delay.
*/
crm_debug("Pending update for %s can be written now", a->id);
write_attribute(a, false);
/* We're re-attempting a write because the original failed; delay
* the next attempt so we don't potentially flood the CIB manager
* and logs with a zillion attempts per second.
*
* @TODO We could elect a new writer instead. However, we'd have to
* somehow downgrade our vote, and we'd still need something like this
* if all peers similarly fail to write this attribute (which may
* indicate a corrupted attribute entry rather than a CIB issue).
*/
} else if (a->timer) {
// Attribute has a dampening value, so use that as delay
if (!mainloop_timer_running(a->timer)) {
crm_trace("Delayed re-attempted write for %s by %s",
name, pcmk__readable_interval(a->timeout_ms));
mainloop_timer_start(a->timer);
}
} else {
/* Set a temporary dampening of 2 seconds (timer will continue
* to exist until the attribute's dampening gets set or the
* write succeeds).
*/
a->timer = attrd_add_timer(a->id, 2000, a);
mainloop_timer_start(a->timer);
}
}
}
/*!
* \internal
* \brief Add a set-attribute update request to the current CIB transaction
*
* \param[in] attr Attribute to update
* \param[in] attr_id ID of attribute to update
* \param[in] node_id ID of node for which to update attribute value
* \param[in] set_id ID of attribute set
* \param[in] value New value for attribute
*
* \return Standard Pacemaker return code
*/
static int
add_set_attr_update(const attribute_t *attr, const char *attr_id,
const char *node_id, const char *set_id, const char *value)
{
xmlNode *update = pcmk__xe_create(NULL, PCMK__XE_NODE_STATE);
xmlNode *child = update;
int rc = ENOMEM;
crm_xml_add(child, PCMK_XA_ID, node_id);
child = pcmk__xe_create(child, PCMK__XE_TRANSIENT_ATTRIBUTES);
crm_xml_add(child, PCMK_XA_ID, node_id);
child = pcmk__xe_create(child, attr->set_type);
crm_xml_add(child, PCMK_XA_ID, set_id);
child = pcmk__xe_create(child, PCMK_XE_NVPAIR);
crm_xml_add(child, PCMK_XA_ID, attr_id);
crm_xml_add(child, PCMK_XA_NAME, attr->id);
crm_xml_add(child, PCMK_XA_VALUE, value);
rc = the_cib->cmds->modify(the_cib, PCMK_XE_STATUS, update,
cib_can_create|cib_transaction);
rc = pcmk_legacy2rc(rc);
pcmk__xml_free(update);
return rc;
}
/*!
* \internal
* \brief Add an unset-attribute update request to the current CIB transaction
*
* \param[in] attr Attribute to update
* \param[in] attr_id ID of attribute to update
* \param[in] node_id ID of node for which to update attribute value
* \param[in] set_id ID of attribute set
*
* \return Standard Pacemaker return code
*/
static int
add_unset_attr_update(const attribute_t *attr, const char *attr_id,
const char *node_id, const char *set_id)
{
char *xpath = crm_strdup_printf("/" PCMK_XE_CIB
"/" PCMK_XE_STATUS
"/" PCMK__XE_NODE_STATE
"[@" PCMK_XA_ID "='%s']"
"/" PCMK__XE_TRANSIENT_ATTRIBUTES
"[@" PCMK_XA_ID "='%s']"
"/%s[@" PCMK_XA_ID "='%s']"
"/" PCMK_XE_NVPAIR
"[@" PCMK_XA_ID "='%s' "
"and @" PCMK_XA_NAME "='%s']",
node_id, node_id, attr->set_type, set_id,
attr_id, attr->id);
int rc = the_cib->cmds->remove(the_cib, xpath, NULL,
cib_xpath|cib_transaction);
free(xpath);
return pcmk_legacy2rc(rc);
}
/*!
* \internal
* \brief Add an attribute update request to the current CIB transaction
*
* \param[in] attr Attribute to update
* \param[in] value New value for attribute
* \param[in] node_id ID of node for which to update attribute value
*
* \return Standard Pacemaker return code
*/
static int
add_attr_update(const attribute_t *attr, const char *value, const char *node_id)
{
char *set_id = attrd_set_id(attr, node_id);
char *nvpair_id = attrd_nvpair_id(attr, node_id);
int rc = pcmk_rc_ok;
if (value == NULL) {
rc = add_unset_attr_update(attr, nvpair_id, node_id, set_id);
} else {
rc = add_set_attr_update(attr, nvpair_id, node_id, set_id, value);
}
free(set_id);
free(nvpair_id);
return rc;
}
static void
send_alert_attributes_value(attribute_t *a, GHashTable *t)
{
int rc = 0;
attribute_value_t *at = NULL;
GHashTableIter vIter;
g_hash_table_iter_init(&vIter, t);
while (g_hash_table_iter_next(&vIter, NULL, (gpointer *) & at)) {
const char *node_xml_id = attrd_get_node_xml_id(at->nodename);
rc = attrd_send_attribute_alert(at->nodename, node_xml_id,
a->id, at->current);
crm_trace("Sent alerts for %s[%s]=%s with node XML ID %s "
"(%s agents failed)",
a->id, at->nodename, at->current,
pcmk__s(node_xml_id, "unknown"),
((rc == 0)? "no" : ((rc == -1)? "some" : "all")));
}
}
static void
set_alert_attribute_value(GHashTable *t, attribute_value_t *v)
{
attribute_value_t *a_v = pcmk__assert_alloc(1, sizeof(attribute_value_t));
a_v->nodename = pcmk__str_copy(v->nodename);
a_v->current = pcmk__str_copy(v->current);
g_hash_table_replace(t, a_v->nodename, a_v);
}
mainloop_timer_t *
attrd_add_timer(const char *id, int timeout_ms, attribute_t *attr)
{
return mainloop_timer_add(id, timeout_ms, FALSE, attribute_timer_cb, attr);
}
/*!
* \internal
* \brief Write an attribute's values to the CIB if appropriate
*
* \param[in,out] a Attribute to write
* \param[in] ignore_delay If true, write attribute now regardless of any
* configured delay
*/
static void
write_attribute(attribute_t *a, bool ignore_delay)
{
int private_updates = 0, cib_updates = 0;
attribute_value_t *v = NULL;
GHashTableIter iter;
GHashTable *alert_attribute_value = NULL;
int rc = pcmk_ok;
bool should_write = attrd_for_cib(a);
if (a == NULL) {
return;
}
if (should_write) {
/* Defer the write if now's not a good time */
if (a->update && (a->update < last_cib_op_done)) {
crm_info("Write out of '%s' continuing: update %d considered lost",
a->id, a->update);
a->update = 0; // Don't log this message again
} else if (a->update) {
crm_info("Write out of '%s' delayed: update %d in progress",
a->id, a->update);
goto done;
} else if (mainloop_timer_running(a->timer)) {
if (ignore_delay) {
mainloop_timer_stop(a->timer);
crm_debug("Overriding '%s' write delay", a->id);
} else {
crm_info("Delaying write of '%s'", a->id);
goto done;
}
}
// Initiate a transaction for all the peer value updates
CRM_CHECK(the_cib != NULL, goto done);
the_cib->cmds->set_user(the_cib, a->user);
rc = the_cib->cmds->init_transaction(the_cib);
if (rc != pcmk_ok) {
crm_err("Failed to write %s (set %s): Could not initiate "
"CIB transaction",
a->id, pcmk__s(a->set_id, "unspecified"));
goto done;
}
}
/* The changed and force-write flags apply only to the next write,
* which this is, so clear them now. Also clear the "node unknown" flag
* because we will check whether it is known below and reset if appopriate.
*/
attrd_clear_attr_flags(a, attrd_attr_changed
|attrd_attr_force_write
|attrd_attr_node_unknown);
/* Make the table for the attribute trap */
alert_attribute_value = pcmk__strikey_table(NULL,
attrd_free_attribute_value);
/* Iterate over each peer value of this attribute */
g_hash_table_iter_init(&iter, a->values);
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++;
continue;
}
/* We need the node's CIB XML ID to write out its attributes, so look
* for it now. Check the node caches first, even if the ID was
* previously known (in case it changed), but use any previous value as
* a fallback.
*/
prev_xml_id = attrd_get_node_xml_id(v->nodename);
if (pcmk_is_set(v->flags, attrd_value_remote)) {
// 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
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;
}
}
// Defer write if this is a cluster node that's never been seen
if (node_xml_id == NULL) {
attrd_set_attr_flags(a, attrd_attr_node_unknown);
crm_notice("Cannot write %s[%s]='%s' to CIB because node's XML ID "
"is unknown (will retry if learned)",
a->id, v->nodename, v->current);
continue;
}
if (!pcmk__str_eq(prev_xml_id, node_xml_id, pcmk__str_none)) {
crm_trace("Setting %s[%s] node XML ID to %s (was %s)",
a->id, v->nodename, node_xml_id,
pcmk__s(prev_xml_id, "unknown"));
attrd_set_node_xml_id(v->nodename, node_xml_id);
}
// Update this value as part of the CIB transaction we're building
rc = add_attr_update(a, v->current, node_xml_id);
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, pcmk__s(v->current, "(unset)"),
pcmk_rc_str(rc), node_xml_id);
continue;
}
crm_debug("Added %s[%s]=%s to CIB transaction (node XML ID %s)",
a->id, v->nodename, pcmk__s(v->current, "(unset)"),
node_xml_id);
cib_updates++;
/* Preservation of the attribute to transmit alert */
set_alert_attribute_value(alert_attribute_value, v);
// Save this value so we can log it when write completes
pcmk__str_update(&(v->requested), v->current);
}
if (private_updates) {
crm_info("Processed %d private change%s for %s (set %s)",
private_updates, pcmk__plural_s(private_updates),
a->id, pcmk__s(a->set_id, "unspecified"));
}
if (cib_updates > 0) {
char *id = pcmk__str_copy(a->id);
// Commit transaction
a->update = the_cib->cmds->end_transaction(the_cib, true, cib_none);
crm_info("Sent CIB request %d with %d change%s for %s (set %s)",
a->update, cib_updates, pcmk__plural_s(cib_updates),
a->id, pcmk__s(a->set_id, "unspecified"));
if (the_cib->cmds->register_callback_full(the_cib, a->update,
CIB_OP_TIMEOUT_S, FALSE, id,
"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);
}
}
done:
// Discard transaction (if any)
if (the_cib != NULL) {
the_cib->cmds->end_transaction(the_cib, false, cib_none);
the_cib->cmds->set_user(the_cib, NULL);
}
if (alert_attribute_value != NULL) {
g_hash_table_destroy(alert_attribute_value);
}
}
/*!
* \internal
* \brief Write out attributes
*
* \param[in] options Group of enum attrd_write_options
*/
void
attrd_write_attributes(uint32_t options)
{
GHashTableIter iter;
attribute_t *a = NULL;
crm_debug("Writing out %s attributes",
pcmk_is_set(options, attrd_write_all)? "all" : "changed");
g_hash_table_iter_init(&iter, attributes);
while (g_hash_table_iter_next(&iter, NULL, (gpointer *) & a)) {
if (!pcmk_is_set(options, attrd_write_all)
&& pcmk_is_set(a->flags, attrd_attr_node_unknown)) {
// Try writing this attribute again, in case peer ID was learned
attrd_set_attr_flags(a, attrd_attr_changed);
} else if (pcmk_is_set(a->flags, attrd_attr_force_write)) {
/* If the force_write flag is set, write the attribute. */
attrd_set_attr_flags(a, attrd_attr_changed);
}
if (pcmk_is_set(options, attrd_write_all) ||
pcmk_is_set(a->flags, attrd_attr_changed)) {
bool ignore_delay = pcmk_is_set(options, attrd_write_no_delay);
if (pcmk_is_set(a->flags, attrd_attr_force_write)) {
// Always ignore delay when forced write flag is set
ignore_delay = true;
}
write_attribute(a, ignore_delay);
} else {
crm_trace("Skipping unchanged attribute %s", a->id);
}
}
}
void
attrd_write_or_elect_attribute(attribute_t *a)
{
if (attrd_election_won()) {
write_attribute(a, false);
} else {
attrd_start_election_if_needed();
}
}
diff --git a/daemons/attrd/attrd_corosync.c b/daemons/attrd/attrd_corosync.c
index 8b06d98a8b..8d52ab8c8a 100644
--- a/daemons/attrd/attrd_corosync.c
+++ b/daemons/attrd/attrd_corosync.c
@@ -1,640 +1,629 @@
/*
* Copyright 2013-2025 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 <crm_internal.h>
#include <errno.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <crm/cluster.h>
#include <crm/cluster/internal.h>
#include <crm/common/logging.h>
#include <crm/common/results.h>
#include <crm/common/strings_internal.h>
#include <crm/common/xml.h>
#include "pacemaker-attrd.h"
static xmlNode *
attrd_confirmation(int callid)
{
xmlNode *node = pcmk__xe_create(NULL, __func__);
crm_xml_add(node, PCMK__XA_T, PCMK__VALUE_ATTRD);
crm_xml_add(node, PCMK__XA_SRC, pcmk__cluster_local_node_name());
crm_xml_add(node, PCMK_XA_TASK, PCMK__ATTRD_CMD_CONFIRM);
crm_xml_add_int(node, PCMK__XA_CALL_ID, callid);
return node;
}
static void
attrd_peer_message(pcmk__node_status_t *peer, xmlNode *xml)
{
const char *election_op = crm_element_value(xml, PCMK__XA_CRM_TASK);
if (election_op) {
attrd_handle_election_op(peer, 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.
*/
return;
} else {
pcmk__request_t request = {
.ipc_client = NULL,
.ipc_id = 0,
.ipc_flags = 0,
.peer = peer->name,
.xml = xml,
.call_options = 0,
.result = PCMK__UNKNOWN_RESULT,
};
request.op = crm_element_value_copy(request.xml, PCMK_XA_TASK);
CRM_CHECK(request.op != NULL, return);
attrd_handle_request(&request);
/* Having finished handling the request, check to see if the originating
* peer requested confirmation. If so, send that confirmation back now.
*/
if (pcmk__xe_attr_is_true(xml, PCMK__XA_CONFIRM) &&
!pcmk__str_eq(request.op, PCMK__ATTRD_CMD_CONFIRM, pcmk__str_none)) {
int callid = 0;
xmlNode *reply = NULL;
/* Add the confirmation ID for the message we are confirming to the
* response so the originating peer knows what they're a confirmation
* for.
*/
crm_element_value_int(xml, PCMK__XA_CALL_ID, &callid);
reply = attrd_confirmation(callid);
/* And then send the confirmation back to the originating peer. This
* ends up right back in this same function (attrd_peer_message) on the
* peer where it will have to do something with a PCMK__XA_CONFIRM type
* message.
*/
crm_debug("Sending %s a confirmation", peer->name);
attrd_send_message(peer, reply, false);
pcmk__xml_free(reply);
}
pcmk__reset_request(&request);
}
}
static void
attrd_cpg_dispatch(cpg_handle_t handle,
const struct cpg_name *groupName,
uint32_t nodeid, uint32_t pid, void *msg, size_t msg_len)
{
xmlNode *xml = NULL;
const char *from = NULL;
char *data = pcmk__cpg_message_data(handle, nodeid, pid, msg, &from);
if(data == NULL) {
return;
}
xml = pcmk__xml_parse(data);
if (xml == NULL) {
crm_err("Bad message received from %s[%u]: '%.120s'",
from, nodeid, data);
} else {
attrd_peer_message(pcmk__get_node(nodeid, from, NULL,
pcmk__node_search_cluster_member),
xml);
}
pcmk__xml_free(xml);
free(data);
}
static void
attrd_cpg_destroy(gpointer unused)
{
- if (attrd_shutting_down(false)) {
+ if (attrd_shutting_down()) {
crm_info("Disconnected from Corosync process group");
} else {
crm_crit("Lost connection to Corosync process group, shutting down");
attrd_exit_status = CRM_EX_DISCONNECT;
attrd_shutdown(0);
}
}
/*!
* \internal
* \brief Broadcast an update for a single attribute value
*
* \param[in] a Attribute to broadcast
* \param[in] v Attribute value to broadcast
*/
void
attrd_broadcast_value(const attribute_t *a, const attribute_value_t *v)
{
xmlNode *op = pcmk__xe_create(NULL, PCMK_XE_OP);
crm_xml_add(op, PCMK_XA_TASK, PCMK__ATTRD_CMD_UPDATE);
attrd_add_value_xml(op, a, v, false);
attrd_send_message(NULL, op, false);
pcmk__xml_free(op);
}
#define state_text(state) pcmk__s((state), "in unknown state")
static void
attrd_peer_change_cb(enum pcmk__node_update kind, pcmk__node_status_t *peer,
const void *data)
{
bool gone = false;
bool is_remote = pcmk_is_set(peer->flags, pcmk__node_status_remote);
switch (kind) {
case pcmk__node_update_name:
crm_debug("%s node %s is now %s",
(is_remote? "Remote" : "Cluster"),
peer->name, state_text(peer->state));
break;
case pcmk__node_update_processes:
if (!pcmk_is_set(peer->processes, crm_get_cluster_proc())) {
gone = true;
}
crm_debug("Node %s is %s a peer",
peer->name, (gone? "no longer" : "now"));
break;
case pcmk__node_update_state:
crm_debug("%s node %s is now %s (was %s)",
(is_remote? "Remote" : "Cluster"),
peer->name, state_text(peer->state), state_text(data));
if (pcmk__str_eq(peer->state, PCMK_VALUE_MEMBER, pcmk__str_none)) {
/* If we're the writer, send new peers a list of all attributes
* (unless it's a remote node, which doesn't run its own attrd)
*/
if (attrd_election_won()
&& !pcmk_is_set(peer->flags, pcmk__node_status_remote)) {
attrd_peer_sync(peer);
}
} else {
// Remove all attribute values associated with lost nodes
attrd_peer_remove(peer->name, false, "loss");
gone = true;
}
break;
}
// Remove votes from cluster nodes that leave, in case election in progress
if (gone && !is_remote) {
attrd_remove_voter(peer);
attrd_remove_peer_protocol_ver(peer->name);
attrd_do_not_expect_from_peer(peer->name);
}
}
#define readable_value(rv_v) pcmk__s((rv_v)->current, "(unset)")
#define readable_peer(p) \
(((p) == NULL)? "all peers" : pcmk__s((p)->name, "unknown peer"))
static void
update_attr_on_host(attribute_t *a, const pcmk__node_status_t *peer,
const xmlNode *xml, const char *attr, const char *value,
const char *host, bool filter)
{
int is_remote = 0;
bool changed = false;
attribute_value_t *v = NULL;
const char *prev_xml_id = NULL;
const char *node_xml_id = crm_element_value(xml, PCMK__XA_ATTR_HOST_ID);
// Create entry for value if not already existing
v = g_hash_table_lookup(a->values, host);
if (v == NULL) {
v = pcmk__assert_alloc(1, sizeof(attribute_value_t));
v->nodename = pcmk__str_copy(host);
g_hash_table_replace(a->values, v->nodename, v);
}
/* If update doesn't contain the node XML ID, fall back to any previously
* known value (for logging)
*/
prev_xml_id = attrd_get_node_xml_id(v->nodename);
if (node_xml_id == NULL) {
node_xml_id = prev_xml_id;
}
// If value is for a Pacemaker Remote node, remember that
crm_element_value_int(xml, PCMK__XA_ATTR_IS_REMOTE, &is_remote);
if (is_remote) {
attrd_set_value_flags(v, attrd_value_remote);
pcmk__assert(pcmk__cluster_lookup_remote_node(host) != NULL);
}
// Check whether the value changed
changed = !pcmk__str_eq(v->current, value, pcmk__str_casei);
if (changed && filter
&& pcmk__str_eq(host, attrd_cluster->priv->node_name,
pcmk__str_casei)) {
/* Broadcast the local value for an attribute that differs from the
* value provided in a peer's attribute synchronization response. This
* ensures a node's values for itself take precedence and all peers are
* kept in sync.
*/
v = g_hash_table_lookup(a->values, attrd_cluster->priv->node_name);
crm_notice("%s[%s]: local value '%s' takes priority over '%s' from %s",
attr, host, readable_value(v), value, peer->name);
attrd_broadcast_value(a, v);
} else if (changed) {
crm_notice("Setting %s[%s]%s%s: %s -> %s "
QB_XS " from %s with %s write delay and node XML ID %s",
attr, host, a->set_type ? " in " : "",
pcmk__s(a->set_type, ""), readable_value(v),
pcmk__s(value, "(unset)"), peer->name,
(a->timeout_ms == 0)? "no" : pcmk__readable_interval(a->timeout_ms),
pcmk__s(node_xml_id, "unknown"));
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",
attr, pcmk__readable_interval(a->timeout_ms));
mainloop_timer_start(a->timer);
} else {
attrd_write_or_elect_attribute(a);
}
} else {
int is_force_write = 0;
crm_element_value_int(xml, PCMK__XA_ATTRD_IS_FORCE_WRITE,
&is_force_write);
if (is_force_write == 1 && a->timeout_ms && a->timer) {
/* Save forced writing and set change flag. */
/* The actual attribute is written by Writer after election. */
crm_trace("%s[%s] from %s is unchanged (%s), forcing write",
attr, host, peer->name, pcmk__s(value, "unset"));
attrd_set_attr_flags(a, attrd_attr_force_write);
} else {
crm_trace("%s[%s] from %s is unchanged (%s)",
attr, host, peer->name, pcmk__s(value, "unset"));
}
}
// This allows us to later detect local values that peer doesn't know about
attrd_set_value_flags(v, attrd_value_from_peer);
// Remember node's XML ID if we're just learning it
if ((node_xml_id != NULL)
&& !pcmk__str_eq(node_xml_id, prev_xml_id, pcmk__str_none)) {
crm_trace("Learned %s[%s] node XML ID is %s (was %s)",
a->id, v->nodename, node_xml_id,
pcmk__s(prev_xml_id, "unknown"));
attrd_set_node_xml_id(v->nodename, node_xml_id);
if (attrd_election_won()) {
// In case we couldn't write a value missing the XML ID before
attrd_write_attributes(attrd_write_changed);
}
}
}
static void
attrd_peer_update_one(const pcmk__node_status_t *peer, xmlNode *xml,
bool filter)
{
attribute_t *a = NULL;
const char *attr = crm_element_value(xml, PCMK__XA_ATTR_NAME);
const char *value = crm_element_value(xml, PCMK__XA_ATTR_VALUE);
const char *host = crm_element_value(xml, PCMK__XA_ATTR_HOST);
if (attr == NULL) {
crm_warn("Could not update attribute: peer did not specify name");
return;
}
a = attrd_populate_attribute(xml, attr);
if (a == NULL) {
return;
}
if (host == NULL) {
// If no host was specified, update all hosts
GHashTableIter vIter;
crm_debug("Setting %s for all hosts to %s", attr, value);
pcmk__xe_remove_attr(xml, PCMK__XA_ATTR_HOST_ID);
g_hash_table_iter_init(&vIter, a->values);
while (g_hash_table_iter_next(&vIter, (gpointer *) & host, NULL)) {
update_attr_on_host(a, peer, xml, attr, value, host, filter);
}
} else {
// Update attribute value for the given host
update_attr_on_host(a, peer, xml, attr, value, host, filter);
}
/* If this is a message from some attrd instance broadcasting its protocol
* version, check to see if it's a new minimum version.
*/
if (pcmk__str_eq(attr, CRM_ATTR_PROTOCOL, pcmk__str_none)) {
attrd_update_minimum_protocol_ver(peer->name, value);
}
}
static void
broadcast_unseen_local_values(void)
{
GHashTableIter aIter;
GHashTableIter vIter;
attribute_t *a = NULL;
attribute_value_t *v = NULL;
xmlNode *sync = NULL;
g_hash_table_iter_init(&aIter, attributes);
while (g_hash_table_iter_next(&aIter, NULL, (gpointer *) & a)) {
g_hash_table_iter_init(&vIter, a->values);
while (g_hash_table_iter_next(&vIter, NULL, (gpointer *) & v)) {
if (!pcmk_is_set(v->flags, attrd_value_from_peer)
&& pcmk__str_eq(v->nodename, attrd_cluster->priv->node_name,
pcmk__str_casei)) {
crm_trace("* %s[%s]='%s' is local-only",
a->id, v->nodename, readable_value(v));
if (sync == NULL) {
sync = pcmk__xe_create(NULL, __func__);
crm_xml_add(sync, PCMK_XA_TASK, PCMK__ATTRD_CMD_SYNC_RESPONSE);
}
attrd_add_value_xml(sync, a, v, a->timeout_ms && a->timer);
}
}
}
if (sync != NULL) {
crm_debug("Broadcasting local-only values");
attrd_send_message(NULL, sync, false);
pcmk__xml_free(sync);
}
}
int
attrd_cluster_connect(void)
{
int rc = pcmk_rc_ok;
attrd_cluster = pcmk_cluster_new();
pcmk_cluster_set_destroy_fn(attrd_cluster, attrd_cpg_destroy);
pcmk_cpg_set_deliver_fn(attrd_cluster, attrd_cpg_dispatch);
pcmk_cpg_set_confchg_fn(attrd_cluster, pcmk__cpg_confchg_cb);
pcmk__cluster_set_status_callback(&attrd_peer_change_cb);
rc = pcmk_cluster_connect(attrd_cluster);
rc = pcmk_rc2legacy(rc);
if (rc != pcmk_ok) {
crm_err("Cluster connection failed");
return rc;
}
return pcmk_ok;
}
void
attrd_peer_clear_failure(pcmk__request_t *request)
{
xmlNode *xml = request->xml;
const char *rsc = crm_element_value(xml, PCMK__XA_ATTR_RESOURCE);
const char *host = crm_element_value(xml, PCMK__XA_ATTR_HOST);
const char *op = crm_element_value(xml, PCMK__XA_ATTR_CLEAR_OPERATION);
const char *interval_spec = crm_element_value(xml,
PCMK__XA_ATTR_CLEAR_INTERVAL);
guint interval_ms = 0U;
char *attr = NULL;
GHashTableIter iter;
regex_t regex;
pcmk__node_status_t *peer =
pcmk__get_node(0, request->peer, NULL,
pcmk__node_search_cluster_member);
pcmk_parse_interval_spec(interval_spec, &interval_ms);
if (attrd_failure_regex(®ex, rsc, op, interval_ms) != pcmk_ok) {
crm_info("Ignoring invalid request to clear failures for %s",
pcmk__s(rsc, "all resources"));
return;
}
crm_xml_add(xml, PCMK_XA_TASK, PCMK__ATTRD_CMD_UPDATE);
/* Make sure value is not set, so we delete */
pcmk__xe_remove_attr(xml, PCMK__XA_ATTR_VALUE);
g_hash_table_iter_init(&iter, attributes);
while (g_hash_table_iter_next(&iter, (gpointer *) &attr, NULL)) {
if (regexec(®ex, attr, 0, NULL, 0) == 0) {
crm_trace("Matched %s when clearing %s",
attr, pcmk__s(rsc, "all resources"));
crm_xml_add(xml, PCMK__XA_ATTR_NAME, attr);
attrd_peer_update(peer, xml, host, false);
}
}
regfree(®ex);
}
/*!
* \internal
* \brief Load attributes from a peer sync response
*
* \param[in] peer Peer that sent sync response
* \param[in] peer_won Whether peer is the attribute writer
* \param[in,out] xml Request XML
*/
void
attrd_peer_sync_response(const pcmk__node_status_t *peer, bool peer_won,
xmlNode *xml)
{
crm_info("Processing " PCMK__ATTRD_CMD_SYNC_RESPONSE " from %s",
peer->name);
if (peer_won) {
/* Initialize the "seen" flag for all attributes to cleared, so we can
* detect attributes that local node has but the writer doesn't.
*/
attrd_clear_value_seen();
}
// Process each attribute update in the sync response
for (xmlNode *child = pcmk__xe_first_child(xml, NULL, NULL, NULL);
child != NULL; child = pcmk__xe_next(child, NULL)) {
attrd_peer_update(peer, child,
crm_element_value(child, PCMK__XA_ATTR_HOST), true);
}
if (peer_won) {
/* If any attributes are still not marked as seen, the writer doesn't
* know about them, so send all peers an update with them.
*/
broadcast_unseen_local_values();
}
}
/*!
* \internal
* \brief Remove all attributes and optionally peer cache entries for a node
*
* \param[in] host Name of node to purge
* \param[in] uncache If true, remove node from peer caches
* \param[in] source Who requested removal (only used for logging)
*/
void
attrd_peer_remove(const char *host, bool uncache, const char *source)
{
attribute_t *a = NULL;
GHashTableIter aIter;
CRM_CHECK(host != NULL, return);
crm_notice("Removing all %s attributes for node %s "
QB_XS " %s reaping node from cache",
host, source, (uncache? "and" : "without"));
g_hash_table_iter_init(&aIter, attributes);
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);
}
}
/*!
* \internal
* \brief Send all known attributes and values to a peer
*
* \param[in] peer Peer to send sync to (if NULL, broadcast to all peers)
*/
void
attrd_peer_sync(pcmk__node_status_t *peer)
{
GHashTableIter aIter;
GHashTableIter vIter;
attribute_t *a = NULL;
attribute_value_t *v = NULL;
xmlNode *sync = pcmk__xe_create(NULL, __func__);
crm_xml_add(sync, PCMK_XA_TASK, PCMK__ATTRD_CMD_SYNC_RESPONSE);
g_hash_table_iter_init(&aIter, attributes);
while (g_hash_table_iter_next(&aIter, NULL, (gpointer *) & a)) {
g_hash_table_iter_init(&vIter, a->values);
while (g_hash_table_iter_next(&vIter, NULL, (gpointer *) & v)) {
crm_debug("Syncing %s[%s]='%s' to %s",
a->id, v->nodename, readable_value(v),
readable_peer(peer));
attrd_add_value_xml(sync, a, v, false);
}
}
crm_debug("Syncing values to %s", readable_peer(peer));
attrd_send_message(peer, sync, false);
pcmk__xml_free(sync);
}
void
attrd_peer_update(const pcmk__node_status_t *peer, xmlNode *xml,
const char *host, bool filter)
{
bool handle_sync_point = false;
CRM_CHECK((peer != NULL) && (xml != NULL), return);
if (xml->children != NULL) {
for (xmlNode *child = pcmk__xe_first_child(xml, PCMK_XE_OP, NULL, NULL);
child != NULL; child = pcmk__xe_next(child, PCMK_XE_OP)) {
pcmk__xe_copy_attrs(child, xml, pcmk__xaf_no_overwrite);
attrd_peer_update_one(peer, child, filter);
if (attrd_request_has_sync_point(child)) {
handle_sync_point = true;
}
}
} else {
attrd_peer_update_one(peer, xml, filter);
if (attrd_request_has_sync_point(xml)) {
handle_sync_point = true;
}
}
/* If the update XML specified that the client wanted to wait for a sync
* point, process that now.
*/
if (handle_sync_point) {
crm_trace("Hit local sync point for attribute update");
attrd_ack_waitlist_clients(attrd_sync_point_local, xml);
}
}
diff --git a/daemons/attrd/attrd_elections.c b/daemons/attrd/attrd_elections.c
index 281ec12c2f..1a79a93df2 100644
--- a/daemons/attrd/attrd_elections.c
+++ b/daemons/attrd/attrd_elections.c
@@ -1,176 +1,176 @@
/*
- * 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.
*
* This source code is licensed under the GNU General Public License version 2
* or later (GPLv2+) WITHOUT ANY WARRANTY.
*/
#include <crm_internal.h>
#include <crm/cluster.h>
#include <crm/cluster/election_internal.h>
#include <crm/common/xml.h>
#include "pacemaker-attrd.h"
static char *peer_writer = NULL;
static void
attrd_election_cb(pcmk_cluster_t *cluster)
{
attrd_declare_winner();
/* Update the peers after an election */
attrd_peer_sync(NULL);
/* After winning an election, update the CIB with the values of all
* attributes as the winner knows them.
*/
attrd_write_attributes(attrd_write_all);
}
void
attrd_election_init(void)
{
election_init(attrd_cluster, attrd_election_cb);
}
void
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);
}
}
bool
attrd_election_won(void)
{
return (election_state(attrd_cluster) == election_won);
}
void
attrd_handle_election_op(const pcmk__node_status_t *peer, xmlNode *xml)
{
enum election_result rc = 0;
enum election_result previous = election_state(attrd_cluster);
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:
crm_debug("Unsetting writer (was %s) and starting new election",
peer_writer? peer_writer : "unset");
free(peer_writer);
peer_writer = NULL;
election_vote(attrd_cluster);
break;
case election_lost:
/* The election API should really distinguish between "we just lost
* to this peer" and "we already lost previously, and we are
* discarding this vote for some reason", but it doesn't.
*
* In the first case, we want to tentatively set the peer writer to
* this peer, even though another peer may eventually win (which we
* will learn via attrd_check_for_new_writer()), so
* attrd_start_election_if_needed() doesn't start a new election.
*
* Approximate a test for that case as best as possible.
*/
if ((peer_writer == NULL) || (previous != election_lost)) {
pcmk__str_update(&peer_writer, peer->name);
crm_debug("Election lost, presuming %s is writer for now",
peer_writer);
}
break;
case election_in_progress:
election_check(attrd_cluster);
break;
default:
crm_info("Ignoring election op from %s due to error", peer->name);
break;
}
}
bool
attrd_check_for_new_writer(const pcmk__node_status_t *peer, const xmlNode *xml)
{
int peer_state = 0;
crm_element_value_int(xml, PCMK__XA_ATTR_WRITER, &peer_state);
if (peer_state == election_won) {
if ((election_state(attrd_cluster) == election_won)
&& !pcmk__str_eq(peer->name, attrd_cluster->priv->node_name,
pcmk__str_casei)) {
crm_notice("Detected another attribute writer (%s), starting new "
"election",
peer->name);
election_vote(attrd_cluster);
} else if (!pcmk__str_eq(peer->name, peer_writer, pcmk__str_casei)) {
crm_notice("Recorded new attribute writer: %s (was %s)",
peer->name, pcmk__s(peer_writer, "unset"));
pcmk__str_update(&peer_writer, peer->name);
}
}
return (peer_state == election_won);
}
void
attrd_declare_winner(void)
{
crm_notice("Recorded local node as attribute writer (was %s)",
(peer_writer? peer_writer : "unset"));
pcmk__str_update(&peer_writer, attrd_cluster->priv->node_name);
}
void
attrd_remove_voter(const pcmk__node_status_t *peer)
{
election_remove(attrd_cluster, peer->name);
if ((peer_writer != NULL)
&& pcmk__str_eq(peer->name, peer_writer, pcmk__str_casei)) {
free(peer_writer);
peer_writer = NULL;
crm_notice("Lost attribute writer %s", peer->name);
/* Clear any election dampening in effect. Otherwise, if the lost writer
* had just won, the election could fizzle out with no new writer.
*/
election_clear_dampening(attrd_cluster);
/* If the writer received attribute updates during its shutdown, it will
* not have written them to the CIB. Ensure we get a new writer so they
* are written out. This means that every node that sees the writer
* leave will start a new election, but that's better than losing
* attributes.
*/
attrd_start_election_if_needed();
/* If an election is in progress, we need to call election_check(), in case
* this lost peer is the only one that hasn't voted, otherwise the election
* would be pending until it's timed out.
*/
} else if (election_state(attrd_cluster) == election_in_progress) {
crm_debug("Checking election status upon loss of voter %s", peer->name);
election_check(attrd_cluster);
}
}
void
attrd_xml_add_writer(xmlNode *xml)
{
crm_xml_add_int(xml, PCMK__XA_ATTR_WRITER, election_state(attrd_cluster));
}
diff --git a/daemons/attrd/attrd_ipc.c b/daemons/attrd/attrd_ipc.c
index fd917a37bb..e030c125e7 100644
--- a/daemons/attrd/attrd_ipc.c
+++ b/daemons/attrd/attrd_ipc.c
@@ -1,630 +1,630 @@
/*
- * 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.
*
* This source code is licensed under the GNU General Public License version 2
* or later (GPLv2+) WITHOUT ANY WARRANTY.
*/
#include <crm_internal.h>
#include <errno.h>
#include <stdint.h>
#include <stdlib.h>
#include <inttypes.h> // PRIu32
#include <sys/types.h>
#include <crm/cluster.h>
#include <crm/cluster/internal.h>
#include <crm/common/acl_internal.h>
#include <crm/common/ipc_internal.h>
#include <crm/common/logging.h>
#include <crm/common/results.h>
#include <crm/common/strings_internal.h>
#include <crm/common/util.h>
#include <crm/common/xml.h>
#include "pacemaker-attrd.h"
static qb_ipcs_service_t *ipcs = NULL;
/*!
* \internal
* \brief Build the XML reply to a client query
*
* \param[in] attr Name of requested attribute
* \param[in] host Name of requested host (or NULL for all hosts)
*
* \return New XML reply
* \note Caller is responsible for freeing the resulting XML
*/
static xmlNode *build_query_reply(const char *attr, const char *host)
{
xmlNode *reply = pcmk__xe_create(NULL, __func__);
attribute_t *a;
crm_xml_add(reply, PCMK__XA_T, PCMK__VALUE_ATTRD);
crm_xml_add(reply, PCMK__XA_SUBT, PCMK__ATTRD_CMD_QUERY);
crm_xml_add(reply, PCMK__XA_ATTR_VERSION, ATTRD_PROTOCOL_VERSION);
/* If desired attribute exists, add its value(s) to the reply */
a = g_hash_table_lookup(attributes, attr);
if (a) {
attribute_value_t *v;
xmlNode *host_value;
crm_xml_add(reply, PCMK__XA_ATTR_NAME, attr);
/* Allow caller to use "localhost" to refer to local node */
if (pcmk__str_eq(host, "localhost", pcmk__str_casei)) {
host = attrd_cluster->priv->node_name;
crm_trace("Mapped localhost to %s", host);
}
/* If a specific node was requested, add its value */
if (host) {
v = g_hash_table_lookup(a->values, host);
host_value = pcmk__xe_create(reply, PCMK_XE_NODE);
pcmk__xe_add_node(host_value, host, 0);
crm_xml_add(host_value, PCMK__XA_ATTR_VALUE,
(v? v->current : NULL));
/* Otherwise, add all nodes' values */
} else {
GHashTableIter iter;
g_hash_table_iter_init(&iter, a->values);
while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &v)) {
host_value = pcmk__xe_create(reply, PCMK_XE_NODE);
pcmk__xe_add_node(host_value, v->nodename, 0);
crm_xml_add(host_value, PCMK__XA_ATTR_VALUE, v->current);
}
}
}
return reply;
}
xmlNode *
attrd_client_clear_failure(pcmk__request_t *request)
{
xmlNode *xml = request->xml;
const char *rsc, *op, *interval_spec;
if (minimum_protocol_version >= 2) {
/* Propagate to all peers (including ourselves).
* This ends up at attrd_peer_message().
*/
attrd_send_message(NULL, xml, false);
pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
return NULL;
}
rsc = crm_element_value(xml, PCMK__XA_ATTR_RESOURCE);
op = crm_element_value(xml, PCMK__XA_ATTR_CLEAR_OPERATION);
interval_spec = crm_element_value(xml, PCMK__XA_ATTR_CLEAR_INTERVAL);
/* Map this to an update */
crm_xml_add(xml, PCMK_XA_TASK, PCMK__ATTRD_CMD_UPDATE);
/* Add regular expression matching desired attributes */
if (rsc) {
char *pattern;
if (op == NULL) {
pattern = crm_strdup_printf(ATTRD_RE_CLEAR_ONE, rsc);
} else {
guint interval_ms = 0U;
pcmk_parse_interval_spec(interval_spec, &interval_ms);
pattern = crm_strdup_printf(ATTRD_RE_CLEAR_OP,
rsc, op, interval_ms);
}
crm_xml_add(xml, PCMK__XA_ATTR_REGEX, pattern);
free(pattern);
} else {
crm_xml_add(xml, PCMK__XA_ATTR_REGEX, ATTRD_RE_CLEAR_ALL);
}
/* Make sure attribute and value are not set, so we delete via regex */
pcmk__xe_remove_attr(xml, PCMK__XA_ATTR_NAME);
pcmk__xe_remove_attr(xml, PCMK__XA_ATTR_VALUE);
return attrd_client_update(request);
}
xmlNode *
attrd_client_peer_remove(pcmk__request_t *request)
{
xmlNode *xml = request->xml;
// Host and ID are not used in combination, rather host has precedence
const char *host = crm_element_value(xml, PCMK__XA_ATTR_HOST);
char *host_alloc = NULL;
attrd_send_ack(request->ipc_client, request->ipc_id, request->ipc_flags);
if (host == NULL) {
int nodeid = 0;
crm_element_value_int(xml, PCMK__XA_ATTR_HOST_ID, &nodeid);
if (nodeid > 0) {
pcmk__node_status_t *node = NULL;
char *host_alloc = NULL;
node = pcmk__search_node_caches(nodeid, NULL, NULL,
pcmk__node_search_cluster_member);
if ((node != NULL) && (node->name != NULL)) {
// Use cached name if available
host = node->name;
} else {
// Otherwise ask cluster layer
host_alloc = pcmk__cluster_node_name(nodeid);
host = host_alloc;
}
pcmk__xe_add_node(xml, host, 0);
}
}
if (host) {
crm_info("Client %s is requesting all values for %s be removed",
pcmk__client_name(request->ipc_client), host);
attrd_send_message(NULL, xml, false); /* ends up at attrd_peer_message() */
free(host_alloc);
} else {
crm_info("Ignoring request by client %s to remove all peer values without specifying peer",
pcmk__client_name(request->ipc_client));
}
pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
return NULL;
}
xmlNode *
attrd_client_query(pcmk__request_t *request)
{
xmlNode *query = request->xml;
xmlNode *reply = NULL;
const char *attr = NULL;
crm_debug("Query arrived from %s", pcmk__client_name(request->ipc_client));
/* Request must specify attribute name to query */
attr = crm_element_value(query, PCMK__XA_ATTR_NAME);
if (attr == NULL) {
pcmk__format_result(&request->result, CRM_EX_ERROR, PCMK_EXEC_ERROR,
"Ignoring malformed query from %s (no attribute name given)",
pcmk__client_name(request->ipc_client));
return NULL;
}
/* Build the XML reply */
reply = build_query_reply(attr,
crm_element_value(query, PCMK__XA_ATTR_HOST));
if (reply == NULL) {
pcmk__format_result(&request->result, CRM_EX_ERROR, PCMK_EXEC_ERROR,
"Could not respond to query from %s: could not create XML reply",
pcmk__client_name(request->ipc_client));
return NULL;
} else {
pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
}
request->ipc_client->request_id = 0;
return reply;
}
xmlNode *
attrd_client_refresh(pcmk__request_t *request)
{
crm_info("Updating all attributes");
attrd_send_ack(request->ipc_client, request->ipc_id, request->ipc_flags);
attrd_write_attributes(attrd_write_all|attrd_write_no_delay);
pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
return NULL;
}
static void
handle_missing_host(xmlNode *xml)
{
if (crm_element_value(xml, PCMK__XA_ATTR_HOST) == NULL) {
crm_trace("Inferring local node %s with XML ID %s",
attrd_cluster->priv->node_name,
attrd_cluster->priv->node_xml_id);
crm_xml_add(xml, PCMK__XA_ATTR_HOST, attrd_cluster->priv->node_name);
crm_xml_add(xml, PCMK__XA_ATTR_HOST_ID,
attrd_cluster->priv->node_xml_id);
}
}
/* Convert a single IPC message with a regex into one with multiple children, one
* for each regex match.
*/
static int
expand_regexes(xmlNode *xml, const char *attr, const char *value, const char *regex)
{
if (attr == NULL && regex) {
bool matched = false;
GHashTableIter aIter;
regex_t r_patt;
crm_debug("Setting %s to %s", regex, value);
if (regcomp(&r_patt, regex, REG_EXTENDED|REG_NOSUB)) {
return EINVAL;
}
g_hash_table_iter_init(&aIter, attributes);
while (g_hash_table_iter_next(&aIter, (gpointer *) & attr, NULL)) {
int status = regexec(&r_patt, attr, 0, NULL, 0);
if (status == 0) {
xmlNode *child = pcmk__xe_create(xml, PCMK_XE_OP);
crm_trace("Matched %s with %s", attr, regex);
matched = true;
/* Copy all the non-conflicting attributes from the parent over,
* but remove the regex and replace it with the name.
*/
pcmk__xe_copy_attrs(child, xml, pcmk__xaf_no_overwrite);
pcmk__xe_remove_attr(child, PCMK__XA_ATTR_REGEX);
crm_xml_add(child, PCMK__XA_ATTR_NAME, attr);
}
}
regfree(&r_patt);
/* Return a code if we never matched anything. This should not be treated
* as an error. It indicates there was a regex, and it was a valid regex,
* but simply did not match anything and the caller should not continue
* doing any regex-related processing.
*/
if (!matched) {
return pcmk_rc_op_unsatisfied;
}
} else if (attr == NULL) {
return pcmk_rc_bad_nvpair;
}
return pcmk_rc_ok;
}
static int
handle_regexes(pcmk__request_t *request)
{
xmlNode *xml = request->xml;
int rc = pcmk_rc_ok;
const char *attr = crm_element_value(xml, PCMK__XA_ATTR_NAME);
const char *value = crm_element_value(xml, PCMK__XA_ATTR_VALUE);
const char *regex = crm_element_value(xml, PCMK__XA_ATTR_REGEX);
rc = expand_regexes(xml, attr, value, regex);
if (rc == EINVAL) {
pcmk__format_result(&request->result, CRM_EX_ERROR, PCMK_EXEC_ERROR,
"Bad regex '%s' for update from client %s", regex,
pcmk__client_name(request->ipc_client));
} else if (rc == pcmk_rc_bad_nvpair) {
crm_err("Update request did not specify attribute or regular expression");
pcmk__format_result(&request->result, CRM_EX_ERROR, PCMK_EXEC_ERROR,
"Client %s update request did not specify attribute or regular expression",
pcmk__client_name(request->ipc_client));
}
return rc;
}
static int
handle_value_expansion(const char **value, xmlNode *xml, const char *op,
const char *attr)
{
attribute_t *a = g_hash_table_lookup(attributes, attr);
if (a == NULL && pcmk__str_eq(op, PCMK__ATTRD_CMD_UPDATE_DELAY, pcmk__str_none)) {
return EINVAL;
}
if (*value && attrd_value_needs_expansion(*value)) {
int int_value;
attribute_value_t *v = NULL;
if (a) {
const char *host = crm_element_value(xml, PCMK__XA_ATTR_HOST);
v = g_hash_table_lookup(a->values, host);
}
int_value = attrd_expand_value(*value, (v? v->current : NULL));
crm_info("Expanded %s=%s to %d", attr, *value, int_value);
crm_xml_add_int(xml, PCMK__XA_ATTR_VALUE, int_value);
/* Replacing the value frees the previous memory, so re-query it */
*value = crm_element_value(xml, PCMK__XA_ATTR_VALUE);
}
return pcmk_rc_ok;
}
static void
send_update_msg_to_cluster(pcmk__request_t *request, xmlNode *xml)
{
if (pcmk__str_eq(attrd_request_sync_point(xml), PCMK__VALUE_CLUSTER, pcmk__str_none)) {
/* The client is waiting on the cluster-wide sync point. In this case,
* the response ACK is not sent until this attrd broadcasts the update
* and receives its own confirmation back from all peers.
*/
attrd_expect_confirmations(request, attrd_cluster_sync_point_update);
attrd_send_message(NULL, xml, true); /* ends up at attrd_peer_message() */
} else {
/* The client is either waiting on the local sync point or was not
* waiting on any sync point at all. For the local sync point, the
* response ACK is sent in attrd_peer_update. For clients not
* waiting on any sync point, the response ACK is sent in
* handle_update_request immediately before this function was called.
*/
attrd_send_message(NULL, xml, false); /* ends up at attrd_peer_message() */
}
}
static int
send_child_update(xmlNode *child, void *data)
{
pcmk__request_t *request = (pcmk__request_t *) data;
/* Calling pcmk__set_result is handled by one of these calls to
* attrd_client_update, so no need to do it again here.
*/
request->xml = child;
attrd_client_update(request);
return pcmk_rc_ok;
}
xmlNode *
attrd_client_update(pcmk__request_t *request)
{
xmlNode *xml = NULL;
const char *attr, *value, *regex;
CRM_CHECK((request != NULL) && (request->xml != NULL), return NULL);
xml = request->xml;
/* If the message has children, that means it is a message from a newer
* client that supports sending multiple operations at a time. There are
* two ways we can handle that.
*/
if (xml->children != NULL) {
if (ATTRD_SUPPORTS_MULTI_MESSAGE(minimum_protocol_version)) {
/* First, if all peers support a certain protocol version, we can
* just broadcast the big message and they'll handle it. However,
* we also need to apply all the transformations in this function
* to the children since they don't happen anywhere else.
*/
for (xmlNode *child = pcmk__xe_first_child(xml, PCMK_XE_OP, NULL,
NULL);
child != NULL; child = pcmk__xe_next(child, PCMK_XE_OP)) {
attr = crm_element_value(child, PCMK__XA_ATTR_NAME);
value = crm_element_value(child, PCMK__XA_ATTR_VALUE);
handle_missing_host(child);
if (handle_value_expansion(&value, child, request->op, attr) == EINVAL) {
pcmk__format_result(&request->result, CRM_EX_NOSUCH, PCMK_EXEC_ERROR,
"Attribute %s does not exist", attr);
return NULL;
}
}
send_update_msg_to_cluster(request, xml);
pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
} else {
/* Save the original xml node pointer so it can be restored after iterating
* over all the children.
*/
xmlNode *orig_xml = request->xml;
/* Second, if they do not support that protocol version, split it
* up into individual messages and call attrd_client_update on
* each one.
*/
pcmk__xe_foreach_child(xml, PCMK_XE_OP, send_child_update, request);
request->xml = orig_xml;
}
return NULL;
}
attr = crm_element_value(xml, PCMK__XA_ATTR_NAME);
value = crm_element_value(xml, PCMK__XA_ATTR_VALUE);
regex = crm_element_value(xml, PCMK__XA_ATTR_REGEX);
if (handle_regexes(request) != pcmk_rc_ok) {
/* Error handling was already dealt with in handle_regexes, so just return. */
return NULL;
} else if (regex) {
/* Recursively call attrd_client_update on the new message with regexes
* expanded. If supported by the attribute daemon, this means that all
* matches can also be handled atomically.
*/
return attrd_client_update(request);
}
handle_missing_host(xml);
if (handle_value_expansion(&value, xml, request->op, attr) == EINVAL) {
pcmk__format_result(&request->result, CRM_EX_NOSUCH, PCMK_EXEC_ERROR,
"Attribute %s does not exist", attr);
return NULL;
}
crm_debug("Broadcasting %s[%s]=%s%s",
attr, crm_element_value(xml, PCMK__XA_ATTR_HOST),
value, (attrd_election_won()? " (writer)" : ""));
send_update_msg_to_cluster(request, xml);
pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
return NULL;
}
/*!
* \internal
* \brief Accept a new client IPC connection
*
* \param[in,out] c New connection
* \param[in] uid Client user id
* \param[in] gid Client group id
*
* \return pcmk_ok on success, -errno otherwise
*/
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;
}
if (pcmk__new_client(c, uid, gid) == NULL) {
return -ENOMEM;
}
return pcmk_ok;
}
/*!
* \internal
* \brief Destroy a client IPC connection
*
* \param[in] c Connection to destroy
*
* \return FALSE (i.e. do not re-run this callback)
*/
static int32_t
attrd_ipc_closed(qb_ipcs_connection_t *c)
{
pcmk__client_t *client = pcmk__find_client(c);
if (client == NULL) {
crm_trace("Ignoring request to clean up unknown connection %p", c);
} else {
crm_trace("Cleaning up closed client connection %p", c);
/* Remove the client from the sync point waitlist if it's present. */
attrd_remove_client_from_waitlist(client);
/* And no longer wait for confirmations from any peers. */
attrd_do_not_wait_for_client(client);
pcmk__free_client(client);
}
return FALSE;
}
/*!
* \internal
* \brief Destroy a client IPC connection
*
* \param[in,out] c Connection to destroy
*
* \note We handle a destroyed connection the same as a closed one,
* but we need a separate handler because the return type is different.
*/
static void
attrd_ipc_destroy(qb_ipcs_connection_t *c)
{
crm_trace("Destroying client connection %p", c);
attrd_ipc_closed(c);
}
static int32_t
attrd_ipc_dispatch(qb_ipcs_connection_t * c, void *data, size_t size)
{
uint32_t id = 0;
uint32_t flags = 0;
pcmk__client_t *client = pcmk__find_client(c);
xmlNode *xml = NULL;
// Sanity-check, and parse XML from IPC data
CRM_CHECK((c != NULL) && (client != NULL), return 0);
if (data == NULL) {
crm_debug("No IPC data from PID %d", pcmk__client_pid(c));
return 0;
}
xml = pcmk__client_data2xml(client, data, &id, &flags);
if (xml == NULL) {
crm_debug("Unrecognizable IPC data from PID %d", pcmk__client_pid(c));
pcmk__ipc_send_ack(client, id, flags, PCMK__XE_ACK, NULL,
CRM_EX_PROTOCOL);
return 0;
} else {
pcmk__request_t request = {
.ipc_client = client,
.ipc_id = id,
.ipc_flags = flags,
.peer = NULL,
.xml = xml,
.call_options = 0,
.result = PCMK__UNKNOWN_RESULT,
};
pcmk__assert(client->user != NULL);
pcmk__update_acl_user(xml, PCMK__XA_ATTR_USER, client->user);
request.op = crm_element_value_copy(request.xml, PCMK_XA_TASK);
CRM_CHECK(request.op != NULL, return 0);
attrd_handle_request(&request);
pcmk__reset_request(&request);
}
pcmk__xml_free(xml);
return 0;
}
static struct qb_ipcs_service_handlers ipc_callbacks = {
.connection_accept = attrd_ipc_accept,
.connection_created = NULL,
.msg_process = attrd_ipc_dispatch,
.connection_closed = attrd_ipc_closed,
.connection_destroyed = attrd_ipc_destroy
};
void
attrd_ipc_fini(void)
{
if (ipcs != NULL) {
pcmk__drop_all_clients(ipcs);
qb_ipcs_destroy(ipcs);
ipcs = NULL;
}
attrd_unregister_handlers();
pcmk__client_cleanup();
}
/*!
* \internal
* \brief Set up attrd IPC communication
*/
void
attrd_init_ipc(void)
{
pcmk__serve_attrd_ipc(&ipcs, &ipc_callbacks);
}
diff --git a/daemons/attrd/attrd_utils.c b/daemons/attrd/attrd_utils.c
index f219b8862d..f6c6f0ac52 100644
--- a/daemons/attrd/attrd_utils.c
+++ b/daemons/attrd/attrd_utils.c
@@ -1,306 +1,277 @@
/*
- * 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.
*
* This source code is licensed under the GNU General Public License version 2
* or later (GPLv2+) WITHOUT ANY WARRANTY.
*/
#include <crm_internal.h>
#include <stdio.h>
#include <stdbool.h>
#include <errno.h>
#include <glib.h>
#include <regex.h>
#include <sys/types.h>
#include <crm/crm.h>
#include <crm/common/ipc_internal.h>
#include <crm/common/mainloop.h>
#include <crm/common/xml.h>
#include "pacemaker-attrd.h"
cib_t *the_cib = NULL;
-static bool requesting_shutdown = false;
static bool shutting_down = false;
static GMainLoop *mloop = NULL;
/* A hash table storing information on the protocol version of each peer attrd.
* The key is the peer's uname, and the value is the protocol version number.
*/
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;
}
/*!
* \internal
* \brief Exit (using mainloop or not, as appropriate)
*
* \param[in] nsig Ignored
*/
void
attrd_shutdown(int nsig)
{
// Tell various functions not to do anthing
shutting_down = true;
// Don't respond to signals while shutting down
mainloop_destroy_signal(SIGTERM);
mainloop_destroy_signal(SIGCHLD);
mainloop_destroy_signal(SIGPIPE);
mainloop_destroy_signal(SIGUSR1);
mainloop_destroy_signal(SIGUSR2);
mainloop_destroy_signal(SIGTRAP);
attrd_free_waitlist();
attrd_free_confirmations();
if (peer_protocol_vers != NULL) {
g_hash_table_destroy(peer_protocol_vers);
peer_protocol_vers = NULL;
}
if ((mloop == NULL) || !g_main_loop_is_running(mloop)) {
/* If there's no main loop active, just exit. This should be possible
* only if we get SIGTERM in brief windows at start-up and shutdown.
*/
crm_exit(CRM_EX_OK);
} else {
g_main_loop_quit(mloop);
g_main_loop_unref(mloop);
}
}
/*!
* \internal
* \brief Create a main loop for attrd
*/
void
attrd_init_mainloop(void)
{
mloop = g_main_loop_new(NULL, FALSE);
}
/*!
* \internal
* \brief Run attrd main loop
*/
void
attrd_run_mainloop(void)
{
g_main_loop_run(mloop);
}
/* strlen("value") */
#define plus_plus_len (5)
/*!
* \internal
* \brief Check whether an attribute value should be expanded
*
* \param[in] value Attribute value to check
*
* \return true if value needs expansion, false otherwise
*/
bool
attrd_value_needs_expansion(const char *value)
{
return ((strlen(value) >= (plus_plus_len + 2))
&& (value[plus_plus_len] == '+')
&& ((value[plus_plus_len + 1] == '+')
|| (value[plus_plus_len + 1] == '=')));
}
/*!
* \internal
* \brief Expand an increment expression into an integer
*
* \param[in] value Attribute increment expression to expand
* \param[in] old_value Previous value of attribute
*
* \return Expanded value
*/
int
attrd_expand_value(const char *value, const char *old_value)
{
int increment = 1;
int score = 0;
if (pcmk_parse_score(old_value, &score, 0) != pcmk_rc_ok) {
return 0; // Original value is not a score
}
// value++ means increment by one, value+=OFFSET means incremement by OFFSET
if ((value[plus_plus_len + 1] != '+')
&& (pcmk_parse_score(value + plus_plus_len + 2, &increment,
0) != pcmk_rc_ok)) {
increment = 0; // Invalid increment
}
if (increment < 0) {
return QB_MAX(score + increment, -PCMK_SCORE_INFINITY);
}
return QB_MIN(score + increment, PCMK_SCORE_INFINITY);
}
/*!
* \internal
* \brief Create regular expression matching failure-related attributes
*
* \param[out] regex Where to store created regular expression
* \param[in] rsc Name of resource to clear (or NULL for all)
* \param[in] op Operation to clear if rsc is specified (or NULL for all)
* \param[in] interval_ms Interval of operation to clear if op is specified
*
* \return pcmk_ok on success, -EINVAL if arguments are invalid
*
* \note The caller is responsible for freeing the result with regfree().
*/
int
attrd_failure_regex(regex_t *regex, const char *rsc, const char *op,
guint interval_ms)
{
char *pattern = NULL;
int rc;
/* Create a pattern that matches desired attributes */
if (rsc == NULL) {
pattern = pcmk__str_copy(ATTRD_RE_CLEAR_ALL);
} else if (op == NULL) {
pattern = crm_strdup_printf(ATTRD_RE_CLEAR_ONE, rsc);
} else {
pattern = crm_strdup_printf(ATTRD_RE_CLEAR_OP, rsc, op, interval_ms);
}
/* Compile pattern into regular expression */
crm_trace("Clearing attributes matching %s", pattern);
rc = regcomp(regex, pattern, REG_EXTENDED|REG_NOSUB);
free(pattern);
return (rc == 0)? pcmk_ok : -EINVAL;
}
void
attrd_free_attribute_value(gpointer data)
{
attribute_value_t *v = data;
free(v->nodename);
free(v->current);
free(v->requested);
free(v);
}
void
attrd_free_attribute(gpointer data)
{
attribute_t *a = data;
if(a) {
free(a->id);
free(a->set_id);
free(a->set_type);
free(a->user);
mainloop_timer_del(a->timer);
g_hash_table_destroy(a->values);
free(a);
}
}
/*!
* \internal
* \brief When a peer node leaves the cluster, stop tracking its protocol version.
*
* \param[in] host The peer node's uname to be removed
*/
void
attrd_remove_peer_protocol_ver(const char *host)
{
if (peer_protocol_vers != NULL) {
g_hash_table_remove(peer_protocol_vers, host);
}
}
/*!
* \internal
* \brief When a peer node broadcasts a message with its protocol version, keep
* track of that information.
*
* We keep track of each peer's protocol version so we know which peers to
* expect confirmation messages from when handling cluster-wide sync points.
* We additionally keep track of the lowest protocol version supported by all
* peers so we know when we can send IPC messages containing more than one
* request.
*
* \param[in] host The peer node's uname to be tracked
* \param[in] value The peer node's protocol version
*/
void
attrd_update_minimum_protocol_ver(const char *host, const char *value)
{
int ver;
if (peer_protocol_vers == NULL) {
peer_protocol_vers = pcmk__strkey_table(free, NULL);
}
pcmk__scan_min_int(value, &ver, 0);
if (ver > 0) {
/* Record the peer attrd's protocol version. */
g_hash_table_insert(peer_protocol_vers, pcmk__str_copy(host),
GINT_TO_POINTER(ver));
/* If the protocol version is a new minimum, record it as such. */
if (minimum_protocol_version == -1 || ver < minimum_protocol_version) {
minimum_protocol_version = ver;
crm_trace("Set minimum attrd protocol version to %d",
minimum_protocol_version);
}
}
}
diff --git a/daemons/attrd/pacemaker-attrd.h b/daemons/attrd/pacemaker-attrd.h
index 8399ad925e..193964e41c 100644
--- a/daemons/attrd/pacemaker-attrd.h
+++ b/daemons/attrd/pacemaker-attrd.h
@@ -1,264 +1,262 @@
/*
* Copyright 2013-2025 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.
*/
#ifndef PACEMAKER_ATTRD__H
# define PACEMAKER_ATTRD__H
#include <regex.h>
#include <glib.h>
#include <crm/crm.h>
#include <crm/cluster.h>
#include <crm/cluster/election_internal.h>
#include <crm/common/messages_internal.h>
#include <crm/cib/cib_types.h>
/*
* Legacy attrd (all pre-1.1.11 Pacemaker versions, plus all versions when used
* with the no-longer-supported CMAN or corosync-plugin stacks) is unversioned.
*
* With atomic attrd, each attrd will send ATTRD_PROTOCOL_VERSION with every
* peer request and reply. As of Pacemaker 2.0.0, at start-up each attrd will
* also set a private attribute for itself with its version, so any attrd can
* determine the minimum version supported by all peers.
*
* Protocol Pacemaker Significant changes
* -------- --------- -------------------
* 1 1.1.11 PCMK__ATTRD_CMD_UPDATE (PCMK__XA_ATTR_NAME only),
* PCMK__ATTRD_CMD_PEER_REMOVE, PCMK__ATTRD_CMD_REFRESH,
* "flush", PCMK__ATTRD_CMD_SYNC_RESPONSE
* 1 1.1.13 PCMK__ATTRD_CMD_UPDATE (with PCMK__XA_ATTR_REGEX),
* PCMK__ATTRD_CMD_QUERY
* 1 1.1.15 PCMK__ATTRD_CMD_UPDATE_BOTH,
* PCMK__ATTRD_CMD_UPDATE_DELAY
* 2 1.1.17 PCMK__ATTRD_CMD_CLEAR_FAILURE
* 3 2.1.1 PCMK__ATTRD_CMD_SYNC_RESPONSE indicates remote nodes
* 4 2.1.5 Multiple attributes can be updated in a single IPC
* message
* 5 2.1.5 Peers can request confirmation of a sent message
* 6 2.1.7 PCMK__ATTRD_CMD_PEER_REMOVE supports PCMK__XA_REAP
* 7 3.0.0 "flush" support dropped
*/
#define ATTRD_PROTOCOL_VERSION "7"
#define ATTRD_SUPPORTS_MULTI_MESSAGE(x) ((x) >= 4)
#define ATTRD_SUPPORTS_CONFIRMATION(x) ((x) >= 5)
#define attrd_send_ack(client, id, flags) \
pcmk__ipc_send_ack((client), (id), (flags), PCMK__XE_ACK, \
ATTRD_PROTOCOL_VERSION, CRM_EX_INDETERMINATE)
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);
int attrd_cib_connect(int max_retry);
void attrd_cib_disconnect(void);
void attrd_cib_init(void);
void attrd_cib_erase_transient_attrs(const char *node);
bool attrd_value_needs_expansion(const char *value);
int attrd_expand_value(const char *value, const char *old_value);
/* regular expression to clear failures of all resources */
#define ATTRD_RE_CLEAR_ALL \
"^(" PCMK__FAIL_COUNT_PREFIX "|" PCMK__LAST_FAILURE_PREFIX ")-"
/* regular expression to clear failure of all operations for one resource
* (format takes resource name)
*/
#define ATTRD_RE_CLEAR_ONE ATTRD_RE_CLEAR_ALL "%s#.+_[0-9]+$"
/* regular expression to clear failure of one operation for one resource
* (format takes resource name, operation name, and interval)
*/
#define ATTRD_RE_CLEAR_OP ATTRD_RE_CLEAR_ALL "%s#%s_%u$"
int attrd_failure_regex(regex_t *regex, const char *rsc, const char *op,
guint interval_ms);
extern cib_t *the_cib;
extern crm_exit_t attrd_exit_status;
/* Alerts */
extern lrmd_t *the_lrmd;
extern crm_trigger_t *attrd_config_read;
void attrd_lrmd_disconnect(void);
gboolean attrd_read_options(gpointer user_data);
int attrd_send_attribute_alert(const char *node, const char *node_xml_id,
const char *attr, const char *value);
// Elections
void attrd_election_init(void);
void attrd_start_election_if_needed(void);
bool attrd_election_won(void);
void attrd_handle_election_op(const pcmk__node_status_t *peer, xmlNode *xml);
bool attrd_check_for_new_writer(const pcmk__node_status_t *peer,
const xmlNode *xml);
void attrd_declare_winner(void);
void attrd_remove_voter(const pcmk__node_status_t *peer);
void attrd_xml_add_writer(xmlNode *xml);
enum attrd_attr_flags {
attrd_attr_none = 0U,
// At least one of attribute's values has changed since last write
attrd_attr_changed = (1U << 0),
// At least one of attribute's values has an unknown node XML ID
attrd_attr_node_unknown = (1U << 1),
// This attribute should never be written to the CIB
attrd_attr_is_private = (1U << 2),
// Ignore any configured delay for next write of this attribute
attrd_attr_force_write = (1U << 3),
};
typedef struct attribute_s {
char *id; // Attribute name
char *set_type; // PCMK_XE_INSTANCE_ATTRIBUTES or PCMK_XE_UTILIZATION
char *set_id; // Set's XML ID to use when writing
char *user; // ACL user to use for CIB writes
int update; // Call ID of pending write
int timeout_ms; // How long to wait for more changes before writing
uint32_t flags; // Group of enum attrd_attr_flags
GHashTable *values; // Key: node name, value: attribute_value_t
mainloop_timer_t *timer; // Timer to use for timeout_ms
} attribute_t;
#define attrd_set_attr_flags(attr, flags_to_set) do { \
(attr)->flags = pcmk__set_flags_as(__func__, __LINE__, \
LOG_TRACE, "Value for attribute", (attr)->id, \
(attr)->flags, (flags_to_set), #flags_to_set); \
} while (0)
#define attrd_clear_attr_flags(attr, flags_to_clear) do { \
(attr)->flags = pcmk__clear_flags_as(__func__, __LINE__, \
LOG_TRACE, "Value for attribute", (attr)->id, \
(attr)->flags, (flags_to_clear), #flags_to_clear); \
} while (0)
enum attrd_value_flags {
attrd_value_none = 0U,
attrd_value_remote = (1U << 0), // Value is for Pacemaker Remote node
attrd_value_from_peer = (1U << 1), // Value is from peer sync response
};
typedef struct attribute_value_s {
char *nodename; // Node that this value is for
char *current; // Attribute value
char *requested; // Value specified in pending CIB write, if any
uint32_t flags; // Group of attrd_value_flags
} attribute_value_t;
#define attrd_set_value_flags(attr_value, flags_to_set) do { \
(attr_value)->flags = pcmk__set_flags_as(__func__, __LINE__, \
LOG_TRACE, "Value for node", (attr_value)->nodename, \
(attr_value)->flags, (flags_to_set), #flags_to_set); \
} while (0)
#define attrd_clear_value_flags(attr_value, flags_to_clear) do { \
(attr_value)->flags = pcmk__clear_flags_as(__func__, __LINE__, \
LOG_TRACE, "Value for node", (attr_value)->nodename, \
(attr_value)->flags, (flags_to_clear), #flags_to_clear); \
} while (0)
extern pcmk_cluster_t *attrd_cluster;
extern GHashTable *attributes;
extern GHashTable *peer_protocol_vers;
#define CIB_OP_TIMEOUT_S 120
int attrd_cluster_connect(void);
void attrd_broadcast_value(const attribute_t *a, const attribute_value_t *v);
void attrd_peer_update(const pcmk__node_status_t *peer, xmlNode *xml,
const char *host, bool filter);
void attrd_peer_sync(pcmk__node_status_t *peer);
void attrd_peer_remove(const char *host, bool uncache, const char *source);
void attrd_peer_clear_failure(pcmk__request_t *request);
void attrd_peer_sync_response(const pcmk__node_status_t *peer, bool peer_won,
xmlNode *xml);
void attrd_broadcast_protocol(void);
xmlNode *attrd_client_peer_remove(pcmk__request_t *request);
xmlNode *attrd_client_clear_failure(pcmk__request_t *request);
xmlNode *attrd_client_update(pcmk__request_t *request);
xmlNode *attrd_client_refresh(pcmk__request_t *request);
xmlNode *attrd_client_query(pcmk__request_t *request);
gboolean attrd_send_message(pcmk__node_status_t *node, xmlNode *data,
bool confirm);
xmlNode *attrd_add_value_xml(xmlNode *parent, const attribute_t *a,
const attribute_value_t *v, bool force_write);
void attrd_clear_value_seen(void);
void attrd_free_attribute(gpointer data);
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,
attrd_write_all = (1 << 0),
attrd_write_no_delay = (1 << 1),
};
void attrd_write_attributes(uint32_t options);
void attrd_write_or_elect_attribute(attribute_t *a);
extern int minimum_protocol_version;
void attrd_remove_peer_protocol_ver(const char *host);
void attrd_update_minimum_protocol_ver(const char *host, const char *value);
mainloop_timer_t *attrd_add_timer(const char *id, int timeout_ms, attribute_t *attr);
void attrd_unregister_handlers(void);
void attrd_handle_request(pcmk__request_t *request);
enum attrd_sync_point {
attrd_sync_point_local,
attrd_sync_point_cluster,
};
typedef int (*attrd_confirmation_action_fn)(xmlNode *);
void attrd_add_client_to_waitlist(pcmk__request_t *request);
void attrd_ack_waitlist_clients(enum attrd_sync_point sync_point, const xmlNode *xml);
int attrd_cluster_sync_point_update(xmlNode *xml);
void attrd_do_not_expect_from_peer(const char *host);
void attrd_do_not_wait_for_client(pcmk__client_t *client);
void attrd_expect_confirmations(pcmk__request_t *request, attrd_confirmation_action_fn fn);
void attrd_free_confirmations(void);
void attrd_handle_confirmation(int callid, const char *host);
void attrd_remove_client_from_waitlist(pcmk__client_t *client);
const char *attrd_request_sync_point(xmlNode *xml);
bool attrd_request_has_sync_point(xmlNode *xml);
extern gboolean stand_alone;
// Node utilities (from attrd_nodes.c)
const char *attrd_get_node_xml_id(const char *node_name);
void attrd_set_node_xml_id(const char *node_name, const char *node_xml_id);
void attrd_forget_node_xml_id(const char *node_name);
void attrd_cleanup_xml_ids(void);
#endif /* PACEMAKER_ATTRD__H */
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sat, Jan 25, 11:39 AM (1 d, 16 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1318342
Default Alt Text
(98 KB)
Attached To
Mode
rP Pacemaker
Attached
Detach File
Event Timeline
Log In to Comment