pacemaker 2.1.6-6fdc9deea29
Scalable High-Availability cluster resource manager
Loading...
Searching...
No Matches
election.c
Go to the documentation of this file.
1/*
2 * Copyright 2004-2022 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 Lesser General Public License
7 * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
8 */
9
10#include <crm_internal.h>
11
12#include <sys/time.h>
13#include <sys/resource.h>
14
15#include <crm/msg_xml.h>
16#include <crm/common/xml.h>
17
18#include <crm/common/mainloop.h>
21#include <crm/crm.h>
22
23#define STORM_INTERVAL 2 /* in seconds */
24
25struct election_s {
26 enum election_result state;
27 guint count; // How many times local node has voted
28 char *name; // Descriptive name for this election
29 char *uname; // Local node's name
30 GSourceFunc cb; // Function to call if election is won
31 GHashTable *voted; // Key = node name, value = how node voted
32 mainloop_timer_t *timeout; // When to abort if all votes not received
33 int election_wins; // Track wins, for storm detection
34 bool wrote_blackbox; // Write a storm blackbox at most once
35 time_t expires; // When storm detection period ends
36 time_t last_election_loss; // When dampening period ends
37};
38
39static void
40election_complete(election_t *e)
41{
42 e->state = election_won;
43 if (e->cb != NULL) {
44 e->cb(e);
45 }
47}
48
49static gboolean
50election_timer_cb(gpointer user_data)
51{
52 election_t *e = user_data;
53
54 crm_info("%s timed out, declaring local node as winner", e->name);
55 election_complete(e);
56 return FALSE;
57}
58
68{
69 return (e == NULL)? election_error : e->state;
70}
71
89election_init(const char *name, const char *uname, guint period_ms, GSourceFunc cb)
90{
91 election_t *e = NULL;
92
93 static guint count = 0;
94
95 CRM_CHECK(uname != NULL, return NULL);
96
97 e = calloc(1, sizeof(election_t));
98 if (e == NULL) {
99 crm_perror(LOG_CRIT, "Cannot create election");
100 return NULL;
101 }
102
103 e->uname = strdup(uname);
104 if (e->uname == NULL) {
105 crm_perror(LOG_CRIT, "Cannot create election");
106 free(e);
107 return NULL;
108 }
109
110 e->name = name? crm_strdup_printf("election-%s", name)
111 : crm_strdup_printf("election-%u", count++);
112 e->cb = cb;
113 e->timeout = mainloop_timer_add(e->name, period_ms, FALSE,
114 election_timer_cb, e);
115 crm_trace("Created %s", e->name);
116 return e;
117}
118
128void
130{
131 if ((e != NULL) && (uname != NULL) && (e->voted != NULL)) {
132 crm_trace("Discarding %s (no-)vote from lost peer %s", e->name, uname);
133 g_hash_table_remove(e->voted, uname);
134 }
135}
136
142void
144{
145 if (e != NULL) {
146 crm_trace("Resetting election %s", e->name);
147 mainloop_timer_stop(e->timeout);
148 if (e->voted) {
149 crm_trace("Destroying voted cache with %d members", g_hash_table_size(e->voted));
150 g_hash_table_destroy(e->voted);
151 e->voted = NULL;
152 }
153 }
154}
155
164void
166{
167 if (e != NULL) {
169 crm_trace("Destroying %s", e->name);
170 mainloop_timer_del(e->timeout);
171 free(e->uname);
172 free(e->name);
173 free(e);
174 }
175}
176
177static void
178election_timeout_start(election_t *e)
179{
180 if (e != NULL) {
181 mainloop_timer_start(e->timeout);
182 }
183}
184
190void
192{
193 if (e != NULL) {
194 mainloop_timer_stop(e->timeout);
195 }
196}
197
204void
206{
207 if (e != NULL) {
208 mainloop_timer_set_period(e->timeout, period);
209 } else {
210 crm_err("No election defined");
211 }
212}
213
214static int
215get_uptime(struct timeval *output)
216{
217 static time_t expires = 0;
218 static struct rusage info;
219
220 time_t tm_now = time(NULL);
221
222 if (expires < tm_now) {
223 int rc = 0;
224
225 info.ru_utime.tv_sec = 0;
226 info.ru_utime.tv_usec = 0;
227 rc = getrusage(RUSAGE_SELF, &info);
228
229 output->tv_sec = 0;
230 output->tv_usec = 0;
231
232 if (rc < 0) {
233 crm_perror(LOG_ERR, "Could not calculate the current uptime");
234 expires = 0;
235 return -1;
236 }
237
238 crm_debug("Current CPU usage is: %lds, %ldus", (long)info.ru_utime.tv_sec,
239 (long)info.ru_utime.tv_usec);
240 }
241
242 expires = tm_now + STORM_INTERVAL; /* N seconds after the last _access_ */
243 output->tv_sec = info.ru_utime.tv_sec;
244 output->tv_usec = info.ru_utime.tv_usec;
245
246 return 1;
247}
248
249static int
250compare_age(struct timeval your_age)
251{
252 struct timeval our_age;
253
254 get_uptime(&our_age); /* If an error occurred, our_age will be compared as {0,0} */
255
256 if (our_age.tv_sec > your_age.tv_sec) {
257 crm_debug("Win: %ld vs %ld (seconds)", (long)our_age.tv_sec, (long)your_age.tv_sec);
258 return 1;
259 } else if (our_age.tv_sec < your_age.tv_sec) {
260 crm_debug("Lose: %ld vs %ld (seconds)", (long)our_age.tv_sec, (long)your_age.tv_sec);
261 return -1;
262 } else if (our_age.tv_usec > your_age.tv_usec) {
263 crm_debug("Win: %ld.%06ld vs %ld.%06ld (usec)",
264 (long)our_age.tv_sec, (long)our_age.tv_usec, (long)your_age.tv_sec, (long)your_age.tv_usec);
265 return 1;
266 } else if (our_age.tv_usec < your_age.tv_usec) {
267 crm_debug("Lose: %ld.%06ld vs %ld.%06ld (usec)",
268 (long)our_age.tv_sec, (long)our_age.tv_usec, (long)your_age.tv_sec, (long)your_age.tv_usec);
269 return -1;
270 }
271
272 return 0;
273}
274
289void
291{
292 struct timeval age;
293 xmlNode *vote = NULL;
294 crm_node_t *our_node;
295
296 if (e == NULL) {
297 crm_trace("Election vote requested, but no election available");
298 return;
299 }
300
301 our_node = crm_get_peer(0, e->uname);
302 if ((our_node == NULL) || (crm_is_peer_active(our_node) == FALSE)) {
303 crm_trace("Cannot vote in %s yet: local node not connected to cluster",
304 e->name);
305 return;
306 }
307
309 e->state = election_in_progress;
310 vote = create_request(CRM_OP_VOTE, NULL, NULL, CRM_SYSTEM_CRMD, CRM_SYSTEM_CRMD, NULL);
311
312 e->count++;
313 crm_xml_add(vote, F_CRM_ELECTION_OWNER, our_node->uuid);
314 crm_xml_add_int(vote, F_CRM_ELECTION_ID, e->count);
315
316 get_uptime(&age);
318
319 send_cluster_message(NULL, crm_msg_crmd, vote, TRUE);
320 free_xml(vote);
321
322 crm_debug("Started %s round %d", e->name, e->count);
323 election_timeout_start(e);
324 return;
325}
326
342bool
344{
345 int voted_size = 0;
346 int num_members = 0;
347
348 if (e == NULL) {
349 crm_trace("Election check requested, but no election available");
350 return FALSE;
351 }
352 if (e->voted == NULL) {
353 crm_trace("%s check requested, but no votes received yet", e->name);
354 return FALSE;
355 }
356
357 voted_size = g_hash_table_size(e->voted);
358 num_members = crm_active_peers();
359
360 /* in the case of #voted > #members, it is better to
361 * wait for the timeout and give the cluster time to
362 * stabilize
363 */
364 if (voted_size >= num_members) {
365 /* we won and everyone has voted */
367 if (voted_size > num_members) {
368 GHashTableIter gIter;
369 const crm_node_t *node;
370 char *key = NULL;
371
372 crm_warn("Received too many votes in %s", e->name);
373 g_hash_table_iter_init(&gIter, crm_peer_cache);
374 while (g_hash_table_iter_next(&gIter, NULL, (gpointer *) & node)) {
375 if (crm_is_peer_active(node)) {
376 crm_warn("* expected vote: %s", node->uname);
377 }
378 }
379
380 g_hash_table_iter_init(&gIter, e->voted);
381 while (g_hash_table_iter_next(&gIter, (gpointer *) & key, NULL)) {
382 crm_warn("* actual vote: %s", key);
383 }
384
385 }
386
387 crm_info("%s won by local node", e->name);
388 election_complete(e);
389 return TRUE;
390
391 } else {
392 crm_debug("%s still waiting on %d of %d votes",
393 e->name, num_members - voted_size, num_members);
394 }
395
396 return FALSE;
397}
398
399#define LOSS_DAMPEN 2 /* in seconds */
400
401struct vote {
402 const char *op;
403 const char *from;
404 const char *version;
405 const char *election_owner;
406 int election_id;
407 struct timeval age;
408};
409
421static bool
422parse_election_message(const election_t *e, const xmlNode *message,
423 struct vote *vote)
424{
425 CRM_CHECK(message && vote, return FALSE);
426
427 vote->election_id = -1;
428 vote->age.tv_sec = -1;
429 vote->age.tv_usec = -1;
430
431 vote->op = crm_element_value(message, F_CRM_TASK);
432 vote->from = crm_element_value(message, F_CRM_HOST_FROM);
433 vote->version = crm_element_value(message, F_CRM_VERSION);
434 vote->election_owner = crm_element_value(message, F_CRM_ELECTION_OWNER);
435
436 crm_element_value_int(message, F_CRM_ELECTION_ID, &(vote->election_id));
437
438 if ((vote->op == NULL) || (vote->from == NULL) || (vote->version == NULL)
439 || (vote->election_owner == NULL) || (vote->election_id < 0)) {
440
441 crm_warn("Invalid %s message from %s in %s ",
442 (vote->op? vote->op : "election"),
443 (vote->from? vote->from : "unspecified node"),
444 (e? e->name : "election"));
445 return FALSE;
446 }
447
448 // Op-specific validation
449
450 if (pcmk__str_eq(vote->op, CRM_OP_VOTE, pcmk__str_none)) {
451 // Only vote ops have uptime
453 F_CRM_ELECTION_AGE_US, &(vote->age));
454 if ((vote->age.tv_sec < 0) || (vote->age.tv_usec < 0)) {
455 crm_warn("Cannot count %s %s from %s because it is missing uptime",
456 (e? e->name : "election"), vote->op, vote->from);
457 return FALSE;
458 }
459
460 } else if (!pcmk__str_eq(vote->op, CRM_OP_NOVOTE, pcmk__str_none)) {
461 crm_info("Cannot process %s message from %s because %s is not a known election op",
462 (e? e->name : "election"), vote->from, vote->op);
463 return FALSE;
464 }
465
466 // Election validation
467
468 if (e == NULL) {
469 crm_info("Cannot count %s from %s because no election available",
470 vote->op, vote->from);
471 return FALSE;
472 }
473
474 /* If the membership cache is NULL, we REALLY shouldn't be voting --
475 * the question is how we managed to get here.
476 */
477 if (crm_peer_cache == NULL) {
478 crm_info("Cannot count %s %s from %s because no peer information available",
479 e->name, vote->op, vote->from);
480 return FALSE;
481 }
482 return TRUE;
483}
484
485static void
486record_vote(election_t *e, struct vote *vote)
487{
488 char *voter_copy = NULL;
489 char *vote_copy = NULL;
490
491 CRM_ASSERT(e && vote && vote->from && vote->op);
492 if (e->voted == NULL) {
493 e->voted = pcmk__strkey_table(free, free);
494 }
495
496 voter_copy = strdup(vote->from);
497 vote_copy = strdup(vote->op);
498 CRM_ASSERT(voter_copy && vote_copy);
499
500 g_hash_table_replace(e->voted, voter_copy, vote_copy);
501}
502
503static void
504send_no_vote(crm_node_t *peer, struct vote *vote)
505{
506 // @TODO probably shouldn't hardcode CRM_SYSTEM_CRMD and crm_msg_crmd
507
508 xmlNode *novote = create_request(CRM_OP_NOVOTE, NULL, vote->from,
510
511 crm_xml_add(novote, F_CRM_ELECTION_OWNER, vote->election_owner);
512 crm_xml_add_int(novote, F_CRM_ELECTION_ID, vote->election_id);
513
514 send_cluster_message(peer, crm_msg_crmd, novote, TRUE);
515 free_xml(novote);
516}
517
534election_count_vote(election_t *e, const xmlNode *message, bool can_win)
535{
536 int log_level = LOG_INFO;
537 gboolean done = FALSE;
538 gboolean we_lose = FALSE;
539 const char *reason = "unknown";
540 bool we_are_owner = FALSE;
541 crm_node_t *our_node = NULL, *your_node = NULL;
542 time_t tm_now = time(NULL);
543 struct vote vote;
544
545 CRM_CHECK(message != NULL, return election_error);
546 if (parse_election_message(e, message, &vote) == FALSE) {
547 return election_error;
548 }
549
550 your_node = crm_get_peer(0, vote.from);
551 our_node = crm_get_peer(0, e->uname);
552 we_are_owner = (our_node != NULL)
553 && pcmk__str_eq(our_node->uuid, vote.election_owner,
555
556 if (!can_win) {
557 reason = "Not eligible";
558 we_lose = TRUE;
559
560 } else if (our_node == NULL || crm_is_peer_active(our_node) == FALSE) {
561 reason = "We are not part of the cluster";
562 log_level = LOG_ERR;
563 we_lose = TRUE;
564
565 } else if (we_are_owner && (vote.election_id != e->count)) {
566 log_level = LOG_TRACE;
567 reason = "Superseded";
568 done = TRUE;
569
570 } else if (your_node == NULL || crm_is_peer_active(your_node) == FALSE) {
571 /* Possibly we cached the message in the FSA queue at a point that it wasn't */
572 reason = "Peer is not part of our cluster";
573 log_level = LOG_WARNING;
574 done = TRUE;
575
576 } else if (pcmk__str_eq(vote.op, CRM_OP_NOVOTE, pcmk__str_none)
577 || pcmk__str_eq(vote.from, e->uname, pcmk__str_none)) {
578 /* Receiving our own broadcast vote, or a no-vote from peer, is a vote
579 * for us to win
580 */
581 if (!we_are_owner) {
582 crm_warn("Cannot count %s round %d %s from %s because we are not election owner (%s)",
583 e->name, vote.election_id, vote.op, vote.from,
584 vote.election_owner);
585 return election_error;
586 }
587 if (e->state != election_in_progress) {
588 // Should only happen if we already lost
589 crm_debug("Not counting %s round %d %s from %s because no election in progress",
590 e->name, vote.election_id, vote.op, vote.from);
591 return e->state;
592 }
593 record_vote(e, &vote);
594 reason = "Recorded";
595 done = TRUE;
596
597 } else {
598 // A peer vote requires a comparison to determine which node is better
599 int age_result = compare_age(vote.age);
600 int version_result = compare_version(vote.version, CRM_FEATURE_SET);
601
602 if (version_result < 0) {
603 reason = "Version";
604 we_lose = TRUE;
605
606 } else if (version_result > 0) {
607 reason = "Version";
608
609 } else if (age_result < 0) {
610 reason = "Uptime";
611 we_lose = TRUE;
612
613 } else if (age_result > 0) {
614 reason = "Uptime";
615
616 } else if (strcasecmp(e->uname, vote.from) > 0) {
617 reason = "Host name";
618 we_lose = TRUE;
619
620 } else {
621 reason = "Host name";
622 }
623 }
624
625 if (e->expires < tm_now) {
626 e->election_wins = 0;
627 e->expires = tm_now + STORM_INTERVAL;
628
629 } else if (done == FALSE && we_lose == FALSE) {
630 int peers = 1 + g_hash_table_size(crm_peer_cache);
631
632 /* If every node has to vote down every other node, thats N*(N-1) total elections
633 * Allow some leeway before _really_ complaining
634 */
635 e->election_wins++;
636 if (e->election_wins > (peers * peers)) {
637 crm_warn("%s election storm detected: %d wins in %d seconds",
638 e->name, e->election_wins, STORM_INTERVAL);
639 e->election_wins = 0;
640 e->expires = tm_now + STORM_INTERVAL;
641 if (e->wrote_blackbox == FALSE) {
642 /* It's questionable whether a black box (from every node in the
643 * cluster) would be truly helpful in diagnosing an election
644 * storm. It's also highly doubtful a production environment
645 * would get multiple election storms from distinct causes, so
646 * saving one blackbox per process lifetime should be
647 * sufficient. Alternatives would be to save a timestamp of the
648 * last blackbox write instead of a boolean, and write a new one
649 * if some amount of time has passed; or to save a storm count,
650 * write a blackbox on every Nth occurrence.
651 */
652 crm_write_blackbox(0, NULL);
653 e->wrote_blackbox = TRUE;
654 }
655 }
656 }
657
658 if (done) {
659 do_crm_log(log_level + 1,
660 "Processed %s round %d %s (current round %d) from %s (%s)",
661 e->name, vote.election_id, vote.op, e->count, vote.from,
662 reason);
663 return e->state;
664
665 } else if (we_lose == FALSE) {
666 /* We track the time of the last election loss to implement an election
667 * dampening period, reducing the likelihood of an election storm. If
668 * this node has lost within the dampening period, don't start a new
669 * election, even if we win against a peer's vote -- the peer we lost to
670 * should win again.
671 *
672 * @TODO This has a problem case: if an election winner immediately
673 * leaves the cluster, and a new election is immediately called, all
674 * nodes could lose, with no new winner elected. The ideal solution
675 * would be to tie the election structure with the peer caches, which
676 * would allow us to clear the dampening when the previous winner
677 * leaves (and would allow other improvements as well).
678 */
679 if ((e->last_election_loss == 0)
680 || ((tm_now - e->last_election_loss) > (time_t) LOSS_DAMPEN)) {
681
682 do_crm_log(log_level, "%s round %d (owner node ID %s) pass: %s from %s (%s)",
683 e->name, vote.election_id, vote.election_owner, vote.op,
684 vote.from, reason);
685
686 e->last_election_loss = 0;
688
689 /* Start a new election by voting down this, and other, peers */
690 e->state = election_start;
691 return e->state;
692 } else {
693 char *loss_time = ctime(&e->last_election_loss);
694
695 if (loss_time) {
696 // Show only HH:MM:SS
697 loss_time += 11;
698 loss_time[8] = '\0';
699 }
700 crm_info("Ignoring %s round %d (owner node ID %s) pass vs %s because we lost less than %ds ago at %s",
701 e->name, vote.election_id, vote.election_owner, vote.from,
702 LOSS_DAMPEN, (loss_time? loss_time : "unknown"));
703 }
704 }
705
706 e->last_election_loss = tm_now;
707
708 do_crm_log(log_level, "%s round %d (owner node ID %s) lost: %s from %s (%s)",
709 e->name, vote.election_id, vote.election_owner, vote.op,
710 vote.from, reason);
711
713 send_no_vote(your_node, &vote);
714 e->state = election_lost;
715 return e->state;
716}
717
723void
725{
726 e->last_election_loss = 0;
727}
const char * name
Definition cib.c:24
GHashTable * crm_peer_cache
Definition membership.c:36
@ crm_msg_crmd
Definition cluster.h:120
crm_node_t * crm_get_peer(unsigned int id, const char *uname)
Get a cluster node cache entry.
Definition membership.c:701
gboolean crm_is_peer_active(const crm_node_t *node)
Definition membership.c:281
gboolean send_cluster_message(const crm_node_t *node, enum crm_ais_msg_types service, xmlNode *data, gboolean ordered)
Send an XML message via the cluster messaging layer.
Definition cluster.c:162
guint crm_active_peers(void)
Definition membership.c:374
uint32_t version
Definition remote.c:1
char * crm_strdup_printf(char const *format,...) G_GNUC_PRINTF(1
int compare_version(const char *version1, const char *version2)
Definition utils.c:189
char uname[MAX_NAME]
Definition cpg.c:5
A dumping ground.
#define CRM_SYSTEM_CRMD
Definition crm.h:105
#define CRM_OP_VOTE
Definition crm.h:136
#define CRM_FEATURE_SET
Definition crm.h:69
#define CRM_OP_NOVOTE
Definition crm.h:137
void election_remove(election_t *e, const char *uname)
Disregard any previous vote by specified peer.
Definition election.c:129
enum election_result election_count_vote(election_t *e, const xmlNode *message, bool can_win)
Process an election message (vote or no-vote) from a peer.
Definition election.c:534
void election_fini(election_t *e)
Free an election object.
Definition election.c:165
bool election_check(election_t *e)
Check whether local node has won an election.
Definition election.c:343
#define LOSS_DAMPEN
Definition election.c:399
void election_vote(election_t *e)
Start a new election by offering local node's candidacy.
Definition election.c:290
void election_timeout_set_period(election_t *e, guint period)
Change an election's timeout (restarting timer if running)
Definition election.c:205
election_t * election_init(const char *name, const char *uname, guint period_ms, GSourceFunc cb)
Create a new election object.
Definition election.c:89
void election_reset(election_t *e)
Stop election timer and disregard all votes.
Definition election.c:143
#define STORM_INTERVAL
Definition election.c:23
void election_timeout_stop(election_t *e)
Stop an election's timer, if running.
Definition election.c:191
enum election_result election_state(const election_t *e)
Get current state of an election.
Definition election.c:67
void election_clear_dampening(election_t *e)
Reset any election dampening currently in effect.
Definition election.c:724
Functions for conducting elections.
struct election_s election_t
election_result
@ election_won
@ election_error
@ election_lost
@ election_in_progress
@ election_start
#define create_request(task, xml_data, host_to, sys_to, sys_from, uuid_from)
Definition ipc.h:43
#define crm_info(fmt, args...)
Definition logging.h:378
void crm_write_blackbox(int nsig, const struct qb_log_callsite *callsite)
Definition logging.c:500
#define do_crm_log(level, fmt, args...)
Log a message.
Definition logging.h:172
#define crm_warn(fmt, args...)
Definition logging.h:376
#define crm_perror(level, fmt, args...)
Send a system error message to both the log and stderr.
Definition logging.h:319
#define CRM_CHECK(expr, failure_action)
Definition logging.h:235
#define crm_debug(fmt, args...)
Definition logging.h:380
#define crm_err(fmt, args...)
Definition logging.h:375
#define crm_trace(fmt, args...)
Definition logging.h:381
#define LOG_TRACE
Definition logging.h:37
Wrappers for and extensions to glib mainloop.
guint mainloop_timer_set_period(mainloop_timer_t *t, guint period_ms)
Definition mainloop.c:1349
struct mainloop_timer_s mainloop_timer_t
Definition mainloop.h:35
mainloop_timer_t * mainloop_timer_add(const char *name, guint period_ms, bool repeat, GSourceFunc cb, void *userdata)
Definition mainloop.c:1365
void mainloop_timer_del(mainloop_timer_t *t)
Definition mainloop.c:1386
void mainloop_timer_start(mainloop_timer_t *t)
Definition mainloop.c:1329
void mainloop_timer_stop(mainloop_timer_t *t)
Definition mainloop.c:1339
#define F_CRM_HOST_FROM
Definition msg_xml.h:109
#define F_CRM_ELECTION_AGE_S
Definition msg_xml.h:117
#define F_CRM_VERSION
Definition msg_xml.h:111
#define F_CRM_ELECTION_OWNER
Definition msg_xml.h:119
#define F_CRM_TASK
Definition msg_xml.h:104
#define F_CRM_ELECTION_ID
Definition msg_xml.h:116
#define F_CRM_ELECTION_AGE_US
Definition msg_xml.h:118
const char * crm_element_value(const xmlNode *data, const char *name)
Retrieve the value of an XML attribute.
Definition nvpair.c:496
const char * crm_xml_add_timeval(xmlNode *xml, const char *name_sec, const char *name_usec, const struct timeval *value)
Create XML attributes for seconds and microseconds.
Definition nvpair.c:472
int crm_element_value_int(const xmlNode *data, const char *name, int *dest)
Retrieve the integer value of an XML attribute.
Definition nvpair.c:532
const char * crm_xml_add_int(xmlNode *node, const char *name, int value)
Create an XML attribute with specified name and integer value.
Definition nvpair.c:398
int crm_element_value_timeval(const xmlNode *data, const char *name_sec, const char *name_usec, struct timeval *dest)
Retrieve the value of XML second/microsecond attributes as time.
Definition nvpair.c:646
const char * crm_xml_add(xmlNode *node, const char *name, const char *value)
Create an XML attribute with specified name and value.
Definition nvpair.c:302
unsigned int timeout
Definition pcmk_fence.c:32
#define CRM_ASSERT(expr)
Definition results.h:42
GHashTable * pcmk__strkey_table(GDestroyNotify key_destroy_func, GDestroyNotify value_destroy_func)
Definition strings.c:611
@ pcmk__str_none
char * uname
Definition cluster.h:59
char * uuid
Definition cluster.h:60
Wrappers for and extensions to libxml2.
void free_xml(xmlNode *child)
Definition xml.c:813