Test Failed
Push — master ( 701a71...4aff4f )
by Antonio
04:10 queued 13s
created

build.main.Main.update()   D

Complexity

Conditions 13

Size

Total Lines 56
Code Lines 46

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 76.6589

Importance

Changes 0
Metric Value
cc 13
eloc 46
nop 2
dl 0
loc 56
ccs 10
cts 36
cp 0.2778
crap 76.6589
rs 4.2
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

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