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

build.main   F

Complexity

Total Complexity 151

Size/Duplication

Total Lines 823
Duplicated Lines 7.05 %

Test Coverage

Coverage 88.97%

Importance

Changes 0
Metric Value
eloc 576
dl 58
loc 823
ccs 468
cts 526
cp 0.8897
rs 2
c 0
b 0
f 0
wmc 151

58 Methods

Rating   Name   Duplication   Size   Complexity  
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.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.handle_link_down() 0 8 3
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.handle_interface_link_down() 0 8 1
A Main.handle_interface_up() 0 9 1
A Main.handle_interface_deleted() 0 4 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
A Main._get_switches_dict() 0 12 3
A Main._restore_links() 0 20 4
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.shutdown() 0 3 1
A Main.notify_topology_update() 0 6 1
A Main.setup() 0 16 1
A Main.handle_new_switch() 0 13 1
A Main.execute() 0 3 1
A Main.save_status_on_storehouse() 0 13 2
A Main.update_instance() 0 8 2
B Main._restore_switches() 0 46 8
A Main._load_network_status() 0 21 4
A Main.get_topology() 0 7 1
A Main.save_metadata_on_store() 0 25 4
A Main._get_links_dict() 0 4 1
A Main.handle_link_maintenance_start() 0 19 4
A Main.add_links() 0 15 1
A Main._get_link_or_create() 0 9 3
A Main.restore_network_status() 0 11 5
A Main._get_link_from_interface() 0 6 3
A Main.notify_metadata_changes() 0 17 4
A Main._get_topology_dict() 0 4 1
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.links_verified = []
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
        """Execute once when the napp is running."""
46
        self._load_network_status()
47
48 1
    def shutdown(self):
49
        """Do nothing."""
50
        log.info('NApp kytos/topology shutting down.')
51
52 1
    def _get_link_or_create(self, endpoint_a, endpoint_b):
53 1
        new_link = Link(endpoint_a, endpoint_b)
54
55 1
        for link in self.links.values():
56
            if new_link == link:
57
                return link
58
59 1
        self.links[new_link.id] = new_link
60 1
        return new_link
61
62 1
    def _get_switches_dict(self):
63
        """Return a dictionary with the known switches."""
64 1
        switches = {'switches': {}}
65 1
        for idx, switch in enumerate(self.controller.switches.values()):
66 1
            switch_data = switch.as_dict()
67 1
            if not all(key in switch_data['metadata']
68
                       for key in ('lat', 'lng')):
69
                # Switches are initialized somewhere in the ocean
70
                switch_data['metadata']['lat'] = str(0.0)
71
                switch_data['metadata']['lng'] = str(-30.0+idx*10.0)
72 1
            switches['switches'][switch.id] = switch_data
73 1
        return switches
74
75 1
    def _get_links_dict(self):
76
        """Return a dictionary with the known links."""
77 1
        return {'links': {l.id: l.as_dict() for l in
78
                          self.links.values()}}
79
80 1
    def _get_topology_dict(self):
81
        """Return a dictionary with the known topology."""
82 1
        return {'topology': {**self._get_switches_dict(),
83
                             **self._get_links_dict()}}
84
85 1
    def _get_topology(self):
86
        """Return an object representing the topology."""
87 1
        return Topology(self.controller.switches, self.links)
88
89 1
    def _get_link_from_interface(self, interface):
90
        """Return the link of the interface, or None if it does not exist."""
91 1
        for link in self.links.values():
92 1
            if interface in (link.endpoint_a, link.endpoint_b):
93 1
                return link
94 1
        return None
95
96 1
    def _restore_links(self, link_id):
97
        """Restore administrative state the link saved in storehouse."""
98 1
        try:
99 1
            state = self.links_state[link_id]
100 1
        except KeyError:
101 1
            error = ('The stored topology is different from the current'
102
                     f' topology. The link {link_id} has no stored '
103
                     'administrative state to restore.')
104 1
            raise KeyError(error)
