Passed
Pull Request — master (#120)
by
unknown
03:32
created

build.main.Main._restore_status()   C

Complexity

Conditions 10

Size

Total Lines 39
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 20
CRAP Score 12.9877

Importance

Changes 0
Metric Value
cc 10
eloc 29
nop 1
dl 0
loc 39
rs 5.9999
c 0
b 0
f 0
ccs 20
cts 29
cp 0.6897
crap 12.9877

How to fix   Complexity   

Complexity

Complex classes like build.main.Main._restore_status() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

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