#!/usr/bin/env python
# -*- coding: utf-8 -*-
# $Id: backendctl 1634 2013-04-12 15:36:36Z amelung $
#
# Copyright (c) 2007-2009 Otto-von-Guericke-Universität, Magdeburg
#
# This file is part of ECSpooler.
#
"""
This module contains functions to start/stop a backend or get status 
information from a backend. __main__ reads the command lines arguments and
processes them. A configuration file must be avaiable for each backend 
with entries about spooler server's host and port as well as base port 
for the backend. If a port is already in use, we will try another one.
"""

import os, sys, time, socket, xmlrpclib
import getopt
from getpass import getpass

# add parent directory to the system path
sys.path.append(os.path.join(os.path.abspath(os.path.dirname(__file__)), os.pardir))

# local imports
from lib.util import settings
from lib.util.servicecontrol import ServiceControl

from backends import *

# TODO: should be in config.py
MAX_TRIALS = 15

class BackendControl(ServiceControl):
    """
    """

    @staticmethod
    def usage():
        """
        """
        print """
Usage: backendctl [OPTION ...] BACKEND {start|stop|restart|status}

OPTIONs are:
  -P port         Port to bind/connect backend to, i.e. where to listen for 
                  new request.  Default: First free port in the range 
                  of 5060 .. 5074.
  -S host         Hostname or IP of the machine on which ECSpooler runs.  
                  Needed for registering a backend.  Default: localhost
  -B port         Port on which ECSpooler runs. Needed for registering a 
                  backend.  Default: 5050  
  -u user         Name of the user required for authentication when starting
                  and registering to ECSpooler
  -p password     Password of the user required for authentication when
                  starting and registering to ECSpooler
  BACKEND         Name of the backend to start|stop|restart|status.
"""

    def __init__(self, name, spooler_host, spooler_port, backend_port, username=None, password=None):
        """
        """
        ServiceControl.__init__(self, name, backend_port, username=username, password=password)
        
        self.spooler_host = spooler_host
        self.spooler_port = spooler_port


    def start(self):
        """
        @see: ServiceControl.start()
        """
        # Run backend as nobody
        try:
            os.setuid(settings.NOBODY_UID)
        except os.error, e:
            print >> sys.stderr, "Cannot change uid:", e

        try:
            exec 'from backends.%s import %s' % (self.name.lower(), self.name)
            exec 'id = %s.%s.id' % (self.name, self.name,)
        except NameError, ne:
            print >> sys.stderr, "No such backend: '%s'" % self.name
            raise ne
        except AttributeError, ae:
            print >> sys.stderr, "No such backend: '%s'.  Did you mean '%s'?" \
                  % (self.name, self.name.capitalize(),)
            raise ae
        
        exec 'version = %s.%s.version' % (self.name, self.name,)
    
        (backend, p)= self.__get_backend_instance()
    
        if backend:
            print >> sys.stdout, "Starting %s backend at %s" % (self.name, time.asctime())
            print >> sys.stdout, "    Hostname: %s" % self.host
            print >> sys.stdout, "    Port: %i" % p
            
            print >> sys.stdout, "Registering to ECSpooler at '%s:%s'..." % (self.spooler_host, self.spooler_port)
    
            try:
                if sys.platform in ['unixware7']:
                    cpid = os.fork1()
                else:
                    cpid = os.fork()
            except AttributeError:
                print >> sys.stdout, 'os.fork not defined - skipping.'
                cpid = 0
        
            if cpid == 0:
                # child process
                # blocks here 'til shutdown
                err_msg = backend.start()  
                if err_msg:
                    print >> sys.stderr, err_msg
        
            else:
                # parent process
                time.sleep(1)
                print >> sys.stdout, 'pid=%d' % cpid
    
        else:
            #print 'Backend start failed. See log for more information.'
            print >> sys.stderr, 'Backend start failed. See log for more information.'


    def __get_backend_instance(self):
        """
        Returns an instance of the backend class if one could be created.
        """
        # put together the backend module and class name using backend_id
        moduleName = '%s.%s' % (self.name, self.name,)
        
        for port in range(self.port, self.port + MAX_TRIALS, 1):
        
            # get a instance, e.g., Python-Backend
            # FIXME: use spooler instead of spooler
            instance_create_stmt = \
            "backend = %s({ \
                        'host': '%s', \
                        'port': %d, \
                        'spooler': 'http://%s:%d', \
                        'auth': %s}, %s.__file__)" \
                    % (moduleName, self.host, port, \
                       self.spooler_host, self.spooler_port, \
                       repr(self._get_auth()), self.name)
                    
            retval = self.__try_get_backend_instance(instance_create_stmt)
            
            if retval: 
                return (retval, port)
            else:
                time.sleep(0.1)
        
        return (None, 0)


    def __try_get_backend_instance(self, instance_create_stmt):
        """
        Executes the import statement for the backend class, creates an  
        instance and returns this instance.
        
        An exception is thrown if the port is already in use. In this case we
        will return None.
    
        @param: instance_create_stmt Statement which will be executed to create
                                     a new instance of the backend class.
        @return: The backend instance or None if the instance couldn't be created.
        """
        try:
            backend = None
            
            #log.debug("Trying to create instance of backend '%s'" % self.name)
            #log.debug(instance_create_stmt)
    
            exec 'from backends.%s import %s' % (self.name.lower(), self.name)
            exec(instance_create_stmt)
    
            return backend
    
        # FIXME: This doesn't work on Windows -> no socket.error is thrown if
        #        port is already in use!
        except socket.error:
            return None

    def _process_stop(self):
        """
        @see ServiceControl.stop
        @see ServiceControl._process_stop
        """
        backend = xmlrpclib.ServerProxy("http://%s:%d" % (self.host, self.port))
        result = backend.shutdown(self._get_auth(), self.name)
        print >> sys.stdout, result

    def _process_status(self):
        """
        @see ServiceControl.status
        @see ServiceControl._process_status
        """
        server = xmlrpclib.ServerProxy("http://%s:%d" % (self.host, self.port))
        return server.getStatus(self._get_auth(), self.name)


