Passed
Pull Request — master (#155)
by Carlos
03:31
created

build.main   F

Complexity

Total Complexity 159

Size/Duplication

Total Lines 876
Duplicated Lines 6.62 %

Test Coverage

Coverage 89.58%

Importance

Changes 0
Metric Value
eloc 618
dl 58
loc 876
ccs 507
cts 566
cp 0.8958
rs 1.982
c 0
b 0
f 0
wmc 159

60 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._restore_link() 0 22 4
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.disable_switch() 0 13 2
A Main.disable_link() 0 9 2
A Main.enable_switch() 0 13 2
B Main.enable_interface() 29 29 7
A Main.add_switch_metadata() 0 12 2
A Main.get_interfaces() 0 10 3
A Main.delete_interface_metadata() 0 21 4
A Main.add_interface_metadata() 0 20 3
A Main.get_switches() 0 4 1
A Main.get_interface_metadata() 0 16 3
A Main.delete_link_metadata() 0 13 3
C Main._restore_switch() 0 64 11
A Main.enable_link() 0 9 2
A Main.get_switch_metadata() 0 8 2
A Main.get_links() 0 7 1
B Main.disable_interface() 29 29 7
B Main._load_network_status() 0 25 5
A Main.get_topology() 0 7 1
A Main.restore_network_status() 0 11 5
A Main.add_link_metadata() 0 12 2
A Main.delete_switch_metadata() 0 11 2
A Main.get_link_metadata() 0 7 2
A Main.notify_port_created() 0 6 1
A Main.verify_storehouse() 0 8 1
A Main.load_from_store() 0 8 2
A Main.request_retrieve_entities() 0 18 2
B Main.update_instance_metadata() 0 19 8
A Main.handle_link_maintenance_end() 0 19 4
A Main.notify_link_status_change() 0 9 2
A Main.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.notify_switch_enabled() 0 5 1
A Main.handle_interface_created() 0 4 1
A Main.notify_topology_update() 0 6 1
A Main.handle_link_down() 0 8 3
A Main.handle_new_switch() 0 15 2
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.handle_switch_maintenance_start() 0 11 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.notify_switch_disabled() 0 5 1
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.notify_metadata_changes() 0 17 4

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