#! /usr/bin/python
# This file is part of kwapi-g5k
#
# It allows to configure automatically the probes using the reference API
#


from socket import getfqdn
from pprint import pprint
from execo import Process, logger
from execo_g5k import get_site_clusters, get_cluster_hosts, get_host_attributes, get_resource_attributes, topology
import re
from pysnmp.entity.rfc3413.oneliner import cmdgen

_community = 'public'
_protocol_power = '1'
_protocol_network = '2c'

# Determining site
hostname = getfqdn().split('.')
site = hostname[1] if len(hostname) > 2 else hostname[0]
# site = 'lyon'

def to_long_uid(uid, site):
    if uid:
        return "%s.%s.grid5000.fr" % (uid, site)
    else:
        return None

cmd_gen = cmdgen.CommandGenerator()
def retrieve_switch_if(switch):
    community = cmdgen.CommunityData('public',mpModel=1)
    errorIndication, errorStatus, errorIndex, varBindTable = \
        cmd_gen.bulkCmd(
            community,
            cmdgen.UdpTransportTarget((switch, 161)),
            1, 0,
            '1.3.6.1.2.1.2.2.1.2', # IfDescr OID
        )
    if errorIndication:
        logger.error(errorIndication)
        return None
    else:
        if errorStatus:
            logger.error('%s at %s' % (
                errorStatus.prettyPrint(),
                errorIndex and varBindTable[-1][int(errorIndex)-1] or '?'
            ))
            return None
        else:
            ifDescr_list = []
            for varBindTableRow in varBindTable:
                for name, value in varBindTableRow:
                    ifDescr_list.append(str(value))
            return ifDescr_list

def format_host_name(host):
    return re.sub(r"renater(-.*)", "renater", host)

def format_host_port(port):
    return '-' + port.replace("/", "").replace(":", "")

def format_probes(probes_list, switch, site, outgoing):
    if outgoing:
        return [("%s.%s_%s" % (site, switch, x) if x else None) for x in probes_list]
    else:
        return [("%s.%s_%s" % (site, x, switch) if x else None) for x in probes_list]

logger.info('Generating configuration of kwapi-drivers for %s',
            site)

logger.info('Retrieving monitoring equipments information')
equips = {}
try:
    for pdu in get_resource_attributes('/sites/'+site+'/pdus/')['items']:
        if pdu.has_key('sensors'):
            for sensor in pdu['sensors']:
                if sensor.has_key('power'):
                    if 'snmp' in sensor['power']:
                        if sensor['power']['per_outlets']:
                            oid = sensor['power']['snmp']['outlet_prefix_oid']
                        else:
                            oid = sensor['power']['snmp']['total_oids'][0][0:-2]
                        equips[pdu['uid']] = {'driver': 'Snmp', 'parameters':
                            {'community': _community, 'protocol': _protocol_power,
                             'ip': pdu['uid'] + '.' + site + '.grid5000.fr',
                             'oid': oid }, 'mapping': [], 'probes': []}
                    if 'wattmetre' in sensor['power']:
                        equips[pdu['uid']] = {'driver': 'Json_url', 'parameters':
                            {'url': sensor['power']['wattmetre']['www']['url']},
                                            'probes': []}
except:
    logger.error('Fail to retrieve power equipments')

logger.info('Retrieving network equipments information')
switchs = {}
g = topology.g5k_graph(site)
switches_cluster = [x[0] for x in filter(lambda n: n[1]['kind'] in ['switch', 'router'], g.nodes(True))]
hosts_cluster = set(g.nodes()) - set(switches_cluster)

