kytos.core.interface.Interface.speed()   C
last analyzed

Complexity

Conditions 9

Size

Total Lines 37
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
eloc 17
nop 1
dl 0
loc 37
rs 6.6666
c 0
b 0
f 0
1
"""Module with main classes related to Interfaces."""
2
import json
3
import logging
4
import operator
5
from collections import OrderedDict
6
from copy import deepcopy
7
from enum import Enum
8
from functools import reduce
9
from threading import Lock
10
from typing import Union
11
12
from pyof.v0x01.common.phy_port import Port as PortNo01
13
from pyof.v0x01.common.phy_port import PortFeatures as PortFeatures01
14
from pyof.v0x04.common.port import PortFeatures as PortFeatures04
15
from pyof.v0x04.common.port import PortNo as PortNo04
16
17
from kytos.core.common import EntityStatus, GenericEntity
18
from kytos.core.events import KytosEvent
19
from kytos.core.exceptions import (KytosSetTagRangeError,
20
                                   KytosTagsAreNotAvailable,
21
                                   KytosTagsNotInTagRanges,
22
                                   KytosTagtypeNotSupported)
23
from kytos.core.helpers import now
24
from kytos.core.id import InterfaceID
25
from kytos.core.tag_ranges import (find_index_add, find_index_remove,
26
                                   get_special_tags, get_validated_tags,
27
                                   range_addition, range_difference)
28
29
__all__ = ('Interface',)
30
31
LOG = logging.getLogger(__name__)
32
33
34
class TAGType(Enum):
35
    """Class that represents a TAG Type."""
36
37
    VLAN = 'vlan'
38
    VLAN_QINQ = 'vlan_qinq'
39
    MPLS = 'mpls'
40
41
42
class TAG:
43
    """Class that represents a TAG."""
44
45
    def __init__(self, tag_type: str, value: int):
46
        self.tag_type = TAGType(tag_type).value
47
        self.value = value
48
49
    def __eq__(self, other):
50
        if not other:
51
            return False
52
        return self.tag_type == other.tag_type and self.value == other.value
53
54
    def as_dict(self):
55
        """Return a dictionary representating a tag object."""
56
        return {'tag_type': self.tag_type, 'value': self.value}
57
58
    @classmethod
59
    def from_dict(cls, tag_dict):
60
        """Return a TAG instance from python dictionary."""
61
        return cls(tag_dict.get('tag_type'), tag_dict.get('value'))
62
63
    @classmethod
64
    def from_json(cls, tag_json):
65
        """Return a TAG instance from json."""
66
        return cls.from_dict(json.loads(tag_json))
67
68
    def as_json(self):
69
        """Return a json representating a tag object."""
70
        return json.dumps(self.as_dict())
71
72
    def __repr__(self):
73
        return f"TAG({self.tag_type!r}, {self.value!r})"
74
75
76
# pylint: disable=super-init-not-called
77
class TAGRange(TAG):
78
    """Class that represents an User-to-Network Interface with
79
     a tag value as a list."""
80
81
    def __init__(
82
        self,
83
        tag_type: str,
84
        value: list[list[int]],
85
        mask_list: list[Union[str, int]] = None
86
    ):
87
        self.tag_type = TAGType(tag_type).value
88
        self.value = value
89
        self.mask_list = mask_list or []
90
91
    def as_dict(self):
92
        """Return a dictionary representating a tag range object."""
93
        return {
94
            'tag_type': self.tag_type,
95
            'value': self.value,
96
            'mask_list': self.mask_list
97
        }
98
99
100
class Interface(GenericEntity):  # pylint: disable=too-many-instance-attributes
101
    """Interface Class used to abstract the network interfaces."""
102
103
    status_funcs = OrderedDict()
104
    status_reason_funcs = OrderedDict()
105
106
    # pylint: disable=too-many-arguments, too-many-public-methods
107
    def __init__(self, name, port_number, switch, address=None, state=None,
108
                 features=None, speed=None, config=None):
