public inbox for pve-devel@lists.proxmox.com
 help / color / mirror / Atom feed
From: Joaquin Varela <joaquinvarela@neatech.ar>
To: pve-devel@lists.proxmox.com
Cc: Joaquin Varela <joaquinvarela@neatech.ar>
Subject: [PATCH storage v2 4/7] zfsnvme: make all-path loss policy explicit
Date: Sun,  2 Aug 2026 00:31:38 -0300	[thread overview]
Message-ID: <5dc4f773ff3bcabb4debdeb26815c2acea17b371.1785636979.git.joaquinvarela@neatech.ar> (raw)
In-Reply-To: <cover.1785636979.git.joaquinvarela@neatech.ar>

Expose nvme-fast-io-fail-tmo as an optional storage property.
Leaving it unset preserves queue-until-controller-loss behavior.
Setting it asks the kernel to return I/O errors earlier when all
paths are reconnecting.

Reject values greater than a finite controller-loss timeout.
Pass the policy through libnvme runtime JSON and explicit nvme
connect arguments. Add tests for propagation and validation.

Signed-off-by: Joaquin Varela <joaquinvarela@neatech.ar>
---
 src/PVE/Storage/ZFSNVMePlugin.pm | 29 ++++++++++++++++++++++
 src/test/zfsnvme_test.pm         | 42 ++++++++++++++++++++++++++++++++
 2 files changed, 71 insertions(+)

diff --git a/src/PVE/Storage/ZFSNVMePlugin.pm b/src/PVE/Storage/ZFSNVMePlugin.pm
index d02dff4..6513df0 100644
--- a/src/PVE/Storage/ZFSNVMePlugin.pm
+++ b/src/PVE/Storage/ZFSNVMePlugin.pm
@@ -237,6 +237,14 @@ sub properties($class) {
             maximum => 86400,
             default => 600,
         },
