mirror of
https://github.com/sprockets/sprockets.git
synced 2024-11-23 19:29:52 +00:00
Remove daemonization
No need in first revision
This commit is contained in:
parent
4d81938155
commit
b03b3a36c8
2 changed files with 6 additions and 300 deletions
|
@ -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')
|
||||||
|
|
|
@ -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()))
|
|
Loading…
Reference in a new issue