Test Failed
Push — master ( e254e3...e50c4e )
by Antonio
03:12 queued 11s
created

build.main   F

Complexity

Total Complexity 101

Size/Duplication

Total Lines 665
Duplicated Lines 0 %

Test Coverage

Coverage 63.88%

Importance

Changes 0
Metric Value
eloc 395
dl 0
loc 665
ccs 237
cts 371
cp 0.6388
rs 2
c 0
b 0
f 0
wmc 101

24 Methods

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