Passed
Push — master ( 94e66d...1c4745 )
by Vinicius
02:14 queued 16s
created

build.main.Main.on_link_down()   A

Complexity

Conditions 1

Size

Total Lines 4
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1.125

Importance

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