multiple fixes
This commit is contained in:
+36
@@ -0,0 +1,36 @@
|
||||
# *****************************************************************************
|
||||
# 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 <markus.zolliker@psi.ch>
|
||||
# *****************************************************************************
|
||||
|
||||
from configparser import ConfigParser
|
||||
|
||||
|
||||
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 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)
|
||||
|
||||
@@ -28,15 +28,55 @@ DEFAULT_CFG = """[this]
|
||||
instrument={instrument}
|
||||
frappy_ports = 15000-15009
|
||||
"""
|
||||
MARCHESRC = ['/home/software/marche']
|
||||
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 dict(parser)
|
||||
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):
|
||||
@@ -46,6 +86,7 @@ def write_config(filename, newvalues):
|
||||
parser.read_dict(newvalues)
|
||||
with open(filename, 'w') as f:
|
||||
parser.write(f)
|
||||
change_mode(Path(filename), group='+w')
|
||||
|
||||
|
||||
class ArgError(ValueError):
|
||||
@@ -53,26 +94,41 @@ class ArgError(ValueError):
|
||||
|
||||
|
||||
class Config:
|
||||
this = None
|
||||
|
||||
def __init__(self):
|
||||
configfile = Path('~/.config/linsetools.cfg').expanduser()
|
||||
configfile = Path('/sq_sw/linse/etc/linsetools.cfg')
|
||||
self.sections = read_config(configfile)
|
||||
self.instruments = {}
|
||||
for key, section in self.sections.items():
|
||||
if key == 'this':
|
||||
host = socket.gethostname().split('.')[0]
|
||||
section = {'instrument': host, 'frappy_ports': '15000-15009'}
|
||||
mandatory = set(section)
|
||||
this = self.sections.get('this', {})
|
||||
section.update(this)
|
||||
self.sections['this'] = section
|
||||
if set(this) & mandatory != mandatory:
|
||||
write_config(configfile, self.sections)
|
||||
self.this = section['instrument']
|
||||
self.instruments[self.this] = section
|
||||
else:
|
||||
head, _, tail = key.partition('.')
|
||||
if tail and head == 'instrument':
|
||||
self.instruments[tail] = section
|
||||
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:
|
||||
@@ -109,14 +165,14 @@ for marchedir in MARCHESRC:
|
||||
break
|
||||
|
||||
|
||||
STATUS_MAP = { # values are (<busy: bool), <target state:bool>, name)
|
||||
STATUS_MAP = { # values are (<target state:bool>, <busy:bool>, 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: (False, True, 'RUNNING'),
|
||||
mj.WARNING: (False, True, 'WARNING'),
|
||||
mj.STOPPING: (True, False, 'STOPPING'),
|
||||
mj.RUNNING: (True, False, 'RUNNING'),
|
||||
mj.WARNING: (True, False, 'WARNING'),
|
||||
mj.STOPPING: (False, True, 'STOPPING'),
|
||||
mj.NOT_AVAILABLE: (False, False, 'NOT AVAILABLE'),
|
||||
}
|
||||
|
||||
@@ -125,7 +181,7 @@ def wait_status(cl, service):
|
||||
delay = 0.2
|
||||
while True:
|
||||
sts = cl.getServiceStatus(service)
|
||||
if STATUS_MAP[sts][0]: # busy
|
||||
if STATUS_MAP[sts][1]: # busy
|
||||
if delay > 1.5: # this happens after about 5 sec
|
||||
return False
|
||||
time.sleep(delay)
|
||||
@@ -136,25 +192,30 @@ def wait_status(cl, service):
|
||||
|
||||
class MarcheControl:
|
||||
port = 8124
|
||||
otherarg = None
|
||||
argmap = {k: 'action' for k in ('start', 'restart', 'stop', 'gui', 'cli', 'list', 'listcfg')}
|
||||
|
||||
def __init__(self, host, port=None, user=None, instrument=None):
|
||||
self.config = Config()
|
||||
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.insttrument]
|
||||
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
|
||||
self.argmap = {k: 'action' for k in ('start', 'restart', 'stop', 'gui', 'cli', 'list')}
|
||||
self.argmap.update((k, 'instrument') for k in self.config.instruments)
|
||||
|
||||
def connect(self):
|
||||
if self._client is None:
|
||||
# TODO: may need more generic solution for last arg
|
||||
print(self.host, self.port, self.user)
|
||||
self._client = Client(self.host, self.port, self.user, self.user.upper() + 'LNS')
|
||||
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?
|
||||
|
||||
@@ -186,26 +247,38 @@ class MarcheControl:
|
||||
self.connect()
|
||||
self._client.reloadJobs()
|
||||
|
||||
def _run(self, action, other):
|
||||
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}')
|
||||
|
||||
def run(self, *args):
|
||||
self._run(self.parse_args(*args))
|
||||
|
||||
def parse_args(self, *args):
|
||||
result = {}
|
||||
for arg in args:
|
||||
if arg in self.argmap:
|
||||
kind = self.argmap.get(arg, 'other')
|
||||
if kind in result:
|
||||
raise ArgError(f'duplicate {kind}')
|
||||
result[kind] = arg
|
||||
return result
|
||||
|
||||
@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)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import re
|
||||
from pathlib import Path
|
||||
from .base import MarcheControl, Logger, ArgError
|
||||
from .base import MarcheControl, Logger, ArgError, write_content
|
||||
|
||||
|
||||
WRAPPER_CFG = """interface = '{port}'
|
||||
@@ -10,11 +10,29 @@ overrideNode(interface=interface)
|
||||
WRAPPER_PAT = re.compile(r"interface\s=\s*'(\d*)'\s*\n")
|
||||
|
||||
|
||||
class FrappyMarche(MarcheControl):
|
||||
services = 'main', 'stick', 'addons'
|
||||
class Config:
|
||||
log = None
|
||||
process_file = None
|
||||
|
||||
def __init__(self, instance, host='localhost', port=None, user=None):
|
||||
super().__init__(host, port, user, instance)
|
||||
@classmethod
|
||||
def get(cls, cfgfile):
|
||||
if not cls.process_file:
|
||||
import logging
|
||||
from frappy.config import process_file
|
||||
from frappy.lib import generalConfig
|
||||
generalConfig.init()
|
||||
cls.log = logging.getLogger('linsetools')
|
||||
cls.process_file = process_file
|
||||
return cls.process_file(Path(cfgfile), cls.log)
|
||||
|
||||
|
||||
class FrappyControl(MarcheControl):
|
||||
services = {k: 'service' for k in ('main', 'stick', 'addons')}
|
||||
argmap = dict(MarcheControl.argmap, all='service', **services)
|
||||
otherarg = 'cfg'
|
||||
|
||||
def __init__(self, instance, host='localhost', port=None, user=None, config=None):
|
||||
super().__init__(instance, host, port, user, config)
|
||||
superconfig = self.config.sections['superfrappy']
|
||||
self.instance = instance
|
||||
|
||||
@@ -28,7 +46,6 @@ class FrappyMarche(MarcheControl):
|
||||
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}'
|
||||
@@ -44,6 +61,7 @@ class FrappyMarche(MarcheControl):
|
||||
|
||||
services = [service] if service else list(self.services)
|
||||
services.append('')
|
||||
cfgdirs = cfgdirs or self.cfgdirs
|
||||
|
||||
for servicedir in services:
|
||||
for cfgdir in cfgdirs.split(':'):
|
||||
@@ -61,16 +79,17 @@ class FrappyMarche(MarcheControl):
|
||||
return self.frappy_ports[1]
|
||||
return self.frappy_ports[2:]
|
||||
|
||||
def get_local_ports(self):
|
||||
def get_local_ports(self, run_state=None):
|
||||
"""return dict <port> of (on, busy, cfg)"""
|
||||
self.get_cfg_info()
|
||||
run_state = self.status('frappy')
|
||||
run_state = run_state or 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))
|
||||
on, busy, _ = run_state.get(cfg, (0,0,0))
|
||||
if on or busy:
|
||||
result.setdefault(port, []).append((on, busy, cfg))
|
||||
result.setdefault(int(port), []).append((on, busy, cfg))
|
||||
return {sorted(v)[-1][-1]: k for k, v in result.items()}
|
||||
|
||||
def get_port(self, service):
|
||||
@@ -97,11 +116,13 @@ class FrappyMarche(MarcheControl):
|
||||
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)
|
||||
write_content(self.wrapper_file(cfgname), wrapper_content, group='+rw')
|
||||
|
||||
self.get_cfg_info()
|
||||
log.info('wrapper %r %r', self.wrapper_file(cfgname), wrapper_content)
|
||||
self.reload()
|
||||
log.info('registered %r', cfgname)
|
||||
return f'localhost:{port}'
|
||||
|
||||
def get_cfg_info(self):
|
||||
"""get info from wrapper dir"""
|
||||
@@ -120,8 +141,137 @@ class FrappyMarche(MarcheControl):
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
|
||||
def _run(self, action, service=None, other=None):
|
||||
cfg= other
|
||||
def running(self):
|
||||
"""return a dict <name> of (kind, uri) of running servers"""
|
||||
run_state = self.status('frappy')
|
||||
ports = self.get_local_ports(run_state)
|
||||
result = {}
|
||||
for cfg, port in ports.items():
|
||||
on, busy, _ = run_state.pop(cfg)
|
||||
if on:
|
||||
port = ports.get(cfg)
|
||||
kind = (self.frappy_ports + [port]).index(port)
|
||||
result[cfg] = kind, f'localhost:{port}'
|
||||
return result
|
||||
|
||||
def cli(self):
|
||||
self.get_cfg_info()
|
||||
from frappy.client.interactive import init, interact
|
||||
# from frappy.protocol.discovery import scan
|
||||
# if ins == self.single_ins:
|
||||
# all_nodes = {}
|
||||
# for node in nodes:
|
||||
# host, port = node.split(':')
|
||||
# if host == 'localhost':
|
||||
# host = gethostname()
|
||||
# all_nodes[gethostbyname(host), int(port)] = node
|
||||
# for a in scan():
|
||||
# all_nodes.setdefault((a.address, a.port), f'{a.hostname}:{a.port}')
|
||||
# nodes = list(all_nodes.values())
|
||||
init(*self.cfg_info)
|
||||
interact(appname=self.instrument)
|
||||
|
||||
def gui(self):
|
||||
self.get_cfg_info()
|
||||
nodes = list(self.cfg_info)
|
||||
import logging
|
||||
from frappy.gui.qt import QApplication
|
||||
from frappy.gui.mainwindow import MainWindow
|
||||
|
||||
app = QApplication([])
|
||||
args = type('args', (), dict(detailed=True, node=nodes))
|
||||
win = MainWindow(args, logging.getLogger('gui'))
|
||||
win.show()
|
||||
return app.exec_()
|
||||
|
||||
@staticmethod
|
||||
def get_cfg_details(cfgfile):
|
||||
mods = Config.get(cfgfile)
|
||||
node = mods.pop('node') or {}
|
||||
sea_cfg = None
|
||||
for mod, config in mods.items():
|
||||
cls = config['cls']
|
||||
cls = getattr(cls, '__name__', cls)
|
||||
if cls.endswith('SeaClient'):
|
||||
try:
|
||||
sea_cfg = config['config']['value']
|
||||
except KeyError:
|
||||
sea_cfg = None
|
||||
return node.get('description', '').strip(), sea_cfg
|
||||
|
||||
def all_cfg(self, ins, service, details=False):
|
||||
"""get available cfg files
|
||||
|
||||
:param ins: instance
|
||||
:param service: service nor None for all services
|
||||
:param details: get details about relation to sea
|
||||
:return: set of available cfgs
|
||||
|
||||
implicit results:
|
||||
self.frappy2sea: a dict <frappycfg> of <seacfg> (including extension .config/.stick/.addon)
|
||||
self.sea2frappy: a dict <seacfg> of set of <frappycfg>
|
||||
self.list_info: dict <cfgdir> of <cfg> of <description>
|
||||
"""
|
||||
all_cfg = set()
|
||||
if not ins:
|
||||
return {}
|
||||
if details:
|
||||
self.frappy2sea = f2s = {}
|
||||
self.sea2frappy = s2f = {}
|
||||
self.list_info = list_info = {}
|
||||
for servicedir in [service] if service else self.services:
|
||||
for cfgdir in self.cfgdirs.split(':'):
|
||||
cfgdir = Path(cfgdir) / servicedir
|
||||
for cfgfile in cfgdir.glob('*_cfg.py'):
|
||||
cfg = cfgfile.name[:-7]
|
||||
if details:
|
||||
try:
|
||||
desc, sea_cfg = self.get_cfg_details(cfgfile)
|
||||
except TypeError:
|
||||
raise
|
||||
except Exception as e:
|
||||
sea_cfg = None
|
||||
desc = repr(e)
|
||||
if cfg not in all_cfg:
|
||||
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 listcfg(self, service='', prt=print):
|
||||
self.all_cfg(self.instrument, service, True)
|
||||
seacfgpat = re.compile(r'(.*)(\.config|\.stick|\.addon)')
|
||||
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 = 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(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:
|
||||
prt(f'{gap} ! {len(ambiguous)} ambiguous mappings sea -> frappy')
|
||||
|
||||
def do(self, action=None, service=None, cfg=None):
|
||||
if action == 'start':
|
||||
self.add_frappy_service(service, cfg, None)
|
||||
self.start(cfg)
|
||||
@@ -129,5 +279,13 @@ class FrappyMarche(MarcheControl):
|
||||
self.restart(cfg)
|
||||
elif action == 'stop':
|
||||
self.stop(cfg)
|
||||
elif action == 'list':
|
||||
self.list(service or 'frappy')
|
||||
elif action == 'listcfg':
|
||||
self.listcfg(service)
|
||||
elif action == 'gui':
|
||||
self.gui()
|
||||
elif action in ('cli', None):
|
||||
self.cli()
|
||||
else:
|
||||
raise ArgError(f'unknown action {action!r}')
|
||||
|
||||
Reference in New Issue
Block a user