Move wipe_disks from PVE::Ceph::Tools to PVE::Diskmanage and improve it by
 - Handling invalid parameters
 - Adding options for wiping
 - Making names clearer
 - Adding tests

Relies on the corresponding patch in pve-manager.

Signed-off-by: Dominic Jäger <d.jae...@proxmox.com>
---
v2->v3:
 - Relative instead of full paths
 - Use run_command for system calls
 - Die when a device is invalid instead of skipping
 - More concise syntax
 - More tests (with fake blockdevice path)
v1->v2:
 - Fix typo
 - Add improvements

@Dominik: Thank you for the feedback!

 PVE/Diskmanage.pm     |  58 ++++++++++++++++++++
 test/disklist_test.pm | 125 ++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 183 insertions(+)

diff --git a/PVE/Diskmanage.pm b/PVE/Diskmanage.pm
index abb90a7..448f753 100644
--- a/PVE/Diskmanage.pm
+++ b/PVE/Diskmanage.pm
@@ -761,4 +761,62 @@ sub append_partition {
     return $partition;
 }
 
+# Return undef on error, the size of the blockdev otherwise
+sub get_blockdev_size {
+    my ($dev) = @_;
+    if (!defined($dev)) {
+       warn "Undefined parameter";
+       return undef;
+    }
+    if (!assert_blockdev($dev, 1)) {
+       warn "$dev is not a blockdevice";
+       return undef;
+    }
+    my $size;
+    my $exitcode = eval { run_command(
+       ['blockdev', '--getsize64', $dev],
+       (outfunc => sub { $size = shift })
+    )};
+    warn "run_command for \'blockdev\' syscall failed: $@" if $@;
+    return $size;
+}
+
+# Wipe the first part of a device
+#
+# Calls dd and optionally wipefs. This is useful to clear off leftovers from
+# previous use as it avoids failing OSD and directory creation.
+#
+# $devices array of blockdevices (including partitions), e.g. ['/dev/sda3']
+# $options hash with
+#   - {amount} number of bytes that will be deleted. Will wipe at most the
+#      device size. Default: 209715200.
+#   - {wipefs} additionally erase all available signatures from all devices.
+#      Default: false
+sub wipe_blockdevices {
+    my ($devices, $options) = @_;
+    my @dd_cmd = qw(dd if=/dev/zero iflag=count_bytes conv=fdatasync);
+    my $max_amount = $options->{amount} // 209715200;
+    die "$max_amount is not a natural number and thus an invalid value for 
max_amount."
+       if ($max_amount !~ /^[0-9]+$/);
+
+    my %toDelete = ();
+    foreach my $dev (@$devices) {
+       my $device_size = get_blockdev_size($dev);
+       die "Not wiping anything because could not get size of $dev." if 
!defined($device_size);
+       my $amount = ($device_size < $max_amount) ? $device_size : $max_amount;
+       $toDelete{$dev} = $amount;
+    }
+
+    foreach my $dev (keys %toDelete) {
+       print "Wiping device: $dev\n";
+       if ($options->{wipefs}) {
+           eval { run_command(['wipefs', '-a', $dev]) };
+           warn $@ if $@;
+       }
+       # 1M seems reasonable for this case
+       eval { run_command([@dd_cmd, "count=$toDelete{$dev}", "bs=1M", 
"of=$dev"]) };
+       warn $@ if $@;
+    }
+}
+
 1;
diff --git a/test/disklist_test.pm b/test/disklist_test.pm
index 9cb6763..e5d339b 100644
--- a/test/disklist_test.pm
+++ b/test/disklist_test.pm
@@ -10,6 +10,7 @@ use PVE::Tools;
 
 use Test::MockModule;
 use Test::More;
+use Capture::Tiny;
 use JSON;
 use Data::Dumper;
 
@@ -248,4 +249,128 @@ while (readdir $dh) {
     test_disk_list($dir);
 }
 
