ConfigWrapper class introduced: sharing of the same ConfigReader object between JailsReader and JailReader (don't read jail config each jail);

sharing of the same DefinitionInitConfigReader (ActionReader, FilterReader) between all jails using that;
cache of read a config files was optimized;
test case extended for all types of config readers;
pull/716/head
sebres 2014-10-09 14:51:08 +02:00
parent 2a54e61238
commit 4244c87802
9 changed files with 153 additions and 62 deletions

View File

@ -26,7 +26,7 @@ __license__ = "GPL"
import os import os
from .configreader import ConfigReader, DefinitionInitConfigReader from .configreader import DefinitionInitConfigReader
from ..helpers import getLogger from ..helpers import getLogger
# Gets the instance of the logger. # Gets the instance of the logger.
@ -47,15 +47,19 @@ class ActionReader(DefinitionInitConfigReader):
DefinitionInitConfigReader.__init__( DefinitionInitConfigReader.__init__(
self, file_, jailName, initOpts, **kwargs) self, file_, jailName, initOpts, **kwargs)
def setFile(self, fileName):
self.__file = fileName
DefinitionInitConfigReader.setFile(self, os.path.join("action.d", fileName))
def getFile(self):
return self.__file
def setName(self, name): def setName(self, name):
self._name = name self._name = name
def getName(self): def getName(self):
return self._name return self._name
def read(self):
return ConfigReader.read(self, os.path.join("action.d", self._file))
def convert(self): def convert(self):
head = ["set", self._jailName] head = ["set", self._jailName]
stream = list() stream = list()

View File

@ -253,17 +253,14 @@ after = 1.conf
if i: if i:
ret += i ret += i
# merge defaults and all sections to self: # merge defaults and all sections to self:
for (n, v) in cfg.get_defaults().items(): alld.update(cfg.get_defaults())
alld[n] = v for n, s in cfg.get_sections().iteritems():
for (n, s) in cfg.get_sections().items():
if isinstance(s, dict): if isinstance(s, dict):
s2 = alls.get(n) s2 = alls.get(n)
if s2 is not None: if isinstance(s2, dict):
for (n, v) in s.items(): s2.update(s)
s2[n] = v
else: else:
s2 = s.copy() alls[n] = s.copy()
alls[n] = s2
else: else:
alls[n] = s alls[n] = s

View File

@ -32,6 +32,65 @@ from ..helpers import getLogger
# Gets the instance of the logger. # Gets the instance of the logger.
logSys = getLogger(__name__) logSys = getLogger(__name__)
logLevel = 6
class ConfigWrapper():
def __init__(self, use_config=None, share_config=None, **kwargs):
# use given shared config if possible (see read):
self._cfg_share = None
self._cfg = None
if use_config is not None:
self._cfg = use_config
else:
# share config if possible:
if share_config is not None:
self._cfg_share = share_config
self._cfg_share_kwargs = kwargs
else:
self._cfg = ConfigReader(**kwargs)
def setBaseDir(self, basedir):
self._cfg.setBaseDir(basedir)
def getBaseDir(self):
return self._cfg.getBaseDir()
def read(self, name, once=True):
# shared ?
if not self._cfg and self._cfg_share is not None:
self._cfg = self._cfg_share.get(name)
if not self._cfg:
self._cfg = ConfigReader(**self._cfg_share_kwargs)
self._cfg_share[name] = self._cfg
# performance feature - read once if using shared config reader:
rc = self._cfg.read_cfg_files
if once and rc.get(name) is not None:
return rc.get(name)
# read:
ret = self._cfg.read(name)
# save already read:
if once:
rc[name] = ret
return ret
def sections(self):
return self._cfg.sections()
def has_section(self, sec):
return self._cfg.has_section(sec)
def options(self, *args):
return self._cfg.options(*args)
def get(self, sec, opt):
return self._cfg.get(sec, opt)
def getOptions(self, *args, **kwargs):
return self._cfg.getOptions(*args, **kwargs)
class ConfigReader(SafeConfigParserWithIncludes): class ConfigReader(SafeConfigParserWithIncludes):
@ -39,8 +98,8 @@ class ConfigReader(SafeConfigParserWithIncludes):
def __init__(self, basedir=None): def __init__(self, basedir=None):
SafeConfigParserWithIncludes.__init__(self) SafeConfigParserWithIncludes.__init__(self)
self.read_cfg_files = dict()
self.setBaseDir(basedir) self.setBaseDir(basedir)
self.__opts = None
def setBaseDir(self, basedir): def setBaseDir(self, basedir):
if basedir is None: if basedir is None:
@ -122,17 +181,15 @@ class ConfigReader(SafeConfigParserWithIncludes):
logSys.warning("'%s' not defined in '%s'. Using default one: %r" logSys.warning("'%s' not defined in '%s'. Using default one: %r"
% (option[1], sec, option[2])) % (option[1], sec, option[2]))
values[option[1]] = option[2] values[option[1]] = option[2]
else: elif logSys.getEffectiveLevel() <= logLevel:
logSys.debug( logSys.log(logLevel, "Non essential option '%s' not defined in '%s'.", option[1], sec)
"Non essential option '%s' not defined in '%s'.",
option[1], sec)
except ValueError: except ValueError:
logSys.warning("Wrong value for '" + option[1] + "' in '" + sec + logSys.warning("Wrong value for '" + option[1] + "' in '" + sec +
"'. Using default one: '" + `option[2]` + "'") "'. Using default one: '" + `option[2]` + "'")
values[option[1]] = option[2] values[option[1]] = option[2]
return values return values
class DefinitionInitConfigReader(ConfigReader): class DefinitionInitConfigReader(ConfigWrapper):
"""Config reader for files with options grouped in [Definition] and """Config reader for files with options grouped in [Definition] and
[Init] sections. [Init] sections.
@ -144,7 +201,7 @@ class DefinitionInitConfigReader(ConfigReader):
_configOpts = [] _configOpts = []
def __init__(self, file_, jailName, initOpts, **kwargs): def __init__(self, file_, jailName, initOpts, **kwargs):
ConfigReader.__init__(self, **kwargs) ConfigWrapper.__init__(self, **kwargs)
self.setFile(file_) self.setFile(file_)
self.setJailName(jailName) self.setJailName(jailName)
self._initOpts = initOpts self._initOpts = initOpts
@ -163,14 +220,14 @@ class DefinitionInitConfigReader(ConfigReader):
return self._jailName return self._jailName
def read(self): def read(self):
return ConfigReader.read(self, self._file) return ConfigWrapper.read(self, self._file)
# needed for fail2ban-regex that doesn't need fancy directories # needed for fail2ban-regex that doesn't need fancy directories
def readexplicit(self): def readexplicit(self):
return SafeConfigParserWithIncludes.read(self, self._file) return SafeConfigParserWithIncludes.read(self, self._file)
def getOptions(self, pOpts): def getOptions(self, pOpts):
self._opts = ConfigReader.getOptions( self._opts = ConfigWrapper.getOptions(
self, "Definition", self._configOpts, pOpts) self, "Definition", self._configOpts, pOpts)
if self.has_section("Init"): if self.has_section("Init"):

View File

@ -33,11 +33,11 @@ logSys = getLogger(__name__)
class Configurator: class Configurator:
def __init__(self): def __init__(self, force_enable=False):
self.__settings = dict() self.__settings = dict()
self.__streams = dict() self.__streams = dict()
self.__fail2ban = Fail2banReader() self.__fail2ban = Fail2banReader()
self.__jails = JailsReader() self.__jails = JailsReader(force_enable=force_enable)
def setBaseDir(self, folderName): def setBaseDir(self, folderName):
self.__fail2ban.setBaseDir(folderName) self.__fail2ban.setBaseDir(folderName)

View File

@ -24,31 +24,32 @@ __author__ = "Cyril Jaquier"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier" __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL" __license__ = "GPL"
from .configreader import ConfigReader from .configreader import ConfigWrapper
from ..helpers import getLogger from ..helpers import getLogger
# Gets the instance of the logger. # Gets the instance of the logger.
logSys = getLogger(__name__) logSys = getLogger(__name__)
class Fail2banReader(ConfigReader): class Fail2banReader(ConfigWrapper):
def __init__(self, **kwargs): def __init__(self, **kwargs):
ConfigReader.__init__(self, **kwargs) self.__opts = None
ConfigWrapper.__init__(self, **kwargs)
def read(self): def read(self):
ConfigReader.read(self, "fail2ban") ConfigWrapper.read(self, "fail2ban")
def getEarlyOptions(self): def getEarlyOptions(self):
opts = [["string", "socket", "/var/run/fail2ban/fail2ban.sock"], opts = [["string", "socket", "/var/run/fail2ban/fail2ban.sock"],
["string", "pidfile", "/var/run/fail2ban/fail2ban.pid"]] ["string", "pidfile", "/var/run/fail2ban/fail2ban.pid"]]
return ConfigReader.getOptions(self, "Definition", opts) return ConfigWrapper.getOptions(self, "Definition", opts)
def getOptions(self): def getOptions(self):
opts = [["string", "loglevel", "INFO" ], opts = [["string", "loglevel", "INFO" ],
["string", "logtarget", "STDERR"], ["string", "logtarget", "STDERR"],
["string", "dbfile", "/var/lib/fail2ban/fail2ban.sqlite3"], ["string", "dbfile", "/var/lib/fail2ban/fail2ban.sqlite3"],
["int", "dbpurgeage", 86400]] ["int", "dbpurgeage", 86400]]
self.__opts = ConfigReader.getOptions(self, "Definition", opts) self.__opts = ConfigWrapper.getOptions(self, "Definition", opts)
def convert(self): def convert(self):
stream = list() stream = list()

View File

@ -26,7 +26,7 @@ __license__ = "GPL"
import os, shlex import os, shlex
from .configreader import ConfigReader, DefinitionInitConfigReader from .configreader import DefinitionInitConfigReader
from ..server.action import CommandAction from ..server.action import CommandAction
from ..helpers import getLogger from ..helpers import getLogger
@ -40,8 +40,12 @@ class FilterReader(DefinitionInitConfigReader):
["string", "failregex", ""], ["string", "failregex", ""],
] ]
def read(self): def setFile(self, fileName):
return ConfigReader.read(self, os.path.join("filter.d", self._file)) self.__file = fileName
DefinitionInitConfigReader.setFile(self, os.path.join("filter.d", fileName))
def getFile(self):
return self.__file
def convert(self): def convert(self):
stream = list() stream = list()

