hacked together load balancing reroutes in fileconfig (#211)

* hacked together load balancing reroutes in fileconfig

* some renaming and refactoring

* more renames

* hacked away the old config json

* test for issue 213

* renamed key

* dont share ports

* oops

* updated docs

* mvoed docs around

* port being used
This commit is contained in:
Tom Pallister
2018-01-31 20:34:55 +00:00
committed by GitHub
parent f572d1b0ca
commit 3ac9b3bd87
440 changed files with 29740 additions and 28464 deletions

View File

@ -1,415 +1,469 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
using Ocelot.Cache;
using Ocelot.Configuration.File;
using Shouldly;
using TestStack.BDDfy;
using Xunit;
[assembly: CollectionBehavior(DisableTestParallelization = true)]
namespace Ocelot.IntegrationTests
{
public class AdministrationTests : IDisposable
{
private readonly HttpClient _httpClient;
private readonly HttpClient _httpClientTwo;
private HttpResponseMessage _response;
private IWebHost _builder;
private IWebHostBuilder _webHostBuilder;
private readonly string _ocelotBaseUrl;
private BearerToken _token;
private IWebHostBuilder _webHostBuilderTwo;
private IWebHost _builderTwo;
public AdministrationTests()
{
_httpClient = new HttpClient();
_httpClientTwo = new HttpClient();
_ocelotBaseUrl = "http://localhost:5000";
_httpClient.BaseAddress = new Uri(_ocelotBaseUrl);
}
[Fact]
public void should_return_response_401_with_call_re_routes_controller()
{
var configuration = new FileConfiguration();
this.Given(x => GivenThereIsAConfiguration(configuration))
.And(x => GivenOcelotIsRunning())
.When(x => WhenIGetUrlOnTheApiGateway("/administration/configuration"))
.Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.Unauthorized))
.BDDfy();
}
[Fact]
public void should_return_response_200_with_call_re_routes_controller()
{
var configuration = new FileConfiguration();
this.Given(x => GivenThereIsAConfiguration(configuration))
.And(x => GivenOcelotIsRunning())
.And(x => GivenIHaveAnOcelotToken("/administration"))
.And(x => GivenIHaveAddedATokenToMyRequest())
.When(x => WhenIGetUrlOnTheApiGateway("/administration/configuration"))
.Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.BDDfy();
}
[Fact]
public void should_be_able_to_use_token_from_ocelot_a_on_ocelot_b()
{
var configuration = new FileConfiguration();
this.Given(x => GivenThereIsAConfiguration(configuration))
.And(x => GivenIdentityServerSigningEnvironmentalVariablesAreSet())
.And(x => GivenOcelotIsRunning())
.And(x => GivenIHaveAnOcelotToken("/administration"))
.And(x => GivenAnotherOcelotIsRunning("http://localhost:5007"))
.When(x => WhenIGetUrlOnTheSecondOcelot("/administration/configuration"))
.Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.BDDfy();
}
[Fact]
public void should_return_file_configuration()
{
var configuration = new FileConfiguration
{
GlobalConfiguration = new FileGlobalConfiguration
{
RequestIdKey = "RequestId",
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider
{
Host = "127.0.0.1",
}
},
ReRoutes = new List<FileReRoute>()
{
new FileReRoute()
{
DownstreamHost = "localhost",
DownstreamPort = 80,
DownstreamScheme = "https",
DownstreamPathTemplate = "/",
UpstreamHttpMethod = new List<string> { "get" },
UpstreamPathTemplate = "/",
FileCacheOptions = new FileCacheOptions
{
TtlSeconds = 10,
Region = "Geoff"
}
},
new FileReRoute()
{
DownstreamHost = "localhost",
DownstreamPort = 80,
DownstreamScheme = "https",
DownstreamPathTemplate = "/",
UpstreamHttpMethod = new List<string> { "get" },
UpstreamPathTemplate = "/test",
FileCacheOptions = new FileCacheOptions
{
TtlSeconds = 10,
Region = "Dave"
}
}
}
};
this.Given(x => GivenThereIsAConfiguration(configuration))
.And(x => GivenOcelotIsRunning())
.And(x => GivenIHaveAnOcelotToken("/administration"))
.And(x => GivenIHaveAddedATokenToMyRequest())
.When(x => WhenIGetUrlOnTheApiGateway("/administration/configuration"))
.Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(x => ThenTheResponseShouldBe(configuration))
.BDDfy();
}
[Fact]
public void should_get_file_configuration_edit_and_post_updated_version()
{
var initialConfiguration = new FileConfiguration
{
GlobalConfiguration = new FileGlobalConfiguration
{
},
ReRoutes = new List<FileReRoute>()
{
new FileReRoute()
{
DownstreamHost = "localhost",
DownstreamPort = 80,
DownstreamScheme = "https",
DownstreamPathTemplate = "/",
UpstreamHttpMethod = new List<string> { "get" },
UpstreamPathTemplate = "/"
},
new FileReRoute()
{
DownstreamHost = "localhost",
DownstreamPort = 80,
DownstreamScheme = "https",
DownstreamPathTemplate = "/",
UpstreamHttpMethod = new List<string> { "get" },
UpstreamPathTemplate = "/test"
}
}
};
var updatedConfiguration = new FileConfiguration
{
GlobalConfiguration = new FileGlobalConfiguration
{
},
ReRoutes = new List<FileReRoute>()
{
new FileReRoute()
{
DownstreamHost = "127.0.0.1",
DownstreamPort = 80,
DownstreamScheme = "http",
DownstreamPathTemplate = "/geoffrey",
UpstreamHttpMethod = new List<string> { "get" },
UpstreamPathTemplate = "/"
},
new FileReRoute()
{
DownstreamHost = "123.123.123",
DownstreamPort = 443,
DownstreamScheme = "https",
DownstreamPathTemplate = "/blooper/{productId}",
UpstreamHttpMethod = new List<string> { "post" },
UpstreamPathTemplate = "/test"
}
}
};
this.Given(x => GivenThereIsAConfiguration(initialConfiguration))
.And(x => GivenOcelotIsRunning())
.And(x => GivenIHaveAnOcelotToken("/administration"))
.And(x => GivenIHaveAddedATokenToMyRequest())
.When(x => WhenIGetUrlOnTheApiGateway("/administration/configuration"))
.When(x => WhenIPostOnTheApiGateway("/administration/configuration", updatedConfiguration))
.Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(x => ThenTheResponseShouldBe(updatedConfiguration))
.When(x => WhenIGetUrlOnTheApiGateway("/administration/configuration"))
.And(x => ThenTheResponseShouldBe(updatedConfiguration))
.BDDfy();
}
[Fact]
public void should_clear_region()
{
var initialConfiguration = new FileConfiguration
{
GlobalConfiguration = new FileGlobalConfiguration
{
},
ReRoutes = new List<FileReRoute>()
{
new FileReRoute()
{
DownstreamHost = "localhost",
DownstreamPort = 80,
DownstreamScheme = "https",
DownstreamPathTemplate = "/",
UpstreamHttpMethod = new List<string> { "get" },
UpstreamPathTemplate = "/",
FileCacheOptions = new FileCacheOptions
{
TtlSeconds = 10
}
},
new FileReRoute()
{
DownstreamHost = "localhost",
DownstreamPort = 80,
DownstreamScheme = "https",
DownstreamPathTemplate = "/",
UpstreamHttpMethod = new List<string> { "get" },
UpstreamPathTemplate = "/test",
FileCacheOptions = new FileCacheOptions
{
TtlSeconds = 10
}
}
}
};
var regionToClear = "gettest";
this.Given(x => GivenThereIsAConfiguration(initialConfiguration))
.And(x => GivenOcelotIsRunning())
.And(x => GivenIHaveAnOcelotToken("/administration"))
.And(x => GivenIHaveAddedATokenToMyRequest())
.When(x => WhenIDeleteOnTheApiGateway($"/administration/outputcache/{regionToClear}"))
.Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.NoContent))
.BDDfy();
}
private void GivenAnotherOcelotIsRunning(string baseUrl)
{
_httpClientTwo.BaseAddress = new Uri(baseUrl);
_webHostBuilderTwo = new WebHostBuilder()
.UseUrls(baseUrl)
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.ConfigureServices(x => {
x.AddSingleton(_webHostBuilderTwo);
})
.UseStartup<IntegrationTestsStartup>();
_builderTwo = _webHostBuilderTwo.Build();
_builderTwo.Start();
}
private void GivenIdentityServerSigningEnvironmentalVariablesAreSet()
{
Environment.SetEnvironmentVariable("OCELOT_CERTIFICATE", "idsrv3test.pfx");
Environment.SetEnvironmentVariable("OCELOT_CERTIFICATE_PASSWORD", "idsrv3test");
}
private void WhenIGetUrlOnTheSecondOcelot(string url)
{
_httpClientTwo.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token.AccessToken);
_response = _httpClientTwo.GetAsync(url).Result;
}
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(List<string> expected)
{
var content = _response.Content.ReadAsStringAsync().Result;
var result = JsonConvert.DeserializeObject<Regions>(content);
result.Value.ShouldBe(expected);
}
private void ThenTheResponseShouldBe(FileConfiguration expected)
{
var response = JsonConvert.DeserializeObject<FileConfiguration>(_response.Content.ReadAsStringAsync().Result);
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);
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<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("client_id", "admin"),
new KeyValuePair<string, string>("client_secret", "secret"),
new KeyValuePair<string, string>("scope", "admin"),
new KeyValuePair<string, string>("grant_type", "client_credentials")
};
var content = new FormUrlEncodedContent(formData);
var response = _httpClient.PostAsync(tokenUrl, content).Result;
var responseContent = response.Content.ReadAsStringAsync().Result;
response.EnsureSuccessStatusCode();
_token = JsonConvert.DeserializeObject<BearerToken>(responseContent);
var configPath = $"{adminPath}/.well-known/openid-configuration";
response = _httpClient.GetAsync(configPath).Result;
response.EnsureSuccessStatusCode();
}
private void GivenOcelotIsRunning()
{
_webHostBuilder = new WebHostBuilder()
.UseUrls(_ocelotBaseUrl)
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.ConfigureServices(x => {
x.AddSingleton(_webHostBuilder);
})
.UseStartup<IntegrationTestsStartup>();
_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);
}
private void WhenIGetUrlOnTheApiGateway(string url)
{
_response = _httpClient.GetAsync(url).Result;
}
private void WhenIDeleteOnTheApiGateway(string url)
{
_response = _httpClient.DeleteAsync(url).Result;
}
private void ThenTheStatusCodeShouldBe(HttpStatusCode expectedHttpStatusCode)
{
_response.StatusCode.ShouldBe(expectedHttpStatusCode);
}
public void Dispose()
{
Environment.SetEnvironmentVariable("OCELOT_CERTIFICATE", "");
Environment.SetEnvironmentVariable("OCELOT_CERTIFICATE_PASSWORD", "");
_builder?.Dispose();
_httpClient?.Dispose();
}
}
}
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
using Ocelot.Cache;
using Ocelot.Configuration.File;
using Shouldly;
using TestStack.BDDfy;
using Xunit;
[assembly: CollectionBehavior(DisableTestParallelization = true)]
namespace Ocelot.IntegrationTests
{
public class AdministrationTests : IDisposable
{
private readonly HttpClient _httpClient;
private readonly HttpClient _httpClientTwo;
private HttpResponseMessage _response;
private IWebHost _builder;
private IWebHostBuilder _webHostBuilder;
private readonly string _ocelotBaseUrl;
private BearerToken _token;
private IWebHostBuilder _webHostBuilderTwo;
private IWebHost _builderTwo;
public AdministrationTests()
{
_httpClient = new HttpClient();
_httpClientTwo = new HttpClient();
_ocelotBaseUrl = "http://localhost:5000";
_httpClient.BaseAddress = new Uri(_ocelotBaseUrl);
}
[Fact]
public void should_return_response_401_with_call_re_routes_controller()
{
var configuration = new FileConfiguration();
this.Given(x => GivenThereIsAConfiguration(configuration))
.And(x => GivenOcelotIsRunning())
.When(x => WhenIGetUrlOnTheApiGateway("/administration/configuration"))
.Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.Unauthorized))
.BDDfy();
}
[Fact]
public void should_return_response_200_with_call_re_routes_controller()
{
var configuration = new FileConfiguration();
this.Given(x => GivenThereIsAConfiguration(configuration))
.And(x => GivenOcelotIsRunning())
.And(x => GivenIHaveAnOcelotToken("/administration"))
.And(x => GivenIHaveAddedATokenToMyRequest())
.When(x => WhenIGetUrlOnTheApiGateway("/administration/configuration"))
.Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.BDDfy();
}
[Fact]
public void should_be_able_to_use_token_from_ocelot_a_on_ocelot_b()
{
var configuration = new FileConfiguration();
this.Given(x => GivenThereIsAConfiguration(configuration))
.And(x => GivenIdentityServerSigningEnvironmentalVariablesAreSet())
.And(x => GivenOcelotIsRunning())
.And(x => GivenIHaveAnOcelotToken("/administration"))
.And(x => GivenAnotherOcelotIsRunning("http://localhost:5007"))
.When(x => WhenIGetUrlOnTheSecondOcelot("/administration/configuration"))
.Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.BDDfy();
}
[Fact]
public void should_return_file_configuration()
{
var configuration = new FileConfiguration
{
GlobalConfiguration = new FileGlobalConfiguration
{
RequestIdKey = "RequestId",
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider
{
Host = "127.0.0.1",
}
},
ReRoutes = new List<FileReRoute>()
{
new FileReRoute()
{
DownstreamHostAndPorts = new List<FileHostAndPort>
{
new FileHostAndPort
{
Host = "localhost",
Port = 80,
}
},
DownstreamScheme = "https",
DownstreamPathTemplate = "/",
UpstreamHttpMethod = new List<string> { "get" },
UpstreamPathTemplate = "/",
FileCacheOptions = new FileCacheOptions
{
TtlSeconds = 10,
Region = "Geoff"
}
},
new FileReRoute()
{
DownstreamHostAndPorts = new List<FileHostAndPort>
{
new FileHostAndPort
{
Host = "localhost",
Port = 80,
}
},
DownstreamScheme = "https",
DownstreamPathTemplate = "/",
UpstreamHttpMethod = new List<string> { "get" },
UpstreamPathTemplate = "/test",
FileCacheOptions = new FileCacheOptions
{
TtlSeconds = 10,
Region = "Dave"
}
}
}
};
this.Given(x => GivenThereIsAConfiguration(configuration))
.And(x => GivenOcelotIsRunning())
.And(x => GivenIHaveAnOcelotToken("/administration"))
.And(x => GivenIHaveAddedATokenToMyRequest())
.When(x => WhenIGetUrlOnTheApiGateway("/administration/configuration"))
.Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(x => ThenTheResponseShouldBe(configuration))
.BDDfy();
}
[Fact]
public void should_get_file_configuration_edit_and_post_updated_version()
{
var initialConfiguration = new FileConfiguration
{
GlobalConfiguration = new FileGlobalConfiguration
{
},
ReRoutes = new List<FileReRoute>()
{
new FileReRoute()
{
DownstreamHostAndPorts = new List<FileHostAndPort>
{
new FileHostAndPort
{
Host = "localhost",
Port = 80,
}
},
DownstreamScheme = "https",
DownstreamPathTemplate = "/",
UpstreamHttpMethod = new List<string> { "get" },
UpstreamPathTemplate = "/"
},
new FileReRoute()
{
DownstreamHostAndPorts = new List<FileHostAndPort>
{
new FileHostAndPort
{
Host = "localhost",
Port = 80,
}
},
DownstreamScheme = "https",
DownstreamPathTemplate = "/",
UpstreamHttpMethod = new List<string> { "get" },
UpstreamPathTemplate = "/test"
}
}
};
var updatedConfiguration = new FileConfiguration
{
GlobalConfiguration = new FileGlobalConfiguration
{
},
ReRoutes = new List<FileReRoute>()
{
new FileReRoute()
{
DownstreamHostAndPorts = new List<FileHostAndPort>
{
new FileHostAndPort
{
Host = "localhost",
Port = 80,
}
},
DownstreamScheme = "http",
DownstreamPathTemplate = "/geoffrey",
UpstreamHttpMethod = new List<string> { "get" },
UpstreamPathTemplate = "/"
},
new FileReRoute()
{
DownstreamHostAndPorts = new List<FileHostAndPort>
{
new FileHostAndPort
{
Host = "123.123.123",
Port = 443,
}
},
DownstreamScheme = "https",
DownstreamPathTemplate = "/blooper/{productId}",
UpstreamHttpMethod = new List<string> { "post" },
UpstreamPathTemplate = "/test"
}
}
};
this.Given(x => GivenThereIsAConfiguration(initialConfiguration))
.And(x => GivenOcelotIsRunning())
.And(x => GivenIHaveAnOcelotToken("/administration"))
.And(x => GivenIHaveAddedATokenToMyRequest())
.When(x => WhenIGetUrlOnTheApiGateway("/administration/configuration"))
.When(x => WhenIPostOnTheApiGateway("/administration/configuration", updatedConfiguration))
.Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(x => ThenTheResponseShouldBe(updatedConfiguration))
.When(x => WhenIGetUrlOnTheApiGateway("/administration/configuration"))
.And(x => ThenTheResponseShouldBe(updatedConfiguration))
.BDDfy();
}
[Fact]
public void should_clear_region()
{
var initialConfiguration = new FileConfiguration
{
GlobalConfiguration = new FileGlobalConfiguration
{
},
ReRoutes = new List<FileReRoute>()
{
new FileReRoute()
{
DownstreamHostAndPorts = new List<FileHostAndPort>
{
new FileHostAndPort
{
Host = "localhost",
Port = 80,
}
},
DownstreamScheme = "https",
DownstreamPathTemplate = "/",
UpstreamHttpMethod = new List<string> { "get" },
UpstreamPathTemplate = "/",
FileCacheOptions = new FileCacheOptions
{
TtlSeconds = 10
}
},
new FileReRoute()
{
DownstreamHostAndPorts = new List<FileHostAndPort>
{
new FileHostAndPort
{
Host = "localhost",
Port = 80,
}
},
DownstreamScheme = "https",
DownstreamPathTemplate = "/",
UpstreamHttpMethod = new List<string> { "get" },
UpstreamPathTemplate = "/test",
FileCacheOptions = new FileCacheOptions
{
TtlSeconds = 10
}
}
}
};
var regionToClear = "gettest";
this.Given(x => GivenThereIsAConfiguration(initialConfiguration))
.And(x => GivenOcelotIsRunning())
.And(x => GivenIHaveAnOcelotToken("/administration"))
.And(x => GivenIHaveAddedATokenToMyRequest())
.When(x => WhenIDeleteOnTheApiGateway($"/administration/outputcache/{regionToClear}"))
.Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.NoContent))
.BDDfy();
}
private void GivenAnotherOcelotIsRunning(string baseUrl)
{
_httpClientTwo.BaseAddress = new Uri(baseUrl);
_webHostBuilderTwo = new WebHostBuilder()
.UseUrls(baseUrl)
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.ConfigureServices(x => {
x.AddSingleton(_webHostBuilderTwo);
})
.UseStartup<IntegrationTestsStartup>();
_builderTwo = _webHostBuilderTwo.Build();
_builderTwo.Start();
}
private void GivenIdentityServerSigningEnvironmentalVariablesAreSet()
{
Environment.SetEnvironmentVariable("OCELOT_CERTIFICATE", "idsrv3test.pfx");
Environment.SetEnvironmentVariable("OCELOT_CERTIFICATE_PASSWORD", "idsrv3test");
}
private void WhenIGetUrlOnTheSecondOcelot(string url)
{
_httpClientTwo.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token.AccessToken);
_response = _httpClientTwo.GetAsync(url).Result;
}
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(List<string> expected)
{
var content = _response.Content.ReadAsStringAsync().Result;
var result = JsonConvert.DeserializeObject<Regions>(content);
result.Value.ShouldBe(expected);
}
private void ThenTheResponseShouldBe(FileConfiguration expecteds)
{
var response = JsonConvert.DeserializeObject<FileConfiguration>(_response.Content.ReadAsStringAsync().Result);
response.GlobalConfiguration.RequestIdKey.ShouldBe(expecteds.GlobalConfiguration.RequestIdKey);
response.GlobalConfiguration.ServiceDiscoveryProvider.Host.ShouldBe(expecteds.GlobalConfiguration.ServiceDiscoveryProvider.Host);
response.GlobalConfiguration.ServiceDiscoveryProvider.Port.ShouldBe(expecteds.GlobalConfiguration.ServiceDiscoveryProvider.Port);
for (var i = 0; i < response.ReRoutes.Count; i++)
{
for (var j = 0; j < response.ReRoutes[i].DownstreamHostAndPorts.Count; j++)
{
var result = response.ReRoutes[i].DownstreamHostAndPorts[j];
var expected = expecteds.ReRoutes[i].DownstreamHostAndPorts[j];
result.Host.ShouldBe(expected.Host);
result.Port.ShouldBe(expected.Port);
}
response.ReRoutes[i].DownstreamPathTemplate.ShouldBe(expecteds.ReRoutes[i].DownstreamPathTemplate);
response.ReRoutes[i].DownstreamScheme.ShouldBe(expecteds.ReRoutes[i].DownstreamScheme);
response.ReRoutes[i].UpstreamPathTemplate.ShouldBe(expecteds.ReRoutes[i].UpstreamPathTemplate);
response.ReRoutes[i].UpstreamHttpMethod.ShouldBe(expecteds.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<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("client_id", "admin"),
new KeyValuePair<string, string>("client_secret", "secret"),
new KeyValuePair<string, string>("scope", "admin"),
new KeyValuePair<string, string>("grant_type", "client_credentials")
};
var content = new FormUrlEncodedContent(formData);
var response = _httpClient.PostAsync(tokenUrl, content).Result;
var responseContent = response.Content.ReadAsStringAsync().Result;
response.EnsureSuccessStatusCode();
_token = JsonConvert.DeserializeObject<BearerToken>(responseContent);
var configPath = $"{adminPath}/.well-known/openid-configuration";
response = _httpClient.GetAsync(configPath).Result;
response.EnsureSuccessStatusCode();
}
private void GivenOcelotIsRunning()
{
_webHostBuilder = new WebHostBuilder()
.UseUrls(_ocelotBaseUrl)
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.ConfigureServices(x => {
x.AddSingleton(_webHostBuilder);
})
.UseStartup<IntegrationTestsStartup>();
_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);
}
private void WhenIGetUrlOnTheApiGateway(string url)
{
_response = _httpClient.GetAsync(url).Result;
}
private void WhenIDeleteOnTheApiGateway(string url)
{
_response = _httpClient.DeleteAsync(url).Result;
}
private void ThenTheStatusCodeShouldBe(HttpStatusCode expectedHttpStatusCode)
{
_response.StatusCode.ShouldBe(expectedHttpStatusCode);
}
public void Dispose()
{
Environment.SetEnvironmentVariable("OCELOT_CERTIFICATE", "");
Environment.SetEnvironmentVariable("OCELOT_CERTIFICATE_PASSWORD", "");
_builder?.Dispose();
_httpClient?.Dispose();
}
}
}

