Ce serveur Gitlab sera éteint le 30 juin 2020, pensez à migrer vos projets vers les serveurs gitlab-research.centralesupelec.fr et gitlab-student.centralesupelec.fr !

models.py 26.5 KB
Newer Older
1
# -*- mode: python; coding: utf-8 -*-
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
# Re2o est un logiciel d'administration développé initiallement au rezometz. Il
# se veut agnostique au réseau considéré, de manière à être installable en
# quelques clics.
#
# Copyright © 2017  Gabriel Détraz
# Copyright © 2017  Goulven Kermarec
# Copyright © 2017  Augustin Lemesle
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23 24 25 26 27 28 29 30 31 32 33 34 35 36
"""
Definition des modèles de l'application topologie.

On défini les models suivants :

- stack (id, id_min, id_max et nom) regrouppant les switches
- switch : nom, nombre de port, et interface
machine correspondante (mac, ip, etc) (voir machines.models.interface)
- Port: relié à un switch parent par foreign_key, numero du port,
relié de façon exclusive à un autre port, une machine
(serveur ou borne) ou une prise murale
- room : liste des prises murales, nom et commentaire de l'état de
la prise
"""
37

38 39
from __future__ import unicode_literals

40 41
import itertools

chirac's avatar
chirac committed
42
from django.db import models
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
43
from django.db.models.signals import post_save, post_delete
grisel-davy's avatar
grisel-davy committed
44
from django.utils.functional import cached_property
45
from django.dispatch import receiver
46
from django.core.exceptions import ValidationError
47 48
from django.db import IntegrityError
from django.db import transaction
Fernet Laouen's avatar
Fernet Laouen committed
49
from django.utils.translation import ugettext_lazy as _
50
from reversion import revisions as reversion
chirac's avatar
chirac committed
51

52 53 54 55 56
from preferences.models import (
    OptionalTopologie,
    RadiusKey,
    SwitchManagementCred
)
57
from machines.models import Machine, regen
58 59
from re2o.mixins import AclMixin, RevMixin

60

61
class Stack(AclMixin, RevMixin, models.Model):
62 63
    """Un objet stack. Regrouppe des switchs en foreign key
    ,contient une id de stack, un switch id min et max dans
64
    le stack"""
65 66 67 68

    name = models.CharField(max_length=32, blank=True, null=True)
    stack_id = models.CharField(max_length=32, unique=True)
    details = models.CharField(max_length=255, blank=True, null=True)
69 70
    member_id_min = models.PositiveIntegerField()
    member_id_max = models.PositiveIntegerField()
71

72 73
    class Meta:
        permissions = (
74
            ("view_stack", _("Can view a stack object")),
75
        )
76 77
        verbose_name = _("switches stack")
        verbose_name_plural = _("switches stacks")
78

79 80 81 82
    def __str__(self):
        return " ".join([self.name, self.stack_id])

    def save(self, *args, **kwargs):
83
        self.clean()
84 85 86 87 88
        if not self.name:
            self.name = self.stack_id
        super(Stack, self).save(*args, **kwargs)

    def clean(self):
89
        """ Verification que l'id_max < id_min"""
90
        if self.member_id_max < self.member_id_min:
91 92 93 94
            raise ValidationError(
                    {'member_id_max': _("The maximum ID is less than the"
                                        " minimum ID.")}
            )
95

chirac's avatar
chirac committed
96

97
class AccessPoint(AclMixin, Machine):
98
    """Define a wireless AP. Inherit from machines.interfaces
99

100 101 102
    Definition pour une borne wifi , hérite de machines.interfaces
    """

103
    location = models.CharField(
104
        max_length=255,
105
        help_text=_("Details about the AP's location"),
106 107 108 109 110 111
        blank=True,
        null=True
    )

    class Meta:
        permissions = (
112
            ("view_accesspoint", _("Can view an access point object")),
113
        )
114 115
        verbose_name = _("access point")
        verbose_name_plural = _("access points")
116

