switchs2.py 24.4 KB
Newer Older
1
#!/bin/bash /usr/scripts/python.sh
2
# -*- mode: python; coding: utf-8 -*-
3
4
5
6
7
8
9
"""
    Génération de la configuration d'un switch.

    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
10
    pour les reconfiguration copier le fichier de conf
11
12
13
14
15
    dans /cfg/startup-config

    Dans tous les cas FAIRE LE SNMP A LA MAIN (script hptools)
"""

Hamza Dely's avatar
Hamza Dely committed
16
from __future__ import print_function, unicode_literals
17

18
19
import os
import sys
20
21
22
import datetime
import itertools
import argparse
23
24
25
26
from collections import OrderedDict

import netaddr
import jinja2
27

28
import gestion.config as config
29
30
import gestion.secrets_new as secrets
import gestion.annuaires_pg as annuaire
Daniel STAN's avatar
Daniel STAN committed
31
import gestion.config.encoding as enc
32
33
34
35
from gestion.config import hp_switchs
import lc_ldap.objets as ldap_classes
from lc_ldap import crans_utils
from lc_ldap.shortcuts import lc_ldap_admin as make_ldap_conn
36

37
38
39
MIB_PRISE_VLAN = 'SNMPv2-SMI::enterprises.11.2.14.11.5.1.7.1.15.3.1.1'
MIB_PRISE_MAC = 'SNMPv2-SMI::enterprises.11.2.14.11.5.1.9.4.2'

40
# Blocs verticaux pour ascii art des prises d'un switch
Daniel STAN's avatar
Daniel STAN committed
41
42
43
START_BLOCK = [u' ', u'┌', u'│', u'│', u'├', u'│', u'│', u'└', u' ']
MIDDLE_BLOCK = [u' ', u'┬', u'│', u'│', u'┼', u'│', u'│', u'┴', u' ']
END_BLOCK = [u' ', u'┐', u'│', u'│', u'┤', u'│', u'│', u'┘', u' ']
44

45
46
ldap = make_ldap_conn()

47
48
49
50
51
52
53
54
## Paramètres de configuration

# VLANs disponibles
AVAILABLE_VLANS = OrderedDict([
    ('adherent', {
        'id' : config.vlans['adherent'],
        'dhcp_snooping' : True,
        'igmp_snooping' : True,
55
        'mld_snooping' : True,
56
57
58
59
        'network' : {
            'IPv4' : netaddr.IPNetwork(config.NETs['serveurs'][0]),
            'IPv6' : netaddr.IPNetwork(config.prefix['serveurs'][0]),
        },
60
61
62
63
    }),
    ('adm', {
        'id' : config.vlans['adm'],
        'dhcp_snooping' : False,
64
65
66
67
        'network' : {
            'IPv4' : netaddr.IPNetwork(config.NETs['adm'][0]),
            'IPv6' : netaddr.IPNetwork(config.prefix['adm'][0]),
        },
68
69
70
71
    }),
    ('wifi', {
        'id' : config.vlans['wifi'],
        'dhcp_snooping' : True,
72
73
74
75
        'network' : {
            'IPv4' : netaddr.IPNetwork(config.NETs['wifi-serveurs'][0]),
            'IPv6' : netaddr.IPNetwork(config.prefix['wifi'][0]),
        },
76
77
78
79
    }),
    ('switches', {
        'id' : config.vlans['switches'],
        'dhcp_snooping' : False,
80
81
82
83
        'network' : {
            'IPv4' : netaddr.IPNetwork(config.NETs['switches'][0]),
            'IPv6' : netaddr.IPNetwork(config.prefix['switches'][0]),
        },
84
85
    }),
    ('bornes', {
Hamza Dely's avatar
Hamza Dely committed
86
        'id' : config.vlans['v6only'],
87
        'dhcp_snooping' : True,
88
89
90
91
        'network' : {
            'IPv4' : netaddr.IPNetwork(config.NETs['bornes'][0]),
            'IPv6' : netaddr.IPNetwork(config.prefix['bornes'][0]),
        },
92
93
94
95
    }),
    ('accueil', {
        'id' : config.vlans['accueil'],
        'dhcp_snooping' : True,
96
97
98
99
        'network' : {
            'IPv4' : netaddr.IPNetwork(config.NETs['accueil'][0]),
            'IPv6' : None,
        },
100
101
102
103
    }),
    ('isolement', {
        'id' : config.vlans['isolement'],
        'dhcp_snooping' : True,
104
105
106
107
        'network' : {
            'IPv4' : netaddr.IPNetwork(config.NETs['isolement'][0]),
            'IPv6' : None,
        },
108
109
110
    }),
    ('event', {
        'id' : config.vlans['event'],
111
        'dhcp_snooping' : False,
112
113
114
115
        'network' : {
            'IPv4' : netaddr.IPNetwork(config.NETs['evenementiel'][0]),
            'IPv6' : netaddr.IPNetwork(config.prefix['evenementiel'][0]),
        },
116
117
118
119
    }),
    ('appts', {
        'id' : config.vlans['appts'],
        'dhcp_snooping' : True,
120
121
122
123
        'network' : {
            'IPv4' : netaddr.IPNetwork(config.NETs['personnel-ens'][0]),
            'IPv6' : netaddr.IPNetwork(config.prefix['personnel-ens'][0]),
        },
124
125
126
127
    }),
    ('federez', {
        'id' : config.vlans['federez'],
        'dhcp_snooping' : True,
128
129
130
131
        'network' : {
            'IPv4' : netaddr.IPNetwork(config.NETs['federez'][0]),
            'IPv6' : netaddr.IPNetwork(config.prefix['federez'][0]),
        },
132
133
134
135
136
137
138
139
140
141
142
    }),
])

