Page MenuHomeClusterLabs Projects

No OneTemporary

diff --git a/src/config.c b/src/config.c
index 577644b..e8b47f4 100644
--- a/src/config.c
+++ b/src/config.c
@@ -1,1042 +1,1064 @@
/*
* Copyright (C) 2011 Jiaju Zhang <jjzhang@suse.de>
* Copyright (C) 2013-2014 Philipp Marek <philipp.marek@linbit.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <assert.h>
#include <zlib.h>
#include <sys/types.h>
#include <pwd.h>
#include <grp.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include "b_config.h"
#include "booth.h"
#include "config.h"
#include "log.h"
#include "raft.h"
#include "ticket.h"
#include "utils.h"
static int ticket_size = 0;
static const struct booth_site _no_leader = {
.addr_string = "none",
.site_id = NO_ONE,
.index = -1,
};
struct booth_site *const no_leader = (struct booth_site*) &_no_leader;
static int ticket_realloc(struct booth_config *conf_ptr)
{
const int added = 5;
int had, want;
void *p;
assert(conf_ptr != NULL);
had = conf_ptr->ticket_allocated;
want = had + added;
p = realloc(conf_ptr->ticket, sizeof(struct ticket_config) * want);
if (!p) {
log_error("can't alloc more tickets");
return -ENOMEM;
}
conf_ptr->ticket = p;
memset(conf_ptr->ticket + had, 0,
sizeof(struct ticket_config) * added);
conf_ptr->ticket_allocated = want;
return 0;
}
static void hostname_to_ip(char * hostname)
{
struct hostent *he;
struct in_addr **addr_list;
if ((he = gethostbyname(hostname)) == NULL) {
log_error("can't find IP for the host \"%s\"", hostname);
return;
}
addr_list = (struct in_addr **) he->h_addr_list;
/* Return the first found address */
if (addr_list[0] != NULL) {
strncpy(hostname, inet_ntoa(*addr_list[0]), BOOTH_NAME_LEN - 1);
/* buffer overflow will not happen (IPv6 notation < 63 chars),
but suppress the warnings */
hostname[BOOTH_NAME_LEN - 1] = '\0';
}
else {
log_error("no IP addresses found for the host \"%s\"", hostname);
}
}
static int add_site(struct booth_config *conf_ptr, char *addr_string, int type)
{
int rv;
struct booth_site *site;
uLong nid;
uint32_t mask;
int i;
assert(conf_ptr != NULL);
rv = 1;
if (conf_ptr->site_count == MAX_NODES) {
log_error("too many nodes");
goto out;
}
if (strnlen(addr_string, sizeof(conf_ptr->site[0].addr_string))
>= sizeof(conf_ptr->site[0].addr_string)) {
log_error("site address \"%s\" too long", addr_string);
goto out;
}
site = conf_ptr->site + conf_ptr->site_count;
site->family = AF_INET;
site->type = type;
/* buffer overflow will not hapen (we've already checked that
addr_string will fit incl. terminating '\0' above), but
suppress the warnings with copying everything but the boundary
byte, which is valid as-is, since this last byte will be safely
pre-zeroed from the struct booth_config initialization */
strncpy(site->addr_string, addr_string, sizeof(site->addr_string) - 1);
if (!(inet_pton(AF_INET, site->addr_string, &site->sa4.sin_addr) > 0) &&
!(inet_pton(AF_INET6, site->addr_string, &site->sa6.sin6_addr) > 0)) {
/* Not a valid address, so let us try to convert it into an IP address */
hostname_to_ip(site->addr_string);
}
site->index = conf_ptr->site_count;
site->bitmask = 1 << conf_ptr->site_count;
/* Catch site overflow */
assert(site->bitmask);
conf_ptr->all_bits |= site->bitmask;
if (type == SITE)
conf_ptr->sites_bits |= site->bitmask;
site->tcp_fd = -1;
conf_ptr->site_count++;
rv = 0;
memset(&site->sa6, 0, sizeof(site->sa6));
nid = crc32(0L, NULL, 0);
/* Using the ASCII representation in site->addr_string (both sizeof()
* and strlen()) gives quite a lot of collisions; a brute-force run
* from 0.0.0.0 to 24.0.0.0 gives ~4% collisions, and this tends to
* increase even more.
* Whether there'll be a collision in real-life, with 3 or 5 nodes, is
* another question ... but for now get the ID from the binary
* representation - that had *no* collisions up to 32.0.0.0.
* Note that POSIX mandates inet_pton to arange the address pointed
* to by "dst" in network byte order, assuring little/big-endianess
* mutual compatibility. */
if (inet_pton(AF_INET,
site->addr_string,
&site->sa4.sin_addr) > 0) {
site->family = AF_INET;
site->sa4.sin_family = site->family;
site->sa4.sin_port = htons(conf_ptr->port);
site->saddrlen = sizeof(site->sa4);
site->addrlen = sizeof(site->sa4.sin_addr);
site->site_id = crc32(nid, (void*)&site->sa4.sin_addr, site->addrlen);
} else if (inet_pton(AF_INET6,
site->addr_string,
&site->sa6.sin6_addr) > 0) {
site->family = AF_INET6;
site->sa6.sin6_family = site->family;
site->sa6.sin6_flowinfo = 0;
site->sa6.sin6_port = htons(conf_ptr->port);
site->saddrlen = sizeof(site->sa6);
site->addrlen = sizeof(site->sa6.sin6_addr);
site->site_id = crc32(nid, (void*)&site->sa6.sin6_addr, site->addrlen);
} else {
log_error("Address string \"%s\" is bad", site->addr_string);
rv = EINVAL;
}
/* Make sure we will never collide with NO_ONE,
* or be negative (to get "get_local_id() < 0" working). */
mask = 1 << (sizeof(site->site_id)*8 -1);
assert(NO_ONE & mask);
site->site_id &= ~mask;
/* Test for collisions with other sites */
for(i=0; i<site->index; i++)
if (conf_ptr->site[i].site_id == site->site_id) {
log_error("Got a site-ID collision. Please file a bug on https://github.com/ClusterLabs/booth/issues/new, attaching the configuration file.");
exit(1);
}
out:
return rv;
}
inline static char *skip_while_in(const char *cp, int (*fn)(int), const char *allowed)
{
/* strchr() returns a pointer to the terminator if *cp == 0. */
while (*cp &&
(fn(*cp) ||
strchr(allowed, *cp)))
cp++;
/* discard "const" qualifier */
return (char*)cp;
}
inline static char *skip_while(char *cp, int (*fn)(int))
{
while (fn(*cp))
cp++;
return cp;
}
inline static char *skip_until(char *cp, char expected)
{
while (*cp && *cp != expected)
cp++;
return cp;
}
static inline int is_end_of_line(char *cp)
{
char c = *cp;
return c == '\n' || c == 0 || c == '#';
}
static int add_ticket(struct booth_config *conf_ptr, const char *name,
struct ticket_config **tkp, const struct ticket_config *def)
{
int rv;
struct ticket_config *tk;
assert(conf_ptr != NULL);
if (conf_ptr->ticket_count == conf_ptr->ticket_allocated) {
rv = ticket_realloc(conf_ptr);
if (rv < 0)
return rv;
}
tk = conf_ptr->ticket + conf_ptr->ticket_count;
conf_ptr->ticket_count++;
if (!check_max_len_valid(name, sizeof(tk->name))) {
log_error("ticket name \"%s\" too long.", name);
return -EINVAL;
}
if (find_ticket_by_name(conf_ptr, name, NULL)) {
log_error("ticket name \"%s\" used again.", name);
return -EINVAL;
}
if (* skip_while_in(name, isalnum, "-/")) {
log_error("ticket name \"%s\" invalid; only alphanumeric names.", name);
return -EINVAL;
}
strcpy(tk->name, name);
tk->timeout = def->timeout;
tk->term_duration = def->term_duration;
tk->retries = def->retries;
memcpy(tk->weight, def->weight, sizeof(tk->weight));
tk->mode = def->mode;
if (tkp)
*tkp = tk;
return 0;
}
static int postproc_ticket(struct ticket_config *tk)
{
if (!tk)
return 1;
if (!tk->renewal_freq) {
tk->renewal_freq = tk->term_duration/2;
}
if (tk->timeout*(tk->retries+1) >= tk->renewal_freq) {
log_error("%s: total amount of time to "
"retry sending packets cannot exceed "
"renewal frequency "
"(%d*(%d+1) >= %d)",
tk->name, tk->timeout, tk->retries, tk->renewal_freq);
return 0;
}
return 1;
}
/* returns number of weights, or -1 on bad input. */
static int parse_weights(const char *input, int weights[MAX_NODES])
{
int i, v;
char *cp;
for(i=0; i<MAX_NODES; i++) {
/* End of input? */
if (*input == 0)
break;
v = strtol(input, &cp, 0);
if (input == cp) {
log_error("No integer weight value at \"%s\"", input);
return -1;
}
weights[i] = v;
while (*cp) {
/* Separator characters */
if (isspace(*cp) ||
strchr(",;:-+", *cp))
cp++;
/* Next weight */
else if (isdigit(*cp))
break;
/* Rest */
else {
log_error("Invalid character at \"%s\"", cp);
return -1;
}
}
input = cp;
}
/* Fill rest of vector. */
for(v=i; v<MAX_NODES; v++) {
weights[v] = 0;
}
return i;
}
/* returns TICKET_MODE_AUTO if failed to parse the ticket mode. */
static ticket_mode_e retrieve_ticket_mode(const char *input)
{
if (strcasecmp(input, "manual") == 0) {
return TICKET_MODE_MANUAL;
}
return TICKET_MODE_AUTO;
}
/* scan val for time; time is [0-9]+(ms)?, i.e. either in seconds
* or milliseconds
* returns -1 on failure, otherwise time in ms
*/
static long read_time(char *val)
{
long t;
char *ep;
t = strtol(val, &ep, 10);
if (ep == val) { /* matched none */
t = -1L;
} else if (*ep == '\0') { /* matched all */
t = t*1000L; /* in seconds, convert to ms */
} else if (strcmp(ep, "ms")) { /* ms not exactly matched */
t = -1L;
} /* otherwise, time in ms */
/* if second fractions configured, send finer resolution
* times (i.e. term_valid_for) */
if (t % 1000L) {
TIME_MULT = 1000;
}
return t;
}
/* make arguments for execv(2)
* tk_test.path points to the path
* tk_test.argv is argument vector (starts with the prog)
* (strtok pokes holes in the configuration parameter value, i.e.
* we don't need to allocate memory for arguments)
*/
static int parse_extprog(char *val, struct ticket_config *tk)
{
char *p;
int i = 0;
if (tk_test.path) {
free(tk_test.path);
}
if (!(tk_test.path = strdup(val))) {
log_error("out of memory");
return -1;
}
p = strtok(tk_test.path, " \t");
tk_test.argv[i++] = p;
do {
p = strtok(NULL, " \t");
if (i >= MAX_ARGS) {
log_error("too many arguments for the acquire-handler");
free(tk_test.path);
return -1;
}
tk_test.argv[i++] = p;
} while (p);
return 0;
}
struct toktab grant_type[] = {
{ "auto", GRANT_AUTO},
{ "manual", GRANT_MANUAL},
{ NULL, 0},
};
struct toktab attr_op[] = {
{"eq", ATTR_OP_EQ},
{"ne", ATTR_OP_NE},
{NULL, 0},
};
static int lookup_tokval(char *key, struct toktab *tab)
{
struct toktab *tp;
for (tp = tab; tp->str; tp++) {
if (!strcmp(tp->str, key))
return tp->val;
}
return 0;
}
/* attribute prerequisite
*/
static int parse_attr_prereq(char *val, struct ticket_config *tk)
{
struct attr_prereq *ap = NULL;
char *p;
ap = (struct attr_prereq *)calloc(1, sizeof(struct attr_prereq));
if (!ap) {
log_error("out of memory");
return -1;
}
p = strtok(val, " \t");
if (!p) {
log_error("not enough arguments to attr-prereq");
goto err_out;
}
ap->grant_type = lookup_tokval(p, grant_type);
if (!ap->grant_type) {
log_error("%s is not a grant type", p);
goto err_out;
}
p = strtok(NULL, " \t");
if (!p) {
log_error("not enough arguments to attr-prereq");
goto err_out;
}
if (!(ap->attr_name = strdup(p))) {
log_error("out of memory");
goto err_out;
}
p = strtok(NULL, " \t");
if (!p) {
log_error("not enough arguments to attr-prereq");
goto err_out;
}
ap->op = lookup_tokval(p, attr_op);
if (!ap->op) {
log_error("%s is not an attribute operation", p);
goto err_out;
}
p = strtok(NULL, " \t");
if (!p) {
log_error("not enough arguments to attr-prereq");
goto err_out;
}
if (!(ap->attr_val = strdup(p))) {
log_error("out of memory");
goto err_out;
}
tk->attr_prereqs = g_list_append(tk->attr_prereqs, ap);
if (!tk->attr_prereqs) {
log_error("out of memory");
goto err_out;
}
return 0;
err_out:
if (ap) {
if (ap->attr_val)
free(ap->attr_val);
if (ap->attr_name)
free(ap->attr_name);
free(ap);
}
return -1;
}
int read_config(struct booth_config **conf_pptr,
const booth_transport_table_t *transport,
const struct ticket_handler *ticket_handler,
const char *path, int type)
{
char line[1024];
FILE *fp;
char *s, *key, *val, *end_of_key;
const char *error;
char *cp, *cp2;
int i;
int lineno = 0;
int got_transport = 0;
int min_timeout = 0;
struct ticket_config defaults = { { 0 } };
struct ticket_config *current_tk = NULL;
assert(conf_pptr != NULL);
free(*conf_pptr);
fp = fopen(path, "r");
if (!fp) {
log_error("failed to open %s: %s", path, strerror(errno));
*conf_pptr = NULL;
return -1;
}
*conf_pptr = malloc(sizeof(struct booth_config)
+ TICKET_ALLOC * sizeof(struct ticket_config));
if (*conf_pptr == NULL) {
fclose(fp);
log_error("failed to alloc memory for booth config");
return -ENOMEM;
}
memset(*conf_pptr, 0, sizeof(struct booth_config)
+ TICKET_ALLOC * sizeof(struct ticket_config));
ticket_size = TICKET_ALLOC;
(*conf_pptr)->transport = transport;
(*conf_pptr)->ticket_handler = ticket_handler;
(*conf_pptr)->proto = UDP;
(*conf_pptr)->port = BOOTH_DEFAULT_PORT;
(*conf_pptr)->maxtimeskew = BOOTH_DEFAULT_MAX_TIME_SKEW;
(*conf_pptr)->authkey[0] = '\0';
/* Provide safe defaults. -1 is reserved, though. */
(*conf_pptr)->uid = -2;
(*conf_pptr)->gid = -2;
strcpy((*conf_pptr)->site_user, "hacluster");
strcpy((*conf_pptr)->site_group, "haclient");
strcpy((*conf_pptr)->arb_user, "nobody");
strcpy((*conf_pptr)->arb_group, "nobody");
parse_weights("", defaults.weight);
defaults.clu_test.path = NULL;
defaults.clu_test.pid = 0;
defaults.clu_test.status = 0;
defaults.clu_test.progstate = EXTPROG_IDLE;
defaults.term_duration = DEFAULT_TICKET_EXPIRY;
defaults.timeout = DEFAULT_TICKET_TIMEOUT;
defaults.retries = DEFAULT_RETRIES;
defaults.acquire_after = 0;
defaults.mode = TICKET_MODE_AUTO;
error = "";
log_debug("reading config file %s", path);
while (fgets(line, sizeof(line), fp)) {
lineno++;
s = skip_while(line, isspace);
if (is_end_of_line(s) || *s == '#')
continue;
key = s;
/* Key */
end_of_key = skip_while_in(key, isalnum, "-_");
if (end_of_key == key) {
error = "No key";
goto err;
}
if (!*end_of_key)
goto exp_equal;
/* whitespace, and something else but nothing more? */
s = skip_while(end_of_key, isspace);
if (*s != '=') {
exp_equal:
error = "Expected '=' after key";
goto err;
}
s++;
/* It's my buffer, and I terminate if I want to. */
/* But not earlier than that, because we had to check for = */
*end_of_key = 0;
/* Value tokenizing */
s = skip_while(s, isspace);
switch (*s) {
case '"':
case '\'':
val = s+1;
s = skip_until(val, *s);
/* Terminate value */
if (!*s) {
error = "Unterminated quoted string";
goto err;
}
/* Remove and skip quote */
*s = 0;
s++;
if (*(s = skip_while(s, isspace)) && *s != '#') {
error = "Surplus data after value";
goto err;
}
*s = 0;
break;
case 0:
no_value:
error = "No value";
goto err;
break;
default:
val = s;
/* Rest of line. */
i = strlen(s);
/* i > 0 because of "case 0" above. */
while (i > 0 && isspace(s[i-1]))
i--;
s += i;
*s = 0;
}
if (val == s)
goto no_value;
if (strlen(key) > BOOTH_NAME_LEN
|| strlen(val) > BOOTH_NAME_LEN) {
error = "key/value too long";
goto err;
}
if (strcmp(key, "transport") == 0) {
if (got_transport) {
error = "config file has multiple transport lines";
goto err;
}
if (strcasecmp(val, "UDP") == 0)
(*conf_pptr)->proto = UDP;
else if (strcasecmp(val, "SCTP") == 0)
(*conf_pptr)->proto = SCTP;
else {
error = "invalid transport protocol";
goto err;
}
got_transport = 1;
continue;
}
if (strcmp(key, "port") == 0) {
(*conf_pptr)->port = atoi(val);
continue;
}
if (strcmp(key, "name") == 0) {
safe_copy((*conf_pptr)->name,
val, BOOTH_NAME_LEN,
"name");
continue;
}
#if HAVE_LIBGCRYPT || HAVE_LIBMHASH
if (strcmp(key, "authfile") == 0) {
safe_copy((*conf_pptr)->authfile,
val, BOOTH_PATH_LEN,
"authfile");
continue;
}
if (strcmp(key, "maxtimeskew") == 0) {
(*conf_pptr)->maxtimeskew = atoi(val);
continue;
}
#endif
if (strcmp(key, "site") == 0) {
if (add_site(*conf_pptr, val, SITE))
goto err;
continue;
}
if (strcmp(key, "arbitrator") == 0) {
if (add_site(*conf_pptr, val, ARBITRATOR))
goto err;
continue;
}
if (strcmp(key, "site-user") == 0) {
safe_copy((*conf_pptr)->site_user, optarg, BOOTH_NAME_LEN,
"site-user");
continue;
}
if (strcmp(key, "site-group") == 0) {
safe_copy((*conf_pptr)->site_group, optarg, BOOTH_NAME_LEN,
"site-group");
continue;
}
if (strcmp(key, "arbitrator-user") == 0) {
safe_copy((*conf_pptr)->arb_user, optarg, BOOTH_NAME_LEN,
"arbitrator-user");
continue;
}
if (strcmp(key, "arbitrator-group") == 0) {
safe_copy((*conf_pptr)->arb_group, optarg, BOOTH_NAME_LEN,
"arbitrator-group");
continue;
}
if (strcmp(key, "debug") == 0) {
if (type != CLIENT && type != GEOSTORE)
debug_level = max(debug_level, atoi(val));
continue;
}
if (strcmp(key, "ticket") == 0) {
if (current_tk && strcmp(current_tk->name, "__defaults__")) {
if (!postproc_ticket(current_tk)) {
goto err;
}
}
if (!strcmp(val, "__defaults__")) {
current_tk = &defaults;
} else if (add_ticket(*conf_pptr, val, &current_tk,
&defaults)) {
goto err;
}
continue;
}
/* current_tk must be allocated at this point, otherwise
* we don't know to which ticket the key refers
*/
if (!current_tk) {
error = "Unexpected keyword";
goto err;
}
if (strcmp(key, "expire") == 0) {
current_tk->term_duration = read_time(val);
if (current_tk->term_duration <= 0) {
error = "Expected time >0 for expire";
goto err;
}
continue;
}
if (strcmp(key, "timeout") == 0) {
current_tk->timeout = read_time(val);
if (current_tk->timeout <= 0) {
error = "Expected time >0 for timeout";
goto err;
}
if (!min_timeout) {
min_timeout = current_tk->timeout;
} else {
min_timeout = min(min_timeout, current_tk->timeout);
}
continue;
}
if (strcmp(key, "retries") == 0) {
current_tk->retries = strtol(val, &s, 0);
if (*s || s == val ||
current_tk->retries<3 || current_tk->retries > 100) {
error = "Expected plain integer value in the range [3, 100] for retries";
goto err;
}
continue;
}
if (strcmp(key, "renewal-freq") == 0) {
current_tk->renewal_freq = read_time(val);
if (current_tk->renewal_freq <= 0) {
error = "Expected time >0 for renewal-freq";
goto err;
}
continue;
}
if (strcmp(key, "acquire-after") == 0) {
current_tk->acquire_after = read_time(val);
if (current_tk->acquire_after < 0) {
error = "Expected time >=0 for acquire-after";
goto err;
}
continue;
}
if (strcmp(key, "before-acquire-handler") == 0) {
if (parse_extprog(val, current_tk)) {
goto err;
}
continue;
}
if (strcmp(key, "attr-prereq") == 0) {
if (parse_attr_prereq(val, current_tk)) {
goto err;
}
continue;
}
if (strcmp(key, "mode") == 0) {
current_tk->mode = retrieve_ticket_mode(val);
continue;
}
if (strcmp(key, "weights") == 0) {
if (parse_weights(val, current_tk->weight) < 0)
goto err;
continue;
}
error = "Unknown keyword";
goto err;
}
fclose(fp);
if (((*conf_pptr)->site_count % 2) == 0) {
log_warn("Odd number of nodes is strongly recommended!");
}
/* Default: make config name match config filename. */
if (!(*conf_pptr)->name[0]) {
cp = strrchr(path, '/');
cp = cp ? cp+1 : (char *)path;
cp2 = strrchr(cp, '.');
if (!cp2)
cp2 = cp + strlen(cp);
if (cp2-cp >= BOOTH_NAME_LEN) {
log_error("booth config file name too long");
goto out;
}
strncpy((*conf_pptr)->name, cp, cp2-cp);
*((*conf_pptr)->name+(cp2-cp)) = '\0';
}
if (!postproc_ticket(current_tk)) {
goto out;
}
safe_copy((*conf_pptr)->path_to_self, path,
sizeof((*conf_pptr)->path_to_self),
"path to config file itself");
(*conf_pptr)->poll_timeout = min(POLL_TIMEOUT, min_timeout/10);
if ((*conf_pptr)->poll_timeout == 0)
(*conf_pptr)->poll_timeout = POLL_TIMEOUT;
return 0;
err:
fclose(fp);
out:
log_error("%s in config file line %d",
error, lineno);
free(*conf_pptr);
*conf_pptr = NULL;
return -1;
}
int check_config(struct booth_config *conf_ptr, int type)
{
struct passwd *pw;
struct group *gr;
char *cp, *input;
if (conf_ptr == NULL)
return -1;
input = (type == ARBITRATOR)
? conf_ptr->arb_user
: conf_ptr->site_user;
if (!*input)
goto u_inval;
if (isdigit(input[0])) {
conf_ptr->uid = strtol(input, &cp, 0);
if (*cp != 0) {
u_inval:
log_error("User \"%s\" cannot be resolved into a UID.", input);
return ENOENT;
}
} else {
pw = getpwnam(input);
if (!pw)
goto u_inval;
conf_ptr->uid = pw->pw_uid;
}
input = (type == ARBITRATOR)
? conf_ptr->arb_group
: conf_ptr->site_group;
if (!*input)
goto g_inval;
if (isdigit(input[0])) {
conf_ptr->gid = strtol(input, &cp, 0);
if (*cp != 0) {
g_inval:
log_error("Group \"%s\" cannot be resolved into a UID.", input);
return ENOENT;
}
} else {
gr = getgrnam(input);
if (!gr)
goto g_inval;
conf_ptr->gid = gr->gr_gid;
}
return 0;
}
static int get_other_site(struct booth_config *conf_ptr,
struct booth_site **node)
{
struct booth_site *n;
int i;
*node = NULL;
if (conf_ptr == NULL)
return 0;
assert(conf_ptr->local != NULL);
FOREACH_NODE(conf_ptr, i, n) {
if (n != conf_ptr->local && n->type == SITE) {
if (!*node) {
*node = n;
} else {
return 0;
}
}
}
return !*node ? 0 : 1;
}
int find_site_by_name(struct booth_config *conf_ptr, const char *site,
struct booth_site **node, int any_type)
{
struct booth_site *n;
int i;
if (conf_ptr == NULL)
return 0;
if (!strcmp(site, OTHER_SITE))
return get_other_site(conf_ptr, node);
FOREACH_NODE(conf_ptr, i, n) {
if ((n->type == SITE || any_type) &&
strncmp(n->addr_string, site, sizeof(n->addr_string)) == 0) {
*node = n;
return 1;
}
}
return 0;
}
int find_site_by_id(struct booth_config *conf_ptr, uint32_t site_id,
struct booth_site **node)
{
struct booth_site *n;
int i;
if (site_id == NO_ONE) {
*node = no_leader;
return 1;
}
if (conf_ptr == NULL)
return 0;
FOREACH_NODE(conf_ptr, i, n) {
if (n->site_id == site_id) {
*node = n;
return 1;
}
}
return 0;
}
const char *type_to_string(int type)
{
switch (type)
{
case ARBITRATOR: return "arbitrator";
case SITE: return "site";
case CLIENT: return "client";
case GEOSTORE: return "attr";
}
return "??invalid-type??";
}
+
+int find_ticket_by_name(struct booth_config *conf_ptr,
+ const char *ticket, struct ticket_config **found)
+{
+ struct ticket_config *tk;
+ int i;
+
+ assert(conf_ptr != NULL);
+
+ if (found)
+ *found = NULL;
+
+ FOREACH_TICKET(conf_ptr, i, tk) {
+ if (!strncmp(tk->name, ticket, sizeof(tk->name))) {
+ if (found)
+ *found = tk;
+ return 1;
+ }
+ }
+
+ return 0;
+}
diff --git a/src/config.h b/src/config.h
index 19b7dec..5344064 100644
--- a/src/config.h
+++ b/src/config.h
@@ -1,398 +1,411 @@
/*
* Copyright (C) 2011 Jiaju Zhang <jjzhang@suse.de>
* Copyright (C) 2013-2014 Philipp Marek <philipp.marek@linbit.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef _CONFIG_H
#define _CONFIG_H
#include <stdint.h>
#include <sys/stat.h>
struct booth_config;
#include "booth.h"
#include "timer.h"
#include "raft.h"
#include "transport.h"
/** @{ */
/** Definitions for in-RAM data. */
#define MAX_NODES 16
#define MAX_ARGS 16
#define TICKET_ALLOC 16
#define OTHER_SITE "other"
typedef enum {
EXTPROG_IDLE,
EXTPROG_RUNNING,
EXTPROG_EXITED,
EXTPROG_IGNORE,
} extprog_state_e;
#define tk_test tk->clu_test
typedef enum {
ATTR_OP_EQ = 1,
ATTR_OP_NE,
} attr_op_e;
typedef enum {
GRANT_AUTO = 1,
GRANT_MANUAL,
} grant_type_e;
typedef enum {
TICKET_MODE_AUTO = 1,
TICKET_MODE_MANUAL,
} ticket_mode_e;
struct toktab {
const char *str;
int val;
};
struct attr_prereq {
grant_type_e grant_type; /* grant type */
attr_op_e op; /* attribute operation */
char *attr_name;
char *attr_val;
};
struct ticket_config {
/** \name Configuration items.
* @{ */
/** Name of ticket. */
boothc_ticket name;
/** How long a term lasts if not refreshed (in ms) */
int term_duration;
/** Network related timeouts (in ms) */
int timeout;
/** Retries before giving up. */
int retries;
/** If >0, time to wait for a site to get fenced.
* The ticket may be acquired after that timespan by
* another site. */
int acquire_after;
/* How often to renew the ticket (in ms)
*/
int renewal_freq;
/* Program to ask whether it makes sense to
* acquire the ticket */
struct clu_test {
char *path;
int is_dir;
char *argv[MAX_ARGS];
pid_t pid;
int status; /* child exit status */
extprog_state_e progstate; /* program running/idle/waited on */
} clu_test;
/** Node weights. */
int weight[MAX_NODES];
/* Mode operation of the ticket.
* Set to MANUAL to make sure that the ticket will be manipulated
* only by manual commands of the administrator. In such a case
* automatic elections will be disabled.
* Manual tickets do not have to be renewed every some time.
* The leader will continue to send heartbeat messages to other sites.
*/
ticket_mode_e mode;
/** @} */
/** \name Runtime values.
* @{ */
/** Current state. */
server_state_e state;
/** Next state. Used at startup. */
server_state_e next_state;
/** When something has to be done */
timetype next_cron;
/** Current leader. This is effectively the log[] in Raft. */
struct booth_site *leader;
/** Leader that got lost. */
struct booth_site *lost_leader;
/** Is the ticket granted? */
int is_granted;
/** Which site considered itself a leader.
* For manual tickets it is possible, that
* more than one site will act as a leader.
* This array is used for tracking that situation
* and notifying the user about the issue.
*
* Possible values for every site:
* 0: the site does not claim to be the leader
* 1: the site considers itself a leader and
* is sending or used to send heartbeat messages
*
* The site will be marked as '1' until this site
* receives revoke confirmation.
*
* If more than one site has '1', the geo cluster is
* considered to have multiple leadership and proper
* warning are generated.
*/
int sites_where_granted[MAX_NODES];
/** Timestamp of leadership expiration */
timetype term_expires;
/** End of election period */
timetype election_end;
struct booth_site *voted_for;
/** Who the various sites vote for.
* NO_OWNER = no vote yet. */
struct booth_site *votes_for[MAX_NODES];
/* bitmap */
uint64_t votes_received;
/** Last voting round that was seen. */
uint32_t current_term;
/** Do ticket updates whenever we get enough heartbeats.
* But do that only once.
* This is reset to 0 whenever we broadcast heartbeat and set
* to 1 once enough acks are received.
* Increased to 2 when the ticket is commited to the CIB (see
* delay_commit).
*/
uint32_t ticket_updated;
/** Outcome of whatever ticket request was processed.
* Can also be an intermediate stage.
*/
uint32_t outcome;
/** @} */
/** */
uint32_t last_applied;
uint32_t next_index[MAX_NODES];
uint32_t match_index[MAX_NODES];
/* Why did we start the elections?
*/
cmd_reason_t election_reason;
/* if it is potentially dangerous to grant the ticket
* immediately, then this is set to some point in time,
* usually (now + term_duration + acquire_after)
*/
timetype delay_commit;
/* the last request RPC we sent
*/
uint32_t last_request;
/* if we expect some acks, then set this to the id of
* the RPC which others will send us; it is cleared once all
* replies were received
*/
uint32_t acks_expected;
/* bitmask of servers which sent acks
*/
uint64_t acks_received;
/* timestamp of the request */
timetype req_sent_at;
/* we need to wait for MY_INDEX from other servers,
* hold the ticket processing for a while until they reply
*/
int start_postpone;
/** Last renewal time */
timetype last_renewal;
/* Do we need to update the copy in the CIB?
* Normally, the ticket is written only when it changes via
* the UPDATE RPC (for followers) and on expiration update
* (for leaders)
*/
int update_cib;
/* Is this ticket in election?
*/
int in_election;
/* don't log warnings unnecessarily
*/
int expect_more_rejects;
/** \name Needed while proposals are being done.
* @{ */
/* Need to keep the previous valid ticket in case we moved to
* start new elections and another server asks for the ticket
* status. It would be wrong to send our candidate ticket.
*/
struct ticket_config *last_valid_tk;
/** Attributes, user defined
*/
GHashTable *attr;
/** Attribute prerequisites
*/
GList *attr_prereqs;
/** Whom to vote for the next time.
* Needed to push a ticket to someone else. */
#if 0
/** Bitmap of sites that acknowledge that state. */
uint64_t proposal_acknowledges;
/** When an incompletely acknowledged proposal gets done.
* If all peers agree, that happens sooner.
* See switch_state_to(). */
struct timeval proposal_switch;
/** Timestamp of proposal expiration. */
time_t proposal_expires;
#endif
/** Number of send retries left.
* Used on the new owner.
* Starts at 0, counts up. */
int retry_number;
/** @} */
};
struct booth_config {
char name[BOOTH_NAME_LEN];
/** File containing the authentication file. */
char authfile[BOOTH_PATH_LEN];
struct stat authstat;
char authkey[BOOTH_MAX_KEY_LEN];
int authkey_len;
/** Maximum time skew between peers allowed */
int maxtimeskew;
transport_layer_t proto;
uint16_t port;
/** Stores the OR of sites bitmasks. */
uint64_t sites_bits;
/** Stores the OR of all members' bitmasks. */
uint64_t all_bits;
char site_user[BOOTH_NAME_LEN];
char site_group[BOOTH_NAME_LEN];
char arb_user[BOOTH_NAME_LEN];
char arb_group[BOOTH_NAME_LEN];
uid_t uid;
gid_t gid;
int site_count;
struct booth_site site[MAX_NODES];
int ticket_count;
int ticket_allocated;
struct ticket_config *ticket;
int poll_timeout;
char path_to_self[BOOTH_PATH_LEN];
struct booth_site *local;
const booth_transport_table_t *transport;
const struct ticket_handler *ticket_handler;
};
#define is_auth_req(b_) ((b_)->authkey[0] != '\0')
/**
* @internal
* Parse booth configuration file and store as structured data
*
* @param[inout] conf_pptr config object to free-alloc cycle & fill accordingly
* @param[in] transport transport handlers table
* @param[in] path where the configuration file is expected
* @param[in] type role currently being acted as
*
* @return 0 or negative value (-1 or -errno) on error
*/
int read_config(struct booth_config **conf_pptr,
const booth_transport_table_t *transport,
const struct ticket_handler *ticket_handler,
const char *path, int type);
/**
* @internal
* Check booth configuration
*
* Currently it means checking that login user/group indeed exists,
* while converting it to respective numeric values for further use.
*
* @param[inout] conf_ptr config object to check
* @param[in] type role currently being acted as
*
* @return 0 or negative value (-1 or -errno) on error
*/
int check_config(struct booth_config *conf_ptr, int type);
/**
* @internal
* Find site in booth configuration by resolved host name
*
* @param[inout] conf_ptr config object to refer to
* @param[in] site name to match against previously resolved host names
* @param[out] node relevant tracked data when found
* @param[in] any_type whether or not to consider also non-site members
*
* @return 0 if nothing found, or 1 when found (node assigned accordingly)
*/
int find_site_by_name(struct booth_config *conf_ptr, const char *site,
struct booth_site **node, int any_type);
/**
* @internal
* Find site in booth configuration by a hash (id)
*
* @param[inout] conf_ptr config object to refer to
* @param[in] site_id hash (id) to match against previously resolved ones
* @param[out] node relevant tracked data when found
*
* @return 0 if nothing found, or 1 when found (node assigned accordingly)
*/
int find_site_by_id(struct booth_config *conf_ptr, uint32_t site_id,
struct booth_site **node);
const char *type_to_string(int type);
+/**
+ * @internal
+ * Pick a ticket structure based on given name
+ *
+ * @param[inout] conf_ptr config object to refer to
+ * @param[in] ticket name of the ticket to search for
+ * @param[out] found place the reference here when found
+ *
+ * @return see @list_ticket and @send_header_plus
+ */
+int find_ticket_by_name(struct booth_config *conf_ptr,
+ const char *ticket, struct ticket_config **found);
+
#endif /* _CONFIG_H */
diff --git a/src/ticket.c b/src/ticket.c
index a15f8e0..f2b58cb 100644
--- a/src/ticket.c
+++ b/src/ticket.c
@@ -1,1509 +1,1487 @@
/*
* Copyright (C) 2011 Jiaju Zhang <jjzhang@suse.de>
* Copyright (C) 2013-2014 Philipp Marek <philipp.marek@linbit.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <arpa/inet.h>
#include <inttypes.h>
#include <stdio.h>
#include <assert.h>
#include <time.h>
#include "b_config.h"
#ifndef RANGE2RANDOM_GLIB
#include <clplumbing/cl_random.h>
#else
#include "alt/range2random_glib.h"
#endif
#include "booth.h"
#include "config.h"
#include "handler.h"
#include "inline-fn.h"
#include "log.h"
#include "manual.h"
#include "pacemaker.h"
#include "raft.h"
#include "request.h"
#include "ticket.h"
#include "utils.h"
#define TK_LINE 256
extern int TIME_RES;
-int find_ticket_by_name(struct booth_config *conf_ptr,
- const char *ticket, struct ticket_config **found)
-{
- struct ticket_config *tk;
- int i;
-
- assert(conf_ptr != NULL);
-
- if (found)
- *found = NULL;
-
- FOREACH_TICKET(conf_ptr, i, tk) {
- if (!strncmp(tk->name, ticket, sizeof(tk->name))) {
- if (found)
- *found = tk;
- return 1;
- }
- }
-
- return 0;
-}
-
int check_ticket(struct booth_config *conf_ptr, const char *ticket,
struct ticket_config **found)
{
if (found)
*found = NULL;
if (conf_ptr == NULL)
return 0;
if (!check_max_len_valid(ticket, sizeof(conf_ptr->ticket[0].name)))
return 0;
return find_ticket_by_name(conf_ptr, ticket, found);
}
/* XXX UNUSED */
int check_site(struct booth_config *conf_ptr, const char *site,
int *is_local)
{
struct booth_site *node;
if (!check_max_len_valid(site, sizeof(node->addr_string)))
return 0;
if (find_site_by_name(conf_ptr, site, &node, 0)) {
*is_local = node->local;
return 1;
}
return 0;
}
/* is it safe to commit the grant?
* if we didn't hear from all sites on the initial grant, we may
* need to delay the commit
*
* TODO: investigate possibility to devise from history whether a
* missing site could be holding a ticket or not
*/
static int ticket_dangerous(struct booth_config *conf_ptr,
struct ticket_config *tk)
{
int tdiff;
/* we may be invoked often, don't spam the log unnecessarily
*/
static int no_log_delay_msg;
assert(conf_ptr != NULL);
assert(conf_ptr->local != NULL);
if (!is_time_set(&tk->delay_commit))
return 0;
if (is_past(&tk->delay_commit) || all_sites_replied(conf_ptr, tk)) {
if (tk->leader == conf_ptr->local) {
tk_log_info("%s, committing to CIB",
is_past(&tk->delay_commit) ?
"ticket delay expired" : "all sites replied");
}
time_reset(&tk->delay_commit);
no_log_delay_msg = 0;
return 0;
}
tdiff = time_left(&tk->delay_commit);
tk_log_debug("delay ticket commit for another " intfmt(tdiff));
if (!no_log_delay_msg) {
tk_log_info("delaying ticket commit to CIB for " intfmt(tdiff));
tk_log_info("(or all sites are reached)");
no_log_delay_msg = 1;
}
return 1;
}
int ticket_write(struct booth_config *conf_ptr, struct ticket_config *tk)
{
assert(conf_ptr != NULL);
assert(conf_ptr->local != NULL);
if (conf_ptr->local->type != SITE)
return -EINVAL;
if (ticket_dangerous(conf_ptr, tk))
return 1;
if (tk->leader == conf_ptr->local) {
if (tk->state != ST_LEADER) {
tk_log_info("ticket state not yet consistent, "
"delaying ticket grant to CIB");
return 1;
}
conf_ptr->ticket_handler->grant_ticket(tk);
} else {
conf_ptr->ticket_handler->revoke_ticket(tk);
}
tk->update_cib = 0;
return 0;
}
void save_committed_tkt(struct ticket_config *tk)
{
if (!tk->last_valid_tk) {
tk->last_valid_tk = malloc(sizeof(struct ticket_config));
if (!tk->last_valid_tk) {
log_error("out of memory");
return;
}
}
memcpy(tk->last_valid_tk, tk, sizeof(struct ticket_config));
}
static void ext_prog_failed(struct booth_config *conf_ptr,
struct ticket_config *tk, int start_election)
{
assert(conf_ptr != NULL);
assert(conf_ptr->local != NULL);
if (!is_manual(tk)) {
/* Give it to somebody else.
* Just send a VOTE_FOR message, so the
* others can start elections. */
if (leader_and_valid(tk, conf_ptr->local)) {
save_committed_tkt(tk);
reset_ticket(tk);
ticket_write(conf_ptr, tk);
if (start_election) {
ticket_broadcast(conf_ptr, tk, OP_VOTE_FOR,
OP_REQ_VOTE, RLT_SUCCESS,
OR_LOCAL_FAIL);
}
}
} else {
/* There is not much we can do now because
* the manual ticket cannot be relocated.
* Just warn the user. */
if (tk->leader == conf_ptr->local) {
save_committed_tkt(tk);
reset_ticket(tk);
ticket_write(conf_ptr, tk);
log_error("external test failed on the specified machine, cannot acquire a manual ticket");
}
}
}
#define attr_found(geo_ap, ap) \
((geo_ap) && !strcmp((geo_ap)->val, (ap)->attr_val))
int check_attr_prereq(struct ticket_config *tk, grant_type_e grant_type)
{
GList *el;
struct attr_prereq *ap;
struct geo_attr *geo_ap;
for (el = g_list_first(tk->attr_prereqs); el; el = g_list_next(el))
{
ap = (struct attr_prereq *)el->data;
if (ap->grant_type != grant_type)
continue;
geo_ap = (struct geo_attr *)g_hash_table_lookup(tk->attr, ap->attr_name);
switch(ap->op) {
case ATTR_OP_EQ:
if (!attr_found(geo_ap, ap))
goto fail;
break;
case ATTR_OP_NE:
if (attr_found(geo_ap, ap))
goto fail;
break;
default:
break;
}
}
return 0;
fail:
tk_log_warn("'%s' attr-prereq failed", ap->attr_name);
return 1;
}
/* do we need to run the external program?
* or we already done that and waiting for the outcome
* or program exited and we can collect the status
* return codes
* 0: no program defined
* RUNCMD_MORE: program forked, results later
* != 0: executing program failed (or some other failure)
*/
static int do_ext_prog(struct booth_config *conf_ptr,
struct ticket_config *tk, int start_election)
{
int rv = 0;
if (!tk_test.path)
return 0;
switch(tk_test.progstate) {
case EXTPROG_IDLE:
rv = run_handler(conf_ptr, tk);
if (rv == RUNCMD_ERR) {
tk_log_warn("couldn't run external test, not allowed to acquire ticket");
ext_prog_failed(conf_ptr, tk, start_election);
}
break;
case EXTPROG_RUNNING:
/* should never get here, but just in case */
rv = RUNCMD_MORE;
break;
case EXTPROG_EXITED:
rv = tk_test_exit_status(tk);
if (rv) {
ext_prog_failed(conf_ptr, tk, start_election);
}
break;
case EXTPROG_IGNORE:
/* nothing to do here */
break;
}
return rv;
}
/* Try to acquire a ticket
* Could be manual grant or after start (if the ticket is granted
* and still valid in the CIB)
* If the external program needs to run, this is run twice, once
* to start the program, and then to get the result and start
* elections.
*/
static int acquire_ticket(struct booth_config *conf_ptr,
struct ticket_config *tk, cmd_reason_t reason)
{
int rv;
assert(conf_ptr != NULL);
assert(conf_ptr->local != NULL);
if (reason == OR_ADMIN && check_attr_prereq(tk, GRANT_MANUAL))
return RLT_ATTR_PREREQ;
switch(do_ext_prog(conf_ptr, tk, 0)) {
case 0:
/* everything fine */
break;
case RUNCMD_MORE:
/* need to wait for the outcome before starting elections */
return 0;
default:
return RLT_EXT_FAILED;
}
if (is_manual(tk)) {
rv = manual_selection(conf_ptr, tk, conf_ptr->local, 1, reason);
} else {
rv = new_election(conf_ptr, tk, conf_ptr->local, 1, reason);
}
return rv ? RLT_SYNC_FAIL : 0;
}
/** Try to get the ticket for the local site.
* */
static int do_grant_ticket(struct booth_config *conf_ptr,
struct ticket_config *tk, int options)
{
int rv;
assert(conf_ptr != NULL);
assert(conf_ptr->local != NULL);
tk_log_info("granting ticket");
if (tk->leader == conf_ptr->local)
return RLT_SUCCESS;
if (is_owned(tk)) {
if (is_manual(tk) && (options & OPT_IMMEDIATE)) {
/* -F flag has been used while granting a manual ticket.
* The ticket will be granted and may end up being granted
* on multiple sites */
tk_log_warn("manual ticket forced to be granted! be aware that "
"you may end up having two sites holding the same manual "
"ticket! revoke the ticket from the unnecessary site!");
} else {
return RLT_OVERGRANT;
}
}
set_future_time(&tk->delay_commit, tk->term_duration + tk->acquire_after);
if (options & OPT_IMMEDIATE) {
tk_log_warn("granting ticket immediately! If there are "
"unreachable sites, _hope_ you are sure that they don't "
"have the ticket!");
time_reset(&tk->delay_commit);
}
rv = acquire_ticket(conf_ptr, tk, OR_ADMIN);
if (rv) {
time_reset(&tk->delay_commit);
return rv;
} else {
return RLT_MORE;
}
}
static void start_revoke_ticket(struct booth_config *conf_ptr,
struct ticket_config *tk)
{
tk_log_info("revoking ticket");
save_committed_tkt(tk);
reset_ticket_and_set_no_leader(tk);
ticket_write(conf_ptr, tk);
ticket_broadcast(conf_ptr, tk, OP_REVOKE, OP_ACK, RLT_SUCCESS, OR_ADMIN);
}
/** Ticket revoke.
* Only to be started from the leader. */
static int do_revoke_ticket(struct booth_config *conf_ptr,
struct ticket_config *tk)
{
if (tk->acks_expected) {
tk_log_info("delay ticket revoke until the current operation finishes");
set_next_state(tk, ST_INIT);
return RLT_MORE;
} else {
start_revoke_ticket(conf_ptr, tk);
return RLT_SUCCESS;
}
}
static int number_sites_marked_as_granted(struct booth_config *conf_ptr,
struct ticket_config *tk)
{
struct booth_site *ignored __attribute__((unused));
int i, result = 0;
assert(conf_ptr != NULL);
FOREACH_NODE(conf_ptr, i, ignored) {
result += tk->sites_where_granted[i];
}
return result;
}
static int list_ticket(struct booth_config *conf_ptr, char **pdata,
unsigned int *len)
{
struct ticket_config *tk;
struct booth_site *site;
char timeout_str[64];
char pending_str[64];
char *data, *cp;
int i, alloc, site_index;
time_t ts;
int multiple_grant_warning_length = 0;
assert(conf_ptr != NULL);
assert(conf_ptr->local != NULL);
*pdata = NULL;
*len = 0;
alloc = conf_ptr->ticket_count * (BOOTH_NAME_LEN * 2 + 128 + 16);
FOREACH_TICKET(conf_ptr, i, tk) {
multiple_grant_warning_length = \
number_sites_marked_as_granted(conf_ptr, tk);
if (multiple_grant_warning_length > 1) {
// 164: 55 + 45 + 2*number_of_multiple_sites + some margin
alloc += 164 + BOOTH_NAME_LEN * (1+multiple_grant_warning_length);
}
}
data = malloc(alloc);
if (!data)
return -ENOMEM;
cp = data;
FOREACH_TICKET(conf_ptr, i, tk) {
if ((!is_manual(tk)) && is_time_set(&tk->term_expires)) {
/* Manual tickets doesn't have term_expires defined */
ts = wall_ts(&tk->term_expires);
strftime(timeout_str, sizeof(timeout_str), "%F %T",
localtime(&ts));
} else
strcpy(timeout_str, "INF");
if (tk->leader == conf_ptr->local
&& is_time_set(&tk->delay_commit)
&& !is_past(&tk->delay_commit)) {
ts = wall_ts(&tk->delay_commit);
strcpy(pending_str, " (commit pending until ");
strftime(pending_str + strlen(" (commit pending until "),
sizeof(pending_str) - strlen(" (commit pending until ") - 1,
"%F %T", localtime(&ts));
strcat(pending_str, ")");
} else
*pending_str = '\0';
cp += snprintf(cp,
alloc - (cp - data),
"ticket: %s, leader: %s",
tk->name,
ticket_leader_string(tk));
if (is_owned(tk)) {
cp += snprintf(cp,
alloc - (cp - data),
", expires: %s%s",
timeout_str,
pending_str);
}
if (is_manual(tk)) {
cp += snprintf(cp,
alloc - (cp - data),
" [manual mode]");
}
cp += snprintf(cp, alloc - (cp - data), "\n");
if (alloc - (cp - data) <= 0) {
free(data);
return -ENOMEM;
}
}
FOREACH_TICKET(conf_ptr, i, tk) {
multiple_grant_warning_length = \
number_sites_marked_as_granted(conf_ptr, tk);
if (multiple_grant_warning_length > 1) {
cp += snprintf(cp,
alloc - (cp - data),
"\nWARNING: The ticket %s is granted to multiple sites: ", // ~55 characters
tk->name);
FOREACH_NODE(conf_ptr, site_index, site) {
if (tk->sites_where_granted[site_index] > 0) {
cp += snprintf(cp,
alloc - (cp - data),
"%s",
site_string(site));
if (--multiple_grant_warning_length > 0) {
cp += snprintf(cp,
alloc - (cp - data),
", ");
}
}
}
cp += snprintf(cp,
alloc - (cp - data),
". Revoke the ticket from the faulty sites.\n"); // ~45 characters
}
}
*pdata = data;
*len = cp - data;
return 0;
}
void disown_ticket(struct ticket_config *tk)
{
set_leader(tk, NULL);
tk->is_granted = 0;
get_time(&tk->term_expires);
}
/* XXX UNUSED */
int disown_if_expired(struct ticket_config *tk)
{
if (is_past(&tk->term_expires) ||
!tk->leader) {
disown_ticket(tk);
return 1;
}
return 0;
}
void reset_ticket(struct ticket_config *tk)
{
ignore_ext_test(tk);
disown_ticket(tk);
no_resends(tk);
set_state(tk, ST_INIT);
set_next_state(tk, 0);
tk->voted_for = NULL;
}
void reset_ticket_and_set_no_leader(struct ticket_config *tk)
{
mark_ticket_as_revoked_from_leader(tk);
reset_ticket(tk);
tk->leader = no_leader;
tk_log_debug("ticket leader set to no_leader");
}
static void log_reacquire_reason(struct booth_config *conf_ptr,
struct ticket_config *tk)
{
int valid;
const char *where_granted = "\0";
char buff[75];
assert(conf_ptr != NULL);
assert(conf_ptr->local != NULL);
valid = is_time_set(&tk->term_expires) && !is_past(&tk->term_expires);
if (tk->leader == conf_ptr->local) {
where_granted = "granted here";
} else {
snprintf(buff, sizeof(buff), "granted to %s",
site_string(tk->leader));
where_granted = buff;
}
if (!valid) {
tk_log_warn("%s, but not valid "
"anymore (will try to reacquire)", where_granted);
}
if (tk->is_granted && tk->leader != conf_ptr->local) {
if (tk->leader && tk->leader != no_leader) {
tk_log_error("granted here, but also %s, "
"that's really too bad (will try to reacquire)",
where_granted);
} else {
tk_log_warn("granted here, but we're "
"not recorded as the grantee (will try to reacquire)");
}
}
}
void update_ticket_state(struct booth_config *conf_ptr,
struct ticket_config *tk, struct booth_site *sender)
{
assert(conf_ptr != NULL);
assert(conf_ptr->local != NULL);
if (tk->state == ST_CANDIDATE) {
tk_log_info("learned from %s about "
"newer ticket, stopping elections",
site_string(sender));
/* there could be rejects coming from others; don't log
* warnings unnecessarily */
tk->expect_more_rejects = 1;
}
if (tk->leader == conf_ptr->local || tk->is_granted) {
/* message from a live leader with valid ticket? */
if (sender == tk->leader && term_time_left(tk)) {
if (tk->is_granted) {
tk_log_warn("ticket was granted here, "
"but it's live at %s (revoking here)",
site_string(sender));
} else {
tk_log_info("ticket live at %s",
site_string(sender));
}
disown_ticket(tk);
ticket_write(conf_ptr, tk);
set_state(tk, ST_FOLLOWER);
set_next_state(tk, ST_FOLLOWER);
} else {
if (tk->state == ST_CANDIDATE) {
set_state(tk, ST_FOLLOWER);
}
set_next_state(tk, ST_LEADER);
}
} else {
if (!tk->leader || tk->leader == no_leader) {
if (sender)
tk_log_info("ticket is not granted");
else
tk_log_info("ticket is not granted (from CIB)");
set_state(tk, ST_INIT);
} else {
if (sender)
tk_log_info("ticket granted to %s (says %s)",
site_string(tk->leader),
tk->leader == sender ? "they" : site_string(sender));
else
tk_log_info("ticket granted to %s (from CIB)",
site_string(tk->leader));
set_state(tk, ST_FOLLOWER);
/* just make sure that we check the ticket soon */
set_next_state(tk, ST_FOLLOWER);
}
}
}
int setup_ticket(struct booth_config *conf_ptr)
{
struct ticket_config *tk;
int i;
assert(conf_ptr != NULL);
assert(conf_ptr->local != NULL);
FOREACH_TICKET(conf_ptr, i, tk) {
reset_ticket(tk);
if (conf_ptr->local->type == SITE) {
if (!conf_ptr->ticket_handler->load_ticket(conf_ptr, tk)) {
update_ticket_state(conf_ptr, tk, NULL);
}
tk->update_cib = 1;
}
tk_log_info("broadcasting state query");
/* wait until all send their status (or the first
* timeout) */
tk->start_postpone = 1;
ticket_broadcast(conf_ptr, tk, OP_STATUS, OP_MY_INDEX,
RLT_SUCCESS, 0);
}
return 0;
}
int ticket_answer_list(struct booth_config *conf_ptr, int fd)
{
char *data;
int rv;
unsigned int olen;
struct boothc_hdr_msg hdr;
rv = list_ticket(conf_ptr, &data, &olen);
if (rv < 0)
goto out;
init_header(conf_ptr, &hdr.header, CL_LIST, 0, 0, RLT_SUCCESS, 0,
sizeof(hdr) + olen);
rv = send_header_plus(conf_ptr, fd, &hdr, data, olen);
out:
if (data)
free(data);
return rv;
}
int process_client_request(struct booth_config *conf_ptr,
struct client *req_client, void *buf)
{
int rv, rc = 1;
struct ticket_config *tk;
int cmd;
struct boothc_ticket_msg omsg;
struct boothc_ticket_msg *msg;
assert(conf_ptr != NULL);
assert(conf_ptr->local != NULL);
msg = (struct boothc_ticket_msg *)buf;
cmd = ntohl(msg->header.cmd);
if (!check_ticket(conf_ptr, msg->ticket.id, &tk)) {
log_warn("client referenced unknown ticket %s",
msg->ticket.id);
rv = RLT_INVALID_ARG;
goto reply_now;
}
/* Perform the initial check before granting
* an already granted non-manual ticket */
if ((!is_manual(tk) && (cmd == CMD_GRANT) && is_owned(tk))) {
log_warn("client wants to grant an (already granted!) ticket %s",
msg->ticket.id);
rv = RLT_OVERGRANT;
goto reply_now;
}
if ((cmd == CMD_REVOKE) && !is_owned(tk)) {
log_info("client wants to revoke a free ticket %s",
msg->ticket.id);
rv = RLT_TICKET_IDLE;
goto reply_now;
}
if ((cmd == CMD_REVOKE) && tk->leader != conf_ptr->local) {
tk_log_info("not granted here, redirect to %s",
ticket_leader_string(tk));
rv = RLT_REDIRECT;
goto reply_now;
}
if (cmd == CMD_REVOKE)
rv = do_revoke_ticket(conf_ptr, tk);
else
rv = do_grant_ticket(conf_ptr, tk, ntohl(msg->header.options));
if (rv == RLT_MORE) {
/* client may receive further notifications, save the
* request for further processing */
add_req(tk, req_client, msg);
tk_log_debug("queue request %s for client %d",
state_to_string(cmd), req_client->fd);
rc = 0; /* we're not yet done with the message */
}
reply_now:
init_ticket_msg(conf_ptr, &omsg, CL_RESULT, 0, rv, 0, tk);
send_client_msg(conf_ptr, req_client->fd, &omsg);
return rc;
}
int notify_client(struct booth_config *conf_ptr, struct ticket_config *tk,
int client_fd, struct boothc_ticket_msg *msg)
{
struct boothc_ticket_msg omsg;
void (*deadfn) (int ci);
int rv, rc, ci;
int cmd, options;
struct client *req_client;
cmd = ntohl(msg->header.cmd);
options = ntohl(msg->header.options);
rv = tk->outcome;
ci = find_client_by_fd(client_fd);
if (ci < 0) {
tk_log_info("client %d (request %s) left before being notified",
client_fd, state_to_string(cmd));
return 0;
}
tk_log_debug("notifying client %d (request %s)",
client_fd, state_to_string(cmd));
init_ticket_msg(conf_ptr, &omsg, CL_RESULT, 0, rv, 0, tk);
rc = send_client_msg(conf_ptr, client_fd, &omsg);
if (rc == 0 && ((rv == RLT_MORE) ||
(rv == RLT_CIB_PENDING && (options & OPT_WAIT_COMMIT)))) {
/* more to do here, keep the request */
return 1;
} else {
/* we sent a definite answer or there was a write error, drop
* the client */
if (rc) {
tk_log_debug("failed to notify client %d (request %s)",
client_fd, state_to_string(cmd));
} else {
tk_log_debug("client %d (request %s) got final notification",
client_fd, state_to_string(cmd));
}
req_client = clients + ci;
deadfn = req_client->deadfn;
if(deadfn) {
deadfn(ci);
}
return 0; /* we're done with this request */
}
}
int ticket_broadcast(struct booth_config *conf_ptr,
struct ticket_config *tk, cmd_request_t cmd,
cmd_request_t expected_reply, cmd_result_t res,
cmd_reason_t reason)
{
struct boothc_ticket_msg msg;
assert(conf_ptr != NULL);
init_ticket_msg(conf_ptr, &msg, cmd, 0, res, reason, tk);
tk_log_debug("broadcasting '%s' (term=%d, valid=%d)",
state_to_string(cmd),
ntohl(msg.ticket.term),
msg_term_time(&msg));
tk->last_request = cmd;
if (expected_reply) {
expect_replies(tk, expected_reply, conf_ptr->local);
}
ticket_activate_timeout(tk);
return transport(conf_ptr)->broadcast_auth(conf_ptr, &msg,
sendmsglen(&msg));
}
/* update the ticket on the leader, write it to the CIB, and
send out the update message to others with the new expiry
time
*/
int leader_update_ticket(struct booth_config *conf_ptr,
struct ticket_config *tk)
{
int rv = 0, rv2;
timetype now;
if (tk->ticket_updated >= 2)
return 0;
/* for manual tickets, we don't set time expiration */
if (!is_manual(tk)) {
if (tk->ticket_updated < 1) {
tk->ticket_updated = 1;
get_time(&now);
copy_time(&now, &tk->last_renewal);
set_future_time(&tk->term_expires, tk->term_duration);
rv = ticket_broadcast(conf_ptr, tk, OP_UPDATE, OP_ACK,
RLT_SUCCESS, 0);
}
}
if (tk->ticket_updated < 2) {
rv2 = ticket_write(conf_ptr, tk);
switch(rv2) {
case 0:
tk->ticket_updated = 2;
tk->outcome = RLT_SUCCESS;
foreach_tkt_req(conf_ptr, tk, notify_client);
break;
case 1:
if (tk->outcome != RLT_CIB_PENDING) {
tk->outcome = RLT_CIB_PENDING;
foreach_tkt_req(conf_ptr, tk, notify_client);
}
break;
default:
break;
}
}
return rv;
}
static void log_lost_servers(struct booth_config *conf_ptr,
struct ticket_config *tk)
{
struct booth_site *n;
int i;
assert(conf_ptr != NULL);
if (tk->retry_number > 1)
/* log those that we couldn't reach, but do
* that only on the first retry
*/
return;
FOREACH_NODE(conf_ptr, i, n) {
if (!(tk->acks_received & n->bitmask)) {
tk_log_warn("%s %s didn't acknowledge our %s, "
"will retry %d times",
(n->type == ARBITRATOR ? "arbitrator" : "site"),
site_string(n),
state_to_string(tk->last_request),
tk->retries);
}
}
}
static void resend_msg(struct booth_config *conf_ptr,
struct ticket_config *tk)
{
struct booth_site *n;
int i;
assert(conf_ptr != NULL);
assert(conf_ptr->local != NULL);
if (!(tk->acks_received ^ conf_ptr->local->bitmask)) {
ticket_broadcast(conf_ptr, tk, tk->last_request, 0,
RLT_SUCCESS, 0);
} else {
FOREACH_NODE(conf_ptr, i, n) {
if (!(tk->acks_received & n->bitmask)) {
n->resend_cnt++;
tk_log_debug("resending %s to %s",
state_to_string(tk->last_request),
site_string(n)
);
send_msg(conf_ptr, tk->last_request, tk, n,
NULL);
}
}
ticket_activate_timeout(tk);
}
}
static void handle_resends(struct booth_config *conf_ptr,
struct ticket_config *tk)
{
int ack_cnt;
if (++tk->retry_number > tk->retries) {
tk_log_info("giving up on sending retries");
no_resends(tk);
set_ticket_wakeup(conf_ptr, tk);
return;
}
/* try to reach some sites again if we just stepped down */
if (tk->last_request == OP_VOTE_FOR) {
tk_log_warn("no answers to our VtFr request to step down (try #%d), "
"we are alone",
tk->retry_number);
goto just_resend;
}
if (!majority_of_bits(conf_ptr, tk, tk->acks_received)) {
ack_cnt = count_bits(tk->acks_received) - 1;
if (!ack_cnt) {
tk_log_warn("no answers to our request (try #%d), "
"we are alone",
tk->retry_number);
} else {
tk_log_warn("not enough answers to our request (try #%d): "
"only got %d answers",
tk->retry_number,
ack_cnt);
}
} else {
log_lost_servers(conf_ptr, tk);
}
just_resend:
resend_msg(conf_ptr, tk);
}
static int postpone_ticket_processing(struct ticket_config *tk)
{
extern timetype start_time;
return tk->start_postpone &&
(-time_left(&start_time) < tk->timeout);
}
#define has_extprog_exited(tk) ((tk)->clu_test.progstate == EXTPROG_EXITED)
static void process_next_state(struct booth_config *conf_ptr,
struct ticket_config *tk)
{
int rv;
switch(tk->next_state) {
case ST_LEADER:
if (has_extprog_exited(tk)) {
if (tk->state != ST_LEADER) {
rv = acquire_ticket(conf_ptr, tk, OR_ADMIN);
if (rv != 0) { /* external program failed */
tk->outcome = rv;
foreach_tkt_req(conf_ptr, tk, notify_client);
}
}
} else {
log_reacquire_reason(conf_ptr, tk);
acquire_ticket(conf_ptr, tk, OR_REACQUIRE);
}
break;
case ST_INIT:
no_resends(tk);
start_revoke_ticket(conf_ptr, tk);
tk->outcome = RLT_SUCCESS;
foreach_tkt_req(conf_ptr, tk, notify_client);
break;
/* wanting to be follower is not much of an ambition; no
* processing, just return; don't reset start_postpone until
* we got some replies to status */
case ST_FOLLOWER:
return;
default:
break;
}
tk->start_postpone = 0;
}
static void ticket_lost(struct booth_config *conf_ptr,
struct ticket_config *tk)
{
int reason = OR_TKT_LOST;
assert(conf_ptr != NULL);
assert(conf_ptr->local != NULL);
if (tk->leader != conf_ptr->local) {
tk_log_warn("lost at %s", site_string(tk->leader));
} else {
if (is_ext_prog_running(tk)) {
ext_prog_timeout(tk);
reason = OR_LOCAL_FAIL;
} else {
tk_log_warn("lost majority (revoking locally)");
reason = tk->election_reason ? tk->election_reason : OR_REACQUIRE;
}
}
tk->lost_leader = tk->leader;
save_committed_tkt(tk);
mark_ticket_as_revoked_from_leader(tk);
reset_ticket(tk);
set_state(tk, ST_FOLLOWER);
if (conf_ptr->local->type == SITE) {
ticket_write(conf_ptr, tk);
schedule_election(conf_ptr, tk, reason);
}
}
static void next_action(struct booth_config *conf_ptr,
struct ticket_config *tk)
{
int rv;
switch(tk->state) {
case ST_INIT:
/* init state, handle resends for ticket revoke */
/* and rebroadcast if stepping down */
/* try to acquire ticket on grant */
if (has_extprog_exited(tk)) {
rv = acquire_ticket(conf_ptr, tk, OR_ADMIN);
if (rv != 0) { /* external program failed */
tk->outcome = rv;
foreach_tkt_req(conf_ptr, tk, notify_client);
}
} else {
if (tk->acks_expected) {
handle_resends(conf_ptr, tk);
}
}
break;
case ST_FOLLOWER:
if (!is_manual(tk)) {
/* leader/ticket lost? and we didn't vote yet */
tk_log_debug("leader: %s, voted_for: %s",
site_string(tk->leader),
site_string(tk->voted_for));
if (!tk->leader) {
if (!tk->voted_for || !tk->in_election) {
disown_ticket(tk);
if (!new_election(conf_ptr, tk, NULL,
1, OR_AGAIN)) {
ticket_activate_timeout(tk);
}
} else {
/* we should restart elections in case nothing
* happens in the meantime */
tk->in_election = 0;
ticket_activate_timeout(tk);
}
}
} else {
/* for manual tickets, also try to acquire ticket on grant
* in the Follower state (because we may end up having
* two Leaders) */
if (has_extprog_exited(tk)) {
rv = acquire_ticket(conf_ptr, tk, OR_ADMIN);
if (rv != 0) { /* external program failed */
tk->outcome = rv;
foreach_tkt_req(conf_ptr, tk,
notify_client);
}
} else {
/* Otherwise, just send ACKs if needed */
if (tk->acks_expected) {
handle_resends(conf_ptr, tk);
}
}
}
break;
case ST_CANDIDATE:
/* elections timed out? */
elections_end(conf_ptr, tk);
break;
case ST_LEADER:
/* timeout or ticket renewal? */
if (tk->acks_expected) {
handle_resends(conf_ptr, tk);
if (majority_of_bits(conf_ptr, tk, tk->acks_received)) {
leader_update_ticket(conf_ptr, tk);
}
} else {
/* this is ticket renewal, run local test */
if (!do_ext_prog(conf_ptr, tk, 1)) {
ticket_broadcast(conf_ptr, tk, OP_HEARTBEAT,
OP_ACK, RLT_SUCCESS, 0);
tk->ticket_updated = 0;
}
}
break;
default:
break;
}
}
static void ticket_cron(struct booth_config *conf_ptr,
struct ticket_config *tk)
{
/* don't process the tickets too early after start */
if (postpone_ticket_processing(tk)) {
tk_log_debug("ticket processing postponed (start_postpone=%d)",
tk->start_postpone);
/* but run again soon */
ticket_activate_timeout(tk);
return;
}
/* no need for status resends, we hope we got at least one
* my_index back */
if (tk->acks_expected == OP_MY_INDEX) {
no_resends(tk);
}
/* after startup, we need to decide what to do based on the
* current ticket state; tk->next_state has a hint
* also used for revokes which had to be delayed
*/
if (tk->next_state) {
process_next_state(conf_ptr, tk);
goto out;
}
/* Has an owner, has an expiry date, and expiry date in the past?
* For automatic tickets, losing the ticket must happen
* in _every_ state.
*/
if ((!is_manual(tk)) &&
is_owned(tk) && is_time_set(&tk->term_expires)
&& is_past(&tk->term_expires)) {
ticket_lost(conf_ptr, tk);
goto out;
}
next_action(conf_ptr, tk);
out:
tk->next_state = 0;
if (!tk->in_election && tk->update_cib)
ticket_write(conf_ptr, tk);
}
void process_tickets(struct booth_config *conf_ptr)
{
struct ticket_config *tk;
int i;
timetype last_cron;
assert(conf_ptr != NULL);
FOREACH_TICKET(conf_ptr, i, tk) {
if (!has_extprog_exited(tk) &&
is_time_set(&tk->next_cron) && !is_past(&tk->next_cron))
continue;
tk_log_debug("ticket cron");
copy_time(&tk->next_cron, &last_cron);
ticket_cron(conf_ptr, tk);
if (time_cmp(&last_cron, &tk->next_cron, ==)) {
tk_log_debug("nobody set ticket wakeup");
set_ticket_wakeup(conf_ptr, tk);
}
}
}
void tickets_log_info(struct booth_config *conf_ptr)
{
struct ticket_config *tk;
int i;
time_t ts;
assert(conf_ptr != NULL);
FOREACH_TICKET(conf_ptr, i, tk) {
ts = wall_ts(&tk->term_expires);
tk_log_info("state '%s' "
"term %d "
"leader %s "
"expires %-24.24s",
state_to_string(tk->state),
tk->current_term,
ticket_leader_string(tk),
ctime(&ts));
}
}
static void update_acks(struct booth_config *conf_ptr, struct ticket_config *tk,
struct booth_site *sender, struct booth_site *leader,
struct boothc_ticket_msg *msg)
{
uint32_t cmd;
uint32_t req;
cmd = ntohl(msg->header.cmd);
req = ntohl(msg->header.request);
if (req != tk->last_request ||
(tk->acks_expected != cmd &&
tk->acks_expected != OP_REJECTED))
return;
/* got an ack! */
tk->acks_received |= sender->bitmask;
if (all_replied(conf_ptr, tk) ||
/* we just stepped down, need only one site to start
* elections */
(cmd == OP_REQ_VOTE && tk->last_request == OP_VOTE_FOR)) {
no_resends(tk);
tk->start_postpone = 0;
set_ticket_wakeup(conf_ptr, tk);
}
}
/* read ticket message */
int ticket_recv(struct booth_config *conf_ptr, void *buf,
struct booth_site *source)
{
struct boothc_ticket_msg *msg;
struct ticket_config *tk;
struct booth_site *leader;
uint32_t leader_u;
msg = (struct boothc_ticket_msg *)buf;
if (!check_ticket(conf_ptr, msg->ticket.id, &tk)) {
log_warn("got invalid ticket name %s from %s",
msg->ticket.id, site_string(source));
source->invalid_cnt++;
return -EINVAL;
}
leader_u = ntohl(msg->ticket.leader);
if (!find_site_by_id(conf_ptr, leader_u, &leader)) {
tk_log_error("message with unknown leader %u received", leader_u);
source->invalid_cnt++;
return -EINVAL;
}
update_acks(conf_ptr, tk, source, leader, msg);
return raft_answer(conf_ptr, tk, source, leader, msg);
}
static void log_next_wakeup(struct ticket_config *tk)
{
int left;
left = time_left(&tk->next_cron);
tk_log_debug("set ticket wakeup in " intfmt(left));
}
/* New vote round; §5.2 */
/* delay the next election start for some random time
* (up to 1 second)
*/
void add_random_delay(struct ticket_config *tk)
{
timetype tv;
interval_add(&tk->next_cron, rand_time(min(1000, tk->timeout)), &tv);
ticket_next_cron_at(tk, &tv);
if (ANYDEBUG) {
log_next_wakeup(tk);
}
}
void set_ticket_wakeup(struct booth_config *conf_ptr, struct ticket_config *tk)
{
timetype near_future, tv, next_vote;
set_future_time(&near_future, 10);
if (!is_manual(tk)) {
/* At least every hour, perhaps sooner (default) */
tk_log_debug("ticket will be woken up after up to one hour");
ticket_next_cron_in(tk, 3600*TIME_RES);
switch (tk->state) {
case ST_LEADER:
assert(tk->leader == conf_ptr->local);
get_next_election_time(tk, &next_vote, conf_ptr->local);
/* If timestamp is in the past, wakeup in
* near future */
if (!is_time_set(&next_vote)) {
tk_log_debug("next ts unset, wakeup soon");
ticket_next_cron_at(tk, &near_future);
} else if (is_past(&next_vote)) {
int tdiff = time_left(&next_vote);
tk_log_debug("next ts in the past " intfmt(tdiff));
ticket_next_cron_at(tk, &near_future);
} else {
ticket_next_cron_at(tk, &next_vote);
}
break;
case ST_CANDIDATE:
assert(is_time_set(&tk->election_end));
ticket_next_cron_at(tk, &tk->election_end);
break;
case ST_INIT:
case ST_FOLLOWER:
/* If there is (or should be) some owner, check on it later on.
* If no one is interested - don't care. */
if (is_owned(tk)) {
interval_add(&tk->term_expires, tk->acquire_after, &tv);
ticket_next_cron_at(tk, &tv);
}
break;
default:
tk_log_error("unknown ticket state: %d", tk->state);
}
if (tk->next_state) {
/* we need to do something soon here */
if (!tk->acks_expected) {
ticket_next_cron_at(tk, &near_future);
} else {
ticket_activate_timeout(tk);
}
}
} else {
/* At least six minutes, to make sure that multi-leader situations
* will be solved promptly.
*/
tk_log_debug("manual ticket will be woken up after up to six minutes");
ticket_next_cron_in(tk, 60*TIME_RES);
/* For manual tickets, no earlier timeout could be set in a similar
* way as it is done in a switch above for automatic tickets.
* The reason is that term's timeout is INF and no Raft-based elections
* are performed.
*/
}
if (ANYDEBUG) {
log_next_wakeup(tk);
}
}
void schedule_election(struct booth_config *conf_ptr, struct ticket_config *tk,
cmd_reason_t reason)
{
assert(conf_ptr != NULL);
assert(conf_ptr->local != NULL);
if (conf_ptr->local->type != SITE)
return;
tk->election_reason = reason;
get_time(&tk->next_cron);
/* introduce a short delay before starting election */
add_random_delay(tk);
}
int is_manual(struct ticket_config *tk)
{
return (tk->mode == TICKET_MODE_MANUAL) ? 1 : 0;
}
/* Given a state (in host byte order), return a human-readable (char*).
* An array is used so that multiple states can be printed in a single printf(). */
char *state_to_string(uint32_t state_ho)
{
union mu { cmd_request_t s; char c[5]; };
static union mu cache[6] = { { 0 } }, *cur;
static int current = 0;
current ++;
if (current >= sizeof(cache)/sizeof(cache[0]))
current = 0;
cur = cache + current;
cur->s = htonl(state_ho);
/* Shouldn't be necessary, union array is initialized with zeroes, and
* these bytes never get written. */
cur->c[4] = 0;
return cur->c;
}
int send_reject(struct booth_config *conf_ptr, struct booth_site *dest,
struct ticket_config *tk, cmd_result_t code,
struct boothc_ticket_msg *in_msg)
{
int req = ntohl(in_msg->header.cmd);
struct boothc_ticket_msg msg;
tk_log_debug("sending reject to %s",
site_string(dest));
init_ticket_msg(conf_ptr, &msg, OP_REJECTED, req, code, 0, tk);
return booth_udp_send_auth(conf_ptr, dest, &msg, sendmsglen(&msg));
}
int send_msg(struct booth_config *conf_ptr, int cmd, struct ticket_config *tk,
struct booth_site *dest, struct boothc_ticket_msg *in_msg)
{
int req = 0;
struct ticket_config *valid_tk = tk;
struct boothc_ticket_msg msg;
/* if we want to send the last valid ticket, then if we're in
* the ST_CANDIDATE state, the last valid ticket is in
* tk->last_valid_tk
*/
if (cmd == OP_MY_INDEX) {
if (tk->state == ST_CANDIDATE && tk->last_valid_tk) {
valid_tk = tk->last_valid_tk;
}
tk_log_info("sending status to %s",
site_string(dest));
}
if (in_msg)
req = ntohl(in_msg->header.cmd);
init_ticket_msg(conf_ptr, &msg, cmd, req, RLT_SUCCESS, 0, valid_tk);
return booth_udp_send_auth(conf_ptr, dest, &msg, sendmsglen(&msg));
}
diff --git a/src/ticket.h b/src/ticket.h
index 26a0a24..29891f0 100644
--- a/src/ticket.h
+++ b/src/ticket.h
@@ -1,349 +1,336 @@
/*
* Copyright (C) 2011 Jiaju Zhang <jjzhang@suse.de>
* Copyright (C) 2013-2014 Philipp Marek <philipp.marek@linbit.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef _TICKET_H
#define _TICKET_H
#include <time.h>
#include <sys/time.h>
#include <math.h>
#include "timer.h"
#include "config.h"
#include "log.h"
extern int TIME_RES;
#define DEFAULT_TICKET_EXPIRY (600*TIME_RES)
#define DEFAULT_TICKET_TIMEOUT (5*TIME_RES)
#define DEFAULT_RETRIES 10
#define FOREACH_TICKET(b_, i_, t_) \
for (i_ = 0; \
(t_ = (b_)->ticket + i_, i_ < (b_)->ticket_count); \
i_++)
#define FOREACH_NODE(b_, i_, n_) \
for (i_ = 0; \
(n_ = (b_)->site + i_, i_ < (b_)->site_count); \
i_++)
#define set_leader(tk, who) do { \
if (who == NULL) { \
mark_ticket_as_revoked_from_leader(tk); \
} \
\
tk->leader = who; \
tk_log_debug("ticket leader set to %s", ticket_leader_string(tk)); \
\
if (tk->leader) { \
mark_ticket_as_granted(tk, tk->leader); \
} \
} while(0)
#define mark_ticket_as_granted(tk, who) do { \
if (is_manual(tk) && (who->index > -1)) { \
tk->sites_where_granted[who->index] = 1; \
tk_log_debug("manual ticket marked as granted to %s", ticket_leader_string(tk)); \
} \
} while(0)
#define mark_ticket_as_revoked(tk, who) do { \
if (is_manual(tk) && who && (who->index > -1)) { \
tk->sites_where_granted[who->index] = 0; \
tk_log_debug("manual ticket marked as revoked from %s", site_string(who)); \
} \
} while(0)
#define mark_ticket_as_revoked_from_leader(tk) do { \
if (tk->leader) { \
mark_ticket_as_revoked(tk, tk->leader); \
} \
} while(0)
#define set_state(tk, newst) do { \
tk_log_debug("state transition: %s -> %s", \
state_to_string(tk->state), state_to_string(newst)); \
tk->state = newst; \
} while(0)
#define set_next_state(tk, newst) do { \
if (!(newst)) tk_log_debug("next state reset"); \
else tk_log_debug("next state set to %s", state_to_string(newst)); \
tk->next_state = newst; \
} while(0)
#define is_term_invalid(tk, term) \
((tk)->last_valid_tk && (tk)->last_valid_tk->current_term > (term))
void save_committed_tkt(struct ticket_config *tk);
void disown_ticket(struct ticket_config *tk);
/* XXX UNUSED */
int disown_if_expired(struct ticket_config *tk);
/**
* @internal
* Pick a ticket structure based on given name, with some apriori sanity checks
*
* @param[inout] conf_ptr config object to refer to
* @param[in] ticket name of the ticket to search for
* @param[out] found place the reference here when found
*
* @return 0 on failure, see @find_ticket_by_name otherwise
*/
int check_ticket(struct booth_config *conf_ptr, const char *ticket,
struct ticket_config **tc);
/**
* @internal
* Check whether given site is valid
*
* @param[inout] conf_ptr config object to refer to
* @param[in] site which member to look for
* @param[out] is_local store whether the member is local on success
*
* @note XXX UNUSED
*
* @return 1 on success (found and valid), 0 otherwise
*/
int check_site(struct booth_config *conf_ptr, const char *site,
int *local);
/**
* @internal
* Second stage of incoming datagram handling (after authentication)
*
* @param[inout] conf_ptr config object to refer to
* @param[in] buf raw message to act upon
* @param[in] source member originating this message
*
* @return 0 on success or negative value (-1 or -errno) on error
*/
int ticket_recv(struct booth_config *conf_ptr, void *buf,
struct booth_site *source);
void reset_ticket(struct ticket_config *tk);
void reset_ticket_and_set_no_leader(struct ticket_config *tk);
/**
* @internal
* Based on the current state and circumstances, make a state transition
*
* @param[inout] conf_ptr config object to refer to
* @param[in] tk ticket at hand
* @param[in] sender site structure of the sender
*/
void update_ticket_state(struct booth_config *conf_ptr,
struct ticket_config *tk, struct booth_site *sender);
/**
* @internal
* Initial "consult local pacemaker and booth peers" inquiries
*
* @param[inout] conf_ptr config object to use as a starting point
*
* @return 0 (for the time being)
*/
int setup_ticket(struct booth_config *conf_ptr);
-/**
- * @internal
- * Pick a ticket structure based on given name
- *
- * @param[inout] conf_ptr config object to refer to
- * @param[in] ticket name of the ticket to search for
- * @param[out] found place the reference here when found
- *
- * @return see @list_ticket and @send_header_plus
- */
-int find_ticket_by_name(struct booth_config *conf_ptr,
- const char *ticket, struct ticket_config **found);
-
/**
* @internal
* Apply the next step with the ticket if possible.
*
* @param[inout] conf_ptr config object to refer to
* @param[in] tk ticket at hand
*/
void set_ticket_wakeup(struct booth_config *conf_ptr,
struct ticket_config *tk);
/**
* @internal
* Implementation of the ticket listing
*
* @param[inout] conf_ptr config object to refer to
* @param[in] file descriptor of the socket to respond to
*
* @return see @list_ticket and @send_header_plus
*/
int ticket_answer_list(struct booth_config *conf_ptr, int fd);
/**
* @internal
* Process request from the client (as opposed to peer daemon)
*
* @param[inout] conf_ptr config object to refer to
* @param[in] req_client client structure of the sender
* @param[in] buf message itself
*
* @return 1 on success, 0 when not done with the message, yet
*/
int process_client_request(struct booth_config *conf_ptr,
struct client *req_client, void *buf);
/**
* @internal
* Cause the ticket storage backend to persist the ticket
*
* @param[inout] conf_ptr config object to refer to
* @param[in] tk ticket at hand
*
* @return 0 on success, 1 when not carried out for being dangerous
*/
int ticket_write(struct booth_config *conf_ptr,
struct ticket_config *tk);
/**
* @internal
* Mainloop of booth ticket handling
*
* @param[inout] conf_ptr config object to refer to
*/
void process_tickets(struct booth_config *conf_ptr);
/**
* @internal
* For each ticket, log some notable properties
*
* @param[inout] conf_ptr config object to refer to
*/
void tickets_log_info(struct booth_config *conf_ptr);
char *state_to_string(uint32_t state_ho);
/**
* @internal
* For a given ticket and recipient site, send a rejection
*
* @param[inout] conf_ptr config object to refer to
* @param[in] dest site structure of the recipient
* @param[in] tk ticket at hand
* @param[in] code further detail for the rejection
* @param[in] in_msg message this is going to be a response to
*/
int send_reject(struct booth_config *conf_ptr, struct booth_site *dest,
struct ticket_config *tk, cmd_result_t code,
struct boothc_ticket_msg *in_msg);
/**
* @internal
* For a given ticket, recipient site and possibly its message, send a response
*
* @param[inout] conf_ptr config object to refer to
* @param[in] cmd what type of message is to be sent
* @param[in] dest site structure of the recipient
* @param[in] in_msg message this is going to be a response to
*/
int send_msg(struct booth_config *conf_ptr, int cmd, struct ticket_config *tk,
struct booth_site *dest, struct boothc_ticket_msg *in_msg);
/**
* @internal
* Notify client at particular socket, regarding particular ticket
*
* @param[inout] conf_ptr config object to refer to
* @param[in] tk ticket at hand
* @param[in] fd file descriptor of the socket to respond to
* @param[in] msg input message being responded to
*/
int notify_client(struct booth_config *conf_ptr, struct ticket_config *tk,
int client_fd, struct boothc_ticket_msg *msg);
/**
* @internal
* Broadcast the current state of the ticket as seen from local perspective
*
* @param[inout] conf_ptr config object to refer to
* @param[in] tk ticket at hand
* @param[in] cmd what type of message is to be sent
* @param[in] expected_reply what to expect in response
* @param[in] res may carry further detail with cmd == OP_REJECTED
* @param[in] reason trigger of this broadcast
*/
int ticket_broadcast(struct booth_config *conf_ptr,
struct ticket_config *tk, cmd_request_t cmd,
cmd_request_t expected_reply, cmd_result_t res,
cmd_reason_t reason);
/**
* @internal
* Update the ticket (+broadcast to that effect) and/or write it to the backend
*
* @param[inout] conf_ptr config object to refer to
* @param[in] tk ticket at hand
*
* @return 0 or see #ticket_broadcast
*/
int leader_update_ticket(struct booth_config *conf_ptr,
struct ticket_config *tk);
void add_random_delay(struct ticket_config *tk);
/**
* @internal
* Make it so the nearest ticket swipe will start election
*
* @param[inout] conf_ptr config object to refer to
* @param[in] tk ticket at hand
* @param[in] reason explains why new election is conducted
*/
void schedule_election(struct booth_config *conf_ptr, struct ticket_config *tk,
cmd_reason_t reason);
int is_manual(struct ticket_config *tk);
int check_attr_prereq(struct ticket_config *tk, grant_type_e grant_type);
static inline void ticket_next_cron_at(struct ticket_config *tk, timetype *when)
{
copy_time(when, &tk->next_cron);
}
static inline void ticket_next_cron_in(struct ticket_config *tk, int interval)
{
timetype tv;
set_future_time(&tv, interval);
ticket_next_cron_at(tk, &tv);
}
static inline void ticket_activate_timeout(struct ticket_config *tk)
{
/* TODO: increase timeout when no answers */
tk_log_debug("activate ticket timeout in %d", tk->timeout);
ticket_next_cron_in(tk, tk->timeout);
}
#endif /* _TICKET_H */

File Metadata

Mime Type
text/x-diff
Expires
Mon, Feb 24, 5:15 AM (1 d, 15 h ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1463971
Default Alt Text
(84 KB)

Event Timeline