Test Failed
Pull Request — master (#1152)
by Humberto
03:59
created

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

Complexity

Conditions 5

Size

Total Lines 27
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 5.4742

Importance

Changes 0
Metric Value
cc 5
eloc 15
nop 1
dl 0
loc 27
ccs 11
cts 15
cp 0.7332
crap 5.4742
rs 9.1832
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 (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 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 1
        return (self._enabled and self.endpoint_a.is_enabled() and
46
                self.endpoint_b.is_enabled())
47
48 1
    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 1
        return (self._active and self.endpoint_a.is_active() and
58
                self.endpoint_b.is_active())
59
60 1
    def __eq__(self, other):
61
        """Check if two instances of Link are equal."""
62 1
        return self.id == other.id
63
64 1
    @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 1
        dpid_a = self.endpoint_a.switch.dpid
73 1
        port_a = self.endpoint_a.port_number
74 1
        dpid_b = self.endpoint_b.switch.dpid
75 1
        port_b = self.endpoint_b.port_number
76 1
        if dpid_a < dpid_b:
77 1
            elements = (dpid_a, port_a, dpid_b, port_b)
78 1
        elif dpid_a > dpid_b:
79 1
            elements = (dpid_b, port_b, dpid_a, port_a)
80 1
        elif port_a < port_b:
81 1
            elements = (dpid_a, port_a, dpid_b, port_b)
82
        else:
83 1
            elements = (dpid_b, port_b, dpid_a, port_a)
84
85 1
        str_id = "%s:%s:%s:%s" % elements
86 1
        return hashlib.sha256(str_id.encode('utf-8')).hexdigest()
87
88 1
    @property
89
    def available_tags(self):
90
        """Return the available tags for the link.
91
92
        Based on the endpoint tags.
93
        """
94 1
        return [tag for tag in self.endpoint_a.available_tags if tag in
95
                self.endpoint_b.available_tags]
96
97 1
    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 1
        if self.is_tag_available(tag):
103 1
            self.endpoint_a.use_tag(tag)
104 1
            self.endpoint_b.use_tag(tag)
105 1
            return True
106 1
        return False
107
108 1
    def is_tag_available(self, tag):
109
        """Check if a tag is available."""
110 1
        return (self.endpoint_a.is_tag_available(tag) and
111
                self.endpoint_b.is_tag_available(tag))
112
113 1
    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 1
        available_tags_a = self.endpoint_a.available_tags.copy()
118 1
        available_tags_b = self.endpoint_b.available_tags.copy()
119 1
        random.shuffle(available_tags_a)
120 1
        random.shuffle(available_tags_b)
121
122 1
        for tag in available_tags_a:
123
            # Tag does not exist in endpoint B. Try another tag.
124 1
            if tag not in available_tags_b:
125
                continue
126
127
            # Tag already in use. Try another tag.
128 1
            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 1
            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 1
            return tag
138
139
        raise KytosNoTagAvailableError(self)
140
141 1
    def make_tag_available(self, tag):
142
        """Add a specific tag in available_tags."""
143 1
        if not self.is_tag_available(tag):
144 1
            self.endpoint_a.make_tag_available(tag)
145 1
            self.endpoint_b.make_tag_available(tag)
146 1
            return True
147 1
        return False
148
149 1
    def available_vlans(self):
150
        """Get all available vlans from each interface in the link."""
151 1
        vlans_a = self._get_available_vlans(self.endpoint_a)
152 1
        vlans_b = self._get_available_vlans(self.endpoint_b)
153 1
        return [vlan for vlan in vlans_a if vlan in vlans_b]
154
155 1
    @staticmethod
156
    def _get_available_vlans(endpoint):
157
        """Return all vlans from endpoint."""
158 1
        tags = endpoint.available_tags
159 1
        return [tag for tag in tags if tag.tag_type == TAGType.VLAN]
160
161 1
    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 1
    def as_json(self):
171
        """Return the Link as a JSON string."""
172
        return json.dumps(self.as_dict())
173