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