Passed
Push — master ( e17dc3...e954fe )
by Humberto
03:04 queued 11s
created

build.main   F

Complexity

Total Complexity 105

Size/Duplication

Total Lines 689
Duplicated Lines 0 %

Test Coverage

Coverage 76.24%

Importance

Changes 0
Metric Value
wmc 105
eloc 411
dl 0
loc 689
ccs 292
cts 383
cp 0.7624
rs 2
c 0
b 0
f 0

26 Methods

Rating   Name   Duplication   Size   Complexity  
A Main.load_circuits_by_interface() 0 13 4
A Main.add_to_dict_of_sets() 0 5 2
A Main._is_duplicated_evc() 0 14 4
A Main._find_evc_by_schedule_id() 0 21 5
A Main._link_from_dict() 0 20 4
A Main._get_circuits_buffer() 0 13 3
A Main.update_schedule() 0 50 3
A Main.delete_schedule() 0 35 3
A Main.shutdown() 0 2 1
A Main.handle_link_down() 0 8 3
B Main.list_schedules() 0 29 5
B Main.create_schedule() 0 76 7
A Main.list_circuits() 0 17 3
A Main.handle_link_up() 0 7 4
A Main.get_circuit() 0 16 2
A Main.setup() 0 25 1
A Main.execute() 0 5 4
A Main._evc_from_dict() 0 3 1
A Main._uni_from_dict() 0 19 5
A Main.json_from_request() 0 23 4
B Main.load_evcs() 0 25 6
A Main.handle_flow_mod_error() 0 11 3
B Main.create_circuit() 0 72 6
C Main.update() 0 52 10
A Main.delete_circuit() 0 33 3
C Main._evc_dict_with_instances() 0 40 9

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