bump version to 7.0-13
[pve-storage.git] / PVE / Storage / LunCmd / Istgt.pm
CommitLineData
a62d1e99
MR
1package PVE::Storage::LunCmd::Istgt;
2
3# TODO
4# Create initial target and LUN if target is missing ?
5# Create and use list of free LUNs
6
7use strict;
8use warnings;
074b2cb4 9
a62d1e99 10use PVE::Tools qw(run_command file_read_firstline trim dir_glob_regex dir_glob_foreach);
a62d1e99
MR
11
12my @CONFIG_FILES = (
3b219e80
MR
13 '/usr/local/etc/istgt/istgt.conf', # FreeBSD, FreeNAS
14 '/var/etc/iscsi/istgt.conf' # NAS4Free
a62d1e99
MR
15);
16my @DAEMONS = (
3b219e80
MR
17 '/usr/local/etc/rc.d/istgt', # FreeBSD, FreeNAS
18 '/var/etc/rc.d/istgt' # NAS4Free
a62d1e99
MR
19);
20
21# A logical unit can max have 63 LUNs
22# https://code.google.com/p/istgt/source/browse/src/istgt_lu.h#39
23my $MAX_LUNS = 64;
24
25my $CONFIG_FILE = undef;
26my $DAEMON = undef;
27my $SETTINGS = undef;
28my $CONFIG = undef;
29my $OLD_CONFIG = undef;
30
31my @ssh_opts = ('-o', 'BatchMode=yes');
32my @ssh_cmd = ('/usr/bin/ssh', @ssh_opts);
33my @scp_cmd = ('/usr/bin/scp', @ssh_opts);
3b219e80 34my $id_rsa_path = '/etc/pve/priv/zfs';
a62d1e99
MR
35
36#Current SIGHUP reload limitations (http://www.peach.ne.jp/archives/istgt/):
37#
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.
3b219e80 42# Updating IG is not affected until the next login.
a62d1e99
MR
43#
44# FreeBSD
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)
58
59sub get_base;
60sub run_lun_command;
61
62my $read_config = sub {
3b219e80
MR
63 my ($scfg, $timeout, $method) = @_;
64
a62d1e99 65 my $msg = '';
3b219e80 66 my $err = undef;
a62d1e99
MR
67 my $luncmd = 'cat';
68 my $target;
69 $timeout = 10 if !$timeout;
3b219e80 70
a62d1e99
MR
71 my $output = sub {
72 my $line = shift;
3b219e80 73 $msg .= "$line\n";
a62d1e99 74 };
3b219e80
MR
75
76 my $errfunc = sub {
a62d1e99 77 my $line = shift;
3b219e80
MR
78 $err .= "$line";
79 };
80
81 $target = 'root@' . $scfg->{portal};
82
83 my $daemon = 0;
84 foreach my $config (@CONFIG_FILES) {
85 $err = undef;
86 my $cmd = [@ssh_cmd, '-i', "$id_rsa_path/$scfg->{portal}_id_rsa", $target, $luncmd, $config];
87 eval {
88 run_command($cmd, outfunc => $output, errfunc => $errfunc, timeout => $timeout);
89 };
90 do {
91 $err = undef;
92 $DAEMON = $DAEMONS[$daemon];
93 $CONFIG_FILE = $config;
94 last;
95 } unless $@;
96 $daemon++;
97 }
98 die $err if ($err && $err !~ /No such file or directory/);
99 die "No configuration found. Install istgt on $scfg->{portal}" if $msg eq '';
100
101 return $msg;
a62d1e99
MR
102};
103
104my $get_config = sub {
3b219e80
MR
105 my ($scfg) = @_;
106 my @conf = undef;
a62d1e99 107
3b219e80
MR
108 my $config = $read_config->($scfg, undef, 'get_config');
109 die "Missing config file" unless $config;
a62d1e99 110
3b219e80
MR
111 $OLD_CONFIG = $config;
112
113 return $config;
a62d1e99
MR
114};
115
116my $parse_size = sub {
117 my ($text) = @_;
118
119 return 0 if !$text;
120
121 if ($text =~ m/^(\d+(\.\d+)?)([TGMK]B)?$/) {
3b219e80
MR
122 my ($size, $reminder, $unit) = ($1, $2, $3);
123 return $size if !$unit;
124 if ($unit eq 'KB') {
125 $size *= 1024;
126 } elsif ($unit eq 'MB') {
127 $size *= 1024*1024;
128 } elsif ($unit eq 'GB') {
129 $size *= 1024*1024*1024;
130 } elsif ($unit eq 'TB') {
131 $size *= 1024*1024*1024*1024;
132 }
133 if ($reminder) {
134 $size = ceil($size);
135 }
136 return $size;
a62d1e99 137 } elsif ($text =~ /^auto$/i) {
3b219e80
MR
138 return 'AUTO';
139 } else {
140 return 0;
a62d1e99
MR
141 }
142};
143
144my $size_with_unit = sub {
145 my ($size, $n) = (shift, 0);
146
147 return '0KB' if !$size;
148
3b219e80
MR
149 return $size if $size eq 'AUTO';
150
a62d1e99 151 if ($size =~ m/^\d+$/) {
3b219e80
MR
152 ++$n and $size /= 1024 until $size < 1024;
153 if ($size =~ /\./) {
154 return sprintf "%.2f%s", $size, ( qw[bytes KB MB GB TB] )[ $n ];
155 } else {
156 return sprintf "%d%s", $size, ( qw[bytes KB MB GB TB] )[ $n ];
157 }
158 }
159 die "$size: Not a number";
a62d1e99
MR
160};
161
162my $lun_dumper = sub {
3b219e80
MR
163 my ($lun) = @_;
164 my $config = '';
165
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";
172
173 foreach my $conf (@{$SETTINGS->{$lun}->{luns}}) {
174 $config .= "$conf->{lun} Storage " . $conf->{Storage};
175 $config .= ' ' . $size_with_unit->($conf->{Size}) . "\n";
70986fd9
CA
176 foreach ($conf->{options}) {
177 if ($_) {
178 $config .= "$conf->{lun} Option " . $_ . "\n";
179 }
180 }
3b219e80
MR
181 }
182 $config .= "\n";
183
184 return $config;
a62d1e99
MR
185};
186
187my $get_lu_name = sub {
3b219e80
MR
188 my ($target) = @_;
189 my $used = ();
190 my $i;
191
192 if (! exists $SETTINGS->{$target}->{used}) {
193 for ($i = 0; $i < $MAX_LUNS; $i++) {
194 $used->{$i} = 0;
195 }
196 foreach my $lun (@{$SETTINGS->{$target}->{luns}}) {
197 $lun->{lun} =~ /^LUN(\d+)$/;
198 $used->{$1} = 1;
199 }
200 $SETTINGS->{$target}->{used} = $used;
201 }
202
203 $used = $SETTINGS->{$target}->{used};
204 for ($i = 0; $i < $MAX_LUNS; $i++) {
205 last unless $used->{$i};
206 }
207 $SETTINGS->{$target}->{used}->{$i} = 1;
208
209 return "LUN$i";
a62d1e99
MR
210};
211
212my $init_lu_name = sub {
3b219e80
MR
213 my ($target) = @_;
214 my $used = ();
215
216 if (! exists($SETTINGS->{$target}->{used})) {
217 for (my $i = 0; $i < $MAX_LUNS; $i++) {
218 $used->{$i} = 0;
219 }
220 $SETTINGS->{$target}->{used} = $used;
221 }
222 foreach my $lun (@{$SETTINGS->{$target}->{luns}}) {
223 $lun->{lun} =~ /^LUN(\d+)$/;
224 $SETTINGS->{$target}->{used}->{$1} = 1;
225 }
a62d1e99
MR
226};
227
228my $free_lu_name = sub {
3b219e80 229 my ($target, $lu_name) = @_;
a62d1e99 230
3b219e80
MR
231 $lu_name =~ /^LUN(\d+)$/;
232 $SETTINGS->{$target}->{used}->{$1} = 0;
a62d1e99
MR
233};
234
235my $make_lun = sub {
70986fd9 236 my ($scfg, $path) = @_;
3b219e80
MR
237
238 my $target = $SETTINGS->{current};
239 die 'Maximum number of LUNs per target is 63' if scalar @{$SETTINGS->{$target}->{luns}} >= $MAX_LUNS;
240
70986fd9 241 my @options = ();
3b219e80 242 my $lun = $get_lu_name->($target);
70986fd9 243 if ($scfg->{nowritecache}) {
b200cefd 244 push @options, "WriteCache Disable";
70986fd9 245 }
3b219e80
MR
246 my $conf = {
247 lun => $lun,
248 Storage => $path,
249 Size => 'AUTO',
70986fd9 250 options => @options,
3b219e80
MR
251 };
252 push @{$SETTINGS->{$target}->{luns}}, $conf;
253
254 return $conf->{lun};
a62d1e99
MR
255};
256
257my $parser = sub {
3b219e80
MR
258 my ($scfg) = @_;
259
260 my $lun = undef;
261 my $line = 0;
262
263 my $config = $get_config->($scfg);
264 my @cfgfile = split "\n", $config;
265
266 foreach (@cfgfile) {
267 $line++;
268 if ($_ =~ /^\s*\[(PortalGroup\d+)\]\s*/) {
269 $lun = undef;
270 $SETTINGS->{$1} = ();
271 } elsif ($_ =~ /^\s*\[(InitiatorGroup\d+)\]\s*/) {
272 $lun = undef;
273 $SETTINGS->{$1} = ();
274 } elsif ($_ =~ /^\s*PidFile\s+"?([\w\/\.]+)"?\s*/) {
275 $lun = undef;
276 $SETTINGS->{pidfile} = $1;
277 } elsif ($_ =~ /^\s*NodeBase\s+"?([\w\-\.]+)"?\s*/) {
278 $lun = undef;
279 $SETTINGS->{nodebase} = $1;
280 } elsif ($_ =~ /^\s*\[(LogicalUnit\d+)\]\s*/) {
281 $lun = $1;
282 $SETTINGS->{$lun} = ();
283 $SETTINGS->{targets}++;
284 } elsif ($lun) {
285 next if (($_ =~ /^\s*#/) || ($_ =~ /^\s*$/));
286 if ($_ =~ /^\s*(\w+)\s+(.+)\s*/) {
551534a3 287 my $arg1 = $1;
b200cefd
MR
288 my $arg2 = $2;
289 $arg2 =~ s/^\s+|\s+$|"\s*//g;
290 if ($arg2 =~ /^Storage\s*(.+)/i) {
551534a3 291 $SETTINGS->{$lun}->{$arg1}->{storage} = $1;
b200cefd 292 } elsif ($arg2 =~ /^Option\s*(.+)/i) {
551534a3
CA
293 push @{$SETTINGS->{$lun}->{$arg1}->{options}}, $1;
294 } else {
b200cefd 295 $SETTINGS->{$lun}->{$arg1} = $arg2;
551534a3 296 }
3b219e80
MR
297 } else {
298 die "$line: parse error [$_]";
299 }
300 }
301 $CONFIG .= "$_\n" unless $lun;
302 }
303
304 $CONFIG =~ s/\n$//;
305 die "$scfg->{target}: Target not found" unless $SETTINGS->{targets};
306 my $max = $SETTINGS->{targets};
307 my $base = get_base;
308
309 for (my $i = 1; $i <= $max; $i++) {
310 my $target = $SETTINGS->{nodebase}.':'.$SETTINGS->{"LogicalUnit$i"}->{TargetName};
311 if ($target eq $scfg->{target}) {
312 my $lu = ();
313 while ((my $key, my $val) = each(%{$SETTINGS->{"LogicalUnit$i"}})) {
314 if ($key =~ /^LUN\d+/) {
551534a3
CA
315 $val->{storage} =~ /^([\w\/\-]+)\s+(\w+)/;
316 my $storage = $1;
317 my $size = $parse_size->($2);
318 my $conf = undef;
319 my @options = ();
320 if ($val->{options}) {
321 @options = @{$val->{options}};
322 }
323 if ($storage =~ /^$base\/$scfg->{pool}\/([\w\-]+)$/) {
324 $conf = {
325 lun => $key,
326 Storage => $storage,
327 Size => $size,
328 options => @options,
3b219e80 329 }
3b219e80 330 }
551534a3 331 push @$lu, $conf if $conf;
3b219e80
MR
332 delete $SETTINGS->{"LogicalUnit$i"}->{$key};
333 }
334 }
335 $SETTINGS->{"LogicalUnit$i"}->{luns} = $lu;
336 $SETTINGS->{current} = "LogicalUnit$i";
337 $init_lu_name->("LogicalUnit$i");
338 } else {
339 $CONFIG .= $lun_dumper->("LogicalUnit$i");
340 delete $SETTINGS->{"LogicalUnit$i"};
341 $SETTINGS->{targets}--;
342 }
343 }
344 die "$scfg->{target}: Target not found" unless $SETTINGS->{targets} > 0;
a62d1e99
MR
345};
346
347my $list_lun = sub {
3b219e80
MR
348 my ($scfg, $timeout, $method, @params) = @_;
349 my $name = undef;
350
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};
357 }
358 }
359 }
360
361 return $name;
a62d1e99
MR
362};
363
364my $create_lun = sub {
3b219e80
MR
365 my ($scfg, $timeout, $method, @params) = @_;
366 my $res = ();
367 my $file = "/tmp/config$$";
368
369 if ($list_lun->($scfg, $timeout, $method, @params)) {
370 die "$params[0]: LUN exists";
371 }
372 my $lun = $params[0];
70986fd9 373 $lun = $make_lun->($scfg, $lun);
3b219e80
MR
374 my $config = $lun_dumper->($SETTINGS->{current});
375 open(my $fh, '>', $file) or die "Could not open file '$file' $!";
376
377 print $fh $CONFIG;
378 print $fh $config;
379 close $fh;
380 @params = ($CONFIG_FILE);
381 $res = {
382 cmd => 'scp',
383 method => $file,
384 params => \@params,
385 msg => $lun,
386 post_exe => sub {
387 unlink $file;
388 },
389 };
390
391 return $res;
a62d1e99
MR
392};
393
394my $delete_lun = sub {
3b219e80
MR
395 my ($scfg, $timeout, $method, @params) = @_;
396 my $res = ();
397 my $file = "/tmp/config$$";
398
399 my $target = $SETTINGS->{current};
400 my $luns = ();
401
402 foreach my $conf (@{$SETTINGS->{$target}->{luns}}) {
403 if ($conf->{Storage} =~ /^$params[0]$/) {
404 $free_lu_name->($target, $conf->{lun});
405 } else {
406 push @$luns, $conf;
407 }
408 }
409 $SETTINGS->{$target}->{luns} = $luns;
410
411 my $config = $lun_dumper->($SETTINGS->{current});
412 open(my $fh, '>', $file) or die "Could not open file '$file' $!";
413
414 print $fh $CONFIG;
415 print $fh $config;
416 close $fh;
417 @params = ($CONFIG_FILE);
418 $res = {
419 cmd => 'scp',
420 method => $file,
421 params => \@params,
422 post_exe => sub {
423 unlink $file;
424 run_lun_command($scfg, undef, 'add_view', 'restart');
425 },
426 };
427
428 return $res;
a62d1e99
MR
429};
430
431my $import_lun = sub {
3b219e80 432 my ($scfg, $timeout, $method, @params) = @_;
a62d1e99 433
3b219e80 434 my $res = $create_lun->($scfg, $timeout, $method, @params);
a62d1e99 435
3b219e80 436 return $res;
a62d1e99
MR
437};
438
439my $add_view = sub {
3b219e80
MR
440 my ($scfg, $timeout, $method, @params) = @_;
441 my $cmdmap;
442
443 if (@params && $params[0] eq 'restart') {
c521e801 444 @params = ('onerestart', '>&', '/dev/null');
3b219e80
MR
445 $cmdmap = {
446 cmd => 'ssh',
447 method => $DAEMON,
448 params => \@params,
449 };
450 } else {
c521e801 451 @params = ('-HUP', '`cat '. "$SETTINGS->{pidfile}`");
3b219e80
MR
452 $cmdmap = {
453 cmd => 'ssh',
454 method => 'kill',
455 params => \@params,
456 };
457 }
458
459 return $cmdmap;
a62d1e99
MR
460};
461
462my $modify_lun = sub {
3b219e80
MR
463 my ($scfg, $timeout, $method, @params) = @_;
464
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);
471
472 return $add_view->($scfg, $timeout, $method, @params);
a62d1e99
MR
473};
474
475my $list_view = sub {
3b219e80
MR
476 my ($scfg, $timeout, $method, @params) = @_;
477 my $lun = undef;
478
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+)/) {
485 return $1;
486 }
487 die "$lun->{Storage}: Missing LUN";
488 }
489 }
490 }
491
492 return $lun;
a62d1e99
MR
493};
494
495my $get_lun_cmd_map = sub {
3b219e80
MR
496 my ($method) = @_;
497
498 my $cmdmap = {
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 },
506 };
507
508 die "unknown command '$method'" unless exists $cmdmap->{$method};
509
510 return $cmdmap->{$method};
a62d1e99
MR
511};
512
513sub run_lun_command {
3b219e80 514 my ($scfg, $timeout, $method, @params) = @_;
a62d1e99
MR
515
516 my $msg = '';
517 my $luncmd;
518 my $target;
3b219e80
MR
519 my $cmd;
520 my $res;
a62d1e99 521 $timeout = 10 if !$timeout;
3b219e80
MR
522 my $is_add_view = 0;
523
a62d1e99
MR
524 my $output = sub {
525 my $line = shift;
3b219e80
MR
526 $msg .= "$line\n";
527 };
528
529 $target = 'root@' . $scfg->{portal};
530
531 $parser->($scfg) unless $SETTINGS;
532 my $cmdmap = $get_lun_cmd_map->($method);
533 if ($method eq 'add_view') {
534 $is_add_view = 1 ;
535 $timeout = 15;
536 }
537 if (ref $cmdmap->{cmd} eq 'CODE') {
538 $res = $cmdmap->{cmd}->($scfg, $timeout, $method, @params);
539 if (ref $res) {
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]"];
544 } else {
545 $cmd = [@ssh_cmd, '-i', "$id_rsa_path/$scfg->{portal}_id_rsa", $target, $method, @params];
546 }
547 } else {
548 return $res;
549 }
550 } else {
551 $luncmd = $cmdmap->{cmd};
552 $method = $cmdmap->{method};
553 $cmd = [@ssh_cmd, '-i', "$id_rsa_path/$scfg->{portal}_id_rsa", $target, $luncmd, $method, @params];
554 }
555
556 eval {
557 run_command($cmd, outfunc => $output, timeout => $timeout);
a62d1e99 558 };
3b219e80
MR
559 if ($@ && $is_add_view) {
560 my $err = $@;
561 if ($OLD_CONFIG) {
562 my $err1 = undef;
563 my $file = "/tmp/config$$";
564 open(my $fh, '>', $file) or die "Could not open file '$file' $!";
565 print $fh $OLD_CONFIG;
566 close $fh;
567 $cmd = [@scp_cmd, '-i', "$id_rsa_path/$scfg->{portal}_id_rsa", $file, $CONFIG_FILE];
568 eval {
569 run_command($cmd, outfunc => $output, timeout => $timeout);
570 };
571 $err1 = $@ if $@;
572 unlink $file;
573 die "$err\n$err1" if $err1;
574 eval {
575 run_lun_command($scfg, undef, 'add_view', 'restart');
576 };
577 die "$err\n$@" if ($@);
578 }
579 die $err;
580 } elsif ($@) {
581 die $@;
582 } elsif ($is_add_view) {
583 $OLD_CONFIG = undef;
584 }
585
586 if ($res->{post_exe} && ref $res->{post_exe} eq 'CODE') {
587 $res->{post_exe}->();
588 }
589
590 if ($res->{msg}) {
591 $msg = $res->{msg};
592 }
a62d1e99 593
3b219e80 594 return $msg;
a62d1e99
MR
595}
596
597sub get_base {
3b219e80 598 return '/dev/zvol';
a62d1e99
MR
599}
600
3b219e80 6011;