1 /*
2 * Copyright 2004-2019 Andrew Beekhof <andrew@beekhof.net>
3 *
4 * This source code is licensed under the GNU Lesser General Public License
5 * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
6 */
7
8 #include <crm_internal.h>
9
10 #include <sys/time.h>
11 #include <sys/resource.h>
12
13 #include <crm/msg_xml.h>
14 #include <crm/common/xml.h>
15
16 #include <crm/common/mainloop.h>
17 #include <crm/cluster/internal.h>
18 #include <crm/cluster/election.h>
19 #include <crm/crm.h>
20
21 #define STORM_INTERVAL 2 /* in seconds */
22
23 struct election_s {
24 enum election_result state;
25 guint count; // How many times local node has voted
26 char *name; // Descriptive name for this election
27 char *uname; // Local node's name
28 GSourceFunc cb; // Function to call if election is won
29 GHashTable *voted; // Key = node name, value = how node voted
30 mainloop_timer_t *timeout; // When to abort if all votes not received
31 int election_wins; // Track wins, for storm detection
32 bool wrote_blackbox; // Write a storm blackbox at most once
33 time_t expires; // When storm detection period ends
34 time_t last_election_loss; // When dampening period ends
35 };
36
election_complete(election_t * e)37 static void election_complete(election_t *e)
38 {
39 e->state = election_won;
40
41 if(e->cb) {
42 e->cb(e);
43 }
44
45 election_reset(e);
46 }
47
election_timer_cb(gpointer user_data)48 static gboolean election_timer_cb(gpointer user_data)
49 {
50 election_t *e = user_data;
51
52 crm_info("%s timed out, declaring local node as winner", e->name);
53 election_complete(e);
54 return FALSE;
55 }
56
57 enum election_result
election_state(election_t * e)58 election_state(election_t *e)
59 {
60 if(e) {
61 return e->state;
62 }
63 return election_error;
64 }
65
66 /*!
67 * \brief Create a new election object
68 *
69 * Every node that wishes to participate in an election must create an election
70 * object. Typically, this should be done once, at start-up. A caller should
71 * only create a single election object.
72 *
73 * \param[in] name Label for election (for logging)
74 * \param[in] uname Local node's name
75 * \param[in] period_ms How long to wait for all peers to vote
76 * \param[in] cb Function to call if local node wins election
77 *
78 * \return Newly allocated election object on success, NULL on error
79 * \note The caller is responsible for freeing the returned value using
80 * election_fini().
81 */
82 election_t *
election_init(const char * name,const char * uname,guint period_ms,GSourceFunc cb)83 election_init(const char *name, const char *uname, guint period_ms, GSourceFunc cb)
84 {
85 election_t *e = NULL;
86
87 static guint count = 0;
88
89 CRM_CHECK(uname != NULL, return NULL);
90
91 e = calloc(1, sizeof(election_t));
92 if (e == NULL) {
93 crm_perror(LOG_CRIT, "Cannot create election");
94 return NULL;
95 }
96
97 e->uname = strdup(uname);
98 if (e->uname == NULL) {
99 crm_perror(LOG_CRIT, "Cannot create election");
100 free(e);
101 return NULL;
102 }
103
104 e->name = name? crm_strdup_printf("election-%s", name)
105 : crm_strdup_printf("election-%u", count++);
106 e->cb = cb;
107 e->timeout = mainloop_timer_add(e->name, period_ms, FALSE,
108 election_timer_cb, e);
109 crm_trace("Created %s", e->name);
110 return e;
111 }
112
113 /*!
114 * \brief Disregard any previous vote by specified peer
115 *
116 * This discards any recorded vote from a specified peer. Election users should
117 * call this whenever a voting peer becomes inactive.
118 *
119 * \param[in] e Election object
120 * \param[in] uname Name of peer to disregard
121 */
122 void
election_remove(election_t * e,const char * uname)123 election_remove(election_t *e, const char *uname)
124 {
125 if(e && uname && e->voted) {
126 crm_trace("Discarding %s (no-)vote from lost peer %s", e->name, uname);
127 g_hash_table_remove(e->voted, uname);
128 }
129 }
130
131 /*!
132 * \brief Stop election timer and disregard all votes
133 *
134 * \param[in] e Election object
135 */
136 void
election_reset(election_t * e)137 election_reset(election_t *e)
138 {
139 crm_trace("Resetting election %s", e->name);
140 if(e) {
141 mainloop_timer_stop(e->timeout);
142 }
143 if (e && e->voted) {
144 crm_trace("Destroying voted cache with %d members", g_hash_table_size(e->voted));
145 g_hash_table_destroy(e->voted);
146 e->voted = NULL;
147 }
148 }
149
150 /*!
151 * \brief Free an election object
152 *
153 * Free all memory associated with an election object, stopping its
154 * election timer (if running).
155 *
156 * \param[in] e Election object
157 */
158 void
election_fini(election_t * e)159 election_fini(election_t *e)
160 {
161 if(e) {
162 election_reset(e);
163 crm_trace("Destroying %s", e->name);
164 mainloop_timer_del(e->timeout);
165 free(e->uname);
166 free(e->name);
167 free(e);
168 }
169 }
170
171 static void
election_timeout_start(election_t * e)172 election_timeout_start(election_t *e)
173 {
174 if(e) {
175 mainloop_timer_start(e->timeout);
176 }
177 }
178
179 /*!
180 * \brief Stop an election's timer, if running
181 *
182 * \param[in] e Election object
183 */
184 void
election_timeout_stop(election_t * e)185 election_timeout_stop(election_t *e)
186 {
187 if(e) {
188 mainloop_timer_stop(e->timeout);
189 }
190 }
191
192 /*!
193 * \brief Change an election's timeout (restarting timer if running)
194 *
195 * \param[in] e Election object
196 * \param[in] period New timeout
197 */
198 void
election_timeout_set_period(election_t * e,guint period)199 election_timeout_set_period(election_t *e, guint period)
200 {
201 if(e) {
202 mainloop_timer_set_period(e->timeout, period);
203 } else {
204 crm_err("No election defined");
205 }
206 }
207
208 static int
crm_uptime(struct timeval * output)209 crm_uptime(struct timeval *output)
210 {
211 static time_t expires = 0;
212 static struct rusage info;
213
214 time_t tm_now = time(NULL);
215
216 if (expires < tm_now) {
217 int rc = 0;
218
219 info.ru_utime.tv_sec = 0;
220 info.ru_utime.tv_usec = 0;
221 rc = getrusage(RUSAGE_SELF, &info);
222
223 output->tv_sec = 0;
224 output->tv_usec = 0;
225
226 if (rc < 0) {
227 crm_perror(LOG_ERR, "Could not calculate the current uptime");
228 expires = 0;
229 return -1;
230 }
231
232 crm_debug("Current CPU usage is: %lds, %ldus", (long)info.ru_utime.tv_sec,
233 (long)info.ru_utime.tv_usec);
234 }
235
236 expires = tm_now + STORM_INTERVAL; /* N seconds after the last _access_ */
237 output->tv_sec = info.ru_utime.tv_sec;
238 output->tv_usec = info.ru_utime.tv_usec;
239
240 return 1;
241 }
242
243 static int
crm_compare_age(struct timeval your_age)244 crm_compare_age(struct timeval your_age)
245 {
246 struct timeval our_age;
247
248 crm_uptime(&our_age); /* If an error occurred, our_age will be compared as {0,0} */
249
250 if (our_age.tv_sec > your_age.tv_sec) {
251 crm_debug("Win: %ld vs %ld (seconds)", (long)our_age.tv_sec, (long)your_age.tv_sec);
252 return 1;
253 } else if (our_age.tv_sec < your_age.tv_sec) {
254 crm_debug("Lose: %ld vs %ld (seconds)", (long)our_age.tv_sec, (long)your_age.tv_sec);
255 return -1;
256 } else if (our_age.tv_usec > your_age.tv_usec) {
257 crm_debug("Win: %ld.%06ld vs %ld.%06ld (usec)",
258 (long)our_age.tv_sec, (long)our_age.tv_usec, (long)your_age.tv_sec, (long)your_age.tv_usec);
259 return 1;
260 } else if (our_age.tv_usec < your_age.tv_usec) {
261 crm_debug("Lose: %ld.%06ld vs %ld.%06ld (usec)",
262 (long)our_age.tv_sec, (long)our_age.tv_usec, (long)your_age.tv_sec, (long)your_age.tv_usec);
263 return -1;
264 }
265
266 return 0;
267 }
268
269 /*!
270 * \brief Start a new election by offering local node's candidacy
271 *
272 * Broadcast a "vote" election message containing the local node's ID,
273 * (incremented) election counter, and uptime, and start the election timer.
274 *
275 * \param[in] e Election object
276 * \note Any nodes agreeing to the candidacy will send a "no-vote" reply, and if
277 * all active peers do so, or if the election times out, the local node
278 * wins the election. (If we lose to any peer vote, we will stop the
279 * timer, so a timeout means we did not lose -- either some peer did not
280 * vote, or we did not call election_check() in time.)
281 */
282 void
election_vote(election_t * e)283 election_vote(election_t *e)
284 {
285 struct timeval age;
286 xmlNode *vote = NULL;
287 crm_node_t *our_node;
288
289 if (e == NULL) {
290 crm_trace("Election vote requested, but no election available");
291 return;
292 }
293
294 our_node = crm_get_peer(0, e->uname);
295 if ((our_node == NULL) || (crm_is_peer_active(our_node) == FALSE)) {
296 crm_trace("Cannot vote in %s yet: local node not connected to cluster",
297 e->name);
298 return;
299 }
300
301 election_reset(e);
302 e->state = election_in_progress;
303 vote = create_request(CRM_OP_VOTE, NULL, NULL, CRM_SYSTEM_CRMD, CRM_SYSTEM_CRMD, NULL);
304
305 e->count++;
306 crm_xml_add(vote, F_CRM_ELECTION_OWNER, our_node->uuid);
307 crm_xml_add_int(vote, F_CRM_ELECTION_ID, e->count);
308
309 crm_uptime(&age);
310 crm_xml_add_int(vote, F_CRM_ELECTION_AGE_S, age.tv_sec);
311 crm_xml_add_int(vote, F_CRM_ELECTION_AGE_US, age.tv_usec);
312
313 send_cluster_message(NULL, crm_msg_crmd, vote, TRUE);
314 free_xml(vote);
315
316 crm_debug("Started %s round %d", e->name, e->count);
317 election_timeout_start(e);
318 return;
319 }
320
321 /*!
322 * \brief Check whether local node has won an election
323 *
324 * If all known peers have sent no-vote messages, stop the election timer, set
325 * the election state to won, and call any registered win callback.
326 *
327 * \param[in] e Election object
328 *
329 * \return TRUE if local node has won, FALSE otherwise
330 * \note If all known peers have sent no-vote messages, but the election owner
331 * does not call this function, the election will not be won (and the
332 * callback will not be called) until the election times out.
333 * \note This should be called when election_count_vote() returns
334 * \c election_in_progress.
335 */
336 bool
election_check(election_t * e)337 election_check(election_t *e)
338 {
339 int voted_size = 0;
340 int num_members = 0;
341
342 if(e == NULL) {
343 crm_trace("Election check requested, but no election available");
344 return FALSE;
345 }
346 if (e->voted == NULL) {
347 crm_trace("%s check requested, but no votes received yet", e->name);
348 return FALSE;
349 }
350
351 voted_size = g_hash_table_size(e->voted);
352 num_members = crm_active_peers();
353
354 /* in the case of #voted > #members, it is better to
355 * wait for the timeout and give the cluster time to
356 * stabilize
357 */
358 if (voted_size >= num_members) {
359 /* we won and everyone has voted */
360 election_timeout_stop(e);
361 if (voted_size > num_members) {
362 GHashTableIter gIter;
363 const crm_node_t *node;
364 char *key = NULL;
365
366 crm_warn("Received too many votes in %s", e->name);
367 g_hash_table_iter_init(&gIter, crm_peer_cache);
368 while (g_hash_table_iter_next(&gIter, NULL, (gpointer *) & node)) {
369 if (crm_is_peer_active(node)) {
370 crm_warn("* expected vote: %s", node->uname);
371 }
372 }
373
374 g_hash_table_iter_init(&gIter, e->voted);
375 while (g_hash_table_iter_next(&gIter, (gpointer *) & key, NULL)) {
376 crm_warn("* actual vote: %s", key);
377 }
378
379 }
380
381 crm_info("%s won by local node", e->name);
382 election_complete(e);
383 return TRUE;
384
385 } else {
386 crm_debug("%s still waiting on %d of %d votes",
387 e->name, num_members - voted_size, num_members);
388 }
389
390 return FALSE;
391 }
392
393 #define loss_dampen 2 /* in seconds */
394
395 struct vote {
396 const char *op;
397 const char *from;
398 const char *version;
399 const char *election_owner;
400 int election_id;
401 struct timeval age;
402 };
403
404 /*!
405 * \brief Unpack an election message
406 *
407 * \param[in] e Election object
408 * \param[in] message Election message XML
409 * \param[out] vote Parsed fields from message
410 *
411 * \return TRUE if election message and election are valid, FALSE otherwise
412 * \note The parsed struct's pointer members are valid only for the lifetime of
413 * the message argument.
414 */
415 static bool
parse_election_message(election_t * e,xmlNode * message,struct vote * vote)416 parse_election_message(election_t *e, xmlNode *message, struct vote *vote)
417 {
418 CRM_CHECK(message && vote, return FALSE);
419
420 vote->election_id = -1;
421 vote->age.tv_sec = -1;
422 vote->age.tv_usec = -1;
423
424 vote->op = crm_element_value(message, F_CRM_TASK);
425 vote->from = crm_element_value(message, F_CRM_HOST_FROM);
426 vote->version = crm_element_value(message, F_CRM_VERSION);
427 vote->election_owner = crm_element_value(message, F_CRM_ELECTION_OWNER);
428
429 crm_element_value_int(message, F_CRM_ELECTION_ID, &(vote->election_id));
430
431 if ((vote->op == NULL) || (vote->from == NULL) || (vote->version == NULL)
432 || (vote->election_owner == NULL) || (vote->election_id < 0)) {
433
434 crm_warn("Invalid %s message from %s in %s ",
435 (vote->op? vote->op : "election"),
436 (vote->from? vote->from : "unspecified node"),
437 (e? e->name : "election"));
438 return FALSE;
439 }
440
441 // Op-specific validation
442
443 if (crm_str_eq(vote->op, CRM_OP_VOTE, TRUE)) {
444 // Only vote ops have uptime
445 crm_element_value_timeval(message, F_CRM_ELECTION_AGE_S,
446 F_CRM_ELECTION_AGE_US, &(vote->age));
447 if ((vote->age.tv_sec < 0) || (vote->age.tv_usec < 0)) {
448 crm_warn("Cannot count %s %s from %s because it is missing uptime",
449 (e? e->name : "election"), vote->op, vote->from);
450 return FALSE;
451 }
452
453 } else if (!crm_str_eq(vote->op, CRM_OP_NOVOTE, TRUE)) {
454 crm_info("Cannot process %s message from %s because %s is not a known election op",
455 (e? e->name : "election"), vote->from, vote->op);
456 return FALSE;
457 }
458
459 // Election validation
460
461 if (e == NULL) {
462 crm_info("Cannot count %s from %s because no election available",
463 vote->op, vote->from);
464 return FALSE;
465 }
466
467 /* If the membership cache is NULL, we REALLY shouldn't be voting --
468 * the question is how we managed to get here.
469 */
470 if (crm_peer_cache == NULL) {
471 crm_info("Cannot count %s %s from %s because no peer information available",
472 e->name, vote->op, vote->from);
473 return FALSE;
474 }
475 return TRUE;
476 }
477
478 static void
record_vote(election_t * e,struct vote * vote)479 record_vote(election_t *e, struct vote *vote)
480 {
481 char *voter_copy = NULL;
482 char *vote_copy = NULL;
483
484 CRM_ASSERT(e && vote && vote->from && vote->op);
485 if (e->voted == NULL) {
486 e->voted = crm_str_table_new();
487 }
488
489 voter_copy = strdup(vote->from);
490 vote_copy = strdup(vote->op);
491 CRM_ASSERT(voter_copy && vote_copy);
492
493 g_hash_table_replace(e->voted, voter_copy, vote_copy);
494 }
495
496 static void
send_no_vote(crm_node_t * peer,struct vote * vote)497 send_no_vote(crm_node_t *peer, struct vote *vote)
498 {
499 // @TODO probably shouldn't hardcode CRM_SYSTEM_CRMD and crm_msg_crmd
500
501 xmlNode *novote = create_request(CRM_OP_NOVOTE, NULL, vote->from,
502 CRM_SYSTEM_CRMD, CRM_SYSTEM_CRMD, NULL);
503
504 crm_xml_add(novote, F_CRM_ELECTION_OWNER, vote->election_owner);
505 crm_xml_add_int(novote, F_CRM_ELECTION_ID, vote->election_id);
506
507 send_cluster_message(peer, crm_msg_crmd, novote, TRUE);
508 free_xml(novote);
509 }
510
511 /*!
512 * \brief Process an election message (vote or no-vote) from a peer
513 *
514 * \param[in] e Election object
515 * \param[in] vote Election message XML from peer
516 * \param[in] can_win Whether to consider the local node eligible for winning
517 *
518 * \return Election state after new vote is considered
519 * \note If the peer message is a vote, and we prefer the peer to win, this will
520 * send a no-vote reply to the peer.
521 * \note The situations "we lost to this vote" from "this is a late no-vote
522 * after we've already lost" both return election_lost. If a caller needs
523 * to distinguish them, it should save the current state before calling
524 * this function, and then compare the result.
525 */
526 enum election_result
election_count_vote(election_t * e,xmlNode * message,bool can_win)527 election_count_vote(election_t *e, xmlNode *message, bool can_win)
528 {
529 int log_level = LOG_INFO;
530 gboolean use_born_on = FALSE;
531 gboolean done = FALSE;
532 gboolean we_lose = FALSE;
533 const char *reason = "unknown";
534 bool we_are_owner = FALSE;
535 crm_node_t *our_node = NULL, *your_node = NULL;
536 time_t tm_now = time(NULL);
537 struct vote vote;
538
539 CRM_CHECK(message != NULL, return election_error);
540 if (parse_election_message(e, message, &vote) == FALSE) {
541 return election_error;
542 }
543
544 your_node = crm_get_peer(0, vote.from);
545 our_node = crm_get_peer(0, e->uname);
546 we_are_owner = (our_node != NULL)
547 && crm_str_eq(our_node->uuid, vote.election_owner, TRUE);
548
549 if (is_heartbeat_cluster()) {
550 use_born_on = TRUE;
551 } else if (is_classic_ais_cluster()) {
552 use_born_on = TRUE;
553 }
554
555 if(can_win == FALSE) {
556 reason = "Not eligible";
557 we_lose = TRUE;
558
559 } else if (our_node == NULL || crm_is_peer_active(our_node) == FALSE) {
560 reason = "We are not part of the cluster";
561 log_level = LOG_ERR;
562 we_lose = TRUE;
563
564 } else if (we_are_owner && (vote.election_id != e->count)) {
565 log_level = LOG_TRACE;
566 reason = "Superseded";
567 done = TRUE;
568
569 } else if (your_node == NULL || crm_is_peer_active(your_node) == FALSE) {
570 /* Possibly we cached the message in the FSA queue at a point that it wasn't */
571 reason = "Peer is not part of our cluster";
572 log_level = LOG_WARNING;
573 done = TRUE;
574
575 } else if (crm_str_eq(vote.op, CRM_OP_NOVOTE, TRUE)
576 || crm_str_eq(vote.from, e->uname, TRUE)) {
577 /* Receiving our own broadcast vote, or a no-vote from peer, is a vote
578 * for us to win
579 */
580 if (!we_are_owner) {
581 crm_warn("Cannot count %s round %d %s from %s because we are not election owner (%s)",
582 e->name, vote.election_id, vote.op, vote.from,
583 vote.election_owner);
584 return election_error;
585 }
586 if (e->state != election_in_progress) {
587 // Should only happen if we already lost
588 crm_debug("Not counting %s round %d %s from %s because no election in progress",
589 e->name, vote.election_id, vote.op, vote.from);
590 return e->state;
591 }
592 record_vote(e, &vote);
593 reason = "Recorded";
594 done = TRUE;
595
596 } else {
597 // A peer vote requires a comparison to determine which node is better
598 int age_result = crm_compare_age(vote.age);
599 int version_result = compare_version(vote.version, CRM_FEATURE_SET);
600
601 if (version_result < 0) {
602 reason = "Version";
603 we_lose = TRUE;
604
605 } else if (version_result > 0) {
606 reason = "Version";
607
608 } else if (age_result < 0) {
609 reason = "Uptime";
610 we_lose = TRUE;
611
612 } else if (age_result > 0) {
613 reason = "Uptime";
614
615 /* TODO: Check for y(our) born < 0 */
616 } else if (use_born_on && your_node->born < our_node->born) {
617 reason = "Born";
618 we_lose = TRUE;
619
620 } else if (use_born_on && your_node->born > our_node->born) {
621 reason = "Born";
622
623 } else if (strcasecmp(e->uname, vote.from) > 0) {
624 reason = "Host name";
625 we_lose = TRUE;
626
627 } else {
628 reason = "Host name";
629 }
630 }
631
632 if (e->expires < tm_now) {
633 e->election_wins = 0;
634 e->expires = tm_now + STORM_INTERVAL;
635
636 } else if (done == FALSE && we_lose == FALSE) {
637 int peers = 1 + g_hash_table_size(crm_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 e->election_wins++;
643 if (e->election_wins > (peers * peers)) {
644 crm_warn("%s election storm detected: %d wins in %d seconds",
645 e->name, e->election_wins, STORM_INTERVAL);
646 e->election_wins = 0;
647 e->expires = tm_now + STORM_INTERVAL;
648 if (e->wrote_blackbox == FALSE) {
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 e->wrote_blackbox = TRUE;
661 }
662 }
663 }
664
665 if (done) {
666 do_crm_log(log_level + 1,
667 "Processed %s round %d %s (current round %d) from %s (%s)",
668 e->name, vote.election_id, vote.op, e->count, vote.from,
669 reason);
670 return e->state;
671
672 } else if (we_lose == FALSE) {
673 /* We track the time of the last election loss to implement an election
674 * dampening period, reducing the likelihood of an election storm. If
675 * this node has lost within the dampening period, don't start a new
676 * election, even if we win against a peer's vote -- the peer we lost to
677 * should win again.
678 *
679 * @TODO This has a problem case: if an election winner immediately
680 * leaves the cluster, and a new election is immediately called, all
681 * nodes could lose, with no new winner elected. The ideal solution
682 * would be to tie the election structure with the peer caches, which
683 * would allow us to clear the dampening when the previous winner
684 * leaves (and would allow other improvements as well).
685 */
686 if ((e->last_election_loss == 0)
687 || ((tm_now - e->last_election_loss) > (time_t) loss_dampen)) {
688
689 do_crm_log(log_level, "%s round %d (owner node ID %s) pass: %s from %s (%s)",
690 e->name, vote.election_id, vote.election_owner, vote.op,
691 vote.from, reason);
692
693 e->last_election_loss = 0;
694 election_timeout_stop(e);
695
696 /* Start a new election by voting down this, and other, peers */
697 e->state = election_start;
698 return e->state;
699 } else {
700 char *loss_time = ctime(&e->last_election_loss);
701
702 if (loss_time) {
703 // Show only HH:MM:SS
704 loss_time += 11;
705 loss_time[8] = '\0';
706 }
707 crm_info("Ignoring %s round %d (owner node ID %s) pass vs %s because we lost less than %ds ago at %s",
708 e->name, vote.election_id, vote.election_owner, vote.from,
709 loss_dampen, (loss_time? loss_time : "unknown"));
710 }
711 }
712
713 e->last_election_loss = tm_now;
714
715 do_crm_log(log_level, "%s round %d (owner node ID %s) lost: %s from %s (%s)",
716 e->name, vote.election_id, vote.election_owner, vote.op,
717 vote.from, reason);
718
719 election_reset(e);
720 send_no_vote(your_node, &vote);
721 e->state = election_lost;
722 return e->state;
723 }
724
725 /*!
726 * \brief Reset any election dampening currently in effect
727 *
728 * \param[in] e Election object to clear
729 */
730 void
election_clear_dampening(election_t * e)731 election_clear_dampening(election_t *e)
732 {
733 e->last_election_loss = 0;
734 }
735