Move nbd commands to privsep.

The same pattern as previous patches. Some of these unit tests
are starting to be a bit simpler as we finish the transition.

Change-Id: If0e1fe4c0466f2f88525dc575af2ef366d4bb59d
blueprint: hurrah-for-privsep
This commit is contained in:
Michael Still 2017-09-27 06:59:01 +10:00
parent fd4b2aa4cb
commit c7dae4e19b
7 changed files with 88 additions and 100 deletions

View File

@ -10,10 +10,6 @@ kpartx: CommandFilter, kpartx, root
# nova/virt/xenapi/vm_utils.py: tune2fs, -j, partition_path
tune2fs: CommandFilter, tune2fs, root
# nova/virt/disk/mount/nbd.py: 'qemu-nbd', '-c', device, image
# nova/virt/disk/mount/nbd.py: 'qemu-nbd', '-d', device
qemu-nbd: CommandFilter, qemu-nbd, root
# nova/virt/disk/vfs/localfs.py: 'blkid', '-o', 'value', '-s', 'TYPE', device
blkid: CommandFilter, blkid, root

View File

@ -97,3 +97,13 @@ def loopsetup(path):
@nova.privsep.sys_admin_pctxt.entrypoint
def loopremove(device):
return processutils.execute('losetup', '--detach', device, attempts=3)
@nova.privsep.sys_admin_pctxt.entrypoint
def nbd_connect(device, image):
return processutils.execute('qemu-nbd', '-c', device, image)
@nova.privsep.sys_admin_pctxt.entrypoint
def nbd_disconnect(device):
return processutils.execute('qemu-nbd', '-d', device)

View File

