Hello community,

here is the log from the commit of package booth for openSUSE:Factory checked 
in at 2017-12-01 15:54:31
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/booth (Old)
 and      /work/SRC/openSUSE:Factory/.booth.new (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "booth"

Fri Dec  1 15:54:31 2017 rev:43 rq:547071 version:1.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/booth/booth.changes      2017-06-23 
09:18:58.593248682 +0200
+++ /work/SRC/openSUSE:Factory/.booth.new/booth.changes 2017-12-01 
15:55:01.741034009 +0100
@@ -1,0 +2,12 @@
+Fri Dec  1 10:50:28 UTC 2017 - ckowalc...@suse.com
+
+- Tickets: added manual tickets, which allow handling 2-site setup
+  (fate#322100)
+- Debug mode: fixed the interaction with resource agents (bsc#1046790)
+- Patch file bug-1045067_booth-fix-booth-grant-cmd.patch has been removed
+  after the code being merged to upstream:
+  * Clinet commands: fixed local IP addresses for booth grant, list,
+    and peers commands (bsc#1045067)
+- Upstream version cs: d4cb8cbdaf87e46f636c3d06730b902b79bdcb9c 
+
+-------------------------------------------------------------------

Old:
----
  booth-1.0+20170619.766d618.tar.bz2
  bug-1045067_booth-fix-booth-grant-cmd.patch

New:
----
  booth-1.0+20171123.d4cb8cb.tar.bz2

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ booth.spec ++++++
--- /var/tmp/diff_new_pack.7fmU46/_old  2017-12-01 15:55:02.425009396 +0100
+++ /var/tmp/diff_new_pack.7fmU46/_new  2017-12-01 15:55:02.429009252 +0100
@@ -21,7 +21,7 @@
 %bcond_with glue
 
 # local commit:
-%global commit 1.0+20170619.766d618
+%global commit 1.0+20171123.d4cb8cb
 
 %global uname hacluster
 %global gname haclient
@@ -39,7 +39,6 @@
 Url:            https://github.com/ClusterLabs/booth
 Source:         %{name}-%{commit}.tar.bz2
 Source1:        %{name}-rpmlintrc
-Patch1:         bug-1045067_booth-fix-booth-grant-cmd.patch
 BuildRequires:  asciidoc
 BuildRequires:  autoconf
 BuildRequires:  automake
@@ -78,7 +77,6 @@
 
 %prep
 %setup -q -n %{name}-%{commit}
-%patch1 -p1
 
 %build
 autoreconf -fvi

++++++ booth-1.0+20170619.766d618.tar.bz2 -> booth-1.0+20171123.d4cb8cb.tar.bz2 
++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/booth-1.0+20170619.766d618/.git_info 
new/booth-1.0+20171123.d4cb8cb/.git_info
--- old/booth-1.0+20170619.766d618/.git_info    2017-06-22 10:32:51.935559567 
+0200
+++ new/booth-1.0+20171123.d4cb8cb/.git_info    2017-12-01 10:44:10.863914778 
+0100
@@ -1 +1 @@
-v1.0-114-g766d618
+v1.0-131-gd4cb8cb
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/booth-1.0+20170619.766d618/README-testing 
new/booth-1.0+20171123.d4cb8cb/README-testing
--- old/booth-1.0+20170619.766d618/README-testing       2017-06-19 
12:23:29.000000000 +0200
+++ new/booth-1.0+20171123.d4cb8cb/README-testing       2017-11-23 
14:07:59.000000000 +0100
@@ -16,9 +16,9 @@
 emulation functions.
 
 There are some restrictions on how booth.conf is formatted.
-There could be several tickets defined, but only the first ticket
-is used for testing. This ticket must have expire and timeout
-parameters configured.
+There may be several tickets defined and all of them will be
+tested, one after another (they will be tested separately).
+The tickets must have expire and timeout parameters configured.
 
 Example booth.conf:
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/booth-1.0+20170619.766d618/docs/boothd.8.txt 
new/booth-1.0+20171123.d4cb8cb/docs/boothd.8.txt
--- old/booth-1.0+20170619.766d618/docs/boothd.8.txt    2017-06-19 
12:23:29.000000000 +0200
+++ new/booth-1.0+20171123.d4cb8cb/docs/boothd.8.txt    2017-11-23 
14:07:59.000000000 +0100
@@ -74,6 +74,9 @@
        'immediate grant': Don't wait for unreachable sites to
        relinquish the ticket. See the 'Booth ticket management'
        section below for more details.
+       For manual tickets this option allows to grant a ticket
+       which is currently granted. See the 'Manual tickets' section
+       below for more details.
 +
        This option may be DANGEROUS. It makes booth grant the ticket
        even though it cannot ascertain that unreachable sites don't
@@ -97,12 +100,11 @@
        Report version information.
 
 *-S*::
-       'systemd' mode: don't fork. This is like '-D' but without the debug 
output.
+       'systemd' mode: don't fork.
+       Disables daemonizing, the process will remain in the foreground.
 
 *-D*::
-       Debug output/don't daemonize.
-       Increases the debug output level; booth daemon remains
-       in the foreground.
+       Increases the debug output level.
 
 *-l* 'lockfile'::
        Use another lock file. By default, the lock file name is
@@ -367,6 +369,14 @@
 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.
 
+*'mode'*::
+       Specifies if the ticket is manual or automatic.
++
+By default all tickets are automatic (that is, they are fully
+controlled by Raft algorithm). Assign the strings "manual" or
+"MANUAL" to define the ticket as manually controlled.
+
+
 One example of a booth configuration file:
 
 -----------------------
@@ -517,6 +527,49 @@
 
 'systemctl' requires the configuration name, even for the default
 name 'booth'.
+-----------------------
+
+
+MANUAL TICKETS
+-----------------------
+Manual tickets allow users to create and manage tickets which are
+subsequently handled by booth without using the Raft algorithm.
+Granting and revoking manual tickets is fully controlled
+by the administrator. It is possible to define a number of manual and
+normal tickets in one GEO cluster.
+
+Automatic ticket management provided by Raft algorithm isn't applied
+to manually controlled tickets. In particular, there is no elections,
+automatic failover procedures, and term expiration.
+
+However, 'booth' controls if a ticket is currently being granted to
+any site and warns the user approprietly.
+
+Tickets which were manually granted to a site, will remain there until
+they are manually revoked. Even if a site becomes offline, the ticket
+will not be moved to another site. This behavior allows administrators
+to make sure that some services will remain in a particular site and
+will not be moved to another site, possibly located in a different
+geographical location.
+
+Also, configuring only manual tickets in a GEO cluster, allows to have
+just two sites in a cluster, without a need of having an arbitrator.
+This is possible because there is no automatic elections and no voting
+performed for manual tickets.
+
+Manual tickets are defined in a configuration files by adding a 'mode'
+ticket parameter and setting it to 'manual' or 'MANUAL':
+
+ticket="manual-ticket"
+    [...]
+    mode = manual
+    [...]
+
+Manual tickets can be granted and revoked by using normal 'grant' and
+'revoke' commands, with the usual flags and parameters. The only
+difference is that specyfiyng '-F' flag during 'grant' command, forced
+a site to become a leader of the specified ticket, even if the ticket
+is granted to another site.
 
 
 EXIT STATUS
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/booth-1.0+20170619.766d618/src/Makefile.am 
new/booth-1.0+20171123.d4cb8cb/src/Makefile.am
--- old/booth-1.0+20170619.766d618/src/Makefile.am      2017-06-19 
12:23:29.000000000 +0200
+++ new/booth-1.0+20171123.d4cb8cb/src/Makefile.am      2017-11-23 
14:07:59.000000000 +0100
@@ -8,10 +8,10 @@
 sbin_PROGRAMS          = boothd
 
 boothd_SOURCES         = config.c main.c raft.c ticket.c  transport.c \
-                         pacemaker.c handler.c request.c attr.c
+                         pacemaker.c handler.c request.c attr.c manual.c
 
 noinst_HEADERS         = booth.h pacemaker.h \
-                         config.h log.h raft.h ticket.h transport.h handler.h 
request.h attr.h
+                         config.h log.h raft.h ticket.h transport.h handler.h 
request.h attr.h manual.h
 
 if BUILD_TIMER_C
 boothd_SOURCES += timer.c
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/booth-1.0+20170619.766d618/src/booth.h 
new/booth-1.0+20171123.d4cb8cb/src/booth.h
--- old/booth-1.0+20170619.766d618/src/booth.h  2017-06-19 12:23:29.000000000 
+0200
+++ new/booth-1.0+20171123.d4cb8cb/src/booth.h  2017-11-23 14:07:59.000000000 
+0100
@@ -295,7 +295,8 @@
        int tcp_fd;
        int udp_fd;
 
-       /* 0-based, used for indexing into per-ticket weights */
+       /* 0-based, used for indexing into per-ticket weights.
+        * -1 for no_leader. */
        int index;
        uint64_t bitmask;
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/booth-1.0+20170619.766d618/src/config.c 
new/booth-1.0+20171123.d4cb8cb/src/config.c
--- old/booth-1.0+20170619.766d618/src/config.c 2017-06-19 12:23:29.000000000 
+0200
+++ new/booth-1.0+20171123.d4cb8cb/src/config.c 2017-11-23 14:07:59.000000000 
+0100
@@ -259,6 +259,7 @@
        tk->term_duration = def->term_duration;
        tk->retries = def->retries;
        memcpy(tk->weight, def->weight, sizeof(tk->weight));
+       tk->mode = def->mode;
 
        if (tkp)
                *tkp = tk;
@@ -331,6 +332,15 @@
        return i;
 }
 
+/* returns TICKET_MODE_AUTO if failed to parse the ticket mode. */
+static ticket_mode_e retrieve_ticket_mode(const char *input)
+{
+       if (strcasecmp(input, "manual") == 0) {
+               return TICKET_MODE_MANUAL;
+       }
+
+       return TICKET_MODE_AUTO;
+}
 
 /* scan val for time; time is [0-9]+(ms)?, i.e. either in seconds
  * or milliseconds
@@ -546,6 +556,7 @@
        defaults.timeout       = DEFAULT_TICKET_TIMEOUT;
        defaults.retries       = DEFAULT_RETRIES;
        defaults.acquire_after = 0;
+       defaults.mode          = TICKET_MODE_AUTO;
 
        error = "";
 
@@ -808,6 +819,11 @@
                        continue;
                }
 
+               if (strcmp(key, "mode") == 0) {
+                       current_tk->mode = retrieve_ticket_mode(val);
+                       continue;
+               }
+
                if (strcmp(key, "weights") == 0) {
                        if (parse_weights(val, current_tk->weight) < 0)
                                goto err;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/booth-1.0+20170619.766d618/src/config.h 
new/booth-1.0+20171123.d4cb8cb/src/config.h
--- old/booth-1.0+20170619.766d618/src/config.h 2017-06-19 12:23:29.000000000 
+0200
+++ new/booth-1.0+20171123.d4cb8cb/src/config.h 2017-11-23 14:07:59.000000000 
+0100
@@ -57,6 +57,11 @@
        GRANT_MANUAL,
 } grant_type_e;
 
+typedef enum {
+       TICKET_MODE_AUTO = 1,
+       TICKET_MODE_MANUAL,
+} ticket_mode_e;
+
 struct toktab {
        const char *str;
        int val;
@@ -107,6 +112,15 @@
 
        /** Node weights. */
        int weight[MAX_NODES];
+
+       /* Mode operation of the ticket.
+        * Set to MANUAL to make sure that the ticket will be manipulated
+        * only by manual commands of the administrator. In such a case
+        * automatic elections will be disabled.
+        * Manual tickets do not have to be renewed every some time.
+        * The leader will continue to send heartbeat messages to other sites.
+        */
+       ticket_mode_e mode;
        /** @} */
 
 
@@ -129,6 +143,27 @@
 
        /** Is the ticket granted? */
        int is_granted;
+
+       /** Which site considered itself a leader.
+        * For manual tickets it is possible, that
+        * more than one site will act as a leader.
+        * This array is used for tracking that situation
+        * and notifying the user about the issue.
+        *
+        * Possible values for every site:
+        *  0: the site does not claim to be the leader
+        *  1: the site considers itself a leader and
+        *     is sending or used to send heartbeat messages
+        *
+        * The site will be marked as '1' until this site
+        * receives revoke confirmation.
+        *
+        * If more than one site has '1', the geo cluster is
+        * considered to have multiple leadership and proper
+        * warning are generated.
+        */
+       int sites_where_granted[MAX_NODES];
+
        /** Timestamp of leadership expiration */
        timetype term_expires;
        /** End of election period */
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/booth-1.0+20170619.766d618/src/main.c 
new/booth-1.0+20171123.d4cb8cb/src/main.c
--- old/booth-1.0+20170619.766d618/src/main.c   2017-06-19 12:23:29.000000000 
+0200
+++ new/booth-1.0+20171123.d4cb8cb/src/main.c   2017-11-23 14:07:59.000000000 
+0100
@@ -93,6 +93,7 @@
 static const struct booth_site _no_leader = {
        .addr_string = "none",
        .site_id = NO_ONE,
+       .index = -1,
 };
 struct booth_site *const no_leader = (struct booth_site*) &_no_leader;
 
@@ -942,6 +943,8 @@
        "  -s <site>     Connect/grant to a different site\n"
        "  -F            Try to grant the ticket immediately\n"
        "                even if not all sites are reachable\n"
+       "                For manual tickets:\n"
+       "                grant a manual ticket even if it has been already 
granted\n"
        "  -w            Wait forever for the outcome of the request\n"
        "  -C            Wait until the ticket is committed to the CIB (grant 
only)\n"
        "  -h            Print this help\n"
@@ -1116,12 +1119,14 @@
                                        strcat(cp, BOOTH_DEFAULT_CONF_EXT);
                        }
                        break;
+
                case 'D':
                        debug_level++;
-                       enable_stderr = 1;
-                       /* Fall through */
+                       break;
+
                case 'S':
                        daemonize = 0;
+                       enable_stderr = 1;
                        break;
 
                case 'l':
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/booth-1.0+20170619.766d618/src/manual.c 
new/booth-1.0+20171123.d4cb8cb/src/manual.c
--- old/booth-1.0+20170619.766d618/src/manual.c 1970-01-01 01:00:00.000000000 
+0100
+++ new/booth-1.0+20171123.d4cb8cb/src/manual.c 2017-11-23 14:07:59.000000000 
+0100
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2017 Chris Kowalczyk <ckowalc...@suse.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "manual.h"
+
+#include "transport.h"
+#include "ticket.h"
+#include "config.h"
+#include "log.h"
+#include "request.h"
+
+/* For manual tickets, manual_selection function is an equivalent
+ * of new_election function used for assigning automatic tickets.
+ * The workflow here is much simplier, as no voting is performed,
+ * and the current node doesn't have to wait for any responses
+ * from other sites.
+ */
+int manual_selection(struct ticket_config *tk,
+       struct booth_site *preference, int update_term, cmd_reason_t reason)
+{
+       if (local->type != SITE)
+               return 0;
+
+       tk_log_debug("starting manual selection (caused by %s %s)",
+                               state_to_string(reason),
+                               reason == OR_AGAIN ? 
state_to_string(tk->election_reason) : "" );
+
+       // Manual selection is done without any delay, the leader is assigned
+       set_leader(tk, local);
+       set_state(tk, ST_LEADER);
+
+       // Manual tickets never expire, we don't specify expiration time
+
+       // Make sure that election_end field is empty
+       time_reset(&tk->election_end);
+
+       // Make sure that delay commit is empty, as manual tickets don't
+       // wait for any kind of confirmation from other nodes
+       time_reset(&tk->delay_commit);
+
+       save_committed_tkt(tk);
+
+       // Inform others about the new leader
+       ticket_broadcast(tk, OP_HEARTBEAT, OP_ACK, RLT_SUCCESS, 0);
+       tk->ticket_updated = 0;
+
+       return 0;
+}
+
+/* This function is called for manual tickets that were
+ * revoked from another site, which this site doesn't
+ * consider as a leader.
+ */
+int process_REVOKE_for_manual_ticket (
+       struct ticket_config *tk,
+       struct booth_site *sender,
+       struct boothc_ticket_msg *msg)
+{
+       int rv;
+
+       // For manual tickets, we may end up having two leaders.
+       // If one of them is revoked, it will send information 
+       // to all members of the GEO cluster.
+       
+       // We may find ourselves here if this particular site
+       // has not been following the leader which had been revoked
+       // (and which had sent this message).
+
+       // We send the ACK, to satisfy the requestor.
+       rv = send_msg(OP_ACK, tk, sender, msg);         
+
+       // Mark this ticket as not granted to the sender anymore.
+       mark_ticket_as_revoked(tk, sender);
+       
+       if (tk->state == ST_LEADER) {
+               tk_log_warn("%s wants to revoke ticket, "
+                       "but this site is itself a leader",
+                       site_string(sender));
+
+               // Because another leader is presumably stepping down,
+               // let's notify other sites that now we are the only leader.
+               ticket_broadcast(tk, OP_HEARTBEAT, OP_ACK, RLT_SUCCESS, 0);
+       } else {
+               tk_log_warn("%s wants to revoke ticket, "
+                       "but this site is not following it",
+                       site_string(sender));
+       }
+
+       return rv;
+}
+
+
+
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/booth-1.0+20170619.766d618/src/manual.h 
new/booth-1.0+20171123.d4cb8cb/src/manual.h
--- old/booth-1.0+20170619.766d618/src/manual.h 1970-01-01 01:00:00.000000000 
+0100
+++ new/booth-1.0+20171123.d4cb8cb/src/manual.h 2017-11-23 14:07:59.000000000 
+0100
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2017 Chris Kowalczyk <ckowalc...@suse.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef _MANUAL_H
+#define _MANUAL_H
+
+#include "booth.h"
+
+struct ticket_config;
+
+int manual_selection(struct ticket_config *tk,
+               struct booth_site *new_leader, int update_term, cmd_reason_t 
reason);
+
+int process_REVOKE_for_manual_ticket (
+               struct ticket_config *tk,
+               struct booth_site *sender,
+               struct boothc_ticket_msg *msg);
+
+
+#endif /* _MANUAL_H */
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/booth-1.0+20170619.766d618/src/pacemaker.c 
new/booth-1.0+20171123.d4cb8cb/src/pacemaker.c
--- old/booth-1.0+20170619.766d618/src/pacemaker.c      2017-06-19 
12:23:29.000000000 +0200
+++ new/booth-1.0+20171123.d4cb8cb/src/pacemaker.c      2017-11-23 
14:07:59.000000000 +0100
@@ -513,7 +513,7 @@
                if (tk->is_granted) {
                        log_warn("%s: granted here, assume it belonged to us",
                                tk->name);
-                       tk->leader = local;
+                       set_leader(tk, local);
                }
        }
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/booth-1.0+20170619.766d618/src/raft.c 
new/booth-1.0+20171123.d4cb8cb/src/raft.c
--- old/booth-1.0+20170619.766d618/src/raft.c   2017-06-19 12:23:29.000000000 
+0200
+++ new/booth-1.0+20171123.d4cb8cb/src/raft.c   2017-11-23 14:07:59.000000000 
+0100
@@ -30,7 +30,7 @@
 #include "ticket.h"
 #include "request.h"
 #include "log.h"
