#!/usr/bin/python

#############################################################################
#
# NAME:        mrs-bootstrapper
#
# FACILITY:    SAM (Service Availability Monitoring)
#
# COPYRIGHT:
#         Copyright (c) 2009, Members of the EGEE Collaboration.
#         http://www.eu-egee.org/partners/
#         Licensed under the Apache License, Version 2.0.
#         http://www.apache.org/licenses/LICENSE-2.0
#         This software is provided "as is", without warranties
#         or conditions of any kind, either express or implied.
#
# DESCRIPTION: 
#         This script provides the MRS bootstrapping mechanism
#         https://tomtools.cern.ch/confluence/display/SAM/MRS
#
# AUTHORS:     Paloma Fuente, CERN
#
# CREATED:     Feb-2012
#
# MODIFIED:    July-2012
#
##############################################################################


########################
#
# Imports
#
########################

import sys
import getopt
import simplejson
import urllib2
import time
import os
import fcntl
import signal


########################
#
# class DB
#
########################

class DB:

    def __init__(self, params):
        self.node = params.node
        if self.node == 'sam-gridmon':
            import cx_Oracle
            self.dbInfo = params.dbUser + "/" + params.dbPasswd + '@' + params.dbName
            try:
                self.dbConn = cx_Oracle.connect(self.dbInfo)
            except cx_Oracle.DatabaseError, exc:
                error, = exc.args
                log.error(error.message)
        elif self.node == 'sam-nagios':
            import MySQLdb  
            import warnings
            warnings.filterwarnings('error', category=MySQLdb.Warning)
            self.dbHost = params.dbHost
            self.dbUser = params.dbUser
            self.dbPasswd = params.dbPasswd
            self.dbName = params.dbName
            try:
                self.dbConn = MySQLdb.connect(self.dbHost, self.dbUser, self.dbPasswd, self.dbName)
            except MySQLdb.Error, e:
                log.error("%d - %s" %(e.args[0], e.args[1]))
        self.dbCursor = self.dbConn.cursor()
        log.logger("Creating DB connection")

    def execute(self, query):
        if self.node == 'sam-gridmon':
            import cx_Oracle
            try:
                self.dbCursor.execute(query)
            except cx_Oracle.DatabaseError, exc:
                error, = exc.args
                self.close()
                log.error(error.message)
        elif self.node == 'sam-nagios':
            import MySQLdb
            try:
                self.dbCursor.execute(query)
            except MySQLdb.Error, e:
                self.close()
                log.error("%d - %s" %(e.args[0], e.args[1]))
            except MySQLdb.Warning, w:
                pass

    def commit(self):
        if self.node == 'sam-gridmon':
            import cx_Oracle
            try:
                self.dbConn.commit()
            except cx_Oracle.DatabaseError, exc:
                error, = exc.args
                self.close()
                log.error(error.message)
        elif self.node == 'sam-nagios':
            import MySQLdb
            try:
                self.dbConn.commit()
            except MySQLdb.Error, e:
                self.close()
                log.error("%d - %s" %(e.args[0], e.args[1]))

    def close(self):
        if self.node == 'sam-nagios':
            self.dbCursor.close()
        self.dbConn.close()
        log.logger("Closing DB connection")

########################
#
# Logger 
#
########################

class Logger:

    def logger(self, log):
        print time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()), " -- MRS bootstrapper -- ", log

    def error(self, error):
        print time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()), " -- MRS bootstrapper -- ERROR: ", error
        print time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()), " -- MRS bootstrapper -- End\n\n"
        sys.exit(1)

########################
#
# Params
#
########################

class Params:

    def lockFile(self, lockfile):
        fd = os.open(lockfile, os.O_CREAT | os.O_TRUNC | os.O_WRONLY)
        try:
            # Request exclusive (EX) non-blocking (NB) advisory lock.
            fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
        except IOError:
            return False
        return True

    def __init__(self):
        self.__getParameters()
        self.__processParameters()

    def __getParameters(self):
        if not self.lockFile('/tmp/mrs_bootstrapper_flock'):
            log.error("Another MRS bootstrapper instance might be running")
        self.opts, extraparams = getopt.getopt(sys.argv[1:], '', ['help', 'file=', 'node=', 'timeout='])
        self.file = None
        self.node = None
        self.timeout = None
        for opt, arg in self.opts:
            if opt == "--help":
                help = "\npython mrs-bootstrapper --node=<node type> --timeout=<timeout>\n<node type> accepted values are sam-nagios and sam-gridmon\n"
                help += "<timeout> timeout to get poem-sync information (in seconds)\n(following parameter is optional)\n--file=jsonFile\n\n"
                sys.exit(help)
            elif opt == '--file':
                self.file = arg
            elif opt == '--node':
                self.node = arg
            elif opt == '--timeout':
                self.timeout = arg

    def __processParameters(self):
        log.logger("Begin")
        if self.node == None:
            log.error("Missing node parameter")
        elif self.node != 'sam-nagios' and self.node != 'sam-gridmon':
            log.error("Wrong node parameter, should be sam-nagios or sam-gridmon")
        if self.timeout == None:
            log.error("Missing timeout parameter")
        if self.node == 'sam-gridmon':
            file = '/etc/mrs.d/oracle-mrs.conf'
        elif self.node == 'sam-nagios':
            file = '/etc/mrs.d/mysql-mrs.conf'
        for line in open(file):
            if "db-pwd=" in line:
                self.dbPasswd = line[:-1].split('=')[-1]
            elif "db-user=" in line:
                self.dbUser = line[:-1].split('=')[-1]
            elif "db-uri=" in line:
                if self.node == 'sam-gridmon':
                    self.dbName = line[:-1].split('=')[-1]
                elif self.node == 'sam-nagios':
                    self.dbName = line[:-1].split(';')[0].split('=')[-1]
                    self.dbHost = line[:-1].split(';')[1].split('=')[-1]
        log.logger("Getting DB info")

