mox.StubOutWithMock(drv, '_find_image_in_cache')
mox.StubOutWithMock(drv, '_do_clone_rel_img_cache')
mox.StubOutWithMock(drv, '_post_clone_image')
- mox.StubOutWithMock(drv, '_is_share_vol_compatible')
+ mox.StubOutWithMock(drv, '_is_share_clone_compatible')
utils.get_volume_extra_specs(mox_lib.IgnoreArg())
drv._find_image_in_cache(mox_lib.IgnoreArg()).AndReturn(
[('share', 'file_name')])
- drv._is_share_vol_compatible(mox_lib.IgnoreArg(),
- mox_lib.IgnoreArg()).AndReturn(True)
+ drv._is_share_clone_compatible(mox_lib.IgnoreArg(),
+ mox_lib.IgnoreArg()).AndReturn(True)
drv._do_clone_rel_img_cache('file_name', 'vol', 'share', 'file_name')
drv._post_clone_image(volume)
mox.StubOutWithMock(utils, 'get_volume_extra_specs')
mox.StubOutWithMock(drv, '_find_image_in_cache')
mox.StubOutWithMock(drv, '_is_cloneable_share')
- mox.StubOutWithMock(drv, '_is_share_vol_compatible')
+ mox.StubOutWithMock(drv, '_is_share_clone_compatible')
utils.get_volume_extra_specs(mox_lib.IgnoreArg())
drv._find_image_in_cache(mox_lib.IgnoreArg()).AndReturn([])
drv._is_cloneable_share(
mox_lib.IgnoreArg()).AndReturn('127.0.0.1:/share')
- drv._is_share_vol_compatible(mox_lib.IgnoreArg(),
- mox_lib.IgnoreArg()).AndReturn(False)
+ drv._is_share_clone_compatible(mox_lib.IgnoreArg(),
+ mox_lib.IgnoreArg()).AndReturn(False)
mox.ReplayAll()
(prop, cloned) = drv.clone_image(
mox.StubOutWithMock(drv, '_discover_file_till_timeout')
mox.StubOutWithMock(drv, '_set_rw_permissions')
mox.StubOutWithMock(drv, '_resize_image_file')
- mox.StubOutWithMock(drv, '_is_share_vol_compatible')
+ mox.StubOutWithMock(drv, '_is_share_clone_compatible')
utils.get_volume_extra_specs(mox_lib.IgnoreArg())
drv._find_image_in_cache(mox_lib.IgnoreArg()).AndReturn([])
drv._is_cloneable_share(
mox_lib.IgnoreArg()).AndReturn('127.0.0.1:/share')
- drv._is_share_vol_compatible(mox_lib.IgnoreArg(),
- mox_lib.IgnoreArg()).AndReturn(True)
+ drv._is_share_clone_compatible(mox_lib.IgnoreArg(),
+ mox_lib.IgnoreArg()).AndReturn(True)
drv._get_mount_point_for_share(mox_lib.IgnoreArg()).AndReturn('/mnt')
image_utils.qemu_img_info('/mnt/img-id', run_as_root=True).\
AndReturn(self.get_img_info('raw'))
mox.StubOutWithMock(drv, '_resize_image_file')
mox.StubOutWithMock(image_utils, 'convert_image')
mox.StubOutWithMock(drv, '_register_image_in_cache')
- mox.StubOutWithMock(drv, '_is_share_vol_compatible')
+ mox.StubOutWithMock(drv, '_is_share_clone_compatible')
utils.get_volume_extra_specs(mox_lib.IgnoreArg())
drv._find_image_in_cache(mox_lib.IgnoreArg()).AndReturn([])
drv._is_cloneable_share('nfs://127.0.0.1/share/img-id').AndReturn(
'127.0.0.1:/share')
- drv._is_share_vol_compatible(mox_lib.IgnoreArg(),
- mox_lib.IgnoreArg()).AndReturn(True)
+ drv._is_share_clone_compatible(mox_lib.IgnoreArg(),
+ mox_lib.IgnoreArg()).AndReturn(True)
drv._get_mount_point_for_share('127.0.0.1:/share').AndReturn('/mnt')
image_utils.qemu_img_info('/mnt/img-id', run_as_root=True).\
AndReturn(self.get_img_info('notraw'))
mox.StubOutWithMock(drv, '_discover_file_till_timeout')
mox.StubOutWithMock(image_utils, 'convert_image')
mox.StubOutWithMock(drv, '_register_image_in_cache')
- mox.StubOutWithMock(drv, '_is_share_vol_compatible')
+ mox.StubOutWithMock(drv, '_is_share_clone_compatible')
mox.StubOutWithMock(drv, '_do_qos_for_volume')
mox.StubOutWithMock(drv, 'local_path')
drv._find_image_in_cache(mox_lib.IgnoreArg()).AndReturn([])
drv._is_cloneable_share('nfs://127.0.0.1/share/img-id').AndReturn(
'127.0.0.1:/share')
- drv._is_share_vol_compatible(mox_lib.IgnoreArg(),
- mox_lib.IgnoreArg()).AndReturn(True)
+ drv._is_share_clone_compatible(mox_lib.IgnoreArg(),
+ mox_lib.IgnoreArg()).AndReturn(True)
drv._get_mount_point_for_share('127.0.0.1:/share').AndReturn('/mnt')
image_utils.qemu_img_info('/mnt/img-id', run_as_root=True).\
AndReturn(self.get_img_info('notraw'))
mox.StubOutWithMock(image_utils, 'convert_image')
mox.StubOutWithMock(drv, '_do_qos_for_volume')
mox.StubOutWithMock(drv, '_register_image_in_cache')
- mox.StubOutWithMock(drv, '_is_share_vol_compatible')
+ mox.StubOutWithMock(drv, '_is_share_clone_compatible')
mox.StubOutWithMock(drv, 'local_path')
utils.get_volume_extra_specs(mox_lib.IgnoreArg())
drv._find_image_in_cache(mox_lib.IgnoreArg()).AndReturn([])
drv._is_cloneable_share('nfs://127.0.0.1/share/img-id').AndReturn(
'127.0.0.1:/share')
- drv._is_share_vol_compatible(mox_lib.IgnoreArg(),
- mox_lib.IgnoreArg()).AndReturn(True)
+ drv._is_share_clone_compatible(mox_lib.IgnoreArg(),
+ mox_lib.IgnoreArg()).AndReturn(True)
drv._get_mount_point_for_share('127.0.0.1:/share').AndReturn('/mnt')
image_utils.qemu_img_info('/mnt/img-id', run_as_root=True).\
AndReturn(self.get_img_info('notraw'))
fake.NFS_SHARE, [])
self.assertEqual([], result)
+
+ @ddt.data({'has_space': True, 'expected': True},
+ {'has_space': False, 'expected': False})
+ @ddt.unpack
+ def test_is_share_clone_compatible(self, has_space, expected):
+ mock_share_has_space_for_clone = self.mock_object(
+ self.driver, '_share_has_space_for_clone')
+ mock_share_has_space_for_clone.return_value = has_space
+
+ result = self.driver._is_share_clone_compatible(fake.VOLUME,
+ fake.NFS_SHARE)
+
+ self.assertEqual(expected, result)
import os
import copy
+import ddt
import mock
from os_brick.remotefs import remotefs as remotefs_brick
from oslo_utils import units
from cinder.volume.drivers import nfs
+@ddt.ddt
class NetAppNfsDriverTestCase(test.TestCase):
def setUp(self):
super(NetAppNfsDriverTestCase, self).setUp()
configuration = mock.Mock()
configuration.reserved_percentage = 0
configuration.nfs_mount_point_base = '/mnt/test'
- configuration.nfs_used_ratio = 0.89
- configuration.nfs_oversub_ratio = 3.0
+ configuration.nfs_used_ratio = 1.0
+ configuration.nfs_oversub_ratio = 1.1
kwargs = {'configuration': configuration}
result = self.driver._get_export_path(fake.VOLUME_ID)
self.assertEqual(expected, result)
+
+ def test_is_share_clone_compatible(self):
+ self.assertRaises(NotImplementedError,
+ self.driver._is_share_clone_compatible,
+ fake.NFS_VOLUME,
+ fake.NFS_SHARE)
+
+ @ddt.data(
+ {'size': 12, 'thin': False, 'over': 1.0, 'res': 0, 'expected': True},
+ {'size': 12, 'thin': False, 'over': 1.0, 'res': 5, 'expected': False},
+ {'size': 12, 'thin': True, 'over': 1.0, 'res': 5, 'expected': False},
+ {'size': 12, 'thin': True, 'over': 1.1, 'res': 5, 'expected': True},
+ {'size': 240, 'thin': True, 'over': 20.0, 'res': 0, 'expected': True},
+ {'size': 241, 'thin': True, 'over': 20.0, 'res': 0, 'expected': False},
+ )
+ @ddt.unpack
+ def test_share_has_space_for_clone(self, size, thin, over, res, expected):
+ total_bytes = 20 * units.Gi
+ available_bytes = 12 * units.Gi
+
+ with mock.patch.object(self.driver,
+ '_get_capacity_info',
+ return_value=(
+ total_bytes, available_bytes)):
+ with mock.patch.object(self.driver,
+ 'over_subscription_ratio',
+ over):
+ with mock.patch.object(self.driver,
+ 'reserved_percentage',
+ res):
+ result = self.driver._share_has_space_for_clone(
+ fake.NFS_SHARE,
+ size,
+ thin=thin)
+ self.assertEqual(expected, result)
+
+ @ddt.data(
+ {'size': 12, 'thin': False, 'over': 1.0, 'res': 0, 'expected': True},
+ {'size': 12, 'thin': False, 'over': 1.0, 'res': 5, 'expected': False},
+ {'size': 12, 'thin': True, 'over': 1.0, 'res': 5, 'expected': False},
+ {'size': 12, 'thin': True, 'over': 1.1, 'res': 5, 'expected': True},
+ {'size': 240, 'thin': True, 'over': 20.0, 'res': 0, 'expected': True},
+ {'size': 241, 'thin': True, 'over': 20.0, 'res': 0, 'expected': False},
+ )
+ @ddt.unpack
+ @mock.patch.object(nfs_base.NetAppNfsDriver, '_get_capacity_info')
+ def test_share_has_space_for_clone2(self,
+ mock_get_capacity,
+ size, thin, over, res, expected):
+ total_bytes = 20 * units.Gi
+ available_bytes = 12 * units.Gi
+ mock_get_capacity.return_value = (total_bytes, available_bytes)
+
+ with mock.patch.object(self.driver,
+ 'over_subscription_ratio',
+ over):
+ with mock.patch.object(self.driver,
+ 'reserved_percentage',
+ res):
+ result = self.driver._share_has_space_for_clone(
+ fake.NFS_SHARE,
+ size,
+ thin=thin)
+ self.assertEqual(expected, result)
mock_loopingcall.assert_has_calls([
mock.call(mock_remove_unused_qos_policy_groups)])
self.assertTrue(harvest_qos_periodic_task.start.called)
+
+ @ddt.data(
+ {'space': True, 'ssc': True, 'match': True, 'expected': True},
+ {'space': True, 'ssc': True, 'match': False, 'expected': False},
+ {'space': True, 'ssc': False, 'match': True, 'expected': True},
+ {'space': True, 'ssc': False, 'match': False, 'expected': True},
+ {'space': False, 'ssc': True, 'match': True, 'expected': False},
+ {'space': False, 'ssc': True, 'match': False, 'expected': False},
+ {'space': False, 'ssc': False, 'match': True, 'expected': False},
+ {'space': False, 'ssc': False, 'match': False, 'expected': False},
+ )
+ @ddt.unpack
+ @mock.patch.object(nfs_cmode.NetAppCmodeNfsDriver,
+ '_is_share_vol_type_match')
+ @mock.patch.object(nfs_cmode.NetAppCmodeNfsDriver,
+ '_share_has_space_for_clone')
+ @mock.patch.object(nfs_cmode.NetAppCmodeNfsDriver,
+ '_is_volume_thin_provisioned')
+ def test_is_share_clone_compatible(self,
+ mock_is_volume_thin_provisioned,
+ mock_share_has_space_for_clone,
+ mock_is_share_vol_type_match,
+ space, ssc, match, expected):
+ mock_share_has_space_for_clone.return_value = space
+ mock_is_share_vol_type_match.return_value = match
+
+ with mock.patch.object(self.driver, 'ssc_enabled', ssc):
+ result = self.driver._is_share_clone_compatible(fake.VOLUME,
+ fake.NFS_SHARE)
+ self.assertEqual(expected, result)
+
+ @ddt.data(
+ {'sparsed': True, 'ssc': True, 'vol_thin': True, 'expected': True},
+ {'sparsed': True, 'ssc': True, 'vol_thin': False, 'expected': True},
+ {'sparsed': True, 'ssc': False, 'vol_thin': True, 'expected': True},
+ {'sparsed': True, 'ssc': False, 'vol_thin': False, 'expected': True},
+ {'sparsed': False, 'ssc': True, 'vol_thin': True, 'expected': True},
+ {'sparsed': False, 'ssc': True, 'vol_thin': False, 'expected': False},
+ {'sparsed': False, 'ssc': False, 'vol_thin': True, 'expected': False},
+ {'sparsed': False, 'ssc': False, 'vol_thin': False, 'expected': False},
+ )
+ @ddt.unpack
+ def test_is_volume_thin_provisioned(
+ self, sparsed, ssc, vol_thin, expected):
+ fake_volume = object()
+ ssc_vols = {'thin': {fake_volume if vol_thin else None}}
+
+ with mock.patch.object(self.driver, 'ssc_enabled', ssc):
+ with mock.patch.object(self.driver, 'ssc_vols', ssc_vols):
+ with mock.patch.object(self.driver.configuration,
+ 'nfs_sparsed_volumes',
+ sparsed):
+ result = self.driver._is_volume_thin_provisioned(
+ fake_volume)
+
+ self.assertEqual(expected, result)
LOG.debug('No share match found for ip %s', ip)
return None
- def _is_share_vol_compatible(self, volume, share):
- """Checks if share is compatible with volume to host it."""
- return self._is_share_eligible(share, volume['size'])
+ def _is_share_clone_compatible(self, volume, share):
+ """Checks if share is compatible with volume to host its clone."""
+ thin = self.configuration.nfs_sparsed_volumes
+ return self._share_has_space_for_clone(share, volume['size'], thin)
def _check_volume_type(self, volume, share, file_name, extra_specs):
"""Matches a volume type for share file."""
(share, file_name) = res
LOG.debug('Cache share: %s', share)
if (share and
- self._is_share_vol_compatible(volume, share)):
+ self._is_share_clone_compatible(volume, share)):
try:
self._do_clone_rel_img_cache(
file_name, volume['name'], share, file_name)
run_as_root = self._execute_as_root
for loc in image_locations:
share = self._is_cloneable_share(loc)
- if share and self._is_share_vol_compatible(volume, share):
+ if share and self._is_share_clone_compatible(volume, share):
LOG.debug('Share is cloneable %s', share)
volume['provider_location'] = share
(__, ___, img_file) = loc.rpartition('/')
path = self.local_path(volume)
self._resize_image_file(path, new_size)
- def _is_share_vol_compatible(self, volume, share):
- """Checks if share is compatible with volume to host it."""
+ def _is_share_clone_compatible(self, volume, share):
+ """Checks if share is compatible with volume to host its clone."""
raise NotImplementedError()
+ def _share_has_space_for_clone(self, share, size_in_gib, thin=True):
+ """Is there space on the share for a clone given the original size?"""
+ requested_size = size_in_gib * units.Gi
+
+ total_size, total_available = self._get_capacity_info(share)
+
+ reserved_ratio = self.reserved_percentage / 100.0
+ reserved = int(round(total_size * reserved_ratio))
+ available = max(0, total_available - reserved)
+ if thin:
+ available = available * self.over_subscription_ratio
+
+ return available >= requested_size
+
def _check_share_can_hold_size(self, share, size):
"""Checks if volume can hold image with size."""
_tot_size, tot_available = self._get_capacity_info(
return vol
return None
- def _is_share_vol_compatible(self, volume, share):
- """Checks if share is compatible with volume to host it."""
- compatible = self._is_share_eligible(share, volume['size'])
+ def _is_share_clone_compatible(self, volume, share):
+ """Checks if share is compatible with volume to host its clone."""
+ thin = self._is_volume_thin_provisioned(volume)
+ compatible = self._share_has_space_for_clone(share,
+ volume['size'],
+ thin)
if compatible and self.ssc_enabled:
matched = self._is_share_vol_type_match(volume, share)
compatible = compatible and matched
return compatible
+ def _is_volume_thin_provisioned(self, volume):
+ if self.configuration.nfs_sparsed_volumes:
+ return True
+ if self.ssc_enabled and volume in self.ssc_vols['thin']:
+ return True
+ return False
+
def _is_share_vol_type_match(self, volume, share):
"""Checks if share matches volume type."""
netapp_vol = self._get_vol_for_share(share)