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