From fac2346161f76512883e1fbfb35e4525ea65d82c Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Mon, 6 Mar 2017 07:33:55 +0000 Subject: [PATCH 1/3] added thread safe test --- configuration.json | 2 +- .../ThreadSafeHeadersTests.cs | 242 ++++++++++++++++++ .../configuration.json | 2 +- 3 files changed, 244 insertions(+), 2 deletions(-) create mode 100644 test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs diff --git a/configuration.json b/configuration.json index 3f39532c..6c4e9ae7 100755 --- a/configuration.json +++ b/configuration.json @@ -1 +1 @@ -{"ReRoutes":[],"GlobalConfiguration":{"RequestIdKey":null,"ServiceDiscoveryProvider":{"Provider":null,"Host":null,"Port":0},"AdministrationPath":"/administration"}} \ No newline at end of file +{"ReRoutes":[{"DownstreamPathTemplate":"/","UpstreamPathTemplate":"/","UpstreamHttpMethod":"Get","AuthenticationOptions":{"Provider":null,"ProviderRootUrl":null,"ScopeName":null,"RequireHttps":false,"AdditionalScopes":[],"ScopeSecret":null},"AddHeadersToRequest":{},"AddClaimsToRequest":{},"RouteClaimsRequirement":{},"AddQueriesToRequest":{},"RequestIdKey":null,"FileCacheOptions":{"TtlSeconds":0},"ReRouteIsCaseSensitive":false,"ServiceName":null,"DownstreamScheme":"http","DownstreamHost":"localhost","DownstreamPort":51879,"QoSOptions":{"ExceptionsAllowedBeforeBreaking":0,"DurationOfBreak":0,"TimeoutValue":0},"LoadBalancer":null,"RateLimitOptions":{"ClientWhitelist":[],"EnableRateLimiting":false,"Period":null,"PeriodTimespan":0.0,"Limit":0}}],"GlobalConfiguration":{"RequestIdKey":null,"ServiceDiscoveryProvider":{"Provider":null,"Host":null,"Port":0},"AdministrationPath":null,"RateLimitOptions":{"ClientIdHeader":"ClientId","QuotaExceededMessage":null,"RateLimitCounterPrefix":"ocelot","DisableRateLimitHeaders":false,"HttpStatusCode":429}}} \ No newline at end of file diff --git a/test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs b/test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs new file mode 100644 index 00000000..c8792f5d --- /dev/null +++ b/test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs @@ -0,0 +1,242 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Newtonsoft.Json; +using Ocelot.Configuration.File; +using Ocelot.ManualTest; +using Shouldly; +using TestStack.BDDfy; +using Xunit; +using Microsoft.AspNetCore.Http; +using System.Threading.Tasks; +using System.Threading; +using System.Collections.Concurrent; + +namespace Ocelot.IntegrationTests +{ + public class ThreadSafeHeadersTests : IDisposable + { + private readonly HttpClient _httpClient; + private HttpResponseMessage _response; + private IWebHost _builder; + private IWebHostBuilder _webHostBuilder; + private readonly string _ocelotBaseUrl; + private BearerToken _token; + private IWebHost _downstreamBuilder; + private readonly Random _random; + private ConcurrentBag _results; + + public ThreadSafeHeadersTests() + { + _results = new ConcurrentBag(); + _random = new Random(); + _httpClient = new HttpClient(); + _ocelotBaseUrl = "http://localhost:5000"; + _httpClient.BaseAddress = new Uri(_ocelotBaseUrl); + } + + [Fact] + public void should_return_same_response_for_each_different_header_under_load_to_downsteam_service() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHost = "localhost", + DownstreamPort = 51879, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = "Get", + } + } + }; + + this.Given(x => GivenThereIsAConfiguration(configuration)) + .And(x => GivenThereIsAServiceRunningOn("http://localhost:51879")) + .And(x => GivenOcelotIsRunning()) + .When(x => WhenIGetUrlOnTheApiGatewayMultipleTimesWithDifferentHeaderValues("/", 50)) + .Then(x => ThenTheSameHeaderValuesAreReturnedByTheDownstreamService()) + .BDDfy(); + } + private void GivenThereIsAServiceRunningOn(string url) + { + _downstreamBuilder = new WebHostBuilder() + .UseUrls(url) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseUrls(url) + .Configure(app => + { + app.Run(async context => + { + var header = context.Request.Headers["ThreadSafeHeadersTest"]; + + context.Response.StatusCode = 200; + await context.Response.WriteAsync(header[0]); + }); + }) + .Build(); + + _downstreamBuilder.Start(); + } + private void WhenIPostOnTheApiGateway(string url, FileConfiguration updatedConfiguration) + { + var json = JsonConvert.SerializeObject(updatedConfiguration); + var content = new StringContent(json); + content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); + _response = _httpClient.PostAsync(url, content).Result; + } + + private void ThenTheResponseShouldBe(FileConfiguration expected) + { + var response = JsonConvert.DeserializeObject(_response.Content.ReadAsStringAsync().Result); + + response.GlobalConfiguration.AdministrationPath.ShouldBe(expected.GlobalConfiguration.AdministrationPath); + response.GlobalConfiguration.RequestIdKey.ShouldBe(expected.GlobalConfiguration.RequestIdKey); + response.GlobalConfiguration.ServiceDiscoveryProvider.Host.ShouldBe(expected.GlobalConfiguration.ServiceDiscoveryProvider.Host); + response.GlobalConfiguration.ServiceDiscoveryProvider.Port.ShouldBe(expected.GlobalConfiguration.ServiceDiscoveryProvider.Port); + response.GlobalConfiguration.ServiceDiscoveryProvider.Provider.ShouldBe(expected.GlobalConfiguration.ServiceDiscoveryProvider.Provider); + + for (var i = 0; i < response.ReRoutes.Count; i++) + { + response.ReRoutes[i].DownstreamHost.ShouldBe(expected.ReRoutes[i].DownstreamHost); + response.ReRoutes[i].DownstreamPathTemplate.ShouldBe(expected.ReRoutes[i].DownstreamPathTemplate); + response.ReRoutes[i].DownstreamPort.ShouldBe(expected.ReRoutes[i].DownstreamPort); + response.ReRoutes[i].DownstreamScheme.ShouldBe(expected.ReRoutes[i].DownstreamScheme); + response.ReRoutes[i].UpstreamPathTemplate.ShouldBe(expected.ReRoutes[i].UpstreamPathTemplate); + response.ReRoutes[i].UpstreamHttpMethod.ShouldBe(expected.ReRoutes[i].UpstreamHttpMethod); + } + } + + private void GivenIHaveAddedATokenToMyRequest() + { + _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token.AccessToken); + } + + private void GivenIHaveAnOcelotToken(string adminPath) + { + var tokenUrl = $"{adminPath}/connect/token"; + var formData = new List> + { + new KeyValuePair("client_id", "admin"), + new KeyValuePair("client_secret", "secret"), + new KeyValuePair("scope", "admin"), + new KeyValuePair("username", "admin"), + new KeyValuePair("password", "secret"), + new KeyValuePair("grant_type", "password") + }; + var content = new FormUrlEncodedContent(formData); + + var response = _httpClient.PostAsync(tokenUrl, content).Result; + var responseContent = response.Content.ReadAsStringAsync().Result; + response.EnsureSuccessStatusCode(); + _token = JsonConvert.DeserializeObject(responseContent); + } + + private void GivenOcelotIsRunning() + { + _webHostBuilder = new WebHostBuilder() + .UseUrls(_ocelotBaseUrl) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .ConfigureServices(x => + { + x.AddSingleton(_webHostBuilder); + }) + .UseStartup(); + + _builder = _webHostBuilder.Build(); + + _builder.Start(); + } + + private void GivenThereIsAConfiguration(FileConfiguration fileConfiguration) + { + var configurationPath = $"{Directory.GetCurrentDirectory()}/configuration.json"; + + var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration); + + if (File.Exists(configurationPath)) + { + File.Delete(configurationPath); + } + + File.WriteAllText(configurationPath, jsonConfiguration); + + var text = File.ReadAllText(configurationPath); + + configurationPath = $"{AppContext.BaseDirectory}/configuration.json"; + + if (File.Exists(configurationPath)) + { + File.Delete(configurationPath); + } + + File.WriteAllText(configurationPath, jsonConfiguration); + + text = File.ReadAllText(configurationPath); + } + + public void WhenIGetUrlOnTheApiGatewayMultipleTimesWithDifferentHeaderValues(string url, int times) + { + var tasks = new Task[times]; + + for (int i = 0; i < times; i++) + { + var urlCopy = url; + var random = _random.Next(0, 50); + tasks[i] = GetForThreadSafeHeadersTest(urlCopy, random); + } + + Task.WaitAll(tasks); + } + + private async Task GetForThreadSafeHeadersTest(string url, int random) + { + var request = new HttpRequestMessage(HttpMethod.Get, url); + request.Headers.Add("ThreadSafeHeadersTest", new List { random.ToString() }); + var response = await _httpClient.SendAsync(request); + var content = await response.Content.ReadAsStringAsync(); + int result = int.Parse(content); + var tshtr = new ThreadSafeHeadersTestResult(result, random); + _results.Add(tshtr); + } + + private void ThenTheSameHeaderValuesAreReturnedByTheDownstreamService() + { + foreach(var result in _results) + { + result.Result.ShouldBe(result.Random); + } + } + public void Dispose() + { + _builder?.Dispose(); + _httpClient?.Dispose(); + _downstreamBuilder?.Dispose(); + } + + class ThreadSafeHeadersTestResult + { + public ThreadSafeHeadersTestResult(int result, int random) + { + Result = result; + Random = random; + + } + + public int Result { get; private set; } + public int Random { get; private set; } + } + } +} diff --git a/test/Ocelot.IntegrationTests/configuration.json b/test/Ocelot.IntegrationTests/configuration.json index 2ce0beff..6c4e9ae7 100755 --- a/test/Ocelot.IntegrationTests/configuration.json +++ b/test/Ocelot.IntegrationTests/configuration.json @@ -1 +1 @@ -{"ReRoutes":[],"GlobalConfiguration":{"RequestIdKey":null,"ServiceDiscoveryProvider":{"Provider":null,"Host":null,"Port":0},"AdministrationPath":"/administration","RateLimitOptions":{"ClientIdHeader":"ClientId","QuotaExceededMessage":null,"RateLimitCounterPrefix":"ocelot","DisableRateLimitHeaders":false,"HttpStatusCode":429}}} \ No newline at end of file +{"ReRoutes":[{"DownstreamPathTemplate":"/","UpstreamPathTemplate":"/","UpstreamHttpMethod":"Get","AuthenticationOptions":{"Provider":null,"ProviderRootUrl":null,"ScopeName":null,"RequireHttps":false,"AdditionalScopes":[],"ScopeSecret":null},"AddHeadersToRequest":{},"AddClaimsToRequest":{},"RouteClaimsRequirement":{},"AddQueriesToRequest":{},"RequestIdKey":null,"FileCacheOptions":{"TtlSeconds":0},"ReRouteIsCaseSensitive":false,"ServiceName":null,"DownstreamScheme":"http","DownstreamHost":"localhost","DownstreamPort":51879,"QoSOptions":{"ExceptionsAllowedBeforeBreaking":0,"DurationOfBreak":0,"TimeoutValue":0},"LoadBalancer":null,"RateLimitOptions":{"ClientWhitelist":[],"EnableRateLimiting":false,"Period":null,"PeriodTimespan":0.0,"Limit":0}}],"GlobalConfiguration":{"RequestIdKey":null,"ServiceDiscoveryProvider":{"Provider":null,"Host":null,"Port":0},"AdministrationPath":null,"RateLimitOptions":{"ClientIdHeader":"ClientId","QuotaExceededMessage":null,"RateLimitCounterPrefix":"ocelot","DisableRateLimitHeaders":false,"HttpStatusCode":429}}} \ No newline at end of file From f0dcefff38ea346234669252ed075cbc0a64b341 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Mon, 6 Mar 2017 07:34:07 +0000 Subject: [PATCH 2/3] test fails on my mac.. --- test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs b/test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs index c8792f5d..25b7acb0 100644 --- a/test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs +++ b/test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs @@ -63,7 +63,7 @@ namespace Ocelot.IntegrationTests this.Given(x => GivenThereIsAConfiguration(configuration)) .And(x => GivenThereIsAServiceRunningOn("http://localhost:51879")) .And(x => GivenOcelotIsRunning()) - .When(x => WhenIGetUrlOnTheApiGatewayMultipleTimesWithDifferentHeaderValues("/", 50)) + .When(x => WhenIGetUrlOnTheApiGatewayMultipleTimesWithDifferentHeaderValues("/", 100)) .Then(x => ThenTheSameHeaderValuesAreReturnedByTheDownstreamService()) .BDDfy(); } From f44357518517878a02e01ff4584906ef17ba978d Mon Sep 17 00:00:00 2001 From: "tom.pallister" Date: Mon, 6 Mar 2017 13:24:36 +0000 Subject: [PATCH 3/3] thread safe test passing on windows --- .../AdministrationTests.cs | 1 + .../ThreadSafeHeadersTests.cs | 60 ++----------------- 2 files changed, 5 insertions(+), 56 deletions(-) diff --git a/test/Ocelot.IntegrationTests/AdministrationTests.cs b/test/Ocelot.IntegrationTests/AdministrationTests.cs index 210c7ac5..2f78e795 100644 --- a/test/Ocelot.IntegrationTests/AdministrationTests.cs +++ b/test/Ocelot.IntegrationTests/AdministrationTests.cs @@ -13,6 +13,7 @@ using Shouldly; using TestStack.BDDfy; using Xunit; +[assembly: CollectionBehavior(DisableTestParallelization = true)] namespace Ocelot.IntegrationTests { public class AdministrationTests : IDisposable diff --git a/test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs b/test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs index 25b7acb0..191dea47 100644 --- a/test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs +++ b/test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs @@ -30,14 +30,14 @@ namespace Ocelot.IntegrationTests private BearerToken _token; private IWebHost _downstreamBuilder; private readonly Random _random; - private ConcurrentBag _results; + private readonly ConcurrentBag _results; public ThreadSafeHeadersTests() { _results = new ConcurrentBag(); _random = new Random(); _httpClient = new HttpClient(); - _ocelotBaseUrl = "http://localhost:5000"; + _ocelotBaseUrl = "http://localhost:5001"; _httpClient.BaseAddress = new Uri(_ocelotBaseUrl); } @@ -63,10 +63,11 @@ namespace Ocelot.IntegrationTests this.Given(x => GivenThereIsAConfiguration(configuration)) .And(x => GivenThereIsAServiceRunningOn("http://localhost:51879")) .And(x => GivenOcelotIsRunning()) - .When(x => WhenIGetUrlOnTheApiGatewayMultipleTimesWithDifferentHeaderValues("/", 100)) + .When(x => WhenIGetUrlOnTheApiGatewayMultipleTimesWithDifferentHeaderValues("/", 300)) .Then(x => ThenTheSameHeaderValuesAreReturnedByTheDownstreamService()) .BDDfy(); } + private void GivenThereIsAServiceRunningOn(string url) { _downstreamBuilder = new WebHostBuilder() @@ -89,59 +90,6 @@ namespace Ocelot.IntegrationTests _downstreamBuilder.Start(); } - private void WhenIPostOnTheApiGateway(string url, FileConfiguration updatedConfiguration) - { - var json = JsonConvert.SerializeObject(updatedConfiguration); - var content = new StringContent(json); - content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); - _response = _httpClient.PostAsync(url, content).Result; - } - - private void ThenTheResponseShouldBe(FileConfiguration expected) - { - var response = JsonConvert.DeserializeObject(_response.Content.ReadAsStringAsync().Result); - - response.GlobalConfiguration.AdministrationPath.ShouldBe(expected.GlobalConfiguration.AdministrationPath); - response.GlobalConfiguration.RequestIdKey.ShouldBe(expected.GlobalConfiguration.RequestIdKey); - response.GlobalConfiguration.ServiceDiscoveryProvider.Host.ShouldBe(expected.GlobalConfiguration.ServiceDiscoveryProvider.Host); - response.GlobalConfiguration.ServiceDiscoveryProvider.Port.ShouldBe(expected.GlobalConfiguration.ServiceDiscoveryProvider.Port); - response.GlobalConfiguration.ServiceDiscoveryProvider.Provider.ShouldBe(expected.GlobalConfiguration.ServiceDiscoveryProvider.Provider); - - for (var i = 0; i < response.ReRoutes.Count; i++) - { - response.ReRoutes[i].DownstreamHost.ShouldBe(expected.ReRoutes[i].DownstreamHost); - response.ReRoutes[i].DownstreamPathTemplate.ShouldBe(expected.ReRoutes[i].DownstreamPathTemplate); - response.ReRoutes[i].DownstreamPort.ShouldBe(expected.ReRoutes[i].DownstreamPort); - response.ReRoutes[i].DownstreamScheme.ShouldBe(expected.ReRoutes[i].DownstreamScheme); - response.ReRoutes[i].UpstreamPathTemplate.ShouldBe(expected.ReRoutes[i].UpstreamPathTemplate); - response.ReRoutes[i].UpstreamHttpMethod.ShouldBe(expected.ReRoutes[i].UpstreamHttpMethod); - } - } - - private void GivenIHaveAddedATokenToMyRequest() - { - _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token.AccessToken); - } - - private void GivenIHaveAnOcelotToken(string adminPath) - { - var tokenUrl = $"{adminPath}/connect/token"; - var formData = new List> - { - new KeyValuePair("client_id", "admin"), - new KeyValuePair("client_secret", "secret"), - new KeyValuePair("scope", "admin"), - new KeyValuePair("username", "admin"), - new KeyValuePair("password", "secret"), - new KeyValuePair("grant_type", "password") - }; - var content = new FormUrlEncodedContent(formData); - - var response = _httpClient.PostAsync(tokenUrl, content).Result; - var responseContent = response.Content.ReadAsStringAsync().Result; - response.EnsureSuccessStatusCode(); - _token = JsonConvert.DeserializeObject(responseContent); - } private void GivenOcelotIsRunning() {