109
        """Assign the parameters to instance attributes.
110
111
        Args:
112
            name (string): name from this interface.
113
            port_number (int): port number from this interface.
114
            switch (:class:`~.core.switch.Switch`): Switch with this interface.
115
            address (|hw_address|): Port address from this interface.
116
            state (|port_stats|): Port Stat from interface. It will be
117
            deprecated.
118
            features (|port_features|): Port feature used to calculate link
119
                utilization from this interface. It will be deprecated.
120
            speed (int, float): Interface speed in bytes per second. Defaults
121
                to what is informed by the switch. Return ``None`` if not set
122
                and switch does not inform the speed.
123
            config(|port_config|): Port config used to indicate interface
124
                behavior. In general, the port config bits are set by the
125
                controller and are not changed by the switch. Options
126
                are: administratively down, ignore received packets, drop
127
                forwarded packets, and/or do not send packet-in messages.
128
129
        Attributes:
130
            available_tags (dict[str, list[list[int]]]): Contains the available
131
                tags integers in the current interface, the availability is
132
                represented as a list of ranges. These ranges are
133
                [inclusive, inclusive]. For example, [1, 5] represents
134
                [1, 2, 3, 4, 5].
135
            tag_ranges (dict[str, list[list[int]]]): Contains restrictions for
136
                available_tags. The list of ranges is the same type as in
137
                available_tags. Setting a new tag_ranges will required for
138
                available_tags to be resize.
139
140
        """
141
        self.name = name
142
        self.port_number = int(port_number)
143
        self.switch = switch
144
        self.address = address
145
        self.state = state
146
        self.features = features
147
        self.config = config
148
        self.nni = False
149
        self.endpoints = []
150
        self.stats = None
151
        self.link = None
152
        self.lldp = True
153
        self._id = InterfaceID(switch.id, port_number)
154
        self._custom_speed = speed
155
        self._tag_lock = Lock()
156
        self.available_tags = {'vlan': self.default_tag_values['vlan']}
157
        self.tag_ranges = {'vlan': self.default_tag_values['vlan']}
158
        self.special_available_tags = {'vlan': self.default_special_tags}
159
        self.special_tags = {'vlan': self.default_special_tags}
160
        self.set_available_tags_tag_ranges(
161
            self.available_tags, self.tag_ranges,
162
            self.special_available_tags, self.special_tags
163
        )
164
        super().__init__()
165
166
    def __repr__(self):
167
        return f"Interface('{self.name}', {self.port_number}, {self.switch!r})"
168
169
    def __eq__(self, other):
170
        """Compare Interface class with another instance."""
171
        if isinstance(other, str):
172
            return self.address == other
173
        if isinstance(other, Interface):
174
            return self.port_number == other.port_number and \
175
                self.switch.dpid == other.switch.dpid
176
        return False
177
178
    @property
179
    def id(self):  # pylint: disable=invalid-name
180
        """Return id from Interface instance.
181
182
        Returns:
183
            string: Interface id.
184
185
        """
186
        return self._id
187
188
    @property
189
    def uni(self):
190
        """Return if an interface is a user-to-network Interface."""
191
        return not self.nni
192
193 View Code Duplication
    @property
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
194
    def status(self):
195
        """Return the current status of the Entity."""
196
        state = super().status
197
        if state == EntityStatus.DISABLED:
198
            return state
199
200
        for status_func in self.status_funcs.values():
201
            if status_func(self) == EntityStatus.DOWN:
202
                return EntityStatus.DOWN
203
        return state
204
205
    @classmethod
206
    def register_status_func(cls, name: str, func):
207
        """Register status func given its name and a callable at setup time."""
208
        cls.status_funcs[name] = func
209
210
    @classmethod
211
    def register_status_reason_func(cls, name: str, func):
212
        """Register status reason func given its name
213
        and a callable at setup time."""
214
        cls.status_reason_funcs[name] = func
215
216
    @property
217
    def status_reason(self):
218
        """Return the reason behind the current status of the entity."""
219
        return reduce(
220
            operator.or_,
221
            map(
222
                lambda x: x(self),
223
                self.status_reason_funcs.values()
224
            ),
225
            super().status_reason
226
        )
227
228
    @property
229
    def default_tag_values(self) -> dict[str, list[list[int]]]:
230
        """Return a default list of ranges. Applicable to
231
        available_tags and tag_ranges."""
