Skip to content

Commit f35114a

Browse files
committed
gh-20 Integration test for MinIO
gh-25 Change APIs for Verify.. Signed-off-by: Victor Chang <vicchang@nvidia.com>
1 parent 6999f5f commit f35114a

21 files changed

+758
-194
lines changed

.github/workflows/ci.yml

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -134,10 +134,11 @@ jobs:
134134
run: dotnet build -c ${{ env.BUILD_CONFIG }} --nologo "${{ env.SOLUTION }}"
135135
working-directory: ./src
136136

137+
- name: Start docker-compose
138+
run: docker compose up -d --wait
139+
working-directory: ./src/Plugins/MinIO/Tests
140+
137141
- name: Test
138-
env:
139-
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
140-
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
141142
run: find ~+ -type f -name "*.Test.csproj" | xargs -L1 dotnet test -c ${{ env.BUILD_CONFIG }} -v=minimal -r "${{ env.TEST_RESULTS }}" --collect:"XPlat Code Coverage" --settings coverlet.runsettings
142143
working-directory: ./src
143144

@@ -158,6 +159,14 @@ jobs:
158159
fail_ci_if_error: true
159160
verbose: true
160161

162+
- name: Stop docker-compose
163+
run: |
164+
docker compose down
165+
docker volume rm tests_minio_data
166+
docker volume rm tests_minio_config
167+
working-directory: ./src/Plugins/MinIO/Tests
168+
169+
161170
build:
162171
runs-on: ${{ matrix.os }}
163172

src/Plugins/AWSS3/AWS3StorageService.cs

Lines changed: 61 additions & 61 deletions
Large diffs are not rendered by default.

src/Plugins/MinIO/MinIoStorageService.cs

Lines changed: 57 additions & 57 deletions
Large diffs are not rendered by default.

src/Plugins/MinIO/StorageAdminService.cs

Lines changed: 21 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,11 @@ public class StorageAdminService : IStorageAdminService
4040
private readonly string _get_connections_cmd;
4141
private readonly string _get_users_cmd;
4242

