]> git.proxmox.com Git - pve-storage.git/blame - PVE/Storage/LunCmd/Istgt.pm
Code clean up. Fix wrong indentation.
[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 = (
13 '/usr/local/etc/istgt/istgt.conf', # FreeBSD, FreeNAS
14 '/var/etc/iscsi/istgt.conf' # NAS4Free
15);
16my @DAEMONS = (
17 '/usr/local/etc/rc.d/istgt', # FreeBSD, FreeNAS
18 '/var/etc/rc.d/istgt' # NAS4Free
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);
34
35#Current SIGHUP reload limitations (http://www.peach.ne.jp/archives/istgt/):
36#
37# The parameters other than PG, IG, and LU are not reloaded by SIGHUP.
38# LU connected by the initiator can't be reloaded by SIGHUP.
39# PG and IG mapped to LU can't be deleted by SIGHUP.
40# If you delete an active LU, all connections of the LU are closed by SIGHUP.
41# Updating IG is not affected until the next login.
42#
43# FreeBSD
44# 1. Alt-F2 to change to native shell (zfsguru)
45# 2. pw mod user root -w yes (change password for root to root)
46# 3. vi /etc/ssh/sshd_config
47# 4. uncomment PermitRootLogin yes
48# 5. change PasswordAuthentication no to PasswordAuthentication yes
49# 5. /etc/rc.d/sshd restart
50# 6. On one of the proxmox nodes login as root and run: ssh-copy-id ip_freebsd_host
51# 7. vi /etc/ssh/sshd_config
52# 8. comment PermitRootLogin yes
53# 9. change PasswordAuthentication yes to PasswordAuthentication no
54# 10. /etc/rc.d/sshd restart
55# 11. Reset passwd -> pw mod user root -w no
56# 12. Alt-Ctrl-F1 to return to zfsguru shell (zfsguru)
57
58sub get_base;
59sub run_lun_command;
60
61my $read_config = sub {
62 my ($scfg, $timeout, $method) = @_;
63
64 my $msg = '';
65 my $err = undef;
66 my $luncmd = 'cat';
67 my $target;
68 $timeout = 10 if !$timeout;
69
70 my $output = sub {
71 my $line = shift;
72 $msg .= "$line\n";
73 };
74
75 my $errfunc = sub {
76 my $line = shift;
77 $err .= "$line";
78 };
79
80 $target = 'root@' . $scfg->{portal};
81
82 my $daemon = 0;
83 foreach my $config (@CONFIG_FILES) {
84 $err = undef;
85 my $cmd = [@ssh_cmd, $target, $luncmd, $config];
86 eval {
87 run_command($cmd, outfunc => $output, errfunc => $errfunc, timeout => $timeout);
88 };
89 do {
90 $err = undef;
91 $DAEMON = $DAEMONS[$daemon];
92 $CONFIG_FILE = $config;
93 last;
94 } unless $@;
95 $daemon++;
96 }
97 die $err if ($err && $err !~ /No such file or directory/);
98 die "No configuration found. Install istgt on $scfg->{portal}" if $msg eq '';
99
100 return $msg;
101};
102
103my $get_config = sub {
104 my ($scfg) = @_;
105 my @conf = undef;
106
107 my $config = $read_config->($scfg, undef, 'get_config');
108 die "Missing config file" unless $config;
109
110 $OLD_CONFIG = $config;
111
112 return $config;
113};
114
115my $parse_size = sub {
116 my ($text) = @_;
117
118 return 0 if !$text;
119
120 if ($text =~ m/^(\d+(\.\d+)?)([TGMK]B)?$/) {
121 my ($size, $reminder, $unit) = ($1, $2, $3);
122 return $size if !$unit;
123 if ($unit eq 'KB') {
124 $size *= 1024;
125 } elsif ($unit eq 'MB') {
126 $size *= 1024*1024;
127 } elsif ($unit eq 'GB') {
128 $size *= 1024*1024*1024;
129 } elsif ($unit eq 'TB') {
130 $size *= 1024*1024*1024*1024;
131 }
132 if ($reminder) {
133 $size = ceil($size);
134 }
135 return $size;
136 } elsif ($text =~ /^auto$/i) {
137 return 'AUTO';
138 } else {
139 return 0;
140 }
141};
142
143my $size_with_unit = sub {
144 my ($size, $n) = (shift, 0);
145
146 return '0KB' if !$size;
147
148 return $size if $size eq 'AUTO';
149
150 if ($size =~ m/^\d+$/) {
151 ++$n and $size /= 1024 until $size < 1024;
152 if ($size =~ /\./) {
153 return sprintf "%.2f%s", $size, ( qw[bytes KB MB GB TB] )[ $n ];
154 } else {
155 return sprintf "%d%s", $size, ( qw[bytes KB MB GB TB] )[ $n ];
156 }
157 }
158 die "$size: Not a number";
159};
160
161my $lun_dumper = sub {
162 my ($lun) = @_;
163 my $config = '';
164
165 $config .= "\n[$lun]\n";
166 $config .= 'TargetName ' . $SETTINGS->{$lun}->{TargetName} . "\n";
167 $config .= 'Mapping ' . $SETTINGS->{$lun}->{Mapping} . "\n";
168 $config .= 'AuthGroup ' . $SETTINGS->{$lun}->{AuthGroup} . "\n";
169 $config .= 'UnitType ' . $SETTINGS->{$lun}->{UnitType} . "\n";
170 $config .= 'QueueDepth ' . $SETTINGS->{$lun}->{QueueDepth} . "\n";
171
172 foreach my $conf (@{$SETTINGS->{$lun}->{luns}}) {
173 $config .= "$conf->{lun} Storage " . $conf->{Storage};
174 $config .= ' ' . $size_with_unit->($conf->{Size}) . "\n";
175 }
176 $config .= "\n";
177
178 return $config;
179};
180
181my $get_lu_name = sub {
182 my ($target) = @_;
183 my $used = ();
184 my $i;
185
186 if (! exists $SETTINGS->{$target}->{used}) {
187 for ($i = 0; $i < $MAX_LUNS; $i++) {
188 $used->{$i} = 0;
189 }
190 foreach my $lun (@{$SETTINGS->{$target}->{luns}}) {
191 $lun->{lun} =~ /^LUN(\d+)$/;
192 $used->{$1} = 1;
193 }
194 $SETTINGS->{$target}->{used} = $used;
195 }
196
197 $used = $SETTINGS->{$target}->{used};
198 for ($i = 0; $i < $MAX_LUNS; $i++) {
199 last unless $used->{$i};
200 }
201 $SETTINGS->{$target}->{used}->{$i} = 1;
202
203 return "LUN$i";
204};
205
206my $init_lu_name = sub {
207 my ($target) = @_;
208 my $used = ();
209
210 if (! exists($SETTINGS->{$target}->{used})) {
211 for (my $i = 0; $i < $MAX_LUNS; $i++) {
212 $used->{$i} = 0;
213 }
214 $SETTINGS->{$target}->{used} = $used;
215 }
216 foreach my $lun (@{$SETTINGS->{$target}->{luns}}) {
217 $lun->{lun} =~ /^LUN(\d+)$/;
218 $SETTINGS->{$target}->{used}->{$1} = 1;
219 }
220};
221
222my $free_lu_name = sub {
223 my ($target, $lu_name) = @_;
a62d1e99
MR
224
225 $lu_name =~ /^LUN(\d+)$/;
226 $SETTINGS->{$target}->{used}->{$1} = 0;
227};
228
229my $make_lun = sub {
230 my ($path) = @_;
231
232 my $target = $SETTINGS->{current};
233 die 'Maximum number of LUNs per target is 63' if scalar @{$SETTINGS->{$target}->{luns}} >= $MAX_LUNS;
234
235 my $lun = $get_lu_name->($target);
236 my $conf = {
237 lun => $lun,
238 Storage => $path,
239 Size => 'AUTO',
240 };
241 push @{$SETTINGS->{$target}->{luns}}, $conf;
242
243 return $conf->{lun};
244};
245
246my $parser = sub {
247 my ($scfg) = @_;
248
249 my $lun = undef;
250 my $line = 0;
251
252 my $config = $get_config->($scfg);
253 my @cfgfile = split "\n", $config;
254
255 foreach (@cfgfile) {
256 $line++;
257 if ($_ =~ /^\s*\[(PortalGroup\d+)\]\s*/) {
258 $lun = undef;
259 $SETTINGS->{$1} = ();
260 } elsif ($_ =~ /^\s*\[(InitiatorGroup\d+)\]\s*/) {
261 $lun = undef;
262 $SETTINGS->{$1} = ();
263 } elsif ($_ =~ /^\s*PidFile\s+"?([\w\/\.]+)"?\s*/) {
264 $lun = undef;
265 $SETTINGS->{pidfile} = $1;
266 } elsif ($_ =~ /^\s*NodeBase\s+"?([\w\-\.]+)"?\s*/) {
267 $lun = undef;
268 $SETTINGS->{nodebase} = $1;
269 } elsif ($_ =~ /^\s*\[(LogicalUnit\d+)\]\s*/) {
270 $lun = $1;
271 $SETTINGS->{$lun} = ();
272 $SETTINGS->{targets}++;
273 } elsif ($lun) {
274 next if (($_ =~ /^\s*#/) || ($_ =~ /^\s*$/));
275 if ($_ =~ /^\s*(\w+)\s+(.+)\s*/) {
276 #next if $2 =~ /^Option.*/;
277 $SETTINGS->{$lun}->{$1} = $2;
278 $SETTINGS->{$lun}->{$1} =~ s/^\s+|\s+$|"\s*//g;
279 } else {
280 die "$line: parse error [$_]";
281 }
282 }
283 $CONFIG .= "$_\n" unless $lun;
284 }
285
286 $CONFIG =~ s/\n$//;
287 die "$scfg->{target}: Target not found" unless $SETTINGS->{targets};
288 my $max = $SETTINGS->{targets};
289 my $base = get_base;
099925e4 290
a62d1e99
MR
291 for (my $i = 1; $i <= $max; $i++) {
292 my $target = $SETTINGS->{nodebase}.':'.$SETTINGS->{"LogicalUnit$i"}->{TargetName};
293 if ($target eq $scfg->{target}) {
294 my $lu = ();
295 while ((my $key, my $val) = each(%{$SETTINGS->{"LogicalUnit$i"}})) {
296 if ($key =~ /^LUN\d+/) {
297 if ($val =~ /^Storage\s+([\w\/\-]+)\s+(\w+)/) {
298 my $storage = $1;
299 my $size = $parse_size->($2);
300 my $conf = undef;
301 if ($storage =~ /^$base\/$scfg->{pool}\/([\w\-]+)$/) {
302 $conf = {
303 lun => $key,
304 Storage => $storage,
305 Size => $size,
306 };
307 }
308 push @$lu, $conf if $conf;
309 }
310 delete $SETTINGS->{"LogicalUnit$i"}->{$key};
311 }
312 }
313 $SETTINGS->{"LogicalUnit$i"}->{luns} = $lu;
314 $SETTINGS->{current} = "LogicalUnit$i";
315 $init_lu_name->("LogicalUnit$i");
316 } else {
317 $CONFIG .= $lun_dumper->("LogicalUnit$i");
318 delete $SETTINGS->{"LogicalUnit$i"};
319 $SETTINGS->{targets}--;
320 }
321 }
322 die "$scfg->{target}: Target not found" unless $SETTINGS->{targets} > 0;
323};
324
325my $list_lun = sub {
326 my ($scfg, $timeout, $method, @params) = @_;
327 my $name = undef;
328
329 my $object = $params[0];
330 for my $key (keys %$SETTINGS) {
331 next unless $key =~ /^LogicalUnit\d+$/;
332 foreach my $lun (@{$SETTINGS->{$key}->{luns}}) {
333 if ($lun->{Storage} =~ /^$object$/) {
334 return $lun->{Storage};
335 }
336 }
337 }
338
339 return $name;
340};
341
342my $create_lun = sub {
343 my ($scfg, $timeout, $method, @params) = @_;
344 my $res = ();
345 my $file = "/tmp/config$$";
346
347 if ($list_lun->($scfg, $timeout, $method, @params)) {
348 die "$params[0]: LUN exists";
349 }
350 my $lun = $params[0];
351 $lun = $make_lun->($lun);
352 my $config = $lun_dumper->($SETTINGS->{current});
353 open(my $fh, '>', $file) or die "Could not open file '$file' $!";
354
355 print $fh $CONFIG;
356 print $fh $config;
357 close $fh;
358 @params = ($CONFIG_FILE);
359 $res = {
360 cmd => 'scp',
361 method => $file,
362 params => \@params,
363 msg => $lun,
364 post_exe => sub {
365 unlink $file;
366 },
367 };
368
369 return $res;
370};
371
372my $delete_lun = sub {
373 my ($scfg, $timeout, $method, @params) = @_;
374 my $res = ();
375 my $file = "/tmp/config$$";
376
377 my $target = $SETTINGS->{current};
378 my $luns = ();
379
380 foreach my $conf (@{$SETTINGS->{$target}->{luns}}) {
381 if ($conf->{Storage} =~ /^$params[0]$/) {
382 $free_lu_name->($target, $conf->{lun});
383 } else {
384 push @$luns, $conf;
385 }
386 }
387 $SETTINGS->{$target}->{luns} = $luns;
388
389 my $config = $lun_dumper->($SETTINGS->{current});
390 open(my $fh, '>', $file) or die "Could not open file '$file' $!";
391
392 print $fh $CONFIG;
393 print $fh $config;
394 close $fh;
395 @params = ($CONFIG_FILE);
396 $res = {
397 cmd => 'scp',
398 method => $file,
399 params => \@params,
400 post_exe => sub {
401 unlink $file;
402 run_lun_command($scfg, undef, 'add_view', 'restart');
403 },
404 };
405
406 return $res;
407};
408
409my $import_lun = sub {
410 my ($scfg, $timeout, $method, @params) = @_;
411
412 my $res = $create_lun->($scfg, $timeout, $method, @params);
413
414 return $res;
415};
416
417my $add_view = sub {
418 my ($scfg, $timeout, $method, @params) = @_;
419 my $cmdmap;
420
421 if (@params && $params[0] eq 'restart') {
422 @params = ('restart', '1>&2', '>', '/dev/null');
423 $cmdmap = {
424 cmd => 'ssh',
425 method => $DAEMON,
426 params => \@params,
427 };
428 } else {
429 @params = ('-HUP', '$(cat '. "$SETTINGS->{pidfile})");
430 $cmdmap = {
431 cmd => 'ssh',
432 method => 'kill',
433 params => \@params,
434 };
435 }
436
437 return $cmdmap;
438};
439
440my $modify_lun = sub {
441 my ($scfg, $timeout, $method, @params) = @_;
442
443 # Current SIGHUP reload limitations
444 # LU connected by the initiator can't be reloaded by SIGHUP.
445 # Until above limitation persists modifying a LUN will require
446 # a restart of the daemon breaking all current connections
447 #die 'Modify a connected LUN is not currently supported by istgt';
448 @params = ('restart', @params);
449
450 return $add_view->($scfg, $timeout, $method, @params);
451};
452
453my $list_view = sub {
454 my ($scfg, $timeout, $method, @params) = @_;
455 my $lun = undef;
456
457 my $object = $params[0];
458 for my $key (keys %$SETTINGS) {
459 next unless $key =~ /^LogicalUnit\d+$/;
460 foreach my $lun (@{$SETTINGS->{$key}->{luns}}) {
461 if ($lun->{Storage} =~ /^$object$/) {
462 if ($lun->{lun} =~ /^LUN(\d+)/) {
463 return $1;
464 }
465 die "$lun->{Storage}: Missing LUN";
466 }
467 }
468 }
469
470 return $lun;
471};
472
473my $get_lun_cmd_map = sub {
474 my ($method) = @_;
475
476 my $cmdmap = {
477 create_lu => { cmd => $create_lun },
478 delete_lu => { cmd => $delete_lun },
479 import_lu => { cmd => $import_lun },
480 modify_lu => { cmd => $modify_lun },
481 add_view => { cmd => $add_view },
482 list_view => { cmd => $list_view },
483 list_lu => { cmd => $list_lun },
484 };
485
486 die "unknown command '$method'" unless exists $cmdmap->{$method};
487
488 return $cmdmap->{$method};
489};
490
491sub run_lun_command {
492 my ($scfg, $timeout, $method, @params) = @_;
493
494 my $msg = '';
495 my $luncmd;
496 my $target;
497 my $cmd;
498 my $res;
499 $timeout = 10 if !$timeout;
500 my $is_add_view = 0;
501
502 my $output = sub {
503 my $line = shift;
504 $msg .= "$line\n";
505 };
506
507 $target = 'root@' . $scfg->{portal};
508
509 $parser->($scfg) unless $SETTINGS;
510 my $cmdmap = $get_lun_cmd_map->($method);
511 if ($method eq 'add_view') {
512 $is_add_view = 1 ;
513 $timeout = 15;
514 }
515 if (ref $cmdmap->{cmd} eq 'CODE') {
516 $res = $cmdmap->{cmd}->($scfg, $timeout, $method, @params);
517 if (ref $res) {
518 $method = $res->{method};
519 @params = @{$res->{params}};
520 if ($res->{cmd} eq 'scp') {
521 $cmd = [@scp_cmd, $method, "$target:$params[0]"];
522 } else {
523 $cmd = [@ssh_cmd, $target, $method, @params];
524 }
525 } else {
526 return $res;
527 }
528 } else {
529 $luncmd = $cmdmap->{cmd};
530 $method = $cmdmap->{method};
531 $cmd = [@ssh_cmd, $target, $luncmd, $method, @params];
532 }
533
534 eval {
535 run_command($cmd, outfunc => $output, timeout => $timeout);
536 };
537 if ($@ && $is_add_view) {
538 my $err = $@;
539 if ($OLD_CONFIG) {
540 my $err1 = undef;
541 my $file = "/tmp/config$$";
542 open(my $fh, '>', $file) or die "Could not open file '$file' $!";
543 print $fh $OLD_CONFIG;
544 close $fh;
545 $cmd = [@scp_cmd, $file, $CONFIG_FILE];
546 eval {
547 run_command($cmd, outfunc => $output, timeout => $timeout);
548 };
549 $err1 = $@ if $@;
550 unlink $file;
551 die "$err\n$err1" if $err1;
552 eval {
553 run_lun_command($scfg, undef, 'add_view', 'restart');
554 };
555 die "$err\n$@" if ($@);
556 }
557 die $err;
558 } elsif ($@) {
559 die $@;
560 } elsif ($is_add_view) {
561 $OLD_CONFIG = undef;
562 }
563
564 if ($res->{post_exe} && ref $res->{post_exe} eq 'CODE') {
565 $res->{post_exe}->();
566 }
567
568 if ($res->{msg}) {
569 $msg = $res->{msg};
570 }
571
572 return $msg;
573}
574
575sub get_base {
576 return '/dev/zvol';
577}
578
5791;