ENABLED_VLANS = [k for k in AVAILABLE_VLANS]

# États possibles d'un port pour l'appartenance à un VLAN
V_TAGGED = "tagged"
V_UNTAGGED = "untagged"
V_FORBID = "forbid"
V_NO = "no"

143
144
145
146
147
148
# États possibles pour la priorité d'un port PoE
POE_CRITICAL = "critical"
POE_HIGH = "high"
POE_LOW = "low"
POE_DISABLED = ""

149
150
151
152
153
154
155
156
157
158
# Nombre maximum de VLANs
MAX_VLAN_NUMBER = 64

# Nombre maximal de MACs par prise
MAX_MAC_NUMBER = {
    'adherent' : 2,
    'club' : 6,
}

# RIDs des serveurs RADIUS
159
160
161
162
RADIUS_SERVERS = [
    'radius.switches.crans.org',
    'radius-failover.switches.crans.org',
]
163
164
165
166
167

# RIDs des serveurs DHCP
DHCP_SERVER_RID = [34, 160]

# RIDs des serveurs NTP
168
NTP_SERVERS = ['fy.switches.crans.org']
169
170

# RIDs des serveurs de log
171
LOG_SERVERS = ['thot.switches.crans.org']
172
173

################################################################################
174

175
176
177
178
179
180
181
182
183
184
185
def get_servers(server_list):
    """Renvoie les informations sur les serveurs demandés depuis la base LDAP"""
    return ldap.search("(|%s)" % "".join("(host=%s)" % h for h in server_list))

def ipv4(server):
    """Renvoie l'IPv4 d'un serveur donné sous sa forme LDAPObject"""
    return server['ipHostNumber'][0].value

def ipv6(server):
    """Renvoie l'IPv6 d'un serveur donné sous sa forme LDAPObject"""
    return server['ip6HostNumber'][0].value
186
187
188
189

class Port(object):
    """Un port de switch"""
    num = None
190

191
192
    # : uplink: None ou str
    uplink = None
193

194
195
    # : Liste de serveurs
    servers = None
196

197
198
199
200
201
202
    # : Liste de bornes
    bornes = None

    # : Liste de noms de chambres
    chambres = None

