started implementing service discovery integration

This commit is contained in:
Tom Gardham-Pallister 2017-01-20 19:03:18 +00:00
parent 622c49d057
commit 044b609ea9
16 changed files with 346 additions and 32 deletions

8
run-acceptance-tests.bat Executable file
View File

@ -0,0 +1,8 @@
echo Running Ocelot.AcceptanceTests
cd test/Ocelot.AcceptanceTests/
dotnet restore
dotnet test
cd ../../
echo Restoring Ocelot.ManualTest
dotnet restore test/Ocelot.ManualTest/

View File

@ -1,17 +1,2 @@
echo ------------------------- ./run-unit-tests.bat
./run-acceptance-tests.bat
echo Restoring Ocelot
dotnet restore src/Ocelot
echo Restoring Ocelot.ManualTest
dotnet restore test/Ocelot.ManualTest/
echo Running Ocelot.UnitTests
dotnet restore test/Ocelot.UnitTests/
dotnet test test/Ocelot.UnitTests/
echo Running Ocelot.AcceptanceTests
cd test/Ocelot.AcceptanceTests/
dotnet restore
dotnet test
cd ../../

8
run-unit-tests.bat Executable file
View File

@ -0,0 +1,8 @@
echo -------------------------
echo Restoring Ocelot
dotnet restore src/Ocelot
echo Running Ocelot.UnitTests
dotnet restore test/Ocelot.UnitTests/
dotnet test test/Ocelot.UnitTests/

View File

@ -1,4 +1,5 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
namespace Ocelot.Configuration.Builder namespace Ocelot.Configuration.Builder
{ {
@ -23,12 +24,54 @@ namespace Ocelot.Configuration.Builder
private string _requestIdHeaderKey; private string _requestIdHeaderKey;
private bool _isCached; private bool _isCached;
private CacheOptions _fileCacheOptions; private CacheOptions _fileCacheOptions;
private bool _useServiceDiscovery;
private string _serviceName;
private string _serviceDiscoveryProvider;
private string _serviceDiscoveryAddress;
private string _downstreamScheme;
private string _downstreamHost;
public ReRouteBuilder() public ReRouteBuilder()
{ {
_additionalScopes = new List<string>(); _additionalScopes = new List<string>();
} }
public ReRouteBuilder WithDownstreamScheme(string downstreamScheme)
{
_downstreamScheme = downstreamScheme;
return this;
}
public ReRouteBuilder WithDownstreamHost(string downstreamHost)
{
_downstreamHost = downstreamHost;
return this;
}
public ReRouteBuilder WithServiceDiscoveryAddress(string serviceDiscoveryAddress)
{
_serviceDiscoveryAddress = serviceDiscoveryAddress;
return this;
}
public ReRouteBuilder WithServiceDiscoveryProvider(string serviceDiscoveryProvider)
{
_serviceDiscoveryProvider = serviceDiscoveryProvider;
return this;
}
public ReRouteBuilder WithServiceName(string serviceName)
{
_serviceName = serviceName;
return this;
}
public ReRouteBuilder WithUseServiceDiscovery(bool useServiceDiscovery)
{
_useServiceDiscovery = useServiceDiscovery;
return this;
}
public ReRouteBuilder WithDownstreamTemplate(string input) public ReRouteBuilder WithDownstreamTemplate(string input)
{ {
_downstreamTemplate = input; _downstreamTemplate = input;
@ -143,10 +186,13 @@ namespace Ocelot.Configuration.Builder
public ReRoute Build() public ReRoute Build()
{ {
Func<string> downstreamHostFunc = () => { return _downstreamHost; };
return new ReRoute(_downstreamTemplate, _upstreamTemplate, _upstreamHttpMethod, _upstreamTemplatePattern, return new ReRoute(_downstreamTemplate, _upstreamTemplate, _upstreamHttpMethod, _upstreamTemplatePattern,
_isAuthenticated, new AuthenticationOptions(_authenticationProvider, _authenticationProviderUrl, _scopeName, _isAuthenticated, new AuthenticationOptions(_authenticationProvider, _authenticationProviderUrl, _scopeName,
_requireHttps, _additionalScopes, _scopeSecret), _configHeaderExtractorProperties, _claimToClaims, _routeClaimRequirement, _requireHttps, _additionalScopes, _scopeSecret), _configHeaderExtractorProperties, _claimToClaims, _routeClaimRequirement,
_isAuthorised, _claimToQueries, _requestIdHeaderKey, _isCached, _fileCacheOptions); _isAuthorised, _claimToQueries, _requestIdHeaderKey, _isCached, _fileCacheOptions, _serviceName,
_useServiceDiscovery, _serviceDiscoveryAddress, _serviceDiscoveryProvider, downstreamHostFunc, _downstreamScheme);
} }
} }
} }

