views.py 38.1 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
# 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.
chirac's avatar
chirac committed
22 23
"""
Page des vues de l'application topologie
24

chirac's avatar
chirac committed
25 26 27 28 29 30 31 32 33 34 35
Permet de créer, modifier et supprimer :
- un port (add_port, edit_port, del_port)
- un switch : les vues d'ajout et d'édition font appel aux forms de creation
de switch, mais aussi aux forms de machines.forms (domain, interface et
machine). Le views les envoie et les save en même temps. TODO : rationaliser
et faire que la creation de machines (interfaces, domain etc) soit gérée
coté models et forms de topologie
- une chambre (new_room, edit_room, del_room)
- une stack
- l'historique de tous les objets cités
"""
36 37
from __future__ import unicode_literals

38
from django.urls import reverse
Dalahro's avatar
Dalahro committed
39 40
from django.shortcuts import render, redirect
from django.contrib import messages
41
from django.contrib.auth.decorators import login_required
Dalahro's avatar
Dalahro committed
42
from django.db import IntegrityError
43
from django.db.models import ProtectedError, Prefetch
44
from django.core.exceptions import ValidationError
grisel-davy's avatar
grisel-davy committed
45
from django.template import Context, Template, loader
Fernet Laouen's avatar
Fernet Laouen committed
46
from django.utils.translation import ugettext as _
grisel-davy's avatar
grisel-davy committed
47

grisel-davy's avatar
grisel-davy committed
48
import tempfile
Dalahro's avatar
Dalahro committed
49

50 51 52 53 54 55 56 57 58
from users.views import form
from re2o.utils import re2o_paginator, SortTable
from re2o.acl import (
    can_create,
    can_edit,
    can_delete,
    can_view,
    can_view_all,
)
grisel-davy's avatar
grisel-davy committed
59
from re2o.settings import MEDIA_ROOT
60 61 62 63 64 65
from machines.forms import (
    DomainForm,
    EditInterfaceForm,
    AddInterfaceForm
)
from machines.views import generate_ipv4_mbf_param
grisel-davy's avatar
grisel-davy committed
66
from machines.models import Interface, Service_link
67 68 69
from preferences.models import AssoOption, GeneralOption

from .models import (
70 71 72 73 74
    Switch,
    Port,
    Room,
    Stack,
    ModelSwitch,
75
    ConstructorSwitch,
76 77
    AccessPoint,
    SwitchBay,
grisel-davy's avatar
grisel-davy committed
78
    Building,
grisel-davy's avatar
grisel-davy committed
79
    Server,
Fernet Laouen's avatar
Fernet Laouen committed
80
    PortProfile,
81
)
82 83 84 85
from .forms import (
    EditPortForm,
    NewSwitchForm,
    EditSwitchForm,
86 87 88
    AddPortForm,
    EditRoomForm,
    StackForm,
89
    EditModelSwitchForm,
90
    EditConstructorSwitchForm,
91
    CreatePortsForm,
92
    AddAccessPointForm,
93 94
    EditAccessPointForm,
    EditSwitchBayForm,
Fernet Laouen's avatar
Fernet Laouen committed
95 96
    EditBuildingForm,
    EditPortProfileForm,
97
)
98

grisel-davy's avatar
grisel-davy committed
99 100 101 102 103
from subprocess import (
    Popen,
    PIPE
)

Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
104
from os.path import isfile
105

chirac's avatar
chirac committed
106

chirac's avatar
chirac committed
107
@login_required
108
@can_view_all(Switch)
Dalahro's avatar
Dalahro committed
109
def index(request):
110
    """ Vue d'affichage de tous les swicthes"""
111 112 113 114 115 116 117 118
    switch_list = (Switch.objects
                   .prefetch_related(Prefetch(
                       'interface_set',
                       queryset=(Interface.objects
                                 .select_related('ipv4__ip_type__extension')
                                 .select_related('domain__extension'))
                   ))
                   .select_related('stack'))
119 120 121 122 123 124
    switch_list = SortTable.sort(
        switch_list,
        request.GET.get('col'),
        request.GET.get('order'),
        SortTable.TOPOLOGIE_INDEX
    )
Fernet Laouen's avatar
Fernet Laouen committed
125

126
    pagination_number = GeneralOption.get_cached_value('pagination_number')
127
    switch_list = re2o_paginator(request, switch_list, pagination_number)
grisel-davy's avatar
grisel-davy committed
128

Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
129 130 131 132 133
    if any(
        service_link.need_regen
        for service_link in Service_link.objects.filter(
            service__service_type='graph_topo')
    ):
grisel-davy's avatar
grisel-davy committed
134
        make_machine_graph()
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
135 136
        for service_link in Service_link.objects.filter(
                service__service_type='graph_topo'):
grisel-davy's avatar
grisel-davy committed
137 138
            service_link.done_regen()

grisel-davy's avatar
grisel-davy committed
139 140
    if not isfile("/var/www/re2o/media/images/switchs.png"):
        make_machine_graph()
141 142 143
    return render(
        request,
        'topologie/index.html',
144 145 146 147 148 149 150 151
        {'switch_list': switch_list}
    )


@login_required
@can_view_all(PortProfile)
def index_port_profile(request):
    pagination_number = GeneralOption.get_cached_value('pagination_number')
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
152 153 154 155
    port_profile_list = PortProfile.objects.all().select_related(
        'vlan_untagged')
    port_profile_list = re2o_paginator(
        request, port_profile_list, pagination_number)
156 157 158 159
    return render(
        request,
        'topologie/index_portprofile.html',
        {'port_profile_list': port_profile_list}
160
    )
chirac's avatar
chirac committed
161

