1# Copyright 2012 Josh Durgin 2# Copyright 2013 Canonical Ltd. 3# All Rights Reserved. 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); you may 6# not use this file except in compliance with the License. You may obtain 7# a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14# License for the specific language governing permissions and limitations 15# under the License. 16 17import math 18import os 19import tempfile 20import uuid 21 22import castellan 23import ddt 24import mock 25from mock import call 26from oslo_utils import imageutils 27from oslo_utils import units 28 29from cinder import context 30from cinder import db 31from cinder import exception 32import cinder.image.glance 33from cinder.image import image_utils 34from cinder import objects 35from cinder.objects import fields 36from cinder import test 37from cinder.tests.unit import fake_constants as fake 38from cinder.tests.unit import fake_snapshot 39from cinder.tests.unit import fake_volume 40from cinder.tests.unit.keymgr import fake as fake_keymgr 41from cinder.tests.unit import utils 42from cinder.tests.unit.volume import test_driver 43from cinder.volume import configuration as conf 44import cinder.volume.drivers.rbd as driver 45from cinder.volume.flows.manager import create_volume 46 47 48# This is used to collect raised exceptions so that tests may check what was 49# raised. 50# NOTE: this must be initialised in test setUp(). 51RAISED_EXCEPTIONS = [] 52 53 54class MockException(Exception): 55 56 def __init__(self, *args, **kwargs): 57 RAISED_EXCEPTIONS.append(self.__class__) 58 59 60class MockImageNotFoundException(MockException): 61 """Used as mock for rbd.ImageNotFound.""" 62 63 64class MockImageBusyException(MockException): 65 """Used as mock for rbd.ImageBusy.""" 66 67 68class MockImageExistsException(MockException): 69 """Used as mock for rbd.ImageExists.""" 70 71 72class MockOSErrorException(MockException): 73 """Used as mock for rbd.OSError.""" 74 75 76class KeyObject(object): 77 def get_encoded(arg): 78 return "asdf".encode('utf-8') 79 80 81def common_mocks(f): 82 """Decorator to set mocks common to all tests. 83 84 The point of doing these mocks here is so that we don't accidentally set 85 mocks that can't/don't get unset. 86 """ 87 def _FakeRetrying(wait_func=None, 88 original_retrying = driver.utils.retrying.Retrying, 89 *args, **kwargs): 90 return original_retrying(wait_func=lambda *a, **k: 0, 91 *args, **kwargs) 92 93 def _common_inner_inner1(inst, *args, **kwargs): 94 @mock.patch('retrying.Retrying', _FakeRetrying) 95 @mock.patch.object(driver.RBDDriver, '_get_usage_info') 96 @mock.patch('cinder.volume.drivers.rbd.RBDVolumeProxy') 97 @mock.patch('cinder.volume.drivers.rbd.RADOSClient') 98 @mock.patch('cinder.backup.drivers.ceph.rbd') 99 @mock.patch('cinder.backup.drivers.ceph.rados') 100 def _common_inner_inner2(mock_rados, mock_rbd, mock_client, 101 mock_proxy, mock_usage_info): 102 inst.mock_rbd = mock_rbd 103 inst.mock_rados = mock_rados 104 inst.mock_client = mock_client 105 inst.mock_proxy = mock_proxy 106 inst.mock_rbd.RBD.Error = Exception 107 inst.mock_rados.Error = Exception 108 inst.mock_rbd.ImageBusy = MockImageBusyException 109 inst.mock_rbd.ImageNotFound = MockImageNotFoundException 110 inst.mock_rbd.ImageExists = MockImageExistsException 111 inst.mock_rbd.InvalidArgument = MockImageNotFoundException 112 113 inst.driver.rbd = inst.mock_rbd 114 inst.driver.rados = inst.mock_rados 115 return f(inst, *args, **kwargs) 116 117 return _common_inner_inner2() 118 119 return _common_inner_inner1 120 121 122CEPH_MON_DUMP = r"""dumped monmap epoch 1 123{ "epoch": 1, 124 "fsid": "33630410-6d93-4d66-8e42-3b953cf194aa", 125 "modified": "2013-05-22 17:44:56.343618", 126 "created": "2013-05-22 17:44:56.343618", 127 "mons": [ 128 { "rank": 0, 129 "name": "a", 130 "addr": "[::1]:6789\/0"}, 131 { "rank": 1, 132 "name": "b", 133 "addr": "[::1]:6790\/0"}, 134 { "rank": 2, 135 "name": "c", 136 "addr": "[::1]:6791\/0"}, 137 { "rank": 3, 138 "name": "d", 139 "addr": "127.0.0.1:6792\/0"}, 140 { "rank": 4, 141 "name": "e", 142 "addr": "example.com:6791\/0"}], 143 "quorum": [ 144 0, 145 1, 146 2]} 147""" 148 149 150class MockDriverConfig(object): 151 def __init__(self, **kwargs): 152 my_dict = vars(self) 153 my_dict.update(kwargs) 154 my_dict.setdefault('max_over_subscription_ratio', 1.0) 155 my_dict.setdefault('reserved_percentage', 0) 156 my_dict.setdefault('volume_backend_name', 'RBD') 157 my_dict.setdefault('_default', None) 158 159 def __call__(self, value): 160 return getattr(self, value, self._default) 161 162 163def mock_driver_configuration(value): 164 if value == 'max_over_subscription_ratio': 165 return 1.0 166 if value == 'reserved_percentage': 167 return 0 168 return 'RBD' 169 170 171@ddt.ddt 172class RBDTestCase(test.TestCase): 173 174 def setUp(self): 175 global RAISED_EXCEPTIONS 176 RAISED_EXCEPTIONS = [] 177 super(RBDTestCase, self).setUp() 178 179 self.cfg = mock.Mock(spec=conf.Configuration) 180 self.cfg.image_conversion_dir = None 181 self.cfg.rbd_cluster_name = 'nondefault' 182 self.cfg.rbd_pool = 'rbd' 183 self.cfg.rbd_ceph_conf = '/usr/local/etc/ceph/my_ceph.conf' 184 self.cfg.rbd_keyring_conf = '/usr/local/etc/ceph/my_ceph.client.keyring' 185 self.cfg.rbd_secret_uuid = None 186 self.cfg.rbd_user = 'cinder' 187 self.cfg.volume_backend_name = None 188 self.cfg.volume_dd_blocksize = '1M' 189 self.cfg.rbd_store_chunk_size = 4 190 self.cfg.rados_connection_retries = 3 191 self.cfg.rados_connection_interval = 5 192 self.cfg.backup_use_temp_snapshot = False 193 194 mock_exec = mock.Mock() 195 mock_exec.return_value = ('', '') 196 197 self.driver = driver.RBDDriver(execute=mock_exec, 198 configuration=self.cfg) 199 self.driver.set_initialized() 200 201 self.context = context.get_admin_context() 202 203 self.volume_a = fake_volume.fake_volume_obj( 204 self.context, 205 **{'name': u'volume-0000000a', 206 'id': '4c39c3c7-168f-4b32-b585-77f1b3bf0a38', 207 'size': 10}) 208 209 self.volume_b = fake_volume.fake_volume_obj( 210 self.context, 211 **{'name': u'volume-0000000b', 212 'id': '0c7d1f44-5a06-403f-bb82-ae7ad0d693a6', 213 'size': 10}) 214 215 self.volume_c = fake_volume.fake_volume_obj( 216 self.context, 217 **{'name': u'volume-0000000a', 218 'id': '55555555-222f-4b32-b585-9991b3bf0a99', 219 'size': 12, 220 'encryption_key_id': fake.ENCRYPTION_KEY_ID}) 221 222 self.snapshot = fake_snapshot.fake_snapshot_obj( 223 self.context, name='snapshot-0000000a') 224 225 self.snapshot_b = fake_snapshot.fake_snapshot_obj( 226 self.context, 227 **{'name': u'snapshot-0000000n', 228 'expected_attrs': ['volume'], 229 'volume': {'id': fake.VOLUME_ID, 230 'name': 'cinder-volume', 231 'size': 128, 232 'host': 'host@fakebackend#fakepool'} 233 }) 234 235 @ddt.data({'cluster_name': None, 'pool_name': 'rbd'}, 236 {'cluster_name': 'volumes', 'pool_name': None}) 237 @ddt.unpack 238 def test_min_config(self, cluster_name, pool_name): 239 self.cfg.rbd_cluster_name = cluster_name 240 self.cfg.rbd_pool = pool_name 241 242 with mock.patch('cinder.volume.drivers.rbd.rados'): 243 self.assertRaises(exception.InvalidConfigurationValue, 244 self.driver.check_for_setup_error) 245 246 def test_parse_replication_config_empty(self): 247 self.driver._parse_replication_configs([]) 248 self.assertEqual([], self.driver._replication_targets) 249 250 def test_parse_replication_config_missing(self): 251 """Parsing replication_device without required backend_id.""" 252 cfg = [{'conf': '/usr/local/etc/ceph/secondary.conf'}] 253 self.assertRaises(exception.InvalidConfigurationValue, 254 self.driver._parse_replication_configs, 255 cfg) 256 257 def test_parse_replication_config_defaults(self): 258 """Parsing replication_device with default conf and user.""" 259 cfg = [{'backend_id': 'secondary-backend'}] 260 expected = [{'name': 'secondary-backend', 261 'conf': '/usr/local/etc/ceph/secondary-backend.conf', 262 'user': 'cinder'}] 263 self.driver._parse_replication_configs(cfg) 264 self.assertEqual(expected, self.driver._replication_targets) 265 266 @ddt.data(1, 2) 267 def test_parse_replication_config(self, num_targets): 268 cfg = [{'backend_id': 'secondary-backend', 269 'conf': 'foo', 270 'user': 'bar'}, 271 {'backend_id': 'tertiary-backend'}] 272 expected = [{'name': 'secondary-backend', 273 'conf': 'foo', 274 'user': 'bar'}, 275 {'name': 'tertiary-backend', 276 'conf': '/usr/local/etc/ceph/tertiary-backend.conf', 277 'user': 'cinder'}] 278 self.driver._parse_replication_configs(cfg[:num_targets]) 279 self.assertEqual(expected[:num_targets], 280 self.driver._replication_targets) 281 282 def test_do_setup_replication_disabled(self): 283 with mock.patch.object(self.driver.configuration, 'safe_get', 284 return_value=None): 285 self.driver.do_setup(self.context) 286 self.assertFalse(self.driver._is_replication_enabled) 287 self.assertEqual([], self.driver._replication_targets) 288 self.assertEqual([], self.driver._target_names) 289 self.assertEqual({'name': self.cfg.rbd_cluster_name, 290 'conf': self.cfg.rbd_ceph_conf, 291 'user': self.cfg.rbd_user}, 292 self.driver._active_config) 293 294 def test_do_setup_replication(self): 295 cfg = [{'backend_id': 'secondary-backend', 296 'conf': 'foo', 297 'user': 'bar'}] 298 expected = [{'name': 'secondary-backend', 299 'conf': 'foo', 300 'user': 'bar'}] 301 302 with mock.patch.object(self.driver.configuration, 'safe_get', 303 return_value=cfg): 304 self.driver.do_setup(self.context) 305 self.assertTrue(self.driver._is_replication_enabled) 306 self.assertEqual(expected, self.driver._replication_targets) 307 self.assertEqual({'name': self.cfg.rbd_cluster_name, 308 'conf': self.cfg.rbd_ceph_conf, 309 'user': self.cfg.rbd_user}, 310 self.driver._active_config) 311 312 def test_do_setup_replication_failed_over(self): 313 cfg = [{'backend_id': 'secondary-backend', 314 'conf': 'foo', 315 'user': 'bar'}] 316 expected = [{'name': 'secondary-backend', 317 'conf': 'foo', 318 'user': 'bar'}] 319 self.driver._active_backend_id = 'secondary-backend' 320 321 with mock.patch.object(self.driver.configuration, 'safe_get', 322 return_value=cfg): 323 self.driver.do_setup(self.context) 324 self.assertTrue(self.driver._is_replication_enabled) 325 self.assertEqual(expected, self.driver._replication_targets) 326 self.assertEqual(expected[0], self.driver._active_config) 327 328 def test_do_setup_replication_failed_over_unknown(self): 329 cfg = [{'backend_id': 'secondary-backend', 330 'conf': 'foo', 331 'user': 'bar'}] 332 self.driver._active_backend_id = 'unknown-backend' 333 334 with mock.patch.object(self.driver.configuration, 'safe_get', 335 return_value=cfg): 336 self.assertRaises(exception.InvalidReplicationTarget, 337 self.driver.do_setup, 338 self.context) 339 340 @mock.patch.object(driver.RBDDriver, '_enable_replication', 341 return_value=mock.sentinel.volume_update) 342 def test_enable_replication_if_needed_replicated_volume(self, mock_enable): 343 self.volume_a.volume_type = fake_volume.fake_volume_type_obj( 344 self.context, 345 id=fake.VOLUME_TYPE_ID, 346 extra_specs={'replication_enabled': '<is> True'}) 347 res = self.driver._enable_replication_if_needed(self.volume_a) 348 self.assertEqual(mock.sentinel.volume_update, res) 349 mock_enable.assert_called_once_with(self.volume_a) 350 351 @ddt.data(False, True) 352 @mock.patch.object(driver.RBDDriver, '_enable_replication') 353 def test_enable_replication_if_needed_non_replicated(self, enabled, 354 mock_enable): 355 self.driver._is_replication_enabled = enabled 356 res = self.driver._enable_replication_if_needed(self.volume_a) 357 if enabled: 358 expect = {'replication_status': fields.ReplicationStatus.DISABLED} 359 else: 360 expect = None 361 self.assertEqual(expect, res) 362 mock_enable.assert_not_called() 363 364 @ddt.data([True, False], [False, False], [True, True]) 365 @ddt.unpack 366 @common_mocks 367 def test_enable_replication(self, exclusive_lock_enabled, 368 journaling_enabled): 369 """Test _enable_replication method. 370 371 We want to confirm that if the Ceph backend has globally enabled 372 'exclusive_lock' and 'journaling'. we don't try to enable them 373 again and we properly indicate with our return value that they were 374 already enabled. 375 'journaling' depends on 'exclusive_lock', so if 'exclusive-lock' 376 is disabled, 'journaling' can't be enabled so the '[False. True]' 377 case is impossible. 378 In this test case, there are three test scenarios: 379 1. 'exclusive_lock' and 'journaling' both enabled, 380 'image.features()' will not be called. 381 2. 'exclusive_lock' enabled, 'journaling' disabled, 382 'image.features()' will be only called for 'journaling'. 383 3. 'exclusice_lock' and 'journaling' are both disabled, 384 'image.features()'will be both called for 'exclusive-lock' and 385 'journaling' in this order. 386 """ 387 journaling_feat = 1 388 exclusive_lock_feat = 2 389 self.driver.rbd.RBD_FEATURE_JOURNALING = journaling_feat 390 self.driver.rbd.RBD_FEATURE_EXCLUSIVE_LOCK = exclusive_lock_feat 391 image = self.mock_proxy.return_value.__enter__.return_value 392 image.features.return_value = 0 393 if exclusive_lock_enabled: 394 image.features.return_value += exclusive_lock_feat 395 if journaling_enabled: 396 image.features.return_value += journaling_feat 397 journaling_status = str(journaling_enabled).lower() 398 exclusive_lock_status = str(exclusive_lock_enabled).lower() 399 expected = { 400 'replication_driver_data': ('{"had_exclusive_lock":%s,' 401 '"had_journaling":%s}' % 402 (exclusive_lock_status, 403 journaling_status)), 404 'replication_status': 'enabled', 405 } 406 res = self.driver._enable_replication(self.volume_a) 407 self.assertEqual(expected, res) 408 if exclusive_lock_enabled and journaling_enabled: 409 image.update_features.assert_not_called() 410 elif exclusive_lock_enabled and not journaling_enabled: 411 image.update_features.assert_called_once_with(journaling_feat, 412 True) 413 else: 414 calls = [call(exclusive_lock_feat, True), 415 call(journaling_feat, True)] 416 image.update_features.assert_has_calls(calls, any_order=False) 417 image.mirror_image_enable.assert_called_once_with() 418 419 @ddt.data(['false', 'true'], ['true', 'true'], ['false', 'false']) 420 @ddt.unpack 421 @common_mocks 422 def test_disable_replication(self, had_journaling, had_exclusive_lock): 423 driver_data = ('{"had_journaling": %s,"had_exclusive_lock": %s}' % 424 (had_journaling, had_exclusive_lock)) 425 self.volume_a.replication_driver_data = driver_data 426 image = self.mock_proxy.return_value.__enter__.return_value 427 428 res = self.driver._disable_replication(self.volume_a) 429 expected = {'replication_status': fields.ReplicationStatus.DISABLED, 430 'replication_driver_data': None} 431 self.assertEqual(expected, res) 432 image.mirror_image_disable.assert_called_once_with(False) 433 434 if had_journaling == 'true' and had_exclusive_lock == 'true': 435 image.update_features.assert_not_called() 436 elif had_journaling == 'false' and had_exclusive_lock == 'true': 437 image.update_features.assert_called_once_with( 438 self.driver.rbd.RBD_FEATURE_JOURNALING, False) 439 else: 440 calls = [call(self.driver.rbd.RBD_FEATURE_JOURNALING, False), 441 call(self.driver.rbd.RBD_FEATURE_EXCLUSIVE_LOCK, 442 False)] 443 image.update_features.assert_has_calls(calls, any_order=False) 444 445 @common_mocks 446 @mock.patch.object(driver.RBDDriver, '_enable_replication') 447 def test_create_volume(self, mock_enable_repl): 448 client = self.mock_client.return_value 449 client.__enter__.return_value = client 450 451 res = self.driver.create_volume(self.volume_a) 452 453 self.assertIsNone(res) 454 chunk_size = self.cfg.rbd_store_chunk_size * units.Mi 455 order = int(math.log(chunk_size, 2)) 456 args = [client.ioctx, str(self.volume_a.name), 457 self.volume_a.size * units.Gi, order] 458 kwargs = {'old_format': False, 459 'features': client.features} 460 self.mock_rbd.RBD.return_value.create.assert_called_once_with( 461 *args, **kwargs) 462 client.__enter__.assert_called_once_with() 463 client.__exit__.assert_called_once_with(None, None, None) 464 mock_enable_repl.assert_not_called() 465 466 @common_mocks 467 @mock.patch.object(driver.RBDDriver, '_enable_replication') 468 def test_create_volume_replicated(self, mock_enable_repl): 469 self.volume_a.volume_type = fake_volume.fake_volume_type_obj( 470 self.context, 471 id=fake.VOLUME_TYPE_ID, 472 extra_specs={'replication_enabled': '<is> True'}) 473 474 client = self.mock_client.return_value 475 client.__enter__.return_value = client 476 477 expected_update = { 478 'replication_status': 'enabled', 479 'replication_driver_data': '{"had_journaling": false}' 480 } 481 mock_enable_repl.return_value = expected_update 482 483 res = self.driver.create_volume(self.volume_a) 484 self.assertEqual(expected_update, res) 485 mock_enable_repl.assert_called_once_with(self.volume_a) 486 487 chunk_size = self.cfg.rbd_store_chunk_size * units.Mi 488 order = int(math.log(chunk_size, 2)) 489 self.mock_rbd.RBD.return_value.create.assert_called_once_with( 490 client.ioctx, self.volume_a.name, self.volume_a.size * units.Gi, 491 order, old_format=False, features=client.features) 492 493 client.__enter__.assert_called_once_with() 494 client.__exit__.assert_called_once_with(None, None, None) 495 496 @common_mocks 497 def test_manage_existing_get_size(self): 498 with mock.patch.object(self.driver.rbd.Image(), 'size') as \ 499 mock_rbd_image_size: 500 with mock.patch.object(self.driver.rbd.Image(), 'close') \ 501 as mock_rbd_image_close: 502 mock_rbd_image_size.return_value = 2 * units.Gi 503 existing_ref = {'source-name': self.volume_a.name} 504 return_size = self.driver.manage_existing_get_size( 505 self.volume_a, 506 existing_ref) 507 self.assertEqual(2, return_size) 508 mock_rbd_image_size.assert_called_once_with() 509 mock_rbd_image_close.assert_called_once_with() 510 511 @common_mocks 512 def test_manage_existing_get_non_integer_size(self): 513 rbd_image = self.driver.rbd.Image.return_value 514 rbd_image.size.return_value = int(1.75 * units.Gi) 515 existing_ref = {'source-name': self.volume_a.name} 516 return_size = self.driver.manage_existing_get_size(self.volume_a, 517 existing_ref) 518 self.assertEqual(2, return_size) 519 rbd_image.size.assert_called_once_with() 520 rbd_image.close.assert_called_once_with() 521 522 @common_mocks 523 def test_manage_existing_get_invalid_size(self): 524 525 with mock.patch.object(self.driver.rbd.Image(), 'size') as \ 526 mock_rbd_image_size: 527 with mock.patch.object(self.driver.rbd.Image(), 'close') \ 528 as mock_rbd_image_close: 529 mock_rbd_image_size.return_value = 'abcd' 530 existing_ref = {'source-name': self.volume_a.name} 531 self.assertRaises(exception.VolumeBackendAPIException, 532 self.driver.manage_existing_get_size, 533 self.volume_a, existing_ref) 534 535 mock_rbd_image_size.assert_called_once_with() 536 mock_rbd_image_close.assert_called_once_with() 537 538 @common_mocks 539 def test_manage_existing(self): 540 client = self.mock_client.return_value 541 client.__enter__.return_value = client 542 543 with mock.patch.object(self.driver.rbd.RBD(), 'rename') as \ 544 mock_rbd_image_rename: 545 exist_volume = 'vol-exist' 546 existing_ref = {'source-name': exist_volume} 547 mock_rbd_image_rename.return_value = 0 548 self.driver.manage_existing(self.volume_a, existing_ref) 549 mock_rbd_image_rename.assert_called_with( 550 client.ioctx, 551 exist_volume, 552 self.volume_a.name) 553 554 @common_mocks 555 def test_manage_existing_with_exist_rbd_image(self): 556 client = self.mock_client.return_value 557 client.__enter__.return_value = client 558 559 self.mock_rbd.RBD.return_value.rename.side_effect = ( 560 MockImageExistsException) 561 562 exist_volume = 'vol-exist' 563 existing_ref = {'source-name': exist_volume} 564 self.assertRaises(self.mock_rbd.ImageExists, 565 self.driver.manage_existing, 566 self.volume_a, existing_ref) 567 568 # Make sure the exception was raised 569 self.assertEqual(RAISED_EXCEPTIONS, 570 [self.mock_rbd.ImageExists]) 571 572 @common_mocks 573 def test_manage_existing_with_invalid_rbd_image(self): 574 self.mock_rbd.Image.side_effect = self.mock_rbd.ImageNotFound 575 576 invalid_volume = 'vol-invalid' 577 invalid_ref = {'source-name': invalid_volume} 578 579 self.assertRaises(exception.ManageExistingInvalidReference, 580 self.driver.manage_existing_get_size, 581 self.volume_a, invalid_ref) 582 # Make sure the exception was raised 583 self.assertEqual([self.mock_rbd.ImageNotFound], 584 RAISED_EXCEPTIONS) 585 586 @common_mocks 587 @mock.patch.object(driver.RBDDriver, '_get_image_status') 588 def test_get_manageable_volumes(self, mock_get_image_status): 589 cinder_vols = [{'id': '00000000-0000-0000-0000-000000000000'}] 590 vols = ['volume-00000000-0000-0000-0000-000000000000', 'vol1', 'vol2'] 591 self.mock_rbd.RBD.return_value.list.return_value = vols 592 image = self.mock_proxy.return_value.__enter__.return_value 593 image.size.side_effect = [2 * units.Gi, 4 * units.Gi, 6 * units.Gi] 594 mock_get_image_status.side_effect = [ 595 {'watchers': []}, 596 {'watchers': [{"address": "192.168.120.61:0\/3012034728", 597 "client": 44431941, "cookie": 94077162321152}]}] 598 res = self.driver.get_manageable_volumes( 599 cinder_vols, None, 1000, 0, ['size'], ['asc']) 600 exp = [{'size': 2, 'reason_not_safe': 'already managed', 601 'extra_info': None, 'safe_to_manage': False, 602 'reference': {'source-name': 603 'volume-00000000-0000-0000-0000-000000000000'}, 604 'cinder_id': '00000000-0000-0000-0000-000000000000'}, 605 {'size': 4, 'reason_not_safe': None, 606 'safe_to_manage': True, 'reference': {'source-name': 'vol1'}, 607 'cinder_id': None, 'extra_info': None}, 608 {'size': 6, 'reason_not_safe': 'volume in use', 609 'safe_to_manage': False, 'reference': {'source-name': 'vol2'}, 610 'cinder_id': None, 'extra_info': None}] 611 self.assertEqual(exp, res) 612 613 @common_mocks 614 def test_delete_backup_snaps(self): 615 self.driver.rbd.Image.remove_snap = mock.Mock() 616 with mock.patch.object(self.driver, '_get_backup_snaps') as \ 617 mock_get_backup_snaps: 618 mock_get_backup_snaps.return_value = [{'name': 'snap1'}] 619 rbd_image = self.driver.rbd.Image() 620 self.driver._delete_backup_snaps(rbd_image) 621 mock_get_backup_snaps.assert_called_once_with(rbd_image) 622 self.assertTrue( 623 self.driver.rbd.Image.return_value.remove_snap.called) 624 625 @common_mocks 626 def test_delete_volume(self): 627 client = self.mock_client.return_value 628 629 self.driver.rbd.Image.return_value.list_snaps.return_value = [] 630 631 with mock.patch.object(self.driver, '_get_clone_info') as \ 632 mock_get_clone_info: 633 with mock.patch.object(self.driver, '_delete_backup_snaps') as \ 634 mock_delete_backup_snaps: 635 mock_get_clone_info.return_value = (None, None, None) 636 637 self.driver.delete_volume(self.volume_a) 638 639 mock_get_clone_info.assert_called_once_with( 640 self.mock_rbd.Image.return_value, 641 self.volume_a.name, 642 None) 643 (self.driver.rbd.Image.return_value 644 .list_snaps.assert_called_once_with()) 645 client.__enter__.assert_called_once_with() 646 client.__exit__.assert_called_once_with(None, None, None) 647 mock_delete_backup_snaps.assert_called_once_with( 648 self.mock_rbd.Image.return_value) 649 self.assertFalse( 650 self.driver.rbd.Image.return_value.unprotect_snap.called) 651 self.assertEqual( 652 1, self.driver.rbd.RBD.return_value.remove.call_count) 653 654 @common_mocks 655 def delete_volume_not_found(self): 656 self.mock_rbd.Image.side_effect = self.mock_rbd.ImageNotFound 657 self.assertIsNone(self.driver.delete_volume(self.volume_a)) 658 self.mock_rbd.Image.assert_called_once_with() 659 # Make sure the exception was raised 660 self.assertEqual(RAISED_EXCEPTIONS, [self.mock_rbd.ImageNotFound]) 661 662 @common_mocks 663 def test_delete_busy_volume(self): 664 self.mock_rbd.Image.return_value.list_snaps.return_value = [] 665 666 self.mock_rbd.RBD.return_value.remove.side_effect = ( 667 self.mock_rbd.ImageBusy) 668 669 with mock.patch.object(self.driver, '_get_clone_info') as \ 670 mock_get_clone_info: 671 mock_get_clone_info.return_value = (None, None, None) 672 with mock.patch.object(self.driver, '_delete_backup_snaps') as \ 673 mock_delete_backup_snaps: 674 with mock.patch.object(driver, 'RADOSClient') as \ 675 mock_rados_client: 676 self.assertRaises(exception.VolumeIsBusy, 677 self.driver.delete_volume, self.volume_a) 678 679 mock_get_clone_info.assert_called_once_with( 680 self.mock_rbd.Image.return_value, 681 self.volume_a.name, 682 None) 683 (self.mock_rbd.Image.return_value.list_snaps 684 .assert_called_once_with()) 685 mock_rados_client.assert_called_once_with(self.driver) 686 mock_delete_backup_snaps.assert_called_once_with( 687 self.mock_rbd.Image.return_value) 688 self.assertFalse( 689 self.mock_rbd.Image.return_value.unprotect_snap.called) 690 self.assertEqual( 691 3, self.mock_rbd.RBD.return_value.remove.call_count) 692 self.assertEqual(3, len(RAISED_EXCEPTIONS)) 693 # Make sure the exception was raised 694 self.assertIn(self.mock_rbd.ImageBusy, RAISED_EXCEPTIONS) 695 696 @common_mocks 697 def test_delete_volume_not_found(self): 698 self.mock_rbd.Image.return_value.list_snaps.return_value = [] 699 700 self.mock_rbd.RBD.return_value.remove.side_effect = ( 701 self.mock_rbd.ImageNotFound) 702 703 with mock.patch.object(self.driver, '_get_clone_info') as \ 704 mock_get_clone_info: 705 mock_get_clone_info.return_value = (None, None, None) 706 with mock.patch.object(self.driver, '_delete_backup_snaps') as \ 707 mock_delete_backup_snaps: 708 with mock.patch.object(driver, 'RADOSClient') as \ 709 mock_rados_client: 710 self.assertIsNone(self.driver.delete_volume(self.volume_a)) 711 mock_get_clone_info.assert_called_once_with( 712 self.mock_rbd.Image.return_value, 713 self.volume_a.name, 714 None) 715 (self.mock_rbd.Image.return_value.list_snaps 716 .assert_called_once_with()) 717 mock_rados_client.assert_called_once_with(self.driver) 718 mock_delete_backup_snaps.assert_called_once_with( 719 self.mock_rbd.Image.return_value) 720 self.assertFalse( 721 self.mock_rbd.Image.return_value.unprotect_snap.called) 722 self.assertEqual( 723 1, self.mock_rbd.RBD.return_value.remove.call_count) 724 # Make sure the exception was raised 725 self.assertEqual(RAISED_EXCEPTIONS, 726 [self.mock_rbd.ImageNotFound]) 727 728 @common_mocks 729 @mock.patch('cinder.objects.Volume.get_by_id') 730 def test_create_snapshot(self, volume_get_by_id): 731 volume_get_by_id.return_value = self.volume_a 732 proxy = self.mock_proxy.return_value 733 proxy.__enter__.return_value = proxy 734 735 self.driver.create_snapshot(self.snapshot) 736 737 args = [str(self.snapshot.name)] 738 proxy.create_snap.assert_called_with(*args) 739 proxy.protect_snap.assert_called_with(*args) 740 741 @common_mocks 742 @mock.patch('cinder.objects.Volume.get_by_id') 743 def test_delete_snapshot(self, volume_get_by_id): 744 volume_get_by_id.return_value = self.volume_a 745 proxy = self.mock_proxy.return_value 746 proxy.__enter__.return_value = proxy 747 748 self.driver.delete_snapshot(self.snapshot) 749 750 proxy.remove_snap.assert_called_with(self.snapshot.name) 751 proxy.unprotect_snap.assert_called_with(self.snapshot.name) 752 753 @common_mocks 754 @mock.patch('cinder.objects.Volume.get_by_id') 755 def test_delete_notfound_snapshot(self, volume_get_by_id): 756 volume_get_by_id.return_value = self.volume_a 757 proxy = self.mock_proxy.return_value 758 proxy.__enter__.return_value = proxy 759 760 proxy.unprotect_snap.side_effect = ( 761 self.mock_rbd.ImageNotFound) 762 763 self.driver.delete_snapshot(self.snapshot) 764 765 proxy.remove_snap.assert_called_with(self.snapshot.name) 766 proxy.unprotect_snap.assert_called_with(self.snapshot.name) 767 768 @common_mocks 769 @mock.patch('cinder.objects.Volume.get_by_id') 770 def test_delete_notfound_on_remove_snapshot(self, volume_get_by_id): 771 volume_get_by_id.return_value = self.volume_a 772 proxy = self.mock_proxy.return_value 773 proxy.__enter__.return_value = proxy 774 775 proxy.remove_snap.side_effect = ( 776 self.mock_rbd.ImageNotFound) 777 778 self.driver.delete_snapshot(self.snapshot) 779 780 proxy.remove_snap.assert_called_with(self.snapshot.name) 781 proxy.unprotect_snap.assert_called_with(self.snapshot.name) 782 783 @common_mocks 784 @mock.patch('cinder.objects.Volume.get_by_id') 785 def test_delete_unprotected_snapshot(self, volume_get_by_id): 786 volume_get_by_id.return_value = self.volume_a 787 proxy = self.mock_proxy.return_value 788 proxy.__enter__.return_value = proxy 789 proxy.unprotect_snap.side_effect = self.mock_rbd.InvalidArgument 790 791 self.driver.delete_snapshot(self.snapshot) 792 self.assertTrue(proxy.unprotect_snap.called) 793 self.assertTrue(proxy.remove_snap.called) 794 795 @common_mocks 796 @mock.patch('cinder.objects.Volume.get_by_id') 797 def test_delete_busy_snapshot(self, volume_get_by_id): 798 volume_get_by_id.return_value = self.volume_a 799 proxy = self.mock_proxy.return_value 800 proxy.__enter__.return_value = proxy 801 802 proxy.unprotect_snap.side_effect = ( 803 self.mock_rbd.ImageBusy) 804 805 with mock.patch.object(self.driver, '_get_children_info') as \ 806 mock_get_children_info: 807 mock_get_children_info.return_value = [('pool', 'volume2')] 808 809 with mock.patch.object(driver, 'LOG') as \ 810 mock_log: 811 812 self.assertRaises(exception.SnapshotIsBusy, 813 self.driver.delete_snapshot, 814 self.snapshot) 815 816 mock_get_children_info.assert_called_once_with( 817 proxy, 818 self.snapshot.name) 819 820 self.assertTrue(mock_log.info.called) 821 self.assertTrue(proxy.unprotect_snap.called) 822 self.assertFalse(proxy.remove_snap.called) 823 824 @common_mocks 825 def test_get_children_info(self): 826 volume = self.mock_proxy 827 volume.set_snap = mock.Mock() 828 volume.list_children = mock.Mock() 829 list_children = [('pool', 'volume2')] 830 volume.list_children.return_value = list_children 831 832 info = self.driver._get_children_info(volume, 833 self.snapshot['name']) 834 835 self.assertEqual(list_children, info) 836 837 @common_mocks 838 def test_get_clone_info(self): 839 volume = self.mock_rbd.Image() 840 volume.set_snap = mock.Mock() 841 volume.parent_info = mock.Mock() 842 parent_info = ('a', 'b', '%s.clone_snap' % (self.volume_a.name)) 843 volume.parent_info.return_value = parent_info 844 845 info = self.driver._get_clone_info(volume, self.volume_a.name) 846 847 self.assertEqual(parent_info, info) 848 849 self.assertFalse(volume.set_snap.called) 850 volume.parent_info.assert_called_once_with() 851 852 @common_mocks 853 def test_get_clone_info_w_snap(self): 854 volume = self.mock_rbd.Image() 855 volume.set_snap = mock.Mock() 856 volume.parent_info = mock.Mock() 857 parent_info = ('a', 'b', '%s.clone_snap' % (self.volume_a.name)) 858 volume.parent_info.return_value = parent_info 859 860 snapshot = self.mock_rbd.ImageSnapshot() 861 862 info = self.driver._get_clone_info(volume, self.volume_a.name, 863 snap=snapshot) 864 865 self.assertEqual(parent_info, info) 866 867 self.assertEqual(2, volume.set_snap.call_count) 868 volume.parent_info.assert_called_once_with() 869 870 @common_mocks 871 def test_get_clone_info_w_exception(self): 872 volume = self.mock_rbd.Image() 873 volume.set_snap = mock.Mock() 874 volume.parent_info = mock.Mock() 875 volume.parent_info.side_effect = self.mock_rbd.ImageNotFound 876 877 snapshot = self.mock_rbd.ImageSnapshot() 878 879 info = self.driver._get_clone_info(volume, self.volume_a.name, 880 snap=snapshot) 881 882 self.assertEqual((None, None, None), info) 883 884 self.assertEqual(2, volume.set_snap.call_count) 885 volume.parent_info.assert_called_once_with() 886 # Make sure the exception was raised 887 self.assertEqual(RAISED_EXCEPTIONS, [self.mock_rbd.ImageNotFound]) 888 889 @common_mocks 890 def test_get_clone_info_deleted_volume(self): 891 volume = self.mock_rbd.Image() 892 volume.set_snap = mock.Mock() 893 volume.parent_info = mock.Mock() 894 parent_info = ('a', 'b', '%s.clone_snap' % (self.volume_a.name)) 895 volume.parent_info.return_value = parent_info 896 897 info = self.driver._get_clone_info(volume, 898 "%s.deleted" % (self.volume_a.name)) 899 900 self.assertEqual(parent_info, info) 901 902 self.assertFalse(volume.set_snap.called) 903 volume.parent_info.assert_called_once_with() 904 905 @common_mocks 906 @mock.patch.object(driver.RBDDriver, '_enable_replication') 907 def test_create_cloned_volume_same_size(self, mock_enable_repl): 908 self.cfg.rbd_max_clone_depth = 2 909 910 with mock.patch.object(self.driver, '_get_clone_depth') as \ 911 mock_get_clone_depth: 912 # Try with no flatten required 913 with mock.patch.object(self.driver, '_resize') as mock_resize: 914 mock_get_clone_depth.return_value = 1 915 916 res = self.driver.create_cloned_volume(self.volume_b, 917 self.volume_a) 918 919 self.assertIsNone(res) 920 (self.mock_rbd.Image.return_value.create_snap 921 .assert_called_once_with('.'.join( 922 (self.volume_b.name, 'clone_snap')))) 923 (self.mock_rbd.Image.return_value.protect_snap 924 .assert_called_once_with('.'.join( 925 (self.volume_b.name, 'clone_snap')))) 926 self.assertEqual( 927 1, self.mock_rbd.RBD.return_value.clone.call_count) 928 self.assertEqual( 929 2, self.mock_rbd.Image.return_value.close.call_count) 930 self.assertTrue(mock_get_clone_depth.called) 931 mock_resize.assert_not_called() 932 mock_enable_repl.assert_not_called() 933 934 @common_mocks 935 @mock.patch.object(driver.RBDDriver, '_get_clone_depth', return_value=1) 936 @mock.patch.object(driver.RBDDriver, '_resize') 937 @mock.patch.object(driver.RBDDriver, '_enable_replication') 938 def test_create_cloned_volume_replicated(self, 939 mock_enable_repl, 940 mock_resize, 941 mock_get_clone_depth): 942 self.cfg.rbd_max_clone_depth = 2 943 self.volume_b.volume_type = fake_volume.fake_volume_type_obj( 944 self.context, 945 id=fake.VOLUME_TYPE_ID, 946 extra_specs={'replication_enabled': '<is> True'}) 947 948 expected_update = { 949 'replication_status': 'enabled', 950 'replication_driver_data': '{"had_journaling": false}' 951 } 952 mock_enable_repl.return_value = expected_update 953 954 res = self.driver.create_cloned_volume(self.volume_b, self.volume_a) 955 self.assertEqual(expected_update, res) 956 mock_enable_repl.assert_called_once_with(self.volume_b) 957 958 name = self.volume_b.name 959 image = self.mock_rbd.Image.return_value 960 961 image.create_snap.assert_called_once_with(name + '.clone_snap') 962 image.protect_snap.assert_called_once_with(name + '.clone_snap') 963 self.assertEqual(1, self.mock_rbd.RBD.return_value.clone.call_count) 964 self.assertEqual( 965 2, self.mock_rbd.Image.return_value.close.call_count) 966 mock_get_clone_depth.assert_called_once_with( 967 self.mock_client().__enter__(), self.volume_a.name) 968 mock_resize.assert_not_called() 969 970 @common_mocks 971 @mock.patch.object(driver.RBDDriver, '_enable_replication') 972 def test_create_cloned_volume_different_size(self, mock_enable_repl): 973 self.cfg.rbd_max_clone_depth = 2 974 975 with mock.patch.object(self.driver, '_get_clone_depth') as \ 976 mock_get_clone_depth: 977 # Try with no flatten required 978 with mock.patch.object(self.driver, '_resize') as mock_resize: 979 mock_get_clone_depth.return_value = 1 980 981 self.volume_b.size = 20 982 res = self.driver.create_cloned_volume(self.volume_b, 983 self.volume_a) 984 985 self.assertIsNone(res) 986 (self.mock_rbd.Image.return_value.create_snap 987 .assert_called_once_with('.'.join( 988 (self.volume_b.name, 'clone_snap')))) 989 (self.mock_rbd.Image.return_value.protect_snap 990 .assert_called_once_with('.'.join( 991 (self.volume_b.name, 'clone_snap')))) 992 self.assertEqual( 993 1, self.mock_rbd.RBD.return_value.clone.call_count) 994 self.assertEqual( 995 2, self.mock_rbd.Image.return_value.close.call_count) 996 self.assertTrue(mock_get_clone_depth.called) 997 self.assertEqual( 998 1, mock_resize.call_count) 999 mock_enable_repl.assert_not_called() 1000 1001 @common_mocks 1002 def test_create_cloned_volume_different_size_copy_only(self): 1003 self.cfg.rbd_max_clone_depth = 0 1004 1005 with mock.patch.object(self.driver, '_get_clone_depth') as \ 1006 mock_get_clone_depth: 1007 # Try with no flatten required 1008 with mock.patch.object(self.driver, '_resize') as mock_resize: 1009 mock_get_clone_depth.return_value = 1 1010 1011 self.volume_b.size = 20 1012 self.driver.create_cloned_volume(self.volume_b, self.volume_a) 1013 1014 self.assertEqual(1, mock_resize.call_count) 1015 1016 @common_mocks 1017 @mock.patch.object(driver.RBDDriver, '_enable_replication') 1018 def test_create_cloned_volume_w_flatten(self, mock_enable_repl): 1019 self.cfg.rbd_max_clone_depth = 1 1020 1021 with mock.patch.object(self.driver, '_get_clone_info') as \ 1022 mock_get_clone_info: 1023 mock_get_clone_info.return_value = ( 1024 ('fake_pool', self.volume_b.name, 1025 '.'.join((self.volume_b.name, 'clone_snap')))) 1026 with mock.patch.object(self.driver, '_get_clone_depth') as \ 1027 mock_get_clone_depth: 1028 # Try with no flatten required 1029 mock_get_clone_depth.return_value = 1 1030 1031 res = self.driver.create_cloned_volume(self.volume_b, 1032 self.volume_a) 1033 1034 self.assertIsNone(res) 1035 (self.mock_rbd.Image.return_value.create_snap 1036 .assert_called_once_with('.'.join( 1037 (self.volume_b.name, 'clone_snap')))) 1038 (self.mock_rbd.Image.return_value.protect_snap 1039 .assert_called_once_with('.'.join( 1040 (self.volume_b.name, 'clone_snap')))) 1041 self.assertEqual( 1042 1, self.mock_rbd.RBD.return_value.clone.call_count) 1043 (self.mock_rbd.Image.return_value.unprotect_snap 1044 .assert_called_once_with('.'.join( 1045 (self.volume_b.name, 'clone_snap')))) 1046 (self.mock_rbd.Image.return_value.remove_snap 1047 .assert_called_once_with('.'.join( 1048 (self.volume_b.name, 'clone_snap')))) 1049 1050 # We expect the driver to close both volumes, so 2 is expected 1051 self.assertEqual( 1052 3, self.mock_rbd.Image.return_value.close.call_count) 1053 self.assertTrue(mock_get_clone_depth.called) 1054 mock_enable_repl.assert_not_called() 1055 1056 @common_mocks 1057 @mock.patch.object(driver.RBDDriver, '_enable_replication') 1058 def test_create_cloned_volume_w_clone_exception(self, mock_enable_repl): 1059 self.cfg.rbd_max_clone_depth = 2 1060 self.mock_rbd.RBD.return_value.clone.side_effect = ( 1061 self.mock_rbd.RBD.Error) 1062 with mock.patch.object(self.driver, '_get_clone_depth') as \ 1063 mock_get_clone_depth: 1064 # Try with no flatten required 1065 mock_get_clone_depth.return_value = 1 1066 1067 self.assertRaises(self.mock_rbd.RBD.Error, 1068 self.driver.create_cloned_volume, 1069 self.volume_b, self.volume_a) 1070 1071 (self.mock_rbd.Image.return_value.create_snap 1072 .assert_called_once_with('.'.join( 1073 (self.volume_b.name, 'clone_snap')))) 1074 (self.mock_rbd.Image.return_value.protect_snap 1075 .assert_called_once_with('.'.join( 1076 (self.volume_b.name, 'clone_snap')))) 1077 self.assertEqual( 1078 1, self.mock_rbd.RBD.return_value.clone.call_count) 1079 (self.mock_rbd.Image.return_value.unprotect_snap 1080 .assert_called_once_with('.'.join( 1081 (self.volume_b.name, 'clone_snap')))) 1082 (self.mock_rbd.Image.return_value.remove_snap 1083 .assert_called_once_with('.'.join( 1084 (self.volume_b.name, 'clone_snap')))) 1085 self.assertEqual( 1086 1, self.mock_rbd.Image.return_value.close.call_count) 1087 mock_enable_repl.assert_not_called() 1088 1089 @common_mocks 1090 def test_good_locations(self): 1091 locations = ['rbd://fsid/pool/image/snap', 1092 'rbd://%2F/%2F/%2F/%2F', ] 1093 map(self.driver._parse_location, locations) 1094 1095 @common_mocks 1096 def test_bad_locations(self): 1097 locations = ['rbd://image', 1098 'http://path/to/somewhere/else', 1099 'rbd://image/extra', 1100 'rbd://image/', 1101 'rbd://fsid/pool/image/', 1102 'rbd://fsid/pool/image/snap/', 1103 'rbd://///', ] 1104 for loc in locations: 1105 self.assertRaises(exception.ImageUnacceptable, 1106 self.driver._parse_location, 1107 loc) 1108 self.assertFalse( 1109 self.driver._is_cloneable(loc, {'disk_format': 'raw'})) 1110 1111 @common_mocks 1112 def test_cloneable(self): 1113 with mock.patch.object(self.driver, '_get_fsid') as mock_get_fsid: 1114 mock_get_fsid.return_value = 'abc' 1115 location = 'rbd://abc/pool/image/snap' 1116 info = {'disk_format': 'raw'} 1117 self.assertTrue(self.driver._is_cloneable(location, info)) 1118 self.assertTrue(mock_get_fsid.called) 1119 1120 @common_mocks 1121 def test_uncloneable_different_fsid(self): 1122 with mock.patch.object(self.driver, '_get_fsid') as mock_get_fsid: 1123 mock_get_fsid.return_value = 'abc' 1124 location = 'rbd://def/pool/image/snap' 1125 self.assertFalse( 1126 self.driver._is_cloneable(location, {'disk_format': 'raw'})) 1127 self.assertTrue(mock_get_fsid.called) 1128 1129 @common_mocks 1130 def test_uncloneable_unreadable(self): 1131 with mock.patch.object(self.driver, '_get_fsid') as mock_get_fsid: 1132 mock_get_fsid.return_value = 'abc' 1133 location = 'rbd://abc/pool/image/snap' 1134 1135 self.driver.rbd.Error = Exception 1136 self.mock_proxy.side_effect = Exception 1137 1138 args = [location, {'disk_format': 'raw'}] 1139 self.assertFalse(self.driver._is_cloneable(*args)) 1140 self.assertEqual(1, self.mock_proxy.call_count) 1141 self.assertTrue(mock_get_fsid.called) 1142 1143 @common_mocks 1144 def test_uncloneable_bad_format(self): 1145 with mock.patch.object(self.driver, '_get_fsid') as mock_get_fsid: 1146 mock_get_fsid.return_value = 'abc' 1147 location = 'rbd://abc/pool/image/snap' 1148 formats = ['qcow2', 'vmdk', 'vdi'] 1149 for f in formats: 1150 self.assertFalse( 1151 self.driver._is_cloneable(location, {'disk_format': f})) 1152 self.assertTrue(mock_get_fsid.called) 1153 1154 def _copy_image(self): 1155 with mock.patch.object(tempfile, 'NamedTemporaryFile'): 1156 with mock.patch.object(os.path, 'exists') as mock_exists: 1157 mock_exists.return_value = True 1158 with mock.patch.object(image_utils, 'fetch_to_raw'): 1159 with mock.patch.object(self.driver, 'delete_volume'): 1160 with mock.patch.object(self.driver, '_resize'): 1161 mock_image_service = mock.MagicMock() 1162 args = [None, self.volume_a, 1163 mock_image_service, None] 1164 self.driver.copy_image_to_volume(*args) 1165 1166 @common_mocks 1167 def test_copy_image_no_volume_tmp(self): 1168 self.cfg.image_conversion_dir = None 1169 self._copy_image() 1170 1171 @common_mocks 1172 def test_copy_image_volume_tmp(self): 1173 self.cfg.image_conversion_dir = '/var/run/cinder/tmp' 1174 self._copy_image() 1175 1176 @ddt.data(True, False) 1177 @common_mocks 1178 @mock.patch('cinder.volume.drivers.rbd.RBDDriver._get_usage_info') 1179 @mock.patch('cinder.volume.drivers.rbd.RBDDriver._get_pool_stats') 1180 def test_update_volume_stats(self, replication_enabled, stats_mock, 1181 usage_mock): 1182 stats_mock.return_value = (mock.sentinel.free_capacity_gb, 1183 mock.sentinel.total_capacity_gb) 1184 usage_mock.return_value = mock.sentinel.provisioned_capacity_gb 1185 1186 expected_fsid = 'abc' 1187 expected_location_info = ('nondefault:%s:%s:%s:rbd' % 1188 (self.cfg.rbd_ceph_conf, expected_fsid, 1189 self.cfg.rbd_user)) 1190 expected = dict( 1191 volume_backend_name='RBD', 1192 replication_enabled=replication_enabled, 1193 vendor_name='Open Source', 1194 driver_version=self.driver.VERSION, 1195 storage_protocol='ceph', 1196 total_capacity_gb=mock.sentinel.total_capacity_gb, 1197 free_capacity_gb=mock.sentinel.free_capacity_gb, 1198 reserved_percentage=0, 1199 thin_provisioning_support=True, 1200 provisioned_capacity_gb=mock.sentinel.provisioned_capacity_gb, 1201 max_over_subscription_ratio=1.0, 1202 multiattach=False, 1203 location_info=expected_location_info) 1204 1205 if replication_enabled: 1206 targets = [{'backend_id': 'secondary-backend'}, 1207 {'backend_id': 'tertiary-backend'}] 1208 with mock.patch.object(self.driver.configuration, 'safe_get', 1209 return_value=targets): 1210 self.driver._do_setup_replication() 1211 expected['replication_targets'] = [t['backend_id']for t in targets] 1212 expected['replication_targets'].append('default') 1213 1214 my_safe_get = MockDriverConfig(rbd_exclusive_cinder_pool=False) 1215 self.mock_object(self.driver.configuration, 'safe_get', 1216 my_safe_get) 1217 1218 with mock.patch.object(self.driver, '_get_fsid') as mock_get_fsid: 1219 mock_get_fsid.return_value = expected_fsid 1220 actual = self.driver.get_volume_stats(True) 1221 self.assertDictEqual(expected, actual) 1222 1223 @common_mocks 1224 @mock.patch('cinder.volume.drivers.rbd.RBDDriver._get_usage_info') 1225 @mock.patch('cinder.volume.drivers.rbd.RBDDriver._get_pool_stats') 1226 def test_update_volume_stats_exclusive_pool(self, stats_mock, usage_mock): 1227 stats_mock.return_value = (mock.sentinel.free_capacity_gb, 1228 mock.sentinel.total_capacity_gb) 1229 1230 expected_fsid = 'abc' 1231 expected_location_info = ('nondefault:%s:%s:%s:rbd' % 1232 (self.cfg.rbd_ceph_conf, expected_fsid, 1233 self.cfg.rbd_user)) 1234 expected = dict( 1235 volume_backend_name='RBD', 1236 replication_enabled=False, 1237 vendor_name='Open Source', 1238 driver_version=self.driver.VERSION, 1239 storage_protocol='ceph', 1240 total_capacity_gb=mock.sentinel.total_capacity_gb, 1241 free_capacity_gb=mock.sentinel.free_capacity_gb, 1242 reserved_percentage=0, 1243 thin_provisioning_support=True, 1244 max_over_subscription_ratio=1.0, 1245 multiattach=False, 1246 location_info=expected_location_info) 1247 1248 my_safe_get = MockDriverConfig(rbd_exclusive_cinder_pool=True) 1249 self.mock_object(self.driver.configuration, 'safe_get', 1250 my_safe_get) 1251 1252 with mock.patch.object(self.driver, '_get_fsid', 1253 return_value=expected_fsid): 1254 actual = self.driver.get_volume_stats(True) 1255 1256 self.assertDictEqual(expected, actual) 1257 usage_mock.assert_not_called() 1258 1259 @common_mocks 1260 @mock.patch('cinder.volume.drivers.rbd.RBDDriver._get_usage_info') 1261 @mock.patch('cinder.volume.drivers.rbd.RBDDriver._get_pool_stats') 1262 def test_update_volume_stats_error(self, stats_mock, usage_mock): 1263 my_safe_get = MockDriverConfig(rbd_exclusive_cinder_pool=False) 1264 self.mock_object(self.driver.configuration, 'safe_get', 1265 my_safe_get) 1266 1267 expected_fsid = 'abc' 1268 expected_location_info = ('nondefault:%s:%s:%s:rbd' % 1269 (self.cfg.rbd_ceph_conf, expected_fsid, 1270 self.cfg.rbd_user)) 1271 expected = dict(volume_backend_name='RBD', 1272 replication_enabled=False, 1273 vendor_name='Open Source', 1274 driver_version=self.driver.VERSION, 1275 storage_protocol='ceph', 1276 total_capacity_gb='unknown', 1277 free_capacity_gb='unknown', 1278 reserved_percentage=0, 1279 multiattach=False, 1280 max_over_subscription_ratio=1.0, 1281 thin_provisioning_support=True, 1282 location_info=expected_location_info) 1283 1284 with mock.patch.object(self.driver, '_get_fsid') as mock_get_fsid: 1285 mock_get_fsid.return_value = expected_fsid 1286 actual = self.driver.get_volume_stats(True) 1287 self.assertDictEqual(expected, actual) 1288 1289 @ddt.data( 1290 # Normal case, no quota and dynamic total 1291 {'free_capacity': 27.0, 'total_capacity': 28.44}, 1292 # No quota and static total 1293 {'dynamic_total': False, 1294 'free_capacity': 27.0, 'total_capacity': 59.96}, 1295 # Quota and dynamic total 1296 {'quota_max_bytes': 3221225472, 'max_avail': 1073741824, 1297 'free_capacity': 1, 'total_capacity': 2.44}, 1298 # Quota and static total 1299 {'quota_max_bytes': 3221225472, 'max_avail': 1073741824, 1300 'dynamic_total': False, 1301 'free_capacity': 1, 'total_capacity': 3.00}, 1302 # Quota and dynamic total when free would be negative 1303 {'quota_max_bytes': 1073741824, 1304 'free_capacity': 0, 'total_capacity': 1.44}, 1305 ) 1306 @ddt.unpack 1307 @common_mocks 1308 def test_get_pool(self, free_capacity, total_capacity, 1309 max_avail=28987613184, quota_max_bytes=0, 1310 dynamic_total=True): 1311 client = self.mock_client.return_value 1312 client.__enter__.return_value = client 1313 client.cluster.mon_command.side_effect = [ 1314 (0, '{"stats":{"total_bytes":64385286144,' 1315 '"total_used_bytes":3289628672,"total_avail_bytes":61095657472},' 1316 '"pools":[{"name":"rbd","id":2,"stats":{"kb_used":1510197,' 1317 '"bytes_used":1546440971,"max_avail":%s,"objects":412}},' 1318 '{"name":"volumes","id":3,"stats":{"kb_used":0,"bytes_used":0,' 1319 '"max_avail":28987613184,"objects":0}}]}\n' % max_avail, ''), 1320 (0, '{"pool_name":"volumes","pool_id":4,"quota_max_objects":0,' 1321 '"quota_max_bytes":%s}\n' % quota_max_bytes, ''), 1322 ] 1323 with mock.patch.object(self.driver.configuration, 'safe_get', 1324 return_value=dynamic_total): 1325 result = self.driver._get_pool_stats() 1326 client.cluster.mon_command.assert_has_calls([ 1327 mock.call('{"prefix":"df", "format":"json"}', ''), 1328 mock.call('{"prefix":"osd pool get-quota", "pool": "rbd",' 1329 ' "format":"json"}', ''), 1330 ]) 1331 self.assertEqual((free_capacity, total_capacity), result) 1332 1333 @common_mocks 1334 def test_get_pool_bytes(self, free_capacity=1.56, total_capacity=3.0, 1335 dynamic_total=True): 1336 """test for mon_commands returning bytes instead of str""" 1337 client = self.mock_client.return_value 1338 client.__enter__.return_value = client 1339 client.cluster.mon_command.side_effect = [ 1340 (0, b'{"stats":{"total_bytes":64385286144,' 1341 b'"total_used_bytes":3289628672,"total_avail_bytes":61095657472},' 1342 b'"pools":[{"name":"rbd","id":2,"stats":{"kb_used":1510197,' 1343 b'"bytes_used":1546440971,"max_avail":2897613184,"objects":412}},' 1344 b'{"name":"volumes","id":3,"stats":{"kb_used":0,"bytes_used":0,' 1345 b'"max_avail":28987613184,"objects":0}}]}\n', ''), 1346 (0, b'{"pool_name":"volumes","pool_id":4,"quota_max_objects":0,' 1347 b'"quota_max_bytes":3221225472}\n', ''), 1348 ] 1349 with mock.patch.object(self.driver.configuration, 'safe_get', 1350 return_value=dynamic_total): 1351 result = self.driver._get_pool_stats() 1352 client.cluster.mon_command.assert_has_calls([ 1353 mock.call('{"prefix":"df", "format":"json"}', ''), 1354 mock.call('{"prefix":"osd pool get-quota", "pool": "rbd",' 1355 ' "format":"json"}', ''), 1356 ]) 1357 self.assertEqual((free_capacity, total_capacity), result) 1358 1359 @common_mocks 1360 def test_get_pool_stats_failure(self): 1361 client = self.mock_client.return_value 1362 client.__enter__.return_value = client 1363 client.cluster.mon_command.return_value = (-1, '', '') 1364 1365 result = self.driver._get_pool_stats() 1366 self.assertEqual(('unknown', 'unknown'), result) 1367 1368 @common_mocks 1369 def test_get_mon_addrs(self): 1370 with mock.patch.object(self.driver, '_execute') as mock_execute: 1371 mock_execute.return_value = (CEPH_MON_DUMP, '') 1372 hosts = ['::1', '::1', '::1', '127.0.0.1', 'example.com'] 1373 ports = ['6789', '6790', '6791', '6792', '6791'] 1374 self.assertEqual((hosts, ports), self.driver._get_mon_addrs()) 1375 1376 @common_mocks 1377 def _initialize_connection_helper(self, expected, hosts, ports): 1378 1379 with mock.patch.object(self.driver, '_get_mon_addrs') as \ 1380 mock_get_mon_addrs: 1381 mock_get_mon_addrs.return_value = (hosts, ports) 1382 actual = self.driver.initialize_connection(self.volume_a, None) 1383 self.assertDictEqual(expected, actual) 1384 self.assertTrue(mock_get_mon_addrs.called) 1385 1386 @mock.patch.object(cinder.volume.drivers.rbd.RBDDriver, 1387 '_get_keyring_contents') 1388 def test_initialize_connection(self, mock_keyring): 1389 hosts = ['::1', '::1', '::1', '127.0.0.1', 'example.com'] 1390 ports = ['6789', '6790', '6791', '6792', '6791'] 1391 1392 keyring_data = "[client.cinder]\n key = test\n" 1393 mock_keyring.return_value = keyring_data 1394 1395 expected = { 1396 'driver_volume_type': 'rbd', 1397 'data': { 1398 'name': '%s/%s' % (self.cfg.rbd_pool, 1399 self.volume_a.name), 1400 'hosts': hosts, 1401 'ports': ports, 1402 'cluster_name': self.cfg.rbd_cluster_name, 1403 'auth_enabled': True, 1404 'auth_username': self.cfg.rbd_user, 1405 'secret_type': 'ceph', 1406 'secret_uuid': None, 1407 'volume_id': self.volume_a.id, 1408 'discard': True, 1409 'keyring': keyring_data, 1410 } 1411 } 1412 self._initialize_connection_helper(expected, hosts, ports) 1413 1414 # Check how it will work with empty keyring path 1415 mock_keyring.return_value = None 1416 expected['data']['keyring'] = None 1417 self._initialize_connection_helper(expected, hosts, ports) 1418 1419 def test__get_keyring_contents_no_config_file(self): 1420 self.cfg.rbd_keyring_conf = '' 1421 self.assertIsNone(self.driver._get_keyring_contents()) 1422 1423 @mock.patch('os.path.isfile') 1424 def test__get_keyring_contents_read_file(self, mock_isfile): 1425 mock_isfile.return_value = True 1426 keyring_data = "[client.cinder]\n key = test\n" 1427 mockopen = mock.mock_open(read_data=keyring_data) 1428 mockopen.return_value.__exit__ = mock.Mock() 1429 with mock.patch('cinder.volume.drivers.rbd.open', mockopen, 1430 create=True): 1431 self.assertEqual(self.driver._get_keyring_contents(), keyring_data) 1432 1433 @mock.patch('os.path.isfile') 1434 def test__get_keyring_contents_raise_error(self, mock_isfile): 1435 mock_isfile.return_value = True 1436 mockopen = mock.mock_open() 1437 mockopen.return_value.__exit__ = mock.Mock() 1438 with mock.patch('cinder.volume.drivers.rbd.open', mockopen, 1439 create=True) as mock_keyring_file: 1440 mock_keyring_file.side_effect = IOError 1441 self.assertIsNone(self.driver._get_keyring_contents()) 1442 1443 @ddt.data({'rbd_chunk_size': 1, 'order': 20}, 1444 {'rbd_chunk_size': 8, 'order': 23}, 1445 {'rbd_chunk_size': 32, 'order': 25}) 1446 @ddt.unpack 1447 @common_mocks 1448 @mock.patch.object(driver.RBDDriver, '_enable_replication') 1449 def test_clone(self, mock_enable_repl, rbd_chunk_size, order): 1450 self.cfg.rbd_store_chunk_size = rbd_chunk_size 1451 src_pool = u'images' 1452 src_image = u'image-name' 1453 src_snap = u'snapshot-name' 1454 1455 client_stack = [] 1456 1457 def mock__enter__(inst): 1458 def _inner(): 1459 client_stack.append(inst) 1460 return inst 1461 return _inner 1462 1463 client = self.mock_client.return_value 1464 # capture both rados client used to perform the clone 1465 client.__enter__.side_effect = mock__enter__(client) 1466 1467 res = self.driver._clone(self.volume_a, src_pool, src_image, src_snap) 1468 1469 self.assertEqual({}, res) 1470 1471 args = [client_stack[0].ioctx, str(src_image), str(src_snap), 1472 client_stack[1].ioctx, str(self.volume_a.name)] 1473 kwargs = {'features': client.features, 1474 'order': order} 1475 self.mock_rbd.RBD.return_value.clone.assert_called_once_with( 1476 *args, **kwargs) 1477 self.assertEqual(2, client.__enter__.call_count) 1478 mock_enable_repl.assert_not_called() 1479 1480 @common_mocks 1481 @mock.patch.object(driver.RBDDriver, '_enable_replication') 1482 def test_clone_replicated(self, mock_enable_repl): 1483 rbd_chunk_size = 1 1484 order = 20 1485 self.volume_a.volume_type = fake_volume.fake_volume_type_obj( 1486 self.context, 1487 id=fake.VOLUME_TYPE_ID, 1488 extra_specs={'replication_enabled': '<is> True'}) 1489 1490 expected_update = { 1491 'replication_status': 'enabled', 1492 'replication_driver_data': '{"had_journaling": false}' 1493 } 1494 mock_enable_repl.return_value = expected_update 1495 1496 self.cfg.rbd_store_chunk_size = rbd_chunk_size 1497 src_pool = u'images' 1498 src_image = u'image-name' 1499 src_snap = u'snapshot-name' 1500 1501 client_stack = [] 1502 1503 def mock__enter__(inst): 1504 def _inner(): 1505 client_stack.append(inst) 1506 return inst 1507 return _inner 1508 1509 client = self.mock_client.return_value 1510 # capture both rados client used to perform the clone 1511 client.__enter__.side_effect = mock__enter__(client) 1512 1513 res = self.driver._clone(self.volume_a, src_pool, src_image, src_snap) 1514 1515 self.assertEqual(expected_update, res) 1516 mock_enable_repl.assert_called_once_with(self.volume_a) 1517 1518 args = [client_stack[0].ioctx, str(src_image), str(src_snap), 1519 client_stack[1].ioctx, str(self.volume_a.name)] 1520 kwargs = {'features': client.features, 1521 'order': order} 1522 self.mock_rbd.RBD.return_value.clone.assert_called_once_with( 1523 *args, **kwargs) 1524 self.assertEqual(2, client.__enter__.call_count) 1525 1526 @ddt.data({}, 1527 {'replication_status': 'enabled', 1528 'replication_driver_data': '{"had_journaling": false}'}) 1529 @common_mocks 1530 @mock.patch.object(driver.RBDDriver, '_is_cloneable', return_value=True) 1531 def test_clone_image_replication(self, return_value, mock_cloneable): 1532 mock_clone = self.mock_object(self.driver, '_clone', 1533 return_value=return_value) 1534 image_loc = ('rbd://fee/fi/fo/fum', None) 1535 image_meta = {'disk_format': 'raw', 'id': 'id.foo'} 1536 1537 res = self.driver.clone_image(self.context, 1538 self.volume_a, 1539 image_loc, 1540 image_meta, 1541 mock.Mock()) 1542 1543 expected = return_value.copy() 1544 expected['provider_location'] = None 1545 self.assertEqual((expected, True), res) 1546 1547 mock_clone.assert_called_once_with(self.volume_a, 'fi', 'fo', 'fum') 1548 1549 @common_mocks 1550 @mock.patch.object(driver.RBDDriver, '_clone', 1551 return_value=mock.sentinel.volume_update) 1552 @mock.patch.object(driver.RBDDriver, '_resize', mock.Mock()) 1553 def test_create_vol_from_snap_replication(self, mock_clone): 1554 self.cfg.rbd_flatten_volume_from_snapshot = False 1555 snapshot = mock.Mock() 1556 1557 res = self.driver.create_volume_from_snapshot(self.volume_a, snapshot) 1558 1559 self.assertEqual(mock.sentinel.volume_update, res) 1560 mock_clone.assert_called_once_with(self.volume_a, 1561 self.cfg.rbd_pool, 1562 snapshot.volume_name, 1563 snapshot.name) 1564 1565 @common_mocks 1566 def test_extend_volume(self): 1567 fake_size = '20' 1568 size = int(fake_size) * units.Gi 1569 with mock.patch.object(self.driver, '_resize') as mock_resize: 1570 self.driver.extend_volume(self.volume_a, fake_size) 1571 mock_resize.assert_called_once_with(self.volume_a, size=size) 1572 1573 @ddt.data(False, True) 1574 @common_mocks 1575 def test_retype(self, enabled): 1576 """Test retyping a non replicated volume. 1577 1578 We will test on a system that doesn't have replication enabled and on 1579 one that hast it enabled. 1580 """ 1581 self.driver._is_replication_enabled = enabled 1582 if enabled: 1583 expect = {'replication_status': fields.ReplicationStatus.DISABLED} 1584 else: 1585 expect = None 1586 context = {} 1587 diff = {'encryption': {}, 1588 'extra_specs': {}} 1589 updates = {'name': 'testvolume', 1590 'host': 'currenthost', 1591 'id': fake.VOLUME_ID} 1592 fake_type = fake_volume.fake_volume_type_obj(context) 1593 volume = fake_volume.fake_volume_obj(context, **updates) 1594 volume.volume_type = None 1595 1596 # The hosts have been checked same before rbd.retype 1597 # is called. 1598 # RBD doesn't support multiple pools in a driver. 1599 host = {'host': 'currenthost'} 1600 self.assertEqual((True, expect), 1601 self.driver.retype(context, volume, 1602 fake_type, diff, host)) 1603 1604 # The encryptions have been checked as same before rbd.retype 1605 # is called. 1606 diff['encryption'] = {} 1607 self.assertEqual((True, expect), 1608 self.driver.retype(context, volume, 1609 fake_type, diff, host)) 1610 1611 # extra_specs changes are supported. 1612 diff['extra_specs'] = {'non-empty': 'non-empty'} 1613 self.assertEqual((True, expect), 1614 self.driver.retype(context, volume, 1615 fake_type, diff, host)) 1616 diff['extra_specs'] = {} 1617 1618 self.assertEqual((True, expect), 1619 self.driver.retype(context, volume, 1620 fake_type, diff, host)) 1621 1622 @ddt.data({'old_replicated': False, 'new_replicated': False}, 1623 {'old_replicated': False, 'new_replicated': True}, 1624 {'old_replicated': True, 'new_replicated': False}, 1625 {'old_replicated': True, 'new_replicated': True}) 1626 @ddt.unpack 1627 @common_mocks 1628 @mock.patch.object(driver.RBDDriver, '_disable_replication', 1629 return_value=mock.sentinel.disable_replication) 1630 @mock.patch.object(driver.RBDDriver, '_enable_replication', 1631 return_value=mock.sentinel.enable_replication) 1632 def test_retype_replicated(self, mock_disable, mock_enable, old_replicated, 1633 new_replicated): 1634 """Test retyping a non replicated volume. 1635 1636 We will test on a system that doesn't have replication enabled and on 1637 one that hast it enabled. 1638 """ 1639 self.driver._is_replication_enabled = True 1640 replicated_type = fake_volume.fake_volume_type_obj( 1641 self.context, 1642 id=fake.VOLUME_TYPE_ID, 1643 extra_specs={'replication_enabled': '<is> True'}) 1644 1645 self.volume_a.volume_type = replicated_type if old_replicated else None 1646 1647 if new_replicated: 1648 new_type = replicated_type 1649 if old_replicated: 1650 update = None 1651 else: 1652 update = mock.sentinel.enable_replication 1653 else: 1654 new_type = fake_volume.fake_volume_type_obj( 1655 self.context, 1656 id=fake.VOLUME_TYPE2_ID), 1657 if old_replicated: 1658 update = mock.sentinel.disable_replication 1659 else: 1660 update = {'replication_status': 1661 fields.ReplicationStatus.DISABLED} 1662 1663 res = self.driver.retype(self.context, self.volume_a, new_type, None, 1664 None) 1665 self.assertEqual((True, update), res) 1666 1667 @common_mocks 1668 def test_update_migrated_volume(self): 1669 client = self.mock_client.return_value 1670 client.__enter__.return_value = client 1671 1672 with mock.patch.object(self.driver.rbd.RBD(), 'rename') as mock_rename: 1673 context = {} 1674 mock_rename.return_value = 0 1675 model_update = self.driver.update_migrated_volume(context, 1676 self.volume_a, 1677 self.volume_b, 1678 'available') 1679 mock_rename.assert_called_with(client.ioctx, 1680 'volume-%s' % self.volume_b.id, 1681 'volume-%s' % self.volume_a.id) 1682 self.assertEqual({'_name_id': None, 1683 'provider_location': None}, model_update) 1684 1685 def test_rbd_volume_proxy_init(self): 1686 mock_driver = mock.Mock(name='driver') 1687 mock_driver._connect_to_rados.return_value = (None, None) 1688 with driver.RBDVolumeProxy(mock_driver, self.volume_a.name): 1689 self.assertEqual(1, mock_driver._connect_to_rados.call_count) 1690 self.assertFalse(mock_driver._disconnect_from_rados.called) 1691 1692 self.assertEqual(1, mock_driver._disconnect_from_rados.call_count) 1693 1694 mock_driver.reset_mock() 1695 1696 snap = u'snapshot-name' 1697 with driver.RBDVolumeProxy(mock_driver, self.volume_a.name, 1698 snapshot=snap): 1699 self.assertEqual(1, mock_driver._connect_to_rados.call_count) 1700 self.assertFalse(mock_driver._disconnect_from_rados.called) 1701 1702 self.assertEqual(1, mock_driver._disconnect_from_rados.call_count) 1703 1704 def test_rbd_volume_proxy_external_conn(self): 1705 mock_driver = mock.Mock(name='driver') 1706 mock_driver._connect_to_rados.return_value = (None, None) 1707 with driver.RBDVolumeProxy(mock_driver, self.volume_a.name, 1708 client='fake_cl', ioctx='fake_io'): 1709 1710 mock_driver._connect_to_rados.assert_not_called() 1711 1712 mock_driver._disconnect_from_rados.assert_not_called() 1713 1714 def test_rbd_volume_proxy_external_conn_no_iocxt(self): 1715 mock_driver = mock.Mock(name='driver') 1716 mock_driver._connect_to_rados.return_value = ('fake_cl', 'fake_io') 1717 with driver.RBDVolumeProxy(mock_driver, self.volume_a.name, 1718 client='fake_cl', pool='vol_pool'): 1719 mock_driver._connect_to_rados.assert_called_once_with( 1720 'vol_pool', None, None) 1721 1722 mock_driver._disconnect_from_rados.assert_called_once_with( 1723 'fake_cl', 'fake_io') 1724 1725 def test_rbd_volume_proxy_external_conn_error(self): 1726 mock_driver = mock.Mock(name='driver') 1727 mock_driver._connect_to_rados.return_value = (None, None) 1728 1729 class RBDError(Exception): 1730 pass 1731 1732 mock_driver.rbd.Error = RBDError 1733 mock_driver.rbd.Image.side_effect = RBDError() 1734 1735 self.assertRaises(RBDError, driver.RBDVolumeProxy, 1736 mock_driver, self.volume_a.name, 1737 client='fake_cl', ioctx='fake_io') 1738 1739 mock_driver._connect_to_rados.assert_not_called() 1740 mock_driver._disconnect_from_rados.assert_not_called() 1741 1742 def test_rbd_volume_proxy_conn_error(self): 1743 mock_driver = mock.Mock(name='driver') 1744 mock_driver._connect_to_rados.return_value = ( 1745 'fake_client', 'fake_ioctx') 1746 1747 class RBDError(Exception): 1748 pass 1749 1750 mock_driver.rbd.Error = RBDError 1751 mock_driver.rbd.Image.side_effect = RBDError() 1752 1753 self.assertRaises(RBDError, driver.RBDVolumeProxy, 1754 mock_driver, self.volume_a.name, 1755 pool='fake-volumes') 1756 1757 mock_driver._connect_to_rados.assert_called_once_with( 1758 'fake-volumes', None, None) 1759 mock_driver._disconnect_from_rados.assert_called_once_with( 1760 'fake_client', 'fake_ioctx') 1761 1762 @common_mocks 1763 def test_connect_to_rados(self): 1764 # Default 1765 self.cfg.rados_connect_timeout = -1 1766 1767 self.mock_rados.Rados.return_value.open_ioctx.return_value = \ 1768 self.mock_rados.Rados.return_value.ioctx 1769 1770 # default configured pool 1771 ret = self.driver._connect_to_rados() 1772 self.assertTrue(self.mock_rados.Rados.return_value.connect.called) 1773 # Expect no timeout if default is used 1774 self.mock_rados.Rados.return_value.connect.assert_called_once_with() 1775 self.assertTrue(self.mock_rados.Rados.return_value.open_ioctx.called) 1776 self.assertEqual(self.mock_rados.Rados.return_value.ioctx, ret[1]) 1777 self.mock_rados.Rados.return_value.open_ioctx.assert_called_with( 1778 self.cfg.rbd_pool) 1779 conf_set = self.mock_rados.Rados.return_value.conf_set 1780 conf_set.assert_not_called() 1781 1782 # different pool 1783 ret = self.driver._connect_to_rados('alt_pool') 1784 self.assertTrue(self.mock_rados.Rados.return_value.connect.called) 1785 self.assertTrue(self.mock_rados.Rados.return_value.open_ioctx.called) 1786 self.assertEqual(self.mock_rados.Rados.return_value.ioctx, ret[1]) 1787 self.mock_rados.Rados.return_value.open_ioctx.assert_called_with( 1788 'alt_pool') 1789 1790 # With timeout 1791 self.cfg.rados_connect_timeout = 1 1792 self.mock_rados.Rados.return_value.connect.reset_mock() 1793 self.driver._connect_to_rados() 1794 conf_set.assert_has_calls((mock.call('rados_osd_op_timeout', '1'), 1795 mock.call('rados_mon_op_timeout', '1'), 1796 mock.call('client_mount_timeout', '1'))) 1797 self.mock_rados.Rados.return_value.connect.assert_called_once_with() 1798 1799 # error 1800 self.mock_rados.Rados.return_value.open_ioctx.reset_mock() 1801 self.mock_rados.Rados.return_value.shutdown.reset_mock() 1802 self.mock_rados.Rados.return_value.open_ioctx.side_effect = ( 1803 self.mock_rados.Error) 1804 self.assertRaises(exception.VolumeBackendAPIException, 1805 self.driver._connect_to_rados) 1806 self.assertTrue(self.mock_rados.Rados.return_value.open_ioctx.called) 1807 self.assertEqual( 1808 3, self.mock_rados.Rados.return_value.shutdown.call_count) 1809 1810 @common_mocks 1811 def test_failover_host_no_replication(self): 1812 self.driver._is_replication_enabled = False 1813 self.assertRaises(exception.UnableToFailOver, 1814 self.driver.failover_host, 1815 self.context, [self.volume_a], []) 1816 1817 @ddt.data(None, 'tertiary-backend') 1818 @common_mocks 1819 @mock.patch.object(driver.RBDDriver, '_get_failover_target_config') 1820 @mock.patch.object(driver.RBDDriver, '_failover_volume', autospec=True) 1821 def test_failover_host(self, secondary_id, mock_failover_vol, 1822 mock_get_cfg): 1823 mock_failover_vol.side_effect = lambda self, v, r, d, s: v 1824 self.mock_object(self.driver.configuration, 'safe_get', 1825 return_value=[{'backend_id': 'secondary-backend'}, 1826 {'backend_id': 'tertiary-backend'}]) 1827 self.driver._do_setup_replication() 1828 volumes = [self.volume_a, self.volume_b] 1829 remote = self.driver._replication_targets[1 if secondary_id else 0] 1830 mock_get_cfg.return_value = (remote['name'], remote) 1831 1832 res = self.driver.failover_host(self.context, volumes, secondary_id, 1833 []) 1834 1835 self.assertEqual((remote['name'], volumes, []), res) 1836 self.assertEqual(remote, self.driver._active_config) 1837 mock_failover_vol.assert_has_calls( 1838 [mock.call(mock.ANY, v, remote, False, 1839 fields.ReplicationStatus.FAILED_OVER) 1840 for v in volumes]) 1841 mock_get_cfg.assert_called_once_with(secondary_id) 1842 1843 @mock.patch.object(driver.RBDDriver, '_failover_volume', autospec=True) 1844 def test_failover_host_failback(self, mock_failover_vol): 1845 mock_failover_vol.side_effect = lambda self, v, r, d, s: v 1846 self.driver._active_backend_id = 'secondary-backend' 1847 self.mock_object(self.driver.configuration, 'safe_get', 1848 return_value=[{'backend_id': 'secondary-backend'}, 1849 {'backend_id': 'tertiary-backend'}]) 1850 self.driver._do_setup_replication() 1851 1852 remote = self.driver._get_target_config('default') 1853 volumes = [self.volume_a, self.volume_b] 1854 res = self.driver.failover_host(self.context, volumes, 'default', []) 1855 1856 self.assertEqual(('default', volumes, []), res) 1857 self.assertEqual(remote, self.driver._active_config) 1858 mock_failover_vol.assert_has_calls( 1859 [mock.call(mock.ANY, v, remote, False, 1860 fields.ReplicationStatus.ENABLED) 1861 for v in volumes]) 1862 1863 @mock.patch.object(driver.RBDDriver, '_failover_volume') 1864 def test_failover_host_no_more_replica_targets(self, mock_failover_vol): 1865 mock_failover_vol.side_effect = lambda w, x, y, z: w 1866 self.driver._active_backend_id = 'secondary-backend' 1867 self.mock_object(self.driver.configuration, 'safe_get', 1868 return_value=[{'backend_id': 'secondary-backend'}]) 1869 self.driver._do_setup_replication() 1870 1871 volumes = [self.volume_a, self.volume_b] 1872 self.assertRaises(exception.InvalidReplicationTarget, 1873 self.driver.failover_host, 1874 self.context, volumes, None, []) 1875 1876 @ddt.data(True, False) 1877 @mock.patch.object(driver.RBDDriver, '_exec_on_volume', 1878 side_effect=Exception) 1879 def test_failover_volume_error(self, is_demoted, mock_exec): 1880 self.volume_a.replication_driver_data = '{"had_journaling": false}' 1881 self.volume_a.volume_type = fake_volume.fake_volume_type_obj( 1882 self.context, 1883 id=fake.VOLUME_TYPE_ID, 1884 extra_specs={'replication_enabled': '<is> True'}) 1885 remote = {'name': 'name', 'user': 'user', 'conf': 'conf', 1886 'pool': 'pool'} 1887 repl_status = fields.ReplicationStatus.FAILOVER_ERROR 1888 expected = {'volume_id': self.volume_a.id, 1889 'updates': {'status': 'error', 1890 'previous_status': self.volume_a.status, 1891 'replication_status': repl_status}} 1892 res = self.driver._failover_volume( 1893 self.volume_a, remote, is_demoted, 1894 fields.ReplicationStatus.FAILED_OVER) 1895 self.assertEqual(expected, res) 1896 mock_exec.assert_called_once_with(self.volume_a.name, remote, 1897 'mirror_image_promote', 1898 not is_demoted) 1899 1900 @mock.patch.object(driver.RBDDriver, '_exec_on_volume') 1901 def test_failover_volume(self, mock_exec): 1902 self.volume_a.replication_driver_data = '{"had_journaling": false}' 1903 self.volume_a.volume_type = fake_volume.fake_volume_type_obj( 1904 self.context, 1905 id=fake.VOLUME_TYPE_ID, 1906 extra_specs={'replication_enabled': '<is> True'}) 1907 remote = {'name': 'name', 'user': 'user', 'conf': 'conf', 1908 'pool': 'pool'} 1909 repl_status = fields.ReplicationStatus.FAILED_OVER 1910 expected = {'volume_id': self.volume_a.id, 1911 'updates': {'replication_status': repl_status}} 1912 res = self.driver._failover_volume(self.volume_a, remote, True, 1913 repl_status) 1914 self.assertEqual(expected, res) 1915 mock_exec.assert_called_once_with(self.volume_a.name, remote, 1916 'mirror_image_promote', False) 1917 1918 @common_mocks 1919 def test_manage_existing_snapshot_get_size(self): 1920 with mock.patch.object(self.driver.rbd.Image(), 'size') as \ 1921 mock_rbd_image_size: 1922 with mock.patch.object(self.driver.rbd.Image(), 'close') \ 1923 as mock_rbd_image_close: 1924 mock_rbd_image_size.return_value = 2 * units.Gi 1925 existing_ref = {'source-name': self.snapshot_b.name} 1926 return_size = self.driver.manage_existing_snapshot_get_size( 1927 self.snapshot_b, 1928 existing_ref) 1929 self.assertEqual(2, return_size) 1930 mock_rbd_image_size.assert_called_once_with() 1931 mock_rbd_image_close.assert_called_once_with() 1932 1933 @common_mocks 1934 def test_manage_existing_snapshot_get_non_integer_size(self): 1935 rbd_snapshot = self.driver.rbd.Image.return_value 1936 rbd_snapshot.size.return_value = int(1.75 * units.Gi) 1937 existing_ref = {'source-name': self.snapshot_b.name} 1938 return_size = self.driver.manage_existing_snapshot_get_size( 1939 self.snapshot_b, existing_ref) 1940 self.assertEqual(2, return_size) 1941 rbd_snapshot.size.assert_called_once_with() 1942 rbd_snapshot.close.assert_called_once_with() 1943 1944 @common_mocks 1945 def test_manage_existing_snapshot_get_invalid_size(self): 1946 1947 with mock.patch.object(self.driver.rbd.Image(), 'size') as \ 1948 mock_rbd_image_size: 1949 with mock.patch.object(self.driver.rbd.Image(), 'close') \ 1950 as mock_rbd_image_close: 1951 mock_rbd_image_size.return_value = 'abcd' 1952 existing_ref = {'source-name': self.snapshot_b.name} 1953 self.assertRaises( 1954 exception.VolumeBackendAPIException, 1955 self.driver.manage_existing_snapshot_get_size, 1956 self.snapshot_b, existing_ref) 1957 1958 mock_rbd_image_size.assert_called_once_with() 1959 mock_rbd_image_close.assert_called_once_with() 1960 1961 @common_mocks 1962 def test_manage_existing_snapshot_with_invalid_rbd_image(self): 1963 self.mock_rbd.Image.side_effect = self.mock_rbd.ImageNotFound 1964 1965 invalid_snapshot = 'snapshot-invalid' 1966 invalid_ref = {'source-name': invalid_snapshot} 1967 1968 self.assertRaises(exception.ManageExistingInvalidReference, 1969 self.driver.manage_existing_snapshot_get_size, 1970 self.snapshot_b, invalid_ref) 1971 # Make sure the exception was raised 1972 self.assertEqual([self.mock_rbd.ImageNotFound], 1973 RAISED_EXCEPTIONS) 1974 1975 @common_mocks 1976 def test_manage_existing_snapshot(self): 1977 proxy = self.mock_proxy.return_value 1978 proxy.__enter__.return_value = proxy 1979 exist_snapshot = 'snapshot-exist' 1980 existing_ref = {'source-name': exist_snapshot} 1981 proxy.rename_snap.return_value = 0 1982 self.driver.manage_existing_snapshot(self.snapshot_b, existing_ref) 1983 proxy.rename_snap.assert_called_with(exist_snapshot, 1984 self.snapshot_b.name) 1985 1986 @common_mocks 1987 def test_manage_existing_snapshot_with_exist_rbd_image(self): 1988 proxy = self.mock_proxy.return_value 1989 proxy.__enter__.return_value = proxy 1990 proxy.rename_snap.side_effect = MockImageExistsException 1991 1992 exist_snapshot = 'snapshot-exist' 1993 existing_ref = {'source-name': exist_snapshot} 1994 self.assertRaises(self.mock_rbd.ImageExists, 1995 self.driver.manage_existing_snapshot, 1996 self.snapshot_b, existing_ref) 1997 1998 # Make sure the exception was raised 1999 self.assertEqual(RAISED_EXCEPTIONS, 2000 [self.mock_rbd.ImageExists]) 2001 2002 @mock.patch('cinder.volume.drivers.rbd.RBDVolumeProxy') 2003 @mock.patch('cinder.volume.drivers.rbd.RADOSClient') 2004 @mock.patch('cinder.volume.drivers.rbd.RBDDriver.RBDProxy') 2005 def test__get_usage_info(self, rbdproxy_mock, client_mock, volproxy_mock): 2006 2007 def FakeVolProxy(size_or_exc): 2008 return mock.Mock(return_value=mock.Mock( 2009 size=mock.Mock(side_effect=(size_or_exc,)))) 2010 2011 volumes = [ 2012 'volume-1', 2013 'non-existent', 2014 'non-existent', 2015 'non-cinder-volume' 2016 ] 2017 2018 client = client_mock.return_value.__enter__.return_value 2019 rbdproxy_mock.return_value.list.return_value = volumes 2020 2021 with mock.patch.object(self.driver, 'rbd', 2022 ImageNotFound=MockImageNotFoundException, 2023 OSError=MockOSErrorException): 2024 volproxy_mock.side_effect = [ 2025 mock.MagicMock(**{'__enter__': FakeVolProxy(s)}) 2026 for s in (1.0 * units.Gi, 2027 self.driver.rbd.ImageNotFound, 2028 self.driver.rbd.OSError, 2029 2.0 * units.Gi) 2030 ] 2031 total_provision = self.driver._get_usage_info() 2032 2033 rbdproxy_mock.return_value.list.assert_called_once_with(client.ioctx) 2034 2035 expected_volproxy_calls = [ 2036 mock.call(self.driver, v, read_only=True, 2037 client=client.cluster, ioctx=client.ioctx) 2038 for v in volumes] 2039 self.assertEqual(expected_volproxy_calls, volproxy_mock.mock_calls) 2040 2041 self.assertEqual(3.00, total_provision) 2042 2043 def test_migrate_volume_bad_volume_status(self): 2044 self.volume_a.status = 'in-use' 2045 ret = self.driver.migrate_volume(context, self.volume_a, None) 2046 self.assertEqual((False, None), ret) 2047 2048 def test_migrate_volume_bad_host(self): 2049 host = { 2050 'capabilities': { 2051 'storage_protocol': 'not-ceph'}} 2052 ret = self.driver.migrate_volume(context, self.volume_a, host) 2053 self.assertEqual((False, None), ret) 2054 2055 def test_migrate_volume_missing_location_info(self): 2056 host = { 2057 'capabilities': { 2058 'storage_protocol': 'ceph'}} 2059 ret = self.driver.migrate_volume(context, self.volume_a, host) 2060 self.assertEqual((False, None), ret) 2061 2062 def test_migrate_volume_invalid_location_info(self): 2063 host = { 2064 'capabilities': { 2065 'storage_protocol': 'ceph', 2066 'location_info': 'foo:bar:baz'}} 2067 ret = self.driver.migrate_volume(context, self.volume_a, host) 2068 self.assertEqual((False, None), ret) 2069 2070 @mock.patch('os_brick.initiator.linuxrbd.rbd') 2071 @mock.patch('os_brick.initiator.linuxrbd.RBDClient') 2072 def test_migrate_volume_mismatch_fsid(self, mock_client, mock_rbd): 2073 host = { 2074 'capabilities': { 2075 'storage_protocol': 'ceph', 2076 'location_info': 'nondefault:None:abc:None:rbd'}} 2077 2078 mock_client().__enter__().client.get_fsid.return_value = 'abc' 2079 2080 with mock.patch.object(self.driver, '_get_fsid') as mock_get_fsid: 2081 mock_get_fsid.return_value = 'not-abc' 2082 ret = self.driver.migrate_volume(context, self.volume_a, host) 2083 self.assertEqual((False, None), ret) 2084 2085 mock_client().__enter__().client.get_fsid.return_value = 'not-abc' 2086 2087 with mock.patch.object(self.driver, '_get_fsid') as mock_get_fsid: 2088 mock_get_fsid.return_value = 'abc' 2089 ret = self.driver.migrate_volume(context, self.volume_a, host) 2090 self.assertEqual((False, None), ret) 2091 2092 host = { 2093 'capabilities': { 2094 'storage_protocol': 'ceph', 2095 'location_info': 'nondefault:None:not-abc:None:rbd'}} 2096 2097 mock_client().__enter__().client.get_fsid.return_value = 'abc' 2098 2099 with mock.patch.object(self.driver, '_get_fsid') as mock_get_fsid: 2100 mock_get_fsid.return_value = 'abc' 2101 ret = self.driver.migrate_volume(context, self.volume_a, host) 2102 self.assertEqual((False, None), ret) 2103 2104 @mock.patch('os_brick.initiator.linuxrbd.rbd') 2105 @mock.patch('os_brick.initiator.linuxrbd.RBDClient') 2106 @mock.patch('cinder.volume.drivers.rbd.RBDVolumeProxy') 2107 def test_migrate_volume(self, mock_proxy, mock_client, mock_rbd): 2108 host = { 2109 'capabilities': { 2110 'storage_protocol': 'ceph', 2111 'location_info': 'nondefault:None:abc:None:rbd'}} 2112 2113 mock_client().__enter__().client.get_fsid.return_value = 'abc' 2114 2115 with mock.patch.object(self.driver, '_get_fsid') as mock_get_fsid, \ 2116 mock.patch.object(self.driver, 'delete_volume') as mock_delete: 2117 mock_get_fsid.return_value = 'abc' 2118 proxy = mock_proxy.return_value 2119 proxy.__enter__.return_value = proxy 2120 ret = self.driver.migrate_volume(context, self.volume_a, 2121 host) 2122 proxy.copy.assert_called_once_with( 2123 mock_client.return_value.__enter__.return_value.ioctx, 2124 self.volume_a.name) 2125 mock_delete.assert_called_once_with(self.volume_a) 2126 self.assertEqual((True, None), ret) 2127 2128 @mock.patch('tempfile.NamedTemporaryFile') 2129 @mock.patch('cinder.volume.drivers.rbd.RBDDriver.' 2130 '_check_encryption_provider', 2131 return_value={'encryption_key_id': fake.ENCRYPTION_KEY_ID}) 2132 def test_create_encrypted_volume(self, 2133 mock_check_enc_prov, 2134 mock_temp_file): 2135 class DictObj(object): 2136 # convert a dict to object w/ attributes 2137 def __init__(self, d): 2138 self.__dict__ = d 2139 2140 mock_temp_file.return_value.__enter__.side_effect = [ 2141 DictObj({'name': '/imgfile'}), 2142 DictObj({'name': '/passfile'})] 2143 2144 key_mgr = fake_keymgr.fake_api() 2145 2146 self.mock_object(castellan.key_manager, 'API', return_value=key_mgr) 2147 key_id = key_mgr.store(self.context, KeyObject()) 2148 self.volume_c.encryption_key_id = key_id 2149 2150 enc_info = {'encryption_key_id': key_id, 2151 'cipher': 'aes-xts-essiv', 2152 'key_size': 256} 2153 2154 with mock.patch('cinder.volume.drivers.rbd.RBDDriver.' 2155 '_check_encryption_provider', return_value=enc_info), \ 2156 mock.patch('cinder.volume.drivers.rbd.open') as mock_open, \ 2157 mock.patch.object(self.driver, '_execute') as mock_exec: 2158 self.driver._create_encrypted_volume(self.volume_c, 2159 self.context) 2160 mock_open.assert_called_with('/passfile', 'w') 2161 2162 mock_exec.assert_any_call( 2163 'qemu-img', 'create', '-f', 'luks', '-o', 2164 'cipher-alg=aes-256,cipher-mode=xts,ivgen-alg=essiv', 2165 '--object', 2166 'secret,id=luks_sec,format=raw,file=/passfile', 2167 '-o', 'key-secret=luks_sec', '/imgfile', '12288M') 2168 mock_exec.assert_any_call( 2169 'rbd', 'import', '--pool', 'rbd', '--order', 22, 2170 '/imgfile', self.volume_c.name) 2171 2172 @mock.patch('cinder.objects.Volume.get_by_id') 2173 @mock.patch('cinder.db.volume_glance_metadata_get', return_value={}) 2174 @common_mocks 2175 def test_get_backup_device_ceph(self, mock_gm_get, volume_get_by_id): 2176 # Use the same volume for backup (volume_a) 2177 volume_get_by_id.return_value = self.volume_a 2178 driver = self.driver 2179 2180 self._create_backup_db_entry(fake.BACKUP_ID, self.volume_a['id'], 1) 2181 backup = objects.Backup.get_by_id(self.context, fake.BACKUP_ID) 2182 backup.service = 'asdf#ceph' 2183 2184 ret = driver.get_backup_device(self.context, backup) 2185 self.assertEqual(ret, (self.volume_a, False)) 2186 2187 def _create_backup_db_entry(self, backupid, volid, size, 2188 userid=str(uuid.uuid4()), 2189 projectid=str(uuid.uuid4())): 2190 backup = {'id': backupid, 'size': size, 'volume_id': volid, 2191 'user_id': userid, 'project_id': projectid} 2192 return db.backup_create(self.context, backup)['id'] 2193 2194 @mock.patch('cinder.volume.driver.BaseVD._get_backup_volume_temp_snapshot') 2195 @mock.patch('cinder.volume.driver.BaseVD._get_backup_volume_temp_volume') 2196 @mock.patch('cinder.objects.Volume.get_by_id') 2197 @mock.patch('cinder.db.volume_glance_metadata_get', return_value={}) 2198 @common_mocks 2199 def test_get_backup_device_other(self, 2200 mock_gm_get, 2201 volume_get_by_id, 2202 mock_get_temp_volume, 2203 mock_get_temp_snapshot): 2204 # Use a cloned volume for backup (volume_b) 2205 self.volume_a.previous_status = 'in-use' 2206 mock_get_temp_volume.return_value = self.volume_b 2207 mock_get_temp_snapshot.return_value = (self.volume_b, False) 2208 2209 volume_get_by_id.return_value = self.volume_a 2210 driver = self.driver 2211 2212 self._create_backup_db_entry(fake.BACKUP_ID, self.volume_a['id'], 1) 2213 backup = objects.Backup.get_by_id(self.context, fake.BACKUP_ID) 2214 backup.service = 'asdf' 2215 2216 ret = driver.get_backup_device(self.context, backup) 2217 self.assertEqual(ret, (self.volume_b, False)) 2218 2219 2220class ManagedRBDTestCase(test_driver.BaseDriverTestCase): 2221 driver_name = "cinder.volume.drivers.rbd.RBDDriver" 2222 2223 def setUp(self): 2224 super(ManagedRBDTestCase, self).setUp() 2225 self.volume.driver.set_initialized() 2226 self.volume.stats = {'allocated_capacity_gb': 0, 2227 'pools': {}} 2228 self.called = [] 2229 2230 def _create_volume_from_image(self, expected_status, raw=False, 2231 clone_error=False): 2232 """Try to clone a volume from an image, and check status afterwards. 2233 2234 NOTE: if clone_error is True we force the image type to raw otherwise 2235 clone_image is not called 2236 """ 2237 2238 # See tests.image.fake for image types. 2239 if raw: 2240 image_id = '155d900f-4e14-4e4c-a73d-069cbf4541e6' 2241 else: 2242 image_id = 'c905cedb-7281-47e4-8a62-f26bc5fc4c77' 2243 2244 # creating volume testdata 2245 db_volume = {'display_description': 'Test Desc', 2246 'size': 20, 2247 'status': 'creating', 2248 'availability_zone': 'fake_zone', 2249 'attach_status': fields.VolumeAttachStatus.DETACHED, 2250 'host': 'dummy'} 2251 volume = objects.Volume(context=self.context, **db_volume) 2252 volume.create() 2253 2254 try: 2255 if not clone_error: 2256 self.volume.create_volume(self.context, volume, 2257 request_spec={'image_id': image_id}) 2258 else: 2259 self.assertRaises(exception.CinderException, 2260 self.volume.create_volume, 2261 self.context, 2262 volume, 2263 request_spec={'image_id': image_id}) 2264 2265 volume = objects.Volume.get_by_id(self.context, volume.id) 2266 self.assertEqual(expected_status, volume.status) 2267 finally: 2268 # cleanup 2269 volume.destroy() 2270 2271 @mock.patch('cinder.image.image_utils.check_available_space') 2272 @mock.patch.object(cinder.image.glance, 'get_default_image_service') 2273 def test_create_vol_from_image_status_available(self, mock_gdis, 2274 mock_check_space): 2275 """Clone raw image then verify volume is in available state.""" 2276 2277 def _mock_clone_image(context, volume, image_location, 2278 image_meta, image_service): 2279 return {'provider_location': None}, True 2280 2281 with mock.patch.object(self.volume.driver, 'clone_image') as \ 2282 mock_clone_image: 2283 mock_clone_image.side_effect = _mock_clone_image 2284 with mock.patch.object(self.volume.driver, 'create_volume') as \ 2285 mock_create: 2286 with mock.patch.object(create_volume.CreateVolumeFromSpecTask, 2287 '_copy_image_to_volume') as mock_copy: 2288 self._create_volume_from_image('available', raw=True) 2289 self.assertFalse(mock_copy.called) 2290 2291 self.assertTrue(mock_clone_image.called) 2292 self.assertFalse(mock_create.called) 2293 self.assertTrue(mock_gdis.called) 2294 2295 @mock.patch('cinder.image.image_utils.check_available_space') 2296 @mock.patch.object(cinder.image.glance, 'get_default_image_service') 2297 @mock.patch('cinder.image.image_utils.TemporaryImages.fetch') 2298 @mock.patch('cinder.image.image_utils.qemu_img_info') 2299 def test_create_vol_from_non_raw_image_status_available( 2300 self, mock_qemu_info, mock_fetch, mock_gdis, mock_check_space): 2301 """Clone non-raw image then verify volume is in available state.""" 2302 2303 def _mock_clone_image(context, volume, image_location, 2304 image_meta, image_service): 2305 return {'provider_location': None}, False 2306 2307 image_info = imageutils.QemuImgInfo() 2308 image_info.virtual_size = '1073741824' 2309 mock_qemu_info.return_value = image_info 2310 2311 mock_fetch.return_value = mock.MagicMock(spec=utils.get_file_spec()) 2312 with mock.patch.object(self.volume.driver, 'clone_image') as \ 2313 mock_clone_image: 2314 mock_clone_image.side_effect = _mock_clone_image 2315 with mock.patch.object(self.volume.driver, 'create_volume') as \ 2316 mock_create: 2317 with mock.patch.object(create_volume.CreateVolumeFromSpecTask, 2318 '_copy_image_to_volume') as mock_copy: 2319 self._create_volume_from_image('available', raw=False) 2320 self.assertTrue(mock_copy.called) 2321 2322 self.assertTrue(mock_clone_image.called) 2323 self.assertTrue(mock_create.called) 2324 self.assertTrue(mock_gdis.called) 2325 2326 @mock.patch('cinder.image.image_utils.check_available_space') 2327 @mock.patch.object(cinder.image.glance, 'get_default_image_service') 2328 def test_create_vol_from_image_status_error(self, mock_gdis, 2329 mock_check_space): 2330 """Fail to clone raw image then verify volume is in error state.""" 2331 with mock.patch.object(self.volume.driver, 'clone_image') as \ 2332 mock_clone_image: 2333 mock_clone_image.side_effect = exception.CinderException 2334 with mock.patch.object(self.volume.driver, 'create_volume'): 2335 with mock.patch.object(create_volume.CreateVolumeFromSpecTask, 2336 '_copy_image_to_volume') as mock_copy: 2337 self._create_volume_from_image('error', raw=True, 2338 clone_error=True) 2339 self.assertFalse(mock_copy.called) 2340 2341 self.assertTrue(mock_clone_image.called) 2342 self.assertFalse(self.volume.driver.create_volume.called) 2343 self.assertTrue(mock_gdis.called) 2344 2345 def test_clone_failure(self): 2346 driver = self.volume.driver 2347 2348 with mock.patch.object(driver, '_is_cloneable', lambda *args: False): 2349 image_loc = (mock.Mock(), None) 2350 actual = driver.clone_image(mock.Mock(), 2351 mock.Mock(), 2352 image_loc, 2353 {}, 2354 mock.Mock()) 2355 self.assertEqual(({}, False), actual) 2356 2357 self.assertEqual(({}, False), 2358 driver.clone_image('', object(), None, {}, '')) 2359 2360 def test_clone_success(self): 2361 expected = ({'provider_location': None}, True) 2362 driver = self.volume.driver 2363 2364 with mock.patch.object(self.volume.driver, '_is_cloneable') as \ 2365 mock_is_cloneable: 2366 mock_is_cloneable.return_value = True 2367 with mock.patch.object(self.volume.driver, '_clone') as \ 2368 mock_clone: 2369 with mock.patch.object(self.volume.driver, '_resize') as \ 2370 mock_resize: 2371 mock_clone.return_value = {} 2372 image_loc = ('rbd://fee/fi/fo/fum', None) 2373 2374 volume = {'name': 'vol1'} 2375 actual = driver.clone_image(mock.Mock(), 2376 volume, 2377 image_loc, 2378 {'disk_format': 'raw', 2379 'id': 'id.foo'}, 2380 mock.Mock()) 2381 2382 self.assertEqual(expected, actual) 2383 mock_clone.assert_called_once_with(volume, 2384 'fi', 'fo', 'fum') 2385 mock_resize.assert_called_once_with(volume) 2386 2387 def test_clone_multilocation_success(self): 2388 expected = ({'provider_location': None}, True) 2389 driver = self.volume.driver 2390 2391 def cloneable_side_effect(url_location, image_meta): 2392 return url_location == 'rbd://fee/fi/fo/fum' 2393 2394 with mock.patch.object(self.volume.driver, '_is_cloneable') \ 2395 as mock_is_cloneable, \ 2396 mock.patch.object(self.volume.driver, '_clone') as mock_clone, \ 2397 mock.patch.object(self.volume.driver, '_resize') \ 2398 as mock_resize: 2399 mock_is_cloneable.side_effect = cloneable_side_effect 2400 mock_clone.return_value = {} 2401 image_loc = ('rbd://bee/bi/bo/bum', 2402 [{'url': 'rbd://bee/bi/bo/bum'}, 2403 {'url': 'rbd://fee/fi/fo/fum'}]) 2404 volume = {'name': 'vol1'} 2405 image_meta = mock.sentinel.image_meta 2406 image_service = mock.sentinel.image_service 2407 2408 actual = driver.clone_image(self.context, 2409 volume, 2410 image_loc, 2411 image_meta, 2412 image_service) 2413 2414 self.assertEqual(expected, actual) 2415 self.assertEqual(2, mock_is_cloneable.call_count) 2416 mock_clone.assert_called_once_with(volume, 2417 'fi', 'fo', 'fum') 2418 mock_is_cloneable.assert_called_with('rbd://fee/fi/fo/fum', 2419 image_meta) 2420 mock_resize.assert_called_once_with(volume) 2421 2422 def test_clone_multilocation_failure(self): 2423 expected = ({}, False) 2424 driver = self.volume.driver 2425 2426 with mock.patch.object(driver, '_is_cloneable', return_value=False) \ 2427 as mock_is_cloneable, \ 2428 mock.patch.object(self.volume.driver, '_clone') as mock_clone, \ 2429 mock.patch.object(self.volume.driver, '_resize') \ 2430 as mock_resize: 2431 image_loc = ('rbd://bee/bi/bo/bum', 2432 [{'url': 'rbd://bee/bi/bo/bum'}, 2433 {'url': 'rbd://fee/fi/fo/fum'}]) 2434 2435 volume = {'name': 'vol1'} 2436 image_meta = mock.sentinel.image_meta 2437 image_service = mock.sentinel.image_service 2438 actual = driver.clone_image(self.context, 2439 volume, 2440 image_loc, 2441 image_meta, 2442 image_service) 2443 2444 self.assertEqual(expected, actual) 2445 self.assertEqual(2, mock_is_cloneable.call_count) 2446 mock_is_cloneable.assert_any_call('rbd://bee/bi/bo/bum', 2447 image_meta) 2448 mock_is_cloneable.assert_any_call('rbd://fee/fi/fo/fum', 2449 image_meta) 2450 self.assertFalse(mock_clone.called) 2451 self.assertFalse(mock_resize.called) 2452