Passed
Push — master ( 51d10d...074f60 )
by Humberto
02:18
created

build.main   F

Complexity

Total Complexity 156

Size/Duplication

Total Lines 858
Duplicated Lines 6.76 %

Test Coverage

Coverage 89.31%

Importance

Changes 0
Metric Value
eloc 604
dl 58
loc 858
ccs 493
cts 552
cp 0.8931
rs 1.996
c 0
b 0
f 0
wmc 156

58 Methods

Rating   Name   Duplication   Size   Complexity  
A Main.notify_port_created() 0 6 1
A Main.disable_switch() 0 12 2
A Main.verify_storehouse() 0 8 1
A Main.load_from_store() 0 8 2
A Main.request_retrieve_entities() 0 18 2
A Main.disable_link() 0 9 2
A Main.enable_switch() 0 12 2
B Main.enable_interface() 29 29 7
A Main.add_switch_metadata() 0 12 2
A Main.get_interfaces() 0 10 3
A Main._get_switches_dict() 0 12 3
A Main.delete_interface_metadata() 0 21 4
A Main.add_interface_metadata() 0 20 3
B Main.update_instance_metadata() 0 19 8
A Main.get_switches() 0 4 1
A Main.handle_link_maintenance_end() 0 19 4
A Main.notify_link_status_change() 0 9 2
A Main.handle_interface_down() 0 9 1
A Main.handle_interface_link_up() 0 8 1
B Main.handle_link_up() 0 27 7
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.shutdown() 0 3 1
C Main._restore_switch() 0 62 11
A Main.notify_topology_update() 0 6 1
A Main.enable_link() 0 9 2
A Main.handle_link_down() 0 8 3
A Main.setup() 0 18 1
A Main.handle_new_switch() 0 13 1
A Main.execute() 0 3 1
A Main.save_status_on_storehouse() 0 13 2
A Main.get_switch_metadata() 0 8 2
A Main.update_instance() 0 8 2
A Main.handle_switch_maintenance_end() 0 10 3
A Main.handle_connection_lost() 0 12 2
A Main.get_links() 0 7 1
B Main.disable_interface() 29 29 7
A Main.handle_switch_maintenance_start() 0 11 4
B Main._load_network_status() 0 25 5
A Main.get_topology() 0 7 1
A Main._restore_link() 0 22 4
A Main.save_metadata_on_store() 0 25 4
A Main.handle_interface_link_down() 0 8 1
A Main._get_links_dict() 0 4 1
A Main.handle_link_maintenance_start() 0 19 4
A Main.add_links() 0 20 2
A Main.handle_interface_up() 0 9 1
A Main.handle_interface_deleted() 0 4 1
A Main._get_link_or_create() 0 9 3
A Main.restore_network_status() 0 11 5
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

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
import time
6
7 1
from flask import jsonify, request
8
9 1
from kytos.core import KytosEvent, KytosNApp, log, rest
10 1
from kytos.core.exceptions import KytosLinkCreationError
11 1
from kytos.core.helpers import listen_to
12 1
from kytos.core.interface import Interface
13 1
from kytos.core.link import Link
14 1
from kytos.core.switch import Switch
15 1
from napps.kytos.topology import settings
16 1
from napps.kytos.topology.exceptions import RestoreError
17 1
from napps.kytos.topology.models import Topology
18 1
from napps.kytos.topology.storehouse import StoreHouse
19
20 1
DEFAULT_LINK_UP_TIMER = 10
21 1
DEFAULT_INTERFACE_RESTORE_TIMER = 2
22 1
RESTORE_INTERFACE_ATTEMPTS = 20
23
24
25 1
class Main(KytosNApp):  # pylint: disable=too-many-public-methods
26
    """Main class of kytos/topology NApp.
27
28
    This class is the entry point for this napp.
29
    """
30
31 1
    def setup(self):
32
        """Initialize the NApp's links list."""
33 1
        self.links = {}
34 1
        self.store_items = {}
35 1
        self.switches_state = {}
36 1
        self.interfaces_state = {}
37 1
        self.links_state = {}
38 1
        self._verified_links = []
39 1
        self.link_up_timer = getattr(settings, 'LINK_UP_TIMER',
40
                                     DEFAULT_LINK_UP_TIMER)
41 1
        self.interface_restore = getattr(settings, 'INTERFACE_RESTORE_TIMER',
42
                                         DEFAULT_INTERFACE_RESTORE_TIMER)
43
44 1
        self.verify_storehouse('switches')
45 1
        self.verify_storehouse('interfaces')
46 1
        self.verify_storehouse('links')
47
48 1
        self.storehouse = StoreHouse(self.controller)
