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 Azure Event Hubs #2870

Merged
merged 56 commits into from
Mar 27, 2024
Merged

Conversation

oising
Copy link
Contributor

@oising oising commented Mar 13, 2024

Initial support for Azure Event Hubs, as per #1147

  • Minimally adapt the Service Bus component and resource for Event Hubs
  • Add top-level support for EventHubProducerClient
  • Add top-level support for EventHubConsumerClient
  • Add top-level support for EventProcessorClient
    • Allow providing component name for a BlobContainerClient to use (read connection string and instantiate local instance)
    • Read BlobContainerClient from keyed DI using its Aspire connection name
  • Add top-level support for PartitionReceiver
  • Add playground sample AppHost and projects (modify davidfowl's test project)
  • Add ability to configure credential used in Azure Provisioner (via AzureProvisionCredential in AppHost configuration)
  • Add xmldoc documentation for components etc
  • Update README

Optional extras (may defer to a second PR)

  • Add support for chained AddHub(...) calls for top-level EH resource to allow variable config of hubs
    • Implement as child resource to allow deep-linking (i.e. provide connection string including EntityPath)
  • Implement Microsoft.Extensions.Azure style top-level component AddEventHubs(...) and hang clients configuration from clientBuilder
  • Add Streaming sample with Orleans

Aspire API shortcomings

EventProcessorClient requires a BlobContainerClient for managing EH checkpoints. Ideally we should be able to pass a connection name to a mounted aspire blob client component and retrieve the instance from the service provider, but there is no plumbing to get it in the component infra. The unbuilt service collection is available on the distributed builder and it could be fished out of there, assuming it was added before the current component, but this seems flakey. Instead, I'm creating a new blob client using the blob connection name (taken from user-supplied settings) and creating my own.

Microsoft Reviewers: Open in CodeFlow

@dotnet-issue-labeler dotnet-issue-labeler bot added the area-integrations Issues pertaining to Aspire Integrations packages label Mar 13, 2024
@dotnet-policy-service dotnet-policy-service bot added the community-contribution Indicates that the PR has been added by a community member label Mar 13, 2024
@davidfowl
Copy link
Member

The azure provisioning stuff here is outdated so I would just delete all of it since it isn't required.

@davidfowl
Copy link
Member

This entire PR seems based on a really old version of aspire. Everything is bicep based now.

@oising
Copy link
Contributor Author

oising commented Mar 14, 2024

This entire PR seems based on a really old version of aspire. Everything is bicep based now.

I think your definition of "really old" doesn't match with mine :) I'm working off of main, and I cloned barely a week ago? I'll update it.

@davidfowl
Copy link
Member

davidfowl commented Mar 14, 2024

I'm not sure what happened in this PR, but if you look at main the Aspire.Hosting.Azure.Provisioning package has been deleted... 😄

#2603

Somehow this brought it back.

@oising
Copy link
Contributor Author

oising commented Mar 14, 2024

I'm not sure what happened in this PR, but if you look at main the Aspire.Hosting.Azure.Provisioning package has been deleted... 😄

#2603

Somehow this brought it back.

I'll start from scratch -- tbh, this first pass was just about understanding how components and resources work (in this week's build lol)

@oising
Copy link
Contributor Author

oising commented Mar 15, 2024

Restarting for the third time, because of #2875

Done.

…onents); modified davidfowl's demo EH project to use new resources and components; bugfixes;
@davidfowl
Copy link
Member

@sebastienros @eerhardt for the component review
@mitchdenny @JoshLove-msft for the resource review. CDK codegen is also required here.

@davidfowl
Copy link
Member

Also @jsquire for azure sdk review

@oising
Copy link
Contributor Author

oising commented Mar 17, 2024

I rebased to stay up to date and now I'm hitting:

Application orchestrator dependency check had an unexpected error System.TimeoutException: The operation has timed out.
at Aspire.Hosting.Dcp.DcpDependencyCheck.EnsureDcpDependenciesAsync(CancellationToken cancellationToken) in C:\p\github\aspire\src\Aspire.Hosting\Dcp\DcpDependencyCheck.cs:line 67

on start up.

@davidfowl
Copy link
Member

Application orchestrator dependency check had an unexpected error System.TimeoutException: The operation has timed out.
at Aspire.Hosting.Dcp.DcpDependencyCheck.EnsureDcpDependenciesAsync(CancellationToken cancellationToken) in C:\p\github\aspire\src\Aspire.Hosting\Dcp\DcpDependencyCheck.cs:line 67

Consistently?

@oising
Copy link
Contributor Author

oising commented Mar 17, 2024

Application orchestrator dependency check had an unexpected error System.TimeoutException: The operation has timed out.
at Aspire.Hosting.Dcp.DcpDependencyCheck.EnsureDcpDependenciesAsync(CancellationToken cancellationToken) in C:\p\github\aspire\src\Aspire.Hosting\Dcp\DcpDependencyCheck.cs:line 67

