Skip to content

Commit

Permalink
Expose IConfigNotifier callback service
Browse files Browse the repository at this point in the history
  • Loading branch information
nulltoken committed Jul 20, 2022
1 parent 903b7cd commit d4985a2
Show file tree
Hide file tree
Showing 8 changed files with 395 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,16 @@ private class MessageConfig : IProxyConfig
{
private readonly CancellationTokenSource _cts = new CancellationTokenSource();

public MessageConfig(IReadOnlyList<RouteConfig> routes, IReadOnlyList<ClusterConfig> clusters)
public MessageConfig(IReadOnlyList<RouteConfig> routes, IReadOnlyList<ClusterConfig> clusters, string id = null)
{
Id = id;
Routes = routes;
Clusters = clusters;
ChangeToken = new CancellationChangeToken(_cts.Token);
}

public string Id { get; }

public IReadOnlyList<RouteConfig> Routes { get; }

public IReadOnlyList<ClusterConfig> Clusters { get; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ namespace Yarp.ReverseProxy.Configuration.ConfigProvider;

internal sealed class ConfigurationSnapshot : IProxyConfig
{
// TODO See how and where this should be valued.
public string? Id { get; internal set; }

public List<RouteConfig> Routes { get; internal set; } = new List<RouteConfig>();

public List<ClusterConfig> Clusters { get; internal set; } = new List<ClusterConfig>();
Expand Down
45 changes: 45 additions & 0 deletions src/ReverseProxy/Configuration/IConfigNotifier.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;

namespace Yarp.ReverseProxy.Configuration;

/// <summary>
/// Allows configuration management to optionaly notify of errors occuring during loading and applying.
/// In order to leverage this service, one has got to register a type implementing this interface in
/// the dependency injection container.
/// </summary>
public interface IConfigNotifier
{
/// <summary>
/// Invoked when an error occurs during the loading of the configuration.
/// </summary>
/// <param name="configProvider">The instance of the configuration provider that failed to provide the configuration.</param>
/// <param name="ex">The thrown exception.</param>
///
void ConfigurationLoadingErrored(IProxyConfigProvider configProvider, Exception ex);

/// <summary>
/// Invoked once the configuration have been successfully loaded.
/// </summary>
/// <param name="proxyConfigs">The list of instances that have been loaded.</param>
///
void ConfigurationLoaded(IReadOnlyList<IProxyConfig> proxyConfigs);

/// <summary>
/// Invoked when an error occurs during the parsing of the configuration.
/// </summary>
/// <param name="proxyConfigs">The list of instances that were being processed.</param>
/// <param name="ex">The thrown exception.</param>
///
void ConfigurationApplyingErrored(IReadOnlyList<IProxyConfig> proxyConfigs, Exception ex);

/// <summary>
/// Invoked once the configuration have been successfully applied.
/// </summary>
/// <param name="proxyConfigs">The list of instances that have been applied.</param>
///
void ConfigurationApplied(IReadOnlyList<IProxyConfig> proxyConfigs);
}
5 changes: 5 additions & 0 deletions src/ReverseProxy/Configuration/IProxyConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ namespace Yarp.ReverseProxy.Configuration;
/// </summary>
public interface IProxyConfig
{
/// <summary>
/// Optional configuration version identifier
/// </summary>
string? Id { get; }

/// <summary>
/// Routes matching requests to clusters.
/// </summary>
Expand Down
16 changes: 11 additions & 5 deletions src/ReverseProxy/Configuration/InMemoryConfigProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ public sealed class InMemoryConfigProvider : IProxyConfigProvider
/// <summary>
/// Creates a new instance.
/// </summary>
public InMemoryConfigProvider(IReadOnlyList<RouteConfig> routes, IReadOnlyList<ClusterConfig> clusters)
public InMemoryConfigProvider(IReadOnlyList<RouteConfig> routes, IReadOnlyList<ClusterConfig> clusters, string? id = null)
{
_config = new InMemoryConfig(routes, clusters);
_config = new InMemoryConfig(routes, clusters, id);
}

/// <summary>
Expand All @@ -32,9 +32,9 @@ public InMemoryConfigProvider(IReadOnlyList<RouteConfig> routes, IReadOnlyList<C
/// <summary>
/// Swaps the config state with a new snapshot of the configuration, then signals that the old one is outdated.
/// </summary>
public void Update(IReadOnlyList<RouteConfig> routes, IReadOnlyList<ClusterConfig> clusters)
public void Update(IReadOnlyList<RouteConfig> routes, IReadOnlyList<ClusterConfig> clusters, string? id = null)
{
var newConfig = new InMemoryConfig(routes, clusters);
var newConfig = new InMemoryConfig(routes, clusters, id);
var oldConfig = Interlocked.Exchange(ref _config, newConfig);
oldConfig.SignalChange();
}
Expand All @@ -47,13 +47,19 @@ private class InMemoryConfig : IProxyConfig
// Used to implement the change token for the state
private readonly CancellationTokenSource _cts = new CancellationTokenSource();

public InMemoryConfig(IReadOnlyList<RouteConfig> routes, IReadOnlyList<ClusterConfig> clusters)
public InMemoryConfig(IReadOnlyList<RouteConfig> routes, IReadOnlyList<ClusterConfig> clusters, string? id = null)
{
Id = id;
Routes = routes;
Clusters = clusters;
ChangeToken = new CancellationChangeToken(_cts.Token);
}

/// <summary>
/// Optional configuration version identifier
/// </summary>
public string? Id { get; }

/// <summary>
/// A snapshot of the list of routes for the proxy
/// </summary>
Expand Down
51 changes: 49 additions & 2 deletions src/ReverseProxy/Management/ProxyConfigManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ internal sealed class ProxyConfigManager : EndpointDataSource, IProxyStateLookup
private readonly List<Action<EndpointBuilder>> _conventions;
private readonly IActiveHealthCheckMonitor _activeHealthCheckMonitor;
private readonly IClusterDestinationsUpdater _clusterDestinationsUpdater;

private readonly IConfigNotifier? _configNotifier;
private List<Endpoint>? _endpoints;
private CancellationTokenSource _endpointsChangeSource = new();
private IChangeToken _endpointsChangeToken;
Expand All @@ -66,7 +66,8 @@ public ProxyConfigManager(
ITransformBuilder transformBuilder,
IForwarderHttpClientFactory httpClientFactory,
IActiveHealthCheckMonitor activeHealthCheckMonitor,
IClusterDestinationsUpdater clusterDestinationsUpdater)
IClusterDestinationsUpdater clusterDestinationsUpdater,
IConfigNotifier? configNotifier = null)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_providers = providers?.ToArray() ?? throw new ArgumentNullException(nameof(providers));
Expand All @@ -80,6 +81,8 @@ public ProxyConfigManager(
_activeHealthCheckMonitor = activeHealthCheckMonitor ?? throw new ArgumentNullException(nameof(activeHealthCheckMonitor));
_clusterDestinationsUpdater = clusterDestinationsUpdater ?? throw new ArgumentNullException(nameof(clusterDestinationsUpdater));

_configNotifier = configNotifier;

if (_providers.Length == 0)
{
throw new ArgumentException($"At least one {nameof(IProxyConfigProvider)} is required.", nameof(providers));
Expand Down Expand Up @@ -141,6 +144,11 @@ private void CreateEndpoints()
/// <inheritdoc/>
public override IChangeToken GetChangeToken() => Volatile.Read(ref _endpointsChangeToken);

private static IReadOnlyList<IProxyConfig> ExtractListOfProxyConfigs(IEnumerable<ConfigState> configStates)
{
return configStates.Select(state => { return state.LatestConfig; }).ToList().AsReadOnly();
}

internal async Task<EndpointDataSource> InitialLoadAsync()
{
// Trigger the first load immediately and throw if it fails.
Expand All @@ -160,8 +168,18 @@ internal async Task<EndpointDataSource> InitialLoadAsync()
clusters.AddRange(config.Clusters ?? Array.Empty<ClusterConfig>());
}

if (_configNotifier is not null)
{
_configNotifier.ConfigurationLoaded(ProxyConfigManager.ExtractListOfProxyConfigs(_configs));
}

await ApplyConfigAsync(routes, clusters);

if (_configNotifier is not null)
{
_configNotifier.ConfigurationApplied(_configs.Select(state => { return state.LatestConfig; }).ToList().AsReadOnly());
}

ListenForConfigChanges();
}
catch (Exception ex)
Expand Down Expand Up @@ -199,13 +217,24 @@ private async Task ReloadConfigAsync()
{
instance.LoadFailed = true;
Log.ErrorReloadingConfig(_logger, ex);

if (_configNotifier is not null)
{
// When callback is defined, notify about the configuration provider that failed to provide a valid configuration.
_configNotifier.ConfigurationLoadingErrored(instance.Provider, ex);
}
}

// If we didn't/couldn't get a new config then re-use the last one.
routes.AddRange(instance.LatestConfig.Routes ?? Array.Empty<RouteConfig>());
clusters.AddRange(instance.LatestConfig.Clusters ?? Array.Empty<ClusterConfig>());
}

if (_configNotifier is not null)
{
_configNotifier.ConfigurationLoaded(ProxyConfigManager.ExtractListOfProxyConfigs(_configs));
}

// Only reload if at least one provider changed.
if (sourcesChanged)
{
Expand All @@ -221,10 +250,28 @@ private async Task ReloadConfigAsync()
CreateEndpoints();
}
}

if (_configNotifier is not null)
{
_configNotifier.ConfigurationApplied(ProxyConfigManager.ExtractListOfProxyConfigs(_configs));
}
}
catch (Exception ex)
{
Log.ErrorApplyingConfig(_logger, ex);

if (_configNotifier is not null)
{
// When callback is defined, notify about the configurations that failed to be applied..
_configNotifier.ConfigurationApplyingErrored(ProxyConfigManager.ExtractListOfProxyConfigs(_configs), ex);
}
}
}
else
{
if (_configNotifier is not null)
{
_configNotifier.ConfigurationApplied(ProxyConfigManager.ExtractListOfProxyConfigs(_configs));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,11 @@ void VerifyFullyInitialized(object obj, string name)
switch (obj)
{
case null:
if (name == "ConfigurationSnapshot.Id")
{
// Optional string property
break;
}
Assert.True(false, $"Property {name} is not initialized.");
break;
case Enum m:
Expand Down
Loading

0 comments on commit d4985a2

Please sign in to comment.