import re from pathlib import Path from .base import MarcheControl, Logger, ArgError WRAPPER_CFG = """interface = '{port}' include({cfg!r}) overrideNode(interface=interface) """ WRAPPER_PAT = re.compile(r"interface\s=\s*'(\d*)'\s*\n") class FrappyMarche(MarcheControl): services = 'main', 'stick', 'addons' def __init__(self, instance, host='localhost', port=None, user=None): super().__init__(host, port, user, instance) superconfig = self.config.sections['superfrappy'] self.instance = instance self.wrapperdir = superconfig.pop('wrapperdir') self.cfgdirs = superconfig.pop('cfgdirs') frappy_ports = self.ins_config.get('frappy_ports') ports = frappy_ports.split('-') self.frappy_ports = list(range(int(ports[0]), int(ports[-1]))) self.frappy_servers = [f'{host}:{p}' for p in self.frappy_ports] if not Path(self.wrapperdir).is_dir(): raise ValueError(f'{self.wrapperdir} does not exist') self.get_cfg_info() # do we need to update this from time to time? self.argmap.update((k, 'service') for k in self.services) def get_service(self, instance): return f'frappy.{instance}' if self.instance == 'this' else f'frappy.{self.instrument}-{instance}' def wrapper_file(self, cfg): return Path(self.wrapperdir) / f'{cfg}_cfg.py' def cfg_file(self, cfgdirs, service, cfg): if '/' in cfg: return Path(cfg) cfgpy = f'{cfg}_cfg.py' tries = [] services = [service] if service else list(self.services) services.append('') for servicedir in services: for cfgdir in cfgdirs.split(':'): cfgfile = Path(cfgdir) / servicedir / cfgpy tries.append(cfgfile) if cfgfile.is_file(): return servicedir, cfgfile else: raise FileNotFoundError(f'can not find {cfgpy} in {tries}') def get_std_port(self, service): if service == 'main': return self.frappy_ports[0] if service == 'stick': return self.frappy_ports[1] return self.frappy_ports[2:] def get_local_ports(self): self.get_cfg_info() run_state = self.status('frappy') result = {} for host_port, cfg in self.cfg_info.items(): host, port = host_port.split(':') if host == 'localhost': busy, on, _ = run_state.get(cfg, (0,0,0)) if on or busy: result.setdefault(port, []).append((on, busy, cfg)) return {sorted(v)[-1][-1]: k for k, v in result.items()} def get_port(self, service): if service not in {'main', 'stick', 'addons', 'addon'}: raise ArgError('illegal service argument') ports = self.get_std_port(service) if isinstance(ports, int): return ports self.get_cfg_info() used_ports = self.get_local_ports() for port in ports: if port not in used_ports: return port raise ValueError('too many frappy servers') def add_frappy_service(self, service, cfg, port, log=None): if log is None: log = Logger('info') log.info('add %r port=%r', cfg, port) service, cfgfile = self.cfg_file(self.cfgdirs, service, cfg) if not port: if not service: raise ArgError('service is not given and can not be determined from cfg file location') port = self.get_port(service) wrapper_content = WRAPPER_CFG.format(cfg=str(cfgfile), port=port) cfgname = cfgfile.stem.removesuffix('_cfg') self.wrapper_file(cfgname).write_text(wrapper_content) self.get_cfg_info() log.info('wrapper %r %r', self.wrapper_file(cfgname), wrapper_content) self.reload() log.info('registered %r', cfgname) def get_cfg_info(self): """get info from wrapper dir""" result = {} for cfgfile in Path(self.wrapperdir).glob('*_cfg.py'): cfg = cfgfile.stem[:-4] match = WRAPPER_PAT.match(cfgfile.read_text()) if match: result[f'localhost:{match.group(1)}'] = cfg self.cfg_info = result def delete_frappy_service(self, cfg): try: self.wrapper_file(cfg).unlink() self.reload() except FileNotFoundError: pass def _run(self, action, service=None, other=None): cfg= other if action == 'start': self.add_frappy_service(service, cfg, None) self.start(cfg) elif action == 'restart': self.restart(cfg) elif action == 'stop': self.stop(cfg) else: raise ArgError(f'unknown action {action!r}')