1 /*
2 * Copyright 2004-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 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 <stdbool.h>
13 #include <sys/time.h>
14 #include <sys/resource.h>
15
16 #include <crm/crm.h>
17 #include <crm/common/mainloop.h>
18 #include <crm/common/xml.h>
19
20 #include "crmcluster_private.h"
21
22 #define STORM_INTERVAL 2 /* in seconds */
23
24 struct pcmk__election {
25 enum election_result state; // Current state of election
26 unsigned int count; // How many times local node has voted
27 void (*cb)(pcmk_cluster_t *); // Function to call if election is won
28 GHashTable *voted; // Key = node name, value = how node voted
29 mainloop_timer_t *timeout; // When to abort if all votes not received
30 int election_wins; // Track wins, for storm detection
31 bool wrote_blackbox; // Write a storm blackbox at most once
32 time_t expires; // When storm detection period ends
33 time_t last_election_loss; // When dampening period ends
34 };
35
36 static void
37 election_complete(pcmk_cluster_t *cluster)
38 {
39 pcmk__assert((cluster != NULL) && (cluster->priv->election != NULL));
40 cluster->priv->election->state = election_won;
41 if (cluster->priv->election->cb != NULL) {
42 cluster->priv->election->cb(cluster);
43 }
44 election_reset(cluster);
45 }
46
47 static gboolean
48 election_timer_cb(void *user_data)
49 {
50 pcmk_cluster_t *cluster = user_data;
51
52 pcmk__info("Declaring local node as winner after election timed out");
53 election_complete(cluster);
54 return FALSE;
55 }
56
57 /*!
58 * \internal
59 * \brief Get current state of an election
60 *
61 * \param[in] cluster Cluster with election
62 *
63 * \return Current state of \e
64 */
65 enum election_result
66 election_state(const pcmk_cluster_t *cluster)
67 {
68 if ((cluster == NULL) || (cluster->priv->election == NULL)) {
69 return election_error;
70 }
71 return cluster->priv->election->state;
72 }
73
74 /* The local node will be declared the winner if missing votes are not received
75 * within this time. The value is chosen to be the same as the default for the
76 * election-timeout cluster option.
77 */
78 #define ELECTION_TIMEOUT_MS 120000
79
80 /*!
81 * \internal
82 * \brief Track election state in a cluster
83 *
84 * Every node that wishes to participate in an election must initialize the
85 * election once, typically at start-up.
86 *
87 * \param[in] cluster Cluster that election is for
88 * \param[in] cb Function to call if local node wins election
89 */
90 void
91 election_init(pcmk_cluster_t *cluster, void (*cb)(pcmk_cluster_t *))
92 {
93 const char *name = pcmk__s(crm_system_name, "election");
94
95 CRM_CHECK(cluster->priv->election == NULL, return);
96
|
CID (unavailable; MK=bf77ad6f79598a6146f5533ef1330eae) (#1 of 1): Resource not released (INCOMPLETE_DEALLOCATOR): |
|
(1) Event allocation: |
Memory is allocated. [details] |
|
(2) Event allocation: |
The field "cluster->priv->election" is allocated, but not released in the identified deallocator. |
| Also see events: |
[deallocator] |
97 cluster->priv->election = pcmk__assert_alloc(1, sizeof(pcmk__election_t));
98 cluster->priv->election->cb = cb;
99 cluster->priv->election->timeout = mainloop_timer_add(name,
100 ELECTION_TIMEOUT_MS,
101 FALSE,
102 election_timer_cb,
103 cluster);
104 }
105
106 /*!
107 * \internal
108 * \brief Disregard any previous vote by specified peer
109 *
110 * This discards any recorded vote from a specified peer. Election users should
111 * call this whenever a voting peer becomes inactive.
112 *
113 * \param[in,out] cluster Cluster with election
114 * \param[in] uname Name of peer to disregard
115 */
116 void
117 election_remove(pcmk_cluster_t *cluster, const char *uname)
118 {
119 if ((cluster != NULL) && (cluster->priv->election != NULL)
120 && (uname != NULL) && (cluster->priv->election->voted != NULL)) {
121 pcmk__trace("Discarding (no-)vote from lost peer %s", uname);
122 g_hash_table_remove(cluster->priv->election->voted, uname);
123 }
124 }
125
126 /*!
127 * \internal
128 * \brief Stop election timer and disregard all votes
129 *
130 * \param[in,out] cluster Cluster with election
131 */
132 void
133 election_reset(pcmk_cluster_t *cluster)
134 {
135 if ((cluster != NULL) && (cluster->priv->election != NULL)) {
136 pcmk__trace("Resetting election");
137 mainloop_timer_stop(cluster->priv->election->timeout);
138 g_clear_pointer(&cluster->priv->election->voted, g_hash_table_destroy);
139 }
140 }
141
142 /*!
143 * \internal
144 * \brief Free an election object
145 *
146 * Free all memory associated with an election object, stopping its
147 * election timer (if running).
148 *
149 * \param[in,out] cluster Cluster with election
150 */
151 void
|
(3) Event deallocator: |
Deallocator for "struct pcmk__cluster". |
| Also see events: |
[allocation][allocation] |
152 election_fini(pcmk_cluster_t *cluster)
153 {
154 if ((cluster != NULL) && (cluster->priv->election != NULL)) {
155 election_reset(cluster);
156 pcmk__trace("Destroying election");
157 mainloop_timer_del(cluster->priv->election->timeout);
158 g_clear_pointer(&cluster->priv->election, free);
159 }
160 }
161
162 static void
163 election_timeout_start(pcmk_cluster_t *cluster)
164 {
165 mainloop_timer_start(cluster->priv->election->timeout);
166 }
167
168 /*!
169 * \internal
170 * \brief Stop an election's timer, if running
171 *
172 * \param[in,out] cluster Cluster with election
173 */
174 void
175 election_timeout_stop(pcmk_cluster_t *cluster)
176 {
177 if ((cluster != NULL) && (cluster->priv->election != NULL)) {
178 mainloop_timer_stop(cluster->priv->election->timeout);
179 }
180 }
181
182 /*!
183 * \internal
184 * \brief Change an election's timeout (restarting timer if running)
185 *
186 * \param[in,out] cluster Cluster with election
187 * \param[in] period New timeout
188 */
189 void
190 election_timeout_set_period(pcmk_cluster_t *cluster, unsigned int period)
191 {
192 CRM_CHECK((cluster != NULL) && (cluster->priv->election != NULL), return);
193 mainloop_timer_set_period(cluster->priv->election->timeout, period);
194 }
195
196 static int
197 get_uptime(struct timeval *output)
198 {
199 static time_t expires = 0;
200 static struct rusage info;
201
202 time_t tm_now = time(NULL);
203
204 if (expires < tm_now) {
205 int rc = 0;
206
207 output->tv_sec = 0;
208 output->tv_usec = 0;
209
210 info.ru_utime.tv_sec = 0;
211 info.ru_utime.tv_usec = 0;
212
213 rc = getrusage(RUSAGE_SELF, &info);
214 if (rc < 0) {
215 pcmk__err("Could not calculate the current uptime: %s",
216 strerror(errno));
217 expires = 0;
218 return -1;
219 }
220
221 pcmk__debug("Current CPU usage is: %llds, %lldus",
222 (long long) info.ru_utime.tv_sec,
223 (long long) info.ru_utime.tv_usec);
224 }
225
226 expires = tm_now + STORM_INTERVAL; /* N seconds after the last _access_ */
227 output->tv_sec = info.ru_utime.tv_sec;
228 output->tv_usec = info.ru_utime.tv_usec;
229
230 return 1;
231 }
232
233 static int
234 compare_age(struct timeval your_age)
235 {
236 struct timeval our_age;
237
238 get_uptime(&our_age); /* If an error occurred, our_age will be compared as {0,0} */
239
240 if (our_age.tv_sec > your_age.tv_sec) {
241 pcmk__debug("Win: %lld vs %lld (seconds)",
242 (long long) our_age.tv_sec, (long long) your_age.tv_sec);
243 return 1;
244 } else if (our_age.tv_sec < your_age.tv_sec) {
245 pcmk__debug("Lose: %lld vs %lld (seconds)",
246 (long long) our_age.tv_sec, (long long) your_age.tv_sec);
247 return -1;
248 } else if (our_age.tv_usec > your_age.tv_usec) {
249 pcmk__debug("Win: %lld.%06lld vs %lld.%06lld (usec)",
250 (long long) our_age.tv_sec, (long long) our_age.tv_usec,
251 (long long) your_age.tv_sec, (long long) your_age.tv_usec);
252 return 1;
253 } else if (our_age.tv_usec < your_age.tv_usec) {
254 pcmk__debug("Lose: %lld.%06lld vs %lld.%06lld (usec)",
255 (long long) our_age.tv_sec, (long long) our_age.tv_usec,
256 (long long) your_age.tv_sec, (long long) your_age.tv_usec);
257 return -1;
258 }
259
260 return 0;
261 }
262
263 /*!
264 * \internal
265 * \brief Start a new election by offering local node's candidacy
266 *
267 * Broadcast a "vote" election message containing the local node's ID,
268 * (incremented) election counter, and uptime, and start the election timer.
269 *
270 * \param[in,out] cluster Cluster with election
271 *
272 * \note Any nodes agreeing to the candidacy will send a "no-vote" reply, and if
273 * all active peers do so, or if the election times out, the local node
274 * wins the election. (If we lose to any peer vote, we will stop the
275 * timer, so a timeout means we did not lose -- either some peer did not
276 * vote, or we did not call election_check() in time.)
277 */
278 void
279 election_vote(pcmk_cluster_t *cluster)
280 {
281 struct timeval age;
282 xmlNode *vote = NULL;
283 pcmk__node_status_t *our_node = NULL;
284 const char *message_type = NULL;
285
286 CRM_CHECK((cluster != NULL) && (cluster->priv->election != NULL), return);
287
288 if (cluster->priv->node_name == NULL) {
289 pcmk__err("Cannot start an election: Local node name unknown");
290 return;
291 }
292
293 our_node = pcmk__get_node(0, cluster->priv->node_name, NULL,
294 pcmk__node_search_cluster_member);
295 if (!pcmk__cluster_is_node_active(our_node)) {
296 pcmk__trace("Cannot vote yet: local node not connected to cluster");
297 return;
298 }
299
300 election_reset(cluster);
301 cluster->priv->election->state = election_in_progress;
302 message_type = pcmk__server_message_type(cluster->priv->server);
303
304 /* @COMPAT We use message_type as the sender and recipient system for
305 * backward compatibility (see T566).
306 */
307 vote = pcmk__new_request(cluster->priv->server, message_type,
308 NULL, message_type, CRM_OP_VOTE, NULL);
309
310 cluster->priv->election->count++;
311 pcmk__xe_set(vote, PCMK__XA_ELECTION_OWNER,
312 pcmk__cluster_get_xml_id(our_node));
313 pcmk__xe_set_int(vote, PCMK__XA_ELECTION_ID,
314 cluster->priv->election->count);
315
316 // Warning: PCMK__XA_ELECTION_AGE_NANO_SEC value is actually microseconds
317 get_uptime(&age);
318 pcmk__xe_set_timeval(vote, PCMK__XA_ELECTION_AGE_SEC,
319 PCMK__XA_ELECTION_AGE_NANO_SEC, &age);
320
321 pcmk__cluster_send_message(NULL, cluster->priv->server, vote);
322 pcmk__xml_free(vote);
323
324 pcmk__debug("Started election round %u", cluster->priv->election->count);
325 election_timeout_start(cluster);
326 }
327
328 /*!
329 * \internal
330 * \brief Check whether local node has won an election
331 *
332 * If all known peers have sent no-vote messages, stop the election timer, set
333 * the election state to won, and call any registered win callback.
334 *
335 * \param[in,out] cluster Cluster with election
336 *
337 * \return TRUE if local node has won, FALSE otherwise
338 * \note If all known peers have sent no-vote messages, but the election owner
339 * does not call this function, the election will not be won (and the
340 * callback will not be called) until the election times out.
341 * \note This should be called when election_count_vote() returns
342 * \c election_in_progress.
343 */
344 bool
345 election_check(pcmk_cluster_t *cluster)
346 {
347 int voted_size = 0;
348 int num_members = 0;
349
350 CRM_CHECK((cluster != NULL) && (cluster->priv->election != NULL),
351 return false);
352
353 if (cluster->priv->election->voted == NULL) {
354 pcmk__trace("Election check requested, but no votes received yet");
355 return FALSE;
356 }
357
358 voted_size = g_hash_table_size(cluster->priv->election->voted);
359 num_members = pcmk__cluster_num_active_nodes();
360
361 /* in the case of #voted > #members, it is better to
362 * wait for the timeout and give the cluster time to
363 * stabilize
364 */
365 if (voted_size >= num_members) {
366 /* we won and everyone has voted */
367 election_timeout_stop(cluster);
368 if (voted_size > num_members) {
369 GHashTableIter gIter;
370 const pcmk__node_status_t *node = NULL;
371 char *key = NULL;
372
373 pcmk__warn("Received too many votes in election");
374 g_hash_table_iter_init(&gIter, pcmk__peer_cache);
375 while (g_hash_table_iter_next(&gIter, NULL, (void **) &node)) {
376 if (pcmk__cluster_is_node_active(node)) {
377 pcmk__warn("* expected vote: %s", node->name);
378 }
379 }
380
381 g_hash_table_iter_init(&gIter, cluster->priv->election->voted);
382 while (g_hash_table_iter_next(&gIter, (void **) &key, NULL)) {
383 pcmk__warn("* actual vote: %s", key);
384 }
385
386 }
387
388 pcmk__info("Election won by local node");
389 election_complete(cluster);
390 return TRUE;
391
392 } else {
393 pcmk__debug("Election still waiting on %d of %d vote%s",
394 (num_members - voted_size), num_members,
395 pcmk__plural_s(num_members));
396 }
397
398 return FALSE;
399 }
400
401 #define LOSS_DAMPEN 2 /* in seconds */
402
403 struct vote {
404 const char *op;
405 const char *from;
406 const char *version;
407 const char *election_owner;
408 int election_id;
409 struct timeval age;
410 };
411
412 /*!
413 * \internal
414 * \brief Unpack an election message
415 *
416 * \param[in] message Election message XML
417 * \param[out] vote Parsed fields from message
418 *
419 * \return TRUE if election message and election are valid, FALSE otherwise
420 * \note The parsed struct's pointer members are valid only for the lifetime of
421 * the message argument.
422 */
423 static bool
424 parse_election_message(const xmlNode *message, struct vote *vote)
425 {
426 CRM_CHECK(message && vote, return FALSE);
427
428 vote->election_id = -1;
429 vote->age.tv_sec = -1;
430 vote->age.tv_usec = -1;
431
432 vote->op = pcmk__xe_get(message, PCMK__XA_CRM_TASK);
433 vote->from = pcmk__xe_get(message, PCMK__XA_SRC);
434 vote->version = pcmk__xe_get(message, PCMK_XA_VERSION);
435 vote->election_owner = pcmk__xe_get(message, PCMK__XA_ELECTION_OWNER);
436
437 pcmk__xe_get_int(message, PCMK__XA_ELECTION_ID, &vote->election_id);
438
439 if ((vote->op == NULL) || (vote->from == NULL) || (vote->version == NULL)
440 || (vote->election_owner == NULL) || (vote->election_id < 0)) {
441
442 pcmk__warn("Invalid %s message from %s", pcmk__s(vote->op, "election"),
443 pcmk__s(vote->from, "unspecified node"));
444 pcmk__log_xml_trace(message, "bad-vote");
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.
452 Warning: PCMK__XA_ELECTION_AGE_NANO_SEC value is in microseconds.
453 */
454 if ((pcmk__xe_get_timeval(message, PCMK__XA_ELECTION_AGE_SEC,
455 PCMK__XA_ELECTION_AGE_NANO_SEC,
456 &vote->age) != pcmk_rc_ok)
457 || (vote->age.tv_sec < 0) || (vote->age.tv_usec < 0)) {
458
459 pcmk__warn("Cannot count election %s from %s because uptime is "
460 "missing or invalid",
461 vote->op, vote->from);
462 return FALSE;
463 }
464
465 } else if (!pcmk__str_eq(vote->op, CRM_OP_NOVOTE, pcmk__str_none)) {
466 pcmk__info("Cannot process election message from %s because %s is not "
467 "a known election op",
468 vote->from, vote->op);
469 return FALSE;
470 }
471
472 /* If the membership cache is NULL, we REALLY shouldn't be voting --
473 * the question is how we managed to get here.
474 */
475 if (pcmk__peer_cache == NULL) {
476 pcmk__info("Cannot count election %s from %s becasue no peer "
477 "information available",
478 vote->op, vote->from);
479 return FALSE;
480 }
481 return TRUE;
482 }
483
484 static void
485 record_vote(pcmk_cluster_t *cluster, struct vote *vote)
486 {
487 pcmk__assert((vote->from != NULL) && (vote->op != NULL));
488
489 if (cluster->priv->election->voted == NULL) {
490 cluster->priv->election->voted = pcmk__strkey_table(free, free);
491 }
492 pcmk__insert_dup(cluster->priv->election->voted, vote->from, vote->op);
493 }
494
495 static void
496 send_no_vote(pcmk_cluster_t *cluster, pcmk__node_status_t *peer,
497 struct vote *vote)
498 {
499 const char *message_type = NULL;
500 xmlNode *novote = NULL;
501
502 message_type = pcmk__server_message_type(cluster->priv->server);
503 novote = pcmk__new_request(cluster->priv->server, message_type,
504 vote->from, message_type, CRM_OP_NOVOTE, NULL);
505 pcmk__xe_set(novote, PCMK__XA_ELECTION_OWNER, vote->election_owner);
506 pcmk__xe_set_int(novote, PCMK__XA_ELECTION_ID, vote->election_id);
507
508 pcmk__cluster_send_message(peer, cluster->priv->server, novote);
509 pcmk__xml_free(novote);
510 }
511
512 /*!
513 * \internal
514 * \brief Process an election message (vote or no-vote) from a peer
515 *
516 * \param[in,out] cluster Cluster with election
517 * \param[in] message Election message XML from peer
518 * \param[in] can_win Whether local node is eligible to win
519 *
520 * \return Election state after new vote is considered
521 * \note If the peer message is a vote, and we prefer the peer to win, this will
522 * send a no-vote reply to the peer.
523 * \note The situations "we lost to this vote" from "this is a late no-vote
524 * after we've already lost" both return election_lost. If a caller needs
525 * to distinguish them, it should save the current state before calling
526 * this function, and then compare the result.
527 */
528 enum election_result
529 election_count_vote(pcmk_cluster_t *cluster, const xmlNode *message,
530 bool can_win)
531 {
532 int log_level = LOG_INFO;
533 gboolean done = FALSE;
534 gboolean we_lose = FALSE;
535 const char *reason = NULL;
536 bool we_are_owner = FALSE;
537 pcmk__node_status_t *our_node = NULL;
538 pcmk__node_status_t *your_node = NULL;
539 time_t tm_now = time(NULL);
540 struct vote vote;
541
542 CRM_CHECK((cluster != NULL) && (cluster->priv->election != NULL)
543 && (message != NULL) && (cluster->priv->node_name != NULL),
544 return election_error);
545
546 if (!parse_election_message(message, &vote)) {
547 return election_error;
548 }
549
550 your_node = pcmk__get_node(0, vote.from, NULL,
551 pcmk__node_search_cluster_member);
552 our_node = pcmk__get_node(0, cluster->priv->node_name, NULL,
553 pcmk__node_search_cluster_member);
554 we_are_owner = (our_node != NULL)
555 && pcmk__str_eq(pcmk__cluster_get_xml_id(our_node),
556 vote.election_owner, pcmk__str_none);
557
558 if (!can_win) {
559 reason = "Not eligible";
560 we_lose = TRUE;
561
562 } else if (!pcmk__cluster_is_node_active(our_node)) {
563 reason = "We are not part of the cluster";
564 log_level = LOG_ERR;
565 we_lose = TRUE;
566
567 } else if (we_are_owner
568 && (vote.election_id != cluster->priv->election->count)) {
569 log_level = LOG_TRACE;
570 reason = "Superseded";
571 done = TRUE;
572
573 } else if (!pcmk__cluster_is_node_active(your_node)) {
574 /* Possibly we cached the message in the FSA queue at a point that it wasn't */
575 reason = "Peer is not part of our cluster";
576 log_level = LOG_WARNING;
577 done = TRUE;
578
579 } else if (pcmk__str_eq(vote.op, CRM_OP_NOVOTE, pcmk__str_none)
580 || pcmk__str_eq(vote.from, cluster->priv->node_name,
581 pcmk__str_casei)) {
582 /* Receiving our own broadcast vote, or a no-vote from peer, is a vote
583 * for us to win
584 */
585 if (!we_are_owner) {
586 pcmk__warn("Cannot count election round %d %s from %s because we "
587 "did not start election (node ID %s did)",
588 vote.election_id, vote.op, vote.from,
589 vote.election_owner);
590 return election_error;
591 }
592 if (cluster->priv->election->state != election_in_progress) {
593 // Should only happen if we already lost
594 pcmk__debug("Not counting election round %d %s from %s because no "
595 "election in progress",
596 vote.election_id, vote.op, vote.from);
597 return cluster->priv->election->state;
598 }
599 record_vote(cluster, &vote);
600 reason = "Recorded";
601 done = TRUE;
602
603 } else {
604 // A peer vote requires a comparison to determine which node is better
605 int age_result = compare_age(vote.age);
606 int version_result = pcmk__compare_versions(vote.version,
607 CRM_FEATURE_SET);
608
609 if (version_result < 0) {
610 reason = "Version";
611 we_lose = TRUE;
612
613 } else if (version_result > 0) {
614 reason = "Version";
615
616 } else if (age_result < 0) {
617 reason = "Uptime";
618 we_lose = TRUE;
619
620 } else if (age_result > 0) {
621 reason = "Uptime";
622
623 } else if (strcasecmp(cluster->priv->node_name, vote.from) > 0) {
624 reason = "Host name";
625 we_lose = TRUE;
626
627 } else {
628 reason = "Host name";
629 }
630 }
631
632 if (cluster->priv->election->expires < tm_now) {
633 cluster->priv->election->election_wins = 0;
634 cluster->priv->election->expires = tm_now + STORM_INTERVAL;
635
636 } else if (done == FALSE && we_lose == FALSE) {
637 int peers = 1 + g_hash_table_size(pcmk__peer_cache);
638
639 /* If every node has to vote down every other node, thats N*(N-1) total elections
640 * Allow some leeway before _really_ complaining
641 */
642 cluster->priv->election->election_wins++;
643 if (cluster->priv->election->election_wins > (peers * peers)) {
644 pcmk__warn("Election storm detected: %d wins in %d seconds",
645 cluster->priv->election->election_wins, STORM_INTERVAL);
646 cluster->priv->election->election_wins = 0;
647 cluster->priv->election->expires = tm_now + STORM_INTERVAL;
648 if (!(cluster->priv->election->wrote_blackbox)) {
649 /* It's questionable whether a black box (from every node in the
650 * cluster) would be truly helpful in diagnosing an election
651 * storm. It's also highly doubtful a production environment
652 * would get multiple election storms from distinct causes, so
653 * saving one blackbox per process lifetime should be
654 * sufficient. Alternatives would be to save a timestamp of the
655 * last blackbox write instead of a boolean, and write a new one
656 * if some amount of time has passed; or to save a storm count,
657 * write a blackbox on every Nth occurrence.
658 */
659 crm_write_blackbox(0, NULL);
660 cluster->priv->election->wrote_blackbox = true;
661 }
662 }
663 }
664
665 if (done) {
666 do_crm_log(log_level + 1,
667 "Processed election round %u %s (current round %d) "
668 "from %s (%s)",
669 vote.election_id, vote.op, cluster->priv->election->count,
670 vote.from, reason);
671 return cluster->priv->election->state;
672
673 } else if (we_lose == FALSE) {
674 /* We track the time of the last election loss to implement an election
675 * dampening period, reducing the likelihood of an election storm. If
676 * this node has lost within the dampening period, don't start a new
677 * election, even if we win against a peer's vote -- the peer we lost to
678 * should win again.
679 *
680 * @TODO This has a problem case: if an election winner immediately
681 * leaves the cluster, and a new election is immediately called, all
682 * nodes could lose, with no new winner elected. The ideal solution
683 * would be to tie the election structure with the peer caches, which
684 * would allow us to clear the dampening when the previous winner
685 * leaves (and would allow other improvements as well).
686 */
687 if ((cluster->priv->election->last_election_loss == 0)
688 || ((tm_now - cluster->priv->election->last_election_loss)
689 > (time_t) LOSS_DAMPEN)) {
690
691 do_crm_log(log_level,
692 "Election round %d (started by node ID %s) pass: "
693 "%s from %s (%s)",
694 vote.election_id, vote.election_owner, vote.op,
695 vote.from, reason);
696
697 cluster->priv->election->last_election_loss = 0;
698 election_timeout_stop(cluster);
699
700 /* Start a new election by voting down this, and other, peers */
701 cluster->priv->election->state = election_start;
702 return cluster->priv->election->state;
703 } else {
704 char *loss_time = NULL;
705
706 loss_time = ctime(&cluster->priv->election->last_election_loss);
707 if (loss_time) {
708 // Show only HH:MM:SS
709 loss_time += 11;
710 loss_time[8] = '\0';
711 }
712 pcmk__info("Ignoring election round %d (started by node ID %s) "
713 "pass vs %s because we lost less than %ds ago at %s",
714 vote.election_id, vote.election_owner, vote.from,
715 LOSS_DAMPEN, pcmk__s(loss_time, "unknown"));
716 }
717 }
718
719 cluster->priv->election->last_election_loss = tm_now;
720
721 do_crm_log(log_level,
722 "Election round %d (started by node ID %s) lost: "
723 "%s from %s (%s)",
724 vote.election_id, vote.election_owner, vote.op,
725 vote.from, reason);
726
727 election_reset(cluster);
728 send_no_vote(cluster, your_node, &vote);
729 cluster->priv->election->state = election_lost;
730 return cluster->priv->election->state;
731 }
732
733 /*!
734 * \internal
735 * \brief Reset any election dampening currently in effect
736 *
737 * \param[in,out] cluster Cluster with election
738 */
739 void
740 election_clear_dampening(pcmk_cluster_t *cluster)
741 {
742 if ((cluster != NULL) && (cluster->priv->election != NULL)) {
743 cluster->priv->election->last_election_loss = 0;
744 }
745 }
746