]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
Fixes 3PAR Host already exists error.
authorJim Branen <james.branen@hp.com>
Fri, 24 May 2013 16:30:10 +0000 (09:30 -0700)
committerJim Branen <james.branen@hp.com>
Wed, 29 May 2013 16:23:11 +0000 (09:23 -0700)
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

cinder/tests/test_hp3par.py
cinder/volume/drivers/san/hp/hp_3par_common.py
cinder/volume/drivers/san/hp/hp_3par_fc.py
cinder/volume/drivers/san/hp/hp_3par_iscsi.py

index 86519290063657ea3ea0ebce1cfef7f8ba7c2ac2..1fa0b3efebd4aafdf1b1a60847599fa3dd897cef 100644 (file)
@@ -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')
index 9468904994c53e82f272d02192a4381bc07c33f0..abfb741351995a02187775d33e3be00b48529062 100644 (file)
@@ -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
index 7af686268fdcf019639e6e525b85d400fed8e5f0..0479e083af92d205cfae577ef9b646d1b0cffbd6 100644 (file)
@@ -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
index f1b1b83552a8008867e1edd50820477186a7429b..bf14c8791a32bafceaef1e579ce9243a4a1b0725 100644 (file)
@@ -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