5dc5a55d33
For some reason the leaked descriptor warning message coming from LVM is causing Cinder to fail startup and it appears to be masking out the vg response in vgs calls. We typically don't hit this, but due to the nature of Kolla and I guess going through the different processes via the containers this gets logged every time vgs is called. Eric Harney rightly pointed out that rather than use exception handling and such that we should use the LVM env variable mechanism we already have in place in Cinder. This was added to the LVM local_dev code in the openstack/cinder repo, but was not also added to os-brick. The long term goal is to have all of this handling in os-brick, so this replicates the changes from cinder done in I85612fa49475beea58d30330c8fe8352a2f91123. Change-Id: I9d5aaad0e6213535afc1ce071f12190cc6aa02d0 Signed-off-by: Sean McGinnis <sean.mcginnis@gmail.com>
407 lines
17 KiB
Python
407 lines
17 KiB
Python
# Copyright 2012 OpenStack Foundation
|
|
# 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 mock
|
|
from oslo_concurrency import processutils
|
|
|
|
from os_brick import exception
|
|
from os_brick.local_dev import lvm as brick
|
|
from os_brick.privileged import rootwrap as priv_rootwrap
|
|
from os_brick.tests import base
|
|
|
|
|
|
class BrickLvmTestCase(base.TestCase):
|
|
|
|
def setUp(self):
|
|
super(BrickLvmTestCase, self).setUp()
|
|
if not hasattr(self, 'configuration'):
|
|
self.configuration = mock.Mock()
|
|
self.configuration.lvm_suppress_fd_warnings = False
|
|
self.volume_group_name = 'fake-vg'
|
|
|
|
# Stub processutils.execute for static methods
|
|
self.mock_object(priv_rootwrap, 'execute',
|
|
self.fake_execute)
|
|
self.vg = brick.LVM(
|
|
self.volume_group_name,
|
|
'sudo',
|
|
create_vg=False,
|
|
physical_volumes=None,
|
|
lvm_type='default',
|
|
executor=self.fake_execute,
|
|
suppress_fd_warn=self.configuration.lvm_suppress_fd_warnings)
|
|
|
|
def failed_fake_execute(obj, *cmd, **kwargs):
|
|
return ("\n", "fake-error")
|
|
|
|
def fake_pretend_lvm_version(obj, *cmd, **kwargs):
|
|
return (" LVM version: 2.03.00 (2012-03-06)\n", "")
|
|
|
|
def fake_old_lvm_version(obj, *cmd, **kwargs):
|
|
# Does not support thin prov or snap activation
|
|
return (" LVM version: 2.02.65(2) (2012-03-06)\n", "")
|
|
|
|
def fake_customised_lvm_version(obj, *cmd, **kwargs):
|
|
return (" LVM version: 2.02.100(2)-RHEL6 (2013-09-12)\n", "")
|
|
|
|
def fake_f23_lvm_version(obj, *cmd, **kwargs):
|
|
return (" LVM version: 2.02.132(2) (2015-09-22)\n", "")
|
|
|
|
def fake_execute(obj, *cmd, **kwargs):
|
|
# TODO(eharney): remove this and move to per-test mocked execute calls
|
|
if obj.configuration.lvm_suppress_fd_warnings:
|
|
_lvm_prefix = 'env, LC_ALL=C, LVM_SUPPRESS_FD_WARNINGS=1, '
|
|
else:
|
|
_lvm_prefix = 'env, LC_ALL=C, '
|
|
|
|
cmd_string = ', '.join(cmd)
|
|
data = "\n"
|
|
|
|
if (_lvm_prefix + 'vgs, --noheadings, --unit=g, -o, name' ==
|
|
cmd_string):
|
|
data = " fake-vg\n"
|
|
data += " some-other-vg\n"
|
|
elif (_lvm_prefix + 'vgs, --noheadings, -o, name, fake-vg' ==
|
|
cmd_string):
|
|
data = " fake-vg\n"
|
|
elif _lvm_prefix + 'vgs, --version' in cmd_string:
|
|
data = " LVM version: 2.02.95(2) (2012-03-06)\n"
|
|
elif (_lvm_prefix + 'vgs, --noheadings, -o, uuid, fake-vg' in
|
|
cmd_string):
|
|
data = " kVxztV-dKpG-Rz7E-xtKY-jeju-QsYU-SLG6Z1\n"
|
|
elif _lvm_prefix + 'vgs, --noheadings, --unit=g, ' \
|
|
'-o, name,size,free,lv_count,uuid, ' \
|
|
'--separator, :, --nosuffix' in cmd_string:
|
|
data = (" test-prov-cap-vg-unit:10.00:10.00:0:"
|
|
"mXzbuX-dKpG-Rz7E-xtKY-jeju-QsYU-SLG8Z4\n")
|
|
if 'test-prov-cap-vg-unit' in cmd_string:
|
|
return (data, "")
|
|
data = (" test-prov-cap-vg-no-unit:10.00:10.00:0:"
|
|
"mXzbuX-dKpG-Rz7E-xtKY-jeju-QsYU-SLG8Z4\n")
|
|
if 'test-prov-cap-vg-no-unit' in cmd_string:
|
|
return (data, "")
|
|
data = " fake-vg:10.00:10.00:0:"\
|
|
"kVxztV-dKpG-Rz7E-xtKY-jeju-QsYU-SLG6Z1\n"
|
|
if 'fake-vg' in cmd_string:
|
|
return (data, "")
|
|
data += " fake-vg-2:10.00:10.00:0:"\
|
|
"lWyauW-dKpG-Rz7E-xtKY-jeju-QsYU-SLG7Z2\n"
|
|
data += " fake-vg-3:10.00:10.00:0:"\
|
|
"mXzbuX-dKpG-Rz7E-xtKY-jeju-QsYU-SLG8Z3\n"
|
|
elif (_lvm_prefix + 'lvs, --noheadings, '
|
|
'--unit=g, -o, vg_name,name,size, --nosuffix, '
|
|
'fake-vg/lv-nothere' in cmd_string):
|
|
raise processutils.ProcessExecutionError(
|
|
stderr="One or more specified logical volume(s) not found.")
|
|
elif (_lvm_prefix + 'lvs, --noheadings, '
|
|
'--unit=g, -o, vg_name,name,size, --nosuffix, '
|
|
'fake-vg/lv-newerror' in cmd_string):
|
|
raise processutils.ProcessExecutionError(
|
|
stderr="Failed to find logical volume \"fake-vg/lv-newerror\"")
|
|
elif (_lvm_prefix + 'lvs, --noheadings, '
|
|
'--unit=g, -o, vg_name,name,size' in cmd_string):
|
|
if 'fake-unknown' in cmd_string:
|
|
raise processutils.ProcessExecutionError(
|
|
stderr="One or more volume(s) not found."
|
|
)
|
|
if 'test-prov-cap-vg-unit' in cmd_string:
|
|
data = " fake-vg test-prov-cap-pool-unit 9.50g\n"
|
|
data += " fake-vg fake-volume-1 1.00g\n"
|
|
data += " fake-vg fake-volume-2 2.00g\n"
|
|
elif 'test-prov-cap-vg-no-unit' in cmd_string:
|
|
data = " fake-vg test-prov-cap-pool-no-unit 9.50\n"
|
|
data += " fake-vg fake-volume-1 1.00\n"
|
|
data += " fake-vg fake-volume-2 2.00\n"
|
|
elif 'test-found-lv-name' in cmd_string:
|
|
data = " fake-vg test-found-lv-name 9.50\n"
|
|
else:
|
|
data = " fake-vg fake-1 1.00g\n"
|
|
data += " fake-vg fake-2 1.00g\n"
|
|
elif (_lvm_prefix + 'lvdisplay, --noheading, -C, -o, Attr' in
|
|
cmd_string):
|
|
if 'test-volumes' in cmd_string:
|
|
data = ' wi-a-'
|
|
else:
|
|
data = ' owi-a-'
|
|
elif _lvm_prefix + 'pvs, --noheadings' in cmd_string:
|
|
data = " fake-vg|/dev/sda|10.00|1.00\n"
|
|
data += " fake-vg|/dev/sdb|10.00|1.00\n"
|
|
data += " fake-vg|/dev/sdc|10.00|8.99\n"
|
|
data += " fake-vg-2|/dev/sdd|10.00|9.99\n"
|
|
elif _lvm_prefix + 'lvs, --noheadings, --unit=g' \
|
|
', -o, size,data_percent, --separator, :' in cmd_string:
|
|
if 'test-prov-cap-pool' in cmd_string:
|
|
data = " 9.5:20\n"
|
|
else:
|
|
data = " 9:12\n"
|
|
elif 'lvcreate, -T, -L, ' in cmd_string:
|
|
pass
|
|
elif 'lvcreate, -T, -l, 100%FREE' in cmd_string:
|
|
pass
|
|
elif 'lvcreate, -T, -V, ' in cmd_string:
|
|
pass
|
|
elif 'lvcreate, -n, ' in cmd_string:
|
|
pass
|
|
elif 'lvcreate, --name, ' in cmd_string:
|
|
pass
|
|
elif 'lvextend, -L, ' in cmd_string:
|
|
pass
|
|
else:
|
|
raise AssertionError('unexpected command called: %s' % cmd_string)
|
|
|
|
return (data, "")
|
|
|
|
def test_create_lv_snapshot(self):
|
|
self.assertIsNone(self.vg.create_lv_snapshot('snapshot-1', 'fake-1'))
|
|
|
|
with mock.patch.object(self.vg, 'get_volume', return_value=None):
|
|
try:
|
|
self.vg.create_lv_snapshot('snapshot-1', 'fake-non-existent')
|
|
except exception.VolumeDeviceNotFound as e:
|
|
self.assertEqual('fake-non-existent', e.kwargs['device'])
|
|
else:
|
|
self.fail("Exception not raised")
|
|
|
|
def test_vg_exists(self):
|
|
self.assertTrue(self.vg._vg_exists())
|
|
|
|
def test_get_vg_uuid(self):
|
|
self.assertEqual('kVxztV-dKpG-Rz7E-xtKY-jeju-QsYU-SLG6Z1',
|
|
self.vg._get_vg_uuid()[0])
|
|
|
|
def test_get_all_volumes(self):
|
|
out = self.vg.get_volumes()
|
|
|
|
self.assertEqual('fake-1', out[0]['name'])
|
|
self.assertEqual('1.00g', out[0]['size'])
|
|
self.assertEqual('fake-vg', out[0]['vg'])
|
|
|
|
def test_get_volume(self):
|
|
self.assertEqual('fake-1', self.vg.get_volume('fake-1')['name'])
|
|
|
|
def test_get_volume_none(self):
|
|
self.assertIsNone(self.vg.get_volume('fake-unknown'))
|
|
|
|
def test_get_lv_info_notfound(self):
|
|
# lv-nothere will raise lvm < 2.102.112 exception
|
|
self.assertEqual(
|
|
[],
|
|
self.vg.get_lv_info(
|
|
'sudo', vg_name='fake-vg', lv_name='lv-nothere')
|
|
)
|
|
# lv-newerror will raise lvm > 2.102.112 exception
|
|
self.assertEqual(
|
|
[],
|
|
self.vg.get_lv_info(
|
|
'sudo', vg_name='fake-vg', lv_name='lv-newerror')
|
|
)
|
|
|
|
def test_get_lv_info_found(self):
|
|
lv_info = [{'size': '9.50', 'name': 'test-found-lv-name',
|
|
'vg': 'fake-vg'}]
|
|
self.assertEqual(
|
|
lv_info,
|
|
self.vg.get_lv_info(
|
|
'sudo', vg_name='fake-vg',
|
|
lv_name='test-found-lv-name')
|
|
)
|
|
|
|
def test_get_lv_info_no_lv_name(self):
|
|
lv_info = [{'name': 'fake-1', 'size': '1.00g', 'vg': 'fake-vg'},
|
|
{'name': 'fake-2', 'size': '1.00g', 'vg': 'fake-vg'}]
|
|
self.assertEqual(
|
|
lv_info,
|
|
self.vg.get_lv_info(
|
|
'sudo', vg_name='fake-vg')
|
|
)
|
|
|
|
def test_get_all_physical_volumes(self):
|
|
# Filtered VG version
|
|
pvs = self.vg.get_all_physical_volumes('sudo', 'fake-vg')
|
|
self.assertEqual(3, len(pvs))
|
|
|
|
# Non-Filtered, all VG's
|
|
pvs = self.vg.get_all_physical_volumes('sudo')
|
|
self.assertEqual(4, len(pvs))
|
|
|
|
def test_get_physical_volumes(self):
|
|
pvs = self.vg.get_physical_volumes()
|
|
self.assertEqual(3, len(pvs))
|
|
|
|
def test_get_volume_groups(self):
|
|
self.assertEqual(3, len(self.vg.get_all_volume_groups('sudo')))
|
|
self.assertEqual(1,
|
|
len(self.vg.get_all_volume_groups('sudo', 'fake-vg')))
|
|
|
|
def test_thin_support(self):
|
|
# lvm.supports_thin() is a static method and doesn't
|
|
# use the self._executor fake we pass in on init
|
|
# so we need to stub processutils.execute appropriately
|
|
|
|
with mock.patch.object(priv_rootwrap, 'execute',
|
|
side_effect=self.fake_execute):
|
|
self.assertTrue(self.vg.supports_thin_provisioning('sudo'))
|
|
|
|
with mock.patch.object(priv_rootwrap, 'execute',
|
|
side_effect=self.fake_pretend_lvm_version):
|
|
self.assertTrue(self.vg.supports_thin_provisioning('sudo'))
|
|
|
|
with mock.patch.object(priv_rootwrap, 'execute',
|
|
side_effect=self.fake_old_lvm_version):
|
|
self.assertFalse(self.vg.supports_thin_provisioning('sudo'))
|
|
|
|
with mock.patch.object(priv_rootwrap, 'execute',
|
|
side_effect=self.fake_customised_lvm_version):
|
|
self.assertTrue(self.vg.supports_thin_provisioning('sudo'))
|
|
|
|
def test_snapshot_lv_activate_support(self):
|
|
self.vg._supports_snapshot_lv_activation = None
|
|
with mock.patch.object(priv_rootwrap, 'execute',
|
|
side_effect=self.fake_execute):
|
|
self.assertTrue(self.vg.supports_snapshot_lv_activation)
|
|
|
|
self.vg._supports_snapshot_lv_activation = None
|
|
with mock.patch.object(priv_rootwrap, 'execute',
|
|
side_effect=self.fake_old_lvm_version):
|
|
self.assertFalse(self.vg.supports_snapshot_lv_activation)
|
|
|
|
self.vg._supports_snapshot_lv_activation = None
|
|
|
|
def test_lvchange_ignskipact_support_yes(self):
|
|
"""Tests if lvchange -K is available via a lvm2 version check."""
|
|
|
|
self.vg._supports_lvchange_ignoreskipactivation = None
|
|
with mock.patch.object(priv_rootwrap, 'execute',
|
|
side_effect=self.fake_pretend_lvm_version):
|
|
self.assertTrue(self.vg.supports_lvchange_ignoreskipactivation)
|
|
|
|
self.vg._supports_lvchange_ignoreskipactivation = None
|
|
with mock.patch.object(priv_rootwrap, 'execute',
|
|
side_effect=self.fake_old_lvm_version):
|
|
self.assertFalse(self.vg.supports_lvchange_ignoreskipactivation)
|
|
|
|
self.vg._supports_lvchange_ignoreskipactivation = None
|
|
|
|
def test_thin_pool_creation_manual(self):
|
|
# The size of fake-vg volume group is 10g, so the calculated thin
|
|
# pool size should be 9.5g (95% of 10g).
|
|
self.vg.create_thin_pool()
|
|
|
|
def test_thin_pool_provisioned_capacity(self):
|
|
self.vg.vg_thin_pool = "test-prov-cap-pool-unit"
|
|
self.vg.vg_name = 'test-prov-cap-vg-unit'
|
|
self.assertIsNone(self.vg.create_thin_pool(name=self.vg.vg_thin_pool))
|
|
self.assertEqual(9.50, self.vg.vg_thin_pool_size)
|
|
self.assertEqual(7.6, self.vg.vg_thin_pool_free_space)
|
|
self.assertEqual(3.0, self.vg.vg_provisioned_capacity)
|
|
|
|
self.vg.vg_thin_pool = "test-prov-cap-pool-no-unit"
|
|
self.vg.vg_name = 'test-prov-cap-vg-no-unit'
|
|
self.assertIsNone(self.vg.create_thin_pool(name=self.vg.vg_thin_pool))
|
|
self.assertEqual(9.50, self.vg.vg_thin_pool_size)
|
|
self.assertEqual(7.6, self.vg.vg_thin_pool_free_space)
|
|
self.assertEqual(3.0, self.vg.vg_provisioned_capacity)
|
|
|
|
def test_thin_pool_free_space(self):
|
|
# The size of fake-vg-pool is 9g and the allocated data sums up to
|
|
# 12% so the calculated free space should be 7.92
|
|
self.assertEqual(float("7.92"),
|
|
self.vg._get_thin_pool_free_space("fake-vg",
|
|
"fake-vg-pool"))
|
|
|
|
def test_volume_create_after_thin_creation(self):
|
|
"""Test self.vg.vg_thin_pool is set to pool_name
|
|
|
|
See bug #1220286 for more info.
|
|
"""
|
|
|
|
vg_name = "vg-name"
|
|
pool_name = vg_name + "-pool"
|
|
pool_path = "%s/%s" % (vg_name, pool_name)
|
|
|
|
def executor(obj, *cmd, **kwargs):
|
|
self.assertEqual(pool_path, cmd[-1])
|
|
|
|
self.vg._executor = executor
|
|
self.vg.create_thin_pool(pool_name)
|
|
self.vg.create_volume("test", "1G", lv_type='thin')
|
|
|
|
self.assertEqual(pool_name, self.vg.vg_thin_pool)
|
|
|
|
def test_lv_has_snapshot(self):
|
|
self.assertTrue(self.vg.lv_has_snapshot('fake-vg'))
|
|
self.assertFalse(self.vg.lv_has_snapshot('test-volumes'))
|
|
|
|
def test_activate_lv(self):
|
|
self.vg._supports_lvchange_ignoreskipactivation = True
|
|
|
|
with mock.patch.object(self.vg, '_execute') as mock_exec:
|
|
self.vg.activate_lv('my-lv')
|
|
expected = [mock.call('lvchange', '-a', 'y', '--yes', '-K',
|
|
'fake-vg/my-lv', root_helper='sudo',
|
|
run_as_root=True)]
|
|
self.assertEqual(expected, mock_exec.call_args_list)
|
|
|
|
def test_get_mirrored_available_capacity(self):
|
|
self.assertEqual(2.0, self.vg.vg_mirror_free_space(1))
|
|
|
|
def test_lv_extend(self):
|
|
self.vg.deactivate_lv = mock.MagicMock()
|
|
|
|
# Extend lv with snapshot and make sure deactivate called
|
|
self.vg.create_volume("test", "1G")
|
|
self.vg.extend_volume("test", "2G")
|
|
self.vg.deactivate_lv.assert_called_once_with('test')
|
|
self.vg.deactivate_lv.reset_mock()
|
|
|
|
# Extend lv without snapshot so deactivate should not be called
|
|
self.vg.create_volume("test", "1G")
|
|
self.vg.vg_name = "test-volumes"
|
|
self.vg.extend_volume("test", "2G")
|
|
self.assertFalse(self.vg.deactivate_lv.called)
|
|
|
|
def test_lv_deactivate(self):
|
|
with mock.patch.object(self.vg, '_execute'):
|
|
is_active_mock = mock.Mock()
|
|
is_active_mock.return_value = False
|
|
self.vg._lv_is_active = is_active_mock
|
|
self.vg.create_volume('test', '1G')
|
|
self.vg.deactivate_lv('test')
|
|
|
|
@mock.patch('time.sleep')
|
|
def test_lv_deactivate_timeout(self, mock_sleep):
|
|
with mock.patch.object(self.vg, '_execute'):
|
|
is_active_mock = mock.Mock()
|
|
is_active_mock.return_value = True
|
|
self.vg._lv_is_active = is_active_mock
|
|
self.vg.create_volume('test', '1G')
|
|
self.assertRaises(exception.VolumeNotDeactivated,
|
|
self.vg.deactivate_lv, 'test')
|
|
|
|
def test_lv_is_active(self):
|
|
self.vg.create_volume('test', '1G')
|
|
with mock.patch.object(self.vg, '_execute',
|
|
return_value=['owi-a---', '']):
|
|
self.assertTrue(self.vg._lv_is_active('test'))
|
|
with mock.patch.object(self.vg, '_execute',
|
|
return_value=['owi-----', '']):
|
|
self.assertFalse(self.vg._lv_is_active('test'))
|
|
|
|
|
|
class BrickLvmTestCaseIgnoreFDWarnings(BrickLvmTestCase):
|
|
def setUp(self):
|
|
self.configuration = mock.Mock()
|
|
self.configuration.lvm_suppress_fd_warnings = True
|
|
super(BrickLvmTestCaseIgnoreFDWarnings, self).setUp()
|