Test Failed
Pull Request — master (#123)
by Humberto
02:17
created

build.main.Main.delete_interface_metadata()   A

Complexity

Conditions 4

Size

Total Lines 21
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 4.0092

Importance

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