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