Hello community, here is the log from the commit of package booth for openSUSE:Factory checked in at 2015-11-28 15:19:04 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/booth (Old) and /work/SRC/openSUSE:Factory/.booth.new (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "booth" Changes: -------- --- /work/SRC/openSUSE:Factory/booth/booth.changes 2015-11-08 11:26:58.000000000 +0100 +++ /work/SRC/openSUSE:Factory/.booth.new/booth.changes 2015-11-28 15:19:06.000000000 +0100 @@ -1,0 +2,14 @@ +Tue Nov 24 13:11:00 UTC 2015 - dmuhameda...@suse.com + +- Update to version v0.2.0_120_gf3d73a5: + + arbitrator: mark expired tickets as lost (bsc#956321) + + attr: better control of election cause + +------------------------------------------------------------------- +Fri Nov 20 11:37:43 UTC 2015 - dmuhameda...@suse.com + +- Update to version v0.2.0_116_g88c3d6a: + + attr: attribute prerequisites (fate#318182) + + attr: keep attributes in the CIB (fate#318182) + +------------------------------------------------------------------- ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.amwQe7/_old 2015-11-28 15:19:07.000000000 +0100 +++ /var/tmp/diff_new_pack.amwQe7/_new 2015-11-28 15:19:07.000000000 +0100 @@ -1,4 +1,4 @@ <servicedata> <service name="tar_scm"> <param name="url">git://github.com/ClusterLabs/booth.git</param> - <param name="changesrevision">3e73b683b36c38650d1d2ff4c9393cb1bb2056e6</param></service></servicedata> \ No newline at end of file + <param name="changesrevision">88c3d6a865cea676778bdeb7858f715a6ece6b10</param></service></servicedata> \ No newline at end of file ++++++ booth.tar.bz2 ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/booth/.git_info new/booth/.git_info --- old/booth/.git_info 2015-11-06 10:07:54.000000000 +0100 +++ new/booth/.git_info 2015-11-24 14:08:30.000000000 +0100 @@ -1 +1 @@ -v0.2.0-113-gaeef08d +v0.2.0-120-gf3d73a5 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/booth/configure.ac new/booth/configure.ac --- old/booth/configure.ac 2015-11-06 09:20:07.000000000 +0100 +++ new/booth/configure.ac 2015-11-23 19:13:11.000000000 +0100 @@ -61,6 +61,7 @@ AC_PATH_PROGS(PKGCONFIG, pkg-config) AC_PATH_PROGS(ASCIIDOC, asciidoc) +AC_PATH_PROGS(XML2CONFIG, xml2-config) AM_CONDITIONAL(BUILD_ASCIIDOC, test x"${ASCIIDOC}" != x"") @@ -82,6 +83,17 @@ AM_CONDITIONAL(BUILD_AUTH_C, test "x${mhash_installed}" = "xyes") fi +AC_MSG_CHECKING(for special libxml2 includes) +if test "x$XML2CONFIG" = "x"; then + AC_MSG_ERROR(libxml2 config not found) +else + XML2HEAD="`$XML2CONFIG --cflags`" + AC_MSG_RESULT($XML2HEAD) + AC_CHECK_LIB(xml2, xmlReadMemory) +fi + +CPPFLAGS="$CPPFLAGS $XML2HEAD" + PKG_CHECK_MODULES(GLIB, [glib-2.0]) # Checks for header files. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/booth/docs/boothd.8.txt new/booth/docs/boothd.8.txt --- old/booth/docs/boothd.8.txt 2015-11-06 09:20:07.000000000 +0100 +++ new/booth/docs/boothd.8.txt 2015-11-23 19:13:11.000000000 +0100 @@ -325,6 +325,38 @@ The distributed 'service-runnable' script is an example which may be used to test whether a pacemaker resource can be started. +*'attr-prereq'*:: + Sites can have GEO attributes managed with the 'geostore(8)' + program. Attributes are within ticket's scope and may be + tested by 'boothd' for additional control of ticket failover + (automatic) or ticket acquire (manual). ++ +Attributes are typically used to convey extra information about +resources, for instance database replication status. The +attributes are commonly updated by resource agents. ++ +Attribute values are referenced in expressions and may be tested +for equality with the 'eq' binary operator or inequality with the +'ne' operator. The usage is as follows: + + attr-prereq = <grant_type> <name> <op> <value> + + <grant_type>: "auto" | "manual" + <name>: attribute name + <op>: "eq" | "ne" + <value>: attribute value ++ +The two grant types are 'auto' for ticket failover and 'manual' +for grants using the booth client. Only in case the expression +evaluates to true can the ticket be granted. ++ +It is not clear whether the 'manual' grant type has any practical +use because, obviously, this operation is anyway controlled by a +human. ++ +Note that there can be no guarantee on whether an attribute value +is up to date, i.e. if it actually reflects the current state. + One example of a booth configuration file: ----------------------- @@ -345,6 +377,7 @@ retries = 5 renewal-freq = 60 before-acquire-handler = /usr/share/booth/service-runnable db8 + attr-prereq = auto repl_state eq ACTIVE ----------------------- diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/booth/src/attr.c new/booth/src/attr.c --- old/booth/src/attr.c 2015-11-06 09:20:07.000000000 +0100 +++ new/booth/src/attr.c 2015-11-23 19:13:11.000000000 +0100 @@ -19,6 +19,7 @@ #include <stdio.h> #include "attr.h" #include "ticket.h" +#include "pacemaker.h" void print_geostore_usage(void) { @@ -243,11 +244,13 @@ g_free(a); } -static cmd_result_t attr_set(struct ticket_config *tk, struct boothc_attr_msg *msg) +int store_geo_attr(struct ticket_config *tk, const char *name, char *val, int notime) { struct geo_attr *a; GDestroyNotify free_geo_attr_notify = free_geo_attr; + if (!tk) + return -1; /* * allocate new, if attr doesn't already exist * copy the attribute value @@ -258,21 +261,34 @@ free_geo_attr_notify, g_free); if (!tk->attr) { log_error("out of memory"); - return RLT_SYNC_FAIL; + return -1; } a = (struct geo_attr *)calloc(1, sizeof(struct geo_attr)); if (!a) { log_error("out of memory"); - return RLT_SYNC_FAIL; + return -1; } - a->val = g_strdup(msg->attr.val); - get_time(&a->update_ts); + a->val = g_strdup(val); + if (!notime) + get_time(&a->update_ts); g_hash_table_insert(tk->attr, - g_strndup(msg->attr.name, BOOTH_NAME_LEN), a); + g_strndup(name, BOOTH_NAME_LEN), a); + + return 0; +} + +static cmd_result_t attr_set(struct ticket_config *tk, struct boothc_attr_msg *msg) +{ + int rc; + rc = store_geo_attr(tk, msg->attr.name, msg->attr.val, 0); + if (rc) { + return RLT_SYNC_FAIL; + } + (void)pcmk_handler.set_attr(tk, msg->attr.name, msg->attr.val); return RLT_SUCCESS; } @@ -296,6 +312,8 @@ rv = g_hash_table_remove(tk->attr, msg->attr.name); + (void)pcmk_handler.del_attr(tk, msg->attr.name); + return gbool2rlt(rv); } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/booth/src/attr.h new/booth/src/attr.h --- old/booth/src/attr.h 2015-11-06 09:20:07.000000000 +0100 +++ new/booth/src/attr.h 2015-11-23 19:13:11.000000000 +0100 @@ -34,5 +34,6 @@ int do_attr_command(cmd_request_t cmd); int process_attr_request(struct client *req_client, void *buf); int attr_recv(void *buf, struct booth_site *source); +int store_geo_attr(struct ticket_config *tk, const char *name, char *val, int notime); #endif /* _ATTR_H */ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/booth/src/booth.h new/booth/src/booth.h --- old/booth/src/booth.h 2015-11-06 09:20:07.000000000 +0100 +++ new/booth/src/booth.h 2015-11-23 19:13:11.000000000 +0100 @@ -244,6 +244,7 @@ RLT_NO_SUCH_ATTR = CHAR2CONST('N', 'A', 't', 'r'), RLT_CIB_PENDING = CHAR2CONST('P', 'e', 'n', 'd'), RLT_EXT_FAILED = CHAR2CONST('X', 'P', 'r', 'g'), + RLT_ATTR_PREREQ = CHAR2CONST('A', 'P', 'r', 'q'), RLT_TICKET_IDLE = CHAR2CONST('T', 'i', 'd', 'l'), RLT_OVERGRANT = CHAR2CONST('O', 'v', 'e', 'r'), RLT_PROBABLY_SUCCESS = CHAR2CONST('S', 'u', 'c', '?'), diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/booth/src/config.c new/booth/src/config.c --- old/booth/src/config.c 2015-11-06 09:20:07.000000000 +0100 +++ new/booth/src/config.c 2015-11-23 19:13:11.000000000 +0100 @@ -370,6 +370,103 @@ 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; +} + extern int poll_timeout; int read_config(const char *path, int type) @@ -680,6 +777,13 @@ goto err; } continue; + } + + if (strcmp(key, "attr-prereq") == 0) { + if (parse_attr_prereq(val, current_tk)) { + goto err; + } + continue; } if (strcmp(key, "weights") == 0) { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/booth/src/config.h new/booth/src/config.h --- old/booth/src/config.h 2015-11-06 09:20:07.000000000 +0100 +++ new/booth/src/config.h 2015-11-23 19:13:11.000000000 +0100 @@ -46,6 +46,28 @@ #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; + +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. * @{ */ @@ -200,6 +222,10 @@ */ GHashTable *attr; + /** Attribute prerequisites + */ + GList *attr_prereqs; + /** Whom to vote for the next time. * Needed to push a ticket to someone else. */ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/booth/src/main.c new/booth/src/main.c --- old/booth/src/main.c 2015-11-06 09:20:07.000000000 +0100 +++ new/booth/src/main.c 2015-11-23 19:13:11.000000000 +0100 @@ -599,6 +599,12 @@ rv = -1; break; + case RLT_ATTR_PREREQ: + log_error("attr-prereq for ticket \"%s\" failed, grant denied", + cl.msg.ticket.id); + rv = -1; + break; + case RLT_REDIRECT: /* talk to another site */ rv = 1; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/booth/src/pacemaker.c new/booth/src/pacemaker.c --- old/booth/src/pacemaker.c 2015-11-06 09:20:07.000000000 +0100 +++ new/booth/src/pacemaker.c 2015-11-23 19:13:11.000000000 +0100 @@ -21,9 +21,14 @@ #include <stdlib.h> #include <errno.h> #include <inttypes.h> +#include <unistd.h> #include <sys/types.h> #include <sys/wait.h> +#include <libxml/parser.h> +#include <libxml/tree.h> +#include "ticket.h" #include "log.h" +#include "attr.h" #include "pacemaker.h" #include "inline-fn.h" @@ -196,15 +201,10 @@ } -static int crm_ticket_set(const struct ticket_config *tk, const char *attr, int64_t val) +static int _run_crm_ticket(char *cmd) { - 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)); @@ -215,6 +215,36 @@ return rv; } +static int crm_ticket_set_int(const struct ticket_config *tk, const char *attr, int64_t val) +{ + char cmd[COMMAND_MAX]; + + snprintf(cmd, COMMAND_MAX, + "crm_ticket -t '%s' -S '%s' -v %" PRIi64, + tk->name, attr, val); + return _run_crm_ticket(cmd); +} + +static int pcmk_set_attr(struct ticket_config *tk, const char *attr, const char *val) +{ + char cmd[COMMAND_MAX]; + + snprintf(cmd, COMMAND_MAX, + "crm_ticket -t '%s' -S '%s' -v '%s'", + tk->name, attr, val); + return _run_crm_ticket(cmd); +} + +static int pcmk_del_attr(struct ticket_config *tk, const char *attr) +{ + char cmd[COMMAND_MAX]; + + snprintf(cmd, COMMAND_MAX, + "crm_ticket -t '%s' -D '%s'", + tk->name, attr); + return _run_crm_ticket(cmd); +} + static int pcmk_store_ticket_nonatomic(struct ticket_config *tk) { @@ -222,9 +252,9 @@ /* 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->leader)); - rv = crm_ticket_set(tk, "expires", wall_ts(&tk->term_expires)) || rv; - rv = crm_ticket_set(tk, "term", tk->current_term) || rv; + rv = crm_ticket_set_int(tk, "owner", (int32_t)get_node_id(tk->leader)); + rv = crm_ticket_set_int(tk, "expires", wall_ts(&tk->term_expires)) || rv; + rv = crm_ticket_set_int(tk, "term", tk->current_term) || rv; if (rv) log_error("setting crm_ticket attributes failed; %s", @@ -235,19 +265,88 @@ return rv; } +typedef int (*attr_f)(struct ticket_config *tk, const char *name, char *val); + +struct attr_tab +{ + const char *name; + attr_f handling_f; +}; -static int crm_ticket_get(struct ticket_config *tk, - const char *attr, int64_t *data) +static int save_expires(struct ticket_config *tk, const char *name, char *val) +{ + secs2tv(unwall_ts(atol(val)), &tk->term_expires); + return 0; +} + +static int save_term(struct ticket_config *tk, const char *name, char *val) +{ + tk->current_term = atol(val); + return 0; +} + +static int parse_boolean(char *val) +{ + long v; + + if (!strncmp(val, "false", 5)) { + v = 0; + } else if (!strncmp(val, "true", 4)) { + v = 1; + } else { + v = atol(val); + } + return v; +} + +static int save_granted(struct ticket_config *tk, const char *name, char *val) +{ + tk->is_granted = parse_boolean(val); + return 0; +} + +static int save_owner(struct ticket_config *tk, const char *name, char *val) +{ + /* No check, node could have been deconfigured. */ + tk->leader = NULL; + return !find_site_by_id(atol(val), &tk->leader); +} + +static int ignore_attr(struct ticket_config *tk, const char *name, char *val) +{ + return 0; +} + +static int save_attr(struct ticket_config *tk, const char *name, char *val) +{ + /* tell store_geo_attr not to store time, we don't have that + * information available + */ + return store_geo_attr(tk, name, val, 1); +} + +struct attr_tab attr_handlers[] = { + { "expires", save_expires}, + { "term", save_term}, + { "granted", save_granted}, + { "owner", save_owner}, + { "id", ignore_attr}, + { "last-granted", ignore_attr}, + { NULL, 0}, +}; + + +/* get_attr is currently not used and has not been tested + */ +static int pcmk_get_attr(struct ticket_config *tk, const char *attr, const char **vp) { char cmd[COMMAND_MAX]; - char line[256]; - int rv; - int64_t v; + char line[BOOTH_ATTRVAL_LEN+1]; + int rv = 0; FILE *p; - *data = -1; - v = 0; + *vp = NULL; snprintf(cmd, COMMAND_MAX, "crm_ticket -t '%s' -G '%s' --quiet", tk->name, attr); @@ -257,85 +356,167 @@ rv = errno; log_error("popen error %d (%s) for \"%s\"", rv, strerror(rv), cmd); - return rv || -EINVAL; + return rv || EINVAL; } - if (fgets(line, sizeof(line) - 1, p) == NULL) { + if (fgets(line, BOOTH_ATTRVAL_LEN, p) == NULL) { rv = ENODATA; goto out; } - rv = EINVAL; - if (!strncmp(line, "false", 5)) { - v = 0; - rv = 0; - } else if (!strncmp(line, "true", 4)) { - v = 1; - rv = 0; - } else if (sscanf(line, "%" PRIi64, &v) == 1) { - rv = 0; - } - - *data = v; + *vp = g_strdup(line); out: rv = pclose(p); if (!rv) { - log_debug("command \"%s\" value %" PRIi64, cmd, v); + log_debug("command \"%s\"", cmd); } else if (WEXITSTATUS(rv) == 6) { log_info("command \"%s\", ticket not found", cmd); } else { - log_error("command \"%s\" %s, value %" PRIi64, cmd, interpret_rv(rv), v); + log_error("command \"%s\" %s", cmd, interpret_rv(rv)); + } + return rv; +} + +static int save_attributes(struct ticket_config *tk, xmlDocPtr doc) +{ + int rv = 0, rc; + xmlNodePtr n; + xmlAttrPtr attr; + xmlChar *v; + struct attr_tab *atp; + + n = xmlDocGetRootElement(doc); + if (n == NULL) { + tk_log_error("crm_ticket xml output empty"); + return -EINVAL; + } + if (xmlStrcmp(n->name, (const xmlChar *)"ticket_state")) { + tk_log_error("crm_ticket xml root element not ticket_state"); + return -EINVAL; + } + for (attr = n->properties; attr; attr = attr->next) { + v = xmlGetProp(n, attr->name); + for (atp = attr_handlers; atp->name; atp++) { + if (!strcmp(atp->name, attr->name)) { + rc = atp->handling_f(tk, attr->name, v); + break; + } + } + if (!atp->name) { + rc = save_attr(tk, attr->name, v); + } + if (rc) { + tk_log_error("error storing attribute %s", attr->name); + rv |= rc; + } + xmlFree(v); } return rv; } -static int pcmk_load_ticket(struct ticket_config *tk) +#define CHUNK_SIZE 256 + +static int parse_ticket_state(struct ticket_config *tk, FILE *p) { - int rv; - int64_t v; + int rv = 0; + GString *input = NULL; + char line[CHUNK_SIZE]; + xmlDocPtr doc = NULL; + xmlErrorPtr errptr; + int opts = XML_PARSE_COMPACT | XML_PARSE_NONET; + + /* skip first two lines of output */ + if (fgets(line, CHUNK_SIZE-1, p) == NULL || fgets(line, CHUNK_SIZE-1, p) == NULL) { + tk_log_error("crm_ticket xml output empty"); + rv = ENODATA; + goto out; + } + input = g_string_sized_new(CHUNK_SIZE); + if (!input) { + log_error("out of memory"); + rv = -1; + goto out; + } + while (fgets(line, CHUNK_SIZE-1, p) != NULL) { + if (!g_string_append(input, line)) { + log_error("out of memory"); + rv = -1; + goto out; + } + } + + doc = xmlReadDoc(input->str, NULL, NULL, opts); + if (doc == NULL) { + errptr = xmlGetLastError(); + if (errptr) { + tk_log_error("crm_ticket xml parse failed (domain=%d, level=%d, code=%d): %s", + errptr->domain, errptr->level, + errptr->code, errptr->message); + } else { + tk_log_error("crm_ticket xml parse failed"); + } + rv = -EINVAL; + goto out; + } + rv = save_attributes(tk, doc); +out: + if (doc) + xmlFreeDoc(doc); + if (input) + g_string_free(input, TRUE); + return rv; +} + +static int pcmk_load_ticket(struct ticket_config *tk) +{ + char cmd[COMMAND_MAX]; + int rv = 0, pipe_rv; + FILE *p; /* This here gets run during startup; testing that here means that * normal operation won't be interrupted with that test. */ test_atomicity(); + snprintf(cmd, COMMAND_MAX, + "crm_ticket -t '%s' -q", + tk->name); - rv = crm_ticket_get(tk, "expires", &v); - if (!rv) { - secs2tv(unwall_ts(v), &tk->term_expires); - } - - rv = crm_ticket_get(tk, "term", &v); - if (!rv) { - tk->current_term = v; + p = popen(cmd, "r"); + if (p == NULL) { + pipe_rv = errno; + log_error("popen error %d (%s) for \"%s\"", + pipe_rv, strerror(pipe_rv), cmd); + return pipe_rv || -EINVAL; } - rv = crm_ticket_get(tk, "granted", &v); - if (!rv) { - tk->is_granted = v; - } + rv = parse_ticket_state(tk, p); - rv = crm_ticket_get(tk, "owner", &v); - if (!rv) { - /* No check, node could have been deconfigured. */ - if (!find_site_by_id(v, &tk->leader)) { - /* Hmm, no site found for the ticket we have in the - * CIB!? - * Assume that the ticket belonged to us if it was - * granted here! - */ - log_warn("%s: no site matches; site got reconfigured?", + if (!tk->leader) { + /* Hmm, no site found for the ticket we have in the + * CIB!? + * Assume that the ticket belonged to us if it was + * granted here! + */ + log_warn("%s: no site matches; site got reconfigured?", + tk->name); + if (tk->is_granted) { + log_warn("%s: granted here, assume it belonged to us", tk->name); - if (tk->is_granted) { - log_warn("%s: granted here, assume it belonged to us", - tk->name); - tk->leader = local; - } + tk->leader = local; } } - return rv; + pipe_rv = pclose(p); + if (!pipe_rv) { + log_debug("command \"%s\"", cmd); + } else if (WEXITSTATUS(pipe_rv) == 6) { + log_info("command \"%s\", ticket not found", cmd); + } else { + log_error("command \"%s\" %s", cmd, interpret_rv(pipe_rv)); + } + return rv | pipe_rv; } @@ -343,4 +524,7 @@ .grant_ticket = pcmk_grant_ticket, .revoke_ticket = pcmk_revoke_ticket, .load_ticket = pcmk_load_ticket, + .set_attr = pcmk_set_attr, + .get_attr = pcmk_get_attr, + .del_attr = pcmk_del_attr, }; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/booth/src/pacemaker.h new/booth/src/pacemaker.h --- old/booth/src/pacemaker.h 2015-11-06 09:20:07.000000000 +0100 +++ new/booth/src/pacemaker.h 2015-11-23 19:13:11.000000000 +0100 @@ -27,6 +27,9 @@ int (*grant_ticket) (struct ticket_config *tk); int (*revoke_ticket) (struct ticket_config *tk); int (*load_ticket) (struct ticket_config *tk); + int (*set_attr) (struct ticket_config *tk, const char *a, const char *v); + int (*get_attr) (struct ticket_config *tk, const char *a, const char **vp); + int (*del_attr) (struct ticket_config *tk, const char *a); }; struct ticket_handler pcmk_handler; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/booth/src/raft.c new/booth/src/raft.c --- old/booth/src/raft.c 2015-11-06 09:20:07.000000000 +0100 +++ new/booth/src/raft.c 2015-11-23 19:13:11.000000000 +0100 @@ -726,6 +726,8 @@ return booth_udp_send_auth(sender, &omsg, sendmsglen(&omsg)); } +#define is_reason(r, tk) \ + (reason == (r) || (reason == OR_AGAIN && (tk)->election_reason == (r))) int new_election(struct ticket_config *tk, struct booth_site *preference, int update_term, cmd_reason_t reason) @@ -735,6 +737,13 @@ if (local->type != SITE) return 0; + if ((is_reason(OR_TKT_LOST, tk) || is_reason(OR_STEPDOWN, tk)) && + check_attr_prereq(tk, GRANT_AUTO)) { + tk_log_info("attribute prerequisite not met, " + "not starting elections"); + return 0; + } + /* elections were already started, but not yet finished/timed out */ if (is_time_set(&tk->election_end) && !is_past(&tk->election_end)) return 1; @@ -747,6 +756,9 @@ } else { tk_log_debug("starting elections"); } + tk_log_debug("elections caused by %s %s", + state_to_string(reason), + reason == OR_AGAIN ? state_to_string(tk->election_reason) : "" ); } /* ยง5.2 */ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/booth/src/ticket.c new/booth/src/ticket.c --- old/booth/src/ticket.c 2015-11-06 09:20:07.000000000 +0100 +++ new/booth/src/ticket.c 2015-11-23 19:13:11.000000000 +0100 @@ -230,6 +230,41 @@ return rv; } +#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 @@ -278,6 +313,9 @@ { int rv; + if (reason == OR_ADMIN && check_attr_prereq(tk, GRANT_MANUAL)) + return RLT_ATTR_PREREQ; + switch(do_ext_prog(tk, 0)) { case 0: /* everything fine */ @@ -887,10 +925,13 @@ static void ticket_lost(struct ticket_config *tk) { + int reason = OR_TKT_LOST; + if (tk->leader != local) { tk_log_warn("lost at %s", site_string(tk->leader)); } else { tk_log_warn("lost majority (revoking locally)"); + reason = tk->election_reason ? tk->election_reason : OR_REACQUIRE; } tk->lost_leader = tk->leader; @@ -899,7 +940,7 @@ set_state(tk, ST_FOLLOWER); if (local->type == SITE) { ticket_write(tk); - schedule_election(tk, OR_TKT_LOST); + schedule_election(tk, reason); } } @@ -1180,8 +1221,7 @@ 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) && - (local->type == SITE)) { + if (is_owned(tk)) { interval_add(&tk->term_expires, tk->acquire_after, &tv); ticket_next_cron_at(tk, &tv); } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/booth/src/ticket.h new/booth/src/ticket.h --- old/booth/src/ticket.h 2015-11-06 09:20:07.000000000 +0100 +++ new/booth/src/ticket.h 2015-11-23 19:13:11.000000000 +0100 @@ -106,6 +106,8 @@ void add_random_delay(struct ticket_config *tk); void schedule_election(struct ticket_config *tk, cmd_reason_t reason); +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); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/booth/src/transport.c new/booth/src/transport.c --- old/booth/src/transport.c 2015-11-06 09:20:07.000000000 +0100 +++ new/booth/src/transport.c 2015-11-23 19:13:11.000000000 +0100 @@ -236,10 +236,15 @@ } /* First try with exact addresses, then optionally with subnet matching. */ - if (ifa->ifa_prefixlen > address_bits_matched) + if (ifa->ifa_prefixlen > address_bits_matched) { find_address(ipaddr, ifa->ifa_family, ifa->ifa_prefixlen, fuzzy_allowed, &me, &address_bits_matched); + if (me) { + log_debug("found myself at %s (%d bits matched)", + site_string(me), address_bits_matched); + } + } } h = NLMSG_NEXT(h, status); } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/booth/test/live_test.sh new/booth/test/live_test.sh --- old/booth/test/live_test.sh 2015-11-06 09:20:07.000000000 +0100 +++ new/booth/test/live_test.sh 2015-11-23 19:13:11.000000000 +0100 @@ -370,7 +370,26 @@ /^ticket.*'$tkt'/ {n=1} ' $cnf } +get_attr() { + awk ' +n && /^[[:space:]]*attr-prereq = auto .* eq / {print $4,$6; exit} +n && (/^$/ || /^ticket.*/) {exit} +/^ticket.*'$tkt'/ {n=1} +' $cnf +} +set_site_attr() { + local site + site=`get_site $1` + set -- `get_attr` + geostore set -s $site $1 $2 +} +del_site_attr() { + local site + site=`get_site $1` + set -- `get_attr` + geostore delete -s $site $1 +} break_external_prog() { run_site $1 crm configure "location $PREFNAME `get_rsc` rule -inf: defined \#uname" } @@ -507,7 +526,7 @@ # list on all of them (at least one should have booth # running) ticket_line=`forall_sites booth list | grep $tkt | sort -u | head -1` - grantee=`echo "$ticket_line" | sed 's/.*leader: //;s/,.*//'` + grantee=`echo "$ticket_line" | sed 's/.*leader: //;s/,.*//;s/NONE/none/'` echo $grantee [ "$grantee" = "none" ] && return ! echo "$ticket_line" | grep -q "$tkt.*pending" @@ -948,6 +967,8 @@ # ticket failover setup_failover() { grant_ticket 1 + [ -n "`get_attr`" ] && set_site_attr 2 + return 0 } test_failover() { stop_site_clean `get_site 1` || return 1 @@ -969,6 +990,8 @@ # split brain (leader alone) setup_split_leader() { grant_ticket_cib 1 + [ -n "`get_attr`" ] && set_site_attr 2 + return 0 } test_split_leader() { run_site 1 $iprules stop $port >/dev/null @@ -1029,6 +1052,7 @@ # external test prog failed setup_external_prog_failed() { grant_ticket 1 || return 1 + [ -n "`get_attr`" ] && set_site_attr 2 break_external_prog 1 show_pref 1 || return 1 } @@ -1047,6 +1071,57 @@ [ -n "`get_rsc`" ] } +## TEST: attr_prereq_ok ## + +# failover with attribute prerequisite +setup_attr_prereq_ok() { + grant_ticket 1 || return 1 + set_site_attr 2 + stop_site_clean `get_site 1` + booth_status `get_site 1` && return 1 + return 0 +} +test_attr_prereq_ok() { + wait_exp + wait_timeout +} +check_attr_prereq_ok() { + check_consistency `get_site 2` +} +recover_attr_prereq_ok() { + start_site `get_site 1` + del_site_attr 2 +} +applicable_attr_prereq_ok() { + [ -n "`get_attr`" ] +} + +## TEST: attr_prereq_fail ## + +# failover with failed attribute prerequisite +setup_attr_prereq_fail() { + grant_ticket 1 || return 1 + del_site_attr 2 >/dev/null 2>&1 + stop_site_clean `get_site 1` + booth_status `get_site 1` && return 1 + return 0 +} +test_attr_prereq_fail() { + wait_exp + wait_exp + wait_exp +} +check_attr_prereq_fail() { + check_consistency && + booth_where_granted | grep -qwi none +} +recover_attr_prereq_fail() { + start_site `get_site 1` +} +applicable_attr_prereq_fail() { + [ -n "`get_attr`" ] +} + # # environment modifications # @@ -1161,7 +1236,7 @@ simultaneous_start_even slow_start_granted restart_granted reload_granted restart_granted_nocib restart_notgranted failover split_leader split_follower split_edge -external_prog_failed"} +external_prog_failed attr_prereq_ok attr_prereq_fail"} for t in $TESTS; do runtest $t