Fixes 3PAR Host already exists error.
Fixed “Host already exists” error by, * Screen scraping error message and extracting the host name used by the 3PAR backend. * Cache the host name used by the 3PAR backend for subsequent methods using host name. * After a restart, and 3PAR host name it no longer available, retrieve 3PAR host name using wwn/iqn, if a terminate_connection fails. Fixes bug 1182134 Change-Id: Ia08a311af168ae19dbe7c1405db9c606fd878e53
This commit is contained in:
parent
1df6516dce
commit
9b6dc3dc58
@ -496,6 +496,7 @@ class TestHP3PARFCDriver(HP3PARBaseDriver, test.TestCase):
|
||||
'target_lun': 186,
|
||||
'target_portal': '1.1.1.2:1234'},
|
||||
'driver_volume_type': 'fibre_channel'}
|
||||
return hostname
|
||||
|
||||
def test_create_volume(self):
|
||||
self.flags(lock_path=self.tempdir)
|
||||
@ -602,14 +603,17 @@ class TestHP3PARFCDriver(HP3PARBaseDriver, test.TestCase):
|
||||
|
||||
create_host_cmd = ('createhost -persona 1 -domain (\'OpenStack\',) '
|
||||
'fakehost 123456789012345 123456789054321')
|
||||
create_host_ret = pack(CLI_CR + 'already used by host fakehost.foo ')
|
||||
create_host_ret = pack(CLI_CR +
|
||||
'already used by host fakehost.foo (19)')
|
||||
_run_ssh(create_host_cmd, False).AndReturn([create_host_ret, ''])
|
||||
|
||||
show_3par_cmd = 'showhost -verbose fakehost.foo'
|
||||
_run_ssh(show_3par_cmd, False).AndReturn([pack(FC_SHOWHOST_RET), ''])
|
||||
self.mox.ReplayAll()
|
||||
|
||||
self.assertRaises(exception.Duplicate3PARHost,
|
||||
self.driver._create_host,
|
||||
self.volume,
|
||||
self.connector)
|
||||
host = self.driver._create_host(self.volume, self.connector)
|
||||
|
||||
self.assertEquals(host['name'], 'fakehost.foo')
|
||||
|
||||
def test_create_modify_host(self):
|
||||
self.flags(lock_path=self.tempdir)
|
||||
@ -714,6 +718,7 @@ class TestHP3PARISCSIDriver(HP3PARBaseDriver, test.TestCase):
|
||||
'id': 11,
|
||||
'name': hostname}
|
||||
self._hosts[hostname] = host
|
||||
return hostname
|
||||
|
||||
def test_create_volume(self):
|
||||
self.flags(lock_path=self.tempdir)
|
||||
@ -830,12 +835,14 @@ class TestHP3PARISCSIDriver(HP3PARBaseDriver, test.TestCase):
|
||||
'fakehost iqn.1993-08.org.debian:01:222')
|
||||
in_use_ret = pack('\r\nalready used by host fakehost.foo ')
|
||||
_run_ssh(create_host_cmd, False).AndReturn([in_use_ret, ''])
|
||||
|
||||
show_3par_cmd = 'showhost -verbose fakehost.foo'
|
||||
_run_ssh(show_3par_cmd, False).AndReturn([pack(ISCSI_3PAR_RET), ''])
|
||||
self.mox.ReplayAll()
|
||||
|
||||
self.assertRaises(exception.Duplicate3PARHost,
|
||||
self.driver._create_host,
|
||||
self.volume,
|
||||
self.connector)
|
||||
host = self.driver._create_host(self.volume, self.connector)
|
||||
|
||||
self.assertEquals(host['name'], 'fakehost.foo')
|
||||
|
||||
def test_create_modify_host(self):
|
||||
self.flags(lock_path=self.tempdir)
|
||||
@ -934,6 +941,27 @@ FC_HOST_RET = (
|
||||
'Contact : --\r\n'
|
||||
'Comment : -- \r\n\r\n\r\n')
|
||||
|
||||
FC_SHOWHOST_RET = (
|
||||
'Id,Name,Persona,-WWN/iSCSI_Name-,Port,IP_addr\r\n'
|
||||
'75,fakehost.foo,Generic,50014380242B8B4C,0:2:1,n/a\r\n'
|
||||
'75,fakehost.foo,Generic,50014380242B8B4E,---,n/a\r\n'
|
||||
'75,fakehost.foo,Generic,1000843497F90711,0:2:1,n/a \r\n'
|
||||
'75,fakehost.foo,Generic,1000843497F90715,1:2:1,n/a\r\n'
|
||||
'\r\n'
|
||||
'Id,Name,-Initiator_CHAP_Name-,-Target_CHAP_Name-\r\n'
|
||||
'75,fakehost.foo,--,--\r\n'
|
||||
'\r\n'
|
||||
'---------- Host fakehost.foo ----------\r\n'
|
||||
'Name : fakehost.foo\r\n'
|
||||
'Domain : FAKE_TEST\r\n'
|
||||
'Id : 75\r\n'
|
||||
'Location : --\r\n'
|
||||
'IP Address : --\r\n'
|
||||
'OS : --\r\n'
|
||||
'Model : --\r\n'
|
||||
'Contact : --\r\n'
|
||||
'Comment : -- \r\n\r\n\r\n')
|
||||
|
||||
NO_FC_HOST_RET = (
|
||||
'Id,Name,Persona,-WWN/iSCSI_Name-,Port,IP_addr\r\n'
|
||||
'\r\n'
|
||||
@ -1042,3 +1070,22 @@ ISCSI_PORT_RET = (
|
||||
'1:8:1,ready,10.10.220.253,255.255.224.0,0.0.0.0,181,1500,10Gbps,'
|
||||
'0,0.0.0.0,3205\r\n'
|
||||
'1:8:2,loss_sync,0.0.0.0,0.0.0.0,0.0.0.0,182,1500,n/a,0,0.0.0.0,3205\r\n')
|
||||
|
||||
ISCSI_3PAR_RET = (
|
||||
'Id,Name,Persona,-WWN/iSCSI_Name-,Port,IP_addr\r\n'
|
||||
'75,fakehost.foo,Generic,iqn.1993-08.org.debian:01:222,---,'
|
||||
'10.10.222.12\r\n'
|
||||
'\r\n'
|
||||
'Id,Name,-Initiator_CHAP_Name-,-Target_CHAP_Name-\r\n'
|
||||
'75,fakehost.foo,--,--\r\n'
|
||||
'\r\n'
|
||||
'---------- Host fakehost.foo ----------\r\n'
|
||||
'Name : fakehost.foo\r\n'
|
||||
'Domain : FAKE_TEST\r\n'
|
||||
'Id : 75\r\n'
|
||||
'Location : --\r\n'
|
||||
'IP Address : --\r\n'
|
||||
'OS : --\r\n'
|
||||
'Model : --\r\n'
|
||||
'Contact : --\r\n'
|
||||
'Comment : -- \r\n\r\n\r\n')
|
||||
|
@ -41,6 +41,7 @@ import json
|
||||
import paramiko
|
||||
import pprint
|
||||
from random import randint
|
||||
import re
|
||||
import time
|
||||
import uuid
|
||||
|
||||
@ -111,6 +112,7 @@ class HP3PARCommon():
|
||||
def __init__(self, config):
|
||||
self.sshpool = None
|
||||
self.config = config
|
||||
self.hosts_naming_dict = dict()
|
||||
|
||||
def check_flags(self, options, required_flags):
|
||||
for flag in required_flags:
|
||||
@ -459,9 +461,7 @@ exit
|
||||
self._create_3par_vlun(volume_name, host['name'])
|
||||
return client.getVLUN(volume_name)
|
||||
|
||||
def delete_vlun(self, volume, connector, client):
|
||||
hostname = self._safe_hostname(connector['host'])
|
||||
|
||||
def delete_vlun(self, volume, hostname, client):
|
||||
volume_name = self._get_3par_vol_name(volume['id'])
|
||||
vlun = client.getVLUN(volume_name)
|
||||
client.deleteVLUN(volume_name, vlun['lun'], hostname)
|
||||
@ -595,6 +595,16 @@ exit
|
||||
|
||||
return status
|
||||
|
||||
def get_next_word(self, s, search_string):
|
||||
"""Return the next word.
|
||||
|
||||
Search 's' for 'search_string', if found
|
||||
return the word preceding 'search_string'
|
||||
from 's'.
|
||||
"""
|
||||
word = re.search(search_string.strip(' ') + ' ([^ ]*)', s)
|
||||
return word.groups()[0].strip(' ')
|
||||
|
||||
@utils.synchronized('3parclone', external=True)
|
||||
def create_cloned_volume(self, volume, src_vref, client):
|
||||
|
||||
@ -740,3 +750,47 @@ exit
|
||||
raise exception.NotAuthorized()
|
||||
except hpexceptions.HTTPNotFound as ex:
|
||||
LOG.error(str(ex))
|
||||
|
||||
def _get_3par_hostname_from_wwn_iqn(self, wwns_iqn):
|
||||
out = self._cli_run('showhost -d', None)
|
||||
# wwns_iqn may be a list of strings or a single
|
||||
# string. So, if necessary, create a list to loop.
|
||||
if not isinstance(wwns_iqn, list):
|
||||
wwn_iqn_list = [wwns_iqn]
|
||||
else:
|
||||
wwn_iqn_list = wwns_iqn
|
||||
|
||||
for wwn_iqn in wwn_iqn_list:
|
||||
for showhost in out:
|
||||
if (wwn_iqn.upper() in showhost.upper()):
|
||||
return showhost.split(',')[1]
|
||||
|
||||
def terminate_connection(self, volume, hostname, wwn_iqn, client):
|
||||
""" Driver entry point to unattach a volume from an instance."""
|
||||
try:
|
||||
# does 3par know this host by a different name?
|
||||
if hostname in self.hosts_naming_dict:
|
||||
hostname = self.hosts_naming_dict.get(hostname)
|
||||
self.delete_vlun(volume, hostname, client)
|
||||
return
|
||||
except hpexceptions.HTTPNotFound as e:
|
||||
if 'host does not exist' in e.get_description():
|
||||
# use the wwn to see if we can find the hostname
|
||||
hostname = self._get_3par_hostname_from_wwn_iqn(wwn_iqn)
|
||||
# no 3par host, re-throw
|
||||
if (hostname == None):
|
||||
raise
|
||||
else:
|
||||
# not a 'host does not exist' HTTPNotFound exception, re-throw
|
||||
raise
|
||||
|
||||
#try again with name retrieved from 3par
|
||||
self.delete_vlun(volume, hostname, client)
|
||||
|
||||
def parse_create_host_error(self, hostname, out):
|
||||
search_str = "already used by host "
|
||||
if search_str in out[1]:
|
||||
#host exists, return name used by 3par
|
||||
hostname_3par = self.get_next_word(out[1], search_str)
|
||||
self.hosts_naming_dict[hostname] = hostname_3par
|
||||
return hostname_3par
|
||||
|
@ -202,21 +202,26 @@ must be the same" % (cpg['domain'], self.configuration.hp3par_domain)
|
||||
|
||||
@utils.synchronized('3par-attach', external=True)
|
||||
def terminate_connection(self, volume, connector, force):
|
||||
"""
|
||||
Driver entry point to unattach a volume from an instance.
|
||||
"""
|
||||
self.common.delete_vlun(volume, connector, self.client)
|
||||
pass
|
||||
"""Driver entry point to unattach a volume from an instance."""
|
||||
self.common.terminate_connection(volume,
|
||||
connector['host'],
|
||||
connector['wwpns'],
|
||||
self.client)
|
||||
|
||||
def _create_3par_fibrechan_host(self, hostname, wwn, domain, persona_id):
|
||||
"""Create a 3PAR host.
|
||||
|
||||
Create a 3PAR host, if there is already a host on the 3par using
|
||||
the same wwn but with a different hostname, return the hostname
|
||||
used by 3PAR.
|
||||
"""
|
||||
out = self.common._cli_run('createhost -persona %s -domain %s %s %s'
|
||||
% (persona_id, domain,
|
||||
hostname, " ".join(wwn)), None)
|
||||
if out and len(out) > 1:
|
||||
if "already used by host" in out[1]:
|
||||
err = out[1].strip()
|
||||
info = _("The hostname must be called '%s'") % hostname
|
||||
raise exception.Duplicate3PARHost(err=err, info=info)
|
||||
return self.common.parse_create_host_error(hostname, out)
|
||||
|
||||
return hostname
|
||||
|
||||
def _modify_3par_fibrechan_host(self, hostname, wwn):
|
||||
# when using -add, you can not send the persona or domain options
|
||||
@ -224,10 +229,7 @@ must be the same" % (cpg['domain'], self.configuration.hp3par_domain)
|
||||
% (hostname, " ".join(wwn)), None)
|
||||
|
||||
def _create_host(self, volume, connector):
|
||||
"""
|
||||
This is a 3PAR host entry for exporting volumes
|
||||
via active VLUNs.
|
||||
"""
|
||||
"""Creates or modifies existing 3PAR host."""
|
||||
host = None
|
||||
hostname = self.common._safe_hostname(connector['host'])
|
||||
try:
|
||||
@ -239,9 +241,11 @@ must be the same" % (cpg['domain'], self.configuration.hp3par_domain)
|
||||
# get persona from the volume type extra specs
|
||||
persona_id = self.common.get_persona_type(volume)
|
||||
# host doesn't exist, we have to create it
|
||||
self._create_3par_fibrechan_host(hostname, connector['wwpns'],
|
||||
self.configuration.hp3par_domain,
|
||||
persona_id)
|
||||
hostname = self._create_3par_fibrechan_host(hostname,
|
||||
connector['wwpns'],
|
||||
self.configuration.
|
||||
hp3par_domain,
|
||||
persona_id)
|
||||
host = self.common._get_3par_host(hostname)
|
||||
|
||||
return host
|
||||
|
@ -207,10 +207,11 @@ must be the same" % (cpg['domain'], self.configuration.hp3par_domain)
|
||||
|
||||
@utils.synchronized('3par-attach', external=True)
|
||||
def terminate_connection(self, volume, connector, force):
|
||||
"""
|
||||
Driver entry point to unattach a volume from an instance.
|
||||
"""
|
||||
self.common.delete_vlun(volume, connector, self.client)
|
||||
"""Driver entry point to unattach a volume from an instance."""
|
||||
self.common.terminate_connection(volume,
|
||||
connector['host'],
|
||||
connector['initiator'],
|
||||
self.client)
|
||||
|
||||
def _iscsi_discover_target_iqn(self, remote_ip):
|
||||
result = self.common._cli_run('showport -ids', None)
|
||||
@ -228,14 +229,18 @@ must be the same" % (cpg['domain'], self.configuration.hp3par_domain)
|
||||
return iqn
|
||||
|
||||
def _create_3par_iscsi_host(self, hostname, iscsi_iqn, domain, persona_id):
|
||||
"""Create a 3PAR host.
|
||||
|
||||
Create a 3PAR host, if there is already a host on the 3par using
|
||||
the same iqn but with a different hostname, return the hostname
|
||||
used by 3PAR.
|
||||
"""
|
||||
cmd = 'createhost -iscsi -persona %s -domain %s %s %s' % \
|
||||
(persona_id, domain, hostname, iscsi_iqn)
|
||||
out = self.common._cli_run(cmd, None)
|
||||
if out and len(out) > 1:
|
||||
if "already used by host" in out[1]:
|
||||
err = out[1].strip()
|
||||
info = _("The hostname must be called '%s'") % hostname
|
||||
raise exception.Duplicate3PARHost(err=err, info=info)
|
||||
return self.common.parse_create_host_error(hostname, out)
|
||||
return hostname
|
||||
|
||||
def _modify_3par_iscsi_host(self, hostname, iscsi_iqn):
|
||||
# when using -add, you can not send the persona or domain options
|
||||
@ -243,10 +248,7 @@ must be the same" % (cpg['domain'], self.configuration.hp3par_domain)
|
||||
% (hostname, iscsi_iqn), None)
|
||||
|
||||
def _create_host(self, volume, connector):
|
||||
"""
|
||||
This is a 3PAR host entry for exporting volumes
|
||||
via active VLUNs.
|
||||
"""
|
||||
"""Creates or modifies existing 3PAR host."""
|
||||
# make sure we don't have the host already
|
||||
host = None
|
||||
hostname = self.common._safe_hostname(connector['host'])
|
||||
@ -259,9 +261,11 @@ must be the same" % (cpg['domain'], self.configuration.hp3par_domain)
|
||||
# get persona from the volume type extra specs
|
||||
persona_id = self.common.get_persona_type(volume)
|
||||
# host doesn't exist, we have to create it
|
||||
self._create_3par_iscsi_host(hostname, connector['initiator'],
|
||||
self.configuration.hp3par_domain,
|
||||
persona_id)
|
||||
hostname = self._create_3par_iscsi_host(hostname,
|
||||
connector['initiator'],
|
||||
self.configuration.
|
||||
hp3par_domain,
|
||||
persona_id)
|
||||
host = self.common._get_3par_host(hostname)
|
||||
|
||||
return host
|
||||
|
Loading…
Reference in New Issue
Block a user