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