49
50 1
    def execute(self):
51
        """Execute once when the napp is running."""
52
        self._load_network_status()
53
54 1
    def shutdown(self):
55
        """Do nothing."""
56
        log.info('NApp kytos/topology shutting down.')
57
58 1
    def _get_link_or_create(self, endpoint_a, endpoint_b):
59 1
        new_link = Link(endpoint_a, endpoint_b)
60
61 1
        for link in self.links.values():
62
            if new_link == link:
63
                return link
64
65 1
        self.links[new_link.id] = new_link
66 1
        return new_link
67
68 1
    def _get_switches_dict(self):
69
        """Return a dictionary with the known switches."""
70 1
        switches = {'switches': {}}
71 1
        for idx, switch in enumerate(self.controller.switches.values()):
72 1
            switch_data = switch.as_dict()
73 1
            if not all(key in switch_data['metadata']
74
                       for key in ('lat', 'lng')):
75
                # Switches are initialized somewhere in the ocean
76
                switch_data['metadata']['lat'] = str(0.0)
77
                switch_data['metadata']['lng'] = str(-30.0+idx*10.0)
78 1
            switches['switches'][switch.id] = switch_data
79 1
        return switches
80
81 1
    def _get_links_dict(self):
82
        """Return a dictionary with the known links."""
83 1
        return {'links': {l.id: l.as_dict() for l in
84
                          self.links.values()}}
85
86 1
    def _get_topology_dict(self):
87
        """Return a dictionary with the known topology."""
88 1
        return {'topology': {**self._get_switches_dict(),
89
                             **self._get_links_dict()}}
90
91 1
    def _get_topology(self):
92
        """Return an object representing the topology."""
93 1
        return Topology(self.controller.switches, self.links)
94
95 1
    def _get_link_from_interface(self, interface):
96
        """Return the link of the interface, or None if it does not exist."""
97 1
        for link in self.links.values():
98 1
            if interface in (link.endpoint_a, link.endpoint_b):
99 1
                return link
100 1
        return None
101
102 1
    def _restore_link(self, link_id):
103
        """Restore link's administrative state from storehouse."""
104 1
        try:
105 1
            state = self.links_state[link_id]
106 1
        except KeyError:
107 1
            error = (f'The link {link_id} has no stored '
108
                     'administrative state to be restored.')
109 1
            raise RestoreError(error)
110
111 1
        try:
112 1
            link = self.links[link_id]
113 1
            if state['enabled']:
114 1
                link.enable()
115
            else:
116 1
                link.disable()
117 1
        except KeyError:
118 1
            error = ('Error restoring link status.'
119
                     f'The link {link_id} does not exist.')
120 1
            raise RestoreError(error)
121 1
        log.info(f'The state of link {link.id} has been restored.')
122 1
        self.notify_topology_update()
123 1
        self.update_instance_metadata(link)
124
125 1
    def _restore_switch(self, switch_id):
126
        """Restore switch's administrative state from storehouse."""
127 1
        try:
128 1
            state = self.switches_state[switch_id]
129 1
        except KeyError:
130 1
            error = (f'The switch {switch_id} has no stored'
131
                     ' administrative state to be restored.')
132 1
            raise RestoreError(error)
133
134 1
        try:
135 1
            switch = self.controller.switches[switch_id]
136 1
        except KeyError:
137
            # Maybe we should remove the switch from switches_state here
138 1
            error = ('Error while restoring switches status. The '
139
                     f'switch {switch_id} does not exist.')
140 1
            raise RestoreError(error)
141
142 1
        if state:
143 1
            switch.enable()
144
        else:
145 1
            switch.disable()
146
147 1
        log.debug('Waiting to restore administrative state of switch '
148
                  f'{switch_id} interfaces.')
149 1
        i = 0
150
        # wait to restore interfaces
151 1
        while not switch.interfaces and i < RESTORE_INTERFACE_ATTEMPTS:
152
            time.sleep(self.interface_restore)
153
            i += 1
154 1
        if not switch.interfaces:
155
            error = ('Error restoring administrative state of switch '
156
                     f'{switch_id} interfaces.')
157
            raise RestoreError(error)
158
159
        # restore interfaces
160 1
        for interface_id in switch.interfaces:
161 1
            iface_id = ":".join([switch_id, str(interface_id)])
162
            # restore only the administrative state of saved interfaces
163 1
            if iface_id not in self.interfaces_state:
164
                error = ("The stored topology is different from the current "
165
                         f"topology. The interface {iface_id} hasn't been "
166
                         "stored.")
167
                log.info(error)
168
                continue
169 1
            state = self.interfaces_state[iface_id]
