Skip to content

Commit a2589e1

Browse files
authored
Add support for listening to global events (#1110)
1 parent 75849b9 commit a2589e1

File tree

7 files changed

+174
-0
lines changed

7 files changed

+174
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2024 MinIO, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
using Minio.DataModel.Args;
18+
using Minio.DataModel.Notification;
19+
20+
namespace Minio.Examples.Cases;
21+
22+
internal static class ListenNotifications
23+
{
24+
// Listen for gloabal notifications (a Minio-only extension)
25+
public static void Run(IMinioClient minio,
26+
List<EventType> events = null)
27+
{
28+
IDisposable subscription = null;
29+
try
30+
{
31+
Console.WriteLine("Running example for API: ListenNotifications");
32+
Console.WriteLine();
33+
events ??= new List<EventType> { EventType.BucketCreatedAll };
34+
var args = new ListenBucketNotificationsArgs().WithEvents(events);
35+
var observable = minio.ListenNotificationsAsync(events);
36+
37+
subscription = observable.Subscribe(
38+
notification => Console.WriteLine($"Notification: {notification.Json}"),
39+
ex => Console.WriteLine($"OnError: {ex}"),
40+
() => Console.WriteLine("Stopped listening for bucket notifications\n"));
41+
42+
Console.WriteLine("Press any key to stop listening for notifications...");
43+
Console.ReadLine();
44+
}
45+
catch (Exception e)
46+
{
47+
Console.WriteLine($"[Bucket] Exception: {e}");
48+
}
49+
finally
50+
{
51+
subscription?.Dispose();
52+
}
53+
}
54+
}

Minio.Examples/Program.cs

+3
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,9 @@ public static async Task Main()
152152
// Start listening for bucket notifications
153153
ListenBucketNotifications.Run(minioClient, bucketName, new List<EventType> { EventType.ObjectCreatedAll });
154154

155+
// Start listening for global notifications
156+
ListenNotifications.Run(minioClient, new List<EventType> { EventType.BucketCreatedAll });
157+
155158
// Put an object to the new bucket
156159
await PutObject.Run(minioClient, bucketName, objectName, smallFileName, progress).ConfigureAwait(false);
157160

Minio.Functional.Tests/FunctionalTest.cs

+95
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,9 @@ public static class FunctionalTest
8383
private const string listenBucketNotificationsSignature =
8484
"IObservable<MinioNotificationRaw> ListenBucketNotificationsAsync(ListenBucketNotificationsArgs args, CancellationToken cancellationToken = default(CancellationToken))";
8585

86+
private const string listenNotificationsSignature =
87+
"IObservable<MinioNotificationRaw> ListenNotificationsAsync(IList<EventType> events, CancellationToken cancellationToken = default(CancellationToken))";
88+
8689
private const string copyObjectSignature =
8790
"Task<CopyObjectResult> CopyObjectAsync(CopyObjectArgs args, CancellationToken cancellationToken = default(CancellationToken))";
8891

@@ -1034,6 +1037,19 @@ internal static async Task<ObjectStat> PutObject_Tester(IMinioClient minio,
10341037
return statObject;
10351038
}
10361039

1040+
internal static async Task<bool> CreateBucket_Tester(IMinioClient minio, string bucketName)
1041+
{
1042+
// Create a new bucket
1043+
await minio.MakeBucketAsync(new MakeBucketArgs().WithBucket(bucketName)).ConfigureAwait(false);
1044+
1045+
// Verify the bucket exists
1046+
var bucketExists = await minio.BucketExistsAsync(new BucketExistsArgs().WithBucket(bucketName))
1047+
.ConfigureAwait(false);
1048+
Assert.IsTrue(bucketExists, $"Bucket {bucketName} was not created successfully.");
1049+
1050+
return bucketExists;
1051+
}
1052+
10371053
internal static async Task StatObject_Test1(IMinioClient minio)
10381054
{
10391055
var startTime = DateTime.Now;
@@ -2621,6 +2637,84 @@ await ListObjects_Test(minio, bucketName, singleObjectName, 1, headers: extractH
26212637
}
26222638
}
26232639

2640+
#region Global Notifications
2641+
2642+
internal static async Task ListenNotificationsAsync_Test1(IMinioClient minio)
2643+
{
2644+
var startTime = DateTime.Now;
2645+
var bucketName = GetRandomName(15);
2646+
var args = new Dictionary<string, string>
2647+
(StringComparer.Ordinal) { { "bucketName", bucketName } };
2648+
try
2649+
{
2650+
var received = new List<MinioNotificationRaw>();
2651+
2652+
var eventsList = new List<EventType> { EventType.BucketCreatedAll };
2653+
2654+
var events = minio.ListenNotificationsAsync(eventsList);
2655+
var subscription = events.Subscribe(
2656+
received.Add,
2657+
ex => Console.WriteLine($"OnError: {ex}"),
2658+
() => Console.WriteLine("Stopped listening for bucket notifications\n"));
2659+
2660+
// Ensure the subscription is established
2661+
await Task.Delay(1000).ConfigureAwait(false);
2662+
2663+
// Trigger the event by creating a new bucket
2664+
var isBucketCreated1 = await CreateBucket_Tester(minio, bucketName).ConfigureAwait(false);
2665+
2666+
var eventDetected = false;
2667+
for (var attempt = 0; attempt < 20; attempt++)
2668+
{
2669+
if (received.Count > 0)
2670+
{
2671+
var notification = JsonSerializer.Deserialize<MinioNotification>(received[0].Json);
2672+
2673+
if (notification.Records is not null)
2674+
{
2675+
Assert.AreEqual(1, notification.Records.Count);
2676+
eventDetected = true;
2677+
break;
2678+
}
2679+
}
2680+
2681+
await Task.Delay(500).ConfigureAwait(false); // Delay between attempts
2682+
}
2683+
2684+
subscription.Dispose();
2685+
if (!eventDetected)
2686+
throw new UnexpectedMinioException("Failed to detect the expected bucket notification event.");
2687+
2688+
new MintLogger(nameof(ListenNotificationsAsync_Test1),
2689+
listenNotificationsSignature,
2690+
"Tests whether ListenNotifications passes",
2691+
TestStatus.PASS, DateTime.Now - startTime, args: args).Log();
2692+
}
2693+
catch (NotImplementedException ex)
2694+
{
2695+
new MintLogger(nameof(ListenNotificationsAsync_Test1),
2696+
listenNotificationsSignature,
2697+
"Tests whether ListenNotifications passes",
2698+
TestStatus.NA, DateTime.Now - startTime, ex.Message,
2699+
ex.ToString(), args: args).Log();
2700+
}
2701+
catch (Exception ex)
2702+
{
2703+
new MintLogger(nameof(ListenNotificationsAsync_Test1),
2704+
listenNotificationsSignature,
2705+
"Tests whether ListenNotifications passes",
2706+
TestStatus.FAIL, DateTime.Now - startTime, ex.Message,
2707+
ex.ToString(), args: args).Log();
2708+
throw;
2709+
}
2710+
finally
2711+
{
2712+
await TearDown(minio, bucketName).ConfigureAwait(false);
2713+
}
2714+
}
2715+
2716+
#endregion
2717+
26242718
#region Bucket Notifications
26252719

26262720
internal static async Task ListenBucketNotificationsAsync_Test1(IMinioClient minio)
@@ -2655,6 +2749,7 @@ internal static async Task ListenBucketNotificationsAsync_Test1(IMinioClient min
26552749
() => { }
26562750
);
26572751

2752+
26582753
_ = await PutObject_Tester(minio, bucketName, objectName, null, contentType,
26592754
0, null, rsg.GenerateStreamFromSeed(1 * KB)).ConfigureAwait(false);
26602755

Minio.Functional.Tests/Program.cs

+3
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,9 @@ public static async Task Main(string[] args)
107107

108108
ConcurrentBag<Task> functionalTestTasks = new();
109109

110+
// Global Notification
111+
await FunctionalTest.ListenNotificationsAsync_Test1(minioClient).ConfigureAwait(false);
112+
110113
// Try catch as 'finally' section needs to run in the Functional Tests
111114
// Bucket notification is a minio specific feature.
112115
// If the following test is run against AWS, then the SDK throws

Minio/ApiEndpoints/BucketOperations.cs

+13
Original file line numberDiff line numberDiff line change
@@ -759,6 +759,19 @@ await this.ExecuteTaskAsync(ResponseErrorHandlers, requestMessageBuilder,
759759
.ConfigureAwait(false);
760760
}
761761

762+
/// <summary>
763+
/// Subscribes to global change notifications (a Minio-only extension)
764+
/// </summary>
765+
/// <param name="events">Events to listen for</param>
766+
/// <param name="cancellationToken">Optional cancellation token to cancel the operation</param>
767+
/// <returns>An observable of JSON-based notification events</returns>
768+
public IObservable<MinioNotificationRaw> ListenNotificationsAsync(IList<EventType> events,
769+
CancellationToken cancellationToken = default)
770+
{
771+
var args = new ListenBucketNotificationsArgs().WithEvents(events);
772+
return ListenBucketNotificationsAsync(args, cancellationToken);
773+
}
774+
762775
/// <summary>
763776
/// Subscribes to bucket change notifications (a Minio-only extension)
764777
/// </summary>

Minio/ApiEndpoints/IBucketOperations.cs

+3
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,9 @@ Task<ReplicationConfiguration> GetBucketReplicationAsync(GetBucketReplicationArg
379379

380380
Task<string> GetPolicyAsync(GetPolicyArgs args, CancellationToken cancellationToken = default);
381381

382+
IObservable<MinioNotificationRaw> ListenNotificationsAsync(IList<EventType> events,
383+
CancellationToken cancellationToken = default);
384+
382385
IObservable<MinioNotificationRaw> ListenBucketNotificationsAsync(string bucketName, IList<EventType> events,
383386
string prefix = "", string suffix = "", CancellationToken cancellationToken = default);
384387

Minio/DataModel/Notification/EventType.cs

+3
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ public sealed class EventType
4343
public static readonly EventType ObjectRemovedDeleteMarkerCreated = new("s3:ObjectRemoved:DeleteMarkerCreated");
4444
public static readonly EventType ReducedRedundancyLostObject = new("s3:ReducedRedundancyLostObject");
4545

46+
public static readonly EventType BucketCreatedAll = new("s3:BucketCreated:*");
47+
public static readonly EventType BucketRemovedAll = new("s3:BucketRemoved:*");
48+
4649
private EventType()
4750
{
4851
Value = null;

0 commit comments

Comments
 (0)