Dalahro's avatar
Dalahro committed
162

chirac's avatar
chirac committed
163
@login_required
164 165
@can_view_all(Port)
@can_view(Switch)
166
def index_port(request, switch, switchid):
167
    """ Affichage de l'ensemble des ports reliés à un switch particulier"""
168 169 170 171 172 173 174 175 176 177 178 179
    port_list = (Port.objects
                 .filter(switch=switch)
                 .select_related('room')
                 .select_related('machine_interface__domain__extension')
                 .select_related('machine_interface__machine__user')
                 .select_related('related__switch')
                 .prefetch_related(Prefetch(
                     'related__switch__interface_set',
                     queryset=(Interface.objects
                               .select_related('domain__extension'))
                 ))
                 .select_related('switch'))
180 181 182 183 184 185
    port_list = SortTable.sort(
        port_list,
        request.GET.get('col'),
        request.GET.get('order'),
        SortTable.TOPOLOGIE_INDEX_PORT
    )
186 187 188 189 190 191 192 193 194
    return render(
        request,
        'topologie/index_p.html',
        {
            'port_list': port_list,
            'id_switch': switchid,
            'nom_switch': switch
        }
    )
chirac's avatar
chirac committed
195

Dalahro's avatar
Dalahro committed
196

chirac's avatar
chirac committed
197
@login_required
198
@can_view_all(Room)
chirac's avatar
chirac committed
199
def index_room(request):
200
    """ Affichage de l'ensemble des chambres"""
201
    room_list = Room.objects
202 203 204 205 206 207
    room_list = SortTable.sort(
        room_list,
        request.GET.get('col'),
        request.GET.get('order'),
        SortTable.TOPOLOGIE_INDEX_ROOM
    )
208
    pagination_number = GeneralOption.get_cached_value('pagination_number')
209
    room_list = re2o_paginator(request, room_list, pagination_number)
210 211 212 213 214
    return render(
        request,
        'topologie/index_room.html',
        {'room_list': room_list}
    )
chirac's avatar
chirac committed
215

chirac's avatar
chirac committed
216

217
@login_required
218 219
@can_view_all(AccessPoint)
def index_ap(request):
220
    """ Affichage de l'ensemble des bornes"""
221 222 223 224 225 226 227
    ap_list = (AccessPoint.objects
               .prefetch_related(Prefetch(
                   'interface_set',
                   queryset=(Interface.objects
                             .select_related('ipv4__ip_type__extension')
                             .select_related('domain__extension'))
               )))
228 229
    ap_list = SortTable.sort(
        ap_list,
230 231 232 233 234
        request.GET.get('col'),
        request.GET.get('order'),
        SortTable.TOPOLOGIE_INDEX_BORNE
    )
    pagination_number = GeneralOption.get_cached_value('pagination_number')
235
    ap_list = re2o_paginator(request, ap_list, pagination_number)
236 237 238 239 240
    return render(
        request,
        'topologie/index_ap.html',
        {'ap_list': ap_list}
    )
241 242


243
@login_required
244
@can_view_all(Stack, Building, SwitchBay)
245
def index_physical_grouping(request):
chirac's avatar
chirac committed
246
    """Affichage de la liste des stacks (affiche l'ensemble des switches)"""
247 248 249 250
    stack_list = (Stack.objects
                  .prefetch_related(
                      'switch_set__interface_set__domain__extension'
                  ))
251 252
    building_list = Building.objects.all()
    switch_bay_list = SwitchBay.objects.select_related('building')
253 254 255 256 257 258
    stack_list = SortTable.sort(
        stack_list,
        request.GET.get('col'),
        request.GET.get('order'),
        SortTable.TOPOLOGIE_INDEX_STACK
    )
259 260 261 262 263 264 265 266 267 268 269 270
    building_list = SortTable.sort(
        building_list,
        request.GET.get('col'),
        request.GET.get('order'),
        SortTable.TOPOLOGIE_INDEX_BUILDING
    )
    switch_bay_list = SortTable.sort(
        switch_bay_list,
        request.GET.get('col'),
        request.GET.get('order'),
        SortTable.TOPOLOGIE_INDEX_SWITCH_BAY
    )
271 272 273 274 275 276 277 278 279
    return render(
        request,
        'topologie/index_physical_grouping.html',
        {
            'stack_list': stack_list,
            'switch_bay_list': switch_bay_list,
            'building_list': building_list,
        }
    )
280 281


282
@login_required
283
@can_view_all(ModelSwitch, ConstructorSwitch)
284 285
def index_model_switch(request):
    """ Affichage de l'ensemble des modèles de switches"""
286
    model_switch_list = ModelSwitch.objects.select_related('constructor')
287 288 289 290 291 292 293 294 295 296 297 298 299
    constructor_switch_list = ConstructorSwitch.objects
    model_switch_list = SortTable.sort(
        model_switch_list,
        request.GET.get('col'),
        request.GET.get('order'),
        SortTable.TOPOLOGIE_INDEX_MODEL_SWITCH
    )
    constructor_switch_list = SortTable.sort(
        constructor_switch_list,
        request.GET.get('col'),
        request.GET.get('order'),
        SortTable.TOPOLOGIE_INDEX_CONSTRUCTOR_SWITCH
    )
300 301 302 303 304 305 306 307
    return render(
        request,
        'topologie/index_model_switch.html',
        {
            'model_switch_list': model_switch_list,
            'constructor_switch_list': constructor_switch_list,
        }
    )
308 309


chirac's avatar
chirac committed
310
@login_required
311
@can_create(Port)
312
def new_port(request, switchid):
313
    """ Nouveau port"""
