switchs2.py : réécriture du script
This commit is contained in:
parent
50948688e8
commit
cbbf060fb2
2 changed files with 448 additions and 0 deletions
345
gestion/gen_confs/switchs2.py
Executable file
345
gestion/gen_confs/switchs2.py
Executable file
|
@ -0,0 +1,345 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Génération de la configuration d'un switch.
|
||||
|
||||
Attention, cette version n'a pas encore été totalement testée.
|
||||
|
||||
procédure de configuration initiale :
|
||||
* mot de passe admin (password manager user-name <username>)
|
||||
* activation du ssh (crypto key generate ssh)
|
||||
* copie fichier de conf
|
||||
pour les reconfiguration copier le fichier de conf
|
||||
dans /cfg/startup-config
|
||||
|
||||
Dans tous les cas FAIRE LE SNMP A LA MAIN (script hptools)
|
||||
"""
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import sys, os
|
||||
import datetime
|
||||
import re
|
||||
import jinja2
|
||||
import itertools
|
||||
from socket import gethostbyname
|
||||
import netaddr
|
||||
import argparse
|
||||
|
||||
sys.path.append('/usr/scripts/')
|
||||
import gestion.secrets_new as secrets
|
||||
from lc_ldap.shortcuts import lc_ldap_readonly as make_ldap_conn
|
||||
import gestion.annuaires_pg as annuaire
|
||||
import gestion.config as config
|
||||
|
||||
capture_model = re.compile(r'\((.*)\)')
|
||||
|
||||
GIGABIT_MODELS = ['J9021A', 'J9145A']
|
||||
|
||||
ldap = make_ldap_conn()
|
||||
|
||||
# états possibles
|
||||
V_TAGGED = 1
|
||||
V_UNTAGGED = 2
|
||||
V_NO = 3
|
||||
|
||||
def vlan_id(name):
|
||||
"""Vlan id of a name (filtre jinja)"""
|
||||
return config.vlans[name]
|
||||
|
||||
def net_of_vlan_name(name):
|
||||
net_name = {
|
||||
'adherent': 'fil',
|
||||
'appts': 'personnel-ens',
|
||||
'v6only': 'gratuit',
|
||||
'wifi': 'bornes',
|
||||
}.get(name, name)
|
||||
return config.NETs[net_name]
|
||||
|
||||
class Port(object):
|
||||
"""Un port de switch"""
|
||||
num = None
|
||||
|
||||
# : uplink: None ou str
|
||||
uplink = None
|
||||
|
||||
# : Liste de serveurs
|
||||
servers = None
|
||||
|
||||
# : Liste de bornes
|
||||
bornes = None
|
||||
|
||||
# : Liste de noms de chambres
|
||||
chambres = None
|
||||
|
||||
def __init__(self, num):
|
||||
self.num = num
|
||||
self.servers = list()
|
||||
self.bornes = list()
|
||||
self.chambres = list()
|
||||
|
||||
def __str__(self):
|
||||
if self.uplink:
|
||||
return self.uplink
|
||||
else:
|
||||
labels = []
|
||||
if self.servers:
|
||||
labels.append('Srv_' +
|
||||
','.join(s['host'][0].value.split('.',1)[0]
|
||||
for s in self.servers))
|
||||
if self.chambres:
|
||||
labels.append('Ch_' + ','.join(self.chambres))
|
||||
if self.bornes:
|
||||
labels.append('Wifi_' +
|
||||
','.join(b['host'][0].value.split('.',1)[0]
|
||||
for b in self.bornes))
|
||||
return ",".join(labels) or "Inconnu"
|
||||
|
||||
def __int__(self):
|
||||
return self.num
|
||||
|
||||
def speed(self):
|
||||
"""Full speed or 100Mb ?"""
|
||||
if any("cl" in nom for nom in self.chambres) or self.uplink or \
|
||||
self.servers:
|
||||
return ''
|
||||
if any( adh['droits'] for adh in self.adherents()):
|
||||
return ''
|
||||
return 'speed-duplex auto-10-100'
|
||||
|
||||
def flowcontrol(self):
|
||||
if self.uplink or self.servers:
|
||||
return 'no flow-control'
|
||||
else:
|
||||
return 'flow-control'
|
||||
|
||||
def is_trusted(self):
|
||||
"""Est-ce une prise que l'on maîtrise ?"""
|
||||
return self.uplink or self.servers
|
||||
|
||||
def vlan_member(self, vlan):
|
||||
"""Renvoie V_TAGGED, V_UNTAGGED ou V_NO
|
||||
suivant le ``vlan`` (str) demandé"""
|
||||
if self.uplink or self.servers:
|
||||
# TODO retirer ce hack dégueux: tous les switchs devraient tout
|
||||
# tagguer, même le vlan adhérent
|
||||
if vlan == 'adherent':
|
||||
return V_UNTAGGED
|
||||
else:
|
||||
return V_TAGGED
|
||||
# Précisons tout de suite qu'adm ne va pas plus loin
|
||||
elif vlan == 'adm':
|
||||
return V_NO
|
||||
elif self.bornes:
|
||||
if vlan in ['wifi', 'accueil', 'isolement', 'v6only', 'appts',
|
||||
'event']:
|
||||
return V_TAGGED
|
||||
# Cas d'une borne dans une chambre: l'adherent doit pouvoir
|
||||
# se connecter
|
||||
elif vlan == 'adherent' and self.chambres:
|
||||
return V_UNTAGGED
|
||||
else:
|
||||
return V_NO
|
||||
# C'est donc une chambre d'adherent sans borne, il y aura
|
||||
# l'auth radius
|
||||
else:
|
||||
return V_NO
|
||||
|
||||
def radius_auth(self):
|
||||
"""Doit-on faire de l'auth radius ?"""
|
||||
return not self.uplink and not self.servers and not self.bornes
|
||||
|
||||
def adherents(self):
|
||||
"""Adhérents sur la prise"""
|
||||
filtre = u'(|%s)' % (''.join('(chbre=%s)' % c for c in self.chambres))
|
||||
return ldap.search(filtre)
|
||||
|
||||
def num_mac(self):
|
||||
"""Renvoie le nombre de macs autorisées.
|
||||
Ne devrait pas être appelée si c'est une prise d'uplink ou de bornes
|
||||
"""
|
||||
assert(not self.bornes and not self.uplink)
|
||||
num = 2
|
||||
# Si c'est un club, on peut supposer qu'il a besoin de beaucoup
|
||||
# de machines (krobot etc)
|
||||
if any("cl" in nom for nom in self.chambres):
|
||||
num += 6
|
||||
else:
|
||||
# Si c'est une chambre d'adhérent, on rajoute le nombre de machines
|
||||
# filaires
|
||||
# TODO cette config serait à faire régulièrement
|
||||
# TODO serait-ce plus rapide de tout chercher d'abord ?
|
||||
for adh in self.adherents():
|
||||
num += len(adh.machines())
|
||||
return num
|
||||
|
||||
class PortList(list):
|
||||
"""Liste de ports"""
|
||||
def __str__(self):
|
||||
""" transforme une liste de prises en une chaine pour le switch
|
||||
exemple : 1, 3, 4, 5, 6, 7, 9, 10, 11, 12 => 1,3-7,9-12
|
||||
"""
|
||||
liste = list(int(x) for x in self)
|
||||
liste.sort()
|
||||
|
||||
sortie = []
|
||||
groupe = [-99999,-99999]
|
||||
for x in itertools.chain(liste, [99999]):
|
||||
if x > groupe[1]+1: # Nouveau groupe !
|
||||
if groupe[0] == groupe[1]:
|
||||
sortie.append('%d' % groupe[1])
|
||||
else:
|
||||
sortie.append('%d-%d' % tuple(groupe))
|
||||
groupe[0] = x
|
||||
groupe[1] = x
|
||||
return ','.join(sortie[1:])
|
||||
|
||||
def filter_vlan(self, vlan):
|
||||
"""Prend un ``vlan`` et renvoie deux PortList, la première
|
||||
avec les ports taggués, la seconde pour les ports untaggués"""
|
||||
tagged = PortList()
|
||||
untagged = PortList()
|
||||
for port in self:
|
||||
assign = port.vlan_member(vlan)
|
||||
if assign == V_TAGGED:
|
||||
tagged.append(port)
|
||||
elif assign == V_UNTAGGED:
|
||||
untagged.append(port)
|
||||
return (tagged, untagged)
|
||||
|
||||
def conf_switch(hostname):
|
||||
"""Affiche la configuration d'un switch"""
|
||||
bat = hostname[3].lower()
|
||||
sw_num = int(hostname[5])
|
||||
|
||||
switch = ldap.search(u'host=bat%s-%d.adm.crans.org' % (bat, sw_num))[0]
|
||||
|
||||
tpl_env = jinja2.Environment(loader=jinja2.FileSystemLoader(os.path.dirname(__file__)))
|
||||
##for info:
|
||||
tpl_env.filters['vlan_id'] = vlan_id
|
||||
|
||||
data = {
|
||||
'switch': switch,
|
||||
'hostname': 'bat%s-%d' % (bat, sw_num),
|
||||
'bat': bat.upper(),
|
||||
|
||||
'date_gen': datetime.datetime.now(),
|
||||
# TODO fill that depuis bcfg2 ou whatever
|
||||
'radius_servers': ['10.231.136.72', '10.231.136.9' ],
|
||||
'radius_key': secrets.get('radius_key'),
|
||||
|
||||
'ntp_servers': ['10.231.136.98'],
|
||||
'log_servers': ['10.231.136.38'],
|
||||
|
||||
# dhcp et isc (secondaire) sont les deux seuls serveurs
|
||||
'dhcp_rid_servers': [34, 160],
|
||||
|
||||
# vlans activés (cf data.vlans)
|
||||
'vlan_names': ['adherent', 'adm', 'wifi', 'v6only', 'accueil',
|
||||
'isolement', 'appts', 'event'],
|
||||
|
||||
# réseaux où on fait du dhcp snooping (cf data.NETs)
|
||||
'dhcp_snooping_vlan_names': ['adherent', 'wifi', 'accueil',
|
||||
'isolement', 'v6only', 'appts'],
|
||||
}
|
||||
|
||||
for com in switch['info']:
|
||||
if com.value.startswith(';'):
|
||||
data['data_header'] = com.value.encode('utf-8')
|
||||
break
|
||||
else:
|
||||
print(((u'Impossible de déterminer le header à utiliser pour %s;' +
|
||||
u"Utilisation d'une valeur par défaut (remplir ldap)")
|
||||
% switch).encode('utf-8'), file=sys.stderr)
|
||||
data['config_header']= '; J4899A Configuration Editor; Created on release #H.10.50'
|
||||
|
||||
imodel = data['data_header'].split(' ', 2)[1]
|
||||
if imodel == "J9145A":
|
||||
data['module_type'] = 'module 1 type J9145A'
|
||||
|
||||
# Pas de snooping pour les 2810
|
||||
if "2810" in switch['info'][0].value:
|
||||
data['dhcp_snooping_vlan_names'] = []
|
||||
else:
|
||||
data['dhcp_servers'] = []
|
||||
for vname in data['dhcp_snooping_vlan_names']:
|
||||
for rid in data['dhcp_rid_servers']:
|
||||
first = netaddr.IPNetwork(net_of_vlan_name(vname)[0]).first
|
||||
data['dhcp_servers'].append(str(netaddr.IPAddress(first + rid)))
|
||||
|
||||
# Switch avec des ports gigabit uniquement
|
||||
if imodel in GIGABIT_MODELS:
|
||||
data['gigabit'] = True
|
||||
|
||||
# Build ports !
|
||||
ports = {}
|
||||
for x in xrange(1, 1+int(switch['nombrePrises'][0].value)):
|
||||
ports[x] = Port(x)
|
||||
|
||||
# Remplit les machines ayant une prise spécifiée
|
||||
# (normalement uniquement les serveurs et les bornes)
|
||||
for machine in ldap.search(u'prise=%s%i*' % (bat.upper(), sw_num)):
|
||||
port = ports[int(machine['prise'][0].value[2:])]
|
||||
classe = machine['objectClass'][0].value
|
||||
if classe == 'machineCrans':
|
||||
port.servers.append(machine)
|
||||
elif classe == 'borneWifi':
|
||||
port.bornes.append(machine)
|
||||
|
||||
# On remplit les chambres
|
||||
for prise, chbres in annuaire.reverse(bat).iteritems():
|
||||
# TODO rajouter un arg à reverse
|
||||
if int(prise[0]) != int(sw_num):
|
||||
continue
|
||||
port = ports[int(prise[1:])]
|
||||
# (below) beware: type(num) == str (ex: 302g)
|
||||
port.chambres += [bat.upper() + num for num in chbres]
|
||||
|
||||
# Remplit les uplinks
|
||||
for num_prise, label in annuaire.uplink_prises[bat].iteritems():
|
||||
if num_prise/100 != int(sw_num):
|
||||
continue
|
||||
port = ports[num_prise % 100]
|
||||
port.uplink = label
|
||||
|
||||
ports_list = PortList(ports.itervalues())
|
||||
data['ports'] = ports_list
|
||||
data['vlans'] = []
|
||||
# On remplit les objets vlans (nom, id, tagged, untagged etc)
|
||||
vlans = {}
|
||||
for name in data['vlan_names']:
|
||||
vlan = {
|
||||
'name': name,
|
||||
'id': config.vlans[name],
|
||||
}
|
||||
for assign, p in itertools.groupby(ports_list, lambda p: p.vlan_member(name)):
|
||||
attr = {
|
||||
V_TAGGED: 'tagged',
|
||||
V_UNTAGGED: 'untagged',
|
||||
V_NO: 'no'}[assign]
|
||||
vlan.setdefault(attr, PortList())
|
||||
vlan[attr].extend(p)
|
||||
if name == 'adm':
|
||||
vlan['ip_cfg'] = (gethostbyname(hostname), '255.255.255.0')
|
||||
if name == 'adherent':
|
||||
# igmp snooping (multicast) mais nous ne sommes pas querier
|
||||
vlan['extra'] = 'ip igmp\nno ip igmp querier'
|
||||
vlans[name] = vlan
|
||||
data['vlans'].append(vlan)
|
||||
|
||||
# Quelles sont les prises de confiance ?
|
||||
data['trusted'] = str(PortList(p for p in ports_list if p.is_trusted()))
|
||||
data['non_trusted'] = str(PortList(p for p in ports_list if not p.is_trusted()))
|
||||
|
||||
# On render :
|
||||
return tpl_env.get_template('switch_conf.tpl').render(**data).encode('utf-8')
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="Génération de la conf d'un "+
|
||||
"switch.")
|
||||
parser.add_argument('hostname', help="Nom du switch à regénérer " +
|
||||
"(ex: batg-4)")
|
||||
|
||||
options = parser.parse_args(sys.argv[1:])
|
||||
|
||||
print(conf_switch(options.hostname))
|
Loading…
Add table
Add a link
Reference in a new issue