improve frappy server management

- do connect in background when the frappy server is not running
  on startup
This commit is contained in:
2023-09-20 10:02:09 +02:00
parent 1e2721579d
commit ba0f4e62b6
2 changed files with 184 additions and 83 deletions

View File

@ -86,32 +86,31 @@ def cleanup_defunct():
def all_info(all_cfg, prefix='currently configured: '):
info = []
addkwd = False
info = []
for srv in SERVICES:
cfginfo = all_cfg.get(srv)
if cfginfo is None:
cfglist = all_cfg.get(srv)
if cfglist is None:
addkwd = True
elif addkwd:
info.append('%s=%r' % (srv, cfginfo))
else:
info.append(repr(cfginfo))
if isinstance(cfglist, str):
cfglist = [cfglist]
cfginfo = ','.join(c if isinstance(c, str)
else f"<one of {', '.join(repr(v) for v in c)}>"
for c in cfglist)
if addkwd:
info.append('%s=%r' % (srv, cfginfo))
else:
info.append(repr(cfginfo))
return f"{prefix}frappy({', '.join(info)})"
def get_guess(allcfg):
guess = {}
seacfg = FrappyManager().cfg_from_sea(config.instrument)
guess.pop('confirmed', '')
for s in ('main', 'stick', 'addons'):
info = allcfg.get(s, '')
prev = info.split(' ', 1)[0]
if prev != info:
guess[s] = prev
fromsea = seacfg.get(s)
if fromsea and fromsea != prev:
guess[s] = fromsea
return guess
def get_frappy_config():
try:
return session.devices['frappy_config']
except KeyError:
session.log.error("'frappy_config' is not available - 'frappy' setup is not loaded")
return None
class FrappyConfig(Device):
@ -141,10 +140,16 @@ class FrappyConfig(Device):
meanings.remove('nodes')
_trigger_change = None
_previous_shown = None
_initial_config = None
_servers_loaded = False
def doInit(self, mode):
if mode != SIMULATION and session.sessiontype != POLLER:
self._trigger_change = threading.Event()
for name in self.nodes:
secnode = session.devices.get(name)
if secnode:
secnode.uri = ''
createThread('frappy change notification', self.handle_notifications)
def handle_notifications(self):
@ -156,17 +161,42 @@ class FrappyConfig(Device):
if self._trigger_change.is_set():
continue
try:
current = self.check_or_start()
if current != self._previous_shown:
cfgs = self.check_services()
guess_info = self.to_consider(cfgs)
if (cfgs, guess_info) != self._previous_shown and (guess_info or not self._servers_loaded):
cmd = 'frappy_changed()'
controller.new_request(ScriptRequest(cmd, None, User('guest', USER)))
except RequestError as e:
session.log.error(f'can not queue request {e!r}')
def check_or_start(self, main=None, stick=None, addons=None):
def to_consider(self, cfgs):
"""return info from sea and frappy servers
for a potential "please consider calling frappy(...)" message
"""
guess_info = {}
for service, guess in FrappyManager().guess_cfgs(config.instrument, cfgs).items():
proposed = guess.get('proposed')
if proposed:
guess_info[service] = proposed
else:
missing = guess.get('missing')
if missing:
guess_info[service] = [m + '?' for m in missing]
return guess_info
def check_services(self):
cfgs = {}
for secnodename in self.nodes:
secnode = session.devices.get(secnodename)
if secnode:
cfgs[secnode.service] = secnode.get_info()
return cfgs
def start_services(self, main=None, stick=None, addons=None):
"""start/stop frappy servers
for example: check_or_start(main='xy', stick='')
for example: start_services(main='xy', stick='')
- restart main server with cfg='xy'
- stop stick server
- do not touch addons server
@ -231,26 +261,78 @@ class FrappyConfig(Device):
self.set_envlist()
for secnode in remove_cfg:
secnode.disable()
return all_cfg, get_guess(all_cfg)
self._cache.put(self, 'config', all_cfg)
return all_cfg
def show_config(self, allcfg, guess):
def show_config(self, allcfg):
guess_info = self.to_consider(allcfg)
# remove 'frappy_changed()' commands in script queue
controller = session.daemon_device._controller
controller.block_requests(r['reqid'] for r in controller.get_queue() if r['script'] == 'frappy_changed()')
self._previous_shown = allcfg, guess
self._previous_shown = allcfg, guess_info
session.log.info(all_info(allcfg))
if guess:
info = all_info(guess, '')
if guess_info:
info = all_info(guess_info, '')
session.log.warning('please consider to call:')
session.log.info(info)
if '?' in info:
session.log.warning("but create cfg files first for items marked with '?'")
def initial_restart_cfg(self, service):
"""get cfg for (re)start of the service
returns:
cfg, when the server has to (re)started with a new cfg
True, when the server is running and does not need a restart
None, when the server is not running, but does not need a restart
"""
if self._servers_loaded:
return None
if self._initial_config is None:
success = True
fm = FrappyManager()
running = fm.get_cfg(config.instrument, None)
cache = self._getCache()
cfgs = {}
for serv, secnode in zip(fm.services, self.nodes):
cfg = running.get(serv)
if not cfg and cache:
cfg = cache.get(secnode, 'value')
if cfg:
cfgs[serv] = cfg
running_main = running.get('main')
if running_main and running_main != cfgs.get('main'):
# new main device: clear old stick
running_stick = running.get('stick')
if running_stick:
cfgs['stick'] = running_stick
else:
cfgs.pop('stick', None)
for serv, guess in fm.guess_cfgs(config.instrument, cfgs).items():
ok = guess.get('ok', [])
for proposed in guess.get('proposed', []):
if isinstance(proposed, str): # start only when ambiguous
ok.append(proposed)
else:
success = False
if ok:
cfgs[serv] = ','.join(ok)
if not success:
cfgs = {}
result = {}
for serv, cfg in cfgs.items():
runcfg = running.get(serv)
if runcfg != cfg:
result[serv] = cfg
elif runcfg:
result[serv] = True # restart not needed as cfg has not changed
self._initial_config = result
return self._initial_config.get(service)
def changed(self):
current = self.check_or_start()
#if current == self._previous_shown:
# return
self.show_config(*current)
self._servers_loaded = True
self.show_config(self.check_services())
def remove_aliases(self):
for meaning in self.meanings:
@ -279,7 +361,7 @@ class FrappyConfig(Device):
return result
def set_envlist(self):
"""create aliases for SECoP devices
"""create aliases and envlist for SECoP devices
depending on their meaning
"""
@ -355,7 +437,7 @@ class FrappyConfig(Device):
newenv[devname] = aliasname
break
else:
to_remove.union(aliasnames)
to_remove.update(aliasnames)
for aliasname in previous_aliases:
session.destroyDevice(aliasname)
@ -397,17 +479,30 @@ class FrappyNode(SecNodeDevice, Moveable):
def doStart(self, value):
if value == 'None':
value = None
self.restart(value, True) # frappy server will be restarted even when unchanged
self.restart(value)
def doInit(self, mode):
if mode == SIMULATION or session.sessiontype == POLLER:
super().doInit(mode)
else:
fc = session.devices.get('frappy_config')
if fc:
cfg = fc.initial_restart_cfg(self.service)
if isinstance(cfg, str): # may also be None or True
self.restart(cfg)
if cfg is None: # None means: server is not running, and does not need to be restarted
# connect in background, as the server might be started later
createThread('connect', self._connect)
# TODO: check if it is not better to add a try_period argument to SecNode._connect()
return
try:
self._connect()
except Exception:
pass
def doStop(self):
"""never busy"""
def doInit(self, mode):
if mode != SIMULATION and session.sessiontype != POLLER:
self._lastcfg = self.doRead()
self.restart(self._lastcfg, False) # do not restart when not changed
super().doInit(mode)
def doRead(self, maxage=0):
return self._cfgvalue or ''
@ -415,7 +510,9 @@ class FrappyNode(SecNodeDevice, Moveable):
cfg = self.read()
super().createDevices()
if cfg != self._lastcfg:
session.devices.get('frappy_config')._trigger_change.set()
fc = get_frappy_config()
if fc:
fc._trigger_change.set()
if self.param_category:
for devname, (_, devcfg) in self.setup_info.items():
params_cfg = devcfg['params_cfg']
@ -437,10 +534,9 @@ class FrappyNode(SecNodeDevice, Moveable):
cfg = self.read()
if self._lastcfg != cfg:
self._lastcfg = cfg
try:
session.devices.get('frappy_config')._trigger_change.set()
except AttributeError: # frappy_config does not exist
pass
fc = get_frappy_config()
if fc:
fc._trigger_change.set()
def disable(self):
seaconn = session.devices.get('sea_%s' % self.service)
@ -453,13 +549,10 @@ class FrappyNode(SecNodeDevice, Moveable):
code, text = status.DISABLED, 'disabled'
SecNodeDevice._set_status(self, code, text)
def restart(self, cfg=None, forced=True):
def restart(self, cfg=None):
"""restart frappy server
:param cfg: config for frappy server, if not given, restart with the same config
:param forced: True: restart anyway, force using cfg
False: try to get cfg (1) from sea, (2) from running frappy server, (3) from given cfg
when cfg has not changed, do not restart
"""
if cfg is None:
cfg = self._cfgvalue
@ -467,25 +560,8 @@ class FrappyNode(SecNodeDevice, Moveable):
fm = FrappyManager()
info = fm.get_ins_info(ins)
running_cfg = fm.get_cfg(ins, self.service) or ''
if not forced or cfg is None:
sea_cfg = fm.cfg_from_sea(ins).get(self.service, '')
if '?' in sea_cfg:
if sea_cfg == '?':
self.log.warning('undefined sea device')
else:
self.log.warning(f"missing frappy cfg file for {sea_cfg.replace('?', '')}")
cfg = '' # stop server
elif sea_cfg:
cfg = sea_cfg
elif running_cfg:
self._cfgvalue = running_cfg
return
if cfg == running_cfg:
self._cfgvalue = running_cfg
return
if cfg is None:
if forced:
self.log.error('can not restart - previous cfg unknown')
self.log.error('can not restart - previous cfg unknown')
return
if cfg != running_cfg:
self.disable()
@ -494,15 +570,15 @@ class FrappyNode(SecNodeDevice, Moveable):
fm.do_stop(ins, self.service)
is_cfg = cfg and ':' not in cfg
if is_cfg:
available_cfg = FrappyManager().all_cfg(config.instrument, self.service)
failed = False
available_cfg = None
for cfgitem in cfg.split(','):
if cfgitem not in available_cfg:
failed = True
if not fm.is_cfg(config.instrument, self.service, cfgitem):
if available_cfg is None:
available_cfg = fm.all_cfg(config.instrument, self.service)
suggestions = suggest(cfgitem, available_cfg)
if suggestions:
session.log.error('%s unknown, did you mean: %s' % (cfgitem, ', '.join(suggestions)))
if failed:
if available_cfg is not None:
raise ValueError('use "frappy_list()" to get a list of valid frappy configurations')
uri = 'localhost:%d' % info[self.service]
else: