Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for adding metadata to proxy routes #328

Merged
9 commits merged into from
Dec 15, 2020

Conversation

Kahbazi
Copy link
Collaborator

@Kahbazi Kahbazi commented Jul 15, 2020

Partially Fixes #286

I changed the way endpoints are being built. I tried to mirror MVC, and now endpoints are being built in ProxyDynamicEndpointDataSource instead of RuntimeRouteBuilder.

I think the main challenge is with ProxyConfigLoader which is a IHostedService and runs before MapReverseProxy has a chance to add metadata, so I add a bool firstTime parameter and pass in around , so I only update RouteConfig and not building the endpoints.

I want feedback for design before I continue with unit tests.

@Tratcher
Copy link
Member

I hadn't realized this would be so complex a change. It's going to take me some time to go over this.

The firstTime parameter is concerning, it seems like a blunt way to address a race condition. The design changes proposed in #277 should remove this race because they reverse the model from push to pull. I think we'll want to do #227 before continuing work on this PR.

@Tratcher Tratcher self-assigned this Jul 16, 2020
@Kahbazi
Copy link
Collaborator Author

Kahbazi commented Jul 16, 2020

I don't like the firstTime either, I just wanted to make it work.
Overall I didn't understand the need for the IHostedService for reloading the config. Can't it be done with just IOptionsMonitor.OnChange(...)?

@Tratcher
Copy link
Member

I don't like the firstTime either, I just wanted to make it work.
Overall I didn't understand the need for the IHostedService for reloading the config. Can't it be done with just IOptionsMonitor.OnChange(...)?

But where would you call OnChange?

@Kahbazi
Copy link
Collaborator Author

Kahbazi commented Jul 16, 2020

But where would you call OnChange?

Maybe in LoadFromConfig() but then somehow it should also run for the first time.

@Tratcher
Copy link
Member

But where would you call OnChange?

Maybe in LoadFromConfig() but then somehow it should also run for the first time.

That's the trick, LoadFromConfig doesn't have access to IOptionsMonitor, the DI container hasn't been built yet. Something has to resolve IOptionsMonitor from DI at startup in order to kick of the process. The IHostedService is one way of doing that, but #227 should be able to replace that with a direct config source resolved from DI.

@Tratcher
Copy link
Member

@Kahbazi can you please rebase this?

@Kahbazi Kahbazi force-pushed the kahbazi/EndpointConvention branch from 31069c4 to 8b1590f Compare August 25, 2020 19:54
@Kahbazi
Copy link
Collaborator Author

Kahbazi commented Aug 25, 2020

I did the rebase. I need some feedback and then I will fix and add tests.

@Tratcher
Copy link
Member

There's another merge conflict?

@Kahbazi Kahbazi force-pushed the kahbazi/EndpointConvention branch from 8b1590f to 19a7313 Compare September 22, 2020 13:22
@Kahbazi Kahbazi marked this pull request as ready for review September 22, 2020 14:26
@davidfowl
Copy link
Contributor

I think we're going to need an explanation of the overall design that comes out of this change since it looks like it's evolved a bunch from the original purpose of the PR.

@Kahbazi
Copy link
Collaborator Author

Kahbazi commented Oct 17, 2020

The purpose of this PR is to enable users to add their own Metadata to Endpoints that are created by YARP and it can be done by ReverseProxyConventionBuilder .

- void MapReverseProxy(...)
+ ReverseProxyConventionBuilder MapReverseProxy(...)

The challenge was that when calling MapReverseProxy() it will eventually call IRuntimeRouteBuilder.Build() and it both creates the RouteConfig and Endpoint. So there's no chance for the user convention to be applied to the Endpoint.
So I introcude ProxyEndpointFactory and move the responsibility of creating Endpoint from IRuntimeRouteBuilder to it, so the Endpoint could be created at a later point in time.
For the same reason I change the ProxyConfigManager.Endpoints getter from simply returning the Endpoint to Initializing the Endpoint first and then return them.

This is the API that user can use to add Metadata.

public class ReverseProxyConventionBuilder
{
    // Configure all routes
    public void Add(Action<EndpointBuilder> convention); 
    // Configure all routes based on their Cluster
    public ReverseProxyConventionBuilder ConfigureClusters(Action<IEndpointConventionBuilder, Cluster> convention);
    // Configure all routes based on their Route
    public ReverseProxyConventionBuilder ConfigureRoutes(Action<IEndpointConventionBuilder, ProxyRoute> convention);
    // Configure all routes for a specific Cluster
    public ReverseProxyConventionBuilder ConfigureCluster(string clusterId, Action<IEndpointConventionBuilder> convention);
    // Configure a specific Route
    public ReverseProxyConventionBuilder ConfigureRoute(string routeId, Action<IEndpointConventionBuilder> convention);
}

@davidfowl
Copy link
Contributor

I like the design. Do those configure callbacks get run every time the configuration changes?

@Kahbazi
Copy link
Collaborator Author

Kahbazi commented Oct 19, 2020

I like the design. Do those configure callbacks get run every time the configuration changes?

If the configuration changes causes a change on routes.

var hasChanged = await ApplyConfigAsync(newConfig);
if (hasChanged)
{
CreateEndpoints();
}