View File

@ -91,6 +91,13 @@ namespace Ocelot.Configuration.Creator
? globalConfiguration.RequestIdKey ? globalConfiguration.RequestIdKey
: reRoute.RequestIdKey; : reRoute.RequestIdKey;
var useServiceDiscovery = !string.IsNullOrEmpty(reRoute.ServiceName)
&& !string.IsNullOrEmpty(globalConfiguration?.ServiceDiscoveryProvider?.Address)
&& !string.IsNullOrEmpty(globalConfiguration?.ServiceDiscoveryProvider?.Provider);
Func<string> downstreamHostFunc = () => { return reRoute.DownstreamHost; };
if (isAuthenticated) if (isAuthenticated)
{ {
var authOptionsForRoute = new AuthenticationOptions(reRoute.AuthenticationOptions.Provider, var authOptionsForRoute = new AuthenticationOptions(reRoute.AuthenticationOptions.Provider,
@ -106,14 +113,18 @@ namespace Ocelot.Configuration.Creator
reRoute.UpstreamHttpMethod, upstreamTemplate, isAuthenticated, reRoute.UpstreamHttpMethod, upstreamTemplate, isAuthenticated,
authOptionsForRoute, claimsToHeaders, claimsToClaims, authOptionsForRoute, claimsToHeaders, claimsToClaims,
reRoute.RouteClaimsRequirement, isAuthorised, claimsToQueries, reRoute.RouteClaimsRequirement, isAuthorised, claimsToQueries,
requestIdKey, isCached, new CacheOptions(reRoute.FileCacheOptions.TtlSeconds)); requestIdKey, isCached, new CacheOptions(reRoute.FileCacheOptions.TtlSeconds),
reRoute.ServiceName, useServiceDiscovery, globalConfiguration?.ServiceDiscoveryProvider?.Provider,
globalConfiguration?.ServiceDiscoveryProvider?.Address, downstreamHostFunc, reRoute.DownstreamScheme);
} }
return new ReRoute(reRoute.DownstreamTemplate, reRoute.UpstreamTemplate, return new ReRoute(reRoute.DownstreamTemplate, reRoute.UpstreamTemplate,
reRoute.UpstreamHttpMethod, upstreamTemplate, isAuthenticated, reRoute.UpstreamHttpMethod, upstreamTemplate, isAuthenticated,
null, new List<ClaimToThing>(), new List<ClaimToThing>(), null, new List<ClaimToThing>(), new List<ClaimToThing>(),
reRoute.RouteClaimsRequirement, isAuthorised, new List<ClaimToThing>(), reRoute.RouteClaimsRequirement, isAuthorised, new List<ClaimToThing>(),
requestIdKey, isCached, new CacheOptions(reRoute.FileCacheOptions.TtlSeconds)); requestIdKey, isCached, new CacheOptions(reRoute.FileCacheOptions.TtlSeconds),
reRoute.ServiceName, useServiceDiscovery, globalConfiguration?.ServiceDiscoveryProvider?.Provider,
globalConfiguration?.ServiceDiscoveryProvider?.Address, downstreamHostFunc, reRoute.DownstreamScheme);
} }
private string BuildUpstreamTemplate(FileReRoute reRoute) private string BuildUpstreamTemplate(FileReRoute reRoute)

View File

@ -2,6 +2,11 @@
{ {
public class FileGlobalConfiguration public class FileGlobalConfiguration
{ {
public FileGlobalConfiguration()
{
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider();
}
public string RequestIdKey { get; set; } public string RequestIdKey { get; set; }
public FileServiceDiscoveryProvider ServiceDiscoveryProvider {get;set;}
} }
} }

View File

@ -25,5 +25,8 @@ namespace Ocelot.Configuration.File
public string RequestIdKey { get; set; } public string RequestIdKey { get; set; }
public FileCacheOptions FileCacheOptions { get; set; } public FileCacheOptions FileCacheOptions { get; set; }
public bool ReRouteIsCaseSensitive { get; set; } public bool ReRouteIsCaseSensitive { get; set; }
public string ServiceName { get; set; }
public string DownstreamScheme {get;set;}
public string DownstreamHost {get;set;}
} }
} }

View File

