Test Failed
Pull Request — master (#142)
by Carlos
02:24
created

build.main.Main.handle_switch_maintenance_end()   A

Complexity

Conditions 3

Size

Total Lines 10
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 3

Importance

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