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