varios fixes at psi repo, as of 2022-02-01

Change-Id: I8cdc849126d52ef0f2f27a0faf661830aac6f874
This commit is contained in:
zolliker 2022-02-02 09:57:30 +01:00
parent 5e3fb10884
commit acb3bdad6a
4 changed files with 93 additions and 52 deletions

View File

@ -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 <name_of_the_instance>\n"
"cfgfiles given without '.cfg' extension are searched in the configuration directory,"
help="comma separated list of cfg files,\n"
"defaults to <name_of_the_instance>.\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()

View File

@ -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.

View File

@ -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

View File

@ -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