# ***************************************************************************** # This program is free software; you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation; either version 2 of the License, or (at your option) any later # version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License along with # this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Module authors: # Markus Zolliker # ***************************************************************************** import sys import time import socket from pathlib import Path from configparser import ConfigParser DEFAULT_CFG = """[this] instrument={instrument} frappy_ports = 15000-15009 """ MARCHESRC = ['/home/software/marche', '/home/l_samenv/marche'] CFGDIRS = ['/home/linse/config', '/home/l_samenv/linse_config'] def read_config(filename): parser = ConfigParser() parser.optxform = str parser.read([str(filename)]) return {k: dict(parser[k].items()) for k in parser.sections()} def change_mode(path, *, user=None, group=None, other=None): # adds given modes # example change_mode(path, user='+w', group='w', other='-r') prev = path.stat().st_mode mask = 0 bits = 0 for access in (user, group, other): mask *= 8 bits *= 8 if access is not None: mode = None try: for char in access: if char in '+-=': if mode is None: mode = char else: raise ValueError(f'{char} must appear first in mode') else: if mode is None: mode = '=' bitpos = 'xwr'.index(char) if mode != '=': mask |= 1 << bitpos if mode != '-': bits |= 1 << bitpos if mode == '=': mask |= 7 except KeyError: raise ValueError(f'illegal access mode {access!r}') mode = prev & ~mask | bits if mode != prev: path.chmod(mode) def write_content(filename, content, **kwds): path = Path(filename) path.write_text(content) change_mode(path, **kwds) def write_config(filename, newvalues): parser = ConfigParser() parser.optxform = str parser.read([str(filename)]) parser.read_dict(newvalues) with open(filename, 'w') as f: parser.write(f) change_mode(Path(filename), group='+w') class ArgError(ValueError): pass class Config: this = None def __init__(self): configfile = Path('/sq_sw/linse/etc/linsetools.cfg') self.sections = read_config(configfile) instconfig = Path('/home/linse/.config/instrument.cfg') if instconfig.is_file(): self.instruments = read_config(instconfig) else: host = socket.gethostname().split('.')[0] self.instruments = {'this': {'instrument': host, 'frappy_ports': '15000-15009'}} try: write_config(instconfig, self.instruments) except PermissionError: pass this = self.instruments.pop('this', None) if this: self.this = this['instrument'] self.instruments[self.this] = this def parse_args(self, argmap, args, other=None): result = {} for arg in args: if arg in self.instruments: if 'instrument' in result: raise ArgError('cannot give instrument twice') result['instrument'] = arg elif arg in argmap: kind = argmap.get(arg, other) if kind is None: raise ArgError(f'unexpected argument: {arg!r}') if kind in result: raise ArgError(f'cannot give {kind} twice') result[kind] = arg return result class Logger: loggers = {} levels = ['error', 'warning', 'info', 'debug'] def __new__(cls, level): log = cls.loggers.get(level) if log: print('old') return log log = object.__new__(cls) cls.loggers[level] = log method = log.show for lev in log.levels: setattr(log, lev, method) print(lev, level) if lev == level: method = log.hide return log def show(self, fmt, *args): print(fmt % args) def hide(self, fmt, *args): pass for marchedir in MARCHESRC: if Path(marchedir).is_dir(): sys.path.append(marchedir) import marche.jobs as mj from marche.client import Client break STATUS_MAP = { # values are (, , name) mj.DEAD: (False, False, 'DEAD'), mj.NOT_RUNNING: (False, False, 'NOT RUNNING'), mj.STARTING: (True, True, 'STARTING'), mj.INITIALIZING: (True, True, 'INITIALIZING'), mj.RUNNING: (True, False, 'RUNNING'), mj.WARNING: (True, False, 'WARNING'), mj.STOPPING: (False, True, 'STOPPING'), mj.NOT_AVAILABLE: (False, False, 'NOT AVAILABLE'), } def wait_status(cl, service): delay = 0.2 while True: sts = cl.getServiceStatus(service) if STATUS_MAP[sts][1]: # busy if delay > 1.5: # this happens after about 5 sec return False time.sleep(delay) delay *= 1.225 continue return True class MarcheControl: port = 8124 otherarg = None argmap = {k: 'action' for k in ('start', 'restart', 'stop', 'gui', 'cli', 'list', 'listcfg')} def __init__(self, instrument=None, host='localhost', port=None, user=None, config=None): self.config = config or Config() self.host = host self.instance = instrument or 'this' self.instrument = self.config.this if self.instance == 'this' else self.instance self.ins_config = self.config.instruments[self.instrument] self.user = user or self.instrument # SINQ instruments if not (Path('/home') / self.user).is_dir(): self.user = 'l_samenv' if port is not None: self.port = port self._client = None def connect(self): if self._client is None: if self.user == 'l_samenv': x = 1 arg = f'{2**4+x}lns{x}' else: arg = self.user.upper() + 'LNS' self._client = Client(self.host, self.port, self.user, arg) # TODO; do we need disconnect? def get_service(self, instance): return instance def start(self, instance): self.connect() self._client.startService(self.get_service(instance)) def restart(self, instance): self.connect() self._client.restartService(self.get_service(instance)) def stop(self, instance): self.connect() self._client.stopService(self.get_service(instance)) def status(self, service): """returns a dict of (, , )""" self.connect() servdict = self._client.getAllServiceInfo().get(service) if not servdict: return {} statedict = servdict['instances'] return {k: STATUS_MAP[v['state']] for k, v in statedict.items()} def reload(self): self.connect() self._client.reloadJobs() def list(self, service='all', prt=print): self.connect() for key, value in self._client.getAllServiceInfo().items(): if service != 'all' and service != key: continue for inst, value in value['instances'].items(): name = '.'.join([key, inst] if inst else [key]) state = STATUS_MAP[value['state']][2].lower() desc = value['desc'] prt(f'{name:30s} {state:12s} {desc}') def do(self, action, other): if action == 'start': self.start(other) elif action == 'restart': self.restart(other) elif action == 'stop': self.stop(other) elif action == 'list': self.list() else: raise ArgError(f'unknown action {action!r}') @classmethod def do_on_ins(cls, args): config = Config() argdict = config.parse_args(cls.argmap, args, cls.otherarg) instrument = argdict.pop('instrument', None) if instrument is None and argdict.get('action') == 'list': # TODO: make more generic if needed instruments = list(config.instruments) else: instruments = [instrument] for ins in instruments: control = cls(ins, config=config) control.do(**argdict)