Compare commits

62 Commits
tmp ... master

Author SHA1 Message Date
886d25ccee allow the use of nicos_gui/nicos_cli
instead of 'nicos gui' / 'nicos cli'
beacuse of possible conflict with existing nicos command
2025-12-18 10:47:10 +01:00
d7b21439ed seaman: fix the case when no recorders are needed 2025-11-19 14:59:14 +01:00
f73f764094 fix setups and 'nicos create' with linse_nicos repo 2025-11-19 14:59:14 +01:00
83f04cf670 try to fix the case the *_ROOT dir is not in sys.path 2025-08-29 11:20:35 +02:00
94e8014a90 fix get_gitea_token 2025-08-07 11:28:23 +02:00
f136d81e44 remove VENV from servicemanager.cfg 2025-07-08 13:14:18 +02:00
77876562f1 add remote_cmd, to be used with python -i getsestuff 2025-07-08 11:28:38 +02:00
acd785061c FrappyMananger.get_cfg_file: service=None as argument returns None 2025-07-08 11:28:01 +02:00
48a45d454b use frappy.config for details when listing cfg files
bare sea_cfg = ... is ignored
2025-07-08 11:15:37 +02:00
41a396d6d9 frappy cli: inse ins == single_ins as criterium for scan 2025-07-07 17:57:56 +02:00
16cd31ccf5 frappy cli returns now all frappy servers in subnet
only on instruments, not onlinse-c
2025-07-07 17:54:02 +02:00
ccc67f2c3f feeder/seweb: restart automatically on reboot
use enable and enable-linger for this
2025-06-24 08:37:00 +02:00
3ac7c9bdca seaman.sea_recorder: change argument to dict 2025-06-18 09:57:00 +02:00
70da667690 FrappyManager.cfg_details: fix 'cfgfile' argument 2025-06-17 16:14:57 +02:00
d235f6d35f add seaman.sea_recorder
+ do_start argument logger=False disables info messages
2025-06-17 11:05:11 +02:00
bcb3a64f8c fix frappy gui call
after change in MainWindow arguments
2025-06-10 08:11:22 +02:00
2a34a276ce use sevenv for frappy 2025-05-21 12:01:04 +02:00
759c96a83b fix bugs for starting seweb/feeder 2025-05-21 11:42:03 +02:00
6b79317a00 create SeaClient link 2025-05-20 14:09:39 +02:00
7d991e7e4e seweb ports on instruments: 8642
currently, the firewall rules allow only a small number of ports
8642 and 8742 were used for the second SEA instance on
hrpt2 and zebra2 - changed them to 8652 and 8752, e.g. they are
now only available within the instrument network.
2025-05-20 11:50:18 +02:00
4aa30d5b39 change password for nicos client 2025-05-20 11:14:09 +02:00
1845f1485a use nicosenv for running bin/nicos 2025-05-20 11:10:15 +02:00
a8cb0c28f6 fix pip upgrade command 2025-05-20 11:04:46 +02:00
16173769ce to create sevenv, start from nicosenv, if available
+ always upgrade pip
2025-05-20 11:02:17 +02:00
19eb8378a7 add sevenv to getsestuff 2025-05-20 10:43:59 +02:00
3b0d272669 start feeder and seweb in sevenv virtualenv 2025-05-20 10:38:44 +02:00
2866eb741c do 'systemctl --user deamon-reload' when service file has changed 2025-05-20 08:54:39 +02:00
96b2f583b6 copy systemd files also when repo already exists 2025-05-20 08:47:38 +02:00
2641138975 copy systemd service files for feeder and seweb 2025-05-20 08:46:02 +02:00
b35004b8b9 allow 'incldue' and 'override' 2025-05-19 18:09:32 +02:00
9a673afb21 atsrt seweb and feeder as systemd user unit 2025-05-19 18:09:02 +02:00
bb95798ff3 remove ssh related stuff 2025-05-08 12:25:05 +02:00
a813132fe9 move amor to the end of the list (is slower) 2025-05-08 11:33:25 +02:00
6d46f2ccd3 move frappy and seweb to gitlab 2025-05-08 11:32:08 +02:00
08afad6b4d mention getsestuff in README.md 2025-05-08 10:25:53 +02:00
512c3ae80f restrict printing of help more
+ remove some disabled code
2025-05-08 09:56:29 +02:00
5685b01ec7 move sea, calcurves and frappy_sinq to gitea 2025-05-08 08:47:51 +02:00
79a93a26df allins.py: scripts for all instruments
- influxdb setup
2025-05-06 12:50:41 +02:00
19b6144310 remove morpheus, add boa 2025-05-06 12:50:11 +02:00
888a898dde create ~/.config when it does not exist 2025-05-06 12:48:12 +02:00
1c3c02ccad add sehistory config to be updated 2025-05-06 10:43:47 +02:00
645a1e1198 update getsestuff (feeder)
+ replace 'service' by 'serv' in cfg/*.cfg
2025-05-02 14:35:12 +02:00
74c4e1ec92 return result in self.get_info() 2025-05-02 14:30:44 +02:00
ea2df3a9a3 cfg/servicemanager.cfg: change sehistory to feeder 2025-05-02 14:23:17 +02:00
09d7d016e1 avoid KeyError when frappy is used with an invalid instrument 2025-05-02 11:54:00 +02:00
dbd7a0433a rename sehistory service to feeder
however, the name of the sehistory repo containing the feeder
is kept
2025-05-02 11:52:12 +02:00
0a324c3f05 treat single instance of seweb / sehistory properly
there is only one seweb / sehistory instance.
care has to be taken as the instrument is not part of the command
2025-04-30 16:13:52 +02:00
f2e01b651d seweb service should be '' 2025-04-30 15:18:45 +02:00
7a09db0eab add global or local as service for sehistory 2025-04-29 15:36:54 +02:00
3beb31cfc7 use still gitlab for seweb 2025-04-29 12:03:52 +02:00
ce37e3727c add seweb and sehistory to bin/ 2025-04-29 11:56:10 +02:00
74684381a6 add sehistory and seweb 2025-04-29 11:52:47 +02:00
a36df58081 move seaweb from init to sewebman.py 2025-04-29 11:37:59 +02:00
18c693d800 add 'sea create' command 2025-04-29 11:36:57 +02:00
7025ec3a22 frappy-cli: support older frappy client 2025-04-29 11:35:37 +02:00
38009190e4 add getsestuff related stuff 2025-04-29 11:18:48 +02:00
a3d2ef3f27 keep gesestuff in servicemanager
- still need to copy to
  /afs/psi.ch/project/sinq/common/stow/markus/bin/
2025-04-29 11:18:39 +02:00
l_samenv
556a7eff9d improve missing arg handling
- better error messages
- missing instrument is only allowed with 'list'
- missing argument to be replaced with ? in error message
- sea cli may now be given with 'graph' or 'sea' for service
2025-01-21 10:52:02 +01:00
fda6e37238 erroneous config files should be accepted in command line
frappyman.is_cfg(config) should not try to run the cfg file
2024-12-11 09:05:00 +01:00
dmc
910f281b0c add nicosenv to PATH before starting
asssure that PATH and therefore python venv ist the same when
starting frappy from NICOS and from commandline
2024-11-26 14:57:23 +01:00
8b13e32ed7 some more cosmetic changes
- fix an intend
- add TODO: remove pkg
- add .before_bott_at to backup of seastatus.tcl
2024-06-27 15:37:59 +02:00
dmc
95a3ff4ffd allow PY in commands, to be replaced with a specific python executable 2024-06-12 09:23:18 +02:00
17 changed files with 1109 additions and 117 deletions

View File

@@ -3,3 +3,5 @@
A manager for starting, stopping and listing services/servers like frappy, nicos and sea. A manager for starting, stopping and listing services/servers like frappy, nicos and sea.
Several instances of nicos, frappy, sea and seweb might run on the same machine. Several instances of nicos, frappy, sea and seweb might run on the same machine.
contains also getsestuff, the script to manage the repos on all instruments

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# ***************************************************************************** # *****************************************************************************
# #
# This program is free software; you can redistribute it and/or modify it under # This program is free software; you can redistribute it and/or modify it under
@@ -27,28 +26,19 @@ this code is currently used:
- from a script allowing to start/stop/list (and more) multiple frappy and nicos servers - from a script allowing to start/stop/list (and more) multiple frappy and nicos servers
""" """
import sys
from servicemanager.base import ServiceManager, ServiceDown, UsageError, get_config from servicemanager.base import ServiceManager, ServiceDown, UsageError, get_config
from servicemanager.nicosman import NicosManager from servicemanager.nicosman import NicosManager
from servicemanager.seaman import SeaManager from servicemanager.seaman import SeaManager
from servicemanager.frappyman import FrappyManager, Reconnect, Keep from servicemanager.frappyman import FrappyManager, Reconnect, Keep
from servicemanager.single import SewebManager, FeederManager
#from servicemanager.racks import RackConfig
class SewebManager(ServiceManager): #rack = RackConfig()
group = 'seweb'
services = ('',)
USAGE = """
Usage:
seaweb list [instance] all = NicosManager, FrappyManager, SeaManager, SewebManager, FeederManager
seaweb start <instance> KINDS = 'action', 'ins', 'service'
seaweb restart <instance>
seaweb stop <instance>
%s
"""
all = NicosManager, FrappyManager, SeaManager, SewebManager
def run(group, arglist): def run(group, arglist):
@@ -64,11 +54,11 @@ def run(group, arglist):
arg_is = { arg_is = {
'action': lambda arg: hasattr(serv, 'do_' + arg), 'action': lambda arg: hasattr(serv, 'do_' + arg),
'ins': lambda arg: arg in serv.info or arg == 'all' or serv.wildcard(arg), 'ins': lambda arg: arg in serv.info or arg in ('all', 'check') or serv.wildcard(arg),
'service': lambda arg: arg in serv.services, 'service': lambda arg: arg in serv.services,
} }
for kind in 'action', 'ins', 'service': for kind in KINDS:
if arglist and arg_is[kind](arglist[0]): # arg is of expected kind if arglist and arg_is[kind](arglist[0]): # arg is of expected kind
args[kind] = arglist.pop(0) args[kind] = arglist.pop(0)
continue continue
@@ -91,8 +81,11 @@ def run(group, arglist):
args.setdefault('ins', serv.main_ins) args.setdefault('ins', serv.main_ins)
if guessed_args: if guessed_args:
args.setdefault('action', 'gui') args.setdefault('action', 'gui')
print('do you mean:\n %s %s %s %s %s' % guessed = [group] + [args.get(k, '') for k in KINDS] + extra
(group, args.get('action', ''), args.get('ins', ''), args.get('service', ''), ' '.join(extra))) while not guessed[-1]:
guessed.pop()
guessed = ' '.join(a or '?' for a in guessed)
print(f'do you mean:\n {guessed}')
else: else:
try: try:
serv.action(args['action'], *serv.treat_args(args, extra + arglist)) serv.action(args['action'], *serv.treat_args(args, extra + arglist))
@@ -101,5 +94,4 @@ def run(group, arglist):
except UsageError as e: except UsageError as e:
serv.do_help() serv.do_help()
print('ERROR:', str(e)) print('ERROR:', str(e))
sys.exit(1)

12
allins.py Normal file
View File

@@ -0,0 +1,12 @@
import os
instruments = ['amor', 'camea', 'dmc', 'eiger', 'focus', 'hrpt', 'boa', 'sans', 'tasp', 'zebra'] # morpheus
def influx_setup(instrument=None):
ins_list = instruments if instrument is None else [instrument]
for ins in ins_list:
print(ins)
os.system(f'ssh {ins} influx setup --username l_samenv --password LIN3601se '
'--token lZpLL_FrPw3cgkeuOWo_DcwjFZerrVTa1QQk6bcjSOYgjQ8W0eyvsp4Z4FULvsz2XEWTZZ8OPengYfLaWU-gdA== '
f'--org linse --bucket sehistory --force --name {ins}')

63
base.py
View File

@@ -24,7 +24,7 @@
import sys import sys
import json import json
import os import os
from os.path import expanduser, basename from os.path import expanduser, basename, exists
import subprocess import subprocess
import time import time
import re import re
@@ -84,6 +84,7 @@ class ServiceManager:
revcmd = {} revcmd = {}
USAGE = None USAGE = None
main_ins = None main_ins = None
single_ins = None
def __init__(self): def __init__(self):
self.env = {} self.env = {}
@@ -146,12 +147,18 @@ class ServiceManager:
nr = section.get(self.group) nr = section.get(self.group)
if nr is not None: if nr is not None:
nr = '%02d' % int(nr) nr = '%02d' % int(nr)
self.commands[ins] = command.replace('~', expanduser('~'))
services = self.get_services(section) services = self.get_services(section)
env = {k: get_subs(section, k, ins, nr) for k in section if k.isupper()} env = {k: get_subs(section, k, ins, nr) for k in section if k.isupper()}
result[ins] = services result[ins] = services
self.env[ins] = env self.env[ins] = env
cmd = command.replace('~', expanduser('~'))
if cmd.startswith('PY '):
cmd = env.get('PY', 'python3') + cmd[2:]
self.commands[ins] = cmd
self.info = result self.info = result
if len(self.info) == 1:
self.single_ins = list(self.info)[0]
return result
#def get_cmdpats(self, groups): #def get_cmdpats(self, groups):
# return self.cmdpats # return self.cmdpats
@@ -207,7 +214,7 @@ class ServiceManager:
match = cmdpat.match(cmd) match = cmdpat.match(cmd)
if match: if match:
gdict = match.groupdict() gdict = match.groupdict()
ins = gdict.get('ins', self.main_ins) ins = gdict.get('ins', self.main_ins) or self.single_ins
serv = gdict.get('serv', '') serv = gdict.get('serv', '')
if cfginfo is not None and 'cfg' in gdict: if cfginfo is not None and 'cfg' in gdict:
cfginfo[ins, serv] = gdict['cfg'] cfginfo[ins, serv] = gdict['cfg']
@@ -219,7 +226,7 @@ class ServiceManager:
or None, when no wildcard character in ins or None, when no wildcard character in ins
""" """
if ins is None or ins == 'all': if not ins or ins == 'all':
return list(self.info) return list(self.info)
pat = re.sub(r'(\.|\*)', '.*', ins) pat = re.sub(r'(\.|\*)', '.*', ins)
if pat == ins: if pat == ins:
@@ -279,9 +286,9 @@ class ServiceManager:
return done return done
def do_stop(self, ins, service=None, *args): def do_stop(self, ins, service=None, *args):
if not ins:
raise UsageError(f'need instrument or "all" to stop all')
self.get_info() self.get_info()
if ins is None:
raise ValueError('use stop all if you really want to stop all')
ins_list = self.wildcard(ins) ins_list = self.wildcard(ins)
if ins_list is not None: if ins_list is not None:
return ins_list return ins_list
@@ -295,31 +302,42 @@ class ServiceManager:
if not ins: if not ins:
raise UsageError('need instance') raise UsageError('need instance')
env = self.env[ins] env = self.env[ins]
return env.get('%s_ROOT' % gr, ''), env startdir = env.get('%s_ROOT' % gr, '')
if startdir not in sys.path:
sys.path.insert(0, startdir)
return startdir, env
def do_start(self, ins, service=None, cfg='', restart=False, wait=False, logger=None, opts=''): def do_start(self, ins, service=None, cfg='', restart=False, wait=False, logger=None, opts=''):
if not ins:
raise UsageError(f'need instrument or "all" to start all')
ins_list = self.wildcard(ins) ins_list = self.wildcard(ins)
if ins_list is not None: if ins_list is not None:
return ins_list return ins_list
if logger is None: if not logger:
class logger: if logger is False:
@staticmethod def info(fmt, *args):
pass
else:
def info(fmt, *args): def info(fmt, *args):
print(fmt % args) print(fmt % args)
@staticmethod def error(fmt, *args):
def error(fmt, *args): print(('ERROR: ' + fmt) % args)
print(('ERROR: ' + fmt) % args)
logger = type('Logger', (), {})
logger.info = info
logger.error = error
if ins is None: if ins is None:
logger.info('nothing to start') logger.info('nothing to start')
return return
try: try:
service_ports = self.get_ins_info(ins) service_ports = self.get_ins_info(ins)
except ValueError: except (KeyError, ValueError):
raise ValueError('do not know %r' % ins) raise UsageError('do not know %r' % ins)
if ins in self.remote_hosts: if ins in self.remote_hosts:
raise ValueError('can not start, %s is running on a remote host' % self.group) raise UsageError('can not start, %s is running on a remote host' % self.group)
services = list(service_ports) if service is None else [service] services = list(service_ports) if service is None else [service]
if restart: if restart:
self.stop(ins, service) self.stop(ins, service)
@@ -336,6 +354,7 @@ class ServiceManager:
services = to_start services = to_start
for service_i in services: for service_i in services:
port = service_ports[service_i] port = service_ports[service_i]
# TODO: remove unused pkg
cmd = self.commands[ins] % dict(ins=ins, serv=service_i, port=port, cfg=cfg, pkg=self.pkg or ins) cmd = self.commands[ins] % dict(ins=ins, serv=service_i, port=port, cfg=cfg, pkg=self.pkg or ins)
if opts: if opts:
cmd = f'{cmd} {opts}' cmd = f'{cmd} {opts}'
@@ -351,8 +370,9 @@ class ServiceManager:
start_dir, env = self.prepare_start(ins, service_i, cfg) start_dir, env = self.prepare_start(ins, service_i, cfg)
env = dict(os.environ, **env, Instrument=ins) env = dict(os.environ, **env, Instrument=ins)
os.chdir(start_dir) os.chdir(start_dir)
if start_dir not in sys.path: nicosenv = '/home/nicos/nicos/nicosenv/bin/'
sys.path.insert(0, start_dir) if exists(nicosenv):
env['PATH'] = f"{nicosenv}:{env['PATH']}"
if wait: if wait:
proc = subprocess.Popen(cmd.split(), env=env) proc = subprocess.Popen(cmd.split(), env=env)
for _ in range(3): for _ in range(3):
@@ -399,8 +419,8 @@ class ServiceManager:
os.chdir(wd) os.chdir(wd)
def do_restart(self, ins, service=None, cfg=None, logger=None): def do_restart(self, ins, service=None, cfg=None, logger=None):
if ins is None: if not ins:
raise UsageError("need instance or 'all' or wildcard") raise UsageError("need instrument or 'all' or wildcard")
ins_list = self.wildcard(ins) ins_list = self.wildcard(ins)
if ins_list is not None: if ins_list is not None:
if cfg is not None: if cfg is not None:
@@ -411,7 +431,7 @@ class ServiceManager:
def do_run(self, ins, service=None, cfg=None, opts=''): def do_run(self, ins, service=None, cfg=None, opts=''):
"""for tests: run and wait""" """for tests: run and wait"""
if self.wildcard(ins) is not None: if self.wildcard(ins) is not None:
raise UsageError('no wildcards allowed with %s run' % self.group) raise UsageError('need instrument and service for "%s run"' % self.group)
if not service: if not service:
try: try:
service, = self.services service, = self.services
@@ -421,6 +441,7 @@ class ServiceManager:
def do_list(self, ins=None, *args): def do_list(self, ins=None, *args):
"""info about running services""" """info about running services"""
self.get_info()
show_unused = ins == 'all' show_unused = ins == 'all'
if show_unused: if show_unused:
ins = None ins = None

8
bin/feeder Executable file
View File

@@ -0,0 +1,8 @@
#!/usr/bin/env python3
import sys
from os.path import expanduser
sys.path.append(expanduser('~'))
from servicemanager import run
run('feeder', sys.argv[1:])

View File

@@ -1,4 +1,4 @@
#!/home/nicos/nicos/nicosenv/bin/python3 #!/home/software/virtualenv/nicosenv/bin/python3
import sys import sys
import time import time
from os import path from os import path
@@ -10,18 +10,18 @@ usage = """
Usage: Usage:
nicos-gui start nicos gui nicos-gui start nicos gui
nicos gui start nicos gui and connect without asking for password nicos_gui start nicos gui and connect without asking for password
nicos-client start nicos command line client nicos-client start nicos command line client
nicos cli start nicos command line client and connect without asking for password nicos_cli start nicos command line client and connect without asking for password
""" """
yr = time.strftime('%y') # yr = time.strftime('%y')
connect = f'user:{yr}lns1@localhost' connect = f'user:sinq@localhost'
if sys.argv[-1] == 'cli': if sys.argv[-1] == 'cli' or sys.argv[0].endswith('cli'):
from nicos.clients.cli import main from nicos.clients.cli import main
sys.exit(main([connect])) sys.exit(main([connect]))
elif sys.argv[-1] == 'gui': elif sys.argv[-1] == 'gui' or sys.argv[0].endswith('gui'):
from nicos.clients.gui.main import main from nicos.clients.gui.main import main
instrument = gethostname().split('.')[0] instrument = gethostname().split('.')[0]
guiconfig = f'{nicosroot}/nicos_sinq/{instrument}/guiconfig.py' guiconfig = f'{nicosroot}/nicos_sinq/{instrument}/guiconfig.py'

11
cfg/feeder@.service Normal file
View File

@@ -0,0 +1,11 @@
[Unit]
Description=sehistory feeder process
[Service]
Type=simple
WorkingDirectory=%h/sehistory
ExecStart=%h/sevenv/bin/python feeder.py -d %i
Restart=always
[Install]
WantedBy=default.target

View File

@@ -0,0 +1,7 @@
[hrpt2]
SEA_SEA_PORT = 8652
SEA_GRAPH_PORT = 8752
sea_command = ./SeaServer %(serv)s_%(ins)s.tcl
sea = 2

11
cfg/sehistory Normal file
View File

@@ -0,0 +1,11 @@
[local]
uri=http://localhost:8086
bucket=sehistory
org=linse
token=lZpLL_FrPw3cgkeuOWo_DcwjFZerrVTa1QQk6bcjSOYgjQ8W0eyvsp4Z4FULvsz2XEWTZZ8OPengYfLaWU-gdA==
[central]
uri=http://linse-c:8086
bucket=sehistory
org=linse
token=ggo2cRoCIkgniXdUGi-s6oxXJPPDzgKEFHh6PXuBg1QQa3aBo36tGFP_6cf50FRNoDQgGcelh3xBM5QsVw4rHA==

33
cfg/servicemanager.cfg Normal file
View File

@@ -0,0 +1,33 @@
[DEFAULT]
FRAPPY_MAIN_PORT = 151nr
FRAPPY_STICK_PORT = 152nr
FRAPPY_ADDONS_PORT = 153nr
FRAPPY_ROOT = ~/frappy
#FRAPPY_HISTORY = ~/frappy/history/
FRAPPY_CONFDIR = ~/frappy/cfg/<SERV>:~/frappy/cfg/develop
FRAPPY_LOGDIR = ~/frappylog
FRAPPY_PIDDIR = ~/frappylog/pid
FRAPPY_SEA_DIR = ~/frappy/cfg/sea
FRAPPY_CALIB_PATH = ~/calcurves
frappy_command = ~/sevenv/bin/python bin/frappy-server %(ins)s_%(serv)s -p=%(port)s -c=%(cfg)s
SEA_SEA_PORT = 8641
SEA_GRAPH_PORT = 8741
SEA_ROOT = ~/sea
sea_command = ./SeaServer %(serv)s.tcl
SEWEB_ROOT = ~/seweb
SEWEB_PORT = 8642
seweb_command = ~/sevenv/bin/python secop-webserver %(port)s -i %(ins)s
FEEDER_ROOT = ~/sehistory
FEEDER_PORT = 0
feeder_command = ~/sevenv/bin/python feeder.py -d %(serv)s
PYTHONPATH = ~:~/frappy
[MAIN]
frappy = 1
sea = 1
seweb = 1
feeder = 1

11
cfg/seweb@.service Normal file
View File

@@ -0,0 +1,11 @@
[Unit]
Description=SE web server
[Service]
Type=simple
WorkingDirectory=%h/seweb
ExecStart=%h/sevenv/bin/python secop-webserver 8642 -i %i
Restart=always
[Install]
WantedBy=default.target

View File

@@ -0,0 +1,7 @@
[zebra2]
SEA_SEA_PORT = 8652
SEA_GRAPH_PORT = 8752
sea_command = ./SeaServer %(serv)s_%(ins)s.tcl
sea = 2

View File

@@ -24,7 +24,9 @@ import os
import re import re
import builtins import builtins
from glob import glob from glob import glob
from socket import gethostbyname, gethostname
from itertools import zip_longest from itertools import zip_longest
from pathlib import Path
from collections import defaultdict from collections import defaultdict
from os.path import join, isdir, basename, expanduser, exists from os.path import join, isdir, basename, expanduser, exists
from configparser import ConfigParser from configparser import ConfigParser
@@ -36,30 +38,24 @@ MAIN = 1
STICK = 2 STICK = 2
class Namespace(dict): class Config:
def __init__(self): log = None
self['Node'] = self.node process_file = None
self['Mod'] = self.mod
for fun in 'Param', 'Command', 'Group':
self[fun] = self.dummy
self.init()
def init(self): @classmethod
self.description = '' def get(cls, cfgfile):
self.sea_cfg = None if not cls.process_file:
import logging
def node(self, equipment_id, description, *args, **kwds): try:
self.description = description from frappy.config import process_file
except Exception as e:
def mod(self, name, cls, description, config=None, **kwds): print(sys.path)
cls = getattr(cls, '__name__', cls) raise
if cls.endswith('SeaClient'): from frappy.lib import generalConfig
self.sea_cfg = config generalConfig.init()
cls.log = logging.getLogger('frappyman')
def dummy(self, *args, **kwds): cls.process_file = process_file
return None return cls.process_file(Path(cfgfile), cls.log)
__builtins__ = builtins
SEAEXT = {'main': '.config', 'stick': '.stick'} SEAEXT = {'main': '.config', 'stick': '.stick'}
@@ -114,16 +110,19 @@ class FrappyManager(ServiceManager):
cfgpaths = [] cfgpaths = []
cfgparser = ConfigParser() cfgparser = ConfigParser()
cfgparser.optionxform = str cfgparser.optionxform = str
cfgfile = self.env[ins].get('FRAPPY_CONFIG_FILE') env = self.env.get(ins, {})
confdir = self.env[ins].get('FRAPPY_CONFDIR') cfgfile = env.get('FRAPPY_CONFIG_FILE')
confdir = env.get('FRAPPY_CONFDIR')
if cfgfile: if cfgfile:
cfgfile = self.env[ins]['FRAPPY_CONFIG_FILE'].replace('<SERV>', service) cfgfile = cfgfile.replace('<SERV>', service)
cfgparser.read(cfgfile) cfgparser.read(cfgfile)
try: try:
section = cfgparser['FRAPPY'] section = cfgparser['FRAPPY']
except KeyError: except KeyError:
raise ValueError('%s does not exist or has no FRAPPY section' % cfgfile) raise ValueError('%s does not exist or has no FRAPPY section' % cfgfile)
confdir = section.get('confdir', confdir) confdir = section.get('confdir', confdir)
if not confdir:
return []
for cfgpath in confdir.split(os.pathsep): for cfgpath in confdir.split(os.pathsep):
if cfgpath.endswith('<SERV>'): if cfgpath.endswith('<SERV>'):
cfgpaths.append(expanduser(cfgpath[:-6] + service)) cfgpaths.append(expanduser(cfgpath[:-6] + service))
@@ -138,9 +137,9 @@ class FrappyManager(ServiceManager):
start_dir, env = super().prepare_start(ins, service) start_dir, env = super().prepare_start(ins, service)
env_update = {} env_update = {}
for key, value in env.items(): for key, value in env.items():
if '<SERV>' in value: if '<SERV>' in value:
env_update[key] = value.replace('<SERV>', service) env_update[key] = value.replace('<SERV>', service)
os.environ[key] = env[key] os.environ[key] = env[key]
cfgpaths = self.config_dirs(ins, service) cfgpaths = self.config_dirs(ins, service)
if cfgpaths: if cfgpaths:
env_update['FRAPPY_CONFDIR'] = os.pathsep.join(cfgpaths) env_update['FRAPPY_CONFDIR'] = os.pathsep.join(cfgpaths)
@@ -170,9 +169,7 @@ class FrappyManager(ServiceManager):
return [i for i in ins_list if i in cfgs] return [i for i in ins_list if i in cfgs]
def get_nodes(self, ins='', service=None): def get_nodes(self, ins='', service=None):
start_dir = ServiceManager.prepare_start(self, ins, None)[0] ServiceManager.prepare_start(self, ins, None)
if start_dir not in sys.path:
sys.path.insert(0, start_dir)
nodes = [] nodes = []
services = self.services if service is None else [service] services = self.services if service is None else [service]
for service in services: for service in services:
@@ -199,39 +196,66 @@ class FrappyManager(ServiceManager):
from frappy.gui.mainwindow import MainWindow from frappy.gui.mainwindow import MainWindow
app = QApplication([]) app = QApplication([])
win = MainWindow(nodes, logging.getLogger('gui')) args = type('args', (), dict(detailed=True, node=nodes))
win = MainWindow(args, logging.getLogger('gui'))
win.show() win.show()
return app.exec_() return app.exec_()
def do_cli(self, ins='', service=None): def do_cli(self, ins='', service=None):
nodes = self.get_nodes(ins, service) nodes = self.get_nodes(ins, service)
from frappy.client.interactive import init, interact 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(*nodes) init(*nodes)
interact() try:
interact(appname=ins)
except TypeError: # older frappy client
interact()
@staticmethod @staticmethod
def get_cfg_details(namespace, cfgfile): def get_cfg_details(cfgfile):
# get sea_cfg option from frappy cfg file mods = Config.get(cfgfile)
namespace.init() node = mods.pop('node') or {}
local = {} sea_cfg = None
with open(cfgfile, encoding='utf-8') as f: for mod, config in mods.items():
exec(f.read(), namespace, local) cls = config['cls']
return namespace.description, local.get('sea_cfg', namespace.sea_cfg) 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 cfg_details(self, ins, service, cfg): def cfg_details(self, ins, service, cfgfile):
namespace = Namespace() if cfgfile:
return self.get_cfg_details(cfgfile)
raise FileNotFoundError(f'{cfgfile} not found')
def get_cfg_file(self, ins, service, cfg, lazy=False):
if service is None:
return None
filenames = [f'{cfg}_cfg.py']
if lazy:
filenames.extend([f'{cfg}.py', cfg])
for cfgdir in self.config_dirs(ins, service): for cfgdir in self.config_dirs(ins, service):
cfgfile = join(cfgdir, f'{cfg}_cfg.py') for filename in filenames:
if exists(cfgfile): cfgfile = join(cfgdir, filename)
return self.get_cfg_details(namespace, cfgfile) if exists(cfgfile):
raise FileNotFoundError(f'{cfg} not found') return cfgfile
return None
def is_cfg(self, ins, service, cfg): def is_cfg(self, ins, service, cfg):
try: return bool(self.get_cfg_file(ins, service, cfg))
self.cfg_details(ins, service, cfg)
return True
except Exception:
return False
def all_cfg(self, ins, service, details=False): def all_cfg(self, ins, service, details=False):
"""get available cfg files """get available cfg files
@@ -249,7 +273,6 @@ class FrappyManager(ServiceManager):
all_cfg = set() all_cfg = set()
if not ins: if not ins:
return {} return {}
namespace = Namespace()
if details: if details:
self.frappy2sea = f2s = {} self.frappy2sea = f2s = {}
self.sea2frappy = s2f = {} self.sea2frappy = s2f = {}
@@ -260,7 +283,9 @@ class FrappyManager(ServiceManager):
cfg = basename(cfgfile)[:-7] cfg = basename(cfgfile)[:-7]
if details: if details:
try: try:
desc, sea_cfg = self.get_cfg_details(namespace, cfgfile) desc, sea_cfg = self.get_cfg_details(cfgfile)
except TypeError:
raise
except Exception as e: except Exception as e:
sea_cfg = None sea_cfg = None
desc = repr(e) desc = repr(e)
@@ -278,6 +303,7 @@ class FrappyManager(ServiceManager):
def do_listcfg(self, ins='', service='', prt=print): def do_listcfg(self, ins='', service='', prt=print):
if not ins: if not ins:
raise UsageError('missing instance') raise UsageError('missing instance')
ServiceManager.prepare_start(self, ins, service)
self.all_cfg(ins, service, True) self.all_cfg(ins, service, True)
seacfgpat = re.compile(r'(.*)(\.config|\.stick|\.addon)') 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) keylen = max((max(len(k) for k in cfgs) for cfgs in self.list_info.values()), default=1)
@@ -320,7 +346,7 @@ class FrappyManager(ServiceManager):
argdict['service'] = cfg argdict['service'] = cfg
return super().treat_args(argdict, (), extra) return super().treat_args(argdict, (), extra)
if (',' in cfg or cfg.endswith('.cfg') or if (',' in cfg or cfg.endswith('.cfg') or
self.is_cfg(argdict.get('ins'), argdict.get('service'), cfg)): self.get_cfg_file(argdict.get('ins'), argdict.get('service'), cfg, True)):
return super().treat_args(argdict, (), [cfg] + extra) return super().treat_args(argdict, (), [cfg] + extra)
return super().treat_args(argdict, unknown, extra) return super().treat_args(argdict, unknown, extra)
@@ -404,7 +430,6 @@ class FrappyManager(ServiceManager):
if proposed_addons: # and set(proposed_addons) != set(running_addons): if proposed_addons: # and set(proposed_addons) != set(running_addons):
proposed_cfg['addons'] = {','.join(proposed_addons)} proposed_cfg['addons'] = {','.join(proposed_addons)}
self._debug = {}
for service in FrappyManager.services: for service in FrappyManager.services:
given = givencfgs.get(service) given = givencfgs.get(service)
running = self.frappy_cfgs.get(service) running = self.frappy_cfgs.get(service)
@@ -413,8 +438,6 @@ class FrappyManager(ServiceManager):
if running: if running:
self.state[f'frappy {service}'] = running self.state[f'frappy {service}'] = running
self._debug[service] = (seaconfig, available, running, running in self.frappy2sea)
if seaconfig and (available or running in self.frappy2sea): if seaconfig and (available or running in self.frappy2sea):
# we get here when the sea server is running and either at least one of: # we get here when the sea server is running and either at least one of:
# - the sea config is matching any frappy cfg # - the sea config is matching any frappy cfg

673
getsestuff Executable file
View File

@@ -0,0 +1,673 @@
#!/usr/bin/env python3
import sys
import os
import time
from os import chdir, environ, getcwd
from ast import literal_eval
from configparser import ConfigParser
from os.path import expanduser, exists, join, basename, realpath, isdir
from subprocess import Popen, PIPE, check_output
from glob import glob
from socket import gethostname
instruments = ['camea', 'dmc', 'eiger', 'focus', 'hrpt', 'sans', 'tasp', 'zebra', 'boa', 'amor']
stuffsrc = '/afs/psi.ch/project/sinq/common/lib/servicemanager/'
doit = True # False: check only
sim = False # True: show what to do
action = ''
todo = set()
home = expanduser('~')
hostname = gethostname().split('.')[0]
remote = hostname not in instruments
if home.endswith('zolliker'):
chdir(expanduser('~/servicemanager/'))
cfg_fil = '/afs/psi.ch/project/sinq/common/stow/markus/lib/servicemanager/', glob('cfg/*.cfg')
bin_dst = '/afs/psi.ch/project/sinq/common/stow/markus/bin/', ['getsestuff']
diff = False
for dstdir, files in cfg_fil, bin_dst:
for src in files:
diff = os.system(f'diff {src} {dstdir}')
if diff and os.system(f'cp {src} {dstdir}'):
print('can not copy', src, 'to', dstdir)
if diff:
print('updated getsestuff, please do again')
sys.exit(0)
if remote:
instrument = ''
nicosroot = ''
nicosenv = ''
else:
nicosroot = '/home/nicos/nicos'
if not exists(f'{nicosroot}/.git/config'):
print('nicos repo not found at', nicosroot)
nicosroot = ''
nicosenv = '/home/software/virtualenv/nicosenv'
instrument = environ.get('Instrument')
if instrument is None:
instrhome = '/home/%s' % hostname
if exists(instrhome):
instrument = hostname
else:
instrhome = '/home/%s' % instrument
if not exists(instrhome):
instrhome = home
if not exists(instrhome) or (instrhome != home and not nicosroot.startswith(home)):
print(instrhome, exists(instrhome), home, nicosroot)
print('can not guess instrument, please define environment variable "Instrument"')
instrument = ''
def doget(cmd):
out = Popen(cmd.split(), stdout=PIPE).communicate()[0]
return list(out.decode().split('\n'))
def docmd(cmd):
lines = doget(cmd)
print('\n'.join(lines), end='')
def from_str(string):
try:
return literal_eval(string)
except Exception as e:
return string.strip()
def scramble(arg):
return bytes([(158 - b) for b in arg.encode('ascii')]).decode('ascii')
def do(cmd):
print('>', cmd)
if not sim:
return not os.system(cmd)
return True
def docopy(src, dst):
if not os.system(f'diff {dst} {src}'):
return False
if doit:
if not do(f'cp {src} {dst}'):
do(f'mv {dst} {dst}0')
do(f'cp {src} {dst}')
else:
todo.add(action)
return True
def dolink(dst, src):
src = realpath(expanduser(src))
dst = realpath(expanduser(dst))
if exists(dst):
if src != dst:
if doit:
do(f'ln -sf {dst} {src}')
else:
todo.add(action)
print('%s !-> %s' % (src, dst))
else:
print(f'target {dst} does not exist')
def ch_repo_dir(gitdir):
"""change working directory to repo dir"""
# git complains when using it at a directory where we have no write access
# this can be avoided with adding the command below
chdir(gitdir)
if not os.access(gitdir, os.W_OK):
entry = ['directory', gitdir]
with open(join(home, ".gitconfig")) as f:
for line in f:
if entry == [v.strip() for v in line.split('=')]:
return
docmd(f'git config --global --add safe.directory {gitdir}')
def check_repo(root, repo, url=None, branch=None):
chdir(root)
created = exists(join(repo, '.git'))
if url is None:
url = f'https://gitea.psi.ch/linse/{repo}.git'
elif url == 'gitlab':
url = f'git@gitlab.psi.ch-samenv:samenv/{repo}.git'
pull = False
if (doit or sim) and not created:
todo.add(action)
short = repo.rpartition('/')[2]
if exists(repo):
chdir(repo)
gitconfig = {}
for line in doget('git config --list'):
key, _, value = line.partition('=')
if value:
gitconfig[key] = value
if gitconfig.get('remote.origin.url') != 'url':
print(gitconfig.get('remote.origin.url'), 'does not match', url)
print(f'{join(root, repo)} exists already')
return False
else:
do('git clone %s' % url)
pull = True
created = doit
if 'gitea' in url:
hooksrc = '/afs/psi.ch/project/sinq/common/stow/markus/lib/gitea'
docopy(f'{hooksrc}/get_gitea_token', '.git/hooks/')
docopy(f'{hooksrc}/pre-commit', '.git/hooks/')
if created:
chdir(join(root, repo))
docmd('git remote update')
gitconfig = {}
for line in doget('git config --list'):
key, _, value = line.partition('=')
if value:
gitconfig[key] = value
prev = dict(gitconfig)
gitconfig['push.default'] = 'upstream'
gitconfig['pull.rebase'] = 'True'
gitconfig['remote.origin.url'] = url
if 'gitea' in url:
# hooksrc = '/afs/psi.ch/project/sinq/common/stow/markus/lib/gitea'
# docopy(f'{hooksrc}/get_gitea_token', '.git/hooks/')
# docopy(f'{hooksrc}/pre-commit', '.git/hooks/')
gitconfig['credential.helper'] = f'{getcwd()}/.git/hooks/get_gitea_token'
diff = {k: v for k, v in gitconfig.items() if v != prev.get(k) and v != prev.get(k.replace('branch.', ''))}
if doit:
for kv in diff.items():
do('git config %s %s' % kv)
elif diff:
todo.add(action)
print('modified git config keys:')
for key, val in diff.items():
print('%s=%s -> %s' % (key, prev.get(key), val))
if branch:
for line in doget('git rev-parse --abbrev-ref HEAD'):
if branch != line and line:
print(f'wrong branch: {line}')
return False
pull = False
show_status = True
for line in doget('git status'):
if '"git pull"' in line:
pull = doit
todo.add(action)
break
elif 'working tree clean' in line:
show_status = False
if show_status:
docmd('git status')
else:
todo.add(action)
print('not yet created', action)
return pull
SSH_CONFIG = """Host gitlab.psi.ch-samenv
HostName gitlab.psi.ch
User zolliker
IdentityFile ~/.ssh/id_rsa_samenv
"""
def do_ssh():
"""ssh config for pushing git to samenv repo"""
chdir(home)
try:
skip = False
with open('.ssh/config') as f:
content = f.read()
except FileNotFoundError:
content = ''
lines = []
for line in content.split('\n'):
if line.startswith('Host gitlab.psi.ch-samenv'):
skip = True
elif line[:1] not in ' \t':
skip = False
if not skip:
lines.append(line)
lines.append(SSH_CONFIG)
result = '\n'.join(lines)
if result == content:
pass
elif sim:
todo.add(action)
print('--- .ssh/config current:')
print(content)
print('--- .ssh/config new:')
print(result)
print('---')
elif not doit:
todo.add(action)
print('.ssh/config needs update')
else:
with open('.ssh/config', 'w') as f:
f.write(result)
if not exists('.ssh/id_rsa_samenv'):
if doit:
do('scp l_samenv@samenv:.ssh/id_rsa .ssh/id_rsa_samenv')
do('chmod g-r-x .ssh/id_rsa_samenv')
else:
todo.add(action)
print('missing id_rsa_samenv')
return
if not doget('ls -l .ssh/id_rsa_samenv')[0].startswith('-rw------'):
todo.add(action)
do('chmod -x .ssh/id_rsa_samenv')
do('chmod g-r-w .ssh/id_rsa_samenv')
do('chmod o-r-w .ssh/id_rsa_samenv')
def do_sshnicos():
do_ssh()
def do_sevenv():
"""install python packages needed for seweb etc."""
sevenv = join(home, 'sevenv')
if not exists(join(sevenv, 'bin')):
if exists(nicosenv):
# start from nicos env if possible
py = join(nicosenv, 'bin/python')
else:
py = 'python3'
do(f'{py} -m venv {sevenv}')
if exists(join(sevenv, 'bin')):
upgrade = True
for pkg in ['mlzlog', 'scipy', 'psutil', 'flask', 'gevent', 'influxdb_client']:
if not glob(f'{sevenv}/lib/python3*/site-packages/{pkg}'):
if doit:
if upgrade:
do(f'{sevenv}/bin/python3 -m pip install --upgrade pip')
upgrade = False
do(f'{sevenv}/bin/python3 -m pip install {pkg}')
else:
print(f'missing {pkg} in sevenv')
def do_frappy():
"""Frappy framework"""
frappydir = join(home, 'frappy')
if exists(frappydir) and not exists(join(frappydir, '.git')):
do('rm -rf %s' % frappydir)
if check_repo(home, 'frappy'):
do('git pull')
if exists(join(frappydir, '.git')):
sys.path.extend(glob(f'{nicosenv}/lib/*/site-packages'))
try:
missing = 'psutil'
import psutil
missing = 'mlzlog'
import mlzlog
except ImportError:
print('MISSING', missing, 'in nicosenv (do it manually!)')
# if doit:
# do('pip3 install --user psutil')
# do('pip3 install --user mlzlog')
# else:
# todo.add(action)
# print('missing', missing, 'in nicosenv')
def do_servicemanager():
"""servicemanager package"""
if check_repo(home, 'servicemanager', None, 'master'):
do('git pull')
def do_feeder():
"""history feeder"""
if check_repo(home, 'sehistory', None, 'master'):
do('git pull')
configdir = join(home, '.config')
systemddir = join(configdir, 'systemd/user')
if not exists(systemddir):
do(f'mkdir -p {systemddir}')
docopy(join(home, 'servicemanager/cfg/sehistory'), join(configdir, 'sehistory'))
if docopy(join(home, 'servicemanager/cfg/feeder@.service'),
join(systemddir, 'feeder@.service')):
do('systemctl --user daemon-reload')
def do_seweb():
"""SE web client"""
if check_repo(home, 'seweb', None, 'master'):
do('git pull')
systemddir = join(home, '.config/systemd/user')
if not exists(systemddir):
do(f'mkdir -p {systemddir}')
if docopy(join(home, 'servicemanager/cfg/seweb@.service'),
join(systemddir, 'seweb@.service')):
do('systemctl --user daemon-reload')
def do_sea():
"""SEA server scripts"""
if check_repo(home, 'sea'):
do('git pull')
# do('git checkout master -- .gitignore') # do not know why this is needed
# do('chmod -x tcl/config/json_racklist tcl/*.* tcl/*.tcl tcl/*/*.tcl')
if not os.access('tcl/luft.tclsh', os.X_OK):
do('chmod +x tcl/luft.tclsh')
if not exists('tcl/plugin'):
do('git checkout master -- tcl/plugin')
if not exists('tcl/calcurves/.git') and not exists('tcl/calcurves/coil.inp'):
do('rm -rf tcl/calcurves')
do_calcurves()
docopy('/afs/psi.ch/user/z/zolliker/public/git.rhel7/sics/SeaServer', join(home, 'sea', 'SeaServer'))
dolink('/afs/psi.ch/project/sinq/rhel7/bin/SeaClient', join(home, 'bin', 'SeaClient'))
def do_calcurves():
"""calibration curves"""
if check_repo(home, 'calcurves'):
do('git pull')
dolink('~/calcurves', '~/sea/tcl/calcurves')
def do_frappysinq():
"""nicos_sinq/frappy_sinq setups and extensions"""
if exists(nicosroot):
if check_repo(join(nicosroot, 'nicos_sinq'), 'frappy_sinq'):
do('git pull')
def do_nicosconf():
"""nicos.conf"""
# do the change in two places, as there is not always a link
for nicosconf in [join(nicosroot, 'nicos.conf'),
join(nicosroot, 'nicos_sinq', instrument, 'nicos.conf')]:
if not exists(nicosconf):
continue
cp = ConfigParser()
cp.optionxform = str
cp.read(nicosconf)
assert from_str(cp['nicos']['instrument']) == instrument
setup_subdirs = from_str(cp['nicos'].get('setup_subdirs', '["%s"]' % instrument))
new_subdirs = setup_subdirs[:]
if "frappy_sinq" not in new_subdirs:
print("missing frappy_sinq in setup_subdirs")
new_subdirs.append("frappy_sinq")
todo.add(action)
if "frappy" in new_subdirs:
print("superflous frappy in setup_subdirs")
new_subdirs.remove("frappy")
todo.add(action)
pythonpath = from_str(cp['environment'].get('PYTHONPATH', '""'))
pythonpath = pythonpath.split(':') if pythonpath else []
newpath = pythonpath[:]
frappyhome = join(instrhome, 'frappy')
localpy = join(instrhome, '.local/lib/python3.6/site-packages')
if frappyhome not in newpath:
print("missing %s in PYTHONPATH" % frappyhome)
newpath.append(frappyhome)
todo.add(action)
if instrhome not in newpath:
print("missing %s in PYTHONPATH" % instrhome)
newpath.append(instrhome)
todo.add(action)
if localpy in newpath:
print("remove %s from PYTHONPATH" % localpy)
newpath.remove(localpy)
todo.add(action)
if doit:
dirty = False
if new_subdirs != setup_subdirs:
cp['nicos']['setup_subdirs'] = '["%s"]' % '", "'.join(new_subdirs)
dirty = True
if newpath != pythonpath:
cp['environment']['PYTHONPATH'] = '"%s"' % ':'.join(newpath)
dirty = True
if dirty and not sim:
print('modify', nicosconf)
with open(nicosconf, 'w') as f:
cp.write(f)
def do_nicosenv():
"""install python packages needed for frappy/servicemanager"""
chdir(nicosroot)
for pkg in ['mlzlog', 'scipy']:
if not glob(f'{nicosenv}/lib/python3*/site-packages/{pkg}'):
if doit:
do(f'{nicosenv}/bin/python3 -m pip install {pkg}')
else:
print(f'missing {pkg} in nicosenv')
def do_nicos_pick():
"""OBSOLETE: cherry-pick all new commits in sinq-3.11 related to nicos/devices/secop"""
chdir(nicosroot)
docmd('git fetch sinq')
def get_change_ids(branch):
command = f'git log --since=2023-09-01 {branch} nicos/devices/secop'
ps = Popen(command.split(), stdout=PIPE)
result = []
try:
for line in check_output(('grep', 'Change-Id:'), stdin=ps.stdout).decode('latin-1').split('\n'):
line = line.strip().split()
if len(line) == 2:
result.append(line[1])
except Exception as e:
print(repr(e))
ps.wait()
return result
src = 'sinq/sinq-3.11'
dst = ' '
srcids = get_change_ids(src)
dstids = get_change_ids(dst)
dstset = set(dstids)
for changeid in reversed(srcids):
if changeid not in dstset:
line = check_output(f'git log --oneline {src} --grep={changeid}'.split()).decode('latin-1').strip()
if line:
if doit:
commit = line.split()[0]
docmd(f'git cherry-pick {commit}')
else:
todo.add(action)
print(line)
BIN = """#!%s
import sys
sys.path.append('%s')
from servicemanager import run
run('%s', sys.argv[1:])
"""
def do_bin():
"""added commands"""
chdir(home)
if not exists('bin'):
do('mkdir -p bin')
pgms = ['frappy', 'sea', 'seweb', 'feeder']
if nicosroot:
executable = f'{nicosenv}/bin/python3'
else:
executable = '/usr/bin/env python3.11'
pgms.append('nicos')
for pgm in pgms:
content = BIN % (executable, home, pgm)
binfile = join(home, 'bin', pgm)
try:
with open(binfile) as f:
writeit = f.read() != content
except FileNotFoundError:
writeit = 'create'
if writeit:
print('modify', binfile)
if doit and not sim:
if writeit != 'create':
os.remove(binfile)
with open(binfile, 'w') as f:
f.write(content)
do(f'chmod +x {binfile}')
if nicosroot:
dolink('~/servicemanager/bin/nicos_sinq', '~/bin/nicos')
def do_scfg():
"""cfg file for sea and frappy service"""
chdir(home)
insfile = f'{stuffsrc}/{instrument}_servicemanager.cfg'
srcfile = f'{stuffsrc}/servicemanager.cfg'
if exists(insfile):
if doit:
os.system(f'cat {srcfile} {insfile} > servicemanager.cfg')
else:
todo.add(action)
os.system(f'cat {srcfile} {insfile} | diff servicemanager.cfg -')
return
docopy(srcfile, 'servicemanager.cfg')
def remove_line(file, content):
if os.system('grep %s %s' % (content, file)) == 0:
if doit:
do('grep -v %s %s > %s_new' % (content, file, file))
do('mv %s_new %s' % (file, file))
else:
todo.add(action)
print('remove above')
selected_instruments = None
action_arg = ''
help = True
for arg in sys.argv[1:]:
if arg in instruments:
if remote:
selected_instruments = [arg]
elif arg != instrument:
raise ValueError(f'bad instrument: {arg}')
elif arg == 'allin':
if not remote:
raise ValueError(f'"allin" is only allowed on samenv')
selected_instruments = instruments
elif arg == 'check':
doit = False
elif arg == 'nohelp':
help = False
elif action_arg:
raise ValueError('only one action is allowed')
else:
action_arg = arg
nc_actions = ['frappysinq', 'nicosconf', 'nicosenv'] # 'sshnicos', 'nicos_pick'
ncactionfuncs = {}
with_su = False
if nicosroot.startswith(home):
actions = nc_actions
nc_actions = []
else:
if nicosroot or remote:
with_su = action_arg in nc_actions
for a in nc_actions:
ncactionfuncs[a] = locals()['do_%s' % a]
actions = ['bin', 'scfg', 'frappy', 'servicemanager', 'sea',
'calcurves', 'feeder', 'seweb', 'sevenv'] # 'ssh'
actionfuncs = {}
for a in actions:
actionfuncs[a] = locals()['do_%s' % a]
# if nicosroot:
# for a in nc_actions:
# actionfuncs['su %s' % a] = locals()['do_%s' % a]
def print_help():
if not help:
return
if remote:
inst = "<instrument> "
else:
inst =''
def list_actions(funcs, postfix=''):
return '\n'.join(f" getsestuff {inst}%-15s # %s%s" % (a, f.__doc__, postfix) for a, f in funcs.items()) + '\n'
print(f"""-----
getsestuff {inst} # status
getsestuff {inst}all # install all
getsestuff {inst}sim # show commands to do
{list_actions(actionfuncs)}{list_actions(ncactionfuncs, ' *')}""", end='')
print(' ' * (32 + len(inst)), '* executed as user nicos')
if remote:
print(" replace <instrument> by allin for applying to all instruments")
def remote_cmd(command, instlist=None):
# to be used with "python -i getsestuff"
for inst in instlist or instruments:
print(f'===== {inst} ' + '=' * (70-len(inst)))
os.environ['SSHPASS'] = inst.upper()+'LNS'
docmd(f'sshpass -e ssh {inst}@{inst} {command}')
if remote:
if not doit:
action_arg = 'check ' + action_arg
if selected_instruments is None:
print_help()
else:
remote_cmd(f'getsestuff {action_arg} nohelp', selected_instruments)
sys.exit(0)
def su_action(action):
os.environ['SSHPASS'] = scramble('P[d20+2:1eh')
docmd(f'sshpass -e ssh nicos@localhost {sys.argv[0]} {action} nohelp')
if with_su:
su_action(action_arg)
sys.exit(0)
if action_arg == 'sim':
sim = True
doit = False
elif action_arg in actions:
action = action_arg
actionfuncs[action]()
sys.exit()
elif action_arg != 'all':
if action_arg != '':
print(f'unknown action {action}, not in', actions)
print_help()
sys.exit(1)
doit = False
print(f'===== user {os.environ.get("USER", home)}')
for action, func in actionfuncs.items():
print(f'----- {action} ({home})')
func()
if action_arg in ('', 'all', 'sim') and nc_actions:
su_action(action_arg)
print_help()
sys.exit(0)
print_help()
if sim or not action_arg and todo:
print('needs updates: %s' % ' '.join(todo))

