scripts/gestion/hptools.py
Michel Blockelet e9242a36af [gestion/] Kill popen, subprocess ftw
On diminue le nombre de DeprecationWarnings ...

darcs-hash:20110307225857-ddb99-74a1452ba6f828f102e8803de1307e8147314887.gz
2011-03-07 23:58:57 +01:00

525 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 sys import stderr, path
from commands import getstatusoutput
from annuaires_pg 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
if version == '1' or version == '2c' :
self.options = "-v %s -c '%s' %s " % ( version, community, host )
elif version =='3' :
self.fetch_engineid()
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 getBaseMac(self):
''' retourne la Base Mac du switch concerné. C'est elle qui est
utilisée pour l'engineid.'''
switch = cl.search("host=%s" % self.host)["machineCrans"][0]
baseMac = switch.mac().replace(':', '')[0:11] + '0'
return baseMac
def fetch_engineid(self):
self._engineid = '0000000b0000%s' % self.getBaseMac()
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(self.get('SNMPv2-SMI::mib-2.17.1.2.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
for sw in switchs :
print sw
# 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)