deb-cinder/cinder/tests/unit/test_vmware_datastore.py
Vipin Balachandran c20778fe6b VMware: Skip unsupported datastore types
vCenter supports different types of datastores such as
VMFS, NFS etc. Currently the VMDK driver supports only
vSAN, VMFS and NFS datastores for storing volume vmdks.
This patch adds a check to skip unsupported datastore
types during volume creation.

Closes-bug: #1519316
Change-Id: I314abbe0de09bb304859c72b27054784eeef9044
2015-11-24 16:59:13 +05:30

469 lines
20 KiB
Python

# Copyright (c) 2014 VMware, Inc.
# 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.
"""
Unit tests for datastore module.
"""
import mock
from oslo_utils import units
from oslo_vmware import exceptions
from cinder import test
from cinder.volume.drivers.vmware import datastore as ds_sel
from cinder.volume.drivers.vmware import exceptions as vmdk_exceptions
class DatastoreTest(test.TestCase):
"""Unit tests for Datastore."""
def setUp(self):
super(DatastoreTest, self).setUp()
self._session = mock.Mock()
self._vops = mock.Mock()
self._ds_sel = ds_sel.DatastoreSelector(self._vops, self._session)
@mock.patch('oslo_vmware.pbm.get_profile_id_by_name')
def test_get_profile_id(self, get_profile_id_by_name):
profile_id = mock.sentinel.profile_id
get_profile_id_by_name.return_value = profile_id
profile_name = mock.sentinel.profile_name
self.assertEqual(profile_id, self._ds_sel.get_profile_id(profile_name))
get_profile_id_by_name.assert_called_once_with(self._session,
profile_name)
@mock.patch('oslo_vmware.pbm.get_profile_id_by_name')
def test_get_profile_id_with_invalid_profile(self, get_profile_id_by_name):
get_profile_id_by_name.return_value = None
profile_name = mock.sentinel.profile_name
self.assertRaises(vmdk_exceptions.ProfileNotFoundException,
self._ds_sel.get_profile_id,
profile_name)
get_profile_id_by_name.assert_called_once_with(self._session,
profile_name)
def _create_datastore(self, moref):
return mock.Mock(value=moref)
def _create_summary(
self, ds, free_space=units.Mi, _type=ds_sel.DatastoreType.VMFS,
capacity=2 * units.Mi):
return mock.Mock(datastore=ds, freeSpace=free_space, type=_type,
capacity=capacity)
def _create_host(self, value):
host = mock.Mock(spec=['_type', 'value'])
host._type = 'HostSystem'
host.value = value
return host
def test_filter_datastores_with_unsupported_type(self):
ds_1 = self._create_datastore('ds-1')
ds_2 = self._create_datastore('ds-2')
datastores = [ds_1, ds_2]
self._vops.get_summary.side_effect = [
self._create_summary(ds_1),
self._create_summary(ds_2, _type='foo')]
res = self._ds_sel._filter_datastores(
datastores, units.Ki, None, None, None)
self.assertEqual(1, len(res))
self.assertEqual(ds_1, res[0].datastore)
@mock.patch('cinder.volume.drivers.vmware.datastore.DatastoreSelector.'
'_filter_by_profile')
def test_filter_datastores(self, filter_by_profile):
# Test with empty datastore list.
datastores = []
size_bytes = 2 * units.Mi
profile_id = mock.sentinel.profile_id
hard_anti_affinity_datastores = None
hard_affinity_ds_types = None
self.assertEqual([], self._ds_sel._filter_datastores(
datastores, size_bytes, profile_id, hard_anti_affinity_datastores,
hard_affinity_ds_types))
# Test with single datastore with hard anti-affinity.
ds_1 = self._create_datastore('ds-1')
datastores = [ds_1]
hard_anti_affinity_datastores = [ds_1.value]
self.assertEqual([], self._ds_sel._filter_datastores(
datastores, size_bytes, profile_id, hard_anti_affinity_datastores,
hard_affinity_ds_types))
# Extend previous case with a profile non-compliant datastore.
ds_2 = self._create_datastore('ds-2')
datastores.append(ds_2)
filter_by_profile.return_value = []
self.assertEqual([], self._ds_sel._filter_datastores(
datastores, size_bytes, profile_id, hard_anti_affinity_datastores,
hard_affinity_ds_types))
filter_by_profile.assert_called_once_with([ds_2], profile_id)
# Extend previous case with a less free space datastore.
ds_3 = self._create_datastore('ds-3')
datastores.append(ds_3)
filter_by_profile.return_value = [ds_3]
free_space_list = [units.Mi]
type_list = [ds_sel.DatastoreType.NFS]
self._vops.get_summary.side_effect = (
lambda ds: self._create_summary(ds,
free_space_list.pop(0),
type_list.pop(0)))
self.assertEqual([], self._ds_sel._filter_datastores(
datastores, size_bytes, profile_id, hard_anti_affinity_datastores,
hard_affinity_ds_types))
# Extend previous case with a datastore not satisfying hard affinity
# datastore type requirement.
ds_4 = self._create_datastore('ds-4')
datastores.append(ds_4)
filter_by_profile.return_value = [ds_3, ds_4]
free_space_list = [units.Mi, 4 * units.Mi]
type_list = [ds_sel.DatastoreType.NFS, ds_sel.DatastoreType.VSAN]
hard_affinity_ds_types = [ds_sel.DatastoreType.NFS]
self.assertEqual([], self._ds_sel._filter_datastores(
datastores, size_bytes, profile_id, hard_anti_affinity_datastores,
hard_affinity_ds_types))
# Modify the previous case to remove hard affinity datastore type
# requirement.
free_space_list = [units.Mi, 4 * units.Mi]
type_list = [ds_sel.DatastoreType.NFS, ds_sel.DatastoreType.VSAN]
hard_affinity_ds_types = None
res = self._ds_sel._filter_datastores(
datastores, size_bytes, profile_id, hard_anti_affinity_datastores,
hard_affinity_ds_types)
self.assertTrue(len(res) == 1)
self.assertEqual(ds_4, res[0].datastore)
# Extend the previous case by adding a datastore satisfying
# hard affinity datastore type requirement.
ds_5 = self._create_datastore('ds-5')
datastores.append(ds_5)
filter_by_profile.return_value = [ds_3, ds_4, ds_5]
free_space_list = [units.Mi, 4 * units.Mi, 5 * units.Mi]
type_list = [ds_sel.DatastoreType.NFS, ds_sel.DatastoreType.VSAN,
ds_sel.DatastoreType.VMFS]
hard_affinity_ds_types = [ds_sel.DatastoreType.VMFS]
res = self._ds_sel._filter_datastores(
datastores, size_bytes, profile_id, hard_anti_affinity_datastores,
hard_affinity_ds_types)
self.assertTrue(len(res) == 1)
self.assertEqual(ds_5, res[0].datastore)
# Modify the previous case to have two datastores satisfying
# hard affinity datastore type requirement.
free_space_list = [units.Mi, 4 * units.Mi, 5 * units.Mi]
type_list = [ds_sel.DatastoreType.NFS, ds_sel.DatastoreType.VSAN,
ds_sel.DatastoreType.VSAN]
hard_affinity_ds_types = [ds_sel.DatastoreType.VSAN]
res = self._ds_sel._filter_datastores(
datastores, size_bytes, profile_id, hard_anti_affinity_datastores,
hard_affinity_ds_types)
self.assertTrue(len(res) == 2)
self.assertEqual(ds_4, res[0].datastore)
self.assertEqual(ds_5, res[1].datastore)
# Clear side effects.
self._vops.get_summary.side_effect = None
def test_select_best_summary(self):
# No tie-- all datastores with different host mount count.
summary_1 = self._create_summary(mock.sentinel.ds_1,
free_space=units.Mi,
capacity=2 * units.Mi)
summary_2 = self._create_summary(mock.sentinel.ds_2,
free_space=units.Mi,
capacity=3 * units.Mi)
summary_3 = self._create_summary(mock.sentinel.ds_3,
free_space=units.Mi,
capacity=4 * units.Mi)
host_1 = self._create_host('host-1')
host_2 = self._create_host('host-2')
host_3 = self._create_host('host-3')
connected_hosts = {mock.sentinel.ds_1: [host_1.value],
mock.sentinel.ds_2: [host_1.value, host_2.value],
mock.sentinel.ds_3: [host_1.value, host_2.value,
host_3.value]}
self._vops.get_connected_hosts.side_effect = (
lambda summary: connected_hosts[summary])
summaries = [summary_1, summary_2, summary_3]
(best_summary, best_utilization) = self._ds_sel._select_best_summary(
summaries)
self.assertEqual(summary_3, best_summary)
self.assertEqual(3 / 4.0, best_utilization)
# Tie-- two datastores with max host mount count.
summary_4 = self._create_summary(mock.sentinel.ds_4,
free_space=2 * units.Mi,
capacity=4 * units.Mi)
connected_hosts[mock.sentinel.ds_4] = (
connected_hosts[mock.sentinel.ds_3])
summaries.append(summary_4)
(best_summary, best_utilization) = self._ds_sel._select_best_summary(
summaries)
self.assertEqual(summary_4, best_summary)
self.assertEqual(1 / 2.0, best_utilization)
# Clear side effects.
self._vops.get_connected_hosts.side_effect = None
@mock.patch('cinder.volume.drivers.vmware.datastore.DatastoreSelector.'
'get_profile_id')
@mock.patch('cinder.volume.drivers.vmware.datastore.DatastoreSelector.'
'_filter_datastores')
def test_select_datastore(self, filter_datastores, get_profile_id):
# Test with no hosts.
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))
self._vops.get_hosts.assert_called_once_with()
# Test with single host with no valid datastores.
host_1 = self._create_host('host-1')
self._vops.get_hosts.return_value = mock.Mock(
objects=[mock.Mock(obj=host_1)])
self._vops.continue_retrieval.return_value = None
self._vops.get_dss_rp.side_effect = exceptions.VimException('error')
self.assertEqual((), self._ds_sel.select_datastore(req))
self._vops.get_dss_rp.assert_called_once_with(host_1)
# Test with three hosts and vCenter connection problem while fetching
# datastores for the second host.
self._vops.get_dss_rp.reset_mock()
host_2 = self._create_host('host-2')
host_3 = self._create_host('host-3')
self._vops.get_hosts.return_value = mock.Mock(
objects=[mock.Mock(obj=host_1),
mock.Mock(obj=host_2),
mock.Mock(obj=host_3)])
self._vops.get_dss_rp.side_effect = [
exceptions.VimException('no valid datastores'),
exceptions.VimConnectionException('connection error')]
self.assertRaises(exceptions.VimConnectionException,
self._ds_sel.select_datastore,
req)
get_dss_rp_exp_calls = [mock.call(host_1), mock.call(host_2)]
self.assertEqual(get_dss_rp_exp_calls,
self._vops.get_dss_rp.call_args_list)
# Modify previous case to return datastores for second and third host,
# where none of them meet the requirements which include a storage
# profile and affinity requirements.
aff_ds_types = [ds_sel.DatastoreType.VMFS]
req[ds_sel.DatastoreSelector.HARD_AFFINITY_DS_TYPE] = aff_ds_types
ds_1a = mock.sentinel.ds_1a
anti_affinity_ds = [ds_1a]
req[ds_sel.DatastoreSelector.HARD_ANTI_AFFINITY_DS] = anti_affinity_ds
profile_name = mock.sentinel.profile_name
req[ds_sel.DatastoreSelector.PROFILE_NAME] = profile_name
profile_id = mock.sentinel.profile_id
get_profile_id.return_value = profile_id
ds_2a = mock.sentinel.ds_2a
ds_2b = mock.sentinel.ds_2b
ds_3a = mock.sentinel.ds_3a
self._vops.get_dss_rp.reset_mock()
rp_2 = mock.sentinel.rp_2
rp_3 = mock.sentinel.rp_3
self._vops.get_dss_rp.side_effect = [
exceptions.VimException('no valid datastores'),
([ds_2a, ds_2b], rp_2),
([ds_3a], rp_3)]
filter_datastores.return_value = []
self.assertEqual((), self._ds_sel.select_datastore(req))
get_profile_id.assert_called_once_with(profile_name)
get_dss_rp_exp_calls.append(mock.call(host_3))
self.assertEqual(get_dss_rp_exp_calls,
self._vops.get_dss_rp.call_args_list)
filter_datastores_exp_calls = [
mock.call([ds_2a, ds_2b], size_bytes, profile_id, anti_affinity_ds,
aff_ds_types),
mock.call([ds_3a], size_bytes, profile_id, anti_affinity_ds,
aff_ds_types)]
self.assertEqual(filter_datastores_exp_calls,
filter_datastores.call_args_list)
# Modify previous case to have a non-empty summary list after filtering
# with preferred utilization threshold unset.
self._vops.get_dss_rp.side_effect = [
exceptions.VimException('no valid datastores'),
([ds_2a, ds_2b], rp_2),
([ds_3a], rp_3)]
summary_2b = self._create_summary(ds_2b, free_space=0.5 * units.Mi,
capacity=units.Mi)
filter_datastores.side_effect = [[summary_2b]]
self._vops.get_connected_hosts.return_value = [host_1]
self.assertEqual((host_2, rp_2, summary_2b),
self._ds_sel.select_datastore(req))
# Modify previous case to have a preferred utilization threshold
# satsified by one datastore.
self._vops.get_dss_rp.side_effect = [
exceptions.VimException('no valid datastores'),
([ds_2a, ds_2b], rp_2),
([ds_3a], rp_3)]
req[ds_sel.DatastoreSelector.PREF_UTIL_THRESH] = 0.4
summary_3a = self._create_summary(ds_3a, free_space=0.7 * units.Mi,
capacity=units.Mi)
filter_datastores.side_effect = [[summary_2b], [summary_3a]]
self.assertEqual((host_3, rp_3, summary_3a),
self._ds_sel.select_datastore(req))
# Modify previous case to have a preferred utilization threshold
# which cannot be satisfied.
self._vops.get_dss_rp.side_effect = [
exceptions.VimException('no valid datastores'),
([ds_2a, ds_2b], rp_2),
([ds_3a], rp_3)]
filter_datastores.side_effect = [[summary_2b], [summary_3a]]
req[ds_sel.DatastoreSelector.PREF_UTIL_THRESH] = 0.2
summary_2b.freeSpace = 0.75 * units.Mi
self.assertEqual((host_2, rp_2, summary_2b),
self._ds_sel.select_datastore(req))
# Clear side effects.
self._vops.get_dss_rp.side_effect = None
@mock.patch('cinder.volume.drivers.vmware.datastore.DatastoreSelector.'
'_filter_datastores')
def test_select_datastore_with_single_host(self, filter_datastores):
host = self._create_host('host-1')
req = {self._ds_sel.SIZE_BYTES: units.Gi}
ds = mock.sentinel.ds
rp = mock.sentinel.rp
self._vops.get_dss_rp.return_value = ([ds], rp)
summary = self._create_summary(ds, free_space=2 * units.Gi,
capacity=3 * units.Gi)
filter_datastores.return_value = [summary]
self._vops.get_connected_hosts.return_value = [host.value]
self.assertEqual((host, rp, summary),
self._ds_sel.select_datastore(req, [host]))
# reset mocks
self._vops.get_dss_rp.reset_mock()
self._vops.get_dss_rp.return_value = None
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')
def test_is_datastore_compliant(self, filter_by_profile,
get_profile_id_by_name):
# Test with empty profile.
profile_name = None
datastore = mock.sentinel.datastore
self.assertTrue(self._ds_sel.is_datastore_compliant(datastore,
profile_name))
# Test with invalid profile.
profile_name = mock.sentinel.profile_name
get_profile_id_by_name.return_value = None
self.assertRaises(vmdk_exceptions.ProfileNotFoundException,
self._ds_sel.is_datastore_compliant,
datastore,
profile_name)
get_profile_id_by_name.assert_called_once_with(self._session,
profile_name)
# Test with valid profile and non-compliant datastore.
get_profile_id_by_name.reset_mock()
profile_id = mock.sentinel.profile_id
get_profile_id_by_name.return_value = profile_id
filter_by_profile.return_value = []
self.assertFalse(self._ds_sel.is_datastore_compliant(datastore,
profile_name))
get_profile_id_by_name.assert_called_once_with(self._session,
profile_name)
filter_by_profile.assert_called_once_with([datastore], profile_id)
# Test with valid profile and compliant datastore.
get_profile_id_by_name.reset_mock()
filter_by_profile.reset_mock()
filter_by_profile.return_value = [datastore]
self.assertTrue(self._ds_sel.is_datastore_compliant(datastore,
profile_name))
get_profile_id_by_name.assert_called_once_with(self._session,
profile_name)
filter_by_profile.assert_called_once_with([datastore], profile_id)
def test_get_all_hosts(self):
host_1 = self._create_host('host-1')
host_2 = self._create_host('host-2')
hosts = mock.Mock(objects=[mock.Mock(obj=host_1),
mock.Mock(obj=host_2)])
self._vops.get_hosts.return_value = hosts
self._vops.continue_retrieval.return_value = None
# host_1 is usable and host_2 is not usable
self._vops.is_host_usable.side_effect = [True, False]
ret = self._ds_sel._get_all_hosts()
self.assertEqual([host_1], ret)
self._vops.get_hosts.assert_called_once_with()
self._vops.continue_retrieval.assert_called_once_with(hosts)
exp_calls = [mock.call(host_1), mock.call(host_2)]
self.assertEqual(exp_calls, self._vops.is_host_usable.call_args_list)