From: Walter A. Boring IV Date: Thu, 6 Mar 2014 19:52:33 +0000 (-0800) Subject: Fixed nova VM live migration issue with 3PAR X-Git-Url: https://review.fuel-infra.org/gitweb?a=commitdiff_plain;h=2c1701e2f50feb5fa25ea6fb1b620f39346779de;p=openstack-build%2Fcinder-build.git 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 --- diff --git a/cinder/tests/test_hp3par.py b/cinder/tests/test_hp3par.py index 9990d4a5f..d8f38273a 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 41010b30d..b392d9f25 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)