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 8.78 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

chirac's avatar
chirac committed
40
from django.db import models
41 42
from django.db.models.signals import post_delete
from django.dispatch import receiver
43
from django.core.exceptions import ValidationError
chirac's avatar
chirac committed
44

45
class Stack(models.Model):
46 47
    """Un objet stack. Regrouppe des switchs en foreign key
    ,contient une id de stack, un switch id min et max dans
48
    le stack"""
49 50 51 52 53 54 55 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)
    member_id_min = models.IntegerField()
    member_id_max = models.IntegerField()

    def __str__(self):
        return " ".join([self.name, self.stack_id])

    def save(self, *args, **kwargs):
61
        self.clean()
62 63 64 65 66
        if not self.name:
            self.name = self.stack_id
        super(Stack, self).save(*args, **kwargs)

    def clean(self):
67
        """ Verification que l'id_max < id_min"""
68
        if self.member_id_max < self.member_id_min:
69 70
            raise ValidationError({'member_id_max':"L'id maximale est\
                inférieure à l'id minimale"})
71

chirac's avatar
chirac committed
72
class Switch(models.Model):
73
    """ Definition d'un switch. Contient un nombre de ports (number),
74 75 76
    un emplacement (location), un stack parent (optionnel, stack)
    et un id de membre dans le stack (stack_member_id)
    relié en onetoone à une interface
77
    Pourquoi ne pas avoir fait hériter switch de interface ?
78 79
    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
80 81 82 83
    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"""
84 85
    PRETTY_NAME = "Switch / Commutateur"

86 87 88 89
    switch_interface = models.OneToOneField(
        'machines.Interface',
        on_delete=models.CASCADE
        )
chirac's avatar
chirac committed
90
    location = models.CharField(max_length=255)
chirac's avatar
chirac committed
91
    number = models.IntegerField()
chirac's avatar
chirac committed
92
    details = models.CharField(max_length=255, blank=True)
93 94 95 96 97 98
    stack = models.ForeignKey(
        Stack,
        blank=True,
        null=True,
        on_delete=models.SET_NULL
        )
99 100 101
    stack_member_id = models.IntegerField(blank=True, null=True)

    class Meta:
102
        unique_together = ('stack', 'stack_member_id')
chirac's avatar
chirac committed
103 104

    def __str__(self):
105
        return str(self.location) + ' ' + str(self.switch_interface)
chirac's avatar
chirac committed
106

107
    def clean(self):
108
        """ Verifie que l'id stack est dans le bon range"""
109 110
        if self.stack is not None:
            if self.stack_member_id is not None:
111 112 113 114
                if (self.stack_member_id > self.stack.member_id_max) or\
                    (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"})
115
            else:
116 117
                raise ValidationError({'stack_member_id': "L'id dans la stack\
                    ne peut être nul"})
118

chirac's avatar
chirac committed
119
class Port(models.Model):
120
    """ Definition d'un port. Relié à un switch(foreign_key),
121 122 123 124
    un port peut etre relié de manière exclusive à :
    - une chambre (room)
    - une machine (serveur etc) (machine_interface)
    - un autre port (uplink) (related)
125
    Champs supplémentaires :
126
    - RADIUS (mode STRICT : connexion sur port uniquement si machine
127 128
    d'un adhérent à jour de cotisation et que la chambre est également à
    jour de cotisation
129 130 131 132
    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
133
    de forcer un port sur un vlan particulier. S'additionne à la politique
134
    RADIUS"""
135
    PRETTY_NAME = "Port de switch"
136
    STATES = (
137 138 139 140 141 142
        ('NO', 'NO'),
        ('STRICT', 'STRICT'),
        ('BLOQ', 'BLOQ'),
        ('COMMON', 'COMMON'),
        )

Dalahro's avatar
Dalahro committed
143
    switch = models.ForeignKey('Switch', related_name="ports")
chirac's avatar
chirac committed
144
    port = models.IntegerField()
145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162
    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
163
    radius = models.CharField(max_length=32, choices=STATES, default='NO')
164 165 166 167 168 169
    vlan_force = models.ForeignKey(
        'machines.Vlan',
        on_delete=models.SET_NULL,
        blank=True,
        null=True
        )
Dalahro's avatar
Dalahro committed
170
    details = models.CharField(max_length=255, blank=True)
chirac's avatar
chirac committed
171 172

    class Meta:
173 174
        unique_together = ('switch', 'port')

175 176 177 178 179
    def make_port_related(self):
        """ Synchronise le port distant sur self"""
        related_port = self.related
        related_port.related = self
        related_port.save()
180

181 182 183 184 185 186
    def clean_port_related(self):
        """ Supprime la relation related sur self"""
        related_port = self.related_port
        related_port.related = None
        related_port.save()

187
    def clean(self):
188 189 190 191
        """ 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
192
        Si un port related point vers self, on nettoie la relation
193 194 195
        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
196 197 198
        if hasattr(self, 'switch'):
            if self.port > self.switch.number:
                raise ValidationError("Ce port ne peut exister, numero trop élevé")
199 200 201 202 203
        if self.room and self.machine_interface or self.room and\
            self.related or self.machine_interface and self.related:
            raise ValidationError("Chambre, interface et related_port sont\
                mutuellement exclusifs")
        if self.related == self:
204 205 206
            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:
207 208
                raise ValidationError("Le port relié est déjà occupé, veuillez\
                    le libérer avant de créer une relation")
209
            else:
210
                self.make_port_related()
211
        elif hasattr(self, 'related_port'):
212
            self.clean_port_related()
chirac's avatar
chirac committed
213 214

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

chirac's avatar
chirac committed
217
class Room(models.Model):
218
    """Une chambre/local contenant une prise murale"""
219 220
    PRETTY_NAME = "Chambre/ Prise murale"

lhark's avatar
lhark committed
221
    name = models.CharField(max_length=255, unique=True)
chirac's avatar
chirac committed
222
    details = models.CharField(max_length=255, blank=True)
chirac's avatar
chirac committed
223

224 225
    class Meta:
        ordering = ['name']
226

chirac's avatar
chirac committed
227
    def __str__(self):
lhark's avatar
lhark committed
228
        return str(self.name)
chirac's avatar
chirac committed
229

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