8
0
Fork 0
mirror of https://gitlab2.federez.net/re2o/re2o synced 2024-12-23 15:33:45 +00:00

Merge branch 'switch_conf_json' into 'dev'

Switch conf json

See merge request federez/re2o!180

(cherry picked from commit a02e03ac71)

7ab0d656 add model PortProfile
adc5757f Merge branch 'switch_conf_json' of https://gitlab.federez.net/federez/re2o into switch_conf_json
983b5620 Refactor port_profil
e7b49bd5 Pas de null sur manytomany
92f30fbe Ajout reglages sécurité + frontend
d123ce92 Menu séparé pour les profils de ports
447919a2 Ajout et transfert des anciennes données vers le nouveau système de profil de ports
e6b8c5c8 Finition, gestion du renvoie du profil par défaut du port
b2d45d00 Adapte freeradius pour le nouveau système de profil de ports
51793bde Boolean direct pour désactiver un port + logo
2e092b3f Fix langue et 802.X radius  + divers
a07e0d92 Petit bug affichage vlans
eefa0b4a PEP8 mon amour + typos
feddc3f6 Conflicts fix switch_conf_json
This commit is contained in:
chirac 2018-08-01 17:50:07 +00:00
parent ea7ea032f0
commit f0386df809
19 changed files with 888 additions and 132 deletions

View file

