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