Completed
Pull Request — master (#76)
by Antonio
01:52
created

build.main   F

Complexity

Total Complexity 101

Size/Duplication

Total Lines 586
Duplicated Lines 5.8 %

Test Coverage

Coverage 40.46%

Importance

Changes 0
Metric Value
eloc 391
dl 34
loc 586
ccs 140
cts 346
cp 0.4046
rs 2
c 0
b 0
f 0
wmc 101

47 Methods

Rating   Name   Duplication   Size   Complexity  
A Main.get_switch_metadata() 0 8 2
A Main.disable_switch() 0 8 2
A Main.enable_switch() 0 8 2
A Main.add_switch_metadata() 0 12 2
A Main.get_interfaces() 0 10 3
A Main.get_switches() 0 4 1
A Main.get_topology() 0 7 1
A Main.delete_switch_metadata() 0 11 2
A Main.verify_storehouse() 0 8 1
A Main.notify_port_created() 0 6 1
A Main.load_from_store() 0 8 2
A Main.request_retrieve_entities() 0 18 2
A Main.disable_link() 0 9 2
A Main.enable_interface() 17 17 3
A Main._get_switches_dict() 0 4 1
A Main.delete_interface_metadata() 0 21 4
A Main.add_interface_metadata() 0 20 3
B Main.update_instance_metadata() 0 19 8
A Main.handle_interface_down() 0 9 1
A Main.notify_link_status_change() 0 9 2
B Main.handle_interface_link_up() 0 32 7
A Main.handle_interface_created() 0 4 1
A Main.get_interface_metadata() 0 16 3
A Main.shutdown() 0 3 1
A Main.delete_link_metadata() 0 13 3
A Main.notify_topology_update() 0 6 1
A Main.setup() 0 10 1
A Main.enable_link() 0 9 2
A Main.handle_new_switch() 0 12 1
A Main.execute() 0 2 1
A Main.update_instance() 0 8 2
A Main.handle_connection_lost() 0 12 2
A Main.disable_interface() 17 17 3
A Main.get_links() 0 7 1
A Main.save_metadata_on_store() 0 25 4
A Main.handle_interface_link_down() 0 13 3
A Main._get_links_dict() 0 4 1
A Main.add_links() 0 14 1
A Main.handle_interface_up() 0 9 1
A Main.handle_interface_deleted() 0 4 1
A Main._get_link_or_create() 0 9 3
A Main._get_link_from_interface() 0 6 3
A Main.add_link_metadata() 0 12 2
A Main.notify_metadata_changes() 0 17 4
A Main._get_topology_dict() 0 4 1
A Main.get_link_metadata() 0 7 2
A Main._get_topology() 0 3 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complexity

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like build.main often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

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
17 1
DEFAULT_LINK_UP_TIMER = 10
18
19
20 1
class Main(KytosNApp):  # pylint: disable=too-many-public-methods
21
    """Main class of kytos/topology NApp.
22
23
    This class is the entry point for this napp.
24
    """
25
26 1
    def setup(self):
27
        """Initialize the NApp's links list."""
28 1
        self.links = {}
29 1
        self.store_items = {}
30 1
        self.link_up_timer = getattr(settings, 'LINK_UP_TIMER',
31
                                     DEFAULT_LINK_UP_TIMER)
32
33 1
        self.verify_storehouse('switches')
34 1
        self.verify_storehouse('interfaces')
35 1
        self.verify_storehouse('links')
36
37 1
    def execute(self):
38
        """Do nothing."""
39
40 1
    def shutdown(self):
41
        """Do nothing."""
42
        log.info('NApp kytos/topology shutting down.')
43
44 1
    def _get_link_or_create(self, endpoint_a, endpoint_b):
45
        new_link = Link(endpoint_a, endpoint_b)
46
47
        for link in self.links.values():
48
            if new_link == link:
49
                return link
50
51
        self.links[new_link.id] = new_link
52
        return new_link
53
54 1
    def _get_switches_dict(self):
55
        """Return a dictionary with the known switches."""
56
        return {'switches': {s.id: s.as_dict() for s in
57
                             self.controller.switches.values()}}
58
59 1
    def _get_links_dict(self):
60
        """Return a dictionary with the known links."""
61
        return {'links': {l.id: l.as_dict() for l in
62
                          self.links.values()}}
63
64 1
    def _get_topology_dict(self):
65
        """Return a dictionary with the known topology."""
66
        return {'topology': {**self._get_switches_dict(),
67
                             **self._get_links_dict()}}
68
69 1
    def _get_topology(self):
70
        """Return an object representing the topology."""
71 1
        return Topology(self.controller.switches, self.links)
72
73 1
    def _get_link_from_interface(self, interface):
74
        """Return the link of the interface, or None if it does not exist."""
75
        for link in self.links.values():
76
            if interface in (link.endpoint_a, link.endpoint_b):
77
                return link
78
        return None
79
80 1
    @rest('v3/')
81
    def get_topology(self):
82
        """Return the latest known topology.
83
84
        This topology is updated when there are network events.
85
        """
86
        return jsonify(self._get_topology_dict())
87
88
    # Switch related methods
89 1
    @rest('v3/switches')
90
    def get_switches(self):
91
        """Return a json with all the switches in the topology."""
92
        return jsonify(self._get_switches_dict())
93
94 1
    @rest('v3/switches/<dpid>/enable', methods=['POST'])
95
    def enable_switch(self, dpid):
96
        """Administratively enable a switch in the topology."""
97
        try:
98
            self.controller.switches[dpid].enable()
99
            return jsonify("Operation successful"), 201
100
        except KeyError:
101
            return jsonify("Switch not found"), 404
102
103 1
    @rest('v3/switches/<dpid>/disable', methods=['POST'])
104
    def disable_switch(self, dpid):
105
        """Administratively disable a switch in the topology."""
106
        try:
107
            self.controller.switches[dpid].disable()
108
            return jsonify("Operation successful"), 201
109
        except KeyError:
110
            return jsonify("Switch not found"), 404
111
112 1
    @rest('v3/switches/<dpid>/metadata')
113
    def get_switch_metadata(self, dpid):
114
        """Get metadata from a switch."""
115
        try:
116
            return jsonify({"metadata":
117
                            self.controller.switches[dpid].metadata}), 200
118
        except KeyError:
119
            return jsonify("Switch not found"), 404
120
121 1
    @rest('v3/switches/<dpid>/metadata', methods=['POST'])
122
    def add_switch_metadata(self, dpid):
123
        """Add metadata to a switch."""
124
        metadata = request.get_json()
125
        try:
126
            switch = self.controller.switches[dpid]
127
        except KeyError:
128
            return jsonify("Switch not found"), 404
129
130
        switch.extend_metadata(metadata)
131
        self.notify_metadata_changes(switch, 'added')
132
        return jsonify("Operation successful"), 201
133
134 1
    @rest('v3/switches/<dpid>/metadata/<key>', methods=['DELETE'])
135
    def delete_switch_metadata(self, dpid, key):
136
        """Delete metadata from a switch."""
137
        try:
138
            switch = self.controller.switches[dpid]
139
        except KeyError:
140
            return jsonify("Switch not found"), 404
141
142
        switch.remove_metadata(key)
143
        self.notify_metadata_changes(switch, 'removed')
144
        return jsonify("Operation successful"), 200
145
146
    # Interface related methods
147 1
    @rest('v3/interfaces')
148
    def get_interfaces(self):
149
        """Return a json with all the interfaces in the topology."""
150
        interfaces = {}
151
        switches = self._get_switches_dict()
152
        for switch in switches['switches'].values():
153
            for interface_id, interface in switch['interfaces'].items():
154
                interfaces[interface_id] = interface
155
156
        return jsonify({'interfaces': interfaces})
157
158 1 View Code Duplication
    @rest('v3/interfaces/<interface_id>/enable', methods=['POST'])
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
159
    def enable_interface(self, interface_id):
160
        """Administratively enable an interface in the topology."""
161
        switch_id = ":".join(interface_id.split(":")[:-1])
162
        interface_number = int(interface_id.split(":")[-1])
163
164
        try:
165
            switch = self.controller.switches[switch_id]
166
        except KeyError:
167
            return jsonify("Switch not found"), 404
168
169
        try:
170
            switch.interfaces[interface_number].enable()
171
        except KeyError:
172
            return jsonify("Interface not found"), 404
173
174
        return jsonify("Operation successful"), 201
175
176 1 View Code Duplication
    @rest('v3/interfaces/<interface_id>/disable', methods=['POST'])
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
177
    def disable_interface(self, interface_id):
178
        """Administratively disable an interface in the topology."""
179
        switch_id = ":".join(interface_id.split(":")[:-1])
180
        interface_number = int(interface_id.split(":")[-1])
181
182
        try:
183
            switch = self.controller.switches[switch_id]
184
        except KeyError:
185
            return jsonify("Switch not found"), 404
186
187
        try:
188
            switch.interfaces[interface_number].disable()
189
        except KeyError:
190
            return jsonify("Interface not found"), 404
191
192
        return jsonify("Operation successful"), 201
193
194 1
    @rest('v3/interfaces/<interface_id>/metadata')
195
    def get_interface_metadata(self, interface_id):
196
        """Get metadata from an interface."""
197
        switch_id = ":".join(interface_id.split(":")[:-1])
198
        interface_number = int(interface_id.split(":")[-1])
199
        try:
200
            switch = self.controller.switches[switch_id]
201
        except KeyError:
202
            return jsonify("Switch not found"), 404
203
204
        try:
205
            interface = switch.interfaces[interface_number]
206
        except KeyError:
207
            return jsonify("Interface not found"), 404
208
209
        return jsonify({"metadata": interface.metadata}), 200
210
211 1
    @rest('v3/interfaces/<interface_id>/metadata', methods=['POST'])
212
    def add_interface_metadata(self, interface_id):
213
        """Add metadata to an interface."""
214
        metadata = request.get_json()
215
216
        switch_id = ":".join(interface_id.split(":")[:-1])
217
        interface_number = int(interface_id.split(":")[-1])
218
        try:
219
            switch = self.controller.switches[switch_id]
220
        except KeyError:
221
            return jsonify("Switch not found"), 404
222
223
        try:
224
            interface = switch.interfaces[interface_number]
225
        except KeyError:
226
            return jsonify("Interface not found"), 404
227
228
        interface.extend_metadata(metadata)
229
        self.notify_metadata_changes(interface, 'added')
230
        return jsonify("Operation successful"), 201
231
232 1
    @rest('v3/interfaces/<interface_id>/metadata/<key>', methods=['DELETE'])
233
    def delete_interface_metadata(self, interface_id, key):
234
        """Delete metadata from an interface."""
235
        switch_id = ":".join(interface_id.split(":")[:-1])
236
        interface_number = int(interface_id.split(":")[-1])
237
238
        try:
239
            switch = self.controller.switches[switch_id]
240
        except KeyError:
241
            return jsonify("Switch not found"), 404
242
243
        try:
244
            interface = switch.interfaces[interface_number]
245
        except KeyError:
246
            return jsonify("Interface not found"), 404
247
248
        if interface.remove_metadata(key) is False:
249
            return jsonify("Metadata not found"), 404
250
251
        self.notify_metadata_changes(interface, 'removed')
252
        return jsonify("Operation successful"), 200
253
254
    # Link related methods
255 1
    @rest('v3/links')
256
    def get_links(self):
257
        """Return a json with all the links in the topology.
258
259
        Links are connections between interfaces.
260
        """
261
        return jsonify(self._get_links_dict()), 200
262
263 1
    @rest('v3/links/<link_id>/enable', methods=['POST'])
264
    def enable_link(self, link_id):
265
        """Administratively enable a link in the topology."""
266
        try:
267
            self.links[link_id].enable()
268
        except KeyError:
269
            return jsonify("Link not found"), 404
270
271
        return jsonify("Operation successful"), 201
272
273 1
    @rest('v3/links/<link_id>/disable', methods=['POST'])
274
    def disable_link(self, link_id):
275
        """Administratively disable a link in the topology."""
276
        try:
277
            self.links[link_id].disable()
278
        except KeyError:
279
            return jsonify("Link not found"), 404
280
281
        return jsonify("Operation successful"), 201
282
283 1
    @rest('v3/links/<link_id>/metadata')
284
    def get_link_metadata(self, link_id):
285
        """Get metadata from a link."""
286
        try:
287
            return jsonify({"metadata": self.links[link_id].metadata}), 200
288
        except KeyError:
289
            return jsonify("Link not found"), 404
290
291 1
    @rest('v3/links/<link_id>/metadata', methods=['POST'])
292
    def add_link_metadata(self, link_id):
293
        """Add metadata to a link."""
294
        metadata = request.get_json()
295
        try:
296
            link = self.links[link_id]
297
        except KeyError:
298
            return jsonify("Link not found"), 404
299
300
        link.extend_metadata(metadata)
301
        self.notify_metadata_changes(link, 'added')
302
        return jsonify("Operation successful"), 201
303
304 1
    @rest('v3/links/<link_id>/metadata/<key>', methods=['DELETE'])
305
    def delete_link_metadata(self, link_id, key):
306
        """Delete metadata from a link."""
307
        try:
308
            link = self.links[link_id]
309
        except KeyError:
310
            return jsonify("Link not found"), 404
311
312
        if link.remove_metadata(key) is False:
313
            return jsonify("Metadata not found"), 404
314
315
        self.notify_metadata_changes(link, 'removed')
316
        return jsonify("Operation successful"), 200
317
318 1
    @listen_to('.*.switch.(new|reconnected)')
319
    def handle_new_switch(self, event):
320
        """Create a new Device on the Topology.
321
322
        Handle the event of a new created switch and update the topology with
323
        this new device.
324
        """
325 1
        switch = event.content['switch']
326 1
        switch.activate()
327 1
        log.debug('Switch %s added to the Topology.', switch.id)
328 1
        self.notify_topology_update()
329 1
        self.update_instance_metadata(switch)
330
331 1
    @listen_to('.*.connection.lost')
332
    def handle_connection_lost(self, event):
333
        """Remove a Device from the topology.
334
335
        Remove the disconnected Device and every link that has one of its
336
        interfaces.
337
        """
338 1
        switch = event.content['source'].switch
339 1
        if switch:
340 1
            switch.deactivate()
341 1
            log.debug('Switch %s removed from the Topology.', switch.id)
342 1
            self.notify_topology_update()
343
344 1
    def handle_interface_up(self, event):
345
        """Update the topology based on a Port Modify event.
346
347
        The event notifies that an interface was changed to 'up'.
348
        """
349 1
        interface = event.content['interface']
350 1
        interface.activate()
351 1
        self.notify_topology_update()
352 1
        self.update_instance_metadata(interface)
353
354 1
    @listen_to('.*.switch.interface.created')
355
    def handle_interface_created(self, event):
356
        """Update the topology based on a Port Create event."""
357 1
        self.handle_interface_up(event)
358
359 1
    def handle_interface_down(self, event):
360
        """Update the topology based on a Port Modify event.
361
362
        The event notifies that an interface was changed to 'down'.
363
        """
364 1
        interface = event.content['interface']
365 1
        interface.deactivate()
366 1
        self.handle_interface_link_down(event)
367 1
        self.notify_topology_update()
368
369 1
    @listen_to('.*.switch.interface.deleted')
370
    def handle_interface_deleted(self, event):
371
        """Update the topology based on a Port Delete event."""
372 1
        self.handle_interface_down(event)
373
374 1
    @listen_to('.*.switch.interface.link_up')
375
    def handle_interface_link_up(self, event):
376
        """Update the topology based on a Port Modify event.
377
378
        The event notifies that an interface's link was changed to 'up'.
379
        """
380 1
        interface = event.content['interface']
381 1
        link = self._get_link_from_interface(interface)
382 1
        if not link:
383
            return
384 1
        if link.endpoint_a == interface:
385 1
            other_interface = link.endpoint_b
386
        else:
387
            other_interface = link.endpoint_a
388 1
        interface.activate()
389 1
        if other_interface.is_active() is False:
390
            return
391 1
        if link.is_active() is False:
392 1
            link.update_metadata('last_status_change', time.time())
393 1
            link.activate()
394
395
            # As each run of this method uses a different thread,
396
            # there is no risk this sleep will lock the NApp.
397 1
            time.sleep(self.link_up_timer)
398
399 1
            last_status_change = link.get_metadata('last_status_change')
400 1
            now = time.time()
401 1
            if link.is_active() and \
402
                    now - last_status_change >= self.link_up_timer:
403 1
                self.notify_topology_update()
404 1
                self.update_instance_metadata(link)
405 1
                self.notify_link_status_change(link)
406
407 1
    @listen_to('.*.switch.interface.link_down')
408
    def handle_interface_link_down(self, event):
409
        """Update the topology based on a Port Modify event.
410
411
        The event notifies that an interface's link was changed to 'down'.
412
        """
413 1
        interface = event.content['interface']
414 1
        link = self._get_link_from_interface(interface)
415 1
        if link and link.is_active():
416 1
            link.deactivate()
417 1
            link.update_metadata('last_status_change', time.time())
418 1
            self.notify_topology_update()
419 1
            self.notify_link_status_change(link)
420
421 1
    @listen_to('.*.interface.is.nni')
422
    def add_links(self, event):
423
        """Update the topology with links related to the NNI interfaces."""
424 1
        interface_a = event.content['interface_a']
425 1
        interface_b = event.content['interface_b']
426
427 1
        link = self._get_link_or_create(interface_a, interface_b)
428 1
        interface_a.update_link(link)
429 1
        interface_b.update_link(link)
430
431 1
        interface_a.nni = True
432 1
        interface_b.nni = True
433
434 1
        self.notify_topology_update()
435
436
    # def add_host(self, event):
437
    #    """Update the topology with a new Host."""
438
439
    #    interface = event.content['port']
440
    #    mac = event.content['reachable_mac']
441
442
    #    host = Host(mac)
443
    #    link = self.topology.get_link(interface.id)
444
    #    if link is not None:
445
    #        return
446
447
    #    self.topology.add_link(interface.id, host.id)
448
    #    self.topology.add_device(host)
449
450
    #    if settings.DISPLAY_FULL_DUPLEX_LINKS:
451
    #        self.topology.add_link(host.id, interface.id)
452
453 1
    def notify_topology_update(self):
454
        """Send an event to notify about updates on the topology."""
455 1
        name = 'kytos/topology.updated'
456 1
        event = KytosEvent(name=name, content={'topology':
457
                                               self._get_topology()})
458 1
        self.controller.buffers.app.put(event)
459
460 1
    def notify_link_status_change(self, link):
461
        """Send an event to notify about a status change on a link."""
462 1
        name = 'kytos/topology.'
463 1
        if link.is_active():
464 1
            status = 'link_up'
465
        else:
466
            status = 'link_down'
467 1
        event = KytosEvent(name=name+status, content={'link': link})
468 1
        self.controller.buffers.app.put(event)
469
470 1
    def notify_metadata_changes(self, obj, action):
471
        """Send an event to notify about metadata changes."""
472 1
        if isinstance(obj, Switch):
473 1
            entity = 'switch'
474 1
            entities = 'switches'
475
        elif isinstance(obj, Interface):
476
            entity = 'interface'
477
            entities = 'interfaces'
478
        elif isinstance(obj, Link):
479
            entity = 'link'
480
            entities = 'links'
481
482 1
        name = f'kytos/topology.{entities}.metadata.{action}'
483 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...
484
                                               'metadata': obj.metadata})
