diff --git a/.booth.metadata b/.booth.metadata
new file mode 100644
index 0000000..c852833
--- /dev/null
+++ b/.booth.metadata
@@ -0,0 +1 @@
+1c6b69921dc435310094b53f4555d60c95889a19 SOURCES/booth-f2d38ce.tar.gz
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..3e818f0
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+SOURCES/booth-f2d38ce.tar.gz
diff --git a/SOURCES/0000-test-remove-superfluous-shebangs-for-import-only-mod.patch b/SOURCES/0000-test-remove-superfluous-shebangs-for-import-only-mod.patch
new file mode 100644
index 0000000..d53772f
--- /dev/null
+++ b/SOURCES/0000-test-remove-superfluous-shebangs-for-import-only-mod.patch
@@ -0,0 +1,158 @@
+From 9469ffc2d58a5673fffae8778b9c48f5605dda6a Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jan=20Pokorn=C3=BD?= <jpokorny@redhat.com>
+Date: Tue, 10 Jul 2018 18:41:18 +0200
+Subject: [PATCH] test: remove superfluous shebangs for import-only modules
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Consequently, there's no reason to have the affected files marked as
+executable.
+
+Signed-off-by: Jan Pokorný <jpokorny@redhat.com>
+---
+ test/arbtests.py     | 2 --
+ test/assertions.py   | 2 --
+ test/boothrunner.py  | 2 --
+ test/boothtestenv.py | 2 --
+ test/clientenv.py    | 2 --
+ test/clienttests.py  | 2 --
+ test/serverenv.py    | 2 --
+ test/servertests.py  | 2 --
+ test/sitetests.py    | 2 --
+ test/utils.py        | 2 --
+ 10 files changed, 20 deletions(-)
+ mode change 100755 => 100644 test/arbtests.py
+ mode change 100755 => 100644 test/assertions.py
+ mode change 100755 => 100644 test/boothrunner.py
+ mode change 100755 => 100644 test/boothtestenv.py
+ mode change 100755 => 100644 test/clientenv.py
+ mode change 100755 => 100644 test/clienttests.py
+ mode change 100755 => 100644 test/serverenv.py
+ mode change 100755 => 100644 test/servertests.py
+ mode change 100755 => 100644 test/sitetests.py
+ mode change 100755 => 100644 test/utils.py
+
+diff --git a/test/arbtests.py b/test/arbtests.py
+old mode 100755
+new mode 100644
+index caba010..ef7b7f9
+--- a/test/arbtests.py
++++ b/test/arbtests.py
+@@ -1,5 +1,3 @@
+-#!/usr/bin/python
+-
+ from servertests import ServerTests
+ 
+ class ArbitratorConfigTests(ServerTests):
+diff --git a/test/assertions.py b/test/assertions.py
+old mode 100755
+new mode 100644
+index 4396ab7..0b7f995
+--- a/test/assertions.py
++++ b/test/assertions.py
+@@ -1,5 +1,3 @@
+-#!/usr/bin/python
+-
+ import re
+ 
+ class BoothAssertions:
+diff --git a/test/boothrunner.py b/test/boothrunner.py
+old mode 100755
+new mode 100644
+index f9154e7..d981183
+--- a/test/boothrunner.py
++++ b/test/boothrunner.py
+@@ -1,5 +1,3 @@
+-#!/usr/bin/python
+-
+ import os
+ import subprocess
+ import time
+diff --git a/test/boothtestenv.py b/test/boothtestenv.py
+old mode 100755
+new mode 100644
+index 89a484a..fcd0c4d
+--- a/test/boothtestenv.py
++++ b/test/boothtestenv.py
+@@ -1,5 +1,3 @@
+-#!/usr/bin/python
+-
+ import os
+ import subprocess
+ import time
+diff --git a/test/clientenv.py b/test/clientenv.py
+old mode 100755
+new mode 100644
+index fcd40fa..73b2791
+--- a/test/clientenv.py
++++ b/test/clientenv.py
+@@ -1,5 +1,3 @@
+-#!/usr/bin/python
+-
+ from boothtestenv import BoothTestEnvironment
+ from boothrunner  import BoothRunner
+ 
+diff --git a/test/clienttests.py b/test/clienttests.py
+old mode 100755
+new mode 100644
+index 61b691b..c4b9d8a
+--- a/test/clienttests.py
++++ b/test/clienttests.py
+@@ -1,5 +1,3 @@
+-#!/usr/bin/python
+-
+ import string
+ 
+ from clientenv import ClientTestEnvironment
+diff --git a/test/serverenv.py b/test/serverenv.py
+old mode 100755
+new mode 100644
+index d0467b9..c6d4e30
+--- a/test/serverenv.py
++++ b/test/serverenv.py
+@@ -1,5 +1,3 @@
+-#!/usr/bin/python
+-
+ import os
+ import re
+ import time
+diff --git a/test/servertests.py b/test/servertests.py
+old mode 100755
+new mode 100644
+index f574f26..39a6ffc
+--- a/test/servertests.py
++++ b/test/servertests.py
+@@ -1,5 +1,3 @@
+-#!/usr/bin/python
+-
+ import copy
+ from   pprint    import pprint, pformat
+ import re
+diff --git a/test/sitetests.py b/test/sitetests.py
+old mode 100755
+new mode 100644
+index dfdf6b9..6944ffe
+--- a/test/sitetests.py
++++ b/test/sitetests.py
+@@ -1,5 +1,3 @@
+-#!/usr/bin/python
+-
+ from servertests import ServerTests
+ 
+ class SiteConfigTests(ServerTests):
+diff --git a/test/utils.py b/test/utils.py
+old mode 100755
+new mode 100644
+index ceeef98..5b70cfc
+--- a/test/utils.py
++++ b/test/utils.py
+@@ -1,5 +1,3 @@
+-#!/usr/bin/python
+-
+ import subprocess
+ import re
+ 
+-- 
+2.18.0.rc2
+
diff --git a/SOURCES/0001-test-do-not-mix-tabs-with-spaces-in-Python-code.patch b/SOURCES/0001-test-do-not-mix-tabs-with-spaces-in-Python-code.patch
new file mode 100644
index 0000000..e495122
--- /dev/null
+++ b/SOURCES/0001-test-do-not-mix-tabs-with-spaces-in-Python-code.patch
@@ -0,0 +1,60 @@
+From a642a833e31a6bd1e71dc2045a16e494775b35e6 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jan=20Pokorn=C3=BD?= <jpokorny@redhat.com>
+Date: Thu, 12 Jul 2018 18:58:32 +0200
+Subject: [PATCH] test: do not mix tabs with spaces in Python code
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Beside being matter of a good style, it's also forbidden inside
+a single, non-delimited block in Python 3.
+
+Signed-off-by: Jan Pokorný <jpokorny@redhat.com>
+---
+ test/servertests.py | 16 ++++++++--------
+ 1 file changed, 8 insertions(+), 8 deletions(-)
+
+diff --git a/test/servertests.py b/test/servertests.py
+index 39a6ffc..71e808e 100644
+--- a/test/servertests.py
++++ b/test/servertests.py
+@@ -51,8 +51,8 @@ class ServerTests(ServerTestEnvironment):
+                            config_text=self.working_config)
+ 
+     def test_missing_quotes(self):
+-	# quotes no longer required
+-	return True
++        # quotes no longer required
++        return True
+         orig_lines = self.working_config.split("\n")
+         for i in xrange(len(orig_lines)):
+             new_lines = copy.copy(orig_lines)
+@@ -97,8 +97,8 @@ class ServerTests(ServerTestEnvironment):
+                            expected_exitcode=None, expected_daemon=True)
+ 
+     def test_missing_transport(self):
+-	# UDP is default -- TODO?
+-	return True
++        # UDP is default -- TODO?
++        return True
+         config = re.sub('transport=.+\n', '', self.typical_config)
+         (pid, ret, stdout, stderr, runner) = \
+             self.run_booth(config_text=config, expected_exitcode=1, expected_daemon=False)
+@@ -141,10 +141,10 @@ class ServerTests(ServerTestEnvironment):
+             self.assertRegexpMatches(stderr, 'ticket name "' + ticket + '" invalid')
+ 
+     def test_unreachable_peer(self):
+-	# what should this test do? daemon not expected, but no exitcode either?
+-	# booth would now just run, and try to reach that peer...
+-	# TCP reachability is not required during startup anymore.
+-	return True
++        # what should this test do? daemon not expected, but no exitcode either?
++        # booth would now just run, and try to reach that peer...
++        # TCP reachability is not required during startup anymore.
++        return True
+         config = re.sub('#(.+147.+)', lambda m: m.group(1), self.working_config)
+         self.run_booth(config_text=config,
+                        expected_exitcode=None, expected_daemon=False)
+-- 
+2.18.0.rc2
+
diff --git a/SOURCES/0002-test-make-Python-files-supported-_also_-with-Python-.patch b/SOURCES/0002-test-make-Python-files-supported-_also_-with-Python-.patch
new file mode 100644
index 0000000..f366aa8
--- /dev/null
+++ b/SOURCES/0002-test-make-Python-files-supported-_also_-with-Python-.patch
@@ -0,0 +1,510 @@
+From ab2229451827f530959d554920619d87daa34586 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jan=20Pokorn=C3=BD?= <jpokorny@redhat.com>
+Date: Wed, 11 Jul 2018 16:18:25 +0200
+Subject: [PATCH] test: make Python files supported _also_ with Python 3.3+
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+- use "print" like a function rather than a statement
+  . where implicit newline is to be suppressed, don't do that
+    and rather strip it from the string to be printed instead
+- use 2+3 compatible convention for parametrizing exceptions
+- Python 3 doesn't recognize "basestring" class, and at the place
+  of use (pre Python 2.7 only), unicode string is really not expected
+  (also re.UNICODE flag is not used...)
+- Python 3 doesn't recognize "xrange" function, but the surrounding
+  code can be reasonably simplified using "enumerate" function
+- arrange dict treatment in a compatible way:
+  . d.has_key(k) -> k in d
+  . d.iteritems() -> custom "iter_items", always efficient wrapper
+  . d.iterkeys(), here incl. lazy mapping and filtering
+    -> rewrite while retaining laziness
+  . optimize UT.merge_dicts in script/unit-test.py along
+- also in three instances, deal with string/uninterpreted bytes proper
+  dichotomy introduced in Python 3, and related to that, "string"
+  module only supports "ascii_lowercase" attribute in Python 3
+  (as opposed to system-specific plain "lowercase" one)
+
+Note that script/unit-test.py has a pre-existing issue (regardless
+of which Python version interpreter is used), so at least document
+that in the header for now.
+
+Signed-off-by: Jan Pokorný <jpokorny@redhat.com>
+---
+ script/unit-test.py  | 65 ++++++++++++++++++++++++++------------------
+ test/assertions.py   |  4 +--
+ test/boothrunner.py  | 32 ++++++++++++----------
+ test/boothtestenv.py |  6 ++--
+ test/clienttests.py  |  4 +--
+ test/runtests.py     |  2 +-
+ test/serverenv.py    | 18 ++++++------
+ test/servertests.py  | 10 +++----
+ test/utils.py        | 10 +++++--
+ 9 files changed, 84 insertions(+), 67 deletions(-)
+
+diff --git a/script/unit-test.py b/script/unit-test.py
+index 6871930..399528e 100755
+--- a/script/unit-test.py
++++ b/script/unit-test.py
+@@ -1,6 +1,8 @@
+ #!/usr/bin/python
+ # vim: fileencoding=utf-8
+ # see http://stackoverflow.com/questions/728891/correct-way-to-define-python-source-code-encoding
++# NOTE: setting the encoding is needed as non-ASCII characters are contained
++# FIXME: apparently, pexpect.EOF is not being excepted properly
+ 
+ import os, sys, time, signal, tempfile, socket, posix, time
+ import re, shutil, pexpect, logging, pprint
+@@ -16,6 +18,16 @@ default_log_format = '%(asctime)s: : %(message)s'
+ default_log_datefmt = '%b %d %H:%M:%S'
+ 
+ 
++# Compatibility with dictionary methods not present in Python 3;
++# https://www.python.org/dev/peps/pep-0469/#migrating-to-the-common-subset-of-python-2-and-3
++try:
++    dict.iteritems
++except AttributeError:  # Python 3
++    iter_items = lambda d: iter(d.items())
++else:  # Python 2
++    iter_items = lambda d: d.iteritems()
++
++
+ # {{{ pexpect-logging glue
+ # needed for use as pexpect.logfile, to relay into existing logfiles
+ class expect_logging():
+@@ -28,9 +40,12 @@ class expect_logging():
+ 
+     def flush(self, *arg):
+         pass
++
+     def write(self, stg):
+         if self.test.dont_log_expect == 0:
+             # TODO: split by input/output, give program
++            if sys.version_info[0] >= 3:
++                stg = str(stg, 'UTF-8')
+             for line in re.split(r"[\r\n]+", stg):
+                 if line == self.test.prompt:
+                     continue
+@@ -110,7 +125,7 @@ class UT():
+             res = re.match(r"^\s*(\w+)\s*:(?:\s*(#.*?\S))?\s*$", line)
+             if res:
+                 state = res.group(1)
+-                if not m.has_key(state):
++                if state not in m:
+                     m[state] = dict_plus()
+                 if res.group(2):
+                     m[state].aux["comment"] = res.group(2)
+@@ -188,17 +203,15 @@ class UT():
+         name = re.sub(r".*/", "", bin)
+         # How to get stderr, too?
+         expct = pexpect.spawn(bin,
+-                env = dict( os.environ.items() +
+-                    [('PATH',
+-                        self.test_base + "/bin/:" +
+-                        os.getenv('PATH')),
+-                    ('UNIT_TEST_PATH', self.test_base),
+-                    ('LC_ALL', 'C'),
+-                    ('LANG', 'C')] +
+-                    env_add ),
+-                timeout = 30,
+-                maxread = 32768,
+-                **args)
++                    env=dict(os.environ, **dict({
++                             'PATH': ':'.join((self.test_base + "/bin/",
++                                               os.getenv('PATH'))),
++                             'UNIT_TEST_PATH': self.test_base,
++                             'LC_ALL': 'C',
++                             'LANG': 'C'}, **dict(env_add))),
++                    timeout=30,
++                    maxread=32768,
++                    **args)
+         expct.setecho(False)
+         expct.logfile_read = expect_logging("<-  %s" % name, self)
+         expct.logfile_send = expect_logging(" -> %s" % name, self)
+@@ -361,7 +374,7 @@ class UT():
+ 
+         self.current_nr = kv.aux.get("line")
+         #os.system("strace -f -tt -s 2000 -e write -p" + str(self.gdb.pid) + " &")
+-        for n, v in kv.iteritems():
++        for n, v in iter_items(kv):
+             self.set_val( self.translate_shorthand(n, "ticket"), v)
+         logging.info("set state")
+ 
+@@ -372,7 +385,7 @@ class UT():
+         if not sys.stdin.isatty():
+             logging.error("Not a terminal, stopping.")
+         else:
+-            print "\n\nEntering interactive mode.\n\n"
++            print("\n\nEntering interactive mode.\n\n")
+             self.gdb.sendline("set prompt GDB> \n")
+             self.gdb.setecho(True)
+             # can't use send_cmd, doesn't reply with expected prompt anymore.
+@@ -415,7 +428,7 @@ class UT():
+         self.send_cmd("next")
+         
+         # push message.
+-        for (n, v) in msg.iteritems():
++        for (n, v) in iter_items(msg):
+             self.set_val( self.translate_shorthand(n, "message"), v, "htonl")
+ 
+         # set "received" length
+@@ -426,7 +439,7 @@ class UT():
+     def wait_outgoing(self, msg):
+         self.wait_for_function("booth_udp_send")
+         ok = True
+-        for (n, v) in msg.iteritems():
++        for (n, v) in iter_items(msg):
+             if re.search(r"\.", n):
+                 ok = self.check_value( self.translate_shorthand(n, "inject"), v) and ok
+             else:
+@@ -438,14 +451,12 @@ class UT():
+         #stopped_at = self.sync() 
+ 
+     def merge_dicts(self, base, overlay):
+-        return dict(base.items() + overlay.items())
++        return dict(base, **overlay)
+        
+ 
+     def loop(self, fn, data):
+-        matches = map(lambda k: re.match(r"^(outgoing|message)(\d+)$", k), data.iterkeys())
+-        valid_matches = filter(None, matches)
+-        nums = map(lambda m: int(m.group(2)), valid_matches)
+-        loop_max = max(nums)
++        matches = (re.match(r"^(outgoing|message)(\d+)$", k) for k in data)
++        loop_max = max(int(m.group(2)) for m in matches if m)
+         for counter in range(0, loop_max+1):    # incl. last message
+ 
+             kmsg = 'message%d' % counter
+@@ -471,14 +482,14 @@ class UT():
+                 logging.info("ticket change %s  (%s:%d)  %s" % (ktkt, fn, self.current_nr, comment))
+                 self.set_state(tkt)
+             if gdb:
+-                for (k, v) in gdb.iteritems():
++                for (k, v) in iter_items(gdb):
+                     self.send_cmd(k + " " + v.replace("§", "\n"))
+             if msg:
+                 self.current_nr = msg.aux.get("line")
+                 comment = msg.aux.get("comment", "")
+                 logging.info("sending %s  (%s:%d)  %s" % (kmsg, fn, self.current_nr, comment))
+                 self.send_message(self.merge_dicts(data["message"], msg))
+-            if data.has_key(kgdb) and len(gdb) == 0:
++            if kgdb in data and len(gdb) == 0:
+                 self.user_debug("manual override")
+             if out:
+                 self.current_nr = out.aux.get("line")
+@@ -520,7 +531,7 @@ class UT():
+         self.let_booth_go_a_bit()
+ 
+         ok = True
+-        for (n, v) in data.iteritems():
++        for (n, v) in iter_items(data):
+             ok = self.check_value( self.translate_shorthand(n, "ticket"), v) and ok
+         if not ok:
+             sys.exit(1)
+@@ -529,8 +540,8 @@ class UT():
+     def run(self, start_from="000", end_with="999"):
+         os.chdir(self.test_base)
+         # TODO: sorted, random order
+-        tests = filter( (lambda f: re.match(r"^\d\d\d_.*\.txt$", f)), glob.glob("*"))
+-        tests.sort()
++        tests = sorted(f for f in glob.glob("*")
++                       if re.match(r"^\d\d\d_.*\.txt$", f))
+         failed = 0
+         for f in tests:
+             if f[0:3] < start_from:
+@@ -561,7 +572,7 @@ class UT():
+             except:
+                 failed += 1
+                 logging.error(self.colored_string("Broke in %s:%s %s" % (f, self.current_nr, sys.exc_info()), self.RED))
+-                for frame in traceback.format_tb(sys.exc_traceback):
++                for frame in traceback.format_tb(sys.exc_info()[2]):
+                     logging.info("  -  %s " % frame.rstrip())
+             finally:
+                 self.stop_processes()
+diff --git a/test/assertions.py b/test/assertions.py
+index 0b7f995..34333ca 100644
+--- a/test/assertions.py
++++ b/test/assertions.py
+@@ -21,7 +21,7 @@ class BoothAssertions:
+     # backported from 2.7 just in case we're running on an older Python
+     def assertRegexpMatches(self, text, expected_regexp, msg=None):
+         """Fail the test unless the text matches the regular expression."""
+-        if isinstance(expected_regexp, basestring):
++        if isinstance(expected_regexp, str):
+             expected_regexp = re.compile(expected_regexp)
+         if not expected_regexp.search(text, MULTILINE):
+             msg = msg or "Regexp didn't match"
+@@ -30,7 +30,7 @@ class BoothAssertions:
+ 
+     def assertNotRegexpMatches(self, text, unexpected_regexp, msg=None):
+         """Fail the test if the text matches the regular expression."""
+-        if isinstance(unexpected_regexp, basestring):
++        if isinstance(unexpected_regexp, str):
+             unexpected_regexp = re.compile(unexpected_regexp)
+         match = unexpected_regexp.search(text)
+         if match:
+diff --git a/test/boothrunner.py b/test/boothrunner.py
+index d981183..347912b 100644
+--- a/test/boothrunner.py
++++ b/test/boothrunner.py
+@@ -1,4 +1,5 @@
+ import os
++import sys
+ import subprocess
+ import time
+ import unittest
+@@ -37,14 +38,14 @@ class BoothRunner:
+ 
+     def show_output(self, stdout, stderr):
+         if stdout:
+-            print "STDOUT:"
+-            print "------"
+-            print stdout,
++            print("STDOUT:")
++            print("------")
++            print(stdout.rstrip('\n'))
+         if stderr:
+-            print "STDERR: (N.B. crm_ticket failures indicate daemon started correctly)"
+-            print "------"
+-            print stderr,
+-        print "-" * 70
++            print("STDERR: (N.B. crm_ticket failures indicate daemon started correctly)")
++            print("------")
++            print(stderr.rstrip('\n'))
++        print("-" * 70)
+ 
+     def subproc_completed_within(self, p, timeout):
+         start = time.time()
+@@ -55,7 +56,7 @@ class BoothRunner:
+             elapsed = time.time() - start
+             if elapsed + wait > timeout:
+                 wait = timeout - elapsed
+-            print "Waiting on %d for %.1fs ..." % (p.pid, wait)
++            print("Waiting on %d for %.1fs ..." % (p.pid, wait))
+             time.sleep(wait)
+             elapsed = time.time() - start
+             if elapsed >= timeout:
+@@ -83,26 +84,29 @@ class BoothRunner:
+         return text
+ 
+     def show_args(self):
+-        print "\n"
+-        print "-" * 70
+-        print "Running", ' '.join(self.all_args())
++        print("\n")
++        print("-" * 70)
++        print("Running", ' '.join(self.all_args()))
+         msg = "with config from %s" % self.config_file_used()
+         config_text = self.config_text_used()
+         if config_text is not None:
+             msg += ": [%s]" % config_text
+-        print msg
++        print(msg)
+ 
+     def run(self):
+         p = subprocess.Popen(self.all_args(), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+         if not p:
+-            raise RuntimeError, "failed to start subprocess"
++            raise RuntimeError("failed to start subprocess")
+ 
+-        print "Started subprocess pid %d" % p.pid
++        print("Started subprocess pid %d" % p.pid)
+ 
+         completed = self.subproc_completed_within(p, 2)
+ 
+         if completed:
+             (stdout, stderr) = p.communicate()
++            if sys.version_info[0] >= 3:
++                # only expect ASCII/UTF-8 encodings for the obtained input bytes
++                stdout, stderr = str(stdout, 'UTF-8'), str(stderr, 'UTF-8')
+             self.show_output(stdout, stderr)
+             return (p.pid, p.returncode, stdout, stderr)
+ 
+diff --git a/test/boothtestenv.py b/test/boothtestenv.py
+index fcd0c4d..59e25c3 100644
+--- a/test/boothtestenv.py
++++ b/test/boothtestenv.py
+@@ -17,7 +17,7 @@ class BoothTestEnvironment(unittest.TestCase, BoothAssertions):
+ 
+     def setUp(self):
+         if not self._testMethodName.startswith('test_'):
+-            raise RuntimeError, "unexpected test method name: " + self._testMethodName
++            raise RuntimeError("unexpected test method name: " + self._testMethodName)
+         self.test_name = self._testMethodName[5:]
+         self.test_path = os.path.join(self.test_run_path, self.test_name)
+         os.makedirs(self.test_path)
+@@ -54,11 +54,11 @@ class BoothTestEnvironment(unittest.TestCase, BoothAssertions):
+ 
+     def check_return_code(self, pid, return_code, expected_exitcode):
+         if return_code is None:
+-            print "pid %d still running" % pid
++            print("pid %d still running" % pid)
+             if expected_exitcode is not None:
+                 self.fail("expected exit code %d, not long-running process" % expected_exitcode)
+         else:
+-            print "pid %d exited with code %d" % (pid, return_code)
++            print("pid %d exited with code %d" % (pid, return_code))
+             if expected_exitcode is None:
+                 msg = "should not exit"
+             else:
+diff --git a/test/clienttests.py b/test/clienttests.py
+index c4b9d8a..512620e 100644
+--- a/test/clienttests.py
++++ b/test/clienttests.py
+@@ -7,14 +7,14 @@ class ClientConfigTests(ClientTestEnvironment):
+ 
+     def test_site_buffer_overflow(self):
+         # https://bugzilla.novell.com/show_bug.cgi?id=750256
+-        longfile = (string.lowercase * 3)[:63]
++        longfile = (string.ascii_lowercase * 3)[:63]
+         expected_error = "'%s' exceeds maximum site name length" % longfile
+         args = [ 'grant', '-s', longfile, '-t', 'ticket' ]
+         self._test_buffer_overflow(expected_error, args=args)
+ 
+     def test_ticket_buffer_overflow(self):
+         # https://bugzilla.novell.com/show_bug.cgi?id=750256
+-        longfile = (string.lowercase * 3)[:63]
++        longfile = (string.ascii_lowercase * 3)[:63]
+         expected_error = "'%s' exceeds maximum ticket name length" % longfile
+         args = [ 'grant', '-s', 'site', '-t', longfile ]
+         self._test_buffer_overflow(expected_error, args=args)
+diff --git a/test/runtests.py b/test/runtests.py
+index 0532c01..833b1a7 100755
+--- a/test/runtests.py
++++ b/test/runtests.py
+@@ -53,5 +53,5 @@ if __name__ == '__main__':
+         shutil.rmtree(test_run_path)
+         sys.exit(0)
+     else:
+-        print "Left %s for debugging" % test_run_path
++        print("Left %s for debugging" % test_run_path)
+         sys.exit(1)
+diff --git a/test/serverenv.py b/test/serverenv.py
+index c6d4e30..5d6c6c4 100644
+--- a/test/serverenv.py
++++ b/test/serverenv.py
+@@ -73,12 +73,10 @@ ticket="ticketB"
+         where return_code/stdout/stderr are None iff pid is still running.
+         '''
+         if expected_daemon and expected_exitcode is not None and expected_exitcode != 0:
+-            raise RuntimeError, \
+-                "Shouldn't ever expect daemon to start and then failure"
++            raise RuntimeError("Shouldn't ever expect daemon to start and then failure")
+ 
+         if not expected_daemon and expected_exitcode == 0:
+-            raise RuntimeError, \
+-                "Shouldn't ever expect success without starting daemon"
++            raise RuntimeError("Shouldn't ever expect success without starting daemon")
+ 
+         self.init_log()
+ 
+@@ -122,9 +120,9 @@ ticket="ticketB"
+         return config_file
+ 
+     def kill_pid(self, pid):
+-        print "killing %d ..." % pid
++        print("killing %d ..." % pid)
+         os.kill(pid, 15)
+-        print "killed"
++        print("killed")
+ 
+     def check_daemon_handling(self, runner, expected_daemon):
+         '''
+@@ -154,7 +152,7 @@ ticket="ticketB"
+         Returns the pid contained in lock_file, or None if it doesn't exist.
+         '''
+         if not os.path.exists(lock_file):
+-            print "%s does not exist" % lock_file
++            print("%s does not exist" % lock_file)
+             return None
+ 
+         l = open(lock_file)
+@@ -162,7 +160,7 @@ ticket="ticketB"
+         l.close()
+         self.assertEqual(len(lines), 1, "Lock file should contain one line")
+         pid = re.search('\\bbooth_pid="?(\\d+)"?', lines[0]).group(1)
+-        print "lockfile contains: <%s>" % pid
++        print("lockfile contains: <%s>" % pid)
+         return pid
+ 
+     def is_pid_running_daemon(self, pid):
+@@ -185,11 +183,11 @@ ticket="ticketB"
+ 
+         c = open("/proc/%s/cmdline" % pid)
+         cmdline = "".join(c.readlines())
+-        print cmdline
++        print(cmdline)
+         c.close()
+ 
+         if cmdline.find('boothd') == -1:
+-            print 'no boothd in cmdline:', cmdline
++            print('no boothd in cmdline:', cmdline)
+             return False
+ 
+         # self.assertRegexpMatches(
+diff --git a/test/servertests.py b/test/servertests.py
+index 71e808e..288d19f 100644
+--- a/test/servertests.py
++++ b/test/servertests.py
+@@ -35,13 +35,13 @@ class ServerTests(ServerTestEnvironment):
+ 
+     def test_config_file_buffer_overflow(self):
+         # https://bugzilla.novell.com/show_bug.cgi?id=750256
+-        longfile = (string.lowercase * 5)[:127]
++        longfile = (string.ascii_lowercase * 5)[:127]
+         expected_error = "'%s' exceeds maximum config name length" % longfile
+         self._test_buffer_overflow(expected_error, config_file=longfile)
+ 
+     def test_lock_file_buffer_overflow(self):
+         # https://bugzilla.novell.com/show_bug.cgi?id=750256
+-        longfile = (string.lowercase * 5)[:127]
++        longfile = (string.ascii_lowercase * 5)[:127]
+         expected_error = "'%s' exceeds maximum lock file length" % longfile
+         self._test_buffer_overflow(expected_error, lock_file=longfile)
+ 
+@@ -54,12 +54,12 @@ class ServerTests(ServerTestEnvironment):
+         # quotes no longer required
+         return True
+         orig_lines = self.working_config.split("\n")
+-        for i in xrange(len(orig_lines)):
++        for (i, line) in enumerate(orig_lines):
+             new_lines = copy.copy(orig_lines)
+-            new_lines[i] = new_lines[i].replace('"', '')
++            new_lines[i] = line.replace('"', '')
+             new_config = "\n".join(new_lines)
+ 
+-            line_contains_IP = re.search('^\s*(site|arbitrator)=.*[0-9]\.', orig_lines[i])
++            line_contains_IP = re.search('^\s*(site|arbitrator)=.*[0-9]\.', line)
+             if line_contains_IP:
+                 # IP addresses need to be surrounded by quotes,
+                 # so stripping them should cause it to fail
+diff --git a/test/utils.py b/test/utils.py
+index 5b70cfc..aca3592 100644
+--- a/test/utils.py
++++ b/test/utils.py
+@@ -1,5 +1,6 @@
+ import subprocess
+ import re
++import sys
+ 
+ def run_cmd(cmd):
+     p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+@@ -9,8 +10,11 @@ def run_cmd(cmd):
+ def get_IP():
+     (stdout, stderr, returncode) = run_cmd(['hostname', '-i'])
+     if returncode != 0:
+-        raise RuntimeError, "Failed to run hostname -i:\n" + stderr
++        raise RuntimeError("Failed to run hostname -i:\n" + stderr)
+     # in case multiple IP addresses are returned, use only the first
+-    # and also strip '%<device>' part possibly present with IPv6 address
+-    ret = re.sub(r'\s.*', '', stdout)
++    # and also strip '%<device>' part possibly present with IPv6 address;
++    # in Python 3 context, only expect ASCII/UTF-8 encodings for the
++    # obtained input bytes
++    ret = re.sub(r'\s.*', '',
++                 stdout if sys.version_info[0] < 3 else str(stdout, 'UTF-8'))
+     return "::1" if '%' in ret else ret
+-- 
+2.18.0.rc2
+
diff --git a/SOURCES/0003-build-parametrize-Python-invocations-in-the-shebangs.patch b/SOURCES/0003-build-parametrize-Python-invocations-in-the-shebangs.patch
new file mode 100644
index 0000000..a3ce25f
--- /dev/null
+++ b/SOURCES/0003-build-parametrize-Python-invocations-in-the-shebangs.patch
@@ -0,0 +1,89 @@
+From 31133e8ac07c08b607ee7799c0074c1dce37a952 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jan=20Pokorn=C3=BD?= <jpokorny@redhat.com>
+Date: Wed, 11 Jul 2018 14:18:50 +0200
+Subject: [PATCH] build: parametrize Python invocations in the shebangs
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Consequently, there's no reason to have the affected files marked as
+executable (processing the files by the means of AC_CONFIG_FILES will
+get rid of any such permission bits, anyway), but at the very least,
+test/runtests.py needs to be set executable afterwards so as no to
+cause failures with the current "make check" arrangement that invokes
+TESTS subtargets directly (i.e. no extension-based LOG_COMPILER set).
+
+Signed-off-by: Jan Pokorný <jpokorny@redhat.com>
+---
+ configure.ac                             | 12 +++++++++++-
+ script/{unit-test.py => unit-test.py.in} |  2 +-
+ test/{runtests.py => runtests.py.in}     |  2 +-
+ 3 files changed, 13 insertions(+), 3 deletions(-)
+ rename script/{unit-test.py => unit-test.py.in} (99%)
+ mode change 100755 => 100644
+ rename test/{runtests.py => runtests.py.in} (98%)
+ mode change 100755 => 100644
+
+diff --git a/configure.ac b/configure.ac
+index 3bf41b3..a6ad86e 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -97,6 +97,14 @@ CPPFLAGS="$CPPFLAGS $XML2HEAD"
+ 
+ PKG_CHECK_MODULES(GLIB, [glib-2.0])
+ 
++# Python casing, prefer 3.3+ to 2.{6...}
++AM_PATH_PYTHON([3.3], , [PYTHON=:])
++if test "x$PYTHON" = x:; then
++	AM_PATH_PYTHON([2.6])
++fi
++PYTHON_SHEBANG="$PYTHON ${PYTHON_OPTS--Es}"
++AC_ARG_VAR([PYTHON_SHEBANG], [Python invocation used in shebangs])
++
+ # Checks for header files.
+ AC_FUNC_ALLOCA
+ AC_HEADER_DIRENT
+@@ -157,7 +165,9 @@ AC_CONFIG_FILES([Makefile
+ 		 docs/Makefile
+ 		 conf/Makefile])
+ AC_CONFIG_FILES([conf/booth-arbitrator.service conf/booth@.service])
+-
++AC_CONFIG_FILES([script/unit-test.py test/runtests.py],
++		dnl Following required at least for "make check"
++		[chmod +x test/runtests.py])
+ AC_CONFIG_FILES([script/service-runnable], [chmod +x script/service-runnable])
+ 
+ # ===============================================
+diff --git a/script/unit-test.py b/script/unit-test.py.in
+old mode 100755
+new mode 100644
+similarity index 99%
+rename from script/unit-test.py
+rename to script/unit-test.py.in
+index 399528e..4f3cf62
+--- a/script/unit-test.py
++++ b/script/unit-test.py.in
+@@ -1,4 +1,4 @@
+-#!/usr/bin/python
++#!@PYTHON_SHEBANG@
+ # vim: fileencoding=utf-8
+ # see http://stackoverflow.com/questions/728891/correct-way-to-define-python-source-code-encoding
+ # NOTE: setting the encoding is needed as non-ASCII characters are contained
+diff --git a/test/runtests.py b/test/runtests.py.in
+old mode 100755
+new mode 100644
+similarity index 98%
+rename from test/runtests.py
+rename to test/runtests.py.in
+index 833b1a7..ec59159
+--- a/test/runtests.py
++++ b/test/runtests.py.in
+@@ -1,4 +1,4 @@
+-#!/usr/bin/python
++#!@PYTHON_SHEBANG@
+ 
+ import os
+ import re
+-- 
+2.18.0.rc2
+
diff --git a/SOURCES/0004-test-drop-underqualified-identifier-in-unittest-s-2..patch b/SOURCES/0004-test-drop-underqualified-identifier-in-unittest-s-2..patch
new file mode 100644
index 0000000..b337059
--- /dev/null
+++ b/SOURCES/0004-test-drop-underqualified-identifier-in-unittest-s-2..patch
@@ -0,0 +1,36 @@
+From 541e6184fca60a01ff7e8c1bba794c083ac4245f Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jan=20Pokorn=C3=BD?= <jpokorny@redhat.com>
+Date: Tue, 10 Jul 2018 19:25:34 +0200
+Subject: [PATCH] test: drop underqualified identifier in unittest's 2.6 compat
+ "polyfill"
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Specifically, in supplemented unittest.TestCase.assertRegexpMatches
+method.  In Python 2.7's implementation, there's no re.MULTILINE
+modifier at that very place, either.
+
+Not sure what the original purpose of introducing that with c1c47f5 was.
+
+Signed-off-by: Jan Pokorný <jpokorny@redhat.com>
+---
+ test/assertions.py | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/test/assertions.py b/test/assertions.py
+index 34333ca..fafb291 100644
+--- a/test/assertions.py
++++ b/test/assertions.py
+@@ -23,7 +23,7 @@ class BoothAssertions:
+         """Fail the test unless the text matches the regular expression."""
+         if isinstance(expected_regexp, str):
+             expected_regexp = re.compile(expected_regexp)
+-        if not expected_regexp.search(text, MULTILINE):
++        if not expected_regexp.search(text):
+             msg = msg or "Regexp didn't match"
+             msg = '%s: %r not found in %r' % (msg, expected_regexp.pattern, text)
+             raise self.failureException(msg)
+-- 
+2.18.0.rc2
+
diff --git a/SOURCES/0005-test-drop-comment-out-superfluous-imports.patch b/SOURCES/0005-test-drop-comment-out-superfluous-imports.patch
new file mode 100644
index 0000000..45adf33
--- /dev/null
+++ b/SOURCES/0005-test-drop-comment-out-superfluous-imports.patch
@@ -0,0 +1,90 @@
+From 6a6834a8110d9e6aff50cd6d6935976af4cbdb8f Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jan=20Pokorn=C3=BD?= <jpokorny@redhat.com>
+Date: Thu, 12 Jul 2018 20:18:07 +0200
+Subject: [PATCH] test: drop/comment out superfluous imports
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Signed-off-by: Jan Pokorný <jpokorny@redhat.com>
+---
+ test/boothrunner.py  | 2 --
+ test/boothtestenv.py | 1 -
+ test/runtests.py.in  | 3 +--
+ test/serverenv.py    | 2 --
+ test/servertests.py  | 1 -
+ 5 files changed, 1 insertion(+), 8 deletions(-)
+
+diff --git a/test/boothrunner.py b/test/boothrunner.py
+index 347912b..31c2213 100644
+--- a/test/boothrunner.py
++++ b/test/boothrunner.py
+@@ -1,8 +1,6 @@
+-import os
+ import sys
+ import subprocess
+ import time
+-import unittest
+ 
+ class BoothRunner:
+     default_config_file = '/etc/booth/booth.conf'
+diff --git a/test/boothtestenv.py b/test/boothtestenv.py
+index 59e25c3..ba54360 100644
+--- a/test/boothtestenv.py
++++ b/test/boothtestenv.py
+@@ -5,7 +5,6 @@ import tempfile
+ import unittest
+ 
+ from assertions  import BoothAssertions
+-from boothrunner import BoothRunner
+ 
+ class BoothTestEnvironment(unittest.TestCase, BoothAssertions):
+     test_src_path       = os.path.abspath(os.path.dirname(__file__))
+diff --git a/test/runtests.py.in b/test/runtests.py.in
+index ec59159..73d70a3 100644
+--- a/test/runtests.py.in
++++ b/test/runtests.py.in
+@@ -1,7 +1,6 @@
+ #!@PYTHON_SHEBANG@
+ 
+ import os
+-import re
+ import shutil
+ import sys
+ import tempfile
+@@ -10,7 +9,7 @@ import unittest
+ 
+ from clienttests import ClientConfigTests
+ from sitetests   import SiteConfigTests
+-from arbtests    import ArbitratorConfigTests
++#from arbtests    import ArbitratorConfigTests
+ 
+ if __name__ == '__main__':
+     if os.geteuid() == 0:
+diff --git a/test/serverenv.py b/test/serverenv.py
+index 5d6c6c4..7b8915d 100644
+--- a/test/serverenv.py
++++ b/test/serverenv.py
+@@ -1,9 +1,7 @@
+ import os
+ import re
+ import time
+-import unittest
+ 
+-from assertions   import BoothAssertions
+ from boothrunner  import BoothRunner
+ from boothtestenv import BoothTestEnvironment
+ from utils        import get_IP
+diff --git a/test/servertests.py b/test/servertests.py
+index 288d19f..f72dbed 100644
+--- a/test/servertests.py
++++ b/test/servertests.py
+@@ -1,5 +1,4 @@
+ import copy
+-from   pprint    import pprint, pformat
+ import re
+ import string
+ 
+-- 
+2.18.0.rc2
+
diff --git a/SOURCES/0006-test-avoid-dangerous-mutable-sticky-default-value.patch b/SOURCES/0006-test-avoid-dangerous-mutable-sticky-default-value.patch
new file mode 100644
index 0000000..88ba09c
--- /dev/null
+++ b/SOURCES/0006-test-avoid-dangerous-mutable-sticky-default-value.patch
@@ -0,0 +1,178 @@
+From 34cc2fcda6804d42ee66fa5a417fc42b64fe3806 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jan=20Pokorn=C3=BD?= <jpokorny@redhat.com>
+Date: Tue, 10 Jul 2018 19:45:56 +0200
+Subject: [PATCH] test: avoid dangerous mutable/sticky default value
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Incl. slight refactoring towards more frequent use of tuples where
+advantage of lists are dubious.
+
+Signed-off-by: Jan Pokorný <jpokorny@redhat.com>
+---
+ script/unit-test.py.in | 34 ++++++++++++++++------------------
+ test/assertions.py     |  2 +-
+ test/boothrunner.py    | 14 +++++++-------
+ test/clientenv.py      |  4 ++--
+ test/serverenv.py      |  4 ++--
+ 5 files changed, 28 insertions(+), 30 deletions(-)
+
+diff --git a/script/unit-test.py.in b/script/unit-test.py.in
+index 4f3cf62..fc98bc1 100644
+--- a/script/unit-test.py.in
++++ b/script/unit-test.py.in
+@@ -199,7 +199,7 @@ class UT():
+             self.booth.close( force=self.booth.isalive() )
+ 
+ 
+-    def start_a_process(self, bin, env_add=[], **args):
++    def start_a_process(self, bin, env_add=(), **args):
+         name = re.sub(r".*/", "", bin)
+         # How to get stderr, too?
+         expct = pexpect.spawn(bin,
+@@ -220,16 +220,15 @@ class UT():
+ 
+     def start_processes(self, test):
+         self.booth = self.start_a_process(self.binary,
+-                args = [ "daemon", "-D",
+-                    "-c", self.test_base + "/booth.conf",
+-                    "-s", "127.0.0.1",
+-                    "-l", self.lockfile, 
+-                    ],
+-                env_add=[ ('UNIT_TEST', test),
++                args = ["daemon", "-D",
++                        "-c", self.test_base + "/booth.conf",
++                        "-s", "127.0.0.1",
++                        "-l", self.lockfile],
++                env_add=( ('UNIT_TEST', test),
+                     ('UNIT_TEST_FILE', os.path.realpath(test)),
+                     # provide some space, so that strcpy(getenv()) works
+                     ('UNIT_TEST_AUX', "".zfill(1024)),
+-                    ]);
++                    ));
+ 
+         logging.info("started booth with PID %d, lockfile %s" % (self.booth.pid, self.lockfile))
+         self.booth.expect("BOOTH site \S+ \(build \S+\) daemon is starting", timeout=2)
+@@ -237,16 +236,15 @@ class UT():
+ 
+         self.gdb = self.start_a_process("gdb",
+                 args=["-quiet",
+-                    "-p", str(self.booth.pid),
+-                    # Don't use .gdbinit
+-                    "-nx", "-nh",
+-                    # Run until the defined point.
+-                    # This is necessary so that ticket state setting doesn't
+-                    # happen _before_ the call to pcmk_load_ticket()
+-                    # (which would overwrite our data)
+-                    "-ex", "break ticket_cron",
+-                    "-ex", "continue",
+-                    ])
++                      "-p", str(self.booth.pid),
++                      # Don't use .gdbinit
++                      "-nx", "-nh",
++                      # Run until the defined point.
++                      # This is necessary so that ticket state setting doesn't
++                      # happen _before_ the call to pcmk_load_ticket()
++                      # (which would overwrite our data)
++                      "-ex", "break ticket_cron",
++                      "-ex", "continue"])
+         logging.info("started GDB with PID %d" % self.gdb.pid)
+         self.gdb.expect("(gdb)")
+         self.gdb.sendline("set pagination off\n")
+diff --git a/test/assertions.py b/test/assertions.py
+index fafb291..db6fcd8 100644
+--- a/test/assertions.py
++++ b/test/assertions.py
+@@ -10,7 +10,7 @@ class BoothAssertions:
+         self.assertRegexpMatches(stderr, expected_error)
+ 
+     def assertLockFileError(self, config_file=None, config_text=None,
+-                            lock_file=True, args=[]):
++                            lock_file=True, args=()):
+         (pid, ret, stdout, stderr, runner) = \
+             self.run_booth(config_text=config_text, config_file=config_file,
+                            lock_file=lock_file, args=args, expected_exitcode=1)
+diff --git a/test/boothrunner.py b/test/boothrunner.py
+index 31c2213..0285fe6 100644
+--- a/test/boothrunner.py
++++ b/test/boothrunner.py
+@@ -8,14 +8,14 @@ class BoothRunner:
+ 
+     def __init__(self, boothd_path, mode, args):
+         self.boothd_path = boothd_path
+-        self.args        = [ mode ]
+-        self.final_args  = args # will be appended to self.args
++        self.args        = (mode, )
++        self.final_args  = tuple(args)  # will be appended to self.args
+         self.mode        = mode
+         self.config_file = None
+         self.lock_file   = None
+ 
+     def set_config_file_arg(self):
+-        self.args += [ '-c', self.config_file ]
++        self.args += ('-c', self.config_file)
+ 
+     def set_config_file(self, config_file):
+         self.config_file = config_file
+@@ -23,16 +23,16 @@ class BoothRunner:
+ 
+     def set_lock_file(self, lock_file):
+         self.lock_file = lock_file
+-        self.args += [ '-l', self.lock_file ]
++        self.args += ('-l', self.lock_file)
+ 
+     def set_debug(self):
+-        self.args += [ '-D' ]
++        self.args += ('-D', )
+ 
+     def set_foreground(self):
+-        self.args += [ '-S' ]
++        self.args += ('-S', )
+ 
+     def all_args(self):
+-        return [ self.boothd_path ] + self.args + self.final_args
++        return (self.boothd_path, ) + self.args + self.final_args
+ 
+     def show_output(self, stdout, stderr):
+         if stdout:
+diff --git a/test/clientenv.py b/test/clientenv.py
+index 73b2791..141e33c 100644
+--- a/test/clientenv.py
++++ b/test/clientenv.py
+@@ -4,8 +4,8 @@ from boothrunner  import BoothRunner
+ class ClientTestEnvironment(BoothTestEnvironment):
+     mode = 'client'
+ 
+-    def run_booth(self, config_text=None, config_file=None, lock_file=True, args=[],
+-                  expected_exitcode=0, debug=False):
++    def run_booth(self, config_text=None, config_file=None, lock_file=True,
++                  args=(), expected_exitcode=0, debug=False):
+         '''
+         Runs boothd.
+ 
+diff --git a/test/serverenv.py b/test/serverenv.py
+index 7b8915d..62c37d0 100644
+--- a/test/serverenv.py
++++ b/test/serverenv.py
+@@ -29,7 +29,7 @@ ticket="ticketB"
+ 
+     def run_booth(self, expected_exitcode, expected_daemon,
+                   config_text=None, config_file=None, lock_file=True,
+-                  args=[], debug=False, foreground=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
+@@ -52,7 +52,7 @@ ticket="ticketB"
+                 True: pass a temporary lockfile parameter to booth via -l
+                 string: pass the given lockfile path to booth via -l
+             args
+-                array of extra args to pass to booth
++                iterable of extra args to pass to booth
+             expected_exitcode
+                 an integer, or False if booth is not expected to terminate
+                 within the timeout
+-- 
+2.18.0.rc2
+
diff --git a/SOURCES/0007-test-unit-test.py-daemon-will-not-stay-in-foreground.patch b/SOURCES/0007-test-unit-test.py-daemon-will-not-stay-in-foreground.patch
new file mode 100644
index 0000000..83058df
--- /dev/null
+++ b/SOURCES/0007-test-unit-test.py-daemon-will-not-stay-in-foreground.patch
@@ -0,0 +1,41 @@
+From 0a7b51d1eb6f948724c08e94148e8ff1f448d100 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jan=20Pokorn=C3=BD?= <jpokorny@redhat.com>
+Date: Fri, 13 Jul 2018 14:10:28 +0200
+Subject: [PATCH] test: unit-test.py: daemon will not stay in foreground with
+ -D anymore
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Commit a66ac33 missed this impact (it may have missed impact on
+test/boothrunner.py but it appears to be fine either way).
+
+Signed-off-by: Jan Pokorný <jpokorny@redhat.com>
+---
+ script/unit-test.py.in | 3 +--
+ 1 file changed, 1 insertion(+), 2 deletions(-)
+
+diff --git a/script/unit-test.py.in b/script/unit-test.py.in
+index fc98bc1..74a014b 100644
+--- a/script/unit-test.py.in
++++ b/script/unit-test.py.in
+@@ -2,7 +2,6 @@
+ # vim: fileencoding=utf-8
+ # see http://stackoverflow.com/questions/728891/correct-way-to-define-python-source-code-encoding
+ # NOTE: setting the encoding is needed as non-ASCII characters are contained
+-# FIXME: apparently, pexpect.EOF is not being excepted properly
+ 
+ import os, sys, time, signal, tempfile, socket, posix, time
+ import re, shutil, pexpect, logging, pprint
+@@ -220,7 +219,7 @@ class UT():
+ 
+     def start_processes(self, test):
+         self.booth = self.start_a_process(self.binary,
+-                args = ["daemon", "-D",
++                args = ["daemon", "-DS",
+                         "-c", self.test_base + "/booth.conf",
+                         "-s", "127.0.0.1",
+                         "-l", self.lockfile],
+-- 
+2.18.0.rc2
+
diff --git a/SOURCES/0008-Refactor-fix-strncpy-may-miss-trailing-null-byte-war.patch b/SOURCES/0008-Refactor-fix-strncpy-may-miss-trailing-null-byte-war.patch
new file mode 100644
index 0000000..bbb56f1
--- /dev/null
+++ b/SOURCES/0008-Refactor-fix-strncpy-may-miss-trailing-null-byte-war.patch
@@ -0,0 +1,50 @@
+From d3bf9f5ced41ad0f4e8ae87e80c7e44df4157b61 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jan=20Pokorn=C3=BD?= <jpokorny@redhat.com>
+Date: Fri, 13 Jul 2018 14:40:07 +0200
+Subject: [PATCH] Refactor: fix "strncpy may miss trailing null byte" warnings
+ of GCC 8.1
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Verbatim warning:
+> ‘strncpy’ specified bound 64 equals destination size [-Wstringop-truncation]
+
+Signed-off-by: Jan Pokorný <jpokorny@redhat.com>
+---
+ src/config.c | 12 ++++++++++--
+ 1 file changed, 10 insertions(+), 2 deletions(-)
+
+diff --git a/src/config.c b/src/config.c
+index 9df5767..e4d36ab 100644
+--- a/src/config.c
++++ b/src/config.c
+@@ -75,7 +75,10 @@ static void hostname_to_ip(char * hostname)
+ 
+ 	/* Return the first found address */
+ 	if (addr_list[0] != NULL) {
+-		strncpy(hostname, inet_ntoa(*addr_list[0]), BOOTH_NAME_LEN);
++		strncpy(hostname, inet_ntoa(*addr_list[0]), BOOTH_NAME_LEN - 1);
++		/* buffer overflow will not happen (IPv6 notation < 63 chars),
++		   but suppress the warnings */
++		hostname[BOOTH_NAME_LEN - 1] = '\0';
+ 	}
+ 	else {
+ 		log_error("no IP addresses found for the host \"%s\"", hostname);
+@@ -106,7 +109,12 @@ static int add_site(char *addr_string, int type)
+ 	site->family = AF_INET;
+ 	site->type = type;
+ 
+-	strncpy(site->addr_string, addr_string, sizeof(site->addr_string));
++	/* buffer overflow will not hapen (we've already checked that
++	   addr_string will fit incl. terminating '\0' above), but
++	   suppress the warnings with copying everything but the boundary
++	   byte, which is valid as-is, since this last byte will be safely
++	   pre-zeroed from the struct booth_config initialization */
++	strncpy(site->addr_string, addr_string, sizeof(site->addr_string) - 1);
+ 
+ 	if (!(inet_pton(AF_INET, site->addr_string, &site->sa4.sin_addr) > 0) &&
+         !(inet_pton(AF_INET6, site->addr_string, &site->sa6.sin6_addr) > 0)) {
+-- 
+2.18.0.rc2
+
diff --git a/SOURCES/0009-maint-lsb-polish-arbitrator-s-initscript-fix-condres.patch b/SOURCES/0009-maint-lsb-polish-arbitrator-s-initscript-fix-condres.patch
new file mode 100644
index 0000000..ef8d9c4
--- /dev/null
+++ b/SOURCES/0009-maint-lsb-polish-arbitrator-s-initscript-fix-condres.patch
@@ -0,0 +1,145 @@
+From 5703061ba7142509647eb6ee8a0155464677b45a Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jan=20Pokorn=C3=BD?= <jpokorny@redhat.com>
+Date: Thu, 19 Jul 2018 18:45:59 +0200
+Subject: [PATCH 1/3] maint: lsb: polish arbitrator's initscript, fix
+ condrestart
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+- previously, condrestart/try-restart (regardless whether "multi-tenant"
+  or not) would not work as expected
+- make it apparent the script is in fact not fully LSB-compliant
+- suppress some ShellCheck issues, mostly related to the fact that
+  some variables are injected based on "boothd status" invocation,
+  also drop an unused variable (see commits 8732542 + 054dafa
+  [until which it might have been useful] + 9e6359f)
+- fix whitespace issues + normalize indentation of "case - esac"
+
+Signed-off-by: Jan Pokorný <jpokorny@redhat.com>
+---
+ script/lsb/booth-arbitrator | 64 ++++++++++++++++++++++---------------
+ 1 file changed, 38 insertions(+), 26 deletions(-)
+
+diff --git a/script/lsb/booth-arbitrator b/script/lsb/booth-arbitrator
+index d90fe4b..3cdafc0 100755
+--- a/script/lsb/booth-arbitrator
++++ b/script/lsb/booth-arbitrator
+@@ -1,6 +1,7 @@
+ #!/bin/bash
+ #
+-# BOOTH daemon init script for LSB-compliant Linux distributions.
++# BOOTH daemon init script for SUSE Linux based distributions
++# (almost LSB-compliant, except for s/startproc/start_daemon/ etc.)
+ #
+ # booth-arbitrator	BOOTH arbitrator daemon
+ #
+@@ -40,12 +41,13 @@ check_status() {
+ 
+ 	rc=$BOOTH_ERROR_GENERIC
+ 	eval `"$exec" status "${cnf:+-c$cnf}" ; echo rc=$?`
+-	case $rc in 
++	case $rc in
+ 	0)
+-		case  "$booth_state" in 
++		# shellcheck disable=SC2154
++		case "$booth_state" in
+ 		started)  return $BOOTH_DAEMON_STARTED;;
+ 		starting) return $BOOTH_DAEMON_STARTING;;
+-		*) return $BOOTH_ERROR_GENERIC;;	
++		*) return $BOOTH_ERROR_GENERIC;;
+ 		esac
+ 	;;
+ 	$OCF_NOT_RUNNING) return $BOOTH_DAEMON_NOT_RUNNING;;
+@@ -57,6 +59,7 @@ check_status() {
+ status() {
+ 	echo -n "BOOTH daemon is "
+ 	if check_status; then
++		# shellcheck disable=SC2154
+ 		echo "running - PID $booth_lockpid for $booth_cfg_name, $booth_addr_string:$booth_port"
+ 		return 0
+ 	else
+@@ -90,16 +93,15 @@ stop() {
+ 	wait_time=5
+ 	check_status; rc=$?
+ 	case $rc in
+-	$BOOTH_DAEMON_STARTED);;
+-	$BOOTH_DAEMON_STARTING);;
+-	$BOOTH_DAEMON_EXIST);;
++	$BOOTH_DAEMON_STARTED|$BOOTH_DAEMON_STARTING|$BOOTH_DAEMON_EXIST)
++		;;
+ 	$BOOTH_DAEMON_NOT_RUNNING)
+ 		echo "BOOTH arbitrator daemon is not running."
+ 		return 0
+-	;;
++		;;
+ 	*) return 1;;
+ 	esac
+-	
++
+ 	echo -n $"Stopping BOOTH arbitrator daemon: "
+ #	$exec stop "${cnf:+-c$cnf}"
+ #	sleep 1
+@@ -122,11 +124,10 @@ stop() {
+ }
+ 
+ foreach() {
+-	local cnf cnf_base
++	local cnf
+ 	local rc=0
+ 
+ 	for cnf in ${BOOTH_CONF_FILE:-$CONF_DIR/*.conf} ; do
+-		cnf_base=`basename $cnf`
+ 		"$@"
+ 		rc=$((rc|$?))
+ 	done
+@@ -138,20 +139,31 @@ restart() {
+ 	start
+ }
+ 
++condrestart() {
++	local rc
++
++	check_status; rc=$?
++
++	case "$rc" in
++	$BOOTH_DAEMON_STARTED|$BOOTH_DAEMON_STARTING|$BOOTH_DAEMON_EXIST)
++		# shellcheck disable=SC2154
++		[ ! -f "$booth_lockfile" ] || restart
++		;;
++	esac
++}
++
+ case "$1" in
+-	start|stop|restart)
+-        foreach $1
+-        ;;
+-	reload|force-reload)
+-        foreach restart
+-        ;;
+-	condrestart|try-restart)
+-	[ ! -f "$booth_lockfile" ] || restart
+-        ;;
+-  status)
+-        foreach status
+-        ;;
+-  *)
+-        echo $"Usage: $0 {start|stop|restart|try-restart|condrestart|reload|force-reload|status}"
+-        exit 2
++start|stop|restart|condrestart|status)
++	foreach $1
++	;;
++reload|force-reload)
++	foreach restart
++	;;
++try-restart)
++	foreach condrestart
++	;;
++*)
++	echo $"Usage: $0 {start|stop|restart|try-restart|condrestart|reload|force-reload|status}"
++	exit 2
++	;;
+ esac
+-- 
+2.18.0.rc2
+
diff --git a/SOURCES/0010-maint-test-polish-live_test.sh.patch b/SOURCES/0010-maint-test-polish-live_test.sh.patch
new file mode 100644
index 0000000..af0dc8d
--- /dev/null
+++ b/SOURCES/0010-maint-test-polish-live_test.sh.patch
@@ -0,0 +1,260 @@
+From 4d7278dbd9cf5f9a7d6060eb96e1ba5af6b3d505 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jan=20Pokorn=C3=BD?= <jpokorny@redhat.com>
+Date: Thu, 19 Jul 2018 19:44:34 +0200
+Subject: [PATCH 2/3] maint: test: polish live_test.sh
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+- from issues found with ShellCheck, fix those marked as important
+- suppress some ShellCheck issues, mostly related to the fact that
+  some ticket-related variables are established dynamically,
+  also drop a never-used local variable (see commits 74a3f5c + 4136682)
+  and another global one that was used but consequently ditched
+  (see commits 08f56bd + d952b27)
+- fix s/boots not uptodate/booths not up-to-date/ spelling
+- fix whitespace issues
+
+Signed-off-by: Jan Pokorný <jpokorny@redhat.com>
+---
+ test/live_test.sh | 60 ++++++++++++++++++++++++-----------------------
+ 1 file changed, 31 insertions(+), 29 deletions(-)
+
+diff --git a/test/live_test.sh b/test/live_test.sh
+index f8644a2..c131f8c 100755
+--- a/test/live_test.sh
++++ b/test/live_test.sh
+@@ -41,7 +41,6 @@ EOF
+ [ $# -eq 0 ] && usage 0
+ 
+ cnf=$1
+-BOOTH_DIR="/etc/booth"
+ run_cnf="/etc/booth/booth.conf"
+ 
+ shift 1
+@@ -62,9 +61,9 @@ get_internal_site() {
+ 
+ logmsg() {
+ 	if [ "$WE_SERVER" -o "$_JUST_NETEM" ]; then
+-		logger -t "BOOTHTEST" -p $HA_LOGFACILITY.info -- $@
++		logger -t "BOOTHTEST" -p $HA_LOGFACILITY.info -- "$@"
+ 	else
+-		ssh $SSH_OPTS `get_site 1` logger -t "BOOTHTEST" -p $HA_LOGFACILITY.info -- $@
++		ssh $SSH_OPTS `get_site 1` logger -t "BOOTHTEST" -p $HA_LOGFACILITY.info -- "$@"
+ 	fi
+ }
+ 
+@@ -141,20 +140,20 @@ local_netem_env() {
+ }
+ 
+ is_function() {
+-    test z"`command -v $1`" = z"$1"
++	test z"`command -v $1`" = z"$1"
+ }
+ runcmd() {
+ 	local h=$1 rc
+ 	shift 1
+-	echo "$h: running '$@'" | logmsg
++	echo "$h: running '$*'" | logmsg
+ 	if ip a l | fgrep -wq $h; then
+-		eval $@
++		eval "$@"
+ 	else
+-		ssh $SSH_OPTS $h $@
++		ssh $SSH_OPTS $h "$@"
+ 	fi
+ 	rc=$?
+ 	if [ $rc -ne 0 ]; then
+-		echo "$h: '$@' failed (exit code $rc)" | logmsg
++		echo "$h: '$*' failed (exit code $rc)" | logmsg
+ 	fi
+ 	return $rc
+ }
+@@ -306,7 +305,7 @@ dump_conf() {
+ forall() {
+ 	local h rc=0
+ 	for h in $sites $arbitrators; do
+-		runcmd $h $@
++		runcmd $h "$@"
+ 		rc=$((rc|$?))
+ 	done
+ 	return $rc
+@@ -314,7 +313,7 @@ forall() {
+ forall_withname() {
+ 	local h rc=0 output
+ 	for h in $sites $arbitrators; do
+-		output=`runcmd $h $@`
++		output=`runcmd $h "$@"`
+ 		rc=$((rc|$?))
+ 		echo $h: $output
+ 	done
+@@ -323,7 +322,7 @@ forall_withname() {
+ forall_sites() {
+ 	local h rc=0
+ 	for h in $sites; do
+-		runcmd $h $@
++		runcmd $h "$@"
+ 		rc=$((rc|$?))
+ 	done
+ 	return $rc
+@@ -343,7 +342,7 @@ forall_fun2() {
+ 	f=$1
+ 	shift 1
+ 	for h in $sites $arbitrators; do
+-		$f $@ | ssh $SSH_OPTS $h
++		$f "$@" | ssh $SSH_OPTS $h
+ 		rc=$((rc|$?))
+ 		[ $rc -ne 0 ] && break
+ 	done
+@@ -353,13 +352,13 @@ run_site() {
+ 	local n=$1 h
+ 	shift 1
+ 	h=`echo $sites | awk '{print $'$n'}'`
+-	runcmd $h $@
++	runcmd $h "$@"
+ }
+ run_arbitrator() {
+ 	local n=$1 h
+ 	shift 1
+ 	h=`echo $arbitrators | awk '{print $'$n'}'`
+-	runcmd $h $@
++	runcmd $h "$@"
+ }
+ 
+ # need to get logs from _all_ clusters' nodes
+@@ -450,6 +449,8 @@ n && (/^$/ || /^ticket.*/) {exit}
+ ' $1
+ }
+ wait_exp() {
++	# shellcheck disable=SC2154
++	# (T_expire: defined with get_tkt_settings)
+ 	sleep $T_expire
+ }
+ wait_renewal() {
+@@ -593,7 +594,7 @@ booth_leader_consistency_2() {
+ # b) some booths not uptodate (have no leader for the ticket)
+ # c) ticket expiry times differ
+ check_booth_consistency() {
+-	local tlist tlist_validate rc rc_lead maxdiff
++	local tlist rc rc_lead maxdiff
+ 	tlist=`forall_withname booth list 2>/dev/null | grep $tkt`
+ 
+ 	# Check time consistency
+@@ -620,7 +621,7 @@ check_booth_consistency() {
+ `if [ $rc -ge 4 ]; then
+ 	echo "booth list consistency failed (more than one leader!):"
+ elif [ $rc -ge 2 ]; then
+-	echo "booth list consistency failed (some boots not uptodate):"
++	echo "booth list consistency failed (some booths not up-to-date):"
+ else
+ 	echo "booth list consistency failed (max valid time diff: $maxdiff):"
+ fi`
+@@ -677,11 +678,10 @@ run_report() {
+ runtest() {
+ 	local start_ts end_ts
+ 	local rc booth_status dep_rsc_status
+-	local start_time end_time
+ 	local usrmsg
+ 	rc=0
+ 	TEST=$1
+-	start_time=`date`
++	start_ts=`date`  # to have the expanded form in the logfile
+ 	start_ts=`date +%s`
+ 	echo -n "Testing: $1 (ticket: $tkt)... "
+ 	can_run_test $1 || return 0
+@@ -719,7 +719,7 @@ runtest() {
+ 		usrmsg="test FAIL: $rc"
+ 		;;
+ 	esac
+-	end_time=`date`
++	end_ts=`date`  # to have the expanded form in the logfile
+ 	end_ts=`date +%s`
+ 	echo "finished booth test $1 ($tkt): $usrmsg" | logmsg
+ 	echo "==================================================" | logmsg
+@@ -791,6 +791,8 @@ setup_longgrant2() {
+ }
+ test_longgrant2() {
+ 	local i
++	# shellcheck disable=SC2034
++	# (variable exists merely out of necessity)
+ 	for i in `seq 10`; do
+ 		wait_exp
+ 	done
+@@ -1033,14 +1035,14 @@ setup_split_leader() {
+ 	return 0
+ }
+ test_split_leader() {
+-	run_site 1 $iprules stop $port   >/dev/null
++	run_site 1 $iprules stop $port >/dev/null
+ 	wait_exp
+ 	wait_timeout
+ 	wait_timeout
+ 	wait_timeout
+ 	wait_timeout
+ 	check_cib any || return 1
+-	run_site 1 $iprules start $port  >/dev/null
++	run_site 1 $iprules start $port >/dev/null
+ 	wait_timeout
+ 	wait_timeout
+ 	wait_timeout
+@@ -1049,7 +1051,7 @@ check_split_leader() {
+ 	check_consistency any
+ }
+ recover_split_leader() {
+-	run_site 1 $iprules start $port  >/dev/null
++	run_site 1 $iprules start $port >/dev/null
+ }
+ 
+ ## TEST: split_follower ##
+@@ -1059,10 +1061,10 @@ setup_split_follower() {
+ 	grant_ticket_cib 1
+ }
+ test_split_follower() {
+-	run_site 2 $iprules stop $port  >/dev/null
++	run_site 2 $iprules stop $port >/dev/null
+ 	wait_exp
+ 	wait_timeout
+-	run_site 2 $iprules start $port  >/dev/null
++	run_site 2 $iprules start $port >/dev/null
+ 	wait_timeout
+ }
+ check_split_follower() {
+@@ -1076,9 +1078,9 @@ setup_split_edge() {
+ 	grant_ticket_cib 1
+ }
+ test_split_edge() {
+-	run_site 1 $iprules stop $port  >/dev/null
++	run_site 1 $iprules stop $port >/dev/null
+ 	wait_exp
+-	run_site 1 $iprules start $port  >/dev/null
++	run_site 1 $iprules start $port >/dev/null
+ 	wait_timeout
+ 	wait_timeout
+ }
+@@ -1215,13 +1217,11 @@ internal_arbitrators=`get_value arbitrator < $cnf`
+ all_nodes=`get_all_nodes`
+ port=`get_value port < $cnf`
+ : ${port:=9929}
+-site_cnt=`echo $internal_sites | wc -w`
+-arbitrator_cnt=`echo $internal_arbitrators | wc -w`
+ 
+ if [ "$1" = "__netem__" ]; then
+ 	shift 1
+ 	_JUST_NETEM=1
+-	local_netem_env $@
++	local_netem_env "$@"
+ 	exit
+ fi
+ 
+@@ -1314,6 +1314,8 @@ do
+ 
+ 	eval `get_tkt_settings booth_${i}.conf`
+ 
++	# shellcheck disable=SC2154
++	# (T_timeout: defined with get_tkt_settings)
+ 	MIN_TIMEOUT=`awk -v tm=$T_timeout 'BEGIN{
+ 			if (tm >= 2) print tm;
+ 			else print 2*tm;
+-- 
+2.18.0.rc2
+
diff --git a/SOURCES/0011-maint-ocf-script-eliminate-some-false-positives-with.patch b/SOURCES/0011-maint-ocf-script-eliminate-some-false-positives-with.patch
new file mode 100644
index 0000000..0aeb51a
--- /dev/null
+++ b/SOURCES/0011-maint-ocf-script-eliminate-some-false-positives-with.patch
@@ -0,0 +1,120 @@
+From 6eb95429bb92cab5616feaef0111733f79164811 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jan=20Pokorn=C3=BD?= <jpokorny@redhat.com>
+Date: Thu, 19 Jul 2018 19:11:36 +0200
+Subject: [PATCH 3/3] maint: ocf + script: eliminate some false positives with
+ ShellCheck
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+In particular, prevent sed's character classes to be confused with
+array-like variable access through specified index, which demonstrates
+why it's better to have the ${variable} "enbraced".
+
+Signed-off-by: Jan Pokorný <jpokorny@redhat.com>
+---
+ script/ocf/booth-site      | 7 +++++++
+ script/ocf/geostore        | 2 ++
+ script/ocf/sharedrsc       | 5 +++++
+ script/service-runnable.in | 2 +-
+ 4 files changed, 15 insertions(+), 1 deletion(-)
+
+diff --git a/script/ocf/booth-site b/script/ocf/booth-site
+index 809928c..8178e35 100755
+--- a/script/ocf/booth-site
++++ b/script/ocf/booth-site
+@@ -30,6 +30,7 @@ DEFAULT_BIN="boothd"
+ DEFAULT_CONF="/etc/booth/booth.conf"
+ 
+ : ${OCF_FUNCTIONS_DIR=${OCF_ROOT}/lib/heartbeat}
++# shellcheck source=/usr/lib/ocf/lib/heartbeat/ocf-shellfuncs
+ . ${OCF_FUNCTIONS_DIR}/ocf-shellfuncs
+ 
+ #######################################################################
+@@ -140,6 +141,8 @@ booth_site_start() {
+ 	$OCF_NOT_RUNNING) ;;
+ 	esac
+ 
++	# shellcheck disable=SC2154
++	# (OCF_RESKEY_args: injected by CRM)
+ 	$OCF_RESKEY_daemon daemon -c $OCF_RESKEY_config $OCF_RESKEY_args ||
+ 		return $OCF_ERR_GENERIC
+ 	sleep 1
+@@ -188,6 +191,8 @@ booth_site_validate_all() {
+ 		return $OCF_ERR_INSTALLED
+ 	fi
+ 
++	# shellcheck disable=SC2154
++	# (OCF_RESKEY_CRM_meta_globally_unique: injected by CRM)
+ 	if ocf_is_true $OCF_RESKEY_CRM_meta_globally_unique; then
+ 		ocf_log err "$OCF_RESOURCE_INSTANCE must be configured with the globally_unique=false meta attribute"
+ 		return $OCF_ERR_CONFIGURED
+@@ -198,6 +203,8 @@ booth_site_validate_all() {
+ 
+ : ${OCF_RESKEY_daemon:=$DEFAULT_BIN}
+ : ${OCF_RESKEY_config:=$DEFAULT_CONF}
++# shellcheck disable=SC2034
++# (OCF_REQUIRED_BINARIES consumed by ocf_rarun)
+ OCF_REQUIRED_BINARIES=${OCF_RESKEY_daemon}
+ 
+ ocf_rarun $*
+diff --git a/script/ocf/geostore b/script/ocf/geostore
+index 85842a8..c180418 100755
+--- a/script/ocf/geostore
++++ b/script/ocf/geostore
+@@ -31,7 +31,9 @@
+ # Initialization:
+ 
+ : ${OCF_FUNCTIONS_DIR=${OCF_ROOT}/lib/heartbeat}
++# shellcheck source=/usr/lib/ocf/lib/heartbeat/ocf-shellfuncs
+ . ${OCF_FUNCTIONS_DIR}/ocf-shellfuncs
++# shellcheck source=script/ocf/geo_attr.sh
+ . ${OCF_ROOT}/lib/booth/geo_attr.sh
+ 
+ #######################################################################
+diff --git a/script/ocf/sharedrsc b/script/ocf/sharedrsc
+index 384cfd2..c2ed8ff 100755
+--- a/script/ocf/sharedrsc
++++ b/script/ocf/sharedrsc
+@@ -36,6 +36,7 @@
+ # Initialization:
+ 
+ : ${OCF_FUNCTIONS_DIR=${OCF_ROOT}/lib/heartbeat}
++# shellcheck source=/usr/lib/ocf/lib/heartbeat/ocf-shellfuncs
+ . ${OCF_FUNCTIONS_DIR}/ocf-shellfuncs
+ 
+ #######################################################################
+@@ -148,6 +149,8 @@ sharedrsc_start() {
+ 	if ! owner=`runcmd getowner $DIR`; then
+ 		owner="... nobody, it's only half-claimed"
+ 	fi
++	# shellcheck disable=SC2154
++	# (OCF_RESKEY_dir: injected by CRM)
+ 	ocf_log err "eek, $OCF_RESKEY_dir already owned by $owner"
+ 	return $OCF_ERR_GENERIC
+ }
+@@ -180,6 +183,8 @@ sharedrsc_validate_all() {
+ 	return $OCF_SUCCESS
+ }
+ 
++# shellcheck disable=SC2034
++# (OCF_REQUIRED_PARAMS consumed by ocf_rarun)
+ OCF_REQUIRED_PARAMS="dir"
+ ocf_rarun $*
+ 
+diff --git a/script/service-runnable.in b/script/service-runnable.in
+index 9ea33d4..2f58641 100755
+--- a/script/service-runnable.in
++++ b/script/service-runnable.in
+@@ -30,7 +30,7 @@ fi
+ 
+ if echo "$status" |
+ 	sed -n '/^Revised cluster status:/,$p' |
+-	egrep "^[[:space:]]+$service[[:space:]]+\(.*\):[[:space:]]+Started ([^[:space:]]+) *$" >/dev/null
++	egrep "^[[:space:]]+${service}[[:space:]]+\(.*\):[[:space:]]+Started ([^[:space:]]+) *$" >/dev/null
+ then
+ 	# can be started - we're done.
+ 	exit 0
+-- 
+2.18.0.rc2
+
diff --git a/SPECS/booth.spec b/SPECS/booth.spec
new file mode 100644
index 0000000..6a3e62c
--- /dev/null
+++ b/SPECS/booth.spec
@@ -0,0 +1,325 @@
+# RPMs are split as follows:
+# * booth:
+#   - envelope package serving as a syntactic shortcut to install
+#     booth-site (with architecture reliably preserved)
+# * booth-core:
+#   - package serving as a base for booth-{arbitrator,site},
+#     carrying also basic documentation, license, etc.
+# * booth-arbitrator:
+#   - package to be installed at a machine accessible within HA cluster(s),
+#     but not (necessarily) a member of any, hence no dependency
+#     on anything from cluster stack is required
+# * booth-site:
+#   - package to be installed at a cluster member node
+#     (requires working cluster environment to be useful)
+# * booth-test:
+#   - files for testing booth
+#
+# TODO:
+# wireshark-dissector.lua currently of no use (rhbz#1259623), but if/when
+# this no longer persists, add -wireshark package (akin to libvirt-wireshark)
+
+%bcond_with html_man
+%bcond_with glue
+
+# Disable automatic compilation of Python files in extra directories
+%global _python_bytecompile_extra 0
+
+%global specver 5
+%global boothver 1.0
+# set following to the actual commit or, for final release, concatenate
+# "boothver" macro to "v" (will yield a tag per the convention)
+%global commit f2d38ce3d61502bda2a28e79db103737a691faf4
+%global lparen (
+%global rparen )
+%global shortcommit %(c=%{commit}; case ${c} in
+                      v*%{rparen} echo ${c:1};;
+                      *%{rparen} echo ${c:0:7};; esac)
+%global pre_release %(s=%{shortcommit}; [ ${s: -3:2} != rc ]; echo $?)
+%global post_release %([ %{commit} = v%{shortcommit} ]; echo $?)
+%global github_owner ClusterLabs
+
+%if 0%{pre_release}
+%global boothrel    0.%{specver}.%(s=%{shortcommit}; echo ${s: -3})
+%else
+%if 0%{post_release}
+%global boothrel    %{specver}.%{shortcommit}.git
+%else
+%global boothrel    %{specver}
+%endif
+%endif
+
+%{!?_pkgdocdir: %global _pkgdocdir %{_docdir}/%{name}}
+# https://fedoraproject.org/wiki/EPEL:Packaging?rd=Packaging:EPEL#The_.25license_tag
+%{!?_licensedir:%global license %doc}
+
+%global test_path   %{_datadir}/booth/tests
+
+Name:           booth
+Version:        %{boothver}
+Release:        %{boothrel}%{dist}
+Summary:        Ticket Manager for Multi-site Clusters
+Group:          System Environment/Daemons
+License:        GPLv2+
+Url:            https://github.com/%{github_owner}/%{name}
+Source0:        https://github.com/%{github_owner}/%{name}/archive/%{commit}/%{name}-%{shortcommit}.tar.gz
+Patch0:         0000-test-remove-superfluous-shebangs-for-import-only-mod.patch
+Patch1:         0001-test-do-not-mix-tabs-with-spaces-in-Python-code.patch
+Patch2:         0002-test-make-Python-files-supported-_also_-with-Python-.patch
+Patch3:         0003-build-parametrize-Python-invocations-in-the-shebangs.patch
+Patch4:         0004-test-drop-underqualified-identifier-in-unittest-s-2..patch
+Patch5:         0005-test-drop-comment-out-superfluous-imports.patch
+Patch6:         0006-test-avoid-dangerous-mutable-sticky-default-value.patch
+Patch7:         0007-test-unit-test.py-daemon-will-not-stay-in-foreground.patch
+Patch8:         0008-Refactor-fix-strncpy-may-miss-trailing-null-byte-war.patch
+Patch9:         0009-maint-lsb-polish-arbitrator-s-initscript-fix-condres.patch
+Patch10:        0010-maint-test-polish-live_test.sh.patch
+Patch11:        0011-maint-ocf-script-eliminate-some-false-positives-with.patch
+
+# direct build process dependencies
+BuildRequires:  autoconf
+BuildRequires:  automake
+BuildRequires:  coreutils
+BuildRequires:  make
+## ./autogen.sh
+BuildRequires:  /bin/sh
+# general build dependencies
+BuildRequires:  asciidoc
+BuildRequires:  gcc
+BuildRequires:  pkgconfig
+# linking dependencies
+BuildRequires:  libgcrypt-devel
+BuildRequires:  libxml2-devel
+## just for <pacemaker/crm/services.h> include
+BuildRequires:  pacemaker-libs-devel
+BuildRequires:  pkgconfig(glib-2.0)
+BuildRequires:  zlib-devel
+## logging provider
+BuildRequires:  pkgconfig(libqb)
+## random2range provider
+BuildRequires:  pkgconfig(glib-2.0)
+## nametag provider
+BuildRequires:  pkgconfig(libsystemd)
+# check scriptlet (for hostname and killall respectively)
+BuildRequires:  hostname psmisc
+BuildRequires:  python3-devel
+# spec file specifics
+## for _unitdir, systemd_requires and specific scriptlet macros
+BuildRequires:  systemd
+## for autosetup
+BuildRequires:  git
+
+# this is for a composite-requiring-its-components arranged
+# as an empty package (empty files section) requiring subpackages
+# (_isa so as to preserve the architecture)
+Requires:       %{name}-core%{?_isa}
+Requires:       %{name}-site
+%files
+# intentionally empty
+
+%description
+Booth manages tickets which authorize cluster sites located
+in geographically dispersed locations to run resources.
+It facilitates support of geographically distributed
+clustering in Pacemaker.
+
+# SUBPACKAGES #
+
+%package        core
+Summary:        Booth core files (executables, etc.)
+Group:          System Environment/Daemons
+# for booth-keygen (chown, dd)
+Requires:       coreutils
+# deal with pre-split arrangement
+Conflicts:      %{name} < 1.0-1
+
+%description    core
+Core files (executables, etc.) for Booth, ticket manager for
+multi-site clusters.
+
+%package        arbitrator
+Summary:        Booth support for running as an arbitrator
+Group:          System Environment/Daemons
+BuildArch:      noarch
+Requires:       %{name}-core = %{version}-%{release}
+%{?systemd_requires}
+# deal with pre-split arrangement
+Conflicts:      %{name} < 1.0-1
+
+%description    arbitrator
+Support for running Booth, ticket manager for multi-site clusters,
+as an arbitrator.
+
+%post arbitrator
+%systemd_post booth@.service booth-arbitrator.service
+
+%preun arbitrator
+%systemd_preun booth@.service booth-arbitrator.service
+
+%postun arbitrator
+%systemd_postun_with_restart booth@.service booth-arbitrator.service
+
+%package        site
+Summary:        Booth support for running as a full-fledged site
+Group:          System Environment/Daemons
+BuildArch:      noarch
+Requires:       %{name}-core = %{version}-%{release}
+# for crm_{resource,simulate,ticket} utilities
+Requires:       pacemaker >= 1.1.8
+# for ocf-shellfuncs and other parts of OCF shell-based environment
+Requires:       resource-agents
+# deal with pre-split arrangement
+Conflicts:      %{name} < 1.0-1
+
+%description    site
+Support for running Booth, ticket manager for multi-site clusters,
+as a full-fledged site.
+
+%package        test
+Summary:        Test scripts for Booth
+Group:          System Environment/Daemons
+BuildArch:      noarch
+# runtests.py suite (for hostname and killall respectively)
+Requires:       hostname psmisc
+# any of the following internal dependencies will pull -core package
+## for booth@booth.service
+Requires:       %{name}-arbitrator = %{version}-%{release}
+## for booth-site and service-runnable scripts
+## (and /usr/lib/ocf/resource.d/booth)
+Requires:       %{name}-site = %{version}-%{release}
+Requires:       gdb
+Requires:       %{__python3}
+Requires:       python3-pexpect
+
+%description    test
+Automated tests for running Booth, ticket manager for multi-site clusters.
+
+# BUILD #
+
+%prep
+%autosetup -n %{name}-%{commit} -S git_am
+
+%build
+./autogen.sh
+%{configure} \
+        --with-initddir=%{_initrddir} \
+        --docdir=%{_pkgdocdir} \
+        --enable-user-flags \
+        %{!?with_html_man:--without-html_man} \
+        %{!?with_glue:--without-glue}
+%{make_build}
+
+%install
+%{make_install}
+mkdir -p %{buildroot}/%{_unitdir}
+cp -a -t %{buildroot}/%{_unitdir} \
+        -- conf/booth@.service conf/booth-arbitrator.service
+install -D -m 644 -t %{buildroot}/%{_mandir}/man8 \
+        -- docs/boothd.8
+ln -s boothd.8 %{buildroot}/%{_mandir}/man8/booth.8
+cp -a -t %{buildroot}/%{_pkgdocdir} \
+        -- ChangeLog README-testing conf/booth.conf.example
+# drop what we don't package anyway (COPYING added via tarball-relative path)
+rm -rf %{buildroot}/%{_initrddir}/booth-arbitrator
+rm -rf %{buildroot}/%{_pkgdocdir}/README.upgrade-from-v0.1
+rm -rf %{buildroot}/%{_pkgdocdir}/COPYING
+# tests
+mkdir -p %{buildroot}/%{test_path}
+cp -a -t %{buildroot}/%{test_path} \
+        -- conf test unit-tests script/unit-test.py
+chmod +x %{buildroot}/%{test_path}/test/booth_path
+chmod +x %{buildroot}/%{test_path}/test/live_test.sh
+mkdir -p %{buildroot}/%{test_path}/src
+ln -s -t %{buildroot}/%{test_path}/src \
+        -- %{_sbindir}/boothd
+
+# https://fedoraproject.org/wiki/Packaging:Python_Appendix#Manual_byte_compilation
+%py_byte_compile %{__python3} %{buildroot}/%{test_path}
+
+%check
+# alternatively: test/runtests.py
+VERBOSE=1 make check
+
+%files          core
+%license COPYING
+%doc %{_pkgdocdir}/AUTHORS
+%doc %{_pkgdocdir}/ChangeLog
+%doc %{_pkgdocdir}/README
+%doc %{_pkgdocdir}/booth.conf.example
+# core command(s) + man pages
+%{_sbindir}/booth*
+%{_mandir}/man8/booth*.8*
+# configuration
+%dir %{_sysconfdir}/booth
+%exclude %{_sysconfdir}/booth/booth.conf.example
+
+%files          arbitrator
+%{_unitdir}/booth@.service
+%{_unitdir}/booth-arbitrator.service
+
+%files          site
+# OCF (agent + a helper)
+## /usr/lib/ocf/resource.d/pacemaker provided by pacemaker
+/usr/lib/ocf/resource.d/pacemaker/booth-site
+%dir /usr/lib/ocf/lib/booth
+     /usr/lib/ocf/lib/booth/geo_attr.sh
+# geostore (command + OCF agent)
+%{_sbindir}/geostore
+%{_mandir}/man8/geostore.8*
+## /usr/lib/ocf/resource.d provided by resource-agents
+%dir /usr/lib/ocf/resource.d/booth
+     /usr/lib/ocf/resource.d/booth/geostore
+# helper (possibly used in the configuration hook)
+%dir %{_datadir}/booth
+     %{_datadir}/booth/service-runnable
+
+%files          test
+%doc %{_pkgdocdir}/README-testing
+# /usr/share/booth provided by -site
+%{test_path}
+# /usr/lib/ocf/resource.d/booth provided by -site
+/usr/lib/ocf/resource.d/booth/sharedrsc
+
+%changelog
+* Wed Sep 19 2018 Tomas Orsava <torsava@redhat.com> - 1.0-5.f2d38ce.git
+- Require the Python interpreter directly instead of using the package name
+- Related: rhbz#1619153
+
+* Thu Jul 19 2018 Jan Pokorný <jpokorny+rpm-booth@redhat.com> - 1.0-4.f2d38ce.git
+- revert back to using asciidoc instead of asciidoctor for generating man pages
+  (rhbz#1603119)
+- fix some issues in the shell scripts (rhbz#1602455)
+
+* Mon Jul 16 2018 Jan Pokorný <jpokorny+rpm-booth@redhat.com> - 1.0-3.f2d38ce.git
+- update for another, current snapshot beyond booth-1.0
+  (commit f2d38ce), including:
+  . support for solely manually managed tickets (9a365f9)
+  . use asciidoctor instead of asciidoc for generating man pages (65e6a6b)
+- switch to using Python 3 for the tests instead of Python 2
+  (behind unversioned "python" references; rhbz#1590856)
+
+* Thu Jun 21 2018 Troy Dawson <tdawson@redhat.com> - 1.0-2.570876d.git.3
+- Fix python shebangs (#1580601)
+
+* Fri Feb 10 2017 Fedora Release Engineering <releng@fedoraproject.org> - 1.0-2.570876d.git.2
+- Rebuilt for https://fedoraproject.org/wiki/Fedora_26_Mass_Rebuild
+
+* Tue Jul 19 2016 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 1.0-2.570876d.git.1
+- https://fedoraproject.org/wiki/Changes/Automatic_Provides_for_Python_RPM_Packages
+
+* Wed May 25 2016 Jan Pokorný <jpokorny+rpm-booth@fedoraproject.org> - 1.0-3.570876d.git
+- update per the changesets recently accepted by the upstream
+  (memory/resource leaks fixes, patches previously attached separately
+  that make unit test pass, internal cleanups, etc.)
+
+* Thu May 05 2016 Jan Pokorný <jpokorny+rpm-booth@fedoraproject.org> - 1.0-2.eb4256a.git
+- update a subset of out-of-tree patches per
+  https://github.com/ClusterLabs/booth/pull/22#issuecomment-216936987
+- pre-inclusion cleanups in the spec (apply systemd scriptlet operations
+  with booth-arbitrator, avoid overloading file implicitly considered %%doc
+  as %%license)
+  Resolves: rhbz#1314865
+  Related: rhbz#1333509
+
+* Thu Apr 28 2016 Jan Pokorný <jpokorny+rpm-booth@fedoraproject.org> - 1.0-1.eb4256a.git
+- initial build