mirror of
				https://github.com/nsnail/Ocelot.git
				synced 2025-11-04 23:30:50 +08:00 
			
		
		
		
	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:
		@@ -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();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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; }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -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>
 | 
			
		||||
@@ -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")]
 | 
			
		||||
 
 | 
			
		||||
@@ -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();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
            {
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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; }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,10 @@
 | 
			
		||||
{
 | 
			
		||||
  "Logging": {
 | 
			
		||||
    "IncludeScopes": true,
 | 
			
		||||
    "LogLevel": {
 | 
			
		||||
      "Default": "Error",
 | 
			
		||||
      "System": "Error",
 | 
			
		||||
      "Microsoft": "Error"
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
{
 | 
			
		||||
  "Logging": {
 | 
			
		||||
    "IncludeScopes": true,
 | 
			
		||||
    "LogLevel": {
 | 
			
		||||
      "Default": "Error",
 | 
			
		||||
      "System": "Error",
 | 
			
		||||
      "Microsoft": "Error"
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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"
 | 
			
		||||
		}
 | 
			
		||||
	]
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user