X-Git-Url: https://git.proxmox.com/?a=blobdiff_plain;f=PVE%2FStorage%2FZFSPlugin.pm;h=63b95517adad6a544bd15ba1d06328e1fcf9119a;hb=32dbc619a5ab7f45a9b0bb28e28a458e4a3fa2ce;hp=2d422d7b853b9e3c6040535e38ffba32f2b4c48b;hpb=a9bd7bdfdc894420fc03cebc2b2166d70105c1f1;p=pve-storage.git diff --git a/PVE/Storage/ZFSPlugin.pm b/PVE/Storage/ZFSPlugin.pm index 2d422d7..63b9551 100644 --- a/PVE/Storage/ZFSPlugin.pm +++ b/PVE/Storage/ZFSPlugin.pm @@ -5,12 +5,15 @@ use warnings; use IO::File; use POSIX; use PVE::Tools qw(run_command); -use PVE::Storage::Plugin; +use PVE::Storage::ZFSPoolPlugin; +use PVE::RPCEnvironment; -use base qw(PVE::Storage::Plugin); +use base qw(PVE::Storage::ZFSPoolPlugin); use PVE::Storage::LunCmd::Comstar; use PVE::Storage::LunCmd::Istgt; use PVE::Storage::LunCmd::Iet; +use PVE::Storage::LunCmd::LIO; + my @ssh_opts = ('-o', 'BatchMode=yes'); my @ssh_cmd = ('/usr/bin/ssh', @ssh_opts); @@ -29,7 +32,7 @@ my $lun_cmds = { my $zfs_unknown_scsi_provider = sub { my ($provider) = @_; - die "$provider: unknown iscsi provider. Available [comstar, istgt, iet]"; + die "$provider: unknown iscsi provider. Available [comstar, istgt, iet, LIO]"; }; my $zfs_get_base = sub { @@ -41,20 +44,20 @@ my $zfs_get_base = sub { return PVE::Storage::LunCmd::Istgt::get_base; } elsif ($scfg->{iscsiprovider} eq 'iet') { return PVE::Storage::LunCmd::Iet::get_base; + } elsif ($scfg->{iscsiprovider} eq 'LIO') { + return PVE::Storage::LunCmd::LIO::get_base; } else { $zfs_unknown_scsi_provider->($scfg->{iscsiprovider}); } }; sub zfs_request { - my ($scfg, $timeout, $method, @params) = @_; + my ($class, $scfg, $timeout, $method, @params) = @_; - my $cmdmap; - my $zfscmd; - my $target; - my $msg; + $timeout = PVE::RPCEnvironment->is_worker() ? 60*60 : 10 + if !$timeout; - $timeout = 5 if !$timeout; + my $msg = ''; if ($lun_cmds->{$method}) { if ($scfg->{iscsiprovider} eq 'comstar') { @@ -63,26 +66,28 @@ sub zfs_request { $msg = PVE::Storage::LunCmd::Istgt::run_lun_command($scfg, $timeout, $method, @params); } elsif ($scfg->{iscsiprovider} eq 'iet') { $msg = PVE::Storage::LunCmd::Iet::run_lun_command($scfg, $timeout, $method, @params); + } elsif ($scfg->{iscsiprovider} eq 'LIO') { + $msg = PVE::Storage::LunCmd::LIO::run_lun_command($scfg, $timeout, $method, @params); } else { $zfs_unknown_scsi_provider->($scfg->{iscsiprovider}); } } else { - if ($method eq 'zpool_list') { - $zfscmd = 'zpool'; - $method = 'list', - } else { - $zfscmd = 'zfs'; - } - $target = 'root@' . $scfg->{portal}; + my $target = 'root@' . $scfg->{portal}; - my $cmd = [@ssh_cmd, '-i', "$id_rsa_path/$scfg->{portal}_id_rsa", $target, $zfscmd, $method, @params]; + my $cmd = [@ssh_cmd, '-i', "$id_rsa_path/$scfg->{portal}_id_rsa", $target]; - $msg = ''; + if ($method eq 'zpool_list') { + push @$cmd, 'zpool', 'list'; + } else { + push @$cmd, 'zfs', $method; + } - my $output = sub { - my $line = shift; - $msg .= "$line\n"; + push @$cmd, @params; + + my $output = sub { + my $line = shift; + $msg .= "$line\n"; }; run_command($cmd, outfunc => $output, timeout => $timeout); @@ -91,219 +96,74 @@ sub zfs_request { return $msg; } -sub zfs_parse_size { - my ($text) = @_; - - return 0 if !$text; - - if ($text =~ m/^(\d+(\.\d+)?)([TGMK])?$/) { - my ($size, $reminder, $unit) = ($1, $2, $3); - return $size if !$unit; - if ($unit eq 'K') { - $size *= 1024; - } elsif ($unit eq 'M') { - $size *= 1024*1024; - } elsif ($unit eq 'G') { - $size *= 1024*1024*1024; - } elsif ($unit eq 'T') { - $size *= 1024*1024*1024*1024; - } - - if ($reminder) { - $size = ceil($size); - } - return $size; - } else { - return 0; - } -} - -sub zfs_get_pool_stats { - my ($scfg) = @_; - - my $available = 0; - my $used = 0; - - my $text = zfs_request($scfg, undef, 'get', '-o', 'value', '-Hp', - 'available,used', $scfg->{pool}); - - my @lines = split /\n/, $text; - - if($lines[0] =~ /^(\d+)$/) { - $available = $1; - } - - if($lines[1] =~ /^(\d+)$/) { - $used = $1; - } - - return ($available, $used); -} - -sub zfs_parse_zvol_list { - my ($text) = @_; - - my $list = (); - - return $list if !$text; - - my @lines = split /\n/, $text; - foreach my $line (@lines) { - if ($line =~ /^(.+)\s+([a-zA-Z0-9\.]+|\-)\s+(.+)$/) { - my $zvol = {}; - my @parts = split /\//, $1; - my $name = pop @parts; - my $pool = join('/', @parts); - - if ($pool !~ /^rpool$/) { - next unless $name =~ m!^(\w+)-(\d+)-(\w+)-(\d+)$!; - $name = $pool . '/' . $name; - } else { - next; - } - - $zvol->{pool} = $pool; - $zvol->{name} = $name; - $zvol->{size} = zfs_parse_size($2); - if ($3 !~ /^-$/) { - $zvol->{origin} = $3; - } - push @$list, $zvol; - } - } - - return $list; -} - sub zfs_get_lu_name { - my ($scfg, $zvol) = @_; - my $object; + my ($class, $scfg, $zvol) = @_; my $base = $zfs_get_base->($scfg); - if ($zvol =~ /^.+\/.+/) { - $object = "$base/$zvol"; - } else { - $object = "$base/$scfg->{pool}/$zvol"; - } - - my $lu_name = zfs_request($scfg, undef, 'list_lu', $object); - - return $lu_name if $lu_name; - die "Could not find lu_name for zvol $zvol"; -} + $zvol = ($class->parse_volname($zvol))[1]; -sub zfs_get_zvol_size { - my ($scfg, $zvol) = @_; + my $object = ($zvol =~ /^.+\/.+/) ? "$base/$zvol" : "$base/$scfg->{pool}/$zvol"; - my $text = zfs_request($scfg, undef, 'get', '-Hp', 'volsize', "$scfg->{pool}/$zvol"); + my $lu_name = $class->zfs_request($scfg, undef, 'list_lu', $object); - if($text =~ /volsize\s(\d+)/){ - return $1; - } + return $lu_name if $lu_name; - die "Could not get zvol size"; + die "Could not find lu_name for zvol $zvol"; } sub zfs_add_lun_mapping_entry { - my ($scfg, $zvol, $guid) = @_; + my ($class, $scfg, $zvol, $guid) = @_; - if (! defined($guid)) { - $guid = zfs_get_lu_name($scfg, $zvol); + if (!defined($guid)) { + $guid = $class->zfs_get_lu_name($scfg, $zvol); } - zfs_request($scfg, undef, 'add_view', $guid); + $class->zfs_request($scfg, undef, 'add_view', $guid); } sub zfs_delete_lu { - my ($scfg, $zvol) = @_; + my ($class, $scfg, $zvol) = @_; - my $guid = zfs_get_lu_name($scfg, $zvol); + my $guid = $class->zfs_get_lu_name($scfg, $zvol); - zfs_request($scfg, undef, 'delete_lu', $guid); + $class->zfs_request($scfg, undef, 'delete_lu', $guid); } sub zfs_create_lu { - my ($scfg, $zvol) = @_; + my ($class, $scfg, $zvol) = @_; my $base = $zfs_get_base->($scfg); - my $guid = zfs_request($scfg, undef, 'create_lu', "$base/$scfg->{pool}/$zvol"); + my $guid = $class->zfs_request($scfg, undef, 'create_lu', "$base/$scfg->{pool}/$zvol"); return $guid; } sub zfs_import_lu { - my ($scfg, $zvol) = @_; + my ($class, $scfg, $zvol) = @_; my $base = $zfs_get_base->($scfg); - zfs_request($scfg, undef, 'import_lu', "$base/$scfg->{pool}/$zvol"); + $class->zfs_request($scfg, undef, 'import_lu', "$base/$scfg->{pool}/$zvol"); } sub zfs_resize_lu { - my ($scfg, $zvol, $size) = @_; - - my $guid = zfs_get_lu_name($scfg, $zvol); - - zfs_request($scfg, undef, 'modify_lu', "${size}K", $guid); -} - -sub zfs_create_zvol { - my ($scfg, $zvol, $size) = @_; - - my $sparse = ''; - if ($scfg->{sparse}) { - $sparse = '-s'; - } + my ($class, $scfg, $zvol, $size) = @_; - zfs_request($scfg, undef, 'create', $sparse, '-b', $scfg->{blocksize}, '-V', "${size}k", "$scfg->{pool}/$zvol"); -} - -sub zfs_delete_zvol { - my ($scfg, $zvol) = @_; + my $guid = $class->zfs_get_lu_name($scfg, $zvol); - zfs_request($scfg, undef, 'destroy', '-r', "$scfg->{pool}/$zvol"); + $class->zfs_request($scfg, undef, 'modify_lu', "${size}K", $guid); } sub zfs_get_lun_number { - my ($scfg, $guid) = @_; + my ($class, $scfg, $guid) = @_; die "could not find lun_number for guid $guid" if !$guid; - return zfs_request($scfg, undef, 'list_view', $guid); -} - -sub zfs_list_zvol { - my ($scfg) = @_; - - my $text = zfs_request($scfg, 10, 'list', '-o', 'name,volsize,origin', '-t', 'volume', '-Hr'); - my $zvols = zfs_parse_zvol_list($text); - return undef if !$zvols; - - my $list = (); - foreach my $zvol (@$zvols) { - my @values = split('/', $zvol->{name}); - - my $image = pop @values; - my $pool = join('/', @values); - - next if $image !~ m/^((vm|base)-(\d+)-\S+)$/; - my $owner = $3; - - my $parent = $zvol->{origin}; - if($zvol->{origin} && $zvol->{origin} =~ m/^$scfg->{pool}\/(\S+)$/){ - $parent = $1; - } - - $list->{$pool}->{$image} = { - name => $image, - size => $zvol->{size}, - parent => $parent, - format => 'raw', - vmid => $owner - }; + if ($class->zfs_request($scfg, undef, 'list_view', $guid) =~ /^(\d+)$/) { + return $1; } - return $list; + die "lun_number for guid $guid is not a number"; } # Configuration @@ -314,95 +174,78 @@ sub type { sub plugindata { return { - content => [ {images => 1}, { images => 1 }], + content => [ {images => 1}, { images => 1 }], }; } sub properties { return { - iscsiprovider => { - description => "iscsi provider", - type => 'string', - }, - blocksize => { - description => "block size", - type => 'string', - }, - sparse => { - description => "use sparse volumes", - type => 'boolean', - optional => 1, - } + iscsiprovider => { + description => "iscsi provider", + type => 'string', + }, + # this will disable write caching on comstar and istgt. + # it is not implemented for iet. iet blockio always operates with + # writethrough caching when not in readonly mode + nowritecache => { + description => "disable write caching on the target", + type => 'boolean', + }, + comstar_tg => { + description => "target group for comstar views", + type => 'string', + }, + comstar_hg => { + description => "host group for comstar views", + type => 'string', + }, + lio_tpg => { + description => "target portal group for Linux LIO targets", + type => 'string', + }, }; } sub options { return { - nodes => { optional => 1 }, - disable => { optional => 1 }, - portal => { fixed => 1 }, - target => { fixed => 1 }, - pool => { fixed => 1 }, - blocksize => { fixed => 1 }, - iscsiprovider => { fixed => 1 }, - sparse => { optional => 1 }, - content => { optional => 1 }, + nodes => { optional => 1 }, + disable => { optional => 1 }, + portal => { fixed => 1 }, + target => { fixed => 1 }, + pool => { fixed => 1 }, + blocksize => { fixed => 1 }, + iscsiprovider => { fixed => 1 }, + nowritecache => { optional => 1 }, + sparse => { optional => 1 }, + comstar_hg => { optional => 1 }, + comstar_tg => { optional => 1 }, + lio_tpg => { optional => 1 }, + content => { optional => 1 }, + bwlimit => { optional => 1 }, }; } # Storage implementation -sub parse_volname { - my ($class, $volname) = @_; - - if ($volname =~ m/^(((base|vm)-(\d+)-\S+)\/)?((base)?(vm)?-(\d+)-\S+)$/) { - return ('images', $5, $8, $2, $4, $6); - } - - die "unable to parse zfs volume name '$volname'\n"; -} - sub path { - my ($class, $scfg, $volname) = @_; + my ($class, $scfg, $volname, $storeid, $snapname) = @_; + + die "direct access to snapshots not implemented" + if defined($snapname); my ($vtype, $name, $vmid) = $class->parse_volname($volname); my $target = $scfg->{target}; my $portal = $scfg->{portal}; - my $guid = zfs_get_lu_name($scfg, $name); - my $lun = zfs_get_lun_number($scfg, $guid); + my $guid = $class->zfs_get_lu_name($scfg, $name); + my $lun = $class->zfs_get_lun_number($scfg, $guid); my $path = "iscsi://$portal/$target/$lun"; return ($path, $vmid, $vtype); } -my $find_free_diskname = sub { - my ($storeid, $scfg, $vmid) = @_; - - my $name = undef; - my $volumes = zfs_list_zvol($scfg); - - my $disk_ids = {}; - my $dat = $volumes->{$scfg->{pool}}; - - foreach my $image (keys %$dat) { - my $volname = $dat->{$image}->{name}; - if ($volname =~ m/(vm|base)-$vmid-disk-(\d+)/){ - $disk_ids->{$2} = 1; - } - } - - for (my $i = 1; $i < 100; $i++) { - if (!$disk_ids->{$i}) { - return "vm-$vmid-disk-$i"; - } - } - - die "unable to allocate an image name for VM $vmid in storage '$storeid'\n"; -}; - sub create_base { my ($class, $storeid, $scfg, $volname) = @_; @@ -418,11 +261,11 @@ sub create_base { my $newvolname = $basename ? "$basename/$newname" : "$newname"; - zfs_delete_lu($scfg, $name); - zfs_request($scfg, undef, 'rename', "$scfg->{pool}/$name", "$scfg->{pool}/$newname"); + $class->zfs_delete_lu($scfg, $name); + $class->zfs_request($scfg, undef, 'rename', "$scfg->{pool}/$name", "$scfg->{pool}/$newname"); - my $guid = zfs_create_lu($scfg, $newname); - zfs_add_lun_mapping_entry($scfg, $newname, $guid); + my $guid = $class->zfs_create_lu($scfg, $newname); + $class->zfs_add_lun_mapping_entry($scfg, $newname, $guid); my $running = undef; #fixme : is create_base always offline ? @@ -432,42 +275,37 @@ sub create_base { } sub clone_image { - my ($class, $scfg, $storeid, $volname, $vmid) = @_; + my ($class, $scfg, $storeid, $volname, $vmid, $snap) = @_; - my $snap = '__base__'; + my $name = $class->SUPER::clone_image($scfg, $storeid, $volname, $vmid, $snap); - my ($vtype, $basename, $basevmid, undef, undef, $isBase) = - $class->parse_volname($volname); + # get ZFS dataset name from PVE volname + my (undef, $clonedname) = $class->parse_volname($name); - die "clone_image only works on base images\n" if !$isBase; - - my $name = &$find_free_diskname($storeid, $scfg, $vmid); - - warn "clone $volname: $basename to $name\n"; - - zfs_request($scfg, undef, 'clone', "$scfg->{pool}/$basename\@$snap", "$scfg->{pool}/$name"); - - my $guid = zfs_create_lu($scfg, $name); - zfs_add_lun_mapping_entry($scfg, $name, $guid); + my $guid = $class->zfs_create_lu($scfg, $clonedname); + $class->zfs_add_lun_mapping_entry($scfg, $clonedname, $guid); return $name; } sub alloc_image { my ($class, $storeid, $scfg, $vmid, $fmt, $name, $size) = @_; - + die "unsupported format '$fmt'" if $fmt ne 'raw'; die "illegal name '$name' - sould be 'vm-$vmid-*'\n" if $name && $name !~ m/^vm-$vmid-/; - $name = &$find_free_diskname($storeid, $scfg, $vmid); + my $volname = $name; - zfs_create_zvol($scfg, $name, $size); - my $guid = zfs_create_lu($scfg, $name); - zfs_add_lun_mapping_entry($scfg, $name, $guid); + $volname = $class->find_free_diskname($storeid, $scfg, $vmid, $fmt) if !$volname; + + $class->zfs_create_zvol($scfg, $volname, $size); + + my $guid = $class->zfs_create_lu($scfg, $volname); + $class->zfs_add_lun_mapping_entry($scfg, $volname, $guid); - return $name; + return $volname; } sub free_image { @@ -475,160 +313,116 @@ sub free_image { my ($vtype, $name, $vmid) = $class->parse_volname($volname); - zfs_delete_lu($scfg, $name); - eval { - zfs_delete_zvol($scfg, $name); - }; - do { - my $err = $@; - my $guid = zfs_create_lu($scfg, $name); - zfs_add_lun_mapping_entry($scfg, $name, $guid); + $class->zfs_delete_lu($scfg, $name); + + eval { $class->zfs_delete_zvol($scfg, $name); }; + if (my $err = $@) { + my $guid = $class->zfs_create_lu($scfg, $name); + $class->zfs_add_lun_mapping_entry($scfg, $name, $guid); die $err; - } if $@; + } return undef; } -sub list_images { - my ($class, $storeid, $scfg, $vmid, $vollist, $cache) = @_; - - $cache->{zfs} = zfs_list_zvol($scfg) if !$cache->{zfs}; - my $zfspool = $scfg->{pool}; - my $res = []; +sub volume_resize { + my ($class, $scfg, $storeid, $volname, $size, $running) = @_; - if (my $dat = $cache->{zfs}->{$zfspool}) { + $volname = ($class->parse_volname($volname))[1]; - foreach my $image (keys %$dat) { + my $new_size = $class->SUPER::volume_resize($scfg, $storeid, $volname, $size, $running); - my $volname = $dat->{$image}->{name}; - my $parent = $dat->{$image}->{parent}; + $class->zfs_resize_lu($scfg, $volname, $new_size); - my $volid = undef; - if ($parent && $parent =~ m/^(\S+)@(\S+)$/) { - my ($basename) = ($1); - $volid = "$storeid:$basename/$volname"; - } else { - $volid = "$storeid:$volname"; - } + return $new_size; +} - my $owner = $dat->{$volname}->{vmid}; - if ($vollist) { - my $found = grep { $_ eq $volid } @$vollist; - next if !$found; - } else { - next if defined ($vmid) && ($owner ne $vmid); - } +sub volume_snapshot_delete { + my ($class, $scfg, $storeid, $volname, $snap, $running) = @_; - my $info = $dat->{$volname}; - $info->{volid} = $volid; - push @$res, $info; - } - } + $volname = ($class->parse_volname($volname))[1]; - return $res; + $class->zfs_request($scfg, undef, 'destroy', "$scfg->{pool}/$volname\@$snap"); } -sub status { - my ($class, $storeid, $scfg, $cache) = @_; +sub volume_snapshot_rollback { + my ($class, $scfg, $storeid, $volname, $snap) = @_; - my $total = 0; - my $free = 0; - my $used = 0; - my $active = 0; + $volname = ($class->parse_volname($volname))[1]; - eval { - ($free, $used) = zfs_get_pool_stats($scfg); - $active = 1; - $total = $free + $used; - }; - warn $@ if $@; + $class->zfs_delete_lu($scfg, $volname); - return ($total, $free, $used, $active); -} + $class->zfs_request($scfg, undef, 'rollback', "$scfg->{pool}/$volname\@$snap"); -sub activate_storage { - my ($class, $storeid, $scfg, $cache) = @_; - return 1; -} + $class->zfs_import_lu($scfg, $volname); -sub deactivate_storage { - my ($class, $storeid, $scfg, $cache) = @_; - return 1; + $class->zfs_add_lun_mapping_entry($scfg, $volname); } -sub activate_volume { - my ($class, $storeid, $scfg, $volname, $exclusive, $cache) = @_; - return 1; -} +sub storage_can_replicate { + my ($class, $scfg, $storeid, $format) = @_; -sub deactivate_volume { - my ($class, $storeid, $scfg, $volname, $exclusive, $cache) = @_; - return 1; + return 0; } -sub volume_size_info { - my ($class, $scfg, $storeid, $volname, $timeout) = @_; +sub volume_has_feature { + my ($class, $scfg, $feature, $storeid, $volname, $snapname, $running) = @_; - return zfs_get_zvol_size($scfg, $volname); -} + my $features = { + snapshot => { current => 1, snap => 1}, + clone => { base => 1}, + template => { current => 1}, + copy => { base => 1, current => 1}, + }; -sub volume_resize { - my ($class, $scfg, $storeid, $volname, $size, $running) = @_; + my ($vtype, $name, $vmid, $basename, $basevmid, $isBase) = + $class->parse_volname($volname); - my $new_size = ($size/1024); + my $key = undef; - zfs_request($scfg, undef, 'set', 'volsize=' . $new_size . 'k', "$scfg->{pool}/$volname"); - zfs_resize_lu($scfg, $volname, $new_size); -} + if ($snapname) { + $key = 'snap'; + } else { + $key = $isBase ? 'base' : 'current'; + } -sub volume_snapshot { - my ($class, $scfg, $storeid, $volname, $snap, $running) = @_; + return 1 if $features->{$feature}->{$key}; - zfs_request($scfg, undef, 'snapshot', "$scfg->{pool}/$volname\@$snap"); + return undef; } -sub volume_snapshot_rollback { - my ($class, $scfg, $storeid, $volname, $snap) = @_; - - zfs_delete_lu($scfg, $volname); - - zfs_request($scfg, undef, 'rollback', "$scfg->{pool}/$volname\@$snap"); +sub volume_snapshot_list { + my ($class, $scfg, $storeid, $volname) = @_; + # return an empty array if dataset does not exist. + die "Volume_snapshot_list is not implemented for ZFS over iSCSI.\n"; +} - zfs_import_lu($scfg, $volname); +sub activate_storage { + my ($class, $storeid, $scfg, $cache) = @_; - zfs_add_lun_mapping_entry($scfg, $volname); + return 1; } -sub volume_snapshot_delete { - my ($class, $scfg, $storeid, $volname, $snap, $running) = @_; +sub deactivate_storage { + my ($class, $storeid, $scfg, $cache) = @_; - zfs_request($scfg, undef, 'destroy', "$scfg->{pool}/$volname\@$snap"); + return 1; } -sub volume_has_feature { - my ($class, $scfg, $feature, $storeid, $volname, $snapname, $running) = @_; - - my $features = { - snapshot => { current => 1, snap => 1}, - clone => { base => 1}, - template => { current => 1}, - copy => { base => 1, current => 1}, - }; +sub activate_volume { + my ($class, $storeid, $scfg, $volname, $snapname, $cache) = @_; - my ($vtype, $name, $vmid, $basename, $basevmid, $isBase) = - $class->parse_volname($volname); + die "unable to activate snapshot from remote zfs storage" if $snapname; - my $key = undef; + return 1; +} - if ($snapname) { - $key = 'snap'; - } else { - $key = $isBase ? 'base' : 'current'; - } +sub deactivate_volume { + my ($class, $storeid, $scfg, $volname, $snapname, $cache) = @_; - return 1 if $features->{$feature}->{$key}; + die "unable to deactivate snapshot from remote zfs storage" if $snapname; - return undef; + return 1; } 1;