#!/www/python/bin/python

"""$URL: svn+ssh://svn.mems-exchange.org/repos/trunk/dulcinea/bin/site $
$Id: site 25444 2004-10-26 14:55:47Z dbinger $

The site command script.
This controls the starting and stopping of durus, scgi, and apache servers.
"""
import os
import os.path
import sys
import signal
import errno
import time
from dulcinea.site_util import get_config_value, is_local, any_live_sites, \
     any_staging_sites, list_sites, get_pid_file_name, is_live, is_staging

valid_actions = ['start', 'stop', 'restart', 'status', 'help']
valid_sites = list_sites()
assert valid_sites, "You must have at least one site defined"
valid_daemons = ['durus', 'apache', 'scgi']

def get_start_script_name(site, daemon):
    start_script_dir = get_config_value('start_script_directory',
                                        site=site or valid_sites[0])
    return os.path.join(start_script_dir, 'start-%s.py' % daemon)

def usage(msg=None):
    def alts(lst):
        return ' | '.join(lst)
    if msg:
        sys.stderr.write('error: %s\n' % msg)
    sys.stderr.write('Usage: %s [ <action> | <site> | <daemon ]*\n' %
                     sys.argv[0])
    sys.stderr.write('where:\n')
    sys.stderr.write('       <action> = %s\n' % alts(valid_actions))
    sys.stderr.write('           default is status\n')
    sys.stderr.write('       <site> = %s\n' % alts(valid_sites))
    sys.stderr.write('           to specify some subset of all sites\n')
    sys.stderr.write('           default is all sites\n')
    sys.stderr.write('       <daemon> = %s\n' % alts(valid_daemons))
    sys.stderr.write('           to specify some subset of all daemons\n')
    sys.stderr.write('           default is all daemons\n')
    sys.exit(1)

def is_running(pid):
    """Return true if program with 'pid' exists."""
    # Currently Linux specific.
    return os.path.exists('/proc/%s' % pid)

def write(*args):
    sys.stdout.write(' '.join(args))
    sys.stdout.flush()

def sanitize_environment(env):
    if env is None:
        env = {}
    for name in ['PATH', 'LOGNAME']:
        if name not in env and name in os.environ:
            env[name] = os.environ[name]
    site_conf = os.environ.get('SITE_CONF')
    if site_conf:
        env['SITE_CONF'] = site_conf
    return env

def fork_child(exe, args=(), env=None, wd=None):
    pid = os.fork()
    if pid == 0:
        for i in range(4, 256):
            try:
                os.close(i)
            except:
                pass
        if wd:
            os.chdir(wd)
        env = sanitize_environment(env)
        try:
            os.execve(exe, [exe] + map(str, args), env)
        except OSError, err:
            sys.stderr.write("couldn't exec %s: %s\n" % (exe, err.strerror))
        except:
            sys.stderr.write("couldn't exec %s\n" % exe)
        os._exit(2)
    (pid, status) = os.wait()
    if status:
        print >>sys.stderr, "%s returned error status %s" % (exe, status)


def start_daemon(daemon, site='', exe=None, args=None, env=None):
    pid_file = get_pid_file_name(daemon, site)
    if os.path.isfile(pid_file):
        pid = open(pid_file).read().strip()
        if is_running(pid):
            write('\n%s %s appears to be running, not starting.\n' % (
                site, daemon))
            return
        else:
            write('\nPID file for %s %s appears obsolete, removing it.\n' % (
                site, daemon))
            os.unlink(pid_file)
    fork_child(exe, args, env, os.path.dirname(pid_file))

def kill_daemon(daemon, site):
    pid_file = get_pid_file_name(daemon, site)
    pid = open(pid_file).read().strip()
    try:
        os.kill(int(pid), signal.SIGTERM)
    except OSError, e:
        if errno.ESRCH == e.errno:
            write('%s %s (%s) process not found.' % (daemon, site, pid))
        else:
            write('\nkill failed: %s %s (%s)\n' % (daemon, site, pid))
            write('%s\n' % os.strerror(e.errno))
    else:
        for i in range(10):
            if not is_running(pid):
                break
            time.sleep(1)
        else:
            write('\nkill failed: %s %s (%s)\n' % (daemon, site, pid))
    if os.path.exists(pid_file):
        os.unlink(pid_file)

