Passed
Pull Request — master (#134)
by Italo Valcy
36:46 queued 29:57
created

kytos.core.interface   F

Complexity

Total Complexity 95

Size/Duplication

Total Lines 510
Duplicated Lines 0 %

Test Coverage

Coverage 91.59%

Importance

Changes 0
Metric Value
eloc 249
dl 0
loc 510
ccs 196
cts 214
cp 0.9159
rs 2
c 0
b 0
f 0
wmc 95

42 Methods

Rating   Name   Duplication   Size   Complexity  
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 TAG.from_dict() 0 4 1
A TAG.__repr__() 0 2 1
A Interface._is_v0x04() 0 4 1
A Interface.id() 0 9 1
A Interface.__init__() 0 39 1
A UNI.__eq__() 0 4 1
A Interface.is_tag_available() 0 3 1
A Interface.set_available_tags() 0 13 3
B Interface.update_link() 0 24 7
A Interface.uni() 0 4 1
A Interface.__repr__() 0 2 1
A Interface.as_dict() 0 44 3
A Interface.as_json() 0 20 1
A TAG.__init__() 0 3 1
A Interface.get_next_available_tag() 0 12 3
A Interface.__eq__() 0 8 3
A TAG.from_json() 0 4 1
A TAG.__eq__() 0 4 2
A UNI.from_dict() 0 5 1
A Interface.set_custom_speed() 0 7 1
A NNI.__init__() 0 2 1
A Interface.enable() 0 7 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 TAG.as_json() 0 3 1
A UNI.as_json() 0 3 1
A Interface.get_of_features_speed() 0 17 3
A Interface.use_tag() 0 11 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
A TAG.as_dict() 0 3 1

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
        return tag in self.available_tags
170
171 2
    def get_next_available_tag(self):
172
        """Get the next available tag from the interface.
173
174
        Return the next available tag if exists and remove from the
175
        available tags.
176
        If no tag is available return False.
177
        """
178 2
        try:
179 2
            with self._tag_lock:
180 2
                return self.available_tags.pop()
181 2
        except IndexError:
182 2
            return False
183
184 2
    def make_tag_available(self, tag):
185
        """Add a specific tag in available_tags."""
186 2
        if not self.is_tag_available(tag):
187 2
            with self._tag_lock:
188 2
                self.available_tags.append(tag)
189 2
            return True
190
        return False
191
192 2
    def get_endpoint(self, endpoint):
193
        """Return a tuple with existent endpoint, None otherwise.
194
195
        Args:
196
            endpoint(|hw_address|, :class:`.Interface`): endpoint instance.
197
198
        Returns:
199
            tuple: A tuple with endpoint and time of last update.
200
201
        """
202 2
        for item in self.endpoints:
203 2
            if endpoint == item[0]:
204 2
                return item
205 2
        return None
206
207 2
    def add_endpoint(self, endpoint):
208
        """Create a new endpoint to Interface instance.
209
210
        Args:
211
            endpoint(|hw_address|, :class:`.Interface`): A target endpoint.
212
        """
213 2
        exists = self.get_endpoint(endpoint)
214 2
        if not exists:
215 2
            self.endpoints.append((endpoint, now()))
216
217 2
    def delete_endpoint(self, endpoint):
218
        """Delete a existent endpoint in Interface instance.
219
220
        Args:
221
            endpoint (|hw_address|, :class:`.Interface`): A target endpoint.
222
        """
223 2
        exists = self.get_endpoint(endpoint)
224 2
        if exists:
225 2
            self.endpoints.remove(exists)
226
227 2
    def update_endpoint(self, endpoint):
228
        """Update or create new endpoint to Interface instance.
229
230
        Args:
231
            endpoint(|hw_address|, :class:`.Interface`): A target endpoint.
232
        """
233 2
        exists = self.get_endpoint(endpoint)
234 2
        if exists:
235 2
            self.delete_endpoint(endpoint)
236 2
        self.add_endpoint(endpoint)
237
238 2
    def update_link(self, link):
239
        """Update link for this interface in a consistent way.
240
241
        Verify of the other endpoint of the link has the same Link information
242
        attached to it, and change it if necessary.
243
244
        Warning: This method can potentially change information of other
245
        Interface instances. Use it with caution.
246
        """
247 2
        if self not in (link.endpoint_a, link.endpoint_b):
248 2
            return False
249
250 2
        if self.link is None or self.link != link:
251 2
            self.link = link
252
253 2
        if link.endpoint_a == self:
254 2
            endpoint = link.endpoint_b
255
        else:
256 2
            endpoint = link.endpoint_a
257
258 2
        if endpoint.link is None or endpoint.link != link:
259 2
            endpoint.link = link
260
261 2
        return True
262
263 2
    @property
264
    def speed(self):
265
        """Return the link speed in bytes per second, None otherwise.
266
267
        If the switch was disconnected, we have :attr:`features` and speed is
268
        still returned for common values between v0x01 and v0x04. For specific
269
        v0x04 values (40 Gbps, 100 Gbps and 1 Tbps), the connection must be
270
        active so we can make sure the protocol version is v0x04.
271
272
        Returns:
273
            int, None: Link speed in bytes per second or ``None``.
274
275
        """
276 2
        speed = self.get_of_features_speed()
277
278 2
        if speed is not None:
279 2
            return speed
280
281 2
        if self._custom_speed is not None:
282 2
            return self._custom_speed
283
284 2
        if self._is_v0x04() and self.port_number == PortNo04.OFPP_LOCAL:
285
            return 0
286
287 2
        if not self._is_v0x04() and self.port_number == PortNo01.OFPP_LOCAL:
288
            return 0
289
290
        # Warn unknown speed
291
        # Use shorter switch ID with its beginning and end
292 2
        if isinstance(self.switch.id, str) and len(self.switch.id) > 20:
293
            switch_id = self.switch.id[:3] + '...' + self.switch.id[-3:]
294
        else:
295 2
            switch_id = self.switch.id
296 2
        LOG.warning("Couldn't get port %s speed, sw %s, feats %s",
297
                    self.port_number, switch_id, self.features)
298
299 2
        return None
300
301 2
    def set_custom_speed(self, bytes_per_second):
302
        """Set a speed that overrides switch OpenFlow information.
303
304
        If ``None`` is given, :attr:`speed` becomes the one given by the
305
        switch.
306
        """
307 2
        self._custom_speed = bytes_per_second
308
309 2
    def get_custom_speed(self):
310
        """Return custom speed or ``None`` if not set."""
311
        return self._custom_speed
312
313 2
    def get_of_features_speed(self):
314
        """Return the link speed in bytes per second, None otherwise.
315
316
        If the switch was disconnected, we have :attr:`features` and speed is
317
        still returned for common values between v0x01 and v0x04. For specific
318
        v0x04 values (40 Gbps, 100 Gbps and 1 Tbps), the connection must be
319
        active so we can make sure the protocol version is v0x04.
320
321
        Returns:
322
            int, None: Link speed in bytes per second or ``None``.
323
324
        """
325 2
        speed = self._get_v0x01_v0x04_speed()
326
        # Don't use switch.is_connected() because we can have the protocol
327 2
        if speed is None and self._is_v0x04():
328 2
            speed = self._get_v0x04_speed()
329 2
        return speed
330
331 2
    def _is_v0x04(self):
332
        """Whether the switch is connected using OpenFlow 1.3."""
333 2
        return self.switch.is_connected() and \
334
            self.switch.connection.protocol.version == 0x04
335
336 2
    def _get_v0x01_v0x04_speed(self):
337
        """Check against all values of v0x01. They're part of v0x04."""
338 2
        fts = self.features
339 2
        pfts = PortFeatures01
340 2
        if fts and fts & pfts.OFPPF_10GB_FD:
341 2
            return 10 * 10**9 / 8
342 2
        if fts and fts & (pfts.OFPPF_1GB_HD | pfts.OFPPF_1GB_FD):
343 2
            return 10**9 / 8
344 2
        if fts and fts & (pfts.OFPPF_100MB_HD | pfts.OFPPF_100MB_FD):
345 2
            return 100 * 10**6 / 8
346 2
        if fts and fts & (pfts.OFPPF_10MB_HD | pfts.OFPPF_10MB_FD):
347 2
            return 10 * 10**6 / 8
348 2
        return None
349
350 2
    def _get_v0x04_speed(self):
351
        """Check against higher enums of v0x04.
352
353
        Must be called after :meth:`get_v0x01_speed` returns ``None``.
354
        """
355 2
        fts = self.features
356 2
        pfts = PortFeatures04
357 2
        if fts and fts & pfts.OFPPF_1TB_FD:
358 2
            return 10**12 / 8
359 2
        if fts and fts & pfts.OFPPF_100GB_FD:
360 2
            return 100 * 10**9 / 8
361 2
        if fts and fts & pfts.OFPPF_40GB_FD:
362 2
            return 40 * 10**9 / 8
363 2
        return None
364
365 2
    def get_hr_speed(self):
366
        """Return Human-Readable string for link speed.
367
368
        Returns:
369
            string: String with link speed. e.g: '350 Gbps' or '350 Mbps'.
370
371
        """
372 2
        speed = self.speed
373 2
        if speed is None:
374 2
            return ''
375 2
        speed *= 8
376 2
        if speed == 10**12:
377 2
            return '1 Tbps'
378 2
        if speed >= 10**9:
379 2
            return '{} Gbps'.format(round(speed / 10**9))
380 2
        return '{} Mbps'.format(round(speed / 10**6))
381
382 2
    def as_dict(self):
383
        """Return a dictionary with Interface attributes.
384
385
        Speed is in bytes/sec. Example of output (100 Gbps):
386
387
        .. code-block:: python3
388
389
            {'id': '00:00:00:00:00:00:00:01:2',
390
             'name': 'eth01',
391
             'port_number': 2,
392
             'mac': '00:7e:04:3b:c2:a6',
393
             'switch': '00:00:00:00:00:00:00:01',
394
             'type': 'interface',
395
             'nni': False,
396
             'uni': True,
397
             'speed': 12500000000,
398
             'metadata': {},
399
             'lldp': True,
400
             'active': True,
401
             'enabled': False,
402
             'link': ""
403
            }
404
405
        Returns:
406
            dict: Dictionary filled with interface attributes.
407
408
        """
409
        iface_dict = {'id': self.id,
410
                      'name': self.name,
411
                      'port_number': self.port_number,
412
                      'mac': self.address,
413
                      'switch': self.switch.dpid,
414
                      'type': 'interface',
415
                      'nni': self.nni,
416
                      'uni': self.uni,
417
                      'speed': self.speed,
418
                      'metadata': self.metadata,
419
                      'lldp': self.lldp,
420
                      'active': self.is_active(),
421
                      'enabled': self.is_enabled(),
422
                      'link': self.link.id if self.link else ""}
423
        if self.stats:
424
            iface_dict['stats'] = self.stats.as_dict()
425
        return iface_dict
426
427 2
    @classmethod
428
    def from_dict(cls, interface_dict):
429
        """Return a Interface instance from python dictionary."""
430
        return cls(interface_dict.get('name'),
431
                   interface_dict.get('port_number'),
432
                   interface_dict.get('switch'),
433
                   interface_dict.get('address'),
434
                   interface_dict.get('state'),
435
                   interface_dict.get('features'),
436
                   interface_dict.get('speed'))
437
438 2
    def as_json(self):
439
        """Return a json with Interfaces attributes.
440
441
        Example of output:
442
443
        .. code-block:: json
444
445
            {"mac": "00:7e:04:3b:c2:a6",
446
             "switch": "00:00:00:00:00:00:00:01",
447
             "type": "interface",
448
             "name": "eth01",
449
             "id": "00:00:00:00:00:00:00:01:2",
450
             "port_number": 2,
451
             "speed": "350 Mbps"}
452
453
        Returns:
454
            string: Json filled with interface attributes.
455
456
        """
457
        return json.dumps(self.as_dict())
458
459
460 2
class UNI:
461
    """Class that represents an User-to-Network Interface."""
462
463 2
    def __init__(self, interface, user_tag=None):
464 2
        self.user_tag = user_tag
465 2
        self.interface = interface
466
467 2
    def __eq__(self, other):
468
        """Override the default implementation."""
469 2
        return (self.user_tag == other.user_tag and
470
                self.interface == other.interface)
471
472 2
    def is_valid(self):
473
        """Check if TAG is possible for this interface TAG pool."""
474 2
        if self.user_tag:
475 2
            return self.interface.is_tag_available(self.user_tag)
476 2
        return True
477
478 2
    def as_dict(self):
479
        """Return a dict representating a UNI object."""
480 2
        return {
481
            'interface_id': self.interface.id,
482
            'tag': self.user_tag.as_dict() if self.user_tag else None
483
            }
484
485 2
    @classmethod
486
    def from_dict(cls, uni):
487
        """Return a Uni instance from python dictionary."""
488
        return cls(uni.get('interface'),
489
                   uni.get('user_tag'))
490
491 2
    def as_json(self):
492
        """Return a json representating a UNI object."""
493 2
        return json.dumps(self.as_dict())
494
495
496 2
class NNI:
497
    """Class that represents an Network-to-Network Interface."""
498
499 2
    def __init__(self, interface):
500
        self.interface = interface
501
502
503 2
class VNNI(NNI):
504
    """Class that represents an Virtual Network-to-Network Interface."""
505
506 2
    def __init__(self, service_tag, *args, **kwargs):
507
        self.service_tag = service_tag
508
509
        super().__init__(*args, **kwargs)
510