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

@ -22,7 +22,8 @@
from nicos import session, config from nicos import session, config
from nicos.utils import printTable from nicos.utils import printTable
from nicos.commands import helparglist, usercommand from nicos.commands import helparglist, usercommand
from servicemanager import FrappyManager from .devices import get_frappy_config, all_info
from servicemanager import FrappyManager, SeaManager
SERVICES = FrappyManager.services SERVICES = FrappyManager.services
@ -30,8 +31,9 @@ SERVICES = FrappyManager.services
@usercommand @usercommand
def set_se_list(): def set_se_list():
frappy_config = session.devices['frappy_config'] fc = get_frappy_config()
frappy_config.set_envlist() if fc:
fc.set_envlist()
@usercommand @usercommand
@ -47,7 +49,11 @@ def frappy(*args, main=None, stick=None, addons=None, force=False):
- frappy('restart') # restart all frappy servers - frappy('restart') # restart all frappy servers
- frappy(stick='restart') # restart stick frappy server - frappy(stick='restart') # restart stick frappy server
""" """
confirmed = FrappyManager().cfg_from_sea(config.instrument).get('confirmed') fc = get_frappy_config()
if not fc:
return
stickarg = stick
confirmed = SeaManager().get_cfg(config.instrument, 'sea', True).split('/', 1)[0]
if args: if args:
if main is not None: if main is not None:
raise TypeError('got multiple values for main') raise TypeError('got multiple values for main')
@ -60,9 +66,8 @@ def frappy(*args, main=None, stick=None, addons=None, force=False):
if main == '': if main == '':
stick = '' # remove stick with main stick = '' # remove stick with main
else: else:
allsticks = FrappyManager().all_cfg(config.instrument, 'stick')
stickcfg = main + 'stick' stickcfg = main + 'stick'
if stickcfg in allsticks: if FrappyManager().is_cfg(config.instrument, 'stick', stickcfg):
# if a default stick is available, start this also # if a default stick is available, start this also
stick = stickcfg stick = stickcfg
else: else:
@ -75,12 +80,30 @@ def frappy(*args, main=None, stick=None, addons=None, force=False):
if addons is not None: if addons is not None:
raise TypeError('got multiple values for addons') raise TypeError('got multiple values for addons')
addons = ','.join(alist) addons = ','.join(alist)
elif main is None and stick is None and addons is None: # bare frappy() command
fc.show_config(fc.check_services())
return
if confirmed and confirmed != main and main not in (None, 'restart') and not force: if confirmed and confirmed != main and main not in (None, 'restart') and not force:
session.log.warning('%r is plugged to the cryostat control rack', confirmed) session.log.warning('%r is plugged to the cryostat control rack', confirmed)
session.log.warning('if you are sure, use frappy(..., force=True)', confirmed) cmd = all_info({'main': main, 'stick': stickarg, 'addons': addons}, '')[:-1] + ', force=True)'
session.log.warning(f'if you are sure, use: %s', cmd)
raise TypeError('refuse to override plugged device') raise TypeError('refuse to override plugged device')
frappy_config = session.devices['frappy_config'] fc.show_config(fc.start_services(main, stick, addons))
frappy_config.show_config(*frappy_config.check_or_start(main, stick, addons))
@usercommand
def frappy_main(*args):
raise NameError('frappy_main(<cfg>) is no longer avaiable, use frappy(<cfg>) instead')
@usercommand
def frappy_stick(*args):
raise NameError('frappy_stick(<cfg>) is no longer avaiable, use frappy(stick=<cfg>) instead')
@usercommand
def frappy_addons(*args):
raise NameError('frappy_addons(<cfg>) is no longer avaiable, use frappy(addons=<cfg>) instead')
@usercommand @usercommand
@ -108,4 +131,6 @@ def frappy_list(service=None):
@usercommand @usercommand
def frappy_changed(): def frappy_changed():
session.devices['frappy_config'].changed() fc = get_frappy_config()
if fc:
fc.changed()

View File