@ -355,27 +355,47 @@ def decide_vlan_and_register_switch(nas_machine, nas_type, port_number,
port=port_number port=port_number
) )
.first()) .first())
# Si le port est inconnu, on place sur le vlan defaut # Si le port est inconnu, on place sur le vlan defaut
# Aucune information particulière ne permet de déterminer quelle
# politique à appliquer sur ce port
if not port: if not port:
return (sw_name, "Chambre inconnue", u'Port inconnu', VLAN_OK) return (sw_name, "Chambre inconnue", u'Port inconnu', VLAN_OK)
# Si un vlan a été précisé, on l'utilise pour VLAN_OK # On récupère le profil du port
if port.vlan_force: port_profile = port.get_port_profile
DECISION_VLAN = int(port.vlan_force.vlan_id)
# Si un vlan a été précisé dans la config du port,
# on l'utilise pour VLAN_OK
if port_profile.vlan_untagged:
DECISION_VLAN = int(port_profile.vlan_untagged.vlan_id)
extra_log = u"Force sur vlan " + str(DECISION_VLAN) extra_log = u"Force sur vlan " + str(DECISION_VLAN)
else: else:
DECISION_VLAN = VLAN_OK DECISION_VLAN = VLAN_OK
if port.radius == 'NO': # Si le port est désactivé, on rejette sur le vlan de déconnexion
if not port.state:
return (sw_name, port.room, u'Port desactivé', VLAN_NOK)
# Si radius est désactivé, on laisse passer
if port_profile.radius_type == 'NO':
return (sw_name, return (sw_name,
"", "",
u"Pas d'authentification sur ce port" + extra_log, u"Pas d'authentification sur ce port" + extra_log,
DECISION_VLAN) DECISION_VLAN)
if port.radius == 'BLOQ': # Si le 802.1X est activé sur ce port, cela veut dire que la personne a été accept précédemment
return (sw_name, port.room, u'Port desactive', VLAN_NOK) # Par conséquent, on laisse passer sur le bon vlan
if nas_type.port_access_mode == '802.1X' and port_profile.radius_type == '802.1X':
room = port.room or "Chambre/local inconnu"
return (sw_name, room, u'Acceptation authentification 802.1X', DECISION_VLAN)
if port.radius == 'STRICT': # Sinon, cela veut dire qu'on fait de l'auth radius par mac
# Si le port est en mode strict, on vérifie que tous les users
# rattachés à ce port sont bien à jour de cotisation. Sinon on rejette (anti squattage)
# Il n'est pas possible de se connecter sur une prise strict sans adhérent à jour de cotis
# dedans
if port_profile.radius_mode == 'STRICT':
room = port.room room = port.room
if not room: if not room:
return (sw_name, "Inconnue", u'Chambre inconnue', VLAN_NOK) return (sw_name, "Inconnue", u'Chambre inconnue', VLAN_NOK)
@ -390,7 +410,8 @@ def decide_vlan_and_register_switch(nas_machine, nas_type, port_number,
return (sw_name, room, u'Chambre resident desactive', VLAN_NOK) return (sw_name, room, u'Chambre resident desactive', VLAN_NOK)
# else: user OK, on passe à la verif MAC # else: user OK, on passe à la verif MAC
if port.radius == 'COMMON' or port.radius == 'STRICT': # Si on fait de l'auth par mac, on cherche l'interface via sa mac dans la bdd
if port_profile.radius_mode == 'COMMON' or port_profile.radius_mode == 'STRICT':
# Authentification par mac # Authentification par mac
interface = (Interface.objects interface = (Interface.objects
.filter(mac_address=mac_address) .filter(mac_address=mac_address)
@ -399,15 +420,19 @@ def decide_vlan_and_register_switch(nas_machine, nas_type, port_number,
.first()) .first())
if not interface: if not interface:
room = port.room room = port.room
# On essaye de register la mac # On essaye de register la mac, si l'autocapture a été activée
# Sinon on rejette sur vlan_nok
if not nas_type.autocapture_mac: if not nas_type.autocapture_mac:
return (sw_name, "", u'Machine inconnue', VLAN_NOK) return (sw_name, "", u'Machine inconnue', VLAN_NOK)
# On ne peut autocapturer que si on connait la chambre et donc l'user correspondant
elif not room: elif not room:
return (sw_name, return (sw_name,
"Inconnue", "Inconnue",
u'Chambre et machine inconnues', u'Chambre et machine inconnues',
VLAN_NOK) VLAN_NOK)
else: else:
# Si la chambre est vide (local club, prises en libre services)
# Impossible d'autocapturer
if not room_user: if not room_user:
room_user = User.objects.filter( room_user = User.objects.filter(
Q(club__room=port.room) | Q(adherent__room=port.room) Q(club__room=port.room) | Q(adherent__room=port.room)
@ -418,6 +443,8 @@ def decide_vlan_and_register_switch(nas_machine, nas_type, port_number,
u'Machine et propriétaire de la chambre ' u'Machine et propriétaire de la chambre '
'inconnus', 'inconnus',
VLAN_NOK) VLAN_NOK)
# Si il y a plus d'un user dans la chambre, impossible de savoir à qui
# Ajouter la machine
elif room_user.count() > 1: elif room_user.count() > 1:
return (sw_name, return (sw_name,
room, room,
@ -425,11 +452,13 @@ def decide_vlan_and_register_switch(nas_machine, nas_type, port_number,
'dans la chambre/local -> ajout de mac ' 'dans la chambre/local -> ajout de mac '
'automatique impossible', 'automatique impossible',
VLAN_NOK) VLAN_NOK)
# Si l'adhérent de la chambre n'est pas à jour de cotis, pas d'autocapture
elif not room_user.first().has_access(): elif not room_user.first().has_access():
return (sw_name, return (sw_name,
room, room,
u'Machine inconnue et adhérent non cotisant', u'Machine inconnue et adhérent non cotisant',
VLAN_NOK) VLAN_NOK)
# Sinon on capture et on laisse passer sur le bon vlan
else: else:
result, reason = (room_user result, reason = (room_user
.first() .first()
@ -449,6 +478,9 @@ def decide_vlan_and_register_switch(nas_machine, nas_type, port_number,
reason + str(mac_address) reason + str(mac_address)
), ),
VLAN_NOK) VLAN_NOK)
# L'interface a été trouvée, on vérifie qu'elle est active, sinon on reject
# Si elle n'a pas d'ipv4, on lui en met une
# Enfin on laisse passer sur le vlan pertinent
else: else:
room = port.room room = port.room
if not interface.is_active: if not interface.is_active:

View file

@ -262,9 +262,9 @@ def search_single_word(word, filters, user,
) | Q( ) | Q(
related__switch__interface__domain__name__icontains=word related__switch__interface__domain__name__icontains=word
) | Q( ) | Q(
radius__icontains=word custom_profile__name__icontains=word
) | Q( ) | Q(
vlan_force__name__icontains=word custom_profile__profil_default__icontains=word
) | Q( ) | Q(
details__icontains=word details__icontains=word
) )

View file

@ -38,7 +38,8 @@ from .models import (
ConstructorSwitch, ConstructorSwitch,
AccessPoint, AccessPoint,
SwitchBay, SwitchBay,
Building Building,
PortProfile,
) )
@ -86,6 +87,9 @@ class BuildingAdmin(VersionAdmin):
"""Administration d'un batiment""" """Administration d'un batiment"""
pass pass
class PortProfileAdmin(VersionAdmin):
"""Administration of a port profile"""
pass
admin.site.register(Port, PortAdmin) admin.site.register(Port, PortAdmin)
admin.site.register(AccessPoint, AccessPointAdmin) admin.site.register(AccessPoint, AccessPointAdmin)
@ -96,3 +100,4 @@ admin.site.register(ModelSwitch, ModelSwitchAdmin)
admin.site.register(ConstructorSwitch, ConstructorSwitchAdmin) admin.site.register(ConstructorSwitch, ConstructorSwitchAdmin)
admin.site.register(Building, BuildingAdmin) admin.site.register(Building, BuildingAdmin)
admin.site.register(SwitchBay, SwitchBayAdmin) admin.site.register(SwitchBay, SwitchBayAdmin)
admin.site.register(PortProfile, PortProfileAdmin)

View file

@ -35,6 +35,7 @@ from __future__ import unicode_literals
from django import forms from django import forms
from django.forms import ModelForm from django.forms import ModelForm
from django.db.models import Prefetch from django.db.models import Prefetch
from django.utils.translation import ugettext_lazy as _
from machines.models import Interface from machines.models import Interface
from machines.forms import ( from machines.forms import (
@ -53,6 +54,7 @@ from .models import (
AccessPoint, AccessPoint,
SwitchBay, SwitchBay,
Building, Building,
PortProfile,
) )
@ -78,8 +80,8 @@ class EditPortForm(FormRevMixin, ModelForm):
optimiser le temps de chargement avec select_related (vraiment optimiser le temps de chargement avec select_related (vraiment
lent sans)""" lent sans)"""
class Meta(PortForm.Meta): class Meta(PortForm.Meta):
fields = ['room', 'related', 'machine_interface', 'radius', fields = ['room', 'related', 'machine_interface', 'custom_profile',
'vlan_force', 'details'] 'state', 'details']
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
prefix = kwargs.pop('prefix', self.Meta.model.__name__) prefix = kwargs.pop('prefix', self.Meta.model.__name__)
@ -99,8 +101,8 @@ class AddPortForm(FormRevMixin, ModelForm):
'room', 'room',
'machine_interface', 'machine_interface',
'related', 'related',
'radius', 'custom_profile',
'vlan_force', 'state',
'details' 'details'
] ]
@ -254,3 +256,16 @@ class EditBuildingForm(FormRevMixin, ModelForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
prefix = kwargs.pop('prefix', self.Meta.model.__name__) prefix = kwargs.pop('prefix', self.Meta.model.__name__)
super(EditBuildingForm, self).__init__(*args, prefix=prefix, **kwargs) super(EditBuildingForm, self).__init__(*args, prefix=prefix, **kwargs)
class EditPortProfileForm(FormRevMixin, ModelForm):
"""Form to edit a port profile"""
class Meta:
model = PortProfile
fields = '__all__'
def __init__(self, *args, **kwargs):
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
super(EditPortProfileForm, self).__init__(*args,
prefix=prefix,
**kwargs)

View file

@ -0,0 +1,44 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2018-06-26 16:37
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
import re2o.mixins
class Migration(migrations.Migration):
dependencies = [
('machines', '0082_auto_20180525_2209'),
('topologie', '0060_server'),
]
operations = [
migrations.CreateModel(
name='PortProfile',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255, verbose_name='Name')),
('profil_default', models.CharField(blank=True, choices=[('room', 'room'), ('nothing', 'nothing'), ('accespoint', 'accesspoint'), ('uplink', 'uplink'), ('asso_machine', 'asso_machine')], max_length=32, null=True, unique=True, verbose_name='profil default')),
('radius_type', models.CharField(choices=[('NO', 'NO'), ('802.1X', '802.1X'), ('MAC-radius', 'MAC-radius')], max_length=32, verbose_name='RADIUS type')),
('radius_mode', models.CharField(choices=[('STRICT', 'STRICT'), ('COMMON', 'COMMON')], default='COMMON', max_length=32, verbose_name='RADIUS mode')),
('speed', models.CharField(choices=[('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')], default='auto', help_text='Mode de transmission et vitesse du port', max_length=32, verbose_name='Speed')),
('mac_limit', models.IntegerField(blank=True, help_text='Limit du nombre de mac sur le port', null=True, verbose_name='Mac limit')),
('flow_control', models.BooleanField(default=False, help_text='Gestion des débits', verbose_name='Flow control')),
('dhcp_snooping', models.BooleanField(default=False, help_text='Protection dhcp pirate', verbose_name='Dhcp snooping')),
('dhcpv6_snooping', models.BooleanField(default=False, help_text='Protection dhcpv6 pirate', verbose_name='Dhcpv6 snooping')),
('arp_protect', models.BooleanField(default=False, help_text="Verification assignation de l'IP par dhcp", verbose_name='Arp protect')),
('ra_guard', models.BooleanField(default=False, help_text='Protection contre ra pirate', verbose_name='Ra guard')),
('loop_protect', models.BooleanField(default=False, help_text='Protection contre les boucles', verbose_name='Loop Protect')),
('vlan_tagged', models.ManyToManyField(blank=True, related_name='vlan_tagged', to='machines.Vlan', verbose_name='VLAN(s) tagged')),
('vlan_untagged', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='vlan_untagged', to='machines.Vlan', verbose_name='VLAN untagged')),
],
options={
'verbose_name': 'Port profile',
'permissions': (('view_port_profile', 'Can view a port profile object'),),
'verbose_name_plural': 'Port profiles',
},
bases=(re2o.mixins.AclMixin, re2o.mixins.RevMixin, models.Model),
),
]