grisel-davy's avatar
grisel-davy committed
117 118 119 120 121 122 123 124 125 126 127 128 129
    def port(self):
        """Return the queryset of ports for this device"""
        return Port.objects.filter(
            machine_interface__machine=self
        )

    def switch(self):
        """Return the switch where this is plugged"""
        return Switch.objects.filter(
            ports__machine_interface__machine=self
        )

    def building(self):
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
130 131 132 133
        """
        Return the building of the AP/Server (building of the switchs
        connected to...)
        """
grisel-davy's avatar
grisel-davy committed
134 135 136 137 138 139 140 141 142 143 144
        return Building.objects.filter(
            switchbay__switch=self.switch()
        )

    @cached_property
    def short_name(self):
        return str(self.interface_set.first().domain.name)

    @classmethod
    def all_ap_in(cls, building_instance):
        """Get a building as argument, returns all ap of a building"""
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
145 146 147
        return cls.objects.filter(
            interface__port__switch__switchbay__building=building_instance
        )
grisel-davy's avatar
grisel-davy committed
148 149 150 151 152 153

    def __str__(self):
        return str(self.interface_set.first())


class Server(Machine):
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
154 155 156
    """
    Dummy class, to retrieve servers of a building, or get switch of a server
    """
grisel-davy's avatar
grisel-davy committed
157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173

    class Meta:
        proxy = True

    def port(self):
        """Return the queryset of ports for this device"""
        return Port.objects.filter(
            machine_interface__machine=self
        )

    def switch(self):
        """Return the switch where this is plugged"""
        return Switch.objects.filter(
            ports__machine_interface__machine=self
        )

    def building(self):
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
174 175 176 177
        """
        Return the building of the AP/Server
        (building of the switchs connected to...)
        """
grisel-davy's avatar
grisel-davy committed
178 179 180 181 182 183 184 185 186 187 188
        return Building.objects.filter(
            switchbay__switch=self.switch()
        )

    @cached_property
    def short_name(self):
        return str(self.interface_set.first().domain.name)

    @classmethod
    def all_server_in(cls, building_instance):
        """Get a building as argument, returns all server of a building"""
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
189 190 191
        return cls.objects.filter(
            interface__port__switch__switchbay__building=building_instance
        ).exclude(accesspoint__isnull=False)
grisel-davy's avatar
grisel-davy committed
192

193 194 195
    def __str__(self):
        return str(self.interface_set.first())

196

197
class Switch(AclMixin, Machine):
198
    """ Definition d'un switch. Contient un nombre de ports (number),
199 200 201
    un emplacement (location), un stack parent (optionnel, stack)
    et un id de membre dans le stack (stack_member_id)
    relié en onetoone à une interface
202
    Pourquoi ne pas avoir fait hériter switch de interface ?
203 204
    Principalement par méconnaissance de la puissance de cette façon de faire.
    Ceci étant entendu, django crée en interne un onetoone, ce qui a un
205 206 207 208
    effet identique avec ce que l'on fait ici

    Validation au save que l'id du stack est bien dans le range id_min
    id_max de la stack parente"""
209

Gabriel Detraz's avatar
Gabriel Detraz committed
210
    number = models.PositiveIntegerField(
211
        help_text=_("Number of ports")
Gabriel Detraz's avatar
Gabriel Detraz committed
212
    )
213
    stack = models.ForeignKey(
214
        'topologie.Stack',
215 216 217
        blank=True,
        null=True,
        on_delete=models.SET_NULL
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
218
    )
Gabriel Detraz's avatar
Gabriel Detraz committed
219 220
    stack_member_id = models.PositiveIntegerField(
        blank=True,
221
        null=True
Gabriel Detraz's avatar
Gabriel Detraz committed
222
    )
223 224 225 226
    model = models.ForeignKey(
        'topologie.ModelSwitch',
        blank=True,
        null=True,
Gabriel Detraz's avatar
Gabriel Detraz committed
227
        on_delete=models.SET_NULL,
228
        help_text=_("Switch model")
229
    )
230 231 232 233
    switchbay = models.ForeignKey(
        'topologie.SwitchBay',
        blank=True,
        null=True,
Gabriel Detraz's avatar
Gabriel Detraz committed
234
        on_delete=models.SET_NULL,
235
    )
