Completed
Push — master ( 4fca5c...e35304 )
by Humberto
02:31 queued 11s
created

build.main.Main.enable_switch()   A

Complexity

Conditions 2

Size

Total Lines 9
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

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