Passed
Pull Request — master (#101)
by Italo Valcy
14:49 queued 12:13
created

build.main   F

Complexity

Total Complexity 144

Size/Duplication

Total Lines 835
Duplicated Lines 0 %

Test Coverage

Coverage 74.27%

Importance

Changes 0
Metric Value
eloc 528
dl 0
loc 835
rs 2
c 0
b 0
f 0
ccs 358
cts 482
cp 0.7427
wmc 144

32 Methods

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