Passed
Push — master ( f8912e...f21663 )
by Vinicius
06:13 queued 12s
created

kytos.core.link   A

Complexity

Total Complexity 27

Size/Duplication

Total Lines 179
Duplicated Lines 0 %

Test Coverage

Coverage 90.12%

Importance

Changes 0
Metric Value
eloc 97
dl 0
loc 179
ccs 73
cts 81
cp 0.9012
rs 10
c 0
b 0
f 0
wmc 27

16 Methods

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