Removing locks from 3PAR FC and iSCSI drivers

Removed locks from the 3PAR FC and iSCSI drivers.  In high load
environments where many simultaneous volume creations/deletions
and attaches/detaches are happening errors occur periodically.

By changing the drivers to create a new connection to the 3PAR
backend whenever a volume request is made the errors are avoided
and performance is improved.

Closes-Bug: 1381190
Change-Id: Ie588a1d87cf5a22ddf2e890c440582e1fe67f2cb
This commit is contained in:
Anthony Lee 2014-10-15 14:16:32 -07:00
parent 5c203bb3d3
commit 9ef47c280b
4 changed files with 2296 additions and 1846 deletions

File diff suppressed because it is too large Load Diff

View File

@ -158,10 +158,11 @@ class HP3PARCommon(object):
2.0.25 - Migrate without losing type settings bug #1356608
2.0.26 - Don't ignore extra-specs snap_cpg when missing cpg #1368972
2.0.27 - Fixing manage source-id error bug #1357075
2.0.28 - Removing locks bug #1381190
"""
VERSION = "2.0.27"
VERSION = "2.0.28"
stats = {}
@ -199,6 +200,7 @@ class HP3PARCommon(object):
self.config = config
self.hosts_naming_dict = dict()
self.client = None
self.uuid = uuid.uuid4()
def get_version(self):
return self.VERSION
@ -222,27 +224,6 @@ class HP3PARCommon(object):
LOG.error(ex_msg)
raise exception.InvalidInput(reason=ex_msg)
if client_version < MIN_CLIENT_SSH_ARGS_VERSION:
cl.setSSHOptions(self.config.san_ip,
self.config.san_login,
self.config.san_password,
port=self.config.san_ssh_port,
conn_timeout=self.config.ssh_conn_timeout,
privatekey=self.config.san_private_key)
else:
known_hosts_file = CONF.ssh_hosts_key_file
policy = "AutoAddPolicy"
if CONF.strict_ssh_host_key_policy:
policy = "RejectPolicy"
cl.setSSHOptions(self.config.san_ip,
self.config.san_login,
self.config.san_password,
port=self.config.san_ssh_port,
conn_timeout=self.config.ssh_conn_timeout,
privatekey=self.config.san_private_key,
missing_key_policy=policy,
known_hosts_file=known_hosts_file)
return cl
def client_login(self):
@ -256,9 +237,35 @@ class HP3PARCommon(object):
LOG.error(msg)
raise exception.InvalidInput(reason=msg)
client_version = hp3parclient.version
if client_version < MIN_CLIENT_SSH_ARGS_VERSION:
self.client.setSSHOptions(
self.config.san_ip,
self.config.san_login,
self.config.san_password,
port=self.config.san_ssh_port,
conn_timeout=self.config.ssh_conn_timeout,
privatekey=self.config.san_private_key)
else:
known_hosts_file = CONF.ssh_hosts_key_file
policy = "AutoAddPolicy"
if CONF.strict_ssh_host_key_policy:
policy = "RejectPolicy"
self.client.setSSHOptions(
self.config.san_ip,
self.config.san_login,
self.config.san_password,
port=self.config.san_ssh_port,
conn_timeout=self.config.ssh_conn_timeout,
privatekey=self.config.san_private_key,
missing_key_policy=policy,
known_hosts_file=known_hosts_file)
def client_logout(self):
LOG.info(_LI("Disconnect from 3PAR REST and SSH %s") % self.uuid)
self.client.logout()
LOG.debug("Disconnect from 3PAR")
LOG.info(_LI("logout Done %s") % self.uuid)
def do_setup(self, context):
if hp3parclient is None:
@ -274,8 +281,8 @@ class HP3PARCommon(object):
if self.config.hp3par_debug:
self.client.debug_rest(True)
def check_for_setup_error(self):
self.client_login()
try:
cpg_names = self.config.hp3par_cpg
for cpg_name in cpg_names:

View File

@ -34,10 +34,11 @@ try:
except ImportError:
hpexceptions = None
import pprint
from cinder import exception
from cinder.i18n import _, _LI
from cinder.i18n import _, _LI, _LW
from cinder.openstack.common import log as logging
from cinder import utils
import cinder.volume.driver
from cinder.volume.drivers.san.hp import hp_3par_common as hpcommon
from cinder.volume.drivers.san import san
@ -72,14 +73,14 @@ class HP3PARFCDriver(cinder.volume.driver.FibreChannelDriver):
2.0.8 - Fixing missing login/logout around attach/detach bug #1367429
2.0.9 - Add support for pools with model update
2.0.10 - Migrate without losing type settings bug #1356608
2.0.11 - Removing locks bug #1381190
"""
VERSION = "2.0.10"
VERSION = "2.0.11"
def __init__(self, *args, **kwargs):
super(HP3PARFCDriver, self).__init__(*args, **kwargs)
self.common = None
self.configuration.append_config_values(hpcommon.hp3par_opts)
self.configuration.append_config_values(san.san_opts)
self.lookup_service = fczm_utils.create_lookup_service()
@ -87,18 +88,26 @@ class HP3PARFCDriver(cinder.volume.driver.FibreChannelDriver):
def _init_common(self):
return hpcommon.HP3PARCommon(self.configuration)
def _check_flags(self):
def _login(self):
common = self._init_common()
common.do_setup(None)
common.client_login()
return common
def _logout(self, common):
common.client_logout()
def _check_flags(self, common):
"""Sanity check to ensure we have required options set."""
required_flags = ['hp3par_api_url', 'hp3par_username',
'hp3par_password',
'san_ip', 'san_login', 'san_password']
self.common.check_flags(self.configuration, required_flags)
common.check_flags(self.configuration, required_flags)
@utils.synchronized('3par', external=True)
def get_volume_stats(self, refresh):
self.common.client_login()
common = self._login()
try:
stats = self.common.get_volume_stats(refresh)
stats = common.get_volume_stats(refresh)
stats['storage_protocol'] = 'FC'
stats['driver_version'] = self.VERSION
backend_name = self.configuration.safe_get('volume_backend_name')
@ -106,72 +115,65 @@ class HP3PARFCDriver(cinder.volume.driver.FibreChannelDriver):
self.__class__.__name__)
return stats
finally:
self.common.client_logout()
self._logout(common)
def do_setup(self, context):
self.common = self._init_common()
self._check_flags()
self.common.do_setup(context)
common = self._init_common()
common.do_setup(context)
self._check_flags(common)
common.check_for_setup_error()
def check_for_setup_error(self):
"""Returns an error if prerequisites aren't met."""
self._check_flags()
"""Setup errors are already checked for in do_setup so return pass."""
pass
@utils.synchronized('3par', external=True)
def create_volume(self, volume):
self.common.client_login()
common = self._login()
try:
return self.common.create_volume(volume)
return common.create_volume(volume)
finally:
self.common.client_logout()
self._logout(common)
@utils.synchronized('3par', external=True)
def create_cloned_volume(self, volume, src_vref):
self.common.client_login()
common = self._login()
try:
return self.common.create_cloned_volume(volume, src_vref)
return common.create_cloned_volume(volume, src_vref)
finally:
self.common.client_logout()
self._logout(common)
@utils.synchronized('3par', external=True)
def delete_volume(self, volume):
self.common.client_login()
common = self._login()
try:
self.common.delete_volume(volume)
common.delete_volume(volume)
finally:
self.common.client_logout()
self._logout(common)
@utils.synchronized('3par', external=True)
def create_volume_from_snapshot(self, volume, snapshot):
"""Create a volume from a snapshot.
TODO: support using the size from the user.
"""
self.common.client_login()
common = self._login()
try:
return self.common.create_volume_from_snapshot(volume,
snapshot)
return common.create_volume_from_snapshot(volume, snapshot)
finally:
self.common.client_logout()
self._logout(common)
@utils.synchronized('3par', external=True)
def create_snapshot(self, snapshot):
self.common.client_login()
common = self._login()
try:
self.common.create_snapshot(snapshot)
common.create_snapshot(snapshot)
finally:
self.common.client_logout()
self._logout(common)
@utils.synchronized('3par', external=True)
def delete_snapshot(self, snapshot):
self.common.client_login()
common = self._login()
try:
self.common.delete_snapshot(snapshot)
common.delete_snapshot(snapshot)
finally:
self.common.client_logout()
self._logout(common)
@fczm_utils.AddFCZone
@utils.synchronized('3par', external=True)
def initialize_connection(self, volume, connector):
"""Assigns the volume to a server.
@ -209,25 +211,25 @@ class HP3PARFCDriver(cinder.volume.driver.FibreChannelDriver):
* Create a VLUN for that HOST with the volume we want to export.
"""
self.common.client_login()
common = self._login()
try:
# we have to make sure we have a host
host = self._create_host(volume, connector)
host = self._create_host(common, volume, connector)
target_wwns, init_targ_map, numPaths = \
self._build_initiator_target_map(connector)
self._build_initiator_target_map(common, connector)
# now that we have a host, create the VLUN
if self.lookup_service is not None and numPaths == 1:
nsp = None
active_fc_port_list = self.common.get_active_fc_target_ports()
active_fc_port_list = common.get_active_fc_target_ports()
for port in active_fc_port_list:
if port['portWWN'].lower() == target_wwns[0].lower():
nsp = port['nsp']
break
vlun = self.common.create_vlun(volume, host, nsp)
vlun = common.create_vlun(volume, host, nsp)
else:
vlun = self.common.create_vlun(volume, host)
vlun = common.create_vlun(volume, host)
info = {'driver_volume_type': 'fibre_channel',
'data': {'target_lun': vlun['lun'],
@ -236,42 +238,41 @@ class HP3PARFCDriver(cinder.volume.driver.FibreChannelDriver):
'initiator_target_map': init_targ_map}}
return info
finally:
self.common.client_logout()
self._logout(common)
@fczm_utils.RemoveFCZone
@utils.synchronized('3par', external=True)
def terminate_connection(self, volume, connector, **kwargs):
"""Driver entry point to unattach a volume from an instance."""
self.common.client_login()
common = self._login()
try:
hostname = self.common._safe_hostname(connector['host'])
self.common.terminate_connection(volume, hostname,
wwn=connector['wwpns'])
hostname = common._safe_hostname(connector['host'])
common.terminate_connection(volume, hostname,
wwn=connector['wwpns'])
info = {'driver_volume_type': 'fibre_channel',
'data': {}}
try:
self.common.client.getHostVLUNs(hostname)
common.client.getHostVLUNs(hostname)
except hpexceptions.HTTPNotFound:
# No more exports for this host.
LOG.info(_LI("Need to remove FC Zone, building initiator "
"target map"))
target_wwns, init_targ_map, _numPaths = \
self._build_initiator_target_map(connector)
self._build_initiator_target_map(common, connector)
info['data'] = {'target_wwn': target_wwns,
'initiator_target_map': init_targ_map}
return info
finally:
self.common.client_logout()
self._logout(common)
def _build_initiator_target_map(self, connector):
def _build_initiator_target_map(self, common, connector):
"""Build the target_wwns and the initiator target map."""
fc_ports = self.common.get_active_fc_target_ports()
fc_ports = common.get_active_fc_target_ports()
all_target_wwns = []
target_wwns = []
init_targ_map = {}
@ -308,7 +309,8 @@ class HP3PARFCDriver(cinder.volume.driver.FibreChannelDriver):
return target_wwns, init_targ_map, numPaths
def _create_3par_fibrechan_host(self, hostname, wwns, domain, persona_id):
def _create_3par_fibrechan_host(self, common, hostname, wwns,
domain, persona_id):
"""Create a 3PAR host.
Create a 3PAR host, if there is already a host on the 3par using
@ -317,48 +319,50 @@ class HP3PARFCDriver(cinder.volume.driver.FibreChannelDriver):
"""
# first search for an existing host
host_found = None
for wwn in wwns:
host_found = self.common.client.findHost(wwn=wwn)
if host_found is not None:
break
hosts = common.client.queryHost(wwns)
LOG.warn(_LW("Found HOSTS %s") % pprint.pformat(hosts))
if hosts and hosts['members']:
host_found = hosts['members'][0]['name']
if host_found is not None:
self.common.hosts_naming_dict[hostname] = host_found
common.hosts_naming_dict[hostname] = host_found
return host_found
else:
persona_id = int(persona_id)
self.common.client.createHost(hostname, FCWwns=wwns,
optional={'domain': domain,
'persona': persona_id})
common.client.createHost(hostname, FCWwns=wwns,
optional={'domain': domain,
'persona': persona_id})
return hostname
def _modify_3par_fibrechan_host(self, hostname, wwn):
mod_request = {'pathOperation': self.common.client.HOST_EDIT_ADD,
def _modify_3par_fibrechan_host(self, common, hostname, wwn):
mod_request = {'pathOperation': common.client.HOST_EDIT_ADD,
'FCWWNs': wwn}
self.common.client.modifyHost(hostname, mod_request)
common.client.modifyHost(hostname, mod_request)
def _create_host(self, volume, connector):
def _create_host(self, common, volume, connector):
"""Creates or modifies existing 3PAR host."""
host = None
hostname = self.common._safe_hostname(connector['host'])
cpg = self.common.get_cpg(volume, allowSnap=True)
domain = self.common.get_domain(cpg)
hostname = common._safe_hostname(connector['host'])
cpg = common.get_cpg(volume, allowSnap=True)
domain = common.get_domain(cpg)
try:
host = self.common._get_3par_host(hostname)
host = common._get_3par_host(hostname)
except hpexceptions.HTTPNotFound:
# get persona from the volume type extra specs
persona_id = self.common.get_persona_type(volume)
persona_id = common.get_persona_type(volume)
# host doesn't exist, we have to create it
hostname = self._create_3par_fibrechan_host(hostname,
hostname = self._create_3par_fibrechan_host(common,
hostname,
connector['wwpns'],
domain,
persona_id)
host = self.common._get_3par_host(hostname)
host = common._get_3par_host(hostname)
return self._add_new_wwn_to_host(host, connector['wwpns'])
return self._add_new_wwn_to_host(common, host, connector['wwpns'])
def _add_new_wwn_to_host(self, host, wwns):
def _add_new_wwn_to_host(self, common, host, wwns):
"""Add wwns to a host if one or more don't exist.
Identify if argument wwns contains any world wide names
@ -383,85 +387,71 @@ class HP3PARFCDriver(cinder.volume.driver.FibreChannelDriver):
# if any wwns found that were not in host list,
# add them to the host
if (len(new_wwns) > 0):
self._modify_3par_fibrechan_host(host['name'], new_wwns)
host = self.common._get_3par_host(host['name'])
self._modify_3par_fibrechan_host(common, host['name'], new_wwns)
host = common._get_3par_host(host['name'])
return host
@utils.synchronized('3par', external=True)
def create_export(self, context, volume):
pass
@utils.synchronized('3par', external=True)
def ensure_export(self, context, volume):
pass
@utils.synchronized('3par', external=True)
def remove_export(self, context, volume):
pass
@utils.synchronized('3par', external=True)
def extend_volume(self, volume, new_size):
self.common.client_login()
common = self._login()
try:
self.common.extend_volume(volume, new_size)
common.extend_volume(volume, new_size)
finally:
self.common.client_logout()
self._logout(common)
@utils.synchronized('3par', external=True)
def manage_existing(self, volume, existing_ref):
self.common.client_login()
common = self._login()
try:
return self.common.manage_existing(volume, existing_ref)
return common.manage_existing(volume, existing_ref)
finally:
self.common.client_logout()
self._logout(common)
@utils.synchronized('3par', external=True)
def manage_existing_get_size(self, volume, existing_ref):
self.common.client_login()
common = self._login()
try:
size = self.common.manage_existing_get_size(volume, existing_ref)
return common.manage_existing_get_size(volume, existing_ref)
finally:
self.common.client_logout()
self._logout(common)
return size
@utils.synchronized('3par', external=True)
def unmanage(self, volume):
self.common.client_login()
common = self._login()
try:
self.common.unmanage(volume)
common.unmanage(volume)
finally:
self.common.client_logout()
self._logout(common)
@utils.synchronized('3par', external=True)
def attach_volume(self, context, volume, instance_uuid, host_name,
mountpoint):
self.common.client_login()
common = self._login()
try:
self.common.attach_volume(volume, instance_uuid)
common.attach_volume(volume, instance_uuid)
finally:
self.common.client_logout()
self._logout(common)
@utils.synchronized('3par', external=True)
def detach_volume(self, context, volume):
self.common.client_login()
common = self._login()
try:
self.common.detach_volume(volume)
common.detach_volume(volume)
finally:
self.common.client_logout()
self._logout(common)
@utils.synchronized('3par', external=True)
def retype(self, context, volume, new_type, diff, host):
"""Convert the volume to be of the new type."""
self.common.client_login()
common = self._login()
try:
return self.common.retype(volume, new_type, diff, host)
return common.retype(volume, new_type, diff, host)
finally:
self.common.client_logout()
self._logout(common)
@utils.synchronized('3par', external=True)
def migrate_volume(self, context, volume, host):
if volume['status'] == 'in-use':
protocol = host['capabilities']['storage_protocol']
if protocol != 'FC':
@ -469,19 +459,19 @@ class HP3PARFCDriver(cinder.volume.driver.FibreChannelDriver):
"to a host with storage_protocol=%s." % protocol)
return False, None
self.common.client_login()
common = self._login()
try:
return self.common.migrate_volume(volume, host)
return common.migrate_volume(volume, host)
finally:
self.common.client_logout()
self._logout(common)
def get_pool(self, volume):
self.common.client_login()
common = self._login()
try:
return self.common.get_cpg(volume)
return common.get_cpg(volume)
except hpexceptions.HTTPNotFound:
reason = (_("Volume %s doesn't exist on array.") % volume)
LOG.error(reason)
raise exception.InvalidVolume(reason)
finally:
self.common.client_logout()
self._logout(common)

View File

@ -38,7 +38,6 @@ except ImportError:
from cinder import exception
from cinder.i18n import _, _LE
from cinder.openstack.common import log as logging
from cinder import utils
import cinder.volume.driver
from cinder.volume.drivers.san.hp import hp_3par_common as hpcommon
from cinder.volume.drivers.san import san
@ -76,32 +75,40 @@ class HP3PARISCSIDriver(cinder.volume.driver.ISCSIDriver):
2.0.6 - Fixing missing login/logout around attach/detach bug #1367429
2.0.7 - Add support for pools with model update
2.0.8 - Migrate without losing type settings bug #1356608
2.0.9 - Removing locks bug #1381190
"""
VERSION = "2.0.8"
VERSION = "2.0.9"
def __init__(self, *args, **kwargs):
super(HP3PARISCSIDriver, self).__init__(*args, **kwargs)
self.common = None
self.configuration.append_config_values(hpcommon.hp3par_opts)
self.configuration.append_config_values(san.san_opts)
def _init_common(self):
return hpcommon.HP3PARCommon(self.configuration)
def _check_flags(self):
def _login(self):
common = self._init_common()
common.do_setup(None)
common.client_login()
return common
def _logout(self, common):
common.client_logout()
def _check_flags(self, common):
"""Sanity check to ensure we have required options set."""
required_flags = ['hp3par_api_url', 'hp3par_username',
'hp3par_password', 'san_ip', 'san_login',
'san_password']
self.common.check_flags(self.configuration, required_flags)
common.check_flags(self.configuration, required_flags)
@utils.synchronized('3par', external=True)
def get_volume_stats(self, refresh):
self.common.client_login()
common = self._login()
try:
stats = self.common.get_volume_stats(refresh)
stats = common.get_volume_stats(refresh)
stats['storage_protocol'] = 'iSCSI'
stats['driver_version'] = self.VERSION
backend_name = self.configuration.safe_get('volume_backend_name')
@ -109,20 +116,21 @@ class HP3PARISCSIDriver(cinder.volume.driver.ISCSIDriver):
self.__class__.__name__)
return stats
finally:
self.common.client_logout()
self._logout(common)
def do_setup(self, context):
self.common = self._init_common()
self._check_flags()
self.common.do_setup(context)
common = self._init_common()
common.do_setup(context)
self._check_flags(common)
common.check_for_setup_error()
self.common.client_login()
common.client_login()
try:
self.initialize_iscsi_ports()
self.initialize_iscsi_ports(common)
finally:
self.common.client_logout()
self._logout(common)
def initialize_iscsi_ports(self):
def initialize_iscsi_ports(self, common):
# map iscsi_ip-> ip_port
# -> iqn
# -> nsp
@ -153,7 +161,7 @@ class HP3PARISCSIDriver(cinder.volume.driver.ISCSIDriver):
# get all the valid iSCSI ports from 3PAR
# when found, add the valid iSCSI ip, ip port, iqn and nsp
# to the iSCSI IP dictionary
iscsi_ports = self.common.get_active_iscsi_target_ports()
iscsi_ports = common.get_active_iscsi_target_ports()
for port in iscsi_ports:
ip = port['IPAddr']
@ -184,64 +192,56 @@ class HP3PARISCSIDriver(cinder.volume.driver.ISCSIDriver):
raise exception.InvalidInput(reason=(msg))
def check_for_setup_error(self):
"""Returns an error if prerequisites aren't met."""
self._check_flags()
"""Setup errors are already checked for in do_setup so return pass."""
pass
@utils.synchronized('3par', external=True)
def create_volume(self, volume):
self.common.client_login()
common = self._login()
try:
return self.common.create_volume(volume)
return common.create_volume(volume)
finally:
self.common.client_logout()
self._logout(common)
@utils.synchronized('3par', external=True)
def create_cloned_volume(self, volume, src_vref):
"""Clone an existing volume."""
self.common.client_login()
common = self._login()
try:
return self.common.create_cloned_volume(volume, src_vref)
return common.create_cloned_volume(volume, src_vref)
finally:
self.common.client_logout()
self._logout(common)
@utils.synchronized('3par', external=True)
def delete_volume(self, volume):
self.common.client_login()
common = self._login()
try:
self.common.delete_volume(volume)
common.delete_volume(volume)
finally:
self.common.client_logout()
self._logout(common)
@utils.synchronized('3par', external=True)
def create_volume_from_snapshot(self, volume, snapshot):
"""Creates a volume from a snapshot.
TODO: support using the size from the user.
"""
self.common.client_login()
common = self._login()
try:
return self.common.create_volume_from_snapshot(volume,
snapshot)
return common.create_volume_from_snapshot(volume, snapshot)
finally:
self.common.client_logout()
self._logout(common)
@utils.synchronized('3par', external=True)
def create_snapshot(self, snapshot):
self.common.client_login()
common = self._login()
try:
self.common.create_snapshot(snapshot)
common.create_snapshot(snapshot)
finally:
self.common.client_logout()
self._logout(common)
@utils.synchronized('3par', external=True)
def delete_snapshot(self, snapshot):
self.common.client_login()
common = self._login()
try:
self.common.delete_snapshot(snapshot)
common.delete_snapshot(snapshot)
finally:
self.common.client_logout()
self._logout(common)
@utils.synchronized('3par', external=True)
def initialize_connection(self, volume, connector):
"""Assigns the volume to a server.
@ -267,14 +267,19 @@ class HP3PARISCSIDriver(cinder.volume.driver.ISCSIDriver):
* Create a host on the 3par
* create vlun on the 3par
"""
self.common.client_login()
common = self._login()
try:
# we have to make sure we have a host
host, username, password = self._create_host(volume, connector)
least_used_nsp = self._get_least_used_nsp_for_host(host['name'])
host, username, password = self._create_host(
common,
volume,
connector)
least_used_nsp = self._get_least_used_nsp_for_host(
common,
host['name'])
# now that we have a host, create the VLUN
vlun = self.common.create_vlun(volume, host, least_used_nsp)
vlun = common.create_vlun(volume, host, least_used_nsp)
if least_used_nsp is None:
msg = _("Least busy iSCSI port not found, "
@ -302,42 +307,44 @@ class HP3PARISCSIDriver(cinder.volume.driver.ISCSIDriver):
return info
finally:
self.common.client_logout()
self._logout(common)
@utils.synchronized('3par', external=True)
def terminate_connection(self, volume, connector, **kwargs):
"""Driver entry point to unattach a volume from an instance."""
self.common.client_login()
common = self._login()
try:
hostname = self.common._safe_hostname(connector['host'])
self.common.terminate_connection(volume, hostname,
iqn=connector['initiator'])
self._clear_chap_3par(volume)
hostname = common._safe_hostname(connector['host'])
common.terminate_connection(
volume,
hostname,
iqn=connector['initiator'])
self._clear_chap_3par(common, volume)
finally:
self.common.client_logout()
self._logout(common)
def _clear_chap_3par(self, volume):
def _clear_chap_3par(self, common, volume):
"""Clears CHAP credentials on a 3par volume.
Ignore exceptions caused by the keys not being present on a volume.
"""
vol_name = self.common._get_3par_vol_name(volume['id'])
vol_name = common._get_3par_vol_name(volume['id'])
try:
self.common.client.removeVolumeMetaData(vol_name, CHAP_USER_KEY)
common.client.removeVolumeMetaData(vol_name, CHAP_USER_KEY)
except hpexceptions.HTTPNotFound:
pass
except Exception:
raise
try:
self.common.client.removeVolumeMetaData(vol_name, CHAP_PASS_KEY)
common.client.removeVolumeMetaData(vol_name, CHAP_PASS_KEY)
except hpexceptions.HTTPNotFound:
pass
except Exception:
raise
def _create_3par_iscsi_host(self, hostname, iscsi_iqn, domain, persona_id):
def _create_3par_iscsi_host(self, common, hostname, iscsi_iqn, domain,
persona_id):
"""Create a 3PAR host.
Create a 3PAR host, if there is already a host on the 3par using
@ -345,9 +352,9 @@ class HP3PARISCSIDriver(cinder.volume.driver.ISCSIDriver):
used by 3PAR.
"""
# first search for an existing host
host_found = self.common.client.findHost(iqn=iscsi_iqn)
host_found = common.client.findHost(iqn=iscsi_iqn)
if host_found is not None:
self.common.hosts_naming_dict[hostname] = host_found
common.hosts_naming_dict[hostname] = host_found
return host_found
else:
if isinstance(iscsi_iqn, str) or isinstance(iscsi_iqn, unicode):
@ -355,73 +362,86 @@ class HP3PARISCSIDriver(cinder.volume.driver.ISCSIDriver):
else:
iqn = iscsi_iqn
persona_id = int(persona_id)
self.common.client.createHost(hostname, iscsiNames=iqn,
optional={'domain': domain,
'persona': persona_id})
common.client.createHost(hostname, iscsiNames=iqn,
optional={'domain': domain,
'persona': persona_id})
return hostname
def _modify_3par_iscsi_host(self, hostname, iscsi_iqn):
mod_request = {'pathOperation': self.common.client.HOST_EDIT_ADD,
def _modify_3par_iscsi_host(self, common, hostname, iscsi_iqn):
mod_request = {'pathOperation': common.client.HOST_EDIT_ADD,
'iSCSINames': [iscsi_iqn]}
self.common.client.modifyHost(hostname, mod_request)
common.client.modifyHost(hostname, mod_request)
def _set_3par_chaps(self, hostname, volume, username, password):
def _set_3par_chaps(self, common, hostname, volume, username, password):
"""Sets a 3PAR host's CHAP credentials."""
if not self.configuration.hp3par_iscsi_chap_enabled:
return
mod_request = {'chapOperation': self.common.client.HOST_EDIT_ADD,
'chapOperationMode': self.common.client.CHAP_INITIATOR,
mod_request = {'chapOperation': common.client.HOST_EDIT_ADD,
'chapOperationMode': common.client.CHAP_INITIATOR,
'chapName': username,
'chapSecret': password}
self.common.client.modifyHost(hostname, mod_request)
common.client.modifyHost(hostname, mod_request)
def _create_host(self, volume, connector):
def _create_host(self, common, volume, connector):
"""Creates or modifies existing 3PAR host."""
# make sure we don't have the host already
host = None
username = None
password = None
hostname = self.common._safe_hostname(connector['host'])
cpg = self.common.get_cpg(volume, allowSnap=True)
domain = self.common.get_domain(cpg)
hostname = common._safe_hostname(connector['host'])
cpg = common.get_cpg(volume, allowSnap=True)
domain = common.get_domain(cpg)
# Get the CHAP secret if CHAP is enabled
if self.configuration.hp3par_iscsi_chap_enabled:
vol_name = self.common._get_3par_vol_name(volume['id'])
username = self.common.client.getVolumeMetaData(
vol_name = common._get_3par_vol_name(volume['id'])
username = common.client.getVolumeMetaData(
vol_name, CHAP_USER_KEY)['value']
password = self.common.client.getVolumeMetaData(
password = common.client.getVolumeMetaData(
vol_name, CHAP_PASS_KEY)['value']
try:
host = self.common._get_3par_host(hostname)
host = common._get_3par_host(hostname)
except hpexceptions.HTTPNotFound:
# get persona from the volume type extra specs
persona_id = self.common.get_persona_type(volume)
persona_id = common.get_persona_type(volume)
# host doesn't exist, we have to create it
hostname = self._create_3par_iscsi_host(hostname,
hostname = self._create_3par_iscsi_host(common,
hostname,
connector['initiator'],
domain,
persona_id)
self._set_3par_chaps(hostname, volume, username, password)
host = self.common._get_3par_host(hostname)
self._set_3par_chaps(common, hostname, volume, username, password)
host = common._get_3par_host(hostname)
else:
if 'iSCSIPaths' not in host or len(host['iSCSIPaths']) < 1:
self._modify_3par_iscsi_host(hostname, connector['initiator'])
self._set_3par_chaps(hostname, volume, username, password)
host = self.common._get_3par_host(hostname)
self._modify_3par_iscsi_host(
common, hostname,
connector['initiator'])
self._set_3par_chaps(
common,
hostname,
volume,
username,
password)
host = common._get_3par_host(hostname)
elif (not host['initiatorChapEnabled'] and
self.configuration.hp3par_iscsi_chap_enabled):
LOG.warn(_("Host exists without CHAP credentials set and has "
"iSCSI attachments but CHAP is enabled. Updating "
"host with new CHAP credentials."))
self._set_3par_chaps(hostname, volume, username, password)
self._set_3par_chaps(
common,
hostname,
volume,
username,
password)
return host, username, password
def _do_export(self, volume):
def _do_export(self, common, volume):
"""Gets the associated account, generates CHAP info and updates."""
model_update = {}
@ -435,10 +455,10 @@ class HP3PARISCSIDriver(cinder.volume.driver.ISCSIDriver):
chap_password = None
try:
# Get all active VLUNs for the host
vluns = self.common.client.getHostVLUNs(chap_username)
vluns = common.client.getHostVLUNs(chap_username)
# Host has active VLUNs... is CHAP enabled on host?
host_info = self.common.client.getHost(chap_username)
host_info = common.client.getHost(chap_username)
if not host_info['initiatorChapEnabled']:
LOG.warn(_("Host has no CHAP key, but CHAP is enabled."))
@ -464,7 +484,7 @@ class HP3PARISCSIDriver(cinder.volume.driver.ISCSIDriver):
if ('remoteName' in vlun and
re.match('iqn.*', vlun['remoteName'])):
try:
chap_password = self.common.client.getVolumeMetaData(
chap_password = common.client.getVolumeMetaData(
vlun['volumeName'], CHAP_PASS_KEY)['value']
chap_exists = True
break
@ -481,10 +501,10 @@ class HP3PARISCSIDriver(cinder.volume.driver.ISCSIDriver):
"Generating new CHAP key."))
# Add CHAP credentials to the volume metadata
vol_name = self.common._get_3par_vol_name(volume['id'])
self.common.client.setVolumeMetaData(
vol_name = common._get_3par_vol_name(volume['id'])
common.client.setVolumeMetaData(
vol_name, CHAP_USER_KEY, chap_username)
self.common.client.setVolumeMetaData(
common.client.setVolumeMetaData(
vol_name, CHAP_PASS_KEY, chap_password)
model_update['provider_auth'] = ('CHAP %s %s' %
@ -492,28 +512,26 @@ class HP3PARISCSIDriver(cinder.volume.driver.ISCSIDriver):
return model_update
@utils.synchronized('3par', external=True)
def create_export(self, context, volume):
common = self._login()
try:
self.common.client_login()
return self._do_export(volume)
return self._do_export(common, volume)
finally:
self.common.client_logout()
self._logout(common)
@utils.synchronized('3par', external=True)
def ensure_export(self, context, volume):
"""Ensure the volume still exists on the 3PAR.
Also retrieves CHAP credentials, if present on the volume
"""
common = self._login()
try:
self.common.client_login()
vol_name = self.common._get_3par_vol_name(volume['id'])
self.common.client.getVolume(vol_name)
vol_name = common._get_3par_vol_name(volume['id'])
common.client.getVolume(vol_name)
except hpexceptions.HTTPNotFound:
LOG.error(_LE("Volume %s doesn't exist on array.") % vol_name)
else:
metadata = self.common.client.getAllVolumeMetaData(vol_name)
metadata = common.client.getAllVolumeMetaData(vol_name)
username = None
password = None
@ -532,13 +550,12 @@ class HP3PARISCSIDriver(cinder.volume.driver.ISCSIDriver):
return model_update
finally:
self.common.client_logout()
self._logout(common)
@utils.synchronized('3par', external=True)
def remove_export(self, context, volume):
pass
def _get_least_used_nsp_for_host(self, hostname):
def _get_least_used_nsp_for_host(self, common, hostname):
"""Get the least used NSP for the current host.
Steps to determine which NSP to use.
@ -553,17 +570,18 @@ class HP3PARISCSIDriver(cinder.volume.driver.ISCSIDriver):
return iscsi_nsps[0]
# Try to reuse an existing iscsi path to the host
vluns = self.common.client.getVLUNs()
vluns = common.client.getVLUNs()
for vlun in vluns['members']:
if vlun['active']:
if vlun['hostname'] == hostname:
temp_nsp = self.common.build_nsp(vlun['portPos'])
temp_nsp = common.build_nsp(vlun['portPos'])
if temp_nsp in iscsi_nsps:
# this host already has an iscsi path, so use it
return temp_nsp
# Calculate the least used iscsi nsp
least_used_nsp = self._get_least_used_nsp(vluns['members'],
least_used_nsp = self._get_least_used_nsp(common,
vluns['members'],
self._get_iscsi_nsps())
return least_used_nsp
@ -580,7 +598,7 @@ class HP3PARISCSIDriver(cinder.volume.driver.ISCSIDriver):
if value['nsp'] == nsp:
return key
def _get_least_used_nsp(self, vluns, nspss):
def _get_least_used_nsp(self, common, vluns, nspss):
""""Return the nsp that has the fewest active vluns."""
# return only the nsp (node:server:port)
# count the number of nsps
@ -593,7 +611,7 @@ class HP3PARISCSIDriver(cinder.volume.driver.ISCSIDriver):
for vlun in vluns:
if vlun['active']:
nsp = self.common.build_nsp(vlun['portPos'])
nsp = common.build_nsp(vlun['portPos'])
if nsp in nsp_counts:
nsp_counts[nsp] = nsp_counts[nsp] + 1
@ -606,69 +624,58 @@ class HP3PARISCSIDriver(cinder.volume.driver.ISCSIDriver):
return current_least_used_nsp
@utils.synchronized('3par', external=True)
def extend_volume(self, volume, new_size):
self.common.client_login()
common = self._login()
try:
self.common.extend_volume(volume, new_size)
common.extend_volume(volume, new_size)
finally:
self.common.client_logout()
self._logout(common)
@utils.synchronized('3par', external=True)
def manage_existing(self, volume, existing_ref):
self.common.client_login()
common = self._login()
try:
return self.common.manage_existing(volume, existing_ref)
return common.manage_existing(volume, existing_ref)
finally:
self.common.client_logout()
self._logout(common)
@utils.synchronized('3par', external=True)
def manage_existing_get_size(self, volume, existing_ref):
self.common.client_login()
common = self._login()
try:
size = self.common.manage_existing_get_size(volume, existing_ref)
return common.manage_existing_get_size(volume, existing_ref)
finally:
self.common.client_logout()
self._logout(common)
return size
@utils.synchronized('3par', external=True)
def unmanage(self, volume):
self.common.client_login()
common = self._login()
try:
self.common.unmanage(volume)
common.unmanage(volume)
finally:
self.common.client_logout()
self._logout(common)
@utils.synchronized('3par', external=True)
def attach_volume(self, context, volume, instance_uuid, host_name,
mountpoint):
self.common.client_login()
common = self._login()
try:
self.common.attach_volume(volume, instance_uuid)
common.attach_volume(volume, instance_uuid)
finally:
self.common.client_logout()
self._logout(common)
@utils.synchronized('3par', external=True)
def detach_volume(self, context, volume):
self.common.client_login()
common = self._login()
try:
self.common.detach_volume(volume)
common.detach_volume(volume)
finally:
self.common.client_logout()
self._logout(common)
@utils.synchronized('3par', external=True)
def retype(self, context, volume, new_type, diff, host):
"""Convert the volume to be of the new type."""
self.common.client_login()
common = self._login()
try:
return self.common.retype(volume, new_type, diff, host)
return common.retype(volume, new_type, diff, host)
finally:
self.common.client_logout()
self._logout(common)
@utils.synchronized('3par', external=True)
def migrate_volume(self, context, volume, host):
if volume['status'] == 'in-use':
protocol = host['capabilities']['storage_protocol']
if protocol != 'iSCSI':
@ -676,19 +683,19 @@ class HP3PARISCSIDriver(cinder.volume.driver.ISCSIDriver):
"to a host with storage_protocol=%s." % protocol)
return False, None
self.common.client_login()
common = self._login()
try:
return self.common.migrate_volume(volume, host)
return common.migrate_volume(volume, host)
finally:
self.common.client_logout()
self._logout(common)
def get_pool(self, volume):
self.common.client_login()
common = self._login()
try:
return self.common.get_cpg(volume)
return common.get_cpg(volume)
except hpexceptions.HTTPNotFound:
reason = (_("Volume %s doesn't exist on array.") % volume)
LOG.error(reason)
raise exception.InvalidVolume(reason)
finally:
self.common.client_logout()
self._logout(common)