all lists on lists.proxmox.com
 help / color / mirror / Atom feed
From: "Max R. Carrara" <m.carrara@proxmox.com>
To: pve-devel@lists.proxmox.com
Subject: [PATCH pve-storage 3/9] luncmd: lio: remove rest of old parsing logic
Date: Thu, 25 Jun 2026 18:40:55 +0200	[thread overview]
Message-ID: <20260625164110.706694-4-m.carrara@proxmox.com> (raw)
In-Reply-To: <20260625164110.706694-1-m.carrara@proxmox.com>

Instead of saving the entire config to a package-level variable, fetch
and parse the remote config in the LUN command dispatching sub
(serving as an entry point for in each iSCSI provider) and pass it
along wherever needed.

Do this by taking the opportunity to start modernizing the code in
this package, introducing helpers for fetching and parsing the remote
config, as well as for running remote commands in general.

The new `run_remote_command()` function in particular will be used in
future commits for better error reporting, too.

Signed-off-by: Max R. Carrara <m.carrara@proxmox.com>
---
 src/PVE/Storage/LunCmd/LIO.pm | 278 +++++++++++++++++++---------------
 1 file changed, 152 insertions(+), 126 deletions(-)

diff --git a/src/PVE/Storage/LunCmd/LIO.pm b/src/PVE/Storage/LunCmd/LIO.pm
index 9dd25ad2..ae5f3188 100644
--- a/src/PVE/Storage/LunCmd/LIO.pm
+++ b/src/PVE/Storage/LunCmd/LIO.pm
@@ -34,13 +34,138 @@ my @CONFIG_FILES = (
 );
 my $BACKSTORE = '/backstores/block';
 
-my $SETTINGS = undef;
-
 my @ssh_opts = ('-o', 'BatchMode=yes');
 my @ssh_cmd = ('/usr/bin/ssh', @ssh_opts);
 my $id_rsa_path = '/etc/pve/priv/zfs';
 my $targetcli = '/usr/bin/targetcli';
 
