Passed
Push — master ( 83bc37...b4cfef )
by Vinicius
08:29 queued 05:14
created

kytos.core.interface.VNNI.__init__()   A

Complexity

Conditions 1

Size

Total Lines 4
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nop 4
dl 0
loc 4
rs 10
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_validated_tags, range_addition,
27
                                   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.set_available_tags_tag_ranges(
159
            self.available_tags, self.tag_ranges
160
        )
161
        super().__init__()
162
163
    def __repr__(self):
164
        return f"Interface('{self.name}', {self.port_number}, {self.switch!r})"
165
166
    def __eq__(self, other):
167
        """Compare Interface class with another instance."""
168
        if isinstance(other, str):
169
            return self.address == other
170
        if isinstance(other, Interface):
171
            return self.port_number == other.port_number and \
172
                self.switch.dpid == other.switch.dpid
173
        return False
174
175
    @property
176
    def id(self):  # pylint: disable=invalid-name
177
        """Return id from Interface instance.
178
179
        Returns:
180
            string: Interface id.
181
182
        """
183
        return self._id
184
185
    @property
186
    def uni(self):
187
        """Return if an interface is a user-to-network Interface."""
188
        return not self.nni
189
190 View Code Duplication
    @property
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
191
    def status(self):
192
        """Return the current status of the Entity."""
193
        state = super().status
194
        if state == EntityStatus.DISABLED:
195
            return state
196
197
        for status_func in self.status_funcs.values():
198
            if status_func(self) == EntityStatus.DOWN:
199
                return EntityStatus.DOWN
200
        return state
201
202
    @classmethod
203
    def register_status_func(cls, name: str, func):
204
        """Register status func given its name and a callable at setup time."""
205
        cls.status_funcs[name] = func
206
207
    @classmethod
208
    def register_status_reason_func(cls, name: str, func):
209
        """Register status reason func given its name
210
        and a callable at setup time."""
211
        cls.status_reason_funcs[name] = func
212
213
    @property
214
    def status_reason(self):
215
        """Return the reason behind the current status of the entity."""
216
        return reduce(
217
            operator.or_,
218
            map(
219
                lambda x: x(self),
220
                self.status_reason_funcs.values()
221
            ),
222
            super().status_reason
223
        )
224
225
    @property
226
    def default_tag_values(self) -> dict[str, list[list[int]]]:
227
        """Return a default list of ranges. Applicable to
228
        available_tags and tag_ranges."""
229
        default_values = {
230
            "vlan": [[1, 4095]],
231
            "vlan_qinq": [[1, 4095]],
232
            "mpls": [[1, 1048575]],
233
        }
234
        return default_values
235
236
    def set_tag_ranges(self, tag_ranges: list[list[int]], tag_type: str):
237
        """Set new restriction, tag_ranges."""
238
        if tag_type != TAGType.VLAN.value:
239
            msg = f"Tag type {tag_type} is not supported."
240
            raise KytosTagtypeNotSupported(msg)
241
        with self._tag_lock:
242
            used_tags = range_difference(
243
                self.tag_ranges[tag_type], self.available_tags[tag_type]
244
            )
245
            # Verify new tag_ranges
246
            missing = range_difference(used_tags, tag_ranges)
247
            if missing:
248
                msg = f"Missing tags in tag_range: {missing}"
249
                raise KytosSetTagRangeError(msg)
250
251
            # Resizing
252
            new_available_tags = range_difference(
253
                tag_ranges, used_tags
254
            )
255
            self.available_tags[tag_type] = new_available_tags
256
            self.tag_ranges[tag_type] = tag_ranges
257
258
    def remove_tag_ranges(self, tag_type: str):
259
        """Set tag_ranges[tag_type] to default value"""
260
        if tag_type != TAGType.VLAN.value:
261
            msg = f"Tag type {tag_type} is not supported."
262
            raise KytosTagtypeNotSupported(msg)
263
        with self._tag_lock:
264
            used_tags = range_difference(
265
                self.tag_ranges[tag_type], self.available_tags[tag_type]
266
            )
