Passed
Push — master ( 2ba592...9b95a3 )
by Beraldo
02:20
created

UNI.is_valid()   A

Complexity

Conditions 2

Size

Total Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 3.6875

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
c 1
b 0
f 0
dl 0
loc 5
ccs 1
cts 4
cp 0.25
crap 3.6875
rs 9.4285
1
"""Module with main classes related to Interfaces."""
2 1
import json
3 1
import logging
4 1
from enum import IntEnum
5
6 1
from pyof.v0x01.common.phy_port import PortFeatures as PortFeatures01
7 1
from pyof.v0x04.common.port import PortFeatures as PortFeatures04
8
9 1
from kytos.core.common import GenericEntity
10 1
from kytos.core.helpers import now
11
12 1
__all__ = ('Interface',)
13
14 1
LOG = logging.getLogger(__name__)
15
16
17 1
class TAGType(IntEnum):
18
    """Class that represents a TAG Type."""
19
20 1
    VLAN = 1
21 1
    VLAN_QINQ = 2
22 1
    MPLS = 3
23
24
25 1
class TAG:
26
    """Class that represents a TAG."""
27
28 1
    def __init__(self, tag_type, value):
29 1
        self.tag_type = tag_type
30 1
        self.value = value
31
32 1
    def __eq__(self, other):
33
        return self.tag_type == other.tag_type and self.value == other.value
34
35
36 1
class Interface(GenericEntity):  # pylint: disable=too-many-instance-attributes
37
    """Interface Class used to abstract the network interfaces."""
38
39
    # pylint: disable=too-many-arguments
40 1
    def __init__(self, name, port_number, switch, address=None, state=None,
41
                 features=None, speed=None):
42
        """Assign the parameters to instance attributes.
43
44
        Args:
45
            name (string): name from this interface.
46
            port_number (int): port number from this interface.
47
            switch (:class:`~.core.switch.Switch`): Switch with this interface.
48
            address (|hw_address|): Port address from this interface.
49
            state (|port_stats|): Port Stat from interface. It will be
50
            deprecated.
51
            features (|port_features|): Port feature used to calculate link
52
                utilization from this interface. It will be deprecated.
53
            speed (int, float): Interface speed in bytes per second. Defaults
54
                to what is informed by the switch. Return ``None`` if not set
55
                and switch does not inform the speed.
56
        """
57 1
        self.name = name
58 1
        self.port_number = int(port_number)
59 1
        self.switch = switch
60 1
        self.address = address
61 1
        self.state = state
62 1
        self.features = features
63 1
        self.nni = False
64 1
        self.endpoints = []
65 1
        self.stats = None
66 1
        self.link = None
67 1
        self._custom_speed = speed
68 1
        self.available_tags = []
69
70 1
        for i in range(1, 4096):
71 1
            vlan = TAGType.VLAN
72 1
            tag = TAG(vlan, i)
73 1
            self.available_tags.append(tag)
74
75 1
        super().__init__()
76
77 1
    def __eq__(self, other):
78
        """Compare Interface class with another instance."""
79
        if isinstance(other, str):
80
            return self.address == other
81
        elif isinstance(other, Interface):
82
            return self.port_number == other.port_number and \
83
                self.name == other.name and \
84
                self.address == other.address and \
85
                self.switch.dpid == other.switch.dpid
86
        return False
87
88 1
    @property
89
    def id(self):  # pylint: disable=invalid-name
90
        """Return id from Interface intance.
91
92
        Returns:
93
            string: Interface id.
94
95
        """
96
        return "{}:{}".format(self.switch.dpid, self.port_number)
97
98 1
    @property
99
    def uni(self):
100
        """Return if an interface is a user-to-network Interface."""
101
        return not self.nni
102
103 1
    def enable(self):
104
        """Enable this interface instance.
105
106
        Also enable the switch instance this interface is attached to.
107
        """
108
        self.switch.enable()
109
        self.enabled = True
110
111 1
    def disable(self):
112
        """Disable this interface instance.
113
114
        Also disable the link related to this interface.
115
        """
116
        if self.link:
117
            self.link.disable()
118
        self.enabled = False
119
120 1
    def use_tag(self, tag):
121
        """Remove a specific tag from available_tags if it is there."""
122
        for available_tag in self.available_tags:
123
            if tag == available_tag:
124
                self.available_tags.remove(available_tag)
125
                return True
126
        return False
127
128 1
    def is_tag_available(self, tag):
129
        """Check if a tag is available."""
130
        return tag in self.available_tags
131
132 1
    def get_next_available_tag(self):
133
        """Return the next available tag if exists."""
134
        try:
135
            return self.available_tags.pop()
136
        except IndexError:
137
            return False
138
139 1
    def make_tag_available(self, tag):
140
        """Add a specific tag in available_tags."""
141
        if not self.is_tag_available(tag):
142
            self.available_tags.append(tag)
