-
Notifications
You must be signed in to change notification settings - Fork 843
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add cluster access API, reassignment (#1538)
- Loading branch information
Showing
11 changed files
with
239 additions
and
23 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
# A/B Testing and Rolling Upgrades | ||
|
||
## Introduction | ||
|
||
A/B testing and rolling upgrades require procedures for dynamically assigning incoming traffic to evaluate changes in the destination application. YARP does not have a built-in model for this, but it does expose some infrastructure useful for building such a system. See [issue #126](https://github.com/microsoft/reverse-proxy/issues/126) for additional details about this scenario. | ||
|
||
## Example | ||
|
||
``` | ||
public void Configure(IApplicationBuilder app, IProxyStateLookup lookup) | ||
{ | ||
app.UseRouting(); | ||
app.UseEndpoints(endpoints => | ||
{ | ||
endpoints.MapReverseProxy(proxyPipeline => | ||
{ | ||
// Custom cluster selection | ||
proxyPipeline.Use((context, next) => | ||
{ | ||
if (lookup.TryGetCluster(ChooseCluster(context), out var cluster)) | ||
{ | ||
context.ReassignProxyRequest(cluster); | ||
} | ||
return next(); | ||
}); | ||
proxyPipeline.UseSessionAffinity(); | ||
proxyPipeline.UseLoadBalancing(); | ||
}); | ||
}); | ||
} | ||
private string ChooseCluster(HttpContext context) | ||
{ | ||
// Decide which cluster to use. This could be random, weighted, based on headers, etc. | ||
return Random.Shared.Next(2) == 1 ? "cluster1" : "cluster2"; | ||
} | ||
``` | ||
|
||
## Usage | ||
|
||
This scenario makes use of two APIs, [IProxyStateLookup](xref:Yarp.ReverseProxy.IProxyStateLookup) and [ReassignProxyRequest](xref:Microsoft.AspNetCore.Http.HttpContextFeaturesExtensions.ReassignProxyRequest), called from a custom proxy middleware as shown in the sample above. | ||
|
||
`IProxyStateLookup` is a service available in the Dependency Injection container that can be used to look up or enumerate the current routes and clusters. Note this data may change if the configuration changes. An A/B orchestration algorithm can examine the request, decide which cluster to send it to, and then retrieve that cluster from `IProxyStateLookup.TryGetCluster`. | ||
|
||
Once the cluster is selected, `ReassignProxyRequest` can be called to assign the request to that cluster. This updates the [IReverseProxyFeature](xref:Yarp.ReverseProxy.Model.IReverseProxyFeature) with the new cluster and destination information needed for the rest of the proxy middleware pipeline to handle the request. | ||
|
||
## Session affinity | ||
|
||
Note that session affinity functionality is split between middleware, which reads it settings from the current cluster, and transforms, which are part of the original route. Clusters used for A/B testing should use the same session affinity configuration to avoid conflicts. | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
// Copyright (c) Microsoft Corporation. | ||
// Licensed under the MIT License. | ||
|
||
using System.Collections.Generic; | ||
using System.Diagnostics.CodeAnalysis; | ||
using Yarp.ReverseProxy.Model; | ||
|
||
namespace Yarp.ReverseProxy; | ||
|
||
/// <summary> | ||
/// Allows access to the proxy's current set of routes and clusters. | ||
/// </summary> | ||
public interface IProxyStateLookup | ||
{ | ||
/// <summary> | ||
/// Retrieves a specific route by id, if present. | ||
/// </summary> | ||
bool TryGetRoute(string id, [NotNullWhen(true)] out RouteModel? route); | ||
|
||
/// <summary> | ||
/// Enumerates all current routes. This is thread safe but the collection may change mid enumeration if the configuration is reloaded. | ||
/// </summary> | ||
IEnumerable<RouteModel> GetRoutes(); | ||
|
||
/// <summary> | ||
/// Retrieves a specific cluster by id, if present. | ||
/// </summary> | ||
bool TryGetCluster(string id, [NotNullWhen(true)] out ClusterState? cluster); | ||
|
||
/// <summary> | ||
/// Enumerates all current clusters. This is thread safe but the collection may change mid enumeration if the configuration is reloaded. | ||
/// </summary> | ||
IEnumerable<ClusterState> GetClusters(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
53 changes: 53 additions & 0 deletions
53
test/ReverseProxy.Tests/Model/HttpContextFeaturesExtensions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
// Copyright (c) Microsoft Corporation. | ||
// Licensed under the MIT License. | ||
|
||
using System.Net.Http; | ||
using Microsoft.AspNetCore.Http; | ||
using Xunit; | ||
using Yarp.ReverseProxy.Configuration; | ||
using Yarp.ReverseProxy.Forwarder; | ||
|
||
namespace Yarp.ReverseProxy.Model.Tests; | ||
|
||
public class HttpContextFeaturesExtensions | ||
{ | ||
[Fact] | ||
public void ReassignProxyRequest_Success() | ||
{ | ||
var client = new HttpMessageInvoker(new SocketsHttpHandler()); | ||
var context = new DefaultHttpContext(); | ||
var d1 = new DestinationState("d1"); | ||
var d2 = new DestinationState("d2"); | ||
var cc1 = new ClusterConfig() { ClusterId = "c1" }; | ||
var cm1 = new ClusterModel(cc1, client); | ||
var cs1 = new ClusterState("c1") { Model = cm1 }; | ||
var r1 = new RouteModel(new RouteConfig() { RouteId = "r1" }, cs1, HttpTransformer.Empty); | ||
var feature = new ReverseProxyFeature() | ||
{ | ||
AllDestinations = d1, | ||
AvailableDestinations = d1, | ||
Cluster = cm1, | ||
Route = r1, | ||
ProxiedDestination = d1, | ||
}; | ||
|
||
context.Features.Set<IReverseProxyFeature>(feature); | ||
|
||
var cc2 = new ClusterConfig() { ClusterId = "cc2" }; | ||
var cm2 = new ClusterModel(cc2, client); | ||
var cs2 = new ClusterState("cs2") | ||
{ | ||
DestinationsState = new ClusterDestinationsState(d2, d2), | ||
Model = cm2, | ||
}; | ||
context.ReassignProxyRequest(cs2); | ||
|
||
var newFeature = context.GetReverseProxyFeature(); | ||
Assert.NotSame(feature, newFeature); | ||
Assert.Same(d2, newFeature.AllDestinations); | ||
Assert.Same(d2, newFeature.AvailableDestinations); | ||
Assert.Same(d1, newFeature.ProxiedDestination); // Copied unmodified. | ||
Assert.Same(cm2, newFeature.Cluster); | ||
Assert.Same(r1, newFeature.Route); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters