mirror of
https://github.com/nsnail/Ocelot.git
synced 2025-06-19 15:58:16 +08:00
Activate ChangeToken when Ocelot's configuration changes #1037
* Add configuration change token (#1036) * Add IOptionsMonitor<IInternalConfiguration> * Activate change token from *ConfigurationRepository instead of FileAndInternalConfigurationSetter; add acceptance & integration tests * Update documentation * Use IWebHostEnvironment as IHostingEnvironment deprecated Co-authored-by: Chris Swinchatt <chrisswinchatt@gmail.com>
This commit is contained in:
@ -1,5 +1,6 @@
|
||||
using Ocelot.Configuration.File;
|
||||
using System;
|
||||
using Ocelot.Configuration.ChangeTracking;
|
||||
using TestStack.BDDfy;
|
||||
using Xunit;
|
||||
|
||||
@ -54,6 +55,33 @@ namespace Ocelot.AcceptanceTests
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_trigger_change_token_on_change()
|
||||
{
|
||||
this.Given(x => _steps.GivenThereIsAConfiguration(_initialConfig))
|
||||
.And(x => _steps.GivenOcelotIsRunningReloadingConfig(true))
|
||||
.And(x => _steps.GivenIHaveAChangeToken())
|
||||
.And(x => _steps.GivenThereIsAConfiguration(_anotherConfig))
|
||||
.And(x => _steps.GivenIWait(MillisecondsToWaitForChangeToken))
|
||||
.Then(x => _steps.TheChangeTokenShouldBeActive(true))
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_not_trigger_change_token_with_no_change()
|
||||
{
|
||||
this.Given(x => _steps.GivenThereIsAConfiguration(_initialConfig))
|
||||
.And(x => _steps.GivenOcelotIsRunningReloadingConfig(false))
|
||||
.And(x => _steps.GivenIHaveAChangeToken())
|
||||
.And(x => _steps.GivenIWait(MillisecondsToWaitForChangeToken)) // Wait for prior activation to expire.
|
||||
.And(x => _steps.GivenThereIsAConfiguration(_anotherConfig))
|
||||
.And(x => _steps.GivenIWait(MillisecondsToWaitForChangeToken))
|
||||
.Then(x => _steps.TheChangeTokenShouldBeActive(false))
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
private const int MillisecondsToWaitForChangeToken = (int) (OcelotConfigurationChangeToken.PollingIntervalSeconds*1000) - 100;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_steps.Dispose();
|
||||
|
@ -1,4 +1,6 @@
|
||||
namespace Ocelot.AcceptanceTests
|
||||
using Ocelot.Configuration.ChangeTracking;
|
||||
|
||||
namespace Ocelot.AcceptanceTests
|
||||
{
|
||||
using Caching;
|
||||
using Configuration.Repository;
|
||||
@ -54,6 +56,7 @@
|
||||
private IWebHostBuilder _webHostBuilder;
|
||||
private WebHostBuilder _ocelotBuilder;
|
||||
private IWebHost _ocelotHost;
|
||||
private IOcelotConfigurationChangeTokenSource _changeToken;
|
||||
|
||||
public Steps()
|
||||
{
|
||||
@ -216,6 +219,11 @@
|
||||
_ocelotClient = _ocelotServer.CreateClient();
|
||||
}
|
||||
|
||||
public void GivenIHaveAChangeToken()
|
||||
{
|
||||
_changeToken = _ocelotServer.Host.Services.GetRequiredService<IOcelotConfigurationChangeTokenSource>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is annoying cos it should be in the constructor but we need to set up the file before calling startup so its a step.
|
||||
/// </summary>
|
||||
@ -1123,6 +1131,11 @@
|
||||
_ocelotClient = _ocelotServer.CreateClient();
|
||||
}
|
||||
|
||||
public void TheChangeTokenShouldBeActive(bool itShouldBeActive)
|
||||
{
|
||||
_changeToken.ChangeToken.HasChanged.ShouldBe(itShouldBeActive);
|
||||
}
|
||||
|
||||
public void GivenOcelotIsRunningWithLogger()
|
||||
{
|
||||
_webHostBuilder = new WebHostBuilder();
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,35 @@
|
||||
namespace Ocelot.UnitTests.Configuration.ChangeTracking
|
||||
{
|
||||
using Ocelot.Configuration.ChangeTracking;
|
||||
using Shouldly;
|
||||
using TestStack.BDDfy;
|
||||
using Xunit;
|
||||
|
||||
public class OcelotConfigurationChangeTokenSourceTests
|
||||
{
|
||||
private readonly IOcelotConfigurationChangeTokenSource _source;
|
||||
|
||||
public OcelotConfigurationChangeTokenSourceTests()
|
||||
{
|
||||
_source = new OcelotConfigurationChangeTokenSource();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_activate_change_token()
|
||||
{
|
||||
this.Given(_ => GivenIActivateTheChangeTokenSource())
|
||||
.Then(_ => ThenTheChangeTokenShouldBeActivated())
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
private void GivenIActivateTheChangeTokenSource()
|
||||
{
|
||||
_source.Activate();
|
||||
}
|
||||
|
||||
private void ThenTheChangeTokenShouldBeActivated()
|
||||
{
|
||||
_source.ChangeToken.HasChanged.ShouldBeTrue();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,91 @@
|
||||
using Xunit;
|
||||
|
||||
namespace Ocelot.UnitTests.Configuration.ChangeTracking
|
||||
{
|
||||
using System;
|
||||
using Shouldly;
|
||||
using Ocelot.Configuration.ChangeTracking;
|
||||
using TestStack.BDDfy;
|
||||
|
||||
public class OcelotConfigurationChangeTokenTests
|
||||
{
|
||||
[Fact]
|
||||
public void should_call_callback_with_state()
|
||||
{
|
||||
this.Given(_ => GivenIHaveAChangeToken())
|
||||
.And(_ => AndIRegisterACallback())
|
||||
.Then(_ => ThenIShouldGetADisposableWrapper())
|
||||
.Given(_ => GivenIActivateTheToken())
|
||||
.Then(_ => ThenTheCallbackShouldBeCalled())
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_not_call_callback_if_it_is_disposed()
|
||||
{
|
||||
this.Given(_ => GivenIHaveAChangeToken())
|
||||
.And(_ => AndIRegisterACallback())
|
||||
.Then(_ => ThenIShouldGetADisposableWrapper())
|
||||
.And(_ => GivenIActivateTheToken())
|
||||
.And(_ => AndIDisposeTheCallbackWrapper())
|
||||
.And(_ => GivenIActivateTheToken())
|
||||
.Then(_ => ThenTheCallbackShouldNotBeCalled())
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
private OcelotConfigurationChangeToken _changeToken;
|
||||
private IDisposable _callbackWrapper;
|
||||
private int _callbackCounter;
|
||||
private readonly object _callbackInitialState = new object();
|
||||
private object _callbackState;
|
||||
|
||||
private void Callback(object state)
|
||||
{
|
||||
_callbackCounter++;
|
||||
_callbackState = state;
|
||||
_changeToken.HasChanged.ShouldBeTrue();
|
||||
}
|
||||
|
||||
private void GivenIHaveAChangeToken()
|
||||
{
|
||||
_changeToken = new OcelotConfigurationChangeToken();
|
||||
}
|
||||
|
||||
private void AndIRegisterACallback()
|
||||
{
|
||||
_callbackWrapper = _changeToken.RegisterChangeCallback(Callback, _callbackInitialState);
|
||||
}
|
||||
|
||||
private void ThenIShouldGetADisposableWrapper()
|
||||
{
|
||||
_callbackWrapper.ShouldNotBeNull();
|
||||
}
|
||||
|
||||
private void GivenIActivateTheToken()
|
||||
{
|
||||
_callbackCounter = 0;
|
||||
_callbackState = null;
|
||||
_changeToken.Activate();
|
||||
}
|
||||
|
||||
private void ThenTheCallbackShouldBeCalled()
|
||||
{
|
||||
_callbackCounter.ShouldBe(1);
|
||||
_callbackState.ShouldNotBeNull();
|
||||
_callbackState.ShouldBeSameAs(_callbackInitialState);
|
||||
}
|
||||
|
||||
private void ThenTheCallbackShouldNotBeCalled()
|
||||
{
|
||||
_callbackCounter.ShouldBe(0);
|
||||
_callbackState.ShouldBeNull();
|
||||
}
|
||||
|
||||
private void AndIDisposeTheCallbackWrapper()
|
||||
{
|
||||
_callbackState = null;
|
||||
_callbackCounter = 0;
|
||||
_callbackWrapper.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@ namespace Ocelot.UnitTests.Configuration
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Moq;
|
||||
using Newtonsoft.Json;
|
||||
using Ocelot.Configuration.ChangeTracking;
|
||||
using Ocelot.Configuration.File;
|
||||
using Ocelot.Configuration.Repository;
|
||||
using Shouldly;
|
||||
@ -16,6 +17,7 @@ namespace Ocelot.UnitTests.Configuration
|
||||
public class DiskFileConfigurationRepositoryTests : IDisposable
|
||||
{
|
||||
private readonly Mock<IWebHostEnvironment> _hostingEnvironment;
|
||||
private readonly Mock<IOcelotConfigurationChangeTokenSource> _changeTokenSource;
|
||||
private IFileConfigurationRepository _repo;
|
||||
private string _environmentSpecificPath;
|
||||
private string _ocelotJsonPath;
|
||||
@ -35,7 +37,9 @@ namespace Ocelot.UnitTests.Configuration
|
||||
_semaphore.Wait();
|
||||
_hostingEnvironment = new Mock<IWebHostEnvironment>();
|
||||
_hostingEnvironment.Setup(he => he.EnvironmentName).Returns(_environmentName);
|
||||
_repo = new DiskFileConfigurationRepository(_hostingEnvironment.Object);
|
||||
_changeTokenSource = new Mock<IOcelotConfigurationChangeTokenSource>(MockBehavior.Strict);
|
||||
_changeTokenSource.Setup(m => m.Activate());
|
||||
_repo = new DiskFileConfigurationRepository(_hostingEnvironment.Object, _changeTokenSource.Object);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@ -70,6 +74,7 @@ namespace Ocelot.UnitTests.Configuration
|
||||
.When(_ => WhenISetTheConfiguration())
|
||||
.Then(_ => ThenTheConfigurationIsStoredAs(config))
|
||||
.And(_ => ThenTheConfigurationJsonIsIndented(config))
|
||||
.And(x => AndTheChangeTokenIsActivated())
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
@ -117,7 +122,7 @@ namespace Ocelot.UnitTests.Configuration
|
||||
{
|
||||
_environmentName = null;
|
||||
_hostingEnvironment.Setup(he => he.EnvironmentName).Returns(_environmentName);
|
||||
_repo = new DiskFileConfigurationRepository(_hostingEnvironment.Object);
|
||||
_repo = new DiskFileConfigurationRepository(_hostingEnvironment.Object, _changeTokenSource.Object);
|
||||
}
|
||||
|
||||
private void GivenIHaveAConfiguration(FileConfiguration fileConfiguration)
|
||||
@ -210,6 +215,11 @@ namespace Ocelot.UnitTests.Configuration
|
||||
}
|
||||
}
|
||||
|
||||
private void AndTheChangeTokenIsActivated()
|
||||
{
|
||||
_changeTokenSource.Verify(m => m.Activate(), Times.Once);
|
||||
}
|
||||
|
||||
private FileConfiguration FakeFileConfigurationForSet()
|
||||
{
|
||||
var reRoutes = new List<FileReRoute>
|
||||
@ -222,11 +232,11 @@ namespace Ocelot.UnitTests.Configuration
|
||||
{
|
||||
Host = "123.12.12.12",
|
||||
Port = 80,
|
||||
}
|
||||
},
|
||||
},
|
||||
DownstreamScheme = "https",
|
||||
DownstreamPathTemplate = "/asdfs/test/{test}"
|
||||
}
|
||||
DownstreamPathTemplate = "/asdfs/test/{test}",
|
||||
},
|
||||
};
|
||||
|
||||
var globalConfiguration = new FileGlobalConfiguration
|
||||
|
@ -9,6 +9,7 @@ using Ocelot.Errors;
|
||||
using Ocelot.Responses;
|
||||
using Shouldly;
|
||||
using System.Collections.Generic;
|
||||
using Ocelot.Configuration.ChangeTracking;
|
||||
using TestStack.BDDfy;
|
||||
using Xunit;
|
||||
|
||||
@ -21,7 +22,7 @@ namespace Ocelot.UnitTests.Configuration
|
||||
private Mock<IInternalConfigurationRepository> _configRepo;
|
||||
private Mock<IInternalConfigurationCreator> _configCreator;
|
||||
private Response<IInternalConfiguration> _configuration;
|
||||
private object _result;
|
||||
private object _result;
|
||||
private Mock<IFileConfigurationRepository> _repo;
|
||||
|
||||
public FileConfigurationSetterTests()
|
||||
@ -104,8 +105,7 @@ namespace Ocelot.UnitTests.Configuration
|
||||
|
||||
private void ThenTheConfigurationRepositoryIsCalledCorrectly()
|
||||
{
|
||||
_configRepo
|
||||
.Verify(x => x.AddOrReplace(_configuration.Data), Times.Once);
|
||||
_configRepo.Verify(x => x.AddOrReplace(_configuration.Data), Times.Once);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,8 @@ using Ocelot.Responses;
|
||||
using Shouldly;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Moq;
|
||||
using Ocelot.Configuration.ChangeTracking;
|
||||
using TestStack.BDDfy;
|
||||
using Xunit;
|
||||
|
||||
@ -16,10 +18,13 @@ namespace Ocelot.UnitTests.Configuration
|
||||
private IInternalConfiguration _config;
|
||||
private Response _result;
|
||||
private Response<IInternalConfiguration> _getResult;
|
||||
private readonly Mock<IOcelotConfigurationChangeTokenSource> _changeTokenSource;
|
||||
|
||||
public InMemoryConfigurationRepositoryTests()
|
||||
{
|
||||
_repo = new InMemoryInternalConfigurationRepository();
|
||||
_changeTokenSource = new Mock<IOcelotConfigurationChangeTokenSource>(MockBehavior.Strict);
|
||||
_changeTokenSource.Setup(m => m.Activate());
|
||||
_repo = new InMemoryInternalConfigurationRepository(_changeTokenSource.Object);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@ -28,6 +33,7 @@ namespace Ocelot.UnitTests.Configuration
|
||||
this.Given(x => x.GivenTheConfigurationIs(new FakeConfig("initial", "adminath")))
|
||||
.When(x => x.WhenIAddOrReplaceTheConfig())
|
||||
.Then(x => x.ThenNoErrorsAreReturned())
|
||||
.And(x => AndTheChangeTokenIsActivated())
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
@ -71,6 +77,11 @@ namespace Ocelot.UnitTests.Configuration
|
||||
_result.IsError.ShouldBeFalse();
|
||||
}
|
||||
|
||||
private void AndTheChangeTokenIsActivated()
|
||||
{
|
||||
_changeTokenSource.Verify(m => m.Activate(), Times.Once);
|
||||
}
|
||||
|
||||
private class FakeConfig : IInternalConfiguration
|
||||
{
|
||||
private readonly string _downstreamTemplatePath;
|
||||
@ -111,4 +122,4 @@ namespace Ocelot.UnitTests.Configuration
|
||||
public HttpHandlerOptions HttpHandlerOptions { get; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user