summaryrefslogtreecommitdiffstats
path: root/daemons/attrd/attrd_elections.c
blob: 82fbe8affcf90fdb86bff078fbdcead2bea9c48b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
/*
 * Copyright 2013-2023 the Pacemaker project contributors
 *
 * The version control history for this file may have further details.
 *
 * This source code is licensed under the GNU General Public License version 2
 * or later (GPLv2+) WITHOUT ANY WARRANTY.
 */

#include <crm_internal.h>
#include <crm/msg_xml.h>
#include <crm/cluster.h>
#include <crm/cluster/election_internal.h>

#include "pacemaker-attrd.h"

static char *peer_writer = NULL;
static election_t *writer = NULL;

static gboolean
attrd_election_cb(gpointer user_data)
{
    attrd_declare_winner();

    /* Update the peers after an election */
    attrd_peer_sync(NULL, NULL);

    /* After winning an election, update the CIB with the values of all
     * attributes as the winner knows them.
     */
    attrd_write_attributes(attrd_write_all);
    return G_SOURCE_REMOVE;
}

void
attrd_election_init(void)
{
    writer = election_init(T_ATTRD, attrd_cluster->uname, 120000,
                           attrd_election_cb);
}

void
attrd_election_fini(void)
{
    election_fini(writer);
}

void
attrd_start_election_if_needed(void)
{
    if ((peer_writer == NULL)
        && (election_state(writer) != election_in_progress)
        && !attrd_shutting_down(false)) {

        crm_info("Starting an election to determine the writer");
        election_vote(writer);
    }
}

bool
attrd_election_won(void)
{
    return (election_state(writer) == election_won);
}

void
attrd_handle_election_op(const crm_node_t *peer, xmlNode *xml)
{
    enum election_result rc = 0;
    enum election_result previous = election_state(writer);

    crm_xml_add(xml, F_CRM_HOST_FROM, peer->uname);

    // Don't become writer if we're shutting down
    rc = election_count_vote(writer, xml, !attrd_shutting_down(false));

    switch(rc) {
        case election_start:
            crm_debug("Unsetting writer (was %s) and starting new election",
                      peer_writer? peer_writer : "unset");
            free(peer_writer);
            peer_writer = NULL;
            election_vote(writer);
            break;

        case election_lost:
            /* The election API should really distinguish between "we just lost
             * to this peer" and "we already lost previously, and we are
             * discarding this vote for some reason", but it doesn't.
             *
             * In the first case, we want to tentatively set the peer writer to
             * this peer, even though another peer may eventually win (which we
             * will learn via attrd_check_for_new_writer()), so
             * attrd_start_election_if_needed() doesn't start a new election.
             *
             * Approximate a test for that case as best as possible.
             */
            if ((peer_writer == NULL) || (previous != election_lost)) {
                pcmk__str_update(&peer_writer, peer->uname);
                crm_debug("Election lost, presuming %s is writer for now",
                          peer_writer);
            }
            break;

        case election_in_progress:
            election_check(writer);
            break;

        default:
            crm_info("Ignoring election op from %s due to error", peer->uname);
            break;
    }
}

bool
attrd_check_for_new_writer(const crm_node_t *peer, const xmlNode *xml)
{
    int peer_state = 0;

    crm_element_value_int(xml, PCMK__XA_ATTR_WRITER, &peer_state);
    if (peer_state == election_won) {
        if ((election_state(writer) == election_won)
           && !pcmk__str_eq(peer->uname, attrd_cluster->uname, pcmk__str_casei)) {
            crm_notice("Detected another attribute writer (%s), starting new election",
                       peer->uname);
            election_vote(writer);

        } else if (!pcmk__str_eq(peer->uname, peer_writer, pcmk__str_casei)) {
            crm_notice("Recorded new attribute writer: %s (was %s)",
                       peer->uname, (peer_writer? peer_writer : "unset"));
            pcmk__str_update(&peer_writer, peer->uname);
        }
    }
    return (peer_state == election_won);
}

void
attrd_declare_winner(void)
{
    crm_notice("Recorded local node as attribute writer (was %s)",
               (peer_writer? peer_writer : "unset"));
    pcmk__str_update(&peer_writer, attrd_cluster->uname);
}

void
attrd_remove_voter(const crm_node_t *peer)
{
    election_remove(writer, peer->uname);
    if (peer_writer && pcmk__str_eq(peer->uname, peer_writer, pcmk__str_casei)) {
        free(peer_writer);
        peer_writer = NULL;
        crm_notice("Lost attribute writer %s", peer->uname);

        /* Clear any election dampening in effect. Otherwise, if the lost writer
         * had just won, the election could fizzle out with no new writer.
         */
        election_clear_dampening(writer);

        /* If the writer received attribute updates during its shutdown, it will
         * not have written them to the CIB. Ensure we get a new writer so they
         * are written out. This means that every node that sees the writer
         * leave will start a new election, but that's better than losing
         * attributes.
         */
        attrd_start_election_if_needed();

    /* If an election is in progress, we need to call election_check(), in case
     * this lost peer is the only one that hasn't voted, otherwise the election
     * would be pending until it's timed out.
     */
    } else if (election_state(writer) == election_in_progress) {
       crm_debug("Checking election status upon loss of voter %s", peer->uname);
       election_check(writer);
    }
}

void
attrd_xml_add_writer(xmlNode *xml)
{
    crm_xml_add_int(xml, PCMK__XA_ATTR_WRITER, election_state(writer));
}