1414# You should have received a copy of the GNU General Public License
1515# along with this program. If not, see <https://www.gnu.org/licenses/>.
1616
17- from sm_typing import override
17+ from sm_typing import Any , Callable , Dict , IO , List , Optional , override
1818
1919from linstorjournaler import LinstorJournaler
20- from linstorvolumemanager import LinstorVolumeManager
20+ from linstorvolumemanager import LinstorVolumeManager , LinstorVolumeOpeners
2121import base64
2222import errno
2323import json
@@ -165,47 +165,74 @@ def wrapper(*args, **kwargs):
165165class LinstorVhdUtil :
166166 MAX_SIZE = 2 * 1024 * 1024 * 1024 * 1024 # Max VHD size.
167167
168+ class Chain (object ):
169+ def __init__ (self , files : List [IO ], leaf_path : str ):
170+ self ._files = files
171+ self ._leaf_path = leaf_path
172+
173+ @property
174+ def leaf_path (self ) -> str :
175+ return self ._leaf_path
176+
177+ def close (self ) -> None :
178+ for file in self ._files :
179+ try :
180+ file .close ()
181+ except Exception : # pylint: disable = W0718
182+ pass
183+
168184 def __init__ (self , session , linstor ):
169185 self ._session = session
170186 self ._linstor = linstor
171187
172- def create_chain_paths (self , vdi_uuid , readonly = False ):
188+ def create_chain_paths (
189+ self ,
190+ vdi_uuid : str ,
191+ readonly = False ,
192+ cb_openers : Optional [Callable [[str , LinstorVolumeOpeners ], Any ]] = None
193+ ) -> Chain :
173194 # OPTIMIZE: Add a limit_to_first_allocated_block param to limit vhdutil calls.
174195 # Useful for the snapshot code algorithm.
175196
176- leaf_vdi_path = self ._linstor .get_device_path (vdi_uuid )
177- path = leaf_vdi_path
178- while True :
179- if not util .pathexists (path ):
180- raise xs_errors .XenError (
181- 'VDIUnavailable' , opterr = 'Could not find: {}' .format (path )
182- )
197+ files = []
183198
184- # Diskless path can be created on the fly, ensure we can open it.
185- def check_volume_usable ():
186- while True :
187- try :
188- with open (path , 'r' if readonly else 'r+' ):
189- pass
190- except IOError as e :
191- if e .errno == errno .ENODATA :
192- time .sleep (2 )
193- continue
194- if e .errno == errno .EROFS or e .errno == errno .EMEDIUMTYPE :
195- util .SMlog ('Volume not attachable because used. Openers: {}' .format (
196- self ._linstor .get_volume_openers (vdi_uuid )
197- ))
198- raise
199+ leaf_path = self ._linstor .get_device_path (vdi_uuid )
200+ path = leaf_path
201+ try :
202+ while True :
203+ if not util .pathexists (path ):
204+ raise xs_errors .XenError (
205+ 'VDIUnavailable' , opterr = 'Could not find: {}' .format (path )
206+ )
207+
208+ # Diskless path can be created on the fly, ensure we can open it.
209+ def check_volume_usable ():
210+ while True :
211+ try :
212+ files .append (open (path , 'r' if readonly else 'r+' ))
213+ except OSError as e :
214+ if e .errno == errno .ENODATA :
215+ time .sleep (2 )
216+ continue
217+ if e .errno in (errno .EROFS , errno .EMEDIUMTYPE ):
218+ openers = self ._linstor .get_volume_openers (vdi_uuid )
219+ util .SMlog (f'Volume not attachable because used. Openers: { openers } ' )
220+ if cb_openers :
221+ cb_openers (vdi_uuid , openers )
222+ raise
223+ break
224+ util .retry (check_volume_usable , 15 , 2 )
225+
226+ vdi_uuid = self .get_vhd_info (vdi_uuid ).parentUuid
227+ if not vdi_uuid :
199228 break
200- util .retry (check_volume_usable , 15 , 2 )
201-
202- vdi_uuid = self .get_vhd_info (vdi_uuid ).parentUuid
203- if not vdi_uuid :
204- break
205- path = self ._linstor .get_device_path (vdi_uuid )
206- readonly = True # Non-leaf is always readonly.
229+ path = self ._linstor .get_device_path (vdi_uuid )
230+ readonly = True # Non-leaf is always readonly.
231+ except Exception as e :
232+ self .Chain (files , leaf_path ).close ()
233+ raise e
207234
208- return leaf_vdi_path
235+ return self . Chain ( files , leaf_path )
209236
210237 # --------------------------------------------------------------------------
211238 # Getters: read locally and try on another host in case of failure.
@@ -554,7 +581,7 @@ def _call_method(self, local_method, remote_method, device_path, use_parent, *ar
554581 # B.3. Call!
555582 def remote_call ():
556583 try :
557- all_openers = self ._linstor .get_volume_openers (openers_uuid )
584+ openers = self ._linstor .get_volume_openers (openers_uuid )
558585 except Exception as e :
559586 raise xs_errors .XenError (
560587 'VDIUnavailable' ,
@@ -563,8 +590,8 @@ def remote_call():
563590 )
564591
565592 no_host_found = True
566- for hostname , openers in all_openers .items ():
567- if not openers :
593+ for hostname , host_openers in openers .items ():
594+ if not host_openers :
568595 continue
569596
570597 host_ref = self ._find_host_ref_from_hostname (hosts , hostname )
0 commit comments