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