105
106 1
        try:
107 1
            if state['enabled']:
108 1
                self.links[link_id].enable()
109
            else:
110 1
                self.links[link_id].disable()
111 1
        except KeyError:
112 1
            error = ('Error restoring link status.'
113
                     f'The link {link_id} does not exist.')
114 1
            raise KeyError(error)
115 1
        log.info(f'The state of link {link_id} has been restored.')
116
117 1
    def _restore_switches(self, switch_id):
118
        """Restore administrative state the switches saved in storehouse."""
119
        # restore switches
120 1
        try:
121 1
            state = self.switches_state[switch_id]
122 1
        except KeyError:
123 1
            error = ('The stored topology is different from the current'
124
                     f' topology. The switch {switch_id} has no stored'
125
                     ' administrative state to restore.')
126 1
            raise KeyError(error)
127
128 1
        try:
129 1
            if state:
130 1
                self.controller.switches[switch_id].enable()
131
            else:
132 1
                self.controller.switches[switch_id].disable()
133 1
        except KeyError:
134 1
            error = ('Error while restoring switches status. The '
135
                     f'{switch_id} does not exist.')
136 1
            raise KeyError(error)
137
138
        # restore interfaces
139 1
        for interface_id in self.controller.switches[switch_id].interfaces:
140 1
            iface_id = ":".join([switch_id, str(interface_id)])
141
            # restore only the administrative state of saved interfaces
142 1
            if iface_id not in self.interfaces_state:
143
                error = ("The stored topology is different from the current "
144
                         f"topology. The interface {iface_id} hasn't been "
145
                         "stored.")
146
                log.info(error)
147
                continue
148 1
            state = self.interfaces_state[iface_id]
149 1
            iface_number = int(interface_id)
150 1
            iface_status, lldp_status = state
151 1
            try:
152 1
                switch = self.controller.switches[switch_id]
153 1
                if iface_status:
154 1
                    switch.interfaces[iface_number].enable()
155
                else:
156 1
                    switch.interfaces[iface_number].disable()
157 1
                switch.interfaces[iface_number].lldp = lldp_status
158
            except KeyError:
159
                error = ('Error while restoring interface status. The '
160
                         f'interface {iface_id} does not exist.')
161
                raise KeyError(error)
162 1
        log.info(f'The state of switch {switch_id} has been restored.')
163
164
    # pylint: disable=attribute-defined-outside-init
165 1
    def _load_network_status(self):
166
        """Load network status saved in storehouse."""
167 1
        status = self.storehouse.get_data()
168 1
        if status:
169 1
            switches = status['network_status']['switches']
170 1
            self.links_state = status['network_status']['links']
171
172 1
            for switch_id, switch_att in switches.items():
173
                # get swicthes status
174 1
                self.switches_state[switch_id] = switch_att['enabled']
175 1
                iface = switch_att['interfaces']
176
                # get interface status
177 1
                for iface_id, iface_att in iface.items():
178 1
                    enabled_value = iface_att['enabled']
179 1
                    lldp_value = iface_att['lldp']
180 1
                    self.interfaces_state[iface_id] = (enabled_value,
181
                                                       lldp_value)
182
183
        else:
184
            error = 'There is no status saved to restore.'