+        'nvme-fast-io-fail-tmo' => {
+            description =>
+                "Optional time before failing I/O on a reconnecting NVMe controller. Unset queues I/O until controller loss timeout.",
+            type => 'integer',
+            minimum => 0,
+            maximum => 86400,
+            optional => 1,
+        },
         'nvme-nr-io-queues' => {
             description => "Number of NVMe/TCP I/O queues per controller.",
             type => 'integer',
@@ -261,6 +269,7 @@ sub options($class) {
         'nvme-keep-alive-tmo' => { optional => 1 },
         'nvme-reconnect-delay' => { optional => 1 },
         'nvme-ctrl-loss-tmo' => { optional => 1 },
+        'nvme-fast-io-fail-tmo' => { optional => 1 },
         'nvme-nr-io-queues' => { optional => 1 },
         nodes => { optional => 1 },
         disable => { optional => 1 },
@@ -269,6 +278,18 @@ sub options($class) {
     };
 }
 
+sub _validate_fail_fast_timeout($config, $default_ctrl_loss_tmo = undef) {
+    my $fast = $config->{'nvme-fast-io-fail-tmo'};
+    return if !defined($fast);
+
+    my $ctrl = $config->{'nvme-ctrl-loss-tmo'};
+    $ctrl = $default_ctrl_loss_tmo if !defined($ctrl);
+    return if !defined($ctrl) || $ctrl < 0;
+
+    die "nvme-fast-io-fail-tmo must not exceed nvme-ctrl-loss-tmo\n"
+        if $fast > $ctrl;
+}
+
 sub check_config($class, $section_id, $config, $create, $skip_schema_check) {
     if ($create) {
         $config->{sparse} = 1 if !defined($config->{sparse});
@@ -285,6 +306,7 @@ sub check_config($class, $section_id, $config, $create, $skip_schema_check) {
     } elsif (defined($config->{'nvme-host-ifaces'})) {
         parse_nvme_host_ifaces($config->{'nvme-host-ifaces'});
     }
+    _validate_fail_fast_timeout($config, $create ? 600 : undef);
     return $class->SUPER::check_config($section_id, $config, $create, $skip_schema_check);
 }
 
@@ -380,6 +402,7 @@ sub on_update_hook_full($class, $storeid, $scfg, $update, $delete, $sensitive) {
     delete @prospective{$delete->@*} if $delete;
     verify_nvme_nqn($prospective{subsysnqn});
     _configured_portals(\%prospective);
+    _validate_fail_fast_timeout(\%prospective, 600);
     _assert_unique_target($storeid, \%prospective);
 
     my $old_key = file_read_firstline(secret_path($storeid));
@@ -433,6 +456,9 @@ my sub write_runtime_config($storeid, $scfg, $hostnqn, $hostid, $key, $portals)
                 keep_alive_tmo => $scfg->{'nvme-keep-alive-tmo'} // 5,
                 reconnect_delay => $scfg->{'nvme-reconnect-delay'} // 2,
                 ctrl_loss_tmo => $scfg->{'nvme-ctrl-loss-tmo'} // 600,
+                (defined($scfg->{'nvme-fast-io-fail-tmo'})
+                    ? (fast_io_fail_tmo => $scfg->{'nvme-fast-io-fail-tmo'})
+                    : ()),
                 ($scfg->{'nvme-nr-io-queues'}
                     ? (nr_io_queues => $scfg->{'nvme-nr-io-queues'})
                     : ()),
@@ -585,6 +611,9 @@ sub _connect_portal($config_path, $scfg, $portal) {
         '--ctrl-loss-tmo',
         $scfg->{'nvme-ctrl-loss-tmo'} // 600,
     ];
+    if (defined(my $fast = $scfg->{'nvme-fast-io-fail-tmo'})) {
+        push $cmd->@*, '--fast_io_fail_tmo', $fast;
+    }
     if (my $queues = $scfg->{'nvme-nr-io-queues'}) {
         push $cmd->@*, '--nr-io-queues', $queues;
     }
diff --git a/src/test/zfsnvme_test.pm b/src/test/zfsnvme_test.pm
index edb8b38..e06ac76 100644
--- a/src/test/zfsnvme_test.pm
+++ b/src/test/zfsnvme_test.pm
@@ -244,6 +244,22 @@ is_deeply(
     'nvme connect is explicitly bound to the configured data interface',
 );
 
+PVE::Storage::ZFSNVMePlugin::_connect_portal(
+    '/run/pve-storage/test.json',
+    {
+        subsysnqn => 'nqn.2026-07.example:test',
+        'nvme-ctrl-loss-tmo' => 60,
+        'nvme-fast-io-fail-tmo' => 15,
+    },
+    { address => '10.90.2.11', port => 4420, host_iface => 'ens21' },
+);
+my $connect_args = join(' ', @connect_cmd);
+like(
+    $connect_args,
+    qr/--fast_io_fail_tmo 15/,
+    'configured fast I/O fail timeout is passed to nvme connect',
+);
+
 $nvme_mock->redefine(
     zfs_request => sub {
         my ($class, $config, $timeout, $method, @params) = @_;
@@ -329,6 +345,32 @@ eval {
 };
 is($@, '', 'check_config accepts a valid partial update');
 
+eval {
+    PVE::Storage::ZFSNVMePlugin::_validate_fail_fast_timeout(
+        {
+            'nvme-ctrl-loss-tmo' => 30,
+            'nvme-fast-io-fail-tmo' => 31,
+        },
+        600,
+    );
+};
+like(
+    $@,
+    qr/must not exceed nvme-ctrl-loss-tmo/,
+    'fast I/O fail timeout cannot outlive the controller loss timeout',
+);
+
+eval {
+    PVE::Storage::ZFSNVMePlugin::_validate_fail_fast_timeout(
+        {
+            'nvme-ctrl-loss-tmo' => -1,
+            'nvme-fast-io-fail-tmo' => 30,
+        },
+        600,
+    );
+};
+is($@, '', 'fast I/O fail remains valid with infinite controller reconnect');
+
 {
     no warnings 'redefine';
     local *PVE::Storage::config = sub { return { ids => {} } };
-- 
2.54.0.windows.1




  parent reply	other threads:[~2026-08-02  3:33 UTC|newest]

Thread overview: 8+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-02  3:31 [PATCH storage v2 0/7] add native ZFS over NVMe/TCP backend Joaquin Varela
2026-08-02  3:31 ` [PATCH storage v2 1/7] zfs: make LUN provider dispatch overridable Joaquin Varela
2026-08-02  3:31 ` [PATCH storage v2 2/7] zfs: add native NVMe/TCP storage backend Joaquin Varela
2026-08-02  3:31 ` [PATCH storage v2 3/7] zfsnvme: harden node preflight and storage teardown Joaquin Varela
2026-08-02  3:31 ` Joaquin Varela [this message]
2026-08-02  3:31 ` [PATCH storage v2 5/7] zfsnvme: accept activation hints from storage API Joaquin Varela
2026-08-02  3:31 ` [PATCH storage v2 6/7] zfsnvme: accept short volume activation calls Joaquin Varela
2026-08-02  3:31 ` [PATCH storage v2 7/7] zfsnvme: restore ACLs before publishing target Joaquin Varela

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=5dc4f773ff3bcabb4debdeb26815c2acea17b371.1785636979.git.joaquinvarela@neatech.ar \
    --to=joaquinvarela@neatech.ar \
    --cc=pve-devel@lists.proxmox.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal