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