View file

@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2018-06-26 23:23
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('topologie', '0061_portprofile'),
]
operations = [
migrations.AlterField(
model_name='portprofile',
name='radius_mode',
field=models.CharField(choices=[('STRICT', 'STRICT'), ('COMMON', 'COMMON')], default='COMMON', help_text="En cas d'auth par mac, auth common ou strcit sur le port", max_length=32, verbose_name='RADIUS mode'),
),
migrations.AlterField(
model_name='portprofile',
name='radius_type',
field=models.CharField(choices=[('NO', 'NO'), ('802.1X', '802.1X'), ('MAC-radius', 'MAC-radius')], help_text="Choix du type d'authentification radius : non actif, mac ou 802.1X", max_length=32, verbose_name='RADIUS type'),
),
]

View file

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2018-06-28 07:49
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('topologie', '0062_auto_20180627_0123'),
]
operations = [
migrations.AddField(
model_name='port',
name='custom_profil',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='topologie.PortProfile'),
),
]

View file

@ -0,0 +1,53 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2017-12-31 19:53
from __future__ import unicode_literals
from django.db import migrations
def transfer_profil(apps, schema_editor):
db_alias = schema_editor.connection.alias
port = apps.get_model("topologie", "Port")
profil = apps.get_model("topologie", "PortProfile")
vlan = apps.get_model("machines", "Vlan")
port_list = port.objects.using(db_alias).all()
profil_nothing = profil.objects.using(db_alias).create(name='nothing', profil_default='nothing', radius_type='NO')
profil_uplink = profil.objects.using(db_alias).create(name='uplink', profil_default='uplink', radius_type='NO')
profil_machine = profil.objects.using(db_alias).create(name='asso_machine', profil_default='asso_machine', radius_type='NO')
profil_room = profil.objects.using(db_alias).create(name='room', profil_default='room', radius_type='NO')
profil_borne = profil.objects.using(db_alias).create(name='accesspoint', profil_default='accesspoint', radius_type='NO')
for vlan_instance in vlan.objects.using(db_alias).all():
if port.objects.using(db_alias).filter(vlan_force=vlan_instance):
custom_profil = profil.objects.using(db_alias).create(name='vlan-force-' + str(vlan_instance.vlan_id), radius_type='NO', vlan_untagged=vlan_instance)
port.objects.using(db_alias).filter(vlan_force=vlan_instance).update(custom_profil=custom_profil)
if port.objects.using(db_alias).filter(room__isnull=False).filter(radius='STRICT').count() > port.objects.using(db_alias).filter(room__isnull=False).filter(radius='NO').count() and port.objects.using(db_alias).filter(room__isnull=False).filter(radius='STRICT').count() > port.objects.using(db_alias).filter(room__isnull=False).filter(radius='COMMON').count():
profil_room.radius_type = 'MAC-radius'
profil_room.radius_mode = 'STRICT'
common_profil = profil.objects.using(db_alias).create(name='mac-radius-common', radius_type='MAC-radius', radius_mode='COMMON')
no_rad_profil = profil.objects.using(db_alias).create(name='no-radius', radius_type='NO')
port.objects.using(db_alias).filter(room__isnull=False).filter(radius='COMMON').update(custom_profil=common_profil)
port.objects.using(db_alias).filter(room__isnull=False).filter(radius='NO').update(custom_profil=no_rad_profil)
elif port.objects.using(db_alias).filter(room__isnull=False).filter(radius='COMMON').count() > port.objects.using(db_alias).filter(room__isnull=False).filter(radius='NO').count() and port.objects.using(db_alias).filter(room__isnull=False).filter(radius='COMMON').count() > port.objects.using(db_alias).filter(room__isnull=False).filter(radius='STRICT').count():
profil_room.radius_type = 'MAC-radius'
profil_room.radius_mode = 'COMMON'
strict_profil = profil.objects.using(db_alias).create(name='mac-radius-strict', radius_type='MAC-radius', radius_mode='STRICT')
no_rad_profil = profil.objects.using(db_alias).create(name='no-radius', radius_type='NO')
port.objects.using(db_alias).filter(room__isnull=False).filter(radius='STRICT').update(custom_profil=strict_profil)
port.objects.using(db_alias).filter(room__isnull=False).filter(radius='NO').update(custom_profil=no_rad_profil)
else:
strict_profil = profil.objects.using(db_alias).create(name='mac-radius-strict', radius_type='MAC-radius', radius_mode='STRICT')
common_profil = profil.objects.using(db_alias).create(name='mac-radius-common', radius_type='MAC-radius', radius_mode='COMMON')
port.objects.using(db_alias).filter(room__isnull=False).filter(radius='STRICT').update(custom_profil=strict_profil)
port.objects.using(db_alias).filter(room__isnull=False).filter(radius='NO').update(custom_profil=common_profil)
profil_room.save()
class Migration(migrations.Migration):
dependencies = [
('topologie', '0063_port_custom_profil'),
]
operations = [
migrations.RunPython(transfer_profil),
]

View file

@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2018-06-30 15:03
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('topologie', '0064_createprofil'),
]
operations = [
migrations.RemoveField(
model_name='port',
name='radius',
),
migrations.RemoveField(
model_name='port',
name='vlan_force',
),
]

View file

@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2018-06-30 16:55
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('topologie', '0065_auto_20180630_1703'),
]
operations = [
migrations.AddField(
model_name='port',
name='state',
field=models.BooleanField(default=True, help_text='Etat du port Actif', verbose_name='Etat du port Actif'),
),
migrations.AlterField(
model_name='portprofile',
name='profil_default',
field=models.CharField(blank=True, choices=[('room', 'room'), ('accespoint', 'accesspoint'), ('uplink', 'uplink'), ('asso_machine', 'asso_machine'), ('nothing', 'nothing')], max_length=32, null=True, unique=True, verbose_name='profil default'),
),
]

