Passed
Pull Request — master (#123)
by Carlos
05:21
created

build.main   F

Complexity

Total Complexity 153

Size/Duplication

Total Lines 837
Duplicated Lines 6.93 %

Test Coverage

Coverage 90.17%

Importance

Changes 0
Metric Value
eloc 590
dl 58
loc 837
ccs 486
cts 539
cp 0.9017
rs 2
c 0
b 0
f 0
wmc 153

58 Methods

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