]> git.proxmox.com Git - pve-storage.git/blame - PVE/Storage/LunCmd/Istgt.pm
Fixed command substitution and output redirection in Istgt module to work in csh...
[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;
9use PVE::Tools qw(run_command file_read_firstline trim dir_glob_regex dir_glob_foreach);
10use Data::Dumper;
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
CA
243 if ($scfg->{nowritecache}) {
244 push @options, "WriteCache Disable";
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*/) {
70986fd9 287 next if $2 =~ /^Option.*/;
3b219e80
MR
288 $SETTINGS->{$lun}->{$1} = $2;
289 $SETTINGS->{$lun}->{$1} =~ s/^\s+|\s+$|"\s*//g;
290 } else {
291 die "$line: parse error [$_]";
292 }
293 }
294 $CONFIG .= "$_\n" unless $lun;
295 }
296
297 $CONFIG =~ s/\n$//;
298 die "$scfg->{target}: Target not found" unless $SETTINGS->{targets};
299 my $max = $SETTINGS->{targets};
300 my $base = get_base;
301
302 for (my $i = 1; $i <= $max; $i++) {
303 my $target = $SETTINGS->{nodebase}.':'.$SETTINGS->{"LogicalUnit$i"}->{TargetName};
304 if ($target eq $scfg->{target}) {
305 my $lu = ();
306 while ((my $key, my $val) = each(%{$SETTINGS->{"LogicalUnit$i"}})) {
307 if ($key =~ /^LUN\d+/) {
308 if ($val =~ /^Storage\s+([\w\/\-]+)\s+(\w+)/) {
309 my $storage = $1;
310 my $size = $parse_size->($2);
311 my $conf = undef;
312 if ($storage =~ /^$base\/$scfg->{pool}\/([\w\-]+)$/) {
313 $conf = {
314 lun => $key,
315 Storage => $storage,
316 Size => $size,
317 };
318 }
319 push @$lu, $conf if $conf;
320 }
321 delete $SETTINGS->{"LogicalUnit$i"}->{$key};
322 }
323 }
324 $SETTINGS->{"LogicalUnit$i"}->{luns} = $lu;
325 $SETTINGS->{current} = "LogicalUnit$i";
326 $init_lu_name->("LogicalUnit$i");
327 } else {
328 $CONFIG .= $lun_dumper->("LogicalUnit$i");
329 delete $SETTINGS->{"LogicalUnit$i"};
330 $SETTINGS->{targets}--;
331 }
332 }
333 die "$scfg->{target}: Target not found" unless $SETTINGS->{targets} > 0;
a62d1e99
MR
334};
335
336my $list_lun = sub {
3b219e80
MR
337 my ($scfg, $timeout, $method, @params) = @_;
338 my $name = undef;
339
340 my $object = $params[0];
341 for my $key (keys %$SETTINGS) {
342 next unless $key =~ /^LogicalUnit\d+$/;
343 foreach my $lun (@{$SETTINGS->{$key}->{luns}}) {
344 if ($lun->{Storage} =~ /^$object$/) {
345 return $lun->{Storage};
346 }
347 }
348 }
349
350 return $name;
a62d1e99
MR
351};
352
353my $create_lun = sub {
3b219e80
MR
354 my ($scfg, $timeout, $method, @params) = @_;
355 my $res = ();
356 my $file = "/tmp/config$$";
357
358 if ($list_lun->($scfg, $timeout, $method, @params)) {
359 die "$params[0]: LUN exists";
360 }
361 my $lun = $params[0];
70986fd9 362 $lun = $make_lun->($scfg, $lun);
3b219e80
MR
363 my $config = $lun_dumper->($SETTINGS->{current});
364 open(my $fh, '>', $file) or die "Could not open file '$file' $!";
365
366 print $fh $CONFIG;
367 print $fh $config;
368 close $fh;
369 @params = ($CONFIG_FILE);
370 $res = {
371 cmd => 'scp',
372 method => $file,
373 params => \@params,
374 msg => $lun,
375 post_exe => sub {
376 unlink $file;
377 },
378 };
379
380 return $res;
a62d1e99
MR
381};
382
383my $delete_lun = sub {
3b219e80
MR
384 my ($scfg, $timeout, $method, @params) = @_;
385 my $res = ();
386 my $file = "/tmp/config$$";
387
388 my $target = $SETTINGS->{current};
389 my $luns = ();
390
391 foreach my $conf (@{$SETTINGS->{$target}->{luns}}) {
392 if ($conf->{Storage} =~ /^$params[0]$/) {
393 $free_lu_name->($target, $conf->{lun});
394 } else {
395 push @$luns, $conf;
396 }
397 }
398 $SETTINGS->{$target}->{luns} = $luns;
399
400 my $config = $lun_dumper->($SETTINGS->{current});
401 open(my $fh, '>', $file) or die "Could not open file '$file' $!";
402
403 print $fh $CONFIG;
404 print $fh $config;
405 close $fh;
406 @params = ($CONFIG_FILE);
407 $res = {
408 cmd => 'scp',
409 method => $file,
410 params => \@params,
411 post_exe => sub {
412 unlink $file;
413 run_lun_command($scfg, undef, 'add_view', 'restart');
414 },
415 };
416
417 return $res;
a62d1e99
MR
418};
419
420my $import_lun = sub {
3b219e80 421 my ($scfg, $timeout, $method, @params) = @_;
a62d1e99 422
3b219e80 423 my $res = $create_lun->($scfg, $timeout, $method, @params);
a62d1e99 424
3b219e80 425 return $res;
a62d1e99
MR
426};
427
428my $add_view = sub {
3b219e80
MR
429 my ($scfg, $timeout, $method, @params) = @_;
430 my $cmdmap;
431
432 if (@params && $params[0] eq 'restart') {
c521e801 433 @params = ('onerestart', '>&', '/dev/null');
3b219e80
MR
434 $cmdmap = {
435 cmd => 'ssh',
436 method => $DAEMON,
437 params => \@params,
438 };
439 } else {
c521e801 440 @params = ('-HUP', '`cat '. "$SETTINGS->{pidfile}`");
3b219e80
MR
441 $cmdmap = {
442 cmd => 'ssh',
443 method => 'kill',
444 params => \@params,
445 };
446 }
447
448 return $cmdmap;
a62d1e99
MR
449};
450
451my $modify_lun = sub {
3b219e80
MR
452 my ($scfg, $timeout, $method, @params) = @_;
453
454 # Current SIGHUP reload limitations
455 # LU connected by the initiator can't be reloaded by SIGHUP.
456 # Until above limitation persists modifying a LUN will require
457 # a restart of the daemon breaking all current connections
458 #die 'Modify a connected LUN is not currently supported by istgt';
459 @params = ('restart', @params);
460
461 return $add_view->($scfg, $timeout, $method, @params);
a62d1e99
MR
462};
463
464my $list_view = sub {
3b219e80
MR
465 my ($scfg, $timeout, $method, @params) = @_;
466 my $lun = undef;
467
468 my $object = $params[0];
469 for my $key (keys %$SETTINGS) {
470 next unless $key =~ /^LogicalUnit\d+$/;
471 foreach my $lun (@{$SETTINGS->{$key}->{luns}}) {
472 if ($lun->{Storage} =~ /^$object$/) {
473 if ($lun->{lun} =~ /^LUN(\d+)/) {
474 return $1;
475 }
476 die "$lun->{Storage}: Missing LUN";
477 }
478 }
479 }
480
481 return $lun;
a62d1e99
MR
482};
483
484my $get_lun_cmd_map = sub {
3b219e80
MR
485 my ($method) = @_;
486
487 my $cmdmap = {
488 create_lu => { cmd => $create_lun },
489 delete_lu => { cmd => $delete_lun },
490 import_lu => { cmd => $import_lun },
491 modify_lu => { cmd => $modify_lun },
492 add_view => { cmd => $add_view },
493 list_view => { cmd => $list_view },
494 list_lu => { cmd => $list_lun },
495 };
496
497 die "unknown command '$method'" unless exists $cmdmap->{$method};
498
499 return $cmdmap->{$method};
a62d1e99
MR
500};
501
502sub run_lun_command {
3b219e80 503 my ($scfg, $timeout, $method, @params) = @_;
a62d1e99
MR
504
505 my $msg = '';
506 my $luncmd;
507 my $target;
3b219e80
MR
508 my $cmd;
509 my $res;
a62d1e99 510 $timeout = 10 if !$timeout;
3b219e80
MR
511 my $is_add_view = 0;
512
a62d1e99
MR
513 my $output = sub {
514 my $line = shift;
3b219e80
MR
515 $msg .= "$line\n";
516 };
517
518 $target = 'root@' . $scfg->{portal};
519
520 $parser->($scfg) unless $SETTINGS;
521 my $cmdmap = $get_lun_cmd_map->($method);
522 if ($method eq 'add_view') {
523 $is_add_view = 1 ;
524 $timeout = 15;
525 }
526 if (ref $cmdmap->{cmd} eq 'CODE') {
527 $res = $cmdmap->{cmd}->($scfg, $timeout, $method, @params);
528 if (ref $res) {
529 $method = $res->{method};
530 @params = @{$res->{params}};
531 if ($res->{cmd} eq 'scp') {
532 $cmd = [@scp_cmd, '-i', "$id_rsa_path/$scfg->{portal}_id_rsa", $method, "$target:$params[0]"];
533 } else {
534 $cmd = [@ssh_cmd, '-i', "$id_rsa_path/$scfg->{portal}_id_rsa", $target, $method, @params];
535 }
536 } else {
537 return $res;
538 }
539 } else {
540 $luncmd = $cmdmap->{cmd};
541 $method = $cmdmap->{method};
542 $cmd = [@ssh_cmd, '-i', "$id_rsa_path/$scfg->{portal}_id_rsa", $target, $luncmd, $method, @params];
543 }
544
545 eval {
546 run_command($cmd, outfunc => $output, timeout => $timeout);
a62d1e99 547 };
3b219e80
MR
548 if ($@ && $is_add_view) {
549 my $err = $@;
550 if ($OLD_CONFIG) {
551 my $err1 = undef;
552 my $file = "/tmp/config$$";
553 open(my $fh, '>', $file) or die "Could not open file '$file' $!";
554 print $fh $OLD_CONFIG;
555 close $fh;
556 $cmd = [@scp_cmd, '-i', "$id_rsa_path/$scfg->{portal}_id_rsa", $file, $CONFIG_FILE];
557 eval {
558 run_command($cmd, outfunc => $output, timeout => $timeout);
559 };
560 $err1 = $@ if $@;
561 unlink $file;
562 die "$err\n$err1" if $err1;
563 eval {
564 run_lun_command($scfg, undef, 'add_view', 'restart');
565 };
566 die "$err\n$@" if ($@);
567 }
568 die $err;
569 } elsif ($@) {
570 die $@;
571 } elsif ($is_add_view) {
572 $OLD_CONFIG = undef;
573 }
574
575 if ($res->{post_exe} && ref $res->{post_exe} eq 'CODE') {
576 $res->{post_exe}->();
577 }
578
579 if ($res->{msg}) {
580 $msg = $res->{msg};
581 }
a62d1e99 582
3b219e80 583 return $msg;
a62d1e99
MR
584}
585
586sub get_base {
3b219e80 587 return '/dev/zvol';
a62d1e99
MR
588}
589
3b219e80 5901;