mirror of
				https://github.com/nsnail/Ocelot.git
				synced 2025-11-04 15:30:49 +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