232
        default_values = {
233
            "vlan": [[1, 4094]],
234
            "vlan_qinq": [[1, 4094]],
235
            "mpls": [[1, 1048575]],
236
        }
237
        return default_values
238
239
    @property
240
    def default_special_tags(self) -> list[str]:
241
        """Reurn a default list of special tags. Applicable to
242
         special_available_tags and special_tags."""
243
        return ["untagged", "any"]
244
245
    def all_tags_available(self) -> bool:
246
        """Return True if all tags are avaiable (no tags used),
247
         False otherwise"""
248
        with self._tag_lock:
249
            if self.available_tags != self.tag_ranges:
250
                return False
251
            for field, ranges in self.special_available_tags.items():
252
                if set(ranges) != set(self.special_tags[field]):
253
                    return False
254
            return True
255
256
    def set_tag_ranges(self, tag_ranges: list[list[int]], tag_type: str):
257
        """Set new restriction, tag_ranges."""
258
        if tag_type != TAGType.VLAN.value:
259
            msg = f"Tag type {tag_type} is not supported."
260
            raise KytosTagtypeNotSupported(msg)
261
        with self._tag_lock:
262
            used_tags = range_difference(
263
                self.tag_ranges[tag_type], self.available_tags[tag_type]
264
            )
265
            # Verify new tag_ranges
266
            missing = range_difference(used_tags, tag_ranges)
267
            if missing:
268
                msg = f"Missing tags in tag_range: {missing}"
269
                raise KytosSetTagRangeError(msg)
270
271
            # Resizing
272
            new_available_tags = range_difference(
273
                tag_ranges, used_tags
274
            )
275
            self.available_tags[tag_type] = new_available_tags
276
            self.tag_ranges[tag_type] = tag_ranges
277
278
    def remove_tag_ranges(self, tag_type: str):
279
        """Set tag_ranges[tag_type] to default value"""
280
        if tag_type != TAGType.VLAN.value:
281
            msg = f"Tag type {tag_type} is not supported."
282
            raise KytosTagtypeNotSupported(msg)
283
        with self._tag_lock:
284
            used_tags = range_difference(
285
                self.tag_ranges[tag_type], self.available_tags[tag_type]
286
            )
287
            self.available_tags[tag_type] = range_difference(
288
                self.default_tag_values[tag_type], used_tags
289
            )
290
            self.tag_ranges[tag_type] = self.default_tag_values[tag_type]
291
292
    def set_special_tags(
293
        self,
294
        tag_type: str,
295
        special_tags: list[str],
296
    ):
297
        """Set new restriction, special_tags"""
298
        # Verify values in special_tags
299
        tag_range = get_special_tags(
300
            special_tags, self.default_special_tags
301
        )
302
303
        if tag_type != TAGType.VLAN.value:
304
            msg = f"Tag type {tag_type} is not supported."
305
            raise KytosTagtypeNotSupported(msg)
306
        old_special_set = set(self.special_tags[tag_type])
307
308
        for tag in self.special_available_tags[tag_type]:
309
            old_special_set.remove(tag)  # Get special used tags
310
        used_special = old_special_set.copy()
311
312
        for tag in tag_range:
313
            used_special.discard(tag)
314
315
        # Missing used special used tags
316
        if used_special:
317
            msg = f"Missing tags in tag_range: {used_special}"
318
            raise KytosSetTagRangeError(msg)
319
320
        new_special_available = set(tag_range)
321
        self.special_available_tags[tag_type] = list(
322
            new_special_available - old_special_set
323
        )
324
        self.special_tags[tag_type] = tag_range
325
326
    def _remove_tags(self, tags: list[int], tag_type: str = 'vlan') -> bool:
327
        """Remove tags by resizing available_tags
328
        Returns False if nothing was remove, True otherwise"""
329
        available = self.available_tags[tag_type]
330
        if not available:
331
            return False
332
        index = find_index_remove(available, tags)
333
        if index is None:
334
            return False
335
        # Resizing
336
        if tags[0] == available[index][0]:
337
            if tags[1] == available[index][1]:
338
                available.pop(index)
339
            else:
340
                available[index][0] = tags[1] + 1
341
        elif tags[1] == available[index][1]:
