# Copyright 2014 Cloudbase Solutions Srl # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import contextlib import copy import os import mock from cinder import exception from cinder.image import image_utils from cinder import test from cinder.volume.drivers import smbfs class SmbFsTestCase(test.TestCase): _FAKE_SHARE = '//1.2.3.4/share1' _FAKE_MNT_BASE = '/mnt' _FAKE_VOLUME_NAME = 'volume-4f711859-4928-4cb7-801a-a50c37ceaccc' _FAKE_TOTAL_SIZE = '2048' _FAKE_TOTAL_AVAILABLE = '1024' _FAKE_TOTAL_ALLOCATED = 1024 _FAKE_VOLUME = {'id': '4f711859-4928-4cb7-801a-a50c37ceaccc', 'size': 1, 'provider_location': _FAKE_SHARE, 'name': _FAKE_VOLUME_NAME, 'status': 'available'} _FAKE_MNT_POINT = os.path.join(_FAKE_MNT_BASE, 'fake_hash') _FAKE_VOLUME_PATH = os.path.join(_FAKE_MNT_POINT, _FAKE_VOLUME_NAME) _FAKE_SNAPSHOT_ID = '5g811859-4928-4cb7-801a-a50c37ceacba' _FAKE_SNAPSHOT = {'id': _FAKE_SNAPSHOT_ID, 'volume': _FAKE_VOLUME, 'status': 'available', 'volume_size': 1} _FAKE_SNAPSHOT_PATH = ( _FAKE_VOLUME_PATH + '-snapshot' + _FAKE_SNAPSHOT_ID) _FAKE_SHARE_OPTS = '-o username=Administrator,password=12345' _FAKE_OPTIONS_DICT = {'username': 'Administrator', 'password': '12345'} _FAKE_LISTDIR = [_FAKE_VOLUME_NAME, _FAKE_VOLUME_NAME + '.vhd', _FAKE_VOLUME_NAME + '.vhdx', 'fake_folder'] _FAKE_SMBFS_CONFIG = mock.MagicMock() _FAKE_SMBFS_CONFIG.smbfs_oversub_ratio = 2 _FAKE_SMBFS_CONFIG.smbfs_used_ratio = 0.5 _FAKE_SMBFS_CONFIG.smbfs_shares_config = '/fake/config/path' _FAKE_SMBFS_CONFIG.smbfs_default_volume_format = 'raw' _FAKE_SMBFS_CONFIG.smbfs_sparsed_volumes = False def setUp(self): super(SmbFsTestCase, self).setUp() smbfs.SmbfsDriver.__init__ = lambda x: None self._smbfs_driver = smbfs.SmbfsDriver() self._smbfs_driver._remotefsclient = mock.Mock() self._smbfs_driver._local_volume_dir = mock.Mock( return_value=self._FAKE_MNT_POINT) self._smbfs_driver._execute = mock.Mock() self._smbfs_driver.base = self._FAKE_MNT_BASE def test_delete_volume(self): drv = self._smbfs_driver fake_vol_info = self._FAKE_VOLUME_PATH + '.info' drv._ensure_share_mounted = mock.MagicMock() fake_ensure_mounted = drv._ensure_share_mounted drv._local_volume_dir = mock.Mock( return_value=self._FAKE_MNT_POINT) drv.get_active_image_from_info = mock.Mock( return_value=self._FAKE_VOLUME_NAME) drv._delete = mock.Mock() drv._local_path_volume_info = mock.Mock( return_value=fake_vol_info) with mock.patch('os.path.exists', lambda x: True): drv.delete_volume(self._FAKE_VOLUME) fake_ensure_mounted.assert_called_once_with(self._FAKE_SHARE) drv._delete.assert_any_call( self._FAKE_VOLUME_PATH) drv._delete.assert_any_call(fake_vol_info) def _test_setup(self, config, share_config_exists=True): fake_exists = mock.Mock(return_value=share_config_exists) fake_ensure_mounted = mock.MagicMock() self._smbfs_driver._ensure_shares_mounted = fake_ensure_mounted self._smbfs_driver.configuration = config with mock.patch('os.path.exists', fake_exists): if not (config.smbfs_shares_config and share_config_exists and config.smbfs_oversub_ratio > 0 and 0 <= config.smbfs_used_ratio <= 1): self.assertRaises(exception.SmbfsException, self._smbfs_driver.do_setup, None) else: self._smbfs_driver.do_setup(None) self.assertEqual(self._smbfs_driver.shares, {}) fake_ensure_mounted.assert_called_once() def test_setup_missing_shares_config_option(self): fake_config = copy.copy(self._FAKE_SMBFS_CONFIG) fake_config.smbfs_shares_config = None self._test_setup(fake_config, None) def test_setup_missing_shares_config_file(self): self._test_setup(self._FAKE_SMBFS_CONFIG, False) def test_setup_invlid_oversub_ratio(self): fake_config = copy.copy(self._FAKE_SMBFS_CONFIG) fake_config.smbfs_oversub_ratio = -1 self._test_setup(fake_config) def test_setup_invalid_used_ratio(self): fake_config = copy.copy(self._FAKE_SMBFS_CONFIG) fake_config.smbfs_used_ratio = -1 self._test_setup(fake_config) def _test_create_volume(self, volume_exists=False, volume_format=None): fake_method = mock.MagicMock() self._smbfs_driver.configuration = copy.copy(self._FAKE_SMBFS_CONFIG) self._smbfs_driver._set_rw_permissions_for_all = mock.MagicMock() fake_set_permissions = self._smbfs_driver._set_rw_permissions_for_all self._smbfs_driver.get_volume_format = mock.MagicMock() windows_image_format = False fake_vol_path = self._FAKE_VOLUME_PATH self._smbfs_driver.get_volume_format.return_value = volume_format if volume_format: if volume_format in ('vhd', 'vhdx'): windows_image_format = volume_format if volume_format == 'vhd': windows_image_format = 'vpc' method = '_create_windows_image' fake_vol_path += '.' + volume_format else: method = '_create_%s_file' % volume_format if volume_format == 'sparsed': self._smbfs_driver.configuration.smbfs_sparsed_volumes = ( True) else: method = '_create_regular_file' setattr(self._smbfs_driver, method, fake_method) with mock.patch('os.path.exists', new=lambda x: volume_exists): if volume_exists: self.assertRaises(exception.InvalidVolume, self._smbfs_driver._do_create_volume, self._FAKE_VOLUME) return self._smbfs_driver._do_create_volume(self._FAKE_VOLUME) if windows_image_format: fake_method.assert_called_once_with( fake_vol_path, self._FAKE_VOLUME['size'], windows_image_format) else: fake_method.assert_called_once_with( fake_vol_path, self._FAKE_VOLUME['size']) fake_set_permissions.assert_called_once_with(fake_vol_path) def test_create_existing_volume(self): self._test_create_volume(volume_exists=True) def test_create_vhdx(self): self._test_create_volume(volume_format='vhdx') def test_create_qcow2(self): self._test_create_volume(volume_format='qcow2') def test_create_sparsed(self): self._test_create_volume(volume_format='sparsed') def test_create_regular(self): self._test_create_volume() def _test_find_share(self, existing_mounted_shares=True, eligible_shares=True): if existing_mounted_shares: mounted_shares = ('fake_share1', 'fake_share2', 'fake_share3') else: mounted_shares = None self._smbfs_driver._mounted_shares = mounted_shares self._smbfs_driver._is_share_eligible = mock.Mock( return_value=eligible_shares) fake_capacity_info = ((2, 1, 5), (2, 1, 4), (2, 1, 1)) self._smbfs_driver._get_capacity_info = mock.Mock( side_effect=fake_capacity_info) if not mounted_shares: self.assertRaises(exception.SmbfsNoSharesMounted, self._smbfs_driver._find_share, self._FAKE_VOLUME['size']) elif not eligible_shares: self.assertRaises(exception.SmbfsNoSuitableShareFound, self._smbfs_driver._find_share, self._FAKE_VOLUME['size']) else: ret_value = self._smbfs_driver._find_share( self._FAKE_VOLUME['size']) # The eligible share with the minimum allocated space # will be selected self.assertEqual(ret_value, 'fake_share3') def test_find_share(self): self._test_find_share() def test_find_share_missing_mounted_shares(self): self._test_find_share(existing_mounted_shares=False) def test_find_share_missing_eligible_shares(self): self._test_find_share(eligible_shares=False) def _test_is_share_eligible(self, capacity_info, volume_size): self._smbfs_driver._get_capacity_info = mock.Mock( return_value=[float(x << 30) for x in capacity_info]) self._smbfs_driver.configuration = self._FAKE_SMBFS_CONFIG return self._smbfs_driver._is_share_eligible(self._FAKE_SHARE, volume_size) def test_share_volume_above_used_ratio(self): fake_capacity_info = (4, 1, 1) fake_volume_size = 2 ret_value = self._test_is_share_eligible(fake_capacity_info, fake_volume_size) self.assertEqual(ret_value, False) def test_eligible_share(self): fake_capacity_info = (4, 4, 0) fake_volume_size = 1 ret_value = self._test_is_share_eligible(fake_capacity_info, fake_volume_size) self.assertEqual(ret_value, True) def test_share_volume_above_oversub_ratio(self): fake_capacity_info = (4, 4, 7) fake_volume_size = 2 ret_value = self._test_is_share_eligible(fake_capacity_info, fake_volume_size) self.assertEqual(ret_value, False) def test_share_reserved_above_oversub_ratio(self): fake_capacity_info = (4, 4, 10) fake_volume_size = 1 ret_value = self._test_is_share_eligible(fake_capacity_info, fake_volume_size) self.assertEqual(ret_value, False) def test_parse_options(self): (opt_list, opt_dict) = self._smbfs_driver.parse_options( self._FAKE_SHARE_OPTS) expected_ret = ([], self._FAKE_OPTIONS_DICT) self.assertEqual(expected_ret, (opt_list, opt_dict)) def test_parse_credentials(self): fake_smb_options = r'-o user=MyDomain\Administrator,noperm' expected_flags = '-o username=Administrator,noperm' flags = self._smbfs_driver.parse_credentials(fake_smb_options) self.assertEqual(expected_flags, flags) def test_get_volume_path(self): self._smbfs_driver.get_volume_format = mock.Mock( return_value='vhd') self._smbfs_driver._local_volume_dir = mock.Mock( return_value=self._FAKE_MNT_POINT) expected = self._FAKE_VOLUME_PATH + '.vhd' ret_val = self._smbfs_driver.local_path(self._FAKE_VOLUME) self.assertEqual(expected, ret_val) def test_initialize_connection(self): self._smbfs_driver.get_active_image_from_info = mock.Mock( return_value=self._FAKE_VOLUME_NAME) self._smbfs_driver._get_mount_point_base = mock.Mock( return_value=self._FAKE_MNT_BASE) self._smbfs_driver.shares = {self._FAKE_SHARE: self._FAKE_SHARE_OPTS} self._smbfs_driver._qemu_img_info = mock.Mock( return_value=mock.Mock(file_format='raw')) fake_data = {'export': self._FAKE_SHARE, 'format': 'raw', 'name': self._FAKE_VOLUME_NAME, 'options': self._FAKE_SHARE_OPTS} expected = { 'driver_volume_type': 'smbfs', 'data': fake_data, 'mount_point_base': self._FAKE_MNT_BASE} ret_val = self._smbfs_driver.initialize_connection( self._FAKE_VOLUME, None) self.assertEqual(expected, ret_val) def _test_extend_volume(self, extend_failed=False, image_format='raw'): drv = self._smbfs_driver drv.local_path = mock.Mock( return_value=self._FAKE_VOLUME_PATH) drv._check_extend_volume_support = mock.Mock( return_value=True) drv._is_file_size_equal = mock.Mock( return_value=not extend_failed) drv._qemu_img_info = mock.Mock( return_value=mock.Mock(file_format=image_format)) with contextlib.nested( mock.patch.object(image_utils, 'resize_image'), mock.patch.object(image_utils, 'convert_image')) as ( fake_resize, fake_convert): if extend_failed: self.assertRaises(exception.ExtendVolumeError, drv._extend_volume, self._FAKE_VOLUME, mock.sentinel.new_size) else: drv._extend_volume( self._FAKE_VOLUME, mock.sentinel.new_size) if image_format in (drv._DISK_FORMAT_VHDX, drv._DISK_FORMAT_VHD_LEGACY): fake_tmp_path = self._FAKE_VOLUME_PATH + '.tmp' fake_convert.assert_any_call(self._FAKE_VOLUME_PATH, fake_tmp_path, 'raw') fake_resize.assert_called_once_with( fake_tmp_path, mock.sentinel.new_size) fake_convert.assert_any_call(fake_tmp_path, self._FAKE_VOLUME_PATH, image_format) else: fake_resize.assert_called_once_with( self._FAKE_VOLUME_PATH, mock.sentinel.new_size) def test_extend_volume(self): self._test_extend_volume() def test_extend_volume_failed(self): self._test_extend_volume(extend_failed=True) def test_extend_vhd_volume(self): self._test_extend_volume(image_format='vpc') def _test_check_extend_support(self, has_snapshots=False, is_eligible=True): self._smbfs_driver.local_path = mock.Mock( return_value=self._FAKE_VOLUME_PATH) if has_snapshots: active_file_path = self._FAKE_SNAPSHOT_PATH else: active_file_path = self._FAKE_VOLUME_PATH self._smbfs_driver.get_active_image_from_info = mock.Mock( return_value=active_file_path) self._smbfs_driver._is_share_eligible = mock.Mock( return_value=is_eligible) if has_snapshots: self.assertRaises(exception.InvalidVolume, self._smbfs_driver._check_extend_volume_support, self._FAKE_VOLUME, 2) elif not is_eligible: self.assertRaises(exception.ExtendVolumeError, self._smbfs_driver._check_extend_volume_support, self._FAKE_VOLUME, 2) else: self._smbfs_driver._check_extend_volume_support( self._FAKE_VOLUME, 2) self._smbfs_driver._is_share_eligible.assert_called_once_with( self._FAKE_SHARE, 1) def test_check_extend_support(self): self._test_check_extend_support() def test_check_extend_volume_with_snapshots(self): self._test_check_extend_support(has_snapshots=True) def test_check_extend_volume_uneligible_share(self): self._test_check_extend_support(is_eligible=False) def test_create_volume_from_in_use_snapshot(self): fake_snapshot = {'status': 'in-use'} self.assertRaises( exception.InvalidSnapshot, self._smbfs_driver.create_volume_from_snapshot, self._FAKE_VOLUME, fake_snapshot) def test_copy_volume_from_snapshot(self): drv = self._smbfs_driver fake_volume_info = {self._FAKE_SNAPSHOT_ID: 'fake_snapshot_file_name'} fake_img_info = mock.MagicMock() fake_img_info.backing_file = self._FAKE_VOLUME_NAME drv.get_volume_format = mock.Mock( return_value='raw') drv._local_path_volume_info = mock.Mock( return_value=self._FAKE_VOLUME_PATH + '.info') drv._local_volume_dir = mock.Mock( return_value=self._FAKE_MNT_POINT) drv._read_info_file = mock.Mock( return_value=fake_volume_info) drv._qemu_img_info = mock.Mock( return_value=fake_img_info) drv.local_path = mock.Mock( return_value=self._FAKE_VOLUME_PATH[:-1]) drv._extend_volume = mock.Mock() drv._set_rw_permissions_for_all = mock.Mock() with mock.patch.object(image_utils, 'convert_image') as ( fake_convert_image): drv._copy_volume_from_snapshot( self._FAKE_SNAPSHOT, self._FAKE_VOLUME, self._FAKE_VOLUME['size']) drv._extend_volume.assert_called_once_with( self._FAKE_VOLUME, self._FAKE_VOLUME['size']) fake_convert_image.assert_called_once_with( self._FAKE_VOLUME_PATH, self._FAKE_VOLUME_PATH[:-1], 'raw') def test_ensure_mounted(self): self._smbfs_driver.shares = {self._FAKE_SHARE: self._FAKE_SHARE_OPTS} self._smbfs_driver._ensure_share_mounted(self._FAKE_SHARE) self._smbfs_driver._remotefsclient.mount.assert_called_once_with( self._FAKE_SHARE, self._FAKE_SHARE_OPTS.split()) def _test_copy_image_to_volume(self, unsupported_qemu_version=False, wrong_size_after_fetch=False): drv = self._smbfs_driver vol_size_bytes = self._FAKE_VOLUME['size'] << 30 fake_image_service = mock.MagicMock() fake_image_service.show.return_value = ( {'id': 'fake_image_id', 'disk_format': 'raw'}) fake_img_info = mock.MagicMock() if wrong_size_after_fetch: fake_img_info.virtual_size = 2 * vol_size_bytes else: fake_img_info.virtual_size = vol_size_bytes if unsupported_qemu_version: qemu_version = [1, 5] else: qemu_version = [1, 7] drv.get_volume_format = mock.Mock( return_value=drv._DISK_FORMAT_VHDX) drv.local_path = mock.Mock( return_value=self._FAKE_VOLUME_PATH) drv.get_qemu_version = mock.Mock( return_value=qemu_version) drv._do_extend_volume = mock.Mock() drv.configuration = mock.MagicMock() drv.configuration.volume_dd_blocksize = ( mock.sentinel.block_size) exc = None with contextlib.nested( mock.patch.object(image_utils, 'fetch_to_volume_format'), mock.patch.object(image_utils, 'qemu_img_info')) as ( fake_fetch, fake_qemu_img_info): if wrong_size_after_fetch: exc = exception.ImageUnacceptable elif unsupported_qemu_version: exc = exception.InvalidVolume fake_qemu_img_info.return_value = fake_img_info if exc: self.assertRaises( exc, drv.copy_image_to_volume, mock.sentinel.context, self._FAKE_VOLUME, fake_image_service, mock.sentinel.image_id) else: drv.copy_image_to_volume( mock.sentinel.context, self._FAKE_VOLUME, fake_image_service, mock.sentinel.image_id) fake_fetch.assert_called_once_with( mock.sentinel.context, fake_image_service, mock.sentinel.image_id, self._FAKE_VOLUME_PATH, drv._DISK_FORMAT_VHDX, mock.sentinel.block_size) drv._do_extend_volume.assert_called_once_with( self._FAKE_VOLUME_PATH, self._FAKE_VOLUME['size'], self._FAKE_VOLUME['name']) def test_copy_image_to_volume(self): self._test_copy_image_to_volume() def test_copy_image_to_volume_wrong_size_after_fetch(self): self._test_copy_image_to_volume(wrong_size_after_fetch=True) def test_copy_image_to_volume_unsupported_qemu_version(self): self._test_copy_image_to_volume(unsupported_qemu_version=True) def test_get_capacity_info(self): fake_block_size = 4096.0 fake_total_blocks = 1024 fake_avail_blocks = 512 fake_total_allocated = fake_total_blocks * fake_block_size fake_df = ('%s %s %s' % (fake_block_size, fake_total_blocks, fake_avail_blocks), None) fake_du = (str(fake_total_allocated), None) self._smbfs_driver._get_mount_point_for_share = mock.Mock( return_value=self._FAKE_MNT_POINT) self._smbfs_driver._execute = mock.Mock( side_effect=(fake_df, fake_du)) ret_val = self._smbfs_driver._get_capacity_info(self._FAKE_SHARE) expected = (fake_block_size * fake_total_blocks, fake_block_size * fake_avail_blocks, fake_total_allocated) self.assertEqual(expected, ret_val)