models.py 20.4 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
43 44
from django.db.models.signals import post_delete
from django.dispatch import receiver
45
from django.core.exceptions import ValidationError
46 47 48
from django.db import IntegrityError
from django.db import transaction
from reversion import revisions as reversion
chirac's avatar
chirac committed
49

50
from machines.models import Interface
chirac's avatar
chirac committed
51

52
class Stack(models.Model):
53 54
    """Un objet stack. Regrouppe des switchs en foreign key
    ,contient une id de stack, un switch id min et max dans
55
    le stack"""
56 57 58 59 60
    PRETTY_NAME = "Stack de switchs"

    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)
61 62
    member_id_min = models.PositiveIntegerField()
    member_id_max = models.PositiveIntegerField()
63

64 65 66 67 68
    class Meta:
        permissions = (
            ("view_stack", "Peut voir un objet stack"),
        )

69 70 71 72
    def get_instance(stack_id, *args, **kwargs):
        return Stack.objects.get(pk=stack_id)

    def can_create(user_request, *args, **kwargs):
73
        return user_request.has_perm('topologie.add_stack') , u"Vous n'avez pas le droit\
74 75 76
            de créer un stack"

    def can_edit(self, user_request, *args, **kwargs):
77
        if not user_request.has_perm('topologie.change_stack'):
78 79 80
            return False, u"Vous n'avez pas le droit d'éditer des stack"
        return True, None

81
    def can_delete(self, user_request, *args, **kwargs):
82
        if not user_request.has_perm('topologie.delete_stack'):
83 84
            return False, u"Vous n'avez pas le droit de supprimer une stack"
        return True, None
85 86

    def can_view_all(user_request, *args, **kwargs):
87
        if not user_request.has_perm('topologie.view_stack'):
88 89 90 91
            return False, u"Vous n'avez pas le droit de voir une stack"
        return True, None

    def can_view(self, user_request, *args, **kwargs):
92
        if not user_request.has_perm('topologie.view_stack'):
93 94 95
            return False, u"Vous n'avez pas le droit de voir une stack"
        return True, None

96 97 98 99
    def __str__(self):
        return " ".join([self.name, self.stack_id])

    def save(self, *args, **kwargs):
100
        self.clean()
101 102 103 104 105
        if not self.name:
            self.name = self.stack_id
        super(Stack, self).save(*args, **kwargs)

    def clean(self):
106
        """ Verification que l'id_max < id_min"""
107
        if self.member_id_max < self.member_id_min:
chirac's avatar
chirac committed
108
            raise ValidationError({'member_id_max': "L'id maximale est\
109
                inférieure à l'id minimale"})
110

chirac's avatar
chirac committed
111

112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158
class Borne(Interface):
    """Define a wireless AP. Inherit from machines.interfaces
    
    Definition pour une borne wifi , hérite de machines.interfaces
    """
    PRETTY_NAME = "Borne WiFi"

    location = models.CharField(    
        max_length=255,
        help_text="Détails sur la localisation de l'AP",
        blank=True,
        null=True
    )

    class Meta:
        permissions = (
            ("view_borne", "Peut voir une borne"),
        )

    def get_instance(borne_id, *args, **kwargs):
        return Borne.objects.get(pk=borne_id)

    def can_create(user_request, *args, **kwargs):
        return user_request.has_perm('topologie.add_borne') , u"Vous n'avez pas le droit\
            de créer une borne"

    def can_edit(self, user_request, *args, **kwargs):
        if not user_request.has_perm('topologie.change_borne'):
            return False, u"Vous n'avez pas le droit d'éditer des bornes"
        return True, None

    def can_delete(self, user_request, *args, **kwargs):
        if not user_request.has_perm('topologie.delete_borne'):
            return False, u"Vous n'avez pas le droit de supprimer une borne"
        return True, None

    def can_view_all(user_request, *args, **kwargs):
        if not user_request.has_perm('topologie.view_borne'):
            return False, u"Vous n'avez pas le droit de voir les bornes"
        return True, None

    def can_view(self, user_request, *args, **kwargs):
        if not user_request.has_perm('topologie.view_borne'):
            return False, u"Vous n'avez pas le droit de voir les bornes"
        return True, None


chirac's avatar
chirac committed
159
class Switch(models.Model):
160
    """ Definition d'un switch. Contient un nombre de ports (number),
161 162 163
    un emplacement (location), un stack parent (optionnel, stack)
    et un id de membre dans le stack (stack_member_id)
    relié en onetoone à une interface
164
    Pourquoi ne pas avoir fait hériter switch de interface ?
165 166
    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
167 168 169 170
    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"""
171 172
    PRETTY_NAME = "Switch / Commutateur"

173 174 175 176
    switch_interface = models.OneToOneField(
        'machines.Interface',
        on_delete=models.CASCADE
        )
chirac's avatar
chirac committed
177
    location = models.CharField(max_length=255)
178
    number = models.PositiveIntegerField()
chirac's avatar
chirac committed
179
    details = models.CharField(max_length=255, blank=True)
180
    stack = models.ForeignKey(
181
        'topologie.Stack',
182 183 184 185
        blank=True,
        null=True,
        on_delete=models.SET_NULL
        )
186
    stack_member_id = models.PositiveIntegerField(blank=True, null=True)
187 188 189 190 191 192
    model = models.ForeignKey(
        'topologie.ModelSwitch',
        blank=True,
        null=True,
        on_delete=models.SET_NULL
    )
193 194

    class Meta:
195
        unique_together = ('stack', 'stack_member_id')
196 197 198
        permissions = (
            ("view_switch", "Peut voir un objet switch"),
        )
chirac's avatar
chirac committed
199

200 201 202 203
    def get_instance(switch_id, *args, **kwargs):
        return Switch.objects.get(pk=switch_id)

    def can_create(user_request, *args, **kwargs):
204
        return user_request.has_perm('topologie.add_switch') , u"Vous n'avez pas le droit\
205 206 207
            de créer un switch"

    def can_edit(self, user_request, *args, **kwargs):
208
        if not user_request.has_perm('topologie.change_switch'):
209 210 211
            return False, u"Vous n'avez pas le droit d'éditer des switch"
        return True, None

212
    def can_delete(self, user_request, *args, **kwargs):
213
        if not user_request.has_perm('topologie.delete_switch'):
214 215
            return False, u"Vous n'avez pas le droit de supprimer un switch"
        return True, None
216 217

    def can_view_all(user_request, *args, **kwargs):
218
        if not user_request.has_perm('topologie.view_switch'):
219
            return False, u"Vous n'avez pas le droit de voir les switch"
220 221 222
        return True, None

    def can_view(self, user_request, *args, **kwargs):
223
        if not user_request.has_perm('topologie.view_switch'):
224
            return False, u"Vous n'avez pas le droit de voir les switch"
225
        return True, None
226
 
chirac's avatar
chirac committed
227
    def __str__(self):
228
        return self.location + ' ' + str(self.switch_interface)
chirac's avatar
chirac committed
229

230
    def clean(self):
231
        """ Verifie que l'id stack est dans le bon range"""
232 233
        if self.stack is not None:
            if self.stack_member_id is not None:
234
                if (self.stack_member_id > self.stack.member_id_max) or\
chirac's avatar
chirac committed
235 236 237 238 239
                        (self.stack_member_id < self.stack.member_id_min):
                    raise ValidationError(
                        {'stack_member_id': "L'id de ce switch est en\
                            dehors des bornes permises pas la stack"}
                        )
240
            else:
241 242
                raise ValidationError({'stack_member_id': "L'id dans la stack\
                    ne peut être nul"})
243

244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269
    def create_ports(self, begin, end):
        """ Crée les ports de begin à end si les valeurs données sont cohérentes. """

        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:
            raise ValidationError("Port de fin inférieur au port de début !")
        if end - begin > self.number:
            raise ValidationError("Ce switch ne peut avoir autant de ports.")
        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()
                    reversion.set_comment("Création")
            except IntegrityError:
                ValidationError("Création d'un port existant.")
270

chirac's avatar
chirac committed
271

272 273
class ModelSwitch(models.Model):
    """Un modèle (au sens constructeur) de switch"""
Gabriel Detraz's avatar
Gabriel Detraz committed
274
    PRETTY_NAME = "Modèle de switch"
275 276 277 278 279 280
    reference = models.CharField(max_length=255)
    constructor = models.ForeignKey(
        'topologie.ConstructorSwitch',
        on_delete=models.PROTECT
    )

281 282 283 284 285
    class Meta:
        permissions = (
            ("view_modelswitch", "Peut voir un objet modelswitch"),
        )

286 287 288 289
    def get_instance(model_switch_id, *args, **kwargs):
        return ModelSwitch.objects.get(pk=model_switch_id)

    def can_create(user_request, *args, **kwargs):
290
        return user_request.has_perm('topologie.add_modelswitch') , u"Vous n'avez pas le droit\
291 292 293
            de créer un modèle de switch"

    def can_edit(self, user_request, *args, **kwargs):
294
        if not user_request.has_perm('topologie.change_modelswitch'):
295 296 297
            return False, u"Vous n'avez pas le droit d'éditer des modèle de switchs"
        return True, None

298
    def can_delete(self, user_request, *args, **kwargs):
299
        if not user_request.has_perm('topologie.delete_modelswitch'):
300 301
            return False, u"Vous n'avez pas le droit de supprimer un modèle switch"
        return True, None
302 303

    def can_view(self, user_request, *args, **kwargs):
304
        if not user_request.has_perm('topologie.view_modelswitch'):
305 306 307 308
            return False, u"Vous n'avez pas le droit de voir un modèle switch"
        return True, None

    def can_view_all(user_request, *args, **kwargs):
309
        if not user_request.has_perm('topologie.view_modelswitch'):
310 311
            return False, u"Vous n'avez pas le droit de voir un modèle switch"
        return True, None
312
   
313
    def __str__(self):
314
        return str(self.constructor) + ' ' + self.reference
315 316 317 318


class ConstructorSwitch(models.Model):
    """Un constructeur de switch"""
Gabriel Detraz's avatar
Gabriel Detraz committed
319
    PRETTY_NAME = "Constructeur de switch"
320 321
    name = models.CharField(max_length=255)

322 323 324 325 326
    class Meta:
        permissions = (
            ("view_constructorswitch", "Peut voir un objet constructorswitch"),
        )

327 328 329 330
    def get_instance(constructor_switch_id, *args, **kwargs):
        return ConstructorSwitch.objects.get(pk=constructor_switch_id)

    def can_create(user_request, *args, **kwargs):
331
        return user_request.has_perm('topologie.add_constructorswitch') , u"Vous n'avez pas le droit\
332 333 334
            de créer un constructeur de switch"

    def can_edit(self, user_request, *args, **kwargs):
335
        if not user_request.has_perm('topologie.change_constructorswitch'):
336 337 338 339
            return False, u"Vous n'avez pas le droit d'éditer des\
                constructeurs de switchs"
        return True, None

340
    def can_delete(self, user_request, *args, **kwargs):
341
        if not user_request.has_perm('topologie.delete_constructorswitch'):
342 343
            return False, u"Vous n'avez pas le droit de supprimer un constructeur"
        return True, None
344 345

    def can_view_all(user_request, *args, **kwargs):
346
        if not user_request.has_perm('topologie.view_constructorswitch'):
347 348 349 350
            return False, u"Vous n'avez pas le droit de voir un constructeur"
        return True, None

    def can_view(self, user_request, *args, **kwargs):
351
        if not user_request.has_perm('topologie.view_constructorswitch'):
352 353
            return False, u"Vous n'avez pas le droit de voir un constructeur"
        return True, None
354
    
355
    def __str__(self):
356
        return self.name
357 358


chirac's avatar
chirac committed
359
class Port(models.Model):
360
    """ Definition d'un port. Relié à un switch(foreign_key),
361 362 363 364
    un port peut etre relié de manière exclusive à :
    - une chambre (room)
    - une machine (serveur etc) (machine_interface)
    - un autre port (uplink) (related)
365
    Champs supplémentaires :
366
    - RADIUS (mode STRICT : connexion sur port uniquement si machine
367 368
    d'un adhérent à jour de cotisation et que la chambre est également à
    jour de cotisation
369 370 371 372
    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
373
    de forcer un port sur un vlan particulier. S'additionne à la politique
374
    RADIUS"""
375
    PRETTY_NAME = "Port de switch"
376
    STATES = (
377 378 379 380 381 382
        ('NO', 'NO'),
        ('STRICT', 'STRICT'),
        ('BLOQ', 'BLOQ'),
        ('COMMON', 'COMMON'),
        )

383 384 385 386 387
    switch = models.ForeignKey(
        'Switch',
        related_name="ports",
        on_delete=models.CASCADE
    )
388
    port = models.PositiveIntegerField()
389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406
    room = models.ForeignKey(
        'Room',
        on_delete=models.PROTECT,
        blank=True,
        null=True
        )
    machine_interface = models.ForeignKey(
        'machines.Interface',
        on_delete=models.SET_NULL,
        blank=True,
        null=True
        )
    related = models.OneToOneField(
        'self',
        null=True,
        blank=True,
        related_name='related_port'
        )
Gabriel Detraz's avatar
Gabriel Detraz committed
407
    radius = models.CharField(max_length=32, choices=STATES, default='NO')
408 409 410 411 412 413
    vlan_force = models.ForeignKey(
        'machines.Vlan',
        on_delete=models.SET_NULL,
        blank=True,
        null=True
        )
Dalahro's avatar
Dalahro committed
414
    details = models.CharField(max_length=255, blank=True)
chirac's avatar
chirac committed
415 416

    class Meta:
417
        unique_together = ('switch', 'port')
418 419 420
        permissions = (
            ("view_port", "Peut voir un objet port"),
        )
421

422
    def get_instance(port_id, *args, **kwargs):
423 424 425 426 427 428 429
        return Port.objects\
            .select_related('switch__switch_interface__domain__extension')\
            .select_related('machine_interface__domain__extension')\
            .select_related('machine_interface__switch')\
            .select_related('room')\
            .select_related('related')\
            .get(pk=port_id)
430 431

    def can_create(user_request, *args, **kwargs):
432
        return user_request.has_perm('topologie.add_port') , u"Vous n'avez pas le droit\
433 434 435
            de créer un port"

    def can_edit(self, user_request, *args, **kwargs):
436
        if not user_request.has_perm('topologie.change_port'):
437 438 439
            return False, u"Vous n'avez pas le droit d'éditer des ports"
        return True, None

440
    def can_delete(self, user_request, *args, **kwargs):
441
        if not user_request.has_perm('topologie.delete_port'):
442 443
            return False, u"Vous n'avez pas le droit de supprimer un port"
        return True, None
444 445

    def can_view_all(user_request, *args, **kwargs):
446
        if not user_request.has_perm('topologie.view_port'):
447 448 449 450
            return False, u"Vous n'avez pas le droit de voir les ports"
        return True, None

    def can_view(self, user_request, *args, **kwargs):
451
        if not user_request.has_perm('topologie.view_port'):
452 453
            return False, u"Vous n'avez pas le droit de voir les ports"
        return True, None
454
   
455 456 457 458 459
    def make_port_related(self):
        """ Synchronise le port distant sur self"""
        related_port = self.related
        related_port.related = self
        related_port.save()
460

461 462 463 464 465 466
    def clean_port_related(self):
        """ Supprime la relation related sur self"""
        related_port = self.related_port
        related_port.related = None
        related_port.save()

467
    def clean(self):
468 469 470 471
        """ 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
472
        Si un port related point vers self, on nettoie la relation
473 474 475
        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
476 477
        if hasattr(self, 'switch'):
            if self.port > self.switch.number:
chirac's avatar
chirac committed
478 479
                raise ValidationError("Ce port ne peut exister,\
                    numero trop élevé")
480
        if self.room and self.machine_interface or self.room and\
chirac's avatar
chirac committed
481
                self.related or self.machine_interface and self.related:
482 483 484
            raise ValidationError("Chambre, interface et related_port sont\
                mutuellement exclusifs")
        if self.related == self:
485 486 487
            raise ValidationError("On ne peut relier un port à lui même")
        if self.related and not self.related.related:
            if self.related.machine_interface or self.related.room:
488 489
                raise ValidationError("Le port relié est déjà occupé, veuillez\
                    le libérer avant de créer une relation")
490
            else:
491
                self.make_port_related()
492
        elif hasattr(self, 'related_port'):
493
            self.clean_port_related()
chirac's avatar
chirac committed
494 495

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

chirac's avatar
chirac committed
498

chirac's avatar
chirac committed
499
class Room(models.Model):
500
    """Une chambre/local contenant une prise murale"""
501 502
    PRETTY_NAME = "Chambre/ Prise murale"

lhark's avatar
lhark committed
503
    name = models.CharField(max_length=255, unique=True)
chirac's avatar
chirac committed
504
    details = models.CharField(max_length=255, blank=True)
chirac's avatar
chirac committed
505

506 507
    class Meta:
        ordering = ['name']
508 509 510
        permissions = (
            ("view_room", "Peut voir un objet chambre"),
        )
511

512 513 514 515
    def get_instance(room_id, *args, **kwargs):
        return Room.objects.get(pk=room_id)

    def can_create(user_request, *args, **kwargs):
516
        return user_request.has_perm('topologie.add_room') , u"Vous n'avez pas le droit\
517 518 519
            de créer une chambre"

    def can_edit(self, user_request, *args, **kwargs):
520
        if not user_request.has_perm('topologie.change_room'):
521 522 523
            return False, u"Vous n'avez pas le droit d'éditer une chambre"
        return True, None

524
    def can_delete(self, user_request, *args, **kwargs):
525
        if not user_request.has_perm('topologie.delete_room'):
526 527 528
            return False, u"Vous n'avez pas le droit de supprimer une chambre"
        return True, None

529
    def can_view_all(user_request, *args, **kwargs):
530
        if not user_request.has_perm('topologie.view_room'):
531 532 533 534
            return False, u"Vous n'avez pas le droit de voir les chambres"
        return True, None

    def can_view(self, user_request, *args, **kwargs):
535
        if not user_request.has_perm('topologie.view_room'):
536 537 538
            return False, u"Vous n'avez pas le droit de voir les chambres"
        return True, None

chirac's avatar
chirac committed
539
    def __str__(self):
540
        return self.name
chirac's avatar
chirac committed
541

chirac's avatar
chirac committed
542

543 544
@receiver(post_delete, sender=Stack)
def stack_post_delete(sender, **kwargs):
545 546
    """Vide les id des switches membres d'une stack supprimée"""
    Switch.objects.filter(stack=None).update(stack_member_id=None)