Test Failed
Pull Request — master (#214)
by Antonio
03:24
created

build.main.Main.handle_flow_mod_error()   A

Complexity

Conditions 3

Size

Total Lines 12
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 3

Importance

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