proxmox: add zfs module for retrieving importable zpool info

Will be used for prompting the user to rename existing "rpool" ZFS
pools, similar to what we do for an existing LVM "pve" volume group.

Signed-off-by: Christoph Heiss <c.heiss@proxmox.com>
Reviewed-by: Aaron Lauterer <a.lauterer@proxmox.com>
Tested-by: Aaron Lauterer <a.lauterer@proxmox.com>
 [ TL: added a bit context for what this will be used and merge in the
   tests into this preparatory commit ]
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
This commit is contained in:
Christoph Heiss 2024-07-16 10:18:07 +02:00 committed by Thomas Lamprecht
parent f39ce7ba56
commit 5443b7049e
4 changed files with 157 additions and 1 deletions

View File

@ -17,6 +17,7 @@ PERL_MODULES=\
Sys/File.pm \
Sys/Net.pm \
Sys/Udev.pm \
Sys/ZFS.pm \
UI.pm \
UI/Base.pm \
UI/Gtk3.pm \

93
Proxmox/Sys/ZFS.pm Normal file
View File

@ -0,0 +1,93 @@
package Proxmox::Sys::ZFS;
use strict;
use warnings;
use Proxmox::Sys::Command qw(run_command);
use base qw(Exporter);
our @EXPORT_OK = qw(get_exported_pools);
# Parses the output of running `zpool import`, which shows all importable
# ZFS pools.
# Unfortunately, `zpool` does not support JSON or any other machine-readable
# format for output, so we have to do it the hard way.
#
# Example output of `zpool import` looks like this:
#
# root@proxmox:/# zpool import
# pool: testpool
# id: 4958685680270539150
# state: ONLINE
# action: The pool can be imported using its name or numeric identifier.
# config:
#
# testpool ONLINE
# vdc ONLINE
# vdd ONLINE
#
# pool: rpool
# id: 9412322616744093413
# state: ONLINE
# status: The pool was last accessed by another system.
# action: The pool can be imported using its name or numeric identifier and
# the '-f' flag.
# see: https://openzfs.github.io/openzfs-docs/msg/ZFS-8000-EY
# config:
#
# rpool ONLINE
# mirror-0 ONLINE
# vda3 ONLINE
# vdb3 ONLINE
#
sub zpool_import_parse_output {
my ($fh) = @_;
my $pools = []; # all found pools
my $pool = {}; # last found pool in output
while (my $line = <$fh>) {
# first, if we find the start of a new pool, add it to the list with
# its name
if ($line =~ /^\s+pool: (.+)$/) {
# push the previous parsed pool to the result list
push @$pools, $pool if %$pool;
$pool = { name => $1 };
next;
}
# ignore any (garbage) output before the actual list, just in case
next if !%$pool;
# add any possibly-useful attribute to the last (aka. current) pool
if ($line =~ /^\s*(id|state|status|action): (.+)$/) {
chomp($pool->{$1} = $2);
next;
}
}
# add the final parsed pool to the list
push @$pools, $pool if %$pool;
return $pools;
}
# Returns an array of all importable ZFS pools on the system.
# Each entry is a hash of the format:
#
# {
# name => '<pool name>',
# id => <numeric pool-id>,
# /* see also zpool_state_to_name() in lib/libzfs/libzfs_pool.c /*
# state => 'OFFLINE' | 'REMOVED' | 'FAULTED' | 'SPLIT' | 'UNAVAIL' \
# | 'FAULTED' | 'DEGRADED' | 'ONLINE',
# status => '<human-readable status string>', optional
# action => '<human-readable action string>', optional
# }
sub get_exported_pools {
my $raw = run_command(['zpool', 'import']);
open (my $fh, '<', \$raw) or die 'failed to open in-memory stream';
return zpool_import_parse_output($fh);
}
1;

View File

@ -3,12 +3,13 @@ all:
export PERLLIB=..
.PHONY: check
check: test-zfs-arc-max test-run-command test-parse-fqdn test-ui2-stdio
check: test-zfs-arc-max test-run-command test-parse-fqdn test-ui2-stdio test-zfs-get-pool-list
.PHONY: test-zfs-arc-max
test-zfs-arc-max:
./zfs-arc-max.pl
.PHONY: test-run-command
test-run-command:
./run-command.pl
@ -19,3 +20,7 @@ test-parse-fqdn:
.PHONY: test-ui2-stdio
test-ui2-stdio:
./ui2-stdio.pl
.PHONY: test-zfs-get-pool-list
test-zfs-get-pool-list:
./zfs-get-pool-list.pl

57
test/zfs-get-pool-list.pl Executable file
View File

@ -0,0 +1,57 @@
#!/usr/bin/perl
use strict;
use warnings;
use File::Temp;
use Test::More tests => 8;
use Proxmox::Sys::ZFS;
use Proxmox::UI;
my $log_file = File::Temp->new();
Proxmox::Log::init($log_file->filename);
Proxmox::UI::init_stdio();
our $ZPOOL_IMPORT_TEST_OUTPUT = <<EOT;
pool: testpool
id: 4958685680270539150
state: ONLINE
action: The pool can be imported using its name or numeric identifier.
config:
testpool ONLINE
vdc ONLINE
vdd ONLINE
pool: rpool
id: 9412322616744093413
state: FAULTED
status: The pool was last accessed by another system.
action: The pool can be imported using its name or numeric identifier and
the '-f' flag.
see: https://openzfs.github.io/openzfs-docs/msg/ZFS-8000-EY
config:
rpool ONLINE
mirror-0 ONLINE
vda3 ONLINE
vdb3 ONLINE
EOT
my $pools = {
testpool => { id => '4958685680270539150', state => 'ONLINE' },
rpool => { id => '9412322616744093413', state => 'FAULTED' },
};
open(my $fh, '<', \$ZPOOL_IMPORT_TEST_OUTPUT);
my $result = Proxmox::Sys::ZFS::zpool_import_parse_output($fh);
while (my ($name, $info) = each %$pools) {
my ($p) = grep { $_->{name} eq $name } @$result;
ok(defined($p), "pool $name was found");
is($p->{id}, $info->{id}, "pool $name has correct id");
is($p->{state}, $info->{state}, "pool $name has correct state");
like($p->{action}, qr/^The pool can be imported using its name or numeric identifier/,
"pool $name can be imported");
}