diff --git a/frappyman.py b/frappyman.py index 8837205..ba0eb4e 100644 --- a/frappyman.py +++ b/frappyman.py @@ -25,13 +25,17 @@ import os import re import builtins from glob import glob -from collections import defaultdict +from itertools import zip_longest from os.path import join, isdir, basename, expanduser, exists from configparser import ConfigParser from .base import ServiceManager, ServiceDown, UsageError from .seaman import SeaManager +MAIN = 1 +STICK = 2 + + class Namespace(dict): def __init__(self): self['Node'] = self.node @@ -58,6 +62,117 @@ class Namespace(dict): __builtins__ = builtins +SEAEXT = {'main': '.config', 'stick': '.stick'} + + +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 of given configuration (from nicos cache) + :param outcfgs: dict of running configuration (frappy) + :param sealist: list of running sea configuration + :param sea_info: dict of with info about sea configs + :param strict: when True return empty cfg result on error + :return: tuple (, , (, ), where: + : proposed config not sure + : dict of proposed cfg + : dict of items runnning in frappy servers (addons are separated) + : dict of items running on the sea server + : dict of actions / remarks + """ + 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 + + 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: + if key in ('main', 'stick'): + service = key + else: + 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) + + 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 + + class FrappyManager(ServiceManager): group = 'frappy' services = ('main', 'stick', 'addons') @@ -203,7 +318,8 @@ class FrappyManager(ServiceManager): :param service: service nor None for all services :param list_info: None or a dict to collect info in the form dict of of - :param sea_info: None or a dict of set() to be populated + :param sea_info: None or a dict of with info about sea configs + in frappy cfgs to be populated :return: set of available config """ all_cfg = set() @@ -224,6 +340,7 @@ class FrappyManager(ServiceManager): 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] @@ -239,11 +356,10 @@ class FrappyManager(ServiceManager): else: for service in self.services: self.all_cfg(ins, service, list_info, sea_info) - sea_ambig = {} # collect info about ambiguous sea info seacfgpat = re.compile(r'(.*)(\.config|\.stick|\.addon)') - for seacfg, cfgset in sea_info.items(): - name, ext = seacfgpat.match(seacfg).groups() - sea_ambig.update({k: (name, ext, len(cfgset)) for k in cfgset}) + sea_ambig = {} # collect info about ambiguous sea info + for cfg, seacfg in sea_info.items(): + sea_ambig.setdefault(seacfg, set()).add(cfg) ambiguous = 0 keylen = max(max(len(k) for k in cfgs) for cfgs in list_info.values()) for cfgdir, cfgs in list_info.items(): @@ -251,12 +367,14 @@ class FrappyManager(ServiceManager): prt('') prt('--- %s:' % cfgdir) for cfg, desc in cfgs.items(): - if cfg in sea_ambig: - name, ext, n = sea_ambig[cfg] + seacfg = sea_info.get(cfg) + if seacfg: + name, ext = seacfgpat.match(seacfg).groups() if name == cfg or name + 'stick' == cfg: prefix = '* ' else: prefix = f'* ({name}{ext}) ' + n = len(sea_ambig.get(seacfg)) if n > 1: prefix = '!' + prefix[1:] ambiguous += 1 @@ -290,40 +408,25 @@ class FrappyManager(ServiceManager): except Exception: return cfg + '?' - def guess_cfgs(self, ins, cfgs): + 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 of cfg given by the ECS + + :return: tuple (, , (, ), where: + : proposed config not sure + : dict of proposed cfg + : dict of items runnning in frappy servers (addons are separated) + : dict of items running on the sea server + : dict of actions to do / remarks + """ + ourcfgs = self.get_cfg(ins, None) sea = SeaManager() seacfgs = sea.get_cfg(ins, 'sea').split('/') - sea_info = {} 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) - guess = defaultdict(lambda: defaultdict(list)) - - def check_cfg(service, ext, frappyset, seacfgs): - for seacfg in seacfgs: - available = sea_info.get(seacfg + ext, ()) - if available: - for a in available: - if a in frappyset: - guess[service]['ok'].append(a) - break - else: - if len(available) == 1: - available = next(iter(available)) - # available is either a string or a set of strings - guess[service]['proposed'].append(available) - else: - guess[service]['missing'].append(seacfg) - - main = cfgs.get('main') - check_cfg('main', '.config', set() if main is None else {main}, {seacfgs[0]}) - - if len(seacfgs) > 1: - stick = cfgs.get('stick') - check_cfg('stick', '.stick', set() if stick is None else {stick}, {seacfgs[1]}) - - if len(seacfgs) > 2: - addons = set(cfgs.get('addons').split(',')) - check_cfg('addons', '.addons', set(addons), set(seacfgs[2:])) - return guess