Source code for provy.more.debian.monitoring.supervisor

#!/usr/bin/python
# -*- coding: utf-8 -*-

'''
Roles in this namespace are meant to provide `Supervisor <http://supervisord.org/>`_ monitoring utility methods for Debian distributions.
'''

from os.path import join

from provy.core import Role
from provy.more.debian.package.pip import PipRole

PROGRAMS_KEY = 'supervisor-programs'
CONFIG_KEY = 'supervisor-config'
MUST_UPDATE_CONFIG_KEY = 'must-update-supervisor-config'
MUST_RESTART_KEY = 'must-restart-supervisor'


[docs]class WithProgram(object): ''' This class acts as the context manager for the :meth:`SupervisorRole.with_program` method. Don't use it directly; Instead, use the :meth:`SupervisorRole.with_program` method. ''' def __init__(self, supervisor, name): self.supervisor = supervisor self.name = name self.directory = None self.command = None self.process_name = name + '-%(process_num)s' self.number_of_processes = 1 self.priority = 100 self.user = self.supervisor.context['owner'] self.auto_start = True self.auto_restart = True self.start_retries = 3 self.stop_signal = 'TERM' self.log_folder = '/var/log' self.log_file_max_mb = 1 self.log_file_backups = 10 self.environment = {} def __enter__(self): return self def __exit__(self, exc_type, exc_value, traceback): if not self.directory or not self.command: raise RuntimeError('[Supervisor] Both directory and command properties must be specified for program %s' % self.name) if PROGRAMS_KEY not in self.supervisor.context: self.supervisor.context[PROGRAMS_KEY] = [] env = [] for key, value in self.environment.iteritems(): env.append('%s="%s"' % (key, value)) env = ",".join(env) self.supervisor.context[PROGRAMS_KEY].append({ 'name': self.name, 'directory': self.directory, 'command': self.command, 'process_name': self.process_name, 'number_of_processes': self.number_of_processes, 'priority': self.priority, 'user': self.user, 'auto_start': self.auto_start, 'auto_restart': self.auto_restart, 'start_retries': self.start_retries, 'stop_signal': self.stop_signal, 'log_folder': self.log_folder, 'log_file_max_mb': self.log_file_max_mb, 'log_file_backups': self.log_file_backups, 'environment': env })
[docs]class SupervisorRole(Role): ''' This role provides `Supervisor <http://supervisord.org/>`_ monitoring utilities for Debian distributions. Example: :: from provy.core import Role from provy.more.debian import SupervisorRole class MySampleRole(Role): def provision(self): with self.using(SupervisorRole) as role: role.config( config_file_directory='/home/backend', log_folder='/home/backend/logs', user=self.context['supervisor-user'] ) with role.with_program('website') as program: program.directory = '/home/backend/provy/tests/functional' program.command = 'python website.py 800%(process_num)s' program.number_of_processes = 4 program.log_folder = '/home/backend/logs' '''
[docs] def provision(self): ''' Installs `Supervisor <http://supervisord.org/>`_ and its dependencies. This method should be called upon if overriden in base classes, or `Supervisor <http://supervisord.org/>`_ won't work properly in the remote server. Example: :: from provy.core import Role from provy.more.debian import SupervisorRole class MySampleRole(Role): def provision(self): self.provision_role(SupervisorRole) # no need to call this if using with block. ''' self.register_template_loader('provy.more.debian.monitoring') with self.using(PipRole) as pip: pip.set_sudo() pip.ensure_package_installed('supervisor')
[docs] def update_init_script(self, config_file_path): ''' Creates a supervisord `/etc/init.d` script that points to the specified config file path. :param config_file_path: Path to the `supervisord.conf` at the server. :type config_file_path: :class:`str` Example: :: from provy.core import Role from provy.more.debian import SupervisorRole class MySampleRole(Role): def provision(self): with self.using(SupervisorRole) as role: role.update_init_script('/etc/supervisord.conf') ''' options = {'config_file': join(config_file_path, 'supervisord.conf')} result = self.update_file('supervisord.init.template', '/etc/init.d/supervisord', owner=self.context['owner'], options=options, sudo=True) if result: self.execute('chmod +x /etc/init.d/supervisord', stdout=False, sudo=True) self.execute('update-rc.d supervisord defaults', stdout=False, sudo=True) self.ensure_restart()
[docs] def ensure_config_update(self): ''' Makes sure that the config file is updated upon cleanup. Example: :: from provy.core import Role from provy.more.debian import SupervisorRole class MySampleRole(Role): def provision(self): with self.using(SupervisorRole) as role: role.ensure_config_update() ''' self.context[MUST_UPDATE_CONFIG_KEY] = True
[docs] def config(self, config_file_directory=None, log_folder='/var/log', log_file_max_mb=50, log_file_backups=10, log_level='info', pidfile='/var/run/supervisord.pid', user=None): ''' Configures supervisor by creating a supervisord.conf file at the specified location. :param config_file_directory: Directory to create the supervisord.conf file at the server. :type config_file_directory: :class:`str` :param log_folder: Path where log files will be created by supervisor. Defaults to `/var/log` (if you use the default, make sure your user has access). :type log_folder: :class:`str` :param log_file_max_mb: Maximum size of log file in megabytes. Defaults to 50. :type log_file_max_mb: :class:`int` :param log_file_backups: Number of log backups that supervisor keeps. Defaults to 10. :type log_file_backups: :class:`int` :param log_level: Level of logging for supervisor. Defaults to 'info'. :type log_level: :class:`str` :param pidfile: Path for the pidfile that supervisor creates for itself. Defaults to `/var/run/supervisor.pid` (if you use the default, make sure your user has access). :type pidfile: :class:`str` :param user: User that runs supervisor. Defaults to :data:`None`, which means the last created user. :type user: :class:`str` Example: :: from provy.core import Role from provy.more.debian import SupervisorRole class MySampleRole(Role): def provision(self): with self.using(SupervisorRole) as role: role.config( config_file_directory='/home/backend', log_folder='/home/backend/logs', pidfile='/home/backend/supervisord.pid', user='backend' ) ''' self.log_folder = log_folder self.config_file_directory = config_file_directory, self.log_folder = log_folder self.log_file_max_mb = log_file_max_mb self.log_file_backups = log_file_backups self.log_level = log_level self.pidfile = pidfile self.user = user if config_file_directory is None: config_file_directory = '/home/%s' % self.context['owner'] if user is None: user = self.context['owner'] self.context[CONFIG_KEY] = { 'config_file_directory': config_file_directory, 'log_file': join(log_folder, 'supervisord.log'), 'log_file_max_mb': log_file_max_mb, 'log_file_backups': log_file_backups, 'log_level': log_level, 'pidfile': pidfile, 'user': user } self.ensure_config_update()
[docs] def with_program(self, name): ''' Enters a with block with a :data:`program` variable that allows you to configure a program entry in supervisord.conf. :param name: Name of the program being supervised. :type name: :class:`str` Example: :: from provy.core import Role from provy.more.debian import SupervisorRole class MySampleRole(Role): def provision(self): with self.using(SupervisorRole) as role: with role.with_program('website') as program: program.directory = '/home/backend/provy/tests/functional' program.command = 'python website.py 800%(process_num)s' program.number_of_processes = 4 program.log_folder = '/home/backend/logs' ''' return WithProgram(self, name)
[docs] def update_config_file(self): ''' Updates the config file to match the configurations done under the :meth:`config` method. There's no need to call this method after :meth:`config`, since :meth:`SupervisorRole.cleanup` will call it for you. Example: :: from provy.core import Role from provy.more.debian import SupervisorRole class MySampleRole(Role): def provision(self): with self.using(SupervisorRole) as role: role.update_config_file() ''' if CONFIG_KEY in self.context or PROGRAMS_KEY in self.context: if CONFIG_KEY not in self.context: self.config() config = self.context[CONFIG_KEY] conf_path = join(config['config_file_directory'], 'supervisord.conf') options = config if PROGRAMS_KEY in self.context: options['programs'] = self.context[PROGRAMS_KEY] result = self.update_file('supervisord.conf.template', conf_path, options=options, owner=self.context['owner'], sudo=True) if result: self.ensure_restart()
[docs] def cleanup(self): ''' Updates the config file and/or init files and restarts supervisor if needed. There's no need to call this method since provy's lifecycle will make sure it is called. ''' super(SupervisorRole, self).cleanup() if MUST_UPDATE_CONFIG_KEY in self.context and self.context[MUST_UPDATE_CONFIG_KEY]: self.update_init_script(self.context[CONFIG_KEY]['config_file_directory']) self.update_config_file() if MUST_RESTART_KEY in self.context and self.context[MUST_RESTART_KEY]: self.restart()
[docs] def ensure_restart(self): ''' Makes sure supervisor is restarted on cleanup. There's no need to call this method since it will be called when changes occur by the other methods. ''' self.context[MUST_RESTART_KEY] = True
[docs] def restart(self): ''' Forcefully restarts supervisor. Example: :: from provy.core import Role from provy.more.debian import SupervisorRole class MySampleRole(Role): def provision(self): with self.using(SupervisorRole) as role: role.restart() ''' if not self.is_process_running('supervisord'): command = '/etc/init.d/supervisord start' else: command = '/etc/init.d/supervisord restart' self.execute(command, sudo=True)