Skip to content

Commit 9fd8c3c

Browse files
authored
[sfputil] Use host lane mask as part of rx-output enable/disable (#3911)
* [sfputil] Use host lane mask as part of rx-output enable/disable Signed-off-by: Mihir Patel <patelmi@microsoft.com> * Fixed pre-commit failures * Fixed pre-commit failure * Modified the state restore logic in case of failure * Removed output restoration in case of an error * Modified get_subport to return 0 if subport field does not exists * Returning integer in get_subport --------- Signed-off-by: Mihir Patel <patelmi@microsoft.com>
1 parent 3e157a2 commit 9fd8c3c

3 files changed

Lines changed: 135 additions & 78 deletions

File tree

sfputil/debug.py

Lines changed: 52 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import sys
2+
import time
23
import click
34
import utilities_common.cli as clicommon
45
from utilities_common import platform_sfputil_helper
@@ -19,6 +20,8 @@
1920
ERROR_NOT_IMPLEMENTED = 5
2021
ERROR_INVALID_PORT = 6
2122

23+
CMIS_MAX_CHANNELS = 8
24+
TX_RX_OUTPUT_UPDATE_WAIT_TIME = 2 # seconds
2225

2326
@click.group(cls=clicommon.AliasedGroup)
2427
def debug():
@@ -82,18 +85,61 @@ def set_output(port_name, enable, direction):
8285
Enable or disable TX/RX output based on direction ('tx' or 'rx').
8386
"""
8487
sfp = get_sfp_object(port_name)
88+
try:
89+
api = sfp.get_xcvr_api()
90+
except NotImplementedError:
91+
click.echo(f"{port_name}: This functionality is not implemented")
92+
sys.exit(ERROR_NOT_IMPLEMENTED)
8593

8694
subport = get_subport(port_name)
8795

88-
media_lane_count = get_media_lane_count(port_name)
89-
90-
lane_mask = get_subport_lane_mask(int(subport), int(media_lane_count))
91-
9296
try:
9397
if direction == "tx":
94-
sfp.tx_disable_channel(lane_mask, enable == "disable")
98+
lane_count = get_media_lane_count(port_name)
99+
disable_func = sfp.tx_disable_channel
100+
get_status_func = api.get_tx_output_status
101+
status_key = "TxOutputStatus"
95102
elif direction == "rx":
96-
sfp.rx_disable_channel(lane_mask, enable == "disable")
103+
lane_count = get_host_lane_count(port_name)
104+
disable_func = sfp.rx_disable_channel
105+
get_status_func = api.get_rx_output_status
106+
status_key = "RxOutputStatus"
107+
108+
lane_mask = get_subport_lane_mask(int(subport), int(lane_count))
109+
if not disable_func(lane_mask, enable == "disable"):
110+
click.echo(f"{port_name}: {direction.upper()} disable failed for subport {subport}")
111+
sys.exit(EXIT_FAIL)
112+
113+
time.sleep(TX_RX_OUTPUT_UPDATE_WAIT_TIME)
114+
115+
output_dict = get_status_func()
116+
if output_dict is None:
117+
click.echo(f"{port_name}: {direction.upper()} output status not available for subport {subport}")
118+
sys.exit(EXIT_FAIL)
119+
120+
for lane in range(1, CMIS_MAX_CHANNELS + 1):
121+
if lane_mask & (1 << (lane - 1)):
122+
lane_status = output_dict.get(f'{status_key}{lane}')
123+
if lane_status is None:
124+
click.echo(
125+
f"{port_name}: {direction.upper()} output status not available for "
126+
f"lane {lane} on subport {subport}"
127+
)
128+
sys.exit(EXIT_FAIL)
129+
if enable == "disable":
130+
if lane_status:
131+
click.echo(
132+
f"{port_name}: {direction.upper()} output on lane {lane} is still "
133+
f"enabled on subport {subport}. Restoring state."
134+
)
135+
sys.exit(EXIT_FAIL)
136+
else:
137+
if not lane_status:
138+
click.echo(
139+
f"{port_name}: {direction.upper()} output on lane {lane} is still disabled "
140+
f"on subport {subport}. Restoring state."
141+
)
142+
sys.exit(EXIT_FAIL)
97143

98144
click.echo(
99145
f"{port_name}: {direction.upper()} output "

tests/sfputil_test.py

Lines changed: 71 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1821,83 +1821,87 @@ def test_debug_loopback(self, mock_sonic_v2_connector, mock_config_db_connector,
18211821
assert result.output == 'Error: \nEthernet0: subport is not present in CONFIG_DB\n'
18221822
assert result.exit_code == EXIT_FAIL
18231823

1824-
# Test for 'tx-output' command
1825-
@patch('sfputil.debug.get_sfp_object')
1826-
@patch('utilities_common.platform_sfputil_helper.ConfigDBConnector')
1827-
@patch('utilities_common.platform_sfputil_helper.SonicV2Connector')
1828-
@patch('sonic_py_common.multi_asic.get_front_end_namespaces', MagicMock(return_value=['']))
1829-
def test_tx_output(self, mock_sonic_v2_connector, mock_config_db_connector, mock_get_sfp_object):
1830-
"""Test for tx-output command"""
1831-
mock_sfp = MagicMock()
1832-
mock_get_sfp_object.return_value = mock_sfp # Ensure get_sfp_object returns the mock
1833-
mock_sonic_v2_connector.return_value = MagicMock()
1824+
@pytest.mark.parametrize(
1825+
"direction, lane_count, enable, disable_func_result, output_dict, expected_echo, expected_exit",
1826+
[
1827+
# TX disable success
1828+
(
1829+
"tx", 2, "disable", True, {"TxOutputStatus1": False, "TxOutputStatus2": False},
1830+
"TX output disabled", None
1831+
),
1832+
# RX enable success
1833+
("rx", 1, "enable", True, {"RxOutputStatus1": True}, "RX output enabled", None),
1834+
# TX disable fails to disable
1835+
("tx", 1, "disable", True, {"TxOutputStatus1": True}, "TX output on lane 1 is still enabled", SystemExit),
1836+
# RX enable fails to enable
1837+
("rx", 1, "enable", True, {"RxOutputStatus1": False}, "RX output on lane 1 is still disabled", SystemExit),
1838+
# TX disable_func returns False
1839+
("tx", 1, "disable", False, {}, "TX disable failed", SystemExit),
1840+
# RX output_dict is None
1841+
("rx", 1, "disable", True, None, "RX output status not available", SystemExit),
1842+
]
1843+
)
1844+
@patch("sfputil.debug.get_sfp_object")
1845+
@patch("sfputil.debug.get_subport")
1846+
@patch("sfputil.debug.get_media_lane_count")
1847+
@patch("sfputil.debug.get_host_lane_count")
1848+
@patch("sfputil.debug.time.sleep", return_value=None)
1849+
def test_set_output_cli(
1850+
self,
1851+
mock_sleep,
1852+
mock_get_host_lane_count,
1853+
mock_get_media_lane_count,
1854+
mock_get_subport,
1855+
mock_get_sfp_object,
1856+
direction,
1857+
lane_count,
1858+
enable,
1859+
disable_func_result,
1860+
output_dict,
1861+
expected_echo,
1862+
expected_exit
1863+
):
1864+
from click.testing import CliRunner
1865+
import sfputil.main as sfputil
18341866

1835-
mock_sfp.get_presence.return_value = False
1867+
port_name = "Ethernet0"
1868+
subport = 1
18361869
runner = CliRunner()
18371870

1838-
# Test the case where the module is not applicable
1839-
mock_sfp.get_presence.return_value = True
1840-
mock_sfp.tx_disable_channel = MagicMock(side_effect=AttributeError)
1841-
result = runner.invoke(sfputil.cli.commands['debug'].commands['tx-output'], ["Ethernet0", "enable"])
1842-
assert result.output == 'Ethernet0: TX disable is not applicable for this module\n'
1843-
assert result.exit_code == ERROR_NOT_IMPLEMENTED
1844-
1845-
# Test the case where enabling/disabling TX works
1846-
mock_sfp.tx_disable_channel = MagicMock(return_value=None)
1847-
result = runner.invoke(sfputil.cli.commands['debug'].commands['tx-output'], ["Ethernet0", "enable"])
1848-
assert result.output == 'Ethernet0: TX output enabled on subport 1\n'
1849-
assert result.exit_code != ERROR_NOT_IMPLEMENTED
1850-
1851-
mock_sfp.tx_disable_channel = MagicMock(return_value=None)
1852-
result = runner.invoke(sfputil.cli.commands['debug'].commands['tx-output'], ["Ethernet0", "disable"])
1853-
assert result.output == 'Ethernet0: TX output disabled on subport 1\n'
1854-
assert result.exit_code != ERROR_NOT_IMPLEMENTED
1871+
mock_get_subport.return_value = subport
1872+
mock_get_media_lane_count.return_value = lane_count
1873+
mock_get_host_lane_count.return_value = lane_count
18551874

1856-
# Test the case where there is a failure while disabling TX
1857-
mock_sfp.tx_disable_channel = MagicMock(side_effect=Exception("TX disable failed"))
1858-
result = runner.invoke(sfputil.cli.commands['debug'].commands['tx-output'], ["Ethernet0", "disable"])
1859-
assert result.output == 'Ethernet0: TX disable failed due to TX disable failed\n'
1860-
assert result.exit_code == EXIT_FAIL
1861-
1862-
# Test for 'rx-output' command
1863-
@patch('sfputil.debug.get_sfp_object')
1864-
@patch('utilities_common.platform_sfputil_helper.ConfigDBConnector')
1865-
@patch('utilities_common.platform_sfputil_helper.SonicV2Connector')
1866-
@patch('sonic_py_common.multi_asic.get_front_end_namespaces', MagicMock(return_value=['']))
1867-
def test_rx_output(self, mock_sonic_v2_connector, mock_config_db_connector, mock_get_sfp_object):
1868-
"""Test for rx-output command"""
1875+
# Mock SFP and API
18691876
mock_sfp = MagicMock()
1870-
mock_get_sfp_object.return_value = mock_sfp # Ensure get_sfp_object returns the mock
1871-
mock_sonic_v2_connector.return_value = MagicMock()
1872-
1873-
mock_sfp.get_presence.return_value = False
1874-
runner = CliRunner()
1875-
1876-
# Test the case where the module is not applicable
1877-
mock_sfp.get_presence.return_value = True
1878-
mock_sfp.rx_disable_channel = MagicMock(side_effect=AttributeError)
1879-
result = runner.invoke(sfputil.cli.commands['debug'].commands['rx-output'], ["Ethernet0", "enable"])
1880-
assert result.output == 'Ethernet0: RX disable is not applicable for this module\n'
1881-
assert result.exit_code == ERROR_NOT_IMPLEMENTED
1877+
mock_api = MagicMock()
1878+
if direction == "tx":
1879+
mock_sfp.tx_disable_channel.return_value = disable_func_result
1880+
mock_api.get_tx_output_status.return_value = output_dict
1881+
elif direction == "rx":
1882+
mock_sfp.rx_disable_channel.return_value = disable_func_result
1883+
mock_api.get_rx_output_status.return_value = output_dict
1884+
mock_sfp.get_xcvr_api.return_value = mock_api
1885+
mock_get_sfp_object.return_value = mock_sfp
18821886

1883-
# Test the case where enabling/disabling RX works
1884-
mock_sfp.rx_disable_channel = MagicMock(return_value=None)
1885-
result = runner.invoke(sfputil.cli.commands['debug'].commands['rx-output'], ["Ethernet0", "enable"])
1886-
assert result.output == 'Ethernet0: RX output enabled on subport 1\n'
1887-
assert result.exit_code != ERROR_NOT_IMPLEMENTED
1887+
# Map direction to CLI command
1888+
direction_to_cli = {"tx": "tx-output", "rx": "rx-output"}
1889+
cli_cmd = direction_to_cli.get(direction, direction)
18881890

1889-
mock_sfp.rx_disable_channel = MagicMock(return_value=None)
1890-
result = runner.invoke(sfputil.cli.commands['debug'].commands['rx-output'], ["Ethernet0", "disable"])
1891-
assert result.output == 'Ethernet0: RX output disabled on subport 1\n'
1892-
assert result.exit_code != ERROR_NOT_IMPLEMENTED
1891+
# Run CLI and check output/exit
1892+
result = runner.invoke(sfputil.cli.commands['debug'].commands.get(cli_cmd, lambda *a, **k: None),
1893+
[port_name, enable])
18931894

1894-
# Test the case where there is a failure while disabling RX
1895-
mock_sfp.rx_disable_channel = MagicMock(side_effect=Exception("RX disable failed"))
1896-
result = runner.invoke(sfputil.cli.commands['debug'].commands['rx-output'], ["Ethernet0", "disable"])
1897-
assert result.output == 'Ethernet0: RX disable failed due to RX disable failed\n'
1898-
assert result.exit_code == EXIT_FAIL
1895+
if expected_exit:
1896+
assert result.exit_code != 0
1897+
assert expected_echo in result.output
1898+
else:
1899+
assert result.exit_code == 0
1900+
assert expected_echo in result.output
18991901

19001902
@pytest.mark.parametrize("subport, lane_count, expected_mask", [
1903+
(0, 2, 0x3),
1904+
(0, 4, 0xf),
19011905
(1, 1, 0x1),
19021906
(1, 4, 0xf),
19031907
(2, 1, 0x2),

utilities_common/platform_sfputil_helper.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ def get_subport_lane_mask(subport, lane_count):
150150
int: The lane mask calculated for the given subport and lane count.
151151
"""
152152
# Calculating the lane mask using bitwise operations.
153-
return ((1 << lane_count) - 1) << ((subport - 1) * lane_count)
153+
return ((1 << lane_count) - 1) << ((0 if subport == 0 else subport - 1) * lane_count)
154154

155155

156156
def get_sfp_object(port_name):
@@ -191,7 +191,7 @@ def get_host_lane_count(port_name):
191191

192192
lane_count = get_value_from_db_by_field("STATE_DB", "TRANSCEIVER_INFO", "host_lane_count", port_name)
193193

194-
if lane_count == 0 or lane_count is None:
194+
if lane_count == 0 or lane_count is None or lane_count == '':
195195
click.echo(f"{port_name}: unable to retreive correct host lane count")
196196
sys.exit(EXIT_FAIL)
197197

@@ -202,7 +202,7 @@ def get_media_lane_count(port_name):
202202

203203
lane_count = get_value_from_db_by_field("STATE_DB", "TRANSCEIVER_INFO", "media_lane_count", port_name)
204204

205-
if lane_count == 0 or lane_count is None:
205+
if lane_count == 0 or lane_count is None or lane_count == '':
206206
click.echo(f"{port_name}: unable to retreive correct media lane count")
207207
sys.exit(EXIT_FAIL)
208208

@@ -237,7 +237,12 @@ def get_value_from_db_by_field(db_name, table_name, field, key):
237237
db.connect(getattr(db, db_name)) # Get the corresponding attribute (e.g., STATE_DB) from the connector
238238

239239
# Retrieve the value from the database
240-
return db.get(db_name, f"{table_name}|{key}", field)
240+
value = db.get(db_name, f"{table_name}|{key}", field)
241+
if value is None:
242+
click.echo(f"Field '{field}' not found in table '{table_name}' for key '{key}' in {db_name}.")
243+
return ''
244+
else:
245+
return value
241246
except (TypeError, KeyError, AttributeError) as e:
242247
click.echo(f"Error: {e}")
243248
return None
@@ -277,8 +282,10 @@ def get_subport(port_name):
277282
if subport is None:
278283
click.echo(f"{port_name}: subport is not present in CONFIG_DB")
279284
sys.exit(EXIT_FAIL)
285+
elif subport == '':
286+
subport = 0
280287

281-
return max(int(subport), 1)
288+
return int(subport)
282289

283290

284291
def is_sfp_present(port_name):

0 commit comments

Comments
 (0)