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

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