View file

@ -0,0 +1,75 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2018-06-30 22:16
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('topologie', '0066_auto_20180630_1855'),
]
operations = [
migrations.RenameField(
model_name='port',
old_name='custom_profil',
new_name='custom_profile',
),
migrations.AlterField(
model_name='port',
name='state',
field=models.BooleanField(default=True, help_text='Port state Active', verbose_name='Port State Active'),
),
migrations.AlterField(
model_name='portprofile',
name='arp_protect',
field=models.BooleanField(default=False, help_text='Check if ip is dhcp assigned', verbose_name='Arp protect'),
),
migrations.AlterField(
model_name='portprofile',
name='dhcp_snooping',
field=models.BooleanField(default=False, help_text='Protect against rogue dhcp', verbose_name='Dhcp snooping'),
),
migrations.AlterField(
model_name='portprofile',
name='dhcpv6_snooping',
field=models.BooleanField(default=False, help_text='Protect against rogue dhcpv6', verbose_name='Dhcpv6 snooping'),
),
migrations.AlterField(
model_name='portprofile',
name='flow_control',
field=models.BooleanField(default=False, help_text='Flow control', verbose_name='Flow control'),
),
migrations.AlterField(
model_name='portprofile',
name='loop_protect',
field=models.BooleanField(default=False, help_text='Protect again loop', verbose_name='Loop Protect'),
),
migrations.AlterField(
model_name='portprofile',
name='mac_limit',
field=models.IntegerField(blank=True, help_text='Limit of mac-address on this port', null=True, verbose_name='Mac limit'),
),
migrations.AlterField(
model_name='portprofile',
name='ra_guard',
field=models.BooleanField(default=False, help_text='Protect against rogue ra', verbose_name='Ra guard'),
),
migrations.AlterField(
model_name='portprofile',
name='radius_mode',
field=models.CharField(choices=[('STRICT', 'STRICT'), ('COMMON', 'COMMON')], default='COMMON', help_text='In case of mac-auth : mode common or strict on this port', max_length=32, verbose_name='RADIUS mode'),
),
migrations.AlterField(
model_name='portprofile',
name='radius_type',
field=models.CharField(choices=[('NO', 'NO'), ('802.1X', '802.1X'), ('MAC-radius', 'MAC-radius')], help_text='Type of radius auth : inactive, mac-address or 802.1X', max_length=32, verbose_name='RADIUS type'),
),
migrations.AlterField(
model_name='portprofile',
name='speed',
field=models.CharField(choices=[('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')], default='auto', help_text='Port speed limit', max_length=32, verbose_name='Speed'),
),
]

View file