def stop_daemon(daemon, site='', script='', args=(), env=None):
    pid_file = get_pid_file_name(daemon, site or valid_sites[0])
    if os.path.isfile(pid_file):
        if script:
            fork_child(script, args, env)
            os.unlink(pid_file)
        else:
            kill_daemon(daemon, site)
    else:
        write('\n%s %s does not appear to be running.\n' % (site, daemon))

def start(daemon, site=None):
    start_daemon(daemon, site,
                 get_start_script_name(site, daemon),
                 (site,))

def start_durus(sites):
    for site in sites:
        durus_address = get_config_value('durus_address', site=site)
        if not durus_address:
            continue
        if is_local(durus_address):
            write(' ' + site)
            start_daemon('durus', site,
                         get_start_script_name(site, 'durus'),
                         (site, 'start'))

def stop_durus(sites):
    for site in sites:
        durus_address = get_config_value('durus_address', site=site)
        if not durus_address:
            continue
        if is_local(durus_address):
            write(' ' + site)
            stop_daemon('durus', site,
                         get_start_script_name(site, 'durus'),
                        (site, 'stop'))

def start_scgi(sites):
    for site in sites:
        scgi_address = get_config_value('scgi_address', site=site)
        if not scgi_address:
            continue
        if is_local(scgi_address):
            write(' ' + site)
            start('scgi', site)

def stop_scgi(sites):
    for site in sites:
        scgi_address = get_config_value('scgi_address', site=site)
        if not scgi_address:
            continue
        if is_local(scgi_address):
            write(' ' + site)
            stop_daemon('scgi', site)

def _apache_required(sites):
    for site in sites:
        if get_config_value('httpd', site=site):
            return True
    return False

def start_apache(sites):
    start('apache', sites[0])

def stop_apache(sites):
    stop_daemon('apache', sites[0])

def show_status():
    cmd = 'ps xo pid,command | grep "\\(start-\||scgi\\|durus\\|apache\\|httpd\\)" | grep -v grep'
    os.system(cmd)

def main():
    action = 'status'
    for act in valid_actions:
        if act in sys.argv:
            action = act
            break

    sites = ([ site for site in valid_sites if site in sys.argv ] or
             valid_sites)
    if any_live_sites():
        # Exclude all sites that aren't live
        sites = [site for site in sites if is_live(site)]
    elif any_staging_sites():
        # Exclude all sites that aren't staging
        sites = [site for site in sites if is_staging(site)]
    if not _apache_required(sites):
        valid_daemons.remove('apache')
    daemons = ([ daemon for daemon in valid_daemons if daemon in sys.argv ] or
               valid_daemons)


    def start_daemons():
        if 'durus' in daemons:
            write('Starting Durus servers:')
            start_durus(sites)
            write('\n')
        if 'scgi' in daemons:
            write('Starting SCGI servers:')
            start_scgi(sites)
            write('\n')
        if 'apache' in daemons:
            write('Starting Apache:')
            start_apache(sites)
            write('done\n')

    def stop_daemons():
        if 'apache' in daemons:
            write('Stopping Apache:')
            stop_apache(sites)
            write('done\n')
        if 'scgi' in daemons:
            write('Stopping SCGI servers:')
            stop_scgi(sites)
            write('\n')
        if 'durus' in daemons:
            write('Stopping Durus servers:')
            stop_durus(sites)
            write('\n')

    if action == 'start':
        start_daemons()

    elif action == 'stop':
        stop_daemons()

    elif action == 'restart':
        stop_daemons()
        start_daemons()

    elif action == 'status':
        show_status()

    elif action == 'help':
        usage()

main()
