Passed
Push — master ( f84184...cafc34 )
by Vinicius
04:02 queued 15s
created

kytos.core.link   A

Complexity

Total Complexity 34

Size/Duplication

Total Lines 213
Duplicated Lines 5.16 %

Importance

Changes 0
Metric Value
eloc 119
dl 11
loc 213
rs 9.68
c 0
b 0
f 0
wmc 34

21 Methods

Rating   Name   Duplication   Size   Complexity  
A Link.use_tag() 0 10 2
A Link.status_reason() 0 10 2
A Link.available_vlans() 0 5 1
A Link.__hash__() 0 2 1
B Link.get_next_available_tag() 0 26 6
A Link.register_status_reason_func() 0 5 1
A Link.available_tags() 0 8 1
A Link.is_active() 0 11 1
A Link._get_available_vlans() 0 5 1
A Link.__repr__() 0 2 1
A Link.register_status_func() 0 4 1
A Link.make_tag_available() 0 7 2
A Link.id() 0 9 1
A Link.__eq__() 0 3 1
A Link.is_tag_available() 0 4 1
A Link.status() 11 11 4
A Link.is_enabled() 0 11 1
A Link.__init__() 0 13 3
A Link.as_dict() 0 11 1
A Link.as_json() 0 3 1
A Link.from_dict() 0 5 1

How to fix   Duplicated Code   

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:

1
"""Module with all classes related to links.
2
3
Links are low level abstractions representing connections between two
4
interfaces.
5
"""
6
import json
7
import operator
8
from collections import OrderedDict
9
from functools import reduce
10
from threading import Lock
11
12
from kytos.core.common import EntityStatus, GenericEntity
13
from kytos.core.exceptions import (KytosLinkCreationError,
14
                                   KytosNoTagAvailableError)
15
from kytos.core.id import LinkID
16
from kytos.core.interface import TAGType
17
18
19
class Link(GenericEntity):
20
    """Define a link between two Endpoints."""
21
22
    status_funcs = OrderedDict()
23
    status_reason_funcs = OrderedDict()
24
    _get_available_vlans_lock = Lock()
25
26
    def __init__(self, endpoint_a, endpoint_b):
27
        """Create a Link instance and set its attributes.
28
29
        Two kytos.core.interface.Interface are required as parameters.
30
        """
31
        if endpoint_a is None:
32
            raise KytosLinkCreationError("endpoint_a cannot be None")
33
        if endpoint_b is None:
34
            raise KytosLinkCreationError("endpoint_b cannot be None")
35
        self.endpoint_a = endpoint_a
36
        self.endpoint_b = endpoint_b
37
        self._id = LinkID(endpoint_a.id, endpoint_b.id)
38
        super().__init__()
39
40
    def __hash__(self):
41
        return hash(self.id)
42
43
    def __repr__(self):
44
        return f"Link({self.endpoint_a!r}, {self.endpoint_b!r}, {self.id})"
45
46
    @classmethod
47
    def register_status_func(cls, name: str, func):
48
        """Register status func given its name and a callable at setup time."""
49
        cls.status_funcs[name] = func
50
51 View Code Duplication
    @property
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
52
    def status(self):
53
        """Return the current status of the Entity."""
54
        state = super().status
55
        if state == EntityStatus.DISABLED:
56
            return state
57
58
        for status_func in self.status_funcs.values():
59
            if status_func(self) == EntityStatus.DOWN:
60
                return EntityStatus.DOWN
61
        return state
62
63
    @classmethod
64
    def register_status_reason_func(cls, name: str, func):
65
        """Register status reason func given its name
66
        and a callable at setup time."""
67
        cls.status_reason_funcs[name] = func
68
69
    @property
70
    def status_reason(self):
71
        """Return the reason behind the current status of the entity."""
72
        return reduce(
73
            operator.or_,
74
            map(
75
                lambda x: x(self),
76
                self.status_reason_funcs.values()
77
            ),
78
            super().status_reason
79
        )
80
81
    def is_enabled(self):
82
        """Override the is_enabled method.
83
84
        We consider a link enabled when all the interfaces are enabled.
85
86
        Returns:
87
            boolean: True if both interfaces are enabled, False otherwise.
88
89
        """