View File

@ -1,16 +1,16 @@
using Newtonsoft.Json;
namespace Ocelot.IntegrationTests
{
class BearerToken
{
[JsonProperty("access_token")]
public string AccessToken { get; set; }
[JsonProperty("expires_in")]
public int ExpiresIn { get; set; }
[JsonProperty("token_type")]
public string TokenType { get; set; }
}
}
using Newtonsoft.Json;
namespace Ocelot.IntegrationTests
{
class BearerToken
{
[JsonProperty("access_token")]
public string AccessToken { get; set; }
[JsonProperty("expires_in")]
public int ExpiresIn { get; set; }
[JsonProperty("token_type")]
public string TokenType { get; set; }
}
}

View File

@ -1,51 +1,51 @@
using System;
using CacheManager.Core;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Ocelot.DependencyInjection;
using Ocelot.Middleware;
using ConfigurationBuilder = Microsoft.Extensions.Configuration.ConfigurationBuilder;
namespace Ocelot.IntegrationTests
{
public class IntegrationTestsStartup
{
public IntegrationTestsStartup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddJsonFile("configuration.json")
.AddEnvironmentVariables();
Configuration = builder.Build();
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
Action<ConfigurationBuilderCachePart> settings = (x) =>
{
x.WithMicrosoftLogging(log =>
{
log.AddConsole(LogLevel.Debug);
})
.WithDictionaryHandle();
};
services.AddOcelot(Configuration)
.AddCacheManager(settings)
.AddAdministration("/administration", "secret");
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
app.UseOcelot().Wait();
}
}
using System;
using CacheManager.Core;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Ocelot.DependencyInjection;
using Ocelot.Middleware;
using ConfigurationBuilder = Microsoft.Extensions.Configuration.ConfigurationBuilder;
namespace Ocelot.IntegrationTests
{
public class IntegrationTestsStartup
{
public IntegrationTestsStartup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddJsonFile("configuration.json")
.AddEnvironmentVariables();
Configuration = builder.Build();
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
Action<ConfigurationBuilderCachePart> settings = (x) =>
{
x.WithMicrosoftLogging(log =>
{
log.AddConsole(LogLevel.Debug);
})
.WithDictionaryHandle();
};
services.AddOcelot(Configuration)
.AddCacheManager(settings)
.AddAdministration("/administration", "secret");
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
app.UseOcelot().Wait();
}
}
}

