File: KubernetesResourceQuotaProviderTests.cs
Web Access
Project: src\test\Libraries\Microsoft.Extensions.Diagnostics.ResourceMonitoring.Kubernetes.Tests\Microsoft.Extensions.Diagnostics.ResourceMonitoring.Kubernetes.Tests.csproj (Microsoft.Extensions.Diagnostics.ResourceMonitoring.Kubernetes.Tests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using Xunit;
 
namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Kubernetes.Tests;
 
public class KubernetesResourceQuotaProviderTests
{
    [Fact]
    public void Constructor_WithValidMetadata_DoesNotThrow()
    {
        // Arrange
        var metadata = CreateKubernetesMetadata(
            limitsMemory: 2_147_483_648,
            limitsCpu: 2_000,
            requestsMemory: 1_073_741_824,
            requestsCpu: 1_000);
 
        // Act & Assert
        var exception = Record.Exception(() => new KubernetesResourceQuotaProvider(metadata));
        Assert.Null(exception);
    }
 
    [Fact]
    public void Constructor_WithNullMetadata_ThrowsArgumentNullException()
    {
        // Act & Assert
        Assert.Throws<ArgumentNullException>(() => new KubernetesResourceQuotaProvider(null!));
    }
 
    [Fact]
    public void GetResourceQuota_WithValidRequestsAndLimits_ReturnsCorrectQuota()
    {
        // Arrange
        ulong limitsMemory = 2_147_483_648; // 2GB
        ulong limitsCpu = 2_000; // 2 cores in millicores
        ulong requestsMemory = 1_073_741_824; // 1GB
        ulong requestsCpu = 1_000; // 1 core in millicores
 
        var metadata = CreateKubernetesMetadata(limitsMemory, limitsCpu, requestsMemory, requestsCpu);
        var provider = new KubernetesResourceQuotaProvider(metadata);
 
        // Act
        var quota = provider.GetResourceQuota();
 
        // Assert
        Assert.NotNull(quota);
        Assert.Equal(limitsMemory, quota.MaxMemoryInBytes);
        Assert.Equal(2.0, quota.MaxCpuInCores); // 2000 millicores = 2 cores
        Assert.Equal(requestsMemory, quota.BaselineMemoryInBytes);
        Assert.Equal(1.0, quota.BaselineCpuInCores); // 1000 millicores = 1 core
    }
 
    [Fact]
    public void GetResourceQuota_WithZeroCpuRequests_UsesLimitAsBaseline()
    {
        // Arrange
        ulong limitsMemory = 2_147_483_648; // 2GB
        ulong limitsCpu = 2_000; // 2 cores in millicores
        ulong requestsMemory = 1_073_741_824; // 1GB
        ulong requestsCpu = 0; // No CPU requests
 
        var metadata = CreateKubernetesMetadata(limitsMemory, limitsCpu, requestsMemory, requestsCpu);
        var provider = new KubernetesResourceQuotaProvider(metadata);
 
        // Act
        var quota = provider.GetResourceQuota();
 
        // Assert
        Assert.NotNull(quota);
        Assert.Equal(limitsMemory, quota.MaxMemoryInBytes);
        Assert.Equal(2.0, quota.MaxCpuInCores); // 2000 millicores = 2 cores
        Assert.Equal(requestsMemory, quota.BaselineMemoryInBytes);
        Assert.Equal(2.0, quota.BaselineCpuInCores); // Should fallback to limit (2 cores)
    }
 
    [Fact]
    public void GetResourceQuota_WithZeroMemoryRequests_UsesLimitAsBaseline()
    {
        // Arrange
        ulong limitsMemory = 2_147_483_648; // 2GB
        ulong limitsCpu = 2_000; // 2 cores in millicores
        ulong requestsMemory = 0; // No memory requests
        ulong requestsCpu = 1_000; // 1 core in millicores
 
        var metadata = CreateKubernetesMetadata(limitsMemory, limitsCpu, requestsMemory, requestsCpu);
        var provider = new KubernetesResourceQuotaProvider(metadata);
 
        // Act
        var quota = provider.GetResourceQuota();
 
        // Assert
        Assert.NotNull(quota);
        Assert.Equal(limitsMemory, quota.MaxMemoryInBytes);
        Assert.Equal(2.0, quota.MaxCpuInCores); // 2000 millicores = 2 cores
        Assert.Equal(limitsMemory, quota.BaselineMemoryInBytes); // Should fallback to limit (2GB)
        Assert.Equal(1.0, quota.BaselineCpuInCores); // 1000 millicores = 1 core
    }
 
    [Fact]
    public void GetResourceQuota_WithZeroRequestsAndLimits_ReturnsZeroValues()
    {
        // Arrange
        var metadata = CreateKubernetesMetadata(0, 0, 0, 0);
        var provider = new KubernetesResourceQuotaProvider(metadata);
 
        // Act
        var quota = provider.GetResourceQuota();
 
        // Assert
        Assert.NotNull(quota);
        Assert.Equal(0UL, quota.MaxMemoryInBytes);
        Assert.Equal(0.0, quota.MaxCpuInCores);
        Assert.Equal(0UL, quota.BaselineMemoryInBytes);
        Assert.Equal(0.0, quota.BaselineCpuInCores);
    }
 
    [Fact]
    public void GetResourceQuota_WithMaximumValues_HandlesCorrectly()
    {
        // Arrange
        ulong maxMemory = ulong.MaxValue;
        ulong maxCpu = ulong.MaxValue;
        ulong requestsMemory = ulong.MaxValue / 2;
        ulong requestsCpu = ulong.MaxValue / 2;
 
        var metadata = CreateKubernetesMetadata(maxMemory, maxCpu, requestsMemory, requestsCpu);
        var provider = new KubernetesResourceQuotaProvider(metadata);
 
        // Act
        var quota = provider.GetResourceQuota();
 
        // Assert
        Assert.NotNull(quota);
        Assert.Equal(maxMemory, quota.MaxMemoryInBytes);
        Assert.Equal(maxCpu / 1000.0, quota.MaxCpuInCores);
        Assert.Equal(requestsMemory, quota.BaselineMemoryInBytes);
        Assert.Equal(requestsCpu / 1000.0, quota.BaselineCpuInCores);
    }
 
    [Theory]
    [InlineData(2_000UL, 2.0)] // 2 cores
    [InlineData(1_000UL, 1.0)] // 1 core
    [InlineData(1_500UL, 1.5)] // 1.5 cores
    [InlineData(500UL, 0.5)] // 0.5 cores
    [InlineData(100UL, 0.1)] // 0.1 cores
    [InlineData(0UL, 0.0)] // 0 cores
    public void GetResourceQuota_ConvertsMillicoresToCoresCorrectly(ulong millicores, double expectedCores)
    {
        // Arrange
        var metadata = CreateKubernetesMetadata(
            limitsMemory: 1_073_741_824, // 1GB
            limitsCpu: millicores,
            requestsMemory: 536_870_912, // 512MB
            requestsCpu: millicores / 2);
 
        var provider = new KubernetesResourceQuotaProvider(metadata);
 
        // Act
        var quota = provider.GetResourceQuota();
 
        // Assert
        Assert.Equal(expectedCores, quota.MaxCpuInCores, precision: 10);
        Assert.Equal(expectedCores / 2, quota.BaselineCpuInCores, precision: 10);
    }
 
    [Fact]
    public void GetResourceQuota_CallMultipleTimes_ReturnsConsistentResults()
    {
        // Arrange
        var metadata = CreateKubernetesMetadata(
            limitsMemory: 2_147_483_648,
            limitsCpu: 2_000,
            requestsMemory: 1_073_741_824,
            requestsCpu: 1_000);
 
        var provider = new KubernetesResourceQuotaProvider(metadata);
 
        // Act
        var quota1 = provider.GetResourceQuota();
        var quota2 = provider.GetResourceQuota();
        var quota3 = provider.GetResourceQuota();
 
        // Assert
        Assert.Equal(quota1.MaxMemoryInBytes, quota2.MaxMemoryInBytes);
        Assert.Equal(quota1.MaxCpuInCores, quota2.MaxCpuInCores);
        Assert.Equal(quota1.BaselineMemoryInBytes, quota2.BaselineMemoryInBytes);
        Assert.Equal(quota1.BaselineCpuInCores, quota2.BaselineCpuInCores);
 
        Assert.Equal(quota2.MaxMemoryInBytes, quota3.MaxMemoryInBytes);
        Assert.Equal(quota2.MaxCpuInCores, quota3.MaxCpuInCores);
        Assert.Equal(quota2.BaselineMemoryInBytes, quota3.BaselineMemoryInBytes);
        Assert.Equal(quota2.BaselineCpuInCores, quota3.BaselineCpuInCores);
    }
 
    [Fact]
    public void GetResourceQuota_WithRequestsHigherThanLimits_ReturnsActualValues()
    {
        // Arrange - This scenario shouldn't normally happen in Kubernetes, but testing edge case
        ulong limitsMemory = 1_073_741_824; // 1GB
        ulong limitsCpu = 1_000; // 1 core
        ulong requestsMemory = 2_147_483_648; // 2GB (higher than limit)
        ulong requestsCpu = 2_000; // 2 cores (higher than limit)
 
        var metadata = CreateKubernetesMetadata(limitsMemory, limitsCpu, requestsMemory, requestsCpu);
        var provider = new KubernetesResourceQuotaProvider(metadata);
 
        // Act
        var quota = provider.GetResourceQuota();
 
        // Assert
        Assert.NotNull(quota);
        Assert.Equal(limitsMemory, quota.MaxMemoryInBytes); // Limits are preserved
        Assert.Equal(1.0, quota.MaxCpuInCores);
        Assert.Equal(requestsMemory, quota.BaselineMemoryInBytes); // Requests are preserved as-is
        Assert.Equal(2.0, quota.BaselineCpuInCores);
    }
 
    [Fact]
    public void GetResourceQuota_WithTypicalKubernetesValues_ReturnsExpectedQuota()
    {
        // Arrange - Typical Kubernetes pod resource configuration
        ulong limitsMemory = 4_294_967_296; // 4GB
        ulong limitsCpu = 4_000; // 4 cores in millicores
        ulong requestsMemory = 1_073_741_824; // 1GB
        ulong requestsCpu = 500; // 0.5 cores in millicores
 
        var metadata = CreateKubernetesMetadata(limitsMemory, limitsCpu, requestsMemory, requestsCpu);
        var provider = new KubernetesResourceQuotaProvider(metadata);
 
        // Act
        var quota = provider.GetResourceQuota();
 
        // Assert
        Assert.NotNull(quota);
        Assert.Equal(4_294_967_296UL, quota.MaxMemoryInBytes); // 4GB
        Assert.Equal(4.0, quota.MaxCpuInCores);
        Assert.Equal(1_073_741_824UL, quota.BaselineMemoryInBytes); // 1GB
        Assert.Equal(0.5, quota.BaselineCpuInCores);
    }
 
    [Theory]
    [InlineData(0UL, 1_000UL)] // Only CPU requests missing
    [InlineData(1_000UL, 0UL)] // Only memory requests missing
    public void GetResourceQuota_WithPartialRequests_AppliesFallbackLogicCorrectly(ulong requestsMemory, ulong requestsCpu)
    {
        // Arrange
        ulong limitsMemory = 2_147_483_648; // 2GB
        ulong limitsCpu = 2_000; // 2 cores
 
        var metadata = CreateKubernetesMetadata(limitsMemory, limitsCpu, requestsMemory, requestsCpu);
        var provider = new KubernetesResourceQuotaProvider(metadata);
 
        // Act
        var quota = provider.GetResourceQuota();
 
        // Assert
        Assert.NotNull(quota);
        Assert.Equal(limitsMemory, quota.MaxMemoryInBytes);
        Assert.Equal(2.0, quota.MaxCpuInCores);
 
        // Check fallback logic
        ulong expectedBaselineMemory = requestsMemory == 0 ? limitsMemory : requestsMemory;
        double expectedBaselineCpu = requestsCpu == 0 ? 2.0 : requestsCpu / 1000.0;
 
        Assert.Equal(expectedBaselineMemory, quota.BaselineMemoryInBytes);
        Assert.Equal(expectedBaselineCpu, quota.BaselineCpuInCores);
    }
 
    /// <summary>
    /// Creates a KubernetesMetadata instance with the specified values for testing.
    /// </summary>
    private static KubernetesMetadata CreateKubernetesMetadata(
        ulong limitsMemory,
        ulong limitsCpu,
        ulong requestsMemory,
        ulong requestsCpu)
    {
        return new KubernetesMetadata
        {
            LimitsMemory = limitsMemory,
            LimitsCpu = limitsCpu,
            RequestsMemory = requestsMemory,
            RequestsCpu = requestsCpu
        };
    }
}