libvirt: support configurable wipe methods for LVM backed instances

Provide configurable methods to clear these volumes.
The new 'volume_clear' and 'volume_clear_size' options
are the same as currently supported by cinder.

* nova/virt/libvirt/imagebackend.py: Define the new options.
* nova/virt/libvirt/utils.py (clear_logical_volume): Support the
new options. Refactor the existing dd method out to
_zero_logic_volume().
* nova/tests/virt/libvirt/test_libvirt_utils.py: Add missing test cases
for the existing clear_logical_volume code, and for the new code
supporting the new clearing methods.
* etc/nova/nova.conf.sample: Add the 2 new config descriptions
to the [libvirt] section.

Change-Id: I5551197f9ec89ae2f9b051696bccdeb1af2c031f
Closes-Bug: #889299
This commit is contained in:
Pádraig Brady 2014-02-11 11:51:39 +00:00
parent 5aa0cac98f
commit 7194685559
5 changed files with 173 additions and 11 deletions

View File

@ -2883,6 +2883,14 @@
# Deprecated group/name - [DEFAULT]/libvirt_sparse_logical_volumes
#sparse_logical_volumes=false
# Method used to wipe old volumes (valid options are: none,
# zero, shred) (string value)
#volume_clear=zero
# Size in MiB to wipe at start of old volumes. 0 => all
# (integer value)
#volume_clear_size=0
# The RADOS pool in which rbd volumes are stored (string
# value)
# Deprecated group/name - [DEFAULT]/libvirt_images_rbd_pool

View File

@ -217,6 +217,9 @@ rpc.mountd: CommandFilter, rpc.mountd, root
# nova/virt/libvirt/utils.py:
rbd: CommandFilter, rbd, root
# nova/virt/libvirt/utils.py: 'shred', '-n3', '-s%d' % volume_size, path
shred: CommandFilter, shred, root
# nova/virt/libvirt/volume.py: 'cp', '/dev/stdin', delete_control..
cp: CommandFilter, cp, root

View File

@ -17,12 +17,15 @@ import os
import tempfile
import fixtures
from oslo.config import cfg
from nova.openstack.common import processutils
from nova import test
from nova import utils
from nova.virt.libvirt import utils as libvirt_utils
CONF = cfg.CONF
class LibvirtUtilsTestCase(test.NoDBTestCase):
def test_get_disk_type(self):
@ -56,6 +59,117 @@ blah BLAH: bb
self.assertEqual(expected_commands, executes)
self.assertEqual(size, 123456789)
def test_lvm_clear(self):
def fake_lvm_size(path):
return lvm_size
def fake_execute(*cmd, **kwargs):
executes.append(cmd)
self.stubs.Set(libvirt_utils, 'logical_volume_size', fake_lvm_size)
self.stubs.Set(utils, 'execute', fake_execute)
# Test the correct dd commands are run for various sizes
lvm_size = 1
executes = []
expected_commands = [('dd', 'bs=1', 'if=/dev/zero', 'of=/dev/v1',
'seek=0', 'count=1', 'conv=fdatasync')]
libvirt_utils.clear_logical_volume('/dev/v1')
self.assertEqual(expected_commands, executes)
lvm_size = 1024
executes = []
expected_commands = [('dd', 'bs=1024', 'if=/dev/zero', 'of=/dev/v2',
'seek=0', 'count=1', 'conv=fdatasync')]
libvirt_utils.clear_logical_volume('/dev/v2')
self.assertEqual(expected_commands, executes)
lvm_size = 1025
executes = []
expected_commands = [('dd', 'bs=1024', 'if=/dev/zero', 'of=/dev/v3',
'seek=0', 'count=1', 'conv=fdatasync')]
expected_commands += [('dd', 'bs=1', 'if=/dev/zero', 'of=/dev/v3',
'seek=1024', 'count=1', 'conv=fdatasync')]
libvirt_utils.clear_logical_volume('/dev/v3')
self.assertEqual(expected_commands, executes)
lvm_size = 1048576
executes = []
expected_commands = [('dd', 'bs=1048576', 'if=/dev/zero', 'of=/dev/v4',
'seek=0', 'count=1', 'oflag=direct')]
libvirt_utils.clear_logical_volume('/dev/v4')
self.assertEqual(expected_commands, executes)
lvm_size = 1048577
executes = []
expected_commands = [('dd', 'bs=1048576', 'if=/dev/zero', 'of=/dev/v5',
'seek=0', 'count=1', 'oflag=direct')]
expected_commands += [('dd', 'bs=1', 'if=/dev/zero', 'of=/dev/v5',
'seek=1048576', 'count=1', 'conv=fdatasync')]
libvirt_utils.clear_logical_volume('/dev/v5')
self.assertEqual(expected_commands, executes)
lvm_size = 1234567
executes = []
expected_commands = [('dd', 'bs=1048576', 'if=/dev/zero', 'of=/dev/v6',
'seek=0', 'count=1', 'oflag=direct')]
expected_commands += [('dd', 'bs=1024', 'if=/dev/zero', 'of=/dev/v6',
'seek=1024', 'count=181', 'conv=fdatasync')]
expected_commands += [('dd', 'bs=1', 'if=/dev/zero', 'of=/dev/v6',
'seek=1233920', 'count=647', 'conv=fdatasync')]
libvirt_utils.clear_logical_volume('/dev/v6')
self.assertEqual(expected_commands, executes)
# Test volume_clear_size limits the size
lvm_size = 10485761
CONF.set_override('volume_clear_size', '1', 'libvirt')
executes = []
expected_commands = [('dd', 'bs=1048576', 'if=/dev/zero', 'of=/dev/v7',
'seek=0', 'count=1', 'oflag=direct')]
libvirt_utils.clear_logical_volume('/dev/v7')
self.assertEqual(expected_commands, executes)
CONF.set_override('volume_clear_size', '2', 'libvirt')
lvm_size = 1048576
executes = []
expected_commands = [('dd', 'bs=1048576', 'if=/dev/zero', 'of=/dev/v9',
'seek=0', 'count=1', 'oflag=direct')]
libvirt_utils.clear_logical_volume('/dev/v9')
self.assertEqual(expected_commands, executes)
# Test volume_clear=shred
CONF.set_override('volume_clear', 'shred', 'libvirt')
CONF.set_override('volume_clear_size', '0', 'libvirt')
lvm_size = 1048576
executes = []
expected_commands = [('shred', '-n3', '-s1048576', '/dev/va')]
libvirt_utils.clear_logical_volume('/dev/va')
self.assertEqual(expected_commands, executes)
CONF.set_override('volume_clear', 'shred', 'libvirt')
CONF.set_override('volume_clear_size', '1', 'libvirt')
lvm_size = 10485761
executes = []
expected_commands = [('shred', '-n3', '-s1048576', '/dev/vb')]
libvirt_utils.clear_logical_volume('/dev/vb')
self.assertEqual(expected_commands, executes)
# Test volume_clear=none does nothing
CONF.set_override('volume_clear', 'none', 'libvirt')
executes = []
expected_commands = []
libvirt_utils.clear_logical_volume('/dev/vc')
self.assertEqual(expected_commands, executes)
# Test volume_clear=invalid falls back to the default 'zero'
CONF.set_override('volume_clear', 'invalid', 'libvirt')
lvm_size = 1
executes = []
expected_commands = [('dd', 'bs=1', 'if=/dev/zero', 'of=/dev/vd',
'seek=0', 'count=1', 'conv=fdatasync')]
libvirt_utils.clear_logical_volume('/dev/vd')
self.assertEqual(expected_commands, executes)
def test_list_rbd_volumes(self):
conf = '/etc/ceph/fake_ceph.conf'
pool = 'fake_pool'