@ -143,70 +143,45 @@ class NbdTestCase(test.NoDBTestCase):
n = nbd.NbdMount(self.file, tempdir)
self.assertFalse(n._inner_get_dev())
def test_inner_get_dev_qemu_fails(self):
@mock.patch('nova.privsep.fs.nbd_connect', return_value=('', 'broken'))
def test_inner_get_dev_qemu_fails(self, mock_nbd_connect):
tempdir = self.useFixture(fixtures.TempDir()).path
n = nbd.NbdMount(self.file, tempdir)
self.useFixture(fixtures.MonkeyPatch('os.path.exists',
_fake_exists_no_users))
# We have a trycmd that always fails
def fake_trycmd(*args, **kwargs):
return '', 'broken'
self.useFixture(fixtures.MonkeyPatch('nova.utils.trycmd', fake_trycmd))
# Error logged, no device consumed
self.assertFalse(n._inner_get_dev())
self.assertTrue(n.error.startswith('qemu-nbd error'))
def test_inner_get_dev_qemu_timeout(self):
@mock.patch('random.shuffle')
@mock.patch('os.path.exists', side_effect=[True, False, False, False,
False, False, False, False])
@mock.patch('os.listdir', return_value=['nbd0', 'nbd1', 'loop0'])
@mock.patch('nova.privsep.fs.nbd_connect', return_value=('', ''))
@mock.patch('nova.privsep.fs.nbd_disconnect', return_value=('', ''))
@mock.patch('time.sleep')
def test_inner_get_dev_qemu_timeout(self, mock_sleep, mock_nbd_disconnct,
mock_nbd_connect, mock_exists,
mock_listdir, mock_shuffle):
self.flags(timeout_nbd=3)
tempdir = self.useFixture(fixtures.TempDir()).path
n = nbd.NbdMount(self.file, tempdir)
self.useFixture(fixtures.MonkeyPatch('os.path.exists',
_fake_exists_no_users))
# We have a trycmd that always passed
def fake_trycmd(*args, **kwargs):
return '', ''
self.useFixture(fixtures.MonkeyPatch('nova.utils.trycmd', fake_trycmd))
self.useFixture(fixtures.MonkeyPatch('time.sleep', _fake_noop))
# Error logged, no device consumed
self.assertFalse(n._inner_get_dev())
self.assertTrue(n.error.endswith('did not show up'))
def fake_exists_one(self, path):
# We need the pid file for the device which is allocated to exist, but
# only once it is allocated to us
if path.startswith('/sys/block/nbd'):
if path == '/sys/block/nbd1/pid':
return False
if path.endswith('pid'):
return False
return True
return ORIG_EXISTS(path)
def fake_trycmd_creates_pid(self, *args, **kwargs):
def fake_exists_two(path):
if path.startswith('/sys/block/nbd'):
if path == '/sys/block/nbd0/pid':
return True
if path.endswith('pid'):
return False
return True
return ORIG_EXISTS(path)
self.useFixture(fixtures.MonkeyPatch('os.path.exists',
fake_exists_two))
return '', ''
def test_inner_get_dev_works(self):
@mock.patch('random.shuffle')
@mock.patch('os.path.exists', side_effect=[True, False, False, False,
False, True])
@mock.patch('os.listdir', return_value=['nbd0', 'nbd1', 'loop0'])
@mock.patch('nova.privsep.fs.nbd_connect', return_value=('', ''))
@mock.patch('nova.privsep.fs.nbd_disconnect')
def test_inner_get_dev_works(self, mock_nbd_disconnect, mock_nbd_connect,
mock_exists, mock_listdir, mock_shuffle):
tempdir = self.useFixture(fixtures.TempDir()).path
n = nbd.NbdMount(self.file, tempdir)
self.useFixture(fixtures.MonkeyPatch('random.shuffle', _fake_noop))
self.useFixture(fixtures.MonkeyPatch('os.path.exists',
self.fake_exists_one))
self.useFixture(fixtures.MonkeyPatch('nova.utils.trycmd',
self.fake_trycmd_creates_pid))
self.useFixture(fixtures.MonkeyPatch('nova.utils.execute', _fake_noop))
# No error logged, device consumed
self.assertTrue(n._inner_get_dev())
@ -228,15 +203,16 @@ class NbdTestCase(test.NoDBTestCase):
self.useFixture(fixtures.MonkeyPatch('nova.utils.execute', _fake_noop))
n.unget_dev()
def test_get_dev(self):
@mock.patch('random.shuffle')
@mock.patch('os.path.exists', side_effect=[True, False, False, False,
False, True])
@mock.patch('os.listdir', return_value=['nbd0', 'nbd1', 'loop0'])
@mock.patch('nova.privsep.fs.nbd_connect', return_value=('', ''))
@mock.patch('nova.privsep.fs.nbd_disconnect')
def test_get_dev(self, mock_nbd_disconnect, mock_nbd_connect,
mock_exists, mock_listdir, mock_shuffle):
tempdir = self.useFixture(fixtures.TempDir()).path
n = nbd.NbdMount(self.file, tempdir)
self.useFixture(fixtures.MonkeyPatch('random.shuffle', _fake_noop))
self.useFixture(fixtures.MonkeyPatch('nova.utils.execute', _fake_noop))
self.useFixture(fixtures.MonkeyPatch('os.path.exists',
self.fake_exists_one))
self.useFixture(fixtures.MonkeyPatch('nova.utils.trycmd',
self.fake_trycmd_creates_pid))
# No error logged, device consumed
self.assertTrue(n.get_dev())
@ -250,22 +226,18 @@ class NbdTestCase(test.NoDBTestCase):
self.assertEqual('', n.error)
self.assertIsNone(n.device)
def test_get_dev_timeout(self):
# Always fail to get a device
def fake_get_dev_fails(self):
return False
self.stub_out('nova.virt.disk.mount.nbd.NbdMount._inner_get_dev',
fake_get_dev_fails)
@mock.patch('random.shuffle')
@mock.patch('time.sleep')
@mock.patch('nova.privsep.fs.nbd_connect')
@mock.patch('nova.privsep.fs.nbd_disconnect')
@mock.patch('os.path.exists', return_value=True)
@mock.patch('nova.virt.disk.mount.nbd.NbdMount._inner_get_dev',
return_value=False)
def test_get_dev_timeout(self, mock_get_dev, mock_exists,
mock_nbd_disconnect, mock_nbd_connect,
mock_sleep, mock_shuffle):
tempdir = self.useFixture(fixtures.TempDir()).path
n = nbd.NbdMount(self.file, tempdir)
self.useFixture(fixtures.MonkeyPatch('random.shuffle', _fake_noop))
self.useFixture(fixtures.MonkeyPatch('time.sleep', _fake_noop))
self.useFixture(fixtures.MonkeyPatch('nova.utils.execute', _fake_noop))
self.useFixture(fixtures.MonkeyPatch('os.path.exists',
self.fake_exists_one))
self.useFixture(fixtures.MonkeyPatch('nova.utils.trycmd',
self.fake_trycmd_creates_pid))
self.useFixture(fixtures.MonkeyPatch(('nova.virt.disk.mount.api.'
'MAX_DEVICE_WAIT'), -10))
@ -288,7 +260,11 @@ class NbdTestCase(test.NoDBTestCase):
self.assertFalse(mount.do_mount())
def test_device_creation_race(self):
@mock.patch('nova.privsep.fs.nbd_connect')
@mock.patch('nova.privsep.fs.nbd_disconnect')
@mock.patch('os.path.exists')
def test_device_creation_race(self, mock_exists, mock_nbd_disconnect,
mock_nbd_connect):
# Make sure that even if two threads create instances at the same time
# they cannot choose the same nbd number (see bug 1207422)
@ -316,10 +292,8 @@ class NbdTestCase(test.NoDBTestCase):
self.stub_out('nova.virt.disk.mount.nbd.NbdMount._allocate_nbd',
fake_find_unused)
self.useFixture(fixtures.MonkeyPatch('nova.utils.trycmd',
delay_and_remove_device))
self.useFixture(fixtures.MonkeyPatch('os.path.exists',
pid_exists))
mock_nbd_connect.side_effect = delay_and_remove_device
mock_exists.side_effect = pid_exists
def get_a_device():
n = nbd.NbdMount(self.file, tempdir)

