Passed
Pull Request — master (#123)
by
unknown
07:23
created

build.main   F

Complexity

Total Complexity 152

Size/Duplication

Total Lines 828
Duplicated Lines 7 %

Test Coverage

Coverage 88.87%

Importance

Changes 0
Metric Value
eloc 581
dl 58
loc 828
ccs 471
cts 530
cp 0.8887
rs 2
c 0
b 0
f 0
wmc 152

58 Methods

Rating   Name   Duplication   Size   Complexity  
A Main._get_switches_dict() 0 12 3
A Main.shutdown() 0 3 1
A Main.setup() 0 18 1
A Main.execute() 0 3 1
A Main._get_links_dict() 0 4 1
A Main._get_link_or_create() 0 9 3
A Main._get_link_from_interface() 0 6 3
A Main._get_topology_dict() 0 4 1
A Main._get_topology() 0 3 1
A Main.get_switch_metadata() 0 8 2
A Main.handle_switch_maintenance_start() 0 11 4
A Main.handle_interface_deleted() 0 4 1
A Main.get_link_metadata() 0 7 2
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
A Main.add_switch_metadata() 0 12 2
A Main.get_interfaces() 0 10 3
A Main._restore_links() 0 20 4
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 13 1
A Main.save_status_on_storehouse() 0 13 2
A Main.update_instance() 0 8 2
A Main.handle_switch_maintenance_end() 0 10 3
A Main.handle_connection_lost() 0 12 2
A Main.get_links() 0 7 1
B Main.disable_interface() 29 29 7
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.handle_interface_link_down() 0 8 1
A Main.handle_link_maintenance_start() 0 19 4
A Main.add_links() 0 15 1
A Main.handle_interface_up() 0 9 1
A Main.restore_network_status() 0 11 5
A Main.add_link_metadata() 0 12 2
A Main.notify_metadata_changes() 0 17 4
A Main.delete_switch_metadata() 0 11 2
C Main._restore_switches() 0 48 9

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