Skip to content

Commit dfad0fc

Browse files
Merge branch 'sonic-net:master' into graceful-shutdown
2 parents b959da9 + 9204d7e commit dfad0fc

File tree

5 files changed

+362
-36
lines changed

5 files changed

+362
-36
lines changed

sonic-xcvrd/tests/test_xcvrd.py

Lines changed: 232 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -468,26 +468,65 @@ def test_is_npu_si_settings_update_required(self):
468468
@patch('xcvrd.xcvrd_utilities.port_event_helper.PortMapping.logical_port_name_to_physical_port_list', MagicMock(return_value=[0]))
469469
@patch('xcvrd.xcvrd._wrapper_get_transceiver_firmware_info', MagicMock(return_value={'active_firmware': '2.1.1',
470470
'inactive_firmware': '1.2.4'}))
471-
@patch('xcvrd.xcvrd._wrapper_is_flat_memory')
471+
@patch('xcvrd.xcvrd._wrapper_is_flat_memory', MagicMock(return_value=False))
472472
@patch('xcvrd.xcvrd._wrapper_get_presence')
473-
def test_post_port_sfp_firmware_info_to_db(self, mock_get_presence, mock_is_flat_memory):
473+
def test_post_port_sfp_firmware_info_to_db(self, mock_get_presence):
474474
logical_port_name = "Ethernet0"
475475
port_mapping = PortMapping()
476+
port_mapping.get_physical_to_logical = MagicMock(return_value=["Ethernet0", "Ethernet4"])
476477
mock_sfp_obj_dict = MagicMock()
477478
stop_event = threading.Event()
478479
mock_cmis_manager = MagicMock()
479480
dom_info_update = DomInfoUpdateTask(DEFAULT_NAMESPACE, port_mapping, mock_sfp_obj_dict, stop_event, mock_cmis_manager)
480481
firmware_info_tbl = Table("STATE_DB", TRANSCEIVER_FIRMWARE_INFO_TABLE)
482+
483+
# Test 1: stop_event is set - should not update table
481484
stop_event.set()
482485
dom_info_update.post_port_sfp_firmware_info_to_db(logical_port_name, port_mapping, firmware_info_tbl, stop_event)
483486
assert firmware_info_tbl.get_size() == 0
487+
488+
# Test 2: transceiver not present - should not update table
484489
stop_event.clear()
485490
mock_get_presence.return_value = False
486491
dom_info_update.post_port_sfp_firmware_info_to_db(logical_port_name, port_mapping, firmware_info_tbl, stop_event)
487492
assert firmware_info_tbl.get_size() == 0
493+
494+
# Test 3: transceiver present - should update table for both logical ports
488495
mock_get_presence.return_value = True
489496
dom_info_update.post_port_sfp_firmware_info_to_db(logical_port_name, port_mapping, firmware_info_tbl, stop_event)
497+
# Verify firmware info is posted for Ethernet0 (2 entries: active + inactive firmware)
490498
assert firmware_info_tbl.get_size_for_key(logical_port_name) == 2
499+
# Verify firmware info is also posted for Ethernet4 (2 entries: active + inactive firmware)
500+
assert firmware_info_tbl.get_size_for_key("Ethernet4") == 2
501+
# Verify total table has 2 logical ports (keys)
502+
assert firmware_info_tbl.get_size() == 2
503+
504+
@patch('xcvrd.xcvrd_utilities.port_event_helper.PortMapping.logical_port_name_to_physical_port_list', MagicMock(return_value=[0]))
505+
@patch('xcvrd.xcvrd._wrapper_get_transceiver_firmware_info', MagicMock(return_value={'active_firmware': '2.1.1',
506+
'inactive_firmware': '1.2.4'}))
507+
@patch('xcvrd.xcvrd._wrapper_is_flat_memory', MagicMock(return_value=False))
508+
@patch('xcvrd.xcvrd._wrapper_get_presence')
509+
def test_post_port_sfp_firmware_info_to_db_lport_list_None(self, mock_get_presence):
510+
logical_port_name = "Ethernet0"
511+
port_mapping = PortMapping()
512+
port_mapping.get_physical_to_logical = MagicMock(return_value=None)
513+
port_mapping.logical_port_name_to_physical_port_list = MagicMock(return_value=[0])
514+
mock_sfp_obj_dict = MagicMock()
515+
stop_event = threading.Event()
516+
mock_cmis_manager = MagicMock()
517+
dom_info_update = DomInfoUpdateTask(DEFAULT_NAMESPACE, port_mapping, mock_sfp_obj_dict, stop_event, mock_cmis_manager)
518+
firmware_info_tbl = MagicMock()
519+
firmware_info_tbl.get_size.return_value = 0
520+
stop_event.set()
521+
dom_info_update.post_port_sfp_firmware_info_to_db(logical_port_name, port_mapping, firmware_info_tbl, stop_event)
522+
assert firmware_info_tbl.get_size() == 0
523+
stop_event.clear()
524+
mock_get_presence.return_value = False
525+
dom_info_update.post_port_sfp_firmware_info_to_db(logical_port_name, port_mapping, firmware_info_tbl, stop_event)
526+
assert firmware_info_tbl.get_size() == 0
527+
mock_get_presence.return_value = True
528+
dom_info_update.post_port_sfp_firmware_info_to_db(logical_port_name, port_mapping, firmware_info_tbl, stop_event)
529+
assert firmware_info_tbl.set.call_count == 0
491530

