mirror of
https://github.com/nsnail/Ocelot.git
synced 2025-04-22 14:02:49 +08:00
* #568 worked out how to check if qos handler present and kill Ocelot if options specified but no handler, need to refactor this into fluent validation style * #568 acceptance tests to make sure Ocelot won't start if the user specifies QoSOptions but doesnt have a QoSHandler registered
This commit is contained in:
parent
8db5570840
commit
29a7af9486
@ -11,6 +11,12 @@ namespace Ocelot.Configuration.Validator
|
||||
Errors = new List<Error>();
|
||||
}
|
||||
|
||||
public ConfigurationValidationResult(bool isError, Error error)
|
||||
{
|
||||
IsError = isError;
|
||||
Errors = new List<Error> { error };
|
||||
}
|
||||
|
||||
public ConfigurationValidationResult(bool isError, List<Error> errors)
|
||||
{
|
||||
IsError = isError;
|
||||
|
@ -15,13 +15,17 @@ namespace Ocelot.Configuration.Validator
|
||||
|
||||
public class FileConfigurationFluentValidator : AbstractValidator<FileConfiguration>, IConfigurationValidator
|
||||
{
|
||||
private readonly IServiceProvider _provider;
|
||||
private readonly QosDelegatingHandlerDelegate _qosDelegatingHandlerDelegate;
|
||||
|
||||
public FileConfigurationFluentValidator(IAuthenticationSchemeProvider authenticationSchemeProvider, IServiceProvider provider)
|
||||
{
|
||||
_provider = provider;
|
||||
_qosDelegatingHandlerDelegate = provider.GetService<QosDelegatingHandlerDelegate>();
|
||||
|
||||
RuleFor(configuration => configuration.ReRoutes)
|
||||
.SetCollectionValidator(new ReRouteFluentValidator(authenticationSchemeProvider, provider));
|
||||
.SetCollectionValidator(new ReRouteFluentValidator(authenticationSchemeProvider, _qosDelegatingHandlerDelegate));
|
||||
|
||||
RuleFor(configuration => configuration.GlobalConfiguration)
|
||||
.SetValidator(new FileGlobalConfigurationFluentValidator(_qosDelegatingHandlerDelegate));
|
||||
|
||||
RuleForEach(configuration => configuration.ReRoutes)
|
||||
.Must((config, reRoute) => IsNotDuplicateIn(reRoute, config.ReRoutes))
|
||||
@ -44,13 +48,6 @@ namespace Ocelot.Configuration.Validator
|
||||
.WithMessage((config, aggregateReRoute) => $"{nameof(aggregateReRoute)} {aggregateReRoute.UpstreamPathTemplate} contains ReRoute with specific RequestIdKey, this is not possible with Aggregates");
|
||||
}
|
||||
|
||||
private bool AllReRoutesForAggregateExist(FileAggregateReRoute fileAggregateReRoute, List<FileReRoute> reRoutes)
|
||||
{
|
||||
var reRoutesForAggregate = reRoutes.Where(r => fileAggregateReRoute.ReRouteKeys.Contains(r.Key));
|
||||
|
||||
return reRoutesForAggregate.Count() == fileAggregateReRoute.ReRouteKeys.Count;
|
||||
}
|
||||
|
||||
public async Task<Response<ConfigurationValidationResult>> IsValid(FileConfiguration configuration)
|
||||
{
|
||||
var validateResult = await ValidateAsync(configuration);
|
||||
@ -67,6 +64,13 @@ namespace Ocelot.Configuration.Validator
|
||||
return new OkResponse<ConfigurationValidationResult>(result);
|
||||
}
|
||||
|
||||
private bool AllReRoutesForAggregateExist(FileAggregateReRoute fileAggregateReRoute, List<FileReRoute> reRoutes)
|
||||
{
|
||||
var reRoutesForAggregate = reRoutes.Where(r => fileAggregateReRoute.ReRouteKeys.Contains(r.Key));
|
||||
|
||||
return reRoutesForAggregate.Count() == fileAggregateReRoute.ReRouteKeys.Count;
|
||||
}
|
||||
|
||||
private static bool DoesNotContainReRoutesWithSpecificRequestIdKeys(FileAggregateReRoute fileAggregateReRoute,
|
||||
List<FileReRoute> reRoutes)
|
||||
{
|
||||
|
@ -0,0 +1,21 @@
|
||||
using FluentValidation;
|
||||
using Ocelot.Configuration.File;
|
||||
|
||||
namespace Ocelot.Configuration.Validator
|
||||
{
|
||||
using Requester;
|
||||
|
||||
public class FileGlobalConfigurationFluentValidator : AbstractValidator<FileGlobalConfiguration>
|
||||
{
|
||||
private readonly QosDelegatingHandlerDelegate _qosDelegatingHandlerDelegate;
|
||||
|
||||
public FileGlobalConfigurationFluentValidator(QosDelegatingHandlerDelegate qosDelegatingHandlerDelegate)
|
||||
{
|
||||
_qosDelegatingHandlerDelegate = qosDelegatingHandlerDelegate;
|
||||
|
||||
RuleFor(configuration => configuration.QoSOptions)
|
||||
.SetValidator(new FileQoSOptionsFluentValidator(_qosDelegatingHandlerDelegate));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
using FluentValidation;
|
||||
using Ocelot.Configuration.File;
|
||||
|
||||
namespace Ocelot.Configuration.Validator
|
||||
{
|
||||
using Requester;
|
||||
|
||||
public class FileQoSOptionsFluentValidator : AbstractValidator<FileQoSOptions>
|
||||
{
|
||||
private readonly QosDelegatingHandlerDelegate _qosDelegatingHandlerDelegate;
|
||||
|
||||
public FileQoSOptionsFluentValidator(QosDelegatingHandlerDelegate qosDelegatingHandlerDelegate)
|
||||
{
|
||||
_qosDelegatingHandlerDelegate = qosDelegatingHandlerDelegate;
|
||||
|
||||
When(qosOptions => qosOptions.TimeoutValue > 0 && qosOptions.ExceptionsAllowedBeforeBreaking > 0, () => {
|
||||
RuleFor(qosOptions => qosOptions)
|
||||
.Must(HaveQosHandlerRegistered)
|
||||
.WithMessage("Unable to start Ocelot because either a ReRoute or GlobalConfiguration are using QoSOptions but no QosDelegatingHandlerDelegate has been registered in dependency injection container. Are you missing a package like Ocelot.Provider.Polly and services.AddPolly()?");
|
||||
});
|
||||
}
|
||||
|
||||
private bool HaveQosHandlerRegistered(FileQoSOptions arg)
|
||||
{
|
||||
return _qosDelegatingHandlerDelegate != null;
|
||||
}
|
||||
}
|
||||
}
|
@ -13,12 +13,15 @@
|
||||
public class ReRouteFluentValidator : AbstractValidator<FileReRoute>
|
||||
{
|
||||
private readonly IAuthenticationSchemeProvider _authenticationSchemeProvider;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly QosDelegatingHandlerDelegate _qosDelegatingHandlerDelegate;
|
||||
|
||||
public ReRouteFluentValidator(IAuthenticationSchemeProvider authenticationSchemeProvider, IServiceProvider serviceProvider)
|
||||
public ReRouteFluentValidator(IAuthenticationSchemeProvider authenticationSchemeProvider, QosDelegatingHandlerDelegate qosDelegatingHandlerDelegate)
|
||||
{
|
||||
_authenticationSchemeProvider = authenticationSchemeProvider;
|
||||
_serviceProvider = serviceProvider;
|
||||
_qosDelegatingHandlerDelegate = qosDelegatingHandlerDelegate;
|
||||
|
||||
RuleFor(reRoute => reRoute.QoSOptions)
|
||||
.SetValidator(new FileQoSOptionsFluentValidator(_qosDelegatingHandlerDelegate));
|
||||
|
||||
RuleFor(reRoute => reRoute.DownstreamPathTemplate)
|
||||
.Must(path => path.StartsWith("/"))
|
||||
|
@ -15,6 +15,101 @@ namespace Ocelot.AcceptanceTests
|
||||
_steps = new Steps();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_throw_exception_if_cannot_start_because_no_qos_delegate_registered_globally()
|
||||
{
|
||||
var invalidConfig = new FileConfiguration
|
||||
{
|
||||
ReRoutes = new List<FileReRoute>
|
||||
{
|
||||
new FileReRoute
|
||||
{
|
||||
DownstreamPathTemplate = "/",
|
||||
DownstreamScheme = "http",
|
||||
DownstreamHostAndPorts = new List<FileHostAndPort>
|
||||
{
|
||||
new FileHostAndPort
|
||||
{
|
||||
Host = "localhost",
|
||||
Port = 51878,
|
||||
}
|
||||
},
|
||||
UpstreamPathTemplate = "/laura",
|
||||
UpstreamHttpMethod = new List<string> { "Get" },
|
||||
Key = "Laura",
|
||||
}
|
||||
},
|
||||
GlobalConfiguration = new FileGlobalConfiguration
|
||||
{
|
||||
QoSOptions = new FileQoSOptions
|
||||
{
|
||||
TimeoutValue = 1,
|
||||
ExceptionsAllowedBeforeBreaking = 1
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Exception exception = null;
|
||||
_steps.GivenThereIsAConfiguration(invalidConfig);
|
||||
try
|
||||
{
|
||||
_steps.GivenOcelotIsRunning();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
exception = ex;
|
||||
}
|
||||
|
||||
exception.ShouldNotBeNull();
|
||||
exception.Message.ShouldBe("One or more errors occurred. (Unable to start Ocelot, errors are: Unable to start Ocelot because either a ReRoute or GlobalConfiguration are using QoSOptions but no QosDelegatingHandlerDelegate has been registered in dependency injection container. Are you missing a package like Ocelot.Provider.Polly and services.AddPolly()?)");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_throw_exception_if_cannot_start_because_no_qos_delegate_registered_for_re_route()
|
||||
{
|
||||
var invalidConfig = new FileConfiguration
|
||||
{
|
||||
ReRoutes = new List<FileReRoute>
|
||||
{
|
||||
new FileReRoute
|
||||
{
|
||||
DownstreamPathTemplate = "/",
|
||||
DownstreamScheme = "http",
|
||||
DownstreamHostAndPorts = new List<FileHostAndPort>
|
||||
{
|
||||
new FileHostAndPort
|
||||
{
|
||||
Host = "localhost",
|
||||
Port = 51878,
|
||||
}
|
||||
},
|
||||
UpstreamPathTemplate = "/laura",
|
||||
UpstreamHttpMethod = new List<string> { "Get" },
|
||||
Key = "Laura",
|
||||
QoSOptions = new FileQoSOptions
|
||||
{
|
||||
TimeoutValue = 1,
|
||||
ExceptionsAllowedBeforeBreaking = 1
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Exception exception = null;
|
||||
_steps.GivenThereIsAConfiguration(invalidConfig);
|
||||
try
|
||||
{
|
||||
_steps.GivenOcelotIsRunning();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
exception = ex;
|
||||
}
|
||||
|
||||
exception.ShouldNotBeNull();
|
||||
exception.Message.ShouldBe("One or more errors occurred. (Unable to start Ocelot, errors are: Unable to start Ocelot because either a ReRoute or GlobalConfiguration are using QoSOptions but no QosDelegatingHandlerDelegate has been registered in dependency injection container. Are you missing a package like Ocelot.Provider.Polly and services.AddPolly()?)");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_throw_exception_if_cannot_start()
|
||||
{
|
||||
|
@ -18,19 +18,179 @@
|
||||
using Ocelot.Requester;
|
||||
using Requester;
|
||||
|
||||
public class ConfigurationFluentValidationTests
|
||||
public class FileConfigurationFluentValidatorTests
|
||||
{
|
||||
private IConfigurationValidator _configurationValidator;
|
||||
private FileConfiguration _fileConfiguration;
|
||||
private Response<ConfigurationValidationResult> _result;
|
||||
private readonly Mock<IAuthenticationSchemeProvider> _provider;
|
||||
private readonly Mock<IAuthenticationSchemeProvider> _authProvider;
|
||||
|
||||
public ConfigurationFluentValidationTests()
|
||||
public FileConfigurationFluentValidatorTests()
|
||||
{
|
||||
_provider = new Mock<IAuthenticationSchemeProvider>();
|
||||
_authProvider = new Mock<IAuthenticationSchemeProvider>();
|
||||
var provider = new ServiceCollection()
|
||||
.BuildServiceProvider();
|
||||
_configurationValidator = new FileConfigurationFluentValidator(_provider.Object, provider);
|
||||
_configurationValidator = new FileConfigurationFluentValidator(_authProvider.Object, provider);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void configuration_is_valid_if_qos_options_specified_and_has_qos_handler()
|
||||
{
|
||||
var configuration = new FileConfiguration
|
||||
{
|
||||
ReRoutes = new List<FileReRoute>
|
||||
{
|
||||
new FileReRoute
|
||||
{
|
||||
DownstreamPathTemplate = "/",
|
||||
DownstreamScheme = "http",
|
||||
DownstreamHostAndPorts = new List<FileHostAndPort>
|
||||
{
|
||||
new FileHostAndPort
|
||||
{
|
||||
Host = "localhost",
|
||||
Port = 51878,
|
||||
}
|
||||
},
|
||||
UpstreamPathTemplate = "/laura",
|
||||
UpstreamHttpMethod = new List<string> { "Get" },
|
||||
Key = "Laura",
|
||||
QoSOptions = new FileQoSOptions
|
||||
{
|
||||
TimeoutValue = 1,
|
||||
ExceptionsAllowedBeforeBreaking = 1
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.Given(x => x.GivenAConfiguration(configuration))
|
||||
.And(x => x.GivenAQoSHandler())
|
||||
.When(x => x.WhenIValidateTheConfiguration())
|
||||
.Then(x => x.ThenTheResultIsValid())
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void configuration_is_valid_if_qos_options_specified_globally_and_has_qos_handler()
|
||||
{
|
||||
var configuration = new FileConfiguration
|
||||
{
|
||||
ReRoutes = new List<FileReRoute>
|
||||
{
|
||||
new FileReRoute
|
||||
{
|
||||
DownstreamPathTemplate = "/",
|
||||
DownstreamScheme = "http",
|
||||
DownstreamHostAndPorts = new List<FileHostAndPort>
|
||||
{
|
||||
new FileHostAndPort
|
||||
{
|
||||
Host = "localhost",
|
||||
Port = 51878,
|
||||
}
|
||||
},
|
||||
UpstreamPathTemplate = "/laura",
|
||||
UpstreamHttpMethod = new List<string> { "Get" },
|
||||
Key = "Laura",
|
||||
}
|
||||
},
|
||||
GlobalConfiguration = new FileGlobalConfiguration
|
||||
{
|
||||
QoSOptions = new FileQoSOptions
|
||||
{
|
||||
TimeoutValue = 1,
|
||||
ExceptionsAllowedBeforeBreaking = 1
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.Given(x => x.GivenAConfiguration(configuration))
|
||||
.And(x => x.GivenAQoSHandler())
|
||||
.When(x => x.WhenIValidateTheConfiguration())
|
||||
.Then(x => x.ThenTheResultIsValid())
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void configuration_is_invalid_if_qos_options_specified_but_no_qos_handler()
|
||||
{
|
||||
var configuration = new FileConfiguration
|
||||
{
|
||||
ReRoutes = new List<FileReRoute>
|
||||
{
|
||||
new FileReRoute
|
||||
{
|
||||
DownstreamPathTemplate = "/",
|
||||
DownstreamScheme = "http",
|
||||
DownstreamHostAndPorts = new List<FileHostAndPort>
|
||||
{
|
||||
new FileHostAndPort
|
||||
{
|
||||
Host = "localhost",
|
||||
Port = 51878,
|
||||
}
|
||||
},
|
||||
UpstreamPathTemplate = "/laura",
|
||||
UpstreamHttpMethod = new List<string> { "Get" },
|
||||
Key = "Laura",
|
||||
QoSOptions = new FileQoSOptions
|
||||
{
|
||||
TimeoutValue = 1,
|
||||
ExceptionsAllowedBeforeBreaking = 1
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.Given(x => x.GivenAConfiguration(configuration))
|
||||
.When(x => x.WhenIValidateTheConfiguration())
|
||||
.Then(x => x.ThenTheResultIsNotValid())
|
||||
.And(x => x.ThenTheErrorIs<FileValidationFailedError>())
|
||||
.And(x => x.ThenTheErrorMessageAtPositionIs(0, "Unable to start Ocelot because either a ReRoute or GlobalConfiguration are using QoSOptions but no QosDelegatingHandlerDelegate has been registered in dependency injection container. Are you missing a package like Ocelot.Provider.Polly and services.AddPolly()?"))
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void configuration_is_invalid_if_qos_options_specified_globally_but_no_qos_handler()
|
||||
{
|
||||
var configuration = new FileConfiguration
|
||||
{
|
||||
ReRoutes = new List<FileReRoute>
|
||||
{
|
||||
new FileReRoute
|
||||
{
|
||||
DownstreamPathTemplate = "/",
|
||||
DownstreamScheme = "http",
|
||||
DownstreamHostAndPorts = new List<FileHostAndPort>
|
||||
{
|
||||
new FileHostAndPort
|
||||
{
|
||||
Host = "localhost",
|
||||
Port = 51878,
|
||||
}
|
||||
},
|
||||
UpstreamPathTemplate = "/laura",
|
||||
UpstreamHttpMethod = new List<string> { "Get" },
|
||||
Key = "Laura",
|
||||
}
|
||||
},
|
||||
GlobalConfiguration = new FileGlobalConfiguration
|
||||
{
|
||||
QoSOptions = new FileQoSOptions
|
||||
{
|
||||
TimeoutValue = 1,
|
||||
ExceptionsAllowedBeforeBreaking = 1
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.Given(x => x.GivenAConfiguration(configuration))
|
||||
.When(x => x.WhenIValidateTheConfiguration())
|
||||
.Then(x => x.ThenTheResultIsNotValid())
|
||||
.And(x => x.ThenTheErrorIs<FileValidationFailedError>())
|
||||
.And(x => x.ThenTheErrorMessageAtPositionIs(0, "Unable to start Ocelot because either a ReRoute or GlobalConfiguration are using QoSOptions but no QosDelegatingHandlerDelegate has been registered in dependency injection container. Are you missing a package like Ocelot.Provider.Polly and services.AddPolly()?"))
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@ -1045,19 +1205,19 @@
|
||||
|
||||
private void GivenTheAuthSchemeExists(string name)
|
||||
{
|
||||
_provider.Setup(x => x.GetAllSchemesAsync()).ReturnsAsync(new List<AuthenticationScheme>
|
||||
_authProvider.Setup(x => x.GetAllSchemesAsync()).ReturnsAsync(new List<AuthenticationScheme>
|
||||
{
|
||||
new AuthenticationScheme(name, name, typeof(TestHandler))
|
||||
});
|
||||
}
|
||||
|
||||
private void GivenAQosDelegate()
|
||||
private void GivenAQoSHandler()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
var collection = new ServiceCollection();
|
||||
QosDelegatingHandlerDelegate del = (a,b) => new FakeDelegatingHandler();
|
||||
services.AddSingleton<QosDelegatingHandlerDelegate>(del);
|
||||
var provider = services.BuildServiceProvider();
|
||||
_configurationValidator = new FileConfigurationFluentValidator(_provider.Object, provider);
|
||||
collection.AddSingleton<QosDelegatingHandlerDelegate>(del);
|
||||
var provider = collection.BuildServiceProvider();
|
||||
_configurationValidator = new FileConfigurationFluentValidator(_authProvider.Object, provider);
|
||||
}
|
||||
|
||||
private class TestOptions : AuthenticationSchemeOptions
|
Loading…
x
Reference in New Issue
Block a user