203
204
205
206
207
208
    # : Liste des macs vues
    seen_macs = None

    # : Liste des vlans vus
    seen_vlans = None

209
210
211
    # : Port (Q)SFP(+) ?
    sfp = False

212
213
214
    # : Port PoE ?
    poe = False

215
    # Radius activé
216
    radius_enabled = True
217

218
219
220
221
    # : PoE Activé + Priorité
    poe_enabled = POE_DISABLED

    def __init__(self, num, sfp=False, poe=False):
222
223
224
225
        self.num = num
        self.servers = list()
        self.bornes = list()
        self.chambres = list()
226
227
        self.seen_macs = list()
        self.seen_vlans = list()
228
        self.sfp = sfp
229
        self.poe = poe
230

231
232
233
234
235
236
    def __str__(self):
        if self.uplink:
            return self.uplink
        else:
            labels = []
            if self.servers:
237
238
239
                labels.append(
                    'Srv_%s' % ','.join(s['host'][0].value.split('.', 1)[0] for s in self.servers)
                )
240
            if self.chambres:
241
                labels.append('Ch_%s' % ','.join(self.chambres))
242
            if self.bornes:
243
244
245
                labels.append(
                    'Wifi_%s' % ','.join(b['host'][0].value.split('.', 1)[0] for b in self.bornes)
                )
246
            return ",".join(labels) or "Inconnu"
247

248
249
250
251
    def __int__(self):
        return self.num

    @property
252
253
254
255
256
257
    def brief(self):
        """Description brève de la prise"""
        if self.uplink:
            return unicode(self.uplink).replace(u'uplink->', u'')
        else:
            labels = self.servers + self.bornes
258
259
            labels = [s['host'][0].value.split('.', 1)[0] for s in labels]
            labels += [unicode(c) for c in self.chambres]
260
261
            return u",".join(labels)

262
    @property
263
264
    def speed(self):
        """Full speed or 100Mb ?"""
265
266
267
        if any("cl" in nom for nom in self.chambres):
            return ''
        elif self.uplink or self.servers or self.bornes:
268
            return ''
269
        elif any(adh.get('droits', None) for adh in self.adherents):
270
271
            return ''
        elif self.sfp:
272
            return ''
273
274
        else:
            return 'speed-duplex auto-10-100'
275

276
    @property
277
    def flowcontrol(self):
278
        """Est-ce que le flowcontrol est activé sur ce port ?"""
279
280
281
282
283
        if self.uplink or self.servers:
            return 'no flow-control'
        else:
            return 'flow-control'

284
285
    @property
    def trusted(self):
286
287
288
        """Est-ce une prise que l'on maîtrise ?"""
        return self.uplink or self.servers

289
    @property
290
291
    def radius_auth(self):
        """Doit-on faire de l'auth radius ?"""
292
        return not self.uplink and not self.servers and not self.bornes and self.radius_enabled
293

294
    @property
295
296
297
298
299
    def adherents(self):
        """Adhérents sur la prise"""
        filtre = u'(|%s)' % (''.join('(chbre=%s)' % c for c in self.chambres))
        return ldap.search(filtre)

300
    @property
301
302
303
304
    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
        """
305
306
307
308
309
310
        assert not self.bornes and not self.uplink
        # Si la pièce en question héberge au moins un club, on ajoute un certain
        # nombre de machines supplémentaires *PAR CLUB*, car on suppose que les
        # besoins d'un club sont plus grands que ceux d'un adhérent classique
        return (
            MAX_MAC_NUMBER['adherent']
311
            + MAX_MAC_NUMBER['club']*len([chbre for chbre in self.chambres if "cl" in chbre])
312
        )
313

314
315
316
    @property
    def enable_poe(self):
        """Indique si le PoE doit être actif sur ce port"""
317
        return self.poe and (self.poe_enabled or self.bornes)
318

319
320
321
    @property
    def arp_protect_trust(self):
        """Indique si on active la protection arp.