492531
def test_post_port_dom_sensor_info_to_db(self):
493532
def mock_get_transceiver_dom_sensor_real_value(physical_port):
@@ -3634,6 +3673,7 @@ def test_DomInfoUpdateTask_task_worker(self, mock_post_pm_info,
36343673
task.status_db_utils.post_port_transceiver_hw_status_to_db = MagicMock()
36353674
task.status_db_utils.post_port_transceiver_hw_status_flags_to_db = MagicMock()
36363675
task.vdm_utils.is_transceiver_vdm_supported = MagicMock(return_value=True)
3676+
task.xcvrd_utils.is_transceiver_lpmode_on = MagicMock(return_value=False)
36373677
task.vdm_db_utils = MagicMock()
36383678
task.vdm_db_utils.post_port_vdm_real_values_to_db = MagicMock()
36393679
task.task_worker()
@@ -3698,6 +3738,7 @@ def test_DomInfoUpdateTask_task_worker_vdm_failure(self, mock_post_pm_info):
36983738
task.vdm_utils._unfreeze_vdm_stats_and_confirm = MagicMock(return_value=True)
36993739
task.vdm_db_utils.post_port_vdm_real_values_to_db = MagicMock()
37003740
task.vdm_db_utils.post_port_vdm_flags_to_db = MagicMock()
3741+
task.xcvrd_utils.is_transceiver_lpmode_on = MagicMock(return_value=False)
37013742
task.task_worker()
37023743
assert task.vdm_utils._unfreeze_vdm_stats_and_confirm.call_count == 1
37033744
assert task.vdm_db_utils.post_port_vdm_real_values_to_db.call_count == 0
@@ -4659,6 +4700,33 @@ def test_is_transceiver_flat_memory(self):
46594700
mock_api.is_flat_memory = MagicMock(side_effect=NotImplementedError)
46604701
assert xcvrd_util.is_transceiver_flat_memory(1)
46614702

4703+
def test_is_transceiver_lpmode_on(self):
4704+
from xcvrd.xcvrd_utilities.utils import XCVRDUtils
4705+
mock_sfp = MagicMock()
4706+
xcvrd_util = XCVRDUtils({1: mock_sfp}, MagicMock())
4707+
4708+
# Test case where get_xcvr_api returns None
4709+
mock_sfp.get_lpmode = MagicMock(return_value=None)
4710+
assert not xcvrd_util.is_transceiver_lpmode_on(1)
4711+
4712+
# Test case where get_lpmode returns True
4713+
mock_sfp.get_lpmode = MagicMock(return_value=True)
4714+
assert xcvrd_util.is_transceiver_lpmode_on(1)
4715+
4716+
# Test case where get_lpmode returns False
4717+
4718+
mock_sfp.get_lpmode = MagicMock(return_value=False)
4719+
assert not xcvrd_util.is_transceiver_lpmode_on(1)
4720+
4721+
# Test case where get_xcvr_api raises KeyError
4722+
xcvrd_util.sfp_obj_dict = {}
4723+
assert not xcvrd_util.is_transceiver_lpmode_on(1)
4724+
4725+
# Test case where is_flat_memory raises NotImplementedError
4726+
xcvrd_util.sfp_obj_dict = {1: mock_sfp}
4727+
mock_sfp.get_lpmode = MagicMock(side_effect=NotImplementedError)
4728+
assert not xcvrd_util.is_transceiver_lpmode_on(1)
4729+
46624730
@patch('time.sleep', MagicMock())
46634731
@patch('xcvrd.xcvrd.XcvrTableHelper', MagicMock())
46644732
@patch('xcvrd.xcvrd._wrapper_soak_sfp_insert_event', MagicMock())
@@ -4772,3 +4840,165 @@ def wait_until(total_wait_time, interval, call_back, *args, **kwargs):
47724840
time.sleep(interval)
47734841
wait_time += interval
47744842
return False
4843+
4844+
class TestOpticSiParser(object):
4845+
def test_match_optics_si_key_regex_error(self):
4846+
"""Test _match_optics_si_key with invalid regex pattern (lines 31-32, 34)"""
4847+
from xcvrd.xcvrd_utilities.optics_si_parser import _match_optics_si_key
4848+
4849+
# Test with invalid regex pattern that causes re.error
4850+
dict_key = "[invalid regex" # Unclosed bracket causes regex error
4851+
key = "VENDOR-1234"
4852+
vendor_name_str = "VENDOR"
4853+
4854+
# Should fall back to string comparison and return True for exact match
4855+
result = _match_optics_si_key(dict_key, key, vendor_name_str)
4856+
assert result == False # No exact string match
4857+
4858+
# Test with exact string match after regex error
4859+
result = _match_optics_si_key(dict_key, dict_key, vendor_name_str)
4860+
assert result == True # Exact string match
4861+
4862+
def test_match_optics_si_key_fallback_string_match(self):
4863+
"""Test _match_optics_si_key fallback string comparison (line 37)"""
4864+
from xcvrd.xcvrd_utilities.optics_si_parser import _match_optics_si_key
4865+
4866+
# Test with invalid regex that falls back to string comparison
4867+
dict_key = "[invalid"
4868+
key = "VENDOR-1234"
4869+
vendor_name_str = "VENDOR"
4870+
4871+
# Test exact key match
4872+
result = _match_optics_si_key(key, key, vendor_name_str)
4873+
assert result == True
4874+
4875+
# Test vendor name match
4876+
result = _match_optics_si_key(vendor_name_str, key, vendor_name_str)
4877+
assert result == True
4878+
4879+
# Test split key match
4880+
result = _match_optics_si_key("VENDOR", key, vendor_name_str)
4881+
assert result == True
4882+
4883+
def test_get_port_media_settings_speed_key_missing(self):
4884+
"""Test _get_port_media_settings when SPEED_KEY not in optics_si_dict (line 126)"""
4885+
from xcvrd.xcvrd_utilities.optics_si_parser import _get_port_media_settings
4886+
import xcvrd.xcvrd_utilities.optics_si_parser as parser
4887+
4888+
original_dict = parser.g_optics_si_dict
4889+
parser.g_optics_si_dict = {
4890+
'PORT_MEDIA_SETTINGS': {
4891+
'5': {
4892+
# Missing SPEED_KEY (25G_SPEED)
4893+
}
4894+
}
4895+
}
4896+
4897+
try:
4898+
result = _get_port_media_settings(5, 25, "VENDOR-1234", "VENDOR", {'default': 'value'})
4899+
assert result == {'default': 'value'}
4900+
finally:
4901+
parser.g_optics_si_dict = original_dict
4902+
4903+
def test_get_module_vendor_key_api_none(self):
4904+
"""Test get_module_vendor_key when API is None (line 152)"""
4905+
from xcvrd.xcvrd_utilities.optics_si_parser import get_module_vendor_key
4906+
4907+
# Mock SFP with None API
4908+
mock_sfp = MagicMock()
4909+
mock_sfp.get_xcvr_api.return_value = None
4910+
4911+
result = get_module_vendor_key(1, mock_sfp)
4912+
assert result is None
4913+
4914+
def test_get_module_vendor_key_vendor_name_none(self):
4915+
"""Test get_module_vendor_key when vendor name is None"""
4916+
from xcvrd.xcvrd_utilities.optics_si_parser import get_module_vendor_key
4917+
4918+
# Mock API with None vendor name
4919+
mock_api = MagicMock()
4920+
mock_api.get_manufacturer.return_value = None
4921+
mock_sfp = MagicMock()
4922+
mock_sfp.get_xcvr_api.return_value = mock_api
4923+
4924+
result = get_module_vendor_key(1, mock_sfp)
4925+
assert result is None
4926+
4927+
def test_get_module_vendor_key_vendor_pn_none(self):
4928+
"""Test get_module_vendor_key when vendor part number is None"""
4929+
from xcvrd.xcvrd_utilities.optics_si_parser import get_module_vendor_key
4930+
4931+
# Mock API with None vendor part number
4932+
mock_api = MagicMock()
4933+
mock_api.get_manufacturer.return_value = "VENDOR"
4934+
mock_api.get_model.return_value = None
4935+
mock_sfp = MagicMock()
4936+
mock_sfp.get_xcvr_api.return_value = mock_api
4937+
4938+
result = get_module_vendor_key(1, mock_sfp)
4939+
assert result is None
4940+
4941+
def test_get_port_media_settings_no_values_with_empty_default(self):
4942+
"""Test _get_port_media_settings logging when port exists but has empty config and no default values"""
4943+
from xcvrd.xcvrd_utilities.optics_si_parser import _get_port_media_settings
4944+
import xcvrd.xcvrd_utilities.optics_si_parser as parser
4945+
4946+
original_dict = parser.g_optics_si_dict
4947+
4948+
# Set up scenario where:
4949+
# 1. Port exists in PORT_MEDIA_SETTINGS but has empty configuration
4950+
# 2. This makes len(optics_si_dict) == 0
4951+
# 3. Default dict is empty (len(default_dict) == 0)
4952+
parser.g_optics_si_dict = {
4953+
'PORT_MEDIA_SETTINGS': {
4954+
'5': {} # Port exists but is empty - this triggers len(optics_si_dict) == 0
4955+
}
4956+
}
4957+
4958+
try:
4959+
# This should trigger the log_info line at lines 119-121
4960+
# since len(optics_si_dict) == 0 and len(default_dict) == 0
4961+
result = _get_port_media_settings(5, 25, "VENDOR-1234", "VENDOR", {})
4962+
4963+
# Should return empty dict when no values found and no defaults
4964+
assert result == {}
4965+
finally:
4966+
parser.g_optics_si_dict = original_dict
4967+
4968+
def test_load_optics_si_settings_no_file(self):
4969+
"""Test load_optics_si_settings when no file exists"""
4970+
from xcvrd.xcvrd_utilities.optics_si_parser import load_optics_si_settings
4971+
4972+
with patch('sonic_py_common.device_info.get_paths_to_platform_and_hwsku_dirs',
4973+
return_value=('/nonexistent/platform', '/nonexistent/hwsku')):
4974+
with patch('os.path.isfile', return_value=False):
4975+
result = load_optics_si_settings()
4976+
assert result == {}
4977+
4978+
def test_optics_si_present_empty_dict(self):
4979+
"""Test optics_si_present when global dict is empty"""
4980+
from xcvrd.xcvrd_utilities.optics_si_parser import optics_si_present
4981+
import xcvrd.xcvrd_utilities.optics_si_parser as parser
4982+
4983+
original_dict = parser.g_optics_si_dict
4984+
parser.g_optics_si_dict = {}
4985+
4986+
try:
4987+
result = optics_si_present()
4988+
assert result == False
4989+
finally:
4990+
parser.g_optics_si_dict = original_dict
4991+
4992+
def test_optics_si_present_with_data(self):
4993+
"""Test optics_si_present when global dict has data"""
4994+
from xcvrd.xcvrd_utilities.optics_si_parser import optics_si_present
4995+
import xcvrd.xcvrd_utilities.optics_si_parser as parser
4996+
4997+
original_dict = parser.g_optics_si_dict
4998+
parser.g_optics_si_dict = {'some': 'data'}
4999+
5000+
try:
5001+
result = optics_si_present()
5002+
assert result == True
5003+
finally:
5004+
parser.g_optics_si_dict = original_dict

sonic-xcvrd/xcvrd/dom/dom_mgr.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,13 @@ def post_port_sfp_firmware_info_to_db(self, logical_port_name, port_mapping, tab
160160
firmware_info_cache[physical_port] = transceiver_firmware_info_dict
161161
if transceiver_firmware_info_dict:
162162
fvs = swsscommon.FieldValuePairs([(k, v) for k, v in transceiver_firmware_info_dict.items()])
163-
table.set(physical_port_name, fvs)
163+
# For firmware info, we update all logical ports associated with this physical port
164+
logical_port_list = self.port_mapping.get_physical_to_logical(physical_port)
165+
if logical_port_list is None:
166+
self.log_warning("Got unknown physical port index {} while updating firmware info".format(physical_port))
167+
continue
168+
for logical_port in logical_port_list:
169+
table.set(logical_port, fvs)
164170
else:
165171
return xcvrd.SFP_EEPROM_NOT_READY
166172

@@ -297,7 +303,7 @@ def task_worker(self):
297303
self.log_warning("Got exception {} while processing transceiver status hw flags for "
298304
"port {}, ignored".format(repr(e), logical_port_name))
299305
continue
300-
if self.vdm_utils.is_transceiver_vdm_supported(physical_port):
306+
if self.vdm_utils.is_transceiver_vdm_supported(physical_port) and (not self.xcvrd_utils.is_transceiver_lpmode_on(physical_port)):
301307
# Freeze VDM stats before reading VDM values
302308
with self.vdm_utils.vdm_freeze_context(physical_port) as vdm_frozen:
303309
if not vdm_frozen:

sonic-xcvrd/xcvrd/xcvrd.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -464,6 +464,16 @@ def is_warm_reboot_enabled():
464464
is_warm_start = warmstart.isWarmStart()
465465
return is_warm_start
466466

467+
def is_syncd_warm_restore_complete():
468+
"""
469+
This function determins whether syncd's restore count is not 0, which indicates warm-reboot
470+
to avoid premature config push by xcvrd that caused port flaps.
471+
"""
472+
state_db = daemon_base.db_connect("STATE_DB")
473+
restore_count = state_db.hget("WARM_RESTART_TABLE|syncd", "restore_count") or "0"
474+
system_enabled = state_db.hget("WARM_RESTART_ENABLE_TABLE|system", "enable")
475+
return int(restore_count.strip()) > 0 or "true" in system_enabled
476+
467477
#
468478
# Helper classes ===============================================================
469479
#
@@ -1596,7 +1606,7 @@ def _post_port_sfp_info_and_dom_thr_to_db_once(self, port_mapping, xcvr_table_he
15961606
transceiver_dict = {}
15971607
retry_eeprom_set = set()
15981608

1599-
is_warm_start = is_warm_reboot_enabled()
1609+
is_warm_start = is_syncd_warm_restore_complete()
16001610
# Post all the current interface sfp/dom threshold info to STATE_DB
16011611
logical_port_list = port_mapping.logical_port_list
16021612
for logical_port_name in logical_port_list:
@@ -2324,7 +2334,7 @@ def init(self):
23242334
def deinit(self):
23252335
self.log_info("Start daemon deinit...")
23262336

2327-
is_warm_fast_reboot = is_warm_reboot_enabled() or is_fast_reboot_enabled()
2337+
is_warm_fast_reboot = is_syncd_warm_restore_complete() or is_fast_reboot_enabled()
23282338

23292339
# Delete all the information from DB and then exit
23302340
port_mapping_data = port_event_helper.get_port_mapping(self.namespaces)

0 commit comments

Comments
 (0)