mfd-dcb 1.18.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mfd_dcb/__init__.py +6 -0
- mfd_dcb/const.py +7 -0
- mfd_dcb/data_structures.py +12 -0
- mfd_dcb/dcbnl_wrapper.py +144 -0
- mfd_dcb/dcbtool_wrapper.py +155 -0
- mfd_dcb/enums.py +28 -0
- mfd_dcb/exceptions.py +21 -0
- mfd_dcb/freebsd.py +146 -0
- mfd_dcb/linux.py +735 -0
- mfd_dcb/mfd_dcb.py +48 -0
- mfd_dcb/windows.py +987 -0
- mfd_dcb-1.18.0.dist-info/METADATA +244 -0
- mfd_dcb-1.18.0.dist-info/RECORD +17 -0
- mfd_dcb-1.18.0.dist-info/WHEEL +5 -0
- mfd_dcb-1.18.0.dist-info/licenses/AUTHORS.md +7 -0
- mfd_dcb-1.18.0.dist-info/licenses/LICENSE.md +8 -0
- mfd_dcb-1.18.0.dist-info/top_level.txt +1 -0
mfd_dcb/__init__.py
ADDED
mfd_dcb/const.py
ADDED
mfd_dcb/dcbnl_wrapper.py
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# Copyright (C) 2025 Intel Corporation
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
"""Module for dcbnl."""
|
|
5
|
+
|
|
6
|
+
import re
|
|
7
|
+
|
|
8
|
+
from typing import Dict, TYPE_CHECKING, Any
|
|
9
|
+
from mfd_const.qos import LOCAL_ETS, LOCAL_PFC, LOCAL_APP, DCB_TOOL_PATH_LNX
|
|
10
|
+
from .exceptions import DcbExecutionError, DcbExecutionProcessError
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from mfd_connect import RPyCConnection
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Dcbnl:
|
|
17
|
+
"""Dcbnl class provides DCB configuration using dcbnl python script."""
|
|
18
|
+
|
|
19
|
+
def __init__(self, interface_name: str, connection: "RPyCConnection"):
|
|
20
|
+
"""Initialize class parameters.
|
|
21
|
+
|
|
22
|
+
:param interface_name: tested interface
|
|
23
|
+
:param connection: connection
|
|
24
|
+
"""
|
|
25
|
+
self.interface_name = interface_name
|
|
26
|
+
self._connection = connection
|
|
27
|
+
|
|
28
|
+
def get_dcb(self) -> Dict[str, Dict[str, Any]]:
|
|
29
|
+
"""Get complete DCB configuration for ETS, PFC and APP TLVs, local and remote.
|
|
30
|
+
|
|
31
|
+
:return: Dictionary of QoS configuration
|
|
32
|
+
"""
|
|
33
|
+
qos = {}
|
|
34
|
+
qos.update(self.get_ets())
|
|
35
|
+
qos.update(self.get_pfc(from_cache=True))
|
|
36
|
+
qos.update(self.get_app(from_cache=True))
|
|
37
|
+
return qos
|
|
38
|
+
|
|
39
|
+
def get_ets(self, from_cache: bool = False) -> Dict[str, Dict[str, Any]]:
|
|
40
|
+
"""Get local ETS TLV configuration of the interface_name.
|
|
41
|
+
|
|
42
|
+
:param from_cache: use current configuration output
|
|
43
|
+
:return: Dictionary of local ETS TLV configuration
|
|
44
|
+
"""
|
|
45
|
+
if not from_cache:
|
|
46
|
+
self._read_qos_configuration()
|
|
47
|
+
return self._parse_ets(LOCAL_ETS)
|
|
48
|
+
|
|
49
|
+
def get_pfc(self, from_cache: bool = False) -> Dict[str, bool]:
|
|
50
|
+
"""Get local PFC TLV configuration of the interface_name.
|
|
51
|
+
|
|
52
|
+
:param from_cache: use current configuration output
|
|
53
|
+
:return: Dictionary of local PFC TLV configuration
|
|
54
|
+
"""
|
|
55
|
+
if not from_cache:
|
|
56
|
+
self._read_qos_configuration()
|
|
57
|
+
return self._parse_pfc(LOCAL_PFC)
|
|
58
|
+
|
|
59
|
+
def get_app(self, from_cache: bool = False) -> Dict[str, Dict[str, Any]]:
|
|
60
|
+
"""Get local APP TLV configuration of the interface_name.
|
|
61
|
+
|
|
62
|
+
:param from_cache: use current configuration output
|
|
63
|
+
:return: Dictionary of local APP TLV configuration
|
|
64
|
+
"""
|
|
65
|
+
if not from_cache:
|
|
66
|
+
self._read_qos_configuration()
|
|
67
|
+
return self._parse_app(LOCAL_APP)
|
|
68
|
+
|
|
69
|
+
def _read_qos_configuration(self) -> None:
|
|
70
|
+
"""Run dcbnl.py script for given interface_name and store it in self._qos_output for future use.
|
|
71
|
+
|
|
72
|
+
dcbnl.py - Netlink message generation/parsing - DCB_TOOL_PATH_LNX = /tmp/tools/dcb
|
|
73
|
+
:raises DcbExecutionError: when return code of bash command is unexpected
|
|
74
|
+
"""
|
|
75
|
+
dcbnl_path = self._connection.path(DCB_TOOL_PATH_LNX, "dcbnl.py")
|
|
76
|
+
command = f"{self._connection.modules().sys.executable} {dcbnl_path} {self.interface_name}"
|
|
77
|
+
output = self._connection.execute_command(
|
|
78
|
+
command, custom_exception=DcbExecutionProcessError, expected_return_codes=[0]
|
|
79
|
+
)
|
|
80
|
+
if output.stderr:
|
|
81
|
+
raise DcbExecutionError(f"Error while executing dcbnl {command}: {output.stderr}")
|
|
82
|
+
self._qos_output = output.stdout
|
|
83
|
+
|
|
84
|
+
def _parse_ets(self, key: str) -> Dict[str, Dict[str, int]]:
|
|
85
|
+
"""Get local or remote QoS traffic classes as dictionary.
|
|
86
|
+
|
|
87
|
+
:param key: key in configuration dictionary for ETS
|
|
88
|
+
:return: Dictionary of QoS traffic classes configuration
|
|
89
|
+
"""
|
|
90
|
+
ets_config = {}
|
|
91
|
+
current_tc = ""
|
|
92
|
+
for line in self._qos_output.splitlines():
|
|
93
|
+
line = line.strip()
|
|
94
|
+
exp = re.compile(r"tc: (?P<tc>[0-7]) tsa: (?P<tsa>ets|strict)(?:, bw: (?P<bw>\d+)%)")
|
|
95
|
+
match = exp.match(line)
|
|
96
|
+
if match:
|
|
97
|
+
current_tc = match.group("tc")
|
|
98
|
+
ets_config[current_tc] = {
|
|
99
|
+
"TSA": match.group("tsa").upper(),
|
|
100
|
+
"Bandwidth": int(match.group("bw") or "0"),
|
|
101
|
+
"Priorities": [],
|
|
102
|
+
}
|
|
103
|
+
continue
|
|
104
|
+
match = re.match(r"up:\s+(?P<up>[0-7])", line)
|
|
105
|
+
if match:
|
|
106
|
+
up = int(match.group("up"))
|
|
107
|
+
ets_config[current_tc]["Priorities"].append(up)
|
|
108
|
+
continue
|
|
109
|
+
return {key: ets_config}
|
|
110
|
+
|
|
111
|
+
def _parse_pfc(self, key: str) -> Dict[str, bool]:
|
|
112
|
+
"""Get operational or remote QoS flow control as dictionary.
|
|
113
|
+
|
|
114
|
+
:param key: key in configuration dictionary for PFC
|
|
115
|
+
:return: Dictionary of QoS flow control configuration
|
|
116
|
+
"""
|
|
117
|
+
pfc_config = [False] * 8
|
|
118
|
+
match = re.search(r"pfc_enable:\s+(?P<pfc>0x[0-9a-fA-F]+)", self._qos_output)
|
|
119
|
+
if match:
|
|
120
|
+
pfc_bin = int(match.group("pfc"), 16)
|
|
121
|
+
for i in range(8):
|
|
122
|
+
pfc_config[i] = pfc_bin & (pow(2, i)) > 0
|
|
123
|
+
return {key: pfc_config}
|
|
124
|
+
|
|
125
|
+
def _parse_app(self, key: str) -> Dict[str, Dict[str, str]]:
|
|
126
|
+
"""Get operational or remote QoS apps table.
|
|
127
|
+
|
|
128
|
+
:param key: key in configuration dictionary for PFC
|
|
129
|
+
:return: Dictionary of QoS flow control configuration
|
|
130
|
+
"""
|
|
131
|
+
app_config = {}
|
|
132
|
+
no = 0
|
|
133
|
+
for line in self._qos_output.splitlines():
|
|
134
|
+
line = line.strip()
|
|
135
|
+
exp = re.compile(r"selector: (?P<selector>\d+) priority: (?P<priority>\d+) protocol: (?P<protocol>\S*)")
|
|
136
|
+
match = exp.match(line)
|
|
137
|
+
if match:
|
|
138
|
+
app_config[no] = {
|
|
139
|
+
"selector": int(match.group("selector")),
|
|
140
|
+
"priority": int(match.group("priority")),
|
|
141
|
+
"protocol": match.group("protocol"),
|
|
142
|
+
}
|
|
143
|
+
no += 1
|
|
144
|
+
return {key: app_config}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# Copyright (C) 2025 Intel Corporation
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
"""Module for dcbtool."""
|
|
5
|
+
|
|
6
|
+
import re
|
|
7
|
+
from typing import List, Dict, Any, TYPE_CHECKING
|
|
8
|
+
from mfd_const.qos import LOCAL_ETS, LOCAL_PFC
|
|
9
|
+
from .exceptions import DcbExecutionError, DcbExecutionProcessError
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from mfd_connect import RPyCConnection
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class DcbTool:
|
|
16
|
+
"""DcbTool class provide check DCB configuration using dcbtool tool."""
|
|
17
|
+
|
|
18
|
+
def __init__(self, interface_name: str, connection: "RPyCConnection"):
|
|
19
|
+
"""Initialize class parameters.
|
|
20
|
+
|
|
21
|
+
:param interface_name: tested interface
|
|
22
|
+
:param connection: connection
|
|
23
|
+
"""
|
|
24
|
+
self.interface_name = interface_name
|
|
25
|
+
self._connection = connection
|
|
26
|
+
|
|
27
|
+
def get_dcb(self) -> Dict[str, Dict[str, Any]]:
|
|
28
|
+
"""Get complete DCB configuration for ETS, PFC and APP TLVs, local and remote.
|
|
29
|
+
|
|
30
|
+
:return: Dictionary of QoS configuration
|
|
31
|
+
"""
|
|
32
|
+
qos = {}
|
|
33
|
+
qos.update(self.get_ets())
|
|
34
|
+
qos.update(self.get_pfc())
|
|
35
|
+
return qos
|
|
36
|
+
|
|
37
|
+
def get_ets(self, from_cache: bool = False) -> Dict[str, Dict[str, Any]]:
|
|
38
|
+
"""Get local ETS TLV configuration of the interface_name.
|
|
39
|
+
|
|
40
|
+
:param from_cache: use current configuration output
|
|
41
|
+
:return: Dictionary of local ETS TLV configuration
|
|
42
|
+
"""
|
|
43
|
+
if not from_cache:
|
|
44
|
+
self._read_qos_configuration(check_type="pg")
|
|
45
|
+
return self._parse_qos(LOCAL_ETS)
|
|
46
|
+
|
|
47
|
+
def get_pfc(self, from_cache: bool = False) -> Dict[str, bool]:
|
|
48
|
+
"""Get local PFC TLV configuration of the interface_name.
|
|
49
|
+
|
|
50
|
+
:param from_cache: use current configuration output
|
|
51
|
+
:return: Dictionary of local PFC TLV configuration
|
|
52
|
+
"""
|
|
53
|
+
if not from_cache:
|
|
54
|
+
self._read_qos_configuration(check_type="pfc")
|
|
55
|
+
return self._parse_qos(LOCAL_PFC)
|
|
56
|
+
|
|
57
|
+
def _read_qos_configuration(self, check_type: str) -> None:
|
|
58
|
+
"""Run dcbtool tool for given interface_name and store it in self._qos_output for future use.
|
|
59
|
+
|
|
60
|
+
:param check_type: for checking dcbtool type - pg/ pfc
|
|
61
|
+
:raises DcbExecutionProcessError: when return code of bash command is unexpected
|
|
62
|
+
"""
|
|
63
|
+
command = f"dcbtool go {self.interface_name} {check_type}"
|
|
64
|
+
output = self._connection.execute_command(command, custom_exception=DcbExecutionProcessError)
|
|
65
|
+
if output.stderr:
|
|
66
|
+
raise DcbExecutionError(f"Error while executing dcbnl {command}: {output.stderr}")
|
|
67
|
+
self._qos_output = output.stdout
|
|
68
|
+
|
|
69
|
+
def _parse_qos(self, key: str) -> Dict[str, Dict[str, Any]]:
|
|
70
|
+
"""Get local or remote QoS traffic classes as dictionary.
|
|
71
|
+
|
|
72
|
+
:param key: key in configuration dictionary for ETS & PFC
|
|
73
|
+
:return: Dictionary of QoS traffic classes configuration
|
|
74
|
+
"""
|
|
75
|
+
config_ets = {}
|
|
76
|
+
config_pfc = []
|
|
77
|
+
for line in self._qos_output.splitlines():
|
|
78
|
+
self._parse_lines(key, line, config_ets, config_pfc)
|
|
79
|
+
configs = {LOCAL_PFC: config_pfc, LOCAL_ETS: config_ets}
|
|
80
|
+
return {key: configs[key]}
|
|
81
|
+
|
|
82
|
+
def _parse_lines(
|
|
83
|
+
self,
|
|
84
|
+
key: str,
|
|
85
|
+
line: str,
|
|
86
|
+
config_ets: dict,
|
|
87
|
+
config_pfc: list,
|
|
88
|
+
) -> None:
|
|
89
|
+
"""Parse and updates in-place config_pfc and config_ets.
|
|
90
|
+
|
|
91
|
+
:param key: key in configuration dictionary for ETS or PFC
|
|
92
|
+
:param line: Each entries from dcbtool go interface <checkType(pg/pfc)>
|
|
93
|
+
:param config_ets: ets config data
|
|
94
|
+
:param config_pfc: pfc config data
|
|
95
|
+
"""
|
|
96
|
+
bw_pattern = r"\s+(\d{1,3})%\s"
|
|
97
|
+
tc_pattern = r"pgid:\s+(\d\s)*\d"
|
|
98
|
+
pattern = r"pfcup:\s+(\d\s)*\d"
|
|
99
|
+
if "ETS" in key and (("pgid" in line) or ("pgpct" in line) or ("uppct" in line)):
|
|
100
|
+
line = line.strip()
|
|
101
|
+
bw_list = self._parse_line(line, bw_pattern)
|
|
102
|
+
if bw_list:
|
|
103
|
+
self._set_bw_config(bw_list, config_ets)
|
|
104
|
+
tc_list = self._parse_line(line, tc_pattern)
|
|
105
|
+
if tc_list:
|
|
106
|
+
self._set_up_config(tc_list, config_ets)
|
|
107
|
+
if not (tc_list or bw_list):
|
|
108
|
+
raise DcbExecutionError("No match found for ETS")
|
|
109
|
+
elif "PFC" in key and "pfc" in line:
|
|
110
|
+
pfc_list = self._parse_line(line, pattern)
|
|
111
|
+
if pfc_list:
|
|
112
|
+
for pfc in pfc_list:
|
|
113
|
+
config_pfc.append(bool(not (pfc)))
|
|
114
|
+
else:
|
|
115
|
+
raise DcbExecutionError("No match found for PFC")
|
|
116
|
+
|
|
117
|
+
@staticmethod
|
|
118
|
+
def _parse_line(line: str, pattern: str) -> List[int]:
|
|
119
|
+
"""To parse line and return dict with data.
|
|
120
|
+
|
|
121
|
+
:param line: line to search
|
|
122
|
+
:param pattern: regex pattern
|
|
123
|
+
:return: list of data with bw_pattern/tc_pattern
|
|
124
|
+
"""
|
|
125
|
+
data = []
|
|
126
|
+
if "%" in line:
|
|
127
|
+
data = list(map(int, (re.findall(pattern, line))))
|
|
128
|
+
else:
|
|
129
|
+
match = re.compile(pattern).match(line)
|
|
130
|
+
if match:
|
|
131
|
+
for idx in range(re.compile(pattern).groups):
|
|
132
|
+
data.append(int(match.group(idx + 1)))
|
|
133
|
+
return data
|
|
134
|
+
|
|
135
|
+
@staticmethod
|
|
136
|
+
def _set_bw_config(bw_list: list, ets_config: dict) -> None:
|
|
137
|
+
"""To set bandwidth data in ets_config.
|
|
138
|
+
|
|
139
|
+
:param bw_list: list with bandwidth data
|
|
140
|
+
:param ets_config: ets config data
|
|
141
|
+
"""
|
|
142
|
+
for up, bw in enumerate(bw_list):
|
|
143
|
+
if bw > 0:
|
|
144
|
+
current_tc = str(up)
|
|
145
|
+
ets_config[current_tc] = {"TSA": "ETS", "Bandwidth": bw, "Priorities": []}
|
|
146
|
+
|
|
147
|
+
@staticmethod
|
|
148
|
+
def _set_up_config(tc_list: list, ets_config: dict) -> None:
|
|
149
|
+
"""To set user priority data in ets_config.
|
|
150
|
+
|
|
151
|
+
:param tc_list: list with user priority
|
|
152
|
+
:param ets_config: ets config data
|
|
153
|
+
"""
|
|
154
|
+
for up, tc in enumerate(tc_list):
|
|
155
|
+
ets_config[str(tc)]["Priorities"].append(up)
|
mfd_dcb/enums.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Copyright (C) 2025 Intel Corporation
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
"""Enums for DCBmodule."""
|
|
4
|
+
|
|
5
|
+
from enum import Enum, IntEnum
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class WindowsDcbMode(Enum):
|
|
9
|
+
"""Enum class for Windows DCB Features."""
|
|
10
|
+
|
|
11
|
+
SWITCH = "Switch Settings"
|
|
12
|
+
OS = "OS Controlled"
|
|
13
|
+
ENABLED = "Enabled"
|
|
14
|
+
DISABLED = "Disabled"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class PfcType(Enum):
|
|
18
|
+
"""Enum class for PFC Type."""
|
|
19
|
+
|
|
20
|
+
LOCAL = "local"
|
|
21
|
+
REMOTE = "remote"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class PfcMode(IntEnum):
|
|
25
|
+
"""Enum class for PFC Mode."""
|
|
26
|
+
|
|
27
|
+
VLAN = 0
|
|
28
|
+
DSCP = 1
|
mfd_dcb/exceptions.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Copyright (C) 2025 Intel Corporation
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
"""Exceptions for module."""
|
|
4
|
+
|
|
5
|
+
from subprocess import CalledProcessError
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class DcbConnectedOSNotSupported(Exception):
|
|
9
|
+
"""Handle Dcb unsupported os exceptions."""
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class DcbException(Exception):
|
|
13
|
+
"""Handle Dcb exceptions."""
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class DcbExecutionError(Exception):
|
|
17
|
+
"""Handle Dcb execution errors."""
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class DcbExecutionProcessError(CalledProcessError):
|
|
21
|
+
"""Handle Dcb process execution errors."""
|
mfd_dcb/freebsd.py
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# Copyright (C) 2025 Intel Corporation
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
"""Module for DCB support for FreeBSD OS."""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
from typing import TYPE_CHECKING, List, Tuple
|
|
10
|
+
|
|
11
|
+
from mfd_common_libs import add_logging_level, log_levels, os_supported
|
|
12
|
+
from mfd_typing import OSName
|
|
13
|
+
from mfd_sysctl.freebsd import FreebsdSysctl
|
|
14
|
+
from mfd_connect.exceptions import ConnectionCalledProcessError
|
|
15
|
+
from .mfd_dcb import Dcb
|
|
16
|
+
from .enums import PfcMode
|
|
17
|
+
from .exceptions import DcbException
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
if TYPE_CHECKING:
|
|
21
|
+
from mfd_connect import RPyCConnection
|
|
22
|
+
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
|
+
add_logging_level("MODULE_DEBUG", log_levels.MODULE_DEBUG)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class FreeBsdDcb(Dcb):
|
|
28
|
+
"""Class to handle DCB in FreeBSD."""
|
|
29
|
+
|
|
30
|
+
@os_supported(OSName.FREEBSD)
|
|
31
|
+
def __init__(self, *, connection: "RPyCConnection") -> None:
|
|
32
|
+
"""Initialize FreeBSD DCB."""
|
|
33
|
+
super().__init__(connection=connection)
|
|
34
|
+
self._sysctl = FreebsdSysctl(connection=connection)
|
|
35
|
+
|
|
36
|
+
def dscp_apply_map(self, interface_name: str, dscpmap: List[int]) -> None:
|
|
37
|
+
"""Configure DSCP2TC map on the interface.
|
|
38
|
+
|
|
39
|
+
:param interface_name: name of the network interface
|
|
40
|
+
:param dscpmap: DSCP2TC map for setting on the interface
|
|
41
|
+
:raises DcbException: when DSCP map failed to apply (wrong number of elements or invalid values).
|
|
42
|
+
:raises ConnectionCalledProcessError: when sysctl failed and returned an unexpected error code.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
def _apply_range(device_oid: str, values: list, start: int) -> None:
|
|
46
|
+
command = f"sysctl {device_oid}.dscp2tc_map.{start}-{start + 7}={','.join(map(str, values))}"
|
|
47
|
+
try:
|
|
48
|
+
self._connection.execute_command(command, expected_return_codes=[0])
|
|
49
|
+
except ConnectionCalledProcessError as e:
|
|
50
|
+
if "returned unexpected exit status 1" in str(e):
|
|
51
|
+
raise DcbException(f"Failed to apply DSCP map for range {start}-{start + 7}")
|
|
52
|
+
else:
|
|
53
|
+
raise e
|
|
54
|
+
|
|
55
|
+
device_oid = (
|
|
56
|
+
f"dev.{self._sysctl.get_driver_name(interface_name)}."
|
|
57
|
+
f"{self._sysctl.get_driver_interface_number(interface_name)}"
|
|
58
|
+
)
|
|
59
|
+
logger.log(
|
|
60
|
+
level=log_levels.MODULE_DEBUG,
|
|
61
|
+
msg=f"Applying DSCP map: {dscpmap} on interface {interface_name}",
|
|
62
|
+
)
|
|
63
|
+
# apply DSCP map in parts (8 elements for each sysctl variable)
|
|
64
|
+
for r in range(0, 64, 8):
|
|
65
|
+
_apply_range(device_oid, dscpmap[r : r + 8], r)
|
|
66
|
+
|
|
67
|
+
def dscp_read_map(self, interface_name: str) -> List[int]:
|
|
68
|
+
"""Read DSCP2TC map from the interface.
|
|
69
|
+
|
|
70
|
+
:param interface_name: name of the network interface
|
|
71
|
+
:return: DSCP map that is configured on the interface
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
def _read_range(device_oid: str, start: int) -> Tuple[int]:
|
|
75
|
+
full_oid = f"{device_oid}.dscp2tc_map.{start}-{start + 7}"
|
|
76
|
+
result = self._sysctl.get_sysctl_value(full_oid)
|
|
77
|
+
return tuple(map(int, str(result).split(",")))
|
|
78
|
+
|
|
79
|
+
device_oid = (
|
|
80
|
+
f"dev.{self._sysctl.get_driver_name(interface_name)}."
|
|
81
|
+
f"{self._sysctl.get_driver_interface_number(interface_name)}"
|
|
82
|
+
)
|
|
83
|
+
m = []
|
|
84
|
+
logger.log(level=log_levels.MODULE_DEBUG, msg=f"Reading DSCP map on interface {interface_name}")
|
|
85
|
+
# read DSCP map in chunks (8 elements each) and add them to a list
|
|
86
|
+
for r in range(0, 64, 8):
|
|
87
|
+
m.extend(_read_range(device_oid, r))
|
|
88
|
+
return m
|
|
89
|
+
|
|
90
|
+
def dscp_verify_map(self, interface_name: str, dscpmap: List[int]) -> bool:
|
|
91
|
+
"""Check that the interface has the same DSCP2TC map.
|
|
92
|
+
|
|
93
|
+
:param interface_name: Network interface
|
|
94
|
+
:param dscpmap: DSCP2TC map to compare
|
|
95
|
+
:return: True if the interface has the same dscp2tc map
|
|
96
|
+
"""
|
|
97
|
+
current_dscpmap = self.dscp_read_map(interface_name)
|
|
98
|
+
return current_dscpmap == dscpmap
|
|
99
|
+
|
|
100
|
+
def set_pfc_mode(self, interface_name: str, mode: PfcMode) -> None:
|
|
101
|
+
"""Enable/disable DSCP mode on the interface.
|
|
102
|
+
|
|
103
|
+
:param interface_name: Network interface
|
|
104
|
+
:param mode: Enable (PfcMode.DSCP) or disable (PfcMode.VLAN) DSCP mode on the interface
|
|
105
|
+
"""
|
|
106
|
+
oid = (
|
|
107
|
+
f"dev.{self._sysctl.get_driver_name(interface_name)}."
|
|
108
|
+
f"{self._sysctl.get_driver_interface_number(interface_name)}.pfc_mode"
|
|
109
|
+
)
|
|
110
|
+
self._sysctl.set_sysctl_value(oid, value=str(mode.value))
|
|
111
|
+
|
|
112
|
+
def get_pfc_mode(self, interface_name: str) -> PfcMode:
|
|
113
|
+
"""Get the current PFC mode on the interface.
|
|
114
|
+
|
|
115
|
+
:param interface_name: Name of the network interface
|
|
116
|
+
:return: Current PFC mode (VLAN/DSCP) on the interface
|
|
117
|
+
"""
|
|
118
|
+
oid = (
|
|
119
|
+
f"dev.{self._sysctl.get_driver_name(interface_name)}."
|
|
120
|
+
f"{self._sysctl.get_driver_interface_number(interface_name)}.pfc_mode"
|
|
121
|
+
)
|
|
122
|
+
return PfcMode(int(self._sysctl.get_sysctl_value(oid)))
|
|
123
|
+
|
|
124
|
+
def set_ets_min_rate(self, interface_name: str, ets: List[int]) -> None:
|
|
125
|
+
"""Configure ETS on an interface.
|
|
126
|
+
|
|
127
|
+
:param interface_name: Network interface
|
|
128
|
+
:param ets: ETS bandwidth table (list of 8 elements of ints)
|
|
129
|
+
"""
|
|
130
|
+
oid = (
|
|
131
|
+
f"dev.{self._sysctl.get_driver_name(interface_name)}."
|
|
132
|
+
f"{self._sysctl.get_driver_interface_number(interface_name)}.ets_min_rate"
|
|
133
|
+
)
|
|
134
|
+
self._sysctl.set_sysctl_value(oid, value=str(",".join(map(str, ets))))
|
|
135
|
+
|
|
136
|
+
def get_ets_min_rate(self, interface_name: str) -> List[int]:
|
|
137
|
+
"""Read current ETS bandwidth table.
|
|
138
|
+
|
|
139
|
+
:param interface_name: Name of the network interface
|
|
140
|
+
:return: Current ETS bandwidth table configured on the interface
|
|
141
|
+
"""
|
|
142
|
+
oid = (
|
|
143
|
+
f"dev.{self._sysctl.get_driver_name(interface_name)}."
|
|
144
|
+
f"{self._sysctl.get_driver_interface_number(interface_name)}.ets_min_rate"
|
|
145
|
+
)
|
|
146
|
+
return list(map(int, str(self._sysctl.get_sysctl_value(oid)).split(",")))
|