diff --git a/devices.py b/devices.py index 04eb565..03cb569 100644 --- a/devices.py +++ b/devices.py @@ -144,6 +144,9 @@ class FrappyConfig(Device): _shutdown_event = None _restarting = False _within_update_setup = False + _current_cfgs = None + _target_cfgs = None + _rebuild_env = None def doInit(self, mode): if mode != SIMULATION and session.sessiontype != POLLER: @@ -160,15 +163,23 @@ class FrappyConfig(Device): def detect_changes(self): before_check = before_change = prev_shown = None cnt = 0 + normal_state = None while not self._shutdown_event.wait(1): busy = bool(session.experiment.scripts or session.daemon_device._controller.queue.scripts) if busy and cnt < 10: # check only every 10 sec when busy cnt += 1 continue + if not busy and normal_state == 'back_to_normal': + session.log.warning(all_info(self._current_cfgs, 'servers match configuration: ')) + normal_state = None cnt = 0 need_change, changes, fm = self.to_consider() - if fm.state == before_change or not need_change: + if fm.state == before_change: + continue + if not need_change: + if normal_state == 'changed': + normal_state = 'back_to_normal' continue if fm.state != before_check: # must be twice the same @@ -176,27 +187,36 @@ class FrappyConfig(Device): continue if busy or fm.state == prev_shown: continue + normal_state = 'changed' self.show_changes(changes) prev_shown = fm.state def show_changes(self, changes): if changes is None: need_change, changes, fm = self.to_consider() - session.log.warning('sample environment has changed:') - args = [] + session.log.info(' ') + session.log.warning('sample environment configuration should be changed:') + session.log.info(' ') + kwargs = {} for service, cfg in changes.items(): - arg = str(cfg) + kwargs[service] = str(cfg) + prev = self._target_cfgs.get(service) if cfg: if isinstance(cfg, Keep): - arg = f'={cfg}' - session.log.info('keep %s: %s', service, cfg) + kwargs.pop(service, None) + session.log.info('%s: keep %s', service, cfg) else: - session.log.warning('change %s to %s', service, cfg) + if prev: + session.log.info('%s: change from %s to %s', service, prev, cfg) + else: + session.log.info('%s: start as %s', service, cfg) elif cfg == '': - session.log.warning('remove %s', service) - args.append(arg) - args = tuple(args) - session.log.warning('use frappy%r or frappy.update() to activate', args) + session.log.info('%s: remove %s', service, prev) + if self._rebuild_env: + session.log.info('%s', self._rebuild_env) + alternative = f" or {all_info(kwargs, '')}" if kwargs else '' + session.log.info(' ') + session.log.info('use frappy.update()%s to activate', alternative) def to_consider(self, cfgs=None): """return info about a proposed changes @@ -206,37 +226,48 @@ class FrappyConfig(Device): the information takes into account running frappy and sea servers and their configuration + + side effect: try to reconnect a frappy server which should run with + the target cfg """ current_cfgs, target_cfgs = self.check_services() if cfgs is None: cfgs = current_cfgs - self._current_cfgs = cfgs fm = FrappyManager() proposed = fm.get_server_state(config.instrument, cfgs) changes = dict(proposed) - for service, cfg in proposed.items(): - if cfg == 'reconnect': # means: server is running, no restart needed - if service in current_cfgs: - changes.pop(service) need_change = False for service in SERVICES: cfg = changes.get(service) # proposed cfg - info = cfgs.get(service) # running cfg - if not info: + running = fm.frappy_cfgs.get(service) # running cfg + if running: + prop = changes.get(service) + if prop: + if prop == target_cfgs.get(service) == running: + secnode = session.devices.get('se_' + service) + if secnode: + if secnode._secnode: + secnode._secnode.connect() + else: + secnode._connect() + else: + changes[service] = '' + need_change = True + cfg = '' + else: if cfg == '': changes.pop(service) cfg = None if target_cfgs.get(service): - need_change = True - elif info == '': - if not changes.get(service): - changes[service] = '' - need_change = True - cfg = '' + need_change = 1 if cfg and not isinstance(cfg, Keep): + need_change = cfg + if not need_change and all(isinstance(v, Keep) for v in changes.values()): + self.set_envlist(checkonly=True) + if self._rebuild_env: need_change = True - # elif target_cfgs.get(service) != cfgs.get(service): - # need_change = True + else: + self._rebuild_env = None return need_change, changes, fm def check_services(self): @@ -247,6 +278,8 @@ class FrappyConfig(Device): if secnode: cfgs[secnode.service] = secnode.get_info() targets[secnode.service] = secnode.target + self._current_cfgs = cfgs + self._target_cfgs = targets return cfgs, targets def start_services(self, main=None, stick=None, addons=None,): @@ -265,7 +298,6 @@ class FrappyConfig(Device): self._restarting = True try: services = {'main': main, 'stick': stick, 'addons': addons} - to_reconnect = {} for service, cfg in services.items(): if cfg == '': seaconn = session.devices.get(f'se_sea_{service}') @@ -286,8 +318,6 @@ class FrappyConfig(Device): all_cfg[service] = chkinfo = secnode.get_info() if cfginfo is not None and (cfginfo != chkinfo or not isinstance(cfginfo, Reconnect)): new_cfg[service] = chkinfo = cfginfo - if secnode: - to_reconnect[service] = secnode # check cfg is not used twice for cfg in chkinfo.split(','): @@ -315,13 +345,13 @@ class FrappyConfig(Device): AddSetup('frappy_' + service) secnode = session.devices[nodename] secnode(cfginfo) - to_reconnect.pop(service, None) all_cfg[service] = secnode.get_info() CreateDevice(nodename) cleanup_defunct() CreateAllDevices() - for secnode in to_reconnect.values(): - secnode._secnode.connect() + for service, secnode in secnodes.items(): + if services.get(service): + secnode._secnode.connect() self.set_envlist() for secnode in remove_cfg: secnode.disable() @@ -404,11 +434,12 @@ class FrappyConfig(Device): if need_change: self.show_changes(changes) - def update(self, *args): - if args: - changes = {k: (Keep(v[1:]) if v.startswith('=') else v) for k, v in zip(SERVICES, args) if v is not None} - else: + def update(self, main=None, stick=None, addons=None): + if main is None and stick is None and addons is None: changes = self.to_consider()[1] + else: + changes = {k: Keep(v) for k, v in zip(SERVICES, (main, stick, addons)) + if v is not None} self.show_config(self.start_services(**changes)) def get_init_info(self, service): @@ -457,7 +488,7 @@ class FrappyConfig(Device): result[aliasname] = aliasdev return result - def set_envlist(self): + def set_envlist(self, checkonly=False): """create aliases and envlist for SECoP devices depending on their meaning @@ -511,6 +542,18 @@ class FrappyConfig(Device): dev = session.devices.get(devname) if dev is None or info.get('drivable_only', False) and not isinstance(dev, Moveable): continue + if checkonly: + if aliasnames and devname not in newenv: + newenv[devname] = aliasnames[0] + for aliasname in aliasnames: + adev = previous_aliases.pop(aliasname, None) + if not adev: + self._rebuild_env = f'need alias {aliasname} for {devname}' + return + if adev.alias != devname: + self._rebuild_env = f'alias {aliasname} must point to {devname}' + return + continue for aliasname in aliasnames: devcfg = ('nicos.core.DeviceAlias', {}) session.configured_devices[aliasname] = devcfg @@ -526,6 +569,10 @@ class FrappyConfig(Device): session.cache.put(aliasname, 'description', dev.description) aliasdev = session.createDevice(aliasname, recreate=True, explicit=True) aliasdev.alias = devname + # make sure device panel is updated + dev = aliasdev._obj + dev._cache.put(dev, 'status', dev.status()) + dev._cache.put(dev, 'value', dev.read()) if aliasnames: # only the first item of aliasnames is added to the envlist aliasname = aliasnames[0] @@ -539,14 +586,25 @@ class FrappyConfig(Device): else: to_remove.update(aliasnames) - for aliasname in previous_aliases: - session.destroyDevice(aliasname) - session.configured_devices.pop(aliasname, None) - session.dynamic_devices.pop(aliasname, None) + if checkonly: + if previous_aliases: + self._rebuild_env = f'need to remove aliases {", ".join(previous_aliases)}' + return + else: + for aliasname in previous_aliases: + session.destroyDevice(aliasname) + session.configured_devices.pop(aliasname, None) + session.dynamic_devices.pop(aliasname, None) - applyAliasConfig() # for other aliases + applyAliasConfig() # for other aliases envlist = [k for k in session.experiment.envlist if k not in to_remove] + list(newenv.values()) + if checkonly: + if set(envlist) != set(session.experiment.envlist): + self._rebuild_env = f'need to update envlist to {envlist}' + else: + self._rebuild_env = None + return if envlist != session.experiment.envlist: removed = set(session.experiment.envlist).difference(envlist) session.experiment.setEnvironment(envlist) @@ -645,7 +703,6 @@ class FrappyNode(SecNodeDevice, Moveable): self.__log_recording = () def nodeStateChange(self, online, state): - print(f'NODE {self.service} {online} {state}') # self.log.info('NODE %r %r', online, state) if online: super().nodeStateChange(online, state)