Test Failed
Push — master ( 40cb77...f066c4 )
by Humberto
02:32
created

build.main.Main.load_evcs()   B

Complexity

Conditions 6

Size

Total Lines 24
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 36.6126

Importance

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