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