Remove daemonization

No need in first revision
This commit is contained in:
Gavin M. Roy 2014-08-25 10:51:36 -04:00
parent 4d81938155
commit b03b3a36c8
2 changed files with 6 additions and 300 deletions

View file

@ -32,7 +32,6 @@ else:
import pkg_resources import pkg_resources
from sprockets import daemon
from sprockets import __version__ from sprockets import __version__
DESCRIPTION = 'Available sprockets application controllers' DESCRIPTION = 'Available sprockets application controllers'
@ -91,36 +90,14 @@ class CLI(object):
# The list command prevents any other processing of args # The list command prevents any other processing of args
if self._args.list: if self._args.list:
self._print_installed_apps(self._args.controller) self._print_installed_apps(self._args.controller)
sys.exit(0)
# Should be starting an application # If app is not specified at this point, raise an error
else: if not self._args.application:
sys.stderr.write('\nerror: application not specified\n\n')
self._arg_parser.print_help()
sys.exit(-1)
# If app is not specified at this point, raise an error
if not self._args.application:
sys.stderr.write('\nerror: application not specified\n\n')
self._arg_parser.print_help()
sys.exit(-1)
# Fork into the background as a daemon if instructed to do so
if self._args.daemonize:
try:
with daemon.Daemon(self) as obj:
obj.start()
except (OSError, ValueError) as error:
sys.stderr.write('\nerror: could not start %s (%s)\n\n' %
(sys.argv[0], error))
sys.exit(1)
else:
# Start the application directly, no forking involved
self.start()
def start(self):
"""Invoked once the application is ready to be started. If the cli
args indicate that the application should be a daemon, it will already
be forked at this point.
"""
# If it's a registered app reference by name, get the module name # If it's a registered app reference by name, get the module name
app_module = self._get_application_module(self._args.controller, app_module = self._get_application_module(self._args.controller,
self._args.application) self._args.application)
@ -149,10 +126,6 @@ class CLI(object):
action='store_true', action='store_true',
help='List installed sprockets apps') help='List installed sprockets apps')
self._arg_parser.add_argument('-d', '--daemonize',
action='store_true',
help='Fork into a background process')
self._arg_parser.add_argument('-s', '--syslog', self._arg_parser.add_argument('-s', '--syslog',
action='store_true', action='store_true',
help='Log to syslog') help='Log to syslog')

View file

