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 Aug 16, 2022
1 parent d203a23 commit 9195ea4
Show file tree
Hide file tree
Showing 6 changed files with 399 additions and 12 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
Expand Down Expand Up @@ -36,12 +37,19 @@ private class MessageConfig : IProxyConfig
private readonly CancellationTokenSource _cts = new CancellationTokenSource();

public MessageConfig(IReadOnlyList<RouteConfig> routes, IReadOnlyList<ClusterConfig> clusters)
: this(routes, clusters, Guid.NewGuid().ToString())
{ }

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

public string RevisionId { get; }

public IReadOnlyList<RouteConfig> Routes { get; }

public IReadOnlyList<ClusterConfig> Clusters { get; }
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 ConfigurationLoadingFailed(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 when attempting to apply the configuration.
/// </summary>
/// <param name="proxyConfigs">The list of instances that were being processed.</param>
/// <param name="ex">The thrown exception.</param>
///
void ConfigurationApplyingFailed(IReadOnlyList<IProxyConfig> proxyConfigs, Exception ex);

/// <summary>
/// Invoked once the configuration has been successfully applied.
/// </summary>
/// <param name="proxyConfigs">The list of instances that have been applied.</param>
///
void ConfigurationApplied(IReadOnlyList<IProxyConfig> proxyConfigs);
}
9 changes: 9 additions & 0 deletions src/ReverseProxy/Configuration/IProxyConfig.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using Microsoft.Extensions.Primitives;

namespace Yarp.ReverseProxy.Configuration;
Expand All @@ -11,6 +13,13 @@ namespace Yarp.ReverseProxy.Configuration;
/// </summary>
public interface IProxyConfig
{
private static readonly ConditionalWeakTable<IProxyConfig, string> _revisionIdsTable = new();

/// <summary>
/// Configuration version identifier
/// </summary>
string RevisionId => _revisionIdsTable.GetValue(this, static _ => Guid.NewGuid().ToString());

/// <summary>
/// Routes matching requests to clusters.
/// </summary>
Expand Down
34 changes: 33 additions & 1 deletion src/ReverseProxy/Configuration/InMemoryConfigProvider.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.Threading;
using Microsoft.Extensions.Primitives;
Expand All @@ -19,8 +20,15 @@ public sealed class InMemoryConfigProvider : IProxyConfigProvider
/// Creates a new instance.
/// </summary>
public InMemoryConfigProvider(IReadOnlyList<RouteConfig> routes, IReadOnlyList<ClusterConfig> clusters)
: this(routes, clusters, Guid.NewGuid().ToString())
{ }

/// <summary>
/// Creates a new instance, specifying a version of the configuration.
/// </summary>
public InMemoryConfigProvider(IReadOnlyList<RouteConfig> routes, IReadOnlyList<ClusterConfig> clusters, string revisionId)
{
_config = new InMemoryConfig(routes, clusters);
_config = new InMemoryConfig(routes, clusters, revisionId);
}

/// <summary>
Expand All @@ -35,6 +43,20 @@ public InMemoryConfigProvider(IReadOnlyList<RouteConfig> routes, IReadOnlyList<C
public void Update(IReadOnlyList<RouteConfig> routes, IReadOnlyList<ClusterConfig> clusters)
{
var newConfig = new InMemoryConfig(routes, clusters);
UpdateInternal(newConfig);
}

/// <summary>
/// Swaps the config state with a new versioned snapshot of the configuration, then signals that the old one is outdated.
/// </summary>
public void Update(IReadOnlyList<RouteConfig> routes, IReadOnlyList<ClusterConfig> clusters, string revisionId)
{
var newConfig = new InMemoryConfig(routes, clusters, revisionId);
UpdateInternal(newConfig);
}

private void UpdateInternal(InMemoryConfig newConfig)
{
var oldConfig = Interlocked.Exchange(ref _config, newConfig);
oldConfig.SignalChange();
}
Expand All @@ -48,12 +70,22 @@ private class InMemoryConfig : IProxyConfig
private readonly CancellationTokenSource _cts = new CancellationTokenSource();

public InMemoryConfig(IReadOnlyList<RouteConfig> routes, IReadOnlyList<ClusterConfig> clusters)
: this(routes, clusters, Guid.NewGuid().ToString())
{ }

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

/// <summary>
/// Configuration version identifier
/// </summary>
public string RevisionId { get; }

/// <summary>
/// A snapshot of the list of routes for the proxy
/// </summary>
Expand Down
39 changes: 30 additions & 9 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 => state.LatestConfig).ToList().AsReadOnly();
}

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

_configNotifier?.ConfigurationLoaded(ProxyConfigManager.ExtractListOfProxyConfigs(_configs));

await ApplyConfigAsync(routes, clusters);

_configNotifier?.ConfigurationApplied(_configs.Select(state => state.LatestConfig).ToList().AsReadOnly());

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

_configNotifier?.ConfigurationLoadingFailed(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>());
}

// Only reload if at least one provider changed.
if (sourcesChanged)
_configNotifier?.ConfigurationLoaded(ProxyConfigManager.ExtractListOfProxyConfigs(_configs));

try
{
try
// Only reload if at least one provider changed.
if (sourcesChanged)
{

var hasChanged = await ApplyConfigAsync(routes, clusters);
lock (_syncRoot)
{
Expand All @@ -222,10 +239,14 @@ private async Task ReloadConfigAsync()
}
}
}
catch (Exception ex)
{
Log.ErrorApplyingConfig(_logger, ex);
}

_configNotifier?.ConfigurationApplied(ProxyConfigManager.ExtractListOfProxyConfigs(_configs));
}
catch (Exception ex)
{
Log.ErrorApplyingConfig(_logger, ex);

_configNotifier?.ConfigurationApplyingFailed(ProxyConfigManager.ExtractListOfProxyConfigs(_configs), ex);
}

ListenForConfigChanges();
Expand Down
Loading

0 comments on commit 9195ea4

Please sign in to comment.