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

build.main   F

Complexity

Total Complexity 164

Size/Duplication

Total Lines 897
Duplicated Lines 10.93 %

Test Coverage

Coverage 89.37%

Importance

Changes 0
Metric Value
eloc 636
dl 98
loc 897
ccs 521
cts 583
cp 0.8937
rs 1.964
c 0
b 0
f 0
wmc 164

61 Methods

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