485 1
        self.controller.buffers.app.put(event)
486 1
        log.debug(f'Metadata from {obj.id} was {action}.')
487
488 1
    @listen_to('.*.switch.port.created')
489
    def notify_port_created(self, original_event):
490
        """Notify when a port is created."""
491 1
        name = f'kytos/topology.port.created'
492 1
        event = KytosEvent(name=name, content=original_event.content)
493 1
        self.controller.buffers.app.put(event)
494
495 1
    @listen_to('kytos/topology.*.metadata.*')
496
    def save_metadata_on_store(self, event):
497
        """Send to storehouse the data updated."""
498
        name = 'kytos.storehouse.update'
499
        if 'switch' in event.content:
500
            store = self.store_items.get('switches')
501
            obj = event.content.get('switch')
502
            namespace = 'kytos.topology.switches.metadata'
503
        elif 'interface' in event.content:
504
            store = self.store_items.get('interfaces')
505
            obj = event.content.get('interface')
506
            namespace = 'kytos.topology.iterfaces.metadata'
507
        elif 'link' in event.content:
508
            store = self.store_items.get('links')
509
            obj = event.content.get('link')
510
            namespace = 'kytos.topology.links.metadata'
511
512
        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...
513
        content = {'namespace': namespace,
0 ignored issues
show
introduced by
The variable namespace does not seem to be defined for all execution paths.
Loading history...
514
                   'box_id': store.box_id,
515
                   'data': store.data,
516
                   'callback': self.update_instance}