Dalahro's avatar
Dalahro committed
314
    try:
315
        switch = Switch.objects.get(pk=switchid)
Dalahro's avatar
Dalahro committed
316 317
    except Switch.DoesNotExist:
        messages.error(request, u"Switch inexistant")
318
        return redirect(reverse('topologie:index'))
Dalahro's avatar
Dalahro committed
319 320 321 322 323
    port = AddPortForm(request.POST or None)
    if port.is_valid():
        port = port.save(commit=False)
        port.switch = switch
        try:
324
            port.save()
Dalahro's avatar
Dalahro committed
325 326
            messages.success(request, "Port ajouté")
        except IntegrityError:
chirac's avatar
chirac committed
327
            messages.error(request, "Ce port existe déjà")
328
        return redirect(reverse(
329
            'topologie:index-port',
330 331 332 333 334 335
            kwargs={'switchid': switchid}
        ))
    return form(
        {'id_switch': switchid, 'topoform': port, 'action_name': 'Ajouter'},
        'topologie/topo.html',
        request)
chirac's avatar
chirac committed
336

Dalahro's avatar
Dalahro committed
337

chirac's avatar
chirac committed
338
@login_required
339
@can_edit(Port)
340
def edit_port(request, port_object, **_kwargs):
chirac's avatar
chirac committed
341 342
    """ Edition d'un port. Permet de changer le switch parent et
    l'affectation du port"""
343

344
    port = EditPortForm(request.POST or None, instance=port_object)
Dalahro's avatar
Dalahro committed
345
    if port.is_valid():
346 347 348
        if port.changed_data:
            port.save()
            messages.success(request, "Le port a bien été modifié")
349 350
        return redirect(reverse(
            'topologie:index-port',
351
            kwargs={'switchid': str(port_object.switch.id)}
352 353 354 355 356 357 358 359 360 361
        ))
    return form(
        {
            'id_switch': str(port_object.switch.id),
            'topoform': port,
            'action_name': 'Editer'
        },
        'topologie/topo.html',
        request
    )
chirac's avatar
chirac committed
362

chirac's avatar
chirac committed
363

364
@login_required
365
@can_delete(Port)
366
def del_port(request, port, **_kwargs):
367
    """ Supprime le port"""
Gabriel Detraz's avatar
Gabriel Detraz committed
368 369
    if request.method == "POST":
        try:
370 371
            port.delete()
            messages.success(request, "Le port a été détruit")
Gabriel Detraz's avatar
Gabriel Detraz committed
372
        except ProtectedError:
373 374 375 376 377
            messages.error(
                request,
                ("Le port %s est affecté à un autre objet, impossible "
                 "de le supprimer" % port)
            )
378 379
        return redirect(reverse(
            'topologie:index-port',
380 381
            kwargs={'switchid': str(port.switch.id)}
        ))
chirac's avatar
chirac committed
382 383
    return form({'objet': port}, 'topologie/delete.html', request)

Gabriel Detraz's avatar
Gabriel Detraz committed
384 385

@login_required
386
@can_create(Stack)
387
def new_stack(request):
388
    """Ajoute un nouveau stack : stackid_min, max, et nombre de switches"""
389
    stack = StackForm(request.POST or None)
chirac's avatar
chirac committed
390
    if stack.is_valid():
391
        stack.save()
chirac's avatar
chirac committed
392
        messages.success(request, "Stack crée")
Gabriel Detraz's avatar
Gabriel Detraz committed
393
        return redirect(reverse('topologie:index-physical-grouping'))
394 395 396 397 398
    return form(
        {'topoform': stack, 'action_name': 'Créer'},
        'topologie/topo.html',
        request
    )
399 400 401


@login_required
402
@can_edit(Stack)
403
def edit_stack(request, stack, **_kwargs):
chirac's avatar
chirac committed
404
    """Edition d'un stack (nombre de switches, nom...)"""
405 406
    stack = StackForm(request.POST or None, instance=stack)
    if stack.is_valid():
407 408
        if stack.changed_data:
            stack.save()
Gabriel Detraz's avatar
Gabriel Detraz committed
409
        return redirect(reverse('topologie:index-physical-grouping'))
410 411 412 413 414
    return form(
        {'topoform': stack, 'action_name': 'Editer'},
        'topologie/topo.html',
        request
    )
chirac's avatar
chirac committed
415

416

417
@login_required
418
@can_delete(Stack)
419
def del_stack(request, stack, **_kwargs):
chirac's avatar
chirac committed
420
    """Supprime un stack"""
421 422
    if request.method == "POST":
        try:
423 424
            stack.delete()
            messages.success(request, "La stack a eté détruite")
425
        except ProtectedError:
426 427 428 429 430
            messages.error(
                request,
                ("La stack %s est affectée à un autre objet, impossible "
                 "de la supprimer" % stack)
            )
431
        return redirect(reverse('topologie:index-physical-grouping'))
chirac's avatar
chirac committed
432 433
    return form({'objet': stack}, 'topologie/delete.html', request)

434

435
@login_required
436
@can_edit(Stack)
437
def edit_switchs_stack(request, stack, **_kwargs):
chirac's avatar
chirac committed
438
    """Permet d'éditer la liste des switches dans une stack et l'ajouter"""
439

440 441 442 443 444 445 446 447
    if request.method == "POST":
        pass
    else:
        context = {'stack': stack}
        context['switchs_stack'] = stack.switchs_set.all()
        context['switchs_autres'] = Switch.object.filter(stack=None)


