]> git.proxmox.com Git - pve-storage.git/blame - PVE/Storage/ZFSPlugin.pm
zfsdir: cleanup zfs_parse_size
[pve-storage.git] / PVE / Storage / ZFSPlugin.pm
CommitLineData
4f914e6e
MR
1package PVE::Storage::ZFSPlugin;
2
3use strict;
4use warnings;
5use IO::File;
6use POSIX;
5332e6c9 7use PVE::Tools qw(run_command);
5bb8e010 8use PVE::Storage::ZFSDirPlugin;
4f914e6e 9
5bb8e010 10use base qw(PVE::Storage::ZFSDirPlugin);
a7d56be6
MR
11use PVE::Storage::LunCmd::Comstar;
12use PVE::Storage::LunCmd::Istgt;
78a64432 13use PVE::Storage::LunCmd::Iet;
4f914e6e
MR
14
15my @ssh_opts = ('-o', 'BatchMode=yes');
16my @ssh_cmd = ('/usr/bin/ssh', @ssh_opts);
3b219e80 17my $id_rsa_path = '/etc/pve/priv/zfs';
4f914e6e 18
a7d56be6 19my $lun_cmds = {
3b219e80
MR
20 create_lu => 1,
21 delete_lu => 1,
22 import_lu => 1,
23 modify_lu => 1,
24 add_view => 1,
25 list_view => 1,
26 list_lu => 1,
a7d56be6
MR
27};
28
29my $zfs_unknown_scsi_provider = sub {
3b219e80 30 my ($provider) = @_;
a7d56be6 31
3b219e80 32 die "$provider: unknown iscsi provider. Available [comstar, istgt, iet]";
a7d56be6
MR
33};
34
35my $zfs_get_base = sub {
3b219e80
MR
36 my ($scfg) = @_;
37
38 if ($scfg->{iscsiprovider} eq 'comstar') {
39 return PVE::Storage::LunCmd::Comstar::get_base;
40 } elsif ($scfg->{iscsiprovider} eq 'istgt') {
41 return PVE::Storage::LunCmd::Istgt::get_base;
42 } elsif ($scfg->{iscsiprovider} eq 'iet') {
43 return PVE::Storage::LunCmd::Iet::get_base;
44 } else {
45 $zfs_unknown_scsi_provider->($scfg->{iscsiprovider});
46 }
a7d56be6
MR
47};
48
4f914e6e
MR
49sub zfs_request {
50 my ($scfg, $timeout, $method, @params) = @_;
51
5332e6c9 52 my $cmdmap;
4f914e6e
MR
53 my $zfscmd;
54 my $target;
3b219e80 55 my $msg;
4f914e6e 56
5332e6c9 57 $timeout = 5 if !$timeout;
4f914e6e 58
3b219e80
MR
59 if ($lun_cmds->{$method}) {
60 if ($scfg->{iscsiprovider} eq 'comstar') {
61 $msg = PVE::Storage::LunCmd::Comstar::run_lun_command($scfg, $timeout, $method, @params);
62 } elsif ($scfg->{iscsiprovider} eq 'istgt') {
63 $msg = PVE::Storage::LunCmd::Istgt::run_lun_command($scfg, $timeout, $method, @params);
64 } elsif ($scfg->{iscsiprovider} eq 'iet') {
65 $msg = PVE::Storage::LunCmd::Iet::run_lun_command($scfg, $timeout, $method, @params);
66 } else {
67 $zfs_unknown_scsi_provider->($scfg->{iscsiprovider});
68 }
69 } else {
70 if ($method eq 'zpool_list') {
71 $zfscmd = 'zpool';
72 $method = 'list',
73 } else {
74 $zfscmd = 'zfs';
75 }
76
77 $target = 'root@' . $scfg->{portal};
78
79 my $cmd = [@ssh_cmd, '-i', "$id_rsa_path/$scfg->{portal}_id_rsa", $target, $zfscmd, $method, @params];
80
81 $msg = '';
82
83 my $output = sub {
84 my $line = shift;
85 $msg .= "$line\n";
86 };
87
88 run_command($cmd, outfunc => $output, timeout => $timeout);
89 }
4f914e6e
MR
90
91 return $msg;
92}
93
4f914e6e 94sub zfs_get_pool_stats {
5332e6c9 95 my ($scfg) = @_;
4f914e6e 96
1fca1464 97 my $available = 0;
5332e6c9 98 my $used = 0;
4f914e6e 99
5332e6c9 100 my $text = zfs_request($scfg, undef, 'get', '-o', 'value', '-Hp',
3b219e80 101 'available,used', $scfg->{pool});
4f914e6e 102
5332e6c9 103 my @lines = split /\n/, $text;
4f914e6e 104
5332e6c9 105 if($lines[0] =~ /^(\d+)$/) {
3b219e80 106 $available = $1;
5332e6c9 107 }
4f914e6e 108
5332e6c9 109 if($lines[1] =~ /^(\d+)$/) {
3b219e80 110 $used = $1;
5332e6c9 111 }
4f914e6e 112
1fca1464 113 return ($available, $used);
4f914e6e
MR
114}
115
116sub zfs_parse_zvol_list {
117 my ($text) = @_;
118
119 my $list = ();
120
121 return $list if !$text;
122
123 my @lines = split /\n/, $text;
124 foreach my $line (@lines) {
3b219e80
MR
125 if ($line =~ /^(.+)\s+([a-zA-Z0-9\.]+|\-)\s+(.+)$/) {
126 my $zvol = {};
5e479180
PRG
127 my @parts = split /\//, $1;
128 my $name = pop @parts;
129 my $pool = join('/', @parts);
130
131 if ($pool !~ /^rpool$/) {
132 next unless $name =~ m!^(\w+)-(\d+)-(\w+)-(\d+)$!;
133 $name = $pool . '/' . $name;
3b219e80 134 } else {
5e479180 135 next;
3b219e80
MR
136 }
137
5e479180 138 $zvol->{pool} = $pool;
3b219e80
MR
139 $zvol->{name} = $name;
140 $zvol->{size} = zfs_parse_size($2);
141 if ($3 !~ /^-$/) {
5e479180 142 $zvol->{origin} = $3;
3b219e80
MR
143 }
144 push @$list, $zvol;
145 }
4f914e6e
MR
146 }
147
148 return $list;
149}
150
151sub zfs_get_lu_name {
152 my ($scfg, $zvol) = @_;
153 my $object;
154
3b219e80 155 my $base = $zfs_get_base->($scfg);
4f914e6e 156 if ($zvol =~ /^.+\/.+/) {
a7d56be6 157 $object = "$base/$zvol";
5332e6c9 158 } else {
a7d56be6 159 $object = "$base/$scfg->{pool}/$zvol";
4f914e6e
MR
160 }
161
a7d56be6
MR
162 my $lu_name = zfs_request($scfg, undef, 'list_lu', $object);
163
3b219e80
MR
164 return $lu_name if $lu_name;
165
4f914e6e
MR
166 die "Could not find lu_name for zvol $zvol";
167}
168
169sub zfs_get_zvol_size {
170 my ($scfg, $zvol) = @_;
171
172 my $text = zfs_request($scfg, undef, 'get', '-Hp', 'volsize', "$scfg->{pool}/$zvol");
173
174 if($text =~ /volsize\s(\d+)/){
3b219e80 175 return $1;
4f914e6e
MR
176 }
177
178 die "Could not get zvol size";
179}
180
181sub zfs_add_lun_mapping_entry {
182 my ($scfg, $zvol, $guid) = @_;
183
184 if (! defined($guid)) {
3b219e80 185 $guid = zfs_get_lu_name($scfg, $zvol);
4f914e6e 186 }
3b219e80 187
4f914e6e
MR
188 zfs_request($scfg, undef, 'add_view', $guid);
189}
190
191sub zfs_delete_lu {
192 my ($scfg, $zvol) = @_;
193
194 my $guid = zfs_get_lu_name($scfg, $zvol);
195
196 zfs_request($scfg, undef, 'delete_lu', $guid);
197}
198
199sub zfs_create_lu {
200 my ($scfg, $zvol) = @_;
201
3b219e80 202 my $base = $zfs_get_base->($scfg);
a7d56be6 203 my $guid = zfs_request($scfg, undef, 'create_lu', "$base/$scfg->{pool}/$zvol");
4f914e6e
MR
204
205 return $guid;
206}
207
208sub zfs_import_lu {
209 my ($scfg, $zvol) = @_;
210
3b219e80 211 my $base = $zfs_get_base->($scfg);
a7d56be6 212 zfs_request($scfg, undef, 'import_lu', "$base/$scfg->{pool}/$zvol");
4f914e6e
MR
213}
214
215sub zfs_resize_lu {
216 my ($scfg, $zvol, $size) = @_;
217
218 my $guid = zfs_get_lu_name($scfg, $zvol);
219
a7d56be6 220 zfs_request($scfg, undef, 'modify_lu', "${size}K", $guid);
4f914e6e
MR
221}
222
223sub zfs_create_zvol {
224 my ($scfg, $zvol, $size) = @_;
a9bd7bdf
CA
225
226 my $sparse = '';
227 if ($scfg->{sparse}) {
228 $sparse = '-s';
229 }
4f914e6e 230
a9bd7bdf 231 zfs_request($scfg, undef, 'create', $sparse, '-b', $scfg->{blocksize}, '-V', "${size}k", "$scfg->{pool}/$zvol");
4f914e6e
MR
232}
233
234sub zfs_delete_zvol {
235 my ($scfg, $zvol) = @_;
236
237 zfs_request($scfg, undef, 'destroy', '-r', "$scfg->{pool}/$zvol");
238}
239
240sub zfs_get_lun_number {
241 my ($scfg, $guid) = @_;
4f914e6e
MR
242
243 die "could not find lun_number for guid $guid" if !$guid;
244
a7d56be6 245 return zfs_request($scfg, undef, 'list_view', $guid);
4f914e6e
MR
246}
247
248sub zfs_list_zvol {
249 my ($scfg) = @_;
250
5e479180 251 my $text = zfs_request($scfg, 10, 'list', '-o', 'name,volsize,origin', '-t', 'volume', '-Hr');
4f914e6e
MR
252 my $zvols = zfs_parse_zvol_list($text);
253 return undef if !$zvols;
254
255 my $list = ();
256 foreach my $zvol (@$zvols) {
3b219e80
MR
257 my @values = split('/', $zvol->{name});
258
5e479180
PRG
259 my $image = pop @values;
260 my $pool = join('/', @values);
3b219e80
MR
261
262 next if $image !~ m/^((vm|base)-(\d+)-\S+)$/;
263 my $owner = $3;
264
265 my $parent = $zvol->{origin};
266 if($zvol->{origin} && $zvol->{origin} =~ m/^$scfg->{pool}\/(\S+)$/){
267 $parent = $1;
268 }
269
270 $list->{$pool}->{$image} = {
271 name => $image,
272 size => $zvol->{size},
273 parent => $parent,
274 format => 'raw',
275 vmid => $owner
276 };
4f914e6e
MR
277 }
278
279 return $list;
280}
281
282# Configuration
283
284sub type {
285 return 'zfs';
286}
287
288sub plugindata {
289 return {
3b219e80 290 content => [ {images => 1}, { images => 1 }],
4f914e6e
MR
291 };
292}
293
294sub properties {
295 return {
3b219e80
MR
296 iscsiprovider => {
297 description => "iscsi provider",
298 type => 'string',
299 },
a9bd7bdf
CA
300 blocksize => {
301 description => "block size",
302 type => 'string',
303 },
70986fd9
CA
304 # this will disable write caching on comstar and istgt.
305 # it is not implemented for iet. iet blockio always operates with
306 # writethrough caching when not in readonly mode
307 nowritecache => {
308 description => "disable write caching on the target",
309 type => 'boolean',
310 },
a9bd7bdf
CA
311 sparse => {
312 description => "use sparse volumes",
313 type => 'boolean',
454c15e2
CA
314 },
315 comstar_tg => {
316 description => "target group for comstar views",
317 type => 'string',
318 },
319 comstar_hg => {
320 description => "host group for comstar views",
321 type => 'string',
322 },
4f914e6e
MR
323 };
324}
325
326sub options {
327 return {
a7d56be6
MR
328 nodes => { optional => 1 },
329 disable => { optional => 1 },
330 portal => { fixed => 1 },
3b219e80 331 target => { fixed => 1 },
a7d56be6 332 pool => { fixed => 1 },
3b219e80
MR
333 blocksize => { fixed => 1 },
334 iscsiprovider => { fixed => 1 },
70986fd9 335 nowritecache => { optional => 1 },
a9bd7bdf 336 sparse => { optional => 1 },
454c15e2
CA
337 comstar_hg => { optional => 1 },
338 comstar_tg => { optional => 1 },
3b219e80 339 content => { optional => 1 },
4f914e6e
MR
340 };
341}
342
343# Storage implementation
344
345sub parse_volname {
346 my ($class, $volname) = @_;
347
348 if ($volname =~ m/^(((base|vm)-(\d+)-\S+)\/)?((base)?(vm)?-(\d+)-\S+)$/) {
3b219e80 349 return ('images', $5, $8, $2, $4, $6);
4f914e6e
MR
350 }
351
352 die "unable to parse zfs volume name '$volname'\n";
353}
354
355sub path {
356 my ($class, $scfg, $volname) = @_;
357
358 my ($vtype, $name, $vmid) = $class->parse_volname($volname);
359
360 my $target = $scfg->{target};
361 my $portal = $scfg->{portal};
362
363 my $guid = zfs_get_lu_name($scfg, $name);
364 my $lun = zfs_get_lun_number($scfg, $guid);
3b219e80 365
4f914e6e 366 my $path = "iscsi://$portal/$target/$lun";
3b219e80 367
4f914e6e
MR
368 return ($path, $vmid, $vtype);
369}
370
371my $find_free_diskname = sub {
372 my ($storeid, $scfg, $vmid) = @_;
373
374 my $name = undef;
375 my $volumes = zfs_list_zvol($scfg);
376
377 my $disk_ids = {};
378 my $dat = $volumes->{$scfg->{pool}};
379
380 foreach my $image (keys %$dat) {
381 my $volname = $dat->{$image}->{name};
382 if ($volname =~ m/(vm|base)-$vmid-disk-(\d+)/){
383 $disk_ids->{$2} = 1;
384 }
385 }
386
387 for (my $i = 1; $i < 100; $i++) {
388 if (!$disk_ids->{$i}) {
389 return "vm-$vmid-disk-$i";
390 }
391 }
392
393 die "unable to allocate an image name for VM $vmid in storage '$storeid'\n";
394};
395
396sub create_base {
397 my ($class, $storeid, $scfg, $volname) = @_;
398
399 my $snap = '__base__';
400
401 my ($vtype, $name, $vmid, $basename, $basevmid, $isBase) =
402 $class->parse_volname($volname);
403
404 die "create_base not possible with base image\n" if $isBase;
405
406 my $newname = $name;
407 $newname =~ s/^vm-/base-/;
408
409 my $newvolname = $basename ? "$basename/$newname" : "$newname";
410
411 zfs_delete_lu($scfg, $name);
412 zfs_request($scfg, undef, 'rename', "$scfg->{pool}/$name", "$scfg->{pool}/$newname");
413
414 my $guid = zfs_create_lu($scfg, $newname);
415 zfs_add_lun_mapping_entry($scfg, $newname, $guid);
416
417 my $running = undef; #fixme : is create_base always offline ?
418
419 $class->volume_snapshot($scfg, $storeid, $newname, $snap, $running);
420
421 return $newvolname;
422}
423
424sub clone_image {
f236eaf8 425 my ($class, $scfg, $storeid, $volname, $vmid, $snap) = @_;
4f914e6e 426
f236eaf8 427 $snap ||= '__base__';
4f914e6e
MR
428
429 my ($vtype, $basename, $basevmid, undef, undef, $isBase) =
430 $class->parse_volname($volname);
431
432 die "clone_image only works on base images\n" if !$isBase;
433
434 my $name = &$find_free_diskname($storeid, $scfg, $vmid);
435
436 warn "clone $volname: $basename to $name\n";
437
438 zfs_request($scfg, undef, 'clone', "$scfg->{pool}/$basename\@$snap", "$scfg->{pool}/$name");
439
440 my $guid = zfs_create_lu($scfg, $name);
441 zfs_add_lun_mapping_entry($scfg, $name, $guid);
442
443 return $name;
444}
445
446sub alloc_image {
447 my ($class, $storeid, $scfg, $vmid, $fmt, $name, $size) = @_;
448
449 die "unsupported format '$fmt'" if $fmt ne 'raw';
450
451 die "illegal name '$name' - sould be 'vm-$vmid-*'\n"
3b219e80 452 if $name && $name !~ m/^vm-$vmid-/;
4f914e6e 453
3fad2603 454 $name = &$find_free_diskname($storeid, $scfg, $vmid) if !$name;
4f914e6e
MR
455
456 zfs_create_zvol($scfg, $name, $size);
457 my $guid = zfs_create_lu($scfg, $name);
458 zfs_add_lun_mapping_entry($scfg, $name, $guid);
459
460 return $name;
461}
462
463sub free_image {
464 my ($class, $storeid, $scfg, $volname, $isBase) = @_;
465
466 my ($vtype, $name, $vmid) = $class->parse_volname($volname);
467
468 zfs_delete_lu($scfg, $name);
469 eval {
470 zfs_delete_zvol($scfg, $name);
471 };
472 do {
473 my $err = $@;
474 my $guid = zfs_create_lu($scfg, $name);
475 zfs_add_lun_mapping_entry($scfg, $name, $guid);
476 die $err;
477 } if $@;
478
479 return undef;
480}
481
482sub list_images {
483 my ($class, $storeid, $scfg, $vmid, $vollist, $cache) = @_;
484
485 $cache->{zfs} = zfs_list_zvol($scfg) if !$cache->{zfs};
486 my $zfspool = $scfg->{pool};
487 my $res = [];
488
489 if (my $dat = $cache->{zfs}->{$zfspool}) {
490
3b219e80 491 foreach my $image (keys %$dat) {
4f914e6e 492
3b219e80
MR
493 my $volname = $dat->{$image}->{name};
494 my $parent = $dat->{$image}->{parent};
4f914e6e 495
3b219e80 496 my $volid = undef;
4f914e6e 497 if ($parent && $parent =~ m/^(\S+)@(\S+)$/) {
3b219e80
MR
498 my ($basename) = ($1);
499 $volid = "$storeid:$basename/$volname";
500 } else {
501 $volid = "$storeid:$volname";
502 }
503
504 my $owner = $dat->{$volname}->{vmid};
505 if ($vollist) {
506 my $found = grep { $_ eq $volid } @$vollist;
507 next if !$found;
508 } else {
509 next if defined ($vmid) && ($owner ne $vmid);
510 }
511
512 my $info = $dat->{$volname};
513 $info->{volid} = $volid;
514 push @$res, $info;
515 }
4f914e6e
MR
516 }
517
518 return $res;
519}
520
521sub status {
522 my ($class, $storeid, $scfg, $cache) = @_;
523
524 my $total = 0;
525 my $free = 0;
526 my $used = 0;
527 my $active = 0;
528
529 eval {
3b219e80
MR
530 ($free, $used) = zfs_get_pool_stats($scfg);
531 $active = 1;
532 $total = $free + $used;
4f914e6e
MR
533 };
534 warn $@ if $@;
535
536 return ($total, $free, $used, $active);
537}
538
539sub activate_storage {
540 my ($class, $storeid, $scfg, $cache) = @_;
541 return 1;
542}
543
544sub deactivate_storage {
545 my ($class, $storeid, $scfg, $cache) = @_;
546 return 1;
547}
548
549sub activate_volume {
550 my ($class, $storeid, $scfg, $volname, $exclusive, $cache) = @_;
551 return 1;
552}
553
554sub deactivate_volume {
555 my ($class, $storeid, $scfg, $volname, $exclusive, $cache) = @_;
556 return 1;
557}
558
559sub volume_size_info {
560 my ($class, $scfg, $storeid, $volname, $timeout) = @_;
561
562 return zfs_get_zvol_size($scfg, $volname);
563}
564
565sub volume_resize {
566 my ($class, $scfg, $storeid, $volname, $size, $running) = @_;
567
568 my $new_size = ($size/1024);
569
570 zfs_request($scfg, undef, 'set', 'volsize=' . $new_size . 'k', "$scfg->{pool}/$volname");
571 zfs_resize_lu($scfg, $volname, $new_size);
572}
573
574sub volume_snapshot {
575 my ($class, $scfg, $storeid, $volname, $snap, $running) = @_;
576
577 zfs_request($scfg, undef, 'snapshot', "$scfg->{pool}/$volname\@$snap");
578}
579
580sub volume_snapshot_rollback {
581 my ($class, $scfg, $storeid, $volname, $snap) = @_;
582
a315b9ff
CA
583 # abort rollback if snapshot is not the latest
584 my @params = ('-t', 'snapshot', '-o', 'name', '-s', 'creation');
585 my $text = zfs_request($scfg, undef, 'list', @params);
586 my @snapshots = split(/\n/, $text);
587 my $recentsnap = undef;
588 foreach (@snapshots) {
589 if (/$scfg->{pool}\/$volname/) {
590 s/^.*@//;
591 $recentsnap = $_;
592 }
593 }
594 if ($snap ne $recentsnap) {
595 die "cannot rollback, more recent snapshots exist\n";
596 }
597
4f914e6e
MR
598 zfs_delete_lu($scfg, $volname);
599
600 zfs_request($scfg, undef, 'rollback', "$scfg->{pool}/$volname\@$snap");
601
602 zfs_import_lu($scfg, $volname);
603
604 zfs_add_lun_mapping_entry($scfg, $volname);
605}
606
607sub volume_snapshot_delete {
608 my ($class, $scfg, $storeid, $volname, $snap, $running) = @_;
609
610 zfs_request($scfg, undef, 'destroy', "$scfg->{pool}/$volname\@$snap");
611}
612
613sub volume_has_feature {
614 my ($class, $scfg, $feature, $storeid, $volname, $snapname, $running) = @_;
615
616 my $features = {
3b219e80
MR
617 snapshot => { current => 1, snap => 1},
618 clone => { base => 1},
619 template => { current => 1},
620 copy => { base => 1, current => 1},
4f914e6e
MR
621 };
622
623 my ($vtype, $name, $vmid, $basename, $basevmid, $isBase) =
3b219e80 624 $class->parse_volname($volname);
4f914e6e
MR
625
626 my $key = undef;
5332e6c9
DM
627
628 if ($snapname) {
3b219e80 629 $key = 'snap';
4f914e6e 630 } else {
3b219e80 631 $key = $isBase ? 'base' : 'current';
4f914e6e 632 }
5332e6c9 633
4f914e6e
MR
634 return 1 if $features->{$feature}->{$key};
635
636 return undef;
637}
638
6391;