Page MenuHomeClusterLabs Projects

No OneTemporary

diff --git a/docs/boothd.8.txt b/docs/boothd.8.txt
index b43283c..c3765c9 100644
--- a/docs/boothd.8.txt
+++ b/docs/boothd.8.txt
@@ -1,367 +1,379 @@
BOOTHD(8)
===========
:doctype: manpage
NAME
----
boothd - The Booth Cluster Ticket Manager.
SYNOPSIS
--------
*boothd* 'daemon' ['-D'] [-c 'config']
*booth* ['client'] {'list'} [-S 'site'] ['-D'] [-c 'config']
*booth* ['client'] {'grant'|'revoke'} [-S 'site'] ['-D'] [-t] 'ticket' [-c 'config']
*booth* 'status' ['-D'] [-c 'config']
DESCRIPTION
-----------
Booth manages tickets which authorizes one of the cluster sites located in
geographically dispersed distances to run certain resources. It is designed to
be an add-on to Pacemaker, which extends Pacemaker to support geographically
distributed clustering.
It is based on the RAFT protocol, see eg.
<https://ramcloud.stanford.edu/wiki/download/attachments/11370504/raft.pdf>
for details.
SHORT EXAMPLES
--------------
---------------------
# boothd daemon
# boothd client list
# boothd client grant -t ticket-nfs
# boothd client revoke -t ticket-nfs
---------------------
OPTIONS
-------
*-c*::
Configuration to use.
+
Can be a full path to a configuration file, or a short name; in the latter
case, the directory '/etc/booth' and suffix '.conf' are added.
Per default 'booth' is used, which results in the path '/etc/booth/booth.conf'.
+
The configuration name also determines the name of the PID file - for the defaults,
'/var/run/booth/booth.pid'.
*-D*::
Debug output/don't daemonize.
Increases the debug output level; for 'boothd daemon', keeps the process
in the foreground.
*-h*, *--help*::
Give a short usage output.
*-s*::
Site address.
*-t*::
Ticket name.
*-v*, *--version*::
Report version information.
*-S*::
'systemd' mode: don't fork. This is like '-D' but without the debug output.
COMMANDS
--------
Whether the binary is called as 'boothd' or 'booth' doesn't matter; the first
argument determines the mode of operation.
*'daemon'*::
Tells 'boothd' to serve a site. The locally configured interfaces are
searched for an IP address that got defined in the configuration,
so that Booth can operate in /arbitrator/ resp. /site/ mode.
*'client'*::
Allows to list the ticket information (see also 'crm_ticket -L'),
and to revoke or (initially) grant tickets to a site.
+
In this mode the configuration file is searched for an IP address that is
locally reachable, ie. matches a configured subnet.
This allows to run the client commands on another node in the same cluster, as
long as the config file and the service IP is locally reachable.
+
Example: If the booth service IP is 192.168.55.200, and the local node has
192.168.55.15 configured on an interface, it knows which site it belongs to.
+
The client can also ask another site; use '-s' to tell where to connect to.
*'status'*::
'boothd' looks for the (locked) PID file and the UDP socket, prints
some output to stdout (for use in shell scripts) and returns a
OCF-compatible return code.
With '-D', a human-readable message is printed to STDERR as well.
CONFIGURATION FILE
------------------
A basic file looks like this:
-----------------------
site="192.168.201.100"
site="192.168.202.100"
arbitrator="192.168.203.100"
ticket="I-want-a-pony"
-----------------------
You can use comment lines, by starting them with a hash-sign (''#'').
Whitespace at the start and end of the line, and around the ''='', are ignored.
The following key/value pairs are defined:
*'port'*::
The UDP/TCP port to use. Default is '9929'.
*'transport'*::
- The transport protocol to use for PAXOS exchanges.
+ The transport protocol to use for Raft exchanges.
Currently only UDP is available.
+
Please note that the client mode always uses TCP to talk to a daemon; Booth
will always bind and listen to *both* UDP and TCP ports.
*'site'*, *'arbitrator'*::
- Defines a PAXOS member with the given IP, which should be a service IP.
+ Defines a Raft member with the given IP, which should be a service IP.
+
You will need at least three members for normal operation; an odd number is
preferred.
*'ticket'*::
Registers a ticket. Multiple tickets can be handled in a single Booth instance.
The next items modify per-ticket defaults. They are stored as defaults for
further tickets, and are used as value for the last defined ticket (if any).
*'expire'*::
- The lease time for a ticket, in seconds. After that time the ticket gets
+ The lease time for a ticket, in seconds. After that time the ticket can be
revoked, and another site can get it.
+
Typically 'booth' will try to renew a held ticket after half the lease time.
*'timeout'*::
After that time 'booth' will re-send packets if there was an insufficient
number of replies.
+
The default is '3'.
*'weights'*::
A comma-separated list of integers that define the weight of individual
- PAXOS members, in the same order as the 'site' and 'arbitrator' lines.
+ Raft members, in the same order as the 'site' and 'arbitrator' lines.
+
Default is '0' for all; this means that the ordering within the configuration
file defines a kind of priority for conflicting requests.
*'acquire-after'*::
Setting this to a positive value will make 'booth' try to acquire a ticket
that got lost.
+
Ie. if the site that _had_ the ticket is not reachable any more,
then 'acquire-after' seconds after ticket expiration other sites will try
to activate the ticket. (Only one will succeed, though.)
+
A typical delay might be 60 seconds.
*'retries'*::
Defines how often broadcast packets are sent out before the current
action (grant, revoke) is aborted.
+
Default is 10; values lower than 3 are forbidden, and high values won't
make much sense, too.
+
Please note that this counts only for a single packet; if ticket *renewal*
runs into this limit (because the network was temporarily down), but the
ticket is still valid afterwards, a new renewal run will be started
automatically.
*'site-user'*, *'site-group'*, *'arbitrator-user'*, *'arbitrator-group'*::
These define the credentials 'boothd' will be running with.
+
On a (Pacemaker) site the booth process will have to call 'crm_ticket', so the
default is to use 'hacluster':'haclient'; for an arbitrator this user and group
might not exists, so that will default to 'nobody':'nobody'.
-*'before-acquire-handler'::
+*'before-acquire-handler'*::
If set, this script/program will be called before 'boothd' tries to
acquire or renew a ticket. Only a clean exit will allow 'boothd' to
proceed; any other return value will cancel the operation.
+
This makes it possible to check whether it makes sense to try
to acquire the ticket; eg. if a service in the
dependency-chain has a failcount of 'INFINITY' on all
available nodes, the service will be unable to run - and so
another cluster (and not this one!) should try to start it.
+
Please assume that 'boothd' will wait synchronously for the result of that
call, so having that program return quickly would be an advantage.
+
Please see below for details about available environment variables.
A more verbose example of a configuration file might be
-----------------------
transport = udp
port = 9930
# D-85774
site="192.168.201.100"
# D-90409
site="::ffff:192.168.202.100"
# A-1120
arbitrator="192.168.203.100"
ticket="I-want-a-pony"
expire = 600
acquire-after = 60
timeout = 10
retries = 5
-----------------------
NOTES
-----
Please note that Booth tickets are not meant to be real-time - a reasonable
'expire' time might be 300 seconds (5 minutes). Due to possible delays on the
WAN connections it makes no sense to expect detection of problems and failover
within a few seconds.
'booth' works with IPv6 addresses, too.
'booth' will start to renew a ticket before it expires, to account
for transmission delays.
This will happen so that (the bigger one of) half the 'expire' time, or
'timeout'*'retries'/2 seconds, will be left for the renewal.
Of course, that means that with bad configuration values (eg. 'expire' 60
seconds, 'timeout' 3 seconds, and 'retries' > 40) the ticket renewal
process will be started just after the ticket got acquired.
HANDLERS
--------
Currently, there's only one external handler defined (see the 'before-acquire-handler'
configuration item above).
It gets the following data via the environment:
*'BOOTH_TICKET'::
The ticket name, as given in the configuration file. (See 'ticket' item above.)
*'BOOTH_LOCAL'::
The local site specification, as defined in 'site'.
*'BOOTH_CONF_PATH'::
The path to the active configuration file.
*'BOOTH_CONF_NAME'::
The configuration name, as used by the '-c' commandline argument.
*'BOOTH_TICKET_EXPIRES'::
Timestamp for the ticket expiration (seconds since 1.1.1970), or '0'.
FILES
-----
*'/etc/booth/booth.conf'*::
The default configuration file name. See also the '-c' argument.
*'/var/run/booth/'*::
Directory that holds PID/lock files. See also the 'status' command.
+RAFT IMPLEMENTATION
+-------------------
+
+Basically, each Pacemaker ticket corresponds to a separate Raft cluster.
+
+A ticket is granted _only_ to the Raft _Leader_, but a Leader needs not grant the ticket to Pacemaker.
+To move a ticket, the Leader withdraws, and votes for the new Leader instead.
+
+So, the Raft "log" consists of -- nothing, more or less; there's no history to keep.
+
+
SYSTEMD INTEGRATION
-------------------
The Booth sources (and, very likely, packages too) include a 'systemd' unit
file for 'boothd'.
So don't forget to install 'boothd' into 'systemd' after configuration!
-----------
# systemctl enable booth@{configurationname}.service
# systemctl start booth@{configurationname}.service
-----------
EXIT STATUS
-----------
*0*::
Success. For the 'status' command: Daemon running.
*1* (PCMK_OCF_UNKNOWN_ERROR)::
General error code.
*7* (PCMK_OCF_NOT_RUNNING)::
No daemon process for that configuration active.
BUGS
----
Probably.
Please report them on GitHub: <https://github.com/ClusterLabs/booth/issues>
AUTHOR
------
'boothd' was originally written (mostly) by Jiaju Zhang.
Many people have contributed to it.
In 2013 Philipp Marek took over maintainership.
RESOURCES
---------
GitHub: <https://github.com/ClusterLabs/booth>
Documentation: <http://doc.opensuse.org/products/draft/SLE-HA/SLE-ha-guide_sd_draft/cha.ha.geo.html>
COPYING
-------
Copyright (C) 2011 Jiaju Zhang <jjzhang@suse.de>
Copyright (C) 2013-2014 Philipp Marek <philipp.marek@linbit.com>
Free use of this software is
granted under the terms of the GNU General Public License (GPL).
+// vim: set ft=asciidoc :
diff --git a/script/wireshark-dissector.lua b/script/wireshark-dissector.lua
index f227ac0..d45b7a5 100644
--- a/script/wireshark-dissector.lua
+++ b/script/wireshark-dissector.lua
@@ -1,67 +1,68 @@
-- dofile("wireshark-dissector.lua")
--
do
booth_proto = Proto("Booth","Booth")
function T32(tree, buffer, start, format)
local b = buffer(start, 4)
return tree:add(b, string.format(format, b:uint()))
end
function booth_proto.dissector(buffer, pinfo, tree)
local endbuf = buffer:len()
pinfo.cols.protocol = "Booth"
if (endbuf < 24) then
pinfo.cols.info = "Booth - too small"
else
local hdr = tree:add(booth_proto, buffer(0, 24), "Booth header")
local cmd = buffer(28, 4)
local tcmd = T32(hdr, cmd, 0, "Cmd %08x, \"" .. cmd:string() .. "\"");
local from = buffer(20, 4)
local tfrom = T32(hdr, from, 0, "From %08x");
if bit.band(from:uint(), 0x80000000) > 0 then
tfrom:add_expert_info(PI_PROTOCOL, PI_WARN, "Highest bit set")
end
local len = buffer(24, 4)
local tlen = T32(hdr, len, 0, "Length %8d");
if len:uint() > 1000 then
tlen:add_expert_info(PI_PROTOCOL, PI_WARN, "Length too big?")
end
T32(hdr, buffer, 32, "Result %08x");
T32(hdr, buffer, 12, "Magic %08x");
T32(hdr, buffer, 16, "Version %08x");
T32(hdr, buffer, 0, "IV %08x");
T32(hdr, buffer, 4, "Auth1 %08x");
T32(hdr, buffer, 8, "Auth2 %08x");
if (endbuf > 36) then
local tick = tree:add(booth_proto, buffer(36, endbuf-36), "Booth data")
local name = buffer(36, 64)
tick:add(name, "Ticket name: ", name:string())
- T32(tick, buffer, 36+64 + 0, "Owner: %08x")
- T32(tick, buffer, 36+64 + 4, "Ballot: %08x")
- T32(tick, buffer, 36+64 + 8, "Prev. Ballot: %08x")
- T32(tick, buffer, 36+64 + 12, "Expiry: %8d")
+ T32(tick, buffer, 36+64 + 0, "Leader: %08x")
+ T32(tick, buffer, 36+64 + 4, "Term: %08x")
+ T32(tick, buffer, 36+64 + 8, "Term valid for: %08x")
+ T32(tick, buffer, 36+64 + 12, "last Log index: %8d")
+ T32(tick, buffer, 36+64 + 16, "Leader commit: %8d")
end
pinfo.cols.info = "Booth, cmd " .. cmd:string()
end
tree:add(booth_proto, buffer(0, endbuf), "data")
end
local tbl = DissectorTable.get("udp.port")
tbl:add(9929, booth_proto)
local tbl = DissectorTable.get("tcp.port")
tbl:add(9929, booth_proto)
end
diff --git a/src/booth.h b/src/booth.h
index 737645a..0bb3f7c 100644
--- a/src/booth.h
+++ b/src/booth.h
@@ -1,231 +1,236 @@
/*
* 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.1 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 library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifndef _BOOTH_H
#define _BOOTH_H
#include <stdint.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#define BOOTH_RUN_DIR "/var/run/booth/"
#define BOOTH_LOG_DIR "/var/log"
#define BOOTH_LOGFILE_NAME "booth.log"
#define BOOTH_DEFAULT_CONF_DIR "/etc/booth/"
#define BOOTH_DEFAULT_CONF_NAME "booth"
#define BOOTH_DEFAULT_CONF_EXT ".conf"
#define BOOTH_DEFAULT_CONF \
BOOTH_DEFAULT_CONF_DIR BOOTH_DEFAULT_CONF_NAME BOOTH_DEFAULT_CONF_EXT
#define DAEMON_NAME "boothd"
#define BOOTH_PATH_LEN 127
#define BOOTH_DEFAULT_PORT 9929
/* TODO: remove */
#define BOOTH_PROTO_FAMILY AF_INET
#define BOOTHC_MAGIC 0x5F1BA08C
#define BOOTHC_VERSION 0x00010002
/** Timeout value for poll().
* Determines frequency of periodic jobs, eg. when send-retries are done.
* See process_tickets(). */
#define POLL_TIMEOUT 1000
/** @{ */
/** The on-network data structures and constants. */
#define BOOTH_NAME_LEN 64
-#define NO_OWNER (-1)
+/* NONE wouldn't be specific enough. */
+#define NO_ONE (-1)
typedef unsigned char boothc_site [BOOTH_NAME_LEN];
typedef unsigned char boothc_ticket[BOOTH_NAME_LEN];
struct boothc_header {
/** Authentication data; not used now. */
uint32_t iv;
uint32_t auth1;
uint32_t auth2;
/** BOOTHC_MAGIC */
uint32_t magic;
/** BOOTHC_VERSION */
uint32_t version;
/** Packet source; site_id. See add_site(). */
uint32_t from;
/** Length including header */
uint32_t length;
/** The command respectively protocol state. See cmd_request_t. */
uint32_t cmd;
/** Result of operation. 0 == OK */
uint32_t result;
char data[0];
} __attribute__((packed));
struct ticket_msg {
/** Ticket name. */
boothc_ticket id;
- /** Current leader. May be NO_OWNER. See add_site().
-* For a OP_REQ_VOTE this is */
+ /** Current leader. May be NO_ONE. See add_site().
+ * For a OP_REQ_VOTE this is */
uint32_t leader;
/** Current term. */
uint32_t term;
uint32_t term_valid_for;
+#if 0
union {
uint32_t prev_log_term;
uint32_t last_log_term;
};
+#endif
union {
uint32_t prev_log_index;
uint32_t last_log_index;
};
uint32_t leader_commit;
} __attribute__((packed));
struct boothc_ticket_msg {
struct boothc_header header;
struct ticket_msg ticket;
} __attribute__((packed));
#define CHAR2CONST(a,b,c,d) ((a << 24) | (b << 16) | (c << 8) | d)
#define STG2CONST(X) ({ const char _ggg[4] = X; return (uint32_t*)_ggg; })
typedef enum {
/* 0x43 = "C"ommands */
CMD_LIST = CHAR2CONST('C', 'L', 's', 't'),
CMD_GRANT = CHAR2CONST('C', 'G', 'n', 't'),
CMD_REVOKE = CHAR2CONST('C', 'R', 'v', 'k'),
/* Replies */
CMR_GENERAL = CHAR2CONST('G', 'n', 'l', 'R'), // Increase distance to CMR_GRANT
CMR_LIST = CHAR2CONST('R', 'L', 's', 't'),
CMR_GRANT = CHAR2CONST('R', 'G', 'n', 't'),
CMR_REVOKE = CHAR2CONST('R', 'R', 'v', 'k'),
/* Raft */
OP_REQ_VOTE = CHAR2CONST('R', 'V', 'o', 't'),
OP_VOTE_FOR = CHAR2CONST('V', 't', 'F', 'r'),
- OP_APP_ENTRY= CHAR2CONST('A', 'p', 'p', 'E'),
- OP_REJECTED = CHAR2CONST('R', 'J', 'C', '!'),
+ OP_HEARTBEAT= CHAR2CONST('H', 'r', 't', 'B'), /* AppendEntry in Raft */
+ OP_MY_INDEX = CHAR2CONST('M', 'I', 'd', 'x'), /* Answer to Heartbeat */
+ OP_REJECTED = CHAR2CONST('R', 'J', 'C', '!'),
} cmd_request_t;
/* TODO: make readable constants */
typedef enum {
/* for compatibility with other functions */
RLT_SUCCESS = 0,
RLT_ASYNC = CHAR2CONST('A', 's', 'y', 'n'),
RLT_SYNC_SUCC = CHAR2CONST('S', 'c', 'c', 's'),
RLT_SYNC_FAIL = CHAR2CONST('F', 'a', 'i', 'l'),
RLT_INVALID_ARG = CHAR2CONST('I', 'A', 'r', 'g'),
RLT_OVERGRANT = CHAR2CONST('O', 'v', 'e', 'r'),
RLT_PROBABLY_SUCCESS = CHAR2CONST('S', 'u', 'c', '?'),
RLT_BUSY = CHAR2CONST('B', 'u', 's', 'y'),
+ RLT_TERM_OUTDATED = CHAR2CONST('t', 'O', 'd', 'a'),
} cmd_result_t;
/** @} */
/** @{ */
struct booth_site {
/** Calculated ID. See add_site(). */
int site_id;
int type;
int local;
/** Roles, like ACCEPTOR, PROPOSER, or LEARNER. Not really used ATM. */
int role;
char addr_string[BOOTH_NAME_LEN];
int tcp_fd;
int udp_fd;
/* 0-based, used for indexing into per-ticket weights */
int index;
uint64_t bitmask;
unsigned short family;
union {
struct sockaddr_in sa4;
struct sockaddr_in6 sa6;
};
int saddrlen;
int addrlen;
} __attribute__((packed));
extern struct booth_site *local;
/** @} */
struct booth_transport;
struct client {
int fd;
const struct booth_transport *transport;
void (*workfn)(int);
void (*deadfn)(int);
};
extern struct client *clients;
extern struct pollfd *pollfds;
int client_add(int fd, const struct booth_transport *tpt,
void (*workfn)(int ci), void (*deadfn)(int ci));
int do_read(int fd, void *buf, size_t count);
int do_write(int fd, void *buf, size_t count);
void process_connection(int ci);
void safe_copy(char *dest, char *value, size_t buflen, const char *description);
struct command_line {
int type; /* ACT_ */
int op; /* OP_ */
char configfile[BOOTH_PATH_LEN];
char lockfile[BOOTH_PATH_LEN];
char site[BOOTH_NAME_LEN];
struct boothc_ticket_msg msg;
};
extern struct command_line cl;
#endif /* _BOOTH_H */
diff --git a/src/config.c b/src/config.c
index 373003f..a88df22 100644
--- a/src/config.c
+++ b/src/config.c
@@ -1,711 +1,711 @@
/*
* 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.1 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 library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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 "booth.h"
#include "config.h"
#include "raft.h"
#include "ticket.h"
#include "log.h"
static int ticket_size = 0;
static int ticket_realloc(void)
{
const int added = 5;
int had, want;
void *p;
had = booth_conf->ticket_allocated;
want = had + added;
p = realloc(booth_conf->ticket,
sizeof(struct ticket_config) * want);
if (!booth_conf) {
log_error("can't alloc more tickets");
return -ENOMEM;
}
booth_conf->ticket = p;
memset(booth_conf->ticket + had, 0,
sizeof(struct ticket_config) * added);
booth_conf->ticket_allocated = want;
return 0;
}
int add_site(char *address, int type);
int add_site(char *addr_string, int type)
{
int rv;
struct booth_site *site;
uLong nid;
uint32_t mask;
rv = 1;
if (booth_conf->site_count == MAX_NODES) {
log_error("too many nodes");
goto out;
}
if (strlen(addr_string)+1 >= sizeof(booth_conf->site[0].addr_string)) {
log_error("site address \"%s\" too long", addr_string);
goto out;
}
site = booth_conf->site + booth_conf->site_count;
site->family = BOOTH_PROTO_FAMILY;
site->type = type;
/* Make site_id start at a non-zero point.
* Perhaps use hash over string or address? */
strcpy(site->addr_string, addr_string);
nid = crc32(0L, NULL, 0);
/* booth_config() uses memset(), so sizeof() is guaranteed to give
* the same result everywhere - no uninitialized bytes. */
site->site_id = crc32(nid, site->addr_string,
sizeof(site->addr_string));
- /* Make sure we will never collide with NO_OWNER,
+ /* 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_OWNER & mask);
+ assert(NO_ONE & mask);
site->site_id &= ~mask;
site->index = booth_conf->site_count;
site->bitmask = 1 << booth_conf->site_count;
/* Catch site overflow */
assert(site->bitmask);
booth_conf->site_bits |= site->bitmask;
site->tcp_fd = -1;
booth_conf->site_count++;
rv = 0;
memset(&site->sa6, 0, sizeof(site->sa6));
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(booth_conf->port);
site->saddrlen = sizeof(site->sa4);
site->addrlen = sizeof(site->sa4.sin_addr);
} 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(booth_conf->port);
site->saddrlen = sizeof(site->sa6);
site->addrlen = sizeof(site->sa6.sin6_addr);
} else {
log_error("Address string \"%s\" is bad", site->addr_string);
rv = EINVAL;
}
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(const char *name, struct ticket_config **tkp,
const struct ticket_config *def)
{
int rv;
struct ticket_config *tk;
if (booth_conf->ticket_count == booth_conf->ticket_allocated) {
rv = ticket_realloc();
if (rv < 0)
return rv;
}
tk = booth_conf->ticket + booth_conf->ticket_count;
booth_conf->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(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->expiry = def->expiry;
+ tk->term_duration = def->term_duration;
tk->retries = def->retries;
memcpy(tk->weight, def->weight, sizeof(tk->weight));
tk->state = ST_INIT;
if (tkp)
*tkp = tk;
return 0;
}
/* 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;
}
int read_config(const char *path)
{
char line[1024];
FILE *fp;
char *s, *key, *val, *end_of_key;
const char *cp, *error;
int i;
int lineno = 0;
int got_transport = 0;
struct ticket_config defaults = { { 0 } };
struct ticket_config *last_ticket = NULL;
fp = fopen(path, "r");
if (!fp) {
log_error("failed to open %s: %s", path, strerror(errno));
return -1;
}
booth_conf = malloc(sizeof(struct booth_config)
+ TICKET_ALLOC * sizeof(struct ticket_config));
if (!booth_conf) {
log_error("failed to alloc memory for booth config");
return -ENOMEM;
}
memset(booth_conf, 0, sizeof(struct booth_config)
+ TICKET_ALLOC * sizeof(struct ticket_config));
ticket_size = TICKET_ALLOC;
booth_conf->proto = UDP;
booth_conf->port = BOOTH_DEFAULT_PORT;
/* Provide safe defaults. -1 is reserved, though. */
booth_conf->uid = -2;
booth_conf->gid = -2;
strcpy(booth_conf->site_user, "hacluster");
strcpy(booth_conf->site_group, "haclient");
strcpy(booth_conf->arb_user, "nobody");
strcpy(booth_conf->arb_group, "nobody");
parse_weights("", defaults.weight);
defaults.ext_verifier = NULL;
- defaults.expiry = DEFAULT_TICKET_EXPIRY;
+ defaults.term_duration = DEFAULT_TICKET_EXPIRY;
defaults.timeout = DEFAULT_TICKET_TIMEOUT;
defaults.retries = DEFAULT_RETRIES;
defaults.acquire_after = 0;
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))
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 (* skip_while(s, isspace)) {
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)
booth_conf->proto = UDP;
else if (strcasecmp(val, "SCTP") == 0)
booth_conf->proto = SCTP;
else {
error = "invalid transport protocol";
goto err;
}
got_transport = 1;
continue;
}
if (strcmp(key, "port") == 0) {
booth_conf->port = atoi(val);
continue;
}
if (strcmp(key, "name") == 0) {
safe_copy(booth_conf->name,
val, BOOTH_NAME_LEN,
"name");
continue;
}
if (strcmp(key, "site") == 0) {
if (add_site(val, SITE))
goto out;
continue;
}
if (strcmp(key, "arbitrator") == 0) {
if (add_site(val, ARBITRATOR))
goto out;
continue;
}
if (strcmp(key, "ticket") == 0) {
if (add_ticket(val, &last_ticket, &defaults))
goto out;
/* last_ticket is valid until another one is needed -
* and then it already has the new address and
* is valid again. */
continue;
}
if (strcmp(key, "expire") == 0) {
- defaults.expiry = strtol(val, &s, 0);
- if (*s || s == val || defaults.expiry<10) {
+ defaults.term_duration = strtol(val, &s, 0);
+ if (*s || s == val || defaults.term_duration<10) {
error = "Expected plain integer value >=10 for expire";
goto err;
}
if (last_ticket)
- last_ticket->expiry = defaults.expiry;
+ last_ticket->term_duration = defaults.term_duration;
continue;
}
if (strcmp(key, "site-user") == 0) {
safe_copy(booth_conf->site_user, optarg, BOOTH_NAME_LEN,
"site-user");
continue;
}
if (strcmp(key, "site-group") == 0) {
safe_copy(booth_conf->site_group, optarg, BOOTH_NAME_LEN,
"site-group");
continue;
}
if (strcmp(key, "arbitrator-user") == 0) {
safe_copy(booth_conf->arb_user, optarg, BOOTH_NAME_LEN,
"arbitrator-user");
continue;
}
if (strcmp(key, "arbitrator-group") == 0) {
safe_copy(booth_conf->arb_group, optarg, BOOTH_NAME_LEN,
"arbitrator-group");
continue;
}
if (strcmp(key, "timeout") == 0) {
defaults.timeout = strtol(val, &s, 0);
if (*s || s == val || defaults.timeout<1) {
error = "Expected plain integer value >=1 for timeout";
goto err;
}
if (last_ticket)
last_ticket->timeout = defaults.timeout;
continue;
}
if (strcmp(key, "retries") == 0) {
defaults.retries = strtol(val, &s, 0);
if (*s || s == val || defaults.retries<3 || defaults.retries > 100) {
error = "Expected plain integer value in the range [3, 100] for retries";
goto err;
}
if (last_ticket)
last_ticket->retries = defaults.retries;
continue;
}
if (strcmp(key, "acquire-after") == 0) {
defaults.acquire_after = strtol(val, &s, 0);
if (*s || s == val || defaults.acquire_after<0) {
error = "Expected plain integer value >=1 for acquire-after";
goto err;
}
if (last_ticket)
last_ticket->acquire_after = defaults.acquire_after;
continue;
}
if (strcmp(key, "before-acquire-handler") == 0) {
defaults.ext_verifier = strdup(val);
if (*s || s == val || defaults.timeout<1) {
error = "Expected plain integer value >=1 for timeout";
goto err;
}
if (last_ticket)
last_ticket->ext_verifier = defaults.ext_verifier;
continue;
}
if (strcmp(key, "weights") == 0) {
if (parse_weights(val, defaults.weight) < 0)
goto out;
if (last_ticket)
memcpy(last_ticket->weight, defaults.weight,
sizeof(last_ticket->weight));
continue;
}
error = "Unknown item";
goto out;
}
if ((booth_conf->site_count % 2) == 0) {
log_warn("An odd number of nodes is strongly recommended!");
}
/* Default: make config name match config filename. */
if (!booth_conf->name[0]) {
cp = strrchr(path, '/');
if (!cp)
cp = path;
/* TODO: locale? */
/* NUL-termination by memset. */
for(i=0; i<BOOTH_NAME_LEN-1 && isalnum(*cp); i++)
booth_conf->name[i] = *(cp++);
/* Last resort. */
if (!booth_conf->name[0])
strcpy(booth_conf->name, "booth");
}
return 0;
err:
out:
log_error("%s in config file line %d",
error, lineno);
free(booth_conf);
booth_conf = NULL;
return -1;
}
int check_config(int type)
{
struct passwd *pw;
struct group *gr;
char *cp, *input;
if (!booth_conf)
return -1;
input = (type == ARBITRATOR)
? booth_conf->arb_user
: booth_conf->site_user;
if (!*input)
goto u_inval;
if (isdigit(input[0])) {
booth_conf->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;
booth_conf->uid = pw->pw_uid;
}
input = (type == ARBITRATOR)
? booth_conf->arb_group
: booth_conf->site_group;
if (!*input)
goto g_inval;
if (isdigit(input[0])) {
booth_conf->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;
booth_conf->gid = gr->gr_gid;
}
/* TODO: check whether uid or gid is 0 again?
* The admin may shoot himself in the foot, though. */
return 0;
}
int find_site_by_name(unsigned char *site, struct booth_site **node, int any_type)
{
struct booth_site *n;
int i;
if (!booth_conf)
return 0;
for (i = 0; i < booth_conf->site_count; i++) {
n = booth_conf->site + i;
if ((n->type == SITE || any_type) &&
strcmp(n->addr_string, site) == 0) {
*node = n;
return 1;
}
}
return 0;
}
int find_site_by_id(uint32_t site_id, struct booth_site **node)
{
struct booth_site *n;
int i;
- if (site_id == NO_OWNER) {
+ if (site_id == NO_ONE) {
*node = NULL;
return 1;
}
if (!booth_conf)
return 0;
for (i = 0; i < booth_conf->site_count; i++) {
n = booth_conf->site + i;
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";
}
return "??invalid-type??";
}
diff --git a/src/config.h b/src/config.h
index 4f2505e..5806442 100644
--- a/src/config.h
+++ b/src/config.h
@@ -1,161 +1,174 @@
/*
* 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.1 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 library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifndef _CONFIG_H
#define _CONFIG_H
#include <stdint.h>
#include "booth.h"
#include "raft.h"
#include "transport.h"
/** @{ */
/** Definitions for in-RAM data. */
#define MAX_NODES 16
#define TICKET_ALLOC 16
struct ticket_config {
/** \name Configuration items.
* @{ */
/** Name of ticket. */
boothc_ticket name;
- /** How many seconds until expiration. */
- int expiry;
+ /** How many seconds a term lasts (if not refreshed). */
+ int term_duration;
/** Network related timeouts. */
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;
+ int acquire_after; /* TODO: needed? */
+#if 0
+#endif
+
/* Program to ask whether it makes sense to
* acquire the ticket */
char *ext_verifier;
/** Node weights. */
int weight[MAX_NODES];
/** @} */
/** \name Runtime values.
* @{ */
/** Current state. */
server_state_e state;
/** When something has to be done */
struct timeval next_cron;
/** Current leader. This is effectively the log[] in Raft. */
struct booth_site *leader;
- /** Timestamp of leadership expiration. */
+ /** Timestamp of leadership expiration */
time_t term_expires;
+ /** End of election period */
+ time_t election_end;
+ struct booth_site *voted_for;
- /** Last ballot number that was agreed on. */
+
+ /** 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;
/** @} */
/** */
uint32_t commit_index;
/** */
uint32_t last_applied;
uint32_t next_index[MAX_NODES];
uint32_t match_index[MAX_NODES];
/** \name Needed while proposals are being done.
* @{ */
/** Whom to vote for the next time.
* Needed to push a ticket to someone else. */
- struct booth_site *vote_for;
+
#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];
transport_layer_t proto;
uint16_t port;
/** Stores the OR of the individual host bitmasks. */
uint64_t site_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;
};
extern struct booth_config *booth_conf;
int read_config(const char *path);
int check_config(int type);
int find_site_by_name(unsigned char *site, struct booth_site **node, int any_type);
int find_site_by_id(uint32_t site_id, struct booth_site **node);
const char *type_to_string(int type);
#endif /* _CONFIG_H */
diff --git a/src/handler.c b/src/handler.c
index 8670385..fe0435b 100644
--- a/src/handler.c
+++ b/src/handler.c
@@ -1,68 +1,68 @@
/*
* Copyright (C) 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.1 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 library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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 "ticket.h"
#include "config.h"
#include "inline-fn.h"
#include "log.h"
#include "pacemaker.h"
#include "booth.h"
#include "handler.h"
/** Runs an external handler.
* See eg. 'before-acquire-handler'.
* TODO: timeout, async operation?. */
int run_handler(struct ticket_config *tk,
const char *cmd, int synchronous)
{
int rv;
char expires[16];
assert(synchronous);
- sprintf(expires, "%" PRId64, tk->expires);
+ sprintf(expires, "%" PRId64, tk->term_expires);
rv = setenv("BOOTH_TICKET", tk->name, 1) ||
setenv("BOOTH_LOCAL", local->addr_string, 1) ||
setenv("BOOTH_CONF_NAME", booth_conf->name, 1) ||
setenv("BOOTH_CONF_PATH", cl.configfile, 1) ||
setenv("BOOTH_TICKET_EXPIRES", expires, 1);
if (rv) {
log_error("Cannot set environment: %d", errno);
} else {
rv = system(cmd);
if (rv)
log_error("Error calling \"%s\": %s",
cmd, interpret_rv(rv));
else
log_info("Ran \"%s\" successfully.", cmd);
}
return rv;
}
diff --git a/src/inline-fn.h b/src/inline-fn.h
index e443b10..f28020c 100644
--- a/src/inline-fn.h
+++ b/src/inline-fn.h
@@ -1,281 +1,288 @@
/*
* 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.1 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 library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifndef _INLINE_FN_H
#define _INLINE_FN_H
#include <time.h>
#include <sys/time.h>
#include <assert.h>
#include <string.h>
#include "config.h"
+#include "ticket.h"
#include "transport.h"
inline static uint32_t get_local_id(void)
{
return local ? local->site_id : -1;
}
inline static uint32_t get_node_id(struct booth_site *node)
{
- return node ? node->site_id : NO_OWNER;
+ return node ? node->site_id : NO_ONE;
}
-inline static int ticket_valid_for(const struct ticket_config *tk)
+inline static int term_valid_for(const struct ticket_config *tk)
{
int left;
- left = tk->expires - time(NULL);
+ left = tk->term_expires - time(NULL);
return (left < 0) ? 0 : left;
}
/** Returns number of seconds left, if any. */
-inline static int owner_and_valid(const struct ticket_config *tk)
+inline static int leader_and_valid(const struct ticket_config *tk)
{
- if (tk->owner != local)
+ if (tk->leader != local)
return 0;
- return ticket_valid_for(tk);
+ return term_valid_for(tk);
}
+
static inline void init_header_bare(struct boothc_header *h) {
h->magic = htonl(BOOTHC_MAGIC);
h->version = htonl(BOOTHC_VERSION);
h->from = htonl(local->site_id);
h->iv = htonl(0);
h->auth1 = htonl(0);
h->auth2 = htonl(0);
}
static inline void init_header(struct boothc_header *h, int cmd,
int result, int data_len)
{
init_header_bare(h);
h->length = htonl(data_len);
h->cmd = htonl(cmd);
h->result = htonl(result);
}
static inline void init_ticket_site_header(struct boothc_ticket_msg *msg, int cmd)
{
init_header(&msg->header, cmd, 0, sizeof(*msg));
}
static inline void init_ticket_msg(struct boothc_ticket_msg *msg,
int cmd, int rv,
struct ticket_config *tk)
{
assert(sizeof(msg->ticket.id) == sizeof(tk->name));
init_header(&msg->header, cmd, rv, sizeof(*msg));
if (!tk) {
memset(&msg->ticket, 0, sizeof(msg->ticket));
} else {
memcpy(msg->ticket.id, tk->name, sizeof(msg->ticket.id));
- msg->ticket.expiry = htonl(ticket_valid_for(tk));
- msg->ticket.owner = htonl(get_node_id(tk->owner));
- msg->ticket.ballot = htonl(tk->new_ballot);
- msg->ticket.prev_ballot = htonl(tk->last_ack_ballot);
+ msg->ticket.leader = htonl(get_node_id(tk->leader ?: tk->voted_for));
+ msg->ticket.term = htonl(tk->current_term);
+ msg->ticket.term_valid_for = htonl(term_valid_for(tk));
+
+ msg->ticket.prev_log_index = htonl(tk->last_applied);
+ msg->ticket.leader_commit = htonl(tk->commit_index);
}
}
static inline struct booth_transport const *transport(void)
{
return booth_transport + booth_conf->proto;
}
-static inline const char *ticket_owner_string(struct booth_site *site)
+static inline const char *site_string(struct booth_site *site)
{
return site ? site->addr_string : "NONE";
}
-static inline void disown_ticket(struct ticket_config *tk)
+static inline const char *ticket_leader_string(struct ticket_config *tk)
{
- /* ONLY the "current state" is changed;
- * current paxos rounds should not be affected.
- * tk->proposed_owner = NULL;
- */
- tk->owner = NULL;
- time(&tk->expires);
+ return site_string(tk->leader);
}
-static inline void disown_if_expired(struct ticket_config *tk)
-{
- if (time(NULL) >= tk->expires ||
- (!tk->proposed_owner && !tk->owner))
- disown_ticket(tk);
-}
-
-static inline int all_agree(struct ticket_config *tk)
+static inline void disown_ticket(struct ticket_config *tk)
{
- return tk->proposal_acknowledges == booth_conf->site_bits;
+ tk->leader = NULL;
+ time(&tk->term_expires);
}
-static inline int majority_agree(struct ticket_config *tk)
+static inline int disown_if_expired(struct ticket_config *tk)
{
- /* Use ">" to get majority decision, even for an even number
- * of participants. */
- return __builtin_popcount(tk->proposal_acknowledges) * 2 >
- booth_conf->site_count;
-}
+ if (time(NULL) >= tk->term_expires ||
+ !tk->leader) {
+ disown_ticket(tk);
+ return 1;
+ }
+ return 0;
+}
/* We allow half of the uint32_t to be used;
* half of that below, half of that above the current known "good" value.
* 0 UINT32_MAX
* |--------------------------+----------------+------------|
* | | |
* |--------+-------| allowed range
* |
- * current ballot
+ * current commit index
*
* So, on overflow it looks like that:
* UINT32_MAX 0
* |--------------------------+-----------||---+------------|
* | | |
* |--------+-------| allowed range
* |
- * current ballot
+ * current commit index
*
* This should be possible by using the same datatype and relying
* on the under/overflow semantics.
*
*
* Having 30 bits available, and assuming an expire time of
- * one minute and a (high) ballot step of 64 == 2^6 (because
+ * one minute and a (high) commit index step of 64 == 2^6 (because
* of weights), we get 2^24 minutes of range - which is ~750
* years. "Should be enough for everybody."
*/
-static inline int ballot_is_higher_than(uint32_t b_high, uint32_t b_low)
+static inline int index_is_higher_than(uint32_t c_high, uint32_t c_low)
{
uint32_t diff;
- if (b_high == b_low)
+ if (c_high == c_low)
return 0;
- diff = b_high - b_low;
+ diff = c_high - c_low;
if (diff < UINT32_MAX/4)
return 1;
- diff = b_low - b_high;
+ diff = c_low - c_high;
if (diff < UINT32_MAX/4)
return 0;
- assert(!"ballot out of range - invalid");
+ assert(!"commit index out of range - invalid");
}
-static inline uint32_t ballot_max2(uint32_t a, uint32_t b)
+static inline uint32_t index_max2(uint32_t a, uint32_t b)
{
- return ballot_is_higher_than(a, b) ? a : b;
+ return index_is_higher_than(a, b) ? a : b;
}
-static inline uint32_t ballot_max3(uint32_t a, uint32_t b, uint32_t c)
+static inline uint32_t index_max3(uint32_t a, uint32_t b, uint32_t c)
{
- return ballot_max2( ballot_max2(a, b), c);
+ return index_max2( index_max2(a, b), c);
}
static inline double timeval_to_float(struct timeval tv)
{
return tv.tv_sec + tv.tv_usec*(double)1.0e-6;
}
static inline int timeval_msec(struct timeval tv)
{
int m;
m = tv.tv_usec / 1000;
if (m >= 1000)
m = 999;
return m;
}
static inline int timeval_compare(struct timeval tv1, struct timeval tv2)
{
if (tv1.tv_sec < tv2.tv_sec)
return -1;
if (tv1.tv_sec > tv2.tv_sec)
return +1;
if (tv1.tv_usec < tv2.tv_usec)
return -1;
if (tv1.tv_usec > tv2.tv_usec)
return +1;
return 0;
}
static inline int timeval_in_past(struct timeval which)
{
struct timeval tv;
gettimeofday(&tv, NULL);
return timeval_compare(tv, which) > 0;
}
-static inline time_t next_renewal_starts_at(struct ticket_config *tk)
+static inline time_t next_vote_starts_at(struct ticket_config *tk)
{
time_t half_exp, retries_needed;
/* If not owner, don't renew. */
- if (tk->owner != local)
+ if (tk->leader != local)
return 0;
/* Try to renew at half of expiry time. */
- half_exp = tk->expires - tk->expiry/2;
+ half_exp = tk->term_expires - tk->term_duration/2;
/* Also start renewal if we couldn't get
* a few message retransmission in the alloted
* expiry time. */
- retries_needed = tk->expires - tk->timeout * tk->retries/2;
+ retries_needed = tk->term_expires - tk->timeout * tk->retries/2;
/* Return earlier timestamp. */
return half_exp < retries_needed
? half_exp
: retries_needed;
}
static inline int should_start_renewal(struct ticket_config *tk)
{
time_t now, when;
- when = next_renewal_starts_at(tk);
+ when = next_vote_starts_at(tk);
if (!when)
return 0;
time(&now);
return when <= now;
}
+static inline int send_heartbeat(struct ticket_config *tk)
+{
+ return ticket_broadcast(tk, OP_HEARTBEAT, RLT_SUCCESS);
+}
+
+static inline struct booth_site *my_vote(struct ticket_config *tk)
+{
+ return tk->votes_for[ local->index ];
+}
+
+
+
#endif
diff --git a/src/pacemaker.c b/src/pacemaker.c
index c371744..27d42d2 100644
--- a/src/pacemaker.c
+++ b/src/pacemaker.c
@@ -1,326 +1,326 @@
/*
* 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.1 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 library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <inttypes.h>
#include <sys/types.h>
#include <sys/wait.h>
#include "log.h"
#include "pacemaker.h"
#include "inline-fn.h"
enum atomic_ticket_supported {
YES=0,
NO,
FILENOTFOUND, /* Ie. UNKNOWN */
UNKNOWN = FILENOTFOUND,
};
/* http://thedailywtf.com/Articles/What_Is_Truth_0x3f_.aspx */
enum atomic_ticket_supported atomicity = UNKNOWN;
#define COMMAND_MAX 1024
/** Determines whether the installed crm_ticket can do atomic ticket grants,
* _including_ multiple attribute changes.
*
* See
* https://bugzilla.novell.com/show_bug.cgi?id=855099
*
* Run "crm_ticket" without "--force";
* - the old version asks for "Y/N" via STDIN, and returns 0
* when reading "no";
* - the new version just reports an error without asking.
*/
static void test_atomicity(void)
{
int rv;
if (atomicity != UNKNOWN)
return;
rv = system("echo n | crm_ticket -g -t any-ticket-name > /dev/null 2> /dev/null");
if (rv == -1) {
log_error("Cannot run \"crm_ticket\"!");
/* BIG problem. Abort. */
exit(1);
}
if (WIFSIGNALED(rv)) {
log_error("\"crm_ticket\" terminated by a signal!");
/* Problem. Abort. */
exit(1);
}
switch (WEXITSTATUS(rv)) {
case 0:
atomicity = NO;
log_info("Old \"crm_ticket\" found, using non-atomic ticket updates.");
break;
case 1:
atomicity = YES;
log_info("New \"crm_ticket\" found, using atomic ticket updates.");
break;
default:
log_error("Unexpected return value from \"crm_ticket\" (%d), "
"falling back to non-atomic ticket updates.",
rv);
atomicity = NO;
}
assert(atomicity == YES || atomicity == NO);
}
const char * interpret_rv(int rv)
{
static char text[64];
int p;
if (rv == 0)
return "0";
p = sprintf(text, "rv %d", WEXITSTATUS(rv));
if (WIFSIGNALED(rv))
sprintf(text + p, " signal %d", WTERMSIG(rv));
return text;
}
static int pcmk_write_ticket_atomic(struct ticket_config *tk, int grant)
{
char cmd[COMMAND_MAX];
int rv;
- /* The values are appended to "-v", so that NO_OWNER
+ /* The values are appended to "-v", so that NO_ONE
* (which is -1) isn't seen as another option. */
snprintf(cmd, COMMAND_MAX,
"crm_ticket -t '%s' "
"%s --force "
"-S owner -v%" PRIi32 " "
"-S expires -v%" PRIi64 " "
- "-S ballot -v%" PRIi64,
+ "-S term -v%" PRIi64,
tk->name,
(grant > 0 ? "-g" :
grant < 0 ? "-r" :
""),
- (int32_t)get_node_id(tk->owner),
- (int64_t)tk->expires,
- (int64_t)tk->last_ack_ballot);
+ (int32_t)get_node_id(tk->leader),
+ (int64_t)tk->term_expires,
+ (int64_t)tk->current_term);
rv = system(cmd);
log_info("command: '%s' was executed", cmd);
if (rv != 0)
log_error("error: \"%s\" failed, %s", cmd, interpret_rv(rv));
return rv;
}
static int pcmk_store_ticket_nonatomic(struct ticket_config *tk);
static int pcmk_grant_ticket(struct ticket_config *tk)
{
char cmd[COMMAND_MAX];
int rv;
test_atomicity();
if (atomicity == YES)
return pcmk_write_ticket_atomic(tk, +1);
rv = pcmk_store_ticket_nonatomic(tk);
if (rv)
return rv;
snprintf(cmd, COMMAND_MAX, "crm_ticket -t %s -g --force",
tk->name);
log_info("command: '%s' was executed", cmd);
rv = system(cmd);
if (rv != 0)
log_error("error: \"%s\" failed, %s", cmd, interpret_rv(rv));
return rv;
}
static int pcmk_revoke_ticket(struct ticket_config *tk)
{
char cmd[COMMAND_MAX];
int rv;
test_atomicity();
if (atomicity == YES)
return pcmk_write_ticket_atomic(tk, -1);
rv = pcmk_store_ticket_nonatomic(tk);
if (rv)
return rv;
snprintf(cmd, COMMAND_MAX, "crm_ticket -t %s -r --force",
tk->name);
log_info("command: '%s' was executed", cmd);
rv = system(cmd);
if (rv != 0)
log_error("error: \"%s\" failed, %s", cmd, interpret_rv(rv));
return rv;
}
static int crm_ticket_set(const struct ticket_config *tk, const char *attr, int64_t val)
{
char cmd[COMMAND_MAX];
int i, rv;
snprintf(cmd, COMMAND_MAX,
"crm_ticket -t '%s' -S '%s' -v %" PRIi64,
tk->name, attr, val);
/* If there are errors, there's not much we can do but retry ... */
for (i=0; i<3 &&
(rv = system(cmd));
i++) ;
log_debug("'%s' gave result %s", cmd, interpret_rv(rv));
return rv;
}
static int pcmk_store_ticket_nonatomic(struct ticket_config *tk)
{
int rv;
/* Always try to store *each* attribute, even if there's an error
* for one of them. */
- rv = crm_ticket_set(tk, "owner", (int32_t)get_node_id(tk->owner));
- rv = crm_ticket_set(tk, "expires", tk->expires) || rv;
- rv = crm_ticket_set(tk, "ballot", tk->last_ack_ballot) || rv;
+ rv = crm_ticket_set(tk, "owner", (int32_t)get_node_id(tk->leader));
+ rv = crm_ticket_set(tk, "expires", tk->term_expires) || rv;
+ rv = crm_ticket_set(tk, "term", tk->current_term) || rv;
if (rv)
log_error("setting crm_ticket attributes failed; %s",
interpret_rv(rv));
else
log_info("setting crm_ticket attributes successful");
return rv;
}
static int crm_ticket_get(struct ticket_config *tk,
const char *attr, int64_t *data)
{
char cmd[COMMAND_MAX];
char line[256];
int rv;
int64_t v;
FILE *p;
*data = -1;
v = 0;
snprintf(cmd, COMMAND_MAX,
"crm_ticket -t '%s' -G '%s' --quiet",
tk->name, attr);
p = popen(cmd, "r");
if (p == NULL) {
rv = errno;
log_error("popen error %d (%s) for \"%s\"",
rv, strerror(rv), cmd);
return rv || -EINVAL;
}
if (fgets(line, sizeof(line) - 1, p) == NULL) {
rv = ENODATA;
goto out;
}
rv = EINVAL;
if (sscanf(line, "%" PRIi64, &v) == 1)
rv = 0;
*data = v;
out:
rv = pclose(p);
log_debug("command \"%s\" returned %s, value %" PRIi64, cmd, interpret_rv(rv), v);
return rv;
}
static int pcmk_load_ticket(struct ticket_config *tk)
{
int rv;
int64_t v;
/* This here gets run during startup; testing that here means that
* normal operation won't be interrupted with that test. */
test_atomicity();
rv = crm_ticket_get(tk, "expires", &v);
if (!rv) {
- tk->expires = v;
+ tk->term_expires = v;
}
- rv = crm_ticket_get(tk, "ballot", &v);
+ rv = crm_ticket_get(tk, "term", &v);
if (!rv) {
- tk->new_ballot =
- tk->last_ack_ballot = v;
+ tk->current_term = v;
}
rv = crm_ticket_get(tk, "owner", &v);
if (!rv) {
/* No check, node could have been deconfigured. */
- find_site_by_id(v, &tk->proposed_owner);
+ find_site_by_id(v, &tk->leader);
}
- disown_if_expired(tk);
+ if (disown_if_expired(tk))
+ pcmk_revoke_ticket(tk);
- tk->proposal_acknowledges = local->bitmask;
+// tk->proposal_acknowledges = local->bitmask;
/* We load only when the state is completely unknown. */
tk->state = ST_INIT;
return rv;
}
struct ticket_handler pcmk_handler = {
.grant_ticket = pcmk_grant_ticket,
.revoke_ticket = pcmk_revoke_ticket,
.load_ticket = pcmk_load_ticket,
};
diff --git a/src/raft.c b/src/raft.c
new file mode 100644
index 0000000..3d29e1a
--- /dev/null
+++ b/src/raft.c
@@ -0,0 +1,290 @@
+/*
+ * Copyright (C) 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.1 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 library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <stdlib.h>
+#include <inttypes.h>
+#include <string.h>
+#include <errno.h>
+#include <arpa/inet.h>
+#include "booth.h"
+#include "transport.h"
+#include "inline-fn.h"
+#include "config.h"
+#include "raft.h"
+#include "ticket.h"
+#include "log.h"
+
+
+
+inline static void clear_election(struct ticket_config *tk)
+{
+ int i;
+ struct booth_site *site;
+
+ log_info("clear election");
+ tk->votes_received = 0;
+ foreach_node(i, site)
+ tk->votes_for[site->index] = NULL;
+}
+
+
+inline static void site_voted_for(struct ticket_config *tk,
+ struct booth_site *who,
+ struct booth_site *vote)
+{
+ log_info("site \"%s\" votes for \"%s\"",
+ who->addr_string,
+ vote->addr_string);
+
+ if (!tk->votes_for[who->index]) {
+ tk->votes_for[who->index] = vote;
+ tk->votes_received |= who->bitmask;
+ } else {
+ if (tk->votes_for[who->index] != vote)
+ log_error("voted previously (but in same term!) for \"%s\"...",
+ tk->votes_for[who->index]->addr_string);
+ }
+}
+
+
+static struct booth_site *majority_votes(struct ticket_config *tk)
+{
+ int i, n;
+ struct booth_site *v;
+ int count[MAX_NODES] = { 0, };
+
+
+ for(i=0; i<booth_conf->site_count; i++) {
+ v = tk->votes_for[i];
+ if (!v)
+ continue;
+
+ n = v->index;
+ count[n]++;
+ log_info("Majority: %d \"%s\" wants %d \"%s\" => %d",
+ i, booth_conf->site[i].addr_string,
+ n, v->addr_string,
+ count[n]);
+
+ if (count[n]*2 <= booth_conf->site_count)
+ continue;
+
+
+ log_info("Majority reached: %d of %d for \"%s\"",
+ count[n], booth_conf->site_count,
+ v->addr_string);
+ return v;
+ }
+
+ return NULL;
+}
+
+static int answer_HEARTBEAT (
+ struct ticket_config *tk,
+ struct booth_site *sender,
+ struct booth_site *leader,
+ struct boothc_ticket_msg *msg
+ )
+{
+ uint32_t term;
+ uint32_t index;
+
+ term = ntohl(msg->ticket.term);
+ log_debug("leader: %s, have %s; term %d vs %d",
+ site_string(leader), ticket_leader_string(tk),
+ term, tk->current_term);
+ if (term < tk->current_term)
+ return 0; //send_reject(sender, tk, RLT_TERM_OUTDATED);
+
+ /* § 5.3 */
+ index = ntohl(msg->ticket.leader_commit);
+ if (index > tk->commit_index)
+ tk->commit_index = index;
+
+ assert(tk->leader == leader);
+
+
+ return 0;
+}
+
+
+static int process_VOTE_FOR(
+ struct ticket_config *tk,
+ struct booth_site *sender,
+ struct booth_site *leader,
+ struct boothc_ticket_msg *msg
+ )
+{
+ uint32_t term;
+ struct booth_site *new_leader;
+
+
+ term = ntohl(msg->ticket.term);
+ if (term < tk->current_term)
+ return send_reject(sender, tk, RLT_TERM_OUTDATED);
+
+ if (term > tk->current_term)
+ clear_election(tk);
+
+ site_voted_for(tk, sender, leader);
+
+
+ /* §5.2 */
+ new_leader = majority_votes(tk);
+ if (new_leader) {
+ tk->leader = new_leader;
+
+ if ( new_leader == local) {
+ tk->current_term++;
+ tk->state = ST_LEADER;
+ send_heartbeat(tk);
+ tk->voted_for = NULL;
+ }
+ else
+ tk->state = ST_FOLLOWER;
+
+ }
+
+ set_ticket_wakeup(tk);
+ return 0;
+}
+
+
+static int process_REJECTED(
+ struct ticket_config *tk,
+ struct booth_site *sender,
+ struct booth_site *leader,
+ struct boothc_ticket_msg *msg
+ )
+{
+ return 0;
+}
+
+
+/* §5.2 */
+static int answer_REQ_VOTE(
+ struct ticket_config *tk,
+ struct booth_site *sender,
+ struct booth_site *leader,
+ struct boothc_ticket_msg *msg
+ )
+{
+ uint32_t term;
+ struct boothc_ticket_msg omsg;
+
+
+ term = ntohl(msg->ticket.term);
+
+ /* §5.1 */
+ if (term < tk->current_term)
+ return send_reject(sender, tk, RLT_TERM_OUTDATED);
+
+ /* §5.2, §5.4 */
+ if (!tk->voted_for &&
+ ntohl(msg->ticket.last_log_index) >= tk->last_applied) {
+ tk->voted_for = sender;
+ site_voted_for(tk, sender, leader);
+ goto yes_you_can;
+ }
+
+
+yes_you_can:
+ init_ticket_msg(&omsg, OP_VOTE_FOR, RLT_SUCCESS, tk);
+ omsg.ticket.leader = htonl(get_node_id(tk->voted_for));
+
+ return transport()->broadcast(&omsg, sizeof(omsg));
+}
+
+
+int new_election(struct ticket_config *tk, struct booth_site *preference)
+{
+ struct booth_site *new_leader;
+ time_t now;
+
+
+ time(&now);
+ log_debug("start new election?, now=%" PRIi64 ", end %" PRIi64,
+ now, tk->election_end);
+ if (now <= tk->election_end)
+ return 0;
+
+
+ /* §5.2 */
+ tk->current_term++;
+ tk->election_end = now + tk->term_duration;
+
+ log_debug("start new election! term=%d, until %" PRIi64,
+ tk->current_term, tk->election_end);
+ clear_election(tk);
+
+ if(preference)
+ new_leader = preference;
+ else
+ new_leader = (local->type == SITE) ? local : NULL;
+ site_voted_for(tk, local, new_leader);
+ tk->voted_for = new_leader;
+
+ tk->state = ST_CANDIDATE;
+
+ ticket_broadcast(tk, OP_REQ_VOTE, RLT_SUCCESS);
+ return 0;
+}
+
+
+int raft_answer(
+ struct ticket_config *tk,
+ struct booth_site *from,
+ struct booth_site *leader,
+ struct boothc_ticket_msg *msg
+ )
+{
+ int cmd;
+ uint32_t term;
+
+ cmd = ntohl(msg->header.cmd);
+ term = ntohl(msg->ticket.term);
+
+ log_debug("got message %s from \"%s\", term %d vs. %d",
+ state_to_string(cmd),
+ from->addr_string,
+ term, tk->current_term);
+
+ /* §5.1 */
+ if (term > tk->current_term) {
+ tk->state = ST_FOLLOWER;
+ tk->current_term = term;
+ tk->leader = leader;
+ log_info("higher term %d vs. %d, following \"%s\"",
+ term, tk->current_term,
+ ticket_leader_string(tk));
+ }
+
+
+ switch (cmd) {
+ case OP_REQ_VOTE:
+ return answer_REQ_VOTE (tk, from, leader, msg);
+ case OP_VOTE_FOR:
+ return process_VOTE_FOR(tk, from, leader, msg);
+ case OP_HEARTBEAT:
+ return answer_HEARTBEAT(tk, from, leader, msg);
+ case OP_REJECTED:
+ return process_REJECTED(tk, from, leader, msg);
+ }
+ log_error("unprocessed message, cmd %x", cmd);
+ return -EINVAL;
+}
diff --git a/src/raft.h b/src/raft.h
new file mode 100644
index 0000000..3f03196
--- /dev/null
+++ b/src/raft.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 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.1 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 library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef _RAFT_H
+#define _RAFT_H
+
+#include "booth.h"
+
+
+typedef enum {
+ ST_INIT = CHAR2CONST('I', 'n', 'i', 't'),
+ ST_FOLLOWER = CHAR2CONST('F', 'l', 'l', 'w'),
+ ST_CANDIDATE = CHAR2CONST('C', 'n', 'd', 'i'),
+ ST_LEADER = CHAR2CONST('L', 'e', 'a', 'd'),
+} server_state_e;
+
+
+struct ticket_config;
+
+int raft_answer(struct ticket_config *tk,
+ struct booth_site *from,
+ struct booth_site *leader,
+ struct boothc_ticket_msg *msg);
+
+int new_election(struct ticket_config *tk, struct booth_site *new_leader);
+int start_election(struct ticket_config *tk, struct booth_site *new_leader);
+
+
+#endif /* _RAFT_H */
diff --git a/src/ticket.c b/src/ticket.c
index 1ff01aa..16aaf72 100644
--- a/src/ticket.c
+++ b/src/ticket.c
@@ -1,799 +1,818 @@
/*
* 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.1 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 library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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 "ticket.h"
#include "config.h"
#include "pacemaker.h"
#include "inline-fn.h"
#include "log.h"
#include "booth.h"
#include "raft.h"
#include "handler.h"
#define TK_LINE 256
/* Untrusted input, must fit (incl. \0) in a buffer of max chars. */
int check_max_len_valid(const char *s, int max)
{
int i;
for(i=0; i<max; i++)
if (s[i] == 0)
return 1;
return 0;
}
int find_ticket_by_name(const char *ticket, struct ticket_config **found)
{
int i;
if (found)
*found = NULL;
for (i = 0; i < booth_conf->ticket_count; i++) {
if (!strcmp(booth_conf->ticket[i].name, ticket)) {
if (found)
*found = booth_conf->ticket + i;
return 1;
}
}
return 0;
}
int check_ticket(char *ticket, struct ticket_config **found)
{
if (found)
*found = NULL;
if (!booth_conf)
return 0;
if (!check_max_len_valid(ticket, sizeof(booth_conf->ticket[0].name)))
return 0;
return find_ticket_by_name(ticket, found);
}
int check_site(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(site, &node, 0)) {
*is_local = node->local;
return 1;
}
return 0;
}
+#if 0
/** Find out what others think about this ticket.
*
* If we're a SITE, we can ask (and have to tell) Pacemaker.
* An ARBITRATOR can only ask others. */
static int ticket_send_catchup(struct ticket_config *tk)
{
int i, rv = 0;
struct booth_site *site;
struct boothc_ticket_msg msg;
foreach_node(i, site) {
if (!site->local) {
init_ticket_msg(&msg, CMD_CATCHUP, RLT_SUCCESS, tk);
log_debug("attempting catchup from %s", site->addr_string);
rv = booth_udp_send(site, &msg, sizeof(msg));
}
}
ticket_activate_timeout(tk);
return rv;
}
+#endif
int ticket_write(struct ticket_config *tk)
{
if (local->type != SITE)
return -EINVAL;
disown_if_expired(tk);
- if (tk->owner == local) {
+ if (tk->leader == local) {
pcmk_handler.grant_ticket(tk);
} else {
pcmk_handler.revoke_ticket(tk);
}
return 0;
}
/* Ask an external program whether getting the ticket
* makes sense.
* Eg. if the services have a failcount of INFINITY,
* we can't serve here anyway. */
int get_ticket_locally_if_allowed(struct ticket_config *tk)
{
int rv;
if (!tk->ext_verifier)
goto get_it;
rv = run_handler(tk, tk->ext_verifier, 1);
if (rv) {
log_error("May not acquire ticket.");
/* Give it to somebody else.
* Just send a commit message, as the
* others couldn't help anyway. */
- if (owner_and_valid(tk)) {
+ if (leader_and_valid(tk)) {
disown_ticket(tk);
+#if 0
tk->proposed_owner = NULL;
/* Just go one further - others may easily override. */
tk->new_ballot++;
ticket_broadcast_proposed_state(tk, OP_COMMITTED);
tk->state = ST_STABLE;
+#endif
+ ticket_broadcast(tk, OP_VOTE_FOR, RLT_SUCCESS);
}
return rv;
} else {
log_info("May keep ticket.");
}
get_it:
+ if (leader_and_valid(tk)) {
+ return send_heartbeat(tk);
+ }
+ else {
+ new_election(tk, local);
+ return ticket_broadcast(tk, OP_REQ_VOTE, RLT_SUCCESS);
+ }
+#if 0
return paxos_start_round(tk, local);
+#endif
}
/** Try to get the ticket for the local site.
* */
int do_grant_ticket(struct ticket_config *tk)
{
int rv;
- if (tk->owner == local)
+ if (tk->leader == local)
return RLT_SUCCESS;
- if (tk->owner)
+ if (tk->leader)
return RLT_OVERGRANT;
rv = get_ticket_locally_if_allowed(tk);
return rv;
}
/** Start a PAXOS round for revoking.
* That can be started from any site. */
int do_revoke_ticket(struct ticket_config *tk)
{
int rv;
- if (!tk->owner)
+ if (!tk->leader)
return RLT_SUCCESS;
+ disown_ticket(tk);
+ ticket_write(tk);
+ return ticket_broadcast(tk, OP_REQ_VOTE, RLT_SUCCESS);
+#if 0
rv = paxos_start_round(tk, NULL);
+#endif
return rv;
}
int list_ticket(char **pdata, unsigned int *len)
{
struct ticket_config *tk;
char timeout_str[64];
char *data, *cp;
int i, alloc;
*pdata = NULL;
*len = 0;
alloc = 256 +
booth_conf->ticket_count * (BOOTH_NAME_LEN * 2 + 128);
data = malloc(alloc);
if (!data)
return -ENOMEM;
cp = data;
foreach_ticket(i, tk) {
- if (tk->expires != 0)
+ if (tk->term_expires != 0)
strftime(timeout_str, sizeof(timeout_str), "%F %T",
- localtime(&tk->expires));
+ localtime(&tk->term_expires));
else
strcpy(timeout_str, "INF");
cp += sprintf(cp,
- "ticket: %s, owner: %s, expires: %s, ballot: %d\n",
+ "ticket: %s, leader: %s, expires: %s, commit: %d\n",
tk->name,
- tk->owner ? tk->owner->addr_string : "None",
+ ticket_leader_string(tk),
timeout_str,
- tk->last_ack_ballot);
+ tk->commit_index);
*len = cp - data;
assert(*len < alloc);
}
*pdata = data;
return 0;
}
int setup_ticket(void)
{
struct ticket_config *tk;
int i;
/* TODO */
foreach_ticket(i, tk) {
- tk->owner = NULL;
- tk->expires = 0;
+ tk->leader = NULL;
+ tk->term_expires = 0;
- abort_proposal(tk);
+// abort_proposal(tk);
- if (local->role & PROPOSER) {
+ if (local->type == SITE) {
pcmk_handler.load_ticket(tk);
}
}
return 0;
}
int ticket_answer_list(int fd, struct boothc_ticket_msg *msg)
{
char *data;
int olen, rv;
struct boothc_header hdr;
rv = list_ticket(&data, &olen);
if (rv < 0)
return rv;
init_header(&hdr, CMR_LIST, RLT_SUCCESS, sizeof(hdr) + olen);
return send_header_plus(fd, &hdr, data, olen);
}
int ticket_answer_grant(int fd, struct boothc_ticket_msg *msg)
{
int rv;
struct ticket_config *tk;
if (!check_ticket(msg->ticket.id, &tk)) {
log_error("Client asked to grant unknown ticket");
rv = RLT_INVALID_ARG;
goto reply;
}
- if (tk->owner) {
+ if (tk->leader) {
log_error("client wants to get an (already granted!) ticket \"%s\"",
msg->ticket.id);
rv = RLT_OVERGRANT;
goto reply;
}
rv = do_grant_ticket(tk);
reply:
init_header(&msg->header, CMR_GRANT, rv ?: RLT_ASYNC, sizeof(*msg));
return send_ticket_msg(fd, msg);
}
int ticket_answer_revoke(int fd, struct boothc_ticket_msg *msg)
{
int rv;
struct ticket_config *tk;
if (!check_ticket(msg->ticket.id, &tk)) {
log_error("Client asked to grant unknown ticket");
rv = RLT_INVALID_ARG;
goto reply;
}
- if (!tk->owner) {
+ if (!tk->leader) {
log_info("client wants to revoke a free ticket \"%s\"",
msg->ticket.id);
/* Return a different result code? */
rv = RLT_SUCCESS;
goto reply;
}
rv = do_revoke_ticket(tk);
if (rv == 0)
rv = RLT_ASYNC;
reply:
init_ticket_msg(msg, CMR_REVOKE, rv, tk);
return send_ticket_msg(fd, msg);
}
+#if 0
/** Got a CMD_CATCHUP query.
* In this file because it's mostly used during startup. */
static int ticket_answer_catchup(
struct ticket_config *tk,
struct booth_site *from,
struct boothc_ticket_msg *msg,
uint32_t ballot,
struct booth_site *new_owner)
{
int rv;
log_debug("got CATCHUP query for \"%s\" from %s",
msg->ticket.id, from->addr_string);
/* We do _always_ answer.
* In case all booth daemons are restarted at the same time, nobody
* would answer any questions, leading to timeouts and delays.
* Just admit we don't know. */
rv = (tk->state == ST_INIT) ?
RLT_PROBABLY_SUCCESS : RLT_SUCCESS;
init_ticket_msg(msg, CMR_CATCHUP, rv, tk);
/* On catchup, don't tell about ongoing proposals;
* if we did, the other site might believe that the
* ballot numbers have already been used.
* Send the known ballot number, so that a PREPARE
* gets accepted. */
msg->ticket.ballot = msg->ticket.prev_ballot;
return booth_udp_send(from, msg, sizeof(*msg));
}
/** Got a CMR_CATCHUP message.
* Gets handled here because it's not PAXOS per se,
* but only needed during startup. */
static int ticket_process_catchup(
struct ticket_config *tk,
struct booth_site *from,
struct boothc_ticket_msg *msg,
uint32_t ballot,
struct booth_site *new_owner)
{
int rv;
uint32_t prev_ballot;
time_t peer_expiry;
log_info("got CATCHUP answer for \"%s\" from %s; says owner %s with ballot %d",
tk->name, from->addr_string,
- ticket_owner_string(new_owner), ballot);
+ ticket_leader_string(new_owner), ballot);
prev_ballot = ntohl(msg->ticket.prev_ballot);
rv = ntohl(msg->header.result);
if (rv != RLT_SUCCESS &&
rv != RLT_PROBABLY_SUCCESS) {
log_error("dropped because of wrong rv: 0x%x", rv);
return -EINVAL;
}
if (ballot == tk->new_ballot &&
ballot == tk->last_ack_ballot &&
new_owner == tk->owner) {
/* Peer says the same thing we're believing. */
tk->proposal_acknowledges |= from->bitmask | local->bitmask;
tk->expires = ntohl(msg->ticket.expiry) + time(NULL);
if (should_switch_state_p(tk)) {
if (tk->state == ST_INIT)
tk->state = ST_STABLE;
}
disown_if_expired(tk);
log_debug("catchup: peer ack 0x%" PRIx64 ", now state '%s'",
tk->proposal_acknowledges,
state_to_string(tk->state));
goto ex;
}
if (ticket_valid_for(tk) == 0 && !tk->owner) {
/* We see the ticket as expired, and therefore don't know an owner.
* So believe some other host. */
tk->state = ST_STABLE;
log_debug("catchup: no owner locally, believe peer.");
goto accept;
}
if (ballot >= tk->new_ballot &&
ballot >= tk->last_ack_ballot &&
rv == RLT_SUCCESS) {
/* Peers seems to know better, but as yet we only have _her_
* word for that. */
log_debug("catchup: peer has higher ballot: %d >= %d/%d",
ballot, tk->new_ballot, tk->last_ack_ballot);
accept:
peer_expiry = ntohl(msg->ticket.expiry) + time(NULL);
tk->expires = (tk->expires > peer_expiry) ?
tk->expires : peer_expiry;
tk->new_ballot = ballot_max2(ballot, tk->new_ballot);
tk->last_ack_ballot = ballot_max2(prev_ballot, tk->last_ack_ballot);
tk->owner = new_owner;
tk->proposal_acknowledges = from->bitmask;
/* We stay in ST_INIT and wait for confirmation. */
goto ex;
}
if (ballot >= tk->last_ack_ballot &&
rv == RLT_PROBABLY_SUCCESS &&
tk->state == ST_INIT &&
tk->retry_number > 3) {
/* Peer seems to know better than us, and there's no
* convincing other report. Just take it. */
tk->state = ST_STABLE;
log_debug("catchup: exceeded retries, peer has higher ballot.");
goto accept;
}
if (ballot < tk->new_ballot ||
ballot < tk->last_ack_ballot) {
/* Peer seems outdated ... tell it to reload? */
log_debug("catchup: peer outdated?");
#if 0
init_ticket_msg(&msg, CMD_DO_CATCHUP, RLT_SUCCESS, tk, &tk->current_state);
#endif
goto ex;
}
if (ballot >= tk->last_ack_ballot &&
local->type == SITE &&
new_owner == tk->owner) {
/* We've got some information (local Pacemaker?), and a peer
* says same owner, with same or higher ballot number. */
log_debug("catchup: peer agrees about owner.");
goto ex;
}
log_debug("catchup: unhandled situation!");
ex:
ticket_write(tk);
if (tk->state == ST_STABLE) {
/* If we believe to have enough information, we can try to
* acquire the ticket (again). */
time(&tk->expires);
}
/* Allow further actions. */
ticket_activate_timeout(tk);
return 0;
}
+#endif
+
+
+int ticket_broadcast(struct ticket_config *tk, cmd_request_t cmd, cmd_result_t res)
+{
+ struct boothc_ticket_msg msg;
+
+ init_ticket_msg(&msg, cmd, res, tk);
+ log_debug("broadcasting '%s' for ticket \"%s\"",
+ state_to_string(cmd), tk->name);
+
+ return transport()->broadcast(&msg, sizeof(msg));
+}
+#if 0
/** Send new state request to all sites.
* Perhaps this should take a flag for ACCEPTOR etc.?
* No need currently, as all nodes are more or less identical. */
int ticket_broadcast_proposed_state(struct ticket_config *tk, cmd_request_t state)
{
struct boothc_ticket_msg msg;
- if (state != tk->state) {
- tk->proposal_acknowledges = local->bitmask;
- tk->retry_number = 0;
- }
-
tk->state = state;
init_ticket_msg(&msg, state, RLT_SUCCESS, tk);
- msg.ticket.owner = htonl(get_node_id(tk->proposed_owner));
+ msg.ticket.leader = htonl(get_node_id(tk->proposed_owner));
log_debug("broadcasting '%s' for ticket \"%s\"",
state_to_string(state), tk->name);
/* Switch state after one second, if the majority says ok. */
gettimeofday(&tk->proposal_switch, NULL);
tk->proposal_switch.tv_sec++;
return transport()->broadcast(&msg, sizeof(msg));
}
+#endif
static void ticket_cron(struct ticket_config *tk)
{
time_t now;
now = time(NULL);
/* Has an owner, has an expiry date, and expiry date in the past?
* Losing the ticket must happen in _every_ state. */
- if (tk->expires &&
- tk->owner &&
- now > tk->expires) {
+ if (tk->term_expires &&
+ tk->leader &&
+ now > tk->term_expires) {
log_info("LOST ticket: \"%s\" no longer at %s",
tk->name,
- ticket_owner_string(tk->owner));
+ ticket_leader_string(tk));
/* Couldn't renew in time - ticket lost. */
- tk->owner = NULL;
disown_ticket(tk);
- /* This gets us into ST_INIT again; we couldn't
- * talk to a majority of sites, so we don't know
- * whether somebody else has the ticket now.
- * Keep asking until we know. */
- abort_proposal(tk);
+
+ /* New vote round; §5.2 */
+ if (local->type == SITE)
+ new_election(tk, NULL);
+/* should be "always" that way
+ else
+ tk->state = ST_FOLLOWER;
+ */
+// abort_proposal(tk); TODO
ticket_write(tk);
ticket_activate_timeout(tk);
/* May not try to re-acquire now, need to find out
* what others think. */
return;
}
switch(tk->state) {
case ST_INIT:
/* Unknown state, ask others. */
- ticket_send_catchup(tk);
+// ticket_send_catchup(tk);
return;
- case OP_COMMITTED:
- case ST_STABLE:
-
- /* No matter whether the ticket just got lost by someone,
- * or whether is wasn't active anywhere - if automatic
- * acquiration is configured, try to get it active.
- * Condition:
- * - no owner,
- * - no active proposal,
- * - acquire_after has passed,
- * - could activate locally.
- * Now the sites can try to trump each other. */
- if (!tk->owner &&
- !tk->proposed_owner &&
- !tk->proposer &&
- tk->expires &&
- tk->acquire_after &&
- tk->expires + tk->acquire_after >= now &&
- local->type == SITE) {
- if (!get_ticket_locally_if_allowed(tk))
- log_info("ACQUIRE ticket \"%s\" after timeout; ac=%d", tk->name, tk->acquire_after);
- break;
- }
-
-
- /* Are we the current owner, and do we need to refresh?
- * This is not the same as above. */
- if (should_start_renewal(tk)) {
- if (!get_ticket_locally_if_allowed(tk))
- log_info("RENEW ticket \"%s\"", tk->name);
-
- /* TODO: remember when we started, and restart afresh after some retries */
- }
+ case ST_FOLLOWER:
- break;
-
- case OP_PREPARING:
- PREPARE_to_PROPOSE(tk);
- break;
-
- case OP_PROPOSING:
- PROPOSE_to_COMMIT(tk);
- break;
+ case ST_CANDIDATE:
+ /* §5.2 */
+ if (now > tk->election_end)
+ new_election(tk, NULL);
+ return;
- case OP_PROMISING:
- case OP_ACCEPTING:
- case OP_RECOVERY:
- case OP_REJECTED:
- break;
+ case ST_LEADER:
+ tk->term_expires = now + tk->term_duration;
+ ticket_broadcast(tk, OP_HEARTBEAT, RLT_SUCCESS);
default:
break;
}
}
void process_tickets(void)
{
struct ticket_config *tk;
int i;
struct timeval now;
float sec_until;
gettimeofday(&now, NULL);
foreach_ticket(i, tk) {
sec_until = timeval_to_float(tk->next_cron) - timeval_to_float(now);
if (0)
log_debug("ticket %s next cron %" PRIx64 ".%03d, "
"now %" PRIx64 "%03d, in %f",
tk->name,
(uint64_t)tk->next_cron.tv_sec, timeval_msec(tk->next_cron),
(uint64_t)now.tv_sec, timeval_msec(now),
sec_until);
if (sec_until > 0.0)
continue;
log_debug("ticket cron: doing %s", tk->name);
/* Set next value, handler may override.
* This should already be handled via the state logic;
* but to be on the safe side the renew repetition is
* duplicated here, too. */
set_ticket_wakeup(tk);
ticket_cron(tk);
}
}
void tickets_log_info(void)
{
struct ticket_config *tk;
int i;
foreach_ticket(i, tk) {
log_info("Ticket %s: state '%s' "
- "mask %" PRIx64 "/%" PRIx64 " "
- "ballot %d (current %d) "
+ "commit index %d "
+ "leader \"%s\" "
"expires %-24.24s",
tk->name,
state_to_string(tk->state),
- tk->proposal_acknowledges,
- booth_conf->site_bits,
- tk->last_ack_ballot, tk->new_ballot,
- ctime(&tk->expires));
+ tk->commit_index,
+ ticket_leader_string(tk),
+ ctime(&tk->term_expires));
}
}
/* UDP message receiver. */
int message_recv(struct boothc_ticket_msg *msg, int msglen)
{
- int cmd, rv;
+ int rv;
uint32_t from;
- struct booth_site *dest;
+ struct booth_site *source;
struct ticket_config *tk;
- struct booth_site *new_owner_p;
- uint32_t ballot, new_owner;
+ struct booth_site *leader;
+ uint32_t leader_u;
if (check_boothc_header(&msg->header, sizeof(*msg)) < 0 ||
msglen != sizeof(*msg)) {
log_error("message receive error");
return -1;
}
from = ntohl(msg->header.from);
- if (!find_site_by_id(from, &dest) || !dest) {
+ if (!find_site_by_id(from, &source) || !source) {
log_error("unknown sender: %08x", from);
return -1;
}
if (!check_ticket(msg->ticket.id, &tk)) {
log_error("got invalid ticket name \"%s\" from %s",
- msg->ticket.id, dest->addr_string);
+ msg->ticket.id, source->addr_string);
return -EINVAL;
}
- cmd = ntohl(msg->header.cmd);
- ballot = ntohl(msg->ticket.ballot);
-
- new_owner = ntohl(msg->ticket.owner);
- if (!find_site_by_id(new_owner, &new_owner_p)) {
- log_error("Message with unknown owner %x received", new_owner);
+ leader_u = ntohl(msg->ticket.leader);
+ if (!find_site_by_id(leader_u, &leader)) {
+ log_error("Message with unknown owner %x received", leader_u);
return -EINVAL;
}
+ rv = raft_answer(tk, source, leader, msg);
+#if 0
+ cmd = ntohl(msg->header.cmd);
switch (cmd) {
case CMD_CATCHUP:
- return ticket_answer_catchup(tk, dest, msg, ballot, new_owner_p);
+ return ticket_answer_catchup(tk, source, msg, ballot, new_owner_p);
case CMR_CATCHUP:
- return ticket_process_catchup(tk, dest, msg, ballot, new_owner_p);
+ return ticket_process_catchup(tk, source, msg, ballot, new_owner_p);
default:
/* only used in catchup, and not even really there ?? */
assert(ntohl(msg->header.result) == 0);
-
- rv = paxos_answer(tk, dest, msg, ballot, new_owner_p);
- assert((tk->proposal_acknowledges & ~booth_conf->site_bits) == 0);
+ rv = raft_answer(tk, source, msg);
+// TODO assert((tk->proposal_acknowledges & ~booth_conf->site_bits) == 0);
return rv;
}
- return 0;
+#endif
+ return rv;
}
void set_ticket_wakeup(struct ticket_config *tk)
{
struct timeval tv, now;
- if (tk->owner == local) {
+ /* At least every hour, perhaps sooner. */
+ ticket_next_cron_in(tk, 3600);
+
+ switch (tk->state) {
+ case ST_LEADER:
+ assert(tk->leader == local);
gettimeofday(&now, NULL);
tv = now;
- tv.tv_sec = next_renewal_starts_at(tk);
+ tv.tv_sec = next_vote_starts_at(tk);
/* If timestamp is in the past, look again in one second. */
if (timeval_compare(tv, now) <= 0)
tv.tv_sec = now.tv_sec + 1;
ticket_next_cron_at(tk, tv);
- } else {
+ break;
+
+ case ST_CANDIDATE:
+ assert(tk->election_end);
+ ticket_next_cron_at_coarse(tk, tk->election_end);
+ break;
+
+ case ST_FOLLOWER:
/* If there is (or should be) some owner, check on her later on.
* If no one is interested - don't care. */
- if ((tk->owner || tk->acquire_after) &&
+ if ((tk->leader || tk->acquire_after) &&
(local->type == SITE))
- ticket_next_cron_in(tk, tk->expiry + tk->acquire_after);
- else
- ticket_next_cron_in(tk, 3600);
+ ticket_next_cron_at_coarse(tk,
+ tk->term_expires + tk->acquire_after);
+ break;
+
+ default:
+ log_error("why here?");
}
}
/* 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_site *dest, struct ticket_config *tk, cmd_result_t code)
+{
+ struct boothc_ticket_msg msg;
+
+
+ init_ticket_msg(&msg, OP_REJECTED, code, tk);
+ return booth_udp_send(dest, &msg, sizeof(msg));
+}
diff --git a/src/ticket.h b/src/ticket.h
index 7f535bd..e0af8b2 100644
--- a/src/ticket.h
+++ b/src/ticket.h
@@ -1,96 +1,104 @@
/*
* 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.1 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 library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifndef _TICKET_H
#define _TICKET_H
#include <time.h>
#include <sys/time.h>
#include <math.h>
#include "config.h"
#define DEFAULT_TICKET_EXPIRY 600
#define DEFAULT_TICKET_TIMEOUT 10
#define DEFAULT_RETRIES 10
#define foreach_ticket(i_,t_) for(i=0; (t_=booth_conf->ticket+i, i<booth_conf->ticket_count); i++)
#define foreach_node(i_,n_) for(i=0; (n_=booth_conf->site+i, i<booth_conf->site_count); i++)
int check_ticket(char *ticket, struct ticket_config **tc);
int check_site(char *site, int *local);
int do_grant_ticket(struct ticket_config *ticket);
int revoke_ticket(struct ticket_config *ticket);
int list_ticket(char **pdata, unsigned int *len);
int message_recv(struct boothc_ticket_msg *msg, int msglen);
int setup_ticket(void);
int check_max_len_valid(const char *s, int max);
int do_grant_ticket(struct ticket_config *tk);
int do_revoke_ticket(struct ticket_config *tk);
int find_ticket_by_name(const char *ticket, struct ticket_config **found);
void set_ticket_wakeup(struct ticket_config *tk);
int get_ticket_locally_if_allowed(struct ticket_config *tk);
int ticket_answer_list(int fd, struct boothc_ticket_msg *msg);
int ticket_answer_grant(int fd, struct boothc_ticket_msg *msg);
int ticket_answer_revoke(int fd, struct boothc_ticket_msg *msg);
int ticket_broadcast_proposed_state(struct ticket_config *tk, cmd_request_t state);
int ticket_write(struct ticket_config *tk);
void process_tickets(void);
void tickets_log_info(void);
char *state_to_string(uint32_t state_ho);
+int send_reject(struct booth_site *dest, struct ticket_config *tk, cmd_result_t code);
+int ticket_broadcast(struct ticket_config *tk, cmd_request_t cmd, cmd_result_t res);
static inline void ticket_next_cron_at(struct ticket_config *tk, struct timeval when)
{
tk->next_cron = when;
}
+static inline void ticket_next_cron_at_coarse(struct ticket_config *tk, time_t when)
+{
+ tk->next_cron.tv_sec = when;
+ tk->next_cron.tv_usec = 0;
+}
+
static inline void ticket_next_cron_in(struct ticket_config *tk, float seconds)
{
struct timeval tv;
gettimeofday(&tv, NULL);
tv.tv_sec += trunc(seconds);
tv.tv_usec += (seconds - trunc(seconds)) * 1e6;
ticket_next_cron_at(tk, tv);
}
static inline void ticket_activate_timeout(struct ticket_config *tk)
{
/* TODO: increase timeout when no answers */
ticket_next_cron_in(tk, tk->timeout);
tk->retry_number ++;
}
#endif /* _TICKET_H */

File Metadata

Mime Type
text/x-diff
Expires
Mon, Feb 24, 8:27 PM (9 h, 8 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1462561
Default Alt Text
(91 KB)

Event Timeline