View File

@ -214,8 +214,10 @@ class TestVirtDisk(test.NoDBTestCase):
@mock.patch('os.path.exists', return_value=True)
@mock.patch('nova.privsep.fs.loopremove')
@mock.patch('nova.privsep.fs.umount')
@mock.patch('nova.privsep.fs.nbd_disconnect')
def test_lxc_teardown_container(
self, mock_umount, mock_loopremove, mock_exist):
self, mock_nbd_disconnect, mock_umount, mock_loopremove,
mock_exist):
def proc_mounts(mount_point):
mount_points = {
@ -248,18 +250,20 @@ class TestVirtDisk(test.NoDBTestCase):
disk_api.teardown_container('/mnt/nbd/nopart')
expected_commands += [
('blockdev', '--flushbufs', '/dev/nbd15'),
('qemu-nbd', '-d', '/dev/nbd15'),
]
mock_nbd_disconnect.assert_has_calls([mock.call('/dev/nbd15')])
mock_umount.assert_has_calls([mock.call('/dev/nbd15')])
mock_nbd_disconnect.reset_mock()
mock_umount.reset_mock()
disk_api.teardown_container('/mnt/nbd/part')
expected_commands += [
('blockdev', '--flushbufs', '/dev/nbd15'),
('kpartx', '-d', '/dev/nbd15'),
('qemu-nbd', '-d', '/dev/nbd15'),
]
mock_nbd_disconnect.assert_has_calls([mock.call('/dev/nbd15')])
mock_umount.assert_has_calls([mock.call('/dev/mapper/nbd15p1')])
mock_nbd_disconnect.reset_mock()
mock_umount.reset_mock()
# NOTE(thomasem): Not adding any commands in this case, because we're
@ -274,10 +278,10 @@ class TestVirtDisk(test.NoDBTestCase):
@mock.patch('nova.virt.disk.api._DiskImage._device_for_path',
return_value=None)
@mock.patch('nova.privsep.fs.loopremove')
@mock.patch('nova.privsep.fs.nbd_disconnect')
def test_lxc_teardown_container_with_namespace_cleaned(
self, mock_loopremove, mock_device_for_path, mock_exists):
expected_commands = []
self, mock_nbd_disconnect, mock_loopremove, mock_device_for_path,
mock_exists):
disk_api.teardown_container('/mnt/loop/nopart', '/dev/loop0')
mock_loopremove.assert_has_calls([mock.call('/dev/loop0')])
@ -288,13 +292,9 @@ class TestVirtDisk(test.NoDBTestCase):
mock_loopremove.reset_mock()
disk_api.teardown_container('/mnt/nbd/nopart', '/dev/nbd15')
expected_commands += [
('qemu-nbd', '-d', '/dev/nbd15'),
]
mock_nbd_disconnect.assert_has_calls([mock.call('/dev/nbd15')])
mock_nbd_disconnect.reset_mock()
disk_api.teardown_container('/mnt/nbd/part', '/dev/nbd15')
expected_commands += [
('qemu-nbd', '-d', '/dev/nbd15'),
]
self.assertEqual(self.executes, expected_commands)
mock_nbd_disconnect.assert_has_calls([mock.call('/dev/nbd15')])
mock_nbd_disconnect.reset_mock()

