New HP LeftHand array iSCSI driver
This driver is intended to replace the current OpenStack Block Storage HP LeftHand (LH) StoreVirtual iSCSI Driver, (cinder.volume.drivers.san.HpSanISCSIDriver), by moving the existing SSH interface into the new driver to maintain backwards compatibility, and add the new LH REST interface for new driver features. We have the driver broken into 3 files: hp_lefthand_iscis.py (common interface) hp_lefthand_cliq_proxy.py (old SSH interface) hp_lefthand_rest_proxy.py (new REST interface) The reason we are doing this is because the SSH interface on LH array has connections and performance limitations. These problems will be resolved by moving to the new LH OS REST interface. Also, new LeftHand array capabilities will only be supported in the REST(hplefthandclient) interface. To support new driver capabilities (create cloned volume), the python REST client (hplefthandclient) is required and can be downloaded from the pypi repository: http://pypi.python.org/pypi/hplefthandclient. This REST client requires LeftHand firmware version 11.5 or greater. The SSH interface will be phased out over time. Driver cert test results; Related-Bug: 1276809 Closes-Bug: 1277339 DocImpact: Document new driver configuration. Implements blueprint lefthand-cinder-driver Change-Id: Id557cab69022c3f7851be14cd82bdab0e4157e55
This commit is contained in:
parent
87e906c168
commit
0e83faf4b8
|
@ -1,360 +0,0 @@
|
|||
# Copyright 2012 OpenStack Foundation
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import mox
|
||||
|
||||
from cinder import exception
|
||||
from cinder.openstack.common import log as logging
|
||||
from cinder import test
|
||||
from cinder.volume import configuration as conf
|
||||
from cinder.volume.drivers.san.hp_lefthand import HpSanISCSIDriver
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class HpSanISCSITestCase(test.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(HpSanISCSITestCase, self).setUp()
|
||||
self.stubs.Set(HpSanISCSIDriver, "_cliq_run",
|
||||
self._fake_cliq_run)
|
||||
self.stubs.Set(HpSanISCSIDriver, "_get_iscsi_properties",
|
||||
self._fake_get_iscsi_properties)
|
||||
configuration = mox.MockObject(conf.Configuration)
|
||||
configuration.san_is_local = False
|
||||
configuration.san_ip = "10.0.0.1"
|
||||
configuration.san_login = "foo"
|
||||
configuration.san_password = "bar"
|
||||
configuration.san_ssh_port = 16022
|
||||
configuration.san_clustername = "CloudCluster1"
|
||||
configuration.san_thin_provision = True
|
||||
configuration.append_config_values(mox.IgnoreArg())
|
||||
|
||||
self.driver = HpSanISCSIDriver(configuration=configuration)
|
||||
self.volume_name = "fakevolume"
|
||||
self.snapshot_name = "fakeshapshot"
|
||||
self.connector = {'ip': '10.0.0.2',
|
||||
'initiator': 'iqn.1993-08.org.debian:01:222',
|
||||
'host': 'fakehost'}
|
||||
self.properties = {
|
||||
'target_discoverd': True,
|
||||
'target_portal': '10.0.1.6:3260',
|
||||
'target_iqn':
|
||||
'iqn.2003-10.com.lefthandnetworks:group01:25366:fakev',
|
||||
'volume_id': 1}
|
||||
|
||||
def tearDown(self):
|
||||
super(HpSanISCSITestCase, self).tearDown()
|
||||
|
||||
def _fake_get_iscsi_properties(self, volume):
|
||||
return self.properties
|
||||
|
||||
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
|
||||
|
||||
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}
|
||||
except KeyError:
|
||||
raise NotImplementedError()
|
||||
|
||||
return verbs[verb](cliq_args)
|
||||
|
||||
def test_create_volume(self):
|
||||
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)
|
||||
|
||||
def test_delete_volume(self):
|
||||
volume = {'name': self.volume_name}
|
||||
self.driver.delete_volume(volume)
|
||||
|
||||
def test_extend_volume(self):
|
||||
volume = {'name': self.volume_name}
|
||||
self.driver.extend_volume(volume, 2)
|
||||
|
||||
def test_initialize_connection(self):
|
||||
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)
|
||||
|
||||
def test_terminate_connection(self):
|
||||
volume = {'name': self.volume_name}
|
||||
self.driver.terminate_connection(volume, self.connector)
|
||||
|
||||
def test_create_snapshot(self):
|
||||
snapshot = {'name': self.snapshot_name,
|
||||
'volume_name': self.volume_name}
|
||||
self.driver.create_snapshot(snapshot)
|
||||
|
||||
def test_delete_snapshot(self):
|
||||
snapshot = {'name': self.snapshot_name}
|
||||
self.driver.delete_snapshot(snapshot)
|
||||
|
||||
def test_create_volume_from_snapshot(self):
|
||||
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)
|
||||
|
||||
def test_cliq_error(self):
|
||||
try:
|
||||
self.driver._cliq_run_xml("testError", {})
|
||||
except exception.VolumeBackendAPIException:
|
||||
pass
|
|
@ -28,7 +28,6 @@ SHEEPDOG_MODULE = "cinder.volume.drivers.sheepdog.SheepdogDriver"
|
|||
NEXENTA_MODULE = "cinder.volume.drivers.nexenta.iscsi.NexentaISCSIDriver"
|
||||
SAN_MODULE = "cinder.volume.drivers.san.san.SanISCSIDriver"
|
||||
SOLARIS_MODULE = "cinder.volume.drivers.san.solaris.SolarisISCSIDriver"
|
||||
LEFTHAND_MODULE = "cinder.volume.drivers.san.hp_lefthand.HpSanISCSIDriver"
|
||||
NFS_MODULE = "cinder.volume.drivers.nfs.NfsDriver"
|
||||
SOLIDFIRE_MODULE = "cinder.volume.drivers.solidfire.SolidFireDriver"
|
||||
STORWIZE_MODULE = "cinder.volume.drivers.ibm.storwize_svc.StorwizeSVCDriver"
|
||||
|
@ -36,6 +35,8 @@ WINDOWS_MODULE = "cinder.volume.drivers.windows.windows.WindowsDriver"
|
|||
XIV_DS8K_MODULE = "cinder.volume.drivers.xiv_ds8k.XIVDS8KDriver"
|
||||
ZADARA_MODULE = "cinder.volume.drivers.zadara.ZadaraVPSAISCSIDriver"
|
||||
NETAPP_MODULE = "cinder.volume.drivers.netapp.common.Deprecated"
|
||||
LEFTHAND_REST_MODULE = ("cinder.volume.drivers.san.hp.hp_lefthand_iscsi."
|
||||
"HPLeftHandISCSIDriver")
|
||||
|
||||
|
||||
class VolumeDriverCompatibility(test.TestCase):
|
||||
|
@ -103,14 +104,6 @@ class VolumeDriverCompatibility(test.TestCase):
|
|||
self._load_driver(SOLARIS_MODULE)
|
||||
self.assertEqual(self._driver_module_name(), SOLARIS_MODULE)
|
||||
|
||||
def test_hp_lefthand_old(self):
|
||||
self._load_driver('cinder.volume.san.HpSanISCSIDriver')
|
||||
self.assertEqual(self._driver_module_name(), LEFTHAND_MODULE)
|
||||
|
||||
def test_hp_lefthand_new(self):
|
||||
self._load_driver(LEFTHAND_MODULE)
|
||||
self.assertEqual(self._driver_module_name(), LEFTHAND_MODULE)
|
||||
|
||||
def test_nfs_old(self):
|
||||
self._load_driver('cinder.volume.nfs.NfsDriver')
|
||||
self.assertEqual(self._driver_module_name(), NFS_MODULE)
|
||||
|
@ -198,3 +191,12 @@ class VolumeDriverCompatibility(test.TestCase):
|
|||
self._load_driver(
|
||||
'cinder.volume.drivers.netapp.nfs.NetAppCmodeNfsDriver')
|
||||
self.assertEqual(self._driver_module_name(), NETAPP_MODULE)
|
||||
|
||||
def test_hp_lefthand_rest_old(self):
|
||||
self._load_driver(
|
||||
'cinder.volume.drivers.san.hp_lefthand.HpSanISCSIDriver')
|
||||
self.assertEqual(self._driver_module_name(), LEFTHAND_REST_MODULE)
|
||||
|
||||
def test_hp_lefthand_rest_new(self):
|
||||
self._load_driver(LEFTHAND_REST_MODULE)
|
||||
self.assertEqual(self._driver_module_name(), LEFTHAND_REST_MODULE)
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -22,6 +22,5 @@
|
|||
"""
|
||||
|
||||
# Adding imports for backwards compatibility in loading volume_driver.
|
||||
from hp_lefthand import HpSanISCSIDriver # noqa
|
||||
from san import SanISCSIDriver # noqa
|
||||
from solaris import SolarisISCSIDriver # noqa
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
# Copyright 2012 OpenStack Foundation
|
||||
# (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
|
||||
|
@ -11,8 +12,9 @@
|
|||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
"""
|
||||
HP Lefthand SAN ISCSI Driver.
|
||||
HP LeftHand SAN ISCSI Driver.
|
||||
|
||||
The driver communicates to the backend aka Cliq via SSH to perform all the
|
||||
operations on the SAN.
|
||||
|
@ -22,14 +24,15 @@ from lxml import etree
|
|||
from cinder import exception
|
||||
from cinder.openstack.common import log as logging
|
||||
from cinder.openstack.common import processutils
|
||||
from cinder import units
|
||||
from cinder.volume.drivers.san.san import SanISCSIDriver
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class HpSanISCSIDriver(SanISCSIDriver):
|
||||
"""Executes commands relating to HP/Lefthand SAN ISCSI volumes.
|
||||
class HPLeftHandCLIQProxy(SanISCSIDriver):
|
||||
"""Executes commands relating to HP/LeftHand SAN ISCSI volumes.
|
||||
|
||||
We use the CLIQ interface, over SSH.
|
||||
|
||||
|
@ -53,8 +56,6 @@ class HpSanISCSIDriver(SanISCSIDriver):
|
|||
|
||||
:getClusterInfo: (to discover the iSCSI target IP address)
|
||||
|
||||
:assignVolumeChap: (exports it with CHAP security)
|
||||
|
||||
The 'trick' here is that the HP SAN enforces security by default, so
|
||||
normally a volume mount would need both to configure the SAN in the volume
|
||||
layer and do the mount on the compute layer. Multi-layer operations are
|
||||
|
@ -67,16 +68,26 @@ class HpSanISCSIDriver(SanISCSIDriver):
|
|||
1.0.0 - Initial driver
|
||||
1.1.0 - Added create/delete snapshot, extend volume, create volume
|
||||
from snapshot support.
|
||||
1.2.0 - Ported into the new HP LeftHand driver.
|
||||
"""
|
||||
|
||||
VERSION = "1.1.0"
|
||||
VERSION = "1.2.0"
|
||||
|
||||
device_stats = {}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(HpSanISCSIDriver, self).__init__(*args, **kwargs)
|
||||
super(HPLeftHandCLIQProxy, self).__init__(*args, **kwargs)
|
||||
self.cluster_vip = None
|
||||
|
||||
def do_setup(self, context):
|
||||
pass
|
||||
|
||||
def check_for_setup_error(self):
|
||||
pass
|
||||
|
||||
def get_version_string(self):
|
||||
return (_('CLIQ %(proxy_ver)s') % {'proxy_ver': self.VERSION})
|
||||
|
||||
def _cliq_run(self, verb, cliq_args, check_exit_code=True):
|
||||
"""Runs a CLIQ command over SSH, without doing any result parsing."""
|
||||
cmd_list = [verb]
|
||||
|
@ -301,7 +312,7 @@ class HpSanISCSIDriver(SanISCSIDriver):
|
|||
try:
|
||||
volume_info = self._cliq_get_volume_info(volume['name'])
|
||||
except processutils.ProcessExecutionError:
|
||||
LOG.error_("Volume did not exist. It will not be deleted")
|
||||
LOG.error(_("Volume did not exist. It will not be deleted"))
|
||||
return
|
||||
self._cliq_run_xml("deleteVolume", cliq_args)
|
||||
|
||||
|
@ -313,9 +324,16 @@ class HpSanISCSIDriver(SanISCSIDriver):
|
|||
try:
|
||||
volume_info = self._cliq_get_snapshot_info(snapshot['name'])
|
||||
except processutils.ProcessExecutionError:
|
||||
LOG.error_("Snapshot did not exist. It will not be deleted")
|
||||
LOG.error(_("Snapshot did not exist. It will not be deleted"))
|
||||
return
|
||||
self._cliq_run_xml("deleteSnapshot", cliq_args)
|
||||
try:
|
||||
self._cliq_run_xml("deleteSnapshot", cliq_args)
|
||||
except Exception as ex:
|
||||
in_use_msg = 'cannot be deleted because it is a clone point'
|
||||
if in_use_msg in ex.message:
|
||||
raise exception.SnapshotIsBusy(str(ex))
|
||||
|
||||
raise exception.VolumeBackendAPIException(str(ex))
|
||||
|
||||
def local_path(self, volume):
|
||||
msg = _("local_path not supported")
|
||||
|
@ -349,10 +367,10 @@ class HpSanISCSIDriver(SanISCSIDriver):
|
|||
cliq_args['serverName'] = connector['host']
|
||||
self._cliq_run_xml("assignVolumeToServer", cliq_args)
|
||||
|
||||
iscsi_properties = self._get_iscsi_properties(volume)
|
||||
iscsi_data = self._get_iscsi_properties(volume)
|
||||
return {
|
||||
'driver_volume_type': 'iscsi',
|
||||
'data': iscsi_properties
|
||||
'data': iscsi_data
|
||||
}
|
||||
|
||||
def _create_server(self, connector):
|
||||
|
@ -405,7 +423,6 @@ class HpSanISCSIDriver(SanISCSIDriver):
|
|||
data = {}
|
||||
backend_name = self.configuration.safe_get('volume_backend_name')
|
||||
data['volume_backend_name'] = backend_name or self.__class__.__name__
|
||||
data['driver_version'] = self.VERSION
|
||||
data['reserved_percentage'] = 0
|
||||
data['storage_protocol'] = 'iSCSI'
|
||||
data['vendor_name'] = 'Hewlett-Packard'
|
||||
|
@ -414,8 +431,20 @@ class HpSanISCSIDriver(SanISCSIDriver):
|
|||
cluster_node = result_xml.find("response/cluster")
|
||||
total_capacity = cluster_node.attrib.get("spaceTotal")
|
||||
free_capacity = cluster_node.attrib.get("unprovisionedSpace")
|
||||
GB = 1073741824
|
||||
GB = units.GiB
|
||||
|
||||
data['total_capacity_gb'] = int(total_capacity) / GB
|
||||
data['free_capacity_gb'] = int(free_capacity) / GB
|
||||
self.device_stats = data
|
||||
|
||||
def create_cloned_volume(self, volume, src_vref):
|
||||
raise NotImplementedError()
|
||||
|
||||
def create_export(self, context, volume):
|
||||
pass
|
||||
|
||||
def ensure_export(self, context, volume):
|
||||
pass
|
||||
|
||||
def remove_export(self, context, volume):
|
||||
pass
|
|
@ -0,0 +1,137 @@
|
|||
# (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.
|
||||
#
|
||||
"""
|
||||
Volume driver for HP LeftHand Storage array.
|
||||
This driver requires 11.5 or greater firmware on the LeftHand array, using
|
||||
the 1.0 or greater version of the hplefthandclient.
|
||||
|
||||
You will need to install the python hplefthandclient.
|
||||
sudo pip install hplefthandclient
|
||||
|
||||
Set the following in the cinder.conf file to enable the
|
||||
LeftHand Channel Driver along with the required flags:
|
||||
|
||||
volume_driver=cinder.volume.drivers.san.hp.hp_lefthand_iscsi.
|
||||
HPLeftHandISCSIDriver
|
||||
|
||||
It also requires the setting of hplefthand_api_url, hplefthand_username,
|
||||
hplefthand_password for credentials to talk to the REST service on the
|
||||
LeftHand array.
|
||||
"""
|
||||
from cinder import exception
|
||||
from cinder.openstack.common import log as logging
|
||||
from cinder import utils
|
||||
from cinder.volume.driver import VolumeDriver
|
||||
from cinder.volume.drivers.san.hp import hp_lefthand_cliq_proxy as cliq_proxy
|
||||
from cinder.volume.drivers.san.hp import hp_lefthand_rest_proxy as rest_proxy
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class HPLeftHandISCSIDriver(VolumeDriver):
|
||||
"""Executes commands relating to HP/LeftHand SAN ISCSI volumes.
|
||||
|
||||
Version history:
|
||||
1.0.0 - Initial driver
|
||||
"""
|
||||
|
||||
VERSION = "1.0.0"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(HPLeftHandISCSIDriver, self).__init__(*args, **kwargs)
|
||||
self.proxy = self._create_proxy(*args, **kwargs)
|
||||
|
||||
def _create_proxy(self, *args, **kwargs):
|
||||
try:
|
||||
proxy = rest_proxy.HPLeftHandRESTProxy(*args, **kwargs)
|
||||
except exception.NotFound:
|
||||
proxy = cliq_proxy.HPLeftHandCLIQProxy(*args, **kwargs)
|
||||
|
||||
return proxy
|
||||
|
||||
@utils.synchronized('lefthand', external=True)
|
||||
def check_for_setup_error(self):
|
||||
self.proxy.check_for_setup_error()
|
||||
|
||||
@utils.synchronized('lefthand', external=True)
|
||||
def do_setup(self, context):
|
||||
self.proxy.do_setup(context)
|
||||
|
||||
LOG.info(_("HPLeftHand driver %(driver_ver)s, proxy %(proxy_ver)s") % {
|
||||
"driver_ver": self.VERSION,
|
||||
"proxy_ver": self.proxy.get_version_string()})
|
||||
|
||||
@utils.synchronized('lefthand', external=True)
|
||||
def create_volume(self, volume):
|
||||
"""Creates a volume."""
|
||||
return self.proxy.create_volume(volume)
|
||||
|
||||
@utils.synchronized('lefthand', external=True)
|
||||
def extend_volume(self, volume, new_size):
|
||||
"""Extend the size of an existing volume."""
|
||||
self.proxy.extend_volume(volume, new_size)
|
||||
|
||||
@utils.synchronized('lefthand', external=True)
|
||||
def create_volume_from_snapshot(self, volume, snapshot):
|
||||
"""Creates a volume from a snapshot."""
|
||||
return self.proxy.create_volume_from_snapshot(volume, snapshot)
|
||||
|
||||
@utils.synchronized('lefthand', external=True)
|
||||
def create_snapshot(self, snapshot):
|
||||
"""Creates a snapshot."""
|
||||
self.proxy.create_snapshot(snapshot)
|
||||
|
||||
@utils.synchronized('lefthand', external=True)
|
||||
def delete_volume(self, volume):
|
||||
"""Deletes a volume."""
|
||||
self.proxy.delete_volume(volume)
|
||||
|
||||
@utils.synchronized('lefthand', external=True)
|
||||
def delete_snapshot(self, snapshot):
|
||||
"""Deletes a snapshot."""
|
||||
self.proxy.delete_snapshot(snapshot)
|
||||
|
||||
@utils.synchronized('lefthand', external=True)
|
||||
def initialize_connection(self, volume, connector):
|
||||
"""Assigns the volume to a server."""
|
||||
return self.proxy.initialize_connection(volume, connector)
|
||||
|
||||
@utils.synchronized('lefthand', external=True)
|
||||
def terminate_connection(self, volume, connector, **kwargs):
|
||||
"""Unassign the volume from the host."""
|
||||
self.proxy.terminate_connection(volume, connector)
|
||||
|
||||
@utils.synchronized('lefthand', external=True)
|
||||
def get_volume_stats(self, refresh):
|
||||
data = self.proxy.get_volume_stats(refresh)
|
||||
data['driver_version'] = self.VERSION
|
||||
return data
|
||||
|
||||
@utils.synchronized('lefthand', external=True)
|
||||
def create_cloned_volume(self, volume, src_vref):
|
||||
return self.proxy.create_cloned_volume(volume, src_vref)
|
||||
|
||||
@utils.synchronized('lefthand', external=True)
|
||||
def create_export(self, context, volume):
|
||||
return self.proxy.create_export(context, volume)
|
||||
|
||||
@utils.synchronized('lefthand', external=True)
|
||||
def ensure_export(self, context, volume):
|
||||
return self.proxy.ensure_export(context, volume)
|
||||
|
||||
@utils.synchronized('lefthand', external=True)
|
||||
def remove_export(self, context, volume):
|
||||
return self.proxy.remove_export(context, volume)
|
|
@ -0,0 +1,363 @@
|
|||
# (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.
|
||||
#
|
||||
"""HP LeftHand SAN ISCSI REST Proxy."""
|
||||
|
||||
from cinder import context
|
||||
from cinder import exception
|
||||
from cinder.openstack.common import log as logging
|
||||
from cinder import units
|
||||
from cinder import utils
|
||||
from cinder.volume.driver import ISCSIDriver
|
||||
from cinder.volume import volume_types
|
||||
from oslo.config import cfg
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
try:
|
||||
import hplefthandclient
|
||||
from hplefthandclient import client
|
||||
from hplefthandclient import exceptions as hpexceptions
|
||||
except ImportError:
|
||||
LOG.error(_('Module hplefthandclient not installed.'))
|
||||
|
||||
hplefthand_opts = [
|
||||
cfg.StrOpt('hplefthand_api_url',
|
||||
default=None,
|
||||
help="HP LeftHand WSAPI Server Url like "
|
||||
"https://<LeftHand ip>:8081/lhos"),
|
||||
cfg.StrOpt('hplefthand_username',
|
||||
default=None,
|
||||
help="HP LeftHand Super user username"),
|
||||
cfg.StrOpt('hplefthand_password',
|
||||
default=None,
|
||||
help="HP LeftHand Super user password",
|
||||
secret=True),
|
||||
cfg.StrOpt('hplefthand_clustername',
|
||||
default=None,
|
||||
help="HP LeftHand cluster name"),
|
||||
cfg.BoolOpt('hplefthand_iscsi_chap_enabled',
|
||||
default=False,
|
||||
help='Configure CHAP authentication for iSCSI connections '
|
||||
'(Default: Disabled)'),
|
||||
cfg.BoolOpt('hplefthand_debug',
|
||||
default=False,
|
||||
help="Enable HTTP debugging to LeftHand"),
|
||||
|
||||
]
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.register_opts(hplefthand_opts)
|
||||
|
||||
|
||||
# map the extra spec key to the REST client option key
|
||||
extra_specs_key_map = {
|
||||
'hplh:provisioning': 'isThinProvisioned',
|
||||
'hplh:ao': 'isAdaptiveOptimizationEnabled',
|
||||
'hplh:data_pl': 'dataProtectionLevel',
|
||||
}
|
||||
|
||||
# map the extra spec value to the REST client option value
|
||||
extra_specs_value_map = {
|
||||
'isThinProvisioned': {'thin': True, 'full': False},
|
||||
'isAdaptiveOptimizationEnabled': {'true': True, 'false': False},
|
||||
'dataProtectionLevel': {
|
||||
'r-0': 0, 'r-5': 1, 'r-10-2': 2, 'r-10-3': 3, 'r-10-4': 4, 'r-6': 5}
|
||||
}
|
||||
|
||||
|
||||
class HPLeftHandRESTProxy(ISCSIDriver):
|
||||
"""Executes REST commands relating to HP/LeftHand SAN ISCSI volumes.
|
||||
|
||||
Version history:
|
||||
1.0.0 - Initial REST iSCSI proxy
|
||||
"""
|
||||
|
||||
VERSION = "1.0.0"
|
||||
|
||||
device_stats = {}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(HPLeftHandRESTProxy, self).__init__(*args, **kwargs)
|
||||
self.configuration.append_config_values(hplefthand_opts)
|
||||
if not self.configuration.hplefthand_api_url:
|
||||
raise exception.NotFound(_("HPLeftHand url not found"))
|
||||
|
||||
def do_setup(self, context):
|
||||
"""Set up LeftHand client."""
|
||||
try:
|
||||
self.client = client.HPLeftHandClient(
|
||||
self.configuration.hplefthand_api_url)
|
||||
self.client.login(
|
||||
self.configuration.hplefthand_username,
|
||||
self.configuration.hplefthand_password)
|
||||
|
||||
if self.configuration.hplefthand_debug:
|
||||
self.client.debug_rest(True)
|
||||
|
||||
cluster_info = self.client.getClusterByName(
|
||||
self.configuration.hplefthand_clustername)
|
||||
self.cluster_id = cluster_info['id']
|
||||
virtual_ips = cluster_info['virtualIPAddresses']
|
||||
self.cluster_vip = virtual_ips[0]['ipV4Address']
|
||||
self._update_backend_status()
|
||||
except hpexceptions.HTTPNotFound:
|
||||
raise exception.DriverNotInitialized(
|
||||
_('LeftHand cluster not found'))
|
||||
except Exception as ex:
|
||||
raise exception.DriverNotInitialized(str(ex))
|
||||
|
||||
def check_for_setup_error(self):
|
||||
pass
|
||||
|
||||
def get_version_string(self):
|
||||
return (_('REST %(proxy_ver)s hplefthandclient %(rest_ver)s') % {
|
||||
'proxy_ver': self.VERSION,
|
||||
'rest_ver': hplefthandclient.get_version_string()})
|
||||
|
||||
def create_volume(self, volume):
|
||||
"""Creates a volume."""
|
||||
try:
|
||||
# get the extra specs of interest from this volume's volume type
|
||||
extra_specs = self._get_extra_specs(
|
||||
volume,
|
||||
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._map_extra_specs(extra_specs)
|
||||
|
||||
# if provisioning is not set, default to thin
|
||||
if 'isThinProvisioned' not in optional:
|
||||
optional['isThinProvisioned'] = True
|
||||
|
||||
clusterName = self.configuration.hplefthand_clustername
|
||||
optional['clusterName'] = clusterName
|
||||
|
||||
volume_info = self.client.createVolume(
|
||||
volume['name'], self.cluster_id,
|
||||
volume['size'] * units.GiB,
|
||||
optional)
|
||||
|
||||
return self._update_provider(volume_info)
|
||||
except Exception as ex:
|
||||
raise exception.VolumeBackendAPIException(str(ex))
|
||||
|
||||
def delete_volume(self, volume):
|
||||
"""Deletes a volume."""
|
||||
try:
|
||||
volume_info = self.client.getVolumeByName(volume['name'])
|
||||
self.client.deleteVolume(volume_info['id'])
|
||||
except hpexceptions.HTTPNotFound:
|
||||
LOG.error(_("Volume did not exist. It will not be deleted"))
|
||||
except Exception as ex:
|
||||
raise exception.VolumeBackendAPIException(str(ex))
|
||||
|
||||
def extend_volume(self, volume, new_size):
|
||||
"""Extend the size of an existing volume."""
|
||||
try:
|
||||
volume_info = self.client.getVolumeByName(volume['name'])
|
||||
|
||||
# convert GB to bytes
|
||||
options = {'size': int(new_size) * units.GiB}
|
||||
self.client.modifyVolume(volume_info['id'], options)
|
||||
except Exception as ex:
|
||||
raise exception.VolumeBackendAPIException(str(ex))
|
||||
|
||||
def create_snapshot(self, snapshot):
|
||||
"""Creates a snapshot."""
|
||||
try:
|
||||
volume_info = self.client.getVolumeByName(snapshot['volume_name'])
|
||||
|
||||
option = {'inheritAccess': True}
|
||||
self.client.createSnapshot(snapshot['name'],
|
||||
volume_info['id'],
|
||||
option)
|
||||
except Exception as ex:
|
||||
raise exception.VolumeBackendAPIException(str(ex))
|
||||
|
||||
def delete_snapshot(self, snapshot):
|
||||
"""Deletes a snapshot."""
|
||||
try:
|
||||
snap_info = self.client.getSnapshotByName(snapshot['name'])
|
||||
self.client.deleteSnapshot(snap_info['id'])
|
||||
except hpexceptions.HTTPNotFound:
|
||||
LOG.error(_("Snapshot did not exist. It will not be deleted"))
|
||||
except hpexceptions.HTTPServerError as ex:
|
||||
in_use_msg = 'cannot be deleted because it is a clone point'
|
||||
if in_use_msg in ex.get_description():
|
||||
raise exception.SnapshotIsBusy(str(ex))
|
||||
|
||||
raise exception.VolumeBackendAPIException(str(ex))
|
||||
|
||||
except Exception as ex:
|
||||
raise exception.VolumeBackendAPIException(str(ex))
|
||||
|
||||
def get_volume_stats(self, refresh):
|
||||
"""Gets volume stats."""
|
||||
if refresh:
|
||||
self._update_backend_status()
|
||||
|
||||
return self.device_stats
|
||||
|
||||
def _update_backend_status(self):
|
||||
data = {}
|
||||
backend_name = self.configuration.safe_get('volume_backend_name')
|
||||
data['volume_backend_name'] = backend_name or self.__class__.__name__
|
||||
data['reserved_percentage'] = 0
|
||||
data['storage_protocol'] = 'iSCSI'
|
||||
data['vendor_name'] = 'Hewlett-Packard'
|
||||
|
||||
cluster_info = self.client.getCluster(self.cluster_id)
|
||||
|
||||
total_capacity = cluster_info['spaceTotal']
|
||||
free_capacity = cluster_info['spaceAvailable']
|
||||
|
||||
# convert to GB
|
||||
data['total_capacity_gb'] = int(total_capacity) / units.GiB
|
||||
data['free_capacity_gb'] = int(free_capacity) / units.GiB
|
||||
|
||||
self.device_stats = data
|
||||
|
||||
def initialize_connection(self, volume, connector):
|
||||
"""Assigns the volume to a server.
|
||||
|
||||
Assign any created volume to a compute node/host so that it can be
|
||||
used from that host. HP VSA requires a volume to be assigned
|
||||
to a server.
|
||||
"""
|
||||
try:
|
||||
server_info = self._create_server(connector)
|
||||
volume_info = self.client.getVolumeByName(volume['name'])
|
||||
self.client.addServerAccess(volume_info['id'], server_info['id'])
|
||||
|
||||
iscsi_properties = self._get_iscsi_properties(volume)
|
||||
|
||||
if ('chapAuthenticationRequired' in server_info
|
||||
and server_info['chapAuthenticationRequired']):
|
||||
iscsi_properties['auth_method'] = 'CHAP'
|
||||
iscsi_properties['auth_username'] = connector['initiator']
|
||||
iscsi_properties['auth_password'] = (
|
||||
server_info['chapTargetSecret'])
|
||||
|
||||
return {'driver_volume_type': 'iscsi', 'data': iscsi_properties}
|
||||
except Exception as ex:
|
||||
raise exception.VolumeBackendAPIException(str(ex))
|
||||
|
||||
def terminate_connection(self, volume, connector, **kwargs):
|
||||
"""Unassign the volume from the host."""
|
||||
try:
|
||||
volume_info = self.client.getVolumeByName(volume['name'])
|
||||
server_info = self.client.getServerByName(connector['host'])
|
||||
self.client.removeServerAccess(
|
||||
volume_info['id'],
|
||||
server_info['id'])
|
||||
except Exception as ex:
|
||||
raise exception.VolumeBackendAPIException(str(ex))
|
||||
|
||||
def create_volume_from_snapshot(self, volume, snapshot):
|
||||
"""Creates a volume from a snapshot."""
|
||||
try:
|
||||
snap_info = self.client.getSnapshotByName(snapshot['name'])
|
||||
volume_info = self.client.cloneSnapshot(
|
||||
volume['name'],
|
||||
snap_info['id'])
|
||||
return self._update_provider(volume_info)
|
||||
except Exception as ex:
|
||||
raise exception.VolumeBackendAPIException(str(ex))
|
||||
|
||||
def create_cloned_volume(self, volume, src_vref):
|
||||
try:
|
||||
volume_info = self.client.getVolumeByName(src_vref['name'])
|
||||
self.client.cloneVolume(volume['name'], volume_info['id'])
|
||||
except Exception as ex:
|
||||
raise exception.VolumeBackendAPIException(str(ex))
|
||||
|
||||
def _get_extra_specs(self, volume, valid_keys):
|
||||
"""Get extra specs of interest (valid_keys) from volume type."""
|
||||
extra_specs = {}
|
||||
type_id = volume.get('volume_type_id', None)
|
||||
if type_id is not None:
|
||||
ctxt = context.get_admin_context()
|
||||
volume_type = volume_types.get_volume_type(ctxt, type_id)
|
||||
specs = volume_type.get('extra_specs')
|
||||
for key, value in specs.iteritems():
|
||||
if key in valid_keys:
|
||||
extra_specs[key] = value
|
||||
return extra_specs
|
||||
|
||||
def _map_extra_specs(self, extra_specs):
|
||||
"""Map the extra spec key/values to LeftHand key/values."""
|
||||
client_options = {}
|
||||
for key, value in extra_specs.iteritems():
|
||||
# map extra spec key to lh client option key
|
||||
client_key = extra_specs_key_map[key]
|
||||
# map extra spect value to lh client option value
|
||||
try:
|
||||
value_map = extra_specs_value_map[client_key]
|
||||
# an invalid value will throw KeyError
|
||||
client_value = value_map[value]
|
||||
client_options[client_key] = client_value
|
||||
except KeyError:
|
||||
LOG.error(_("'%(value)s' is an invalid value "
|
||||
"for extra spec '%(key)s'") %
|
||||
{'value': value, 'key': key})
|
||||
return client_options
|
||||
|
||||
def _update_provider(self, volume_info):
|
||||
# TODO(justinsb): Is this always 1? Does it matter?
|
||||
cluster_interface = '1'
|
||||
iscsi_portal = self.cluster_vip + ":3260," + cluster_interface
|
||||
|
||||
return {'provider_location': (
|
||||
"%s %s %s" % (iscsi_portal, volume_info['iscsiIqn'], 0))}
|
||||
|
||||
def _create_server(self, connector):
|
||||
server_info = None
|
||||
chap_enabled = self.configuration.hplefthand_iscsi_chap_enabled
|
||||
try:
|
||||
server_info = self.client.getServerByName(connector['host'])
|
||||
chap_secret = server_info['chapTargetSecret']
|
||||
if not chap_enabled and chap_secret:
|
||||
LOG.warning(_('CHAP secret exists for host %s but CHAP is '
|
||||
'disabled') % connector['host'])
|
||||
if chap_enabled and chap_secret is None:
|
||||
LOG.warning(_('CHAP is enabled, but server secret not '
|
||||
'configured on server %s') % connector['host'])
|
||||
return server_info
|
||||
except hpexceptions.HTTPNotFound:
|
||||
# server does not exist, so create one
|
||||
pass
|
||||
|
||||
optional = None
|
||||
if chap_enabled:
|
||||
chap_secret = utils.generate_password()
|
||||
optional = {'chapName': connector['initiator'],
|
||||
'chapTargetSecret': chap_secret,
|
||||
'chapAuthenticationRequired': True
|
||||
}
|
||||
server_info = self.client.createServer(connector['host'],
|
||||
connector['initiator'],
|
||||
optional)
|
||||
return server_info
|
||||
|
||||
def create_export(self, context, volume):
|
||||
pass
|
||||
|
||||
def ensure_export(self, context, volume):
|
||||
pass
|
||||
|
||||
def remove_export(self, context, volume):
|
||||
pass
|
|
@ -94,8 +94,6 @@ MAPPING = {
|
|||
'cinder.volume.drivers.san.san.SanISCSIDriver',
|
||||
'cinder.volume.san.SolarisISCSIDriver':
|
||||
'cinder.volume.drivers.san.solaris.SolarisISCSIDriver',
|
||||
'cinder.volume.san.HpSanISCSIDriver':
|
||||
'cinder.volume.drivers.san.hp_lefthand.HpSanISCSIDriver',
|
||||
'cinder.volume.nfs.NfsDriver':
|
||||
'cinder.volume.drivers.nfs.NfsDriver',
|
||||
'cinder.volume.solidfire.SolidFire':
|
||||
|
@ -133,7 +131,9 @@ MAPPING = {
|
|||
'cinder.volume.drivers.netapp.nfs.NetAppCmodeNfsDriver':
|
||||
'cinder.volume.drivers.netapp.common.Deprecated',
|
||||
'cinder.volume.drivers.huawei.HuaweiISCSIDriver':
|
||||
'cinder.volume.drivers.huawei.HuaweiVolumeDriver'}
|
||||
'cinder.volume.drivers.huawei.HuaweiVolumeDriver',
|
||||
'cinder.volume.drivers.san.hp_lefthand.HpSanISCSIDriver':
|
||||
'cinder.volume.drivers.san.hp.hp_lefthand_iscsi.HPLeftHandISCSIDriver'}
|
||||
|
||||
|
||||
def locked_volume_operation(f):
|
||||
|
|
|
@ -1491,6 +1491,31 @@
|
|||
#hp3par_iscsi_ips=
|
||||
|
||||
|
||||
#
|
||||
# Options defined in cinder.volume.drivers.san.hp.hp_lefthand_rest_proxy
|
||||
#
|
||||
|
||||
# HP LeftHand WSAPI Server Url like https://<LeftHand
|
||||
# ip>:8081/lhos (string value)
|
||||
#hplefthand_api_url=<None>
|
||||
|
||||
# HP LeftHand Super user username (string value)
|
||||
#hplefthand_username=<None>
|
||||
|
||||
# HP LeftHand Super user password (string value)
|
||||
#hplefthand_password=<None>
|
||||
|
||||
# HP LeftHand cluster name (string value)
|
||||
#hplefthand_clustername=<None>
|
||||
|
||||
# Configure CHAP authentication for iSCSI connections
|
||||
# (Default: Disabled) (boolean value)
|
||||
#hplefthand_iscsi_chap_enabled=false
|
||||
|
||||
# Enable HTTP debugging to LeftHand (boolean value)
|
||||
#hplefthand_debug=false
|
||||
|
||||
|
||||
#
|
||||
# Options defined in cinder.volume.drivers.san.san
|
||||
#
|
||||
|
|
|
@ -4,6 +4,7 @@ coverage>=3.6
|
|||
discover
|
||||
fixtures>=0.3.14
|
||||
hp3parclient>=2.0,<3.0
|
||||
hplefthandclient>=1.0.0,<2.0.0
|
||||
mock>=1.0
|
||||
mox>=0.5.3
|
||||
MySQL-python
|
||||
|
|
Loading…
Reference in New Issue