Passed
Pull Request — master (#130)
by
unknown
02:09
created

build.main   F

Complexity

Total Complexity 148

Size/Duplication

Total Lines 820
Duplicated Lines 7.07 %

Test Coverage

Coverage 90.1%

Importance

Changes 0
Metric Value
eloc 578
dl 58
loc 820
ccs 473
cts 525
cp 0.901
rs 2
c 0
b 0
f 0
wmc 148

58 Methods

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