11/*
2- * Copyright 2021-2022 MONAI Consortium
2+ * Copyright 2021-2023 MONAI Consortium
33 *
44 * Licensed under the Apache License, Version 2.0 (the "License");
55 * you may not use this file except in compliance with the License.
2020using Microsoft . Extensions . Logging ;
2121using Microsoft . Extensions . Options ;
2222using Minio ;
23+ using Minio . Exceptions ;
2324using Monai . Deploy . Storage . API ;
2425using Monai . Deploy . Storage . Configuration ;
2526using Monai . Deploy . Storage . S3Policy ;
2627using Newtonsoft . Json ;
28+ using ObjectNotFoundException = Minio . Exceptions . ObjectNotFoundException ;
2729
2830namespace Monai . Deploy . Storage . MinIO
2931{
@@ -39,7 +41,7 @@ public class MinIoStorageService : IStorageService
3941 public MinIoStorageService ( IMinIoClientFactory minioClientFactory , IAmazonSecurityTokenServiceClientFactory amazonSecurityTokenServiceClientFactory , IOptions < StorageServiceConfiguration > options , ILogger < MinIoStorageService > logger )
4042 {
4143 Guard . Against . Null ( options ) ;
42- _minioClientFactory = minioClientFactory ?? throw new ArgumentNullException ( nameof ( IMinIoClientFactory ) ) ;
44+ _minioClientFactory = minioClientFactory ?? throw new ArgumentNullException ( nameof ( minioClientFactory ) ) ;
4345 _amazonSecurityTokenServiceClientFactory = amazonSecurityTokenServiceClientFactory ?? throw new ArgumentNullException ( nameof ( amazonSecurityTokenServiceClientFactory ) ) ;
4446 _logger = logger ?? throw new ArgumentNullException ( nameof ( logger ) ) ;
4547
@@ -101,13 +103,14 @@ public async Task<Dictionary<string, bool>> VerifyObjectsExistAsync(string bucke
101103 Guard . Against . Null ( artifactList ) ;
102104
103105 var existingObjectsDict = new Dictionary < string , bool > ( ) ;
106+ var exceptions = new List < Exception > ( ) ;
104107
105108 foreach ( var artifact in artifactList )
106109 {
107110 try
108111 {
109- var fileObjects = await ListObjectsAsync ( bucketName , artifact ) . ConfigureAwait ( false ) ;
110- var folderObjects = await ListObjectsAsync ( bucketName , artifact . EndsWith ( "/" ) ? artifact : $ "{ artifact } /", true ) . ConfigureAwait ( false ) ;
112+ var fileObjects = await ListObjectsAsync ( bucketName , artifact , cancellationToken : cancellationToken ) . ConfigureAwait ( false ) ;
113+ var folderObjects = await ListObjectsAsync ( bucketName , artifact . EndsWith ( "/" ) ? artifact : $ "{ artifact } /", true , cancellationToken ) . ConfigureAwait ( false ) ;
111114
112115 if ( ! folderObjects . Any ( ) && ! fileObjects . Any ( ) )
113116 {
@@ -122,10 +125,14 @@ public async Task<Dictionary<string, bool>> VerifyObjectsExistAsync(string bucke
122125 {
123126 _logger . VerifyObjectError ( bucketName , e ) ;
124127 existingObjectsDict . Add ( artifact , false ) ;
128+ exceptions . Add ( e ) ;
125129 }
126-
127130 }
128131
132+ if ( exceptions . Any ( ) )
133+ {
134+ throw new VerifyObjectsException ( exceptions , existingObjectsDict ) ;
135+ }
129136 return existingObjectsDict ;
130137 }
131138
@@ -134,17 +141,25 @@ public async Task<bool> VerifyObjectExistsAsync(string bucketName, string artifa
134141 Guard . Against . NullOrWhiteSpace ( bucketName ) ;
135142 Guard . Against . NullOrWhiteSpace ( artifactName ) ;
136143
137- var fileObjects = await ListObjectsAsync ( bucketName , artifactName ) . ConfigureAwait ( false ) ;
138- var folderObjects = await ListObjectsAsync ( bucketName , artifactName . EndsWith ( "/" ) ? artifactName : $ "{ artifactName } /", true ) . ConfigureAwait ( false ) ;
139-
140- if ( folderObjects . Any ( ) || fileObjects . Any ( ) )
144+ try
141145 {
142- return true ;
143- }
146+ var fileObjects = await ListObjectsAsync ( bucketName , artifactName , cancellationToken : cancellationToken ) . ConfigureAwait ( false ) ;
147+ var folderObjects = await ListObjectsAsync ( bucketName , artifactName . EndsWith ( "/" ) ? artifactName : $ "{ artifactName } /", true , cancellationToken ) . ConfigureAwait ( false ) ;
148+
149+ if ( folderObjects . Any ( ) || fileObjects . Any ( ) )
150+ {
151+ return true ;
152+ }
144153
145- _logger . FileNotFoundError ( bucketName , $ "{ artifactName } ") ;
154+ _logger . FileNotFoundError ( bucketName , $ "{ artifactName } ") ;
146155
147- return false ;
156+ return false ;
157+ }
158+ catch ( Exception ex )
159+ {
160+ _logger . VerifyObjectError ( bucketName , ex ) ;
161+ throw new VerifyObjectsException ( ex . Message , ex ) ;
162+ }
148163 }
149164
150165 public async Task PutObjectAsync ( string bucketName , string objectName , Stream data , long size , string contentType , Dictionary < string , string > ? metadata , CancellationToken cancellationToken = default )
@@ -295,36 +310,51 @@ public async Task CreateFolderWithCredentialsAsync(string bucketName, string fol
295310
296311 #region Internal Helper Methods
297312
298- private static async Task CopyObjectUsingClient ( IObjectOperations client , string sourceBucketName , string sourceObjectName , string destinationBucketName , string destinationObjectName , CancellationToken cancellationToken )
313+ private async Task CopyObjectUsingClient ( IObjectOperations client , string sourceBucketName , string sourceObjectName , string destinationBucketName , string destinationObjectName , CancellationToken cancellationToken )
299314 {
300- var copySourceObjectArgs = new CopySourceObjectArgs ( )
301- . WithBucket ( sourceBucketName )
302- . WithObject ( sourceObjectName ) ;
303- var copyObjectArgs = new CopyObjectArgs ( )
304- . WithBucket ( destinationBucketName )
305- . WithObject ( destinationObjectName )
306- . WithCopyObjectSource ( copySourceObjectArgs ) ;
307- await client . CopyObjectAsync ( copyObjectArgs , cancellationToken ) . ConfigureAwait ( false ) ;
315+ await CallApi ( async ( ) =>
316+ {
317+ try
318+ {
319+ var copySourceObjectArgs = new CopySourceObjectArgs ( )
320+ . WithBucket ( sourceBucketName )
321+ . WithObject ( sourceObjectName ) ;
322+ var copyObjectArgs = new CopyObjectArgs ( )
323+ . WithBucket ( destinationBucketName )
324+ . WithObject ( destinationObjectName )
325+ . WithCopyObjectSource ( copySourceObjectArgs ) ;
326+ await client . CopyObjectAsync ( copyObjectArgs , cancellationToken ) . ConfigureAwait ( false ) ;
327+ }
328+ catch ( ObjectNotFoundException ex ) when ( ex . ServerMessage . Contains ( "Not found" , StringComparison . OrdinalIgnoreCase ) )
329+ {
330+ throw new API . StorageObjectNotFoundException ( ex . ServerMessage ) ;
331+ }
332+ } ) . ConfigureAwait ( false ) ;
308333 }
309334
310- private static async Task GetObjectUsingClient ( IObjectOperations client , string bucketName , string objectName , Action < Stream > callback , CancellationToken cancellationToken )
335+ private async Task GetObjectUsingClient ( IObjectOperations client , string bucketName , string objectName , Action < Stream > callback , CancellationToken cancellationToken )
311336 {
312- var args = new GetObjectArgs ( )
313- . WithBucket ( bucketName )
314- . WithObject ( objectName )
315- . WithCallbackStream ( callback ) ;
316- await client . GetObjectAsync ( args , cancellationToken ) . ConfigureAwait ( false ) ;
337+ await CallApi ( async ( ) =>
338+ {
339+ var args = new GetObjectArgs ( )
340+ . WithBucket ( bucketName )
341+ . WithObject ( objectName )
342+ . WithCallbackStream ( callback ) ;
343+ await client . GetObjectAsync ( args , cancellationToken ) . ConfigureAwait ( false ) ;
344+ } ) . ConfigureAwait ( false ) ;
317345 }
318346
319- private async Task < IList < VirtualFileInfo > > ListObjectsUsingClient ( IBucketOperations client , string bucketName , string ? prefix , bool recursive , CancellationToken cancellationToken )
347+ private Task < IList < VirtualFileInfo > > ListObjectsUsingClient ( IBucketOperations client , string bucketName , string ? prefix , bool recursive , CancellationToken cancellationToken )
320348 {
321- return await Task . Run ( ( ) =>
349+ var files = new List < VirtualFileInfo > ( ) ;
350+ var listArgs = new ListObjectsArgs ( )
351+ . WithBucket ( bucketName )
352+ . WithPrefix ( prefix )
353+ . WithRecursive ( recursive ) ;
354+
355+ try
322356 {
323- var files = new List < VirtualFileInfo > ( ) ;
324- var listArgs = new ListObjectsArgs ( )
325- . WithBucket ( bucketName )
326- . WithPrefix ( prefix )
327- . WithRecursive ( recursive ) ;
357+ var done = new TaskCompletionSource < IList < VirtualFileInfo > > ( ) ;
328358
329359 var objservable = client . ListObjectsAsync ( listArgs , cancellationToken ) ;
330360 var completedEvent = new ManualResetEventSlim ( false ) ;
@@ -341,44 +371,103 @@ private async Task<IList<VirtualFileInfo>> ListObjectsUsingClient(IBucketOperati
341371 error =>
342372 {
343373 _logger . ListObjectError ( bucketName , error . Message ) ;
374+ if ( error is OperationCanceledException )
375+ done . SetException ( error ) ;
376+ else
377+ done . SetException ( new ListObjectException ( error . ToString ( ) ) ) ;
344378 } ,
345- ( ) => completedEvent . Set ( ) , cancellationToken ) ;
379+ ( ) =>
380+ {
381+ done . SetResult ( files ) ;
382+ if ( cancellationToken . IsCancellationRequested )
383+ {
384+ throw new ListObjectTimeoutException ( "Timed out waiting for results." ) ;
385+ }
386+ } , cancellationToken ) ;
346387
347- completedEvent . Wait ( cancellationToken ) ;
348- return files ;
349- } ) . ConfigureAwait ( false ) ;
388+ return done . Task ;
389+ }
390+ catch ( ConnectionException ex )
391+ {
392+ _logger . ConnectionError ( ex ) ;
393+ var iex = new StorageConnectionException ( ex . Message ) ;
394+ iex . Errors . Add ( ex . ServerMessage ) ;
395+ if ( ex . ServerResponse is not null && ! string . IsNullOrWhiteSpace ( ex . ServerResponse . ErrorMessage ) )
396+ {
397+ iex . Errors . Add ( ex . ServerResponse . ErrorMessage ) ;
398+ }
399+ throw iex ;
400+ }
401+ catch ( Exception ex ) when ( ex is not ListObjectTimeoutException && ex is not ListObjectException )
402+ {
403+ _logger . StorageServiceError ( ex ) ;
404+ throw new StorageServiceException ( ex . ToString ( ) ) ;
405+ }
350406 }
351407
352- private static async Task RemoveObjectUsingClient ( IObjectOperations client , string bucketName , string objectName , CancellationToken cancellationToken )
408+ private async Task RemoveObjectUsingClient ( IObjectOperations client , string bucketName , string objectName , CancellationToken cancellationToken )
353409 {
354- var args = new RemoveObjectArgs ( )
410+ await CallApi ( async ( ) =>
411+ {
412+ var args = new RemoveObjectArgs ( )
355413 . WithBucket ( bucketName )
356414 . WithObject ( objectName ) ;
357- await client . RemoveObjectAsync ( args , cancellationToken ) . ConfigureAwait ( false ) ;
415+ await client . RemoveObjectAsync ( args , cancellationToken ) . ConfigureAwait ( false ) ;
416+ } ) . ConfigureAwait ( false ) ;
358417 }
359418
360- private static async Task PutObjectUsingClient ( IObjectOperations client , string bucketName , string objectName , Stream data , long size , string contentType , Dictionary < string , string > ? metadata , CancellationToken cancellationToken )
419+ private async Task PutObjectUsingClient ( IObjectOperations client , string bucketName , string objectName , Stream data , long size , string contentType , Dictionary < string , string > ? metadata , CancellationToken cancellationToken )
361420 {
362- var args = new PutObjectArgs ( )
363- . WithBucket ( bucketName )
364- . WithObject ( objectName )
365- . WithStreamData ( data )
366- . WithObjectSize ( size )
367- . WithContentType ( contentType ) ;
368- if ( metadata is not null )
421+ await CallApi ( async ( ) =>
369422 {
370- args . WithHeaders ( metadata ) ;
371- }
423+ var args = new PutObjectArgs ( )
424+ . WithBucket ( bucketName )
425+ . WithObject ( objectName )
426+ . WithStreamData ( data )
427+ . WithObjectSize ( size )
428+ . WithContentType ( contentType ) ;
429+ if ( metadata is not null )
430+ {
431+ args . WithHeaders ( metadata ) ;
432+ }
433+
434+ await client . PutObjectAsync ( args , cancellationToken ) . ConfigureAwait ( false ) ;
435+ } ) . ConfigureAwait ( false ) ;
436+ }
372437
373- await client . PutObjectAsync ( args , cancellationToken ) . ConfigureAwait ( false ) ;
438+ private async Task RemoveObjectsUsingClient ( IObjectOperations client , string bucketName , IEnumerable < string > objectNames , CancellationToken cancellationToken )
439+ {
440+ await CallApi ( async ( ) =>
441+ {
442+ var args = new RemoveObjectsArgs ( )
443+ . WithBucket ( bucketName )
444+ . WithObjects ( objectNames . ToList ( ) ) ;
445+ await client . RemoveObjectsAsync ( args , cancellationToken ) . ConfigureAwait ( false ) ;
446+ } ) . ConfigureAwait ( false ) ;
374447 }
375448
376- private static async Task RemoveObjectsUsingClient ( IObjectOperations client , string bucketName , IEnumerable < string > objectNames , CancellationToken cancellationToken )
449+ private async Task CallApi ( Func < Task > func )
377450 {
378- var args = new RemoveObjectsArgs ( )
379- . WithBucket ( bucketName )
380- . WithObjects ( objectNames . ToList ( ) ) ;
381- await client . RemoveObjectsAsync ( args , cancellationToken ) . ConfigureAwait ( false ) ;
451+ try
452+ {
453+ await func ( ) . ConfigureAwait ( false ) ;
454+ }
455+ catch ( ConnectionException ex )
456+ {
457+ _logger . ConnectionError ( ex ) ;
458+ var iex = new StorageConnectionException ( ex . Message ) ;
459+ iex . Errors . Add ( ex . ServerMessage ) ;
460+ if ( ex . ServerResponse is not null && ! string . IsNullOrWhiteSpace ( ex . ServerResponse . ErrorMessage ) )
461+ {
462+ iex . Errors . Add ( ex . ServerResponse . ErrorMessage ) ;
463+ }
464+ throw iex ;
465+ }
466+ catch ( Exception ex )
467+ {
468+ _logger . StorageServiceError ( ex ) ;
469+ throw new StorageServiceException ( ex . ToString ( ) ) ;
470+ }
382471 }
383472
384473 #endregion Internal Helper Methods
0 commit comments