Files
pve-storage/PVE/Storage/ZFSPlugin.pm
Daniel Berteaud c243be83b4 Parse volname where needed
The common ZFSPlugin was missing volume name parsing
in a few places. This was not a problem for standard
volumes, but broke functionnalities (like resize,
snapshot, rollback) with linked clones as the name of
the zvol must be extracted from the entry in the config
(remove base-X-disk-Y prefix)

Signed-off-by: Daniel Berteaud <daniel@firewall-services.com>
2019-09-23 16:09:10 +02:00

425 lines
10 KiB
Perl

package PVE::Storage::ZFSPlugin;
use strict;
use warnings;
use IO::File;
use POSIX;
use PVE::Tools qw(run_command);
use PVE::Storage::ZFSPoolPlugin;
use PVE::RPCEnvironment;
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);
my $id_rsa_path = '/etc/pve/priv/zfs';
my $lun_cmds = {
create_lu => 1,
delete_lu => 1,
import_lu => 1,
modify_lu => 1,
add_view => 1,
list_view => 1,
list_lu => 1,
};
my $zfs_unknown_scsi_provider = sub {
my ($provider) = @_;
die "$provider: unknown iscsi provider. Available [comstar, istgt, iet, LIO]";
};
my $zfs_get_base = sub {
my ($scfg) = @_;
if ($scfg->{iscsiprovider} eq 'comstar') {
return PVE::Storage::LunCmd::Comstar::get_base;
} elsif ($scfg->{iscsiprovider} eq 'istgt') {
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 ($class, $scfg, $timeout, $method, @params) = @_;
$timeout = PVE::RPCEnvironment->is_worker() ? 60*60 : 10
if !$timeout;
my $msg = '';
if ($lun_cmds->{$method}) {
if ($scfg->{iscsiprovider} eq 'comstar') {
$msg = PVE::Storage::LunCmd::Comstar::run_lun_command($scfg, $timeout, $method, @params);
} elsif ($scfg->{iscsiprovider} eq 'istgt') {
$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 {
my $target = 'root@' . $scfg->{portal};
my $cmd = [@ssh_cmd, '-i', "$id_rsa_path/$scfg->{portal}_id_rsa", $target];
if ($method eq 'zpool_list') {
push @$cmd, 'zpool', 'list';
} else {
push @$cmd, 'zfs', $method;
}
push @$cmd, @params;
my $output = sub {
my $line = shift;
$msg .= "$line\n";
};
run_command($cmd, outfunc => $output, timeout => $timeout);
}
return $msg;
}
sub zfs_get_lu_name {
my ($class, $scfg, $zvol) = @_;
my $base = $zfs_get_base->($scfg);
$zvol = ($class->parse_volname($zvol))[1];
my $object = ($zvol =~ /^.+\/.+/) ? "$base/$zvol" : "$base/$scfg->{pool}/$zvol";
my $lu_name = $class->zfs_request($scfg, undef, 'list_lu', $object);
return $lu_name if $lu_name;
die "Could not find lu_name for zvol $zvol";
}
sub zfs_add_lun_mapping_entry {
my ($class, $scfg, $zvol, $guid) = @_;
if (!defined($guid)) {
$guid = $class->zfs_get_lu_name($scfg, $zvol);
}
$class->zfs_request($scfg, undef, 'add_view', $guid);
}
sub zfs_delete_lu {
my ($class, $scfg, $zvol) = @_;
my $guid = $class->zfs_get_lu_name($scfg, $zvol);
$class->zfs_request($scfg, undef, 'delete_lu', $guid);
}
sub zfs_create_lu {
my ($class, $scfg, $zvol) = @_;
my $base = $zfs_get_base->($scfg);
my $guid = $class->zfs_request($scfg, undef, 'create_lu', "$base/$scfg->{pool}/$zvol");
return $guid;
}
sub zfs_import_lu {
my ($class, $scfg, $zvol) = @_;
my $base = $zfs_get_base->($scfg);
$class->zfs_request($scfg, undef, 'import_lu', "$base/$scfg->{pool}/$zvol");
}
sub zfs_resize_lu {
my ($class, $scfg, $zvol, $size) = @_;
my $guid = $class->zfs_get_lu_name($scfg, $zvol);
$class->zfs_request($scfg, undef, 'modify_lu', "${size}K", $guid);
}
sub zfs_get_lun_number {
my ($class, $scfg, $guid) = @_;
die "could not find lun_number for guid $guid" if !$guid;
return $class->zfs_request($scfg, undef, 'list_view', $guid);
}
# Configuration
sub type {
return 'zfs';
}
sub plugindata {
return {
content => [ {images => 1}, { images => 1 }],
};
}
sub properties {
return {
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 },
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 path {
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 = $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);
}
sub create_base {
my ($class, $storeid, $scfg, $volname) = @_;
my $snap = '__base__';
my ($vtype, $name, $vmid, $basename, $basevmid, $isBase) =
$class->parse_volname($volname);
die "create_base not possible with base image\n" if $isBase;
my $newname = $name;
$newname =~ s/^vm-/base-/;
my $newvolname = $basename ? "$basename/$newname" : "$newname";
$class->zfs_delete_lu($scfg, $name);
$class->zfs_request($scfg, undef, 'rename', "$scfg->{pool}/$name", "$scfg->{pool}/$newname");
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 ?
$class->volume_snapshot($scfg, $storeid, $newname, $snap, $running);
return $newvolname;
}
sub clone_image {
my ($class, $scfg, $storeid, $volname, $vmid, $snap) = @_;
my $name = $class->SUPER::clone_image($scfg, $storeid, $volname, $vmid, $snap);
# get ZFS dataset name from PVE volname
my (undef, $clonedname) = $class->parse_volname($name);
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-/;
my $volname = $name;
$volname = $class->zfs_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 $volname;
}
sub free_image {
my ($class, $storeid, $scfg, $volname, $isBase) = @_;
my ($vtype, $name, $vmid) = $class->parse_volname($volname);
$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;
}
return undef;
}
sub volume_resize {
my ($class, $scfg, $storeid, $volname, $size, $running) = @_;
$volname = ($class->parse_volname($volname))[1];
my $new_size = $class->SUPER::volume_resize($scfg, $storeid, $volname, $size, $running);
$class->zfs_resize_lu($scfg, $volname, $new_size);
return $new_size;
}
sub volume_snapshot_delete {
my ($class, $scfg, $storeid, $volname, $snap, $running) = @_;
$volname = ($class->parse_volname($volname))[1];
$class->zfs_request($scfg, undef, 'destroy', "$scfg->{pool}/$volname\@$snap");
}
sub volume_snapshot_rollback {
my ($class, $scfg, $storeid, $volname, $snap) = @_;
$volname = ($class->parse_volname($volname))[1];
$class->zfs_delete_lu($scfg, $volname);
$class->zfs_request($scfg, undef, 'rollback', "$scfg->{pool}/$volname\@$snap");
$class->zfs_import_lu($scfg, $volname);
$class->zfs_add_lun_mapping_entry($scfg, $volname);
}
sub storage_can_replicate {
my ($class, $scfg, $storeid, $format) = @_;
return 0;
}
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},
};
my ($vtype, $name, $vmid, $basename, $basevmid, $isBase) =
$class->parse_volname($volname);
my $key = undef;
if ($snapname) {
$key = 'snap';
} else {
$key = $isBase ? 'base' : 'current';
}
return 1 if $features->{$feature}->{$key};
return undef;
}
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";
}
sub activate_storage {
my ($class, $storeid, $scfg, $cache) = @_;
return 1;
}
sub deactivate_storage {
my ($class, $storeid, $scfg, $cache) = @_;
return 1;
}
sub activate_volume {
my ($class, $storeid, $scfg, $volname, $snapname, $cache) = @_;
die "unable to activate snapshot from remote zfs storage" if $snapname;
return 1;
}
sub deactivate_volume {
my ($class, $storeid, $scfg, $volname, $snapname, $cache) = @_;
die "unable to deactivate snapshot from remote zfs storage" if $snapname;
return 1;
}
1;