533 lines
18 KiB
Python
533 lines
18 KiB
Python
#!/usr/bin/env python
|
|
# -*- coding: utf-8 -*-
|
|
|
|
"""
|
|
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
|
|
from os.path import exists
|
|
from os import system
|
|
import sys
|
|
from re import findall
|
|
from config import vlans
|
|
|
|
path.append('/usr/scripts/gestion')
|
|
from ldap_crans import crans_ldap
|
|
cl = crans_ldap()
|
|
|
|
try:
|
|
path.append('/usr/scripts/gestion/secrets')
|
|
from secrets import config_snmp_secrete, reconf_snmp
|
|
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)
|
|
|
|
import pexpect
|
|
class ssh :
|
|
""" Ouverture d'une connexion ssh, envoi de commandes et récupération du résultat """
|
|
|
|
|
|
def __init__(self,host) :
|
|
""" Ouverture d'une connexion ssh vers le switch choisi """
|
|
self.switch = host.split('.')[0] # On garde pas le fqdn
|
|
self.__sshout = ''
|
|
self.ssh = pexpect.spawn("ssh %s" % host)
|
|
self.ssh.sendline("")
|
|
self.ssh.sendline("configure")
|
|
try:
|
|
self.ssh.expect("%s\(config\)# " % self.switch, timeout=20)
|
|
except pexpect.TIMEOUT:
|
|
print "Timeout !"
|
|
import traceback
|
|
traceback.print_exc()
|
|
print "Contenu du buffer :"
|
|
print self.ssh.before
|
|
|
|
def __del__(self) :
|
|
"""Ferme la connexion : envoi logout et attend """
|
|
self.ssh.sendline("logout")
|
|
self.ssh.send("y")
|
|
self.ssh.send("y")
|
|
self.ssh.close()
|
|
|
|
def send_cmd(self,cmd,timeout=15):
|
|
""" Envoi une commande, attend le prompt et retourne la réponse """
|
|
# Envoi de la commande
|
|
self.ssh.sendline(cmd)
|
|
self.__sshout = ''
|
|
|
|
try:
|
|
# Attente de la réponse
|
|
while 1:
|
|
index = self.ssh.expect([' \[y/n\]\? ',
|
|
'%s\(config\)# ' % self.switch,
|
|
'quit: Control-C'],
|
|
timeout=timeout)
|
|
|
|
self.__sshout = self.__sshout + self.ssh.before
|
|
if index == 0:
|
|
# On répond oui
|
|
self.ssh.send("y")
|
|
elif index == 1:
|
|
# On est revenu au prompt
|
|
break
|
|
elif index == 2:
|
|
# On doit continuer, on envoie espace
|
|
self.ssh.send(" ")
|
|
|
|
return self.__sshout
|
|
except pexpect.TIMEOUT:
|
|
print "Timeout !"
|
|
import traceback
|
|
traceback.print_exc()
|
|
print "Contenu du buffer :"
|
|
print self.ssh.before
|
|
|
|
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
|
|
|
|
try:
|
|
self._machine = cl.search("host=%(host)s" % {'host': host})["machineCrans"][0]
|
|
except IndexError:
|
|
raise ValueError(u"Cette machine n'est pas un switch du Cr@ns : %s" % host)
|
|
|
|
# l'engineid du switch n'est que sa mac avec quelques fioritures autour...
|
|
mac = self._machine.mac().replace(':', '')
|
|
self._engineid = '0000000b0000%(mac)s0' % {'mac': mac[:-1]}
|
|
|
|
if version == '1' or version == '2c' :
|
|
self.options = "-v %s -c '%s' %s " % ( version, community, host )
|
|
elif version =='3' :
|
|
self.options = "-v 3 -e %s -u %s -a %s -A '%s' -l authNoPriv" % ( self._engineid, 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 :
|
|
r=r.replace('snmpget: ','')
|
|
raise ConversationError(r,cmd)
|
|
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 get_string(self,oid) :
|
|
""" Retourne le resultat convertit en String correspondant à l'oid demandé. Fonctionne avec les types de depart String, Integer, Hex-String. Raise ValueError sinon. """
|
|
s= self.__exec('snmpget -O v %s %s ' % ( self.options, oid ) )
|
|
if s=="\"\"":
|
|
return ""
|
|
type=s[0:s.find(":")]
|
|
var=s[s.find(":")+2:]
|
|
if type=="STRING":
|
|
return var[1:-1]
|
|
elif type=="Hex-STRING":
|
|
return var.replace(" ","").replace("\n", "").decode("hex")
|
|
elif type=="INTEGER":
|
|
return unicode(var)
|
|
else:
|
|
raise ValueError('Type inconnu')
|
|
|
|
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 :
|
|
if l !="" :
|
|
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
|
|
|
|
# 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 send_cmd(self,cmd,timout=15) :
|
|
""" Envoi une commande par ssh au switch """
|
|
if not self.__conn_ssh :
|
|
self.__conn_ssh = ssh(self.switch)
|
|
|
|
return self.__conn_ssh.send_cmd(cmd,timout)
|
|
|
|
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))
|
|
macs = []
|
|
for value in data.itervalues():
|
|
mac = findall('".*"', value)[0][1:-1]
|
|
if ' ' in mac:
|
|
macs.append(":".join(mac.lower().split(" ")[:-1]))
|
|
else:
|
|
macs.append(":".join("%02x" % ord(digit) for digit in mac))
|
|
return macs
|
|
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
|
|
try:
|
|
data = self.walk('STATISTICS-MIB::hpSwitchPortFdbAddress')
|
|
except ValueError:
|
|
print >> sys.stderr, "Le switch %s fait du caca..." % self.switch
|
|
return None
|
|
|
|
# On cherche dans data la bonne adresse MAC
|
|
for (onesnmp, onemac) in data.iteritems():
|
|
onemac = findall('".*"', onemac)[0][1:-1]
|
|
if ' ' not in onemac:
|
|
onemac = " ".join("%02x" % ord(digit) for digit in mac)
|
|
if onemac.startswith(mac):
|
|
return int(onesnmp.split(".")[1])
|
|
|
|
# On a rien trouvé
|
|
return None
|
|
|
|
def __scp(self,destination,fichier) :
|
|
if self.__debug :
|
|
self.__logDest.write("HP DEBUG : scp(%s,%s,%s)\n" % (fichier, self.switch, destination))
|
|
if exists(fichier):
|
|
system('scp %s %s:%s' % (fichier, self.switch, destination))
|
|
|
|
def update(self,file=None) :
|
|
""" Upload le fichier de config fourni
|
|
Fait rebooter le switch """
|
|
self.__scp('cfg/startup-config',file)
|
|
|
|
def upgrade(self,file=None) :
|
|
""" Changement de firmware,
|
|
le firmware est dans le fichier fourni en argument
|
|
Ne reboote pas le switch """
|
|
self.__scp('os/primary',file)
|
|
|
|
def add_key(self,file=None) :
|
|
""" Ajoute la clef publique ssh aux clefs autorises
|
|
par le switch """
|
|
self.scp('ssh/mgr_keys',file)
|
|
|
|
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(findall(r'Switch 26([0-9]{2})', self.version())[0])
|
|
|
|
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)
|
|
|
|
def vlans(self, prise = None):
|
|
"""Récupère les vlans activés sur la prise 'prise'"""
|
|
if not prise:
|
|
prise = self.prise
|
|
prise = int(prise)
|
|
oid_base = 'SNMPv2-SMI::enterprises.11.2.14.11.5.1.7.1.15.3.1.1'
|
|
oid_format = oid_base + '.%(vlan)d.%(prise)d'
|
|
oids = self.walk(oid_base)
|
|
result = []
|
|
for vlan_name, vlan in vlans.iteritems():
|
|
if oid_format % {'vlan': vlan, 'prise': prise} in oids:
|
|
result.append(vlan_name)
|
|
|
|
return result
|
|
|
|
|
|
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-%i.adm.crans.org' % (bat , int(prise[0]) )
|
|
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)
|
|
|
|
class sw_prise(sw_chbre):
|
|
def __init__(self, prise):
|
|
bat = prise[0].lower()
|
|
prise = prise[1:]
|
|
self.prise_brute = prise
|
|
self.switch = 'bat%s-%i.adm.crans.org' % (bat , int(prise[0]))
|
|
if prise[-1] == '-' :
|
|
#Prise en 10
|
|
self.prise = int(prise[1:-1])
|
|
self.prise10Mb = True
|
|
else :
|
|
self.prise = int(prise[1:])
|
|
self.prise10Mb = False
|
|
|
|
# Config snmp
|
|
self.get, self.set, self.walk = config_snmp_secrete(snmp,self.switch)
|
|
|
|
|
|
if __name__ == '__main__' :
|
|
import sys, getopt, re
|
|
|
|
try :
|
|
options, arg = getopt.getopt(sys.argv[1:], 'U:hc:', [ 'help', 'snmp' ])
|
|
except getopt.error, msg :
|
|
print msg
|
|
sys.exit(1)
|
|
|
|
cmds = []
|
|
firmware=''
|
|
wait=True
|
|
for opt, val in options :
|
|
if opt == '-h' or opt=='--help' :
|
|
print "Usage : %s [[-c commande1] -c commande2...] [-U firmware] [--snmp] regex "
|
|
print "Envoi les commandes données au switchs matchant la regex"
|
|
print "si aucune commande est founie lit l'entree standart"
|
|
print "L'envoi de firmware ne fait pas rebooter le switch"
|
|
print "L'option --snmp ajoute les commandes de reconfiguration snmp"
|
|
sys.exit(0)
|
|
|
|
elif opt=='-c' :
|
|
cmds.append(val)
|
|
|
|
elif opt=='-U' :
|
|
firmware=val
|
|
|
|
elif opt=='--snmp' :
|
|
cmds.append(reconf_snmp)
|
|
cmds.append("write memory")
|
|
|
|
# Quels switchs ?
|
|
switchs=[]
|
|
if arg :
|
|
rexp=re.compile(arg[0])
|
|
for sw in all_switchs() :
|
|
if rexp.match(sw) :
|
|
switchs.append(sw)
|
|
|
|
if not switchs :
|
|
print "Aucun switch trouvé"
|
|
print "Note : il faut une _regex_ (!= wilcards au sens du shell)"
|
|
sys.exit(3)
|
|
|
|
if not cmds and not firmware :
|
|
cmds=map(str.strip,sys.stdin.readlines())
|
|
|
|
# Ce que l'on va faire
|
|
print "Commandes :\n\t", '\n\t'.join(cmds)
|
|
print "\nSwitchs : ", ' '.join(switchs)
|
|
print
|
|
|
|
try:
|
|
raw_input("Appuyer sur entrée pour continuer")
|
|
except EOFError:
|
|
# On lisait depuis un pipe
|
|
print '\r'+' '*33+'\r'
|
|
|
|
for sw in switchs :
|
|
print sw
|
|
try:
|
|
# Au cas ou le switch ne répondrai pas
|
|
s = hpswitch(sw)
|
|
if firmware :
|
|
s.upgrade(firmware)
|
|
for cmd in cmds :
|
|
print s.send_cmd(cmd)
|
|
except :
|
|
print 'ERREUR'
|
|
|