private void CreateEndpoints()
{
var endpoints = new List<Endpoint>();
foreach (var existingRoute in _routeManager.GetItems())
{
var runtimeConfig = existingRoute.Config.Value;
_proxyEndpointFactory.AddEndpoint(endpoints, runtimeConfig, _conventions);
}
UpdateEndpoints(endpoints);
}

src/ReverseProxy/Service/Config/RuntimeRouteBuilder.cs Outdated Show resolved Hide resolved
Comment on lines 168 to 192
var hasChanged = await ApplyConfigAsync(newConfig);
if (hasChanged)
{
CreateEndpoints();
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If an individual route or cluster changes we shouldn't have to re-create every endpoint, only those that changed. That's why the endpoints were stashed on the RouteConfig before. What gets recreated is the list of endpoints.

There are callbacks to configure an endpoint route based on cluster id or data, but this doesn't detect changes to clusters so those wouldn't re-run unless the route itself changed.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where do you suggest we should stash the endpoints now that RouteConfig is too early to create the endpoints?

Also do we need to re-create the endpoints of a cluster if its destinations changed?

@Kahbazi Kahbazi force-pushed the kahbazi/EndpointConvention branch from 2a6a87b to 007fdb0 Compare November 13, 2020 13:39

var cluster = routeConfig.Cluster?.Config.Cluster;
var proxyRoute = routeConfig.ProxyRoute;
if (proxyRoute != null && cluster != null)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess it's technically possible for a route not to be assigned to a cluster, but why would proxyRoute ever be null?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed the null check for proxy route

}

/// <summary>
/// Configures the endpoint for all routes
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// Configures the endpoint for all routes
/// Configures the endpoints for all routes

/// <param name="routeId">The route id to aplly convention to its endpoint.</param>
/// <param name="convention">The convention to add to the builder.</param>
/// <returns></returns>
public ReverseProxyConventionBuilder ConfigureRoute(string routeId, Action<IEndpointConventionBuilder> convention)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The experience question we need to answer is how common you'll write code to configure a route or cluster by name vs inspecting all the endpoints and reacting to other fields like metadata. i.e. do we need all 9 of these overloads, or only the first three Configure ones? The cluster and route specific ones can be built on top of the first three Configure conventions if needed.

I'd suggest limiting it to just the Configure overloads for now. We can revisit as we see demand for common variations.

@Tratcher Tratcher force-pushed the kahbazi/EndpointConvention branch from 7160a75 to c24e188 Compare December 8, 2020 18:27
Copy link
Member

@Tratcher Tratcher left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Design update:
MapReverseProxy now returns a ReverseProxyConventionBuilder : IEndpointConventionBuilder. This has a new Configure API with three overloads. The purpose of the API is to let you provide one or more conventions that examine all of the routes loaded from config and append any custom endpoint metadata those routes need.

E.g. A convention could check for all routes that use cluster1 and set AllowAnonymous auth for those endpoints, where auth is normally configured per route. A developer could also insert their own endpoint metadata based on route or cluster extensible metadata fields.

New API:

Configure(Action<IEndpointConventionBuilder> convention)
Configure(Action<IEndpointConventionBuilder, ProxyRoute> convention)
Configure(Action<IEndpointConventionBuilder, ProxyRoute, Cluster> convention)

Clarifications from the prior discussion:

  • The ProxyRoute and Cluster types are given here for information purposes, they're not modifiable at this point the the lifecycle. Use freezable pattern for contract types instead of deep cloning #179 tracks clarifying that.
  • We don't want to define new routes in Startup, we want routes to be sourced from an external store so they can always be updated without restarting.
  • When routes or clusters are updated, the associated endpoints are recreated and the conventions re-run.
  • The overloads that reference individual routes or clusters by name have been removed. While it's possible to make decisions based on names, I don't think that's the primary use case, but rather to use config metadata from the routes and clusters.

@Tratcher Tratcher requested review from Tratcher and removed request for Tratcher December 8, 2020 23:14
Copy link
Member

@JamesNK JamesNK left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor comments

@Tratcher Tratcher force-pushed the kahbazi/EndpointConvention branch from a13539b to f4c21d0 Compare December 15, 2020 21:01
@Tratcher Tratcher added this to the YARP 1.0.0-preview8 milestone Dec 15, 2020
@Tratcher Tratcher added the Auto-Merge Apply this label and the msftbot will auto-merge this PR when it is eligible for merge label Dec 15, 2020
@ghost
Copy link

ghost commented Dec 15, 2020

Hello @Tratcher!

Because this pull request has the Auto-Merge label, I will be glad to assist with helping to merge this pull request once all check-in policies pass.

p.s. you can customize the way I help with merging this pull request, such as holding this pull request until a specific person approves. Simply @mention me (@msftbot) and give me an instruction to get started! Learn more here.

@ghost ghost merged commit 71fc0df into microsoft:master Dec 15, 2020
@Kahbazi Kahbazi deleted the kahbazi/EndpointConvention branch December 15, 2020 21:30
@Tratcher
Copy link
Member

@Kahbazi thanks for the hard work on this one, I'm glad we were able to work through it eventually.

This pull request was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Auto-Merge Apply this label and the msftbot will auto-merge this PR when it is eligible for merge
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Custom routing endpoint metadata
5 participants