]> git.proxmox.com Git - pve-storage.git/blame - PVE/Storage/ZFSPoolPlugin.pm
rename ZFSDirPlugin to ZFSPoolPlugin
[pve-storage.git] / PVE / Storage / ZFSPoolPlugin.pm
CommitLineData
85fda4dd 1package PVE::Storage::ZFSPoolPlugin;
5bb8e010
DM
2
3use strict;
4use warnings;
5use IO::File;
6use POSIX;
7use PVE::Tools qw(run_command);
8use PVE::Storage::Plugin;
9
10
11use base qw(PVE::Storage::Plugin);
12
5bb8e010 13sub type {
85fda4dd 14 return 'zfspool';
5bb8e010
DM
15}
16
17sub plugindata {
18 return {
85fda4dd 19 content => [ {images => 1}, { images => 1 }],
5bb8e010 20 };
85fda4dd 21}
5bb8e010 22
7730694e
DM
23sub properties {
24 return {
25 blocksize => {
26 description => "block size",
27 type => 'string',
28 },
29 sparse => {
30 description => "use sparse volumes",
31 type => 'boolean',
32 },
33 };
34}
35
5bb8e010
DM
36sub options {
37 return {
7730694e
DM
38 pool => { fixed => 1 },
39 blocksize => { optional => 1 },
40 sparse => { optional => 1 },
41 nodes => { optional => 1 },
5bb8e010
DM
42 disable => { optional => 1 },
43 maxfiles => { optional => 1 },
44 content => { optional => 1 },
45 };
46}
47
7730694e
DM
48# static zfs helper methods
49
060ef890
DM
50sub zfs_parse_size {
51 my ($text) = @_;
52
53 return 0 if !$text;
54
55 if ($text =~ m/^(\d+(\.\d+)?)([TGMK])?$/) {
56
57 my ($size, $reminder, $unit) = ($1, $2, $3);
58
59 if ($unit) {
60 if ($unit eq 'K') {
61 $size *= 1024;
62 } elsif ($unit eq 'M') {
63 $size *= 1024*1024;
64 } elsif ($unit eq 'G') {
65 $size *= 1024*1024*1024;
66 } elsif ($unit eq 'T') {
67 $size *= 1024*1024*1024*1024;
68 } else {
69 die "got unknown zfs size unit '$unit'\n";
70 }
71 }
72
73 if ($reminder) {
74 $size = ceil($size);
75 }
76
77 return $size;
78
79 }
80
81 warn "unable to parse zfs size '$text'\n";
82
83 return 0;
84}
85
7730694e
DM
86sub zfs_parse_zvol_list {
87 my ($text) = @_;
88
89 my $list = ();
90
91 return $list if !$text;
92
93 my @lines = split /\n/, $text;
94 foreach my $line (@lines) {
95 if ($line =~ /^(.+)\s+([a-zA-Z0-9\.]+|\-)\s+(.+)$/) {
96 my $zvol = {};
97 my @parts = split /\//, $1;
98 my $name = pop @parts;
99 my $pool = join('/', @parts);
100
101 if ($pool !~ /^rpool$/) {
102 next unless $name =~ m!^(\w+)-(\d+)-(\w+)-(\d+)$!;
103 $name = $pool . '/' . $name;
104 } else {
105 next;
106 }
107
108 $zvol->{pool} = $pool;
109 $zvol->{name} = $name;
110 $zvol->{size} = zfs_parse_size($2);
111 if ($3 !~ /^-$/) {
112 $zvol->{origin} = $3;
113 }
114 push @$list, $zvol;
115 }
116 }
117
118 return $list;
119}
120
cc80ed9c
WL
121sub parse_volname {
122 my ($class, $volname) = @_;
123
124 if ($volname =~ m/^(((base|vm)-(\d+)-\S+)\/)?((base)?(vm)?-(\d+)-\S+)$/) {
125 return ('images', $5, $8, $2, $4, $6);
126 }
127
128 die "unable to parse zfs volume name '$volname'\n";
129}
130
7730694e
DM
131# virtual zfs methods (subclass can overwrite them)
132
f3e632d0
WL
133sub path {
134 my ($class, $scfg, $volname) = @_;
135
136 my ($vtype, $name, $vmid) = $class->parse_volname($volname);
137
138 my $path = '';
139
140 if($vtype eq "images"){
141 $path = "/dev/zvol/$scfg->{pool}/$volname";
142 } else {
85fda4dd 143 die "$vtype is not allowed in ZFSPool!";
f3e632d0
WL
144 }
145
146 return ($path, $vmid, $vtype);
147}
148
7730694e
DM
149sub zfs_request {
150 my ($class, $scfg, $timeout, $method, @params) = @_;
151
152 $timeout = 5 if !$timeout;
153
154 my $cmd = [];
155
156 if ($method eq 'zpool_list') {
157 push @$cmd = 'zpool', 'list';
158 } else {
159 push @$cmd, 'zfs', $method;
160 }
161
162 push @$cmd, @params;
163
164 my $msg = '';
165
166 my $output = sub {
167 my $line = shift;
168 $msg .= "$line\n";
169 };
170
171 run_command($cmd, outfunc => $output, timeout => $timeout);
172
173 return $msg;
174}
175
b3ba95e4
WL
176sub alloc_image {
177 my ($class, $storeid, $scfg, $vmid, $fmt, $name, $size) = @_;
178
179 die "unsupported format '$fmt'" if $fmt ne 'raw';
180
181 die "illegal name '$name' - sould be 'vm-$vmid-*'\n"
182 if $name && $name !~ m/^vm-$vmid-/;
183
184 $name = $class->zfs_find_free_diskname($storeid, $scfg, $vmid) if !$name;
185
186 $class->zfs_create_zvol($scfg, $name, $size);
187
188 return $name;
189}
190
e9565df5
WL
191sub free_image {
192 my ($class, $storeid, $scfg, $volname, $isBase) = @_;
193
194 my (undef, $name, undef) = $class->parse_volname($volname);
195
196 $class->zfs_delete_zvol($scfg, $name);
197
198 return undef;
199}
200
ca04180f
WL
201sub list_images {
202 my ($class, $storeid, $scfg, $vmid, $vollist, $cache) = @_;
203
204 $cache->{zfs} = $class->zfs_list_zvol($scfg) if !$cache->{zfs};
205 my $zfspool = $scfg->{pool};
206 my $res = [];
207
208 if (my $dat = $cache->{zfs}->{$zfspool}) {
209
210 foreach my $image (keys %$dat) {
211
212 my $volname = $dat->{$image}->{name};
213 my $parent = $dat->{$image}->{parent};
214
215 my $volid = undef;
216 if ($parent && $parent =~ m/^(\S+)@(\S+)$/) {
217 my ($basename) = ($1);
218 $volid = "$storeid:$basename/$volname";
219 } else {
220 $volid = "$storeid:$volname";
221 }
222
223 my $owner = $dat->{$volname}->{vmid};
224 if ($vollist) {
225 my $found = grep { $_ eq $volid } @$vollist;
226 next if !$found;
227 } else {
228 next if defined ($vmid) && ($owner ne $vmid);
229 }
230
231 my $info = $dat->{$volname};
232 $info->{volid} = $volid;
233 push @$res, $info;
234 }
235 }
236
237 return $res;
238}
239
7730694e
DM
240sub zfs_get_pool_stats {
241 my ($class, $scfg) = @_;
242
243 my $available = 0;
244 my $used = 0;
245
246 my $text = $class->zfs_request($scfg, undef, 'get', '-o', 'value', '-Hp',
247 'available,used', $scfg->{pool});
248
249 my @lines = split /\n/, $text;
250
251 if($lines[0] =~ /^(\d+)$/) {
252 $available = $1;
253 }
254
255 if($lines[1] =~ /^(\d+)$/) {
256 $used = $1;
257 }
258
259 return ($available, $used);
260}
261
262sub zfs_get_zvol_size {
263 my ($class, $scfg, $zvol) = @_;
264
265 my $text = $class->zfs_request($scfg, undef, 'get', '-Hp', 'volsize', "$scfg->{pool}/$zvol");
266
267 if ($text =~ /volsize\s(\d+)/) {
268 return $1;
269 }
270
271 die "Could not get zvol size";
272}
273
274sub zfs_create_zvol {
275 my ($class, $scfg, $zvol, $size) = @_;
276
277 my $cmd = ['create'];
278
279 push @$cmd, '-s' if $scfg->{sparse};
280
281 push @$cmd, '-b', $scfg->{blocksize} if $scfg->{blocksize};
282
283 push @$cmd, '-V', "${size}k", "$scfg->{pool}/$zvol";
284
285 $class->zfs_request($scfg, undef, @$cmd);
286}
287
288sub zfs_delete_zvol {
289 my ($class, $scfg, $zvol) = @_;
290
291 $class->zfs_request($scfg, undef, 'destroy', '-r', "$scfg->{pool}/$zvol");
292}
293
294sub zfs_list_zvol {
295 my ($class, $scfg) = @_;
296
297 my $text = $class->zfs_request($scfg, 10, 'list', '-o', 'name,volsize,origin', '-t', 'volume', '-Hr');
298 my $zvols = zfs_parse_zvol_list($text);
299 return undef if !$zvols;
300
301 my $list = ();
302 foreach my $zvol (@$zvols) {
303 my @values = split('/', $zvol->{name});
304
305 my $image = pop @values;
306 my $pool = join('/', @values);
307
308 next if $image !~ m/^((vm|base)-(\d+)-\S+)$/;
309 my $owner = $3;
310
311 my $parent = $zvol->{origin};
312 if($zvol->{origin} && $zvol->{origin} =~ m/^$scfg->{pool}\/(\S+)$/){
313 $parent = $1;
314 }
315
316 $list->{$pool}->{$image} = {
317 name => $image,
318 size => $zvol->{size},
319 parent => $parent,
320 format => 'raw',
321 vmid => $owner
322 };
323 }
324
325 return $list;
326}
327
328sub zfs_find_free_diskname {
329 my ($class, $storeid, $scfg, $vmid) = @_;
330
331 my $name = undef;
332 my $volumes = $class->zfs_list_zvol($scfg);
333
334 my $disk_ids = {};
335 my $dat = $volumes->{$scfg->{pool}};
336
337 foreach my $image (keys %$dat) {
338 my $volname = $dat->{$image}->{name};
339 if ($volname =~ m/(vm|base)-$vmid-disk-(\d+)/){
340 $disk_ids->{$2} = 1;
341 }
342 }
343
344 for (my $i = 1; $i < 100; $i++) {
345 if (!$disk_ids->{$i}) {
346 return "vm-$vmid-disk-$i";
347 }
348 }
349
350 die "unable to allocate an image name for VM $vmid in storage '$storeid'\n";
351}
352
2fc59177
DM
353sub zfs_get_latest_snapshot {
354 my ($class, $scfg, $volname) = @_;
355
356 # abort rollback if snapshot is not the latest
357 my @params = ('-t', 'snapshot', '-o', 'name', '-s', 'creation');
358 my $text = zfs_request($class, $scfg, undef, 'list', @params);
359 my @snapshots = split(/\n/, $text);
360
361 my $recentsnap;
362 foreach (@snapshots) {
363 if (/$scfg->{pool}\/$volname/) {
364 s/^.*@//;
365 $recentsnap = $_;
366 }
367 }
368
369 return $recentsnap;
370}
371
b5e5f7e3
DM
372sub status {
373 my ($class, $storeid, $scfg, $cache) = @_;
374
375 my $total = 0;
376 my $free = 0;
377 my $used = 0;
378 my $active = 0;
379
380 eval {
381 ($free, $used) = $class->zfs_get_pool_stats($scfg);
382 $active = 1;
383 $total = $free + $used;
384 };
385 warn $@ if $@;
386
387 return ($total, $free, $used, $active);
388}
389
390sub volume_size_info {
391 my ($class, $scfg, $storeid, $volname, $timeout) = @_;
392
393 return $class->zfs_get_zvol_size($scfg, $volname);
394}
395
396sub volume_snapshot {
397 my ($class, $scfg, $storeid, $volname, $snap, $running) = @_;
398
399 $class->zfs_request($scfg, undef, 'snapshot', "$scfg->{pool}/$volname\@$snap");
400}
401
402sub volume_snapshot_delete {
403 my ($class, $scfg, $storeid, $volname, $snap, $running) = @_;
404
405 $class->zfs_request($scfg, undef, 'destroy', "$scfg->{pool}/$volname\@$snap");
406}
407
2b40ffae
WL
408sub volume_snapshot_rollback {
409 my ($class, $scfg, $storeid, $volname, $snap) = @_;
410
411 # abort rollback if snapshot is not the latest
2fc59177 412 my $recentsnap = $class->zfs_get_latest_snapshot($scfg, $volname);
2b40ffae
WL
413 if ($snap ne $recentsnap) {
414 die "cannot rollback, more recent snapshots exist\n";
415 }
416
417 zfs_request($class, $scfg, undef, 'rollback', "$scfg->{pool}/$volname\@$snap");
2b40ffae
WL
418}
419
d4c63dc1
WL
420sub activate_volume {
421 my ($class, $storeid, $scfg, $volname, $exclusive, $cache) = @_;
422 return 1;
423}
424
425sub deactivate_volume {
426 my ($class, $storeid, $scfg, $volname, $exclusive, $cache) = @_;
427 return 1;
428}
5bb8e010 429
d3a282e8
WL
430sub clone_image {
431 my ($class, $scfg, $storeid, $volname, $vmid, $snap) = @_;
432
433 $snap ||= '__base__';
434
435 my ($vtype, $basename, $basevmid, undef, undef, $isBase) =
436 $class->parse_volname($volname);
437
438 die "clone_image only works on base images\n" if !$isBase;
439
440 my $name = $class->zfs_find_free_diskname($storeid, $scfg, $vmid);
441
d3a282e8
WL
442 $class->zfs_request($scfg, undef, 'clone', "$scfg->{pool}/$basename\@$snap", "$scfg->{pool}/$name");
443
444 return $name;
445}
446
447sub create_base {
448 my ($class, $storeid, $scfg, $volname) = @_;
449
450 my $snap = '__base__';
451
452 my ($vtype, $name, $vmid, $basename, $basevmid, $isBase) =
453 $class->parse_volname($volname);
454
455 die "create_base not possible with base image\n" if $isBase;
456
457 my $newname = $name;
458 $newname =~ s/^vm-/base-/;
459
460 my $newvolname = $basename ? "$basename/$newname" : "$newname";
461
462 $class->zfs_request($scfg, undef, 'rename', "$scfg->{pool}/$name", "$scfg->{pool}/$newname");
463
464 my $running = undef; #fixme : is create_base always offline ?
465
466 $class->volume_snapshot($scfg, $storeid, $newname, $snap, $running);
467
468 return $newvolname;
469}
470
2b40ffae
WL
471sub volume_has_feature {
472 my ($class, $scfg, $feature, $storeid, $volname, $snapname, $running) = @_;
473
474 my $features = {
475 snapshot => { current => 1, snap => 1},
476 clone => { base => 1},
477 template => { current => 1},
478 copy => { base => 1, current => 1},
479 };
480
481 my ($vtype, $name, $vmid, $basename, $basevmid, $isBase) =
482 $class->parse_volname($volname);
483
484 my $key = undef;
485
486 if ($snapname) {
487 $key = 'snap';
488 } else {
489 $key = $isBase ? 'base' : 'current';
490 }
491
492 return 1 if $features->{$feature}->{$key};
493
494 return undef;
495}
496
5bb8e010 4971;