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 <stdint.h>
15 #include <stdlib.h>
16
17 #include <crm/cluster.h>
18 #include <crm/cluster/internal.h>
19 #include <crm/common/logging.h>
20 #include <crm/common/results.h>
21 #include <crm/common/xml.h>
22
23 #include "pacemaker-attrd.h"
24
25 pcmk_cluster_t *attrd_cluster = NULL;
26
27 /*!
28 * \internal
29 * \brief Nodes removed by \c attrd_peer_remove()
30 *
31 * This table is to be used as a set. It contains nodes that have been removed
32 * by \c attrd_peer_remove() and whose transient attributes should be erased
33 * from the CIB.
34 *
35 * Setting an attribute value for a node via \c update_attr_on_host() removes
36 * the node from the table. At that point, we have transient attributes in
37 * memory for the node, so it should no longer be erased from the CIB.
38 *
39 * If another node erases a removed node's transient attributes from the CIB,
40 * the removed node remains in this table until an attribute value is set for
41 * it. This is for convenience: it avoids the need to monitor for CIB updates
42 * that erase a node's \c node_state or \c transient attributes element, just to
43 * remove the node from the table.
44 *
45 * Leaving a removed node in the table after erasure should be harmless. If a
46 * node is in this table, then we have no transient attributes for it in memory.
47 * If for some reason we erase its transient attributes from the CIB twice, its
48 * state in the CIB will still be correct.
49 */
50 static GHashTable *removed_peers = NULL;
51
52 /*!
53 * \internal
54 * \brief Free the removed nodes table
55 */
56 void
57 attrd_free_removed_peers(void)
58 {
|
CID (unavailable; MK=3002fa9b2875eb96a573335d778576cf) (#1 of 1): Inconsistent C union access (INCONSISTENT_UNION_ACCESS): |
|
(1) Event assign_union_field: |
The union field "in" of "_pp" is written. |
|
(2) Event inconsistent_union_field_access: |
In "_pp.out", the union field used: "out" is inconsistent with the field most recently stored: "in". |
59 g_clear_pointer(&removed_peers, g_hash_table_destroy);
60 }
61
62 static xmlNode *
63 attrd_confirmation(int callid)
64 {
65 xmlNode *node = pcmk__xe_create(NULL, __func__);
66
67 pcmk__xe_set(node, PCMK__XA_T, PCMK__VALUE_ATTRD);
68 pcmk__xe_set(node, PCMK__XA_SRC, pcmk__cluster_local_node_name());
69 pcmk__xe_set(node, PCMK_XA_TASK, PCMK__ATTRD_CMD_CONFIRM);
70 pcmk__xe_set_int(node, PCMK__XA_CALL_ID, callid);
71
72 return node;
73 }
74
75 static void
76 attrd_peer_message(pcmk__node_status_t *peer, xmlNode *xml)
77 {
78 const char *election_op = pcmk__xe_get(xml, PCMK__XA_CRM_TASK);
79
80 if (election_op) {
81 attrd_handle_election_op(peer, xml);
82 return;
83 }
84
85 if (attrd_shutting_down()) {
86 /* If we're shutting down, we want to continue responding to election
87 * ops as long as we're a cluster member (because our vote may be
88 * needed). Ignore all other messages.
89 */
90 return;
91
92 } else {
93 pcmk__request_t request = {
94 .ipc_client = NULL,
95 .ipc_id = 0,
96 .ipc_flags = crm_ipc_flags_none,
97 .peer = peer->name,
98 .xml = xml,
99 .call_options = 0,
100 .result = PCMK__UNKNOWN_RESULT,
101 };
102
103 request.op = pcmk__xe_get_copy(request.xml, PCMK_XA_TASK);
104 CRM_CHECK(request.op != NULL, return);
105
106 attrd_handle_request(&request);
107
108 /* Having finished handling the request, check to see if the originating
109 * peer requested confirmation. If so, send that confirmation back now.
110 */
111 if (pcmk__xe_attr_is_true(xml, PCMK__XA_CONFIRM) &&
112 !pcmk__str_eq(request.op, PCMK__ATTRD_CMD_CONFIRM, pcmk__str_none)) {
113 int callid = 0;
114 xmlNode *reply = NULL;
115
116 /* Add the confirmation ID for the message we are confirming to the
117 * response so the originating peer knows what they're a confirmation
118 * for.
119 */
120 pcmk__xe_get_int(xml, PCMK__XA_CALL_ID, &callid);
121 reply = attrd_confirmation(callid);
122
123 /* And then send the confirmation back to the originating peer. This
124 * ends up right back in this same function (attrd_peer_message) on the
125 * peer where it will have to do something with a PCMK__XA_CONFIRM type
126 * message.
127 */
128 pcmk__debug("Sending %s a confirmation", peer->name);
129 attrd_send_message(peer, reply, false);
130 pcmk__xml_free(reply);
131 }
132 }
133 }
134
135 #if SUPPORT_COROSYNC
136 /*!
137 * \internal
138 * \brief Callback for when a peer message is received
139 *
140 * \param[in] handle The cluster connection
141 * \param[in] group_name The group that \p nodeid is a member of
142 * \param[in] nodeid Peer node that sent \p msg
143 * \param[in] pid Process that sent \p msg
144 * \param[in,out] msg Received message
145 * \param[in] msg_len Length of \p msg
146 */
147 static void
148 attrd_cpg_dispatch(cpg_handle_t handle, const struct cpg_name *group_name,
149 uint32_t nodeid, uint32_t pid, void *msg, size_t msg_len)
150 {
151 xmlNode *xml = NULL;
152 const char *from = NULL;
153 char *data = pcmk__cpg_message_data(handle, nodeid, pid, msg, &from);
154
155 if (data == NULL) {
156 return;
157 }
158
159 xml = pcmk__xml_parse(data);
160 if (xml == NULL) {
161 pcmk__err("Bad message received from %s[%" PRIu32 "]: '%.120s'", from,
162 nodeid, data);
163 } else {
164 attrd_peer_message(pcmk__get_node(nodeid, from, NULL,
165 pcmk__node_search_cluster_member),
166 xml);
167 }
168
169 pcmk__xml_free(xml);
170 free(data);
171 }
172
173 /*!
174 * \internal
175 * \brief Callback for when the cluster object is destroyed
176 *
177 * \param[in] unused Unused
178 */
179 static void
180 attrd_cpg_destroy(gpointer unused)
181 {
182 if (attrd_shutting_down()) {
183 pcmk__info("Disconnected from Corosync process group");
184
185 } else {
186 pcmk__crit("Lost connection to Corosync process group, shutting down");
187 attrd_exit_status = CRM_EX_DISCONNECT;
188 attrd_shutdown(0);
189 }
190 }
191 #endif // SUPPORT_COROSYNC
192
193 /*!
194 * \internal
195 * \brief Broadcast an update for a single attribute value
196 *
197 * \param[in] a Attribute to broadcast
198 * \param[in] v Attribute value to broadcast
199 */
200 void
201 attrd_broadcast_value(const attribute_t *a, const attribute_value_t *v)
202 {
203 xmlNode *op = pcmk__xe_create(NULL, PCMK_XE_OP);
204
205 pcmk__xe_set(op, PCMK_XA_TASK, PCMK__ATTRD_CMD_UPDATE);
206 attrd_add_value_xml(op, a, v, false);
207 attrd_send_message(NULL, op, false);
208 pcmk__xml_free(op);
209 }
210
211 #define state_text(state) pcmk__s((state), "in unknown state")
212
213 /*!
214 * \internal
215 * \brief Callback for peer status changes
216 *
217 * \param[in] type What changed
218 * \param[in] node What peer had the change
219 * \param[in] data Previous value of what changed
220 */
221 static void
222 attrd_peer_change_cb(enum pcmk__node_update kind, pcmk__node_status_t *peer,
223 const void *data)
224 {
225 bool gone = false;
226 bool is_remote = pcmk__is_set(peer->flags, pcmk__node_status_remote);
227
228 switch (kind) {
229 case pcmk__node_update_name:
230 pcmk__debug("%s node %s[%" PRIu32 "] is now %s",
231 (is_remote? "Remote" : "Cluster"),
232 pcmk__s(peer->name, "unknown"), peer->cluster_layer_id,
233 state_text(peer->state));
234 break;
235
236 case pcmk__node_update_processes:
237 if (!pcmk__is_set(peer->processes, crm_get_cluster_proc())) {
238 gone = true;
239 }
240 pcmk__debug("Node %s[%" PRIu32 "] is %s a peer",
241 pcmk__s(peer->name, "unknown"), peer->cluster_layer_id,
242 (gone? "no longer" : "now"));
243 break;
244
245 case pcmk__node_update_state:
246 pcmk__debug("%s node %s[%" PRIu32 "] is now %s (was %s)",
247 (is_remote? "Remote" : "Cluster"),
248 pcmk__s(peer->name, "unknown"), peer->cluster_layer_id,
249 state_text(peer->state), state_text(data));
250
251 if (pcmk__str_eq(peer->state, PCMK_VALUE_MEMBER, pcmk__str_none)) {
252 /* If we're the writer, send new peers a list of all attributes
253 * (unless it's a remote node, which doesn't run its own attrd)
254 */
255 if (!is_remote) {
256 if (attrd_election_won()) {
257 attrd_peer_sync(peer);
258
259 } else {
260 // Anyway send a message so that the peer learns our name
261 attrd_send_protocol(peer);
262 }
263 }
264
265 } else {
266 // Remove all attribute values associated with lost nodes
267 if (peer->name != NULL) {
268 attrd_peer_remove(peer->name, false, "loss");
269 }
270 gone = true;
271 }
272 break;
273 }
274
275 // Remove votes from cluster nodes that leave, in case election in progress
276 if (gone && !is_remote && peer->name != NULL) {
277 attrd_remove_voter(peer);
278 attrd_remove_peer_protocol_ver(peer->name);
279 attrd_do_not_expect_from_peer(peer->name);
280 }
281 }
282
283 #define readable_value(rv_v) pcmk__s((rv_v)->current, "(unset)")
284
285 #define readable_peer(p) \
286 (((p) == NULL)? "all peers" : pcmk__s((p)->name, "unknown peer"))
287
288 static void
289 update_attr_on_host(attribute_t *a, const pcmk__node_status_t *peer,
290 const xmlNode *xml, const char *attr, const char *value,
291 const char *host, bool filter)
292 {
293 int is_remote = 0;
294 bool changed = false;
295 attribute_value_t *v = NULL;
296 const char *prev_xml_id = NULL;
297 const char *node_xml_id = pcmk__xe_get(xml, PCMK__XA_ATTR_HOST_ID);
298
299 if (removed_peers != NULL) {
300 g_hash_table_remove(removed_peers, host);
301 }
302
303 // Create entry for value if not already existing
304 v = g_hash_table_lookup(a->values, host);
305 if (v == NULL) {
306 v = pcmk__assert_alloc(1, sizeof(attribute_value_t));
307
308 v->nodename = pcmk__str_copy(host);
309 g_hash_table_replace(a->values, v->nodename, v);
310 }
311
312 /* If update doesn't contain the node XML ID, fall back to any previously
313 * known value (for logging)
314 */
315 prev_xml_id = attrd_get_node_xml_id(v->nodename);
316 if (node_xml_id == NULL) {
317 node_xml_id = prev_xml_id;
318 }
319
320 // If value is for a Pacemaker Remote node, remember that
321 pcmk__xe_get_int(xml, PCMK__XA_ATTR_IS_REMOTE, &is_remote);
322 if (is_remote) {
323 attrd_set_value_flags(v, attrd_value_remote);
324 pcmk__assert(pcmk__cluster_lookup_remote_node(host) != NULL);
325 }
326
327 // Check whether the value changed
328 changed = !pcmk__str_eq(v->current, value, pcmk__str_casei);
329
330 if (changed && filter
331 && pcmk__str_eq(host, attrd_cluster->priv->node_name,
332 pcmk__str_casei)) {
333 /* Broadcast the local value for an attribute that differs from the
334 * value provided in a peer's attribute synchronization response. This
335 * ensures a node's values for itself take precedence and all peers are
336 * kept in sync.
337 */
338 v = g_hash_table_lookup(a->values, attrd_cluster->priv->node_name);
339 pcmk__notice("%s[%s]: local value '%s' takes priority over '%s' from "
340 "%s",
341 attr, host, readable_value(v), value, peer->name);
342 attrd_broadcast_value(a, v);
343
344 } else if (changed) {
345 const char *timeout_s = "no";
346
347 if (a->timeout_ms != 0) {
348 timeout_s = pcmk__readable_interval(a->timeout_ms);
349 }
350
351 pcmk__notice("Setting %s[%s]%s%s: %s -> %s "
352 QB_XS " from %s with %s write delay and node XML ID %s",
353 attr, host, ((a->set_type != NULL)? " in " : ""),
354 pcmk__s(a->set_type, ""), readable_value(v),
355 pcmk__s(value, "(unset)"), peer->name, timeout_s,
356 pcmk__s(node_xml_id, "unknown"));
357 pcmk__str_update(&v->current, value);
358 attrd_set_attr_flags(a, attrd_attr_changed);
359
360 // Write out new value or start dampening timer
361 if (a->timeout_ms && a->timer) {
362 pcmk__trace("Delaying write of %s %s for dampening", attr,
363 pcmk__readable_interval(a->timeout_ms));
364 mainloop_timer_start(a->timer);
365 } else {
366 attrd_write_or_elect_attribute(a);
367 }
368
369 } else {
370 int is_force_write = 0;
371
372 pcmk__xe_get_int(xml, PCMK__XA_ATTRD_IS_FORCE_WRITE, &is_force_write);
373
374 if (is_force_write == 1 && a->timeout_ms && a->timer) {
375 /* Save forced writing and set change flag. */
376 /* The actual attribute is written by Writer after election. */
377 pcmk__trace("%s[%s] from %s is unchanged (%s), forcing write", attr,
378 host, peer->name, pcmk__s(value, "unset"));
379 attrd_set_attr_flags(a, attrd_attr_force_write);
380 } else {
381 pcmk__trace("%s[%s] from %s is unchanged (%s)", attr, host,
382 peer->name, pcmk__s(value, "unset"));
383 }
384 }
385
386 // This allows us to later detect local values that peer doesn't know about
387 attrd_set_value_flags(v, attrd_value_from_peer);
388
389 // Remember node's XML ID if we're just learning it
390 if ((node_xml_id != NULL)
391 && !pcmk__str_eq(node_xml_id, prev_xml_id, pcmk__str_none)) {
392 // Remember node's name in case unknown in the membership cache
393 pcmk__node_status_t *known_peer =
394 pcmk__get_node(0, host, node_xml_id,
395 pcmk__node_search_cluster_member);
396
397 pcmk__trace("Learned %s[%s] node XML ID is %s (was %s)", a->id,
398 known_peer->name, node_xml_id,
399 pcmk__s(prev_xml_id, "unknown"));
400
401 attrd_set_node_xml_id(v->nodename, node_xml_id);
402 if (attrd_election_won()) {
403 // In case we couldn't write a value missing the XML ID before
404 attrd_write_attributes(attrd_write_changed);
405 }
406 }
407 }
408
409 static void
410 attrd_peer_update_one(const pcmk__node_status_t *peer, xmlNode *xml,
411 bool filter)
412 {
413 attribute_t *a = NULL;
414 const char *attr = pcmk__xe_get(xml, PCMK__XA_ATTR_NAME);
415 const char *value = pcmk__xe_get(xml, PCMK__XA_ATTR_VALUE);
416 const char *host = pcmk__xe_get(xml, PCMK__XA_ATTR_HOST);
417
418 if (attr == NULL) {
419 pcmk__warn("Could not update attribute: peer did not specify name");
420 return;
421 }
422
423 a = attrd_populate_attribute(xml, attr);
424 if (a == NULL) {
425 return;
426 }
427
428 if (host == NULL) {
429 // If no host was specified, update all hosts
430 GHashTableIter vIter;
431
432 pcmk__debug("Setting %s for all hosts to %s", attr, value);
433 pcmk__xe_remove_attr(xml, PCMK__XA_ATTR_HOST_ID);
434 g_hash_table_iter_init(&vIter, a->values);
435
436 while (g_hash_table_iter_next(&vIter, (gpointer *) & host, NULL)) {
437 update_attr_on_host(a, peer, xml, attr, value, host, filter);
438 }
439
440 } else {
441 // Update attribute value for the given host
442 update_attr_on_host(a, peer, xml, attr, value, host, filter);
443 }
444
445 /* If this is a message from some attrd instance broadcasting its protocol
446 * version, check to see if it's a new minimum version.
447 */
448 if (pcmk__str_eq(attr, CRM_ATTR_PROTOCOL, pcmk__str_none)) {
449 attrd_update_minimum_protocol_ver(peer->name, value);
450 }
451 }
452
453 static void
454 broadcast_unseen_local_values(void)
455 {
456 GHashTableIter aIter;
457 GHashTableIter vIter;
458 attribute_t *a = NULL;
459 attribute_value_t *v = NULL;
460 xmlNode *sync = NULL;
461
462 g_hash_table_iter_init(&aIter, attributes);
463 while (g_hash_table_iter_next(&aIter, NULL, (gpointer *) & a)) {
464
465 g_hash_table_iter_init(&vIter, a->values);
466 while (g_hash_table_iter_next(&vIter, NULL, (gpointer *) & v)) {
467
468 if (!pcmk__is_set(v->flags, attrd_value_from_peer)
469 && pcmk__str_eq(v->nodename, attrd_cluster->priv->node_name,
470 pcmk__str_casei)) {
471 pcmk__trace("* %s[%s]='%s' is local-only", a->id, v->nodename,
472 readable_value(v));
473 if (sync == NULL) {
474 sync = pcmk__xe_create(NULL, __func__);
475 pcmk__xe_set(sync, PCMK_XA_TASK,
476 PCMK__ATTRD_CMD_SYNC_RESPONSE);
477 }
478 attrd_add_value_xml(sync, a, v, a->timeout_ms && a->timer);
479 }
480 }
481 }
482
483 if (sync != NULL) {
484 pcmk__debug("Broadcasting local-only values");
485 attrd_send_message(NULL, sync, false);
486 pcmk__xml_free(sync);
487 }
488 }
489
490 int
491 attrd_cluster_connect(void)
492 {
493 int rc = pcmk_rc_ok;
494
495 attrd_cluster = pcmk_cluster_new();
496
497 #if SUPPORT_COROSYNC
498 if (pcmk_get_cluster_layer() == pcmk_cluster_layer_corosync) {
499 pcmk_cluster_set_destroy_fn(attrd_cluster, attrd_cpg_destroy);
500 pcmk_cpg_set_deliver_fn(attrd_cluster, attrd_cpg_dispatch);
501 pcmk_cpg_set_confchg_fn(attrd_cluster, pcmk__cpg_confchg_cb);
502 }
503 #endif // SUPPORT_COROSYNC
504
505 pcmk__cluster_set_status_callback(&attrd_peer_change_cb);
506
507 rc = pcmk_cluster_connect(attrd_cluster);
508 if (rc != pcmk_rc_ok) {
509 pcmk__err("Cluster connection failed");
510 }
511
512 return rc;
513 }
514
515 void
516 attrd_cluster_disconnect(void)
517 {
518 pcmk_cluster_disconnect(attrd_cluster);
519 pcmk_cluster_free(attrd_cluster);
520 }
521
522 void
523 attrd_peer_clear_failure(pcmk__request_t *request)
524 {
525 xmlNode *xml = request->xml;
526 const char *rsc = pcmk__xe_get(xml, PCMK__XA_ATTR_RESOURCE);
527 const char *host = pcmk__xe_get(xml, PCMK__XA_ATTR_HOST);
528 const char *op = pcmk__xe_get(xml, PCMK__XA_ATTR_CLEAR_OPERATION);
529 const char *interval_spec = pcmk__xe_get(xml, PCMK__XA_ATTR_CLEAR_INTERVAL);
530 guint interval_ms = 0U;
531 char *attr = NULL;
532 GHashTableIter iter;
533 regex_t regex;
534
535 pcmk__node_status_t *peer =
536 pcmk__get_node(0, request->peer, NULL,
537 pcmk__node_search_cluster_member);
538
539 pcmk_parse_interval_spec(interval_spec, &interval_ms);
540
541 if (attrd_failure_regex(®ex, rsc, op, interval_ms) != pcmk_ok) {
542 pcmk__info("Ignoring invalid request to clear failures for %s",
543 pcmk__s(rsc, "all resources"));
544 return;
545 }
546
547 pcmk__xe_set(xml, PCMK_XA_TASK, PCMK__ATTRD_CMD_UPDATE);
548
549 /* Make sure value is not set, so we delete */
550 pcmk__xe_remove_attr(xml, PCMK__XA_ATTR_VALUE);
551
552 g_hash_table_iter_init(&iter, attributes);
553 while (g_hash_table_iter_next(&iter, (gpointer *) &attr, NULL)) {
554 if (regexec(®ex, attr, 0, NULL, 0) == 0) {
555 pcmk__trace("Matched %s when clearing %s", attr,
556 pcmk__s(rsc, "all resources"));
557 pcmk__xe_set(xml, PCMK__XA_ATTR_NAME, attr);
558 attrd_peer_update(peer, xml, host, false);
559 }
560 }
561 regfree(®ex);
562 }
563
564 /*!
565 * \internal
566 * \brief Load attributes from a peer sync response
567 *
568 * \param[in] peer Peer that sent sync response
569 * \param[in] peer_won Whether peer is the attribute writer
570 * \param[in,out] xml Request XML
571 */
572 void
573 attrd_peer_sync_response(const pcmk__node_status_t *peer, bool peer_won,
574 xmlNode *xml)
575 {
576 pcmk__info("Processing " PCMK__ATTRD_CMD_SYNC_RESPONSE " from %s",
577 peer->name);
578
579 if (peer_won) {
580 /* Initialize the "seen" flag for all attributes to cleared, so we can
581 * detect attributes that local node has but the writer doesn't.
582 */
583 attrd_clear_value_seen();
584 }
585
586 // Process each attribute update in the sync response
587 for (xmlNode *child = pcmk__xe_first_child(xml, NULL, NULL, NULL);
588 child != NULL; child = pcmk__xe_next(child, NULL)) {
589
590 attrd_peer_update(peer, child, pcmk__xe_get(child, PCMK__XA_ATTR_HOST),
591 true);
592 }
593
594 if (peer_won) {
595 /* If any attributes are still not marked as seen, the writer doesn't
596 * know about them, so send all peers an update with them.
597 */
598 broadcast_unseen_local_values();
599 }
600 }
601
602 /*!
603 * \internal
604 * \brief Erase all removed nodes' transient attributes from the CIB
605 *
606 * This should be called by a newly elected writer upon winning the election.
607 */
608 void
609 attrd_erase_removed_peer_attributes(void)
610 {
611 const char *host = NULL;
612 GHashTableIter iter;
613
614 if (!attrd_election_won() || (removed_peers == NULL)) {
615 return;
616 }
617
618 g_hash_table_iter_init(&iter, removed_peers);
619 while (g_hash_table_iter_next(&iter, (gpointer *) &host, NULL)) {
620 attrd_cib_erase_transient_attrs(host);
621 g_hash_table_iter_remove(&iter);
622 }
623 }
624
625 /*!
626 * \internal
627 * \brief Remove all attributes and optionally peer cache entries for a node
628 *
629 * \param[in] host Name of node to purge
630 * \param[in] uncache If true, remove node from peer caches
631 * \param[in] source Who requested removal (only used for logging)
632 */
633 void
634 attrd_peer_remove(const char *host, bool uncache, const char *source)
635 {
636 attribute_t *a = NULL;
637 GHashTableIter aIter;
638
639 CRM_CHECK(host != NULL, return);
640 pcmk__notice("Removing all %s attributes for node %s "
641 QB_XS " %s reaping node from cache",
642 host, source, (uncache? "and" : "without"));
643
644 g_hash_table_iter_init(&aIter, attributes);
645 while (g_hash_table_iter_next(&aIter, NULL, (gpointer *) & a)) {
646 if(g_hash_table_remove(a->values, host)) {
647 pcmk__debug("Removed %s[%s] for peer %s", a->id, host, source);
648 }
649 }
650
651 if (attrd_election_won()) {
652 // We are the writer. Wipe node's transient attributes from CIB now.
653 attrd_cib_erase_transient_attrs(host);
654
655 } else {
656 /* Make sure the attributes get erased from the CIB eventually.
657 * - If there's already a writer, it will call this function and enter
658 * the "if" block above, requesting the erasure (unless it leaves
659 * before sending the request -- see below).
660 * attrd_start_election_if_needed() will do nothing here.
661 * - Otherwise, we ensure an election is happening (unless we're
662 * shutting down). The winner will erase transient attributes from the
663 * CIB for all removed nodes in attrd_election_cb().
664 *
665 * We add the node to the removed_peers table in case we win an election
666 * and need to request CIB erasures based on the table contents. This
667 * could happen for either of two reasons:
668 * - There is no current writer and we're not shutting down. An election
669 * either is already in progress or will be triggered here.
670 * - The current writer leaves before sending the CIB update request. A
671 * new election will be triggered.
672 */
673 if (removed_peers == NULL) {
674 removed_peers = pcmk__strikey_table(free, NULL);
675 }
676 g_hash_table_add(removed_peers, pcmk__str_copy(host));
677 attrd_start_election_if_needed();
678 }
679
680 if (uncache) {
681 pcmk__purge_node_from_cache(host, 0);
682 attrd_forget_node_xml_id(host);
683 }
684 }
685
686 /*!
687 * \internal
688 * \brief Send all known attributes and values to a peer
689 *
690 * \param[in] peer Peer to send sync to (if NULL, broadcast to all peers)
691 */
692 void
693 attrd_peer_sync(pcmk__node_status_t *peer)
694 {
695 GHashTableIter aIter;
696 GHashTableIter vIter;
697
698 attribute_t *a = NULL;
699 attribute_value_t *v = NULL;
700 xmlNode *sync = pcmk__xe_create(NULL, __func__);
701
702 pcmk__xe_set(sync, PCMK_XA_TASK, PCMK__ATTRD_CMD_SYNC_RESPONSE);
703
704 g_hash_table_iter_init(&aIter, attributes);
705 while (g_hash_table_iter_next(&aIter, NULL, (gpointer *) & a)) {
706 g_hash_table_iter_init(&vIter, a->values);
707 while (g_hash_table_iter_next(&vIter, NULL, (gpointer *) & v)) {
708 pcmk__debug("Syncing %s[%s]='%s' to %s", a->id, v->nodename,
709 readable_value(v), readable_peer(peer));
710 attrd_add_value_xml(sync, a, v, false);
711 }
712 }
713
714 pcmk__debug("Syncing values to %s", readable_peer(peer));
715 attrd_send_message(peer, sync, false);
716 pcmk__xml_free(sync);
717 }
718
719 void
720 attrd_peer_update(const pcmk__node_status_t *peer, xmlNode *xml,
721 const char *host, bool filter)
722 {
723 bool handle_sync_point = false;
724
725 CRM_CHECK((peer != NULL) && (xml != NULL), return);
726 if (xml->children != NULL) {
727 for (xmlNode *child = pcmk__xe_first_child(xml, PCMK_XE_OP, NULL, NULL);
728 child != NULL; child = pcmk__xe_next(child, PCMK_XE_OP)) {
729
730 pcmk__xe_copy_attrs(child, xml, pcmk__xaf_no_overwrite);
731 attrd_peer_update_one(peer, child, filter);
732
733 if (attrd_request_has_sync_point(child)) {
734 handle_sync_point = true;
735 }
736 }
737
738 } else {
739 attrd_peer_update_one(peer, xml, filter);
740
741 if (attrd_request_has_sync_point(xml)) {
742 handle_sync_point = true;
743 }
744 }
745
746 /* If the update XML specified that the client wanted to wait for a sync
747 * point, process that now.
748 */
749 if (handle_sync_point) {
750 pcmk__trace("Hit local sync point for attribute update");
751 attrd_ack_waitlist_clients(attrd_sync_point_local, xml);
752 }
753 }
754