From 239dcfb6bd1734c1a18f6e26650b10109f9fc757 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Mon, 26 Jun 2017 19:10:20 +0100 Subject: [PATCH 1/6] working on region clearing cache, if using cachemanager back plance this would clear all servers in cluster --- src/Ocelot/Cache/IOcelotCache.cs | 6 +- .../Cache/Middleware/OutputCacheMiddleware.cs | 4 +- src/Ocelot/Cache/OcelotCacheManagerCache.cs | 18 +++-- .../ConsulOcelotConfigurationRepository.cs | 2 +- .../Controllers/OutputCacheController.cs | 33 +++++++++ .../Cache/CacheManagerCacheTests.cs | 32 ++++++++- .../Cache/OutputCacheMiddlewareTests.cs | 2 +- .../Controllers/OutputCacheControllerTests.cs | 70 +++++++++++++++++++ 8 files changed, 155 insertions(+), 12 deletions(-) create mode 100644 src/Ocelot/Controllers/OutputCacheController.cs create mode 100644 test/Ocelot.UnitTests/Controllers/OutputCacheControllerTests.cs diff --git a/src/Ocelot/Cache/IOcelotCache.cs b/src/Ocelot/Cache/IOcelotCache.cs index 9ac26d8f..9d7ead9b 100644 --- a/src/Ocelot/Cache/IOcelotCache.cs +++ b/src/Ocelot/Cache/IOcelotCache.cs @@ -1,11 +1,13 @@ using System; +using System.Collections.Generic; namespace Ocelot.Cache { public interface IOcelotCache { - void Add(string key, T value, TimeSpan ttl); - void AddAndDelete(string key, T value, TimeSpan ttl); + void Add(string key, T value, TimeSpan ttl, string region); + void AddAndDelete(string key, T value, TimeSpan ttl, string region); T Get(string key); + void ClearRegion(string region); } } diff --git a/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs b/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs index c2cb019e..fb1ebce5 100644 --- a/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs +++ b/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs @@ -63,7 +63,9 @@ namespace Ocelot.Cache.Middleware var response = HttpResponseMessage; - _outputCache.Add(downstreamUrlKey, response, TimeSpan.FromSeconds(DownstreamRoute.ReRoute.FileCacheOptions.TtlSeconds)); + var region = $"{DownstreamRoute.ReRoute.UpstreamHttpMethod}-{DownstreamRoute.ReRoute.UpstreamPathTemplate.Value}"; + + _outputCache.Add(downstreamUrlKey, response, TimeSpan.FromSeconds(DownstreamRoute.ReRoute.FileCacheOptions.TtlSeconds), region); _logger.LogDebug("finished response added to cache for {downstreamUrlKey}", downstreamUrlKey); } diff --git a/src/Ocelot/Cache/OcelotCacheManagerCache.cs b/src/Ocelot/Cache/OcelotCacheManagerCache.cs index e92d678d..4edfdaad 100644 --- a/src/Ocelot/Cache/OcelotCacheManagerCache.cs +++ b/src/Ocelot/Cache/OcelotCacheManagerCache.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Linq; using CacheManager.Core; namespace Ocelot.Cache @@ -6,18 +8,21 @@ namespace Ocelot.Cache public class OcelotCacheManagerCache : IOcelotCache { private readonly ICacheManager _cacheManager; + private HashSet _keys; public OcelotCacheManagerCache(ICacheManager cacheManager) { _cacheManager = cacheManager; + _keys = new HashSet(); } - public void Add(string key, T value, TimeSpan ttl) + public void Add(string key, T value, TimeSpan ttl, string region) { - _cacheManager.Add(new CacheItem(key, value, ExpirationMode.Absolute, ttl)); + _cacheManager.Add(new CacheItem(key, region, value, ExpirationMode.Absolute, ttl)); + _keys.Add(key); } - public void AddAndDelete(string key, T value, TimeSpan ttl) + public void AddAndDelete(string key, T value, TimeSpan ttl, string region) { var exists = _cacheManager.Get(key); @@ -26,12 +31,17 @@ namespace Ocelot.Cache _cacheManager.Remove(key); } - _cacheManager.Add(new CacheItem(key, value, ExpirationMode.Absolute, ttl)); + Add(key, value, ttl, region); } public T Get(string key) { return _cacheManager.Get(key); } + + public void ClearRegion(string region) + { + _cacheManager.ClearRegion(region); + } } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/Repository/ConsulOcelotConfigurationRepository.cs b/src/Ocelot/Configuration/Repository/ConsulOcelotConfigurationRepository.cs index 703d039b..d7929aaa 100644 --- a/src/Ocelot/Configuration/Repository/ConsulOcelotConfigurationRepository.cs +++ b/src/Ocelot/Configuration/Repository/ConsulOcelotConfigurationRepository.cs @@ -68,7 +68,7 @@ namespace Ocelot.Configuration.Repository if (result.Response) { - _cache.AddAndDelete(_ocelotConfiguration, ocelotConfiguration, TimeSpan.FromSeconds(3)); + _cache.AddAndDelete(_ocelotConfiguration, ocelotConfiguration, TimeSpan.FromSeconds(3), "OcelotConfiguration"); return new OkResponse(); } diff --git a/src/Ocelot/Controllers/OutputCacheController.cs b/src/Ocelot/Controllers/OutputCacheController.cs new file mode 100644 index 00000000..2f56bf8d --- /dev/null +++ b/src/Ocelot/Controllers/OutputCacheController.cs @@ -0,0 +1,33 @@ +using System.Net.Http; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Ocelot.Cache; + +namespace Ocelot.Controllers +{ + [Authorize] + [Route("cache")] + public class OutputCacheController : Controller + { + private IOcelotCache _cache; + + public OutputCacheController(IOcelotCache cache) + { + _cache = cache; + } + + [HttpGet] + public IActionResult Get() + { + return new NotFoundResult(); + } + + [HttpDelete] + [Route("{region}")] + public IActionResult Delete(string region) + { + _cache.ClearRegion(region); + return new NoContentResult(); + } + } +} \ No newline at end of file diff --git a/test/Ocelot.UnitTests/Cache/CacheManagerCacheTests.cs b/test/Ocelot.UnitTests/Cache/CacheManagerCacheTests.cs index 87752a52..5780cff2 100644 --- a/test/Ocelot.UnitTests/Cache/CacheManagerCacheTests.cs +++ b/test/Ocelot.UnitTests/Cache/CacheManagerCacheTests.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using CacheManager.Core; using Moq; using Ocelot.Cache; @@ -16,12 +17,14 @@ namespace Ocelot.UnitTests.Cache private string _value; private string _resultGet; private TimeSpan _ttlSeconds; + private List _resultKeys; public CacheManagerCacheTests() { _mockCacheManager = new Mock>(); _ocelotOcelotCacheManager = new OcelotCacheManagerCache(_mockCacheManager.Object); } + [Fact] public void should_get_from_cache() { @@ -29,7 +32,6 @@ namespace Ocelot.UnitTests.Cache .When(x => x.WhenIGetFromTheCache()) .Then(x => x.ThenTheResultIs("someValue")) .BDDfy(); - } [Fact] @@ -40,13 +42,37 @@ namespace Ocelot.UnitTests.Cache .BDDfy(); } + [Fact] + public void should_delete_key_from_cache() + { + this.Given(_ => GivenTheFollowingRegion("fookey")) + .When(_ => WhenIDeleteTheRegion("fookey")) + .Then(_ => ThenTheRegionIsDeleted("fookey")) + .BDDfy(); + } + + private void WhenIDeleteTheRegion(string region) + { + _ocelotOcelotCacheManager.ClearRegion(region); + } + + private void ThenTheRegionIsDeleted(string region) + { + _mockCacheManager + .Verify(x => x.ClearRegion(region), Times.Once); + } + + private void GivenTheFollowingRegion(string key) + { + _ocelotOcelotCacheManager.Add(key, "doesnt matter", TimeSpan.FromSeconds(10), "region"); + } + private void WhenIAddToTheCache(string key, string value, TimeSpan ttlSeconds) { _key = key; _value = value; _ttlSeconds = ttlSeconds; - - _ocelotOcelotCacheManager.Add(_key, _value, _ttlSeconds); + _ocelotOcelotCacheManager.Add(_key, _value, _ttlSeconds, "region"); } private void ThenTheCacheIsCalledCorrectly() diff --git a/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs b/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs index dfc0220f..ab063b61 100644 --- a/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs @@ -124,7 +124,7 @@ namespace Ocelot.UnitTests.Cache private void ThenTheCacheAddIsCalledCorrectly() { _cacheManager - .Verify(x => x.Add(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + .Verify(x => x.Add(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); } private void GivenResponseIsNotCached() diff --git a/test/Ocelot.UnitTests/Controllers/OutputCacheControllerTests.cs b/test/Ocelot.UnitTests/Controllers/OutputCacheControllerTests.cs new file mode 100644 index 00000000..5e2268af --- /dev/null +++ b/test/Ocelot.UnitTests/Controllers/OutputCacheControllerTests.cs @@ -0,0 +1,70 @@ +using Xunit; +using Shouldly; +using TestStack.BDDfy; +using Ocelot.Controllers; +using System; +using Moq; +using Ocelot.Cache; +using System.Net.Http; +using System.Collections.Generic; +using Microsoft.AspNetCore.Mvc; + +namespace Ocelot.UnitTests.Controllers +{ + public class OutputCacheControllerTests + { + private OutputCacheController _controller; + private Mock> _cache; + private IActionResult _result; + + public OutputCacheControllerTests() + { + _cache = new Mock>(); + _controller = new OutputCacheController(_cache.Object); + } + + [Fact] + public void should_get_all_keys_from_server() + { + this.Given(_ => GivenTheFollowingKeys(new List{"b", "a"})) + .When(_ => WhenIGetTheKeys()) + .Then(_ => ThenTheKeysAreReturned()) + .BDDfy(); + } + + [Fact] + public void should_delete_key() + { + this.When(_ => WhenIDeleteTheKey("a")) + .Then(_ => ThenTheKeyIsDeleted("a")) + .BDDfy(); + } + + private void ThenTheKeyIsDeleted(string key) + { + _result.ShouldBeOfType(); + _cache + .Verify(x => x.ClearRegion(key), Times.Once); + } + + private void WhenIDeleteTheKey(string key) + { + _result = _controller.Delete(key); + } + + private void GivenTheFollowingKeys(List keys) + { + + } + + private void WhenIGetTheKeys() + { + _result = _controller.Get(); + } + + private void ThenTheKeysAreReturned() + { + _result.ShouldBeOfType(); + } + } +} \ No newline at end of file From e4e7fcc943662069b403c1fafeb53d9e11518151 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Tue, 27 Jun 2017 18:54:15 +0100 Subject: [PATCH 2/6] unit tests for cache clearing passing --- .../Cache/Middleware/OutputCacheMiddleware.cs | 8 +- src/Ocelot/Cache/RegionBuilder.cs | 22 ++++ src/Ocelot/Cache/RegionsGetter.cs | 47 ++++++++ .../Controllers/OutputCacheController.cs | 8 +- .../ServiceCollectionExtensions.cs | 2 + .../Ocelot.AcceptanceTests.csproj | 1 + .../Cache/OutputCacheMiddlewareTests.cs | 2 + .../Cache/RegionCreatorTests.cs | 46 ++++++++ .../Cache/RegionsGetterTests.cs | 109 ++++++++++++++++++ .../Controllers/OutputCacheControllerTests.cs | 4 +- 10 files changed, 244 insertions(+), 5 deletions(-) create mode 100644 src/Ocelot/Cache/RegionBuilder.cs create mode 100644 src/Ocelot/Cache/RegionsGetter.cs create mode 100644 test/Ocelot.UnitTests/Cache/RegionCreatorTests.cs create mode 100644 test/Ocelot.UnitTests/Cache/RegionsGetterTests.cs diff --git a/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs b/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs index fb1ebce5..8b8b565d 100644 --- a/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs +++ b/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Net.Http; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; @@ -13,16 +14,19 @@ namespace Ocelot.Cache.Middleware private readonly RequestDelegate _next; private readonly IOcelotLogger _logger; private readonly IOcelotCache _outputCache; + private readonly IRegionCreator _regionCreator; public OutputCacheMiddleware(RequestDelegate next, IOcelotLoggerFactory loggerFactory, IRequestScopedDataRepository scopedDataRepository, - IOcelotCache outputCache) + IOcelotCache outputCache, + IRegionCreator regionCreator) :base(scopedDataRepository) { _next = next; _outputCache = outputCache; _logger = loggerFactory.CreateLogger(); + _regionCreator = regionCreator; } public async Task Invoke(HttpContext context) @@ -63,7 +67,7 @@ namespace Ocelot.Cache.Middleware var response = HttpResponseMessage; - var region = $"{DownstreamRoute.ReRoute.UpstreamHttpMethod}-{DownstreamRoute.ReRoute.UpstreamPathTemplate.Value}"; + var region = _regionCreator.Region(DownstreamRoute.ReRoute); _outputCache.Add(downstreamUrlKey, response, TimeSpan.FromSeconds(DownstreamRoute.ReRoute.FileCacheOptions.TtlSeconds), region); diff --git a/src/Ocelot/Cache/RegionBuilder.cs b/src/Ocelot/Cache/RegionBuilder.cs new file mode 100644 index 00000000..eb9972bc --- /dev/null +++ b/src/Ocelot/Cache/RegionBuilder.cs @@ -0,0 +1,22 @@ +using System.Linq; +using Ocelot.Configuration; + +namespace Ocelot.Cache +{ + public interface IRegionCreator + { + string Region(ReRoute reRoute); + } + + public class RegionCreator : IRegionCreator + { + public string Region(ReRoute reRoute) + { + var methods = string.Join(",", reRoute.UpstreamHttpMethod.Select(m => m.Method)); + + var region = $"{methods} {reRoute.UpstreamPathTemplate.Value}"; + + return region; + } + } +} \ No newline at end of file diff --git a/src/Ocelot/Cache/RegionsGetter.cs b/src/Ocelot/Cache/RegionsGetter.cs new file mode 100644 index 00000000..1c331c2a --- /dev/null +++ b/src/Ocelot/Cache/RegionsGetter.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Ocelot.Configuration.Provider; +using Ocelot.Logging; + +namespace Ocelot.Cache +{ + public interface IRegionsGetter + { + Task> Regions(); + } + public class RegionsGetter : IRegionsGetter + { + private readonly IOcelotConfigurationProvider _provider; + private readonly IRegionCreator _creator; + private readonly IOcelotLogger _logger; + + public RegionsGetter(IOcelotConfigurationProvider provider, IRegionCreator creator, IOcelotLoggerFactory loggerFactory) + { + _logger = loggerFactory.CreateLogger(); + _provider = provider; + _creator = creator; + } + + public async Task> Regions() + { + var config = await _provider.Get(); + + if(config.IsError) + { + _logger.LogError("unable to find regions", new Exception(string.Join(",", config.Errors))); + return new List(); + } + + var regions = new List(); + + foreach(var reRoute in config.Data.ReRoutes) + { + var region = _creator.Region(reRoute); + regions.Add(region); + } + + return regions; + } + } +} \ No newline at end of file diff --git a/src/Ocelot/Controllers/OutputCacheController.cs b/src/Ocelot/Controllers/OutputCacheController.cs index 2f56bf8d..04150a5f 100644 --- a/src/Ocelot/Controllers/OutputCacheController.cs +++ b/src/Ocelot/Controllers/OutputCacheController.cs @@ -2,6 +2,7 @@ using System.Net.Http; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Ocelot.Cache; +using Ocelot.Configuration.Provider; namespace Ocelot.Controllers { @@ -10,16 +11,19 @@ namespace Ocelot.Controllers public class OutputCacheController : Controller { private IOcelotCache _cache; + private IRegionsGetter _regionsGetter; - public OutputCacheController(IOcelotCache cache) + public OutputCacheController(IOcelotCache cache, IRegionsGetter regionsGetter) { _cache = cache; + _regionsGetter = regionsGetter; } [HttpGet] public IActionResult Get() { - return new NotFoundResult(); + var regions = _regionsGetter.Regions(); + return new OkObjectResult(regions); } [HttpDelete] diff --git a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs index 14410379..d2ff4b74 100644 --- a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs @@ -145,6 +145,8 @@ namespace Ocelot.DependencyInjection .AddJsonFormatters(); services.AddLogging(); + services.TryAddSingleton(); + services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); diff --git a/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj b/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj index 08bbf527..eea05872 100644 --- a/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj +++ b/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj @@ -30,6 +30,7 @@ + diff --git a/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs b/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs index ab063b61..0039e2af 100644 --- a/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs @@ -5,6 +5,7 @@ using System.Net.Http; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; using Moq; using Ocelot.Cache; using Ocelot.Cache.Middleware; @@ -43,6 +44,7 @@ namespace Ocelot.UnitTests.Cache x.AddLogging(); x.AddSingleton(_cacheManager.Object); x.AddSingleton(_scopedRepo.Object); + x.AddSingleton(); }) .UseUrls(_url) .UseKestrel() diff --git a/test/Ocelot.UnitTests/Cache/RegionCreatorTests.cs b/test/Ocelot.UnitTests/Cache/RegionCreatorTests.cs new file mode 100644 index 00000000..9d2b5a65 --- /dev/null +++ b/test/Ocelot.UnitTests/Cache/RegionCreatorTests.cs @@ -0,0 +1,46 @@ +using System.Collections.Generic; +using Ocelot.Cache; +using Ocelot.Configuration; +using Ocelot.Configuration.Builder; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Cache +{ + public class RegionCreatorTests + { + private string _result; + private ReRoute _reRoute; + + [Fact] + public void should_create_region() + { + var reRoute = new ReRouteBuilder() + .WithUpstreamHttpMethod(new List{"Get"}) + .WithUpstreamPathTemplate("/test/dummy") + .Build(); + + this.Given(_ => GivenTheReRoute(reRoute)) + .When(_ => WhenICreateTheRegion()) + .Then(_ => ThenTheRegionIs("Get /test/dummy")) + .BDDfy(); + } + + private void GivenTheReRoute(ReRoute reRoute) + { + _reRoute = reRoute; + } + + private void WhenICreateTheRegion() + { + RegionCreator regionCreator = new RegionCreator(); + _result = regionCreator.Region(_reRoute); + } + + private void ThenTheRegionIs(string expected) + { + _result.ShouldBe(expected); + } + } +} \ No newline at end of file diff --git a/test/Ocelot.UnitTests/Cache/RegionsGetterTests.cs b/test/Ocelot.UnitTests/Cache/RegionsGetterTests.cs new file mode 100644 index 00000000..1f235daf --- /dev/null +++ b/test/Ocelot.UnitTests/Cache/RegionsGetterTests.cs @@ -0,0 +1,109 @@ +using Xunit; +using TestStack.BDDfy; +using Shouldly; +using Ocelot.Cache; +using Moq; +using Ocelot.Configuration.Provider; +using System.Collections.Generic; +using Ocelot.Responses; +using Ocelot.Configuration; +using System.Threading.Tasks; +using Ocelot.Configuration.Builder; +using System; +using Ocelot.Errors; +using Ocelot.Logging; + +namespace Ocelot.UnitTests.Cache +{ + public class RegionsGetterTests + { + private RegionsGetter _regionsGetter; + private readonly Mock _provider; + private readonly Mock _creator; + private readonly Mock _factory; + private List _result; + + public RegionsGetterTests() + { + _provider = new Mock(); + _creator = new Mock(); + _factory = new Mock(); + var logger = new Mock(); + _factory + .Setup(x => x.CreateLogger()) + .Returns(logger.Object); + _regionsGetter = new RegionsGetter(_provider.Object, _creator.Object, _factory.Object); + } + + [Fact] + public void should_get_regions() + { + var reRoute = new ReRouteBuilder() + .WithUpstreamHttpMethod(new List{"Get"}) + .WithUpstreamPathTemplate("/") + .Build(); + + var reRoutes = new List + { + reRoute + }; + + var config = new OcelotConfiguration(reRoutes, "whocares!"); + + var expected = new List + { + "balls" + }; + + this.Given(_ => GivenTheFollowingConfig(config)) + .And(_ => GivenTheProviderReturns("balls")) + .When(_ => WhenIGetTheRegions()) + .Then(_ => ThenTheFollowingIsReturned(expected)) + .BDDfy(); + } + + [Fact] + public void should_return_empty_regions() + { + var expected = new List(); + + this.Given(_ => GivenAnErrorGettingTheConfig()) + .When(_ => WhenIGetTheRegions()) + .Then(_ => ThenTheFollowingIsReturned(expected)) + .BDDfy(); + } + + private void GivenAnErrorGettingTheConfig() + { + var config = new OcelotConfiguration(new List(), "whocares!"); + + _provider + .Setup(x => x.Get()) + .ReturnsAsync(new ErrorResponse(It.IsAny())); + } + + private void GivenTheProviderReturns(string expected) + { + _creator + .Setup(x => x.Region(It.IsAny())) + .Returns(expected); + } + + private void GivenTheFollowingConfig(IOcelotConfiguration config) + { + _provider + .Setup(x => x.Get()) + .ReturnsAsync(new OkResponse(config)); + } + + private void WhenIGetTheRegions() + { + _result = _regionsGetter.Regions().Result; + } + + private void ThenTheFollowingIsReturned(List expected) + { + _result.ShouldBe(expected); + } + } +} \ No newline at end of file diff --git a/test/Ocelot.UnitTests/Controllers/OutputCacheControllerTests.cs b/test/Ocelot.UnitTests/Controllers/OutputCacheControllerTests.cs index 5e2268af..fa43369e 100644 --- a/test/Ocelot.UnitTests/Controllers/OutputCacheControllerTests.cs +++ b/test/Ocelot.UnitTests/Controllers/OutputCacheControllerTests.cs @@ -15,12 +15,14 @@ namespace Ocelot.UnitTests.Controllers { private OutputCacheController _controller; private Mock> _cache; + private Mock _getter; private IActionResult _result; public OutputCacheControllerTests() { _cache = new Mock>(); - _controller = new OutputCacheController(_cache.Object); + _getter = new Mock(); + _controller = new OutputCacheController(_cache.Object, _getter.Object); } [Fact] From 0fa759c76c6b568449fb906cd0aaa86be54c9fc9 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Tue, 27 Jun 2017 19:07:55 +0100 Subject: [PATCH 3/6] first acceptance test failing.. --- src/Ocelot/Cache/RegionsGetter.cs | 5 +- .../Controllers/OutputCacheController.cs | 2 +- .../AdministrationTests.cs | 64 +++++++++++++++++++ .../Cache/RegionsGetterTests.cs | 6 +- 4 files changed, 74 insertions(+), 3 deletions(-) diff --git a/src/Ocelot/Cache/RegionsGetter.cs b/src/Ocelot/Cache/RegionsGetter.cs index 1c331c2a..ae87a49f 100644 --- a/src/Ocelot/Cache/RegionsGetter.cs +++ b/src/Ocelot/Cache/RegionsGetter.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Ocelot.Configuration.Provider; using Ocelot.Logging; @@ -33,9 +34,11 @@ namespace Ocelot.Cache return new List(); } + var cachedReRoutes = config.Data.ReRoutes.Where(x => x.IsCached); + var regions = new List(); - foreach(var reRoute in config.Data.ReRoutes) + foreach(var reRoute in cachedReRoutes) { var region = _creator.Region(reRoute); regions.Add(region); diff --git a/src/Ocelot/Controllers/OutputCacheController.cs b/src/Ocelot/Controllers/OutputCacheController.cs index 04150a5f..67a07d07 100644 --- a/src/Ocelot/Controllers/OutputCacheController.cs +++ b/src/Ocelot/Controllers/OutputCacheController.cs @@ -7,7 +7,7 @@ using Ocelot.Configuration.Provider; namespace Ocelot.Controllers { [Authorize] - [Route("cache")] + [Route("outputcache")] public class OutputCacheController : Controller { private IOcelotCache _cache; diff --git a/test/Ocelot.IntegrationTests/AdministrationTests.cs b/test/Ocelot.IntegrationTests/AdministrationTests.cs index b03d587e..e0826b68 100644 --- a/test/Ocelot.IntegrationTests/AdministrationTests.cs +++ b/test/Ocelot.IntegrationTests/AdministrationTests.cs @@ -218,6 +218,63 @@ namespace Ocelot.IntegrationTests .BDDfy(); } + [Fact] + public void should_return_regions() + { + + var initialConfiguration = new FileConfiguration + { + GlobalConfiguration = new FileGlobalConfiguration + { + AdministrationPath = "/administration" + }, + ReRoutes = new List() + { + new FileReRoute() + { + DownstreamHost = "localhost", + DownstreamPort = 80, + DownstreamScheme = "https", + DownstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "get" }, + UpstreamPathTemplate = "/", + FileCacheOptions = new FileCacheOptions + { + TtlSeconds = 10 + } + }, + new FileReRoute() + { + DownstreamHost = "localhost", + DownstreamPort = 80, + DownstreamScheme = "https", + DownstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "get" }, + UpstreamPathTemplate = "/test", + FileCacheOptions = new FileCacheOptions + { + TtlSeconds = 10 + } + } + } + }; + + var expected = new List + { + "get /", + "get /test" + }; + + this.Given(x => GivenThereIsAConfiguration(initialConfiguration)) + .And(x => GivenOcelotIsRunning()) + .And(x => GivenIHaveAnOcelotToken("/administration")) + .And(x => GivenIHaveAddedATokenToMyRequest()) + .When(x => WhenIGetUrlOnTheApiGateway("/administration/outputcache")) + .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => ThenTheResponseShouldBe(expected)) + .BDDfy(); + } + private void GivenAnotherOcelotIsRunning(string baseUrl) { _httpClientTwo.BaseAddress = new Uri(baseUrl); @@ -256,6 +313,13 @@ namespace Ocelot.IntegrationTests _response = _httpClient.PostAsync(url, content).Result; } + private void ThenTheResponseShouldBe(List expected) + { + var content = _response.Content.ReadAsStringAsync().Result; + var result = JsonConvert.DeserializeObject>(content); + result.ShouldBe(expected); + } + private void ThenTheResponseShouldBe(FileConfiguration expected) { var response = JsonConvert.DeserializeObject(_response.Content.ReadAsStringAsync().Result); diff --git a/test/Ocelot.UnitTests/Cache/RegionsGetterTests.cs b/test/Ocelot.UnitTests/Cache/RegionsGetterTests.cs index 1f235daf..a7d2b662 100644 --- a/test/Ocelot.UnitTests/Cache/RegionsGetterTests.cs +++ b/test/Ocelot.UnitTests/Cache/RegionsGetterTests.cs @@ -38,9 +38,13 @@ namespace Ocelot.UnitTests.Cache [Fact] public void should_get_regions() { + var cacheOptions = new CacheOptions(12); + var reRoute = new ReRouteBuilder() .WithUpstreamHttpMethod(new List{"Get"}) .WithUpstreamPathTemplate("/") + .WithCacheOptions(cacheOptions) + .WithIsCached(true) .Build(); var reRoutes = new List @@ -62,7 +66,7 @@ namespace Ocelot.UnitTests.Cache .BDDfy(); } - [Fact] + [Fact] public void should_return_empty_regions() { var expected = new List(); From ab953f28fdeb29f37e05758d3ee09891e893f1a8 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Wed, 28 Jun 2017 08:17:48 +0100 Subject: [PATCH 4/6] realised the user can just set the region..delete time --- src/Ocelot/Cache/RegionBuilder.cs | 4 +- src/Ocelot/Cache/Regions.cs | 13 ++++ src/Ocelot/Configuration/CacheOptions.cs | 1 + .../Configuration/File/FileCacheOptions.cs | 1 + .../Controllers/OutputCacheController.cs | 7 +- .../AdministrationTests.cs | 66 +++++++++++++++++-- .../Ocelot.IntegrationTests.csproj | 1 + .../Ocelot.ManualTest.csproj | 1 + .../Cache/RegionCreatorTests.cs | 2 +- .../Controllers/OutputCacheControllerTests.cs | 2 +- 10 files changed, 86 insertions(+), 12 deletions(-) create mode 100644 src/Ocelot/Cache/Regions.cs diff --git a/src/Ocelot/Cache/RegionBuilder.cs b/src/Ocelot/Cache/RegionBuilder.cs index eb9972bc..5ccf8973 100644 --- a/src/Ocelot/Cache/RegionBuilder.cs +++ b/src/Ocelot/Cache/RegionBuilder.cs @@ -12,9 +12,9 @@ namespace Ocelot.Cache { public string Region(ReRoute reRoute) { - var methods = string.Join(",", reRoute.UpstreamHttpMethod.Select(m => m.Method)); + var methods = string.Join("", reRoute.UpstreamHttpMethod.Select(m => m.Method)); - var region = $"{methods} {reRoute.UpstreamPathTemplate.Value}"; + var region = $"{methods}{reRoute.UpstreamPathTemplate.Value.Replace("/", "")}"; return region; } diff --git a/src/Ocelot/Cache/Regions.cs b/src/Ocelot/Cache/Regions.cs new file mode 100644 index 00000000..1dbefbd5 --- /dev/null +++ b/src/Ocelot/Cache/Regions.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; + +namespace Ocelot.Cache +{ + public class Regions + { + public Regions(List value) + { + Value = value; + } + public List Value {get;private set;} + } +} \ No newline at end of file diff --git a/src/Ocelot/Configuration/CacheOptions.cs b/src/Ocelot/Configuration/CacheOptions.cs index 2fdaf2bb..3de9e019 100644 --- a/src/Ocelot/Configuration/CacheOptions.cs +++ b/src/Ocelot/Configuration/CacheOptions.cs @@ -8,5 +8,6 @@ } public int TtlSeconds { get; private set; } + public string Region {get;private set;} } } diff --git a/src/Ocelot/Configuration/File/FileCacheOptions.cs b/src/Ocelot/Configuration/File/FileCacheOptions.cs index 3f86006b..46fc7b87 100644 --- a/src/Ocelot/Configuration/File/FileCacheOptions.cs +++ b/src/Ocelot/Configuration/File/FileCacheOptions.cs @@ -3,5 +3,6 @@ public class FileCacheOptions { public int TtlSeconds { get; set; } + public string Region {get;private set;} } } diff --git a/src/Ocelot/Controllers/OutputCacheController.cs b/src/Ocelot/Controllers/OutputCacheController.cs index 67a07d07..21dccb8d 100644 --- a/src/Ocelot/Controllers/OutputCacheController.cs +++ b/src/Ocelot/Controllers/OutputCacheController.cs @@ -1,4 +1,5 @@ using System.Net.Http; +using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Ocelot.Cache; @@ -20,10 +21,10 @@ namespace Ocelot.Controllers } [HttpGet] - public IActionResult Get() + public async Task Get() { - var regions = _regionsGetter.Regions(); - return new OkObjectResult(regions); + var regions = await _regionsGetter.Regions(); + return new OkObjectResult(new Regions(regions)); } [HttpDelete] diff --git a/test/Ocelot.IntegrationTests/AdministrationTests.cs b/test/Ocelot.IntegrationTests/AdministrationTests.cs index e0826b68..0a0f20e4 100644 --- a/test/Ocelot.IntegrationTests/AdministrationTests.cs +++ b/test/Ocelot.IntegrationTests/AdministrationTests.cs @@ -7,6 +7,7 @@ using System.Net.Http.Headers; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; using Newtonsoft.Json; +using Ocelot.Cache; using Ocelot.Configuration.File; using Ocelot.ManualTest; using Shouldly; @@ -221,7 +222,6 @@ namespace Ocelot.IntegrationTests [Fact] public void should_return_regions() { - var initialConfiguration = new FileConfiguration { GlobalConfiguration = new FileGlobalConfiguration @@ -261,8 +261,8 @@ namespace Ocelot.IntegrationTests var expected = new List { - "get /", - "get /test" + "get", + "gettest" }; this.Given(x => GivenThereIsAConfiguration(initialConfiguration)) @@ -275,6 +275,57 @@ namespace Ocelot.IntegrationTests .BDDfy(); } + [Fact] + public void should_clear_region() + { + var initialConfiguration = new FileConfiguration + { + GlobalConfiguration = new FileGlobalConfiguration + { + AdministrationPath = "/administration" + }, + ReRoutes = new List() + { + new FileReRoute() + { + DownstreamHost = "localhost", + DownstreamPort = 80, + DownstreamScheme = "https", + DownstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "get" }, + UpstreamPathTemplate = "/", + FileCacheOptions = new FileCacheOptions + { + TtlSeconds = 10 + } + }, + new FileReRoute() + { + DownstreamHost = "localhost", + DownstreamPort = 80, + DownstreamScheme = "https", + DownstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "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); @@ -316,8 +367,8 @@ namespace Ocelot.IntegrationTests private void ThenTheResponseShouldBe(List expected) { var content = _response.Content.ReadAsStringAsync().Result; - var result = JsonConvert.DeserializeObject>(content); - result.ShouldBe(expected); + var result = JsonConvert.DeserializeObject(content); + result.Value.ShouldBe(expected); } private void ThenTheResponseShouldBe(FileConfiguration expected) @@ -417,6 +468,11 @@ namespace Ocelot.IntegrationTests _response = _httpClient.GetAsync(url).Result; } + private void WhenIDeleteOnTheApiGateway(string url) + { + _response = _httpClient.DeleteAsync(url).Result; + } + private void ThenTheStatusCodeShouldBe(HttpStatusCode expectedHttpStatusCode) { _response.StatusCode.ShouldBe(expectedHttpStatusCode); diff --git a/test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.csproj b/test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.csproj index 59513b38..1556111a 100644 --- a/test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.csproj +++ b/test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.csproj @@ -30,6 +30,7 @@ + diff --git a/test/Ocelot.ManualTest/Ocelot.ManualTest.csproj b/test/Ocelot.ManualTest/Ocelot.ManualTest.csproj index f541111f..57f23ad0 100644 --- a/test/Ocelot.ManualTest/Ocelot.ManualTest.csproj +++ b/test/Ocelot.ManualTest/Ocelot.ManualTest.csproj @@ -22,6 +22,7 @@ + diff --git a/test/Ocelot.UnitTests/Cache/RegionCreatorTests.cs b/test/Ocelot.UnitTests/Cache/RegionCreatorTests.cs index 9d2b5a65..ed8491ec 100644 --- a/test/Ocelot.UnitTests/Cache/RegionCreatorTests.cs +++ b/test/Ocelot.UnitTests/Cache/RegionCreatorTests.cs @@ -23,7 +23,7 @@ namespace Ocelot.UnitTests.Cache this.Given(_ => GivenTheReRoute(reRoute)) .When(_ => WhenICreateTheRegion()) - .Then(_ => ThenTheRegionIs("Get /test/dummy")) + .Then(_ => ThenTheRegionIs("Gettestdummy")) .BDDfy(); } diff --git a/test/Ocelot.UnitTests/Controllers/OutputCacheControllerTests.cs b/test/Ocelot.UnitTests/Controllers/OutputCacheControllerTests.cs index fa43369e..7639ed78 100644 --- a/test/Ocelot.UnitTests/Controllers/OutputCacheControllerTests.cs +++ b/test/Ocelot.UnitTests/Controllers/OutputCacheControllerTests.cs @@ -61,7 +61,7 @@ namespace Ocelot.UnitTests.Controllers private void WhenIGetTheKeys() { - _result = _controller.Get(); + _result = _controller.Get().Result; } private void ThenTheKeysAreReturned() From 0f60a353efdaca0d73f17e17b122fbd8aa58ef76 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Wed, 28 Jun 2017 19:02:08 +0100 Subject: [PATCH 5/6] now set region in config...or it defaults to something --- src/Ocelot/Cache/IOcelotCache.cs | 2 +- src/Ocelot/Cache/IRegionCreator.cs | 9 ++ .../Cache/Middleware/OutputCacheMiddleware.cs | 8 +- src/Ocelot/Cache/OcelotCacheManagerCache.cs | 7 +- .../{RegionBuilder.cs => RegionCreator.cs} | 16 +-- src/Ocelot/Cache/RegionsGetter.cs | 50 -------- src/Ocelot/Configuration/CacheOptions.cs | 3 +- .../Creator/FileOcelotConfigurationCreator.cs | 10 +- .../Configuration/File/FileCacheOptions.cs | 2 +- src/Ocelot/Configuration/ReRoute.cs | 4 +- .../ConsulOcelotConfigurationRepository.cs | 4 +- .../Controllers/OutputCacheController.cs | 11 +- .../ServiceCollectionExtensions.cs | 1 - .../Cache/CacheManagerCacheTests.cs | 10 +- .../Cache/OutputCacheMiddlewareTests.cs | 6 +- .../Cache/RegionCreatorTests.cs | 33 +++-- .../Cache/RegionsGetterTests.cs | 113 ------------------ .../FileConfigurationCreatorTests.cs | 49 +++++++- .../Controllers/OutputCacheControllerTests.cs | 28 +---- 19 files changed, 124 insertions(+), 242 deletions(-) create mode 100644 src/Ocelot/Cache/IRegionCreator.cs rename src/Ocelot/Cache/{RegionBuilder.cs => RegionCreator.cs} (53%) delete mode 100644 src/Ocelot/Cache/RegionsGetter.cs delete mode 100644 test/Ocelot.UnitTests/Cache/RegionsGetterTests.cs diff --git a/src/Ocelot/Cache/IOcelotCache.cs b/src/Ocelot/Cache/IOcelotCache.cs index 9d7ead9b..9abf5761 100644 --- a/src/Ocelot/Cache/IOcelotCache.cs +++ b/src/Ocelot/Cache/IOcelotCache.cs @@ -7,7 +7,7 @@ namespace Ocelot.Cache { void Add(string key, T value, TimeSpan ttl, string region); void AddAndDelete(string key, T value, TimeSpan ttl, string region); - T Get(string key); + T Get(string key, string region); void ClearRegion(string region); } } diff --git a/src/Ocelot/Cache/IRegionCreator.cs b/src/Ocelot/Cache/IRegionCreator.cs new file mode 100644 index 00000000..8ed186dd --- /dev/null +++ b/src/Ocelot/Cache/IRegionCreator.cs @@ -0,0 +1,9 @@ +using Ocelot.Configuration.File; + +namespace Ocelot.Cache +{ + public interface IRegionCreator + { + string Create(FileReRoute reRoute); + } +} \ No newline at end of file diff --git a/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs b/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs index 8b8b565d..9c6dcf32 100644 --- a/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs +++ b/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs @@ -37,11 +37,11 @@ namespace Ocelot.Cache.Middleware return; } - var downstreamUrlKey = DownstreamRequest.RequestUri.OriginalString; + var downstreamUrlKey = $"{DownstreamRequest.Method.Method}-{DownstreamRequest.RequestUri.OriginalString}"; _logger.LogDebug("started checking cache for {downstreamUrlKey}", downstreamUrlKey); - var cached = _outputCache.Get(downstreamUrlKey); + var cached = _outputCache.Get(downstreamUrlKey, DownstreamRoute.ReRoute.CacheOptions.Region); if (cached != null) { @@ -67,9 +67,7 @@ namespace Ocelot.Cache.Middleware var response = HttpResponseMessage; - var region = _regionCreator.Region(DownstreamRoute.ReRoute); - - _outputCache.Add(downstreamUrlKey, response, TimeSpan.FromSeconds(DownstreamRoute.ReRoute.FileCacheOptions.TtlSeconds), region); + _outputCache.Add(downstreamUrlKey, response, TimeSpan.FromSeconds(DownstreamRoute.ReRoute.CacheOptions.TtlSeconds), DownstreamRoute.ReRoute.CacheOptions.Region); _logger.LogDebug("finished response added to cache for {downstreamUrlKey}", downstreamUrlKey); } diff --git a/src/Ocelot/Cache/OcelotCacheManagerCache.cs b/src/Ocelot/Cache/OcelotCacheManagerCache.cs index 4edfdaad..33ee1543 100644 --- a/src/Ocelot/Cache/OcelotCacheManagerCache.cs +++ b/src/Ocelot/Cache/OcelotCacheManagerCache.cs @@ -8,18 +8,15 @@ namespace Ocelot.Cache public class OcelotCacheManagerCache : IOcelotCache { private readonly ICacheManager _cacheManager; - private HashSet _keys; public OcelotCacheManagerCache(ICacheManager cacheManager) { _cacheManager = cacheManager; - _keys = new HashSet(); } public void Add(string key, T value, TimeSpan ttl, string region) { _cacheManager.Add(new CacheItem(key, region, value, ExpirationMode.Absolute, ttl)); - _keys.Add(key); } public void AddAndDelete(string key, T value, TimeSpan ttl, string region) @@ -34,9 +31,9 @@ namespace Ocelot.Cache Add(key, value, ttl, region); } - public T Get(string key) + public T Get(string key, string region) { - return _cacheManager.Get(key); + return _cacheManager.Get(key, region); } public void ClearRegion(string region) diff --git a/src/Ocelot/Cache/RegionBuilder.cs b/src/Ocelot/Cache/RegionCreator.cs similarity index 53% rename from src/Ocelot/Cache/RegionBuilder.cs rename to src/Ocelot/Cache/RegionCreator.cs index 5ccf8973..87de751c 100644 --- a/src/Ocelot/Cache/RegionBuilder.cs +++ b/src/Ocelot/Cache/RegionCreator.cs @@ -1,20 +1,22 @@ using System.Linq; using Ocelot.Configuration; +using Ocelot.Configuration.File; namespace Ocelot.Cache { - public interface IRegionCreator - { - string Region(ReRoute reRoute); - } public class RegionCreator : IRegionCreator { - public string Region(ReRoute reRoute) + public string Create(FileReRoute reRoute) { - var methods = string.Join("", reRoute.UpstreamHttpMethod.Select(m => m.Method)); + if(!string.IsNullOrEmpty(reRoute?.FileCacheOptions?.Region)) + { + return reRoute?.FileCacheOptions?.Region; + } - var region = $"{methods}{reRoute.UpstreamPathTemplate.Value.Replace("/", "")}"; + var methods = string.Join("", reRoute.UpstreamHttpMethod.Select(m => m)); + + var region = $"{methods}{reRoute.UpstreamPathTemplate.Replace("/", "")}"; return region; } diff --git a/src/Ocelot/Cache/RegionsGetter.cs b/src/Ocelot/Cache/RegionsGetter.cs deleted file mode 100644 index ae87a49f..00000000 --- a/src/Ocelot/Cache/RegionsGetter.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Ocelot.Configuration.Provider; -using Ocelot.Logging; - -namespace Ocelot.Cache -{ - public interface IRegionsGetter - { - Task> Regions(); - } - public class RegionsGetter : IRegionsGetter - { - private readonly IOcelotConfigurationProvider _provider; - private readonly IRegionCreator _creator; - private readonly IOcelotLogger _logger; - - public RegionsGetter(IOcelotConfigurationProvider provider, IRegionCreator creator, IOcelotLoggerFactory loggerFactory) - { - _logger = loggerFactory.CreateLogger(); - _provider = provider; - _creator = creator; - } - - public async Task> Regions() - { - var config = await _provider.Get(); - - if(config.IsError) - { - _logger.LogError("unable to find regions", new Exception(string.Join(",", config.Errors))); - return new List(); - } - - var cachedReRoutes = config.Data.ReRoutes.Where(x => x.IsCached); - - var regions = new List(); - - foreach(var reRoute in cachedReRoutes) - { - var region = _creator.Region(reRoute); - regions.Add(region); - } - - return regions; - } - } -} \ No newline at end of file diff --git a/src/Ocelot/Configuration/CacheOptions.cs b/src/Ocelot/Configuration/CacheOptions.cs index 3de9e019..a8b6a9d3 100644 --- a/src/Ocelot/Configuration/CacheOptions.cs +++ b/src/Ocelot/Configuration/CacheOptions.cs @@ -2,9 +2,10 @@ { public class CacheOptions { - public CacheOptions(int ttlSeconds) + public CacheOptions(int ttlSeconds, string region) { TtlSeconds = ttlSeconds; + Region = region; } public int TtlSeconds { get; private set; } diff --git a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs index c44edd08..3b9c91d0 100644 --- a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs @@ -4,6 +4,7 @@ using System.Text; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using Ocelot.Cache; using Ocelot.Configuration.Builder; using Ocelot.Configuration.File; using Ocelot.Configuration.Parser; @@ -36,6 +37,7 @@ namespace Ocelot.Configuration.Creator private IQoSOptionsCreator _qosOptionsCreator; private IReRouteOptionsCreator _fileReRouteOptionsCreator; private IRateLimitOptionsCreator _rateLimitOptionsCreator; + private IRegionCreator _regionCreator; public FileOcelotConfigurationCreator( IOptions options, @@ -52,9 +54,11 @@ namespace Ocelot.Configuration.Creator IServiceProviderConfigurationCreator serviceProviderConfigCreator, IQoSOptionsCreator qosOptionsCreator, IReRouteOptionsCreator fileReRouteOptionsCreator, - IRateLimitOptionsCreator rateLimitOptionsCreator + IRateLimitOptionsCreator rateLimitOptionsCreator, + IRegionCreator regionCreator ) { + _regionCreator = regionCreator; _rateLimitOptionsCreator = rateLimitOptionsCreator; _requestIdKeyCreator = requestIdKeyCreator; _upstreamTemplatePatternCreator = upstreamTemplatePatternCreator; @@ -137,6 +141,8 @@ namespace Ocelot.Configuration.Creator var rateLimitOption = _rateLimitOptionsCreator.Create(fileReRoute, globalConfiguration, fileReRouteOptions.EnableRateLimiting); + var region = _regionCreator.Create(fileReRoute); + var reRoute = new ReRouteBuilder() .WithDownstreamPathTemplate(fileReRoute.DownstreamPathTemplate) .WithUpstreamPathTemplate(fileReRoute.UpstreamPathTemplate) @@ -151,7 +157,7 @@ namespace Ocelot.Configuration.Creator .WithClaimsToQueries(claimsToQueries) .WithRequestIdKey(requestIdKey) .WithIsCached(fileReRouteOptions.IsCached) - .WithCacheOptions(new CacheOptions(fileReRoute.FileCacheOptions.TtlSeconds)) + .WithCacheOptions(new CacheOptions(fileReRoute.FileCacheOptions.TtlSeconds, region)) .WithDownstreamScheme(fileReRoute.DownstreamScheme) .WithLoadBalancer(fileReRoute.LoadBalancer) .WithDownstreamHost(fileReRoute.DownstreamHost) diff --git a/src/Ocelot/Configuration/File/FileCacheOptions.cs b/src/Ocelot/Configuration/File/FileCacheOptions.cs index 46fc7b87..df9fb631 100644 --- a/src/Ocelot/Configuration/File/FileCacheOptions.cs +++ b/src/Ocelot/Configuration/File/FileCacheOptions.cs @@ -3,6 +3,6 @@ public class FileCacheOptions { public int TtlSeconds { get; set; } - public string Region {get;private set;} + public string Region {get; set;} } } diff --git a/src/Ocelot/Configuration/ReRoute.cs b/src/Ocelot/Configuration/ReRoute.cs index a6673d09..2b8734c4 100644 --- a/src/Ocelot/Configuration/ReRoute.cs +++ b/src/Ocelot/Configuration/ReRoute.cs @@ -46,7 +46,7 @@ namespace Ocelot.Configuration IsAuthorised = isAuthorised; RequestIdKey = requestIdKey; IsCached = isCached; - FileCacheOptions = fileCacheOptions; + CacheOptions = fileCacheOptions; ClaimsToQueries = claimsToQueries ?? new List(); ClaimsToClaims = claimsToClaims @@ -74,7 +74,7 @@ namespace Ocelot.Configuration public Dictionary RouteClaimsRequirement { get; private set; } public string RequestIdKey { get; private set; } public bool IsCached { get; private set; } - public CacheOptions FileCacheOptions { get; private set; } + public CacheOptions CacheOptions { get; private set; } public string DownstreamScheme {get;private set;} public bool IsQos { get; private set; } public QoSOptions QosOptionsOptions { get; private set; } diff --git a/src/Ocelot/Configuration/Repository/ConsulOcelotConfigurationRepository.cs b/src/Ocelot/Configuration/Repository/ConsulOcelotConfigurationRepository.cs index d7929aaa..c5413274 100644 --- a/src/Ocelot/Configuration/Repository/ConsulOcelotConfigurationRepository.cs +++ b/src/Ocelot/Configuration/Repository/ConsulOcelotConfigurationRepository.cs @@ -30,7 +30,7 @@ namespace Ocelot.Configuration.Repository public async Task> Get() { - var config = _cache.Get(_ocelotConfiguration); + var config = _cache.Get(_ocelotConfiguration, _ocelotConfiguration); if (config != null) { @@ -68,7 +68,7 @@ namespace Ocelot.Configuration.Repository if (result.Response) { - _cache.AddAndDelete(_ocelotConfiguration, ocelotConfiguration, TimeSpan.FromSeconds(3), "OcelotConfiguration"); + _cache.AddAndDelete(_ocelotConfiguration, ocelotConfiguration, TimeSpan.FromSeconds(3), _ocelotConfiguration); return new OkResponse(); } diff --git a/src/Ocelot/Controllers/OutputCacheController.cs b/src/Ocelot/Controllers/OutputCacheController.cs index 21dccb8d..a68201a1 100644 --- a/src/Ocelot/Controllers/OutputCacheController.cs +++ b/src/Ocelot/Controllers/OutputCacheController.cs @@ -12,19 +12,10 @@ namespace Ocelot.Controllers public class OutputCacheController : Controller { private IOcelotCache _cache; - private IRegionsGetter _regionsGetter; - public OutputCacheController(IOcelotCache cache, IRegionsGetter regionsGetter) + public OutputCacheController(IOcelotCache cache) { _cache = cache; - _regionsGetter = regionsGetter; - } - - [HttpGet] - public async Task Get() - { - var regions = await _regionsGetter.Regions(); - return new OkObjectResult(new Regions(regions)); } [HttpDelete] diff --git a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs index d2ff4b74..954942dc 100644 --- a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs @@ -145,7 +145,6 @@ namespace Ocelot.DependencyInjection .AddJsonFormatters(); services.AddLogging(); - services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); diff --git a/test/Ocelot.UnitTests/Cache/CacheManagerCacheTests.cs b/test/Ocelot.UnitTests/Cache/CacheManagerCacheTests.cs index 5780cff2..f097ccf1 100644 --- a/test/Ocelot.UnitTests/Cache/CacheManagerCacheTests.cs +++ b/test/Ocelot.UnitTests/Cache/CacheManagerCacheTests.cs @@ -18,6 +18,7 @@ namespace Ocelot.UnitTests.Cache private string _resultGet; private TimeSpan _ttlSeconds; private List _resultKeys; + private string _region; public CacheManagerCacheTests() { @@ -28,7 +29,7 @@ namespace Ocelot.UnitTests.Cache [Fact] public void should_get_from_cache() { - this.Given(x => x.GivenTheFollowingIsCached("someKey", "someValue")) + this.Given(x => x.GivenTheFollowingIsCached("someKey", "someRegion", "someValue")) .When(x => x.WhenIGetFromTheCache()) .Then(x => x.ThenTheResultIs("someValue")) .BDDfy(); @@ -88,15 +89,16 @@ namespace Ocelot.UnitTests.Cache private void WhenIGetFromTheCache() { - _resultGet = _ocelotOcelotCacheManager.Get(_key); + _resultGet = _ocelotOcelotCacheManager.Get(_key, _region); } - private void GivenTheFollowingIsCached(string key, string value) + private void GivenTheFollowingIsCached(string key, string region, string value) { _key = key; _value = value; + _region = region; _mockCacheManager - .Setup(x => x.Get(It.IsAny())) + .Setup(x => x.Get(It.IsAny(), It.IsAny())) .Returns(value); } } diff --git a/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs b/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs index 0039e2af..ff74fba0 100644 --- a/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs @@ -92,7 +92,7 @@ namespace Ocelot.UnitTests.Cache { var reRoute = new ReRouteBuilder() .WithIsCached(true) - .WithCacheOptions(new CacheOptions(100)) + .WithCacheOptions(new CacheOptions(100, "kanken")) .WithUpstreamHttpMethod(new List { "Get" }) .Build(); @@ -120,7 +120,7 @@ namespace Ocelot.UnitTests.Cache private void ThenTheCacheGetIsCalledCorrectly() { _cacheManager - .Verify(x => x.Get(It.IsAny()), Times.Once); + .Verify(x => x.Get(It.IsAny(), It.IsAny()), Times.Once); } private void ThenTheCacheAddIsCalledCorrectly() @@ -140,7 +140,7 @@ namespace Ocelot.UnitTests.Cache { _response = response; _cacheManager - .Setup(x => x.Get(It.IsAny())) + .Setup(x => x.Get(It.IsAny(), It.IsAny())) .Returns(_response); } diff --git a/test/Ocelot.UnitTests/Cache/RegionCreatorTests.cs b/test/Ocelot.UnitTests/Cache/RegionCreatorTests.cs index ed8491ec..6a475425 100644 --- a/test/Ocelot.UnitTests/Cache/RegionCreatorTests.cs +++ b/test/Ocelot.UnitTests/Cache/RegionCreatorTests.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using Ocelot.Cache; using Ocelot.Configuration; using Ocelot.Configuration.Builder; +using Ocelot.Configuration.File; using Shouldly; using TestStack.BDDfy; using Xunit; @@ -11,23 +12,41 @@ namespace Ocelot.UnitTests.Cache public class RegionCreatorTests { private string _result; - private ReRoute _reRoute; + private FileReRoute _reRoute; [Fact] public void should_create_region() { - var reRoute = new ReRouteBuilder() - .WithUpstreamHttpMethod(new List{"Get"}) - .WithUpstreamPathTemplate("/test/dummy") - .Build(); + var reRoute = new FileReRoute + { + UpstreamHttpMethod = new List { "Get" }, + UpstreamPathTemplate = "/testdummy" + }; this.Given(_ => GivenTheReRoute(reRoute)) .When(_ => WhenICreateTheRegion()) .Then(_ => ThenTheRegionIs("Gettestdummy")) .BDDfy(); } + + [Fact] + public void should_use_region() + { + var reRoute = new FileReRoute + { + FileCacheOptions = new FileCacheOptions + { + Region = "region" + } + }; + + this.Given(_ => GivenTheReRoute(reRoute)) + .When(_ => WhenICreateTheRegion()) + .Then(_ => ThenTheRegionIs("region")) + .BDDfy(); + } - private void GivenTheReRoute(ReRoute reRoute) + private void GivenTheReRoute(FileReRoute reRoute) { _reRoute = reRoute; } @@ -35,7 +54,7 @@ namespace Ocelot.UnitTests.Cache private void WhenICreateTheRegion() { RegionCreator regionCreator = new RegionCreator(); - _result = regionCreator.Region(_reRoute); + _result = regionCreator.Create(_reRoute); } private void ThenTheRegionIs(string expected) diff --git a/test/Ocelot.UnitTests/Cache/RegionsGetterTests.cs b/test/Ocelot.UnitTests/Cache/RegionsGetterTests.cs deleted file mode 100644 index a7d2b662..00000000 --- a/test/Ocelot.UnitTests/Cache/RegionsGetterTests.cs +++ /dev/null @@ -1,113 +0,0 @@ -using Xunit; -using TestStack.BDDfy; -using Shouldly; -using Ocelot.Cache; -using Moq; -using Ocelot.Configuration.Provider; -using System.Collections.Generic; -using Ocelot.Responses; -using Ocelot.Configuration; -using System.Threading.Tasks; -using Ocelot.Configuration.Builder; -using System; -using Ocelot.Errors; -using Ocelot.Logging; - -namespace Ocelot.UnitTests.Cache -{ - public class RegionsGetterTests - { - private RegionsGetter _regionsGetter; - private readonly Mock _provider; - private readonly Mock _creator; - private readonly Mock _factory; - private List _result; - - public RegionsGetterTests() - { - _provider = new Mock(); - _creator = new Mock(); - _factory = new Mock(); - var logger = new Mock(); - _factory - .Setup(x => x.CreateLogger()) - .Returns(logger.Object); - _regionsGetter = new RegionsGetter(_provider.Object, _creator.Object, _factory.Object); - } - - [Fact] - public void should_get_regions() - { - var cacheOptions = new CacheOptions(12); - - var reRoute = new ReRouteBuilder() - .WithUpstreamHttpMethod(new List{"Get"}) - .WithUpstreamPathTemplate("/") - .WithCacheOptions(cacheOptions) - .WithIsCached(true) - .Build(); - - var reRoutes = new List - { - reRoute - }; - - var config = new OcelotConfiguration(reRoutes, "whocares!"); - - var expected = new List - { - "balls" - }; - - this.Given(_ => GivenTheFollowingConfig(config)) - .And(_ => GivenTheProviderReturns("balls")) - .When(_ => WhenIGetTheRegions()) - .Then(_ => ThenTheFollowingIsReturned(expected)) - .BDDfy(); - } - - [Fact] - public void should_return_empty_regions() - { - var expected = new List(); - - this.Given(_ => GivenAnErrorGettingTheConfig()) - .When(_ => WhenIGetTheRegions()) - .Then(_ => ThenTheFollowingIsReturned(expected)) - .BDDfy(); - } - - private void GivenAnErrorGettingTheConfig() - { - var config = new OcelotConfiguration(new List(), "whocares!"); - - _provider - .Setup(x => x.Get()) - .ReturnsAsync(new ErrorResponse(It.IsAny())); - } - - private void GivenTheProviderReturns(string expected) - { - _creator - .Setup(x => x.Region(It.IsAny())) - .Returns(expected); - } - - private void GivenTheFollowingConfig(IOcelotConfiguration config) - { - _provider - .Setup(x => x.Get()) - .ReturnsAsync(new OkResponse(config)); - } - - private void WhenIGetTheRegions() - { - _result = _regionsGetter.Regions().Result; - } - - private void ThenTheFollowingIsReturned(List expected) - { - _result.ShouldBe(expected); - } - } -} \ No newline at end of file diff --git a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs index bd8c46d3..386958c9 100644 --- a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs @@ -2,6 +2,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Moq; +using Ocelot.Cache; using Ocelot.Configuration; using Ocelot.Configuration.Builder; using Ocelot.Configuration.Creator; @@ -39,6 +40,7 @@ namespace Ocelot.UnitTests.Configuration private Mock _qosOptionsCreator; private Mock _fileReRouteOptionsCreator; private Mock _rateLimitOptions; + private Mock _regionCreator; public FileConfigurationCreatorTests() { @@ -59,6 +61,7 @@ namespace Ocelot.UnitTests.Configuration _qosOptionsCreator = new Mock(); _fileReRouteOptionsCreator = new Mock(); _rateLimitOptions = new Mock(); + _regionCreator = new Mock(); _ocelotConfigurationCreator = new FileOcelotConfigurationCreator( _fileConfig.Object, _validator.Object, _logger.Object, @@ -66,7 +69,51 @@ namespace Ocelot.UnitTests.Configuration _qosProviderFactory.Object, _qosProviderHouse.Object, _claimsToThingCreator.Object, _authOptionsCreator.Object, _upstreamTemplatePatternCreator.Object, _requestIdKeyCreator.Object, _serviceProviderConfigCreator.Object, _qosOptionsCreator.Object, _fileReRouteOptionsCreator.Object, - _rateLimitOptions.Object); + _rateLimitOptions.Object, _regionCreator.Object); + } + + [Fact] + public void should_call_region_creator() + { + var reRouteOptions = new ReRouteOptionsBuilder() + .Build(); + + this.Given(x => x.GivenTheConfigIs(new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamHost = "127.0.0.1", + UpstreamPathTemplate = "/api/products/{productId}", + DownstreamPathTemplate = "/products/{productId}", + UpstreamHttpMethod = new List { "Get" }, + FileCacheOptions = new FileCacheOptions + { + Region = "region" + } + } + }, + })) + .And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions)) + .And(x => x.GivenTheConfigIsValid()) + .And(x => x.GivenTheFollowingRegionIsReturned("region")) + .When(x => x.WhenICreateTheConfig()) + .Then(x => x.ThenTheRegionCreatorIsCalledCorrectly("region")) + .BDDfy(); + } + + private void GivenTheFollowingRegionIsReturned(string region) + { + _regionCreator + .Setup(x => x.Create(It.IsAny())) + .Returns(region); + } + + private void ThenTheRegionCreatorIsCalledCorrectly(string expected) + { + _regionCreator + .Verify(x => x.Create(_fileConfiguration.ReRoutes[0]), Times.Once); } [Fact] diff --git a/test/Ocelot.UnitTests/Controllers/OutputCacheControllerTests.cs b/test/Ocelot.UnitTests/Controllers/OutputCacheControllerTests.cs index 7639ed78..100597a0 100644 --- a/test/Ocelot.UnitTests/Controllers/OutputCacheControllerTests.cs +++ b/test/Ocelot.UnitTests/Controllers/OutputCacheControllerTests.cs @@ -15,23 +15,12 @@ namespace Ocelot.UnitTests.Controllers { private OutputCacheController _controller; private Mock> _cache; - private Mock _getter; private IActionResult _result; public OutputCacheControllerTests() { _cache = new Mock>(); - _getter = new Mock(); - _controller = new OutputCacheController(_cache.Object, _getter.Object); - } - - [Fact] - public void should_get_all_keys_from_server() - { - this.Given(_ => GivenTheFollowingKeys(new List{"b", "a"})) - .When(_ => WhenIGetTheKeys()) - .Then(_ => ThenTheKeysAreReturned()) - .BDDfy(); + _controller = new OutputCacheController(_cache.Object); } [Fact] @@ -53,20 +42,5 @@ namespace Ocelot.UnitTests.Controllers { _result = _controller.Delete(key); } - - private void GivenTheFollowingKeys(List keys) - { - - } - - private void WhenIGetTheKeys() - { - _result = _controller.Get().Result; - } - - private void ThenTheKeysAreReturned() - { - _result.ShouldBeOfType(); - } } } \ No newline at end of file From 9a4a670e795959304e436d14cb3f4757ec952bee Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Wed, 28 Jun 2017 19:05:06 +0100 Subject: [PATCH 6/6] sorted int tests --- .../AdministrationTests.cs | 70 ++++--------------- 1 file changed, 12 insertions(+), 58 deletions(-) diff --git a/test/Ocelot.IntegrationTests/AdministrationTests.cs b/test/Ocelot.IntegrationTests/AdministrationTests.cs index 0a0f20e4..39721df6 100644 --- a/test/Ocelot.IntegrationTests/AdministrationTests.cs +++ b/test/Ocelot.IntegrationTests/AdministrationTests.cs @@ -121,7 +121,12 @@ namespace Ocelot.IntegrationTests DownstreamScheme = "https", DownstreamPathTemplate = "/", UpstreamHttpMethod = new List { "get" }, - UpstreamPathTemplate = "/" + UpstreamPathTemplate = "/", + FileCacheOptions = new FileCacheOptions + { + TtlSeconds = 10, + Region = "Geoff" + } }, new FileReRoute() { @@ -130,7 +135,12 @@ namespace Ocelot.IntegrationTests DownstreamScheme = "https", DownstreamPathTemplate = "/", UpstreamHttpMethod = new List { "get" }, - UpstreamPathTemplate = "/test" + UpstreamPathTemplate = "/test", + FileCacheOptions = new FileCacheOptions + { + TtlSeconds = 10, + Region = "Dave" + } } } }; @@ -219,62 +229,6 @@ namespace Ocelot.IntegrationTests .BDDfy(); } - [Fact] - public void should_return_regions() - { - var initialConfiguration = new FileConfiguration - { - GlobalConfiguration = new FileGlobalConfiguration - { - AdministrationPath = "/administration" - }, - ReRoutes = new List() - { - new FileReRoute() - { - DownstreamHost = "localhost", - DownstreamPort = 80, - DownstreamScheme = "https", - DownstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "get" }, - UpstreamPathTemplate = "/", - FileCacheOptions = new FileCacheOptions - { - TtlSeconds = 10 - } - }, - new FileReRoute() - { - DownstreamHost = "localhost", - DownstreamPort = 80, - DownstreamScheme = "https", - DownstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "get" }, - UpstreamPathTemplate = "/test", - FileCacheOptions = new FileCacheOptions - { - TtlSeconds = 10 - } - } - } - }; - - var expected = new List - { - "get", - "gettest" - }; - - this.Given(x => GivenThereIsAConfiguration(initialConfiguration)) - .And(x => GivenOcelotIsRunning()) - .And(x => GivenIHaveAnOcelotToken("/administration")) - .And(x => GivenIHaveAddedATokenToMyRequest()) - .When(x => WhenIGetUrlOnTheApiGateway("/administration/outputcache")) - .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => ThenTheResponseShouldBe(expected)) - .BDDfy(); - } - [Fact] public void should_clear_region() {