diff --git a/commands.py b/commands.py index ce3fde7..c3c62f6 100644 --- a/commands.py +++ b/commands.py @@ -22,7 +22,8 @@ from nicos import session, config from nicos.utils import printTable 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 @@ -30,8 +31,9 @@ SERVICES = FrappyManager.services @usercommand def set_se_list(): - frappy_config = session.devices['frappy_config'] - frappy_config.set_envlist() + fc = get_frappy_config() + if fc: + fc.set_envlist() @usercommand @@ -47,7 +49,11 @@ def frappy(*args, main=None, stick=None, addons=None, force=False): - frappy('restart') # restart all frappy servers - 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 main is not None: raise TypeError('got multiple values for main') @@ -60,9 +66,8 @@ def frappy(*args, main=None, stick=None, addons=None, force=False): if main == '': stick = '' # remove stick with main else: - allsticks = FrappyManager().all_cfg(config.instrument, '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 stick = stickcfg else: @@ -75,12 +80,30 @@ def frappy(*args, main=None, stick=None, addons=None, force=False): if addons is not None: raise TypeError('got multiple values for addons') 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: 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') - frappy_config = session.devices['frappy_config'] - frappy_config.show_config(*frappy_config.check_or_start(main, stick, addons)) + fc.show_config(fc.start_services(main, stick, addons)) + + +@usercommand +def frappy_main(*args): + raise NameError('frappy_main() is no longer avaiable, use frappy() instead') + + +@usercommand +def frappy_stick(*args): + raise NameError('frappy_stick() is no longer avaiable, use frappy(stick=) instead') + + +@usercommand +def frappy_addons(*args): + raise NameError('frappy_addons() is no longer avaiable, use frappy(addons=) instead') @usercommand @@ -108,4 +131,6 @@ def frappy_list(service=None): @usercommand def frappy_changed(): - session.devices['frappy_config'].changed() + fc = get_frappy_config() + if fc: + fc.changed() diff --git a/devices.py b/devices.py index 8d54e85..771f102 100644 --- a/devices.py +++ b/devices.py @@ -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"" + 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: