A smart S.M.A.R.T. check
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

170 lines
6.8 KiB

  1. # -*- coding: utf-8 -*-
  2. import unittest
  3. import os
  4. from smartcheck.check import SMARTCheck, AttributeWarning
  5. samples_path = os.path.join(os.path.dirname(__file__), 'samples')
  6. db_path = os.path.join(samples_path, '../../src/smartcheck/disks.yaml')
  7. class CheckTest(unittest.TestCase):
  8. def test_check_broken1(self):
  9. with open(os.path.join(samples_path, 'seagate-barracuda-broken1.txt')) as f:
  10. check = SMARTCheck(f)
  11. self.assertFalse(check.check_tests())
  12. self.assertEqual(check.ata_error_count, 8)
  13. self.assertFalse(check.check())
  14. def test_check_broken2(self):
  15. with open(os.path.join(samples_path, 'seagate-barracuda-broken2.txt')) as f:
  16. check = SMARTCheck(f)
  17. self.assertFalse(check.check_tests())
  18. self.assertEqual(check.ata_error_count, 52)
  19. self.assertFalse(check.check())
  20. def test_check_broken3(self):
  21. with open(os.path.join(samples_path, 'WDC-WD1000FYPS-01ZKB0.txt')) as f:
  22. check = SMARTCheck(f)
  23. self.assertTrue(check.check_tests()) # no test ran
  24. self.assertEqual(check.ata_error_count, 32)
  25. self.assertFalse(check.check())
  26. def test_smart_attributes_not_found(self):
  27. with open(os.path.join(samples_path, 'ST2000NM0033-9ZM175.txt')) as f:
  28. check = SMARTCheck(f, db_path)
  29. self.assertTrue(check.check_tests())
  30. self.assertDictEqual(check.check_attributes(), {}) # Attributes not found in disks.json
  31. self.assertEqual(check.ata_error_count, 0)
  32. self.assertTrue(check.check())
  33. def test_smart_attributes_nothing_wrong(self):
  34. with open(os.path.join(samples_path, 'WDC-WD2000FYYZ-01UL1B1.txt')) as f:
  35. check = SMARTCheck(f, db_path)
  36. self.assertTrue(check.check_tests())
  37. self.assertDictEqual(check.check_attributes(), {})
  38. self.assertEqual(check.ata_error_count, 0)
  39. self.assertTrue(check.check())
  40. def test_smart_attributes_min_max(self):
  41. # from list
  42. with open(os.path.join(samples_path, 'ST2000NM0033-9ZM175.txt')) as f:
  43. check = SMARTCheck(f, os.path.join(samples_path, 'disks-min-max.yaml'))
  44. self.assertTrue(check.check_tests())
  45. self.assertDictEqual(check.check_attributes(), {
  46. (9, 'Power_On_Hours'): AttributeWarning(AttributeWarning.Critical, 'Power_On_Hours', 16998)
  47. })
  48. self.assertEqual(check.ata_error_count, 0)
  49. self.assertFalse(check.check())
  50. # from dict
  51. with open(os.path.join(samples_path, 'ST2000NM0033-9ZM175.txt')) as f:
  52. check = SMARTCheck(f, os.path.join(samples_path, 'disks-min-or-max.yaml'))
  53. self.assertTrue(check.check_tests())
  54. self.assertDictEqual(check.check_attributes(), {
  55. (9, 'Power_On_Hours'): AttributeWarning(AttributeWarning.Critical, 'Power_On_Hours', 16998),
  56. (194, 'Temperature_Celsius'): AttributeWarning(AttributeWarning.Critical, 'Temperature_Celsius', 30)
  57. })
  58. self.assertEqual(check.ata_error_count, 0)
  59. self.assertFalse(check.check())
  60. def test_smart_attributes_thresholds_min(self):
  61. for sample_file, expected_attributes in [
  62. # only warning
  63. ('disks-thresholds.yaml', {
  64. (9, 'Power_On_Hours'): AttributeWarning(AttributeWarning.Warning, 'Power_On_Hours', 15360)
  65. }),
  66. # warning and critical - critical wins
  67. ('disks-thresholds-warn-and-crit.yaml', {
  68. (9, 'Power_On_Hours'): AttributeWarning(AttributeWarning.Critical, 'Power_On_Hours', 15360)
  69. }),
  70. # warning threshold with range
  71. ('disks-thresholds-range.yaml', {
  72. (4, 'Start_Stop_Count'): AttributeWarning(AttributeWarning.Warning, 'Start_Stop_Count', 2)
  73. })
  74. ]:
  75. with open(os.path.join(samples_path, 'WDC-WD2000FYYZ-01UL1B1.txt')) as f:
  76. check = SMARTCheck(f, os.path.join(samples_path, sample_file))
  77. self.assertTrue(check.check_tests())
  78. self.assertDictEqual(check.check_attributes(), expected_attributes)
  79. self.assertFalse(check.check())
  80. def test_generic_attributes(self):
  81. base = """=== START OF INFORMATION SECTION ===
  82. Model Family: Seagate Barracuda 7200.14 (AF)
  83. Device Model: ST3000DM001-1CH167
  84. Serial Number: Z1F220RJ
  85. === START OF READ SMART DATA SECTION ===
  86. SMART Attributes Data Structure revision number: 1
  87. Vendor Specific SMART Attributes with Thresholds:
  88. ID# ATTRIBUTE_NAME FLAG VALUE WORST THRESH TYPE UPDATED WHEN_FAILED RAW_VALUE
  89. """
  90. for attr_id, attr_name in [
  91. ('5', "Reallocated_Sector_Ct"),
  92. ('197', "current_pending_sector"),
  93. ('197', "current_pending_sector_count"),
  94. ('197', "total_pending_sectors"),
  95. ('0', 'spin_up_retry_count'),
  96. ('0', 'soft_read_error_rate'),
  97. ('0', 'Reallocation_Event_Count'),
  98. ('0', 'ssd_life_left'),
  99. ]:
  100. s = base + "%s %s 0x000f 001 000 000 Pre-fail Always - 1" % (
  101. attr_id.rjust(3), attr_name.ljust(23)
  102. )
  103. check = SMARTCheck(s, db_path)
  104. failed_attributes = check.check_generic_attributes()
  105. assert len(failed_attributes) == 1
  106. (failed_id, failed_name), warning = list(failed_attributes.items())[0]
  107. assert int(failed_id) == int(attr_id)
  108. assert warning.level == AttributeWarning.Notice
  109. assert warning.value == '1'
  110. self.assertTrue(check.check_tests())
  111. self.assertFalse(check.check())
  112. def test_generic_temperature_attribute(self):
  113. base = """=== START OF INFORMATION SECTION ===
  114. Model Family: Seagate Barracuda 7200.14 (AF)
  115. Device Model: ST3000DM001-1CH167
  116. Serial Number: Z1F220RJ
  117. === START OF READ SMART DATA SECTION ===
  118. SMART Attributes Data Structure revision number: 1
  119. Vendor Specific SMART Attributes with Thresholds:
  120. ID# ATTRIBUTE_NAME FLAG VALUE WORST THRESH TYPE UPDATED WHEN_FAILED RAW_VALUE
  121. """
  122. for attr_id, attr_name, attr_value, notice_expected in [
  123. ('0', 'temperature_celsius', 1, False), # too low
  124. ('0', 'temperature_celsius', 51, True), # within range
  125. ('0', 'temperature_celsius', 130, False), # out of range
  126. ('0', 'temperature_celsius_x10', 1, False), # too low
  127. ('0', 'temperature_celsius_x10', 501, True), # within range
  128. ]:
  129. s = base + "%s %s 0x000f %s 000 000 Pre-fail Always - %s" % (
  130. attr_id.rjust(3), attr_name.ljust(23), str(attr_value).zfill(3), attr_value
  131. )
  132. check = SMARTCheck(s, db_path)
  133. failed_attributes = check.check_generic_attributes()
  134. if notice_expected:
  135. assert len(failed_attributes) == 1
  136. (failed_id, failed_name), warning = list(failed_attributes.items())[0]
  137. assert int(failed_id) == int(attr_id)
  138. assert warning.level == AttributeWarning.Notice
  139. assert warning.value == attr_value
  140. self.assertFalse(check.check())
  141. else:
  142. assert len(failed_attributes) == 0
  143. self.assertTrue(check.check_tests())
  144. def test_ignore_attributes(self):
  145. with open(os.path.join(samples_path, 'seagate-barracuda-broken1.txt')) as f:
  146. check = SMARTCheck(f)
  147. for ignore_value in (198, '198', 'Offline_Uncorrectable'):
  148. failed_attributes = check.check_attributes(ignore_attributes=[ignore_value])
  149. assert len(failed_attributes) == 1
  150. (failed_id, failed_name), failed_attribute = list(failed_attributes.items())[0]
  151. assert failed_id == 197
  152. assert failed_name == "Current_Pending_Sector"
  153. assert int(failed_attribute.value) == 24