chirac's avatar
chirac committed
448
@login_required
449
@can_create(Switch)
Dalahro's avatar
Dalahro committed
450
def new_switch(request):
chirac's avatar
chirac committed
451 452 453
    """ Creation d'un switch. Cree en meme temps l'interface et la machine
    associée. Vue complexe. Appelle successivement les 4 models forms
    adaptés : machine, interface, domain et switch"""
454
    switch = NewSwitchForm(
455 456 457
        request.POST or None,
        user=request.user
    )
458
    interface = AddInterfaceForm(
chirac's avatar
chirac committed
459
        request.POST or None,
460
        user=request.user
461
    )
462
    domain = DomainForm(
chirac's avatar
chirac committed
463
        request.POST or None,
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
464
    )
465
    if switch.is_valid() and interface.is_valid():
466
        user = AssoOption.get_cached_value('utilisateur_asso')
467
        if not user:
468 469 470 471 472
            messages.error(
                request,
                ("L'user association n'existe pas encore, veuillez le "
                 "créer ou le linker dans preferences")
            )
473
            return redirect(reverse('topologie:index'))
474 475 476 477
        new_switch_obj = switch.save(commit=False)
        new_switch_obj.user = user
        new_interface_obj = interface.save(commit=False)
        domain.instance.interface_parent = new_interface_obj
478
        if domain.is_valid():
479 480 481 482 483 484
            new_domain_obj = domain.save(commit=False)
            new_switch_obj.save()
            new_interface_obj.machine = new_switch_obj
            new_interface_obj.save()
            new_domain_obj.interface_parent = new_interface_obj
            new_domain_obj.save()
485 486
            messages.success(request, "Le switch a été créé")
            return redirect(reverse('topologie:index'))
487
    i_mbf_param = generate_ipv4_mbf_param(interface, False)
488 489 490 491 492 493 494 495 496 497 498
    return form(
        {
            'topoform': interface,
            'machineform': switch,
            'domainform': domain,
            'i_mbf_param': i_mbf_param,
            'device': 'switch',
        },
        'topologie/topo_more.html',
        request
    )
chirac's avatar
chirac committed
499

Dalahro's avatar
Dalahro committed
500

501
@login_required
502
@can_create(Port)
503
def create_ports(request, switchid):
504 505
    """ Création d'une liste de ports pour un switch."""
    try:
506
        switch = Switch.objects.get(pk=switchid)
507 508
    except Switch.DoesNotExist:
        messages.error(request, u"Switch inexistant")
509
        return redirect(reverse('topologie:index'))
510

511
    s_begin = s_end = 0
512 513
    nb_ports = switch.ports.count()
    if nb_ports > 0:
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
514 515 516
        ports = switch.ports.order_by('port').values('port')
        s_begin = ports.first().get('port')
        s_end = ports.last().get('port')
517

518 519
    port_form = CreatePortsForm(
        request.POST or None,
520
        initial={'begin': s_begin, 'end': s_end}
521
    )
522

523 524 525
    if port_form.is_valid():
        begin = port_form.cleaned_data['begin']
        end = port_form.cleaned_data['end']
526 527 528 529 530
        try:
            switch.create_ports(begin, end)
            messages.success(request, "Ports créés.")
        except ValidationError as e:
            messages.error(request, ''.join(e))
531 532
        return redirect(reverse(
            'topologie:index-port',
533
            kwargs={'switchid': switchid}
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
534
        ))
535 536 537 538 539
    return form(
        {'id_switch': switchid, 'topoform': port_form},
        'topologie/switch.html',
        request
    )
540

Dalahro's avatar
Dalahro committed
541

chirac's avatar
chirac committed
542
@login_required
543
@can_edit(Switch)
544
def edit_switch(request, switch, switchid):
chirac's avatar
chirac committed
545 546
    """ Edition d'un switch. Permet de chambre nombre de ports,
    place dans le stack, interface et machine associée"""
547

548
    switch_form = EditSwitchForm(
chirac's avatar
chirac committed
549
        request.POST or None,
550
        instance=switch,
551
        user=request.user
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
552
    )
553
    interface_form = EditInterfaceForm(
chirac's avatar
chirac committed
554
        request.POST or None,
555
        instance=switch.interface_set.first(),
556
        user=request.user
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
557
    )
558
    domain_form = DomainForm(
chirac's avatar
chirac committed
559
        request.POST or None,
560
        instance=switch.interface_set.first().domain
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
561
    )
562
    if switch_form.is_valid() and interface_form.is_valid():
563 564 565
        new_switch_obj = switch_form.save(commit=False)
        new_interface_obj = interface_form.save(commit=False)
        new_domain_obj = domain_form.save(commit=False)
566
        if switch_form.changed_data:
567
            new_switch_obj.save()
568
        if interface_form.changed_data:
569
            new_interface_obj.save()
570
        if domain_form.changed_data:
571
            new_domain_obj.save()
572
        messages.success(request, "Le switch a bien été modifié")
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
573
        return redirect(reverse('topologie:index'))
574 575 576 577 578 579 580 581 582 583 584 585 586
    i_mbf_param = generate_ipv4_mbf_param(interface_form, False)
    return form(
        {
            'id_switch': switchid,
            'topoform': interface_form,
            'machineform': switch_form,
            'domainform': domain_form,
            'i_mbf_param': i_mbf_param,
            'device': 'switch',
        },
        'topologie/topo_more.html',
        request
    )
chirac's avatar
chirac committed
587

chirac's avatar
chirac committed
588

589
@login_required
590 591 592
@can_create(AccessPoint)
def new_ap(request):
    """ Creation d'une ap. Cree en meme temps l'interface et la machine
593 594
    associée. Vue complexe. Appelle successivement les 3 models forms
    adaptés : machine, interface, domain et switch"""