342
            available[index][1] = tags[0] - 1
343
        else:
344
            available[index: index+1] = [
345
                [available[index][0], tags[0]-1],
346
                [tags[1]+1, available[index][1]]
347
            ]
348
        return True
349
350
    def use_tags(
351
        self,
352
        controller,
353
        tags: Union[str, int, list[int], list[list[int]]],
354
        tag_type: str = 'vlan',
355
        use_lock: bool = True,
356
        check_order: bool = True,
357
    ):
358
        """Remove a specific tag from available_tags if it is there.
359
        Exception raised in case the tags were not able to be removed.
360
361
        Args:
362
            controller: Kytos controller
363
            tags: value to be removed, multiple types for compatibility:
364
                (str): Special vlan, "untagged" or "vlan"
365
                (int): Single tag
366
                (list[int]): Single range of tags
367
                (list[list[int]]): List of ranges of tags
368
            tag_type: TAG type value
369
            use_lock: Boolean to whether use a lock or not
370
            check_order: Boolean to whether validate tags(list). Check order,
371
                type and length. Set to false when invocated internally.
372
373
        Exceptions:
374
            KytosTagsAreNotAvailable from _use_tags()
375
        """
376
        if isinstance(tags, int):
377
            tags = [tags] * 2
378
        elif check_order and not isinstance(tags, str):
379
            tags = get_validated_tags(tags)
380
        if use_lock:
381
            with self._tag_lock:
382
                try:
383
                    self._use_tags(tags, tag_type)
384
                except KeyError as err:
385
                    msg = f"Tag type {err.args[0]} is not supported"
386
                    raise KytosTagtypeNotSupported(msg)
387
        else:
388
            try:
389
                self._use_tags(tags, tag_type)
390
            except KeyError as err:
391
                msg = f"Tag type {err.args[0]} is not supported"
392
                raise KytosTagtypeNotSupported(msg)
393
394
        self._notify_interface_tags(controller)
395
396
    def _use_tags(
397
        self,
398
        tags: Union[str, list[int], list[list[int]]],
399
        tag_type: str
400
    ):
401
        """Manage available_tags deletion changes."""
402
        if isinstance(tags[0], list):
403
            available_copy = deepcopy(self.available_tags[tag_type])
404
            for tag_range in tags:
405
                result = self._remove_tags(tag_range, tag_type)
406
                if result is False:
407
                    self.available_tags[tag_type] = available_copy
408
                    conflict = range_difference(tags, available_copy)
409
                    raise KytosTagsAreNotAvailable(conflict, self._id)
410
        elif isinstance(tags, list):
411
            result = self._remove_tags(tags, tag_type)
412
            if result is False:
413
                raise KytosTagsAreNotAvailable([tags], self._id)
414
        else:
415
            try:
416
                self.special_available_tags[tag_type].remove(tags)
417
            except ValueError:
418
                raise KytosTagsAreNotAvailable(tags, self.id)
419
420
    # pylint: disable=too-many-branches
421
    def _add_tags(self, tags: list[int], tag_type: str = 'vlan') -> bool:
422
        """Add tags, return True if they were added.
423
        Returns False when nothing was added, True otherwise
424
        Ensuring that ranges are not unnecessarily divided
425
        available_tag e.g [[7, 10], [20, 30], [78, 92], [100, 109], [189, 200]]
426
        tags examples are in each if statement.
427
        """
428
        if not tags[0] or not tags[1]:
429
            return False
430
431
        # Check if tags is within self.tag_ranges
432
        tag_ranges = self.tag_ranges[tag_type]
433
        if find_index_remove(tag_ranges, tags) is None:
434
            raise KytosTagsNotInTagRanges([tags], self._id)
435
436
        available = self.available_tags[tag_type]
437
        if not available:
438
            self.available_tags[tag_type] = [tags]
439
            return True
440
441
        index = find_index_add(available, tags)
442
        if index is None:
443
            return False
444
        if index == 0:
445
            # [1, 6]
446
            if tags[1] == available[index][0] - 1:
447
                available[index][0] = tags[0]
448
            # [1, 2]
449
            else:
450
                available.insert(0, tags)
451
        elif index == len(available):
452
            # [201, 300]
