Skip to content

Commit af3e5f9

Browse files
authored
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)
1 parent 9678a78 commit af3e5f9

4 files changed

Lines changed: 70 additions & 41 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
@@ -127,11 +127,7 @@ def delete_snap(snap):
127127
raise ex.SnapDeleteIsCalled()
128128

129129
@staticmethod
130-
def create_host(name, uids):
131-
return test_client.MockResource(name=name)
132-
133-
@staticmethod
134-
def get_host(name):
130+
def create_host(name):
135131
return test_client.MockResource(name=name)
136132

137133
@staticmethod
@@ -188,6 +184,10 @@ def thin_clone(obj, name, io_limit_policy, description, new_size_gb):
188184
else:
189185
raise ex.UnityThinCloneLimitExceededError
190186

187+
@staticmethod
188+
def update_host_initiators(host, wwns):
189+
return None
190+
191191
@property
192192
def system(self):
193193
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
@@ -48,6 +47,7 @@ def __init__(self, name=None, _id=None):
4847
self.max_kbps = None
4948
self.pool_name = 'Pool0'
5049
self._storage_resource = None
50+
self.host_cache = []
5151

5252
@property
5353
def id(self):
@@ -250,6 +250,10 @@ def create_host(name):
250250
def get_host(name):
251251
if name == 'not_found':
252252
raise ex.UnityResourceNotFoundError()
253+
if name == 'host1':
254+
ret = MockResource(name)
255+
ret.initiator_id = ['old-iqn']
256+
return ret
253257
return MockResource(name)
254258

255259
@staticmethod
@@ -410,16 +414,18 @@ def test_get_snap_not_found(self):
410414
ret = self.client.get_snap('not_found')
411415
self.assertIsNone(ret)
412416

413-
def test_create_host_found(self):
414-
iqns = ['iqn.1-1.com.e:c.a.a0']
415-
host = self.client.create_host('host1', iqns)
417+
@mock.patch.object(coordination.Coordinator, 'get_lock')
418+
def test_create_host_found(self, fake_coordination):
419+
host = self.client.create_host('host1')
416420

417421
self.assertEqual('host1', host.name)
418422
self.assertLessEqual(['iqn.1-1.com.e:c.a.a0'], host.initiator_id)
419423

420-
def test_create_host_not_found(self):
421-
host = self.client.create_host('not_found', [])
424+
@mock.patch.object(coordination.Coordinator, 'get_lock')
425+
def test_create_host_not_found(self, fake):
426+
host = self.client.create_host('not_found')
422427
self.assertEqual('not_found', host.name)
428+
self.assertIn('not_found', self.client.host_cache)
423429

424430
def test_attach_lun(self):
425431
lun = MockResource(_id='lun1', name='l1')
@@ -440,8 +446,20 @@ def f():
440446

441447
self.assertRaises(ex.DetachIsCalled, f)
442448

443-
def test_get_host(self):
444-
self.assertEqual('host2', self.client.get_host('host2').name)
449+
@mock.patch.object(coordination.Coordinator, 'get_lock')
450+
def test_create_host(self, fake):
451+
self.assertEqual('host2', self.client.create_host('host2').name)
452+
453+
@mock.patch.object(coordination.Coordinator, 'get_lock')
454+
def test_create_host_in_cache(self, fake):
455+
self.client.host_cache['already_in'] = MockResource(name='already_in')
456+
host = self.client.create_host('already_in')
457+
self.assertIn('already_in', self.client.host_cache)
458+
self.assertEqual('already_in', host.name)
459+
460+
def test_update_host_initiators(self):
461+
host = MockResource(name='host_init')
462+
host = self.client.update_host_initiators(host, 'fake-iqn-1')
445463

446464
def test_get_iscsi_target_info(self):
447465
ret = self.client.get_iscsi_target_info()

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

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -287,8 +287,9 @@ def delete_volume(self, volume):
287287

