Passed
Pull Request — master (#160)
by Antonio
03:45
created

build.main.Main._find_evc_by_schedule_id()   A

Complexity

Conditions 5

Size

Total Lines 21
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 5

Importance

Changes 0
Metric Value
cc 5
eloc 13
nop 2
dl 0
loc 21
ccs 13
cts 13
cp 1
crap 5
rs 9.2833
c 0
b 0
f 0
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 NotFound(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 1
            result = f"Circuit {circuit_id} already removed"
317 1
            log.debug("delete_circuit result %s %s", result, 404)
318 1
            raise NotFound(result) from NotFound
319
320 1
        log.info("Removing %s", evc)
321 1
        with evc.lock:
322 1
            evc.remove_current_flows()
323 1
            evc.deactivate()
324 1
            evc.disable()
325 1
            self.sched.remove(evc)
326 1
            evc.archive()
327 1
            evc.sync()
328 1
        log.info("EVC removed. %s", evc)
329 1
        result = {"response": f"Circuit {circuit_id} removed"}
330 1
        status = 200
331
332 1
        log.debug("delete_circuit result %s %s", result, status)
333 1
        emit_event(self.controller, "deleted", evc_id=evc.id)
334 1
        return jsonify(result), status
335
336 1
    @rest("v2/evc/<circuit_id>/metadata", methods=["GET"])
337
    def get_metadata(self, circuit_id):
338
        """Get metadata from an EVC."""
339 1
        try:
340 1
            return (
341
                jsonify({"metadata": self.circuits[circuit_id].metadata}),
342
                200,
343
            )
344
        except KeyError as error:
345
            raise NotFound(f"circuit_id {circuit_id} not found.") from error
346
347 1
    @rest("v2/evc/<circuit_id>/metadata", methods=["POST"])
348
    def add_metadata(self, circuit_id):
349
        """Add metadata to an EVC."""
350 1
        try:
351 1
            metadata = request.get_json()
352 1
            content_type = request.content_type
353 1
        except BadRequest as error:
354 1
            result = "The request body is not a well-formed JSON."
355 1
            raise BadRequest(result) from error
356 1
        if content_type is None:
357 1
            result = "The request body is empty."
358 1
            raise BadRequest(result)
359 1
        if metadata is None:
360 1
            if content_type != "application/json":
361 1
                result = (
362
                    "The content type must be application/json "
363
                    f"(received {content_type})."
364
                )
365
            else:
366
                result = "Metadata is empty."
367 1
            raise UnsupportedMediaType(result)
368
369 1
        try:
370 1
            evc = self.circuits[circuit_id]
371 1
        except KeyError as error:
372 1
            raise NotFound(f"circuit_id {circuit_id} not found.") from error
373
374 1
        evc.extend_metadata(metadata)
375 1
        evc.sync()
376 1
        return jsonify("Operation successful"), 201
377
378 1
    @rest("v2/evc/<circuit_id>/metadata/<key>", methods=["DELETE"])
379
    def delete_metadata(self, circuit_id, key):
380
        """Delete metadata from an EVC."""
381 1
        try:
382 1
            evc = self.circuits[circuit_id]
383 1
        except KeyError as error:
384 1
            raise NotFound(f"circuit_id {circuit_id} not found.") from error
385
386 1
        evc.remove_metadata(key)
387 1
        evc.sync()
388 1
        return jsonify("Operation successful"), 200
389
390 1
    @rest("/v2/evc/<circuit_id>/redeploy", methods=["PATCH"])
391
    def redeploy(self, circuit_id):
392
        """Endpoint to force the redeployment of an EVC."""
393 1
        log.debug("redeploy /v2/evc/%s/redeploy", circuit_id)
394 1
        try:
395 1
            evc = self.circuits[circuit_id]
396 1
        except KeyError:
397 1
            result = f"circuit_id {circuit_id} not found"
398 1
            raise NotFound(result) from NotFound
399 1
        if evc.is_enabled():
400 1
            with evc.lock:
401 1
                evc.remove_current_flows()
402 1
                evc.deploy()
403 1
            result = {"response": f"Circuit {circuit_id} redeploy received."}
404 1
            status = 202
405
        else:
406 1
            result = {"response": f"Circuit {circuit_id} is disabled."}
407 1
            status = 409
408
409 1
        return jsonify(result), status
410
411 1
    @rest("/v2/evc/schedule", methods=["GET"])
412
    def list_schedules(self):
413
        """Endpoint to return all schedules stored for all circuits.
414
415
        Return a JSON with the following template:
416
        [{"schedule_id": <schedule_id>,
417
         "circuit_id": <circuit_id>,
418
         "schedule": <schedule object>}]
419
        """
420 1
        log.debug("list_schedules /v2/evc/schedule")
421 1
        circuits = self.storehouse.get_data().values()
422 1
        if not circuits:
423 1
            result = {}
424 1
            status = 200
425 1
            return jsonify(result), status
426
427 1
        result = []
428 1
        status = 200
429 1
        for circuit in circuits:
430 1
            circuit_scheduler = circuit.get("circuit_scheduler")
431 1
            if circuit_scheduler:
432 1
                for scheduler in circuit_scheduler:
433 1
                    value = {
434
                        "schedule_id": scheduler.get("id"),
435
                        "circuit_id": circuit.get("id"),
436
                        "schedule": scheduler,
437
                    }
438 1
                    result.append(value)
439
440 1
        log.debug("list_schedules result %s %s", result, status)
441 1
        return jsonify(result), status
442
443 1
    @rest("/v2/evc/schedule/", methods=["POST"])
444
    def create_schedule(self):
445
        """
446
        Create a new schedule for a given circuit.
447
448
        This service do no check if there are conflicts with another schedule.
449
        Payload example:
450
            {
451
              "circuit_id":"aa:bb:cc",
452
              "schedule": {
453
                "date": "2019-08-07T14:52:10.967Z",
454
                "interval": "string",
455
                "frequency": "1 * * * *",
456
                "action": "create"
457
              }
458
            }
459
        """
460 1
        log.debug("create_schedule /v2/evc/schedule/")
461
462 1
        json_data = self._json_from_request("create_schedule")
463 1
        try:
464 1
            circuit_id = json_data["circuit_id"]
465 1
        except TypeError:
466 1
            result = "The payload should have a dictionary."
467 1
            log.debug("create_schedule result %s %s", result, 400)
468 1
            raise BadRequest(result) from BadRequest
469 1
        except KeyError:
470 1
            result = "Missing circuit_id."
471 1
            log.debug("create_schedule result %s %s", result, 400)
472 1
            raise BadRequest(result) from BadRequest
473
474 1
        try:
475 1
            schedule_data = json_data["schedule"]
476 1
        except KeyError:
477 1
            result = "Missing schedule data."
478 1
            log.debug("create_schedule result %s %s", result, 400)
479 1
            raise BadRequest(result) from BadRequest
480
481
        # Get EVC from circuits buffer
482 1
        circuits = self._get_circuits_buffer()
483
484
        # get the circuit
485 1
        evc = circuits.get(circuit_id)
486
487
        # get the circuit
488 1
        if not evc:
489 1
            result = f"circuit_id {circuit_id} not found"
490 1
            log.debug("create_schedule result %s %s", result, 404)
491 1
            raise NotFound(result) from NotFound
492
        # Can not modify circuits deleted and archived
493 1
        if evc.archived:
494 1
            result = f"Circuit {circuit_id} is archived. Update is forbidden."
495 1
            log.debug("create_schedule result %s %s", result, 403)
496 1
            raise Forbidden(result) from Forbidden
497
498
        # new schedule from dict
499 1
        new_schedule = CircuitSchedule.from_dict(schedule_data)
500
501
        # If there is no schedule, create the list
502 1
        if not evc.circuit_scheduler:
503 1
            evc.circuit_scheduler = []
504
505
        # Add the new schedule
506 1
        evc.circuit_scheduler.append(new_schedule)
507
508
        # Add schedule job
509 1
        self.sched.add_circuit_job(evc, new_schedule)
510
511
        # save circuit to storehouse
512 1
        evc.sync()
513
514 1
        result = new_schedule.as_dict()
515 1
        status = 201
516
517 1
        log.debug("create_schedule result %s %s", result, status)
518 1
        return jsonify(result), status
519
520 1
    @rest("/v2/evc/schedule/<schedule_id>", methods=["PATCH"])
521
    def update_schedule(self, schedule_id):
522
        """Update a schedule.
523
524
        Change all attributes from the given schedule from a EVC circuit.
525
        The schedule ID is preserved as default.
526
        Payload example:
527
            {
528
              "date": "2019-08-07T14:52:10.967Z",
529
              "interval": "string",
530
              "frequency": "1 * * *",
531
              "action": "create"
532
            }
533
        """
534 1
        log.debug("update_schedule /v2/evc/schedule/%s", schedule_id)
535
536
        # Try to find a circuit schedule
537 1
        evc, found_schedule = self._find_evc_by_schedule_id(schedule_id)
538
539
        # Can not modify circuits deleted and archived
540 1
        if not found_schedule:
541 1
            result = f"schedule_id {schedule_id} not found"
542 1
            log.debug("update_schedule result %s %s", result, 404)
543 1
            raise NotFound(result) from NotFound
544 1
        if evc.archived:
545 1
            result = f"Circuit {evc.id} is archived. Update is forbidden."
546 1
            log.debug("update_schedule result %s %s", result, 403)
547 1
            raise Forbidden(result) from Forbidden
548
549 1
        data = self._json_from_request("update_schedule")
550
551 1
        new_schedule = CircuitSchedule.from_dict(data)
552 1
        new_schedule.id = found_schedule.id
553
        # Remove the old schedule
554 1
        evc.circuit_scheduler.remove(found_schedule)
555
        # Append the modified schedule
556 1
        evc.circuit_scheduler.append(new_schedule)
557
558
        # Cancel all schedule jobs
559 1
        self.sched.cancel_job(found_schedule.id)
560
        # Add the new circuit schedule
561 1
        self.sched.add_circuit_job(evc, new_schedule)
562
        # Save EVC to the storehouse
563 1
        evc.sync()
564
565 1
        result = new_schedule.as_dict()
566 1
        status = 200
567
568 1
        log.debug("update_schedule result %s %s", result, status)
569 1
        return jsonify(result), status
570
571 1
    @rest("/v2/evc/schedule/<schedule_id>", methods=["DELETE"])
572
    def delete_schedule(self, schedule_id):
573
        """Remove a circuit schedule.
574
575
        Remove the Schedule from EVC.
576
        Remove the Schedule from cron job.
577
        Save the EVC to the Storehouse.
578
        """
579 1
        log.debug("delete_schedule /v2/evc/schedule/%s", schedule_id)
580 1
        evc, found_schedule = self._find_evc_by_schedule_id(schedule_id)
581
582
        # Can not modify circuits deleted and archived
583 1
        if not found_schedule:
584 1
            result = f"schedule_id {schedule_id} not found"
585 1
            log.debug("delete_schedule result %s %s", result, 404)
586 1
            raise NotFound(result)
587
588 1
        if evc.archived:
589 1
            result = f"Circuit {evc.id} is archived. Update is forbidden."
590 1
            log.debug("delete_schedule result %s %s", result, 403)
591 1
            raise Forbidden(result)
592
593
        # Remove the old schedule
594 1
        evc.circuit_scheduler.remove(found_schedule)
595
596
        # Cancel all schedule jobs
597 1
        self.sched.cancel_job(found_schedule.id)
598
        # Save EVC to the storehouse
599 1
        evc.sync()
600
601 1
        result = "Schedule removed"
602 1
        status = 200
603
604 1
        log.debug("delete_schedule result %s %s", result, status)
605 1
        return jsonify(result), status
606
607 1
    def _is_duplicated_evc(self, evc):
608
        """Verify if the circuit given is duplicated with the stored evcs.
609
610
        Args:
611
            evc (EVC): circuit to be analysed.
612
613
        Returns:
614
            boolean: True if the circuit is duplicated, otherwise False.
615
616
        """
617 1
        for circuit in tuple(self.circuits.values()):
618 1
            if not circuit.archived and circuit.shares_uni(evc):
619 1
                return True
620 1
        return False
621
622 1
    @listen_to("kytos/topology.link_up")
623
    def on_link_up(self, event):
624
        """Change circuit when link is up or end_maintenance."""
625
        self.handle_link_up(event)
626
627 1
    def handle_link_up(self, event):
628
        """Change circuit when link is up or end_maintenance."""
629 1
        log.debug("Event handle_link_up %s", event)
630 1
        for evc in self.circuits.values():
631 1
            if evc.is_enabled() and not evc.archived:
632 1
                with evc.lock:
633 1
                    evc.handle_link_up(event.content["link"])
634
635 1
    @listen_to("kytos/topology.link_down")
636
    def on_link_down(self, event):
637
        """Change circuit when link is down or under_mantenance."""
638
        self.handle_link_down(event)
639
640 1
    def handle_link_down(self, event):
641
        """Change circuit when link is down or under_mantenance."""
642 1
        log.debug("Event handle_link_down %s", event)
643 1
        for evc in self.circuits.values():
644 1
            with evc.lock:
645 1
                if evc.is_affected_by_link(event.content["link"]):
646 1
                    log.debug(f"Handling evc {evc.id} on link down")
647 1
                    if evc.handle_link_down():
648 1
                        emit_event(
649
                            self.controller,
650
                            "redeployed_link_down",
651
                            evc_id=evc.id,
652
                        )
653
                    else:
654
                        emit_event(
655
                            self.controller,
656
                            "error_redeploy_link_down",
657
                            evc_id=evc.id,
658
                        )
659
660 1
    def load_all_evcs(self):
661
        """Try to load all EVCs on startup."""
662 1
        for circuit_id, circuit in self.storehouse.get_data().items():
663 1
            if circuit_id not in self.circuits:
664 1
                self._load_evc(circuit)
665
666 1
    def _load_evc(self, circuit_dict):
667
        """Load one EVC from storehouse to memory."""
668
        try:
669
            evc = self._evc_from_dict(circuit_dict)
670
        except ValueError as exception:
671
            log.error(
672
                f'Could not load EVC {circuit_dict["id"]} '
673
                f"because {exception}"
674
            )
675
            return None
676
677
        if evc.archived:
678
            return None
679
        evc.deactivate()
680
        evc.sync()
681
        self.circuits.setdefault(evc.id, evc)
682
        self.sched.add(evc)
683
        return evc
684
685 1
    @listen_to("kytos/flow_manager.flow.error")
686
    def on_flow_mod_error(self, event):
687
        """Handle flow mod errors related to an EVC."""
688
        self.handle_flow_mod_error(event)
689
690 1
    def handle_flow_mod_error(self, event):
691
        """Handle flow mod errors related to an EVC."""
692 1
        flow = event.content["flow"]
693 1
        command = event.content.get("error_command")
694 1
        if command != "add":
695
            return
696 1
        evc = self.circuits.get(EVC.get_id_from_cookie(flow.cookie))
697 1
        if evc:
698 1
            evc.remove_current_flows()
699
700 1
    def _evc_dict_with_instances(self, evc_dict):
701
        """Convert some dict values to instance of EVC classes.
702
703
        This method will convert: [UNI, Link]
704
        """
705 1
        data = evc_dict.copy()  # Do not modify the original dict
706
707 1
        for attribute, value in data.items():
708
            # Get multiple attributes.
709
            # Ex: uni_a, uni_z
710 1
            if "uni" in attribute:
711 1
                try:
712 1
                    data[attribute] = self._uni_from_dict(value)
713 1
                except ValueError:
714 1
                    result = "Error creating UNI: Invalid value"
715 1
                    raise BadRequest(result) from BadRequest
716
717 1
            if attribute == "circuit_scheduler":
718 1
                data[attribute] = []
719 1
                for schedule in value:
720 1
                    data[attribute].append(CircuitSchedule.from_dict(schedule))
721
722
            # Get multiple attributes.
723
            # Ex: primary_links,
724
            #     backup_links,
725
            #     current_links_cache,
726
            #     primary_links_cache,
727
            #     backup_links_cache
728 1
            if "links" in attribute:
729 1
                data[attribute] = [
730
                    self._link_from_dict(link) for link in value
731
                ]
732
733
            # Ex: current_path,
734
            #     primary_path,
735
            #     backup_path
736 1
            if "path" in attribute and attribute != "dynamic_backup_path":
737 1
                data[attribute] = Path(
738
                    [self._link_from_dict(link) for link in value]
739
                )
740
741 1
        return data
742
743 1
    def _evc_from_dict(self, evc_dict):
744 1
        data = self._evc_dict_with_instances(evc_dict)
745 1
        return EVC(self.controller, **data)
746
747 1
    def _uni_from_dict(self, uni_dict):
748
        """Return a UNI object from python dict."""
749
        if uni_dict is None:
750
            return False
751
752
        interface_id = uni_dict.get("interface_id")
753
        interface = self.controller.get_interface_by_id(interface_id)
754
        if interface is None:
755
            result = (
756
                "Error creating UNI:"
757
                + f"Could not instantiate interface {interface_id}"
758
            )
759
            raise ValueError(result) from ValueError
760
761
        tag_dict = uni_dict.get("tag", None)
762
        if tag_dict:
763
            tag = TAG.from_dict(tag_dict)
764
        else:
765
            tag = None
766
        uni = UNI(interface, tag)
767
768
        return uni
769
770 1
    def _link_from_dict(self, link_dict):
771
        """Return a Link object from python dict."""
772 1
        id_a = link_dict.get("endpoint_a").get("id")
773 1
        id_b = link_dict.get("endpoint_b").get("id")
774
775 1
        endpoint_a = self.controller.get_interface_by_id(id_a)
776 1
        endpoint_b = self.controller.get_interface_by_id(id_b)
777
778 1
        link = Link(endpoint_a, endpoint_b)
779 1
        if "metadata" in link_dict:
780
            link.extend_metadata(link_dict.get("metadata"))
781
782 1
        s_vlan = link.get_metadata("s_vlan")
783 1
        if s_vlan:
784
            tag = TAG.from_dict(s_vlan)
785
            if tag is False:
786
                error_msg = f"Could not instantiate tag from dict {s_vlan}"
787
                raise ValueError(error_msg)
788
            link.update_metadata("s_vlan", tag)
789 1
        return link
790
791 1
    def _find_evc_by_schedule_id(self, schedule_id):
792
        """
793
        Find an EVC and CircuitSchedule based on schedule_id.
794
795
        :param schedule_id: Schedule ID
796
        :return: EVC and Schedule
797
        """
798 1
        circuits = self._get_circuits_buffer()
799 1
        found_schedule = None
800 1
        evc = None
801
802
        # pylint: disable=unused-variable
803 1
        for c_id, circuit in circuits.items():
804 1
            for schedule in circuit.circuit_scheduler:
805 1
                if schedule.id == schedule_id:
806 1
                    found_schedule = schedule
807 1
                    evc = circuit
808 1
                    break
809 1
            if found_schedule:
810 1
                break
811 1
        return evc, found_schedule
812
813 1
    def _get_circuits_buffer(self):
814
        """
815
        Return the circuit buffer.
816
817
        If the buffer is empty, try to load data from storehouse.
818
        """
819 1
        if not self.circuits:
820
            # Load storehouse circuits to buffer
821 1
            circuits = self.storehouse.get_data()
822 1
            for c_id, circuit in circuits.items():
823 1
                evc = self._evc_from_dict(circuit)
824 1
                self.circuits[c_id] = evc
825 1
        return self.circuits
826
827 1
    @staticmethod
828
    def _json_from_request(caller):
829
        """Return a json from request.
830
831
        If it was not possible to get a json from the request, log, for debug,
832
        who was the caller and the error that ocurred, and raise an
833
        Exception.
834
        """
835 1
        try:
836 1
            json_data = request.get_json()
837
        except ValueError as exception:
838
            log.error(exception)
839
            log.debug(f"{caller} result {exception} 400")
840
            raise BadRequest(str(exception)) from BadRequest
841
        except BadRequest:
842
            result = "The request is not a valid JSON."
843
            log.debug(f"{caller} result {result} 400")
844
            raise BadRequest(result) from BadRequest
845 1
        if json_data is None:
846 1
            result = "Content-Type must be application/json"
847 1
            log.debug(f"{caller} result {result} 415")
848 1
            raise UnsupportedMediaType(result)
849
        return json_data
850