453
            if available[index-1][1] + 1 == tags[0]:
454
                available[index-1][1] = tags[1]
455
            # [250, 300]
456
            else:
457
                available.append(tags)
458
        else:
459
            # [11, 19]
460
            if (available[index-1][1] + 1 == tags[0] and
461
                    available[index][0] - 1 == tags[1]):
462
                available[index-1: index+1] = [
463
                    [available[index-1][0], available[index][1]]
464
                ]
465
            # [11, 15]
466
            elif available[index-1][1] + 1 == tags[0]:
467
                available[index-1][1] = tags[1]
468
            # [15, 19]
469
            elif available[index][0] - 1 == tags[1]:
470
                available[index][0] = tags[0]
471
            # [15, 15]
472
            else:
473
                available.insert(index, tags)
474
        return True
475
476
    def make_tags_available(
477
        self,
478
        controller,
479
        tags: Union[str,  int, list[int], list[list[int]]],
480
        tag_type: str = 'vlan',
481
        use_lock: bool = True,
482
        check_order: bool = True,
483
    ) -> list[list[int]]:
484
        """Add a tags in available_tags.
485
486
        Args:
487
            controller: Kytos controller
488
            tags: value to be added, multiple types for compatibility:
489
                (str): Special vlan, "untagged" or "vlan"
490
                (int): Single tag
491
                (list[int]): Single range of tags
492
                (list[list[int]]): List of ranges of tags
493
            tag_type: TAG type value
494
            use_lock: Boolean to whether use a lock or not
495
            check_order: Boolean to whether validate tags(list). Check order,
496
                type and length. Set to false when invocated internally.
497
498
        Return:
499
            conflict: Return any values that were not added.
500
501
        Exeptions:
502
            KytosTagsNotInTagRanges from _make_tags_available()
503
        """
504
        if isinstance(tags, int):
505
            tags = [tags] * 2
506
        elif check_order and not isinstance(tags, str):
507
            tags = get_validated_tags(tags)
508
        if isinstance(tags[0], int) and tags[0] != tags[1]:
509
            tags = [tags]
510
        if use_lock:
511
            with self._tag_lock:
512
                try:
513
                    conflict = self._make_tags_available(tags, tag_type)
514
                except KeyError as err:
515
                    msg = f"Tag type {err.args[0]} is not supported"
516
                    raise KytosTagtypeNotSupported(msg)
517
        else:
518
            try:
519
                conflict = self._make_tags_available(tags, tag_type)
520
            except KeyError as err:
521
                msg = f"Tag type {err.args[0]} is not supported"
522
                raise KytosTagtypeNotSupported(msg)
523
        self._notify_interface_tags(controller)
524
        return conflict
525
526
    def _make_tags_available(
527
        self,
528
        tags: Union[str, list[int], list[list[int]]],
529
        tag_type: str,
530
    ) -> Union[str, list[list[int]], None]:
531
        """Manage available_tags adittion changes
532
533
        Exceptions:
534
            KytosTagsNotInTagRanges from _add_tags()
535
        """
536
        if isinstance(tags[0], list):
537
            diff = range_difference(tags, self.tag_ranges[tag_type])
538
            if diff:
539
                raise KytosTagsNotInTagRanges(diff, self._id)
540
            available_tags = self.available_tags[tag_type]
541
            new_tags, conflict = range_addition(tags, available_tags)
542
            self.available_tags[tag_type] = new_tags
543
            return conflict
544
        if isinstance(tags, list):
545
            result = self._add_tags(tags, tag_type)
546
            if result is False:
547
                return [tags]
548
        if isinstance(tags, str):
549
            if tags not in self.special_tags[tag_type]:
550
                raise KytosTagsNotInTagRanges(tags, self._id)
551
            if tags not in self.special_available_tags[tag_type]:
552
                self.special_available_tags[tag_type].append(tags)
553
                return None
554
            return tags
555
        return None
556
557
    def set_available_tags_tag_ranges(
558
        self,
559
        available_tag: dict[str, list[list[int]]],
560
        tag_ranges: dict[str, list[list[int]]],
561
        special_available_tags: dict[str, list[str]],
562
        special_tags: dict[str, list[str]]
563
    ):