@ -0,0 +1,8 @@
namespace Ocelot.Configuration.File
{
public class FileServiceDiscoveryProvider
{
public string Provider {get;set;}
public string Address {get;set;}
}
}

View File

@ -1,4 +1,5 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
namespace Ocelot.Configuration namespace Ocelot.Configuration
{ {
@ -7,7 +8,8 @@ namespace Ocelot.Configuration
public ReRoute(string downstreamTemplate, string upstreamTemplate, string upstreamHttpMethod, string upstreamTemplatePattern, public ReRoute(string downstreamTemplate, string upstreamTemplate, string upstreamHttpMethod, string upstreamTemplatePattern,
bool isAuthenticated, AuthenticationOptions authenticationOptions, List<ClaimToThing> configurationHeaderExtractorProperties, bool isAuthenticated, AuthenticationOptions authenticationOptions, List<ClaimToThing> configurationHeaderExtractorProperties,
List<ClaimToThing> claimsToClaims, Dictionary<string, string> routeClaimsRequirement, bool isAuthorised, List<ClaimToThing> claimsToQueries, List<ClaimToThing> claimsToClaims, Dictionary<string, string> routeClaimsRequirement, bool isAuthorised, List<ClaimToThing> claimsToQueries,
string requestIdKey, bool isCached, CacheOptions fileCacheOptions) string requestIdKey, bool isCached, CacheOptions fileCacheOptions, string serviceName, bool useServiceDiscovery,
string serviceDiscoveryProvider, string serviceDiscoveryAddress, Func<string> downstreamHost, string downstreamScheme)
{ {
DownstreamTemplate = downstreamTemplate; DownstreamTemplate = downstreamTemplate;
UpstreamTemplate = upstreamTemplate; UpstreamTemplate = upstreamTemplate;
@ -26,6 +28,12 @@ namespace Ocelot.Configuration
?? new List<ClaimToThing>(); ?? new List<ClaimToThing>();
ClaimsToHeaders = configurationHeaderExtractorProperties ClaimsToHeaders = configurationHeaderExtractorProperties
?? new List<ClaimToThing>(); ?? new List<ClaimToThing>();
ServiceName = serviceName;
UseServiceDiscovery = useServiceDiscovery;
ServiceDiscoveryProvider = serviceDiscoveryProvider;
ServiceDiscoveryAddress = serviceDiscoveryAddress;
DownstreamHost = downstreamHost;
DownstreamScheme = downstreamScheme;
} }
public string DownstreamTemplate { get; private set; } public string DownstreamTemplate { get; private set; }
@ -42,5 +50,11 @@ namespace Ocelot.Configuration
public string RequestIdKey { get; private set; } public string RequestIdKey { get; private set; }
public bool IsCached { get; private set; } public bool IsCached { get; private set; }
public CacheOptions FileCacheOptions { get; private set; } public CacheOptions FileCacheOptions { get; private set; }
public string ServiceName { get; private set;}
public bool UseServiceDiscovery { get; private set;}
public string ServiceDiscoveryProvider { get; private set;}
public string ServiceDiscoveryAddress { get; private set;}
public Func<string> DownstreamHost {get;private set;}
public string DownstreamScheme {get;private set;}
} }
} }

View File

@ -0,0 +1,12 @@
using Ocelot.Errors;
namespace Ocelot.Configuration.Validator
{
public class DownstreamTemplateContainsHostError : Error
{
public DownstreamTemplateContainsHostError(string message)
: base(message, OcelotErrorCode.DownstreamTemplateContainsHostError)
{
}
}
}

View File

@ -0,0 +1,12 @@
using Ocelot.Errors;
namespace Ocelot.Configuration.Validator
{
public class DownstreamTemplateContainsSchemeError : Error
{
public DownstreamTemplateContainsSchemeError(string message)
: base(message, OcelotErrorCode.DownstreamTemplateContainsSchemeError)
{
}
}
}

View File

