From 6cd19894e9b2630be3fdc2a60ff4acb39fe55c6d Mon Sep 17 00:00:00 2001 From: sebres Date: Fri, 12 Feb 2016 21:51:32 +0100 Subject: [PATCH] some compatibility fixes (prevent forking of testcase-process, code review), wait 4 server ready, test cases fixed (py2/py3) --- fail2ban/client/fail2banclient.py | 80 ++++++++++------ fail2ban/client/fail2bancmdline.py | 8 +- fail2ban/client/fail2banserver.py | 108 +++++++++++++-------- fail2ban/server/server.py | 36 ++++--- fail2ban/tests/fail2banclienttestcase.py | 116 +++++++++++++---------- fail2ban/tests/utils.py | 6 +- 6 files changed, 219 insertions(+), 135 deletions(-) diff --git a/fail2ban/client/fail2banclient.py b/fail2ban/client/fail2banclient.py index 4f4caf79..ad5cc57e 100644 --- a/fail2ban/client/fail2banclient.py +++ b/fail2ban/client/fail2banclient.py @@ -34,14 +34,18 @@ from threading import Thread from ..version import version from .csocket import CSocket from .beautifier import Beautifier -from .fail2bancmdline import Fail2banCmdLine, ExitException, PRODUCTION, logSys, exit, output +from .fail2bancmdline import Fail2banCmdLine, ServerExecutionException, ExitException, \ + logSys, PRODUCTION, exit, output MAX_WAITTIME = 30 +PROMPT = "fail2ban> " def _thread_name(): return threading.current_thread().__class__.__name__ +def input_command(): + return raw_input(PROMPT) ## # @@ -49,8 +53,6 @@ def _thread_name(): class Fail2banClient(Fail2banCmdLine, Thread): - PROMPT = "fail2ban> " - def __init__(self): Fail2banCmdLine.__init__(self) Thread.__init__(self) @@ -91,11 +93,11 @@ class Fail2banClient(Fail2banCmdLine, Thread): client = CSocket(self._conf["socket"]) ret = client.send(c) if ret[0] == 0: - logSys.debug("OK : " + `ret[1]`) + logSys.debug("OK : %r", ret[1]) if showRet or c[0] == 'echo': output(beautifier.beautify(ret[1])) else: - logSys.error("NOK: " + `ret[1].args`) + logSys.error("NOK: %r", ret[1].args) if showRet: output(beautifier.beautifyError(ret[1])) streamRet = False @@ -202,7 +204,10 @@ class Fail2banClient(Fail2banCmdLine, Thread): except Exception as e: output("") logSys.error("Exception while starting server " + ("background" if background else "foreground")) - logSys.error(e) + if self._conf["verbose"] > 1: + logSys.exception(e) + else: + logSys.error(e) return False return True @@ -249,7 +254,9 @@ class Fail2banClient(Fail2banCmdLine, Thread): if self._conf.get("interactive", False): output(' ## stop ... ') self.__processCommand(['stop']) - self.__waitOnServer(False) + if not self.__waitOnServer(False): + logSys.error("Could not stop server") + return False # in interactive mode reset config, to make full-reload if there something changed: if self._conf.get("interactive", False): output(' ## load configuration ... ') @@ -286,10 +293,14 @@ class Fail2banClient(Fail2banCmdLine, Thread): def __processStartStreamAfterWait(self, *args): try: # Wait for the server to start - self.__waitOnServer() + if not self.__waitOnServer(): + logSys.error("Could not find server, waiting failed") + return False # Configure the server self.__processCmd(*args) - except ServerExecutionException: + except ServerExecutionException as e: + if self._conf["verbose"] > 1: + logSys.exception(e) logSys.error("Could not start server. Maybe an old " "socket file is still present. Try to " "remove " + self._conf["socket"] + ". If " @@ -306,11 +317,13 @@ class Fail2banClient(Fail2banCmdLine, Thread): # Wait for the server to start (the server has 30 seconds to answer ping) starttime = time.time() logSys.debug("__waitOnServer: %r", (alive, maxtime)) + test = lambda: os.path.exists(self._conf["socket"]) and self.__ping() with VisualWait(self._conf["verbose"]) as vis: - while self._alive and ( - not self.__ping() == alive or ( - not alive and os.path.exists(self._conf["socket"]) - )): + sltime = 0.0125 / 2 + while self._alive: + runf = test() + if runf == alive: + return True now = time.time() # Wonderful visual :) if now > starttime + 1: @@ -318,7 +331,9 @@ class Fail2banClient(Fail2banCmdLine, Thread): # f end time reached: if now - starttime >= maxtime: raise ServerExecutionException("Failed to start server") - time.sleep(0.1) + sltime = min(sltime * 2, 0.5) + time.sleep(sltime) + return False def start(self, argv): # Install signal handlers @@ -332,27 +347,31 @@ class Fail2banClient(Fail2banCmdLine, Thread): if self._argv is None: ret = self.initCmdLine(argv) if ret is not None: - return ret + if ret: + return True + raise ServerExecutionException("Init of command line failed") # Commands args = self._args # Interactive mode if self._conf.get("interactive", False): - try: - import readline - except ImportError: - logSys.error("Readline not available") - return False + # no readline in test: + if PRODUCTION: # pragma: no cover + try: + import readline + except ImportError: + raise ServerExecutionException("Readline not available") try: ret = True if len(args) > 0: ret = self.__processCommand(args) if ret: - readline.parse_and_bind("tab: complete") + if PRODUCTION: # pragma: no cover + readline.parse_and_bind("tab: complete") self.dispInteractive() while True: - cmd = raw_input(self.PROMPT) + cmd = input_command() if cmd == "exit" or cmd == "quit": # Exit return True @@ -362,26 +381,31 @@ class Fail2banClient(Fail2banCmdLine, Thread): try: self.__processCommand(shlex.split(cmd)) except Exception, e: - logSys.error(e) + if self._conf["verbose"] > 1: + logSys.exception(e) + else: + logSys.error(e) except (EOFError, KeyboardInterrupt): output("") - return True + raise # Single command mode else: if len(args) < 1: self.dispUsage() return False return self.__processCommand(args) + except Exception as e: + if self._conf["verbose"] > 1: + logSys.exception(e) + else: + logSys.error(e) + return False finally: self._alive = False for s, sh in _prev_signals.iteritems(): signal.signal(s, sh) -class ServerExecutionException(Exception): - pass - - ## # Wonderful visual :) # diff --git a/fail2ban/client/fail2bancmdline.py b/fail2ban/client/fail2bancmdline.py index abbf8363..58fd47c2 100644 --- a/fail2ban/client/fail2bancmdline.py +++ b/fail2ban/client/fail2bancmdline.py @@ -259,5 +259,9 @@ class Fail2banCmdLine(): exit = Fail2banCmdLine.exit -class ExitException: - pass \ No newline at end of file +class ExitException(Exception): + pass + + +class ServerExecutionException(Exception): + pass diff --git a/fail2ban/client/fail2banserver.py b/fail2ban/client/fail2banserver.py index ac927251..73e528ca 100644 --- a/fail2ban/client/fail2banserver.py +++ b/fail2ban/client/fail2banserver.py @@ -24,10 +24,8 @@ __license__ = "GPL" import os import sys -from ..version import version -from ..server.server import Server -from ..server.utils import Utils -from .fail2bancmdline import Fail2banCmdLine, logSys, PRODUCTION, exit +from .fail2bancmdline import Fail2banCmdLine, ServerExecutionException, \ + logSys, PRODUCTION, exit MAX_WAITTIME = 30 @@ -44,13 +42,14 @@ class Fail2banServer(Fail2banCmdLine): # Fail2banCmdLine.__init__(self) ## - # Start Fail2Ban server in main thread without fork (foreground). + # Start Fail2Ban server in main thread without fork (direct, it can fork itself in Server if daemon=True). # - # Start the Fail2ban server in foreground (daemon mode or not). + # Start the Fail2ban server in background/foreground (daemon mode or not). @staticmethod def startServerDirect(conf, daemon=True): logSys.debug("-- direct starting of server in %s, deamon: %s", os.getpid(), daemon) + from ..server.server import Server server = None try: # Start it in foreground (current thread, not new process), @@ -59,11 +58,14 @@ class Fail2banServer(Fail2banCmdLine): server.start(conf["socket"], conf["pidfile"], conf["force"], conf=conf) - except Exception, e: - logSys.exception(e) - if server: - server.quit() - exit(-1) + except Exception as e: + try: + if server: + server.quit() + except Exception as e2: + if conf["verbose"] > 1: + logSys.exception(e2) + raise return server @@ -74,10 +76,6 @@ class Fail2banServer(Fail2banCmdLine): @staticmethod def startServerAsync(conf): - # Directory of client (to try the first start from the same directory as client): - startdir = sys.path[0] - if startdir in ("", "."): # may be uresolved in test-cases, so get bin-directory: - startdir = os.path.dirname(sys.argv[0]) # Forks the current process, don't fork if async specified (ex: test cases) pid = 0 frk = not conf["async"] and PRODUCTION @@ -103,25 +101,43 @@ class Fail2banServer(Fail2banCmdLine): for o in ('loglevel', 'logtarget', 'syslogsocket'): args.append("--"+o) args.append(conf[o]) - try: - # Use the current directory. - exe = os.path.abspath(os.path.join(startdir, SERVER)) + # Directory of client (to try the first start from current or the same directory as client, and from relative bin): + exe = Fail2banServer.getServerPath() + if not frk: + # Wrapr args to use the same python version in client/server (important for multi-python systems): + args[0] = exe + exe = sys.executable + args[0:0] = [exe] logSys.debug("Starting %r with args %r", exe, args) if frk: - os.execv(exe, args) + return os.execv(exe, args) else: - os.spawnv(os.P_NOWAITO, exe, args) + # use P_WAIT instead of P_NOWAIT (to prevent defunct-zomby process), it startet as daemon, so parent exit fast after fork): + return os.spawnv(os.P_WAIT, exe, args) except OSError as e: - try: - # Use the PATH env. - logSys.warning("Initial start attempt failed (%s). Starting %r with the same args", e, SERVER) - if frk: - os.execvp(SERVER, args) - else: - os.spawnvp(os.P_NOWAITO, SERVER, args) - except OSError: - exit(-1) + # Use the PATH env. + logSys.warning("Initial start attempt failed (%s). Starting %r with the same args", e, SERVER) + if frk: + return os.execvp(SERVER, args) + else: + del args[0] + args[0] = SERVER + return os.spawnvp(os.P_WAIT, SERVER, args) + return pid + + @staticmethod + def getServerPath(): + startdir = sys.path[0] + exe = os.path.abspath(os.path.join(startdir, SERVER)) + if not os.path.isfile(exe): # may be uresolved in test-cases, so get relative starter (client): + startdir = os.path.dirname(sys.argv[0]) + exe = os.path.abspath(os.path.join(startdir, SERVER)) + if not os.path.isfile(exe): # may be uresolved in test-cases, so try to get relative bin-directory: + startdir = os.path.dirname(os.path.abspath(__file__)) + startdir = os.path.join(os.path.dirname(os.path.dirname(startdir)), "bin") + exe = os.path.abspath(os.path.join(startdir, SERVER)) + return exe def _Fail2banClient(self): from .fail2banclient import Fail2banClient @@ -139,18 +155,24 @@ class Fail2banServer(Fail2banCmdLine): args = self._args cli = None - # If client mode - whole processing over client: - if len(args) or self._conf.get("interactive", False): - cli = self._Fail2banClient() - return cli.start(argv) + # Just start: + if len(args) == 1 and args[0] == 'start' and not self._conf.get("interactive", False): + pass + else: + # If client mode - whole processing over client: + if len(args) or self._conf.get("interactive", False): + cli = self._Fail2banClient() + return cli.start(argv) # Start the server: server = None try: - # async = True, if started from client, should fork, daemonize, etc... - # background = True, if should start in new process, otherwise start in foreground - async = self._conf.get("async", False) + from ..server.utils import Utils + # background = True, if should be new process running in background, otherwise start in foreground + # process will be forked in daemonize, inside of Server module. + # async = True, if started from client, should... background = self._conf["background"] + async = self._conf.get("async", False) # If was started not from the client: if not async: # Start new thread with client to read configuration and @@ -162,13 +184,14 @@ class Fail2banServer(Fail2banCmdLine): # wait up to MAX_WAITTIME, do not continue if configuration is not 100% valid: Utils.wait_for(lambda: phase.get('ready', None) is not None, MAX_WAITTIME) if not phase.get('start', False): - return False + raise ServerExecutionException('Async configuration of server failed') # Start server, daemonize it, etc. - if async or not background: - server = Fail2banServer.startServerDirect(self._conf, background) - else: - Fail2banServer.startServerAsync(self._conf) + pid = os.getpid() + server = Fail2banServer.startServerDirect(self._conf, background) + # If forked - just exit other processes + if pid != os.getpid(): + os._exit(0) if cli: cli._server = server @@ -182,7 +205,8 @@ class Fail2banServer(Fail2banCmdLine): logSys.debug('Starting server done') except Exception, e: - logSys.exception(e) + if self._conf["verbose"] > 1: + logSys.exception(e) if server: server.quit() exit(-1) diff --git a/fail2ban/server/server.py b/fail2ban/server/server.py index 9f56f8a8..0ddaea35 100644 --- a/fail2ban/server/server.py +++ b/fail2ban/server/server.py @@ -93,9 +93,15 @@ class Server: if self.__daemon: # pragma: no cover logSys.info("Starting in daemon mode") ret = self.__createDaemon() - if not ret: - logSys.error("Could not create daemon") - raise ServerInitializationError("Could not create daemon") + # If forked parent - return here (parent process will configure server later): + if ret is None: + return False + # If error: + if not ret[0]: + err = "Could not create daemon %s", ret[1:] + logSys.error(err) + raise ServerInitializationError(err) + # We are daemon. # Set all logging parameters (or use default if not specified): self.setSyslogSocket(conf.get("syslogsocket", @@ -159,8 +165,12 @@ class Server: logging.shutdown() # Restore default signal handlers: - for s, sh in self.__prev_signals.iteritems(): - signal.signal(s, sh) + if _thread_name() == '_MainThread': + for s, sh in self.__prev_signals.iteritems(): + signal.signal(s, sh) + + # Prevent to call quit twice: + self.quit = lambda: False def addJail(self, name, backend): self.__jails.add(name, backend, self.__db) @@ -559,10 +569,9 @@ class Server: # We need to set this in the parent process, so it gets inherited by the # child process, and this makes sure that it is effect even if the parent # terminates quickly. - if _thread_name() == '_MainThread': - for s in (signal.SIGHUP,): - self.__prev_signals[s] = signal.getsignal(s) - signal.signal(s, signal.SIG_IGN) + for s in (signal.SIGHUP,): + self.__prev_signals[s] = signal.getsignal(s) + signal.signal(s, signal.SIG_IGN) try: # Fork a child process so the parent can exit. This will return control @@ -573,7 +582,7 @@ class Server: # PGID. pid = os.fork() except OSError, e: - return((e.errno, e.strerror)) # ERROR (return a tuple) + return (False, (e.errno, e.strerror)) # ERROR (return a tuple) if pid == 0: # The first child. @@ -594,7 +603,7 @@ class Server: # preventing the daemon from ever acquiring a controlling terminal. pid = os.fork() # Fork a second child. except OSError, e: - return((e.errno, e.strerror)) # ERROR (return a tuple) + return (False, (e.errno, e.strerror)) # ERROR (return a tuple) if (pid == 0): # The second child. # Ensure that the daemon doesn't keep any directory in use. Failure @@ -603,7 +612,8 @@ class Server: else: os._exit(0) # Exit parent (the first child) of the second child. else: - os._exit(0) # Exit parent of the first child. + # Signal to exit, parent of the first child. + return None # Close all open files. Try the system configuration variable, SC_OPEN_MAX, # for the maximum number of open files to close. If it doesn't exist, use @@ -631,7 +641,7 @@ class Server: os.open("/dev/null", os.O_RDONLY) # standard input (0) os.open("/dev/null", os.O_RDWR) # standard output (1) os.open("/dev/null", os.O_RDWR) # standard error (2) - return True + return (True,) class ServerInitializationError(Exception): diff --git a/fail2ban/tests/fail2banclienttestcase.py b/fail2ban/tests/fail2banclienttestcase.py index fd8a074b..6fee732b 100644 --- a/fail2ban/tests/fail2banclienttestcase.py +++ b/fail2ban/tests/fail2banclienttestcase.py @@ -26,6 +26,7 @@ __license__ = "GPL" import fileinput import os import re +import sys import time import unittest @@ -50,10 +51,9 @@ else: CLIENT = "fail2ban-client" SERVER = "fail2ban-server" -BIN = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), "bin") +BIN = os.path.dirname(Fail2banServer.getServerPath()) -MAX_WAITTIME = 10 -MAX_WAITTIME = unittest.F2B.maxWaitTime(MAX_WAITTIME) +MAX_WAITTIME = 30 if not unittest.F2B.fast else 5 ## # Several wrappers and settings for proper testing: @@ -67,8 +67,6 @@ fail2bancmdline.logSys = \ fail2banclient.logSys = \ fail2banserver.logSys = logSys -LOG_LEVEL = logSys.level - server.DEF_LOGTARGET = "/dev/null" def _test_output(*args): @@ -89,13 +87,13 @@ fail2banclient.exit = \ fail2banserver.exit = _test_exit INTERACT = [] -def _test_raw_input(*args): +def _test_input_command(*args): if len(INTERACT): - #print('--- interact command: ', INTERACT[0]) + #logSys.debug('--- interact command: %r', INTERACT[0]) return INTERACT.pop(0) else: return "exit" -fail2banclient.raw_input = _test_raw_input +fail2banclient.input_command = _test_input_command # prevents change logging params, log capturing, etc: fail2bancmdline.PRODUCTION = \ @@ -142,7 +140,7 @@ def _start_params(tmp, use_stock=False, logtarget="/dev/null"): else: # just empty config directory without anything (only fail2ban.conf/jail.conf): os.mkdir(cfg) - f = open(cfg+"/fail2ban.conf", "wb") + f = open(cfg+"/fail2ban.conf", "w") f.write('\n'.join(( "[Definition]", "loglevel = INFO", @@ -156,20 +154,20 @@ def _start_params(tmp, use_stock=False, logtarget="/dev/null"): "", ))) f.close() - f = open(cfg+"/jail.conf", "wb") + f = open(cfg+"/jail.conf", "w") f.write('\n'.join(( "[INCLUDES]", "", "[DEFAULT]", "", "", ))) f.close() - if LOG_LEVEL < logging.DEBUG: # if HEAVYDEBUG + if logSys.level < logging.DEBUG: # if HEAVYDEBUG _out_file(cfg+"/fail2ban.conf") _out_file(cfg+"/jail.conf") - # parameters: - return ("-c", cfg, - "--logtarget", logtarget, "--loglevel", "DEBUG", "--syslogsocket", "auto", - "-s", tmp+"/f2b.sock", "-p", tmp+"/f2b.pid") + # parameters (sock/pid and config, increase verbosity, set log, etc.): + return ("-c", cfg, "-s", tmp+"/f2b.sock", "-p", tmp+"/f2b.pid", + "-vv", "--logtarget", logtarget, "--loglevel", "DEBUG", "--syslogsocket", "auto", + ) def _kill_srv(pidfile): # pragma: no cover def _pid_exists(pid): @@ -206,14 +204,14 @@ def _kill_srv(pidfile): # pragma: no cover os.kill(pid, signal.SIGKILL) return not _pid_exists(pid) except Exception as e: - sysLog.debug(e) + logSys.debug(e) finally: if f is not None: f.close() return True -class Fail2banClientTest(LogCaptureTestCase): +class Fail2banClientServerBase(LogCaptureTestCase): def setUp(self): """Call before every test case.""" @@ -223,6 +221,21 @@ class Fail2banClientTest(LogCaptureTestCase): """Call after every test case.""" LogCaptureTestCase.tearDown(self) + def _wait_for_srv(self, tmp, ready=True, startparams=None): + sock = tmp+"/f2b.sock" + # wait for server (socket): + ret = Utils.wait_for(lambda: os.path.exists(sock), MAX_WAITTIME) + if not ret: + raise Exception('Unexpected: Socket file does not exists.\nStart failed: %r' % (startparams,)) + if ready: + # wait for communication with worker ready: + ret = Utils.wait_for(lambda: "Server ready" in self.getLog(), MAX_WAITTIME) + if not ret: + raise Exception('Unexpected: Server ready was not found.\nStart failed: %r' % (startparams,)) + + +class Fail2banClientTest(Fail2banClientServerBase): + def testClientUsage(self): self.assertRaises(ExitException, _exec_client, (CLIENT, "-h",)) @@ -232,12 +245,13 @@ class Fail2banClientTest(LogCaptureTestCase): @withtmpdir def testClientStartBackgroundInside(self, tmp): try: - # always add "--async" by start inside, should don't fork by async (not replace client with server, just start in new process) - # (we can't fork the test cases process): + # use once the stock configuration (to test starting also) startparams = _start_params(tmp, True) # start: self.assertRaises(ExitException, _exec_client, - (CLIENT, "--async", "-b") + startparams + ("start",)) + (CLIENT, "-b") + startparams + ("start",)) + # wait for server (socket and ready): + self._wait_for_srv(tmp, True, startparams=startparams) self.assertLogged("Server ready") self.assertLogged("Exit with code 0") try: @@ -245,6 +259,11 @@ class Fail2banClientTest(LogCaptureTestCase): (CLIENT,) + startparams + ("echo", "TEST-ECHO",)) self.assertRaises(FailExitException, _exec_client, (CLIENT,) + startparams + ("~~unknown~cmd~failed~~",)) + self.pruneLog() + # start again (should fail): + self.assertRaises(FailExitException, _exec_client, + (CLIENT, "-b") + startparams + ("start",)) + self.assertLogged("Server already running") finally: self.pruneLog() # stop: @@ -260,11 +279,14 @@ class Fail2banClientTest(LogCaptureTestCase): try: global INTERACT startparams = _start_params(tmp) - # start (without async in new process): - cmd = os.path.join(os.path.join(BIN), CLIENT) + # start (in new process, using the same python version): + cmd = (sys.executable, os.path.join(os.path.join(BIN), CLIENT)) logSys.debug('Start %s ...', cmd) - Utils.executeCmd((cmd,) + startparams + ("start",), - timeout=MAX_WAITTIME, shell=False, output=False) + cmd = cmd + startparams + ("start",) + Utils.executeCmd(cmd, timeout=MAX_WAITTIME, shell=False, output=True) + # wait for server (socket and ready): + self._wait_for_srv(tmp, True, startparams=cmd) + self.assertLogged("Server ready") self.pruneLog() try: # echo from client (inside): @@ -312,7 +334,7 @@ class Fail2banClientTest(LogCaptureTestCase): # start and wait to end (foreground): logSys.debug("-- start of test worker") phase['start'] = True - self.assertRaises(ExitException, _exec_client, + self.assertRaises(fail2bancmdline.ExitException, _exec_client, (CLIENT, "-f") + startparams + ("start",)) # end : phase['end'] = True @@ -334,9 +356,10 @@ class Fail2banClientTest(LogCaptureTestCase): # wait for start thread: Utils.wait_for(lambda: phase.get('start', None) is not None, MAX_WAITTIME) self.assertTrue(phase.get('start', None)) - # wait for server (socket): - Utils.wait_for(lambda: os.path.exists(tmp+"/f2b.sock"), MAX_WAITTIME) - self.assertLogged("Starting communication") + # wait for server (socket and ready): + self._wait_for_srv(tmp, True, startparams=startparams) + self.pruneLog() + # several commands to server: self.assertRaises(ExitException, _exec_client, (CLIENT,) + startparams + ("ping",)) self.assertRaises(FailExitException, _exec_client, @@ -382,15 +405,7 @@ class Fail2banClientTest(LogCaptureTestCase): cntr -= 1 -class Fail2banServerTest(LogCaptureTestCase): - - def setUp(self): - """Call before every test case.""" - LogCaptureTestCase.setUp(self) - - def tearDown(self): - """Call after every test case.""" - LogCaptureTestCase.tearDown(self) +class Fail2banServerTest(Fail2banClientServerBase): def testServerUsage(self): self.assertRaises(ExitException, _exec_server, @@ -401,15 +416,17 @@ class Fail2banServerTest(LogCaptureTestCase): @withtmpdir def testServerStartBackground(self, tmp): try: - # don't add "--async" by start, because if will fork current process by daemonize - # (we can't fork the test cases process), - # because server started internal communication in new thread use INHERITED as logtarget here: - startparams = _start_params(tmp, logtarget="INHERITED") - # start: - self.assertRaises(ExitException, _exec_server, - (SERVER, "-b") + startparams) + # to prevent fork of test-cases process, start server in background via command: + startparams = _start_params(tmp) + # start (in new process, using the same python version): + cmd = (sys.executable, os.path.join(os.path.join(BIN), SERVER)) + logSys.debug('Start %s ...', cmd) + cmd = cmd + startparams + ("-b",) + Utils.executeCmd(cmd, timeout=MAX_WAITTIME, shell=False, output=True) + # wait for server (socket and ready): + self._wait_for_srv(tmp, True, startparams=cmd) self.assertLogged("Server ready") - self.assertLogged("Exit with code 0") + self.pruneLog() try: self.assertRaises(ExitException, _exec_server, (SERVER,) + startparams + ("echo", "TEST-ECHO",)) @@ -429,7 +446,7 @@ class Fail2banServerTest(LogCaptureTestCase): # start and wait to end (foreground): logSys.debug("-- start of test worker") phase['start'] = True - self.assertRaises(ExitException, _exec_server, + self.assertRaises(fail2bancmdline.ExitException, _exec_server, (SERVER, "-f") + startparams + ("start",)) # end : phase['end'] = True @@ -451,9 +468,10 @@ class Fail2banServerTest(LogCaptureTestCase): # wait for start thread: Utils.wait_for(lambda: phase.get('start', None) is not None, MAX_WAITTIME) self.assertTrue(phase.get('start', None)) - # wait for server (socket): - Utils.wait_for(lambda: os.path.exists(tmp+"/f2b.sock"), MAX_WAITTIME) - self.assertLogged("Starting communication") + # wait for server (socket and ready): + self._wait_for_srv(tmp, True, startparams=startparams) + self.pruneLog() + # several commands to server: self.assertRaises(ExitException, _exec_server, (SERVER,) + startparams + ("ping",)) self.assertRaises(FailExitException, _exec_server, diff --git a/fail2ban/tests/utils.py b/fail2ban/tests/utils.py index 1a54d37f..b171511d 100644 --- a/fail2ban/tests/utils.py +++ b/fail2ban/tests/utils.py @@ -54,6 +54,10 @@ if not CONFIG_DIR: else: CONFIG_DIR = '/etc/fail2ban' +# In not installed env (setup, test-cases) use fail2ban modules from main directory: +if 1 or os.environ.get('PYTHONPATH', None) is None: + os.putenv('PYTHONPATH', os.path.dirname(os.path.dirname(os.path.dirname( + os.path.abspath(__file__))))) class F2B(optparse.Values): def __init__(self, opts={}): @@ -315,7 +319,7 @@ class LogCaptureTestCase(unittest.TestCase): if self._old_level <= logging.DEBUG: # so if DEBUG etc -- show them (and log it in travis)! print("") logSys.handlers += self._old_handlers - logSys.debug('--'*40) + logSys.debug('='*10 + ' %s ' + '='*20, self.id()) logSys.setLevel(getattr(logging, 'DEBUG')) def tearDown(self):