#!/usr/bin/python -ut
# -*- coding: utf-8 -*-

'''Harvester client. Gathers and transmits OpenVZ related info'''

import ConfigParser
import popen2
import os
import sys
import simplejson
import logging
import syslog
import MySQLdb
import _mysql

def prepareDict(config,  section):
    data_dict = {}
    for param in config.options(section):
        data_dict[param] = config.get(section,  param)
    return data_dict

def stdX(cmd,  *args):
    '''Execute given cmd and return normal output or 
    'NULL' in case of any errors
    '''
    cmd = cmd.strip('\n')
    if args:
        stdout, stdin, stderr = popen2.popen3(cmd % args[0])
    else:
        stdout, stdin, stderr = popen2.popen3(cmd)
    if stderr.read():
        log(stderr.read())
        output = 'NULL'
    else:
        output = stdout.read().strip().strip('\n')
    return output

def vzlist(cmd,  exclude_list):
    '''Returns list of str(veids)'''
    try:
        stdout, stdin = popen2.popen4(cmd)
        # TODO: sudo!
        veids = stdout.read().strip()
        veids = veids.split("\n")
        veids = [veid.strip() for veid in veids]
        if exclude_list:
            [veids.remove(veid) for veid in exclude_list if veid in veids]
    except:
        veids=[]
    return veids

def readState(statefile):
    '''Read and return data from given state file'''
    try:
        sf = open(statedir+statefile, 'rb')
        data = simplejson.loads(sf.readlines()[0])
        sf.close()
    except IOError:
        log('error reading state file')
        data = None
    except IndexError:
        log('looks like state file is empty')
        data = None
    #there should be no other errors
    return data

def writeState(statefile, data):
    '''Write down data to state file'''
    try:
        sf = open(statedir+statefile, 'wb')
        sf.write(simplejson.dumps(data))
        sf.close()
    except:
        log('error writing state file!')
        return False
    #return True #?

def writeQueue(type,  data):
    '''Write down queue file'''
    import datetime
    import time
    date = str(int(time.mktime(datetime.datetime.now().timetuple())))
    path = statedir+"unsent-"+type+"/"+date
    try:
        qf = open(path, 'wb')
        qf.write(simplejson.dumps(data))
        qf.close()
        log('success : queue %s wrote down' % date)
    except:
        log('error writing queue file to %s' % path)
        return False

def readQueue(type, qfile=False):
    '''Read given queue file and return data.
    Otherwise return list of queue files'''
    import os
    if qfile:
        qf = open(statedir+"unsent-"+type+"/"+qfile)
        data = simplejson.loads(qf.readlines()[0])
        qf.close()
        os.unlink(filename) # isn`t it too early?
        return data
    else:
        return os.listdir(statedir+'unsent/'+type+".*")


class DBConnect:
    ''' db connection class '''
    def __init__(self,  params):
        self.db = params
    
    def connect(self):
        self.dbconn = MySQLdb.connect(db=self.db['dbname'], host=self.db['host'], port=int(self.db['port']), user=self.db['username'], passwd=self.db['password'])
        self.cursor = self.dbconn.cursor()
    
    def formQuery(self,  datadict,  tablename):
        self.datadict = datadict
        self.tablename = tablename
        self.query=""
        self.datalist = ['%s="%s", ' % (i,  self.datadict[i]) for i in self.datadict.keys()]
        self.query = 'insert into `%s` set hostnode="%s", ' % (self.tablename,  hostnode) + self.query.join(self.datalist).rstrip(", ")
        try:
            self.cursor.execute(self.query)
        except _mysql.ProgrammingError:
            log('looks like unexistent table %s' % tablename)
            raise
        except _mysql.OperationalError:
            log('something is not ok with table %s' % tablename)
            raise
    
    def ping(self, tablename):
        '''refresh ${table}_monitoring'''
        self.tablename = tablename
        self.cursor.execute('replace into `%s_monitoring` values("%s", NULL)' % (tablename, hostnode))
    
    def closeCursor(self):
        self.cursor.close()
        self.dbconn.close()


def mailAlert(msg):
    ''' Mail error reports '''
    import os
    MAIL = "/usr/sbin/sendmail"
    message = """From: Harvester <%s>
To: %s
Subject: [Harvester] Error report for %s
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8

%s
""" % (mailfrom,  mailfrom,  hostnode,  str(msg))
    p = os.popen("%s -t" % MAIL, 'w')
    p.write(message)
    p.close()


def compareData(cdata, pdata):
    '''Compare current data and previous data/
    Return data to be inserted.'''
    if pdata:
        ps = pdata.keys()
    else:
        ps = []
    if cdata:
        cs = cdata.keys()
    else:
        cs = []
    #
    new = []
    citizens = []
    changed = []
    new_data = {}
    #
    [new.append(i) for i in cs if i not in ps]
    citizens = [i for i in cs if i not in new]
    changed = [i for i in citizens if pdata[i] != cdata[i]]
    for i in new+changed:
        new_data[i] = cdata[i]
    return new_data



try:
    arg = sys.argv[1]
except IndexError:
    print ''' Possible arguments are:
    'hn' - for host info harvesting;
    've' - for VE info harvesting.'''
    sys.exit()

config = ConfigParser.ConfigParser()
config.read('/etc/harvester/harvester.conf')

statedir = config.get('main', 'statedir')
hostnode = config.get('main', 'hostnode')
mailto = config.get('main', 'mail_to').split(' ')
mailfrom = "harvester@immo.ru"
relay_host = config.get('main', 'relay_host')

if config.getboolean('main', 'syslog'): 
    syslog.openlog('harvester')
    log = syslog.syslog
else:
    log = lambda x: None

params = {}
for i in config.options('database'): params[i] = config.get('database', i)
tablename = params['%s_table' % arg]

vzlist = vzlist(config.get('vzlist', 'vzlist'),  config.get('vzlist', 'exclude_list').split())

data_dict = prepareDict(config, '%s-plugins' % arg)
current_data = {}

if arg == 've':
    for ve in vzlist:
        data_tmp = {}
        for cmd in data_dict.keys():
            data_tmp[cmd] = stdX(data_dict[cmd] % ve)
        current_data[ve] = data_tmp
else:
    for cmd in data_dict.keys():
        current_data[cmd] = stdX(data_dict[cmd])

dbc = DBConnect(params)
previous_data = readState(arg)
if current_data != previous_data:
    writeState(arg,  current_data)
    try:
        dbc.connect()
    except _mysql.OperationalError:
        log('error connecting to database')
        mailAlert('error connecting to database')
        sys.exit()
    if arg == 've':
        data_to_insert = compareData(current_data, previous_data)
        try:
            for ve in data_to_insert.keys():
                dbc.formQuery(data_to_insert[ve], tablename)
        except:
            log('error writing to table')
    else:
        try:
            dbc.formQuery(current_data, tablename)
        except:
            log('error writing to table')
dbc.connect()
dbc.ping(tablename)
dbc.closeCursor()