236 237 238 239 240 241 242
    radius_key = models.ForeignKey(
        'preferences.RadiusKey',
        blank=True,
        null=True,
        on_delete=models.PROTECT,
        help_text="Clef radius du switch"
    )
243 244 245 246 247 248 249
    management_creds = models.ForeignKey(
        'preferences.SwitchManagementCred',
        blank=True,
        null=True,
        on_delete=models.PROTECT,
        help_text="Identifiant de management de ce switch"
    )
250 251 252 253
    automatic_provision = models.BooleanField(
        default=False,
        help_text='Provision automatique de ce switch',
    )
254 255

    class Meta:
256
        unique_together = ('stack', 'stack_member_id')
257
        permissions = (
258
            ("view_switch", _("Can view a switch object")),
259
        )
260 261
        verbose_name = _("switch")
        verbose_name_plural = _("switches")
chirac's avatar
chirac committed
262

263
    def clean(self):
Gabriel Detraz's avatar
Gabriel Detraz committed
264 265 266
        """ Verifie que l'id stack est dans le bon range
        Appelle également le clean de la classe parente"""
        super(Switch, self).clean()
267 268
        if self.stack is not None:
            if self.stack_member_id is not None:
269
                if (self.stack_member_id > self.stack.member_id_max) or\
chirac's avatar
chirac committed
270 271
                        (self.stack_member_id < self.stack.member_id_min):
                    raise ValidationError(
272 273 274
                        {'stack_member_id': _("The switch ID exceeds the"
                                              " limits allowed by the stack.")}
                        )
275
            else:
276 277 278 279
                raise ValidationError(
                        {'stack_member_id': _("The stack member ID can't be"
                                              " void.")}
                )
280

281
    def create_ports(self, begin, end):
282 283
        """ Crée les ports de begin à end si les valeurs données
        sont cohérentes. """
284 285 286 287 288 289 290 291 292

        s_begin = s_end = 0
        nb_ports = self.ports.count()
        if nb_ports > 0:
            ports = self.ports.order_by('port').values('port')
            s_begin = ports.first().get('port')
            s_end = ports.last().get('port')

        if end < begin:
293 294
            raise ValidationError(_("The end port is less than the start"
                                    " port."))
295
        if end - begin > self.number:
296
            raise ValidationError(_("This switch can't have that many ports."))
297 298 299 300 301 302 303 304 305
        begin_range = range(begin, s_begin)
        end_range = range(s_end+1, end+1)
        for i in itertools.chain(begin_range, end_range):
            port = Port()
            port.switch = self
            port.port = i
            try:
                with transaction.atomic(), reversion.create_revision():
                    port.save()
306
                    reversion.set_comment(_("Creation"))
307
            except IntegrityError:
308
                ValidationError(_("Creation of an existing port."))
309

310
    def main_interface(self):
311 312 313
        """ Returns the 'main' interface of the switch
        It must the the management interface for that device"""
        switch_iptype = OptionalTopologie.get_cached_value('switchs_ip_type')
314
        if switch_iptype:
315
            return self.interface_set.filter(type__ip_type=switch_iptype).first()
316 317
        return self.interface_set.first()

Gabriel Detraz's avatar
Gabriel Detraz committed
318 319 320
    @cached_property
    def get_name(self):
        return self.name or self.main_interface().domain.name
321 322 323

    @cached_property
    def get_radius_key(self):
324
        """Retourne l'objet de la clef radius de ce switch"""
325 326 327 328
        return self.radius_key or RadiusKey.objects.filter(default_switch=True).first()

    @cached_property
    def get_radius_key_value(self):
329
        """Retourne la valeur en str de la clef radius, none si il n'y en a pas"""
330 331 332 333 334
        if self.get_radius_key:
            return self.get_radius_key.radius_key
        else:
            return None

