Skip to content

Commit

Permalink
Add KubernetesProbesOptionsValidator to ensure different ports for pr…
Browse files Browse the repository at this point in the history
…obes (#5400)

* Add KubernetesProbesOptionsValidator to enforce different ports for probes

* Add another tests that KubernetesProbesOptions are validated when Host is starting

* update

---------

Co-authored-by: Yifan Zhu <yifzhu@microsoft.com>
  • Loading branch information
makazeu and Yifan Zhu authored Sep 25, 2024
1 parent 05df083 commit c799d76
Show file tree
Hide file tree
Showing 3 changed files with 151 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ public static IServiceCollection AddKubernetesProbes(this IServiceCollection ser
_ = Throw.IfNull(services);
_ = Throw.IfNull(configure);

_ = services
.AddOptionsWithValidateOnStart<KubernetesProbesOptions, KubernetesProbesOptionsValidator>()
.Configure(configure);

var wrapperOptions = new KubernetesProbesOptions();

return services
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.Extensions.Options;

namespace Microsoft.Extensions.Diagnostics.Probes;

internal sealed class KubernetesProbesOptionsValidator : IValidateOptions<KubernetesProbesOptions>
{
public ValidateOptionsResult Validate(string? name, KubernetesProbesOptions options)
{
var builder = new ValidateOptionsResultBuilder();

if (options.LivenessProbe.TcpPort == options.StartupProbe.TcpPort
|| options.LivenessProbe.TcpPort == options.ReadinessProbe.TcpPort
|| options.StartupProbe.TcpPort == options.ReadinessProbe.TcpPort)
{
builder.AddError("Liveness, startup and readiness probes must use different ports.");
}

return builder.Build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
// 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 System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using Xunit;

namespace Microsoft.Extensions.Diagnostics.Probes.Test;

public class KubernetesProbesOptionsValidatorTests
{
[Fact]
public void Validator_DefaultValues_Succeeds()
{
var options = new KubernetesProbesOptions();
ValidateOptionsResult result = new KubernetesProbesOptionsValidator().Validate(nameof(options), options);

Assert.True(result.Succeeded);
}

[Fact]
public void Validator_GivenValidOptions_Succeeds()
{
var options = new KubernetesProbesOptions();
options.LivenessProbe.TcpPort = 2305;
options.StartupProbe.TcpPort = 2306;
options.ReadinessProbe.TcpPort = 2307;

ValidateOptionsResult result = new KubernetesProbesOptionsValidator().Validate(nameof(options), options);

Assert.True(result.Succeeded);
}

[Fact]
public void Validator_GivenInvalidOptions_Fails()
{
var options = new KubernetesProbesOptions();
options.LivenessProbe.TcpPort = 2305;
options.StartupProbe.TcpPort = 2305;
options.ReadinessProbe.TcpPort = 2307;

var validator = new KubernetesProbesOptionsValidator();
ValidateOptionsResult result = validator.Validate(nameof(options), options);
Assert.True(result.Failed);

options.LivenessProbe.TcpPort = 2305;
options.StartupProbe.TcpPort = 2306;
options.ReadinessProbe.TcpPort = 2305;
result = validator.Validate(nameof(options), options);
Assert.True(result.Failed);

options.LivenessProbe.TcpPort = 2305;
options.StartupProbe.TcpPort = 2306;
options.ReadinessProbe.TcpPort = 2306;
result = validator.Validate(nameof(options), options);
Assert.True(result.Failed);

options.LivenessProbe.TcpPort = 2305;
options.StartupProbe.TcpPort = 2305;
options.ReadinessProbe.TcpPort = 2305;
result = validator.Validate(nameof(options), options);
Assert.True(result.Failed);
}

[Fact]
public async Task Validator_WhenHostStarts_Succeeds()
{
using IHost host = CreateHost(services =>
{
services.AddKubernetesProbes(options =>
{
options.LivenessProbe.TcpPort = 22305;
options.StartupProbe.TcpPort = 22306;
options.ReadinessProbe.TcpPort = 22307;
}).AddHealthChecks();
});

try
{
host.Start();
await host.StopAsync();
}
catch (OptionsValidationException ex)
{
Assert.Fail("Unexpected OptionsValidationException: " + ex.Message);
}
catch (Exception ex)
{
Assert.Fail("Unexpected exception: " + ex.Message);
throw;
}
}

[Fact]
public void Validator_WhenHostStarts_Fails()
{
Action action = () =>
{
using IHost host = CreateHost(services =>
{
services.AddKubernetesProbes(options =>
{
options.LivenessProbe.TcpPort = 22305;
options.StartupProbe.TcpPort = 22305;
options.ReadinessProbe.TcpPort = 22307;
}).AddHealthChecks();
});

host.Start();
};

Assert.Throws<OptionsValidationException>(action);
}

private static IHost CreateHost(Action<IServiceCollection> configureServices)
{
return Host.CreateDefaultBuilder()
.ConfigureServices(configureServices)
.Build();
}
}

0 comments on commit c799d76

Please sign in to comment.