feat: auto-detect and correct ZFS blocksize for TrueNAS SCALE/CORE (#241)
* feat: auto-detect and correct ZFS blocksize for TrueNAS SCALE/CORE (#241) SCALE requires >= 16k volblocksize; CORE works with 8k. Without correction, creating a disk on SCALE with blocksize=8k triggers a ZFS warning and wastes space on SCALE's minimum-4k-block pool layout. Three changes: - freenas_get_recommended_blocksize: fixes freenas_api_connect → freenas_api_check so product_name is actually populated before the SCALE check (was always returning 8192 before this fix) - freenas_parse_blocksize: new helper to compare "8k"/"16k"/integer blocksize strings - alloc_image (both 8.x and 8.4.x patches): always detect recommended blocksize for freenas provider; if configured < recommended, override for this call AND persist the correction back to storage.cfg via lock_storage_config - on_add_hook (8.4.x patch only): detect at storage creation time and correct $scfg before write_config saves it — no extra write needed Tested on pve01-hq (PVE 8.4.19) against Tank02 (SCALE 24.10.2.1): - Disk created with blocksize=8k → task log shows correction message, storage.cfg updated to 16384, no volblocksize warning from TrueNAS - Disk created with blocksize=16384 → no-op, clean TASK OK Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * ci: add validate-patches step for ZFSPlugin PVE 8.4.x patch ZFSPlugin-8.4.14_1.pm.patch has been in the build since v2.3.0 but was never dry-run validated in CI. Now that we have the 8.4 orig committed (ZFSPlugin-8.4.14_1.pm.orig from libpve-storage-perl 8.3.8), wire it up. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: replace explicit return undef with bare return (perlcritic) Subroutines::ProhibitExplicitReturnUndef violation in freenas_get_recommended_blocksize. Bare return in list context returns an empty list rather than a list containing undef, which is the correct Perl idiom. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
a0d0285194
commit
c166501873
|
|
@ -69,6 +69,13 @@ jobs:
|
|||
< stable-8/perl5/PVE/Storage/ZFSPlugin.pm.patch \
|
||||
&& echo "ZFSPlugin PVE-8 patch: OK"
|
||||
|
||||
- name: "Dry-run patch: ZFSPlugin (PVE 8.4.x)"
|
||||
run: |
|
||||
patch --dry-run --ignore-whitespace \
|
||||
stable-8/perl5/PVE/Storage/ZFSPlugin-8.4.14_1.pm.orig \
|
||||
< stable-8/perl5/PVE/Storage/ZFSPlugin-8.4.14_1.pm.patch \
|
||||
&& echo "ZFSPlugin PVE-8.4 patch: OK"
|
||||
|
||||
- name: "Dry-run patch: apidoc.js (PVE 8)"
|
||||
run: |
|
||||
patch --dry-run --ignore-whitespace \
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ package PVE::Storage::LunCmd::FreeNAS;
|
|||
use strict;
|
||||
use warnings;
|
||||
|
||||
our $VERSION = '2.3.0';
|
||||
our $VERSION = '2.4.0';
|
||||
use Data::Dumper;
|
||||
use PVE::SafeSyslog;
|
||||
use IO::Socket::SSL;
|
||||
|
|
@ -638,6 +638,43 @@ sub freenas_delete_zvol {
|
|||
}
|
||||
}
|
||||
|
||||
# Parse a blocksize string (e.g. "8k", "16k", "8192") to bytes.
|
||||
sub freenas_parse_blocksize {
|
||||
my ($val) = @_;
|
||||
return 0 unless defined $val && $val ne '';
|
||||
return $1 * 1024 if $val =~ /^(\d+)\s*[kK]$/;
|
||||
return $1 * 1024 * 1024 if $val =~ /^(\d+)\s*[mM]$/;
|
||||
return $val + 0;
|
||||
}
|
||||
|
||||
# Returns the recommended ZFS volblocksize for this TrueNAS host based on its
|
||||
# product type. TrueNAS SCALE ships a newer ZFS that requires >= 16k; CORE is
|
||||
# fine with 8k. Returns undef if the API is unreachable so callers can fall
|
||||
# back to the user-configured value or the ZFS default.
|
||||
sub freenas_get_recommended_blocksize {
|
||||
my ($scfg) = @_;
|
||||
|
||||
my $apihost = defined($scfg->{freenas_apiv4_host}) ? $scfg->{freenas_apiv4_host} : $scfg->{portal};
|
||||
|
||||
# freenas_api_check (not freenas_api_connect) must be used here because
|
||||
# product_name is only parsed and cached by freenas_api_check.
|
||||
# freenas_api_connect only sets up the HTTP client.
|
||||
eval { freenas_api_check($scfg) };
|
||||
if ($@) {
|
||||
syslog("warn", (caller(0))[3] . " : unable to connect to TrueNAS API for blocksize detection: $@");
|
||||
return;
|
||||
}
|
||||
|
||||
my $product = $freenas_server_list->{$apihost}{product_name} // '';
|
||||
if ($product =~ /SCALE/i) {
|
||||
syslog("info", (caller(0))[3] . " : detected TrueNAS SCALE — recommending blocksize 16384");
|
||||
return 16384;
|
||||
}
|
||||
|
||||
syslog("info", (caller(0))[3] . " : detected '$product' — recommending blocksize 8192");
|
||||
return 8192;
|
||||
}
|
||||
|
||||
#
|
||||
# Remove an extent by it's id
|
||||
# http://api.freenas.org/resources/iscsi/index.html#delete-resource
|
||||
|
|
|
|||
|
|
@ -0,0 +1,470 @@
|
|||
package PVE::Storage::ZFSPlugin;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use IO::File;
|
||||
use POSIX qw(ENOENT);
|
||||
use PVE::Tools qw(run_command);
|
||||
use PVE::Storage::ZFSPoolPlugin;
|
||||
use PVE::RESTEnvironment qw(log_warn);
|
||||
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($scfg);
|
||||
} elsif ($scfg->{iscsiprovider} eq 'istgt') {
|
||||
return PVE::Storage::LunCmd::Istgt::get_base($scfg);
|
||||
} elsif ($scfg->{iscsiprovider} eq 'iet') {
|
||||
return PVE::Storage::LunCmd::Iet::get_base($scfg);
|
||||
} elsif ($scfg->{iscsiprovider} eq 'LIO') {
|
||||
return PVE::Storage::LunCmd::LIO::get_base($scfg);
|
||||
} 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;
|
||||
|
||||
if ($class->zfs_request($scfg, undef, 'list_view', $guid) =~ /^(\d+)$/) {
|
||||
return $1;
|
||||
}
|
||||
|
||||
die "lun_number for guid $guid is not a number";
|
||||
}
|
||||
|
||||
# Configuration
|
||||
|
||||
sub type {
|
||||
return 'zfs';
|
||||
}
|
||||
|
||||
sub plugindata {
|
||||
return {
|
||||
content => [{ images => 1 }, { images => 1 }],
|
||||
'sensitive-properties' => {},
|
||||
};
|
||||
}
|
||||
|
||||
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',
|
||||
},
|
||||
'zfs-base-path' => {
|
||||
description => "Base path where to look for the created ZFS block devices. Set"
|
||||
. " automatically during creation if not specified. Usually '/dev/zvol'.",
|
||||
type => 'string',
|
||||
format => 'pve-storage-path',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
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 },
|
||||
'zfs-base-path' => { optional => 1 },
|
||||
};
|
||||
}
|
||||
|
||||
# Storage implementation
|
||||
|
||||
sub on_add_hook {
|
||||
my ($class, $storeid, $scfg, %param) = @_;
|
||||
|
||||
if (!$scfg->{'zfs-base-path'}) {
|
||||
my $base_path;
|
||||
if ($scfg->{iscsiprovider} eq 'comstar') {
|
||||
$base_path = PVE::Storage::LunCmd::Comstar::get_base($scfg);
|
||||
} elsif ($scfg->{iscsiprovider} eq 'istgt') {
|
||||
$base_path = PVE::Storage::LunCmd::Istgt::get_base($scfg);
|
||||
} elsif ($scfg->{iscsiprovider} eq 'iet' || $scfg->{iscsiprovider} eq 'LIO') {
|
||||
# Provider implementations hard-code '/dev/', which does not work for distributions like
|
||||
# Debian 12. Keep that implementation as-is for backwards compatibility, but use custom
|
||||
# logic here.
|
||||
my $target = 'root@' . $scfg->{portal};
|
||||
my $cmd = [@ssh_cmd, '-i', "$id_rsa_path/$scfg->{portal}_id_rsa", $target];
|
||||
push $cmd->@*, 'ls', '/dev/zvol';
|
||||
|
||||
my $rc = eval { run_command($cmd, timeout => 10, noerr => 1, quiet => 1) };
|
||||
my $err = $@;
|
||||
if (defined($rc) && $rc == 0) {
|
||||
$base_path = '/dev/zvol';
|
||||
} elsif (defined($rc) && $rc == ENOENT) {
|
||||
$base_path = '/dev';
|
||||
} else {
|
||||
my $message = $err ? $err : "remote command failed";
|
||||
chomp($message);
|
||||
$message .= " ($rc)" if defined($rc);
|
||||
$message .= " - check 'zfs-base-path' setting manually!";
|
||||
log_warn($message);
|
||||
$base_path = '/dev/zvol';
|
||||
}
|
||||
} else {
|
||||
$zfs_unknown_scsi_provider->($scfg->{iscsiprovider});
|
||||
}
|
||||
|
||||
$scfg->{'zfs-base-path'} = $base_path;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
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' - should be 'vm-$vmid-*'\n"
|
||||
if $name && $name !~ m/^vm-$vmid-/;
|
||||
|
||||
my $volname = $name;
|
||||
|
||||
$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 $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 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;
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
--- /tmp/ZFSPlugin-8.4.19-stock.pm 2026-05-19 23:09:15.498561777 -0400
|
||||
+++ /tmp/ZFSPlugin-8.4.19-patched.pm 2026-05-19 23:13:40.830560931 -0400
|
||||
--- /tmp/ZFSPlugin-8.4.orig 2026-05-22 08:54:24.992472212 -0400
|
||||
+++ /tmp/ZFSPlugin-8.4-final.pm 2026-05-22 08:51:00.460468461 -0400
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
use base qw(PVE::Storage::ZFSPoolPlugin);
|
||||
|
|
@ -8,7 +8,7 @@
|
|||
use PVE::Storage::LunCmd::Istgt;
|
||||
use PVE::Storage::LunCmd::Iet;
|
||||
use PVE::Storage::LunCmd::LIO;
|
||||
@@ -26,6 +27,7 @@
|
||||
@@ -26,13 +27,14 @@
|
||||
modify_lu => 1,
|
||||
add_view => 1,
|
||||
list_view => 1,
|
||||
|
|
@ -16,6 +16,14 @@
|
|||
list_lu => 1,
|
||||
};
|
||||
|
||||
my $zfs_unknown_scsi_provider = sub {
|
||||
my ($provider) = @_;
|
||||
|
||||
- die "$provider: unknown iscsi provider. Available [comstar, istgt, iet, LIO]";
|
||||
+ die "$provider: unknown iscsi provider. Available [comstar, freenas, istgt, iet, LIO]";
|
||||
};
|
||||
|
||||
my $zfs_get_base = sub {
|
||||
@@ -44,6 +46,8 @@
|
||||
return PVE::Storage::LunCmd::Istgt::get_base($scfg);
|
||||
} elsif ($scfg->{iscsiprovider} eq 'iet') {
|
||||
|
|
@ -104,3 +112,58 @@
|
|||
sparse => { optional => 1 },
|
||||
comstar_hg => { optional => 1 },
|
||||
comstar_tg => { optional => 1 },
|
||||
@@ -243,6 +289,21 @@
|
||||
my $base_path;
|
||||
if ($scfg->{iscsiprovider} eq 'comstar') {
|
||||
$base_path = PVE::Storage::LunCmd::Comstar::get_base($scfg);
|
||||
+ } elsif ($scfg->{iscsiprovider} eq 'freenas') {
|
||||
+ $base_path = PVE::Storage::LunCmd::FreeNAS::get_base($scfg);
|
||||
+ # Auto-detect and persist correct blocksize at storage creation time.
|
||||
+ # $scfg is the live config ref -- write_config() saves it after this hook returns.
|
||||
+ eval {
|
||||
+ my $recommended = PVE::Storage::LunCmd::FreeNAS::freenas_get_recommended_blocksize($scfg);
|
||||
+ if (defined $recommended) {
|
||||
+ my $configured = PVE::Storage::LunCmd::FreeNAS::freenas_parse_blocksize($scfg->{blocksize});
|
||||
+ if (!$configured || $configured < $recommended) {
|
||||
+ warn "freenas-proxmox: auto-correcting blocksize from " . ($configured ? $configured : 'unset') . " to $recommended for '$storeid'\n";
|
||||
+ $scfg->{blocksize} = $recommended;
|
||||
+ }
|
||||
+ }
|
||||
+ };
|
||||
+ warn "freenas-proxmox: blocksize detection in on_add_hook failed: $@\n" if $@;
|
||||
} elsif ($scfg->{iscsiprovider} eq 'istgt') {
|
||||
$base_path = PVE::Storage::LunCmd::Istgt::get_base($scfg);
|
||||
} elsif ($scfg->{iscsiprovider} eq 'iet' || $scfg->{iscsiprovider} eq 'LIO') {
|
||||
@@ -349,6 +410,32 @@
|
||||
|
||||
$volname = $class->find_free_diskname($storeid, $scfg, $vmid, $fmt) if !$volname;
|
||||
|
||||
+ # Auto-detect ZFS blocksize from TrueNAS API and override if wrong.
|
||||
+ # SCALE requires >= 16k; CORE works with 8k. If the configured blocksize is
|
||||
+ # less than recommended (or unset), correct it now and persist to storage.cfg.
|
||||
+ if ($scfg->{iscsiprovider} eq 'freenas') {
|
||||
+ eval {
|
||||
+ my $recommended = PVE::Storage::LunCmd::FreeNAS::freenas_get_recommended_blocksize($scfg);
|
||||
+ if (defined $recommended) {
|
||||
+ my $configured = PVE::Storage::LunCmd::FreeNAS::freenas_parse_blocksize($scfg->{blocksize});
|
||||
+ if (!$configured || $configured < $recommended) {
|
||||
+ warn "freenas-proxmox: blocksize " . ($configured ? $configured : 'unset') . " < recommended $recommended -- correcting storage '$storeid'\n";
|
||||
+ $scfg->{blocksize} = $recommended;
|
||||
+ eval {
|
||||
+ require PVE::Storage;
|
||||
+ PVE::Storage::lock_storage_config(sub {
|
||||
+ my $cfg = PVE::Storage::config();
|
||||
+ $cfg->{ids}{$storeid}{blocksize} = $recommended if $cfg->{ids}{$storeid};
|
||||
+ PVE::Storage::write_config($cfg);
|
||||
+ });
|
||||
+ };
|
||||
+ warn "freenas-proxmox: could not persist blocksize to storage config: $@\n" if $@;
|
||||
+ }
|
||||
+ }
|
||||
+ };
|
||||
+ warn "TrueNAS blocksize detection failed: $@" if $@;
|
||||
+ }
|
||||
+
|
||||
$class->zfs_create_zvol($scfg, $volname, $size);
|
||||
|
||||
my $guid = $class->zfs_create_lu($scfg, $volname);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
--- ZFSPlugin.pm.orig 2023-12-31 09:56:18.895228853 -0500
|
||||
+++ ZFSPlugin.pm 2023-12-31 09:57:08.830488875 -0500
|
||||
--- /mnt/c/Users/Kevin/eclipse-workspace/freenas-proxmox/stable-8/perl5/PVE/Storage/ZFSPlugin.pm.orig 2026-05-20 12:28:37.312211300 -0400
|
||||
+++ /tmp/ZFSPlugin-8x-work.pm 2026-05-22 08:57:21.728470343 -0400
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
use base qw(PVE::Storage::ZFSPoolPlugin);
|
||||
|
|
@ -164,3 +164,38 @@
|
|||
return ($path, $vmid, $vtype);
|
||||
}
|
||||
|
||||
@@ -299,7 +380,33 @@
|
||||
my $volname = $name;
|
||||
|
||||
$volname = $class->find_free_diskname($storeid, $scfg, $vmid, $fmt) if !$volname;
|
||||
-
|
||||
+
|
||||
+ # Auto-detect ZFS blocksize from TrueNAS API and override if wrong.
|
||||
+ # SCALE requires >= 16k; CORE works with 8k. If the configured blocksize is
|
||||
+ # less than recommended (or unset), correct it now and persist to storage.cfg.
|
||||
+ if ( eq 'freenas') {
|
||||
+ eval {
|
||||
+ my = PVE::Storage::LunCmd::FreeNAS::freenas_get_recommended_blocksize(HASH(0x6158b91f5240));
|
||||
+ if (defined ) {
|
||||
+ my = PVE::Storage::LunCmd::FreeNAS::freenas_parse_blocksize();
|
||||
+ if (! || < ) {
|
||||
+ warn "freenas-proxmox: blocksize " . ( ? : 'unset') . " < recommended -- correcting storage ''\n";
|
||||
+ = ;
|
||||
+ eval {
|
||||
+ require PVE::Storage;
|
||||
+ PVE::Storage::lock_storage_config(sub {
|
||||
+ my HASH(0x6158b91f52a0) = PVE::Storage::config();
|
||||
+ = if HASH(0x6158b91f5420);
|
||||
+ PVE::Storage::write_config(HASH(0x6158b91f52a0));
|
||||
+ });
|
||||
+ };
|
||||
+ warn "freenas-proxmox: could not persist blocksize to storage config: \n" if ;
|
||||
+ }
|
||||
+ }
|
||||
+ };
|
||||
+ warn "TrueNAS blocksize detection failed: " if ;
|
||||
+ }
|
||||
+
|
||||
$class->zfs_create_zvol($scfg, $volname, $size);
|
||||
|
||||
my $guid = $class->zfs_create_lu($scfg, $volname);
|
||||
|
|
|
|||
Loading…
Reference in New Issue