+subtest 'get_blockdev_size' => sub {
+    plan tests => 8;
+
+    my $blockdevice = '/fake/block/device';
+    my $msg = "Test";
+    $diskmanage_module->mock('assert_blockdev' => sub { return 1 });
+    {
+       $diskmanage_module->mock('run_command' => sub { die });
+       my (undef, $stderr, $exitcode) = Capture::Tiny::capture { 
PVE::Diskmanage::get_blockdev_size(undef) };
+       is ($exitcode, undef, 'Return undef if parameter is undefined.');
+       like ($stderr, qr!Undefined parameter!, 'Print warning for undefined 
parameter.');
+    }
+    {
+       $diskmanage_module->mock('assert_blockdev' => sub { return undef });
+       my (undef, $stderr, $exitcode) = Capture::Tiny::capture { 
PVE::Diskmanage::get_blockdev_size($blockdevice) };
+       is ($exitcode, undef, 'Return undef if device is not a blockdevice.');
+       like ($stderr, qr!not a blockdev!, 'Print warning for invalid 
blockdevices.');
+       $diskmanage_module->mock('assert_blockdev' => sub { return 1 });
+    }
+    {
+       $diskmanage_module->mock('run_command' => sub { return 0 });
+       my (undef, $stderr, $exitcode) = Capture::Tiny::capture { 
PVE::Diskmanage::get_blockdev_size($blockdevice) };
+       is ($exitcode, undef, 'Return undef when reading size from stdout 
failed.');
+    }
+    {
+       $diskmanage_module->mock('run_command' => sub { die });
+       my (undef, $stderr, $exitcode) = Capture::Tiny::capture { 
PVE::Diskmanage::get_blockdev_size($blockdevice) };
+       is ($exitcode, undef, 'Return undef on error without message.');
+    }
+    {
+       $diskmanage_module->mock('run_command' => sub { die $msg});
+       my (undef, $stderr, $exitcode) = Capture::Tiny::capture { 
PVE::Diskmanage::get_blockdev_size($blockdevice) };
+       is ($exitcode, undef, 'Return undef on error with message.');
+       like ($stderr, qr!${msg}!, 'Print error message from run_command as 
warning.');
+    }
+    $testcount++;
+};
+
+subtest 'wipe_blockdevices' => sub {
+    plan tests => 12;
+
+    our $blockdevice = '/fake/block/device';
+    sub wipe_helper {
+       my ($options, $devices) = @_;
+       $devices //= [$blockdevice];
+       return Capture::Tiny::capture {
+           PVE::Diskmanage::wipe_blockdevices($devices, $options);
+       };
+    }
+
+    my $device_size = 0;
+    $diskmanage_module->mock('run_command' => sub { });
+    $diskmanage_module->mock('get_blockdev_size' => sub { return $device_size 
});
+    {
+       eval { PVE::Diskmanage::wipe_blockdevices([$blockdevice], 
{amount=>-3})};
+       like ($@, qr/-3 is not/, 'Die with negative amounts');
+    }
+    {
+       $diskmanage_module->mock('get_blockdev_size' => sub { return undef });
+       eval { PVE::Diskmanage::wipe_blockdevices([$blockdevice])};
+       like ($@, qr!Not wiping anything!, 'Die when helper returns undef');
+       $diskmanage_module->mock('get_blockdev_size' => sub { return 
$device_size });
+    }
+    {
+       $diskmanage_module->unmock('get_blockdev_size');
+       $diskmanage_module->mock('assert_blockdev' => sub { return undef });
+       my (undef, $stderr, $exitcode) = Capture::Tiny::capture {
+           eval { PVE::Diskmanage::wipe_blockdevices([$blockdevice]) }
+       };
+       like ($@, qr!Not wiping anything!, 'Die if helper finds an invalid 
device');
+       like ($stderr, qr!is not a blockdev!, 'Helper prints a reason for 
dying');
+       $diskmanage_module->mock('get_blockdev_size' => sub { return 
$device_size });
+    }
+    {
+       $diskmanage_module->mock('get_blockdev_size' => sub { return undef });
+       eval { PVE::Diskmanage::wipe_blockdevices([$blockdevice])};
+       like ($@, qr!Not wiping anything!, 'Die with invalid devices');
+       $diskmanage_module->mock('get_blockdev_size' => sub { return 
$device_size });
+    }
+    {
+       my ($stdout, undef, undef) = wipe_helper();
+       like ($stdout, qr!Wiping device: $blockdevice\n!, 'Invoking with a 
disk');
+    }
+    {
+       my ($stdout, undef, undef) = wipe_helper(undef, ["${blockdevice}1"]);
+       like ($stdout, qr!Wiping device: ${blockdevice}1\n!, 'Invoking with a 
partition');
+    }
+
+    $diskmanage_module->mock('run_command' => sub { print Dumper(@_) });
+    my $default_max_wipe_amount = 209715200;
+    {
+       $device_size = $default_max_wipe_amount-1;
+       my ($stdout, undef, undef) = wipe_helper();
+       like ($stdout, qr!count=$device_size!,
+               'Device size is smaller than default maximum wipe amount');
+    }
+    {
+       $device_size = $default_max_wipe_amount+1;
+       my ($stdout, undef, undef) = wipe_helper();
+       like ($stdout, qr!count=$default_max_wipe_amount!,
+               'Device size is bigger than default maximum wipe amount');
+    }
+
+    my $changed_max_wipe_amount = 50000;
+    {
+       $device_size = $changed_max_wipe_amount-1;
+       my ($stdout, undef, undef) = 
wipe_helper({amount=>$changed_max_wipe_amount});
+       like ($stdout, qr!count=$device_size!,
+               'Device size is smaller than changed maximum wipe amount');
+    }
+    {
+       $device_size = $changed_max_wipe_amount+1;
+       my ($stdout, undef, undef) = 
wipe_helper({amount=>$changed_max_wipe_amount});
+       like ($stdout, qr!count=$changed_max_wipe_amount!,
+               'Device size is bigger than changed maximum wipe amount');
+    }
+    {
+       $device_size = 0;
+       my ($stdout, $stderr, undef) = wipe_helper({wipefs=>1});
+       like ($stdout, qr!'wipefs'!, 'Call wipefs');
+    }
+    $testcount++;
+};
+
 done_testing($testcount);
-- 
2.20.1

_______________________________________________
pve-devel mailing list
pve-devel@pve.proxmox.com
https://pve.proxmox.com/cgi-bin/mailman/listinfo/pve-devel

Reply via email to