Passed
Pull Request — master (#123)
by Carlos
03:00
created

build.main   F

Complexity

Total Complexity 156

Size/Duplication

Total Lines 851
Duplicated Lines 6.82 %

Test Coverage

Coverage 89.29%

Importance

Changes 0
Metric Value
eloc 602
dl 58
loc 851
ccs 492
cts 551
cp 0.8929
rs 1.998
c 0
b 0
f 0
wmc 156

58 Methods

Rating   Name   Duplication   Size   Complexity  
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._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.handle_interface_created() 0 4 1
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 57 11
A Main.notify_topology_update() 0 6 1
A Main.enable_link() 0 9 2
A Main.handle_link_down() 0 8 3
A Main.setup() 0 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.handle_connection_lost() 0 12 2
A Main.get_links() 0 7 1
B Main.disable_interface() 29 29 7
A Main.handle_switch_maintenance_start() 0 11 4
B Main._load_network_status() 0 25 5
A Main.get_topology() 0 7 1
A Main._restore_link() 0 23 4
A Main.save_metadata_on_store() 0 25 4
A Main.handle_interface_link_down() 0 8 1
A Main._get_links_dict() 0 4 1
A Main.handle_link_maintenance_start() 0 19 4
A Main.add_links() 0 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_topology_dict() 0 4 1
A Main.get_link_metadata() 0 7 2
A Main._get_topology() 0 3 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

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

Common duplication problems, and corresponding solutions are:

Complexity

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

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

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

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