From 2c1701e2f50feb5fa25ea6fb1b620f39346779de Mon Sep 17 00:00:00 2001 From: "Walter A. Boring IV" Date: Thu, 6 Mar 2014 11:52:33 -0800 Subject: [PATCH] Fixed nova VM live migration issue with 3PAR 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 | 36 ++++++++++++------- .../volume/drivers/san/hp/hp_3par_common.py | 36 ++++++++++++++----- 2 files changed, 51 insertions(+), 21 deletions(-) diff --git a/cinder/tests/test_hp3par.py b/cinder/tests/test_hp3par.py index 9990d4a5fe0..d8f38273a7b 100644 --- a/cinder/tests/test_hp3par.py +++ b/cinder/tests/test_hp3par.py @@ -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) diff --git a/cinder/volume/drivers/san/hp/hp_3par_common.py b/cinder/volume/drivers/san/hp/hp_3par_common.py index 41010b30d04..b392d9f2536 100644 --- a/cinder/volume/drivers/san/hp/hp_3par_common.py +++ b/cinder/volume/drivers/san/hp/hp_3par_common.py @@ -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)