#529 removed consul deps and introduced delegate to find service discovery provider (#538)

This commit is contained in:
Tom Pallister
2018-08-12 14:46:46 +05:30
committed by GitHub
parent afe322693e
commit a91235b405
26 changed files with 101 additions and 2385 deletions

View File

@ -1,469 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Text;
using Consul;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Newtonsoft.Json;
using Ocelot.Configuration.File;
using Shouldly;
using TestStack.BDDfy;
using Xunit;
using static Ocelot.Infrastructure.Wait;
namespace Ocelot.AcceptanceTests
{
using Cache;
public class ConfigurationInConsulTests : IDisposable
{
private IWebHost _builder;
private readonly Steps _steps;
private IWebHost _fakeConsulBuilder;
private FileConfiguration _config;
private List<ServiceEntry> _consulServices;
public ConfigurationInConsulTests()
{
_consulServices = new List<ServiceEntry>();
_steps = new Steps();
}
[Fact]
public void should_return_response_200_with_simple_url()
{
var configuration = new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
DownstreamPathTemplate = "/",
DownstreamScheme = "http",
DownstreamHostAndPorts = new List<FileHostAndPort>
{
new FileHostAndPort
{
Host = "localhost",
Port = 51779,
}
},
UpstreamPathTemplate = "/",
UpstreamHttpMethod = new List<string> { "Get" },
}
},
GlobalConfiguration = new FileGlobalConfiguration()
{
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider()
{
Host = "localhost",
Port = 9500
}
}
};
var fakeConsulServiceDiscoveryUrl = "http://localhost:9500";
this.Given(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl, ""))
.And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51779", "", 200, "Hello from Laura"))
.And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunningUsingConsulToStoreConfig())
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
.BDDfy();
}
[Fact]
public void should_load_configuration_out_of_consul()
{
var consulPort = 8500;
var configuration = new FileConfiguration
{
GlobalConfiguration = new FileGlobalConfiguration()
{
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider()
{
Host = "localhost",
Port = consulPort
}
}
};
var fakeConsulServiceDiscoveryUrl = $"http://localhost:{consulPort}";
var consulConfig = new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
DownstreamPathTemplate = "/status",
DownstreamScheme = "http",
DownstreamHostAndPorts = new List<FileHostAndPort>
{
new FileHostAndPort
{
Host = "localhost",
Port = 51779,
}
},
UpstreamPathTemplate = "/cs/status",
UpstreamHttpMethod = new List<string> {"Get"}
}
},
GlobalConfiguration = new FileGlobalConfiguration()
{
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider()
{
Host = "localhost",
Port = consulPort
}
}
};
this.Given(x => GivenTheConsulConfigurationIs(consulConfig))
.And(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl, ""))
.And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51779", "/status", 200, "Hello from Laura"))
.And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunningUsingConsulToStoreConfig())
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/cs/status"))
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
.BDDfy();
}
[Fact]
public void should_load_configuration_out_of_consul_if_it_is_changed()
{
var consulPort = 8506;
var configuration = new FileConfiguration
{
GlobalConfiguration = new FileGlobalConfiguration()
{
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider()
{
Host = "localhost",
Port = consulPort
}
}
};
var fakeConsulServiceDiscoveryUrl = $"http://localhost:{consulPort}";
var consulConfig = new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
DownstreamPathTemplate = "/status",
DownstreamScheme = "http",
DownstreamHostAndPorts = new List<FileHostAndPort>
{
new FileHostAndPort
{
Host = "localhost",
Port = 51780,
}
},
UpstreamPathTemplate = "/cs/status",
UpstreamHttpMethod = new List<string> {"Get"}
}
},
GlobalConfiguration = new FileGlobalConfiguration()
{
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider()
{
Host = "localhost",
Port = consulPort
}
}
};
var secondConsulConfig = new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
DownstreamPathTemplate = "/status",
DownstreamScheme = "http",
DownstreamHostAndPorts = new List<FileHostAndPort>
{
new FileHostAndPort
{
Host = "localhost",
Port = 51780,
}
},
UpstreamPathTemplate = "/cs/status/awesome",
UpstreamHttpMethod = new List<string> {"Get"}
}
},
GlobalConfiguration = new FileGlobalConfiguration()
{
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider()
{
Host = "localhost",
Port = consulPort
}
}
};
this.Given(x => GivenTheConsulConfigurationIs(consulConfig))
.And(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl, ""))
.And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51780", "/status", 200, "Hello from Laura"))
.And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunningUsingConsulToStoreConfig())
.And(x => _steps.WhenIGetUrlOnTheApiGateway("/cs/status"))
.And(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
.When(x => GivenTheConsulConfigurationIs(secondConsulConfig))
.Then(x => ThenTheConfigIsUpdatedInOcelot())
.BDDfy();
}
[Fact]
public void should_handle_request_to_consul_for_downstream_service_and_make_request_no_re_routes_and_rate_limit()
{
const int consulPort = 8523;
const string serviceName = "web";
const int downstreamServicePort = 8187;
var downstreamServiceOneUrl = $"http://localhost:{downstreamServicePort}";
var fakeConsulServiceDiscoveryUrl = $"http://localhost:{consulPort}";
var serviceEntryOne = new ServiceEntry()
{
Service = new AgentService()
{
Service = serviceName,
Address = "localhost",
Port = downstreamServicePort,
ID = "web_90_0_2_224_8080",
Tags = new[] {"version-v1"}
},
};
var consulConfig = new FileConfiguration
{
DynamicReRoutes = new List<FileDynamicReRoute>
{
new FileDynamicReRoute
{
ServiceName = serviceName,
RateLimitRule = new FileRateLimitRule()
{
EnableRateLimiting = true,
ClientWhitelist = new List<string>(),
Limit = 3,
Period = "1s",
PeriodTimespan = 1000
}
}
},
GlobalConfiguration = new FileGlobalConfiguration
{
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider
{
Host = "localhost",
Port = consulPort
},
RateLimitOptions = new FileRateLimitOptions()
{
ClientIdHeader = "ClientId",
DisableRateLimitHeaders = false,
QuotaExceededMessage = "",
RateLimitCounterPrefix = "",
HttpStatusCode = 428
},
DownstreamScheme = "http",
}
};
var configuration = new FileConfiguration
{
GlobalConfiguration = new FileGlobalConfiguration
{
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider
{
Host = "localhost",
Port = consulPort
}
}
};
this.Given(x => x.GivenThereIsAServiceRunningOn(downstreamServiceOneUrl, "/something", 200, "Hello from Laura"))
.And(x => GivenTheConsulConfigurationIs(consulConfig))
.And(x => x.GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl, serviceName))
.And(x => x.GivenTheServicesAreRegisteredWithConsul(serviceEntryOne))
.And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunningUsingConsulToStoreConfig())
.When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/web/something",1))
.Then(x => _steps.ThenTheStatusCodeShouldBe(200))
.When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/web/something", 2))
.Then(x => _steps.ThenTheStatusCodeShouldBe(200))
.When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/web/something",1))
.Then(x => _steps.ThenTheStatusCodeShouldBe(428))
.BDDfy();
}
private void ThenTheConfigIsUpdatedInOcelot()
{
var result = WaitFor(20000).Until(() => {
try
{
_steps.WhenIGetUrlOnTheApiGateway("/cs/status/awesome");
_steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK);
_steps.ThenTheResponseBodyShouldBe("Hello from Laura");
return true;
}
catch (Exception)
{
return false;
}
});
result.ShouldBeTrue();
}
private void GivenTheConsulConfigurationIs(FileConfiguration config)
{
_config = config;
}
private void GivenTheServicesAreRegisteredWithConsul(params ServiceEntry[] serviceEntries)
{
foreach(var serviceEntry in serviceEntries)
{
_consulServices.Add(serviceEntry);
}
}
private void GivenThereIsAFakeConsulServiceDiscoveryProvider(string url, string serviceName)
{
_fakeConsulBuilder = new WebHostBuilder()
.UseUrls(url)
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseUrls(url)
.Configure(app =>
{
app.Run(async context =>
{
if (context.Request.Method.ToLower() == "get" && context.Request.Path.Value == "/v1/kv/InternalConfiguration")
{
var json = JsonConvert.SerializeObject(_config);
var bytes = Encoding.UTF8.GetBytes(json);
var base64 = Convert.ToBase64String(bytes);
var kvp = new FakeConsulGetResponse(base64);
await context.Response.WriteJsonAsync(new FakeConsulGetResponse[] { kvp });
}
else if (context.Request.Method.ToLower() == "put" && context.Request.Path.Value == "/v1/kv/InternalConfiguration")
{
try
{
var reader = new StreamReader(context.Request.Body);
var json = reader.ReadToEnd();
_config = JsonConvert.DeserializeObject<FileConfiguration>(json);
var response = JsonConvert.SerializeObject(true);
await context.Response.WriteAsync(response);
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
else if (context.Request.Path.Value == $"/v1/health/service/{serviceName}")
{
await context.Response.WriteJsonAsync(_consulServices);
}
});
})
.Build();
_fakeConsulBuilder.Start();
}
public class FakeConsulGetResponse
{
public FakeConsulGetResponse(string value)
{
Value = value;
}
public int CreateIndex => 100;
public int ModifyIndex => 200;
public int LockIndex => 200;
public string Key => "InternalConfiguration";
public int Flags => 0;
public string Value { get; private set; }
public string Session => "adf4238a-882b-9ddc-4a9d-5b6758e4159e";
}
private void GivenThereIsAServiceRunningOn(string url, string basePath, int statusCode, string responseBody)
{
_builder = new WebHostBuilder()
.UseUrls(url)
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseUrls(url)
.Configure(app =>
{
app.UsePathBase(basePath);
app.Run(async context =>
{
context.Response.StatusCode = statusCode;
await context.Response.WriteAsync(responseBody);
});
})
.Build();
_builder.Start();
}
public void Dispose()
{
_builder?.Dispose();
_steps.Dispose();
}
class FakeCache : IOcelotCache<FileConfiguration>
{
public void Add(string key, FileConfiguration value, TimeSpan ttl, string region)
{
throw new NotImplementedException();
}
public void AddAndDelete(string key, FileConfiguration value, TimeSpan ttl, string region)
{
throw new NotImplementedException();
}
public FileConfiguration Get(string key, string region)
{
throw new NotImplementedException();
}
public void ClearRegion(string region)
{
throw new NotImplementedException();
}
}
}
}