322
323
        Désactivé sur nos prises ainsi que sur le wifi (because roaming) et celles des clubs"""
        return self.bornes or self.trusted or any("cl" in chbre for chbre in self.chambres)
324

325
326
327
328
329
    @property
    def poe_level(self):
        """Indique la priorité accordée à ce port PoE"""
        return self.poe_enabled

330
331
332
    def vlan_member(self, vlan):
        """Renvoie V_TAGGED, V_UNTAGGED ou V_NO
        suivant le ``vlan`` (str) demandé"""
333
334
335
        # Si la prise est particulière et que un vlan untag est spécifié, on l'utilise (imprimante et autres)
        if any(unicode(AVAILABLE_VLANS[vlan]['id']) == server.get('untagvlan', [None])[0] for server in self.servers):
            return V_UNTAGGED
336
337
        elif any(unicode(AVAILABLE_VLANS[vlan]['id']) == borne.get('untagvlan', [None])[0] for borne in self.bornes):
            return V_UNTAGGED
338
339
        # Sinon, tous les VLANs sont taggués pour nos machines et pour les switches
        elif self.servers or self.uplink:
340
341
342
343
344
            return V_TAGGED
        # Les VLANs de management ne doivent pas sortir sur des prises inconnues
        elif vlan in ['adm', 'switches']:
            return V_NO
        # Dans le cas d'une borne, on ne veut qu'une sélection de VLANs
Hamza Dely's avatar
Hamza Dely committed
345
        elif self.bornes and vlan in ['wifi', 'accueil', 'bornes', 'event', 'federez']:
346
347
348
349
350
351
            return V_TAGGED
        # Ici, soit on a un VLAN non désiré pour une borne, soit une simple chambre d'adhérent
        # Dans le premier cas, on coupe le VLAN, dans le second on laisse RADIUS travailler.
        else:
            return V_NO

352
353
354
355
356
357
358
359
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()
360

361
        sortie = []
362
        groupe = [-99999, -99999]
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
        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)

386
def get_port_dict(switch, features=None):
387
388
389
    """Renvoie le dictionnaire prise->objet Port"""
    # Build ports !
    ports = {}
390
    features = features or {'sfp' : [], 'poe' : []}
391
    for num in xrange(1, 1+int(switch['nombrePrises'][0].value)):
392
        ports[num] = Port(num, sfp=num in features['sfp'], poe=num in features['poe'])
393
394
395
396
397
398
399
400
401
402
403

    bat, sw_num = get_bat_num(unicode(switch['host'][0]))
    # 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, 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)
404

405
    # On remplit les chambres
406
407
    annuaire_no_radius = annuaire.disabled_radius()
    annuaire_poe_on = dict(annuaire.poe_enabled())
408
409
410
411
412
413
414
    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]
415
        # On remplit si radius a été désactivé
416
417
418
        port.radius_enabled = not any(x in annuaire_no_radius for x in port.chambres)
        # On regarde dans la base pour voir si le PoE est autorisé
        port.poe_enabled = annuaire_poe_on.get(bat.upper() + prise, POE_DISABLED)
419
420
421
422
423
424
425

    # 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
426

427
428
429
430
431
432
    return ports


def fill_port_infos(hostname, port_dict):
    """Rajoute des infos sur les ports d'un switch"""

433
    from gestion.hptools import snmp
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
    conn = snmp(hostname, version='1', community='public')

    prise_vlan = conn.walk(MIB_PRISE_VLAN, bin_comp=True)
    for res in prise_vlan:
        res = res.split('.')
        port = int(res[-2])
        vlan = int(res[-3])
        port_dict[port].seen_vlans.append(vlan)

    prise_mac = conn.walk(MIB_PRISE_MAC, bin_comp=True)
    for mib in prise_mac:
        port = int(mib.split('.')[-8])
        mib = mib.split('.')
        mac = ':'.join('%02x' % int(mib[i]) for i in xrange(-7, -1))
        port_dict[port].seen_macs.append(mac)

