diff --git a/src/Orleans.Core/Core/DefaultClientServices.cs b/src/Orleans.Core/Core/DefaultClientServices.cs index 69e1c74aa3d..4b7984a719b 100644 --- a/src/Orleans.Core/Core/DefaultClientServices.cs +++ b/src/Orleans.Core/Core/DefaultClientServices.cs @@ -1,31 +1,32 @@ #nullable enable -using Orleans.Configuration; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; using Microsoft.AspNetCore.Connections; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Options; +using Orleans.Configuration; using Orleans.Configuration.Internal; using Orleans.Configuration.Validators; using Orleans.GrainReferences; +using Orleans.Hosting; using Orleans.Messaging; using Orleans.Metadata; using Orleans.Networking.Shared; +using Orleans.Placement.Repartitioning; using Orleans.Providers; using Orleans.Runtime; using Orleans.Runtime.Messaging; using Orleans.Runtime.Versions; using Orleans.Serialization; -using Orleans.Statistics; -using Orleans.Serialization.Serializers; using Orleans.Serialization.Cloning; -using Microsoft.Extensions.Hosting; -using System.Collections.Generic; using Orleans.Serialization.Internal; -using System; -using Orleans.Hosting; -using System.Reflection; -using Microsoft.Extensions.Configuration; -using Orleans.Placement.Repartitioning; +using Orleans.Serialization.Serializers; +using Orleans.Statistics; namespace Orleans { @@ -217,7 +218,17 @@ static IProviderBuilder GetRequiredProvider(Dictionary<(string K ?? throw new InvalidOperationException($"{kind} provider, '{name}', of type {type}, does not implement {typeof(IProviderBuilder)}."); } - throw new InvalidOperationException($"Could not find {kind} provider named '{name}'. This can indicate that either the 'Microsoft.Orleans.Sdk' package the provider's package are not referenced by your application."); + var knownProvidersOfKind = knownProviderTypes + .Where(kvp => string.Equals(kvp.Key.Kind, kind, StringComparison.OrdinalIgnoreCase)) + .Select(kvp => kvp.Key.Name) + .OrderBy(n => n) + .ToList(); + + var knownProvidersMessage = knownProvidersOfKind.Count > 0 + ? $" Known {kind} providers: {string.Join(", ", knownProvidersOfKind)}." + : string.Empty; + + throw new InvalidOperationException($"Could not find {kind} provider named '{name}'. This can indicate that either the 'Microsoft.Orleans.Sdk' or the provider's package are not referenced by your application.{knownProvidersMessage}"); } static Dictionary<(string Kind, string Name), Type> GetRegisteredProviders() diff --git a/src/Orleans.Runtime/Hosting/DefaultSiloServices.cs b/src/Orleans.Runtime/Hosting/DefaultSiloServices.cs index 7df642d3263..5d981529b92 100644 --- a/src/Orleans.Runtime/Hosting/DefaultSiloServices.cs +++ b/src/Orleans.Runtime/Hosting/DefaultSiloServices.cs @@ -482,7 +482,17 @@ static IProviderBuilder GetRequiredProvider(Dictionary<(string Kin ?? throw new InvalidOperationException($"{kind} provider, '{name}', of type {type}, does not implement {typeof(IProviderBuilder)}."); } - throw new InvalidOperationException($"Could not find {kind} provider named '{name}'. This can indicate that either the 'Microsoft.Orleans.Sdk' or the provider's package are not referenced by your application."); + var knownProvidersOfKind = knownProviderTypes + .Where(kvp => string.Equals(kvp.Key.Kind, kind, StringComparison.OrdinalIgnoreCase)) + .Select(kvp => kvp.Key.Name) + .OrderBy(n => n) + .ToList(); + + var knownProvidersMessage = knownProvidersOfKind.Count > 0 + ? $" Known {kind} providers: {string.Join(", ", knownProvidersOfKind)}." + : string.Empty; + + throw new InvalidOperationException($"Could not find {kind} provider named '{name}'. This can indicate that either the 'Microsoft.Orleans.Sdk' or the provider's package are not referenced by your application.{knownProvidersMessage}"); } static Dictionary<(string Kind, string Name), Type> GetRegisteredProviders() diff --git a/test/NonSilo.Tests/ProviderErrorMessageTests.cs b/test/NonSilo.Tests/ProviderErrorMessageTests.cs new file mode 100644 index 00000000000..00bd1b8605e --- /dev/null +++ b/test/NonSilo.Tests/ProviderErrorMessageTests.cs @@ -0,0 +1,122 @@ +#nullable enable +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; +using Orleans.Hosting; +using Xunit; + +namespace NonSilo.Tests +{ + /// + /// Tests for provider error messages to ensure they include helpful information about known/registered providers. + /// These tests verify that when a provider is not found, the error message includes a list of available providers + /// for the specified kind (e.g., Clustering, GrainStorage, etc.) to help users diagnose configuration issues. + /// + [TestCategory("BVT")] + [TestCategory("Providers")] + public class ProviderErrorMessageTests + { + /// + /// Tests that client builder includes known providers in error message when a provider is not found. + /// Verifies that the error message contains both the standard message and a list of known providers + /// for the specified kind when an invalid provider type is requested. + /// + [Fact] + public void ClientBuilder_IncludesKnownProvidersInErrorMessage() + { + var configDict = new Dictionary + { + { "Orleans:ClusterId", "test-cluster" }, + { "Orleans:ServiceId", "test-service" }, + { "Orleans:Clustering:ProviderType", "NonExistentProvider" } + }; + + var exception = Assert.Throws(() => + { + _ = new HostBuilder() + .ConfigureAppConfiguration(configBuilder => + { + configBuilder.AddInMemoryCollection(configDict); + }) + .UseOrleansClient(_ => { }) + .Build(); + }); + + // Verify the error message contains the provider name that was not found + Assert.Contains("Could not find Clustering provider named 'NonExistentProvider'", exception.Message); + + // Verify the error message includes information about known providers + // The exact list will depend on what providers are registered, but the message should contain "Known Clustering providers:" + // if there are any registered Clustering providers + Assert.Contains("This can indicate that either the 'Microsoft.Orleans.Sdk' or the provider's package are not referenced", exception.Message); + } + + /// + /// Tests that silo builder includes known providers in error message when a provider is not found. + /// Verifies that the error message contains both the standard message and a list of known providers + /// for the specified kind when an invalid provider type is requested. + /// + [Fact] + public void SiloBuilder_IncludesKnownProvidersInErrorMessage() + { + var configDict = new Dictionary + { + { "Orleans:ClusterId", "test-cluster" }, + { "Orleans:ServiceId", "test-service" }, + { "Orleans:Clustering:ProviderType", "NonExistentProvider" } + }; + + var exception = Assert.Throws(() => + { + _ = new HostBuilder() + .ConfigureAppConfiguration(configBuilder => + { + configBuilder.AddInMemoryCollection(configDict); + }) + .UseOrleans(_ => { }) + .Build(); + }); + + // Verify the error message contains the provider name that was not found + Assert.Contains("Could not find Clustering provider named 'NonExistentProvider'", exception.Message); + + // Verify the error message includes information about known providers + Assert.Contains("This can indicate that either the 'Microsoft.Orleans.Sdk' or the provider's package are not referenced", exception.Message); + } + + /// + /// Tests that error message for GrainStorage provider includes known providers. + /// Verifies that when an invalid GrainStorage provider is specified, the error message + /// includes helpful information about available GrainStorage providers. + /// + [Fact] + public void SiloBuilder_IncludesKnownGrainStorageProvidersInErrorMessage() + { + var configDict = new Dictionary + { + { "Orleans:ClusterId", "test-cluster" }, + { "Orleans:ServiceId", "test-service" }, + { "Orleans:GrainStorage:MyStorage:ProviderType", "InvalidStorageProvider" } + }; + + var exception = Assert.Throws(() => + { + _ = new HostBuilder() + .ConfigureAppConfiguration(configBuilder => + { + configBuilder.AddInMemoryCollection(configDict); + }) + .UseOrleans(siloBuilder => + { + siloBuilder.UseLocalhostClustering(); + }) + .Build(); + }); + + // Verify the error message contains the provider name that was not found + Assert.Contains("Could not find GrainStorage provider named 'InvalidStorageProvider'", exception.Message); + + // Verify the error message includes information about known providers + Assert.Contains("This can indicate that either the 'Microsoft.Orleans.Sdk' or the provider's package are not referenced", exception.Message); + } + } +}