View File

@ -50,7 +50,6 @@
<PackageReference Include="Microsoft.DotNet.InternalAbstractions" Version="1.0.500-preview2-1-003177" />
<PackageReference Include="Shouldly" Version="3.0.0" />
<PackageReference Include="TestStack.BDDfy" Version="4.3.2" />
<PackageReference Include="Consul" Version="0.7.2.5" />
<PackageReference Include="xunit" Version="2.3.1" />
<PackageReference Include="Butterfly.Client.AspNetCore" Version="0.0.8" />
<PackageReference Include="Rafty" Version="0.4.4" />

View File

@ -2,12 +2,9 @@ namespace Ocelot.AcceptanceTests
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using Consul;
using Microsoft.AspNetCore.Http;
using Ocelot.Configuration.File;
using Shouldly;
using TestStack.BDDfy;
using Xunit;
using Newtonsoft.Json;
@ -16,20 +13,13 @@ namespace Ocelot.AcceptanceTests
public class ServiceDiscoveryTests : IDisposable
{
private readonly Steps _steps;
private readonly List<ServiceEntry> _consulServices;
private readonly List<IServiceInstance> _eurekaInstances;
private int _counterOne;
private int _counterTwo;
private static readonly object SyncLock = new object();
private string _downstreamPath;
private string _receivedToken;
private readonly ServiceHandler _serviceHandler;
public ServiceDiscoveryTests()
{
_serviceHandler = new ServiceHandler();
_steps = new Steps();
_consulServices = new List<ServiceEntry>();
_eurekaInstances = new List<IServiceInstance>();
}
@ -80,479 +70,6 @@ namespace Ocelot.AcceptanceTests
.BDDfy();
}
[Fact]
public void should_use_consul_service_discovery_and_load_balance_request()
{
var consulPort = 8502;
var serviceName = "product";
var downstreamServiceOneUrl = "http://localhost:50881";
var downstreamServiceTwoUrl = "http://localhost:50882";
var fakeConsulServiceDiscoveryUrl = $"http://localhost:{consulPort}";
var serviceEntryOne = new ServiceEntry()
{
Service = new AgentService()
{
Service = serviceName,
Address = "localhost",
Port = 50881,
ID = Guid.NewGuid().ToString(),
Tags = new string[0]
},
};
var serviceEntryTwo = new ServiceEntry()
{
Service = new AgentService()
{
Service = serviceName,
Address = "localhost",
Port = 50882,
ID = Guid.NewGuid().ToString(),
Tags = new string[0]
},
};
var configuration = new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
DownstreamPathTemplate = "/",
DownstreamScheme = "http",
UpstreamPathTemplate = "/",
UpstreamHttpMethod = new List<string> { "Get" },
ServiceName = serviceName,
LoadBalancerOptions = new FileLoadBalancerOptions { Type = "LeastConnection" },
UseServiceDiscovery = true,
}
},
GlobalConfiguration = new FileGlobalConfiguration()
{
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider()
{
Host = "localhost",
Port = consulPort
}
}
};
this.Given(x => x.GivenProductServiceOneIsRunning(downstreamServiceOneUrl, 200))
.And(x => x.GivenProductServiceTwoIsRunning(downstreamServiceTwoUrl, 200))
.And(x => x.GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl, serviceName))
.And(x => x.GivenTheServicesAreRegisteredWithConsul(serviceEntryOne, serviceEntryTwo))
.And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunning())
.When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimes("/", 50))
.Then(x => x.ThenTheTwoServicesShouldHaveBeenCalledTimes(50))
.And(x => x.ThenBothServicesCalledRealisticAmountOfTimes(24, 26))
.BDDfy();
}
[Fact]
public void should_handle_request_to_consul_for_downstream_service_and_make_request()
{
const int consulPort = 8505;
const string serviceName = "web";
const string downstreamServiceOneUrl = "http://localhost:8080";
var fakeConsulServiceDiscoveryUrl = $"http://localhost:{consulPort}";
var serviceEntryOne = new ServiceEntry()
{
Service = new AgentService()
{
Service = serviceName,
Address = "localhost",
Port = 8080,
ID = "web_90_0_2_224_8080",
Tags = new[] {"version-v1"}
},
};
var configuration = new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
DownstreamPathTemplate = "/api/home",
DownstreamScheme = "http",
UpstreamPathTemplate = "/home",
UpstreamHttpMethod = new List<string> { "Get", "Options" },
ServiceName = serviceName,
LoadBalancerOptions = new FileLoadBalancerOptions { Type = "LeastConnection" },
UseServiceDiscovery = true,
}
},
GlobalConfiguration = new FileGlobalConfiguration()
{
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider()
{
Host = "localhost",
Port = consulPort
}
}
};
this.Given(x => x.GivenThereIsAServiceRunningOn(downstreamServiceOneUrl, "/api/home", 200, "Hello from Laura"))
.And(x => x.GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl, serviceName))
.And(x => x.GivenTheServicesAreRegisteredWithConsul(serviceEntryOne))
.And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunning())
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/home"))
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
.BDDfy();
}
[Fact]
public void should_handle_request_to_consul_for_downstream_service_and_make_request_no_re_routes()
{
const int consulPort = 8513;
const string serviceName = "web";
const int downstreamServicePort = 8087;
var downstreamServiceOneUrl = $"http://localhost:{downstreamServicePort}";
var fakeConsulServiceDiscoveryUrl = $"http://localhost:{consulPort}";
var serviceEntryOne = new ServiceEntry()
{
Service = new AgentService()
{
Service = serviceName,
Address = "localhost",
Port = downstreamServicePort,
ID = "web_90_0_2_224_8080",
Tags = new[] {"version-v1"}
},
};
var configuration = new FileConfiguration
{
GlobalConfiguration = new FileGlobalConfiguration
{
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider
{
Host = "localhost",
Port = consulPort
},
DownstreamScheme = "http",
HttpHandlerOptions = new FileHttpHandlerOptions
{
AllowAutoRedirect = true,
UseCookieContainer = true,
UseTracing = false
},
QoSOptions = new FileQoSOptions
{
TimeoutValue = 100,
DurationOfBreak = 1000,
ExceptionsAllowedBeforeBreaking = 1
}
}
};
this.Given(x => x.GivenThereIsAServiceRunningOn(downstreamServiceOneUrl, "/something", 200, "Hello from Laura"))
.And(x => x.GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl, serviceName))
.And(x => x.GivenTheServicesAreRegisteredWithConsul(serviceEntryOne))
.And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunning())
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/web/something"))
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
.BDDfy();
}
[Fact]
public void should_use_consul_service_discovery_and_load_balance_request_no_re_routes()
{
var consulPort = 8510;
var serviceName = "product";
var serviceOnePort = 50888;
var serviceTwoPort = 50889;
var downstreamServiceOneUrl = $"http://localhost:{serviceOnePort}";
var downstreamServiceTwoUrl = $"http://localhost:{serviceTwoPort}";
var fakeConsulServiceDiscoveryUrl = $"http://localhost:{consulPort}";
var serviceEntryOne = new ServiceEntry()
{
Service = new AgentService()
{
Service = serviceName,
Address = "localhost",
Port = serviceOnePort,
ID = Guid.NewGuid().ToString(),
Tags = new string[0]
},
};
var serviceEntryTwo = new ServiceEntry()
{
Service = new AgentService()
{
Service = serviceName,
Address = "localhost",
Port = serviceTwoPort,
ID = Guid.NewGuid().ToString(),
Tags = new string[0]
},
};
var configuration = new FileConfiguration
{
GlobalConfiguration = new FileGlobalConfiguration()
{
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider()
{
Host = "localhost",
Port = consulPort
},
LoadBalancerOptions = new FileLoadBalancerOptions { Type = "LeastConnection" },
DownstreamScheme = "http"
}
};
this.Given(x => x.GivenProductServiceOneIsRunning(downstreamServiceOneUrl, 200))
.And(x => x.GivenProductServiceTwoIsRunning(downstreamServiceTwoUrl, 200))
.And(x => x.GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl, serviceName))
.And(x => x.GivenTheServicesAreRegisteredWithConsul(serviceEntryOne, serviceEntryTwo))
.And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunning())
.When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimes($"/{serviceName}/", 50))
.Then(x => x.ThenTheTwoServicesShouldHaveBeenCalledTimes(50))
.And(x => x.ThenBothServicesCalledRealisticAmountOfTimes(24, 26))
.BDDfy();
}
[Fact]
public void should_use_token_to_make_request_to_consul()
{
var token = "abctoken";
var consulPort = 8515;
var serviceName = "web";
var downstreamServiceOneUrl = "http://localhost:8081";
var fakeConsulServiceDiscoveryUrl = $"http://localhost:{consulPort}";
var serviceEntryOne = new ServiceEntry()
{
Service = new AgentService()
{
Service = serviceName,
Address = "localhost",
Port = 8081,
ID = "web_90_0_2_224_8080",
Tags = new[] { "version-v1" }
},
};
var configuration = new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
DownstreamPathTemplate = "/api/home",
DownstreamScheme = "http",
UpstreamPathTemplate = "/home",
UpstreamHttpMethod = new List<string> { "Get", "Options" },
ServiceName = serviceName,
LoadBalancerOptions = new FileLoadBalancerOptions { Type = "LeastConnection" },
UseServiceDiscovery = true,
}
},
GlobalConfiguration = new FileGlobalConfiguration()
{
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider()
{
Host = "localhost",
Port = consulPort,
Token = token
}
}
};
this.Given(_ => GivenThereIsAServiceRunningOn(downstreamServiceOneUrl, "/api/home", 200, "Hello from Laura"))
.And(_ => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl, serviceName))
.And(_ => GivenTheServicesAreRegisteredWithConsul(serviceEntryOne))
.And(_ => _steps.GivenThereIsAConfiguration(configuration))
.And(_ => _steps.GivenOcelotIsRunning())
.When(_ => _steps.WhenIGetUrlOnTheApiGateway("/home"))
.Then(_ => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(_ => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
.And(_ => _receivedToken.ShouldBe(token))
.BDDfy();
}
[Fact]
public void should_send_request_to_service_after_it_becomes_available_in_consul()
{
var consulPort = 8501;
var serviceName = "product";
var downstreamServiceOneUrl = "http://localhost:50879";
var downstreamServiceTwoUrl = "http://localhost:50880";
var fakeConsulServiceDiscoveryUrl = $"http://localhost:{consulPort}";
var serviceEntryOne = new ServiceEntry()
{
Service = new AgentService()
{
Service = serviceName,
Address = "localhost",
Port = 50879,
ID = Guid.NewGuid().ToString(),
Tags = new string[0]
},
};
var serviceEntryTwo = new ServiceEntry()
{
Service = new AgentService()
{
Service = serviceName,
Address = "localhost",
Port = 50880,
ID = Guid.NewGuid().ToString(),
Tags = new string[0]
},
};
var configuration = new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
DownstreamPathTemplate = "/",
DownstreamScheme = "http",
UpstreamPathTemplate = "/",
UpstreamHttpMethod = new List<string> { "Get" },
ServiceName = serviceName,
LoadBalancerOptions = new FileLoadBalancerOptions { Type = "LeastConnection" },
UseServiceDiscovery = true,
}
},
GlobalConfiguration = new FileGlobalConfiguration()
{
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider()
{
Host = "localhost",
Port = consulPort
}
}
};
this.Given(x => x.GivenProductServiceOneIsRunning(downstreamServiceOneUrl, 200))
.And(x => x.GivenProductServiceTwoIsRunning(downstreamServiceTwoUrl, 200))
.And(x => x.GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl, serviceName))
.And(x => x.GivenTheServicesAreRegisteredWithConsul(serviceEntryOne, serviceEntryTwo))
.And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunning())
.And(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimes("/", 10))
.And(x => x.ThenTheTwoServicesShouldHaveBeenCalledTimes(10))
.And(x => x.ThenBothServicesCalledRealisticAmountOfTimes(4, 6))
.And(x => WhenIRemoveAService(serviceEntryTwo))
.And(x => GivenIResetCounters())
.And(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimes("/", 10))
.And(x => ThenOnlyOneServiceHasBeenCalled())
.And(x => WhenIAddAServiceBackIn(serviceEntryTwo))
.And(x => GivenIResetCounters())
.When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimes("/", 10))
.Then(x => x.ThenTheTwoServicesShouldHaveBeenCalledTimes(10))
.And(x => x.ThenBothServicesCalledRealisticAmountOfTimes(4, 6))
.BDDfy();
}
[Fact]
public void should_handle_request_to_poll_consul_for_downstream_service_and_make_request()
{
const int consulPort = 8518;
const string serviceName = "web";
const int downstreamServicePort = 8082;
var downstreamServiceOneUrl = $"http://localhost:{downstreamServicePort}";
var fakeConsulServiceDiscoveryUrl = $"http://localhost:{consulPort}";
var serviceEntryOne = new ServiceEntry()
{
Service = new AgentService()
{
Service = serviceName,
Address = "localhost",
Port = downstreamServicePort,
ID = $"web_90_0_2_224_{downstreamServicePort}",
Tags = new[] {"version-v1"}
},
};
var configuration = new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
DownstreamPathTemplate = "/api/home",
DownstreamScheme = "http",
UpstreamPathTemplate = "/home",
UpstreamHttpMethod = new List<string> { "Get", "Options" },
ServiceName = serviceName,
LoadBalancerOptions = new FileLoadBalancerOptions { Type = "LeastConnection" },
UseServiceDiscovery = true,
}
},
GlobalConfiguration = new FileGlobalConfiguration()
{
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider()
{
Host = "localhost",
Port = consulPort,
Type = "PollConsul",
PollingInterval = 0
}
}
};
this.Given(x => x.GivenThereIsAServiceRunningOn(downstreamServiceOneUrl, "/api/home", 200, "Hello from Laura"))
.And(x => x.GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl, serviceName))
.And(x => x.GivenTheServicesAreRegisteredWithConsul(serviceEntryOne))
.And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunning())
.When(x => _steps.WhenIGetUrlOnTheApiGatewayWaitingForTheResponseToBeOk("/home"))
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
.BDDfy();
}
private void WhenIAddAServiceBackIn(ServiceEntry serviceEntryTwo)
{
_consulServices.Add(serviceEntryTwo);
}
private void ThenOnlyOneServiceHasBeenCalled()
{
_counterOne.ShouldBe(10);
_counterTwo.ShouldBe(0);
}
private void WhenIRemoveAService(ServiceEntry serviceEntryTwo)
{
_consulServices.Remove(serviceEntryTwo);
}
private void GivenIResetCounters()
{
_counterOne = 0;
_counterTwo = 0;
}
private void ThenBothServicesCalledRealisticAmountOfTimes(int bottom, int top)
{
_counterOne.ShouldBeInRange(bottom, top);
_counterOne.ShouldBeInRange(bottom, top);
}
private void ThenTheTwoServicesShouldHaveBeenCalledTimes(int expected)
{
var total = _counterOne + _counterTwo;
total.ShouldBe(expected);
}
private void GivenTheServicesAreRegisteredWithConsul(params ServiceEntry[] serviceEntries)
{
foreach(var serviceEntry in serviceEntries)
{
_consulServices.Add(serviceEntry);
}
}
private void GivenTheServicesAreRegisteredWithEureka(params IServiceInstance[] serviceInstances)
{
foreach (var instance in serviceInstances)
@ -631,68 +148,6 @@ namespace Ocelot.AcceptanceTests
});
}
private void GivenThereIsAFakeConsulServiceDiscoveryProvider(string url, string serviceName)
{
_serviceHandler.GivenThereIsAServiceRunningOn(url, async context =>
{
if (context.Request.Path.Value == $"/v1/health/service/{serviceName}")
{
if (context.Request.Headers.TryGetValue("X-Consul-Token", out var values))
{
_receivedToken = values.First();
}
await context.Response.WriteJsonAsync(_consulServices);
}
});
}
private void GivenProductServiceOneIsRunning(string url, int statusCode)
{
_serviceHandler.GivenThereIsAServiceRunningOn(url, async context =>
{
try
{
string response;
lock (SyncLock)
{
_counterOne++;
response = _counterOne.ToString();
}
context.Response.StatusCode = statusCode;
await context.Response.WriteAsync(response);
}
catch (Exception exception)
{
await context.Response.WriteAsync(exception.StackTrace);
}
});
}
private void GivenProductServiceTwoIsRunning(string url, int statusCode)
{
_serviceHandler.GivenThereIsAServiceRunningOn(url, async context =>
{
try
{
string response;
lock (SyncLock)
{
_counterTwo++;
response = _counterTwo.ToString();
}
context.Response.StatusCode = statusCode;
await context.Response.WriteAsync(response);
}
catch (Exception exception)
{
await context.Response.WriteAsync(exception.StackTrace);
}
});
}
private void GivenEurekaProductServiceOneIsRunning(string url)
{
_serviceHandler.GivenThereIsAServiceRunningOn(url, async context =>
@ -709,25 +164,6 @@ namespace Ocelot.AcceptanceTests
});
}
private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode, string responseBody)
{
_serviceHandler.GivenThereIsAServiceRunningOn(baseUrl, basePath, async context =>
{
_downstreamPath = !string.IsNullOrEmpty(context.Request.PathBase.Value) ? context.Request.PathBase.Value : context.Request.Path.Value;
if (_downstreamPath != basePath)
{
context.Response.StatusCode = statusCode;
await context.Response.WriteAsync("downstream path didnt match base path");
}
else
{
context.Response.StatusCode = statusCode;
await context.Response.WriteAsync(responseBody);
}
});
}
public void Dispose()
{
_serviceHandler?.Dispose();

View File

@ -422,34 +422,6 @@
header.First().ShouldBe(value);
}
public void GivenOcelotIsRunningUsingConsulToStoreConfig()
{
_webHostBuilder = new WebHostBuilder();
_webHostBuilder
.ConfigureAppConfiguration((hostingContext, config) =>
{
config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath);
var env = hostingContext.HostingEnvironment;
config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: false);
config.AddJsonFile("ocelot.json", optional: true, reloadOnChange: false);
config.AddEnvironmentVariables();
})
.ConfigureServices(s =>
{
s.AddOcelot().AddStoreOcelotConfigurationInConsul();
})
.Configure(app =>
{
app.UseOcelot().Wait();
});
_ocelotServer = new TestServer(_webHostBuilder);
_ocelotClient = _ocelotServer.CreateClient();
}
/// <summary>
/// This is annoying cos it should be in the constructor but we need to set up the file before calling startup so its a step.
/// </summary>
@ -576,24 +548,6 @@
_response = _ocelotClient.GetAsync(url).Result;
}
public void WhenIGetUrlOnTheApiGatewayWaitingForTheResponseToBeOk(string url)
{
var result = WaitFor(2000).Until(() => {
try
{
_response = _ocelotClient.GetAsync(url).Result;
_response.EnsureSuccessStatusCode();
return true;
}
catch(Exception)
{
return false;
}
});
result.ShouldBeTrue();
}
public void WhenIGetUrlOnTheApiGateway(string url, string cookie, string value)
{
var request = _ocelotServer.CreateRequest(url);

View File

@ -1,151 +0,0 @@
namespace Ocelot.AcceptanceTests
{
using System;
using System.Collections.Generic;
using System.Net;
using Consul;
using Microsoft.AspNetCore.Http;
using Ocelot.Configuration.File;
using TestStack.BDDfy;
using Xunit;
public class TwoDownstreamServicesTests : IDisposable
{
private readonly Steps _steps;
private readonly List<ServiceEntry> _serviceEntries;
private string _downstreamPathOne;
private string _downstreamPathTwo;
private readonly ServiceHandler _serviceHandler;
public TwoDownstreamServicesTests()
{
_serviceHandler = new ServiceHandler();
_steps = new Steps();
_serviceEntries = new List<ServiceEntry>();
}
[Fact]
public void should_fix_issue_194()
{
var consulPort = 8503;
var downstreamServiceOneUrl = "http://localhost:8362";
var downstreamServiceTwoUrl = "http://localhost:8330";
var fakeConsulServiceDiscoveryUrl = $"http://localhost:{consulPort}";
var configuration = new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
DownstreamPathTemplate = "/api/user/{user}",
DownstreamScheme = "http",
DownstreamHostAndPorts = new List<FileHostAndPort>
{
new FileHostAndPort
{
Host = "localhost",
Port = 8362,
}
},
UpstreamPathTemplate = "/api/user/{user}",
UpstreamHttpMethod = new List<string> { "Get" },
},
new FileReRoute
{
DownstreamPathTemplate = "/api/product/{product}",
DownstreamScheme = "http",
DownstreamHostAndPorts = new List<FileHostAndPort>
{
new FileHostAndPort
{
Host = "localhost",
Port = 8330,
}
},
UpstreamPathTemplate = "/api/product/{product}",
UpstreamHttpMethod = new List<string> { "Get" },
}
},
GlobalConfiguration = new FileGlobalConfiguration()
{
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider()
{
Host = "localhost",
Port = consulPort
}
}
};
this.Given(x => x.GivenProductServiceOneIsRunning(downstreamServiceOneUrl, "/api/user/info", 200, "user"))
.And(x => x.GivenProductServiceTwoIsRunning(downstreamServiceTwoUrl, "/api/product/info", 200, "product"))
.And(x => x.GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl))
.And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunning())
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/api/user/info?id=1"))
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(x => _steps.ThenTheResponseBodyShouldBe("user"))
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/api/product/info?id=1"))
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(x => _steps.ThenTheResponseBodyShouldBe("product"))
.BDDfy();
}
private void GivenThereIsAFakeConsulServiceDiscoveryProvider(string url)
{
_serviceHandler.GivenThereIsAServiceRunningOn(url, async context =>
{
if (context.Request.Path.Value == "/v1/health/service/product")
{
await context.Response.WriteJsonAsync(_serviceEntries);
}
});
}
private void GivenProductServiceOneIsRunning(string baseUrl, string basePath, int statusCode, string responseBody)
{
_serviceHandler.GivenThereIsAServiceRunningOn(baseUrl, basePath, async context =>
{
_downstreamPathOne = !string.IsNullOrEmpty(context.Request.PathBase.Value)
? context.Request.PathBase.Value
: context.Request.Path.Value;
if (_downstreamPathOne != basePath)
{
context.Response.StatusCode = statusCode;
await context.Response.WriteAsync("downstream path didnt match base path");
}
else
{
context.Response.StatusCode = statusCode;
await context.Response.WriteAsync(responseBody);
}
});
}
private void GivenProductServiceTwoIsRunning(string baseUrl, string basePath, int statusCode, string responseBody)
{
_serviceHandler.GivenThereIsAServiceRunningOn(baseUrl, basePath, async context =>
{
_downstreamPathTwo = !string.IsNullOrEmpty(context.Request.PathBase.Value) ? context.Request.PathBase.Value : context.Request.Path.Value;
if (_downstreamPathTwo != basePath)
{
context.Response.StatusCode = statusCode;
await context.Response.WriteAsync("downstream path didnt match base path");
}
else
{
context.Response.StatusCode = statusCode;
await context.Response.WriteAsync(responseBody);
}
});
}
public void Dispose()
{
_serviceHandler?.Dispose();
_steps.Dispose();
}
}
}

