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:
parent
5aa0cac98f
commit
7194685559
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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'
|
||||
|
@ -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',
|
||||
|
@ -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."""
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user