Passed
Push — master ( dd71a3...270b5c )
by Italo Valcy
01:51 queued 12s
created

build.main.Main.handle_link_up()   A

Complexity

Conditions 5

Size

Total Lines 8
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 5

Importance

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