Passed
Pull Request — master (#148)
by Carlos
02:52
created

build.main   F

Complexity

Total Complexity 161

Size/Duplication

Total Lines 887
Duplicated Lines 11.05 %

Test Coverage

Coverage 89.74%

Importance

Changes 0
Metric Value
eloc 628
dl 98
loc 887
ccs 516
cts 575
cp 0.8974
rs 1.972
c 0
b 0
f 0
wmc 161

61 Methods

Rating   Name   Duplication   Size   Complexity  
A Main.disable_link() 0 9 2
A Main.shutdown() 0 3 1
A Main.enable_link() 0 9 2
A Main.setup() 0 18 1
A Main.execute() 0 3 1
A Main.get_links() 0 7 1
A Main.get_link_metadata() 0 7 2
A Main.notify_port_created() 0 6 1
A Main.disable_switch() 0 13 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.enable_switch() 0 13 2
B Main.enable_interface() 29 29 7
A Main.add_switch_metadata() 0 13 2
A Main.get_interfaces() 0 10 3
A Main._get_switches_dict() 0 12 3
A Main.delete_interface_metadata() 21 21 4
A Main.add_interface_metadata() 19 19 3
B Main.update_instance_metadata() 0 18 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.notify_switch_enabled() 0 5 1
A Main.handle_interface_created() 0 4 1
A Main.get_interface_metadata() 0 16 3
A Main.delete_link_metadata() 0 13 3
C Main._restore_switch() 0 64 11
A Main.notify_topology_update() 0 6 1
A Main.handle_link_down() 0 8 3
A Main.handle_new_switch() 0 13 1
A Main.save_status_on_storehouse() 0 13 2
A Main.get_switch_metadata() 0 8 2
A Main._get_metadata() 0 12 3
A Main.update_instance() 0 8 2
A Main.handle_switch_maintenance_end() 0 10 3
A Main.handle_connection_lost() 0 12 2
B Main.disable_interface() 29 29 7
A Main.handle_switch_maintenance_start() 0 11 4
B Main._load_network_status() 0 25 5
A Main.get_topology() 0 7 1
A Main._restore_link() 0 22 4
A Main.save_metadata_on_store() 0 25 4
A Main.handle_interface_link_down() 0 8 1
A Main._get_links_dict() 0 4 1
A Main.handle_link_maintenance_start() 0 19 4
A Main.notify_switch_disabled() 0 5 1
A Main.add_links() 0 20 2
A Main.handle_interface_up() 0 9 1
A Main.handle_interface_deleted() 0 4 1
A Main._get_link_or_create() 0 9 3
A Main.restore_network_status() 0 11 5
A Main._get_link_from_interface() 0 6 3
A Main.add_link_metadata() 0 12 2
A Main.notify_metadata_changes() 0 17 4
A Main.delete_switch_metadata() 0 11 2
A Main._get_topology_dict() 0 4 1
A Main._get_topology() 0 3 1

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