Skip to content

Commit 1d3ca5a

Browse files
committed
Robustify attachment tracking in CinderFixtureNewAttachFlow
Cinder allows more than one attachment record on a non-multiattach volume as long as only one attachment is "connected" (has a host connector). When you create an empty (no connector) attachment for an in-use volume, the volume status changes to "attaching". If you try to update the empty attachment before deleting the previous attachment, Cinder will return a 400 like: Invalid volume: Volume b2aba195-6570-40c4-82bb-46d3557fceeb status must be available or downloading to reserve, but the current status is attaching. This change refactors the attachment tracking in the CinderFixtureNewAttachFlow fixture to be able to track the connector per attachment and if code tries to update an attachment on an already connected volume it will fail. Change-Id: I369f82245465e96fc15d4fc71a79a8a71f6f2c6d
1 parent ad9f373 commit 1d3ca5a

File tree

1 file changed

+37
-20
lines changed

1 file changed

+37
-20
lines changed

nova/tests/fixtures.py

Lines changed: 37 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1841,16 +1841,21 @@ def __init__(self, test):
18411841
self.swap_volume_instance_uuid = None
18421842
self.swap_volume_instance_error_uuid = None
18431843
self.attachment_error_id = None
1844-
# A map of volumes to a list of (attachment_id, instance_uuid).
1844+
# A dict, keyed by volume id, to a dict, keyed by attachment id,
1845+
# with keys:
1846+
# - id: the attachment id
1847+
# - instance_uuid: uuid of the instance attached to the volume
1848+
# - connector: host connector dict; None if not connected
18451849
# Note that a volume can have multiple attachments even without
18461850
# multi-attach, as some flows create a blank 'reservation' attachment
1847-
# before deleting another attachment.
1848-
self.volume_to_attachment = collections.defaultdict(list)
1851+
# before deleting another attachment. However, a non-multiattach volume
1852+
# can only have at most one attachment with a host connector at a time.
1853+
self.volume_to_attachment = collections.defaultdict(dict)
18491854

18501855
def volume_ids_for_instance(self, instance_uuid):
18511856
for volume_id, attachments in self.volume_to_attachment.items():
1852-
for _, _instance_uuid in attachments:
1853-
if _instance_uuid == instance_uuid:
1857+
for attachment in attachments.values():
1858+
if attachment['instance_uuid'] == instance_uuid:
18541859
# we might have multiple volumes attached to this instance
18551860
# so yield rather than return
18561861
yield volume_id
@@ -1882,14 +1887,14 @@ def fake_get(self_api, context, volume_id, microversion=None):
18821887
else self.swap_volume_instance_error_uuid)
18831888

18841889
if attachments:
1885-
attachment_id, instance_uuid = attachments[0]
1890+
attachment = list(attachments.values())[0]
18861891

18871892
volume.update({
18881893
'status': 'in-use',
18891894
'attachments': {
18901895
instance_uuid: {
18911896
'mountpoint': '/dev/vdb',
1892-
'attachment_id': attachment_id
1897+
'attachment_id': attachment['id']
18931898
}
18941899
},
18951900
'attach_status': 'attached'
@@ -1899,7 +1904,7 @@ def fake_get(self_api, context, volume_id, microversion=None):
18991904
# Check to see if the volume is attached.
19001905
if attachments:
19011906
# The volume is attached.
1902-
attachment_id, instance_uuid = attachments[0]
1907+
attachment = list(attachments.values())[0]
19031908
volume = {
19041909
'status': 'in-use',
19051910
'display_name': volume_id,
@@ -1908,8 +1913,8 @@ def fake_get(self_api, context, volume_id, microversion=None):
19081913
'multiattach': volume_id == self.MULTIATTACH_VOL,
19091914
'size': 1,
19101915
'attachments': {
1911-
instance_uuid: {
1912-
'attachment_id': attachment_id,
1916+
attachment['instance_uuid']: {
1917+
'attachment_id': attachment['id'],
19131918
'mountpoint': '/dev/vdb'
19141919
}
19151920
}
@@ -1953,14 +1958,13 @@ def _find_attachment(attachment_id):
19531958
"""Find attachment corresponding to ``attachment_id``.
19541959
19551960
Returns:
1956-
A tuple of the volume ID, an attachment-instance mapping tuple
1957-
for the given attachment ID, and a list of attachment-instance
1958-
mapping tuples for the volume.
1961+
A tuple of the volume ID, an attachment dict
1962+
for the given attachment ID, and a dict (keyed by attachment
1963+
id) of attachment dicts for the volume.
19591964
"""
19601965
for volume_id, attachments in self.volume_to_attachment.items():
1961-
for attachment in attachments:
1962-
_attachment_id, instance_uuid = attachment
1963-
if attachment_id == _attachment_id:
1966+
for attachment in attachments.values():
1967+
if attachment_id == attachment['id']:
19641968
return volume_id, attachment, attachments
19651969
raise exception.VolumeAttachmentNotFound(
19661970
attachment_id=attachment_id)
@@ -1971,8 +1975,10 @@ def fake_attachment_create(_self, context, volume_id, instance_uuid,
19711975
if self.attachment_error_id is not None:
19721976
attachment_id = self.attachment_error_id
19731977
attachment = {'id': attachment_id, 'connection_info': {'data': {}}}
1974-
self.volume_to_attachment[volume_id].append(
1975-
(attachment_id, instance_uuid))
1978+
self.volume_to_attachment[volume_id][attachment_id] = {
1979+
'id': attachment_id,
1980+
'instance_uuid': instance_uuid,
1981+
'connector': connector}
19761982
LOG.info('Created attachment %s for volume %s. Total '
19771983
'attachments for volume: %d', attachment_id, volume_id,
19781984
len(self.volume_to_attachment[volume_id]))
@@ -1983,15 +1989,26 @@ def fake_attachment_delete(_self, context, attachment_id):
19831989
# 'attachment' is a tuple defining a attachment-instance mapping
19841990
volume_id, attachment, attachments = (
19851991
_find_attachment(attachment_id))
1986-
attachments.remove(attachment)
1992+
del attachments[attachment_id]
19871993
LOG.info('Deleted attachment %s for volume %s. Total attachments '
19881994
'for volume: %d', attachment_id, volume_id,
19891995
len(attachments))
19901996

19911997
def fake_attachment_update(_self, context, attachment_id, connector,
19921998
mountpoint=None):
19931999
# Ensure the attachment exists
1994-
_find_attachment(attachment_id)
2000+
volume_id, attachment, attachments = (
2001+
_find_attachment(attachment_id))
2002+
# Cinder will only allow one "connected" attachment per
2003+
# non-multiattach volume at a time.
2004+
if volume_id != self.MULTIATTACH_VOL:
2005+
for _attachment in attachments.values():
2006+
if _attachment['connector'] is not None:
2007+
raise exception.InvalidInput(
2008+
'Volume %s is already connected with attachment '
2009+
'%s on host %s' % (volume_id, _attachment['id'],
2010+
_attachment['connector'].get('host')))
2011+
attachment['connector'] = connector
19952012
LOG.info('Updating volume attachment: %s', attachment_id)
19962013
attachment_ref = {'driver_volume_type': 'fake_type',
19972014
'id': attachment_id,

0 commit comments

Comments
 (0)