Skip to content

Commit

Permalink
Merge pull request #975 from tbzatek/nvme-base-2
Browse files Browse the repository at this point in the history
nvme: Health Information
  • Loading branch information
tbzatek committed Aug 1, 2022
2 parents 043a614 + a2fe2f4 commit b98210e
Show file tree
Hide file tree
Showing 10 changed files with 1,880 additions and 10 deletions.
47 changes: 47 additions & 0 deletions data/org.freedesktop.UDisks2.policy.in
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,53 @@
</defaults>
</action>

<!-- ###################################################################### -->
<!-- NVMe Controller operations -->

<!-- Update/refresh SMART data -->
<action id="org.freedesktop.udisks2.nvme-smart-update">
<description>Update SMART data</description>
<message>Authentication is required to update SMART data</message>
<defaults>
<allow_any>auth_admin</allow_any>
<allow_inactive>auth_admin</allow_inactive>
<allow_active>yes</allow_active>
</defaults>
</action>

<!-- Start and abort SMART self-tests -->
<action id="org.freedesktop.udisks2.nvme-smart-selftest">
<description>Run device self-test</description>
<message>Authentication is required to run a device self-test</message>
<defaults>
<allow_any>auth_admin</allow_any>
<allow_inactive>auth_admin</allow_inactive>
<allow_active>auth_admin_keep</allow_active>
</defaults>
</action>

<!-- Sanitize operation -->
<action id="org.freedesktop.udisks2.nvme-sanitize">
<description>Start the sanitize operation</description>
<message>Authentication is required to perform a sanitize operation</message>
<defaults>
<allow_any>auth_admin</allow_any>
<allow_inactive>auth_admin</allow_inactive>
<allow_active>auth_admin_keep</allow_active>
</defaults>
</action>

<!-- Format namespace -->
<action id="org.freedesktop.udisks2.nvme-format-namespace">
<description>Format a namespace</description>
<message>Authentication is required to format a namespace</message>
<defaults>
<allow_any>auth_admin</allow_any>
<allow_inactive>auth_admin</allow_inactive>
<allow_active>auth_admin_keep</allow_active>
</defaults>
</action>

<!-- ###################################################################### -->
<!-- Canceling jobs -->

