Passed
Push — master ( cf72e6...2ad18d )
by Humberto
06:56 queued 03:57
created

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

Complexity

Conditions 3

Size

Total Lines 12
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 3

Importance

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