55from irods .manager import Manager
66from irods .message import (
77 iRODSMessage , FileOpenRequest , ObjCopyRequest , StringStringMap , DataObjInfo , ModDataObjMeta ,
8- DataObjChksumRequest , DataObjChksumResponse , RErrorStack )
8+ DataObjChksumRequest , DataObjChksumResponse , RErrorStack , STR_PI
9+ )
910import irods .exception as ex
1011from irods .api_number import api_number
1112from irods .collection import iRODSCollection
1920import json
2021import logging
2122
22-
2323MAXIMUM_SINGLE_THREADED_TRANSFER_SIZE = 32 * ( 1024 ** 2 )
2424
2525DEFAULT_NUMBER_OF_THREADS = 0 # Defaults for reasonable number of threads -- optimized to be
2626 # performant but allow no more worker threads than available CPUs.
2727
2828DEFAULT_QUEUE_DEPTH = 32
2929
30+ logger = logging .getLogger (__name__ )
3031
3132class Server_Checksum_Warning (Exception ):
3233 """Error from iRODS server indicating some replica checksums are missing or incorrect."""
@@ -96,16 +97,18 @@ def _download(self, obj, local_path, num_threads, **options):
9697 if os .path .exists (local_file ) and kw .FORCE_FLAG_KW not in options :
9798 raise ex .OVERWRITE_WITHOUT_FORCE_FLAG
9899
99- with open (local_file , 'wb' ) as f , self .open (obj , 'r' , ** options ) as o :
100-
101- if self .should_parallelize_transfer (num_threads , o ):
102- f .close ()
103- if not self .parallel_get ( (obj ,o ), local_path , num_threads = num_threads ,
104- target_resource_name = options .get (kw .RESC_NAME_KW ,'' )):
105- raise RuntimeError ("parallel get failed" )
106- else :
107- for chunk in chunks (o , self .READ_BUFFER_SIZE ):
108- f .write (chunk )
100+ data_open_returned_values_ = {}
101+ with open (local_file , 'wb' ) as f :
102+ with self .open (obj , 'r' , returned_values = data_open_returned_values_ , ** options ) as o :
103+ if self .should_parallelize_transfer (num_threads , o ):
104+ f .close ()
105+ if not self .parallel_get ( (obj ,o ), local_path , num_threads = num_threads ,
106+ target_resource_name = options .get (kw .RESC_NAME_KW ,'' ),
107+ data_open_returned_values = data_open_returned_values_ ):
108+ raise RuntimeError ("parallel get failed" )
109+ else :
110+ for chunk in chunks (o , self .READ_BUFFER_SIZE ):
111+ f .write (chunk )
109112
110113
111114 def get (self , path , local_path = None , num_threads = DEFAULT_NUMBER_OF_THREADS , ** options ):
@@ -214,6 +217,7 @@ def parallel_get(self,
214217 async_ = False ,
215218 num_threads = 0 ,
216219 target_resource_name = '' ,
220+ data_open_returned_values = None ,
217221 progressQueue = False ):
218222 """Call into the irods.parallel library for multi-1247 GET.
219223
@@ -224,6 +228,7 @@ def parallel_get(self,
224228 """
225229 return parallel .io_main ( self .sess , data_or_path_ , parallel .Oper .GET | (parallel .Oper .NONBLOCKING if async_ else 0 ), file_ ,
226230 num_threads = num_threads , target_resource_name = target_resource_name ,
231+ data_open_returned_values = data_open_returned_values ,
227232 queueLength = (DEFAULT_QUEUE_DEPTH if progressQueue else 0 ))
228233
229234 def parallel_put (self ,
@@ -296,7 +301,9 @@ def open_with_FileRaw(self, *arg, **kw_options):
296301 kw .RESC_HIER_STR_KW
297302 ))
298303
299- def open (self , path , mode , create = True , finalize_on_close = True , ** options ):
304+
305+ def open (self , path , mode , create = True , finalize_on_close = True , returned_values = None , allow_redirect = True , ** options ):
306+
300307 _raw_fd_holder = options .get ('_raw_fd_holder' ,[])
301308 # If no keywords are used that would influence the server as to the choice of a storage resource,
302309 # then use the default resource in the client configuration.
@@ -317,29 +324,79 @@ def open(self, path, mode, create = True, finalize_on_close = True, **options):
317324 }[mode ]
318325 # TODO: Use seek_to_end
319326
327+ if not isinstance (returned_values , dict ):
328+ returned_values = {}
329+
320330 try :
321331 oprType = options [kw .OPR_TYPE_KW ]
322332 except KeyError :
323333 oprType = 0
324334
325- message_body = FileOpenRequest (
326- objPath = path ,
327- createMode = 0 ,
328- openFlags = flags ,
329- offset = 0 ,
330- dataSize = - 1 ,
331- numThreads = self .sess .numThreads ,
332- oprType = oprType ,
333- KeyValPair_PI = StringStringMap (options ),
334- )
335- message = iRODSMessage ('RODS_API_REQ' , msg = message_body ,
336- int_info = api_number ['DATA_OBJ_OPEN_AN' ])
335+ def make_FileOpenRequest (** extra_opts ):
336+ options_ = dict (options ) if extra_opts else options
337+ options_ .update (extra_opts )
338+ return FileOpenRequest (
339+ objPath = path ,
340+ createMode = 0 ,
341+ openFlags = flags ,
342+ offset = 0 ,
343+ dataSize = - 1 ,
344+ numThreads = self .sess .numThreads ,
345+ oprType = oprType ,
346+ KeyValPair_PI = StringStringMap (options_ ),
347+ )
348+
349+ requested_hierarchy = options .get (kw .RESC_HIER_STR_KW , None )
337350
338351 conn = self .sess .pool .get_connection ()
352+ redirected_host = ''
353+
354+ use_get_rescinfo_apis = False
355+
356+ if allow_redirect and conn .server_version >= (4 ,3 ,1 ):
357+ key = 'CREATE' if mode [0 ] in ('w' ,'a' ) else 'OPEN'
358+ message = iRODSMessage ('RODS_API_REQ' ,
359+ msg = make_FileOpenRequest (** {kw .GET_RESOURCE_INFO_OP_TYPE_KW :key }),
360+ int_info = api_number ['GET_RESOURCE_INFO_FOR_OPERATION_AN' ])
361+ conn .send (message )
362+ response = conn .recv ()
363+ msg = response .get_main_message ( STR_PI )
364+ use_get_rescinfo_apis = True
365+
366+ # Get the information needed for the redirect
367+ _ = json .loads (msg .myStr )
368+ redirected_host = _ ["host" ]
369+ requested_hierarchy = _ ["resource_hierarchy" ]
370+
371+ target_zone = list (filter (None , path .split ('/' )))
372+ if target_zone :
373+ target_zone = target_zone [0 ]
374+
375+ directed_sess = self .sess
376+
377+ if redirected_host and use_get_rescinfo_apis :
378+ # Redirect only if the local zone is being targeted, and if the hostname is changed from the original.
379+ if target_zone == self .sess .zone and (self .sess .host != redirected_host ):
380+ # This is the actual redirect.
381+ directed_sess = self .sess .clone (host = redirected_host )
382+ returned_values ['session' ] = directed_sess
383+ conn = directed_sess .pool .get_connection ()
384+ logger .debug ('redirect_to_host = %s' , redirected_host )
385+
386+ # Restore RESC HIER for DATA_OBJ_OPEN call
387+ if requested_hierarchy is not None :
388+ options [kw .RESC_HIER_STR_KW ] = requested_hierarchy
389+ message_body = make_FileOpenRequest ()
390+
391+ # Perform DATA_OBJ_OPEN call
392+ message = iRODSMessage ('RODS_API_REQ' , msg = message_body ,
393+ int_info = api_number ['DATA_OBJ_OPEN_AN' ])
339394 conn .send (message )
340395 desc = conn .recv ().int_info
341396
342397 raw = iRODSDataObjectFileRaw (conn , desc , finalize_on_close = finalize_on_close , ** options )
398+ raw .session = directed_sess
399+
343400 (_raw_fd_holder ).append (raw )
344401 return io .BufferedRandom (raw )
345402
0 commit comments