267
            self.available_tags[tag_type] = range_difference(
268
                self.default_tag_values[tag_type], used_tags
269
            )
270
            self.tag_ranges[tag_type] = self.default_tag_values[tag_type]
271
272
    def _remove_tags(self, tags: list[int], tag_type: str = 'vlan') -> bool:
273
        """Remove tags by resizing available_tags
274
        Returns False if nothing was remove, True otherwise"""
275
        available = self.available_tags[tag_type]
276
        if not available:
277
            return False
278
        index = find_index_remove(available, tags)
279
        if index is None:
280
            return False
281
        # Resizing
282
        if tags[0] == available[index][0]:
283
            if tags[1] == available[index][1]:
284
                available.pop(index)
285
            else:
286
                available[index][0] = tags[1] + 1
287
        elif tags[1] == available[index][1]:
288
            available[index][1] = tags[0] - 1
289
        else:
290
            available[index: index+1] = [
291
                [available[index][0], tags[0]-1],
292
                [tags[1]+1, available[index][1]]
293
            ]
294
        return True
295
296
    def use_tags(
297
        self,
298
        controller,
299
        tags: Union[int, list[int], list[list[int]]],
300
        tag_type: str = 'vlan',
301
        use_lock: bool = True,
302
        check_order: bool = True,
303
    ):
304
        """Remove a specific tag from available_tags if it is there.
305
        Exception raised in case the tags were not able to be removed.
306
307
        Args:
308
            controller: Kytos controller
309
            tags: value to be removed, multiple types for compatibility:
310
                (int): Single tag
311
                (list[int]): Single range of tags
312
                (list[list[int]]): List of ranges of tags
313
            tag_type: TAG type value
314
            use_lock: Boolean to whether use a lock or not
315
            check_order: Boolean to whether validate tags(list). Check order,
316
                type and length. Set to false when invocated internally.
317
318
        Exceptions:
319
            KytosTagsAreNotAvailable from _use_tags()
320
        """
321
        if isinstance(tags, int):
322
            tags = [tags] * 2
323
        elif check_order:
324
            tags = get_validated_tags(tags)
325
        if use_lock:
326
            with self._tag_lock:
327
                self._use_tags(tags, tag_type)
328
        else:
329
            self._use_tags(tags, tag_type)
330
331
        self._notify_interface_tags(controller)
332
333
    def _use_tags(
334
        self,
335
        tags: Union[list[int], list[list[int]]],
336
        tag_type: str
337
    ):
338
        """Manage available_tags deletion changes."""
339
        if isinstance(tags[0], list):
340
            available_copy = deepcopy(self.available_tags[tag_type])
341
            for tag_range in tags:
342
                result = self._remove_tags(tag_range, tag_type)
343
                if result is False:
344
                    self.available_tags[tag_type] = available_copy
345
                    conflict = range_difference(tags, available_copy)
346
                    raise KytosTagsAreNotAvailable(conflict, self._id)
347
        else:
348
            result = self._remove_tags(tags, tag_type)
349
            if result is False:
350
                raise KytosTagsAreNotAvailable([tags], self._id)
351
352
    # pylint: disable=too-many-branches
353
    def _add_tags(self, tags: list[int], tag_type: str = 'vlan') -> bool:
354
        """Add tags, return True if they were added.
355
        Returns False when nothing was added, True otherwise
356
        Ensuring that ranges are not unnecessarily divided
357
        available_tag e.g [[7, 10], [20, 30], [78, 92], [100, 109], [189, 200]]
358
        tags examples are in each if statement.
359
        """
360
        if not tags[0] or not tags[1]:
361
            return False
362
363
        # Check if tags is within self.tag_ranges
364
        tag_ranges = self.tag_ranges[tag_type]
365
        if find_index_remove(tag_ranges, tags) is None:
366
            raise KytosTagsNotInTagRanges([tags], self._id)
367
368
        available = self.available_tags[tag_type]
369
        if not available:
370
            self.available_tags[tag_type] = [tags]
371
            return True
372
373
        index = find_index_add(available, tags)
374
        if index is None:
375
            return False
376
        if index == 0:
377
            # [1, 6]
378
            if tags[1] == available[index][0] - 1:
379
                available[index][0] = tags[0]
380
            # [1, 2]
381
            else:
382
                available.insert(0, tags)
383
        elif index == len(available):
384
            # [201, 300]
385
            if available[index-1][1] + 1 == tags[0]:
386
                available[index-1][1] = tags[1]
387
            # [250, 300]
388
            else:
389
                available.append(tags)
390
        else:
391
            # [11, 19]
392
            if (available[index-1][1] + 1 == tags[0] and
393
                    available[index][0] - 1 == tags[1]):
394
                available[index-1: index+1] = [
395
                    [available[index-1][0], available[index][1]]
396
                ]
397
            # [11, 15]
398
            elif available[index-1][1] + 1 == tags[0]:
399
                available[index-1][1] = tags[1]
400
            # [15, 19]
401
            elif available[index][0] - 1 == tags[1]:
402
                available[index][0] = tags[0]
403
            # [15, 15]
404
            else:
405
                available.insert(index, tags)
406
        return True
407
408
    def make_tags_available(
409
        self,
410
        controller,
411
        tags: Union[int, list[int], list[list[int]]],
412
        tag_type: str = 'vlan',
413
        use_lock: bool = True,
414
        check_order: bool = True,
415
    ) -> list[list[int]]:
416
        """Add a tags in available_tags.
417
418
        Args:
419
            controller: Kytos controller
420
            tags: value to be added, multiple types for compatibility:
421
                (int): Single tag
422
                (list[int]): Single range of tags
423
                (list[list[int]]): List of ranges of tags
424
            tag_type: TAG type value
425
            use_lock: Boolean to whether use a lock or not
426
            check_order: Boolean to whether validate tags(list). Check order,
427
                type and length. Set to false when invocated internally.
428
429
        Return:
430
            conflict: Return any values that were not added.
431
432
        Exeptions:
433
            KytosTagsNotInTagRanges from _make_tags_available()
434
        """
435
        if isinstance(tags, int):
436
            tags = [tags] * 2
437
        elif check_order:
438
            tags = get_validated_tags(tags)
439
        if isinstance(tags[0], int) and tags[0] != tags[1]:
440
            tags = [tags]
441
        if use_lock:
442
            with self._tag_lock:
443
                conflict = self._make_tags_available(tags, tag_type)
444
        else:
445
            conflict = self._make_tags_available(tags, tag_type)
446
        self._notify_interface_tags(controller)
447
        return conflict
448
449
    def _make_tags_available(
450
        self,
451
        tags: Union[list[int], list[list[int]]],
452
        tag_type: str,
453
    ) -> list[list[int]]:
454
        """Manage available_tags adittion changes
455
456
        Exceptions:
457
            KytosTagsNotInTagRanges from _add_tags()
458
        """
459
        if isinstance(tags[0], list):
460
            diff = range_difference(tags, self.tag_ranges[tag_type])
461
            if diff:
462
                raise KytosTagsNotInTagRanges(diff, self._id)
463
            available_tags = self.available_tags[tag_type]
464
            new_tags, conflict = range_addition(tags, available_tags)
465
            self.available_tags[tag_type] = new_tags
466
            return conflict
467
        result = self._add_tags(tags, tag_type)
468
        if result is False:
469
            return [tags]
470
        return []
471
472
    def set_available_tags_tag_ranges(
473
        self,
474
        available_tag: dict[str, list[list[int]]],
475
        tag_ranges: dict[str, list[list[int]]]
476
    ):
477
        """Set a range of VLAN tags to be used by this Interface.
478
479
        Args:
480
            available_tag: Available tags from each tag type
481
            tag_ranges: Restriction for each type of available tag
482
        """
483
        with self._tag_lock:
484
            self.available_tags = available_tag
485
            self.tag_ranges = tag_ranges
486
487
    def enable(self):
488
        """Enable this interface instance.
489
490
        Also enable the switch instance this interface is attached to.
491
        """
