Skip to content

Commit 80f849e

Browse files
peter-wangxuMurray-LIANG
authored andcommitted
Unity: Fix duplicate hosts created with same name (#10)
In some circumstance, there is a race condition in querying/creating Unity host thus multiple hosts with same name were created. This issue stops any further attach/detach operation. This patch leverage the DSL in cinder to avoid above race condition. Change-Id: I3b775da8c5e862def54e9d44d7b157cf17a07a24 Closes-bug: #1726284 (cherry picked from commit d8dd30f) (cherry picked from commit 85bc7559fb3b8c8ff3d508b3ef54bc4ba4a0591f) (cherry picked from commit af3e5f9)
1 parent 477b924 commit 80f849e

5 files changed

Lines changed: 71 additions & 40 deletions

File tree

cinder/tests/unit/volume/drivers/dell_emc/unity/test_adapter.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -116,11 +116,7 @@ def delete_snap(snap):
116116
raise ex.SnapDeleteIsCalled()
117117

118118
@staticmethod
119-
def create_host(name, uids):
120-
return test_client.MockResource(name=name)
121-
122-
@staticmethod
123-
def get_host(name):
119+
def create_host(name):
124120
return test_client.MockResource(name=name)
125121

126122
@staticmethod
@@ -169,6 +165,10 @@ def get_fc_ports():
169165
def get_ethernet_ports():
170166
return test_client.MockResourceList(ids=['spa_eth0', 'spb_eth0'])
171167

168+
@staticmethod
169+
def update_host_initiators(host, wwns):
170+
return None
171+
172172
@property
173173
def system(self):
174174
return self._system

cinder/tests/unit/volume/drivers/dell_emc/unity/test_client.py

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,16 @@
1212
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
1313
# License for the specific language governing permissions and limitations
1414
# under the License.
15-
1615
import unittest
1716

1817
from mock import mock
1918
from oslo_utils import units
2019

20+
from cinder import coordination
2121
from cinder.tests.unit.volume.drivers.dell_emc.unity \
2222
import fake_exception as ex
2323
from cinder.volume.drivers.dell_emc.unity import client
2424

25-
2625
########################
2726
#
2827
# Start of Mocks
@@ -47,6 +46,7 @@ def __init__(self, name=None, _id=None):
4746
self.max_iops = None
4847
self.max_kbps = None
4948
self.pool_name = 'Pool0'
49+
self.host_cache = []
5050

5151
@property
5252
def id(self):
@@ -228,6 +228,10 @@ def create_host(name):
228228
def get_host(name):
229229
if name == 'not_found':
230230
raise ex.UnityResourceNotFoundError()
231+
if name == 'host1':
232+
ret = MockResource(name)
233+
ret.initiator_id = ['old-iqn']
234+
return ret
231235
return MockResource(name)
232236

233237
@staticmethod
@@ -368,16 +372,18 @@ def test_get_snap_not_found(self):
368372
ret = self.client.get_snap('not_found')
369373
self.assertIsNone(ret)
370374

371-
def test_create_host_found(self):
372-
iqns = ['iqn.1-1.com.e:c.a.a0']
373-
host = self.client.create_host('host1', iqns)
375+
@mock.patch.object(coordination.Coordinator, 'get_lock')
376+
def test_create_host_found(self, fake_coordination):
377+
host = self.client.create_host('host1')
374378

375379
self.assertEqual('host1', host.name)
376380
self.assertLessEqual(['iqn.1-1.com.e:c.a.a0'], host.initiator_id)
377381

378-
def test_create_host_not_found(self):
379-
host = self.client.create_host('not_found', [])
382+
@mock.patch.object(coordination.Coordinator, 'get_lock')
383+
def test_create_host_not_found(self, fake):
384+
host = self.client.create_host('not_found')
380385
self.assertEqual('not_found', host.name)
386+
self.assertIn('not_found', self.client.host_cache)
381387

382388
def test_attach_lun(self):
383389
lun = MockResource(_id='lun1', name='l1')
@@ -398,8 +404,20 @@ def f():
398404

399405
self.assertRaises(ex.DetachIsCalled, f)
400406

401-
def test_get_host(self):
402-
self.assertEqual('host2', self.client.get_host('host2').name)
407+
@mock.patch.object(coordination.Coordinator, 'get_lock')
408+
def test_create_host(self, fake):
409+
self.assertEqual('host2', self.client.create_host('host2').name)
410+
411+
@mock.patch.object(coordination.Coordinator, 'get_lock')
412+
def test_create_host_in_cache(self, fake):
413+
self.client.host_cache['already_in'] = MockResource(name='already_in')
414+
host = self.client.create_host('already_in')
415+
self.assertIn('already_in', self.client.host_cache)
416+
self.assertEqual('already_in', host.name)
417+
418+
def test_update_host_initiators(self):
419+
host = MockResource(name='host_init')
420+
host = self.client.update_host_initiators(host, 'fake-iqn-1')
403421

404422
def test_get_iscsi_target_info(self):
405423
ret = self.client.get_iscsi_target_info()

cinder/volume/drivers/dell_emc/unity/adapter.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -201,8 +201,9 @@ def delete_volume(self, volume):
201201

202202
@cinder_utils.trace
203203
def _initialize_connection(self, lun_or_snap, connector, vol_id):
204-
host = self.client.create_host(connector['host'],
205-
self.get_connector_uids(connector))
204+
host = self.client.create_host(connector['host'])
205+
self.client.update_host_initiators(
206+
host, self.get_connector_uids(connector))
206207
hlu = self.client.attach(host, lun_or_snap)
207208
data = self.get_connection_info(hlu, host, connector)
208209
data['target_discovered'] = True
@@ -222,7 +223,7 @@ def initialize_connection(self, volume, connector):
222223

223224
@cinder_utils.trace
224225
def _terminate_connection(self, lun_or_snap, connector):
225-
host = self.client.get_host(connector['host'])
226+
host = self.client.create_host(connector['host'])
226227
self.client.detach(host, lun_or_snap)
227228

228229
@cinder_utils.trace
@@ -593,7 +594,7 @@ def _terminate_connection(self, lun_or_snap, connector):
593594
'driver_volume_type': self.driver_volume_type,
594595
'data': {}
595596
}
596-
host = self.client.get_host(connector['host'])
597+
host = self.client.create_host(connector['host'])
597598
if len(host.host_luns) == 0:
598599
targets = self.client.get_fc_target_info(
599600
logged_in_only=True, allowed_ports=self.allowed_ports)

