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
This commit is contained in:
Walter A. Boring IV 2014-03-06 11:52:33 -08:00
parent 015555acb7
commit 2c1701e2f5
2 changed files with 51 additions and 21 deletions

View 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)

View 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)