]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
Fixed nova VM live migration issue with 3PAR
authorWalter A. Boring IV <walter.boring@hp.com>
Thu, 6 Mar 2014 19:52:33 +0000 (11:52 -0800)
committerWalter A. Boring IV <walter.boring@hp.com>
Tue, 11 Mar 2014 17:41:13 +0000 (10:41 -0700)
Nova bypasses the cinder checks for a volume
being available, when it tries to attach a volume
to a new host during live migration.  The assumption
in cinder to this point has been that volumes can only
be attached to one host.  The 3PAR driver worked under
that assumption.  This assumption fell apart during detach
time as the driver was only looking for a VLUN on the
entire 3PAR, since it assumed it could only exist on one host.

This patch ensures that the driver looks for the VLUN on the
hostname it expects.

Change-Id: Ie894ad386990794d270ca1cb72f40095bd40c2e6
Closes-Bug: 1288927

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

index 9990d4a5fe08a117d4a0591f623d9ff8e0ff096c..d8f38273a7b39a991272451ff66e6f83a2c6d64e 100644 (file)
@@ -701,7 +701,10 @@ class HP3PARBaseDriver(object):
         # setup_mock_client drive with default configuration
         # and return the mock HTTP 3PAR client
         mock_client = self.setup_driver()
-        mock_client.getVLUN.return_value = {'lun': None, 'type': 0}
+        mock_client.getHostVLUNs.return_value = [
+            {'active': True,
+             'volumeName': self.VOLUME_3PAR_NAME,
+             'lun': None, 'type': 0}]
 
         self.driver.terminate_connection(
             self.volume,
@@ -710,7 +713,7 @@ class HP3PARBaseDriver(object):
 
         expected = [
             mock.call.login(HP3PAR_USER_NAME, HP3PAR_USER_PASS),
-            mock.call.getVLUN(self.VOLUME_3PAR_NAME),
+            mock.call.getHostVLUNs(self.FAKE_HOST),
             mock.call.deleteVLUN(
                 self.VOLUME_3PAR_NAME,
                 None,
@@ -986,7 +989,10 @@ class TestHP3PARFCDriver(HP3PARBaseDriver, test.TestCase):
                              'vendor': None,
                              'wwn': self.wwn[1]}]}]
         mock_client.findHost.return_value = self.FAKE_HOST
-        mock_client.getVLUN.return_value = {'lun': 90}
+        mock_client.getHostVLUNs.return_value = [
+            {'active': True,
+             'volumeName': self.VOLUME_3PAR_NAME,
+             'lun': 90, 'type': 0}]
         mock_client.getPorts.return_value = {
             'members': self.FAKE_FC_PORTS + [self.FAKE_ISCSI_PORT]}
 
@@ -994,16 +1000,16 @@ class TestHP3PARFCDriver(HP3PARBaseDriver, test.TestCase):
 
         expected = [
             mock.call.login(HP3PAR_USER_NAME, HP3PAR_USER_PASS),
-            mock.call.getVolume('osv-0DM4qZEVSKON-DXN-NwVpw'),
+            mock.call.getVolume(self.VOLUME_3PAR_NAME),
             mock.call.getCPG(HP3PAR_CPG),
             mock.call.getHost(self.FAKE_HOST),
             mock.ANY,
             mock.call.getHost(self.FAKE_HOST),
             mock.call.createVLUN(
-                'osv-0DM4qZEVSKON-DXN-NwVpw',
+                self.VOLUME_3PAR_NAME,
                 auto=True,
                 hostname=self.FAKE_HOST),
-            mock.call.getVLUN('osv-0DM4qZEVSKON-DXN-NwVpw'),
+            mock.call.getHostVLUNs(self.FAKE_HOST),
             mock.call.getPorts(),
             mock.call.logout()]
 
@@ -1015,7 +1021,10 @@ class TestHP3PARFCDriver(HP3PARBaseDriver, test.TestCase):
         # setup_mock_client drive with default configuration
         # and return the mock HTTP 3PAR client
         mock_client = self.setup_driver()
-        mock_client.getVLUN.return_value = {'lun': None, 'type': 0}
+        mock_client.getHostVLUNs.return_value = [
+            {'active': True,
+             'volumeName': self.VOLUME_3PAR_NAME,
+             'lun': None, 'type': 0}]
         mock_client.getPorts.return_value = {
             'members': self.FAKE_FC_PORTS + [self.FAKE_ISCSI_PORT]}
 
