#!/usr/bin/env python
# Copyright European Organization for Nuclear Research (CERN) 2013
#
# Licensed under the Apache License, Version 2.0 (the "License");
# You may not use this file except in compliance with the License.
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
#
# Authors:
# - Ralph Vigne <ralph.vigne@cern.ch>, 2014

import argparse
import datetime
import logging
import requests
import sys

from pystatsd import Client
from sys import stdout

logging.basicConfig(stream=stdout,
                    level=logging.ERROR,
                    format='%(asctime)s\t%(process)d\t%(levelname)s\t%(message)s')

logger = logging.getLogger(__name__)

def monitor_nginx(uri):
    raise NotImplemented


def monitor_apache(uri):
    fqdn = uri
    logger.info('Requesting status from Apache running on %s' % fqdn)

    response = requests.get(fqdn+'/server-status?auto', verify='/etc/pki/tls/certs/CERN-bundle.pem')
    if response.status_code != 200:
        logger.error('Invalid HTTP status code')
        logger.error(response)

    text = response.text.split('\n')
    # Total Accesses: 2975146
    # Total kBytes: 14555116
    # CPULoad: .601986
    # Uptime: 111858
    # ReqPerSec: 26.5975
    # BytesPerSec: 133244
    # BytesPerReq: 5009.65
    # BusyWorkers: 23
    # IdleWorkers: 7

    stats = dict()
    IGNORE = ['BytesPerSec', 'BytesPerReq', 'Uptime', 'Total_kBytes', 'Total_Accesses', 'ReqPerSec']

    server_name = get_server_name(uri)
    stats['server_name'] = server_name
    for i in range(9):
        tmp = text[i].split(': ')
        key = tmp[0].replace(' ', '_')
        if key not in IGNORE:
            stats[key] = tmp[1]
    ascii = ''.join(text[9:])
    logger.debug(ascii)
    stats['Keepalive'] = int(ascii.count('K'))
    stats['Sending'] = int(ascii.count('W'))
    stats['Reading'] = int(ascii.count('R'))
    stats['Waiting'] = int(ascii.count('_'))
    stats['availability'] = 100 / ((int(stats['BusyWorkers']) + int(stats['IdleWorkers']))) * (stats['Keepalive'] + stats['Waiting'])
    return stats


def backend_graphite(url, stats, prefix):
    server, port = url.split(':')
    try:
        pystatsd_client = Client(host=server, port=port, prefix='%s.%s' % (prefix, stats['server_name']))
    except Exception, e:
        logger.error('Unable to connect to Graphite backend %s: %s' % (url, e))
        raise
    for s in stats:
        if s in ['server_name']:
            continue
        try:
            logger.debug('%s.%s.%s => %s' % (prefix, stats['server_name'], s, float(stats[s])))
            pystatsd_client.gauge(s, float(stats[s]))
        except Exception as e:
            logger.error('Failed reporting %s (%s): %s' % (s, stats[s], e))


def get_server_name(fqdn):
    return fqdn.split('//')[1].split('.')[0]


def backend_xsls(fqdn, stats):
    xml_str = '<serviceupdate xmlns="http://sls.cern.ch/SLS/XML/update">'
    xml_str += '<id>rucio.%s.httpd</id>' % stats['server_name']
    xml_str += '<availability>%s</availability>' % stats['availability']
    xml_str += '<availabilityinfo>%s of %s worker processes idel.</availabilityinfo>' % ((int(stats['BusyWorkers']) + int(stats['IdleWorkers'])), int(stats['IdleWorkers']))
    xml_str += '<timestamp>%s</timestamp>' % (datetime.datetime.now().isoformat().split('.')[0])
    xml_str += '<data>'
    for s in stats:
        if s in ['server_name']:
            continue
        xml_str += '<numericvalue name="rucio.%s.httpd.%s" desc="#">%s</numericvalue>' % (stats['server_name'], s,  stats[s])
    xml_str += '</data>'
    xml_str += '</serviceupdate>'
    logger.debug(xml_str)
    r = requests.post(fqdn, files={'file': xml_str})
    return r.status_code

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description='Takes backend classes and Apaches URLs')
    parser.add_argument('--backend', metavar='B', type=str, nargs='+',  help='a list of type::URL[:port][::scope] sets to which the script will report to. E.g. --backend graphite::rucio-graphite-int.cern.ch:8025/listen/now::rucio.backends xsls::xsls.cern.ch')
    parser.add_argument('--server', metavar='S', type=str, nargs='+',  help='a list of type::FQDN tuples. Type is either apache or nginx. E.g. --sever apache::www.example.com nginx::www.example2.net')
    parser.add_argument('--verbose', help='makes it chatty', action="store_true")
    args = parser.parse_args()

    if args.verbose:
        logger.setLevel(level=logging.DEBUG)
    args = vars(args)

    if not args['server']:
        logger.critical("No server to monitor provided. Exiting.")
        sys.exit(1)
    servers = []
    for server in args['server']:
        try:
            t, fqdn = server.split('::')
            logger.debug('Monitoring server => Type: %s\tFQDN: %s' % (t, fqdn))
            servers.append((t, fqdn))
        except ValueError:
            logger.critical('Can not unpack server information: %s' % server)
            sys.exit(1)
    if not len(servers):
        logger.critical("No server to monitor provided. Exiting.")
        sys.exit(1)

    backends = []
    for backend in args['backend']:
        try:
            tmp = backend.split('::')
            if len(tmp) < 2 or len(tmp) > 3:
                raise ValueError
            t, url, prefix = tmp if len(tmp) == 3 else [tmp[0], tmp[1], None]
            logger.debug('Reporting to backend => Type: %s\tURL: %s\tPrefix: %s' % (t, url, prefix))
            backends.append((t, url, prefix))
        except ValueError:
            logger.critical('Can not unpack backend information: %s' % backend)
            sys.exit(1)
    if not len(backends):
        logger.critical("No backend provided. Exiting.")
        sys.exit(1)

    for server in servers:
        try:
            t, fqdn = server
            stats = {}
            if t == 'apache':
                stats = monitor_apache(fqdn)
            elif t == 'nginx':
                stats = monitor_nginx(fqdn)
            else:
                logger.critical('Can not monitor server of type %s: Not supported' % type)
                sys.exit(1)
            logger.info('Stats gathering from %s OK.' % fqdn)
        except Exception as e:
            logger.error('Failed requesting data from %s server: %s' % (t, fqdn))
            logger.error(e)
            stats = {'server_name': get_server_name(fqdn),
                     'availability': 0}  # Resetting Gauge values in Graphite

        for backend in backends:
            t, url, prefix = backend
            if t == 'graphite':
                if not prefix:
                    logger.critical('Can not report to graphite without prefix')
                    sys.exit(1)
                try:
                    backend_graphite(url, stats, prefix)
                    logger.info('Reporting to %s OK.' % url)
                except Exception as e:
                    logger.error('Unable to report to Graphite backend: %s' % e)
            elif t == 'xsls':
                try:
                    sc =  backend_xsls(url, stats)
                except Exception:
                    sc = -1
                if sc != 200:
                    logger.error('Unable to report to XSLS backend. Statuscode: %s' % sc)
                else:
                    logger.info('Reporting to %s OK.' % url)
            else:
                logger.critical('Can not report to backend of type %s: Not supported' % type)
                sys.exit(1)
    sys.exit(0)