View File

@ -1,46 +1,46 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<VersionPrefix>0.0.0-dev</VersionPrefix>
<TargetFramework>netcoreapp2.0</TargetFramework>
<RuntimeFrameworkVersion>2.0.0</RuntimeFrameworkVersion>
<AssemblyName>Ocelot.IntegrationTests</AssemblyName>
<OutputType>Exe</OutputType>
<PackageId>Ocelot.IntegrationTests</PackageId>
<GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
<RuntimeIdentifiers>win10-x64;osx.10.11-x64;osx.10.12-x64;win7-x64</RuntimeIdentifiers>
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
</PropertyGroup>
<ItemGroup>
<None Update="peers.json;configuration.json;appsettings.json;idsrv3test.pfx">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Ocelot\Ocelot.csproj"/>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0"/>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.5.0-preview-20171031-01"/>
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.1"/>
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.0.0"/>
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="2.0.0"/>
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.0.0"/>
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.0.0"/>
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.0.0"/>
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.0.0"/>
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="2.0.0"/>
<PackageReference Include="Microsoft.DotNet.InternalAbstractions" Version="1.0.500-preview2-1-003177"/>
<PackageReference Include="xunit" Version="2.3.1"/>
<PackageReference Include="IdentityServer4" Version="2.0.2"/>
<PackageReference Include="Shouldly" Version="3.0.0-beta0003"/>
<PackageReference Include="TestStack.BDDfy" Version="4.3.2"/>
<PackageReference Include="Consul" Version="0.7.2.3"/>
<PackageReference Include="Rafty" Version="0.4.2"/>
<PackageReference Include="Microsoft.Data.SQLite" Version="2.0.0"/>
</ItemGroup>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<VersionPrefix>0.0.0-dev</VersionPrefix>
<TargetFramework>netcoreapp2.0</TargetFramework>
<RuntimeFrameworkVersion>2.0.0</RuntimeFrameworkVersion>
<AssemblyName>Ocelot.IntegrationTests</AssemblyName>
<OutputType>Exe</OutputType>
<PackageId>Ocelot.IntegrationTests</PackageId>
<GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
<RuntimeIdentifiers>win10-x64;osx.10.11-x64;osx.10.12-x64;win7-x64</RuntimeIdentifiers>
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
</PropertyGroup>
<ItemGroup>
<None Update="peers.json;configuration.json;appsettings.json;idsrv3test.pfx">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Ocelot\Ocelot.csproj"/>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0"/>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.5.0-preview-20171031-01"/>
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.1"/>
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.0.0"/>
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="2.0.0"/>
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.0.0"/>
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.0.0"/>
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.0.0"/>
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.0.0"/>
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="2.0.0"/>
<PackageReference Include="Microsoft.DotNet.InternalAbstractions" Version="1.0.500-preview2-1-003177"/>
<PackageReference Include="xunit" Version="2.3.1"/>
<PackageReference Include="IdentityServer4" Version="2.0.2"/>
<PackageReference Include="Shouldly" Version="3.0.0-beta0003"/>
<PackageReference Include="TestStack.BDDfy" Version="4.3.2"/>
<PackageReference Include="Consul" Version="0.7.2.3"/>
<PackageReference Include="Rafty" Version="0.4.2"/>
<PackageReference Include="Microsoft.Data.SQLite" Version="2.0.0"/>
</ItemGroup>
</Project>

