Test Failed
Pull Request — master (#123)
by Carlos
03:44
created

build.main.Main.add_interface_metadata()   A

Complexity

Conditions 3

Size

Total Lines 20
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 3

Importance

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