Expand Down
289 changes: 289 additions & 0 deletions data/org.freedesktop.UDisks2.xml

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions doc/udisks2-sections.txt.daemon.sections.in
Original file line number Diff line number Diff line change
Expand Up @@ -597,6 +597,7 @@ udisks_module_get_type
UDisksLinuxNVMeController
udisks_linux_nvme_controller_new
udisks_linux_nvme_controller_update
udisks_linux_nvme_controller_refresh_smart_sync
<SUBSECTION Standard>
UDISKS_LINUX_NVME_CONTROLLER
UDISKS_IS_LINUX_NVME_CONTROLLER
Expand Down
45 changes: 45 additions & 0 deletions doc/udisks2-sections.txt.in.in
Original file line number Diff line number Diff line change
Expand Up @@ -1259,21 +1259,60 @@ UDisksNVMeController
UDisksNVMeControllerIface
udisks_nvme_controller_interface_info
udisks_nvme_controller_override_properties
udisks_nvme_controller_call_smart_get_attributes
udisks_nvme_controller_call_smart_get_attributes_finish
udisks_nvme_controller_call_smart_get_attributes_sync
udisks_nvme_controller_complete_smart_get_attributes
udisks_nvme_controller_call_smart_update
udisks_nvme_controller_call_smart_update_finish
udisks_nvme_controller_call_smart_update_sync
udisks_nvme_controller_complete_smart_update
udisks_nvme_controller_call_smart_selftest_start
udisks_nvme_controller_call_smart_selftest_start_finish
udisks_nvme_controller_call_smart_selftest_start_sync
udisks_nvme_controller_complete_smart_selftest_start
udisks_nvme_controller_call_smart_selftest_abort
udisks_nvme_controller_call_smart_selftest_abort_finish
udisks_nvme_controller_call_smart_selftest_abort_sync
udisks_nvme_controller_complete_smart_selftest_abort
udisks_nvme_controller_call_sanitize_start
udisks_nvme_controller_call_sanitize_start_finish
udisks_nvme_controller_call_sanitize_start_sync
udisks_nvme_controller_complete_sanitize_start
udisks_nvme_controller_get_controller_id
udisks_nvme_controller_get_fguid
udisks_nvme_controller_get_nvme_revision
udisks_nvme_controller_get_sanitize_percent_remaining
udisks_nvme_controller_get_sanitize_status
udisks_nvme_controller_get_smart_critical_warning
udisks_nvme_controller_get_smart_power_on_hours
udisks_nvme_controller_get_smart_selftest_percent_remaining
udisks_nvme_controller_get_smart_selftest_status
udisks_nvme_controller_get_smart_temperature
udisks_nvme_controller_get_smart_updated
udisks_nvme_controller_get_state
udisks_nvme_controller_get_subsystem_nqn
udisks_nvme_controller_get_transport
udisks_nvme_controller_get_unallocated_capacity
udisks_nvme_controller_dup_fguid
udisks_nvme_controller_dup_nvme_revision
udisks_nvme_controller_dup_sanitize_status
udisks_nvme_controller_dup_smart_critical_warning
udisks_nvme_controller_dup_state
udisks_nvme_controller_dup_smart_selftest_status
udisks_nvme_controller_dup_subsystem_nqn
udisks_nvme_controller_dup_transport
udisks_nvme_controller_set_controller_id
udisks_nvme_controller_set_fguid
udisks_nvme_controller_set_nvme_revision
udisks_nvme_controller_set_sanitize_percent_remaining
udisks_nvme_controller_set_sanitize_status
udisks_nvme_controller_set_smart_critical_warning
udisks_nvme_controller_set_smart_power_on_hours
udisks_nvme_controller_set_smart_selftest_percent_remaining
udisks_nvme_controller_set_smart_selftest_status
udisks_nvme_controller_set_smart_temperature
udisks_nvme_controller_set_smart_updated
udisks_nvme_controller_set_state
udisks_nvme_controller_set_subsystem_nqn
udisks_nvme_controller_set_transport
Expand Down Expand Up @@ -1319,7 +1358,12 @@ UDisksNVMeNamespace
UDisksNVMeNamespaceIface
udisks_nvme_namespace_interface_info
udisks_nvme_namespace_override_properties
udisks_nvme_namespace_call_format_namespace
udisks_nvme_namespace_call_format_namespace_finish
udisks_nvme_namespace_call_format_namespace_sync
udisks_nvme_namespace_complete_format_namespace
udisks_nvme_namespace_get_eui64
udisks_nvme_namespace_get_format_percent_remaining
udisks_nvme_namespace_get_formatted_lbasize
udisks_nvme_namespace_get_lbaformats
udisks_nvme_namespace_get_namespace_capacity
Expand All @@ -1336,6 +1380,7 @@ udisks_nvme_namespace_dup_nguid
udisks_nvme_namespace_dup_uuid
udisks_nvme_namespace_dup_wwn
udisks_nvme_namespace_set_eui64
udisks_nvme_namespace_set_format_percent_remaining
udisks_nvme_namespace_set_formatted_lbasize
udisks_nvme_namespace_set_lbaformats
udisks_nvme_namespace_set_namespace_capacity
Expand Down
149 changes: 149 additions & 0 deletions src/tests/dbus-tests/test_nvme.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import os
import stat
import re
import six
import tempfile
import time
import sys
Expand Down Expand Up @@ -342,3 +343,151 @@ def test_namespace_info(self):
self.assertEqual(ncap, self.NS_SIZE / lbaf_curr[0])
nutl = self.get_property_raw(ns, '.NVMe.Namespace', 'NamespaceUtilization')
self.assertEqual(nutl, self.NS_SIZE / lbaf_curr[0])
format_progress = self.get_property_raw(ns, '.NVMe.Namespace', 'FormatPercentRemaining')
self.assertEqual(format_progress, -1)


def test_health_info(self):
self._nvme_connect()
self.addCleanup(self._nvme_disconnect, self.SUBNQN, ignore_errors=True)
time.sleep(1)

ctrl_devs = find_nvme_ctrl_devs_for_subnqn(self.SUBNQN)
self.assertEqual(len(ctrl_devs), 1)
ns_devs = find_nvme_ns_devs_for_subnqn(self.SUBNQN)
self.assertEqual(len(ns_devs), self.NUM_NS)

