Merge "VMware: Create volume backing in specific clusters"

This commit is contained in:
Jenkins 2015-07-23 12:46:00 +00:00 committed by Gerrit Code Review
commit 20f780262d
7 changed files with 283 additions and 14 deletions

View File

@ -383,6 +383,14 @@ class DatastoreTest(test.TestCase):
self._vops.get_connected_hosts.reset_mock()
self._vops.get_connected_hosts.return_value = None
def test_select_datastore_with_empty_host_list(self):
size_bytes = units.Ki
req = {self._ds_sel.SIZE_BYTES: size_bytes}
self._vops.get_hosts.return_value = mock.Mock(objects=[])
self.assertEqual((), self._ds_sel.select_datastore(req, hosts=[]))
self._vops.get_hosts.assert_called_once_with()
@mock.patch('oslo_vmware.pbm.get_profile_id_by_name')
@mock.patch('cinder.volume.drivers.vmware.datastore.DatastoreSelector.'
'_filter_by_profile')

View File

@ -149,6 +149,7 @@ class VMwareEsxVmdkDriverTestCase(test.TestCase):
TMP_DIR = "/vmware-tmp"
CA_FILE = "/etc/ssl/rui-ca-cert.pem"
VMDK_DRIVER = vmdk.VMwareEsxVmdkDriver
CLUSTERS = ["cls-1", "cls-2"]
def setUp(self):
super(VMwareEsxVmdkDriverTestCase, self).setUp()
@ -166,10 +167,11 @@ class VMwareEsxVmdkDriverTestCase(test.TestCase):
self._config.vmware_tmp_dir = self.TMP_DIR
self._config.vmware_ca_file = self.CA_FILE
self._config.vmware_insecure = False
self._config.vmware_cluster_name = self.CLUSTERS
self._db = mock.Mock()
self._driver = vmdk.VMwareEsxVmdkDriver(configuration=self._config,
db=self._db)
api_retry_count = self._config.vmware_api_retry_count,
api_retry_count = self._config.vmware_api_retry_count
task_poll_interval = self._config.vmware_task_poll_interval,
self._session = api.VMwareAPISession(self.IP, self.USERNAME,
self.PASSWORD, api_retry_count,
@ -1773,21 +1775,37 @@ class VMwareVcVmdkDriverTestCase(VMwareEsxVmdkDriverTestCase):
version = self._driver._get_vc_version()
self.assertEqual(ver.LooseVersion('6.0.1'), version)
@mock.patch('cinder.volume.drivers.vmware.volumeops.VMwareVolumeOps')
@mock.patch('cinder.volume.drivers.vmware.vmdk.VMwareVcVmdkDriver.'
'_get_vc_version')
@mock.patch('cinder.volume.drivers.vmware.vmdk.VMwareVcVmdkDriver.'
'session', new_callable=mock.PropertyMock)
def test_do_setup_with_pbm_disabled(self, session, get_vc_version):
def test_do_setup_with_pbm_disabled(self, session, get_vc_version,
vops_cls):
session_obj = mock.Mock(name='session')
session.return_value = session_obj
get_vc_version.return_value = ver.LooseVersion('5.0')
cluster_refs = mock.Mock()
cluster_refs.values.return_value = mock.sentinel.cluster_refs
vops = mock.Mock()
vops.get_cluster_refs.return_value = cluster_refs
def vops_side_effect(session, max_objects):
vops._session = session
vops._max_objects = max_objects
return vops
vops_cls.side_effect = vops_side_effect
self._driver.do_setup(mock.ANY)
self.assertFalse(self._driver._storage_policy_enabled)
get_vc_version.assert_called_once_with()
self.assertEqual(session_obj, self._driver.volumeops._session)
self.assertEqual(session_obj, self._driver.ds_sel._session)
self.assertEqual(mock.sentinel.cluster_refs, self._driver._clusters)
vops.get_cluster_refs.assert_called_once_with(self.CLUSTERS)
@mock.patch('oslo_vmware.pbm.get_pbm_wsdl_location')
@mock.patch('cinder.volume.drivers.vmware.vmdk.VMwareVcVmdkDriver.'
@ -1807,12 +1825,14 @@ class VMwareVcVmdkDriverTestCase(VMwareEsxVmdkDriverTestCase):
get_pbm_wsdl_location.assert_called_once_with(
six.text_type(vc_version))
@mock.patch('cinder.volume.drivers.vmware.volumeops.VMwareVolumeOps')
@mock.patch('oslo_vmware.pbm.get_pbm_wsdl_location')
@mock.patch('cinder.volume.drivers.vmware.vmdk.VMwareVcVmdkDriver.'
'_get_vc_version')
@mock.patch('cinder.volume.drivers.vmware.vmdk.VMwareVcVmdkDriver.'
'session', new_callable=mock.PropertyMock)
def test_do_setup(self, session, get_vc_version, get_pbm_wsdl_location):
def test_do_setup(self, session, get_vc_version, get_pbm_wsdl_location,
vops_cls):
session_obj = mock.Mock(name='session')
session.return_value = session_obj
@ -1820,6 +1840,18 @@ class VMwareVcVmdkDriverTestCase(VMwareEsxVmdkDriverTestCase):
get_vc_version.return_value = vc_version
get_pbm_wsdl_location.return_value = 'file:///pbm.wsdl'
cluster_refs = mock.Mock()
cluster_refs.values.return_value = mock.sentinel.cluster_refs
vops = mock.Mock()
vops.get_cluster_refs.return_value = cluster_refs
def vops_side_effect(session, max_objects):
vops._session = session
vops._max_objects = max_objects
return vops
vops_cls.side_effect = vops_side_effect
self._driver.do_setup(mock.ANY)
self.assertTrue(self._driver._storage_policy_enabled)
@ -1828,6 +1860,8 @@ class VMwareVcVmdkDriverTestCase(VMwareEsxVmdkDriverTestCase):
six.text_type(vc_version))
self.assertEqual(session_obj, self._driver.volumeops._session)
self.assertEqual(session_obj, self._driver.ds_sel._session)
self.assertEqual(mock.sentinel.cluster_refs, self._driver._clusters)
vops.get_cluster_refs.assert_called_once_with(self.CLUSTERS)
@mock.patch.object(VMDK_DRIVER, '_extend_volumeops_virtual_disk')
@mock.patch.object(VMDK_DRIVER, '_create_backing')
@ -2578,6 +2612,106 @@ class VMwareVcVmdkDriverTestCase(VMwareEsxVmdkDriverTestCase):
close.assert_called_once_with(fd)
delete_if_exists.assert_called_once_with(tmp)
@mock.patch.object(VMDK_DRIVER, 'volumeops')
@mock.patch.object(VMDK_DRIVER, 'ds_sel')
def test_select_datastore(self, ds_sel, vops):
cls_1 = mock.sentinel.cls_1
cls_2 = mock.sentinel.cls_2
self._driver._clusters = [cls_1, cls_2]
host_1 = mock.sentinel.host_1
host_2 = mock.sentinel.host_2
host_3 = mock.sentinel.host_3
vops.get_cluster_hosts.side_effect = [[host_1, host_2], [host_3]]
best_candidate = mock.sentinel.best_candidate
ds_sel.select_datastore.return_value = best_candidate
req = mock.sentinel.req
self.assertEqual(best_candidate, self._driver._select_datastore(req))
exp_calls = [mock.call(cls_1), mock.call(cls_2)]
self.assertEqual(exp_calls, vops.get_cluster_hosts.call_args_list)
ds_sel.select_datastore.assert_called_once_with(
req, hosts=[host_1, host_2, host_3])
@mock.patch.object(VMDK_DRIVER, 'volumeops')
@mock.patch.object(VMDK_DRIVER, 'ds_sel')
def test_select_datastore_with_no_best_candidate(self, ds_sel, vops):
cls_1 = mock.sentinel.cls_1
cls_2 = mock.sentinel.cls_2
self._driver._clusters = [cls_1, cls_2]
host_1 = mock.sentinel.host_1
host_2 = mock.sentinel.host_2
host_3 = mock.sentinel.host_3
vops.get_cluster_hosts.side_effect = [[host_1, host_2], [host_3]]
ds_sel.select_datastore.return_value = ()
req = mock.sentinel.req
self.assertRaises(vmdk_exceptions.NoValidDatastoreException,
self._driver._select_datastore,
req)
exp_calls = [mock.call(cls_1), mock.call(cls_2)]
self.assertEqual(exp_calls, vops.get_cluster_hosts.call_args_list)
ds_sel.select_datastore.assert_called_once_with(
req, hosts=[host_1, host_2, host_3])
@mock.patch.object(VMDK_DRIVER, 'volumeops')
@mock.patch.object(VMDK_DRIVER, 'ds_sel')
def test_select_datastore_with_single_host(self, ds_sel, vops):
cls_1 = mock.sentinel.cls_1
cls_2 = mock.sentinel.cls_2
self._driver._clusters = [cls_1, cls_2]
host_1 = mock.sentinel.host_1
best_candidate = mock.sentinel.best_candidate
ds_sel.select_datastore.return_value = best_candidate
req = mock.sentinel.req
self.assertEqual(best_candidate,
self._driver._select_datastore(req, host_1))
ds_sel.select_datastore.assert_called_once_with(req, hosts=[host_1])
self.assertFalse(vops.get_cluster_hosts.called)
@mock.patch.object(VMDK_DRIVER, 'volumeops')
@mock.patch.object(VMDK_DRIVER, 'ds_sel')
def test_select_datastore_with_empty_clusters(self, ds_sel, vops):
self._driver._clusters = None
best_candidate = mock.sentinel.best_candidate
ds_sel.select_datastore.return_value = best_candidate
req = mock.sentinel.req
self.assertEqual(best_candidate, self._driver._select_datastore(req))
ds_sel.select_datastore.assert_called_once_with(req, hosts=None)
self.assertFalse(vops.get_cluster_hosts.called)
@mock.patch.object(VMDK_DRIVER, 'volumeops')
@mock.patch.object(VMDK_DRIVER, 'ds_sel')
def test_select_datastore_with_no_valid_host(self, ds_sel, vops):
cls_1 = mock.sentinel.cls_1
cls_2 = mock.sentinel.cls_2
self._driver._clusters = [cls_1, cls_2]
vops.get_cluster_hosts.side_effect = [[], []]
req = mock.sentinel.req
self.assertRaises(vmdk_exceptions.NoValidHostException,
self._driver._select_datastore, req)
exp_calls = [mock.call(cls_1), mock.call(cls_2)]
self.assertEqual(exp_calls, vops.get_cluster_hosts.call_args_list)
self.assertFalse(ds_sel.called)
@mock.patch.object(VMDK_DRIVER, 'volumeops')
@mock.patch.object(VMDK_DRIVER, 'ds_sel')
def test_relocate_backing_nop(self, ds_sel, vops):

View File

@ -1510,6 +1510,58 @@ class VolumeOpsTestCase(test.TestCase):
eagerZero=False)
self.session.wait_for_task.assert_called_once_with(task)
@mock.patch('cinder.volume.drivers.vmware.volumeops.VMwareVolumeOps.'
'_get_all_clusters')
def test_get_cluster_refs(self, get_all_clusters):
cls_1 = mock.sentinel.cls_1
cls_2 = mock.sentinel.cls_2
clusters = {"cls_1": cls_1, "cls_2": cls_2}
get_all_clusters.return_value = clusters
self.assertEqual({"cls_2": cls_2},
self.vops.get_cluster_refs(["cls_2"]))
@mock.patch('cinder.volume.drivers.vmware.volumeops.VMwareVolumeOps.'
'_get_all_clusters')
def test_get_cluster_refs_with_invalid_cluster(self, get_all_clusters):
cls_1 = mock.sentinel.cls_1
cls_2 = mock.sentinel.cls_2
clusters = {"cls_1": cls_1, "cls_2": cls_2}
get_all_clusters.return_value = clusters
self.assertRaises(vmdk_exceptions.ClusterNotFoundException,
self.vops.get_cluster_refs,
["cls_1", "cls_3"])
def test_get_cluster_hosts(self):
host_1 = mock.sentinel.host_1
host_2 = mock.sentinel.host_2
hosts = mock.Mock(ManagedObjectReference=[host_1, host_2])
self.session.invoke_api.return_value = hosts
cluster = mock.sentinel.cluster
ret = self.vops.get_cluster_hosts(cluster)
self.assertEqual([host_1, host_2], ret)
self.session.invoke_api.assert_called_once_with(vim_util,
'get_object_property',
self.session.vim,
cluster,
'host')
def test_get_cluster_hosts_with_no_host(self):
self.session.invoke_api.return_value = None
cluster = mock.sentinel.cluster
ret = self.vops.get_cluster_hosts(cluster)
self.assertEqual([], ret)
self.session.invoke_api.assert_called_once_with(vim_util,
'get_object_property',
self.session.vim,
cluster,
'host')
class VirtualDiskPathTest(test.TestCase):
"""Unit tests for VirtualDiskPath."""

View File

@ -205,7 +205,7 @@ class DatastoreSelector(object):
if profile_name is not None:
profile_id = self.get_profile_id(profile_name)
if hosts is None:
if not hosts:
hosts = self._get_all_hosts()
LOG.debug("Using hosts: %(hosts)s for datastore selection based on "

View File

@ -45,3 +45,13 @@ class ProfileNotFoundException(exceptions.VMwareDriverException):
class NoValidDatastoreException(exceptions.VMwareDriverException):
"""Thrown when there are no valid datastores."""
message = _("There are no valid datastores.")
class ClusterNotFoundException(exceptions.VMwareDriverException):
"""Thrown when the given cluster cannot be found."""
message = _("Compute cluster: %(cluster)s not found.")
class NoValidHostException(exceptions.VMwareDriverException):
"""Thrown when there are no valid ESX hosts."""
message = _("There are no valid ESX hosts.")

View File

@ -117,6 +117,10 @@ vmdk_opts = [
'verified. If false, then the default CA truststore is '
'used for verification. This option is ignored if '
'"vmware_ca_file" is set.'),
cfg.MultiStrOpt('vmware_cluster_name',
default=None,
help='Name of a vCenter compute cluster where volumes '
'should be created.'),
]
CONF = cfg.CONF
@ -226,6 +230,7 @@ class VMwareEsxVmdkDriver(driver.VolumeDriver):
# directly to ESX
self._storage_policy_enabled = False
self._ds_sel = None
self._clusters = None
@property
def session(self):
@ -461,13 +466,28 @@ class VMwareEsxVmdkDriver(driver.VolumeDriver):
def _relocate_backing(self, volume, backing, host):
pass
def _get_hosts(self, clusters):
hosts = []
if clusters:
for cluster in clusters:
hosts.extend(self.volumeops.get_cluster_hosts(cluster))
return hosts
def _select_datastore(self, req, host=None):
"""Selects datastore satisfying the given requirements.
:return: (host, resource_pool, summary)
"""
hosts = None
if host:
hosts = [host]
elif self._clusters:
hosts = self._get_hosts(self._clusters)
if not hosts:
LOG.error(_LE("There are no valid hosts available in "
"configured cluster(s): %s."), self._clusters)
raise vmdk_exceptions.NoValidHostException()
hosts = [host] if host else None
best_candidate = self.ds_sel.select_datastore(req, hosts=hosts)
if not best_candidate:
LOG.error(_LE("There is no valid datastore satisfying "
@ -1836,6 +1856,13 @@ class VMwareVcVmdkDriver(VMwareEsxVmdkDriver):
self._volumeops = volumeops.VMwareVolumeOps(self.session, max_objects)
self._ds_sel = hub.DatastoreSelector(self.volumeops, self.session)
# Get clusters to be used for backing VM creation.
cluster_names = self.configuration.vmware_cluster_name
if cluster_names:
self._clusters = self.volumeops.get_cluster_refs(
cluster_names).values()
LOG.info(_LI("Using compute cluster(s): %s."), cluster_names)
LOG.info(_LI("Successfully setup driver: %(driver)s for server: "
"%(ip)s."), {'driver': self.__class__.__name__,
'ip': self.configuration.vmware_host_ip})
@ -1889,15 +1916,7 @@ class VMwareVcVmdkDriver(VMwareEsxVmdkDriver):
req[hub.DatastoreSelector.PROFILE_NAME] = backing_profile
# Select datastore satisfying the requirements.
best_candidate = self.ds_sel.select_datastore(req, hosts=[host])
if not best_candidate:
# No candidate datastore to relocate.
msg = _("There are no datastores matching volume requirements;"
" can't relocate volume: %s.") % volume['name']
LOG.error(msg)
raise vmdk_exceptions.NoValidDatastoreException(msg)
(host, resource_pool, summary) = best_candidate
(host, resource_pool, summary) = self._select_datastore(req, host)
dc = self.volumeops.get_dc(resource_pool)
folder = self._get_volume_group_folder(dc)

View File

@ -1413,3 +1413,49 @@ class VMwareVolumeOps(object):
profile_manager,
profileIds=profile_ids)
return profiles[0].name
def _get_all_clusters(self):
clusters = {}
retrieve_result = self._session.invoke_api(vim_util, 'get_objects',
self._session.vim,
'ClusterComputeResource',
self._max_objects)
while retrieve_result:
if retrieve_result.objects:
for cluster in retrieve_result.objects:
name = urllib.unquote(cluster.propSet[0].val)
clusters[name] = cluster.obj
retrieve_result = self.continue_retrieval(retrieve_result)
return clusters
def get_cluster_refs(self, names):
"""Get references to given clusters.
:param names: list of cluster names
:return: Dictionary of cluster names to references
"""
clusters = self._get_all_clusters()
for name in names:
if name not in clusters:
LOG.error(_LE("Compute cluster: %s not found."), name)
raise vmdk_exceptions.ClusterNotFoundException(cluster=name)
return {name: clusters[name] for name in names}
def get_cluster_hosts(self, cluster):
"""Get hosts in the given cluster.
:param cluster: cluster reference
:return: references to hosts in the cluster
"""
hosts = self._session.invoke_api(vim_util,
'get_object_property',
self._session.vim,
cluster,
'host')
host_refs = []
if hosts and hosts.ManagedObjectReference:
host_refs.extend(hosts.ManagedObjectReference)
return host_refs