Test Failed
Pull Request — master (#132)
by
unknown
02:14
created

build.main.Main.handle_link_maintenance_start()   A

Complexity

Conditions 4

Size

Total Lines 19
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 4.0312

Importance

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