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 # Deprecated group/name - [DEFAULT]/libvirt_sparse_logical_volumes
#sparse_logical_volumes=false #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 # The RADOS pool in which rbd volumes are stored (string
# value) # value)
# Deprecated group/name - [DEFAULT]/libvirt_images_rbd_pool # 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: # nova/virt/libvirt/utils.py:
rbd: CommandFilter, rbd, root 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.. # nova/virt/libvirt/volume.py: 'cp', '/dev/stdin', delete_control..
cp: CommandFilter, cp, root cp: CommandFilter, cp, root

View File

@ -17,12 +17,15 @@ import os
import tempfile import tempfile
import fixtures import fixtures
from oslo.config import cfg
from nova.openstack.common import processutils from nova.openstack.common import processutils
from nova import test from nova import test
from nova import utils from nova import utils
from nova.virt.libvirt import utils as libvirt_utils from nova.virt.libvirt import utils as libvirt_utils
CONF = cfg.CONF
class LibvirtUtilsTestCase(test.NoDBTestCase): class LibvirtUtilsTestCase(test.NoDBTestCase):
def test_get_disk_type(self): def test_get_disk_type(self):
@ -56,6 +59,117 @@ blah BLAH: bb
self.assertEqual(expected_commands, executes) self.assertEqual(expected_commands, executes)
self.assertEqual(size, 123456789) 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): def test_list_rbd_volumes(self):
conf = '/etc/ceph/fake_ceph.conf' conf = '/etc/ceph/fake_ceph.conf'
pool = 'fake_pool' pool = 'fake_pool'

View File

@ -62,6 +62,13 @@ __imagebackend_opts = [
' if this flag is set to True.', ' if this flag is set to True.',
deprecated_group='DEFAULT', deprecated_group='DEFAULT',
deprecated_name='libvirt_sparse_logical_volumes'), 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', cfg.StrOpt('images_rbd_pool',
default='rbd', default='rbd',
help='The RADOS pool in which rbd volumes are stored', help='The RADOS pool in which rbd volumes are stored',

View File

@ -361,26 +361,23 @@ def logical_volume_size(path):
return int(out) return int(out)
def clear_logical_volume(path): def _zero_logical_volume(path, volume_size):
"""Obfuscate the logical volume. """Write zeros over the specified path
:param path: logical volume 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 bs = units.Mi
direct_flags = ('oflag=direct',) direct_flags = ('oflag=direct',)
sync_flags = () sync_flags = ()
remaining_bytes = vol_size remaining_bytes = volume_size
# The loop caters for versions of dd that # The loop efficiently writes zeros using dd,
# don't support the iflag=count_bytes option. # and caters for versions of dd that don't have
# the easier to use iflag=count_bytes option.
while remaining_bytes: while remaining_bytes:
zero_blocks = remaining_bytes / bs 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, zero_cmd = ('dd', 'bs=%s' % bs,
'if=/dev/zero', 'of=%s' % path, 'if=/dev/zero', 'of=%s' % path,
'seek=%s' % seek_blocks, 'count=%s' % zero_blocks) 'seek=%s' % seek_blocks, 'count=%s' % zero_blocks)
@ -395,6 +392,39 @@ def clear_logical_volume(path):
sync_flags = ('conv=fdatasync',) 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): def remove_logical_volumes(*paths):
"""Remove one or more logical volume.""" """Remove one or more logical volume."""