Passed
Pull Request — master (#123)
by
unknown
02:35
created

build.main.Main.execute()   A

Complexity

Conditions 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1.125

Importance

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