492
        self.switch.enable()
493
        self._enabled = True
494
495
    def is_tag_available(self, tag: int, tag_type: str = 'vlan'):
496
        """Check if a tag is available."""
497
        with self._tag_lock:
498
            if find_index_remove(
499
                    self.available_tags[tag_type], [tag, tag]
500
            ) is not None:
501
                return True
502
            return False
503
504
    def get_endpoint(self, endpoint):
505
        """Return a tuple with existent endpoint, None otherwise.
506
507
        Args:
508
            endpoint(|hw_address|, :class:`.Interface`): endpoint instance.
509
510
        Returns:
511
            tuple: A tuple with endpoint and time of last update.
512
513
        """
514
        for item in self.endpoints:
515
            if endpoint == item[0]:
516
                return item
517
        return None
518
519
    def add_endpoint(self, endpoint):
520
        """Create a new endpoint to Interface instance.
521
522
        Args:
523
            endpoint(|hw_address|, :class:`.Interface`): A target endpoint.
524
        """
525
        exists = self.get_endpoint(endpoint)
526
        if not exists:
527
            self.endpoints.append((endpoint, now()))
528
529
    def delete_endpoint(self, endpoint):
530
        """Delete a existent endpoint in Interface instance.
531
532
        Args:
533
            endpoint (|hw_address|, :class:`.Interface`): A target endpoint.
534
        """
535
        exists = self.get_endpoint(endpoint)
536
        if exists:
537
            self.endpoints.remove(exists)
538
539
    def update_endpoint(self, endpoint):
540
        """Update or create new endpoint to Interface instance.
541
542
        Args:
543
            endpoint(|hw_address|, :class:`.Interface`): A target endpoint.
544
        """
545
        exists = self.get_endpoint(endpoint)
546
        if exists:
547
            self.delete_endpoint(endpoint)
548
        self.add_endpoint(endpoint)
549
550
    def update_link(self, link):
551
        """Update link for this interface in a consistent way.
552
553
        Verify of the other endpoint of the link has the same Link information
554
        attached to it, and change it if necessary.
555
556
        Warning: This method can potentially change information of other
557
        Interface instances. Use it with caution.
558
        """
559
        if self not in (link.endpoint_a, link.endpoint_b):
560
            return False
561
562
        if self.link is None or self.link != link:
563
            self.link = link
564
565
        if link.endpoint_a == self:
566
            endpoint = link.endpoint_b
567
        else:
568
            endpoint = link.endpoint_a
569
570
        if endpoint.link is None or endpoint.link != link:
571
            endpoint.link = link
572
573
        return True
574
575
    @property
576
    def speed(self):
577
        """Return the link speed in bytes per second, None otherwise.
578
579
        If the switch was disconnected, we have :attr:`features` and speed is
580
        still returned for common values between v0x01 and v0x04. For specific
581
        v0x04 values (40 Gbps, 100 Gbps and 1 Tbps), the connection must be
582
        active so we can make sure the protocol version is v0x04.
583
584
        Returns:
585
            int, None: Link speed in bytes per second or ``None``.
586
587
        """
588
        speed = self.get_of_features_speed()
589
590
        if speed is not None:
591
            return speed
592
593
        if self._custom_speed is not None:
594
            return self._custom_speed
595
596
        if self._is_v0x04() and self.port_number == PortNo04.OFPP_LOCAL:
597
            return 0
598
599
        if not self._is_v0x04() and self.port_number == PortNo01.OFPP_LOCAL:
600
            return 0
601
602
        # Warn unknown speed
603
        # Use shorter switch ID with its beginning and end
604
        if isinstance(self.switch.id, str) and len(self.switch.id) > 20:
605
            switch_id = self.switch.id[:3] + '...' + self.switch.id[-3:]
606
        else:
607
            switch_id = self.switch.id
608
        LOG.warning("Couldn't get port %s speed, sw %s, feats %s",
609
                    self.port_number, switch_id, self.features)
610
611
        return None
612
613
    def set_custom_speed(self, bytes_per_second):
