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

build.main   F

Complexity

Total Complexity 69

Size/Duplication

Total Lines 459
Duplicated Lines 0 %

Test Coverage

Coverage 42.18%

Importance

Changes 0
Metric Value
eloc 255
dl 0
loc 459
ccs 62
cts 147
cp 0.4218
rs 2.88
c 0
b 0
f 0
wmc 69

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.delete_circuit() 0 15 1
A Main.uni_from_dict() 0 18 4
B Main.update_schedule() 0 55 7
A Main.is_duplicated_evc() 0 21 4
A Main.handle_link_up() 0 14 3
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.update() 0 25 3
A Main.get_schedule() 0 14 2
B Main.create_schedule() 0 39 5

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
        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.disable()
168
        self.storehouse.save_evc(evc)
169
170 2
        return jsonify("Circuit removed"), 200
171
172
    @rest('/v2/evc/schedule', methods=['GET'])
173
    def list_schedules(self):
174
        """Endpoint to return all circuits stored."""
175
        circuits = self.storehouse.get_data().values()
176
        if not circuits:
177
            return jsonify({}), 200
178
179
        result = []
180 2
        for circuit in circuits:
181
            if circuit["circuit_scheduler"]:
182
                schedule = {circuit["id"]: circuit["circuit_scheduler"]}
183
                result.append(schedule)
184
185
        return jsonify(result), 200
186
187
    @rest('/v2/evc/<circuit_id>/schedule/', methods=['GET'])
188
    def get_schedule(self, circuit_id):
189 2
        """Endpoint to return list all schedule from a circuit."""
190
        circuits = self.storehouse.get_data()
191 2
192
        if circuit_id in circuits:
193
            circuit = circuits[circuit_id]
194
            result = circuit["circuit_scheduler"]
195
            status = 200
196
        else:
197
            result = {'response': f'circuit_id {circuit_id} not found'}
198
            status = 400
199
200
        return jsonify(result), status
201
202
    @rest('/v2/evc/<circuit_id>/schedule/', methods=['POST'])
203
    def create_schedule(self, circuit_id):
204
        """
205 2
        Create a new schedule for a given circuit.
206
207
        This service do no check if there are conflicts with another schedule.
208
        """
209
        # Try to create the circuit object
210
        data = request.get_json()
211
212
        if not data:
213
            return jsonify("Bad request: The request do not have a json."), 400
214
215
        try:
216
            # new schedule from dict
217
            new_schedule = CircuitSchedule.from_dict(data)
218
        except ValueError as exception:
219
            log.error(exception)
220
            return jsonify("Bad request: {}".format(exception)), 400
221 2
222
        # get the circuit
223
        circuits = self.storehouse.get_data()
224
        if circuit_id not in circuits:
225
            result = {'response': f'circuit_id {circuit_id} not found'}
226 2
            return jsonify(result), 404
227
228 2
        evc = self.evc_from_dict(circuits.get(circuit_id))
229
230 2
        # If there is no schedule, create the list
231 2
        if not evc.circuit_scheduler:
232 2
            evc.circuit_scheduler = []
233
234
        # Add the new schedule
235
        evc.circuit_scheduler.append(new_schedule)
236 2
237
        # save circuit
238
        self.storehouse.save_evc(evc)
239
240
        return jsonify({"schedule_id": new_schedule.id}), 201
241 2
242
    @rest('/v2/evc/<circuit_id>/schedule/<schedule_id>', methods=['PATCH'])
243
    def update_schedule(self, circuit_id, schedule_id):
244
        """Update a schedule.
245 2
246
        Change all attributes from the given schedule from a EVC circuit.
247
        The schedule ID is preserved as default, but it can also be modified.
248
249
        The scheduler is notified and refresh all the schedules from
250 2
        the given EVC circuit.
251
        """
252 2
        data = request.get_json()
253
        circuits = self.storehouse.get_data()
254
255
        if circuit_id not in circuits:
256
            result = {'response': f'circuit_id {circuit_id} not found'}
257
            return jsonify(result), 404
258
259
        try:
260
            evc = self.evc_from_dict(circuits.get(circuit_id))
261
262
            # Try to find a circuit schedule
263
            found_schedule = None
264
            for schedule in evc.circuit_scheduler:
265
                if schedule.id == schedule_id:
266
                    found_schedule = schedule
267
                    break
268
269
            if found_schedule:
270
                new_schedule = CircuitSchedule.from_dict(data)
271 2
                new_schedule.id = found_schedule.id
272
                # Remove the old schedule
273
                evc.circuit_scheduler.remove(found_schedule)
274
                # Append the modified schedule
275
                evc.circuit_scheduler.append(new_schedule)
276
277
                # Cancel all schedule jobs
278
                for schedule in evc.circuit_scheduler:
279
                    self.sched.cancel_job(schedule.id)
280
                # Add all the circuit schedules again
281
                self.sched.add(evc)
282
                # Save EVC to the storehouse
283
                self.storehouse.save_evc(evc)
284
285
                result = {evc.id: evc.as_dict()}
286
                status = 200
287
            else:
288
                result = {'response': f'schedule_id {schedule_id} not found'}
289
                status = 404
290
291
        except ValueError as exception:
292
            log.error(exception)
293
            result = "Bad request: {}".format(exception)
294
            status = 400
295
296
        return jsonify(result), status
297
298
    @rest('/v2/evc/<circuit_id>/schedule/<schedule_id>', methods=['DELETE'])
