Passed
Push — master ( 369831...28d78d )
by Italo Valcy
01:35 queued 13s
created

build.main.Main.delete_circuit()   A

Complexity

Conditions 3

Size

Total Lines 34
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 22
CRAP Score 3.0155

Importance

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