From 604e454e6f54385961b0350cd0bb04a14daf4cc9 Mon Sep 17 00:00:00 2001 From: longhuan-cisco <84595962+longhuan-cisco@users.noreply.github.com> Date: Mon, 30 Sep 2024 21:51:52 -0700 Subject: [PATCH] Improve parsing of media-settings.json for non-CMIS and breakout ports (#533) * Enhance media_settings_parser for 100G xcvr and DPB etc * Revert space change * Cover corner cases * Change log message level * Fix docstring and update name of get_speed_lane_count_and_subport * Address comment * Change to re.fullmatch for lane_speed key --- sonic-xcvrd/tests/test_xcvrd.py | 230 ++++++++++++++---- .../xcvrd_utilities/media_settings_parser.py | 176 ++++++++------ 2 files changed, 290 insertions(+), 116 deletions(-) diff --git a/sonic-xcvrd/tests/test_xcvrd.py b/sonic-xcvrd/tests/test_xcvrd.py index 3141cc749..b21b3d22a 100644 --- a/sonic-xcvrd/tests/test_xcvrd.py +++ b/sonic-xcvrd/tests/test_xcvrd.py @@ -64,6 +64,32 @@ media_settings_extended_format_dict = json.load(f) +# Define some example keys/values of media_settings.json for testing purposes +asic_serdes_si_value_dict = {'lane' + str(i): '0x0000000d' for i in range(4)} +asic_serdes_si_value_dict2 = {'lane' + str(i): '0x0000000a' for i in range(4)} +asic_serdes_si_value_dict3 = {'lane' + str(i): '0x0000000b' for i in range(8)} +asic_serdes_si_value_dict4 = {'lane' + str(i): '0x00000003' for i in range(8)} +asic_serdes_si_value_dict5 = {'lane' + str(i): '0x00000004' for i in range(8)} +asic_serdes_si_settings_example = { + 'idriver': asic_serdes_si_value_dict, + 'pre1': asic_serdes_si_value_dict, + 'ob_m2lp': asic_serdes_si_value_dict, +} +asic_serdes_si_settings_example2 = {'idriver': asic_serdes_si_value_dict2} +asic_serdes_si_settings_example3 = {'main': asic_serdes_si_value_dict3} +asic_serdes_si_settings_example4 = {'main': asic_serdes_si_value_dict4} +asic_serdes_si_settings_example5 = {'idriver': asic_serdes_si_value_dict5} +asic_serdes_si_settings_example3_expected_value_in_db = \ + {attr: ','.join(value_dict.values()) for attr, value_dict in asic_serdes_si_settings_example3.items()} +asic_serdes_si_settings_example3_expected_value_in_db_4_lanes = \ + {attr: ','.join(list(value_dict.values())[:4]) for attr, value_dict in asic_serdes_si_settings_example3.items()} +asic_serdes_si_settings_example4_expected_value_in_db = \ + {attr: ','.join(list(value_dict.values())) for attr, value_dict in asic_serdes_si_settings_example4.items()} +asic_serdes_si_settings_example4_expected_value_in_db_4_lanes = \ + {attr: ','.join(list(value_dict.values())[:4]) for attr, value_dict in asic_serdes_si_settings_example4.items()} +asic_serdes_si_settings_example5_expected_value_in_db = \ + {attr: ','.join(value_dict.values()) for attr, value_dict in asic_serdes_si_settings_example5.items()} + # Creating instances of media_settings.json for testing purposes # Each instance represents a different possible structure for media_settings.json. media_settings_global_range_media_key_lane_speed_si = copy.deepcopy(media_settings_extended_format_dict) @@ -102,7 +128,7 @@ media_settings_global_list_of_ranges_media_key_si['GLOBAL_MEDIA_SETTINGS']['0-15,16-31'] = media_settings_global_list_of_ranges_media_key_si['GLOBAL_MEDIA_SETTINGS'].pop('0-31') media_settings_global_list_of_ranges_media_key_lane_speed_si_with_default_section = copy.deepcopy(media_settings_extended_format_dict) -media_settings_global_list_of_ranges_media_key_lane_speed_si_with_default_section['GLOBAL_MEDIA_SETTINGS']['0-31']['Default'] = {'speed:400GAUI-8': {'idriver': {'lane0': '0x0000000d', 'lane1': '0x0000000d', 'lane2': '0x0000000d', 'lane3': '0x0000000d'}, 'pre1': {'lane0': '0x0000000d', 'lane1': '0x0000000d', 'lane2': '0x0000000d', 'lane3': '0x0000000d'}, 'ob_m2lp': {'lane0': '0x0000000d', 'lane1': '0x0000000d', 'lane2': '0x0000000d', 'lane3': '0x0000000d'}}} +media_settings_global_list_of_ranges_media_key_lane_speed_si_with_default_section['GLOBAL_MEDIA_SETTINGS']['0-31']['Default'] = asic_serdes_si_settings_example media_settings_port_media_key_lane_speed_si = copy.deepcopy(media_settings_extended_format_dict) media_settings_port_media_key_lane_speed_si['PORT_MEDIA_SETTINGS'] = {'7': media_settings_port_media_key_lane_speed_si['GLOBAL_MEDIA_SETTINGS'].pop('0-31')} @@ -134,11 +160,37 @@ media_settings_global_default_port_media_key_lane_speed_si = copy.deepcopy(media_settings_extended_format_dict) port_media_settings_data = {'7': media_settings_global_default_port_media_key_lane_speed_si['GLOBAL_MEDIA_SETTINGS'].pop('0-31')} -media_settings_global_default_port_media_key_lane_speed_si['GLOBAL_MEDIA_SETTINGS'] = {'0-31': {'Default': {'speed:400GAUI-8': {'idriver': {'lane0': '0x0000000d', 'lane1': '0x0000000d', 'lane2': '0x0000000d', 'lane3': '0x0000000d'}, 'pre1': {'lane0': '0x0000000d', 'lane1': '0x0000000d', 'lane2': '0x0000000d', 'lane3': '0x0000000d'}, 'ob_m2lp': {'lane0': '0x0000000d', 'lane1': '0x0000000d', 'lane2': '0x0000000d', 'lane3': '0x0000000d'}}}}} +media_settings_global_default_port_media_key_lane_speed_si['GLOBAL_MEDIA_SETTINGS'] = {'0-31': {'Default': asic_serdes_si_settings_example}} media_settings_global_default_port_media_key_lane_speed_si['PORT_MEDIA_SETTINGS'] = port_media_settings_data media_settings_port_default_media_key_lane_speed_si = copy.deepcopy(media_settings_port_media_key_lane_speed_si) -media_settings_port_default_media_key_lane_speed_si['PORT_MEDIA_SETTINGS']['7']['Default'] = {'speed:400GAUI-8': {'idriver': {'lane0': '0x0000000d', 'lane1': '0x0000000d', 'lane2': '0x0000000d', 'lane3': '0x0000000d'}, 'pre1': {'lane0': '0x0000000d', 'lane1': '0x0000000d', 'lane2': '0x0000000d', 'lane3': '0x0000000d'}, 'ob_m2lp': {'lane0': '0x0000000d', 'lane1': '0x0000000d', 'lane2': '0x0000000d', 'lane3': '0x0000000d'}}} +media_settings_port_default_media_key_lane_speed_si['PORT_MEDIA_SETTINGS']['7']['Default'] = { + LANE_SPEED_DEFAULT_KEY: asic_serdes_si_settings_example, + 'speed:400GAUI-8': asic_serdes_si_settings_example2, +} + +media_settings_optic_copper_si = { + 'GLOBAL_MEDIA_SETTINGS': { + '0-31': { + '(SFP|QSFP(\\+|28|-DD)*)-(?!.*((40|100)GBASE-CR|100G ACC|Active Copper Cable|passive_copper_media_interface)).*': { + 'speed:400GAUI-8': asic_serdes_si_settings_example4, + 'speed:200GAUI-8|100GAUI-4|50GAUI-2|25G': asic_serdes_si_settings_example3, + }, + '(SFP|QSFP(\\+|28|-DD)*)-((40|100)GBASE-CR|100G ACC|Active Copper Cable|passive_copper_media_interface).*': { + 'speed:400GAUI-8|200GAUI-4|100GAUI-2': asic_serdes_si_settings_example3, + 'speed:25G': asic_serdes_si_settings_example4, + LANE_SPEED_DEFAULT_KEY: asic_serdes_si_settings_example5, + }, + }, + '32-63': { + 'INNOLIGHT': asic_serdes_si_settings_example5, + 'Default': { + 'speed:400GAUI-8': asic_serdes_si_settings_example3, + LANE_SPEED_DEFAULT_KEY: asic_serdes_si_settings_example4, + } + }, + } +} media_settings_empty = {} @@ -687,14 +739,44 @@ def test_get_media_settings_key(self, mock_is_cmis_api, mock_chassis): # Test a good 'specification_compliance' value result = media_settings_parser.get_media_settings_key(0, xcvr_info_dict, 100000, 2) - assert result == { 'vendor_key': 'MOLEX-1064141421', 'media_key': 'QSFP+-10GBase-SR-255M', 'lane_speed_key': None } + assert result == { 'vendor_key': 'MOLEX-1064141421', 'media_key': 'QSFP+-10GBase-SR-255M', 'lane_speed_key': 'speed:50G'} # Test a bad 'specification_compliance' value xcvr_info_dict[0]['specification_compliance'] = 'N/A' result = media_settings_parser.get_media_settings_key(0, xcvr_info_dict, 100000, 2) - assert result == { 'vendor_key': 'MOLEX-1064141421', 'media_key': 'QSFP+-*', 'lane_speed_key': None } + assert result == { 'vendor_key': 'MOLEX-1064141421', 'media_key': 'QSFP+-*', 'lane_speed_key': 'speed:50G'} # TODO: Ensure that error message was logged + xcvr_info_dict_for_qsfp28 = { + 0: { + "type": "QSFP28 or later", + "type_abbrv_name": "QSFP28", + "vendor_rev": "05", + "serial": "AAABBBCCCDDD", + "manufacturer": "AVAGO", + "model": "XXX-YYY-ZZZ", + "connector": "MPO 1x12", + "encoding": "64B/66B", + "ext_identifier": "Power Class 4 Module (3.5W max.), CLEI code present in Page 02h, CDR present in TX, CDR present in RX", + "ext_rateselect_compliance": "Unknown", + "cable_type": "Length Cable Assembly(m)", + "cable_length": 50.0, + "nominal_bit_rate": 255, + "specification_compliance": "{'10/40G Ethernet Compliance Code': 'Unknown', 'SONET Compliance Codes': 'Unknown', 'SAS/SATA Compliance Codes': 'Unknown', 'Gigabit Ethernet Compliant Codes': 'Unknown', 'Fibre Channel Link Length': 'Unknown', 'Fibre Channel Transmitter Technology': 'Unknown', 'Fibre Channel Transmission Media': 'Unknown', 'Fibre Channel Speed': 'Unknown', 'Extended Specification Compliance': '100GBASE-SR4 or 25GBASE-SR'}", + "vendor_date": "2020-11-11", + "vendor_oui": "00-77-7a", + "application_advertisement": "N/A", + } + } + result = media_settings_parser.get_media_settings_key( + 0, xcvr_info_dict_for_qsfp28, 100000, 4 + ) + assert result == { + "vendor_key": "AVAGO-XXX-YYY-ZZZ", + "media_key": "QSFP28-100GBASE-SR4 or 25GBASE-SR-50.0M", + "lane_speed_key": "speed:25G", + } + mock_is_cmis_api.return_value = True xcvr_info_dict = { 0: { @@ -723,17 +805,18 @@ def test_get_media_settings_key(self, mock_is_cmis_api, mock_chassis): assert result == { 'vendor_key': 'MOLEX-1064141421', 'media_key': 'QSFP-DD-sm_media_interface', 'lane_speed_key': 'speed:100GBASE-CR2' } @pytest.mark.parametrize("data_found, data, expected", [ - (True, [('speed', '400000'), ('lanes', '1,2,3,4,5,6,7,8'), ('mtu', '9100')], ('400000', 8)), - (True, [('lanes', '1,2,3,4,5,6,7,8'), ('mtu', '9100')], ('0', 0)), - (True, [('speed', '400000'), ('mtu', '9100')], ('0', 0)), - (False, [], ('0', 0)) + (True, [('speed', '400000'), ('lanes', '1,2,3,4,5,6,7,8'), ('mtu', '9100')], (400000, 8, 0)), + (True, [('speed', '25000'), ('lanes', '1'), ('mtu', '9100'), ('subport', '1')], (25000, 1, 1)), + (True, [('lanes', '1,2,3,4,5,6,7,8'), ('mtu', '9100')], (0, 0, 0)), + (True, [('speed', '400000'), ('mtu', '9100')], (0, 0, 0)), + (False, [], (0, 0, 0)) ]) - def test_get_speed_and_lane_count(self, data_found, data, expected): + def test_get_speed_lane_count_and_subport(self, data_found, data, expected): cfg_port_tbl = MagicMock() cfg_port_tbl.get = MagicMock(return_value=(data_found, data)) port = MagicMock() - assert media_settings_parser.get_speed_and_lane_count(port, cfg_port_tbl) == expected + assert media_settings_parser.get_speed_lane_count_and_subport(port, cfg_port_tbl) == expected def test_is_si_per_speed_supported(self): media_dict = { @@ -804,7 +887,7 @@ def test_is_si_per_speed_supported(self): (media_settings_global_list_media_key_si, 7, {'vendor_key': 'UNKOWN', 'media_key': 'QSFP-DD-active_cable_media_interface', 'lane_speed_key': 'UNKOWN'}, {'pre1': {'lane0': '0x00000002', 'lane1': '0x00000002'}, 'main': {'lane0': '0x00000020', 'lane1': '0x00000020'}, 'post1': {'lane0': '0x00000006', 'lane1': '0x00000006'}, 'regn_bfm1n': {'lane0': '0x000000aa', 'lane1': '0x000000aa'}}), (media_settings_global_list_of_ranges_media_key_lane_speed_si, 7, {'vendor_key': 'UNKOWN', 'media_key': 'QSFP-DD-active_cable_media_interface', 'lane_speed_key': 'speed:100GAUI-2'}, {'pre1': {'lane0': '0x00000002', 'lane1': '0x00000002'}, 'main': {'lane0': '0x00000020', 'lane1': '0x00000020'}, 'post1': {'lane0': '0x00000006', 'lane1': '0x00000006'}, 'regn_bfm1n': {'lane0': '0x000000aa', 'lane1': '0x000000aa'}}), (media_settings_global_list_of_ranges_media_key_si, 7, {'vendor_key': 'UNKOWN', 'media_key': 'QSFP-DD-active_cable_media_interface', 'lane_speed_key': 'UNKOWN'}, {'pre1': {'lane0': '0x00000002', 'lane1': '0x00000002'}, 'main': {'lane0': '0x00000020', 'lane1': '0x00000020'}, 'post1': {'lane0': '0x00000006', 'lane1': '0x00000006'}, 'regn_bfm1n': {'lane0': '0x000000aa', 'lane1': '0x000000aa'}}), - (media_settings_global_default_port_media_key_lane_speed_si, 6, {'vendor_key': 'AMPHANOL-5678', 'media_key': 'UNKOWN', 'lane_speed_key': 'speed:100GAUI-2'}, {'speed:400GAUI-8': {'idriver': {'lane0': '0x0000000d', 'lane1': '0x0000000d', 'lane2': '0x0000000d', 'lane3': '0x0000000d'}, 'pre1': {'lane0': '0x0000000d', 'lane1': '0x0000000d', 'lane2': '0x0000000d', 'lane3': '0x0000000d'}, 'ob_m2lp': {'lane0': '0x0000000d', 'lane1': '0x0000000d', 'lane2': '0x0000000d', 'lane3': '0x0000000d'}}}), + (media_settings_global_default_port_media_key_lane_speed_si, 6, {'vendor_key': 'AMPHANOL-5678', 'media_key': 'UNKOWN', 'lane_speed_key': 'speed:100GAUI-2'}, asic_serdes_si_settings_example), (media_settings_port_vendor_key_lane_speed_si, -1, {'vendor_key': 'AMPHANOL-5678', 'media_key': 'UNKOWN', 'lane_speed_key': 'speed:100GAUI-2'}, {}), (media_settings_port_media_key_lane_speed_si, 7, {'vendor_key': 'UNKOWN', 'media_key': 'QSFP-DD-active_cable_media_interface', 'lane_speed_key': 'speed:100GAUI-2'}, {'pre1': {'lane0': '0x00000002', 'lane1': '0x00000002'}, 'main': {'lane0': '0x00000020', 'lane1': '0x00000020'}, 'post1': {'lane0': '0x00000006', 'lane1': '0x00000006'}, 'regn_bfm1n': {'lane0': '0x000000aa', 'lane1': '0x000000aa'}}), (media_settings_port_media_key_lane_speed_si, 7, {'vendor_key': 'UNKOWN', 'media_key': 'QSFP-DD-active_cable_media_interface', 'lane_speed_key': 'MISSING'}, {}), @@ -815,9 +898,9 @@ def test_is_si_per_speed_supported(self): (media_settings_port_generic_vendor_key_lane_speed_si, 7, {'vendor_key': 'GENERIC_VENDOR-1234', 'media_key': 'UNKOWN', 'lane_speed_key': 'MISSING'}, {}), (media_settings_port_vendor_key_si, 7, {'vendor_key': 'AMPHANOL-5678', 'media_key': 'UNKOWN', 'lane_speed_key': 'UNKOWN'}, {'pre1': {'lane0': '0x00000002', 'lane1': '0x00000002'}, 'main': {'lane0': '0x00000020', 'lane1': '0x00000020'}, 'post1': {'lane0': '0x00000006', 'lane1': '0x00000006'}, 'regn_bfm1n': {'lane0': '0x000000aa', 'lane1': '0x000000aa'}}), (media_settings_port_generic_vendor_key_si, 7, {'vendor_key': 'GENERIC_VENDOR-1234', 'media_key': 'UNKOWN', 'lane_speed_key': 'UNKOWN'}, {'pre1': {'lane0': '0x00000002', 'lane1': '0x00000002'}, 'main': {'lane0': '0x00000020', 'lane1': '0x00000020'}, 'post1': {'lane0': '0x00000006', 'lane1': '0x00000006'}, 'regn_bfm1n': {'lane0': '0x000000aa', 'lane1': '0x000000aa'}}), - (media_settings_port_default_media_key_lane_speed_si, 7, {'vendor_key': 'MISSING', 'media_key': 'MISSING', 'lane_speed_key': 'MISSING'}, {'speed:400GAUI-8': {'idriver': {'lane0': '0x0000000d', 'lane1': '0x0000000d', 'lane2': '0x0000000d', 'lane3': '0x0000000d'}, 'pre1': {'lane0': '0x0000000d', 'lane1': '0x0000000d', 'lane2': '0x0000000d', 'lane3': '0x0000000d'}, 'ob_m2lp': {'lane0': '0x0000000d', 'lane1': '0x0000000d', 'lane2': '0x0000000d', 'lane3': '0x0000000d'}}}), - (media_settings_global_default_port_media_key_lane_speed_si, 7, {'vendor_key': 'MISSING', 'media_key': 'MISSING', 'lane_speed_key': 'MISSING'}, {'speed:400GAUI-8': {'idriver': {'lane0': '0x0000000d', 'lane1': '0x0000000d', 'lane2': '0x0000000d', 'lane3': '0x0000000d'}, 'pre1': {'lane0': '0x0000000d', 'lane1': '0x0000000d', 'lane2': '0x0000000d', 'lane3': '0x0000000d'}, 'ob_m2lp': {'lane0': '0x0000000d', 'lane1': '0x0000000d', 'lane2': '0x0000000d', 'lane3': '0x0000000d'}}}), - (media_settings_global_list_of_ranges_media_key_lane_speed_si_with_default_section, 7, {'vendor_key': 'MISSING', 'media_key': 'MISSING', 'lane_speed_key': 'MISSING'}, {'speed:400GAUI-8': {'idriver': {'lane0': '0x0000000d', 'lane1': '0x0000000d', 'lane2': '0x0000000d', 'lane3': '0x0000000d'}, 'pre1': {'lane0': '0x0000000d', 'lane1': '0x0000000d', 'lane2': '0x0000000d', 'lane3': '0x0000000d'}, 'ob_m2lp': {'lane0': '0x0000000d', 'lane1': '0x0000000d', 'lane2': '0x0000000d', 'lane3': '0x0000000d'}}}), + (media_settings_port_default_media_key_lane_speed_si, 7, {'vendor_key': 'MISSING', 'media_key': 'MISSING', 'lane_speed_key': 'MISSING'}, asic_serdes_si_settings_example), + (media_settings_global_default_port_media_key_lane_speed_si, 7, {'vendor_key': 'MISSING', 'media_key': 'MISSING', 'lane_speed_key': 'MISSING'}, asic_serdes_si_settings_example), + (media_settings_global_list_of_ranges_media_key_lane_speed_si_with_default_section, 7, {'vendor_key': 'MISSING', 'media_key': 'MISSING', 'lane_speed_key': 'MISSING'}, asic_serdes_si_settings_example), (media_settings_empty, 7, {'vendor_key': 'AMPHANOL-5678', 'media_key': 'QSFP-DD-active_cable_media_interface', 'lane_speed_key': 'speed:100GAUI-2'}, {}), (media_settings_with_regular_expression_dict, 7, {'vendor_key': 'UNKOWN', 'media_key': 'QSFP28-40GBASE-CR4-1M', 'lane_speed_key': 'UNKOWN'}, {'preemphasis': {'lane0': '0x16440A', 'lane1': '0x16440A', 'lane2': '0x16440A', 'lane3': '0x16440A'}}), (media_settings_with_regular_expression_dict, 7, {'vendor_key': 'UNKOWN', 'media_key': 'QSFP+-40GBASE-CR4-2M', 'lane_speed_key': 'UNKOWN'}, {'preemphasis': {'lane0': '0x18420A', 'lane1': '0x18420A', 'lane2': '0x18420A', 'lane3': '0x18420A'}}), @@ -828,26 +911,65 @@ def test_get_media_settings_value(self, media_settings_dict, port, key, expected result = media_settings_parser.get_media_settings_value(port, key) assert result == expected - @patch('xcvrd.xcvrd.g_dict', media_settings_dict) @patch('xcvrd.xcvrd._wrapper_get_presence', MagicMock(return_value=True)) @patch('xcvrd.xcvrd.XcvrTableHelper', MagicMock()) @patch('xcvrd.xcvrd.XcvrTableHelper.get_cfg_port_tbl', MagicMock()) - @patch('xcvrd.xcvrd_utilities.media_settings_parser.get_media_settings_key', MagicMock(return_value={ 'vendor_key': 'MOLEX-1064141421', 'media_key': 'QSFP+-10GBase-SR-255M', 'lane_speed_key': 'speed:100GBASE-CR2' })) - @patch('xcvrd.xcvrd_utilities.media_settings_parser.get_speed_and_lane_count', MagicMock(return_value=(100000, 2))) + @patch('xcvrd.xcvrd_utilities.media_settings_parser.g_dict', media_settings_optic_copper_si) + @patch('xcvrd.xcvrd_utilities.media_settings_parser.get_media_settings_key', + MagicMock(return_value={'vendor_key': 'INNOLIGHT-X-DDDDD-NNN', 'media_key': 'QSFP-DD-sm_media_interface', 'lane_speed_key': 'speed:400GAUI-8'})) + @patch('xcvrd.xcvrd_utilities.media_settings_parser.get_speed_lane_count_and_subport', MagicMock(return_value=(400000, 8, 0))) def test_notify_media_setting(self): - self._check_notify_media_setting(1) + # Test matching 400G optical transceiver (lane speed 50G) + self._check_notify_media_setting(1, True, asic_serdes_si_settings_example4_expected_value_in_db) + + # Test matching 100G optical transceiver (lane speed 25G), via regular expression lane speed pattern + with patch.multiple('xcvrd.xcvrd_utilities.media_settings_parser', + get_media_settings_key=MagicMock(return_value={'vendor_key':'INNOLIGHT-X-DDDDD-NNN', 'media_key': 'QSFP28-100GBASE-SR4', 'lane_speed_key': 'speed:25G'}), + get_speed_lane_count_and_subport=MagicMock(return_value=(100000, 4, 0))): + self._check_notify_media_setting(1, True, asic_serdes_si_settings_example3_expected_value_in_db_4_lanes) + + # Test matching 100G copper transceiver (lane speed 25G) + with patch.multiple('xcvrd.xcvrd_utilities.media_settings_parser', + get_media_settings_key=MagicMock(return_value={'vendor_key':'INNOLIGHT-X-DDDDD-NNN', 'media_key': 'QSFP28-100GBASE-CR4, 25GBASE-CR CA-25G-L or 50GBASE-CR2 with RS-1.0M', 'lane_speed_key': 'speed:25G'}), + get_speed_lane_count_and_subport=MagicMock(return_value=(100000, 4, 0))): + self._check_notify_media_setting(1, True, asic_serdes_si_settings_example4_expected_value_in_db_4_lanes) + + # Test with lane speed None + with patch.multiple('xcvrd.xcvrd_utilities.media_settings_parser', + get_media_settings_key=MagicMock(return_value={'vendor_key':'INNOLIGHT-X-DDDDD-NNN', 'media_key': 'QSFP28-100GBASE-CR4', 'lane_speed_key': None}), + get_speed_lane_count_and_subport=MagicMock(return_value=(100000, 4, 0))): + self._check_notify_media_setting(1) + + # Test default value in the case of no matched lane speed for 800G copper transceiver (lane speed 100G) + with patch.multiple('xcvrd.xcvrd_utilities.media_settings_parser', + get_media_settings_key=MagicMock(return_value={'vendor_key':'INNOLIGHT-X-DDDDD-NNN', 'media_key': 'QSFP-DD-passive_copper_media_interface', 'lane_speed_key': 'speed:800G-ETC-CR8'}), + get_speed_lane_count_and_subport=MagicMock(return_value=(800000, 8, 0))): + self._check_notify_media_setting(1, True, asic_serdes_si_settings_example5_expected_value_in_db) + + # Test lane speed matching under 'Default' vendor/media for 400G transceiver (lane speed 50G) + with patch.multiple('xcvrd.xcvrd_utilities.media_settings_parser', + get_media_settings_key=MagicMock(return_value={'vendor_key':'Molex', 'media_key': 'QSFP-DD-passive_copper_media_interface', 'lane_speed_key': 'speed:400GAUI-8'}), + get_speed_lane_count_and_subport=MagicMock(return_value=(400000, 8, 0))): + self._check_notify_media_setting(41, True, asic_serdes_si_settings_example3_expected_value_in_db) + + # Test with empty xcvr_info_dict + self._check_notify_media_setting(1, False, None, {}) + + # Test with sfp not present + with patch('xcvrd.xcvrd._wrapper_get_presence', MagicMock(return_value=False)): + self._check_notify_media_setting(1) - @patch('xcvrd.xcvrd.g_dict', media_settings_with_comma_dict) @patch('xcvrd.xcvrd._wrapper_get_presence', MagicMock(return_value=True)) @patch('xcvrd.xcvrd.XcvrTableHelper', MagicMock()) @patch('xcvrd.xcvrd.XcvrTableHelper.get_cfg_port_tbl', MagicMock()) + @patch('xcvrd.xcvrd_utilities.media_settings_parser.g_dict', media_settings_with_comma_dict) @patch('xcvrd.xcvrd_utilities.media_settings_parser.get_media_settings_key', MagicMock(return_value={ 'vendor_key': 'MOLEX-1064141421', 'media_key': 'QSFP+-10GBase-SR-255M', 'lane_speed_key': 'speed:100GBASE-CR2' })) - @patch('xcvrd.xcvrd_utilities.media_settings_parser.get_speed_and_lane_count', MagicMock(return_value=(100000, 2))) + @patch('xcvrd.xcvrd_utilities.media_settings_parser.get_speed_lane_count_and_subport', MagicMock(return_value=(100000, 2, 0))) def test_notify_media_setting_with_comma(self): - self._check_notify_media_setting(1) - self._check_notify_media_setting(6) + self._check_notify_media_setting(1, True, {'preemphasis': ','.join(['0x164509'] * 2)}) + self._check_notify_media_setting(6, True, {'preemphasis': ','.join(['0x124A08'] * 2)}) - def _check_notify_media_setting(self, index): + def _check_notify_media_setting(self, index, expected_found=False, expected_value=None, xcvr_info_dict=None): xcvr_table_helper = XcvrTableHelper(DEFAULT_NAMESPACE) cfg_port_tbl = MagicMock() mock_cfg_table = xcvr_table_helper.get_cfg_port_tbl = MagicMock(return_value=cfg_port_tbl) @@ -862,12 +984,16 @@ def _check_notify_media_setting(self, index): 'specification_compliance': "{'10/40G Ethernet Compliance Code': '10GBase-SR'}", 'type_abbrv_name': 'QSFP+' } - } + } if xcvr_info_dict is None else xcvr_info_dict app_port_tbl = Table("APPL_DB", 'PORT_TABLE') port_mapping = PortMapping() port_change_event = PortChangeEvent('Ethernet0', index, 0, PortChangeEvent.PORT_ADD) port_mapping.handle_port_change_event(port_change_event) media_settings_parser.notify_media_setting(logical_port_name, xcvr_info_dict, app_port_tbl, mock_cfg_table, port_mapping) + found, result = app_port_tbl.get(logical_port_name) + result_dict = dict(result) if result else None + assert found == expected_found + assert result_dict == expected_value @patch('xcvrd.xcvrd_utilities.optics_si_parser.g_optics_si_dict', optics_si_settings_dict) @patch('xcvrd.xcvrd._wrapper_get_presence', MagicMock(return_value=True)) @@ -883,7 +1009,7 @@ def test_fetch_optics_si_setting_with_comma(self): @patch('xcvrd.xcvrd_utilities.optics_si_parser.g_optics_si_dict', port_optics_si_settings) @patch('xcvrd.xcvrd._wrapper_get_presence', MagicMock(return_value=True)) def test_fetch_optics_si_setting_with_port(self): - self._check_fetch_optics_si_setting(1) + self._check_fetch_optics_si_setting(1) @patch('xcvrd.xcvrd_utilities.optics_si_parser.g_optics_si_dict', port_optics_si_settings) @patch('xcvrd.xcvrd_utilities.optics_si_parser.get_module_vendor_key', MagicMock(return_value=(None, None))) @@ -931,7 +1057,6 @@ def test_is_error_sfp_status(self): assert not is_error_block_eeprom_reading(int(SFP_STATUS_INSERTED)) assert not is_error_block_eeprom_reading(int(SFP_STATUS_REMOVED)) - @patch('swsscommon.swsscommon.Select.addSelectable', MagicMock()) @patch('swsscommon.swsscommon.Table') @patch('swsscommon.swsscommon.SubscriberStateTable') @@ -984,7 +1109,6 @@ def handle_port_change_event(self, port_event): assert len(port_change_event_handler.port_event_cache) == 2 assert list(observer.port_event_cache.keys()) == [('Ethernet0', CONFIG_DB, PORT_TABLE), ('Ethernet16', CONFIG_DB, PORT_TABLE)] - @patch('swsscommon.swsscommon.Select.addSelectable', MagicMock()) @patch('swsscommon.swsscommon.SubscriberStateTable') @patch('swsscommon.swsscommon.Select.select') @@ -1517,7 +1641,6 @@ def test_CmisManagerTask_handle_port_change_event(self): task.on_port_update_event(port_change_event) assert len(task.port_dict) == 1 - @patch('xcvrd.xcvrd.XcvrTableHelper') def test_CmisManagerTask_get_configured_freq(self, mock_table_helper): port_mapping = PortMapping() @@ -1810,7 +1933,6 @@ def test_CmisManagerTask_post_port_active_apsel_to_db(self): ret = task.post_port_active_apsel_to_db(mock_xcvr_api, lport, host_lanes_mask) assert int_tbl.getKeys() == [] - @patch('xcvrd.xcvrd.XcvrTableHelper.get_status_tbl') @patch('xcvrd.xcvrd.platform_chassis') @patch('xcvrd.xcvrd.is_fast_reboot_enabled', MagicMock(return_value=(False))) @@ -2221,7 +2343,6 @@ def test_CmisManagerTask_task_worker_fastboot(self, mock_chassis, mock_get_statu assert get_cmis_state_from_state_db('Ethernet0', task.xcvr_table_helper.get_status_tbl(task.port_mapping.get_asic_id_for_logical_port('Ethernet0'))) == CMIS_STATE_READY - @patch('xcvrd.xcvrd.XcvrTableHelper.get_status_tbl') @patch('xcvrd.xcvrd.platform_chassis') @patch('xcvrd.xcvrd.is_fast_reboot_enabled', MagicMock(return_value=(False))) @@ -3030,21 +3151,40 @@ def test_check_port_in_range(self): physical_port = 33 assert not check_port_in_range(range_str, physical_port) - def test_get_media_val_str_from_dict(self): - media_dict = {'lane0': '1', 'lane1': '2'} - media_str = media_settings_parser.get_media_val_str_from_dict(media_dict) - assert media_str == '1,2' - - def test_get_media_val_str(self): - num_logical_ports = 1 + def test_get_serdes_si_setting_val_str(self): lane_dict = {'lane0': '1', 'lane1': '2', 'lane2': '3', 'lane3': '4'} - logical_idx = 1 - media_str = get_media_val_str(num_logical_ports, lane_dict, logical_idx) + # non-breakout case + lane_count = 4 + subport_num = 0 + media_str = get_serdes_si_setting_val_str(lane_dict, lane_count, subport_num) assert media_str == '1,2,3,4' - num_logical_ports = 2 - logical_idx = 1 - media_str = get_media_val_str(num_logical_ports, lane_dict, logical_idx) + # breakout case + lane_count = 2 + subport_num = 2 + media_str = get_serdes_si_setting_val_str(lane_dict, lane_count, subport_num) assert media_str == '3,4' + # breakout case without subport number specified in config + lane_count = 2 + subport_num = 0 + media_str = get_serdes_si_setting_val_str(lane_dict, lane_count, subport_num) + assert media_str == '1,2' + # breakout case with out-of-range subport number + lane_count = 2 + subport_num = 3 + media_str = get_serdes_si_setting_val_str(lane_dict, lane_count, subport_num) + assert media_str == '1,2' + # breakout case with smaler lane_dict + lane_dict = {'lane0': '1', 'lane1': '2'} + lane_count = 2 + subport_num = 2 + media_str = get_serdes_si_setting_val_str(lane_dict, lane_count, subport_num) + assert media_str == '1,2' + # lane key-value pair inserted in non-asceding order + lane_dict = {'lane0': 'a', 'lane2': 'c', 'lane1': 'b', 'lane3': 'd'} + lane_count = 2 + subport_num = 2 + media_str = get_serdes_si_setting_val_str(lane_dict, lane_count, subport_num) + assert media_str == 'c,d' class MockPortMapping: logical_port_list = [0, 1, 2] @@ -3100,7 +3240,7 @@ def test_DaemonXcvrd_init_deinit_cold(self): xcvrdaemon.deinit() status_tbl.hdel.assert_called() - + @patch('sonic_py_common.device_info.get_paths_to_platform_and_hwsku_dirs', MagicMock(return_value=(test_path, '/invalid/path'))) def test_load_optical_si_file_from_platform_folder(self): assert optics_si_parser.load_optics_si_settings() != {} @@ -3116,7 +3256,7 @@ def test_load_media_settings_file_from_platform_folder(self): @patch('sonic_py_common.device_info.get_paths_to_platform_and_hwsku_dirs', MagicMock(return_value=('/invalid/path', test_path))) def test_load_media_settings_file_from_hwsku_folder(self): assert media_settings_parser.load_media_settings() != {} - + @pytest.mark.parametrize("lport, freq, grid, expected", [ (1, 193100, 75, True), (1, 193100, 100, False), diff --git a/sonic-xcvrd/xcvrd/xcvrd_utilities/media_settings_parser.py b/sonic-xcvrd/xcvrd/xcvrd_utilities/media_settings_parser.py index ce7805ee8..2dd79d5ea 100644 --- a/sonic-xcvrd/xcvrd/xcvrd_utilities/media_settings_parser.py +++ b/sonic-xcvrd/xcvrd/xcvrd_utilities/media_settings_parser.py @@ -6,6 +6,7 @@ import os import ast import re +from natsort import natsorted from sonic_py_common import device_info, logger from swsscommon import swsscommon @@ -17,6 +18,9 @@ VENDOR_KEY = 'vendor_key' MEDIA_KEY = 'media_key' LANE_SPEED_KEY = 'lane_speed_key' +DEFAULT_KEY = 'Default' +# This is useful if default value is desired when no match is found for lane speed key +LANE_SPEED_DEFAULT_KEY = LANE_SPEED_KEY_PREFIX + DEFAULT_KEY SYSLOG_IDENTIFIER = "xcvrd" helper_logger = logger.Logger(SYSLOG_IDENTIFIER) @@ -48,24 +52,45 @@ def media_settings_present(): def get_lane_speed_key(physical_port, port_speed, lane_count): + """ + Get lane speed key for the given port + + Args: + physical_port: physical port number for this logical port + port_speed: logical port speed in Mbps + lane_count: number of lanes for this logical port + + Returns: + the lane speed key string, in either host electrical interface format (e.g: 'speed:200GAUI-8') + or regular format (e.g: 'speed:25G') + Refer to Table 4-5 of SFF-8024 for different kinds of host electrical interfaces. + """ sfp = xcvrd.platform_chassis.get_sfp(physical_port) api = sfp.get_xcvr_api() lane_speed_key = None if xcvrd.is_cmis_api(api): appl_adv_dict = api.get_application_advertisement() - app_id = xcvrd.get_cmis_application_desired(api, int(lane_count), int(port_speed)) + app_id = xcvrd.get_cmis_application_desired(api, lane_count, port_speed) if app_id and app_id in appl_adv_dict: host_electrical_interface_id = appl_adv_dict[app_id].get('host_electrical_interface_id') if host_electrical_interface_id: lane_speed_key = LANE_SPEED_KEY_PREFIX + host_electrical_interface_id.split()[0] - + if not lane_speed_key: + helper_logger.log_error("No host_electrical_interface_id found for CMIS module on physical port {}" + ", failed to construct lane_speed_key".format(physical_port)) + else: + # Directly calculate lane speed and use it as key, this is especially useful for + # non-CMIS transceivers which typically have no host_electrical_interface_id + lane_speed_key = '{}{}G'.format(LANE_SPEED_KEY_PREFIX, port_speed // lane_count // 1000) return lane_speed_key def get_media_settings_key(physical_port, transceiver_dict, port_speed, lane_count): sup_compliance_str = '10/40G Ethernet Compliance Code' sup_len_str = 'Length Cable Assembly(m)' + sup_compliance_extended_values = ['Extended', 'Unknown'] + extended_spec_compliance_str = 'Extended Specification Compliance' vendor_name_str = transceiver_dict[physical_port]['manufacturer'] vendor_pn_str = transceiver_dict[physical_port]['model'] vendor_key = vendor_name_str.upper() + '-' + vendor_pn_str @@ -89,6 +114,10 @@ def get_media_settings_key(physical_port, transceiver_dict, port_speed, lane_cou media_compliance_dict = ast.literal_eval(media_compliance_dict_str) if sup_compliance_str in media_compliance_dict: media_compliance_code = media_compliance_dict[sup_compliance_str] + # For 100G transceivers, it's usually in extended specification compliance + if media_compliance_code in sup_compliance_extended_values and \ + extended_spec_compliance_str in media_compliance_dict: + media_compliance_code = media_compliance_dict[extended_spec_compliance_str] except ValueError as e: helper_logger.log_error("Invalid value for port {} 'specification_compliance': {}".format(physical_port, media_compliance_dict_str)) @@ -123,71 +152,78 @@ def is_si_per_speed_supported(media_dict): return LANE_SPEED_KEY_PREFIX in list(media_dict.keys())[0] -def get_media_val_str_from_dict(media_dict): - LANE_STR = 'lane' - LANE_SEPARATOR = ',' - - media_str = '' - tmp_dict = {} - - for keys in media_dict: - lane_num = int(keys.strip()[len(LANE_STR):]) - tmp_dict[lane_num] = media_dict[keys] - - for key in range(0, len(tmp_dict)): - media_str += tmp_dict[key] - if key != list(tmp_dict.keys())[-1]: - media_str += LANE_SEPARATOR - return media_str - - -def get_media_val_str(num_logical_ports, lane_dict, logical_idx): - LANE_STR = 'lane' - - logical_media_dict = {} - num_lanes_on_port = len(lane_dict) - - # The physical ports has more than one logical port meaning it is - # in breakout mode. So fetch the corresponding lanes from the file - media_val_str = '' - if (num_logical_ports > 1) and \ - (num_lanes_on_port >= num_logical_ports): - num_lanes_per_logical_port = num_lanes_on_port//num_logical_ports - start_lane = logical_idx * num_lanes_per_logical_port - - for lane_idx in range(start_lane, start_lane + - num_lanes_per_logical_port): - lane_idx_str = LANE_STR + str(lane_idx) - logical_lane_idx_str = LANE_STR + str(lane_idx - start_lane) - logical_media_dict[logical_lane_idx_str] = lane_dict[lane_idx_str] - - media_val_str = get_media_val_str_from_dict(logical_media_dict) - else: - media_val_str = get_media_val_str_from_dict(lane_dict) - return media_val_str +def get_serdes_si_setting_val_str(val_dict, lane_count, subport_num=0): + """ + Get ASIC side SerDes SI settings for the given logical port (subport) + + Args: + val_dict: dictionary containing SerDes settings for all lanes of the port + e.g. {'lane0': '0x1f', 'lane1': '0x1f', 'lane2': '0x1f', 'lane3': '0x1f'} + lane_count: number of lanes for this subport + subport_num: subport number (1-based), 0 for non-breakout case + + Returns: + string containing SerDes settings for the given subport, separated by comma + e.g. '0x1f,0x1f,0x1f,0x1f' + """ + start_lane_idx = (subport_num - 1) * lane_count if subport_num else 0 + if start_lane_idx + lane_count > len(val_dict): + helper_logger.log_notice( + "start_lane_idx + lane_count ({}) is beyond length of {}, " + "default start_lane_idx to 0 as a best effort".format(start_lane_idx + lane_count, val_dict) + ) + start_lane_idx = 0 + val_list = [val_dict[lane_key] for lane_key in natsorted(val_dict)] + # If subport_num ('subport') is not specified in config_db, return values for first lane_count number of lanes + return ','.join(val_list[start_lane_idx:start_lane_idx + lane_count]) + + +def get_media_settings_for_speed(settings_dict, lane_speed_key): + """ + Get settings for the given lane speed key + + Args: + settings_dict: dictionary used to look up the settings for the given lane speed key, + its key can also be regular expression pattern string. + e.g. {'speed:400GAUI-8': {'idriver': {'lane0': '0x1f', ...}}, ...} + or {'idriver': {'lane0': '0x1f', ...}, ...} + or {'speed:200GAUI-8|100GAUI-4|25G': {'idriver': {'lane0': '0x1f', ...}}, ...} + lane_speed_key: the lane speed key either in host electrical interface format (e.g: 'speed:200GAUI-8') + or regular format (e.g: 'speed:25G') + + Returns: + dictionary containing the settings for the given lane speed key if matched, return {} if no match + If no lane speed key defined in input dictionary, return the input dictionary as is + """ + if not is_si_per_speed_supported(settings_dict): + return settings_dict + if not lane_speed_key: + return {} + # Check if lane_speed_key matches any key defined in the input dictionary + lane_speed_str = lane_speed_key[len(LANE_SPEED_KEY_PREFIX):] + for candidate_lane_speed_key, value_dict in settings_dict.items(): + lane_speed_pattern = candidate_lane_speed_key[len(LANE_SPEED_KEY_PREFIX):] + if re.fullmatch(lane_speed_pattern, lane_speed_str): + return value_dict + # If no match found, return default settings if present (defined as LANE_SPEED_DEFAULT_KEY) + return settings_dict.get(LANE_SPEED_DEFAULT_KEY, {}) def get_media_settings_value(physical_port, key): GLOBAL_MEDIA_SETTINGS_KEY = 'GLOBAL_MEDIA_SETTINGS' PORT_MEDIA_SETTINGS_KEY = 'PORT_MEDIA_SETTINGS' - DEFAULT_KEY = 'Default' RANGE_SEPARATOR = '-' COMMA_SEPARATOR = ',' media_dict = {} default_dict = {} + lane_speed_key = key[LANE_SPEED_KEY] def get_media_settings(key, media_dict): for dict_key in media_dict.keys(): if (re.match(dict_key, key[VENDOR_KEY]) or \ re.match(dict_key, key[VENDOR_KEY].split('-')[0]) # e.g: 'AMPHENOL-1234' or re.match(dict_key, key[MEDIA_KEY]) ): # e.g: 'QSFP28-40GBASE-CR4-1M' - if is_si_per_speed_supported(media_dict[dict_key]): - if key[LANE_SPEED_KEY] is not None and key[LANE_SPEED_KEY] in media_dict[dict_key]: # e.g: 'speed:400GAUI-8' - return media_dict[dict_key][key[LANE_SPEED_KEY]] - else: - return {} - else: - return media_dict[dict_key] + return get_media_settings_for_speed(media_dict[dict_key], key[LANE_SPEED_KEY]) return None # Keys under global media settings can be a list or range or list of ranges @@ -220,7 +256,7 @@ def get_media_settings(key, media_dict): return media_settings # Try to match 'default' key if it does not match any keys elif DEFAULT_KEY in media_dict: - default_dict = media_dict[DEFAULT_KEY] + default_dict = get_media_settings_for_speed(media_dict[DEFAULT_KEY], lane_speed_key) media_dict = {} @@ -234,7 +270,7 @@ def get_media_settings(key, media_dict): if len(default_dict) != 0: return default_dict else: - helper_logger.log_error("Error: No values for physical port '{}'".format(physical_port)) + helper_logger.log_notice("No values for physical port '{}'".format(physical_port)) return {} media_settings = get_media_settings(key, media_dict) @@ -242,7 +278,7 @@ def get_media_settings(key, media_dict): return media_settings # Try to match 'default' key if it does not match any keys elif DEFAULT_KEY in media_dict: - return media_dict[DEFAULT_KEY] + return get_media_settings_for_speed(media_dict[DEFAULT_KEY], lane_speed_key) elif len(default_dict) != 0: return default_dict else: @@ -252,15 +288,18 @@ def get_media_settings(key, media_dict): return {} -def get_speed_and_lane_count(port, cfg_port_tbl): - port_speed, lane_count = '0', 0 +def get_speed_lane_count_and_subport(port, cfg_port_tbl): + port_speed, lane_count, subport_num = 0, 0, 0 found, port_info = cfg_port_tbl.get(port) port_info_dict = dict(port_info) if found and 'speed' in port_info_dict and 'lanes' in port_info_dict: - port_speed = port_info_dict['speed'] + port_speed = int(port_info_dict['speed']) lanes = port_info_dict['lanes'] lane_count = len(lanes.split(',')) - return port_speed, lane_count + subport_num = int(port_info_dict.get('subport', subport_num)) + else: + helper_logger.log_error("No info found for port {} in cfg_port_tbl".format(port)) + return port_speed, lane_count, subport_num def notify_media_setting(logical_port_name, transceiver_dict, @@ -269,7 +308,7 @@ def notify_media_setting(logical_port_name, transceiver_dict, if not media_settings_present(): return - port_speed, lane_count = get_speed_and_lane_count(logical_port_name, cfg_port_tbl) + port_speed, lane_count, subport_num = get_speed_lane_count_and_subport(logical_port_name, cfg_port_tbl) ganged_port = False ganged_member_num = 1 @@ -283,9 +322,6 @@ def notify_media_setting(logical_port_name, transceiver_dict, ganged_port = True for physical_port in physical_port_list: - logical_port_list = port_mapping.get_physical_to_logical(physical_port) - num_logical_ports = len(logical_port_list) - logical_idx = logical_port_list.index(logical_port_name) if not xcvrd._wrapper_get_presence(physical_port): helper_logger.log_info("Media {} presence not detected during notify".format(physical_port)) continue @@ -298,7 +334,7 @@ def notify_media_setting(logical_port_name, transceiver_dict, ganged_member_num += 1 key = get_media_settings_key(physical_port, transceiver_dict, port_speed, lane_count) - helper_logger.log_debug("Retrieving media settings for port {}, operating at a speed of {} with a lane count of {}, using the following lookup keys: {}".format(logical_port_name, port_speed, lane_count, key)) + helper_logger.log_notice("Retrieving media settings for port {} speed {} num_lanes {}, using key {}".format(logical_port_name, port_speed, lane_count, key)) media_dict = get_media_settings_value(physical_port, key) if len(media_dict) == 0: @@ -308,16 +344,14 @@ def notify_media_setting(logical_port_name, transceiver_dict, fvs = swsscommon.FieldValuePairs(len(media_dict)) index = 0 - helper_logger.log_debug("Publishing ASIC-side SI setting for port {} in APP_DB:".format(logical_port_name)) + helper_logger.log_notice("Publishing ASIC-side SI setting for port {} in APP_DB:".format(logical_port_name)) for media_key in media_dict: if type(media_dict[media_key]) is dict: - media_val_str = get_media_val_str(num_logical_ports, - media_dict[media_key], - logical_idx) + val_str = get_serdes_si_setting_val_str(media_dict[media_key], lane_count, subport_num) else: - media_val_str = media_dict[media_key] - helper_logger.log_debug("{}:({},{}) ".format(index, str(media_key), str(media_val_str))) - fvs[index] = (str(media_key), str(media_val_str)) + val_str = media_dict[media_key] + helper_logger.log_notice("{}:({},{}) ".format(index, str(media_key), str(val_str))) + fvs[index] = (str(media_key), str(val_str)) index += 1 app_port_tbl.set(port_name, fvs)