185
            log.info(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
    def restore_network_status(self, obj):
196
        """Restore the network administrative status saved in storehouse."""
197 1
        try:
198 1
            if isinstance(obj, Switch):
199 1
                self._restore_switches(obj.id)
200 1
            elif isinstance(obj, Link):
201 1
                if obj.id not in self.links_verified:
202 1
                    self.links_verified.append(obj.id)
203 1
                    self._restore_links(obj.id)
204
        except (KeyError, FileNotFoundError) as exc:
205
            log.info(exc)
206
207
    # Switch related methods
208 1
    @rest('v3/switches')
209
    def get_switches(self):
210
        """Return a json with all the switches in the topology."""
211
        return jsonify(self._get_switches_dict())
212
213 1
    @rest('v3/switches/<dpid>/enable', methods=['POST'])
214
    def enable_switch(self, dpid):
215
        """Administratively enable a switch in the topology."""
216 1
        try:
217 1
            self.controller.switches[dpid].enable()
218 1
            log.info(f"Storing administrative state from switch {dpid}"
219
                     " to enabled.")
220 1
            self.save_status_on_storehouse()
221 1
            return jsonify("Operation successful"), 201
222 1
        except KeyError:
223 1
            return jsonify("Switch not found"), 404
224
225 1
    @rest('v3/switches/<dpid>/disable', methods=['POST'])
226
    def disable_switch(self, dpid):
227
        """Administratively disable a switch in the topology."""
228 1
        try:
229 1
            self.controller.switches[dpid].disable()
230 1
            log.info(f"Storing administrative state from switch {dpid}"
231
                     " to disabled.")
232 1
            self.save_status_on_storehouse()
233 1
            return jsonify("Operation successful"), 201
234 1
        except KeyError:
235 1
            return jsonify("Switch not found"), 404
236
237 1
    @rest('v3/switches/<dpid>/metadata')
238
    def get_switch_metadata(self, dpid):
239
        """Get metadata from a switch."""
240 1
        try:
241 1
            return jsonify({"metadata":
242
                            self.controller.switches[dpid].metadata}), 200
243 1
        except KeyError:
244 1
            return jsonify("Switch not found"), 404
245
246 1
    @rest('v3/switches/<dpid>/metadata', methods=['POST'])
247
    def add_switch_metadata(self, dpid):
248
        """Add metadata to a switch."""
249 1
        metadata = request.get_json()
250 1
        try:
251 1
            switch = self.controller.switches[dpid]
252 1
        except KeyError:
253 1
            return jsonify("Switch not found"), 404
254
255 1
        switch.extend_metadata(metadata)
256 1
        self.notify_metadata_changes(switch, 'added')
257 1
        return jsonify("Operation successful"), 201
258
259 1
    @rest('v3/switches/<dpid>/metadata/<key>', methods=['DELETE'])
260
    def delete_switch_metadata(self, dpid, key):
261
        """Delete metadata from a switch."""
262 1
        try:
263 1
            switch = self.controller.switches[dpid]
264 1
        except KeyError:
265 1
            return jsonify("Switch not found"), 404
266
267 1
        switch.remove_metadata(key)
268 1
        self.notify_metadata_changes(switch, 'removed')
269 1
        return jsonify("Operation successful"), 200
270
271
    # Interface related methods
272 1
    @rest('v3/interfaces')
273
    def get_interfaces(self):
274
        """Return a json with all the interfaces in the topology."""
275
        interfaces = {}
276
        switches = self._get_switches_dict()
277
        for switch in switches['switches'].values():
278
            for interface_id, interface in switch['interfaces'].items():
279
                interfaces[interface_id] = interface
280
281
        return jsonify({'interfaces': interfaces})
282
283 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...
284 1
    @rest('v3/interfaces/<interface_enable_id>/enable', methods=['POST'])
285 1
    def enable_interface(self, interface_enable_id=None, dpid=None):
286
        """Administratively enable interfaces in the topology."""
287 1
        error_list = []  # List of interfaces that were not activated.
288 1
        msg_error = "Some interfaces couldn't be found and activated: "
289 1
        if dpid is None:
290 1
            dpid = ":".join(interface_enable_id.split(":")[:-1])
291 1
        try:
292 1
            switch = self.controller.switches[dpid]
293 1
        except KeyError as exc:
294 1
            return jsonify(f"Switch not found: {exc}"), 404
295
296 1
        if interface_enable_id:
297 1
            interface_number = int(interface_enable_id.split(":")[-1])
298
299 1
            try:
300 1
                switch.interfaces[interface_number].enable()
301 1
            except KeyError as exc:
302 1
                error_list.append(f"Switch {dpid} Interface {exc}")
303
        else:
304 1
            for interface in switch.interfaces.values():
305 1
                interface.enable()
306 1
        if not error_list:
307 1
            log.info(f"Storing administrative state for enabled interfaces.")
308 1
            self.save_status_on_storehouse()
309 1
            return jsonify("Operation successful"), 200
310 1
        return jsonify({msg_error:
311
                        error_list}), 409
312
313 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...
314 1
    @rest('v3/interfaces/<interface_disable_id>/disable', methods=['POST'])
315 1
    def disable_interface(self, interface_disable_id=None, dpid=None):
316
        """Administratively disable interfaces in the topology."""
317 1
        error_list = []  # List of interfaces that were not deactivated.
318 1
        msg_error = "Some interfaces couldn't be found and deactivated: "
319 1
        if dpid is None:
320 1
            dpid = ":".join(interface_disable_id.split(":")[:-1])
321 1
        try:
322 1
            switch = self.controller.switches[dpid]
323 1
        except KeyError as exc:
324 1
            return jsonify(f"Switch not found: {exc}"), 404
325
326 1
        if interface_disable_id:
327 1
            interface_number = int(interface_disable_id.split(":")[-1])
328
329 1
            try:
330 1
                switch.interfaces[interface_number].disable()
331 1
            except KeyError as exc:
332 1
                error_list.append(f"Switch {dpid} Interface {exc}")
333
        else:
334 1
            for interface in switch.interfaces.values():
335 1
                interface.disable()
336 1
        if not error_list:
337 1
            log.info(f"Storing administrative state for disabled interfaces.")
338 1
            self.save_status_on_storehouse()
339 1
            return jsonify("Operation successful"), 200
340 1
        return jsonify({msg_error:
341
                        error_list}), 409
342
343 1
    @rest('v3/interfaces/<interface_id>/metadata')
344
    def get_interface_metadata(self, interface_id):
345
        """Get metadata from an interface."""
346 1
        switch_id = ":".join(interface_id.split(":")[:-1])
347 1
        interface_number = int(interface_id.split(":")[-1])
348 1
        try:
349 1
            switch = self.controller.switches[switch_id]
350 1
        except KeyError:
351 1
            return jsonify("Switch not found"), 404
352
353 1
        try:
354 1
            interface = switch.interfaces[interface_number]
355 1
        except KeyError:
356 1
            return jsonify("Interface not found"), 404
357
358 1
        return jsonify({"metadata": interface.metadata}), 200
359
360 1
    @rest('v3/interfaces/<interface_id>/metadata', methods=['POST'])
361
    def add_interface_metadata(self, interface_id):
362
        """Add metadata to an interface."""
363 1
        metadata = request.get_json()
364
365 1
        switch_id = ":".join(interface_id.split(":")[:-1])
366 1
        interface_number = int(interface_id.split(":")[-1])
367 1
        try:
368 1
            switch = self.controller.switches[switch_id]
369 1
        except KeyError:
370 1
            return jsonify("Switch not found"), 404
371
372 1
        try:
373 1
            interface = switch.interfaces[interface_number]
374 1
        except KeyError:
375 1
            return jsonify("Interface not found"), 404
376
377 1
        interface.extend_metadata(metadata)
378 1
        self.notify_metadata_changes(interface, 'added')
379 1
        return jsonify("Operation successful"), 201
380
381 1
    @rest('v3/interfaces/<interface_id>/metadata/<key>', methods=['DELETE'])
382
    def delete_interface_metadata(self, interface_id, key):
383
        """Delete metadata from an interface."""
384 1
        switch_id = ":".join(interface_id.split(":")[:-1])
385 1
        interface_number = int(interface_id.split(":")[-1])
386
387 1
        try:
388 1
            switch = self.controller.switches[switch_id]
389 1
        except KeyError:
390 1
            return jsonify("Switch not found"), 404
391
392 1
        try:
393 1
            interface = switch.interfaces[interface_number]
394 1
        except KeyError:
395 1
            return jsonify("Interface not found"), 404
396
397 1
        if interface.remove_metadata(key) is False:
398 1
            return jsonify("Metadata not found"), 404
399
400 1
        self.notify_metadata_changes(interface, 'removed')
401 1
        return jsonify("Operation successful"), 200
402
403
    # Link related methods
404 1
    @rest('v3/links')
405
    def get_links(self):
406
        """Return a json with all the links in the topology.
407
408
        Links are connections between interfaces.
409
        """
410
        return jsonify(self._get_links_dict()), 200
411
412 1
    @rest('v3/links/<link_id>/enable', methods=['POST'])
413
    def enable_link(self, link_id):
414
        """Administratively enable a link in the topology."""
415 1
        try:
416 1
            self.links[link_id].enable()
417 1
        except KeyError:
418 1
            return jsonify("Link not found"), 404
419 1
        self.save_status_on_storehouse()
420 1
        return jsonify("Operation successful"), 201
421
422 1
    @rest('v3/links/<link_id>/disable', methods=['POST'])
423
    def disable_link(self, link_id):
424
        """Administratively disable a link in the topology."""
425 1
        try:
426 1
            self.links[link_id].disable()
427 1
        except KeyError:
428 1
            return jsonify("Link not found"), 404
429 1
        self.save_status_on_storehouse()
430 1
        return jsonify("Operation successful"), 201
431
432 1
    @rest('v3/links/<link_id>/metadata')
433
    def get_link_metadata(self, link_id):
434
        """Get metadata from a link."""
435 1
        try:
436 1
            return jsonify({"metadata": self.links[link_id].metadata}), 200
437 1
        except KeyError:
438 1
            return jsonify("Link not found"), 404
439
440 1
    @rest('v3/links/<link_id>/metadata', methods=['POST'])
441
    def add_link_metadata(self, link_id):
442
        """Add metadata to a link."""
443 1
        metadata = request.get_json()
444 1
        try:
445 1
            link = self.links[link_id]
446 1
        except KeyError:
447 1
            return jsonify("Link not found"), 404
448
449 1
        link.extend_metadata(metadata)
450 1
        self.notify_metadata_changes(link, 'added')
451 1
        return jsonify("Operation successful"), 201
452
453 1
    @rest('v3/links/<link_id>/metadata/<key>', methods=['DELETE'])
454
    def delete_link_metadata(self, link_id, key):
455
        """Delete metadata from a link."""
456 1
        try:
457 1
            link = self.links[link_id]
458 1
        except KeyError:
459 1
            return jsonify("Link not found"), 404
460
461 1
        if link.remove_metadata(key) is False:
462 1
            return jsonify("Metadata not found"), 404
463
464 1
        self.notify_metadata_changes(link, 'removed')
465 1
        return jsonify("Operation successful"), 200
466
467 1
    @listen_to('.*.switch.(new|reconnected)')
468
    def handle_new_switch(self, event):
469
        """Create a new Device on the Topology.
470
471
        Handle the event of a new created switch and update the topology with
472
        this new device.
473
        """
474 1
        switch = event.content['switch']
475 1
        switch.activate()
476 1
        log.debug('Switch %s added to the Topology.', switch.id)
477 1
        self.notify_topology_update()
478 1
        self.update_instance_metadata(switch)
479 1
        self.restore_network_status(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 1
        interface_a.update_link(link)
610 1
        interface_b.update_link(link)
611
612 1
        interface_a.nni = True
613 1
        interface_b.nni = True
614
615 1
        self.notify_topology_update()
616 1
        self.restore_network_status(link)
617
618
    # def add_host(self, event):
619
    #    """Update the topology with a new Host."""
620
621
    #    interface = event.content['port']
622
    #    mac = event.content['reachable_mac']
623
624
    #    host = Host(mac)
625
    #    link = self.topology.get_link(interface.id)
626
    #    if link is not None:
627
    #        return
628
629
    #    self.topology.add_link(interface.id, host.id)
630
    #    self.topology.add_device(host)
631
632
    #    if settings.DISPLAY_FULL_DUPLEX_LINKS:
633
    #        self.topology.add_link(host.id, interface.id)
634
635
    # pylint: disable=unused-argument
636 1
    @listen_to('.*.network_status.updated')
637 1
    def save_status_on_storehouse(self, event=None):
638
        """Save the network administrative status using storehouse."""
639 1
        status = self._get_switches_dict()
640 1
        status['id'] = 'network_status'
641 1
        if event:
642
            content = event.content
643
            log.info(f"Storing the administrative state of the"
644
                     f" {content['attribute']} attribute to"
645
                     f" {content['state']} in the interfaces"
646
                     f" {content['interface_ids']}")
647 1
        status.update(self._get_links_dict())
648 1
        self.storehouse.save_status(status)
649
650 1
    def notify_topology_update(self):
651
        """Send an event to notify about updates on the topology."""
652 1
        name = 'kytos/topology.updated'
653 1
        event = KytosEvent(name=name, content={'topology':
654
                                               self._get_topology()})
655 1
        self.controller.buffers.app.put(event)
656
657 1
    def notify_link_status_change(self, link):
658
        """Send an event to notify about a status change on a link."""
659 1
        name = 'kytos/topology.'
660 1
        if link.is_active():
661 1
            status = 'link_up'
662
        else:
663
            status = 'link_down'
664 1
        event = KytosEvent(name=name+status, content={'link': link})
665 1
        self.controller.buffers.app.put(event)
666
667 1
    def notify_metadata_changes(self, obj, action):
668
        """Send an event to notify about metadata changes."""
669 1
        if isinstance(obj, Switch):
670 1
            entity = 'switch'
671 1
            entities = 'switches'
672 1
        elif isinstance(obj, Interface):
673 1
            entity = 'interface'
674 1
            entities = 'interfaces'
675
        elif isinstance(obj, Link):
676
            entity = 'link'
677
            entities = 'links'
678
679 1
        name = f'kytos/topology.{entities}.metadata.{action}'
680 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...
681
                                               'metadata': obj.metadata})
