Passed
Push — master ( 4fc02b...d65ad0 )
by Antonio
03:34
created

build.main   F

Complexity

Total Complexity 140

Size/Duplication

Total Lines 864
Duplicated Lines 0 %

Test Coverage

Coverage 86.69%

Importance

Changes 0
Metric Value
wmc 140
eloc 540
dl 0
loc 864
ccs 430
cts 496
cp 0.8669
rs 2
c 0
b 0
f 0

35 Methods

Rating   Name   Duplication   Size   Complexity  
A Main.list_circuits() 0 23 3
A Main.delete_circuit() 0 35 4
B Main.add_metadata() 0 30 6
A Main.on_link_down() 0 4 1
A Main.update_schedule() 0 50 3
A Main.get_metadata() 0 10 2
A Main.delete_metadata() 0 11 2
A Main.handle_link_up() 0 7 5
A Main.on_link_up() 0 4 1
A Main.delete_schedule() 0 35 3
A Main.get_circuit() 0 15 2
A Main.shutdown() 0 2 1
A Main.handle_link_down() 0 18 5
A Main.setup() 0 27 1
A Main.execute() 0 11 4
A Main.get_eline_controller() 0 4 1
D Main.create_circuit() 0 98 12
B Main.list_schedules() 0 32 5
A Main.redeploy() 0 20 4
A Main._is_duplicated_evc() 0 14 4
C Main.execute_consistency() 0 24 10
D Main.update() 0 57 13
B Main.create_schedule() 0 76 7
C Main._evc_dict_with_instances() 0 41 9
A Main._evc_from_dict() 0 3 1
A Main._find_evc_by_schedule_id() 0 21 5
A Main.load_all_evcs() 0 6 3
A Main._json_from_request() 0 23 4
A Main._link_from_dict() 0 20 4
A Main._uni_from_dict() 0 22 4
A Main.handle_flow_mod_error() 0 9 3
A Main.on_flow_mod_error() 0 4 1
A Main.on_topology_loaded() 0 4 1
A Main._load_evc() 0 18 3
A Main._get_circuits_buffer() 0 13 3

How to fix   Complexity   

Complexity

Complex classes like build.main often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

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 mongodb.
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 mongodb 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 1
            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 mongodb
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 mongodb
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 mongodb
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
    @listen_to("kytos/topology.topology_loaded")
670 1
    def on_topology_loaded(self, event):  # pylint: disable=unused-argument
671
        """Load EVCs once the topology is available."""
672
        self.load_all_evcs()
673
674 1
    def load_all_evcs(self):
675
        """Try to load all EVCs on startup."""
676 1
        circuits = self.mongo_controller.get_circuits()['circuits'].items()
677 1
        for circuit_id, circuit in circuits:
678 1
            if circuit_id not in self.circuits:
679 1
                self._load_evc(circuit)
680
681 1
    def _load_evc(self, circuit_dict):
682
        """Load one EVC from mongodb to memory."""
683
        try:
684
            evc = self._evc_from_dict(circuit_dict)
685
        except ValueError as exception:
686
            log.error(
687
                f'Could not load EVC {circuit_dict["id"]} '
688
                f"because {exception}"
689
            )
690
            return None
691
692
        if evc.archived:
693
            return None
694
        evc.deactivate()
695
        evc.sync()
696
        self.circuits.setdefault(evc.id, evc)
697
        self.sched.add(evc)
698
        return evc
699
700 1
    @listen_to("kytos/flow_manager.flow.error")
701 1
    def on_flow_mod_error(self, event):
702
        """Handle flow mod errors related to an EVC."""
703
        self.handle_flow_mod_error(event)
704
705 1
    def handle_flow_mod_error(self, event):
706
        """Handle flow mod errors related to an EVC."""
707 1
        flow = event.content["flow"]
708 1
        command = event.content.get("error_command")
709 1
        if command != "add":
710
            return
711 1
        evc = self.circuits.get(EVC.get_id_from_cookie(flow.cookie))
712 1
        if evc:
713 1
            evc.remove_current_flows()
714
715 1
    def _evc_dict_with_instances(self, evc_dict):
716
        """Convert some dict values to instance of EVC classes.
717
718
        This method will convert: [UNI, Link]
719
        """
720 1
        data = evc_dict.copy()  # Do not modify the original dict
721 1
        for attribute, value in data.items():
722
            # Get multiple attributes.
723
            # Ex: uni_a, uni_z
724 1
            if "uni" in attribute:
725 1
                try:
726 1
                    data[attribute] = self._uni_from_dict(value)
727 1
                except ValueError as exception:
728 1
                    result = "Error creating UNI: Invalid value"
729 1
                    raise ValueError(result) from exception
730
731 1
            if attribute == "circuit_scheduler":
732 1
                data[attribute] = []