335 336 337 338 339 340 341 342 343 344 345 346 347
    @cached_property
    def get_management_cred(self):
        """Retourne l'objet des creds de managament de ce switch"""
        return self.management_creds or SwitchManagementCred.objects.filter(default_switch=True).first()

    @cached_property
    def get_management_cred_value(self):
        """Retourne un dict des creds de management du switch"""
        if self.get_management_cred:
            return {'id': self.get_management_cred.management_id, 'pass': self.get_management_cred.management_pass}
        else:
            return None

348 349 350 351 352 353 354 355 356 357 358 359 360 361 362
    @cached_property
    def rest_enabled(self):
        return OptionalTopologie.get_cached_value('switchs_rest_management') or self.automatic_provision

    @cached_property
    def web_management_enabled(self):
        sw_management = OptionalTopologie.get_cached_value('switchs_web_management')
        sw_management_ssl = OptionalTopologie.get_cached_value('switchs_web_management_ssl')
        if sw_management_ssl:
            return "ssl"
        elif sw_management:
            return "plain"
        else:
            return self.automatic_provision

363 364
    @cached_property
    def ipv4(self):
365
        """Return the switch's management ipv4"""
366 367 368 369
        return str(self.main_interface().ipv4)

    @cached_property
    def ipv6(self):
370
        """Returne the switch's management ipv6"""
371
        return str(self.main_interface().ipv6().first())
Gabriel Detraz's avatar
Gabriel Detraz committed
372

373
    @cached_property
374 375 376
    def interfaces_subnet(self):
        """Return dict ip:subnet for all ip of the switch"""
        return dict((str(interface.ipv4), interface.type.ip_type.ip_set_full_info) for interface in self.interface_set.all())
377 378

    @cached_property
379 380 381
    def interfaces6_subnet(self):
        """Return dict ip6:subnet for all ipv6 of the switch"""
        return dict((str(interface.ipv6().first()), interface.type.ip_type.ip6_set_full_info) for interface in self.interface_set.all())
382

383
    def __str__(self):
Gabriel Detraz's avatar
Gabriel Detraz committed
384
        return str(self.get_name)
385

chirac's avatar
chirac committed
386

387
class ModelSwitch(AclMixin, RevMixin, models.Model):
388
    """Un modèle (au sens constructeur) de switch"""
389

390 391 392 393 394
    reference = models.CharField(max_length=255)
    constructor = models.ForeignKey(
        'topologie.ConstructorSwitch',
        on_delete=models.PROTECT
    )
395 396 397 398 399
    firmware = models.CharField(
        max_length=255,
        null=True,
        blank=True
    )
400

401 402
    class Meta:
        permissions = (
403
            ("view_modelswitch", _("Can view a switch model object")),
404
        )
405 406
        verbose_name = _("switch model")
        verbose_name_plural = _("switch models")
407

408
    def __str__(self):
409
        return str(self.constructor) + ' ' + self.reference
410 411


412
class ConstructorSwitch(AclMixin, RevMixin, models.Model):
413
    """Un constructeur de switch"""
414

415 416
    name = models.CharField(max_length=255)

417 418
    class Meta:
        permissions = (
419 420
            ("view_constructorswitch", _("Can view a switch constructor"
                                         " object")),
421
        )
422 423
        verbose_name = _("switch constructor")
        verbose_name_plural = ("switch constructors")
424

425
    def __str__(self):
426
        return self.name
427 428


429 430
class SwitchBay(AclMixin, RevMixin, models.Model):
    """Une baie de brassage"""
431

432 433 434 435 436 437 438 439
    name = models.CharField(max_length=255)
    building = models.ForeignKey(
        'Building',
        on_delete=models.PROTECT
    )
    info = models.CharField(
        max_length=255,
        blank=True,
440
        null=True
441 442 443 444
    )

    class Meta:
        permissions = (
445
            ("view_switchbay", _("Can view a switch bay object")),
446
        )
447 448
        verbose_name = _("switch bay")
        verbose_name_plural = _("switch bays")
449 450 451 452 453 454 455

    def __str__(self):
        return self.name


class Building(AclMixin, RevMixin, models.Model):
    """Un batiment"""
456

457 458 459 460
    name = models.CharField(max_length=255)

    class Meta:
        permissions = (
461
            ("view_building", _("Can view a building object")),
462
        )
