'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)
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)
'id': 11,
'name': hostname}
self._hosts[hostname] = host
+ return hostname
def test_create_volume(self):
self.flags(lock_path=self.tempdir)
'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)
'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'
'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')
import paramiko
import pprint
from random import randint
+import re
import time
import uuid
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:
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)
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):
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
@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
% (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:
# 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
@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)
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
% (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'])
# 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