Merge "Remove Linux SMBFS driver"
This commit is contained in:
commit
b34ef0558e
@ -167,7 +167,6 @@ from cinder.volume.drivers.san.hp import hpmsa_common as \
|
||||
cinder_volume_drivers_san_hp_hpmsacommon
|
||||
from cinder.volume.drivers.san import san as cinder_volume_drivers_san_san
|
||||
from cinder.volume.drivers import sheepdog as cinder_volume_drivers_sheepdog
|
||||
from cinder.volume.drivers import smbfs as cinder_volume_drivers_smbfs
|
||||
from cinder.volume.drivers import solidfire as cinder_volume_drivers_solidfire
|
||||
from cinder.volume.drivers.synology import synology_common as \
|
||||
cinder_volume_drivers_synology_synologycommon
|
||||
@ -178,6 +177,8 @@ from cinder.volume.drivers.violin import v7000_common as \
|
||||
from cinder.volume.drivers.vmware import vmdk as \
|
||||
cinder_volume_drivers_vmware_vmdk
|
||||
from cinder.volume.drivers import vzstorage as cinder_volume_drivers_vzstorage
|
||||
from cinder.volume.drivers.windows import smbfs as \
|
||||
cinder_volume_drivers_windows_smbfs
|
||||
from cinder.volume.drivers.windows import windows as \
|
||||
cinder_volume_drivers_windows_windows
|
||||
from cinder.volume.drivers import xio as cinder_volume_drivers_xio
|
||||
@ -347,7 +348,6 @@ def list_opts():
|
||||
cinder_volume_drivers_san_hp_hpmsacommon.iscsi_opts,
|
||||
cinder_volume_drivers_san_san.san_opts,
|
||||
cinder_volume_drivers_sheepdog.sheepdog_opts,
|
||||
cinder_volume_drivers_smbfs.volume_opts,
|
||||
cinder_volume_drivers_solidfire.sf_opts,
|
||||
cinder_volume_drivers_synology_synologycommon.cinder_opts,
|
||||
cinder_volume_drivers_tegile.tegile_opts,
|
||||
@ -355,6 +355,7 @@ def list_opts():
|
||||
cinder_volume_drivers_violin_v7000common.violin_opts,
|
||||
cinder_volume_drivers_vmware_vmdk.vmdk_opts,
|
||||
cinder_volume_drivers_vzstorage.vzstorage_opts,
|
||||
cinder_volume_drivers_windows_smbfs.volume_opts,
|
||||
cinder_volume_drivers_windows_windows.windows_opts,
|
||||
cinder_volume_drivers_xio.XIO_OPTS,
|
||||
cinder_volume_drivers_zadara.zadara_opts,
|
||||
|
@ -1,787 +0,0 @@
|
||||
# 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 copy
|
||||
import functools
|
||||
import os
|
||||
|
||||
import ddt
|
||||
import mock
|
||||
from oslo_utils import fileutils
|
||||
|
||||
from cinder import context
|
||||
from cinder import exception
|
||||
from cinder.image import image_utils
|
||||
from cinder import test
|
||||
from cinder.tests.unit import fake_snapshot
|
||||
from cinder.tests.unit import fake_volume
|
||||
from cinder.volume.drivers import remotefs
|
||||
from cinder.volume.drivers import smbfs
|
||||
|
||||
|
||||
def requires_allocation_data_update(expected_size):
|
||||
def wrapper(func):
|
||||
@functools.wraps(func)
|
||||
def inner(inst, *args, **kwargs):
|
||||
with mock.patch.object(
|
||||
inst._smbfs_driver,
|
||||
'update_disk_allocation_data') as fake_update:
|
||||
func(inst, *args, **kwargs)
|
||||
fake_update.assert_called_once_with(inst.volume,
|
||||
expected_size)
|
||||
return inner
|
||||
return wrapper
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class SmbFsTestCase(test.TestCase):
|
||||
|
||||
_FAKE_SHARE = '//1.2.3.4/share1'
|
||||
_FAKE_SHARE_HASH = 'db0bf952c1734092b83e8990bd321131'
|
||||
_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_MNT_POINT = os.path.join(_FAKE_MNT_BASE, _FAKE_SHARE_HASH)
|
||||
_FAKE_VOLUME_PATH = os.path.join(_FAKE_MNT_POINT, _FAKE_VOLUME_NAME)
|
||||
_FAKE_VOLUME_SIZE = 1
|
||||
_FAKE_SNAPSHOT_ID = '50811859-4928-4cb7-801a-a50c37ceacba'
|
||||
_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_ALLOCATION_DATA_PATH = os.path.join('fake_dir',
|
||||
'fake_allocation_data')
|
||||
|
||||
def setUp(self):
|
||||
super(SmbFsTestCase, self).setUp()
|
||||
|
||||
self._FAKE_SMBFS_CONFIG = mock.MagicMock(
|
||||
smbfs_oversub_ratio = 2,
|
||||
smbfs_used_ratio = 0.5,
|
||||
smbfs_shares_config = '/fake/config/path',
|
||||
smbfs_default_volume_format = 'raw',
|
||||
smbfs_sparsed_volumes = False)
|
||||
|
||||
self._smbfs_driver = smbfs.SmbfsDriver(configuration=mock.Mock())
|
||||
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
|
||||
self._smbfs_driver._alloc_info_file_path = (
|
||||
self._FAKE_ALLOCATION_DATA_PATH)
|
||||
self.context = context.get_admin_context()
|
||||
|
||||
self.volume = fake_volume.fake_volume_obj(
|
||||
self.context,
|
||||
id='4f711859-4928-4cb7-801a-a50c37ceaccc',
|
||||
size=self._FAKE_VOLUME_SIZE,
|
||||
provider_location=self._FAKE_SHARE,
|
||||
display_name=self._FAKE_VOLUME_NAME,
|
||||
status='available')
|
||||
|
||||
self.snapshot = fake_snapshot.fake_snapshot_obj(
|
||||
self.context,
|
||||
id=self._FAKE_SNAPSHOT_ID,
|
||||
status='available',
|
||||
volume_size=1)
|
||||
self.snapshot.volume = self.volume
|
||||
|
||||
def _get_fake_allocation_data(self):
|
||||
return {self._FAKE_SHARE_HASH: {
|
||||
'total_allocated': self._FAKE_TOTAL_ALLOCATED}}
|
||||
|
||||
@mock.patch.object(smbfs, 'open', create=True)
|
||||
@mock.patch('os.path.exists')
|
||||
@mock.patch.object(fileutils, 'ensure_tree')
|
||||
@mock.patch('json.load')
|
||||
def _test_setup_allocation_data(self, mock_json_load, mock_ensure_tree,
|
||||
mock_exists, mock_open,
|
||||
allocation_data_exists=False):
|
||||
mock_exists.return_value = allocation_data_exists
|
||||
self._smbfs_driver._update_allocation_data_file = mock.Mock()
|
||||
|
||||
self._smbfs_driver._setup_allocation_data()
|
||||
|
||||
if allocation_data_exists:
|
||||
fd = mock_open.return_value.__enter__.return_value
|
||||
mock_json_load.assert_called_once_with(fd)
|
||||
self.assertEqual(mock_json_load.return_value,
|
||||
self._smbfs_driver._allocation_data)
|
||||
else:
|
||||
mock_ensure_tree.assert_called_once_with(
|
||||
os.path.dirname(self._FAKE_ALLOCATION_DATA_PATH))
|
||||
update_func = self._smbfs_driver._update_allocation_data_file
|
||||
update_func.assert_called_once_with()
|
||||
|
||||
def test_setup_allocation_data_file_unexisting(self):
|
||||
self._test_setup_allocation_data()
|
||||
|
||||
def test_setup_allocation_data_file_existing(self):
|
||||
self._test_setup_allocation_data(allocation_data_exists=True)
|
||||
|
||||
def _test_update_allocation_data(self, virtual_size_gb=None,
|
||||
volume_exists=True):
|
||||
self._smbfs_driver._update_allocation_data_file = mock.Mock()
|
||||
update_func = self._smbfs_driver._update_allocation_data_file
|
||||
|
||||
fake_alloc_data = self._get_fake_allocation_data()
|
||||
if volume_exists:
|
||||
fake_alloc_data[self._FAKE_SHARE_HASH][
|
||||
self._FAKE_VOLUME_NAME] = self.volume.size
|
||||
|
||||
self._smbfs_driver._allocation_data = fake_alloc_data
|
||||
|
||||
self._smbfs_driver.update_disk_allocation_data(self.volume,
|
||||
virtual_size_gb)
|
||||
|
||||
vol_allocated_size = fake_alloc_data[self._FAKE_SHARE_HASH].get(
|
||||
self._FAKE_VOLUME_NAME, None)
|
||||
if not virtual_size_gb:
|
||||
expected_total_allocated = (self._FAKE_TOTAL_ALLOCATED -
|
||||
self.volume.size)
|
||||
|
||||
self.assertIsNone(vol_allocated_size)
|
||||
else:
|
||||
expected_total_allocated = (self._FAKE_TOTAL_ALLOCATED +
|
||||
virtual_size_gb -
|
||||
self.volume.size)
|
||||
self.assertEqual(virtual_size_gb, vol_allocated_size)
|
||||
|
||||
update_func.assert_called_once_with()
|
||||
|
||||
self.assertEqual(
|
||||
expected_total_allocated,
|
||||
fake_alloc_data[self._FAKE_SHARE_HASH]['total_allocated'])
|
||||
|
||||
def test_update_allocation_data_volume_deleted(self):
|
||||
self._test_update_allocation_data()
|
||||
|
||||
def test_update_allocation_data_volume_extended(self):
|
||||
self._test_update_allocation_data(
|
||||
virtual_size_gb=self.volume.size + 1)
|
||||
|
||||
def test_update_allocation_data_volume_created(self):
|
||||
self._test_update_allocation_data(
|
||||
virtual_size_gb=self.volume.size)
|
||||
|
||||
@requires_allocation_data_update(expected_size=None)
|
||||
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.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)
|
||||
|
||||
@mock.patch('os.path.exists')
|
||||
@mock.patch.object(image_utils, 'check_qemu_img_version')
|
||||
def _test_setup(self, mock_check_qemu_img_version,
|
||||
mock_exists, config, share_config_exists=True):
|
||||
mock_exists.return_value = share_config_exists
|
||||
fake_ensure_mounted = mock.MagicMock()
|
||||
self._smbfs_driver._ensure_shares_mounted = fake_ensure_mounted
|
||||
self._smbfs_driver.configuration = config
|
||||
|
||||
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(mock.sentinel.context)
|
||||
mock_check_qemu_img_version.assert_called_once_with()
|
||||
self.assertEqual({}, self._smbfs_driver.shares)
|
||||
fake_ensure_mounted.assert_called_once_with()
|
||||
|
||||
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(config=fake_config,
|
||||
share_config_exists=False)
|
||||
|
||||
def test_setup_missing_shares_config_file(self):
|
||||
self._test_setup(config=self._FAKE_SMBFS_CONFIG,
|
||||
share_config_exists=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(config=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(config=fake_config)
|
||||
|
||||
def test_setup_invalid_used_ratio2(self):
|
||||
fake_config = copy.copy(self._FAKE_SMBFS_CONFIG)
|
||||
fake_config.smbfs_used_ratio = 1.1
|
||||
self._test_setup(config=fake_config)
|
||||
|
||||
@mock.patch('os.path.exists')
|
||||
@mock.patch.multiple(smbfs.SmbfsDriver,
|
||||
_create_windows_image=mock.DEFAULT,
|
||||
_create_regular_file=mock.DEFAULT,
|
||||
_create_qcow2_file=mock.DEFAULT,
|
||||
_create_sparsed_file=mock.DEFAULT,
|
||||
get_volume_format=mock.DEFAULT,
|
||||
local_path=mock.DEFAULT,
|
||||
_set_rw_permissions_for_all=mock.DEFAULT)
|
||||
def _test_create_volume(self, mock_exists, volume_exists=False,
|
||||
volume_format=None, use_sparsed_file=False,
|
||||
**mocks):
|
||||
self._smbfs_driver.configuration = copy.copy(self._FAKE_SMBFS_CONFIG)
|
||||
self._smbfs_driver.configuration.smbfs_sparsed_volumes = (
|
||||
use_sparsed_file)
|
||||
|
||||
self._smbfs_driver.get_volume_format.return_value = volume_format
|
||||
self._smbfs_driver.local_path.return_value = mock.sentinel.vol_path
|
||||
mock_exists.return_value = volume_exists
|
||||
|
||||
if volume_exists:
|
||||
self.assertRaises(exception.InvalidVolume,
|
||||
self._smbfs_driver._do_create_volume,
|
||||
self.volume)
|
||||
return
|
||||
|
||||
self._smbfs_driver._do_create_volume(self.volume)
|
||||
expected_create_args = [mock.sentinel.vol_path,
|
||||
self.volume.size]
|
||||
if volume_format in [self._smbfs_driver._DISK_FORMAT_VHDX,
|
||||
self._smbfs_driver._DISK_FORMAT_VHD]:
|
||||
expected_create_args.append(volume_format)
|
||||
exp_create_method = self._smbfs_driver._create_windows_image
|
||||
else:
|
||||
if volume_format == self._smbfs_driver._DISK_FORMAT_QCOW2:
|
||||
exp_create_method = self._smbfs_driver._create_qcow2_file
|
||||
elif use_sparsed_file:
|
||||
exp_create_method = self._smbfs_driver._create_sparsed_file
|
||||
else:
|
||||
exp_create_method = self._smbfs_driver._create_regular_file
|
||||
|
||||
exp_create_method.assert_called_once_with(*expected_create_args)
|
||||
mock_set_permissions = self._smbfs_driver._set_rw_permissions_for_all
|
||||
mock_set_permissions.assert_called_once_with(mock.sentinel.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='raw',
|
||||
use_sparsed_file=True)
|
||||
|
||||
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)
|
||||
self._smbfs_driver._get_total_allocated = mock.Mock(
|
||||
side_effect=[3, 2, 1])
|
||||
|
||||
if not mounted_shares:
|
||||
self.assertRaises(exception.SmbfsNoSharesMounted,
|
||||
self._smbfs_driver._find_share,
|
||||
self.volume.size)
|
||||
elif not eligible_shares:
|
||||
self.assertRaises(exception.SmbfsNoSuitableShareFound,
|
||||
self._smbfs_driver._find_share,
|
||||
self.volume.size)
|
||||
else:
|
||||
ret_value = self._smbfs_driver._find_share(
|
||||
self.volume.size)
|
||||
# The eligible share with the minimum allocated space
|
||||
# will be selected
|
||||
self.assertEqual('fake_share3', ret_value)
|
||||
|
||||
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.assertFalse(ret_value)
|
||||
|
||||
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.assertTrue(ret_value)
|
||||
|
||||
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.assertFalse(ret_value)
|
||||
|
||||
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.assertFalse(ret_value)
|
||||
|
||||
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)
|
||||
|
||||
@mock.patch.object(smbfs.SmbfsDriver, '_get_local_volume_path_template')
|
||||
@mock.patch.object(smbfs.SmbfsDriver, '_lookup_local_volume_path')
|
||||
@mock.patch.object(smbfs.SmbfsDriver, 'get_volume_format')
|
||||
def _test_get_volume_path(self, mock_get_volume_format, mock_lookup_volume,
|
||||
mock_get_path_template, volume_exists=True):
|
||||
drv = self._smbfs_driver
|
||||
mock_get_path_template.return_value = self._FAKE_VOLUME_PATH
|
||||
volume_format = 'raw'
|
||||
|
||||
expected_vol_path = self._FAKE_VOLUME_PATH + '.' + volume_format
|
||||
|
||||
mock_lookup_volume.return_value = (
|
||||
expected_vol_path if volume_exists else None)
|
||||
mock_get_volume_format.return_value = volume_format
|
||||
|
||||
ret_val = drv.local_path(self.volume)
|
||||
|
||||
if volume_exists:
|
||||
self.assertFalse(mock_get_volume_format.called)
|
||||
else:
|
||||
mock_get_volume_format.assert_called_once_with(self.volume)
|
||||
self.assertEqual(expected_vol_path, ret_val)
|
||||
|
||||
def test_get_existing_volume_path(self):
|
||||
self._test_get_volume_path()
|
||||
|
||||
def test_get_new_volume_path(self):
|
||||
self._test_get_volume_path(volume_exists=False)
|
||||
|
||||
@mock.patch.object(smbfs.SmbfsDriver, '_local_volume_dir')
|
||||
def test_get_local_volume_path_template(self, mock_get_local_dir):
|
||||
mock_get_local_dir.return_value = self._FAKE_MNT_POINT
|
||||
ret_val = self._smbfs_driver._get_local_volume_path_template(
|
||||
self.volume)
|
||||
self.assertEqual(self._FAKE_VOLUME_PATH, ret_val)
|
||||
|
||||
@mock.patch('os.path.exists')
|
||||
def test_lookup_local_volume_path(self, mock_exists):
|
||||
expected_path = self._FAKE_VOLUME_PATH + '.vhdx'
|
||||
mock_exists.side_effect = lambda x: x == expected_path
|
||||
|
||||
ret_val = self._smbfs_driver._lookup_local_volume_path(
|
||||
self._FAKE_VOLUME_PATH)
|
||||
|
||||
extensions = [''] + [
|
||||
".%s" % ext
|
||||
for ext in self._smbfs_driver._SUPPORTED_IMAGE_FORMATS]
|
||||
possible_paths = [self._FAKE_VOLUME_PATH + ext
|
||||
for ext in extensions]
|
||||
mock_exists.assert_has_calls(
|
||||
[mock.call(path) for path in possible_paths])
|
||||
self.assertEqual(expected_path, ret_val)
|
||||
|
||||
@mock.patch.object(smbfs.SmbfsDriver, '_get_local_volume_path_template')
|
||||
@mock.patch.object(smbfs.SmbfsDriver, '_lookup_local_volume_path')
|
||||
@mock.patch.object(smbfs.SmbfsDriver, '_qemu_img_info')
|
||||
@mock.patch.object(smbfs.SmbfsDriver, '_get_volume_format_spec')
|
||||
def _mock_get_volume_format(self, mock_get_format_spec, mock_qemu_img_info,
|
||||
mock_lookup_volume, mock_get_path_template,
|
||||
qemu_format=False, volume_format='raw',
|
||||
volume_exists=True):
|
||||
mock_get_path_template.return_value = self._FAKE_VOLUME_PATH
|
||||
mock_lookup_volume.return_value = (
|
||||
self._FAKE_VOLUME_PATH if volume_exists else None)
|
||||
|
||||
mock_qemu_img_info.return_value.file_format = volume_format
|
||||
mock_get_format_spec.return_value = volume_format
|
||||
|
||||
ret_val = self._smbfs_driver.get_volume_format(self.volume,
|
||||
qemu_format)
|
||||
|
||||
if volume_exists:
|
||||
mock_qemu_img_info.assert_called_once_with(self._FAKE_VOLUME_PATH,
|
||||
self._FAKE_VOLUME_NAME)
|
||||
self.assertFalse(mock_get_format_spec.called)
|
||||
else:
|
||||
mock_get_format_spec.assert_called_once_with(self.volume)
|
||||
self.assertFalse(mock_qemu_img_info.called)
|
||||
|
||||
return ret_val
|
||||
|
||||
def test_get_existing_raw_volume_format(self):
|
||||
fmt = self._mock_get_volume_format()
|
||||
self.assertEqual('raw', fmt)
|
||||
|
||||
def test_get_new_vhd_volume_format(self):
|
||||
expected_fmt = 'vhd'
|
||||
fmt = self._mock_get_volume_format(volume_format=expected_fmt,
|
||||
volume_exists=False)
|
||||
self.assertEqual(expected_fmt, fmt)
|
||||
|
||||
def test_get_new_vhd_legacy_volume_format(self):
|
||||
img_fmt = 'vhd'
|
||||
expected_fmt = 'vpc'
|
||||
ret_val = self._mock_get_volume_format(volume_format=img_fmt,
|
||||
volume_exists=False,
|
||||
qemu_format=True)
|
||||
self.assertEqual(expected_fmt, 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.get_volume_format = mock.Mock(
|
||||
return_value=mock.sentinel.format)
|
||||
|
||||
fake_data = {'export': self._FAKE_SHARE,
|
||||
'format': mock.sentinel.format,
|
||||
'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.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))
|
||||
drv._delete = mock.Mock()
|
||||
|
||||
with mock.patch.object(image_utils, 'resize_image') as fake_resize, \
|
||||
mock.patch.object(image_utils, 'convert_image') as \
|
||||
fake_convert:
|
||||
if extend_failed:
|
||||
self.assertRaises(exception.ExtendVolumeError,
|
||||
drv.extend_volume,
|
||||
self.volume, mock.sentinel.new_size)
|
||||
else:
|
||||
drv.extend_volume(self.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)
|
||||
|
||||
@requires_allocation_data_update(expected_size=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)
|
||||
|
||||
@requires_allocation_data_update(expected_size=mock.sentinel.new_size)
|
||||
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.volume, 2)
|
||||
elif not is_eligible:
|
||||
self.assertRaises(exception.ExtendVolumeError,
|
||||
self._smbfs_driver._check_extend_volume_support,
|
||||
self.volume, 2)
|
||||
else:
|
||||
self._smbfs_driver._check_extend_volume_support(
|
||||
self.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)
|
||||
|
||||
@requires_allocation_data_update(expected_size=_FAKE_VOLUME_SIZE)
|
||||
@mock.patch.object(remotefs.RemoteFSSnapDriver, 'create_volume')
|
||||
def test_create_volume_base(self, mock_create_volume):
|
||||
self._smbfs_driver.create_volume(self.volume)
|
||||
mock_create_volume.assert_called_once_with(self.volume)
|
||||
|
||||
@requires_allocation_data_update(expected_size=_FAKE_VOLUME_SIZE)
|
||||
@mock.patch.object(smbfs.SmbfsDriver,
|
||||
'_create_volume_from_snapshot')
|
||||
def test_create_volume_from_snapshot(self, mock_create_volume):
|
||||
self._smbfs_driver.create_volume_from_snapshot(self.volume,
|
||||
self.snapshot)
|
||||
mock_create_volume.assert_called_once_with(self.volume,
|
||||
self.snapshot)
|
||||
|
||||
@requires_allocation_data_update(expected_size=_FAKE_VOLUME_SIZE)
|
||||
@mock.patch.object(smbfs.SmbfsDriver, '_create_cloned_volume')
|
||||
def test_create_cloned_volume(self, mock_create_volume):
|
||||
self._smbfs_driver.create_cloned_volume(self.volume,
|
||||
mock.sentinel.src_vol)
|
||||
mock_create_volume.assert_called_once_with(self.volume,
|
||||
mock.sentinel.src_vol)
|
||||
|
||||
def test_create_volume_from_unavailable_snapshot(self):
|
||||
self.snapshot.status = 'error'
|
||||
self.assertRaises(
|
||||
exception.InvalidSnapshot,
|
||||
self._smbfs_driver.create_volume_from_snapshot,
|
||||
self.volume, self.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.snapshot, self.volume,
|
||||
self.volume.size)
|
||||
drv._extend_volume.assert_called_once_with(
|
||||
self.volume, self.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, wrong_size_after_fetch=False):
|
||||
drv = self._smbfs_driver
|
||||
|
||||
vol_size_bytes = self.volume.size << 30
|
||||
|
||||
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
|
||||
|
||||
drv.get_volume_format = mock.Mock(
|
||||
return_value=drv._DISK_FORMAT_VHDX)
|
||||
drv.local_path = mock.Mock(
|
||||
return_value=self._FAKE_VOLUME_PATH)
|
||||
drv._do_extend_volume = mock.Mock()
|
||||
drv.configuration = mock.MagicMock()
|
||||
drv.configuration.volume_dd_blocksize = (
|
||||
mock.sentinel.block_size)
|
||||
|
||||
with mock.patch.object(image_utils, 'fetch_to_volume_format') as \
|
||||
fake_fetch, mock.patch.object(image_utils, 'qemu_img_info') as \
|
||||
fake_qemu_img_info:
|
||||
|
||||
fake_qemu_img_info.return_value = fake_img_info
|
||||
|
||||
if wrong_size_after_fetch:
|
||||
self.assertRaises(
|
||||
exception.ImageUnacceptable,
|
||||
drv.copy_image_to_volume,
|
||||
mock.sentinel.context, self.volume,
|
||||
mock.sentinel.image_service,
|
||||
mock.sentinel.image_id)
|
||||
else:
|
||||
drv.copy_image_to_volume(
|
||||
mock.sentinel.context, self.volume,
|
||||
mock.sentinel.image_service,
|
||||
mock.sentinel.image_id)
|
||||
fake_fetch.assert_called_once_with(
|
||||
mock.sentinel.context, mock.sentinel.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.volume.size,
|
||||
self.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_get_capacity_info(self):
|
||||
fake_block_size = 4096.0
|
||||
fake_total_blocks = 1024
|
||||
fake_avail_blocks = 512
|
||||
|
||||
fake_df = ('%s %s %s' % (fake_block_size, fake_total_blocks,
|
||||
fake_avail_blocks), None)
|
||||
|
||||
self._smbfs_driver._get_mount_point_for_share = mock.Mock(
|
||||
return_value=self._FAKE_MNT_POINT)
|
||||
self._smbfs_driver._get_total_allocated = mock.Mock(
|
||||
return_value=self._FAKE_TOTAL_ALLOCATED)
|
||||
self._smbfs_driver._execute.return_value = fake_df
|
||||
|
||||
ret_val = self._smbfs_driver._get_capacity_info(self._FAKE_SHARE)
|
||||
expected = (fake_block_size * fake_total_blocks,
|
||||
fake_block_size * fake_avail_blocks,
|
||||
self._FAKE_TOTAL_ALLOCATED)
|
||||
self.assertEqual(expected, ret_val)
|
||||
|
||||
@ddt.data([False, False],
|
||||
[True, True],
|
||||
[False, True])
|
||||
@ddt.unpack
|
||||
def test_get_volume_format_spec(self,
|
||||
volume_meta_contains_fmt,
|
||||
volume_type_contains_fmt):
|
||||
self._smbfs_driver.configuration = copy.copy(self._FAKE_SMBFS_CONFIG)
|
||||
|
||||
fake_vol_meta_fmt = 'vhd'
|
||||
fake_vol_type_fmt = 'vhdx'
|
||||
|
||||
volume_metadata = {}
|
||||
volume_type_extra_specs = {}
|
||||
|
||||
if volume_meta_contains_fmt:
|
||||
volume_metadata['volume_format'] = fake_vol_meta_fmt
|
||||
elif volume_type_contains_fmt:
|
||||
volume_type_extra_specs['volume_format'] = fake_vol_type_fmt
|
||||
|
||||
volume_type = fake_volume.fake_volume_type_obj(self.context)
|
||||
volume = fake_volume.fake_volume_obj(self.context)
|
||||
# Optional arguments are not set in _from_db_object,
|
||||
# so have to set explicitly here
|
||||
volume.volume_type = volume_type
|
||||
volume.metadata = volume_metadata
|
||||
# Same for extra_specs and VolumeType
|
||||
volume_type.extra_specs = volume_type_extra_specs
|
||||
|
||||
resulted_fmt = self._smbfs_driver._get_volume_format_spec(volume)
|
||||
|
||||
if volume_meta_contains_fmt:
|
||||
expected_fmt = fake_vol_meta_fmt
|
||||
elif volume_type_contains_fmt:
|
||||
expected_fmt = fake_vol_type_fmt
|
||||
else:
|
||||
expected_fmt = self._FAKE_SMBFS_CONFIG.smbfs_default_volume_format
|
||||
|
||||
self.assertEqual(expected_fmt, resulted_fmt)
|
@ -12,8 +12,11 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import copy
|
||||
import functools
|
||||
import os
|
||||
|
||||
import ddt
|
||||
import mock
|
||||
from oslo_utils import units
|
||||
|
||||
@ -23,48 +26,87 @@ from cinder.image import image_utils
|
||||
from cinder import test
|
||||
from cinder.tests.unit import fake_snapshot
|
||||
from cinder.tests.unit import fake_volume
|
||||
from cinder.volume.drivers import remotefs
|
||||
from cinder.volume.drivers.windows import smbfs
|
||||
|
||||
|
||||
def requires_allocation_data_update(expected_size):
|
||||
def wrapper(func):
|
||||
@functools.wraps(func)
|
||||
def inner(inst, *args, **kwargs):
|
||||
with mock.patch.object(
|
||||
inst._smbfs_driver,
|
||||
'update_disk_allocation_data') as fake_update:
|
||||
func(inst, *args, **kwargs)
|
||||
fake_update.assert_called_once_with(inst.volume,
|
||||
expected_size)
|
||||
return inner
|
||||
return wrapper
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class WindowsSmbFsTestCase(test.TestCase):
|
||||
|
||||
_FAKE_SHARE = '//1.2.3.4/share1'
|
||||
_FAKE_SHARE_HASH = 'db0bf952c1734092b83e8990bd321131'
|
||||
_FAKE_MNT_BASE = 'c:\openstack\mnt'
|
||||
_FAKE_MNT_POINT = os.path.join(_FAKE_MNT_BASE, 'fake_hash')
|
||||
_FAKE_VOLUME_NAME = 'volume-4f711859-4928-4cb7-801a-a50c37ceaccc'
|
||||
_FAKE_SNAPSHOT_NAME = _FAKE_VOLUME_NAME + '-snapshot.vhdx'
|
||||
_FAKE_MNT_POINT = os.path.join(_FAKE_MNT_BASE, _FAKE_SHARE_HASH)
|
||||
_FAKE_VOLUME_ID = '4f711859-4928-4cb7-801a-a50c37ceaccc'
|
||||
_FAKE_VOLUME_NAME = 'volume-%s.vhdx' % _FAKE_VOLUME_ID
|
||||
_FAKE_SNAPSHOT_ID = '50811859-4928-4cb7-801a-a50c37ceacba'
|
||||
_FAKE_SNAPSHOT_NAME = 'volume-%s-%s.vhdx' % (_FAKE_VOLUME_ID,
|
||||
_FAKE_SNAPSHOT_ID)
|
||||
_FAKE_SNAPSHOT_PATH = os.path.join(_FAKE_MNT_POINT,
|
||||
_FAKE_SNAPSHOT_NAME)
|
||||
_FAKE_TOTAL_SIZE = '2048'
|
||||
_FAKE_TOTAL_AVAILABLE = '1024'
|
||||
_FAKE_VOLUME_SIZE = 1
|
||||
_FAKE_TOTAL_SIZE = 2048
|
||||
_FAKE_TOTAL_AVAILABLE = 1024
|
||||
_FAKE_TOTAL_ALLOCATED = 1024
|
||||
_FAKE_SHARE_OPTS = '-o username=Administrator,password=12345'
|
||||
_FAKE_VOLUME_PATH = os.path.join(_FAKE_MNT_POINT,
|
||||
_FAKE_VOLUME_NAME + '.vhdx')
|
||||
_FAKE_VOLUME_NAME)
|
||||
_FAKE_ALLOCATION_DATA_PATH = os.path.join('fake_dir',
|
||||
'fake_allocation_data')
|
||||
_FAKE_SHARE_OPTS = '-o username=Administrator,password=12345'
|
||||
|
||||
@mock.patch.object(smbfs, 'utilsfactory')
|
||||
@mock.patch.object(smbfs, 'remotefs_brick')
|
||||
def setUp(self, mock_remotefs, mock_utilsfactory):
|
||||
super(WindowsSmbFsTestCase, self).setUp()
|
||||
|
||||
self.context = context.get_admin_context()
|
||||
|
||||
self._FAKE_SMBFS_CONFIG = mock.MagicMock(
|
||||
smbfs_oversub_ratio = 2,
|
||||
smbfs_used_ratio = 0.5,
|
||||
smbfs_shares_config = mock.sentinel.share_config_file,
|
||||
smbfs_default_volume_format = 'vhdx',
|
||||
smbfs_sparsed_volumes = False)
|
||||
|
||||
self._smbfs_driver = smbfs.WindowsSmbfsDriver(
|
||||
configuration=mock.Mock())
|
||||
self._smbfs_driver._delete = mock.Mock()
|
||||
self._smbfs_driver.local_path = mock.Mock(
|
||||
return_value=self._FAKE_VOLUME_PATH)
|
||||
self._smbfs_driver._local_volume_dir = mock.Mock(
|
||||
return_value=self._FAKE_MNT_POINT)
|
||||
self._smbfs_driver.base = self._FAKE_MNT_BASE
|
||||
self._smbfs_driver._alloc_info_file_path = (
|
||||
self._FAKE_ALLOCATION_DATA_PATH)
|
||||
|
||||
self.volume = self._simple_volume()
|
||||
self.snapshot = self._simple_snapshot(volume=self.volume)
|
||||
|
||||
def _simple_volume(self, **kwargs):
|
||||
updates = {'id': 'e8d76af4-cbb9-4b70-8e9e-5a133f1a1a66',
|
||||
'size': 1,
|
||||
updates = {'id': self._FAKE_VOLUME_ID,
|
||||
'size': self._FAKE_VOLUME_SIZE,
|
||||
'provider_location': self._FAKE_SHARE}
|
||||
updates.update(kwargs)
|
||||
ctxt = context.get_admin_context()
|
||||
return fake_volume.fake_volume_obj(ctxt, **updates)
|
||||
|
||||
def _simple_snapshot(self, **kwargs):
|
||||
volume = self._simple_volume()
|
||||
volume = kwargs.pop('volume', None) or self._simple_volume()
|
||||
ctxt = context.get_admin_context()
|
||||
updates = {'id': '35a23942-7625-4683-ad84-144b76e87a80',
|
||||
updates = {'id': self._FAKE_SNAPSHOT_ID,
|
||||
'volume_size': volume.size,
|
||||
'volume_id': volume.id}
|
||||
updates.update(kwargs)
|
||||
@ -72,6 +114,381 @@ class WindowsSmbFsTestCase(test.TestCase):
|
||||
snapshot.volume = volume
|
||||
return snapshot
|
||||
|
||||
@mock.patch('os.path.exists')
|
||||
@mock.patch.object(image_utils, 'check_qemu_img_version')
|
||||
def _test_setup(self, mock_check_qemu_img_version,
|
||||
mock_exists, config, share_config_exists=True):
|
||||
mock_exists.return_value = share_config_exists
|
||||
fake_ensure_mounted = mock.MagicMock()
|
||||
self._smbfs_driver._ensure_shares_mounted = fake_ensure_mounted
|
||||
self._smbfs_driver.configuration = config
|
||||
|
||||
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(mock.sentinel.context)
|
||||
mock_check_qemu_img_version.assert_called_once_with()
|
||||
self.assertEqual({}, self._smbfs_driver.shares)
|
||||
fake_ensure_mounted.assert_called_once_with()
|
||||
|
||||
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.get_volume_format = mock.Mock(
|
||||
return_value=mock.sentinel.format)
|
||||
|
||||
fake_data = {'export': self._FAKE_SHARE,
|
||||
'format': mock.sentinel.format,
|
||||
'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.volume, None)
|
||||
|
||||
self.assertEqual(expected, ret_val)
|
||||
|
||||
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(config=fake_config,
|
||||
share_config_exists=False)
|
||||
|
||||
def test_setup_missing_shares_config_file(self):
|
||||
self._test_setup(config=self._FAKE_SMBFS_CONFIG,
|
||||
share_config_exists=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(config=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(config=fake_config)
|
||||
|
||||
def test_setup_invalid_used_ratio2(self):
|
||||
fake_config = copy.copy(self._FAKE_SMBFS_CONFIG)
|
||||
fake_config.smbfs_used_ratio = 1.1
|
||||
self._test_setup(config=fake_config)
|
||||
|
||||
@mock.patch.object(smbfs, 'open', create=True)
|
||||
@mock.patch('os.path.exists')
|
||||
@mock.patch.object(smbfs.fileutils, 'ensure_tree')
|
||||
@mock.patch('json.load')
|
||||
def _test_setup_allocation_data(self, mock_json_load, mock_ensure_tree,
|
||||
mock_exists, mock_open,
|
||||
allocation_data_exists=False):
|
||||
mock_exists.return_value = allocation_data_exists
|
||||
self._smbfs_driver._update_allocation_data_file = mock.Mock()
|
||||
|
||||
self._smbfs_driver._setup_allocation_data()
|
||||
|
||||
if allocation_data_exists:
|
||||
fd = mock_open.return_value.__enter__.return_value
|
||||
mock_json_load.assert_called_once_with(fd)
|
||||
self.assertEqual(mock_json_load.return_value,
|
||||
self._smbfs_driver._allocation_data)
|
||||
else:
|
||||
mock_ensure_tree.assert_called_once_with(
|
||||
os.path.dirname(self._FAKE_ALLOCATION_DATA_PATH))
|
||||
update_func = self._smbfs_driver._update_allocation_data_file
|
||||
update_func.assert_called_once_with()
|
||||
|
||||
def test_setup_allocation_data_file_unexisting(self):
|
||||
self._test_setup_allocation_data()
|
||||
|
||||
def test_setup_allocation_data_file_existing(self):
|
||||
self._test_setup_allocation_data(allocation_data_exists=True)
|
||||
|
||||
def _test_update_allocation_data(self, virtual_size_gb=None,
|
||||
volume_exists=True):
|
||||
self._smbfs_driver._update_allocation_data_file = mock.Mock()
|
||||
update_func = self._smbfs_driver._update_allocation_data_file
|
||||
|
||||
fake_alloc_data = {
|
||||
self._FAKE_SHARE_HASH: {
|
||||
'total_allocated': self._FAKE_TOTAL_ALLOCATED}}
|
||||
if volume_exists:
|
||||
fake_alloc_data[self._FAKE_SHARE_HASH][
|
||||
self.volume.name] = self.volume.size
|
||||
|
||||
self._smbfs_driver._allocation_data = fake_alloc_data
|
||||
|
||||
self._smbfs_driver.update_disk_allocation_data(self.volume,
|
||||
virtual_size_gb)
|
||||
|
||||
vol_allocated_size = fake_alloc_data[self._FAKE_SHARE_HASH].get(
|
||||
self.volume.name, None)
|
||||
if not virtual_size_gb:
|
||||
expected_total_allocated = (self._FAKE_TOTAL_ALLOCATED -
|
||||
self.volume.size)
|
||||
|
||||
self.assertIsNone(vol_allocated_size)
|
||||
else:
|
||||
exp_added = (self.volume.size if not volume_exists
|
||||
else virtual_size_gb - self.volume.size)
|
||||
expected_total_allocated = (self._FAKE_TOTAL_ALLOCATED +
|
||||
exp_added)
|
||||
self.assertEqual(virtual_size_gb, vol_allocated_size)
|
||||
|
||||
update_func.assert_called_once_with()
|
||||
|
||||
self.assertEqual(
|
||||
expected_total_allocated,
|
||||
fake_alloc_data[self._FAKE_SHARE_HASH]['total_allocated'])
|
||||
|
||||
def test_update_allocation_data_volume_deleted(self):
|
||||
self._test_update_allocation_data()
|
||||
|
||||
def test_update_allocation_data_volume_extended(self):
|
||||
self._test_update_allocation_data(
|
||||
virtual_size_gb=self.volume.size + 1)
|
||||
|
||||
def test_update_allocation_data_volume_created(self):
|
||||
self._test_update_allocation_data(
|
||||
virtual_size_gb=self.volume.size,
|
||||
volume_exists=False)
|
||||
|
||||
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)
|
||||
self._smbfs_driver._get_total_allocated = mock.Mock(
|
||||
side_effect=[3, 2, 1])
|
||||
|
||||
if not mounted_shares:
|
||||
self.assertRaises(exception.SmbfsNoSharesMounted,
|
||||
self._smbfs_driver._find_share,
|
||||
self.volume.size)
|
||||
elif not eligible_shares:
|
||||
self.assertRaises(exception.SmbfsNoSuitableShareFound,
|
||||
self._smbfs_driver._find_share,
|
||||
self.volume.size)
|
||||
else:
|
||||
ret_value = self._smbfs_driver._find_share(
|
||||
self.volume.size)
|
||||
# The eligible share with the minimum allocated space
|
||||
# will be selected
|
||||
self.assertEqual('fake_share3', ret_value)
|
||||
|
||||
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.assertFalse(ret_value)
|
||||
|
||||
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.assertTrue(ret_value)
|
||||
|
||||
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.assertFalse(ret_value)
|
||||
|
||||
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.assertFalse(ret_value)
|
||||
|
||||
@mock.patch.object(smbfs.WindowsSmbfsDriver,
|
||||
'_get_local_volume_path_template')
|
||||
@mock.patch.object(smbfs.WindowsSmbfsDriver, '_lookup_local_volume_path')
|
||||
@mock.patch.object(smbfs.WindowsSmbfsDriver, 'get_volume_format')
|
||||
def _test_get_volume_path(self, mock_get_volume_format, mock_lookup_volume,
|
||||
mock_get_path_template, volume_exists=True):
|
||||
drv = self._smbfs_driver
|
||||
(mock_get_path_template.return_value,
|
||||
ext) = os.path.splitext(self._FAKE_VOLUME_PATH)
|
||||
volume_format = ext.strip('.')
|
||||
|
||||
mock_lookup_volume.return_value = (
|
||||
self._FAKE_VOLUME_PATH if volume_exists else None)
|
||||
mock_get_volume_format.return_value = volume_format
|
||||
|
||||
ret_val = drv.local_path(self.volume)
|
||||
|
||||
if volume_exists:
|
||||
self.assertFalse(mock_get_volume_format.called)
|
||||
else:
|
||||
mock_get_volume_format.assert_called_once_with(self.volume)
|
||||
self.assertEqual(self._FAKE_VOLUME_PATH, ret_val)
|
||||
|
||||
def test_get_existing_volume_path(self):
|
||||
self._test_get_volume_path()
|
||||
|
||||
def test_get_new_volume_path(self):
|
||||
self._test_get_volume_path(volume_exists=False)
|
||||
|
||||
@mock.patch.object(smbfs.WindowsSmbfsDriver, '_local_volume_dir')
|
||||
def test_get_local_volume_path_template(self, mock_get_local_dir):
|
||||
mock_get_local_dir.return_value = self._FAKE_MNT_POINT
|
||||
ret_val = self._smbfs_driver._get_local_volume_path_template(
|
||||
self.volume)
|
||||
exp_template = os.path.splitext(self._FAKE_VOLUME_PATH)[0]
|
||||
self.assertEqual(exp_template, ret_val)
|
||||
|
||||
@mock.patch('os.path.exists')
|
||||
def test_lookup_local_volume_path(self, mock_exists):
|
||||
expected_path = self._FAKE_VOLUME_PATH + '.vhdx'
|
||||
mock_exists.side_effect = lambda x: x == expected_path
|
||||
|
||||
ret_val = self._smbfs_driver._lookup_local_volume_path(
|
||||
self._FAKE_VOLUME_PATH)
|
||||
|
||||
extensions = [
|
||||
".%s" % ext
|
||||
for ext in self._smbfs_driver._SUPPORTED_IMAGE_FORMATS]
|
||||
possible_paths = [self._FAKE_VOLUME_PATH + ext
|
||||
for ext in extensions]
|
||||
mock_exists.assert_has_calls(
|
||||
[mock.call(path) for path in possible_paths])
|
||||
self.assertEqual(expected_path, ret_val)
|
||||
|
||||
@mock.patch.object(smbfs.WindowsSmbfsDriver,
|
||||
'_get_local_volume_path_template')
|
||||
@mock.patch.object(smbfs.WindowsSmbfsDriver, '_lookup_local_volume_path')
|
||||
@mock.patch.object(smbfs.WindowsSmbfsDriver, '_get_volume_format_spec')
|
||||
def _test_get_volume_format(self, mock_get_format_spec,
|
||||
mock_lookup_volume, mock_get_path_template,
|
||||
qemu_format=False, volume_format='vhdx',
|
||||
expected_vol_fmt=None,
|
||||
volume_exists=True):
|
||||
expected_vol_fmt = expected_vol_fmt or volume_format
|
||||
|
||||
vol_path = '%s.%s' % (os.path.splitext(self._FAKE_VOLUME_PATH)[0],
|
||||
volume_format)
|
||||
mock_get_path_template.return_value = vol_path
|
||||
mock_lookup_volume.return_value = (
|
||||
vol_path if volume_exists else None)
|
||||
|
||||
mock_get_format_spec.return_value = volume_format
|
||||
|
||||
supported_fmts = self._smbfs_driver._SUPPORTED_IMAGE_FORMATS
|
||||
if volume_format.lower() not in supported_fmts:
|
||||
self.assertRaises(exception.SmbfsException,
|
||||
self._smbfs_driver.get_volume_format,
|
||||
self.volume,
|
||||
qemu_format)
|
||||
|
||||
else:
|
||||
ret_val = self._smbfs_driver.get_volume_format(self.volume,
|
||||
qemu_format)
|
||||
|
||||
if volume_exists:
|
||||
self.assertFalse(mock_get_format_spec.called)
|
||||
else:
|
||||
mock_get_format_spec.assert_called_once_with(self.volume)
|
||||
|
||||
self.assertEqual(expected_vol_fmt, ret_val)
|
||||
|
||||
def test_get_volume_format_invalid_extension(self):
|
||||
self._test_get_volume_format(volume_format='fake')
|
||||
|
||||
def test_get_existing_vhdx_volume_format(self):
|
||||
self._test_get_volume_format()
|
||||
|
||||
def test_get_new_vhd_volume_format(self):
|
||||
fmt = 'vhd'
|
||||
self._test_get_volume_format(volume_format=fmt,
|
||||
volume_exists=False,
|
||||
expected_vol_fmt=fmt)
|
||||
|
||||
def test_get_new_vhd_legacy_volume_format(self):
|
||||
img_fmt = 'vhd'
|
||||
expected_fmt = 'vpc'
|
||||
self._test_get_volume_format(volume_format=img_fmt,
|
||||
volume_exists=False,
|
||||
qemu_format=True,
|
||||
expected_vol_fmt=expected_fmt)
|
||||
|
||||
@ddt.data([False, False],
|
||||
[True, True],
|
||||
[False, True])
|
||||
@ddt.unpack
|
||||
def test_get_volume_format_spec(self,
|
||||
volume_meta_contains_fmt,
|
||||
volume_type_contains_fmt):
|
||||
self._smbfs_driver.configuration = copy.copy(self._FAKE_SMBFS_CONFIG)
|
||||
|
||||
fake_vol_meta_fmt = 'vhd'
|
||||
fake_vol_type_fmt = 'vhdx'
|
||||
|
||||
volume_metadata = {}
|
||||
volume_type_extra_specs = {}
|
||||
|
||||
if volume_meta_contains_fmt:
|
||||
volume_metadata['volume_format'] = fake_vol_meta_fmt
|
||||
elif volume_type_contains_fmt:
|
||||
volume_type_extra_specs['volume_format'] = fake_vol_type_fmt
|
||||
|
||||
volume_type = fake_volume.fake_volume_type_obj(self.context)
|
||||
volume = fake_volume.fake_volume_obj(self.context)
|
||||
# Optional arguments are not set in _from_db_object,
|
||||
# so have to set explicitly here
|
||||
volume.volume_type = volume_type
|
||||
volume.metadata = volume_metadata
|
||||
# Same for extra_specs and VolumeType
|
||||
volume_type.extra_specs = volume_type_extra_specs
|
||||
|
||||
resulted_fmt = self._smbfs_driver._get_volume_format_spec(volume)
|
||||
|
||||
if volume_meta_contains_fmt:
|
||||
expected_fmt = fake_vol_meta_fmt
|
||||
elif volume_type_contains_fmt:
|
||||
expected_fmt = fake_vol_type_fmt
|
||||
else:
|
||||
expected_fmt = self._FAKE_SMBFS_CONFIG.smbfs_default_volume_format
|
||||
|
||||
self.assertEqual(expected_fmt, resulted_fmt)
|
||||
|
||||
@requires_allocation_data_update(expected_size=_FAKE_VOLUME_SIZE)
|
||||
@mock.patch.object(remotefs.RemoteFSSnapDriver, 'create_volume')
|
||||
def test_create_volume_base(self, mock_create_volume):
|
||||
self._smbfs_driver.create_volume(self.volume)
|
||||
mock_create_volume.assert_called_once_with(self.volume)
|
||||
|
||||
def _test_create_volume(self, volume_exists=False, volume_format='vhdx'):
|
||||
self._smbfs_driver.create_dynamic_vhd = mock.MagicMock()
|
||||
fake_create = self._smbfs_driver._vhdutils.create_dynamic_vhd
|
||||
@ -99,6 +516,37 @@ class WindowsSmbFsTestCase(test.TestCase):
|
||||
def test_create_volume_invalid_volume(self):
|
||||
self._test_create_volume(volume_format="qcow")
|
||||
|
||||
@requires_allocation_data_update(expected_size=None)
|
||||
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.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_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)
|
||||
|
||||
def test_get_capacity_info(self):
|
||||
self._smbfs_driver._smbutils.get_share_capacity_info.return_value = (
|
||||
self._FAKE_TOTAL_SIZE, self._FAKE_TOTAL_AVAILABLE)
|
||||
@ -116,7 +564,7 @@ class WindowsSmbFsTestCase(test.TestCase):
|
||||
backing_file)
|
||||
|
||||
image_info = self._smbfs_driver._qemu_img_info(self._FAKE_VOLUME_PATH)
|
||||
self.assertEqual(self._FAKE_VOLUME_NAME + '.vhdx',
|
||||
self.assertEqual(self._FAKE_VOLUME_NAME,
|
||||
image_info.image)
|
||||
backing_file_name = backing_file and os.path.basename(backing_file)
|
||||
self.assertEqual(backing_file_name, image_info.backing_file)
|
||||
@ -144,6 +592,30 @@ class WindowsSmbFsTestCase(test.TestCase):
|
||||
fake_create_diff.assert_called_once_with(self._FAKE_SNAPSHOT_PATH,
|
||||
self._FAKE_VOLUME_PATH)
|
||||
|
||||
@requires_allocation_data_update(expected_size=_FAKE_VOLUME_SIZE)
|
||||
@mock.patch.object(smbfs.WindowsSmbfsDriver,
|
||||
'_create_volume_from_snapshot')
|
||||
def test_create_volume_from_snapshot(self, mock_create_volume):
|
||||
self._smbfs_driver.create_volume_from_snapshot(self.volume,
|
||||
self.snapshot)
|
||||
mock_create_volume.assert_called_once_with(self.volume,
|
||||
self.snapshot)
|
||||
|
||||
@requires_allocation_data_update(expected_size=_FAKE_VOLUME_SIZE)
|
||||
@mock.patch.object(smbfs.WindowsSmbfsDriver, '_create_cloned_volume')
|
||||
def test_create_cloned_volume(self, mock_create_volume):
|
||||
self._smbfs_driver.create_cloned_volume(self.volume,
|
||||
mock.sentinel.src_vol)
|
||||
mock_create_volume.assert_called_once_with(self.volume,
|
||||
mock.sentinel.src_vol)
|
||||
|
||||
def test_create_volume_from_unavailable_snapshot(self):
|
||||
self.snapshot.status = 'error'
|
||||
self.assertRaises(
|
||||
exception.InvalidSnapshot,
|
||||
self._smbfs_driver.create_volume_from_snapshot,
|
||||
self.volume, self.snapshot)
|
||||
|
||||
def _test_copy_volume_to_image(self, has_parent=False,
|
||||
volume_format='vhd'):
|
||||
drv = self._smbfs_driver
|
||||
@ -249,7 +721,7 @@ class WindowsSmbFsTestCase(test.TestCase):
|
||||
fake_volume_info = {
|
||||
snapshot.id: 'fake_snapshot_file_name'}
|
||||
fake_img_info = mock.MagicMock()
|
||||
fake_img_info.backing_file = self._FAKE_VOLUME_NAME + '.vhdx'
|
||||
fake_img_info.backing_file = self._FAKE_VOLUME_NAME
|
||||
|
||||
drv._local_path_volume_info = mock.Mock(
|
||||
return_value=self._FAKE_VOLUME_PATH + '.info')
|
||||
@ -279,6 +751,6 @@ class WindowsSmbFsTestCase(test.TestCase):
|
||||
drv = self._smbfs_driver
|
||||
drv._rebase_img(
|
||||
self._FAKE_SNAPSHOT_PATH,
|
||||
self._FAKE_VOLUME_NAME + '.vhdx', 'vhdx')
|
||||
self._FAKE_VOLUME_NAME, 'vhdx')
|
||||
drv._vhdutils.reconnect_parent_vhd.assert_called_once_with(
|
||||
self._FAKE_SNAPSHOT_PATH, self._FAKE_VOLUME_PATH)
|
||||
|
@ -1,663 +0,0 @@
|
||||
# Copyright (c) 2014 Cloudbase Solutions SRL
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import decorator
|
||||
|
||||
import inspect
|
||||
import json
|
||||
import os
|
||||
|
||||
from os_brick.remotefs import remotefs
|
||||
from oslo_concurrency import processutils as putils
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import fileutils
|
||||
from oslo_utils import units
|
||||
|
||||
from cinder import exception
|
||||
from cinder.i18n import _, _LI, _LW
|
||||
from cinder.image import image_utils
|
||||
from cinder import interface
|
||||
from cinder import utils
|
||||
from cinder.volume.drivers import remotefs as remotefs_drv
|
||||
|
||||
|
||||
VERSION = '1.1.0'
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
volume_opts = [
|
||||
cfg.StrOpt('smbfs_shares_config',
|
||||
default='/etc/cinder/smbfs_shares',
|
||||
help='File with the list of available smbfs shares.'),
|
||||
cfg.StrOpt('smbfs_allocation_info_file_path',
|
||||
default='$state_path/allocation_data',
|
||||
help=('The path of the automatically generated file containing '
|
||||
'information about volume disk space allocation.')),
|
||||
cfg.StrOpt('smbfs_default_volume_format',
|
||||
default='qcow2',
|
||||
choices=['raw', 'qcow2', 'vhd', 'vhdx'],
|
||||
help=('Default format that will be used when creating volumes '
|
||||
'if no volume format is specified.')),
|
||||
cfg.BoolOpt('smbfs_sparsed_volumes',
|
||||
default=True,
|
||||
help=('Create volumes as sparsed files which take no space '
|
||||
'rather than regular files when using raw format, '
|
||||
'in which case volume creation takes lot of time.')),
|
||||
cfg.FloatOpt('smbfs_used_ratio',
|
||||
default=0.95,
|
||||
help=('Percent of ACTUAL usage of the underlying volume '
|
||||
'before no new volumes can be allocated to the volume '
|
||||
'destination.')),
|
||||
cfg.FloatOpt('smbfs_oversub_ratio',
|
||||
default=1.0,
|
||||
help=('This will compare the allocated to available space on '
|
||||
'the volume destination. If the ratio exceeds this '
|
||||
'number, the destination will no longer be valid.')),
|
||||
cfg.StrOpt('smbfs_mount_point_base',
|
||||
default='$state_path/mnt',
|
||||
help=('Base dir containing mount points for smbfs shares.')),
|
||||
cfg.StrOpt('smbfs_mount_options',
|
||||
default='noperm,file_mode=0775,dir_mode=0775',
|
||||
help=('Mount options passed to the smbfs client. See '
|
||||
'mount.cifs man page for details.')),
|
||||
]
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.register_opts(volume_opts)
|
||||
|
||||
|
||||
def update_allocation_data(delete=False):
|
||||
@decorator.decorator
|
||||
def wrapper(func, inst, *args, **kwargs):
|
||||
ret_val = func(inst, *args, **kwargs)
|
||||
|
||||
call_args = inspect.getcallargs(func, inst, *args, **kwargs)
|
||||
volume = call_args['volume']
|
||||
requested_size = call_args.get('size_gb', None)
|
||||
|
||||
if delete:
|
||||
allocated_size_gb = None
|
||||
else:
|
||||
allocated_size_gb = requested_size or volume.size
|
||||
|
||||
inst.update_disk_allocation_data(volume, allocated_size_gb)
|
||||
return ret_val
|
||||
return wrapper
|
||||
|
||||
|
||||
@interface.volumedriver
|
||||
class SmbfsDriver(remotefs_drv.RemoteFSSnapDriver):
|
||||
"""SMBFS based cinder volume driver."""
|
||||
|
||||
SUPPORTED = False
|
||||
|
||||
driver_volume_type = 'smbfs'
|
||||
driver_prefix = 'smbfs'
|
||||
volume_backend_name = 'Generic_SMBFS'
|
||||
SHARE_FORMAT_REGEX = r'//.+/.+'
|
||||
VERSION = VERSION
|
||||
|
||||
# ThirdPartySystems wiki page
|
||||
CI_WIKI_NAME = "Cinder_Jenkins"
|
||||
|
||||
_MINIMUM_QEMU_IMG_VERSION = '1.7'
|
||||
|
||||
_DISK_FORMAT_VHD = 'vhd'
|
||||
_DISK_FORMAT_VHD_LEGACY = 'vpc'
|
||||
_DISK_FORMAT_VHDX = 'vhdx'
|
||||
_DISK_FORMAT_RAW = 'raw'
|
||||
_DISK_FORMAT_QCOW2 = 'qcow2'
|
||||
|
||||
_SUPPORTED_IMAGE_FORMATS = [_DISK_FORMAT_RAW, _DISK_FORMAT_QCOW2,
|
||||
_DISK_FORMAT_VHD, _DISK_FORMAT_VHDX]
|
||||
_VALID_IMAGE_EXTENSIONS = _SUPPORTED_IMAGE_FORMATS
|
||||
|
||||
def __init__(self, execute=putils.execute, *args, **kwargs):
|
||||
self._remotefsclient = None
|
||||
super(SmbfsDriver, self).__init__(*args, **kwargs)
|
||||
self.configuration.append_config_values(volume_opts)
|
||||
root_helper = utils.get_root_helper()
|
||||
self.base = getattr(self.configuration,
|
||||
'smbfs_mount_point_base')
|
||||
opts = getattr(self.configuration,
|
||||
'smbfs_mount_options')
|
||||
self._remotefsclient = remotefs.RemoteFsClient(
|
||||
'cifs', root_helper, execute=execute,
|
||||
smbfs_mount_point_base=self.base,
|
||||
smbfs_mount_options=opts)
|
||||
self.img_suffix = None
|
||||
self._alloc_info_file_path = CONF.smbfs_allocation_info_file_path
|
||||
|
||||
def _qemu_img_info(self, path, volume_name):
|
||||
return super(SmbfsDriver, self)._qemu_img_info_base(
|
||||
path, volume_name, self.configuration.smbfs_mount_point_base)
|
||||
|
||||
@remotefs_drv.locked_volume_id_operation
|
||||
def initialize_connection(self, volume, connector):
|
||||
"""Allow connection to connector and return connection info.
|
||||
|
||||
:param volume: volume reference
|
||||
:param connector: connector reference
|
||||
"""
|
||||
# Find active image
|
||||
active_file = self.get_active_image_from_info(volume)
|
||||
fmt = self.get_volume_format(volume)
|
||||
|
||||
data = {'export': volume.provider_location,
|
||||
'format': fmt,
|
||||
'name': active_file}
|
||||
if volume.provider_location in self.shares:
|
||||
data['options'] = self.shares[volume.provider_location]
|
||||
return {
|
||||
'driver_volume_type': self.driver_volume_type,
|
||||
'data': data,
|
||||
'mount_point_base': self._get_mount_point_base()
|
||||
}
|
||||
|
||||
def do_setup(self, context):
|
||||
image_utils.check_qemu_img_version(self._MINIMUM_QEMU_IMG_VERSION)
|
||||
|
||||
config = self.configuration.smbfs_shares_config
|
||||
if not config:
|
||||
msg = (_("SMBFS config file not set (smbfs_shares_config)."))
|
||||
LOG.error(msg)
|
||||
raise exception.SmbfsException(msg)
|
||||
if not os.path.exists(config):
|
||||
msg = (_("SMBFS config file at %(config)s doesn't exist.") %
|
||||
{'config': config})
|
||||
LOG.error(msg)
|
||||
raise exception.SmbfsException(msg)
|
||||
if not os.path.isabs(self.base):
|
||||
msg = _("Invalid mount point base: %s") % self.base
|
||||
LOG.error(msg)
|
||||
raise exception.SmbfsException(msg)
|
||||
if not self.configuration.smbfs_oversub_ratio > 0:
|
||||
msg = _(
|
||||
"SMBFS config 'smbfs_oversub_ratio' invalid. Must be > 0: "
|
||||
"%s") % self.configuration.smbfs_oversub_ratio
|
||||
|
||||
LOG.error(msg)
|
||||
raise exception.SmbfsException(msg)
|
||||
|
||||
if not 0 < self.configuration.smbfs_used_ratio <= 1:
|
||||
msg = _("SMBFS config 'smbfs_used_ratio' invalid. Must be > 0 "
|
||||
"and <= 1.0: %s") % self.configuration.smbfs_used_ratio
|
||||
LOG.error(msg)
|
||||
raise exception.SmbfsException(msg)
|
||||
|
||||
self.shares = {} # address : options
|
||||
self._ensure_shares_mounted()
|
||||
self._setup_allocation_data()
|
||||
|
||||
def _setup_allocation_data(self):
|
||||
if not os.path.exists(self._alloc_info_file_path):
|
||||
fileutils.ensure_tree(
|
||||
os.path.dirname(self._alloc_info_file_path))
|
||||
self._allocation_data = {}
|
||||
self._update_allocation_data_file()
|
||||
else:
|
||||
with open(self._alloc_info_file_path, 'r') as f:
|
||||
self._allocation_data = json.load(f)
|
||||
|
||||
def update_disk_allocation_data(self, volume, virtual_size_gb=None):
|
||||
volume_name = volume.name
|
||||
smbfs_share = volume.provider_location
|
||||
if smbfs_share:
|
||||
share_hash = self._get_hash_str(smbfs_share)
|
||||
else:
|
||||
return
|
||||
|
||||
share_alloc_data = self._allocation_data.get(share_hash, {})
|
||||
old_virtual_size = share_alloc_data.get(volume_name, 0)
|
||||
total_allocated = share_alloc_data.get('total_allocated', 0)
|
||||
|
||||
if virtual_size_gb:
|
||||
share_alloc_data[volume_name] = virtual_size_gb
|
||||
total_allocated += virtual_size_gb - old_virtual_size
|
||||
elif share_alloc_data.get(volume_name):
|
||||
# The volume is deleted.
|
||||
del share_alloc_data[volume_name]
|
||||
total_allocated -= old_virtual_size
|
||||
|
||||
share_alloc_data['total_allocated'] = total_allocated
|
||||
self._allocation_data[share_hash] = share_alloc_data
|
||||
self._update_allocation_data_file()
|
||||
|
||||
def _update_allocation_data_file(self):
|
||||
with open(self._alloc_info_file_path, 'w') as f:
|
||||
json.dump(self._allocation_data, f)
|
||||
|
||||
def _get_total_allocated(self, smbfs_share):
|
||||
share_hash = self._get_hash_str(smbfs_share)
|
||||
share_alloc_data = self._allocation_data.get(share_hash, {})
|
||||
total_allocated = share_alloc_data.get('total_allocated', 0) << 30
|
||||
return float(total_allocated)
|
||||
|
||||
def local_path(self, volume):
|
||||
"""Get volume path (mounted locally fs path) for given volume.
|
||||
|
||||
:param volume: volume reference
|
||||
"""
|
||||
volume_path_template = self._get_local_volume_path_template(volume)
|
||||
volume_path = self._lookup_local_volume_path(volume_path_template)
|
||||
if volume_path:
|
||||
return volume_path
|
||||
|
||||
# The image does not exist, so retrieve the volume format
|
||||
# in order to build the path.
|
||||
fmt = self.get_volume_format(volume)
|
||||
volume_path = volume_path_template + '.' + fmt
|
||||
return volume_path
|
||||
|
||||
def _get_local_volume_path_template(self, volume):
|
||||
local_dir = self._local_volume_dir(volume)
|
||||
local_path_template = os.path.join(local_dir, volume.name)
|
||||
return local_path_template
|
||||
|
||||
def _lookup_local_volume_path(self, volume_path_template):
|
||||
for ext in [''] + self._SUPPORTED_IMAGE_FORMATS:
|
||||
volume_path = (volume_path_template + '.' + ext
|
||||
if ext else volume_path_template)
|
||||
if os.path.exists(volume_path):
|
||||
return volume_path
|
||||
|
||||
def _local_path_volume_info(self, volume):
|
||||
return '%s%s' % (self.local_path(volume), '.info')
|
||||
|
||||
def _get_new_snap_path(self, snapshot):
|
||||
vol_path = self.local_path(snapshot.volume)
|
||||
snap_path, ext = os.path.splitext(vol_path)
|
||||
snap_path += '.' + snapshot.id + ext
|
||||
return snap_path
|
||||
|
||||
def get_volume_format(self, volume, qemu_format=False):
|
||||
volume_path_template = self._get_local_volume_path_template(volume)
|
||||
volume_path = self._lookup_local_volume_path(volume_path_template)
|
||||
|
||||
if volume_path:
|
||||
ext = os.path.splitext(volume_path)[1].strip('.').lower()
|
||||
if ext in self._SUPPORTED_IMAGE_FORMATS:
|
||||
volume_format = ext
|
||||
else:
|
||||
info = self._qemu_img_info(volume_path, volume.name)
|
||||
volume_format = info.file_format
|
||||
else:
|
||||
volume_format = (
|
||||
self._get_volume_format_spec(volume) or
|
||||
self.configuration.smbfs_default_volume_format)
|
||||
|
||||
if qemu_format and volume_format == self._DISK_FORMAT_VHD:
|
||||
volume_format = self._DISK_FORMAT_VHD_LEGACY
|
||||
elif volume_format == self._DISK_FORMAT_VHD_LEGACY:
|
||||
volume_format = self._DISK_FORMAT_VHD
|
||||
|
||||
return volume_format
|
||||
|
||||
@remotefs_drv.locked_volume_id_operation
|
||||
@update_allocation_data(delete=True)
|
||||
def delete_volume(self, volume):
|
||||
"""Deletes a logical volume."""
|
||||
if not volume.provider_location:
|
||||
LOG.warning(_LW('Volume %s does not have provider_location '
|
||||
'specified, skipping.'), volume.name)
|
||||
return
|
||||
|
||||
self._ensure_share_mounted(volume.provider_location)
|
||||
volume_dir = self._local_volume_dir(volume)
|
||||
mounted_path = os.path.join(volume_dir,
|
||||
self.get_active_image_from_info(volume))
|
||||
if os.path.exists(mounted_path):
|
||||
self._delete(mounted_path)
|
||||
else:
|
||||
LOG.debug("Skipping deletion of volume %s as it does not exist.",
|
||||
mounted_path)
|
||||
|
||||
info_path = self._local_path_volume_info(volume)
|
||||
self._delete(info_path)
|
||||
|
||||
def _create_windows_image(self, volume_path, volume_size, volume_format):
|
||||
"""Creates a VHD or VHDX file of a given size."""
|
||||
# vhd is regarded as vpc by qemu
|
||||
if volume_format == self._DISK_FORMAT_VHD:
|
||||
volume_format = self._DISK_FORMAT_VHD_LEGACY
|
||||
|
||||
self._execute('qemu-img', 'create', '-f', volume_format,
|
||||
volume_path, str(volume_size * units.Gi),
|
||||
run_as_root=True)
|
||||
|
||||
@remotefs_drv.locked_volume_id_operation
|
||||
@update_allocation_data()
|
||||
def create_volume(self, volume):
|
||||
return super(SmbfsDriver, self).create_volume(volume)
|
||||
|
||||
def _do_create_volume(self, volume):
|
||||
"""Create a volume on given smbfs_share.
|
||||
|
||||
:param volume: volume reference
|
||||
"""
|
||||
volume_format = self.get_volume_format(volume)
|
||||
volume_path = self.local_path(volume)
|
||||
volume_size = volume.size
|
||||
|
||||
LOG.debug("Creating new volume at %s.", volume_path)
|
||||
|
||||
if os.path.exists(volume_path):
|
||||
msg = _('File already exists at %s.') % volume_path
|
||||
LOG.error(msg)
|
||||
raise exception.InvalidVolume(reason=msg)
|
||||
|
||||
if volume_format in (self._DISK_FORMAT_VHD, self._DISK_FORMAT_VHDX):
|
||||
self._create_windows_image(volume_path, volume_size,
|
||||
volume_format)
|
||||
else:
|
||||
self.img_suffix = None
|
||||
if volume_format == self._DISK_FORMAT_QCOW2:
|
||||
self._create_qcow2_file(volume_path, volume_size)
|
||||
elif self.configuration.smbfs_sparsed_volumes:
|
||||
self._create_sparsed_file(volume_path, volume_size)
|
||||
else:
|
||||
self._create_regular_file(volume_path, volume_size)
|
||||
|
||||
self._set_rw_permissions_for_all(volume_path)
|
||||
|
||||
def _get_capacity_info(self, smbfs_share):
|
||||
"""Calculate available space on the SMBFS share.
|
||||
|
||||
:param smbfs_share: example //172.18.194.100/share
|
||||
"""
|
||||
|
||||
mount_point = self._get_mount_point_for_share(smbfs_share)
|
||||
|
||||
df, _ = self._execute('stat', '-f', '-c', '%S %b %a', mount_point,
|
||||
run_as_root=True)
|
||||
block_size, blocks_total, blocks_avail = map(float, df.split())
|
||||
total_available = block_size * blocks_avail
|
||||
total_size = block_size * blocks_total
|
||||
|
||||
total_allocated = self._get_total_allocated(smbfs_share)
|
||||
return total_size, total_available, total_allocated
|
||||
|
||||
def _find_share(self, volume_size_in_gib):
|
||||
"""Choose SMBFS share among available ones for given volume size.
|
||||
|
||||
For instances with more than one share that meets the criteria, the
|
||||
share with the least "allocated" space will be selected.
|
||||
|
||||
:param volume_size_in_gib: int size in GB
|
||||
"""
|
||||
|
||||
if not self._mounted_shares:
|
||||
raise exception.SmbfsNoSharesMounted()
|
||||
|
||||
target_share = None
|
||||
target_share_reserved = 0
|
||||
|
||||
for smbfs_share in self._mounted_shares:
|
||||
if not self._is_share_eligible(smbfs_share, volume_size_in_gib):
|
||||
continue
|
||||
total_allocated = self._get_total_allocated(smbfs_share)
|
||||
if target_share is not None:
|
||||
if target_share_reserved > total_allocated:
|
||||
target_share = smbfs_share
|
||||
target_share_reserved = total_allocated
|
||||
else:
|
||||
target_share = smbfs_share
|
||||
target_share_reserved = total_allocated
|
||||
|
||||
if target_share is None:
|
||||
raise exception.SmbfsNoSuitableShareFound(
|
||||
volume_size=volume_size_in_gib)
|
||||
|
||||
LOG.debug('Selected %s as target smbfs share.', target_share)
|
||||
|
||||
return target_share
|
||||
|
||||
def _is_share_eligible(self, smbfs_share, volume_size_in_gib):
|
||||
"""Verifies SMBFS share is eligible to host volume with given size.
|
||||
|
||||
First validation step: ratio of actual space (used_space / total_space)
|
||||
is less than 'smbfs_used_ratio'. Second validation step: apparent space
|
||||
allocated (differs from actual space used when using sparse files)
|
||||
and compares the apparent available
|
||||
space (total_available * smbfs_oversub_ratio) to ensure enough space is
|
||||
available for the new volume.
|
||||
|
||||
:param smbfs_share: smbfs share
|
||||
:param volume_size_in_gib: int size in GB
|
||||
"""
|
||||
|
||||
used_ratio = self.configuration.smbfs_used_ratio
|
||||
oversub_ratio = self.configuration.smbfs_oversub_ratio
|
||||
requested_volume_size = volume_size_in_gib * units.Gi
|
||||
|
||||
total_size, total_available, total_allocated = \
|
||||
self._get_capacity_info(smbfs_share)
|
||||
|
||||
apparent_size = max(0, total_size * oversub_ratio)
|
||||
apparent_available = max(0, apparent_size - total_allocated)
|
||||
used = (total_size - total_available) / total_size
|
||||
|
||||
if used > used_ratio:
|
||||
LOG.debug('%s is above smbfs_used_ratio.', smbfs_share)
|
||||
return False
|
||||
if apparent_available <= requested_volume_size:
|
||||
LOG.debug('%s is above smbfs_oversub_ratio.', smbfs_share)
|
||||
return False
|
||||
if total_allocated / total_size >= oversub_ratio:
|
||||
LOG.debug('%s reserved space is above smbfs_oversub_ratio.',
|
||||
smbfs_share)
|
||||
return False
|
||||
return True
|
||||
|
||||
def _create_snapshot_online(self, snapshot, backing_filename,
|
||||
new_snap_path):
|
||||
msg = _("This driver does not support snapshotting in-use volumes.")
|
||||
raise exception.SmbfsException(msg)
|
||||
|
||||
def _delete_snapshot_online(self, context, snapshot, info):
|
||||
msg = _("This driver does not support deleting in-use snapshots.")
|
||||
raise exception.SmbfsException(msg)
|
||||
|
||||
def _do_create_snapshot(self, snapshot, backing_filename, new_snap_path):
|
||||
self._check_snapshot_support(snapshot)
|
||||
super(SmbfsDriver, self)._do_create_snapshot(
|
||||
snapshot, backing_filename, new_snap_path)
|
||||
|
||||
def _check_snapshot_support(self, snapshot):
|
||||
volume_format = self.get_volume_format(snapshot.volume)
|
||||
# qemu-img does not yet support differencing vhd/vhdx
|
||||
if volume_format in (self._DISK_FORMAT_VHD, self._DISK_FORMAT_VHDX):
|
||||
err_msg = _("Snapshots are not supported for this volume "
|
||||
"format: %s") % volume_format
|
||||
raise exception.InvalidVolume(err_msg)
|
||||
|
||||
@remotefs_drv.locked_volume_id_operation
|
||||
@update_allocation_data()
|
||||
def extend_volume(self, volume, size_gb):
|
||||
LOG.info(_LI('Extending volume %s.'), volume.id)
|
||||
self._extend_volume(volume, size_gb)
|
||||
|
||||
def _extend_volume(self, volume, size_gb):
|
||||
volume_path = self.local_path(volume)
|
||||
|
||||
self._check_extend_volume_support(volume, size_gb)
|
||||
LOG.info(_LI('Resizing file to %sG...'), size_gb)
|
||||
|
||||
self._do_extend_volume(volume_path, size_gb, volume.name)
|
||||
|
||||
def _do_extend_volume(self, volume_path, size_gb, volume_name):
|
||||
info = self._qemu_img_info(volume_path, volume_name)
|
||||
fmt = info.file_format
|
||||
|
||||
# Note(lpetrut): as for version 2.0, qemu-img cannot resize
|
||||
# vhd/x images. For the moment, we'll just use an intermediary
|
||||
# conversion in order to be able to do the resize.
|
||||
if fmt in (self._DISK_FORMAT_VHDX, self._DISK_FORMAT_VHD_LEGACY):
|
||||
temp_image = volume_path + '.tmp'
|
||||
image_utils.convert_image(volume_path, temp_image,
|
||||
self._DISK_FORMAT_RAW)
|
||||
image_utils.resize_image(temp_image, size_gb)
|
||||
image_utils.convert_image(temp_image, volume_path, fmt)
|
||||
self._delete(temp_image)
|
||||
else:
|
||||
image_utils.resize_image(volume_path, size_gb)
|
||||
|
||||
if not self._is_file_size_equal(volume_path, size_gb):
|
||||
raise exception.ExtendVolumeError(
|
||||
reason='Resizing image file failed.')
|
||||
|
||||
def _check_extend_volume_support(self, volume, size_gb):
|
||||
volume_path = self.local_path(volume)
|
||||
active_file = self.get_active_image_from_info(volume)
|
||||
active_file_path = os.path.join(self._local_volume_dir(volume),
|
||||
active_file)
|
||||
|
||||
if active_file_path != volume_path:
|
||||
msg = _('Extend volume is only supported for this '
|
||||
'driver when no snapshots exist.')
|
||||
raise exception.InvalidVolume(msg)
|
||||
|
||||
extend_by = int(size_gb) - volume.size
|
||||
if not self._is_share_eligible(volume.provider_location,
|
||||
extend_by):
|
||||
raise exception.ExtendVolumeError(reason='Insufficient space to '
|
||||
'extend volume %s to %sG.'
|
||||
% (volume.id, size_gb))
|
||||
|
||||
@remotefs_drv.locked_volume_id_operation
|
||||
@update_allocation_data()
|
||||
def create_volume_from_snapshot(self, volume, snapshot):
|
||||
return self._create_volume_from_snapshot(volume, snapshot)
|
||||
|
||||
def _copy_volume_from_snapshot(self, snapshot, volume, volume_size):
|
||||
"""Copy data from snapshot to destination volume.
|
||||
|
||||
This is done with a qemu-img convert to raw/qcow2 from the snapshot
|
||||
qcow2.
|
||||
"""
|
||||
|
||||
LOG.debug("Snapshot: %(snap)s, volume: %(vol)s, "
|
||||
"volume_size: %(size)s",
|
||||
{'snap': snapshot.id,
|
||||
'vol': volume.id,
|
||||
'size': volume_size})
|
||||
|
||||
info_path = self._local_path_volume_info(snapshot.volume)
|
||||
snap_info = self._read_info_file(info_path)
|
||||
vol_dir = self._local_volume_dir(snapshot.volume)
|
||||
out_format = self.get_volume_format(volume, qemu_format=True)
|
||||
|
||||
forward_file = snap_info[snapshot.id]
|
||||
forward_path = os.path.join(vol_dir, forward_file)
|
||||
|
||||
# Find the file which backs this file, which represents the point
|
||||
# when this snapshot was created.
|
||||
img_info = self._qemu_img_info(forward_path,
|
||||
snapshot.volume.name)
|
||||
path_to_snap_img = os.path.join(vol_dir, img_info.backing_file)
|
||||
|
||||
LOG.debug("Will copy from snapshot at %s", path_to_snap_img)
|
||||
|
||||
image_utils.convert_image(path_to_snap_img,
|
||||
self.local_path(volume),
|
||||
out_format)
|
||||
self._extend_volume(volume, volume_size)
|
||||
|
||||
self._set_rw_permissions_for_all(self.local_path(volume))
|
||||
|
||||
def copy_image_to_volume(self, context, volume, image_service, image_id):
|
||||
"""Fetch the image from image_service and write it to the volume."""
|
||||
volume_format = self.get_volume_format(volume, qemu_format=True)
|
||||
|
||||
image_utils.fetch_to_volume_format(
|
||||
context, image_service, image_id,
|
||||
self.local_path(volume), volume_format,
|
||||
self.configuration.volume_dd_blocksize)
|
||||
|
||||
self._do_extend_volume(self.local_path(volume),
|
||||
volume.size,
|
||||
volume.name)
|
||||
|
||||
data = image_utils.qemu_img_info(self.local_path(volume))
|
||||
virt_size = data.virtual_size / units.Gi
|
||||
if virt_size != volume.size:
|
||||
raise exception.ImageUnacceptable(
|
||||
image_id=image_id,
|
||||
reason=(_("Expected volume size was %d") % volume.size)
|
||||
+ (_(" but size is now %d.") % virt_size))
|
||||
|
||||
@remotefs_drv.locked_volume_id_operation
|
||||
@update_allocation_data()
|
||||
def create_cloned_volume(self, volume, src_vref):
|
||||
"""Creates a clone of the specified volume."""
|
||||
return self._create_cloned_volume(volume, src_vref)
|
||||
|
||||
def _ensure_share_mounted(self, smbfs_share):
|
||||
mnt_flags = []
|
||||
if self.shares.get(smbfs_share) is not None:
|
||||
mnt_flags = self.shares[smbfs_share]
|
||||
# The domain name must be removed from the
|
||||
# user name when using Samba.
|
||||
mnt_flags = self.parse_credentials(mnt_flags).split()
|
||||
self._remotefsclient.mount(smbfs_share, mnt_flags)
|
||||
|
||||
def parse_options(self, option_str):
|
||||
opts_dict = {}
|
||||
opts_list = []
|
||||
if option_str:
|
||||
for i in option_str.split():
|
||||
if i == '-o':
|
||||
continue
|
||||
for j in i.split(','):
|
||||
tmp_opt = j.split('=')
|
||||
if len(tmp_opt) > 1:
|
||||
opts_dict[tmp_opt[0]] = tmp_opt[1]
|
||||
else:
|
||||
opts_list.append(tmp_opt[0])
|
||||
return opts_list, opts_dict
|
||||
|
||||
def parse_credentials(self, mnt_flags):
|
||||
options_list, options_dict = self.parse_options(mnt_flags)
|
||||
username = (options_dict.pop('user', None) or
|
||||
options_dict.pop('username', None))
|
||||
if username:
|
||||
# Remove the Domain from the user name
|
||||
options_dict['username'] = username.split('\\')[-1]
|
||||
else:
|
||||
options_dict['username'] = 'guest'
|
||||
named_options = ','.join("%s=%s" % (key, val) for (key, val)
|
||||
in options_dict.items())
|
||||
options_list = ','.join(options_list)
|
||||
flags = '-o ' + ','.join([named_options, options_list])
|
||||
|
||||
return flags.strip(',')
|
||||
|
||||
def _get_volume_format_spec(self, volume):
|
||||
vol_type = volume.volume_type
|
||||
extra_specs = {}
|
||||
if vol_type and vol_type.extra_specs:
|
||||
extra_specs = vol_type.extra_specs
|
||||
|
||||
extra_specs.update(volume.metadata or {})
|
||||
|
||||
return (extra_specs.get('volume_format') or
|
||||
self.configuration.smbfs_default_volume_format)
|
||||
|
||||
def _is_file_size_equal(self, path, size):
|
||||
"""Checks if file size at path is equal to size."""
|
||||
data = image_utils.qemu_img_info(path)
|
||||
virt_size = data.virtual_size / units.Gi
|
||||
return virt_size == size
|
@ -13,10 +13,12 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
|
||||
import inspect
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
import decorator
|
||||
from os_brick.remotefs import windows_remotefs as remotefs_brick
|
||||
from os_win import utilsfactory
|
||||
from oslo_config import cfg
|
||||
@ -25,56 +27,171 @@ from oslo_utils import fileutils
|
||||
from oslo_utils import units
|
||||
|
||||
from cinder import exception
|
||||
from cinder.i18n import _, _LI
|
||||
from cinder.i18n import _, _LI, _LW
|
||||
from cinder.image import image_utils
|
||||
from cinder import interface
|
||||
from cinder.volume.drivers import remotefs as remotefs_drv
|
||||
from cinder.volume.drivers import smbfs
|
||||
|
||||
VERSION = '1.1.0'
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
volume_opts = [
|
||||
cfg.StrOpt('smbfs_shares_config',
|
||||
default=r'C:\OpenStack\smbfs_shares.txt',
|
||||
help='File with the list of available smbfs shares.'),
|
||||
cfg.StrOpt('smbfs_allocation_info_file_path',
|
||||
default=r'C:\OpenStack\allocation_data.txt',
|
||||
help=('The path of the automatically generated file containing '
|
||||
'information about volume disk space allocation.')),
|
||||
cfg.StrOpt('smbfs_default_volume_format',
|
||||
default='vhd',
|
||||
choices=['vhd', 'vhdx'],
|
||||
help=('Default format that will be used when creating volumes '
|
||||
'if no volume format is specified.')),
|
||||
cfg.BoolOpt('smbfs_sparsed_volumes',
|
||||
default=True,
|
||||
help=('Create volumes as sparsed files which take no space '
|
||||
'rather than regular files when using raw format, '
|
||||
'in which case volume creation takes lot of time.')),
|
||||
cfg.FloatOpt('smbfs_used_ratio',
|
||||
default=0.95,
|
||||
help=('Percent of ACTUAL usage of the underlying volume '
|
||||
'before no new volumes can be allocated to the volume '
|
||||
'destination.')),
|
||||
cfg.FloatOpt('smbfs_oversub_ratio',
|
||||
default=1.0,
|
||||
help=('This will compare the allocated to available space on '
|
||||
'the volume destination. If the ratio exceeds this '
|
||||
'number, the destination will no longer be valid.')),
|
||||
cfg.StrOpt('smbfs_mount_point_base',
|
||||
default=r'C:\OpenStack\_mnt',
|
||||
help=('Base dir containing mount points for smbfs shares.')),
|
||||
]
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.set_default('smbfs_shares_config', r'C:\OpenStack\smbfs_shares.txt')
|
||||
CONF.set_default('smbfs_allocation_info_file_path',
|
||||
r'C:\OpenStack\allocation_data.txt')
|
||||
CONF.set_default('smbfs_mount_point_base', r'C:\OpenStack\_mnt')
|
||||
CONF.set_default('smbfs_default_volume_format', 'vhd')
|
||||
CONF.register_opts(volume_opts)
|
||||
|
||||
|
||||
def update_allocation_data(delete=False):
|
||||
@decorator.decorator
|
||||
def wrapper(func, inst, *args, **kwargs):
|
||||
ret_val = func(inst, *args, **kwargs)
|
||||
|
||||
call_args = inspect.getcallargs(func, inst, *args, **kwargs)
|
||||
volume = call_args['volume']
|
||||
requested_size = call_args.get('size_gb', None)
|
||||
|
||||
if delete:
|
||||
allocated_size_gb = None
|
||||
else:
|
||||
allocated_size_gb = requested_size or volume.size
|
||||
|
||||
inst.update_disk_allocation_data(volume, allocated_size_gb)
|
||||
return ret_val
|
||||
return wrapper
|
||||
|
||||
|
||||
@interface.volumedriver
|
||||
class WindowsSmbfsDriver(smbfs.SmbfsDriver):
|
||||
# NOTE(lpetrut): This driver is currently inhering the Linux SMBFS driver,
|
||||
# which is being deprecated. This dependency will be removed along with
|
||||
# the Linux SMBFS driver during Pike.
|
||||
SUPPORTED = True
|
||||
class WindowsSmbfsDriver(remotefs_drv.RemoteFSSnapDriver):
|
||||
VERSION = VERSION
|
||||
|
||||
driver_volume_type = 'smbfs'
|
||||
driver_prefix = 'smbfs'
|
||||
volume_backend_name = 'Generic_SMBFS'
|
||||
SHARE_FORMAT_REGEX = r'//.+/.+'
|
||||
VERSION = VERSION
|
||||
|
||||
_DISK_FORMAT_VHD = 'vhd'
|
||||
_DISK_FORMAT_VHD_LEGACY = 'vpc'
|
||||
_DISK_FORMAT_VHDX = 'vhdx'
|
||||
|
||||
# ThirdPartySystems wiki page
|
||||
CI_WIKI_NAME = "Microsoft_iSCSI_CI"
|
||||
|
||||
_MINIMUM_QEMU_IMG_VERSION = '1.6'
|
||||
|
||||
_SUPPORTED_IMAGE_FORMATS = [_DISK_FORMAT_VHD, _DISK_FORMAT_VHDX]
|
||||
_VALID_IMAGE_EXTENSIONS = _SUPPORTED_IMAGE_FORMATS
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self._remotefsclient = None
|
||||
super(WindowsSmbfsDriver, self).__init__(*args, **kwargs)
|
||||
|
||||
self.configuration.append_config_values(volume_opts)
|
||||
|
||||
self.base = getattr(self.configuration,
|
||||
'smbfs_mount_point_base',
|
||||
CONF.smbfs_mount_point_base)
|
||||
opts = getattr(self.configuration,
|
||||
'smbfs_mount_options',
|
||||
CONF.smbfs_mount_options)
|
||||
self._remotefsclient = remotefs_brick.WindowsRemoteFsClient(
|
||||
'cifs', root_helper=None, smbfs_mount_point_base=self.base,
|
||||
smbfs_mount_options=opts, local_path_for_loopback=True)
|
||||
local_path_for_loopback=True)
|
||||
|
||||
self._vhdutils = utilsfactory.get_vhdutils()
|
||||
self._pathutils = utilsfactory.get_pathutils()
|
||||
self._smbutils = utilsfactory.get_smbutils()
|
||||
|
||||
self._alloc_info_file_path = (
|
||||
self.configuration.smbfs_allocation_info_file_path)
|
||||
|
||||
def do_setup(self, context):
|
||||
self._check_os_platform()
|
||||
super(WindowsSmbfsDriver, self).do_setup(context)
|
||||
|
||||
image_utils.check_qemu_img_version(self._MINIMUM_QEMU_IMG_VERSION)
|
||||
|
||||
config = self.configuration.smbfs_shares_config
|
||||
if not config:
|
||||
msg = (_("SMBFS config file not set (smbfs_shares_config)."))
|
||||
LOG.error(msg)
|
||||
raise exception.SmbfsException(msg)
|
||||
if not os.path.exists(config):
|
||||
msg = (_("SMBFS config file at %(config)s doesn't exist.") %
|
||||
{'config': config})
|
||||
LOG.error(msg)
|
||||
raise exception.SmbfsException(msg)
|
||||
if not os.path.isabs(self.base):
|
||||
msg = _("Invalid mount point base: %s") % self.base
|
||||
LOG.error(msg)
|
||||
raise exception.SmbfsException(msg)
|
||||
if not self.configuration.smbfs_oversub_ratio > 0:
|
||||
msg = _(
|
||||
"SMBFS config 'smbfs_oversub_ratio' invalid. Must be > 0: "
|
||||
"%s") % self.configuration.smbfs_oversub_ratio
|
||||
|
||||
LOG.error(msg)
|
||||
raise exception.SmbfsException(msg)
|
||||
|
||||
if not 0 < self.configuration.smbfs_used_ratio <= 1:
|
||||
msg = _("SMBFS config 'smbfs_used_ratio' invalid. Must be > 0 "
|
||||
"and <= 1.0: %s") % self.configuration.smbfs_used_ratio
|
||||
LOG.error(msg)
|
||||
raise exception.SmbfsException(msg)
|
||||
|
||||
self.shares = {} # address : options
|
||||
self._ensure_shares_mounted()
|
||||
self._setup_allocation_data()
|
||||
|
||||
@remotefs_drv.locked_volume_id_operation
|
||||
def initialize_connection(self, volume, connector):
|
||||
"""Allow connection to connector and return connection info.
|
||||
|
||||
:param volume: volume reference
|
||||
:param connector: connector reference
|
||||
"""
|
||||
# Find active image
|
||||
active_file = self.get_active_image_from_info(volume)
|
||||
fmt = self.get_volume_format(volume)
|
||||
|
||||
data = {'export': volume.provider_location,
|
||||
'format': fmt,
|
||||
'name': active_file}
|
||||
if volume.provider_location in self.shares:
|
||||
data['options'] = self.shares[volume.provider_location]
|
||||
return {
|
||||
'driver_volume_type': self.driver_volume_type,
|
||||
'data': data,
|
||||
'mount_point_base': self._get_mount_point_base()
|
||||
}
|
||||
|
||||
def _check_os_platform(self):
|
||||
if sys.platform != 'win32':
|
||||
@ -82,6 +199,196 @@ class WindowsSmbfsDriver(smbfs.SmbfsDriver):
|
||||
"driver supports only Win32 platforms.") % sys.platform
|
||||
raise exception.SmbfsException(_msg)
|
||||
|
||||
def _setup_allocation_data(self):
|
||||
if not os.path.exists(self._alloc_info_file_path):
|
||||
fileutils.ensure_tree(
|
||||
os.path.dirname(self._alloc_info_file_path))
|
||||
self._allocation_data = {}
|
||||
self._update_allocation_data_file()
|
||||
else:
|
||||
with open(self._alloc_info_file_path, 'r') as f:
|
||||
self._allocation_data = json.load(f)
|
||||
|
||||
def update_disk_allocation_data(self, volume, virtual_size_gb=None):
|
||||
volume_name = volume.name
|
||||
smbfs_share = volume.provider_location
|
||||
if smbfs_share:
|
||||
share_hash = self._get_hash_str(smbfs_share)
|
||||
else:
|
||||
return
|
||||
|
||||
share_alloc_data = self._allocation_data.get(share_hash, {})
|
||||
old_virtual_size = share_alloc_data.get(volume_name, 0)
|
||||
total_allocated = share_alloc_data.get('total_allocated', 0)
|
||||
|
||||
if virtual_size_gb:
|
||||
share_alloc_data[volume_name] = virtual_size_gb
|
||||
total_allocated += virtual_size_gb - old_virtual_size
|
||||
elif share_alloc_data.get(volume_name):
|
||||
# The volume is deleted.
|
||||
del share_alloc_data[volume_name]
|
||||
total_allocated -= old_virtual_size
|
||||
|
||||
share_alloc_data['total_allocated'] = total_allocated
|
||||
self._allocation_data[share_hash] = share_alloc_data
|
||||
self._update_allocation_data_file()
|
||||
|
||||
def _update_allocation_data_file(self):
|
||||
with open(self._alloc_info_file_path, 'w') as f:
|
||||
json.dump(self._allocation_data, f)
|
||||
|
||||
def _get_total_allocated(self, smbfs_share):
|
||||
share_hash = self._get_hash_str(smbfs_share)
|
||||
share_alloc_data = self._allocation_data.get(share_hash, {})
|
||||
total_allocated = share_alloc_data.get('total_allocated', 0) << 30
|
||||
return float(total_allocated)
|
||||
|
||||
def _find_share(self, volume_size_in_gib):
|
||||
"""Choose SMBFS share among available ones for given volume size.
|
||||
|
||||
For instances with more than one share that meets the criteria, the
|
||||
share with the least "allocated" space will be selected.
|
||||
|
||||
:param volume_size_in_gib: int size in GB
|
||||
"""
|
||||
|
||||
if not self._mounted_shares:
|
||||
raise exception.SmbfsNoSharesMounted()
|
||||
|
||||
target_share = None
|
||||
target_share_reserved = 0
|
||||
|
||||
for smbfs_share in self._mounted_shares:
|
||||
if not self._is_share_eligible(smbfs_share, volume_size_in_gib):
|
||||
continue
|
||||
total_allocated = self._get_total_allocated(smbfs_share)
|
||||
if target_share is not None:
|
||||
if target_share_reserved > total_allocated:
|
||||
target_share = smbfs_share
|
||||
target_share_reserved = total_allocated
|
||||
else:
|
||||
target_share = smbfs_share
|
||||
target_share_reserved = total_allocated
|
||||
|
||||
if target_share is None:
|
||||
raise exception.SmbfsNoSuitableShareFound(
|
||||
volume_size=volume_size_in_gib)
|
||||
|
||||
LOG.debug('Selected %s as target smbfs share.', target_share)
|
||||
|
||||
return target_share
|
||||
|
||||
def _is_share_eligible(self, smbfs_share, volume_size_in_gib):
|
||||
"""Verifies SMBFS share is eligible to host volume with given size.
|
||||
|
||||
First validation step: ratio of actual space (used_space / total_space)
|
||||
is less than 'smbfs_used_ratio'. Second validation step: apparent space
|
||||
allocated (differs from actual space used when using sparse files)
|
||||
and compares the apparent available
|
||||
space (total_available * smbfs_oversub_ratio) to ensure enough space is
|
||||
available for the new volume.
|
||||
|
||||
:param smbfs_share: smbfs share
|
||||
:param volume_size_in_gib: int size in GB
|
||||
"""
|
||||
|
||||
used_ratio = self.configuration.smbfs_used_ratio
|
||||
oversub_ratio = self.configuration.smbfs_oversub_ratio
|
||||
requested_volume_size = volume_size_in_gib * units.Gi
|
||||
|
||||
total_size, total_available, total_allocated = \
|
||||
self._get_capacity_info(smbfs_share)
|
||||
|
||||
apparent_size = max(0, total_size * oversub_ratio)
|
||||
apparent_available = max(0, apparent_size - total_allocated)
|
||||
used = (total_size - total_available) / total_size
|
||||
|
||||
if used > used_ratio:
|
||||
LOG.debug('%s is above smbfs_used_ratio.', smbfs_share)
|
||||
return False
|
||||
if apparent_available <= requested_volume_size:
|
||||
LOG.debug('%s is above smbfs_oversub_ratio.', smbfs_share)
|
||||
return False
|
||||
if total_allocated / total_size >= oversub_ratio:
|
||||
LOG.debug('%s reserved space is above smbfs_oversub_ratio.',
|
||||
smbfs_share)
|
||||
return False
|
||||
return True
|
||||
|
||||
def local_path(self, volume):
|
||||
"""Get volume path (mounted locally fs path) for given volume.
|
||||
|
||||
:param volume: volume reference
|
||||
"""
|
||||
volume_path_template = self._get_local_volume_path_template(volume)
|
||||
volume_path = self._lookup_local_volume_path(volume_path_template)
|
||||
if volume_path:
|
||||
return volume_path
|
||||
|
||||
# The image does not exist, so retrieve the volume format
|
||||
# in order to build the path.
|
||||
fmt = self.get_volume_format(volume)
|
||||
volume_path = volume_path_template + '.' + fmt
|
||||
return volume_path
|
||||
|
||||
def _get_local_volume_path_template(self, volume):
|
||||
local_dir = self._local_volume_dir(volume)
|
||||
local_path_template = os.path.join(local_dir, volume.name)
|
||||
return local_path_template
|
||||
|
||||
def _lookup_local_volume_path(self, volume_path_template):
|
||||
for ext in self._SUPPORTED_IMAGE_FORMATS:
|
||||
volume_path = (volume_path_template + '.' + ext
|
||||
if ext else volume_path_template)
|
||||
if os.path.exists(volume_path):
|
||||
return volume_path
|
||||
|
||||
def _get_new_snap_path(self, snapshot):
|
||||
vol_path = self.local_path(snapshot.volume)
|
||||
snap_path, ext = os.path.splitext(vol_path)
|
||||
snap_path += '.' + snapshot.id + ext
|
||||
return snap_path
|
||||
|
||||
def get_volume_format(self, volume, qemu_format=False):
|
||||
volume_path_template = self._get_local_volume_path_template(volume)
|
||||
volume_path = self._lookup_local_volume_path(volume_path_template)
|
||||
|
||||
if volume_path:
|
||||
ext = os.path.splitext(volume_path)[1].strip('.').lower()
|
||||
if ext in self._SUPPORTED_IMAGE_FORMATS:
|
||||
volume_format = ext
|
||||
else:
|
||||
# Hyper-V relies on file extensions so we're enforcing them.
|
||||
raise exception.SmbfsException(
|
||||
_("Invalid image file extension: %s") % ext)
|
||||
else:
|
||||
volume_format = (
|
||||
self._get_volume_format_spec(volume) or
|
||||
self.configuration.smbfs_default_volume_format)
|
||||
|
||||
if qemu_format and volume_format == self._DISK_FORMAT_VHD:
|
||||
volume_format = self._DISK_FORMAT_VHD_LEGACY
|
||||
elif volume_format == self._DISK_FORMAT_VHD_LEGACY:
|
||||
volume_format = self._DISK_FORMAT_VHD
|
||||
|
||||
return volume_format
|
||||
|
||||
def _get_volume_format_spec(self, volume):
|
||||
vol_type = volume.volume_type
|
||||
extra_specs = {}
|
||||
if vol_type and vol_type.extra_specs:
|
||||
extra_specs = vol_type.extra_specs
|
||||
|
||||
extra_specs.update(volume.metadata or {})
|
||||
|
||||
return (extra_specs.get('volume_format') or
|
||||
self.configuration.smbfs_default_volume_format)
|
||||
|
||||
@remotefs_drv.locked_volume_id_operation
|
||||
@update_allocation_data()
|
||||
def create_volume(self, volume):
|
||||
return super(WindowsSmbfsDriver, self).create_volume(volume)
|
||||
|
||||
def _do_create_volume(self, volume):
|
||||
volume_path = self.local_path(volume)
|
||||
volume_format = self.get_volume_format(volume)
|
||||
@ -91,8 +398,7 @@ class WindowsSmbfsDriver(smbfs.SmbfsDriver):
|
||||
err_msg = _('File already exists at: %s') % volume_path
|
||||
raise exception.InvalidVolume(err_msg)
|
||||
|
||||
if volume_format not in (self._DISK_FORMAT_VHD,
|
||||
self._DISK_FORMAT_VHDX):
|
||||
if volume_format not in self._SUPPORTED_IMAGE_FORMATS:
|
||||
err_msg = _("Unsupported volume format: %s ") % volume_format
|
||||
raise exception.InvalidVolume(err_msg)
|
||||
|
||||
@ -104,6 +410,28 @@ class WindowsSmbfsDriver(smbfs.SmbfsDriver):
|
||||
mnt_flags = self.shares[smbfs_share]
|
||||
self._remotefsclient.mount(smbfs_share, mnt_flags)
|
||||
|
||||
@remotefs_drv.locked_volume_id_operation
|
||||
@update_allocation_data(delete=True)
|
||||
def delete_volume(self, volume):
|
||||
"""Deletes a logical volume."""
|
||||
if not volume.provider_location:
|
||||
LOG.warning(_LW('Volume %s does not have provider_location '
|
||||
'specified, skipping.'), volume.name)
|
||||
return
|
||||
|
||||
self._ensure_share_mounted(volume.provider_location)
|
||||
volume_dir = self._local_volume_dir(volume)
|
||||
mounted_path = os.path.join(volume_dir,
|
||||
self.get_active_image_from_info(volume))
|
||||
if os.path.exists(mounted_path):
|
||||
self._delete(mounted_path)
|
||||
else:
|
||||
LOG.debug("Skipping deletion of volume %s as it does not exist.",
|
||||
mounted_path)
|
||||
|
||||
info_path = self._local_path_volume_info(volume)
|
||||
self._delete(info_path)
|
||||
|
||||
def _delete(self, path):
|
||||
fileutils.delete_if_exists(path)
|
||||
|
||||
@ -160,10 +488,50 @@ class WindowsSmbfsDriver(smbfs.SmbfsDriver):
|
||||
self._vhdutils.create_differencing_vhd(new_snap_path,
|
||||
backing_file_full_path)
|
||||
|
||||
def _do_extend_volume(self, volume_path, size_gb, volume_name=None):
|
||||
def _create_snapshot_online(self, snapshot, backing_filename,
|
||||
new_snap_path):
|
||||
msg = _("This driver does not support snapshotting in-use volumes.")
|
||||
raise exception.SmbfsException(msg)
|
||||
|
||||
def _delete_snapshot_online(self, context, snapshot, info):
|
||||
msg = _("This driver does not support deleting in-use snapshots.")
|
||||
raise exception.SmbfsException(msg)
|
||||
|
||||
@remotefs_drv.locked_volume_id_operation
|
||||
@update_allocation_data()
|
||||
def extend_volume(self, volume, size_gb):
|
||||
LOG.info(_LI('Extending volume %s.'), volume.id)
|
||||
|
||||
self._check_extend_volume_support(volume, size_gb)
|
||||
self._extend_volume(volume, size_gb)
|
||||
|
||||
def _extend_volume(self, volume, size_gb):
|
||||
volume_path = self.local_path(volume)
|
||||
|
||||
LOG.info(_LI('Resizing file %(volume_path)s to %(size_gb)sGB.'),
|
||||
dict(volume_path=volume_path, size_gb=size_gb))
|
||||
|
||||
self._vhdutils.resize_vhd(volume_path, size_gb * units.Gi,
|
||||
is_file_max_size=False)
|
||||
|
||||
def _check_extend_volume_support(self, volume, size_gb):
|
||||
volume_path = self.local_path(volume)
|
||||
active_file = self.get_active_image_from_info(volume)
|
||||
active_file_path = os.path.join(self._local_volume_dir(volume),
|
||||
active_file)
|
||||
|
||||
if active_file_path != volume_path:
|
||||
msg = _('Extend volume is only supported for this '
|
||||
'driver when no snapshots exist.')
|
||||
raise exception.InvalidVolume(msg)
|
||||
|
||||
extend_by = int(size_gb) - volume.size
|
||||
if not self._is_share_eligible(volume.provider_location,
|
||||
extend_by):
|
||||
raise exception.ExtendVolumeError(reason='Insufficient space to '
|
||||
'extend volume %s to %sG.'
|
||||
% (volume.id, size_gb))
|
||||
|
||||
@remotefs_drv.locked_volume_id_operation
|
||||
def copy_volume_to_image(self, context, volume, image_service, image_meta):
|
||||
"""Copy the volume to the specified image."""
|
||||
@ -216,6 +584,17 @@ class WindowsSmbfsDriver(smbfs.SmbfsDriver):
|
||||
volume.size * units.Gi,
|
||||
is_file_max_size=False)
|
||||
|
||||
@remotefs_drv.locked_volume_id_operation
|
||||
@update_allocation_data()
|
||||
def create_volume_from_snapshot(self, volume, snapshot):
|
||||
return self._create_volume_from_snapshot(volume, snapshot)
|
||||
|
||||
@remotefs_drv.locked_volume_id_operation
|
||||
@update_allocation_data()
|
||||
def create_cloned_volume(self, volume, src_vref):
|
||||
"""Creates a clone of the specified volume."""
|
||||
return self._create_cloned_volume(volume, src_vref)
|
||||
|
||||
def _copy_volume_from_snapshot(self, snapshot, volume, volume_size):
|
||||
"""Copy data from snapshot to destination volume."""
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user