Passed
Pull Request — master (#134)
by Italo Valcy
07:46 queued 43s
created

kytos.core.interface   F

Complexity

Total Complexity 96

Size/Duplication

Total Lines 511
Duplicated Lines 0 %

Test Coverage

Coverage 91.63%

Importance

Changes 0
Metric Value
eloc 250
dl 0
loc 511
ccs 197
cts 215
cp 0.9163
rs 2
c 0
b 0
f 0
wmc 96

42 Methods

Rating   Name   Duplication   Size   Complexity  
A TAG.from_dict() 0 4 1
A TAG.__repr__() 0 2 1
A Interface.id() 0 9 1
A Interface.__init__() 0 39 1
A Interface.set_available_tags() 0 13 3
A Interface.uni() 0 4 1
A Interface.__repr__() 0 2 1
A TAG.__init__() 0 3 1
A Interface.__eq__() 0 8 3
A TAG.from_json() 0 4 1
A TAG.__eq__() 0 4 2
A Interface.enable() 0 7 1
A TAG.as_json() 0 3 1
A Interface.use_tag() 0 11 3
A TAG.as_dict() 0 3 1
A Interface.delete_endpoint() 0 9 2
B Interface._get_v0x04_speed() 0 14 7
A Interface.add_endpoint() 0 9 2
C Interface._get_v0x01_v0x04_speed() 0 13 9
A Interface.make_tag_available() 0 7 3
A Interface.get_endpoint() 0 14 3
A Interface._is_v0x04() 0 4 1
A UNI.__eq__() 0 4 1
A Interface.is_tag_available() 0 4 2
B Interface.update_link() 0 24 7
A Interface.as_dict() 0 44 3
A Interface.as_json() 0 20 1
A Interface.get_next_available_tag() 0 12 3
A UNI.from_dict() 0 5 1
A Interface.set_custom_speed() 0 7 1
A NNI.__init__() 0 2 1
C Interface.speed() 0 37 9
A VNNI.__init__() 0 4 1
A Interface.get_hr_speed() 0 16 4
A UNI.is_valid() 0 5 2
A Interface.get_custom_speed() 0 3 1
A UNI.as_json() 0 3 1
A Interface.get_of_features_speed() 0 17 3
A UNI.__init__() 0 3 1
A Interface.update_endpoint() 0 10 2
A Interface.from_dict() 0 10 1
A UNI.as_dict() 0 5 2

How to fix   Complexity   

Complexity

Complex classes like kytos.core.interface often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

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