public async Task ApplyConfigurationsAsync_OneClusterOneDestinationOneRoute_Works() { // Arrange const string TestAddress = "https://localhost:123/"; var cluster = new Cluster { Destinations = { { "d1", new Destination { Address = TestAddress } } } }; var route = new ParsedRoute { RouteId = "route1", ClusterId = "cluster1", }; var dynamicConfigRoot = new DynamicConfigRoot { Clusters = new Dictionary<string, Cluster> { { "cluster1", cluster } }, Routes = new[] { route }, }; Mock<IDynamicConfigBuilder>() .Setup(d => d.BuildConfigAsync(It.IsAny<CancellationToken>())) .ReturnsAsync(dynamicConfigRoot); var proxyManager = Create<ReverseProxyConfigManager>(); // Act await proxyManager.ApplyConfigurationsAsync(CancellationToken.None); // Assert var actualClusters = _clusterManager.GetItems(); Assert.Single(actualClusters); Assert.Equal("cluster1", actualClusters[0].ClusterId); Assert.NotNull(actualClusters[0].DestinationManager); Assert.NotNull(actualClusters[0].Config.Value); var actualDestinations = actualClusters[0].DestinationManager.GetItems(); Assert.Single(actualDestinations); Assert.Equal("d1", actualDestinations[0].DestinationId); Assert.NotNull(actualDestinations[0].Config); Assert.Equal(TestAddress, actualDestinations[0].Config.Address); var actualRoutes = _routeManager.GetItems(); Assert.Single(actualRoutes); Assert.Equal("route1", actualRoutes[0].RouteId); Assert.NotNull(actualRoutes[0].Config.Value); Assert.Same(actualClusters[0], actualRoutes[0].Config.Value.Cluster); }
public async Task <DynamicConfigRoot> BuildConfigAsync(IConfigErrorReporter errorReporter, CancellationToken cancellation) { var clusters = await GetClustersAsync(errorReporter, cancellation); var routes = await GetRoutesAsync(errorReporter, cancellation); var config = new DynamicConfigRoot { Clusters = clusters, Routes = routes, }; return(config); }
public async Task <Result <DynamicConfigRoot> > BuildConfigAsync(IConfigErrorReporter errorReporter, CancellationToken cancellation) { Contracts.CheckValue(errorReporter, nameof(errorReporter)); var backends = await GetBackendsAsync(errorReporter, cancellation); var routes = await GetRoutesAsync(errorReporter, cancellation); var config = new DynamicConfigRoot { Backends = backends, Routes = routes, }; return(Result.Success(config)); }
public async Task <Result <DynamicConfigRoot> > BuildConfigAsync(IConfigErrorReporter errorReporter, CancellationToken cancellation) { Contracts.CheckValue(errorReporter, nameof(errorReporter)); var backends = await _backendsRepo.GetBackendsAsync(cancellation) ?? new Dictionary <string, Backend>(StringComparer.Ordinal); var routes = await GetRoutesAsync(errorReporter, cancellation); var config = new DynamicConfigRoot { Backends = backends, Routes = routes, }; return(Result.Success(config)); }
public async Task ApplyConfigurationsAsync_OneBackendOneEndpointOneRoute_Works() { // Arrange const string TestAddress = "https://localhost:123/"; var backend = new Backend { Endpoints = { { "ep1", new BackendEndpoint { Address = TestAddress } } } }; var route = new ParsedRoute { RouteId = "route1", BackendId = "backend1", }; var dynamicConfigRoot = new DynamicConfigRoot { Backends = new Dictionary <string, Backend> { { "backend1", backend } }, Routes = new[] { route }, }; Mock <IDynamicConfigBuilder>() .Setup(d => d.BuildConfigAsync(It.IsAny <IConfigErrorReporter>(), It.IsAny <CancellationToken>())) .ReturnsAsync(Result.Success(dynamicConfigRoot)); var errorReporter = new TestConfigErrorReporter(); var proxyManager = Create <ReverseProxyConfigManager>(); // Act var result = await proxyManager.ApplyConfigurationsAsync(errorReporter, CancellationToken.None); // Assert Assert.True(result); var actualBackends = _backendManager.GetItems(); Assert.Single(actualBackends); Assert.Equal("backend1", actualBackends[0].BackendId); Assert.NotNull(actualBackends[0].EndpointManager); Assert.NotNull(actualBackends[0].Config.Value); var actualEndpoints = actualBackends[0].EndpointManager.GetItems(); Assert.Single(actualEndpoints); Assert.Equal("ep1", actualEndpoints[0].EndpointId); Assert.NotNull(actualEndpoints[0].Config.Value); Assert.Equal(TestAddress, actualEndpoints[0].Config.Value.Address); var actualRoutes = _routeManager.GetItems(); Assert.Single(actualRoutes); Assert.Equal("route1", actualRoutes[0].RouteId); Assert.NotNull(actualRoutes[0].Config.Value); Assert.Same(actualBackends[0], actualRoutes[0].Config.Value.BackendOrNull); }
private void UpdateRuntimeBackends(DynamicConfigRoot config) { var desiredBackends = new HashSet <string>(StringComparer.Ordinal); foreach (var configBackendPair in config.Backends) { var configBackend = configBackendPair.Value; desiredBackends.Add(configBackendPair.Key); _backendManager.GetOrCreateItem( itemId: configBackendPair.Key, setupAction: backend => { UpdateRuntimeDestinations(configBackend.Destinations, backend.DestinationManager); var newConfig = new BackendConfig( new BackendConfig.BackendHealthCheckOptions( enabled: configBackend.HealthCheckOptions?.Enabled ?? false, interval: configBackend.HealthCheckOptions?.Interval ?? TimeSpan.FromSeconds(0), timeout: configBackend.HealthCheckOptions?.Timeout ?? TimeSpan.FromSeconds(0), port: configBackend.HealthCheckOptions?.Port ?? 0, path: configBackend.HealthCheckOptions?.Path ?? string.Empty), new BackendConfig.BackendLoadBalancingOptions( mode: configBackend.LoadBalancing?.Mode ?? default)); var currentBackendConfig = backend.Config.Value; if (currentBackendConfig == null || currentBackendConfig.HealthCheckOptions.Enabled != newConfig.HealthCheckOptions.Enabled || currentBackendConfig.HealthCheckOptions.Interval != newConfig.HealthCheckOptions.Interval || currentBackendConfig.HealthCheckOptions.Timeout != newConfig.HealthCheckOptions.Timeout || currentBackendConfig.HealthCheckOptions.Port != newConfig.HealthCheckOptions.Port || currentBackendConfig.HealthCheckOptions.Path != newConfig.HealthCheckOptions.Path) { if (currentBackendConfig == null) { Log.BackendAdded(_logger, configBackendPair.Key); } else { Log.BackendChanged(_logger, configBackendPair.Key); } // Config changed, so update runtime backend backend.Config.Value = newConfig; } }); } foreach (var existingBackend in _backendManager.GetItems()) { if (!desiredBackends.Contains(existingBackend.BackendId)) { // NOTE 1: This is safe to do within the `foreach` loop // because `IBackendManager.GetItems` returns a copy of the list of backends. // // NOTE 2: Removing the backend from `IBackendManager` is safe and existing // ASP .NET Core endpoints will continue to work with their existing behavior (until those endpoints are updated) // and the Garbage Collector won't destroy this backend object while it's referenced elsewhere. Log.BackendRemoved(_logger, existingBackend.BackendId); _backendManager.TryRemoveItem(existingBackend.BackendId); } } }
private void UpdateRuntimeRoutes(DynamicConfigRoot config) { var desiredRoutes = new HashSet <string>(StringComparer.Ordinal); var changed = false; foreach (var configRoute in config.Routes) { desiredRoutes.Add(configRoute.RouteId); // Note that this can be null, and that is fine. The resulting route may match // but would then fail to route, which is exactly what we were instructed to do in this case // since no valid backend was specified. var backendOrNull = _backendManager.TryGetItem(configRoute.BackendId); _routeManager.GetOrCreateItem( itemId: configRoute.RouteId, setupAction: route => { var currentRouteConfig = route.Config.Value; if (currentRouteConfig == null || currentRouteConfig.HasConfigChanged(configRoute, backendOrNull)) { // Config changed, so update runtime route changed = true; if (currentRouteConfig == null) { Log.RouteAdded(_logger, configRoute.RouteId); } else { Log.RouteChanged(_logger, configRoute.RouteId); } var newConfig = _routeEndpointBuilder.Build(configRoute, backendOrNull, route); route.Config.Value = newConfig; } }); } foreach (var existingRoute in _routeManager.GetItems()) { if (!desiredRoutes.Contains(existingRoute.RouteId)) { // NOTE 1: This is safe to do within the `foreach` loop // because `IRouteManager.GetItems` returns a copy of the list of routes. // // NOTE 2: Removing the route from `IRouteManager` is safe and existing // ASP .NET Core endpoints will continue to work with their existing behavior since // their copy of `RouteConfig` is immutable and remains operational in whichever state is was in. Log.RouteRemoved(_logger, existingRoute.RouteId); _routeManager.TryRemoveItem(existingRoute.RouteId); changed = true; } } if (changed) { var endpoints = new List <Endpoint>(); foreach (var existingRoute in _routeManager.GetItems()) { var runtimeConfig = existingRoute.Config.Value; if (runtimeConfig?.Endpoints != null) { endpoints.AddRange(runtimeConfig.Endpoints); } } // This is where the new routes take effect! _dynamicEndpointDataSource.Update(endpoints); } }
private void UpdateRuntimeClusters(DynamicConfigRoot config) { var desiredClusters = new HashSet <string>(StringComparer.Ordinal); foreach (var configClusterPair in config.Clusters) { var configCluster = configClusterPair.Value; desiredClusters.Add(configClusterPair.Key); _clusterManager.GetOrCreateItem( itemId: configClusterPair.Key, setupAction: cluster => { UpdateRuntimeDestinations(configCluster.Destinations, cluster.DestinationManager); var newConfig = new ClusterConfig( new ClusterConfig.ClusterHealthCheckOptions( enabled: configCluster.HealthCheckOptions?.Enabled ?? false, interval: configCluster.HealthCheckOptions?.Interval ?? TimeSpan.FromSeconds(0), timeout: configCluster.HealthCheckOptions?.Timeout ?? TimeSpan.FromSeconds(0), port: configCluster.HealthCheckOptions?.Port ?? 0, path: configCluster.HealthCheckOptions?.Path ?? string.Empty), new ClusterConfig.ClusterLoadBalancingOptions( mode: configCluster.LoadBalancing?.Mode ?? default), new ClusterConfig.ClusterSessionAffinityOptions( enabled: configCluster.SessionAffinity?.Enabled ?? false, mode: configCluster.SessionAffinity?.Mode, failurePolicy: configCluster.SessionAffinity?.FailurePolicy, settings: configCluster.SessionAffinity?.Settings as IReadOnlyDictionary <string, string>)); var currentClusterConfig = cluster.Config.Value; if (currentClusterConfig == null || currentClusterConfig.HealthCheckOptions.Enabled != newConfig.HealthCheckOptions.Enabled || currentClusterConfig.HealthCheckOptions.Interval != newConfig.HealthCheckOptions.Interval || currentClusterConfig.HealthCheckOptions.Timeout != newConfig.HealthCheckOptions.Timeout || currentClusterConfig.HealthCheckOptions.Port != newConfig.HealthCheckOptions.Port || currentClusterConfig.HealthCheckOptions.Path != newConfig.HealthCheckOptions.Path) { if (currentClusterConfig == null) { Log.ClusterAdded(_logger, configClusterPair.Key); } else { Log.ClusterChanged(_logger, configClusterPair.Key); } // Config changed, so update runtime cluster cluster.Config.Value = newConfig; } }); } foreach (var existingCluster in _clusterManager.GetItems()) { if (!desiredClusters.Contains(existingCluster.ClusterId)) { // NOTE 1: This is safe to do within the `foreach` loop // because `IClusterManager.GetItems` returns a copy of the list of clusters. // // NOTE 2: Removing the cluster from `IClusterManager` is safe and existing // ASP .NET Core endpoints will continue to work with their existing behavior (until those endpoints are updated) // and the Garbage Collector won't destroy this cluster object while it's referenced elsewhere. Log.ClusterRemoved(_logger, existingCluster.ClusterId); _clusterManager.TryRemoveItem(existingCluster.ClusterId); } } }