View File

@ -1,19 +1,19 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Ocelot.IntegrationTests")]
[assembly: AssemblyTrademark("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("d4575572-99ca-4530-8737-c296eda326f8")]
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Ocelot.IntegrationTests")]
[assembly: AssemblyTrademark("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("d4575572-99ca-4530-8737-c296eda326f8")]

View File

@ -1,52 +1,52 @@
using System;
using System.IO;
using System.Linq;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Ocelot.DependencyInjection;
using Ocelot.Middleware;
using Ocelot.Raft;
using Rafty.Concensus;
using Rafty.FiniteStateMachine;
using Rafty.Infrastructure;
using Rafty.Log;
using ConfigurationBuilder = Microsoft.Extensions.Configuration.ConfigurationBuilder;
namespace Ocelot.IntegrationTests
{
public class RaftStartup
{
public RaftStartup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddJsonFile("peers.json", optional: true, reloadOnChange: true)
.AddJsonFile("configuration.json")
.AddEnvironmentVariables();
Configuration = builder.Build();
}
public IConfiguration Configuration { get; }
public virtual void ConfigureServices(IServiceCollection services)
{
services
.AddOcelot(Configuration)
.AddAdministration("/administration", "secret")
.AddRafty();
}
public virtual void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
app.UseOcelot().Wait();
}
}
}
using System;
using System.IO;
using System.Linq;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Ocelot.DependencyInjection;
using Ocelot.Middleware;
using Ocelot.Raft;
using Rafty.Concensus;
using Rafty.FiniteStateMachine;
using Rafty.Infrastructure;
using Rafty.Log;
using ConfigurationBuilder = Microsoft.Extensions.Configuration.ConfigurationBuilder;
namespace Ocelot.IntegrationTests
{
public class RaftStartup
{
public RaftStartup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddJsonFile("peers.json", optional: true, reloadOnChange: true)
.AddJsonFile("configuration.json")
.AddEnvironmentVariables();
Configuration = builder.Build();
}
public IConfiguration Configuration { get; }
public virtual void ConfigureServices(IServiceCollection services)
{
services
.AddOcelot(Configuration)
.AddAdministration("/administration", "secret")
.AddRafty();
}
public virtual void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
app.UseOcelot().Wait();
}
}
}