170 1
            iface_number = int(interface_id)
171 1
            iface_status, lldp_status = state
172 1
            try:
173 1
                interface = switch.interfaces[iface_number]
174
            except KeyError:
175
                log.error('Error restoring interface status: '
176
                          '%s does not exist.', iface_id)
177
                continue
178
179 1
            if iface_status:
180 1
                interface.enable()
181
            else:
182 1
                interface.disable()
183 1
            interface.lldp = lldp_status
184 1
            self.update_instance_metadata(interface)
185
186 1
        log.info(f'The state of switch {switch_id} has been restored.')
187
188
    # pylint: disable=attribute-defined-outside-init
189 1
    def _load_network_status(self):
190
        """Load network status saved in storehouse."""
191 1
        try:
192 1
            status = self.storehouse.get_data()
193
        except FileNotFoundError as error:
194
            log.info(error)
195
            return
196 1
        if status:
197 1
            switches = status['network_status']['switches']
198 1
            self.links_state = status['network_status']['links']
199
200 1
            for switch_id, switch_att in switches.items():
201
                # get switches status
202 1
                self.switches_state[switch_id] = switch_att['enabled']
203 1
                iface = switch_att['interfaces']
204
                # get interface status
205 1
                for iface_id, iface_att in iface.items():
206 1
                    enabled_value = iface_att['enabled']
207 1
                    lldp_value = iface_att['lldp']
208 1
                    self.interfaces_state[iface_id] = (enabled_value,
209
                                                       lldp_value)
210
211
        else:
212
            error = 'There is no status saved to restore.'
213
            log.info(error)
214
215 1
    @rest('v3/')
216
    def get_topology(self):
217
        """Return the latest known topology.
218
219
        This topology is updated when there are network events.
220
        """
221 1
        return jsonify(self._get_topology_dict())
222
223 1
    def restore_network_status(self, obj):
224
        """Restore the network administrative status saved in storehouse."""
225 1
        try:
226 1
            if isinstance(obj, Switch):
227 1
                self._restore_switch(obj.id)
228 1
            elif isinstance(obj, Link):
229 1
                if obj.id not in self._verified_links:
230 1
                    self._verified_links.append(obj.id)
231 1
                    self._restore_link(obj.id)
232
        except RestoreError as exc:
233
            log.debug(exc)
234
235
    # Switch related methods
236 1
    @rest('v3/switches')
237
    def get_switches(self):
238
        """Return a json with all the switches in the topology."""
239
        return jsonify(self._get_switches_dict())
240
241 1
    @rest('v3/switches/<dpid>/enable', methods=['POST'])
242
    def enable_switch(self, dpid):
243
        """Administratively enable a switch in the topology."""
244 1
        try:
245 1
            self.controller.switches[dpid].enable()
246 1
        except KeyError:
247 1
            return jsonify("Switch not found"), 404
248
249 1
        log.info(f"Storing administrative state from switch {dpid}"
250
                 " to enabled.")
251 1
        self.save_status_on_storehouse()
252 1
        return jsonify("Operation successful"), 201
253
254 1
    @rest('v3/switches/<dpid>/disable', methods=['POST'])
255
    def disable_switch(self, dpid):
256
        """Administratively disable a switch in the topology."""
257 1
        try:
258 1
            self.controller.switches[dpid].disable()
259 1
        except KeyError:
260 1
            return jsonify("Switch not found"), 404
261
262 1
        log.info(f"Storing administrative state from switch {dpid}"
263
                 " to disabled.")
264 1
        self.save_status_on_storehouse()
265 1
        return jsonify("Operation successful"), 201
266
267 1
    @rest('v3/switches/<dpid>/metadata')
268
    def get_switch_metadata(self, dpid):
269
        """Get metadata from a switch."""
270 1
        try:
271 1
            return jsonify({"metadata":
272
                            self.controller.switches[dpid].metadata}), 200
273 1
        except KeyError:
274 1
            return jsonify("Switch not found"), 404
275
276 1
    @rest('v3/switches/<dpid>/metadata', methods=['POST'])
277
    def add_switch_metadata(self, dpid):
278
        """Add metadata to a switch."""
279 1
        metadata = request.get_json()
280 1
        try:
281 1
            switch = self.controller.switches[dpid]
282 1
        except KeyError:
283 1
            return jsonify("Switch not found"), 404
284
285 1
        switch.extend_metadata(metadata)
286 1
        self.notify_metadata_changes(switch, 'added')
287 1
        return jsonify("Operation successful"), 201
288
289 1
    @rest('v3/switches/<dpid>/metadata/<key>', methods=['DELETE'])
