Move loopback setup and removal to privsep.

Once more, again.

Change-Id: I602582927c30f2929722474f68601ce47b4e98f6
blueprint: hurrah-for-privsep
This commit is contained in:
Michael Still 2017-09-27 06:57:28 +10:00
parent 3f7995f586
commit fd4b2aa4cb
7 changed files with 48 additions and 53 deletions

View File

@ -14,10 +14,6 @@ tune2fs: CommandFilter, tune2fs, root
# nova/virt/disk/mount/nbd.py: 'qemu-nbd', '-d', device # nova/virt/disk/mount/nbd.py: 'qemu-nbd', '-d', device
qemu-nbd: CommandFilter, qemu-nbd, root qemu-nbd: CommandFilter, qemu-nbd, root
# nova/virt/disk/mount/loop.py: 'losetup', '--find', '--show', image
# nova/virt/disk/mount/loop.py: 'losetup', '--detach', device
losetup: CommandFilter, losetup, root
# nova/virt/disk/vfs/localfs.py: 'blkid', '-o', 'value', '-s', 'TYPE', device # nova/virt/disk/vfs/localfs.py: 'blkid', '-o', 'value', '-s', 'TYPE', device
blkid: CommandFilter, blkid, root blkid: CommandFilter, blkid, root

View File

@ -87,3 +87,13 @@ def clear(path, volume_size, shred=False):
cmd.extend(['-n0', '-z']) cmd.extend(['-n0', '-z'])
cmd.extend(['-s%d' % volume_size, path]) cmd.extend(['-s%d' % volume_size, path])
processutils.execute(*cmd) processutils.execute(*cmd)
@nova.privsep.sys_admin_pctxt.entrypoint
def loopsetup(path):
return processutils.execute('losetup', '--find', '--show', path)
@nova.privsep.sys_admin_pctxt.entrypoint
def loopremove(device):
return processutils.execute('losetup', '--detach', device, attempts=3)

View File

@ -15,6 +15,7 @@
import fixtures import fixtures
import mock
from nova import test from nova import test
from nova.virt.disk.mount import loop from nova.virt.disk.mount import loop
@ -25,14 +26,6 @@ def _fake_noop(*args, **kwargs):
return return
def _fake_trycmd_losetup_works(*args, **kwargs):
return '/dev/loop0', ''
def _fake_trycmd_losetup_fails(*args, **kwards):
return '', 'doh'
class LoopTestCase(test.NoDBTestCase): class LoopTestCase(test.NoDBTestCase):
def setUp(self): def setUp(self):
super(LoopTestCase, self).setUp() super(LoopTestCase, self).setUp()
@ -40,11 +33,11 @@ class LoopTestCase(test.NoDBTestCase):
self.file = imgmodel.LocalFileImage("/some/file.qcow2", self.file = imgmodel.LocalFileImage("/some/file.qcow2",
imgmodel.FORMAT_QCOW2) imgmodel.FORMAT_QCOW2)
def test_get_dev(self): @mock.patch('nova.privsep.fs.loopsetup', return_value=('/dev/loop0', ''))
@mock.patch('nova.privsep.fs.loopremove')
def test_get_dev(self, mock_loopremove, mock_loopsetup):
tempdir = self.useFixture(fixtures.TempDir()).path tempdir = self.useFixture(fixtures.TempDir()).path
l = loop.LoopMount(self.file, tempdir) l = loop.LoopMount(self.file, tempdir)
self.useFixture(fixtures.MonkeyPatch('nova.utils.trycmd',
_fake_trycmd_losetup_works))
self.useFixture(fixtures.MonkeyPatch('nova.utils.execute', self.useFixture(fixtures.MonkeyPatch('nova.utils.execute',
_fake_noop)) _fake_noop))
@ -60,11 +53,10 @@ class LoopTestCase(test.NoDBTestCase):
self.assertEqual('', l.error) self.assertEqual('', l.error)
self.assertIsNone(l.device) self.assertIsNone(l.device)
def test_inner_get_dev_fails(self): @mock.patch('nova.privsep.fs.loopsetup', return_value=('', 'doh'))
def test_inner_get_dev_fails(self, mock_loopsetup):
tempdir = self.useFixture(fixtures.TempDir()).path tempdir = self.useFixture(fixtures.TempDir()).path
l = loop.LoopMount(self.file, tempdir) l = loop.LoopMount(self.file, tempdir)
self.useFixture(fixtures.MonkeyPatch('nova.utils.trycmd',
_fake_trycmd_losetup_fails))
# No error logged, device consumed # No error logged, device consumed
self.assertFalse(l._inner_get_dev()) self.assertFalse(l._inner_get_dev())
@ -77,12 +69,11 @@ class LoopTestCase(test.NoDBTestCase):
self.assertFalse(l.linked) self.assertFalse(l.linked)
self.assertIsNone(l.device) self.assertIsNone(l.device)
def test_get_dev_timeout(self): @mock.patch('nova.privsep.fs.loopsetup', return_value=('', 'doh'))
def test_get_dev_timeout(self, mock_loopsetup):
tempdir = self.useFixture(fixtures.TempDir()).path tempdir = self.useFixture(fixtures.TempDir()).path
l = loop.LoopMount(self.file, tempdir) l = loop.LoopMount(self.file, tempdir)
self.useFixture(fixtures.MonkeyPatch('time.sleep', _fake_noop)) self.useFixture(fixtures.MonkeyPatch('time.sleep', _fake_noop))
self.useFixture(fixtures.MonkeyPatch('nova.utils.trycmd',
_fake_trycmd_losetup_fails))
self.useFixture(fixtures.MonkeyPatch(('nova.virt.disk.mount.api.' self.useFixture(fixtures.MonkeyPatch(('nova.virt.disk.mount.api.'
'MAX_DEVICE_WAIT'), -10)) 'MAX_DEVICE_WAIT'), -10))
@ -94,11 +85,10 @@ class LoopTestCase(test.NoDBTestCase):
# Fail to get a device # Fail to get a device
self.assertFalse(l.get_dev()) self.assertFalse(l.get_dev())
def test_unget_dev(self): @mock.patch('nova.privsep.fs.loopremove')
def test_unget_dev(self, mock_loopremove):
tempdir = self.useFixture(fixtures.TempDir()).path tempdir = self.useFixture(fixtures.TempDir()).path
l = loop.LoopMount(self.file, tempdir) l = loop.LoopMount(self.file, tempdir)
self.useFixture(fixtures.MonkeyPatch('nova.utils.execute',
_fake_noop))
# This just checks that a free of something we don't have doesn't # This just checks that a free of something we don't have doesn't
# throw an exception # throw an exception