View File

@ -27,7 +27,7 @@ __license__ = "GPL"
import re, glob, os.path import re, glob, os.path
import json import json
from .configreader import ConfigReader from .configreader import ConfigReader, ConfigWrapper
from .filterreader import FilterReader from .filterreader import FilterReader
from .actionreader import ActionReader from .actionreader import ActionReader
from ..helpers import getLogger from ..helpers import getLogger
@ -35,17 +35,19 @@ from ..helpers import getLogger
# Gets the instance of the logger. # Gets the instance of the logger.
logSys = getLogger(__name__) logSys = getLogger(__name__)
class JailReader(ConfigReader): class JailReader(ConfigWrapper):
optionCRE = re.compile("^((?:\w|-|_|\.)+)(?:\[(.*)\])?$") optionCRE = re.compile("^((?:\w|-|_|\.)+)(?:\[(.*)\])?$")
optionExtractRE = re.compile( optionExtractRE = re.compile(
r'([\w\-_\.]+)=(?:"([^"]*)"|\'([^\']*)\'|([^,]*))(?:,|$)') r'([\w\-_\.]+)=(?:"([^"]*)"|\'([^\']*)\'|([^,]*))(?:,|$)')
def __init__(self, name, force_enable=False, **kwargs): def __init__(self, name, force_enable=False, cfg_share=None, **kwargs):
ConfigReader.__init__(self, **kwargs) # use shared config if possible:
ConfigWrapper.__init__(self, **kwargs)
self.__name = name self.__name = name
self.__filter = None self.__filter = None
self.__force_enable = force_enable self.__force_enable = force_enable
self.__cfg_share = cfg_share
self.__actions = list() self.__actions = list()
self.__opts = None self.__opts = None
@ -60,7 +62,7 @@ class JailReader(ConfigReader):
return self.__name return self.__name
def read(self): def read(self):
out = ConfigReader.read(self, "jail") out = ConfigWrapper.read(self, "jail")
# Before returning -- verify that requested section # Before returning -- verify that requested section
# exists at all # exists at all
if not (self.__name in self.sections()): if not (self.__name in self.sections()):
@ -101,7 +103,7 @@ class JailReader(ConfigReader):
["string", "ignoreip", None], ["string", "ignoreip", None],
["string", "filter", ""], ["string", "filter", ""],
["string", "action", ""]] ["string", "action", ""]]
self.__opts = ConfigReader.getOptions(self, self.__name, opts) self.__opts = ConfigWrapper.getOptions(self, self.__name, opts)
if not self.__opts: if not self.__opts:
return False return False
@ -111,7 +113,7 @@ class JailReader(ConfigReader):
filterName, filterOpt = JailReader.extractOptions( filterName, filterOpt = JailReader.extractOptions(
self.__opts["filter"]) self.__opts["filter"])
self.__filter = FilterReader( self.__filter = FilterReader(
filterName, self.__name, filterOpt, basedir=self.getBaseDir()) filterName, self.__name, filterOpt, share_config=self.__cfg_share, basedir=self.getBaseDir())
ret = self.__filter.read() ret = self.__filter.read()
if ret: if ret:
self.__filter.getOptions(self.__opts) self.__filter.getOptions(self.__opts)
@ -141,7 +143,7 @@ class JailReader(ConfigReader):
else: else:
action = ActionReader( action = ActionReader(
actName, self.__name, actOpt, actName, self.__name, actOpt,
basedir=self.getBaseDir()) share_config=self.__cfg_share, basedir=self.getBaseDir())
ret = action.read() ret = action.read()
if ret: if ret:
action.getOptions(self.__opts) action.getOptions(self.__opts)
@ -213,7 +215,7 @@ class JailReader(ConfigReader):
if self.__filter: if self.__filter:
stream.extend(self.__filter.convert()) stream.extend(self.__filter.convert())
for action in self.__actions: for action in self.__actions:
if isinstance(action, ConfigReader): if isinstance(action, (ConfigReader, ConfigWrapper)):
stream.extend(action.convert()) stream.extend(action.convert())
else: else:
stream.append(action) stream.append(action)

View File

@ -24,14 +24,14 @@ __author__ = "Cyril Jaquier"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier" __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL" __license__ = "GPL"
from .configreader import ConfigReader from .configreader import ConfigReader, ConfigWrapper
from .jailreader import JailReader from .jailreader import JailReader
from ..helpers import getLogger from ..helpers import getLogger
# Gets the instance of the logger. # Gets the instance of the logger.
logSys = getLogger(__name__) logSys = getLogger(__name__)
class JailsReader(ConfigReader): class JailsReader(ConfigWrapper):
def __init__(self, force_enable=False, **kwargs): def __init__(self, force_enable=False, **kwargs):
""" """
@ -41,7 +41,9 @@ class JailsReader(ConfigReader):
Passed to JailReader to force enable the jails. Passed to JailReader to force enable the jails.
It is for internal use It is for internal use
""" """
ConfigReader.__init__(self, **kwargs) # use shared config if possible:
ConfigWrapper.__init__(self, **kwargs)
self.__cfg_share = dict()
self.__jails = list() self.__jails = list()
self.__force_enable = force_enable self.__force_enable = force_enable
@ -50,13 +52,13 @@ class JailsReader(ConfigReader):
return self.__jails return self.__jails
def read(self): def read(self):
return ConfigReader.read(self, "jail") return ConfigWrapper.read(self, "jail")
def getOptions(self, section=None): def getOptions(self, section=None):
"""Reads configuration for jail(s) and adds enabled jails to __jails """Reads configuration for jail(s) and adds enabled jails to __jails
""" """
opts = [] opts = []
self.__opts = ConfigReader.getOptions(self, "Definition", opts) self.__opts = ConfigWrapper.getOptions(self, "Definition", opts)
if section is None: if section is None:
sections = self.sections() sections = self.sections()
@ -68,9 +70,10 @@ class JailsReader(ConfigReader):
for sec in sections: for sec in sections:
if sec == 'INCLUDES': if sec == 'INCLUDES':
continue continue
jail = JailReader(sec, basedir=self.getBaseDir(), # use the cfg_share for filter/action caching and the same config for all
force_enable=self.__force_enable) # jails (use_config=...), therefore don't read it here:
jail.read() jail = JailReader(sec, force_enable=self.__force_enable,
cfg_share=self.__cfg_share, use_config=self._cfg)
ret = jail.getOptions() ret = jail.getOptions()
if ret: if ret:
if jail.isEnabled(): if jail.isEnabled():