@ -26,6 +26,13 @@ namespace Ocelot.Configuration.Validator
return new OkResponse<ConfigurationValidationResult>(result); return new OkResponse<ConfigurationValidationResult>(result);
} }
result = CheckForReRoutesContainingDownstreamScheme(configuration);
if (result.IsError)
{
return new OkResponse<ConfigurationValidationResult>(result);
}
return new OkResponse<ConfigurationValidationResult>(result); return new OkResponse<ConfigurationValidationResult>(result);
} }
@ -63,6 +70,27 @@ namespace Ocelot.Configuration.Validator
return Enum.TryParse(provider, true, out supportedProvider); return Enum.TryParse(provider, true, out supportedProvider);
} }
private ConfigurationValidationResult CheckForReRoutesContainingDownstreamScheme(FileConfiguration configuration)
{
var errors = new List<Error>();
foreach(var reRoute in configuration.ReRoutes)
{
if(reRoute.DownstreamTemplate.Contains("https://")
|| reRoute.DownstreamTemplate.Contains("http://"))
{
errors.Add(new DownstreamTemplateContainsSchemeError($"{reRoute.DownstreamTemplate} contains scheme"));
}
}
if(errors.Any())
{
return new ConfigurationValidationResult(false, errors);
}
return new ConfigurationValidationResult(true, errors);
}
private ConfigurationValidationResult CheckForDupliateReRoutes(FileConfiguration configuration) private ConfigurationValidationResult CheckForDupliateReRoutes(FileConfiguration configuration)
{ {
var hasDupes = configuration.ReRoutes var hasDupes = configuration.ReRoutes

View File

@ -17,6 +17,8 @@
InstructionNotForClaimsError, InstructionNotForClaimsError,
UnauthorizedError, UnauthorizedError,
ClaimValueNotAuthorisedError, ClaimValueNotAuthorisedError,
UserDoesNotHaveClaimError UserDoesNotHaveClaimError,
DownstreamTemplateContainsSchemeError,
DownstreamTemplateContainsHostError
} }
} }

View File