290
    def delete_switch_metadata(self, dpid, key):
291
        """Delete metadata from a switch."""
292 1
        try:
293 1
            switch = self.controller.switches[dpid]
294 1
        except KeyError:
295 1
            return jsonify("Switch not found"), 404
296
297 1
        switch.remove_metadata(key)
298 1
        self.notify_metadata_changes(switch, 'removed')
299 1
        return jsonify("Operation successful"), 200
300
301
    # Interface related methods
302 1
    @rest('v3/interfaces')
303
    def get_interfaces(self):
304
        """Return a json with all the interfaces in the topology."""
305
        interfaces = {}
306
        switches = self._get_switches_dict()
307
        for switch in switches['switches'].values():
308
            for interface_id, interface in switch['interfaces'].items():
309
                interfaces[interface_id] = interface
310
311
        return jsonify({'interfaces': interfaces})
312
313 1 View Code Duplication
    @rest('v3/interfaces/switch/<dpid>/enable', methods=['POST'])
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
314 1
    @rest('v3/interfaces/<interface_enable_id>/enable', methods=['POST'])
315 1
    def enable_interface(self, interface_enable_id=None, dpid=None):
316
        """Administratively enable interfaces in the topology."""
317 1
        error_list = []  # List of interfaces that were not activated.
318 1
        msg_error = "Some interfaces couldn't be found and activated: "
319 1
        if dpid is None:
320 1
            dpid = ":".join(interface_enable_id.split(":")[:-1])
321 1
        try:
322 1
            switch = self.controller.switches[dpid]
323 1
        except KeyError as exc:
324 1
            return jsonify(f"Switch not found: {exc}"), 404
325
326 1
        if interface_enable_id:
327 1
            interface_number = int(interface_enable_id.split(":")[-1])
328
329 1
            try:
330 1
                switch.interfaces[interface_number].enable()
331 1
            except KeyError as exc:
332 1
                error_list.append(f"Switch {dpid} Interface {exc}")
333
        else:
334 1
            for interface in switch.interfaces.values():
335 1
                interface.enable()
336 1
        if not error_list:
337 1
            log.info(f"Storing administrative state for enabled interfaces.")
338 1
            self.save_status_on_storehouse()
339 1
            return jsonify("Operation successful"), 200
340 1
        return jsonify({msg_error:
341
                        error_list}), 409
342
343 1 View Code Duplication
    @rest('v3/interfaces/switch/<dpid>/disable', methods=['POST'])
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
344 1
    @rest('v3/interfaces/<interface_disable_id>/disable', methods=['POST'])
345 1
    def disable_interface(self, interface_disable_id=None, dpid=None):
346
        """Administratively disable interfaces in the topology."""
347 1
        error_list = []  # List of interfaces that were not deactivated.
348 1
        msg_error = "Some interfaces couldn't be found and deactivated: "
349 1
        if dpid is None:
350 1
            dpid = ":".join(interface_disable_id.split(":")[:-1])
351 1
        try:
352 1
            switch = self.controller.switches[dpid]
353 1
        except KeyError as exc:
354 1
            return jsonify(f"Switch not found: {exc}"), 404
355
356 1
        if interface_disable_id:
357 1
            interface_number = int(interface_disable_id.split(":")[-1])
358
359 1
            try:
360 1
                switch.interfaces[interface_number].disable()
361 1
            except KeyError as exc:
362 1
                error_list.append(f"Switch {dpid} Interface {exc}")
363
        else:
364 1
            for interface in switch.interfaces.values():
365 1
                interface.disable()
366 1
        if not error_list:
367 1
            log.info(f"Storing administrative state for disabled interfaces.")
368 1
            self.save_status_on_storehouse()
369 1
            return jsonify("Operation successful"), 200
370 1
        return jsonify({msg_error:
371
                        error_list}), 409
372
373 1
    @rest('v3/interfaces/<interface_id>/metadata')
374
    def get_interface_metadata(self, interface_id):
375
        """Get metadata from an interface."""
376 1
        switch_id = ":".join(interface_id.split(":")[:-1])
377 1
        interface_number = int(interface_id.split(":")[-1])
378 1
        try:
379 1
            switch = self.controller.switches[switch_id]
380 1
        except KeyError:
381 1
            return jsonify("Switch not found"), 404
382
383 1
        try:
384 1
            interface = switch.interfaces[interface_number]
385 1
        except KeyError:
386 1
            return jsonify("Interface not found"), 404
387
388 1
        return jsonify({"metadata": interface.metadata}), 200
389
390 1
    @rest('v3/interfaces/<interface_id>/metadata', methods=['POST'])
391
    def add_interface_metadata(self, interface_id):
