Test Failed
Pull Request — master (#166)
by Antonio
04:21 queued 56s
created

build.main.Main._is_duplicated_evc()   A

Complexity

Conditions 4

Size

Total Lines 14
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 4.0312

Importance

Changes 0
Metric Value
cc 4
eloc 5
nop 2
dl 0
loc 14
rs 10
c 0
b 0
f 0
ccs 7
cts 8
cp 0.875
crap 4.0312
1
# pylint: disable=protected-access
2 1
"""Main module of kytos/mef_eline Kytos Network Application.
3
4
NApp to provision circuits from user request.
5
"""
6 1
from threading import Lock
7
8 1
from flask import jsonify, request
9 1
from werkzeug.exceptions import (BadRequest, Conflict, Forbidden,
10
                                 MethodNotAllowed, NotFound,
11
                                 UnsupportedMediaType)
12
13 1
from kytos.core import KytosNApp, log, rest
14 1
from kytos.core.events import KytosEvent
15 1
from kytos.core.helpers import listen_to
16 1
from kytos.core.interface import TAG, UNI
17 1
from kytos.core.link import Link
18 1
from napps.kytos.mef_eline import controllers, settings
19 1
from napps.kytos.mef_eline.exceptions import InvalidPath
20 1
from napps.kytos.mef_eline.models import EVC, DynamicPathManager, Path
21 1
from napps.kytos.mef_eline.scheduler import CircuitSchedule, Scheduler
22 1
from napps.kytos.mef_eline.utils import emit_event, load_spec, validate
23 1
24
25
# pylint: disable=too-many-public-methods
26
class Main(KytosNApp):
27 1
    """Main class of amlight/mef_eline NApp.
28
29
    This class is the entry point for this napp.
30
    """
31
32
    spec = load_spec()
33 1
34
    def setup(self):
35 1
        """Replace the '__init__' method for the KytosNApp subclass.
36
37
        The setup method is automatically called by the controller when your
38
        application is loaded.
39
40
        So, if you have any setup routine, insert it here.
41
        """
42
        # object used to scheduler circuit events
43
        self.sched = Scheduler()
44 1
45
        # object to save and load circuits
46
        self.mongo_controller = self.get_eline_controller()
47 1
        self.mongo_controller.bootstrap_indexes()
48
49
        # set the controller that will manager the dynamic paths
50 1
        DynamicPathManager.set_controller(self.controller)
51
52
        # dictionary of EVCs created. It acts as a circuit buffer.
53
        # Every create/update/delete must be synced to storehouse.
54 1
        self.circuits = {}
55
56 1
        self._lock = Lock()
57
        self.execution_rounds = -1
58 1
        self.execute_as_loop(settings.DEPLOY_EVCS_INTERVAL)
59 1
60 1
        self.load_all_evcs()
61
62 1
    @staticmethod
63
    def get_eline_controller():
64 1
        """Return the ELineController instance."""
65 1
        return controllers.ELineController()
66 1
67 1
    def execute(self):
68 1
        """Execute once when the napp is running."""
69 1
        if self.execution_rounds < 0:
70
            self.execution_rounds += 1
71 1
            return
72
        if self._lock.locked():
73 1
            return
74 1
        log.debug("Starting consistency routine")
75 1
        with self._lock:
76 1
            self.execute_consistency()
77 1
        log.debug("Finished consistency routine")
78
79
    def execute_consistency(self):
80
        """Execute consistency routine."""
81
        self.execution_rounds += 1
82 1
        stored_circuits = self.mongo_controller.get_circuits()['circuits']
83 1
        for circuit in tuple(self.circuits.values()):
84 1
            stored_circuits.pop(circuit.id, None)
85 1
            if (
86 1
                circuit.is_enabled()
87
                and not circuit.is_active()
88 1
                and not circuit.lock.locked()
89 1
            ):
90 1
                if circuit.check_traces():
91 1
                    log.info(f"{circuit} enabled but inactive - activating")
92 1
                    with circuit.lock:
93 1
                        circuit.activate()
94 1
                        circuit.sync()
95
                else:
96 1
                    if self.execution_rounds > settings.WAIT_FOR_OLD_PATH:
97
                        log.info(f"{circuit} enabled but inactive - redeploy")
98
                        with circuit.lock:
99
                            circuit.deploy()
100
        for circuit_id in stored_circuits:
101
            log.info(f"EVC found in storehouse but unloaded {circuit_id}")
102 1
            self._load_evc(stored_circuits[circuit_id])
103
104
    def shutdown(self):
105
        """Execute when your napp is unloaded.
106
107
        If you have some cleanup procedure, insert it here.
108
        """
109 1
110 1
    @rest("/v2/evc/", methods=["GET"])
111 1
    def list_circuits(self):
112 1
        """Endpoint to return circuits stored.
113 1
114 1
        If archived is set to True return all circuits, else only the ones
115 1
        not archived.
116 1
        """
117
        log.debug("list_circuits /v2/evc")
118
        archived = request.args.get("archived", False)
119
        circuits = self.mongo_controller.get_circuits()['circuits']
120
        if not circuits:
121
            return jsonify({}), 200
122
        if archived:
123
            return jsonify(circuits), 200
124
        return (
125
            jsonify(
126
                {
127 1
                    circuit_id: circuit
128
                    for circuit_id, circuit in circuits.items()
129
                    if not circuit.get("archived", False)
130 1
                }
131 1
            ),
132
            200,
133 1
        )
134 1
135 1
    @rest("/v2/evc/<circuit_id>", methods=["GET"])
136 1
    def get_circuit(self, circuit_id):
137 1
        """Endpoint to return a circuit based on id."""
138 1
        log.debug("get_circuit /v2/evc/%s", circuit_id)
139 1
        circuits = self.mongo_controller.get_circuits()['circuits']
140 1
141 1
        try:
142
            result = circuits[circuit_id]
143 1
        except KeyError:
144 1
            result = f"circuit_id {circuit_id} not found"
145
            log.debug("get_circuit result %s %s", result, 404)
146
            raise NotFound(result) from KeyError
147
        status = 200
148
        log.debug("get_circuit result %s %s", result, status)
149
        return jsonify(result), status
150
151
    @rest("/v2/evc/", methods=["POST"])
152
    @validate(spec)
153
    def create_circuit(self, data):
154
        """Try to create a new circuit.
155
156
        Firstly, for EVPL: E-Line NApp verifies if UNI_A's requested C-VID and
157
        UNI_Z's requested C-VID are available from the interfaces' pools. This
158
        is checked when creating the UNI object.
159
160
        Then, E-Line NApp requests a primary and a backup path to the
161
        Pathfinder NApp using the attributes primary_links and backup_links
162
        submitted via REST
163
164
        # For each link composing paths in #3:
165
        #  - E-Line NApp requests a S-VID available from the link VLAN pool.
166
        #  - Using the S-VID obtained, generate abstract flow entries to be
167
        #    sent to FlowManager
168
169
        Push abstract flow entries to FlowManager and FlowManager pushes
170 1
        OpenFlow entries to datapaths
171
172 1
        E-Line NApp generates an event to notify all Kytos NApps of a new EVC
173 1
        creation
174 1
175
        Finnaly, notify user of the status of its request.
176
        """
177
        # Try to create the circuit object
178 1
        log.debug("create_circuit /v2/evc/")
179
180
        try:
181
            evc = self._evc_from_dict(data)
182
        except ValueError as exception:
183
            log.debug("create_circuit result %s %s", exception, 400)
184
            raise BadRequest(str(exception)) from BadRequest
185
186
        if evc.primary_path:
187
            try:
188
                evc.primary_path.is_valid(
189 1
                    evc.uni_a.interface.switch,
190
                    evc.uni_z.interface.switch,
191
                    bool(evc.circuit_scheduler),
192
                )
193
            except InvalidPath as exception:
194
                raise BadRequest(
195
                    f"primary_path is not valid: {exception}"
196
                ) from exception
197
        if evc.backup_path:
198
            try:
199
                evc.backup_path.is_valid(
200
                    evc.uni_a.interface.switch,
201
                    evc.uni_z.interface.switch,
202 1
                    bool(evc.circuit_scheduler),
203 1
                )
204 1
            except InvalidPath as exception:
205 1
                raise BadRequest(
206
                    f"backup_path is not valid: {exception}"
207 1
                ) from exception
208
209
        # verify duplicated evc
210
        if self._is_duplicated_evc(evc):
211
            result = "The EVC already exists."
212 1
            log.debug("create_circuit result %s %s", result, 409)
213 1
            raise Conflict(result)
214 1
215
        if (
216
            not evc.primary_path
217 1
            and evc.dynamic_backup_path is False
218
            and evc.uni_a.interface.switch != evc.uni_z.interface.switch
219
        ):
220 1
            result = "The EVC must have a primary path or allow dynamic paths."
221
            log.debug("create_circuit result %s %s", result, 400)
222
            raise BadRequest(result)
223 1
224
        # store circuit in dictionary
225
        self.circuits[evc.id] = evc
226 1
227 1
        # save circuit
228 1
        evc.sync()
229
230
        # Schedule the circuit deploy
231 1
        self.sched.add(evc)
232
233
        # Circuit has no schedule, deploy now
234 1
        if not evc.circuit_scheduler:
235
            with evc.lock:
236 1
                evc.deploy()
237 1
238 1
        # Notify users
239 1
        event = KytosEvent(
240 1
            name="kytos.mef_eline.created", content=evc.as_dict()
241
        )
242 1
        self.controller.buffers.app.put(event)
243
244
        result = {"circuit_id": evc.id}
245
        status = 201
246
        log.debug("create_circuit result %s %s", result, status)
247
        emit_event(self.controller, "created", evc_id=evc.id)
248 1
        return jsonify(result), status
249 1
250 1
    @rest("/v2/evc/<circuit_id>", methods=["PATCH"])
251 1
    def update(self, circuit_id):
252 1
        """Update a circuit based on payload.
253 1
254 1
        The EVC required attributes (name, uni_a, uni_z) can't be updated.
255
        """
256 1
        log.debug("update /v2/evc/%s", circuit_id)
257 1
        try:
258 1
            evc = self.circuits[circuit_id]
259 1
        except KeyError:
260
            result = f"circuit_id {circuit_id} not found"
261 1
            log.debug("update result %s %s", result, 404)
262 1
            raise NotFound(result) from NotFound
263 1
264 1
        if evc.archived:
265 1
            result = "Can't update archived EVC"
266 1
            log.debug("update result %s %s", result, 405)
267 1
            raise MethodNotAllowed(["GET"], result)
268 1
269 1
        try:
270 1
            data = request.get_json()
271
        except BadRequest:
272 1
            result = "The request body is not a well-formed JSON."
273 1
            log.debug("update result %s %s", result, 400)
274
            raise BadRequest(result) from BadRequest
275
        if data is None:
276 1
            result = "The request body mimetype is not application/json."
277 1
            log.debug("update result %s %s", result, 415)
278 1
            raise UnsupportedMediaType(result) from UnsupportedMediaType
279 1
280
        try:
281 1
            enable, redeploy = evc.update(
282
                **self._evc_dict_with_instances(data)
283
            )
284
        except ValueError as exception:
285
            log.error(exception)
286
            log.debug("update result %s %s", exception, 400)
287
            raise BadRequest(str(exception)) from BadRequest
288
289
        if evc.is_active():
290 1
            if enable is False:  # disable if active
291 1
                with evc.lock:
292 1
                    evc.remove()
293 1
            elif redeploy is not None:  # redeploy if active
294 1
                with evc.lock:
295
                    evc.remove()
296 1
                    evc.deploy()
297 1
        else:
298 1
            if enable is True:  # enable if inactive
299
                with evc.lock:
300 1
                    evc.deploy()
301
        result = {evc.id: evc.as_dict()}
302
        status = 200
303
304
        log.debug("update result %s %s", result, status)
305
        emit_event(self.controller, "updated", evc_id=evc.id, data=data)
306
        return jsonify(result), status
307 1
308 1
    @rest("/v2/evc/<circuit_id>", methods=["DELETE"])
309 1
    def delete_circuit(self, circuit_id):
310 1
        """Remove a circuit.
311 1
312 1
        First, the flows are removed from the switches, and then the EVC is
313 1
        disabled.
314
        """
315 1
        log.debug("delete_circuit /v2/evc/%s", circuit_id)
316 1
        try:
317 1
            evc = self.circuits[circuit_id]
318 1
        except KeyError:
319
            result = f"circuit_id {circuit_id} not found"
320 1
            log.debug("delete_circuit result %s %s", result, 404)
321 1
            raise NotFound(result) from NotFound
322 1
323 1
        if evc.archived:
324 1
            result = f"Circuit {circuit_id} already removed"
325 1
            log.debug("delete_circuit result %s %s", result, 404)
326 1
            raise NotFound(result) from NotFound
327 1
328 1
        log.info("Removing %s", evc)
329 1
        with evc.lock:
330 1
            evc.remove_current_flows()
331
            evc.deactivate()
332 1
            evc.disable()
333 1
            self.sched.remove(evc)
334 1
            evc.archive()
335
            evc.sync()
336 1
        log.info("EVC removed. %s", evc)
337
        result = {"response": f"Circuit {circuit_id} removed"}
338
        status = 200
339 1
340 1
        log.debug("delete_circuit result %s %s", result, status)
341
        emit_event(self.controller, "deleted", evc_id=evc.id)
342
        return jsonify(result), status
343
344
    @rest("v2/evc/<circuit_id>/metadata", methods=["GET"])
345
    def get_metadata(self, circuit_id):
346
        """Get metadata from an EVC."""
347 1
        try:
348
            return (
349
                jsonify({"metadata": self.circuits[circuit_id].metadata}),
350 1
                200,
351 1
            )
352 1
        except KeyError as error:
353 1
            raise NotFound(f"circuit_id {circuit_id} not found.") from error
354 1
355 1
    @rest("v2/evc/<circuit_id>/metadata", methods=["POST"])
356 1
    def add_metadata(self, circuit_id):
357 1
        """Add metadata to an EVC."""
358 1
        try:
359 1
            metadata = request.get_json()
360 1
            content_type = request.content_type
361 1
        except BadRequest as error:
362
            result = "The request body is not a well-formed JSON."
363
            raise BadRequest(result) from error
364
        if content_type is None:
365
            result = "The request body is empty."
366
            raise BadRequest(result)
367 1
        if metadata is None:
368
            if content_type != "application/json":
369 1
                result = (
370 1
                    "The content type must be application/json "
371 1
                    f"(received {content_type})."
372 1
                )
373
            else:
374 1
                result = "Metadata is empty."
375 1
            raise UnsupportedMediaType(result)
376 1
377
        try:
378 1
            evc = self.circuits[circuit_id]
379
        except KeyError as error:
380
            raise NotFound(f"circuit_id {circuit_id} not found.") from error
381 1
382 1
        evc.extend_metadata(metadata)
383 1
        evc.sync()
384 1
        return jsonify("Operation successful"), 201
385
386 1
    @rest("v2/evc/<circuit_id>/metadata/<key>", methods=["DELETE"])
387 1
    def delete_metadata(self, circuit_id, key):
388 1
        """Delete metadata from an EVC."""
389
        try:
390 1
            evc = self.circuits[circuit_id]
391
        except KeyError as error:
392
            raise NotFound(f"circuit_id {circuit_id} not found.") from error
393 1
394 1
        evc.remove_metadata(key)
395 1
        evc.sync()
396 1
        return jsonify("Operation successful"), 200
397 1
398 1
    @rest("/v2/evc/<circuit_id>/redeploy", methods=["PATCH"])
399 1
    def redeploy(self, circuit_id):
400 1
        """Endpoint to force the redeployment of an EVC."""
401 1
        log.debug("redeploy /v2/evc/%s/redeploy", circuit_id)
402 1
        try:
403 1
            evc = self.circuits[circuit_id]
404 1
        except KeyError:
405
            result = f"circuit_id {circuit_id} not found"
406 1
            raise NotFound(result) from NotFound
407 1
        if evc.is_enabled():
408
            with evc.lock:
409 1
                evc.remove_current_flows()
410
                evc.deploy()
411 1
            result = {"response": f"Circuit {circuit_id} redeploy received."}
412
            status = 202
413
        else:
414
            result = {"response": f"Circuit {circuit_id} is disabled."}
415
            status = 409
416
417
        return jsonify(result), status
418
419
    @rest("/v2/evc/schedule", methods=["GET"])
420 1
    def list_schedules(self):
421 1
        """Endpoint to return all schedules stored for all circuits.
422 1
423 1
        Return a JSON with the following template:
424 1
        [{"schedule_id": <schedule_id>,
425 1
         "circuit_id": <circuit_id>,
426
         "schedule": <schedule object>}]
427 1
        """
428 1
        log.debug("list_schedules /v2/evc/schedule")
429 1
        circuits = self.mongo_controller.get_circuits()['circuits'].values()
430 1
        print(circuits)
431 1
        if not circuits:
432 1
            result = {}
433 1
            status = 200
434
            return jsonify(result), status
435
436
        result = []
437
        status = 200
438 1
        for circuit in circuits:
439
            circuit_scheduler = circuit.get("circuit_scheduler")
440 1
            if circuit_scheduler:
441 1
                for scheduler in circuit_scheduler:
442
                    value = {
443 1
                        "schedule_id": scheduler.get("id"),
444
                        "circuit_id": circuit.get("id"),
445
                        "schedule": scheduler,
446
                    }
447
                    result.append(value)
448
449
        log.debug("list_schedules result %s %s", result, status)
450
        return jsonify(result), status
451
452
    @rest("/v2/evc/schedule/", methods=["POST"])
453
    def create_schedule(self):
454
        """
455
        Create a new schedule for a given circuit.
456
457
        This service do no check if there are conflicts with another schedule.
458
        Payload example:
459
            {
460 1
              "circuit_id":"aa:bb:cc",
461
              "schedule": {
462 1
                "date": "2019-08-07T14:52:10.967Z",
463 1
                "interval": "string",
464 1
                "frequency": "1 * * * *",
465 1
                "action": "create"
466 1
              }
467 1
            }
468 1
        """
469 1
        log.debug("create_schedule /v2/evc/schedule/")
470 1
471 1
        json_data = self._json_from_request("create_schedule")
472 1
        try:
473
            circuit_id = json_data["circuit_id"]
474 1
        except TypeError:
475 1
            result = "The payload should have a dictionary."
476 1
            log.debug("create_schedule result %s %s", result, 400)
477 1
            raise BadRequest(result) from BadRequest
478 1
        except KeyError:
479 1
            result = "Missing circuit_id."
480
            log.debug("create_schedule result %s %s", result, 400)
481
            raise BadRequest(result) from BadRequest
482 1
483
        try:
484
            schedule_data = json_data["schedule"]
485 1
        except KeyError:
486
            result = "Missing schedule data."
487
            log.debug("create_schedule result %s %s", result, 400)
488 1
            raise BadRequest(result) from BadRequest
489 1
490 1
        # Get EVC from circuits buffer
491 1
        circuits = self._get_circuits_buffer()
492
493 1
        # get the circuit
494 1
        evc = circuits.get(circuit_id)
495 1
496 1
        # get the circuit
497
        if not evc:
498
            result = f"circuit_id {circuit_id} not found"
499 1
            log.debug("create_schedule result %s %s", result, 404)
500
            raise NotFound(result) from NotFound
501
        # Can not modify circuits deleted and archived
502 1
        if evc.archived:
503 1
            result = f"Circuit {circuit_id} is archived. Update is forbidden."
504
            log.debug("create_schedule result %s %s", result, 403)
505
            raise Forbidden(result) from Forbidden
506 1
507
        # new schedule from dict
508
        new_schedule = CircuitSchedule.from_dict(schedule_data)
509 1
510
        # If there is no schedule, create the list
511
        if not evc.circuit_scheduler:
512 1
            evc.circuit_scheduler = []
513
514 1
        # Add the new schedule
515 1
        evc.circuit_scheduler.append(new_schedule)
516
517 1
        # Add schedule job
518 1
        self.sched.add_circuit_job(evc, new_schedule)
519
520 1
        # save circuit to storehouse
521
        evc.sync()
522
523
        result = new_schedule.as_dict()
524
        status = 201
525
526
        log.debug("create_schedule result %s %s", result, status)
527
        return jsonify(result), status
528
529
    @rest("/v2/evc/schedule/<schedule_id>", methods=["PATCH"])
530
    def update_schedule(self, schedule_id):
531
        """Update a schedule.
532
533
        Change all attributes from the given schedule from a EVC circuit.
534 1
        The schedule ID is preserved as default.
535
        Payload example:
536
            {
537 1
              "date": "2019-08-07T14:52:10.967Z",
538
              "interval": "string",
539
              "frequency": "1 * * *",
540 1
              "action": "create"
541 1
            }
542 1
        """
543 1
        log.debug("update_schedule /v2/evc/schedule/%s", schedule_id)
544 1
545 1
        # Try to find a circuit schedule
546 1
        evc, found_schedule = self._find_evc_by_schedule_id(schedule_id)
547 1
548
        # Can not modify circuits deleted and archived
549 1
        if not found_schedule:
550
            result = f"schedule_id {schedule_id} not found"
551 1
            log.debug("update_schedule result %s %s", result, 404)
552 1
            raise NotFound(result) from NotFound
553
        if evc.archived:
554 1
            result = f"Circuit {evc.id} is archived. Update is forbidden."
555
            log.debug("update_schedule result %s %s", result, 403)
556 1
            raise Forbidden(result) from Forbidden
557
558
        data = self._json_from_request("update_schedule")
559 1
560
        new_schedule = CircuitSchedule.from_dict(data)
561 1
        new_schedule.id = found_schedule.id
562
        # Remove the old schedule
563 1
        evc.circuit_scheduler.remove(found_schedule)
564
        # Append the modified schedule
565 1
        evc.circuit_scheduler.append(new_schedule)
566 1
567
        # Cancel all schedule jobs
568 1
        self.sched.cancel_job(found_schedule.id)
569 1
        # Add the new circuit schedule
570
        self.sched.add_circuit_job(evc, new_schedule)
571 1
        # Save EVC to the storehouse
572
        evc.sync()
573
574
        result = new_schedule.as_dict()
575
        status = 200
576
577
        log.debug("update_schedule result %s %s", result, status)
578
        return jsonify(result), status
579 1
580 1
    @rest("/v2/evc/schedule/<schedule_id>", methods=["DELETE"])
581
    def delete_schedule(self, schedule_id):
582
        """Remove a circuit schedule.
583 1
584 1
        Remove the Schedule from EVC.
585 1
        Remove the Schedule from cron job.
586 1
        Save the EVC to the Storehouse.
587
        """
588 1
        log.debug("delete_schedule /v2/evc/schedule/%s", schedule_id)
589 1
        evc, found_schedule = self._find_evc_by_schedule_id(schedule_id)
590 1
591 1
        # Can not modify circuits deleted and archived
592
        if not found_schedule:
593
            result = f"schedule_id {schedule_id} not found"
594 1
            log.debug("delete_schedule result %s %s", result, 404)
595
            raise NotFound(result)
596
597 1
        if evc.archived:
598
            result = f"Circuit {evc.id} is archived. Update is forbidden."
599 1
            log.debug("delete_schedule result %s %s", result, 403)
600
            raise Forbidden(result)
601 1
602 1
        # Remove the old schedule
603
        evc.circuit_scheduler.remove(found_schedule)
604 1
605 1
        # Cancel all schedule jobs
606
        self.sched.cancel_job(found_schedule.id)
607 1
        # Save EVC to the storehouse
608
        evc.sync()
609
610
        result = "Schedule removed"
611
        status = 200
612
613
        log.debug("delete_schedule result %s %s", result, status)
614
        return jsonify(result), status
615
616
    def _is_duplicated_evc(self, evc):
617 1
        """Verify if the circuit given is duplicated with the stored evcs.
618 1
619 1
        Args:
620 1
            evc (EVC): circuit to be analysed.
621
622 1
        Returns:
623
            boolean: True if the circuit is duplicated, otherwise False.
624
625
        """
626
        for circuit in tuple(self.circuits.values()):
627 1
            if not circuit.archived and circuit.shares_uni(evc):
628
                return True
629 1
        return False
630 1
631 1
    @listen_to("kytos/topology.link_up")
632 1
    def on_link_up(self, event):
633 1
        """Change circuit when link is up or end_maintenance."""
634
        self.handle_link_up(event)
635 1
636
    def handle_link_up(self, event):
637
        """Change circuit when link is up or end_maintenance."""
638
        log.debug("Event handle_link_up %s", event)
639
        for evc in self.circuits.values():
640 1
            if evc.is_enabled() and not evc.archived:
641
                with evc.lock:
642 1
                    evc.handle_link_up(event.content["link"])
643 1
644 1
    @listen_to("kytos/topology.link_down")
645 1
    def on_link_down(self, event):
646 1
        """Change circuit when link is down or under_mantenance."""
647 1
        self.handle_link_down(event)
648 1
649
    def handle_link_down(self, event):
650
        """Change circuit when link is down or under_mantenance."""
651
        log.debug("Event handle_link_down %s", event)
652
        for evc in self.circuits.values():
653
            with evc.lock:
654
                if evc.is_affected_by_link(event.content["link"]):
655
                    log.debug(f"Handling evc {evc.id} on link down")
656
                    if evc.handle_link_down():
657
                        emit_event(
658
                            self.controller,
659
                            "redeployed_link_down",
660 1
                            evc_id=evc.id,
661
                        )
662 1
                    else:
663 1
                        emit_event(
664 1
                            self.controller,
665
                            "error_redeploy_link_down",
666 1
                            evc_id=evc.id,
667
                        )
668
669
    def load_all_evcs(self):
670
        """Try to load all EVCs on startup."""
671
        circuits = self.mongo_controller.get_circuits()['circuits'].items()
672
        for circuit_id, circuit in circuits:
673
            if circuit_id not in self.circuits:
674
                self._load_evc(circuit)
675
676
    def _load_evc(self, circuit_dict):
677
        """Load one EVC from storehouse to memory."""
678
        try:
679
            evc = self._evc_from_dict(circuit_dict)
680
        except ValueError as exception:
681
            log.error(
682
                f'Could not load EVC {circuit_dict["id"]} '
683
                f"because {exception}"
684
            )
685 1
            return None
686
687
        if evc.archived:
688
            return None
689
        evc.deactivate()
690 1
        evc.sync()
691
        self.circuits.setdefault(evc.id, evc)
692 1
        self.sched.add(evc)
693 1
        return evc
694 1
695
    @listen_to("kytos/flow_manager.flow.error")
696 1
    def on_flow_mod_error(self, event):
697 1
        """Handle flow mod errors related to an EVC."""
698 1
        self.handle_flow_mod_error(event)
699
700 1
    def handle_flow_mod_error(self, event):
701
        """Handle flow mod errors related to an EVC."""
702
        flow = event.content["flow"]
703
        command = event.content.get("error_command")
704
        if command != "add":
705 1
            return
706
        evc = self.circuits.get(EVC.get_id_from_cookie(flow.cookie))
707 1
        if evc:
708
            evc.remove_current_flows()
709
710 1
    def _evc_dict_with_instances(self, evc_dict):
711 1
        """Convert some dict values to instance of EVC classes.
712 1
713 1
        This method will convert: [UNI, Link]
714 1
        """
715 1
        data = evc_dict.copy()  # Do not modify the original dict
716
        for attribute, value in data.items():
717 1
            # Get multiple attributes.
718 1
            # Ex: uni_a, uni_z
719 1
            if "uni" in attribute:
720 1
                try:
721
                    data[attribute] = self._uni_from_dict(value)
722
                except ValueError as exception:
723
                    result = "Error creating UNI: Invalid value"
724
                    raise ValueError(result) from exception
725
726
            if attribute == "circuit_scheduler":
727
                data[attribute] = []
728 1
                for schedule in value:
729 1
                    data[attribute].append(CircuitSchedule.from_dict(schedule))
730
731
            # Get multiple attributes.
732
            # Ex: primary_links,
733
            #     backup_links,
734
            #     current_links_cache,
735
            #     primary_links_cache,
736 1
            #     backup_links_cache
737 1
            if "links" in attribute:
738
                data[attribute] = [
739
                    self._link_from_dict(link) for link in value
740
                ]
741 1
742
            # Ex: current_path,
743 1
            #     primary_path,
744 1
            #     backup_path
745 1
            if "path" in attribute and attribute != "dynamic_backup_path":
746
                data[attribute] = Path(
747 1
                    [self._link_from_dict(link) for link in value]
748
                )
749
750
        return data
751
752
    def _evc_from_dict(self, evc_dict):
753
        data = self._evc_dict_with_instances(evc_dict)
754
        return EVC(self.controller, **data)
755
756
    def _uni_from_dict(self, uni_dict):
757
        """Return a UNI object from python dict."""
758
        if uni_dict is None:
759
            return False
760
761
        interface_id = uni_dict.get("interface_id")
762
        interface = self.controller.get_interface_by_id(interface_id)
763
        if interface is None:
764
            result = (
765
                "Error creating UNI:"
766
                + f"Could not instantiate interface {interface_id}"
767
            )
768
            raise ValueError(result) from ValueError
769
770 1
        tag_dict = uni_dict.get("tag", None)
771
        if tag_dict:
772 1
            tag = TAG.from_dict(tag_dict)
773 1
        else:
774
            tag = None
775 1
        uni = UNI(interface, tag)
776 1
777
        return uni
778 1
779 1
    def _link_from_dict(self, link_dict):
780
        """Return a Link object from python dict."""
781
        id_a = link_dict.get("endpoint_a").get("id")
782 1
        id_b = link_dict.get("endpoint_b").get("id")
783 1
784
        endpoint_a = self.controller.get_interface_by_id(id_a)
785
        endpoint_b = self.controller.get_interface_by_id(id_b)
786
787
        link = Link(endpoint_a, endpoint_b)
788
        if "metadata" in link_dict:
789 1
            link.extend_metadata(link_dict.get("metadata"))
790
791 1
        s_vlan = link.get_metadata("s_vlan")
792
        if s_vlan:
793
            tag = TAG.from_dict(s_vlan)
794
            if tag is False:
795
                error_msg = f"Could not instantiate tag from dict {s_vlan}"
796
                raise ValueError(error_msg)
797
            link.update_metadata("s_vlan", tag)
798 1
        return link
799 1
800 1
    def _find_evc_by_schedule_id(self, schedule_id):
801
        """
802
        Find an EVC and CircuitSchedule based on schedule_id.
803 1
804 1
        :param schedule_id: Schedule ID
805 1
        :return: EVC and Schedule
806 1
        """
807 1
        circuits = self._get_circuits_buffer()
808 1
        found_schedule = None
809 1
        evc = None
810 1
811 1
        # pylint: disable=unused-variable
812
        for c_id, circuit in circuits.items():
813 1
            for schedule in circuit.circuit_scheduler:
814
                if schedule.id == schedule_id:
815
                    found_schedule = schedule
816
                    evc = circuit
817
                    break
818
            if found_schedule:
819 1
                break
820
        return evc, found_schedule
821 1
822 1
    def _get_circuits_buffer(self):
823 1
        """
824 1
        Return the circuit buffer.
825 1
826
        If the buffer is empty, try to load data from storehouse.
827 1
        """
828
        if not self.circuits:
829
            # Load storehouse circuits to buffer
830
            circuits = self.mongo_controller.get_circuits()['circuits']
831
            for c_id, circuit in circuits.items():
832
                evc = self._evc_from_dict(circuit)
833
                self.circuits[c_id] = evc
834
        return self.circuits
835 1
836 1
    @staticmethod
837
    def _json_from_request(caller):
838
        """Return a json from request.
839
840
        If it was not possible to get a json from the request, log, for debug,
841
        who was the caller and the error that ocurred, and raise an
842
        Exception.
843
        """
844
        try:
845 1
            json_data = request.get_json()
846 1
        except ValueError as exception:
847 1
            log.error(exception)
848 1
            log.debug(f"{caller} result {exception} 400")
849 1
            raise BadRequest(str(exception)) from BadRequest
850
        except BadRequest:
851
            result = "The request is not a valid JSON."
852
            log.debug(f"{caller} result {result} 400")
853
            raise BadRequest(result) from BadRequest
854
        if json_data is None:
855
            result = "Content-Type must be application/json"
856
            log.debug(f"{caller} result {result} 415")
857
            raise UnsupportedMediaType(result)
858
        return json_data
859