Passed
Pull Request — master (#73)
by Gleyberson
02:07
created

build.main   F

Complexity

Total Complexity 100

Size/Duplication

Total Lines 568
Duplicated Lines 5.99 %

Test Coverage

Coverage 20.36%

Importance

Changes 0
Metric Value
wmc 100
eloc 378
dl 34
loc 568
ccs 68
cts 334
cp 0.2036
rs 2
c 0
b 0
f 0

47 Methods

Rating   Name   Duplication   Size   Complexity  
A Main.shutdown() 0 3 1
A Main.setup() 0 8 1
A Main.execute() 0 2 1
A Main._get_link_or_create() 0 9 3
A Main.disable_switch() 0 8 2
A Main.disable_link() 0 9 2
A Main.enable_switch() 0 8 2
A Main.enable_interface() 17 17 3
A Main.add_switch_metadata() 0 12 2
A Main.get_interfaces() 0 10 3
A Main.delete_interface_metadata() 0 21 4
A Main.add_interface_metadata() 0 20 3
A Main.get_switches() 0 4 1
A Main.notify_link_status_change() 0 9 2
A Main.handle_interface_down() 0 9 1
A Main.handle_interface_link_up() 0 13 3
A Main.handle_interface_created() 0 4 1
A Main.get_interface_metadata() 0 16 3
A Main.delete_link_metadata() 0 13 3
A Main.notify_topology_update() 0 6 1
A Main.enable_link() 0 9 2
A Main.handle_new_switch() 0 12 1
A Main.get_switch_metadata() 0 8 2
A Main.handle_connection_lost() 0 12 2
A Main.disable_interface() 17 17 3
A Main.get_links() 0 7 1
A Main.get_topology() 0 7 1
A Main.handle_interface_link_down() 0 12 3
A Main._get_links_dict() 0 4 1
A Main.add_links() 0 14 1
A Main.handle_interface_up() 0 9 1
A Main.handle_interface_deleted() 0 4 1
A Main._get_link_from_interface() 0 6 3
A Main.add_link_metadata() 0 12 2
A Main.notify_metadata_changes() 0 17 4
A Main.delete_switch_metadata() 0 11 2
A Main._get_topology_dict() 0 4 1
A Main.get_link_metadata() 0 7 2
A Main._get_topology() 0 3 1
A Main.load_from_store() 0 8 2
A Main.verify_storehouse() 0 8 1
A Main.notify_port_created() 0 6 1
A Main.request_retrieve_entities() 0 18 2
A Main._get_switches_dict() 0 12 4
B Main.update_instance_metadata() 0 19 8
A Main.update_instance() 0 8 2
A Main.save_metadata_on_store() 0 25 4

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complexity

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

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/topology Kytos Network Application.
2
3
Manage the network topology
4
"""
5 1
from flask import jsonify, request
6
7 1
from kytos.core import KytosEvent, KytosNApp, log, rest
8 1
from kytos.core.helpers import listen_to
9 1
from kytos.core.interface import Interface
10 1
from kytos.core.link import Link
11 1
from kytos.core.switch import Switch
12
# from napps.kytos.topology import settings
13 1
from napps.kytos.topology.models import Topology
14
15
16 1
class Main(KytosNApp):  # pylint: disable=too-many-public-methods
17
    """Main class of kytos/topology NApp.
18
19
    This class is the entry point for this napp.
20
    """
21
22 1
    def setup(self):
23
        """Initialize the NApp's links list."""
24 1
        self.links = {}
25 1
        self.store_items = {}
26
27 1
        self.verify_storehouse('switches')
28 1
        self.verify_storehouse('interfaces')
29 1
        self.verify_storehouse('links')
30
31 1
    def execute(self):
32
        """Do nothing."""
33
34 1
    def shutdown(self):
35
        """Do nothing."""
36
        log.info('NApp kytos/topology shutting down.')
37
38 1
    def _get_link_or_create(self, endpoint_a, endpoint_b):
39
        new_link = Link(endpoint_a, endpoint_b)
40
41
        for link in self.links.values():
42
            if new_link == link:
43
                return link
44
45
        self.links[new_link.id] = new_link
46
        return new_link
47
48 1
    def _get_switches_dict(self):
49
        """Return a dictionary with the known switches."""
50 1
        swi = {'switches': {s.id: s.as_dict() for s in
51
                            self.controller.switches.values()}}
52 1
        for idx, switch_id in enumerate(swi['switches']):
53
            if ('lat' in swi['switches'][switch_id]['metadata'] and
54
                    'lng' in swi['switches'][switch_id]['metadata']):
55
                continue
56
            # Values are constants so the switches are initialized in the ocean
57
            swi['switches'][switch_id]['metadata']['lat'] = str(0.0)
58
            swi['switches'][switch_id]['metadata']['lng'] = str(-30.0+idx*10.0)
59 1
        return swi
60
61 1
    def _get_links_dict(self):
62
        """Return a dictionary with the known links."""
63
        return {'links': {l.id: l.as_dict() for l in
64
                          self.links.values()}}
65
66 1
    def _get_topology_dict(self):
67
        """Return a dictionary with the known topology."""
68
        return {'topology': {**self._get_switches_dict(),
69
                             **self._get_links_dict()}}
70
71 1
    def _get_topology(self):
72
        """Return an object representing the topology."""
73
        return Topology(self.controller.switches, self.links)
74
75 1
    def _get_link_from_interface(self, interface):
76
        """Return the link of the interface, or None if it does not exist."""
77
        for link in self.links.values():
78
            if interface in (link.endpoint_a, link.endpoint_b):
79
                return link
80
        return None
81
82 1
    @rest('v3/')
83
    def get_topology(self):
84
        """Return the latest known topology.
85
86
        This topology is updated when there are network events.
87
        """
88
        return jsonify(self._get_topology_dict())
89
90
    # Switch related methods
91 1
    @rest('v3/switches')
92
    def get_switches(self):
93
        """Return a json with all the switches in the topology."""
94
        return jsonify(self._get_switches_dict())
95
96 1
    @rest('v3/switches/<dpid>/enable', methods=['POST'])
97
    def enable_switch(self, dpid):
98
        """Administratively enable a switch in the topology."""
99
        try:
100
            self.controller.switches[dpid].enable()
101
            return jsonify("Operation successful"), 201
102
        except KeyError:
103
            return jsonify("Switch not found"), 404
104
105 1
    @rest('v3/switches/<dpid>/disable', methods=['POST'])
106
    def disable_switch(self, dpid):
107
        """Administratively disable a switch in the topology."""
108
        try:
109
            self.controller.switches[dpid].disable()
110
            return jsonify("Operation successful"), 201
111
        except KeyError:
112
            return jsonify("Switch not found"), 404
113
114 1
    @rest('v3/switches/<dpid>/metadata')
115
    def get_switch_metadata(self, dpid):
116
        """Get metadata from a switch."""
117
        try:
118
            return jsonify({"metadata":
119
                            self.controller.switches[dpid].metadata}), 200
120
        except KeyError:
121
            return jsonify("Switch not found"), 404
122
123 1
    @rest('v3/switches/<dpid>/metadata', methods=['POST'])
124
    def add_switch_metadata(self, dpid):
125
        """Add metadata to a switch."""
126
        metadata = request.get_json()
127
        try:
128
            switch = self.controller.switches[dpid]
129
        except KeyError:
130
            return jsonify("Switch not found"), 404
131
132
        switch.extend_metadata(metadata)
133
        self.notify_metadata_changes(switch, 'added')
134
        return jsonify("Operation successful"), 201
135
136 1
    @rest('v3/switches/<dpid>/metadata/<key>', methods=['DELETE'])
137
    def delete_switch_metadata(self, dpid, key):
138
        """Delete metadata from a switch."""
139
        try:
140
            switch = self.controller.switches[dpid]
141
        except KeyError:
142
            return jsonify("Switch not found"), 404
143
144
        switch.remove_metadata(key)
145
        self.notify_metadata_changes(switch, 'removed')
146
        return jsonify("Operation successful"), 200
147
148
    # Interface related methods
149 1
    @rest('v3/interfaces')
150
    def get_interfaces(self):
151
        """Return a json with all the interfaces in the topology."""
152
        interfaces = {}
153
        switches = self._get_switches_dict()
154
        for switch in switches['switches'].values():
155
            for interface_id, interface in switch['interfaces'].items():
156
                interfaces[interface_id] = interface
157
158
        return jsonify({'interfaces': interfaces})
159
160 1 View Code Duplication
    @rest('v3/interfaces/<interface_id>/enable', methods=['POST'])
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
161
    def enable_interface(self, interface_id):
162
        """Administratively enable an interface in the topology."""
163
        switch_id = ":".join(interface_id.split(":")[:-1])
164
        interface_number = int(interface_id.split(":")[-1])
165
166
        try:
167
            switch = self.controller.switches[switch_id]
168
        except KeyError:
169
            return jsonify("Switch not found"), 404
170
171
        try:
172
            switch.interfaces[interface_number].enable()
173
        except KeyError:
174
            return jsonify("Interface not found"), 404
175
176
        return jsonify("Operation successful"), 201
177
178 1 View Code Duplication
    @rest('v3/interfaces/<interface_id>/disable', methods=['POST'])
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
179
    def disable_interface(self, interface_id):
180
        """Administratively disable an interface in the topology."""
181
        switch_id = ":".join(interface_id.split(":")[:-1])
182
        interface_number = int(interface_id.split(":")[-1])
183
184
        try:
185
            switch = self.controller.switches[switch_id]
186
        except KeyError:
187
            return jsonify("Switch not found"), 404
188
189
        try:
190
            switch.interfaces[interface_number].disable()
191
        except KeyError:
192
            return jsonify("Interface not found"), 404
193
194
        return jsonify("Operation successful"), 201
195
196 1
    @rest('v3/interfaces/<interface_id>/metadata')
197
    def get_interface_metadata(self, interface_id):
198
        """Get metadata from an interface."""
199
        switch_id = ":".join(interface_id.split(":")[:-1])
200
        interface_number = int(interface_id.split(":")[-1])
201
        try:
202
            switch = self.controller.switches[switch_id]
203
        except KeyError:
204
            return jsonify("Switch not found"), 404
205
206
        try:
207
            interface = switch.interfaces[interface_number]
208
        except KeyError:
209
            return jsonify("Interface not found"), 404
210
211
        return jsonify({"metadata": interface.metadata}), 200
212
213 1
    @rest('v3/interfaces/<interface_id>/metadata', methods=['POST'])
214
    def add_interface_metadata(self, interface_id):
215
        """Add metadata to an interface."""
216
        metadata = request.get_json()
217
218
        switch_id = ":".join(interface_id.split(":")[:-1])
219
        interface_number = int(interface_id.split(":")[-1])
220
        try:
221
            switch = self.controller.switches[switch_id]
222
        except KeyError:
223
            return jsonify("Switch not found"), 404
224
225
        try:
226
            interface = switch.interfaces[interface_number]
227
        except KeyError:
228
            return jsonify("Interface not found"), 404
229
230
        interface.extend_metadata(metadata)
231
        self.notify_metadata_changes(interface, 'added')
232
        return jsonify("Operation successful"), 201
233
234 1
    @rest('v3/interfaces/<interface_id>/metadata/<key>', methods=['DELETE'])
235
    def delete_interface_metadata(self, interface_id, key):
236
        """Delete metadata from an interface."""
237
        switch_id = ":".join(interface_id.split(":")[:-1])
238
        interface_number = int(interface_id.split(":")[-1])
239
240
        try:
241
            switch = self.controller.switches[switch_id]
242
        except KeyError:
243
            return jsonify("Switch not found"), 404
244
245
        try:
246
            interface = switch.interfaces[interface_number]
247
        except KeyError:
248
            return jsonify("Interface not found"), 404
249
250
        if interface.remove_metadata(key) is False:
251
            return jsonify("Metadata not found"), 404
252
253
        self.notify_metadata_changes(interface, 'removed')
254
        return jsonify("Operation successful"), 200
255
256
    # Link related methods
257 1
    @rest('v3/links')
258
    def get_links(self):
259
        """Return a json with all the links in the topology.
260
261
        Links are connections between interfaces.
262
        """
263
        return jsonify(self._get_links_dict()), 200
264
265 1
    @rest('v3/links/<link_id>/enable', methods=['POST'])
266
    def enable_link(self, link_id):
267
        """Administratively enable a link in the topology."""
268
        try:
269
            self.links[link_id].enable()
270
        except KeyError:
271
            return jsonify("Link not found"), 404
272
273
        return jsonify("Operation successful"), 201
274
275 1
    @rest('v3/links/<link_id>/disable', methods=['POST'])
276
    def disable_link(self, link_id):
277
        """Administratively disable a link in the topology."""
278
        try:
279
            self.links[link_id].disable()
280
        except KeyError:
281
            return jsonify("Link not found"), 404
282
283
        return jsonify("Operation successful"), 201
284
285 1
    @rest('v3/links/<link_id>/metadata')
286
    def get_link_metadata(self, link_id):
287
        """Get metadata from a link."""
288
        try:
289
            return jsonify({"metadata": self.links[link_id].metadata}), 200
290
        except KeyError:
291
            return jsonify("Link not found"), 404
292
293 1
    @rest('v3/links/<link_id>/metadata', methods=['POST'])
294
    def add_link_metadata(self, link_id):
295
        """Add metadata to a link."""
296
        metadata = request.get_json()
297
        try:
298
            link = self.links[link_id]
299
        except KeyError:
300
            return jsonify("Link not found"), 404
301
302
        link.extend_metadata(metadata)
303
        self.notify_metadata_changes(link, 'added')
304
        return jsonify("Operation successful"), 201
305
306 1
    @rest('v3/links/<link_id>/metadata/<key>', methods=['DELETE'])
307
    def delete_link_metadata(self, link_id, key):
308
        """Delete metadata from a link."""
309
        try:
310
            link = self.links[link_id]
311
        except KeyError:
312
            return jsonify("Link not found"), 404
313
314
        if link.remove_metadata(key) is False:
315
            return jsonify("Metadata not found"), 404
316
317
        self.notify_metadata_changes(link, 'removed')
318
        return jsonify("Operation successful"), 200
319
320 1
    @listen_to('.*.switch.(new|reconnected)')
321
    def handle_new_switch(self, event):
322
        """Create a new Device on the Topology.
323
324
        Handle the event of a new created switch and update the topology with
325
        this new device.
326
        """
327
        switch = event.content['switch']
328
        switch.activate()
329
        log.debug('Switch %s added to the Topology.', switch.id)
330
        self.notify_topology_update()
331
        self.update_instance_metadata(switch)
332
333 1
    @listen_to('.*.connection.lost')
334
    def handle_connection_lost(self, event):
335
        """Remove a Device from the topology.
336
337
        Remove the disconnected Device and every link that has one of its
338
        interfaces.
339
        """
340
        switch = event.content['source'].switch
341
        if switch:
342
            switch.deactivate()
343
            log.debug('Switch %s removed from the Topology.', switch.id)
344
            self.notify_topology_update()
345
346 1
    def handle_interface_up(self, event):
347
        """Update the topology based on a Port Modify event.
348
349
        The event notifies that an interface was changed to 'up'.
350
        """
351
        interface = event.content['interface']
352
        interface.activate()
353
        self.notify_topology_update()
354
        self.update_instance_metadata(interface)
355
356 1
    @listen_to('.*.switch.interface.created')
357
    def handle_interface_created(self, event):
358
        """Update the topology based on a Port Create event."""
359
        self.handle_interface_up(event)
360
361 1
    def handle_interface_down(self, event):
362
        """Update the topology based on a Port Modify event.
363
364
        The event notifies that an interface was changed to 'down'.
365
        """
366
        interface = event.content['interface']
367
        interface.deactivate()
368
        self.handle_interface_link_down(event)
369
        self.notify_topology_update()
370
371 1
    @listen_to('.*.switch.interface.deleted')
372
    def handle_interface_deleted(self, event):
373
        """Update the topology based on a Port Delete event."""
374
        self.handle_interface_down(event)
375
376 1
    @listen_to('.*.switch.interface.link_up')
377
    def handle_interface_link_up(self, event):
378
        """Update the topology based on a Port Modify event.
379
380
        The event notifies that an interface's link was changed to 'up'.
381
        """
382
        interface = event.content['interface']
383
        link = self._get_link_from_interface(interface)
384
        if link and not link.is_active():
385
            link.activate()
386
            self.notify_topology_update()
387
            self.update_instance_metadata(interface.link)
388
            self.notify_link_status_change(link)
389
390 1
    @listen_to('.*.switch.interface.link_down')
391
    def handle_interface_link_down(self, event):
392
        """Update the topology based on a Port Modify event.
393
394
        The event notifies that an interface's link was changed to 'down'.
395
        """
396
        interface = event.content['interface']
397
        link = self._get_link_from_interface(interface)
398
        if link and link.is_active():
399
            link.deactivate()
400
            self.notify_topology_update()
401
            self.notify_link_status_change(link)
402
403 1
    @listen_to('.*.interface.is.nni')
404
    def add_links(self, event):
405
        """Update the topology with links related to the NNI interfaces."""
406
        interface_a = event.content['interface_a']
407
        interface_b = event.content['interface_b']
408
409
        link = self._get_link_or_create(interface_a, interface_b)
410
        interface_a.update_link(link)
411
        interface_b.update_link(link)
412
413
        interface_a.nni = True
414
        interface_b.nni = True
415
416
        self.notify_topology_update()
417
418
    # def add_host(self, event):
419
    #    """Update the topology with a new Host."""
420
421
    #    interface = event.content['port']
422
    #    mac = event.content['reachable_mac']
423
424
    #    host = Host(mac)
425
    #    link = self.topology.get_link(interface.id)
426
    #    if link is not None:
427
    #        return
428
429
    #    self.topology.add_link(interface.id, host.id)
430
    #    self.topology.add_device(host)
431
432
    #    if settings.DISPLAY_FULL_DUPLEX_LINKS:
433
    #        self.topology.add_link(host.id, interface.id)
434
435 1
    def notify_topology_update(self):
436
        """Send an event to notify about updates on the topology."""
437
        name = 'kytos/topology.updated'
438
        event = KytosEvent(name=name, content={'topology':
439
                                               self._get_topology()})
440
        self.controller.buffers.app.put(event)
441
442 1
    def notify_link_status_change(self, link):
443
        """Send an event to notify about a status change on a link."""
444
        name = 'kytos/topology.'
445
        if link.is_active():
446
            status = 'link_up'
447
        else:
448
            status = 'link_down'
449
        event = KytosEvent(name=name+status, content={'link': link})
450
        self.controller.buffers.app.put(event)
451
452 1
    def notify_metadata_changes(self, obj, action):
453
        """Send an event to notify about metadata changes."""
454
        if isinstance(obj, Switch):
455
            entity = 'switch'
456
            entities = 'switches'
457
        elif isinstance(obj, Interface):
458
            entity = 'interface'
459
            entities = 'interfaces'
460
        elif isinstance(obj, Link):
461
            entity = 'link'
462
            entities = 'links'
463
464
        name = f'kytos/topology.{entities}.metadata.{action}'
465
        event = KytosEvent(name=name, content={entity: obj,
0 ignored issues
show
introduced by
The variable entity does not seem to be defined for all execution paths.
Loading history...
466
                                               'metadata': obj.metadata})
467
        self.controller.buffers.app.put(event)
468
        log.debug(f'Metadata from {obj.id} was {action}.')
469
470 1
    @listen_to('.*.switch.port.created')
471
    def notify_port_created(self, original_event):
472
        """Notify when a port is created."""
473
        name = f'kytos/topology.port.created'
474
        event = KytosEvent(name=name, content=original_event.content)
475
        self.controller.buffers.app.put(event)
476
477 1
    @listen_to('kytos/topology.*.metadata.*')
478
    def save_metadata_on_store(self, event):
479
        """Send to storehouse the data updated."""
480
        name = 'kytos.storehouse.update'
481
        if 'switch' in event.content:
482
            store = self.store_items.get('switches')
483
            obj = event.content.get('switch')
484
            namespace = 'kytos.topology.switches.metadata'
485
        elif 'interface' in event.content:
486
            store = self.store_items.get('interfaces')
487
            obj = event.content.get('interface')
488
            namespace = 'kytos.topology.iterfaces.metadata'
489
        elif 'link' in event.content:
490
            store = self.store_items.get('links')
491
            obj = event.content.get('link')
492
            namespace = 'kytos.topology.links.metadata'
493
494
        store.data[obj.id] = obj.metadata
0 ignored issues
show
introduced by
The variable obj does not seem to be defined for all execution paths.
Loading history...
introduced by
The variable store does not seem to be defined for all execution paths.
Loading history...
495
        content = {'namespace': namespace,
0 ignored issues
show
introduced by
The variable namespace does not seem to be defined for all execution paths.
Loading history...
496
                   'box_id': store.box_id,
497
                   'data': store.data,
498
                   'callback': self.update_instance}
499
500
        event = KytosEvent(name=name, content=content)
501
        self.controller.buffers.app.put(event)
502
503 1
    @staticmethod
504
    def update_instance(event, _data, error):
505
        """Display in Kytos console if the data was updated."""
506
        entities = event.content.get('namespace', '').split('.')[-2]
507
        if error:
508
            log.error(f'Error trying to update storehouse {entities}.')
509
        else:
510
            log.debug(f'Storehouse update to entities: {entities}.')
511
512 1
    def verify_storehouse(self, entities):
513
        """Request a list of box saved by specific entity."""
514 1
        name = 'kytos.storehouse.list'
515 1
        content = {'namespace': f'kytos.topology.{entities}.metadata',
516
                   'callback': self.request_retrieve_entities}
517 1
        event = KytosEvent(name=name, content=content)
518 1
        self.controller.buffers.app.put(event)
519 1
        log.info(f'verify data in storehouse for {entities}.')
520
521 1
    def request_retrieve_entities(self, event, data, _error):
522
        """Create a box or retrieve an existent box from storehouse."""
523
        msg = ''
524
        content = {'namespace': event.content.get('namespace'),
525
                   'callback': self.load_from_store,
526
                   'data': {}}
527
528
        if not data:
529
            name = 'kytos.storehouse.create'
530
            msg = 'Create new box in storehouse'
531
        else:
532
            name = 'kytos.storehouse.retrieve'
533
            content['box_id'] = data[0]
534
            msg = 'Retrieve data from storeohouse.'
535
536
        event = KytosEvent(name=name, content=content)
537
        self.controller.buffers.app.put(event)
538
        log.debug(msg)
539
540 1
    def load_from_store(self, event, box, error):
541
        """Save the data retrived from storehouse."""
542
        entities = event.content.get('namespace', '').split('.')[-2]
543
        if error:
544
            log.error('Error while get a box from storehouse.')
545
        else:
546
            self.store_items[entities] = box
547
            log.debug('Data updated')
548
549 1
    def update_instance_metadata(self, obj):
550
        """Update object instance with saved metadata."""
551
        metadata = None
552
        if isinstance(obj, Interface):
553
            all_metadata = self.store_items.get('interfaces', None)
554
            if all_metadata:
555
                metadata = all_metadata.data.get(obj.id)
556
        elif isinstance(obj, Switch):
557
            all_metadata = self.store_items.get('switches', None)
558
            if all_metadata:
559
                metadata = all_metadata.data.get(obj.id)
560
        elif isinstance(obj, Link):
561
            all_metadata = self.store_items.get('links', None)
562
            if all_metadata:
563
                metadata = all_metadata.data.get(obj.id)
564
565
        if metadata:
566
            obj.extend_metadata(metadata)
567
            log.debug(f'Metadata to {obj.id} was updated')
568