Passed
Pull Request — master (#164)
by Antonio
04:59
created

build.main   F

Complexity

Total Complexity 97

Size/Duplication

Total Lines 654
Duplicated Lines 0 %

Test Coverage

Coverage 58.79%

Importance

Changes 0
Metric Value
eloc 388
dl 0
loc 654
rs 2
c 0
b 0
f 0
ccs 214
cts 364
cp 0.5879
wmc 97

23 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
B Main.load_evcs() 0 27 8
B Main.update_schedule() 0 65 6
C Main._evc_from_dict() 0 40 9
A Main._find_evc_by_schedule_id() 0 21 5
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.get_circuit() 0 15 2
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
B Main.create_circuit() 0 72 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 32 6
A Main._get_circuits_buffer() 0 13 3
C Main.create_schedule() 0 94 10
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
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
        log.debug('update /v2/evc/%s', circuit_id)
171
        try:
172
            evc = self.circuits[circuit_id]
173
            data = request.get_json()
174
            evc.update(**data)
175
        except ValueError as exception:
176
            log.error(exception)
177
            result = {'response': 'Bad Request: {}'.format(exception)}
178
            status = 400
179
        except TypeError:
180
            result = {'response': 'Content-Type must be application/json'}
181
            status = 415
182
        except BadRequest:
183
            response = 'Bad Request: The request is not a valid JSON.'
184
            result = {'response': response}
185
            status = 400
186
        except KeyError:
187
            result = {'response': f'circuit_id {circuit_id} not found'}
188
            status = 404
189
        else:
190
            evc.sync()
191
            result = {evc.id: evc.as_dict()}
192
            status = 200
193
194
        log.debug('update result %s %s', result, status)
195
        return jsonify(result), status
196
197 2
    @rest('/v2/evc/<circuit_id>', methods=['DELETE'])
198
    def delete_circuit(self, circuit_id):
199
        """Remove a circuit.
200
201
        First, the flows are removed from the switches, and then the EVC is
202
        disabled.
203
        """
204
        log.debug('delete_circuit /v2/evc/%s', circuit_id)
205
        try:
206
            evc = self.circuits[circuit_id]
207
        except KeyError:
208
            result = {'response': f'circuit_id {circuit_id} not found'}
209
            status = 404
210
        else:
211
            log.info('Removing %s', evc)
212
            if evc.archived:
213
                result = {'response': f'Circuit {circuit_id} already removed'}
214
                status = 404
215
            else:
216
                evc.remove_current_flows()
217
                evc.deactivate()
218
                evc.disable()
219
                self.sched.remove(evc)
220
                evc.archive()
221
                evc.sync()
222
                log.info('EVC removed. %s', evc)
223
                result = {'response': f'Circuit {circuit_id} removed'}
224
                status = 200
225
226
        log.debug('delete_circuit result %s %s', result, status)
227
        return jsonify(result), status
228
229 2
    @rest('/v2/evc/schedule', methods=['GET'])
230
    def list_schedules(self):
231
        """Endpoint to return all schedules stored for all circuits.
232
233
        Return a JSON with the following template:
234
        [{"schedule_id": <schedule_id>,
235
         "circuit_id": <circuit_id>,
236
         "schedule": <schedule object>}]
237
        """
238 2
        log.debug('list_schedules /v2/evc/schedule')
239 2
        circuits = self.storehouse.get_data().values()
240 2
        if not circuits:
241 2
            result = {}
242 2
            status = 200
243 2
            return jsonify(result), status
244
245 2
        result = []
246 2
        status = 200
247 2
        for circuit in circuits:
248 2
            circuit_scheduler = circuit.get("circuit_scheduler")
249 2
            if circuit_scheduler:
250 2
                for scheduler in circuit_scheduler:
251 2
                    value = {"schedule_id": scheduler.get("id"),
252
                             "circuit_id": circuit.get("id"),
253
                             "schedule": scheduler}
254 2
                    result.append(value)
255
256 2
        log.debug('list_schedules result %s %s', result, status)
257 2
        return jsonify(result), status
258
259 2
    @rest('/v2/evc/schedule/', methods=['POST'])
260
    def create_schedule(self):
261
        """
262
        Create a new schedule for a given circuit.
263
264
        This service do no check if there are conflicts with another schedule.
265
        Payload example:
266
            {
267
              "circuit_id":"aa:bb:cc",
268
              "schedule": {
269
                "date": "2019-08-07T14:52:10.967Z",
270
                "interval": "string",
271
                "frequency": "1 * * * *",
272
                "action": "create"
273
              }
274
            }
275
        """
276 2
        log.debug('create_schedule /v2/evc/schedule/')
277 2
        try:
278
            # Try to create the circuit object
279 2
            json_data = request.get_json()
280
281 2
            circuit_id = json_data.get("circuit_id")
282 2
            schedule_data = json_data.get("schedule")
283
284 2
            if not json_data:
285
                result = "Bad request: The request does not have a json."
286
                status = 400
287
                log.debug('create_schedule result %s %s', result, status)
288
                return jsonify(result), status
289 2
            if not circuit_id:
290
                result = "Bad request: Missing circuit_id."
291
                status = 400
292
                log.debug('create_schedule result %s %s', result, status)
293
                return jsonify(result), status
294 2
            if not schedule_data:
295
                result = "Bad request: Missing schedule data."
296
                status = 400
297
                log.debug('create_schedule result %s %s', result, status)
298
                return jsonify(result), status
299
300
            # Get EVC from circuits buffer
301 2
            circuits = self._get_circuits_buffer()
302
303
            # get the circuit
304 2
            evc = circuits.get(circuit_id)
305
306
            # get the circuit
307 2
            if not evc:
308
                result = {'response': f'circuit_id {circuit_id} not found'}
309
                status = 404
310
                log.debug('create_schedule result %s %s', result, status)
311
                return jsonify(result), status
312
            # Can not modify circuits deleted and archived
313 2
            if evc.archived:
314
                result = {'response': f'Circuit is archived.'
315
                                      f'Update is forbidden.'}
316
                status = 403
317
                log.debug('create_schedule result %s %s', result, status)
318
                return jsonify(result), status
319
320
            # new schedule from dict
321 2
            new_schedule = CircuitSchedule.from_dict(schedule_data)
322
323
            # If there is no schedule, create the list
324 2
            if not evc.circuit_scheduler:
325
                evc.circuit_scheduler = []
326
327
            # Add the new schedule
328 2
            evc.circuit_scheduler.append(new_schedule)
329
330
            # Add schedule job
331 2
            self.sched.add_circuit_job(evc, new_schedule)
332
333
            # save circuit to storehouse
334 2
            evc.sync()
335
336 2
            result = new_schedule.as_dict()
337 2
            status = 201
338
339
        except ValueError as exception:
340
            log.error(exception)
341
            result = {'response': 'Bad Request: {}'.format(exception)}
342
            status = 400
343
        except TypeError:
344
            result = {'response': 'Content-Type must be application/json'}
345
            status = 415
346
        except BadRequest:
347
            response = 'Bad Request: The request is not a valid JSON.'
348
            result = {'response': response}
349
            status = 400
350
351 2
        log.debug('create_schedule result %s %s', result, status)
352 2
        return jsonify(result), status
353
354 2
    @rest('/v2/evc/schedule/<schedule_id>', methods=['PATCH'])
355
    def update_schedule(self, schedule_id):
356
        """Update a schedule.
357
358
        Change all attributes from the given schedule from a EVC circuit.
359
        The schedule ID is preserved as default.
360
        Payload example:
361
            {
362
              "date": "2019-08-07T14:52:10.967Z",
363
              "interval": "string",
364
              "frequency": "1 * * *",
365
              "action": "create"
366
            }
367
        """
368 2
        log.debug('update_schedule /v2/evc/schedule/%s', schedule_id)
369 2
        try:
370
            # Try to find a circuit schedule
371 2
            evc, found_schedule = self._find_evc_by_schedule_id(schedule_id)
372
373
            # Can not modify circuits deleted and archived
374 2
            if not found_schedule:
375
                result = {'response': f'schedule_id {schedule_id} not found'}
376
                status = 404
377
                log.debug('update_schedule result %s %s', result, status)
378
                return jsonify(result), status
379 2
            if evc.archived:
380 2
                result = {'response': f'Circuit is archived.'
381
                                      f'Update is forbidden.'}
382 2
                status = 403
383 2
                log.debug('update_schedule result %s %s', result, status)
384 2
                return jsonify(result), status
385
386 2
            data = request.get_json()
387
388 2
            new_schedule = CircuitSchedule.from_dict(data)
389 2
            new_schedule.id = found_schedule.id
390
            # Remove the old schedule
391 2
            evc.circuit_scheduler.remove(found_schedule)
392
            # Append the modified schedule
393 2
            evc.circuit_scheduler.append(new_schedule)
394
395
            # Cancel all schedule jobs
396 2
            self.sched.cancel_job(found_schedule.id)
397
            # Add the new circuit schedule
398 2
            self.sched.add_circuit_job(evc, new_schedule)
399
            # Save EVC to the storehouse
400 2
            evc.sync()
401
402 2
            result = new_schedule.as_dict()
403 2
            status = 200
404
405
        except ValueError as exception:
406
            log.error(exception)
407
            result = {'response': 'Bad Request: {}'.format(exception)}
408
            status = 400
409
        except TypeError:
410
            result = {'response': 'Content-Type must be application/json'}
411
            status = 415
412
        except BadRequest:
413
            result = {'response':
414
                      'Bad Request: The request is not a valid JSON.'}
415
            status = 400
416
417 2
        log.debug('update_schedule result %s %s', result, status)
418 2
        return jsonify(result), status
419
420 2
    @rest('/v2/evc/schedule/<schedule_id>', methods=['DELETE'])
421
    def delete_schedule(self, schedule_id):
422
        """Remove a circuit schedule.
423
424
        Remove the Schedule from EVC.
425
        Remove the Schedule from cron job.
426
        Save the EVC to the Storehouse.
427
        """
428 2
        log.debug('delete_schedule /v2/evc/schedule/%s', schedule_id)
429 2
        evc, found_schedule = self._find_evc_by_schedule_id(schedule_id)
430
431
        # Can not modify circuits deleted and archived
432 2
        if not found_schedule:
433
            result = {'response': f'schedule_id {schedule_id} not found'}
434
            status = 404
435
            log.debug('delete_schedule result %s %s', result, status)
436
            return jsonify(result), status
437
438 2
        if evc.archived:
439 2
            result = {'response': f'Circuit is archived. Update is forbidden.'}
440 2
            status = 403
441 2
            log.debug('delete_schedule result %s %s', result, status)
442 2
            return jsonify(result), status
443
444
        # Remove the old schedule
445 2
        evc.circuit_scheduler.remove(found_schedule)
446
447
        # Cancel all schedule jobs
448 2
        self.sched.cancel_job(found_schedule.id)
449
        # Save EVC to the storehouse
450 2
        evc.sync()
451
452 2
        result = "Schedule removed"
453 2
        status = 200
454
455 2
        log.debug('delete_schedule result %s %s', result, status)
456 2
        return jsonify(result), status
457
458 2
    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 2
        for circuit in self.circuits.values():
469 2
            if not circuit.archived and circuit == evc:
470 2
                return True
471 2
        return False
472
473 2
    @listen_to('kytos/topology.link_up')
474
    def handle_link_up(self, event):
475
        """Change circuit when link is up or end_maintenance."""
476
        log.debug("Event handle_link_up %s", event)
477
        for evc in self.circuits.values():
478
            if evc.is_enabled() and not evc.archived:
479
                evc.handle_link_up(event.content['link'])
480
481 2
    @listen_to('kytos/topology.link_down')
482
    def handle_link_down(self, event):
483
        """Change circuit when link is down or under_mantenance."""
484
        log.debug("Event handle_link_down %s", event)
485
        for evc in self.circuits.values():
486
            if evc.is_affected_by_link(event.content['link']):
487
                log.info('handling evc %s' % evc)
488
                evc.handle_link_down()
489
490 2
    def load_circuits_by_interface(self, circuits):
491
        """Load circuits in storehouse for in-memory dictionary."""
492 2
        for circuit_id, circuit in circuits.items():
493 2
            intf_a = circuit['uni_a']['interface_id']
494 2
            self.add_to_dict_of_sets(intf_a, circuit_id)
495 2
            intf_z = circuit['uni_z']['interface_id']
496 2
            self.add_to_dict_of_sets(intf_z, circuit_id)
497 2
            for path in ('current_path', 'primary_path', 'backup_path'):
498 2
                for link in circuit[path]:
499 2
                    intf_a = link['endpoint_a']['id']
500 2
                    self.add_to_dict_of_sets(intf_a, circuit_id)
501 2
                    intf_b = link['endpoint_b']['id']
502 2
                    self.add_to_dict_of_sets(intf_b, circuit_id)
503
504 2
    def add_to_dict_of_sets(self, intf, circuit_id):
505
        """Add a single item to the dictionary of circuits by interface."""
506 2
        if intf not in self._circuits_by_interface:
507 2
            self._circuits_by_interface[intf] = set()
508 2
        self._circuits_by_interface[intf].add(circuit_id)
509
510 2
    @listen_to('kytos/topology.port.created')
511
    def load_evcs(self, event):
512
        """Try to load the unloaded EVCs from storehouse."""
513
        log.debug("Event load_evcs %s", event)
514
        circuits = self.storehouse.get_data()
515
        if not self._circuits_by_interface:
516
            self.load_circuits_by_interface(circuits)
517
518
        interface_id = '{}:{}'.format(event.content['switch'],
519
                                      event.content['port'])
520
521
        for circuit_id in self._circuits_by_interface.get(interface_id, []):
522
            if circuit_id in circuits and circuit_id not in self.circuits:
523
                try:
524
                    evc = self._evc_from_dict(circuits[circuit_id])
525
                except ValueError as exception:
526
                    log.info(
527
                        f'Could not load EVC {circuit_id} because {exception}')
528
                    continue
529
                log.info(f'Loading EVC {circuit_id}')
530
                if evc.archived:
531
                    continue
532
                if evc.is_enabled():
533
                    log.info(f'Trying to deploy EVC {circuit_id}')
534
                    evc.deploy()
535
                self.circuits[circuit_id] = evc
536
                self.sched.add(evc)
537
538 2
    def _evc_from_dict(self, evc_dict):
539
        """Convert some dict values to instance of EVC classes.
540
541
        This method will convert: [UNI, Link]
542
        """
543 2
        data = evc_dict.copy()  # Do not modify the original dict
544
545 2
        for attribute, value in data.items():
546
            # Get multiple attributes.
547
            # Ex: uni_a, uni_z
548 2
            if 'uni' in attribute:
549 2
                try:
550 2
                    data[attribute] = self._uni_from_dict(value)
551
                except ValueError as exc:
552
                    raise ValueError(f'Error creating UNI: {exc}')
553
554 2
            if attribute == 'circuit_scheduler':
555 2
                data[attribute] = []
556 2
                for schedule in value:
557 2
                    data[attribute].append(CircuitSchedule.from_dict(schedule))
558
559
            # Get multiple attributes.
560
            # Ex: primary_links,
561
            #     backup_links,
562
            #     current_links_cache,
563
            #     primary_links_cache,
564
            #     backup_links_cache
565 2
            if 'links' in attribute:
566 2
                data[attribute] = [self._link_from_dict(link)
567
                                   for link in value]
568
569
            # Get multiple attributes.
570
            # Ex: current_path,
571
            #     primary_path,
572
            #     backup_path
573 2
            if 'path' in attribute and attribute != 'dynamic_backup_path':
574 2
                data[attribute] = [self._link_from_dict(link)
575
                                   for link in value]
576
577 2
        return EVC(self.controller, **data)
578
579 2
    def _uni_from_dict(self, uni_dict):
580
        """Return a UNI object from python dict."""
581
        if uni_dict is None:
582
            return False
583
584
        interface_id = uni_dict.get("interface_id")
585
        interface = self.controller.get_interface_by_id(interface_id)
586
        if interface is None:
587
            raise ValueError(f'Could not instantiate interface {interface_id}')
588
589
        tag_dict = uni_dict.get("tag")
590
        tag = TAG.from_dict(tag_dict)
591
        if tag is False:
592
            raise ValueError(f'Could not instantiate tag from dict {tag_dict}')
593
594
        uni = UNI(interface, tag)
595
596
        return uni
597
598 2
    def _link_from_dict(self, link_dict):
599
        """Return a Link object from python dict."""
600 2
        id_a = link_dict.get('endpoint_a').get('id')
601 2
        id_b = link_dict.get('endpoint_b').get('id')
602
603 2
        endpoint_a = self.controller.get_interface_by_id(id_a)
604 2
        endpoint_b = self.controller.get_interface_by_id(id_b)
605
606 2
        link = Link(endpoint_a, endpoint_b)
607 2
        if 'metadata' in link_dict:
608
            link.extend_metadata(link_dict.get('metadata'))
609
610 2
        s_vlan = link.get_metadata('s_vlan')
611 2
        if s_vlan:
612
            tag = TAG.from_dict(s_vlan)
613
            if tag is False:
614
                error_msg = f'Could not instantiate tag from dict {s_vlan}'
615
                raise ValueError(error_msg)
616
            link.update_metadata('s_vlan', tag)
617 2
        return link
618
619 2
    def _find_evc_by_schedule_id(self, schedule_id):
620
        """
621
        Find an EVC and CircuitSchedule based on schedule_id.
622
623
        :param schedule_id: Schedule ID
624
        :return: EVC and Schedule
625
        """
626 2
        circuits = self._get_circuits_buffer()
627 2
        found_schedule = None
628 2
        evc = None
629
630
        # pylint: disable=unused-variable
631 2
        for c_id, circuit in circuits.items():
632 2
            for schedule in circuit.circuit_scheduler:
633 2
                if schedule.id == schedule_id:
634 2
                    found_schedule = schedule
635 2
                    evc = circuit
636 2
                    break
637 2
            if found_schedule:
638 2
                break
639 2
        return evc, found_schedule
640
641 2
    def _get_circuits_buffer(self):
642
        """
643
        Return the circuit buffer.
644
645
        If the buffer is empty, try to load data from storehouse.
646
        """
647 2
        if not self.circuits:
648
            # Load storehouse circuits to buffer
649 2
            circuits = self.storehouse.get_data()
650 2
            for c_id, circuit in circuits.items():
651 2
                evc = self._evc_from_dict(circuit)
652 2
                self.circuits[c_id] = evc
653
        return self.circuits
654