user friendly reporting of config errors

Config errors are collected first, and raised after processing
all modules. This is more user friendly.

+ remove redundant check for predefined accessibles in modules.py
+ fixed error handling for exporting parameters in params.py
+ fixed handling of bare attributes overwriting properties

Change-Id: I894bda291ab85ccec3d771c4903393c808af0a2a
This commit is contained in:
2021-03-08 10:27:43 +01:00
parent 1ca35cf8e9
commit 804581546d
4 changed files with 106 additions and 76 deletions

View File

@ -26,11 +26,12 @@
import ast
import configparser
import os
import sys
import threading
import time
from collections import OrderedDict
from secop.errors import ConfigError
from secop.errors import ConfigError, SECoPError
from secop.lib import formatException, get_class, getGeneralConfig
from secop.modules import Attached
from secop.params import PREDEFINED_ACCESSIBLES
@ -179,8 +180,8 @@ class Server:
self.run()
def unknown_options(self, cls, options):
raise ConfigError("%s class don't know how to handle option(s): %s" %
(cls.__name__, ', '.join(options)))
return ("%s class don't know how to handle option(s): %s" %
(cls.__name__, ', '.join(options)))
def run(self):
while self._restart:
@ -201,7 +202,7 @@ class Server:
cls = get_class(self.INTERFACES[scheme])
with cls(scheme, self.log.getChild(scheme), opts, self) as self.interface:
if opts:
self.unknown_options(cls, opts)
raise ConfigError(self.unknown_options(cls, opts))
self.log.info('startup done, handling transport messages')
if systemd:
systemd.daemon.notify("READY=1\nSTATUS=accepting requests")
@ -219,16 +220,26 @@ class Server:
self.interface.shutdown()
def _processCfg(self):
errors = []
opts = dict(self.node_cfg)
cls = get_class(opts.pop('class', 'protocol.dispatcher.Dispatcher'))
self.dispatcher = cls(opts.pop('name', self._cfgfiles), self.log.getChild('dispatcher'), opts, self)
if opts:
self.unknown_options(cls, opts)
errors.append(self.unknown_options(cls, opts))
self.modules = OrderedDict()
badclass = None
self.lastError = None
for modname, options in self.module_cfg.items():
opts = dict(options)
cls = get_class(opts.pop('class'))
modobj = cls(modname, self.log.getChild(modname), opts, self)
try:
classname = opts.pop('class')
cls = get_class(classname)
modobj = cls(modname, self.log.getChild(modname), opts, self)
except ConfigError as e:
errors.append(str(e))
except Exception as e:
badclass = classname
errors.append('error while loading %s' % badclass)
# all used args should be popped from opts!
if opts:
self.unknown_options(cls, opts)
@ -249,12 +260,28 @@ class Server:
for modname, modobj in self.modules.items():
for propname, propobj in modobj.propertyDict.items():
if isinstance(propobj, Attached):
setattr(modobj, propobj.attrname or '_' + propname,
self.dispatcher.get_module(getattr(modobj, propname)))
try:
setattr(modobj, propobj.attrname or '_' + propname,
self.dispatcher.get_module(getattr(modobj, propname)))
except SECoPError as e:
errors.append('module %s, attached %s: %s' % (modname, propname, str(e)))
# call init on each module after registering all
for modname, modobj in self.modules.items():
modobj.initModule()
if errors:
for errtxt in errors:
for line in errtxt.split('\n'):
self.log.error(line)
# print a list of config errors to stderr
sys.stderr.write('\n'.join(errors))
sys.stderr.write('\n')
if badclass:
# force stack trace for import of last erroneous module
get_class(badclass)
sys.exit(1)
if self._testonly:
return
start_events = []