143
        else:
144
            return False
145
146 1
    def get_endpoint(self, endpoint):
147
        """Return a tuple with existent endpoint, None otherwise.
148
149
        Args:
150
            endpoint(|hw_address|, :class:`.Interface`): endpoint instance.
151
152
        Returns:
153
            tuple: A tuple with endpoint and time of last update.
154
155
        """
156
        for item in self.endpoints:
157
            if endpoint == item[0]:
158
                return item
159
        return None
160
161 1
    def add_endpoint(self, endpoint):
162
        """Create a new endpoint to Interface instance.
163
164
        Args:
165
            endpoint(|hw_address|, :class:`.Interface`): A target endpoint.
166
        """
167
        exists = self.get_endpoint(endpoint)
168
        if not exists:
169
            self.endpoints.append((endpoint, now()))
170
171 1
    def delete_endpoint(self, endpoint):
172
        """Delete a existent endpoint in Interface instance.
173
174
        Args:
175
            endpoint (|hw_address|, :class:`.Interface`): A target endpoint.
176
        """
177
        exists = self.get_endpoint(endpoint)
178
        if exists:
179
            self.endpoints.remove(exists)
180
181 1
    def update_endpoint(self, endpoint):
182
        """Update or create new endpoint to Interface instance.
183
184
        Args:
185
            endpoint(|hw_address|, :class:`.Interface`): A target endpoint.
186
        """
187
        exists = self.get_endpoint(endpoint)
188
        if exists:
189
            self.delete_endpoint(endpoint)
190
        self.add_endpoint(endpoint)
191
192 1
    def update_link(self, link):
193
        """Update link for this interface in a consistent way.
194
195
        Verify of the other endpoint of the link has the same Link information
196
        attached to it, and change it if necessary.
197
198
        Warning: This method can potentially change information of other
199
        Interface instances. Use it with caution.
200
        """
201
        if (link.endpoint_a != self and
202
                link.endpoint_b != self):
203
            return False
204
205
        if self.link is None or self.link != link:
206
            self.link = link
207
208
        if link.endpoint_a == self:
209
            endpoint = link.endpoint_b
210
        else:
211
            endpoint = link.endpoint_a
212
213
        if endpoint.link is None or endpoint.link != link:
214
            endpoint.link = link
215
216 1
    @property
217
    def speed(self):
218
        """Return the link speed in bytes per second, None otherwise.
219
220
        If the switch was disconnected, we have :attr:`features` and speed is
221
        still returned for common values between v0x01 and v0x04. For specific
222
        v0x04 values (40 Gbps, 100 Gbps and 1 Tbps), the connection must be
223
        active so we can make sure the protocol version is v0x04.
224
225
        Returns:
226
            int, None: Link speed in bytes per second or ``None``.
227
228
        """
229 1
        if self._custom_speed is not None:
230 1
            return self._custom_speed
231 1
        return self.get_of_features_speed()
232
233 1
    def set_custom_speed(self, bytes_per_second):
234
        """Set a speed that overrides switch OpenFlow information.
235
236
        If ``None`` is given, :attr:`speed` becomes the one given by the
237
        switch.
238
        """
239 1
        self._custom_speed = bytes_per_second
240
241 1
    def get_custom_speed(self):
242
        """Return custom speed or ``None`` if not set."""
243
        return self._custom_speed
244
245 1
    def get_of_features_speed(self):
246
        """Return the link speed in bytes per second, None otherwise.
247
248
        If the switch was disconnected, we have :attr:`features` and speed is
249
        still returned for common values between v0x01 and v0x04. For specific
250
        v0x04 values (40 Gbps, 100 Gbps and 1 Tbps), the connection must be
251
        active so we can make sure the protocol version is v0x04.
252
253
        Returns:
254
            int, None: Link speed in bytes per second or ``None``.
255
256
        """
257 1
        speed = self._get_v0x01_v0x04_speed()
258
        # Don't use switch.is_connected() because we can have the protocol
259 1
        if speed is None and self._is_v0x04():
260 1
            speed = self._get_v0x04_speed()
261 1
        if speed is not None:
262 1
            return speed
263
        # Warn unknown speed
264
        # Use shorter switch ID with its beginning and end
265 1
        if isinstance(self.switch.id, str) and len(self.switch.id) > 20:
266
            switch_id = self.switch.id[:3] + '...' + self.switch.id[-3:]
267
        else:
268 1
            switch_id = self.switch.id
269 1
        LOG.warning("Couldn't get port %s speed, sw %s, feats %s",
270
                    self.port_number, switch_id, self.features)
271
272 1
    def _is_v0x04(self):
273
        """Whether the switch is connected using OpenFlow 1.3."""
274 1
        return self.switch.is_connected() and \
275
            self.switch.connection.protocol.version == 0x04
276
277 1
    def _get_v0x01_v0x04_speed(self):