463 464
        verbose_name = _("building")
        verbose_name_plural = _("buildings")
465 466 467 468 469

    def __str__(self):
        return self.name


470
class Port(AclMixin, RevMixin, models.Model):
471
    """ Definition d'un port. Relié à un switch(foreign_key),
472 473 474 475
    un port peut etre relié de manière exclusive à :
    - une chambre (room)
    - une machine (serveur etc) (machine_interface)
    - un autre port (uplink) (related)
476
    Champs supplémentaires :
477
    - RADIUS (mode STRICT : connexion sur port uniquement si machine
478 479
    d'un adhérent à jour de cotisation et que la chambre est également à
    jour de cotisation
480 481 482 483
    mode COMMON : vérification uniquement du statut de la machine
    mode NO : accepte toute demande venant du port et place sur le vlan normal
    mode BLOQ : rejet de toute authentification
    - vlan_force : override la politique générale de placement vlan, permet
484
    de forcer un port sur un vlan particulier. S'additionne à la politique
485
    RADIUS"""
486

487 488 489 490 491
    switch = models.ForeignKey(
        'Switch',
        related_name="ports",
        on_delete=models.CASCADE
    )
492
    port = models.PositiveIntegerField()
493 494 495 496 497
    room = models.ForeignKey(
        'Room',
        on_delete=models.PROTECT,
        blank=True,
        null=True
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
498
    )
499 500 501 502 503
    machine_interface = models.ForeignKey(
        'machines.Interface',
        on_delete=models.SET_NULL,
        blank=True,
        null=True
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
504
    )
505 506 507 508 509
    related = models.OneToOneField(
        'self',
        null=True,
        blank=True,
        related_name='related_port'
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
510
    )
511
    custom_profile = models.ForeignKey(
512 513
        'PortProfile',
        on_delete=models.PROTECT,
514 515
        blank=True,
        null=True
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
516
    )
517 518
    state = models.BooleanField(
        default=True,
519
        help_text='Port state Active',
520
        verbose_name=_("Port state Active")
521
    )
Dalahro's avatar
Dalahro committed
522
    details = models.CharField(max_length=255, blank=True)
chirac's avatar
chirac committed
523 524

    class Meta:
525
        unique_together = ('switch', 'port')
526
        permissions = (
527
            ("view_port", _("Can view a port object")),
528
        )
529 530
        verbose_name = _("port")
        verbose_name_plural = _("ports")
531

532
    @cached_property
533 534 535 536 537 538 539 540 541 542 543 544
    def pretty_name(self):
        """More elaborated name for label on switch conf"""
        if self.related:
            return "Uplink : " + self.related.switch.short_name
        elif self.machine_interface:
            return "Machine : " + str(self.machine_interface.domain)
        elif self.room:
            return "Chambre : " + str(self.room)
        else:
            return "Inconnue"

    @cached_property
545
    def get_port_profile(self):
546
        """Return the config profil for this port
547
        :returns: the profile of self (port)"""
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
548 549
        def profile_or_nothing(profile):
            port_profile = PortProfile.objects.filter(
550
                profil_default=profile).first()
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
551 552
            if port_profile:
                return port_profile
553
            else:
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
554
                nothing_profile, _created = PortProfile.objects.get_or_create(
detraz's avatar
detraz committed
555
                    profil_default='nothing',
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
556 557 558 559
                    name='nothing',
                    radius_type='NO'
                )
                return nothing_profile
560

561 562
        if self.custom_profile:
            return self.custom_profile
563
        elif self.related:
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
564
            return profile_or_nothing('uplink')
565
        elif self.machine_interface:
566
            if hasattr(self.machine_interface.machine, 'accesspoint'):
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
567
                return profile_or_nothing('access_point')
568
            else:
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
569
                return profile_or_nothing('asso_machine')
570
        elif self.room:
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
571
            return profile_or_nothing('room')
572
        else:
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
573
            return profile_or_nothing('nothing')
574