392
        """Add metadata to an interface."""
393 1
        metadata = request.get_json()
394
395 1
        switch_id = ":".join(interface_id.split(":")[:-1])
396 1
        interface_number = int(interface_id.split(":")[-1])
397 1
        try:
398 1
            switch = self.controller.switches[switch_id]
399 1
        except KeyError:
400 1
            return jsonify("Switch not found"), 404
401
402 1
        try:
403 1
            interface = switch.interfaces[interface_number]
404 1
        except KeyError:
405 1
            return jsonify("Interface not found"), 404
406
407 1
        interface.extend_metadata(metadata)
408 1
        self.notify_metadata_changes(interface, 'added')
409 1
        return jsonify("Operation successful"), 201
410
411 1
    @rest('v3/interfaces/<interface_id>/metadata/<key>', methods=['DELETE'])
412
    def delete_interface_metadata(self, interface_id, key):
413
        """Delete metadata from an interface."""
414 1
        switch_id = ":".join(interface_id.split(":")[:-1])
415 1
        interface_number = int(interface_id.split(":")[-1])
416
417 1
        try:
418 1
            switch = self.controller.switches[switch_id]
419 1
        except KeyError:
420 1
            return jsonify("Switch not found"), 404
421
422 1
        try:
423 1
            interface = switch.interfaces[interface_number]
424 1
        except KeyError:
425 1
            return jsonify("Interface not found"), 404
426
427 1
        if interface.remove_metadata(key) is False:
428 1
            return jsonify("Metadata not found"), 404
429
430 1
        self.notify_metadata_changes(interface, 'removed')
431 1
        return jsonify("Operation successful"), 200
432
433
    # Link related methods
434 1
    @rest('v3/links')
435
    def get_links(self):
436
        """Return a json with all the links in the topology.
437
438
        Links are connections between interfaces.
439
        """
440
        return jsonify(self._get_links_dict()), 200
441
442 1
    @rest('v3/links/<link_id>/enable', methods=['POST'])
443
    def enable_link(self, link_id):
444
        """Administratively enable a link in the topology."""
445 1
        try:
446 1
            self.links[link_id].enable()
447 1
        except KeyError:
448 1
            return jsonify("Link not found"), 404
449 1
        self.save_status_on_storehouse()
450 1
        return jsonify("Operation successful"), 201
451
452 1
    @rest('v3/links/<link_id>/disable', methods=['POST'])
453
    def disable_link(self, link_id):
454
        """Administratively disable a link in the topology."""
455 1
        try:
456 1
            self.links[link_id].disable()
457 1
        except KeyError:
458 1
            return jsonify("Link not found"), 404
459 1
        self.save_status_on_storehouse()
460 1
        return jsonify("Operation successful"), 201
461
462 1
    @rest('v3/links/<link_id>/metadata')
463
    def get_link_metadata(self, link_id):
464
        """Get metadata from a link."""
465 1
        try:
466 1
            return jsonify({"metadata": self.links[link_id].metadata}), 200
467 1
        except KeyError:
468 1
            return jsonify("Link not found"), 404
469
470 1
    @rest('v3/links/<link_id>/metadata', methods=['POST'])
471
    def add_link_metadata(self, link_id):
472
        """Add metadata to a link."""
473 1
        metadata = request.get_json()
474 1
        try:
475 1
            link = self.links[link_id]
476 1
        except KeyError:
477 1
            return jsonify("Link not found"), 404
478
479 1
        link.extend_metadata(metadata)
480 1
        self.notify_metadata_changes(link, 'added')
481 1
        return jsonify("Operation successful"), 201
482
483 1
    @rest('v3/links/<link_id>/metadata/<key>', methods=['DELETE'])
484
    def delete_link_metadata(self, link_id, key):
485
        """Delete metadata from a link."""
486 1
        try:
487 1
            link = self.links[link_id]
488 1
        except KeyError:
489 1
            return jsonify("Link not found"), 404
490
491 1
        if link.remove_metadata(key) is False:
492 1
            return jsonify("Metadata not found"), 404
493
494 1
        self.notify_metadata_changes(link, 'removed')
495 1
        return jsonify("Operation successful"), 200
496
497 1
    @listen_to('.*.switch.(new|reconnected)')
498
    def handle_new_switch(self, event):
499
        """Create a new Device on the Topology.
500
501
        Handle the event of a new created switch and update the topology with
502
        this new device.
503
        """
504 1
        switch = event.content['switch']
505 1
        switch.activate()
506 1
        log.debug('Switch %s added to the Topology.', switch.id)
507 1
        self.notify_topology_update()
508 1
        self.update_instance_metadata(switch)
