diff --git a/monasca_setup/main.py b/monasca_setup/main.py index a76d2312..a0216bcf 100644 --- a/monasca_setup/main.py +++ b/monasca_setup/main.py @@ -5,7 +5,6 @@ import argparse import logging import os -import platform import pwd import socket import subprocess @@ -14,13 +13,8 @@ import yaml import agent_config from detection.plugins import DETECTION_PLUGINS -import service.sysv as sysv +from service.detection import detect_init -from detection.utils import check_output - -# List of all detection plugins to run -# Map OS to service type -OS_SERVICE_MAP = {'Linux': sysv.SysV} log = logging.getLogger(__name__) @@ -101,41 +95,8 @@ def main(argv=None): else: logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s") - # Detect os - detected_os = platform.system() - if detected_os == 'Linux': - supported_linux_flavors = ['Ubuntu', 'debian'] - this_linux_flavor = platform.linux_distribution()[0] - if this_linux_flavor in supported_linux_flavors: - for package in ['coreutils']: - # Check for required dependencies for system checks - try: - output = check_output('dpkg -s {0}'.format(package), - stderr=subprocess.STDOUT, - shell=True) - except subprocess.CalledProcessError: - log.warn("*** {0} package is not installed! ***".format(package) + - "\nNOTE: If you do not install the {0} ".format(package) + - "package, you will not receive all of the standard " + - "operating system type metrics!") - else: - pass - elif detected_os == 'Darwin': - print("Mac OS is not currently supported by the Monasca Agent") - sys.exit() - elif detected_os == 'Windows': - print("Windows is not currently supported by the Monasca Agent") - sys.exit() - else: - print("{0} is not currently supported by the Monasca Agent".format(detected_os)) - - # Service enable, includes setup of users/config directories so must be - # done before configuration - agent_service = OS_SERVICE_MAP[detected_os](PREFIX_DIR, - args.config_dir, - args.log_dir, - args.template_dir, - username=args.user) + # Detect and if possibly enable the agent service + agent_service = detect_init(PREFIX_DIR, args.config_dir, args.log_dir, args.template_dir, username=args.user) if not args.skip_enable: agent_service.enable() @@ -152,7 +113,7 @@ def main(argv=None): args.dimensions = dict((name, value) for (name, value) in dimensions.iteritems()) write_template(os.path.join(args.template_dir, 'agent.yaml.template'), os.path.join(args.config_dir, 'agent.yaml'), - {'args': args, 'hostname': socket.getfqdn() }, + {'args': args, 'hostname': socket.getfqdn()}, gid, is_yaml=True) diff --git a/monasca_setup/service/detection.py b/monasca_setup/service/detection.py new file mode 100644 index 00000000..52a1ae4f --- /dev/null +++ b/monasca_setup/service/detection.py @@ -0,0 +1,40 @@ +import logging +import platform +import sys + +import linux + + +log = logging.getLogger(__name__) + + +def detect_init(*args, **kwargs): + """ Detect the service manager running on this box + args/kwargs match those of service.Service + :return: The apropriate Service object for this system + """ + detected_os = platform.system() + if detected_os == 'Linux': + supported_linux_flavors = ['Ubuntu', 'debian'] + flavor = platform.linux_distribution()[0] + if flavor not in supported_linux_flavors: + log.warn('{0} is not a support Linux distribution'.format(flavor)) + return detect_linux_init(*args, **kwargs) + else: + print("{0} is not currently supported by the Monasca Agent".format(detected_os)) + sys.exit(1) + + # Service enable, includes setup of users/config directories so must be + # done before configuration + + +def detect_linux_init(*args, **kwargs): + """ Detect which of the linux inits is running + :return: Return a valid Linux service manager object + """ + with open('/proc/1/comm', 'r') as init_proc: + init = init_proc.readline().strip() + if init == 'systemd': + return linux.Systemd(*args, **kwargs) + else: + return linux.SysV(*args, **kwargs) diff --git a/monasca_setup/service/sysv.py b/monasca_setup/service/linux.py similarity index 53% rename from monasca_setup/service/sysv.py rename to monasca_setup/service/linux.py index d9bc794e..cc8475fd 100644 --- a/monasca_setup/service/sysv.py +++ b/monasca_setup/service/linux.py @@ -1,5 +1,4 @@ -"""System V style service. - +""" Systemd based service """ import glob import logging @@ -9,24 +8,15 @@ import subprocess import service + log = logging.getLogger(__name__) -class SysV(service.Service): - - def __init__(self, prefix_dir, config_dir, log_dir, template_dir, name='monasca-agent', username='monasca-agent'): - """Setup this service with the given init template. - - """ - super(SysV, self).__init__(prefix_dir, config_dir, log_dir, name) - self.init_script = '/etc/init.d/%s' % self.name - self.init_template = os.path.join(template_dir, 'monasca-agent.init.template') - self.username = username - +class LinuxInit(service.Service): + """ Parent class for all Linux based init systems. + """ def enable(self): - """Sets monasca-agent to start on boot. - - Generally this requires running as super user + """ Does user/group directory creation. """ # Create monasca-agent user/group if needed try: @@ -44,6 +34,94 @@ class SysV(service.Service): # the log dir needs to be writable by the user os.chown(self.log_dir, user.pw_uid, user.pw_gid) + def start(self, restart=True): + if not self.is_enabled(): + log.error('The service is not enabled') + return False + + def stop(self): + if not self.is_enabled(): + log.error('The service is not enabled') + return True + + def is_enabled(self): + """Returns True if monasca-agent is setup to start on boot, false otherwise. + + """ + raise NotImplementedError + + +class Systemd(LinuxInit): + def enable(self): + """Sets monasca-agent to start on boot. + + Generally this requires running as super user + """ + LinuxInit.enable(self) + + # Write the systemd script + init_path = '/etc/systemd/system/{0}.service'.format(self.name) + with open(os.path.join(self.template_dir, 'monasca-agent.service.template'), 'r') as template: + with open(init_path, 'w') as service_script: + service_script.write(template.read().format(prefix=self.prefix_dir, monasca_user=self.username, + config_dir=self.config_dir)) + os.chown(init_path, 0, 0) + os.chmod(init_path, 0644) + + # Enable the service + subprocess.check_call(['systemctl', 'daemon-reload']) + subprocess.check_call(['systemctl', 'enable', '{0}.service'.format(self.name)]) + log.info('Enabled {0} service via systemd'.format(self.name)) + + def start(self, restart=True): + """Starts monasca-agent. + + If the agent is running and restart is True, restart + """ + LinuxInit.start(self) + log.info('Starting {0} service via systemd'.format(self.name)) + if restart: + subprocess.check_call(['systemctl', 'restart', '{0}.service'.format(self.name)]) + else: + subprocess.check_call(['systemctl', 'start', '{0}.service'.format(self.name)]) + + return True + + def stop(self): + """Stops monasca-agent. + """ + LinuxInit.stop(self) + log.info('Stopping {0} service'.format(self.name)) + subprocess.check_call(['systemctl', 'stop', '{0}.service'.format(self.name)]) + return True + + def is_enabled(self): + """Returns True if monasca-agent is setup to start on boot, false otherwise. + """ + try: + subprocess.check_output(['systemctl', 'is-enabled', '{0}.service'.format(self.name)]) + except subprocess.CalledProcessError: + return False + + return True + + +class SysV(LinuxInit): + + def __init__(self, prefix_dir, config_dir, log_dir, template_dir, name='monasca-agent', username='monasca-agent'): + """Setup this service with the given init template. + + """ + service.Service.__init__(self, prefix_dir, config_dir, log_dir, template_dir, name, username) + self.init_script = '/etc/init.d/%s' % self.name + self.init_template = os.path.join(template_dir, 'monasca-agent.init.template') + + def enable(self): + """Sets monasca-agent to start on boot. + + Generally this requires running as super user + """ + LinuxInit.enable(self) # Write the init script and enable. with open(self.init_template, 'r') as template: with open(self.init_script, 'w') as conf: @@ -63,9 +141,7 @@ class SysV(service.Service): If the agent is running and restart is True, restart """ - if not self.is_enabled(): - log.error('The service is not enabled') - return False + LinuxInit.start(self) log.info('Starting {0} service via SysV init script'.format(self.name)) if restart: @@ -78,9 +154,7 @@ class SysV(service.Service): """Stops monasca-agent. """ - if not self.is_enabled(): - log.error('The service is not enabled') - return False + LinuxInit.stop(self) log.info('Stopping {0} service via SysV init script'.format(self.name)) subprocess.check_call([self.init_script, 'stop']) # Throws CalledProcessError on error @@ -96,4 +170,4 @@ class SysV(service.Service): if len(glob.glob('/etc/rc?.d/S??monasca-agent')) > 0: return True else: - return False + return False \ No newline at end of file diff --git a/monasca_setup/service/service.py b/monasca_setup/service/service.py index 7efe35c1..cf7a78b0 100644 --- a/monasca_setup/service/service.py +++ b/monasca_setup/service/service.py @@ -1,20 +1,21 @@ -"""Classes implementing different methods for running monasca-agent on startup as well as starting the process immediately. +"""Code to handle various service managers used on different OS """ import psutil class Service(object): - """Abstract base class implementing the interface for various service types. """ - def __init__(self, prefix_dir, config_dir, log_dir, name='monasca-agent'): + def __init__(self, prefix_dir, config_dir, log_dir, template_dir, name='monasca-agent', username='monasca-agent'): self.prefix_dir = prefix_dir self.config_dir = config_dir self.log_dir = log_dir + self.template_dir = template_dir self.name = name + self.username = username def enable(self): """Sets monasca-agent to start on boot. @@ -42,14 +43,13 @@ class Service(object): """ raise NotImplementedError - @staticmethod - def is_running(): + def is_running(self): """Returns True if monasca-agent is running, false otherwise. """ # Looking for the supervisor process not the individual components for process in psutil.process_iter(): - if '/etc/monasca/agent/supervisor.conf' in process.cmdline(): + if '{0}/supervisor.conf'.format(self.config_dir) in process.cmdline(): return True return False diff --git a/packaging/monasca-agent.service.template b/packaging/monasca-agent.service.template new file mode 100644 index 00000000..92d16aad --- /dev/null +++ b/packaging/monasca-agent.service.template @@ -0,0 +1,12 @@ +[Unit] +Description=Monasca Agent + +[Service] +Type=simple +User={monasca_user} +Group={monasca_user} +Restart=on-failure +ExecStart={prefix}/bin/supervisord -c {config_dir}/supervisor.conf -n + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/setup.cfg b/setup.cfg index a6cd7183..7497281b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -24,6 +24,7 @@ data_files= agent.yaml.template packaging/supervisor.conf.template packaging/monasca-agent.init.template + packaging/monasca-agent.service.template share/monasca/agent/conf.d = conf.d/* [entry_points]