911 lines
38 KiB
Python
911 lines
38 KiB
Python
# Copyright (c) 2016 FalconStor, 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.
|
|
|
|
from copy import deepcopy
|
|
import mock
|
|
import time
|
|
|
|
from cinder import context
|
|
from cinder import exception
|
|
from cinder import test
|
|
from cinder.volume import configuration as conf
|
|
from cinder.volume.drivers.falconstor import fc
|
|
from cinder.volume.drivers.falconstor import iscsi
|
|
from cinder.volume.drivers.falconstor import rest_proxy as proxy
|
|
|
|
|
|
DRIVER_PATH = "cinder.volume.drivers.falconstor"
|
|
BASE_DRIVER = DRIVER_PATH + ".fss_common.FalconstorBaseDriver"
|
|
ISCSI_DRIVER = DRIVER_PATH + ".iscsi.FSSISCSIDriver"
|
|
|
|
PRIMARY_IP = '10.0.0.1'
|
|
SECONDARY_IP = '10.0.0.2'
|
|
FAKE_ID = 123
|
|
FAKE_SINGLE_POOLS = {'A': 1}
|
|
FAKE_MULTIPLE_POOLS = {'P': 1, 'O': 2}
|
|
FAKE = 'fake'
|
|
FAKE_HOST = 'fakehost'
|
|
API_RESPONSE = {'rc': 0}
|
|
ISCSI_VOLUME_BACKEND_NAME = "FSSISCSIDriver"
|
|
SESSION_ID = "a76d506c-abcd-1234-efgh-710e1fd90527"
|
|
VOLUME_ID = '6068ea6d-f221-4213-bde9-f1b50aecdf36'
|
|
ADD_VOLUME_ID = '6068ed7f-f231-4283-bge9-f1b51aecdf36'
|
|
GROUP_ID = 'd03338a9-9115-48a3-8dfc-35cdfcdc15a7'
|
|
|
|
PORTAL_RESPONSE = {'rc': 0, 'ipaddress': FAKE}
|
|
VOLUME_METADATA = {'metadata': {'FSS-vid': 1}}
|
|
EXTENT_NEW_SIZE = 3
|
|
DATA_SERVER_INFO = 0, {'metadata': {'vendor': 'FalconStor', 'version': '1.5'}}
|
|
|
|
FSS_SINGLE_TYPE = 'single'
|
|
RAWTIMESTAMP = '1324975390'
|
|
|
|
VOLUME = {'id': VOLUME_ID,
|
|
'name': "volume-" + VOLUME_ID,
|
|
'display_name': 'fake_volume',
|
|
'display_description': '',
|
|
'size': 1,
|
|
'host': "hostname@backend#%s" % FAKE_ID,
|
|
'volume_type': None,
|
|
'volume_type_id': None,
|
|
'consistencygroup_id': None,
|
|
'volume_metadata': [],
|
|
'metadata': {"Type": "work"}}
|
|
|
|
SRC_VOL_ID = "abcdabcd-1234-abcd-1234-abcdeffedcbc"
|
|
SRC_VOL = {
|
|
"name": "volume-" + SRC_VOL_ID,
|
|
"id": SRC_VOL_ID,
|
|
"display_name": "fake_src_vol",
|
|
"size": 1,
|
|
"host": "hostname@backend#%s" % FAKE_ID,
|
|
"volume_type": None,
|
|
"volume_type_id": None,
|
|
"volume_size": 1
|
|
}
|
|
|
|
VOLUME_NAME = 'cinder-' + VOLUME['id']
|
|
SRC_VOL_NAME = 'cinder-' + SRC_VOL['id']
|
|
DATA_OUTPUT = VOLUME_NAME, VOLUME_METADATA
|
|
SNAPSHOT_METADATA = {'fss-tm-comment': None}
|
|
|
|
ADD_VOLUME_IN_CG = {
|
|
'id': ADD_VOLUME_ID,
|
|
'display_name': 'abc123',
|
|
'display_description': '',
|
|
'size': 1,
|
|
'consistencygroup_id': GROUP_ID,
|
|
'status': 'available',
|
|
'host': "hostname@backend#%s" % FAKE_ID}
|
|
|
|
REMOVE_VOLUME_IN_CG = {
|
|
'id': 'fe2dbc515810451dab2f8c8a48d15bee',
|
|
'display_name': 'fe2dbc515810451dab2f8c8a48d15bee',
|
|
'display_description': '',
|
|
'size': 1,
|
|
'consistencygroup_id': GROUP_ID,
|
|
'status': 'available',
|
|
'host': "hostname@backend#%s" % FAKE_ID}
|
|
|
|
CONSISTGROUP = {'id': GROUP_ID,
|
|
'name': 'fake_group',
|
|
'description': 'fake_group_des',
|
|
'status': ''}
|
|
CG_SNAPSHOT = {
|
|
'consistencygroup_id': GROUP_ID,
|
|
'id': '3c61b0f9-842e-46bf-b061-5e0031d8083f',
|
|
'name': 'cgsnapshot1',
|
|
'description': 'cgsnapshot1',
|
|
'status': ''}
|
|
|
|
SNAPSHOT_ID = "abcdabcd-1234-abcd-1234-abcdeffedcbb"
|
|
ENCODED_SNAPSHOT_ID = "cinder-8W45SsgKTG2dnSUHoiQeuA"
|
|
SNAPSHOT = {'name': "snapshot-" + SNAPSHOT_ID,
|
|
'id': SNAPSHOT_ID,
|
|
'volume_id': VOLUME_ID,
|
|
'volume_name': "volume-" + VOLUME_ID,
|
|
'volume_size': 2,
|
|
'display_name': "fake_snapshot",
|
|
'display_description': '',
|
|
'volume': VOLUME,
|
|
'metadata': SNAPSHOT_METADATA,
|
|
'status': ''}
|
|
SNAPSHOT_LONG_NAME = {
|
|
'name': "SnapshotsActionsV1Test-Snapshot-" + SNAPSHOT_ID,
|
|
'id': SNAPSHOT_ID,
|
|
'volume_id': VOLUME_ID,
|
|
'volume_size': 2,
|
|
'display_name': 'SnapshotsActionsV1Test-Snapshot-901108447',
|
|
'volume': VOLUME,
|
|
'metadata': SNAPSHOT_METADATA,
|
|
'status': ''}
|
|
|
|
INITIATOR_IQN = 'iqn.2015-08.org.falconstor:01:fss'
|
|
TARGET_IQN = "iqn.2015-06.com.falconstor:freestor.fss-12345abc"
|
|
TARGET_PORT = "3260"
|
|
ISCSI_PORT_NAMES = ["ct0.eth2", "ct0.eth3", "ct1.eth2", "ct1.eth3"]
|
|
ISCSI_IPS = ["10.0.0." + str(i + 1) for i in range(len(ISCSI_PORT_NAMES))]
|
|
|
|
ISCSI_PORTS = {"iqn": TARGET_IQN, "lun": 1}
|
|
ISCSI_CONNECTOR = {'initiator': INITIATOR_IQN,
|
|
'host': "hostname@backend#%s" % FAKE_ID}
|
|
ISCSI_INFO = {
|
|
'driver_volume_type': 'iscsi',
|
|
'data': {
|
|
'target_discovered': True,
|
|
'discard': True,
|
|
'encrypted': False,
|
|
'qos_specs': None,
|
|
'access_mode': 'rw',
|
|
'volume_id': VOLUME_ID,
|
|
'target_iqn': ISCSI_PORTS['iqn'],
|
|
'target_portal': ISCSI_IPS[0] + ':' + TARGET_PORT,
|
|
'target_lun': 1
|
|
},
|
|
}
|
|
|
|
ISCSI_MULTIPATH_INFO = {
|
|
'driver_volume_type': 'iscsi',
|
|
'data''data': {
|
|
'target_discovered': False,
|
|
'discard': True,
|
|
'encrypted': False,
|
|
'qos_specs': None,
|
|
'access_mode': 'rw',
|
|
'volume_id': VOLUME_ID,
|
|
'target_iqns': [ISCSI_PORTS['iqn']],
|
|
'target_portals': [ISCSI_IPS[0] + ':' + TARGET_PORT],
|
|
'target_luns': [1]
|
|
},
|
|
}
|
|
|
|
FC_INITIATOR_WWPNS = ['2100000d778301c3', '2101000d77a301c3']
|
|
FC_TARGET_WWPNS = ['11000024ff2d2ca4', '11000024ff2d2ca5',
|
|
'11000024ff2d2c23', '11000024ff2d2c24']
|
|
FC_WWNS = ['20000024ff2d2ca4', '20000024ff2d2ca5',
|
|
'20000024ff2d2c23', '20000024ff2d2c24']
|
|
FC_CONNECTOR = {'ip': '10.10.0.1',
|
|
'initiator': 'iqn.1988-08.org.oracle:568eb4ccbbcc',
|
|
'wwpns': FC_INITIATOR_WWPNS,
|
|
'wwnns': FC_WWNS,
|
|
'host': FAKE_HOST,
|
|
'multipath': False}
|
|
FC_INITIATOR_TARGET_MAP = {
|
|
FC_INITIATOR_WWPNS[0]: [FC_TARGET_WWPNS[0], FC_TARGET_WWPNS[1]],
|
|
FC_INITIATOR_WWPNS[1]: [FC_TARGET_WWPNS[2], FC_TARGET_WWPNS[3]]
|
|
}
|
|
FC_DEVICE_MAPPING = {
|
|
"fabric": {
|
|
'initiator_port_wwn_list': FC_INITIATOR_WWPNS,
|
|
'target_port_wwn_list': FC_WWNS
|
|
}
|
|
}
|
|
|
|
FC_INFO = {
|
|
'driver_volume_type': 'fibre_channel',
|
|
'data': {
|
|
'target_discovered': True,
|
|
'volume_id': VOLUME_ID,
|
|
'target_lun': 1,
|
|
'target_wwn': FC_TARGET_WWPNS,
|
|
'initiator_target_map': FC_INITIATOR_TARGET_MAP
|
|
}
|
|
}
|
|
|
|
|
|
def Fake_sleep(time):
|
|
pass
|
|
|
|
|
|
class FSSDriverTestCase(test.TestCase):
|
|
|
|
def setUp(self):
|
|
super(FSSDriverTestCase, self).setUp()
|
|
self.mock_config = mock.Mock()
|
|
self.mock_config.san_ip = PRIMARY_IP
|
|
self.mock_config.san_login = FAKE
|
|
self.mock_config.san_password = FAKE
|
|
self.mock_config.fss_pools = FAKE_SINGLE_POOLS
|
|
self.mock_config.san_is_local = False
|
|
self.mock_config.fss_debug = False
|
|
self.mock_config.additional_retry_list = False
|
|
self.mock_object(time, 'sleep', Fake_sleep)
|
|
|
|
|
|
class TestFSSISCSIDriver(FSSDriverTestCase):
|
|
def __init__(self, method):
|
|
super(TestFSSISCSIDriver, self).__init__(method)
|
|
|
|
def setUp(self):
|
|
super(TestFSSISCSIDriver, self).setUp()
|
|
self.mock_config.use_chap_auth = False
|
|
self.mock_config.use_multipath_for_image_xfer = False
|
|
self.mock_config.volume_backend_name = ISCSI_VOLUME_BACKEND_NAME
|
|
self.driver = iscsi.FSSISCSIDriver(configuration=self.mock_config)
|
|
self.mock_utils = mock.Mock()
|
|
self.driver.driver_utils = self.mock_utils
|
|
|
|
def test_initialized_should_set_fss_info(self):
|
|
self.assertEqual(self.driver.proxy.fss_host,
|
|
self.driver.configuration.san_ip)
|
|
self.assertEqual(self.driver.proxy.fss_defined_pools,
|
|
self.driver.configuration.fss_pools)
|
|
|
|
def test_check_for_setup_error(self):
|
|
self.assertRaises(exception.VolumeBackendAPIException,
|
|
self.driver.check_for_setup_error)
|
|
|
|
@mock.patch.object(proxy.RESTProxy, 'create_vdev',
|
|
return_value=DATA_OUTPUT)
|
|
def test_create_volume(self, mock_create_vdev):
|
|
self.driver.create_volume(VOLUME)
|
|
mock_create_vdev.assert_called_once_with(VOLUME)
|
|
|
|
@mock.patch.object(proxy.RESTProxy, '_get_fss_volume_name',
|
|
return_value=VOLUME_NAME)
|
|
def test_extend_volume(self, mock__get_fss_volume_name):
|
|
"""Volume extended_volume successfully."""
|
|
self.driver.proxy.extend_vdev = mock.Mock()
|
|
result = self.driver.extend_volume(VOLUME, EXTENT_NEW_SIZE)
|
|
mock__get_fss_volume_name.assert_called_once_with(VOLUME)
|
|
self.driver.proxy.extend_vdev.assert_called_once_with(VOLUME_NAME,
|
|
VOLUME["size"],
|
|
EXTENT_NEW_SIZE)
|
|
self.assertIsNone(result)
|
|
|
|
@mock.patch.object(proxy.RESTProxy, '_get_fss_volume_name')
|
|
def test_clone_volume(self, mock__get_fss_volume_name):
|
|
mock__get_fss_volume_name.side_effect = [VOLUME_NAME, SRC_VOL_NAME]
|
|
self.driver.proxy.clone_volume = mock.Mock(
|
|
return_value=VOLUME_METADATA)
|
|
self.driver.proxy.extend_vdev = mock.Mock()
|
|
|
|
self.driver.create_cloned_volume(VOLUME, SRC_VOL)
|
|
self.driver.proxy.clone_volume.assert_called_with(VOLUME_NAME,
|
|
SRC_VOL_NAME)
|
|
|
|
mock__get_fss_volume_name.assert_any_call(VOLUME)
|
|
mock__get_fss_volume_name.assert_any_call(SRC_VOL)
|
|
self.assertEqual(2, mock__get_fss_volume_name.call_count)
|
|
|
|
self.driver.proxy.extend_vdev(VOLUME_NAME, VOLUME["size"],
|
|
SRC_VOL["size"])
|
|
self.driver.proxy.extend_vdev.assert_called_with(VOLUME_NAME,
|
|
VOLUME["size"],
|
|
SRC_VOL["size"])
|
|
|
|
@mock.patch.object(proxy.RESTProxy, 'delete_vdev')
|
|
def test_delete_volume(self, mock_delete_vdev):
|
|
result = self.driver.delete_volume(VOLUME)
|
|
mock_delete_vdev.assert_called_once_with(VOLUME)
|
|
self.assertIsNone(result)
|
|
|
|
@mock.patch.object(proxy.RESTProxy, 'create_snapshot',
|
|
return_value=API_RESPONSE)
|
|
def test_create_snapshot(self, mock_create_snapshot):
|
|
snap_name = SNAPSHOT.get('display_name')
|
|
SNAPSHOT_METADATA["fss-tm-comment"] = snap_name
|
|
result = self.driver.create_snapshot(SNAPSHOT)
|
|
mock_create_snapshot.assert_called_once_with(SNAPSHOT)
|
|
self.assertEqual(result, {'metadata': SNAPSHOT_METADATA})
|
|
|
|
@mock.patch.object(proxy.RESTProxy, 'create_snapshot',
|
|
return_value=API_RESPONSE)
|
|
def test_create_snapshot_exceed_characters_len(self, mock_create_snapshot):
|
|
SNAPSHOT_METADATA["fss-tm-comment"] = ENCODED_SNAPSHOT_ID
|
|
result = self.driver.create_snapshot(SNAPSHOT_LONG_NAME)
|
|
mock_create_snapshot.assert_called_once_with(SNAPSHOT_LONG_NAME)
|
|
self.assertEqual(result, {'metadata': SNAPSHOT_METADATA})
|
|
|
|
@mock.patch.object(proxy.RESTProxy, 'delete_snapshot',
|
|
return_value=API_RESPONSE)
|
|
def test_delete_snapshot(self, mock_delete_snapshot):
|
|
result = self.driver.delete_snapshot(SNAPSHOT)
|
|
mock_delete_snapshot.assert_called_once_with(SNAPSHOT)
|
|
self.assertIsNone(result)
|
|
|
|
@mock.patch.object(proxy.RESTProxy, 'create_volume_from_snapshot',
|
|
return_value=(VOLUME_NAME, VOLUME_METADATA))
|
|
@mock.patch.object(proxy.RESTProxy, '_get_fss_volume_name',
|
|
return_value=VOLUME_NAME)
|
|
def test_create_volume_from_snapshot(self, mock__get_fss_volume_name,
|
|
mock_create_volume_from_snapshot):
|
|
vol_size = VOLUME['size']
|
|
snap_size = SNAPSHOT['volume_size']
|
|
self.driver.proxy.extend_vdev = mock.Mock()
|
|
|
|
self.assertEqual(
|
|
self.driver.create_volume_from_snapshot(VOLUME, SNAPSHOT),
|
|
dict(metadata=VOLUME_METADATA))
|
|
mock_create_volume_from_snapshot.assert_called_once_with(VOLUME,
|
|
SNAPSHOT)
|
|
|
|
if vol_size != snap_size:
|
|
mock__get_fss_volume_name.assert_called_once_with(VOLUME)
|
|
self.driver.proxy.extend_vdev(VOLUME_NAME, snap_size, vol_size)
|
|
self.driver.proxy.extend_vdev.assert_called_with(VOLUME_NAME,
|
|
snap_size,
|
|
vol_size)
|
|
|
|
@mock.patch.object(proxy.RESTProxy, 'create_group')
|
|
def test_create_consistency_group(self, mock_create_group):
|
|
ctxt = context.get_admin_context()
|
|
model_update = self.driver.create_consistencygroup(ctxt, CONSISTGROUP)
|
|
mock_create_group.assert_called_once_with(CONSISTGROUP)
|
|
self.assertDictEqual({'status': 'available'}, model_update)
|
|
|
|
@mock.patch.object(proxy.RESTProxy, 'destroy_group')
|
|
@mock.patch(BASE_DRIVER + ".delete_volume", autospec=True)
|
|
def test_delete_consistency_group(self, mock_delete_vdev,
|
|
mock_destroy_group):
|
|
mock_cgroup = mock.MagicMock()
|
|
mock_cgroup.id = FAKE_ID
|
|
mock_cgroup['status'] = "deleted"
|
|
mock_context = mock.Mock()
|
|
mock_volume = mock.MagicMock()
|
|
expected_volume_updates = [{
|
|
'id': mock_volume.id,
|
|
'status': 'deleted'
|
|
}]
|
|
model_update, volumes = self.driver.delete_consistencygroup(
|
|
mock_context, mock_cgroup, [mock_volume])
|
|
|
|
mock_destroy_group.assert_called_with(mock_cgroup)
|
|
self.assertEqual(expected_volume_updates, volumes)
|
|
self.assertEqual(mock_cgroup['status'], model_update['status'])
|
|
mock_delete_vdev.assert_called_with(self.driver, mock_volume)
|
|
|
|
@mock.patch.object(proxy.RESTProxy, 'set_group')
|
|
def test_update_consistency_group(self, mock_set_group):
|
|
ctxt = context.get_admin_context()
|
|
add_vols = [
|
|
{'name': 'vol1', 'id': 'vol1', 'display_name': ''},
|
|
{'name': 'vol2', 'id': 'vol2', 'display_name': ''}
|
|
]
|
|
remove_vols = [
|
|
{'name': 'vol3', 'id': 'vol3', 'display_name': ''},
|
|
{'name': 'vol4', 'id': 'vol4', 'display_name': ''}
|
|
]
|
|
|
|
expected_addvollist = ["cinder-%s" % volume['id'] for volume in
|
|
add_vols]
|
|
expected_remvollist = ["cinder-%s" % vol['id'] for vol in remove_vols]
|
|
|
|
self.driver.update_consistencygroup(ctxt, CONSISTGROUP,
|
|
add_volumes=add_vols,
|
|
remove_volumes=remove_vols)
|
|
mock_set_group.assert_called_with(GROUP_ID,
|
|
addvollist=expected_addvollist,
|
|
remvollist=expected_remvollist)
|
|
|
|
@mock.patch.object(proxy.RESTProxy, 'create_cgsnapshot')
|
|
def test_create_cgsnapshot(self, mock_create_cgsnapshot):
|
|
mock_cgsnap = CG_SNAPSHOT
|
|
mock_context = mock.Mock()
|
|
mock_snap = mock.MagicMock()
|
|
model_update, snapshots = self.driver.create_cgsnapshot(mock_context,
|
|
mock_cgsnap,
|
|
[mock_snap])
|
|
mock_create_cgsnapshot.assert_called_once_with(mock_cgsnap)
|
|
self.assertEqual({'status': 'available'}, model_update)
|
|
expected_snapshot_update = [{
|
|
'id': mock_snap.id,
|
|
'status': 'available'
|
|
}]
|
|
self.assertEqual(expected_snapshot_update, snapshots)
|
|
|
|
@mock.patch.object(proxy.RESTProxy, 'delete_cgsnapshot')
|
|
def test_delete_cgsnapshot(self, mock_delete_cgsnapshot):
|
|
mock_cgsnap = mock.Mock()
|
|
mock_cgsnap.id = FAKE_ID
|
|
mock_cgsnap.status = 'deleted'
|
|
mock_context = mock.Mock()
|
|
mock_snap = mock.MagicMock()
|
|
|
|
model_update, snapshots = self.driver.delete_cgsnapshot(mock_context,
|
|
mock_cgsnap,
|
|
[mock_snap])
|
|
mock_delete_cgsnapshot.assert_called_once_with(mock_cgsnap)
|
|
self.assertEqual({'status': mock_cgsnap.status}, model_update)
|
|
|
|
expected_snapshot_update = [dict(id=mock_snap.id, status='deleted')]
|
|
self.assertEqual(expected_snapshot_update, snapshots)
|
|
|
|
@mock.patch.object(proxy.RESTProxy, 'initialize_connection_iscsi',
|
|
return_value=ISCSI_PORTS)
|
|
def test_initialize_connection(self, mock_initialize_connection_iscsi):
|
|
FSS_HOSTS = []
|
|
FSS_HOSTS.append(PRIMARY_IP)
|
|
ret = self.driver.initialize_connection(VOLUME, ISCSI_CONNECTOR)
|
|
mock_initialize_connection_iscsi.assert_called_once_with(
|
|
VOLUME,
|
|
ISCSI_CONNECTOR,
|
|
FSS_HOSTS)
|
|
result = deepcopy(ISCSI_INFO)
|
|
self.assertDictEqual(result, ret)
|
|
|
|
@mock.patch.object(proxy.RESTProxy, 'initialize_connection_iscsi')
|
|
@mock.patch(ISCSI_DRIVER + "._check_multipath", autospec=True)
|
|
def test_initialize_connection_multipath(self, mock__check_multipath,
|
|
mock_initialize_connection_iscsi):
|
|
fss_hosts = []
|
|
fss_hosts.append(self.mock_config.san_ip)
|
|
mock_initialize_connection_iscsi.return_value = ISCSI_PORTS
|
|
mock__check_multipath.retuen_value = True
|
|
|
|
self.mock_config.use_multipath_for_image_xfer = True
|
|
self.mock_config.fss_san_secondary_ip = SECONDARY_IP
|
|
multipath_connector = deepcopy(ISCSI_CONNECTOR)
|
|
multipath_connector["multipath"] = True
|
|
fss_hosts.append(SECONDARY_IP)
|
|
|
|
self.driver.initialize_connection(VOLUME, multipath_connector)
|
|
mock_initialize_connection_iscsi.assert_called_once_with(
|
|
VOLUME,
|
|
multipath_connector,
|
|
fss_hosts)
|
|
|
|
@mock.patch.object(proxy.RESTProxy, 'terminate_connection_iscsi')
|
|
def test_terminate_connection(self, mock_terminate_connection_iscsi):
|
|
self.driver.terminate_connection(VOLUME, ISCSI_CONNECTOR)
|
|
mock_terminate_connection_iscsi.assert_called_once_with(
|
|
VOLUME,
|
|
ISCSI_CONNECTOR)
|
|
|
|
@mock.patch.object(proxy.RESTProxy, '_manage_existing_volume')
|
|
@mock.patch.object(proxy.RESTProxy, '_get_existing_volume_ref_vid')
|
|
def test_manage_existing(self, mock__get_existing_volume_ref_vid,
|
|
mock__manage_existing_volume):
|
|
ref_vid = 1
|
|
volume_ref = {'source-id': ref_vid}
|
|
self.driver.manage_existing(VOLUME, volume_ref)
|
|
mock__get_existing_volume_ref_vid.assert_called_once_with(volume_ref)
|
|
mock__manage_existing_volume.assert_called_once_with(
|
|
volume_ref['source-id'], VOLUME)
|
|
|
|
@mock.patch.object(proxy.RESTProxy, '_get_existing_volume_ref_vid',
|
|
return_value=5120)
|
|
def test_manage_existing_get_size(self, mock__get_existing_volume_ref_vid):
|
|
ref_vid = 1
|
|
volume_ref = {'source-id': ref_vid}
|
|
expected_size = 5
|
|
size = self.driver.manage_existing_get_size(VOLUME, volume_ref)
|
|
mock__get_existing_volume_ref_vid.assert_called_once_with(volume_ref)
|
|
self.assertEqual(expected_size, size)
|
|
|
|
@mock.patch.object(proxy.RESTProxy, 'unmanage')
|
|
def test_unmanage(self, mock_unmanage):
|
|
self.driver.unmanage(VOLUME)
|
|
mock_unmanage.assert_called_once_with(VOLUME)
|
|
|
|
|
|
class TestFSSFCDriver(FSSDriverTestCase):
|
|
|
|
def setUp(self):
|
|
super(TestFSSFCDriver, self).setUp()
|
|
self.driver = fc.FSSFCDriver(configuration=self.mock_config)
|
|
self.driver._lookup_service = mock.Mock()
|
|
|
|
@mock.patch.object(proxy.RESTProxy, 'fc_initialize_connection')
|
|
def test_initialize_connection(self, mock_fc_initialize_connection):
|
|
fss_hosts = []
|
|
fss_hosts.append(PRIMARY_IP)
|
|
self.driver.initialize_connection(VOLUME, FC_CONNECTOR)
|
|
mock_fc_initialize_connection.assert_called_once_with(
|
|
VOLUME,
|
|
FC_CONNECTOR,
|
|
fss_hosts)
|
|
|
|
@mock.patch.object(proxy.RESTProxy, '_check_fc_host_devices_empty',
|
|
return_value=False)
|
|
@mock.patch.object(proxy.RESTProxy, 'fc_terminate_connection',
|
|
return_value=FAKE_ID)
|
|
def test_terminate_connection(self, mock_fc_terminate_connection,
|
|
mock__check_fc_host_devices_empty):
|
|
self.driver.terminate_connection(VOLUME, FC_CONNECTOR)
|
|
mock_fc_terminate_connection.assert_called_once_with(
|
|
VOLUME,
|
|
FC_CONNECTOR)
|
|
mock__check_fc_host_devices_empty.assert_called_once_with(FAKE_ID)
|
|
|
|
|
|
class TestRESTProxy(test.TestCase):
|
|
"""Test REST Proxy Driver."""
|
|
|
|
def setUp(self):
|
|
super(TestRESTProxy, self).setUp()
|
|
configuration = mock.Mock(conf.Configuration)
|
|
configuration.san_ip = FAKE
|
|
configuration.san_login = FAKE
|
|
configuration.san_password = FAKE
|
|
configuration.fss_pools = FAKE_SINGLE_POOLS
|
|
configuration.fss_debug = False
|
|
configuration.additional_retry_list = None
|
|
|
|
self.proxy = proxy.RESTProxy(configuration)
|
|
self.FSS_MOCK = mock.MagicMock()
|
|
self.proxy.FSS = self.FSS_MOCK
|
|
self.FSS_MOCK._fss_request.return_value = API_RESPONSE
|
|
self.mock_object(time, 'sleep', Fake_sleep)
|
|
|
|
def test_do_setup(self):
|
|
self.proxy.do_setup()
|
|
self.FSS_MOCK.fss_login.assert_called_once_with()
|
|
self.assertNotEqual(self.proxy.session_id, SESSION_ID)
|
|
|
|
def test_create_volume(self):
|
|
sizemb = self.proxy._convert_size_to_mb(VOLUME['size'])
|
|
volume_name = self.proxy._get_fss_volume_name(VOLUME)
|
|
_pool_id = self.proxy._selected_pool_id(FAKE_SINGLE_POOLS, "P")
|
|
|
|
params = dict(storagepoolid=_pool_id,
|
|
sizemb=sizemb,
|
|
category="virtual",
|
|
name=volume_name)
|
|
self.proxy.create_vdev(VOLUME)
|
|
self.FSS_MOCK.create_vdev.assert_called_once_with(params)
|
|
|
|
@mock.patch.object(proxy.RESTProxy, '_get_fss_vid_from_name',
|
|
return_value=FAKE_ID)
|
|
def test_extend_volume(self, mock__get_fss_vid_from_name):
|
|
size = self.proxy._convert_size_to_mb(EXTENT_NEW_SIZE - VOLUME['size'])
|
|
params = dict(
|
|
action='expand',
|
|
sizemb=size
|
|
)
|
|
volume_name = self.proxy._get_fss_volume_name(VOLUME)
|
|
self.proxy.extend_vdev(volume_name, VOLUME["size"], EXTENT_NEW_SIZE)
|
|
|
|
mock__get_fss_vid_from_name.assert_called_once_with(volume_name,
|
|
FSS_SINGLE_TYPE)
|
|
self.FSS_MOCK.extend_vdev.assert_called_once_with(FAKE_ID, params)
|
|
|
|
@mock.patch.object(proxy.RESTProxy, '_get_fss_vid_from_name',
|
|
return_value=FAKE_ID)
|
|
def test_delete_volume(self, mock__get_fss_vid_from_name):
|
|
volume_name = self.proxy._get_fss_volume_name(VOLUME)
|
|
self.proxy.delete_vdev(VOLUME)
|
|
mock__get_fss_vid_from_name.assert_called_once_with(volume_name,
|
|
FSS_SINGLE_TYPE)
|
|
self.FSS_MOCK.delete_vdev.assert_called_once_with(FAKE_ID)
|
|
|
|
@mock.patch.object(proxy.RESTProxy, '_get_fss_vid_from_name',
|
|
return_value=FAKE_ID)
|
|
def test_clone_volume(self, mock__get_fss_vid_from_name):
|
|
self.FSS_MOCK.create_mirror.return_value = API_RESPONSE
|
|
self.FSS_MOCK.sync_mirror.return_value = API_RESPONSE
|
|
_pool_id = self.proxy._selected_pool_id(FAKE_SINGLE_POOLS, "O")
|
|
mirror_params = dict(
|
|
category='virtual',
|
|
selectioncriteria='anydrive',
|
|
mirrortarget="virtual",
|
|
storagepoolid=_pool_id
|
|
)
|
|
ret = self.proxy.clone_volume(VOLUME_NAME, SRC_VOL_NAME)
|
|
|
|
self.FSS_MOCK.create_mirror.assert_called_once_with(FAKE_ID,
|
|
mirror_params)
|
|
self.FSS_MOCK.sync_mirror.assert_called_once_with(FAKE_ID)
|
|
self.FSS_MOCK.promote_mirror.assert_called_once_with(FAKE_ID,
|
|
VOLUME_NAME)
|
|
self.assertNotEqual(ret, VOLUME_METADATA)
|
|
|
|
@mock.patch.object(proxy.RESTProxy, 'create_vdev_snapshot')
|
|
@mock.patch.object(proxy.RESTProxy, '_get_fss_vid_from_name',
|
|
return_value=FAKE_ID)
|
|
@mock.patch.object(proxy.RESTProxy, '_get_vol_name_from_snap',
|
|
return_value=VOLUME_NAME)
|
|
def test_create_snapshot(self, mock__get_vol_name_from_snap,
|
|
mock__get_fss_vid_from_name,
|
|
mock_create_vdev_snapshot):
|
|
self.FSS_MOCK._check_if_snapshot_tm_exist.return_value = [
|
|
False, False, SNAPSHOT['volume_size']]
|
|
|
|
self.proxy.create_snapshot(SNAPSHOT)
|
|
self.FSS_MOCK._check_if_snapshot_tm_exist.assert_called_once_with(
|
|
FAKE_ID)
|
|
sizemb = self.proxy._convert_size_to_mb(SNAPSHOT['volume_size'])
|
|
mock_create_vdev_snapshot.assert_called_once_with(FAKE_ID, sizemb)
|
|
_pool_id = self.proxy._selected_pool_id(FAKE_SINGLE_POOLS, "O")
|
|
self.FSS_MOCK.create_timemark_policy.assert_called_once_with(
|
|
FAKE_ID,
|
|
storagepoolid=_pool_id)
|
|
self.FSS_MOCK.create_timemark.assert_called_once_with(
|
|
FAKE_ID,
|
|
SNAPSHOT["display_name"])
|
|
|
|
@mock.patch.object(proxy.RESTProxy, '_get_timestamp',
|
|
return_value=RAWTIMESTAMP)
|
|
@mock.patch.object(proxy.RESTProxy, '_get_fss_vid_from_name',
|
|
return_value=FAKE_ID)
|
|
@mock.patch.object(proxy.RESTProxy, '_get_vol_name_from_snap',
|
|
return_value=VOLUME_NAME)
|
|
def test_delete_snapshot(self, mock__get_vol_name_from_snap,
|
|
mock__get_fss_vid_from_name,
|
|
mock__get_timestamp):
|
|
timestamp = '%s_%s' % (FAKE_ID, RAWTIMESTAMP)
|
|
|
|
self.proxy.delete_snapshot(SNAPSHOT)
|
|
mock__get_vol_name_from_snap.assert_called_once_with(SNAPSHOT)
|
|
self.FSS_MOCK.delete_timemark.assert_called_once_with(timestamp)
|
|
self.FSS_MOCK.get_timemark.assert_any_call(FAKE_ID)
|
|
self.assertEqual(2, self.FSS_MOCK.get_timemark.call_count)
|
|
|
|
@mock.patch.object(proxy.RESTProxy, '_get_timestamp')
|
|
@mock.patch.object(proxy.RESTProxy, '_get_fss_vid_from_name')
|
|
@mock.patch.object(proxy.RESTProxy, '_get_vol_name_from_snap')
|
|
def test_create_volume_from_snapshot(self, mock__get_vol_name_from_snap,
|
|
mock__get_fss_vid_from_name,
|
|
mock__get_timestamp):
|
|
tm_info = {"rc": 0,
|
|
"data":
|
|
{
|
|
"guid": "497bad5e-e589-bb0a-e0e7-00004eeac169",
|
|
"name": "SANDisk-001",
|
|
"total": "1",
|
|
"timemark": [
|
|
{
|
|
"size": 131072,
|
|
"comment": "123test456",
|
|
"hastimeview": False,
|
|
"priority": "low",
|
|
"quiescent": "yes",
|
|
"timeviewdata": "notkept",
|
|
"rawtimestamp": "1324975390",
|
|
"timestamp": "2015-10-11 16:43:10"
|
|
}]
|
|
}
|
|
}
|
|
mock__get_vol_name_from_snap.return_value = VOLUME_NAME
|
|
new_vol_name = self.proxy._get_fss_volume_name(VOLUME)
|
|
mock__get_fss_vid_from_name.return_value = FAKE_ID
|
|
|
|
self.FSS_MOCK.get_timemark.return_value = tm_info
|
|
mock__get_timestamp.return_value = RAWTIMESTAMP
|
|
timestamp = '%s_%s' % (FAKE_ID, RAWTIMESTAMP)
|
|
_pool_id = self.proxy._selected_pool_id(FAKE_SINGLE_POOLS, "O")
|
|
|
|
self.proxy.create_volume_from_snapshot(VOLUME, SNAPSHOT)
|
|
self.FSS_MOCK.get_timemark.assert_called_once_with(FAKE_ID)
|
|
mock__get_timestamp.assert_called_once_with(tm_info,
|
|
SNAPSHOT['display_name'])
|
|
self.FSS_MOCK.copy_timemark.assert_called_once_with(
|
|
timestamp,
|
|
storagepoolid=_pool_id,
|
|
name=new_vol_name)
|
|
|
|
@mock.patch.object(proxy.RESTProxy, '_get_group_name_from_id')
|
|
def test_create_consistency_group(self, mock__get_group_name_from_id):
|
|
|
|
mock__get_group_name_from_id.return_value = CONSISTGROUP['name']
|
|
params = dict(name=CONSISTGROUP['name'])
|
|
self.proxy.create_group(CONSISTGROUP)
|
|
self.FSS_MOCK.create_group.assert_called_once_with(params)
|
|
|
|
@mock.patch.object(proxy.RESTProxy, '_get_fss_gid_from_name')
|
|
@mock.patch.object(proxy.RESTProxy, '_get_group_name_from_id')
|
|
def test_delete_consistency_group(self, mock__get_group_name_from_id,
|
|
mock__get_fss_gid_from_name):
|
|
mock__get_group_name_from_id.return_value = CONSISTGROUP['name']
|
|
mock__get_fss_gid_from_name.return_value = FAKE_ID
|
|
|
|
self.proxy.destroy_group(CONSISTGROUP)
|
|
mock__get_group_name_from_id.assert_called_once_with(
|
|
CONSISTGROUP['id'])
|
|
mock__get_fss_gid_from_name.assert_called_once_with(
|
|
CONSISTGROUP['name'])
|
|
self.FSS_MOCK.destroy_group.assert_called_once_with(FAKE_ID)
|
|
|
|
@mock.patch.object(proxy.RESTProxy, '_get_fss_vid_from_name')
|
|
@mock.patch.object(proxy.RESTProxy, '_get_fss_gid_from_name')
|
|
@mock.patch.object(proxy.RESTProxy, '_get_group_name_from_id')
|
|
def test_update_consistency_group(self, mock__get_group_name_from_id,
|
|
mock__get_fss_gid_from_name,
|
|
mock__get_fss_vid_from_name):
|
|
join_vid_list = [1, 2]
|
|
leave_vid_list = [3, 4]
|
|
mock__get_group_name_from_id.return_value = CONSISTGROUP['name']
|
|
mock__get_fss_gid_from_name.return_value = FAKE_ID
|
|
mock__get_fss_vid_from_name.side_effect = [join_vid_list,
|
|
leave_vid_list]
|
|
add_vols = [
|
|
{'name': 'vol1', 'id': 'vol1'},
|
|
{'name': 'vol2', 'id': 'vol2'}
|
|
]
|
|
remove_vols = [
|
|
{'name': 'vol3', 'id': 'vol3'},
|
|
{'name': 'vol4', 'id': 'vol4'}
|
|
]
|
|
expected_addvollist = ["cinder-%s" % volume['id'] for volume in
|
|
add_vols]
|
|
expected_remvollist = ["cinder-%s" % vol['id'] for vol in remove_vols]
|
|
|
|
self.proxy.set_group(CONSISTGROUP, addvollist=expected_addvollist,
|
|
remvollist=expected_remvollist)
|
|
|
|
if expected_addvollist:
|
|
mock__get_fss_vid_from_name.assert_any_call(expected_addvollist)
|
|
|
|
if expected_remvollist:
|
|
mock__get_fss_vid_from_name.assert_any_call(expected_remvollist)
|
|
self.assertEqual(2, mock__get_fss_vid_from_name.call_count)
|
|
|
|
join_params = dict()
|
|
leave_params = dict()
|
|
|
|
join_params.update(
|
|
action='join',
|
|
virtualdevices=join_vid_list
|
|
)
|
|
leave_params.update(
|
|
action='leave',
|
|
virtualdevices=leave_vid_list
|
|
)
|
|
self.FSS_MOCK.set_group.assert_called_once_with(FAKE_ID, join_params,
|
|
leave_params)
|
|
|
|
@mock.patch.object(proxy.RESTProxy, 'create_vdev_snapshot')
|
|
@mock.patch.object(proxy.RESTProxy, 'create_group_timemark')
|
|
@mock.patch.object(proxy.RESTProxy, '_get_vdev_id_from_group_id')
|
|
@mock.patch.object(proxy.RESTProxy, '_get_fss_gid_from_name')
|
|
@mock.patch.object(proxy.RESTProxy, '_get_group_name_from_id')
|
|
def test_create_cgsnapshot(self, mock__get_group_name_from_id,
|
|
mock__get_fss_gid_from_name,
|
|
mock__get_vdev_id_from_group_id,
|
|
mock_create_group_timemark,
|
|
mock_create_vdev_snapshot
|
|
):
|
|
vid_list = [1]
|
|
|
|
group_name = "cinder-consisgroup-%s" % CG_SNAPSHOT[
|
|
'consistencygroup_id']
|
|
mock__get_group_name_from_id.return_value = group_name
|
|
mock__get_fss_gid_from_name.return_value = FAKE_ID
|
|
mock__get_vdev_id_from_group_id.return_value = vid_list
|
|
gsnap_name = self.proxy._encode_name(CG_SNAPSHOT['id'])
|
|
self.FSS_MOCK._check_if_snapshot_tm_exist.return_value = (
|
|
False,
|
|
False,
|
|
1024)
|
|
|
|
self.proxy.create_cgsnapshot(CG_SNAPSHOT)
|
|
mock__get_group_name_from_id.assert_called_once_with(
|
|
CG_SNAPSHOT['consistencygroup_id'])
|
|
mock__get_fss_gid_from_name.assert_called_once_with(group_name)
|
|
mock__get_vdev_id_from_group_id.assert_called_once_with(FAKE_ID)
|
|
_pool_id = self.proxy._selected_pool_id(FAKE_SINGLE_POOLS, "O")
|
|
|
|
for vid in vid_list:
|
|
self.FSS_MOCK._check_if_snapshot_tm_exist.assert_called_with(vid)
|
|
mock_create_vdev_snapshot.assert_called_once_with(vid, 1024)
|
|
self.FSS_MOCK.create_timemark_policy.assert_called_once_with(
|
|
vid,
|
|
storagepoolid=_pool_id)
|
|
|
|
mock_create_group_timemark.assert_called_once_with(FAKE_ID, gsnap_name)
|
|
|
|
@mock.patch.object(proxy.RESTProxy, 'delete_group_timemark')
|
|
@mock.patch.object(proxy.RESTProxy, '_get_fss_group_membercount')
|
|
@mock.patch.object(proxy.RESTProxy, '_get_fss_gid_from_name')
|
|
@mock.patch.object(proxy.RESTProxy, '_get_group_name_from_id')
|
|
def test_delete_cgsnapshot(self, mock__get_group_name_from_id,
|
|
mock__get_fss_gid_from_name,
|
|
mock__get_fss_group_membercount,
|
|
mock_delete_group_timemark):
|
|
tm_info = {
|
|
"rc": 0,
|
|
"data":
|
|
{
|
|
"name": "GroupTestABC",
|
|
"total": 1,
|
|
"timemark": [{
|
|
"size": 65536,
|
|
"comment": "cinder-PGGwaaaaaaaar+wYV4AMdgIPw",
|
|
"priority": "low",
|
|
"quiescent": "yes",
|
|
"hastimeview": "false",
|
|
"timeviewdata": "notkept",
|
|
"rawtimestamp": "1324974940",
|
|
"timestamp": "2015-10-15 16:35:40"}]
|
|
}
|
|
}
|
|
final_tm_data = {
|
|
"rc": 0,
|
|
"data":
|
|
{"name": "GroupTestABC",
|
|
"total": 1,
|
|
"timemark": []
|
|
}}
|
|
|
|
mock__get_group_name_from_id.return_value = CG_SNAPSHOT[
|
|
'consistencygroup_id']
|
|
mock__get_fss_gid_from_name.return_value = FAKE_ID
|
|
self.FSS_MOCK.get_group_timemark.side_effect = [tm_info, final_tm_data]
|
|
encode_snap_name = self.proxy._encode_name(CG_SNAPSHOT['id'])
|
|
self.proxy.delete_cgsnapshot(CG_SNAPSHOT)
|
|
mock__get_fss_group_membercount.assert_called_once_with(FAKE_ID)
|
|
|
|
self.assertEqual(2, self.FSS_MOCK.get_group_timemark.call_count)
|
|
self.FSS_MOCK.get_group_timemark.assert_any_call(FAKE_ID)
|
|
rawtimestamp = self.proxy._get_timestamp(tm_info, encode_snap_name)
|
|
timestamp = '%s_%s' % (FAKE_ID, rawtimestamp)
|
|
mock_delete_group_timemark.assert_called_once_with(timestamp)
|
|
self.FSS_MOCK.delete_group_timemark_policy.assert_called_once_with(
|
|
FAKE_ID)
|
|
|
|
@mock.patch.object(proxy.RESTProxy, 'initialize_connection_iscsi')
|
|
def test_iscsi_initialize_connection(self,
|
|
mock_initialize_connection_iscsi):
|
|
fss_hosts = []
|
|
fss_hosts.append(PRIMARY_IP)
|
|
self.proxy.initialize_connection_iscsi(VOLUME, ISCSI_CONNECTOR,
|
|
fss_hosts)
|
|
mock_initialize_connection_iscsi.assert_called_once_with(
|
|
VOLUME,
|
|
ISCSI_CONNECTOR,
|
|
fss_hosts)
|
|
|
|
@mock.patch.object(proxy.RESTProxy, 'terminate_connection_iscsi')
|
|
def test_iscsi_terminate_connection(self, mock_terminate_connection_iscsi):
|
|
self.FSS_MOCK._get_target_info.return_value = (FAKE_ID, INITIATOR_IQN)
|
|
|
|
self.proxy.terminate_connection_iscsi(VOLUME, ISCSI_CONNECTOR)
|
|
mock_terminate_connection_iscsi.assert_called_once_with(
|
|
VOLUME,
|
|
ISCSI_CONNECTOR)
|
|
|
|
@mock.patch.object(proxy.RESTProxy, 'rename_vdev')
|
|
@mock.patch.object(proxy.RESTProxy, '_get_fss_volume_name')
|
|
def test_manage_existing(self, mock__get_fss_volume_name,
|
|
mock_rename_vdev):
|
|
new_vol_name = 'rename-vol'
|
|
mock__get_fss_volume_name.return_value = new_vol_name
|
|
|
|
self.proxy._manage_existing_volume(FAKE_ID, VOLUME)
|
|
mock__get_fss_volume_name.assert_called_once_with(VOLUME)
|
|
mock_rename_vdev.assert_called_once_with(FAKE_ID, new_vol_name)
|
|
|
|
@mock.patch.object(proxy.RESTProxy, 'list_volume_info')
|
|
def test_manage_existing_get_size(self, mock_list_volume_info):
|
|
volume_ref = {'source-id': FAKE_ID}
|
|
vdev_info = {
|
|
"rc": 0,
|
|
"data": {
|
|
"name": "cinder-2ab1f70a-6c89-432c-84e3-5fa6c187fb92",
|
|
"type": "san",
|
|
"category": "virtual",
|
|
"sizemb": 1020
|
|
}}
|
|
|
|
mock_list_volume_info.return_value = vdev_info
|
|
self.proxy._get_existing_volume_ref_vid(volume_ref)
|
|
mock_list_volume_info.assert_called_once_with(FAKE_ID)
|
|
|
|
@mock.patch.object(proxy.RESTProxy, 'rename_vdev')
|
|
@mock.patch.object(proxy.RESTProxy, '_get_fss_vid_from_name')
|
|
@mock.patch.object(proxy.RESTProxy, '_get_fss_volume_name')
|
|
def test_unmanage(self, mock__get_fss_volume_name,
|
|
mock__get_fss_vid_from_name,
|
|
mock_rename_vdev):
|
|
|
|
mock__get_fss_volume_name.return_value = VOLUME_NAME
|
|
mock__get_fss_vid_from_name.return_value = FAKE_ID
|
|
unmanaged_vol_name = VOLUME_NAME + "-unmanaged"
|
|
|
|
self.proxy.unmanage(VOLUME)
|
|
mock__get_fss_volume_name.assert_called_once_with(VOLUME)
|
|
mock__get_fss_vid_from_name.assert_called_once_with(VOLUME_NAME,
|
|
FSS_SINGLE_TYPE)
|
|
mock_rename_vdev.assert_called_once_with(FAKE_ID, unmanaged_vol_name)
|