Passed
Pull Request — master (#242)
by Vinicius
19:40 queued 13:54
created

kytos.core.link.Link.__hash__()   A

Complexity

Conditions 1

Size

Total Lines 2
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1.125

Importance

Changes 0
Metric Value
cc 1
eloc 2
nop 1
dl 0
loc 2
rs 10
c 0
b 0
f 0
ccs 1
cts 2
cp 0.5
crap 1.125
1
"""Module with all classes related to links.
2
3
Links are low level abstractions representing connections between two
4
interfaces.
5
"""
6
7 1
import hashlib
8 1
import json
9 1
import random
10
11 1
from kytos.core.common import EntityStatus, GenericEntity
12 1
from kytos.core.exceptions import (KytosLinkCreationError,
13
                                   KytosNoTagAvailableError)
14 1
from kytos.core.interface import TAGType
15
16
17 1
class Link(GenericEntity):
18
    """Define a link between two Endpoints."""
19
20 1
    def __init__(self, endpoint_a, endpoint_b):
21
        """Create a Link instance and set its attributes.
22
23
        Two kytos.core.interface.Interface are required as parameters.
24
        """
25 1
        if endpoint_a is None:
26 1
            raise KytosLinkCreationError("endpoint_a cannot be None")
27 1
        if endpoint_b is None:
28 1
            raise KytosLinkCreationError("endpoint_b cannot be None")
29 1
        self.endpoint_a = endpoint_a
30 1
        self.endpoint_b = endpoint_b
31 1
        super().__init__()
32
33 1
    def __hash__(self):
34
        return hash(self.id)
35
36 1
    def __repr__(self):
37 1
        return f"Link({self.endpoint_a!r}, {self.endpoint_b!r})"
38
39 1
    @property
40 1
    def status(self):
41
        """Return the current status of the Entity."""
42 1
        state = super().status
43 1
        if (
44
            state == EntityStatus.UP
45
            and "liveness_status" in self.metadata
46
            and self.metadata["liveness_status"] != "up"
47
        ):
48 1
            return EntityStatus.DOWN
49 1
        return state
50
51 1
    def is_enabled(self):
52
        """Override the is_enabled method.
53
54
        We consider a link enabled when all the interfaces are enabled.
55
56
        Returns:
57
            boolean: True if both interfaces are enabled, False otherwise.
58
59
        """
60 1
        return (self._enabled and self.endpoint_a.is_enabled() and
61
                self.endpoint_b.is_enabled())
62
63 1
    def is_active(self):
64
        """Override the is_active method.
65
66
        We consider a link active whether all the interfaces are active.
67
68
        Returns:
69
            boolean: True if the interfaces are active, othewrise False.
70
71
        """
72 1
        return (self._active and self.endpoint_a.is_active() and
73
                self.endpoint_b.is_active())
74
75 1
    def __eq__(self, other):
76
        """Check if two instances of Link are equal."""
77 1
        return self.id == other.id
78
79 1
    @property
80 1
    def id(self):  # pylint: disable=invalid-name
81
        """Return id from Link intance.
82
83
        Returns:
84
            string: link id.
85
86
        """
87 1
        dpid_a = self.endpoint_a.switch.dpid
88 1
        port_a = self.endpoint_a.port_number
89 1
        dpid_b = self.endpoint_b.switch.dpid
90 1
        port_b = self.endpoint_b.port_number
91 1
        if dpid_a < dpid_b:
92 1
            elements = (dpid_a, port_a, dpid_b, port_b)
93 1
        elif dpid_a > dpid_b:
94 1
            elements = (dpid_b, port_b, dpid_a, port_a)
95 1
        elif port_a < port_b:
96 1
            elements = (dpid_a, port_a, dpid_b, port_b)
97
        else:
98 1
            elements = (dpid_b, port_b, dpid_a, port_a)
99
100 1
        str_id = "%s:%s:%s:%s" % elements
101 1
        return hashlib.sha256(str_id.encode('utf-8')).hexdigest()
102
103 1
    @property
104 1
    def available_tags(self):
105
        """Return the available tags for the link.
106
107
        Based on the endpoint tags.
108
        """
109 1
        return [tag for tag in self.endpoint_a.available_tags if tag in
110
                self.endpoint_b.available_tags]
111
112 1
    def use_tag(self, tag):
113
        """Remove a specific tag from available_tags if it is there.
114
115
        Deprecated: use only the get_next_available_tag method.
116
        """
117 1
        if self.is_tag_available(tag):
118 1
            self.endpoint_a.use_tag(tag)
119 1
            self.endpoint_b.use_tag(tag)
120 1
            return True
121 1
        return False
122
123 1
    def is_tag_available(self, tag):
124
        """Check if a tag is available."""
125 1
        return (self.endpoint_a.is_tag_available(tag) and
126
                self.endpoint_b.is_tag_available(tag))
127
128 1
    def get_next_available_tag(self):
129
        """Return the next available tag if exists."""
130
        # Copy the available tags because in case of error
131
        # we will remove and add elements to the available_tags
132 1
        available_tags_a = self.endpoint_a.available_tags.copy()
133 1
        available_tags_b = self.endpoint_b.available_tags.copy()
134 1
        random.shuffle(available_tags_a)
135 1
        random.shuffle(available_tags_b)
136
137 1
        for tag in available_tags_a:
138
            # Tag does not exist in endpoint B. Try another tag.
139 1
            if tag not in available_tags_b:
140
                continue
141
142
            # Tag already in use. Try another tag.
143 1
            if not self.endpoint_a.use_tag(tag):
144
                continue
145
146
            # Tag already in use in B. Mark the tag as available again.
147 1
            if not self.endpoint_b.use_tag(tag):
148
                self.endpoint_a.make_tag_available(tag)
149
                continue
150
151
            # Tag used successfully by both endpoints. Returning.
152 1
            return tag
153
154
        raise KytosNoTagAvailableError(self)
155
156 1
    def make_tag_available(self, tag):
157
        """Add a specific tag in available_tags."""
158 1
        if not self.is_tag_available(tag):
159 1
            self.endpoint_a.make_tag_available(tag)
160 1
            self.endpoint_b.make_tag_available(tag)
161 1
            return True
162 1
        return False
163
164 1
    def available_vlans(self):
165
        """Get all available vlans from each interface in the link."""
166 1
        vlans_a = self._get_available_vlans(self.endpoint_a)
167 1
        vlans_b = self._get_available_vlans(self.endpoint_b)
168 1
        return [vlan for vlan in vlans_a if vlan in vlans_b]
169
170 1
    @staticmethod
171 1
    def _get_available_vlans(endpoint):
172
        """Return all vlans from endpoint."""
173 1
        tags = endpoint.available_tags
174 1
        return [tag for tag in tags if tag.tag_type == TAGType.VLAN]
175
176 1
    def as_dict(self):
177
        """Return the Link as a dictionary."""
178
        return {'id': self.id,
179
                'endpoint_a': self.endpoint_a.as_dict(),
180
                'endpoint_b': self.endpoint_b.as_dict(),
181
                'metadata': self.get_metadata_as_dict(),
182
                'active': self.is_active(),
183
                'enabled': self.is_enabled()}
184
185 1
    def as_json(self):
186
        """Return the Link as a JSON string."""
187
        return json.dumps(self.as_dict())
188
189 1
    @classmethod
190 1
    def from_dict(cls, link_dict):
191
        """Return a Link instance from python dictionary."""
192
        return cls(link_dict.get('endpoint_a'),
193
                   link_dict.get('endpoint_b'))
194