@ -40,22 +40,18 @@ from __future__ import unicode_literals
import itertools import itertools
from django.db import models from django.db import models
from django.db.models.signals import pre_save, post_save, post_delete from django.db.models.signals import post_save, post_delete
from django.utils.functional import cached_property from django.utils.functional import cached_property
from django.dispatch import receiver from django.dispatch import receiver
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.db import IntegrityError from django.db import IntegrityError
from django.db import transaction from django.db import transaction
from django.utils.translation import ugettext_lazy as _
from reversion import revisions as reversion from reversion import revisions as reversion
from machines.models import Machine, regen from machines.models import Machine, regen
from re2o.mixins import AclMixin, RevMixin from re2o.mixins import AclMixin, RevMixin
from os.path import isfile
from os import remove
class Stack(AclMixin, RevMixin, models.Model): class Stack(AclMixin, RevMixin, models.Model):
"""Un objet stack. Regrouppe des switchs en foreign key """Un objet stack. Regrouppe des switchs en foreign key
@ -122,7 +118,10 @@ class AccessPoint(AclMixin, Machine):
) )
def building(self): def building(self):
"""Return the building of the AP/Server (building of the switchs connected to...)""" """
Return the building of the AP/Server (building of the switchs
connected to...)
"""
return Building.objects.filter( return Building.objects.filter(
switchbay__switch=self.switch() switchbay__switch=self.switch()
) )
@ -134,14 +133,18 @@ class AccessPoint(AclMixin, Machine):
@classmethod @classmethod
def all_ap_in(cls, building_instance): def all_ap_in(cls, building_instance):
"""Get a building as argument, returns all ap of a building""" """Get a building as argument, returns all ap of a building"""
return cls.objects.filter(interface__port__switch__switchbay__building=building_instance) return cls.objects.filter(
interface__port__switch__switchbay__building=building_instance
)
def __str__(self): def __str__(self):
return str(self.interface_set.first()) return str(self.interface_set.first())
class Server(Machine): class Server(Machine):
"""Dummy class, to retrieve servers of a building, or get switch of a server""" """
Dummy class, to retrieve servers of a building, or get switch of a server
"""
class Meta: class Meta:
proxy = True proxy = True
@ -159,7 +162,10 @@ class Server(Machine):
) )
def building(self): def building(self):
"""Return the building of the AP/Server (building of the switchs connected to...)""" """
Return the building of the AP/Server
(building of the switchs connected to...)
"""
return Building.objects.filter( return Building.objects.filter(
switchbay__switch=self.switch() switchbay__switch=self.switch()
) )
@ -171,7 +177,9 @@ class Server(Machine):
@classmethod @classmethod
def all_server_in(cls, building_instance): def all_server_in(cls, building_instance):
"""Get a building as argument, returns all server of a building""" """Get a building as argument, returns all server of a building"""
return cls.objects.filter(interface__port__switch__switchbay__building=building_instance).exclude(accesspoint__isnull=False) return cls.objects.filter(
interface__port__switch__switchbay__building=building_instance
).exclude(accesspoint__isnull=False)
def __str__(self): def __str__(self):
return str(self.interface_set.first()) return str(self.interface_set.first())
@ -369,12 +377,6 @@ class Port(AclMixin, RevMixin, models.Model):
de forcer un port sur un vlan particulier. S'additionne à la politique de forcer un port sur un vlan particulier. S'additionne à la politique
RADIUS""" RADIUS"""
PRETTY_NAME = "Port de switch" PRETTY_NAME = "Port de switch"
STATES = (
('NO', 'NO'),
('STRICT', 'STRICT'),
('BLOQ', 'BLOQ'),
('COMMON', 'COMMON'),
)
switch = models.ForeignKey( switch = models.ForeignKey(
'Switch', 'Switch',
@ -400,13 +402,17 @@ class Port(AclMixin, RevMixin, models.Model):
blank=True, blank=True,
related_name='related_port' related_name='related_port'
) )
radius = models.CharField(max_length=32, choices=STATES, default='NO') custom_profile = models.ForeignKey(
vlan_force = models.ForeignKey( 'PortProfile',
'machines.Vlan', on_delete=models.PROTECT,
on_delete=models.SET_NULL,
blank=True, blank=True,
null=True null=True
) )
state = models.BooleanField(
default=True,
help_text='Port state Active',
verbose_name=_("Port State Active")
)
details = models.CharField(max_length=255, blank=True) details = models.CharField(max_length=255, blank=True)
class Meta: class Meta:
@ -415,6 +421,37 @@ class Port(AclMixin, RevMixin, models.Model):
("view_port", "Peut voir un objet port"), ("view_port", "Peut voir un objet port"),
) )
@cached_property
def get_port_profile(self):
"""Return the config profile for this port
:returns: the profile of self (port)"""
def profile_or_nothing(profile):
port_profile = PortProfile.objects.filter(
profile_default=profile).first()
if port_profile:
return port_profile
else:
nothing_profile, _created = PortProfile.objects.get_or_create(
profile_default='nothing',
name='nothing',
radius_type='NO'
)
return nothing_profile
if self.custom_profile:
return self.custom_profile
elif self.related:
return profile_or_nothing('uplink')
elif self.machine_interface:
if hasattr(self.machine_interface.machine, 'accesspoint'):
return profile_or_nothing('access_point')
else:
return profile_or_nothing('asso_machine')
elif self.room:
return profile_or_nothing('room')
else:
return profile_or_nothing('nothing')
@classmethod @classmethod
def get_instance(cls, portid, *_args, **kwargs): def get_instance(cls, portid, *_args, **kwargs):
return (cls.objects return (cls.objects
@ -492,51 +529,201 @@ class Room(AclMixin, RevMixin, models.Model):
return self.name return self.name
class PortProfile(AclMixin, RevMixin, models.Model):
"""Contains the information of the ports' configuration for a switch"""
TYPES = (
('NO', 'NO'),
('802.1X', '802.1X'),
('MAC-radius', 'MAC-radius'),
)
MODES = (
('STRICT', 'STRICT'),
('COMMON', 'COMMON'),
)
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'),
)
PROFIL_DEFAULT = (
('room', 'room'),
('accespoint', 'accesspoint'),
('uplink', 'uplink'),
('asso_machine', 'asso_machine'),
('nothing', 'nothing'),
)
name = models.CharField(max_length=255, verbose_name=_("Name"))
profil_default = models.CharField(
max_length=32,
choices=PROFIL_DEFAULT,
blank=True,
null=True,
unique=True,
verbose_name=_("profil default")
)
vlan_untagged = models.ForeignKey(
'machines.Vlan',
related_name='vlan_untagged',
on_delete=models.SET_NULL,
blank=True,
null=True,
verbose_name=_("VLAN untagged")
)
vlan_tagged = models.ManyToManyField(
'machines.Vlan',
related_name='vlan_tagged',
blank=True,
verbose_name=_("VLAN(s) tagged")
)
radius_type = models.CharField(
max_length=32,
choices=TYPES,
help_text="Type of radius auth : inactive, mac-address or 802.1X",
verbose_name=_("RADIUS type")
)
radius_mode = models.CharField(
max_length=32,
choices=MODES,
default='COMMON',
help_text="In case of mac-auth : mode common or strict on this port",
verbose_name=_("RADIUS mode")
)
speed = models.CharField(
max_length=32,
choices=SPEED,
default='auto',
help_text='Port speed limit',
verbose_name=_("Speed")
)
mac_limit = models.IntegerField(
null=True,
blank=True,
help_text='Limit of mac-address on this port',
verbose_name=_("Mac limit")
)
flow_control = models.BooleanField(
default=False,
help_text='Flow control',
verbose_name=_("Flow control")
)
dhcp_snooping = models.BooleanField(
default=False,
help_text='Protect against rogue dhcp',
verbose_name=_("Dhcp snooping")
)
dhcpv6_snooping = models.BooleanField(
default=False,
help_text='Protect against rogue dhcpv6',
verbose_name=_("Dhcpv6 snooping")
)
arp_protect = models.BooleanField(
default=False,
help_text='Check if ip is dhcp assigned',
verbose_name=_("Arp protect")
)
ra_guard = models.BooleanField(
default=False,
help_text='Protect against rogue ra',
verbose_name=_("Ra guard")
)
loop_protect = models.BooleanField(
default=False,
help_text='Protect again loop',
verbose_name=_("Loop Protect")
)
class Meta:
permissions = (
("view_port_profile", _("Can view a port profile object")),
)
verbose_name = _("Port profile")
verbose_name_plural = _("Port profiles")
security_parameters_fields = [
'loop_protect',
'ra_guard',
'arp_protect',
'dhcpv6_snooping',
'dhcp_snooping',
'flow_control'
]
@cached_property
def security_parameters_enabled(self):
return [
parameter
for parameter in self.security_parameters_fields
if getattr(self, parameter)
]
@cached_property
def security_parameters_as_str(self):
return ','.join(self.security_parameters_enabled)
def __str__(self):
return self.name
@receiver(post_save, sender=AccessPoint) @receiver(post_save, sender=AccessPoint)
def ap_post_save(**_kwargs): def ap_post_save(**_kwargs):
"""Regeneration des noms des bornes vers le controleur""" """Regeneration des noms des bornes vers le controleur"""
regen('unifi-ap-names') regen('unifi-ap-names')
regen("graph_topo") regen("graph_topo")
@receiver(post_delete, sender=AccessPoint) @receiver(post_delete, sender=AccessPoint)
def ap_post_delete(**_kwargs): def ap_post_delete(**_kwargs):
"""Regeneration des noms des bornes vers le controleur""" """Regeneration des noms des bornes vers le controleur"""
regen('unifi-ap-names') regen('unifi-ap-names')
regen("graph_topo") regen("graph_topo")
@receiver(post_delete, sender=Stack) @receiver(post_delete, sender=Stack)
def stack_post_delete(**_kwargs): def stack_post_delete(**_kwargs):
"""Vide les id des switches membres d'une stack supprimée""" """Vide les id des switches membres d'une stack supprimée"""
Switch.objects.filter(stack=None).update(stack_member_id=None) Switch.objects.filter(stack=None).update(stack_member_id=None)
@receiver(post_save, sender=Port) @receiver(post_save, sender=Port)
def port_post_save(**_kwargs): def port_post_save(**_kwargs):
regen("graph_topo") regen("graph_topo")
@receiver(post_delete, sender=Port) @receiver(post_delete, sender=Port)
def port_post_delete(**_kwargs): def port_post_delete(**_kwargs):
regen("graph_topo") regen("graph_topo")
@receiver(post_save, sender=ModelSwitch) @receiver(post_save, sender=ModelSwitch)
def modelswitch_post_save(**_kwargs): def modelswitch_post_save(**_kwargs):
regen("graph_topo") regen("graph_topo")
@receiver(post_delete, sender=ModelSwitch) @receiver(post_delete, sender=ModelSwitch)
def modelswitch_post_delete(**_kwargs): def modelswitch_post_delete(**_kwargs):
regen("graph_topo") regen("graph_topo")
@receiver(post_save, sender=Building) @receiver(post_save, sender=Building)
def building_post_save(**_kwargs): def building_post_save(**_kwargs):
regen("graph_topo") regen("graph_topo")
@receiver(post_delete, sender=Building) @receiver(post_delete, sender=Building)
def building_post_delete(**_kwargs): def building_post_delete(**_kwargs):
regen("graph_topo") regen("graph_topo")
@receiver(post_save, sender=Switch) @receiver(post_save, sender=Switch)
def switch_post_save(**_kwargs): def switch_post_save(**_kwargs):
regen("graph_topo") regen("graph_topo")
@receiver(post_delete, sender=Switch) @receiver(post_delete, sender=Switch)
def switch_post_delete(**_kwargs): def switch_post_delete(**_kwargs):
regen("graph_topo") regen("graph_topo")

View file

@ -40,6 +40,18 @@ with this program; if not, write to the Free Software Foundation, Inc.,
</tr> </tr>
</thead> </thead>
{% for port in port_list %} {% for port in port_list %}
<tr>
<th>{% include "buttons/sort.html" with prefix='port' col='port' text='Port' %}</th>
<th>{% include "buttons/sort.html" with prefix='port' col='room' text='Room' %}</th>
<th>{% include "buttons/sort.html" with prefix='port' col='interface' text='Interface machine' %}</th>
<th>{% include "buttons/sort.html" with prefix='port' col='related' text='Related' %}</th>
<th>Etat du port</th>
<th>Profil du port</th>
<th>Détails</th>
<th></th>
</tr>
</thead>
{% for port in port_list %}
<tr> <tr>
<td>{{ port.port }}</td> <td>{{ port.port }}</td>
<td> <td>
@ -50,9 +62,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% can_view port.machine_interface.machine.user %} {% can_view port.machine_interface.machine.user %}
<a href="{% url 'users:profil' userid=port.machine_interface.machine.user.id %}"> <a href="{% url 'users:profil' userid=port.machine_interface.machine.user.id %}">
{{ port.machine_interface }} {{ port.machine_interface }}
</a>
{% acl_else %}
{{ port.machine_interface }}
{% acl_end %} {% acl_end %}
{% endif %} {% endif %}
</td> </td>
@ -64,14 +73,19 @@ with this program; if not, write to the Free Software Foundation, Inc.,
</a> </a>
{% acl_else %} {% acl_else %}
{{ port.related }} {{ port.related }}
</a>
{% acl_else %}
{{ port.related }}
{% acl_end %} {% acl_end %}
{% endif %} {% endif %}
</td> </td>
<td>{{ port.radius }}</td> <td>{% if port.state %} <i class="text-success">Actif</i>{% else %}<i class="text-danger">Désactivé</i>{% endif %}</td>
<td>{% if not port.vlan_force %}Aucun{% else %}{{ port.vlan_force }}{% endif %}</td> <td>{% if not port.custom_profile %}<u>Par défaut</u> : {% endif %}{{port.get_port_profil}}</td>
<td>{{ port.details }}</td> <td>{{ port.details }}</td>
<td class="text-right"> <td class="text-right">
{% history_button port %} <a class="btn btn-info btn-sm" role="button" title="Historique" href="{% url 'topologie:history' 'port' port.pk %}">
<i class="fa fa-history"></i>
</a>
{% can_edit port %} {% can_edit port %}
<a class="btn btn-primary btn-sm" role="button" title="Éditer" href="{% url 'topologie:edit-port' port.id %}"> <a class="btn btn-primary btn-sm" role="button" title="Éditer" href="{% url 'topologie:edit-port' port.id %}">
<i class="fa fa-edit"></i> <i class="fa fa-edit"></i>
@ -85,5 +99,5 @@ with this program; if not, write to the Free Software Foundation, Inc.,
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}
</table> </table>
</div> </div>

View file

@ -0,0 +1,85 @@
{% comment %}
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 © 2018 Gabriel Détraz
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.
{% endcomment %}
{% load acl %}
{% load i18n %}
<div class="table-responsive">
{% if port_profile_list.paginator %}
{% include "pagination.html" with list=port_profile_list %}
{% endif %}
<table class="table table-striped">
<thead>
<tr>
<th>{% trans "Name" %}</th>
<th>{% trans "Default for" %}</th>
<th>{% trans "VLANs" %}</th>
<th>{% trans "RADIUS settings" %}</th>
<th>{% trans "Speed" %}</th>
<th>{% trans "Mac address limit" %}</th>
<th>{% trans "Security" %}</th>
<th></th>
</tr>
</thead>
{% for port_profile in port_profile_list %}
<tr>
<td>{{port_profile.name}}</td>
<td>{{port_profile.profil_default}}</td>
<td>
{% if port_profile.vlan_untagged %}
<b>Untagged : </b>{{port_profile.vlan_untagged}}
<br>
{% endif %}
{% if port_profile.vlan_tagged.all %}
<b>Tagged : </b>{{port_profile.vlan_tagged.all|join:", "}}
{% endif %}
</td>
<td>
<b>Type : </b>{{port_profile.radius_type}}
{% if port_profile.radius_type == "MAC-radius" %}
<br>
<b>Mode : </b>{{port_profile.radius_mode}}</td>
{% endif %}
<td>{{port_profile.speed}}</td>
<td>{{port_profile.mac_limit}}</td>
<td>{{port_profile.security_parameters_enabled|join:"<br>"}}</td>
<td class="text-right">
{% include 'buttons/history.html' with href='topologie:history' name='portprofile' id=port_profile.pk %}
{% can_edit port_profile %}
{% include 'buttons/edit.html' with href='topologie:edit-port-profile' id=port_profile.pk %}
{% acl_end %}
{% can_delete port_profile %}
{% include 'buttons/suppr.html' with href='topologie:del-port-profile' id=port_profile.pk %}
{% acl_end %}
</td>
</tr>
{% endfor %}
</table>
{% if port_profile_list.paginator %}
{% include "pagination.html" with list=port_profile_list %}
{% endif %}
</div>