try:
    for network_equipment in get_resource_attributes('/sites/'+site+'/network_equipments/')['items']:
        if network_equipment.get('type', '') == 'network_equipment':
            switch = network_equipment.get('uid', None)
            switch_uid = to_long_uid(switch, site)
            if switch_uid in switches_cluster:
                # Iter on switch linecards/ports
                l = -1
                for linecard in network_equipment.get('linecards', []):
                    l+=1
                    snmp_pattern = linecard.get('snmp_pattern', None)
                    if not snmp_pattern:
                        logger.warn("No snmp_pattern for %s: %s" % (switch, l))
                        continue
                    p = -1
                    for port in linecard.get('ports',[]):
                        p+=1
                        host_uid = None
                        device_port = ""
                        if port.has_key('uid'):
                            # Convert renater name
                            host_uid = format_host_name(port['uid'])
                            if port.has_key('port'):
                                device_port = format_host_port(port['port'])
                            if not to_long_uid(host_uid, site) in hosts_cluster:
                                host_uid = None
                        try:
                            device = host_uid + device_port if host_uid else None
                            port = snmp_pattern.replace('%LINECARD%', str(l)).replace('%PORT%', str(p))
                            if host_uid:
                                if switchs.has_key(switch):
                                    if switchs.has_key(port):
                                        logger.error('Collision on switch %s port %s host %s'
                                                     % (switch, port, host_uid))
                                    switchs[switch][port] = device
                                else:
                                    switchs[switch] = {}
                                    switchs[switch][port] = device
                        except:
                            logger.error("Can't write %s:%s:%d:%d:%s" % (switch, snmp_pattern, l, p, host_uid))

    #pprint(equips)
except:
    logger.error('Failed to retrieve network monitoring')

logger.info('Retrieving hosts plug mapping')
switch_probes = {}
logger.info('Map port to hostname')
try:
    for cluster in get_site_clusters(site):
        nodes = get_resource_attributes('/sites/' + site + '/clusters/' + cluster + \
                                        '/nodes')['items']
        for node in nodes:
            if not node.has_key('sensors'):
                continue
            power = node['sensors']['power']

            if power['available']:
                if 'pdu' in power['via']:
                    if isinstance(power['via']['pdu'], list):
                        for pdu in power['via']['pdu']:
                            if 'port' in pdu:
                                equips[pdu['uid'].split('.')[0]]['mapping'].append((node['uid'], pdu['port']))
                            else:
                                if isinstance(pdu, list):
                                    logger.error("Malformed pdu : %s" % pdu)
                                    continue
                                if pdu['uid'] is None:
                                    #print power
                                    continue
                                else:
                                    equips[pdu['uid'].split('.')[0]]['mapping'].append((node['uid'], 0))
                    else:
                        pdu = power['via']['pdu']
                        if 'port' in pdu:
                            logger.warning('node ' + node['uid'] + ' has str instead of list for power[\'via\']')
                            equips[pdu['uid'].split('.')[0]]['mapping'].append((node['uid'], pdu['port']))
                        else:
                            equips[pdu['uid'].split('.')[0]]['mapping'].append((node['uid'], 0))
                if 'www' in power['via']:
                    if not 'per_outlets' in power or power['per_outlets']:
                        equips[pdu['uid']]['probes'].append(node['uid'])
except:
    logger.error('Failed to map power pdus')

#pprint(equips)
logger.info('Generating probe list for Snmp drivers')
try:
    for equip in equips.itervalues():
        if 'mapping' in equip and len(equip['mapping']) > 0:
            if len(filter(lambda x: x[1] != 0, equip['mapping'])) > 0:
                equip['mapping'] = sorted(equip['mapping'], key=lambda x: x[1])
                equip['probes'] = [None] * equip['mapping'][-1][1]
                for probe, outlet in equip['mapping']:
                    equip['probes'][outlet-1] = probe
            else:  
                cluster = equip['mapping'][0][0].split('-')[0]
                radicals = map(lambda x: int(x[0].split('-')[1]), equip['mapping'])
                equip['probes'].append(cluster + '-' + '-'.join(map(str, sorted(radicals))))
except:
    logger.error('Failed to generate probe list') 

network_probes = {}
topo = {}
try:
    for switch in switchs:
        ifDescr_list = retrieve_switch_if(switch)
        probes = [None] * len(ifDescr_list)
        for port in switchs[switch]:
            if not port in ifDescr_list:
                logger.error("Port %s not found in %s" % (port, switch))
            else:
                probes[ifDescr_list.index(port)+1] = switchs[switch][port]
        while not probes[-1]:
            probes.pop()
            if len(probes) == 0:
                break
        switch_probes[switch] = probes
except:
    logger.error('Failed to generate probe list')