517
518
        event = KytosEvent(name=name, content=content)
519
        self.controller.buffers.app.put(event)
520
521 1
    @staticmethod
522
    def update_instance(event, _data, error):
523
        """Display in Kytos console if the data was updated."""
524
        entities = event.content.get('namespace', '').split('.')[-2]
525
        if error:
526
            log.error(f'Error trying to update storehouse {entities}.')
527
        else:
528
            log.debug(f'Storehouse update to entities: {entities}.')
529
530 1
    def verify_storehouse(self, entities):
531
        """Request a list of box saved by specific entity."""
532 1
        name = 'kytos.storehouse.list'
533 1
        content = {'namespace': f'kytos.topology.{entities}.metadata',
534
                   'callback': self.request_retrieve_entities}
535 1
        event = KytosEvent(name=name, content=content)
536 1
        self.controller.buffers.app.put(event)
537 1
        log.info(f'verify data in storehouse for {entities}.')
538
539 1
    def request_retrieve_entities(self, event, data, _error):
540
        """Create a box or retrieve an existent box from storehouse."""
541
        msg = ''
542
        content = {'namespace': event.content.get('namespace'),
543
                   'callback': self.load_from_store,
544
                   'data': {}}
545
546
        if not data:
547
            name = 'kytos.storehouse.create'
