diff --git a/cinder/tests/unit/volume/drivers/vmware/test_vmware_datastore.py b/cinder/tests/unit/volume/drivers/vmware/test_vmware_datastore.py index 604e9e0b959..30b455311f9 100644 --- a/cinder/tests/unit/volume/drivers/vmware/test_vmware_datastore.py +++ b/cinder/tests/unit/volume/drivers/vmware/test_vmware_datastore.py @@ -17,6 +17,8 @@ Unit tests for datastore module. """ +import re + import mock from oslo_utils import units @@ -73,9 +75,10 @@ class DatastoreTest(test.TestCase): def _create_summary( self, ds, free_space=units.Mi, _type=ds_sel.DatastoreType.VMFS, capacity=2 * units.Mi, accessible=True): - return mock.Mock(datastore=ds, freeSpace=free_space, type=_type, - capacity=capacity, accessible=accessible, - name=ds.value) + summary = mock.Mock(datastore=ds, freeSpace=free_space, type=_type, + capacity=capacity, accessible=accessible) + summary.name = ds.value + return summary def _create_host(self, value): host = mock.Mock(spec=['_type', 'value'], name=value) @@ -145,10 +148,15 @@ class DatastoreTest(test.TestCase): 'host': host_mounts1} # valid datastore - ds10 = self._create_datastore('ds-10') - ds10_props = {'summary': self._create_summary(ds10), + ds1a = self._create_datastore('ds-1a') + ds1a_props = {'summary': self._create_summary(ds1a), + 'host': host_mounts1} + filter_by_profile.return_value = {ds1a: ds1a_props} + + # datastore name not matching the regex filter + ds1b = self._create_datastore('ds-1b') + ds1b_props = {'summary': self._create_summary(ds1b), 'host': host_mounts1} - filter_by_profile.return_value = {ds10: ds10_props} datastores = {ds1: ds1_props, ds2: ds2_props, @@ -159,8 +167,10 @@ class DatastoreTest(test.TestCase): ds7: ds7_props, ds8: ds8_props, ds9: ds9_props, - ds10: ds10_props} + ds1a: ds1a_props, + ds1b: ds1b_props} profile_id = mock.sentinel.profile_id + self._ds_sel._ds_regex = re.compile("ds-[1-9a]{1,2}$") datastores = self._ds_sel._filter_datastores( datastores, 512, @@ -169,9 +179,9 @@ class DatastoreTest(test.TestCase): {ds_sel.DatastoreType.VMFS, ds_sel.DatastoreType.NFS}, valid_host_refs=[host1, host2]) - self.assertEqual({ds10: ds10_props}, datastores) + self.assertEqual({ds1a: ds1a_props}, datastores) filter_by_profile.assert_called_once_with( - {ds9: ds9_props, ds10: ds10_props}, + {ds9: ds9_props, ds1a: ds1a_props}, profile_id) def test_filter_datastores_with_empty_datastores(self): diff --git a/cinder/tests/unit/volume/drivers/vmware/test_vmware_vmdk.py b/cinder/tests/unit/volume/drivers/vmware/test_vmware_vmdk.py index c417858fd61..9969d8d6364 100644 --- a/cinder/tests/unit/volume/drivers/vmware/test_vmware_vmdk.py +++ b/cinder/tests/unit/volume/drivers/vmware/test_vmware_vmdk.py @@ -17,6 +17,8 @@ Test suite for VMware vCenter VMDK driver. """ +import re + import ddt import mock from oslo_utils import units @@ -98,6 +100,7 @@ class VMwareVcVmdkDriverTestCase(test.TestCase): self._config.vmware_adapter_type = self.ADAPTER_TYPE self._config.vmware_snapshot_format = self.SNAPSHOT_FORMAT self._config.vmware_lazy_create = True + self._config.vmware_datastore_regex = None self._db = mock.Mock() self._driver = vmdk.VMwareVcVmdkDriver(configuration=self._config, @@ -1680,6 +1683,7 @@ class VMwareVcVmdkDriverTestCase(test.TestCase): label='OpenStack Cinder') @mock.patch.object(VMDK_DRIVER, '_validate_params') + @mock.patch('re.compile') @mock.patch.object(VMDK_DRIVER, '_get_vc_version') @mock.patch.object(VMDK_DRIVER, '_validate_vcenter_version') @mock.patch('oslo_vmware.pbm.get_pbm_wsdl_location') @@ -1690,8 +1694,9 @@ class VMwareVcVmdkDriverTestCase(test.TestCase): @mock.patch.object(VMDK_DRIVER, 'session') def _test_do_setup( self, session, vops, ds_sel_cls, vops_cls, register_extension, - get_pbm_wsdl_loc, validate_vc_version, get_vc_version, - validate_params, enable_pbm=True): + get_pbm_wsdl_loc, validate_vc_version, get_vc_version, re_compile, + validate_params, enable_pbm=True, ds_regex_pat=None, + invalid_regex=False): if enable_pbm: ver_str = '5.5' pbm_wsdl = mock.sentinel.pbm_wsdl @@ -1705,30 +1710,51 @@ class VMwareVcVmdkDriverTestCase(test.TestCase): cluster_refs = {'cls-1': cls_1, 'cls-2': cls_2} vops.get_cluster_refs.return_value = cluster_refs - self._driver.do_setup(mock.ANY) + self._driver.configuration.vmware_datastore_regex = ds_regex_pat + ds_regex = None + if ds_regex_pat: + if invalid_regex: + re_compile.side_effect = re.error("error") + else: + ds_regex = mock.sentinel.ds_regex + re_compile.return_value = ds_regex - validate_params.assert_called_once_with() - get_vc_version.assert_called_once_with() - validate_vc_version.assert_called_once_with(ver_str) - if enable_pbm: - get_pbm_wsdl_loc.assert_called_once_with(ver_str) - self.assertEqual(pbm_wsdl, self._driver.pbm_wsdl) - self.assertEqual(enable_pbm, self._driver._storage_policy_enabled) - register_extension.assert_called_once() - vops_cls.assert_called_once_with( - session, self._driver.configuration.vmware_max_objects_retrieval, - vmdk.EXTENSION_KEY, vmdk.EXTENSION_TYPE) - self.assertEqual(vops_cls.return_value, self._driver._volumeops) - ds_sel_cls.assert_called_once_with( - vops, - session, - self._driver.configuration.vmware_max_objects_retrieval) - self.assertEqual(ds_sel_cls.return_value, self._driver._ds_sel) - vops.get_cluster_refs.assert_called_once_with( - self._driver.configuration.vmware_cluster_name) - vops.build_backing_ref_cache.assert_called_once_with() - self.assertEqual(list(cluster_refs.values()), - list(self._driver._clusters)) + if ds_regex_pat and invalid_regex: + self.assertRaises(cinder_exceptions.InvalidInput, + self._driver.do_setup, + mock.ANY) + validate_params.assert_called_once_with() + else: + self._driver.do_setup(mock.ANY) + + validate_params.assert_called_once_with() + get_vc_version.assert_called_once_with() + validate_vc_version.assert_called_once_with(ver_str) + if enable_pbm: + get_pbm_wsdl_loc.assert_called_once_with(ver_str) + self.assertEqual(pbm_wsdl, self._driver.pbm_wsdl) + self.assertEqual(enable_pbm, self._driver._storage_policy_enabled) + register_extension.assert_called_once() + vops_cls.assert_called_once_with( + session, + self._driver.configuration.vmware_max_objects_retrieval, + vmdk.EXTENSION_KEY, + vmdk.EXTENSION_TYPE) + self.assertEqual(vops_cls.return_value, self._driver._volumeops) + ds_sel_cls.assert_called_once_with( + vops, + session, + self._driver.configuration.vmware_max_objects_retrieval, + ds_regex=ds_regex) + self.assertEqual(ds_sel_cls.return_value, self._driver._ds_sel) + vops.get_cluster_refs.assert_called_once_with( + self._driver.configuration.vmware_cluster_name) + vops.build_backing_ref_cache.assert_called_once_with() + self.assertEqual(list(cluster_refs.values()), + list(self._driver._clusters)) + + if ds_regex_pat: + re_compile.assert_called_once_with(ds_regex_pat) def test_do_setup(self): self._test_do_setup() @@ -1757,6 +1783,12 @@ class VMwareVcVmdkDriverTestCase(test.TestCase): validate_vc_version.assert_called_once_with(ver_str) get_pbm_wsdl_loc.assert_called_once_with(ver_str) + def test_do_setup_with_ds_regex(self): + self._test_do_setup(ds_regex_pat='foo') + + def test_do_setup_with_invalid_ds_regex(self): + self._test_do_setup(ds_regex_pat='(foo', invalid_regex=True) + @mock.patch.object(VMDK_DRIVER, 'volumeops') def test_get_dc(self, vops): dc_1 = mock.sentinel.dc_1 diff --git a/cinder/volume/drivers/vmware/datastore.py b/cinder/volume/drivers/vmware/datastore.py index 28e9a2d6938..5de7fb20d8c 100644 --- a/cinder/volume/drivers/vmware/datastore.py +++ b/cinder/volume/drivers/vmware/datastore.py @@ -54,10 +54,11 @@ class DatastoreSelector(object): PROFILE_NAME = "storageProfileName" # TODO(vbala) Remove dependency on volumeops. - def __init__(self, vops, session, max_objects): + def __init__(self, vops, session, max_objects, ds_regex=None): self._vops = vops self._session = session self._max_objects = max_objects + self._ds_regex = ds_regex self._profile_id_cache = {} @coordination.synchronized('vmware-datastore-profile-{profile_name}') @@ -129,6 +130,9 @@ class DatastoreSelector(object): if (summary is None or host_mounts is None): return False + if self._ds_regex and not self._ds_regex.match(summary.name): + return False + if (hard_anti_affinity_ds and ds_ref.value in hard_anti_affinity_ds): return False diff --git a/cinder/volume/drivers/vmware/vmdk.py b/cinder/volume/drivers/vmware/vmdk.py index 88b540daabd..02df68b1ca5 100644 --- a/cinder/volume/drivers/vmware/vmdk.py +++ b/cinder/volume/drivers/vmware/vmdk.py @@ -23,6 +23,7 @@ machine is never powered on and is often referred as the shadow VM. """ import math +import re from oslo_config import cfg from oslo_log import log as logging @@ -150,6 +151,9 @@ vmdk_opts = [ ' lazily when the volume is created without any source. ' 'The backend volume is created when the volume is ' 'attached, uploaded to image service or during backup.'), + cfg.StrOpt('vmware_datastore_regex', + help='Regular expression pattern to match the name of ' + 'datastores where backend volumes are created.') ] CONF = cfg.CONF @@ -258,7 +262,8 @@ class VMwareVcVmdkDriver(driver.VolumeDriver): # improve scalability of querying volumes in backend (bug 1600754) # 3.1.0 - support adapter type change using retype # 3.2.0 - config option to disable lazy creation of backend volume - VERSION = '3.2.0' + # 3.3.0 - config option to specify datastore name regex + VERSION = '3.3.0' # ThirdPartySystems wiki page CI_WIKI_NAME = "VMware_CI" @@ -281,6 +286,7 @@ class VMwareVcVmdkDriver(driver.VolumeDriver): self._ds_sel = None self._clusters = None self._dc_cache = {} + self._ds_regex = None @property def volumeops(self): @@ -1825,6 +1831,14 @@ class VMwareVcVmdkDriver(driver.VolumeDriver): """Any initialization the volume driver does while starting.""" self._validate_params() + regex_pattern = self.configuration.vmware_datastore_regex + if regex_pattern: + try: + self._ds_regex = re.compile(regex_pattern) + except re.error: + raise exception.InvalidInput(reason=_( + "Invalid regular expression: %s.") % regex_pattern) + # Validate vCenter version. self._vc_version = self._get_vc_version() self._validate_vcenter_version(self._vc_version) @@ -1851,7 +1865,7 @@ class VMwareVcVmdkDriver(driver.VolumeDriver): self._volumeops = volumeops.VMwareVolumeOps( self.session, max_objects, EXTENSION_KEY, EXTENSION_TYPE) self._ds_sel = hub.DatastoreSelector( - self.volumeops, self.session, max_objects) + self.volumeops, self.session, max_objects, ds_regex=self._ds_regex) # Get clusters to be used for backing VM creation. cluster_names = self.configuration.vmware_cluster_name diff --git a/releasenotes/notes/vmware_vmdk_datastore_regex-fe7b68ad69ef7384.yaml b/releasenotes/notes/vmware_vmdk_datastore_regex-fe7b68ad69ef7384.yaml new file mode 100644 index 00000000000..6201cd2915e --- /dev/null +++ b/releasenotes/notes/vmware_vmdk_datastore_regex-fe7b68ad69ef7384.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + VMware VMDK driver and FCD driver now support a config option + ``vmware_datastore_regex`` to specify the regular expression + pattern to match the name of datastores where backend volumes + are created.