733 1
                for schedule in value:
734 1
                    data[attribute].append(CircuitSchedule.from_dict(schedule))
735
736
            # Get multiple attributes.
737
            # Ex: primary_links,
738
            #     backup_links,
739
            #     current_links_cache,
740
            #     primary_links_cache,
741
            #     backup_links_cache
742 1
            if "links" in attribute:
743 1
                data[attribute] = [
744
                    self._link_from_dict(link) for link in value
745
                ]
746
747
            # Ex: current_path,
748
            #     primary_path,
749
            #     backup_path
750 1
            if "path" in attribute and attribute != "dynamic_backup_path":
751 1
                data[attribute] = Path(
752
                    [self._link_from_dict(link) for link in value]
753
                )
754
755 1
        return data
756
757 1
    def _evc_from_dict(self, evc_dict):
758 1
        data = self._evc_dict_with_instances(evc_dict)
759 1
        return EVC(self.controller, **data)
760
761 1
    def _uni_from_dict(self, uni_dict):
762
        """Return a UNI object from python dict."""
763
        if uni_dict is None:
764
            return False
765
766
        interface_id = uni_dict.get("interface_id")
767
        interface = self.controller.get_interface_by_id(interface_id)
768
        if interface is None:
769
            result = (
770
                "Error creating UNI:"
771
                + f"Could not instantiate interface {interface_id}"
772
            )
773
            raise ValueError(result) from ValueError
774
775
        tag_dict = uni_dict.get("tag", None)
776
        if tag_dict:
777
            tag = TAG.from_dict(tag_dict)
778
        else:
779
            tag = None
780
        uni = UNI(interface, tag)
781
782
        return uni
783
784 1
    def _link_from_dict(self, link_dict):
785
        """Return a Link object from python dict."""
786 1
        id_a = link_dict.get("endpoint_a").get("id")
787 1
        id_b = link_dict.get("endpoint_b").get("id")
788
789 1
        endpoint_a = self.controller.get_interface_by_id(id_a)
790 1
        endpoint_b = self.controller.get_interface_by_id(id_b)
791
792 1
        link = Link(endpoint_a, endpoint_b)
793 1
        if "metadata" in link_dict:
794
            link.extend_metadata(link_dict.get("metadata"))
795
796 1
        s_vlan = link.get_metadata("s_vlan")
797 1
        if s_vlan:
798
            tag = TAG.from_dict(s_vlan)
799
            if tag is False:
800
                error_msg = f"Could not instantiate tag from dict {s_vlan}"
801
                raise ValueError(error_msg)
802
            link.update_metadata("s_vlan", tag)
803 1
        return link
804
805 1
    def _find_evc_by_schedule_id(self, schedule_id):
806
        """
807
        Find an EVC and CircuitSchedule based on schedule_id.
808
809
        :param schedule_id: Schedule ID
810
        :return: EVC and Schedule
811
        """
812 1
        circuits = self._get_circuits_buffer()
813 1
        found_schedule = None
814 1
        evc = None
815
816
        # pylint: disable=unused-variable
817 1
        for c_id, circuit in circuits.items():
818 1
            for schedule in circuit.circuit_scheduler:
819 1
                if schedule.id == schedule_id:
820 1
                    found_schedule = schedule
821 1
                    evc = circuit
822 1
                    break
823 1
            if found_schedule:
824 1
                break
825 1
        return evc, found_schedule
826
827 1
    def _get_circuits_buffer(self):
828
        """
829
        Return the circuit buffer.
830
831
        If the buffer is empty, try to load data from mongodb.
832
        """
833 1
        if not self.circuits:
834
            # Load circuits from mongodb to buffer
835 1
            circuits = self.mongo_controller.get_circuits()['circuits']
836 1
            for c_id, circuit in circuits.items():
837 1
                evc = self._evc_from_dict(circuit)
838 1
                self.circuits[c_id] = evc
839 1
        return self.circuits
840
841 1
    @staticmethod
842 1
    def _json_from_request(caller):
843
        """Return a json from request.
844
845
        If it was not possible to get a json from the request, log, for debug,
846
        who was the caller and the error that ocurred, and raise an
847
        Exception.
848
        """
849 1
        try:
850 1
            json_data = request.get_json()
851
        except ValueError as exception:
852
            log.error(exception)
853
            log.debug(f"{caller} result {exception} 400")
854
            raise BadRequest(str(exception)) from BadRequest
855
        except BadRequest:
856
            result = "The request is not a valid JSON."
857
            log.debug(f"{caller} result {result} 400")
858
            raise BadRequest(result) from BadRequest
859 1
        if json_data is None:
860 1
            result = "Content-Type must be application/json"
861 1
            log.debug(f"{caller} result {result} 415")
862 1
            raise UnsupportedMediaType(result)
863
        return json_data
864