Passed
Pull Request — master (#127)
by Gleyberson
02:56
created

build.main   F

Complexity

Total Complexity 150

Size/Duplication

Total Lines 826
Duplicated Lines 7.02 %

Test Coverage

Coverage 88.47%

Importance

Changes 0
Metric Value
eloc 582
dl 58
loc 826
ccs 468
cts 529
cp 0.8847
rs 2
c 0
b 0
f 0
wmc 150

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.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
A Main._get_switches_dict() 0 12 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.handle_new_switch() 0 12 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.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._get_links_dict() 0 4 1
A Main.handle_link_maintenance_start() 0 19 4
A Main.add_links() 0 18 2
A Main.handle_interface_up() 0 9 1
A Main.handle_interface_deleted() 0 4 1
A Main._get_link_or_create() 0 13 4
A Main.restore_network_status() 0 10 2
A Main._get_link_from_interface() 0 6 3
A Main.add_link_metadata() 0 12 2
A Main.notify_metadata_changes() 0 17 4
A Main.delete_switch_metadata() 0 11 2
A Main._get_topology_dict() 0 4 1
A Main.get_link_metadata() 0 7 2
A Main._get_topology() 0 3 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

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

Common duplication problems, and corresponding solutions are:

Complexity

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

Complex classes like build.main often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

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