#!/usr/bin/python

import os, sys, socket, optparse
import snake.treedb, snake.ksdb, snake.machinedb, snake.anamon
import snake.log, snake.config
from DocXMLRPCServer import DocXMLRPCServer,DocXMLRPCRequestHandler

api_version = 1

def version():
    '''Return the API version that this server supports.'''
    return api_version

def kernel_args_list(argdata):
    '''Takes a dictionary of data, returns a string of suggested kernel arguments'''

    # FIXME - this default could probably be over-written using a configuration option
    cmdline_args = []

    # Tree specific kernel_args?
    if argdata.has_key("tree") and argdata["tree"]:
        '''search for any tree specific cmdline needs'''
        t = snake.treedb.id_to_tree(argdata["tree"])
        if t and hasattr(t,'kernel_args') and t.kernel_args:
            cmdline_args.append(t.kernel_args)

    # URI specific kernel_args?
    if argdata.has_key("uri"):
        '''search for any uri specific cmdline needs'''

    # Kickstart specific kernel_args?
    if argdata.has_key("ks") and argdata["ks"]:
        '''search for any uri specific cmdline needs'''
        ks = snake.ksdb.name_to_kickstart(argdata["ks"])
        try:
            ks.parse()
        except:
            pass
            log.warn("kernel_arg_list failed to parse '%s' to obtain kernel_args" % argdata["ks"])
        if ks and hasattr(ks, 'kernel_args') and ks.kernel_args:
            cmdline_args.append(ks.kernel_args)

    # TODO : machine specific kernel_args?
    if argdata.has_key("fingerprints") and argdata["fingerprints"]:
        '''attempt to identify the client system based on fingerprints'''
        machines = snake.machinedb.listmachines(fingerprint=argdata["fingerprints"])
        if len(machines) == 1:
            '''1 system found'''
            cmdline_args.append(machines[0].bootargs)
        #elif len(machines) > 1:
        else:
            log.warn("kernel_args_list found %s machines with matching fingerprints: %s" % (len(machines), argdata["fingerprints"]))

    # client-supplied specific kernel_args?
    if argdata.has_key("cmdline") and argdata["cmdline"]:
        cmdline_args.append(argdata["cmdline"])

    # if no networking provided, provide suitable defaults
    ksdevice_found = False
    ip_found = False
    for args in cmdline_args:
        if not ksdevice_found:
            ksdevice_found = args.count("ksdevice=") > 0
        if not ip_found:
            ip_found = args.count("ip=") > 0

    if not ksdevice_found:
        cmdline_args.append("ksdevice=link")
    if not ip_found:
        cmdline_args.append("ip=dhcp")

    return " ".join(cmdline_args)

def setup_option_parser():
    parser=optparse.OptionParser()
    parser.add_option("-d", "--debug",
                action="store_true", dest="debug", default=False,
                help="Output noisy debugging info")
    parser.add_option("-q", "--quiet",action="store_true",dest="quiet",
                default=False,
                help="Surpress output")
    parser.add_option("-v", "--verbose",
                action="count", dest="verbosity", default=0,
                help="Output extra information")
    parser.add_option("-c", "--config",
                action="store", dest="conffile", default=snake.config.DEFAULT_CONFIGFILE,
                help="Override default configuration file")
    parser.add_option("-n", "--hostname",
                action="store", dest="hostname", default=None,
                help="Set hostname to be used for URL construction")
    parser.add_option("-l", "--listen",
                action="store", dest="listen", default=None,
                help="Set IP address/hostname to be bound")
    parser.add_option("-p", "--port",
                action="store", dest="port", default=None,
                help="Bind service to specified port")
    parser.add_option("-f", "--foreground",
                action="store_true", dest="fg", default=False,
                help="Run in the foreground")
    parser.add_option("-s", "--sname",
                action="store", dest="sname", default=None,
                help="Specify a zeroconf service name (optional)")
    parser.add_option("-z", "--zeroconf-off",
                action="store_const", dest="zeroconf", default=None, const="no",
                help="Disable zeroconf service advertisment")
    return parser

# FIXME - it would be nice to capture exceptions and pass them back to the
# client like kojihub does.  However, SimpleXMLRPCServer intercepts exceptions
# in do_POST and sends back a generic error code - 500: ('Internal Server
# Error', 'Server got itself in trouble').  For suggestions/tips on ways around
# this, see kojixmlrpc.py
class SnakeXMLRPCServer(DocXMLRPCServer):
    def _dispatch(self, method, params):
        '''overload built-in method to catch and log exceptions to the snake.log'''
        try:
            return DocXMLRPCServer._dispatch(self, method, params)
        except:
            log.exception("Exception occured while processing XMLRPC request %s" % method)