View file

@ -72,5 +72,4 @@ Topologie des Switchs
<br /> <br />
<br /> <br />
{% endblock %} {% endblock %}

View file

@ -0,0 +1,43 @@
{% extends "topologie/sidebar.html" %}
{% comment %}
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 © 2018 Gabriel Détraz
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.
{% endcomment %}
{% load bootstrap3 %}
{% load acl %}
{% load i18n %}
{% block title %}Switchs{% endblock %}
{% block content %}
<h2>{% trans "Port profiles" %}</h2>
{% can_create PortProfile %}
<a class="btn btn-primary btn-sm" role="button" href="{% url 'topologie:new-port-profile' %}"><i class="fa fa-plus"></i>{% trans " Add a port profile" %}</a>
<hr>
{% acl_end %}
{% include "topologie/aff_port_profile.html" with port_profile_list=port_profile_list %}
<br />
<br />
<br />
{% endblock %}

View file

@ -33,6 +33,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<i class="fa fa-microchip"></i> <i class="fa fa-microchip"></i>
Switchs Switchs
</a> </a>
<a class="list-group-item list-group-item-info" href="{% url "topologie:index-port-profile" %}">
<i class="fa fa-cogs"></i>
Config des ports switchs
</a>
<a class="list-group-item list-group-item-info" href="{% url "topologie:index-ap" %}"> <a class="list-group-item list-group-item-info" href="{% url "topologie:index-ap" %}">
<i class="fa fa-wifi"></i> <i class="fa fa-wifi"></i>
Bornes WiFi Bornes WiFi

View file

@ -108,4 +108,16 @@ urlpatterns = [
url(r'^del_building/(?P<buildingid>[0-9]+)$', url(r'^del_building/(?P<buildingid>[0-9]+)$',
views.del_building, views.del_building,
name='del-building'), name='del-building'),
url(r'^index_port_profile/$',
views.index_port_profile,
name='index-port-profile'),
url(r'^new_port_profile/$',
views.new_port_profile,
name='new-port-profile'),
url(r'^edit_port_profile/(?P<portprofileid>[0-9]+)$',
views.edit_port_profile,
name='edit-port-profile'),
url(r'^del_port_profile/(?P<portprofileid>[0-9]+)$',
views.del_port_profile,
name='del-port-profile'),
] ]

View file

