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