[WIP] state as of 2024-05-10 on samenv
This commit is contained in:
parent
dc24f3585c
commit
9ddb7eb3df
@ -30,7 +30,7 @@ this code is currently used:
|
||||
from servicemanager.base import ServiceManager, ServiceDown, UsageError, get_config
|
||||
from servicemanager.nicosman import NicosManager
|
||||
from servicemanager.seaman import SeaManager
|
||||
from servicemanager.frappyman import FrappyManager
|
||||
from servicemanager.frappyman import FrappyManager, Reconnect, Keep
|
||||
|
||||
|
||||
class SewebManager(ServiceManager):
|
||||
@ -93,7 +93,7 @@ def run(group, arglist):
|
||||
args.setdefault('action', 'gui')
|
||||
print('do you mean:\n %s %s %s %s %s' %
|
||||
(group, args.get('action', ''), args.get('ins', ''), args.get('service', ''), ' '.join(extra)))
|
||||
else:
|
||||
else:
|
||||
try:
|
||||
serv.action(args['action'], *serv.treat_args(args, extra + arglist))
|
||||
except AttributeError:
|
||||
|
65
base.py
65
base.py
@ -146,7 +146,7 @@ class ServiceManager:
|
||||
nr = section.get(self.group)
|
||||
if nr is not None:
|
||||
nr = '%02d' % int(nr)
|
||||
self.commands[ins] = command
|
||||
self.commands[ins] = command.replace('~', expanduser('~'))
|
||||
services = self.get_services(section)
|
||||
env = {k: get_subs(section, k, ins, nr) for k in section if k.isupper()}
|
||||
result[ins] = services
|
||||
@ -156,8 +156,12 @@ class ServiceManager:
|
||||
#def get_cmdpats(self, groups):
|
||||
# return self.cmdpats
|
||||
|
||||
def get_ins_info(self, ins):
|
||||
def get_ins_info(self, ins=None):
|
||||
self.get_info()
|
||||
if ins is None:
|
||||
ins = os.environ.get('Instrument')
|
||||
if ins is None:
|
||||
return {}
|
||||
return self.info[ins]
|
||||
|
||||
def get_cfg(self, ins, service):
|
||||
@ -175,7 +179,7 @@ class ServiceManager:
|
||||
def get_procs(self, groups=None, cfginfo=None):
|
||||
"""return processes
|
||||
|
||||
:param groups: group to look for or None for all groups
|
||||
:param groups: group to look for or None for own groups
|
||||
:param cfginfo: cfginfo dict to be populated
|
||||
:result: a dict[ins] of dict[service] of list of tuples (process, cfg)
|
||||
"""
|
||||
@ -211,6 +215,10 @@ class ServiceManager:
|
||||
return result
|
||||
|
||||
def wildcard(self, ins):
|
||||
"""return a list of matching instruments
|
||||
|
||||
or None, when no wildcard character in ins
|
||||
"""
|
||||
if ins is None or ins == 'all':
|
||||
return list(self.info)
|
||||
pat = re.sub(r'(\.|\*)', '.*', ins)
|
||||
@ -248,13 +256,16 @@ class ServiceManager:
|
||||
continue # already killed
|
||||
for i in range(10): # total 0.1 * 10 * 9 / 2 = 4.5 sec
|
||||
try:
|
||||
p.wait(0.1 * i)
|
||||
except psutil.TimeoutExpired:
|
||||
if p.status() != psutil.STATUS_ZOMBIE:
|
||||
if print_wait and i > 4:
|
||||
print('wait for %s %s' % (ins, service))
|
||||
print_wait = False
|
||||
continue
|
||||
try:
|
||||
p.wait(0.1 * i)
|
||||
except psutil.TimeoutExpired:
|
||||
if p.status() != psutil.STATUS_ZOMBIE:
|
||||
if print_wait and i > 4:
|
||||
print('wait for %s %s' % (ins, service))
|
||||
print_wait = False
|
||||
continue
|
||||
except psutil.NoSuchProcess:
|
||||
pass # process stopped in the meantime
|
||||
self.stopped[ins][service] = ' '.join(p.info['cmdline'])
|
||||
break
|
||||
else:
|
||||
@ -281,10 +292,12 @@ class ServiceManager:
|
||||
if ins not in self.env:
|
||||
self.get_info()
|
||||
gr = self.group.upper()
|
||||
if not ins:
|
||||
raise UsageError('need instance')
|
||||
env = self.env[ins]
|
||||
return env.get('%s_ROOT' % gr, ''), env
|
||||
|
||||
def do_start(self, ins, service=None, cfg='', restart=False, wait=False, logger=None):
|
||||
def do_start(self, ins, service=None, cfg='', restart=False, wait=False, logger=None, opts=''):
|
||||
ins_list = self.wildcard(ins)
|
||||
if ins_list is not None:
|
||||
return ins_list
|
||||
@ -323,7 +336,10 @@ class ServiceManager:
|
||||
services = to_start
|
||||
for service_i in services:
|
||||
port = service_ports[service_i]
|
||||
cmd = self.commands[ins] % dict(ins=ins, serv=service_i, port=port, cfg=cfg, pkg=self.pkg)
|
||||
cmd = self.commands[ins] % dict(ins=ins, serv=service_i, port=port, cfg=cfg, pkg=self.pkg or ins)
|
||||
if opts:
|
||||
cmd = f'{cmd} {opts}'
|
||||
print(cmd)
|
||||
if '%(cfg)s' in self.commands[ins] and not cfg:
|
||||
cmd = self.stopped[ins].get(service_i)
|
||||
if not cmd:
|
||||
@ -333,7 +349,7 @@ class ServiceManager:
|
||||
wd = os.getcwd()
|
||||
try:
|
||||
start_dir, env = self.prepare_start(ins, service_i, cfg)
|
||||
env = dict(os.environ, **env)
|
||||
env = dict(os.environ, **env, Instrument=ins)
|
||||
os.chdir(start_dir)
|
||||
if start_dir not in sys.path:
|
||||
sys.path.insert(0, start_dir)
|
||||
@ -392,7 +408,7 @@ class ServiceManager:
|
||||
return ins_list
|
||||
self.do_start(ins, service, cfg, True, logger=logger)
|
||||
|
||||
def do_run(self, ins, service=None, cfg=None):
|
||||
def do_run(self, ins, service=None, cfg=None, opts=''):
|
||||
"""for tests: run and wait"""
|
||||
if self.wildcard(ins) is not None:
|
||||
raise UsageError('no wildcards allowed with %s run' % self.group)
|
||||
@ -401,7 +417,7 @@ class ServiceManager:
|
||||
service, = self.services
|
||||
except ValueError:
|
||||
raise UsageError('need service to start (one of %s)' % ', '.join(self.services))
|
||||
self.do_start(ins, service, cfg, wait=True)
|
||||
self.do_start(ins, service, cfg, wait=True, opts=opts)
|
||||
|
||||
def do_list(self, ins=None, *args):
|
||||
"""info about running services"""
|
||||
@ -436,7 +452,7 @@ class ServiceManager:
|
||||
plist = procs_dict.get(serv)
|
||||
if plist:
|
||||
cfg = cfginfo.get((ins_i, serv), '') or sm.get_cfg(ins_i, serv)
|
||||
if sm == self:
|
||||
if sm == self or instances is None:
|
||||
show_ins = True
|
||||
gs = '%s %s' % (group, serv)
|
||||
port = str(port or '')
|
||||
@ -483,12 +499,21 @@ class ServiceManager:
|
||||
print(str(e))
|
||||
|
||||
def do_help(self, *args):
|
||||
options = {'ins': '<instance> ', 'optional_ins': '[instance] ', 'remark': '*', 'legend': ''}
|
||||
wildcards = True
|
||||
if self.main_ins:
|
||||
usage = self.USAGE.replace('<instance>', '[instance]') % (
|
||||
'[instance] is empty or one of %s' % ', '.join(self.info))
|
||||
if len(self.info) == 1:
|
||||
for key in options:
|
||||
options[key] = ''
|
||||
options['remark'] = ' '
|
||||
wildcards = False
|
||||
else:
|
||||
options['legend'] = ' [instance] is empty or one of %s\n' % ', '.join(self.info)
|
||||
else:
|
||||
usage = self.USAGE % ('<instance> is one of %s' % ', '.join(self.info))
|
||||
print(usage)
|
||||
options['legend'] = ' <instance> is one of %s\n' % ', '.join(self.info)
|
||||
if wildcards:
|
||||
options['legend'] += " * wildcards allowed, using '.' to replace 0 or more arbitrary characters in <instance>\n"
|
||||
print(self.USAGE % options)
|
||||
|
||||
def treat_args(self, argdict, unknown=(), extra=()):
|
||||
if unknown:
|
||||
|
444
frappyman.py
444
frappyman.py
@ -1,4 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# *****************************************************************************
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it under
|
||||
@ -26,6 +25,7 @@ import re
|
||||
import builtins
|
||||
from glob import glob
|
||||
from itertools import zip_longest
|
||||
from collections import defaultdict
|
||||
from os.path import join, isdir, basename, expanduser, exists
|
||||
from configparser import ConfigParser
|
||||
from .base import ServiceManager, ServiceDown, UsageError
|
||||
@ -65,215 +65,22 @@ class Namespace(dict):
|
||||
SEAEXT = {'main': '.config', 'stick': '.stick'}
|
||||
|
||||
|
||||
# TODO: put inline
|
||||
def get_service(key):
|
||||
return key if key in ('main', 'stick') else 'addons'
|
||||
class Reconnect(str):
|
||||
"""a string tagged with the information that the frappy server is already running
|
||||
|
||||
|
||||
def make_proposed(givencfgs, ourcfgs, seacfgs, sea_info, strict=False, giventitle='nicos'):
|
||||
overview = {}
|
||||
for service in 'main', 'stick':
|
||||
given = givencfgs.get(service)
|
||||
if given:
|
||||
overview.setdefault(service, {})['given'] = given
|
||||
frappy = ourcfgs.get(service)
|
||||
if frappy:
|
||||
overview.setdefault(service, {})['frappy'] = frappy
|
||||
for addon in givencfgs.get('addons', '').split(','):
|
||||
addon = addon.strip()
|
||||
if addon:
|
||||
overview.setdefault(addon, {})['given'] = addon
|
||||
for addon in ourcfgs.get('addons', '').split(','):
|
||||
addon = addon.strip()
|
||||
if addon:
|
||||
overview.setdefault(addon, {})['frappy'] = addon
|
||||
|
||||
seacfgfiles = [(c or '') + SEAEXT.get(s, '.addon')
|
||||
for c, s in zip_longest(seacfgs, FrappyManager.services)]
|
||||
seacfgset = set(seacfgfiles)
|
||||
inverted_sea_info = {k: [] for k in seacfgfiles}
|
||||
for cfg, seacfg in sea_info.items():
|
||||
if seacfg in seacfgset:
|
||||
inverted_sea_info[seacfg].append(cfg)
|
||||
error = False
|
||||
result = {}
|
||||
addons = set()
|
||||
for seacfg, seacfgfile, key in zip_longest(seacfgs, seacfgfiles, ('main', 'stick')):
|
||||
service = key or 'addons'
|
||||
if not seacfg:
|
||||
continue
|
||||
available = inverted_sea_info[seacfgfile]
|
||||
if available:
|
||||
proposed = available[0] if len(available) == 1 else None
|
||||
if not proposed:
|
||||
running = None
|
||||
for item in available:
|
||||
if item == overview.get(key or item, {}).get('given'):
|
||||
proposed = item
|
||||
if item == overview.get(key or item, {}).get('frappy'):
|
||||
running = item
|
||||
if running and not proposed:
|
||||
proposed = running
|
||||
if proposed:
|
||||
itemdict = overview.setdefault(key or proposed, {})
|
||||
given = itemdict.get('given')
|
||||
running = overview.get(key or proposed, {}).get('frappy')
|
||||
if running == proposed:
|
||||
remark = '' if given == running else 'reconnect'
|
||||
else:
|
||||
remark = f'restart'
|
||||
|
||||
itemdict.update(name=service, sea=seacfg, remark=remark)
|
||||
if key:
|
||||
result[key] = proposed
|
||||
else:
|
||||
addons.add(proposed)
|
||||
else:
|
||||
for item in available:
|
||||
overview.setdefault(item, {}).update(name=service, frappy=item, remark='ambiguous', sea=seacfg)
|
||||
error = True
|
||||
else:
|
||||
overview.setdefault(key or seacfg, {}).update(name=service, sea=seacfg,
|
||||
remark='missing frappy config')
|
||||
error = True
|
||||
restart = set()
|
||||
for key, itemdict in overview.items():
|
||||
running = itemdict.get('frappy')
|
||||
if running:
|
||||
service = get_service(key)
|
||||
if service == 'addons':
|
||||
addons.add(running)
|
||||
if not itemdict.get('sea'):
|
||||
if sea_info.get(running):
|
||||
restart.add(service)
|
||||
itemdict['remark'] = f'restart to start sea'
|
||||
elif itemdict.get('given') != running:
|
||||
itemdict['remark'] = f'reconnect'
|
||||
|
||||
if addons:
|
||||
result['addons'] = ','.join(addons)
|
||||
|
||||
if error and strict:
|
||||
result = {}
|
||||
else:
|
||||
for service, cfg in ourcfgs.items():
|
||||
if cfg:
|
||||
prop = result.get(service, '')
|
||||
if prop == cfg and service not in restart:
|
||||
result[service] = True
|
||||
|
||||
columns = ['name', 'given', 'frappy', 'sea', 'remark']
|
||||
rows = [['', giventitle, 'frappy', 'sea', '']] + [[d.get(c) or '' for c in columns] for d in overview.values()]
|
||||
wid = [max(len(v) for v in column) for column in zip(*rows)]
|
||||
# insert title lines
|
||||
rows.insert(1, [''] + ['-' * w for w in wid[1:-1]] + [''])
|
||||
return result, [' '.join(v.ljust(w) for w, v in zip(wid, row)) for row in rows]
|
||||
|
||||
|
||||
def summarize_server_state(givencfgs, ourcfgs, sealist, sea_info, strict=False):
|
||||
"""get a guess for the configuration from information about running services
|
||||
|
||||
:param givencfgs: dict <service> of given configuration (from nicos cache)
|
||||
:param outcfgs: dict <service> of running configuration (frappy)
|
||||
:param sealist: list of running sea configuration
|
||||
:param sea_info: dict <frappycfg> of <seacfg> with info about sea configs
|
||||
:param strict: when True return empty cfg result on error
|
||||
:return: tuple (<error>, <proposed>, (<frappyitems>, <seaitems>), <remarks> where:
|
||||
<error>: proposed config not sure
|
||||
<proposed>: dict <service> of proposed cfg
|
||||
<frappyitems>: dict of items runnning in frappy servers (addons are separated)
|
||||
<seaitems>: dict of items running on the sea server
|
||||
<remarks>: dict of actions / remarks
|
||||
isinstance(cfg, Reconnect) means: cfg may need reconnection, but not restart
|
||||
"""
|
||||
givencfgs = dict(givencfgs)
|
||||
addons = givencfgs.pop('addons', '')
|
||||
for addon in addons.split(','):
|
||||
addon = addon.strip()
|
||||
if addon:
|
||||
givencfgs[addon] = addon
|
||||
frappycfgs = dict(ourcfgs)
|
||||
addons = frappycfgs.pop('addons', '')
|
||||
for addon in addons.split(','):
|
||||
addon = addon.strip()
|
||||
if addon:
|
||||
frappycfgs[addon] = addon
|
||||
def __repr__(self):
|
||||
return f'Reconnect({str(self)!r})'
|
||||
|
||||
seacfgfiles = [(c or '') + SEAEXT.get(s, '.addon')
|
||||
for c, s in zip_longest(sealist, FrappyManager.services)]
|
||||
seacfgset = set(seacfgfiles)
|
||||
inverted_sea_info = {k: [] for k in seacfgfiles}
|
||||
for cfg, seacfg in sea_info.items():
|
||||
if seacfg in seacfgset:
|
||||
inverted_sea_info[seacfg].append(cfg)
|
||||
error = False
|
||||
result = {}
|
||||
addons = set()
|
||||
seacfgs = {}
|
||||
remarks = {}
|
||||
for seacfg, seacfgfile, key in zip_longest(sealist, seacfgfiles, ('main', 'stick')):
|
||||
if not seacfg:
|
||||
continue
|
||||
available = inverted_sea_info[seacfgfile]
|
||||
if available:
|
||||
proposed = available[0] if len(available) == 1 else None
|
||||
if not proposed:
|
||||
running = None
|
||||
for item in available:
|
||||
if item == givencfgs.get(key or item):
|
||||
proposed = item
|
||||
if item == frappycfgs.get(key or item):
|
||||
running = item
|
||||
if running and not proposed:
|
||||
proposed = running
|
||||
if proposed:
|
||||
pkey = key or proposed
|
||||
given = givencfgs.get(pkey)
|
||||
running = frappycfgs.get(pkey)
|
||||
if running == proposed:
|
||||
remarks[pkey] = '' if given == running else 'reconnect'
|
||||
elif running:
|
||||
remarks[pkey] = 'frappy restart needed'
|
||||
else:
|
||||
remarks[pkey] = 'frappy start needed'
|
||||
|
||||
seacfgs[pkey] = seacfg
|
||||
if key:
|
||||
result[key] = proposed
|
||||
else:
|
||||
addons.add(proposed)
|
||||
else:
|
||||
for item in available:
|
||||
remarks[item] = 'ambiguous'
|
||||
seacfgs[item] = seacfg
|
||||
error = True
|
||||
else:
|
||||
pkey = key or seacfg
|
||||
seacfgs[pkey] = seacfg
|
||||
remarks[pkey] = 'missing frappy config'
|
||||
error = True
|
||||
restart = set()
|
||||
for key, running in frappycfgs.items():
|
||||
if running:
|
||||
service = get_service(key)
|
||||
if service == 'addons':
|
||||
addons.add(running)
|
||||
if not seacfgs.get(key):
|
||||
if sea_info.get(running):
|
||||
restart.add(service)
|
||||
remarks[key] = 'restart to start sea'
|
||||
elif givencfgs.get(key) != running:
|
||||
remarks[key] = 'reconnect'
|
||||
elif remarks.get(key) is None:
|
||||
remarks[key] = ''
|
||||
if addons:
|
||||
result['addons'] = ','.join(addons)
|
||||
class Keep(Reconnect):
|
||||
"""a string tagged with the information to keep the connection as given
|
||||
|
||||
for service, cfg in ourcfgs.items():
|
||||
if cfg:
|
||||
prop = result.get(service, '')
|
||||
if prop == cfg and service not in restart:
|
||||
result[service] = True
|
||||
return error, result, (frappycfgs, seacfgs), remarks
|
||||
isinstance(cfg, Reconnect) means: the given cfg is already running, so no reconnection is needed
|
||||
"""
|
||||
def __repr__(self):
|
||||
return f'Keep({str(self)!r})'
|
||||
|
||||
|
||||
class FrappyManager(ServiceManager):
|
||||
@ -282,16 +89,16 @@ class FrappyManager(ServiceManager):
|
||||
USAGE = """
|
||||
Usage:
|
||||
|
||||
frappy list [instance] *
|
||||
frappy start <instance> <service> <cfgfiles>
|
||||
frappy restart <instance> [<service>] [<cfgfiles>] *
|
||||
frappy stop <instance> [<service>] *
|
||||
frappy listcfg <instance> [<service> | develop] # list available cfg files
|
||||
frappy list %(optional_ins)s
|
||||
frappy start %(ins)s<service> <cfgfiles>
|
||||
frappy restart %(ins)s[<service>] [<cfgfiles>] %(remark)s
|
||||
frappy stop %(ins)s[<service>] %(remark)s
|
||||
frappy listcfg %(ins)s[<service> | develop] # list available cfg files
|
||||
|
||||
<service> is one of main, stick, addons
|
||||
%s
|
||||
* wildcards allowed, using '.' to replace 0 or more arbitrary characters in <instance>
|
||||
%(legend)s
|
||||
"""
|
||||
sea_info = None
|
||||
|
||||
def config_dirs(self, ins, service):
|
||||
cfgpaths = []
|
||||
@ -336,12 +143,12 @@ class FrappyManager(ServiceManager):
|
||||
env_update['PYTHONPATH'] = os.environ['PYTHONPATH'] = ':'.join(pypathlist)
|
||||
return start_dir, dict(env, **env_update)
|
||||
|
||||
def do_start(self, ins, service=None, cfg='', restart=False, wait=False, logger=None):
|
||||
def do_start(self, ins, service=None, cfg='', restart=False, wait=False, logger=None, opts=''):
|
||||
if self.wildcard(ins) is not None:
|
||||
raise UsageError('no wildcards allowed with %s start' % self.group)
|
||||
if cfg and not service and len(self.services) != 1:
|
||||
raise UsageError('need service to start (one of %s)' % ', '.join(self.services))
|
||||
super().do_start(ins, service, cfg, restart, wait, logger)
|
||||
super().do_start(ins, service, cfg, restart, wait, logger, opts=opts)
|
||||
|
||||
def do_restart(self, ins, service=None, cfg=None, logger=None):
|
||||
ins_list = super().do_restart(ins, service, cfg, logger)
|
||||
@ -414,22 +221,25 @@ class FrappyManager(ServiceManager):
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def all_cfg(self, ins, service, list_info=None, sea_info=None):
|
||||
def all_cfg(self, ins, service, details=False):
|
||||
"""get available cfg files
|
||||
|
||||
:param ins: instance
|
||||
:param service: service nor None for all services
|
||||
:param list_info: None or a dict to collect info in the form
|
||||
dict <cfgdir> of <cfg> of <description>
|
||||
:param sea_info: None or a dict <frappycfg> of <seacfg> with info about sea configs
|
||||
in frappy cfgs to be populated
|
||||
:return: set of available config
|
||||
:param details: get details about relation to sea
|
||||
:return: see param:`what`
|
||||
|
||||
sea_info: is a dict <frappycfg> of <seacfg> (including extension .config/.stick/.addon)
|
||||
list_info: dict <cfgdir> of <cfg> of <description>
|
||||
"""
|
||||
all_cfg = set()
|
||||
if not ins:
|
||||
return {}
|
||||
namespace = Namespace()
|
||||
details = sea_info is not None or list_info is not None
|
||||
if details:
|
||||
self.frappy2sea = f2s = {}
|
||||
self.sea2frappy = s2f = {}
|
||||
self.list_info = list_info = {}
|
||||
for service in [service] if service else self.services:
|
||||
for cfgdir in self.config_dirs(ins, service):
|
||||
for cfgfile in glob(join(cfgdir, '*_cfg.py')):
|
||||
@ -441,67 +251,67 @@ class FrappyManager(ServiceManager):
|
||||
sea_cfg = None
|
||||
desc = repr(e)
|
||||
if cfg not in all_cfg:
|
||||
if sea_cfg and sea_info is not None:
|
||||
sea_info.setdefault(sea_cfg, set()).add(cfg)
|
||||
sea_info[cfg] = sea_cfg
|
||||
all_cfg.add(cfg)
|
||||
if list_info is not None:
|
||||
list_info.setdefault(cfgdir, {})[cfg] = desc.split('\n', 1)[0]
|
||||
if sea_cfg:
|
||||
# sea_info.setdefault(sea_cfg, set()).add(cfg)
|
||||
f2s[cfg] = sea_cfg
|
||||
s2f.setdefault(sea_cfg, set()).add(cfg)
|
||||
list_info.setdefault(cfgdir, {})[cfg] = desc.split('\n', 1)[0]
|
||||
all_cfg.add(cfg)
|
||||
# if service == 'main':
|
||||
# sea_info['none.config'] = {''}
|
||||
return all_cfg
|
||||
|
||||
def do_listcfg(self, ins='', service='', prt=print):
|
||||
if not ins:
|
||||
raise UsageError('missing instance')
|
||||
list_info = {}
|
||||
sea_info = {}
|
||||
if service:
|
||||
self.all_cfg(ins, service, list_info, sea_info)
|
||||
else:
|
||||
for service in self.services:
|
||||
self.all_cfg(ins, service, list_info, sea_info)
|
||||
self.all_cfg(ins, service, True)
|
||||
seacfgpat = re.compile(r'(.*)(\.config|\.stick|\.addon)')
|
||||
inverted_sea_info = {}
|
||||
for seacfg, cfgset in sea_info.items():
|
||||
for cfg in cfgset:
|
||||
inverted_sea_info[cfg] = seacfg
|
||||
ambiguous = 0
|
||||
keylen = max(max(len(k) for k in cfgs) for cfgs in list_info.values())
|
||||
for cfgdir, cfgs in list_info.items():
|
||||
keylen = max((max(len(k) for k in cfgs) for cfgs in self.list_info.values()), default=1)
|
||||
ambiguous = set()
|
||||
for cfgdir, cfgs in self.list_info.items():
|
||||
if cfgs:
|
||||
prt('')
|
||||
prt('--- %s:' % cfgdir)
|
||||
for cfg, desc in sorted(cfgs.items(), key=lambda v: (v[0].lower(), v)):
|
||||
seacfg = inverted_sea_info.get(cfg)
|
||||
seacfg = self.frappy2sea.get(cfg)
|
||||
if seacfg:
|
||||
name, ext = seacfgpat.match(seacfg).groups()
|
||||
if name == cfg or name + 'stick' == cfg:
|
||||
prefix = '* '
|
||||
else:
|
||||
prefix = f'* ({name}{ext}) '
|
||||
if len(sea_info[seacfg]) > 1:
|
||||
prefix = '!' + prefix
|
||||
ambiguous += 1 / len(sea_info[seacfg])
|
||||
if len(self.sea2frappy[seacfg]) > 1:
|
||||
prefix = '!' + prefix[1:]
|
||||
ambiguous.add(seacfg)
|
||||
desc = prefix + desc
|
||||
prt('%s %s' % (cfg.ljust(keylen), desc))
|
||||
prt(' ')
|
||||
gap = ' ' * keylen
|
||||
prt(f'{gap} * need sea')
|
||||
if ambiguous:
|
||||
print(f'{gap} ! {round(ambiguous)} ambiguous mappings sea -> frappy')
|
||||
prt(f'{gap} ! {len(ambiguous)} ambiguous mappings sea -> frappy')
|
||||
|
||||
def treat_args(self, argdict, unknown=(), extra=()):
|
||||
if len(unknown) == 1:
|
||||
cfg = unknown[0]
|
||||
cfg = None
|
||||
extra = list(extra)
|
||||
for arg in unknown:
|
||||
if arg.startswith('-'): # this is an option
|
||||
extra.append(arg)
|
||||
elif cfg is None:
|
||||
cfg = arg
|
||||
else:
|
||||
cfg = ''
|
||||
if cfg:
|
||||
if cfg == 'develop':
|
||||
argdict['service'] = cfg
|
||||
return super().treat_args(argdict, (), ())
|
||||
return super().treat_args(argdict, (), extra)
|
||||
if (',' in cfg or cfg.endswith('.cfg') or
|
||||
self.is_cfg(argdict.get('ins'), argdict.get('service'), cfg)):
|
||||
return super().treat_args(argdict, (), unknown)
|
||||
return super().treat_args(argdict, (), [cfg] + extra)
|
||||
return super().treat_args(argdict, unknown, extra)
|
||||
|
||||
def check_cfg_file(self, ins, service, cfg, needsea=False):
|
||||
if cfg is 'none':
|
||||
if cfg == 'none':
|
||||
return ''
|
||||
try:
|
||||
desc, sea_cfg = self.cfg_details(self, ins, service, cfg)
|
||||
@ -511,52 +321,112 @@ class FrappyManager(ServiceManager):
|
||||
except Exception:
|
||||
return cfg + '?'
|
||||
|
||||
def propose_cfgs(self, ins, givencfgs, strict=False):
|
||||
"""get proposed configuration and an overview of the running servers
|
||||
|
||||
:param ins: the instance to be checked for
|
||||
:param givencfgs: a dict <service> of cfg given by the ECS
|
||||
:param strict: in case of errors (ambiguities or missing frappy config files)
|
||||
proposed will be return as None
|
||||
|
||||
:return: a tuple (<overview>, <proposed>)
|
||||
where <overview> is information from running servers:
|
||||
a dict <item> of dict with optional items 'given', 'frappy', 'sea', 'proposed', 'error'
|
||||
|
||||
given: the values for the cfgs argument
|
||||
frappy: the cfg from running frappy servers
|
||||
sea: the sea config file running
|
||||
proposed: proposed configuration for the ECS
|
||||
error: information about missing or ambiguous cfg files
|
||||
"""
|
||||
ourcfgs = self.get_cfg(ins, None)
|
||||
sea = SeaManager()
|
||||
seacfgs = sea.get_cfg(ins, 'sea').split('/')
|
||||
if len(seacfgs) < 2:
|
||||
seacfgs.append('')
|
||||
sea_info = {}
|
||||
self.all_cfg(ins, None, sea_info=sea_info)
|
||||
return make_proposed(givencfgs, ourcfgs, seacfgs, sea_info, strict)
|
||||
|
||||
def get_server_state(self, ins, givencfgs):
|
||||
"""get proposed configuration and an overview of the running servers
|
||||
|
||||
:param ins: the instance to be checked for
|
||||
:param givencfgs: a dict <service> of cfg given by the ECS
|
||||
|
||||
TODO: update this doc
|
||||
:return: tuple (<error>, <proposed>, (<frappyitems>, <seaitems>), <remarks> where:
|
||||
<error>: proposed config not sure
|
||||
<proposed>: dict <service> of proposed cfg
|
||||
<frappyitems>: dict of items runnning in frappy servers (addons are separated)
|
||||
<frappyitems>: dict of items running in frappy servers (addons are separated)
|
||||
<seaitems>: dict of items running on the sea server
|
||||
<remarks>: dict of actions to do / remarks
|
||||
"""
|
||||
ourcfgs = self.get_cfg(ins, None)
|
||||
sea = SeaManager()
|
||||
seacfgs = sea.get_cfg(ins, 'sea').split('/')
|
||||
if len(seacfgs) < 2:
|
||||
seacfgs.append('')
|
||||
sea_info = {}
|
||||
self.all_cfg(ins, None, sea_info=sea_info)
|
||||
return summarize_server_state(givencfgs, ourcfgs, seacfgs, sea_info)
|
||||
self.frappy_cfgs = self.get_cfg(ins, None) # dict <service> of running frappy servers
|
||||
|
||||
self.sea = SeaManager()
|
||||
seaconfig = self.sea.get_cfg(ins, 'sea')
|
||||
sealist = seaconfig.split('/') # <config>, <stick>, <addon1>, <addon2> ...
|
||||
if len(sealist) < 2:
|
||||
sealist.append('')
|
||||
|
||||
all_cfg = self.all_cfg(ins, None, True)
|
||||
proposed_cfg = {}
|
||||
self.sea_cfgs = {}
|
||||
self.remarks = {}
|
||||
self.error = False
|
||||
self.state = {}
|
||||
result = {}
|
||||
for (service, ext), seacfg in zip_longest(SEAEXT.items(), sealist[0:2], fillvalue=(None, None)):
|
||||
if service:
|
||||
self.sea_cfgs[service] = seacfg
|
||||
proposed = self.sea2frappy.get(seacfg + ext) or set()
|
||||
if seaconfig:
|
||||
proposed_cfg[service] = proposed
|
||||
self.state[f'sea {service}'] = seacfg
|
||||
|
||||
given_addons = givencfgs.get('addons', '')
|
||||
running_addons = self.frappy_cfgs.get('addons', '')
|
||||
if running_addons:
|
||||
for cfg in running_addons.split(','):
|
||||
seacfg = self.frappy2sea.get(cfg.strip())
|
||||
if seacfg.endswith('.addon') and seacfg[:-6] not in sealist[2:]:
|
||||
proposed_cfg['addons'] = {running_addons}
|
||||
break
|
||||
elif given_addons:
|
||||
proposed_cfg['addons'] = {given_addons}
|
||||
|
||||
for service in FrappyManager.services:
|
||||
given = givencfgs.get(service)
|
||||
running = self.frappy_cfgs.get(service)
|
||||
available = proposed_cfg.get(service)
|
||||
|
||||
if running:
|
||||
self.state[f'frappy {service}'] = running
|
||||
|
||||
if seaconfig: # sea server is running
|
||||
if not available:
|
||||
self.remarks[service] = 'no frappy config'
|
||||
continue
|
||||
proposed = list(available)[0] if len(available) == 1 else None
|
||||
if proposed is None:
|
||||
running = None
|
||||
for item in available:
|
||||
if item == given:
|
||||
proposed = item
|
||||
if item == running:
|
||||
running = item
|
||||
if running and not proposed:
|
||||
proposed = running
|
||||
if proposed is None:
|
||||
self.remarks[service] = 'ambiguous frappy cfg'
|
||||
self.error = True
|
||||
else:
|
||||
self.remarks[service] = ''
|
||||
if running == proposed:
|
||||
if given == running:
|
||||
result[service] = Keep(running)
|
||||
elif running:
|
||||
result[service] = proposed
|
||||
self.remarks[service] = 'restart frappy'
|
||||
else:
|
||||
result[service] = proposed
|
||||
else:
|
||||
if running:
|
||||
if self.frappy2sea.get(running, '').endswith(ext):
|
||||
result[service] = running
|
||||
self.remarks[service] = 'restart frappy to start sea'
|
||||
elif given != running:
|
||||
result[service] = Reconnect(running)
|
||||
self.remarks[service] = 'reconnect needed'
|
||||
else:
|
||||
result[service] = Keep(running)
|
||||
self.remarks[service] = ''
|
||||
elif given:
|
||||
if self.frappy2sea.get(given, '').endswith(ext):
|
||||
result[service] = running
|
||||
self.remarks[service] = 'restart frappy and sea'
|
||||
elif ':' in given:
|
||||
self.remarks[service] = 'external device'
|
||||
elif given in all_cfg:
|
||||
result[service] = running
|
||||
self.remarks[service] = 'restart frappy'
|
||||
else:
|
||||
result[service] = ''
|
||||
self.remarks[service] = f'unknown frappy cfg {given}'
|
||||
|
||||
return result
|
||||
|
||||
|
15
nicosman.py
15
nicosman.py
@ -55,17 +55,16 @@ class NicosManager(ServiceManager):
|
||||
Usage:
|
||||
|
||||
nicos gui <instance>
|
||||
nicos <instance> (the same as above)
|
||||
nicos list [<instance>] *
|
||||
nicos start <instance> [<service>] *
|
||||
nicos restart <instance> [<service>] *
|
||||
nicos stop <instance> [<service>] *
|
||||
nicos create <instance> <nr> *
|
||||
nicos <instance> (the same as above)
|
||||
nicos list [<instance>]
|
||||
nicos start <instance> [<service>]
|
||||
nicos restart <instance> [<service>] %(remark)s
|
||||
nicos stop <instance> [<service>] %(remark)s
|
||||
nicos create <instance> <nr>
|
||||
nicos link <instance> (create links to nicos data and scripts)
|
||||
|
||||
<service> is one of cache, deamon, poller
|
||||
%s
|
||||
* wildcards allowed, using '.' to replace 0 or more arbitrary characters in <instance>
|
||||
%(legend)s
|
||||
|
||||
to be done after the experiment:
|
||||
nicos copy (copy data and scripts from link)
|
||||
|
56
seaman.py
56
seaman.py
@ -24,6 +24,7 @@ import sys
|
||||
import time
|
||||
import termios
|
||||
import subprocess
|
||||
import psutil
|
||||
import os
|
||||
import re
|
||||
from os.path import join, exists
|
||||
@ -55,17 +56,16 @@ class SeaManager(ServiceManager):
|
||||
USAGE = """
|
||||
Usage:
|
||||
|
||||
sea gui <instance>
|
||||
sea <instance> # the same as sea gui <instance>
|
||||
sea cli <instance> # the same as old seacmd
|
||||
sea start <instance> [service] * # the same as old 'monit start sea'
|
||||
sea restart <instance> [service] *
|
||||
sea stop <instance> [service] *
|
||||
sea list [instance] *
|
||||
sea gui %(ins)s
|
||||
sea %(ins)s # the same as sea gui <instance>
|
||||
sea cli %(ins)s # the same as old seacmd
|
||||
sea start %(ins)s[service] %(remark)s # the same as old 'monit start sea'
|
||||
sea restart %(ins)s[service] %(remark)s
|
||||
sea stop %(ins)s[service] %(remark)s
|
||||
sea list %(optional_ins)s
|
||||
|
||||
[service] is empty or one of sea, graph
|
||||
%s
|
||||
* wildcards allowed, using '.' to replace 0 or more arbitrary characters in <instance>
|
||||
%(legend)s
|
||||
"""
|
||||
|
||||
def do_cli(self, ins):
|
||||
@ -107,10 +107,31 @@ class SeaManager(ServiceManager):
|
||||
if sea_server_src and exists(sea_server_src):
|
||||
if os.system('diff %s SeaServer' % sea_server_src):
|
||||
print('reload SeaServer')
|
||||
os.rename('SeaServer', 'SeaServer0')
|
||||
try:
|
||||
os.rename('SeaServer', 'SeaServer0')
|
||||
except Exception:
|
||||
pass
|
||||
os.system('cp %s ./' % sea_server_src)
|
||||
if service == 'sea':
|
||||
# debugging: copy status file in case of a reboot
|
||||
seastatus = self.get_status_filename(ins)
|
||||
if seastatus:
|
||||
boot_time = time.strftime("%Y-%m-%dT%H-%M-%S", time.localtime(psutil.boot_time()))
|
||||
dst = seastatus.replace('.tcl', '') + '.' + boot_time
|
||||
if not exists(dst):
|
||||
os.system(f'cp {seastatus} {dst}')
|
||||
return start_dir, env
|
||||
|
||||
def get_status_filename(self, ins):
|
||||
searoot = self.env[ins].get('SEA_ROOT', '')
|
||||
seastatus = join(searoot, ins, 'status', 'seastatus.tcl')
|
||||
if exists(seastatus):
|
||||
return seastatus
|
||||
seastatus = join(searoot, 'status', 'seastatus.tcl')
|
||||
if exists(seastatus):
|
||||
return seastatus
|
||||
return None
|
||||
|
||||
def get_cfg(self, ins, service, addconfirmed=False):
|
||||
"""return cfg info about running programs, if relevant
|
||||
|
||||
@ -121,12 +142,9 @@ class SeaManager(ServiceManager):
|
||||
if 'sea' not in self.get_procs().get(ins, ()):
|
||||
return ''
|
||||
try:
|
||||
searoot = self.env[ins].get('SEA_ROOT', '')
|
||||
seastatus = join(searoot, ins, 'status', 'seastatus.tcl')
|
||||
if not exists(seastatus):
|
||||
seastatus = join(searoot, 'status', 'seastatus.tcl')
|
||||
if not exists(seastatus):
|
||||
return '?'
|
||||
seastatus = self.get_status_filename(ins)
|
||||
if not seastatus:
|
||||
return '?'
|
||||
result = ['', '']
|
||||
confirmed = ''
|
||||
with open(seastatus, 'r', encoding='utf-8') as f:
|
||||
@ -178,4 +196,8 @@ class SeaManager(ServiceManager):
|
||||
argdict['ins'] = arg
|
||||
else:
|
||||
raise UsageError('unknown argument: %s' % arg)
|
||||
return [argdict.pop('ins', '')] + extra
|
||||
result = [argdict.pop('ins', '')]
|
||||
service = argdict.pop('service', '')
|
||||
if service:
|
||||
result.append(service)
|
||||
return result + extra
|
||||
|
Loading…
x
Reference in New Issue
Block a user