]> git.proxmox.com Git - pve-storage.git/blame - PVE/API2/Disks/ZFS.pm
disks: zfs: code indentation/style improvments
[pve-storage.git] / PVE / API2 / Disks / ZFS.pm
CommitLineData
c84106ed
DC
1package PVE::API2::Disks::ZFS;
2
3use strict;
4use warnings;
5
6use PVE::Diskmanage;
7use PVE::JSONSchema qw(get_standard_option);
f720f6c4 8use PVE::Systemd;
c84106ed
DC
9use PVE::API2::Storage::Config;
10use PVE::Storage;
11use PVE::Tools qw(run_command lock_file trim);
12
13use PVE::RPCEnvironment;
14use PVE::RESTHandler;
15
16use base qw(PVE::RESTHandler);
17
18my $ZPOOL = '/sbin/zpool';
19my $ZFS = '/sbin/zfs';
20
21__PACKAGE__->register_method ({
22 name => 'index',
23 path => '',
24 method => 'GET',
25 proxyto => 'node',
26 protected => 1,
27 permissions => {
28 check => ['perm', '/', ['Sys.Audit', 'Datastore.Audit'], any => 1],
29 },
30 description => "List Zpools.",
31 parameters => {
32 additionalProperties => 0,
33 properties => {
34 node => get_standard_option('pve-node'),
35 },
36 },
37 returns => {
38 type => 'array',
39 items => {
40 type => 'object',
41 properties => {
42 name => {
43 type => 'string',
44 description => "",
45 },
46 size => {
47 type => 'integer',
48 description => "",
49 },
50 alloc => {
51 type => 'integer',
52 description => "",
53 },
54 free => {
55 type => 'integer',
56 description => "",
57 },
58 frag => {
59 type => 'integer',
60 description => "",
61 },
62 dedup => {
63 type => 'number',
64 description => "",
65 },
66 health => {
67 type => 'string',
68 description => "",
69 },
70 },
71 },
72 links => [ { rel => 'child', href => "{name}" } ],
73 },
74 code => sub {
75 my ($param) = @_;
76
77 if (!-f $ZPOOL) {
78 die "zfsutils-linux not installed\n";
79 }
80
81 my $propnames = [qw(name size alloc free frag dedup health)];
82 my $numbers = {
83 size => 1,
84 alloc => 1,
85 free => 1,
86 frag => 1,
87 dedup => 1,
88 };
89
90 my $cmd = [$ZPOOL,'list', '-HpPLo', join(',', @$propnames)];
91
92 my $pools = [];
93
94 run_command($cmd, outfunc => sub {
95 my ($line) = @_;
96
97 my @props = split('\s+', trim($line));
98 my $pool = {};
99 for (my $i = 0; $i < scalar(@$propnames); $i++) {
100 if ($numbers->{$propnames->[$i]}) {
101 $pool->{$propnames->[$i]} = $props[$i] + 0;
102 } else {
103 $pool->{$propnames->[$i]} = $props[$i];
104 }
105 }
106
107 push @$pools, $pool;
108 });
109
110 return $pools;
111 }});
112
113sub preparetree {
114 my ($el) = @_;
115 delete $el->{lvl};
116 if ($el->{children} && scalar(@{$el->{children}})) {
117 $el->{leaf} = 0;
118 foreach my $child (@{$el->{children}}) {
119 preparetree($child);
120 }
121 } else {
122 $el->{leaf} = 1;
123 }
124}
125
126
127__PACKAGE__->register_method ({
128 name => 'detail',
129 path => '{name}',
130 method => 'GET',
131 proxyto => 'node',
132 protected => 1,
133 permissions => {
134 check => ['perm', '/', ['Sys.Audit', 'Datastore.Audit'], any => 1],
135 },
136 description => "Get details about a zpool.",
137 parameters => {
138 additionalProperties => 0,
139 properties => {
140 node => get_standard_option('pve-node'),
141 name => get_standard_option('pve-storage-id'),
142 },
143 },
144 returns => {
4d12dbff
DC
145 type => 'object',
146 properties => {
147 name => {
148 type => 'string',
149 description => 'The name of the zpool.',
150 },
151 state => {
152 type => 'string',
153 description => 'The state of the zpool.',
154 },
155 status => {
156 optional => 1,
157 type => 'string',
158 description => 'Information about the state of the zpool.',
159 },
160 action => {
161 optional => 1,
162 type => 'string',
163 description => 'Information about the recommended action to fix the state.',
164 },
165 scan => {
977b80c8 166 optional => 1,
4d12dbff
DC
167 type => 'string',
168 description => 'Information about the last/current scrub.',
169 },
b005f2f4 170 errors => {
4d12dbff
DC
171 type => 'string',
172 description => 'Information about the errors on the zpool.',
173 },
174 children => {
175 type => 'array',
32f749b8 176 description => "The pool configuration information, including the vdevs for each section (e.g. spares, cache), may be nested.",
4d12dbff
DC
177 items => {
178 type => 'object',
179 properties => {
180 name => {
181 type => 'string',
32f749b8 182 description => 'The name of the vdev or section.',
4d12dbff
DC
183 },
184 state => {
32f749b8 185 optional => 1,
4d12dbff
DC
186 type => 'string',
187 description => 'The state of the vdev.',
188 },
189 read => {
32f749b8 190 optional => 1,
4d12dbff
DC
191 type => 'number',
192 },
193 write => {
32f749b8 194 optional => 1,
4d12dbff
DC
195 type => 'number',
196 },
197 cksum => {
32f749b8 198 optional => 1,
4d12dbff
DC
199 type => 'number',
200 },
201 msg => {
202 type => 'string',
203 description => 'An optional message about the vdev.'
204 }
205 },
206 },
207 },
208 },
c84106ed
DC
209 },
210 code => sub {
211 my ($param) = @_;
212
213 if (!-f $ZPOOL) {
214 die "zfsutils-linux not installed\n";
215 }
216
217 my $cmd = [$ZPOOL, 'status', '-P', $param->{name}];
218
219 my $pool = {
220 lvl => 0,
221 };
c84106ed
DC
222
223 my $curfield;
224 my $config = 0;
225
226 my $stack = [$pool];
227 my $curlvl = 0;
228
229 run_command($cmd, outfunc => sub {
230 my ($line) = @_;
231
232 if ($line =~ m/^\s*(\S+): (\S+.*)$/) {
233 $curfield = $1;
234 $pool->{$curfield} = $2;
235
236 $config = 0 if $curfield eq 'errors';
237 } elsif (!$config && $line =~ m/^\s+(\S+.*)$/) {
238 $pool->{$curfield} .= " " . $1;
239 } elsif (!$config && $line =~ m/^\s*config:/) {
240 $config = 1;
a49fc735 241 } elsif ($config && $line =~ m/^(\s+)(\S+)\s*(\S+)?(?:\s+(\S+)\s+(\S+)\s+(\S+))?\s*(.*)$/) {
c84106ed 242 my ($space, $name, $state, $read, $write, $cksum, $msg) = ($1, $2, $3, $4, $5, $6, $7);
576e143a
FE
243 if ($name ne "NAME") {
244 my $lvl = int(length($space) / 2) + 1; # two spaces per level
c84106ed
DC
245 my $vdev = {
246 name => $name,
c84106ed
DC
247 msg => $msg,
248 lvl => $lvl,
249 };
8b6b7102 250
a49fc735
TM
251 $vdev->{state} = $state if defined($state);
252 $vdev->{read} = $read + 0 if defined($read);
253 $vdev->{write} = $write + 0 if defined($write);
254 $vdev->{cksum} = $cksum + 0 if defined($cksum);
8b6b7102 255
c84106ed
DC
256 my $cur = pop @$stack;
257
258 if ($lvl > $curlvl) {
259 $cur->{children} = [ $vdev ];
c84106ed
DC
260 } elsif ($lvl == $curlvl) {
261 $cur = pop @$stack;
262 push @{$cur->{children}}, $vdev;
c84106ed 263 } else {
8b6b7102 264 while ($lvl <= $cur->{lvl} && $cur->{lvl} != 0) {
c84106ed
DC
265 $cur = pop @$stack;
266 }
267 push @{$cur->{children}}, $vdev;
c84106ed 268 }
8b6b7102 269
a49fc735
TM
270 push @$stack, $cur;
271 push @$stack, $vdev;
c84106ed
DC
272 $curlvl = $lvl;
273 }
274 }
275 });
276
277 # change treenodes for extjs tree
278 $pool->{name} = delete $pool->{pool};
279 preparetree($pool);
280
281 return $pool;
282 }});
283
284__PACKAGE__->register_method ({
285 name => 'create',
286 path => '',
287 method => 'POST',
288 proxyto => 'node',
289 protected => 1,
290 permissions => {
291 check => ['perm', '/', ['Sys.Modify', 'Datastore.Allocate']],
292 },
fdc863c7 293 description => "Create a ZFS pool.",
c84106ed
DC
294 parameters => {
295 additionalProperties => 0,
296 properties => {
297 node => get_standard_option('pve-node'),
298 name => get_standard_option('pve-storage-id'),
299 raidlevel => {
300 type => 'string',
7058abe2
DC
301 description => 'The RAID level to use.',
302 enum => ['single', 'mirror', 'raid10', 'raidz', 'raidz2', 'raidz3'],
c84106ed
DC
303 },
304 devices => {
5be1a092 305 type => 'string', format => 'string-list',
fdc863c7 306 description => 'The block devices you want to create the zpool on.',
c84106ed
DC
307 },
308 ashift => {
309 type => 'integer',
310 minimum => 9,
311 maximum => 16,
312 optional => 1,
313 default => 12,
7d597888 314 description => 'Pool sector size exponent.',
c84106ed
DC
315 },
316 compression => {
317 type => 'string',
318 description => 'The compression algorithm to use.',
ae098a19 319 enum => ['on', 'off', 'gzip', 'lz4', 'lzjb', 'zle', 'zstd'],
c84106ed
DC
320 optional => 1,
321 default => 'on',
322 },
323 add_storage => {
fdc863c7 324 description => "Configure storage using the zpool.",
c84106ed
DC
325 type => 'boolean',
326 optional => 1,
327 default => 0,
328 },
329 },
330 },
331 returns => { type => 'string' },
332 code => sub {
333 my ($param) = @_;
334
335 my $rpcenv = PVE::RPCEnvironment::get();
336 my $user = $rpcenv->get_user();
337
338 my $name = $param->{name};
339 my $devs = [PVE::Tools::split_list($param->{devices})];
38572a8f 340 my $raidlevel = $param->{raidlevel};
c84106ed
DC
341 my $compression = $param->{compression} // 'on';
342
107208bd 343 for my $dev (@$devs) {
c84106ed 344 $dev = PVE::Diskmanage::verify_blockdev_path($dev);
0370861c 345 PVE::Diskmanage::assert_disk_unused($dev);
c84106ed 346 }
9280153e 347 PVE::Storage::assert_sid_unused($name) if $param->{add_storage};
c84106ed
DC
348
349 my $numdisks = scalar(@$devs);
350 my $mindisks = {
7058abe2
DC
351 single => 1,
352 mirror => 2,
c84106ed
DC
353 raid10 => 4,
354 raidz => 3,
355 raidz2 => 4,
356 raidz3 => 5,
357 };
358
359 # sanity checks
360 die "raid10 needs an even number of disks\n"
38572a8f 361 if $raidlevel eq 'raid10' && $numdisks % 2 != 0;
c84106ed 362
7058abe2
DC
363 die "please give only one disk for single disk mode\n"
364 if $raidlevel eq 'single' && $numdisks > 1;
365
38572a8f
DC
366 die "$raidlevel needs at least $mindisks->{$raidlevel} disks\n"
367 if $numdisks < $mindisks->{$raidlevel};
c84106ed 368
107208bd
TL
369 my $code = sub {
370 for my $dev (@$devs) {
371 PVE::Diskmanage::assert_disk_unused($dev);
05d91712 372
107208bd 373 my $is_partition = PVE::Diskmanage::is_partition($dev);
a2c34371 374
107208bd
TL
375 if ($is_partition) {
376 eval {
377 PVE::Diskmanage::change_parttype($dev, '6a898cc3-1dd2-11b2-99a6-080020736631');
378 };
379 warn $@ if $@;
380 }
a2c34371 381
107208bd
TL
382 my $sysfsdev = $is_partition ? PVE::Diskmanage::get_blockdev($dev) : $dev;
383
384 $sysfsdev =~ s!^/dev/!/sys/block/!;
385 if ($is_partition) {
386 my $part = $dev =~ s!^/dev/!!r;
387 $sysfsdev .= "/${part}";
e99bc248
FE
388 }
389
107208bd
TL
390 my $udevinfo = PVE::Diskmanage::get_udev_info($sysfsdev);
391 $dev = $udevinfo->{by_id_link} if defined($udevinfo->{by_id_link});
392 }
c84106ed 393
107208bd
TL
394 # create zpool with desired raidlevel
395 my $ashift = $param->{ashift} // 12;
c84106ed 396
107208bd
TL
397 my $cmd = [$ZPOOL, 'create', '-o', "ashift=$ashift", $name];
398
399 if ($raidlevel eq 'raid10') {
400 for (my $i = 0; $i < @$devs; $i+=2) {
401 push @$cmd, 'mirror', $devs->[$i], $devs->[$i+1];
c84106ed 402 }
107208bd
TL
403 } elsif ($raidlevel eq 'single') {
404 push @$cmd, $devs->[0];
405 } else {
406 push @$cmd, $raidlevel, @$devs;
407 }
c84106ed 408
107208bd
TL
409 print "# ", join(' ', @$cmd), "\n";
410 run_command($cmd);
411
412 $cmd = [$ZFS, 'set', "compression=$compression", $name];
413 print "# ", join(' ', @$cmd), "\n";
414 run_command($cmd);
c84106ed 415
107208bd
TL
416 if (-e '/lib/systemd/system/zfs-import@.service') {
417 my $importunit = 'zfs-import@'. PVE::Systemd::escape_unit($name, undef) . '.service';
418 $cmd = ['systemctl', 'enable', $importunit];
c84106ed
DC
419 print "# ", join(' ', @$cmd), "\n";
420 run_command($cmd);
107208bd 421 }
c84106ed 422
107208bd 423 PVE::Diskmanage::udevadm_trigger($devs->@*);
21a75847 424
107208bd
TL
425 if ($param->{add_storage}) {
426 my $storage_params = {
427 type => 'zfspool',
428 pool => $name,
429 storage => $name,
430 content => 'rootdir,images',
431 nodes => $param->{node},
432 };
c84106ed 433
107208bd
TL
434 PVE::API2::Storage::Config->create($storage_params);
435 }
c84106ed
DC
436 };
437
107208bd
TL
438 return $rpcenv->fork_worker('zfscreate', $name, $user, sub {
439 PVE::Diskmanage::locked_disk_action($code);
440 });
c84106ed
DC
441 }});
442
a83d8eb1
FE
443__PACKAGE__->register_method ({
444 name => 'delete',
445 path => '{name}',
446 method => 'DELETE',
447 proxyto => 'node',
448 protected => 1,
449 permissions => {
450 check => ['perm', '/', ['Sys.Modify', 'Datastore.Allocate']],
451 },
452 description => "Destroy a ZFS pool.",
453 parameters => {
454 additionalProperties => 0,
455 properties => {
456 node => get_standard_option('pve-node'),
457 name => get_standard_option('pve-storage-id'),
cde43c48
FE
458 'cleanup-config' => {
459 description => "Marks associated storage(s) as not available on this node anymore ".
460 "or removes them from the configuration (if configured for this node only).",
461 type => 'boolean',
462 optional => 1,
463 default => 0,
464 },
f81908eb
FE
465 'cleanup-disks' => {
466 description => "Also wipe disks so they can be repurposed afterwards.",
467 type => 'boolean',
468 optional => 1,
469 default => 0,
470 },
a83d8eb1
FE
471 },
472 },
473 returns => { type => 'string' },
474 code => sub {
475 my ($param) = @_;
476
477 my $rpcenv = PVE::RPCEnvironment::get();
478 my $user = $rpcenv->get_user();
479
480 my $name = $param->{name};
cde43c48 481 my $node = $param->{node};
a83d8eb1
FE
482
483 my $worker = sub {
484 PVE::Diskmanage::locked_disk_action(sub {
f81908eb
FE
485 my $to_wipe = [];
486 if ($param->{'cleanup-disks'}) {
487 # Using -o name does not only output the name in combination with -v.
488 run_command(['zpool', 'list', '-vHPL', $name], outfunc => sub {
489 my ($line) = @_;
490
491 my ($name) = PVE::Tools::split_list($line);
492 return if $name !~ m|^/dev/.+|;
493
494 my $dev = PVE::Diskmanage::verify_blockdev_path($name);
495 my $wipe = $dev;
496
497 $dev =~ s|^/dev/||;
498 my $info = PVE::Diskmanage::get_disks($dev, 1, 1);
499 die "unable to obtain information for disk '$dev'\n" if !$info->{$dev};
500
501 # Wipe whole disk if usual ZFS layout with partition 9 as ZFS reserved.
502 my $parent = $info->{$dev}->{parent};
503 if ($parent && scalar(keys $info->%*) == 3) {
504 $parent =~ s|^/dev/||;
505 my $info9 = $info->{"${parent}9"};
506
507 $wipe = $info->{$dev}->{parent} # need leading /dev/
508 if $info9 && $info9->{used} && $info9->{used} =~ m/^ZFS reserved/;
509 }
510
511 push $to_wipe->@*, $wipe;
512 });
513 }
514
a83d8eb1
FE
515 if (-e '/lib/systemd/system/zfs-import@.service') {
516 my $importunit = 'zfs-import@' . PVE::Systemd::escape_unit($name) . '.service';
517 run_command(['systemctl', 'disable', $importunit]);
518 }
519
520 run_command(['zpool', 'destroy', $name]);
f81908eb 521
cde43c48
FE
522 my $config_err;
523 if ($param->{'cleanup-config'}) {
524 my $match = sub {
525 my ($scfg) = @_;
526 return $scfg->{type} eq 'zfspool' && $scfg->{pool} eq $name;
527 };
528 eval { PVE::API2::Storage::Config->cleanup_storages_for_node($match, $node); };
529 warn $config_err = $@ if $@;
530 }
531
f81908eb
FE
532 eval { PVE::Diskmanage::wipe_blockdev($_) for $to_wipe->@*; };
533 my $err = $@;
534 PVE::Diskmanage::udevadm_trigger($to_wipe->@*);
535 die "cleanup failed - $err" if $err;
cde43c48
FE
536
537 die "config cleanup failed - $config_err" if $config_err;
a83d8eb1
FE
538 });
539 };
540
541 return $rpcenv->fork_worker('zfsremove', $name, $user, $worker);
542 }});
543
c84106ed 5441;