remove Message objects + rewrite server startup

Change-Id: Ide72fb915c3ca93c74edadd8952853508e677de7
Reviewed-on: https://forge.frm2.tum.de/review/19199
Tested-by: JenkinsCodeReview <bjoern_pedersen@frm2.tum.de>
Reviewed-by: Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
This commit is contained in:
Enrico Faulhaber 2018-10-15 14:24:34 +02:00
parent 9824b9216d
commit 87261382cf
30 changed files with 337 additions and 578 deletions

View File

@ -1,4 +1,4 @@
[equipment MLZ_amagnet(Garfield)] [node MLZ_amagnet(Garfield)]
description=MLZ-Amagnet description=MLZ-Amagnet
. .
Water cooled magnet from ANTARES@MLZ. Water cooled magnet from ANTARES@MLZ.
@ -14,12 +14,9 @@ visibility=expert
foo=bar foo=bar
[interface tcp] [interface tcp]
interface=tcp type=tcp
bindto=0.0.0.0 bindto=0.0.0.0
bindport=10767 bindport=10767
# protocol to use for this interface
framing=eol
encoding=secop
[module enable] [module enable]
class=secop_mlz.entangle.NamedDigitalOutput class=secop_mlz.entangle.NamedDigitalOutput

View File

@ -6,12 +6,9 @@ description = CCR12 box of MLZ Sample environment group
[interface tcp] [interface tcp]
interface=tcp type=tcp
bindto=0.0.0.0 bindto=0.0.0.0
bindport=10767 bindport=10767
# protocol to use for this interface
framing=eol
encoding=secop
[module automatik] [module automatik]
class=secop_mlz.entangle.NamedDigitalOutput class=secop_mlz.entangle.NamedDigitalOutput

View File

@ -1,4 +1,4 @@
[equipment cryo_7] [node cryo_7]
# set SEC-node properties # set SEC-node properties
description = short description description = short description
. .
@ -6,12 +6,9 @@ description = short description
[interface tcp] [interface tcp]
interface=tcp type=tcp
bindto=0.0.0.0 bindto=0.0.0.0
bindport=10769 bindport=10769
# protocol to use for this interface
framing=eol
encoding=secop
[module cryo] [module cryo]

View File

@ -1,13 +1,9 @@
[equipment Equipment_ID_for_demonstration] [node Equipment_ID_for_demonstration]
description = virtual modules to play around with description = virtual modules to play around with
[interface tcp] [interface tcp]
interface=tcp
bindto=0.0.0.0 bindto=0.0.0.0
bindport=10767 bindport=10767
# protocol to use for this interface
framing=eol
encoding=secop
[module heatswitch] [module heatswitch]
class=secop_demo.modules.Switch class=secop_demo.modules.Switch
@ -23,7 +19,7 @@ class=secop_demo.modules.SampleTemp
sensor = 'Q1329V7R3' sensor = 'Q1329V7R3'
ramp = 4 ramp = 4
target = 10 target = 10
default = 10 value = 10
[module tc1] [module tc1]
class=secop_demo.modules.CoilTemp class=secop_demo.modules.CoilTemp

View File

@ -1,20 +1,10 @@
[equipment see_demo_equipment] [node see_demo_equipment]
description=Do not use, it needs to be rewritten.... description=Do not use, it needs to be rewritten....
[client]
connectto=0.0.0.0
port=10767
interface = tcp
framing=eol
encoding=secop
[interface testing] [interface testing]
interface=tcp type=tcp
bindto=0.0.0.0 bindto=0.0.0.0
bindport=10767 bindport=10767
# protocol to use for this interface
framing=eol
encoding=secop
[module tc1] [module tc1]
class=secop_demo.modules.CoilTemp class=secop_demo.modules.CoilTemp

View File

@ -4,12 +4,9 @@ description=description of the simulation sec-node
Testing simulation dummy setup. Testing simulation dummy setup.
[interface tcp] [interface tcp]
interface=tcp type=tcp
bindto=0.0.0.0 bindto=0.0.0.0
bindport=10767 bindport=10767
# protocol to use for this interface
framing=eol
encoding=secop
[module sim] [module sim]

View File

@ -1,4 +1,4 @@
[equipment SIM_MLZ_amagnet(Garfield)] [node SIM_MLZ_amagnet(Garfield)]
description=MLZ-Amagnet description=MLZ-Amagnet
. .
Water cooled magnet from ANTARES@MLZ. Water cooled magnet from ANTARES@MLZ.
@ -14,12 +14,9 @@ visibility=expert
foo=bar foo=bar
[interface tcp] [interface tcp]
interface=tcp type=tcp
bindto=0.0.0.0 bindto=0.0.0.0
bindport=10767 bindport=10767
# protocol to use for this interface
framing=eol
encoding=secop
[module enable] [module enable]
class=secop.simulation.SimWritable class=secop.simulation.SimWritable

View File

@ -7,12 +7,9 @@ description = [sim] cci3he box of MLZ Sample environment group
.meaning={'T_regulation':{'T_cci3he1':300}, 'T_sample':{'T_cci3he1_A':300, 'T_cci3he1_B':280}} .meaning={'T_regulation':{'T_cci3he1':300}, 'T_sample':{'T_cci3he1_A':300, 'T_cci3he1_B':280}}
[interface tcp] [interface tcp]
interface=tcp type=tcp
bindto=0.0.0.0 bindto=0.0.0.0
bindport=10767 bindport=10767
# protocol to use for this interface
framing=eol
encoding=secop
[module T_cci3he1] [module T_cci3he1]
class=secop.simulation.SimDrivable class=secop.simulation.SimDrivable

View File

@ -7,12 +7,9 @@ description = [sim] ccidu box of MLZ Sample environment group
.meaning={'T_regulation':{'T_ccidu1':300}, 'T_sample':{'T_ccidu1_A':300, 'T_ccidu1_B':280}} .meaning={'T_regulation':{'T_ccidu1':300}, 'T_sample':{'T_ccidu1_A':300, 'T_ccidu1_B':280}}
[interface tcp] [interface tcp]
interface=tcp type=tcp
bindto=0.0.0.0 bindto=0.0.0.0
bindport=10767 bindport=10767
# protocol to use for this interface
framing=eol
encoding=secop
[module T_ccidu1] [module T_ccidu1]
class=secop.simulation.SimDrivable class=secop.simulation.SimDrivable

View File

@ -9,12 +9,9 @@ description = [sim] CCR12 box of MLZ Sample environment group
.meaning={'T_regulation':{'T_ccr12':200, 'T_ccr12_stick':150, 'T_ccr12_tube':100}, 'T_sample':{'T_ccr12_B':100, 'T_ccr12_A':90, 'T_ccr12_D':20, 'T_ccr12_C':10}} .meaning={'T_regulation':{'T_ccr12':200, 'T_ccr12_stick':150, 'T_ccr12_tube':100}, 'T_sample':{'T_ccr12_B':100, 'T_ccr12_A':90, 'T_ccr12_D':20, 'T_ccr12_C':10}}
[interface tcp] [interface tcp]
interface=tcp type=tcp
bindto=0.0.0.0 bindto=0.0.0.0
bindport=10767 bindport=10767
# protocol to use for this interface
framing=eol
encoding=secop
[module T_ccr12] [module T_ccr12]
class=secop.simulation.SimDrivable class=secop.simulation.SimDrivable

View File

