Skip to content

Commit

Permalink
Implement ProxyConfig and ProxyConfigProvider callbacks
Browse files Browse the repository at this point in the history
  • Loading branch information
nulltoken committed May 24, 2022
1 parent b09b9dc commit 6f4db9a
Show file tree
Hide file tree
Showing 5 changed files with 329 additions and 17 deletions.
23 changes: 23 additions & 0 deletions src/ReverseProxy/Configuration/IProxyConfigNotifier.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System;

namespace Yarp.ReverseProxy.Configuration;

/// <summary>
/// Represents an optional configuration capability. When decorating a <see cref="IProxyConfig"/> type,
/// will allow config instances to be notified upon config applying.
/// </summary>
public interface IProxyConfigNotifier
{
/// <summary>
/// A callback that will be triggered once changes to the configuration have been successfully applied.
/// </summary>
void SuccessfulConfigChangeApplyingCallback();

/// <summary>
/// A callback that will be triggered once changes to the configuration have been tried to be applied but eventually failed.
/// </summary>
void ErroredConfigChangeApplyingCallback(Exception ex);
}
23 changes: 23 additions & 0 deletions src/ReverseProxy/Configuration/IProxyConfigProviderNotifier.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System;

namespace Yarp.ReverseProxy.Configuration;

/// <summary>
/// Represents an optional configuration provider capability. When decorating a <see cref="IProxyConfigProvider"/> type,
/// will allow config provider instances to be notified upon config loading.
/// </summary>
public interface IProxyConfigProviderNotifier
{
/// <summary>
/// A callback that will be triggered once the configuration have been successfully loaded.
/// </summary>
void SuccessfulConfigLoadingCallback();

/// <summary>
/// A callback that will be triggered once the configuration have been tried to be loaded but eventually failed.
/// </summary>
void ErroredConfigLoadingCallback(Exception ex);
}
46 changes: 46 additions & 0 deletions src/ReverseProxy/Management/ProxyConfigManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -158,10 +158,23 @@ internal async Task<EndpointDataSource> InitialLoadAsync()
_configs[i] = new ConfigState(provider, config);
routes.AddRange(config.Routes ?? Array.Empty<RouteConfig>());
clusters.AddRange(config.Clusters ?? Array.Empty<ClusterConfig>());

if (provider is IProxyConfigProviderNotifier)
{
((IProxyConfigProviderNotifier)provider).SuccessfulConfigLoadingCallback();
}
}

await ApplyConfigAsync(routes, clusters);

foreach (var c in _configs)
{
if (c.LatestConfig is IProxyConfigNotifier)
{
((IProxyConfigNotifier)c.LatestConfig).SuccessfulConfigChangeApplyingCallback();
}
}

ListenForConfigChanges();
}
catch (Exception ex)
Expand All @@ -182,6 +195,9 @@ private async Task ReloadConfigAsync()
var sourcesChanged = false;
var routes = new List<RouteConfig>();
var clusters = new List<ClusterConfig>();

var notifiers = new List<IProxyConfigNotifier>();

foreach (var instance in _configs)
{
try
Expand All @@ -193,12 +209,30 @@ private async Task ReloadConfigAsync()
instance.LatestConfig = config;
instance.LoadFailed = false;
sourcesChanged = true;

if (instance.Provider is IProxyConfigProviderNotifier)
{
// When callback is defined, notify the configuration provider of the successful loading of the configuration.
((IProxyConfigProviderNotifier)instance.Provider).SuccessfulConfigLoadingCallback();
}
}

if (instance.LatestConfig is IProxyConfigNotifier)
{
// And register potentially existing config notifiers for later invocation
notifiers.Add(((IProxyConfigNotifier)instance.LatestConfig));
}
}
catch (Exception ex)
{
instance.LoadFailed = true;
Log.ErrorReloadingConfig(_logger, ex);

if (instance.Provider is IProxyConfigProviderNotifier)
{
// When callback is defined, notify the configuration provider of the unsuccessful loading of the configuration.
((IProxyConfigProviderNotifier)instance.Provider).ErroredConfigLoadingCallback(ex);
}
}

// If we didn't/couldn't get a new config then re-use the last one.
Expand All @@ -221,10 +255,22 @@ private async Task ReloadConfigAsync()
CreateEndpoints();
}
}

foreach (var notifier in notifiers)
{
// Notify all registered config callbacks of successful applying.
notifier.SuccessfulConfigChangeApplyingCallback();
}
}
catch (Exception ex)
{
Log.ErrorApplyingConfig(_logger, ex);

foreach (var notifier in notifiers)
{
// Notify all registered config callbacks of unsuccessful applying.
notifier.ErroredConfigChangeApplyingCallback(ex);
}
}
}

Expand Down
117 changes: 100 additions & 17 deletions test/ReverseProxy.Tests/Common/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 @@ -25,41 +26,123 @@ public class InMemoryConfigProvider : IProxyConfigProvider
{
private volatile InMemoryConfig _config;

public InMemoryConfigProvider(IReadOnlyList<RouteConfig> routes, IReadOnlyList<ClusterConfig> clusters)
public InMemoryConfigProvider(IReadOnlyList<RouteConfig> routes, IReadOnlyList<ClusterConfig> clusters) : this(new InMemoryConfig(routes, clusters))
{ }

public InMemoryConfigProvider(InMemoryConfig config)
{
_config = new InMemoryConfig(routes, clusters);
_config = config;
}

public IProxyConfig GetConfig() => _config;
public virtual IProxyConfig GetConfig() => _config;

public void Update(IReadOnlyList<RouteConfig> routes, IReadOnlyList<ClusterConfig> clusters)
{
Update(new InMemoryConfig(routes, clusters));
}

public void Update(InMemoryConfig config)
{
var oldConfig = _config;
_config = new InMemoryConfig(routes, clusters);
_config = config;
oldConfig.SignalChange();
}
}

private class InMemoryConfig : IProxyConfig
public class InMemoryConfig : IProxyConfig
{
private readonly CancellationTokenSource _cts = new CancellationTokenSource();

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

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

public IReadOnlyList<RouteConfig> Routes { get; }
public IReadOnlyList<ClusterConfig> Clusters { get; }

public IReadOnlyList<ClusterConfig> Clusters { get; }
public IChangeToken ChangeToken { get; }

public IChangeToken ChangeToken { get; }
internal void SignalChange()
{
_cts.Cancel();
}
}

public class InMemoryConfigProviderNotifier : InMemoryConfigProvider, IProxyConfigProviderNotifier
{
private readonly Action _successLoadingCallback;
private readonly Action<Exception> _erroredLoadingCallback;

public InMemoryConfigProviderNotifier(
InMemoryConfig config,
Action successLoadingCallback,
Action<Exception> erroredLoadingCallback) : base(config)
{
_successLoadingCallback = successLoadingCallback;
_erroredLoadingCallback = erroredLoadingCallback;
}

public InMemoryConfigProviderNotifier(
IReadOnlyList<RouteConfig> routes,
IReadOnlyList<ClusterConfig> clusters,
Action successLoadingCallback,
Action<Exception> erroredLoadingCallback) : base(routes, clusters)
{
_successLoadingCallback = successLoadingCallback;
_erroredLoadingCallback = erroredLoadingCallback;
}

internal void SignalChange()
public override IProxyConfig GetConfig()
{
if (ShouldConfigLoadingFail)
{
_cts.Cancel();
return null;
}

return base.GetConfig();
}

public bool ShouldConfigLoadingFail { get; set; }

public void ErroredConfigLoadingCallback(Exception ex)
{
_erroredLoadingCallback(ex);
}

public void SuccessfulConfigLoadingCallback()
{
_successLoadingCallback();
}
}

public class InMemoryConfigNotifier : InMemoryConfig, IProxyConfigNotifier
{
private readonly CancellationTokenSource _cts = new CancellationTokenSource();
private readonly Action _successChangeCallback;
private readonly Action<Exception> _erroredChangeCallback;

public InMemoryConfigNotifier(
IReadOnlyList<RouteConfig> routes,
IReadOnlyList<ClusterConfig> clusters,
Action successChangeCallback,
Action<Exception> erroredChangeCallback) : base(routes, clusters)
{
_successChangeCallback = successChangeCallback;
_erroredChangeCallback = erroredChangeCallback;
}

public void ErroredConfigChangeApplyingCallback(Exception ex)
{
_erroredChangeCallback(ex);
}

public void SuccessfulConfigChangeApplyingCallback()
{
_successChangeCallback();
}
}
}
Loading

0 comments on commit 6f4db9a

Please sign in to comment.