too much for one commit
This commit is contained in:
@@ -0,0 +1,256 @@
|
||||
using EntKube.Provisioning.Domain;
|
||||
using EntKube.Provisioning.Features.MinioTenants;
|
||||
using EntKube.Provisioning.Features.MinioTenants.AdoptMinioTenants;
|
||||
using EntKube.Provisioning.Infrastructure;
|
||||
using EntKube.SharedKernel.Domain;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
|
||||
namespace EntKube.Provisioning.Tests.Features;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for MinIO Tenant adoption — discovering existing MinIO Tenant CRDs on a
|
||||
/// Kubernetes cluster and importing them into the platform's domain so they can be
|
||||
/// viewed, managed, and monitored. This follows the same pattern as CNPG cluster
|
||||
/// adoption: the Provisioning service scans the cluster, finds what's already running,
|
||||
/// and creates domain aggregates for each tenant.
|
||||
/// </summary>
|
||||
public class MinioTenantAdoptionTests
|
||||
{
|
||||
private readonly InMemoryMinioTenantRepository tenantRepository;
|
||||
private readonly Mock<IMinioTenantClient> tenantClient;
|
||||
|
||||
public MinioTenantAdoptionTests()
|
||||
{
|
||||
tenantRepository = new InMemoryMinioTenantRepository();
|
||||
tenantClient = new Mock<IMinioTenantClient>();
|
||||
}
|
||||
|
||||
// ─── Adopt Tenants ──────────────────────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public async Task AdoptTenants_WithExistingTenants_ImportsThemAsRunning()
|
||||
{
|
||||
// Arrange — The Kubernetes cluster has two MinIO tenants already running:
|
||||
// one for platform storage and one for application data. Neither of them
|
||||
// exists in our repository yet, so adoption should discover and import both.
|
||||
|
||||
Guid clusterId = Guid.NewGuid();
|
||||
|
||||
tenantClient.Setup(c => c.DiscoverTenantsAsync(
|
||||
It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new List<DiscoveredMinioTenant>
|
||||
{
|
||||
new(
|
||||
Name: "platform-storage",
|
||||
Namespace: "minio-platform",
|
||||
Pools: new List<DiscoveredMinioPool>
|
||||
{
|
||||
new(Servers: 4, VolumesPerServer: 4, StorageSize: "100Gi", StorageClass: "ceph-block")
|
||||
},
|
||||
CurrentState: "Initialized",
|
||||
Buckets: new List<string> { "cnpg-backups", "loki-logs" }),
|
||||
new(
|
||||
Name: "app-storage",
|
||||
Namespace: "minio-apps",
|
||||
Pools: new List<DiscoveredMinioPool>
|
||||
{
|
||||
new(Servers: 2, VolumesPerServer: 4, StorageSize: "50Gi", StorageClass: "standard")
|
||||
},
|
||||
CurrentState: "Initialized",
|
||||
Buckets: new List<string> { "user-uploads" })
|
||||
});
|
||||
|
||||
AdoptMinioTenantsHandler handler = new(tenantRepository, tenantClient.Object);
|
||||
|
||||
AdoptMinioTenantsRequest request = new(
|
||||
ClusterId: clusterId,
|
||||
KubeConfig: "kubeconfig-content",
|
||||
ContextName: "prod-context");
|
||||
|
||||
// Act
|
||||
|
||||
Result<List<Guid>> result = await handler.HandleAsync(request);
|
||||
|
||||
// Assert — Both tenants adopted with correct details.
|
||||
|
||||
result.IsSuccess.Should().BeTrue();
|
||||
result.Value.Should().HaveCount(2);
|
||||
|
||||
IReadOnlyList<MinioTenant> all = await tenantRepository.GetAllAsync();
|
||||
all.Should().HaveCount(2);
|
||||
|
||||
MinioTenant platformTenant = all.First(t => t.Name == "platform-storage");
|
||||
platformTenant.ClusterId.Should().Be(clusterId);
|
||||
platformTenant.Namespace.Should().Be("minio-platform");
|
||||
platformTenant.Status.Should().Be(MinioTenantStatus.Running);
|
||||
platformTenant.Pools.Should().HaveCount(1);
|
||||
platformTenant.Pools[0].Servers.Should().Be(4);
|
||||
platformTenant.Pools[0].StorageSize.Should().Be("100Gi");
|
||||
|
||||
MinioTenant appTenant = all.First(t => t.Name == "app-storage");
|
||||
appTenant.Status.Should().Be(MinioTenantStatus.Running);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AdoptTenants_NoTenantsOnCluster_ReturnsEmptyList()
|
||||
{
|
||||
// Arrange — The MinIO operator is installed but no tenants are deployed yet.
|
||||
|
||||
tenantClient.Setup(c => c.DiscoverTenantsAsync(
|
||||
It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new List<DiscoveredMinioTenant>());
|
||||
|
||||
AdoptMinioTenantsHandler handler = new(tenantRepository, tenantClient.Object);
|
||||
|
||||
AdoptMinioTenantsRequest request = new(Guid.NewGuid(), "config", "ctx");
|
||||
|
||||
// Act
|
||||
|
||||
Result<List<Guid>> result = await handler.HandleAsync(request);
|
||||
|
||||
// Assert — Success with empty list, not a failure.
|
||||
|
||||
result.IsSuccess.Should().BeTrue();
|
||||
result.Value.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AdoptTenants_AlreadyAdopted_DoesNotDuplicate()
|
||||
{
|
||||
// Arrange — One tenant was already adopted. Running adoption again should
|
||||
// skip it and only import new tenants, not create duplicates.
|
||||
|
||||
Guid clusterId = Guid.NewGuid();
|
||||
|
||||
MinioTenant existing = MinioTenant.Adopt(
|
||||
clusterId: clusterId,
|
||||
name: "platform-storage",
|
||||
ns: "minio-platform",
|
||||
pools: new List<MinioTenantPool>
|
||||
{
|
||||
new(4, 4, "100Gi", "ceph-block")
|
||||
});
|
||||
|
||||
await tenantRepository.AddAsync(existing);
|
||||
|
||||
tenantClient.Setup(c => c.DiscoverTenantsAsync(
|
||||
It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new List<DiscoveredMinioTenant>
|
||||
{
|
||||
new(
|
||||
Name: "platform-storage",
|
||||
Namespace: "minio-platform",
|
||||
Pools: new List<DiscoveredMinioPool>
|
||||
{
|
||||
new(Servers: 4, VolumesPerServer: 4, StorageSize: "100Gi", StorageClass: "ceph-block")
|
||||
},
|
||||
CurrentState: "Initialized",
|
||||
Buckets: new List<string> { "cnpg-backups" }),
|
||||
new(
|
||||
Name: "new-tenant",
|
||||
Namespace: "minio-new",
|
||||
Pools: new List<DiscoveredMinioPool>
|
||||
{
|
||||
new(Servers: 2, VolumesPerServer: 2, StorageSize: "50Gi", StorageClass: "standard")
|
||||
},
|
||||
CurrentState: "Initialized",
|
||||
Buckets: new List<string>())
|
||||
});
|
||||
|
||||
AdoptMinioTenantsHandler handler = new(tenantRepository, tenantClient.Object);
|
||||
|
||||
AdoptMinioTenantsRequest request = new(clusterId, "config", "ctx");
|
||||
|
||||
// Act
|
||||
|
||||
Result<List<Guid>> result = await handler.HandleAsync(request);
|
||||
|
||||
// Assert — Only one new tenant adopted, the existing one was skipped.
|
||||
|
||||
result.IsSuccess.Should().BeTrue();
|
||||
result.Value.Should().HaveCount(2);
|
||||
|
||||
IReadOnlyList<MinioTenant> all = await tenantRepository.GetAllAsync();
|
||||
all.Should().HaveCount(2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AdoptTenants_DegradedTenant_MarkedDegraded()
|
||||
{
|
||||
// Arrange — A tenant with state "Decommissioning" or some non-healthy
|
||||
// state should be imported but marked as Degraded, not Running.
|
||||
|
||||
tenantClient.Setup(c => c.DiscoverTenantsAsync(
|
||||
It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new List<DiscoveredMinioTenant>
|
||||
{
|
||||
new(
|
||||
Name: "sick-tenant",
|
||||
Namespace: "minio-sick",
|
||||
Pools: new List<DiscoveredMinioPool>
|
||||
{
|
||||
new(Servers: 4, VolumesPerServer: 4, StorageSize: "100Gi", StorageClass: "standard")
|
||||
},
|
||||
CurrentState: "Decommissioning",
|
||||
Buckets: new List<string>())
|
||||
});
|
||||
|
||||
AdoptMinioTenantsHandler handler = new(tenantRepository, tenantClient.Object);
|
||||
|
||||
AdoptMinioTenantsRequest request = new(Guid.NewGuid(), "config", "ctx");
|
||||
|
||||
// Act
|
||||
|
||||
Result<List<Guid>> result = await handler.HandleAsync(request);
|
||||
|
||||
// Assert
|
||||
|
||||
result.IsSuccess.Should().BeTrue();
|
||||
|
||||
IReadOnlyList<MinioTenant> all = await tenantRepository.GetAllAsync();
|
||||
all[0].Status.Should().Be(MinioTenantStatus.Degraded);
|
||||
}
|
||||
|
||||
// ─── Domain: MinioTenant.Adopt ──────────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void Adopt_WithValidConfig_CreatesRunningTenant()
|
||||
{
|
||||
// Arrange — We discover a healthy tenant with known pools.
|
||||
|
||||
List<MinioTenantPool> pools = new()
|
||||
{
|
||||
new(Servers: 4, VolumesPerServer: 4, StorageSize: "100Gi", StorageClass: "ceph-block")
|
||||
};
|
||||
|
||||
// Act
|
||||
|
||||
MinioTenant tenant = MinioTenant.Adopt(
|
||||
clusterId: Guid.NewGuid(),
|
||||
name: "discovered-tenant",
|
||||
ns: "minio-discovered",
|
||||
pools: pools);
|
||||
|
||||
// Assert — Adopted tenants start as Running since they're already serving traffic.
|
||||
|
||||
tenant.Id.Should().NotBe(Guid.Empty);
|
||||
tenant.Name.Should().Be("discovered-tenant");
|
||||
tenant.Namespace.Should().Be("minio-discovered");
|
||||
tenant.Status.Should().Be(MinioTenantStatus.Running);
|
||||
tenant.Pools.Should().HaveCount(1);
|
||||
tenant.Pools[0].Servers.Should().Be(4);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Adopt_WithEmptyName_Throws()
|
||||
{
|
||||
// A tenant with no name is not valid — discovery must provide a name.
|
||||
|
||||
Action act = () => MinioTenant.Adopt(
|
||||
Guid.NewGuid(), "", "ns",
|
||||
new List<MinioTenantPool> { new(1, 1, "10Gi", "standard") });
|
||||
|
||||
act.Should().Throw<ArgumentException>();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user