[pve-devel] [PATCH storage 2/2] smartctl: use json parsing

Oguz Bektas o.bektas at proxmox.com
Thu Apr 1 15:40:55 CEST 2021


adapt the smartctl endpoint to run smartctl with the --json or -j flag
to parse it more reasonably.

also add the $format parameter to assist in switching to the new json
parsed output for nvme devices in PVE 7.0 (until 7.0 removing the 'text'
field completely would be a breaking change, so we still default to the
text field but with the parsed "key : value" pairs instead of raw
smartctl output)

for the unit tests from now we need to collect the smartctl outputs with
the json flag. the current tests cover ssd_smart and nvme_smart cases.

Signed-off-by: Oguz Bektas <o.bektas at proxmox.com>
----

RFC -> PATCH:
* split patch for removing old tests
* incorporate stefan's and thomas' recommendations & nits (thanks!!)
* fix issue with array fields not being parsed
* add array element in nvme unit test (temperature_sensor)


 PVE/API2/Disks.pm                             |   9 +-
 PVE/Diskmanage.pm                             | 120 +++---
 test/disk_tests/nvme_smart/disklist           |   1 +
 .../nvme_smart/disklist_expected.json         |  16 +
 test/disk_tests/nvme_smart/nvme0/model        |   1 +
 test/disk_tests/nvme_smart/nvme0_smart        |  65 ++++
 test/disk_tests/nvme_smart/nvme0n1/device     |   1 +
 .../nvme_smart/nvme0n1/queue/rotational       |   1 +
 test/disk_tests/nvme_smart/nvme0n1/size       |   1 +
 .../nvme_smart/nvme0n1_smart_expected.json    |   6 +
 test/disk_tests/nvme_smart/nvme0n1_udevadm    |  18 +
 test/disk_tests/ssd_smart/disklist            |   1 +
 .../ssd_smart/disklist_expected.json          |  16 +
 test/disk_tests/ssd_smart/sda/device/vendor   |   1 +
 .../disk_tests/ssd_smart/sda/queue/rotational |   1 +
 test/disk_tests/ssd_smart/sda/size            |   1 +
 test/disk_tests/ssd_smart/sda_smart           | 352 ++++++++++++++++++
 .../ssd_smart/sda_smart_expected.json         | 146 ++++++++
 test/disk_tests/ssd_smart/sda_udevadm         |  11 +
 test/disklist_test.pm                         |   4 +-
 20 files changed, 714 insertions(+), 58 deletions(-)
 create mode 100644 test/disk_tests/nvme_smart/disklist
 create mode 100644 test/disk_tests/nvme_smart/disklist_expected.json
 create mode 100644 test/disk_tests/nvme_smart/nvme0/model
 create mode 100644 test/disk_tests/nvme_smart/nvme0_smart
 create mode 120000 test/disk_tests/nvme_smart/nvme0n1/device
 create mode 100644 test/disk_tests/nvme_smart/nvme0n1/queue/rotational
 create mode 100644 test/disk_tests/nvme_smart/nvme0n1/size
 create mode 100644 test/disk_tests/nvme_smart/nvme0n1_smart_expected.json
 create mode 100644 test/disk_tests/nvme_smart/nvme0n1_udevadm
 create mode 100644 test/disk_tests/ssd_smart/disklist
 create mode 100644 test/disk_tests/ssd_smart/disklist_expected.json
 create mode 100644 test/disk_tests/ssd_smart/sda/device/vendor
 create mode 100644 test/disk_tests/ssd_smart/sda/queue/rotational
 create mode 100644 test/disk_tests/ssd_smart/sda/size
 create mode 100644 test/disk_tests/ssd_smart/sda_smart
 create mode 100644 test/disk_tests/ssd_smart/sda_smart_expected.json
 create mode 100644 test/disk_tests/ssd_smart/sda_udevadm

