From 3620a1ea63bae0248d1f64efe04f7baccf7687ea Mon Sep 17 00:00:00 2001 From: Rustiqly Date: Wed, 25 Mar 2026 15:07:55 -0700 Subject: [PATCH 1/5] [agent][ssd] Fix NameError: nand_endurance undefined in parse_micron_info() parse_micron_info() references nand_endurance which was never defined in this method (only exists in parse_virtium_info). Parse it from SMART attributes using the same regex pattern as the Virtium parser. Also remove the unused erase_fail_count variable. Fixes: sonic-net/sonic-platform-common#644 Signed-off-by: Rustiqly --- sonic_platform_base/sonic_storage/ssd.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sonic_platform_base/sonic_storage/ssd.py b/sonic_platform_base/sonic_storage/ssd.py index 417068d68..438118a48 100644 --- a/sonic_platform_base/sonic_storage/ssd.py +++ b/sonic_platform_base/sonic_storage/ssd.py @@ -338,9 +338,9 @@ def parse_micron_info(self): if health_raw == NOT_AVAILABLE: average_erase_count = self.parse_id_number(MICRON_AVG_ERASE_COUNT_ID, self.vendor_ssd_info) - erase_fail_count = self.parse_id_number(MICRON_ERASE_FAIL_COUNT_ID, self.vendor_ssd_info) + nand_endurance = self._parse_re(r'NAND_Endurance\s*\d*\s*(\d+?)\s+', self.vendor_ssd_info) - if average_erase_count != NOT_AVAILABLE and erase_fail_count != NOT_AVAILABLE: + if average_erase_count != NOT_AVAILABLE and nand_endurance != NOT_AVAILABLE: try: self.health = 100 - (float(average_erase_count) * 100 / float(nand_endurance)) except (ValueError, ZeroDivisionError) as ex: From 357834165ff032d8dd7f038317f67e84df8f1637 Mon Sep 17 00:00:00 2001 From: Rustiqly Date: Mon, 11 May 2026 02:17:09 -0700 Subject: [PATCH 2/5] Add Micron NAND endurance health unit test Signed-off-by: Rustiqly --- tests/test_ssd.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/test_ssd.py b/tests/test_ssd.py index 7259535fc..9d67424ff 100644 --- a/tests/test_ssd.py +++ b/tests/test_ssd.py @@ -1951,6 +1951,19 @@ def test_micron_ssd(self): assert(micron_ssd.get_disk_io_writes() == '9607694422') assert(micron_ssd.get_reserved_blocks() == '475') + def test_micron_ssd_health_from_nand_endurance(self): + micron_output = output_micron_ssd.replace( + "202 Percent_Lifetime_Used 0x0031 075 075 000 Pre-fail Offline - 25\n", + "202 Unknown_Attribute 0x0031 075 075 000 Pre-fail Offline - 25\n" + "999 NAND_Endurance 0x0032 100 100 000 Old_age Always - 3028\n" + ) + + with mock.patch('sonic_platform_base.sonic_storage.ssd.SsdUtil._execute_shell', + mock.MagicMock(return_value=micron_output)): + micron_ssd = SsdUtil('/dev/sda') + + assert micron_ssd.get_health() == 75.0 + @mock.patch('sonic_platform_base.sonic_storage.ssd.SsdUtil._execute_shell', mock.MagicMock(return_value=output_intel_ssd)) def test_intel_ssd(self): From 43036a4b5ba47363d6002661110e05756d75e7ef Mon Sep 17 00:00:00 2001 From: Rustiqly Date: Mon, 11 May 2026 03:14:00 -0700 Subject: [PATCH 3/5] Fix Micron NAND endurance fallback parsing Signed-off-by: Rustiqly --- sonic_platform_base/sonic_storage/ssd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sonic_platform_base/sonic_storage/ssd.py b/sonic_platform_base/sonic_storage/ssd.py index 438118a48..ef309ebdf 100644 --- a/sonic_platform_base/sonic_storage/ssd.py +++ b/sonic_platform_base/sonic_storage/ssd.py @@ -342,7 +342,7 @@ def parse_micron_info(self): if average_erase_count != NOT_AVAILABLE and nand_endurance != NOT_AVAILABLE: try: - self.health = 100 - (float(average_erase_count) * 100 / float(nand_endurance)) + self.health = 100 - (float(average_erase_count.split()[-1]) * 100 / float(nand_endurance.split()[-1])) except (ValueError, ZeroDivisionError) as ex: self.log.log_info("SsdUtil parse_micron_info exception: {}".format(ex)) pass From 75ef227b6aaa38c40e524405036af98452c4a01a Mon Sep 17 00:00:00 2001 From: Rustiqly Date: Mon, 11 May 2026 04:15:17 -0700 Subject: [PATCH 4/5] Fix Micron NAND endurance SMART parsing Signed-off-by: Rustiqly --- sonic_platform_base/sonic_storage/ssd.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sonic_platform_base/sonic_storage/ssd.py b/sonic_platform_base/sonic_storage/ssd.py index ef309ebdf..f381e0af9 100644 --- a/sonic_platform_base/sonic_storage/ssd.py +++ b/sonic_platform_base/sonic_storage/ssd.py @@ -275,8 +275,8 @@ def parse_virtium_info(self): health_raw = self.parse_id_number(VIRTIUM_HEALTH_ID, self.vendor_ssd_info) self.health = float(health_raw.split()[2]) if health_raw != NOT_AVAILABLE else NOT_AVAILABLE else : - nand_endurance = self._parse_re(r'NAND_Endurance\s*\d*\s*(\d+?)\s+', self.vendor_ssd_info) - avg_erase_count = self._parse_re(r'Average_Erase_Count\s*\d*\s*(\d+?)\s+', self.vendor_ssd_info) + nand_endurance = self._parse_re(r'(?m)NAND_Endurance.*?(\d+)\s*$', self.vendor_ssd_info) + avg_erase_count = self._parse_re(r'(?m)Average_Erase_Count.*?(\d+)\s*$', self.vendor_ssd_info) if nand_endurance != NOT_AVAILABLE and avg_erase_count != NOT_AVAILABLE: try: self.health = 100 - (float(avg_erase_count) * 100 / float(nand_endurance)) @@ -338,7 +338,7 @@ def parse_micron_info(self): if health_raw == NOT_AVAILABLE: average_erase_count = self.parse_id_number(MICRON_AVG_ERASE_COUNT_ID, self.vendor_ssd_info) - nand_endurance = self._parse_re(r'NAND_Endurance\s*\d*\s*(\d+?)\s+', self.vendor_ssd_info) + nand_endurance = self._parse_re(r'(?m)NAND_Endurance.*?(\d+)\s*$', self.vendor_ssd_info) if average_erase_count != NOT_AVAILABLE and nand_endurance != NOT_AVAILABLE: try: From 70efdf52be75dc2bd422f4037ba79d34bc84b1c1 Mon Sep 17 00:00:00 2001 From: Rustiqly Date: Mon, 11 May 2026 05:16:37 -0700 Subject: [PATCH 5/5] Fix Virtium SSD health regression Signed-off-by: Rustiqly --- sonic_platform_base/sonic_storage/ssd.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sonic_platform_base/sonic_storage/ssd.py b/sonic_platform_base/sonic_storage/ssd.py index f381e0af9..c66c381f3 100644 --- a/sonic_platform_base/sonic_storage/ssd.py +++ b/sonic_platform_base/sonic_storage/ssd.py @@ -275,8 +275,8 @@ def parse_virtium_info(self): health_raw = self.parse_id_number(VIRTIUM_HEALTH_ID, self.vendor_ssd_info) self.health = float(health_raw.split()[2]) if health_raw != NOT_AVAILABLE else NOT_AVAILABLE else : - nand_endurance = self._parse_re(r'(?m)NAND_Endurance.*?(\d+)\s*$', self.vendor_ssd_info) - avg_erase_count = self._parse_re(r'(?m)Average_Erase_Count.*?(\d+)\s*$', self.vendor_ssd_info) + nand_endurance = self._parse_re(r'NAND_Endurance\s*\d*\s*(\d+?)\s+', self.vendor_ssd_info) + avg_erase_count = self._parse_re(r'Average_Erase_Count\s*\d*\s*(\d+?)\s+', self.vendor_ssd_info) if nand_endurance != NOT_AVAILABLE and avg_erase_count != NOT_AVAILABLE: try: self.health = 100 - (float(avg_erase_count) * 100 / float(nand_endurance)) @@ -342,7 +342,7 @@ def parse_micron_info(self): if average_erase_count != NOT_AVAILABLE and nand_endurance != NOT_AVAILABLE: try: - self.health = 100 - (float(average_erase_count.split()[-1]) * 100 / float(nand_endurance.split()[-1])) + self.health = 100 - (float(average_erase_count.split()[-1]) * 100 / float(nand_endurance)) except (ValueError, ZeroDivisionError) as ex: self.log.log_info("SsdUtil parse_micron_info exception: {}".format(ex)) pass