@ -9,12 +9,9 @@ description = [sim] CCR12 box of MLZ Sample environment group
.meaning={'T_regulation':{'T_ccr12':200, 'T_ccr12_stick':150, 'T_ccr12_tube':100}, 'T_sample':{'T_ccr12_B':100, 'T_ccr12_A':90, 'T_ccr12_D':20, 'T_ccr12_C':10}} .meaning={'T_regulation':{'T_ccr12':200, 'T_ccr12_stick':150, 'T_ccr12_tube':100}, 'T_sample':{'T_ccr12_B':100, 'T_ccr12_A':90, 'T_ccr12_D':20, 'T_ccr12_C':10}}
[interface tcp] [interface tcp]
interface=tcp type=tcp
bindto=0.0.0.0 bindto=0.0.0.0
bindport=10767 bindport=10767
# protocol to use for this interface
framing=eol
encoding=secop
[module T_ccr12] [module T_ccr12]
class=secop.simulation.SimDrivable class=secop.simulation.SimDrivable

View File

@ -6,12 +6,9 @@ description = [sim] htf02 box of MLZ Sample environment group
.meaning={'T_regulation':{'T_htf02':100}, 'T_sample':{'T_htf02':100}} .meaning={'T_regulation':{'T_htf02':100}, 'T_sample':{'T_htf02':100}}
[interface tcp] [interface tcp]
interface=tcp type=tcp
bindto=0.0.0.0 bindto=0.0.0.0
bindport=10767 bindport=10767
# protocol to use for this interface
framing=eol
encoding=secop
[module T_htf02] [module T_htf02]
class=secop.simulation.SimDrivable class=secop.simulation.SimDrivable

View File

@ -6,12 +6,9 @@ description = [sim] Stressihtf2 box of MLZ Sample environment group
.meaning={'T_regulation':{'T_stressihtf2':100}, 'T_sample':{'T_stressihtf2':100}} .meaning={'T_regulation':{'T_stressihtf2':100}, 'T_sample':{'T_stressihtf2':100}}
[interface tcp] [interface tcp]
interface=tcp type=tcp
bindto=0.0.0.0 bindto=0.0.0.0
bindport=10767 bindport=10767
# protocol to use for this interface
framing=eol
encoding=secop
[module T_stressihtf2] [module T_stressihtf2]
class=secop.simulation.SimDrivable class=secop.simulation.SimDrivable

View File

@ -6,12 +6,9 @@ description = [sim] Stressihtf2 box of MLZ Sample environment group
meaning={'T_regulation':{'T':100}, 'T_sample':{'T_sample':100}} meaning={'T_regulation':{'T':100}, 'T_sample':{'T_sample':100}}
[interface tcp] [interface tcp]
interface=tcp type=tcp
bindto=0.0.0.0 bindto=0.0.0.0
bindport=10767 bindport=10767
# protocol to use for this interface
framing=eol
encoding=secop
[module T] [module T]
class=secop.simulation.SimDrivable class=secop.simulation.SimDrivable

View File

@ -6,12 +6,9 @@ description = Stressihtf2 box of MLZ Sample environment group
meaning={'T_regulation':{'T':100}, 'T_sample':{'T_sample':100}} meaning={'T_regulation':{'T':100}, 'T_sample':{'T_sample':100}}
[interface tcp] [interface tcp]
interface=tcp type=tcp
bindto=0.0.0.0 bindto=0.0.0.0
bindport=10767 bindport=10767
# protocol to use for this interface
framing=eol
encoding=secop
[module T] [module T]
class=secop_mlz.entangle.TemperatureController class=secop_mlz.entangle.TemperatureController

View File

@ -9,12 +9,9 @@ description=description of the testing sec-node
These texts are supposed to be possibly very long. These texts are supposed to be possibly very long.
[interface tcp] [interface tcp]
interface=tcp type=tcp
bindto=0.0.0.0 bindto=0.0.0.0
bindport=10768 bindport=10768
# protocol to use for this interface
framing=eol
encoding=secop
[module LN2] [module LN2]

View File

@ -37,8 +37,8 @@ except ImportError:
import ConfigParser as configparser import ConfigParser as configparser
import mlzlog import mlzlog
from secop.protocol.interface.tcp import decode_msg, get_msg, encode_msg_frame from secop.protocol.interface import decode_msg, get_msg, encode_msg_frame
from secop.protocol.messages import EVENTREPLY, DESCRIPTIONREQUEST, Message from secop.protocol.messages import EVENTREPLY, DESCRIPTIONREQUEST
class NameSpace(dict): class NameSpace(dict):
@ -135,12 +135,13 @@ class TCPConnection(object):
break # no more messages to process break # no more messages to process
if not origin: # empty string if not origin: # empty string
continue # ??? continue # ???
msg = decode_msg(origin) _ = decode_msg(origin)
# construct msgObj from msg # construct msgObj from msg
try: try:
msgObj = Message(*msg) #msgObj = Message(*msg)
msgObj.origin = origin.decode('latin-1') #msgObj.origin = origin.decode('latin-1')
self.handle(msgObj) #self.handle(msgObj)
pass
except Exception: except Exception:
# ??? what to do here? # ??? what to do here?
pass pass
@ -188,7 +189,7 @@ class Client(object):
# XXX: further notification-callbacks needed ??? # XXX: further notification-callbacks needed ???
def populateNamespace(self, namespace): def populateNamespace(self, namespace):
self.connection.send(Message(DESCRIPTIONREQUEST)) #self.connection.send(Message(DESCRIPTIONREQUEST))
# reply = self.connection.read() # reply = self.connection.read()
# self.log.info("found modules %r" % reply) # self.log.info("found modules %r" % reply)
# create proxies, populate cache.... # create proxies, populate cache....

View File

@ -28,7 +28,7 @@ try:
text_type = unicode # Py2 text_type = unicode # Py2
except NameError: except NameError:
text_type = str # Py3 text_type = str # Py3
unicode = str # pylint: disable=redefined-builtin
class EnumMember(object): class EnumMember(object):
"""represents one member of an Enum """represents one member of an Enum

View File

@ -77,11 +77,11 @@ class Module(object):
# reference to the dispatcher (used for sending async updates) # reference to the dispatcher (used for sending async updates)
DISPATCHER = None DISPATCHER = None
def __init__(self, logger, cfgdict, modname, dispatcher): def __init__(self, name, logger, cfgdict, srv):
# remember the dispatcher object (for the async callbacks) # remember the dispatcher object (for the async callbacks)
self.DISPATCHER = dispatcher self.DISPATCHER = srv.dispatcher
self.log = logger self.log = logger
self.name = modname self.name = name
# handle module properties # handle module properties
# 1) make local copies of properties # 1) make local copies of properties
@ -97,6 +97,8 @@ class Module(object):
if k[0] == '.': if k[0] == '.':
if k[1:] in self.properties: if k[1:] in self.properties:
self.properties[k[1:]] = cfgdict.pop(k) self.properties[k[1:]] = cfgdict.pop(k)
elif k[1] == '_':
self.properties[k[1:]] = cfgdict.pop(k)
else: else:
raise ConfigError('Module %r has no property %r' % raise ConfigError('Module %r has no property %r' %
(self.name, k[1:])) (self.name, k[1:]))
@ -109,8 +111,8 @@ class Module(object):
mycls = self.__class__ mycls = self.__class__
myclassname = '%s.%s' % (mycls.__module__, mycls.__name__) myclassname = '%s.%s' % (mycls.__module__, mycls.__name__)
self.properties['_implementation'] = myclassname self.properties['_implementation'] = myclassname
self.properties['interface_class'] = [ self.properties['interface_class'] = [[
b.__name__ for b in mycls.__mro__ if b.__module__.startswith('secop.modules')] b.__name__ for b in mycls.__mro__ if b.__module__.startswith('secop.modules')][0]]
# handle Features # handle Features
# XXX: todo # XXX: todo
@ -138,7 +140,7 @@ class Module(object):
elif hasattr(paramobj, propname): elif hasattr(paramobj, propname):
setattr(paramobj, propname, cfgdict.pop(k)) setattr(paramobj, propname, cfgdict.pop(k))
else: else:
raise ConfigError('Module %s: Parameter %r has not property %r!' % raise ConfigError('Module %s: Parameter %r has no property %r!' %
(self.name, paramname, propname)) (self.name, paramname, propname))
# 3) check config for problems: # 3) check config for problems:
@ -167,12 +169,11 @@ class Module(object):
# 5) 'apply' config: # 5) 'apply' config:
# pass values through the datatypes and store as attributes # pass values through the datatypes and store as attributes
for k, v in cfgdict.items(): for k, v in cfgdict.items():
if k == 'value':
continue
# apply datatype, complain if type does not fit # apply datatype, complain if type does not fit
datatype = self.accessibles[k].datatype datatype = self.accessibles[k].datatype
try: try:
v = datatype.validate(v) v = datatype.validate(v)
self.accessibles[k].default = v
except (ValueError, TypeError): except (ValueError, TypeError):
self.log.exception(formatExtendedStack()) self.log.exception(formatExtendedStack())
raise raise