548
            msg = 'Create new box in storehouse'
549
        else:
550
            name = 'kytos.storehouse.retrieve'
551
            content['box_id'] = data[0]
552
            msg = 'Retrieve data from storeohouse.'
553
554
        event = KytosEvent(name=name, content=content)
555
        self.controller.buffers.app.put(event)
556
        log.debug(msg)
557
558 1
    def load_from_store(self, event, box, error):
559
        """Save the data retrived from storehouse."""
560
        entities = event.content.get('namespace', '').split('.')[-2]
561
        if error:
562
            log.error('Error while get a box from storehouse.')
563
        else:
564
            self.store_items[entities] = box
565
            log.debug('Data updated')
566
567 1
    def update_instance_metadata(self, obj):
568
        """Update object instance with saved metadata."""
569
        metadata = None
570
        if isinstance(obj, Interface):
571
            all_metadata = self.store_items.get('interfaces', None)
572
            if all_metadata:
573
                metadata = all_metadata.data.get(obj.id)
574
        elif isinstance(obj, Switch):
575
            all_metadata = self.store_items.get('switches', None)
576
            if all_metadata:
577
                metadata = all_metadata.data.get(obj.id)
578
        elif isinstance(obj, Link):
579
            all_metadata = self.store_items.get('links', None)
580
            if all_metadata:
581
                metadata = all_metadata.data.get(obj.id)
582
583
        if metadata:
584
            obj.extend_metadata(metadata)
585
            log.debug(f'Metadata to {obj.id} was updated')
586