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

durable task scheduler auth extension support #362

Open
wants to merge 15 commits into
base: main
Choose a base branch
from

Conversation

YunchuWang
Copy link
Member

This pull request introduces a new set of Azure extensions for the Durable Task Framework, including project setup, implementation of core classes and methods, and unit tests. The most important changes include adding new projects to the solution, defining the core classes and methods for Azure integration, and creating unit tests to ensure the functionality of the new extensions.

Project Setup:

  • Added new projects Extensions and Azure to the solution Microsoft.DurableTask.sln and configured their build settings. [1] [2] [3]

Core Implementation:

  • Created the Azure.csproj file to define the Azure extensions project, including dependencies and target frameworks.
  • Implemented the DurableTaskSchedulerConnectionString class to parse and manage connection strings for the Durable Task Scheduler service.
  • Added DurableTaskSchedulerExtensions class with methods to configure Durable Task Worker and Client to use the Durable Task Scheduler service.
  • Defined DurableTaskSchedulerOptions class to encapsulate configuration options for the Durable Task Scheduler.

Unit Tests:

  • Created unit tests in DurableTaskSchedulerConnectionStringTests.cs to validate the parsing and handling of connection strings.

Copy link
Member

@cgillum cgillum left a comment

Choose a reason for hiding this comment

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

Looking great! I added some comments.

@cgillum
Copy link
Member

cgillum commented Jan 9, 2025

@jviau would you mind taking a look at this as well?

The main feedback I'm interested in from you is the package name/organization. The idea behind this PR is to introduce a mechanism for configuring the gRPC channels to work with the Durable Task Scheduler (DTS). This includes setting up the appropriate auth and some other service-specific headers (like the task hub name). I figured this might be the right time to introduce an "Extensions" concept and make "Azure" the extension consumers should use for talking to DTS. There could be others in the future. WDYT?

@YunchuWang YunchuWang requested a review from cgillum January 9, 2025 10:05

static GrpcChannel GetGrpcChannelForOptions(DurableTaskSchedulerOptions options)
{
if (string.IsNullOrEmpty(options.EndpointAddress))
Copy link
Member

Choose a reason for hiding this comment

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

We have a set of helper methods for this, Check.Argument(...)


if (!endpoint.Contains("://"))
{
endpoint = $"https://{endpoint}";
Copy link
Member

Choose a reason for hiding this comment

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

Is performing this kind of manipulation standard practice? Asking because I wonder if users would be confused by https:// being appended?

Copy link
Member

Choose a reason for hiding this comment

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

Not sure what the best practice is, but we did this because a ton of people were making mistakes in terms of whether or not to include the https:// prefix, so we decided to just support both.

Copy link
Member

Choose a reason for hiding this comment

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

Understood. I wonder if we can move the manipulation to the options object, so it will prepend the https:// in the setter of Endpoint.

return new ArgumentException(message: $"Required option '{optionName}' was not provided.");
}

sealed class TokenCache(TokenCredential credential, TokenRequestContext context, TimeSpan margin)
Copy link
Member

Choose a reason for hiding this comment

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

Did you look into Azure.Identity library to see if they already do token caching?

Copy link
Member

Choose a reason for hiding this comment

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

builder.UseGrpc(GetGrpcChannelForOptions(options));
}

static GrpcChannel GetGrpcChannelForOptions(DurableTaskSchedulerOptions options)
Copy link
Member

Choose a reason for hiding this comment

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

Small suggestion: I like putting options-projecting methods like this on the options object itself (as internal).


string resourceId = options.ResourceId ?? "https://durabletask.io";
int processId = Environment.ProcessId;
string workerId = options.WorkerId ?? $"{Environment.MachineName},{processId},{Guid.NewGuid():N}";
Copy link
Member

Choose a reason for hiding this comment

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

Is there any formality to worker ID creation? Also, I recommend putting this default-logic into the options object.

Copy link
Member

Choose a reason for hiding this comment

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

No formality yet. We expect to iterate on this over time.

EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure", "src\Extensions\Azure\Azure.csproj", "{662BF73D-A4DD-4910-8625-7C12F1ACDBEC}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Extensions.Azure.Tests", "test\Extensions.Azure.Tests\Extensions.Azure.Tests.csproj", "{DBB5DB4E-A1B0-4C86-A233-213789C46929}"
Copy link
Member

Choose a reason for hiding this comment

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

Mirror src layout in test please. So, this project should be in test/Extensions/Azure.Tests.csproj


<ItemGroup>
<PackageReference Include="Azure.Identity" Version="1.13.1" />
<PackageReference Include="Grpc.Net.Client" Version="2.67.0" />
Copy link
Member

Choose a reason for hiding this comment

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

There is also a package Grpc.Net.ClientFactory which adds constructing grpc clients via DI. Have you evaluated if that is applicable here? I think one of the major benefits to it is how the underlying HttpClient is managed. Not saying it is mandatory, but want to make sure it is evaluated and either adopted or ruled out.

/// Gets or sets the resource ID of the Durable Task Scheduler resource.
/// The default value is https://durabletask.io.
/// </summary>
public string? ResourceId { get; set; }
Copy link
Member

Choose a reason for hiding this comment

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

Is this needed, or can it be parsed from EndpointAddress?

How will this work in other clouds?

Copy link
Member

Choose a reason for hiding this comment

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

This is not part of the endpoint address. We could include it as part of the connection string, but I'm not sure yet whether/when we'll need to make this generally configurable. I suggest we not worry about this until we're actually ready to test other clouds.

Copy link
Member

Choose a reason for hiding this comment

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

Oh it isn't part of the address? So endpoints aren't of the form {my-resource}.{region}.durabletask.io?

Copy link
Member

Choose a reason for hiding this comment

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

No - this is an OAuth 2.0 resource ID. It represents the entire DTS service owned by us. It's not a customer-owned scheduler resource.

{
DurableTaskSchedulerOptions options = new(endpointAddress, taskHubName, credential);

configure?.Invoke(options);
Copy link
Member

Choose a reason for hiding this comment

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

This class should leverage options configuration instead of directly calling configure?.Invoke.

IE:

builder.Services.Configure(builder.Name, configure);

You will then need to make some adjustments to UseGrpc call:

// method body
if (configure is not null)
{
    builder.Services.Configure(builder.Name, configure); // from above, to configure this options object
}

builder.Services.TryAddEnumerable(
    ServiceDescription.Singleton<IConfigureOptions<GrpcDurableTaskWorkerOptions>, ConfigureGrpcChannel>());
builder.UseGrpc(_ => { }); // empty callback as the line above does the work. This call is to get the build target registered.
// end method body

// helper internal class
class ConfigureGrpcChannel(IOptionsMonitor<DurableTaskSchedulerOptions> schedulerOptions) : IConfigureNamedOptions<GrpcDurableTaskWorkerOptions>
{
    public void Configure(GrpcDurableTaskWorkerOptions options) => this.Configure(Options.DefaultName, options);

    public void Configure(string name, GrpcDurableTaskWorkerOptions options)
    {
        DurableTaskSchedulerOptions source = schedulerOptions.Get(name);
        options.Channel = source.CreateChannel(); // method added to options object.
    }
}

The benefit here is now users can call chain and they can use options configuration directly.

Copy link
Member

Choose a reason for hiding this comment

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

Want to also mention that this is an important change. It will allow for more flexible configuration of this, such as consuming configuration from more varied sources (such as Azure AppConfiguration). With this, an advanced user could write the following code:

builder.Services.AddOptions<DurableTaskSchedulerOptions>(builder.Name)
    .Configure<IConfiguration>((options, config) => options.Endpoint = config["SomeConfigKeyThatHasMyEndpoint"]);

/// <summary>
/// Initializes a new instance of the <see cref="DurableTaskSchedulerOptions"/> class.
/// </summary>
internal DurableTaskSchedulerOptions(string endpointAddress, string taskHubName, TokenCredential? credential = null)
Copy link
Member

Choose a reason for hiding this comment

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

To follow options pattern, this will need a parameterless constructor. You can leverage options validation to ensure all required fields are set.

https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options?view=aspnetcore-9.0#options-validation

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants