From a8a2bb845b1129d24683d462c5d1ab08955a2840 Mon Sep 17 00:00:00 2001 From: Stefano Canepa Date: Mon, 19 Mar 2018 17:14:47 +0000 Subject: [PATCH] Removed dependency on supervisor To support python3 in the near future this was done: * Removed dependency on supervisor. * Added template configuration for systemd target that includes all services. * Added templates configuration for systemd service for every single service. * Changed monasca_setup to use the new templates. In the meanwhile code was formated to cope with pep8 settings and some other small changes were done to comply with pycodestyle and pydocstring. Task: 4126 Story: 2000975 Depends-On: https://review.openstack.org/#/c/566475/ Change-Id: I0d0c4ea41a830581d6b9f247fad6a2dda1f96cbe --- .gitignore | 3 + docs/Agent.md | 5 +- monasca_setup/detection/plugin.py | 21 +- monasca_setup/main.py | 131 ++++++----- monasca_setup/service/__init__.py | 17 +- monasca_setup/service/detection.py | 45 ++-- monasca_setup/service/linux.py | 225 +++++++------------ monasca_setup/service/service.py | 70 ------ packaging/Makefile | 4 - packaging/monasca-agent.service.template | 12 - packaging/monasca-agent.target.template | 6 + packaging/monasca-collector.service.template | 15 ++ packaging/monasca-forwarder.service.template | 15 ++ packaging/monasca-statsd.service.template | 15 ++ packaging/supervisor.conf.template | 48 ---- requirements.txt | 1 - setup.cfg | 7 +- 17 files changed, 249 insertions(+), 391 deletions(-) delete mode 100644 monasca_setup/service/service.py delete mode 100644 packaging/monasca-agent.service.template create mode 100644 packaging/monasca-agent.target.template create mode 100644 packaging/monasca-collector.service.template create mode 100644 packaging/monasca-forwarder.service.template create mode 100644 packaging/monasca-statsd.service.template delete mode 100644 packaging/supervisor.conf.template diff --git a/.gitignore b/.gitignore index 448ff4f7..b799e505 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,6 @@ htmlcov/ cover/ .stestr virtenv/ +Pipfile +Pipfile.lock +.vscode diff --git a/docs/Agent.md b/docs/Agent.md index c24ec79b..5e206e75 100644 --- a/docs/Agent.md +++ b/docs/Agent.md @@ -35,12 +35,13 @@ The Agent is composed of the following components: | Component Name | Process Name | Description | | -------------- | ------------ | ----------- | -| Supervisor | supervisord | Runs as root, launches all other processes as the user configured to run monasca-agent. This process manages the lifecycle of the Collector, Forwarder and Statsd Daemon. It allows Start, Stop and Restart of all the agent processes together. | | Collector | monasca-collector | Gathers system & application metrics on a configurable interval and sends them to the Forwarder process. The collector runs various plugins for collection of different plugins.| | Forwarder | monasca-forwarder | Gathers data from the collector and statsd and submits it to Monasca API over SSL (tcp/17123) | | Statsd Daemon | monasca-statsd | Statsd engine capable of handling dimensions associated with metrics submitted by a client that supports them. Also supports metrics from the standard statsd client. (udp/8125) | | Monasca Setup | monasca-setup | The monasca-setup script configures the agent. The Monasca Setup program can also auto-detect and configure certain agent plugins | +It is possible to Start, Stop and Reset all components by using monasca-agent.target. Monasca-agent.target is the systemd configuration that allows to manage all monasca-agent services. + # Installing The Agent (monasca-agent) is available for installation from the Python Package Index (PyPI). To install it, you first need `pip` installed on the node to be monitored. Instructions on installing pip may be found at https://pip.pypa.io/en/latest/installing.html. The Agent will NOT run under any flavor of Windows or Mac OS at this time but has been tested thoroughly on Ubuntu and should work under most flavors of Linux. Support may be added for Mac OS and Windows in the future. Example of an Ubuntu or Debian based install: @@ -277,7 +278,7 @@ The number of threads to use for running the plugins is via num_collector_thread The collector is optimized for collecting as many metrics on schedule as possible. The plugins are run in reverse order of their collection time, i.e., the fastest plugin first. Also, if a plugin does not complete within the collection frequency, that plugin will be skipped in the next collection cycle. These two optimizations together ensure that plugins that complete with collection frequency seconds will get run on every collection cycle. -If there is some problem with multiple plugins that end up blocking the entire thread pool, the collector will exit so that it can be restarted by the supervisord. The parameter pool_full_max_retries controls when this happens. If pool_full_max_retries consecutive collection cycles have ended with the Thread Pool completely full, the collector will exit. +If there is some problem with multiple plugins that end up blocking the entire thread pool, the collector will exit so that it can be restarted by the monasca-agent systemd target. The parameter pool_full_max_retries controls when this happens. If pool_full_max_retries consecutive collection cycles have ended with the Thread Pool completely full, the collector will exit. Some of the plugins have their own thread pools to handle asynchronous checks. The collector thread pool is separate and has no special interaction with those thread pools. # License diff --git a/monasca_setup/detection/plugin.py b/monasca_setup/detection/plugin.py index b11b7e58..319596f2 100644 --- a/monasca_setup/detection/plugin.py +++ b/monasca_setup/detection/plugin.py @@ -23,7 +23,7 @@ log = logging.getLogger(__name__) class Plugin(object): - """Abstract class implemented by the monasca-agent plugin detection classes. """ + """Abstract class for the monasca-agent plugin detection.""" def __init__(self, template_dir, overwrite=True, args=None): self.available = False @@ -32,7 +32,8 @@ class Plugin(object): self.overwrite = overwrite if args is not None and isinstance(args, str): try: - # Turn 'hostname=host type=ping' to dictionary {'hostname': 'host', 'type': 'ping'} + # Turn 'hostname=host type=ping' to dictionary + # {'hostname': 'host', 'type': 'ping'} self.args = dict([a.split('=') for a in args.split()]) except Exception: log.exception('Error parsing detection arguments') @@ -44,21 +45,25 @@ class Plugin(object): self._detect() def _detect(self): - """Run detection, set self.available True if the service is detected. + """Run detection. + Set self.available True if the service is detected. """ raise NotImplementedError def build_config(self): - """Build the config as a Plugins object and return. - - """ + """Build the config as a Plugins object and return.""" raise NotImplementedError def build_config_with_name(self): - """Builds the config and then adds a field 'built_by' to each instance in the config. + """Build the config and then add 'built_by' field. + + Build the config and then add 'built_by' field to each instance in + the config. + built_by is set to the plugin name - :return: An agent_config.Plugins object + + :return: An agent_config.Plugins object """ conf = self.build_config() if conf is None: diff --git a/monasca_setup/main.py b/monasca_setup/main.py index 227511e6..acfba76f 100644 --- a/monasca_setup/main.py +++ b/monasca_setup/main.py @@ -28,12 +28,12 @@ import sys import six -import agent_config +from monasca_setup import agent_config +from monasca_setup.service.detection import detect_init import monasca_setup.utils as utils from monasca_setup.utils import write_template -from service.detection import detect_init -log = logging.getLogger(__name__) +LOG = logging.getLogger(__name__) CUSTOM_PLUGIN_PATH = '/usr/lib/monasca/agent/custom_detect.d' # dirname is called twice to get the dir 1 above the location of the script @@ -42,22 +42,26 @@ PREFIX_DIR = os.path.dirname(os.path.dirname(os.path.realpath(sys.argv[0]))) def main(argv=None): parser = argparse.ArgumentParser( - description='Configure and setup the agent. In a full run it will detect running' + - ' daemons then configure and start the agent.', + description='Configure and setup the agent. In a full run it will' + + ' detect running daemons then configure and start the agent.', formatter_class=argparse.ArgumentDefaultsHelpFormatter) args = parse_arguments(parser) if args.verbose: - logging.basicConfig(level=logging.DEBUG, format="%(levelname)s: %(message)s") + logging.basicConfig(level=logging.DEBUG, + format="%(levelname)s: %(message)s") else: - logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s") + logging.basicConfig(level=logging.INFO, + format="%(levelname)s: %(message)s") if args.dry_run: - log.info("Running in dry run mode, no changes will be made only reported") + LOG.info("Running in dry run mode, no changes will be made only" + " reported") # 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, name=args.agent_service_name) + agent_service = detect_init(PREFIX_DIR, args.config_dir, args.log_dir, + args.template_dir, username=args.user, + name=args.agent_service_name) # Skip base setup if only installing plugins or running specific detection # plugins @@ -66,9 +70,11 @@ def main(argv=None): agent_service.enable() # Verify required options - if args.username is None or args.password is None or args.keystone_url is None: - log.error('Username, password and keystone_url are required when running full' - 'configuration.') + if (args.username is None or + args.password is None or + args.keystone_url is None): + LOG.error('Username, password and keystone_url are required when' + ' running full configuration.') parser.print_help() sys.exit(1) base_configuration(args) @@ -79,50 +85,59 @@ def main(argv=None): from detection.plugins.system import System plugins = [System] elif args.detection_plugins is not None: - plugins = utils.select_plugins(args.detection_plugins, detected_plugins) + plugins = utils.select_plugins(args.detection_plugins, + detected_plugins) elif args.skip_detection_plugins is not None: - plugins = utils.select_plugins(args.skip_detection_plugins, detected_plugins, skip=True) + plugins = utils.select_plugins(args.skip_detection_plugins, + detected_plugins, skip=True) else: plugins = detected_plugins plugin_names = [p.__name__ for p in plugins] - if args.remove: # Remove entries for each plugin from the various plugin config files + # Remove entries for each plugin from the various plugin config files. + if args.remove: changes = remove_config(args, plugin_names) else: # Run detection for all the plugins, halting on any failures if plugins # were specified in the arguments - detected_config = plugin_detection(plugins, args.template_dir, args.detection_args, + detected_config = plugin_detection(plugins, args.template_dir, + args.detection_args, args.detection_args_json, - skip_failed=(args.detection_plugins is None)) + skip_failed=(args.detection_plugins + is None)) if detected_config is None: - # Indicates detection problem, skip remaining steps and give non-zero exit code + # Indicates detection problem, skip remaining steps and give + # non-zero exit code return 1 changes = modify_config(args, detected_config) # Don't restart if only doing detection plugins and no changes found if args.detection_plugins is not None and not changes: - log.info( + LOG.info( 'No changes found for plugins {0}, skipping restart of' 'Monasca Agent'.format(plugin_names)) return 0 elif args.dry_run: - log.info('Running in dry mode, skipping changes and restart of Monasca Agent') + LOG.info('Running in dry mode, skipping changes and restart of' + ' Monasca Agent') return 0 # Now that the config is built, start the service if args.install_plugins_only: - log.info('Command line option install_plugins_only set, skipping ' + LOG.info('Command line option install_plugins_only set, skipping ' 'service (re)start.') else: try: agent_service.start(restart=True) except subprocess.CalledProcessError: - log.error('The service did not startup correctly see %s' % args.log_dir) + LOG.error('The service did not startup correctly see %s', + args.log_dir) def base_configuration(args): """Write out the primary Agent configuration and setup the service. + :param args: Arguments from the command line :return: None """ @@ -132,15 +147,17 @@ def base_configuration(args): gid = stat.pw_gid # Write the main agent.yaml - Note this is always overwritten - log.info('Configuring base Agent settings.') + LOG.info('Configuring base Agent settings.') dimensions = {} # Join service in with the dimensions if args.service: dimensions.update({'service': args.service}) if args.dimensions: - dimensions.update(dict(item.strip().split(":") for item in args.dimensions.split(","))) + dimensions.update(dict(item.strip().split(":") + for item in args.dimensions.split(","))) - args.dimensions = dict((name, value) for (name, value) in dimensions.items()) + args.dimensions = dict((name, value) + for (name, value) in dimensions.items()) write_template(os.path.join(args.template_dir, 'agent.yaml.template'), os.path.join(args.config_dir, 'agent.yaml'), {'args': args, 'hostname': socket.getfqdn()}, @@ -148,13 +165,6 @@ def base_configuration(args): user=uid, is_yaml=True) - # Write the supervisor.conf - write_template(os.path.join(args.template_dir, 'supervisor.conf.template'), - os.path.join(args.config_dir, 'supervisor.conf'), - {'prefix': PREFIX_DIR, 'log_dir': args.log_dir, 'monasca_user': args.user}, - user=uid, - group=gid) - def modify_config(args, detected_config): """Compare existing and detected config for each check plugin and write out @@ -169,7 +179,8 @@ def modify_config(args, detected_config): continue else: agent_config.save_plugin_config( - args.config_dir, detection_plugin_name, args.user, new_config) + args.config_dir, detection_plugin_name, args.user, + new_config) else: config = agent_config.read_plugin_config_from_disk( args.config_dir, detection_plugin_name) @@ -191,9 +202,11 @@ def modify_config(args, detected_config): # Check endpoint change, use new protocol instead # Note: config is possibly changed after running # check_endpoint_changes function. - config = agent_config.check_endpoint_changes(new_config, config) + config = agent_config.check_endpoint_changes(new_config, + config) - agent_config.merge_by_name(new_config['instances'], config['instances']) + agent_config.merge_by_name(new_config['instances'], + config['instances']) # Sort before compare, if instances have no name the sort will # fail making order changes significant try: @@ -203,7 +216,8 @@ def modify_config(args, detected_config): pass if detection_plugin_name == "http_check": - new_config_urls = [i['url'] for i in new_config['instances'] if 'url' in i] + new_config_urls = [i['url'] for i in new_config['instances'] + if 'url' in i] # Don't write config if no change if new_config_urls == config_urls and new_config == config: continue @@ -212,18 +226,20 @@ def modify_config(args, detected_config): continue modified_config = True if args.dry_run: - log.info("Changes would be made to the config file for the {0}" + LOG.info("Changes would be made to the config file for the {0}" " check plugin".format(detection_plugin_name)) else: agent_config.save_plugin_config( - args.config_dir, detection_plugin_name, args.user, new_config) + args.config_dir, detection_plugin_name, args.user, + new_config) return modified_config def validate_positive(value): int_value = int(value) if int_value <= 0: - raise argparse.ArgumentTypeError("%s must be greater than zero" % value) + raise argparse.ArgumentTypeError("%s must be greater than zero" % + value) return int_value @@ -231,11 +247,13 @@ def parse_arguments(parser): parser.add_argument( '-u', '--username', - help="Username used for keystone authentication. Required for basic configuration.") + help="Username used for keystone authentication. " + + "Required for basic configuration.") parser.add_argument( '-p', '--password', - help="Password used for keystone authentication. Required for basic configuration.") + help="Password used for keystone authentication. " + + "Required for basic configuration.") parser.add_argument( '--user_domain_id', @@ -245,8 +263,9 @@ def parse_arguments(parser): '--user_domain_name', help="User domain name for keystone authentication", default='') - parser.add_argument('--keystone_url', help="Keystone url. Required for basic configuration.") - + parser.add_argument( + '--keystone_url', + help="Keystone url. Required for basic configuration.") parser.add_argument( '--project_name', help="Project name for keystone authentication", @@ -263,7 +282,6 @@ def parse_arguments(parser): '--project_id', help="Keystone project id for keystone authentication", default='') - parser.add_argument( '--monasca_url', help="Monasca API url, if not defined the url is pulled from keystone", @@ -281,7 +299,6 @@ def parse_arguments(parser): '--region_name', help="Monasca API url region name in keystone catalog", default='') - parser.add_argument( '--system_only', help="Setup the service but only configure the base config and system " + @@ -292,10 +309,12 @@ def parse_arguments(parser): '-d', '--detection_plugins', nargs='*', - help="Skip base config and service setup and only configure this space separated list. " + - "This assumes the base config has already run.") - parser.add_argument('--skip_detection_plugins', nargs='*', - help="Skip detection for all plugins in this space separated list.") + help="Skip base config and service setup and only configure this " + + "space separated list. " + + "This assumes the base config has already run.") + parser.add_argument( + '--skip_detection_plugins', nargs='*', + help="Skip detection for all plugins in this space separated list.") detection_args_group = parser.add_mutually_exclusive_group() detection_args_group.add_argument( '-a', @@ -445,12 +464,13 @@ def plugin_detection( if detect.available: new_config = detect.build_config_with_name() if not remove: - log.info('Configuring {0}'.format(detect.name)) + LOG.info('Configuring {0}'.format(detect.name)) if new_config is not None: plugin_config.merge(new_config) elif not skip_failed: - log.warn('Failed detection of plugin {0}.'.format(detect.name) + - "\n\tPossible causes: Service not found or missing arguments.") + LOG.warning("Failed detection of plugin %s." + "\n\tPossible causes: Service not found or missing" + "arguments.", detect.name) return None return plugin_config @@ -495,7 +515,10 @@ def remove_config(args, plugin_names): deletes = True config['instances'].remove(inst) if deletes: - agent_config.delete_from_config(args, config, file_path, plugin_name) + agent_config.delete_from_config(args, config, file_path, + plugin_name) return changes + + if __name__ == "__main__": sys.exit(main()) diff --git a/monasca_setup/service/__init__.py b/monasca_setup/service/__init__.py index cf4b0e35..9f642fe5 100644 --- a/monasca_setup/service/__init__.py +++ b/monasca_setup/service/__init__.py @@ -1,14 +1,5 @@ -# (C) Copyright 2015 Hewlett Packard Enterprise Development Company LP -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. +"""Setup system service for monasca-agent and its dependencies. -from service import Service # noqa +Configure systemd to start monasca-collector, monasca-forwarder, +and monasca-statsd services using a target named monasca-agent. +""" diff --git a/monasca_setup/service/detection.py b/monasca_setup/service/detection.py index b2252d5c..2bd5d889 100644 --- a/monasca_setup/service/detection.py +++ b/monasca_setup/service/detection.py @@ -15,43 +15,30 @@ import logging import platform import sys -import linux +from monasca_setup.service import linux -log = logging.getLogger(__name__) +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 appropriate Service object for this system + """Create a service object if possible. + + Detect if systemd is present on the system and if so return the service + object. + + :return: a systemd service object for this system. """ detected_os = platform.system() - if detected_os == 'Linux': - supported_linux_flavors = [ - 'ubuntu', 'debian', - 'centos linux', 'red hat enterprise linux server', - 'suse linux enterprise server' - ] - flavor = platform.linux_distribution()[0].strip() - if flavor.lower() not in supported_linux_flavors: - log.warn('{0} is not a supported 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 + if has_systemd(): + return linux.Systemd(*args, **kwargs) + LOG.error("{0} is not currently supported by the Monasca Agent" + .format(detected_os)) + sys.exit(1) -def detect_linux_init(*args, **kwargs): - """Detect which of the linux inits is running - :return: Return a valid Linux service manager object - """ +def has_systemd(): + """Detect if Linux init is systemd.""" 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) + return init == 'systemd' diff --git a/monasca_setup/service/linux.py b/monasca_setup/service/linux.py index 8d3e1512..382067c6 100644 --- a/monasca_setup/service/linux.py +++ b/monasca_setup/service/linux.py @@ -13,24 +13,37 @@ """ Systemd based service """ -import glob import logging import os import pwd import subprocess -import service +LOG = logging.getLogger(__name__) -log = logging.getLogger(__name__) +class Systemd(object): + """Manage service using systemd.""" + def __init__(self, prefix_dir, config_dir, log_dir, template_dir, + name='monasca-agent', username='mon-agent'): + """Create a service.""" + 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 -class LinuxInit(service.Service): - """Parent class for all Linux based init systems. - """ def enable(self): - """Does user/group directory creation. + """Set monasca-agent to start on boot. + + Generally this requires running as super user. """ + if os.geteuid() != 0: + LOG.error('This service must be run as root') + raise OSError + + # LinuxInit.enable(self) # Create user/group if needed try: user = pwd.getpwnam(self.username) @@ -39,171 +52,89 @@ class LinuxInit(service.Service): user = pwd.getpwnam(self.username) # Create dirs - # todo log dir is hardcoded - for path in (self.log_dir, self.config_dir, '%s/conf.d' % self.config_dir): + for path in (self.log_dir, self.config_dir, + '%s/conf.d' % self.config_dir): if not os.path.exists(path): os.makedirs(path, 0o755) os.chown(path, 0, user.pw_gid) - # the log dir needs to be writable by the user + # 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 + # Get systemd services and target template + templates = [f for f in os.listdir(self.template_dir) + if (f.endswith('service.template') or + f.endswith('target.template'))] + systemd_path = '/etc/systemd/system/' - 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, 0o644) + # Write the systemd units configuration file: we have 3 services and + # 1 target grouping all of them together + for template_file_name in templates: + service_file_name, e = os.path.splitext(template_file_name) + service_file_path = os.path.join(systemd_path, + service_file_name) + with open(os.path.join(self.template_dir, + template_file_name), 'r') as template: + with open(service_file_path, 'w') as service_file: + LOG.info('Creating service file %s', service_file_name) + service_file.write(template.read(). + format(prefix=self.prefix_dir, + monasca_user=self.username)) + os.chown(service_file_path, 0, 0) + os.chmod(service_file_path, 0o644) # 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)) + subprocess.check_call( + ['systemctl', 'enable', '{0}.target'.format(self.name)]) + LOG.info('Enabled %s target via systemd', self.name) def start(self, restart=True): - """Starts monasca-agent. + """Start monasca-agent. - If the agent is running and restart is True, restart + If the agent is running and restart is True restart it. + + :return: True if monasca-agent is enabled on boot, False otherwise.. """ - 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)]) + if not self.is_enabled(): + LOG.error('The service is not enabled') + return False + LOG.info('Starting %s services via systemd', self.name) + if self.is_running() and restart: + subprocess.check_call( + ['systemctl', 'restart', '{0}.target'.format(self.name)]) + else: + subprocess.check_call( + ['systemctl', 'start', '{0}.target'.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. + """Stop monasca-agent. + :return: True if monasca-agent was stopped successfully, False otherwise """ + LOG.info('Stopping %s services', self.name) try: - subprocess.check_output(['systemctl', 'is-enabled', '{0}.service'.format(self.name)]) - except subprocess.CalledProcessError: + subprocess.check_call( + ['systemctl', 'stop', '{0}.target'.format(self.name)]) + except subprocess.CalledProcessError as call_error: + LOG.error('Unable to stop monasca-agent.') + LOG.error(call_error.output) return False - - return True - - -class SysV(LinuxInit): - - def __init__( - self, - prefix_dir, - config_dir, - log_dir, - template_dir, - username, - name='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: - conf.write( - template.read().format( - prefix=self.prefix_dir, - monasca_user=self.username, - config_dir=self.config_dir)) - os.chown(self.init_script, 0, 0) - os.chmod(self.init_script, 0o755) - - for runlevel in ['2', '3', '4', '5']: - link_path = '/etc/rc%s.d/S10monasca-agent' % runlevel - if not os.path.exists(link_path): - os.symlink(self.init_script, link_path) - - log.info('Enabled {0} service via SysV init script'.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 SysV init script'.format(self.name)) - if restart: - # Throws CalledProcessError on error - subprocess.check_call([self.init_script, 'restart']) else: - subprocess.check_call([self.init_script, 'start']) # Throws CalledProcessError on error - return True + return True - def stop(self): - """Stops monasca-agent. + def is_running(self): + """Check if monasca-agent is running. + :return: True if monasca-agent is running, false otherwise. """ - 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 - return True + return(subprocess.call(['systemctl', 'is-active', '--quiet', + '{0}.target'.format(self.name)]) == 0) def is_enabled(self): - """Returns True if monasca-agent is setup to start on boot, false otherwise. + """Check if monasca-agent is setup to start at boot time. + :return: True if monasca-agent is enabled on boot, False otherwise. """ - if not os.path.exists(self.init_script): - return False - - if len(glob.glob('/etc/rc?.d/S??monasca-agent')) > 0: - return True - else: - return False + return(subprocess.call(['systemctl', 'is-enabled', '--quiet', + '{0}.target'.format(self.name)]) == 0) diff --git a/monasca_setup/service/service.py b/monasca_setup/service/service.py deleted file mode 100644 index af1ffb80..00000000 --- a/monasca_setup/service/service.py +++ /dev/null @@ -1,70 +0,0 @@ -# (C) Copyright 2015 Hewlett Packard Enterprise Development Company LP -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -"""Code to handle various service managers used on different OS - -""" -from monasca_agent.common.psutil_wrapper import psutil - - -class Service(object): - """Abstract base class implementing the interface for various service types. - - """ - - 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. - - Generally this requires running as super user - """ - raise NotImplementedError - - def start(self, restart=True): - """Starts monasca-agent. - - If the agent is running and restart is True, restart - """ - raise NotImplementedError - - def stop(self): - """Stops monasca-agent. - - """ - raise NotImplementedError - - def is_enabled(self): - """Returns True if monasca-agent is setup to start on boot, false otherwise. - - """ - raise NotImplementedError - - 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 ('{0}/supervisor.conf'.format(self.config_dir) - in process.as_dict(['cmdline'])['cmdline']): - return True - - return False diff --git a/packaging/Makefile b/packaging/Makefile index fae37f61..06abce1d 100644 --- a/packaging/Makefile +++ b/packaging/Makefile @@ -69,9 +69,6 @@ install_deb: install_full mkdir -p $(BUILD)/etc/init.d cp monasca-agent-deb/monasca-agent.init $(BUILD)/etc/init.d/monasca-agent chmod 755 $(BUILD)/etc/init.d/monasca-agent - # Install supervisor config. - cp monasca-agent-deb/supervisor.conf $(BUILD)/etc/monasca/agent/supervisor.conf - # Make the monasca agent debian package that includes supervisor, the forwarder # etc. @@ -81,7 +78,6 @@ $(FPM_BUILD) -t deb \ -n monasca-agent \ -d "python (>= 2.6)" \ -d "python-tornado (>= 2.3)" \ --d "supervisor (>= 3.0)" \ -d "adduser" \ -d "sysstat" \ -d "python-pycurl" \ diff --git a/packaging/monasca-agent.service.template b/packaging/monasca-agent.service.template deleted file mode 100644 index 92d16aad..00000000 --- a/packaging/monasca-agent.service.template +++ /dev/null @@ -1,12 +0,0 @@ -[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/packaging/monasca-agent.target.template b/packaging/monasca-agent.target.template new file mode 100644 index 00000000..a31ed0b5 --- /dev/null +++ b/packaging/monasca-agent.target.template @@ -0,0 +1,6 @@ +[Unit] +Description=Monasca Agent +Wants=monasca-collector.service monasca-forwarder.service monasca-statsd.service + +[Install] +WantedBy=multi-user.target diff --git a/packaging/monasca-collector.service.template b/packaging/monasca-collector.service.template new file mode 100644 index 00000000..e0e25afb --- /dev/null +++ b/packaging/monasca-collector.service.template @@ -0,0 +1,15 @@ +[Unit] +Description=Monasca Agent - Collector +PartOf=monasca-agent.target +After=monasca-forwarder.service +Wants=monasca-forwarder.service + +[Service] +Type=simple +User={monasca_user} +Group={monasca_user} +Restart=always +ExecStart={prefix}/bin/monasca-collector foreground + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/packaging/monasca-forwarder.service.template b/packaging/monasca-forwarder.service.template new file mode 100644 index 00000000..6ee6303f --- /dev/null +++ b/packaging/monasca-forwarder.service.template @@ -0,0 +1,15 @@ +[Unit] +Description=Monasca Agent - Forwarder +PartOf=monasca-agent.target +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +User={monasca_user} +Group={monasca_user} +Restart=always +ExecStart={prefix}/bin/monasca-forwarder + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/packaging/monasca-statsd.service.template b/packaging/monasca-statsd.service.template new file mode 100644 index 00000000..74fb4fb6 --- /dev/null +++ b/packaging/monasca-statsd.service.template @@ -0,0 +1,15 @@ +[Unit] +Description=Monasca Agent - Statsd +PartOf=monasca-agent.target +After=monasca-forwarder.service +Wants=monasca-forwarder.service + +[Service] +Type=simple +User={monasca_user} +Group={monasca_user} +Restart=always +ExecStart={prefix}/bin/monasca-statsd + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/packaging/supervisor.conf.template b/packaging/supervisor.conf.template deleted file mode 100644 index 9ef7c531..00000000 --- a/packaging/supervisor.conf.template +++ /dev/null @@ -1,48 +0,0 @@ -[supervisorctl] -serverurl = unix:///var/tmp/monasca-agent-supervisor.sock - -[unix_http_server] -file=/var/tmp/monasca-agent-supervisor.sock - -[rpcinterface:supervisor] -supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface - -[supervisord] -minfds = 1024 -minprocs = 200 -loglevel = info -logfile = {log_dir}/supervisord.log -logfile_maxbytes = 50MB -nodaemon = false -pidfile = /var/run/monasca-agent-supervisord.pid -logfile_backups = 10 - -[program:collector] -command={prefix}/bin/monasca-collector foreground -stdout_logfile=NONE -stderr_logfile=NONE -priority=999 -startsecs=2 -user={monasca_user} -autorestart=true - -[program:forwarder] -command={prefix}/bin/monasca-forwarder -stdout_logfile=NONE -stderr_logfile=NONE -startsecs=3 -priority=998 -user={monasca_user} -autorestart=true - -[program:statsd] -command={prefix}/bin/monasca-statsd -stdout_logfile=NONE -stderr_logfile=NONE -startsecs=3 -priority=998 -user={monasca_user} -autorestart=true - -[group:monasca-agent] -programs=forwarder,collector,statsd diff --git a/requirements.txt b/requirements.txt index 46442846..61be734d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -20,7 +20,6 @@ python-keystoneclient>=3.8.0 # Apache-2.0 redis>=2.10.0 # MIT six>=1.10.0 # MIT stevedore>=1.20.0 # Apache-2.0 -supervisor>=3.3.3,<3.4 # BSD-derived tornado>=4.5.3 # Apache-2.0 futures>=3.0.0;python_version=='2.7' or python_version=='2.6' # BSD # NOTE(sdague): before allowing in >= 0.21 please be sure diff --git a/setup.cfg b/setup.cfg index 74c94f50..eb4682d7 100644 --- a/setup.cfg +++ b/setup.cfg @@ -23,9 +23,10 @@ packages = data_files= share/monasca/agent = agent.yaml.template - packaging/supervisor.conf.template - packaging/monasca-agent.init.template - packaging/monasca-agent.service.template + packaging/monasca-collector.service.template + packaging/monasca-forwarder.service.template + packaging/monasca-statsd.service.template + packaging/monasca-agent.target.template share/monasca/agent/conf.d = conf.d/* [entry_points]