90
        return (self._enabled and self.endpoint_a.is_enabled() and
91
                self.endpoint_b.is_enabled())
92
93
    def is_active(self):
94
        """Override the is_active method.
95
96
        We consider a link active whether all the interfaces are active.
97
98
        Returns:
99
            boolean: True if the interfaces are active, othewrise False.
100
101
        """
102
        return (self._active and self.endpoint_a.is_active() and
103
                self.endpoint_b.is_active())
104
105
    def __eq__(self, other):
106
        """Check if two instances of Link are equal."""
107
        return self.id == other.id
108
109
    @property
110
    def id(self):  # pylint: disable=invalid-name
111
        """Return id from Link intance.
112
113
        Returns:
114
            string: link id.
115
116
        """
117
        return self._id
118
119
    @property
120
    def available_tags(self):
121
        """Return the available tags for the link.
122
123
        Based on the endpoint tags.
124
        """
125
        return [tag for tag in self.endpoint_a.available_tags if tag in
126
                self.endpoint_b.available_tags]
127
128
    def use_tag(self, tag):
129
        """Remove a specific tag from available_tags if it is there.
130
131
        Deprecated: use only the get_next_available_tag method.
132
        """
133
        if self.is_tag_available(tag):
134
            self.endpoint_a.use_tag(tag)
135
            self.endpoint_b.use_tag(tag)
136
            return True
137
        return False
138
139
    def is_tag_available(self, tag):
140
        """Check if a tag is available."""
141
        return (self.endpoint_a.is_tag_available(tag) and
142
                self.endpoint_b.is_tag_available(tag))
143
144
    def get_next_available_tag(self):
145
        """Return the next available tag if exists."""
146
        with self._get_available_vlans_lock:
147
            # Copy the available tags because in case of error
148
            # we will remove and add elements to the available_tags
149
            available_tags_a = self.endpoint_a.available_tags.copy()
150
            available_tags_b = self.endpoint_b.available_tags.copy()
151
152
            for tag in available_tags_a:
153
                # Tag does not exist in endpoint B. Try another tag.
154
                if tag not in available_tags_b:
155
                    continue
156
157
                # Tag already in use. Try another tag.
158
                if not self.endpoint_a.use_tag(tag):
159
                    continue
160
161
                # Tag already in use in B. Mark the tag as available again.
162
                if not self.endpoint_b.use_tag(tag):
163
                    self.endpoint_a.make_tag_available(tag)
164
                    continue
165
166
                # Tag used successfully by both endpoints. Returning.
167
                return tag
168
169
            raise KytosNoTagAvailableError(self)
170
171
    def make_tag_available(self, tag):
172
        """Add a specific tag in available_tags."""
173
        if not self.is_tag_available(tag):
174
            self.endpoint_a.make_tag_available(tag)
175
            self.endpoint_b.make_tag_available(tag)
176
            return True
177
        return False
178
179
    def available_vlans(self):
180
        """Get all available vlans from each interface in the link."""
181
        vlans_a = self._get_available_vlans(self.endpoint_a)
182
        vlans_b = self._get_available_vlans(self.endpoint_b)
183
        return [vlan for vlan in vlans_a if vlan in vlans_b]
184
185
    @staticmethod
186
    def _get_available_vlans(endpoint):
187
        """Return all vlans from endpoint."""
188
        tags = endpoint.available_tags
189
        return [tag for tag in tags if tag.tag_type == TAGType.VLAN]
190
191
    def as_dict(self):
192
        """Return the Link as a dictionary."""
193
        return {
194
            'id': self.id,
195
            'endpoint_a': self.endpoint_a.as_dict(),
196
            'endpoint_b': self.endpoint_b.as_dict(),
197
            'metadata': self.get_metadata_as_dict(),
198
            'active': self.is_active(),
199
            'enabled': self.is_enabled(),
200
            'status': self.status.value,
201
            'status_reason': sorted(self.status_reason),
202
        }
203
204
    def as_json(self):
205
        """Return the Link as a JSON string."""
206
        return json.dumps(self.as_dict())
207
208
    @classmethod
209
    def from_dict(cls, link_dict):
210
        """Return a Link instance from python dictionary."""
211
        return cls(link_dict.get('endpoint_a'),
212
                   link_dict.get('endpoint_b'))
213