# find drive object through one of the namespace block objects
ns = self.get_object('/block_devices/' + os.path.basename(ns_devs[0]))
self.assertHasIface(ns, 'org.freedesktop.UDisks2.NVMe.Namespace')
drive_obj_path = self.get_property_raw(ns, '.Block', 'Drive')
drive_obj = self.get_object(drive_obj_path)
self.assertHasIface(drive_obj, 'org.freedesktop.UDisks2.NVMe.Controller')
state = self.get_property(drive_obj, '.NVMe.Controller', 'State')
state.assertEqual('live', timeout=10)

smart_updated = self.get_property_raw(drive_obj, '.NVMe.Controller', 'SmartUpdated')
self.assertGreater(smart_updated, 0)
smart_warnings = self.get_property_raw(drive_obj, '.NVMe.Controller', 'SmartCriticalWarning')
self.assertEqual(len(smart_warnings), 0)
smart_poh = self.get_property_raw(drive_obj, '.NVMe.Controller', 'SmartPowerOnHours')
self.assertEqual(smart_poh, 0)
smart_temp = self.get_property_raw(drive_obj, '.NVMe.Controller', 'SmartTemperature')
self.assertEqual(smart_temp, 0)
smart_selftest_status = self.get_property_raw(drive_obj, '.NVMe.Controller', 'SmartSelftestStatus')
self.assertEqual(smart_selftest_status, '')
smart_selftest_remaining = self.get_property_raw(drive_obj, '.NVMe.Controller', 'SmartSelftestPercentRemaining')
self.assertEqual(smart_selftest_remaining, -1)

drive_obj.SmartUpdate(self.no_options, dbus_interface=self.iface_prefix + '.NVMe.Controller')
smart_updated2 = self.get_property_raw(drive_obj, '.NVMe.Controller', 'SmartUpdated')
self.assertGreaterEqual(smart_updated2, smart_updated)

attrs = drive_obj.SmartGetAttributes(self.no_options, dbus_interface=self.iface_prefix + '.NVMe.Controller')
self.assertGreater(len(attrs), 10)
self.assertEqual(attrs['avail_spare'], 0);
self.assertEqual(attrs['spare_thresh'], 0);
self.assertEqual(attrs['percent_used'], 0);
self.assertEqual(attrs['ctrl_busy_time'], 0);
self.assertEqual(attrs['power_cycles'], 0);
self.assertEqual(attrs['unsafe_shutdowns'], 0);
self.assertEqual(attrs['media_errors'], 0);
self.assertIn('num_err_log_entries', attrs);
self.assertEqual(attrs['temp_sensors'], [0, 0, 0, 0, 0, 0, 0, 0]);
self.assertEqual(attrs['warning_temp_time'], 0);
self.assertEqual(attrs['critical_temp_time'], 0);

# Try trigerring a self-test operation
msg = 'Unknown self-test type xxx'
with six.assertRaisesRegex(self, dbus.exceptions.DBusException, msg):
drive_obj.SmartSelftestStart('xxx', self.no_options, dbus_interface=self.iface_prefix + '.NVMe.Controller')
msg = 'NVMe Get Log Page - Device Self-test Log command error: Invalid Field in Command'
with six.assertRaisesRegex(self, dbus.exceptions.DBusException, msg):
drive_obj.SmartSelftestStart('short', self.no_options, dbus_interface=self.iface_prefix + '.NVMe.Controller')
msg = 'NVMe Device Self-test command error: Invalid Command Opcode'
with six.assertRaisesRegex(self, dbus.exceptions.DBusException, msg):
drive_obj.SmartSelftestAbort(self.no_options, dbus_interface=self.iface_prefix + '.NVMe.Controller')


def test_sanitize(self):
self._nvme_connect()
self.addCleanup(self._nvme_disconnect, self.SUBNQN, ignore_errors=True)
time.sleep(1)

ctrl_devs = find_nvme_ctrl_devs_for_subnqn(self.SUBNQN)
self.assertEqual(len(ctrl_devs), 1)
ns_devs = find_nvme_ns_devs_for_subnqn(self.SUBNQN)
self.assertEqual(len(ns_devs), self.NUM_NS)

# find drive object through one of the namespace block objects
ns = self.get_object('/block_devices/' + os.path.basename(ns_devs[0]))
self.assertHasIface(ns, 'org.freedesktop.UDisks2.NVMe.Namespace')
drive_obj_path = self.get_property_raw(ns, '.Block', 'Drive')
drive_obj = self.get_object(drive_obj_path)
self.assertHasIface(drive_obj, 'org.freedesktop.UDisks2.NVMe.Controller')
state = self.get_property(drive_obj, '.NVMe.Controller', 'State')
state.assertEqual('live', timeout=10)

sanitize_status = self.get_property_raw(drive_obj, '.NVMe.Controller', 'SanitizeStatus')
self.assertEqual(sanitize_status, '')
sanitize_percent_remaining = self.get_property_raw(drive_obj, '.NVMe.Controller', 'SanitizePercentRemaining')
self.assertEqual(sanitize_percent_remaining, -1)

# Try trigerring a sanitize operation
msg = 'Unknown sanitize action xxx'
with six.assertRaisesRegex(self, dbus.exceptions.DBusException, msg):
drive_obj.SanitizeStart('xxx', self.no_options, dbus_interface=self.iface_prefix + '.NVMe.Controller')
msg = 'NVMe Get Log Page - Sanitize Status Log command error: Invalid Field in Command'
with six.assertRaisesRegex(self, dbus.exceptions.DBusException, msg):
drive_obj.SanitizeStart('block-erase', self.no_options, dbus_interface=self.iface_prefix + '.NVMe.Controller')
with six.assertRaisesRegex(self, dbus.exceptions.DBusException, msg):
drive_obj.SanitizeStart('crypto-erase', self.no_options, dbus_interface=self.iface_prefix + '.NVMe.Controller')
with six.assertRaisesRegex(self, dbus.exceptions.DBusException, msg):
drive_obj.SanitizeStart('overwrite', self.no_options, dbus_interface=self.iface_prefix + '.NVMe.Controller')


def test_format_ns(self):
self._nvme_connect()
self.addCleanup(self._nvme_disconnect, self.SUBNQN, ignore_errors=True)
time.sleep(1)

ns_devs = find_nvme_ns_devs_for_subnqn(self.SUBNQN)
self.assertEqual(len(ns_devs), self.NUM_NS)

for d in ns_devs:
ns = self.get_object('/block_devices/' + os.path.basename(d))
self.assertHasIface(ns, 'org.freedesktop.UDisks2.NVMe.Namespace')

drive_obj_path = self.get_property_raw(ns, '.Block', 'Drive')
drive_obj = self.get_object(drive_obj_path)
self.assertHasIface(drive_obj, 'org.freedesktop.UDisks2.NVMe.Controller')
state = self.get_property(drive_obj, '.NVMe.Controller', 'State')
state.assertEqual('live', timeout=10)

msg = 'Format NVM command error: Invalid Command Opcode: A reserved coded value or an unsupported value in the command opcode field'
with six.assertRaisesRegex(self, dbus.exceptions.DBusException, msg):
ns.FormatNamespace(self.no_options, dbus_interface=self.iface_prefix + '.NVMe.Namespace')

d = dbus.Dictionary(signature='sv')
with six.assertRaisesRegex(self, dbus.exceptions.DBusException, 'Unknown secure erase type xxx'):
d['secure_erase'] = 'xxx'
ns.FormatNamespace(d, dbus_interface=self.iface_prefix + '.NVMe.Namespace')
with six.assertRaisesRegex(self, dbus.exceptions.DBusException, msg):
d['secure_erase'] = 'user_data'
ns.FormatNamespace(d, dbus_interface=self.iface_prefix + '.NVMe.Namespace')
with six.assertRaisesRegex(self, dbus.exceptions.DBusException, msg):
d['secure_erase'] = 'crypto_erase'
ns.FormatNamespace(d, dbus_interface=self.iface_prefix + '.NVMe.Namespace')

