Commit 2ae61bf0 authored by Maiken's avatar Maiken

Merge branch 'arcctl-next' into 'master'

More feature in arcctl

See merge request nordugrid/arc!149
parents eb5d479d 4799c246
......@@ -375,6 +375,7 @@ RTE_stage0 () {
# run RTE stage 0
# WARNING!!! IN SOME CASES DUE TO DIRECT SOURCING OF RTE SCRIPT WITHOUT ANY SAFETY CHECKS
# SPECIALLY CRAFTED RTES CAN BROKE CORRECT SUBMISSION (e.g. RTE redefine 'rte_idx' variable)
[ -n "${rte_params_path}" ] && source "${rte_params_path}" 1>&2
sourcewithargs "$rte_path" $args_value 1>&2
rte0_exitcode=$?
if [ $rte0_exitcode -ne 0 ] ; then
......
......@@ -5,22 +5,30 @@ if [ "$1" = "0" ] ; then
runtimeenv_var="joboption_runtime_${runtimeenv_idx}"
eval "runtimeenv_name=\"\${${runtimeenv_var}}\""
while [ -n "${runtimeenv_name}" ]; do
# define safe-defaults
arcce_runtimeenv_path=/dev/null
arcce_runtimeenv_params_path=/dev/null
# find RTE location (enabled vs default)
if [ -e "${CONFIG_controldir}/rte/enabled/${runtimeenv_name}" ]; then
if [ -e "${joboption_controldir}/rte/enabled/${runtimeenv_name}" ]; then
arcce_runtimeenv_path="${joboption_controldir}/rte/enabled/${runtimeenv_name}"
else
arcce_runtimeenv_path="${joboption_controldir}/rte/default/${runtimeenv_name}"
fi
# check RTE have parameters file
if [ -e "${joboption_controldir}/rte/params/${runtimeenv_name}" ]; then
arcce_runtimeenv_params_path="${joboption_controldir}/rte/params/${runtimeenv_name}"
fi
# copy RTE script to session directory
sessiondir_runtimeenv_path="${joboption_directory}/rte/${runtimeenv_name}"
mkdir -p "${sessiondir_runtimeenv_path%/*}"
cp -v "$arcce_runtimeenv_path" "$sessiondir_runtimeenv_path"
cat "$arcce_runtimeenv_params_path" > "$sessiondir_runtimeenv_path"
cat "$arcce_runtimeenv_path" >> "$sessiondir_runtimeenv_path"
# next RTE
runtimeenv_idx=$((runtimeenv_idx+1))
runtimeenv_var="joboption_runtime_${runtimeenv_idx}"
eval "runtimeenv_name=\"\${${runtimeenv_var}}\""
done
unset runtimeenv_idx runtimeenv_var sessiondir_runtimeenv_path arcce_runtimeenv_path
unset runtimeenv_idx runtimeenv_var sessiondir_runtimeenv_path arcce_runtimeenv_path arcce_runtimeenv_params_path
fi
true
......@@ -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.
......@@ -36,7 +36,7 @@ class JobsControl(ComponentControl):
self.logger.critical('Jobs control cannot work without controldir.')
sys.exit(1)
self.logger.debug('Using controldir location: %s', self.control_dir)
self.cache_ttl = 60
self.cache_ttl = 30
self.jobs = {}
def __get_config_value(self, block, option, default_value=None):
......@@ -132,6 +132,9 @@ class JobsControl(ComponentControl):
jobre = __JOB_RE.match(line)
if jobre:
if job_dict:
for attr in __JOB_ATTRS.keys():
if attr not in job_dict:
job_dict[attr] = 'N/A'
self.jobs[job_dict['id']] = job_dict
job_dict = {'id': jobre.group(1)}
continue
......@@ -140,6 +143,9 @@ class JobsControl(ComponentControl):
if attr_re:
job_dict[attr] = attr_re.group(1)
if job_dict:
for attr in __JOB_ATTRS.keys():
if attr not in job_dict:
job_dict[attr] = 'N/A'
self.jobs[job_dict['id']] = job_dict
# dump jobs dictionary to cache
with open(__cache_file, 'wb') as cfd:
......@@ -242,6 +248,61 @@ class JobsControl(ComponentControl):
for k, v in job_attrs.iteritems():
print '{:<32}: {}'.format(k, v)
def job_stats(self, args):
__RE_JOBSTATES = re.compile(r'^\s*([A-Z]+):\s+([0-9]+)\s+\(([0-9]+)\)\s*$')
__RE_TOTALSTATS = re.compile(r'^\s*([A-Za-z]+):\s+([0-9]+)/([-0-9]+)\s*$')
__RE_DATASTATS = re.compile(r'^\s*Processing:\s+([0-9]+)\+([0-9]+)\s*$')
jobstates = []
totalstats = []
data_download = 0
data_upload = 0
# collect information from gm-jobs
gmjobs_out = self.__run_gmjobs('-J')
for line in iter(gmjobs_out.stdout.readline, ''):
js_re = __RE_JOBSTATES.match(line)
if js_re:
state = js_re.group(1)
jobstates.append({'processing': js_re.group(2), 'waiting': js_re.group(3), 'state': state})
continue
t_re = __RE_TOTALSTATS.match(line)
if t_re:
limit = t_re.group(3)
if limit == '-1':
limit = 'unlimited'
totalstats.append({'jobs': t_re.group(2), 'limit': limit, 'state': t_re.group(1)})
continue
ds_re = __RE_DATASTATS.match(line)
if ds_re:
data_download = ds_re.group(1)
data_upload = ds_re.group(2)
# show general stats per-state
if not args.no_states:
for s in jobstates:
if args.long:
print '{state}\n Processing: {processing:>10}\n Waiting: {waiting:>10}'.format(**s)
else:
print '{state:>11}: {processing:>8} ({waiting})'.format(**s)
# show total stats if requested
if args.total:
for t in totalstats:
if args.long:
if t['state'] == 'Accepted':
t['state'] = 'Total number of jobs accepted for further processing by A-REX'
elif t['state'] == 'Running':
t['state'] = 'Total number of jobs running in LRMS backend'
elif t['state'] == 'Total':
t['state'] = 'Total number of jobs managed by A-REX (including completed)'
print '{state}\n Jobs: {jobs:>15}\n Limit: {limit:>15}'.format(**t)
else:
print '{state:>11}: {jobs:>8} of {limit}'.format(**t)
if args.data_staging:
if args.long:
print 'Processing jobs in data-staging:'
print ' Downloading: {:>9}'.format(data_download)
print ' Uploading: {:>9}'.format(data_upload)
else:
print ' Processing: {:>8} + {}'.format(data_download, data_upload)
def control(self, args):
self.cache_ttl = args.cachettl
if args.action == 'list':
......@@ -263,6 +324,8 @@ class JobsControl(ComponentControl):
self.job_log(args)
elif args.action == 'attr':
self.job_getattr(args)
elif args.action == 'stats':
self.job_stats(args)
def complete_owner(self, args):
owners = []
......@@ -284,7 +347,7 @@ class JobsControl(ComponentControl):
__JOB_STATES = ['ACCEPTED', 'PREPARING', 'SUBMIT', 'INLRMS', 'FINISHING', 'FINISHED', 'DELETED', 'CANCELING']
jobs_ctl = root_parser.add_parser('job', help='A-REX Jobs')
jobs_ctl.add_argument('-t', '--cachettl', action='store', type=int, default=60,
jobs_ctl.add_argument('-t', '--cachettl', action='store', type=int, default=30,
help='GM-Jobs output caching validity in seconds (default is %(default)s)')
jobs_ctl.set_defaults(handler_class=JobsControl)
......@@ -310,15 +373,21 @@ class JobsControl(ComponentControl):
jobs_attr.add_argument('attr', help='Attribute name', nargs='?')
jobs_kill = jobs_actions.add_parser('kill', help='Cancel job')
jobs_kill.add_argument('jobid', nargs='*', help='Job ID').completer = complete_job_id
jobs_kill.add_argument('jobid', nargs='+', help='Job ID').completer = complete_job_id
jobs_killall = jobs_actions.add_parser('killall', help='Cancel all jobs')
jobs_killall.add_argument('-s', '--state', help='Filter jobs by state', action='append', choices=__JOB_STATES)
jobs_killall.add_argument('-o', '--owner', help='Filter jobs by owner').completer = complete_job_owner
jobs_clean = jobs_actions.add_parser('clean', help='Clean job')
jobs_clean.add_argument('jobid', nargs='*', help='Job ID').completer = complete_job_id
jobs_clean.add_argument('jobid', nargs='+', help='Job ID').completer = complete_job_id
jobs_cleanall = jobs_actions.add_parser('cleanall', help='Clean all jobs')
jobs_cleanall.add_argument('-s', '--state', help='Filter jobs by state', action='append', choices=__JOB_STATES)
jobs_cleanall.add_argument('-o', '--owner', help='Filter jobs by owner').completer = complete_job_owner
jobs_stats = jobs_actions.add_parser('stats', help='Show jobs statistics')
jobs_stats.add_argument('-S', '--no-states', help='Do not show per-state job stats', action='store_true')
jobs_stats.add_argument('-t', '--total', help='Show server total stats', action='store_true')
jobs_stats.add_argument('-d', '--data-staging', help='Show server datastaging stats', action='store_true')
jobs_stats.add_argument('-l', '--long', help='Detailed output of stats', action='store_true')
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:
yum_output = subprocess.Popen(self.command_base + ['yum', '--version'],
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
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]
return
except OSError:
pass
# detect apt
try:
apt_output = subprocess.Popen(self.command_base + ['apt-get', '--version'],
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
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 ', '')
return
except OSError:
pass
self.logger.error('Cannot find yum or apt-get to manage OS packages. You distribution is not supported yet.')
sys.exit(1)
def version(self):
print '{} version {}'.format(self.pm, self.pm_version)
def __get_url_content(self, url):
try:
r = requests.get(url)
except requests.exceptions.RequestException as e:
self.logger.error('Failed to fetch content from URL: %s. Error: %s', url, e.strerror)
sys.exit(1)
return r.content
def deploy_apt_key(self, keyurl):
self.logger.info('Installing PGP key for apt from %s', keyurl)
keystr = self.__get_url_content(keyurl)
try:
p = subprocess.Popen(self.command_base + ['apt-key', 'add', '-'], stdin=subprocess.PIPE)
p.communicate(input=keystr)
except OSError as e:
self.logger.error('Failed to install PGP key for apt from %s. Error: %s.', keyurl, e.strerror)
sys.exit(1)
def deploy_repository(self, repoconf):
urlkey = self.pm + '-url'
strkey = self.pm + '-conf'
if urlkey in repoconf:
url = repoconf[urlkey]
fname = url.split('/')[-1]
fcontent = self.__get_url_content(url)
elif strkey in repoconf:
fcontent = repoconf[strkey]
fname = repoconf[self.pm + '-name']
else:
self.logger.error('No repository source provided in the argument. Failed to deploy repofiles.')
sys.exit(1)
if self.pm == 'apt' and 'apt-key-url' in repoconf:
self.deploy_apt_key(repoconf['apt-key-url'])
fpath = self.pm_repodir + fname
self.logger.info('Saving repository configuration to %s', fpath)
try:
with open(fpath, 'wb') as f:
f.write(fcontent)
except (IOError, OSError) as e:
self.logger.error('Failed to save repository configuration to %s. Error: %s', fpath, e.strerror)
sys.exit(1)
def update_cache(self):
if self.pm == 'yum':
command = self.command_base + ['yum', 'makecache']
elif self.pm == 'apt':
command = self.command_base + ['apt-get', 'update']
self.logger.info('Updating packages metadata from repositories')
return subprocess.call(command)
def install(self, packages):
# no underscores according to Debian naming policy: https://www.debian.org/doc/debian-policy/
if self.pm == 'apt':
packages = list(map(lambda p: p.replace('_', '-'), packages))
# install
command = self.command_base + [self.pm_cmd, '-y', 'install'] + packages
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')
......@@ -455,11 +455,11 @@ class RTEControl(ComponentControl):
rte_actions = rte_ctl.add_subparsers(title='RunTime Environments Actions', dest='action',
metavar='ACTION', help='DESCRIPTION')
rte_enable = rte_actions.add_parser('enable', help='Enable RTE to be used by A-REX')
rte_enable.add_argument('rte', nargs='*', help='RTE name').completer = complete_rte_name
rte_enable.add_argument('rte', nargs='+', help='RTE name').completer = complete_rte_name
rte_enable.add_argument('-f', '--force', help='Force RTE enabling', action='store_true')
rte_disable = rte_actions.add_parser('disable', help='Disable RTE to be used by A-REX')
rte_disable.add_argument('rte', nargs='*', help='RTE name').completer = complete_rte_name
rte_disable.add_argument('rte', nargs='+', help='RTE name').completer = complete_rte_name
rte_list = rte_actions.add_parser('list', help='List RunTime Environments')
rte_list.add_argument('-l', '--long', help='Detailed listing of RTEs', action='store_true')
......@@ -471,20 +471,20 @@ class RTEControl(ComponentControl):
rte_list_types.add_argument('-u', '--user', help='List available user-defined RTEs', action='store_true')
rte_default = rte_actions.add_parser('default', help='Transparently use RTE for every A-REX job')
rte_default.add_argument('rte', nargs='*', help='RTE name').completer = complete_rte_name
rte_default.add_argument('rte', nargs='+', help='RTE name').completer = complete_rte_name
rte_default.add_argument('-f', '--force', help='Force RTE enabling', action='store_true')
rte_undefault = rte_actions.add_parser('undefault', help='Remove RTE from transparent A-REX usage')
rte_undefault.add_argument('rte', nargs='*', help='RTE name').completer = complete_rte_name
rte_undefault.add_argument('rte', nargs='+', help='RTE name').completer = complete_rte_name
rte_cat = rte_actions.add_parser('cat', help='Print the content of RTE file')
rte_cat.add_argument('rte', help='RTE name').completer = complete_rte_name
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',