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
from .configreader import ConfigReader, DefinitionInitConfigReader
from .configreader import DefinitionInitConfigReader
from ..helpers import getLogger
# Gets the instance of the logger.
@ -47,15 +47,19 @@ class ActionReader(DefinitionInitConfigReader):
DefinitionInitConfigReader.__init__(
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):
self._name = name
def getName(self):
return self._name
def read(self):
return ConfigReader.read(self, os.path.join("action.d", self._file))
def convert(self):
head = ["set", self._jailName]
stream = list()

View File

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

View File

@ -32,6 +32,65 @@ from ..helpers import getLogger
# Gets the instance of the logger.
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):
@ -39,8 +98,8 @@ class ConfigReader(SafeConfigParserWithIncludes):
def __init__(self, basedir=None):
SafeConfigParserWithIncludes.__init__(self)
self.read_cfg_files = dict()
self.setBaseDir(basedir)
self.__opts = None
def setBaseDir(self, basedir):
if basedir is None:
@ -122,17 +181,15 @@ class ConfigReader(SafeConfigParserWithIncludes):
logSys.warning("'%s' not defined in '%s'. Using default one: %r"
% (option[1], sec, option[2]))
values[option[1]] = option[2]
else:
logSys.debug(
"Non essential option '%s' not defined in '%s'.",
option[1], sec)
elif logSys.getEffectiveLevel() <= logLevel:
logSys.log(logLevel, "Non essential option '%s' not defined in '%s'.", option[1], sec)
except ValueError:
logSys.warning("Wrong value for '" + option[1] + "' in '" + sec +
"'. Using default one: '" + `option[2]` + "'")
values[option[1]] = option[2]
return values
class DefinitionInitConfigReader(ConfigReader):
class DefinitionInitConfigReader(ConfigWrapper):
"""Config reader for files with options grouped in [Definition] and
[Init] sections.
@ -144,7 +201,7 @@ class DefinitionInitConfigReader(ConfigReader):
_configOpts = []
def __init__(self, file_, jailName, initOpts, **kwargs):
ConfigReader.__init__(self, **kwargs)
ConfigWrapper.__init__(self, **kwargs)
self.setFile(file_)
self.setJailName(jailName)
self._initOpts = initOpts
@ -163,14 +220,14 @@ class DefinitionInitConfigReader(ConfigReader):
return self._jailName
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
def readexplicit(self):
return SafeConfigParserWithIncludes.read(self, self._file)
def getOptions(self, pOpts):
self._opts = ConfigReader.getOptions(
self._opts = ConfigWrapper.getOptions(
self, "Definition", self._configOpts, pOpts)
if self.has_section("Init"):

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -347,6 +347,23 @@ class FilterReaderTest(unittest.TestCase):
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):
basedir = tempfile.mkdtemp("fail2ban_conf")
try:
@ -356,21 +373,27 @@ class JailsReaderTestCache(LogCaptureTestCase):
shutil.copy(CONFIG_DIR + '/fail2ban.conf', basedir + '/fail2ban.local')
# read whole configuration like a file2ban-client ...
configurator = Configurator()
configurator.setBaseDir(basedir)
configurator.readEarly()
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
self._readWholeConf(basedir)
# how many times jail.local was read:
cnt = self._getLoggedReadCount('jail.local')
# if cnt > 1:
# self.printLog()
self.assertFalse(cnt > 1, "Too many times reading of config files, cnt = %s" % cnt)
self.assertFalse(cnt <= 0)
self.assertFalse(cnt > 1, "Too many times reading of jail files, cnt = %s" % cnt)
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:
shutil.rmtree(basedir)