########################
#
# Functions
#
########################

def handler(signum, frame):
    log.error("Cannot get poem_sync_url (Signal handler called with signal %d)" %(signum))

def getOrAddFqan(db, in_fqan_name, node):
    dbQuery = "select count(*) from fqan where lower(name) = lower('%s')" %in_fqan_name
    db.execute(dbQuery)
    counter = 0
    fqan_id = 0
    for row in db.dbCursor:
        counter = row[0]
    if counter == 0:
        if node == 'sam-gridmon':
            dbQuery = "insert into fqan(id, name) values (fqan_seq.nextval,'%s')" %in_fqan_name
        elif node == 'sam-nagios':
            dbQuery = "insert into fqan(name) values ('%s')" %in_fqan_name
        db.execute(dbQuery)
        db.commit()
    dbQuery = "select id from fqan where lower(name) = lower('%s')" %in_fqan_name
    db.execute(dbQuery)
    for row in db.dbCursor:
        fqan_id = row[0]
    return fqan_id 

def getPoemData(file):
    log.logger("Getting poem_sync info")
    if file == None:
        query_url="http://localhost/poem_sync/api/0.1/json/servicemetricinstances/"
        try:
            signal.alarm(int(params.timeout))
            url = urllib2.urlopen(query_url)
            signal.alarm(0) #Disable the signal
        except urllib2.HTTPError, e:
            log.logger(e)
            log.error(e.read())
        except urllib2.URLError, e:
            log.error("URLError %d: %s" %(e.reason[0],e.reason[1]))
    else:
        url = open(params.file)
    result = simplejson.load(url)
    return result


def processData(params, db):
    counter = 0
    counterNew = 0
    result = getPoemData(params.file)
    bootstrapper = getDBData(db)
    for row in result:
        info = []
        if row[0] != None:
            service_id = row[0]
            info.append(service_id)
        else:
            info.append(None)
        if row[4] != None:
            metric_id = row[4]
            info.append(metric_id)
        else:
            info.append(None)
        if row[7] != None and row[7] != "" and row[7] != "null":
            fqan = row[7]
            fqan_id = getOrAddFqan(db, fqan, params.node)
            info.append(fqan_id)
        else:
            info.append(None)
        if row[8] != None:
            vo_id = row[8]
            info.append(vo_id)
        else:
            info.append(None)
        if len(info) == 4:
            if info in bootstrapper:
                bootstrapper.remove(info)
                counter = counter + 1
            else:
                insertBootstrapper(db, info[0], info[1], info[2], info[3])
                counterNew = counterNew + 1
    removeOldData(db, bootstrapper)
    log.logger("%d entries were added" % counterNew)
    log.logger("%d were already in bootstrapper" % counter)


def getDBData(db):
    log.logger("Getting entries in bootstrapper from DB")
    dbQuery = "select service_id, metric_id, fqan_id, vo_id from mrs_bootstrapper"
    db.execute(dbQuery)
    log.logger("Processing data")
    bootstrapper = []
    for row in db.dbCursor:
        bootstrapper.append([row[0], row[1], row[2], row[3]])
    return bootstrapper


def removeOldData(db, bootstrapper):
    log.logger("Removing old data from bootstrapper")
    counterRem = 0
    for el in bootstrapper:
        deleteBootstrapper(db, el[0], el[1], el[2], el[3])
        counterRem = counterRem + 1
    db.commit()
    log.logger("%d entries were removed" % counterRem)


def deleteBootstrapper(db, service_id, metric_id, fqan_id, vo_id):
    dbQuery = "delete from mrs_bootstrapper where "
    if service_id is None:
        dbQuery += "service_id is null and "
    else:
        dbQuery += "service_id = %d and " %service_id
    if metric_id is None:
        dbQuery += "metric_id is null and "
    else:
        dbQuery += "metric_id = %d and " %metric_id
    if fqan_id is None:
        dbQuery += "fqan_id is null and "
    else:
        dbQuery += "fqan_id = %d and " %fqan_id
    if vo_id is None:
        dbQuery += "vo_id is null"
    else:
        dbQuery += "vo_id = %d" %vo_id
    db.execute(dbQuery)


def insertBootstrapper(db, service_id, metric_id, fqan_id, vo_id):
    dbQuery = "insert into mrs_bootstrapper (service_id, metric_id, fqan_id, vo_id) values ("
    if service_id is None:
        dbQuery += "null, "
    else:
        dbQuery += "%d, " %service_id
    if metric_id is None:
        dbQuery += "null, "
    else:
        dbQuery += "%d, " %metric_id
    if fqan_id is None:
        dbQuery += "null, "
    else:
        dbQuery += "%d, " %fqan_id
    if vo_id is None:
        dbQuery += "null)"
    else:
        dbQuery += "%d)" %vo_id
    db.execute(dbQuery)


########################
#
# Main
#
########################

signal.signal(signal.SIGALRM, handler)
log = Logger()
params = Params()
db = DB(params)
processData(params, db)
db.close()
log.logger("End\n\n")