View File

@ -347,6 +347,23 @@ class FilterReaderTest(unittest.TestCase):
class JailsReaderTestCache(LogCaptureTestCase): class JailsReaderTestCache(LogCaptureTestCase):
def _readWholeConf(self, basedir, force_enable=False):
# read whole configuration like a file2ban-client ...
configurator = Configurator(force_enable=force_enable)
configurator.setBaseDir(basedir)
configurator.readEarly()
configurator.getEarlyOptions()
configurator.readAll()
# from here we test a cache with all includes / before / after :
self.assertTrue(configurator.getOptions(None))
def _getLoggedReadCount(self, filematch):
cnt = 0
for s in self.getLog().rsplit('\n'):
if re.match(r"^Reading files?: .*/"+filematch, s):
cnt += 1
return cnt
def testTestJailConfCache(self): def testTestJailConfCache(self):
basedir = tempfile.mkdtemp("fail2ban_conf") basedir = tempfile.mkdtemp("fail2ban_conf")
try: try:
@ -356,21 +373,27 @@ class JailsReaderTestCache(LogCaptureTestCase):
shutil.copy(CONFIG_DIR + '/fail2ban.conf', basedir + '/fail2ban.local') shutil.copy(CONFIG_DIR + '/fail2ban.conf', basedir + '/fail2ban.local')
# read whole configuration like a file2ban-client ... # read whole configuration like a file2ban-client ...
configurator = Configurator() self._readWholeConf(basedir)
configurator.setBaseDir(basedir) # how many times jail.local was read:
configurator.readEarly() cnt = self._getLoggedReadCount('jail.local')
configurator.getEarlyOptions()
configurator.readAll()
# from here we test a cache :
self.assertTrue(configurator.getOptions(None))
cnt = 0
for s in self.getLog().rsplit('\n'):
if re.match(r"^Reading files?: .*jail.local", s):
cnt += 1
# if cnt > 1: # if cnt > 1:
# self.printLog() # self.printLog()
self.assertFalse(cnt > 1, "Too many times reading of config files, cnt = %s" % cnt) self.assertFalse(cnt > 1, "Too many times reading of jail files, cnt = %s" % cnt)
self.assertFalse(cnt <= 0) self.assertNotEqual(cnt, 0)
# read whole configuration like a file2ban-client, again ...
# but this time force enable all jails, to check filter and action cached also:
self._readWholeConf(basedir, force_enable=True)
cnt = self._getLoggedReadCount(r'jail\.local')
# still one (no more reads):
self.assertFalse(cnt > 1, "Too many times second reading of jail files, cnt = %s" % cnt)
# same with filter:
cnt = self._getLoggedReadCount(r'filter\.d/common\.conf')
self.assertFalse(cnt > 1, "Too many times reading of filter files, cnt = %s" % cnt)
# same with action:
cnt = self._getLoggedReadCount(r'action\.d/iptables-common\.conf')
self.assertFalse(cnt > 1, "Too many times reading of action files, cnt = %s" % cnt)
finally: finally:
shutil.rmtree(basedir) shutil.rmtree(basedir)