cinder/volume/drivers/dell_emc/unity/client.py

Lines changed: 31 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,11 @@
2424
# Set storops_ex to be None for unit test
2525
storops_ex = None
2626

27+
from cinder import coordination
2728
from cinder import exception
2829
from cinder.i18n import _, _LW
2930
from cinder.volume.drivers.dell_emc.unity import utils
3031

31-
3232
LOG = log.getLogger(__name__)
3333

3434

@@ -43,6 +43,7 @@ def __init__(self, host, username, password, verify_cert=True):
4343
self.username = username
4444
self.password = password
4545
self.verify_cert = verify_cert
46+
self.host_cache = {}
4647

4748
@property
4849
def system(self):
@@ -171,28 +172,40 @@ def get_snap(self, name=None):
171172
LOG.warning(msg, {'name': name, 'err': err})
172173
return None
173174

174-
def create_host(self, name, uids):
175-
"""Creates a host on Unity.
176-
177-
Creates a host on Unity which has the uids associated.
178-
179-
:param name: name of the host
180-
:param uids: iqns or wwns list
181-
:return: UnitHost object
182-
"""
175+
@coordination.synchronized('{self.host}-{name}')
176+
def create_host(self, name):
177+
"""Provides existing host if exists else create one."""
178+
if name not in self.host_cache:
179+
try:
180+
host = self.system.get_host(name=name)
181+
except storops_ex.UnityResourceNotFoundError:
182+
LOG.debug('Host %s not found. Create a new one.',
183+
name)
184+
host = self.system.create_host(name=name)
183185

184-
try:
185-
host = self.system.get_host(name=name)
186-
except storops_ex.UnityResourceNotFoundError:
187-
LOG.debug('Existing host %s not found. Create a new one.', name)
188-
host = self.system.create_host(name=name)
186+
self.host_cache[name] = host
187+
else:
188+
host = self.host_cache[name]
189+
return host
189190

191+
def update_host_initiators(self, host, uids):
192+
"""Updates host with the supplied uids."""
190193
host_initiators_ids = self.get_host_initiator_ids(host)
191194
un_registered = [h for h in uids if h not in host_initiators_ids]
192-
for uid in un_registered:
193-
host.add_initiator(uid, force_create=True)
195+
if un_registered:
196+
for uid in un_registered:
197+
try:
198+
host.add_initiator(uid, force_create=True)
199+
except storops_ex.UnityHostInitiatorExistedError:
200+
# This make concurrent modification of
201+
# host initiators safe
202+
LOG.debug(
203+
'The uid(%s) was already in '
204+
'%s.', uid, host.name)
205+
host.update()
206+
# Update host cached with new initiators.
207+
self.host_cache[host.name] = host
194208

195-
host.update()
196209
return host
197210

198211
@staticmethod
@@ -226,9 +239,6 @@ def detach(host, lun_or_snap):
226239
lun_or_snap.update()
227240
host.detach(lun_or_snap)
228241

229-
def get_host(self, name):
230-
return self.system.get_host(name=name)
231-
232242
def get_ethernet_ports(self):
233243
return self.system.get_ethernet_port()
234244

cinder/volume/drivers/dell_emc/unity/driver.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,13 @@ class UnityDriver(driver.TransferVD,
5353
"""Unity Driver.
5454
5555
Version history:
56+
00.04.04 - Fix duplicate hosts created with same name (cherry-pick from
57+
downstream Newton
5658
00.04.03 - Add TransferVD to base, and fix version number
5759
00.04.02 - Initial version
5860
"""
5961

60-
VERSION = '00.04.03'
62+
VERSION = '00.04.04'
6163
VENDOR = 'Dell EMC'
6264
# ThirdPartySystems wiki page
6365
CI_WIKI_NAME = "EMC_UNITY_CI"

0 commit comments

Comments
 (0)