View File

@ -62,6 +62,13 @@ __imagebackend_opts = [
' if this flag is set to True.',
deprecated_group='DEFAULT',
deprecated_name='libvirt_sparse_logical_volumes'),
cfg.StrOpt('volume_clear',
default='zero',
help='Method used to wipe old volumes (valid options are: '
'none, zero, shred)'),
cfg.IntOpt('volume_clear_size',
default=0,
help='Size in MiB to wipe at start of old volumes. 0 => all'),
cfg.StrOpt('images_rbd_pool',
default='rbd',
help='The RADOS pool in which rbd volumes are stored',

View File

@ -361,26 +361,23 @@ def logical_volume_size(path):
return int(out)
def clear_logical_volume(path):
"""Obfuscate the logical volume.
def _zero_logical_volume(path, volume_size):
"""Write zeros over the specified path
:param path: logical volume path
:param size: number of zeros to write
"""
# TODO(p-draigbrady): We currently overwrite with zeros
# but we may want to make this configurable in future
# for more or less security conscious setups.
vol_size = logical_volume_size(path)
bs = units.Mi
direct_flags = ('oflag=direct',)
sync_flags = ()
remaining_bytes = vol_size
remaining_bytes = volume_size
# The loop caters for versions of dd that
# don't support the iflag=count_bytes option.
# The loop efficiently writes zeros using dd,
# and caters for versions of dd that don't have
# the easier to use iflag=count_bytes option.
while remaining_bytes:
zero_blocks = remaining_bytes / bs
seek_blocks = (vol_size - remaining_bytes) / bs
seek_blocks = (volume_size - remaining_bytes) / bs
zero_cmd = ('dd', 'bs=%s' % bs,
'if=/dev/zero', 'of=%s' % path,
'seek=%s' % seek_blocks, 'count=%s' % zero_blocks)
@ -395,6 +392,39 @@ def clear_logical_volume(path):
sync_flags = ('conv=fdatasync',)
def clear_logical_volume(path):
"""Obfuscate the logical volume.
:param path: logical volume path
"""
volume_clear = CONF.libvirt.volume_clear
if volume_clear not in ('none', 'shred', 'zero'):
LOG.error(_("ignoring unrecognized volume_clear='%s' value"),
volume_clear)
volume_clear = 'zero'
if volume_clear == 'none':
return
volume_clear_size = int(CONF.libvirt.volume_clear_size) * units.Mi
volume_size = logical_volume_size(path)
if volume_clear_size != 0 and volume_clear_size < volume_size:
volume_size = volume_clear_size
if volume_clear == 'zero':
# NOTE(p-draigbrady): we could use shred to do the zeroing
# with -n0 -z, however only versions >= 8.22 perform as well as dd
_zero_logical_volume(path, volume_size)
elif volume_clear == 'shred':
utils.execute('shred', '-n3', '-s%d' % volume_size, path,
run_as_root=True)
else:
raise exception.Invalid(_("volume_clear='%s' is not handled")
% volume_clear)
def remove_logical_volumes(*paths):
"""Remove one or more logical volume."""