2063 lines
80 KiB
Python
2063 lines
80 KiB
Python
# (c) Copyright 2014 Hewlett-Packard Development Company, L.P.
|
|
# 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 OpenStack Cinder volume drivers."""
|
|
|
|
import mock
|
|
from oslo_utils import units
|
|
import six
|
|
|
|
from cinder import context
|
|
from cinder import exception
|
|
from cinder import test
|
|
from cinder.tests.unit import fake_hp_lefthand_client as hplefthandclient
|
|
from cinder.volume.drivers.san.hp import hp_lefthand_iscsi
|
|
from cinder.volume.drivers.san.hp import hp_lefthand_rest_proxy
|
|
from cinder.volume import volume_types
|
|
|
|
hpexceptions = hplefthandclient.hpexceptions
|
|
|
|
GOODNESS_FUNCTION = \
|
|
"capabilities.capacity_utilization < 0.6? 100 : 25"
|
|
FILTER_FUNCTION = \
|
|
"capabilities.total_volumes < 400 && capabilities.capacity_utilization"
|
|
|
|
|
|
class HPLeftHandBaseDriver(object):
|
|
|
|
cluster_id = 1
|
|
|
|
volume_name = "fakevolume"
|
|
volume_id = 1
|
|
volume = {
|
|
'name': volume_name,
|
|
'display_name': 'Foo Volume',
|
|
'provider_location': ('10.0.1.6 iqn.2003-10.com.lefthandnetworks:'
|
|
'group01:25366:fakev 0'),
|
|
'id': volume_id,
|
|
'provider_auth': None,
|
|
'size': 1}
|
|
|
|
serverName = 'fakehost'
|
|
server_id = 0
|
|
server_uri = '/lhos/servers/0'
|
|
|
|
snapshot_name = "fakeshapshot"
|
|
snapshot_id = 3
|
|
snapshot = {
|
|
'name': snapshot_name,
|
|
'volume_name': volume_name}
|
|
|
|
cloned_volume_name = "clone_volume"
|
|
cloned_volume = {'name': cloned_volume_name}
|
|
|
|
cloned_snapshot_name = "clonedshapshot"
|
|
cloned_snapshot_id = 5
|
|
cloned_snapshot = {
|
|
'name': cloned_snapshot_name,
|
|
'volume_name': volume_name}
|
|
|
|
volume_type_id = 4
|
|
init_iqn = 'iqn.1993-08.org.debian:01:222'
|
|
|
|
volume_type = {'name': 'gold',
|
|
'deleted': False,
|
|
'updated_at': None,
|
|
'extra_specs': {'hplh:provisioning': 'thin',
|
|
'hplh:ao': 'true',
|
|
'hplh:data_pl': 'r-0'},
|
|
'deleted_at': None,
|
|
'id': 'gold'}
|
|
|
|
connector = {
|
|
'ip': '10.0.0.2',
|
|
'initiator': 'iqn.1993-08.org.debian:01:222',
|
|
'host': serverName}
|
|
|
|
driver_startup_call_stack = [
|
|
mock.call.login('foo1', 'bar2'),
|
|
mock.call.getClusterByName('CloudCluster1'),
|
|
mock.call.getCluster(1),
|
|
]
|
|
|
|
|
|
class TestHPLeftHandCLIQISCSIDriver(HPLeftHandBaseDriver, test.TestCase):
|
|
|
|
def _fake_cliq_run(self, verb, cliq_args, check_exit_code=True):
|
|
"""Return fake results for the various methods."""
|
|
|
|
def create_volume(cliq_args):
|
|
"""Create volume CLIQ input for test.
|
|
|
|
input = "createVolume description="fake description"
|
|
clusterName=Cluster01 volumeName=fakevolume
|
|
thinProvision=0 output=XML size=1GB"
|
|
"""
|
|
output = """<gauche version="1.0">
|
|
<response description="Operation succeeded."
|
|
name="CliqSuccess" processingTime="181" result="0"/>
|
|
</gauche>"""
|
|
self.assertEqual(cliq_args['volumeName'], self.volume_name)
|
|
self.assertEqual(cliq_args['thinProvision'], '1')
|
|
self.assertEqual(cliq_args['size'], '1GB')
|
|
return output, None
|
|
|
|
def delete_volume(cliq_args):
|
|
"""Delete volume CLIQ input for test.
|
|
|
|
input = "deleteVolume volumeName=fakevolume prompt=false
|
|
output=XML"
|
|
"""
|
|
output = """<gauche version="1.0">
|
|
<response description="Operation succeeded."
|
|
name="CliqSuccess" processingTime="164" result="0"/>
|
|
</gauche>"""
|
|
self.assertEqual(cliq_args['volumeName'], self.volume_name)
|
|
self.assertEqual(cliq_args['prompt'], 'false')
|
|
return output, None
|
|
|
|
def extend_volume(cliq_args):
|
|
"""Extend volume CLIQ input for test.
|
|
|
|
input = "modifyVolume description="fake description"
|
|
volumeName=fakevolume
|
|
output=XML size=2GB"
|
|
"""
|
|
output = """<gauche version="1.0">
|
|
<response description="Operation succeeded."
|
|
name="CliqSuccess" processingTime="181" result="0"/>
|
|
</gauche>"""
|
|
self.assertEqual(cliq_args['volumeName'], self.volume_name)
|
|
self.assertEqual(cliq_args['size'], '2GB')
|
|
return output, None
|
|
|
|
def assign_volume(cliq_args):
|
|
"""Assign volume CLIQ input for test.
|
|
|
|
input = "assignVolumeToServer volumeName=fakevolume
|
|
serverName=fakehost
|
|
output=XML"
|
|
"""
|
|
output = """<gauche version="1.0">
|
|
<response description="Operation succeeded."
|
|
name="CliqSuccess" processingTime="174" result="0"/>
|
|
</gauche>"""
|
|
self.assertEqual(cliq_args['volumeName'], self.volume_name)
|
|
self.assertEqual(cliq_args['serverName'],
|
|
self.connector['host'])
|
|
return output, None
|
|
|
|
def unassign_volume(cliq_args):
|
|
"""Unassign volume CLIQ input for test.
|
|
|
|
input = "unassignVolumeToServer volumeName=fakevolume
|
|
serverName=fakehost output=XML
|
|
"""
|
|
output = """<gauche version="1.0">
|
|
<response description="Operation succeeded."
|
|
name="CliqSuccess" processingTime="205" result="0"/>
|
|
</gauche>"""
|
|
self.assertEqual(cliq_args['volumeName'], self.volume_name)
|
|
self.assertEqual(cliq_args['serverName'],
|
|
self.connector['host'])
|
|
return output, None
|
|
|
|
def create_snapshot(cliq_args):
|
|
"""Create snapshot CLIQ input for test.
|
|
|
|
input = "createSnapshot description="fake description"
|
|
snapshotName=fakesnapshot
|
|
volumeName=fakevolume
|
|
output=XML"
|
|
"""
|
|
output = """<gauche version="1.0">
|
|
<response description="Operation succeeded."
|
|
name="CliqSuccess" processingTime="181" result="0"/>
|
|
</gauche>"""
|
|
self.assertEqual(cliq_args['snapshotName'], self.snapshot_name)
|
|
self.assertEqual(cliq_args['volumeName'], self.volume_name)
|
|
return output, None
|
|
|
|
def delete_snapshot(cliq_args):
|
|
"""Delete shapshot CLIQ input for test.
|
|
|
|
input = "deleteSnapshot snapshotName=fakesnapshot prompt=false
|
|
output=XML"
|
|
"""
|
|
output = """<gauche version="1.0">
|
|
<response description="Operation succeeded."
|
|
name="CliqSuccess" processingTime="164" result="0"/>
|
|
</gauche>"""
|
|
self.assertEqual(cliq_args['snapshotName'], self.snapshot_name)
|
|
self.assertEqual(cliq_args['prompt'], 'false')
|
|
return output, None
|
|
|
|
def create_volume_from_snapshot(cliq_args):
|
|
"""Create volume from snapshot CLIQ input for test.
|
|
|
|
input = "cloneSnapshot description="fake description"
|
|
snapshotName=fakesnapshot
|
|
volumeName=fakevolume
|
|
output=XML"
|
|
"""
|
|
output = """<gauche version="1.0">
|
|
<response description="Operation succeeded."
|
|
name="CliqSuccess" processingTime="181" result="0"/>
|
|
</gauche>"""
|
|
self.assertEqual(cliq_args['snapshotName'], self.snapshot_name)
|
|
self.assertEqual(cliq_args['volumeName'], self.volume_name)
|
|
return output, None
|
|
|
|
def get_cluster_info(cliq_args):
|
|
"""Get cluster info CLIQ input for test.
|
|
|
|
input = "getClusterInfo clusterName=Cluster01 searchDepth=1
|
|
verbose=0 output=XML"
|
|
"""
|
|
output = """<gauche version="1.0">
|
|
<response description="Operation succeeded." name="CliqSuccess"
|
|
processingTime="1164" result="0">
|
|
<cluster blockSize="1024" description=""
|
|
maxVolumeSizeReplication1="622957690"
|
|
maxVolumeSizeReplication2="311480287"
|
|
minVolumeSize="262144" name="Cluster01"
|
|
pageSize="262144" spaceTotal="633697992"
|
|
storageNodeCount="2" unprovisionedSpace="622960574"
|
|
useVip="true">
|
|
<nsm ipAddress="10.0.1.7" name="111-vsa"/>
|
|
<nsm ipAddress="10.0.1.8" name="112-vsa"/>
|
|
<vip ipAddress="10.0.1.6" subnetMask="255.255.255.0"/>
|
|
</cluster></response></gauche>"""
|
|
return output, None
|
|
|
|
def get_volume_info(cliq_args):
|
|
"""Get volume info CLIQ input for test.
|
|
|
|
input = "getVolumeInfo volumeName=fakevolume output=XML"
|
|
"""
|
|
output = """<gauche version="1.0">
|
|
<response description="Operation succeeded." name="CliqSuccess"
|
|
processingTime="87" result="0">
|
|
<volume autogrowPages="4" availability="online"
|
|
blockSize="1024" bytesWritten="0" checkSum="false"
|
|
clusterName="Cluster01" created="2011-02-08T19:56:53Z"
|
|
deleting="false" description="" groupName="Group01"
|
|
initialQuota="536870912" isPrimary="true"
|
|
iscsiIqn="iqn.2003-10.com.lefthandnetworks:group01:25366:fakev"
|
|
maxSize="6865387257856" md5="9fa5c8b2cca54b2948a63d833097e1ca"
|
|
minReplication="1" name="vol-b" parity="0" replication="2"
|
|
reserveQuota="536870912" scratchQuota="4194304"
|
|
serialNumber="9fa5c8b2cca54b2948a63d8"
|
|
size="1073741824" stridePages="32" thinProvision="true">
|
|
<status description="OK" value="2"/>
|
|
<permission access="rw" authGroup="api-1"
|
|
chapName="chapusername" chapRequired="true"
|
|
id="25369" initiatorSecret="" iqn=""
|
|
iscsiEnabled="true" loadBalance="true"
|
|
targetSecret="supersecret"/>
|
|
</volume></response></gauche>"""
|
|
return output, None
|
|
|
|
def get_snapshot_info(cliq_args):
|
|
"""Get snapshot info CLIQ input for test.
|
|
|
|
input = "getSnapshotInfo snapshotName=fakesnapshot output=XML"
|
|
"""
|
|
output = """<gauche version="1.0">
|
|
<response description="Operation succeeded." name="CliqSuccess"
|
|
processingTime="87" result="0">
|
|
<snapshot applicationManaged="false" autogrowPages="32768"
|
|
automatic="false" availability="online" bytesWritten="0"
|
|
clusterName="CloudCluster1" created="2013-08-26T07:03:44Z"
|
|
deleting="false" description="" groupName="CloudGroup1"
|
|
id="730" initialQuota="536870912" isPrimary="true"
|
|
iscsiIqn="iqn.2003-10.com.lefthandnetworks:cloudgroup1:73"
|
|
md5="a64b4f850539c07fb5ce3cee5db1fcce" minReplication="1"
|
|
name="snapshot-7849288e-e5e8-42cb-9687-9af5355d674b"
|
|
replication="2" reserveQuota="536870912" scheduleId="0"
|
|
scratchQuota="4194304" scratchWritten="0"
|
|
serialNumber="a64b4f850539c07fb5ce3cee5db1fcce"
|
|
size="2147483648" stridePages="32"
|
|
volumeSerial="a64b4f850539c07fb5ce3cee5db1fcce">
|
|
<status description="OK" value="2"/>
|
|
<permission access="rw"
|
|
authGroup="api-34281B815713B78-(trimmed)51ADD4B7030853AA7"
|
|
chapName="chapusername" chapRequired="true" id="25369"
|
|
initiatorSecret="" iqn="" iscsiEnabled="true"
|
|
loadBalance="true" targetSecret="supersecret"/>
|
|
</snapshot></response></gauche>"""
|
|
return output, None
|
|
|
|
def get_server_info(cliq_args):
|
|
"""Get server info CLIQ input for test.
|
|
|
|
input = "getServerInfo serverName=fakeName"
|
|
"""
|
|
output = """<gauche version="1.0"><response result="0"/>
|
|
</gauche>"""
|
|
return output, None
|
|
|
|
def create_server(cliq_args):
|
|
"""Create server CLIQ input for test.
|
|
|
|
input = "createServer serverName=fakeName initiator=something"
|
|
"""
|
|
output = """<gauche version="1.0"><response result="0"/>
|
|
</gauche>"""
|
|
return output, None
|
|
|
|
def test_error(cliq_args):
|
|
output = """<gauche version="1.0">
|
|
<response description="Volume '134234' not found."
|
|
name="CliqVolumeNotFound" processingTime="1083"
|
|
result="8000100c"/>
|
|
</gauche>"""
|
|
return output, None
|
|
|
|
def test_paramiko_1_13_0(cliq_args):
|
|
|
|
# paramiko 1.13.0 now returns unicode
|
|
output = six.text_type(
|
|
'<?xml version="1.0" encoding="UTF-8" standalone="no" ?>\n'
|
|
'<gauche version="1.0">\n\n <response description="Operation'
|
|
' succeeded." name="CliqSuccess" processingTime="423" '
|
|
'result="0">\n <cluster adaptiveOptimization="false" '
|
|
'blockSize="1024" description="" maxVolumeSizeReplication1='
|
|
'"114594676736" minVolumeSize="262144" name="clusterdemo" '
|
|
'pageSize="262144" spaceTotal="118889644032" storageNodeCount='
|
|
'"1" unprovisionedSpace="114594676736" useVip="true">\n'
|
|
' <nsm ipAddress="10.10.29.102" name="lefdemo1"/>\n'
|
|
' <vip ipAddress="10.10.22.87" subnetMask='
|
|
'"255.255.224.0"/>\n </cluster>\n </response>\n\n'
|
|
'</gauche>\n ')
|
|
return output, None
|
|
|
|
def test_paramiko_1_10_0(cliq_args):
|
|
|
|
# paramiko 1.10.0 returns python default encoding.
|
|
output = (
|
|
'<?xml version="1.0" encoding="UTF-8" standalone="no" ?>\n'
|
|
'<gauche version="1.0">\n\n <response description="Operation'
|
|
' succeeded." name="CliqSuccess" processingTime="423" '
|
|
'result="0">\n <cluster adaptiveOptimization="false" '
|
|
'blockSize="1024" description="" maxVolumeSizeReplication1='
|
|
'"114594676736" minVolumeSize="262144" name="clusterdemo" '
|
|
'pageSize="262144" spaceTotal="118889644032" storageNodeCount='
|
|
'"1" unprovisionedSpace="114594676736" useVip="true">\n'
|
|
' <nsm ipAddress="10.10.29.102" name="lefdemo1"/>\n'
|
|
' <vip ipAddress="10.10.22.87" subnetMask='
|
|
'"255.255.224.0"/>\n </cluster>\n </response>\n\n'
|
|
'</gauche>\n ')
|
|
return output, None
|
|
|
|
self.assertEqual(cliq_args['output'], 'XML')
|
|
try:
|
|
verbs = {'createVolume': create_volume,
|
|
'deleteVolume': delete_volume,
|
|
'modifyVolume': extend_volume,
|
|
'assignVolumeToServer': assign_volume,
|
|
'unassignVolumeToServer': unassign_volume,
|
|
'createSnapshot': create_snapshot,
|
|
'deleteSnapshot': delete_snapshot,
|
|
'cloneSnapshot': create_volume_from_snapshot,
|
|
'getClusterInfo': get_cluster_info,
|
|
'getVolumeInfo': get_volume_info,
|
|
'getSnapshotInfo': get_snapshot_info,
|
|
'getServerInfo': get_server_info,
|
|
'createServer': create_server,
|
|
'testError': test_error,
|
|
'testParamiko_1.10.1': test_paramiko_1_10_0,
|
|
'testParamiko_1.13.1': test_paramiko_1_13_0}
|
|
except KeyError:
|
|
raise NotImplementedError()
|
|
|
|
return verbs[verb](cliq_args)
|
|
|
|
def setUp(self):
|
|
super(TestHPLeftHandCLIQISCSIDriver, self).setUp()
|
|
|
|
self.properties = {
|
|
'target_discoverd': True,
|
|
'target_portal': '10.0.1.6:3260',
|
|
'target_iqn':
|
|
'iqn.2003-10.com.lefthandnetworks:group01:25366:fakev',
|
|
'volume_id': self.volume_id}
|
|
|
|
def default_mock_conf(self):
|
|
|
|
mock_conf = mock.Mock()
|
|
mock_conf.san_ip = '10.10.10.10'
|
|
mock_conf.san_login = 'foo'
|
|
mock_conf.san_password = 'bar'
|
|
mock_conf.san_ssh_port = 16022
|
|
mock_conf.san_clustername = 'CloudCluster1'
|
|
mock_conf.hplefthand_api_url = None
|
|
return mock_conf
|
|
|
|
def setup_driver(self, config=None):
|
|
|
|
if config is None:
|
|
config = self.default_mock_conf()
|
|
|
|
self.driver = hp_lefthand_iscsi.HPLeftHandISCSIDriver(
|
|
configuration=config)
|
|
self.driver.do_setup(None)
|
|
|
|
self.driver.proxy._cliq_run = mock.Mock(
|
|
side_effect=self._fake_cliq_run)
|
|
return self.driver.proxy._cliq_run
|
|
|
|
def test_create_volume(self):
|
|
|
|
# set up driver with default config
|
|
mock_cliq_run = self.setup_driver()
|
|
|
|
volume = {'name': self.volume_name, 'size': 1}
|
|
model_update = self.driver.create_volume(volume)
|
|
expected_iqn = "iqn.2003-10.com.lefthandnetworks:group01:25366:fakev 0"
|
|
expected_location = "10.0.1.6:3260,1 %s" % expected_iqn
|
|
self.assertEqual(model_update['provider_location'], expected_location)
|
|
|
|
expected = [
|
|
mock.call(
|
|
'createVolume', {
|
|
'clusterName': 'CloudCluster1',
|
|
'volumeName': 'fakevolume',
|
|
'thinProvision': '1',
|
|
'output': 'XML',
|
|
'size': '1GB'},
|
|
True),
|
|
mock.call(
|
|
'getVolumeInfo', {
|
|
'volumeName': 'fakevolume',
|
|
'output': 'XML'},
|
|
True),
|
|
mock.call(
|
|
'getClusterInfo', {
|
|
'clusterName': 'Cluster01',
|
|
'searchDepth': '1',
|
|
'verbose': '0',
|
|
'output': 'XML'},
|
|
True)]
|
|
|
|
# validate call chain
|
|
mock_cliq_run.assert_has_calls(expected)
|
|
|
|
def test_delete_volume(self):
|
|
|
|
# set up driver with default config
|
|
mock_cliq_run = self.setup_driver()
|
|
|
|
volume = {'name': self.volume_name}
|
|
self.driver.delete_volume(volume)
|
|
|
|
expected = [
|
|
mock.call(
|
|
'getVolumeInfo', {
|
|
'volumeName': 'fakevolume',
|
|
'output': 'XML'},
|
|
True),
|
|
mock.call(
|
|
'deleteVolume', {
|
|
'volumeName': 'fakevolume',
|
|
'prompt': 'false',
|
|
'output': 'XML'},
|
|
True)]
|
|
|
|
# validate call chain
|
|
mock_cliq_run.assert_has_calls(expected)
|
|
|
|
def test_extend_volume(self):
|
|
|
|
# set up driver with default config
|
|
mock_cliq_run = self.setup_driver()
|
|
|
|
volume = {'name': self.volume_name}
|
|
self.driver.extend_volume(volume, 2)
|
|
|
|
expected = [
|
|
mock.call(
|
|
'modifyVolume', {
|
|
'volumeName': 'fakevolume',
|
|
'output': 'XML',
|
|
'size': '2GB'},
|
|
True)]
|
|
|
|
# validate call chain
|
|
mock_cliq_run.assert_has_calls(expected)
|
|
|
|
def test_initialize_connection(self):
|
|
|
|
# set up driver with default config
|
|
mock_cliq_run = self.setup_driver()
|
|
|
|
self.driver.proxy._get_iscsi_properties = mock.Mock(
|
|
return_value=self.properties)
|
|
volume = {'name': self.volume_name}
|
|
result = self.driver.initialize_connection(volume,
|
|
self.connector)
|
|
self.assertEqual(result['driver_volume_type'], 'iscsi')
|
|
self.assertDictMatch(result['data'], self.properties)
|
|
|
|
expected = [
|
|
mock.call(
|
|
'getServerInfo', {
|
|
'output': 'XML',
|
|
'serverName': 'fakehost'},
|
|
False),
|
|
mock.call(
|
|
'assignVolumeToServer', {
|
|
'volumeName': 'fakevolume',
|
|
'serverName': 'fakehost',
|
|
'output': 'XML'},
|
|
True)]
|
|
|
|
# validate call chain
|
|
mock_cliq_run.assert_has_calls(expected)
|
|
|
|
def test_terminate_connection(self):
|
|
|
|
# set up driver with default config
|
|
mock_cliq_run = self.setup_driver()
|
|
|
|
volume = {'name': self.volume_name}
|
|
self.driver.terminate_connection(volume, self.connector)
|
|
|
|
expected = [
|
|
mock.call(
|
|
'unassignVolumeToServer', {
|
|
'volumeName': 'fakevolume',
|
|
'serverName': 'fakehost',
|
|
'output': 'XML'},
|
|
True)]
|
|
|
|
# validate call chain
|
|
mock_cliq_run.assert_has_calls(expected)
|
|
|
|
def test_create_snapshot(self):
|
|
|
|
# set up driver with default config
|
|
mock_cliq_run = self.setup_driver()
|
|
|
|
snapshot = {'name': self.snapshot_name,
|
|
'volume_name': self.volume_name}
|
|
self.driver.create_snapshot(snapshot)
|
|
|
|
expected = [
|
|
mock.call(
|
|
'createSnapshot', {
|
|
'snapshotName': 'fakeshapshot',
|
|
'output': 'XML',
|
|
'inheritAccess': 1,
|
|
'volumeName': 'fakevolume'},
|
|
True)]
|
|
|
|
# validate call chain
|
|
mock_cliq_run.assert_has_calls(expected)
|
|
|
|
def test_delete_snapshot(self):
|
|
|
|
# set up driver with default config
|
|
mock_cliq_run = self.setup_driver()
|
|
|
|
snapshot = {'name': self.snapshot_name}
|
|
self.driver.delete_snapshot(snapshot)
|
|
|
|
expected = [
|
|
mock.call(
|
|
'getSnapshotInfo', {
|
|
'snapshotName': 'fakeshapshot',
|
|
'output': 'XML'},
|
|
True),
|
|
mock.call(
|
|
'deleteSnapshot', {
|
|
'snapshotName': 'fakeshapshot',
|
|
'prompt': 'false',
|
|
'output': 'XML'},
|
|
True)]
|
|
|
|
# validate call chain
|
|
mock_cliq_run.assert_has_calls(expected)
|
|
|
|
def test_create_volume_from_snapshot(self):
|
|
|
|
# set up driver with default config
|
|
mock_cliq_run = self.setup_driver()
|
|
|
|
volume = {'name': self.volume_name}
|
|
snapshot = {'name': self.snapshot_name}
|
|
model_update = self.driver.create_volume_from_snapshot(volume,
|
|
snapshot)
|
|
expected_iqn = "iqn.2003-10.com.lefthandnetworks:group01:25366:fakev 0"
|
|
expected_location = "10.0.1.6:3260,1 %s" % expected_iqn
|
|
self.assertEqual(model_update['provider_location'], expected_location)
|
|
|
|
expected = [
|
|
mock.call(
|
|
'cloneSnapshot', {
|
|
'snapshotName': 'fakeshapshot',
|
|
'output': 'XML',
|
|
'volumeName': 'fakevolume'},
|
|
True),
|
|
mock.call(
|
|
'getVolumeInfo', {
|
|
'volumeName': 'fakevolume',
|
|
'output': 'XML'},
|
|
True),
|
|
mock.call(
|
|
'getClusterInfo', {
|
|
'clusterName': 'Cluster01',
|
|
'searchDepth': '1',
|
|
'verbose': '0',
|
|
'output': 'XML'},
|
|
True)]
|
|
|
|
# validate call chain
|
|
mock_cliq_run.assert_has_calls(expected)
|
|
|
|
def test_get_volume_stats(self):
|
|
|
|
# set up driver with default config
|
|
mock_cliq_run = self.setup_driver()
|
|
volume_stats = self.driver.get_volume_stats(True)
|
|
|
|
self.assertEqual(volume_stats['vendor_name'], 'Hewlett-Packard')
|
|
self.assertEqual(volume_stats['storage_protocol'], 'iSCSI')
|
|
|
|
expected = [
|
|
mock.call('getClusterInfo', {
|
|
'searchDepth': 1,
|
|
'clusterName': 'CloudCluster1',
|
|
'output': 'XML'}, True)]
|
|
|
|
# validate call chain
|
|
mock_cliq_run.assert_has_calls(expected)
|
|
|
|
def test_cliq_run_xml_paramiko_1_13_0(self):
|
|
|
|
# set up driver with default config
|
|
self.setup_driver()
|
|
xml = self.driver.proxy._cliq_run_xml('testParamiko_1.13.1', {})
|
|
self.assertIsNotNone(xml)
|
|
|
|
def test_cliq_run_xml_paramiko_1_10_0(self):
|
|
|
|
# set up driver with default config
|
|
self.setup_driver()
|
|
xml = self.driver.proxy._cliq_run_xml('testParamiko_1.10.1', {})
|
|
self.assertIsNotNone(xml)
|
|
|
|
|
|
class TestHPLeftHandRESTISCSIDriver(HPLeftHandBaseDriver, test.TestCase):
|
|
|
|
driver_startup_call_stack = [
|
|
mock.call.login('foo1', 'bar2'),
|
|
mock.call.getClusterByName('CloudCluster1'),
|
|
mock.call.getCluster(1),
|
|
mock.call.getVolumes(
|
|
cluster='CloudCluster1',
|
|
fields=['members[id]', 'members[clusterName]']),
|
|
]
|
|
|
|
def default_mock_conf(self):
|
|
|
|
mock_conf = mock.Mock()
|
|
mock_conf.hplefthand_api_url = 'http://fake.foo:8080/lhos'
|
|
mock_conf.hplefthand_username = 'foo1'
|
|
mock_conf.hplefthand_password = 'bar2'
|
|
mock_conf.hplefthand_iscsi_chap_enabled = False
|
|
mock_conf.hplefthand_debug = False
|
|
mock_conf.hplefthand_clustername = "CloudCluster1"
|
|
mock_conf.goodness_function = GOODNESS_FUNCTION
|
|
mock_conf.filter_function = FILTER_FUNCTION
|
|
return mock_conf
|
|
|
|
@mock.patch('hplefthandclient.client.HPLeftHandClient', spec=True)
|
|
def setup_driver(self, _mock_client, config=None):
|
|
|
|
if config is None:
|
|
config = self.default_mock_conf()
|
|
|
|
_mock_client.return_value.getClusterByName.return_value = {
|
|
'id': 1, 'virtualIPAddresses': [{'ipV4Address': '10.0.1.6'}]}
|
|
_mock_client.return_value.getCluster.return_value = {
|
|
'spaceTotal': units.Gi * 500,
|
|
'spaceAvailable': units.Gi * 250}
|
|
self.driver = hp_lefthand_iscsi.HPLeftHandISCSIDriver(
|
|
configuration=config)
|
|
self.driver.do_setup(None)
|
|
self.cluster_name = config.hplefthand_clustername
|
|
return _mock_client.return_value
|
|
|
|
@mock.patch('hplefthandclient.version', "1.0.0")
|
|
def test_unsupported_client_version(self):
|
|
|
|
self.assertRaises(exception.InvalidInput,
|
|
self.setup_driver)
|
|
|
|
@mock.patch('hplefthandclient.version', "3.0.0")
|
|
def test_supported_client_version(self):
|
|
|
|
self.setup_driver()
|
|
|
|
def test_create_volume(self):
|
|
|
|
# setup drive with default configuration
|
|
# and return the mock HTTP LeftHand client
|
|
mock_client = self.setup_driver()
|
|
|
|
mock_client.getVolumes.return_value = {'total': 1, 'members': []}
|
|
|
|
# mock return value of createVolume
|
|
mock_client.createVolume.return_value = {
|
|
'iscsiIqn': self.connector['initiator']}
|
|
|
|
with mock.patch.object(hp_lefthand_rest_proxy.HPLeftHandRESTProxy,
|
|
'_create_client') as mock_do_setup:
|
|
mock_do_setup.return_value = mock_client
|
|
|
|
# execute driver
|
|
volume_info = self.driver.create_volume(self.volume)
|
|
|
|
self.assertEqual('10.0.1.6:3260,1 iqn.1993-08.org.debian:01:222 0',
|
|
volume_info['provider_location'])
|
|
|
|
expected = self.driver_startup_call_stack + [
|
|
mock.call.createVolume(
|
|
'fakevolume',
|
|
1,
|
|
units.Gi,
|
|
{'isThinProvisioned': True,
|
|
'clusterName': 'CloudCluster1'}),
|
|
mock.call.logout()]
|
|
|
|
mock_client.assert_has_calls(expected)
|
|
|
|
# mock HTTPServerError
|
|
mock_client.createVolume.side_effect =\
|
|
hpexceptions.HTTPServerError()
|
|
# ensure the raised exception is a cinder exception
|
|
self.assertRaises(exception.VolumeBackendAPIException,
|
|
self.driver.create_volume, self.volume)
|
|
|
|
@mock.patch.object(
|
|
volume_types,
|
|
'get_volume_type',
|
|
return_value={'extra_specs': {'hplh:provisioning': 'full'}})
|
|
def test_create_volume_with_es(self, _mock_volume_type):
|
|
|
|
# setup drive with default configuration
|
|
# and return the mock HTTP LeftHand client
|
|
mock_client = self.setup_driver()
|
|
|
|
volume_with_vt = self.volume
|
|
volume_with_vt['volume_type_id'] = 1
|
|
|
|
# mock return value of createVolume
|
|
mock_client.createVolume.return_value = {
|
|
'iscsiIqn': self.connector['initiator']}
|
|
mock_client.getVolumes.return_value = {'total': 1, 'members': []}
|
|
|
|
with mock.patch.object(hp_lefthand_rest_proxy.HPLeftHandRESTProxy,
|
|
'_create_client') as mock_do_setup:
|
|
mock_do_setup.return_value = mock_client
|
|
|
|
# execute create_volume
|
|
volume_info = self.driver.create_volume(volume_with_vt)
|
|
|
|
self.assertEqual('10.0.1.6:3260,1 iqn.1993-08.org.debian:01:222 0',
|
|
volume_info['provider_location'])
|
|
|
|
expected = self.driver_startup_call_stack + [
|
|
mock.call.createVolume(
|
|
'fakevolume',
|
|
1,
|
|
units.Gi,
|
|
{'isThinProvisioned': False,
|
|
'clusterName': 'CloudCluster1'}),
|
|
mock.call.logout()]
|
|
|
|
mock_client.assert_has_calls(expected)
|
|
|
|
def test_delete_volume(self):
|
|
|
|
# setup drive with default configuration
|
|
# and return the mock HTTP LeftHand client
|
|
mock_client = self.setup_driver()
|
|
|
|
# mock return value of getVolumeByName
|
|
mock_client.getVolumeByName.return_value = {'id': self.volume_id}
|
|
mock_client.getVolumes.return_value = {'total': 1, 'members': []}
|
|
|
|
with mock.patch.object(hp_lefthand_rest_proxy.HPLeftHandRESTProxy,
|
|
'_create_client') as mock_do_setup:
|
|
mock_do_setup.return_value = mock_client
|
|
|
|
# execute delete_volume
|
|
self.driver.delete_volume(self.volume)
|
|
|
|
expected = self.driver_startup_call_stack + [
|
|
mock.call.getVolumeByName('fakevolume'),
|
|
mock.call.deleteVolume(self.volume_id),
|
|
mock.call.logout()]
|
|
|
|
mock_client.assert_has_calls(expected)
|
|
|
|
# mock HTTPNotFound (volume not found)
|
|
mock_client.getVolumeByName.side_effect =\
|
|
hpexceptions.HTTPNotFound()
|
|
# no exception should escape method
|
|
self.driver.delete_volume(self.volume)
|
|
|
|
# mock HTTPConflict
|
|
mock_client.deleteVolume.side_effect = hpexceptions.HTTPConflict()
|
|
# ensure the raised exception is a cinder exception
|
|
self.assertRaises(exception.VolumeBackendAPIException,
|
|
self.driver.delete_volume, self.volume_id)
|
|
|
|
def test_extend_volume(self):
|
|
|
|
# setup drive with default configuration
|
|
# and return the mock HTTP LeftHand client
|
|
mock_client = self.setup_driver()
|
|
|
|
# mock return value of getVolumeByName
|
|
mock_client.getVolumeByName.return_value = {'id': self.volume_id}
|
|
mock_client.getVolumes.return_value = {'total': 1, 'members': []}
|
|
|
|
with mock.patch.object(hp_lefthand_rest_proxy.HPLeftHandRESTProxy,
|
|
'_create_client') as mock_do_setup:
|
|
mock_do_setup.return_value = mock_client
|
|
|
|
# execute extend_volume
|
|
self.driver.extend_volume(self.volume, 2)
|
|
|
|
expected = self.driver_startup_call_stack + [
|
|
mock.call.getVolumeByName('fakevolume'),
|
|
mock.call.modifyVolume(1, {'size': 2 * units.Gi}),
|
|
mock.call.logout()]
|
|
|
|
# validate call chain
|
|
mock_client.assert_has_calls(expected)
|
|
|
|
# mock HTTPServerError (array failure)
|
|
mock_client.modifyVolume.side_effect =\
|
|
hpexceptions.HTTPServerError()
|
|
# ensure the raised exception is a cinder exception
|
|
self.assertRaises(exception.VolumeBackendAPIException,
|
|
self.driver.extend_volume, self.volume, 2)
|
|
|
|
def test_initialize_connection(self):
|
|
|
|
# setup drive with default configuration
|
|
# and return the mock HTTP LeftHand client
|
|
mock_client = self.setup_driver()
|
|
|
|
# mock return value of getVolumeByName
|
|
mock_client.getServerByName.side_effect = hpexceptions.HTTPNotFound()
|
|
mock_client.createServer.return_value = {'id': self.server_id}
|
|
mock_client.getVolumeByName.return_value = {
|
|
'id': self.volume_id,
|
|
'iscsiSessions': None
|
|
}
|
|
mock_client.getVolumes.return_value = {'total': 1, 'members': []}
|
|
|
|
with mock.patch.object(hp_lefthand_rest_proxy.HPLeftHandRESTProxy,
|
|
'_create_client') as mock_do_setup:
|
|
mock_do_setup.return_value = mock_client
|
|
|
|
# execute initialize_connection
|
|
result = self.driver.initialize_connection(
|
|
self.volume,
|
|
self.connector)
|
|
|
|
# validate
|
|
self.assertEqual('iscsi', result['driver_volume_type'])
|
|
self.assertFalse(result['data']['target_discovered'])
|
|
self.assertEqual(self.volume_id, result['data']['volume_id'])
|
|
self.assertTrue('auth_method' not in result['data'])
|
|
|
|
expected = self.driver_startup_call_stack + [
|
|
mock.call.getServerByName('fakehost'),
|
|
mock.call.createServer
|
|
(
|
|
'fakehost',
|
|
'iqn.1993-08.org.debian:01:222',
|
|
None
|
|
),
|
|
mock.call.getVolumeByName('fakevolume'),
|
|
mock.call.addServerAccess(1, 0),
|
|
mock.call.logout()]
|
|
|
|
# validate call chain
|
|
mock_client.assert_has_calls(expected)
|
|
|
|
# mock HTTPServerError (array failure)
|
|
mock_client.createServer.side_effect =\
|
|
hpexceptions.HTTPServerError()
|
|
# ensure the raised exception is a cinder exception
|
|
self.assertRaises(
|
|
exception.VolumeBackendAPIException,
|
|
self.driver.initialize_connection, self.volume, self.connector)
|
|
|
|
def test_initialize_connection_session_exists(self):
|
|
|
|
# setup drive with default configuration
|
|
# and return the mock HTTP LeftHand client
|
|
mock_client = self.setup_driver()
|
|
|
|
# mock return value of getVolumeByName
|
|
mock_client.getServerByName.side_effect = hpexceptions.HTTPNotFound()
|
|
mock_client.createServer.return_value = {'id': self.server_id}
|
|
mock_client.getVolumeByName.return_value = {
|
|
'id': self.volume_id,
|
|
'iscsiSessions': [{'server': {'uri': self.server_uri}}]
|
|
}
|
|
mock_client.getVolumes.return_value = {'total': 1, 'members': []}
|
|
|
|
with mock.patch.object(hp_lefthand_rest_proxy.HPLeftHandRESTProxy,
|
|
'_create_client') as mock_do_setup:
|
|
mock_do_setup.return_value = mock_client
|
|
|
|
# execute initialize_connection
|
|
result = self.driver.initialize_connection(
|
|
self.volume,
|
|
self.connector)
|
|
|
|
# validate
|
|
self.assertEqual('iscsi', result['driver_volume_type'])
|
|
self.assertFalse(result['data']['target_discovered'])
|
|
self.assertEqual(self.volume_id, result['data']['volume_id'])
|
|
self.assertTrue('auth_method' not in result['data'])
|
|
|
|
expected = self.driver_startup_call_stack + [
|
|
mock.call.getServerByName('fakehost'),
|
|
mock.call.createServer
|
|
(
|
|
'fakehost',
|
|
'iqn.1993-08.org.debian:01:222',
|
|
None
|
|
),
|
|
mock.call.getVolumeByName('fakevolume'),
|
|
mock.call.logout()]
|
|
|
|
# validate call chain
|
|
mock_client.assert_has_calls(expected)
|
|
|
|
def test_initialize_connection_with_chaps(self):
|
|
|
|
# setup drive with default configuration
|
|
# and return the mock HTTP LeftHand client
|
|
mock_client = self.setup_driver()
|
|
|
|
# mock return value of getVolumeByName
|
|
mock_client.getServerByName.side_effect = hpexceptions.HTTPNotFound()
|
|
mock_client.createServer.return_value = {
|
|
'id': self.server_id,
|
|
'chapAuthenticationRequired': True,
|
|
'chapTargetSecret': 'dont_tell'}
|
|
mock_client.getVolumeByName.return_value = {
|
|
'id': self.volume_id,
|
|
'iscsiSessions': None
|
|
}
|
|
mock_client.getVolumes.return_value = {'total': 1, 'members': []}
|
|
|
|
with mock.patch.object(hp_lefthand_rest_proxy.HPLeftHandRESTProxy,
|
|
'_create_client') as mock_do_setup:
|
|
mock_do_setup.return_value = mock_client
|
|
|
|
# execute initialize_connection
|
|
result = self.driver.initialize_connection(
|
|
self.volume,
|
|
self.connector)
|
|
|
|
# validate
|
|
self.assertEqual('iscsi', result['driver_volume_type'])
|
|
self.assertFalse(result['data']['target_discovered'])
|
|
self.assertEqual(self.volume_id, result['data']['volume_id'])
|
|
self.assertEqual('CHAP', result['data']['auth_method'])
|
|
|
|
expected = self.driver_startup_call_stack + [
|
|
mock.call.getServerByName('fakehost'),
|
|
mock.call.createServer
|
|
(
|
|
'fakehost',
|
|
'iqn.1993-08.org.debian:01:222',
|
|
None
|
|
),
|
|
mock.call.getVolumeByName('fakevolume'),
|
|
mock.call.addServerAccess(1, 0),
|
|
mock.call.logout()]
|
|
|
|
# validate call chain
|
|
mock_client.assert_has_calls(expected)
|
|
|
|
def test_terminate_connection(self):
|
|
|
|
# setup drive with default configuration
|
|
# and return the mock HTTP LeftHand client
|
|
mock_client = self.setup_driver()
|
|
|
|
mock_client.getVolumeByName.return_value = {'id': self.volume_id}
|
|
mock_client.getServerByName.return_value = {
|
|
'id': self.server_id,
|
|
'name': self.serverName}
|
|
mock_client.findServerVolumes.return_value = [{'id': self.volume_id}]
|
|
mock_client.getVolumes.return_value = {'total': 1, 'members': []}
|
|
|
|
with mock.patch.object(hp_lefthand_rest_proxy.HPLeftHandRESTProxy,
|
|
'_create_client') as mock_do_setup:
|
|
mock_do_setup.return_value = mock_client
|
|
|
|
# execute terminate_connection
|
|
self.driver.terminate_connection(self.volume, self.connector)
|
|
|
|
expected = self.driver_startup_call_stack + [
|
|
mock.call.getVolumeByName('fakevolume'),
|
|
mock.call.getServerByName('fakehost'),
|
|
mock.call.findServerVolumes('fakehost'),
|
|
mock.call.removeServerAccess(1, 0),
|
|
mock.call.deleteServer(0)]
|
|
|
|
# validate call chain
|
|
mock_client.assert_has_calls(expected)
|
|
|
|
mock_client.getVolumeByName.side_effect = (
|
|
hpexceptions.HTTPNotFound())
|
|
# ensure the raised exception is a cinder exception
|
|
self.assertRaises(
|
|
exception.VolumeBackendAPIException,
|
|
self.driver.terminate_connection,
|
|
self.volume,
|
|
self.connector)
|
|
|
|
def test_terminate_connection_multiple_volumes_on_server(self):
|
|
|
|
# setup drive with default configuration
|
|
# and return the mock HTTP LeftHand client
|
|
mock_client = self.setup_driver()
|
|
|
|
mock_client.getVolumeByName.return_value = {'id': self.volume_id}
|
|
mock_client.getServerByName.return_value = {
|
|
'id': self.server_id,
|
|
'name': self.serverName}
|
|
mock_client.findServerVolumes.return_value = [
|
|
{'id': self.volume_id},
|
|
{'id': 99999}]
|
|
mock_client.getVolumes.return_value = {'total': 1, 'members': []}
|
|
|
|
with mock.patch.object(hp_lefthand_rest_proxy.HPLeftHandRESTProxy,
|
|
'_create_client') as mock_do_setup:
|
|
mock_do_setup.return_value = mock_client
|
|
|
|
# execute terminate_connection
|
|
self.driver.terminate_connection(self.volume, self.connector)
|
|
|
|
expected = self.driver_startup_call_stack + [
|
|
mock.call.getVolumeByName('fakevolume'),
|
|
mock.call.getServerByName('fakehost'),
|
|
mock.call.findServerVolumes('fakehost'),
|
|
mock.call.removeServerAccess(1, 0)]
|
|
|
|
# validate call chain
|
|
mock_client.assert_has_calls(expected)
|
|
self.assertFalse(mock_client.deleteServer.called)
|
|
|
|
mock_client.getVolumeByName.side_effect = (
|
|
hpexceptions.HTTPNotFound())
|
|
# ensure the raised exception is a cinder exception
|
|
self.assertRaises(
|
|
exception.VolumeBackendAPIException,
|
|
self.driver.terminate_connection,
|
|
self.volume,
|
|
self.connector)
|
|
|
|
def test_create_snapshot(self):
|
|
|
|
# setup drive with default configuration
|
|
# and return the mock HTTP LeftHand client
|
|
mock_client = self.setup_driver()
|
|
|
|
mock_client.getVolumeByName.return_value = {'id': self.volume_id}
|
|
mock_client.getVolumes.return_value = {'total': 1, 'members': []}
|
|
|
|
with mock.patch.object(hp_lefthand_rest_proxy.HPLeftHandRESTProxy,
|
|
'_create_client') as mock_do_setup:
|
|
mock_do_setup.return_value = mock_client
|
|
|
|
# execute create_snapshot
|
|
self.driver.create_snapshot(self.snapshot)
|
|
mock_client.getVolumes.return_value = {'total': 1, 'members': []}
|
|
|
|
expected = self.driver_startup_call_stack + [
|
|
mock.call.getVolumeByName('fakevolume'),
|
|
mock.call.createSnapshot(
|
|
'fakeshapshot',
|
|
1,
|
|
{'inheritAccess': True}),
|
|
mock.call.logout()]
|
|
|
|
# validate call chain
|
|
mock_client.assert_has_calls(expected)
|
|
|
|
# mock HTTPServerError (array failure)
|
|
mock_client.getVolumeByName.side_effect =\
|
|
hpexceptions.HTTPNotFound()
|
|
# ensure the raised exception is a cinder exception
|
|
self.assertRaises(
|
|
exception.VolumeBackendAPIException,
|
|
self.driver.create_snapshot, self.snapshot)
|
|
|
|
def test_delete_snapshot(self):
|
|
|
|
# setup drive with default configuration
|
|
# and return the mock HTTP LeftHand client
|
|
mock_client = self.setup_driver()
|
|
|
|
mock_client.getSnapshotByName.return_value = {'id': self.snapshot_id}
|
|
mock_client.getVolumes.return_value = {'total': 1, 'members': []}
|
|
|
|
with mock.patch.object(hp_lefthand_rest_proxy.HPLeftHandRESTProxy,
|
|
'_create_client') as mock_do_setup:
|
|
mock_do_setup.return_value = mock_client
|
|
|
|
# execute delete_snapshot
|
|
self.driver.delete_snapshot(self.snapshot)
|
|
|
|
expected = self.driver_startup_call_stack + [
|
|
mock.call.getSnapshotByName('fakeshapshot'),
|
|
mock.call.deleteSnapshot(3),
|
|
mock.call.logout()]
|
|
|
|
# validate call chain
|
|
mock_client.assert_has_calls(expected)
|
|
|
|
mock_client.getSnapshotByName.side_effect =\
|
|
hpexceptions.HTTPNotFound()
|
|
# no exception is thrown, just error msg is logged
|
|
self.driver.delete_snapshot(self.snapshot)
|
|
|
|
# mock HTTPServerError (array failure)
|
|
ex = hpexceptions.HTTPServerError({'message': 'Some message.'})
|
|
mock_client.getSnapshotByName.side_effect = ex
|
|
# ensure the raised exception is a cinder exception
|
|
self.assertRaises(
|
|
exception.VolumeBackendAPIException,
|
|
self.driver.delete_snapshot,
|
|
self.snapshot)
|
|
|
|
# mock HTTPServerError because the snap is in use
|
|
ex = hpexceptions.HTTPServerError({
|
|
'message':
|
|
'Hey, dude cannot be deleted because it is a clone point'
|
|
' duh.'})
|
|
mock_client.getSnapshotByName.side_effect = ex
|
|
# ensure the raised exception is a cinder exception
|
|
self.assertRaises(
|
|
exception.SnapshotIsBusy,
|
|
self.driver.delete_snapshot,
|
|
self.snapshot)
|
|
|
|
def test_create_volume_from_snapshot(self):
|
|
|
|
# setup drive with default configuration
|
|
# and return the mock HTTP LeftHand client
|
|
mock_client = self.setup_driver()
|
|
|
|
mock_client.getSnapshotByName.return_value = {'id': self.snapshot_id}
|
|
mock_client.cloneSnapshot.return_value = {
|
|
'iscsiIqn': self.connector['initiator']}
|
|
mock_client.getVolumes.return_value = {'total': 1, 'members': []}
|
|
|
|
with mock.patch.object(hp_lefthand_rest_proxy.HPLeftHandRESTProxy,
|
|
'_create_client') as mock_do_setup:
|
|
mock_do_setup.return_value = mock_client
|
|
|
|
# execute create_volume_from_snapshot
|
|
model_update = self.driver.create_volume_from_snapshot(
|
|
self.volume, self.snapshot)
|
|
|
|
expected_iqn = 'iqn.1993-08.org.debian:01:222 0'
|
|
expected_location = "10.0.1.6:3260,1 %s" % expected_iqn
|
|
self.assertEqual(expected_location,
|
|
model_update['provider_location'])
|
|
|
|
expected = self.driver_startup_call_stack + [
|
|
mock.call.getSnapshotByName('fakeshapshot'),
|
|
mock.call.cloneSnapshot('fakevolume', 3),
|
|
mock.call.logout()]
|
|
|
|
# validate call chain
|
|
mock_client.assert_has_calls(expected)
|
|
|
|
def test_create_cloned_volume(self):
|
|
|
|
# setup drive with default configuration
|
|
# and return the mock HTTP LeftHand client
|
|
mock_client = self.setup_driver()
|
|
|
|
mock_client.getVolumeByName.return_value = {'id': self.volume_id}
|
|
mock_client.cloneVolume.return_value = {
|
|
'iscsiIqn': self.connector['initiator']}
|
|
mock_client.getVolumes.return_value = {'total': 1, 'members': []}
|
|
|
|
with mock.patch.object(hp_lefthand_rest_proxy.HPLeftHandRESTProxy,
|
|
'_create_client') as mock_do_setup:
|
|
mock_do_setup.return_value = mock_client
|
|
|
|
# execute create_cloned_volume
|
|
model_update = self.driver.create_cloned_volume(
|
|
self.cloned_volume, self.volume)
|
|
|
|
expected_iqn = 'iqn.1993-08.org.debian:01:222 0'
|
|
expected_location = "10.0.1.6:3260,1 %s" % expected_iqn
|
|
self.assertEqual(expected_location,
|
|
model_update['provider_location'])
|
|
|
|
expected = self.driver_startup_call_stack + [
|
|
mock.call.getVolumeByName('fakevolume'),
|
|
mock.call.cloneVolume('clone_volume', 1),
|
|
mock.call.logout()]
|
|
|
|
# validate call chain
|
|
mock_client.assert_has_calls(expected)
|
|
|
|
@mock.patch.object(volume_types, 'get_volume_type')
|
|
def test_extra_spec_mapping(self, _mock_get_volume_type):
|
|
|
|
# setup drive with default configuration
|
|
self.setup_driver()
|
|
|
|
# 2 extra specs we don't care about, and
|
|
# 1 that will get mapped
|
|
_mock_get_volume_type.return_value = {
|
|
'extra_specs': {
|
|
'foo:bar': 'fake',
|
|
'bar:foo': 1234,
|
|
'hplh:provisioning': 'full'}}
|
|
|
|
volume_with_vt = self.volume
|
|
volume_with_vt['volume_type_id'] = self.volume_type_id
|
|
|
|
# get the extra specs of interest from this volume's volume type
|
|
volume_extra_specs = self.driver.proxy._get_volume_extra_specs(
|
|
volume_with_vt)
|
|
extra_specs = self.driver.proxy._get_lh_extra_specs(
|
|
volume_extra_specs,
|
|
hp_lefthand_rest_proxy.extra_specs_key_map.keys())
|
|
|
|
# map the extra specs key/value pairs to key/value pairs
|
|
# used as optional configuration values by the LeftHand backend
|
|
optional = self.driver.proxy._map_extra_specs(extra_specs)
|
|
|
|
self.assertDictMatch({'isThinProvisioned': False}, optional)
|
|
|
|
@mock.patch.object(volume_types, 'get_volume_type')
|
|
def test_extra_spec_mapping_invalid_value(self, _mock_get_volume_type):
|
|
|
|
# setup drive with default configuration
|
|
self.setup_driver()
|
|
|
|
volume_with_vt = self.volume
|
|
volume_with_vt['volume_type_id'] = self.volume_type_id
|
|
|
|
_mock_get_volume_type.return_value = {
|
|
'extra_specs': {
|
|
# r-07 is an invalid value for hplh:ao
|
|
'hplh:data_pl': 'r-07',
|
|
'hplh:ao': 'true'}}
|
|
|
|
# get the extra specs of interest from this volume's volume type
|
|
volume_extra_specs = self.driver.proxy._get_volume_extra_specs(
|
|
volume_with_vt)
|
|
extra_specs = self.driver.proxy._get_lh_extra_specs(
|
|
volume_extra_specs,
|
|
hp_lefthand_rest_proxy.extra_specs_key_map.keys())
|
|
|
|
# map the extra specs key/value pairs to key/value pairs
|
|
# used as optional configuration values by the LeftHand backend
|
|
optional = self.driver.proxy._map_extra_specs(extra_specs)
|
|
|
|
# {'hplh:ao': 'true'} should map to
|
|
# {'isAdaptiveOptimizationEnabled': True}
|
|
# without hplh:data_pl since r-07 is an invalid value
|
|
self.assertDictMatch({'isAdaptiveOptimizationEnabled': True}, optional)
|
|
|
|
def test_retype_with_no_LH_extra_specs(self):
|
|
# setup drive with default configuration
|
|
# and return the mock HTTP LeftHand client
|
|
mock_client = self.setup_driver()
|
|
mock_client.getVolumes.return_value = {'total': 1, 'members': []}
|
|
|
|
ctxt = context.get_admin_context()
|
|
|
|
host = {'host': self.serverName}
|
|
key_specs_old = {'foo': False, 'bar': 2, 'error': True}
|
|
key_specs_new = {'foo': True, 'bar': 5, 'error': False}
|
|
old_type_ref = volume_types.create(ctxt, 'old', key_specs_old)
|
|
new_type_ref = volume_types.create(ctxt, 'new', key_specs_new)
|
|
|
|
diff, equal = volume_types.volume_types_diff(ctxt, old_type_ref['id'],
|
|
new_type_ref['id'])
|
|
|
|
volume = dict.copy(self.volume)
|
|
old_type = volume_types.get_volume_type(ctxt, old_type_ref['id'])
|
|
volume['volume_type'] = old_type
|
|
volume['host'] = host
|
|
new_type = volume_types.get_volume_type(ctxt, new_type_ref['id'])
|
|
|
|
with mock.patch.object(hp_lefthand_rest_proxy.HPLeftHandRESTProxy,
|
|
'_create_client') as mock_do_setup:
|
|
mock_do_setup.return_value = mock_client
|
|
|
|
self.driver.retype(ctxt, volume, new_type, diff, host)
|
|
|
|
expected = self.driver_startup_call_stack + [
|
|
mock.call.getVolumeByName('fakevolume'),
|
|
mock.call.logout()]
|
|
|
|
# validate call chain
|
|
mock_client.assert_has_calls(expected)
|
|
|
|
def test_retype_with_only_LH_extra_specs(self):
|
|
# setup drive with default configuration
|
|
# and return the mock HTTP LeftHand client
|
|
mock_client = self.setup_driver()
|
|
mock_client.getVolumeByName.return_value = {'id': self.volume_id}
|
|
mock_client.getVolumes.return_value = {'total': 1, 'members': []}
|
|
|
|
ctxt = context.get_admin_context()
|
|
|
|
host = {'host': self.serverName}
|
|
key_specs_old = {'hplh:provisioning': 'thin'}
|
|
key_specs_new = {'hplh:provisioning': 'full', 'hplh:ao': 'true'}
|
|
old_type_ref = volume_types.create(ctxt, 'old', key_specs_old)
|
|
new_type_ref = volume_types.create(ctxt, 'new', key_specs_new)
|
|
|
|
diff, equal = volume_types.volume_types_diff(ctxt, old_type_ref['id'],
|
|
new_type_ref['id'])
|
|
|
|
volume = dict.copy(self.volume)
|
|
old_type = volume_types.get_volume_type(ctxt, old_type_ref['id'])
|
|
volume['volume_type'] = old_type
|
|
volume['host'] = host
|
|
new_type = volume_types.get_volume_type(ctxt, new_type_ref['id'])
|
|
|
|
with mock.patch.object(hp_lefthand_rest_proxy.HPLeftHandRESTProxy,
|
|
'_create_client') as mock_do_setup:
|
|
mock_do_setup.return_value = mock_client
|
|
|
|
self.driver.retype(ctxt, volume, new_type, diff, host)
|
|
|
|
expected = self.driver_startup_call_stack + [
|
|
mock.call.getVolumeByName('fakevolume'),
|
|
mock.call.modifyVolume(
|
|
1, {
|
|
'isThinProvisioned': False,
|
|
'isAdaptiveOptimizationEnabled': True}),
|
|
mock.call.logout()]
|
|
|
|
# validate call chain
|
|
mock_client.assert_has_calls(expected)
|
|
|
|
def test_retype_with_both_extra_specs(self):
|
|
# setup drive with default configuration
|
|
# and return the mock HTTP LeftHand client
|
|
mock_client = self.setup_driver()
|
|
mock_client.getVolumeByName.return_value = {'id': self.volume_id}
|
|
mock_client.getVolumes.return_value = {'total': 1, 'members': []}
|
|
|
|
ctxt = context.get_admin_context()
|
|
|
|
host = {'host': self.serverName}
|
|
key_specs_old = {'hplh:provisioning': 'full', 'foo': 'bar'}
|
|
key_specs_new = {'hplh:provisioning': 'thin', 'foo': 'foobar'}
|
|
old_type_ref = volume_types.create(ctxt, 'old', key_specs_old)
|
|
new_type_ref = volume_types.create(ctxt, 'new', key_specs_new)
|
|
|
|
diff, equal = volume_types.volume_types_diff(ctxt, old_type_ref['id'],
|
|
new_type_ref['id'])
|
|
|
|
volume = dict.copy(self.volume)
|
|
old_type = volume_types.get_volume_type(ctxt, old_type_ref['id'])
|
|
volume['volume_type'] = old_type
|
|
volume['host'] = host
|
|
new_type = volume_types.get_volume_type(ctxt, new_type_ref['id'])
|
|
|
|
with mock.patch.object(hp_lefthand_rest_proxy.HPLeftHandRESTProxy,
|
|
'_create_client') as mock_do_setup:
|
|
mock_do_setup.return_value = mock_client
|
|
|
|
self.driver.retype(ctxt, volume, new_type, diff, host)
|
|
|
|
expected = self.driver_startup_call_stack + [
|
|
mock.call.getVolumeByName('fakevolume'),
|
|
mock.call.modifyVolume(1, {'isThinProvisioned': True}),
|
|
mock.call.logout()]
|
|
|
|
# validate call chain
|
|
mock_client.assert_has_calls(expected)
|
|
|
|
def test_retype_same_extra_specs(self):
|
|
# setup drive with default configuration
|
|
# and return the mock HTTP LeftHand client
|
|
mock_client = self.setup_driver()
|
|
mock_client.getVolumeByName.return_value = {'id': self.volume_id}
|
|
mock_client.getVolumes.return_value = {'total': 1, 'members': []}
|
|
|
|
ctxt = context.get_admin_context()
|
|
|
|
host = {'host': self.serverName}
|
|
key_specs_old = {'hplh:provisioning': 'full', 'hplh:ao': 'true'}
|
|
key_specs_new = {'hplh:provisioning': 'full', 'hplh:ao': 'false'}
|
|
old_type_ref = volume_types.create(ctxt, 'old', key_specs_old)
|
|
new_type_ref = volume_types.create(ctxt, 'new', key_specs_new)
|
|
|
|
diff, equal = volume_types.volume_types_diff(ctxt, old_type_ref['id'],
|
|
new_type_ref['id'])
|
|
|
|
volume = dict.copy(self.volume)
|
|
old_type = volume_types.get_volume_type(ctxt, old_type_ref['id'])
|
|
volume['volume_type'] = old_type
|
|
volume['host'] = host
|
|
new_type = volume_types.get_volume_type(ctxt, new_type_ref['id'])
|
|
|
|
with mock.patch.object(hp_lefthand_rest_proxy.HPLeftHandRESTProxy,
|
|
'_create_client') as mock_do_setup:
|
|
mock_do_setup.return_value = mock_client
|
|
|
|
self.driver.retype(ctxt, volume, new_type, diff, host)
|
|
|
|
expected = self.driver_startup_call_stack + [
|
|
mock.call.getVolumeByName('fakevolume'),
|
|
mock.call.modifyVolume(
|
|
1,
|
|
{'isAdaptiveOptimizationEnabled': False}),
|
|
mock.call.logout()]
|
|
|
|
# validate call chain
|
|
mock_client.assert_has_calls(expected)
|
|
|
|
def test_migrate_no_location(self):
|
|
# setup drive with default configuration
|
|
# and return the mock HTTP LeftHand client
|
|
mock_client = self.setup_driver()
|
|
|
|
host = {'host': self.serverName, 'capabilities': {}}
|
|
|
|
with mock.patch.object(hp_lefthand_rest_proxy.HPLeftHandRESTProxy,
|
|
'_create_client') as mock_do_setup:
|
|
mock_do_setup.return_value = mock_client
|
|
|
|
(migrated, update) = self.driver.migrate_volume(
|
|
None,
|
|
self.volume,
|
|
host)
|
|
self.assertFalse(migrated)
|
|
|
|
mock_client.assert_has_calls([])
|
|
self.assertEqual(0, len(mock_client.method_calls))
|
|
|
|
def test_migrate_incorrect_vip(self):
|
|
# setup drive with default configuration
|
|
# and return the mock HTTP LeftHand client
|
|
mock_client = self.setup_driver()
|
|
mock_client.getClusterByName.return_value = {
|
|
"virtualIPAddresses": [{
|
|
"ipV4Address": "10.10.10.10",
|
|
"ipV4NetMask": "255.255.240.0"}],
|
|
"id": self.cluster_id}
|
|
|
|
mock_client.getVolumeByName.return_value = {'id': self.volume_id}
|
|
mock_client.getVolumes.return_value = {'total': 1, 'members': []}
|
|
|
|
location = (self.driver.proxy.DRIVER_LOCATION % {
|
|
'cluster': 'New_CloudCluster',
|
|
'vip': '10.10.10.111'})
|
|
|
|
host = {
|
|
'host': self.serverName,
|
|
'capabilities': {'location_info': location}}
|
|
|
|
with mock.patch.object(hp_lefthand_rest_proxy.HPLeftHandRESTProxy,
|
|
'_create_client') as mock_do_setup:
|
|
mock_do_setup.return_value = mock_client
|
|
|
|
(migrated, update) = self.driver.migrate_volume(
|
|
None,
|
|
self.volume,
|
|
host)
|
|
self.assertFalse(migrated)
|
|
|
|
expected = self.driver_startup_call_stack + [
|
|
mock.call.getClusterByName('New_CloudCluster'),
|
|
mock.call.logout()]
|
|
|
|
mock_client.assert_has_calls(expected)
|
|
# and nothing else
|
|
self.assertEqual(
|
|
len(expected),
|
|
len(mock_client.method_calls))
|
|
|
|
def test_migrate_with_location(self):
|
|
# setup drive with default configuration
|
|
# and return the mock HTTP LeftHand client
|
|
mock_client = self.setup_driver()
|
|
mock_client.getClusterByName.return_value = {
|
|
"virtualIPAddresses": [{
|
|
"ipV4Address": "10.10.10.111",
|
|
"ipV4NetMask": "255.255.240.0"}],
|
|
"id": self.cluster_id}
|
|
|
|
mock_client.getVolumeByName.return_value = {'id': self.volume_id,
|
|
'iscsiSessions': None}
|
|
mock_client.getVolume.return_value = {'snapshots': {
|
|
'resource': None}}
|
|
mock_client.getVolumes.return_value = {'total': 1, 'members': []}
|
|
|
|
location = (self.driver.proxy.DRIVER_LOCATION % {
|
|
'cluster': 'New_CloudCluster',
|
|
'vip': '10.10.10.111'})
|
|
|
|
host = {
|
|
'host': self.serverName,
|
|
'capabilities': {'location_info': location}}
|
|
|
|
with mock.patch.object(hp_lefthand_rest_proxy.HPLeftHandRESTProxy,
|
|
'_create_client') as mock_do_setup:
|
|
mock_do_setup.return_value = mock_client
|
|
|
|
(migrated, update) = self.driver.migrate_volume(
|
|
None,
|
|
self.volume,
|
|
host)
|
|
self.assertTrue(migrated)
|
|
|
|
expected = self.driver_startup_call_stack + [
|
|
mock.call.getClusterByName('New_CloudCluster'),
|
|
mock.call.logout()] + self.driver_startup_call_stack + [
|
|
mock.call.getVolumeByName('fakevolume'),
|
|
mock.call.getVolume(
|
|
1,
|
|
'fields=snapshots,snapshots[resource[members[name]]]'),
|
|
mock.call.modifyVolume(1, {'clusterName': 'New_CloudCluster'}),
|
|
mock.call.logout()]
|
|
|
|
mock_client.assert_has_calls(expected)
|
|
# and nothing else
|
|
self.assertEqual(
|
|
len(expected),
|
|
len(mock_client.method_calls))
|
|
|
|
def test_migrate_with_Snapshots(self):
|
|
# setup drive with default configuration
|
|
# and return the mock HTTP LeftHand client
|
|
mock_client = self.setup_driver()
|
|
mock_client.getClusterByName.return_value = {
|
|
"virtualIPAddresses": [{
|
|
"ipV4Address": "10.10.10.111",
|
|
"ipV4NetMask": "255.255.240.0"}],
|
|
"id": self.cluster_id}
|
|
|
|
mock_client.getVolumeByName.return_value = {
|
|
'id': self.volume_id,
|
|
'iscsiSessions': None}
|
|
mock_client.getVolume.return_value = {'snapshots': {
|
|
'resource': 'snapfoo'}}
|
|
mock_client.getVolumes.return_value = {'total': 1, 'members': []}
|
|
|
|
location = (self.driver.proxy.DRIVER_LOCATION % {
|
|
'cluster': 'New_CloudCluster',
|
|
'vip': '10.10.10.111'})
|
|
|
|
host = {
|
|
'host': self.serverName,
|
|
'capabilities': {'location_info': location}}
|
|
|
|
with mock.patch.object(hp_lefthand_rest_proxy.HPLeftHandRESTProxy,
|
|
'_create_client') as mock_do_setup:
|
|
mock_do_setup.return_value = mock_client
|
|
|
|
(migrated, update) = self.driver.migrate_volume(
|
|
None,
|
|
self.volume,
|
|
host)
|
|
self.assertFalse(migrated)
|
|
|
|
expected = self.driver_startup_call_stack + [
|
|
mock.call.getClusterByName('New_CloudCluster'),
|
|
mock.call.logout()] + self.driver_startup_call_stack + [
|
|
mock.call.getVolumeByName('fakevolume'),
|
|
mock.call.getVolume(
|
|
1,
|
|
'fields=snapshots,snapshots[resource[members[name]]]'),
|
|
mock.call.logout()]
|
|
|
|
mock_client.assert_has_calls(expected)
|
|
# and nothing else
|
|
self.assertEqual(
|
|
len(expected),
|
|
len(mock_client.method_calls))
|
|
|
|
@mock.patch.object(volume_types, 'get_volume_type',
|
|
return_value={'extra_specs': {'hplh:ao': 'true'}})
|
|
def test_create_volume_with_ao_true(self, _mock_volume_type):
|
|
|
|
# setup drive with default configuration
|
|
# and return the mock HTTP LeftHand client
|
|
mock_client = self.setup_driver()
|
|
|
|
volume_with_vt = self.volume
|
|
volume_with_vt['volume_type_id'] = 1
|
|
|
|
# mock return value of createVolume
|
|
mock_client.createVolume.return_value = {
|
|
'iscsiIqn': self.connector['initiator']}
|
|
mock_client.getVolumes.return_value = {'total': 1, 'members': []}
|
|
|
|
with mock.patch.object(hp_lefthand_rest_proxy.HPLeftHandRESTProxy,
|
|
'_create_client') as mock_do_setup:
|
|
mock_do_setup.return_value = mock_client
|
|
|
|
volume_info = self.driver.create_volume(volume_with_vt)
|
|
|
|
self.assertEqual('10.0.1.6:3260,1 iqn.1993-08.org.debian:01:222 0',
|
|
volume_info['provider_location'])
|
|
|
|
# make sure createVolume is called without
|
|
# isAdaptiveOptimizationEnabled == true
|
|
expected = self.driver_startup_call_stack + [
|
|
mock.call.createVolume(
|
|
'fakevolume',
|
|
1,
|
|
units.Gi,
|
|
{'isThinProvisioned': True,
|
|
'clusterName': 'CloudCluster1'}),
|
|
mock.call.logout()]
|
|
|
|
mock_client.assert_has_calls(expected)
|
|
|
|
@mock.patch.object(volume_types, 'get_volume_type',
|
|
return_value={'extra_specs': {'hplh:ao': 'false'}})
|
|
def test_create_volume_with_ao_false(self, _mock_volume_type):
|
|
|
|
# setup drive with default configuration
|
|
# and return the mock HTTP LeftHand client
|
|
mock_client = self.setup_driver()
|
|
|
|
volume_with_vt = self.volume
|
|
volume_with_vt['volume_type_id'] = 1
|
|
|
|
# mock return value of createVolume
|
|
mock_client.createVolume.return_value = {
|
|
'iscsiIqn': self.connector['initiator']}
|
|
mock_client.getVolumes.return_value = {'total': 1, 'members': []}
|
|
|
|
with mock.patch.object(hp_lefthand_rest_proxy.HPLeftHandRESTProxy,
|
|
'_create_client') as mock_do_setup:
|
|
mock_do_setup.return_value = mock_client
|
|
|
|
volume_info = self.driver.create_volume(volume_with_vt)
|
|
|
|
self.assertEqual('10.0.1.6:3260,1 iqn.1993-08.org.debian:01:222 0',
|
|
volume_info['provider_location'])
|
|
|
|
# make sure createVolume is called with
|
|
# isAdaptiveOptimizationEnabled == false
|
|
expected = self.driver_startup_call_stack + [
|
|
mock.call.createVolume(
|
|
'fakevolume',
|
|
1,
|
|
units.Gi,
|
|
{'isThinProvisioned': True,
|
|
'clusterName': 'CloudCluster1',
|
|
'isAdaptiveOptimizationEnabled': False}),
|
|
mock.call.logout()]
|
|
|
|
mock_client.assert_has_calls(expected)
|
|
|
|
def test__get_existing_volume_ref_name(self):
|
|
self.setup_driver()
|
|
|
|
existing_ref = {'source-name': self.volume_name}
|
|
result = self.driver.proxy._get_existing_volume_ref_name(
|
|
existing_ref)
|
|
self.assertEqual(self.volume_name, result)
|
|
|
|
existing_ref = {'bad-key': 'foo'}
|
|
self.assertRaises(
|
|
exception.ManageExistingInvalidReference,
|
|
self.driver.proxy._get_existing_volume_ref_name,
|
|
existing_ref)
|
|
|
|
def test_manage_existing(self):
|
|
mock_client = self.setup_driver()
|
|
|
|
self.driver.proxy.api_version = "1.1"
|
|
|
|
volume = {'display_name': 'Foo Volume',
|
|
'volume_type': None,
|
|
'volume_type_id': None,
|
|
'id': '12345'}
|
|
|
|
with mock.patch.object(hp_lefthand_rest_proxy.HPLeftHandRESTProxy,
|
|
'_create_client') as mock_do_setup:
|
|
mock_do_setup.return_value = mock_client
|
|
mock_client.getVolumeByName.return_value = {'id': self.volume_id}
|
|
mock_client.getVolumes.return_value = {
|
|
"type": "volume",
|
|
"total": 1,
|
|
"members": {
|
|
"id": self.volume_id,
|
|
"clusterName": self.cluster_name
|
|
}
|
|
}
|
|
|
|
existing_ref = {'source-name': self.volume_name}
|
|
|
|
expected_obj = {'display_name': 'Foo Volume'}
|
|
|
|
obj = self.driver.manage_existing(volume, existing_ref)
|
|
|
|
mock_client.assert_has_calls(
|
|
self.driver_startup_call_stack + [
|
|
mock.call.getVolumeByName(self.volume_name),
|
|
mock.call.logout()] +
|
|
self.driver_startup_call_stack + [
|
|
mock.call.modifyVolume(self.volume_id,
|
|
{'name': 'volume-12345'}),
|
|
mock.call.logout()])
|
|
self.assertEqual(expected_obj, obj)
|
|
|
|
@mock.patch.object(volume_types, 'get_volume_type')
|
|
def test_manage_existing_retype(self, _mock_volume_types):
|
|
mock_client = self.setup_driver()
|
|
|
|
_mock_volume_types.return_value = {
|
|
'name': 'gold',
|
|
'id': 'gold-id',
|
|
'extra_specs': {
|
|
'hplh:provisioning': 'thin',
|
|
'hplh:ao': 'true',
|
|
'hplh:data_pl': 'r-0',
|
|
'volume_type': self.volume_type}}
|
|
|
|
self.driver.proxy.api_version = "1.1"
|
|
|
|
volume = {'display_name': 'Foo Volume',
|
|
'host': 'stack@lefthand#lefthand',
|
|
'volume_type': 'gold',
|
|
'volume_type_id': 'bcfa9fa4-54a0-4340-a3d8-bfcf19aea65e',
|
|
'id': '12345'}
|
|
|
|
with mock.patch.object(hp_lefthand_rest_proxy.HPLeftHandRESTProxy,
|
|
'_create_client') as mock_do_setup:
|
|
mock_do_setup.return_value = mock_client
|
|
mock_client.getVolumeByName.return_value = {'id': self.volume_id}
|
|
mock_client.getVolumes.return_value = {
|
|
"type": "volume",
|
|
"total": 1,
|
|
"members": {
|
|
"id": self.volume_id,
|
|
"clusterName": self.cluster_name
|
|
}
|
|
}
|
|
|
|
existing_ref = {'source-name': self.volume_name}
|
|
|
|
expected_obj = {'display_name': 'Foo Volume'}
|
|
|
|
obj = self.driver.manage_existing(volume, existing_ref)
|
|
|
|
mock_client.assert_has_calls(
|
|
self.driver_startup_call_stack + [
|
|
mock.call.getVolumeByName(self.volume_name),
|
|
mock.call.logout()] +
|
|
self.driver_startup_call_stack + [
|
|
mock.call.modifyVolume(self.volume_id,
|
|
{'name': 'volume-12345'}),
|
|
mock.call.logout()])
|
|
self.assertEqual(expected_obj, obj)
|
|
|
|
@mock.patch.object(volume_types, 'get_volume_type')
|
|
def test_manage_existing_retype_exception(self, _mock_volume_types):
|
|
mock_client = self.setup_driver()
|
|
|
|
_mock_volume_types.return_value = {
|
|
'name': 'gold',
|
|
'id': 'gold-id',
|
|
'extra_specs': {
|
|
'hplh:provisioning': 'thin',
|
|
'hplh:ao': 'true',
|
|
'hplh:data_pl': 'r-0',
|
|
'volume_type': self.volume_type}}
|
|
|
|
self.driver.proxy.retype = mock.Mock(
|
|
side_effect=exception.VolumeNotFound(volume_id="fake"))
|
|
|
|
self.driver.proxy.api_version = "1.1"
|
|
|
|
volume = {'display_name': 'Foo Volume',
|
|
'host': 'stack@lefthand#lefthand',
|
|
'volume_type': 'gold',
|
|
'volume_type_id': 'bcfa9fa4-54a0-4340-a3d8-bfcf19aea65e',
|
|
'id': '12345'}
|
|
|
|
with mock.patch.object(hp_lefthand_rest_proxy.HPLeftHandRESTProxy,
|
|
'_create_client') as mock_do_setup:
|
|
mock_do_setup.return_value = mock_client
|
|
mock_client.getVolumeByName.return_value = {'id': self.volume_id}
|
|
mock_client.getVolumes.return_value = {
|
|
"type": "volume",
|
|
"total": 1,
|
|
"members": {
|
|
"id": self.volume_id,
|
|
"clusterName": self.cluster_name
|
|
}
|
|
}
|
|
|
|
existing_ref = {'source-name': self.volume_name}
|
|
|
|
self.assertRaises(exception.VolumeNotFound,
|
|
self.driver.manage_existing,
|
|
volume,
|
|
existing_ref)
|
|
|
|
mock_client.assert_has_calls(
|
|
self.driver_startup_call_stack + [
|
|
mock.call.getVolumeByName(self.volume_name),
|
|
mock.call.logout()] +
|
|
self.driver_startup_call_stack + [
|
|
mock.call.modifyVolume(self.volume_id,
|
|
{'name': 'volume-12345'}),
|
|
mock.call.logout()] +
|
|
self.driver_startup_call_stack + [
|
|
mock.call.modifyVolume(self.volume_id,
|
|
{'name': 'fakevolume'}),
|
|
mock.call.logout()])
|
|
|
|
def test_manage_existing_volume_type_exception(self):
|
|
mock_client = self.setup_driver()
|
|
|
|
self.driver.proxy.api_version = "1.1"
|
|
|
|
volume = {'display_name': 'Foo Volume',
|
|
'volume_type': 'gold',
|
|
'volume_type_id': 'bcfa9fa4-54a0-4340-a3d8-bfcf19aea65e',
|
|
'id': '12345'}
|
|
|
|
with mock.patch.object(hp_lefthand_rest_proxy.HPLeftHandRESTProxy,
|
|
'_create_client') as mock_do_setup:
|
|
mock_do_setup.return_value = mock_client
|
|
mock_client.getVolumeByName.return_value = {'id': self.volume_id}
|
|
mock_client.getVolumes.return_value = {
|
|
"type": "volume",
|
|
"total": 1,
|
|
"members": {
|
|
"id": self.volume_id,
|
|
"clusterName": self.cluster_name
|
|
}
|
|
}
|
|
|
|
existing_ref = {'source-name': self.volume_name}
|
|
|
|
self.assertRaises(exception.ManageExistingVolumeTypeMismatch,
|
|
self.driver.manage_existing,
|
|
volume=volume,
|
|
existing_ref=existing_ref)
|
|
|
|
mock_client.assert_has_calls(
|
|
self.driver_startup_call_stack + [
|
|
mock.call.getVolumeByName(self.volume_name),
|
|
mock.call.logout()])
|
|
|
|
def test_manage_existing_get_size(self):
|
|
mock_client = self.setup_driver()
|
|
mock_client.getVolumeByName.return_value = {'size': 2147483648}
|
|
|
|
self.driver.proxy.api_version = "1.1"
|
|
|
|
with mock.patch.object(hp_lefthand_rest_proxy.HPLeftHandRESTProxy,
|
|
'_create_client') as mock_do_setup:
|
|
mock_do_setup.return_value = mock_client
|
|
mock_client.getVolumes.return_value = {
|
|
"type": "volume",
|
|
"total": 1,
|
|
"members": {
|
|
"id": self.volume_id,
|
|
"clusterName": self.cluster_name
|
|
}
|
|
}
|
|
|
|
volume = {}
|
|
existing_ref = {'source-name': self.volume_name}
|
|
|
|
size = self.driver.manage_existing_get_size(volume, existing_ref)
|
|
|
|
expected_size = 2
|
|
expected = [mock.call.getVolumeByName(existing_ref['source-name']),
|
|
mock.call.logout()]
|
|
|
|
mock_client.assert_has_calls(
|
|
self.driver_startup_call_stack +
|
|
expected)
|
|
self.assertEqual(expected_size, size)
|
|
|
|
def test_manage_existing_get_size_invalid_reference(self):
|
|
mock_client = self.setup_driver()
|
|
mock_client.getVolumeByName.return_value = {'size': 2147483648}
|
|
|
|
self.driver.proxy.api_version = "1.1"
|
|
|
|
with mock.patch.object(hp_lefthand_rest_proxy.HPLeftHandRESTProxy,
|
|
'_create_client') as mock_do_setup:
|
|
mock_do_setup.return_value = mock_client
|
|
|
|
volume = {}
|
|
existing_ref = {'source-name': "volume-12345"}
|
|
|
|
self.assertRaises(exception.ManageExistingInvalidReference,
|
|
self.driver.manage_existing_get_size,
|
|
volume=volume,
|
|
existing_ref=existing_ref)
|
|
|
|
mock_client.assert_has_calls([])
|
|
|
|
existing_ref = {}
|
|
|
|
self.assertRaises(exception.ManageExistingInvalidReference,
|
|
self.driver.manage_existing_get_size,
|
|
volume=volume,
|
|
existing_ref=existing_ref)
|
|
|
|
mock_client.assert_has_calls([])
|
|
|
|
def test_manage_existing_get_size_invalid_input(self):
|
|
mock_client = self.setup_driver()
|
|
mock_client.getVolumeByName.side_effect = (
|
|
hpexceptions.HTTPNotFound('fake'))
|
|
|
|
self.driver.proxy.api_version = "1.1"
|
|
|
|
with mock.patch.object(hp_lefthand_rest_proxy.HPLeftHandRESTProxy,
|
|
'_create_client') as mock_do_setup:
|
|
mock_do_setup.return_value = mock_client
|
|
mock_client.getVolumes.return_value = {
|
|
"type": "volume",
|
|
"total": 1,
|
|
"members": {
|
|
"id": self.volume_id,
|
|
"clusterName": self.cluster_name
|
|
}
|
|
}
|
|
|
|
volume = {}
|
|
existing_ref = {'source-name': self.volume_name}
|
|
|
|
self.assertRaises(exception.InvalidInput,
|
|
self.driver.manage_existing_get_size,
|
|
volume=volume,
|
|
existing_ref=existing_ref)
|
|
|
|
expected = [mock.call.getVolumeByName(existing_ref['source-name'])]
|
|
|
|
mock_client.assert_has_calls(
|
|
self.driver_startup_call_stack +
|
|
expected)
|
|
|
|
def test_unmanage(self):
|
|
mock_client = self.setup_driver()
|
|
mock_client.getVolumeByName.return_value = {'id': self.volume_id}
|
|
|
|
# mock return value of getVolumes
|
|
mock_client.getVolumes.return_value = {
|
|
"type": "volume",
|
|
"total": 1,
|
|
"members": {
|
|
"id": self.volume_id,
|
|
"clusterName": self.cluster_name
|
|
}
|
|
}
|
|
|
|
self.driver.proxy.api_version = "1.1"
|
|
|
|
with mock.patch.object(hp_lefthand_rest_proxy.HPLeftHandRESTProxy,
|
|
'_create_client') as mock_do_setup:
|
|
mock_do_setup.return_value = mock_client
|
|
self.driver.unmanage(self.volume)
|
|
|
|
new_name = 'unm-' + str(self.volume['id'])
|
|
|
|
expected = [
|
|
mock.call.getVolumeByName(self.volume['name']),
|
|
mock.call.modifyVolume(self.volume['id'], {'name': new_name}),
|
|
mock.call.logout()
|
|
]
|
|
|
|
mock_client.assert_has_calls(
|
|
self.driver_startup_call_stack +
|
|
expected)
|
|
|
|
def test_api_version(self):
|
|
self.setup_driver()
|
|
self.driver.proxy.api_version = "1.1"
|
|
self.driver.proxy._check_api_version()
|
|
|
|
self.driver.proxy.api_version = "1.0"
|
|
self.assertRaises(exception.InvalidInput,
|
|
self.driver.proxy._check_api_version)
|
|
|
|
def test_get_volume_stats(self):
|
|
|
|
# set up driver with default config
|
|
mock_client = self.setup_driver()
|
|
|
|
# mock return value of getVolumes
|
|
mock_client.getVolumes.return_value = {
|
|
"type": "volume",
|
|
"total": 1,
|
|
"members": {
|
|
"id": 12345,
|
|
"clusterName": self.cluster_name
|
|
}
|
|
}
|
|
|
|
with mock.patch.object(hp_lefthand_rest_proxy.HPLeftHandRESTProxy,
|
|
'_create_client') as mock_do_setup:
|
|
mock_do_setup.return_value = mock_client
|
|
|
|
# execute driver
|
|
stats = self.driver.get_volume_stats(True)
|
|
|
|
self.assertEqual('iSCSI', stats['storage_protocol'])
|
|
self.assertEqual(GOODNESS_FUNCTION, stats['goodness_function'])
|
|
self.assertEqual(FILTER_FUNCTION, stats['filter_function'])
|
|
self.assertEqual(1, int(stats['total_volumes']))
|
|
|
|
cap_util = (
|
|
float(units.Gi * 500 - units.Gi * 250) / float(units.Gi * 500)
|
|
) * 100
|
|
|
|
self.assertEqual(cap_util, float(stats['capacity_utilization']))
|
|
|
|
expected = self.driver_startup_call_stack + [
|
|
mock.call.getCluster(1),
|
|
mock.call.getVolumes(fields=['members[id]',
|
|
'members[clusterName]'],
|
|
cluster=self.cluster_name),
|
|
mock.call.logout()]
|
|
|
|
mock_client.assert_has_calls(expected)
|