View File

@ -182,6 +182,8 @@ class TestDiskImage(test.NoDBTestCase):
class TestVirtDisk(test.NoDBTestCase): class TestVirtDisk(test.NoDBTestCase):
def setUp(self): def setUp(self):
super(TestVirtDisk, self).setUp() super(TestVirtDisk, self).setUp()
# TODO(mikal): this can probably be removed post privsep cleanup.
self.executes = [] self.executes = []
def fake_execute(*cmd, **kwargs): def fake_execute(*cmd, **kwargs):
@ -209,8 +211,11 @@ class TestVirtDisk(test.NoDBTestCase):
self.assertEqual(disk_api.setup_container(image, container_dir), self.assertEqual(disk_api.setup_container(image, container_dir),
'/dev/fake') '/dev/fake')
@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.umount')
def test_lxc_teardown_container(self, mock_umount): def test_lxc_teardown_container(
self, mock_umount, mock_loopremove, mock_exist):
def proc_mounts(mount_point): def proc_mounts(mount_point):
mount_points = { mount_points = {
@ -221,21 +226,22 @@ class TestVirtDisk(test.NoDBTestCase):
} }
return mount_points[mount_point] return mount_points[mount_point]
self.stub_out('os.path.exists', lambda _: True)
self.stub_out('nova.virt.disk.api._DiskImage._device_for_path', self.stub_out('nova.virt.disk.api._DiskImage._device_for_path',
proc_mounts) proc_mounts)
expected_commands = [] expected_commands = []
disk_api.teardown_container('/mnt/loop/nopart') disk_api.teardown_container('/mnt/loop/nopart')
expected_commands += [('losetup', '--detach', '/dev/loop0')] mock_loopremove.assert_has_calls([mock.call('/dev/loop0')])
mock_loopremove.reset_mock()
mock_umount.assert_has_calls([mock.call('/dev/loop0')]) mock_umount.assert_has_calls([mock.call('/dev/loop0')])
mock_umount.reset_mock() mock_umount.reset_mock()
disk_api.teardown_container('/mnt/loop/part') disk_api.teardown_container('/mnt/loop/part')
expected_commands += [ expected_commands += [
('kpartx', '-d', '/dev/loop0'), ('kpartx', '-d', '/dev/loop0')
('losetup', '--detach', '/dev/loop0'),
] ]
mock_loopremove.assert_has_calls([mock.call('/dev/loop0')])
mock_loopremove.reset_mock()
mock_umount.assert_has_calls([mock.call('/dev/mapper/loop0p1')]) mock_umount.assert_has_calls([mock.call('/dev/mapper/loop0p1')])
mock_umount.reset_mock() mock_umount.reset_mock()
@ -264,25 +270,22 @@ class TestVirtDisk(test.NoDBTestCase):
self.assertEqual(self.executes, expected_commands) self.assertEqual(self.executes, expected_commands)
def test_lxc_teardown_container_with_namespace_cleaned(self): @mock.patch('os.path.exists', return_value=True)
@mock.patch('nova.virt.disk.api._DiskImage._device_for_path',
return_value=None)
@mock.patch('nova.privsep.fs.loopremove')
def test_lxc_teardown_container_with_namespace_cleaned(
self, mock_loopremove, mock_device_for_path, mock_exists):
def proc_mounts(mount_point):
return None
self.stub_out('os.path.exists', lambda _: True)
self.stub_out('nova.virt.disk.api._DiskImage._device_for_path',
proc_mounts)
expected_commands = [] expected_commands = []
disk_api.teardown_container('/mnt/loop/nopart', '/dev/loop0') disk_api.teardown_container('/mnt/loop/nopart', '/dev/loop0')
expected_commands += [ mock_loopremove.assert_has_calls([mock.call('/dev/loop0')])
('losetup', '--detach', '/dev/loop0'), mock_loopremove.reset_mock()
]
disk_api.teardown_container('/mnt/loop/part', '/dev/loop0') disk_api.teardown_container('/mnt/loop/part', '/dev/loop0')
expected_commands += [ mock_loopremove.assert_has_calls([mock.call('/dev/loop0')])
('losetup', '--detach', '/dev/loop0'), mock_loopremove.reset_mock()
]
disk_api.teardown_container('/mnt/nbd/nopart', '/dev/nbd15') disk_api.teardown_container('/mnt/nbd/nopart', '/dev/nbd15')
expected_commands += [ expected_commands += [

View File

@ -447,8 +447,7 @@ def teardown_container(container_dir, container_root_device=None):
if container_root_device: if container_root_device:
if 'loop' in container_root_device: if 'loop' in container_root_device:
LOG.debug("Release loop device %s", container_root_device) LOG.debug("Release loop device %s", container_root_device)
utils.execute('losetup', '--detach', container_root_device, nova.privsep.fs.loopremove(container_root_device)
run_as_root=True, attempts=3)
elif 'nbd' in container_root_device: elif 'nbd' in container_root_device:
LOG.debug('Release nbd device %s', container_root_device) LOG.debug('Release nbd device %s', container_root_device)
utils.execute('qemu-nbd', '-d', container_root_device, utils.execute('qemu-nbd', '-d', container_root_device,

View File

@ -16,7 +16,7 @@
from oslo_log import log as logging from oslo_log import log as logging
from nova.i18n import _ from nova.i18n import _
from nova import utils import nova.privsep.fs
from nova.virt.disk.mount import api from nova.virt.disk.mount import api
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
@ -27,9 +27,7 @@ class LoopMount(api.Mount):
mode = 'loop' mode = 'loop'
def _inner_get_dev(self): def _inner_get_dev(self):
out, err = utils.trycmd('losetup', '--find', '--show', out, err = nova.privsep.fs.loopsetup(self.image.path)
self.image.path,
run_as_root=True)
if err: if err:
self.error = _('Could not attach image to loopback: %s') % err self.error = _('Could not attach image to loopback: %s') % err
LOG.info('Loop mount error: %s', self.error) LOG.info('Loop mount error: %s', self.error)
@ -57,7 +55,6 @@ class LoopMount(api.Mount):
# thus leaking a loop device unless the losetup --detach is retried: # thus leaking a loop device unless the losetup --detach is retried:
# https://lkml.org/lkml/2012/9/28/62 # https://lkml.org/lkml/2012/9/28/62
LOG.debug("Release loop device %s", self.device) LOG.debug("Release loop device %s", self.device)
utils.execute('losetup', '--detach', self.device, run_as_root=True, nova.privsep.fs.loopremove(self.device)
attempts=3)
self.linked = False self.linked = False
self.device = None self.device = None

View File

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