Created
June 20, 2010 00:40
-
-
Save nopper/445443 to your computer and use it in GitHub Desktop.
Simple script to manage fcgi apps (useful for django applications)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
# -*- coding: utf-8 -*- | |
# Copyright (C) 2010 Francesco Piccinno | |
# | |
# Author: Francesco Piccinno <[email protected]> | |
# | |
# This program is free software; you can redistribute it and/or modify | |
# it under the terms of the GNU General Public License as published by | |
# the Free Software Foundation; either version 2 of the License, or | |
# (at your option) any later version. | |
# | |
# This program is distributed in the hope that it will be useful, | |
# but WITHOUT ANY WARRANTY; without even the implied warranty of | |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
# GNU General Public License for more details. | |
# | |
# You should have received a copy of the GNU General Public License | |
# along with this program; if not, write to the Free Software | |
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
# Simple script to manage fcgi apps (django apps) | |
import os | |
import os.path | |
import sys | |
import logging | |
import logging.handlers | |
from subprocess import call | |
log = logging.getLogger('spawner') | |
log.setLevel(logging.DEBUG) | |
handler = logging.handlers.RotatingFileHandler( | |
'/home/django/wsgi-apps.log', maxBytes=1024**2, backupCount=3) | |
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") | |
handler.setFormatter(formatter) | |
log.addHandler(handler) | |
PIDFILE_NAME = '/home/django/pids/' | |
LOGGING_DIR = '/home/django/logs/' | |
VIRTUALENV_PATH = '/home/django/pyve/' | |
DEFAULT_ENV = 'django' | |
APPLICATIONS_PATH = '/home/django/releases/' | |
KILL_BIN = '/bin/kill' | |
apps = ( | |
{'app': 'test', | |
'vhost': 'your.vhost-here.org', | |
'virtualenv': 'django', | |
'release': 'current', | |
'port': 3034, | |
'enabled': False, | |
}, | |
) | |
class Spawner(object): | |
def __init__(self, apps): | |
self.apps = apps | |
def enabled_apps(self): | |
return filter( | |
lambda x: x.get('enabled', False) and True or False, | |
self.apps | |
) | |
def get_app(self, name): | |
for app in self.apps: | |
if app['app'] == name: | |
return app | |
def croncheck(self): | |
log.info('Checking health of enabled apps') | |
for app in self.enabled_apps(): | |
self.croncheck_app(app) | |
def croncheck_app(self, app): | |
pid = self.get_app_pid(app) | |
if pid == 0 or not self.is_running(pid): | |
log.info('%s app is not running. Restarting..' % app['app']) | |
try: | |
self.start_app(app) | |
except Exception, exc: | |
log.debug("Error in start_app(%s) -> %s" % \ | |
(str(app), str(exc))) | |
def list(self): | |
for app in self.enabled_apps(): | |
self.list_app(app) | |
print >>sys.stderr | |
def list_app(self, app): | |
print >>sys.stderr, "{0:20} {1:20}".format('Key', 'Value') | |
print >>sys.stderr, "="*20, "=" * 20 | |
for k, v in sorted(app.items()): | |
print "{0:20} {1:20}".format(k, v) | |
def status(self): | |
print >>sys.stderr, "{0:20} {1:20}".format('Application', 'Status') | |
print >>sys.stderr, "="*20, "=" * 20 | |
for app in self.enabled_apps(): | |
self.status_app(app) | |
def status_app(self, app): | |
pid = self.get_app_pid(app) | |
print >>sys.stderr, "{0:20} {1:20}".format( | |
app['app'], | |
(pid != 0) and \ | |
'RUNNING (%d)' % pid or \ | |
'NOT RUNNING' | |
) | |
def get_app_pid(self, app): | |
try: | |
pidfile = open(os.path.join( | |
PIDFILE_NAME, | |
app.get('app') + '.pid', | |
), "r") | |
pid = pidfile.read().strip() | |
pidfile.close() | |
return int(pid) | |
except ValueError: | |
return 0 | |
except IOError: | |
return 0 | |
def stop(self): | |
for app in self.enabled_apps(): | |
try: | |
self.stop_app(app) | |
except Exception, exc: | |
log.debug("Error in stop_app(%s) -> %s" % \ | |
(str(app), str(exc))) | |
def is_running(self, pid): | |
execstr = '%s -0 %d' % (KILL_BIN, pid) | |
retcode = call(execstr, shell=True) | |
if retcode == 0: | |
return True | |
return False | |
def stop_app(self, app): | |
pid = self.get_app_pid(app) | |
if pid == 0: | |
log.warning('Cannot stop %s app. It is not running' % app['app']) | |
return False | |
if self.is_running(pid): | |
execstr = '%s -9 %d' % (KILL_BIN, pid) | |
retcode = call(execstr, shell=True) | |
if retcode == 0: | |
os.unlink(os.path.join( | |
PIDFILE_NAME, | |
app.get('app') + ".pid", | |
)) | |
return True | |
else: | |
log.error('Killing the %s app (PID %d): failed.' % \ | |
(app['app'], pid)) | |
return False | |
else: | |
log.warning('Cannot stop %s app (PID %d): ' \ | |
'Operation not permitted or ' \ | |
'application is not running' % \ | |
(app['app'], pid)) | |
return False | |
def start(self): | |
for app in self.enabled_apps(): | |
try: | |
self.start_app(app) | |
except Exception, exc: | |
log.debug("Error in start_app(%s) -> %s" % \ | |
(str(app), str(exc))) | |
def start_app(self, app): | |
log.info('Spawning %s' % app['app']) | |
proj_dir = os.path.join( | |
APPLICATIONS_PATH, | |
app.get('vhost'), | |
app.get('release', 'current'), | |
app.get('app'), | |
) | |
env = app.get('virtualenv', DEFAULT_ENV) | |
pybin = os.path.join(VIRTUALENV_PATH, env, 'bin', 'python') | |
socket = os.path.join(SOCKET_DIR, app['app'] + '.socket') | |
pidfile = os.path.join(PIDFILE_NAME, app['app'] + '.pid') | |
errlog = os.path.join(LOGGING_DIR, app['app'] + '-errors.log') | |
outlog = os.path.join(LOGGING_DIR, app['app'] + '-output.log') | |
#'-v 0 outlog=%(outlog)s errlog=%(errlog)s ' \ | |
execstr = '%(python)s manage.py runfcgi host=127.0.0.1 port=%(port)s ' \ | |
'method=%(method)s daemonize=true maxrequests=%(maxrequests)s ' \ | |
'minspare=%(minspare)s maxspare=%(maxspare)s ' \ | |
'protocol=scgi pidfile=%(pidfile)s' % ({ \ | |
'port': app['port'], | |
'python': pybin, | |
'socket': socket, | |
'outlog': outlog, | |
'errlog': errlog, | |
'method': app.get('method', 'prefork'), | |
'maxrequests': app.get('maxrequests', 20), | |
'minspare': app.get('maxspare', 2), | |
'maxspare': app.get('maxspare', 4), | |
'pidfile': pidfile}) | |
log.debug('Switching to "%s"' % proj_dir) | |
os.chdir(proj_dir) | |
log.debug('Executing "%s"' % execstr) | |
retcode = call(execstr, shell=True) | |
if retcode != 0: | |
log.error('Error while spawning %s (Return code: %d') % \ | |
(app.get('app'), retcode) | |
return False | |
return True | |
def restart(self): | |
self.stop() | |
self.start() | |
def restart_app(self, app): | |
log.info('Restarting %s app' % app['app']) | |
try: | |
self.stop_app(app) | |
except Exception, exc: | |
log.debug("Error in stop_app(%s) -> %s" % \ | |
(str(app), str(exc))) | |
try: | |
self.start_app(app) | |
except Exception, exc: | |
log.debug("Error in start_app(%s) -> %s" % \ | |
(str(app), str(exc))) | |
if len(sys.argv) < 2 or len(sys.argv) > 3: | |
print("Usage: %s <start>|<stop>|<restart>|<list>|<croncheck> [app]" % sys.argv[0]) | |
sys.exit(-1) | |
spawn = Spawner(apps) | |
if sys.argv[1] in ('start', 'stop', 'restart', 'status', 'croncheck', 'list'): | |
app = None | |
if len(sys.argv) == 3: | |
app = spawn.get_app(sys.argv[2]) | |
if app: | |
getattr(spawn, sys.argv[1] + '_app')(app) | |
else: | |
getattr(spawn, sys.argv[1])() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment