[pve-devel] [PATCH storage v4 2/3] Incorporate wipe_disks from PVE::Ceph::Tools
Dominic Jäger
d.jaeger at proxmox.com
Thu Apr 16 12:26:10 CEST 2020
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.jaeger at proxmox.com>
---
v3->v4:
- Die more often
- More attention to error messages (definedness)
- Cleaner naming
- Calculate deleted amount later
- \d instead of [0-9]: Users cannot manually control this parameter => No
security or long term functionality problems => Style guide says \d
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
PVE/Diskmanage.pm | 50 ++++++++++++++++++
test/disklist_test.pm | 120 ++++++++++++++++++++++++++++++++++++++++++
2 files changed, 170 insertions(+)
diff --git a/PVE/Diskmanage.pm b/PVE/Diskmanage.pm
index 13e7cd8..ece3400 100644
--- a/PVE/Diskmanage.pm
+++ b/PVE/Diskmanage.pm
@@ -761,4 +761,54 @@ sub append_partition {
return $partition;
}
+sub get_blockdev_size {
+ my ($dev) = @_;
+ die "Undefined blockdevice\n" if !defined($dev);
+ assert_blockdev($dev);
+ my $size;
+ run_command(['blockdev', '--getsize64', $dev], (outfunc => sub { $size = shift }));
+ die "Could not read size from run_command\n" if !defined($size);
+ 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 (= 200MB).
+# - {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} // 200 * 1024 * 1024;
+ die "Invalid max_amount '$max_amount' - must be a positive integer\n"
+ if ($max_amount !~ /^\d+$/);
+
+ my %dev_sizes = ();
+ foreach my $dev (@$devices) {
+ my $size;
+ eval { $size = get_blockdev_size($dev) };
+ die "Not wiping anything - Undefined list element\n" if $@ =~ /Undefined block/;
+ die "Not wiping anything because of $dev - $@\n" if $@;
+ $dev_sizes{$dev} = $size;
+ }
+
+ foreach my $dev (keys %dev_sizes) {
+ print "Wiping device: $dev\n";
+ if ($options->{wipefs}) {
+ eval { run_command(['wipefs', '-a', $dev]) };
+ warn $@ if $@;
+ }
+ my $amount = ($dev_sizes{$dev} < $max_amount) ? $dev_sizes{$dev} : $max_amount;
+ # 1M seems reasonable for this case
+ eval { run_command([@dd_cmd, "count=$amount", "bs=1M", "of=$dev"]) };
+ warn $@ if $@;
+ }
+}
+
1;
diff --git a/test/disklist_test.pm b/test/disklist_test.pm
index 9cb6763..ce59c6e 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,123 @@ while (readdir $dh) {
test_disk_list($dir);
}
+subtest 'get_blockdev_size' => sub {
+ plan tests => 5;
+
+ my $blockdevice = '/fake/block/device';
+ my $msg = "Test";
+ $diskmanage_module->mock('assert_blockdev' => sub { return 1 });
+ {
+ eval { PVE::Diskmanage::get_blockdev_size(undef) };
+ like ($@, qr!Undefined blockdevice!, 'Die if parameter is undefined.');
+ }
+ {
+ $diskmanage_module->mock('assert_blockdev' => sub { die });
+ eval { PVE::Diskmanage::get_blockdev_size($blockdevice) };
+ like ($@, qr!Died at!, 'Die if blockdevice is invalid.');
+ $diskmanage_module->mock('assert_blockdev' => sub { return 1 });
+ }
+ {
+ $diskmanage_module->mock('run_command' => sub { return 0 });
+ eval { PVE::Diskmanage::get_blockdev_size($blockdevice) };
+ like ($@, qr!Could not read size!, 'Die if reading size from stdout failed.');
+ }
+ {
+ $diskmanage_module->mock('run_command' => sub { die });
+ eval { PVE::Diskmanage::get_blockdev_size($blockdevice) };
+ like ($@, qr!Died at!, 'Die if run command dies without message.');
+ }
+ {
+ $diskmanage_module->mock('run_command' => sub { die $msg});
+ eval { PVE::Diskmanage::get_blockdev_size($blockdevice) };
+ like ($@, qr!$msg!, 'Die if run command dies with message.');
+ }
+ $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;
+ my $msg = 'Some error message';
+ $diskmanage_module->mock('run_command' => sub { });
+ {
+ eval { PVE::Diskmanage::wipe_blockdevices([$blockdevice], {amount=>-3})};
+ like ($@, qr/'-3' - must be a positive integer/, 'Die with negative amounts');
+ }
+ {
+ $diskmanage_module->mock('get_blockdev_size' => sub { die $msg });
+ eval { PVE::Diskmanage::wipe_blockdevices([$blockdevice])};
+ like ($@, qr!$msg!, 'Die if helper dies');
+ }
+ {
+ $diskmanage_module->unmock('get_blockdev_size');
+ eval { PVE::Diskmanage::wipe_blockdevices([undef, $blockdevice]) };
+ like ($@, qr!Undefined list element!, 'Die if helper finds an undefined device');
+ }
+ {
+ $diskmanage_module->mock('assert_blockdev' => sub { die $msg});
+ eval { PVE::Diskmanage::wipe_blockdevices([$blockdevice]) };
+ like ($@, qr!$msg!, 'Die if helper finds an invalid device');
+ }
+ {
+ $diskmanage_module->mock('assert_blockdev' => sub { return 1});
+ eval { PVE::Diskmanage::wipe_blockdevices([$blockdevice])};
+ like ($@, qr!Could not read size!, 'Die if reading size from stdout failed in helper');
+ }
+ $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
More information about the pve-devel
mailing list