some compatibility fixes (prevent forking of testcase-process, code review), wait 4 server ready, test cases fixed (py2/py3)

pull/1483/head
sebres 2016-02-12 21:51:32 +01:00
parent 2fcb6358ff
commit 0b4143730d
6 changed files with 219 additions and 135 deletions

View File

@ -34,14 +34,18 @@ from threading import Thread
from ..version import version from ..version import version
from .csocket import CSocket from .csocket import CSocket
from .beautifier import Beautifier 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 MAX_WAITTIME = 30
PROMPT = "fail2ban> "
def _thread_name(): def _thread_name():
return threading.current_thread().__class__.__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): class Fail2banClient(Fail2banCmdLine, Thread):
PROMPT = "fail2ban> "
def __init__(self): def __init__(self):
Fail2banCmdLine.__init__(self) Fail2banCmdLine.__init__(self)
Thread.__init__(self) Thread.__init__(self)
@ -91,11 +93,11 @@ class Fail2banClient(Fail2banCmdLine, Thread):
client = CSocket(self._conf["socket"]) client = CSocket(self._conf["socket"])
ret = client.send(c) ret = client.send(c)
if ret[0] == 0: if ret[0] == 0:
logSys.debug("OK : " + `ret[1]`) logSys.debug("OK : %r", ret[1])
if showRet or c[0] == 'echo': if showRet or c[0] == 'echo':
output(beautifier.beautify(ret[1])) output(beautifier.beautify(ret[1]))
else: else:
logSys.error("NOK: " + `ret[1].args`) logSys.error("NOK: %r", ret[1].args)
if showRet: if showRet:
output(beautifier.beautifyError(ret[1])) output(beautifier.beautifyError(ret[1]))
streamRet = False streamRet = False
@ -202,7 +204,10 @@ class Fail2banClient(Fail2banCmdLine, Thread):
except Exception as e: except Exception as e:
output("") output("")
logSys.error("Exception while starting server " + ("background" if background else "foreground")) 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 False
return True return True
@ -249,7 +254,9 @@ class Fail2banClient(Fail2banCmdLine, Thread):
if self._conf.get("interactive", False): if self._conf.get("interactive", False):
output(' ## stop ... ') output(' ## stop ... ')
self.__processCommand(['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: # in interactive mode reset config, to make full-reload if there something changed:
if self._conf.get("interactive", False): if self._conf.get("interactive", False):
output(' ## load configuration ... ') output(' ## load configuration ... ')
@ -286,10 +293,14 @@ class Fail2banClient(Fail2banCmdLine, Thread):
def __processStartStreamAfterWait(self, *args): def __processStartStreamAfterWait(self, *args):
try: try:
# Wait for the server to start # 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 # Configure the server
self.__processCmd(*args) 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 " logSys.error("Could not start server. Maybe an old "
"socket file is still present. Try to " "socket file is still present. Try to "
"remove " + self._conf["socket"] + ". If " "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) # Wait for the server to start (the server has 30 seconds to answer ping)
starttime = time.time() starttime = time.time()
logSys.debug("__waitOnServer: %r", (alive, maxtime)) logSys.debug("__waitOnServer: %r", (alive, maxtime))
test = lambda: os.path.exists(self._conf["socket"]) and self.__ping()
with VisualWait(self._conf["verbose"]) as vis: with VisualWait(self._conf["verbose"]) as vis:
while self._alive and ( sltime = 0.0125 / 2
not self.__ping() == alive or ( while self._alive:
not alive and os.path.exists(self._conf["socket"]) runf = test()
)): if runf == alive:
return True
now = time.time() now = time.time()
# Wonderful visual :) # Wonderful visual :)
if now > starttime + 1: if now > starttime + 1:
@ -318,7 +331,9 @@ class Fail2banClient(Fail2banCmdLine, Thread):
# f end time reached: # f end time reached:
if now - starttime >= maxtime: if now - starttime >= maxtime:
raise ServerExecutionException("Failed to start server") 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): def start(self, argv):
# Install signal handlers # Install signal handlers
@ -332,27 +347,31 @@ class Fail2banClient(Fail2banCmdLine, Thread):
if self._argv is None: if self._argv is None:
ret = self.initCmdLine(argv) ret = self.initCmdLine(argv)
if ret is not None: if ret is not None:
return ret if ret:
return True
raise ServerExecutionException("Init of command line failed")
# Commands # Commands
args = self._args args = self._args
# Interactive mode # Interactive mode
if self._conf.get("interactive", False): if self._conf.get("interactive", False):
try: # no readline in test:
import readline if PRODUCTION: # pragma: no cover
except ImportError: try:
logSys.error("Readline not available") import readline
return False except ImportError:
raise ServerExecutionException("Readline not available")
try: try:
ret = True ret = True
if len(args) > 0: if len(args) > 0:
ret = self.__processCommand(args) ret = self.__processCommand(args)
if ret: if ret:
readline.parse_and_bind("tab: complete") if PRODUCTION: # pragma: no cover
readline.parse_and_bind("tab: complete")
self.dispInteractive() self.dispInteractive()
while True: while True:
cmd = raw_input(self.PROMPT) cmd = input_command()
if cmd == "exit" or cmd == "quit": if cmd == "exit" or cmd == "quit":
# Exit # Exit
return True return True
@ -362,26 +381,31 @@ class Fail2banClient(Fail2banCmdLine, Thread):
try: try:
self.__processCommand(shlex.split(cmd)) self.__processCommand(shlex.split(cmd))
except Exception, e: except Exception, e:
logSys.error(e) if self._conf["verbose"] > 1:
logSys.exception(e)
else:
logSys.error(e)
except (EOFError, KeyboardInterrupt): except (EOFError, KeyboardInterrupt):
output("") output("")
return True raise
# Single command mode # Single command mode
else: else:
if len(args) < 1: if len(args) < 1:
self.dispUsage() self.dispUsage()
return False return False
return self.__processCommand(args) return self.__processCommand(args)
except Exception as e:
if self._conf["verbose"] > 1:
logSys.exception(e)
else:
logSys.error(e)
return False
finally: finally:
self._alive = False self._alive = False
for s, sh in _prev_signals.iteritems(): for s, sh in _prev_signals.iteritems():
signal.signal(s, sh) signal.signal(s, sh)
class ServerExecutionException(Exception):
pass
## ##
# Wonderful visual :) # Wonderful visual :)
# #

View File

@ -261,5 +261,9 @@ class Fail2banCmdLine():
exit = Fail2banCmdLine.exit exit = Fail2banCmdLine.exit
class ExitException: class ExitException(Exception):
pass
class ServerExecutionException(Exception):
pass pass

View File

@ -24,10 +24,8 @@ __license__ = "GPL"
import os import os
import sys import sys
from ..version import version from .fail2bancmdline import Fail2banCmdLine, ServerExecutionException, \
from ..server.server import Server logSys, PRODUCTION, exit
from ..server.utils import Utils
from .fail2bancmdline import Fail2banCmdLine, logSys, PRODUCTION, exit
MAX_WAITTIME = 30 MAX_WAITTIME = 30
@ -44,13 +42,14 @@ class Fail2banServer(Fail2banCmdLine):
# Fail2banCmdLine.__init__(self) # 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 @staticmethod
def startServerDirect(conf, daemon=True): def startServerDirect(conf, daemon=True):
logSys.debug("-- direct starting of server in %s, deamon: %s", os.getpid(), daemon) logSys.debug("-- direct starting of server in %s, deamon: %s", os.getpid(), daemon)
from ..server.server import Server
server = None server = None
try: try:
# Start it in foreground (current thread, not new process), # Start it in foreground (current thread, not new process),
@ -59,11 +58,14 @@ class Fail2banServer(Fail2banCmdLine):
server.start(conf["socket"], server.start(conf["socket"],
conf["pidfile"], conf["force"], conf["pidfile"], conf["force"],
conf=conf) conf=conf)
except Exception, e: except Exception as e:
logSys.exception(e) try:
if server: if server:
server.quit() server.quit()
exit(-1) except Exception as e2:
if conf["verbose"] > 1:
logSys.exception(e2)
raise
return server return server
@ -74,10 +76,6 @@ class Fail2banServer(Fail2banCmdLine):
@staticmethod @staticmethod
def startServerAsync(conf): 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) # Forks the current process, don't fork if async specified (ex: test cases)
pid = 0 pid = 0
frk = not conf["async"] and PRODUCTION frk = not conf["async"] and PRODUCTION
@ -103,25 +101,43 @@ class Fail2banServer(Fail2banCmdLine):
for o in ('loglevel', 'logtarget', 'syslogsocket'): for o in ('loglevel', 'logtarget', 'syslogsocket'):
args.append("--"+o) args.append("--"+o)
args.append(conf[o]) args.append(conf[o])
try: try:
# Use the current directory. # Directory of client (to try the first start from current or the same directory as client, and from relative bin):
exe = os.path.abspath(os.path.join(startdir, SERVER)) 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) logSys.debug("Starting %r with args %r", exe, args)
if frk: if frk:
os.execv(exe, args) return os.execv(exe, args)
else: 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: except OSError as e:
try: # Use the PATH env.
# Use the PATH env. logSys.warning("Initial start attempt failed (%s). Starting %r with the same args", e, SERVER)
logSys.warning("Initial start attempt failed (%s). Starting %r with the same args", e, SERVER) if frk:
if frk: return os.execvp(SERVER, args)
os.execvp(SERVER, args) else:
else: del args[0]
os.spawnvp(os.P_NOWAITO, SERVER, args) args[0] = SERVER
except OSError: return os.spawnvp(os.P_WAIT, SERVER, args)
exit(-1) 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): def _Fail2banClient(self):
from .fail2banclient import Fail2banClient from .fail2banclient import Fail2banClient
@ -139,18 +155,24 @@ class Fail2banServer(Fail2banCmdLine):
args = self._args args = self._args
cli = None cli = None
# If client mode - whole processing over client: # Just start:
if len(args) or self._conf.get("interactive", False): if len(args) == 1 and args[0] == 'start' and not self._conf.get("interactive", False):
cli = self._Fail2banClient() pass
return cli.start(argv) 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: # Start the server:
server = None server = None
try: try:
# async = True, if started from client, should fork, daemonize, etc... from ..server.utils import Utils
# background = True, if should start in new process, otherwise start in foreground # background = True, if should be new process running in background, otherwise start in foreground
async = self._conf.get("async", False) # process will be forked in daemonize, inside of Server module.
# async = True, if started from client, should...
background = self._conf["background"] background = self._conf["background"]
async = self._conf.get("async", False)
# If was started not from the client: # If was started not from the client:
if not async: if not async:
# Start new thread with client to read configuration and # 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: # 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) Utils.wait_for(lambda: phase.get('ready', None) is not None, MAX_WAITTIME)
if not phase.get('start', False): if not phase.get('start', False):
return False raise ServerExecutionException('Async configuration of server failed')
# Start server, daemonize it, etc. # Start server, daemonize it, etc.
if async or not background: pid = os.getpid()
server = Fail2banServer.startServerDirect(self._conf, background) server = Fail2banServer.startServerDirect(self._conf, background)
else: # If forked - just exit other processes
Fail2banServer.startServerAsync(self._conf) if pid != os.getpid():
os._exit(0)
if cli: if cli:
cli._server = server cli._server = server
@ -182,7 +205,8 @@ class Fail2banServer(Fail2banCmdLine):
logSys.debug('Starting server done') logSys.debug('Starting server done')
except Exception, e: except Exception, e:
logSys.exception(e) if self._conf["verbose"] > 1:
logSys.exception(e)
if server: if server:
server.quit() server.quit()
exit(-1) exit(-1)

