diff --git a/sonic-xcvrd/tests/media_settings_extended_format.json b/sonic-xcvrd/tests/media_settings_extended_format.json new file mode 100644 index 000000000..dd51f3501 --- /dev/null +++ b/sonic-xcvrd/tests/media_settings_extended_format.json @@ -0,0 +1,60 @@ +{ + "GLOBAL_MEDIA_SETTINGS": { + "0-31": { + "QSFP-DD-sm_media_interface": { + "speed:400GAUI-8": { + "idriver": { + "lane0": "0x0000003c", + "lane1": "0x0000003c", + "lane2": "0x0000003c", + "lane3": "0x0000003c", + "lane4": "0x0000003c", + "lane5": "0x0000003c", + "lane6": "0x0000003c", + "lane7": "0x0000003c" + }, + "pre1": { + "lane0": "0x00000002", + "lane1": "0x00000002", + "lane2": "0x00000002", + "lane3": "0x00000002", + "lane4": "0x00000002", + "lane5": "0x00000002", + "lane6": "0x00000002", + "lane7": "0x00000002" + }, + "ob_m2lp": { + "lane0": "0x0000000e", + "lane1": "0x0000000e", + "lane2": "0x0000000e", + "lane3": "0x0000000e", + "lane4": "0x0000000e", + "lane5": "0x0000000e", + "lane6": "0x0000000e", + "lane7": "0x0000000e" + } + } + }, + "QSFP-DD-active_cable_media_interface":{ + "speed:100GAUI-2": { + "pre1": { + "lane0": "0x00000002", + "lane1": "0x00000002" + }, + "main": { + "lane0": "0x00000020", + "lane1": "0x00000020" + }, + "post1": { + "lane0": "0x00000006", + "lane1": "0x00000006" + }, + "regn_bfm1n": { + "lane0": "0x000000aa", + "lane1": "0x000000aa" + } + } + } + } + } +} \ No newline at end of file diff --git a/sonic-xcvrd/tests/test_xcvrd.py b/sonic-xcvrd/tests/test_xcvrd.py index a3adf5823..36b93c0b3 100644 --- a/sonic-xcvrd/tests/test_xcvrd.py +++ b/sonic-xcvrd/tests/test_xcvrd.py @@ -1,6 +1,7 @@ #from unittest.mock import DEFAULT from xcvrd.xcvrd_utilities.port_mapping import * from xcvrd.xcvrd_utilities.sfp_status_helper import * +from xcvrd.xcvrd_utilities.media_settings_parser import * from xcvrd.xcvrd_utilities.optics_si_parser import * from xcvrd.xcvrd import * import pytest @@ -50,6 +51,69 @@ port_optics_si_settings['PORT_MEDIA_SETTINGS'] = optics_si_settings_with_comma_dict.pop('PORT_MEDIA_SETTINGS') optics_si_settings_with_comma_dict['GLOBAL_MEDIA_SETTINGS']['0-5,6,7-20,21-31'] = global_optics_si_settings +with open(os.path.join(test_path, 'media_settings_extended_format.json'), 'r') as f: + media_settings_extended_format_dict = json.load(f) + + +# 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) + +media_settings_global_range_media_key_si = copy.deepcopy(media_settings_extended_format_dict) +media_settings_global_range_media_key_si['GLOBAL_MEDIA_SETTINGS']['0-31']['QSFP-DD-sm_media_interface'] = media_settings_global_range_media_key_si['GLOBAL_MEDIA_SETTINGS']['0-31']['QSFP-DD-sm_media_interface'].pop('speed:400GAUI-8') +media_settings_global_range_media_key_si['GLOBAL_MEDIA_SETTINGS']['0-31']['QSFP-DD-active_cable_media_interface'] = media_settings_global_range_media_key_si['GLOBAL_MEDIA_SETTINGS']['0-31']['QSFP-DD-active_cable_media_interface'].pop('speed:100GAUI-2') + +media_settings_global_range_vendor_key_lane_speed_si = copy.deepcopy(media_settings_extended_format_dict) +media_settings_global_range_vendor_key_lane_speed_si['GLOBAL_MEDIA_SETTINGS']['0-31']['AMPHANOL-1234'] = media_settings_global_range_vendor_key_lane_speed_si['GLOBAL_MEDIA_SETTINGS']['0-31'].pop('QSFP-DD-sm_media_interface') +media_settings_global_range_vendor_key_lane_speed_si['GLOBAL_MEDIA_SETTINGS']['0-31']['AMPHANOL-5678'] = media_settings_global_range_vendor_key_lane_speed_si['GLOBAL_MEDIA_SETTINGS']['0-31'].pop('QSFP-DD-active_cable_media_interface') + +media_settings_global_range_vendor_key_si = copy.deepcopy(media_settings_global_range_vendor_key_lane_speed_si) +media_settings_global_range_vendor_key_si['GLOBAL_MEDIA_SETTINGS']['0-31']['AMPHANOL-1234'] = media_settings_global_range_vendor_key_si['GLOBAL_MEDIA_SETTINGS']['0-31']['AMPHANOL-1234'].pop('speed:400GAUI-8') +media_settings_global_range_vendor_key_si['GLOBAL_MEDIA_SETTINGS']['0-31']['AMPHANOL-5678'] = media_settings_global_range_vendor_key_si['GLOBAL_MEDIA_SETTINGS']['0-31']['AMPHANOL-5678'].pop('speed:100GAUI-2') + +media_settings_global_list_media_key_lane_speed_si = copy.deepcopy(media_settings_extended_format_dict) +new_key = str(','.join([str(i) for i in range(32)])) +media_settings_global_list_media_key_lane_speed_si['GLOBAL_MEDIA_SETTINGS'][new_key] = media_settings_global_list_media_key_lane_speed_si['GLOBAL_MEDIA_SETTINGS'].pop('0-31') + +media_settings_global_list_media_key_si = copy.deepcopy(media_settings_global_range_media_key_si) +media_settings_global_list_media_key_si['GLOBAL_MEDIA_SETTINGS'][new_key] = media_settings_global_list_media_key_si['GLOBAL_MEDIA_SETTINGS'].pop('0-31') + +media_settings_global_list_of_ranges_media_key_lane_speed_si = copy.deepcopy(media_settings_extended_format_dict) +media_settings_global_list_of_ranges_media_key_lane_speed_si['GLOBAL_MEDIA_SETTINGS']['0-15,16-31'] = media_settings_global_list_of_ranges_media_key_lane_speed_si['GLOBAL_MEDIA_SETTINGS'].pop('0-31') + +media_settings_global_list_of_ranges_media_key_si = copy.deepcopy(media_settings_global_range_media_key_si) +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_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')} +del media_settings_port_media_key_lane_speed_si['GLOBAL_MEDIA_SETTINGS'] + +media_settings_port_media_key_si = copy.deepcopy(media_settings_port_media_key_lane_speed_si) +media_settings_port_media_key_si['PORT_MEDIA_SETTINGS']['7']["QSFP-DD-sm_media_interface"] = media_settings_port_media_key_si['PORT_MEDIA_SETTINGS']['7']["QSFP-DD-sm_media_interface"].pop("speed:400GAUI-8") +media_settings_port_media_key_si['PORT_MEDIA_SETTINGS']['7']["QSFP-DD-active_cable_media_interface"] = media_settings_port_media_key_si['PORT_MEDIA_SETTINGS']['7']["QSFP-DD-active_cable_media_interface"].pop("speed:100GAUI-2") + +media_settings_port_vendor_key_lane_speed_si = copy.deepcopy(media_settings_port_media_key_lane_speed_si) +media_settings_port_vendor_key_lane_speed_si['PORT_MEDIA_SETTINGS']['7']['AMPHANOL-1234'] = media_settings_port_vendor_key_lane_speed_si['PORT_MEDIA_SETTINGS']['7'].pop('QSFP-DD-sm_media_interface') +media_settings_port_vendor_key_lane_speed_si['PORT_MEDIA_SETTINGS']['7']['AMPHANOL-5678'] = media_settings_port_vendor_key_lane_speed_si['PORT_MEDIA_SETTINGS']['7'].pop('QSFP-DD-active_cable_media_interface') + +media_settings_port_vendor_key_si = copy.deepcopy(media_settings_port_vendor_key_lane_speed_si) +media_settings_port_vendor_key_si['PORT_MEDIA_SETTINGS']['7']['AMPHANOL-1234'] = media_settings_port_vendor_key_si['PORT_MEDIA_SETTINGS']['7']['AMPHANOL-1234'].pop('speed:400GAUI-8') +media_settings_port_vendor_key_si['PORT_MEDIA_SETTINGS']['7']['AMPHANOL-5678'] = media_settings_port_vendor_key_si['PORT_MEDIA_SETTINGS']['7']['AMPHANOL-5678'].pop('speed:100GAUI-2') + +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['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_empty = {} + + class TestXcvrdThreadException(object): @patch('xcvrd.xcvrd.platform_chassis', MagicMock()) @@ -428,7 +492,19 @@ def test_init_port_sfp_status_tbl(self): task = SfpStateUpdateTask(DEFAULT_NAMESPACE, port_mapping, stop_event, sfp_error_event) task._init_port_sfp_status_tbl(port_mapping, xcvr_table_helper, stop_event) - def test_get_media_settings_key(self): + @patch('sonic_py_common.device_info.get_paths_to_platform_and_hwsku_dirs', MagicMock(return_value=('/invalid/path', None))) + def test_load_media_settings_missing_file(self): + assert media_settings_parser.load_media_settings() == {} + + @patch('xcvrd.xcvrd.platform_chassis') + @patch('xcvrd.xcvrd.is_cmis_api') + def test_get_media_settings_key(self, mock_is_cmis_api, mock_chassis): + mock_sfp = MagicMock() + mock_chassis.get_sfp = MagicMock(return_value=mock_sfp) + mock_api = MagicMock() + mock_sfp.get_xcvr_api = MagicMock(return_value=mock_api) + mock_is_cmis_api.return_value = False + xcvr_info_dict = { 0: { 'manufacturer': 'Molex', @@ -441,27 +517,163 @@ def test_get_media_settings_key(self): } # Test a good 'specification_compliance' value - result = get_media_settings_key(0, xcvr_info_dict) - assert result == ['MOLEX-1064141421', 'QSFP+-10GBase-SR-255M'] + 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 } # Test a bad 'specification_compliance' value xcvr_info_dict[0]['specification_compliance'] = 'N/A' - result = get_media_settings_key(0, xcvr_info_dict) - assert result == ['MOLEX-1064141421', 'QSFP+-*'] + 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 } # TODO: Ensure that error message was logged + mock_is_cmis_api.return_value = True + xcvr_info_dict = { + 0: { + 'manufacturer': 'Molex', + 'model': '1064141421', + 'cable_type': 'Length Cable Assembly(m)', + 'cable_length': '255', + 'specification_compliance': "sm_media_interface", + 'type_abbrv_name': 'QSFP-DD' + } + } + + mock_app_adv_value ={ + 1: {'host_electrical_interface_id': '400G CR8', 'module_media_interface_id': 'Copper cable', 'media_lane_count': 8, 'host_lane_count': 8, 'host_lane_assignment_options': 1}, + 2: {'host_electrical_interface_id': '200GBASE-CR4 (Clause 136)', 'module_media_interface_id': 'Copper cable', 'media_lane_count': 4, 'host_lane_count': 4, 'host_lane_assignment_options': 17}, + 3: {'host_electrical_interface_id': '100GBASE-CR2 (Clause 136)', 'module_media_interface_id': 'Copper cable', 'media_lane_count': 2, 'host_lane_count': 2, 'host_lane_assignment_options': 85}, + 4: {'host_electrical_interface_id': '100GBASE-CR4 (Clause 92)', 'module_media_interface_id': 'Copper cable', 'media_lane_count': 4, 'host_lane_count': 4, 'host_lane_assignment_options': 17}, + 5: {'host_electrical_interface_id': '50GBASE-CR (Clause 126)', 'module_media_interface_id': 'Copper cable', 'media_lane_count': 1, 'host_lane_count': 1, 'host_lane_assignment_options': 255}, + 6: {'host_electrical_interface_id': '40GBASE-CR4 (Clause 85)', 'module_media_interface_id': 'Copper cable', 'media_lane_count': 4, 'host_lane_count': 4, 'host_lane_assignment_options': 17}, + 7: {'host_electrical_interface_id': '25GBASE-CR CA-N (Clause 110)', 'module_media_interface_id': 'Copper cable', 'media_lane_count': 1, 'host_lane_count': 1, 'host_lane_assignment_options': 255}, + 8: {'host_electrical_interface_id': '1000BASE -CX(Clause 39)', 'module_media_interface_id': 'Copper cable', 'media_lane_count': 1, 'host_lane_count': 1, 'host_lane_assignment_options': 255} + } + + mock_api.get_application_advertisement = MagicMock(return_value=mock_app_adv_value) + result = media_settings_parser.get_media_settings_key(0, xcvr_info_dict, 100000, 2) + 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)) + ]) + def test_get_speed_and_lane_count(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 + + def test_is_si_per_speed_supported(self): + media_dict = { + 'speed:400G-GAUI-4':{ + 'main':{ + 'lane0': '0x00000000', + 'lane1': '0x00000000', + 'lane2': '0x00000000', + 'lane3': '0x00000000', + 'lane4': '0x00000000', + 'lane5': '0x00000000', + 'lane6': '0x00000000', + 'lane7': '0x00000000' + } + }, + 'speed:400GAUI-8':{ + 'post1':{ + 'lane0': '0x00000000', + 'lane1': '0x00000000', + 'lane2': '0x00000000', + 'lane3': '0x00000000', + 'lane4': '0x00000000', + 'lane5': '0x00000000', + 'lane6': '0x00000000', + 'lane7': '0x00000000' + } + } + } + result = is_si_per_speed_supported(media_dict) + assert result == True + + media_dict = { + 'main':{ + 'lane0': '0x00000000', + 'lane1': '0x00000000', + 'lane2': '0x00000000', + 'lane3': '0x00000000', + 'lane4': '0x00000000', + 'lane5': '0x00000000', + 'lane6': '0x00000000', + 'lane7': '0x00000000' + }, + 'post1':{ + 'lane0': '0x00000000', + 'lane1': '0x00000000', + 'lane2': '0x00000000', + 'lane3': '0x00000000', + 'lane4': '0x00000000', + 'lane5': '0x00000000', + 'lane6': '0x00000000', + 'lane7': '0x00000000' + } + } + result = is_si_per_speed_supported(media_dict) + assert result == False + + @pytest.mark.parametrize("media_settings_dict, port, key, expected", [ + (media_settings_global_range_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_range_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_range_vendor_key_lane_speed_si, 7, {'vendor_key': 'AMPHANOL-5678', 'media_key': 'UNKOWN', '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_range_vendor_key_lane_speed_si, 7, {'vendor_key': 'AMPHANOL-5678', 'media_key': 'UNKOWN', 'lane_speed_key': 'MISSING'}, {}), + (media_settings_global_range_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_global_list_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_media_key_lane_speed_si, 7, {'vendor_key': 'UNKOWN', 'media_key': 'QSFP-DD-active_cable_media_interface', 'lane_speed_key': 'MISSING'}, {}), + (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_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'}, {}), + (media_settings_port_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_port_vendor_key_lane_speed_si, 7, {'vendor_key': 'AMPHANOL-5678', 'media_key': 'UNKOWN', '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_vendor_key_lane_speed_si, 7, {'vendor_key': 'AMPHANOL-5678', '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_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_empty, 7, {'vendor_key': 'AMPHANOL-5678', 'media_key': 'QSFP-DD-active_cable_media_interface', 'lane_speed_key': 'speed:100GAUI-2'}, {}) + ]) + def test_get_media_settings_value(self, media_settings_dict, port, key, expected): + with patch('xcvrd.xcvrd_utilities.media_settings_parser.g_dict', media_settings_dict): + 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))) def test_notify_media_setting(self): 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.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))) def test_notify_media_setting_with_comma(self): self._check_notify_media_setting(1) self._check_notify_media_setting(6) def _check_notify_media_setting(self, index): + 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) + logical_port_name = 'Ethernet0' xcvr_info_dict = { index: { @@ -477,7 +689,7 @@ def _check_notify_media_setting(self, index): port_mapping = PortMapping() port_change_event = PortChangeEvent('Ethernet0', index, 0, PortChangeEvent.PORT_ADD) port_mapping.handle_port_change_event(port_change_event) - notify_media_setting(logical_port_name, xcvr_info_dict, app_port_tbl, port_mapping) + media_settings_parser.notify_media_setting(logical_port_name, xcvr_info_dict, app_port_tbl, mock_cfg_table, port_mapping) @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)) @@ -760,6 +972,27 @@ def get_application(lane): assert task.is_cmis_application_update_required(mock_xcvr_api, app_new, host_lanes_mask) == expected + @pytest.mark.parametrize("ifname, expected", [ + ('400G CR8', 400000), + ('200GBASE-CR4 (Clause 136)', 200000), + ('100GBASE-CR2 (Clause 136)', 100000), + ('CAUI-4 C2M (Annex 83E)', 100000), + ('50GBASE-CR', 50000), + ('LAUI-2 C2M (Annex 135C)', 50000), + ('40GBASE-CR4 (Clause 85)', 40000), + ('XLAUI C2M (Annex 83B)', 40000), + ('XLPPI (Annex 86A)', 40000), + ('25GBASE-CR CA-N (Clause 110)', 25000), + ('10GBASE-CX4 (Clause 54)', 10000), + ('SFI (SFF-8431)', 10000), + ('XFI (SFF INF-8071i)', 10000), + ('1000BASE -CX(Clause 39)', 1000), + ('Unknown Interface', 0) + ]) + def test_get_interface_speed(self, ifname, expected): + assert get_interface_speed(ifname) == expected + + @patch('xcvrd.xcvrd.is_cmis_api', MagicMock(return_value=True)) @pytest.mark.parametrize("host_lane_count, speed, subport, expected", [ (8, 400000, 0, 0xFF), (4, 100000, 1, 0xF), @@ -803,9 +1036,10 @@ def get_host_lane_assignment_option_side_effect(app): stop_event = threading.Event() task = CmisManagerTask(DEFAULT_NAMESPACE, port_mapping, stop_event) - appl = task.get_cmis_application_desired(mock_xcvr_api, host_lane_count, speed) + appl = get_cmis_application_desired(mock_xcvr_api, host_lane_count, speed) assert task.get_cmis_host_lanes_mask(mock_xcvr_api, appl, host_lane_count, subport) == expected + def test_CmisManagerTask_post_port_active_apsel_to_db(self): mock_xcvr_api = MagicMock() mock_xcvr_api.get_active_apsel_hostlane = MagicMock(side_effect=[ @@ -889,11 +1123,13 @@ 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.platform_chassis') @patch('xcvrd.xcvrd_utilities.port_mapping.subscribe_port_update_event', MagicMock(return_value=(None, None))) @patch('xcvrd.xcvrd_utilities.port_mapping.handle_port_update_event', MagicMock()) @patch('xcvrd.xcvrd._wrapper_get_sfp_type', MagicMock(return_value='QSFP_DD')) @patch('xcvrd.xcvrd.CmisManagerTask.wait_for_port_config_done', MagicMock()) + @patch('xcvrd.xcvrd.is_cmis_api', MagicMock(return_value=True)) def test_CmisManagerTask_task_worker(self, mock_chassis): mock_xcvr_api = MagicMock() mock_xcvr_api.set_datapath_deinit = MagicMock(return_value=True) @@ -1168,6 +1404,7 @@ def test_SfpStateUpdateTask_task_run_stop(self): @patch('xcvrd.xcvrd.XcvrTableHelper', MagicMock()) @patch('xcvrd.xcvrd.post_port_sfp_info_to_db') + @patch('xcvrd.xcvrd.XcvrTableHelper.get_cfg_port_tbl', MagicMock()) def test_SfpStateUpdateTask_retry_eeprom_reading(self, mock_post_sfp_info): mock_table = MagicMock() mock_table.get = MagicMock(return_value=(False, None)) @@ -1228,7 +1465,7 @@ def test_SfpStateUpdateTask_mapping_event_from_change_event(self): @patch('xcvrd.xcvrd.SfpStateUpdateTask._mapping_event_from_change_event') @patch('xcvrd.xcvrd._wrapper_get_transceiver_change_event') @patch('xcvrd.xcvrd.del_port_sfp_dom_info_from_db') - @patch('xcvrd.xcvrd.notify_media_setting') + @patch('xcvrd.xcvrd_utilities.media_settings_parser.notify_media_setting') @patch('xcvrd.xcvrd.post_port_dom_threshold_info_to_db') @patch('xcvrd.xcvrd.post_port_sfp_info_to_db') @patch('xcvrd.xcvrd.update_port_transceiver_status_table_sw') @@ -1324,7 +1561,7 @@ def test_SfpStateUpdateTask_task_worker(self, mock_del_status_hw, @patch('xcvrd.xcvrd.XcvrTableHelper') @patch('xcvrd.xcvrd._wrapper_get_presence') - @patch('xcvrd.xcvrd.notify_media_setting') + @patch('xcvrd.xcvrd_utilities.media_settings_parser.notify_media_setting') @patch('xcvrd.xcvrd.post_port_dom_threshold_info_to_db') @patch('xcvrd.xcvrd.post_port_sfp_info_to_db') @patch('xcvrd.xcvrd.update_port_transceiver_status_table_sw') @@ -1597,7 +1834,7 @@ def test_check_port_in_range(self): def test_get_media_val_str_from_dict(self): media_dict = {'lane0': '1', 'lane1': '2'} - media_str = get_media_val_str_from_dict(media_dict) + 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): diff --git a/sonic-xcvrd/xcvrd/xcvrd.py b/sonic-xcvrd/xcvrd/xcvrd.py index ab06b9ab1..361a2874b 100644 --- a/sonic-xcvrd/xcvrd/xcvrd.py +++ b/sonic-xcvrd/xcvrd/xcvrd.py @@ -27,7 +27,11 @@ from .xcvrd_utilities import sfp_status_helper from .xcvrd_utilities import port_mapping + from .xcvrd_utilities import media_settings_parser from .xcvrd_utilities import optics_si_parser + + from sonic_platform_base.sonic_xcvr.api.public.c_cmis import CmisApi + except ImportError as e: raise ImportError(str(e) + " - required module not found") @@ -103,6 +107,75 @@ # Helper functions ============================================================= # + +def is_cmis_api(api): + return type(api) == CmisApi + + +def get_cmis_application_desired(api, host_lane_count, speed): + """ + Get the CMIS application code that matches the specified host side configurations + + Args: + api: + XcvrApi object + host_lane_count: + Number of lanes on the host side + speed: + Integer, the port speed of the host interface + + Returns: + Integer, the transceiver-specific application code + """ + + if speed == 0 or host_lane_count == 0: + return None + + if not is_cmis_api(api): + return None + + appl_dict = api.get_application_advertisement() + for index, app_info in appl_dict.items(): + if (app_info.get('host_lane_count') == host_lane_count and + get_interface_speed(app_info.get('host_electrical_interface_id')) == speed): + return (index & 0xf) + + return None + + +def get_interface_speed(ifname): + """ + Get the port speed from the host interface name + + Args: + ifname: String, interface name + + Returns: + Integer, the port speed if success otherwise 0 + """ + # see HOST_ELECTRICAL_INTERFACE of sff8024.py + speed = 0 + if '400G' in ifname: + speed = 400000 + elif '200G' in ifname: + speed = 200000 + elif '100G' in ifname or 'CAUI-4' in ifname: + speed = 100000 + elif '50G' in ifname or 'LAUI-2' in ifname: + speed = 50000 + elif '40G' in ifname or 'XLAUI' in ifname or 'XLPPI' in ifname: + speed = 40000 + elif '25G' in ifname: + speed = 25000 + elif '10G' in ifname or 'SFI' in ifname or 'XFI' in ifname: + speed = 10000 + elif '1000BASE' in ifname: + speed = 1000 + else: + helper_logger.log_error("No interface speed found for: '{}'".format(ifname)) + return speed + + # Get physical port name @@ -576,223 +649,6 @@ def check_port_in_range(range_str, physical_port): return False -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 = {} - - # Keys under global media settings can be a list or range or list of ranges - # of physical port numbers. Below are some examples - # 1-32 - # 1,2,3,4,5 - # 1-4,9-12 - - if GLOBAL_MEDIA_SETTINGS_KEY in g_dict: - for keys in g_dict[GLOBAL_MEDIA_SETTINGS_KEY]: - if COMMA_SEPARATOR in keys: - port_list = keys.split(COMMA_SEPARATOR) - for port in port_list: - if RANGE_SEPARATOR in port: - if check_port_in_range(port, physical_port): - media_dict = g_dict[GLOBAL_MEDIA_SETTINGS_KEY][keys] - break - elif str(physical_port) == port: - media_dict = g_dict[GLOBAL_MEDIA_SETTINGS_KEY][keys] - break - - elif RANGE_SEPARATOR in keys: - if check_port_in_range(keys, physical_port): - media_dict = g_dict[GLOBAL_MEDIA_SETTINGS_KEY][keys] - - # If there is a match in the global profile for a media type, - # fetch those values - if key[0] in media_dict: - return media_dict[key[0]] - elif key[0].split('-')[0] in media_dict: - return media_dict[key[0].split('-')[0]] - elif key[1] in media_dict: - return media_dict[key[1]] - elif DEFAULT_KEY in media_dict: - default_dict = media_dict[DEFAULT_KEY] - - media_dict = {} - - if PORT_MEDIA_SETTINGS_KEY in g_dict: - for keys in g_dict[PORT_MEDIA_SETTINGS_KEY]: - if int(keys) == physical_port: - media_dict = g_dict[PORT_MEDIA_SETTINGS_KEY][keys] - break - - if len(media_dict) == 0: - if len(default_dict) != 0: - return default_dict - else: - helper_logger.log_error("Error: No values for physical port '{}'".format(physical_port)) - return {} - - if key[0] in media_dict: - return media_dict[key[0]] - elif key[0].split('-')[0] in media_dict: - return media_dict[key[0].split('-')[0]] - elif key[1] in media_dict: - return media_dict[key[1]] - elif DEFAULT_KEY in media_dict: - return media_dict[DEFAULT_KEY] - elif len(default_dict) != 0: - return default_dict - else: - if len(default_dict) != 0: - return default_dict - - return {} - - -def get_media_settings_key(physical_port, transceiver_dict): - sup_compliance_str = '10/40G Ethernet Compliance Code' - sup_len_str = 'Length Cable Assembly(m)' - 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 - - media_len = '' - if transceiver_dict[physical_port]['cable_type'] == sup_len_str: - media_len = transceiver_dict[physical_port]['cable_length'] - - media_compliance_dict_str = transceiver_dict[physical_port]['specification_compliance'] - media_compliance_code = '' - media_type = '' - media_key = '' - media_compliance_dict = {} - - try: - if _wrapper_get_sfp_type(physical_port) == 'QSFP_DD': - media_compliance_code = media_compliance_dict_str - else: - 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] - except ValueError as e: - helper_logger.log_error("Invalid value for port {} 'specification_compliance': {}".format(physical_port, media_compliance_dict_str)) - - media_type = transceiver_dict[physical_port]['type_abbrv_name'] - - if len(media_type) != 0: - media_key += media_type - if len(media_compliance_code) != 0: - media_key += '-' + media_compliance_code - if _wrapper_get_sfp_type(physical_port) == 'QSFP_DD': - if media_compliance_code == "passive_copper_media_interface": - if media_len != 0: - media_key += '-' + str(media_len) + 'M' - else: - if media_len != 0: - media_key += '-' + str(media_len) + 'M' - else: - media_key += '-' + '*' - - return [vendor_key, media_key] - -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 notify_media_setting(logical_port_name, transceiver_dict, - app_port_tbl, port_mapping): - if not g_dict: - return - - ganged_port = False - ganged_member_num = 1 - - physical_port_list = port_mapping.logical_port_name_to_physical_port_list(logical_port_name) - if physical_port_list is None: - helper_logger.log_error("Error: No physical ports found for logical port '{}'".format(logical_port_name)) - return PHYSICAL_PORT_NOT_EXIST - - if len(physical_port_list) > 1: - 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 _wrapper_get_presence(physical_port): - helper_logger.log_info("Media {} presence not detected during notify".format(physical_port)) - continue - if physical_port not in transceiver_dict: - helper_logger.log_error("Media {} eeprom not populated in transceiver dict".format(physical_port)) - continue - - port_name = get_physical_port_name(logical_port_name, - ganged_member_num, ganged_port) - ganged_member_num += 1 - key = get_media_settings_key(physical_port, transceiver_dict) - media_dict = get_media_settings_value(physical_port, key) - - if len(media_dict) == 0: - helper_logger.log_error("Error in obtaining media setting for {}".format(logical_port_name)) - return - - fvs = swsscommon.FieldValuePairs(len(media_dict)) - - index = 0 - 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) - else: - media_val_str = media_dict[media_key] - fvs[index] = (str(media_key), str(media_val_str)) - index += 1 - - app_port_tbl.set(port_name, fvs) - - def waiting_time_compensation_with_sleep(time_start, time_to_wait): time_now = time.time() time_diff = time_now - time_start @@ -899,6 +755,9 @@ def __init__(self, namespaces, port_mapping, main_thread_stop_event, skip_cmis_m self.skip_cmis_mgr = skip_cmis_mgr self.namespaces = namespaces + def log_debug(self, message): + helper_logger.log_debug("CMIS: {}".format(message)) + def log_notice(self, message): helper_logger.log_notice("CMIS: {}".format(message)) @@ -958,66 +817,6 @@ def on_port_update_event(self, port_change_event): else: self.port_dict[lport]['cmis_state'] = self.CMIS_STATE_REMOVED - def get_interface_speed(self, ifname): - """ - Get the port speed from the host interface name - - Args: - ifname: String, interface name - - Returns: - Integer, the port speed if success otherwise 0 - """ - # see HOST_ELECTRICAL_INTERFACE of sff8024.py - speed = 0 - if '400G' in ifname: - speed = 400000 - elif '200G' in ifname: - speed = 200000 - elif '100G' in ifname or 'CAUI-4' in ifname: - speed = 100000 - elif '50G' in ifname or 'LAUI-2' in ifname: - speed = 50000 - elif '40G' in ifname or 'XLAUI' in ifname or 'XLPPI' in ifname: - speed = 40000 - elif '25G' in ifname: - speed = 25000 - elif '10G' in ifname or 'SFI' in ifname or 'XFI' in ifname: - speed = 10000 - elif '1000BASE' in ifname: - speed = 1000 - return speed - - def get_cmis_application_desired(self, api, host_lane_count, speed): - """ - Get the CMIS application code that matches the specified host side configurations - - Args: - api: - XcvrApi object - host_lane_count: - Number of lanes on the host side - speed: - Integer, the port speed of the host interface - - Returns: - Integer, the transceiver-specific application code - """ - if speed == 0 or host_lane_count == 0: - return 0 - - appl_code = 0 - appl_dict = api.get_application_advertisement() - for c in appl_dict.keys(): - d = appl_dict[c] - if d.get('host_lane_count') != host_lane_count: - continue - if self.get_interface_speed(d.get('host_electrical_interface_id')) != speed: - continue - appl_code = c - break - - return (appl_code & 0xf) def get_cmis_dp_init_duration_secs(self, api): return api.get_datapath_init_duration()/1000 @@ -1052,7 +851,7 @@ def get_cmis_host_lanes_mask(self, api, appl, host_lane_count, subport): """ host_lanes_mask = 0 - if appl < 1 or host_lane_count <= 0 or subport < 0: + if appl is None or host_lane_count <= 0 or subport < 0: self.log_error("Invalid input to get host lane mask - appl {} host_lane_count {} " "subport {}!".format(appl, host_lane_count, subport)) return host_lanes_mask @@ -1503,9 +1302,8 @@ def task_worker(self): try: # CMIS state transitions if state == self.CMIS_STATE_INSERTED: - self.port_dict[lport]['appl'] = self.get_cmis_application_desired(api, - host_lane_count, host_speed) - if self.port_dict[lport]['appl'] < 1: + self.port_dict[lport]['appl'] = get_cmis_application_desired(api, host_lane_count, host_speed) + if self.port_dict[lport]['appl'] is None: self.log_error("{}: no suitable app for the port appl {} host_lane_count {} " "host_speed {}".format(lport, appl, host_lane_count, host_speed)) self.port_dict[lport]['cmis_state'] = self.CMIS_STATE_FAILED @@ -1625,7 +1423,13 @@ def task_worker(self): # Apply module SI settings if applicable lane_speed = int(speed/1000)//host_lane_count optics_si_dict = optics_si_parser.fetch_optics_si_setting(pport, lane_speed, sfp) - + + self.log_debug("Read SI parameters for port {} from optics_si_settings.json vendor file:".format(lport)) + for key, sub_dict in optics_si_dict.items(): + self.log_debug("{}".format(key)) + for sub_key, value in sub_dict.items(): + self.log_debug("{}: {}".format(sub_key, str(value))) + if optics_si_dict: self.log_notice("{}: Apply Optics SI found for Vendor: {} PN: {} lane speed: {}G". format(lport, api.get_manufacturer(), api.get_model(), lane_speed)) @@ -1919,7 +1723,7 @@ def _post_port_sfp_info_and_dom_thr_to_db_once(self, port_mapping, xcvr_table_he # Do not notify media settings during warm reboot to avoid dataplane traffic impact if is_warm_start == False: - notify_media_setting(logical_port_name, transceiver_dict, xcvr_table_helper.get_app_port_tbl(asic_index), port_mapping) + media_settings_parser.notify_media_setting(logical_port_name, transceiver_dict, xcvr_table_helper.get_app_port_tbl(asic_index), xcvr_table_helper.get_cfg_port_tbl(asic_index), port_mapping) transceiver_dict.clear() else: retry_eeprom_set.add(logical_port_name) @@ -2141,7 +1945,7 @@ def task_worker(self, stopping_event, sfp_error_event): if rc != SFP_EEPROM_NOT_READY: post_port_dom_threshold_info_to_db(logical_port, self.port_mapping, self.xcvr_table_helper.get_dom_threshold_tbl(asic_index)) - notify_media_setting(logical_port, transceiver_dict, self.xcvr_table_helper.get_app_port_tbl(asic_index), self.port_mapping) + media_settings_parser.notify_media_setting(logical_port, transceiver_dict, self.xcvr_table_helper.get_app_port_tbl(asic_index), self.xcvr_table_helper.get_cfg_port_tbl(asic_index), self.port_mapping) transceiver_dict.clear() elif value == sfp_status_helper.SFP_STATUS_REMOVED: helper_logger.log_notice("{}: Got SFP removed event".format(logical_port)) @@ -2340,7 +2144,7 @@ def on_add_logical_port(self, port_change_event): self.retry_eeprom_set.add(port_change_event.port_name) else: post_port_dom_threshold_info_to_db(port_change_event.port_name, self.port_mapping, dom_threshold_tbl) - notify_media_setting(port_change_event.port_name, transceiver_dict, self.xcvr_table_helper.get_app_port_tbl(port_change_event.asic_id), self.port_mapping) + media_settings_parser.notify_media_setting(port_change_event.port_name, transceiver_dict, self.xcvr_table_helper.get_app_port_tbl(port_change_event.asic_id), self.xcvr_table_helper.get_cfg_port_tbl(port_change_event.asic_id), self.port_mapping) else: status = sfp_status_helper.SFP_STATUS_REMOVED if not status else status update_port_transceiver_status_table_sw(port_change_event.port_name, status_tbl, status, error_description) @@ -2366,7 +2170,7 @@ def retry_eeprom_reading(self): rc = post_port_sfp_info_to_db(logical_port, self.port_mapping, self.xcvr_table_helper.get_intf_tbl(asic_index), transceiver_dict) if rc != SFP_EEPROM_NOT_READY: post_port_dom_threshold_info_to_db(logical_port, self.port_mapping, self.xcvr_table_helper.get_dom_threshold_tbl(asic_index)) - notify_media_setting(logical_port, transceiver_dict, self.xcvr_table_helper.get_app_port_tbl(asic_index), self.port_mapping) + media_settings_parser.notify_media_setting(logical_port, transceiver_dict, self.xcvr_table_helper.get_app_port_tbl(asic_index), self.xcvr_table_helper.get_cfg_port_tbl(asic_index), self.port_mapping) transceiver_dict.clear() retry_success_set.add(logical_port) # Update retry EEPROM set @@ -2422,17 +2226,6 @@ def wait_for_port_config_done(self, namespace): if key in ["PortConfigDone", "PortInitDone"]: break - def load_media_settings(self): - global g_dict - (platform_path, _) = device_info.get_paths_to_platform_and_hwsku_dirs() - - media_settings_file_path = os.path.join(platform_path, "media_settings.json") - if not os.path.isfile(media_settings_file_path): - self.log_info("xcvrd: No media file exists") - return {} - - with open(media_settings_file_path, "r") as media_file: - g_dict = json.load(media_file) # Initialize daemon def init(self): @@ -2477,7 +2270,7 @@ def init(self): if is_fast_reboot_enabled(): self.log_info("Skip loading media_settings.json and optics_si_settings.json in case of fast-reboot") else: - self.load_media_settings() + media_settings_parser.load_media_settings() optics_si_parser.load_optics_si_settings() # Make sure this daemon started after all port configured diff --git a/sonic-xcvrd/xcvrd/xcvrd_utilities/media_settings_parser.py b/sonic-xcvrd/xcvrd/xcvrd_utilities/media_settings_parser.py new file mode 100644 index 000000000..2a27422dc --- /dev/null +++ b/sonic-xcvrd/xcvrd/xcvrd_utilities/media_settings_parser.py @@ -0,0 +1,325 @@ +""" +This parser is responsible for parsing the ASIC side SerDes custom SI settings. +""" + +import json +import os +import ast + +from sonic_py_common import device_info, logger +from swsscommon import swsscommon +from xcvrd import xcvrd + +g_dict = {} + +LANE_SPEED_KEY_PREFIX = "speed:" +VENDOR_KEY = 'vendor_key' +MEDIA_KEY = 'media_key' +LANE_SPEED_KEY = 'lane_speed_key' +SYSLOG_IDENTIFIER = "xcvrd" +helper_logger = logger.Logger(SYSLOG_IDENTIFIER) + + +def load_media_settings(): + global g_dict + (platform_path, _) = device_info.get_paths_to_platform_and_hwsku_dirs() + + media_settings_file_path = os.path.join(platform_path, "media_settings.json") + if not os.path.isfile(media_settings_file_path): + helper_logger.log_info("xcvrd: No media file exists") + return {} + + with open(media_settings_file_path, "r") as media_file: + g_dict = json.load(media_file) + + +def media_settings_present(): + if g_dict: + return True + return False + + +def get_lane_speed_key(physical_port, port_speed, lane_count): + 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)) + 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] + + 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)' + 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 + + media_len = '' + if transceiver_dict[physical_port]['cable_type'] == sup_len_str: + media_len = transceiver_dict[physical_port]['cable_length'] + + media_compliance_dict_str = transceiver_dict[physical_port]['specification_compliance'] + media_compliance_code = '' + media_type = '' + media_key = '' + media_compliance_dict = {} + + try: + sfp = xcvrd.platform_chassis.get_sfp(physical_port) + api = sfp.get_xcvr_api() + if xcvrd.is_cmis_api(api): + media_compliance_code = media_compliance_dict_str + else: + 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] + except ValueError as e: + helper_logger.log_error("Invalid value for port {} 'specification_compliance': {}".format(physical_port, media_compliance_dict_str)) + + media_type = transceiver_dict[physical_port]['type_abbrv_name'] + + if len(media_type) != 0: + media_key += media_type + if len(media_compliance_code) != 0: + media_key += '-' + media_compliance_code + sfp = xcvrd.platform_chassis.get_sfp(physical_port) + api = sfp.get_xcvr_api() + if xcvrd.is_cmis_api(api): + if media_compliance_code == "passive_copper_media_interface": + if media_len != 0: + media_key += '-' + str(media_len) + 'M' + else: + if media_len != 0: + media_key += '-' + str(media_len) + 'M' + else: + media_key += '-' + '*' + + lane_speed_key = get_lane_speed_key(physical_port, port_speed, lane_count) + # return (vendor_key, media_key, lane_speed_key) + return { + VENDOR_KEY: vendor_key, + MEDIA_KEY: media_key, + LANE_SPEED_KEY: lane_speed_key + } + + +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_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 = {} + + # Keys under global media settings can be a list or range or list of ranges + # of physical port numbers. Below are some examples + # 1-32 + # 1,2,3,4,5 + # 1-4,9-12 + + if GLOBAL_MEDIA_SETTINGS_KEY in g_dict: + for keys in g_dict[GLOBAL_MEDIA_SETTINGS_KEY]: + if COMMA_SEPARATOR in keys: + port_list = keys.split(COMMA_SEPARATOR) + for port in port_list: + if RANGE_SEPARATOR in port: + if xcvrd.check_port_in_range(port, physical_port): + media_dict = g_dict[GLOBAL_MEDIA_SETTINGS_KEY][keys] + break + elif str(physical_port) == port: + media_dict = g_dict[GLOBAL_MEDIA_SETTINGS_KEY][keys] + break + + elif RANGE_SEPARATOR in keys: + if xcvrd.check_port_in_range(keys, physical_port): + media_dict = g_dict[GLOBAL_MEDIA_SETTINGS_KEY][keys] + + # If there is a match in the global profile for a media type, + # fetch those values + if key[VENDOR_KEY] in media_dict: # e.g: 'AMPHENOL-1234' + if is_si_per_speed_supported(media_dict[key[VENDOR_KEY]]): + if key[LANE_SPEED_KEY] is not None and key[LANE_SPEED_KEY] in media_dict[key[VENDOR_KEY]]: # e.g: 'speed:400GAUI-8' + return media_dict[key[VENDOR_KEY]][key[LANE_SPEED_KEY]] + else: + return {} + else: + return media_dict[key[VENDOR_KEY]] + elif key[MEDIA_KEY] in media_dict: # e.g: 'QSFP28-40GBASE-CR4-1M' + if is_si_per_speed_supported(media_dict[key[MEDIA_KEY]]): + if key[LANE_SPEED_KEY] is not None and key[LANE_SPEED_KEY] in media_dict[key[MEDIA_KEY]]: + return media_dict[key[MEDIA_KEY]][key[LANE_SPEED_KEY]] + else: + return {} + else: + return media_dict[key[MEDIA_KEY]] + elif DEFAULT_KEY in media_dict: + default_dict = media_dict[DEFAULT_KEY] + + media_dict = {} + + if PORT_MEDIA_SETTINGS_KEY in g_dict: + for keys in g_dict[PORT_MEDIA_SETTINGS_KEY]: + if int(keys) == physical_port: + media_dict = g_dict[PORT_MEDIA_SETTINGS_KEY][keys] + break + + if len(media_dict) == 0: + if len(default_dict) != 0: + return default_dict + else: + helper_logger.log_error("Error: No values for physical port '{}'".format(physical_port)) + return {} + + if key[VENDOR_KEY] in media_dict: + if is_si_per_speed_supported(media_dict[key[VENDOR_KEY]]): + if key[LANE_SPEED_KEY] is not None and key[LANE_SPEED_KEY] in media_dict[key[VENDOR_KEY]]: + return media_dict[key[VENDOR_KEY]][key[LANE_SPEED_KEY]] + else: + return {} + else: + return media_dict[key[VENDOR_KEY]] + elif key[MEDIA_KEY] in media_dict: + if is_si_per_speed_supported(media_dict[key[MEDIA_KEY]]): + if key[LANE_SPEED_KEY] is not None and key[LANE_SPEED_KEY] in media_dict[key[MEDIA_KEY]]: + return media_dict[key[MEDIA_KEY]][key[LANE_SPEED_KEY]] + else: + return {} + else: + return media_dict[key[MEDIA_KEY]] + elif DEFAULT_KEY in media_dict: + return media_dict[DEFAULT_KEY] + elif len(default_dict) != 0: + return default_dict + else: + if len(default_dict) != 0: + return default_dict + + return {} + + +def get_speed_and_lane_count(port, cfg_port_tbl): + port_speed, lane_count = '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'] + lanes = port_info_dict['lanes'] + lane_count = len(lanes.split(',')) + return port_speed, lane_count + + +def notify_media_setting(logical_port_name, transceiver_dict, + app_port_tbl, cfg_port_tbl, port_mapping): + + if not media_settings_present(): + return + + port_speed, lane_count = get_speed_and_lane_count(logical_port_name, cfg_port_tbl) + + ganged_port = False + ganged_member_num = 1 + + physical_port_list = port_mapping.logical_port_name_to_physical_port_list(logical_port_name) + if physical_port_list is None: + helper_logger.log_error("Error: No physical ports found for logical port '{}'".format(logical_port_name)) + return PHYSICAL_PORT_NOT_EXIST + + if len(physical_port_list) > 1: + 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 + if physical_port not in transceiver_dict: + helper_logger.log_error("Media {} eeprom not populated in transceiver dict".format(physical_port)) + continue + + port_name = xcvrd.get_physical_port_name(logical_port_name, + ganged_member_num, ganged_port) + + 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)) + media_dict = get_media_settings_value(physical_port, key) + + if len(media_dict) == 0: + helper_logger.log_info("Error in obtaining media setting for {}".format(logical_port_name)) + return + + 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)) + 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) + 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)) + index += 1 + + app_port_tbl.set(port_name, fvs)