564
        """Set a range of VLAN tags to be used by this Interface.
565
566
        Args:
567
            available_tag: Available tags from each tag type
568
            tag_ranges: Restriction for each type of available tag
569
        """
570
        with self._tag_lock:
571
            self.available_tags = available_tag
572
            self.tag_ranges = tag_ranges
573
            self.special_available_tags = special_available_tags
574
            self.special_tags = special_tags
575
576
    def is_tag_available(self, tag: int, tag_type: str = 'vlan'):
577
        """Check if a tag is available."""
578
        with self._tag_lock:
579
            if find_index_remove(
580
                    self.available_tags[tag_type], [tag, tag]
581
            ) is not None:
582
                return True
583
            return False
584
585
    def get_endpoint(self, endpoint):
586
        """Return a tuple with existent endpoint, None otherwise.
587
588
        Args:
589
            endpoint(|hw_address|, :class:`.Interface`): endpoint instance.
590
591
        Returns:
592
            tuple: A tuple with endpoint and time of last update.
593
594
        """
595
        for item in self.endpoints:
596
            if endpoint == item[0]:
597
                return item
598
        return None
599
600
    def add_endpoint(self, endpoint):
601
        """Create a new endpoint to Interface instance.
602
603
        Args:
604
            endpoint(|hw_address|, :class:`.Interface`): A target endpoint.
605
        """
606
        exists = self.get_endpoint(endpoint)
607
        if not exists:
608
            self.endpoints.append((endpoint, now()))
609
610
    def delete_endpoint(self, endpoint):
611
        """Delete a existent endpoint in Interface instance.
612
613
        Args:
614
            endpoint (|hw_address|, :class:`.Interface`): A target endpoint.
615
        """
616
        exists = self.get_endpoint(endpoint)
617
        if exists:
618
            self.endpoints.remove(exists)
619
620
    def update_endpoint(self, endpoint):
621
        """Update or create new endpoint to Interface instance.
622
623
        Args:
624
            endpoint(|hw_address|, :class:`.Interface`): A target endpoint.
625
        """
626
        exists = self.get_endpoint(endpoint)
627
        if exists:
628
            self.delete_endpoint(endpoint)
629
        self.add_endpoint(endpoint)
630
631
    def update_link(self, link):
632
        """Update link for this interface in a consistent way.
633
634
        Verify of the other endpoint of the link has the same Link information
635
        attached to it, and change it if necessary.
636
637
        Warning: This method can potentially change information of other
638
        Interface instances. Use it with caution.
639
        """
640
        if self not in (link.endpoint_a, link.endpoint_b):
641
            return False
642
643
        if self.link is None or self.link != link:
644
            self.link = link
645
646
        if link.endpoint_a == self:
647
            endpoint = link.endpoint_b
648
        else:
649
            endpoint = link.endpoint_a
650
651
        if endpoint.link is None or endpoint.link != link:
652
            endpoint.link = link
653
654
        return True
655
656
    @property
657
    def speed(self):
658
        """Return the link speed in bytes per second, None otherwise.
659
660
        If the switch was disconnected, we have :attr:`features` and speed is
661
        still returned for common values between v0x01 and v0x04. For specific
662
        v0x04 values (40 Gbps, 100 Gbps and 1 Tbps), the connection must be
663
        active so we can make sure the protocol version is v0x04.
664
665
        Returns:
666
            int, None: Link speed in bytes per second or ``None``.
667
668
        """
669
        speed = self.get_of_features_speed()
670
671
        if speed is not None:
672
            return speed
673
674
        if self._custom_speed is not None:
675
            return self._custom_speed
676
677
        if self._is_v0x04() and self.port_number == PortNo04.OFPP_LOCAL:
678
            return 0
679
680
        if not self._is_v0x04() and self.port_number == PortNo01.OFPP_LOCAL:
681
            return 0
682
683
        # Warn unknown speed
684
        # Use shorter switch ID with its beginning and end
685
        if isinstance(self.switch.id, str) and len(self.switch.id) > 20:
686
            switch_id = self.switch.id[:3] + '...' + self.switch.id[-3:]
687
        else:
688
            switch_id = self.switch.id