@ -19,6 +19,44 @@ namespace Ocelot.UnitTests.Configuration
_configurationValidator = new FileConfigurationValidator(); _configurationValidator = new FileConfigurationValidator();
} }
[Fact]
public void configuration_is_invalid_if_scheme_in_downstream_template()
{
this.Given(x => x.GivenAConfiguration(new FileConfiguration()
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
DownstreamTemplate = "http://www.bbc.co.uk/api/products/{productId}",
UpstreamTemplate = "http://asdf.com"
}
}
}))
.When(x => x.WhenIValidateTheConfiguration())
.Then(x => x.ThenTheResultIsNotValid())
.BDDfy();
}
[Fact]
public void configuration_is_invalid_if_host_in_downstream_template()
{
this.Given(x => x.GivenAConfiguration(new FileConfiguration()
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
DownstreamTemplate = "www.bbc.co.uk/api/products/{productId}",
UpstreamTemplate = "http://asdf.com"
}
}
}))
.When(x => x.WhenIValidateTheConfiguration())
.Then(x => x.ThenTheResultIsNotValid())
.BDDfy();
}
[Fact] [Fact]
public void configuration_is_valid_with_one_reroute() public void configuration_is_valid_with_one_reroute()
{ {
@ -28,7 +66,7 @@ namespace Ocelot.UnitTests.Configuration
{ {
new FileReRoute new FileReRoute
{ {
DownstreamTemplate = "http://www.bbc.co.uk", DownstreamTemplate = "/api/products/",
UpstreamTemplate = "http://asdf.com" UpstreamTemplate = "http://asdf.com"
} }
} }
@ -47,7 +85,7 @@ namespace Ocelot.UnitTests.Configuration
{ {
new FileReRoute new FileReRoute
{ {
DownstreamTemplate = "http://www.bbc.co.uk", DownstreamTemplate = "/api/products/",
UpstreamTemplate = "http://asdf.com", UpstreamTemplate = "http://asdf.com",
AuthenticationOptions = new FileAuthenticationOptions AuthenticationOptions = new FileAuthenticationOptions
{ {
@ -70,7 +108,7 @@ namespace Ocelot.UnitTests.Configuration
{ {
new FileReRoute new FileReRoute
{ {
DownstreamTemplate = "http://www.bbc.co.uk", DownstreamTemplate = "/api/products/",
UpstreamTemplate = "http://asdf.com", UpstreamTemplate = "http://asdf.com",
AuthenticationOptions = new FileAuthenticationOptions AuthenticationOptions = new FileAuthenticationOptions
{ {
@ -94,7 +132,7 @@ namespace Ocelot.UnitTests.Configuration
{ {
new FileReRoute new FileReRoute
{ {
DownstreamTemplate = "http://www.bbc.co.uk", DownstreamTemplate = "/api/products/",
UpstreamTemplate = "http://asdf.com" UpstreamTemplate = "http://asdf.com"
}, },
new FileReRoute new FileReRoute

View File

@ -35,6 +35,143 @@ namespace Ocelot.UnitTests.Configuration
_fileConfig.Object, _validator.Object, _configParser.Object, _logger.Object); _fileConfig.Object, _validator.Object, _configParser.Object, _logger.Object);
} }
[Fact]
public void should_use_downstream_host()
{
this.Given(x => x.GivenTheConfigIs(new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
DownstreamHost = "127.0.0.1",
UpstreamTemplate = "/api/products/{productId}",
DownstreamTemplate = "/products/{productId}",
UpstreamHttpMethod = "Get",
}
},
}))
.And(x => x.GivenTheConfigIsValid())
.When(x => x.WhenICreateTheConfig())
.Then(x => x.ThenTheReRoutesAre(new List<ReRoute>
{
new ReRouteBuilder()
.WithDownstreamHost("127.0.0.1")
.WithDownstreamTemplate("/products/{productId}")
.WithUpstreamTemplate("/api/products/{productId}")
.WithUpstreamHttpMethod("Get")
.WithUpstreamTemplatePattern("(?i)/api/products/.*/$")
.Build()
}))
.BDDfy();
}
public void should_use_downstream_scheme()
{
this.Given(x => x.GivenTheConfigIs(new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
DownstreamScheme = "https",
UpstreamTemplate = "/api/products/{productId}",
DownstreamTemplate = "/products/{productId}",
UpstreamHttpMethod = "Get",
}
},
}))
.And(x => x.GivenTheConfigIsValid())
.When(x => x.WhenICreateTheConfig())
.Then(x => x.ThenTheReRoutesAre(new List<ReRoute>
{
new ReRouteBuilder()
.WithDownstreamScheme("https")
.WithDownstreamTemplate("/products/{productId}")
.WithUpstreamTemplate("/api/products/{productId}")
.WithUpstreamHttpMethod("Get")
.WithUpstreamTemplatePattern("(?i)/api/products/.*/$")
.Build()
}))
.BDDfy();
}
[Fact]
public void should_use_service_discovery_for_downstream_service_host()
{
this.Given(x => x.GivenTheConfigIs(new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
UpstreamTemplate = "/api/products/{productId}",
DownstreamTemplate = "/products/{productId}",
UpstreamHttpMethod = "Get",
ReRouteIsCaseSensitive = false,
ServiceName = "ProductService"
}
},
GlobalConfiguration = new FileGlobalConfiguration
{
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider
{
Provider = "consul",
Address = "127.0.0.1"
}
}
}))
.And(x => x.GivenTheConfigIsValid())
.When(x => x.WhenICreateTheConfig())
.Then(x => x.ThenTheReRoutesAre(new List<ReRoute>
{
new ReRouteBuilder()
.WithDownstreamTemplate("/products/{productId}")
.WithUpstreamTemplate("/api/products/{productId}")
.WithUpstreamHttpMethod("Get")
.WithUpstreamTemplatePattern("(?i)/api/products/.*/$")
.WithServiceName("ProductService")
.WithUseServiceDiscovery(true)
.WithServiceDiscoveryProvider("consul")
.WithServiceDiscoveryAddress("127.0.01")
.Build()
}))
.BDDfy();
}
[Fact]
public void should_not_use_service_discovery_for_downstream_host_url_when_no_service_name()
{
this.Given(x => x.GivenTheConfigIs(new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
UpstreamTemplate = "/api/products/{productId}",
DownstreamTemplate = "/products/{productId}",
UpstreamHttpMethod = "Get",
ReRouteIsCaseSensitive = false,
}
}
}))
.And(x => x.GivenTheConfigIsValid())
.When(x => x.WhenICreateTheConfig())
.Then(x => x.ThenTheReRoutesAre(new List<ReRoute>
{
new ReRouteBuilder()
.WithDownstreamTemplate("/products/{productId}")
.WithUpstreamTemplate("/api/products/{productId}")
.WithUpstreamHttpMethod("Get")
.WithUpstreamTemplatePattern("(?i)/api/products/.*/$")
.WithUseServiceDiscovery(false)
.WithServiceDiscoveryProvider(null)
.WithServiceDiscoveryAddress(null)
.Build()
}))
.BDDfy();
}
[Fact] [Fact]
public void should_use_reroute_case_sensitivity_value() public void should_use_reroute_case_sensitivity_value()
{ {

View File

@ -1,7 +1,4 @@
using System; using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Moq; using Moq;
using Ocelot.Configuration; using Ocelot.Configuration;
using Ocelot.Configuration.Creator; using Ocelot.Configuration.Creator;