614
        """Set a speed that overrides switch OpenFlow information.
615
616
        If ``None`` is given, :attr:`speed` becomes the one given by the
617
        switch.
618
        """
619
        self._custom_speed = bytes_per_second
620
621
    def get_custom_speed(self):
622
        """Return custom speed or ``None`` if not set."""
623
        return self._custom_speed
624
625
    def get_of_features_speed(self):
626
        """Return the link speed in bytes per second, None otherwise.
627
628
        If the switch was disconnected, we have :attr:`features` and speed is
629
        still returned for common values between v0x01 and v0x04. For specific
630
        v0x04 values (40 Gbps, 100 Gbps and 1 Tbps), the connection must be
631
        active so we can make sure the protocol version is v0x04.
632
633
        Returns:
634
            int, None: Link speed in bytes per second or ``None``.
635
636
        """
637
        speed = self._get_v0x01_v0x04_speed()
638
        # Don't use switch.is_connected() because we can have the protocol
639
        if speed is None and self._is_v0x04():
640
            speed = self._get_v0x04_speed()
641
        return speed
642
643
    def _is_v0x04(self):
644
        """Whether the switch is connected using OpenFlow 1.3."""
645
        return self.switch.is_connected() and \
646
            self.switch.connection.protocol.version == 0x04
647
648
    def _get_v0x01_v0x04_speed(self):
649
        """Check against all values of v0x01. They're part of v0x04."""
650
        fts = self.features
651
        pfts = PortFeatures01
652
        if fts and fts & pfts.OFPPF_10GB_FD:
653
            return 10 * 10**9 / 8
654
        if fts and fts & (pfts.OFPPF_1GB_HD | pfts.OFPPF_1GB_FD):
655
            return 10**9 / 8
656
        if fts and fts & (pfts.OFPPF_100MB_HD | pfts.OFPPF_100MB_FD):
657
            return 100 * 10**6 / 8
658
        if fts and fts & (pfts.OFPPF_10MB_HD | pfts.OFPPF_10MB_FD):
659
            return 10 * 10**6 / 8
660
        return None
661
662
    def _get_v0x04_speed(self):
663
        """Check against higher enums of v0x04.
664
665
        Must be called after :meth:`get_v0x01_speed` returns ``None``.
666
        """
667
        fts = self.features
668
        pfts = PortFeatures04
669
        if fts and fts & pfts.OFPPF_1TB_FD:
670
            return 10**12 / 8
671
        if fts and fts & pfts.OFPPF_100GB_FD:
672
            return 100 * 10**9 / 8
673
        if fts and fts & pfts.OFPPF_40GB_FD:
674
            return 40 * 10**9 / 8
675
        return None
676
677
    def get_hr_speed(self):
678
        """Return Human-Readable string for link speed.
679
680
        Returns:
681
            string: String with link speed. e.g: '350 Gbps' or '350 Mbps'.
682
683
        """
684
        speed = self.speed
685
        if speed is None:
686
            return ''
687
        speed *= 8
688
        if speed == 10**12:
689
            return '1 Tbps'
690
        if speed >= 10**9:
691
            return f"{round(speed / 10**9)} Gbps"
692
        return f"{round(speed / 10**6)} Mbps"
693
694
    def as_dict(self):
695
        """Return a dictionary with Interface attributes.
696
697
        Speed is in bytes/sec. Example of output (100 Gbps):
698
699
        .. code-block:: python3
700
701
            {'id': '00:00:00:00:00:00:00:01:2',
702
             'name': 'eth01',
703
             'port_number': 2,
704
             'mac': '00:7e:04:3b:c2:a6',
705
             'switch': '00:00:00:00:00:00:00:01',
706
             'type': 'interface',
707
             'nni': False,
708
             'uni': True,
709
             'speed': 12500000000,
710
             'metadata': {},
711
             'lldp': True,
712
             'active': True,
713
             'enabled': False,
714
             'status': 'DISABLED',
715
             'link': ""
716
            }
717
718
        Returns:
719
            dict: Dictionary filled with interface attributes.
720
721
        """
