1 package PVE
::Storage
::ZFSPlugin
;
7 use PVE
::Tools
qw(run_command);
8 use PVE
::Storage
::Plugin
;
9 use Digest
::MD5
qw(md5_hex);
11 use base
qw(PVE::Storage::Plugin);
13 my @ssh_opts = ('-o', 'BatchMode=yes');
14 my @ssh_cmd = ('/usr/bin/ssh', @ssh_opts);
17 my ($scfg, $timeout, $method, @params) = @_;
23 $timeout = 5 if !$timeout;
25 if ($scfg->{iscsiprovider
} eq 'comstar') {
26 my $stmfadmcmd = "/usr/sbin/stmfadm";
27 my $sbdadmcmd = "/usr/sbin/sbdadm";
30 create_lu
=> { cmd
=> $stmfadmcmd, method => 'create-lu' },
31 delete_lu
=> { cmd
=> $stmfadmcmd, method => 'delete-lu' },
32 import_lu
=> { cmd
=> $stmfadmcmd, method => 'import-lu' },
33 modify_lu
=> { cmd
=> $stmfadmcmd, method => 'modify-lu' },
34 add_view
=> { cmd
=> $stmfadmcmd, method => 'add-view' },
35 list_view
=> { cmd
=> $stmfadmcmd, method => 'list-view' },
36 list_lu
=> { cmd
=> $sbdadmcmd, method => 'list-lu' },
37 zpool_list
=> { cmd
=> 'zpool', method => 'list' },
40 die 'unknown iscsi provider. Available [comstar]';
43 if ($cmdmap->{$method}) {
44 $zfscmd = $cmdmap->{$method}->{cmd
};
45 $method = $cmdmap->{$method}->{method};
51 $zfscmd = 'sudo ' . $zfscmd;
52 $target = $scfg->{portal
};
54 $target = 'root@' . $scfg->{portal
};
57 my $cmd = [@ssh_cmd, $target, $zfscmd, $method, @params];
66 run_command
($cmd, outfunc
=> $output, timeout
=> $timeout);
76 if ($text =~ m/^(\d+(\.\d+)?)([TGMK])?$/) {
77 my ($size, $reminder, $unit) = ($1, $2, $3);
78 return $size if !$unit;
81 } elsif ($unit eq 'M') {
83 } elsif ($unit eq 'G') {
84 $size *= 1024*1024*1024;
85 } elsif ($unit eq 'T') {
86 $size *= 1024*1024*1024*1024;
98 sub zfs_get_pool_stats
{
104 my $text = zfs_request
($scfg, undef, 'get', '-o', 'value', '-Hp',
105 'available,used', $scfg->{pool
});
107 my @lines = split /\n/, $text;
109 if($lines[0] =~ /^(\d+)$/) {
113 if($lines[1] =~ /^(\d+)$/) {
117 return ($size, $used);
120 sub zfs_parse_zvol_list
{
125 return $list if !$text;
127 my @lines = split /\n/, $text;
128 foreach my $line (@lines) {
129 if ($line =~ /^(.+)\s+([a-zA-Z0-9\.]+|\-)\s+(.+)$/) {
133 my @zvols = split /\//, $1;
134 my $pool = $zvols[0];
136 if (scalar(@zvols) == 2 && $zvols[0] !~ /^rpool$/) {
138 next unless $disk =~ m!^(\w+)-(\d+)-(\w+)-(\d+)$!;
139 $name = $pool . '/' . $disk;
144 $zvol->{name
} = $name;
145 $zvol->{size
} = zfs_parse_size
($2);
147 $zvol->{origin
} = $3;
156 sub zfs_get_lu_name
{
157 my ($scfg, $zvol) = @_;
160 if ($zvol =~ /^.+\/.+/) {
161 $object = "/dev/zvol/rdsk/$zvol";
163 $object = "/dev/zvol/rdsk/$scfg->{pool}/$zvol";
166 my $text = zfs_request
($scfg, undef, 'list_lu');
167 my @lines = split /\n/, $text;
168 foreach my $line (@lines) {
169 return $1 if ($line =~ /(\w+)\s+\d+\s+$object$/);
171 die "Could not find lu_name for zvol $zvol";
174 sub zfs_get_zvol_size
{
175 my ($scfg, $zvol) = @_;
177 my $text = zfs_request
($scfg, undef, 'get', '-Hp', 'volsize', "$scfg->{pool}/$zvol");
179 if($text =~ /volsize\s(\d+)/){
183 die "Could not get zvol size";
186 sub zfs_add_lun_mapping_entry
{
187 my ($scfg, $zvol, $guid) = @_;
189 if (! defined($guid)) {
190 $guid = zfs_get_lu_name
($scfg, $zvol);
193 zfs_request
($scfg, undef, 'add_view', $guid);
197 my ($scfg, $zvol) = @_;
199 my $guid = zfs_get_lu_name
($scfg, $zvol);
201 zfs_request
($scfg, undef, 'delete_lu', $guid);
205 my ($scfg, $zvol) = @_;
207 my $prefix = '600144f';
208 my $digest = md5_hex
($zvol);
209 $digest =~ /(\w{7}(.*))/;
210 my $guid = "$prefix$2";
212 zfs_request
($scfg, undef, 'create_lu', '-p', 'wcd=false', '-p', "guid=$guid", "/dev/zvol/rdsk/$scfg->{pool}/$zvol");
218 my ($scfg, $zvol) = @_;
220 zfs_request
($scfg, undef, 'import_lu', "/dev/zvol/rdsk/$scfg->{pool}/$zvol");
224 my ($scfg, $zvol, $size) = @_;
226 my $guid = zfs_get_lu_name
($scfg, $zvol);
228 zfs_request
($scfg, undef, 'modify_lu', '-s', "${size}K", $guid);
231 sub zfs_create_zvol
{
232 my ($scfg, $zvol, $size) = @_;
234 zfs_request
($scfg, undef, 'create', '-b', $scfg->{blocksize
}, '-V', "${size}k", "$scfg->{pool}/$zvol");
237 sub zfs_delete_zvol
{
238 my ($scfg, $zvol) = @_;
240 zfs_request
($scfg, undef, 'destroy', '-r', "$scfg->{pool}/$zvol");
243 sub zfs_get_lun_number
{
244 my ($scfg, $guid) = @_;
247 die "could not find lun_number for guid $guid" if !$guid;
249 my $text = zfs_request
($scfg, undef, 'list_view', '-l', $guid);
250 my @lines = split /\n/, $text;
251 foreach my $line (@lines) {
252 if ($line =~ /^\s*LUN\s*:\s*(\d+)$/) {
264 my $text = zfs_request
($scfg, 10, 'list', '-o', 'name,volsize,origin', '-Hr');
265 my $zvols = zfs_parse_zvol_list
($text);
266 return undef if !$zvols;
269 foreach my $zvol (@$zvols) {
270 my @values = split('/', $zvol->{name
});
272 my $pool = $values[0];
273 my $image = $values[1];
275 next if $image !~ m/^((vm|base)-(\d+)-\S+)$/;
278 my $parent = $zvol->{origin
};
279 if($zvol->{origin
} && $zvol->{origin
} =~ m/^$scfg->{pool}\/(\S
+)$/){
283 $list->{$pool}->{$image} = {
285 size
=> $zvol->{size
},
303 content
=> [ {images
=> 1}, { images
=> 1 }],
310 description
=> "chap",
314 description
=> "password",
318 description
=> "iscsi provider",
322 description
=> "use sudo",
330 nodes
=> { optional
=> 1 },
331 disable
=> { optional
=> 1 },
332 portal
=> { fixed
=> 1 },
333 target
=> { fixed
=> 1 },
334 pool
=> { fixed
=> 1 },
335 chap
=> { optional
=> 1 },
336 pwd
=> { optional
=> 1 },
337 blocksize
=> { fixed
=> 1 },
338 iscsiprovider
=> { fixed
=> 1 },
339 sudo
=> { optional
=> 1 },
340 content
=> { optional
=> 1 },
344 # Storage implementation
347 my ($class, $volname) = @_;
349 if ($volname =~ m/^(((base|vm)-(\d+)-\S+)\/)?
((base
)?
(vm
)?
-(\d
+)-\S
+)$/) {
350 return ('images', $5, $8, $2, $4, $6);
353 die "unable to parse zfs volume name '$volname'\n";
357 my ($class, $scfg, $volname) = @_;
359 my ($vtype, $name, $vmid) = $class->parse_volname($volname);
361 my $target = $scfg->{target
};
362 my $portal = $scfg->{portal
};
364 my $guid = zfs_get_lu_name
($scfg, $name);
365 my $lun = zfs_get_lun_number
($scfg, $guid);
367 my $path = "iscsi://$portal/$target/$lun";
369 return ($path, $vmid, $vtype);
372 my $find_free_diskname = sub {
373 my ($storeid, $scfg, $vmid) = @_;
376 my $volumes = zfs_list_zvol
($scfg);
379 my $dat = $volumes->{$scfg->{pool
}};
381 foreach my $image (keys %$dat) {
382 my $volname = $dat->{$image}->{name
};
383 if ($volname =~ m/(vm|base)-$vmid-disk-(\d+)/){
388 for (my $i = 1; $i < 100; $i++) {
389 if (!$disk_ids->{$i}) {
390 return "vm-$vmid-disk-$i";
394 die "unable to allocate an image name for VM $vmid in storage '$storeid'\n";
398 my ($class, $storeid, $scfg, $volname) = @_;
400 my $snap = '__base__';
402 my ($vtype, $name, $vmid, $basename, $basevmid, $isBase) =
403 $class->parse_volname($volname);
405 die "create_base not possible with base image\n" if $isBase;
408 $newname =~ s/^vm-/base-/;
410 my $newvolname = $basename ?
"$basename/$newname" : "$newname";
412 zfs_delete_lu
($scfg, $name);
413 zfs_request
($scfg, undef, 'rename', "$scfg->{pool}/$name", "$scfg->{pool}/$newname");
415 my $guid = zfs_create_lu
($scfg, $newname);
416 zfs_add_lun_mapping_entry
($scfg, $newname, $guid);
418 my $running = undef; #fixme : is create_base always offline ?
420 $class->volume_snapshot($scfg, $storeid, $newname, $snap, $running);
426 my ($class, $scfg, $storeid, $volname, $vmid) = @_;
428 my $snap = '__base__';
430 my ($vtype, $basename, $basevmid, undef, undef, $isBase) =
431 $class->parse_volname($volname);
433 die "clone_image only works on base images\n" if !$isBase;
435 my $name = &$find_free_diskname($storeid, $scfg, $vmid);
437 warn "clone $volname: $basename to $name\n";
439 zfs_request
($scfg, undef, 'clone', "$scfg->{pool}/$basename\@$snap", "$scfg->{pool}/$name");
441 my $guid = zfs_create_lu
($scfg, $name);
442 zfs_add_lun_mapping_entry
($scfg, $name, $guid);
448 my ($class, $storeid, $scfg, $vmid, $fmt, $name, $size) = @_;
450 die "unsupported format '$fmt'" if $fmt ne 'raw';
452 die "illegal name '$name' - sould be 'vm-$vmid-*'\n"
453 if $name && $name !~ m/^vm-$vmid-/;
455 $name = &$find_free_diskname($storeid, $scfg, $vmid);
457 zfs_create_zvol
($scfg, $name, $size);
458 my $guid = zfs_create_lu
($scfg, $name);
459 zfs_add_lun_mapping_entry
($scfg, $name, $guid);
465 my ($class, $storeid, $scfg, $volname, $isBase) = @_;
467 my ($vtype, $name, $vmid) = $class->parse_volname($volname);
469 zfs_delete_lu
($scfg, $name);
471 zfs_delete_zvol
($scfg, $name);
475 my $guid = zfs_create_lu
($scfg, $name);
476 zfs_add_lun_mapping_entry
($scfg, $name, $guid);
484 my ($class, $storeid, $scfg, $vmid, $vollist, $cache) = @_;
486 $cache->{zfs
} = zfs_list_zvol
($scfg) if !$cache->{zfs
};
487 my $zfspool = $scfg->{pool
};
490 if (my $dat = $cache->{zfs
}->{$zfspool}) {
492 foreach my $image (keys %$dat) {
494 my $volname = $dat->{$image}->{name
};
495 my $parent = $dat->{$image}->{parent
};
498 if ($parent && $parent =~ m/^(\S+)@(\S+)$/) {
499 my ($basename) = ($1);
500 $volid = "$storeid:$basename/$volname";
502 $volid = "$storeid:$volname";
505 my $owner = $dat->{$volname}->{vmid
};
507 my $found = grep { $_ eq $volid } @$vollist;
510 next if defined ($vmid) && ($owner ne $vmid);
513 my $info = $dat->{$volname};
514 $info->{volid
} = $volid;
523 my ($class, $storeid, $scfg, $cache) = @_;
531 ($total, $used) = zfs_get_pool_stats
($scfg);
533 $free = $total - $used;
537 return ($total, $free, $used, $active);
540 sub activate_storage
{
541 my ($class, $storeid, $scfg, $cache) = @_;
545 sub deactivate_storage
{
546 my ($class, $storeid, $scfg, $cache) = @_;
550 sub activate_volume
{
551 my ($class, $storeid, $scfg, $volname, $exclusive, $cache) = @_;
555 sub deactivate_volume
{
556 my ($class, $storeid, $scfg, $volname, $exclusive, $cache) = @_;
560 sub volume_size_info
{
561 my ($class, $scfg, $storeid, $volname, $timeout) = @_;
563 return zfs_get_zvol_size
($scfg, $volname);
567 my ($class, $scfg, $storeid, $volname, $size, $running) = @_;
569 my $new_size = ($size/1024);
571 zfs_request
($scfg, undef, 'set', 'volsize=' . $new_size . 'k', "$scfg->{pool}/$volname");
572 zfs_resize_lu
($scfg, $volname, $new_size);
575 sub volume_snapshot
{
576 my ($class, $scfg, $storeid, $volname, $snap, $running) = @_;
578 zfs_request
($scfg, undef, 'snapshot', "$scfg->{pool}/$volname\@$snap");
581 sub volume_snapshot_rollback
{
582 my ($class, $scfg, $storeid, $volname, $snap) = @_;
584 zfs_delete_lu
($scfg, $volname);
586 zfs_request
($scfg, undef, 'rollback', "$scfg->{pool}/$volname\@$snap");
588 zfs_import_lu
($scfg, $volname);
590 zfs_add_lun_mapping_entry
($scfg, $volname);
593 sub volume_snapshot_delete
{
594 my ($class, $scfg, $storeid, $volname, $snap, $running) = @_;
596 zfs_request
($scfg, undef, 'destroy', "$scfg->{pool}/$volname\@$snap");
599 sub volume_has_feature
{
600 my ($class, $scfg, $feature, $storeid, $volname, $snapname, $running) = @_;
603 snapshot
=> { current
=> 1, snap
=> 1},
604 clone
=> { base
=> 1},
605 template
=> { current
=> 1},
606 copy
=> { base
=> 1, current
=> 1},
609 my ($vtype, $name, $vmid, $basename, $basevmid, $isBase) =
610 $class->parse_volname($volname);
617 $key = $isBase ?
'base' : 'current';
620 return 1 if $features->{$feature}->{$key};