#!/usr/libexec/isilon/isi_rpc_d
#
# Copyright C 2002-2008 by Isilon Systems, Inc. All rights not expressly
# granted herein are reserved.
# Created in the United States of America.
# 
# This digital media is protected by U.S. and international copyright and
# intellectual property laws. Unless otherwise specified, all information
# and screens appearing as part of this digital medium, including
# software, services, documents, text, images, icons, and logos design;
# the selection, assembly, arrangement, and design thereof; and the code
# that enables its presentation, are the sole property of Isilon Systems,
# Inc.
# 
# Except as otherwise required by applicable law, no part of any
# copyrighted material may be reproduced, modified, translated,
# distributed, published, or transmitted by any means, electronic or
# mechanical, for any purpose, without the prior written consent of Isilon
# Systems. 
#  
# Isilon and Isilon Systems are registered trademarks of Isilon Systems,
# Inc.  Legion is a trademark of Isilon Systems, Inc.  All other
# trademarks and registered trademarks are the property of their
# respective holders.

import socket
import sys
import logging
import logging.handlers as handlers
import isi.app.daemon as daemon
import isi.app.lib.objs as objs
import isi.config.array as array
import isi.hw.check.lib.args as argslib
import isi.misc as misc
import isi.net.rpc as rpc
import isi.net.transport as transport

log = logging.getLogger('isi_rpc_d')

def initLogging():
    """Initializes logging systems with desired handler,
       formatter, and level. 
       """
    logs = {
        'app.daemon'        : logging.INFO,
        'app.lib.procs'     : logging.INFO,
        'app.lib.objs'      : logging.INFO,
        'hw.check.lib.misc' : logging.INFO,
        'isi_rpc_d'         : logging.INFO,
        'net.rpc'           : logging.INFO,
        'net.session'       : logging.INFO,
        'net.transport'     : logging.INFO,
        }

    # SysLogHandler with newsyslog tool will handle log rotation    
    handler = handlers.SysLogHandler(address='/var/run/log', facility='daemon' )
    # syslogd depends on a certain format - %(name)s has to be the first one 
    formatter = logging.Formatter('%(name)s[%(process)d]'': ' 
        '%(levelname)-8s %(message)s')    
    handler.setFormatter(formatter)
      
    for (name, level) in logs.items():
        logger = logging.getLogger(name)
        logger.setLevel(level)
        logger.addHandler(handler)

class RPCFactory:
    def __init__(self):
        self.log = logging.getLogger('isi_rpc_d')

    def createAndSwitch(self, spec, *args, **kwargs):        
        log.info('New daemon providing: %s' % spec)
        misc.setproctitle('isi_rpc_d: %s' % spec)

        cls = objs.findAttr(spec)
        if cls:
            obj = cls(*args, **kwargs)
        else:
            raise ImportError('Unable to find class: %s' % spec)

        raise rpc.RPCObjectSwitcher(obj)

def bestInternalIP(args):
    """Returns the best internal IP for the local node, or ''
       if no internal IPs are available.

       If promiscuous mode has been set, always returns ''.
       """
    if args['promiscuous']:
        return ''

    # Not promiscuous, so look up the best internal
    # ip address for this node
    nodeOps = array.NodeOps()
    nodeOps.defaultToLocal()
    try:
        ip = array.getNodes(nodeOps).pop().bestInternalIP()
        if not ip:
            ip = ''
    except KeyError:
        ip = ''
        
    return ip
    

def main(args):
    try:
        misc.setproctitle('isi_rpc_d')
        try:
            # Look up the best internal IP for this node from array.xml
            ip = bestInternalIP(args)
            log.info('Starting RPC daemon on %s port %d' %
                     (ip or '<promiscuous>', args['port']))
            
            tport = transport.TCPTransport(ip, args['port'])
            server = rpc.RPCForkingServer(tport, RPCFactory())
        except socket.error, e:
            log.error('Failed to initialize server: %s' % e)
        else:
            server.serve_forever()
    except SystemExit:
        raise
    except Exception, e:
        log.exception(e)

# Usage text
usage_text = """Usage: %%<app> [OPTIONS] [COMMAND]

OPTIONS:
  -p <port>
  --port <port>          Listen on <port> for incoming connections. Default
                         port is %d.
  
  -h, --help             Show this text

COMMANDS:
  start                  Start the RPC daemon
  restart                Restart the RPC daemon
  stop                   Stop the RPC daemon

If no command is specified, the daemon will run in debug mode.
""" % rpc.DEFAULT_RPC_DAEMON_PORT

# Command-line options
#
# NOTE: If you add an option here, be sure to add a reasonable
# default for it to the dictionary passed in to main() in the
# daemon case below. I really should consolidate the daemon
# case and the non-daemon case to avoid the issue, but I
# don't have time to deal with it right now.
options = {
    'port':
    {'opts'       : ['p:', 'port='],
     'default'    : rpc.DEFAULT_RPC_DAEMON_PORT,
     'func'       : int,
     },

    'promiscuous':
    {'opts'       : ['x', 'promiscuous'],
     'default'    : False,
     'func'       : argslib.true_func(),
     },
    
    'help':
    {'opts'       : ['h', 'help'],
     'func'       : argslib.help_func(usage_text),
     },
	 }

def initSignals():
    import signal
    def signalHandler(signum, frame):
        log.info('Exiting due to signal %d in %s()' % 
        	(signum, frame.f_code.co_name))
        raise SystemExit

    signal.signal(signal.SIGTERM, signalHandler)
    signal.signal(signal.SIGINT, signalHandler)
    signal.signal(signal.SIGHUP, signalHandler)

if __name__ == '__main__':
    # Let the user know that we really are logging info somewhere
    print 'Logging to /var/log/isi_rpc_d'
    
    # Set up the logging systems
    initLogging()

    # For posterity, log any signals which will cause us to exit.
    initSignals()
    
    log.info(' '.join(sys.argv))

    if len(sys.argv) == 2 and sys.argv[1] in ['start', 'restart', 'stop']:
        daemon.startstop()
        main({
            'port'        : rpc.DEFAULT_RPC_DAEMON_PORT,
            'promiscuous' : False,
            })
    else:
        argslib.script_main(main, usage_text, options)