509 1
        self.restore_network_status(switch)
510
511 1
    @listen_to('.*.connection.lost')
512
    def handle_connection_lost(self, event):
513
        """Remove a Device from the topology.
514
515
        Remove the disconnected Device and every link that has one of its
516
        interfaces.
517
        """
518 1
        switch = event.content['source'].switch
519 1
        if switch:
520 1
            switch.deactivate()
521 1
            log.debug('Switch %s removed from the Topology.', switch.id)
522 1
            self.notify_topology_update()
523
524 1
    def handle_interface_up(self, event):
525
        """Update the topology based on a Port Modify event.
526
527
        The event notifies that an interface was changed to 'up'.
528
        """
529 1
        interface = event.content['interface']
530 1
        interface.activate()
531 1
        self.notify_topology_update()
532 1
        self.update_instance_metadata(interface)
533
534 1
    @listen_to('.*.switch.interface.created')
535
    def handle_interface_created(self, event):
536
        """Update the topology based on a Port Create event."""
537 1
        self.handle_interface_up(event)
538
539 1
    def handle_interface_down(self, event):
540
        """Update the topology based on a Port Modify event.
541
542
        The event notifies that an interface was changed to 'down'.
543
        """
544 1
        interface = event.content['interface']
545 1
        interface.deactivate()
546 1
        self.handle_interface_link_down(event)
547 1
        self.notify_topology_update()
548
549 1
    @listen_to('.*.switch.interface.deleted')
550
    def handle_interface_deleted(self, event):
551
        """Update the topology based on a Port Delete event."""
552 1
        self.handle_interface_down(event)
553
554 1
    @listen_to('.*.switch.interface.link_up')
555
    def handle_interface_link_up(self, event):
556
        """Update the topology based on a Port Modify event.
557
558
        The event notifies that an interface's link was changed to 'up'.
559
        """
560 1
        interface = event.content['interface']
561 1
        self.handle_link_up(interface)
562
563 1
    @listen_to('kytos/maintenance.end_switch')
564
    def handle_switch_maintenance_end(self, event):
565
        """Handle the end of the maintenance of a switch."""
566 1
        switches = event.content['switches']
567 1
        for switch in switches:
568 1
            switch.enable()
569 1
            switch.activate()
570 1
            for interface in switch.interfaces.values():
571 1
                interface.enable()
572 1
                self.handle_link_up(interface)
573
574 1
    def handle_link_up(self, interface):
575
        """Notify a link is up."""
576 1
        link = self._get_link_from_interface(interface)
577 1
        if not link:
578
            return
579 1
        if link.endpoint_a == interface:
580 1
            other_interface = link.endpoint_b
581
        else:
582
            other_interface = link.endpoint_a
583 1
        interface.activate()
584 1
        if other_interface.is_active() is False:
585
            return
586 1
        if link.is_active() is False:
587 1
            link.update_metadata('last_status_change', time.time())
588 1
            link.activate()
589
590
            # As each run of this method uses a different thread,
591
            # there is no risk this sleep will lock the NApp.
592 1
            time.sleep(self.link_up_timer)
593
594 1
            last_status_change = link.get_metadata('last_status_change')
595 1
            now = time.time()
596 1
            if link.is_active() and \
597
                    now - last_status_change >= self.link_up_timer:
598 1
                self.notify_topology_update()
599 1
                self.update_instance_metadata(link)
600 1
                self.notify_link_status_change(link)
601
602 1
    @listen_to('.*.switch.interface.link_down')
603
    def handle_interface_link_down(self, event):
604
        """Update the topology based on a Port Modify event.
605
606
        The event notifies that an interface's link was changed to 'down'.
607
        """
608 1
        interface = event.content['interface']
609 1
        self.handle_link_down(interface)
610
611 1
    @listen_to('kytos/maintenance.start_switch')
612
    def handle_switch_maintenance_start(self, event):
613
        """Handle the start of the maintenance of a switch."""
614 1
        switches = event.content['switches']
615 1
        for switch in switches:
616 1
            switch.disable()
617 1
            switch.deactivate()
618 1
            for interface in switch.interfaces.values():
619 1
                interface.disable()
620 1
                if interface.is_active():
621 1
                    self.handle_link_down(interface)
622
623 1
    def handle_link_down(self, interface):
624
        """Notify a link is down."""
625 1
        link = self._get_link_from_interface(interface)
626 1
        if link and link.is_active():
627 1
            link.deactivate()
628 1
            link.update_metadata('last_status_change', time.time())
629 1
            self.notify_topology_update()
630 1
            self.notify_link_status_change(link)