diff --git a/PVE/API2/Disks.pm b/PVE/API2/Disks.pm
index 33bca76..a453efc 100644
--- a/PVE/API2/Disks.pm
+++ b/PVE/API2/Disks.pm
@@ -195,6 +195,12 @@ __PACKAGE__->register_method ({
 		description => "If true returns only the health status",
 		optional => 1,
 	    },
+	    format => {
+		description => "Return json or text",
+		type => 'string',
+		enum => ['text', 'json'],
+		optional => 1,
+	    },
 	},
     },
     returns => {
@@ -204,6 +210,7 @@ __PACKAGE__->register_method ({
 	    type => { type => 'string', optional => 1 },
 	    attributes => { type => 'array', optional => 1},
 	    text => { type => 'string', optional => 1 },
+	    json => { type => 'string', optional => 1 },
 	},
     },
     code => sub {
@@ -211,7 +218,7 @@ __PACKAGE__->register_method ({
 
 	my $disk = PVE::Diskmanage::verify_blockdev_path($param->{disk});
 
-	my $result = PVE::Diskmanage::get_smart_data($disk, $param->{healthonly});
+	my $result = PVE::Diskmanage::get_smart_data($disk, $param->{healthonly}, $param->{format});
 
 	$result->{health} = 'UNKNOWN' if !defined $result->{health};
 	$result = { health => $result->{health} } if $param->{healthonly};
diff --git a/PVE/Diskmanage.pm b/PVE/Diskmanage.pm
index 64bb813..3f433fb 100644
--- a/PVE/Diskmanage.pm
+++ b/PVE/Diskmanage.pm
@@ -81,11 +81,12 @@ sub disk_is_used {
 }
 
 sub get_smart_data {
-    my ($disk, $healthonly) = @_;
+    my ($disk, $healthonly, $format) = @_;
 
     assert_blockdev($disk);
     my $smartdata = {};
     my $type;
+    $format //= 'text';
 
     my $returncode = 0;
 
@@ -95,70 +96,79 @@ sub get_smart_data {
 	    or die "failed to get nvme controller device for $disk\n");
     }
 
-    my $cmd = [$SMARTCTL, '-H'];
-    push @$cmd, '-A', '-f', 'brief' if !$healthonly;
+    my $cmd = [$SMARTCTL, '-j', '-H'];
+    push @$cmd, '-Afbrief' if !$healthonly;
     push @$cmd, $disk;
 
+    my $smart_result = '';
     eval {
-	$returncode = run_command($cmd, noerr => 1, outfunc => sub{
-	    my ($line) = @_;
+	$returncode = run_command($cmd, noerr => 1, outfunc => sub { $smart_result .= shift });
+    };
+    my $err = $@;
 
-# ATA SMART attributes, e.g.:
-# ID# ATTRIBUTE_NAME          FLAGS    VALUE WORST THRESH FAIL RAW_VALUE
-#   1 Raw_Read_Error_Rate     POSR-K   100   100   000    -    0
-#
-# SAS and NVME disks, e.g.:
-# Data Units Written:                 5,584,952 [2.85 TB]
-# Accumulated start-stop cycles:  34
-
-	    if (defined($type) && $type eq 'ata' && $line =~ m/^([ \d]{2}\d)\s+(\S+)\s+(\S{6})\s+(\d+)\s+(\d+)\s+(\S+)\s+(\S+)\s+(.*)$/) {
-		my $entry = {};
-
-		$entry->{name} = $2 if defined $2;
-		$entry->{flags} = $3 if defined $3;
-		# the +0 makes a number out of the strings
-		$entry->{value} = $4+0 if defined $4;
-		$entry->{worst} = $5+0 if defined $5;
-		# some disks report the default threshold as --- instead of 000
-		if (defined($6) && $6 eq '---') {
-		    $entry->{threshold} = 0;
-		} else {
-		    $entry->{threshold} = $6+0 if defined $6;
-		}
-		$entry->{fail} = $7 if defined $7;
-		$entry->{raw} = $8 if defined $8;
-		$entry->{id} = $1 if defined $1;
-		push @{$smartdata->{attributes}}, $entry;
-	    } elsif ($line =~ m/(?:Health Status|self\-assessment test result): (.*)$/ ) {
-		$smartdata->{health} = $1;
-	    } elsif ($line =~ m/Vendor Specific SMART Attributes with Thresholds:/) {
-		$type = 'ata';
-		delete $smartdata->{text};
-	    } elsif ($line =~ m/=== START OF (READ )?SMART DATA SECTION ===/) {
-		$type = 'text';
-	    } elsif (defined($type) && $type eq 'text') {
-		$smartdata->{text} = '' if !defined $smartdata->{text};
-		$smartdata->{text} .= "$line\n";
-		# extract wearout from nvme/sas text, allow for decimal values
-		if ($line =~ m/Percentage Used(?: endurance indicator)?:\s*(\d+(?:\.\d+)?)\%/i) {
-		    $smartdata->{wearout} = 100 - $1;
+    my $json_result = decode_json($smart_result);
+
+    my $smart_health = $json_result->{smart_status}->{passed};
+    if (JSON::is_bool($smart_health)) {
+	$smart_health = $smart_health ? "PASSED" : "FAILED";
+    } else {
+	$smart_health = 'UNKNOWN';
+    }
+
+    $smartdata->{health} = $smart_health;
+
+    $type = $json_result->{device}->{type};
+    if ($type eq 'nvme') {
+	$smartdata->{type} = $format; # text or json but FIXME: remove in PVE 7.0 and use json
+
+	my $nvme_info = $json_result->{nvme_smart_health_information_log};
+
+	if (!$healthonly) {
+	    $smartdata->{wearout} = 100.0 - $nvme_info->{percentage_used};
+	}
+
+	my $add_key = sub {
+	    my ($key, $value) = @_;
+	    if ($format eq 'text') {
+		$smartdata->{text} .= "$key : $value\n";
+	    }
+	};
+
+	foreach my $key (sort keys %{$nvme_info}) {
+	    my $value = $nvme_info->{$key};
+	    # some fields can also be arrays
+	    # e.g. temperature_sensor
+	    if (ref($value) eq 'ARRAY') {
+		while (my $index = each(@$value)) {
+		    $add_key->("${key}_${index}", $value->[$index]);
 		}
-	    } elsif ($line =~ m/SMART Disabled/) {
-		$smartdata->{health} = "SMART Disabled";
+	    } else {
+		$add_key->($key, $value);
 	    }
-	});
-    };
-    my $err = $@;
+	}
+	delete $smartdata->{attributes}; # this is for ata disks only
+    } else {
+	$smartdata->{type} = 'ata';
+	foreach my $attribute (@{$json_result->{ata_smart_attributes}->{table}}) {
+	    # we need to override or delete some fields to fit expected json schema of unit tests
+	    my $flags_string = $attribute->{flags}->{string};
+	    $flags_string =~ s/\s+$//;
+	    $attribute->{flags} = $flags_string;
+	    $attribute->{raw} = $attribute->{raw}->{string};
+	    $attribute->{threshold} = delete $attribute->{thresh};
+	    $attribute->{fail} = ${attribute}->{when_failed} eq "" ? "-" : ${attribute}->{when_failed};
+	    delete ${attribute}->{when_failed};
+
+	    push @{$smartdata->{attributes}}, $attribute;
+	}
+	my @sorted_attributes = sort { $a->{id} <=> $b->{id} } @{$smartdata->{attributes}};
+	@{$smartdata->{attributes}} = @sorted_attributes;
+    }
 
-    # bit 0 and 1 mark an severe smartctl error
-    # all others are for disk status, so ignore them
-    # see smartctl(8)
-    if ((defined($returncode) && ($returncode & 0b00000011)) || $err) {
+    if ($returncode & 0b00000011 || $err) {
 	die "Error getting S.M.A.R.T. data: Exit code: $returncode\n";
     }
 
-    $smartdata->{type} = $type;
-
     return $smartdata;
 }
 
diff --git a/test/disk_tests/nvme_smart/disklist b/test/disk_tests/nvme_smart/disklist
new file mode 100644
index 0000000..d00b90e
--- /dev/null
+++ b/test/disk_tests/nvme_smart/disklist
@@ -0,0 +1 @@
+nvme0n1
diff --git a/test/disk_tests/nvme_smart/disklist_expected.json b/test/disk_tests/nvme_smart/disklist_expected.json
new file mode 100644
index 0000000..4d1c92f
--- /dev/null
+++ b/test/disk_tests/nvme_smart/disklist_expected.json
@@ -0,0 +1,16 @@
+{
+    "nvme0n1" : {
+	"wearout" : 69,
+	"vendor" : "unknown",
+	"size" : 512000,
+	"health" : "PASSED",
+	"serial" : "unknown",
+	"model" : "NVME MODEL 1",
+	"rpm" : 0,
+	"osdid" : -1,
+	"devpath" : "/dev/nvme0n1",
+	"gpt" : 0,
+	"wwn" : "unknown",
+	"type" : "nvme"
+    }
+}
diff --git a/test/disk_tests/nvme_smart/nvme0/model b/test/disk_tests/nvme_smart/nvme0/model
new file mode 100644
index 0000000..9bd6eba
--- /dev/null
+++ b/test/disk_tests/nvme_smart/nvme0/model
@@ -0,0 +1 @@
+NVME MODEL 1
diff --git a/test/disk_tests/nvme_smart/nvme0_smart b/test/disk_tests/nvme_smart/nvme0_smart
new file mode 100644
index 0000000..3f3c799
--- /dev/null
+++ b/test/disk_tests/nvme_smart/nvme0_smart
@@ -0,0 +1,65 @@
+{
+  "json_format_version": [
+    1,
+    0
+  ],
+  "smartctl": {
+    "version": [
+      7,
+      2
+    ],
+    "svn_revision": "5155",
+    "platform_info": "x86_64-linux-5.11.7-1-pve",
+    "build_info": "(local build)",
+    "argv": [
+      "smartctl",
+      "-j",
+      "-H",
+      "-Afbrief",
+      "/dev/nvme0"
+    ],
+    "exit_status": 0
+  },
+  "device": {
+    "name": "/dev/nvme0",
+    "info_name": "/dev/nvme0",
+    "type": "nvme",
+    "protocol": "NVMe"
+  },
+  "smart_status": {
+    "passed": true,
+    "nvme": {
+      "value": 0
+    }
+  },
+  "nvme_smart_health_information_log": {
+    "critical_warning": 0,
+    "temperature": 35,
+    "available_spare": 100,
+    "available_spare_threshold": 10,
+    "percentage_used": 31,
+    "data_units_read": 4546105,
+    "data_units_written": 16623231,
+    "host_reads": 154011524,
+    "host_writes": 282144186,
+    "controller_busy_time": 355,
+    "power_cycles": 514,
+    "power_on_hours": 2491,
+    "unsafe_shutdowns": 38,
+    "media_errors": 0,
+    "num_err_log_entries": 0,
+    "warning_temp_time": 0,
+    "critical_comp_time": 0,
+    "temperature_sensors": [
+	36,
+	34
+    ]
+  },
+  "temperature": {
+    "current": 35
+  },
+  "power_cycle_count": 514,
+  "power_on_time": {
+    "hours": 2491
+  }
+}
diff --git a/test/disk_tests/nvme_smart/nvme0n1/device b/test/disk_tests/nvme_smart/nvme0n1/device
new file mode 120000
index 0000000..e890f3e
--- /dev/null
+++ b/test/disk_tests/nvme_smart/nvme0n1/device
@@ -0,0 +1 @@
+../nvme0
\ No newline at end of file
diff --git a/test/disk_tests/nvme_smart/nvme0n1/queue/rotational b/test/disk_tests/nvme_smart/nvme0n1/queue/rotational
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/test/disk_tests/nvme_smart/nvme0n1/queue/rotational
@@ -0,0 +1 @@
+0
diff --git a/test/disk_tests/nvme_smart/nvme0n1/size b/test/disk_tests/nvme_smart/nvme0n1/size
new file mode 100644
index 0000000..83b33d2
--- /dev/null
+++ b/test/disk_tests/nvme_smart/nvme0n1/size
@@ -0,0 +1 @@
+1000
diff --git a/test/disk_tests/nvme_smart/nvme0n1_smart_expected.json b/test/disk_tests/nvme_smart/nvme0n1_smart_expected.json
new file mode 100644
index 0000000..e3f0ce0
--- /dev/null
+++ b/test/disk_tests/nvme_smart/nvme0n1_smart_expected.json
@@ -0,0 +1,6 @@
+{
+    "text" : "available_spare : 100\navailable_spare_threshold : 10\ncontroller_busy_time : 355\ncritical_comp_time : 0\ncritical_warning : 0\ndata_units_read : 4546105\ndata_units_written : 16623231\nhost_reads : 154011524\nhost_writes : 282144186\nmedia_errors : 0\nnum_err_log_entries : 0\npercentage_used : 31\npower_cycles : 514\npower_on_hours : 2491\ntemperature : 35\ntemperature_sensors_0 : 36\ntemperature_sensors_1 : 34\nunsafe_shutdowns : 38\nwarning_temp_time : 0\n",
+    "health" : "PASSED",
+    "type" : "text",
+    "wearout": 69
+}
diff --git a/test/disk_tests/nvme_smart/nvme0n1_udevadm b/test/disk_tests/nvme_smart/nvme0n1_udevadm
new file mode 100644
index 0000000..36c78ce
--- /dev/null
+++ b/test/disk_tests/nvme_smart/nvme0n1_udevadm
@@ -0,0 +1,18 @@
+
+P: /devices/pci0000:00/0000:00:01.1/0000:02:00.0/nvme/nvme0/nvme0n1
+N: nvme0n1
+S: disk/by-id/lvm-pv-uuid-Py4eod-qfzj-i8Q3-Dxu6-xf0Q-H3Wr-w5Fo8V
+E: DEVLINKS=/dev/disk/by-id/lvm-pv-uuid-Py4eod-qfzj-i8Q3-Dxu6-xf0Q-H3Wr-w5Fo8V
+E: DEVNAME=/dev/nvme0n1
+E: DEVPATH=/devices/pci0000:00/0000:00:01.1/0000:02:00.0/nvme/nvme0/nvme0n1
+E: DEVTYPE=disk
+E: ID_FS_TYPE=LVM2_member
+E: ID_FS_USAGE=raid
+E: ID_FS_UUID=Py4eod-qfzj-i8Q3-Dxu6-xf0Q-H3Wr-w5Fo8V
+E: ID_FS_UUID_ENC=Py4eod-qfzj-i8Q3-Dxu6-xf0Q-H3Wr-w5Fo8V
+E: ID_FS_VERSION=LVM2 001
+E: MAJOR=259
+E: MINOR=0
+E: SUBSYSTEM=block
+E: TAGS=:systemd:
+E: USEC_INITIALIZED=3842
diff --git a/test/disk_tests/ssd_smart/disklist b/test/disk_tests/ssd_smart/disklist
new file mode 100644
index 0000000..9191c61
--- /dev/null
+++ b/test/disk_tests/ssd_smart/disklist
@@ -0,0 +1 @@
+sda
diff --git a/test/disk_tests/ssd_smart/disklist_expected.json b/test/disk_tests/ssd_smart/disklist_expected.json
new file mode 100644
index 0000000..3681cc8
--- /dev/null
+++ b/test/disk_tests/ssd_smart/disklist_expected.json
@@ -0,0 +1,16 @@
+{
+    "sda" : {
+	"serial" : "000000000000",
+	"vendor" : "ATA",
+	"rpm" : 0,
+	"gpt" : 1,
+	"health" : "PASSED",
+	"wearout" : "98",
+	"osdid" : -1,
+	"size" : 512000,
+	"type" : "ssd",
+	"devpath" : "/dev/sda",
+	"model" : "Samsung_SSD_860_EVO_1TB",
+	"wwn" : "0x0000000000000000"
+    }
+}
diff --git a/test/disk_tests/ssd_smart/sda/device/vendor b/test/disk_tests/ssd_smart/sda/device/vendor
new file mode 100644
index 0000000..531030d
--- /dev/null
+++ b/test/disk_tests/ssd_smart/sda/device/vendor
@@ -0,0 +1 @@
+ATA
diff --git a/test/disk_tests/ssd_smart/sda/queue/rotational b/test/disk_tests/ssd_smart/sda/queue/rotational
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/test/disk_tests/ssd_smart/sda/queue/rotational
@@ -0,0 +1 @@
+0
diff --git a/test/disk_tests/ssd_smart/sda/size b/test/disk_tests/ssd_smart/sda/size
new file mode 100644
index 0000000..83b33d2
--- /dev/null
+++ b/test/disk_tests/ssd_smart/sda/size
@@ -0,0 +1 @@
+1000
diff --git a/test/disk_tests/ssd_smart/sda_smart b/test/disk_tests/ssd_smart/sda_smart
new file mode 100644
index 0000000..907e7da
--- /dev/null
+++ b/test/disk_tests/ssd_smart/sda_smart
@@ -0,0 +1,352 @@
+{
+  "json_format_version": [
+    1,
+    0
+  ],
+  "smartctl": {
+    "version": [
+      7,
+      2
+    ],
+    "svn_revision": "5155",
+    "platform_info": "x86_64-linux-5.11.7-1-pve",
+    "build_info": "(local build)",
+    "argv": [
+      "smartctl",
+      "-j",
+      "-H",
+      "-Afbrief",
+      "/dev/sda"
+    ],
+    "exit_status": 0
+  },
+  "device": {
+    "name": "/dev/sda",
+    "info_name": "/dev/sda [SAT]",
+    "type": "sat",
+    "protocol": "ATA"
+  },
+  "smart_status": {
+    "passed": true
+  },
+  "ata_smart_attributes": {
+    "revision": 1,
+    "table": [
+      {
+        "id": 5,
+        "name": "Reallocated_Sector_Ct",
+        "value": 100,
+        "worst": 100,
+        "thresh": 10,
+        "when_failed": "",
+        "flags": {
+          "value": 51,
+          "string": "PO--CK ",
+          "prefailure": true,
+          "updated_online": true,
+          "performance": false,
+          "error_rate": false,
+          "event_count": true,
+          "auto_keep": true
+        },
+        "raw": {
+          "value": 0,
+          "string": "0"
+        }
+      },
+      {
+        "id": 9,
+        "name": "Power_On_Hours",
+        "value": 99,
+        "worst": 99,
+        "thresh": 0,
+        "when_failed": "",
+        "flags": {
+          "value": 50,
+          "string": "-O--CK ",
+          "prefailure": false,
+          "updated_online": true,
+          "performance": false,
+          "error_rate": false,
+          "event_count": true,
+          "auto_keep": true
+        },
+        "raw": {
+          "value": 2463,
+          "string": "2463"
+        }
+      },
+      {
+        "id": 12,
+        "name": "Power_Cycle_Count",
+        "value": 99,
+        "worst": 99,
+        "thresh": 0,
+        "when_failed": "",
+        "flags": {
+          "value": 50,
+          "string": "-O--CK ",
+          "prefailure": false,
+          "updated_online": true,
+          "performance": false,
+          "error_rate": false,
+          "event_count": true,
+          "auto_keep": true
+        },
+        "raw": {
+          "value": 499,
+          "string": "499"
+        }
+      },
+      {
+        "id": 177,
+        "name": "Wear_Leveling_Count",
+        "value": 98,
+        "worst": 98,
+        "thresh": 0,
+        "when_failed": "",
+        "flags": {
+          "value": 19,
+          "string": "PO--C- ",
+          "prefailure": true,
+          "updated_online": true,
+          "performance": false,
+          "error_rate": false,
+          "event_count": true,
+          "auto_keep": false
+        },
+        "raw": {
+          "value": 20,
+          "string": "20"
+        }
+      },
+      {
+        "id": 179,
+        "name": "Used_Rsvd_Blk_Cnt_Tot",
+        "value": 100,
+        "worst": 100,
+        "thresh": 10,
+        "when_failed": "",
+        "flags": {
+          "value": 19,
+          "string": "PO--C- ",
+          "prefailure": true,
+          "updated_online": true,
+          "performance": false,
+          "error_rate": false,
+          "event_count": true,
+          "auto_keep": false
+        },
+        "raw": {
+          "value": 0,
+          "string": "0"
+        }
+      },
+      {
+        "id": 181,
+        "name": "Program_Fail_Cnt_Total",
+        "value": 100,
+        "worst": 100,
+        "thresh": 10,
+        "when_failed": "",
+        "flags": {
+          "value": 50,
+          "string": "-O--CK ",
+          "prefailure": false,
+          "updated_online": true,
+          "performance": false,
+          "error_rate": false,
+          "event_count": true,
+          "auto_keep": true
+        },
+        "raw": {
+          "value": 0,
+          "string": "0"
+        }
+      },
+      {
+        "id": 182,
+        "name": "Erase_Fail_Count_Total",
+        "value": 100,
+        "worst": 100,
+        "thresh": 10,
+        "when_failed": "",
+        "flags": {
+          "value": 50,
+          "string": "-O--CK ",
+          "prefailure": false,
+          "updated_online": true,
+          "performance": false,
+          "error_rate": false,
+          "event_count": true,
+          "auto_keep": true
+        },
+        "raw": {
+          "value": 0,
+          "string": "0"
+        }
+      },
+      {
+        "id": 183,
+        "name": "Runtime_Bad_Block",
+        "value": 100,
+        "worst": 100,
+        "thresh": 10,
+        "when_failed": "",
+        "flags": {
+          "value": 19,
+          "string": "PO--C- ",
+          "prefailure": true,
+          "updated_online": true,
+          "performance": false,
+          "error_rate": false,
+          "event_count": true,
+          "auto_keep": false
+        },
+        "raw": {
+          "value": 0,
+          "string": "0"
+        }
+      },
+      {
+        "id": 187,
+        "name": "Uncorrectable_Error_Cnt",
+        "value": 100,
+        "worst": 100,
+        "thresh": 0,
+        "when_failed": "",
+        "flags": {
+          "value": 50,
+          "string": "-O--CK ",
+          "prefailure": false,
+          "updated_online": true,
+          "performance": false,
+          "error_rate": false,
+          "event_count": true,
+          "auto_keep": true
+        },
+        "raw": {
+          "value": 0,
+          "string": "0"
+        }
+      },
+      {
+        "id": 190,
+        "name": "Airflow_Temperature_Cel",
+        "value": 73,
+        "worst": 45,
+        "thresh": 0,
+        "when_failed": "",
+        "flags": {
+          "value": 50,
+          "string": "-O--CK ",
+          "prefailure": false,
+          "updated_online": true,
+          "performance": false,
+          "error_rate": false,
+          "event_count": true,
+          "auto_keep": true
+        },
+        "raw": {
+          "value": 27,
+          "string": "27"
+        }
+      },
+      {
+        "id": 195,
+        "name": "ECC_Error_Rate",
+        "value": 200,
+        "worst": 200,
+        "thresh": 0,
+        "when_failed": "",
+        "flags": {
+          "value": 26,
+          "string": "-O-RC- ",
+          "prefailure": false,
+          "updated_online": true,
+          "performance": false,
+          "error_rate": true,
+          "event_count": true,
+          "auto_keep": false
+        },
+        "raw": {
+          "value": 0,
+          "string": "0"
+        }
+      },
+      {
+        "id": 199,
+        "name": "CRC_Error_Count",
+        "value": 99,
+        "worst": 99,
+        "thresh": 0,
+        "when_failed": "",
+        "flags": {
+          "value": 62,
+          "string": "-OSRCK ",
+          "prefailure": false,
+          "updated_online": true,
+          "performance": true,
+          "error_rate": true,
+          "event_count": true,
+          "auto_keep": true
+        },
+        "raw": {
+          "value": 1,
+          "string": "1"
+        }
+      },
+      {
+        "id": 235,
+        "name": "POR_Recovery_Count",
+        "value": 99,
+        "worst": 99,
+        "thresh": 0,
+        "when_failed": "",
+        "flags": {
+          "value": 18,
+          "string": "-O--C- ",
+          "prefailure": false,
+          "updated_online": true,
+          "performance": false,
+          "error_rate": false,
+          "event_count": true,
+          "auto_keep": false
+        },
+        "raw": {
+          "value": 39,
+          "string": "39"
+        }
+      },
+      {
+        "id": 241,
+        "name": "Total_LBAs_Written",
+        "value": 99,
+        "worst": 99,
+        "thresh": 0,
+        "when_failed": "",
+        "flags": {
+          "value": 50,
+          "string": "-O--CK ",
+          "prefailure": false,
+          "updated_online": true,
+          "performance": false,
+          "error_rate": false,
+          "event_count": true,
+          "auto_keep": true
+        },
+        "raw": {
+          "value": 19999585737,
+          "string": "19999585737"
+        }
+      }
+    ]
+  },
+  "power_on_time": {
+    "hours": 2515
+  },
+  "power_cycle_count": 508,
+  "temperature": {
+    "current": 29
+  }
+}
diff --git a/test/disk_tests/ssd_smart/sda_smart_expected.json b/test/disk_tests/ssd_smart/sda_smart_expected.json
new file mode 100644
index 0000000..36da71e
--- /dev/null
+++ b/test/disk_tests/ssd_smart/sda_smart_expected.json
@@ -0,0 +1,146 @@
+{
+   "attributes" : [
+      {
+         "fail" : "-",
+         "flags" : "PO--CK",
+         "id" : 5,
+         "name" : "Reallocated_Sector_Ct",
+         "raw" : "0",
+         "threshold" : 10,
+         "value" : 100,
+         "worst" : 100
+      },
+      {
+         "fail" : "-",
+         "flags" : "-O--CK",
+         "id" : 9,
+         "name" : "Power_On_Hours",
+         "raw" : "2463",
+         "threshold" : 0,
+         "value" : 99,
+         "worst" : 99
+      },
+      {
+         "fail" : "-",
+         "flags" : "-O--CK",
+         "id" : 12,
+         "name" : "Power_Cycle_Count",
+         "raw" : "499",
+         "threshold" : 0,
+         "value" : 99,
+         "worst" : 99
+      },
+      {
+         "fail" : "-",
+         "flags" : "PO--C-",
+         "id" : 177,
+         "name" : "Wear_Leveling_Count",
+         "raw" : "20",
+         "threshold" : 0,
+         "value" : 98,
+         "worst" : 98
+      },
+      {
+         "fail" : "-",
+         "flags" : "PO--C-",
+         "id" : 179,
+         "name" : "Used_Rsvd_Blk_Cnt_Tot",
+         "raw" : "0",
+         "threshold" : 10,
+         "value" : 100,
+         "worst" : 100
+      },
+      {
+         "fail" : "-",
+         "flags" : "-O--CK",
+         "id" : 181,
+         "name" : "Program_Fail_Cnt_Total",
+         "raw" : "0",
+         "threshold" : 10,
+         "value" : 100,
+         "worst" : 100
+      },
+      {
+         "fail" : "-",
+         "flags" : "-O--CK",
+         "id" : 182,
+         "name" : "Erase_Fail_Count_Total",
+         "raw" : "0",
+         "threshold" : 10,
+         "value" : 100,
+         "worst" : 100
+      },
+      {
+         "fail" : "-",
+         "flags" : "PO--C-",
+         "id" : 183,
+         "name" : "Runtime_Bad_Block",
+         "raw" : "0",
+         "threshold" : 10,
+         "value" : 100,
+         "worst" : 100
+      },
+      {
+         "fail" : "-",
+         "flags" : "-O--CK",
+         "id" : 187,
+         "name" : "Uncorrectable_Error_Cnt",
+         "raw" : "0",
+         "threshold" : 0,
+         "value" : 100,
+         "worst" : 100
+      },
+      {
+         "fail" : "-",
+         "flags" : "-O--CK",
+         "id" : 190,
+         "name" : "Airflow_Temperature_Cel",
+         "raw" : "27",
+         "threshold" : 0,
+         "value" : 73,
+         "worst" : 45
+      },
+      {
+         "fail" : "-",
+         "flags" : "-O-RC-",
+         "id" : 195,
+         "name" : "ECC_Error_Rate",
+         "raw" : "0",
+         "threshold" : 0,
+         "value" : 200,
+         "worst" : 200
+      },
+      {
+         "fail" : "-",
+         "flags" : "-OSRCK",
+         "id" : 199,
+         "name" : "CRC_Error_Count",
+         "raw" : "1",
+         "threshold" : 0,
+         "value" : 99,
+         "worst" : 99
+      },
+      {
+         "fail" : "-",
+         "flags" : "-O--C-",
+         "id" : 235,
+         "name" : "POR_Recovery_Count",
+         "raw" : "39",
+         "threshold" : 0,
+         "value" : 99,
+         "worst" : 99
+      },
+      {
+         "fail" : "-",
+         "flags" : "-O--CK",
+         "id" : 241,
+         "name" : "Total_LBAs_Written",
+         "raw" : "19999585737",
+         "threshold" : 0,
+         "value" : 99,
+         "worst" : 99
+      }
+   ],
+   "health" : "PASSED",
+   "type" : "ata"
+}
diff --git a/test/disk_tests/ssd_smart/sda_udevadm b/test/disk_tests/ssd_smart/sda_udevadm
new file mode 100644
index 0000000..f662221
--- /dev/null
+++ b/test/disk_tests/ssd_smart/sda_udevadm
@@ -0,0 +1,11 @@
+E: DEVNAME=/dev/sda
+E: DEVTYPE=disk
+E: ID_ATA_ROTATION_RATE_RPM=0
+E: ID_BUS=ata
+E: ID_MODEL=Samsung_SSD_860_EVO_1TB
+E: ID_PART_TABLE_TYPE=gpt
+E: ID_SERIAL=Samsung_SSD_860_EVO_1TB_000000000000000
+E: ID_SERIAL_SHORT=000000000000
+E: ID_TYPE=disk
+E: ID_WWN=0x0000000000000000
+
diff --git a/test/disklist_test.pm b/test/disklist_test.pm
index 7f0e0be..727fb44 100644
--- a/test/disklist_test.pm
+++ b/test/disklist_test.pm
@@ -34,10 +34,10 @@ sub mocked_run_command {
 	    my $dev;
 	    my $type;
 	    if (@$cmd > 3) {
-		$dev = $cmd->[5];
+		$dev = $cmd->[4];
 		$type = 'smart';
 	    } else {
-		$dev = $cmd->[2];
+		$dev = $cmd->[3];
 		$type = 'health';
 	    }
 	    $dev =~ s|/dev/||;
-- 
2.20.1





More information about the pve-devel mailing list