#!/bin/bash /usr/scripts/python.sh
# -*- coding: utf-8 -*-

u"""
Script permettant d'obtenir des statistiques sur les VLans actuellement distribués par les switchs.

Le script va scanner toutes les prises présentes dans le fichier d'annuaire.
S'il est impossbile de se connecter à un switch, on abandonne le batiment concerné

À la fin sont affichées quelques statistiques, dont les prises sur lesquelles on a trouvé
d'autre VLANs qu'"adherent", ainsi que les machines connectées sur ces prises.

Xavier Lagorce - 2009
Gabriel Détraz - 2015

Réparé en 2015, utilise à présent lc_ldap et hptools2

"""

import sys, getopt
from gestion.annuaires_pg import chbre_prises, bat_switchs
from gestion.affich_tools import *
from gestion.hptools2 import HPSwitch
from gestion.config import vlans

from lc_ldap import shortcuts

# Accès à la base ldap
ldap = shortcuts.lc_ldap_readonly()

# On mets les vlans dans l'ordre id/nom
vlandict = dict((v,k) for k,v in vlans.items())

def scan_bats(bats, annuaire=chbre_prises, verbose=False):
    """Fonction permettant de scanner un certain nombre de switchs

    La fonction scanne les switchs enregistrés de annuaire.py se trouvant dans
    les bâtiments contenus dans la liste bats.

    La fonction utilise l'annuaire passé en argument au celui de annuaire.py
    par défaut.

    Si verbose est à 'True', la fonction affiche le détails de ses connexions
    aux switchs

    La fonction renvoie un triplet (vlans_stats,data,erreurs) :
     * vlans_stats est un dictionnaire contenant les statistiques des VLANs
       trouvés
     * data contient les informations sur les machines se trouvant sur des
       prises possédant d'autres VLANs que le VLAN 'adhérent'
     * erreurs contient la liste des noms des switchs sur lesquels il a été
       impossible de se connecter

    """

    # Initialistation des données :
    # nombre de prises possédant chaque VLAN
    vlans_stats = {'adherent' : 0}
    # liste des résultats particuliers découverts pendant le scan
    data = []
    # liste des noms de switch en erreur :
    erreurs = []

    # On Commence par itérer sur chaque batiment :
    for bat in bats:
        prises = dict()
        for chbre, prise in annuaire(bat).iteritems():
            prises[prise] = chbre
        current_switch = -1
        switch_name = ' '

        for prise in sorted(prises.keys()):
            # Si l'on est pas connecté au switch sur lequel se trouve la prise, on en change
            if current_switch != int(prise[0]) :
                switch_name = u"bat" + bat + u"-" + prise[0] + u".adm.crans.org"
                current_switch = int(prise[0])
                if verbose: print u"Connexion à " + switch_name + u" ..."
                try:
                    switch = HPSwitch(switch_name)
                except ValueError:
                    if verbose:
                        print u"\tImpossible de se connecter à ce switch, abandon du bâtiment..."
                        erreurs.append(switch_name)
                    break
                else:
                    if verbose: print u"\tConnexion établie"

            # On récupère les VLANs présents sur la prise
            try:
                vlans = switch.get_vlans(int(prise[1:]))
                if verbose:
                    print vlans
                    print prise[1:]
            except:
                if verbose:
                    print u"\tErreur de communication avec " + switch_name
                    print u"\tAbandon du bâtiment..."
                    erreurs.append(switch_name)
                break

            if vlans:
                # Si la prise ne possède que le VLAN adherent, on ne va pas plus loin
                if len(vlans) == 1 and vlans[0] == 1:
                    vlans_stats[vlandict[vlans[0]]] = vlans_stats[vlandict[vlans[0]]] + 1
                else:
                    # Sinon, on récupère tous les VLANs
                    for vlan in vlans:
                        if not (vlan in vlans_stats):
                            vlans_stats[vlandict[vlan]] = 0
                        vlans_stats[vlandict[vlan]] = vlans_stats[vlandict[vlan]] + 1
                    # On récupère les données pour les afficher plus tard
                    try:
                        if macres:
                            macs = switch.show_port_macs(int(prise[1:]))[0]
                        else:
                            macs = []
                    except ConversationError:
                        if verbose:
                            print u"\tErreur de communication avec " + switch_name
                            print u"\tAbandon du bâtiment..."
                            erreurs.append(switch_name)
                        break

                    if macs:
                        for mac in macs:
                            machine = ldap.search(u"macAddress=%s" % mac)
                            try:
                                machine = machine[0]
                                adh = machine.proprio()
                                chbre = bat.upper() + prises[prise]
                                data.append([bat.upper() + prise,
                                             chbre,
                                             unicode(adh['nom'][0]),
                                             unicode(machine['host'][0]),
                                             machine.ldap_name,
                                             ', '.join(sorted([vlandict[vlan] for vlan in vlans]))])
                            except IndexError:
                                pass

                    # Si aucune machine n'est connectée, on ajoute quand même une
                    # entrée dans les résultats
                    else:
                        # On tente de trouver le nom de l'adherent :
                        chbre = bat.upper() + prises[prise]
                        adh = ldap.search(u"chbre=%s" % chbre)
                        try:
                            adh = adh[0]
                            nom = unicode(adh['nom'][0])
                            data.append([bat.upper() + prise,
                                         chbre,
                                         nom,
                                         '-',
                                         '-',
                                         ', '.join(sorted([vlandict[vlan] for vlan in vlans]))])
                        except IndexError:
                            pass

    # On renvoie les résultats
    return (vlans_stats,data,erreurs)

