Passed
Pull Request — master (#123)
by
unknown
02:19
created

build.main   F

Complexity

Total Complexity 149

Size/Duplication

Total Lines 810
Duplicated Lines 7.16 %

Test Coverage

Coverage 86.92%

Importance

Changes 0
Metric Value
eloc 570
dl 58
loc 810
ccs 452
cts 520
cp 0.8692
rs 2
c 0
b 0
f 0
wmc 149

58 Methods

Rating   Name   Duplication   Size   Complexity  
A Main.setup() 0 15 1
A Main._get_switches_dict() 0 12 3
A Main.shutdown() 0 3 1
A Main.execute() 0 3 1
A Main._get_links_dict() 0 4 1
A Main._get_link_or_create() 0 9 3
A Main._get_link_from_interface() 0 6 3
A Main._get_topology_dict() 0 4 1
A Main._get_topology() 0 3 1
A Main.notify_port_created() 0 6 1
A Main.disable_switch() 0 11 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 11 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._restore_links() 0 18 4
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.notify_topology_update() 0 6 1
A Main.enable_link() 0 9 2
A Main.handle_link_down() 0 8 3
A Main.handle_new_switch() 0 13 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
B Main._restore_switches() 0 38 7
A Main.get_links() 0 7 1
B Main.disable_interface() 29 29 7
A Main.handle_switch_maintenance_start() 0 11 4
A Main._load_network_status() 0 21 4
A Main.get_topology() 0 7 1
A Main.save_metadata_on_store() 0 25 4
A Main.handle_interface_link_down() 0 8 1
A Main.handle_link_maintenance_start() 0 19 4
A Main.add_links() 0 15 1
A Main.handle_interface_up() 0 9 1
A Main.handle_interface_deleted() 0 4 1
A Main.restore_network_status() 0 9 4
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_link_metadata() 0 7 2

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