diff --git a/gestion/config/snmp.py b/gestion/config/snmp.py new file mode 100644 index 00000000..161fb80b --- /dev/null +++ b/gestion/config/snmp.py @@ -0,0 +1,10 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +PRELOAD_MIBS = ( + "STATISTICS-MIB", + "SNMPv2-SMI", + "SNMPv2-MIB", + "IF-MIB", + "CONFIG-MIB", +) diff --git a/gestion/hptools2/__init__.py b/gestion/hptools2/__init__.py new file mode 100644 index 00000000..b36a6b26 --- /dev/null +++ b/gestion/hptools2/__init__.py @@ -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]) diff --git a/gestion/hptools2/defaults.py b/gestion/hptools2/defaults.py new file mode 100644 index 00000000..e75384de --- /dev/null +++ b/gestion/hptools2/defaults.py @@ -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', +} diff --git a/gestion/hptools2/mac.py b/gestion/hptools2/mac.py new file mode 100644 index 00000000..9100d044 --- /dev/null +++ b/gestion/hptools2/mac.py @@ -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()) + diff --git a/gestion/hptools2/port.py b/gestion/hptools2/port.py new file mode 100644 index 00000000..26b26cbb --- /dev/null +++ b/gestion/hptools2/port.py @@ -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 + diff --git a/gestion/hptools2/snmp.py b/gestion/hptools2/snmp.py new file mode 100644 index 00000000..00a10ef8 --- /dev/null +++ b/gestion/hptools2/snmp.py @@ -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 + diff --git a/gestion/hptools2/switch.py b/gestion/hptools2/switch.py new file mode 100644 index 00000000..4ccbd8b0 --- /dev/null +++ b/gestion/hptools2/switch.py @@ -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() + } + diff --git a/gestion/hptools2/tools.py b/gestion/hptools2/tools.py new file mode 100644 index 00000000..e183201b --- /dev/null +++ b/gestion/hptools2/tools.py @@ -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