@ -42,11 +42,8 @@ from django.contrib.auth.decorators import login_required
from django.db import IntegrityError from django.db import IntegrityError
from django.db.models import ProtectedError, Prefetch from django.db.models import ProtectedError, Prefetch
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.contrib.staticfiles.storage import staticfiles_storage
from django.template.loader import get_template
from django.template import Context, Template, loader from django.template import Context, Template, loader
from django.db.models.signals import post_save from django.utils.translation import ugettext as _
from django.dispatch import receiver
import tempfile import tempfile
@ -80,6 +77,7 @@ from .models import (
SwitchBay, SwitchBay,
Building, Building,
Server, Server,
PortProfile,
) )
from .forms import ( from .forms import (
EditPortForm, EditPortForm,
@ -94,7 +92,8 @@ from .forms import (
AddAccessPointForm, AddAccessPointForm,
EditAccessPointForm, EditAccessPointForm,
EditSwitchBayForm, EditSwitchBayForm,
EditBuildingForm EditBuildingForm,
EditPortProfileForm,
) )
from subprocess import ( from subprocess import (
@ -103,7 +102,6 @@ from subprocess import (
) )
from os.path import isfile from os.path import isfile
from os import remove
@login_required @login_required
@ -124,12 +122,18 @@ def index(request):
request.GET.get('order'), request.GET.get('order'),
SortTable.TOPOLOGIE_INDEX SortTable.TOPOLOGIE_INDEX
) )
pagination_number = GeneralOption.get_cached_value('pagination_number') pagination_number = GeneralOption.get_cached_value('pagination_number')
switch_list = re2o_paginator(request, switch_list, pagination_number) switch_list = re2o_paginator(request, switch_list, pagination_number)
if any(service_link.need_regen() for service_link in Service_link.objects.filter(service__service_type='graph_topo')): if any(
service_link.need_regen
for service_link in Service_link.objects.filter(
service__service_type='graph_topo')
):
make_machine_graph() make_machine_graph()
for service_link in Service_link.objects.filter(service__service_type='graph_topo'): for service_link in Service_link.objects.filter(
service__service_type='graph_topo'):
service_link.done_regen() service_link.done_regen()
if not isfile("/var/www/re2o/media/images/switchs.png"): if not isfile("/var/www/re2o/media/images/switchs.png"):
@ -141,6 +145,21 @@ def index(request):
) )
@login_required
@can_view_all(PortProfile)
def index_port_profile(request):
pagination_number = GeneralOption.get_cached_value('pagination_number')
port_profile_list = PortProfile.objects.all().select_related(
'vlan_untagged')
port_profile_list = re2o_paginator(
request, port_profile_list, pagination_number)
return render(
request,
'topologie/index_portprofile.html',
{'port_profile_list': port_profile_list}
)
@login_required @login_required
@can_view_all(Port) @can_view_all(Port)
@can_view(Switch) @can_view(Switch)
@ -955,13 +974,67 @@ def del_constructor_switch(request, constructor_switch, **_kwargs):
}, 'topologie/delete.html', request) }, 'topologie/delete.html', request)
@login_required
@can_create(PortProfile)
def new_port_profile(request):
"""Create a new port profile"""
port_profile = EditPortProfileForm(request.POST or None)
if port_profile.is_valid():
port_profile.save()
messages.success(request, _("Port profile created"))
return redirect(reverse('topologie:index'))
return form(
{'topoform': port_profile, 'action_name': _("Create")},
'topologie/topo.html',
request
)
@login_required
@can_edit(PortProfile)
def edit_port_profile(request, port_profile, **_kwargs):
"""Edit a port profile"""
port_profile = EditPortProfileForm(
request.POST or None, instance=port_profile)
if port_profile.is_valid():
if port_profile.changed_data:
port_profile.save()
messages.success(request, _("Port profile modified"))
return redirect(reverse('topologie:index'))
return form(
{'topoform': port_profile, 'action_name': _("Edit")},
'topologie/topo.html',
request
)
@login_required
@can_delete(PortProfile)
def del_port_profile(request, port_profile, **_kwargs):
"""Delete a port profile"""
if request.method == 'POST':
try:
port_profile.delete()
messages.success(request,
_("The port profile was successfully deleted"))
except ProtectedError:
messages.success(request,
_("Impossible to delete the port profile"))
return redirect(reverse('topologie:index'))
return form(
{'objet': port_profile, 'objet_name': _("Port profile")},
'topologie/delete.html',
request
)
def make_machine_graph(): def make_machine_graph():
""" """
Create the graph of switchs, machines and access points. Create the graph of switchs, machines and access points.
""" """
dico = { dico = {
'subs': [], 'subs': [],
'links' : [], 'links': [],
'alone': [], 'alone': [],
'colors': { 'colors': {
'head': "#7f0505", # Color parameters for the graph 'head': "#7f0505", # Color parameters for the graph
@ -1014,50 +1087,58 @@ def make_machine_graph():
dico['subs'][-1]['machines'].append({ dico['subs'][-1]['machines'].append({
'name': server.short_name, 'name': server.short_name,
'switch': server.switch()[0].main_interface().domain.name, 'switch': server.switch()[0].main_interface().domain.name,
'port': Port.objects.filter(machine_interface__machine=server)[0].port 'port': Port.objects.filter(
machine_interface__machine=server
)[0].port
}) })
# While the list of forgotten ones is not empty # While the list of forgotten ones is not empty
while missing: while missing:
if missing[0].ports.count(): # The switch is not empty if missing[0].ports.count(): # The switch is not empty
links, new_detected = recursive_switchs(missing[0], None, [missing[0]]) links, new_detected = recursive_switchs(
missing[0], None, [missing[0]])
for link in links: for link in links:
dico['links'].append(link) dico['links'].append(link)
# Update the lists of missings and already detected switchs # Update the lists of missings and already detected switchs
missing=[i for i in missing if i not in new_detected] missing = [i for i in missing if i not in new_detected]
detected += new_detected detected += new_detected
else: # If the switch have no ports, don't explore it and hop to the next one # If the switch have no ports, don't explore it and hop to the next one
else:
del missing[0] del missing[0]
# Switchs that are not connected or not in a building # Switchs that are not connected or not in a building
for switch in Switch.objects.filter(switchbay__isnull=True).exclude(ports__related__isnull=False): for switch in Switch.objects.filter(
switchbay__isnull=True).exclude(ports__related__isnull=False):
dico['alone'].append({ dico['alone'].append({
'id': switch.id, 'id': switch.id,
'name': switch.main_interface().domain.name 'name': switch.main_interface().domain.name
}) })
# generate the dot file
dot_data = generate_dot(dico, 'topologie/graph_switch.dot')
dot_data=generate_dot(dico,'topologie/graph_switch.dot') # generate the dot file # Create a temporary file to store the dot data
f = tempfile.NamedTemporaryFile(mode='w+', encoding='utf-8', delete=False)
f = tempfile.NamedTemporaryFile(mode='w+', encoding='utf-8', delete=False) # Create a temporary file to store the dot data
with f: with f:
f.write(dot_data) f.write(dot_data)
unflatten = Popen( # unflatten the graph to make it look better unflatten = Popen( # unflatten the graph to make it look better
["unflatten","-l", "3", f.name], ["unflatten", "-l", "3", f.name],
stdout=PIPE stdout=PIPE
) )
image = Popen( # pipe the result of the first command into the second Popen( # pipe the result of the first command into the second
["dot", "-Tpng", "-o", MEDIA_ROOT + "/images/switchs.png"], ["dot", "-Tpng", "-o", MEDIA_ROOT + "/images/switchs.png"],
stdin=unflatten.stdout, stdin=unflatten.stdout,
stdout=PIPE stdout=PIPE
) )
def generate_dot(data,template):
def generate_dot(data, template):
"""create the dot file """create the dot file
:param data: dictionary passed to the template :param data: dictionary passed to the template
:param template: path to the dot template :param template: path to the dot template
:return: all the lines of the dot file""" :return: all the lines of the dot file"""
t = loader.get_template(template) t = loader.get_template(template)
if not isinstance(t, Template) and not (hasattr(t, 'template') and isinstance(t.template, Template)): if not isinstance(t, Template) and \
not (hasattr(t, 'template') and isinstance(t.template, Template)):
raise Exception("Le template par défaut de Django n'est pas utilisé." raise Exception("Le template par défaut de Django n'est pas utilisé."
"Cela peut mener à des erreurs de rendu." "Cela peut mener à des erreurs de rendu."
"Vérifiez les paramètres") "Vérifiez les paramètres")
@ -1065,27 +1146,40 @@ def generate_dot(data,template):
dot = t.render(c) dot = t.render(c)
return(dot) return(dot)
def recursive_switchs(switch_start, switch_before, detected): def recursive_switchs(switch_start, switch_before, detected):
"""Visit the switch and travel to the switchs linked to it. """Visit the switch and travel to the switchs linked to it.
:param switch_start: the switch to begin the visit on :param switch_start: the switch to begin the visit on
:param switch_before: the switch that you come from. None if switch_start is the first one :param switch_before: the switch that you come from.
:param detected: list of all switchs already visited. None if switch_start is the first one None if switch_start is the first one
:return: A list of all the links found and a list of all the switchs visited""" :param detected: list of all switchs already visited.
None if switch_start is the first one
:return: A list of all the links found and a list of
all the switchs visited
"""
detected.append(switch_start) detected.append(switch_start)
links_return=[] # list of dictionaries of the links to be detected links_return = [] # list of dictionaries of the links to be detected
for port in switch_start.ports.filter(related__isnull=False): # create links to every switchs below # create links to every switchs below
if port.related.switch != switch_before and port.related.switch != port.switch and port.related.switch not in detected: # Not the switch that we come from, not the current switch for port in switch_start.ports.filter(related__isnull=False):
# Not the switch that we come from, not the current switch
if port.related.switch != switch_before \
and port.related.switch != port.switch \
and port.related.switch not in detected:
links = { # Dictionary of a link links = { # Dictionary of a link
'depart':switch_start.id, 'depart': switch_start.id,
'arrive':port.related.switch.id 'arrive': port.related.switch.id
} }
links_return.append(links) # Add current and below levels links links_return.append(links) # Add current and below levels links
for port in switch_start.ports.filter(related__isnull=False): # go down on every related switchs # go down on every related switchs
if port.related.switch not in detected: # The switch at the end of this link has not been visited for port in switch_start.ports.filter(related__isnull=False):
links_down, detected = recursive_switchs(port.related.switch, switch_start, detected) # explore it and get the results # The switch at the end of this link has not been visited
for link in links_down: # Add the non empty links to the current list if port.related.switch not in detected:
# explore it and get the results
links_down, detected = recursive_switchs(
port.related.switch, switch_start, detected)
# Add the non empty links to the current list
for link in links_down:
if link: if link:
links_return.append(link) links_return.append(link)
return (links_return, detected) return (links_return, detected)