def check_conf_ldap(hostname):
    """Vérifie la conf du switch, la base ldap et les macs/prises associées"""
    bat, sw_num = get_bat_num(hostname)
453
454
455
    switch = ldap.search(
        u'(&(host=%s.switches.crans.org)(objectClass=switchCrans))' % hostname
    )[0]
456
457
458
459
460
461
462
463

    port_dict = get_port_dict(switch)
    fill_port_infos(hostname, port_dict)

    for port in port_dict.itervalues():
        print("* Checking port %d (%s)" % (port.num, port))
        # Nombres de vlans
        pr_nb_vlans = len(port.seen_vlans)
464
        th_nb_vlans = sum(port.vlan_member(vname) != V_NO for vname in ENABLED_VLANS)
465
        if not th_nb_vlans and port.radius_auth:
466
467
468
469
            th_nb_vlans = 1
        if port.bornes and port.vlan_member('adherent') == V_NO:
            th_nb_vlans += 1
        if th_nb_vlans != pr_nb_vlans:
470
            print("  Wrong vlan count (%d instead of %d)" % (pr_nb_vlans, th_nb_vlans))
471
            print(port.seen_vlans)
472
            print(list(vname for vname in ENABLED_VLANS if port.vlan_member(vname) != V_NO))
473
474
475
476
477

        # Les macs
        if port.uplink:
            if len(port.seen_macs) < 20:
                print("  Uplink but few macs (%d)" % len(port.seen_macs))
478
        elif port.radius_auth: # On vérifie que c'est la bonne chambre
479
480
481
482
483
484
485
            th_prises_set = set()
            for mac in set(port.seen_macs):
                res = ldap.search(u'macAddress=%s' % mac)
                if not res:
                    continue
                chbre = unicode(res[0].proprio().get('chbre', ['EXT'])[0])
                th_prise = chbre[0].lower()
486
487
488
489
490
                try:
                    th_prise += annuaire.chbre_prises(chbre[0], chbre[1:])
                except annuaire.ChbreNotFound:
                    # La chambre est inconnue -> drop
                    continue
491
                th_prises_set.add(th_prise)
492

493
494
495
            pr_prise = bat.lower() + '%d%02d' % (sw_num, port.num)
            if th_prises_set and pr_prise not in th_prises_set:
                print("   Aucune machine de chbre. Candidats: %r" % th_prises_set)
496
            # Check machine sur prise (si pas uplink)
497
            machines = []
498
499
500
501
502
            for mac in set(port.seen_macs):
                res = ldap.search(u'macAddress=%s' % mac)
                if not res:
                    print("  Unknown mac %s" % mac)
                    continue
503
504
                machines += res
            for machine in machines:
505
506
507
508
509
510
                owner = machine.proprio()
                if isinstance(owner, ldap_classes.AssociationCrans):
                    the = unicode(machine.get('prise', ['N/A'])[0])
                    the = the.lower()
                    pra = bat.lower() + '%d%02d' % (sw_num, port.num)
                    if the != pra:
511
512
513
                        print(
                            "   Machine %s sur mauvaise prise (%s,%s)" % (machine, the, pra)
                        )
514
                        fix_prise(machine, pra)
515
516
517
518

                elif isinstance(machine, ldap_classes.machineWifi):
                    if not port.bornes:
                        print("   Machine %s sur prise sans borne ?" % machine)
519
520
521
522
523
524

def fix_prise(machine, prise):
    """Répare la base en remplaçant la prise de la machine par ce qui est
    conseillé en paramètre"""
    opt = "yN"
    old_prise = unicode(machine.get('prise', ['N/A'])[0])
525
526
527
528
    print(
        "Remplacer prise de %s par %s (ancienne valeur: %s) ?" % (machine, prise, old_prise),
        "[%s]" % opt
    )
529
530
531
532
533
534
535
536
537
538
539
540
541
542
    while True:
        char = raw_input()
        if char in opt.lower():
            break
        print("[%s]" % opt)
    if char == 'y':
        if 'w' not in machine.mode:
            machine = ldap.search(u'%s' % machine.dn.split(',')[0], mode='rw')[0]
        with machine:
            machine['prise'] = unicode(prise)
            machine.history_gen()
            machine.save()
            print("Done !")