575 576 577 578 579 580 581 582 583
    @classmethod
    def get_instance(cls, portid, *_args, **kwargs):
        return (cls.objects
                .select_related('machine_interface__domain__extension')
                .select_related('machine_interface__machine__switch')
                .select_related('room')
                .select_related('related')
                .prefetch_related('switch__interface_set__domain__extension')
                .get(pk=portid))
584

585 586 587 588 589
    def make_port_related(self):
        """ Synchronise le port distant sur self"""
        related_port = self.related
        related_port.related = self
        related_port.save()
590

591 592 593 594 595 596
    def clean_port_related(self):
        """ Supprime la relation related sur self"""
        related_port = self.related_port
        related_port.related = None
        related_port.save()

597
    def clean(self):
598 599 600 601
        """ Verifie que un seul de chambre, interface_parent et related_port
        est rempli. Verifie que le related n'est pas le port lui-même....
        Verifie que le related n'est pas déjà occupé par une machine ou une
        chambre. Si ce n'est pas le cas, applique la relation related
602
        Si un port related point vers self, on nettoie la relation
603 604 605
        A priori pas d'autre solution que de faire ça à la main. A priori
        tout cela est dans un bloc transaction, donc pas de problème de
        cohérence"""
lhark's avatar
lhark committed
606 607
        if hasattr(self, 'switch'):
            if self.port > self.switch.number:
608
                raise ValidationError(
609
                    _("The port can't exist, its number is too great.")
610 611 612 613 614
                )
        if (self.room and self.machine_interface or
                self.room and self.related or
                self.machine_interface and self.related):
            raise ValidationError(
615
                _("Room, interface and related port are mutually exclusive.")
616
            )
617
        if self.related == self:
618
            raise ValidationError(_("A port can't be related to itself."))
619 620
        if self.related and not self.related.related:
            if self.related.machine_interface or self.related.room:
621
                raise ValidationError(
622 623
                    _("The related port is already used, please clear it"
                      " before creating the relation.")
624
                )
625
            else:
626
                self.make_port_related()
627
        elif hasattr(self, 'related_port'):
628
            self.clean_port_related()
chirac's avatar
chirac committed
629 630

    def __str__(self):
chirac's avatar
chirac committed
631
        return str(self.switch) + " - " + str(self.port)
chirac's avatar
chirac committed
632

chirac's avatar
chirac committed
633

634
class Room(AclMixin, RevMixin, models.Model):
635
    """Une chambre/local contenant une prise murale"""
636

lhark's avatar
lhark committed
637
    name = models.CharField(max_length=255, unique=True)
chirac's avatar
chirac committed
638
    details = models.CharField(max_length=255, blank=True)
chirac's avatar
chirac committed
639

640 641
    class Meta:
        ordering = ['name']
642
        permissions = (
643
            ("view_room", _("Can view a room object")),
644
        )
645 646
        verbose_name = _("room")
        verbose_name_plural = _("rooms")
647

chirac's avatar
chirac committed
648
    def __str__(self):
649
        return self.name
chirac's avatar
chirac committed
650

chirac's avatar
chirac committed
651

Gabriel Detraz's avatar
Gabriel Detraz committed
652
class PortProfile(AclMixin, RevMixin, models.Model):
Fernet Laouen's avatar
Fernet Laouen committed
653 654 655 656 657
    """Contains the information of the ports' configuration for a switch"""
    TYPES = (
        ('NO', 'NO'),
        ('802.1X', '802.1X'),
        ('MAC-radius', 'MAC-radius'),
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
658
    )
Fernet Laouen's avatar
Fernet Laouen committed
659 660 661
    MODES = (
        ('STRICT', 'STRICT'),
        ('COMMON', 'COMMON'),
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
662
    )
Gabriel Detraz's avatar
Gabriel Detraz committed
663 664 665 666 667 668 669 670 671
    SPEED = (
        ('10-half', '10-half'),
        ('100-half', '100-half'),
        ('10-full', '10-full'),
        ('100-full', '100-full'),
        ('1000-full', '1000-full'),
        ('auto', 'auto'),
        ('auto-10', 'auto-10'),
        ('auto-100', 'auto-100'),
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
672 673
    )
    PROFIL_DEFAULT = (
Gabriel Detraz's avatar
Gabriel Detraz committed
674
        ('room', 'room'),
675
        ('access_point', 'access_point'),
Gabriel Detraz's avatar
Gabriel Detraz committed
676 677
        ('uplink', 'uplink'),
        ('asso_machine', 'asso_machine'),
678
        ('nothing', 'nothing'),
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
679
    )
