#!/usr/bin/env python
#------------------------------------------------------------------------------
# Copyright 2008-2011 Istituto Nazionale di Fisica Nucleare (INFN)
#
# Licensed under the EUPL, Version 1.1 only (the "Licence").
# You may not use this work except in compliance with the Licence.
# You may obtain a copy of the Licence at:
#
# http://www.osor.eu/eupl/european-union-public-licence-eupl-v.1.1
#
# Unless required by applicable law or agreed to in
# writing, software distributed under the Licence is
# distributed on an "AS IS" basis,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
# either express or implied.
# See the Licence for the specific language governing
# permissions and limitations under the Licence.
#------------------------------------------------------------------------------

from ConfigParser import SafeConfigParser
import commands
import logging
import optparse
import os
import signal
import sys
import threading
import time
import traceback

from wnodes import cachemanager
__version__ = cachemanager.get_version()
__short_name__ = os.path.basename(os.path.splitext(sys.argv[0])[0])
__long_name__ = __short_name__ + "-" + ".".join(map(str, __version__))
__minimum_python__ = (2, 4, 0)

try:
    from wnodes.utils import utils
except ImportError:
    sys.exit("%s: python module 'wnodes.utils' not found."
        % __short_name__)
try:
    from wnodes.utils import wsocket
except ImportError:
    sys.exit("%s: python module 'wnodes.utils.' not found."
        % __short_name__)
try:
    from wnodes.cachemanager import cachemanager
except ImportError:
    sys.exit("%s: python module 'wnodes.cachemanager.' not found."
        % __short_name__)

def main():

    if sys.version_info < __minimum_python__:
        sys.exit("%s: Python >= %s required." % (__short_name__,
            '.'.join(map(str, __minimum_python__))))

    p = optparse.OptionParser(usage="%prog [options] host_list_file",
        version=__long_name__)

    p.add_option("-d", "--daemonize",
                 action="store_true",
                 dest="daemon",
                 help="daemonize process")

    p.add_option("-l", "--log_file",
                 dest="log_file",
                 help="set log file",
                 metavar="FILE")

    p.add_option("-m", "--max_log_size",
                 dest="max_log_size",
        help="set maximum log file size (bytes)")

    p.add_option("-p",
                 dest="cm_port",
                 help="set TCP server port")

    p.add_option("-k", "--cm_keypair",
                 dest="cm_keypair",
                 help="set cloud user's keypair for VM configuration " +
                 "<prv_path;pub_path>")

    p.add_option("-K", "--server_cert_key",
                 dest="server_cert_key",
                 help="set server's keypair for VM configuration " +
                 "<server_cert;server_key>")

    p.add_option("-b", "--database",
                 dest="database",
                 help="set database to use")
    #NB: sqlite:/// is a RELATIVE path, sqlite://// -> ABSOLUTE
