[pve-devel] [PATCH 3/3] Module to support istgt lun commands Istgt is used on any *BSD. As of FreeBSD 10 it seems FreeBSD will provide thier own native replacement.

mir at datanom.net mir at datanom.net
Thu Oct 17 01:18:50 CEST 2013


From: Michael Rasmussen <mir at datanom.net>

Signed-off-by: Michael Rasmussen <mir at datanom.net>
---
 PVE/Storage/LunCmd/Istgt.pm | 580 ++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 580 insertions(+)
 create mode 100644 PVE/Storage/LunCmd/Istgt.pm

diff --git a/PVE/Storage/LunCmd/Istgt.pm b/PVE/Storage/LunCmd/Istgt.pm
new file mode 100644
index 0000000..13d57fa
--- /dev/null
+++ b/PVE/Storage/LunCmd/Istgt.pm
@@ -0,0 +1,580 @@
+package PVE::Storage::LunCmd::Istgt;
+
+# TODO
+# Create initial target and LUN if target is missing ?
+# Create and use list of free LUNs
+
+use strict;
+use warnings;
+use PVE::Tools qw(run_command file_read_firstline trim dir_glob_regex dir_glob_foreach);
+use Data::Dumper;
+
+my @CONFIG_FILES = (
+	'/usr/local/etc/istgt/istgt.conf',  # FreeBSD, FreeNAS
+	'/var/etc/iscsi/istgt.conf' 		# NAS4Free
+);
+my @DAEMONS = (
+	'/usr/local/etc/rc.d/istgt',		# FreeBSD, FreeNAS
+	'/var/etc/rc.d/istgt'				# NAS4Free
+);
+
+# A logical unit can max have 63 LUNs
+# https://code.google.com/p/istgt/source/browse/src/istgt_lu.h#39
+my $MAX_LUNS = 64;
+
+my $CONFIG_FILE = undef;
+my $DAEMON = undef;
+my $SETTINGS = undef;
+my $CONFIG = undef;
+my $OLD_CONFIG = undef;
+
+my @ssh_opts = ('-o', 'BatchMode=yes');
+my @ssh_cmd = ('/usr/bin/ssh', @ssh_opts);
+my @scp_cmd = ('/usr/bin/scp', @ssh_opts);
+
+#Current SIGHUP reload limitations (http://www.peach.ne.jp/archives/istgt/):
+#
+#    The parameters other than PG, IG, and LU are not reloaded by SIGHUP.
+#    LU connected by the initiator can't be reloaded by SIGHUP.
+#    PG and IG mapped to LU can't be deleted by SIGHUP.
+#    If you delete an active LU, all connections of the LU are closed by SIGHUP.
+#    Updating IG is not affected until the next login. 
+#
+# FreeBSD
+# 1. Alt-F2 to change to native shell (zfsguru)
+# 2. pw mod user root -w yes (change password for root to root)
+# 3. vi /etc/ssh/sshd_config
+# 4. uncomment PermitRootLogin yes
+# 5. change PasswordAuthentication no to PasswordAuthentication yes
+# 5. /etc/rc.d/sshd restart
+# 6. On one of the proxmox nodes login as root and run: ssh-copy-id ip_freebsd_host
+# 7. vi /etc/ssh/sshd_config
+# 8. comment PermitRootLogin yes
+# 9. change PasswordAuthentication yes to PasswordAuthentication no
+# 10. /etc/rc.d/sshd restart
+# 11. Reset passwd -> pw mod user root -w no
+# 12. Alt-Ctrl-F1 to return to zfsguru shell (zfsguru)
+
+sub get_base;
+sub run_lun_command;
+
+my $read_config = sub {
+	my ($scfg, $timeout, $method) = @_;
+	
+    my $msg = '';
+	my $err = undef;
+    my $luncmd = 'cat';
+    my $target;
+    $timeout = 10 if !$timeout;
+	
+    my $output = sub {
+    my $line = shift;
+	$msg .= "$line\n";
+    };
+	
+	my $errfunc = sub {
+    my $line = shift;
+	$err .= "$line";
+	};
+
+	$target = 'root@' . $scfg->{portal};
+
+	my $daemon = 0;
+	foreach my $config (@CONFIG_FILES) {
+		$err = undef;
+		my $cmd = [@ssh_cmd, $target, $luncmd, $config];
+		eval {
+			run_command($cmd, outfunc => $output, errfunc => $errfunc, timeout => $timeout);
+		};
+		do {
+			$err = undef;
+			$DAEMON = $DAEMONS[$daemon];
+			$CONFIG_FILE = $config;
+			last;
+		} unless $@;
+		$daemon++;
+	}
+	die $err if ($err && $err !~ /No such file or directory/);
+	die "No configuration found. Install istgt on $scfg->{portal}" if $msg eq '';
+
+	return $msg;
+};
+
+my $get_config = sub {
+	my ($scfg) = @_;
+	my @conf = undef;
+	
+	my $config = $read_config->($scfg, undef, 'get_config');
+	die "Missing config file" unless $config;
+
+	$OLD_CONFIG = $config;
+
+	return $config;
+};
+
+my $parse_size = sub {
+    my ($text) = @_;
+
+    return 0 if !$text;
+
+    if ($text =~ m/^(\d+(\.\d+)?)([TGMK]B)?$/) {
+	my ($size, $reminder, $unit) = ($1, $2, $3);
+	return $size if !$unit;
+	if ($unit eq 'KB') {
+	    $size *= 1024;
+	} elsif ($unit eq 'MB') {
+	    $size *= 1024*1024;
+	} elsif ($unit eq 'GB') {
+	    $size *= 1024*1024*1024;
+	} elsif ($unit eq 'TB') {
+	    $size *= 1024*1024*1024*1024;
+	}
+		if ($reminder) {
+			$size = ceil($size);
+		}
+		return $size;
+    } elsif ($text =~ /^auto$/i) {
+		return 'AUTO';
+	} else {
+		return 0;
+    }
+};
+
+my $size_with_unit = sub {
+    my ($size, $n) = (shift, 0);
+
+    return '0KB' if !$size;
+
+	return $size if $size eq 'AUTO';
+	
+    if ($size =~ m/^\d+$/) {
+		++$n and $size /= 1024 until $size < 1024;
+		if ($size =~ /\./) {
+			return sprintf "%.2f%s", $size, ( qw[bytes KB MB GB TB] )[ $n ];
+		} else {
+			return sprintf "%d%s", $size, ( qw[bytes KB MB GB TB] )[ $n ];
+		}
+	}
+	die "$size: Not a number";
+};
+
+my $lun_dumper = sub {
+	my ($lun) = @_;
+	my $config = '';
+
+	$config .= "\n[$lun]\n";
+	$config .=  'TargetName ' . $SETTINGS->{$lun}->{TargetName} . "\n";
+	$config .=  'Mapping ' . $SETTINGS->{$lun}->{Mapping} . "\n";
+	$config .=  'AuthGroup ' . $SETTINGS->{$lun}->{AuthGroup} . "\n";
+	$config .=  'UnitType ' . $SETTINGS->{$lun}->{UnitType} . "\n";
+	$config .=  'QueueDepth ' . $SETTINGS->{$lun}->{QueueDepth} . "\n";
+
+	foreach my $conf (@{$SETTINGS->{$lun}->{luns}}) {
+		$config .=  "$conf->{lun} Storage " . $conf->{Storage};
+		$config .= ' ' . $size_with_unit->($conf->{Size}) . "\n";
+	}
+	$config .= "\n";
+
+	return $config;
+};
+
+my $get_lu_name = sub {
+	my ($target) = @_;
+	my $used = ();
+	my $i;
+	
+	if (! exists $SETTINGS->{$target}->{used}) {
+		for ($i = 0; $i < $MAX_LUNS; $i++) {
+			$used->{$i} = 0;
+		}
+		foreach my $lun (@{$SETTINGS->{$target}->{luns}}) {
+			$lun->{lun} =~ /^LUN(\d+)$/;
+			$used->{$1} = 1;
+		}
+		$SETTINGS->{$target}->{used} = $used;
+	}
+	
+	$used = $SETTINGS->{$target}->{used};
+	for ($i = 0; $i < $MAX_LUNS; $i++) {
+		last unless $used->{$i};
+	}
+	$SETTINGS->{$target}->{used}->{$i} = 1;
+
+	return "LUN$i";
+};
+
+my $init_lu_name = sub {
+	my ($target) = @_;
+	my $used = ();
+
+	if (! exists($SETTINGS->{$target}->{used})) {
+		for (my $i = 0; $i < $MAX_LUNS; $i++) {
+			$used->{$i} = 0;
+		}
+		$SETTINGS->{$target}->{used} = $used;
+	}
+	foreach my $lun (@{$SETTINGS->{$target}->{luns}}) {
+		$lun->{lun} =~ /^LUN(\d+)$/;
+		$SETTINGS->{$target}->{used}->{$1} = 1;
+	}
+};
+
+my $free_lu_name = sub {
+	my ($target, $lu_name) = @_;
+	my $used = ();
+
+	$lu_name =~ /^LUN(\d+)$/;
+	$SETTINGS->{$target}->{used}->{$1} = 0;
+};
+
+my $make_lun = sub {
+	my ($path) = @_;
+	
+	my $target = $SETTINGS->{current};
+	die 'Maximum number of LUNs per target is 63' if scalar @{$SETTINGS->{$target}->{luns}} >= $MAX_LUNS;
+	
+	my $lun = $get_lu_name->($target);
+	my $conf = {
+		lun => $lun,
+		Storage => $path,
+		Size => 'AUTO',
+	};
+	push @{$SETTINGS->{$target}->{luns}}, $conf;
+
+	return $conf->{lun};
+};
+
+my $parser = sub {
+	my ($scfg) = @_;
+	
+	my $lun = undef;
+	my $line = 0;
+	
+	my $config = $get_config->($scfg);
+	my @cfgfile = split "\n", $config;
+
+	foreach (@cfgfile) {
+		$line++;
+		if ($_ =~ /^\s*\[(PortalGroup\d+)\]\s*/) {
+			$lun = undef;
+			$SETTINGS->{$1} = ();
+		} elsif ($_ =~ /^\s*\[(InitiatorGroup\d+)\]\s*/) {
+			$lun = undef;
+			$SETTINGS->{$1} = ();
+		} elsif ($_ =~ /^\s*PidFile\s+"?([\w\/\.]+)"?\s*/) {
+			$lun = undef;
+			$SETTINGS->{pidfile} = $1;
+		} elsif ($_ =~ /^\s*NodeBase\s+"?([\w\-\.]+)"?\s*/) {
+			$lun = undef;
+			$SETTINGS->{nodebase} = $1;
+		} elsif ($_ =~ /^\s*\[(LogicalUnit\d+)\]\s*/) {
+			$lun = $1;
+			$SETTINGS->{$lun} = ();
+			$SETTINGS->{targets}++;
+		} elsif ($lun) {
+			next if (($_ =~ /^\s*#/) || ($_ =~ /^\s*$/));
+			if ($_ =~ /^\s*(\w+)\s+(.+)\s*/) {
+				#next if $2 =~ /^Option.*/;
+				$SETTINGS->{$lun}->{$1} = $2;
+				$SETTINGS->{$lun}->{$1} =~ s/^\s+|\s+$|"\s*//g;
+			} else {
+				die "$line: parse error [$_]";
+			}
+		}
+		$CONFIG .= "$_\n" unless $lun;
+	}
+
+	$CONFIG =~ s/\n$//;
+	die "$scfg->{target}: Target not found" unless $SETTINGS->{targets};
+	my $max = $SETTINGS->{targets};
+	my $base = get_base;
+	my $n;
+	for (my $i = 1; $i <= $max; $i++) {
+		my $target = $SETTINGS->{nodebase}.':'.$SETTINGS->{"LogicalUnit$i"}->{TargetName};
+		if ($target eq $scfg->{target}) {
+			my $lu = ();
+			while ((my $key, my $val) = each(%{$SETTINGS->{"LogicalUnit$i"}})) {
+				if ($key =~ /^LUN\d+/) {
+					if ($val =~ /^Storage\s+([\w\/\-]+)\s+(\w+)/) {
+						my $storage = $1;
+						my $size = $parse_size->($2);
+						my $conf = undef;
+						if ($storage =~ /^$base\/$scfg->{pool}\/([\w\-]+)$/) {
+							$conf = {
+								lun => $key,
+								Storage => $storage,
+								Size => $size,
+							};
+						}
+						push @$lu, $conf if $conf;
+					}
+					delete $SETTINGS->{"LogicalUnit$i"}->{$key};
+				}
+			}
+			$SETTINGS->{"LogicalUnit$i"}->{luns} = $lu;			
+			$SETTINGS->{current} = "LogicalUnit$i";
+			$init_lu_name->("LogicalUnit$i");
+		} else {
+			$CONFIG .= $lun_dumper->("LogicalUnit$i");
+			delete $SETTINGS->{"LogicalUnit$i"};
+			$SETTINGS->{targets}--;
+		}
+	}
+	die "$scfg->{target}: Target not found" unless $SETTINGS->{targets} > 0;
+};
+
+my $list_lun = sub {
+	my ($scfg, $timeout, $method, @params) = @_;
+	my $name = undef;
+
+	my $object = $params[0];
+	for my $key (keys %$SETTINGS)  {
+		next unless $key =~ /^LogicalUnit\d+$/;
+		foreach my $lun (@{$SETTINGS->{$key}->{luns}}) {
+			if ($lun->{Storage} =~ /^$object$/) {
+				return $lun->{Storage};
+			}
+		}
+	}
+	
+	return $name;
+};
+
+my $create_lun = sub {
+	my ($scfg, $timeout, $method, @params) = @_;
+	my $res = ();
+	my $file = "/tmp/config$$";
+
+	if ($list_lun->($scfg, $timeout, $method, @params)) {
+		die "$params[0]: LUN exists";
+	}
+	my $lun = $params[0];
+	$lun = $make_lun->($lun);
+	my $config = $lun_dumper->($SETTINGS->{current});
+	open(my $fh, '>', $file) or die "Could not open file '$file' $!";
+
+	print $fh $CONFIG;
+	print $fh $config;
+	close $fh;
+	@params = ($CONFIG_FILE);
+	$res = {
+		cmd => 'scp',
+		method => $file,
+		params => \@params,
+		msg => $lun,
+		post_exe => sub {
+			unlink $file;
+		},
+	};
+
+	return $res;
+};
+
+my $delete_lun = sub {
+	my ($scfg, $timeout, $method, @params) = @_;
+	my $res = ();
+	my $file = "/tmp/config$$";
+	
+	my $target = $SETTINGS->{current};
+	my $luns = ();
+
+	foreach my $conf (@{$SETTINGS->{$target}->{luns}}) {
+		if ($conf->{Storage} =~ /^$params[0]$/) {
+			$free_lu_name->($target, $conf->{lun});
+		} else {
+			push @$luns, $conf;
+		}
+	}
+	$SETTINGS->{$target}->{luns} = $luns;
+	
+	my $config = $lun_dumper->($SETTINGS->{current});
+	open(my $fh, '>', $file) or die "Could not open file '$file' $!";
+
+	print $fh $CONFIG;
+	print $fh $config;
+	close $fh;
+	@params = ($CONFIG_FILE);
+	$res = {
+		cmd => 'scp',
+		method => $file,
+		params => \@params,
+		post_exe => sub {
+			unlink $file;
+			run_lun_command($scfg, undef, 'add_view', 'restart');
+		},
+	};
+
+	return $res;
+};
+
+my $import_lun = sub {
+	my ($scfg, $timeout, $method, @params) = @_;
+
+	my $res = $create_lun->($scfg, $timeout, $method, @params);
+
+	return $res;
+};
+
+my $add_view = sub {
+	my ($scfg, $timeout, $method, @params) = @_;
+	my $cmdmap;
+	
+	if (@params && $params[0] eq 'restart') {
+		@params = ('restart', '1>&2', '>', '/dev/null');
+		$cmdmap = {
+			cmd => 'ssh',
+			method => $DAEMON,
+			params => \@params,
+		};
+	} else {
+		@params = ('-HUP', '$(cat '. "$SETTINGS->{pidfile})");
+		$cmdmap = {
+			cmd => 'ssh',
+			method => 'kill',
+			params => \@params,
+		};
+	}
+
+	return $cmdmap;
+};
+
+my $modify_lun = sub {
+	my ($scfg, $timeout, $method, @params) = @_;
+
+	# Current SIGHUP reload limitations
+	# LU connected by the initiator can't be reloaded by SIGHUP.
+	# Until above limitation persists modifying a LUN will require
+	# a restart of the daemon breaking all current connections
+	#die 'Modify a connected LUN is not currently supported by istgt';
+	@params = ('restart', @params);
+	
+	return $add_view->($scfg, $timeout, $method, @params);
+};
+
+my $list_view = sub {
+	my ($scfg, $timeout, $method, @params) = @_;
+	my $lun = undef;
+	
+	my $object = $params[0];
+	for my $key (keys %$SETTINGS)  {
+		next unless $key =~ /^LogicalUnit\d+$/;
+		foreach my $lun (@{$SETTINGS->{$key}->{luns}}) {
+			if ($lun->{Storage} =~ /^$object$/) {
+				if ($lun->{lun} =~ /^LUN(\d+)/) {
+					return $1;
+				}
+				die "$lun->{Storage}: Missing LUN";
+			}
+		}
+	}
+	
+	return $lun;
+};
+
+my $get_lun_cmd_map = sub {
+	my ($method) = @_;
+	
+	my $cmdmap = {
+	    create_lu	=> { cmd => $create_lun },
+	    delete_lu	=> { cmd => $delete_lun },
+	    import_lu	=> { cmd => $import_lun },
+	    modify_lu	=> { cmd => $modify_lun },
+	    add_view	=> { cmd => $add_view },
+	    list_view	=> { cmd => $list_view },
+	    list_lu		=> { cmd => $list_lun },
+	};
+	
+	die "unknown command '$method'" unless exists $cmdmap->{$method};
+	
+	return $cmdmap->{$method};
+};
+
+sub run_lun_command {
+	my ($scfg, $timeout, $method, @params) = @_;
+
+    my $msg = '';
+    my $luncmd;
+    my $target;
+	my $cmd;
+	my $res;
+    $timeout = 10 if !$timeout;
+	my $is_add_view = 0;
+	
+    my $output = sub {
+    my $line = shift;
+	$msg .= "$line\n";
+    };
+
+	$target = 'root@' . $scfg->{portal};
+
+	$parser->($scfg) unless $SETTINGS;
+	my $cmdmap = $get_lun_cmd_map->($method);
+	if ($method eq 'add_view') {
+		$is_add_view = 1 ;
+		$timeout = 15;
+	}
+	if (ref $cmdmap->{cmd} eq 'CODE') {
+		$res = $cmdmap->{cmd}->($scfg, $timeout, $method, @params);
+		if (ref $res) {
+			$method = $res->{method};
+			@params = @{$res->{params}};
+			if ($res->{cmd} eq 'scp') {
+				$cmd = [@scp_cmd, $method, "$target:$params[0]"];
+			} else {
+				$cmd = [@ssh_cmd, $target, $method, @params];
+			}
+		} else {
+			return $res;
+		}
+	} else {
+		$luncmd = $cmdmap->{cmd};
+		$method = $cmdmap->{method};
+		$cmd = [@ssh_cmd, $target, $luncmd, $method, @params];
+	}
+
+	eval {
+		run_command($cmd, outfunc => $output, timeout => $timeout);
+	};
+	if ($@ && $is_add_view) {
+		my $err = $@;
+		if ($OLD_CONFIG) {
+			my $err1 = undef;
+			my $file = "/tmp/config$$";
+			open(my $fh, '>', $file) or die "Could not open file '$file' $!";
+			print $fh $OLD_CONFIG;
+			close $fh;
+			$cmd = [@scp_cmd, $file, $CONFIG_FILE];
+			eval {
+				run_command($cmd, outfunc => $output, timeout => $timeout);
+			};
+			$err1 = $@ if $@;
+			unlink $file;
+			die "$err\n$err1" if $err1;
+			eval {
+				run_lun_command($scfg, undef, 'add_view', 'restart');
+			};
+			die "$err\n$@" if ($@);
+		}
+		die $err;
+	} elsif ($@) {
+		die $@;
+	} elsif ($is_add_view) {
+		$OLD_CONFIG = undef;
+	}
+	
+	if ($res->{post_exe} && ref $res->{post_exe} eq 'CODE') {
+		$res->{post_exe}->();
+	}
+
+	if ($res->{msg}) {
+		$msg = $res->{msg};
+	}
+	
+	return $msg;
+}
+
+sub get_base {
+	return '/dev/zvol';
+}
+
+1;
\ No newline at end of file
-- 
1.8.4.rc3




More information about the pve-devel mailing list