]>
Commit | Line | Data |
---|---|---|
c84106ed DC |
1 | package PVE::API2::Disks::ZFS; |
2 | ||
3 | use strict; | |
4 | use warnings; | |
5 | ||
6 | use PVE::Diskmanage; | |
7 | use PVE::JSONSchema qw(get_standard_option); | |
8 | use PVE::API2::Storage::Config; | |
9 | use PVE::Storage; | |
10 | use PVE::Tools qw(run_command lock_file trim); | |
11 | ||
12 | use PVE::RPCEnvironment; | |
13 | use PVE::RESTHandler; | |
14 | ||
15 | use base qw(PVE::RESTHandler); | |
16 | ||
17 | my $ZPOOL = '/sbin/zpool'; | |
18 | my $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 | ||
112 | sub 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 => { | |
144 | type => 'object' | |
145 | }, | |
146 | code => sub { | |
147 | my ($param) = @_; | |
148 | ||
149 | if (!-f $ZPOOL) { | |
150 | die "zfsutils-linux not installed\n"; | |
151 | } | |
152 | ||
153 | my $cmd = [$ZPOOL, 'status', '-P', $param->{name}]; | |
154 | ||
155 | my $pool = { | |
156 | lvl => 0, | |
157 | }; | |
158 | my $vdevs = []; | |
159 | ||
160 | my $curfield; | |
161 | my $config = 0; | |
162 | ||
163 | my $stack = [$pool]; | |
164 | my $curlvl = 0; | |
165 | ||
166 | run_command($cmd, outfunc => sub { | |
167 | my ($line) = @_; | |
168 | ||
169 | if ($line =~ m/^\s*(\S+): (\S+.*)$/) { | |
170 | $curfield = $1; | |
171 | $pool->{$curfield} = $2; | |
172 | ||
173 | $config = 0 if $curfield eq 'errors'; | |
174 | } elsif (!$config && $line =~ m/^\s+(\S+.*)$/) { | |
175 | $pool->{$curfield} .= " " . $1; | |
176 | } elsif (!$config && $line =~ m/^\s*config:/) { | |
177 | $config = 1; | |
178 | } elsif ($config && $line =~ m/^(\s+)(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s*(.*)$/) { | |
179 | my ($space, $name, $state, $read, $write, $cksum, $msg) = ($1, $2, $3, $4, $5, $6, $7); | |
180 | if ($space =~ m/^\t(\s+)$/) { | |
181 | my $lvl= length($1)/2; # two spaces per level | |
182 | my $vdev = { | |
183 | name => $name, | |
184 | state => $state, | |
185 | read => $read + 0, | |
186 | write => $write + 0, | |
187 | cksum => $cksum + 0, | |
188 | msg => $msg, | |
189 | lvl => $lvl, | |
190 | }; | |
191 | ||
192 | my $cur = pop @$stack; | |
193 | ||
194 | if ($lvl > $curlvl) { | |
195 | $cur->{children} = [ $vdev ]; | |
196 | push @$stack, $cur; | |
197 | push @$stack, $vdev; | |
198 | } elsif ($lvl == $curlvl) { | |
199 | $cur = pop @$stack; | |
200 | push @{$cur->{children}}, $vdev; | |
201 | push @$stack, $cur; | |
202 | push @$stack, $vdev; | |
203 | } else { | |
204 | while ($lvl <= $cur->{lvl}) { | |
205 | $cur = pop @$stack; | |
206 | } | |
207 | push @{$cur->{children}}, $vdev; | |
208 | push @$stack, $cur; | |
209 | push @$stack, $vdev; | |
210 | } | |
211 | $curlvl = $lvl; | |
212 | } | |
213 | } | |
214 | }); | |
215 | ||
216 | # change treenodes for extjs tree | |
217 | $pool->{name} = delete $pool->{pool}; | |
218 | preparetree($pool); | |
219 | ||
220 | return $pool; | |
221 | }}); | |
222 | ||
223 | __PACKAGE__->register_method ({ | |
224 | name => 'create', | |
225 | path => '', | |
226 | method => 'POST', | |
227 | proxyto => 'node', | |
228 | protected => 1, | |
229 | permissions => { | |
230 | check => ['perm', '/', ['Sys.Modify', 'Datastore.Allocate']], | |
231 | }, | |
fdc863c7 | 232 | description => "Create a ZFS pool.", |
c84106ed DC |
233 | parameters => { |
234 | additionalProperties => 0, | |
235 | properties => { | |
236 | node => get_standard_option('pve-node'), | |
237 | name => get_standard_option('pve-storage-id'), | |
238 | raidlevel => { | |
239 | type => 'string', | |
240 | description => 'The RAID level to use, for single disk, use mirror.', | |
241 | enum => ['mirror', 'raid10', 'raidz', 'raidz2', 'raidz3'], | |
242 | }, | |
243 | devices => { | |
5be1a092 | 244 | type => 'string', format => 'string-list', |
fdc863c7 | 245 | description => 'The block devices you want to create the zpool on.', |
c84106ed DC |
246 | }, |
247 | ashift => { | |
248 | type => 'integer', | |
249 | minimum => 9, | |
250 | maximum => 16, | |
251 | optional => 1, | |
252 | default => 12, | |
7d597888 | 253 | description => 'Pool sector size exponent.', |
c84106ed DC |
254 | }, |
255 | compression => { | |
256 | type => 'string', | |
257 | description => 'The compression algorithm to use.', | |
258 | enum => ['on', 'off', 'gzip', 'lz4', 'lzjb', 'zle'], | |
259 | optional => 1, | |
260 | default => 'on', | |
261 | }, | |
262 | add_storage => { | |
fdc863c7 | 263 | description => "Configure storage using the zpool.", |
c84106ed DC |
264 | type => 'boolean', |
265 | optional => 1, | |
266 | default => 0, | |
267 | }, | |
268 | }, | |
269 | }, | |
270 | returns => { type => 'string' }, | |
271 | code => sub { | |
272 | my ($param) = @_; | |
273 | ||
274 | my $rpcenv = PVE::RPCEnvironment::get(); | |
275 | my $user = $rpcenv->get_user(); | |
276 | ||
277 | my $name = $param->{name}; | |
278 | my $devs = [PVE::Tools::split_list($param->{devices})]; | |
279 | my $raidlvl = $param->{raidlevel}; | |
280 | my $node = $param->{node}; | |
281 | my $ashift = $param->{ashift} // 12; | |
282 | my $compression = $param->{compression} // 'on'; | |
283 | ||
284 | foreach my $dev (@$devs) { | |
285 | $dev = PVE::Diskmanage::verify_blockdev_path($dev); | |
286 | die "device $dev is already in use\n" if PVE::Diskmanage::disk_is_used($dev); | |
287 | } | |
288 | ||
289 | my $cfg = PVE::Storage::config(); | |
290 | ||
291 | if (my $scfg = PVE::Storage::storage_config($cfg, $name, 1)) { | |
292 | die "storage ID '$name' already defined\n"; | |
293 | } | |
294 | ||
295 | my $numdisks = scalar(@$devs); | |
296 | my $mindisks = { | |
297 | mirror => 1, | |
298 | raid10 => 4, | |
299 | raidz => 3, | |
300 | raidz2 => 4, | |
301 | raidz3 => 5, | |
302 | }; | |
303 | ||
304 | # sanity checks | |
305 | die "raid10 needs an even number of disks\n" | |
306 | if $raidlvl eq 'raid10' && $numdisks % 2 != 0; | |
307 | ||
308 | die "$raidlvl needs at least $mindisks->{$raidlvl} disks\n" | |
309 | if $numdisks < $mindisks->{$raidlvl}; | |
310 | ||
311 | my $worker = sub { | |
312 | lock_file('/run/lock/pve-diskmanage.lck', 10, sub { | |
313 | # create zpool with desired raidlevel | |
314 | ||
315 | my $cmd = [$ZPOOL, 'create', '-o', "ashift=$ashift", $name]; | |
316 | ||
317 | if ($raidlvl eq 'raid10') { | |
318 | for (my $i = 0; $i < @$devs; $i+=2) { | |
319 | push @$cmd, 'mirror', $devs->[$i], $devs->[$i+1]; | |
320 | } | |
321 | } else { | |
322 | push @$cmd, $raidlvl, @$devs; | |
323 | } | |
324 | ||
325 | print "# ", join(' ', @$cmd), "\n"; | |
326 | run_command($cmd); | |
327 | ||
328 | $cmd = [$ZFS, 'set', "compression=$compression", $name]; | |
329 | print "# ", join(' ', @$cmd), "\n"; | |
330 | run_command($cmd); | |
331 | ||
332 | if ($param->{add_storage}) { | |
333 | my $storage_params = { | |
334 | type => 'zfspool', | |
335 | pool => $name, | |
336 | storage => $name, | |
337 | content => 'rootdir,images', | |
338 | nodes => $node, | |
339 | }; | |
340 | ||
341 | PVE::API2::Storage::Config->create($storage_params); | |
342 | } | |
343 | }); | |
344 | die $@ if $@; | |
345 | }; | |
346 | ||
347 | ||
348 | return $rpcenv->fork_worker('zfscreate', $name, $user, $worker); | |
349 | }}); | |
350 | ||
351 | 1; |