543
544
545
546
547
def format_prises_group(data, first, last):
    """Affiche sous forme d'un groupe de prise, en ascii-art, les noms des
    prises. Entre first et last"""
    first = (first-1)/2*2+1
    last = (-last/2)*-2
548

Daniel STAN's avatar
Daniel STAN committed
549
    def align5(txt, right=False):
550
551
552
        """Aligne le texte en limitant à 5 char"""
        if len(txt) > 5:
            return txt[0:4] + u'*'
Daniel STAN's avatar
Daniel STAN committed
553
554
        if right:
            return (5-len(txt))*u' ' + txt
555
556
557
558
559
560
561
562
563
564
        return txt + (5-len(txt))*u' '

    def align2x5(txt):
        """Aligne le texte sur deux lignes de 5 char"""
        return (align5(txt[0:5]), align5(txt[5:]))

    lines = list(START_BLOCK)
    def fill_prise(prise, i_info, i_prise):
        """Remplis le contenu d'une prise, sur deux lignes"""
        if prise in data:
Hamza Dely's avatar
Hamza Dely committed
565
            txt = data[prise].brief
566
567
568
569
570
        else:
            txt = u""
        (txt1, txt2) = align2x5(txt)
        lines[i_info] += txt1
        lines[i_info+1] += txt2
571
        lines[i_prise] += align5(u"%d" % prise, right=(i_prise != 0))
572
573
574
575
576
577
578
579
580

    sep = MIDDLE_BLOCK
    for prise in xrange(first, last, 2):
        for li in [1, 4, 7]:
            lines[li] += u'─'*5
        fill_prise(prise, 2, 0)
        fill_prise(prise+1, -4, -1)
        if prise == last -1:
            sep = END_BLOCK
Daniel STAN's avatar
Daniel STAN committed
581
        for line_i in xrange(0, 9):
582
583
584
585
586
587
            lines[line_i] += sep[line_i]

    return lines

def pretty_print(hostname):
    """Affiche joliement le plan de connexion d'un switch"""
588
589
590
    switch = ldap.search(
        u'(&(host=%s.switches.crans.org)(objectClass=switchCrans))' % hostname
    )[0]
591
592
593
594
595
596

    port_dict = get_port_dict(switch)
    total = max(port_dict.keys())

    first = 1
    while first < total:
Daniel STAN's avatar
Daniel STAN committed
597
598
599
        for line in format_prises_group(port_dict, first, min(first+60, total)):
            print(line.encode(enc.out_encoding))
        first += 61
600

601
602
603
604
605
def get_bat_num(hostname):
    """Renvoie un tuple (bat, num) où bat est la lettre du bâtiment et
    num l'entier numéro du switch"""
    return (hostname[3].lower(), int(hostname[5]))

606
607
608
609
610
611
612
613
614
615
616
617
618
def get_switch_features(switch, model=None):
    """Renvoie le modèle et les fonctionnalités supportées par
    le switch donné. 'model' permet de forcer le modèle du switch si
    besoin est"""
    if 'info' in switch.keys() and not model:
        header = [com for com in switch['info'] if com.value.startswith(';')] or ['']
        model = header[0].split(' ', 2)[1][:-1]

    if not model or model not in hp_switchs.HP_PROCURVE_MAP:
        sys.exit(u'Impossible de déterminer le modèle du switch %s (%s)' % (switch, model))
    else:
        hp_switchs.HP_PROCURVE_MAP[model].setdefault('modules', [])
        hp_switchs.HP_PROCURVE_MAP[model].setdefault('sfp', [])
619
        hp_switchs.HP_PROCURVE_MAP[model].setdefault('poe', [])
620
621
622
        return (model, hp_switchs.HP_PROCURVE_MAP[model])

