From 664dfe6a3e2327f94153f57a3bca2ef529ec4f4c Mon Sep 17 00:00:00 2001 From: Kevin Adams Date: Mon, 12 Oct 2026 16:14:42 -0400 Subject: [PATCH] fix: return (size, format, used, parent) from volume_size_info in list context (#294) PVE's content API (and the bpg/proxmox Terraform provider through it) calls volume_size_info in list context and dies "no format" when the second value is missing, so every disk on a truenas storage failed to refresh. Mirrors ZFSPoolPlugin: used comes from usedbydataset, and a missing zvol now dies instead of reporting a size of 0. Co-Authored-By: Claude Opus 5.5 --- perl5/PVE/Storage/Custom/TrueNAS.pm | 14 ++++++-- t/size.t | 55 +++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 2 deletions(-) create mode 100644 t/size.t diff --git a/perl5/PVE/Storage/Custom/TrueNAS.pm b/perl5/PVE/Storage/Custom/TrueNAS.pm index f93b64f..eea5177 100644 --- a/perl5/PVE/Storage/Custom/TrueNAS.pm +++ b/perl5/PVE/Storage/Custom/TrueNAS.pm @@ -985,10 +985,20 @@ sub volume_size_info { my ($class, $scfg, $storeid, $volname, $timeout) = @_; _resolve_token($storeid, $scfg); + my (undef, undef, undef, $parent, undef, undef, $format) = $class->parse_volname($volname); + my $zvol = _zvol_prefix($scfg) . "/$volname"; my $enc = uri_escape($zvol, "^A-Za-z0-9\\-_.~"); - my $ds = _api($scfg, 'GET', "/pool/dataset/id/$enc") // {}; - return $ds->{volsize}{parsed} // 0; + my $ds = _api($scfg, 'GET', "/pool/dataset/id/$enc"); + die "volume_size_info: zvol '$zvol' not found on $scfg->{truenas_host}\n" + unless ref $ds eq 'HASH' && defined $ds->{volsize}{parsed}; + + my $size = int($ds->{volsize}{parsed}); + my $used = int($ds->{usedbydataset}{parsed} // $ds->{used}{parsed} // 0); + + # List context is (size, format, used, parent), as in ZFSPoolPlugin. A bare + # scalar here made the content API die with "no format" (#294). + return wantarray ? ($size, $format, $used, $parent) : $size; } sub path { diff --git a/t/size.t b/t/size.t new file mode 100644 index 0000000..2e947a8 --- /dev/null +++ b/t/size.t @@ -0,0 +1,55 @@ +#!/usr/bin/perl +# volume_size_info contract: scalar context → size; list context → +# (size, format, used, parent). PVE's content API dies "no format" otherwise. + +use strict; +use warnings; + +use FindBin; +use lib "$FindBin::Bin/lib", "$FindBin::Bin/../perl5"; + +use Test::More; + +use MockTrueNAS; +use PVE::Storage::Custom::TrueNAS; + +my $TN = 'PVE::Storage::Custom::TrueNAS'; + +MockTrueNAS->install(datasets => { + 'tank/vm/vm-100-disk-0' => { + volsize => { parsed => 34359738368 }, + usedbydataset => { parsed => 1073741824 }, + used => { parsed => 2147483648 }, + }, + 'tank/vm/vm-100-cloudinit' => { + volsize => { parsed => 4194304 }, + used => { parsed => 65536 }, + }, +}); + +subtest 'scalar context returns size' => sub { + my $size = $TN->volume_size_info(MockTrueNAS::scfg(), 'store', 'vm-100-disk-0'); + is($size, 34359738368, 'size'); +}; + +subtest 'list context returns (size, format, used, parent) (#294)' => sub { + my @r = $TN->volume_size_info(MockTrueNAS::scfg(), 'store', 'vm-100-disk-0'); + is(scalar @r, 4, 'four values'); + is($r[0], 34359738368, 'size'); + is($r[1], 'raw', 'format'); + is($r[2], 1073741824, 'used prefers usedbydataset'); + ok(!defined $r[3], 'no parent'); +}; + +subtest 'used falls back to used when usedbydataset is absent' => sub { + my (undef, $fmt, $used) = $TN->volume_size_info(MockTrueNAS::scfg(), 'store', 'vm-100-cloudinit'); + is($fmt, 'raw', 'cloud-init drive has a format'); + is($used, 65536, 'used'); +}; + +subtest 'missing zvol dies instead of reporting size 0' => sub { + eval { $TN->volume_size_info(MockTrueNAS::scfg(), 'store', 'vm-100-disk-9') }; + like($@, qr/not found/, 'dies'); +}; + +done_testing();