#    p.add_option("-b",
#                 "--database",
#                 dest="database",
#                 default=("mysql://root:WnodesCloudManager@" +
#                 "localhost" +  # changed from 131.154.193.140
#                 "/cloudmysql"),
#                 help="set database to use")

    p.add_option("-C", "--configfile",
                 dest="configfile",
                 default='/etc/wnodes/cachemanager/wnodes_cm_config.ini',
                 help="set configfile to use [default: %default]")

    (options, args) = p.parse_args()  # @UnusedVariable

    if not options.configfile:
        p.error('Configuration File Required')
    config_parser = SafeConfigParser()
    if not config_parser.read(options.configfile):
        p.error('Could not parse configuration')

    # set a rule for the configuration checks and defaults
    # define as many dictionary as the number of the section
    # for each option define a nested option with the required name
    # set a value for each options, with this rule:
    # '' : void, in the value is optional
    # 'my_default_value' : a default value, if any
    # 'mandatory' : if the value has to be specify from user
    rule = {'cm_conf':
                {'cache_size': 3,
                 'cache_refresh_interval': 60.0,
                 'database': 'mandatory',
                 'log_file': '/var/log/wnodes/cachemanager/wnodes_cm.log',
                 'max_log_size': 30000000,
                 'port': 54321,
                 'timeout': 30.0},
            'batch_sys':
                {'queue': 'mandatory',
                 'type': 'mandatory',
                 'user': 'mandatory'},
            'bait_conf':
                {'port': 8111},
            'ns_conf':
                {'host': 'mandatory',
                 'port': 8219},
            'webapp_conf':
                {'host': 'localhost'},
            'default_vm_option':
                {'bandwidth': 10,
                 'cpu': 1,
                 'img': 'mandatory',
                 'mem': 2048,
                 'mount': '',
                 'storage:': 1000},
            'key_cert':
                {'ca_cert': '/etc/grid-security/INFNCA.pem',
                 'cm_private_key': '~/.ssh/id_rsa_cm',
                 'cm_public_key': '~/.ssh/id_rsa_cm.pub',
                 'server_cert': '/etc/grid-security/server_cert.pem',
                 'server_key': '/etc/grid-security/server_key.pem'},
            }

    from wnodes.cachemanager.conf.parser_option import WNoDeSParserOption
    cm_opt = WNoDeSParserOption(rule)

    cm_opt.use_config_file(options.configfile)

    # define nodename of the host
    cm_opt._cm_conf__host = os.uname()[1]

    # override command line option (if any)
    # note: DO NO use default values in CLI options
    # otherwise they will priority over the options
    # specified in the configuration file
    if hasattr(p, 'cm_port'):
        cm_opt._cm_conf__port = p.cm_port
    if hasattr(p, 'database'):
        cm_opt._cm_conf__database = p.database
    if hasattr(p, 'log_file'):
        cm_opt._cm_conf__log_file = p.log_file
    if hasattr(p, 'max_log_size'):
        cm_opt._cm_conf__max_log_size = p.max_log_size
    if hasattr(p, 'cm_keypair'):
        cm_opt._key_cert__cloud_cert = p.cloud_keypair.split(';')[0]
        cm_opt._key_cert__cloud_key = p.cloud_keypair.split(';')[1]
    if hasattr(p, 'server_cert_key'):
        cm_opt._key_cert__server_cert = p.server_cert_key.split(';')[0]
        cm_opt._key_cert__server_key = p.server_cert_key.split(';')[1]

    # check all mandatory parameter has been defined
    for attr in dir(cm_opt):
        if getattr(cm_opt, attr) == 'mandatory':
            print "Error: %s not specified" % attr

    # Check if process is already running
    pid = "/var/run/%s.pid" % __short_name__
    if os.path.isfile(pid):
        try:
            stale_pid = int(open(pid).readline().strip("\n"))
        except ValueError:
            sys.exit("%s: cannot read pid file." % __short_name__)
        try:
            os.kill(stale_pid, 0)
            sys.exit("%s: already running." % __short_name__)
        except OSError:
            sys.stderr.write("%s: stale pid found.\n" % __short_name__)

    # Initialize database
    sys.modules['__main__'].database = cm_opt._cm_conf__database

    from wnodes.cachemanager.cachemanager import CacheManager
    cm = CacheManager(cm_opt)

    from wnodes.cachemanager.database.db_interface \
        import Compute, sess, get_next_id  # @UnresolvedImport

    cm.init_database(sess, Compute, get_next_id)

    if options.daemon is True:
        utils.daemonize(stdout="/tmp/%s.stdout" % __short_name__,
            stderr="/tmp/%s.stderr" % __short_name__)
        try:
            of = open(pid, "w")
            of.write("%i\n" % os.getpid())
            of.close()
        except IOError:
            error_msg = ("%s: cannot write pid file %s, exiting."
                         % (__short_name__, pid))
            cm.updateLog(error_msg, "error")
            sys.exit(error_msg)

    serviceDispatchTable = {
        'get_first_requested_host': cm.get_first_requested_host,
        'request_vm_destroy': cm.request_vm_destroy
        }

    wsocket.initServerRequestHandler(cm, serviceDispatchTable, encrypt=True,
        keyfile=cm_opt._key_cert__server_key,
        certfile=cm_opt._key_cert__server_cert,
        cafile=cm_opt._key_cert__ca_cert)

    port = int(cm_opt._cm_conf__port)
    srv = wsocket.WnodesThreadingTCPServer(('', port), \
        wsocket.ServerRequestHandler)

    cm.sync_cache()
    refresh_cache_thread = threading.Thread(target=cm.refresh_cache)
    refresh_cache_thread.start()

    signal.signal(signal.SIGTERM, cm.exit_function)

    try:
        srv.serve_forever()
    except KeyboardInterrupt:
        # Exit on ctrl-c
        cm.exit_function()

if __name__ == "__main__":
    main()
