Passed
Pull Request — master (#114)
by Humberto
02:01
created

build.main   F

Complexity

Total Complexity 143

Size/Duplication

Total Lines 778
Duplicated Lines 7.46 %

Test Coverage

Coverage 89.25%

Importance

Changes 0
Metric Value
wmc 143
eloc 543
dl 58
loc 778
ccs 440
cts 493
cp 0.8925
rs 2
c 0
b 0
f 0

57 Methods

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