Test Failed
Pull Request — master (#140)
by Rogerio
03:59
created

build.main   F

Complexity

Total Complexity 72

Size/Duplication

Total Lines 463
Duplicated Lines 0 %

Test Coverage

Coverage 44.37%

Importance

Changes 0
Metric Value
eloc 259
dl 0
loc 463
ccs 67
cts 151
cp 0.4437
rs 2.64
c 0
b 0
f 0
wmc 72

19 Methods

Rating   Name   Duplication   Size   Complexity  
A Main.setup() 0 16 1
A Main.get_circuit() 0 13 2
A Main.shutdown() 0 2 1
A Main.execute() 0 2 1
A Main.list_circuits() 0 8 2
A Main.update() 0 25 3
A Main.uni_from_dict() 0 18 4
B Main.update_schedule() 0 55 7
A Main.is_duplicated_evc() 0 21 5
A Main.handle_link_up() 0 15 5
A Main.link_from_dict() 0 20 4
B Main.delete_schedule() 0 37 5
A Main.handle_link_down() 0 15 4
A Main.list_schedules() 0 14 4
B Main.create_circuit() 0 57 5
C Main.evc_from_dict() 0 30 11
A Main.get_schedule() 0 14 2
B Main.create_schedule() 0 39 5
A Main.delete_circuit() 0 18 1

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
7 2
from kytos.core import KytosNApp, log, rest
8 2
from kytos.core.events import KytosEvent
9 2
from kytos.core.helpers import listen_to
10 2
from kytos.core.interface import TAG, UNI
11 2
from kytos.core.link import Link
12 2
from napps.kytos.mef_eline.models import EVC, DynamicPathManager
13 2
from napps.kytos.mef_eline.scheduler import CircuitSchedule, Scheduler
14 2
from napps.kytos.mef_eline.storehouse import StoreHouse
15
16
17 2
class Main(KytosNApp):
18
    """Main class of amlight/mef_eline NApp.
19
20
    This class is the entry point for this napp.
21
    """
22
23 2
    def setup(self):
24
        """Replace the '__init__' method for the KytosNApp subclass.
25
26
        The setup method is automatically called by the controller when your
27
        application is loaded.
28
29
        So, if you have any setup routine, insert it here.
30
        """
31
        # object used to scheduler circuit events
32 2
        self.sched = Scheduler()
33
34
        # object to save and load circuits
35 2
        self.storehouse = StoreHouse(self.controller)
36
37
        # set the controller that will manager the dynamic paths
38 2
        DynamicPathManager.set_controller(self.controller)
39
40 2
    def execute(self):
41
        """Execute once when the napp is running."""
42
43 2
    def shutdown(self):
44
        """Execute when your napp is unloaded.
45
46
        If you have some cleanup procedure, insert it here.
47
        """
48
49 2
    @rest('/v2/evc/', methods=['GET'])
50
    def list_circuits(self):
51
        """Endpoint to return all circuits stored."""
52 2
        circuits = self.storehouse.get_data()
53 2
        if not circuits:
54 2
            return jsonify({}), 200
55
56 2
        return jsonify(circuits), 200
57
58 2
    @rest('/v2/evc/<circuit_id>', methods=['GET'])
59
    def get_circuit(self, circuit_id):
60
        """Endpoint to return a circuit based on id."""
61 2
        circuits = self.storehouse.get_data()
62
63 2
        if circuit_id in circuits:
64 2
            result = circuits[circuit_id]
65 2
            status = 200
66
        else:
67 2
            result = {'response': f'circuit_id {circuit_id} not found'}
68 2
            status = 400
69
70 2
        return jsonify(result), status
71
72 2
    @rest('/v2/evc/', methods=['POST'])
73
    def create_circuit(self):
74
        """Try to create a new circuit.
75
76
        Firstly, for EVPL: E-Line NApp verifies if UNI_A's requested C-VID and
77
        UNI_Z's requested C-VID are available from the interfaces' pools. This
78
        is checked when creating the UNI object.
79
80
        Then, E-Line NApp requests a primary and a backup path to the
81
        Pathfinder NApp using the attributes primary_links and backup_links
82
        submitted via REST
83
84
        # For each link composing paths in #3:
85
        #  - E-Line NApp requests a S-VID available from the link VLAN pool.
86
        #  - Using the S-VID obtained, generate abstract flow entries to be
87
        #    sent to FlowManager
88
89
        Push abstract flow entries to FlowManager and FlowManager pushes
90
        OpenFlow entries to datapaths
91
92
        E-Line NApp generates an event to notify all Kytos NApps of a new EVC
93
        creation
94
95
        Finnaly, notify user of the status of its request.
96
        """
97
        # Try to create the circuit object
98 2
        data = request.get_json()
99
100 2
        if not data:
101 2
            return jsonify("Bad request: The request do not have a json."), 400
102
103 2
        try:
104 2
            evc = self.evc_from_dict(data)
105
        except ValueError as exception:
106
            log.error(exception)
107
            return jsonify("Bad request: {}".format(exception)), 400
108
109 2
        # verify duplicated evc
110 2
        if self.is_duplicated_evc(evc):
111
            return jsonify("Not Acceptable: This evc already exists."), 409
112
113 2
        # save circuit
114
        self.storehouse.save_evc(evc)
115
116 2
        # Schedule the circuit deploy
117
        self.sched.add(evc)
118
119 2
        # Circuit has no schedule, deploy now
120 2
        if not evc.circuit_scheduler:
121
            evc.deploy()
122
123 2
        # Notify users
124
        event = KytosEvent(name='kytos.mef_eline.created',
125 2
                           content=evc.as_dict())
126
        self.controller.buffers.app.put(event)
127 2
128
        return jsonify({"circuit_id": evc.id}), 201
129 2
130
    @rest('/v2/evc/<circuit_id>', methods=['PATCH'])
131
    def update(self, circuit_id):
132
        """Update a circuit based on payload.
133
134
        The EVC required attributes (name, uni_a, uni_z) can't be updated.
135
        """
136
        data = request.get_json()
137
        circuits = self.storehouse.get_data()
138
139
        if circuit_id not in circuits:
140
            result = {'response': f'circuit_id {circuit_id} not found'}
141
            return jsonify(result), 404
142
143
        try:
144
            evc = self.evc_from_dict(circuits.get(circuit_id))
145
            evc.update(**data)
146
            self.storehouse.save_evc(evc)
147
            result = {evc.id: evc.as_dict()}
148
            status = 200
149
        except ValueError as exception:
150
            log.error(exception)
151
            result = "Bad request: {}".format(exception)
152
            status = 400
153
154 2
        return jsonify(result), status
155
156
    @rest('/v2/evc/<circuit_id>', methods=['DELETE'])
157
    def delete_circuit(self, circuit_id):
158
        """Remove a circuit.
159
160
        First, the flows are removed from the switches, and then the EVC is
161
        disabled.
162
        """
163
        circuits = self.storehouse.get_data()
164
        log.info("Removing %s" % circuit_id)
165
        evc = self.evc_from_dict(circuits.get(circuit_id))
166
        evc.remove_current_flows()
167
        evc.deactivate()
168
        evc.disable()
169
        self.sched.remove(evc)
170
        evc.archive()
171
        evc.sync()
172
173 2
        return jsonify("Circuit removed"), 200
174
175
    @rest('/v2/evc/schedule', methods=['GET'])
176
    def list_schedules(self):
177
        """Endpoint to return all circuits stored."""
178
        circuits = self.storehouse.get_data().values()
179
        if not circuits:
180
            return jsonify({}), 200
181
182
        result = []
183 2
        for circuit in circuits:
184 2
            if circuit["circuit_scheduler"]:
185 2
                schedule = {circuit["id"]: circuit["circuit_scheduler"]}
186
                result.append(schedule)
187
188
        return jsonify(result), 200
189 2
190 2
    @rest('/v2/evc/<circuit_id>/schedule/', methods=['GET'])
191
    def get_schedule(self, circuit_id):
192 2
        """Endpoint to return list all schedule from a circuit."""
193
        circuits = self.storehouse.get_data()
194 2
195
        if circuit_id in circuits:
196
            circuit = circuits[circuit_id]
197
            result = circuit["circuit_scheduler"]
198
            status = 200
199
        else:
200
            result = {'response': f'circuit_id {circuit_id} not found'}
201
            status = 400
202
203
        return jsonify(result), status
204
205
    @rest('/v2/evc/<circuit_id>/schedule/', methods=['POST'])
206
    def create_schedule(self, circuit_id):
207
        """
208
        Create a new schedule for a given circuit.
209 2
210
        This service do no check if there are conflicts with another schedule.
211
        """
212
        # Try to create the circuit object
213
        data = request.get_json()
214
215
        if not data:
216
            return jsonify("Bad request: The request do not have a json."), 400
217
218
        try:
219
            # new schedule from dict
220
            new_schedule = CircuitSchedule.from_dict(data)
221
        except ValueError as exception:
222
            log.error(exception)
223
            return jsonify("Bad request: {}".format(exception)), 400
224
225 2
        # get the circuit
226
        circuits = self.storehouse.get_data()
227
        if circuit_id not in circuits:
228
            result = {'response': f'circuit_id {circuit_id} not found'}
229
            return jsonify(result), 404
230 2
231
        evc = self.evc_from_dict(circuits.get(circuit_id))
232 2
233
        # If there is no schedule, create the list
234 2
        if not evc.circuit_scheduler:
235 2
            evc.circuit_scheduler = []
236 2
237
        # Add the new schedule
238
        evc.circuit_scheduler.append(new_schedule)
239
240 2
        # save circuit
241
        self.storehouse.save_evc(evc)
242
243
        return jsonify({"schedule_id": new_schedule.id}), 201
244
245 2
    @rest('/v2/evc/<circuit_id>/schedule/<schedule_id>', methods=['PATCH'])
246
    def update_schedule(self, circuit_id, schedule_id):
247
        """Update a schedule.
248
249 2
        Change all attributes from the given schedule from a EVC circuit.
250
        The schedule ID is preserved as default, but it can also be modified.
251
252
        The scheduler is notified and refresh all the schedules from
253
        the given EVC circuit.
254 2
        """
255
        data = request.get_json()
256 2
        circuits = self.storehouse.get_data()
257
258
        if circuit_id not in circuits:
259
            result = {'response': f'circuit_id {circuit_id} not found'}
260
            return jsonify(result), 404
261
262
        try:
263
            evc = self.evc_from_dict(circuits.get(circuit_id))
264
265
            # Try to find a circuit schedule
266
            found_schedule = None
267
            for schedule in evc.circuit_scheduler:
268
                if schedule.id == schedule_id:
269
                    found_schedule = schedule
270
                    break
271
272
            if found_schedule:
273
                new_schedule = CircuitSchedule.from_dict(data)
274
                new_schedule.id = found_schedule.id
275 2
                # Remove the old schedule
276
                evc.circuit_scheduler.remove(found_schedule)
277
                # Append the modified schedule
278
                evc.circuit_scheduler.append(new_schedule)
279
280
                # Cancel all schedule jobs
281
                for schedule in evc.circuit_scheduler:
282
                    self.sched.cancel_job(schedule.id)
283
                # Add all the circuit schedules again
284
                self.sched.add(evc)
285
                # Save EVC to the storehouse
286
                self.storehouse.save_evc(evc)
287
288
                result = {evc.id: evc.as_dict()}
289
                status = 200
290
            else:
291
                result = {'response': f'schedule_id {schedule_id} not found'}
292
                status = 404
293
294
        except ValueError as exception:
295
            log.error(exception)
296
            result = "Bad request: {}".format(exception)
297
            status = 400
298
299
        return jsonify(result), status
300
301
    @rest('/v2/evc/<circuit_id>/schedule/<schedule_id>', methods=['DELETE'])
302
    def delete_schedule(self, circuit_id, schedule_id):
303
        """Remove a circuit schedule.
304
305
        Remove the Schedule from EVC.
306
        Remove the Schedule from cron job.
307
        Save the EVC to the Storehouse.
308
        """
309
        circuits = self.storehouse.get_data()
310
        evc = self.evc_from_dict(circuits.get(circuit_id))
311
312
        # Try to find a circuit schedule
313
        found_schedule = None
314
        for schedule in evc.circuit_scheduler:
315
            if schedule.id == schedule_id:
316
                found_schedule = schedule
317
                break
318
319
        if found_schedule:
320
            # Remove the old schedule
321
            evc.circuit_scheduler.remove(found_schedule)
322
323
            # Cancel all schedule jobs
324
            for schedule in evc.circuit_scheduler:
325
                self.sched.cancel_job(schedule.id)
326
            # Add all the circuit schedules again
327
            self.sched.add(evc)
328
            # Save EVC to the storehouse
329
            self.storehouse.save_evc(evc)
330
331
            result = "Schedule removed"
332
            status = 200
333
        else:
334
            result = {'response': f'schedule_id {schedule_id} not found'}
335
            status = 404
336
337
        return jsonify(result), status
338
339
    def is_duplicated_evc(self, evc):
340
        """Verify if the circuit given is duplicated with the stored evcs.
341
342
        Args:
343
            evc (EVC): circuit to be analysed.
344
345
        Returns:
346
            boolean: True if the circuit is duplicated, otherwise False.
347
348
        """
349
        for circuit_dict in self.storehouse.get_data().values():
350
            try:
351
                circuit = self.evc_from_dict(circuit_dict)
352
            except ValueError as exception:
353
                log.error(exception)
354
                continue
355
356
            if not evc.archived and circuit == evc:
357
                return True
358
359
        return False
360
361
    @listen_to('kytos/topology.link_up')
362
    def handle_link_up(self, event):
363
        """Change circuit when link is up or end_maintenance."""
364
        evc = None
365
366
        for data in self.storehouse.get_data().values():
367
            try:
368
                evc = self.evc_from_dict(data)
369
            except ValueError as _exception:
370
                log.error(_exception)
371
                log.debug(f'{data.get("id")} can not be provisioning yet.')
372
                continue
373
374
            if evc.is_enabled() and not evc.archived:
375
                evc.handle_link_up(event.content['link'])
376
377
    @listen_to('kytos/topology.link_down')
378
    def handle_link_down(self, event):
379
        """Change circuit when link is down or under_mantenance."""
380
        evc = None
381
382
        for data in self.storehouse.get_data().values():
383
            try:
384
                evc = self.evc_from_dict(data)
385
            except ValueError as _exception:
386
                log.debug(f'{data.get("id")} can not be provisioned yet.')
387
                continue
388
389
            if evc.is_affected_by_link(event.content['link']):
390
                log.info('handling evc %s' % evc)
391
                evc.handle_link_down()
392
393
    def evc_from_dict(self, evc_dict):
394
        """Convert some dict values to instance of EVC classes.
395
396
        This method will convert: [UNI, Link]
397
        """
398
        data = evc_dict.copy()  # Do not modify the original dict
399
400
        for attribute, value in data.items():
401
402
            if 'uni' in attribute:
403
                try:
404
                    data[attribute] = self.uni_from_dict(value)
405
                except ValueError as exc:
406
                    raise ValueError(f'Error creating UNI: {exc}')
407
408
            if attribute == 'circuit_scheduler':
409
                data[attribute] = []
410
                for schedule in value:
411
                    data[attribute].append(CircuitSchedule.from_dict(schedule))
412
413
            if 'link' in attribute:
414
                if value:
415
                    data[attribute] = self.link_from_dict(value)
416
417
            if 'path' in attribute and attribute != 'dynamic_backup_path':
418
                if value:
419
                    data[attribute] = [self.link_from_dict(link)
420
                                       for link in value]
421
422
        return EVC(self.controller, **data)
423
424
    def uni_from_dict(self, uni_dict):
425
        """Return a UNI object from python dict."""
426
        if uni_dict is None:
427
            return False
428
429
        interface_id = uni_dict.get("interface_id")
430
        interface = self.controller.get_interface_by_id(interface_id)
431
        if interface is None:
432
            raise ValueError(f'Could not instantiate interface {interface_id}')
433
434
        tag_dict = uni_dict.get("tag")
435
        tag = TAG.from_dict(tag_dict)
436
        if tag is False:
437
            raise ValueError(f'Could not instantiate tag from dict {tag_dict}')
438
439
        uni = UNI(interface, tag)
440
441
        return uni
442
443
    def link_from_dict(self, link_dict):
444
        """Return a Link object from python dict."""
445
        id_a = link_dict.get('endpoint_a').get('id')
446
        id_b = link_dict.get('endpoint_b').get('id')
447
448
        endpoint_a = self.controller.get_interface_by_id(id_a)
449
        endpoint_b = self.controller.get_interface_by_id(id_b)
450
451
        link = Link(endpoint_a, endpoint_b)
452
        if 'metadata' in link_dict:
453
            link.extend_metadata(link_dict.get('metadata'))
454
455
        s_vlan = link.get_metadata('s_vlan')
456
        if s_vlan:
457
            tag = TAG.from_dict(s_vlan)
458
            if tag is False:
459
                error_msg = f'Could not instantiate tag from dict {s_vlan}'
460
                raise ValueError(error_msg)
461
            link.update_metadata('s_vlan', tag)
462
        return link
463