Remove Linux SMBFS driver
This driver has been deprecated in Ocata and is now being removed. Note that the Windows SMBFS driver was inheriting it so we're pulling in the common logic. Change-Id: I3dd8ceb042223402bfc29050f74ad3672badf816
This commit is contained in:
parent
a45d5c7751
commit
137fd502a9
|
@ -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…
Reference in New Issue