From 32ac7ae570231cb34ac6548a167c5cb219fd6e54 Mon Sep 17 00:00:00 2001 From: Vasundhara Volam Date: Thu, 7 Nov 2024 01:22:06 +0000 Subject: [PATCH 1/7] Define DPU helper functions --- .../sonic_py_common/device_info.py | 104 +++++++++++++----- 1 file changed, 76 insertions(+), 28 deletions(-) diff --git a/src/sonic-py-common/sonic_py_common/device_info.py b/src/sonic-py-common/sonic_py_common/device_info.py index 75e8dcb5761a..6ce617f6d6d2 100644 --- a/src/sonic-py-common/sonic_py_common/device_info.py +++ b/src/sonic-py-common/sonic_py_common/device_info.py @@ -168,6 +168,34 @@ def get_platform_and_hwsku(): return (platform, hwsku) +def get_platform_json_data(): + """ + Retrieve the data from platform.json file + + Returns: + A dictionary containing the key/value pairs as found in the platform.json file + """ + platform = get_platform() + if not platform: + return None + + platform_path = get_path_to_platform_dir() + if not platform_path: + return None + + platform_json = os.path.join(platform_path, PLATFORM_JSON_FILE) + if not os.path.isfile(platform_json): + return None + + try: + with open(platform_json, 'r') as f: + platform_data = json.loads(f.read()) + return platform_data + except (json.JSONDecodeError, IOError, TypeError, ValueError): + # Handle any file reading and JSON parsing errors + return None + + def get_asic_conf_file_path(): """ Retrieves the path to the ASIC configuration file on the device @@ -582,15 +610,30 @@ def is_smartswitch(): if not platform: return False - # get platform.json file path - platform_json = os.path.join(HOST_DEVICE_PATH, platform, "platform.json") - try: - with open(platform_json, 'r') as f: - platform_cfg = json.loads(f.read()) - return "DPUS" in platform_cfg - except IOError: + # Retrieve platform.json data + platform_data = get_platform_json_data() + if platform_data: + return "DPUS" in platform_data + + return False + + +def is_dpu(): + # Get platform + platform = get_platform() + if not platform: return False + if not is_smartswitch(): + return False + + # Retrieve platform.json data + platform_data = get_platform_json_data() + if platform_data: + return "DPU" in platform_data + + return False + def is_supervisor(): platform_env_conf_file_path = get_platform_env_conf_file_path() @@ -892,37 +935,42 @@ def is_frontend_port_present_in_host(): return True -def get_num_dpus(): +def get_dpu_info(): """ - Retrieves the number of DPUs from platform.json file. - - Args: + Retrieves the DPU information from platform.json file. Returns: - A integer to indicate the number of DPUs. + A dictionary containing the DPU information. """ platform = get_platform() if not platform: - return 0 + return {} - # Get Platform path. - platform_path = get_path_to_platform_dir() + # Retrieve platform.json data + platform_data = get_platform_json_data() + if platform_data: + # Convert to lower case avoid case sensitive. + data = {k.lower(): v for k, v in platform_data.items()} + dpu_info = data.get('dpus', None) + return dpu_info - if os.path.isfile(os.path.join(platform_path, PLATFORM_JSON_FILE)): - json_file = os.path.join(platform_path, PLATFORM_JSON_FILE) + return {} - try: - with open(json_file, 'r') as file: - platform_data = json.load(file) - except (json.JSONDecodeError, IOError, TypeError, ValueError): - # Handle any file reading and JSON parsing errors - return 0 - # Convert to lower case avoid case sensitive. - data = {k.lower(): v for k, v in platform_data.items()} - DPUs = data.get('dpus', None) - if DPUs is not None and len(DPUs) > 0: - return len(DPUs) + +def get_num_dpus(): + """ + Retrieves the number of DPUs from platform.json file. + + Args: + + Returns: + A integer to indicate the number of DPUs. + """ + + dpu_info = get_dpu_info() + if dpu_info is not None and len(DPUs) > 0: + return len(DPUs) return 0 From 116d40c44283e0742bf944a628172bf0c52ab0c7 Mon Sep 17 00:00:00 2001 From: Vasundhara Volam Date: Thu, 7 Nov 2024 05:16:51 +0000 Subject: [PATCH 2/7] Fix an error --- src/sonic-py-common/sonic_py_common/device_info.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/sonic-py-common/sonic_py_common/device_info.py b/src/sonic-py-common/sonic_py_common/device_info.py index 6ce617f6d6d2..d1576e38ccc6 100644 --- a/src/sonic-py-common/sonic_py_common/device_info.py +++ b/src/sonic-py-common/sonic_py_common/device_info.py @@ -958,7 +958,6 @@ def get_dpu_info(): return {} - def get_num_dpus(): """ Retrieves the number of DPUs from platform.json file. @@ -970,7 +969,7 @@ def get_num_dpus(): """ dpu_info = get_dpu_info() - if dpu_info is not None and len(DPUs) > 0: - return len(DPUs) + if dpu_info is not None and len(dpu_info) > 0: + return len(dpu_info) return 0 From 14287b5c41d60dd5854fc0f1db518629a8b822b3 Mon Sep 17 00:00:00 2001 From: Vasundhara Volam Date: Thu, 7 Nov 2024 20:44:54 +0000 Subject: [PATCH 3/7] Add get_dpu_list() --- .../sonic_py_common/device_info.py | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/sonic-py-common/sonic_py_common/device_info.py b/src/sonic-py-common/sonic_py_common/device_info.py index d1576e38ccc6..992678b98791 100644 --- a/src/sonic-py-common/sonic_py_common/device_info.py +++ b/src/sonic-py-common/sonic_py_common/device_info.py @@ -950,9 +950,9 @@ def get_dpu_info(): # Retrieve platform.json data platform_data = get_platform_json_data() if platform_data: - # Convert to lower case avoid case sensitive. + # Convert keys to lower case to avoid case sensitivity. data = {k.lower(): v for k, v in platform_data.items()} - dpu_info = data.get('dpus', None) + dpu_info = data.get('dpus', {}) return dpu_info return {} @@ -962,8 +962,6 @@ def get_num_dpus(): """ Retrieves the number of DPUs from platform.json file. - Args: - Returns: A integer to indicate the number of DPUs. """ @@ -973,3 +971,18 @@ def get_num_dpus(): return len(dpu_info) return 0 + + +def get_dpu_list(): + """ + Retrieves the list of DPUs from platform.json file. + + Returns: + A list indicating the list of DPUs. + """ + + dpu_info = get_dpu_info() + if dpu_info is not None and len(dpu_info) > 0: + return list(dpu_info) + + return [] From 96f60bd6cd46201d4c23475419088cb5be31c062 Mon Sep 17 00:00:00 2001 From: Vasundhara Volam Date: Sat, 9 Nov 2024 05:53:15 +0000 Subject: [PATCH 4/7] Extend get_dpu_info() for DPU as well --- .../sonic_py_common/device_info.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/sonic-py-common/sonic_py_common/device_info.py b/src/sonic-py-common/sonic_py_common/device_info.py index 992678b98791..d117b27aa2f1 100644 --- a/src/sonic-py-common/sonic_py_common/device_info.py +++ b/src/sonic-py-common/sonic_py_common/device_info.py @@ -949,13 +949,13 @@ def get_dpu_info(): # Retrieve platform.json data platform_data = get_platform_json_data() - if platform_data: - # Convert keys to lower case to avoid case sensitivity. - data = {k.lower(): v for k, v in platform_data.items()} - dpu_info = data.get('dpus', {}) - return dpu_info + if not platform_data: + return {} - return {} + if "DPUS" in platform_data: + return platform_data["DPUS"] + elif 'DPU' in platform_data: + return platform_data['DPU'] def get_num_dpus(): @@ -966,6 +966,9 @@ def get_num_dpus(): A integer to indicate the number of DPUs. """ + if is_dpu(): + return 0 + dpu_info = get_dpu_info() if dpu_info is not None and len(dpu_info) > 0: return len(dpu_info) @@ -981,6 +984,9 @@ def get_dpu_list(): A list indicating the list of DPUs. """ + if is_dpu(): + return [] + dpu_info = get_dpu_info() if dpu_info is not None and len(dpu_info) > 0: return list(dpu_info) From c75e9fd7b0ceb58235b37b64171a05464318c2fb Mon Sep 17 00:00:00 2001 From: Vasundhara Volam Date: Mon, 11 Nov 2024 22:59:54 +0000 Subject: [PATCH 5/7] Add an example return for get_dpu_list() --- src/sonic-py-common/sonic_py_common/device_info.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sonic-py-common/sonic_py_common/device_info.py b/src/sonic-py-common/sonic_py_common/device_info.py index d117b27aa2f1..e2e38bbf52aa 100644 --- a/src/sonic-py-common/sonic_py_common/device_info.py +++ b/src/sonic-py-common/sonic_py_common/device_info.py @@ -982,6 +982,7 @@ def get_dpu_list(): Returns: A list indicating the list of DPUs. + For example, ['dpu0', 'dpu1', 'dpu2'] """ if is_dpu(): From e96fcbfa04c68ac01a49b0c1929bc0c43c4c7d4e Mon Sep 17 00:00:00 2001 From: Vasundhara Volam Date: Sun, 24 Nov 2024 14:25:36 +0000 Subject: [PATCH 6/7] Write unit test for all smartswitch functions in device_info.py file --- src/sonic-py-common/tests/device_info_test.py | 163 ++++++++++++++++++ 1 file changed, 163 insertions(+) diff --git a/src/sonic-py-common/tests/device_info_test.py b/src/sonic-py-common/tests/device_info_test.py index 8596acd178db..17283fd8196f 100644 --- a/src/sonic-py-common/tests/device_info_test.py +++ b/src/sonic-py-common/tests/device_info_test.py @@ -10,6 +10,7 @@ import mock import pytest +import json from sonic_py_common import device_info @@ -162,6 +163,168 @@ def test_get_platform_info(self, mock_hwsku, mock_machine_info, mock_sonic_ver, assert mock_hwsku.called_once() mock_cfg_inst.get_table.assert_called_once_with("DEVICE_METADATA") + @mock.patch("os.path.isfile") + @mock.patch("{}.open".format(BUILTINS)) + @mock.patch("sonic_py_common.device_info.get_path_to_platform_dir") + @mock.patch("sonic_py_common.device_info.get_platform") + def test_get_platform_json_data(self, mock_get_platform, mock_get_path_to_platform_dir, mock_open, mock_isfile): + mock_get_platform.return_value = "x86_64-mlnx_msn2700-r0" + mock_get_path_to_platform_dir.return_value = "/usr/share/sonic/device" + mock_isfile.return_value = True + platform_json_data = { + "chassis": { + "name": "MSN2700" + } + } + open_mocked = mock.mock_open(read_data=json.dumps(platform_json_data)) + mock_open.side_effect = open_mocked + + result = device_info.get_platform_json_data() + assert result == platform_json_data + assert mock_open.call_count == 1 + + # Test case where platform is None + mock_get_platform.return_value = None + result = device_info.get_platform_json_data() + assert result is None + + # Test case where platform path is None + mock_get_platform.return_value = "x86_64-mlnx_msn2700-r0" + mock_get_path_to_platform_dir.return_value = None + result = device_info.get_platform_json_data() + assert result is None + + # Test case where platform.json file does not exist + mock_get_path_to_platform_dir.return_value = "/usr/share/sonic/device" + mock_isfile.return_value = False + result = device_info.get_platform_json_data() + assert result is None + + # Test case where JSON decoding fails + mock_isfile.return_value = True + open_mocked = mock.mock_open(read_data="invalid json") + mock_open.side_effect = open_mocked + result = device_info.get_platform_json_data() + assert result is None + + @mock.patch("sonic_py_common.device_info.get_platform_json_data") + @mock.patch("sonic_py_common.device_info.get_platform") + def test_is_smartswitch(self, mock_get_platform, mock_get_platform_json_data): + # Test case where platform is None + mock_get_platform.return_value = None + assert device_info.is_smartswitch() == False + + # Test case where platform.json data is None + mock_get_platform.return_value="x86_64-mlnx_msn2700-r0" + mock_get_platform_json_data.return_value=None + assert device_info.is_smartswitch() == False + + # Test case where platform.json data does not contain "DPUS" + mock_get_platform_json_data.return_value={} + assert device_info.is_smartswitch() == False + + # Test case where platform.json data contains "DPUS" + mock_get_platform_json_data.return_value={"DPUS": {}} + assert device_info.is_smartswitch() == True + + @mock.patch("sonic_py_common.device_info.get_platform_json_data") + @mock.patch("sonic_py_common.device_info.is_smartswitch") + @mock.patch("sonic_py_common.device_info.get_platform") + def test_is_dpu(self, mock_get_platform, mock_is_smartswitch, mock_get_platform_json_data): + # Test case where platform is None + mock_get_platform.return_value=None + assert device_info.is_dpu() == False + + # Test case where platform is not a smart switch + mock_get_platform.return_value="x86_64-mlnx_msn2700-r0" + mock_is_smartswitch.return_value=False + assert device_info.is_dpu() == False + + # Test case where platform is a smart switch but no DPU data in platform.json + mock_is_smartswitch.return_value=True + mock_get_platform_json_data.return_value={} + assert device_info.is_dpu() == False + + # Test case where platform is a smart switch and DPU data is present in platform.json + mock_get_platform_json_data.return_value={"DPU": {}} + assert device_info.is_dpu() == True + + @mock.patch("sonic_py_common.device_info.get_platform_json_data") + @mock.patch("sonic_py_common.device_info.get_platform") + def test_get_dpu_info(self, mock_get_platform, mock_get_platform_json_data): + # Test case where platform is None + mock_get_platform.return_value = None + assert device_info.get_dpu_info() == {} + + # Test case where platform.json data is None + mock_get_platform.return_value = "x86_64-mlnx_msn2700-r0" + mock_get_platform_json_data.return_value = None + assert device_info.get_dpu_info() == {} + + # Test case where platform.json data does not contain "DPUS" or "DPU" + mock_get_platform_json_data.return_value = {} + assert device_info.get_dpu_info() == {} + + # Test case where platform.json data contains "DPUS" + mock_get_platform_json_data.return_value = {"DPUS": {"dpu0": {}, "dpu1": {}}} + assert device_info.get_dpu_info() == {"dpu0": {}, "dpu1": {}} + + # Test case where platform.json data contains "DPU" + mock_get_platform_json_data.return_value = {"DPU": {"dpu0": {}}} + assert device_info.get_dpu_info() == {"dpu0": {}} + + @mock.patch("sonic_py_common.device_info.get_platform_json_data") + @mock.patch("sonic_py_common.device_info.is_dpu") + @mock.patch("sonic_py_common.device_info.get_platform") + def test_get_num_dpus(self, mock_get_platform, mock_is_dpu, mock_get_platform_json_data): + # Test case where platform is None + mock_get_platform.return_value = None + assert device_info.get_num_dpus() == 0 + + # Test case where platform is a DPU + mock_get_platform.return_value = "x86_64-mlnx_msn2700-r0" + mock_is_dpu.return_value = True + assert device_info.get_num_dpus() == 0 + + # Test case where platform.json data is None + mock_is_dpu.return_value = False + mock_get_platform_json_data.return_value = None + assert device_info.get_num_dpus() == 0 + + # Test case where platform.json data does not contain DPUs + mock_get_platform_json_data.return_value = {} + assert device_info.get_num_dpus() == 0 + + # Test case where platform.json data contains DPUs + mock_get_platform_json_data.return_value = {"DPUS": {"dpu0": {}, "dpu1": {}}} + assert device_info.get_num_dpus() == 2 + + @mock.patch("sonic_py_common.device_info.get_platform_json_data") + @mock.patch("sonic_py_common.device_info.is_dpu") + @mock.patch("sonic_py_common.device_info.get_platform") + def test_get_dpu_list(self, mock_get_platform, mock_is_dpu, mock_get_platform_json_data): + # Test case where platform is None + mock_get_platform.return_value = None + assert device_info.get_dpu_list() == [] + + # Test case where platform is a DPU + mock_get_platform.return_value = "x86_64-mlnx_msn2700-r0" + mock_is_dpu.return_value = True + assert device_info.get_dpu_list() == [] + + # Test case where platform.json data is None + mock_is_dpu.return_value = False + mock_get_platform_json_data.return_value = None + assert device_info.get_dpu_list() == [] + + # Test case where platform.json data does not contain DPUs + mock_get_platform_json_data.return_value = {} + assert device_info.get_dpu_list() == [] + + # Test case where platform.json data contains DPUs + mock_get_platform_json_data.return_value = {"DPUS": {"dpu0": {}, "dpu1": {}}} + assert device_info.get_dpu_list() == ["dpu0", "dpu1"] + @classmethod def teardown_class(cls): print("TEARDOWN") From f540e680952bd8cde0caf576e598c9eddb14b5c2 Mon Sep 17 00:00:00 2001 From: Vasundhara Volam Date: Mon, 25 Nov 2024 23:55:21 +0000 Subject: [PATCH 7/7] Add a missing else condition and corresponding test case --- src/sonic-py-common/sonic_py_common/device_info.py | 4 +++- src/sonic-py-common/tests/device_info_test.py | 8 ++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/sonic-py-common/sonic_py_common/device_info.py b/src/sonic-py-common/sonic_py_common/device_info.py index e2e38bbf52aa..238ab8dcdfdc 100644 --- a/src/sonic-py-common/sonic_py_common/device_info.py +++ b/src/sonic-py-common/sonic_py_common/device_info.py @@ -630,7 +630,7 @@ def is_dpu(): # Retrieve platform.json data platform_data = get_platform_json_data() if platform_data: - return "DPU" in platform_data + return 'DPU' in platform_data return False @@ -956,6 +956,8 @@ def get_dpu_info(): return platform_data["DPUS"] elif 'DPU' in platform_data: return platform_data['DPU'] + else: + return {} def get_num_dpus(): diff --git a/src/sonic-py-common/tests/device_info_test.py b/src/sonic-py-common/tests/device_info_test.py index 17283fd8196f..e7ac9e88c3c2 100644 --- a/src/sonic-py-common/tests/device_info_test.py +++ b/src/sonic-py-common/tests/device_info_test.py @@ -249,6 +249,10 @@ def test_is_dpu(self, mock_get_platform, mock_is_smartswitch, mock_get_platform_ mock_get_platform_json_data.return_value={"DPU": {}} assert device_info.is_dpu() == True + # Test case where platform is a smart switch and DPU data is present in platform.json + mock_get_platform_json_data.return_value={"DPUS": {}} + assert device_info.is_dpu() == False + @mock.patch("sonic_py_common.device_info.get_platform_json_data") @mock.patch("sonic_py_common.device_info.get_platform") def test_get_dpu_info(self, mock_get_platform, mock_get_platform_json_data): @@ -273,6 +277,10 @@ def test_get_dpu_info(self, mock_get_platform, mock_get_platform_json_data): mock_get_platform_json_data.return_value = {"DPU": {"dpu0": {}}} assert device_info.get_dpu_info() == {"dpu0": {}} + # Test case where platform.json data does not contain "DPU" or "DPUS" + mock_get_platform_json_data.return_value = {"chassis": {}} + assert device_info.get_dpu_info() == {} + @mock.patch("sonic_py_common.device_info.get_platform_json_data") @mock.patch("sonic_py_common.device_info.is_dpu") @mock.patch("sonic_py_common.device_info.get_platform")