Test Failed
Pull Request — master (#90)
by Antonio
03:04
created

build.main   F

Complexity

Total Complexity 133

Size/Duplication

Total Lines 837
Duplicated Lines 0 %

Test Coverage

Coverage 81.55%

Importance

Changes 0
Metric Value
eloc 522
dl 0
loc 837
ccs 380
cts 466
cp 0.8155
rs 2
c 0
b 0
f 0
wmc 133

30 Methods

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