288288
@cinder_utils.trace
289289
def _initialize_connection(self, lun_or_snap, connector, vol_id):
290-
host = self.client.create_host(connector['host'],
291-
self.get_connector_uids(connector))
290+
host = self.client.create_host(connector['host'])
291+
self.client.update_host_initiators(
292+
host, self.get_connector_uids(connector))
292293
hlu = self.client.attach(host, lun_or_snap)
293294
data = self.get_connection_info(hlu, host, connector)
294295
data['target_discovered'] = True
@@ -308,7 +309,7 @@ def initialize_connection(self, volume, connector):
308309

309310
@cinder_utils.trace
310311
def _terminate_connection(self, lun_or_snap, connector):
311-
host = self.client.get_host(connector['host'])
312+
host = self.client.create_host(connector['host'])
312313
self.client.detach(host, lun_or_snap)
313314

314315
@cinder_utils.trace
@@ -565,8 +566,8 @@ def _thin_clone(self, vol_params, src_snap, src_lun=None):
565566
io_limit_policy=vol_params.io_limit_policy,
566567
new_size_gb=vol_params.size)
567568
except storops_ex.UnityThinCloneLimitExceededError:
568-
LOG.info('Number of thin clones of base LUN exceeds system '
569-
'limit, dd-copy a new one and thin clone from it.')
569+
LOG.info(_LI('Number of thin clones of base LUN exceeds system '
570+
'limit, dd-copy a new one and thin clone from it.'))
570571
# Copy via dd if thin clone meets the system limit
571572
hidden = copy.copy(vol_params)
572573
hidden.name = 'hidden-%s' % vol_params.name
@@ -738,7 +739,7 @@ def _terminate_connection(self, lun_or_snap, connector):
738739
'driver_volume_type': self.driver_volume_type,
739740
'data': {}
740741
}
741-
host = self.client.get_host(connector['host'])
742+
host = self.client.create_host(connector['host'])
742743
if len(host.host_luns) == 0:
743744
targets = self.client.get_fc_target_info(
744745
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):
@@ -186,28 +187,40 @@ def get_snap(self, name=None):
186187
{'name': name, 'err': err})
187188
return None
188189

189-
def create_host(self, name, uids):
190-
"""Creates a host on Unity.
191-
192-
Creates a host on Unity which has the uids associated.
193-
194-
:param name: name of the host
195-
:param uids: iqns or wwns list
196-
:return: UnitHost object
197-
"""
190+
@coordination.synchronized('{self.host}-{name}')
191+
def create_host(self, name):
192+
"""Provides existing host if exists else create one."""
193+
if name not in self.host_cache:
194+
try:
195+
host = self.system.get_host(name=name)
196+
except storops_ex.UnityResourceNotFoundError:
197+
LOG.debug('Host %s not found. Create a new one.',
198+
name)
199+
host = self.system.create_host(name=name)
198200

199-
try:
200-
host = self.system.get_host(name=name)
201-
except storops_ex.UnityResourceNotFoundError:
202-
LOG.debug('Existing host %s not found. Create a new one.', name)
203-
host = self.system.create_host(name=name)
201+
self.host_cache[name] = host
202+
else:
203+
host = self.host_cache[name]
204+
return host
204205

206+
def update_host_initiators(self, host, uids):
207+
"""Updates host with the supplied uids."""
205208
host_initiators_ids = self.get_host_initiator_ids(host)
206209
un_registered = [h for h in uids if h not in host_initiators_ids]
207-
for uid in un_registered:
208-
host.add_initiator(uid, force_create=True)
210+
if un_registered:
211+
for uid in un_registered:
212+
try:
213+
host.add_initiator(uid, force_create=True)
214+
except storops_ex.UnityHostInitiatorExistedError:
215+
# This make concurrent modification of
216+
# host initiators safe
217+
LOG.debug(
218+
'The uid(%s) was already in '
219+
'%s.', uid, host.name)
220+
host.update()
221+
# Update host cached with new initiators.
222+
self.host_cache[host.name] = host
209223

210-
host.update()
211224
return host
212225

213226
@staticmethod
@@ -241,9 +254,6 @@ def detach(host, lun_or_snap):
241254
lun_or_snap.update()
242255
host.detach(lun_or_snap)
243256

244-
def get_host(self, name):
245-
return self.system.get_host(name=name)
246-
247257
def get_ethernet_ports(self):
248258
return self.system.get_ethernet_port()
249259

0 commit comments

Comments
 (0)