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

build.main   F

Complexity

Total Complexity 107

Size/Duplication

Total Lines 698
Duplicated Lines 0 %

Test Coverage

Coverage 76.29%

Importance

Changes 0
Metric Value
eloc 417
dl 0
loc 698
ccs 296
cts 388
cp 0.7629
rs 2
c 0
b 0
f 0
wmc 107

26 Methods

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

How to fix   Complexity   

Complexity

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

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

1
"""Main module of kytos/mef_eline Kytos Network Application.
2
3
NApp to provision circuits from user request.
4
"""
5 1
from threading import Lock
6
7 1
from flask import jsonify, request
8 1
from werkzeug.exceptions import (BadRequest, Conflict, Forbidden,
9
                                 MethodNotAllowed, NotFound,
10
                                 UnsupportedMediaType)
11
12 1
from kytos.core import KytosNApp, log, rest
13 1
from kytos.core.events import KytosEvent
14 1
from kytos.core.helpers import listen_to
15 1
from kytos.core.interface import TAG, UNI
16 1
from kytos.core.link import Link
17 1
from napps.kytos.mef_eline import settings
18 1
from napps.kytos.mef_eline.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
                evc.handle_link_up(event.content['link'])
480
481 1
    @listen_to('kytos/topology.link_down')
482
    def handle_link_down(self, event):
483
        """Change circuit when link is down or under_mantenance."""
484 1
        log.debug("Event handle_link_down %s", event)
485 1
        for evc in self.circuits.values():
486 1
            if evc.is_affected_by_link(event.content['link']):
487 1
                log.info('handling evc %s' % evc)
488 1
                evc.handle_link_down()
489
490 1
    def load_circuits_by_interface(self, circuits):
491
        """Load circuits in storehouse for in-memory dictionary."""
492 1
        for circuit_id, circuit in circuits.items():
493 1
            if circuit['archived'] is True:
494 1
                continue
495 1
            intf_a = circuit['uni_a']['interface_id']
496 1
            self.add_to_dict_of_sets(intf_a, circuit_id)
497 1
            intf_z = circuit['uni_z']['interface_id']
498 1
            self.add_to_dict_of_sets(intf_z, circuit_id)
499 1
            for path in ('current_path', 'primary_path', 'backup_path'):
500 1
                for link in circuit[path]:
501 1
                    intf_a = link['endpoint_a']['id']
502 1
                    self.add_to_dict_of_sets(intf_a, circuit_id)
503 1
                    intf_b = link['endpoint_b']['id']
504 1
                    self.add_to_dict_of_sets(intf_b, circuit_id)
505
506 1
    def add_to_dict_of_sets(self, intf, circuit_id):
507
        """Add a single item to the dictionary of circuits by interface."""
508 1
        if intf not in self._circuits_by_interface:
509 1
            self._circuits_by_interface[intf] = set()
510 1
        self._circuits_by_interface[intf].add(circuit_id)
511
512 1
    @listen_to('kytos/topology.port.created')
513
    def load_evcs(self, event):
514
        """Try to load the unloaded EVCs from storehouse."""
515
        with self._lock:
516
            log.debug("Event load_evcs %s", event)
517
            circuits = self.storehouse.get_data()
518
            if not self._circuits_by_interface:
519
                self.load_circuits_by_interface(circuits)
520
521
            interface_id = '{}:{}'.format(event.content['switch'],
522
                                          event.content['port'])
523
524
            for circuit_id in self._circuits_by_interface.get(interface_id,
525
                                                              []):
526
                if circuit_id in circuits and circuit_id not in self.circuits:
527
                    try:
528
                        evc = self._evc_from_dict(circuits[circuit_id])
529
                    except ValueError as exception:
530
                        log.info(
531
                            f'Could not load EVC {circuit_id} '
532
                            f'because {exception}')
533
                        continue
534
535
                    evc.deactivate()
536
                    evc.current_path = Path([])
537
                    evc.sync()
538
                    self.circuits.setdefault(circuit_id, evc)
539
                    self.sched.add(evc)
540
541 1
    @listen_to('kytos/flow_manager.flow.error')
542
    def handle_flow_mod_error(self, event):
543
        """Handle flow mod errors related to an EVC."""
544
        flow = event.content['flow']
545
        command = event.content.get('error_command')
546
        if command != 'add':
547
            return
548
        evc_id = f'{flow.cookie:x}'
549
        evc = self.circuits.get(evc_id)
550
        if evc:
551
            evc.remove_current_flows()
552
553 1
    def _evc_dict_with_instances(self, evc_dict):
554
        """Convert some dict values to instance of EVC classes.
555
556
        This method will convert: [UNI, Link]
557
        """
558 1
        data = evc_dict.copy()  # Do not modify the original dict
559
560 1
        for attribute, value in data.items():
561
            # Get multiple attributes.
562
            # Ex: uni_a, uni_z
563 1
            if 'uni' in attribute:
564 1
                try:
565 1
                    data[attribute] = self._uni_from_dict(value)
566 1
                except ValueError as exc:
567 1
                    raise ValueError(f'Error creating UNI: {exc}')
568
569 1
            if attribute == 'circuit_scheduler':
570 1
                data[attribute] = []
571 1
                for schedule in value:
572 1
                    data[attribute].append(CircuitSchedule.from_dict(schedule))
573
574
            # Get multiple attributes.
575
            # Ex: primary_links,
576
            #     backup_links,
577
            #     current_links_cache,
578
            #     primary_links_cache,
579
            #     backup_links_cache
580 1
            if 'links' in attribute:
581 1
                data[attribute] = [self._link_from_dict(link)
582
                                   for link in value]
583
584
            # Get multiple attributes.
585
            # Ex: current_path,
586
            #     primary_path,
587
            #     backup_path
588 1
            if 'path' in attribute and attribute != 'dynamic_backup_path':
589 1
                data[attribute] = Path([self._link_from_dict(link)
590
                                        for link in value])
591
592 1
        return data
593
594 1
    def _evc_from_dict(self, evc_dict):
595 1
        data = self._evc_dict_with_instances(evc_dict)
596 1
        return EVC(self.controller, **data)
597
598 1
    def _uni_from_dict(self, uni_dict):
599
        """Return a UNI object from python dict."""
600
        if uni_dict is None:
601
            return False
602
603
        interface_id = uni_dict.get("interface_id")
604
        interface = self.controller.get_interface_by_id(interface_id)
605
        if interface is None:
606
            raise ValueError(f'Could not instantiate interface {interface_id}')
607
608
        try:
609
            tag_dict = uni_dict["tag"]
610
        except KeyError:
611
            tag = None
612
        else:
613
            tag = TAG.from_dict(tag_dict)
614
        uni = UNI(interface, tag)
615
616
        return uni
617
618 1
    def _link_from_dict(self, link_dict):
619
        """Return a Link object from python dict."""
620 1
        id_a = link_dict.get('endpoint_a').get('id')
621 1
        id_b = link_dict.get('endpoint_b').get('id')
622
623 1
        endpoint_a = self.controller.get_interface_by_id(id_a)
624 1
        endpoint_b = self.controller.get_interface_by_id(id_b)
625
626 1
        link = Link(endpoint_a, endpoint_b)
627 1
        if 'metadata' in link_dict:
628
            link.extend_metadata(link_dict.get('metadata'))
629
630 1
        s_vlan = link.get_metadata('s_vlan')
631 1
        if s_vlan:
632
            tag = TAG.from_dict(s_vlan)
633
            if tag is False:
634
                error_msg = f'Could not instantiate tag from dict {s_vlan}'
635
                raise ValueError(error_msg)
636
            link.update_metadata('s_vlan', tag)
637 1
        return link
638
639 1
    def _find_evc_by_schedule_id(self, schedule_id):
640
        """
641
        Find an EVC and CircuitSchedule based on schedule_id.
642
643
        :param schedule_id: Schedule ID
644
        :return: EVC and Schedule
645
        """
646 1
        circuits = self._get_circuits_buffer()
647 1
        found_schedule = None
648 1
        evc = None
649
650
        # pylint: disable=unused-variable
651 1
        for c_id, circuit in circuits.items():
652 1
            for schedule in circuit.circuit_scheduler:
653 1
                if schedule.id == schedule_id:
654 1
                    found_schedule = schedule
655 1
                    evc = circuit
656 1
                    break
657 1
            if found_schedule:
658 1
                break
659 1
        return evc, found_schedule
660
661 1
    def _get_circuits_buffer(self):
662
        """
663
        Return the circuit buffer.
664
665
        If the buffer is empty, try to load data from storehouse.
666
        """
667 1
        if not self.circuits:
668
            # Load storehouse circuits to buffer
669 1
            circuits = self.storehouse.get_data()
670 1
            for c_id, circuit in circuits.items():
671 1
                evc = self._evc_from_dict(circuit)
672 1
                self.circuits[c_id] = evc
673 1
        return self.circuits
674
675 1
    @staticmethod
676
    def json_from_request(caller):
677
        """Return a json from request.
678
679
        If it was not possible to get a json from the request, log, for debug,
680
        who was the caller and the error that ocurred, and raise an
681
        Exception.
682
        """
683 1
        try:
684 1
            json_data = request.get_json()
685
        except ValueError as exception:
686
            log.error(exception)
687
            log.debug(f'{caller} result {exception} 400')
688
            raise BadRequest(str(exception))
689
        except BadRequest:
690
            result = 'The request is not a valid JSON.'
691
            log.debug(f'{caller} result {result} 400')
692
            raise BadRequest(result)
693 1
        if json_data is None:
694
            result = 'Content-Type must be application/json'
695
            log.debug(f'{caller} result {result} 415')
696
            raise UnsupportedMediaType(result)
697
        return json_data
698