[PATCH v2 storage 1/1] fix #254: iscsi: allow to configure multiple portals
ykonotopov at gnome.org
ykonotopov at gnome.org
Sun Oct 15 20:32:16 CEST 2023
From: Yuri Konotopov <ykonotopov at gnome.org>
Signed-off-by: Yuri Konotopov <ykonotopov at gnome.org>
---
PVE/API2/Storage/Scan.pm | 2 +-
PVE/Storage.pm | 10 +-
PVE/Storage/ISCSIPlugin.pm | 207 ++++++++++++++++++++++++++++++++-----
3 files changed, 187 insertions(+), 32 deletions(-)
diff --git a/PVE/API2/Storage/Scan.pm b/PVE/API2/Storage/Scan.pm
index d7a8743..1f9773c 100644
--- a/PVE/API2/Storage/Scan.pm
+++ b/PVE/API2/Storage/Scan.pm
@@ -305,7 +305,7 @@ __PACKAGE__->register_method({
node => get_standard_option('pve-node'),
portal => {
description => "The iSCSI portal (IP or DNS name with optional port).",
- type => 'string', format => 'pve-storage-portal-dns',
+ type => 'string', format => 'pve-storage-portal-dns-list',
},
},
},
diff --git a/PVE/Storage.pm b/PVE/Storage.pm
index cec3996..0043507 100755
--- a/PVE/Storage.pm
+++ b/PVE/Storage.pm
@@ -1428,12 +1428,14 @@ sub resolv_portal {
sub scan_iscsi {
my ($portal_in) = @_;
- my $portal;
- if (!($portal = resolv_portal($portal_in))) {
- die "unable to parse/resolve portal address '${portal_in}'\n";
+ my @portals = PVE::Tools::split_list($portal_in);
+ for my $portal (@portals) {
+ if (!resolv_portal($portal)) {
+ die "unable to parse/resolve portal address '${portal}'\n";
+ }
}
- return PVE::Storage::ISCSIPlugin::iscsi_discovery($portal);
+ return PVE::Storage::ISCSIPlugin::iscsi_discovery(\@portals);
}
sub storage_default_format {
diff --git a/PVE/Storage/ISCSIPlugin.pm b/PVE/Storage/ISCSIPlugin.pm
index a79fcb0..6f4309f 100644
--- a/PVE/Storage/ISCSIPlugin.pm
+++ b/PVE/Storage/ISCSIPlugin.pm
@@ -18,6 +18,65 @@ use base qw(PVE::Storage::Plugin);
my $ISCSIADM = '/usr/bin/iscsiadm';
$ISCSIADM = undef if ! -X $ISCSIADM;
+my $iscsi_cfg = "/etc/pve/iscsi.cfg";
+
+sub read_config {
+ my ($filename, $raw) = @_;
+
+ my $cfg = {};
+
+ return $cfg if ! -f $iscsi_cfg;
+
+ my $content = PVE::Tools::file_get_contents($iscsi_cfg);
+ return $cfg if !defined($content);
+
+ my @lines = split /\n/, $content;
+
+ my $target;
+
+ for my $line (@lines) {
+ $line =~ s/#.*$//;
+ $line =~ s/^\s+//;
+ $line =~ s/^;.*$//;
+ $line =~ s/\s+$//;
+ next if !$line;
+
+ $target = $1 if $line =~ m/^\[(\S+)\]$/;
+ if (!$target) {
+ warn "no target - skip: $line\n";
+ next;
+ }
+
+ if (!defined($cfg->{$target})) {
+ $cfg->{$target} = [];
+ }
+
+ if ($line =~ m/^((?:$IPV4RE|\[$IPV6RE\]):\d+)$/) {
+ push @{$cfg->{$target}}, $1;
+ }
+ }
+
+ return $cfg;
+}
+
+sub write_config {
+ my ($cfg) = @_;
+
+ my $out = '';
+
+ for my $target (sort keys %$cfg) {
+ $out .= "[$target]\n";
+ for my $portal (sort @{$cfg->{$target}}) {
+ $out .= "$portal\n";
+ }
+ $out .= "\n";
+ }
+
+ PVE::Tools::file_set_contents($iscsi_cfg, $out);
+
+ return;
+}
+
sub check_iscsi_support {
my $noerr = shift;
@@ -45,11 +104,10 @@ sub iscsi_session_list {
eval {
run_command($cmd, errmsg => 'iscsi session scan failed', outfunc => sub {
my $line = shift;
-
- if ($line =~ m/^tcp:\s+\[(\S+)\]\s+\S+\s+(\S+)(\s+\S+)?\s*$/) {
- my ($session, $target) = ($1, $2);
+ if ($line =~ m/^tcp:\s+\[(\S+)\]\s+((?:$IPV4RE|\[$IPV6RE\]):\d+)\,\S+\s+(\S+)(\s+\S+)?\s*$/) {
+ my ($session, $portal, $target) = ($1, $2, $3);
# there can be several sessions per target (multipath)
- push @{$res->{$target}}, $session;
+ push @{$res->{$target}}, [$session, $portal];
}
});
};
@@ -68,42 +126,68 @@ sub iscsi_test_portal {
return PVE::Network::tcp_ping($server, $port || 3260, 2);
}
-sub iscsi_discovery {
- my ($portal) = @_;
+sub iscsi_portals {
+ my ($target) = @_;
check_iscsi_support ();
- my $res = {};
- return $res if !iscsi_test_portal($portal); # fixme: raise exception here?
-
- my $cmd = [$ISCSIADM, '--mode', 'discovery', '--type', 'sendtargets', '--portal', $portal];
+ my $res = [];
+ my $cmd = [$ISCSIADM, '--mode', 'node'];
run_command($cmd, outfunc => sub {
my $line = shift;
if ($line =~ m/^((?:$IPV4RE|\[$IPV6RE\]):\d+)\,\S+\s+(\S+)\s*$/) {
- my $portal = $1;
- my $target = $2;
- # one target can have more than one portal (multipath).
- push @{$res->{$target}}, $portal;
+ my ($portal, $portal_target) = ($1, $2);
+ if ($portal_target eq $target) {
+ push @{$res}, $portal;
+ }
}
});
return $res;
}
+sub iscsi_discovery {
+ my ($portals) = @_;
+
+ check_iscsi_support ();
+
+ my $res = {};
+ for my $portal ($portals->@*) {
+ next if !iscsi_test_portal($portal); # fixme: raise exception here?
+
+ my $cmd = [$ISCSIADM, '--mode', 'discovery', '--type', 'sendtargets', '--portal', $portal];
+ run_command($cmd, outfunc => sub {
+ my $line = shift;
+
+ if ($line =~ m/^((?:$IPV4RE|\[$IPV6RE\]):\d+)\,\S+\s+(\S+)\s*$/) {
+ my $portal = $1;
+ my $target = $2;
+ # one target can have more than one portal (multipath).
+ push @{$res->{$target}}, $portal;
+ }
+ });
+
+ # In case of multipath we want to exit on any portal available
+ last;
+ }
+
+ return $res;
+}
+
sub iscsi_login {
- my ($target, $portal_in) = @_;
+ my ($target, $portals) = @_;
check_iscsi_support();
- eval { iscsi_discovery($portal_in); };
+ eval { iscsi_discovery($portals); };
warn $@ if $@;
run_command([$ISCSIADM, '--mode', 'node', '--targetname', $target, '--login']);
}
sub iscsi_logout {
- my ($target, $portal) = @_;
+ my ($target) = @_;
check_iscsi_support();
@@ -133,7 +217,7 @@ sub iscsi_session_rescan {
}
foreach my $session (@$session_list) {
- my $cmd = [$ISCSIADM, '--mode', 'session', '--sid', $session, '--rescan'];
+ my $cmd = [$ISCSIADM, '--mode', 'session', '--sid', $session->@[0], '--rescan'];
eval { run_command($cmd, outfunc => sub {}); };
warn $@ if $@;
}
@@ -245,8 +329,8 @@ sub properties {
type => 'string',
},
portal => {
- description => "iSCSI portal (IP or DNS name with optional port).",
- type => 'string', format => 'pve-storage-portal-dns',
+ description => "iSCSI portal (IP or DNS name with optional port). For multipath, multiple portals can be specified.",
+ type => 'string', format => 'pve-storage-portal-dns-list',
},
};
}
@@ -264,6 +348,33 @@ sub options {
# Storage implementation
+sub on_add_hook {
+ my ($class, $storeid, $scfg, %param) = @_;
+
+ my @portals = PVE::Tools::split_list($scfg->{portal});
+ my $target = $scfg->{target};
+
+ my $portal_cfg = read_config();
+ $portal_cfg->{$target} = \@portals;
+
+ write_config($portal_cfg);
+
+ return;
+}
+
+sub on_delete_hook {
+ my ($class, $storeid, $scfg) = @_;
+
+ my $portal_cfg = read_config();
+ my $target = $scfg->{target};
+
+ delete $portal_cfg->{$target};
+
+ write_config($portal_cfg);
+
+ return;
+}
+
sub parse_volname {
my ($class, $volname) = @_;
@@ -365,6 +476,21 @@ sub iscsi_session {
return $cache->{iscsi_sessions}->{$target};
}
+sub iscsi_session_portals {
+ my ($cache, $target) = @_;
+
+ my $res = [];
+ my $sessions = iscsi_session($cache, $target);
+
+ if (defined($sessions)) {
+ for my $session ($sessions->@*) {
+ push @{$res}, $session->@[1];
+ }
+ }
+
+ return $res;
+}
+
sub status {
my ($class, $storeid, $scfg, $cache) = @_;
@@ -379,14 +505,37 @@ sub activate_storage {
return if !check_iscsi_support(1);
- my $session = iscsi_session($cache, $scfg->{target});
- if (!defined ($session)) {
- eval { iscsi_login($scfg->{target}, $scfg->{portal}); };
+ my $sessions = iscsi_session($cache, $scfg->{target});
+ my $portal_cfg = read_config();
+
+ my @portals = PVE::Tools::split_list($scfg->{portal});
+ if (defined($portal_cfg->{$scfg->{target}})) {
+ @portals = @{$portal_cfg->{$scfg->{target}}};
+ }
+
+ if (!defined ($sessions)) {
+ eval { iscsi_login($scfg->{target}, \@portals); };
warn $@ if $@;
} else {
+ my @discovered_portals = @{iscsi_portals($scfg->{target})};
+ my @session_portals = @{iscsi_session_portals($cache, $scfg->{target})};
+
+ for my $discovered_portal (@discovered_portals) {
+ if (!grep(/^\Q$discovered_portal\E$/, @session_portals)) {
+ eval { iscsi_login($scfg->{target}, \@discovered_portals); };
+ warn $@ if $@;
+ last;
+ }
+ }
+
+ if(join(",", sort(@discovered_portals)) ne join(",", sort(@portals))) {
+ $portal_cfg->{$scfg->{target}} = \@discovered_portals;
+ write_config($portal_cfg);
+ }
+
# make sure we get all devices
- iscsi_session_rescan($session);
+ iscsi_session_rescan($sessions);
}
}
@@ -396,15 +545,19 @@ sub deactivate_storage {
return if !check_iscsi_support(1);
if (defined(iscsi_session($cache, $scfg->{target}))) {
- iscsi_logout($scfg->{target}, $scfg->{portal});
+ iscsi_logout($scfg->{target});
}
}
sub check_connection {
my ($class, $storeid, $scfg) = @_;
- my $portal = $scfg->{portal};
- return iscsi_test_portal($portal);
+ for my $portal (PVE::Tools::split_list($scfg->{portal})) {
+ my $result = iscsi_test_portal($portal);
+ return $result if $result;
+ }
+
+ return 0;
}
sub volume_resize {
--
2.41.0
More information about the pve-devel
mailing list