initial version

This commit is contained in:
zolliker 2020-10-23 16:18:50 +02:00
parent c5f9c12e98
commit 61ba9336b8
11 changed files with 1112 additions and 1 deletions

View File

@ -1,3 +1,5 @@
# servman
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 and sea might run on the same machine.

394
__init__.py Normal file
View File

@ -0,0 +1,394 @@
# -*- coding: utf-8 -*-
# *****************************************************************************
#
# 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>
#
# *****************************************************************************
"""start/stop/list of services
this code is currently used:
- from NICOS to start and stop frappy servers
- from a script allowing to start/stop/list (and more) multiple frappy and nicos servers
"""
import json
import os
from os.path import expanduser
import subprocess
import time
import re
import socket
import psutil
from collections import OrderedDict, defaultdict
from configparser import ConfigParser
class ServiceDown(Exception):
"""the service is not running"""
class UsageError(Exception):
pass
def printTable(headers, items, printfunc, minlen=0, rjust=False):
"""Print tabular information nicely formatted.
accept less columns for some rows. The last item of such a row
may extend over columns. stolen from nicos.utils and modifed.
"""
if not headers and not items:
return
ncolumns = len(headers or items[0])
rowlens = [minlen] * ncolumns
for row in [headers or []] + items:
# do not consider length of last column when row has less columns
cntrow = row if len(row) == ncolumns else row[:-1]
for i, item in enumerate(cntrow):
rowlens[i] = max(rowlens[i], len(item))
rfmtstr = ('%%%ds ' * ncolumns) % tuple(rowlens)
lfmtstr = ('%%-%ds ' * ncolumns) % tuple(rowlens)
if headers:
printfunc(lfmtstr % tuple(headers))
printfunc(lfmtstr % tuple('=' * n for n in rowlens))
for row in items:
printfunc((rfmtstr if rjust else lfmtstr) % (tuple(row) + ('',) * (ncolumns - len(row))))
def run(serv, arglist):
arglist = arglist + [''] # add dummy argument
action = arglist.pop(0) if hasattr(serv, 'do_' + arglist[0]) else 'gui'
instance = arglist.pop(0) if arglist[0] and arglist[0] not in serv.services else None
if instance is None and len(serv.info) == 1:
instance = list(serv.info)[0]
if instance is not None:
arglist.insert(0, instance)
arglist.pop() # remove dummy argument
try:
serv.action(action, *arglist)
except AttributeError as e:
raise
print(repr(e))
raise ValueError("do not know '%s'" % ' '.join([serv.group, action] + arglist))
class ServiceManager:
services = None
need_cfg = False
start_dir = None
group = None
all = {} # for the list command, we want to register all service managers
virtualenv = None
pkg = ''
def __init__(self):
self.env = {}
self.commands = {}
self.revcmd = {}
self.info = {}
self.all[self.group] = self
#prog, args = self.command.split(None, 1)
#self.cmdpat = re.compile('.* ' + # do not match prog, as it might be modified by the os
# (args % dict(ins=r'(?P<ins>\S*)', serv=r'(?P<serv>\S*)',
# cfg=r'(?P<cfg>\S*)', port=r'\S*', pkg=r'\S*')))
self.get_info()
self.stopped = defaultdict(dict)
def get_services(self, section):
ports = {}
nr = '%02d' % int(section[self.group])
gr = self.group.upper()
for service in self.services:
sv = '%s_%s' % (gr, service.upper())
port = section.get('%s_PORT' % sv)
if port or json.loads(section.get(sv, '0').lower()): # e.g. NICOS_POLLER = True leads to port = 0
ports[service] = int((port or '0').replace('nr', nr))
return ports
def get_info(self):
"""returns port numbers,commands and environment variables
the result is a dict[<service>] of dict(ins=.., port= ..., cmd= ...)
if ins is omitted, return a list of above for all ins
"""
result = OrderedDict()
parser = ConfigParser(interpolation=None)
parser.optionxform = str
parser.read(expanduser('servman.cfg'))
defaults = parser['DEFAULT']
self.commands = {}
self.revcmd = {}
for ins in parser.sections():
section = dict(parser[ins])
command = section.get('%s_command' % self.group)
self.revcmd[command] = self.group
if self.group in section:
self.commands[ins] = command
services = self.get_services(parser[ins])
env = {k: expanduser(section.get(k)) for k in defaults if k.isupper()}
result[ins] = services
self.env[ins] = env
self.info = result
def get_cmdpats(self, groups):
return self.cmdpats
def get_ins_info(self, ins):
self.get_info()
return self.info[ins]
def get_cfg(self, cmd):
"""return info about running program, if relevant
example for frappy: return cfg
"""
return ''
def get_procs(self, groups=None):
"""return processes
result is a dict[ins] of dict[service] of list of tuples (process, cfg)
"""
result = {}
cmdpatterns = []
if groups is None:
groups = [self.group]
for cmd, group in self.revcmd.items():
if group not in groups:
continue
args = cmd.split(None, 1)[1]
cmdpatterns.append(
re.compile('.* ' + # do not match prog, as it might be modified by the os
(args % dict(ins=r'(?P<ins>\S*)',
serv=r'(?P<serv>\S*)',
cfg=r'(?P<cfg>\S*)',
port=r'\S*',
pkg=r'\S*'))))
for p in psutil.process_iter(attrs=['pid', 'cmdline']):
cmdline = p.info['cmdline']
if cmdline:
cmd = ' '.join(cmdline)
for cmdpat in cmdpatterns:
match = cmdpat.match(cmd)
if match:
gdict = match.groupdict()
ins = gdict['ins']
serv = gdict['serv']
result.setdefault(ins, {}).setdefault(serv, []).append(p)
return result
def check_running(self, ins, service):
self.get_info()
if ins not in self.info:
raise KeyError("don't know %r" % ins)
if not self.get_procs().get(ins, {}).get(service):
raise ServiceDown('%s %s is not running' % (service, ins))
def stop(self, ins, service=None):
"""stop service (or all services) of instance <ins>
return a dict[<ins>][<service>] of <cfg> for all stopped processes
this information may be used for restarts
"""
procs = self.get_procs()
done = False
services = self.services if service is None else [service]
for service in reversed(services):
for p in procs.get(ins, {}).get(service, []):
print_wait = True
for action in ('terminate', 'kill'):
getattr(p, action)() # p.terminate or p.kill
for i in range(10): # total 0.1 * 10 * 9 / 2 = 4.5 sec
try:
p.wait(0.1 * i)
except psutil.TimeoutExpired:
if p.status() == psutil.STATUS_ZOMBIE:
break
if print_wait and i > 4:
print('wait for %s %s' % (ins, service))
print_wait = False
continue
self.stopped[ins][service] = ' '.join(p.info['cmdline'])
done = True
break
else:
if action == 'kill':
action = 'kill fail'
break
continue
break
print('%s %s %s' % (ins, service, (action + 'ed').replace('eed', 'ed')))
return done
def do_stop(self, ins, service=None, *args):
self.get_info()
if not self.stop(ins, service):
print('nothing to stop')
def prepare_start(self, ins):
if ins not in self.env:
self.get_info()
gr = self.group.upper()
env = self.env[ins]
return env.get('%s_ROOT' % gr, ''), env
def do_start(self, ins, service=None, cfg='', restart=False, wait=False):
if ins is None:
print('nothing to start')
return
try:
service_ports = self.get_ins_info(ins)
except ValueError:
raise ValueError('do not know %r' % ins)
services = list(service_ports) if service is None else [service]
if restart:
self.stop(ins, service)
else:
procs = self.get_procs()
to_start = []
for service in services:
n = len(procs.get(ins, {}).get(service, []))
if n == 0:
to_start.append(service)
else:
count = '' if n == 1 else ' %sx' % n
print('%s %s is already running%s' % (ins, service, count))
services = to_start
for service in services:
port = service_ports[service]
cmd = self.commands[ins] % dict(ins=ins, serv=service, port=port, cfg=cfg, pkg=self.pkg)
if '%(cfg)s' in self.commands[ins] and not cfg:
cmd = self.stopped[ins].get(service)
if not cmd:
raise ValueError('missing cfg for %s %s' % (ins, service))
wd = os.getcwd()
try:
start_dir, env = self.prepare_start(ins)
env = dict(os.environ, **env)
os.chdir(start_dir)
if wait:
proc = subprocess.Popen(cmd.split(), env=env)
proc.wait()
return
process = subprocess.Popen(cmd.split(), env=env, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
if not port:
print('%s %s started' % (ins, service))
continue
print_wait = True
for i in range(10): # total 10 * 9 / 2 = 4.5 sec
returnvalue = process.poll()
if returnvalue is not None:
print('started process finished with %r' % returnvalue)
process = subprocess.Popen(cmd.split(), env=env, stdout=subprocess.DEVNULL,
stderr=subprocess.PIPE)
try:
_, erroutput = process.communicate(timeout=10)
except subprocess.TimeoutExpired:
process.kill()
_, erroutput = process.communicate()
print(erroutput.decode())
print('%s %s died' % (ins, service))
break
try:
if print_wait and i > 4:
print('wait for port %s' % port)
print_wait = False
s = socket.create_connection(('localhost', port), timeout=5)
s.close()
except socket.error:
time.sleep(0.1 * i)
continue
if restart:
print('%s %s restarted' % (ins, service))
else:
print('%s %s started' % (ins, service))
break
else:
print(cmd)
print('starting %s %s failed' % (ins, service))
finally:
os.chdir(wd)
def do_restart(self, ins, service=None, cfg=None):
self.do_start(ins, service, cfg, True)
def do_run(self, ins, service, cfg=None):
"""for tests: run and wait"""
self.do_start(ins, service, cfg, wait=True)
def do_list(self, ins=None, *args):
"""info about running services"""
procs = self.get_procs(self.all)
rows = []
merged = OrderedDict()
show_unused = ins == 'all'
if show_unused:
ins = None
for group, sm in self.all.items():
sm.get_info()
for ins_i, info_dict in sm.info.items():
if ins is not None and ins != ins_i:
continue
for serv, port in info_dict.items():
if ins_i not in merged:
merged[ins_i] = {g: {} for g in self.all}
merged[ins_i][group][serv] = port
for ins_i, info_dict in merged.items():
show_ins = show_unused
run_info = [[''], [ins_i]]
procs_dict = procs.get(ins_i, {})
for group, sm in self.all.items():
info_grp = info_dict.get(group, {})
for serv, port in info_grp.items():
plist = procs_dict.get(serv)
if plist:
if sm == self:
show_ins = True
cfg = sm.get_cfg(' '.join(plist[0].info['cmdline']))
gs = '%s %s' % (group, serv)
port = str(port or '')
run_info.append(('', gs, port, cfg))
if len(plist) > 1:
rows.append(['', ' WARNING: multiple processes %s'
% ', '.join(str(p.pid) for p, _ in plist)])
extra = sm.extra_info(ins_i)
if extra and run_info:
run_info.append(['', extra])
if show_ins:
rows.extend(run_info)
print('')
printTable(('inst', 'service', 'port', 'cfg'), rows, print)
@staticmethod
def extra_info(ins):
"""provide extra info or None"""
return None
def action(self, action, *args):
method = getattr(self, 'do_' + action, None)
if not callable(method):
raise UsageError('%s is no valid action' % action)
try:
method(*args)
except TypeError as e:
errtxt = str(e)
if ' do_%s(' % action in errtxt and 'argument' in errtxt:
raise UsageError(errtxt)
raise

55
bin/frappy Executable file
View File

@ -0,0 +1,55 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# *****************************************************************************
#
# 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 sys
from os.path import join, abspath
sys.path.insert(1, abspath(join(__file__, '../../..')))
from servman.frappy import FrappyManager
from servman.nicos import NicosManager
from servman.sea import SeaManager
from servman import run
NicosManager()
serv = FrappyManager()
SeaManager()
USAGE = """
Usage:
frappy list [<instance>]
frappy start <instance> <service> <cfgfiles>
frappy restart <instance> [<service>] [<cfgfiles>]
frappy stop <instance> [<service>]
<service> is one of main, stick, addons
<instance> is one of %s
""" % ', '.join(serv.info)
try:
run(serv, sys.argv[1:])
except Exception as e:
print(repr(e))
print(''.join(USAGE))

71
bin/nicos Executable file
View File

@ -0,0 +1,71 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# *****************************************************************************
#
# 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 join, abspath, expanduser
import sys
#nicos_root = os.environ.get('NICOS_ROOT', expanduser('~/nicos'))
#pkg_dir = abspath(join(nicos_root, 'nicos_linse'))
#sys.path.insert(1, join(pkg_dir, 'common'))
sys.path.insert(1, abspath(join(__file__, '../../..')))
from servman.frappy import FrappyManager
from servman.nicos import NicosManager
from servman.sea import SeaManager
from servman import run, UsageError
serv = NicosManager()
FrappyManager()
SeaManager()
USAGE = """
Usage:
nicos gui <instance>
nicos <instance> (the same as above)
nicos list [<instance>]
nicos start <instance> [<service>]
nicos restart <instance> [<service>]
nicos stop <instance> [<service>]
nicos create <instance> <nr>
nicos create all
nicos link <instance> (create links to nicos data and scripts)
<service> is one of main, stick, addons
<instance> is one of %s
to be done after the experiment:
nicos copy (copy data and scripts from link)
nicos copy [<instance> [<year>/<proposal>]] (copy specific data)
""" % ', '.join(serv.info)
try:
run(serv, sys.argv[1:])
except UsageError as e:
print(repr(e))
print(''.join(USAGE))

62
bin/nicos-cache Executable file
View File

@ -0,0 +1,62 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# *****************************************************************************
# NICOS, the Networked Instrument Control System of the MLZ
# Copyright (c) 2009-2019 by the NICOS contributors (see AUTHORS)
#
# 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:
# Georg Brandl <georg.brandl@fz-juelich.de>
# Markus Zolliker <markus.zolliker@psi.ch>
#
# *****************************************************************************
from __future__ import absolute_import, division, print_function
import argparse
import sys
from os import path, environ
sys.path.insert(0, path.dirname(path.dirname(path.dirname(path.realpath(__file__)))))
parser = argparse.ArgumentParser()
parser.add_argument('-d', '--daemon', dest='daemon', action='store_true',
help='daemonize the cache process')
parser.add_argument('-D', '--systemd', dest='daemon', action='store_const',
const='systemd', help='run in systemd service mode')
parser.add_argument('-S', '--setup', action='store', dest='setupname',
default='cache',
help="name of the setup, default is 'cache'")
parser.add_argument('--clear', dest='clear', action='store_true',
default=False,
help='clear the whole cache')
parser.add_argument('-I', '--instrument', action='store',
type=str, default='',
help='instrument as <package>.<instrument>\n', )
parser.add_argument('args', nargs=argparse.REMAINDER, help=argparse.SUPPRESS)
opts = parser.parse_args()
if opts.clear:
opts.args.append('clear')
if opts.instrument:
environ['INSTRUMENT'] = opts.instrument
from nicos.core.sessions.simple import NoninteractiveSession
NoninteractiveSession.run(opts.setupname, 'Server', setupname=opts.setupname,
daemon=opts.daemon, start_args=opts.args)

54
bin/nicos-daemon Executable file
View File

@ -0,0 +1,54 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# *****************************************************************************
# NICOS, the Networked Instrument Control System of the MLZ
# Copyright (c) 2009-2019 by the NICOS contributors (see AUTHORS)
#
# 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:
# Georg Brandl <georg.brandl@fz-juelich.de>
# Markus Zolliker <markus.zolliker@psi.ch>
#
# *****************************************************************************
from __future__ import absolute_import, division, print_function
import argparse
import sys
from os import path, environ
sys.path.insert(0, path.dirname(path.dirname(path.dirname(path.realpath(__file__)))))
parser = argparse.ArgumentParser()
parser.add_argument('-d', '--daemon', dest='daemon', action='store_true',
help='daemonize the daemon process')
parser.add_argument('-D', '--systemd', dest='daemon', action='store_const',
const='systemd', help='run in systemd service mode')
parser.add_argument('-S', '--setup', action='store', dest='setupname',
default='daemon',
help="name of the setup, default is 'daemon'")
parser.add_argument('-I', '--instrument', action='store',
type=str, default='',
help='instrument as <package>.<instrument>\n', )
opts = parser.parse_args()
if opts.instrument:
environ['INSTRUMENT'] = opts.instrument
from nicos.services.daemon.session import DaemonSession
DaemonSession.run(opts.setupname, 'Daemon', daemon=opts.daemon)

63
bin/nicos-poller Executable file
View File

@ -0,0 +1,63 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# *****************************************************************************
# NICOS, the Networked Instrument Control System of the MLZ
# Copyright (c) 2009-2019 by the NICOS contributors (see AUTHORS)
#
# 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:
# Georg Brandl <georg.brandl@fz-juelich.de>
# Markus Zolliker <markus.zolliker@psi.ch>
#
# *****************************************************************************
from __future__ import absolute_import, division, print_function
import argparse
import sys
from os import path, environ
sys.path.insert(0, path.dirname(path.dirname(path.dirname(path.realpath(__file__)))))
parser = argparse.ArgumentParser()
parser.add_argument('-d', '--daemon', dest='daemon', action='store_true',
help='daemonize the poller processes')
parser.add_argument('-D', '--systemd', dest='daemon', action='store_const',
const='systemd', help='run in systemd service mode')
parser.add_argument('-S', '--setup', action='store', dest='setupname',
default='poller',
help="name of the setup, default is 'poller'")
parser.add_argument('-I', '--instrument', action='store',
type=str, default='',
help='instrument as <package>.<instrument>\n', )
parser.add_argument('setup', nargs=argparse.OPTIONAL, help=argparse.SUPPRESS)
opts = parser.parse_args()
if opts.setup:
appname = 'poller-' + opts.setup
args = [opts.setup]
else:
appname = 'poller'
args = []
if opts.instrument:
environ['INSTRUMENT'] = opts.instrument
from nicos.services.poller.psession import PollerSession
PollerSession.run(appname, setupname=opts.setupname, maindevname='Poller',
start_args=args, daemon=opts.daemon)

58
bin/sea Executable file
View File

@ -0,0 +1,58 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# *****************************************************************************
#
# 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 join, abspath, split, dirname, expanduser
import sys
nicos_root = os.environ.get('NICOS_ROOT', expanduser('~/nicos'))
pkg_dir = abspath(join(nicos_root, 'nicos_linse'))
sys.path.insert(1, join(pkg_dir, 'common'))
from clitools import FrappyManager, NicosManager, SeaManager, run
NicosManager(pkg_dir)
FrappyManager(pkg_dir)
serv = SeaManager()
USAGE = """
Usage:
sea gui <instance>
sea <instance> # the same as sea gui <instance>
sea cli <instance> (the same as old seacmd)
sea start <instance> <service>
sea restart <instance> [<service>]
sea stop <instance> [<service>]
sea list [<instance>]
<service> is one of main, stick, addons
<instance> is one of %s
""" % ', '.join(serv.info)
try:
run(serv, sys.argv[1:])
except Exception as e:
print(repr(e))
print(''.join(USAGE))

35
frappy.py Normal file
View File

@ -0,0 +1,35 @@
# -*- coding: utf-8 -*-
# *****************************************************************************
#
# 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 servman import ServiceManager
class FrappyManager(ServiceManager):
group = 'frappy'
services = ('main', 'stick', 'addons')
def get_cfg(self, cmd):
if cmd:
match = self.cmdpat.match(cmd)
if match:
return match.groupdict().get('cfg')
return ''

255
nicos.py Normal file
View File

@ -0,0 +1,255 @@
# -*- coding: utf-8 -*-
# *****************************************************************************
#
# 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
import sys
import shutil
from glob import glob
from os.path import join, abspath, dirname, expanduser, exists, islink
from configparser import ConfigParser
from servman import ServiceManager
ENV_KEYS = {
'NICOS_CACHE_PORT',
'NICOS_DAEMON_PORT',
'FRAPPY_MAIN_PORT',
'FRAPPY_STICK_PORT',
'FRAPPY_ADDONS_PORT',
'SEA_PORT',
}
def copy_all(srcdir, dstdir):
"""copy all files from srcdir to dstdir"""
files = glob(join(srcdir, '*'))
for src in files:
shutil.copy2(src, dstdir)
return files
class NicosManager(ServiceManager):
group = 'nicos'
services = ('cache', 'daemon', 'poller')
def do_create(self, ins, nr=None, *args):
"""TODO: redo"""
self.get_info()
if ins == 'all' or ins == 'check':
inslist = list(self.info)
else:
inslist = [ins]
for ins_i in inslist:
env = self.env[ins_i]
base = join(env['NICOS_ROOT'], env['NICOS_PACKAGE'], ins)
nicos_conf = join(base, 'nicos.conf')
content = {
'nicos': {
'setup_subdirs': '%s, common, frappy' % ins,
'logging_path': '%s/%s' % (env['NICOS_LOG'], ins),
'pid_path': '%s/%s' % (env['NICOS_LOG'], ins),
},
'environment': {
key: env[key] for key in env if key in ENV_KEYS
}
}
try:
cp = ConfigParser()
cp.optionsxform = str
cp.read(nicos_conf)
if set(cp.sections) != set(content):
oldcontent = None
else:
oldcontent = {key: cp[key] for key in content}
except FileNotFoundError:
oldcontent = None
if content != oldcontent:
cp = ConfigParser()
cp.optionsxform = str
for key, sdict in content.items():
cp[key] = sdict
cp.write(nicos_conf + 'x')
# pdir = self.start_dir
# if ins is None or ins == 'common':
# raise ValueError("nothing to do")
# insdict = {info['cache']['port'] % 100: ins_i for ins_i, info in self.info.items()}
# nrdict = {v: k for k, v in insdict.items()}
# if nr is not None:
# nr = int(nr)
# if ins == 'all':
# if nr is not None:
# raise ValueError("'nicos create all' has no <nr> argument")
# action = 'update'
# else:
# if nr is None:
# nr = nrdict.get(ins)
# if nr is None:
# raise ValueError('%s not known, <nr> has to specified' % ins)
# if insdict.get(nr, ins) != ins:
# raise ValueError('%d conflicts with %s' % (nr, insdict[nr]))
# action = 'create' if ins in insdict.values() else 'update'
# insdict = {nr: ins}
# for nr, ins in insdict.items():
# print('%s %3d %s %s' % (action, nr, ins, pdir))
# os.makedirs(join(pdir, ins), exist_ok=True)
# with open(join(pdir, ins, 'nicos.conf'), 'w') as f:
# f.write(NICOS_CONF % dict(insnr='%02d' % nr, ins=ins, sea=8630 + nr,
# logroot=os.environ.get('NICOS_LOG')))
# shutil.copyfile(join(pdir, 'common', 'guiconfig.py'), join(pdir, ins, 'guiconfig.py'))
@staticmethod
def extra_info(ins):
try:
datadir = join(os.environ.get('NICOS_DATA', '.'), ins)
datadir = join(datadir, os.readlink(join(datadir, 'current')))
except FileNotFoundError:
return None
return 'nicos data: %s' % datadir
@staticmethod
def linked_dir(current_wd):
link = join(current_wd, 'data')
if islink(link):
return join(link, os.readlink(link))
if exists(link):
raise ValueError('%s is not a symlink' % link)
return None
@staticmethod
def handle_linked(target, wd=None):
if not target:
print('no links from %s' % os.getcwd())
return
targetdir = abspath(join(target, '..'))
analist_file = join(targetdir, 'data_links.txt')
linked_from = []
if exists(analist_file):
with open(analist_file) as f:
for line in f:
line = line.strip()
if line and line != wd:
if islink(join(line, 'data')):
linked_from.append(line)
if wd:
linked_from.append(wd)
linked_from = '\n'.join(linked_from)
if wd:
with open(analist_file, 'w') as f:
f.write('%s\n' % linked_from)
print('links to %s from:\n%s' % (target, linked_from))
@staticmethod
def make_symlink(ins):
data = join(os.environ['NICOS_DATA'], ins)
target = join(data, os.readlink(join(data, 'current')), 'data')
link = join(os.getcwd(), 'data')
if islink(link):
os.remove(link)
elif exists(link):
raise ValueError('%s is not a symlink' % link)
os.symlink(target, link, True)
NicosManager.handle_linked(target, os.getcwd())
@staticmethod
def copy_linked(datadir=None):
src = NicosManager.linked_dir(os.getcwd())
if src:
if not datadir:
if src.rsplit('/', 1)[-1] != 'data':
raise ValueError('%s is already a copy' % src)
datadir = dirname(src)
else:
if not datadir:
raise ValueError('missing data dir')
src = join(datadir, 'data')
dst = join(os.getcwd(), '%s_%s_data' % tuple(datadir.rsplit('/', 2)[-2:]))
os.makedirs(dst, exist_ok=True)
n = len(copy_all(src, dst))
print('copy %d files to %s' % (n, dst))
# copy scripts
src = join(datadir, 'scripts')
dst = join(dst, 'scripts')
os.makedirs(dst, exist_ok=True)
n = len(copy_all(src, dst))
print('copy %d files to %s' % (n, dst))
@staticmethod
def do_link(ins, *args):
"""make/show symlink to nicos data in current wd"""
if ins is None:
NicosManager.handle_linked(NicosManager.linked_dir(os.getcwd()))
else:
NicosManager.make_symlink(ins)
sys.exit(0)
@staticmethod
def do_copy(ins, year_prop, *args):
"""copy nicos data to current wd"""
if ins is None:
NicosManager.copy_linked()
else:
data = join(os.environ['NICOS_DATA'], ins)
if year_prop:
year, sep, proposal = year_prop.rpartition('/')
if year:
src = join(data, year, proposal)
else:
# get proposal from most recent year
src = next(reversed(sorted(glob(join(data, '*', proposal)))))
else:
src = join(data, os.readlink(join(data, 'current')))
NicosManager.copy_linked(src)
def prepare_start(self, ins):
start_dir, env = super().prepare_start(ins)
instr = '%s.%s' % (env['NICOS_PACKAGE'], ins)
env['INSTRUMENT'] = instr
start_dir = env.get('NICOS_START', start_dir)
return start_dir, env
def prepare_client(self, ins):
self.check_running(ins, 'daemon')
env = self.prepare_start(ins)[1]
os.environ.update(env)
os.chdir(join(os.environ['NICOS_ROOT'], env['NICOS_PACKAGE']))
def run_client(self, ins, main, app, **kwargs):
serverhost = os.environ.get('NICOS_SERVER_HOST', 'localhost')
sys.argv[:] = [app, 'guest:guest@%s:%d' % (serverhost, self.info[ins]['daemon'])]
sys.exit(main(sys.argv, **kwargs))
def do_cli(self, ins):
self.prepare_client(ins)
from nicos.clients.cli import main
os.environ['NICOS_HISTORY_FILE'] = expanduser('~/.nicoshistory_%s' % ins)
self.run_client(ins, main, 'nicos-client',
userpath=expanduser('~/.config/nicos_client_%s' % ins))
def do_gui(self, ins):
self.prepare_client(ins)
from nicos.clients.gui.main import main
print('starting nicos gui %s' % ins, expanduser('~/.config/nicos_%s' % ins))
self.run_client(ins, main, 'nicos-gui', instance=ins)

62
sea.py Normal file
View File

@ -0,0 +1,62 @@
# -*- coding: utf-8 -*-
# *****************************************************************************
#
# 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 sys
import time
import termios
import subprocess
from servman import ServiceManager, ServiceDown
def run_command(cmd, wait=False):
if wait:
old = termios.tcgetattr(sys.stdin)
try:
proc = subprocess.Popen(cmd.split())
proc.wait()
except KeyboardInterrupt:
proc.terminate()
finally:
# in case cmd changed tty attributes
termios.tcsetattr(sys.stdin, termios.TCSAFLUSH, old)
print('')
else:
subprocess.Popen(cmd.split(), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
class SeaManager(ServiceManager):
group = 'sea'
services = ('sea', 'graph')
def do_cli(self, ins):
self.check_running(ins, 'sea')
run_command('six -sea %s' % ins, wait=True)
def do_gui(self, ins):
try:
self.check_running(ins, 'sea')
except ServiceDown as e:
print('%s, try to start...' % e)
self.do_start(ins)
time.sleep(1) # make sure caller did read the message
run_command('SeaClient %s' % ins)
print('starting sea gui %s' % ins)