View File

@@ -84,6 +84,7 @@ class NicosManager(ServiceManager):
print('ambiguos package: %s' % ', '.join(instdir)) print('ambiguos package: %s' % ', '.join(instdir))
else: else:
env['NICOS_PACKAGE'] = basename(dirname(instdir[0])) env['NICOS_PACKAGE'] = basename(dirname(instdir[0]))
return self.info
def do_create(self, ins, *args): def do_create(self, ins, *args):
if ins == 'check': if ins == 'check':
@@ -95,13 +96,13 @@ class NicosManager(ServiceManager):
self.get_info() self.get_info()
for ins_i in ins_list: for ins_i in ins_list:
env = self.env[ins_i] env = self.env[ins_i]
base = join(env['NICOS_ROOT'], env['NICOS_PACKAGE'], ins) base = join(env['NICOS_ROOT'], env['NICOS_PACKAGE'], ins_i)
nicos_conf = join(base, 'nicos.conf') nicos_conf = join(base, 'nicos.conf')
content = { content = {
'nicos': { 'nicos': {
'setup_subdirs': '["%s", "common", "frappy_sinq"]' % ins, 'setup_subdirs': '["%s", "linse_nicos", "frappy_sinq"]' % ins_i,
'logging_path': '"%s/%s"' % (env['NICOS_LOG'], ins), 'logging_path': '"%s/%s"' % (env['NICOS_LOG'], ins_i),
'pid_path': '"%s/%s"' % (env['NICOS_LOG'], ins), 'pid_path': '"%s/%s"' % (env['NICOS_LOG'], ins_i),
}, },
# 'environment': { # 'environment': {
# key: f'"{env[key]}"' for key in env if key in ENV_KEYS # key: f'"{env[key]}"' for key in env if key in ENV_KEYS

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# ***************************************************************************** # *****************************************************************************
# #
# This program is free software; you can redistribute it and/or modify it under # This program is free software; you can redistribute it and/or modify it under
@@ -27,13 +26,24 @@ import subprocess
import psutil import psutil
import os import os
import re import re
from os.path import join, exists import socket
from os.path import join, exists, expanduser
from servicemanager.base import ServiceManager, ServiceDown, UsageError from servicemanager.base import ServiceManager, ServiceDown, UsageError
CFGLINE = re.compile(r'(?:device makeitem (name|stick_name|confirmed) "(.*)" ""' CFGLINE = re.compile(r'(?:device makeitem (name|stick_name|confirmed) "(.*)" ""'
r'|addon_list makeitem (.*) (?:"permanent"|"volatile"))') r'|addon_list makeitem (.*) (?:"permanent"|"volatile"))')
INIFILE_CONTENT = """
set instrument {ins_i}
set serverport {port}
set logbase {insdir}
{extraline}cd {seadir}tcl
exe echo 1
exe {service}init.tcl
"""
def run_command(cmd, wait=False): def run_command(cmd, wait=False):
if wait: if wait:
old = termios.tcgetattr(sys.stdin) old = termios.tcgetattr(sys.stdin)
@@ -68,7 +78,49 @@ class SeaManager(ServiceManager):
%(legend)s %(legend)s
""" """
def do_cli(self, ins): def do_create(self, ins, *args):
if ins == 'check':
ins_list = self.wildcard(None)
else:
ins_list = self.wildcard(ins)
if ins_list is None:
ins_list = [ins]
self.get_info()
for ins_i in ins_list:
seadir = f"{expanduser('~')}/sea/"
if not exists(seadir):
print(f'get {seadir} from git first')
return
insdir = seadir if ins_i == self.main_ins else f'{seadir}{ins_i}/'
if ins != 'check':
os.makedirs(f'{insdir}/logger', exist_ok=True)
os.makedirs(f'{insdir}/log', exist_ok=True)
os.makedirs(f'{insdir}/status', exist_ok=True)
extraline = 'set connect_sea_to_sics 0\n'
for service in 'sea', 'graph':
port = self.info[ins_i][service]
if not port:
continue
sea_init = INIFILE_CONTENT.format(**locals()).strip()
extraline = ''
filename = f'{seadir}{service}_{ins_i}.tcl'
try:
with open(filename) as f:
content = f.read().strip()
except FileNotFoundError:
content = ''
if sea_init != content:
if ins == 'check':
if content:
print(filename, 'does not match')
else:
print(filename, 'does not exist')
else:
with open(filename, 'w') as f:
f.write(sea_init)
f.write('\n')
def do_cli(self, ins, service=None):
if self.wildcard(ins): if self.wildcard(ins):
raise UsageError('wildcards not allowed in sea cli') raise UsageError('wildcards not allowed in sea cli')
try: try:
@@ -78,7 +130,8 @@ class SeaManager(ServiceManager):
print(str(e)) print(str(e))
except KeyError: # running on an other machine? except KeyError: # running on an other machine?
self.do_help() self.do_help()
run_command('six -sea %s' % ins, wait=True) service = service or 'sea'
run_command(f'six -{service} {ins}', wait=True)
def do_gui(self, ins='', *args): def do_gui(self, ins='', *args):
if ins and self.wildcard(ins): if ins and self.wildcard(ins):
@@ -117,7 +170,7 @@ class SeaManager(ServiceManager):
seastatus = self.get_status_filename(ins) seastatus = self.get_status_filename(ins)
if seastatus: if seastatus:
boot_time = time.strftime("%Y-%m-%dT%H-%M-%S", time.localtime(psutil.boot_time())) boot_time = time.strftime("%Y-%m-%dT%H-%M-%S", time.localtime(psutil.boot_time()))
dst = seastatus.replace('.tcl', '') + '.' + boot_time dst = seastatus.replace('.tcl', '') + '.before_boot_at' + boot_time
if not exists(dst): if not exists(dst):
os.system(f'cp {seastatus} {dst}') os.system(f'cp {seastatus} {dst}')
return start_dir, env return start_dir, env
@@ -196,8 +249,28 @@ class SeaManager(ServiceManager):
argdict['ins'] = arg argdict['ins'] = arg
else: else:
raise UsageError('unknown argument: %s' % arg) raise UsageError('unknown argument: %s' % arg)
else:
print('env variable "InstrumentHostList" is not defined')
result = [argdict.pop('ins', '')] result = [argdict.pop('ins', '')]
service = argdict.pop('service', '') service = argdict.pop('service', '')
if service: if service:
result.append(service) result.append(service)
return result + extra return result + extra
def sea_recorder(self, ins, recorders):
self.do_start(ins, None, logger=False)
port = self.info[ins]['sea']
try:
conn = socket.create_connection(('localhost', port))
args = ' '.join(recorders.get(k, '0') for k in ('main', 'stick', 'addons'))
conn.send(f'seauser seaser\nconfig listen 1\nsea_recorder {args}\n'.encode())
conn.settimeout(2)
if args != '0 0 0':
prev = b''
while True:
lines = (prev + conn.recv(999)).split(b'\n')
prev = lines.pop()
if b'ACTIVE' in lines:
break
finally:
conn.close()

107
single.py Normal file
View File

@@ -0,0 +1,107 @@
# *****************************************************************************
#
# 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>
#
# *****************************************************************************
import os
from os.path import expanduser
from configparser import ConfigParser
from servicemanager.base import ServiceManager
class SingleManager(ServiceManager):
"""service to be started with systemd, only one per computer
running as systemd (user level)
"""
def systemd_service(self, service):
"""return the name of the systemd service"""
raise NotImplementedError
def treat_args(self, argdict, unknown=(), extra=()):
if not argdict.get('ins'):
argdict['ins'] = self.single_ins
return super().treat_args(argdict, unknown, extra)
def systemd_action(self, service, action):
service_ports = self.get_ins_info(self.single_ins)
services = list(service_ports) if service is None else [service]
for service in services:
print(f'systemctl --user {action} {self.systemd_service(service)}')
os.system(f'systemctl --user {action} {self.systemd_service(service)}')
def do_start(self, ins, service=None, cfg='', restart=False, wait=False, logger=None, opts=''):
if wait:
super().do_start(ins, service, cfg, False, True, logger, opts)
return
if restart:
action = 'restart'
else:
action = 'start'
os.system('loginctl enable-linger')
self.systemd_action(service, action)
self.systemd_action(service, 'enable')
def do_stop(self, ins, service=None, *args):
self.systemd_action(service, 'stop')
self.systemd_action(service, 'disable')
class FeederManager(SingleManager):
group = 'feeder'
services = ('central', 'local')
USAGE = """
Usage:
feeder start <instance> <service>
feeder restart <instance> <service>
feeder stop <instance> <service>
feeder list [<instance>]
%s
"""
def __init__(self):
parser = ConfigParser()
parser.read(expanduser('~/.config/sehistory'))
services = tuple(s for s in self.services if s in parser.sections())
self.services = services
super().__init__()
def systemd_service(self, service):
return f'{self.group}@{service}'
class SewebManager(SingleManager):
group = 'seweb'
services = ('',)
USAGE = """
Usage:
seweb start <instance>
seweb restart <instance>
seweb stop <instance>
seweb list [<instance>]
%s
"""
def systemd_service(self, service):
return f'{self.group}@{self.single_ins}'