689
        LOG.warning("Couldn't get port %s speed, sw %s, feats %s",
690
                    self.port_number, switch_id, self.features)
691
692
        return None
693
694
    def set_custom_speed(self, bytes_per_second):
695
        """Set a speed that overrides switch OpenFlow information.
696
697
        If ``None`` is given, :attr:`speed` becomes the one given by the
698
        switch.
699
        """
700
        self._custom_speed = bytes_per_second
701
702
    def get_custom_speed(self):
703
        """Return custom speed or ``None`` if not set."""
704
        return self._custom_speed
705
706
    def get_of_features_speed(self):
707
        """Return the link speed in bytes per second, None otherwise.
708
709
        If the switch was disconnected, we have :attr:`features` and speed is
710
        still returned for common values between v0x01 and v0x04. For specific
711
        v0x04 values (40 Gbps, 100 Gbps and 1 Tbps), the connection must be
712
        active so we can make sure the protocol version is v0x04.
713
714
        Returns:
715
            int, None: Link speed in bytes per second or ``None``.
716
717
        """
718
        speed = self._get_v0x01_v0x04_speed()
719
        # Don't use switch.is_connected() because we can have the protocol
720
        if speed is None and self._is_v0x04():
721
            speed = self._get_v0x04_speed()
722
        return speed
723
724
    def _is_v0x04(self):
725
        """Whether the switch is connected using OpenFlow 1.3."""
726
        return self.switch.is_connected() and \
727
            self.switch.connection.protocol.version == 0x04
728
729
    def _get_v0x01_v0x04_speed(self):
730
        """Check against all values of v0x01. They're part of v0x04."""
731
        fts = self.features
732
        pfts = PortFeatures01
733
        if fts and fts & pfts.OFPPF_10GB_FD:
734
            return 10 * 10**9 / 8
735
        if fts and fts & (pfts.OFPPF_1GB_HD | pfts.OFPPF_1GB_FD):
736
            return 10**9 / 8
737
        if fts and fts & (pfts.OFPPF_100MB_HD | pfts.OFPPF_100MB_FD):
738
            return 100 * 10**6 / 8
739
        if fts and fts & (pfts.OFPPF_10MB_HD | pfts.OFPPF_10MB_FD):
740
            return 10 * 10**6 / 8
741
        return None
742
743
    def _get_v0x04_speed(self):
744
        """Check against higher enums of v0x04.
745
746
        Must be called after :meth:`get_v0x01_speed` returns ``None``.
747
        """
748
        fts = self.features
749
        pfts = PortFeatures04
750
        if fts and fts & pfts.OFPPF_1TB_FD:
751
            return 10**12 / 8
752
        if fts and fts & pfts.OFPPF_100GB_FD:
753
            return 100 * 10**9 / 8
754
        if fts and fts & pfts.OFPPF_40GB_FD:
755
            return 40 * 10**9 / 8
756
        return None
757
758
    def get_hr_speed(self):
759
        """Return Human-Readable string for link speed.
760
761
        Returns:
762
            string: String with link speed. e.g: '350 Gbps' or '350 Mbps'.
763
764
        """
765
        speed = self.speed
766
        if speed is None:
767
            return ''
768
        speed *= 8
769
        if speed == 10**12:
770
            return '1 Tbps'
771
        if speed >= 10**9:
772
            return f"{round(speed / 10**9)} Gbps"
773
        return f"{round(speed / 10**6)} Mbps"
774
775
    def as_dict(self):
776
        """Return a dictionary with Interface attributes.
777
778
        Speed is in bytes/sec. Example of output (100 Gbps):
779
780
        .. code-block:: python3
781
782
            {'id': '00:00:00:00:00:00:00:01:2',
783
             'name': 'eth01',
784
             'port_number': 2,
785
             'mac': '00:7e:04:3b:c2:a6',
786
             'switch': '00:00:00:00:00:00:00:01',
787
             'type': 'interface',
788
             'nni': False,
789
             'uni': True,
790
             'speed': 12500000000,
791
             'metadata': {},
792
             'lldp': True,
793
             'active': True,
794
             'enabled': False,
795
             'status': 'DISABLED',
796
             'link': ""
797
            }
798
799
        Returns:
800
            dict: Dictionary filled with interface attributes.
801
802
        """
