Passed
Pull Request — master (#127)
by Humberto
02:16
created

build.main   F

Complexity

Total Complexity 149

Size/Duplication

Total Lines 824
Duplicated Lines 7.04 %

Test Coverage

Coverage 88.64%

Importance

Changes 0
Metric Value
eloc 581
dl 58
loc 824
ccs 468
cts 528
cp 0.8864
rs 2
c 0
b 0
f 0
wmc 149

58 Methods

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