682 1
        self.controller.buffers.app.put(event)
683 1
        log.debug(f'Metadata from {obj.id} was {action}.')
684
685 1
    @listen_to('.*.switch.port.created')
686
    def notify_port_created(self, original_event):
687
        """Notify when a port is created."""
688 1
        name = 'kytos/topology.port.created'
689 1
        event = KytosEvent(name=name, content=original_event.content)
690 1
        self.controller.buffers.app.put(event)
691
692 1
    @listen_to('kytos/topology.*.metadata.*')
693
    def save_metadata_on_store(self, event):
694
        """Send to storehouse the data updated."""
695 1
        name = 'kytos.storehouse.update'
696 1
        if 'switch' in event.content:
697 1
            store = self.store_items.get('switches')
698 1
            obj = event.content.get('switch')
699 1
            namespace = 'kytos.topology.switches.metadata'
700 1
        elif 'interface' in event.content:
701 1
            store = self.store_items.get('interfaces')
702 1
            obj = event.content.get('interface')
703 1
            namespace = 'kytos.topology.iterfaces.metadata'
704 1
        elif 'link' in event.content:
705 1
            store = self.store_items.get('links')
706 1
            obj = event.content.get('link')
707 1
            namespace = 'kytos.topology.links.metadata'
708
709 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...
710 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...
711
                   'box_id': store.box_id,
