Version plus pythonesque de HPTools. Pleinement fonctionnelle sous jessie.
* Les requêtes de type lecture seule marchent très bien tout court ; * Celles de type écriture sont sans effet sous wheezy. C'est a priori un bug dans python-netsnmp
This commit is contained in:
parent
cd5ae8aaa5
commit
a0f0c80ead
8 changed files with 932 additions and 0 deletions
10
gestion/config/snmp.py
Normal file
10
gestion/config/snmp.py
Normal file
|
@ -0,0 +1,10 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
PRELOAD_MIBS = (
|
||||
"STATISTICS-MIB",
|
||||
"SNMPv2-SMI",
|
||||
"SNMPv2-MIB",
|
||||
"IF-MIB",
|
||||
"CONFIG-MIB",
|
||||
)
|
11
gestion/hptools2/__init__.py
Normal file
11
gestion/hptools2/__init__.py
Normal file
|
@ -0,0 +1,11 @@
|
|||
#!/usr/bin/env python2.7
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
|
||||
from .switch import HPSwitch
|
||||
from .tools import trace_mac
|
||||
|
||||
import gestion.config.snmp as config_snmp
|
||||
|
||||
os.environ["MIBS"] = ":".join([mib for mib in config_snmp.PRELOAD_MIBS])
|
53
gestion/hptools2/defaults.py
Normal file
53
gestion/hptools2/defaults.py
Normal file
|
@ -0,0 +1,53 @@
|
|||
#!/bin/bash /usr/scripts/python.sh
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Ce sont les variables utiles pour les autres scripts du
|
||||
module"""
|
||||
|
||||
OPERSTATUS = {
|
||||
1: 'up',
|
||||
2: 'down',
|
||||
3: 'testing',
|
||||
4: 'unknown',
|
||||
5: 'dormant',
|
||||
6: 'notPresent',
|
||||
7: 'lowerLayerDown',
|
||||
}
|
||||
|
||||
ADMINSTATUS = {
|
||||
1: 'up',
|
||||
2: 'down',
|
||||
3: 'testing',
|
||||
}
|
||||
|
||||
ETHSPEED = {
|
||||
'HD': {
|
||||
0: '5',
|
||||
10: '1',
|
||||
100: '2',
|
||||
1000: '5',
|
||||
},
|
||||
'FD': {
|
||||
0: '5',
|
||||
10: '3',
|
||||
100: '4',
|
||||
1000: '6',
|
||||
},
|
||||
'AUTO': {
|
||||
0: '5',
|
||||
10: '7',
|
||||
100: '8',
|
||||
1000: '9',
|
||||
},
|
||||
}
|
||||
|
||||
REV_ETHSPEED = {
|
||||
'1': '10 Mbs Half Duplex',
|
||||
'2': '100 Mbs Half Duplex',
|
||||
'3': '10 Mbs Full Duplex',
|
||||
'4': '100 Mbs Full Duplex',
|
||||
'6': '1000 Mbs Full Duplex',
|
||||
'5': 'auto',
|
||||
'7': '10 Mbs auto',
|
||||
'8': '100 Mbs auto',
|
||||
'9': '1000 Mbs auto',
|
||||
}
|
79
gestion/hptools2/mac.py
Normal file
79
gestion/hptools2/mac.py
Normal file
|
@ -0,0 +1,79 @@
|
|||
#!/bin/bash /usr/scripts/python.sh
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Contient les outils pour manipuler des adresses MAC
|
||||
dans le module hptools"""
|
||||
|
||||
import binascii
|
||||
import netaddr
|
||||
|
||||
def bin_to_mac(raw):
|
||||
"""Convertit une OctetString en une MAC"""
|
||||
return format_mac(binascii.hexlify(raw))
|
||||
|
||||
def format_mac(raw):
|
||||
"""Formatte la mac en aa:bb:cc:dd:ee:ff"""
|
||||
|
||||
return str(netaddr.EUI(raw)).replace('-', ':').lower()
|
||||
|
||||
class MACFactory(object):
|
||||
"""Factory stockant les macs"""
|
||||
|
||||
__macs = {}
|
||||
|
||||
@classmethod
|
||||
def register_mac(cls, mac, parent=None):
|
||||
"""Enregistre une mac dans la factory et
|
||||
retourne une instance de MACAddress si besoin."""
|
||||
|
||||
if cls.__macs.get(mac, None) is None:
|
||||
cls.__macs[mac] = MACAddress(mac, parent)
|
||||
else:
|
||||
cls.__macs[mac].append_parent(parent)
|
||||
return cls.__macs[mac]
|
||||
|
||||
@classmethod
|
||||
def get_mac(cls, mac):
|
||||
"""Récupère une mac dans la factory"""
|
||||
|
||||
return cls.__macs.get(mac, None)
|
||||
|
||||
@classmethod
|
||||
def get_macs(cls):
|
||||
"""Récupère l'ensemble des MACS de la factory"""
|
||||
|
||||
return cls.__macs
|
||||
|
||||
class MACAddress(object):
|
||||
"""Classe représentant une adresse MAC"""
|
||||
|
||||
def __init__(self, value, parent=None):
|
||||
"""Stocke l'adresse mac quelque part et le parent"""
|
||||
|
||||
self.__value = value
|
||||
if parent is not None:
|
||||
self.__parents = {parent.name() : parent}
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
"""Property pour lire la valeur d'une MAC"""
|
||||
return self.__value
|
||||
|
||||
@property
|
||||
def parents(self):
|
||||
"""Retourne les parents"""
|
||||
return self.__parents
|
||||
|
||||
def append_parent(self, parent):
|
||||
"""Ajoute un parent à la MAC si parent n'est pas None"""
|
||||
|
||||
if parent is not None:
|
||||
if self.__parents.get(parent.name(), None) is None:
|
||||
self.__parents[parent.name()] = parent
|
||||
|
||||
def remove_parent(self, parent):
|
||||
"""Retire le parent référencé à la MAC"""
|
||||
|
||||
if parent is not None:
|
||||
if self.__parents.get(parent.name(), None) is not None:
|
||||
_ = self.__parents.pop(parent.name())
|
||||
|
139
gestion/hptools2/port.py
Normal file
139
gestion/hptools2/port.py
Normal file
|
@ -0,0 +1,139 @@
|
|||
#!/bin/bash /usr/scripts/python.sh
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Contient la définition et les outils pour bosser avec
|
||||
les ports.
|
||||
|
||||
C'est essentiellement une couche d'abstraction, les fonctions
|
||||
utiles sont appelées avec les switches."""
|
||||
|
||||
import netaddr
|
||||
|
||||
from .mac import MACFactory, bin_to_mac
|
||||
|
||||
class HPSwitchPort(object):
|
||||
"""Classe représentant le port d'un switch"""
|
||||
|
||||
def __init__(self, num, parent, ptype=None):
|
||||
"""Permet de lier un port au switch parent."""
|
||||
self.__num = num
|
||||
self.__ptype = ptype
|
||||
self.__parent = parent
|
||||
self.__macs = []
|
||||
self.__multicast = []
|
||||
self.__vlans = []
|
||||
self.__oper = False
|
||||
self.__admin = False
|
||||
self.__alias = None
|
||||
self.__eth = None
|
||||
|
||||
def name(self):
|
||||
"""Retourne le nom du port"""
|
||||
|
||||
return "%s%02d" % (self.__parent.name(), self.__num)
|
||||
|
||||
def get_vlans(self):
|
||||
"""Retourne les vlans du port"""
|
||||
return self.__vlans
|
||||
|
||||
def add_vlan(self, vlan):
|
||||
"""Ajoute le vlan à la liste"""
|
||||
self.__vlans.append(vlan)
|
||||
|
||||
def purge_vlans(self):
|
||||
"""Purge la liste des vlans connus du port"""
|
||||
self.__vlans = []
|
||||
|
||||
def get_eth(self):
|
||||
"""Récupère l'alias du port"""
|
||||
if self.__eth is None:
|
||||
self.__parent.update_eth_speed()
|
||||
return self.__eth
|
||||
|
||||
def set_eth(self, val):
|
||||
"""Affecte le nom"""
|
||||
self.__eth = val
|
||||
|
||||
def get_alias(self):
|
||||
"""Récupère l'alias du port"""
|
||||
if self.__alias is None:
|
||||
self.__parent.update_ports_aliases()
|
||||
return self.__alias
|
||||
|
||||
def set_alias(self, alias):
|
||||
"""Affecte le nom"""
|
||||
self.__alias = alias
|
||||
|
||||
def append_mac(self, mac):
|
||||
"""Ajoute une mac au port"""
|
||||
self.__macs.append(MACFactory.register_mac(bin_to_mac(mac), self))
|
||||
|
||||
def get_macs(self, update=False):
|
||||
"""Récupère les adresses mac depuis le parent"""
|
||||
if not self.__macs or update:
|
||||
# On boucle sur les macs et on les sépare du parent actuel (vu
|
||||
# qu'on va régénérer sa liste de macs).
|
||||
self.flush_macs()
|
||||
|
||||
__ret = self.__parent.client.walk('hpSwitchPortFdbAddress.%d' % (self.__num,))
|
||||
self.__macs = [MACFactory.register_mac(bin_to_mac(ret['val']), self) for ret in __ret]
|
||||
return self.__macs
|
||||
|
||||
def flush_macs(self):
|
||||
"""Vire les macs"""
|
||||
if not self.__macs:
|
||||
return True
|
||||
|
||||
for mac in self.__macs:
|
||||
mac.remove_parent(self)
|
||||
|
||||
self.__macs = []
|
||||
return True
|
||||
|
||||
def append_multicast(self, multi_ip):
|
||||
"""Ajoute l'IP aux multicasts"""
|
||||
self.__multicast.append(netaddr.IPAddress(multi_ip))
|
||||
|
||||
@property
|
||||
def multicast(self):
|
||||
"""Retourne les ip multicast liées au port."""
|
||||
return self.__multicast
|
||||
|
||||
def flush_multicast(self):
|
||||
"""Vire les infos sur le multicast."""
|
||||
self.__multicast = []
|
||||
|
||||
@property
|
||||
def parent(self):
|
||||
"""Property sur __parent"""
|
||||
return self.__parent
|
||||
|
||||
@property
|
||||
def oper(self):
|
||||
"""Retourne l'oper status"""
|
||||
return self.__oper
|
||||
|
||||
@property
|
||||
def admin(self):
|
||||
"""Retourne l'admin status"""
|
||||
return self.__admin
|
||||
|
||||
@admin.setter
|
||||
def admin(self, stat):
|
||||
"""Met à jour l'admin status. Si stat n'est pas bon, met 3 (testing)"""
|
||||
try:
|
||||
stat = int(stat)
|
||||
except TypeError:
|
||||
stat = 3
|
||||
|
||||
self.__admin = stat
|
||||
|
||||
@oper.setter
|
||||
def oper(self, stat):
|
||||
"""Met à jour l'oper status. Si stat n'est pas bon, met 4 (unknown)"""
|
||||
try:
|
||||
stat = int(stat)
|
||||
except TypeError:
|
||||
stat = 4
|
||||
|
||||
self.__oper = stat
|
||||
|
124
gestion/hptools2/snmp.py
Normal file
124
gestion/hptools2/snmp.py
Normal file
|
@ -0,0 +1,124 @@
|
|||
#!/bin/bash /usr/scripts/python.sh
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Ce fichier propose un client snmp basique"""
|
||||
|
||||
import netsnmp
|
||||
import socket
|
||||
|
||||
import gestion.secrets_new as secrets_new
|
||||
|
||||
class SNMPClient(object):
|
||||
"""Classe de base définissant un client SNMP."""
|
||||
|
||||
def __init__(self, host):
|
||||
"""Crée une session pointant vers le serveur SNMP, et
|
||||
peuple les variables utiles."""
|
||||
# Le fait se gérer si c'est .adm.crans.org, .crans.org, ou
|
||||
# si le nom est un fqdn ou pas est du ressort du DNS (dans la
|
||||
# mesure où de toute façon, si on a pas de dns, contacter les
|
||||
# switches dont on doit résoudre l'IP va être tendu).
|
||||
try:
|
||||
self.host = socket.gethostbyname_ex(host)[0]
|
||||
except socket.gaierror:
|
||||
self.host = host
|
||||
|
||||
self.__session = None
|
||||
self.__version3 = False
|
||||
self.__snmp_community = None
|
||||
self.__snmp_version = None
|
||||
self.__snmp_seclevel = None
|
||||
self.__snmp_authprotocol = None
|
||||
self.__snmp_authpassword = None
|
||||
self.__snmp_secname = None
|
||||
self.__snmp_privprotocol = None
|
||||
self.__snmp_privpassword = None
|
||||
|
||||
def __get_session(self, version3=False):
|
||||
"""Crée une session en cas de besoin, en vérifiant qu'une
|
||||
session répondant aux besoins n'existe pas déjà."""
|
||||
if version3 and self.__version3 and self.__session:
|
||||
return self.__session
|
||||
|
||||
if not version3 and not self.__version3 and self.__session:
|
||||
return self.__session
|
||||
|
||||
if version3 and (not self.__version3 or not self.__session):
|
||||
self.__snmp_community = 'private'
|
||||
self.__snmp_version = 3
|
||||
self.__snmp_seclevel = 'authPriv'
|
||||
self.__snmp_authprotocol = 'SHA'
|
||||
self.__snmp_authpassword = secrets_new.get('snmp_authentication_pass')
|
||||
self.__snmp_secname = 'crans'
|
||||
self.__snmp_privprotocol = 'DES'
|
||||
self.__snmp_privpassword = secrets_new.get('snmp_privacy_pass')
|
||||
|
||||
if not version3 and (self.__version3 or not self.__session):
|
||||
self.__snmp_community = 'public'
|
||||
self.__snmp_version = 1
|
||||
self.__snmp_seclevel = 'noAuthNoPriv'
|
||||
self.__snmp_authprotocol = 'DEFAULT'
|
||||
self.__snmp_authpassword = ''
|
||||
self.__snmp_secname = 'initial'
|
||||
self.__snmp_privprotocol = 'DEFAULT'
|
||||
self.__snmp_privpassword = ''
|
||||
|
||||
self.__version3 = version3
|
||||
session = netsnmp.Session(Version=self.__snmp_version, DestHost=self.host,
|
||||
Community=self.__snmp_community, SecLevel=self.__snmp_seclevel,
|
||||
SecName=self.__snmp_secname, PrivProto=self.__snmp_privprotocol,
|
||||
PrivPass=self.__snmp_privpassword, AuthProto=self.__snmp_authprotocol,
|
||||
AuthPass=self.__snmp_authpassword)
|
||||
|
||||
return session
|
||||
|
||||
def walk(self, attribute):
|
||||
"""Fait un walk.
|
||||
|
||||
Exemple:
|
||||
Si je demande hpSwitchPortFdbAddress, le retour contiendra
|
||||
des entrées ayant pour tag hpSwitchPortFdbAddress, pour iid
|
||||
une éventuelle valeur (si pertinent), et pour val la valeur
|
||||
associée."""
|
||||
|
||||
self.__session = self.__get_session()
|
||||
|
||||
# Crée une variable netsnmp exploitable pour walk.
|
||||
__varbind = netsnmp.Varbind(attribute)
|
||||
|
||||
# La stocke dans une liste.
|
||||
__varlist = netsnmp.VarList(__varbind)
|
||||
|
||||
# __varlist est modifiée en place par la méthode walk.
|
||||
_ = self.__session.walk(__varlist)
|
||||
|
||||
return [
|
||||
{
|
||||
'tag': ret.tag,
|
||||
'iid': ret.iid,
|
||||
'val': ret.val,
|
||||
}
|
||||
for ret in __varlist
|
||||
]
|
||||
|
||||
def set(self, list_of_vars):
|
||||
"""Met à jour un attribut"""
|
||||
# On passe en SNMPv3
|
||||
self.__session = self.__get_session(True)
|
||||
|
||||
# On construit la varlist à balancer en SNMP
|
||||
__varlist = [
|
||||
netsnmp.Varbind(
|
||||
tag=res['tag'],
|
||||
iid=res['iid'],
|
||||
val=res['val']
|
||||
)
|
||||
for res in list_of_vars
|
||||
]
|
||||
|
||||
# Oui, c'est moche
|
||||
__varlist = netsnmp.VarList(*__varlist)
|
||||
|
||||
__ret = self.__session.set(__varlist)
|
||||
|
||||
return __ret
|
||||
|
392
gestion/hptools2/switch.py
Normal file
392
gestion/hptools2/switch.py
Normal file
|
@ -0,0 +1,392 @@
|
|||
#!/bin/bash /usr/scripts/python.sh
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Outils principaux pour la description d'un switch"""
|
||||
|
||||
import socket
|
||||
import netaddr
|
||||
|
||||
from .port import HPSwitchPort
|
||||
from .snmp import SNMPClient
|
||||
from .mac import format_mac, MACFactory
|
||||
from .defaults import OPERSTATUS, ADMINSTATUS, ETHSPEED, REV_ETHSPEED
|
||||
|
||||
class HPSwitchFactory(object):
|
||||
"""Factory stockant les switches"""
|
||||
|
||||
switches = {}
|
||||
|
||||
@classmethod
|
||||
def get_switch(cls, switch):
|
||||
"""Récupère un switch dans la factory"""
|
||||
|
||||
return cls.switches.get(switch, None)
|
||||
|
||||
@classmethod
|
||||
def register_switch(cls, switch, switch_object):
|
||||
"""Enregistre un switch dans la factory"""
|
||||
cls.switches[switch] = switch_object
|
||||
|
||||
@classmethod
|
||||
def get_switches(cls):
|
||||
"""Récupère l'ensemble des switches dans la Factory"""
|
||||
return cls.switches
|
||||
|
||||
class HPSwitch(object):
|
||||
"""Classe décrivant un switch HP."""
|
||||
def __new__(cls, switch):
|
||||
"""Vérifie d'abord si un switch n'existe pas déjà.
|
||||
|
||||
L'idée est d'éviter de manipuler en parallèle des objets
|
||||
dont les données deviendraient incohérentes. Donc,
|
||||
lorsqu'on instancie un switch, celui-ci est référencé
|
||||
par son hostname s'il existe, ou par le nom donné sinon."""
|
||||
|
||||
try:
|
||||
__switch = socket.gethostbyname_ex(switch)[0]
|
||||
except socket.gaierror:
|
||||
__switch = switch
|
||||
|
||||
switch_object = HPSwitchFactory.get_switch(__switch)
|
||||
if switch_object is None:
|
||||
switch_object = super(HPSwitch, cls).__new__(cls)
|
||||
HPSwitchFactory.register_switch(switch, switch_object)
|
||||
|
||||
return switch_object
|
||||
|
||||
def __init__(self, switch):
|
||||
"""Récupère le nom, et un client snmp"""
|
||||
|
||||
self.switch = switch
|
||||
self.client = SNMPClient(self.switch)
|
||||
self.ports = {}
|
||||
self.__build_ports_list()
|
||||
|
||||
def version(self):
|
||||
"""Retourne les données relatives à la version du firmware"""
|
||||
|
||||
return self.client.walk('sysDescr')[0]['val']
|
||||
|
||||
def name(self):
|
||||
"""Retourne un nom "standardisé" du switch
|
||||
(aka g0, c4, …)"""
|
||||
|
||||
return self.switch.split(".", 1)[0].replace('bat', '').replace('-', '').lower()
|
||||
|
||||
def __build_ports_list(self):
|
||||
"""Construit via une requête SNMP la liste des ports pour le switch."""
|
||||
|
||||
__ret = self.client.walk('hpSwitchPhysicalPortEntry.2')
|
||||
for entry in __ret:
|
||||
self.ports[int(entry['iid'])] = HPSwitchPort(int(entry['iid']), self, int(entry['val']))
|
||||
|
||||
def flush_port(self, num):
|
||||
"""Vide un port de ses MACs"""
|
||||
|
||||
if self.ports.get(num, None) is not None:
|
||||
self.ports[num].flush_macs()
|
||||
|
||||
def flush_ports(self):
|
||||
"""Vide un port de ses MACs"""
|
||||
|
||||
for port in self.ports.itervalues():
|
||||
port.flush_macs()
|
||||
|
||||
def fetch_all_ports(self):
|
||||
"""Récupère les données depuis les ports du switch, et
|
||||
renvoie ce qui a un intérêt"""
|
||||
|
||||
self.flush_ports()
|
||||
__ret = self.client.walk('hpSwitchPortFdbAddress')
|
||||
__ret = [
|
||||
(int(ret['iid'].split('.')[0]), ret['val'])
|
||||
for ret in __ret
|
||||
]
|
||||
|
||||
return __ret
|
||||
|
||||
def __populate_port(self, num):
|
||||
"""Peuple le port numéro num"""
|
||||
|
||||
if self.ports.get(num, None) is not None:
|
||||
_ = self.ports[num].get_macs(update=True)
|
||||
|
||||
def __populate_all_ports(self):
|
||||
"""Peuple tous les ports."""
|
||||
|
||||
__ret = self.fetch_all_ports()
|
||||
|
||||
for (iid, val) in __ret:
|
||||
if self.ports.get(iid, None) is not None:
|
||||
self.ports[iid].append_mac(val)
|
||||
|
||||
def show_port_macs(self, num, populate=False):
|
||||
"""Affiche les macs d'un port donné.
|
||||
|
||||
Si populate vaut True, fait le populate et affiche le
|
||||
bousin."""
|
||||
|
||||
if populate:
|
||||
self.__populate_port(num)
|
||||
|
||||
return (
|
||||
[
|
||||
mac.value
|
||||
for mac in self.ports[num].get_macs()
|
||||
],
|
||||
self.ports[num].name()
|
||||
)
|
||||
|
||||
def show_ports_macs(self, populate=False):
|
||||
"""Affiche les ports et macs associées.
|
||||
|
||||
Si populate vaut True, fait le populate et affiche le
|
||||
bousin."""
|
||||
|
||||
if populate:
|
||||
self.__populate_all_ports()
|
||||
|
||||
return {
|
||||
port.name(): [
|
||||
mac.value
|
||||
for mac in port.get_macs()
|
||||
]
|
||||
for port in self.ports.itervalues()
|
||||
}
|
||||
|
||||
def find_mac(self, mac, populate=False):
|
||||
"""Cherche une mac sur le switch"""
|
||||
|
||||
mac = format_mac(mac)
|
||||
|
||||
if populate:
|
||||
self.__populate_all_ports()
|
||||
|
||||
# On boucle sur les macs dans la factory
|
||||
__mac = MACFactory.get_mac(mac)
|
||||
if __mac is not None:
|
||||
# On boucle sur les parents (des ports) à la recherche
|
||||
# de ceux qui appartiennent au switch courant.
|
||||
__parents = []
|
||||
for parent in __mac.parents.itervalues():
|
||||
if parent.parent == self:
|
||||
__parents.append(parent)
|
||||
|
||||
# Si on en a trouvé, on les retourne avec la mac.
|
||||
if __parents:
|
||||
return (__mac, __parents)
|
||||
|
||||
return None
|
||||
|
||||
def __flush_multicast(self):
|
||||
"""Vide les infos de multicast sur les ports"""
|
||||
for port in self.ports.itervalues():
|
||||
port.flush_multicast()
|
||||
|
||||
def __update_multicast(self):
|
||||
"""Fait la mise à jour des infos de multicast sur chaque port"""
|
||||
# On commence par vider.
|
||||
self.__flush_multicast()
|
||||
|
||||
# On fait un walk.
|
||||
data = self.client.walk('hpIgmpStatsPortIndex2')
|
||||
|
||||
# Le dico est du format standard. L'ip est au milieu du champ iid, et
|
||||
# le port est dans val.
|
||||
for data_dict in data:
|
||||
# En gros, le champ iid ressemble à 1.239.255.255.255.6, où 1 est un truc
|
||||
# que je connais pas, et 6 le numéro du port.
|
||||
data_ip = ".".join(data_dict['iid'].split('.')[1:5])
|
||||
igmp_ip, igmp_port = netaddr.IPAddress(data_ip), int(data_dict['val'])
|
||||
|
||||
# Y a plus qu'à stocker
|
||||
if self.ports.get(igmp_port, None) is not None:
|
||||
self.ports[igmp_port].append_multicast(igmp_ip)
|
||||
|
||||
def get_multicast(self, multi_ip=None, update=True):
|
||||
"""Permet de récupérer les informations sur les ports
|
||||
pour lesquels le multicast est actif."""
|
||||
__output = {}
|
||||
|
||||
if multi_ip is not None:
|
||||
multi_ip = netaddr.IPAddress(multi_ip)
|
||||
|
||||
# En cas d'update
|
||||
if update:
|
||||
self.__update_multicast()
|
||||
|
||||
# On construit le résultat de façon identique dans le cas
|
||||
# update ou non.
|
||||
for port in self.ports.itervalues():
|
||||
for multicast_ip in port.multicast:
|
||||
__output.setdefault(multicast_ip, []).append(port)
|
||||
|
||||
# On filtre par l'ip si besoin.
|
||||
if multi_ip is not None:
|
||||
__output = __output[multi_ip]
|
||||
|
||||
return __output
|
||||
|
||||
def nb_prises(self):
|
||||
"""Retourne le nombre de prises du switch.
|
||||
On pourrait aussi faire un self.client.walk('mib-2.17.1.2')
|
||||
et récupérer la clef "val" du premier élément de la liste
|
||||
retournée."""
|
||||
|
||||
return len(self.ports)
|
||||
|
||||
def __update_oper_status(self):
|
||||
"""Récupère le statut des ports du switch."""
|
||||
__oper = self.client.walk('ifOperStatus')
|
||||
for dico in __oper:
|
||||
port, state = dico['iid'], dico['val']
|
||||
if self.ports.get(int(port), None) is not None:
|
||||
self.ports[int(port)].oper = state
|
||||
|
||||
def __update_admin_status(self):
|
||||
"""Récupère l'état d'un port du switch."""
|
||||
__admin = self.client.walk('ifAdminStatus')
|
||||
for dico in __admin:
|
||||
port, state = dico['iid'], dico['val']
|
||||
if self.ports.get(int(port), None) is not None:
|
||||
self.ports[int(port)].admin = state
|
||||
|
||||
def is_enabled(self, prise, update=True):
|
||||
"""Vérifie si la prise est activée"""
|
||||
|
||||
if update:
|
||||
self.__update_admin_status()
|
||||
|
||||
if self.ports.get(prise, None) is not None:
|
||||
return ADMINSTATUS[self.ports[prise].admin]
|
||||
else:
|
||||
return {
|
||||
port : ADMINSTATUS[port.admin]
|
||||
for port in self.ports.itervalues()
|
||||
}
|
||||
|
||||
def is_up(self, prise, update=True):
|
||||
"""Vérifie si la prise est allumée actuellement
|
||||
(en gros, s'il y a une mac dessus)"""
|
||||
|
||||
if update:
|
||||
self.__update_oper_status()
|
||||
|
||||
if self.ports.get(prise, None) is not None:
|
||||
return OPERSTATUS[self.ports[prise].oper]
|
||||
else:
|
||||
return {
|
||||
port : OPERSTATUS[port.oper]
|
||||
for port in self.ports.itervalues()
|
||||
}
|
||||
|
||||
def set_enabled(self, prise, enabled=True):
|
||||
"""Met le port à enabled/disabled"""
|
||||
|
||||
if enabled:
|
||||
val = '1'
|
||||
else:
|
||||
val = '2'
|
||||
|
||||
command_dict = {
|
||||
'iid': str(prise),
|
||||
'tag': 'ifAdminStatus',
|
||||
'val': val,
|
||||
}
|
||||
|
||||
return self.client.set([
|
||||
command_dict
|
||||
])
|
||||
|
||||
def toggle_enabled(self, prise):
|
||||
"""Alterne up/down"""
|
||||
|
||||
if self.is_enabled(prise) == ADMINSTATUS[1]:
|
||||
enabled = False
|
||||
else:
|
||||
enabled = True
|
||||
|
||||
return self.set_enabled(prise, enabled)
|
||||
|
||||
def get_port_alias(self, prise):
|
||||
"""Retourne le nom du port"""
|
||||
|
||||
if self.ports.get(prise, None) is None:
|
||||
return ""
|
||||
return self.ports[prise].get_alias()
|
||||
|
||||
def update_ports_aliases(self):
|
||||
"""Récupère les aliases des ports et les affecte"""
|
||||
data = self.client.walk('ifAlias')
|
||||
|
||||
for data_dict in data:
|
||||
if self.ports.get(int(data_dict['iid']), None) is not None:
|
||||
self.ports[int(data_dict['iid'])].set_alias(data_dict['val'])
|
||||
|
||||
def set_port_alias(self, prise, alias):
|
||||
"""Affecte un nom au port"""
|
||||
|
||||
if self.ports.get(prise, None) is not None:
|
||||
self.client.set([
|
||||
{
|
||||
'iid': str(prise),
|
||||
'tag': 'ifAlias',
|
||||
'val': alias,
|
||||
}
|
||||
])
|
||||
self.ports[prise].set_alias(alias)
|
||||
|
||||
def get_eth_speed(self, prise):
|
||||
"""Retourne le nom du port"""
|
||||
|
||||
if self.ports.get(prise, None) is None:
|
||||
return ""
|
||||
return REV_ETHSPEED.get(self.ports[prise].get_eth(), 'unknown')
|
||||
|
||||
def update_eth_speed(self):
|
||||
"""Met à jour la vitesse de tous les ports"""
|
||||
data = self.client.walk('hpSwitchPortFastEtherMode')
|
||||
|
||||
for data_dict in data:
|
||||
if self.ports.get(int(data_dict['iid']), None) is not None:
|
||||
self.ports[int(data_dict['iid'])].set_eth(data_dict['val'])
|
||||
|
||||
def set_eth_speed(self, prise, rate=0, dtype='AUTO'):
|
||||
"""Affecte un nom au port"""
|
||||
|
||||
# On affecte une config spécifique en vitesse
|
||||
if self.ports.get(prise, None) is not None:
|
||||
self.client.set([
|
||||
{
|
||||
'iid': str(prise),
|
||||
'tag': 'hpSwitchPortFastEtherMode',
|
||||
'val': ETHSPEED[dtype][int(rate)],
|
||||
}
|
||||
])
|
||||
self.ports[prise].set_eth(ETHSPEED[dtype][int(rate)])
|
||||
|
||||
def get_vlans(self, prise=None, update=True):
|
||||
"""Récupère les vlans actifs sur une prise"""
|
||||
# Si mise à jour
|
||||
if update:
|
||||
# On fait le ménage
|
||||
for port in self.ports.itervalues():
|
||||
port.purge_vlans()
|
||||
|
||||
# Et on recommence
|
||||
data = self.client.walk('enterprises.11.2.14.11.5.1.7.1.15.3.1.1')
|
||||
|
||||
for data_dict in data:
|
||||
vlan, iid = [int(res) for res in data_dict['iid'].split('.')]
|
||||
if self.ports.get(iid, None) is not None:
|
||||
self.ports[iid].add_vlan(vlan)
|
||||
|
||||
# Si la prise vaut none, on file tout, sinon juste elle.
|
||||
if prise is not None:
|
||||
if self.ports.get(prise, None) is not None:
|
||||
return self.ports[prise].get_vlans()
|
||||
else:
|
||||
return {
|
||||
iid: port.get_vlans()
|
||||
for (iid, port) in self.ports.iteritems()
|
||||
}
|
||||
|
124
gestion/hptools2/tools.py
Normal file
124
gestion/hptools2/tools.py
Normal file
|
@ -0,0 +1,124 @@
|
|||
#!/bin/bash /usr/scripts/python.sh
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Fournit des outils et fonctions appelables au besoin"""
|
||||
|
||||
from gestion import annuaires_pg
|
||||
from multiprocessing import Process, Manager
|
||||
|
||||
from .switch import HPSwitch
|
||||
from .mac import MACFactory, format_mac
|
||||
|
||||
def filter_uplink(switch, stuff):
|
||||
"""Filtre les prises uplink d'un retour.
|
||||
stuff est une liste de la forme
|
||||
[(port_id, mac), ...]"""
|
||||
sortie = []
|
||||
|
||||
# Retourne "batg", "4.adm.crans.org", par exemple.
|
||||
bat, num = switch.split('-', 1)
|
||||
|
||||
# On filtre ce qui n'est pas utile.
|
||||
bat = bat[-1]
|
||||
num = int(num[0])
|
||||
|
||||
# On récupère les infos utiles.
|
||||
uplink = annuaires_pg.uplink_prises[bat]
|
||||
gen_num_prise = 100 * num
|
||||
|
||||
for (port_id, mac) in stuff:
|
||||
num_prise = gen_num_prise + port_id
|
||||
if not num_prise in uplink:
|
||||
sortie.append((port_id, mac))
|
||||
|
||||
return sortie
|
||||
|
||||
# +--------------------------------------------------------+
|
||||
# | Mac Tracking Functions |
|
||||
# +--------------------------------------------------------+
|
||||
"""Ces fonctions servent à tracker une mac sur le réseau.
|
||||
La fonction trace_mac s'occupe de ce travail. Elle utilise
|
||||
la librairie multiprocessing pour spawner un process par
|
||||
switch, et aller récupérer auprès de ceux-ci la liste des
|
||||
MACs connectées, et les ports allant bien.
|
||||
|
||||
Multiprocessing ne mettant pas en place du partage de variable
|
||||
par défaut, les objets retournés le sont via un Manager, dans
|
||||
un dico, sans structure complexe.
|
||||
|
||||
Une solution dans laquelle les switches seraient renvoyés dans
|
||||
leur structure python existe, mais elle est plus coûteuse,
|
||||
et peu utile dans notre cas. (l'overhead engendré par la méthode
|
||||
à base de dicos et régénération dans le processus parent est
|
||||
epsilonesque)"""
|
||||
|
||||
def fetch_all_ports(switch, output):
|
||||
"""Récupère l'ensemble des ports d'un switch, avec les MACS
|
||||
dessus."""
|
||||
|
||||
sw = HPSwitch(switch)
|
||||
# output est un Manager().dict()
|
||||
__stuff = sw.fetch_all_ports()
|
||||
__stuff = filter_uplink(switch, __stuff)
|
||||
output[switch] = __stuff
|
||||
|
||||
def populate_all_switches():
|
||||
"""Remplit l'ensemble des switches avec les MACS qui sont
|
||||
présentes sur leurs ports"""
|
||||
switches = annuaires_pg.all_switchs()
|
||||
hp_switches = {
|
||||
switch : HPSwitch(switch)
|
||||
for switch in switches
|
||||
}
|
||||
processes = {}
|
||||
|
||||
# La sortie des appels de fetch_all_ports sera écrite dans ce dico.
|
||||
# On évitera la concurrence en utilisant le nom du switch comme
|
||||
# séparateur
|
||||
output = Manager().dict()
|
||||
|
||||
# Dans une première boucle, on crée les switches. Et on met
|
||||
# les processes en mode actif.
|
||||
for switch in switches:
|
||||
hp_switches[switch].flush_ports()
|
||||
processes[switch] = Process(target=fetch_all_ports, args=(switch, output), name=switch)
|
||||
processes[switch].start()
|
||||
|
||||
# On fait la jointure des processes dans une seconde
|
||||
# boucle, pour s'assurer que les processes ont bien
|
||||
# tous été lancés avant de commencer à les sonder.
|
||||
for switch in switches:
|
||||
processes[switch].join()
|
||||
|
||||
for switch in switches:
|
||||
if output[switch] is not None:
|
||||
for (iid, val) in output[switch]:
|
||||
if hp_switches[switch].ports.get(iid, None) is not None:
|
||||
hp_switches[switch].ports[iid].append_mac(val)
|
||||
else:
|
||||
print "Output for switch %s is None." % (switch,)
|
||||
|
||||
def trace_mac(mac, in_all_switches=False):
|
||||
"""Cherche une MAC. Si in_all_switches est à True, commence
|
||||
par instancier tous les switches, et à les peupler.
|
||||
|
||||
Cette méthode est assez agressive, il faut l'utiliser avec
|
||||
précaution."""
|
||||
if in_all_switches:
|
||||
populate_all_switches()
|
||||
|
||||
mac = format_mac(mac)
|
||||
|
||||
# On boucle sur les macs dans la factory
|
||||
__mac = MACFactory.get_mac(mac)
|
||||
if __mac is not None:
|
||||
# On boucle sur les parents (des ports) à la recherche
|
||||
# de ceux qui appartiennent au switch courant.
|
||||
__parents = []
|
||||
for parent in __mac.parents.itervalues():
|
||||
__parents.append(parent)
|
||||
|
||||
# Si on en a trouvé, on les retourne avec la mac.
|
||||
if __parents:
|
||||
return (__mac, __parents)
|
||||
|
||||
return None
|
Loading…
Add table
Add a link
Reference in a new issue