@@ -1026,7 +1035,7 @@ class TestHP3PARFCDriver(HP3PARBaseDriver, test.TestCase):
 
         expected = [
             mock.call.login(HP3PAR_USER_NAME, HP3PAR_USER_PASS),
-            mock.call.getVLUN(self.VOLUME_3PAR_NAME),
+            mock.call.getHostVLUNs(self.FAKE_HOST),
             mock.call.deleteVLUN(
                 self.VOLUME_3PAR_NAME,
                 None,
@@ -1304,23 +1313,26 @@ class TestHP3PARISCSIDriver(HP3PARBaseDriver, test.TestCase):
             hpexceptions.HTTPNotFound('fake'),
             {'name': self.FAKE_HOST}]
         mock_client.findHost.return_value = self.FAKE_HOST
-        mock_client.getVLUN.return_value = {'lun': self.TARGET_LUN}
+        mock_client.getHostVLUNs.return_value = [
+            {'active': True,
+             'volumeName': self.VOLUME_3PAR_NAME,
+             'lun': self.TARGET_LUN, 'type': 0}]
 
         result = self.driver.initialize_connection(self.volume, self.connector)
 
         expected = [
             mock.call.login(HP3PAR_USER_NAME, HP3PAR_USER_PASS),
-            mock.call.getVolume('osv-0DM4qZEVSKON-DXN-NwVpw'),
+            mock.call.getVolume(self.VOLUME_3PAR_NAME),
             mock.call.getCPG(HP3PAR_CPG),
             mock.call.getHost(self.FAKE_HOST),
             mock.call.findHost(iqn='iqn.1993-08.org.debian:01:222'),
             mock.call.getHost(self.FAKE_HOST),
             mock.call.createVLUN(
-                'osv-0DM4qZEVSKON-DXN-NwVpw',
+                self.VOLUME_3PAR_NAME,
                 auto=True,
                 hostname='fakehost',
                 portPos={'node': 8, 'slot': 1, 'cardPort': 1}),
-            mock.call.getVLUN('osv-0DM4qZEVSKON-DXN-NwVpw'),
+            mock.call.getHostVLUNs(self.FAKE_HOST),
             mock.call.logout()]
 
         mock_client.assert_has_calls(expected)
index 41010b30d04ae9d4e24273fae87c44ddac95f007..b392d9f2536c62ad1e048413edc892495bcf2098 100644 (file)
@@ -121,10 +121,11 @@ class HP3PARCommon(object):
         2.0.5 - Fix extend volume units bug #1284368
         2.0.6 - use loopingcall.wait instead of time.sleep
         2.0.7 - Allow extend volume based on snapshot bug #1285906
+        2.0.8 - Fix detach issue for multiple hosts Bug #1288927
 
     """
 
-    VERSION = "2.0.7"
+    VERSION = "2.0.8"
 
     stats = {}
 
@@ -450,6 +451,21 @@ class HP3PARCommon(object):
                                        'hp3par_cpg')})
         self.stats = stats
 
+    def _get_vlun(self, volume_name, hostname):
+        """find a VLUN on a 3PAR host."""
+        vluns = self.client.getHostVLUNs(hostname)
+        found_vlun = None
+        for vlun in vluns:
+            if volume_name in vlun['volumeName']:
+                found_vlun = vlun
+                break
+
+        msg = (_("3PAR vlun %(name)s not found on host %(host)s") %
+               {'name': volume_name, 'host': hostname})
+        if found_vlun is None:
+            LOG.warn(msg)
+        return found_vlun
+
     def create_vlun(self, volume, host, nsp=None):
         """Create a VLUN.
 
@@ -457,17 +473,19 @@ class HP3PARCommon(object):
         """
         volume_name = self._get_3par_vol_name(volume['id'])
         self._create_3par_vlun(volume_name, host['name'], nsp)
-        return self.client.getVLUN(volume_name)
+        return self._get_vlun(volume_name, host['name'])
 
     def delete_vlun(self, volume, hostname):
         volume_name = self._get_3par_vol_name(volume['id'])
-        vlun = self.client.getVLUN(volume_name)
-        # VLUN Type of MATCHED_SET 4 requires the port to be provided
-        if self.VLUN_TYPE_MATCHED_SET == vlun['type']:
-            self.client.deleteVLUN(volume_name, vlun['lun'], hostname,
-                                   vlun['portPos'])
-        else:
-            self.client.deleteVLUN(volume_name, vlun['lun'], hostname)
+        vlun = self._get_vlun(volume_name, hostname)
+
+        if vlun is not None:
+            # VLUN Type of MATCHED_SET 4 requires the port to be provided
+            if self.VLUN_TYPE_MATCHED_SET == vlun['type']:
+                self.client.deleteVLUN(volume_name, vlun['lun'], hostname,
+                                       vlun['portPos'])
+            else:
+                self.client.deleteVLUN(volume_name, vlun['lun'], hostname)
 
         try:
             self._delete_3par_host(hostname)