View File

@ -41,11 +41,15 @@ from __future__ import print_function
from time import time as currenttime from time import time as currenttime
import threading import threading
from secop.protocol.messages import Message, EVENTREPLY, IDENTREQUEST from secop.protocol.messages import EVENTREPLY, IDENTREQUEST, IDENTREPLY, \
from secop.protocol.errors import SECOPError, NoSuchModuleError, \ ENABLEEVENTSREPLY, DESCRIPTIONREPLY, WRITEREPLY, COMMANDREPLY, \
NoSuchCommandError, NoSuchParameterError, BadValueError, ReadonlyError DISABLEEVENTSREPLY, HEARTBEATREPLY
from secop.lib import formatExtendedStack, formatException
from secop.params import Parameter, Command from secop.protocol.errors import InternalError, NoSuchModuleError, \
NoSuchCommandError, NoSuchParameterError, BadValueError, ReadonlyError, \
ProtocolError
from secop.params import Parameter
try: try:
unicode('a') unicode('a')
@ -56,9 +60,9 @@ except NameError:
class Dispatcher(object): class Dispatcher(object):
def __init__(self, logger, options): def __init__(self, name, logger, options, srv):
# to avoid errors, we want to eat all options here # to avoid errors, we want to eat all options here
self.equipment_id = options[u'equipment_id'] self.equipment_id = name
self.nodeopts = {} self.nodeopts = {}
for k in list(options): for k in list(options):
self.nodeopts[k] = options.pop(k) self.nodeopts[k] = options.pop(k)
@ -84,13 +88,12 @@ class Dispatcher(object):
if reallyall: if reallyall:
listeners = self._connections listeners = self._connections
else: else:
if getattr(msg, u'command', None) is None: # all subscribers to module:param
eventname = u'%s:%s' % (msg.module, msg.parameter listeners = self._subscriptions.get(msg[1], set()).copy()
if msg.parameter else u'value') # all subscribers to module
else: module = msg[1].split(':', 1)[0]
eventname = u'%s:%s()' % (msg.module, msg.command) listeners.update(self._subscriptions.get(module, set()))
listeners = self._subscriptions.get(eventname, set()).copy() # all generic subscribers
listeners.update(self._subscriptions.get(msg.module, set()))
listeners.update(self._active_connections) listeners.update(self._active_connections)
for conn in listeners: for conn in listeners:
conn.queue_async_reply(msg) conn.queue_async_reply(msg)
@ -98,22 +101,26 @@ class Dispatcher(object):
def announce_update(self, moduleobj, pname, pobj): def announce_update(self, moduleobj, pname, pobj):
"""called by modules param setters to notify subscribers of new values """called by modules param setters to notify subscribers of new values
""" """
msg = Message(EVENTREPLY, module=moduleobj.name, parameter=pname) msg = (EVENTREPLY, u'%s:%s' % (moduleobj.name, pname), [pobj.export_value(), dict(t=pobj.timestamp)])
msg.set_result(pobj.export_value(), dict(t=pobj.timestamp))
self.broadcast_event(msg) self.broadcast_event(msg)
def subscribe(self, conn, modulename, pname=u'value'): def subscribe(self, conn, modulename, pname=None):
eventname = modulename eventname = modulename
if pname: if pname:
eventname = u'%s:%s' % (modulename, pname) eventname = u'%s:%s' % (modulename, pname)
self._subscriptions.setdefault(eventname, set()).add(conn) self._subscriptions.setdefault(eventname, set()).add(conn)
def unsubscribe(self, conn, modulename, pname=u'value'): def unsubscribe(self, conn, modulename, pname=None):
eventname = modulename
if pname: if pname:
eventname = u'%s:%s' % (modulename, pname) eventname = u'%s:%s' % (modulename, pname)
else:
eventname = modulename
# also remove 'more specific' subscriptions
for k, v in self._subscriptions.items():
if k.startswith(u'%s:' % modulename):
v.discard(conn)
if eventname in self._subscriptions: if eventname in self._subscriptions:
self._subscriptions.setdefault(eventname, set()).discard(conn) self._subscriptions[eventname].discard(conn)
def add_connection(self, conn): def add_connection(self, conn):
"""registers new connection""" """registers new connection"""
@ -125,6 +132,7 @@ class Dispatcher(object):
self._connections.remove(conn) self._connections.remove(conn)
for _evt, conns in list(self._subscriptions.items()): for _evt, conns in list(self._subscriptions.items()):
conns.discard(conn) conns.discard(conn)
self._active_connections.discard(conn)
def register_module(self, moduleobj, modulename, export=True): def register_module(self, moduleobj, modulename, export=True):
self.log.debug(u'registering module %r as %s (export=%r)' % self.log.debug(u'registering module %r as %s (export=%r)' %
@ -138,15 +146,17 @@ class Dispatcher(object):
return self._modules[modulename] return self._modules[modulename]
elif modulename in list(self._modules.values()): elif modulename in list(self._modules.values()):
return modulename return modulename
raise NoSuchModuleError(module=unicode(modulename)) raise NoSuchModuleError('Module does not exist on this SEC-Node!')
def remove_module(self, modulename_or_obj): def remove_module(self, modulename_or_obj):
moduleobj = self.get_module(modulename_or_obj) or modulename_or_obj moduleobj = self.get_module(modulename_or_obj)
modulename = moduleobj.name modulename = moduleobj.name
if modulename in self._export: if modulename in self._export:
self._export.remove(modulename) self._export.remove(modulename)
self._modules.pop(modulename) self._modules.pop(modulename)
# XXX: also clean _subscriptions self._subscriptions.pop(modulename, None)
for k in [k for k in self._subscriptions if k.startswith(u'%s:' % modulename)]:
self._subscriptions.pop(k, None)
def list_module_names(self): def list_module_names(self):
# return a copy of our list # return a copy of our list
@ -158,7 +168,7 @@ class Dispatcher(object):
# omit export=False params! # omit export=False params!
res = [] res = []
for aname, aobj in self.get_module(modulename).accessibles.items(): for aname, aobj in self.get_module(modulename).accessibles.items():
if isinstance(aobj, Command) or aobj.export: if aobj.export:
res.extend([aname, aobj.for_export()]) res.extend([aname, aobj.for_export()])
self.log.debug(u'list accessibles for module %s -> %r' % self.log.debug(u'list accessibles for module %s -> %r' %
(modulename, res)) (modulename, res))
@ -190,33 +200,32 @@ class Dispatcher(object):
moduleobj = self.get_module(modulename) moduleobj = self.get_module(modulename)
if moduleobj is None: if moduleobj is None:
raise NoSuchModuleError(module=modulename) raise NoSuchModuleError('Module does not exist on this SEC-Node!')
cmdspec = moduleobj.accessibles.get(command, None) cmdspec = moduleobj.accessibles.get(command, None)
if cmdspec is None: if cmdspec is None:
raise NoSuchCommandError(module=modulename, command=command) raise NoSuchCommandError('Module has no such command!')
if len(cmdspec.datatype.argtypes) != len(arguments): num_args_required = len(cmdspec.datatype.argtypes)
raise BadValueError( if num_args_required != len(arguments):
module=modulename, raise BadValueError(u'Wrong number of arguments (need %d, got %d)!' % (num_args_required, len(arguments)))
command=command,
reason=u'Wrong number of arguments!')
# now call func and wrap result as value # now call func and wrap result as value
# note: exceptions are handled in handle_request, not here! # note: exceptions are handled in handle_request, not here!
func = getattr(moduleobj, u'do_' + command) func = getattr(moduleobj, u'do_' + command)
res = func(*arguments) res = func(*arguments)
# XXX: pipe through cmdspec.datatype.result ?
return res, dict(t=currenttime()) return res, dict(t=currenttime())
def _setParameterValue(self, modulename, pname, value): def _setParameterValue(self, modulename, pname, value):
moduleobj = self.get_module(modulename) moduleobj = self.get_module(modulename)
if moduleobj is None: if moduleobj is None:
raise NoSuchModuleError(module=modulename) raise NoSuchModuleError('Module does not exist on this SEC-Node!')
pobj = moduleobj.accessibles.get(pname, None) pobj = moduleobj.accessibles.get(pname, None)
if pobj is None or not isinstance(pobj, Parameter): if pobj is None or not isinstance(pobj, Parameter):
raise NoSuchParameterError(module=modulename, parameter=pname) raise NoSuchParameterError('Module has no such parameter on this SEC-Node!')
if pobj.readonly: if pobj.readonly:
raise ReadonlyError(module=modulename, parameter=pname) raise ReadonlyError('This parameter can not be changed remotely.')
writefunc = getattr(moduleobj, u'write_%s' % pname, None) writefunc = getattr(moduleobj, u'write_%s' % pname, None)
# note: exceptions are handled in handle_request, not here! # note: exceptions are handled in handle_request, not here!
@ -231,11 +240,11 @@ class Dispatcher(object):
def _getParameterValue(self, modulename, pname): def _getParameterValue(self, modulename, pname):
moduleobj = self.get_module(modulename) moduleobj = self.get_module(modulename)
if moduleobj is None: if moduleobj is None:
raise NoSuchModuleError(module=modulename) raise NoSuchModuleError('Module does not exist on this SEC-Node!')
pobj = moduleobj.accessibles.get(pname, None) pobj = moduleobj.accessibles.get(pname, None)
if pobj is None or not isinstance(pobj, Parameter): if pobj is None or not isinstance(pobj, Parameter):
raise NoSuchParameterError(module=modulename, parameter=pname) raise NoSuchParameterError('Module has no such parameter on this SEC-Node!')
readfunc = getattr(moduleobj, u'read_%s' % pname, None) readfunc = getattr(moduleobj, u'read_%s' % pname, None)
if readfunc: if readfunc:
@ -253,155 +262,107 @@ class Dispatcher(object):
def handle_request(self, conn, msg): def handle_request(self, conn, msg):
"""handles incoming request """handles incoming request
will call 'queue.request(data)' on conn to send reply before returning will call 'queue_async_request(data)' on conn or return reply
""" """
self.log.debug(u'Dispatcher: handling msg: %r' % msg) self.log.debug(u'Dispatcher: handling msg: %s' % repr(msg))
# if there was an error in the frontend, bounce the resulting
# error msgObj directly back to the client
if msg.errorclass:
return msg
# play thread safe ! # play thread safe !
# XXX: ONLY ONE REQUEST (per dispatcher) AT A TIME
with self._lock: with self._lock:
if msg.action == IDENTREQUEST: action, specifier, data = msg
self.log.debug(u'Looking for handle_ident') # special case for *IDN?
handler = self.handle_ident if action == IDENTREQUEST:
else: action, specifier, data = 'ident', None, None
self.log.debug(u'Looking for handle_%s' % msg.action)
handler = getattr(self, u'handle_%s' % msg.action, None) self.log.debug(u'Looking for handle_%s' % action)
handler = getattr(self, u'handle_%s' % action, None)
if handler: if handler:
try: return handler(conn, specifier, data)
reply = handler(conn, msg)
if reply:
conn.queue_reply(reply)
return None
except SECOPError as err:
self.log.exception(err)
msg.set_error(err.name, unicode(err), {})#u'traceback': formatException(),
#u'extended_stack':formatExtendedStack()})
return msg
except (ValueError, TypeError) as err:
self.log.exception(err)
msg.set_error(u'BadValue', unicode(err), {u'traceback': formatException()})
print(u'--------------------')
print(formatExtendedStack())
print(u'====================')
return msg
except Exception as err:
self.log.exception(err)
msg.set_error(u'InternalError', unicode(err), {u'traceback': formatException()})
print(u'--------------------')
print(formatExtendedStack())
print(u'====================')
return msg
else: else:
self.log.error(u'Can not handle msg %r' % msg) raise InternalError('unhandled message!')
msg.set_error(u'Protocol', u'unhandled msg', {})
return msg
# now the (defined) handlers for the different requests # now the (defined) handlers for the different requests
def handle_help(self, conn, msg): def handle_help(self, conn, specifier, data):
msg.mkreply() self.log.error('should have been handled in the interface!')
return msg
def handle_ident(self, conn, msg): def handle_ident(self, conn, specifier, data):
msg.mkreply() return (IDENTREPLY, None, None)
return msg
def handle_describe(self, conn, msg): def handle_describe(self, conn, specifier, data):
# XXX:collect descriptive data return (DESCRIPTIONREPLY, '.', self.get_descriptive_data())
msg.setvalue(u'specifier', u'.')
msg.setvalue(u'data', self.get_descriptive_data())
msg.mkreply()
return msg
def handle_read(self, conn, msg): def handle_read(self, conn, specifier, data):
# XXX: trigger polling and force sending event if data:
if not msg.parameter: raise ProtocolError('poll request don\'t take data!')
msg.parameter = u'value' modulename, pname = specifier, u'value'
msg.set_result(*self._getParameterValue(msg.module, msg.parameter)) if ':' in specifier:
modulename, pname = specifier.split(':', 1)
# XXX: trigger polling and force sending event ???
return (EVENTREPLY, specifier, list(self._getParameterValue(modulename, pname)))
#if conn in self._active_connections: def handle_change(self, conn, specifier, data):
# return None # already send to myself modulename, pname = specifier, u'value'
#if conn in self._subscriptions.get(msg.module, set()): if ':' in specifier:
# return None # already send to myself modulename, pname = specifier.split(u':', 1)
msg.mkreply() return (WRITEREPLY, specifier, list(self._setParameterValue(modulename, pname, data)))
return msg # send reply to inactive conns
def handle_change(self, conn, msg): def handle_do(self, conn, specifier, data):
# try to actually write XXX: should this be done asyncron? we could
# just return the reply in that case
if not msg.parameter:
msg.parameter = u'target'
msg.set_result(*self._setParameterValue(msg.module, msg.parameter, msg.data))
#if conn in self._active_connections:
# return None # already send to myself
#if conn in self._subscriptions.get(msg.module, set()):
# return None # already send to myself
msg.mkreply()
return msg # send reply to inactive conns
def handle_do(self, conn, msg):
# XXX: should this be done asyncron? we could just return the reply in # XXX: should this be done asyncron? we could just return the reply in
# that case # that case
if not msg.args: modulename, cmd = specifier.split(u':', 1)
msg.args = [] return (COMMANDREPLY, specifier, list(self._execute_command(modulename, cmd, data)))
# try to actually execute command
msg.set_result(*self._execute_command(msg.module, msg.command, msg.args))
#if conn in self._active_connections: def handle_ping(self, conn, specifier, data):
# return None # already send to myself if data:
#if conn in self._subscriptions.get(msg.module, set()): raise ProtocolError('poll request don\'t take data!')
# return None # already send to myself return (HEARTBEATREPLY, specifier, [None, {u't':currenttime()}])
msg.mkreply()
return msg # send reply to inactive conns
def handle_ping(self, conn, msg): def handle_activate(self, conn, specifier, data):
msg.setvalue(u'data', {u't':currenttime()}) if data:
msg.mkreply() raise ProtocolError('activate request don\'t take data!')
return msg if specifier:
modulename, pname = specifier, None
def handle_activate(self, conn, msg): if ':' in specifier:
if msg.module: modulename, pname = specifier.split(u':', 1)
if msg.module not in self._modules: if modulename not in self._export:
raise NoSuchModuleError() raise NoSuchModuleError('Module does not exist on this SEC-Node!')
if pname and pname not in self.get_module(modulename).accessibles:
# what if we try to subscribe a command here ???
raise NoSuchParameterError('Module has no such parameter on this SEC-Node!')
# activate only ONE module # activate only ONE module
self.subscribe(conn, msg.specifier, u'') self.subscribe(conn, modulename, pname)
modules = [msg.specifier] modules = [(modulename, pname)]
else: else:
# activate all modules # activate all modules
self._active_connections.add(conn) self._active_connections.add(conn)
modules = self._modules modules = [(m, None) for m in self._export]
# send updates for all values. The first poll already happend before the server is active # send updates for all subscribed values.
for modulename in modules: # note: The initial poll already happend before the server is active
for modulename, pname in modules:
moduleobj = self._modules.get(modulename, None) moduleobj = self._modules.get(modulename, None)
if moduleobj is None: if pname:
self.log.error(u'activate: can not lookup module %r, skipping it' % modulename) pobj = moduleobj.accessibles[pname]
updmsg = (EVENTREPLY, u'%s:%s' % (modulename, pname),
[pobj.export_value(), dict(t=pobj.timestamp)])
conn.queue_async_reply(updmsg)
continue continue
for pname, pobj in moduleobj.accessibles.items(): for pname, pobj in moduleobj.accessibles.items(): # pylint: disable=redefined-outer-name
if not isinstance(pobj, Parameter): if not isinstance(pobj, Parameter):
continue continue
if not pobj.export: # XXX: handle export_as cases! if not pobj.export: # XXX: handle export_as cases!
continue continue
# can not use announce_update here, as this will send to all clients # can not use announce_update here, as this will send to all clients
updmsg = Message(EVENTREPLY, module=moduleobj.name, parameter=pname) updmsg = (EVENTREPLY, u'%s:%s' % (modulename, pname),
updmsg.set_result(pobj.export_value(), dict(t=pobj.timestamp)) [pobj.export_value(), dict(t=pobj.timestamp)])
conn.queue_async_reply(updmsg) conn.queue_async_reply(updmsg)
msg.mkreply() return (ENABLEEVENTSREPLY, specifier, None) if specifier else (ENABLEEVENTSREPLY, None, None)
conn.queue_async_reply(msg) # should be sent AFTER all the ^^initial updates
return None
def handle_deactivate(self, conn, msg): def handle_deactivate(self, conn, specifier, data):
if msg.specifier: if specifier:
self.unsubscribe(conn, msg.specifier, u'') self.unsubscribe(conn, specifier)
else: else:
self._active_connections.discard(conn) self._active_connections.discard(conn)
# XXX: also check all entries in self._subscriptions? # XXX: also check all entries in self._subscriptions?
msg.mkreply() return (DISABLEEVENTSREPLY, None, None)
return msg
def handle_error(self, conn, msg):
# is already an error-reply (came from interface frontend) -> just send it back
return msg

View File

@ -19,12 +19,47 @@
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de> # Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
# #
# ***************************************************************************** # *****************************************************************************
"""provide server interfaces to be used by clients"""
from __future__ import absolute_import
from .tcp import TCPServer import json
INTERFACES = {'tcp': TCPServer, } EOL = b'\n'
SPACE = b' '
# for 'from protocol.interface import *' to only import the dict def encode_msg_frame(action, specifier=None, data=None):
__ALL__ = ['INTERFACES'] """ encode a msg_tripel into an msg_frame, ready to be sent
action (and optional specifier) are unicode strings,
data may be an json-yfied python object"""
action = action.encode('utf-8')
if specifier is None:
# implicit: data is None
return b''.join((action, EOL))
specifier = specifier.encode('utf-8')
if data:
data = json.dumps(data).encode('utf-8')
return b''.join((action, SPACE, specifier, SPACE, data, EOL))
return b''.join((action, SPACE, specifier, EOL))
def get_msg(_bytes):
"""try to deframe the next msg in (binary) input
always return a tupel (msg, remaining_input)
msg may also be None
"""
if EOL not in _bytes:
return None, _bytes
return _bytes.split(EOL, 1)
def decode_msg(msg):
"""decode the (binary) msg into a (unicode) msg_tripel"""
# check for leading/trailing CR and remove it
res = msg.split(b' ', 2)
action = res[0].decode('utf-8')
if len(res) == 1:
return action, None, None
specifier = res[1].decode('utf-8')
if len(res) == 2:
return action, specifier, None
data = json.loads(res[2].decode('utf-8'))
return action, specifier, data

View File

@ -30,61 +30,19 @@ except ImportError:
import SocketServer as socketserver # py2 import SocketServer as socketserver # py2
from secop.lib import formatExtendedStack, formatException from secop.lib import formatExtendedStack, formatException
from secop.protocol.messages import HELPREPLY, Message, HelpMessage from secop.protocol.messages import HELPREQUEST, HELPREPLY, HelpMessage
from secop.errors import SECoPError from secop.errors import SECoPError
from secop.protocol.interface import encode_msg_frame, get_msg, decode_msg
DEF_PORT = 10767 DEF_PORT = 10767
MAX_MESSAGE_SIZE = 1024 MESSAGE_READ_SIZE = 1024
EOL = b'\n'
CR = b'\r' CR = b'\r'
SPACE = b' ' SPACE = b' '
def encode_msg_frame(action, specifier=None, data=None):
""" encode a msg_tripel into an msg_frame, ready to be sent
action (and optional specifier) are unicode strings,
data may be an json-yfied python object"""
action = action.encode('utf-8')
if specifier is None:
# implicit: data is None
return b''.join((action, EOL))
specifier = specifier.encode('utf-8')
if data:
data = data.encode('utf-8')
return b''.join((action, SPACE, specifier, SPACE, data, EOL))
return b''.join((action, SPACE, specifier, EOL))
def get_msg(_bytes):
"""try to deframe the next msg in (binary) input
always return a tupel (msg, remaining_input)
msg may also be None
"""
if EOL not in _bytes:
return None, _bytes
return _bytes.split(EOL, 1)
def decode_msg(msg):
"""decode the (binary) msg into a (unicode) msg_tripel"""
# check for leading/trailing CR and remove it
if msg and msg[0] == CR:
msg = msg[1:]
if msg and msg[-1] == CR:
msg = msg[:-1]
res = msg.split(b' ', 2)
action = res[0].decode('utf-8')
if len(res) == 1:
return action, None, None
specifier = res[1].decode('utf-8')
if len(res) == 2:
return action, specifier, None
data = res[2].decode('utf-8')
return action, specifier, data
class TCPRequestHandler(socketserver.BaseRequestHandler): class TCPRequestHandler(socketserver.BaseRequestHandler):
@ -118,10 +76,11 @@ class TCPRequestHandler(socketserver.BaseRequestHandler):
# put frame(s) into framer to get bytestring # put frame(s) into framer to get bytestring
# send bytestring # send bytestring
outmsg = self._queue.popleft() outmsg = self._queue.popleft()
#outmsg.mkreply() if not outmsg:
outdata = encode_msg_frame(*outmsg.serialize()) outmsg = ('error','InternalError', ['<unknown origin>', 'trying to send none-data', {}])
# outframes = self.encoding.encode(outmsg) if len(outmsg) > 3:
# outdata = self.framing.encode(outframes) outmsg = ('error', 'InternalError', ['<unknown origin>', 'bad message format', {'msg':outmsg}])
outdata = encode_msg_frame(*outmsg)
try: try:
mysocket.sendall(outdata) mysocket.sendall(outdata)
except Exception: except Exception:
@ -129,7 +88,7 @@ class TCPRequestHandler(socketserver.BaseRequestHandler):
# XXX: improve: use polling/select here? # XXX: improve: use polling/select here?
try: try:
newdata = mysocket.recv(MAX_MESSAGE_SIZE) newdata = mysocket.recv(MESSAGE_READ_SIZE)
if not newdata: if not newdata:
# no timeout error, but no new data -> connection closed # no timeout error, but no new data -> connection closed
return return
@ -150,33 +109,39 @@ class TCPRequestHandler(socketserver.BaseRequestHandler):
origin, data = get_msg(data) origin, data = get_msg(data)
if origin is None: if origin is None:
break # no more messages to process break # no more messages to process
if not origin: # empty string -> send help message origin = origin.strip()
if origin and origin[0] == CR:
origin = origin[1:]
if origin and origin[-1] == CR:
origin = origin[:-1]
if origin in (HELPREQUEST, ''): # empty string -> send help message
for idx, line in enumerate(HelpMessage.splitlines()): for idx, line in enumerate(HelpMessage.splitlines()):
msg = Message(HELPREPLY, specifier='%d' % idx) self.queue_async_reply((HELPREPLY, '%d' % (idx+1), line))
msg.data = line
self.queue_async_reply(msg)
continue continue
msg = decode_msg(origin) msg = decode_msg(origin)
# construct msgObj from msg result = None
try: try:
msgObj = Message(*msg) result = serverobj.dispatcher.handle_request(self, msg)
msgObj.origin = origin.decode('latin-1') if (msg[0] == 'read') and result:
msgObj = serverobj.dispatcher.handle_request(self, msgObj) # read should only trigger async_replies
self.queue_async_reply(('error', 'InternalError', [origin,
'read should only trigger async data units']))
except SECoPError as err: except SECoPError as err:
msgObj.set_error(err.name, str(err), {'exception': formatException(), result = ('error', err.name, [origin, str(err), {'exception': formatException(),
'traceback': formatExtendedStack()}) 'traceback': formatExtendedStack()}])
except Exception as err: except Exception as err:
# create Error Obj instead # create Error Obj instead
msgObj.set_error(u'Internal', str(err), {'exception': formatException(), result = ('error', 'InternalError', [origin, str(err), {'exception': formatException(),
'traceback':formatExtendedStack()}) 'traceback': formatExtendedStack()}])
print('--------------------') print('--------------------')
print(formatException()) print(formatException())
print('--------------------') print('--------------------')
print(formatExtendedStack()) print(formatExtendedStack())
print('====================') print('====================')
if msgObj: if not result:
self.queue_reply(msgObj) self.log.error('empty result upon msg %s' % repr(msg))
self.queue_async_reply(result)
def queue_async_reply(self, data): def queue_async_reply(self, data):
"""called by dispatcher for async data units""" """called by dispatcher for async data units"""
@ -185,14 +150,6 @@ class TCPRequestHandler(socketserver.BaseRequestHandler):
else: else:
self.log.error('should async_queue empty data!') self.log.error('should async_queue empty data!')
def queue_reply(self, data):
"""called by dispatcher to queue (sync) replies"""
# sync replies go first!
if data:
self._queue.appendleft(data)
else:
self.log.error('should queue empty data!')
def finish(self): def finish(self):
"""called when handle() terminates, i.e. the socket closed""" """called when handle() terminates, i.e. the socket closed"""
self.log.info('closing connection from %s:%d' % self.client_address) self.log.info('closing connection from %s:%d' % self.client_address)
@ -211,16 +168,17 @@ class TCPServer(socketserver.ThreadingTCPServer):
daemon_threads = True daemon_threads = True
allow_reuse_address = True allow_reuse_address = True
def __init__(self, logger, interfaceopts, dispatcher): def __init__(self, name, logger, options, srv):
self.dispatcher = dispatcher self.dispatcher =srv.dispatcher
self.name = name
self.log = logger self.log = logger
bindto = interfaceopts.pop('bindto', 'localhost') bindto = options.pop('bindto', 'localhost')
portnum = int(interfaceopts.pop('bindport', DEF_PORT)) portnum = int(options.pop('bindport', DEF_PORT))
if ':' in bindto: if ':' in bindto:
bindto, _port = bindto.rsplit(':') bindto, _port = bindto.rsplit(':')
portnum = int(_port) portnum = int(_port)
self.log.info("TCPServer binding to %s:%d" % (bindto, portnum)) self.log.info("TCPServer %s binding to %s:%d" % (name, bindto, portnum))
socketserver.ThreadingTCPServer.__init__( socketserver.ThreadingTCPServer.__init__(
self, (bindto, portnum), TCPRequestHandler, bind_and_activate=True) self, (bindto, portnum), TCPRequestHandler, bind_and_activate=True)
self.log.info("TCPServer initiated") self.log.info("TCPServer initiated")

View File

@ -22,9 +22,6 @@
"""Define SECoP Messages""" """Define SECoP Messages"""
from __future__ import print_function from __future__ import print_function
import json
from secop.protocol.errors import EXCEPTIONS
# allowed actions: # allowed actions:
IDENTREQUEST = u'*IDN?' # literal IDENTREQUEST = u'*IDN?' # literal
@ -76,120 +73,6 @@ REQUEST2REPLY = {
class Message(object):
"""base class for messages"""
origin = u'<unknown source>'
action = u'<unknown message type>'
specifier = None
data = None
# cooked versions
module = None
parameter = None
command = None
args = None
# if set, these are used for generating the reply
qualifiers = None # will be rectified to dict() in __init__
value = None # also the result of a command
# if set, these are used for generating the error msg
errorclass = '' # -> specifier
errordescription = '' # -> data[1] (data[0] is origin)
errorinfo = {} # -> data[2]
def __init__(self, action, specifier=None, data=None, **kwds):
self.qualifiers = {}
self.action = action
if data:
data = json.loads(data)
if specifier:
self.module = specifier
self.specifier = specifier
if ':' in specifier:
self.module, p = specifier.split(':',1)
if action in (COMMANDREQUEST, COMMANDREPLY):
self.command = p
# XXX: extract args?
self.args = data
else:
self.parameter = p
if data is not None:
self.data = data
elif data is not None:
self.data = data
# record extra values
self.__arguments = set()
for k, v in kwds.items():
self.setvalue(k, v)
def setvalue(self, key, value):
setattr(self, key, value)
self.__arguments.add(key)
def setqualifier(self, key, value):
self.qualifiers[key] = value
def __repr__(self):
return u'Message(%r' % self.action + \
u', '.join('%s=%s' % (k, repr(getattr(self, k)))
for k in sorted(self.__arguments)) + u')'
def serialize(self):
"""return <action>,<specifier>,<jsonyfied_data> triple"""
if self.errorclass:
for k in self.__arguments:
if k in (u'origin', u'errorclass', u'errorinfo', u'errordescription'):
if k in self.errorinfo:
del self.errorinfo[k]
continue
self.errorinfo[k] = getattr(self, k)
data = [self.origin, self.errordescription, self.errorinfo]
print(repr(data))
return ERRORREPLY, self.errorclass, json.dumps(data)
elif self.value or self.qualifiers:
data = [self.value, self.qualifiers]
else:
data = self.data
try:
data = json.dumps(data) if data else u''
except TypeError:
print('Can not serialze: %s' % repr(data))
data = u'none'
if self.specifier:
specifier = self.specifier
else:
specifier = self.module
if self.parameter:
specifier = u'%s:%s' %(self.module, self.parameter)
if self.command:
specifier = u'%s:%s' %(self.module, self.command)
return self.action, specifier, data
def mkreply(self):
self.action = REQUEST2REPLY.get(self.action, self.action)
def set_error(self, errorclass, errordescription, errorinfo):
if errorclass not in EXCEPTIONS:
errordescription = '%s is not an official errorclass!\n%s' % (errorclass, errordescription)
errorclass = u'Internal'
# used to mark thes as an error message
# XXX: check errorclass for allowed values !
self.setvalue(u'errorclass', errorclass) # a str
self.setvalue(u'errordescription', errordescription) # a str
self.setvalue(u'errorinfo', errorinfo) # a dict
self.action = ERRORREPLY
def set_result(self, value, qualifiers):
# used to mark thes as an result reply message
self.setvalue(u'value', value)
self.qualifiers.update(qualifiers)
self.__arguments.add(u'qualifier')
HelpMessage = u"""Try one of the following: HelpMessage = u"""Try one of the following:
'%s' to query protocol version '%s' to query protocol version
'%s' to read the description '%s' to read the description

View File

@ -27,6 +27,7 @@ import os
import ast import ast
import time import time
import threading import threading
from collections import OrderedDict
try: try:
import configparser # py3 import configparser # py3
@ -46,13 +47,22 @@ except ImportError:
import daemon.pidfile as pidlockfile import daemon.pidfile as pidlockfile
from secop.lib import get_class, formatException, getGeneralConfig from secop.lib import get_class, formatException, getGeneralConfig
from secop.protocol.dispatcher import Dispatcher
from secop.protocol.interface import INTERFACES
from secop.errors import ConfigError from secop.errors import ConfigError
class Server(object):
class Server(object):
# list allowed section prefixes
# if mapped dict does not exist -> section need a 'class' option
# otherwise a 'type' option is evaluatet and the class from the mapping dict used
#
# IMPORTANT: keep he order! (node MUST be first, as the others are referencing it!)
CFGSECTIONS = [
# section_prefix, default type, mapping of selectable classes
('node', None, {None: "protocol.dispatcher.Dispatcher"}),
('module', None, None),
('interface', "tcp", {"tcp": "protocol.interface.tcp.TCPServer"}),
]
def __init__(self, name, parent_logger=None): def __init__(self, name, parent_logger=None):
cfg = getGeneralConfig() cfg = getGeneralConfig()
@ -97,9 +107,9 @@ class Server(object):
self.log.info(u'startup done, handling transport messages') self.log.info(u'startup done, handling transport messages')
self._threads = set() self._threads = set()
for _if in self._interfaces: for ifname, ifobj in self.interfaces.items():
self.log.debug(u'starting thread for interface %r' % _if) self.log.debug(u'starting thread for interface %r' % ifname)
t = threading.Thread(target=_if.serve_forever) t = threading.Thread(target=ifobj.serve_forever)
t.daemon = True t.daemon = True
t.start() t.start()
self._threads.add(t) self._threads.add(t)
@ -122,108 +132,76 @@ class Server(object):
self.log.error(u'Couldn\'t read cfg file !') self.log.error(u'Couldn\'t read cfg file !')
raise ConfigError(u'Couldn\'t read cfg file %r' % self._cfgfile) raise ConfigError(u'Couldn\'t read cfg file %r' % self._cfgfile)
self._interfaces = []
moduleopts = []
interfaceopts = [] for kind, devtype, classmapping in self.CFGSECTIONS:
equipment_id = None kinds = u'%ss' % kind
nodeopts = [] objs = OrderedDict()
self.__dict__[kinds] = objs
for section in parser.sections(): for section in parser.sections():
if section.lower().startswith(u'module '): prefix = u'%s ' % kind
# module section if section.lower().startswith(prefix):
# omit leading 'module ' string name = section[len(prefix):]
devname = section[len(u'module '):] opts = dict(item for item in parser.items(section))
devopts = dict(item for item in parser.items(section)) if u'class' in opts:
if u'class' not in devopts: cls = opts.pop(u'class')
self.log.error(u'Module %s needs a class option!') else:
raise ConfigError( if not classmapping:
u'cfgfile %r: Module %s needs a class option!' % self.log.error(u'%s %s needs a class option!' % (kind.title(), name))
(self._cfgfile, devname)) raise ConfigError(u'cfgfile %r: %s %s needs a class option!' %
(self._cfgfile, kind.title(), name))
type_ = opts.pop(u'type', devtype)
cls = classmapping.get(type_, None)
if not cls:
self.log.error(u'%s %s needs a type option (select one of %s)!' %
(kind.title(), name, ', '.join(repr(r) for r in classmapping)))
raise ConfigError(u'cfgfile %r: %s %s needs a type option (select one of %s)!' %
(self._cfgfile, kind.title(), name, ', '.join(repr(r) for r in classmapping)))
# MAGIC: transform \n.\n into \n\n which are normally stripped # MAGIC: transform \n.\n into \n\n which are normally stripped
# by the ini parser # by the ini parser
for k in devopts: for k in opts:
v = devopts[k] v = opts[k]
while u'\n.\n' in v: while u'\n.\n' in v:
v = v.replace(u'\n.\n', u'\n\n') v = v.replace(u'\n.\n', u'\n\n')
devopts[k] = v
# try to import the class, raise if this fails
devopts[u'class'] = get_class(devopts[u'class'])
# all went well so far
moduleopts.append([devname, devopts])
if section.lower().startswith(u'interface '):
# interface section
# omit leading 'interface ' string
ifname = section[len(u'interface '):]
ifopts = dict(item for item in parser.items(section))
if u'interface' not in ifopts:
self.log.error(u'Interface %s needs an interface option!')
raise ConfigError(
u'cfgfile %r: Interface %s needs an interface option!' %
(self._cfgfile, ifname))
# all went well so far
interfaceopts.append([ifname, ifopts])
if section.lower().startswith(u'equipment ') or section.lower().startswith(u'node '):
if equipment_id is not None:
raise ConfigError(u'cfgfile %r: only one [node <id>] section allowed, found another [%s]!' % (
self._cfgfile, section))
# equipment/node settings
equipment_id = section.split(u' ', 1)[1].replace(u' ', u'_')
nodeopts = dict(item for item in parser.items(section))
nodeopts[u'equipment_id'] = equipment_id
nodeopts[u'id'] = equipment_id
# MAGIC: transform \n.\n into \n\n which are normally stripped
# by the ini parser
for k in nodeopts:
v = nodeopts[k]
while u'\n.\n' in v:
v = v.replace(u'\n.\n', u'\n\n')
nodeopts[k] = v
if equipment_id is None:
self.log.error(u'Need a [node <id>] section, none found!')
raise ConfigError(
u'cfgfile %r: need an [node <id>] option!' % (self._cfgfile))
self._dispatcher = self._buildObject(
u'Dispatcher', Dispatcher, nodeopts)
self._processInterfaceOptions(interfaceopts)
self._processModuleOptions(moduleopts)
def _processModuleOptions(self, moduleopts):
# check modules opts by creating them
devs = []
for devname, devopts in moduleopts:
devclass = devopts.pop(u'class')
# create module
self.log.debug(u'Creating Module %r' % devname)
export = devopts.pop(u'export', u'1')
export = export.lower() in (u'1', u'on', u'true', u'yes')
if u'default' in devopts:
devopts[u'value'] = devopts.pop(u'default')
# strip '"
for k, v in devopts.items():
try: try:
devopts[k] = ast.literal_eval(v) opts[k] = ast.literal_eval(v)
except Exception: except Exception:
pass pass
devobj = devclass( opts[k] = v
self.log.getChild(devname), devopts, devname, self._dispatcher)
devs.append([devname, devobj, export])
# connect modules with dispatcher # try to import the class, raise if this fails
for devname, devobj, export in devs: self.log.debug(u'Creating %s %s ...' % (kind.title(), name))
self.log.info(u'registering module %r' % devname) # cls.__init__ should pop all used args from options!
self._dispatcher.register_module(devobj, devname, export) logname = u'dispatcher' if kind == u'node' else u'%s_%s' % (kind, name.lower())
obj = get_class(cls)(name, self.log.getChild(logname), opts, self)
if opts:
raise ConfigError(u'%s %s: class %s: don\'t know how to handle option(s): %s' %
(kind, name, cls, u', '.join(opts)))
# all went well so far
objs[name] = obj
# following line is the reason for 'node' beeing the first entry in CFGSECTIONS
if len(self.nodes) != 1:
raise ConfigError(u'cfgfile %r: needs exactly one node section!' % self._cfgfile)
self.dispatcher = self.nodes.values()[0]
# all objs created, now start them up and interconnect
for modname, modobj in self.modules.items():
self.log.info(u'registering module %r' % modname)
self.dispatcher.register_module(modobj, modname, modobj.properties['export'])
# also call early_init on the modules # also call early_init on the modules
devobj.early_init() modobj.early_init()
# call init on each module after registering all # call init on each module after registering all
for _devname, devobj, _export in devs: for modname, modobj in self.modules.items():
devobj.init_module() modobj.init_module()
starting_modules = set() starting_modules = set()
finished_modules = Queue() finished_modules = Queue()
for _devname, devobj, _export in devs: for modname, modobj in self.modules.items():
starting_modules.add(devobj) starting_modules.add(modobj)
devobj.start_module(started_callback=finished_modules.put) modobj.start_module(started_callback=finished_modules.put)
# remark: it is the module implementors responsibility to call started_callback # remark: it is the module implementors responsibility to call started_callback
# within reasonable time (using timeouts). If we find later, that this is not # within reasonable time (using timeouts). If we find later, that this is not
# enough, we might insert checking for a timeout here, and somehow set the remaining # enough, we might insert checking for a timeout here, and somehow set the remaining
@ -234,22 +212,3 @@ class Server(object):
# use discard instead of remove here, catching the case when started_callback is called twice # use discard instead of remove here, catching the case when started_callback is called twice
starting_modules.discard(finished) starting_modules.discard(finished)
finished_modules.task_done() finished_modules.task_done()
def _processInterfaceOptions(self, interfaceopts):
# eval interfaces
self._interfaces = []
for ifname, ifopts in interfaceopts:
ifclass = ifopts.pop(u'interface')
ifclass = INTERFACES[ifclass]
interface = self._buildObject(ifname, ifclass, ifopts,
self._dispatcher)
self._interfaces.append(interface)
def _buildObject(self, name, cls, options, *args):
self.log.debug(u'Creating %s ...' % name)
# cls.__init__ should pop all used args from options!
obj = cls(self.log.getChild(name.lower()), options, *args)
if options:
raise ConfigError(u'%s: don\'t know how to handle option(s): %s' %
(cls.__name__, u', '.join(options)))
return obj

View File

@ -20,13 +20,16 @@
# #
# ***************************************************************************** # *****************************************************************************
"""test base client.""" """test base client."""
from __future__ import print_function
import sys
from os import path
sys.path.insert(0, path.abspath(path.join(path.dirname(__file__), '..')))
from collections import OrderedDict
import pytest import pytest
import sys
sys.path.insert(0, sys.path[0] + '/..')
from collections import OrderedDict
from secop.client.baseclient import Client from secop.client.baseclient import Client
# define Test-only connection object # define Test-only connection object
@ -52,6 +55,7 @@ def clientobj(request):
print (" TEARDOWN ClientObj") print (" TEARDOWN ClientObj")
# pylint: disable=redefined-outer-name
def test_describing_data_decode(clientobj): def test_describing_data_decode(clientobj):
assert OrderedDict( assert OrderedDict(
[('a', 1)]) == clientobj._decode_list_to_ordereddict(['a', 1]) [('a', 1)]) == clientobj._decode_list_to_ordereddict(['a', 1])

View File

@ -22,7 +22,8 @@
"""test data types.""" """test data types."""
import sys import sys
sys.path.insert(0, sys.path[0] + '/..') from os import path
sys.path.insert(0, path.abspath(path.join(path.dirname(__file__), '..')))
# no fixtures needed # no fixtures needed
import pytest import pytest

View File

@ -22,7 +22,8 @@
"""test Enum type.""" """test Enum type."""
import sys import sys
sys.path.insert(0, sys.path[0] + '/..') from os import path
sys.path.insert(0, path.abspath(path.join(path.dirname(__file__), '..')))
# no fixtures needed # no fixtures needed
import pytest import pytest

View File

@ -23,7 +23,8 @@
from __future__ import print_function from __future__ import print_function
import sys import sys
sys.path.insert(0, sys.path[0] + '/..') from os import path
sys.path.insert(0, path.abspath(path.join(path.dirname(__file__), '..')))
# no fixtures needed # no fixtures needed
import pytest import pytest
@ -50,7 +51,11 @@ def test_Communicator():
announce_update = lambda self, m, pn, pv: print('%s:%s=%r' % (m.name, pn, pv)), announce_update = lambda self, m, pn, pv: print('%s:%s=%r' % (m.name, pn, pv)),
))() ))()
o = Communicator(logger, {}, 'o1', dispatcher) srv = type('ServerStub', (object,), dict(
dispatcher = dispatcher,
))()
o = Communicator('communicator',logger, {}, srv)
o.early_init() o.early_init()
o.init_module() o.init_module()
q = queue.Queue() q = queue.Queue()
@ -97,8 +102,12 @@ def test_ModuleMeta():
announce_update = lambda self, m, pn, pv: print('%s:%s=%r' % (m.name, pn, pv)), announce_update = lambda self, m, pn, pv: print('%s:%s=%r' % (m.name, pn, pv)),
))() ))()
o1 = newclass(logger, {}, 'o1', dispatcher) srv = type('ServerStub', (object,), dict(
o2 = newclass(logger, {}, 'o1', dispatcher) dispatcher = dispatcher,
))()
o1 = newclass('o1', logger, {}, srv)
o2 = newclass('o2', logger, {}, srv)
params_found= set() params_found= set()
ctr_found = set() ctr_found = set()
for obj in [o1, o2]: for obj in [o1, o2]:

View File

@ -22,7 +22,8 @@
"""test data types.""" """test data types."""
import sys import sys
sys.path.insert(0, sys.path[0] + '/..') from os import path
sys.path.insert(0, path.abspath(path.join(path.dirname(__file__), '..')))
# no fixtures needed # no fixtures needed
import pytest import pytest

View File

@ -22,7 +22,8 @@
"""test data types.""" """test data types."""
import sys import sys
sys.path.insert(0, sys.path[0] + '/..') from os import path
sys.path.insert(0, path.abspath(path.join(path.dirname(__file__), '..')))
from collections import OrderedDict from collections import OrderedDict