Consistently?

Yes. I tried bumping the timeout to 5 mins and at some point something else timed out and brought the whole process down. I tried disabling the timeout (setting to 0) and same thing. Tried about a half dozen times.

edit: resolved, see #3003

No need to construct this (we recently changed this in the others).
The following example assumes that you have an Azure Event Hubs namespace and an Event Hub created and wish to configure an `EventHubProducerClient` to send events to the Event Hub. The `EventHubConsumerClient`, `EventProcessorClient`, and `PartitionReceiver`are configured in a similar manner.

In the _Program.cs_ file of your project, call the `AddAzureEventHubProducerClient` extension method to register
a `EventHubProducerClient` for use via the dependency injection container. The method takes a connection name parameter. This assumes you have included the `EntityPath` in the connection string to specify the Event Hub name.
Copy link
Member

Choose a reason for hiding this comment

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

This assumes you have included the EntityPath in the connection string to specify the Event Hub name.

This doesn't seem ideal, since we say we don't recommend using a connection string.

Copy link
Member

Choose a reason for hiding this comment

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

It is also surprising since I would have expected:

var eventHub = builder.AddAzureEventHubs("eventhubns")
    .AddEventHub("hub");

builder.AddProject<Projects.EventHubsApi>("api")
    .WithReference(eventHub);

To result with the EntityPath in the connection string (just like it does if I'm using a database server + database, the connection string has Database=<db_name> in the connection string.

Is the above code supposed to use EntityPath in the connection string?

Copy link
Contributor Author

@oising oising Mar 26, 2024

Choose a reason for hiding this comment

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

@mitchdenny Brought this up already and it would mean making the "hub" it's own de-facto AspireResource to enable deep-linking like this. I replied and said the same could be said for allowing deep-linking of ASB topic and queues (a feature that isn't there either) so I think this could be punted for another release. But yep, you're right -- AEH and ASB should do like MSSQL does. But I don't have time to get this done for this release. I'm happy to improve it for P6.

As you (or maybe someone else said?) before, while we do control what Resources inject as connection strings when using an AppHost, we don't control what developers may do when managing their own connection strings. I'd rather that we "do the right thing" when a dev passes an entitypath. It seems like the path of least surprise. It's what I would expect.

Copy link
Member

Choose a reason for hiding this comment

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

we don't control what developers may do when managing their own connection strings. I'd rather that we "do the right thing" when a dev passes an entitypath. It seems like the path of least surprise. It's what I would expect.

I don't disagree that we should "do the right thing" when someone passes an entitypath.

The part I was commenting on is the "Usage example" the README gives assumes you are doing something we don't recommend you do - use a connection string.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, gotcha!

/// </summary>
/// <param name="builder">The Azure Event Hubs resource builder.</param>
/// <param name="name">The name of the Event Hub.</param>
public static IResourceBuilder<AzureEventHubsResource> AddEventHub(this IResourceBuilder<AzureEventHubsResource> builder, string name)
Copy link
Member

Choose a reason for hiding this comment

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

This API pattern differs from how databases work. I think it will be surprising to our customers.

In databases, we have:

var db = builder.AddPostgres("db-server").AddDatabase("db1");

builder.AddProject<Projects.Api>("api")
    .WithReference(db);

When executing that code, db points to the db1 resource - a "child" of the db-server. So when you .WithReference(db), the connection string name that you use in your app is going to be db1.

But here,

var hub = builder.AddEventHubs("eventhub-ns").AddEventHub("hub");

builder.AddProject<Projects.Api>("api")
    .WithReference(hub);

hub is going to point to the eventhub-ns resource, and not the hub resource. So in your web app, you need to use builder.AddAzureEventHubProducerClient("eventhub-ns"), which is different than how databases work.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agree 100% -- see my comment above. The service bus resource is equally deficient, and I copied that code. I'd want to see this fixes for both ASB and AEH in P6. /cc @mitchdenny

Copy link
Member

Choose a reason for hiding this comment

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

Does this work with managed identity?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Does this work with managed identity?

Did you mean to put this here, @davidfowl ? If so, I don't see why not?

@eerhardt
Copy link
Member

FYI @oising, I pushed some changes you should be aware of. The most important is that I fixed Tracing to work. Just wanted to let you know so you don't force push and revert those changes.

I think this is close to getting the initial merge. My most major concerns are:

  • Getting the Storage Blob client. I really think this should come from DI
  • The Settings class having properties that don't work depending on the client being used.
  • Full testing

If we want to merge this soon to guarantee it gets into preview5, and log issues for these to be followed up, I would be OK with that. FYI @davidfowl

@oising
Copy link
Contributor Author

