Commit | Line | Data |
---|---|---|
78a64432 MR |
1 | package PVE::Storage::LunCmd::Iet; |
2 | ||
3 | # iscsi storage running Debian | |
4 | # 1) apt-get install iscsitarget iscsitarget-dkms | |
5 | # 2) Create target like (/etc/iet/ietd.conf): | |
6 | # Target iqn.2001-04.com.example:tank | |
86f00da6 | 7 | # Alias tank |
78a64432 MR |
8 | # 3) Activate daemon (/etc/default/iscsitarget) |
9 | # ISCSITARGET_ENABLE=true | |
10 | # 4) service iscsitarget start | |
11 | # | |
12 | # On one of the proxmox nodes: | |
13 | # 1) Login as root | |
14 | # 2) ssh-copy-id <ip_of_iscsi_storage> | |
15 | ||
16 | use strict; | |
17 | use warnings; | |
074b2cb4 | 18 | |
78a64432 | 19 | use PVE::Tools qw(run_command file_read_firstline trim dir_glob_regex dir_glob_foreach); |
78a64432 MR |
20 | |
21 | sub get_base; | |
22 | ||
23 | # A logical unit can max have 16864 LUNs | |
24 | # http://manpages.ubuntu.com/manpages/precise/man5/ietd.conf.5.html | |
25 | my $MAX_LUNS = 16864; | |
26 | ||
27 | my $CONFIG_FILE = '/etc/iet/ietd.conf'; | |
28 | my $DAEMON = '/usr/sbin/ietadm'; | |
29 | my $SETTINGS = undef; | |
30 | my $CONFIG = undef; | |
31 | my $OLD_CONFIG = undef; | |
32 | ||
33 | my @ssh_opts = ('-o', 'BatchMode=yes'); | |
34 | my @ssh_cmd = ('/usr/bin/ssh', @ssh_opts); | |
35 | my @scp_cmd = ('/usr/bin/scp', @ssh_opts); | |
3b219e80 | 36 | my $id_rsa_path = '/etc/pve/priv/zfs'; |
78a64432 MR |
37 | my $ietadm = '/usr/sbin/ietadm'; |
38 | ||
39 | my $execute_command = sub { | |
86f00da6 | 40 | my ($scfg, $exec, $timeout, $method, @params) = @_; |
78a64432 MR |
41 | |
42 | my $msg = ''; | |
43 | my $err = undef; | |
44 | my $target; | |
86f00da6 MR |
45 | my $cmd; |
46 | my $res = (); | |
47 | ||
78a64432 | 48 | $timeout = 10 if !$timeout; |
86f00da6 | 49 | |
78a64432 MR |
50 | my $output = sub { |
51 | my $line = shift; | |
86f00da6 | 52 | $msg .= "$line\n"; |
78a64432 MR |
53 | }; |
54 | ||
86f00da6 | 55 | my $errfunc = sub { |
78a64432 | 56 | my $line = shift; |
86f00da6 MR |
57 | $err .= "$line"; |
58 | }; | |
59 | ||
86f00da6 | 60 | if ($exec eq 'scp') { |
1689e627 WB |
61 | $target = 'root@[' . $scfg->{portal} . ']'; |
62 | $cmd = [@scp_cmd, '-i', "$id_rsa_path/$scfg->{portal}_id_rsa", '--', $method, "$target:$params[0]"]; | |
86f00da6 | 63 | } else { |
1689e627 WB |
64 | $target = 'root@' . $scfg->{portal}; |
65 | $cmd = [@ssh_cmd, '-i', "$id_rsa_path/$scfg->{portal}_id_rsa", $target, '--', $method, @params]; | |
86f00da6 MR |
66 | } |
67 | ||
68 | eval { | |
69 | run_command($cmd, outfunc => $output, errfunc => $errfunc, timeout => $timeout); | |
70 | }; | |
71 | if ($@) { | |
72 | $res = { | |
73 | result => 0, | |
74 | msg => $err, | |
75 | } | |
76 | } else { | |
77 | $res = { | |
78 | result => 1, | |
79 | msg => $msg, | |
80 | } | |
81 | } | |
82 | ||
83 | return $res; | |
78a64432 MR |
84 | }; |
85 | ||
86 | my $read_config = sub { | |
86f00da6 MR |
87 | my ($scfg, $timeout) = @_; |
88 | ||
78a64432 | 89 | my $msg = ''; |
86f00da6 | 90 | my $err = undef; |
78a64432 MR |
91 | my $luncmd = 'cat'; |
92 | my $target; | |
93 | $timeout = 10 if !$timeout; | |
86f00da6 | 94 | |
78a64432 | 95 | my $output = sub { |
86f00da6 MR |
96 | my $line = shift; |
97 | $msg .= "$line\n"; | |
98 | }; | |
99 | ||
100 | my $errfunc = sub { | |
101 | my $line = shift; | |
102 | $err .= "$line"; | |
78a64432 | 103 | }; |
78a64432 | 104 | |
86f00da6 | 105 | $target = 'root@' . $scfg->{portal}; |
78a64432 | 106 | |
3b219e80 | 107 | my $cmd = [@ssh_cmd, '-i', "$id_rsa_path/$scfg->{portal}_id_rsa", $target, $luncmd, $CONFIG_FILE]; |
86f00da6 MR |
108 | eval { |
109 | run_command($cmd, outfunc => $output, errfunc => $errfunc, timeout => $timeout); | |
110 | }; | |
111 | if ($@) { | |
112 | die $err if ($err !~ /No such file or directory/); | |
113 | die "No configuration found. Install iet on $scfg->{portal}" if $msg eq ''; | |
114 | } | |
78a64432 | 115 | |
86f00da6 | 116 | return $msg; |
78a64432 MR |
117 | }; |
118 | ||
119 | my $get_config = sub { | |
86f00da6 MR |
120 | my ($scfg) = @_; |
121 | my @conf = undef; | |
78a64432 | 122 | |
86f00da6 MR |
123 | my $config = $read_config->($scfg, undef); |
124 | die "Missing config file" unless $config; | |
78a64432 | 125 | |
86f00da6 MR |
126 | $OLD_CONFIG = $config; |
127 | ||
128 | return $config; | |
78a64432 MR |
129 | }; |
130 | ||
131 | my $parser = sub { | |
86f00da6 MR |
132 | my ($scfg) = @_; |
133 | ||
134 | my $line = 0; | |
135 | ||
136 | my $base = get_base; | |
137 | my $config = $get_config->($scfg); | |
138 | my @cfgfile = split "\n", $config; | |
139 | ||
140 | my $cfg_target = 0; | |
141 | foreach (@cfgfile) { | |
142 | $line++; | |
143 | if ($_ =~ /^\s*Target\s*([\w\-\:\.]+)\s*$/) { | |
144 | if ($1 eq $scfg->{target} && ! $cfg_target) { | |
145 | # start colect info | |
146 | die "$line: Parse error [$_]" if $SETTINGS; | |
147 | $SETTINGS->{target} = $1; | |
148 | $cfg_target = 1; | |
149 | } elsif ($1 eq $scfg->{target} && $cfg_target) { | |
150 | die "$line: Parse error [$_]"; | |
151 | } elsif ($cfg_target) { | |
152 | $cfg_target = 0; | |
153 | $CONFIG .= "$_\n"; | |
154 | } else { | |
155 | $CONFIG .= "$_\n"; | |
156 | } | |
157 | } else { | |
158 | if ($cfg_target) { | |
159 | $SETTINGS->{text} .= "$_\n"; | |
160 | next if ($_ =~ /^\s*#/ || ! $_); | |
161 | my $option = $_; | |
162 | if ($_ =~ /^(\w+)\s*#/) { | |
163 | $option = $1; | |
164 | } | |
165 | if ($option =~ /^\s*(\w+)\s+(\w+)\s*$/) { | |
166 | if ($1 eq 'Lun') { | |
167 | die "$line: Parse error [$_]"; | |
168 | } | |
169 | $SETTINGS->{$1} = $2; | |
170 | } elsif ($option =~ /^\s*(\w+)\s+(\d+)\s+([\w\-\/=,]+)\s*$/) { | |
171 | die "$line: Parse error [$option]" unless ($1 eq 'Lun'); | |
172 | my $conf = undef; | |
173 | my $num = $2; | |
174 | my @lun = split ',', $3; | |
175 | die "$line: Parse error [$option]" unless (scalar(@lun) > 1); | |
176 | foreach (@lun) { | |
177 | my @lun_opt = split '=', $_; | |
178 | die "$line: Parse error [$option]" unless (scalar(@lun_opt) == 2); | |
179 | $conf->{$lun_opt[0]} = $lun_opt[1]; | |
180 | } | |
181 | if ($conf->{Path} && $conf->{Path} =~ /^$base\/$scfg->{pool}\/([\w\-]+)$/) { | |
182 | $conf->{include} = 1; | |
183 | } else { | |
184 | $conf->{include} = 0; | |
185 | } | |
186 | $conf->{lun} = $num; | |
187 | push @{$SETTINGS->{luns}}, $conf; | |
188 | } else { | |
189 | die "$line: Parse error [$option]"; | |
190 | } | |
191 | } else { | |
192 | $CONFIG .= "$_\n"; | |
193 | } | |
194 | } | |
195 | } | |
196 | $CONFIG =~ s/^\s+|\s+$|"\s*//g; | |
78a64432 MR |
197 | }; |
198 | ||
199 | my $update_config = sub { | |
86f00da6 MR |
200 | my ($scfg) = @_; |
201 | my $file = "/tmp/config$$"; | |
202 | my $config = ''; | |
203 | ||
204 | while ((my $option, my $value) = each(%$SETTINGS)) { | |
205 | next if ($option eq 'include' || $option eq 'luns' || $option eq 'Path' || $option eq 'text' || $option eq 'used'); | |
206 | if ($option eq 'target') { | |
207 | $config = "\n\nTarget " . $SETTINGS->{target} . "\n" . $config; | |
208 | } else { | |
209 | $config .= "\t$option\t\t\t$value\n"; | |
210 | } | |
211 | } | |
212 | foreach my $lun (@{$SETTINGS->{luns}}) { | |
213 | my $lun_opt = ''; | |
214 | while ((my $option, my $value) = each(%$lun)) { | |
215 | next if ($option eq 'include' || $option eq 'lun' || $option eq 'Path'); | |
216 | if ($lun_opt eq '') { | |
217 | $lun_opt = $option . '=' . $value; | |
218 | } else { | |
219 | $lun_opt .= ',' . $option . '=' . $value; | |
220 | } | |
221 | } | |
222 | $config .= "\tLun $lun->{lun} Path=$lun->{Path},$lun_opt\n"; | |
223 | } | |
224 | open(my $fh, '>', $file) or die "Could not open file '$file' $!"; | |
225 | ||
226 | print $fh $CONFIG; | |
227 | print $fh $config; | |
228 | close $fh; | |
229 | ||
230 | my @params = ($CONFIG_FILE); | |
231 | my $res = $execute_command->($scfg, 'scp', undef, $file, @params); | |
232 | unlink $file; | |
233 | ||
234 | die $res->{msg} unless $res->{result}; | |
78a64432 MR |
235 | }; |
236 | ||
237 | my $get_target_tid = sub { | |
86f00da6 MR |
238 | my ($scfg) = @_; |
239 | my $proc = '/proc/net/iet/volume'; | |
240 | my $tid = undef; | |
241 | ||
242 | my @params = ($proc); | |
243 | my $res = $execute_command->($scfg, 'ssh', undef, 'cat', @params); | |
244 | die $res->{msg} unless $res->{result}; | |
245 | my @cfg = split "\n", $res->{msg}; | |
246 | ||
247 | foreach (@cfg) { | |
248 | if ($_ =~ /^\s*tid:(\d+)\s+name:([\w\-\:\.]+)\s*$/) { | |
249 | if ($2 && $2 eq $scfg->{target}) { | |
250 | $tid = $1; | |
251 | last; | |
252 | } | |
253 | } | |
254 | } | |
255 | ||
256 | return $tid; | |
78a64432 MR |
257 | }; |
258 | ||
259 | my $get_lu_name = sub { | |
86f00da6 MR |
260 | my $used = (); |
261 | my $i; | |
262 | ||
263 | if (! exists $SETTINGS->{used}) { | |
264 | for ($i = 0; $i < $MAX_LUNS; $i++) { | |
265 | $used->{$i} = 0; | |
266 | } | |
267 | foreach my $lun (@{$SETTINGS->{luns}}) { | |
268 | $used->{$lun->{lun}} = 1; | |
269 | } | |
270 | $SETTINGS->{used} = $used; | |
271 | } | |
272 | ||
273 | $used = $SETTINGS->{used}; | |
274 | for ($i = 0; $i < $MAX_LUNS; $i++) { | |
275 | last unless $used->{$i}; | |
276 | } | |
277 | $SETTINGS->{used}->{$i} = 1; | |
278 | ||
279 | return $i; | |
78a64432 MR |
280 | }; |
281 | ||
282 | my $init_lu_name = sub { | |
86f00da6 MR |
283 | my $used = (); |
284 | ||
285 | if (! exists($SETTINGS->{used})) { | |
286 | for (my $i = 0; $i < $MAX_LUNS; $i++) { | |
287 | $used->{$i} = 0; | |
288 | } | |
289 | $SETTINGS->{used} = $used; | |
290 | } | |
291 | foreach my $lun (@{$SETTINGS->{luns}}) { | |
292 | $SETTINGS->{used}->{$lun->{lun}} = 1; | |
293 | } | |
78a64432 MR |
294 | }; |
295 | ||
296 | my $free_lu_name = sub { | |
86f00da6 MR |
297 | my ($lu_name) = @_; |
298 | my $new; | |
299 | ||
300 | foreach my $lun (@{$SETTINGS->{luns}}) { | |
301 | if ($lun->{lun} != $lu_name) { | |
302 | push @$new, $lun; | |
303 | } | |
304 | } | |
305 | ||
306 | $SETTINGS->{luns} = $new; | |
307 | $SETTINGS->{used}->{$lu_name} = 0; | |
78a64432 MR |
308 | }; |
309 | ||
310 | my $make_lun = sub { | |
86f00da6 MR |
311 | my ($scfg, $path) = @_; |
312 | ||
313 | die 'Maximum number of LUNs per target is 16384' if scalar @{$SETTINGS->{luns}} >= $MAX_LUNS; | |
314 | ||
315 | my $lun = $get_lu_name->(); | |
316 | my $conf = { | |
317 | lun => $lun, | |
318 | Path => $path, | |
319 | Type => 'blockio', | |
320 | include => 1, | |
321 | }; | |
322 | push @{$SETTINGS->{luns}}, $conf; | |
323 | ||
324 | return $conf; | |
78a64432 MR |
325 | }; |
326 | ||
327 | my $list_view = sub { | |
86f00da6 MR |
328 | my ($scfg, $timeout, $method, @params) = @_; |
329 | my $lun = undef; | |
330 | ||
331 | my $object = $params[0]; | |
332 | foreach my $lun (@{$SETTINGS->{luns}}) { | |
333 | next unless $lun->{include} == 1; | |
334 | if ($lun->{Path} =~ /^$object$/) { | |
335 | return $lun->{lun} if (defined($lun->{lun})); | |
336 | die "$lun->{Path}: Missing LUN"; | |
337 | } | |
338 | } | |
339 | ||
340 | return $lun; | |
78a64432 MR |
341 | }; |
342 | ||
343 | my $list_lun = sub { | |
86f00da6 MR |
344 | my ($scfg, $timeout, $method, @params) = @_; |
345 | my $name = undef; | |
346 | ||
347 | my $object = $params[0]; | |
348 | foreach my $lun (@{$SETTINGS->{luns}}) { | |
349 | next unless $lun->{include} == 1; | |
350 | if ($lun->{Path} =~ /^$object$/) { | |
351 | return $lun->{Path}; | |
352 | } | |
353 | } | |
354 | ||
355 | return $name; | |
78a64432 MR |
356 | }; |
357 | ||
358 | my $create_lun = sub { | |
86f00da6 MR |
359 | my ($scfg, $timeout, $method, @params) = @_; |
360 | ||
361 | if ($list_lun->($scfg, $timeout, $method, @params)) { | |
362 | die "$params[0]: LUN exists"; | |
363 | } | |
364 | my $lun = $params[0]; | |
365 | $lun = $make_lun->($scfg, $lun); | |
366 | my $tid = $get_target_tid->($scfg); | |
367 | $update_config->($scfg); | |
368 | ||
369 | my $path = "Path=$lun->{Path},Type=$lun->{Type}"; | |
370 | ||
371 | @params = ('--op', 'new', "--tid=$tid", "--lun=$lun->{lun}", '--params', $path); | |
372 | my $res = $execute_command->($scfg, 'ssh', $timeout, $ietadm, @params); | |
373 | do { | |
374 | $free_lu_name->($lun->{lun}); | |
375 | $update_config->($scfg); | |
376 | die $res->{msg}; | |
377 | } unless $res->{result}; | |
378 | ||
379 | return $res->{msg}; | |
78a64432 MR |
380 | }; |
381 | ||
382 | my $delete_lun = sub { | |
86f00da6 MR |
383 | my ($scfg, $timeout, $method, @params) = @_; |
384 | my $res = {msg => undef}; | |
385 | ||
386 | my $path = $params[0]; | |
387 | my $tid = $get_target_tid->($scfg); | |
388 | ||
389 | foreach my $lun (@{$SETTINGS->{luns}}) { | |
390 | if ($lun->{Path} eq $path) { | |
391 | @params = ('--op', 'delete', "--tid=$tid", "--lun=$lun->{lun}"); | |
392 | $res = $execute_command->($scfg, 'ssh', $timeout, $ietadm, @params); | |
393 | if ($res->{result}) { | |
394 | $free_lu_name->($lun->{lun}); | |
395 | $update_config->($scfg); | |
396 | last; | |
397 | } else { | |
398 | die $res->{msg}; | |
399 | } | |
400 | } | |
401 | } | |
402 | ||
403 | return $res->{msg}; | |
78a64432 MR |
404 | }; |
405 | ||
406 | my $import_lun = sub { | |
86f00da6 | 407 | my ($scfg, $timeout, $method, @params) = @_; |
78a64432 | 408 | |
86f00da6 | 409 | return $create_lun->($scfg, $timeout, $method, @params); |
78a64432 MR |
410 | }; |
411 | ||
412 | my $modify_lun = sub { | |
86f00da6 MR |
413 | my ($scfg, $timeout, $method, @params) = @_; |
414 | my $lun; | |
415 | my $res; | |
416 | ||
417 | my $path = $params[1]; | |
418 | my $tid = $get_target_tid->($scfg); | |
419 | ||
420 | foreach my $cfg (@{$SETTINGS->{luns}}) { | |
421 | if ($cfg->{Path} eq $path) { | |
422 | $lun = $cfg; | |
423 | last; | |
424 | } | |
425 | } | |
426 | ||
427 | @params = ('--op', 'delete', "--tid=$tid", "--lun=$lun->{lun}"); | |
428 | $res = $execute_command->($scfg, 'ssh', $timeout, $ietadm, @params); | |
429 | die $res->{msg} unless $res->{result}; | |
430 | ||
431 | $path = "Path=$lun->{Path},Type=$lun->{Type}"; | |
432 | @params = ('--op', 'new', "--tid=$tid", "--lun=$lun->{lun}", '--params', $path); | |
433 | $res = $execute_command->($scfg, 'ssh', $timeout, $ietadm, @params); | |
434 | die $res->{msg} unless $res->{result}; | |
435 | ||
436 | return $res->{msg}; | |
78a64432 MR |
437 | }; |
438 | ||
439 | my $add_view = sub { | |
86f00da6 | 440 | my ($scfg, $timeout, $method, @params) = @_; |
78a64432 | 441 | |
86f00da6 | 442 | return ''; |
78a64432 MR |
443 | }; |
444 | ||
445 | my $get_lun_cmd_map = sub { | |
86f00da6 MR |
446 | my ($method) = @_; |
447 | ||
448 | my $cmdmap = { | |
449 | create_lu => { cmd => $create_lun }, | |
450 | delete_lu => { cmd => $delete_lun }, | |
451 | import_lu => { cmd => $import_lun }, | |
452 | modify_lu => { cmd => $modify_lun }, | |
453 | add_view => { cmd => $add_view }, | |
454 | list_view => { cmd => $list_view }, | |
455 | list_lu => { cmd => $list_lun }, | |
456 | }; | |
457 | ||
458 | die "unknown command '$method'" unless exists $cmdmap->{$method}; | |
459 | ||
460 | return $cmdmap->{$method}; | |
78a64432 MR |
461 | }; |
462 | ||
463 | sub run_lun_command { | |
86f00da6 | 464 | my ($scfg, $timeout, $method, @params) = @_; |
78a64432 | 465 | |
86f00da6 MR |
466 | $parser->($scfg) unless $SETTINGS; |
467 | my $cmdmap = $get_lun_cmd_map->($method); | |
468 | my $msg = $cmdmap->{cmd}->($scfg, $timeout, $method, @params); | |
78a64432 | 469 | |
86f00da6 | 470 | return $msg; |
78a64432 MR |
471 | } |
472 | ||
473 | sub get_base { | |
86f00da6 | 474 | return '/dev'; |
78a64432 MR |
475 | } |
476 | ||
477 | 1; | |
86f00da6 | 478 |