595
    ap = AddAccessPointForm(
596 597 598
        request.POST or None,
        user=request.user
    )
599
    interface = AddInterfaceForm(
600 601 602 603 604
        request.POST or None,
        user=request.user
    )
    domain = DomainForm(
        request.POST or None,
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
605
    )
606
    if ap.is_valid() and interface.is_valid():
607 608
        user = AssoOption.get_cached_value('utilisateur_asso')
        if not user:
609 610 611 612 613
            messages.error(
                request,
                ("L'user association n'existe pas encore, veuillez le "
                 "créer ou le linker dans preferences")
            )
614
            return redirect(reverse('topologie:index'))
615 616 617 618
        new_ap_obj = ap.save(commit=False)
        new_ap_obj.user = user
        new_interface_obj = interface.save(commit=False)
        domain.instance.interface_parent = new_interface_obj
619
        if domain.is_valid():
620 621 622 623 624 625
            new_domain_obj = domain.save(commit=False)
            new_ap_obj.save()
            new_interface_obj.machine = new_ap_obj
            new_interface_obj.save()
            new_domain_obj.interface_parent = new_interface_obj
            new_domain_obj.save()
626
            messages.success(request, "La borne a été créé")
627
            return redirect(reverse('topologie:index-ap'))
628
    i_mbf_param = generate_ipv4_mbf_param(interface, False)
629 630 631 632 633 634 635 636 637 638 639
    return form(
        {
            'topoform': interface,
            'machineform': ap,
            'domainform': domain,
            'i_mbf_param': i_mbf_param,
            'device': 'wifi ap',
        },
        'topologie/topo_more.html',
        request
    )
640 641 642


@login_required
643
@can_edit(AccessPoint)
644
def edit_ap(request, ap, **_kwargs):
645 646
    """ Edition d'un switch. Permet de chambre nombre de ports,
    place dans le stack, interface et machine associée"""
647
    interface_form = EditInterfaceForm(
648 649
        request.POST or None,
        user=request.user,
650
        instance=ap.interface_set.first()
651
    )
652
    ap_form = EditAccessPointForm(
653 654
        request.POST or None,
        user=request.user,
655
        instance=ap
656 657 658
    )
    domain_form = DomainForm(
        request.POST or None,
659
        instance=ap.interface_set.first().domain
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
660
    )
661
    if ap_form.is_valid() and interface_form.is_valid():
662 663
        user = AssoOption.get_cached_value('utilisateur_asso')
        if not user:
664 665 666 667 668
            messages.error(
                request,
                ("L'user association n'existe pas encore, veuillez le "
                 "créer ou le linker dans preferences")
            )
669
            return redirect(reverse('topologie:index-ap'))
670 671 672
        new_ap_obj = ap_form.save(commit=False)
        new_interface_obj = interface_form.save(commit=False)
        new_domain_obj = domain_form.save(commit=False)
673
        if ap_form.changed_data:
674
            new_ap_obj.save()
675
        if interface_form.changed_data:
676
            new_interface_obj.save()
677
        if domain_form.changed_data:
678
            new_domain_obj.save()
679
        messages.success(request, "La borne a été modifiée")
680
        return redirect(reverse('topologie:index-ap'))
681 682 683 684 685 686 687 688 689 690 691 692 693
    i_mbf_param = generate_ipv4_mbf_param(interface_form, False)
    return form(
        {
            'topoform': interface_form,
            'machineform': ap_form,
            'domainform': domain_form,
            'i_mbf_param': i_mbf_param,
            'device': 'wifi ap',
        },
        'topologie/topo_more.html',
        request
    )

694

chirac's avatar
chirac committed
695
@login_required
696
@can_create(Room)
chirac's avatar
chirac committed
697
def new_room(request):
698
    """Nouvelle chambre """
chirac's avatar
chirac committed
699 700
    room = EditRoomForm(request.POST or None)
    if room.is_valid():
701
        room.save()
chirac's avatar
chirac committed
702
        messages.success(request, "La chambre a été créé")
703
        return redirect(reverse('topologie:index-room'))
704 705 706 707 708
    return form(
        {'topoform': room, 'action_name': 'Ajouter'},
        'topologie/topo.html',
        request
    )
chirac's avatar
chirac committed
709

chirac's avatar
chirac committed
710 711

@login_required
712
@can_edit(Room)
713
def edit_room(request, room, **_kwargs):
714
    """ Edition numero et details de la chambre"""
chirac's avatar
chirac committed
715 716
    room = EditRoomForm(request.POST or None, instance=room)
    if room.is_valid():
717 718 719
        if room.changed_data:
            room.save()
            messages.success(request, "La chambre a bien été modifiée")
720
        return redirect(reverse('topologie:index-room'))
721 722 723 724 725
    return form(
        {'topoform': room, 'action_name': 'Editer'},
        'topologie/topo.html',
        request
    )
chirac's avatar
chirac committed
726

chirac's avatar
chirac committed
727 728

@login_required
729
@can_delete(Room)
730
def del_room(request, room, **_kwargs):
731
    """ Suppression d'un chambre"""
chirac's avatar
chirac committed
732
    if request.method == "POST":
733
        try:
734 735
            room.delete()
            messages.success(request, "La chambre/prise a été détruite")
736
        except ProtectedError:
737 738 739 740 741
            messages.error(
                request,
                ("La chambre %s est affectée à un autre objet, impossible "
                 "de la supprimer (switch ou user)" % room)
            )
742
        return redirect(reverse('topologie:index-room'))
743 744 745 746 747
    return form(
        {'objet': room, 'objet_name': 'Chambre'},
        'topologie/delete.html',
        request
    )
748 749 750


@login_required
751
@can_create(ModelSwitch)
752 753 754 755
def new_model_switch(request):
    """Nouveau modèle de switch"""
    model_switch = EditModelSwitchForm(request.POST or None)
    if model_switch.is_valid():
756
        model_switch.save()
757
        messages.success(request, "Le modèle a été créé")
758
        return redirect(reverse('topologie:index-model-switch'))
759 760 761 762 763
    return form(
        {'topoform': model_switch, 'action_name': 'Ajouter'},
        'topologie/topo.html',
        request
    )
764 765 766


@login_required
767
@can_edit(ModelSwitch)
768
def edit_model_switch(request, model_switch, **_kwargs):
769
    """ Edition d'un modèle de switch"""
770

771 772 773 774
    model_switch = EditModelSwitchForm(
        request.POST or None,
        instance=model_switch
    )
775
    if model_switch.is_valid():
776 777 778
        if model_switch.changed_data:
            model_switch.save()
            messages.success(request, "Le modèle a bien été modifié")
779
        return redirect(reverse('topologie:index-model-switch'))
780 781 782 783 784
    return form(
        {'topoform': model_switch, 'action_name': 'Editer'},
        'topologie/topo.html',
        request
    )
785 786 787


@login_required
788
@can_delete(ModelSwitch)
789
def del_model_switch(request, model_switch, **_kwargs):
790 791 792
    """ Suppression d'un modèle de switch"""
    if request.method == "POST":
        try:
793 794
            model_switch.delete()
            messages.success(request, "Le modèle a été détruit")
795
        except ProtectedError:
796 797 798 799 800
            messages.error(
                request,
                ("Le modèle %s est affectée à un autre objet, impossible "
                 "de la supprimer (switch ou user)" % model_switch)
            )
801
        return redirect(reverse('topologie:index-model-switch'))
802 803 804 805 806
    return form(
        {'objet': model_switch, 'objet_name': 'Modèle de switch'},
        'topologie/delete.html',
        request
    )
807 808


809 810 811 812 813 814 815 816
@login_required
@can_create(SwitchBay)
def new_switch_bay(request):
    """Nouvelle baie de switch"""
    switch_bay = EditSwitchBayForm(request.POST or None)
    if switch_bay.is_valid():
        switch_bay.save()
        messages.success(request, "La baie a été créé")
817
        return redirect(reverse('topologie:index-physical-grouping'))
818 819 820 821 822
    return form(
        {'topoform': switch_bay, 'action_name': 'Ajouter'},
        'topologie/topo.html',
        request
    )
823 824 825 826


@login_required
@can_edit(SwitchBay)
827
def edit_switch_bay(request, switch_bay, **_kwargs):
828 829 830 831 832 833
    """ Edition d'une baie de switch"""
    switch_bay = EditSwitchBayForm(request.POST or None, instance=switch_bay)
    if switch_bay.is_valid():
        if switch_bay.changed_data:
            switch_bay.save()
            messages.success(request, "Le switch a bien été modifié")
834
        return redirect(reverse('topologie:index-physical-grouping'))
835 836 837 838 839
    return form(
        {'topoform': switch_bay, 'action_name': 'Editer'},
        'topologie/topo.html',
        request
    )
840 841 842 843


@login_required
@can_delete(SwitchBay)
844
def del_switch_bay(request, switch_bay, **_kwargs):
845 846 847 848 849 850
    """ Suppression d'une baie de switch"""
    if request.method == "POST":
        try:
            switch_bay.delete()
            messages.success(request, "La baie a été détruite")
        except ProtectedError:
851 852 853 854 855
            messages.error(
                request,
                ("La baie %s est affecté à un autre objet, impossible "
                 "de la supprimer (switch ou user)" % switch_bay)
            )
856
        return redirect(reverse('topologie:index-physical-grouping'))
857 858 859 860 861
    return form(
        {'objet': switch_bay, 'objet_name': 'Baie de switch'},
        'topologie/delete.html',
        request
    )
862 863


864 865 866 867 868 869 870 871
@login_required
@can_create(Building)
def new_building(request):
    """Nouveau batiment"""
    building = EditBuildingForm(request.POST or None)
    if building.is_valid():
        building.save()
        messages.success(request, "Le batiment a été créé")
872
        return redirect(reverse('topologie:index-physical-grouping'))
873 874 875 876 877
    return form(
        {'topoform': building, 'action_name': 'Ajouter'},
        'topologie/topo.html',
        request
    )
878 879 880 881


@login_required
@can_edit(Building)
882
def edit_building(request, building, **_kwargs):
883 884 885 886 887 888
    """ Edition d'un batiment"""
    building = EditBuildingForm(request.POST or None, instance=building)
    if building.is_valid():
        if building.changed_data:
            building.save()
            messages.success(request, "Le batiment a bien été modifié")
889
        return redirect(reverse('topologie:index-physical-grouping'))
890 891 892 893 894
    return form(
        {'topoform': building, 'action_name': 'Editer'},
        'topologie/topo.html',
        request
    )
895 896 897 898


@login_required
@can_delete(Building)
899
def del_building(request, building, **_kwargs):
900 901 902 903 904 905
    """ Suppression d'un batiment"""
    if request.method == "POST":
        try:
            building.delete()
            messages.success(request, "La batiment a été détruit")
        except ProtectedError:
906 907 908 909 910
            messages.error(
                request,
                ("Le batiment %s est affecté à un autre objet, impossible "
                 "de la supprimer (switch ou user)" % building)
            )
911
        return redirect(reverse('topologie:index-physical-grouping'))
