Test Failed
Pull Request — master (#123)
by
unknown
03:21
created

build.main.Main.shutdown()   A

Complexity

Conditions 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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