712
                   'data': store.data,
713
                   'callback': self.update_instance}
714
715 1
        event = KytosEvent(name=name, content=content)
716 1
        self.controller.buffers.app.put(event)
717
718 1
    @staticmethod
719
    def update_instance(event, _data, error):
720
        """Display in Kytos console if the data was updated."""
721
        entities = event.content.get('namespace', '').split('.')[-2]
722
        if error:
723
            log.error(f'Error trying to update storehouse {entities}.')
724
        else:
725
            log.debug(f'Storehouse update to entities: {entities}.')
726
727 1
    def verify_storehouse(self, entities):
728
        """Request a list of box saved by specific entity."""
729 1
        name = 'kytos.storehouse.list'
730 1
        content = {'namespace': f'kytos.topology.{entities}.metadata',
731
                   'callback': self.request_retrieve_entities}
732 1
        event = KytosEvent(name=name, content=content)
733 1
        self.controller.buffers.app.put(event)
734 1
        log.info(f'verify data in storehouse for {entities}.')
735
736 1
    def request_retrieve_entities(self, event, data, _error):
737
        """Create a box or retrieve an existent box from storehouse."""
738 1
        msg = ''
739 1
        content = {'namespace': event.content.get('namespace'),
740
                   'callback': self.load_from_store,
741
                   'data': {}}
