Passed
Pull Request — master (#120)
by
unknown
02:16
created

build.main   F

Complexity

Total Complexity 147

Size/Duplication

Total Lines 795
Duplicated Lines 7.3 %

Test Coverage

Coverage 88.95%

Importance

Changes 0
Metric Value
eloc 558
dl 58
loc 795
rs 2
c 0
b 0
f 0
ccs 451
cts 507
cp 0.8895
wmc 147

57 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.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
C Main._restore_status() 0 41 10
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.notify_topology_update() 0 6 1
A Main.enable_link() 0 9 2
A Main.handle_link_down() 0 8 3
A Main.handle_new_switch() 0 12 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
A Main._load_network_status() 0 25 5
A Main.get_topology() 0 7 1
A Main.save_metadata_on_store() 0 25 4
A Main.handle_interface_link_down() 0 8 1
A Main.handle_link_maintenance_start() 0 19 4
A Main.add_links() 0 14 1
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 10 2
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

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