Skip to content

Commit

Permalink
NAS-130745 / 25.04 / Update smartctl usage to use JSON (#14479)
Browse files Browse the repository at this point in the history
  • Loading branch information
aiden3c committed Sep 18, 2024
1 parent 85ddd27 commit cc1be39
Show file tree
Hide file tree
Showing 11 changed files with 347 additions and 317 deletions.
1 change: 1 addition & 0 deletions src/middlewared/middlewared/api/v25_04_0/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@
from .privilege import * # noqa
from .user import * # noqa
from .vendor import * # noqa
from .smartctl import * # noqa
38 changes: 38 additions & 0 deletions src/middlewared/middlewared/api/v25_04_0/smartctl.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from typing import Any

from middlewared.api.base import BaseModel

__all__ = ["AtaSelfTest", "NvmeSelfTest", "ScsiSelfTest"]


class AtaSelfTest(BaseModel):
num: int
description: str
status: str
status_verbose: str
remaining: float
lifetime: int
lba_of_first_error: int | None = None


class NvmeSelfTest(BaseModel):
num: int
description: str
status: str
status_verbose: str
power_on_hours: int
failing_lba: int | None = None
nsid: int | None = None
seg: int | None = None
sct: int | None = 0x0
code: int | None = 0x0


class ScsiSelfTest(BaseModel):
num: int
description: str
status: str
status_verbose: str
segment_number: int | None = None
lifetime: int | None = None
lba_of_first_error: int | None = None
10 changes: 6 additions & 4 deletions src/middlewared/middlewared/etc_files/smartd.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import re
import shlex
import subprocess
import json

from middlewared.common.smart.smartctl import get_smartctl_args, smartctl, SMARTCTX
from middlewared.plugins.smart_.schedule import SMARTD_SCHEDULE_PIECES, smartd_schedule_piece
Expand All @@ -23,15 +24,16 @@ async def ensure_smart_enabled(args):
if any(arg.startswith("/dev/nvme") for arg in args):
return True

p = await smartctl(args + ["-i"], stderr=subprocess.STDOUT, check=False, encoding="utf8", errors="ignore")
if not re.search("SMART.*abled", p.stdout):
p = await smartctl(args + ["-i", "--json=c"], check=False, stderr=subprocess.STDOUT, encoding="utf8", errors="ignore")
pjson = json.loads(p.stdout)
if not pjson["smart_support"]["available"]:
logger.debug("SMART is not supported on %r", args)
return False

if re.search("SMART.*Enabled", p.stdout):
if pjson["smart_support"]["enabled"]:
return True

p = await smartctl(args + ["-s", "on"], stderr=subprocess.STDOUT, check=False)
p = await smartctl(args + ["-s", "on"], check=False, stderr=subprocess.STDOUT)
if p.returncode == 0:
return True
else:
Expand Down
8 changes: 3 additions & 5 deletions src/middlewared/middlewared/plugins/disk.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@

RE_SED_RDLOCK_EN = re.compile(r'(RLKEna = Y|ReadLockEnabled:\s*1)', re.M)
RE_SED_WRLOCK_EN = re.compile(r'(WLKEna = Y|WriteLockEnabled:\s*1)', re.M)
RE_SMART_AVAILABLE = re.compile(r'SMART support is:\s+Available')


class DiskModel(sa.Model):
Expand Down Expand Up @@ -140,12 +139,11 @@ async def disk_extend(self, disk, context):

disk['supports_smart'] = None
if context['supports_smart']:
if await self.middleware.call('truenas.is_ix_hardware'):
if await self.middleware.call('truenas.is_ix_hardware') or disk['name'].startswith('nvme'):
disk['supports_smart'] = True
else:
disk['supports_smart'] = disk['name'].startswith('nvme') or bool(RE_SMART_AVAILABLE.search(
await self.middleware.call('disk.smartctl', disk['name'], ['-a'], {'silent': True}) or ''
))
disk_query = await self.middleware.call('disk.smartctl', disk['name'], ['-a', '--json=c'], {'silent': True})
disk['supports_smart'] = disk_query.get('smart_support', {}).get('available', False)

if disk['name'] in context['boot_pool_disks']:
disk['pool'] = context['boot_pool_name']
Expand Down
8 changes: 6 additions & 2 deletions src/middlewared/middlewared/plugins/disk_/smart_attributes.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,16 @@ async def smart_attributes(self, name):
"""
Returns S.M.A.R.T. attributes values for specified disk name.
"""
output = json.loads(await self.middleware.call('disk.smartctl', name, ['-A', '-j']))
output = json.loads(await self.middleware.call('disk.smartctl', name, ['-a', '--json=c']))

if 'ata_smart_attributes' in output:
return output['ata_smart_attributes']['table']
if 'nvme_smart_health_information_log' in output:
return output['nvme_smart_health_information_log']
if 'scsi_error_counter_log' in output and 'scsi_grown_defect_list' in output:
return {'scsi_error_counter_log': output['scsi_error_counter_log'], 'scsi_grown_defect_list': output['scsi_grown_defect_list']}

raise CallError('Only ATA device support S.M.A.R.T. attributes')
raise CallError('Only ATA/SCSI/NVMe devices support S.M.A.R.T. attributes')

@private
async def sata_dom_lifetime_left(self, name):
Expand Down
6 changes: 3 additions & 3 deletions src/middlewared/middlewared/plugins/disk_/temperature.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import asyncio
import datetime
import time
import json

import async_timeout

Expand Down Expand Up @@ -61,9 +62,8 @@ async def temperature(self, name, options):

@private
async def temperature_uncached(self, name, powermode):
output = await self.middleware.call('disk.smartctl', name, ['-a', '-n', powermode.lower()], {'silent': True})
if output is not None:
return parse_smartctl_for_temperature_output(output)
if output := await self.middleware.call('disk.smartctl', name, ['-a', '-n', powermode.lower(), '--json=c'], {'silent': True}):
return parse_smartctl_for_temperature_output(json.loads(output))

@private
async def reset_temperature_cache(self):
Expand Down
Loading

0 comments on commit cc1be39

Please sign in to comment.