486 lines
22 KiB
Python
486 lines
22 KiB
Python
# Copyright (c) 2014 VMware, Inc.
|
|
#
|
|
# 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.
|
|
|
|
import re
|
|
|
|
import mock
|
|
from oslo_utils import units
|
|
from oslo_vmware import exceptions as vexc
|
|
from oslo_vmware.objects import datastore as ds_obj
|
|
|
|
from nova import exception
|
|
from nova import test
|
|
from nova.tests.unit.virt.vmwareapi import fake
|
|
from nova.virt.vmwareapi import ds_util
|
|
|
|
|
|
class DsUtilTestCase(test.NoDBTestCase):
|
|
def setUp(self):
|
|
super(DsUtilTestCase, self).setUp()
|
|
self.session = fake.FakeSession()
|
|
self.flags(api_retry_count=1, group='vmware')
|
|
fake.reset()
|
|
|
|
def tearDown(self):
|
|
super(DsUtilTestCase, self).tearDown()
|
|
fake.reset()
|
|
|
|
def test_get_datacenter_ref(self):
|
|
with mock.patch.object(self.session, '_call_method') as call_method:
|
|
ds_util.get_datacenter_ref(self.session, "datacenter")
|
|
call_method.assert_called_once_with(
|
|
self.session.vim,
|
|
"FindByInventoryPath",
|
|
self.session.vim.service_content.searchIndex,
|
|
inventoryPath="datacenter")
|
|
|
|
def test_file_delete(self):
|
|
def fake_call_method(module, method, *args, **kwargs):
|
|
self.assertEqual('DeleteDatastoreFile_Task', method)
|
|
name = kwargs.get('name')
|
|
self.assertEqual('[ds] fake/path', name)
|
|
datacenter = kwargs.get('datacenter')
|
|
self.assertEqual('fake-dc-ref', datacenter)
|
|
return 'fake_delete_task'
|
|
|
|
with test.nested(
|
|
mock.patch.object(self.session, '_wait_for_task'),
|
|
mock.patch.object(self.session, '_call_method',
|
|
fake_call_method)
|
|
) as (_wait_for_task, _call_method):
|
|
ds_path = ds_obj.DatastorePath('ds', 'fake/path')
|
|
ds_util.file_delete(self.session,
|
|
ds_path, 'fake-dc-ref')
|
|
_wait_for_task.assert_has_calls([
|
|
mock.call('fake_delete_task')])
|
|
|
|
def test_file_copy(self):
|
|
def fake_call_method(module, method, *args, **kwargs):
|
|
self.assertEqual('CopyDatastoreFile_Task', method)
|
|
src_name = kwargs.get('sourceName')
|
|
self.assertEqual('[ds] fake/path/src_file', src_name)
|
|
src_dc_ref = kwargs.get('sourceDatacenter')
|
|
self.assertEqual('fake-src-dc-ref', src_dc_ref)
|
|
dst_name = kwargs.get('destinationName')
|
|
self.assertEqual('[ds] fake/path/dst_file', dst_name)
|
|
dst_dc_ref = kwargs.get('destinationDatacenter')
|
|
self.assertEqual('fake-dst-dc-ref', dst_dc_ref)
|
|
return 'fake_copy_task'
|
|
|
|
with test.nested(
|
|
mock.patch.object(self.session, '_wait_for_task'),
|
|
mock.patch.object(self.session, '_call_method',
|
|
fake_call_method)
|
|
) as (_wait_for_task, _call_method):
|
|
src_ds_path = ds_obj.DatastorePath('ds', 'fake/path', 'src_file')
|
|
dst_ds_path = ds_obj.DatastorePath('ds', 'fake/path', 'dst_file')
|
|
ds_util.file_copy(self.session,
|
|
str(src_ds_path), 'fake-src-dc-ref',
|
|
str(dst_ds_path), 'fake-dst-dc-ref')
|
|
_wait_for_task.assert_has_calls([
|
|
mock.call('fake_copy_task')])
|
|
|
|
def test_file_move(self):
|
|
def fake_call_method(module, method, *args, **kwargs):
|
|
self.assertEqual('MoveDatastoreFile_Task', method)
|
|
sourceName = kwargs.get('sourceName')
|
|
self.assertEqual('[ds] tmp/src', sourceName)
|
|
destinationName = kwargs.get('destinationName')
|
|
self.assertEqual('[ds] base/dst', destinationName)
|
|
sourceDatacenter = kwargs.get('sourceDatacenter')
|
|
self.assertEqual('fake-dc-ref', sourceDatacenter)
|
|
destinationDatacenter = kwargs.get('destinationDatacenter')
|
|
self.assertEqual('fake-dc-ref', destinationDatacenter)
|
|
return 'fake_move_task'
|
|
|
|
with test.nested(
|
|
mock.patch.object(self.session, '_wait_for_task'),
|
|
mock.patch.object(self.session, '_call_method',
|
|
fake_call_method)
|
|
) as (_wait_for_task, _call_method):
|
|
src_ds_path = ds_obj.DatastorePath('ds', 'tmp/src')
|
|
dst_ds_path = ds_obj.DatastorePath('ds', 'base/dst')
|
|
ds_util.file_move(self.session,
|
|
'fake-dc-ref', src_ds_path, dst_ds_path)
|
|
_wait_for_task.assert_has_calls([
|
|
mock.call('fake_move_task')])
|
|
|
|
def test_disk_move(self):
|
|
def fake_call_method(module, method, *args, **kwargs):
|
|
self.assertEqual('MoveVirtualDisk_Task', method)
|
|
src_name = kwargs.get('sourceName')
|
|
self.assertEqual('[ds] tmp/src', src_name)
|
|
dest_name = kwargs.get('destName')
|
|
self.assertEqual('[ds] base/dst', dest_name)
|
|
src_datacenter = kwargs.get('sourceDatacenter')
|
|
self.assertEqual('fake-dc-ref', src_datacenter)
|
|
dest_datacenter = kwargs.get('destDatacenter')
|
|
self.assertEqual('fake-dc-ref', dest_datacenter)
|
|
return 'fake_move_task'
|
|
|
|
with test.nested(
|
|
mock.patch.object(self.session, '_wait_for_task'),
|
|
mock.patch.object(self.session, '_call_method',
|
|
fake_call_method)
|
|
) as (_wait_for_task, _call_method):
|
|
ds_util.disk_move(self.session,
|
|
'fake-dc-ref', '[ds] tmp/src', '[ds] base/dst')
|
|
_wait_for_task.assert_has_calls([
|
|
mock.call('fake_move_task')])
|
|
|
|
def test_disk_copy(self):
|
|
with test.nested(
|
|
mock.patch.object(self.session, '_wait_for_task'),
|
|
mock.patch.object(self.session, '_call_method',
|
|
return_value=mock.sentinel.cm)
|
|
) as (_wait_for_task, _call_method):
|
|
ds_util.disk_copy(self.session, mock.sentinel.dc_ref,
|
|
mock.sentinel.source_ds, mock.sentinel.dest_ds)
|
|
_wait_for_task.assert_called_once_with(mock.sentinel.cm)
|
|
_call_method.assert_called_once_with(
|
|
mock.ANY, 'CopyVirtualDisk_Task', 'VirtualDiskManager',
|
|
sourceName='sentinel.source_ds',
|
|
destDatacenter=mock.sentinel.dc_ref,
|
|
sourceDatacenter=mock.sentinel.dc_ref, force=False,
|
|
destName='sentinel.dest_ds')
|
|
|
|
def test_disk_delete(self):
|
|
with test.nested(
|
|
mock.patch.object(self.session, '_wait_for_task'),
|
|
mock.patch.object(self.session, '_call_method',
|
|
return_value=mock.sentinel.cm)
|
|
) as (_wait_for_task, _call_method):
|
|
ds_util.disk_delete(self.session,
|
|
'fake-dc-ref', '[ds] tmp/disk.vmdk')
|
|
_wait_for_task.assert_called_once_with(mock.sentinel.cm)
|
|
_call_method.assert_called_once_with(
|
|
mock.ANY, 'DeleteVirtualDisk_Task', 'VirtualDiskManager',
|
|
datacenter='fake-dc-ref', name='[ds] tmp/disk.vmdk')
|
|
|
|
def test_mkdir(self):
|
|
def fake_call_method(module, method, *args, **kwargs):
|
|
self.assertEqual('MakeDirectory', method)
|
|
name = kwargs.get('name')
|
|
self.assertEqual('[ds] fake/path', name)
|
|
datacenter = kwargs.get('datacenter')
|
|
self.assertEqual('fake-dc-ref', datacenter)
|
|
createParentDirectories = kwargs.get('createParentDirectories')
|
|
self.assertTrue(createParentDirectories)
|
|
|
|
with mock.patch.object(self.session, '_call_method',
|
|
fake_call_method):
|
|
ds_path = ds_obj.DatastorePath('ds', 'fake/path')
|
|
ds_util.mkdir(self.session, ds_path, 'fake-dc-ref')
|
|
|
|
def test_file_exists(self):
|
|
def fake_call_method(module, method, *args, **kwargs):
|
|
if method == 'SearchDatastore_Task':
|
|
ds_browser = args[0]
|
|
self.assertEqual('fake-browser', ds_browser)
|
|
datastorePath = kwargs.get('datastorePath')
|
|
self.assertEqual('[ds] fake/path', datastorePath)
|
|
return 'fake_exists_task'
|
|
|
|
# Should never get here
|
|
self.fail()
|
|
|
|
def fake_wait_for_task(task_ref):
|
|
if task_ref == 'fake_exists_task':
|
|
result_file = fake.DataObject()
|
|
result_file.path = 'fake-file'
|
|
|
|
result = fake.DataObject()
|
|
result.file = [result_file]
|
|
result.path = '[ds] fake/path'
|
|
|
|
task_info = fake.DataObject()
|
|
task_info.result = result
|
|
|
|
return task_info
|
|
|
|
# Should never get here
|
|
self.fail()
|
|
|
|
with test.nested(
|
|
mock.patch.object(self.session, '_call_method',
|
|
fake_call_method),
|
|
mock.patch.object(self.session, '_wait_for_task',
|
|
fake_wait_for_task)):
|
|
ds_path = ds_obj.DatastorePath('ds', 'fake/path')
|
|
file_exists = ds_util.file_exists(self.session,
|
|
'fake-browser', ds_path, 'fake-file')
|
|
self.assertTrue(file_exists)
|
|
|
|
def test_file_exists_fails(self):
|
|
def fake_call_method(module, method, *args, **kwargs):
|
|
if method == 'SearchDatastore_Task':
|
|
return 'fake_exists_task'
|
|
|
|
# Should never get here
|
|
self.fail()
|
|
|
|
def fake_wait_for_task(task_ref):
|
|
if task_ref == 'fake_exists_task':
|
|
raise vexc.FileNotFoundException()
|
|
|
|
# Should never get here
|
|
self.fail()
|
|
|
|
with test.nested(
|
|
mock.patch.object(self.session, '_call_method',
|
|
fake_call_method),
|
|
mock.patch.object(self.session, '_wait_for_task',
|
|
fake_wait_for_task)):
|
|
ds_path = ds_obj.DatastorePath('ds', 'fake/path')
|
|
file_exists = ds_util.file_exists(self.session,
|
|
'fake-browser', ds_path, 'fake-file')
|
|
self.assertFalse(file_exists)
|
|
|
|
def _mock_get_datastore_calls(self, *datastores):
|
|
"""Mock vim_util calls made by get_datastore."""
|
|
|
|
datastores_i = [None]
|
|
|
|
# For the moment, at least, this list of datastores is simply passed to
|
|
# get_properties_for_a_collection_of_objects, which we mock below. We
|
|
# don't need to over-complicate the fake function by worrying about its
|
|
# contents.
|
|
fake_ds_list = ['fake-ds']
|
|
|
|
def fake_call_method(module, method, *args, **kwargs):
|
|
# Mock the call which returns a list of datastores for the cluster
|
|
if (module == ds_util.vutil and
|
|
method == 'get_object_property' and
|
|
args == ('fake-cluster', 'datastore')):
|
|
fake_ds_mor = fake.DataObject()
|
|
fake_ds_mor.ManagedObjectReference = fake_ds_list
|
|
return fake_ds_mor
|
|
|
|
# Return the datastore result sets we were passed in, in the order
|
|
# given
|
|
if (module == ds_util.vim_util and
|
|
method == 'get_properties_for_a_collection_of_objects' and
|
|
args[0] == 'Datastore' and
|
|
args[1] == fake_ds_list):
|
|
# Start a new iterator over given datastores
|
|
datastores_i[0] = iter(datastores)
|
|
return next(datastores_i[0])
|
|
|
|
# Continue returning results from the current iterator.
|
|
if (module == ds_util.vutil and
|
|
method == 'continue_retrieval'):
|
|
try:
|
|
return next(datastores_i[0])
|
|
except StopIteration:
|
|
return None
|
|
|
|
if (method == 'continue_retrieval' or
|
|
method == 'cancel_retrieval'):
|
|
return
|
|
|
|
# Sentinel that get_datastore's use of vim has changed
|
|
self.fail('Unexpected vim call in get_datastore: %s' % method)
|
|
|
|
return mock.patch.object(self.session, '_call_method',
|
|
side_effect=fake_call_method)
|
|
|
|
def test_get_datastore(self):
|
|
fake_objects = fake.FakeRetrieveResult()
|
|
fake_objects.add_object(fake.Datastore())
|
|
fake_objects.add_object(fake.Datastore("fake-ds-2", 2048, 1000,
|
|
False, "normal"))
|
|
fake_objects.add_object(fake.Datastore("fake-ds-3", 4096, 2000,
|
|
True, "inMaintenance"))
|
|
|
|
with self._mock_get_datastore_calls(fake_objects):
|
|
result = ds_util.get_datastore(self.session, 'fake-cluster')
|
|
self.assertEqual("fake-ds", result.name)
|
|
self.assertEqual(units.Ti, result.capacity)
|
|
self.assertEqual(500 * units.Gi, result.freespace)
|
|
|
|
def test_get_datastore_with_regex(self):
|
|
# Test with a regex that matches with a datastore
|
|
datastore_valid_regex = re.compile("^openstack.*\d$")
|
|
fake_objects = fake.FakeRetrieveResult()
|
|
fake_objects.add_object(fake.Datastore("openstack-ds0"))
|
|
fake_objects.add_object(fake.Datastore("fake-ds0"))
|
|
fake_objects.add_object(fake.Datastore("fake-ds1"))
|
|
|
|
with self._mock_get_datastore_calls(fake_objects):
|
|
result = ds_util.get_datastore(self.session, 'fake-cluster',
|
|
datastore_valid_regex)
|
|
self.assertEqual("openstack-ds0", result.name)
|
|
|
|
def test_get_datastore_with_token(self):
|
|
regex = re.compile("^ds.*\d$")
|
|
fake0 = fake.FakeRetrieveResult()
|
|
fake0.add_object(fake.Datastore("ds0", 10 * units.Gi, 5 * units.Gi))
|
|
fake0.add_object(fake.Datastore("foo", 10 * units.Gi, 9 * units.Gi))
|
|
setattr(fake0, 'token', 'token-0')
|
|
fake1 = fake.FakeRetrieveResult()
|
|
fake1.add_object(fake.Datastore("ds2", 10 * units.Gi, 8 * units.Gi))
|
|
fake1.add_object(fake.Datastore("ds3", 10 * units.Gi, 1 * units.Gi))
|
|
|
|
with self._mock_get_datastore_calls(fake0, fake1):
|
|
result = ds_util.get_datastore(self.session, 'fake-cluster', regex)
|
|
self.assertEqual("ds2", result.name)
|
|
|
|
def test_get_datastore_with_list(self):
|
|
# Test with a regex containing whitelist of datastores
|
|
datastore_valid_regex = re.compile("(openstack-ds0|openstack-ds2)")
|
|
fake_objects = fake.FakeRetrieveResult()
|
|
fake_objects.add_object(fake.Datastore("openstack-ds0"))
|
|
fake_objects.add_object(fake.Datastore("openstack-ds1"))
|
|
fake_objects.add_object(fake.Datastore("openstack-ds2"))
|
|
|
|
with self._mock_get_datastore_calls(fake_objects):
|
|
result = ds_util.get_datastore(self.session, 'fake-cluster',
|
|
datastore_valid_regex)
|
|
self.assertNotEqual("openstack-ds1", result.name)
|
|
|
|
def test_get_datastore_with_regex_error(self):
|
|
# Test with a regex that has no match
|
|
# Checks if code raises DatastoreNotFound with a specific message
|
|
datastore_invalid_regex = re.compile("unknown-ds")
|
|
exp_message = ("Datastore regex %s did not match any datastores"
|
|
% datastore_invalid_regex.pattern)
|
|
fake_objects = fake.FakeRetrieveResult()
|
|
fake_objects.add_object(fake.Datastore("fake-ds0"))
|
|
fake_objects.add_object(fake.Datastore("fake-ds1"))
|
|
# assertRaisesRegExp would have been a good choice instead of
|
|
# try/catch block, but it's available only from Py 2.7.
|
|
try:
|
|
with self._mock_get_datastore_calls(fake_objects):
|
|
ds_util.get_datastore(self.session, 'fake-cluster',
|
|
datastore_invalid_regex)
|
|
except exception.DatastoreNotFound as e:
|
|
self.assertEqual(exp_message, e.args[0])
|
|
else:
|
|
self.fail("DatastoreNotFound Exception was not raised with "
|
|
"message: %s" % exp_message)
|
|
|
|
def test_get_datastore_without_datastore(self):
|
|
self.assertRaises(exception.DatastoreNotFound,
|
|
ds_util.get_datastore,
|
|
fake.FakeObjectRetrievalSession(None), cluster="fake-cluster")
|
|
|
|
def test_get_datastore_inaccessible_ds(self):
|
|
data_store = fake.Datastore()
|
|
data_store.set("summary.accessible", False)
|
|
|
|
fake_objects = fake.FakeRetrieveResult()
|
|
fake_objects.add_object(data_store)
|
|
|
|
with self._mock_get_datastore_calls(fake_objects):
|
|
self.assertRaises(exception.DatastoreNotFound,
|
|
ds_util.get_datastore,
|
|
self.session, 'fake-cluster')
|
|
|
|
def test_get_datastore_ds_in_maintenance(self):
|
|
data_store = fake.Datastore()
|
|
data_store.set("summary.maintenanceMode", "inMaintenance")
|
|
|
|
fake_objects = fake.FakeRetrieveResult()
|
|
fake_objects.add_object(data_store)
|
|
|
|
with self._mock_get_datastore_calls(fake_objects):
|
|
self.assertRaises(exception.DatastoreNotFound,
|
|
ds_util.get_datastore,
|
|
self.session, 'fake-cluster')
|
|
|
|
def test_get_datastore_no_host_in_cluster(self):
|
|
def fake_call_method(module, method, *args, **kwargs):
|
|
return ''
|
|
|
|
with mock.patch.object(self.session, '_call_method',
|
|
fake_call_method):
|
|
self.assertRaises(exception.DatastoreNotFound,
|
|
ds_util.get_datastore,
|
|
self.session, 'fake-cluster')
|
|
|
|
def _test_is_datastore_valid(self, accessible=True,
|
|
maintenance_mode="normal",
|
|
type="VMFS",
|
|
datastore_regex=None,
|
|
ds_types=ds_util.ALL_SUPPORTED_DS_TYPES):
|
|
propdict = {}
|
|
propdict["summary.accessible"] = accessible
|
|
propdict["summary.maintenanceMode"] = maintenance_mode
|
|
propdict["summary.type"] = type
|
|
propdict["summary.name"] = "ds-1"
|
|
|
|
return ds_util._is_datastore_valid(propdict, datastore_regex, ds_types)
|
|
|
|
def test_is_datastore_valid(self):
|
|
for ds_type in ds_util.ALL_SUPPORTED_DS_TYPES:
|
|
self.assertTrue(self._test_is_datastore_valid(True,
|
|
"normal",
|
|
ds_type))
|
|
|
|
def test_is_datastore_valid_inaccessible_ds(self):
|
|
self.assertFalse(self._test_is_datastore_valid(False,
|
|
"normal",
|
|
"VMFS"))
|
|
|
|
def test_is_datastore_valid_ds_in_maintenance(self):
|
|
self.assertFalse(self._test_is_datastore_valid(True,
|
|
"inMaintenance",
|
|
"VMFS"))
|
|
|
|
def test_is_datastore_valid_ds_type_invalid(self):
|
|
self.assertFalse(self._test_is_datastore_valid(True,
|
|
"normal",
|
|
"vfat"))
|
|
|
|
def test_is_datastore_valid_not_matching_regex(self):
|
|
datastore_regex = re.compile("ds-2")
|
|
self.assertFalse(self._test_is_datastore_valid(True,
|
|
"normal",
|
|
"VMFS",
|
|
datastore_regex))
|
|
|
|
def test_is_datastore_valid_matching_regex(self):
|
|
datastore_regex = re.compile("ds-1")
|
|
self.assertTrue(self._test_is_datastore_valid(True,
|
|
"normal",
|
|
"VMFS",
|
|
datastore_regex))
|
|
|
|
def test_get_connected_hosts_none(self):
|
|
with mock.patch.object(self.session,
|
|
'_call_method') as _call_method:
|
|
hosts = ds_util.get_connected_hosts(self.session,
|
|
'fake_datastore')
|
|
self.assertEqual([], hosts)
|
|
_call_method.assert_called_once_with(
|
|
mock.ANY, 'get_object_property',
|
|
'fake_datastore', 'host')
|
|
|
|
def test_get_connected_hosts(self):
|
|
host = mock.Mock(spec=object)
|
|
host.value = 'fake-host'
|
|
host_mount = mock.Mock(spec=object)
|
|
host_mount.key = host
|
|
host_mounts = mock.Mock(spec=object)
|
|
host_mounts.DatastoreHostMount = [host_mount]
|
|
|
|
with mock.patch.object(self.session, '_call_method',
|
|
return_value=host_mounts) as _call_method:
|
|
hosts = ds_util.get_connected_hosts(self.session,
|
|
'fake_datastore')
|
|
self.assertEqual(['fake-host'], hosts)
|
|
_call_method.assert_called_once_with(
|
|
mock.ANY, 'get_object_property',
|
|
'fake_datastore', 'host')
|