@ -1,267 +0,0 @@
"""
Unix daemonization support
"""
import atexit
import datetime
import grp
import logging
import os
from os import path
import platform
import pwd
import re
import subprocess
import sys
import traceback
import warnings
# Ignore the DeprecationWarning caused by os.popen3 in Python 2.6
warnings.filterwarnings("ignore", category=DeprecationWarning)
LOGGER = logging.getLogger(__name__)
def operating_system():
"""Return a string identifying the operating system the application
is running on.
:rtype: str
"""
if platform.system() == 'Darwin':
return 'OS X Version %s' % platform.mac_ver()[0]
distribution = ' '.join(platform.linux_distribution()).strip()
os_platform = platform.platform(True, True)
if distribution:
os_platform += ' (%s)' % distribution
return os_platform
class Daemon(object):
"""Daemonize the helper application, putting it in a forked background
process.
"""
def __init__(self, controller):
"""Daemonize the controller, optionally passing in the user and group
to run as, a pid file, if core dumps should be prevented and a path to
write out exception logs to.
:param controller: The controller to daaemonize & run
:type controller: helper.controller.Controller
"""
# The logger is reset by the time it gets here, fix to avoid warnings
from sprockets import NullHandler
LOGGER.addHandler(NullHandler())
self.controller = controller
self.pidfile_path = self._get_pidfile_path()
def __enter__(self):
"""Context manager method to return the handle to this object.
:rtype: Daemon
"""
return self
def __exit__(self, exc_type, exc_val, exc_tb):
"""When leaving the context, examine why the context is leaving, if
it's an exception and log the error
"""
if exc_type and not isinstance(exc_val, SystemExit):
LOGGER.error('Daemon context manager closed on exception: %r',
exc_type)
def start(self):
"""Daemonize if the process is not already running."""
if self._is_already_running():
LOGGER.error('Is already running')
sys.exit(1)
try:
self._daemonize()
self.controller.start()
except Exception as error:
sys.stderr.write('\nERROR: Startup of %s Failed\n.' %
sys.argv[0].split('/')[-1])
exception_log = self._get_exception_log_path()
if exception_log:
with open(exception_log, 'a') as handle:
timestamp = datetime.datetime.now().isoformat()
handle.write('{:->80}\n'.format(' [START]'))
handle.write('%s Exception [%s]\n' % (sys.argv[0],
timestamp))
handle.write('{:->80}\n'.format(' [INFO]'))
handle.write('Interpreter: %s\n' % sys.executable)
handle.write('CLI arguments: %s\n' % ' '.join(sys.argv))
handle.write('Exception: %s\n' % error)
handle.write('Traceback:\n')
output = traceback.format_exception(*sys.exc_info())
_dev_null = [(handle.write(line),
sys.stdout.write(line)) for line in output]
handle.write('{:->80}\n'.format(' [END]'))
handle.flush()
sys.stderr.write('\nException log: %s\n\n' % exception_log)
sys.exit(1)
@property
def gid(self):
"""Return the group id that the daemon will run with
:rtype: int
"""
return os.getgid()
@property
def uid(self):
"""Return the user id that the process will run as
:rtype: int
"""
return os.getuid()
def _daemonize(self):
"""Fork into a background process and setup the process, copied in part
from http://www.jejik.com/files/examples/daemon3x.py
"""
LOGGER.info('Forking %s into the background', sys.argv[0])
# Write the pidfile if current uid != final uid
if os.getuid() != self.uid:
fd = open(self.pidfile_path, 'w')
os.fchmod(fd.fileno(), 0o644)
os.fchown(fd.fileno(), self.uid, self.gid)
fd.close()
try:
pid = os.fork()
if pid > 0:
sys.exit(0)
except OSError as error:
raise OSError('Could not fork off parent: %s', error)
# Decouple from parent environment
os.chdir('/')
os.setsid()
os.umask(0o022)
# Fork again
try:
pid = os.fork()
if pid > 0:
sys.exit(0)
except OSError as error:
raise OSError('Could not fork child: %s', error)
# redirect standard file descriptors
sys.stdout.flush()
sys.stderr.flush()
si = open(os.devnull, 'r')
so = open(os.devnull, 'a+')
se = open(os.devnull, 'a+')
os.dup2(si.fileno(), sys.stdin.fileno())
os.dup2(so.fileno(), sys.stdout.fileno())
os.dup2(se.fileno(), sys.stderr.fileno())
# Automatically call self._remove_pidfile when the app exits
atexit.register(self._remove_pidfile)
self._write_pidfile()
@staticmethod
def _get_exception_log_path():
"""Return the normalized path for the connection log, raising an
exception if it can not written to.
:return: str
"""
app = sys.argv[0].split('/')[-1]
for exception_log in ['/var/log/%s.errors' % app,
'/var/tmp/%s.errors' % app,
'/tmp/%s.errors' % app]:
if os.access(path.dirname(exception_log), os.W_OK):
return exception_log
return None
def _get_pidfile_path(self):
"""Return the normalized path for the pidfile, raising an
exception if it can not written to.
:return: str
:raises: ValueError
:raises: OSError
"""
app = sys.argv[0].split('/')[-1]
for pidfile in ['%s/pids/%s.pid' % (os.getcwd(), app),
'/var/run/%s.pid' % app,
'/var/run/%s/%s.pid' % (app, app),
'/var/tmp/%s.pid' % app,
'/tmp/%s.pid' % app,
'%s.pid' % app]:
if os.access(path.dirname(pidfile), os.W_OK):
return pidfile
raise OSError('Could not find an appropriate place for a pid file')
def _is_already_running(self):
"""Check to see if the process is running, first looking for a pidfile,
then shelling out in either case, removing a pidfile if it exists but
the process is not running.
"""
# Look for the pidfile, if exists determine if the process is alive
pidfile = self._get_pidfile_path()
if os.path.exists(pidfile):
pid = open(pidfile).read().strip()
try:
os.kill(int(pid), 0)
sys.stderr.write('Process already running as pid # %s\n' % pid)
return True
except OSError as error:
LOGGER.debug('Found pidfile, no process # %s', error)
os.unlink(pidfile)
# Check the os for a process that is not this one that looks the same
pattern = ' '.join(sys.argv)
pattern = '[%s]%s' % (pattern[0], pattern[1:])
try:
output = subprocess.check_output('ps a | grep "%s"' % pattern,
shell=True)
except AttributeError:
# Python 2.6
stdin, stdout, stderr = os.popen3('ps a | grep "%s"' % pattern)
output = stdout.read()
except subprocess.CalledProcessError:
return False
pids = [int(pid) for pid in (re.findall(r'^([0-9]+)\s',
output.decode('latin-1')))]
if os.getpid() in pids:
pids.remove(os.getpid())
if not pids:
return False
if len(pids) == 1:
pids = pids[0]
sys.stderr.write('Process already running as pid # %s\n' % pids)
return True
def _remove_pidfile(self):
"""Remove the pid file from the filesystem"""
LOGGER.debug('Removing pidfile: %s', self.pidfile_path)
try:
os.unlink(self.pidfile_path)
except OSError:
pass
def _write_pidfile(self):
"""Write the pid file out with the process number in the pid file"""
LOGGER.debug('Writing pidfile: %s', self.pidfile_path)
with open(self.pidfile_path, "w") as handle:
handle.write(str(os.getpid()))