#!/usr/bin/env python
"""
Program to run a set of actions.
"""
__rcsid__ = "$Id$"
__author__ = "Dan Gunter"

import os
import sys
import time
#
from netlogger.nllog import OptionParser, get_logger
from netlogger import util
from netlogger.util import ConfigError, ScriptOption
from netlogger.actions import base

## Exceptions


## Global variables


## Functions

def safeAction(action, mod_name, state_file=None, timeout_sec=60):
    log = get_logger(__file__)
    pid = os.fork()
    if pid == 0:
        # child
        status = 0
        log.info("action.child.start", module=mod_name, timeout=timeout_sec)
        try:
            action.execute()
        except Exception, ex:
            log.exc("action.child.error", ex)
            status = -1
        action.finish(status) # cleanup procedures
        if state_file:
            log.debug("action.child.save_state.start", module=mod_name)
            state_file.save()
            log.debug("action.child.save_state.end",
                     module=mod_name, status=0)
        log.info("action.child.end", module=mod_name, status=status)
        sys.exit(0)
    else:
        # parent
        t = t0 = time.time()
        finished = False
        while t - t0 < timeout_sec:
            _, status = os.waitpid(pid, os.WNOHANG)
            if (_, status) != (0, 0):
                finished = True
                break
            time.sleep(1)
            t = time.time()
        if not finished:
            os.kill(pid, 9)
            raise RuntimeError("Timeout")
        elif status != 0:
            raise RuntimeError("Failed")

def run(cfg_path):
    """Run the actions as directed by the configuration
    at 'cfg_path'.

    Raises ConfigError if the configuration is invalid.
    Raises ImportError if there is a problem importing modules.
    Raises IOError if there is a problem opening the state file.
    """
    log = get_logger(__file__)
    try:
        log.info("config.start", file=cfg_path)
        cfg = base.Configuration(cfg_path)
        log.info("config.end", status=0)
    except ConfigError:
        log.error("config.end", status=-1)
        raise
    try:
        log.info("state.open.start", file=cfg.state_path)
        ssf = base.SavedStateFile(cfg.state_path)
        log.info("state.open.end", status=0)
    except IOError, ioe:
        log.exc("state.open.end", ioe)
        raise IOError("Opening state file: %s" % ioe)
    log.info("actions.iter.start", mod_path=cfg.module_path)
    act_fact = base.ActionFactory(cfg.db, ssf)
    acts = base.getModuleActions(mod_path=cfg.module_path,
                                 params=cfg.module_params,
                                 schedules=cfg.when,
                                 action_factory=act_fact)
    log.info("actions.run.start")
    try:
        num_failed, total = 0, 0
        for mod_name, act in acts:
            try:
                log.info("action.start", module=mod_name)
                safeAction(act, mod_name, state_file=ssf, timeout_sec=10)
                log.info("action.end", status=0)
            except Exception, ex:
                log.exc("action.end", ex)
                log.info("actions.iter.end", status=-1)
                num_failed += 1
            total += 1
    except ConfigError, cerr:
        log.info("actions.iter.end", status=-1)
        raise ConfigError("While running actions: %s" % cerr)
    except ImportError, ierr:
        log.exc("actions.run.end", ierr)
        log.info("actions.iter.end", status=-1)
        raise ImportError("While running actions: %s" % ierr)
    log.info("actions.iter.end", status=0)
    return num_failed, total

def main():
    """ Parse args and call run """
    # Parse args
    usage = "%prog [options]"
    desc = "Run pre-written 'actions', which can access the database."
    desc += " %s=%s" % (util.NL_HOME,
                           os.environ.get(util.NL_HOME, "{empty}"))
    if os.environ.has_key(util.NL_HOME):
        config_base = os.environ[util.NL_HOME]
    else:
        config_base = os.getcwd()
    config_path = os.path.join(config_base, "etc", "actions.conf")
    parser = OptionParser(usage=usage, description=desc,
                          option_class=ScriptOption)
    parser.add_option('-c', '--config', action='store', metavar='PATH',
                      default=config_path, dest="cfg_path",
                      help="read module configuration from PATH "
                      "(default=%default)")
    options, args = parser.parse_args()
    parser.check_required("-c")
    log = get_logger(__file__) # Should be first done, just after parsing args
    status = 0
    try:
        log.info("run.start", cfg=options.cfg_path)
        num_failed, total = run(options.cfg_path)
        log.info("run.end", status=0, num__run=total, num__failed=num_failed)
    except Exception, err:
        log.exc("run.end", err)
        status = -1
    return status

if __name__ == '__main__':
    sys.exit(main())