912 913 914 915 916
    return form(
        {'objet': building, 'objet_name': 'Bâtiment'},
        'topologie/delete.html',
        request
    )
917 918


919
@login_required
920
@can_create(ConstructorSwitch)
921 922 923 924
def new_constructor_switch(request):
    """Nouveau constructeur de switch"""
    constructor_switch = EditConstructorSwitchForm(request.POST or None)
    if constructor_switch.is_valid():
925
        constructor_switch.save()
926
        messages.success(request, "Le constructeur a été créé")
927
        return redirect(reverse('topologie:index-model-switch'))
928 929 930 931 932
    return form(
        {'topoform': constructor_switch, 'action_name': 'Ajouter'},
        'topologie/topo.html',
        request
    )
933 934 935


@login_required
936
@can_edit(ConstructorSwitch)
937
def edit_constructor_switch(request, constructor_switch, **_kwargs):
938
    """ Edition d'un constructeur de switch"""
939

940 941 942 943
    constructor_switch = EditConstructorSwitchForm(
        request.POST or None,
        instance=constructor_switch
    )
944
    if constructor_switch.is_valid():
945 946 947
        if constructor_switch.changed_data:
            constructor_switch.save()
            messages.success(request, "Le modèle a bien été modifié")
948
        return redirect(reverse('topologie:index-model-switch'))
949 950 951 952 953
    return form(
        {'topoform': constructor_switch, 'action_name': 'Editer'},
        'topologie/topo.html',
        request
    )
954 955 956


@login_required
957
@can_delete(ConstructorSwitch)
958
def del_constructor_switch(request, constructor_switch, **_kwargs):
959 960 961
    """ Suppression d'un constructeur de switch"""
    if request.method == "POST":
        try:
962 963
            constructor_switch.delete()
            messages.success(request, "Le constructeur a été détruit")
964
        except ProtectedError:
965 966 967 968 969
            messages.error(
                request,
                ("Le constructeur %s est affecté à un autre objet, impossible "
                 "de la supprimer (switch ou user)" % constructor_switch)
            )
970
        return redirect(reverse('topologie:index-model-switch'))
971 972 973
    return form({
        'objet': constructor_switch,
        'objet_name': 'Constructeur de switch'
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
974
    }, 'topologie/delete.html', request)
975 976


Fernet Laouen's avatar
Fernet Laouen committed
977 978 979 980
@login_required
@can_create(PortProfile)
def new_port_profile(request):
    """Create a new port profile"""
981
    port_profile = EditPortProfileForm(request.POST or None)
Fernet Laouen's avatar
Fernet Laouen committed
982 983 984 985 986 987 988 989 990 991 992 993 994 995 996
    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"""
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
997 998
    port_profile = EditPortProfileForm(
        request.POST or None, instance=port_profile)
Fernet Laouen's avatar
Fernet Laouen committed
999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017
    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()
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
1018 1019
            messages.success(request,
                             _("The port profile was successfully deleted"))
Fernet Laouen's avatar
Fernet Laouen committed
1020
        except ProtectedError:
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
1021 1022
            messages.success(request,
                             _("Impossible to delete the port profile"))
Fernet Laouen's avatar
Fernet Laouen committed
1023 1024
        return redirect(reverse('topologie:index'))
    return form(
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
1025 1026 1027
        {'objet': port_profile, 'objet_name': _("Port profile")},
        'topologie/delete.html',
        request
Fernet Laouen's avatar
Fernet Laouen committed
1028 1029
    )

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

1031 1032
def make_machine_graph():
    """
grisel-davy's avatar
grisel-davy committed
1033
    Create the graph of switchs, machines and access points.