742
743 1
        if not data:
744 1
            name = 'kytos.storehouse.create'
745 1
            msg = 'Create new box in storehouse'
746
        else:
747 1
            name = 'kytos.storehouse.retrieve'
748 1
            content['box_id'] = data[0]
749 1
            msg = 'Retrieve data from storeohouse.'
750
751 1
        event = KytosEvent(name=name, content=content)
752 1
        self.controller.buffers.app.put(event)
753 1
        log.debug(msg)
754
755 1
    def load_from_store(self, event, box, error):
756
        """Save the data retrived from storehouse."""
757
        entities = event.content.get('namespace', '').split('.')[-2]
758
        if error:
759
            log.error('Error while get a box from storehouse.')
760
        else:
761
            self.store_items[entities] = box
762
            log.debug('Data updated')
763
764 1
    def update_instance_metadata(self, obj):
765
        """Update object instance with saved metadata."""
766
        metadata = None
767
        if isinstance(obj, Interface):
768
            all_metadata = self.store_items.get('interfaces', None)
769
            if all_metadata:
770
                metadata = all_metadata.data.get(obj.id)
771
        elif isinstance(obj, Switch):
772
            all_metadata = self.store_items.get('switches', None)
773
            if all_metadata:
774
                metadata = all_metadata.data.get(obj.id)
