Skip to content

Commit df078fc

Browse files
authored
feat: Add spec smartctl_health (#4422)
Signed-off-by: jiazhang <[email protected]> rh-pre-commit.version: 2.3.1 rh-pre-commit.check-secrets: ENABLED
1 parent 775523c commit df078fc

File tree

7 files changed

+235
-18
lines changed

7 files changed

+235
-18
lines changed

insights/parsers/smartctl.py

Lines changed: 68 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,18 @@
11
"""
2-
SMARTctl - command ``/sbin/smartctl -a {device}``
3-
=================================================
2+
smartctl - parser for smartctl commands
3+
=======================================
4+
5+
Classes to parse the output of smartctl commands:
6+
7+
8+
SMARTctl - /usr/sbin/smartctl -a {devices}
9+
------------------------------------------
10+
SmartctlHealth - /usr/sbin/smartctl -H {devices} -j
11+
---------------------------------------------------
412
"""
513
import re
614

7-
from insights.core import CommandParser
15+
from insights.core import CommandParser, JSONParser
816
from insights.core.exceptions import ParseException
917
from insights.core.plugins import parser
1018
from insights.specs import Specs
@@ -62,18 +70,12 @@ class SMARTctl(CommandParser):
6270
...
6371
6472
Examples:
65-
>>> for drive in shared[SMARTctl]:
66-
... print "Device:", drive.device
67-
... print "Model:", drive.information['Device Model']
68-
... print "Health check:", drive.health
69-
... print "Last self-test status:", drive.values['Self-test execution status']
70-
... print "Raw read error rate:", drive.attributes['Raw_Read_Error_Rate']['RAW_VALUE']
71-
...
72-
Device: /dev/sda
73-
Model: ST500LM021-1KJ152
74-
Health check: PASSED
75-
Last self-test status: 0
76-
Raw read error rate: 179599704
73+
>>> type(smartctl_all)
74+
<class 'insights.parsers.smartctl.SMARTctl'>
75+
>>> smartctl_all.device
76+
'/dev/sdc'
77+
>>> smartctl_all.information['Vendor']
78+
'NETAPP'
7779
7880
"""
7981

@@ -201,3 +203,54 @@ def parse_attributes(line):
201203

202204
# Delete temporary full line storage
203205
del self.full_line
206+
207+
208+
@parser(Specs.smartctl_health)
209+
class SmartctlHealth(JSONParser):
210+
"""
211+
Parse the output of command "smartctl -H -d scsi {devices}".
212+
213+
Sample input::
214+
215+
{
216+
"json_format_version": [
217+
1,
218+
0
219+
],
220+
"smartctl": {
221+
"version": [
222+
7,
223+
2
224+
],
225+
"svn_revision": "5155",
226+
"platform_info": "x86_64-linux-5.14.0-503.11.1.el9_5.x86_64",
227+
"build_info": "(local build)",
228+
"argv": [
229+
"smartctl",
230+
"-H",
231+
"-d",
232+
"scsi",
233+
"/dev/sdb",
234+
"-j"
235+
],
236+
"exit_status": 0
237+
},
238+
"device": {
239+
"name": "/dev/sdb",
240+
"info_name": "/dev/sdb",
241+
"type": "scsi",
242+
"protocol": "SCSI"
243+
},
244+
"smart_status": {
245+
"passed": true
246+
}
247+
}
248+
249+
Examples:
250+
>>> type(smartctl_health)
251+
<class 'insights.parsers.smartctl.SmartctlHealth'>
252+
>>> smartctl_health.data['smart_status']["passed"]
253+
True
254+
255+
"""
256+
pass

insights/specs/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -750,6 +750,7 @@ class Specs(SpecSet):
750750
filterable=True, no_obfuscate=['hostname', 'ip', 'ipv6', 'mac']
751751
)
752752
smartctl = RegistryPoint(multi_output=True, no_obfuscate=['hostname', 'ip', 'ipv6', 'mac'])
753+
smartctl_health = RegistryPoint(multi_output=True, no_obfuscate=['hostname', 'ip', 'ipv6', 'mac'])
753754
smartpdc_settings = RegistryPoint(filterable=True)
754755
smbstatus_S = RegistryPoint()
755756
smbstatus_p = RegistryPoint()

insights/specs/datasources/dev.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
"""
2+
Custom datasources to get the device names.
3+
"""
4+
5+
from insights.core.context import HostContext
6+
from insights.core.exceptions import SkipComponent
7+
from insights.core.plugins import datasource
8+
from insights.parsers.blkid import BlockIDInfo
9+
from insights.combiners.virt_what import VirtWhat
10+
11+
12+
@datasource(BlockIDInfo, VirtWhat, HostContext)
13+
def physical_devices(broker):
14+
"""
15+
This datasource get the name of the physical_devices.
16+
17+
Returns:
18+
list: the names of the physical_devices
19+
20+
Raises:
21+
SkipComponent: there is physical_device
22+
"""
23+
blockdinfo = broker[BlockIDInfo]
24+
virtwhat = broker[VirtWhat]
25+
result = []
26+
if virtwhat.is_physical:
27+
for item in blockdinfo.data:
28+
device_name = item["NAME"]
29+
if device_name.startswith("/dev/sd") or device_name.startswith("/dev/hd"):
30+
main_dev_name = ''.join([i for i in device_name if not i.isdigit()])
31+
result.append(main_dev_name)
32+
if result:
33+
return sorted(list(set(result)))
34+
raise SkipComponent("No physical device")

insights/specs/default.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
cloud_init,
5050
corosync as corosync_ds,
5151
db2,
52+
dev,
5253
du,
5354
eap_reports,
5455
env,
@@ -824,6 +825,7 @@ class DefaultSpecs(Specs):
824825
sendmail_mc = simple_file("/etc/mail/sendmail.mc")
825826
sestatus = simple_command("/usr/sbin/sestatus -b")
826827
setup_named_chroot = simple_file("/usr/libexec/setup-named-chroot.sh")
828+
smartctl_health = foreach_execute(dev.physical_devices, "/usr/sbin/smartctl -H %s -j")
827829
smbstatus_p = simple_command("/usr/bin/smbstatus -p")
828830
sockstat = simple_file("/proc/net/sockstat")
829831
softnet_stat = simple_file("proc/net/softnet_stat")

insights/specs/manifests.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@
156156
- name: insights.combiners.sap.Sap
157157
enabled: true
158158
159-
# needed for fw_security specs
159+
# needed for specs: fw_security, smartctl_health
160160
- name: insights.parsers.virt_what.VirtWhat
161161
enabled: true
162162
- name: insights.combiners.virt_what.VirtWhat
@@ -190,7 +190,7 @@
190190
- name: insights.parsers.docker_list.DockerListContainers
191191
enabled: true
192192
193-
# needed for 'luks_data_sources' spec
193+
# needed for specs: luks_data_sources, smartctl_health spec
194194
- name: insights.parsers.blkid.BlockIDInfo
195195
enabled: true
196196
- name: insights.components.cryptsetup.HasCryptsetupWithTokens
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import pytest
2+
3+
from insights.core.exceptions import SkipComponent
4+
from insights.specs.datasources.dev import physical_devices
5+
from insights.combiners.virt_what import VirtWhat
6+
from insights.core import dr
7+
from insights.parsers.blkid import BlockIDInfo
8+
from insights.parsers.virt_what import VirtWhat as VWP
9+
from insights.tests import context_wrap
10+
11+
12+
BLKID_INFO = """
13+
/dev/sda1: UUID="3676157d-f2f5-465c-a4c3-fffffffff" TYPE="xfs"
14+
/dev/sda2: UUID="UVTk76-UWOc-vk7s-galL-dxIP-fffffffff" TYPE="LVM2_member"
15+
/dev/mapper/rhel_hp--dl160g8--3-root: UUID="11124c1d-990b-4277-9f74-fffffffff" TYPE="xfs"
16+
/dev/mapper/rhel_hp--dl160g8--3-swap: UUID="c7c45f2d-1d1b-4cf0-9d51-fffffffff" TYPE="swap"
17+
/dev/loop0: LABEL="Satellite-5.6.0 x86_64 Disc 0" TYPE="iso9660"
18+
/dev/block/253:1: UUID="f8508c37-eeb1-4598-b084-fffffffff" TYPE="ext2"
19+
/dev/sdb1: UUID="4676157d-f2f5-465c-a4c3-fffffffff" TYPE="xfs"
20+
""".strip()
21+
22+
BLKID_INFO2 = """
23+
/dev/vda1: UUID="3676157d-f2f5-465c-a4c3-fffffffff" TYPE="xfs"
24+
/dev/vda2: UUID="UVTk76-UWOc-vk7s-galL-dxIP-fffffffff" TYPE="LVM2_member"
25+
/dev/mapper/rhel_hp--dl160g8--3-root: UUID="11124c1d-990b-4277-9f74-fffffffff" TYPE="xfs"
26+
/dev/mapper/rhel_hp--dl160g8--3-swap: UUID="c7c45f2d-1d1b-4cf0-9d51-fffffffff" TYPE="swap"
27+
/dev/loop0: LABEL="Satellite-5.6.0 x86_64 Disc 0" TYPE="iso9660"
28+
/dev/block/253:1: UUID="f8508c37-eeb1-4598-b084-fffffffff" TYPE="ext2"
29+
/dev/vdb1: UUID="4676157d-f2f5-465c-a4c3-fffffffff" TYPE="xfs"
30+
""".strip()
31+
32+
VW_OUT1 = """
33+
kvm
34+
""".strip()
35+
36+
# baremetal retuns blank
37+
VW_OUT2 = """
38+
39+
""".strip()
40+
41+
42+
def test_physical_devices():
43+
virt_what_info = VirtWhat(None, VWP(context_wrap(VW_OUT2)))
44+
blkid_info = BlockIDInfo(context_wrap(BLKID_INFO))
45+
46+
broker = dr.Broker()
47+
broker[BlockIDInfo] = blkid_info
48+
broker[VirtWhat] = virt_what_info
49+
result = physical_devices(broker)
50+
assert result == ["/dev/sda", "/dev/sdb"]
51+
52+
53+
def test_physical_devices_virt_env():
54+
virt_what_info = VirtWhat(None, VWP(context_wrap(VW_OUT1)))
55+
blkid_info = BlockIDInfo(context_wrap(BLKID_INFO))
56+
57+
broker = dr.Broker()
58+
broker[BlockIDInfo] = blkid_info
59+
broker[VirtWhat] = virt_what_info
60+
with pytest.raises(SkipComponent):
61+
physical_devices(broker)
62+
63+
64+
def test_physical_devices_virt_env2():
65+
virt_what_info = VirtWhat(None, VWP(context_wrap(VW_OUT1)))
66+
blkid_info = BlockIDInfo(context_wrap(BLKID_INFO2))
67+
68+
broker = dr.Broker()
69+
broker[BlockIDInfo] = blkid_info
70+
broker[VirtWhat] = virt_what_info
71+
with pytest.raises(SkipComponent):
72+
physical_devices(broker)

insights/tests/parsers/test_smartctl.py

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
import pytest
2+
import doctest
23

34
from insights.core.exceptions import ParseException
4-
from insights.parsers.smartctl import SMARTctl
5+
from insights.parsers import smartctl
6+
from insights.parsers.smartctl import SMARTctl, SmartctlHealth
57
from insights.tests import context_wrap
68

9+
710
STANDARD_DRIVE = """
811
smartctl 6.2 2013-07-26 r3841 [x86_64-linux-3.10.0-267.el7.x86_64] (local build)
912
Copyright (C) 2002-13, Bruce Allen, Christian Franke, www.smartmontools.org
@@ -284,3 +287,55 @@ def test_netapp_drive():
284287
assert data.health == 'not parsed'
285288
assert data.values == {}
286289
assert data.attributes == {}
290+
291+
292+
SMARTCTL_HEALTH = """
293+
{
294+
"json_format_version": [
295+
1,
296+
0
297+
],
298+
"smartctl": {
299+
"version": [
300+
7,
301+
2
302+
],
303+
"svn_revision": "5155",
304+
"platform_info": "x86_64-linux-5.14.0-503.11.1.el9_5.x86_64",
305+
"build_info": "(local build)",
306+
"argv": [
307+
"smartctl",
308+
"-H",
309+
"-d",
310+
"scsi",
311+
"/dev/sdb",
312+
"-j"
313+
],
314+
"exit_status": 0
315+
},
316+
"device": {
317+
"name": "/dev/sdb",
318+
"info_name": "/dev/sdb",
319+
"type": "scsi",
320+
"protocol": "SCSI"
321+
},
322+
"smart_status": {
323+
"passed": true
324+
}
325+
}
326+
""".strip()
327+
328+
329+
def test_smartctl_health():
330+
smartctl_health_info = SmartctlHealth(context_wrap(SMARTCTL_HEALTH))
331+
assert smartctl_health_info.data['device']['name'] == '/dev/sdb'
332+
assert smartctl_health_info.data['smart_status']['passed'] is True
333+
334+
335+
def test_smartctl():
336+
env = {
337+
'smartctl_health': SmartctlHealth(context_wrap(SMARTCTL_HEALTH)),
338+
'smartctl_all': SMARTctl(context_wrap(NETAPP_DRIVE, path='sos_commands/ata/smartctl_-a_.dev.sdc')),
339+
}
340+
failed, total = doctest.testmod(smartctl, globs=env)
341+
assert failed == 0

0 commit comments

Comments
 (0)