@@ -1847,16 +1847,21 @@ def __init__(self, test):
1847
1847
self .swap_volume_instance_uuid = None
1848
1848
self .swap_volume_instance_error_uuid = None
1849
1849
self .attachment_error_id = None
1850
- # A map of volumes to a list of (attachment_id, instance_uuid).
1850
+ # A dict, keyed by volume id, to a dict, keyed by attachment id,
1851
+ # with keys:
1852
+ # - id: the attachment id
1853
+ # - instance_uuid: uuid of the instance attached to the volume
1854
+ # - connector: host connector dict; None if not connected
1851
1855
# Note that a volume can have multiple attachments even without
1852
1856
# multi-attach, as some flows create a blank 'reservation' attachment
1853
- # before deleting another attachment.
1854
- self .volume_to_attachment = collections .defaultdict (list )
1857
+ # before deleting another attachment. However, a non-multiattach volume
1858
+ # can only have at most one attachment with a host connector at a time.
1859
+ self .volume_to_attachment = collections .defaultdict (dict )
1855
1860
1856
1861
def volume_ids_for_instance (self , instance_uuid ):
1857
1862
for volume_id , attachments in self .volume_to_attachment .items ():
1858
- for _ , _instance_uuid in attachments :
1859
- if _instance_uuid == instance_uuid :
1863
+ for attachment in attachments . values () :
1864
+ if attachment [ 'instance_uuid' ] == instance_uuid :
1860
1865
# we might have multiple volumes attached to this instance
1861
1866
# so yield rather than return
1862
1867
yield volume_id
@@ -1888,14 +1893,14 @@ def fake_get(self_api, context, volume_id, microversion=None):
1888
1893
else self .swap_volume_instance_error_uuid )
1889
1894
1890
1895
if attachments :
1891
- attachment_id , instance_uuid = attachments [0 ]
1896
+ attachment = list ( attachments . values ()) [0 ]
1892
1897
1893
1898
volume .update ({
1894
1899
'status' : 'in-use' ,
1895
1900
'attachments' : {
1896
1901
instance_uuid : {
1897
1902
'mountpoint' : '/dev/vdb' ,
1898
- 'attachment_id' : attachment_id
1903
+ 'attachment_id' : attachment [ 'id' ]
1899
1904
}
1900
1905
},
1901
1906
'attach_status' : 'attached'
@@ -1905,7 +1910,7 @@ def fake_get(self_api, context, volume_id, microversion=None):
1905
1910
# Check to see if the volume is attached.
1906
1911
if attachments :
1907
1912
# The volume is attached.
1908
- attachment_id , instance_uuid = attachments [0 ]
1913
+ attachment = list ( attachments . values ()) [0 ]
1909
1914
volume = {
1910
1915
'status' : 'in-use' ,
1911
1916
'display_name' : volume_id ,
@@ -1914,8 +1919,8 @@ def fake_get(self_api, context, volume_id, microversion=None):
1914
1919
'multiattach' : volume_id == self .MULTIATTACH_VOL ,
1915
1920
'size' : 1 ,
1916
1921
'attachments' : {
1917
- instance_uuid : {
1918
- 'attachment_id' : attachment_id ,
1922
+ attachment [ ' instance_uuid' ] : {
1923
+ 'attachment_id' : attachment [ 'id' ] ,
1919
1924
'mountpoint' : '/dev/vdb'
1920
1925
}
1921
1926
}
@@ -1959,14 +1964,13 @@ def _find_attachment(attachment_id):
1959
1964
"""Find attachment corresponding to ``attachment_id``.
1960
1965
1961
1966
Returns:
1962
- A tuple of the volume ID, an attachment-instance mapping tuple
1963
- for the given attachment ID, and a list of attachment-instance
1964
- mapping tuples for the volume.
1967
+ A tuple of the volume ID, an attachment dict
1968
+ for the given attachment ID, and a dict (keyed by attachment
1969
+ id) of attachment dicts for the volume.
1965
1970
"""
1966
1971
for volume_id , attachments in self .volume_to_attachment .items ():
1967
- for attachment in attachments :
1968
- _attachment_id , instance_uuid = attachment
1969
- if attachment_id == _attachment_id :
1972
+ for attachment in attachments .values ():
1973
+ if attachment_id == attachment ['id' ]:
1970
1974
return volume_id , attachment , attachments
1971
1975
raise exception .VolumeAttachmentNotFound (
1972
1976
attachment_id = attachment_id )
@@ -1977,8 +1981,10 @@ def fake_attachment_create(_self, context, volume_id, instance_uuid,
1977
1981
if self .attachment_error_id is not None :
1978
1982
attachment_id = self .attachment_error_id
1979
1983
attachment = {'id' : attachment_id , 'connection_info' : {'data' : {}}}
1980
- self .volume_to_attachment [volume_id ].append (
1981
- (attachment_id , instance_uuid ))
1984
+ self .volume_to_attachment [volume_id ][attachment_id ] = {
1985
+ 'id' : attachment_id ,
1986
+ 'instance_uuid' : instance_uuid ,
1987
+ 'connector' : connector }
1982
1988
LOG .info ('Created attachment %s for volume %s. Total '
1983
1989
'attachments for volume: %d' , attachment_id , volume_id ,
1984
1990
len (self .volume_to_attachment [volume_id ]))
@@ -1989,15 +1995,26 @@ def fake_attachment_delete(_self, context, attachment_id):
1989
1995
# 'attachment' is a tuple defining a attachment-instance mapping
1990
1996
volume_id , attachment , attachments = (
1991
1997
_find_attachment (attachment_id ))
1992
- attachments . remove ( attachment )
1998
+ del attachments [ attachment_id ]
1993
1999
LOG .info ('Deleted attachment %s for volume %s. Total attachments '
1994
2000
'for volume: %d' , attachment_id , volume_id ,
1995
2001
len (attachments ))
1996
2002
1997
2003
def fake_attachment_update (_self , context , attachment_id , connector ,
1998
2004
mountpoint = None ):
1999
2005
# Ensure the attachment exists
2000
- _find_attachment (attachment_id )
2006
+ volume_id , attachment , attachments = (
2007
+ _find_attachment (attachment_id ))
2008
+ # Cinder will only allow one "connected" attachment per
2009
+ # non-multiattach volume at a time.
2010
+ if volume_id != self .MULTIATTACH_VOL :
2011
+ for _attachment in attachments .values ():
2012
+ if _attachment ['connector' ] is not None :
2013
+ raise exception .InvalidInput (
2014
+ 'Volume %s is already connected with attachment '
2015
+ '%s on host %s' % (volume_id , _attachment ['id' ],
2016
+ _attachment ['connector' ].get ('host' )))
2017
+ attachment ['connector' ] = connector
2001
2018
LOG .info ('Updating volume attachment: %s' , attachment_id )
2002
2019
attachment_ref = {'driver_volume_type' : 'fake_type' ,
2003
2020
'id' : attachment_id ,
0 commit comments