+my sub run_remote_command {
+    my ($scfg, $cmd, %param) = @_;
+
+    my $out_log = '';
+    my $err_log = '';
+    my $merged_log = '';
+
+    my $outfunc = sub {
+        my $out = shift;
+        $out_log .= $out . "\n";
+        $merged_log .= $out . "\n";
+    };
+
+    my $errfunc = sub {
+        my $err = shift;
+        $err_log .= $err . "\n";
+        $merged_log .= $err . "\n";
+    };
+
+    my $timeout = $param{timeout} || 10;
+
+    my $ssh_key_path = $id_rsa_path . '/' . $scfg->{portal} . '_id_rsa';
+    my $target = 'root@' . $scfg->{portal};
+
+    my $remote_cmd = [
+        @ssh_cmd, '-i', $ssh_key_path, $target, '--', $cmd->@*,
+    ];
+
+    my $result = 1;
+
+    eval {
+        run_command(
+            $remote_cmd,
+            outfunc => $outfunc,
+            errfunc => $errfunc,
+            timeout => $timeout,
+        );
+    };
+    if (my $err = $@) {
+        $result = 0;
+    }
+
+    return {
+        'err-log' => $err_log,
+        'merged-log' => $merged_log,
+        'out-log' => $out_log,
+        'remote-cmd' => $remote_cmd,
+        result => $result,
+    };
+}
+
+my sub fetch_remote_config {
+    my ($scfg, $timeout) = @_;
+
+    for my $file (@CONFIG_FILES) {
+        my $fetch_res = run_remote_command(
+            $scfg, ['cat', $file], timeout => $timeout,
+        );
+        if (!$fetch_res->{result} && $fetch_res->{'err-log'} !~ m/no such file or directory/i) {
+            my $err_msg =
+                "Failed to fetch config from iSCSI portal $scfg->{portal}:\n"
+                . $fetch_res->{'err-log'} . "\n";
+            die $err_msg;
+        }
+
+        if (my $config = $fetch_res->{'out-log'}) {
+            return JSON->new->utf8->decode($config);
+        }
+    }
+
+    die "No configuration found. Install targetcli on $scfg->{portal}\n";
+}
+
+my sub parse_remote_config {
+    my ($scfg, $remote_config_json) = @_;
+
+    my $tpg = $scfg->{lio_tpg} || die "Target Portal Group not set, aborting!\n";
+
+    $tpg =~ m/^tpg(?<tpg_tag>[0-9]+)$/;
+    my $tpg_tag = $+{tpg_tag};
+
+    if (!$tpg_tag) {
+        die "Target Portal Group has invalid value, must contain string 'tpg'"
+            . " and a suffix number, for example 'tpg17'\n";
+    }
+
+    my $id = $scfg->{portal} . '.' . $scfg->{target};
+    my $parsed_config = {};
+    my $found_target = 0;
+
+    for my $target ($remote_config_json->{targets}->@*) {
+        # We are only interested in iSCSI targets
+        next if $target->{fabric} ne 'iscsi';
+        next if $target->{wwn} ne $scfg->{target};
+
+        for my $tpg ($target->{tpgs}->@*) {
+            next if $tpg->{tag} != $tpg_tag;
+
+            my $luns = [];
+            for my $lun ($tpg->{luns}->@*) {
+                $lun->{index} =~ m/^(?<index>[0-9]+)$/;
+                my $index = $+{index};
+
+                $lun->{storage_object} =~ m|^(?<storage_object>$BACKSTORE/.*)$|;
+                my $storage_object = $+{storage_object};
+
+                if (!(defined($index) && defined($storage_object))) {
+                    die "Invalid LUN definition in config!\n";
+                }
+
+                push($luns->@*, { index => $index, storage_object => $storage_object });
+            }
+
+            $parsed_config->{$id}->{luns} = $luns;
+
+            $found_target = 1;
+            last;
+        }
+    }
+
+    if (!$found_target) {
+        die "Target Portal Group '$tpg' not found!\n";
+    }
+
+    return $parsed_config;
+}
+
 my $execute_remote_command = sub {
     my ($scfg, $timeout, $remote_command, @params) = @_;
 
@@ -82,110 +207,6 @@ my $execute_remote_command = sub {
     return $res;
 };
 
-# fetch targetcli configuration from the portal
-my $read_config = sub {
-    my ($scfg, $timeout) = @_;
-
-    my $msg = '';
-    my $err = undef;
-    my $luncmd = 'cat';
-    my $target;
-    my $retry = 1;
-
-    $timeout = 10 if !$timeout;
-
-    my $output = sub { $msg .= "$_[0]\n" };
-    my $errfunc = sub { $err .= "$_[0]\n" };
-
-    $target = 'root@' . $scfg->{portal};
-
-    foreach my $oneFile (@CONFIG_FILES) {
-        my $cmd =
-            [@ssh_cmd, '-i', "$id_rsa_path/$scfg->{portal}_id_rsa", $target, $luncmd, $oneFile];
-        eval {
-            run_command($cmd, outfunc => $output, errfunc => $errfunc, timeout => $timeout);
-        };
-        if ($@) {
-            die $err if ($err !~ /No such file or directory/);
-        }
-        return $msg if $msg ne '';
-    }
-
-    die "No configuration found. Install targetcli on $scfg->{portal}\n" if $msg eq '';
-
-    return $msg;
-};
-
-my $get_config = sub {
-    my ($scfg) = @_;
-    my @conf = undef;
-
-    my $config = $read_config->($scfg, undef);
-    die "Missing config file" unless $config;
-
-    return $config;
-};
-
-# Return settings of a specific target
-my $get_target_settings = sub {
-    my ($scfg) = @_;
-
-    my $id = "$scfg->{portal}.$scfg->{target}";
-    return undef if !$SETTINGS;
-    return $SETTINGS->{$id};
-};
-
-# fetches and parses targetcli config from the portal
-my $parser = sub {
-    my ($scfg) = @_;
-    my $tpg = $scfg->{lio_tpg} || die "Target Portal Group not set, aborting!\n";
-    my $tpg_tag;
-
-    if ($tpg =~ /^tpg(\d+)$/) {
-        $tpg_tag = $1;
-    } else {
-        die
-            "Target Portal Group has invalid value, must contain string 'tpg' and a suffix number, eg 'tpg17'\n";
-    }
-
-    my $config = $get_config->($scfg);
-    my $jsonconfig = JSON->new->utf8->decode($config);
-
-    my $haveTarget = 0;
-    foreach my $target (@{ $jsonconfig->{targets} }) {
-        # only interested in iSCSI targets
-        next if !($target->{fabric} eq 'iscsi' && $target->{wwn} eq $scfg->{target});
-        # find correct TPG
-        foreach my $tpg (@{ $target->{tpgs} }) {
-            if ($tpg->{tag} == $tpg_tag) {
-                my $res = [];
-                foreach my $lun (@{ $tpg->{luns} }) {
-                    my ($idx, $storage_object);
-                    if ($lun->{index} =~ /^(\d+)$/) {
-                        $idx = $1;
-                    }
-                    if ($lun->{storage_object} =~ m|^($BACKSTORE/.*)$|) {
-                        $storage_object = $1;
-                    }
-                    die "Invalid lun definition in config!\n"
-                        if !(defined($idx) && defined($storage_object));
-                    push @$res, { index => $idx, storage_object => $storage_object };
-                }
-
-                my $id = "$scfg->{portal}.$scfg->{target}";
-                $SETTINGS->{$id}->{luns} = $res;
-                $haveTarget = 1;
-                last;
-            }
-        }
-    }
-
-    # seriously unhappy if the target server lacks iSCSI target configuration ...
-    if (!$haveTarget) {
-        die "target portal group tpg$tpg_tag not found!\n";
-    }
-};
-
 # Get prefix for backstores
 my $get_backstore_prefix = sub {
     my ($scfg) = @_;
@@ -196,14 +217,15 @@ my $get_backstore_prefix = sub {
 
 # extracts the ZFS volume name from a device path
 my $extract_volname = sub {
-    my ($scfg, $lunpath) = @_;
+    my ($scfg, $config, $lunpath) = @_;
     my $volname = undef;
 
     my $base = get_base($scfg);
     if ($lunpath =~ /^$base\/$scfg->{pool}\/([\w\-\.]+)$/) {
         $volname = $1;
         my $prefix = $get_backstore_prefix->($scfg);
-        my $target = $get_target_settings->($scfg);
+        my $id = "$scfg->{portal}.$scfg->{target}";
+        my $target = $config->{$id};
         foreach my $lun (@{ $target->{luns} }) {
             # If we have a lun with the pool prefix matching this vol, then return this one
             # like pool-pve-vm-100-disk-0
@@ -219,12 +241,13 @@ my $extract_volname = sub {
 
 # retrieves the LUN index for a particular object
 my $list_view = sub {
-    my ($scfg, $timeout, $method, @params) = @_;
+    my ($scfg, $config, $timeout, $method, @params) = @_;
     my $lun = undef;
 
     my $object = $params[0];
-    my $volname = $extract_volname->($scfg, $object);
-    my $target = $get_target_settings->($scfg);
+    my $volname = $extract_volname->($scfg, $config, $object);
+    my $id = "$scfg->{portal}.$scfg->{target}";
+    my $target = $config->{$id};
 
     return undef if !defined($volname); # nothing to search for..
 
@@ -239,11 +262,12 @@ my $list_view = sub {
 
 # determines, if the given object exists on the portal
 my $list_lun = sub {
-    my ($scfg, $timeout, $method, @params) = @_;
+    my ($scfg, $config, $timeout, $method, @params) = @_;
 
     my $object = $params[0];
-    my $volname = $extract_volname->($scfg, $object);
-    my $target = $get_target_settings->($scfg);
+    my $volname = $extract_volname->($scfg, $config, $object);
+    my $id = "$scfg->{portal}.$scfg->{target}";
+    my $target = $config->{$id};
 
     foreach my $lun (@{ $target->{luns} }) {
         if ($lun->{storage_object} eq "$BACKSTORE/$volname") {
@@ -256,14 +280,14 @@ my $list_lun = sub {
 
 # adds a new LUN to the target
 my $create_lun = sub {
-    my ($scfg, $timeout, $method, @params) = @_;
+    my ($scfg, $config, $timeout, $method, @params) = @_;
 
-    if ($list_lun->($scfg, $timeout, $method, @params)) {
+    if ($list_lun->($scfg, $config, $timeout, $method, @params)) {
         die "$params[0]: LUN already exists!";
     }
 
     my $device = $params[0];
-    my $volname = $extract_volname->($scfg, $device);
+    my $volname = $extract_volname->($scfg, $config, $device);
     # Here we create a new device, so we didn't get the volname prefixed with the pool name
     # as extract_volname couldn't find a matching vol yet
     $volname = $get_backstore_prefix->($scfg) . $volname;
@@ -303,14 +327,15 @@ my $create_lun = sub {
 };
 
 my $delete_lun = sub {
-    my ($scfg, $timeout, $method, @params) = @_;
+    my ($scfg, $config, $timeout, $method, @params) = @_;
     my $res = { msg => undef };
 
     my $tpg = $scfg->{lio_tpg} || die "Target Portal Group not set, aborting!\n";
 
     my $path = $params[0];
-    my $volname = $extract_volname->($scfg, $path);
-    my $target = $get_target_settings->($scfg);
+    my $volname = $extract_volname->($scfg, $config, $path);
+    my $id = "$scfg->{portal}.$scfg->{target}";
+    my $target = $config->{$id};
 
     foreach my $lun (@{ $target->{luns} }) {
         next if $lun->{storage_object} ne "$BACKSTORE/$volname";
@@ -339,20 +364,20 @@ my $delete_lun = sub {
 };
 
 my $import_lun = sub {
-    my ($scfg, $timeout, $method, @params) = @_;
+    my ($scfg, $config, $timeout, $method, @params) = @_;
 
-    return $create_lun->($scfg, $timeout, $method, @params);
+    return $create_lun->($scfg, $config, $timeout, $method, @params);
 };
 
 # needed for example when the underlying ZFS volume has been resized
 my $modify_lun = sub {
-    my ($scfg, $timeout, $method, @params) = @_;
+    my ($scfg, $config, $timeout, $method, @params) = @_;
     # Nothing to do on volume modification for LIO
     return undef;
 };
 
 my $add_view = sub {
-    my ($scfg, $timeout, $method, @params) = @_;
+    my ($scfg, $config, $timeout, $method, @params) = @_;
 
     return '';
 };
@@ -372,9 +397,10 @@ sub run_lun_command {
 
     die "unknown command '$method'" unless exists $lun_cmd_map{$method};
 
-    $parser->($scfg);
+    my $remote_config_json = fetch_remote_config($scfg, $timeout);
+    my $config = parse_remote_config($scfg, $remote_config_json);
 
-    my $msg = $lun_cmd_map{$method}->($scfg, $timeout, $method, @params);
+    my $msg = $lun_cmd_map{$method}->($scfg, $config, $timeout, $method, @params);
 
     return $msg;
 }
-- 
2.47.3





  parent reply	other threads:[~2026-06-25 16:41 UTC|newest]

Thread overview: 10+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-06-25 16:40 [PATCH storage 0/9] Fix ZFS-over-iSCSI plugin w/ LIO Provider Issues Max R. Carrara
2026-06-25 16:40 ` [PATCH pve-storage 1/9] luncmd: lio: fix various errors by removing caching mechanism Max R. Carrara
2026-06-25 16:40 ` [PATCH pve-storage 2/9] luncmd: lio: fix LUN ops failing when passing nonstandard LUN path Max R. Carrara
2026-06-25 16:40 ` Max R. Carrara [this message]
2026-06-25 16:40 ` [PATCH pve-storage 4/9] luncmd: lio: modernize helpers Max R. Carrara
2026-06-25 16:40 ` [PATCH pve-storage 5/9] luncmd: lio: modernize & clean up `list_view()` and `list_lun()` subs Max R. Carrara
2026-06-25 16:40 ` [PATCH pve-storage 6/9] luncmd: lio: modernize sub `create_lun()` Max R. Carrara
2026-06-25 16:40 ` [PATCH pve-storage 7/9] luncmd: lio: modernize sub `delete_lun()` Max R. Carrara
2026-06-25 16:41 ` [PATCH pve-storage 8/9] luncmd: lio: modernize remaining LUN command subroutines Max R. Carrara
2026-06-25 16:41 ` [PATCH pve-storage 9/9] luncmd: lio: use v5.36 and use subroutine signatures Max R. Carrara

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=20260625164110.706694-4-m.carrara@proxmox.com \
    --to=m.carrara@proxmox.com \
    --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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal