@@ -1841,16 +1841,21 @@ def __init__(self, test):
1841
1841
self .swap_volume_instance_uuid = None
1842
1842
self .swap_volume_instance_error_uuid = None
1843
1843
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
1845
1849
# Note that a volume can have multiple attachments even without
1846
1850
# 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 )
1849
1854
1850
1855
def volume_ids_for_instance (self , instance_uuid ):
1851
1856
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 :
1854
1859
# we might have multiple volumes attached to this instance
1855
1860
# so yield rather than return
1856
1861
yield volume_id
@@ -1882,14 +1887,14 @@ def fake_get(self_api, context, volume_id, microversion=None):
1882
1887
else self .swap_volume_instance_error_uuid )
1883
1888
1884
1889
if attachments :
1885
- attachment_id , instance_uuid = attachments [0 ]
1890
+ attachment = list ( attachments . values ()) [0 ]
1886
1891
1887
1892
volume .update ({
1888
1893
'status' : 'in-use' ,
1889
1894
'attachments' : {
1890
1895
instance_uuid : {
1891
1896
'mountpoint' : '/dev/vdb' ,
1892
- 'attachment_id' : attachment_id
1897
+ 'attachment_id' : attachment [ 'id' ]
1893
1898
}
1894
1899
},
1895
1900
'attach_status' : 'attached'
@@ -1899,7 +1904,7 @@ def fake_get(self_api, context, volume_id, microversion=None):
1899
1904
# Check to see if the volume is attached.
1900
1905
if attachments :
1901
1906
# The volume is attached.
1902
- attachment_id , instance_uuid = attachments [0 ]
1907
+ attachment = list ( attachments . values ()) [0 ]
1903
1908
volume = {
1904
1909
'status' : 'in-use' ,
1905
1910
'display_name' : volume_id ,
@@ -1908,8 +1913,8 @@ def fake_get(self_api, context, volume_id, microversion=None):
1908
1913
'multiattach' : volume_id == self .MULTIATTACH_VOL ,
1909
1914
'size' : 1 ,
1910
1915
'attachments' : {
1911
- instance_uuid : {
1912
- 'attachment_id' : attachment_id ,
1916
+ attachment [ ' instance_uuid' ] : {
1917
+ 'attachment_id' : attachment [ 'id' ] ,
1913
1918
'mountpoint' : '/dev/vdb'
1914
1919
}
1915
1920
}
@@ -1953,14 +1958,13 @@ def _find_attachment(attachment_id):
1953
1958
"""Find attachment corresponding to ``attachment_id``.
1954
1959
1955
1960
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.
1959
1964
"""
1960
1965
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' ]:
1964
1968
return volume_id , attachment , attachments
1965
1969
raise exception .VolumeAttachmentNotFound (
1966
1970
attachment_id = attachment_id )
@@ -1971,8 +1975,10 @@ def fake_attachment_create(_self, context, volume_id, instance_uuid,
1971
1975
if self .attachment_error_id is not None :
1972
1976
attachment_id = self .attachment_error_id
1973
1977
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 }
1976
1982
LOG .info ('Created attachment %s for volume %s. Total '
1977
1983
'attachments for volume: %d' , attachment_id , volume_id ,
1978
1984
len (self .volume_to_attachment [volume_id ]))
@@ -1983,15 +1989,26 @@ def fake_attachment_delete(_self, context, attachment_id):
1983
1989
# 'attachment' is a tuple defining a attachment-instance mapping
1984
1990
volume_id , attachment , attachments = (
1985
1991
_find_attachment (attachment_id ))
1986
- attachments . remove ( attachment )
1992
+ del attachments [ attachment_id ]
1987
1993
LOG .info ('Deleted attachment %s for volume %s. Total attachments '
1988
1994
'for volume: %d' , attachment_id , volume_id ,
1989
1995
len (attachments ))
1990
1996
1991
1997
def fake_attachment_update (_self , context , attachment_id , connector ,
1992
1998
mountpoint = None ):
1993
1999
# 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
1995
2012
LOG .info ('Updating volume attachment: %s' , attachment_id )
1996
2013
attachment_ref = {'driver_volume_type' : 'fake_type' ,
1997
2014
'id' : attachment_id ,
0 commit comments