722
        iface_dict = {
723
            'id': self.id,
724
            'name': self.name,
725
            'port_number': self.port_number,
726
            'mac': self.address,
727
            'switch': self.switch.dpid,
728
            'type': 'interface',
729
            'nni': self.nni,
730
            'uni': self.uni,
731
            'speed': self.speed,
732
            'metadata': self.metadata,
733
            'lldp': self.lldp,
734
            'active': self.is_active(),
735
            'enabled': self.is_enabled(),
736
            'status': self.status.value,
737
            'status_reason': sorted(self.status_reason),
738
            'link': self.link.id if self.link else "",
739
        }
740
        if self.stats:
741
            iface_dict['stats'] = self.stats.as_dict()
742
        return iface_dict
743
744
    @classmethod
745
    def from_dict(cls, interface_dict):
746
        """Return a Interface instance from python dictionary."""
747
        return cls(interface_dict.get('name'),
748
                   interface_dict.get('port_number'),
749
                   interface_dict.get('switch'),
750
                   interface_dict.get('address'),
751
                   interface_dict.get('state'),
752
                   interface_dict.get('features'),
753
                   interface_dict.get('speed'))
754
755
    def as_json(self):
756
        """Return a json with Interfaces attributes.
757
758
        Example of output:
759
760
        .. code-block:: json
761
762
            {"mac": "00:7e:04:3b:c2:a6",
763
             "switch": "00:00:00:00:00:00:00:01",
764
             "type": "interface",
765
             "name": "eth01",
766
             "id": "00:00:00:00:00:00:00:01:2",
767
             "port_number": 2,
768
             "speed": "350 Mbps"}
769
770
        Returns:
771
            string: Json filled with interface attributes.
772
773
        """
774
        return json.dumps(self.as_dict())
775
776
    def _notify_interface_tags(self, controller):
777
        """Notify link available tags"""
778
        name = "kytos/core.interface_tags"
779
        content = {"interface": self}
780
        event = KytosEvent(name=name, content=content)
781
        controller.buffers.app.put(event)
782
783
784
class UNI:
785
    """Class that represents an User-to-Network Interface."""
786
787
    def __init__(
788
        self,
789
        interface: Interface,
790
        user_tag: Union[None, TAG, TAGRange]
791
    ):
792
        self.user_tag = user_tag
793
        self.interface = interface
794
795
    def __eq__(self, other):
796
        """Override the default implementation."""
797
        return (self.user_tag == other.user_tag and
798
                self.interface == other.interface)
799
800
    def _is_reserved_valid_tag(self) -> bool:
801
        """Check if TAG string is possible"""
802
        reserved_tag = {"any", "untagged"}
803
        if self.user_tag.value in reserved_tag:
804
            return True
805
        return False
806
807
    def is_valid(self):
808
        """Check if TAG is possible for this interface TAG pool."""
809
        if self.user_tag:
810
            tag = self.user_tag.value
811
            if isinstance(tag, str):
812
                return self._is_reserved_valid_tag()
813
            if isinstance(tag, int):
814
                return self.interface.is_tag_available(tag)
815
        return True
816
817
    def as_dict(self):
818
        """Return a dict representating a UNI object."""
819
        return {
820
            'interface_id': self.interface.id,
821
            'tag': self.user_tag.as_dict() if self.user_tag else None
822
            }
823
824
    @classmethod
825
    def from_dict(cls, uni):
826
        """Return a Uni instance from python dictionary."""
827
        return cls(uni.get('interface'),
828
                   uni.get('user_tag'))
829
830
    def as_json(self):
831
        """Return a json representating a UNI object."""
832
        return json.dumps(self.as_dict())
833
834
835
class NNI:
836
    """Class that represents an Network-to-Network Interface."""
837
838
    def __init__(self, interface):
839
        self.interface = interface
840
841
842
class VNNI(NNI):
843
    """Class that represents an Virtual Network-to-Network Interface."""
844
845
    def __init__(self, service_tag, *args, **kwargs):
846
        self.service_tag = service_tag
847
848
        super().__init__(*args, **kwargs)
849