]> git.proxmox.com Git - pve-storage.git/blame - PVE/Storage/ZFSPoolPlugin.pm
correctly parse lxc backup files
[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 = {};
8a55ff7d
WL
97 my $size = $2;
98 my $origin = $3;
7730694e
DM
99 my @parts = split /\//, $1;
100 my $name = pop @parts;
101 my $pool = join('/', @parts);
102
90a11abe
WL
103 next unless $name =~ m!^(\w+)-(\d+)-(\w+)-(\d+)$!;
104 $name = $pool . '/' . $name;
7730694e
DM
105
106 $zvol->{pool} = $pool;
107 $zvol->{name} = $name;
8a55ff7d 108 $zvol->{size} = zfs_parse_size($size);
7730694e 109 if ($3 !~ /^-$/) {
8a55ff7d 110 $zvol->{origin} = $origin;
7730694e
DM
111 }
112 push @$list, $zvol;
113 }
114 }
115
116 return $list;
117}
118
cc80ed9c
WL
119sub parse_volname {
120 my ($class, $volname) = @_;
121
122 if ($volname =~ m/^(((base|vm)-(\d+)-\S+)\/)?((base)?(vm)?-(\d+)-\S+)$/) {
123 return ('images', $5, $8, $2, $4, $6);
124 }
125
126 die "unable to parse zfs volume name '$volname'\n";
127}
128
7730694e
DM
129# virtual zfs methods (subclass can overwrite them)
130
f3e632d0
WL
131sub path {
132 my ($class, $scfg, $volname) = @_;
133
134 my ($vtype, $name, $vmid) = $class->parse_volname($volname);
135
136 my $path = '';
137
138 if($vtype eq "images"){
139 $path = "/dev/zvol/$scfg->{pool}/$volname";
140 } else {
85fda4dd 141 die "$vtype is not allowed in ZFSPool!";
f3e632d0
WL
142 }
143
144 return ($path, $vmid, $vtype);
145}
146
7730694e
DM
147sub zfs_request {
148 my ($class, $scfg, $timeout, $method, @params) = @_;
149
150 $timeout = 5 if !$timeout;
151
152 my $cmd = [];
153
154 if ($method eq 'zpool_list') {
86d47239 155 push @$cmd, 'zpool', 'list';
7730694e
DM
156 } else {
157 push @$cmd, 'zfs', $method;
158 }
159
160 push @$cmd, @params;
161
162 my $msg = '';
163
164 my $output = sub {
165 my $line = shift;
166 $msg .= "$line\n";
167 };
168
1f390a30 169 run_command($cmd, errmsg => "zfs error", outfunc => $output, timeout => $timeout);
7730694e
DM
170
171 return $msg;
172}
173
b3ba95e4
WL
174sub alloc_image {
175 my ($class, $storeid, $scfg, $vmid, $fmt, $name, $size) = @_;
176
177 die "unsupported format '$fmt'" if $fmt ne 'raw';
178
179 die "illegal name '$name' - sould be 'vm-$vmid-*'\n"
180 if $name && $name !~ m/^vm-$vmid-/;
181
82e08809
WL
182 my $volname = $name;
183
184 $volname = $class->zfs_find_free_diskname($storeid, $scfg, $vmid) if !$volname;
185
186 $class->zfs_create_zvol($scfg, $volname, $size);
76fd7dc7 187
82e08809 188 my $devname = "/dev/zvol/$scfg->{pool}/$volname";
76fd7dc7
DM
189
190 run_command("udevadm trigger --subsystem-match block");
191 system("udevadm settle --timeout 10 --exit-if-exists=${devname}");
b3ba95e4 192
82e08809 193 return $volname;
b3ba95e4
WL
194}
195
e9565df5
WL
196sub free_image {
197 my ($class, $storeid, $scfg, $volname, $isBase) = @_;
198
199 my (undef, $name, undef) = $class->parse_volname($volname);
200
201 $class->zfs_delete_zvol($scfg, $name);
202
203 return undef;
204}
205
ca04180f
WL
206sub list_images {
207 my ($class, $storeid, $scfg, $vmid, $vollist, $cache) = @_;
208
209 $cache->{zfs} = $class->zfs_list_zvol($scfg) if !$cache->{zfs};
210 my $zfspool = $scfg->{pool};
211 my $res = [];
212
213 if (my $dat = $cache->{zfs}->{$zfspool}) {
214
215 foreach my $image (keys %$dat) {
216
217 my $volname = $dat->{$image}->{name};
218 my $parent = $dat->{$image}->{parent};
219
220 my $volid = undef;
221 if ($parent && $parent =~ m/^(\S+)@(\S+)$/) {
222 my ($basename) = ($1);
223 $volid = "$storeid:$basename/$volname";
224 } else {
225 $volid = "$storeid:$volname";
226 }
227
228 my $owner = $dat->{$volname}->{vmid};
229 if ($vollist) {
230 my $found = grep { $_ eq $volid } @$vollist;
231 next if !$found;
232 } else {
233 next if defined ($vmid) && ($owner ne $vmid);
234 }
235
236 my $info = $dat->{$volname};
237 $info->{volid} = $volid;
238 push @$res, $info;
239 }
240 }
241
242 return $res;
243}
244
7730694e
DM
245sub zfs_get_pool_stats {
246 my ($class, $scfg) = @_;
247
248 my $available = 0;
249 my $used = 0;
250
251 my $text = $class->zfs_request($scfg, undef, 'get', '-o', 'value', '-Hp',
252 'available,used', $scfg->{pool});
253
254 my @lines = split /\n/, $text;
255
256 if($lines[0] =~ /^(\d+)$/) {
257 $available = $1;
258 }
259
260 if($lines[1] =~ /^(\d+)$/) {
261 $used = $1;
262 }
263
264 return ($available, $used);
265}
266
267sub zfs_get_zvol_size {
268 my ($class, $scfg, $zvol) = @_;
269
270 my $text = $class->zfs_request($scfg, undef, 'get', '-Hp', 'volsize', "$scfg->{pool}/$zvol");
271
272 if ($text =~ /volsize\s(\d+)/) {
273 return $1;
274 }
275
276 die "Could not get zvol size";
277}
278
279sub zfs_create_zvol {
280 my ($class, $scfg, $zvol, $size) = @_;
281
282 my $cmd = ['create'];
283
284 push @$cmd, '-s' if $scfg->{sparse};
285
286 push @$cmd, '-b', $scfg->{blocksize} if $scfg->{blocksize};
287
288 push @$cmd, '-V', "${size}k", "$scfg->{pool}/$zvol";
289
290 $class->zfs_request($scfg, undef, @$cmd);
291}
292
293sub zfs_delete_zvol {
294 my ($class, $scfg, $zvol) = @_;
295
1f390a30
WL
296 my $err;
297
298 for (my $i = 0; $i < 6; $i++) {
299
300 eval { $class->zfs_request($scfg, undef, 'destroy', '-r', "$scfg->{pool}/$zvol"); };
301 if ($err = $@) {
302 if ($err =~ m/^zfs error:(.*): dataset is busy.*/) {
303 sleep(1);
304 } else {
305 die $err;
306 }
307 } else {
308 last;
309 }
310 }
311
312 die $err if $err;
7730694e
DM
313}
314
315sub zfs_list_zvol {
316 my ($class, $scfg) = @_;
317
318 my $text = $class->zfs_request($scfg, 10, 'list', '-o', 'name,volsize,origin', '-t', 'volume', '-Hr');
319 my $zvols = zfs_parse_zvol_list($text);
320 return undef if !$zvols;
321
322 my $list = ();
323 foreach my $zvol (@$zvols) {
324 my @values = split('/', $zvol->{name});
325
326 my $image = pop @values;
327 my $pool = join('/', @values);
328
329 next if $image !~ m/^((vm|base)-(\d+)-\S+)$/;
330 my $owner = $3;
331
332 my $parent = $zvol->{origin};
333 if($zvol->{origin} && $zvol->{origin} =~ m/^$scfg->{pool}\/(\S+)$/){
334 $parent = $1;
335 }
336
337 $list->{$pool}->{$image} = {
338 name => $image,
339 size => $zvol->{size},
340 parent => $parent,
341 format => 'raw',
342 vmid => $owner
343 };
344 }
345
346 return $list;
347}
348
349sub zfs_find_free_diskname {
350 my ($class, $storeid, $scfg, $vmid) = @_;
351
352 my $name = undef;
353 my $volumes = $class->zfs_list_zvol($scfg);
354
355 my $disk_ids = {};
356 my $dat = $volumes->{$scfg->{pool}};
357
358 foreach my $image (keys %$dat) {
359 my $volname = $dat->{$image}->{name};
360 if ($volname =~ m/(vm|base)-$vmid-disk-(\d+)/){
361 $disk_ids->{$2} = 1;
362 }
363 }
364
365 for (my $i = 1; $i < 100; $i++) {
366 if (!$disk_ids->{$i}) {
367 return "vm-$vmid-disk-$i";
368 }
369 }
370
371 die "unable to allocate an image name for VM $vmid in storage '$storeid'\n";
372}
373
2fc59177
DM
374sub zfs_get_latest_snapshot {
375 my ($class, $scfg, $volname) = @_;
376
377 # abort rollback if snapshot is not the latest
378 my @params = ('-t', 'snapshot', '-o', 'name', '-s', 'creation');
379 my $text = zfs_request($class, $scfg, undef, 'list', @params);
380 my @snapshots = split(/\n/, $text);
381
382 my $recentsnap;
383 foreach (@snapshots) {
384 if (/$scfg->{pool}\/$volname/) {
385 s/^.*@//;
386 $recentsnap = $_;
387 }
388 }
389
390 return $recentsnap;
391}
392
b5e5f7e3
DM
393sub status {
394 my ($class, $storeid, $scfg, $cache) = @_;
395
396 my $total = 0;
397 my $free = 0;
398 my $used = 0;
399 my $active = 0;
400
401 eval {
402 ($free, $used) = $class->zfs_get_pool_stats($scfg);
403 $active = 1;
404 $total = $free + $used;
405 };
406 warn $@ if $@;
407
408 return ($total, $free, $used, $active);
409}
410
411sub volume_size_info {
412 my ($class, $scfg, $storeid, $volname, $timeout) = @_;
413
414 return $class->zfs_get_zvol_size($scfg, $volname);
415}
416
417sub volume_snapshot {
418 my ($class, $scfg, $storeid, $volname, $snap, $running) = @_;
419
420 $class->zfs_request($scfg, undef, 'snapshot', "$scfg->{pool}/$volname\@$snap");
421}
422
423sub volume_snapshot_delete {
424 my ($class, $scfg, $storeid, $volname, $snap, $running) = @_;
425
426 $class->zfs_request($scfg, undef, 'destroy', "$scfg->{pool}/$volname\@$snap");
427}
428
2b40ffae
WL
429sub volume_snapshot_rollback {
430 my ($class, $scfg, $storeid, $volname, $snap) = @_;
431
1597f1f9
WL
432 zfs_request($class, $scfg, undef, 'rollback', "$scfg->{pool}/$volname\@$snap");
433}
434
435sub volume_rollback_is_possible {
436 my ($class, $scfg, $storeid, $volname, $snap) = @_;
437
2fc59177 438 my $recentsnap = $class->zfs_get_latest_snapshot($scfg, $volname);
2b40ffae 439 if ($snap ne $recentsnap) {
1597f1f9 440 die "can't rollback, more recent snapshots exist\n";
2b40ffae
WL
441 }
442
1597f1f9 443 return 1;
2b40ffae
WL
444}
445
0a3d992f
DM
446sub activate_storage {
447 my ($class, $storeid, $scfg, $cache) = @_;
86d47239
WL
448
449 my @param = ('-o', 'name', '-H');
450
451 my $text = zfs_request($class, $scfg, undef, 'zpool_list', @param);
93124ef4
DM
452
453 # Note: $scfg->{pool} can include dataset <pool>/<dataset>
454 my $pool = $scfg->{pool};
455 $pool =~ s!/.*$!!;
456
457 if ($text !~ $pool) {
86d47239
WL
458 run_command("zpool import -d /dev/disk/by-id/ -a");
459 }
0a3d992f
DM
460 return 1;
461}
462
463sub deactivate_storage {
464 my ($class, $storeid, $scfg, $cache) = @_;
465 return 1;
466}
467
d4c63dc1
WL
468sub activate_volume {
469 my ($class, $storeid, $scfg, $volname, $exclusive, $cache) = @_;
470 return 1;
471}
472
473sub deactivate_volume {
474 my ($class, $storeid, $scfg, $volname, $exclusive, $cache) = @_;
475 return 1;
476}
5bb8e010 477
d3a282e8
WL
478sub clone_image {
479 my ($class, $scfg, $storeid, $volname, $vmid, $snap) = @_;
480
481 $snap ||= '__base__';
482
483 my ($vtype, $basename, $basevmid, undef, undef, $isBase) =
484 $class->parse_volname($volname);
485
486 die "clone_image only works on base images\n" if !$isBase;
487
488 my $name = $class->zfs_find_free_diskname($storeid, $scfg, $vmid);
489
d3a282e8
WL
490 $class->zfs_request($scfg, undef, 'clone', "$scfg->{pool}/$basename\@$snap", "$scfg->{pool}/$name");
491
492 return $name;
493}
494
495sub create_base {
496 my ($class, $storeid, $scfg, $volname) = @_;
497
498 my $snap = '__base__';
499
500 my ($vtype, $name, $vmid, $basename, $basevmid, $isBase) =
501 $class->parse_volname($volname);
502
503 die "create_base not possible with base image\n" if $isBase;
504
505 my $newname = $name;
506 $newname =~ s/^vm-/base-/;
507
508 my $newvolname = $basename ? "$basename/$newname" : "$newname";
509
510 $class->zfs_request($scfg, undef, 'rename', "$scfg->{pool}/$name", "$scfg->{pool}/$newname");
511
512 my $running = undef; #fixme : is create_base always offline ?
513
514 $class->volume_snapshot($scfg, $storeid, $newname, $snap, $running);
515
516 return $newvolname;
517}
518
a4034b9f
WL
519sub volume_resize {
520 my ($class, $scfg, $storeid, $volname, $size, $running) = @_;
521
522 my $new_size = ($size/1024);
523
524 $class->zfs_request($scfg, undef, 'set', 'volsize=' . $new_size . 'k', "$scfg->{pool}/$volname");
525
526 return $new_size;
527}
528
2b40ffae
WL
529sub volume_has_feature {
530 my ($class, $scfg, $feature, $storeid, $volname, $snapname, $running) = @_;
531
532 my $features = {
533 snapshot => { current => 1, snap => 1},
534 clone => { base => 1},
535 template => { current => 1},
536 copy => { base => 1, current => 1},
537 };
538
539 my ($vtype, $name, $vmid, $basename, $basevmid, $isBase) =
540 $class->parse_volname($volname);
541
542 my $key = undef;
543
544 if ($snapname) {
545 $key = 'snap';
546 } else {
547 $key = $isBase ? 'base' : 'current';
548 }
549
550 return 1 if $features->{$feature}->{$key};
551
552 return undef;
553}
554
5bb8e010 5551;