root/daemons/attrd/attrd_cib.c

/* [previous][next][first][last][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. attrd_cib_destroy_cb
  2. attrd_cib_updated_cb
  3. attrd_cib_connect
  4. attrd_cib_disconnect
  5. attrd_erase_cb
  6. attrd_cib_erase_transient_attrs
  7. attrd_cib_init
  8. attribute_timer_cb
  9. attrd_cib_callback
  10. add_set_attr_update
  11. add_unset_attr_update
  12. add_attr_update
  13. send_alert_attributes_value
  14. set_alert_attribute_value
  15. attrd_add_timer
  16. write_attribute
  17. attrd_write_attributes
  18. attrd_write_or_elect_attribute

   1 /*
   2  * Copyright 2013-2024 the Pacemaker project contributors
   3  *
   4  * The version control history for this file may have further details.
   5  *
   6  * This source code is licensed under the GNU General Public License version 2
   7  * or later (GPLv2+) WITHOUT ANY WARRANTY.
   8  */
   9 
  10 #include <crm_internal.h>
  11 
  12 #include <errno.h>
  13 #include <inttypes.h>   // PRIu32
  14 #include <stdbool.h>
  15 #include <stdlib.h>
  16 #include <glib.h>
  17 
  18 #include <crm/cib/internal.h>       // cib__*
  19 #include <crm/common/logging.h>
  20 #include <crm/common/results.h>
  21 #include <crm/common/strings_internal.h>
  22 #include <crm/common/xml.h>
  23 #include <crm/cluster/internal.h>   // pcmk__get_node()
  24 
  25 #include "pacemaker-attrd.h"
  26 
  27 static int last_cib_op_done = 0;
  28 
  29 static void write_attribute(attribute_t *a, bool ignore_delay);
  30 
  31 static void
  32 attrd_cib_destroy_cb(gpointer user_data)
     /* [previous][next][first][last][top][bottom][index][help] */
  33 {
  34     cib_t *cib = user_data;
  35 
  36     cib->cmds->signoff(cib);
  37 
  38     if (attrd_shutting_down(false)) {
  39         crm_info("Disconnected from the CIB manager");
  40 
  41     } else {
  42         // @TODO This should trigger a reconnect, not a shutdown
  43         crm_crit("Lost connection to the CIB manager, shutting down");
  44         attrd_exit_status = CRM_EX_DISCONNECT;
  45         attrd_shutdown(0);
  46     }
  47 }
  48 
  49 static void
  50 attrd_cib_updated_cb(const char *event, xmlNode *msg)
     /* [previous][next][first][last][top][bottom][index][help] */
  51 {
  52     const xmlNode *patchset = NULL;
  53     const char *client_name = NULL;
  54     bool status_changed = false;
  55 
  56     if (attrd_shutting_down(true)) {
  57         crm_debug("Ignoring CIB change during shutdown");
  58         return;
  59     }
  60 
  61     if (cib__get_notify_patchset(msg, &patchset) != pcmk_rc_ok) {
  62         return;
  63     }
  64 
  65     if (pcmk__cib_element_in_patchset(patchset, PCMK_XE_ALERTS)) {
  66         mainloop_set_trigger(attrd_config_read);
  67     }
  68 
  69     status_changed = pcmk__cib_element_in_patchset(patchset, PCMK_XE_STATUS);
  70 
  71     client_name = crm_element_value(msg, PCMK__XA_CIB_CLIENTNAME);
  72     if (!cib__client_triggers_refresh(client_name)) {
  73         /* This change came from a source that ensured the CIB is consistent
  74          * with our attributes table, so we don't need to write anything out.
  75          */
  76         return;
  77     }
  78 
  79     if (!attrd_election_won()) {
  80         // Don't write attributes if we're not the writer
  81         return;
  82     }
  83 
  84     if (status_changed
  85         || pcmk__cib_element_in_patchset(patchset, PCMK_XE_NODES)) {
  86         /* An unsafe client modified the PCMK_XE_NODES or PCMK_XE_STATUS
  87          * section. Write transient attributes to ensure they're up-to-date in
  88          * the CIB.
  89          */
  90         if (client_name == NULL) {
  91             client_name = crm_element_value(msg, PCMK__XA_CIB_CLIENTID);
  92         }
  93         crm_notice("Updating all attributes after %s event triggered by %s",
  94                    event, pcmk__s(client_name, "(unidentified client)"));
  95 
  96         attrd_write_attributes(attrd_write_all);
  97     }
  98 }
  99 
 100 int
 101 attrd_cib_connect(int max_retry)
     /* [previous][next][first][last][top][bottom][index][help] */
 102 {
 103     static int attempts = 0;
 104 
 105     int rc = -ENOTCONN;
 106 
 107     the_cib = cib_new();
 108     if (the_cib == NULL) {
 109         return -ENOTCONN;
 110     }
 111 
 112     do {
 113         if (attempts > 0) {
 114             sleep(attempts);
 115         }
 116         attempts++;
 117         crm_debug("Connection attempt %d to the CIB manager", attempts);
 118         rc = the_cib->cmds->signon(the_cib, crm_system_name, cib_command);
 119 
 120     } while ((rc != pcmk_ok) && (attempts < max_retry));
 121 
 122     if (rc != pcmk_ok) {
 123         crm_err("Connection to the CIB manager failed: %s " QB_XS " rc=%d",
 124                 pcmk_strerror(rc), rc);
 125         goto cleanup;
 126     }
 127 
 128     crm_debug("Connected to the CIB manager after %d attempts", attempts);
 129 
 130     rc = the_cib->cmds->set_connection_dnotify(the_cib, attrd_cib_destroy_cb);
 131     if (rc != pcmk_ok) {
 132         crm_err("Could not set disconnection callback");
 133         goto cleanup;
 134     }
 135 
 136     rc = the_cib->cmds->add_notify_callback(the_cib,
 137                                             PCMK__VALUE_CIB_DIFF_NOTIFY,
 138                                             attrd_cib_updated_cb);
 139     if (rc != pcmk_ok) {
 140         crm_err("Could not set CIB notification callback");
 141         goto cleanup;
 142     }
 143 
 144     return pcmk_ok;
 145 
 146 cleanup:
 147     cib__clean_up_connection(&the_cib);
 148     return -ENOTCONN;
 149 }
 150 
 151 void
 152 attrd_cib_disconnect(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 153 {
 154     CRM_CHECK(the_cib != NULL, return);
 155     the_cib->cmds->del_notify_callback(the_cib, PCMK__VALUE_CIB_DIFF_NOTIFY,
 156                                        attrd_cib_updated_cb);
 157     cib__clean_up_connection(&the_cib);
 158     mainloop_destroy_trigger(attrd_config_read);
 159 }
 160 
 161 static void
 162 attrd_erase_cb(xmlNode *msg, int call_id, int rc, xmlNode *output,
     /* [previous][next][first][last][top][bottom][index][help] */
 163                void *user_data)
 164 {
 165     const char *node = pcmk__s((const char *) user_data, "a node");
 166 
 167     if (rc == pcmk_ok) {
 168         crm_info("Cleared transient node attributes for %s from CIB", node);
 169     } else {
 170         crm_err("Unable to clear transient node attributes for %s from CIB: %s",
 171                 node, pcmk_strerror(rc));
 172     }
 173 }
 174 
 175 #define XPATH_TRANSIENT "//" PCMK__XE_NODE_STATE    \
 176                         "[@" PCMK_XA_UNAME "='%s']" \
 177                         "/" PCMK__XE_TRANSIENT_ATTRIBUTES
 178 
 179 /*!
 180  * \internal
 181  * \brief Wipe all transient node attributes for a node from the CIB
 182  *
 183  * \param[in] node  Node to clear attributes for
 184  */
 185 void
 186 attrd_cib_erase_transient_attrs(const char *node)
     /* [previous][next][first][last][top][bottom][index][help] */
 187 {
 188     int call_id = 0;
 189     char *xpath = NULL;
 190 
 191     CRM_CHECK(node != NULL, return);
 192 
 193     xpath = crm_strdup_printf(XPATH_TRANSIENT, node);
 194 
 195     crm_debug("Clearing transient node attributes for %s from CIB using %s",
 196               node, xpath);
 197 
 198     call_id = the_cib->cmds->remove(the_cib, xpath, NULL, cib_xpath);
 199     free(xpath);
 200 
 201     the_cib->cmds->register_callback_full(the_cib, call_id, 120, FALSE,
 202                                           pcmk__str_copy(node),
 203                                           "attrd_erase_cb", attrd_erase_cb,
 204                                           free);
 205 }
 206 
 207 /*!
 208  * \internal
 209  * \brief Prepare the CIB after cluster is connected
 210  */
 211 void
 212 attrd_cib_init(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 213 {
 214     /* We have no attribute values in memory, so wipe the CIB to match. This is
 215      * normally done by the DC's controller when this node leaves the cluster, but
 216      * this handles the case where the node restarted so quickly that the
 217      * cluster layer didn't notice.
 218      *
 219      * \todo If the attribute manager respawns after crashing (see
 220      *       PCMK_ENV_RESPAWNED), ideally we'd skip this and sync our attributes
 221      *       from the writer. However, currently we reject any values for us
 222      *       that the writer has, in attrd_peer_update().
 223      */
 224     attrd_cib_erase_transient_attrs(attrd_cluster->priv->node_name);
 225 
 226     // Set a trigger for reading the CIB (for the alerts section)
 227     attrd_config_read = mainloop_add_trigger(G_PRIORITY_HIGH, attrd_read_options, NULL);
 228 
 229     // Always read the CIB at start-up
 230     mainloop_set_trigger(attrd_config_read);
 231 }
 232 
 233 static gboolean
 234 attribute_timer_cb(gpointer data)
     /* [previous][next][first][last][top][bottom][index][help] */
 235 {
 236     attribute_t *a = data;
 237     crm_trace("Dampen interval expired for %s", a->id);
 238     attrd_write_or_elect_attribute(a);
 239     return FALSE;
 240 }
 241 
 242 static void
 243 attrd_cib_callback(xmlNode *msg, int call_id, int rc, xmlNode *output, void *user_data)
     /* [previous][next][first][last][top][bottom][index][help] */
 244 {
 245     int level = LOG_ERR;
 246     GHashTableIter iter;
 247     const char *peer = NULL;
 248     attribute_value_t *v = NULL;
 249 
 250     char *name = user_data;
 251     attribute_t *a = g_hash_table_lookup(attributes, name);
 252 
 253     if(a == NULL) {
 254         crm_info("Attribute %s no longer exists", name);
 255         return;
 256     }
 257 
 258     a->update = 0;
 259     if (rc == pcmk_ok && call_id < 0) {
 260         rc = call_id;
 261     }
 262 
 263     switch (rc) {
 264         case pcmk_ok:
 265             level = LOG_INFO;
 266             last_cib_op_done = call_id;
 267             if (a->timer && !a->timeout_ms) {
 268                 // Remove temporary dampening for failed writes
 269                 mainloop_timer_del(a->timer);
 270                 a->timer = NULL;
 271             }
 272             break;
 273 
 274         case -pcmk_err_diff_failed:    /* When an attr changes while the CIB is syncing */
 275         case -ETIME:           /* When an attr changes while there is a DC election */
 276         case -ENXIO:           /* When an attr changes while the CIB is syncing a
 277                                 *   newer config from a node that just came up
 278                                 */
 279             level = LOG_WARNING;
 280             break;
 281     }
 282 
 283     do_crm_log(level, "CIB update %d result for %s: %s " QB_XS " rc=%d",
 284                call_id, a->id, pcmk_strerror(rc), rc);
 285 
 286     g_hash_table_iter_init(&iter, a->values);
 287     while (g_hash_table_iter_next(&iter, (gpointer *) & peer, (gpointer *) & v)) {
 288         if (rc == pcmk_ok) {
 289             crm_info("* Wrote %s[%s]=%s",
 290                      a->id, peer, pcmk__s(v->requested, "(unset)"));
 291             pcmk__str_update(&(v->requested), NULL);
 292         } else {
 293             do_crm_log(level, "* Could not write %s[%s]=%s",
 294                        a->id, peer, pcmk__s(v->requested, "(unset)"));
 295             /* Reattempt write below if we are still the writer */
 296             attrd_set_attr_flags(a, attrd_attr_changed);
 297         }
 298     }
 299 
 300     if (pcmk_is_set(a->flags, attrd_attr_changed) && attrd_election_won()) {
 301         if (rc == pcmk_ok) {
 302             /* We deferred a write of a new update because this update was in
 303              * progress. Write out the new value without additional delay.
 304              */
 305             crm_debug("Pending update for %s can be written now", a->id);
 306             write_attribute(a, false);
 307 
 308         /* We're re-attempting a write because the original failed; delay
 309          * the next attempt so we don't potentially flood the CIB manager
 310          * and logs with a zillion attempts per second.
 311          *
 312          * @TODO We could elect a new writer instead. However, we'd have to
 313          * somehow downgrade our vote, and we'd still need something like this
 314          * if all peers similarly fail to write this attribute (which may
 315          * indicate a corrupted attribute entry rather than a CIB issue).
 316          */
 317         } else if (a->timer) {
 318             // Attribute has a dampening value, so use that as delay
 319             if (!mainloop_timer_running(a->timer)) {
 320                 crm_trace("Delayed re-attempted write for %s by %s",
 321                           name, pcmk__readable_interval(a->timeout_ms));
 322                 mainloop_timer_start(a->timer);
 323             }
 324         } else {
 325             /* Set a temporary dampening of 2 seconds (timer will continue
 326              * to exist until the attribute's dampening gets set or the
 327              * write succeeds).
 328              */
 329             a->timer = attrd_add_timer(a->id, 2000, a);
 330             mainloop_timer_start(a->timer);
 331         }
 332     }
 333 }
 334 
 335 /*!
 336  * \internal
 337  * \brief Add a set-attribute update request to the current CIB transaction
 338  *
 339  * \param[in] attr     Attribute to update
 340  * \param[in] attr_id  ID of attribute to update
 341  * \param[in] node_id  ID of node for which to update attribute value
 342  * \param[in] set_id   ID of attribute set
 343  * \param[in] value    New value for attribute
 344  *
 345  * \return Standard Pacemaker return code
 346  */
 347 static int
 348 add_set_attr_update(const attribute_t *attr, const char *attr_id,
     /* [previous][next][first][last][top][bottom][index][help] */
 349                     const char *node_id, const char *set_id, const char *value)
 350 {
 351     xmlNode *update = pcmk__xe_create(NULL, PCMK__XE_NODE_STATE);
 352     xmlNode *child = update;
 353     int rc = ENOMEM;
 354 
 355     crm_xml_add(child, PCMK_XA_ID, node_id);
 356 
 357     child = pcmk__xe_create(child, PCMK__XE_TRANSIENT_ATTRIBUTES);
 358     crm_xml_add(child, PCMK_XA_ID, node_id);
 359 
 360     child = pcmk__xe_create(child, attr->set_type);
 361     crm_xml_add(child, PCMK_XA_ID, set_id);
 362 
 363     child = pcmk__xe_create(child, PCMK_XE_NVPAIR);
 364     crm_xml_add(child, PCMK_XA_ID, attr_id);
 365     crm_xml_add(child, PCMK_XA_NAME, attr->id);
 366     crm_xml_add(child, PCMK_XA_VALUE, value);
 367 
 368     rc = the_cib->cmds->modify(the_cib, PCMK_XE_STATUS, update,
 369                                cib_can_create|cib_transaction);
 370     rc = pcmk_legacy2rc(rc);
 371 
 372     pcmk__xml_free(update);
 373     return rc;
 374 }
 375 
 376 /*!
 377  * \internal
 378  * \brief Add an unset-attribute update request to the current CIB transaction
 379  *
 380  * \param[in] attr     Attribute to update
 381  * \param[in] attr_id  ID of attribute to update
 382  * \param[in] node_id  ID of node for which to update attribute value
 383  * \param[in] set_id   ID of attribute set
 384  *
 385  * \return Standard Pacemaker return code
 386  */
 387 static int
 388 add_unset_attr_update(const attribute_t *attr, const char *attr_id,
     /* [previous][next][first][last][top][bottom][index][help] */
 389                       const char *node_id, const char *set_id)
 390 {
 391     char *xpath = crm_strdup_printf("/" PCMK_XE_CIB
 392                                     "/" PCMK_XE_STATUS
 393                                     "/" PCMK__XE_NODE_STATE
 394                                         "[@" PCMK_XA_ID "='%s']"
 395                                     "/" PCMK__XE_TRANSIENT_ATTRIBUTES
 396                                         "[@" PCMK_XA_ID "='%s']"
 397                                     "/%s[@" PCMK_XA_ID "='%s']"
 398                                     "/" PCMK_XE_NVPAIR
 399                                         "[@" PCMK_XA_ID "='%s' "
 400                                          "and @" PCMK_XA_NAME "='%s']",
 401                                     node_id, node_id, attr->set_type, set_id,
 402                                     attr_id, attr->id);
 403 
 404     int rc = the_cib->cmds->remove(the_cib, xpath, NULL,
 405                                    cib_xpath|cib_transaction);
 406 
 407     free(xpath);
 408     return pcmk_legacy2rc(rc);
 409 }
 410 
 411 /*!
 412  * \internal
 413  * \brief Add an attribute update request to the current CIB transaction
 414  *
 415  * \param[in] attr      Attribute to update
 416  * \param[in] value     New value for attribute
 417  * \param[in] node_id   ID of node for which to update attribute value
 418  *
 419  * \return Standard Pacemaker return code
 420  */
 421 static int
 422 add_attr_update(const attribute_t *attr, const char *value, const char *node_id)
     /* [previous][next][first][last][top][bottom][index][help] */
 423 {
 424     char *set_id = attrd_set_id(attr, node_id);
 425     char *nvpair_id = attrd_nvpair_id(attr, node_id);
 426     int rc = pcmk_rc_ok;
 427 
 428     if (value == NULL) {
 429         rc = add_unset_attr_update(attr, nvpair_id, node_id, set_id);
 430     } else {
 431         rc = add_set_attr_update(attr, nvpair_id, node_id, set_id, value);
 432     }
 433     free(set_id);
 434     free(nvpair_id);
 435     return rc;
 436 }
 437 
 438 static void
 439 send_alert_attributes_value(attribute_t *a, GHashTable *t)
     /* [previous][next][first][last][top][bottom][index][help] */
 440 {
 441     int rc = 0;
 442     attribute_value_t *at = NULL;
 443     GHashTableIter vIter;
 444 
 445     g_hash_table_iter_init(&vIter, t);
 446 
 447     while (g_hash_table_iter_next(&vIter, NULL, (gpointer *) & at)) {
 448         rc = attrd_send_attribute_alert(at->nodename, at->nodeid,
 449                                         a->id, at->current);
 450         crm_trace("Sent alerts for %s[%s]=%s: nodeid=%d rc=%d",
 451                   a->id, at->nodename, at->current, at->nodeid, rc);
 452     }
 453 }
 454 
 455 static void
 456 set_alert_attribute_value(GHashTable *t, attribute_value_t *v)
     /* [previous][next][first][last][top][bottom][index][help] */
 457 {
 458     attribute_value_t *a_v = pcmk__assert_alloc(1, sizeof(attribute_value_t));
 459 
 460     a_v->nodeid = v->nodeid;
 461     a_v->nodename = pcmk__str_copy(v->nodename);
 462     a_v->current = pcmk__str_copy(v->current);
 463 
 464     g_hash_table_replace(t, a_v->nodename, a_v);
 465 }
 466 
 467 mainloop_timer_t *
 468 attrd_add_timer(const char *id, int timeout_ms, attribute_t *attr)
     /* [previous][next][first][last][top][bottom][index][help] */
 469 {
 470    return mainloop_timer_add(id, timeout_ms, FALSE, attribute_timer_cb, attr);
 471 }
 472 
 473 /*!
 474  * \internal
 475  * \brief Write an attribute's values to the CIB if appropriate
 476  *
 477  * \param[in,out] a             Attribute to write
 478  * \param[in]     ignore_delay  If true, write attribute now regardless of any
 479  *                              configured delay
 480  */
 481 static void
 482 write_attribute(attribute_t *a, bool ignore_delay)
     /* [previous][next][first][last][top][bottom][index][help] */
 483 {
 484     int private_updates = 0, cib_updates = 0;
 485     attribute_value_t *v = NULL;
 486     GHashTableIter iter;
 487     GHashTable *alert_attribute_value = NULL;
 488     int rc = pcmk_ok;
 489 
 490     if (a == NULL) {
 491         return;
 492     }
 493 
 494     /* If this attribute will be written to the CIB ... */
 495     if (!stand_alone && !pcmk_is_set(a->flags, attrd_attr_is_private)) {
 496         /* Defer the write if now's not a good time */
 497         if (a->update && (a->update < last_cib_op_done)) {
 498             crm_info("Write out of '%s' continuing: update %d considered lost",
 499                      a->id, a->update);
 500             a->update = 0; // Don't log this message again
 501 
 502         } else if (a->update) {
 503             crm_info("Write out of '%s' delayed: update %d in progress",
 504                      a->id, a->update);
 505             goto done;
 506 
 507         } else if (mainloop_timer_running(a->timer)) {
 508             if (ignore_delay) {
 509                 mainloop_timer_stop(a->timer);
 510                 crm_debug("Overriding '%s' write delay", a->id);
 511             } else {
 512                 crm_info("Delaying write of '%s'", a->id);
 513                 goto done;
 514             }
 515         }
 516 
 517         // Initiate a transaction for all the peer value updates
 518         CRM_CHECK(the_cib != NULL, goto done);
 519         the_cib->cmds->set_user(the_cib, a->user);
 520         rc = the_cib->cmds->init_transaction(the_cib);
 521         if (rc != pcmk_ok) {
 522             crm_err("Failed to write %s (set %s): Could not initiate "
 523                     "CIB transaction",
 524                     a->id, pcmk__s(a->set_id, "unspecified"));
 525             goto done;
 526         }
 527     }
 528 
 529     /* Attribute will be written shortly, so clear changed flag and force
 530      * write flag, and initialize UUID missing flag to false.
 531      */
 532     attrd_clear_attr_flags(a, attrd_attr_changed|attrd_attr_uuid_missing|attrd_attr_force_write);
 533 
 534     /* Make the table for the attribute trap */
 535     alert_attribute_value = pcmk__strikey_table(NULL,
 536                                                 attrd_free_attribute_value);
 537 
 538     /* Iterate over each peer value of this attribute */
 539     g_hash_table_iter_init(&iter, a->values);
 540     while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &v)) {
 541         const char *uuid = NULL;
 542 
 543         if (pcmk_is_set(v->flags, attrd_value_remote)) {
 544             /* If this is a Pacemaker Remote node, the node's UUID is the same
 545              * as its name, which we already have.
 546              */
 547             uuid = v->nodename;
 548 
 549         } else {
 550             // This will create a cluster node cache entry if none exists
 551             pcmk__node_status_t *peer = pcmk__get_node(v->nodeid, v->nodename,
 552                                                        NULL,
 553                                                        pcmk__node_search_any);
 554 
 555             uuid = peer->xml_id;
 556 
 557             // Remember peer's node ID if we're just now learning it
 558             if ((peer->cluster_layer_id != 0) && (v->nodeid == 0)) {
 559                 crm_trace("Learned ID %" PRIu32 " for node %s",
 560                           peer->cluster_layer_id, v->nodename);
 561                 v->nodeid = peer->cluster_layer_id;
 562             }
 563         }
 564 
 565         /* If this is a private attribute, no update needs to be sent */
 566         if (stand_alone || pcmk_is_set(a->flags, attrd_attr_is_private)) {
 567             private_updates++;
 568             continue;
 569         }
 570 
 571         // Defer write if this is a cluster node that's never been seen
 572         if (uuid == NULL) {
 573             attrd_set_attr_flags(a, attrd_attr_uuid_missing);
 574             crm_notice("Cannot update %s[%s]='%s' now because node's UUID is "
 575                        "unknown (will retry if learned)",
 576                        a->id, v->nodename, v->current);
 577             continue;
 578         }
 579 
 580         // Update this value as part of the CIB transaction we're building
 581         rc = add_attr_update(a, v->current, uuid);
 582         if (rc != pcmk_rc_ok) {
 583             crm_err("Failed to update %s[%s]='%s': %s "
 584                     QB_XS " node uuid=%s id=%" PRIu32,
 585                     a->id, v->nodename, v->current, pcmk_rc_str(rc),
 586                     uuid, v->nodeid);
 587             continue;
 588         }
 589 
 590         crm_debug("Writing %s[%s]=%s (node-state-id=%s node-id=%" PRIu32 ")",
 591                   a->id, v->nodename, pcmk__s(v->current, "(unset)"),
 592                   uuid, v->nodeid);
 593         cib_updates++;
 594 
 595         /* Preservation of the attribute to transmit alert */
 596         set_alert_attribute_value(alert_attribute_value, v);
 597 
 598         // Save this value so we can log it when write completes
 599         pcmk__str_update(&(v->requested), v->current);
 600     }
 601 
 602     if (private_updates) {
 603         crm_info("Processed %d private change%s for %s (set %s)",
 604                  private_updates, pcmk__plural_s(private_updates),
 605                  a->id, pcmk__s(a->set_id, "unspecified"));
 606     }
 607     if (cib_updates > 0) {
 608         char *id = pcmk__str_copy(a->id);
 609 
 610         // Commit transaction
 611         a->update = the_cib->cmds->end_transaction(the_cib, true, cib_none);
 612 
 613         crm_info("Sent CIB request %d with %d change%s for %s (set %s)",
 614                  a->update, cib_updates, pcmk__plural_s(cib_updates),
 615                  a->id, pcmk__s(a->set_id, "unspecified"));
 616 
 617         if (the_cib->cmds->register_callback_full(the_cib, a->update,
 618                                                   CIB_OP_TIMEOUT_S, FALSE, id,
 619                                                   "attrd_cib_callback",
 620                                                   attrd_cib_callback, free)) {
 621             // Transmit alert of the attribute
 622             send_alert_attributes_value(a, alert_attribute_value);
 623         }
 624     }
 625 
 626 done:
 627     // Discard transaction (if any)
 628     if (the_cib != NULL) {
 629         the_cib->cmds->end_transaction(the_cib, false, cib_none);
 630         the_cib->cmds->set_user(the_cib, NULL);
 631     }
 632 
 633     if (alert_attribute_value != NULL) {
 634         g_hash_table_destroy(alert_attribute_value);
 635     }
 636 }
 637 
 638 /*!
 639  * \internal
 640  * \brief Write out attributes
 641  *
 642  * \param[in] options  Group of enum attrd_write_options
 643  */
 644 void
 645 attrd_write_attributes(uint32_t options)
     /* [previous][next][first][last][top][bottom][index][help] */
 646 {
 647     GHashTableIter iter;
 648     attribute_t *a = NULL;
 649 
 650     crm_debug("Writing out %s attributes",
 651               pcmk_is_set(options, attrd_write_all)? "all" : "changed");
 652     g_hash_table_iter_init(&iter, attributes);
 653     while (g_hash_table_iter_next(&iter, NULL, (gpointer *) & a)) {
 654         if (!pcmk_is_set(options, attrd_write_all) &&
 655             pcmk_is_set(a->flags, attrd_attr_uuid_missing)) {
 656             // Try writing this attribute again, in case peer ID was learned
 657             attrd_set_attr_flags(a, attrd_attr_changed);
 658         } else if (pcmk_is_set(a->flags, attrd_attr_force_write)) {
 659             /* If the force_write flag is set, write the attribute. */
 660             attrd_set_attr_flags(a, attrd_attr_changed);
 661         }
 662 
 663         if (pcmk_is_set(options, attrd_write_all) ||
 664             pcmk_is_set(a->flags, attrd_attr_changed)) {
 665             bool ignore_delay = pcmk_is_set(options, attrd_write_no_delay);
 666 
 667             if (pcmk_is_set(a->flags, attrd_attr_force_write)) {
 668                 // Always ignore delay when forced write flag is set
 669                 ignore_delay = true;
 670             }
 671             write_attribute(a, ignore_delay);
 672         } else {
 673             crm_trace("Skipping unchanged attribute %s", a->id);
 674         }
 675     }
 676 }
 677 
 678 void
 679 attrd_write_or_elect_attribute(attribute_t *a)
     /* [previous][next][first][last][top][bottom][index][help] */
 680 {
 681     if (attrd_election_won()) {
 682         write_attribute(a, false);
 683     } else {
 684         attrd_start_election_if_needed();
 685     }
 686 }

/* [previous][next][first][last][top][bottom][index][help] */