diff --git a/Ocelot.Provider.Polly.sln b/Ocelot.Provider.Polly.sln index 429065d..fadf0c0 100644 --- a/Ocelot.Provider.Polly.sln +++ b/Ocelot.Provider.Polly.sln @@ -1,4 +1,5 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 + +Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 VisualStudioVersion = 15.0.27130.2036 MinimumVisualStudioVersion = 10.0.40219.1 diff --git a/README.md b/README.md index f0998df..1470177 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,9 @@ [](http://threemammals.com/ocelot) -[![Build status](https://ci.appveyor.com/api/projects/status/8ry4ailt7rr5mbu7?svg=true)](https://ci.appveyor.com/project/TomPallister/ocelot-cache-cachemanager) -Windows (AppVeyor) -[![Build Status](https://travis-ci.org/ThreeMammals/Ocelot.Provider.Polly.svg?branch=master)](https://travis-ci.org/ThreeMammals/Ocelot.Provider.Polly) Linux & OSX (Travis) +[![Build status](https://ci.appveyor.com/api/projects/status/64dld693t29wgikc?svg=true)](https://ci.appveyor.com/project/TomPallister/ocelot-provider-polly) Windows (AppVeyor) +[![Build Status](https://travis-ci.org/ThreeMammals/Ocelot.Provider.Polly.svg?branch=develop)](https://travis-ci.org/ThreeMammals/Ocelot.Provider.Polly) Linux & OSX (Travis) -[![Coverage Status](https://coveralls.io/repos/github/ThreeMammals/Ocelot.Provider.Polly/badge.svg?branch=develop)](https://coveralls.io/github/ThreeMammals/Ocelot.Provider.Polly?branch=develop) +[![Coverage Status](https://coveralls.io/repos/github/ThreeMammals/Ocelot.Provider.Polly/badge.svg)](https://coveralls.io/github/ThreeMammals/Ocelot.Provider.Polly) # Ocelot diff --git a/src/Ocelot.Provider.Polly/CircuitBreaker.cs b/src/Ocelot.Provider.Polly/CircuitBreaker.cs new file mode 100644 index 0000000..b47f6be --- /dev/null +++ b/src/Ocelot.Provider.Polly/CircuitBreaker.cs @@ -0,0 +1,17 @@ +using Polly.CircuitBreaker; +using Polly.Timeout; + +namespace Ocelot.Provider.Polly +{ + public class CircuitBreaker + { + public CircuitBreaker(CircuitBreakerPolicy circuitBreakerPolicy, TimeoutPolicy timeoutPolicy) + { + CircuitBreakerPolicy = circuitBreakerPolicy; + TimeoutPolicy = timeoutPolicy; + } + + public CircuitBreakerPolicy CircuitBreakerPolicy { get; private set; } + public TimeoutPolicy TimeoutPolicy { get; private set; } + } +} diff --git a/src/Ocelot.Provider.Polly/Ocelot.Provider.Polly.csproj b/src/Ocelot.Provider.Polly/Ocelot.Provider.Polly.csproj index 69f8720..2dc4661 100644 --- a/src/Ocelot.Provider.Polly/Ocelot.Provider.Polly.csproj +++ b/src/Ocelot.Provider.Polly/Ocelot.Provider.Polly.csproj @@ -4,7 +4,7 @@ 2.0.0 2.0.0 true - Provides Ocelot extensions to use CacheManager.Net + Provides Ocelot extensions to use Polly.NET Ocelot.Provider.Polly 0.0.0-dev Ocelot.Provider.Polly @@ -26,9 +26,10 @@ True - + all + diff --git a/src/Ocelot.Provider.Polly/OcelotBuilderExtensions.cs b/src/Ocelot.Provider.Polly/OcelotBuilderExtensions.cs index 49210ba..ae7e881 100644 --- a/src/Ocelot.Provider.Polly/OcelotBuilderExtensions.cs +++ b/src/Ocelot.Provider.Polly/OcelotBuilderExtensions.cs @@ -1,11 +1,37 @@ namespace Ocelot.Provider.Polly { + using System; + using System.Collections.Generic; + using System.Net.Http; + using System.Threading.Tasks; + using Configuration; using DependencyInjection; + using Errors; + using global::Polly.CircuitBreaker; + using global::Polly.Timeout; + using Logging; + using Microsoft.Extensions.DependencyInjection; + using Requester; public static class OcelotBuilderExtensions { - public static IOcelotBuilder AddSomething(this IOcelotBuilder builder) + public static IOcelotBuilder AddPolly(this IOcelotBuilder builder) { + var errorMapping = new Dictionary> + { + {typeof(TaskCanceledException), e => new RequestTimedOutError(e)}, + {typeof(TimeoutRejectedException), e => new RequestTimedOutError(e)}, + {typeof(BrokenCircuitException), e => new RequestTimedOutError(e)} + }; + + builder.Services.AddSingleton(errorMapping); + + DelegatingHandler QosDelegatingHandlerDelegate(DownstreamReRoute reRoute, IOcelotLoggerFactory logger) + { + return new PollyCircuitBreakingDelegatingHandler(new PollyQoSProvider(reRoute, logger), logger); + } + + builder.Services.AddSingleton((QosDelegatingHandlerDelegate) QosDelegatingHandlerDelegate); return builder; } } diff --git a/src/Ocelot.Provider.Polly/PollyCircuitBreakingDelegatingHandler.cs b/src/Ocelot.Provider.Polly/PollyCircuitBreakingDelegatingHandler.cs new file mode 100644 index 0000000..4a17cd2 --- /dev/null +++ b/src/Ocelot.Provider.Polly/PollyCircuitBreakingDelegatingHandler.cs @@ -0,0 +1,43 @@ +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Ocelot.Logging; +using Polly; +using Polly.CircuitBreaker; + +namespace Ocelot.Provider.Polly +{ + public class PollyCircuitBreakingDelegatingHandler : DelegatingHandler + { + private readonly PollyQoSProvider _qoSProvider; + private readonly IOcelotLogger _logger; + + public PollyCircuitBreakingDelegatingHandler( + PollyQoSProvider qoSProvider, + IOcelotLoggerFactory loggerFactory) + { + _qoSProvider = qoSProvider; + _logger = loggerFactory.CreateLogger(); + } + + protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + try + { + return await Policy + .WrapAsync(_qoSProvider.CircuitBreaker.CircuitBreakerPolicy, _qoSProvider.CircuitBreaker.TimeoutPolicy) + .ExecuteAsync(() => base.SendAsync(request,cancellationToken)); + } + catch (BrokenCircuitException ex) + { + _logger.LogError($"Reached to allowed number of exceptions. Circuit is open",ex); + throw; + } + catch (HttpRequestException ex) + { + _logger.LogError($"Error in CircuitBreakingDelegatingHandler.SendAync", ex); + throw; + } + } + } +} diff --git a/src/Ocelot.Provider.Polly/PollyQoSProvider.cs b/src/Ocelot.Provider.Polly/PollyQoSProvider.cs new file mode 100644 index 0000000..a95eb36 --- /dev/null +++ b/src/Ocelot.Provider.Polly/PollyQoSProvider.cs @@ -0,0 +1,52 @@ +namespace Ocelot.Provider.Polly +{ + using System; + using System.Net.Http; + using global::Polly; + using global::Polly.CircuitBreaker; + using global::Polly.Timeout; + using Ocelot.Configuration; + using Ocelot.Logging; + + public class PollyQoSProvider + { + private readonly CircuitBreakerPolicy _circuitBreakerPolicy; + private readonly TimeoutPolicy _timeoutPolicy; + private readonly IOcelotLogger _logger; + + public PollyQoSProvider(DownstreamReRoute reRoute, IOcelotLoggerFactory loggerFactory) + { + _logger = loggerFactory.CreateLogger(); + + Enum.TryParse(reRoute.QosOptions.TimeoutStrategy, out TimeoutStrategy strategy); + + _timeoutPolicy = Policy.TimeoutAsync(TimeSpan.FromMilliseconds(reRoute.QosOptions.TimeoutValue), strategy); + + _circuitBreakerPolicy = Policy + .Handle() + .Or() + .Or() + .CircuitBreakerAsync( + exceptionsAllowedBeforeBreaking: reRoute.QosOptions.ExceptionsAllowedBeforeBreaking, + durationOfBreak: TimeSpan.FromMilliseconds(reRoute.QosOptions.DurationOfBreak), + onBreak: (ex, breakDelay) => + { + _logger.LogError( + ".Breaker logging: Breaking the circuit for " + breakDelay.TotalMilliseconds + "ms!", ex); + }, + onReset: () => + { + _logger.LogDebug(".Breaker logging: Call ok! Closed the circuit again."); + }, + onHalfOpen: () => + { + _logger.LogDebug(".Breaker logging: Half-open; next call is a trial."); + } + ); + + CircuitBreaker = new CircuitBreaker(_circuitBreakerPolicy, _timeoutPolicy); + } + + public CircuitBreaker CircuitBreaker { get; } + } +} diff --git a/src/Ocelot.Provider.Polly/RequestTimedOutError.cs b/src/Ocelot.Provider.Polly/RequestTimedOutError.cs new file mode 100644 index 0000000..4e910b1 --- /dev/null +++ b/src/Ocelot.Provider.Polly/RequestTimedOutError.cs @@ -0,0 +1,13 @@ +namespace Ocelot.Provider.Polly +{ + using System; + using Errors; + + public class RequestTimedOutError : Error + { + public RequestTimedOutError(Exception exception) + : base($"Timeout making http request, exception: {exception}", OcelotErrorCode.RequestTimedOutError) + { + } + } +} diff --git a/test/Ocelot.Provider.Polly.AcceptanceTests/Ocelot.Provider.Polly.AcceptanceTests.csproj b/test/Ocelot.Provider.Polly.AcceptanceTests/Ocelot.Provider.Polly.AcceptanceTests.csproj index 52acdc1..dfd4d60 100644 --- a/test/Ocelot.Provider.Polly.AcceptanceTests/Ocelot.Provider.Polly.AcceptanceTests.csproj +++ b/test/Ocelot.Provider.Polly.AcceptanceTests/Ocelot.Provider.Polly.AcceptanceTests.csproj @@ -33,7 +33,7 @@ - + all diff --git a/test/Ocelot.Provider.Polly.AcceptanceTests/QoSTests.cs b/test/Ocelot.Provider.Polly.AcceptanceTests/QoSTests.cs new file mode 100644 index 0000000..6425764 --- /dev/null +++ b/test/Ocelot.Provider.Polly.AcceptanceTests/QoSTests.cs @@ -0,0 +1,273 @@ +namespace Ocelot.Provider.Polly.AcceptanceTests +{ + using System; + using System.Collections.Generic; + using System.Net; + using System.Threading; + using System.Threading.Tasks; + using Configuration.File; + using Microsoft.AspNetCore.Http; + using TestStack.BDDfy; + using Xunit; + + public class QoSTests : IDisposable + { + private readonly Steps _steps; + private int _requestCount; + private readonly ServiceHandler _serviceHandler; + + public QoSTests() + { + _serviceHandler = new ServiceHandler(); + _steps = new Steps(); + } + + [Fact] + public void should_not_timeout() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51569, + } + }, + DownstreamScheme = "http", + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Post" }, + QoSOptions = new FileQoSOptions + { + TimeoutValue = 1000, + } + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51569", 200, string.Empty, 10)) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .And(x => _steps.GivenThePostHasContent("postContent")) + .When(x => _steps.WhenIPostUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .BDDfy(); + } + + [Fact] + public void should_timeout() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51579, + } + }, + DownstreamScheme = "http", + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Post" }, + QoSOptions = new FileQoSOptions + { + TimeoutValue = 10, + ExceptionsAllowedBeforeBreaking = 10 + } + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51579", 201, string.Empty, 1000)) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .And(x => _steps.GivenThePostHasContent("postContent")) + .When(x => _steps.WhenIPostUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.ServiceUnavailable)) + .BDDfy(); + } + + [Fact] + public void should_open_circuit_breaker_then_close() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51892, + } + }, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + QoSOptions = new FileQoSOptions + { + ExceptionsAllowedBeforeBreaking = 1, + TimeoutValue = 500, + DurationOfBreak = 1000 + }, + } + } + }; + + this.Given(x => x.GivenThereIsAPossiblyBrokenServiceRunningOn("http://localhost:51892", "Hello from Laura")) + .Given(x => _steps.GivenThereIsAConfiguration(configuration)) + .Given(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .Given(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Given(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.ServiceUnavailable)) + .Given(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Given(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.ServiceUnavailable)) + .Given(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Given(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.ServiceUnavailable)) + .Given(x => x.GivenIWaitMilliseconds(3000)) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .BDDfy(); + } + + [Fact] + public void open_circuit_should_not_effect_different_reRoute() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51872, + } + }, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + QoSOptions = new FileQoSOptions + { + ExceptionsAllowedBeforeBreaking = 1, + TimeoutValue = 500, + DurationOfBreak = 1000 + } + }, + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51880, + } + }, + UpstreamPathTemplate = "/working", + UpstreamHttpMethod = new List { "Get" }, + } + } + }; + + this.Given(x => x.GivenThereIsAPossiblyBrokenServiceRunningOn("http://localhost:51872", "Hello from Laura")) + .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51880/", 200, "Hello from Tom", 0)) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .And(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .And(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .And(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .And(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.ServiceUnavailable)) + .And(x => _steps.WhenIGetUrlOnTheApiGateway("/working")) + .And(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Tom")) + .And(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .And(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.ServiceUnavailable)) + .And(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .And(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.ServiceUnavailable)) + .And(x => x.GivenIWaitMilliseconds(3000)) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .BDDfy(); + } + + private void GivenIWaitMilliseconds(int ms) + { + Thread.Sleep(ms); + } + + private void GivenThereIsAPossiblyBrokenServiceRunningOn(string url, string responseBody) + { + _serviceHandler.GivenThereIsAServiceRunningOn(url, async context => + { + //circuit starts closed + if (_requestCount == 0) + { + _requestCount++; + context.Response.StatusCode = 200; + await context.Response.WriteAsync(responseBody); + return; + } + + //request one times out and polly throws exception, circuit opens + if (_requestCount == 1) + { + _requestCount++; + await Task.Delay(1000); + context.Response.StatusCode = 200; + return; + } + + //after break closes we return 200 OK + if (_requestCount == 2) + { + context.Response.StatusCode = 200; + await context.Response.WriteAsync(responseBody); + } + }); + } + + private void GivenThereIsAServiceRunningOn(string url, int statusCode, string responseBody, int timeout) + { + _serviceHandler.GivenThereIsAServiceRunningOn(url, async context => + { + Thread.Sleep(timeout); + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync(responseBody); + }); + } + + public void Dispose() + { + _serviceHandler?.Dispose(); + _steps.Dispose(); + } + } +} diff --git a/test/Ocelot.Provider.Polly.AcceptanceTests/ServiceHandler.cs b/test/Ocelot.Provider.Polly.AcceptanceTests/ServiceHandler.cs new file mode 100644 index 0000000..340b3f6 --- /dev/null +++ b/test/Ocelot.Provider.Polly.AcceptanceTests/ServiceHandler.cs @@ -0,0 +1,38 @@ +namespace Ocelot.Provider.Polly.AcceptanceTests +{ + using System; + using System.IO; + using System.Net; + using System.Threading.Tasks; + using Microsoft.AspNetCore.Builder; + using Microsoft.AspNetCore.Hosting; + using Microsoft.AspNetCore.Http; + using Microsoft.Extensions.Configuration; + using Microsoft.Extensions.Logging; + + public class ServiceHandler : IDisposable + { + private IWebHost _builder; + + public void GivenThereIsAServiceRunningOn(string baseUrl, RequestDelegate del) + { + _builder = new WebHostBuilder() + .UseUrls(baseUrl) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .Configure(app => + { + app.Run(del); + }) + .Build(); + + _builder.Start(); + } + + public void Dispose() + { + _builder?.Dispose(); + } + } +} diff --git a/test/Ocelot.Provider.Polly.AcceptanceTests/Steps.cs b/test/Ocelot.Provider.Polly.AcceptanceTests/Steps.cs new file mode 100644 index 0000000..e47e196 --- /dev/null +++ b/test/Ocelot.Provider.Polly.AcceptanceTests/Steps.cs @@ -0,0 +1,128 @@ +namespace Ocelot.Provider.Polly.AcceptanceTests +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.IO.Compression; + using System.Linq; + using System.Net; + using System.Net.Http; + using System.Net.Http.Headers; + using System.Text; + using System.Threading; + using System.Threading.Tasks; + using Configuration.Creator; + using Configuration.File; + using Configuration.Repository; + using DependencyInjection; + using Microsoft.AspNetCore.Builder; + using Microsoft.AspNetCore.Hosting; + using Microsoft.AspNetCore.TestHost; + using Microsoft.Extensions.Configuration; + using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.Logging; + using Middleware; + using Middleware.Multiplexer; + using Newtonsoft.Json; + using Shouldly; + using ConfigurationBuilder = Microsoft.Extensions.Configuration.ConfigurationBuilder; + using CookieHeaderValue = System.Net.Http.Headers.CookieHeaderValue; + using MediaTypeHeaderValue = System.Net.Http.Headers.MediaTypeHeaderValue; + + public class Steps : IDisposable + { + private TestServer _ocelotServer; + private HttpClient _ocelotClient; + private HttpResponseMessage _response; + private HttpContent _postContent; + public string RequestIdKey = "OcRequestId"; + private readonly Random _random; + private IWebHostBuilder _webHostBuilder; + private WebHostBuilder _ocelotBuilder; + private IWebHost _ocelotHost; + + public Steps() + { + _random = new Random(); + } + + public void GivenThereIsAConfiguration(FileConfiguration fileConfiguration) + { + var configurationPath = TestConfiguration.ConfigurationPath; + + var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration, Formatting.Indented); + + if (File.Exists(configurationPath)) + { + File.Delete(configurationPath); + } + + File.WriteAllText(configurationPath, jsonConfiguration); + } + + /// + /// 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. + /// + public void GivenOcelotIsRunning() + { + _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", false, false); + config.AddEnvironmentVariables(); + }) + .ConfigureServices(s => + { + s.AddOcelot() + .AddPolly(); + }) + .Configure(app => + { + app.UseOcelot() + .Wait(); + }); + + _ocelotServer = new TestServer(_webHostBuilder); + + _ocelotClient = _ocelotServer.CreateClient(); + } + + public void WhenIGetUrlOnTheApiGateway(string url) + { + _response = _ocelotClient.GetAsync(url).Result; + } + + public void WhenIPostUrlOnTheApiGateway(string url) + { + _response = _ocelotClient.PostAsync(url, _postContent).Result; + } + + public void GivenThePostHasContent(string postcontent) + { + _postContent = new StringContent(postcontent); + } + + public void ThenTheResponseBodyShouldBe(string expectedBody) + { + _response.Content.ReadAsStringAsync().Result.ShouldBe(expectedBody); + } + + public void ThenTheStatusCodeShouldBe(HttpStatusCode expectedHttpStatusCode) + { + _response.StatusCode.ShouldBe(expectedHttpStatusCode); + } + + public void Dispose() + { + _ocelotClient?.Dispose(); + _ocelotServer?.Dispose(); + _ocelotHost?.Dispose(); + } + } +} diff --git a/test/Ocelot.Provider.Polly.AcceptanceTests/TestConfiguration.cs b/test/Ocelot.Provider.Polly.AcceptanceTests/TestConfiguration.cs new file mode 100644 index 0000000..f74938e --- /dev/null +++ b/test/Ocelot.Provider.Polly.AcceptanceTests/TestConfiguration.cs @@ -0,0 +1,10 @@ +namespace Ocelot.Provider.Polly.AcceptanceTests +{ + using System; + using System.IO; + + public static class TestConfiguration + { + public static string ConfigurationPath => Path.Combine(AppContext.BaseDirectory, "ocelot.json"); + } +} diff --git a/test/Ocelot.Provider.Polly.Benchmarks/Ocelot.Provider.Polly.Benchmarks.csproj b/test/Ocelot.Provider.Polly.Benchmarks/Ocelot.Provider.Polly.Benchmarks.csproj index 5bb52fd..a6b0507 100644 --- a/test/Ocelot.Provider.Polly.Benchmarks/Ocelot.Provider.Polly.Benchmarks.csproj +++ b/test/Ocelot.Provider.Polly.Benchmarks/Ocelot.Provider.Polly.Benchmarks.csproj @@ -15,7 +15,7 @@ - + all diff --git a/test/Ocelot.Provider.Polly.IntegrationTests/Ocelot.Provider.Polly.IntegrationTests.csproj b/test/Ocelot.Provider.Polly.IntegrationTests/Ocelot.Provider.Polly.IntegrationTests.csproj index 02cb4c7..051e1a1 100644 --- a/test/Ocelot.Provider.Polly.IntegrationTests/Ocelot.Provider.Polly.IntegrationTests.csproj +++ b/test/Ocelot.Provider.Polly.IntegrationTests/Ocelot.Provider.Polly.IntegrationTests.csproj @@ -21,7 +21,7 @@ - + all @@ -45,9 +45,4 @@ - - - ..\..\..\..\.nuget\packages\ocelot\8.0.7\lib\netstandard2.0\Ocelot.dll - - diff --git a/test/Ocelot.Provider.Polly.ManualTest/Ocelot.Provider.Polly.ManualTest.csproj b/test/Ocelot.Provider.Polly.ManualTest/Ocelot.Provider.Polly.ManualTest.csproj index 0c86a23..60d9d6a 100644 --- a/test/Ocelot.Provider.Polly.ManualTest/Ocelot.Provider.Polly.ManualTest.csproj +++ b/test/Ocelot.Provider.Polly.ManualTest/Ocelot.Provider.Polly.ManualTest.csproj @@ -34,7 +34,7 @@ - + all diff --git a/test/Ocelot.Provider.Polly.ManualTest/Program.cs b/test/Ocelot.Provider.Polly.ManualTest/Program.cs index 5c6e516..c0b1278 100644 --- a/test/Ocelot.Provider.Polly.ManualTest/Program.cs +++ b/test/Ocelot.Provider.Polly.ManualTest/Program.cs @@ -25,7 +25,7 @@ public static void Main(string[] args) }) .ConfigureServices(s => { s.AddOcelot() - .AddSomething(); + .AddPolly(); }) .ConfigureLogging((hostingContext, logging) => { diff --git a/test/Ocelot.Provider.Polly.UnitTests/Ocelot.Provider.Polly.UnitTests.csproj b/test/Ocelot.Provider.Polly.UnitTests/Ocelot.Provider.Polly.UnitTests.csproj index 5fa2971..cc20c87 100644 --- a/test/Ocelot.Provider.Polly.UnitTests/Ocelot.Provider.Polly.UnitTests.csproj +++ b/test/Ocelot.Provider.Polly.UnitTests/Ocelot.Provider.Polly.UnitTests.csproj @@ -41,7 +41,7 @@ - + all diff --git a/test/Ocelot.Provider.Polly.UnitTests/OcelotBuilderExtensionsTests.cs b/test/Ocelot.Provider.Polly.UnitTests/OcelotBuilderExtensionsTests.cs new file mode 100644 index 0000000..a679734 --- /dev/null +++ b/test/Ocelot.Provider.Polly.UnitTests/OcelotBuilderExtensionsTests.cs @@ -0,0 +1,45 @@ +namespace Ocelot.Provider.Polly.UnitTests +{ + using System.IO; + using Configuration; + using Configuration.Builder; + using DependencyInjection; + using Logging; + using Microsoft.Extensions.Configuration; + using Microsoft.Extensions.DependencyInjection; + using Moq; + using Requester; + using Shouldly; + using Xunit; + + public class OcelotBuilderExtensionsTests + { + [Fact] + public void should_build() + { + var loggerFactory = new Mock(); + var services = new ServiceCollection(); + var options = new QoSOptionsBuilder() + .WithTimeoutValue(100) + .WithExceptionsAllowedBeforeBreaking(1) + .WithDurationOfBreak(200) + .Build(); + var reRoute = new DownstreamReRouteBuilder().WithQosOptions(options) + .Build(); + + var configuration = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .Build(); + services + .AddOcelot(configuration) + .AddPolly(); + var provider = services.BuildServiceProvider(); + + var handler = provider.GetService(); + handler.ShouldNotBeNull(); + + var delgatingHandler = handler(reRoute, loggerFactory.Object); + delgatingHandler.ShouldNotBeNull(); + } + } +} diff --git a/test/Ocelot.Provider.Polly.UnitTests/PollyQoSProviderTests.cs b/test/Ocelot.Provider.Polly.UnitTests/PollyQoSProviderTests.cs new file mode 100644 index 0000000..a0d791e --- /dev/null +++ b/test/Ocelot.Provider.Polly.UnitTests/PollyQoSProviderTests.cs @@ -0,0 +1,26 @@ +namespace Ocelot.Provider.Polly.UnitTests +{ + using Configuration.Builder; + using Logging; + using Moq; + using Shouldly; + using Xunit; + + public class PollyQoSProviderTests + { + [Fact] + public void should_build() + { + var options = new QoSOptionsBuilder() + .WithTimeoutValue(100) + .WithExceptionsAllowedBeforeBreaking(1) + .WithDurationOfBreak(200) + .Build(); + var reRoute = new DownstreamReRouteBuilder().WithQosOptions(options) + .Build(); + var factory = new Mock(); + var pollyQoSProvider = new PollyQoSProvider(reRoute, factory.Object); + pollyQoSProvider.CircuitBreaker.ShouldNotBeNull(); + } + } +} diff --git a/tools/packages.config b/tools/packages.config new file mode 100644 index 0000000..e52a2c7 --- /dev/null +++ b/tools/packages.config @@ -0,0 +1,4 @@ + + + +