Fernet Laouen's avatar
Fernet Laouen committed
680
    name = models.CharField(max_length=255, verbose_name=_("Name"))
Gabriel Detraz's avatar
Gabriel Detraz committed
681 682 683 684 685 686
    profil_default = models.CharField(
        max_length=32,
        choices=PROFIL_DEFAULT,
        blank=True,
        null=True,
        unique=True,
687
        verbose_name=_("Default profile")
Gabriel Detraz's avatar
Gabriel Detraz committed
688
    )
Fernet Laouen's avatar
Fernet Laouen committed
689
    vlan_untagged = models.ForeignKey(
Gabriel Detraz's avatar
Gabriel Detraz committed
690 691 692 693 694 695
        'machines.Vlan',
        related_name='vlan_untagged',
        on_delete=models.SET_NULL,
        blank=True,
        null=True,
        verbose_name=_("VLAN untagged")
Fernet Laouen's avatar
Fernet Laouen committed
696 697
    )
    vlan_tagged = models.ManyToManyField(
Gabriel Detraz's avatar
Gabriel Detraz committed
698 699 700 701 702
        'machines.Vlan',
        related_name='vlan_tagged',
        blank=True,
        verbose_name=_("VLAN(s) tagged")
    )
Fernet Laouen's avatar
Fernet Laouen committed
703
    radius_type = models.CharField(
Gabriel Detraz's avatar
Gabriel Detraz committed
704 705
        max_length=32,
        choices=TYPES,
706 707
        help_text=_("Type of RADIUS authentication : inactive, MAC-address or"
                    " 802.1X"),
Gabriel Detraz's avatar
Gabriel Detraz committed
708
        verbose_name=_("RADIUS type")
Fernet Laouen's avatar
Fernet Laouen committed
709 710
    )
    radius_mode = models.CharField(
Gabriel Detraz's avatar
Gabriel Detraz committed
711 712 713
        max_length=32,
        choices=MODES,
        default='COMMON',
714 715
        help_text=_("In case of MAC-authentication : mode COMMON or STRICT on"
                    " this port"),
Gabriel Detraz's avatar
Gabriel Detraz committed
716 717 718 719 720 721
        verbose_name=_("RADIUS mode")
    )
    speed = models.CharField(
        max_length=32,
        choices=SPEED,
        default='auto',
722
        help_text=_("Port speed limit"),
Gabriel Detraz's avatar
Gabriel Detraz committed
723 724 725 726
    )
    mac_limit = models.IntegerField(
        null=True,
        blank=True,
727 728
        help_text=_("Limit of MAC-address on this port"),
        verbose_name=_("MAC limit")
Gabriel Detraz's avatar
Gabriel Detraz committed
729 730 731
    )
    flow_control = models.BooleanField(
        default=False,
732
        help_text=_("Flow control"),
Gabriel Detraz's avatar
Gabriel Detraz committed
733 734 735
    )
    dhcp_snooping = models.BooleanField(
        default=False,
736 737
        help_text=_("Protect against rogue DHCP"),
        verbose_name=_("DHCP snooping")
Gabriel Detraz's avatar
Gabriel Detraz committed
738 739 740
    )
    dhcpv6_snooping = models.BooleanField(
        default=False,
741 742
        help_text=_("Protect against rogue DHCPv6"),
        verbose_name=_("DHCPv6 snooping")
Gabriel Detraz's avatar
Gabriel Detraz committed
743 744 745
    )
    arp_protect = models.BooleanField(
        default=False,
746 747
        help_text=_("Check if IP adress is DHCP assigned"),
        verbose_name=_("ARP protection")
Fernet Laouen's avatar
Fernet Laouen committed
748
    )