def __usage_brief(err=''):
    """Affiche un message d'erreur court"""
    if err:
        err = u' : ' + err
    else:
        err = u'.'

    print u"Option(s) invalide(s)" + err
    print u"Pour obtenir de l'aide sur l'utilisation de ce programme utilisez l'option -h"
    sys.exit(2)

def __usage():
    """Affichage de l'aide sur l'utilisation du script"""

    print u"""
    statsVLans.py :
        Script permettant d'obtenir des statistiques sur les VLans présents sur le réseau

    Usage : statsVLans.py OPTIONS

    options disponibles :

    -a / --all :
        Scanne l'intégralité des bâtiments et des switchs présents dans l'annuaire.
        Incompatible avec l'option --bats.
    -b / --bats :
        Permet de spécifier les bâtiments à scanner.
        Incompatible avec l'option --all.
        ex : --bats=abh
    -h / --help :
        Affiche ce message d'aide.
    -v / --verbose :
        Affiche le détail des connexions aux switchs pendant le scan.
    -m / --macresearch
        Effectue un traitement des résultats par mac (et non par propriétaire de chambre)

    Une des options --all ou --bats doit être obligatoirement spécifiée.
    """
    sys.exit(0)

# Traitement par défaut si le fichier est directement appellé comme un script
if __name__ == "__main__":
    # Récupération des options
    if len(sys.argv) == 1:
        # Pas d'option fournie
        __usage_brief()

    try:
        options, arg = getopt.gnu_getopt(sys.argv[1:],'ab:p:hvm',
                                         ['all','bats=','prises=','help','verbose','macresearch'])
    except getopt.error, msg :
        __usage_brief(unicode(msg))

    # Traitement des options
    verb = False

    bats = ''
    all_prises = False
    macres = True

    for opt, val in options:
        if opt == '-a' or opt == '--all':
            if bats:
                __usage_brief(u"les options --all et --bats sont incompatibles !")
            bats = 'all'

        elif opt == '-b' or opt == '--bats':
            if bats == 'all':
                __usage_brief(u"les options --all et --bats sont incompatibles !")

            # On évite les doublons de batiments
            for c in val:
                if not (c in bats):
                    bats += c

        elif opt == '-h' or opt == '--help' :
            __usage()

        elif opt == '-v' or opt == '--verbose':
             verb = True

        elif opt == '-m' or opt == '--macresearch':
             macres = True

    if not bats:
        __usage_brief(u"il faut spécifier des bâtiments à scanner")

    if bats == 'all':
        # On récupère tous les bâtiments contenus dans l'annuaire :
        lbats = bat_switchs
    else:
        # On génère une liste avec les bâtiments pour être propre
        lbats = [bat for bat in bats]

    # On récupère les statistiques sur les bâtiments demandés
    vlans_stats, data, erreurs = scan_bats(lbats,verbose=verb)

    # Fonction helper :
    def concat(s1,s2):
        return s1 + u' ' + s2

    # Et on les affiche
    print u"Statistiques sur les batiments :" + reduce(concat,lbats,'')
    if erreurs:
        print u"Ces switchs n'ont pas pû être contactés :"
        for switch in erreurs:
            print u" * " + switch

    print u"Statistiques des VLANs : ", vlans_stats
    print u""

    if data:
        print u"Prises possédant des VLANs différent d''adherent'"
        print tableau(data,
                      titre = [u"Prise", u"Chambre", u"Adhérent", u"Machine", u"Type", u"VLANs"])
