Skip to content

Commit 4668bdc

Browse files
authored
Enhanced NVMe disk support, added limited eUSB disk support (sonic-net#493)
* Enhanced NVMe storage disk attributes; 'Ssdtil' instantiation for devices with NVMe disks * Added limited support for USB storage devices * usb.py: removed logging, ssd.py: split nvme parsing to its own function
1 parent 7268fad commit 4668bdc

File tree

9 files changed

+327
-32
lines changed

9 files changed

+327
-32
lines changed

sonic_platform_base/sonic_storage/ssd.py

+44-27
Original file line numberDiff line numberDiff line change
@@ -148,24 +148,41 @@ def _parse_vendor(self):
148148
def fetch_generic_ssd_info(self, diskdev):
149149
self.ssd_info = self._execute_shell(self.vendor_ssd_utility["Generic"]["utility"].format(diskdev))
150150

151+
def parse_nvme_ssd_info(self):
152+
self.model = self._parse_re('Model Number:\s*(.+?)\n', self.ssd_info)
153+
154+
health_raw = self._parse_re('Percentage Used\s*(.+?)\n', self.ssd_info)
155+
if health_raw == NOT_AVAILABLE:
156+
self.health = NOT_AVAILABLE
157+
else:
158+
health_raw = health_raw.split()[-1]
159+
self.health = 100 - float(health_raw.strip('%'))
160+
161+
temp_raw = self._parse_re('Temperature\s*(.+?)\n', self.ssd_info)
162+
if temp_raw == NOT_AVAILABLE:
163+
self.temperature = NOT_AVAILABLE
164+
else:
165+
temp_raw = temp_raw.split()[-2]
166+
self.temperature = float(temp_raw)
167+
168+
spare_blocks_raw = self._parse_re('Available Spare\s*(.+?)\n', self.ssd_info)
169+
if spare_blocks_raw != NOT_AVAILABLE:
170+
spare_blocks_raw = spare_blocks_raw.split()[-1]
171+
self.reserved_blocks = float(spare_blocks_raw.strip('%'))
172+
173+
disk_io_reads_raw = self._parse_re('Data Units Read\s*(.+?)\n', self.ssd_info)
174+
if disk_io_reads_raw != NOT_AVAILABLE:
175+
self.disk_io_reads = disk_io_reads_raw.split(':')[-1].strip()
176+
177+
disk_io_writes_raw = self._parse_re('Data Units Written\s*(.+?)\n', self.ssd_info)
178+
if disk_io_writes_raw != NOT_AVAILABLE:
179+
self.disk_io_writes = disk_io_writes_raw.split(':')[-1].strip()
180+
151181
# Health and temperature values may be overwritten with vendor specific data
152182
def parse_generic_ssd_info(self):
153-
if "nvme" in self.dev:
154-
self.model = self._parse_re('Model Number:\s*(.+?)\n', self.ssd_info)
155183

156-
health_raw = self._parse_re('Percentage Used\s*(.+?)\n', self.ssd_info)
157-
if health_raw == NOT_AVAILABLE:
158-
self.health = NOT_AVAILABLE
159-
else:
160-
health_raw = health_raw.split()[-1]
161-
self.health = 100 - float(health_raw.strip('%'))
162-
163-
temp_raw = self._parse_re('Temperature\s*(.+?)\n', self.ssd_info)
164-
if temp_raw == NOT_AVAILABLE:
165-
self.temperature = NOT_AVAILABLE
166-
else:
167-
temp_raw = temp_raw.split()[-2]
168-
self.temperature = float(temp_raw)
184+
if "nvme" in self.dev:
185+
self.parse_nvme_ssd_info()
169186
else:
170187
self.model = self._parse_re('Device Model:\s*(.+?)\n', self.ssd_info)
171188

@@ -184,21 +201,21 @@ def parse_generic_ssd_info(self):
184201
else:
185202
self.temperature = temp_raw.split()[7].split()[0]
186203

187-
self.serial = self._parse_re('Serial Number:\s*(.+?)\n', self.ssd_info)
188-
self.firmware = self._parse_re('Firmware Version:\s*(.+?)\n', self.ssd_info)
204+
io_reads_raw = self.parse_id_number(GENERIC_IO_READS_ID, self.ssd_info)
205+
self.disk_io_reads = NOT_AVAILABLE if io_reads_raw == NOT_AVAILABLE else io_reads_raw.split()[-1]
189206

190-
io_reads_raw = self.parse_id_number(GENERIC_IO_READS_ID, self.ssd_info)
191-
self.disk_io_reads = NOT_AVAILABLE if io_reads_raw == NOT_AVAILABLE else io_reads_raw.split()[-1]
207+
io_writes_raw = self.parse_id_number(GENERIC_IO_WRITES_ID, self.ssd_info)
208+
self.disk_io_writes = NOT_AVAILABLE if io_writes_raw == NOT_AVAILABLE else io_writes_raw.split()[-1]
192209

193-
io_writes_raw = self.parse_id_number(GENERIC_IO_WRITES_ID, self.ssd_info)
194-
self.disk_io_writes = NOT_AVAILABLE if io_writes_raw == NOT_AVAILABLE else io_writes_raw.split()[-1]
210+
for ID in GENERIC_RESERVED_BLOCKS_ID:
211+
rbc_raw = self.parse_id_number(ID, self.ssd_info)
212+
if rbc_raw == NOT_AVAILABLE: self.reserved_blocks = NOT_AVAILABLE
213+
else:
214+
self.reserved_blocks = rbc_raw.split()[-1]
215+
break
195216

196-
for ID in GENERIC_RESERVED_BLOCKS_ID:
197-
rbc_raw = self.parse_id_number(ID, self.ssd_info)
198-
if rbc_raw == NOT_AVAILABLE: self.reserved_blocks = NOT_AVAILABLE
199-
else:
200-
self.reserved_blocks = rbc_raw.split()[-1]
201-
break
217+
self.serial = self._parse_re('Serial Number:\s*(.+?)\n', self.ssd_info)
218+
self.firmware = self._parse_re('Firmware Version:\s*(.+?)\n', self.ssd_info)
202219

203220
def parse_innodisk_info(self):
204221
if self.vendor_ssd_info:

sonic_platform_base/sonic_storage/storage_devices.py

+6-2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
from sonic_py_common import syslogger
1313
from sonic_platform_base.sonic_storage.ssd import SsdUtil
1414
from sonic_platform_base.sonic_storage.emmc import EmmcUtil
15+
from sonic_platform_base.sonic_storage.usb import UsbUtil
16+
1517
except ImportError as e:
1618
raise ImportError (str(e) + "- required module not found")
1719

@@ -23,6 +25,8 @@
2325

2426
BASE_PATH = "/sys/block"
2527
BLKDEV_BASE_PATH = "/dev"
28+
ssdutil_compatible_keys = ('sd', 'nvme')
29+
ssdutil_compatible_paths_strings = ('ata', 'nvme')
2630

2731
class StorageDevices:
2832
def __init__(self):
@@ -65,9 +69,9 @@ def _storage_device_object_factory(self, key):
6569
blkdev = os.path.join(BLKDEV_BASE_PATH, key)
6670
diskdev = os.path.join(BASE_PATH, key)
6771

68-
if key.startswith('sd'):
72+
if key.startswith(ssdutil_compatible_keys):
6973
path = os.path.join(diskdev, "device")
70-
if "ata" in os.path.realpath(path):
74+
if any(substring in os.path.realpath(path) for substring in ssdutil_compatible_paths_strings):
7175
try:
7276
return SsdUtil(blkdev)
7377
except Exception as e:
+136
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
#
2+
# usb.py
3+
#
4+
# Implementation of SSD Utility API for eUSB.
5+
# It reads eUSB health, model, firmware, and serial from /sys/block/*.
6+
#
7+
8+
try:
9+
import os
10+
from .storage_common import StorageCommon
11+
from blkinfo import BlkDiskInfo
12+
except ImportError as e:
13+
raise ImportError(str(e) + "- required module not found")
14+
15+
16+
NOT_AVAILABLE = "N/A"
17+
18+
class UsbUtil(StorageCommon):
19+
20+
model = NOT_AVAILABLE
21+
serial = NOT_AVAILABLE
22+
firmware = NOT_AVAILABLE
23+
temperature = NOT_AVAILABLE
24+
health = NOT_AVAILABLE
25+
vendor = NOT_AVAILABLE
26+
disk_io_reads = NOT_AVAILABLE
27+
disk_io_writes = NOT_AVAILABLE
28+
reserved_blocks = NOT_AVAILABLE
29+
blkd = {}
30+
31+
32+
def __init__(self, diskdev):
33+
34+
self.diskdev = diskdev
35+
self.path = os.path.join('/sys/block', os.path.basename(diskdev))
36+
StorageCommon.__init__(self, diskdev)
37+
38+
self.fetch_parse_info()
39+
40+
def fetch_parse_info(self, diskdev=None):
41+
self.fetch_blkinfo()
42+
self.parse_blkinfo()
43+
44+
def fetch_blkinfo(self):
45+
filters = {}
46+
filters['name'] = '{}'.format(os.path.basename(self.diskdev))
47+
self.blkd = BlkDiskInfo().get_disks(filters)[0]
48+
49+
def parse_blkinfo(self):
50+
if 'dict' in str(type(self.blkd)) and self.blkd:
51+
self.model = self.blkd["model"]
52+
self.serial = self.blkd["serial"]
53+
self.vendor = self.blkd["vendor"]
54+
55+
def get_health(self):
56+
"""
57+
Retrieves current disk health in percentages
58+
59+
Returns:
60+
A float number of current ssd health
61+
e.g. 83.5
62+
"""
63+
return self.health
64+
65+
def get_temperature(self):
66+
"""
67+
Retrieves current disk temperature in Celsius
68+
69+
Returns:
70+
A float number of current temperature in Celsius
71+
e.g. 40.1
72+
"""
73+
return self.temperature
74+
75+
def get_model(self):
76+
"""
77+
Retrieves model for the given disk device
78+
79+
Returns:
80+
A string holding disk model as provided by the manufacturer
81+
"""
82+
return self.model
83+
84+
def get_firmware(self):
85+
"""
86+
Retrieves firmware version for the given disk device
87+
88+
Returns:
89+
A string holding disk firmware version as provided by the manufacturer
90+
"""
91+
return self.firmware
92+
93+
def get_serial(self):
94+
"""
95+
Retrieves serial number for the given disk device
96+
97+
Returns:
98+
A string holding disk serial number as provided by the manufacturer
99+
"""
100+
return self.serial
101+
102+
def get_disk_io_reads(self):
103+
"""
104+
Retrieves the total number of Input/Output (I/O) reads done on an SSD
105+
106+
Returns:
107+
An integer value of the total number of I/O reads
108+
"""
109+
return self.disk_io_reads
110+
111+
def get_disk_io_writes(self):
112+
"""
113+
Retrieves the total number of Input/Output (I/O) writes done on an SSD
114+
115+
Returns:
116+
An integer value of the total number of I/O writes
117+
"""
118+
return self.disk_io_writes
119+
120+
def get_reserved_blocks(self):
121+
"""
122+
Retrieves the total number of reserved blocks in an SSD
123+
124+
Returns:
125+
An integer value of the total number of reserved blocks
126+
"""
127+
return self.reserved_blocks
128+
129+
def get_vendor_output(self):
130+
"""
131+
Retrieves vendor specific data for the given disk device
132+
133+
Returns:
134+
A string holding some vendor specific disk information
135+
"""
136+
return self.vendor

tests/mocked_libs/__init__.py

Whitespace-only changes.

tests/mocked_libs/blkinfo.py

+90
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
mock_json_op = \
2+
[
3+
{
4+
"name": "sdx",
5+
"kname": "sdx",
6+
"fstype": "",
7+
"label": "",
8+
"mountpoint": "",
9+
"size": "3965714432",
10+
"maj:min": "8:0",
11+
"rm": "0",
12+
"model": "SMART EUSB",
13+
"vendor": "SMART EUSB",
14+
"serial": "SPG200807J1",
15+
"hctl": "2:0:0:0",
16+
"tran": "usb",
17+
"rota": "1",
18+
"type": "disk",
19+
"ro": "0",
20+
"owner": "",
21+
"group": "",
22+
"mode": "brw-rw----",
23+
"children": [
24+
{
25+
"name": "sdx1",
26+
"kname": "sdx1",
27+
"fstype": "ext4",
28+
"label": "",
29+
"mountpoint": "/host",
30+
"size": "3964665856",
31+
"maj:min": "8:1",
32+
"rm": "0",
33+
"model": " ",
34+
"vendor": " ",
35+
"serial": "",
36+
"hctl": "",
37+
"tran": "",
38+
"rota": "1",
39+
"type": "part",
40+
"ro": "0",
41+
"owner": "",
42+
"group": "",
43+
"mode": "brw-rw----",
44+
"children": [],
45+
"parents": ["sdx"],
46+
"statistics": {
47+
"major": "8",
48+
"minor": "1",
49+
"kname": "sdx1",
50+
"reads_completed": "22104",
51+
"reads_merged": "5299",
52+
"sectors_read": "1091502",
53+
"time_spent_reading_ms": "51711",
54+
"writes_completed": "11283",
55+
"writes_merged": "13401",
56+
"sectors_written": "443784",
57+
"time_spent_ writing": "133398",
58+
"ios_in_progress": "0",
59+
"time_spent_doing_ios_ms": "112040",
60+
"weighted_time_ios_ms": "112040",
61+
},
62+
}
63+
],
64+
"parents": [],
65+
"statistics": {
66+
"major": "8",
67+
"minor": "0",
68+
"kname": "sdx",
69+
"reads_completed": "22151",
70+
"reads_merged": "5299",
71+
"sectors_read": "1093606",
72+
"time_spent_reading_ms": "52005",
73+
"writes_completed": "11283",
74+
"writes_merged": "13401",
75+
"sectors_written": "443784",
76+
"time_spent_ writing": "133398",
77+
"ios_in_progress": "0",
78+
"time_spent_doing_ios_ms": "112220",
79+
"weighted_time_ios_ms": "112220",
80+
},
81+
}
82+
]
83+
84+
85+
class BlkDiskInfo:
86+
def __init__(self):
87+
return
88+
89+
def get_disks(self, filters={}):
90+
return mock_json_op

tests/mocked_libs/psutil.py

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from collections import namedtuple
2+
3+
4+
def disk_partitions():
5+
sdiskio = namedtuple('sdiskio', ['read_count', 'write_count'])
6+
return sdiskio(read_count=42444, write_count=210141)

tests/test_ssd.py

+3
Original file line numberDiff line numberDiff line change
@@ -1341,6 +1341,9 @@ def test_nvme_ssd(self):
13411341
assert(nvme_ssd.get_firmware() == "COT6OQ")
13421342
assert(nvme_ssd.get_temperature() == 37)
13431343
assert(nvme_ssd.get_serial() == "A0221030722410000027")
1344+
assert(nvme_ssd.get_disk_io_reads() == "1,546,369 [791 GB]")
1345+
assert(nvme_ssd.get_disk_io_writes() == "7,118,163 [3.64 TB]")
1346+
assert(nvme_ssd.get_reserved_blocks() == 100.0)
13441347

13451348
@mock.patch('sonic_platform_base.sonic_storage.ssd.SsdUtil._execute_shell', mock.MagicMock(return_value=output_lack_info_ssd))
13461349
def test_nvme_ssd_with_na_path(self):

0 commit comments

Comments
 (0)