Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 22 additions & 11 deletions src/Orleans.Core/Core/DefaultClientServices.cs
Original file line number Diff line number Diff line change
@@ -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
{
Expand Down Expand Up @@ -210,7 +211,17 @@ static IProviderBuilder<IClientBuilder> GetRequiredProvider(Dictionary<(string K
?? throw new InvalidOperationException($"{kind} provider, '{name}', of type {type}, does not implement {typeof(IProviderBuilder<IClientBuilder>)}.");
}

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()
Expand Down
12 changes: 11 additions & 1 deletion src/Orleans.Runtime/Hosting/DefaultSiloServices.cs
Original file line number Diff line number Diff line change
Expand Up @@ -475,7 +475,17 @@ static IProviderBuilder<ISiloBuilder> GetRequiredProvider(Dictionary<(string Kin
?? throw new InvalidOperationException($"{kind} provider, '{name}', of type {type}, does not implement {typeof(IProviderBuilder<ISiloBuilder>)}.");
}

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()
Expand Down
122 changes: 122 additions & 0 deletions test/NonSilo.Tests/ProviderErrorMessageTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
#nullable enable
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Orleans.Hosting;
using Xunit;

namespace NonSilo.Tests
{
/// <summary>
/// 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.
/// </summary>
[TestCategory("BVT")]
[TestCategory("Providers")]
public class ProviderErrorMessageTests
{
/// <summary>
/// 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.
/// </summary>
[Fact]
public void ClientBuilder_IncludesKnownProvidersInErrorMessage()
{
var configDict = new Dictionary<string, string?>
{
{ "Orleans:ClusterId", "test-cluster" },
{ "Orleans:ServiceId", "test-service" },
{ "Orleans:Clustering:ProviderType", "NonExistentProvider" }
};

var exception = Assert.Throws<InvalidOperationException>(() =>
{
_ = 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);
}

/// <summary>
/// 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.
/// </summary>
[Fact]
public void SiloBuilder_IncludesKnownProvidersInErrorMessage()
{
var configDict = new Dictionary<string, string?>
{
{ "Orleans:ClusterId", "test-cluster" },
{ "Orleans:ServiceId", "test-service" },
{ "Orleans:Clustering:ProviderType", "NonExistentProvider" }
};

var exception = Assert.Throws<InvalidOperationException>(() =>
{
_ = 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);
}

/// <summary>
/// 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.
/// </summary>
[Fact]
public void SiloBuilder_IncludesKnownGrainStorageProvidersInErrorMessage()
{
var configDict = new Dictionary<string, string?>
{
{ "Orleans:ClusterId", "test-cluster" },
{ "Orleans:ServiceId", "test-service" },
{ "Orleans:GrainStorage:MyStorage:ProviderType", "InvalidStorageProvider" }
};

var exception = Assert.Throws<InvalidOperationException>(() =>
{
_ = 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);
}
}
}