Commit 4799c246 authored by Andrii Salnikov's avatar Andrii Salnikov

Controlling ARC services from arcctl

parent 20328497
......@@ -28,12 +28,19 @@ RunTime Environments Actions:
list List RunTime Environments
default Transparently use RTE for every A-REX job
undefault Remove RTE from transparent A-REX usage
cat Print the content of RTE file
params-get
List configurable RTE parameters
params-set
Set configurable RTE parameters
To increase the log-level verbosity or change path to arc.conf pass the global arguments before component name.
BASH-COMPLETION
The arcctl can work smoothly without completion, however tool is designed with bash-completion in mind to be admin-friendly. Arcctl relies on python-argcomplete to implement bash-completion. To get the completion power make sure you have installed and enabled python-argcomplete as described in https://pypi.org/project/argcomplete/
The arcctl can work smoothly without completion, however tool is designed with bash-completion in mind to be admin-friendly.
Arcctl relies on python-argcomplete to implement bash-completion.
To get the completion power make sure you have installed and enabled python-argcomplete as described in https://pypi.org/project/argcomplete/
The easiest way is to enable python completion globally:
yum install python-argcomplete
......@@ -43,10 +50,10 @@ P.S. We can investigate packages availability and maybe add 'Requires' to packag
DEVELOP COMPONENT FOR ARCCTL
arcctl designed to be modular and each component implemented as dedicated python modules in 'arc.control'. Inside the module there should be control class inherited from ComponentControl that:
arcctl designed to be modular and each component implemented as dedicated python module in 'arc.control'.
Inside the module there should be control class inherited from ComponentControl that:
- have arcconfig as the only parameter in constructor
- implements static 'register_parser' method that extends arcparse with proper subparser
- implements static 'register_parser' method that extends argparse with proper subparser
- implements 'control' method that get argparse.parse argument and performs requested action
Just look into already implemented classes for Jobs and RunTimeEnvironments control.
Just look into already implemented classes for Jobs, RunTimeEnvironments control, etc.
......@@ -2,12 +2,14 @@ import subprocess
import logging
import requests
import sys
import os
class OSPackageManagement(object):
"""This class aimed to handle both yum (RedHat) and apt (Debian) cases"""
def __init__(self):
self.command_base = [] # placeholder to include command's prefix, like ssh to host
# self.command_base = ['ssh', 'arc6.grid.org.ua']
self.logger = logging.getLogger('ARCCTL.OSPackageManagement')
# detect yum (will also works with dnf wrapper)
try:
......@@ -16,6 +18,7 @@ class OSPackageManagement(object):
stdout = yum_output.communicate()
if yum_output.returncode == 0:
self.pm = 'yum'
self.pm_is_installed = 'rpm -q {}'
self.pm_cmd = 'yum'
self.pm_repodir = '/etc/yum.repos.d/'
self.pm_version = stdout[0].split('\n')[0]
......@@ -29,6 +32,7 @@ class OSPackageManagement(object):
stdout = apt_output.communicate()
if apt_output.returncode == 0:
self.pm = 'apt'
self.pm_is_installed = 'dpkg -s {}'
self.pm_cmd = 'apt-get'
self.pm_repodir = '/etc/apt/sources.list.d/'
self.pm_version = stdout[0].split('\n')[0].replace('apt ', '')
......@@ -102,4 +106,10 @@ class OSPackageManagement(object):
self.logger.info('Running the following command to install packages: %s', ' '.join(command))
return subprocess.call(command)
def is_installed(self, package):
__DEVNULL = open(os.devnull, 'w')
command = self.command_base + self.pm_is_installed.format(package).split()
self.logger.debug('Running the following command to check package is installed: %s', ' '.join(command))
return subprocess.call(command, stdout=__DEVNULL, stderr=subprocess.STDOUT) == 0
import subprocess
import logging
import sys
import os
class OSServiceManagement(object):
"""This class aimed to handle both Systemd and SysV cases"""
def __init__(self):
self.command_base = [] # placeholder to include command's prefix, like ssh to host
# self.command_base = ['ssh', 'arc6.grid.org.ua']
self.logger = logging.getLogger('ARCCTL.OSServiceManagement')
# detect systemctl
try:
systemctl_output = subprocess.Popen(self.command_base + ['systemctl', '--version'],
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
stdout = systemctl_output.communicate()
if systemctl_output.returncode == 0:
self.sm = 'systemd'
self.sm_ctl = {
'start': 'systemctl start {}',
'stop': 'systemctl stop {}',
'enable': 'systemctl enable {}',
'disable': 'systemctl disable {}'
}
self.sm_check = {
'enabled': 'systemctl -q is-enabled {}',
'active': 'systemctl -q is-active {}'
}
self.sm_version = stdout[0].split('\n')[0]
return
except OSError:
pass
# detect apt
try:
service_output = subprocess.Popen(self.command_base + ['service', '--version'],
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
stdout = service_output.communicate()
if service_output.returncode == 0:
self.sm = 'sysV'
self.sm_ctl = {
'start': 'service {} start',
'stop': 'service {} stop',
'enable': 'chkconfig {} on',
'disable': 'chkconfig {} off'
}
self.sm_check = {
'enabled': 'chkconfig {}',
'active': 'service {} status'
}
self.sm_version = stdout[0].split('\n')[0]
return
except OSError:
pass
self.logger.error('Cannot find systemctl or service to manage OS services. '
'You distribution is not supported yet.')
sys.exit(1)
def __exec_service_cmd(self, service, action):
action_str = self.sm_ctl[action]
command = self.command_base + action_str.format(service).split()
self.logger.info('Running the following command to %s service: %s', action, ' '.join(command))
return subprocess.call(command)
def __check_service(self, service, check):
__DEVNULL = open(os.devnull, 'w')
check_str = self.sm_check[check]
command = self.command_base + check_str.format(service).split()
self.logger.debug('Running the following command to check service is %s: %s', check, ' '.join(command))
return subprocess.call(command, stdout=__DEVNULL, stderr=subprocess.STDOUT) == 0
def enable(self, service):
return self.__exec_service_cmd(service, 'enable')
def disable(self, service):
return self.__exec_service_cmd(service, 'disable')
def start(self, service):
return self.__exec_service_cmd(service, 'start')
def stop(self, service):
return self.__exec_service_cmd(service, 'stop')
def is_enabled(self, service):
return self.__check_service(service, 'enabled')
def is_active(self, service):
return self.__check_service(service, 'active')
......@@ -482,9 +482,9 @@ class RTEControl(ComponentControl):
rte_params_get = rte_actions.add_parser('params-get', help='List configurable RTE parameters')
rte_params_get.add_argument('rte', help='RTE name').completer = complete_rte_name
rte_params_get.add_argument('-l', '--long', help='Detailed listing of RTEs', action='store_true')
rte_params_get.add_argument('-l', '--long', help='Detailed listing of parameters', action='store_true')
rte_params_set = rte_actions.add_parser('params-set', help='List configurable RTE parameters')
rte_params_set = rte_actions.add_parser('params-set', help='Set configurable RTE parameters')
rte_params_set.add_argument('rte', help='RTE name').completer = complete_rte_name
rte_params_set.add_argument('parameter', help='RTE parameter to configure').completer = complete_rte_params
rte_params_set.add_argument('value', help='RTE parameter value to set').completer = complete_rte_params_values
\ No newline at end of file
from ControlCommon import *
import sys
from OSService import OSServiceManagement
from OSPackage import OSPackageManagement
def complete_service_name(prefix, parsed_args, **kwargs):
arcconf = get_parsed_arcconf(parsed_args.config)
return ServicesControl(arcconf).get_all_services()
def add_services_to_parser(parser):
services_list = parser.add_mutually_exclusive_group(required=True)
services_list.add_argument('-a', '--as-configured', action='store_true',
help='Use information from arc.conf to get services list')
services_list.add_argument('-s', '--service', action='append',
help='Service name').completer = complete_service_name
class ServicesControl(ComponentControl):
__blocks_map = {
'arex': {
'package': 'nordugrid-arc-arex',
'service': 'a-rex'
},
'arex/ws/candypond': {
'package': 'nordugrid-arc-candypond',
'service': 'a-rex'
# TODO: the fate of stand-alone service
},
'gridftpd': {
'package': 'nordugrid-arc-gridftpd',
'service': 'gridftpd'
},
'infosys/ldap': {
'package': 'nordugrid-arc-aris',
'service': 'nordugrid-arc-aris'
},
'datadelivery-service': {
'package': 'nordugrid-arc-datadelivery-service',
'service': 'arc-datadelivery-service'
},
'acix-scanner': {
'package': 'nordugrid-arc-acix-cache',
'service': 'acix-cache'
},
'acix-index': {
'package': 'nordugrid-arc-acix-index',
'service': 'acix-index'
},
'nordugridmap': {
'package': 'nordugrid-arc-gridmap-utils',
'service': None
}
}
def __init__(self, arcconfig):
self.logger = logging.getLogger('ARCCTL.Services')
if arcconfig is None:
self.logger.info('Controlling ARC CE Services is not possible without arc.conf.')
sys.exit(1)
self.arcconfig = arcconfig
def __get_configured(self):
packages_needed = set()
services_needed = set()
services_all = set()
for block in self.__blocks_map.keys():
bservice = self.__blocks_map[block]['service']
if bservice is not None:
services_all.add(bservice)
if self.arcconfig.check_blocks(block):
packages_needed.add(self.__blocks_map[block]['package'])
if bservice is not None:
services_needed.add(bservice)
return packages_needed, services_all, services_needed
def __packages_install(self, packages_needed):
pm = OSPackageManagement()
install_list = []
for p in packages_needed:
if not pm.is_installed(p):
install_list.append(p)
if install_list:
self.logger.info('Installing the following needed packages: %s', ','.join(install_list))
pm.install(install_list)
def __services_stop(self, services_stop, sm):
for ds in services_stop:
self.logger.debug('Checking %s service is already stopped', ds)
if sm.is_active(ds): # if service not installed is_active also returns False
self.logger.info('Stopping %s service in accordance to arc.conf configuration', ds)
sm.stop(ds)
def __services_start(self, services_start, sm):
for ss in services_start:
self.logger.debug('Checking %s service is already started', ss)
if not sm.is_active(ss):
self.logger.info('Starting %s service in accordance to arc.conf configuration', ss)
sm.start(ss)
def __services_enable(self, services, sm, now=False):
for es in services:
self.logger.debug('Checking %s service is already enabled', es)
if not sm.is_enabled(es):
self.logger.info('Enabling %s service in accordance to arc.conf configuration', es)
sm.enable(es)
if now:
# start services as configured in current arc.conf
self.__services_start(services, sm)
def __services_disable(self, services, sm, now=False):
for ds in services:
self.logger.debug('Checking %s service is already disabled', ds)
if sm.is_enabled(ds): # if service not installed is_enabled also returns False
self.logger.info('Disabling %s service in accordance to arc.conf configuration', ds)
sm.disable(ds)
if now:
# stop services not configured in current arc.conf
self.__services_stop(services, sm)
def start_as_configured(self):
sm = OSServiceManagement()
packages_needed, services_all, services_needed = self.__get_configured()
# ensure packages are installed
self.__packages_install(packages_needed)
# stop services not configured in current arc.conf
self.__services_stop(list(services_all - services_needed), sm)
# start services as configured in current arc.conf
self.__services_start(services_needed, sm)
def enable_as_configured(self, now=False):
sm = OSServiceManagement()
packages_needed, services_all, services_needed = self.__get_configured()
# ensure packages are installed
self.__packages_install(packages_needed)
# disable services not configured in arc.conf
self.__services_disable(list(services_all - services_needed), sm, now)
# enable necessary services
self.__services_enable(services_needed, sm, now)
def list_services(self, args):
pm = OSPackageManagement()
sm = OSServiceManagement()
services = {}
for s in self.__blocks_map.values():
sname = s['service']
if sname is None:
continue
if sname in services:
continue
installed = pm.is_installed(s['package'])
active = sm.is_active(s['service'])
enabled = sm.is_enabled(s['service'])
services[sname] = {
'name': sname,
'installed': installed,
'installed_str': 'Installed' if installed else 'Not installed',
'active': active,
'active_str': 'Running' if active else 'Stopped',
'enabled': enabled,
'enabled_str': 'Enabled' if enabled else 'Disabled'
}
if args.installed:
print ' '.join(sorted(map(lambda s: s['name'], filter(lambda s: s['installed'], services.values()))))
elif args.enabled:
print ' '.join(sorted(map(lambda s: s['name'], filter(lambda s: s['enabled'], services.values()))))
elif args.active:
print ' '.join(sorted(map(lambda s: s['name'], filter(lambda s: s['active'], services.values()))))
else:
for ss in sorted(services.values(), key=lambda k: k['name']):
print '{name:32} ({installed_str}, {enabled_str}, {active_str})'.format(**ss)
def control(self, args):
if args.action == 'enable':
if args.as_configured:
self.enable_as_configured(args.now)
else:
self.__services_enable(args.service, OSServiceManagement(), args.now)
elif args.action == 'disable':
if args.as_configured:
services = self.get_all_services()
else:
services = args.service
self.__services_disable(services, OSServiceManagement(), args.now)
elif args.action == 'start':
if args.as_configured:
self.start_as_configured()
else:
self.__services_start(args.service, OSServiceManagement())
elif args.action == 'stop':
if args.as_configured:
services = self.get_all_services()
else:
services = args.service
self.__services_stop(services, OSServiceManagement())
elif args.action == 'list':
self.list_services(args)
else:
self.logger.critical('Unsupported ARC services control action %s', args.action)
sys.exit(1)
def get_all_services(self):
return list(set(map(lambda s: s['service'], self.__blocks_map.values())) - {None})
@staticmethod
def register_parser(root_parser):
services_ctl = root_parser.add_parser('service', help='ARC CE services control')
services_ctl.set_defaults(handler_class=ServicesControl)
services_actions = services_ctl.add_subparsers(title='Services Actions', dest='action',
metavar='ACTION', help='DESCRIPTION')
services_enable = services_actions.add_parser('enable', help='Enable ARC CE services')
services_enable.add_argument('--now', help='Start the services just after enable', action='store_true')
add_services_to_parser(services_enable)
services_disable = services_actions.add_parser('disable', help='Disable ARC CE services')
services_disable.add_argument('--now', help='Stop the services just after disable', action='store_true')
add_services_to_parser(services_disable)
services_start = services_actions.add_parser('start', help='Start ARC CE services')
add_services_to_parser(services_start)
services_stop = services_actions.add_parser('stop', help='Start ARC CE services')
add_services_to_parser(services_stop)
services_list = services_actions.add_parser('list', help='List ARC CE services and their states')
services_filter = services_list.add_mutually_exclusive_group(required=False)
services_filter.add_argument('-i', '--installed', help='Show only installed services', action='store_true')
services_filter.add_argument('-e', '--enabled', help='Show only enabled services', action='store_true')
services_filter.add_argument('-a', '--active', help='Show only running services', action='store_true')
......@@ -219,12 +219,11 @@ deb http://dist.eugridpma.info/distribution/igtf/current igtf accredited
deploy_voms_lsc = deploy_actions.add_parser('voms-lsc', help='Deploy VOMS list-of-certificates files')
deploy_voms_lsc.add_argument('vo', help='VO Name')
deploy_voms_sources = deploy_voms_lsc.add_mutually_exclusive_group(required=True)
deploy_voms_sources.add_argument_group('voms-admin', 'voms-admin certificate query')
deploy_voms_sources.add_argument('-v', '--voms', help='VOMS-Admin URL', action='append')
deploy_voms_sources.add_argument('-e', '--egi-vo', help='Fecth information from EGI VOs database',
action='store_true')
deploy_voms_lsc.add_argument('-o', '--openssl', action='store_true',
help='Use external OpenSSL command instead of native python SSL')
help='Use external OpenSSL command instead of python SSL')
igtf_ca = deploy_actions.add_parser('igtf-ca', help='Deploy IGTF CA certificates')
igtf_ca.add_argument('bundle', help='IGTF CA bundle name', nargs='+',
......
from RunTimeEnvironment import RTEControl
from Jobs import JobsControl
from ThirdPartyDeployment import ThirdPartyControl
from Services import ServicesControl
CTL_COMPONENTS = [RTEControl, JobsControl, ThirdPartyControl]
CTL_COMPONENTS = [RTEControl, JobsControl, ServicesControl, ThirdPartyControl]
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment