os-brick/os_brick/tests/initiator/connectors/test_vmware.py

367 lines
14 KiB
Python

# Copyright (c) 2016 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.
import ddt
import mock
from oslo_utils import units
from oslo_vmware.objects import datastore
from oslo_vmware import vim_util
from os_brick import exception
from os_brick.initiator.connectors import vmware
from os_brick.tests.initiator import test_connector
@ddt.ddt
class VmdkConnectorTestCase(test_connector.ConnectorTestCase):
IP = '127.0.0.1'
PORT = 443
USERNAME = 'username'
PASSWORD = 'password'
API_RETRY_COUNT = 3
TASK_POLL_INTERVAL = 5.0
CA_FILE = "/etc/ssl/rui-ca-cert.pem"
TMP_DIR = "/vmware-tmp"
IMG_TX_TIMEOUT = 10
VMDK_CONNECTOR = vmware.VmdkConnector
def setUp(self):
super(VmdkConnectorTestCase, self).setUp()
self._connector = vmware.VmdkConnector(None)
self._connector._ip = self.IP
self._connector._port = self.PORT
self._connector._username = self.USERNAME
self._connector._password = self.PASSWORD
self._connector._api_retry_count = self.API_RETRY_COUNT
self._connector._task_poll_interval = self.TASK_POLL_INTERVAL
self._connector._ca_file = self.CA_FILE
self._connector._insecure = True
self._connector._tmp_dir = self.TMP_DIR
self._connector._timeout = self.IMG_TX_TIMEOUT
def test_load_config(self):
config = {
'vmware_host_ip': 'localhost',
'vmware_host_port': 1234,
'vmware_host_username': 'root',
'vmware_host_password': 'pswd',
'vmware_api_retry_count': 1,
'vmware_task_poll_interval': 1.0,
'vmware_ca_file': None,
'vmware_insecure': False,
'vmware_tmp_dir': '/tmp',
'vmware_image_transfer_timeout_secs': 5,
}
self._connector._load_config({'config': config})
self.assertEqual('localhost', self._connector._ip)
self.assertEqual(1234, self._connector._port)
self.assertEqual('root', self._connector._username)
self.assertEqual('pswd', self._connector._password)
self.assertEqual(1, self._connector._api_retry_count)
self.assertEqual(1.0, self._connector._task_poll_interval)
self.assertIsNone(self._connector._ca_file)
self.assertFalse(self._connector._insecure)
self.assertEqual('/tmp', self._connector._tmp_dir)
self.assertEqual(5, self._connector._timeout)
@mock.patch('oslo_vmware.api.VMwareAPISession')
def test_create_session(self, session):
session.return_value = mock.sentinel.session
ret = self._connector._create_session()
self.assertEqual(mock.sentinel.session, ret)
session.assert_called_once_with(
self._connector._ip,
self._connector._username,
self._connector._password,
self._connector._api_retry_count,
self._connector._task_poll_interval,
port=self._connector._port,
cacert=self._connector._ca_file,
insecure=self._connector._insecure)
@mock.patch('oslo_utils.fileutils.ensure_tree')
@mock.patch('tempfile.mkstemp')
@mock.patch('os.close')
def test_create_temp_file(
self, close, mkstemp, ensure_tree):
fd = mock.sentinel.fd
tmp = mock.sentinel.tmp
mkstemp.return_value = (fd, tmp)
prefix = ".vmdk"
suffix = "test"
ret = self._connector._create_temp_file(prefix=prefix, suffix=suffix)
self.assertEqual(tmp, ret)
ensure_tree.assert_called_once_with(self._connector._tmp_dir)
mkstemp.assert_called_once_with(dir=self._connector._tmp_dir,
prefix=prefix,
suffix=suffix)
close.assert_called_once_with(fd)
@mock.patch('os_brick.initiator.connectors.vmware.open', create=True)
@mock.patch('oslo_vmware.image_transfer.copy_stream_optimized_disk')
def test_download_vmdk(self, copy_disk, file_open):
file_open_ret = mock.Mock()
tmp_file = mock.sentinel.tmp_file
file_open_ret.__enter__ = mock.Mock(return_value=tmp_file)
file_open_ret.__exit__ = mock.Mock(return_value=None)
file_open.return_value = file_open_ret
tmp_file_path = mock.sentinel.tmp_file_path
session = mock.sentinel.session
backing = mock.sentinel.backing
vmdk_path = mock.sentinel.vmdk_path
vmdk_size = mock.sentinel.vmdk_size
self._connector._download_vmdk(
tmp_file_path, session, backing, vmdk_path, vmdk_size)
file_open.assert_called_once_with(tmp_file_path, 'wb')
copy_disk.assert_called_once_with(None,
self._connector._timeout,
tmp_file,
session=session,
host=self._connector._ip,
port=self._connector._port,
vm=backing,
vmdk_file_path=vmdk_path,
vmdk_size=vmdk_size)
def _create_connection_properties(self):
return {'volume_id': 'ed083474-d325-4a99-b301-269111654f0d',
'volume': 'ref-1',
'vmdk_path': '[ds] foo/bar.vmdk',
'vmdk_size': units.Gi,
'datastore': 'ds-1',
'datacenter': 'dc-1',
}
@mock.patch.object(VMDK_CONNECTOR, '_load_config')
@mock.patch.object(VMDK_CONNECTOR, '_create_session')
@mock.patch.object(VMDK_CONNECTOR, '_create_temp_file')
@mock.patch('oslo_vmware.vim_util.get_moref')
@mock.patch.object(VMDK_CONNECTOR, '_download_vmdk')
@mock.patch('os.path.getmtime')
def test_connect_volume(
self, getmtime, download_vmdk, get_moref, create_temp_file,
create_session, load_config):
session = mock.Mock()
create_session.return_value = session
tmp_file_path = mock.sentinel.tmp_file_path
create_temp_file.return_value = tmp_file_path
backing = mock.sentinel.backing
get_moref.return_value = backing
last_modified = mock.sentinel.last_modified
getmtime.return_value = last_modified
props = self._create_connection_properties()
ret = self._connector.connect_volume(props)
self.assertEqual(tmp_file_path, ret['path'])
self.assertEqual(last_modified, ret['last_modified'])
load_config.assert_called_once_with(props)
create_session.assert_called_once_with()
create_temp_file.assert_called_once_with(
suffix=".vmdk", prefix=props['volume_id'])
download_vmdk.assert_called_once_with(
tmp_file_path, session, backing, props['vmdk_path'],
props['vmdk_size'])
session.logout.assert_called_once_with()
@ddt.data((None, False), ([mock.sentinel.snap], True))
@ddt.unpack
def test_snapshot_exists(self, snap_list, exp_return_value):
snapshot = mock.Mock(rootSnapshotList=snap_list)
session = mock.Mock()
session.invoke_api.return_value = snapshot
backing = mock.sentinel.backing
ret = self._connector._snapshot_exists(session, backing)
self.assertEqual(exp_return_value, ret)
session.invoke_api.assert_called_once_with(
vim_util, 'get_object_property', session.vim, backing, 'snapshot')
def test_create_temp_ds_folder(self):
session = mock.Mock()
ds_folder_path = mock.sentinel.ds_folder_path
dc_ref = mock.sentinel.dc_ref
self._connector._create_temp_ds_folder(session, ds_folder_path, dc_ref)
session.invoke_api.assert_called_once_with(
session.vim,
'MakeDirectory',
session.vim.service_content.fileManager,
name=ds_folder_path,
datacenter=dc_ref)
@mock.patch('oslo_vmware.objects.datastore.get_datastore_by_ref')
@mock.patch.object(VMDK_CONNECTOR, '_create_temp_ds_folder')
@mock.patch('os_brick.initiator.connectors.vmware.open', create=True)
@mock.patch.object(VMDK_CONNECTOR, '_upload_vmdk')
@mock.patch('os.path.getsize')
def test_disconnect(
self, getsize, upload_vmdk, file_open, create_temp_ds_folder,
get_ds_by_ref):
ds_ref = mock.sentinel.ds_ref
ds_name = 'datastore-1'
dstore = datastore.Datastore(ds_ref, ds_name)
get_ds_by_ref.return_value = dstore
file_open_ret = mock.Mock()
tmp_file = mock.sentinel.tmp_file
file_open_ret.__enter__ = mock.Mock(return_value=tmp_file)
file_open_ret.__exit__ = mock.Mock(return_value=None)
file_open.return_value = file_open_ret
dc_name = mock.sentinel.dc_name
delete_task = mock.sentinel.delete_vdisk_task
copy_task = mock.sentinel.copy_vdisk_task
delete_file_task = mock.sentinel.delete_file_task
session = mock.Mock()
session.invoke_api.side_effect = [
dc_name, delete_task, copy_task, delete_file_task]
getsize.return_value = units.Gi
tmp_file_path = '/tmp/foo.vmdk'
dc_ref = mock.sentinel.dc_ref
vmdk_path = mock.sentinel.vmdk_path
self._connector._disconnect(
tmp_file_path, session, ds_ref, dc_ref, vmdk_path)
tmp_folder_path = self._connector.TMP_IMAGES_DATASTORE_FOLDER_PATH
ds_folder_path = '[%s] %s' % (ds_name, tmp_folder_path)
create_temp_ds_folder.assert_called_once_with(
session, ds_folder_path, dc_ref)
file_open.assert_called_once_with(tmp_file_path, "rb")
self.assertEqual(
mock.call(vim_util, 'get_object_property', session.vim, dc_ref,
'name'), session.invoke_api.call_args_list[0])
exp_rel_path = '%s/foo.vmdk' % tmp_folder_path
upload_vmdk.assert_called_once_with(
tmp_file, self._connector._ip, self._connector._port, dc_name,
ds_name, session.vim.client.options.transport.cookiejar,
exp_rel_path, units.Gi, self._connector._ca_file,
self._connector._timeout)
disk_mgr = session.vim.service_content.virtualDiskManager
self.assertEqual(
mock.call(session.vim, 'DeleteVirtualDisk_Task', disk_mgr,
name=vmdk_path, datacenter=dc_ref),
session.invoke_api.call_args_list[1])
self.assertEqual(mock.call(delete_task),
session.wait_for_task.call_args_list[0])
src = '[%s] %s' % (ds_name, exp_rel_path)
self.assertEqual(
mock.call(session.vim, 'CopyVirtualDisk_Task', disk_mgr,
sourceName=src, sourceDatacenter=dc_ref,
destName=vmdk_path, destDatacenter=dc_ref),
session.invoke_api.call_args_list[2])
self.assertEqual(mock.call(copy_task),
session.wait_for_task.call_args_list[1])
file_mgr = session.vim.service_content.fileManager
self.assertEqual(
mock.call(session.vim, 'DeleteDatastoreFile_Task', file_mgr,
name=src, datacenter=dc_ref),
session.invoke_api.call_args_list[3])
self.assertEqual(mock.call(delete_file_task),
session.wait_for_task.call_args_list[2])
@mock.patch('os.path.exists')
def test_disconnect_volume_with_missing_temp_file(self, path_exists):
path_exists.return_value = False
path = mock.sentinel.path
self.assertRaises(exception.NotFound,
self._connector.disconnect_volume,
mock.ANY,
{'path': path})
path_exists.assert_called_once_with(path)
@mock.patch('os.path.exists')
@mock.patch('os.path.getmtime')
@mock.patch.object(VMDK_CONNECTOR, '_disconnect')
@mock.patch('os.remove')
def test_disconnect_volume_with_unmodified_file(
self, remove, disconnect, getmtime, path_exists):
path_exists.return_value = True
mtime = 1467802060
getmtime.return_value = mtime
path = mock.sentinel.path
self._connector.disconnect_volume(mock.ANY, {'path': path,
'last_modified': mtime})
path_exists.assert_called_once_with(path)
getmtime.assert_called_once_with(path)
disconnect.assert_not_called()
remove.assert_called_once_with(path)
@mock.patch('os.path.exists')
@mock.patch('os.path.getmtime')
@mock.patch.object(VMDK_CONNECTOR, '_load_config')
@mock.patch.object(VMDK_CONNECTOR, '_create_session')
@mock.patch('oslo_vmware.vim_util.get_moref')
@mock.patch.object(VMDK_CONNECTOR, '_snapshot_exists')
@mock.patch.object(VMDK_CONNECTOR, '_disconnect')
@mock.patch('os.remove')
def test_disconnect_volume(
self, remove, disconnect, snapshot_exists, get_moref,
create_session, load_config, getmtime, path_exists):
path_exists.return_value = True
mtime = 1467802060
getmtime.return_value = mtime
session = mock.Mock()
create_session.return_value = session
snapshot_exists.return_value = False
backing = mock.sentinel.backing
ds_ref = mock.sentinel.ds_ref
dc_ref = mock.sentinel.dc_ref
get_moref.side_effect = [backing, ds_ref, dc_ref]
props = self._create_connection_properties()
path = mock.sentinel.path
self._connector.disconnect_volume(props, {'path': path,
'last_modified': mtime - 1})
path_exists.assert_called_once_with(path)
getmtime.assert_called_once_with(path)
load_config.assert_called_once_with(props)
create_session.assert_called_once_with()
snapshot_exists.assert_called_once_with(session, backing)
disconnect.assert_called_once_with(
path, session, ds_ref, dc_ref, props['vmdk_path'])
remove.assert_called_once_with(path)
session.logout.assert_called_once_with()