oising commented Mar 26, 2024

FYI @oising, I pushed some changes you should be aware of. The most important is that I fixed Tracing to work. Just wanted to let you know so you don't force push and revert those changes.

I think this is close to getting the initial merge. My most major concerns are:

  • Getting the Storage Blob client. I really think this should come from DI
  • The Settings class having properties that don't work depending on the client being used.
  • Full testing

If we want to merge this soon to guarantee it gets into preview5, and log issues for these to be followed up, I would be OK with that. FYI @davidfowl

Thanks @eerhardt -- I really appreciate the thorough review and insightful suggestions :) I'm about to check in the suggested split settings approach as discussed above. It took a bit of jiggery-pokery with generics to fix a few things, but it seems to be good now.

As for the storage blob client, yes, I agree this is a bit of a rough edge; especially around the lack of unit tests here. As mentioned, the service provider isn't built at this point, but I could build it and look for a blob client within. I think this needs some configuration-by-convention love in the sense that we should probably only permit a keyed blob client so it can be configured in settings. Also, to test the logic around the container creation, I might want to lightly shim the blob client so I can unit test the behaviour without requiring a live connection. I'll spike this tomorrow.

@eerhardt
Copy link
Member

As mentioned, the service provider isn't built at this point, but I could build it and look for a blob client within.

This isn't necessary. We should just be able to call a different overload on the AzureClientFactoryBuilder, which gives us the IServiceProvider. See https://github.com/Azure/azure-sdk-for-net/blob/87d426597b261b88f8a6c6c21fab6031f857fb70/sdk/extensions/Microsoft.Extensions.Azure/src/AzureClientFactoryBuilder.cs#L164-L172. (Note it would take some refactoring of the base AzureComponent<TSettings, TClient, TClientOptions> class, as right now the abstract AddClient method takes a TBuilder, when it could just be passed an AzureClientFactoryBuilder.

I think this needs some configuration-by-convention love in the sense that we should probably only permit a keyed blob client so it can be configured in settings.

I see it a different way. It should allow for a keyed blob client, but shouldn't require it to be keyed. If no "blob service key" was configured, it should just get the BlobServiceClient from DI. If a "blob service key" was specified, then use it to get the BlobServiceClient from DI.

That way the simple case "just works". You just need to:

var builder = WebApplication.CreateBuilder(args);

builder.AddAzureBlobClient("blobConnectionName");
builder.AddAzureEventProcessorClient("eventhubns");

and the EventProcessorClient grabs the BlobClient. There's no need to force someone to use keys when there is only one.

- Remove unnecessary Directory.Packages.props entry
- Fix ConfigurationSchema.json to match implementation
- StringComparison.Ordinal is unnecessary when looking for a char
Copy link
Member

@eerhardt eerhardt left a comment

Choose a reason for hiding this comment

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

Thank you, @oising, for this contribution! Great work!

This LGTM to merge for preview5.

Can you log follow up issues for the remaining work? Notably:

  • Using DI to get the blob client in the EventProcessor
  • Testing
  • (I might have missed others. Please ensure it is all logged.)

@eerhardt eerhardt enabled auto-merge (squash) March 27, 2024 16:14
@eerhardt eerhardt merged commit 795a489 into dotnet:main Mar 27, 2024
8 checks passed
eerhardt added a commit to eerhardt/aspire that referenced this pull request Mar 27, 2024
* initial commit for event hubs component and resource (non-functional, but skeleton is there)

* corrected roledefinition

* added consumer client (now have producer, consumer and processor components); modified davidfowl's demo EH project to use new resources and components; bugfixes;

* fixes for processor client; updated build props/targets for playground project

* Update AzureEventHubsExtensions.cs

No need to construct this (we recently changed this in the others).

* some minor refactoring of processor client; made playground project more robust

* address review items; automatically generate processor identifier from hub and consumergroup; add more comments; minor refactoring

* Update src/Aspire.Hosting.Azure/Extensions/AzureEventHubsExtensions.cs

Co-authored-by: Jesse Squire <[email protected]>

* Update src/Aspire.Hosting.Azure/Extensions/AzureEventHubsExtensions.cs

Co-authored-by: Jesse Squire <[email protected]>

* update EH endpoint references to use eventHubsEndpoint instead of serviceBusEndpoint; beef up processor identifier naming.

* add ability to configure credential for azure provisioner (removed my hack); added first batch of documentation for component; added partitionreceiver component.

* Update src/Components/Aspire.Azure.Messaging.EventHubs/AzureMessagingEventHubsSettings.cs

Co-authored-by: Jesse Squire <[email protected]>

* remove azure provisioner credential configuration stuff (will go into separate PR)

* address review points; refactor namespace parsing into base eh component

* fix package tags and add new icon to shared

* remove providercredential reference from config

