nova/nova/tests/virt/disk/test_nbd.py

334 lines
12 KiB
Python

# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2012 Michael Still and Canonical Inc
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import os
import tempfile
import time
import eventlet
import fixtures
from nova import test
from nova.virt.disk.mount import nbd
ORIG_EXISTS = os.path.exists
ORIG_LISTDIR = os.listdir
def _fake_exists_no_users(path):
if path.startswith('/sys/block/nbd'):
if path.endswith('pid'):
return False
return True
return ORIG_EXISTS(path)
def _fake_listdir_nbd_devices(path):
if path.startswith('/sys/block'):
return ['nbd0', 'nbd1']
return ORIG_LISTDIR(path)
def _fake_exists_all_used(path):
if path.startswith('/sys/block/nbd'):
return True
return ORIG_EXISTS(path)
def _fake_detect_nbd_devices_none(self):
return []
def _fake_detect_nbd_devices(self):
return ['nbd0', 'nbd1']
def _fake_noop(*args, **kwargs):
return
class NbdTestCase(test.NoDBTestCase):
def setUp(self):
super(NbdTestCase, self).setUp()
self.stubs.Set(nbd.NbdMount, '_detect_nbd_devices',
_fake_detect_nbd_devices)
self.useFixture(fixtures.MonkeyPatch('os.listdir',
_fake_listdir_nbd_devices))
def test_nbd_no_devices(self):
tempdir = self.useFixture(fixtures.TempDir()).path
self.stubs.Set(nbd.NbdMount, '_detect_nbd_devices',
_fake_detect_nbd_devices_none)
n = nbd.NbdMount(None, tempdir)
self.assertEquals(None, n._allocate_nbd())
def test_nbd_no_free_devices(self):
tempdir = self.useFixture(fixtures.TempDir()).path
n = nbd.NbdMount(None, tempdir)
self.useFixture(fixtures.MonkeyPatch('os.path.exists',
_fake_exists_all_used))
self.assertEquals(None, n._allocate_nbd())
def test_nbd_not_loaded(self):
tempdir = self.useFixture(fixtures.TempDir()).path
n = nbd.NbdMount(None, tempdir)
# Fake out os.path.exists
def fake_exists(path):
if path.startswith('/sys/block/nbd'):
return False
return ORIG_EXISTS(path)
self.useFixture(fixtures.MonkeyPatch('os.path.exists', fake_exists))
# This should fail, as we don't have the module "loaded"
# TODO(mikal): work out how to force english as the gettext language
# so that the error check always passes
self.assertEquals(None, n._allocate_nbd())
self.assertEquals('nbd unavailable: module not loaded', n.error)
def test_nbd_allocation(self):
tempdir = self.useFixture(fixtures.TempDir()).path
n = nbd.NbdMount(None, tempdir)
self.useFixture(fixtures.MonkeyPatch('os.path.exists',
_fake_exists_no_users))
self.useFixture(fixtures.MonkeyPatch('random.shuffle', _fake_noop))
# Allocate a nbd device
self.assertEquals('/dev/nbd0', n._allocate_nbd())
def test_nbd_allocation_one_in_use(self):
tempdir = self.useFixture(fixtures.TempDir()).path
n = nbd.NbdMount(None, tempdir)
self.useFixture(fixtures.MonkeyPatch('random.shuffle', _fake_noop))
# Fake out os.path.exists
def fake_exists(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))
# Allocate a nbd device, should not be the in use one
# TODO(mikal): Note that there is a leak here, as the in use nbd device
# is removed from the list, but not returned so it will never be
# re-added. I will fix this in a later patch.
self.assertEquals('/dev/nbd1', n._allocate_nbd())
def test_inner_get_dev_no_devices(self):
tempdir = self.useFixture(fixtures.TempDir()).path
self.stubs.Set(nbd.NbdMount, '_detect_nbd_devices',
_fake_detect_nbd_devices_none)
n = nbd.NbdMount(None, tempdir)
self.assertFalse(n._inner_get_dev())
def test_inner_get_dev_qemu_fails(self):
tempdir = self.useFixture(fixtures.TempDir()).path
n = nbd.NbdMount(None, 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):
tempdir = self.useFixture(fixtures.TempDir()).path
n = nbd.NbdMount(None, 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):
tempdir = self.useFixture(fixtures.TempDir()).path
n = nbd.NbdMount(None, 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())
self.assertTrue(n.linked)
self.assertEquals('', n.error)
self.assertEquals('/dev/nbd0', n.device)
# Free
n.unget_dev()
self.assertFalse(n.linked)
self.assertEquals('', n.error)
self.assertEquals(None, n.device)
def test_unget_dev_simple(self):
# This test is just checking we don't get an exception when we unget
# something we don't have
tempdir = self.useFixture(fixtures.TempDir()).path
n = nbd.NbdMount(None, tempdir)
self.useFixture(fixtures.MonkeyPatch('nova.utils.execute', _fake_noop))
n.unget_dev()
def test_get_dev(self):
tempdir = self.useFixture(fixtures.TempDir()).path
n = nbd.NbdMount(None, 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())
self.assertTrue(n.linked)
self.assertEquals('', n.error)
self.assertEquals('/dev/nbd0', n.device)
# Free
n.unget_dev()
self.assertFalse(n.linked)
self.assertEquals('', n.error)
self.assertEquals(None, n.device)
def test_get_dev_timeout(self):
# Always fail to get a device
def fake_get_dev_fails(self):
return False
self.stubs.Set(nbd.NbdMount, '_inner_get_dev', fake_get_dev_fails)
tempdir = self.useFixture(fixtures.TempDir()).path
n = nbd.NbdMount(None, 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))
# No error logged, device consumed
self.assertFalse(n.get_dev())
def test_do_mount_need_to_specify_fs_type(self):
# NOTE(mikal): Bug 1094373 saw a regression where we failed to
# communicate a failed mount properly.
def fake_trycmd(*args, **kwargs):
return '', 'broken'
self.useFixture(fixtures.MonkeyPatch('nova.utils.trycmd', fake_trycmd))
imgfile = tempfile.NamedTemporaryFile()
self.addCleanup(imgfile.close)
tempdir = self.useFixture(fixtures.TempDir()).path
mount = nbd.NbdMount(imgfile.name, tempdir)
def fake_returns_true(*args, **kwargs):
return True
mount.get_dev = fake_returns_true
mount.map_dev = fake_returns_true
self.assertFalse(mount.do_mount())
def test_device_creation_race(self):
# Make sure that even if two threads create instances at the same time
# they cannot choose the same nbd number (see bug 1207422)
tempdir = self.useFixture(fixtures.TempDir()).path
free_devices = _fake_detect_nbd_devices(None)[:]
chosen_devices = []
def fake_find_unused(self):
return os.path.join('/dev', free_devices[-1])
def delay_and_remove_device(*args, **kwargs):
# Ensure that context switch happens before the device is marked
# as used. This will cause a failure without nbd-allocation-lock
# in place.
time.sleep(0.1)
# We always choose the top device in find_unused - remove it now.
free_devices.pop()
return '', ''
def pid_exists(pidfile):
return pidfile not in [os.path.join('/sys/block', dev, 'pid')
for dev in free_devices]
self.stubs.Set(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))
def get_a_device():
n = nbd.NbdMount(None, tempdir)
n.get_dev()
chosen_devices.append(n.device)
thread1 = eventlet.spawn(get_a_device)
thread2 = eventlet.spawn(get_a_device)
thread1.wait()
thread2.wait()
self.assertEqual(2, len(chosen_devices))
self.assertNotEqual(chosen_devices[0], chosen_devices[1])