299
    def delete_schedule(self, circuit_id, schedule_id):
300
        """Remove a circuit schedule.
301
302
        Remove the Schedule from EVC.
303
        Remove the Schedule from cron job.
304
        Save the EVC to the Storehouse.
305
        """
306
        circuits = self.storehouse.get_data()
307
        evc = self.evc_from_dict(circuits.get(circuit_id))
308
309
        # Try to find a circuit schedule
310
        found_schedule = None
311
        for schedule in evc.circuit_scheduler:
312
            if schedule.id == schedule_id:
313
                found_schedule = schedule
314
                break
315
316
        if found_schedule:
317
            # Remove the old schedule
318
            evc.circuit_scheduler.remove(found_schedule)
319
320
            # Cancel all schedule jobs
321
            for schedule in evc.circuit_scheduler:
322
                self.sched.cancel_job(schedule.id)
323
            # Add all the circuit schedules again
324
            self.sched.add(evc)
325
            # Save EVC to the storehouse
326
            self.storehouse.save_evc(evc)
327
328
            result = "Schedule removed"
329
            status = 200
330
        else:
331
            result = {'response': f'schedule_id {schedule_id} not found'}
332
            status = 404
333
334
        return jsonify(result), status
335
336
    def is_duplicated_evc(self, evc):
337
        """Verify if the circuit given is duplicated with the stored evcs.
338
339
        Args:
340
            evc (EVC): circuit to be analysed.
341
342
        Returns:
343
            boolean: True if the circuit is duplicated, otherwise False.
344
345
        """
346
        for circuit_dict in self.storehouse.get_data().values():
347
            try:
348
                circuit = self.evc_from_dict(circuit_dict)
349
            except ValueError as exception:
350
                log.error(exception)
351
                continue
352
353
            if circuit == evc:
354
                return True
355
356
        return False
357
358
    @listen_to('kytos/topology.link_up')
359
    def handle_link_up(self, event):
360
        """Change circuit when link is up or end_maintenance."""
361
        evc = None
362
363
        for data in self.storehouse.get_data().values():
364
            try:
365
                evc = self.evc_from_dict(data)
366
            except ValueError as _exception:
367
                log.error(_exception)
368
                log.debug(f'{data.get("id")} can not be provisioning yet.')
369
                continue
370
371
            evc.handle_link_up(event.content['link'])
372
373
    @listen_to('kytos/topology.link_down')
374
    def handle_link_down(self, event):
375
        """Change circuit when link is down or under_mantenance."""
376
        evc = None
377
378
        for data in self.storehouse.get_data().values():
379
            try:
380
                evc = self.evc_from_dict(data)
381
            except ValueError as _exception:
382
                log.debug(f'{data.get("id")} can not be provisioned yet.')
383
                continue
384
385
            if evc.is_affected_by_link(event.content['link']):
386
                log.info('handling evc %s' % evc)
387
                evc.handle_link_down()
388
389
    def evc_from_dict(self, evc_dict):
390
        """Convert some dict values to instance of EVC classes.
391
392
        This method will convert: [UNI, Link]
393
        """
394
        data = evc_dict.copy()  # Do not modify the original dict
395
396
        for attribute, value in data.items():
397
398
            if 'uni' in attribute:
399
                try:
400
                    data[attribute] = self.uni_from_dict(value)
401
                except ValueError as exc:
402
                    raise ValueError(f'Error creating UNI: {exc}')
403
404
            if attribute == 'circuit_scheduler':
405
                data[attribute] = []
406
                for schedule in value:
407
                    data[attribute].append(CircuitSchedule.from_dict(schedule))
408
409
            if 'link' in attribute:
410
                if value:
411
                    data[attribute] = self.link_from_dict(value)
412
413
            if 'path' in attribute and attribute != 'dynamic_backup_path':
414
                if value:
415
                    data[attribute] = [self.link_from_dict(link)
416
                                       for link in value]
417
418
        return EVC(self.controller, **data)
419
420
    def uni_from_dict(self, uni_dict):
421
        """Return a UNI object from python dict."""
422
        if uni_dict is None:
423
            return False
424
425
        interface_id = uni_dict.get("interface_id")
426
        interface = self.controller.get_interface_by_id(interface_id)
427
        if interface is None:
428
            raise ValueError(f'Could not instantiate interface {interface_id}')
429
430
        tag_dict = uni_dict.get("tag")
431
        tag = TAG.from_dict(tag_dict)
432
        if tag is False:
433
            raise ValueError(f'Could not instantiate tag from dict {tag_dict}')
434
435
        uni = UNI(interface, tag)
436
437
        return uni
438
439
    def link_from_dict(self, link_dict):
440
        """Return a Link object from python dict."""
441
        id_a = link_dict.get('endpoint_a').get('id')
442
        id_b = link_dict.get('endpoint_b').get('id')
443
444
        endpoint_a = self.controller.get_interface_by_id(id_a)
445
        endpoint_b = self.controller.get_interface_by_id(id_b)
446
447
        link = Link(endpoint_a, endpoint_b)
448
        if 'metadata' in link_dict:
449
            link.extend_metadata(link_dict.get('metadata'))
450
451
        s_vlan = link.get_metadata('s_vlan')
452
        if s_vlan:
453
            tag = TAG.from_dict(s_vlan)
454
            if tag is False:
455
                error_msg = f'Could not instantiate tag from dict {s_vlan}'
456
                raise ValueError(error_msg)
457
            link.update_metadata('s_vlan', tag)
458
        return link
459