775
        elif isinstance(obj, Link):
776
            all_metadata = self.store_items.get('links', None)
777
            if all_metadata:
778
                metadata = all_metadata.data.get(obj.id)
779
780
        if metadata:
781
            obj.extend_metadata(metadata)
782
            log.debug(f'Metadata to {obj.id} was updated')
783
784 1
    @listen_to('kytos/maintenance.start_link')
785
    def handle_link_maintenance_start(self, event):
786
        """Deals with the start of links maintenance."""
787 1
        notify_links = []
788 1
        maintenance_links = event.content['links']
789 1
        for maintenance_link in maintenance_links:
790 1
            try:
791 1
                link = self.links[maintenance_link.id]
792 1
            except KeyError:
793 1
                continue
794 1
            notify_links.append(link)
795 1
        for link in notify_links:
796 1
            link.disable()
797 1
            link.deactivate()
798 1
            link.endpoint_a.deactivate()
799 1
            link.endpoint_b.deactivate()
800 1
            link.endpoint_a.disable()
801 1
            link.endpoint_b.disable()
802 1
            self.notify_link_status_change(link)
803
804 1
    @listen_to('kytos/maintenance.end_link')
805
    def handle_link_maintenance_end(self, event):
806
        """Deals with the end of links maintenance."""
807 1
        notify_links = []
808 1
        maintenance_links = event.content['links']
809 1
        for maintenance_link in maintenance_links:
810 1
            try:
811 1
                link = self.links[maintenance_link.id]
812 1
            except KeyError:
813 1
                continue
814 1
            notify_links.append(link)
815 1
        for link in notify_links:
816 1
            link.enable()
817 1
            link.activate()
818 1
            link.endpoint_a.activate()
819 1
            link.endpoint_b.activate()
820 1
            link.endpoint_a.enable()
821 1
            link.endpoint_b.enable()
822
            self.notify_link_status_change(link)
823