631
632 1
    @listen_to('.*.interface.is.nni')
633
    def add_links(self, event):
634
        """Update the topology with links related to the NNI interfaces."""
635 1
        interface_a = event.content['interface_a']
636 1
        interface_b = event.content['interface_b']
637
638 1
        try:
639 1
            link = self._get_link_or_create(interface_a, interface_b)
640
        except KytosLinkCreationError as err:
641
            log.error(f'Error creating link: {err}.')
642
            return
643
644 1
        interface_a.update_link(link)
645 1
        interface_b.update_link(link)
646
647 1
        interface_a.nni = True
648 1
        interface_b.nni = True
649
650 1
        self.notify_topology_update()
651 1
        self.restore_network_status(link)
652
653
    # def add_host(self, event):
654
    #    """Update the topology with a new Host."""
655
656
    #    interface = event.content['port']
657
    #    mac = event.content['reachable_mac']
658
659
    #    host = Host(mac)
660
    #    link = self.topology.get_link(interface.id)
661
    #    if link is not None:
662
    #        return
663
664
    #    self.topology.add_link(interface.id, host.id)
665
    #    self.topology.add_device(host)
666
667
    #    if settings.DISPLAY_FULL_DUPLEX_LINKS:
668
    #        self.topology.add_link(host.id, interface.id)
669
670
    # pylint: disable=unused-argument
671 1
    @listen_to('.*.network_status.updated')
672 1
    def save_status_on_storehouse(self, event=None):
673
        """Save the network administrative status using storehouse."""
674 1
        status = self._get_switches_dict()
675 1
        status['id'] = 'network_status'
676 1
        if event:
677
            content = event.content
678
            log.info(f"Storing the administrative state of the"
679
                     f" {content['attribute']} attribute to"
680
                     f" {content['state']} in the interfaces"
681
                     f" {content['interface_ids']}")
682 1
        status.update(self._get_links_dict())
683 1
        self.storehouse.save_status(status)
684
685 1
    def notify_topology_update(self):
686
        """Send an event to notify about updates on the topology."""
687 1
        name = 'kytos/topology.updated'
688 1
        event = KytosEvent(name=name, content={'topology':
689
                                               self._get_topology()})
690 1
        self.controller.buffers.app.put(event)
691
692 1
    def notify_link_status_change(self, link):
693
        """Send an event to notify about a status change on a link."""
694 1
        name = 'kytos/topology.'
695 1
        if link.is_active():
696 1
            status = 'link_up'
697
        else:
698
            status = 'link_down'
699 1
        event = KytosEvent(name=name+status, content={'link': link})
700 1
        self.controller.buffers.app.put(event)
701
702 1
    def notify_metadata_changes(self, obj, action):
703
        """Send an event to notify about metadata changes."""
704 1
        if isinstance(obj, Switch):
705 1
            entity = 'switch'
706 1
            entities = 'switches'
707 1
        elif isinstance(obj, Interface):
708 1
            entity = 'interface'
709 1
            entities = 'interfaces'
710
        elif isinstance(obj, Link):
711
            entity = 'link'
712
            entities = 'links'
713
714 1
        name = f'kytos/topology.{entities}.metadata.{action}'
715 1
        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...
716
                                               'metadata': obj.metadata})
717 1
        self.controller.buffers.app.put(event)
718 1
        log.debug(f'Metadata from {obj.id} was {action}.')
719
720 1
    @listen_to('.*.switch.port.created')
721
    def notify_port_created(self, original_event):
722
        """Notify when a port is created."""
723 1
        name = 'kytos/topology.port.created'
724 1
        event = KytosEvent(name=name, content=original_event.content)
725 1
        self.controller.buffers.app.put(event)
726
727 1
    @listen_to('kytos/topology.*.metadata.*')
728
    def save_metadata_on_store(self, event):
729
        """Send to storehouse the data updated."""
730 1
        name = 'kytos.storehouse.update'
731 1
        if 'switch' in event.content:
732 1
            store = self.store_items.get('switches')
733 1
            obj = event.content.get('switch')
734 1
            namespace = 'kytos.topology.switches.metadata'
735 1
        elif 'interface' in event.content:
736 1
            store = self.store_items.get('interfaces')
737 1
            obj = event.content.get('interface')
738 1
            namespace = 'kytos.topology.interfaces.metadata'
739 1
        elif 'link' in event.content:
740 1
            store = self.store_items.get('links')
741 1
            obj = event.content.get('link')
742 1
            namespace = 'kytos.topology.links.metadata'
743
744 1
        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...
