result from merge with gerrit

secop subdir only

Change-Id: I65ab7049719b374ae3ec0259483e7e7d16aafcd1
This commit is contained in:
2022-03-07 17:49:08 +01:00
parent dee3514065
commit bd246c5ca7
20 changed files with 760 additions and 583 deletions

View File

@ -29,50 +29,78 @@ import time
import threading
from secop.lib.asynconn import AsynConn, ConnectionClosed
from secop.datatypes import ArrayOf, BLOBType, BoolType, FloatRange, IntRange, StringType, TupleOf, ValueType
from secop.errors import CommunicationFailedError, CommunicationSilentError, ConfigError
from secop.datatypes import ArrayOf, BLOBType, BoolType, FloatRange, IntRange, \
StringType, TupleOf, ValueType
from secop.errors import CommunicationFailedError, CommunicationSilentError, \
ConfigError, ProgrammingError
from secop.modules import Attached, Command, \
Communicator, Done, Module, Parameter, Property
from secop.poller import REGULAR
from secop.lib import generalConfig
generalConfig.defaults['legacy_hasiodev'] = False
HEX_CODE = re.compile(r'[0-9a-fA-F][0-9a-fA-F]$')
class HasIodev(Module):
class HasIO(Module):
"""Mixin for modules using a communicator"""
iodev = Attached()
io = Attached()
uri = Property('uri for automatic creation of the attached communication module',
StringType(), default='')
iodevDict = {}
ioDict = {}
ioClass = None
def __init__(self, name, logger, opts, srv):
iodev = opts.get('iodev')
Module.__init__(self, name, logger, opts, srv)
io = opts.get('io')
super().__init__(name, logger, opts, srv)
if self.uri:
opts = {'uri': self.uri, 'description': 'communication device for %s' % name,
'export': False}
ioname = self.iodevDict.get(self.uri)
ioname = self.ioDict.get(self.uri)
if not ioname:
ioname = iodev or name + '_iodev'
iodev = self.iodevClass(ioname, srv.log.getChild(ioname), opts, srv)
srv.modules[ioname] = iodev
self.iodevDict[self.uri] = ioname
self.iodev = ioname
elif not self.iodev:
raise ConfigError("Module %s needs a value for either 'uri' or 'iodev'" % name)
ioname = io or name + '_io'
io = self.ioClass(ioname, srv.log.getChild(ioname), opts, srv) # pylint: disable=not-callable
io.callingModule = []
srv.modules[ioname] = io
self.ioDict[self.uri] = ioname
self.io = ioname
elif not io:
raise ConfigError("Module %s needs a value for either 'uri' or 'io'" % name)
def initModule(self):
try:
self._iodev.read_is_connected()
self.io.read_is_connected()
except (CommunicationFailedError, AttributeError):
# AttributeError: for missing _iodev?
# AttributeError: read_is_connected is not required for an io object
pass
super().initModule()
def sendRecv(self, command):
return self._iodev.communicate(command)
def communicate(self, *args):
return self.io.communicate(*args)
def multicomm(self, *args):
return self.io.multicomm(*args)
class HasIodev(HasIO):
# TODO: remove this legacy mixin
iodevClass = None
@property
def _iodev(self):
return self.io
def __init__(self, name, logger, opts, srv):
self.ioClass = self.iodevClass
super().__init__(name, logger, opts, srv)
if generalConfig.legacy_hasiodev:
self.log.warn('using the HasIodev mixin is deprecated - use HasIO instead')
else:
self.log.error('legacy HasIodev no longer supported')
self.log.error('you may suppress this error message by running the server with --relaxed')
raise ProgrammingError('legacy HasIodev no longer supported')
self.sendRecv = self.communicate
class IOBase(Communicator):
@ -80,7 +108,7 @@ class IOBase(Communicator):
uri = Property('hostname:portnumber', datatype=StringType())
timeout = Parameter('timeout', datatype=FloatRange(0), default=2)
wait_before = Parameter('wait time before sending', datatype=FloatRange(), default=0)
is_connected = Parameter('connection state', datatype=BoolType(), readonly=False, poll=REGULAR)
is_connected = Parameter('connection state', datatype=BoolType(), readonly=False, default=False)
pollinterval = Parameter('reconnect interval', datatype=FloatRange(0), readonly=False, default=10)
_reconnectCallbacks = None
@ -89,8 +117,8 @@ class IOBase(Communicator):
_lock = None
def earlyInit(self):
self._lock = threading.RLock()
super().earlyInit()
self._lock = threading.RLock()
def connectStart(self):
raise NotImplementedError
@ -104,6 +132,9 @@ class IOBase(Communicator):
self._conn = None
self.is_connected = False
def doPoll(self):
self.read_is_connected()
def read_is_connected(self):
"""try to reconnect, when not connected
@ -155,6 +186,9 @@ class IOBase(Communicator):
if removeme:
self._reconnectCallbacks.pop(key)
def communicate(self, command):
return NotImplementedError
class StringIO(IOBase):
"""line oriented communicator
@ -219,7 +253,6 @@ 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:
@ -236,15 +269,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.comLog('garbage: %r', garbage)
self._conn.send(cmd + self._eol_write)
self.log.debug('> %s' % cmd.decode(self.encoding))
self.comLog('> %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('< %s' % reply)
self.comLog('< %s', reply)
return reply
except Exception as e:
if str(e) == self._last_error:
@ -336,14 +369,14 @@ class BytesIO(IOBase):
time.sleep(self.wait_before)
garbage = self._conn.flush_recv()
if garbage:
self.log.debug('garbage: %s', hexify(garbage))
self.comLog('garbage: %r', garbage)
self._conn.send(request)
self.log.debug('> %s', hexify(request))
self.comLog('> %s', hexify(request))
reply = self._conn.readbytes(replylen, self.timeout)
except ConnectionClosed as e:
self.closeConnection()
raise CommunicationFailedError('disconnected') from None
self.log.debug('< %s', hexify(reply))
self.comLog('< %s', hexify(reply))
return self.getFullReply(request, reply)
except Exception as e:
if str(e) == self._last_error:
@ -352,6 +385,15 @@ class BytesIO(IOBase):
self.log.error(self._last_error)
raise
@Command((ArrayOf(TupleOf(BLOBType(), IntRange(0)))), result=ArrayOf(BLOBType()))
def multicomm(self, requests):
"""communicate multiple request/replies in one row"""
replies = []
with self._lock:
for request in requests:
replies.append(self.communicate(*request))
return replies
def readBytes(self, nbytes):
"""read bytes
@ -368,7 +410,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 the minimum expected length of the reply.
with the `replylen` argument set to 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.