def conf_switch(hostname, model=None, ieee8021X=False):
623
    """Affiche la configuration d'un switch"""
624
    bat, sw_num = get_bat_num(hostname)
625

626
627
628
629
630
    switch = ldap.search(
        u'(&(host=%s.switches.crans.org)(objectClass=switchCrans))' % hostname
    )[0]
    model, features = get_switch_features(switch, model=model)
    ports_list = PortList(get_port_dict(switch, features=features).itervalues())
631

632
    tpl_env = jinja2.Environment(loader=jinja2.FileSystemLoader(os.path.dirname(__file__)))
633
    tpl_env.filters.update({
634
635
        'ipv4' : ipv4,
        'ipv6' : ipv6,
636
        'ip4_of_rid' : crans_utils.ip4_of_rid,
Hamza Dely's avatar
Hamza Dely committed
637
        'ip6_of_mac' : crans_utils.ip6_of_mac,
638
    })
639

640
    context = {
641
        'switch': switch,
642
643
644
645
646
        'model' : model,
        'firmware' : features["firmware"],
        'modules' : features["modules"],
        'features' : features["features"],
        'ports' : ports_list,
647
        'bat': bat.upper(),
648
        'hostname': 'bat%s-%d' % (bat, sw_num),
649
        'date_gen': datetime.datetime.now(),
650
        'vlans' : AVAILABLE_VLANS,
651
        'max_vlans' : MAX_VLAN_NUMBER,
652
        'radius_key': secrets.get('radius_key'),
653
654
655
        'radius_servers' : get_servers(RADIUS_SERVERS),
        'ntp_servers' : get_servers(NTP_SERVERS),
        'log_servers' : get_servers(LOG_SERVERS),
656
657
        'dhcp_server_rid': DHCP_SERVER_RID,
        'ieee8021X' : ieee8021X,
658
659
        'trusted' : PortList(p for p in ports_list if p.trusted),
        'non_trusted' : PortList(p for p in ports_list if not p.trusted),
660
    }
661

662
    context.update(**{f: getattr(hp_switchs, f) for f in hp_switchs.ALL_FEATURES})
663

664
    # On remplit les objets vlans (nom, id, tagged, untagged etc)
665
666
667
668
    for vname in AVAILABLE_VLANS.keys():
        for assign, p in itertools.groupby(ports_list, lambda p: p.vlan_member(vname)):
            context['vlans'][vname].setdefault(assign, PortList())
            context['vlans'][vname][assign].extend(p)
669
670
671
672
        if switch['ipHostNumber'][0].value in (context['vlans'][vname]['network']['IPv4'] or []):
            context['vlans'][vname]['ip'] = switch['ipHostNumber'][0].value
        if switch['ip6HostNumber'][0].value in (context['vlans'][vname]['network']['IPv6'] or []):
            context['vlans'][vname]['ipv6'] = switch['ip6HostNumber'][0].value
673

674
    # On render :
675
    return tpl_env.get_template('switch_conf.tpl').render(**context).encode('utf-8')
676
677

if __name__ == "__main__":
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
    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)")
    parser.add_argument(
        'model', type=unicode, nargs='?', default=None,
        help="Force le modèle du switch pour générer la configuration"
    )
    parser.add_argument(
        '-c', '--check', action='store_true', default=False,
        help="Vérifie la conf par rapport aux macs et vlans "
        "effectivement présents sur le switch"
    )
    parser.add_argument(
        '--pretty', action='store_true', default=False,
        help="Affiche un tableau ascii du plan de connexion du switch"
    )
    parser.add_argument(
        '--ieee8021X', action='store_true', default=False,
        help="Générer une configuration supportant IEEE 802.1X"
    )
697
698

    options = parser.parse_args(sys.argv[1:])
699

700
701
    if options.check:
        check_conf_ldap(options.hostname)
702
703
    elif options.pretty:
        pretty_print(options.hostname)
704
    else:
705
        print(conf_switch(options.hostname, model=options.model, ieee8021X=options.ieee8021X))