43-
public StorageAdminService(IOptions<StorageServiceConfiguration> options, ILogger<MinIoStorageService> logger, IFileSystem fileSystem)
43+
public StorageAdminService(IOptions<StorageServiceConfiguration> options, ILogger<StorageAdminService> logger, IFileSystem fileSystem)
4444
{
45-
Guard.Against.Null(options, nameof(options));
46-
Guard.Against.Null(logger, nameof(logger));
45+
Guard.Against.Null(options);
46+
Guard.Against.Null(logger);
47+
4748
_fileSystem = fileSystem ?? throw new ArgumentNullException(nameof(fileSystem));
4849

4950
var configuration = options.Value;
@@ -62,7 +63,7 @@ public StorageAdminService(IOptions<StorageServiceConfiguration> options, ILogge
6263

6364
private static void ValidateConfiguration(StorageServiceConfiguration configuration)
6465
{
65-
Guard.Against.Null(configuration, nameof(configuration));
66+
Guard.Against.Null(configuration);
6667

6768
foreach (var key in ConfigurationKeys.McRequiredKeys)
6869
{
@@ -75,17 +76,17 @@ private static void ValidateConfiguration(StorageServiceConfiguration configurat
7576

7677
private string CreateUserCmd(string username, string secretKey)
7778
{
78-
Guard.Against.NullOrWhiteSpace(username, nameof(username));
79-
Guard.Against.NullOrWhiteSpace(secretKey, nameof(secretKey));
79+
Guard.Against.NullOrWhiteSpace(username);
80+
Guard.Against.NullOrWhiteSpace(secretKey);
8081

8182
return $"admin user add {_serviceName} {username} {secretKey}";
8283
}
8384

8485
public async Task<bool> SetPolicyAsync(IdentityType policyType, List<string> policies, string itemName)
8586
{
86-
Guard.Against.Null(policyType, nameof(policyType));
87-
Guard.Against.Null(policies, nameof(policies));
88-
Guard.Against.NullOrWhiteSpace(itemName, nameof(itemName));
87+
Guard.Against.Null(policyType);
88+
Guard.Against.Null(policies);
89+
Guard.Against.NullOrWhiteSpace(itemName);
8990

9091
var policiesStr = string.Join(',', policies);
9192
var setPolicyCmd = $"admin policy set {_serviceName} {policiesStr} {policyType.ToString().ToLower()}={itemName}";
@@ -101,7 +102,7 @@ public async Task<bool> SetPolicyAsync(IdentityType policyType, List<string> pol
101102

102103
private async Task<List<string>> ExecuteAsync(string cmd)
103104
{
104-
Guard.Against.NullOrWhiteSpace(cmd, nameof(cmd));
105+
Guard.Against.NullOrWhiteSpace(cmd);
105106

106107
if (cmd.StartsWith("mc"))
107108
{
@@ -122,7 +123,7 @@ private async Task<List<string>> ExecuteAsync(string cmd)
122123

123124
private static async Task<(List<string> Output, List<string> Errors)> RunProcessAsync(Process process)
124125
{
125-
Guard.Against.Null(process, nameof(process));
126+
Guard.Against.Null(process);
126127

127128
var output = new List<string>();
128129
var errors = new List<string>();
@@ -146,7 +147,7 @@ private async Task<List<string>> ExecuteAsync(string cmd)
146147

147148
private Process CreateProcess(string cmd)
148149
{
149-
Guard.Against.NullOrWhiteSpace(cmd, nameof(cmd));
150+
Guard.Against.NullOrWhiteSpace(cmd);
150151

151152
ProcessStartInfo startinfo = new()
152153
{
@@ -186,15 +187,15 @@ public async Task<bool> SetConnectionAsync()
186187

187188
public async Task<bool> UserAlreadyExistsAsync(string username)
188189
{
189-
Guard.Against.NullOrWhiteSpace(username, nameof(username));
190+
Guard.Against.NullOrWhiteSpace(username);
190191

191192
var result = await ExecuteAsync(_get_users_cmd).ConfigureAwait(false);
192193
return result.Any(r => r.Contains(username));
193194
}
194195

195196
public async Task RemoveUserAsync(string username)
196197
{
197-
Guard.Against.NullOrWhiteSpace(username, nameof(username));
198+
Guard.Against.NullOrWhiteSpace(username);
198199

199200
var result = await ExecuteAsync($"admin user remove {_serviceName} {username}").ConfigureAwait(false);
200201

@@ -204,26 +205,11 @@ public async Task RemoveUserAsync(string username)
204205
}
205206
}
206207

207-
[Obsolete("CreateUserAsync with bucketNames is deprecated, please use CreateUserAsync with an array of PolicyRequest instead.")]
208-
public async Task<Credentials> CreateUserAsync(string username, AccessPermissions permissions, string[] bucketNames)
209-
{
210-
Guard.Against.NullOrWhiteSpace(username, nameof(username));
211-
Guard.Against.Null(bucketNames, nameof(bucketNames));
212-
213-
var policyRequests = new List<PolicyRequest>();
214-
215-
for (var i = 0; i < bucketNames.Length; i++)
216-
{
217-
policyRequests.Add(new PolicyRequest(bucketNames[i], "/*"));
218-
}
219-
220-
return await CreateUserAsync(username, policyRequests.ToArray()).ConfigureAwait(false);
221-
}
222208

223209
public async Task<Credentials> CreateUserAsync(string username, PolicyRequest[] policyRequests)
224210
{
225-
Guard.Against.NullOrWhiteSpace(username, nameof(username));
226-
Guard.Against.Null(policyRequests, nameof(policyRequests));
211+
Guard.Against.NullOrWhiteSpace(username);
212+
Guard.Against.Null(policyRequests);
227213

228214
if (!await SetConnectionAsync().ConfigureAwait(false))
229215
{
@@ -270,8 +256,8 @@ public async Task<Credentials> CreateUserAsync(string username, PolicyRequest[]
270256
/// <exception cref="InvalidOperationException"></exception>
271257
private async Task<string> CreatePolicyAsync(PolicyRequest[] policyRequests, string username)
272258
{
273-
Guard.Against.Null(policyRequests, nameof(policyRequests));
274-
Guard.Against.NullOrWhiteSpace(username, nameof(username));
259+
Guard.Against.Null(policyRequests);
260+
Guard.Against.NullOrWhiteSpace(username);
275261

276262
var policyFileName = await CreatePolicyFile(policyRequests, username).ConfigureAwait(false);
277263
var result = await ExecuteAsync($"admin policy add {_serviceName} pol_{username} {policyFileName}").ConfigureAwait(false);
@@ -287,8 +273,8 @@ private async Task<string> CreatePolicyAsync(PolicyRequest[] policyRequests, str
287273

288274
private async Task<string> CreatePolicyFile(PolicyRequest[] policyRequests, string username)
289275
{
290-
Guard.Against.NullOrEmpty(policyRequests, nameof(policyRequests));
291-
Guard.Against.NullOrWhiteSpace(username, nameof(username));
276+
Guard.Against.NullOrEmpty(policyRequests);
277+
Guard.Against.NullOrWhiteSpace(username);
292278

293279
var policy = PolicyExtensions.ToPolicy(policyRequests);
294280
var jsonPolicy = policy.ToJson();
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
* Copyright 2022 MONAI Consortium
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 Xunit;
18+
//Optional
19+
[assembly: CollectionBehavior(DisableTestParallelization = true)]
20+
//Optional
21+
[assembly: TestCaseOrderer("Xunit.Extensions.Ordering.TestCaseOrderer", "Xunit.Extensions.Ordering")]
22+
//Optional
23+
[assembly: TestCollectionOrderer("Xunit.Extensions.Ordering.CollectionOrderer", "Xunit.Extensions.Ordering")]
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* Copyright 2022 MONAI Consortium
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 Microsoft.Extensions.Diagnostics.HealthChecks;
18+
using Microsoft.Extensions.Logging;
19+
using Minio;
20+
using Moq;
21+
using Xunit;
22+
using Xunit.Extensions.Ordering;
23+
24+
namespace Monai.Deploy.Storage.MinIO.Tests.Integration
25+
{
26+
[Order(10)]
27+
public class MinIoHealthCheckTest
28+
{
29+
private readonly Mock<IMinIoClientFactory> _minIoClientFactory;
30+
private readonly Mock<ILogger<MinIoHealthCheck>> _logger;
31+
32+
public MinIoHealthCheckTest()
33+
{
34+
_minIoClientFactory = new Mock<IMinIoClientFactory>();
35+
_logger = new Mock<ILogger<MinIoHealthCheck>>();
36+
}
37+
38+
[Fact]
39+
public async Task CheckHealthAsync_WhenListBucketSucceeds_ReturnHealthy()
40+
{
41+
var minioClient = new MinioClient()
42+
.WithEndpoint("localhost:9000")
43+
.WithCredentials("minioadmin", "minioadmin")
44+
.Build();
45+
46+
_minIoClientFactory.Setup(p => p.GetBucketOperationsClient()).Returns(minioClient);
47+
var healthCheck = new MinIoHealthCheck(_minIoClientFactory.Object, _logger.Object);
48+
var results = await healthCheck.CheckHealthAsync(new HealthCheckContext()).ConfigureAwait(false);
49+
50+
Assert.Equal(HealthStatus.Healthy, results.Status);
51+
Assert.Null(results.Exception);
52+
}
53+
}
54+
}
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
/*
2+
* Copyright 2022 MONAI Consortium
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 Microsoft.Extensions.Options;
18+
using Minio;
19+
using Monai.Deploy.Storage.Configuration;
20+
using Xunit;
21+
22+
namespace Monai.Deploy.Storage.MinIO.Tests.Integration
23+
{
24+
[CollectionDefinition("MinIoStorage")]
25+
public class MinIoStorageCollection : ICollectionFixture<MinIoStorageFixture>
26+
{
27+
// This class has no code, and is never created. Its purpose is simply
28+
// to be the place to apply [CollectionDefinition] and all the
29+
// ICollectionFixture<> interfaces.
30+
}
31+
32+
public class MinIoStorageFixture : IAsyncDisposable
33+
{
34+
const int MaxFileSize = 104857600;
35+
private readonly Random _random;
36+
private readonly List<string> _files;
37+
38+
public IOptions<StorageServiceConfiguration> Configurations { get; }
39+
public IMinIoClientFactory ClientFactory { get; }
40+
public IReadOnlyList<string> Files { get => _files; }
41+
public string BucketName { get; }
42+
public string Location { get; }
43+
44+
public MinIoStorageFixture()
45+
{
46+
_random = new Random();
47+
_files = new List<string>();
48+
49+
Configurations = Options.Create(new StorageServiceConfiguration());
50+
Configurations.Value.Settings.Add(ConfigurationKeys.EndPoint, "localhost:9000");
51+
Configurations.Value.Settings.Add(ConfigurationKeys.AccessKey, "minioadmin");
52+
Configurations.Value.Settings.Add(ConfigurationKeys.AccessToken, "minioadmin");
53+
Configurations.Value.Settings.Add(ConfigurationKeys.SecuredConnection, "false");
54+
Configurations.Value.Settings.Add(ConfigurationKeys.Region, "heaven");
55+
56+
ClientFactory = new MinIoClientFactory(Configurations);
57+
BucketName = $"md-test-{_random.Next(1000)}";
58+
Location = $"Heaven-L{_random.Next(28)}";
59+
}
60+
61+
internal async Task GenerateAndUploadData(IObjectOperations client)
62+
{
63+
await GenerateAndUploadFile(client, $"{Guid.NewGuid()}").ConfigureAwait(false);
64+
await GenerateAndUploadFile(client, $"dir-1/{Guid.NewGuid()}").ConfigureAwait(false);
65+
await GenerateAndUploadFile(client, $"dir-2/{Guid.NewGuid()}").ConfigureAwait(false);
66+
await GenerateAndUploadFile(client, $"dir-2/a/b/{Guid.NewGuid()}").ConfigureAwait(false);
67+
}
68+
69+
private async Task GenerateAndUploadFile(IObjectOperations client, string filePath)
70+
{
71+
var data = GetRandomBytes();
72+
var stream = new MemoryStream(data);
73+
74+
var putObjectArgs = new PutObjectArgs()
75+
.WithBucket(BucketName)
76+
.WithObject(filePath)
77+
.WithObjectSize(data.Length)
78+
.WithStreamData(stream);
79+
await client.PutObjectAsync(putObjectArgs).ConfigureAwait(false);
80+
81+
_files.Add(filePath);
82+
}
83+
84+
public byte[] GetRandomBytes()
85+
{
86+
return new byte[_random.Next(1, MaxFileSize)];
87+
}
88+
89+
public async ValueTask DisposeAsync()
90+
{
91+
await RemoveData().ConfigureAwait(false);
92+
await RemoveBucket().ConfigureAwait(false);
93+
}
94+
95+
private async Task RemoveBucket()
96+
{
97+
var client = ClientFactory.GetBucketOperationsClient();
98+
var args = new RemoveBucketArgs()
99+
.WithBucket(BucketName);
100+
await client.RemoveBucketAsync(args).ConfigureAwait(false);
101+
}
102+
103+
private async Task RemoveData()
104+
{
105+
var client = ClientFactory.GetObjectOperationsClient();
106+
var args = new RemoveObjectsArgs()
107+
.WithBucket(BucketName)
108+
.WithObjects(_files);
109+
110+
await client.RemoveObjectsAsync(args).ConfigureAwait(false);
111+
}
112+
}
113+
}

0 commit comments

Comments
 (0)