Passed
Pull Request — master (#248)
by Antonio
02:38
created

build.main   F

Complexity

Total Complexity 109

Size/Duplication

Total Lines 700
Duplicated Lines 0 %

Test Coverage

Coverage 76.41%

Importance

Changes 0
Metric Value
wmc 109
eloc 419
dl 0
loc 700
ccs 298
cts 390
cp 0.7641
rs 2
c 0
b 0
f 0

26 Methods

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

How to fix   Complexity   

Complexity

Complex classes like build.main often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

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