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 @@
+
+
+
+