scripts/gestion/hptools.py
bernat b1adc8fc8d Parfois, snmpwalk renvoie des caractres bizarres
darcs-hash:20041114175048-d1718-e179bcbf1a44b249f0de1e755b930ae4560bd7b9.gz
2004-11-14 18:50:48 +01:00

494 lines
16 KiB
Python

# -*- python -*-
# -*- coding: iso-8859-15 -*-
"""
Fichier de base d'interface avec les switchs manageables.
Donne la classe switch qui permet d'effectuer les opérations
élémentaires sur les switchs manageable HP 26xx.
Frédéric PAUGET
"""
from time import sleep
from popen2 import popen3
from sys import stderr, path
from commands import getstatusoutput
from annuaires import chbre_prises, all_switchs, uplink_prises
import threading
try :
from secrets import config_snmp_secrete
except :
# Si a pas le droit de lire config_snmp_secrete
# on va tenter de tout faire en snmpv1 et communauté public
def config_snmp_secrete(snmp,switch) :
snmp = snmp(switch,version='1',community='public')
return snmp.get, snmp.set, snmp.walk
#############################################################################################
### Définitions de classes utiles
# Quelques exceptions
class ConnectionTimout(Exception) :
pass
class ConnectionClosed(Exception) :
pass
class ConversationError(Exception) :
pass
# Classes de base pour comminiquer (ssh et snmp)
class ssh :
""" Ouverture d'une connexion ssh, envoi de commandes et récupération du résultat """
__debug = 1
__logDest = stderr
__ssh_out = '' # Retour de la connexion ssh
def __init__(self,host) :
""" Ouverture d'une connexion ssh vers le switch choisi """
self.switch = host
if self.__debug : self.__logDest.write("SSH DEBUG : __init__(host=%s)\n" % host)
self.__host = host
self.__retour, self.__input, self.__err = popen3("/usr/bin/ssh -tt %s" % host)
sleep(1)
# Création de threads de lecture de la connexion
r = threading.Thread(target=self.__read_retour,args=(threading.currentThread(),))
r.start()
e = threading.Thread(target=self.__read_err,args=(threading.currentThread(),))
e.start()
self.log=open('/tmp/test_ssh','w')
# On passe l'intro, passe manager et en environnement de configuration
self.send_cmd('\nenable\nconfigure')
def __read_retour(self,parent) :
while parent.isAlive() :
c = self.__retour.read(1)
self.__ssh_out += c
self.log.write(c)
self.log.flush()
def __read_err(self,parent) :
err = ''
while parent.isAlive :
char = self.__err.read(1)
if char == '\n' :
# Il y a eu une erreur
if err.lower() in [ 'connection to %s closed by remote host.' % self.switch ,
'connection to %s closed.' % self.switch ] :
raise ConnectionClosed
else :
print err
err = ''
else :
err += char
def __del__(self) :
"""Ferme la connexion : envoi logout et attend """
if self.__debug : self.__logDest.write("SSH DEBUG : __del__()\n")
try :
self.send_cmd('\nlogout')
except ConnectionClosed :
# C'est le but
pass
def send_cmd(self,cmd,timeout=15):
""" Envoi une commande, attend le prompt et retourne la réponse """
if self.__debug : self.__logDest.write("SSH DEBUG : __send_cmd(%s)\n" % cmd.strip() )
self.__ssh_out = '' # oubli de ce qu'il y a avant
self.log.write('*****\n')
self.log.flush()
# Envoi de la commande
self.__input.write(cmd+'\n')
self.__input.flush()
# Premier retour
count=0
while 1:
# On a récupéré un prompt ?
try :
print "|%s|" % self.__ssh_out[-45:-38]
if self.__ssh_out[-45:-38] == ' [y/n]?' :
self.__input.write('y')
self.__input.flush()
sleep(1)
continue
elif self.__ssh_out[-46:-36].lower() == '(config)# ' :
if self.__debug : self.__logDest.write("SSH DEBUG : __send_cmd -> OK\n")
break
elif self.__ssh_out[-70:-7] == '-- MORE --, next page: Space, next line: Enter, quit: Control-C' :
# Faut appuyer sur une touche
if self.__debug : self.__logDest.write("SSH DEBUG : __send_cmd -> MORE\n")
self.__input.write(' ')
self.__input.flush()
sleep(1)
continue
except: pass
# Rien de bien, le switch es un peu lent, on attend
if self.__debug : self.__logDest.write("SSH DEBUG : __send_cmd -> WAIT\n")
sleep(1)
count += 1
if count > timeout :
# Il y a un problème
raise ConnectionTimout
return self.__ssh_out
class snmp :
""" Classe de communication SNMP """
def __init__(self,host,version=None,community=None,authentication_protocol=None, authentication_pass=None, username=None, privacy_pass=None) :
""" host doit être la machine sur laquelle se connecter
version est la verion du protocole snmp à utiliser : 1, 2c ou 3
le reste des données doit être ou non fourni suivant la version
pour v1 et v2c seule la communauté est prise en compte
pour v3 authentication_protocol, authentication_pass et username sont requis si accès en authNoPriv
si privacy_pass est fourni accès en authPriv
"""
self.host = host
self.version = version
if version == '1' or version == '2c' :
self.options = "-v %s -c '%s' %s " % ( version, community, host )
elif version =='3' :
self.options = "-v 3 -u %s -a %s -A '%s' -l authNoPriv" % ( username, authentication_protocol, authentication_pass )
if privacy_pass :
self.options += " -x DES -X '%s' -l authPriv" % privacy_pass
self.options += " %s " % host
else :
raise ValueError('Version incorrecte')
def __exec(self,cmd) :
s, r = getstatusoutput(cmd)
if s :
raise ConversationError(r)
return r
def get(self,oid) :
""" Retourne le résultat correspondant à l'oid demandé """
return self.__exec('snmpget -O vq %s %s ' % ( self.options, oid ) )
def set(self,oid,typ,val) :
""" Change la valeur le l'oid donné.
type est le type de la valeur
val est la valeur à écrire
"""
return self.__exec('snmpset -O vq %s %s %s %s' % (self.options, oid, typ, val ) )
def walk(self,base_oid) :
""" Retourne le résultat de snmpwalk
le retour est un dictionnaire { oid : valeur }
"""
lignes = self.__exec('snmpwalk -O q %s %s' % (self.options, base_oid ) ).split('\n')
result = {}
for l in lignes :
oid, valeur = l.split('"', 1)
result[oid] = '"' + valeur
return result
#############################################################################################
### Gestion des switchs proprement dite
class hpswitch :
""" Classe pour l'administration des switchs HP. """
# Variables internes
__debug=0
__logDest=stderr
# Quelques paramètres
IP_tftp = '138.231.136.7'
# Variables internes
switch = None # nom du switch
prise = ''
__conn_ssh = None
def __init__(self,switch) :
""" Switch doit être le nom du switch """
if self.__debug : self.__logDest.write("HP DEBUG : __init__(switch=%s)\n" % switch )
self.switch = switch.lower()
# Config snmp
self.get, self.set, self.walk = config_snmp_secrete(snmp,switch)
def __ssh(self,cmd) :
if not self.__conn_ssh :
self.__conn_ssh = ssh(self.switch)
return self.__conn_ssh.send_cmd(cmd)
def set_prise(self,prise,action) :
"""
Effectue l'action action donnée sur la (les) prise(s) donnée(s)
'prise' peut être un ensembre de prises :
ex : '1-3,6' effectuera l'action sur les prises 1,2,3 et 6
'action' peut être principalement
enable/disable
speed-duplex [auto-100/auto-10]
voir manuel des swtichs pour plus d'actions possibles
"""
if not prise : prise = self.prise
if self.__debug : self.__logDest.write("HP DEBUG : set_prise(prise=%s,action=%s)\n" %( prise,action))
return self.__ssh( "interface ethernet %s %s" % (prise,action) )
def show_stats(self,prise='') :
""" Retourne les stats de(s) prise(s) choisie(s)."""
if not prise : prise = self.prise
if self.__debug : self.__logDest.write("HP DEBUG : show_prise_status(prise=%s)\n" % prise)
if prise :
return self.__ssh("show interfaces ethernet %s" % prise)
else :
return self.__ssh("show interfaces")
def show_prise_mac(self,prise='') :
""" Retourne le(s) adresse(s) MAC présentes sur la prise."""
if not prise : prise = self.prise
if self.__debug : self.__logDest.write("HP DEBUG : show_prise_mac(prise=%s)\n" % prise)
try:
data = self.walk('STATISTICS-MIB::hpSwitchPortFdbAddress.%d' % int(prise))
return map(lambda x:":".join(x[1:-2].lower().split(" ")),data.values())
except ValueError:
# Pas de MAC trouvée
return []
def where_is_mac(self, mac) :
"""Retrouve la prise correspondant à une adresse MAC donnée"""
if self.__debug : self.__logDest.write("HP DEBUG : where_is_mac(mac=%s)\n" % mac)
# On va transformer l'adresse MAC cherchée pour la mettre au format 0A 0A 0A 0A 0A 0A
mac = mac.upper()
mac = filter(lambda x: x in "0123456789ABCDEF", mac) # 0A0A0A0A0A0A
mac = "%s %s %s %s %s %s" % (mac[0:2], mac[2:4], mac[4:6],
mac[6:8], mac[8:10], mac[10:12])
# On interroge le switch
data = self.walk('STATISTICS-MIB::hpSwitchPortFdbAddress');
# On cherche dans data la bonne adresse MAC
for (onesnmp, onemac) in data.iteritems():
if onemac[1:-2] == mac:
return int(onesnmp.split(".")[1])
# On a rien trouvé
return None
def set_prise_mac(self,prise='',mac='') :
""" Défini les adresses mac autorisées sur une prise.
/!\ format de la mac : xxxxxx-xxxxxx (x hexa)
On peut aussi en mettre plusieus séparées par des espaces.
Si mac est précédé de -, enlève les adresses (si juste - les vire toutes)
Si mac n'est pas fourni retourne le(s) adresse(s) MAC autorisées sur la prise.
Si prise n'est pas fourni retourne la config de sécurité de tous les ports
"""
if not prise : prise = self.prise
if self.__debug : self.__logDest.write("HP DEBUG : set_prise_mac(prise=%s,mac=%s)\n" % (prise,mac))
if not mac :
return self.__sudo("show port-security %s" % prise)
if mac[0]=='-' :
no='no '
mac=mac[1:]
else :no=''
return self.__ssh("%sport-security ethernet %s mac-address %s" % (no, prise,mac) )
def show_interfaces(self,prise='') :
""" Retourne la liste des interfaces ainsi que leur état
Si prise est spécifié n'affiche que la prise demandée """
if not prise : prise = self.prise
if self.__debug : self.__logDest.write("HP DEBUG : show_interfaces(prise=%s)\n" % prise)
a = self.__ssh("show interfaces brief")
if a and prise :
a=a.split('\n')
try : a='\n'.join(a[:5]+[a[prise+4]],'\n')
except : a=0
return a
def update(self) :
""" Upload de la config courrante
Téléchargment de la nouvelle config
Reboot."""
if self.__debug : self.__logDest.write("HP DEBUG : update()\n")
# Sauvegarde
self.__ssh("copy running-config tftp %s %s unix" % (self.IP_tftp, self.switch + '.running') )
try :
self.__ssh("copy tftp startup-config %s %s unix" % (self.IP_tftp, self.switch+'.conf') )
except ConnectionClosed :
# C'est normal : le switch reboote
pass
def upgrade(self) :
""" Changement de firmware, le firmware est sur la machine
ayant le serveur tftfp et a pour nom firmware26xx """
if self.__debug : self.__logDest.write("HP DEBUG : update()\n")
self.__ssh("copy tftp flash %s firmware26xx" % self.IP_tftp,70)
def reboot(self) :
""" Reboote le switch """
if self.__debug : self.__logDest.write("HP DEBUG : reboot()\n")
try :
self.__ssh("boot")
except ConnectionClosed :
# C'est normal : le switch reboote
pass
# Fonction utilisant le SNMP
def multicast(self,ip='') :
""" Donne la liste des ports du swich inscrits au flux multicast donné
Si aucun flux donné teste tous les flux multicast possibles.
Retourne un dictionnaire : { adresse du flux : [ ports inscrits ] }
"""
if self.__debug : self.__logDest.write("HP DEBUG : multicast(ip=%s)\n" % ip)
if ip :
data = self.walk('STATISTICS-MIB::hpIgmpStatsPortIndex2.%s' % ip)
return { ip : data.values() }
else :
data = self.walk('STATISTICS-MIB::hpIgmpStatsPortIndex2')
result = {}
# On veut tout
for oid, port in data.items() :
try : ip = '.'.join(oid.split('.')[2:6])
except : continue
result.setdefault(ip,[])
result[ip].append(port)
return result
def nb_prises(self) :
""" Retourne le nombre de prises du switch """
if self.__debug : self.__logDest.write("HP DEBUG : nb_prises()\n")
return int(self.get('IF-MIB::interfaces.ifNumber.0')) - 2
def version(self) :
""" Retourne la version du firmware du switch """
if self.__debug : self.__logDest.write("HP DEBUG : version()\n")
return self.get('SNMPv2-MIB::sysDescr.0')
def enable(self,prise=0) :
""" Active une prise """
if not prise : prise = self.prise
if self.__debug : self.__logDest.write("HP DEBUG : enable(prise=%s)\n" % prise)
return self.set('IF-MIB::ifAdminStatus.%d' % int(prise), 'i', 1)
def disable(self,prise=0) :
""" Désactive une prise """
if not prise : prise = self.prise
if self.__debug : self.__logDest.write("HP DEBUG : disable(prise=%s)\n" % prise)
return self.set('IF-MIB::ifAdminStatus.%d' % int(prise), 'i', 2)
def __is(self,oid,prise) :
if not prise : prise = self.prise
prise = str(prise)
if prise=='all' :
nb = 0
for oid,etat in self.walk(oid).items() :
if etat == 'up' and int(oid.split('.')[1])<51 :
# Le <51 est ici pour éviter de compter les ports fictifs
nb += 1
return nb
prise = prise.replace('-','')
return self.get(oid + '.' + prise) == 'up'
def is_enable(self,prise=0) :
""" Retoune True ou False suivant si la prise est activée ou non
Si prise=all retourne le nombre de prises activées sur le switch """
if prise != 'all': prise = int(prise)
return self.__is('IF-MIB::ifAdminStatus',prise)
def is_up(self,prise=0) :
""" Retoune True ou False suivant si la prise est up
Si prise=all retourne le nombre de prises up sur le switch """
if prise != 'all': prise = int(prise)
return self.__is('IF-MIB::ifOperStatus',prise)
def nom(self,nom=None,prise=0) :
""" Retourne ou attribue le nom à la prise fournie """
if not prise : prise = self.prise
oid = 'IF-MIB::ifAlias.%d' % int(prise)
if nom==None :
return self.get(oid)
else :
self.set(oid, 's' , nom)
def eth_mode(self,mode=None,prise=0) :
""" Fixe ou retourne le mode d'une prise
mode est un tuple : (vitesse, duplex) ou simplement "auto"
vitesse est : 10 100 ou 1000
duplex est FD, HD ou auto
"""
if not prise : prise = self.prise
oid = 'CONFIG-MIB::hpSwitchPortFastEtherMode.%d' % int(prise)
if mode == None :
return self.get(oid)
# Conversion du mode
if mode == 'auto' :
code = 5
else :
code = { 'HD' : 2 , 'FD' : 4 , 'AUTO' : 8 }[mode[1].upper()]
if mode[0] == 10 :
code -= 1
elif mode[0] == 1000 :
if code == 8 : code += 1
elif code == 2 : raise ValueError('Mode invelide %s' % mode)
else: code += 1
self.set(oid,'i',code)
class sw_chbre(hpswitch) :
def __init__(self,chbre) :
# On retrouve la chbre dans l'annuaire
self.chbre = chbre
try :
bat = chbre[0].lower()
prise = chbre_prises[bat][chbre[1:]]
self.prise_brute = prise
self.switch = 'bat%s' % bat
num_switch = int(prise[0])
if num_switch != 0 :
self.switch += '-%i' % num_switch
if prise[-1] == '-' :
#Prise en 10
self.prise = int(prise[1:-1])
self.prise10Mb = True
else :
self.prise = int(prise[1:])
self.prise10Mb = False
except :
raise ValueError('Chambre %s inconnue' % chbre)
# Config snmp
self.get, self.set, self.walk = config_snmp_secrete(snmp,self.switch)
def reconfigure(self) :
""" Reconfigure la prise (nom et vitesse) """
in10 = self.eth_mode().find('10Mbits') != 1
if self.prise10Mb and not in10 :
self.eth_mode(('10','auto'))
elif not self.prise10Mb and in10 :
self.eth_mode('auto')
nom = 'Chambre_%s' % self.chbre.capitalize()
if nom != self.nom() :
self.nom(nom)