Passed
Pull Request — master (#132)
by
unknown
02:13
created

build.main   F

Complexity

Total Complexity 147

Size/Duplication

Total Lines 815
Duplicated Lines 7.12 %

Test Coverage

Coverage 90.6%

Importance

Changes 0
Metric Value
eloc 574
dl 58
loc 815
ccs 472
cts 521
cp 0.906
rs 2
c 0
b 0
f 0
wmc 147

58 Methods

Rating   Name   Duplication   Size   Complexity  
A Main.shutdown() 0 3 1
A Main.setup() 0 15 1
A Main.execute() 0 2 1
A Main._get_links_dict() 0 4 1
A Main._get_topology() 0 3 1
A Main._get_switches_dict() 0 12 3
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 7 1
A Main.delete_interface_metadata() 0 21 4
A Main.delete_link_metadata() 0 13 3
A Main.notify_topology_update() 0 6 1
A Main.get_switch_metadata() 0 8 2
A Main.save_metadata_on_store() 0 25 4
A Main.handle_interface_link_down() 0 8 1
A Main.add_links() 0 14 1
A Main.notify_metadata_changes() 0 17 4
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
B Main._restore_status() 0 31 7
A Main.add_switch_metadata() 0 12 2
A Main.get_interfaces() 0 10 3
A Main._restore_links() 0 31 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.enable_link() 0 9 2
A Main.handle_link_down() 0 8 3
A Main.handle_new_switch() 0 12 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.handle_switch_maintenance_start() 0 11 4
A Main._load_network_status() 0 22 4
A Main.handle_link_maintenance_start() 0 19 4
A Main.handle_interface_up() 0 9 1
A Main.handle_interface_deleted() 0 4 1
A Main.restore_network_status() 0 10 2
A Main.add_link_metadata() 0 12 2
A Main.delete_switch_metadata() 0 11 2
A Main.get_link_metadata() 0 7 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

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

Common duplication problems, and corresponding solutions are:

Complexity

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

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

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

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