View File

@ -37,6 +37,7 @@ from oslo_serialization import jsonutils
import nova.conf
from nova import exception
from nova.i18n import _
import nova.privsep.fs
import nova.privsep.libvirt
from nova import utils
from nova.virt.disk.mount import api as mount
@ -450,8 +451,7 @@ def teardown_container(container_dir, container_root_device=None):
nova.privsep.fs.loopremove(container_root_device)
elif 'nbd' in container_root_device:
LOG.debug('Release nbd device %s', container_root_device)
utils.execute('qemu-nbd', '-d', container_root_device,
run_as_root=True)
nova.privsep.fs.nbd_disconnect(container_root_device)
else:
LOG.debug('No release necessary for block device %s',
container_root_device)

View File

@ -18,10 +18,13 @@ import random
import re
import time
from oslo_concurrency import processutils
from oslo_log import log as logging
import six
import nova.conf
from nova.i18n import _
import nova.privsep.fs
from nova import utils
from nova.virt.disk.mount import api
@ -76,9 +79,11 @@ class NbdMount(api.Mount):
# already in use.
LOG.debug('Get nbd device %(dev)s for %(imgfile)s',
{'dev': device, 'imgfile': self.image.path})
_out, err = utils.trycmd('qemu-nbd', '-c', device,
self.image.path,
run_as_root=True)
try:
_out, err = nova.privsep.fs.nbd_connect(device, self.image.path)
except processutils.ProcessExecutionError as exc:
err = six.text_type(exc)
if err:
self.error = _('qemu-nbd error: %s') % err
LOG.info('NBD mount error: %s', self.error)
@ -97,8 +102,11 @@ class NbdMount(api.Mount):
LOG.info('NBD mount error: %s', self.error)
# Cleanup
_out, err = utils.trycmd('qemu-nbd', '-d', device,
run_as_root=True)
try:
_out, err = nova.privsep.fs.nbd_disconnect(device)
except processutils.ProcessExecutionError as exc:
err = six.text_type(exc)
if err:
LOG.warning('Detaching from erroneous nbd device returned '
'error: %s', err)
@ -116,7 +124,7 @@ class NbdMount(api.Mount):
if not self.linked:
return
LOG.debug('Release nbd device %s', self.device)
utils.execute('qemu-nbd', '-d', self.device, run_as_root=True)
nova.privsep.fs.nbd_disconnect(self.device)
self.linked = False
self.device = None

View File

@ -11,5 +11,5 @@ upgrade:
- |
The following commands are no longer required to be listed in your rootwrap
configuration: cat; chown; cryptsetup; dd; losetup; lvcreate; lvremove;
lvs; mkdir; mount; nova-idmapshift; ploop; prl_disk_tool; readlink; shred;
tee; touch; umount; vgs; and xend.
lvs; mkdir; mount; nova-idmapshift; ploop; prl_disk_tool; qemu-nbd;
readlink; shred; tee; touch; umount; vgs; and xend.