try:
    for switch in switchs:
        probesIN = format_probes(switch_probes[switch], switch, site, False)
        probesOUT = format_probes(switch_probes[switch], switch, site, True)
	network_probes[switch] = {'in':probesIN[1:],
				  'out':probesOUT[1:],}
except:
    logger.error('Failed to generate topology and network mapping')
# pprint(topo)
# pprint(equips)
# pprint(network_probes)
logger.info('Writing new configuration file')
powerProbes = []
f = open('/tmp/kwapi-drivers-list.conf', 'w')
for equip, data in equips.iteritems():
    if 'probes' in data and data['probes']:
        sec = "\n["+equip+"]\n"
        sec += "probes = ["
        for probe in data['probes']:
            if probe:
                sec += "'" + site + '.' + probe +"'"
                powerProbes.append(site + '.' + probe)
            else:
                sec += str(None)
            sec += ", "
        sec += "]\n"
        sec += "driver = "+data['driver']+"\n"
        sec += "data_type = {'name':'power', 'type':'Gauge', 'unit':'W'}\n"
        sec += "parameters = "+str(data['parameters']) + "\n"
        f.write(sec)

OID_IN  = "1.3.6.1.2.1.31.1.1.1.6"
OID_OUT = "1.3.6.1.2.1.31.1.1.1.10"

for switch, probes in network_probes.iteritems():
    sec = "\n[%s-IN]\n" % switch
    sec += "probes = ["
    for probe in probes['in']:
        if probe:
            sec += "'" + probe + "'"
        else:
            sec += str(None)
        sec += ", "
    sec += "]\n"
    sec += "driver = Snmp\n"
    sec += "data_type = {'name':'network_out', 'type':'Cumulative', 'unit':'B'}\n"
    sec += "parameters = {'protocol': '"+_protocol_network+"', 'community': 'public', 'ip': '"+switch+"."+site+".grid5000.fr', 'oid': '"+OID_IN+"'}\n"
    f.write(sec)
    sec = "\n[%s-OUT]\n" % switch
    sec += "probes = ["
    for probe in probes['out']:
        if probe:
            sec += "'" + probe + "'"
        else:
            sec += str(None)
        sec += ", "
    sec += "]\n"
    sec += "driver = Snmp\n"
    sec += "data_type = {'name':'network_in', 'type':'Cumulative', 'unit':'B'}\n"
    sec += "parameters = {'protocol': '"+_protocol_network+"', 'community': 'public', 'ip': '"+switch+"."+site+".grid5000.fr', 'oid': '"+OID_OUT+"'}\n"
    f.write(sec)
f.close()
f = open('/tmp/kwapi-live-list.conf', 'w')
f.write("\n[TOPO]\n")
f.write("topo=")
f.write(str(topo))
f.flush()
f.write("\npowerProbes = %s" % powerProbes)
f.close()

logger.info('Adding drivers from API to /etc/kwapi/drivers.conf')
bak_conf = Process('[ -f /etc/kwapi/drivers.conf.orig ] && cp /etc/kwapi/drivers.conf.orig /etc/kwapi/drivers.conf || cp /etc/kwapi/drivers.conf /etc/kwapi/drivers.conf.orig')
bak_conf.shell = True
bak_conf.run()
cat_conf = Process('cat /tmp/kwapi-drivers-list.conf >> /etc/kwapi/drivers.conf ; rm /tmp/kwapi-drivers-list.conf')
cat_conf.shell = True
cat_conf.run()
logger.info('Adding probe-list from API to /etc/kwapi/live.conf')
logger.info('Adding topology to /etc/kwapi/live.conf')
bak_conf = Process('[ -f /etc/kwapi/live.conf.orig ] && cp /etc/kwapi/live.conf.orig /etc/kwapi/live.conf || cp /etc/kwapi/live.conf /etc/kwapi/live.conf.orig')
bak_conf.shell = True
bak_conf.run()
cat_conf = Process('cat /tmp/kwapi-live-list.conf >> /etc/kwapi/live.conf ; rm /tmp/kwapi-live-list.conf')
cat_conf.shell = True
cat_conf.run()

logger.info('Done')

