improve detect_changes

check also if envlist and aliases have to be rebuilt
This commit is contained in:
2024-05-07 08:51:36 +02:00
parent b10102e052
commit ead54b14a5

View File

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