308 lines
8.7 KiB
C#
308 lines
8.7 KiB
C#
using EntKube.Provisioning.Domain;
|
||
using FluentAssertions;
|
||
|
||
namespace EntKube.Provisioning.Tests.Domain;
|
||
|
||
/// <summary>
|
||
/// Tests for the MinioTenant aggregate. A MinIO Tenant represents an actual storage
|
||
/// cluster managed by the MinIO Operator on Kubernetes. Each tenant has one or more
|
||
/// pools, each pool defining how many servers and disks to use.
|
||
///
|
||
/// One MinIO Operator installation can host multiple tenants — each tenant is a
|
||
/// separate storage cluster with its own credentials, namespace, and capacity.
|
||
/// </summary>
|
||
public class MinioTenantTests
|
||
{
|
||
[Fact]
|
||
public void Create_WithValidInputs_CreatesTenantInProvisioningState()
|
||
{
|
||
// Arrange — A platform admin wants to provision a new MinIO storage tenant
|
||
// on a cluster. They specify how much capacity they need: 4 servers, each
|
||
// with 4 disks of 100Gi.
|
||
|
||
Guid clusterId = Guid.NewGuid();
|
||
List<MinioTenantPool> pools = new()
|
||
{
|
||
new MinioTenantPool(Servers: 4, VolumesPerServer: 4, StorageSize: "100Gi", StorageClass: "ceph-block")
|
||
};
|
||
|
||
// Act
|
||
|
||
MinioTenant tenant = MinioTenant.Create(
|
||
clusterId: clusterId,
|
||
name: "platform-storage",
|
||
ns: "minio-tenant-1",
|
||
pools: pools);
|
||
|
||
// Assert — New tenants start in Provisioning state until the operator confirms.
|
||
|
||
tenant.Id.Should().NotBe(Guid.Empty);
|
||
tenant.ClusterId.Should().Be(clusterId);
|
||
tenant.Name.Should().Be("platform-storage");
|
||
tenant.Namespace.Should().Be("minio-tenant-1");
|
||
tenant.Status.Should().Be(MinioTenantStatus.Provisioning);
|
||
tenant.Pools.Should().HaveCount(1);
|
||
tenant.Pools[0].Servers.Should().Be(4);
|
||
tenant.Pools[0].VolumesPerServer.Should().Be(4);
|
||
tenant.Pools[0].StorageSize.Should().Be("100Gi");
|
||
tenant.Pools[0].StorageClass.Should().Be("ceph-block");
|
||
tenant.CreatedAt.Should().BeCloseTo(DateTimeOffset.UtcNow, TimeSpan.FromSeconds(5));
|
||
}
|
||
|
||
[Fact]
|
||
public void Create_WithMultiplePools_TracksAllPools()
|
||
{
|
||
// Arrange — Some deployments need heterogeneous pools:
|
||
// one for hot data (fast NVMe) and one for warm data (spinning disk).
|
||
|
||
List<MinioTenantPool> pools = new()
|
||
{
|
||
new MinioTenantPool(Servers: 4, VolumesPerServer: 4, StorageSize: "100Gi", StorageClass: "nvme-fast"),
|
||
new MinioTenantPool(Servers: 2, VolumesPerServer: 8, StorageSize: "500Gi", StorageClass: "hdd-bulk")
|
||
};
|
||
|
||
// Act
|
||
|
||
MinioTenant tenant = MinioTenant.Create(Guid.NewGuid(), "multi-pool", "minio-mp", pools);
|
||
|
||
// Assert
|
||
|
||
tenant.Pools.Should().HaveCount(2);
|
||
tenant.Pools[0].StorageClass.Should().Be("nvme-fast");
|
||
tenant.Pools[1].StorageClass.Should().Be("hdd-bulk");
|
||
}
|
||
|
||
[Fact]
|
||
public void Create_WithEmptyName_ThrowsArgumentException()
|
||
{
|
||
// Arrange & Act
|
||
|
||
List<MinioTenantPool> pools = new() { new MinioTenantPool(4, 4, "100Gi", "standard") };
|
||
|
||
Action act = () => MinioTenant.Create(Guid.NewGuid(), "", "ns", pools);
|
||
|
||
// Assert
|
||
|
||
act.Should().Throw<ArgumentException>().WithParameterName("name");
|
||
}
|
||
|
||
[Fact]
|
||
public void Create_WithEmptyNamespace_ThrowsArgumentException()
|
||
{
|
||
// Arrange & Act
|
||
|
||
List<MinioTenantPool> pools = new() { new MinioTenantPool(4, 4, "100Gi", "standard") };
|
||
|
||
Action act = () => MinioTenant.Create(Guid.NewGuid(), "tenant", "", pools);
|
||
|
||
// Assert
|
||
|
||
act.Should().Throw<ArgumentException>().WithParameterName("ns");
|
||
}
|
||
|
||
[Fact]
|
||
public void Create_WithNoPools_ThrowsArgumentException()
|
||
{
|
||
// Arrange & Act — A tenant must have at least one pool to be meaningful.
|
||
|
||
Action act = () => MinioTenant.Create(Guid.NewGuid(), "tenant", "ns", new List<MinioTenantPool>());
|
||
|
||
// Assert
|
||
|
||
act.Should().Throw<ArgumentException>().WithParameterName("pools");
|
||
}
|
||
|
||
[Fact]
|
||
public void Create_WithZeroServers_ThrowsArgumentException()
|
||
{
|
||
// Arrange & Act — Each pool needs at least one server.
|
||
|
||
List<MinioTenantPool> pools = new() { new MinioTenantPool(0, 4, "100Gi", "standard") };
|
||
|
||
Action act = () => MinioTenant.Create(Guid.NewGuid(), "tenant", "ns", pools);
|
||
|
||
// Assert
|
||
|
||
act.Should().Throw<ArgumentException>();
|
||
}
|
||
|
||
[Fact]
|
||
public void Create_WithZeroVolumes_ThrowsArgumentException()
|
||
{
|
||
// Arrange & Act — Each pool needs at least one volume per server.
|
||
|
||
List<MinioTenantPool> pools = new() { new MinioTenantPool(4, 0, "100Gi", "standard") };
|
||
|
||
Action act = () => MinioTenant.Create(Guid.NewGuid(), "tenant", "ns", pools);
|
||
|
||
// Assert
|
||
|
||
act.Should().Throw<ArgumentException>();
|
||
}
|
||
|
||
[Fact]
|
||
public void MarkRunning_TransitionsFromProvisioning()
|
||
{
|
||
// Arrange — After the operator creates the tenant, the reconciler confirms it's running.
|
||
|
||
MinioTenant tenant = CreateTestTenant();
|
||
|
||
// Act
|
||
|
||
tenant.MarkRunning();
|
||
|
||
// Assert
|
||
|
||
tenant.Status.Should().Be(MinioTenantStatus.Running);
|
||
tenant.StatusMessage.Should().BeNull();
|
||
}
|
||
|
||
[Fact]
|
||
public void MarkDegraded_SetsStatusAndReason()
|
||
{
|
||
// Arrange
|
||
|
||
MinioTenant tenant = CreateTestTenant();
|
||
tenant.MarkRunning();
|
||
|
||
// Act
|
||
|
||
tenant.MarkDegraded("1 drive offline");
|
||
|
||
// Assert
|
||
|
||
tenant.Status.Should().Be(MinioTenantStatus.Degraded);
|
||
tenant.StatusMessage.Should().Be("1 drive offline");
|
||
}
|
||
|
||
[Fact]
|
||
public void UpdatePools_ReplacesPoolConfiguration()
|
||
{
|
||
// Arrange — The admin wants to scale out: add more servers to the pool.
|
||
|
||
MinioTenant tenant = CreateTestTenant();
|
||
tenant.MarkRunning();
|
||
|
||
List<MinioTenantPool> newPools = new()
|
||
{
|
||
new MinioTenantPool(Servers: 8, VolumesPerServer: 4, StorageSize: "100Gi", StorageClass: "ceph-block")
|
||
};
|
||
|
||
// Act
|
||
|
||
tenant.UpdatePools(newPools);
|
||
|
||
// Assert — Status goes back to Provisioning while the operator reconciles.
|
||
|
||
tenant.Pools.Should().HaveCount(1);
|
||
tenant.Pools[0].Servers.Should().Be(8);
|
||
tenant.Status.Should().Be(MinioTenantStatus.Provisioning);
|
||
}
|
||
|
||
[Fact]
|
||
public void UpdatePools_WithEmptyPools_ThrowsArgumentException()
|
||
{
|
||
// Arrange
|
||
|
||
MinioTenant tenant = CreateTestTenant();
|
||
tenant.MarkRunning();
|
||
|
||
// Act & Assert
|
||
|
||
Action act = () => tenant.UpdatePools(new List<MinioTenantPool>());
|
||
act.Should().Throw<ArgumentException>();
|
||
}
|
||
|
||
[Fact]
|
||
public void Decommission_MarksAsDecommissioned()
|
||
{
|
||
// Arrange — The admin tears down a tenant. The operator will delete resources.
|
||
|
||
MinioTenant tenant = CreateTestTenant();
|
||
tenant.MarkRunning();
|
||
|
||
// Act
|
||
|
||
tenant.Decommission();
|
||
|
||
// Assert
|
||
|
||
tenant.Status.Should().Be(MinioTenantStatus.Decommissioned);
|
||
}
|
||
|
||
[Fact]
|
||
public void GetEndpoint_ReturnsConventionalServiceUrl()
|
||
{
|
||
// Arrange — MinIO tenants expose storage at <name>-hl.<namespace>.svc:9000
|
||
|
||
MinioTenant tenant = CreateTestTenant();
|
||
|
||
// Act
|
||
|
||
string endpoint = tenant.GetEndpoint();
|
||
|
||
// Assert
|
||
|
||
endpoint.Should().Be("platform-storage-hl.minio-tenant-1.svc:9000");
|
||
}
|
||
|
||
[Fact]
|
||
public void GetConsoleEndpoint_ReturnsConventionalConsoleUrl()
|
||
{
|
||
// Arrange
|
||
|
||
MinioTenant tenant = CreateTestTenant();
|
||
|
||
// Act
|
||
|
||
string consoleEndpoint = tenant.GetConsoleEndpoint();
|
||
|
||
// Assert
|
||
|
||
consoleEndpoint.Should().Be("platform-storage-console.minio-tenant-1.svc:9443");
|
||
}
|
||
|
||
[Fact]
|
||
public void GetCredentialsSecretName_ReturnsConventionalSecretName()
|
||
{
|
||
// Arrange
|
||
|
||
MinioTenant tenant = CreateTestTenant();
|
||
|
||
// Act
|
||
|
||
string secret = tenant.GetCredentialsSecretName();
|
||
|
||
// Assert
|
||
|
||
secret.Should().Be("platform-storage-secret");
|
||
}
|
||
|
||
[Fact]
|
||
public void TotalCapacity_CalculatesFromPoolsAsString()
|
||
{
|
||
// Arrange — Capacity = servers * volumesPerServer * storageSize (per pool).
|
||
// With 4 servers × 4 volumes × 100Gi = 1600Gi total.
|
||
|
||
MinioTenant tenant = CreateTestTenant();
|
||
|
||
// Act
|
||
|
||
string capacity = tenant.CalculateTotalCapacity();
|
||
|
||
// Assert — 4 × 4 × 100 = 1600Gi
|
||
|
||
capacity.Should().Be("1600Gi");
|
||
}
|
||
|
||
private static MinioTenant CreateTestTenant()
|
||
{
|
||
List<MinioTenantPool> pools = new()
|
||
{
|
||
new MinioTenantPool(Servers: 4, VolumesPerServer: 4, StorageSize: "100Gi", StorageClass: "ceph-block")
|
||
};
|
||
|
||
return MinioTenant.Create(Guid.NewGuid(), "platform-storage", "minio-tenant-1", pools);
|
||
}
|
||
}
|