@ -86,32 +86,31 @@ def cleanup_defunct():
def all_info(all_cfg, prefix='currently configured: '): def all_info(all_cfg, prefix='currently configured: '):
info = []
addkwd = False addkwd = False
info = []
for srv in SERVICES: for srv in SERVICES:
cfginfo = all_cfg.get(srv) cfglist = all_cfg.get(srv)
if cfginfo is None: if cfglist is None:
addkwd = True addkwd = True
elif addkwd:
info.append('%s=%r' % (srv, cfginfo))
else: 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)})" return f"{prefix}frappy({', '.join(info)})"
def get_guess(allcfg): def get_frappy_config():
guess = {} try:
seacfg = FrappyManager().cfg_from_sea(config.instrument) return session.devices['frappy_config']
guess.pop('confirmed', '') except KeyError:
for s in ('main', 'stick', 'addons'): session.log.error("'frappy_config' is not available - 'frappy' setup is not loaded")
info = allcfg.get(s, '') return None
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
class FrappyConfig(Device): class FrappyConfig(Device):
@ -141,10 +140,16 @@ class FrappyConfig(Device):
meanings.remove('nodes') meanings.remove('nodes')
_trigger_change = None _trigger_change = None
_previous_shown = None _previous_shown = None
_initial_config = None
_servers_loaded = False
def doInit(self, mode): def doInit(self, mode):
if mode != SIMULATION and session.sessiontype != POLLER: if mode != SIMULATION and session.sessiontype != POLLER:
self._trigger_change = threading.Event() 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) createThread('frappy change notification', self.handle_notifications)
def handle_notifications(self): def handle_notifications(self):
@ -156,17 +161,42 @@ class FrappyConfig(Device):
if self._trigger_change.is_set(): if self._trigger_change.is_set():
continue continue
try: try:
current = self.check_or_start() cfgs = self.check_services()
if current != self._previous_shown: guess_info = self.to_consider(cfgs)
if (cfgs, guess_info) != self._previous_shown and (guess_info or not self._servers_loaded):
cmd = 'frappy_changed()' cmd = 'frappy_changed()'
controller.new_request(ScriptRequest(cmd, None, User('guest', USER))) controller.new_request(ScriptRequest(cmd, None, User('guest', USER)))
except RequestError as e: except RequestError as e:
session.log.error(f'can not queue request {e!r}') 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 """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' - restart main server with cfg='xy'
- stop stick server - stop stick server
- do not touch addons server - do not touch addons server
@ -231,26 +261,78 @@ class FrappyConfig(Device):
self.set_envlist() self.set_envlist()
for secnode in remove_cfg: for secnode in remove_cfg:
secnode.disable() 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 # remove 'frappy_changed()' commands in script queue
controller = session.daemon_device._controller controller = session.daemon_device._controller
controller.block_requests(r['reqid'] for r in controller.get_queue() if r['script'] == 'frappy_changed()') 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)) 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.warning('please consider to call:')
session.log.info(info) session.log.info(info)
if '?' in info: if '?' in info:
session.log.warning("but create cfg files first for items marked with '?'") 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): def changed(self):
current = self.check_or_start() self._servers_loaded = True
#if current == self._previous_shown: self.show_config(self.check_services())
# return
self.show_config(*current)
def remove_aliases(self): def remove_aliases(self):
for meaning in self.meanings: for meaning in self.meanings:
@ -279,7 +361,7 @@ class FrappyConfig(Device):
return result return result
def set_envlist(self): def set_envlist(self):
"""create aliases for SECoP devices """create aliases and envlist for SECoP devices
depending on their meaning depending on their meaning
""" """
@ -355,7 +437,7 @@ class FrappyConfig(Device):
newenv[devname] = aliasname newenv[devname] = aliasname
break break
else: else:
to_remove.union(aliasnames) to_remove.update(aliasnames)
for aliasname in previous_aliases: for aliasname in previous_aliases:
session.destroyDevice(aliasname) session.destroyDevice(aliasname)
@ -397,17 +479,30 @@ class FrappyNode(SecNodeDevice, Moveable):
def doStart(self, value): def doStart(self, value):
if value == 'None': if value == 'None':
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): def doStop(self):
"""never busy""" """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): def doRead(self, maxage=0):
return self._cfgvalue or '' return self._cfgvalue or ''
@ -415,7 +510,9 @@ class FrappyNode(SecNodeDevice, Moveable):
cfg = self.read() cfg = self.read()
super().createDevices() super().createDevices()
if cfg != self._lastcfg: 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: if self.param_category:
for devname, (_, devcfg) in self.setup_info.items(): for devname, (_, devcfg) in self.setup_info.items():
params_cfg = devcfg['params_cfg'] params_cfg = devcfg['params_cfg']
@ -437,10 +534,9 @@ class FrappyNode(SecNodeDevice, Moveable):
cfg = self.read() cfg = self.read()
if self._lastcfg != cfg: if self._lastcfg != cfg:
self._lastcfg = cfg self._lastcfg = cfg
try: fc = get_frappy_config()
session.devices.get('frappy_config')._trigger_change.set() if fc:
except AttributeError: # frappy_config does not exist fc._trigger_change.set()
pass
def disable(self): def disable(self):
seaconn = session.devices.get('sea_%s' % self.service) seaconn = session.devices.get('sea_%s' % self.service)
@ -453,13 +549,10 @@ class FrappyNode(SecNodeDevice, Moveable):
code, text = status.DISABLED, 'disabled' code, text = status.DISABLED, 'disabled'
SecNodeDevice._set_status(self, code, text) SecNodeDevice._set_status(self, code, text)
def restart(self, cfg=None, forced=True): def restart(self, cfg=None):
"""restart frappy server """restart frappy server
:param cfg: config for frappy server, if not given, restart with the same config :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: if cfg is None:
cfg = self._cfgvalue cfg = self._cfgvalue
@ -467,25 +560,8 @@ class FrappyNode(SecNodeDevice, Moveable):
fm = FrappyManager() fm = FrappyManager()
info = fm.get_ins_info(ins) info = fm.get_ins_info(ins)
running_cfg = fm.get_cfg(ins, self.service) or '' 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 cfg is None:
if forced: self.log.error('can not restart - previous cfg unknown')
self.log.error('can not restart - previous cfg unknown')
return return
if cfg != running_cfg: if cfg != running_cfg:
self.disable() self.disable()
@ -494,15 +570,15 @@ class FrappyNode(SecNodeDevice, Moveable):
fm.do_stop(ins, self.service) fm.do_stop(ins, self.service)
is_cfg = cfg and ':' not in cfg is_cfg = cfg and ':' not in cfg
if is_cfg: if is_cfg:
available_cfg = FrappyManager().all_cfg(config.instrument, self.service) available_cfg = None
failed = False
for cfgitem in cfg.split(','): for cfgitem in cfg.split(','):
if cfgitem not in available_cfg: if not fm.is_cfg(config.instrument, self.service, cfgitem):
failed = True if available_cfg is None:
available_cfg = fm.all_cfg(config.instrument, self.service)
suggestions = suggest(cfgitem, available_cfg) suggestions = suggest(cfgitem, available_cfg)
if suggestions: if suggestions:
session.log.error('%s unknown, did you mean: %s' % (cfgitem, ', '.join(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') raise ValueError('use "frappy_list()" to get a list of valid frappy configurations')
uri = 'localhost:%d' % info[self.service] uri = 'localhost:%d' % info[self.service]
else: else: