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