cinder/cinder/tests/unit/test_vmware_datastore.py

435 lines
18 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
@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)