745 1
        content = {'namespace': namespace,
0 ignored issues
show
introduced by
The variable namespace does not seem to be defined for all execution paths.
Loading history...
746
                   'box_id': store.box_id,
747
                   'data': store.data,
748
                   'callback': self.update_instance}
749
750 1
        event = KytosEvent(name=name, content=content)
751 1
        self.controller.buffers.app.put(event)
752
753 1
    @staticmethod
754
    def update_instance(event, _data, error):
755
        """Display in Kytos console if the data was updated."""
756
        entities = event.content.get('namespace', '').split('.')[-2]
757
        if error:
758
            log.error(f'Error trying to update storehouse {entities}.')
759
        else:
760
            log.debug(f'Storehouse update to entities: {entities}.')
761
762 1
    def verify_storehouse(self, entities):
763
        """Request a list of box saved by specific entity."""
764 1
        name = 'kytos.storehouse.list'
765 1
        content = {'namespace': f'kytos.topology.{entities}.metadata',
766
                   'callback': self.request_retrieve_entities}
767 1
        event = KytosEvent(name=name, content=content)
768 1
        self.controller.buffers.app.put(event)
769 1
        log.info(f'verify data in storehouse for {entities}.')
770
771 1
    def request_retrieve_entities(self, event, data, _error):
772
        """Create a box or retrieve an existent box from storehouse."""
773 1
        msg = ''
774 1
        content = {'namespace': event.content.get('namespace'),
775
                   'callback': self.load_from_store,
776
                   'data': {}}
777
778 1
        if not data:
779 1
            name = 'kytos.storehouse.create'
780 1
            msg = 'Create new box in storehouse'
781
        else:
782 1
            name = 'kytos.storehouse.retrieve'
783 1
            content['box_id'] = data[0]
784 1
            msg = 'Retrieve data from storehouse.'
785
786 1
        event = KytosEvent(name=name, content=content)
787 1
        self.controller.buffers.app.put(event)
788 1
        log.debug(msg)
789
790 1
    def load_from_store(self, event, box, error):
791
        """Save the data retrived from storehouse."""
792
        entities = event.content.get('namespace', '').split('.')[-2]
793
        if error:
794
            log.error('Error while get a box from storehouse.')
795
        else:
796
            self.store_items[entities] = box
797
            log.debug('Data updated')
798
799 1
    def update_instance_metadata(self, obj):
800
        """Update object instance with saved metadata."""
801 1
        metadata = None
802 1
        if isinstance(obj, Interface):
803 1
            all_metadata = self.store_items.get('interfaces', None)
804 1
            if all_metadata:
805
                metadata = all_metadata.data.get(obj.id)
806 1
        elif isinstance(obj, Switch):
807
            all_metadata = self.store_items.get('switches', None)
808
            if all_metadata:
809
                metadata = all_metadata.data.get(obj.id)
810 1
        elif isinstance(obj, Link):
811 1
            all_metadata = self.store_items.get('links', None)
812 1
            if all_metadata:
813
                metadata = all_metadata.data.get(obj.id)
814
815 1
        if metadata:
816
            obj.extend_metadata(metadata)
817
            log.debug(f'Metadata to {obj.id} was updated')
818
819 1
    @listen_to('kytos/maintenance.start_link')
820
    def handle_link_maintenance_start(self, event):
821
        """Deals with the start of links maintenance."""
822 1
        notify_links = []
823 1
        maintenance_links = event.content['links']
824 1
        for maintenance_link in maintenance_links:
825 1
            try:
826 1
                link = self.links[maintenance_link.id]
827 1
            except KeyError:
828 1
                continue
829 1
            notify_links.append(link)
830 1
        for link in notify_links:
831 1
            link.disable()
832 1
            link.deactivate()
833 1
            link.endpoint_a.deactivate()
834 1
            link.endpoint_b.deactivate()
835 1
            link.endpoint_a.disable()
836 1
            link.endpoint_b.disable()
837 1
            self.notify_link_status_change(link)
838
839 1
    @listen_to('kytos/maintenance.end_link')
840
    def handle_link_maintenance_end(self, event):
841
        """Deals with the end of links maintenance."""
842 1
        notify_links = []
843 1
        maintenance_links = event.content['links']
844 1
        for maintenance_link in maintenance_links:
845 1
            try:
846 1
                link = self.links[maintenance_link.id]
847 1
            except KeyError:
848 1
                continue
849 1
            notify_links.append(link)
850 1
        for link in notify_links:
851 1
            link.enable()
852 1
            link.activate()
853 1
            link.endpoint_a.activate()
854 1
            link.endpoint_b.activate()
855 1
            link.endpoint_a.enable()
856 1
            link.endpoint_b.enable()
857
            self.notify_link_status_change(link)
858