Gabriel Detraz's avatar
Gabriel Detraz committed
749 750
    ra_guard = models.BooleanField(
        default=False,
751 752
        help_text=_("Protect against rogue RA"),
        verbose_name=_("RA guard")
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
753
    )
Gabriel Detraz's avatar
Gabriel Detraz committed
754 755
    loop_protect = models.BooleanField(
        default=False,
756 757
        help_text=_("Protect against loop"),
        verbose_name=_("Loop protection")
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
758
    )
Fernet Laouen's avatar
Fernet Laouen committed
759 760 761

    class Meta:
        permissions = (
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
762
            ("view_port_profile", _("Can view a port profile object")),
Fernet Laouen's avatar
Fernet Laouen committed
763
        )
764 765
        verbose_name = _("port profile")
        verbose_name_plural = _("port profiles")
Fernet Laouen's avatar
Fernet Laouen committed
766

Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
767 768 769 770 771 772 773 774
    security_parameters_fields = [
        'loop_protect',
        'ra_guard',
        'arp_protect',
        'dhcpv6_snooping',
        'dhcp_snooping',
        'flow_control'
    ]
775 776 777

    @cached_property
    def security_parameters_enabled(self):
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
778 779 780 781 782
        return [
            parameter
            for parameter in self.security_parameters_fields
            if getattr(self, parameter)
        ]
783

784 785 786 787
    @cached_property
    def security_parameters_as_str(self):
        return ','.join(self.security_parameters_enabled)

Fernet Laouen's avatar
Fernet Laouen committed
788 789 790 791
    def __str__(self):
        return self.name


792
@receiver(post_save, sender=AccessPoint)
793
def ap_post_save(**_kwargs):
794 795
    """Regeneration des noms des bornes vers le controleur"""
    regen('unifi-ap-names')
grisel-davy's avatar
grisel-davy committed
796
    regen("graph_topo")
797

Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
798

799
@receiver(post_delete, sender=AccessPoint)
800
def ap_post_delete(**_kwargs):
801 802
    """Regeneration des noms des bornes vers le controleur"""
    regen('unifi-ap-names')
grisel-davy's avatar
grisel-davy committed
803
    regen("graph_topo")
804

Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
805

806
@receiver(post_delete, sender=Stack)
807
def stack_post_delete(**_kwargs):
808 809
    """Vide les id des switches membres d'une stack supprimée"""
    Switch.objects.filter(stack=None).update(stack_member_id=None)
grisel-davy's avatar
grisel-davy committed
810

Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
811

grisel-davy's avatar
grisel-davy committed
812 813 814 815
@receiver(post_save, sender=Port)
def port_post_save(**_kwargs):
    regen("graph_topo")

Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
816

grisel-davy's avatar
grisel-davy committed
817 818 819 820
@receiver(post_delete, sender=Port)
def port_post_delete(**_kwargs):
    regen("graph_topo")

Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
821

grisel-davy's avatar
grisel-davy committed
822 823 824 825
@receiver(post_save, sender=ModelSwitch)
def modelswitch_post_save(**_kwargs):
    regen("graph_topo")

Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
826

grisel-davy's avatar
grisel-davy committed
827 828 829 830
@receiver(post_delete, sender=ModelSwitch)
def modelswitch_post_delete(**_kwargs):
    regen("graph_topo")

Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
831

grisel-davy's avatar
grisel-davy committed
832 833 834 835
@receiver(post_save, sender=Building)
def building_post_save(**_kwargs):
    regen("graph_topo")

Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
836

grisel-davy's avatar
grisel-davy committed
837 838 839 840
@receiver(post_delete, sender=Building)
def building_post_delete(**_kwargs):
    regen("graph_topo")

Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
841

grisel-davy's avatar
grisel-davy committed
842 843 844 845
@receiver(post_save, sender=Switch)
def switch_post_save(**_kwargs):
    regen("graph_topo")

Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
846

grisel-davy's avatar
grisel-davy committed
847 848 849
@receiver(post_delete, sender=Switch)
def switch_post_delete(**_kwargs):
    regen("graph_topo")
850