class SnakeRequestHandler(DocXMLRPCRequestHandler):
    '''sub-class to extend logging methods'''
    def log_error(self, *args):
        log.error(*args)

    def log_message(self, format, *args):
        """Overload built-in log_message to use snake logging facility
        """
        log.debug("SnakeHandler %s - %s" % (self.address_string(), format%args))

if __name__ == "__main__":

    # Parse commandline options
    parser = setup_option_parser()
    (opt,args) = parser.parse_args()

    # Set up logging
    if opt.debug:
        opt.verbosity = snake.log.DEBUG
    if opt.quiet:
        opt.verbosity = snake.log.QUIET

    log = snake.log.setup_logger(opt.verbosity)

    # Load 'server' configuration from snake.conf
    if os.access(opt.conffile, os.R_OK):
        # ensure opt.conffile is an absolute path
        if opt.conffile[0] != "/":
            opt.conffile = os.path.join(os.environ["PWD"], opt.conffile)
        log.debug("Loading configuration from %s" % opt.conffile)
        config = snake.config.getconfig("server", opt.conffile)
    else:
        print >> sys.stderr, "Error reading config file: %s" % opt.conffile
        sys.exit(1)

    # With config file loaded and logger setup, add a file logger
    snake.log.add_file_log(config["logfile"])

    # Override configfile options from commandline options
    for i in ('port','hostname','listen','zeroconf', 'sname'):
        a = getattr(opt,i,None)
        if a:
            log.debug("Overriding config['%s']: %s->%s" % (i,config[i],a))
            config[i] = a

    # Are we backgrounding?
    if not opt.fg:
        if os.fork():
            '''parent'''
            sys.exit()
        else:
            '''child'''
            log.debug("Backgrounding snake-server")
            snake.log.detach_console()

    # Setup xmlrpc server
    try:
        server = SnakeXMLRPCServer((config["listen"], int(config["port"])), requestHandler=SnakeRequestHandler)
    except socket.error, (errno, msg):
        log.error("Socket error: %s" % msg)
        sys.exit(1)

    # zeroconf advertisement
    if config['zeroconf'] == 'yes':
        try:
            import snake.zeroconf
        except ImportError:
            log.error("Avahi/DBus python bindings not available")

        try:
            log.info("Enabling Avahi service advertisment")
            avahi_server = snake.zeroconf.AvahiServer()
            log.debug("Advertise on host:%s, port:%s" % (config['hostname'], config['port']))
            avahi_server.advertise(config['hostname'], config['port'], sname=config['sname'])
        except Exception, e:
            log.error("Failed to enable Avahi/DBus service discovery")
            log.debug(e)
    else:
        log.info("Avahi service advertisment disabled")

    # register functions
    server.register_function(snake.treedb.dicttrees,'tree.list')
    server.register_function(snake.treedb.removetree,'tree.remove')
    server.register_function(snake.treedb.add,'uri.add')
    server.register_function(snake.treedb.removeuri,'uri.remove')
    server.register_function(snake.machinedb.dictmachines,'machine.list')
    server.register_function(snake.machinedb.remove,'machine.remove')
    server.register_function(snake.machinedb.add,'machine.add')
    server.register_function(snake.machinedb.update,'machine.update')
    server.register_function(snake.machinedb.getksurl,'machine.getksurl')
    # FIXME: add check function (like snake-tree)
    server.register_function(snake.ksdb.listkickstarts,'kickstart.list')
    server.register_function(snake.ksdb.write_data,'kickstart.add')
    server.register_function(snake.ksdb.remove,'kickstart.remove')
    server.register_function(snake.ksdb.generate,'kickstart.generate')
    server.register_function(snake.ksdb.rename,'kickstart.rename')
    server.register_function(snake.ksdb.describe,'kickstart.describe')
    server.register_function(kernel_args_list,'kernel_args.generate')
    # anamon functions
    server.register_function(snake.anamon.start_task,'anamon.start')
    server.register_function(snake.anamon.update_file,'anamon.update_file')
    # Let us introspect, friend.
    server.register_function(version,'snake.version')
    server.register_function(snake.config.getoption,'snake.getoption')
    server.register_introspection_functions()

    log.info("Listening for XMLRPC requests on http://%s:%s/" % (config["listen"],config["port"]))
    try:
        server.serve_forever()
    except KeyboardInterrupt:
        print "Exiting on keyboard interrupt."
