Passed
Pull Request — master (#114)
by
unknown
02:23
created

build.main   F

Complexity

Total Complexity 142

Size/Duplication

Total Lines 768
Duplicated Lines 7.55 %

Test Coverage

Coverage 86.01%

Importance

Changes 0
Metric Value
eloc 536
dl 58
loc 768
ccs 418
cts 486
cp 0.8601
rs 2
c 0
b 0
f 0
wmc 142

57 Methods

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