Passed
Pull Request — master (#111)
by
unknown
02:10
created

build.main   F

Complexity

Total Complexity 142

Size/Duplication

Total Lines 767
Duplicated Lines 7.3 %

Test Coverage

Coverage 86.01%

Importance

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

57 Methods

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