# -----------------------------------------------------------------------------
def main():
    """
    """

    try:
        opts, args = getopt.getopt(sys.argv[1:], "S:B:P:u:p:h", 
                                   ["spoolerhost=", "spoolerport=", 
                                    "backendport=", "user=", "password=", 
                                    "help"])
    except getopt.GetoptError:
        # print help information and exit:
        BackendControl.usage()
        sys.exit(2)

    if len(args) != 2:
        BackendControl.usage()
        sys.exit(2)
    else:
        spooler_host = 'localhost'
        spooler_port = 5050
        backend_port = 5060
        user = None
        password = None
        
        for o, a in opts:
            if o in ("-h", "--help"):
                BackendControl.usage()
                sys.exit()
    
            if o in ("-S", "--spoolerhost"):
                spooler_host = a
    
            if o in ("-B", "--spoolerport"):
                try:
                    spooler_port = int(a)
                except ValueError:
                    BackendControl.usage()
                    sys.exit(2)
                    
            if o in ("-u", "--user"):
                user = a
    
            if o in ("-p", "--password"):
                password = a
    
            if o in ("-P", "--backendport"):
                try:
                    backend_port = int(a)
                except ValueError:
                    BackendControl.usage()
                    sys.exit(2)

    
        backend_id, cmd = args

        settings.init_logging(backend_id.lower())

        if not user:
            user = raw_input("Username for ECSpooler: ")
        
        if not password:
            password = getpass("Password for ECSpooler: ")

        if not user or not password:
            print "%s requires username and password." % cmd
            BackendControl.usage()
            sys.exit(2)

        backendctl = BackendControl(backend_id, spooler_host, spooler_port, backend_port, user, password)
        backendctl.process(cmd)
        
                
# -- Main ----------------------------------------------------------------------
if __name__ == "__main__":
    main()