* fix some more errant service bus mentions and regenerate configurationschema

* fix xmlcomment on settings

* remove azure section from sample config

* update components README.md

* delete EH playground readme

* minor edits for clarification

* port ASB connection/namespace tests; rewrite validation logic to be more robust

* Update playground/AspireEventHub/EventHubs.AppHost/EventHubs.AppHost.csproj

Co-authored-by: Eric Erhardt <[email protected]>

* Update playground/AspireEventHub/EventHubsConsumer/EventHubsConsumer.csproj

Co-authored-by: Eric Erhardt <[email protected]>

* Update src/Aspire.Hosting.Azure.EventHubs/Aspire.Hosting.Azure.EventHubs.csproj

Co-authored-by: Eric Erhardt <[email protected]>

* generate aspire manifest etc for EH sample

* moved processor Start code into Execute for worker sample

* fixed another ref to service bus

* refine checkpoint blob container creation logic to avoid unnecessary permission demand if we can

* enhance logic for blob checkpoint container; add BlobContainerName to settings as an option

* persist checkpoints in processor sample for EH

* renamed settings class in tests to match AEH; was ASB.

* Fix Tracing with EventHubs
Update Telemetry and Components Progress docs
Minor cleanup feedback

* refactor EH client settings into individual classes

* Add readme for EH hosting

* Fix build

* Address PR feedback

- Remove unnecessary Directory.Packages.props entry
- Fix ConfigurationSchema.json to match implementation
- StringComparison.Ordinal is unnecessary when looking for a char

---------

Co-authored-by: Mitch Denny <[email protected]>
Co-authored-by: Jesse Squire <[email protected]>
Co-authored-by: Eric Erhardt <[email protected]>
davidfowl pushed a commit that referenced this pull request Mar 27, 2024
* initial commit for event hubs component and resource (non-functional, but skeleton is there)

* corrected roledefinition

* added consumer client (now have producer, consumer and processor components); modified davidfowl's demo EH project to use new resources and components; bugfixes;

* fixes for processor client; updated build props/targets for playground project

* Update AzureEventHubsExtensions.cs

No need to construct this (we recently changed this in the others).

* some minor refactoring of processor client; made playground project more robust

* address review items; automatically generate processor identifier from hub and consumergroup; add more comments; minor refactoring

* Update src/Aspire.Hosting.Azure/Extensions/AzureEventHubsExtensions.cs



* Update src/Aspire.Hosting.Azure/Extensions/AzureEventHubsExtensions.cs



* update EH endpoint references to use eventHubsEndpoint instead of serviceBusEndpoint; beef up processor identifier naming.

* add ability to configure credential for azure provisioner (removed my hack); added first batch of documentation for component; added partitionreceiver component.

* Update src/Components/Aspire.Azure.Messaging.EventHubs/AzureMessagingEventHubsSettings.cs



* remove azure provisioner credential configuration stuff (will go into separate PR)

* address review points; refactor namespace parsing into base eh component

* fix package tags and add new icon to shared

* remove providercredential reference from config

* fix some more errant service bus mentions and regenerate configurationschema

* fix xmlcomment on settings

* remove azure section from sample config

* update components README.md

* delete EH playground readme

* minor edits for clarification

* port ASB connection/namespace tests; rewrite validation logic to be more robust

* Update playground/AspireEventHub/EventHubs.AppHost/EventHubs.AppHost.csproj



* Update playground/AspireEventHub/EventHubsConsumer/EventHubsConsumer.csproj



* Update src/Aspire.Hosting.Azure.EventHubs/Aspire.Hosting.Azure.EventHubs.csproj



* generate aspire manifest etc for EH sample

* moved processor Start code into Execute for worker sample

* fixed another ref to service bus

* refine checkpoint blob container creation logic to avoid unnecessary permission demand if we can

* enhance logic for blob checkpoint container; add BlobContainerName to settings as an option

* persist checkpoints in processor sample for EH

* renamed settings class in tests to match AEH; was ASB.

* Fix Tracing with EventHubs
Update Telemetry and Components Progress docs
Minor cleanup feedback

* refactor EH client settings into individual classes

* Add readme for EH hosting

* Fix build

* Address PR feedback

- Remove unnecessary Directory.Packages.props entry
- Fix ConfigurationSchema.json to match implementation
- StringComparison.Ordinal is unnecessary when looking for a char

---------

Co-authored-by: Oisin Grehan <[email protected]>
Co-authored-by: Mitch Denny <[email protected]>
Co-authored-by: Jesse Squire <[email protected]>
@oising oising deleted the support-eventhubs branch March 27, 2024 20:35
@davidfowl
Copy link
Member

Thank you @oising !!!!!

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-integrations Issues pertaining to Aspire Integrations packages community-contribution Indicates that the PR has been added by a community member
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants