Passed
Push — master ( bea2e3...134859 )
by Humberto
01:36 queued 10s
created

build.main   F

Complexity

Total Complexity 102

Size/Duplication

Total Lines 674
Duplicated Lines 0 %

Test Coverage

Coverage 73.19%

Importance

Changes 0
Metric Value
eloc 400
dl 0
loc 674
rs 2
c 0
b 0
f 0
ccs 273
cts 373
cp 0.7319
wmc 102

25 Methods

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