803
        iface_dict = {
804
            'id': self.id,
805
            'name': self.name,
806
            'port_number': self.port_number,
807
            'mac': self.address,
808
            'switch': self.switch.dpid,
809
            'type': 'interface',
810
            'nni': self.nni,
811
            'uni': self.uni,
812
            'speed': self.speed,
813
            'metadata': self.metadata,
814
            'lldp': self.lldp,
815
            'active': self.is_active(),
816
            'enabled': self.is_enabled(),
817
            'status': self.status.value,
818
            'status_reason': sorted(self.status_reason),
819
            'link': self.link.id if self.link else "",
820
        }
821
        if self.stats:
822
            iface_dict['stats'] = self.stats.as_dict()
823
        return iface_dict
824
825
    @classmethod
826
    def from_dict(cls, interface_dict):
827
        """Return a Interface instance from python dictionary."""
828
        return cls(interface_dict.get('name'),
829
                   interface_dict.get('port_number'),
830
                   interface_dict.get('switch'),
831
                   interface_dict.get('address'),
832
                   interface_dict.get('state'),
833
                   interface_dict.get('features'),
834
                   interface_dict.get('speed'))
835
836
    def as_json(self):
837
        """Return a json with Interfaces attributes.
838
839
        Example of output:
840
841
        .. code-block:: json
842
843
            {"mac": "00:7e:04:3b:c2:a6",
844
             "switch": "00:00:00:00:00:00:00:01",
845
             "type": "interface",
846
             "name": "eth01",
847
             "id": "00:00:00:00:00:00:00:01:2",
848
             "port_number": 2,
849
             "speed": "350 Mbps"}
850
851
        Returns:
852
            string: Json filled with interface attributes.
853
854
        """
855
        return json.dumps(self.as_dict())
856
857
    def _notify_interface_tags(self, controller):
858
        """Notify link available tags"""
859
        name = "kytos/core.interface_tags"
860
        content = {"interface": self}
861
        event = KytosEvent(name=name, content=content)
862
        controller.buffers.app.put(event)
863
864
865
class UNI:
866
    """Class that represents an User-to-Network Interface."""
867
868
    def __init__(
869
        self,
870
        interface: Interface,
871
        user_tag: Union[None, TAG, TAGRange]
872
    ):
873
        self.user_tag = user_tag
874
        self.interface = interface
875
876
    def __eq__(self, other):
877
        """Override the default implementation."""
878
        return (self.user_tag == other.user_tag and
879
                self.interface == other.interface)
880
881
    def _is_reserved_valid_tag(self) -> bool:
882
        """Check if TAG string is possible"""
883
        reserved_tag = {"any", "untagged"}
884
        if self.user_tag.value in reserved_tag:
885
            return True
886
        return False
887
888
    def is_valid(self):
889
        """Check if TAG is possible for this interface TAG pool."""
890
        if self.user_tag:
891
            tag = self.user_tag.value
892
            if isinstance(tag, str):
893
                return self._is_reserved_valid_tag()
894
            if isinstance(tag, int):
895
                return self.interface.is_tag_available(tag)
896
        return True
897
898
    def as_dict(self):
899
        """Return a dict representating a UNI object."""
900
        return {
901
            'interface_id': self.interface.id,
902
            'tag': self.user_tag.as_dict() if self.user_tag else None
903
            }
904
905
    @classmethod
906
    def from_dict(cls, uni):
907
        """Return a Uni instance from python dictionary."""
908
        return cls(uni.get('interface'),
909
                   uni.get('user_tag'))
910
911
    def as_json(self):
912
        """Return a json representating a UNI object."""
913
        return json.dumps(self.as_dict())
914
915
916
class NNI:
917
    """Class that represents an Network-to-Network Interface."""
918
919
    def __init__(self, interface):
920
        self.interface = interface
921
922
923
class VNNI(NNI):
924
    """Class that represents an Virtual Network-to-Network Interface."""
925
926
    def __init__(self, service_tag, *args, **kwargs):
927
        self.service_tag = service_tag
928
929
        super().__init__(*args, **kwargs)
930