result from merge with gerrit
secop subdir only Change-Id: I65ab7049719b374ae3ec0259483e7e7d16aafcd1
This commit is contained in:
102
secop/io.py
102
secop/io.py
@ -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.
|
||||
|
Reference in New Issue
Block a user