1# Copyright 2012 OpenStack Foundation 2# All Rights Reserved. 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); you may 5# not use this file except in compliance with the License. You may obtain 6# a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13# License for the specific language governing permissions and limitations 14# under the License. 15import ddt 16import mock 17from oslo_concurrency import processutils 18 19from cinder.brick.local_dev import lvm as brick 20from cinder import exception 21from cinder import test 22from cinder.volume import configuration as conf 23 24 25@ddt.ddt 26class BrickLvmTestCase(test.TestCase): 27 def setUp(self): 28 if not hasattr(self, 'configuration'): 29 self.configuration = mock.Mock(conf.Configuration) 30 self.configuration.lvm_suppress_fd_warnings = False 31 self.configuration.volume_group_name = 'fake-vg' 32 super(BrickLvmTestCase, self).setUp() 33 34 self.mock_object(processutils, 'execute', self.fake_execute) 35 self.vg = brick.LVM( 36 self.configuration.volume_group_name, 37 'sudo', 38 False, None, 39 'default', 40 self.fake_execute, 41 suppress_fd_warn=self.configuration.lvm_suppress_fd_warnings) 42 43 def failed_fake_execute(obj, *cmd, **kwargs): 44 return ("\n", "fake-error") 45 46 def fake_pretend_lvm_version(obj, *cmd, **kwargs): 47 return (" LVM version: 2.03.00 (2012-03-06)\n", "") 48 49 def fake_old_lvm_version(obj, *cmd, **kwargs): 50 # Does not support thin prov or snap activation 51 return (" LVM version: 2.02.65(2) (2012-03-06)\n", "") 52 53 def fake_customised_lvm_version(obj, *cmd, **kwargs): 54 return (" LVM version: 2.02.100(2)-RHEL6 (2013-09-12)\n", "") 55 56 def fake_execute(obj, *cmd, **kwargs): # noqa 57 if obj.configuration.lvm_suppress_fd_warnings: 58 _lvm_prefix = 'env, LC_ALL=C, LVM_SUPPRESS_FD_WARNINGS=1, ' 59 else: 60 _lvm_prefix = 'env, LC_ALL=C, ' 61 62 cmd_string = ', '.join(cmd) 63 data = "\n" 64 if (_lvm_prefix + 'vgs, --noheadings, --unit=g, -o, name' == 65 cmd_string): 66 data = " fake-vg\n" 67 data += " some-other-vg\n" 68 elif (_lvm_prefix + 'vgs, --noheadings, -o, name, fake-vg' == 69 cmd_string): 70 data = " fake-vg\n" 71 elif _lvm_prefix + 'vgs, --version' in cmd_string: 72 data = " LVM version: 2.02.103(2) (2012-03-06)\n" 73 elif(_lvm_prefix + 'vgs, --noheadings, -o, uuid, fake-vg' in 74 cmd_string): 75 data = " kVxztV-dKpG-Rz7E-xtKY-jeju-QsYU-SLG6Z1\n" 76 elif(_lvm_prefix + 'vgs, --noheadings, --unit=g, ' 77 '-o, name,size,free,lv_count,uuid, ' 78 '--separator, :, --nosuffix' in cmd_string): 79 data = (" test-prov-cap-vg-unit:10.00:10.00:0:" 80 "mXzbuX-dKpG-Rz7E-xtKY-jeju-QsYU-SLG8Z4\n") 81 if 'test-prov-cap-vg-unit' in cmd_string: 82 return (data, "") 83 data = (" test-prov-cap-vg-no-unit:10.00:10.00:0:" 84 "mXzbuX-dKpG-Rz7E-xtKY-jeju-QsYU-SLG8Z4\n") 85 if 'test-prov-cap-vg-no-unit' in cmd_string: 86 return (data, "") 87 data = " fake-vg:10.00:10.00:0:"\ 88 "kVxztV-dKpG-Rz7E-xtKY-jeju-QsYU-SLG6Z1\n" 89 if 'fake-vg' in cmd_string: 90 return (data, "") 91 data += " fake-vg-2:10.00:10.00:0:"\ 92 "lWyauW-dKpG-Rz7E-xtKY-jeju-QsYU-SLG7Z2\n" 93 data += " fake-vg-3:10.00:10.00:0:"\ 94 "mXzbuX-dKpG-Rz7E-xtKY-jeju-QsYU-SLG8Z3\n" 95 elif (_lvm_prefix + 'lvs, --noheadings, ' 96 '--unit=g, -o, vg_name,name,size, --nosuffix, ' 97 'fake-vg/lv-nothere' in cmd_string): 98 raise processutils.ProcessExecutionError( 99 stderr="One or more specified logical volume(s) not found.") 100 elif (_lvm_prefix + 'lvs, --noheadings, ' 101 '--unit=g, -o, vg_name,name,size, --nosuffix, ' 102 'fake-vg/lv-newerror' in cmd_string): 103 raise processutils.ProcessExecutionError( 104 stderr="Failed to find logical volume \"fake-vg/lv-newerror\"") 105 elif (_lvm_prefix + 'lvs, --noheadings, ' 106 '--unit=g, -o, vg_name,name,size' in cmd_string): 107 if 'fake-unknown' in cmd_string: 108 raise processutils.ProcessExecutionError( 109 stderr="One or more volume(s) not found." 110 ) 111 if 'test-prov-cap-vg-unit' in cmd_string: 112 data = " fake-vg test-prov-cap-pool-unit 9.50g\n" 113 data += " fake-vg fake-volume-1 1.00g\n" 114 data += " fake-vg fake-volume-2 2.00g\n" 115 elif 'test-prov-cap-vg-no-unit' in cmd_string: 116 data = " fake-vg test-prov-cap-pool-no-unit 9.50\n" 117 data += " fake-vg fake-volume-1 1.00\n" 118 data += " fake-vg fake-volume-2 2.00\n" 119 elif 'test-found-lv-name' in cmd_string: 120 data = " fake-vg test-found-lv-name 9.50\n" 121 else: 122 data = " fake-vg fake-1 1.00g\n" 123 data += " fake-vg fake-2 1.00g\n" 124 elif (_lvm_prefix + 'lvdisplay, --noheading, -C, -o, Attr' in 125 cmd_string): 126 if 'test-volumes' in cmd_string: 127 data = ' wi-a-' 128 elif 'snapshot' in cmd_string: 129 data = ' swi-a-s--' 130 elif 'open' in cmd_string: 131 data = ' -wi-ao---' 132 else: 133 data = ' owi-a-' 134 elif (_lvm_prefix + 'lvdisplay, --noheading, -C, -o, Origin' in 135 cmd_string): 136 if 'snapshot' in cmd_string: 137 data = ' fake-volume-1' 138 else: 139 data = ' ' 140 elif _lvm_prefix + 'pvs, --noheadings' in cmd_string: 141 data = " fake-vg|/dev/sda|10.00|1.00\n" 142 data += " fake-vg|/dev/sdb|10.00|1.00\n" 143 data += " fake-vg|/dev/sdc|10.00|8.99\n" 144 data += " fake-vg-2|/dev/sdd|10.00|9.99\n" 145 if '--ignoreskippedcluster' not in cmd_string: 146 raise processutils.ProcessExecutionError( 147 stderr="Skipping clustered volume group", 148 stdout=data, 149 exit_code=5 150 ) 151 elif _lvm_prefix + 'lvs, --noheadings, --unit=g' \ 152 ', -o, size,data_percent, --separator, :' in cmd_string: 153 if 'test-prov-cap-pool' in cmd_string: 154 data = " 9.5:20\n" 155 else: 156 data = " 9:12\n" 157 elif 'lvcreate, -T, -L, ' in cmd_string: 158 pass 159 elif 'lvcreate, -T, -V, ' in cmd_string: 160 pass 161 elif 'lvcreate, -n, ' in cmd_string: 162 pass 163 elif 'lvcreate, --name, ' in cmd_string: 164 pass 165 elif 'lvextend, -L, ' in cmd_string: 166 pass 167 else: 168 raise AssertionError('unexpected command called: %s' % cmd_string) 169 170 return (data, "") 171 172 def test_create_lv_snapshot(self): 173 self.assertIsNone(self.vg.create_lv_snapshot('snapshot-1', 'fake-1')) 174 175 with mock.patch.object(self.vg, 'get_volume', return_value=None): 176 try: 177 self.vg.create_lv_snapshot('snapshot-1', 'fake-non-existent') 178 except exception.VolumeDeviceNotFound as e: 179 self.assertEqual('fake-non-existent', e.kwargs['device']) 180 else: 181 self.fail("Exception not raised") 182 183 def test_vg_exists(self): 184 self.assertTrue(self.vg._vg_exists()) 185 186 def test_get_all_volumes(self): 187 out = self.vg.get_volumes() 188 189 self.assertEqual('fake-1', out[0]['name']) 190 self.assertEqual('1.00g', out[0]['size']) 191 self.assertEqual('fake-vg', out[0]['vg']) 192 193 def test_get_volume(self): 194 self.assertEqual('fake-1', self.vg.get_volume('fake-1')['name']) 195 196 def test_get_volume_none(self): 197 self.assertIsNone(self.vg.get_volume('fake-unknown')) 198 199 def test_get_lv_info_notfound(self): 200 # lv-nothere will raise lvm < 2.102.112 exception 201 self.assertEqual( 202 [], 203 self.vg.get_lv_info( 204 'sudo', vg_name='fake-vg', lv_name='lv-nothere') 205 ) 206 # lv-newerror will raise lvm > 2.102.112 exception 207 self.assertEqual( 208 [], 209 self.vg.get_lv_info( 210 'sudo', vg_name='fake-vg', lv_name='lv-newerror') 211 ) 212 213 def test_get_lv_info_found(self): 214 lv_info = [{'size': '9.50', 'name': 'test-found-lv-name', 215 'vg': 'fake-vg'}] 216 self.assertEqual( 217 lv_info, 218 self.vg.get_lv_info( 219 'sudo', vg_name='fake-vg', 220 lv_name='test-found-lv-name') 221 ) 222 223 def test_get_lv_info_no_lv_name(self): 224 lv_info = [{'name': 'fake-1', 'size': '1.00g', 'vg': 'fake-vg'}, 225 {'name': 'fake-2', 'size': '1.00g', 'vg': 'fake-vg'}] 226 self.assertEqual( 227 lv_info, 228 self.vg.get_lv_info( 229 'sudo', vg_name='fake-vg') 230 ) 231 232 def test_get_all_physical_volumes(self): 233 # Filtered VG version 234 pvs = self.vg.get_all_physical_volumes('sudo', 'fake-vg') 235 self.assertEqual(3, len(pvs)) 236 237 # Non-Filtered, all VG's 238 pvs = self.vg.get_all_physical_volumes('sudo') 239 self.assertEqual(4, len(pvs)) 240 241 def test_get_volume_groups(self): 242 self.assertEqual(3, len(self.vg.get_all_volume_groups('sudo'))) 243 self.assertEqual(1, 244 len(self.vg.get_all_volume_groups('sudo', 'fake-vg'))) 245 246 def test_thin_support(self): 247 # lvm.supports_thin() is a static method and doesn't 248 # use the self._executor fake we pass in on init 249 # so we need to stub processutils.execute appropriately 250 251 self.assertTrue(self.vg.supports_thin_provisioning('sudo')) 252 253 with mock.patch.object(processutils, 'execute', 254 self.fake_pretend_lvm_version): 255 self.assertTrue(self.vg.supports_thin_provisioning('sudo')) 256 257 with mock.patch.object(processutils, 'execute', 258 self.fake_old_lvm_version): 259 self.assertFalse(self.vg.supports_thin_provisioning('sudo')) 260 261 with mock.patch.object(processutils, 'execute', 262 self.fake_customised_lvm_version): 263 self.assertTrue(self.vg.supports_thin_provisioning('sudo')) 264 265 def test_snapshot_lv_activate_support(self): 266 self.vg._supports_snapshot_lv_activation = None 267 self.assertTrue(self.vg.supports_snapshot_lv_activation) 268 269 self.vg._supports_snapshot_lv_activation = None 270 with mock.patch.object(processutils, 'execute', 271 self.fake_old_lvm_version): 272 self.assertFalse(self.vg.supports_snapshot_lv_activation) 273 274 self.vg._supports_snapshot_lv_activation = None 275 276 def test_lvchange_ignskipact_support_yes(self): 277 """Tests if lvchange -K is available via a lvm2 version check.""" 278 279 self.vg._supports_lvchange_ignoreskipactivation = None 280 with mock.patch.object(processutils, 'execute', 281 self.fake_pretend_lvm_version): 282 self.assertTrue(self.vg.supports_lvchange_ignoreskipactivation) 283 284 self.vg._supports_lvchange_ignoreskipactivation = None 285 with mock.patch.object(processutils, 'execute', 286 self.fake_old_lvm_version): 287 self.assertFalse(self.vg.supports_lvchange_ignoreskipactivation) 288 289 self.vg._supports_lvchange_ignoreskipactivation = None 290 291 def test_pvs_ignoreskippedcluster_support(self): 292 """Tests if lvm support ignoreskippedcluster option.""" 293 294 brick.LVM._supports_pvs_ignoreskippedcluster = None 295 with mock.patch.object(processutils, 'execute', 296 self.fake_pretend_lvm_version): 297 self.assertTrue(brick.LVM.supports_pvs_ignoreskippedcluster( 298 'sudo')) 299 300 brick.LVM._supports_pvs_ignoreskippedcluster = None 301 with mock.patch.object(processutils, 'execute', 302 self.fake_old_lvm_version): 303 self.assertFalse(brick.LVM.supports_pvs_ignoreskippedcluster( 304 'sudo')) 305 306 brick.LVM._supports_pvs_ignoreskippedcluster = None 307 308 def test_thin_pool_creation(self): 309 310 # The size of fake-vg volume group is 10g, so the calculated thin 311 # pool size should be 9.5g (95% of 10g). 312 self.assertEqual("9.5g", self.vg.create_thin_pool()) 313 314 # Passing a size parameter should result in a thin pool of that exact 315 # size. 316 for size in ("1g", "1.2g", "1.75g"): 317 self.assertEqual(size, self.vg.create_thin_pool(size_str=size)) 318 319 def test_thin_pool_provisioned_capacity(self): 320 self.vg.vg_thin_pool = "test-prov-cap-pool-unit" 321 self.vg.vg_name = 'test-prov-cap-vg-unit' 322 self.assertEqual( 323 "9.5g", 324 self.vg.create_thin_pool(name=self.vg.vg_thin_pool)) 325 self.assertEqual(9.50, self.vg.vg_thin_pool_size) 326 self.assertEqual(7.6, self.vg.vg_thin_pool_free_space) 327 self.assertEqual(3.0, self.vg.vg_provisioned_capacity) 328 329 self.vg.vg_thin_pool = "test-prov-cap-pool-no-unit" 330 self.vg.vg_name = 'test-prov-cap-vg-no-unit' 331 self.assertEqual( 332 "9.5g", 333 self.vg.create_thin_pool(name=self.vg.vg_thin_pool)) 334 self.assertEqual(9.50, self.vg.vg_thin_pool_size) 335 self.assertEqual(7.6, self.vg.vg_thin_pool_free_space) 336 self.assertEqual(3.0, self.vg.vg_provisioned_capacity) 337 338 def test_thin_pool_free_space(self): 339 # The size of fake-vg-pool is 9g and the allocated data sums up to 340 # 12% so the calculated free space should be 7.92 341 self.assertEqual(float("7.92"), 342 self.vg._get_thin_pool_free_space("fake-vg", 343 "fake-vg-pool")) 344 345 def test_volume_create_after_thin_creation(self): 346 """Test self.vg.vg_thin_pool is set to pool_name 347 348 See bug #1220286 for more info. 349 """ 350 vg_name = "vg-name" 351 pool_name = vg_name + "-pool" 352 self.vg.create_thin_pool(pool_name, "1G") 353 354 with mock.patch.object(self.vg, '_execute'): 355 self.vg.create_volume("test", "1G", lv_type='thin') 356 if self.configuration.lvm_suppress_fd_warnings is False: 357 self.vg._execute.assert_called_once_with( 358 'env', 'LC_ALL=C', 'lvcreate', '-T', '-V', 359 '1G', '-n', 'test', 'fake-vg/vg-name-pool', 360 root_helper='sudo', run_as_root=True) 361 else: 362 self.vg._execute.assert_called_once_with( 363 'env', 'LC_ALL=C', 'LVM_SUPPRESS_FD_WARNINGS=1', 364 'lvcreate', '-T', '-V', '1G', '-n', 'test', 365 'fake-vg/vg-name-pool', root_helper='sudo', 366 run_as_root=True) 367 self.assertEqual(pool_name, self.vg.vg_thin_pool) 368 369 def test_volume_create_when_executor_failed(self): 370 def fail(*args, **kwargs): 371 raise processutils.ProcessExecutionError() 372 self.vg._execute = fail 373 374 with mock.patch.object(self.vg, 'get_all_volume_groups') as m_gavg: 375 self.assertRaises( 376 processutils.ProcessExecutionError, 377 self.vg.create_volume, "test", "1G" 378 ) 379 m_gavg.assert_called() 380 381 def test_lv_has_snapshot(self): 382 self.assertTrue(self.vg.lv_has_snapshot('fake-vg')) 383 self.assertFalse(self.vg.lv_has_snapshot('test-volumes')) 384 385 def test_lv_is_snapshot(self): 386 self.assertTrue(self.vg.lv_is_snapshot('fake-snapshot')) 387 self.assertFalse(self.vg.lv_is_snapshot('test-volumes')) 388 389 def test_lv_is_open(self): 390 self.assertTrue(self.vg.lv_is_open('fake-open')) 391 self.assertFalse(self.vg.lv_is_open('fake-snapshot')) 392 393 def test_lv_get_origin(self): 394 self.assertEqual('fake-volume-1', 395 self.vg.lv_get_origin('fake-snapshot')) 396 self.assertIsNone(self.vg.lv_get_origin('test-volumes')) 397 398 def test_activate_lv(self): 399 with mock.patch.object(self.vg, '_execute'): 400 self.vg._supports_lvchange_ignoreskipactivation = True 401 402 self.vg._execute('lvchange', '-a', 'y', '--yes', '-K', 403 'fake-vg/my-lv', 404 root_helper='sudo', run_as_root=True) 405 406 self.vg.activate_lv('my-lv') 407 408 def test_get_mirrored_available_capacity(self): 409 self.assertEqual(2.0, self.vg.vg_mirror_free_space(1)) 410 411 @ddt.data(True, False) 412 def test_lv_extend(self, has_snapshot): 413 with mock.patch.object(self.vg, '_execute'): 414 with mock.patch.object(self.vg, 'lv_has_snapshot'): 415 self.vg.deactivate_lv = mock.MagicMock() 416 self.vg.activate_lv = mock.MagicMock() 417 418 self.vg.lv_has_snapshot.return_value = has_snapshot 419 self.vg.extend_volume("test", "2G") 420 421 self.vg.lv_has_snapshot.assert_called_once_with("test") 422 if has_snapshot: 423 self.vg.activate_lv.assert_called_once_with("test") 424 self.vg.deactivate_lv.assert_called_once_with("test") 425 else: 426 self.vg.activate_lv.assert_not_called() 427 self.vg.deactivate_lv.assert_not_called() 428 429 def test_lv_deactivate(self): 430 with mock.patch.object(self.vg, '_execute'): 431 is_active_mock = mock.Mock() 432 is_active_mock.return_value = False 433 self.vg._lv_is_active = is_active_mock 434 self.vg.create_volume('test', '1G') 435 self.vg.deactivate_lv('test') 436 437 @mock.patch('time.sleep') 438 def test_lv_deactivate_timeout(self, _mock_sleep): 439 with mock.patch.object(self.vg, '_execute'): 440 is_active_mock = mock.Mock() 441 is_active_mock.return_value = True 442 self.vg._lv_is_active = is_active_mock 443 self.vg.create_volume('test', '1G') 444 self.assertRaises(exception.VolumeNotDeactivated, 445 self.vg.deactivate_lv, 'test') 446 447 448class BrickLvmTestCaseIgnoreFDWarnings(BrickLvmTestCase): 449 def setUp(self): 450 self.configuration = mock.Mock(conf.Configuration) 451 self.configuration.lvm_suppress_fd_warnings = True 452 super(BrickLvmTestCaseIgnoreFDWarnings, self).setUp() 453