Passed
Pull Request — master (#166)
by Antonio
06:26 queued 54s
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 5
CRAP Score 4

Importance

Changes 0
Metric Value
cc 4
eloc 5
nop 2
dl 0
loc 14
ccs 5
cts 5
cp 1
crap 4
rs 10
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 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
24
25
# pylint: disable=too-many-public-methods
26 1
class Main(KytosNApp):
27
    """Main class of amlight/mef_eline NApp.
28
29
    This class is the entry point for this napp.
30
    """
31
32 1
    spec = load_spec()
33
34 1
    def setup(self):
35
        """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 1
        self.sched = Scheduler()
44
45
        # object to save and load circuits
46 1
        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 1
        self.execution_rounds = -1
58 1
        self.execute_as_loop(settings.DEPLOY_EVCS_INTERVAL)
59
60 1
        self.load_all_evcs()
61
62 1
    @staticmethod
63 1
    def get_eline_controller():
64
        """Return the ELineController instance."""
65
        return controllers.ELineController()
66
67 1
    def execute(self):
68
        """Execute once when the napp is running."""
69 1
        if self.execution_rounds < 0:
70
            self.execution_rounds += 1
71
            return
72 1
        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 1
    def execute_consistency(self):
80
        """Execute consistency routine."""
81 1
        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
                circuit.is_enabled()
87
                and not circuit.is_active()
88
                and not circuit.lock.locked()
89
            ):
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 1
                        log.info(f"{circuit} enabled but inactive - redeploy")
98 1
                        with circuit.lock:
99 1
                            circuit.deploy()
100 1
        for circuit_id in stored_circuits:
101 1
            log.info(f"EVC found in storehouse but unloaded {circuit_id}")
102 1
            self._load_evc(stored_circuits[circuit_id])
103
104 1
    def shutdown(self):
105
        """Execute when your napp is unloaded.
106
107
        If you have some cleanup procedure, insert it here.
108
        """
109
110 1
    @rest("/v2/evc/", methods=["GET"])
111 1
    def list_circuits(self):
112
        """Endpoint to return circuits stored.
113
114
        If archived is set to True return all circuits, else only the ones
115
        not archived.
116
        """
117 1
        log.debug("list_circuits /v2/evc")
118 1
        archived = request.args.get("archived", False)
119 1
        circuits = self.mongo_controller.get_circuits()['circuits']
120 1
        if not circuits:
121
            return jsonify({}), 200
122 1
        if archived:
123 1
            return jsonify(circuits), 200
124 1
        return (
125
            jsonify(
126
                {
127
                    circuit_id: circuit
128
                    for circuit_id, circuit in circuits.items()
129
                    if not circuit.get("archived", False)
130
                }
131
            ),
132
            200,
133
        )
134
135 1
    @rest("/v2/evc/<circuit_id>", methods=["GET"])
136 1
    def get_circuit(self, circuit_id):
137
        """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
141 1
        try:
142 1
            result = circuits[circuit_id]
143 1
        except KeyError:
144 1
            result = f"circuit_id {circuit_id} not found"
145 1
            log.debug("get_circuit result %s %s", result, 404)
146 1
            raise NotFound(result) from KeyError
147 1
        status = 200
148 1
        log.debug("get_circuit result %s %s", result, status)
149 1
        return jsonify(result), status
150
151 1
    @rest("/v2/evc/", methods=["POST"])
152 1
    @validate(spec)
153 1
    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
        OpenFlow entries to datapaths
171
172
        E-Line NApp generates an event to notify all Kytos NApps of a new EVC
173
        creation
174
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 1
        try:
181 1
            evc = self._evc_from_dict(data)
182 1
        except ValueError as exception:
183 1
            log.debug("create_circuit result %s %s", exception, 400)
184 1
            raise BadRequest(str(exception)) from BadRequest
185
186 1
        if evc.primary_path:
187
            try:
188
                evc.primary_path.is_valid(
189
                    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 1
        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
                    bool(evc.circuit_scheduler),
203
                )
204
            except InvalidPath as exception:
205
                raise BadRequest(
206
                    f"backup_path is not valid: {exception}"
207
                ) from exception
208
209
        # verify duplicated evc
210 1
        if self._is_duplicated_evc(evc):
211 1
            result = "The EVC already exists."
212 1
            log.debug("create_circuit result %s %s", result, 409)
213 1
            raise Conflict(result)
214
215 1
        if (
216
            not evc.primary_path
217
            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 1
            log.debug("create_circuit result %s %s", result, 400)
222 1
            raise BadRequest(result)
223
224
        # store circuit in dictionary
225 1
        self.circuits[evc.id] = evc
226
227
        # 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 1
            with evc.lock:
236 1
                evc.deploy()
237
238
        # Notify users
239 1
        event = KytosEvent(
240
            name="kytos.mef_eline.created", content=evc.as_dict()
241
        )
242 1
        self.controller.buffers.app.put(event)
243
244 1
        result = {"circuit_id": evc.id}
245 1
        status = 201
246 1
        log.debug("create_circuit result %s %s", result, status)
247 1
        emit_event(self.controller, "created", evc_id=evc.id)
248 1
        return jsonify(result), status
249
250 1
    @rest("/v2/evc/<circuit_id>", methods=["PATCH"])
251 1
    def update(self, circuit_id):
252
        """Update a circuit based on payload.
253
254
        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 1
            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
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
269 1
        try:
270 1
            data = request.get_json()
271 1
        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 1
            raise BadRequest(result) from BadRequest
275 1
        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
280 1
        try:
281 1
            enable, redeploy = evc.update(
282
                **self._evc_dict_with_instances(data)
283
            )
284 1
        except ValueError as exception:
285 1
            log.error(exception)
286 1
            log.debug("update result %s %s", exception, 400)
287 1
            raise BadRequest(str(exception)) from BadRequest
288
289 1
        if evc.is_active():
290
            if enable is False:  # disable if active
291
                with evc.lock:
292
                    evc.remove()
293
            elif redeploy is not None:  # redeploy if active
294
                with evc.lock:
295
                    evc.remove()
296
                    evc.deploy()
297
        else:
298 1
            if enable is True:  # enable if inactive
299 1
                with evc.lock:
300 1
                    evc.deploy()
301 1
        result = {evc.id: evc.as_dict()}
302 1
        status = 200
303
304 1
        log.debug("update result %s %s", result, status)
305 1
        emit_event(self.controller, "updated", evc_id=evc.id, data=data)
306 1
        return jsonify(result), status
307
308 1
    @rest("/v2/evc/<circuit_id>", methods=["DELETE"])
309 1
    def delete_circuit(self, circuit_id):
310
        """Remove a circuit.
311
312
        First, the flows are removed from the switches, and then the EVC is
313
        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 1
            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
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
328 1
        log.info("Removing %s", evc)
329 1
        with evc.lock:
330 1
            evc.remove_current_flows()
331 1
            evc.deactivate()
332 1
            evc.disable()
333 1
            self.sched.remove(evc)
334 1
            evc.archive()
335 1
            evc.sync()
336 1
        log.info("EVC removed. %s", evc)
337 1
        result = {"response": f"Circuit {circuit_id} removed"}
338 1
        status = 200
339
340 1
        log.debug("delete_circuit result %s %s", result, status)
341 1
        emit_event(self.controller, "deleted", evc_id=evc.id)
342 1
        return jsonify(result), status
343
344 1
    @rest("v2/evc/<circuit_id>/metadata", methods=["GET"])
345 1
    def get_metadata(self, circuit_id):
346
        """Get metadata from an EVC."""
347 1
        try:
348 1
            return (
349
                jsonify({"metadata": self.circuits[circuit_id].metadata}),
350
                200,
351
            )
352
        except KeyError as error:
353
            raise NotFound(f"circuit_id {circuit_id} not found.") from error
354
355 1
    @rest("v2/evc/<circuit_id>/metadata", methods=["POST"])
356 1
    def add_metadata(self, circuit_id):
357
        """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 1
            result = "The request body is not a well-formed JSON."
363 1
            raise BadRequest(result) from error
364 1
        if content_type is None:
365 1
            result = "The request body is empty."
366 1
            raise BadRequest(result)
367 1
        if metadata is None:
368 1
            if content_type != "application/json":
369 1
                result = (
370
                    "The content type must be application/json "
371
                    f"(received {content_type})."
372
                )
373
            else:
374
                result = "Metadata is empty."
375 1
            raise UnsupportedMediaType(result)
376
377 1
        try:
378 1
            evc = self.circuits[circuit_id]
379 1
        except KeyError as error:
380 1
            raise NotFound(f"circuit_id {circuit_id} not found.") from error
381
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
        """Delete metadata from an EVC."""
389 1
        try:
390 1
            evc = self.circuits[circuit_id]
391 1
        except KeyError as error:
392 1
            raise NotFound(f"circuit_id {circuit_id} not found.") from error
393
394 1
        evc.remove_metadata(key)
395 1
        evc.sync()
396 1
        return jsonify("Operation successful"), 200
397
398 1
    @rest("/v2/evc/<circuit_id>/redeploy", methods=["PATCH"])
399 1
    def redeploy(self, circuit_id):
400
        """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 1
            result = f"circuit_id {circuit_id} not found"
406 1
            raise NotFound(result) from NotFound
407 1
        if evc.is_enabled():
408 1
            with evc.lock:
409 1
                evc.remove_current_flows()
410 1
                evc.deploy()
411 1
            result = {"response": f"Circuit {circuit_id} redeploy received."}
412 1
            status = 202
413
        else:
414 1
            result = {"response": f"Circuit {circuit_id} is disabled."}
415 1
            status = 409
416
417 1
        return jsonify(result), status
418
419 1
    @rest("/v2/evc/schedule", methods=["GET"])
420 1
    def list_schedules(self):
421
        """Endpoint to return all schedules stored for all circuits.
422
423
        Return a JSON with the following template:
424
        [{"schedule_id": <schedule_id>,
425
         "circuit_id": <circuit_id>,
426
         "schedule": <schedule object>}]
427
        """
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 1
            return jsonify(result), status
435
436 1
        result = []
437 1
        status = 200
438 1
        for circuit in circuits:
439 1
            circuit_scheduler = circuit.get("circuit_scheduler")
440 1
            if circuit_scheduler:
441 1
                for scheduler in circuit_scheduler:
442 1
                    value = {
443
                        "schedule_id": scheduler.get("id"),
444
                        "circuit_id": circuit.get("id"),
445
                        "schedule": scheduler,
446
                    }
447 1
                    result.append(value)
448
449 1
        log.debug("list_schedules result %s %s", result, status)
450 1
        return jsonify(result), status
451
452 1
    @rest("/v2/evc/schedule/", methods=["POST"])
453 1
    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
              "circuit_id":"aa:bb:cc",
461
              "schedule": {
462
                "date": "2019-08-07T14:52:10.967Z",
463
                "interval": "string",
464
                "frequency": "1 * * * *",
465
                "action": "create"
466
              }
467
            }
468
        """
469 1
        log.debug("create_schedule /v2/evc/schedule/")
470
471 1
        json_data = self._json_from_request("create_schedule")
472 1
        try:
473 1
            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 1
            log.debug("create_schedule result %s %s", result, 400)
481 1
            raise BadRequest(result) from BadRequest
482
483 1
        try:
484 1
            schedule_data = json_data["schedule"]
485 1
        except KeyError:
486 1
            result = "Missing schedule data."
487 1
            log.debug("create_schedule result %s %s", result, 400)
488 1
            raise BadRequest(result) from BadRequest
489
490
        # Get EVC from circuits buffer
491 1
        circuits = self._get_circuits_buffer()
492
493
        # get the circuit
494 1
        evc = circuits.get(circuit_id)
495
496
        # get the circuit
497 1
        if not evc:
498 1
            result = f"circuit_id {circuit_id} not found"
499 1
            log.debug("create_schedule result %s %s", result, 404)
500 1
            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 1
            log.debug("create_schedule result %s %s", result, 403)
505 1
            raise Forbidden(result) from Forbidden
506
507
        # new schedule from dict
508 1
        new_schedule = CircuitSchedule.from_dict(schedule_data)
509
510
        # If there is no schedule, create the list
511 1
        if not evc.circuit_scheduler:
512 1
            evc.circuit_scheduler = []
513
514
        # Add the new schedule
515 1
        evc.circuit_scheduler.append(new_schedule)
516
517
        # Add schedule job
518 1
        self.sched.add_circuit_job(evc, new_schedule)
519
520
        # save circuit to storehouse
521 1
        evc.sync()
522
523 1
        result = new_schedule.as_dict()
524 1
        status = 201
525
526 1
        log.debug("create_schedule result %s %s", result, status)
527 1
        return jsonify(result), status
528
529 1
    @rest("/v2/evc/schedule/<schedule_id>", methods=["PATCH"])
530 1
    def update_schedule(self, schedule_id):
531
        """Update a schedule.
532
533
        Change all attributes from the given schedule from a EVC circuit.
534
        The schedule ID is preserved as default.
535
        Payload example:
536
            {
537
              "date": "2019-08-07T14:52:10.967Z",
538
              "interval": "string",
539
              "frequency": "1 * * *",
540
              "action": "create"
541
            }
542
        """
543 1
        log.debug("update_schedule /v2/evc/schedule/%s", schedule_id)
544
545
        # Try to find a circuit schedule
546 1
        evc, found_schedule = self._find_evc_by_schedule_id(schedule_id)
547
548
        # Can not modify circuits deleted and archived
549 1
        if not found_schedule:
550 1
            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 1
        if evc.archived:
554 1
            result = f"Circuit {evc.id} is archived. Update is forbidden."
555 1
            log.debug("update_schedule result %s %s", result, 403)
556 1
            raise Forbidden(result) from Forbidden
557
558 1
        data = self._json_from_request("update_schedule")
559
560 1
        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
567
        # Cancel all schedule jobs
568 1
        self.sched.cancel_job(found_schedule.id)
569
        # Add the new circuit schedule
570 1
        self.sched.add_circuit_job(evc, new_schedule)
571
        # Save EVC to the storehouse
572 1
        evc.sync()
573
574 1
        result = new_schedule.as_dict()
575 1
        status = 200
576
577 1
        log.debug("update_schedule result %s %s", result, status)
578 1
        return jsonify(result), status
579
580 1
    @rest("/v2/evc/schedule/<schedule_id>", methods=["DELETE"])
581 1
    def delete_schedule(self, schedule_id):
582
        """Remove a circuit schedule.
583
584
        Remove the Schedule from EVC.
585
        Remove the Schedule from cron job.
586
        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
591
        # Can not modify circuits deleted and archived
592 1
        if not found_schedule:
593 1
            result = f"schedule_id {schedule_id} not found"
594 1
            log.debug("delete_schedule result %s %s", result, 404)
595 1
            raise NotFound(result)
596
597 1
        if evc.archived:
598 1
            result = f"Circuit {evc.id} is archived. Update is forbidden."
599 1
            log.debug("delete_schedule result %s %s", result, 403)
600 1
            raise Forbidden(result)
601
602
        # Remove the old schedule
603 1
        evc.circuit_scheduler.remove(found_schedule)
604
605
        # Cancel all schedule jobs
606 1
        self.sched.cancel_job(found_schedule.id)
607
        # Save EVC to the storehouse
608 1
        evc.sync()
609
610 1
        result = "Schedule removed"
611 1
        status = 200
612
613 1
        log.debug("delete_schedule result %s %s", result, status)
614 1
        return jsonify(result), status
615
616 1
    def _is_duplicated_evc(self, evc):
617
        """Verify if the circuit given is duplicated with the stored evcs.
618
619
        Args:
620
            evc (EVC): circuit to be analysed.
621
622
        Returns:
623
            boolean: True if the circuit is duplicated, otherwise False.
624
625
        """
626 1
        for circuit in tuple(self.circuits.values()):
627 1
            if not circuit.archived and circuit.shares_uni(evc):
628 1
                return True
629 1
        return False
630
631 1
    @listen_to("kytos/topology.link_up")
632 1
    def on_link_up(self, event):
633
        """Change circuit when link is up or end_maintenance."""
634
        self.handle_link_up(event)
635
636 1
    def handle_link_up(self, event):
637
        """Change circuit when link is up or end_maintenance."""
638 1
        log.debug("Event handle_link_up %s", event)
639 1
        for evc in self.circuits.values():
640 1
            if evc.is_enabled() and not evc.archived:
641 1
                with evc.lock:
642 1
                    evc.handle_link_up(event.content["link"])
643
644 1
    @listen_to("kytos/topology.link_down")
645 1
    def on_link_down(self, event):
646
        """Change circuit when link is down or under_mantenance."""
647
        self.handle_link_down(event)
648
649 1
    def handle_link_down(self, event):
650
        """Change circuit when link is down or under_mantenance."""
651 1
        log.debug("Event handle_link_down %s", event)
652 1
        for evc in self.circuits.values():
653 1
            with evc.lock:
654 1
                if evc.is_affected_by_link(event.content["link"]):
655 1
                    log.debug(f"Handling evc {evc.id} on link down")
656 1
                    if evc.handle_link_down():
657 1
                        emit_event(
658
                            self.controller,
659
                            "redeployed_link_down",
660
                            evc_id=evc.id,
661
                        )
662
                    else:
663
                        emit_event(
664
                            self.controller,
665
                            "error_redeploy_link_down",
666
                            evc_id=evc.id,
667
                        )
668
669 1
    def load_all_evcs(self):
670
        """Try to load all EVCs on startup."""
671 1
        circuits = self.mongo_controller.get_circuits()['circuits'].items()
672 1
        for circuit_id, circuit in circuits:
673 1
            if circuit_id not in self.circuits:
674 1
                self._load_evc(circuit)
675
676 1
    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
            return None
686
687
        if evc.archived:
688
            return None
689
        evc.deactivate()
690
        evc.sync()
691
        self.circuits.setdefault(evc.id, evc)
692
        self.sched.add(evc)
693
        return evc
694
695 1
    @listen_to("kytos/flow_manager.flow.error")
696 1
    def on_flow_mod_error(self, event):
697
        """Handle flow mod errors related to an EVC."""
698
        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 1
        flow = event.content["flow"]
703 1
        command = event.content.get("error_command")
704 1
        if command != "add":
705
            return
706 1
        evc = self.circuits.get(EVC.get_id_from_cookie(flow.cookie))
707 1
        if evc:
708 1
            evc.remove_current_flows()
709
710 1
    def _evc_dict_with_instances(self, evc_dict):
711
        """Convert some dict values to instance of EVC classes.
712
713
        This method will convert: [UNI, Link]
714
        """
715 1
        data = evc_dict.copy()  # Do not modify the original dict
716 1
        for attribute, value in data.items():
717
            # Get multiple attributes.
718
            # Ex: uni_a, uni_z
719 1
            if "uni" in attribute:
720 1
                try:
721 1
                    data[attribute] = self._uni_from_dict(value)
722 1
                except ValueError as exception:
723 1
                    result = "Error creating UNI: Invalid value"
724 1
                    raise ValueError(result) from exception
725
726 1
            if attribute == "circuit_scheduler":
727 1
                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
            #     backup_links_cache
737 1
            if "links" in attribute:
738 1
                data[attribute] = [
739
                    self._link_from_dict(link) for link in value
740
                ]
741
742
            # Ex: current_path,
743
            #     primary_path,
744
            #     backup_path
745 1
            if "path" in attribute and attribute != "dynamic_backup_path":
746 1
                data[attribute] = Path(
747
                    [self._link_from_dict(link) for link in value]
748
                )
749
750 1
        return data
751
752 1
    def _evc_from_dict(self, evc_dict):
753 1
        data = self._evc_dict_with_instances(evc_dict)
754 1
        return EVC(self.controller, **data)
755
756 1
    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
        tag_dict = uni_dict.get("tag", None)
771
        if tag_dict:
772
            tag = TAG.from_dict(tag_dict)
773
        else:
774
            tag = None
775
        uni = UNI(interface, tag)
776
777
        return uni
778
779 1
    def _link_from_dict(self, link_dict):
780
        """Return a Link object from python dict."""
781 1
        id_a = link_dict.get("endpoint_a").get("id")
782 1
        id_b = link_dict.get("endpoint_b").get("id")
783
784 1
        endpoint_a = self.controller.get_interface_by_id(id_a)
785 1
        endpoint_b = self.controller.get_interface_by_id(id_b)
786
787 1
        link = Link(endpoint_a, endpoint_b)
788 1
        if "metadata" in link_dict:
789
            link.extend_metadata(link_dict.get("metadata"))
790
791 1
        s_vlan = link.get_metadata("s_vlan")
792 1
        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
800 1
    def _find_evc_by_schedule_id(self, schedule_id):
801
        """
802
        Find an EVC and CircuitSchedule based on schedule_id.
803
804
        :param schedule_id: Schedule ID
805
        :return: EVC and Schedule
806
        """
807 1
        circuits = self._get_circuits_buffer()
808 1
        found_schedule = None
809 1
        evc = None
810
811
        # pylint: disable=unused-variable
812 1
        for c_id, circuit in circuits.items():
813 1
            for schedule in circuit.circuit_scheduler:
814 1
                if schedule.id == schedule_id:
815 1
                    found_schedule = schedule
816 1
                    evc = circuit
817 1
                    break
818 1
            if found_schedule:
819 1
                break
820 1
        return evc, found_schedule
821
822 1
    def _get_circuits_buffer(self):
823
        """
824
        Return the circuit buffer.
825
826
        If the buffer is empty, try to load data from storehouse.
827
        """
828 1
        if not self.circuits:
829
            # Load storehouse circuits to buffer
830 1
            circuits = self.mongo_controller.get_circuits()['circuits']
831 1
            for c_id, circuit in circuits.items():
832 1
                evc = self._evc_from_dict(circuit)
833 1
                self.circuits[c_id] = evc
834 1
        return self.circuits
835
836 1
    @staticmethod
837 1
    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 1
        try:
845 1
            json_data = request.get_json()
846
        except ValueError as exception:
847
            log.error(exception)
848
            log.debug(f"{caller} result {exception} 400")
849
            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 1
        if json_data is None:
855 1
            result = "Content-Type must be application/json"
856 1
            log.debug(f"{caller} result {result} 415")
857 1
            raise UnsupportedMediaType(result)
858
        return json_data
859