View File

@ -1,431 +1,372 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
using Ocelot.Configuration.File;
using Ocelot.Raft;
using Rafty.Concensus;
using Rafty.FiniteStateMachine;
using Rafty.Infrastructure;
using Shouldly;
using Xunit;
using static Rafty.Infrastructure.Wait;
using Microsoft.Data.Sqlite;
namespace Ocelot.IntegrationTests
{
public class RaftTests : IDisposable
{
private List<IWebHost> _builders;
private List<IWebHostBuilder> _webHostBuilders;
private List<Thread> _threads;
private FilePeers _peers;
private HttpClient _httpClient;
private HttpClient _httpClientForAssertions;
private string _ocelotBaseUrl;
private BearerToken _token;
private HttpResponseMessage _response;
private static object _lock = new object();
public RaftTests()
{
_httpClientForAssertions = new HttpClient();
_httpClient = new HttpClient();
_ocelotBaseUrl = "http://localhost:5000";
_httpClient.BaseAddress = new Uri(_ocelotBaseUrl);
_webHostBuilders = new List<IWebHostBuilder>();
_builders = new List<IWebHost>();
_threads = new List<Thread>();
}
public void Dispose()
{
foreach (var builder in _builders)
{
builder?.Dispose();
}
foreach (var peer in _peers.Peers)
{
File.Delete(peer.HostAndPort.Replace("/","").Replace(":",""));
File.Delete($"{peer.HostAndPort.Replace("/","").Replace(":","")}.db");
}
}
[Fact]
public void should_persist_command_to_five_servers()
{
var configuration = new FileConfiguration
{
GlobalConfiguration = new FileGlobalConfiguration
{
}
};
var updatedConfiguration = new FileConfiguration
{
GlobalConfiguration = new FileGlobalConfiguration
{
},
ReRoutes = new List<FileReRoute>()
{
new FileReRoute()
{
DownstreamHost = "127.0.0.1",
DownstreamPort = 80,
DownstreamScheme = "http",
DownstreamPathTemplate = "/geoffrey",
UpstreamHttpMethod = new List<string> { "get" },
UpstreamPathTemplate = "/"
},
new FileReRoute()
{
DownstreamHost = "123.123.123",
DownstreamPort = 443,
DownstreamScheme = "https",
DownstreamPathTemplate = "/blooper/{productId}",
UpstreamHttpMethod = new List<string> { "post" },
UpstreamPathTemplate = "/test"
}
}
};
var command = new UpdateFileConfiguration(updatedConfiguration);
GivenThereIsAConfiguration(configuration);
GivenFiveServersAreRunning();
GivenALeaderIsElected();
GivenIHaveAnOcelotToken("/administration");
WhenISendACommandIntoTheCluster(command);
ThenTheCommandIsReplicatedToAllStateMachines(command);
}
[Fact]
public void should_persist_command_to_five_servers_when_using_administration_api()
{
var configuration = new FileConfiguration
{
};
var updatedConfiguration = new FileConfiguration
{
ReRoutes = new List<FileReRoute>()
{
new FileReRoute()
{
DownstreamHost = "127.0.0.1",
DownstreamPort = 80,
DownstreamScheme = "http",
DownstreamPathTemplate = "/geoffrey",
UpstreamHttpMethod = new List<string> { "get" },
UpstreamPathTemplate = "/"
},
new FileReRoute()
{
DownstreamHost = "123.123.123",
DownstreamPort = 443,
DownstreamScheme = "https",
DownstreamPathTemplate = "/blooper/{productId}",
UpstreamHttpMethod = new List<string> { "post" },
UpstreamPathTemplate = "/test"
}
}
};
var command = new UpdateFileConfiguration(updatedConfiguration);
GivenThereIsAConfiguration(configuration);
GivenFiveServersAreRunning();
GivenALeaderIsElected();
GivenIHaveAnOcelotToken("/administration");
GivenIHaveAddedATokenToMyRequest();
WhenIPostOnTheApiGateway("/administration/configuration", updatedConfiguration);
ThenTheCommandIsReplicatedToAllStateMachines(command);
}
private void WhenISendACommandIntoTheCluster(UpdateFileConfiguration command)
{
var p = _peers.Peers.First();
var json = JsonConvert.SerializeObject(command,new JsonSerializerSettings() {
TypeNameHandling = TypeNameHandling.All
});
var httpContent = new StringContent(json);
httpContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
using(var httpClient = new HttpClient())
{
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token.AccessToken);
var response = httpClient.PostAsync($"{p.HostAndPort}/administration/raft/command", httpContent).GetAwaiter().GetResult();
response.EnsureSuccessStatusCode();
var content = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
var result = JsonConvert.DeserializeObject<OkResponse<UpdateFileConfiguration>>(content);
result.Command.Configuration.ReRoutes.Count.ShouldBe(2);
}
//dirty sleep to make sure command replicated...
var stopwatch = Stopwatch.StartNew();
while(stopwatch.ElapsedMilliseconds < 10000)
{
}
}
private void ThenTheCommandIsReplicatedToAllStateMachines(UpdateFileConfiguration expected)
{
//dirty sleep to give a chance to replicate...
var stopwatch = Stopwatch.StartNew();
while(stopwatch.ElapsedMilliseconds < 2000)
{
}
bool CommandCalledOnAllStateMachines()
{
try
{
var passed = 0;
foreach (var peer in _peers.Peers)
{
var path = $"{peer.HostAndPort.Replace("/","").Replace(":","")}.db";
using(var connection = new SqliteConnection($"Data Source={path};"))
{
connection.Open();
var sql = @"select count(id) from logs";
using(var command = new SqliteCommand(sql, connection))
{
var index = Convert.ToInt32(command.ExecuteScalar());
index.ShouldBe(1);
}
}
_httpClientForAssertions.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token.AccessToken);
var result = _httpClientForAssertions.GetAsync($"{peer.HostAndPort}/administration/configuration").Result;
var json = result.Content.ReadAsStringAsync().Result;
var response = JsonConvert.DeserializeObject<FileConfiguration>(json, new JsonSerializerSettings{TypeNameHandling = TypeNameHandling.All});
response.GlobalConfiguration.RequestIdKey.ShouldBe(expected.Configuration.GlobalConfiguration.RequestIdKey);
response.GlobalConfiguration.ServiceDiscoveryProvider.Host.ShouldBe(expected.Configuration.GlobalConfiguration.ServiceDiscoveryProvider.Host);
response.GlobalConfiguration.ServiceDiscoveryProvider.Port.ShouldBe(expected.Configuration.GlobalConfiguration.ServiceDiscoveryProvider.Port);
for (var i = 0; i < response.ReRoutes.Count; i++)
{
response.ReRoutes[i].DownstreamHost.ShouldBe(expected.Configuration.ReRoutes[i].DownstreamHost);
response.ReRoutes[i].DownstreamPathTemplate.ShouldBe(expected.Configuration.ReRoutes[i].DownstreamPathTemplate);
response.ReRoutes[i].DownstreamPort.ShouldBe(expected.Configuration.ReRoutes[i].DownstreamPort);
response.ReRoutes[i].DownstreamScheme.ShouldBe(expected.Configuration.ReRoutes[i].DownstreamScheme);
response.ReRoutes[i].UpstreamPathTemplate.ShouldBe(expected.Configuration.ReRoutes[i].UpstreamPathTemplate);
response.ReRoutes[i].UpstreamHttpMethod.ShouldBe(expected.Configuration.ReRoutes[i].UpstreamHttpMethod);
}
passed++;
}
return passed == 5;
}
catch(Exception e)
{
Console.WriteLine(e);
return false;
}
}
var commandOnAllStateMachines = WaitFor(20000).Until(() => CommandCalledOnAllStateMachines());
commandOnAllStateMachines.ShouldBeTrue();
}
private void ThenTheResponseShouldBe(FileConfiguration expected)
{
var response = JsonConvert.DeserializeObject<FileConfiguration>(_response.Content.ReadAsStringAsync().Result);
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);
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 WhenIGetUrlOnTheApiGateway(string url)
{
_response = _httpClient.GetAsync(url).Result;
}
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 GivenIHaveAddedATokenToMyRequest()
{
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token.AccessToken);
}
private void GivenIHaveAnOcelotToken(string adminPath)
{
var tokenUrl = $"{adminPath}/connect/token";
var formData = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("client_id", "admin"),
new KeyValuePair<string, string>("client_secret", "secret"),
new KeyValuePair<string, string>("scope", "admin"),
new KeyValuePair<string, string>("grant_type", "client_credentials")
};
var content = new FormUrlEncodedContent(formData);
var response = _httpClient.PostAsync(tokenUrl, content).Result;
var responseContent = response.Content.ReadAsStringAsync().Result;
response.EnsureSuccessStatusCode();
_token = JsonConvert.DeserializeObject<BearerToken>(responseContent);
var configPath = $"{adminPath}/.well-known/openid-configuration";
response = _httpClient.GetAsync(configPath).Result;
response.EnsureSuccessStatusCode();
}
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);
}
private void GivenAServerIsRunning(string url)
{
lock(_lock)
{
IWebHostBuilder webHostBuilder = new WebHostBuilder();
webHostBuilder.UseUrls(url)
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.ConfigureServices(x =>
{
x.AddSingleton(webHostBuilder);
x.AddSingleton(new NodeId(url));
})
.UseStartup<RaftStartup>();
var builder = webHostBuilder.Build();
builder.Start();
_webHostBuilders.Add(webHostBuilder);
_builders.Add(builder);
}
}
private void GivenFiveServersAreRunning()
{
var bytes = File.ReadAllText("peers.json");
_peers = JsonConvert.DeserializeObject<FilePeers>(bytes);
foreach (var peer in _peers.Peers)
{
var thread = new Thread(() => GivenAServerIsRunning(peer.HostAndPort));
thread.Start();
_threads.Add(thread);
}
}
private void GivenALeaderIsElected()
{
//dirty sleep to make sure we have a leader
var stopwatch = Stopwatch.StartNew();
while(stopwatch.ElapsedMilliseconds < 20000)
{
}
}
private void WhenISendACommandIntoTheCluster(FakeCommand command)
{
var p = _peers.Peers.First();
var json = JsonConvert.SerializeObject(command,new JsonSerializerSettings() {
TypeNameHandling = TypeNameHandling.All
});
var httpContent = new StringContent(json);
httpContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
using(var httpClient = new HttpClient())
{
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token.AccessToken);
var response = httpClient.PostAsync($"{p.HostAndPort}/administration/raft/command", httpContent).GetAwaiter().GetResult();
response.EnsureSuccessStatusCode();
var content = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
var result = JsonConvert.DeserializeObject<OkResponse<FakeCommand>>(content);
result.Command.Value.ShouldBe(command.Value);
}
//dirty sleep to make sure command replicated...
var stopwatch = Stopwatch.StartNew();
while(stopwatch.ElapsedMilliseconds < 10000)
{
}
}
private void ThenTheCommandIsReplicatedToAllStateMachines(FakeCommand command)
{
//dirty sleep to give a chance to replicate...
var stopwatch = Stopwatch.StartNew();
while(stopwatch.ElapsedMilliseconds < 2000)
{
}
bool CommandCalledOnAllStateMachines()
{
try
{
var passed = 0;
foreach (var peer in _peers.Peers)
{
string fsmData;
fsmData = File.ReadAllText(peer.HostAndPort.Replace("/","").Replace(":",""));
fsmData.ShouldNotBeNullOrEmpty();
var fakeCommand = JsonConvert.DeserializeObject<FakeCommand>(fsmData);
fakeCommand.Value.ShouldBe(command.Value);
passed++;
}
return passed == 5;
}
catch(Exception e)
{
return false;
}
}
var commandOnAllStateMachines = WaitFor(20000).Until(() => CommandCalledOnAllStateMachines());
commandOnAllStateMachines.ShouldBeTrue();
}
}
}
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
using Ocelot.Configuration.File;
using Ocelot.Raft;
using Rafty.Concensus;
using Rafty.Infrastructure;
using Shouldly;
using Xunit;
using static Rafty.Infrastructure.Wait;
using Microsoft.Data.Sqlite;
namespace Ocelot.IntegrationTests
{
public class RaftTests : IDisposable
{
private readonly List<IWebHost> _builders;
private readonly List<IWebHostBuilder> _webHostBuilders;
private readonly List<Thread> _threads;
private FilePeers _peers;
private readonly HttpClient _httpClient;
private readonly HttpClient _httpClientForAssertions;
private string _ocelotBaseUrl;
private BearerToken _token;
private HttpResponseMessage _response;
private static readonly object _lock = new object();
public RaftTests()
{
_httpClientForAssertions = new HttpClient();
_httpClient = new HttpClient();
_ocelotBaseUrl = "http://localhost:5000";
_httpClient.BaseAddress = new Uri(_ocelotBaseUrl);
_webHostBuilders = new List<IWebHostBuilder>();
_builders = new List<IWebHost>();
_threads = new List<Thread>();
}
public void Dispose()
{
foreach (var builder in _builders)
{
builder?.Dispose();
}
foreach (var peer in _peers.Peers)
{
File.Delete(peer.HostAndPort.Replace("/","").Replace(":",""));
File.Delete($"{peer.HostAndPort.Replace("/","").Replace(":","")}.db");
}
}
[Fact]
public void should_persist_command_to_five_servers()
{
var configuration = new FileConfiguration
{
GlobalConfiguration = new FileGlobalConfiguration
{
}
};
var updatedConfiguration = new FileConfiguration
{
GlobalConfiguration = new FileGlobalConfiguration
{
},
ReRoutes = new List<FileReRoute>()
{
new FileReRoute()
{
DownstreamHostAndPorts = new List<FileHostAndPort>
{
new FileHostAndPort
{
Host = "127.0.0.1",
Port = 80,
}
},
DownstreamScheme = "http",
DownstreamPathTemplate = "/geoffrey",
UpstreamHttpMethod = new List<string> { "get" },
UpstreamPathTemplate = "/"
},
new FileReRoute()
{
DownstreamHostAndPorts = new List<FileHostAndPort>
{
new FileHostAndPort
{
Host = "123.123.123",
Port = 443,
}
},
DownstreamScheme = "https",
DownstreamPathTemplate = "/blooper/{productId}",
UpstreamHttpMethod = new List<string> { "post" },
UpstreamPathTemplate = "/test"
}
}
};
var command = new UpdateFileConfiguration(updatedConfiguration);
GivenThereIsAConfiguration(configuration);
GivenFiveServersAreRunning();
GivenALeaderIsElected();
GivenIHaveAnOcelotToken("/administration");
WhenISendACommandIntoTheCluster(command);
ThenTheCommandIsReplicatedToAllStateMachines(command);
}
[Fact]
public void should_persist_command_to_five_servers_when_using_administration_api()
{
var configuration = new FileConfiguration
{
};
var updatedConfiguration = new FileConfiguration
{
ReRoutes = new List<FileReRoute>()
{
new FileReRoute()
{
DownstreamHostAndPorts = new List<FileHostAndPort>
{
new FileHostAndPort
{
Host = "127.0.0.1",
Port = 80,
}
},
DownstreamScheme = "http",
DownstreamPathTemplate = "/geoffrey",
UpstreamHttpMethod = new List<string> { "get" },
UpstreamPathTemplate = "/"
},
new FileReRoute()
{
DownstreamHostAndPorts = new List<FileHostAndPort>
{
new FileHostAndPort
{
Host = "123.123.123",
Port = 443,
}
},
DownstreamScheme = "https",
DownstreamPathTemplate = "/blooper/{productId}",
UpstreamHttpMethod = new List<string> { "post" },
UpstreamPathTemplate = "/test"
}
}
};
var command = new UpdateFileConfiguration(updatedConfiguration);
GivenThereIsAConfiguration(configuration);
GivenFiveServersAreRunning();
GivenALeaderIsElected();
GivenIHaveAnOcelotToken("/administration");
GivenIHaveAddedATokenToMyRequest();
WhenIPostOnTheApiGateway("/administration/configuration", updatedConfiguration);
ThenTheCommandIsReplicatedToAllStateMachines(command);
}
private void WhenISendACommandIntoTheCluster(UpdateFileConfiguration command)
{
var p = _peers.Peers.First();
var json = JsonConvert.SerializeObject(command,new JsonSerializerSettings() {
TypeNameHandling = TypeNameHandling.All
});
var httpContent = new StringContent(json);
httpContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
using(var httpClient = new HttpClient())
{
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token.AccessToken);
var response = httpClient.PostAsync($"{p.HostAndPort}/administration/raft/command", httpContent).GetAwaiter().GetResult();
response.EnsureSuccessStatusCode();
var content = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
var result = JsonConvert.DeserializeObject<OkResponse<UpdateFileConfiguration>>(content);
result.Command.Configuration.ReRoutes.Count.ShouldBe(2);
}
//dirty sleep to make sure command replicated...
var stopwatch = Stopwatch.StartNew();
while(stopwatch.ElapsedMilliseconds < 10000)
{
}
}
private void ThenTheCommandIsReplicatedToAllStateMachines(UpdateFileConfiguration expecteds)
{
//dirty sleep to give a chance to replicate...
var stopwatch = Stopwatch.StartNew();
while(stopwatch.ElapsedMilliseconds < 2000)
{
}
bool CommandCalledOnAllStateMachines()
{
try
{
var passed = 0;
foreach (var peer in _peers.Peers)
{
var path = $"{peer.HostAndPort.Replace("/","").Replace(":","")}.db";
using(var connection = new SqliteConnection($"Data Source={path};"))
{
connection.Open();
var sql = @"select count(id) from logs";
using(var command = new SqliteCommand(sql, connection))
{
var index = Convert.ToInt32(command.ExecuteScalar());
index.ShouldBe(1);
}
}
_httpClientForAssertions.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token.AccessToken);
var result = _httpClientForAssertions.GetAsync($"{peer.HostAndPort}/administration/configuration").Result;
var json = result.Content.ReadAsStringAsync().Result;
var response = JsonConvert.DeserializeObject<FileConfiguration>(json, new JsonSerializerSettings{TypeNameHandling = TypeNameHandling.All});
response.GlobalConfiguration.RequestIdKey.ShouldBe(expecteds.Configuration.GlobalConfiguration.RequestIdKey);
response.GlobalConfiguration.ServiceDiscoveryProvider.Host.ShouldBe(expecteds.Configuration.GlobalConfiguration.ServiceDiscoveryProvider.Host);
response.GlobalConfiguration.ServiceDiscoveryProvider.Port.ShouldBe(expecteds.Configuration.GlobalConfiguration.ServiceDiscoveryProvider.Port);
for (var i = 0; i < response.ReRoutes.Count; i++)
{
for (var j = 0; j < response.ReRoutes[i].DownstreamHostAndPorts.Count; j++)
{
var res = response.ReRoutes[i].DownstreamHostAndPorts[j];
var expected = expecteds.Configuration.ReRoutes[i].DownstreamHostAndPorts[j];
res.Host.ShouldBe(expected.Host);
res.Port.ShouldBe(expected.Port);
}
response.ReRoutes[i].DownstreamPathTemplate.ShouldBe(expecteds.Configuration.ReRoutes[i].DownstreamPathTemplate);
response.ReRoutes[i].DownstreamScheme.ShouldBe(expecteds.Configuration.ReRoutes[i].DownstreamScheme);
response.ReRoutes[i].UpstreamPathTemplate.ShouldBe(expecteds.Configuration.ReRoutes[i].UpstreamPathTemplate);
response.ReRoutes[i].UpstreamHttpMethod.ShouldBe(expecteds.Configuration.ReRoutes[i].UpstreamHttpMethod);
}
passed++;
}
return passed == 5;
}
catch(Exception e)
{
Console.WriteLine(e);
return false;
}
}
var commandOnAllStateMachines = WaitFor(20000).Until(() => CommandCalledOnAllStateMachines());
commandOnAllStateMachines.ShouldBeTrue();
}
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 GivenIHaveAddedATokenToMyRequest()
{
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token.AccessToken);
}
private void GivenIHaveAnOcelotToken(string adminPath)
{
var tokenUrl = $"{adminPath}/connect/token";
var formData = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("client_id", "admin"),
new KeyValuePair<string, string>("client_secret", "secret"),
new KeyValuePair<string, string>("scope", "admin"),
new KeyValuePair<string, string>("grant_type", "client_credentials")
};
var content = new FormUrlEncodedContent(formData);
var response = _httpClient.PostAsync(tokenUrl, content).Result;
var responseContent = response.Content.ReadAsStringAsync().Result;
response.EnsureSuccessStatusCode();
_token = JsonConvert.DeserializeObject<BearerToken>(responseContent);
var configPath = $"{adminPath}/.well-known/openid-configuration";
response = _httpClient.GetAsync(configPath).Result;
response.EnsureSuccessStatusCode();
}
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);
}
private void GivenAServerIsRunning(string url)
{
lock(_lock)
{
IWebHostBuilder webHostBuilder = new WebHostBuilder();
webHostBuilder.UseUrls(url)
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.ConfigureServices(x =>
{
x.AddSingleton(webHostBuilder);
x.AddSingleton(new NodeId(url));
})
.UseStartup<RaftStartup>();
var builder = webHostBuilder.Build();
builder.Start();
_webHostBuilders.Add(webHostBuilder);
_builders.Add(builder);
}
}
private void GivenFiveServersAreRunning()
{
var bytes = File.ReadAllText("peers.json");
_peers = JsonConvert.DeserializeObject<FilePeers>(bytes);
foreach (var peer in _peers.Peers)
{
var thread = new Thread(() => GivenAServerIsRunning(peer.HostAndPort));
thread.Start();
_threads.Add(thread);
}
}
private void GivenALeaderIsElected()
{
//dirty sleep to make sure we have a leader
var stopwatch = Stopwatch.StartNew();
while(stopwatch.ElapsedMilliseconds < 20000)
{
}
}
}
}

View File

@ -1,184 +1,190 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
using Ocelot.Configuration.File;
using Shouldly;
using TestStack.BDDfy;
using Xunit;
using Microsoft.AspNetCore.Http;
using System.Threading.Tasks;
using System.Collections.Concurrent;
namespace Ocelot.IntegrationTests
{
public class ThreadSafeHeadersTests : IDisposable
{
private readonly HttpClient _httpClient;
private IWebHost _builder;
private IWebHostBuilder _webHostBuilder;
private readonly string _ocelotBaseUrl;
private IWebHost _downstreamBuilder;
private readonly Random _random;
private readonly ConcurrentBag<ThreadSafeHeadersTestResult> _results;
public ThreadSafeHeadersTests()
{
_results = new ConcurrentBag<ThreadSafeHeadersTestResult>();
_random = new Random();
_httpClient = new HttpClient();
_ocelotBaseUrl = "http://localhost:5001";
_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<FileReRoute>
{
new FileReRoute
{
DownstreamPathTemplate = "/",
DownstreamScheme = "http",
DownstreamHost = "localhost",
DownstreamPort = 51879,
UpstreamPathTemplate = "/",
UpstreamHttpMethod = new List<string> { "Get" },
}
}
};
this.Given(x => GivenThereIsAConfiguration(configuration))
.And(x => GivenThereIsAServiceRunningOn("http://localhost:51879"))
.And(x => GivenOcelotIsRunning())
.When(x => WhenIGetUrlOnTheApiGatewayMultipleTimesWithDifferentHeaderValues("/", 300))
.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 GivenOcelotIsRunning()
{
_webHostBuilder = new WebHostBuilder()
.UseUrls(_ocelotBaseUrl)
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.ConfigureServices(x =>
{
x.AddSingleton(_webHostBuilder);
})
.UseStartup<IntegrationTestsStartup>();
_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);
}
private 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<string> { 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; }
}
}
}
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
using Ocelot.Configuration.File;
using Shouldly;
using TestStack.BDDfy;
using Xunit;
using Microsoft.AspNetCore.Http;
using System.Threading.Tasks;
using System.Collections.Concurrent;
namespace Ocelot.IntegrationTests
{
public class ThreadSafeHeadersTests : IDisposable
{
private readonly HttpClient _httpClient;
private IWebHost _builder;
private IWebHostBuilder _webHostBuilder;
private readonly string _ocelotBaseUrl;
private IWebHost _downstreamBuilder;
private readonly Random _random;
private readonly ConcurrentBag<ThreadSafeHeadersTestResult> _results;
public ThreadSafeHeadersTests()
{
_results = new ConcurrentBag<ThreadSafeHeadersTestResult>();
_random = new Random();
_httpClient = new HttpClient();
_ocelotBaseUrl = "http://localhost:5001";
_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<FileReRoute>
{
new FileReRoute
{
DownstreamPathTemplate = "/",
DownstreamScheme = "http",
DownstreamHostAndPorts = new List<FileHostAndPort>
{
new FileHostAndPort
{
Host = "localhost",
Port = 51879,
}
},
UpstreamPathTemplate = "/",
UpstreamHttpMethod = new List<string> { "Get" },
}
}
};
this.Given(x => GivenThereIsAConfiguration(configuration))
.And(x => GivenThereIsAServiceRunningOn("http://localhost:51879"))
.And(x => GivenOcelotIsRunning())
.When(x => WhenIGetUrlOnTheApiGatewayMultipleTimesWithDifferentHeaderValues("/", 300))
.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 GivenOcelotIsRunning()
{
_webHostBuilder = new WebHostBuilder()
.UseUrls(_ocelotBaseUrl)
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.ConfigureServices(x =>
{
x.AddSingleton(_webHostBuilder);
})
.UseStartup<IntegrationTestsStartup>();
_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);
}
private 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<string> { 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; }
}
}
}

View File

@ -1,10 +1,10 @@
{
"Logging": {
"IncludeScopes": true,
"LogLevel": {
"Default": "Error",
"System": "Error",
"Microsoft": "Error"
}
}
}
{
"Logging": {
"IncludeScopes": true,
"LogLevel": {
"Default": "Error",
"System": "Error",
"Microsoft": "Error"
}
}
}

View File

@ -1,18 +1,18 @@
{
"Peers": [{
"HostAndPort": "http://localhost:5000"
},
{
"HostAndPort": "http://localhost:5002"
},
{
"HostAndPort": "http://localhost:5003"
},
{
"HostAndPort": "http://localhost:5004"
},
{
"HostAndPort": "http://localhost:5001"
}
]
{
"Peers": [{
"HostAndPort": "http://localhost:5000"
},
{
"HostAndPort": "http://localhost:5002"
},
{
"HostAndPort": "http://localhost:5003"
},
{
"HostAndPort": "http://localhost:5004"
},
{
"HostAndPort": "http://localhost:5001"
}
]
}