1034
    """
grisel-davy's avatar
grisel-davy committed
1035 1036
    dico = {
        'subs': [],
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
1037
        'links': [],
grisel-davy's avatar
grisel-davy committed
1038 1039
        'alone': [],
        'colors': {
grisel-davy's avatar
grisel-davy committed
1040
            'head': "#7f0505",  # Color parameters for the graph
grisel-davy's avatar
grisel-davy committed
1041 1042 1043 1044 1045 1046
            'back': "#b5adad",
            'texte': "#563d01",
            'border_bornes': "#02078e",
            'head_bornes': "#25771c",
            'head_server': "#1c3777"
        }
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
1047
    }
grisel-davy's avatar
grisel-davy committed
1048 1049
    missing = list(Switch.objects.all())
    detected = []
grisel-davy's avatar
grisel-davy committed
1050 1051
    for building in Building.objects.all():  # Visit all buildings

grisel-davy's avatar
grisel-davy committed
1052 1053
        dico['subs'].append(
            {
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
1054 1055 1056 1057 1058
                'bat_id': building.id,
                'bat_name': building,
                'switchs': [],
                'bornes': [],
                'machines': []
grisel-davy's avatar
grisel-davy committed
1059 1060
            }
        )
grisel-davy's avatar
grisel-davy committed
1061
        # Visit all switchs in this building
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
1062
        for switch in Switch.objects.filter(switchbay__building=building):
grisel-davy's avatar
grisel-davy committed
1063 1064 1065 1066 1067 1068 1069 1070
            dico['subs'][-1]['switchs'].append({
                'name': switch.main_interface().domain.name,
                'nombre': switch.number,
                'model': switch.model,
                'id': switch.id,
                'batiment': building,
                'ports': []
            })
grisel-davy's avatar
grisel-davy committed
1071
            # visit all ports of this switch and add the switchs linked to it
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
1072
            for port in switch.ports.filter(related__isnull=False):
grisel-davy's avatar
grisel-davy committed
1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089
                dico['subs'][-1]['switchs'][-1]['ports'].append({
                    'numero': port.port,
                    'related': port.related.switch.main_interface().domain.name
                })

        for ap in AccessPoint.all_ap_in(building):
            dico['subs'][-1]['bornes'].append({
                'name': ap.short_name,
                'switch': ap.switch()[0].main_interface().domain.name,
                'port': ap.switch()[0].ports.filter(
                    machine_interface__machine=ap
                )[0].port
            })
        for server in Server.all_server_in(building):
            dico['subs'][-1]['machines'].append({
                'name': server.short_name,
                'switch': server.switch()[0].main_interface().domain.name,
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
1090 1091 1092
                'port': Port.objects.filter(
                    machine_interface__machine=server
                )[0].port
grisel-davy's avatar
grisel-davy committed
1093 1094
            })

grisel-davy's avatar
grisel-davy committed
1095
    # While the list of forgotten ones is not empty
grisel-davy's avatar
grisel-davy committed
1096
    while missing:
grisel-davy's avatar
grisel-davy committed
1097
        if missing[0].ports.count():  # The switch is not empty
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
1098 1099
            links, new_detected = recursive_switchs(
                missing[0], None, [missing[0]])
grisel-davy's avatar
grisel-davy committed
1100 1101
            for link in links:
                dico['links'].append(link)
grisel-davy's avatar
grisel-davy committed
1102
            # Update the lists of missings and already detected switchs
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
1103
            missing = [i for i in missing if i not in new_detected]
grisel-davy's avatar
grisel-davy committed
1104
            detected += new_detected
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
1105 1106
        # If the switch have no ports, don't explore it and hop to the next one
        else:
grisel-davy's avatar
grisel-davy committed
1107
            del missing[0]
grisel-davy's avatar
grisel-davy committed
1108
    # Switchs that are not connected or not in a building
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
1109 1110
    for switch in Switch.objects.filter(
            switchbay__isnull=True).exclude(ports__related__isnull=False):
grisel-davy's avatar
grisel-davy committed
1111 1112 1113
        dico['alone'].append({
            'id': switch.id,
            'name': switch.main_interface().domain.name
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
1114
        })
grisel-davy's avatar
grisel-davy committed
1115

Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
1116 1117
    # generate the dot file
    dot_data = generate_dot(dico, 'topologie/graph_switch.dot')
grisel-davy's avatar
grisel-davy committed
1118

Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
1119 1120
    # Create a temporary file to store the dot data
    f = tempfile.NamedTemporaryFile(mode='w+', encoding='utf-8', delete=False)
grisel-davy's avatar
grisel-davy committed
1121
    with f:
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
1122
        f.write(dot_data)
grisel-davy's avatar
grisel-davy committed
1123
    unflatten = Popen(  # unflatten the graph to make it look better
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
1124
        ["unflatten", "-l", "3", f.name],
grisel-davy's avatar
grisel-davy committed
1125 1126
        stdout=PIPE
    )
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
1127
    Popen(  # pipe the result of the first command into the second
grisel-davy's avatar
grisel-davy committed
1128 1129 1130 1131
        ["dot", "-Tpng", "-o", MEDIA_ROOT + "/images/switchs.png"],
        stdin=unflatten.stdout,
        stdout=PIPE
    )
1132

Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
1133 1134

def generate_dot(data, template):
grisel-davy's avatar
grisel-davy committed
1135
    """create the dot file
grisel-davy's avatar
grisel-davy committed
1136 1137 1138
    :param data: dictionary passed to the template
    :param template: path to the dot template
    :return: all the lines of the dot file"""
grisel-davy's avatar
grisel-davy committed
1139
    t = loader.get_template(template)
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
1140 1141
    if not isinstance(t, Template) and \
       not (hasattr(t, 'template') and isinstance(t.template, Template)):
grisel-davy's avatar
grisel-davy committed
1142 1143 1144 1145 1146 1147 1148
        raise Exception("Le template par défaut de Django n'est pas utilisé."
                        "Cela peut mener à des erreurs de rendu."
                        "Vérifiez les paramètres")
    c = Context(data).flatten()
    dot = t.render(c)
    return(dot)

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

grisel-davy's avatar
grisel-davy committed
1150 1151
def recursive_switchs(switch_start, switch_before, detected):
    """Visit the switch and travel to the switchs linked to it.
grisel-davy's avatar
grisel-davy committed
1152
    :param switch_start: the switch to begin the visit on
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
1153 1154 1155 1156 1157 1158 1159
    :param switch_before: the switch that you come from.
        None if switch_start is the first one
    :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
    """
grizzly's avatar
grizzly committed
1160
    detected.append(switch_start)
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
1161 1162 1163 1164 1165 1166 1167
    links_return = []  # list of dictionaries of the links to be detected
    # create links to every switchs below
    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:
grisel-davy's avatar
grisel-davy committed
1168
            links = {  # Dictionary of a link
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
1169 1170
                'depart': switch_start.id,
                'arrive': port.related.switch.id
grisel-davy's avatar
grisel-davy committed
1171
            }
grisel-davy's avatar
grisel-davy committed
1172
            links_return.append(links)  # Add current and below levels links
grizzly's avatar
grizzly committed
1173

Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
1174 1175 1176 1177 1178 1179 1180 1181 1182
    # go down on every related switchs
    for port in switch_start.ports.filter(related__isnull=False):
        # The switch at the end of this link has not been visited
        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:
grizzly's avatar
grizzly committed
1183
                if link:
Levy--Falk Hugo's avatar
Levy--Falk Hugo committed
1184
                    links_return.append(link)
grisel-davy's avatar
grisel-davy committed
1185
    return (links_return, detected)