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
|
# 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
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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'
|
||||||
|
@ -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',
|
||||||
|
@ -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."""
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user