d = dbus.Dictionary(signature='sv')
with six.assertRaisesRegex(self, dbus.exceptions.DBusException, msg):
d['lba_data_size'] = 0
ns.FormatNamespace(d, dbus_interface=self.iface_prefix + '.NVMe.Namespace')
lbaf_curr = self.get_property_raw(ns, '.NVMe.Namespace', 'FormattedLBASize')
with six.assertRaisesRegex(self, dbus.exceptions.DBusException, msg):
d['lba_data_size'] = lbaf_curr[0]
ns.FormatNamespace(d, dbus_interface=self.iface_prefix + '.NVMe.Namespace')
msg = "Couldn't match desired LBA data block size in a device supported LBA format data sizes"
with six.assertRaisesRegex(self, dbus.exceptions.DBusException, msg):
d['lba_data_size'] = dbus.UInt16(666)
ns.FormatNamespace(d, dbus_interface=self.iface_prefix + '.NVMe.Namespace')
18 changes: 14 additions & 4 deletions src/udiskslinuxdriveata.c
Original file line number Diff line number Diff line change
Expand Up @@ -516,13 +516,18 @@ udisks_linux_drive_ata_refresh_smart_sync (UDisksLinuxDriveAta *drive,

if (drive->secure_erase_in_progress)
{
g_set_error (error, UDISKS_ERROR, UDISKS_ERROR_DEVICE_BUSY,
"Secure erase in progress");
g_set_error_literal (error, UDISKS_ERROR, UDISKS_ERROR_DEVICE_BUSY,
"Secure erase in progress");
goto out;
}

device = udisks_linux_drive_object_get_device (object, TRUE /* get_hw */);
g_assert (device != NULL);
if (device == NULL)
{
g_set_error_literal (error, UDISKS_ERROR, UDISKS_ERROR_FAILED,
"No udev device");
goto out;
}

/* TODO: use cancellable */

Expand Down Expand Up @@ -696,7 +701,12 @@ udisks_linux_drive_ata_smart_selftest_sync (UDisksLinuxDriveAta *drive,
goto out;

device = udisks_linux_drive_object_get_device (object, TRUE /* get_hw */);
g_assert (device != NULL);
if (device == NULL)
{
g_set_error_literal (error, UDISKS_ERROR, UDISKS_ERROR_FAILED,
"No udev device");
goto out;
}

if (g_strcmp0 (type, "short") == 0)
test = SK_SMART_SELF_TEST_SHORT;
Expand Down
20 changes: 18 additions & 2 deletions src/udiskslinuxdriveobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -1046,12 +1046,12 @@ udisks_linux_drive_object_should_include_device (GUdevClient *client,
/**
* udisks_linux_drive_object_housekeeping:
* @object: A #UDisksLinuxDriveObject.
* @secs_since_last: Number of seconds sincex the last housekeeping or 0 if the first housekeeping ever.
* @secs_since_last: Number of seconds since the last housekeeping or 0 if the first housekeeping ever.
* @cancellable: A %GCancellable or %NULL.
* @error: Return location for error or %NULL.
*
* Called periodically (every ten minutes or so) to perform
* housekeeping tasks such as refreshing ATA SMART data.
* housekeeping tasks such as refreshing ATA/NVMe SMART data.
*
* The function runs in a dedicated thread and is allowed to perform
* blocking I/O.
Expand All @@ -1071,6 +1071,7 @@ udisks_linux_drive_object_housekeeping (UDisksLinuxDriveObject *object,

ret = FALSE;

/* ATA */
if (object->iface_drive_ata != NULL &&
udisks_drive_ata_get_smart_supported (object->iface_drive_ata) &&
udisks_drive_ata_get_smart_enabled (object->iface_drive_ata))
Expand Down Expand Up @@ -1116,6 +1117,21 @@ udisks_linux_drive_object_housekeeping (UDisksLinuxDriveObject *object,
}
}
}
/* NVMe */
if (object->iface_nvme_ctrl != NULL)
{
GError *local_error = NULL;

udisks_info ("Refreshing Health Information on %s",
g_dbus_object_get_object_path (G_DBUS_OBJECT (object)));

if (!udisks_linux_nvme_controller_refresh_smart_sync (UDISKS_LINUX_NVME_CONTROLLER (object->iface_nvme_ctrl),
cancellable, &local_error))
{
g_propagate_prefixed_error (error, local_error, "Error updating Health Information: ");
goto out;
}
}

ret = TRUE;

Expand Down

0 comments on commit b98210e

Please sign in to comment.