From acb3bdad6a35735fc3d37fc894a1332565cdfa5d Mon Sep 17 00:00:00 2001 From: Markus Zolliker Date: Wed, 2 Feb 2022 09:57:30 +0100 Subject: [PATCH] varios fixes at psi repo, as of 2022-02-01 Change-Id: I8cdc849126d52ef0f2f27a0faf661830aac6f874 --- bin/secop-server | 23 ++++++----- secop/io.py | 20 +++++---- secop/iohandler.py | 8 +++- secop/lib/__init__.py | 94 +++++++++++++++++++++++++++---------------- 4 files changed, 93 insertions(+), 52 deletions(-) diff --git a/bin/secop-server b/bin/secop-server index dc271b9..779ae6d 100755 --- a/bin/secop-server +++ b/bin/secop-server @@ -27,12 +27,11 @@ import sys import argparse from os import path -import mlzlog - # Add import path for inplace usage sys.path.insert(0, path.abspath(path.join(path.dirname(__file__), '..'))) from secop.lib import getGeneralConfig +from secop.logging import initLogging from secop.server import Server @@ -60,15 +59,21 @@ def parseArgv(argv): parser.add_argument('-c', '--cfgfiles', action='store', - help="comma separated list of cfg files\n" - "defaults to \n" - "cfgfiles given without '.cfg' extension are searched in the configuration directory," + help="comma separated list of cfg files,\n" + "defaults to .\n" + "cfgfiles given without '.cfg' extension are searched in the configuration directory, " "else they are treated as path names", default=None) + parser.add_argument('-g', + '--gencfg', + action='store', + help="full path of general config file,\n" + "defaults to env. variable FRAPPY_CONFIG_FILE\n", + default=None) parser.add_argument('-t', '--test', action='store_true', - help='Check cfg files only', + help='check cfg files only', default=False) return parser.parse_args(argv) @@ -80,9 +85,9 @@ def main(argv=None): args = parseArgv(argv[1:]) loglevel = 'debug' if args.verbose else ('error' if args.quiet else 'info') - mlzlog.initLogging('secop', loglevel, getGeneralConfig()['logdir']) - - srv = Server(args.name, mlzlog.log, cfgfiles=args.cfgfiles, interface=args.port, testonly=args.test) + getGeneralConfig(args.gencfg) + log = initLogging(loglevel) + srv = Server(args.name, log, cfgfiles=args.cfgfiles, interface=args.port, testonly=args.test) if args.daemonize: srv.start() diff --git a/secop/io.py b/secop/io.py index 90e0717..4a4203d 100644 --- a/secop/io.py +++ b/secop/io.py @@ -90,6 +90,7 @@ class IOBase(Communicator): def earlyInit(self): self._lock = threading.RLock() + super().earlyInit() def connectStart(self): raise NotImplementedError @@ -218,6 +219,7 @@ class StringIO(IOBase): if not self.is_connected: self.read_is_connected() # try to reconnect if not self._conn: + self.log.debug('can not connect to %r' % self.uri) raise CommunicationSilentError('can not connect to %r' % self.uri) try: with self._lock: @@ -234,15 +236,15 @@ class StringIO(IOBase): if garbage is None: # read garbage only once garbage = self._conn.flush_recv() if garbage: - self.log.debug('garbage: %r', garbage) + self.log.debug('garbage: %r' % garbage) self._conn.send(cmd + self._eol_write) - self.log.debug('send: %s', cmd + self._eol_write) + self.log.debug('> %s' % cmd.decode(self.encoding)) reply = self._conn.readline(self.timeout) except ConnectionClosed as e: self.closeConnection() raise CommunicationFailedError('disconnected') from None reply = reply.decode(self.encoding) - self.log.debug('recv: %s', reply) + self.log.debug('< %s' % reply) return reply except Exception as e: if str(e) == self._last_error: @@ -291,6 +293,10 @@ def make_bytes(string): return bytes([int(c, 16) if HEX_CODE.match(c) else ord(c) for c in string.split()]) +def hexify(bytes_): + return ' '.join('%02x' % r for r in bytes_) + + class BytesIO(IOBase): identification = Property( """identification @@ -330,14 +336,14 @@ class BytesIO(IOBase): time.sleep(self.wait_before) garbage = self._conn.flush_recv() if garbage: - self.log.debug('garbage: %r', garbage) + self.log.debug('garbage: %s', hexify(garbage)) self._conn.send(request) - self.log.debug('send: %r', request) + self.log.debug('> %s', hexify(request)) reply = self._conn.readbytes(replylen, self.timeout) except ConnectionClosed as e: self.closeConnection() raise CommunicationFailedError('disconnected') from None - self.log.debug('recv: %r', reply) + self.log.debug('< %s', hexify(reply)) return self.getFullReply(request, reply) except Exception as e: if str(e) == self._last_error: @@ -362,7 +368,7 @@ class BytesIO(IOBase): :return: the full reply (replyheader + additional bytes) When the reply length is variable, :meth:`communicate` should be called - with the `replylen` argument set to minimum expected length of the reply. + with the `replylen` argument set to the minimum expected length of the reply. Typically this method determines then the length of additional bytes from the already received bytes (replyheader) and/or the request and calls :meth:`readBytes` to get the remaining bytes. diff --git a/secop/iohandler.py b/secop/iohandler.py index 594554b..072c5e5 100644 --- a/secop/iohandler.py +++ b/secop/iohandler.py @@ -153,7 +153,7 @@ class Change: self._reply = None def __getattr__(self, key): - """return attribute from module key is not in self._valuedict""" + """return attribute from module key when not in self._valuedict""" if key in self._valuedict: return self._valuedict[key] return getattr(self._module, key) @@ -174,6 +174,9 @@ class Change: self._valuedict.update(result) return self._reply + def __repr__(self): + return 'Change<%s>' % ', '.join('%s=%r' % kv for kv in self._valuedict.items()) + class IOHandlerBase: """abstract IO handler @@ -280,6 +283,8 @@ class IOHandler(IOHandlerBase): reply = self.send_command(module) # convert them to parameters result = self.analyze(module, *reply) + module.log.debug('result of analyze_%s: %s', self.group, + ', '.join('%s=%r' % kv for kv in result.items())) for pname, value in result.items(): setattr(module, pname, value) for pname in self.parameters: @@ -322,6 +327,7 @@ class IOHandler(IOHandlerBase): change = Change(self, module, valuedict) if force_read: change.readValues() + module.log.debug('call change_%s(%r)', self.group, change) values = self.change(module, change) if values is None: # this indicates that nothing has to be written return diff --git a/secop/lib/__init__.py b/secop/lib/__init__.py index ce72e16..99826bb 100644 --- a/secop/lib/__init__.py +++ b/secop/lib/__init__.py @@ -27,42 +27,62 @@ import socket import sys import threading import traceback +from configparser import ConfigParser from os import environ, path - -repodir = path.abspath(path.join(path.dirname(__file__), '..', '..')) - -if path.splitext(sys.executable)[1] == ".exe" and not path.basename(sys.executable).startswith('python'): - CONFIG = { - 'piddir': './', - 'logdir': './log', - 'confdir': './', - } -elif not path.exists(path.join(repodir, '.git')): - CONFIG = { - 'piddir': '/var/run/secop', - 'logdir': '/var/log', - 'confdir': '/etc/secop', - } -else: - CONFIG = { - 'piddir': path.join(repodir, 'pid'), - 'logdir': path.join(repodir, 'log'), - 'confdir': path.join(repodir, 'cfg'), - } -# overwrite with env variables SECOP_LOGDIR, SECOP_PIDDIR, SECOP_CONFDIR, if present -for dirname in CONFIG: - CONFIG[dirname] = environ.get('SECOP_%s' % dirname.upper(), CONFIG[dirname]) - -# this is not customizable -CONFIG['basedir'] = repodir - -# TODO: if ever more general options are need, we should think about a general config file - - +CONFIG = {} unset_value = object() +def getGeneralConfig(confdir=None): + global CONFIG # pylint: disable=global-statement + + if CONFIG: + if confdir: + raise ValueError('getGeneralConfig with argument must be called first') + else: + repodir = path.abspath(path.join(path.dirname(__file__), '..', '..')) + if path.splitext(sys.executable)[1] == ".exe" and not path.basename(sys.executable).startswith('python'): + # special MS windows environment + CONFIG = { + 'piddir': './', + 'logdir': './log', + 'confdir': './', + } + elif not path.exists(path.join(repodir, '.git')): + CONFIG = { + 'piddir': '/var/run/secop', + 'logdir': '/var/log', + 'confdir': '/etc/secop', + } + else: + CONFIG = { + 'piddir': path.join(repodir, 'pid'), + 'logdir': path.join(repodir, 'log'), + 'confdir': path.join(repodir, 'cfg'), + } + gen_config_path = confdir or environ.get('FRAPPY_CONFIG_FILE', + path.join(CONFIG['confdir'], 'generalConfig.cfg')) + if gen_config_path and path.exists(gen_config_path): + parser = ConfigParser() + parser.optionxform = str + parser.read([gen_config_path]) + CONFIG = {} + # only the FRAPPY section is relevant, other sections might be used by others + for key, value in parser['FRAPPY'].items(): + if value.startswith('./'): + CONFIG[key] = path.abspath(path.join(repodir, value)) + else: + # expand ~ to username, also in path lists separated with ':' + CONFIG[key] = ':'.join(path.expanduser(v) for v in value.split(':')) + else: + for dirname in CONFIG: + CONFIG[dirname] = environ.get('SECOP_%s' % dirname.upper(), CONFIG[dirname]) + # this is not customizable + CONFIG['basedir'] = repodir + return CONFIG + + class lazy_property: """A property that calculates its value only once.""" @@ -253,10 +273,6 @@ def getfqdn(name=''): return socket.getfqdn(name) -def getGeneralConfig(): - return CONFIG - - def formatStatusBits(sword, labels, start=0): """Return a list of labels according to bit state in `sword` starting with bit `start` and the first label in `labels`. @@ -266,3 +282,11 @@ def formatStatusBits(sword, labels, start=0): if sword & (1 << i) and lbl: result.append(lbl) return result + + +class UniqueObject: + def __init__(self, name): + self.name = name + + def __repr__(self): + return 'UniqueObject(%r)' % self.name