]>
git.proxmox.com Git - pve-storage.git/blob - PVE/Storage/LunCmd/Istgt.pm
1 package PVE
::Storage
::LunCmd
::Istgt
;
4 # Create initial target and LUN if target is missing ?
5 # Create and use list of free LUNs
10 use PVE
::Tools
qw(run_command file_read_firstline trim dir_glob_regex dir_glob_foreach);
13 '/usr/local/etc/istgt/istgt.conf', # FreeBSD, FreeNAS
14 '/var/etc/iscsi/istgt.conf' # NAS4Free
17 '/usr/local/etc/rc.d/istgt', # FreeBSD, FreeNAS
18 '/var/etc/rc.d/istgt' # NAS4Free
21 # A logical unit can max have 63 LUNs
22 # https://code.google.com/p/istgt/source/browse/src/istgt_lu.h#39
25 my $CONFIG_FILE = undef;
29 my $OLD_CONFIG = undef;
31 my @ssh_opts = ('-o', 'BatchMode=yes');
32 my @ssh_cmd = ('/usr/bin/ssh', @ssh_opts);
33 my @scp_cmd = ('/usr/bin/scp', @ssh_opts);
34 my $id_rsa_path = '/etc/pve/priv/zfs';
36 #Current SIGHUP reload limitations (http://www.peach.ne.jp/archives/istgt/):
38 # The parameters other than PG, IG, and LU are not reloaded by SIGHUP.
39 # LU connected by the initiator can't be reloaded by SIGHUP.
40 # PG and IG mapped to LU can't be deleted by SIGHUP.
41 # If you delete an active LU, all connections of the LU are closed by SIGHUP.
42 # Updating IG is not affected until the next login.
45 # 1. Alt-F2 to change to native shell (zfsguru)
46 # 2. pw mod user root -w yes (change password for root to root)
47 # 3. vi /etc/ssh/sshd_config
48 # 4. uncomment PermitRootLogin yes
49 # 5. change PasswordAuthentication no to PasswordAuthentication yes
50 # 5. /etc/rc.d/sshd restart
51 # 6. On one of the proxmox nodes login as root and run: ssh-copy-id ip_freebsd_host
52 # 7. vi /etc/ssh/sshd_config
53 # 8. comment PermitRootLogin yes
54 # 9. change PasswordAuthentication yes to PasswordAuthentication no
55 # 10. /etc/rc.d/sshd restart
56 # 11. Reset passwd -> pw mod user root -w no
57 # 12. Alt-Ctrl-F1 to return to zfsguru shell (zfsguru)
62 my $read_config = sub {
63 my ($scfg, $timeout, $method) = @_;
69 $timeout = 10 if !$timeout;
81 $target = 'root@' . $scfg->{portal
};
84 foreach my $config (@CONFIG_FILES) {
86 my $cmd = [@ssh_cmd, '-i', "$id_rsa_path/$scfg->{portal}_id_rsa", $target, $luncmd, $config];
88 run_command
($cmd, outfunc
=> $output, errfunc
=> $errfunc, timeout
=> $timeout);
92 $DAEMON = $DAEMONS[$daemon];
93 $CONFIG_FILE = $config;
98 die $err if ($err && $err !~ /No such file or directory/);
99 die "No configuration found. Install istgt on $scfg->{portal}" if $msg eq '';
104 my $get_config = sub {
108 my $config = $read_config->($scfg, undef, 'get_config');
109 die "Missing config file" unless $config;
111 $OLD_CONFIG = $config;
116 my $parse_size = sub {
121 if ($text =~ m/^(\d+(\.\d+)?)([TGMK]B)?$/) {
122 my ($size, $reminder, $unit) = ($1, $2, $3);
123 return $size if !$unit;
126 } elsif ($unit eq 'MB') {
128 } elsif ($unit eq 'GB') {
129 $size *= 1024*1024*1024;
130 } elsif ($unit eq 'TB') {
131 $size *= 1024*1024*1024*1024;
137 } elsif ($text =~ /^auto$/i) {
144 my $size_with_unit = sub {
145 my ($size, $n) = (shift, 0);
147 return '0KB' if !$size;
149 return $size if $size eq 'AUTO';
151 if ($size =~ m/^\d+$/) {
152 ++$n and $size /= 1024 until $size < 1024;
154 return sprintf "%.2f%s", $size, ( qw[bytes KB MB GB TB] )[ $n ];
156 return sprintf "%d%s", $size, ( qw[bytes KB MB GB TB] )[ $n ];
159 die "$size: Not a number";
162 my $lun_dumper = sub {
166 $config .= "\n[$lun]\n";
167 $config .= 'TargetName ' . $SETTINGS->{$lun}->{TargetName
} . "\n";
168 $config .= 'Mapping ' . $SETTINGS->{$lun}->{Mapping
} . "\n";
169 $config .= 'AuthGroup ' . $SETTINGS->{$lun}->{AuthGroup
} . "\n";
170 $config .= 'UnitType ' . $SETTINGS->{$lun}->{UnitType
} . "\n";
171 $config .= 'QueueDepth ' . $SETTINGS->{$lun}->{QueueDepth
} . "\n";
173 foreach my $conf (@{$SETTINGS->{$lun}->{luns
}}) {
174 $config .= "$conf->{lun} Storage " . $conf->{Storage
};
175 $config .= ' ' . $size_with_unit->($conf->{Size
}) . "\n";
176 foreach ($conf->{options
}) {
178 $config .= "$conf->{lun} Option " . $_ . "\n";
187 my $get_lu_name = sub {
192 if (! exists $SETTINGS->{$target}->{used
}) {
193 for ($i = 0; $i < $MAX_LUNS; $i++) {
196 foreach my $lun (@{$SETTINGS->{$target}->{luns
}}) {
197 $lun->{lun
} =~ /^LUN(\d+)$/;
200 $SETTINGS->{$target}->{used
} = $used;
203 $used = $SETTINGS->{$target}->{used
};
204 for ($i = 0; $i < $MAX_LUNS; $i++) {
205 last unless $used->{$i};
207 $SETTINGS->{$target}->{used
}->{$i} = 1;
212 my $init_lu_name = sub {
216 if (! exists($SETTINGS->{$target}->{used
})) {
217 for (my $i = 0; $i < $MAX_LUNS; $i++) {
220 $SETTINGS->{$target}->{used
} = $used;
222 foreach my $lun (@{$SETTINGS->{$target}->{luns
}}) {
223 $lun->{lun
} =~ /^LUN(\d+)$/;
224 $SETTINGS->{$target}->{used
}->{$1} = 1;
228 my $free_lu_name = sub {
229 my ($target, $lu_name) = @_;
231 $lu_name =~ /^LUN(\d+)$/;
232 $SETTINGS->{$target}->{used
}->{$1} = 0;
236 my ($scfg, $path) = @_;
238 my $target = $SETTINGS->{current
};
239 die 'Maximum number of LUNs per target is 63' if scalar @{$SETTINGS->{$target}->{luns
}} >= $MAX_LUNS;
242 my $lun = $get_lu_name->($target);
243 if ($scfg->{nowritecache
}) {
244 push @options, "WriteCache Disable";
252 push @{$SETTINGS->{$target}->{luns
}}, $conf;
263 my $config = $get_config->($scfg);
264 my @cfgfile = split "\n", $config;
268 if ($_ =~ /^\s*\[(PortalGroup\d+)\]\s*/) {
270 $SETTINGS->{$1} = ();
271 } elsif ($_ =~ /^\s*\[(InitiatorGroup\d+)\]\s*/) {
273 $SETTINGS->{$1} = ();
274 } elsif ($_ =~ /^\s*PidFile\s+"?([\w\/\
.]+)"?\s*/) {
276 $SETTINGS->{pidfile} = $1;
277 } elsif ($_ =~ /^\s*NodeBase\s+"?
([\w\
-\
.]+)"?\s*/) {
279 $SETTINGS->{nodebase} = $1;
280 } elsif ($_ =~ /^\s*\[(LogicalUnit\d+)\]\s*/) {
282 $SETTINGS->{$lun} = ();
283 $SETTINGS->{targets}++;
285 next if (($_ =~ /^\s*#/) || ($_ =~ /^\s*$/));
286 if ($_ =~ /^\s*(\w+)\s+(.+)\s*/) {
289 $arg2 =~ s/^\s+|\s+$|"\s*//g;
290 if ($arg2 =~ /^Storage\s*(.+)/i) {
291 $SETTINGS->{$lun}->{$arg1}->{storage} = $1;
292 } elsif ($arg2 =~ /^Option\s*(.+)/i) {
293 push @{$SETTINGS->{$lun}->{$arg1}->{options}}, $1;
295 $SETTINGS->{$lun}->{$arg1} = $arg2;
298 die "$line: parse error
[$_]";
301 $CONFIG .= "$_\n" unless $lun;
305 die "$scfg->{target
}: Target
not found
" unless $SETTINGS->{targets};
306 my $max = $SETTINGS->{targets};
309 for (my $i = 1; $i <= $max; $i++) {
310 my $target = $SETTINGS->{nodebase}.':'.$SETTINGS->{"LogicalUnit
$i"}->{TargetName};
311 if ($target eq $scfg->{target}) {
313 while ((my $key, my $val) = each(%{$SETTINGS->{"LogicalUnit
$i"}})) {
314 if ($key =~ /^LUN\d+/) {
315 $val->{storage} =~ /^([\w\/\-]+)\s+(\w+)/;
317 my $size = $parse_size->($2);
320 if ($val->{options}) {
321 @options = @{$val->{options}};
323 if ($storage =~ /^$base\/$scfg->{pool}\/([\w\-]+)$/) {
331 push @$lu, $conf if $conf;
332 delete $SETTINGS->{"LogicalUnit
$i"}->{$key};
335 $SETTINGS->{"LogicalUnit
$i"}->{luns} = $lu;
336 $SETTINGS->{current} = "LogicalUnit
$i";
337 $init_lu_name->("LogicalUnit
$i");
339 $CONFIG .= $lun_dumper->("LogicalUnit
$i");
340 delete $SETTINGS->{"LogicalUnit
$i"};
341 $SETTINGS->{targets}--;
344 die "$scfg->{target
}: Target
not found
" unless $SETTINGS->{targets} > 0;
348 my ($scfg, $timeout, $method, @params) = @_;
351 my $object = $params[0];
352 for my $key (keys %$SETTINGS) {
353 next unless $key =~ /^LogicalUnit\d+$/;
354 foreach my $lun (@{$SETTINGS->{$key}->{luns}}) {
355 if ($lun->{Storage} =~ /^$object$/) {
356 return $lun->{Storage};
364 my $create_lun = sub {
365 my ($scfg, $timeout, $method, @params) = @_;
367 my $file = "/tmp/config
$$";
369 if ($list_lun->($scfg, $timeout, $method, @params)) {
370 die "$params[0]: LUN
exists";
372 my $lun = $params[0];
373 $lun = $make_lun->($scfg, $lun);
374 my $config = $lun_dumper->($SETTINGS->{current});
375 open(my $fh, '>', $file) or die "Could
not open file
'$file' $!";
380 @params = ($CONFIG_FILE);
394 my $delete_lun = sub {
395 my ($scfg, $timeout, $method, @params) = @_;
397 my $file = "/tmp/config
$$";
399 my $target = $SETTINGS->{current};
402 foreach my $conf (@{$SETTINGS->{$target}->{luns}}) {
403 if ($conf->{Storage} =~ /^$params[0]$/) {
404 $free_lu_name->($target, $conf->{lun});
409 $SETTINGS->{$target}->{luns} = $luns;
411 my $config = $lun_dumper->($SETTINGS->{current});
412 open(my $fh, '>', $file) or die "Could
not open file
'$file' $!";
417 @params = ($CONFIG_FILE);
424 run_lun_command($scfg, undef, 'add_view', 'restart');
431 my $import_lun = sub {
432 my ($scfg, $timeout, $method, @params) = @_;
434 my $res = $create_lun->($scfg, $timeout, $method, @params);
440 my ($scfg, $timeout, $method, @params) = @_;
443 if (@params && $params[0] eq 'restart') {
444 @params = ('onerestart', '>&', '/dev/null');
451 @params = ('-HUP', '`cat '. "$SETTINGS->{pidfile
}`");
462 my $modify_lun = sub {
463 my ($scfg, $timeout, $method, @params) = @_;
465 # Current SIGHUP reload limitations
466 # LU connected by the initiator can't be reloaded by SIGHUP.
467 # Until above limitation persists modifying a LUN will require
468 # a restart of the daemon breaking all current connections
469 #die 'Modify a connected LUN is not currently supported by istgt';
470 @params = ('restart', @params);
472 return $add_view->($scfg, $timeout, $method, @params);
475 my $list_view = sub {
476 my ($scfg, $timeout, $method, @params) = @_;
479 my $object = $params[0];
480 for my $key (keys %$SETTINGS) {
481 next unless $key =~ /^LogicalUnit\d+$/;
482 foreach my $lun (@{$SETTINGS->{$key}->{luns}}) {
483 if ($lun->{Storage} =~ /^$object$/) {
484 if ($lun->{lun} =~ /^LUN(\d+)/) {
487 die "$lun->{Storage}: Missing LUN";
495 my $get_lun_cmd_map = sub {
499 create_lu => { cmd => $create_lun },
500 delete_lu => { cmd => $delete_lun },
501 import_lu => { cmd => $import_lun },
502 modify_lu => { cmd => $modify_lun },
503 add_view => { cmd => $add_view },
504 list_view => { cmd => $list_view },
505 list_lu => { cmd => $list_lun },
508 die "unknown command '$method'" unless exists $cmdmap->{$method};
510 return $cmdmap->{$method};
513 sub run_lun_command {
514 my ($scfg, $timeout, $method, @params) = @_;
521 $timeout = 10 if !$timeout;
529 $target = 'root@' . $scfg->{portal};
531 $parser->($scfg) unless $SETTINGS;
532 my $cmdmap = $get_lun_cmd_map->($method);
533 if ($method eq 'add_view') {
537 if (ref $cmdmap->{cmd} eq 'CODE') {
538 $res = $cmdmap->{cmd}->($scfg, $timeout, $method, @params);
540 $method = $res->{method};
541 @params = @{$res->{params}};
542 if ($res->{cmd} eq 'scp') {
543 $cmd = [@scp_cmd, '-i', "$id_rsa_path/$scfg->{portal}_id_rsa", $method, "$target:$params[0]"];
545 $cmd = [@ssh_cmd, '-i', "$id_rsa_path/$scfg->{portal}_id_rsa", $target, $method, @params];
551 $luncmd = $cmdmap->{cmd};
552 $method = $cmdmap->{method};
553 $cmd = [@ssh_cmd, '-i', "$id_rsa_path/$scfg->{portal}_id_rsa", $target, $luncmd, $method, @params];
557 run_command($cmd, outfunc => $output, timeout => $timeout);
559 if ($@ && $is_add_view) {
563 my $file = "/tmp/config$$";
564 open(my $fh, '>', $file) or die "Could not open file '$file' $!";
565 print $fh $OLD_CONFIG;
567 $cmd = [@scp_cmd, '-i', "$id_rsa_path/$scfg->{portal}_id_rsa", $file, $CONFIG_FILE];
569 run_command($cmd, outfunc => $output, timeout => $timeout);
573 die "$err\n$err1" if $err1;
575 run_lun_command($scfg, undef, 'add_view', 'restart');
577 die "$err\n$@" if ($@);
582 } elsif ($is_add_view) {
586 if ($res->{post_exe} && ref $res->{post_exe} eq 'CODE') {
587 $res->{post_exe}->();