View File

@ -6,8 +6,6 @@ namespace Ocelot.AcceptanceTests
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Consul;
using Microsoft.AspNetCore.Http;
using Ocelot.Configuration.File;
using Shouldly;
using TestStack.BDDfy;
@ -17,7 +15,6 @@ namespace Ocelot.AcceptanceTests
{
private readonly List<string> _secondRecieved;
private readonly List<string> _firstRecieved;
private readonly List<ServiceEntry> _serviceEntries;
private readonly Steps _steps;
private readonly ServiceHandler _serviceHandler;
@ -27,7 +24,6 @@ namespace Ocelot.AcceptanceTests
_steps = new Steps();
_firstRecieved = new List<string>();
_secondRecieved = new List<string>();
_serviceEntries = new List<ServiceEntry>();
}
[Fact]
@ -109,77 +105,6 @@ namespace Ocelot.AcceptanceTests
.BDDfy();
}
[Fact]
public void should_proxy_websocket_input_to_downstream_service_and_use_service_discovery_and_load_balancer()
{
var downstreamPort = 5007;
var downstreamHost = "localhost";
var secondDownstreamPort = 5008;
var secondDownstreamHost = "localhost";
var serviceName = "websockets";
var consulPort = 8509;
var fakeConsulServiceDiscoveryUrl = $"http://localhost:{consulPort}";
var serviceEntryOne = new ServiceEntry()
{
Service = new AgentService()
{
Service = serviceName,
Address = downstreamHost,
Port = downstreamPort,
ID = Guid.NewGuid().ToString(),
Tags = new string[0]
},
};
var serviceEntryTwo = new ServiceEntry()
{
Service = new AgentService()
{
Service = serviceName,
Address = secondDownstreamHost,
Port = secondDownstreamPort,
ID = Guid.NewGuid().ToString(),
Tags = new string[0]
},
};
var config = new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
UpstreamPathTemplate = "/",
DownstreamPathTemplate = "/ws",
DownstreamScheme = "ws",
LoadBalancerOptions = new FileLoadBalancerOptions { Type = "RoundRobin" },
ServiceName = serviceName,
UseServiceDiscovery = true
}
},
GlobalConfiguration = new FileGlobalConfiguration
{
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider
{
Host = "localhost",
Port = consulPort,
Type = "consul"
}
}
};
this.Given(_ => _steps.GivenThereIsAConfiguration(config))
.And(_ => _steps.StartFakeOcelotWithWebSockets())
.And(_ => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl, serviceName))
.And(_ => GivenTheServicesAreRegisteredWithConsul(serviceEntryOne, serviceEntryTwo))
.And(_ => StartFakeDownstreamService($"http://{downstreamHost}:{downstreamPort}", "/ws"))
.And(_ => StartSecondFakeDownstreamService($"http://{secondDownstreamHost}:{secondDownstreamPort}", "/ws"))
.When(_ => WhenIStartTheClients())
.Then(_ => ThenBothDownstreamServicesAreCalled())
.BDDfy();
}
private void ThenBothDownstreamServicesAreCalled()
{
_firstRecieved.Count.ShouldBe(10);
@ -195,25 +120,6 @@ namespace Ocelot.AcceptanceTests
});
}
private void GivenTheServicesAreRegisteredWithConsul(params ServiceEntry[] serviceEntries)
{
foreach (var serviceEntry in serviceEntries)
{
_serviceEntries.Add(serviceEntry);
}
}
private void GivenThereIsAFakeConsulServiceDiscoveryProvider(string url, string serviceName)
{
_serviceHandler.GivenThereIsAServiceRunningOn(url, async context =>
{
if (context.Request.Path.Value == $"/v1/health/service/{serviceName}")
{
await context.Response.WriteJsonAsync(_serviceEntries);
}
});
}
private async Task WhenIStartTheClients()
{
var firstClient = StartClient("ws://localhost:5000/");