]> git.proxmox.com Git - pve-storage.git/blame - PVE/API2/Disks/ZFS.pm
rename check_available to assert_sid_unused
[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);
8use PVE::API2::Storage::Config;
9use PVE::Storage;
10use PVE::Tools qw(run_command lock_file trim);
11
12use PVE::RPCEnvironment;
13use PVE::RESTHandler;
14
15use base qw(PVE::RESTHandler);
16
17my $ZPOOL = '/sbin/zpool';
18my $ZFS = '/sbin/zfs';
19
20__PACKAGE__->register_method ({
21 name => 'index',
22 path => '',
23 method => 'GET',
24 proxyto => 'node',
25 protected => 1,
26 permissions => {
27 check => ['perm', '/', ['Sys.Audit', 'Datastore.Audit'], any => 1],
28 },
29 description => "List Zpools.",
30 parameters => {
31 additionalProperties => 0,
32 properties => {
33 node => get_standard_option('pve-node'),
34 },
35 },
36 returns => {
37 type => 'array',
38 items => {
39 type => 'object',
40 properties => {
41 name => {
42 type => 'string',
43 description => "",
44 },
45 size => {
46 type => 'integer',
47 description => "",
48 },
49 alloc => {
50 type => 'integer',
51 description => "",
52 },
53 free => {
54 type => 'integer',
55 description => "",
56 },
57 frag => {
58 type => 'integer',
59 description => "",
60 },
61 dedup => {
62 type => 'number',
63 description => "",
64 },
65 health => {
66 type => 'string',
67 description => "",
68 },
69 },
70 },
71 links => [ { rel => 'child', href => "{name}" } ],
72 },
73 code => sub {
74 my ($param) = @_;
75
76 if (!-f $ZPOOL) {
77 die "zfsutils-linux not installed\n";
78 }
79
80 my $propnames = [qw(name size alloc free frag dedup health)];
81 my $numbers = {
82 size => 1,
83 alloc => 1,
84 free => 1,
85 frag => 1,
86 dedup => 1,
87 };
88
89 my $cmd = [$ZPOOL,'list', '-HpPLo', join(',', @$propnames)];
90
91 my $pools = [];
92
93 run_command($cmd, outfunc => sub {
94 my ($line) = @_;
95
96 my @props = split('\s+', trim($line));
97 my $pool = {};
98 for (my $i = 0; $i < scalar(@$propnames); $i++) {
99 if ($numbers->{$propnames->[$i]}) {
100 $pool->{$propnames->[$i]} = $props[$i] + 0;
101 } else {
102 $pool->{$propnames->[$i]} = $props[$i];
103 }
104 }
105
106 push @$pools, $pool;
107 });
108
109 return $pools;
110 }});
111
112sub preparetree {
113 my ($el) = @_;
114 delete $el->{lvl};
115 if ($el->{children} && scalar(@{$el->{children}})) {
116 $el->{leaf} = 0;
117 foreach my $child (@{$el->{children}}) {
118 preparetree($child);
119 }
120 } else {
121 $el->{leaf} = 1;
122 }
123}
124
125
126__PACKAGE__->register_method ({
127 name => 'detail',
128 path => '{name}',
129 method => 'GET',
130 proxyto => 'node',
131 protected => 1,
132 permissions => {
133 check => ['perm', '/', ['Sys.Audit', 'Datastore.Audit'], any => 1],
134 },
135 description => "Get details about a zpool.",
136 parameters => {
137 additionalProperties => 0,
138 properties => {
139 node => get_standard_option('pve-node'),
140 name => get_standard_option('pve-storage-id'),
141 },
142 },
143 returns => {
4d12dbff
DC
144 type => 'object',
145 properties => {
146 name => {
147 type => 'string',
148 description => 'The name of the zpool.',
149 },
150 state => {
151 type => 'string',
152 description => 'The state of the zpool.',
153 },
154 status => {
155 optional => 1,
156 type => 'string',
157 description => 'Information about the state of the zpool.',
158 },
159 action => {
160 optional => 1,
161 type => 'string',
162 description => 'Information about the recommended action to fix the state.',
163 },
164 scan => {
165 type => 'string',
166 description => 'Information about the last/current scrub.',
167 },
168 scan => {
169 type => 'string',
170 description => 'Information about the errors on the zpool.',
171 },
172 children => {
173 type => 'array',
174 description => "The tree of the vdevs. Depending on the configuration of the zpool, they can be nested.",
175 items => {
176 type => 'object',
177 properties => {
178 name => {
179 type => 'string',
180 description => 'The name of the vdev.',
181 },
182 state => {
183 type => 'string',
184 description => 'The state of the vdev.',
185 },
186 read => {
187 type => 'number',
188 },
189 write => {
190 type => 'number',
191 },
192 cksum => {
193 type => 'number',
194 },
195 msg => {
196 type => 'string',
197 description => 'An optional message about the vdev.'
198 }
199 },
200 },
201 },
202 },
c84106ed
DC
203 },
204 code => sub {
205 my ($param) = @_;
206
207 if (!-f $ZPOOL) {
208 die "zfsutils-linux not installed\n";
209 }
210
211 my $cmd = [$ZPOOL, 'status', '-P', $param->{name}];
212
213 my $pool = {
214 lvl => 0,
215 };
216 my $vdevs = [];
217
218 my $curfield;
219 my $config = 0;
220
221 my $stack = [$pool];
222 my $curlvl = 0;
223
224 run_command($cmd, outfunc => sub {
225 my ($line) = @_;
226
227 if ($line =~ m/^\s*(\S+): (\S+.*)$/) {
228 $curfield = $1;
229 $pool->{$curfield} = $2;
230
231 $config = 0 if $curfield eq 'errors';
232 } elsif (!$config && $line =~ m/^\s+(\S+.*)$/) {
233 $pool->{$curfield} .= " " . $1;
234 } elsif (!$config && $line =~ m/^\s*config:/) {
235 $config = 1;
236 } elsif ($config && $line =~ m/^(\s+)(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s*(.*)$/) {
237 my ($space, $name, $state, $read, $write, $cksum, $msg) = ($1, $2, $3, $4, $5, $6, $7);
238 if ($space =~ m/^\t(\s+)$/) {
239 my $lvl= length($1)/2; # two spaces per level
240 my $vdev = {
241 name => $name,
242 state => $state,
243 read => $read + 0,
244 write => $write + 0,
245 cksum => $cksum + 0,
246 msg => $msg,
247 lvl => $lvl,
248 };
249
250 my $cur = pop @$stack;
251
252 if ($lvl > $curlvl) {
253 $cur->{children} = [ $vdev ];
254 push @$stack, $cur;
255 push @$stack, $vdev;
256 } elsif ($lvl == $curlvl) {
257 $cur = pop @$stack;
258 push @{$cur->{children}}, $vdev;
259 push @$stack, $cur;
260 push @$stack, $vdev;
261 } else {
262 while ($lvl <= $cur->{lvl}) {
263 $cur = pop @$stack;
264 }
265 push @{$cur->{children}}, $vdev;
266 push @$stack, $cur;
267 push @$stack, $vdev;
268 }
269 $curlvl = $lvl;
270 }
271 }
272 });
273
274 # change treenodes for extjs tree
275 $pool->{name} = delete $pool->{pool};
276 preparetree($pool);
277
278 return $pool;
279 }});
280
281__PACKAGE__->register_method ({
282 name => 'create',
283 path => '',
284 method => 'POST',
285 proxyto => 'node',
286 protected => 1,
287 permissions => {
288 check => ['perm', '/', ['Sys.Modify', 'Datastore.Allocate']],
289 },
fdc863c7 290 description => "Create a ZFS pool.",
c84106ed
DC
291 parameters => {
292 additionalProperties => 0,
293 properties => {
294 node => get_standard_option('pve-node'),
295 name => get_standard_option('pve-storage-id'),
296 raidlevel => {
297 type => 'string',
7058abe2
DC
298 description => 'The RAID level to use.',
299 enum => ['single', 'mirror', 'raid10', 'raidz', 'raidz2', 'raidz3'],
c84106ed
DC
300 },
301 devices => {
5be1a092 302 type => 'string', format => 'string-list',
fdc863c7 303 description => 'The block devices you want to create the zpool on.',
c84106ed
DC
304 },
305 ashift => {
306 type => 'integer',
307 minimum => 9,
308 maximum => 16,
309 optional => 1,
310 default => 12,
7d597888 311 description => 'Pool sector size exponent.',
c84106ed
DC
312 },
313 compression => {
314 type => 'string',
315 description => 'The compression algorithm to use.',
316 enum => ['on', 'off', 'gzip', 'lz4', 'lzjb', 'zle'],
317 optional => 1,
318 default => 'on',
319 },
320 add_storage => {
fdc863c7 321 description => "Configure storage using the zpool.",
c84106ed
DC
322 type => 'boolean',
323 optional => 1,
324 default => 0,
325 },
326 },
327 },
328 returns => { type => 'string' },
329 code => sub {
330 my ($param) = @_;
331
332 my $rpcenv = PVE::RPCEnvironment::get();
333 my $user = $rpcenv->get_user();
334
335 my $name = $param->{name};
336 my $devs = [PVE::Tools::split_list($param->{devices})];
38572a8f 337 my $raidlevel = $param->{raidlevel};
c84106ed
DC
338 my $node = $param->{node};
339 my $ashift = $param->{ashift} // 12;
340 my $compression = $param->{compression} // 'on';
341
342 foreach my $dev (@$devs) {
343 $dev = PVE::Diskmanage::verify_blockdev_path($dev);
76c1e57b 344 PVE::Diskmanage::check_unused($dev);
c84106ed
DC
345 }
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
DC
368
369 my $worker = sub {
e39e8ee2 370 PVE::Diskmanage::locked_disk_action(sub {
c84106ed
DC
371 # create zpool with desired raidlevel
372
373 my $cmd = [$ZPOOL, 'create', '-o', "ashift=$ashift", $name];
374
38572a8f 375 if ($raidlevel eq 'raid10') {
c84106ed
DC
376 for (my $i = 0; $i < @$devs; $i+=2) {
377 push @$cmd, 'mirror', $devs->[$i], $devs->[$i+1];
378 }
7058abe2
DC
379 } elsif ($raidlevel eq 'single') {
380 push @$cmd, $devs->[0];
c84106ed 381 } else {
38572a8f 382 push @$cmd, $raidlevel, @$devs;
c84106ed
DC
383 }
384
385 print "# ", join(' ', @$cmd), "\n";
386 run_command($cmd);
387
388 $cmd = [$ZFS, 'set', "compression=$compression", $name];
389 print "# ", join(' ', @$cmd), "\n";
390 run_command($cmd);
391
392 if ($param->{add_storage}) {
393 my $storage_params = {
394 type => 'zfspool',
395 pool => $name,
396 storage => $name,
397 content => 'rootdir,images',
398 nodes => $node,
399 };
400
401 PVE::API2::Storage::Config->create($storage_params);
402 }
403 });
c84106ed
DC
404 };
405
c84106ed
DC
406 return $rpcenv->fork_worker('zfscreate', $name, $user, $worker);
407 }});
408
4091;