-
+#include "manual.h"
 
 
 inline static void clear_election(struct ticket_config *tk)
@@ -370,6 +370,8 @@
                tk_log_warn("different leader %s wants to update "
                                "our ticket, sending reject",
                        site_string(leader));
+
+               mark_ticket_as_granted(tk, sender);
                return send_reject(sender, tk, RLT_TERM_OUTDATED, msg);
        }
 
@@ -399,10 +401,18 @@
                /* assume that our ack got lost */
                rv = send_msg(OP_ACK, tk, sender, msg);
        } else if (tk->leader != sender) {
-               tk_log_error("%s wants to revoke ticket, "
-                               "but it is not granted there (ignoring)",
-                               site_string(sender));
-               return -1;
+               if (!is_manual(tk)) {
+                       tk_log_error("%s wants to revoke ticket, "
+                                       "but it is not granted there 
(ignoring)",
+                                       site_string(sender));
+                       return -1;
+               } else {
+                       rv = process_REVOKE_for_manual_ticket(tk, sender, msg);
+       
+                       // Ticket data stored in this site is not modified. 
This means
+                       // that this site will still follow another leader (the 
one which
+                       // has not been revoked) or be a leader itself.
+               }
        } else if (tk->state != ST_FOLLOWER) {
                tk_log_error("unexpected ticket revoke from %s "
                                "(in state %s) (ignoring)",
@@ -413,8 +423,7 @@
                tk_log_info("%s revokes ticket",
                                site_string(tk->leader));
                save_committed_tkt(tk);
-               reset_ticket(tk);
-               set_leader(tk, no_leader);
+               reset_ticket_and_set_no_leader(tk);
                ticket_write(tk);
                rv = send_msg(OP_ACK, tk, sender, msg);
        }
@@ -959,6 +968,8 @@
                        tk_log_warn("unexpected message %s, from %s",
                                state_to_string(cmd),
                                site_string(sender));
+                               mark_ticket_as_granted(tk, sender);
+
                        if (ticket_seems_ok(tk))
                                send_reject(sender, tk, RLT_TERM_STILL_VALID, 
msg);
                        rv = -EINVAL;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/booth-1.0+20170619.766d618/src/ticket.c 
new/booth-1.0+20171123.d4cb8cb/src/ticket.c
--- old/booth-1.0+20170619.766d618/src/ticket.c 2017-06-19 12:23:29.000000000 
+0200
+++ new/booth-1.0+20171123.d4cb8cb/src/ticket.c 2017-11-23 14:07:59.000000000 
+0100
@@ -40,6 +40,7 @@
 #include "raft.h"
 #include "handler.h"
 #include "request.h"
+#include "manual.h"
 
 #define TK_LINE                        256
 
@@ -182,15 +183,27 @@
 static void ext_prog_failed(struct ticket_config *tk,
                int start_election)
 {
-       /* Give it to somebody else.
-        * Just send a VOTE_FOR message, so the
-        * others can start elections. */
-       if (leader_and_valid(tk)) {
-               save_committed_tkt(tk);
-               reset_ticket(tk);
-               ticket_write(tk);
-               if (start_election) {
-                       ticket_broadcast(tk, OP_VOTE_FOR, OP_REQ_VOTE, 
RLT_SUCCESS, OR_LOCAL_FAIL);
+       if (!is_manual(tk)) {
+               /* Give it to somebody else.
+                * Just send a VOTE_FOR message, so the
+                * others can start elections. */
+               if (leader_and_valid(tk)) {
+                       save_committed_tkt(tk);
+                       reset_ticket(tk);
+                       ticket_write(tk);       
+                       if (start_election) {
+                               ticket_broadcast(tk, OP_VOTE_FOR, OP_REQ_VOTE, 
RLT_SUCCESS, OR_LOCAL_FAIL);
+                       }
+               }
+       } else {
+               /* There is not much we can do now because
+                * the manual ticket cannot be relocated.
+                * Just warn the user. */
+               if (tk->leader == local) {
+                       save_committed_tkt(tk);
+                       reset_ticket(tk);
+                       ticket_write(tk);       
+                       log_error("external test failed on the specified 
machine, cannot acquire a manual ticket");
                }
        }
 }
@@ -299,7 +312,12 @@
                return RLT_EXT_FAILED;
        }
 
-       rv = new_election(tk, local, 1, reason);
+       if (is_manual(tk)) {
+               rv = manual_selection(tk, local, 1, reason);
+       } else {
+               rv = new_election(tk, local, 1, reason);
+       }
+
        return rv ? RLT_SYNC_FAIL : 0;
 }
 
@@ -314,8 +332,18 @@
 
        if (tk->leader == local)
                return RLT_SUCCESS;
-       if (is_owned(tk))
-               return RLT_OVERGRANT;
+       if (is_owned(tk)) {
+               if (is_manual(tk) && (options & OPT_IMMEDIATE)) {
+                       /* -F flag has been used while granting a manual ticket.
+                        * The ticket will be granted and may end up being 
granted
+                        * on multiple sites */
+                       tk_log_warn("manual ticket forced to be granted! be 
aware that "
+                                       "you may end up having two sites 
holding the same manual "
+                                       "ticket! revoke the ticket from the 
unnecessary site!");
+               } else {
+                       return RLT_OVERGRANT;
+               }
+       }
 
        set_future_time(&tk->delay_commit, tk->term_duration + 
tk->acquire_after);
 
@@ -340,8 +368,7 @@
        tk_log_info("revoking ticket");
 
        save_committed_tkt(tk);
-       reset_ticket(tk);
-       set_leader(tk, no_leader);
+       reset_ticket_and_set_no_leader(tk);
        ticket_write(tk);
        ticket_broadcast(tk, OP_REVOKE, OP_ACK, RLT_SUCCESS, OR_ADMIN);
 }
@@ -367,20 +394,32 @@
        char timeout_str[64];
        char pending_str[64];
        char *data, *cp;
-       int i, alloc;
+       int i, alloc, site_index;
        time_t ts;
+       int multiple_grant_warning_length = 0;
 
        *pdata = NULL;
        *len = 0;
 
-       alloc = booth_conf->ticket_count * (BOOTH_NAME_LEN * 2 + 128);
+       alloc = booth_conf->ticket_count * (BOOTH_NAME_LEN * 2 + 128 + 16);
+
+       foreach_ticket(i, tk) {
+               multiple_grant_warning_length = 
number_sites_marked_as_granted(tk);
+
+               if (multiple_grant_warning_length > 1) {
+                       // 164: 55 + 45 + 2*number_of_multiple_sites + some 
margin
+                       alloc += 164 + BOOTH_NAME_LEN * 
(1+multiple_grant_warning_length);
+               }
+       }
+
        data = malloc(alloc);
        if (!data)
                return -ENOMEM;
 
        cp = data;
        foreach_ticket(i, tk) {
-               if (is_time_set(&tk->term_expires)) {
+               if ((!is_manual(tk)) && is_time_set(&tk->term_expires)) {
+                       /* Manual tickets doesn't have term_expires defined */
                        ts = wall_ts(&tk->term_expires);
                        strftime(timeout_str, sizeof(timeout_str), "%F %T",
                                        localtime(&ts));
@@ -407,19 +446,55 @@
                if (is_owned(tk)) {
                        cp += snprintf(cp,
                                        alloc - (cp - data),
-                                       ", expires: %s%s\n",
+                                       ", expires: %s%s",
                                        timeout_str,
                                        pending_str);
-               } else {
-                       cp += snprintf(cp, alloc - (cp - data), "\n");
                }
 
+               if (is_manual(tk)) {
+                       cp += snprintf(cp,
+                                       alloc - (cp - data),
+                                       " [manual mode]");
+               }
+
+               cp += snprintf(cp, alloc - (cp - data), "\n");
+
                if (alloc - (cp - data) <= 0) {
                        free(data);
                        return -ENOMEM;
                }
        }
 
+       foreach_ticket(i, tk) {
+               multiple_grant_warning_length = 
number_sites_marked_as_granted(tk);
+
+               if (multiple_grant_warning_length > 1) {
+                       cp += snprintf(cp,
+                                       alloc - (cp - data),
+                                       "\nWARNING: The ticket %s is granted to 
multiple sites: ",  // ~55 characters
+                                       tk->name);
+
+                       for(site_index=0; site_index<booth_conf->site_count; 
++site_index) {
+                               if (tk->sites_where_granted[site_index] > 0) {
+                                       cp += snprintf(cp,
+                                               alloc - (cp - data),
+                                               "%s",
+                                               
site_string(&(booth_conf->site[site_index])));
+
+                                       if (--multiple_grant_warning_length > 
0) {
+                                               cp += snprintf(cp,
+                                                       alloc - (cp - data),
+                                                       ", ");
+                                       }
+                               }
+                       }
+
+                       cp += snprintf(cp,
+                               alloc - (cp - data),
+                               ". Revoke the ticket from the faulty 
sites.\n");  // ~45 characters
+               }
+       }
+
        *pdata = data;
        *len = cp - data;
 
@@ -455,12 +530,20 @@
        tk->voted_for = NULL;
 }
 
+void reset_ticket_and_set_no_leader(struct ticket_config *tk)
+{
+       mark_ticket_as_revoked_from_leader(tk);
+       reset_ticket(tk);
+
+       tk->leader = no_leader;
+       tk_log_debug("ticket leader set to no_leader");
+}
 
 static void log_reacquire_reason(struct ticket_config *tk)
 {
        int valid;
        const char *where_granted = "\0";
-       char buff[64];
+       char buff[75];
 
        valid = is_time_set(&tk->term_expires) && !is_past(&tk->term_expires);
 
@@ -606,9 +689,12 @@
                goto reply_now;
        }
 
-       if ((cmd == CMD_GRANT) && is_owned(tk)) {
+       /* Perform the initial check before granting
+        * an already granted non-manual ticket */
+       if ((!is_manual(tk) && (cmd == CMD_GRANT) && is_owned(tk))) {
                log_warn("client wants to grant an (already granted!) ticket 
%s",
                                msg->ticket.id);
+
                rv = RLT_OVERGRANT;
                goto reply_now;
        }
@@ -726,12 +812,15 @@
        if (tk->ticket_updated >= 2)
                return 0;
 
-       if (tk->ticket_updated < 1) {
-               tk->ticket_updated = 1;
-               get_time(&now);
-               copy_time(&now, &tk->last_renewal);
-               set_future_time(&tk->term_expires, tk->term_duration);
-               rv = ticket_broadcast(tk, OP_UPDATE, OP_ACK, RLT_SUCCESS, 0);
+       /* for manual tickets, we don't set time expiration */
+       if (!is_manual(tk)) {
+               if (tk->ticket_updated < 1) {
+                       tk->ticket_updated = 1;
+                       get_time(&now);
+                       copy_time(&now, &tk->last_renewal);
+                       set_future_time(&tk->term_expires, tk->term_duration);
+                       rv = ticket_broadcast(tk, OP_UPDATE, OP_ACK, 
RLT_SUCCESS, 0);
+               }
        }
 
        if (tk->ticket_updated < 2) {
@@ -907,6 +996,7 @@
 
        tk->lost_leader = tk->leader;
        save_committed_tkt(tk);
+       mark_ticket_as_revoked_from_leader(tk);
        reset_ticket(tk);
        set_state(tk, ST_FOLLOWER);
        if (local->type == SITE) {
@@ -938,21 +1028,39 @@
                break;
 
        case ST_FOLLOWER:
-               /* leader/ticket lost? and we didn't vote yet */
-               tk_log_debug("leader: %s, voted_for: %s",
-                               site_string(tk->leader),
-                               site_string(tk->voted_for));
-               if (!tk->leader) {
-                       if (!tk->voted_for || !tk->in_election) {
-                               disown_ticket(tk);
-                               if (!new_election(tk, NULL, 1, OR_AGAIN)) {
+               if (!is_manual(tk)) {
+                       /* leader/ticket lost? and we didn't vote yet */
+                       tk_log_debug("leader: %s, voted_for: %s",
+                                       site_string(tk->leader),
+                                       site_string(tk->voted_for));
+                       if (!tk->leader) {
+                               if (!tk->voted_for || !tk->in_election) {
+                                       disown_ticket(tk);
+                                       if (!new_election(tk, NULL, 1, 
OR_AGAIN)) {
+                                               ticket_activate_timeout(tk);
+                                       }
+                               } else {
+                                       /* we should restart elections in case 
nothing
+                                       * happens in the meantime */
+                                       tk->in_election = 0;
                                        ticket_activate_timeout(tk);
                                }
+                       }
+               } else {
+                       /* for manual tickets, also try to acquire ticket on 
grant
+                        * in the Follower state (because we may end up having
+                        * two Leaders) */
+                       if (has_extprog_exited(tk)) {
+                               rv = acquire_ticket(tk, OR_ADMIN);
+                               if (rv != 0) { /* external program failed */
+                                       tk->outcome = rv;
+                                       foreach_tkt_req(tk, notify_client);
+                               }
                        } else {
-                               /* we should restart elections in case nothing
-                                * happens in the meantime */
-                               tk->in_election = 0;
-                               ticket_activate_timeout(tk);
+                               /* Otherwise, just send ACKs if needed */
+                               if (tk->acks_expected) {
+                                       handle_resends(tk);
+                               }
                        }
                }
                break;
@@ -1010,9 +1118,11 @@
        }
 
        /* Has an owner, has an expiry date, and expiry date in the past?
-        * Losing the ticket must happen in _every_ state.
+        * For automatic tickets, losing the ticket must happen
+        * in _every_ state.
         */
-       if (is_owned(tk) && is_time_set(&tk->term_expires)
+       if ((!is_manual(tk)) &&
+                       is_owned(tk) && is_time_set(&tk->term_expires)
                        && is_past(&tk->term_expires)) {
                ticket_lost(tk);
                goto out;
@@ -1159,56 +1269,72 @@
 {
        timetype near_future, tv, next_vote;
 
-       /* At least every hour, perhaps sooner (default) */
-       ticket_next_cron_in(tk, 3600*TIME_RES);
        set_future_time(&near_future, 10);
 
-       switch (tk->state) {
-       case ST_LEADER:
-               assert(tk->leader == local);
-
-               get_next_election_time(tk, &next_vote);
+       if (!is_manual(tk)) {
+               /* At least every hour, perhaps sooner (default) */
+               tk_log_debug("ticket will be woken up after up to one hour");
+               ticket_next_cron_in(tk, 3600*TIME_RES);
+
+               switch (tk->state) {
+               case ST_LEADER:
+                       assert(tk->leader == local);
+
+                       get_next_election_time(tk, &next_vote);
+
+                       /* If timestamp is in the past, wakeup in
+                       * near future */
+                       if (!is_time_set(&next_vote)) {
+                               tk_log_debug("next ts unset, wakeup soon");
+                               ticket_next_cron_at(tk, &near_future);
+                       } else if (is_past(&next_vote)) {
+                               int tdiff = time_left(&next_vote);
+                               tk_log_debug("next ts in the past " 
intfmt(tdiff));
+                               ticket_next_cron_at(tk, &near_future);
+                       } else {
+                               ticket_next_cron_at(tk, &next_vote);
+                       }
+                       break;
 
-               /* If timestamp is in the past, wakeup in
-                * near future */
-               if (!is_time_set(&next_vote)) {
-                       tk_log_debug("next ts unset, wakeup soon");
-                       ticket_next_cron_at(tk, &near_future);
-               } else if (is_past(&next_vote)) {
-                       int tdiff = time_left(&next_vote);
-                       tk_log_debug("next ts in the past " intfmt(tdiff));
-                       ticket_next_cron_at(tk, &near_future);
-               } else {
-                       ticket_next_cron_at(tk, &next_vote);
-               }
-               break;
+               case ST_CANDIDATE:
+                       assert(is_time_set(&tk->election_end));
+                       ticket_next_cron_at(tk, &tk->election_end);
+                       break;
 
-       case ST_CANDIDATE:
-               assert(is_time_set(&tk->election_end));
-               ticket_next_cron_at(tk, &tk->election_end);
-               break;
+               case ST_INIT:
+               case ST_FOLLOWER:
+                       /* If there is (or should be) some owner, check on it 
later on.
+                       * If no one is interested - don't care. */
+                       if (is_owned(tk)) {
+                               interval_add(&tk->term_expires, 
tk->acquire_after, &tv);
+                               ticket_next_cron_at(tk, &tv);
+                       }
+                       break;
 
-       case ST_INIT:
-       case ST_FOLLOWER:
-               /* If there is (or should be) some owner, check on it later on.
-                * If no one is interested - don't care. */
-               if (is_owned(tk)) {
-                       interval_add(&tk->term_expires, tk->acquire_after, &tv);
-                       ticket_next_cron_at(tk, &tv);
+               default:
+                       tk_log_error("unknown ticket state: %d", tk->state);
                }
-               break;
 
-       default:
-               tk_log_error("unknown ticket state: %d", tk->state);
-       }
-
-       if (tk->next_state) {
-               /* we need to do something soon here */
-               if (!tk->acks_expected) {
-                       ticket_next_cron_at(tk, &near_future);
-               } else {
-                       ticket_activate_timeout(tk);
+               if (tk->next_state) {
+                       /* we need to do something soon here */
+                       if (!tk->acks_expected) {
+                               ticket_next_cron_at(tk, &near_future);
+                       } else {
+                               ticket_activate_timeout(tk);
+                       }
                }
+       } else {
+               /* At least six minutes, to make sure that multi-leader 
situations
+                * will be solved promptly.
+                */
+               tk_log_debug("manual ticket will be woken up after up to six 
minutes");
+               ticket_next_cron_in(tk, 60*TIME_RES);
+
+               /* For manual tickets, no earlier timeout could be set in a 
similar
+                * way as it is done in a switch above for automatic tickets.
+                * The reason is that term's timeout is INF and no Raft-based 
elections
+                * are performed.
+                */
        }
 
        if (ANYDEBUG) {
@@ -1229,6 +1355,24 @@
 }
 
 
+int is_manual(struct ticket_config *tk)
+{
+       return (tk->mode == TICKET_MODE_MANUAL) ? 1 : 0;
+}
+
+int number_sites_marked_as_granted(struct ticket_config *tk)
+{
+       int i, result = 0;
+
+       for(i=0; i<booth_conf->site_count; ++i) {
+               result += tk->sites_where_granted[i];
+       }
+
+       return result;
+}
+
+
+
 /* 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)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/booth-1.0+20170619.766d618/src/ticket.h 
new/booth-1.0+20171123.d4cb8cb/src/ticket.h
--- old/booth-1.0+20170619.766d618/src/ticket.h 2017-06-19 12:23:29.000000000 
+0200
+++ new/booth-1.0+20171123.d4cb8cb/src/ticket.h 2017-11-23 14:07:59.000000000 
+0100
@@ -39,8 +39,36 @@
 #define foreach_node(i_,n_) for(i_=0; (n_=booth_conf->site+i_, 
i_<booth_conf->site_count); i_++)
 
 #define set_leader(tk, who) do { \
+       if (who == NULL) { \
+               mark_ticket_as_revoked_from_leader(tk); \
+       } \
+       \
        tk->leader = who; \
        tk_log_debug("ticket leader set to %s", ticket_leader_string(tk)); \
+       \
+       if (tk->leader) { \
+               mark_ticket_as_granted(tk, tk->leader); \
+       } \
+} while(0)
+
+#define mark_ticket_as_granted(tk, who) do { \
+       if (is_manual(tk) && (who->index > -1)) { \
+               tk->sites_where_granted[who->index] = 1; \
+               tk_log_debug("manual ticket marked as granted to %s", 
ticket_leader_string(tk)); \
+       } \
+} while(0)
+
+#define mark_ticket_as_revoked(tk, who) do { \
+       if (is_manual(tk) && who && (who->index > -1)) { \
+               tk->sites_where_granted[who->index] = 0; \
+               tk_log_debug("manual ticket marked as revoked from %s", 
site_string(who)); \
+       } \
+} while(0)
+
+#define mark_ticket_as_revoked_from_leader(tk) do { \
+       if (tk->leader) { \
+               mark_ticket_as_revoked(tk, tk->leader); \
+       } \
 } while(0)
 
 #define set_state(tk, newst) do { \
@@ -69,6 +97,7 @@
 
 int ticket_recv(void *buf, struct booth_site *source);
 void reset_ticket(struct ticket_config *tk);
+void reset_ticket_and_set_no_leader(struct ticket_config *tk);
 void update_ticket_state(struct ticket_config *tk, struct booth_site *sender);
 int setup_ticket(void);
 int check_max_len_valid(const char *s, int max);
@@ -103,6 +132,9 @@
 void add_random_delay(struct ticket_config *tk);
 void schedule_election(struct ticket_config *tk, cmd_reason_t reason);
 
+int is_manual(struct ticket_config *tk);
+int number_sites_marked_as_granted(struct ticket_config *tk);
+
 int check_attr_prereq(struct ticket_config *tk, grant_type_e grant_type);
 
 static inline void ticket_next_cron_at(struct ticket_config *tk, timetype 
*when)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/booth-1.0+20170619.766d618/src/transport.c 
new/booth-1.0+20171123.d4cb8cb/src/transport.c
--- old/booth-1.0+20170619.766d618/src/transport.c      2017-06-19 
12:23:29.000000000 +0200
+++ new/booth-1.0+20171123.d4cb8cb/src/transport.c      2017-11-23 
14:07:59.000000000 +0100
@@ -237,16 +237,38 @@
                                                        BOOTH_IPADDR_LEN);
                                }
 
-                               /* First try with exact addresses, then 
optionally with subnet matching. */
+                               /* Try to find the exact address or the address 
with subnet matching.
+                                * The function find_address will be called for 
each address received
+                                * from NLMSG_DATA above.
+                                * The exact match will be prefered. If no 
exact match is found,
+                                * the function find_address will try to return 
another, most similar
+                                * address (with the longest possible number of 
same bytes). */
                                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);
                                        }
                                }
+                               /* If the previous NLMSG_DATA calls have 
already allowed us
+                                * to find an address with address_bits_matched 
matching bits,
+                                * then no other better non-exact address can 
bo found.
+                                * But we can still try to find an exact match, 
so let us
+                                * call the function find_address with disabled 
searching of
+                                * similar addresses (fuzzy_allowed == 0) */
+                               else if (ifa->ifa_prefixlen == 
address_bits_matched) {
+                                       find_address(ipaddr,
+                                                       ifa->ifa_family, 
ifa->ifa_prefixlen,
+                                                       0 /* fuzzy_allowed */, 
&me, &address_bits_matched);
+
+                                       if (me) {
+                                               log_debug("found myself at %s 
(exact match)", 
+                                                               
site_string(me));
+                                       }
+                               }
                        }
                        h = NLMSG_NEXT(h, status);
                }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/booth-1.0+20170619.766d618/test/boothrunner.py 
new/booth-1.0+20171123.d4cb8cb/test/boothrunner.py
--- old/booth-1.0+20170619.766d618/test/boothrunner.py  2017-06-19 
12:23:29.000000000 +0200
+++ new/booth-1.0+20171123.d4cb8cb/test/boothrunner.py  2017-11-23 
14:07:59.000000000 +0100
@@ -31,6 +31,9 @@
     def set_debug(self):
         self.args += [ '-D' ]
 
+    def set_foreground(self):
+        self.args += [ '-S' ]
+
     def all_args(self):
         return [ self.boothd_path ] + self.args + self.final_args
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/booth-1.0+20170619.766d618/test/live_test.sh 
new/booth-1.0+20171123.d4cb8cb/test/live_test.sh
--- old/booth-1.0+20170619.766d618/test/live_test.sh    2017-06-19 
12:23:29.000000000 +0200
+++ new/booth-1.0+20171123.d4cb8cb/test/live_test.sh    2017-11-23 
14:07:59.000000000 +0100
@@ -287,7 +287,7 @@
        local h rc=0
        local tmpf
        for h in $sites $arbitrators; do
-               rsync -q -e "ssh $SSH_OPTS" $cnf root@$h:$run_cnf
+               rsync -q -e "ssh $SSH_OPTS" $1 root@$h:$run_cnf
                rc=$((rc|$?))
                if [ -n "$authfile" ]; then
                        tmpf=`mktemp`
@@ -398,6 +398,13 @@
 /^ticket.*'$tkt'/ {n=1}
 ' $cnf
 }
+get_mode() {
+       awk '
+n && /^[[:space:]]*mode/ {print $NF; exit}
+n && (/^$/ || /^ticket.*/) {exit}
+/^ticket.*'$tkt'/ {n=1}
+' $cnf
+}
 
 set_site_attr() {
        local site
@@ -440,7 +447,7 @@
 }
 n && (/^$/ || /^ticket.*/) {exit}
 /^ticket.*'$tkt'/ {n=1}
-' $cnf
+' $1
 }
 wait_exp() {
        sleep $T_expire
@@ -588,17 +595,25 @@
 check_booth_consistency() {
        local tlist tlist_validate rc rc_lead maxdiff
        tlist=`forall_withname booth list 2>/dev/null | grep $tkt`
-       tlist_validate=`echo "$tlist" |
-               sed 's/[^:]*: //;s/commit:.*//;s/NONE/none/'`
-       maxdiff=`echo "$tlist" | max_booth_time_diff`
-       test "$maxdiff" -eq 0
-       rc=$?
+
+       # Check time consistency
+       ticket_times=$(echo "$tlist" | booth_list_fld 3)
+       if [[ $ticket_times == *"INF"* ]]; then
+               rc=0
+       else
+               maxdiff=`echo "$tlist" | max_booth_time_diff`
+               test "$maxdiff" -eq 0
+               rc=$?
+       fi
+
+       # Check leader consistency
        echo "$tlist" | booth_leader_consistency
        rc_lead=$?
        if [ $rc_lead -ne 0 ]; then
                echo "$tlist" | booth_leader_consistency_2
                rc_lead=$(($rc_lead + $?))  # rc_lead=2 if the prev test failed
        fi
+
        rc=$(($rc | $rc_lead<<1))
        test $rc -eq 0 && return
        cat<<EOF | logmsg
@@ -668,7 +683,7 @@
        TEST=$1
        start_time=`date`
        start_ts=`date +%s`
-       echo -n "Testing: $1... "
+       echo -n "Testing: $1 (ticket: $tkt)... "
        can_run_test $1 || return 0
        echo "==================================================" | logmsg
        echo "starting booth test $1 ..." | logmsg
@@ -706,7 +721,7 @@
        esac
        end_time=`date`
        end_ts=`date +%s`
-       echo "finished booth test $1 ($usrmsg)" | logmsg
+       echo "finished booth test $1 ($tkt): $usrmsg" | logmsg
        echo "==================================================" | logmsg
        is_function recover_$1 && recover_$1
        reset_netem_env
@@ -1202,13 +1217,6 @@
 : ${port:=9929}
 site_cnt=`echo $internal_sites | wc -w`
 arbitrator_cnt=`echo $internal_arbitrators | wc -w`
-tkt=`get_tkt < $cnf`
-eval `get_tkt_settings`
-
-MIN_TIMEOUT=`awk -v tm=$T_timeout 'BEGIN{
-               if (tm >= 2) print tm;
-               else print 2*tm;
-               }'`
 
 if [ "$1" = "__netem__" ]; then
        shift 1
@@ -1222,15 +1230,6 @@
        usage 1
 }
 
-[ -z "$T_expire" ] && {
-       echo set $tkt expire time in $cnf
-       usage 1
-}
-
-if [ -z "$T_renewal_freq" ]; then
-       T_renewal_freq=$((T_expire/2))
-fi
-
 exec 2>$logf
 BASH_XTRACEFD=2
 PS4='+ `date +"%T"`: '
@@ -1244,22 +1243,8 @@
 authfile=`get_value authfile < $cnf`
 run_site 1 'test -f '"$authfile"' || booth-keygen '"$authfile"
 
-sync_conf || exit
-reboot_test
-all_booth_status || {
-       start_booth
-       all_booth_status || {
-               echo "some booth servers couldn't be started"
-               exit 1
-       }
-}
-revoke_ticket
-
-ABSPATH=`get_prog_abspath`
-
-dump_conf | logmsg
-
 TESTS="$@"
+MANUAL_TESTS="$@"
 
 : ${TESTS:="grant longgrant grant_noarb grant_elsewhere
 grant_site_lost grant_site_reappear revoke
@@ -1268,9 +1253,99 @@
 failover split_leader split_follower split_edge
 external_prog_failed attr_prereq_ok attr_prereq_fail"}
 
+: ${MANUAL_TESTS:="grant longgrant grant_noarb grant_elsewhere
+grant_site_lost
+restart_granted reload_granted
+split_leader split_follower split_edge
+ "}
+
+#get total number od lines in the file
+conf_file_size=$(grep -c $ $cnf)
+
+#get line numbers for all tickets
+ticket_line_numbers=$(grep -n ticket $cnf | cut -d: -f1)
+read -a TICKET_LINES<<< $ticket_line_numbers
+
+#save the part of config located before ticket definitions
+sed -n "1,$((${TICKET_LINES[0]}-1))p" $cnf > ${cnf}_main.config
+
+#create a separate file for every ticket data
+number_of_tickets=0
+for i in $(seq 0 1 $((${#TICKET_LINES[@]}-1))); do
+       ticket_line_start=${TICKET_LINES[i]}
+       ticket_line_end=$((${TICKET_LINES[i+1]}-1))
+       if [ ${ticket_line_end} -lt 0 ]; then
+               # for the last ticket
+               ticket_line_end=${conf_file_size}
+       fi
+       sed -n "${ticket_line_start},${ticket_line_end}p" $cnf > 
${cnf}_${number_of_tickets}.ticket
+       number_of_tickets=$((number_of_tickets+1))
+done
+
+
 master_rc=0 # updated in runtest
-for t in $TESTS; do
-       runtest $t
+
+for i in `seq 0 $(($number_of_tickets-1))`
+do
+       cat ${cnf}_main.config > booth_${i}.conf
+       cat ${cnf}_${i}.ticket >> booth_${i}.conf
+
+       tkt=`get_tkt < booth_${i}.conf`
+
+       if [ -z "$tkt" ]; then
+               echo "Skipping empty ticket.."
+               continue
+       fi
+
+       sync_conf booth_${i}.conf || exit
+       reboot_test
+       all_booth_status || {
+               start_booth
+               all_booth_status || {
+                       echo "some booth servers couldn't be started"
+                       exit 1
+               }
+       }
+
+       ABSPATH=`get_prog_abspath`
+
+       dump_conf | logmsg
+
+
+       eval `get_tkt_settings booth_${i}.conf`
+
+       MIN_TIMEOUT=`awk -v tm=$T_timeout 'BEGIN{
+                       if (tm >= 2) print tm;
+                       else print 2*tm;
+                       }'`
+
+       [ -z "$T_expire" ] && {
+               echo set $tkt expire time in $cnf
+               usage 1
+       }
+
+       if [ -z "$T_renewal_freq" ]; then
+               T_renewal_freq=$((T_expire/2))
+       fi
+
+       revoke_ticket
+
+       T_mode=`get_mode`
+       T_mode_lowercase=$(echo "$T_mode" | tr '[:upper:]' '[:lower:]')
+
+       if [[ $T_mode_lowercase == *"manual"* ]]; then
+               echo "Running tests for manual tickets.."
+
+               for t in $MANUAL_TESTS; do
+                       runtest $t
+               done
+       else
+               echo "Running tests for automatic Raft tickets.."
+
+               for t in $TESTS; do
+                       runtest $t
+               done
+       fi
 done
 
 exit $master_rc
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/booth-1.0+20170619.766d618/test/serverenv.py 
new/booth-1.0+20171123.d4cb8cb/test/serverenv.py
--- old/booth-1.0+20170619.766d618/test/serverenv.py    2017-06-19 
12:23:29.000000000 +0200
+++ new/booth-1.0+20171123.d4cb8cb/test/serverenv.py    2017-11-23 
14:07:59.000000000 +0100
@@ -33,7 +33,7 @@
 
     def run_booth(self, expected_exitcode, expected_daemon,
                   config_text=None, config_file=None, lock_file=True,
-                  args=[], debug=False):
+                  args=[], debug=False, foreground=False):
         '''
         Runs boothd.  Defaults to using a temporary lock file and the
         standard config file path.  There are four possible types of
@@ -42,7 +42,7 @@
             - boothd exits non-zero without launching a daemon (setup phase 
failed,
               e.g. due to invalid configuration file)
             - boothd exits zero after launching a daemon (successful operation)
-            - boothd does not exit (running in foreground / debug mode)
+            - boothd does not exit (running in foreground mode)
             - boothd does not exit (setup phase hangs, e.g. while attempting
               to connect to peer during ticket_catchup())
 
@@ -61,13 +61,15 @@
                 an integer, or False if booth is not expected to terminate
                 within the timeout
             expected_daemon
-                True iff a daemon is expected to be launched (this includes
-                running the server in debug / foreground mode via -D; even
-                though in this case the server's not technically not a daemon,
+                True iff a daemon is expected to be launched (this means
+                running the server in foreground mode via -S;
+                even though in this case the server's not technically not a 
daemon,
                 we still want to treat it like one by checking the lockfile
                 before and after we kill it)
             debug
                 True means pass the -D parameter
+            foreground
+                True means pass the -S parameter
 
         Returns a (pid, return_code, stdout, stderr, runner) tuple,
         where return_code/stdout/stderr are None iff pid is still running.
@@ -97,6 +99,9 @@
         if debug:
             runner.set_debug()
 
+        if foreground:
+            runner.set_foreground()
+
         runner.show_args()
         (pid, return_code, stdout, stderr) = runner.run()
         self.check_return_code(pid, return_code, expected_exitcode)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/booth-1.0+20170619.766d618/test/servertests.py 
new/booth-1.0+20171123.d4cb8cb/test/servertests.py
--- old/booth-1.0+20170619.766d618/test/servertests.py  2017-06-19 
12:23:29.000000000 +0200
+++ new/booth-1.0+20171123.d4cb8cb/test/servertests.py  2017-11-23 
14:07:59.000000000 +0100
@@ -86,6 +86,16 @@
     def test_debug_mode(self):
         (pid, ret, stdout, stderr, runner) = \
             self.run_booth(config_text=self.working_config, debug=True,
+                           expected_exitcode=0, expected_daemon=True)
+
+    def test_foreground_mode(self):
+        (pid, ret, stdout, stderr, runner) = \
+            self.run_booth(config_text=self.working_config, foreground=True,
+                           expected_exitcode=None, expected_daemon=True)
+
+    def test_debug_and_foreground_mode(self):
+        (pid, ret, stdout, stderr, runner) = \
+            self.run_booth(config_text=self.working_config, debug=True, 
foreground=True,
                            expected_exitcode=None, expected_daemon=True)
 
     def test_missing_transport(self):


Reply via email to