278
        """Check against all values of v0x01. They're part of v0x04."""
279 1
        fts = self.features
280 1
        pfts = PortFeatures01
281 1
        if fts and fts & pfts.OFPPF_10GB_FD:
282 1
            return 10 * 10**9 / 8
283 1
        elif fts and fts & (pfts.OFPPF_1GB_HD | pfts.OFPPF_1GB_FD):
284 1
            return 10**9 / 8
285 1
        elif fts and fts & (pfts.OFPPF_100MB_HD | pfts.OFPPF_100MB_FD):
286 1
            return 100 * 10**6 / 8
287 1
        elif fts and fts & (pfts.OFPPF_10MB_HD | pfts.OFPPF_10MB_FD):
288 1
            return 10 * 10**6 / 8
289
290 1
    def _get_v0x04_speed(self):
291
        """Check against higher enums of v0x04.
292
293
        Must be called after :meth:`get_v0x01_speed` returns ``None``.
294
        """
295 1
        fts = self.features
296 1
        pfts = PortFeatures04
297 1
        if fts and fts & pfts.OFPPF_1TB_FD:
298 1
            return 10**12 / 8
299 1
        elif fts and fts & pfts.OFPPF_100GB_FD:
300 1
            return 100 * 10**9 / 8
301 1
        elif fts and fts & pfts.OFPPF_40GB_FD:
302 1
            return 40 * 10**9 / 8
303
304 1
    def get_hr_speed(self):
305
        """Return Human-Readable string for link speed.
306
307
        Returns:
308
            string: String with link speed. e.g: '350 Gbps' or '350 Mbps'.
309
310
        """
311 1
        speed = self.speed
312 1
        if speed is None:
313 1
            return ''
314 1
        speed *= 8
315 1
        if speed == 10**12:
316 1
            return '1 Tbps'
317 1
        if speed >= 10**9:
318 1
            return '{} Gbps'.format(round(speed / 10**9))
319 1
        return '{} Mbps'.format(round(speed / 10**6))
320
321 1
    def as_dict(self):
322
        """Return a dictionary with Interface attributes.
323
324
        Speed is in bytes/sec. Example of output (100 Gbps):
325
326
        .. code-block:: python3
327
328
            {'id': '00:00:00:00:00:00:00:01:2',
329
             'name': 'eth01',
330
             'port_number': 2,
331
             'mac': '00:7e:04:3b:c2:a6',
332
             'switch': '00:00:00:00:00:00:00:01',
333
             'type': 'interface',
334
             'nni': False,
335
             'uni': True,
336
             'speed': 12500000000,
337
             'metadata': {},
338
             'active': True,
339
             'enabled': False,
340
             'link': ""
341
            }
342
343
        Returns:
344
            dict: Dictionary filled with interface attributes.
345
346
        """
347
        iface_dict = {'id': self.id,
348
                      'name': self.name,
349
                      'port_number': self.port_number,
350
                      'mac': self.address,
351
                      'switch': self.switch.dpid,
352
                      'type': 'interface',
353
                      'nni': self.nni,
354
                      'uni': self.uni,
355
                      'speed': self.speed,
356
                      'metadata': self.metadata,
357
                      'active': self.active,
358
                      'enabled': self.enabled,
359
                      'link': self.link.id if self.link else ""}
360
        if self.stats:
361
            iface_dict['stats'] = self.stats.as_dict()
362
        return iface_dict
363
364 1
    def as_json(self):
365
        """Return a json with Interfaces attributes.
366
367
        Example of output:
368
369
        .. code-block:: json
370
371
            {"mac": "00:7e:04:3b:c2:a6",
372
             "switch": "00:00:00:00:00:00:00:01",
373
             "type": "interface",
374
             "name": "eth01",
375
             "id": "00:00:00:00:00:00:00:01:2",
376
             "port_number": 2,
377
             "speed": "350 Mbps"}
378
379
        Returns:
380
            string: Json filled with interface attributes.
381
382
        """
383
        return json.dumps(self.as_dict())
384
385
386 1
class UNI:
387
    """Class that represents an User-to-Network Interface."""
388
389 1
    def __init__(self, interface, user_tag=None):
390
        self.user_tag = user_tag
391
        self.interface = interface
392
393 1
    def is_valid(self):
394
        """Check if TAG is possible for this interface TAG pool."""
395
        if self.user_tag:
396
            return self.interface.is_tag_available(self.user_tag)
397
        return True
398
399
400 1
class NNI:
401
    """Class that represents an Network-to-Network Interface."""
402
403 1
    def __init__(self, interface):
404
        self.interface = interface
405
406
407 1
class VNNI(NNI):
408
    """Class that represents an Virtual Network-to-Network Interface."""
409
410 1
    def __init__(self, service_tag, *args, **kwargs):
411
        self.service_tag = service_tag
412
413
        super().__init__(*args, **kwargs)
414