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 ADDED
@@ -0,0 +1,6 @@
1
+ # Copyright (C) 2025 Intel Corporation
2
+ # SPDX-License-Identifier: MIT
3
+ """Module for MFD DCB."""
4
+
5
+ from .mfd_dcb import Dcb
6
+ from .dcbtool_wrapper import DcbTool
mfd_dcb/const.py ADDED
@@ -0,0 +1,7 @@
1
+ # Copyright (C) 2025 Intel Corporation
2
+ # SPDX-License-Identifier: MIT
3
+ """Const for DCBmodule."""
4
+
5
+ PFC_STATS = ["GLPRT_PXONRXCNT", "GLPRT_PXOFFRXCNT", "GLPRT_PXONTXCNT", "GLPRT_PXOFFTXCNT"]
6
+
7
+ QOS_ENABLED = ["*QOS"]
@@ -0,0 +1,12 @@
1
+ # Copyright (C) 2025 Intel Corporation
2
+ # SPDX-License-Identifier: MIT
3
+ """Module for DCB common data structures."""
4
+
5
+ from enum import Enum
6
+
7
+
8
+ class State(Enum):
9
+ """States."""
10
+
11
+ ENABLED = "enabled"
12
+ DISABLED = "disabled"
@@ -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(",")))