View File

@ -93,9 +93,15 @@ class Server:
if self.__daemon: # pragma: no cover if self.__daemon: # pragma: no cover
logSys.info("Starting in daemon mode") logSys.info("Starting in daemon mode")
ret = self.__createDaemon() ret = self.__createDaemon()
if not ret: # If forked parent - return here (parent process will configure server later):
logSys.error("Could not create daemon") if ret is None:
raise ServerInitializationError("Could not create daemon") 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): # Set all logging parameters (or use default if not specified):
self.setSyslogSocket(conf.get("syslogsocket", self.setSyslogSocket(conf.get("syslogsocket",
@ -159,8 +165,12 @@ class Server:
logging.shutdown() logging.shutdown()
# Restore default signal handlers: # Restore default signal handlers:
for s, sh in self.__prev_signals.iteritems(): if _thread_name() == '_MainThread':
signal.signal(s, sh) 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): def addJail(self, name, backend):
self.__jails.add(name, backend, self.__db) self.__jails.add(name, backend, self.__db)
@ -569,10 +579,9 @@ class Server:
# We need to set this in the parent process, so it gets inherited by the # 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 # child process, and this makes sure that it is effect even if the parent
# terminates quickly. # terminates quickly.
if _thread_name() == '_MainThread': for s in (signal.SIGHUP,):
for s in (signal.SIGHUP,): self.__prev_signals[s] = signal.getsignal(s)
self.__prev_signals[s] = signal.getsignal(s) signal.signal(s, signal.SIG_IGN)
signal.signal(s, signal.SIG_IGN)
try: try:
# Fork a child process so the parent can exit. This will return control # Fork a child process so the parent can exit. This will return control
@ -583,7 +592,7 @@ class Server:
# PGID. # PGID.
pid = os.fork() pid = os.fork()
except OSError, e: 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. if pid == 0: # The first child.
@ -604,7 +613,7 @@ class Server:
# preventing the daemon from ever acquiring a controlling terminal. # preventing the daemon from ever acquiring a controlling terminal.
pid = os.fork() # Fork a second child. pid = os.fork() # Fork a second child.
except OSError, e: 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. if (pid == 0): # The second child.
# Ensure that the daemon doesn't keep any directory in use. Failure # Ensure that the daemon doesn't keep any directory in use. Failure
@ -613,7 +622,8 @@ class Server:
else: else:
os._exit(0) # Exit parent (the first child) of the second child. os._exit(0) # Exit parent (the first child) of the second child.
else: 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, # 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 # for the maximum number of open files to close. If it doesn't exist, use
@ -641,7 +651,7 @@ class Server:
os.open("/dev/null", os.O_RDONLY) # standard input (0) 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 output (1)
os.open("/dev/null", os.O_RDWR) # standard error (2) os.open("/dev/null", os.O_RDWR) # standard error (2)
return True return (True,)
class ServerInitializationError(Exception): class ServerInitializationError(Exception):

View File

@ -26,6 +26,7 @@ __license__ = "GPL"
import fileinput import fileinput
import os import os
import re import re
import sys
import time import time
import unittest import unittest
@ -50,10 +51,9 @@ else:
CLIENT = "fail2ban-client" CLIENT = "fail2ban-client"
SERVER = "fail2ban-server" 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 = 30 if not unittest.F2B.fast else 5
MAX_WAITTIME = unittest.F2B.maxWaitTime(MAX_WAITTIME)
## ##
# Several wrappers and settings for proper testing: # Several wrappers and settings for proper testing:
@ -67,8 +67,6 @@ fail2bancmdline.logSys = \
fail2banclient.logSys = \ fail2banclient.logSys = \
fail2banserver.logSys = logSys fail2banserver.logSys = logSys
LOG_LEVEL = logSys.level
server.DEF_LOGTARGET = "/dev/null" server.DEF_LOGTARGET = "/dev/null"
def _test_output(*args): def _test_output(*args):
@ -89,13 +87,13 @@ fail2banclient.exit = \
fail2banserver.exit = _test_exit fail2banserver.exit = _test_exit
INTERACT = [] INTERACT = []
def _test_raw_input(*args): def _test_input_command(*args):
if len(INTERACT): if len(INTERACT):
#print('--- interact command: ', INTERACT[0]) #logSys.debug('--- interact command: %r', INTERACT[0])
return INTERACT.pop(0) return INTERACT.pop(0)
else: else:
return "exit" return "exit"
fail2banclient.raw_input = _test_raw_input fail2banclient.input_command = _test_input_command
# prevents change logging params, log capturing, etc: # prevents change logging params, log capturing, etc:
fail2bancmdline.PRODUCTION = \ fail2bancmdline.PRODUCTION = \
@ -142,7 +140,7 @@ def _start_params(tmp, use_stock=False, logtarget="/dev/null"):
else: else:
# just empty config directory without anything (only fail2ban.conf/jail.conf): # just empty config directory without anything (only fail2ban.conf/jail.conf):
os.mkdir(cfg) os.mkdir(cfg)
f = open(cfg+"/fail2ban.conf", "wb") f = open(cfg+"/fail2ban.conf", "w")
f.write('\n'.join(( f.write('\n'.join((
"[Definition]", "[Definition]",
"loglevel = INFO", "loglevel = INFO",
@ -156,20 +154,20 @@ def _start_params(tmp, use_stock=False, logtarget="/dev/null"):
"", "",
))) )))
f.close() f.close()
f = open(cfg+"/jail.conf", "wb") f = open(cfg+"/jail.conf", "w")
f.write('\n'.join(( f.write('\n'.join((
"[INCLUDES]", "", "[INCLUDES]", "",
"[DEFAULT]", "", "[DEFAULT]", "",
"", "",
))) )))
f.close() f.close()
if LOG_LEVEL < logging.DEBUG: # if HEAVYDEBUG if logSys.level < logging.DEBUG: # if HEAVYDEBUG
_out_file(cfg+"/fail2ban.conf") _out_file(cfg+"/fail2ban.conf")
_out_file(cfg+"/jail.conf") _out_file(cfg+"/jail.conf")
# parameters: # parameters (sock/pid and config, increase verbosity, set log, etc.):
return ("-c", cfg, return ("-c", cfg, "-s", tmp+"/f2b.sock", "-p", tmp+"/f2b.pid",
"--logtarget", logtarget, "--loglevel", "DEBUG", "--syslogsocket", "auto", "-vv", "--logtarget", logtarget, "--loglevel", "DEBUG", "--syslogsocket", "auto",
"-s", tmp+"/f2b.sock", "-p", tmp+"/f2b.pid") )
def _kill_srv(pidfile): # pragma: no cover def _kill_srv(pidfile): # pragma: no cover
def _pid_exists(pid): def _pid_exists(pid):
@ -206,14 +204,14 @@ def _kill_srv(pidfile): # pragma: no cover
os.kill(pid, signal.SIGKILL) os.kill(pid, signal.SIGKILL)
return not _pid_exists(pid) return not _pid_exists(pid)
except Exception as e: except Exception as e:
sysLog.debug(e) logSys.debug(e)
finally: finally:
if f is not None: if f is not None:
f.close() f.close()
return True return True
class Fail2banClientTest(LogCaptureTestCase): class Fail2banClientServerBase(LogCaptureTestCase):
def setUp(self): def setUp(self):
"""Call before every test case.""" """Call before every test case."""
@ -223,6 +221,21 @@ class Fail2banClientTest(LogCaptureTestCase):
"""Call after every test case.""" """Call after every test case."""
LogCaptureTestCase.tearDown(self) 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): def testClientUsage(self):
self.assertRaises(ExitException, _exec_client, self.assertRaises(ExitException, _exec_client,
(CLIENT, "-h",)) (CLIENT, "-h",))
@ -232,12 +245,13 @@ class Fail2banClientTest(LogCaptureTestCase):
@withtmpdir @withtmpdir
def testClientStartBackgroundInside(self, tmp): def testClientStartBackgroundInside(self, tmp):
try: try:
# always add "--async" by start inside, should don't fork by async (not replace client with server, just start in new process) # use once the stock configuration (to test starting also)
# (we can't fork the test cases process):
startparams = _start_params(tmp, True) startparams = _start_params(tmp, True)
# start: # start:
self.assertRaises(ExitException, _exec_client, 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("Server ready")
self.assertLogged("Exit with code 0") self.assertLogged("Exit with code 0")
try: try:
@ -245,6 +259,11 @@ class Fail2banClientTest(LogCaptureTestCase):
(CLIENT,) + startparams + ("echo", "TEST-ECHO",)) (CLIENT,) + startparams + ("echo", "TEST-ECHO",))
self.assertRaises(FailExitException, _exec_client, self.assertRaises(FailExitException, _exec_client,
(CLIENT,) + startparams + ("~~unknown~cmd~failed~~",)) (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: finally:
self.pruneLog() self.pruneLog()
# stop: # stop:
@ -260,11 +279,14 @@ class Fail2banClientTest(LogCaptureTestCase):
try: try:
global INTERACT global INTERACT
startparams = _start_params(tmp) startparams = _start_params(tmp)
# start (without async in new process): # start (in new process, using the same python version):
cmd = os.path.join(os.path.join(BIN), CLIENT) cmd = (sys.executable, os.path.join(os.path.join(BIN), CLIENT))
logSys.debug('Start %s ...', cmd) logSys.debug('Start %s ...', cmd)
Utils.executeCmd((cmd,) + startparams + ("start",), cmd = cmd + startparams + ("start",)
timeout=MAX_WAITTIME, shell=False, output=False) 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() self.pruneLog()
try: try:
# echo from client (inside): # echo from client (inside):
@ -312,7 +334,7 @@ class Fail2banClientTest(LogCaptureTestCase):
# start and wait to end (foreground): # start and wait to end (foreground):
logSys.debug("-- start of test worker") logSys.debug("-- start of test worker")
phase['start'] = True phase['start'] = True
self.assertRaises(ExitException, _exec_client, self.assertRaises(fail2bancmdline.ExitException, _exec_client,
(CLIENT, "-f") + startparams + ("start",)) (CLIENT, "-f") + startparams + ("start",))
# end : # end :
phase['end'] = True phase['end'] = True
@ -334,9 +356,10 @@ class Fail2banClientTest(LogCaptureTestCase):
# wait for start thread: # wait for start thread:
Utils.wait_for(lambda: phase.get('start', None) is not None, MAX_WAITTIME) Utils.wait_for(lambda: phase.get('start', None) is not None, MAX_WAITTIME)
self.assertTrue(phase.get('start', None)) self.assertTrue(phase.get('start', None))
# wait for server (socket): # wait for server (socket and ready):
Utils.wait_for(lambda: os.path.exists(tmp+"/f2b.sock"), MAX_WAITTIME) self._wait_for_srv(tmp, True, startparams=startparams)
self.assertLogged("Starting communication") self.pruneLog()
# several commands to server:
self.assertRaises(ExitException, _exec_client, self.assertRaises(ExitException, _exec_client,
(CLIENT,) + startparams + ("ping",)) (CLIENT,) + startparams + ("ping",))
self.assertRaises(FailExitException, _exec_client, self.assertRaises(FailExitException, _exec_client,
@ -382,15 +405,7 @@ class Fail2banClientTest(LogCaptureTestCase):
cntr -= 1 cntr -= 1
class Fail2banServerTest(LogCaptureTestCase): class Fail2banServerTest(Fail2banClientServerBase):
def setUp(self):
"""Call before every test case."""
LogCaptureTestCase.setUp(self)
def tearDown(self):
"""Call after every test case."""
LogCaptureTestCase.tearDown(self)
def testServerUsage(self): def testServerUsage(self):
self.assertRaises(ExitException, _exec_server, self.assertRaises(ExitException, _exec_server,
@ -401,15 +416,17 @@ class Fail2banServerTest(LogCaptureTestCase):
@withtmpdir @withtmpdir
def testServerStartBackground(self, tmp): def testServerStartBackground(self, tmp):
try: try:
# don't add "--async" by start, because if will fork current process by daemonize # to prevent fork of test-cases process, start server in background via command:
# (we can't fork the test cases process), startparams = _start_params(tmp)
# because server started internal communication in new thread use INHERITED as logtarget here: # start (in new process, using the same python version):
startparams = _start_params(tmp, logtarget="INHERITED") cmd = (sys.executable, os.path.join(os.path.join(BIN), SERVER))
# start: logSys.debug('Start %s ...', cmd)
self.assertRaises(ExitException, _exec_server, cmd = cmd + startparams + ("-b",)
(SERVER, "-b") + startparams) 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("Server ready")
self.assertLogged("Exit with code 0") self.pruneLog()
try: try:
self.assertRaises(ExitException, _exec_server, self.assertRaises(ExitException, _exec_server,
(SERVER,) + startparams + ("echo", "TEST-ECHO",)) (SERVER,) + startparams + ("echo", "TEST-ECHO",))
@ -429,7 +446,7 @@ class Fail2banServerTest(LogCaptureTestCase):
# start and wait to end (foreground): # start and wait to end (foreground):
logSys.debug("-- start of test worker") logSys.debug("-- start of test worker")
phase['start'] = True phase['start'] = True
self.assertRaises(ExitException, _exec_server, self.assertRaises(fail2bancmdline.ExitException, _exec_server,
(SERVER, "-f") + startparams + ("start",)) (SERVER, "-f") + startparams + ("start",))
# end : # end :
phase['end'] = True phase['end'] = True
@ -451,9 +468,10 @@ class Fail2banServerTest(LogCaptureTestCase):
# wait for start thread: # wait for start thread:
Utils.wait_for(lambda: phase.get('start', None) is not None, MAX_WAITTIME) Utils.wait_for(lambda: phase.get('start', None) is not None, MAX_WAITTIME)
self.assertTrue(phase.get('start', None)) self.assertTrue(phase.get('start', None))
# wait for server (socket): # wait for server (socket and ready):
Utils.wait_for(lambda: os.path.exists(tmp+"/f2b.sock"), MAX_WAITTIME) self._wait_for_srv(tmp, True, startparams=startparams)
self.assertLogged("Starting communication") self.pruneLog()
# several commands to server:
self.assertRaises(ExitException, _exec_server, self.assertRaises(ExitException, _exec_server,
(SERVER,) + startparams + ("ping",)) (SERVER,) + startparams + ("ping",))
self.assertRaises(FailExitException, _exec_server, self.assertRaises(FailExitException, _exec_server,

View File

@ -54,6 +54,10 @@ if not CONFIG_DIR:
else: else:
CONFIG_DIR = '/etc/fail2ban' 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): class F2B(optparse.Values):
def __init__(self, opts={}): def __init__(self, opts={}):
@ -343,7 +347,7 @@ class LogCaptureTestCase(unittest.TestCase):
if self._old_level <= logging.DEBUG: # so if DEBUG etc -- show them (and log it in travis)! if self._old_level <= logging.DEBUG: # so if DEBUG etc -- show them (and log it in travis)!
print("") print("")
logSys.handlers += self._old_handlers logSys.handlers += self._old_handlers
logSys.debug('--'*40) logSys.debug('='*10 + ' %s ' + '='*20, self.id())
logSys.setLevel(getattr(logging, 'DEBUG')) logSys.setLevel(getattr(logging, 'DEBUG'))
def tearDown(self): def tearDown(self):