From 8e93d4f1c5d290ecf7fa95568fcbd2f64b4f4b1a Mon Sep 17 00:00:00 2001 From: yzt Date: Fri, 16 Jul 2021 13:54:57 +0800 Subject: [PATCH 1/7] Upgrade version to 1.5.1 --- version.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.props b/version.props index 807334e..f97e276 100644 --- a/version.props +++ b/version.props @@ -1,6 +1,6 @@ - 1.5.0 + 1.5.1 preview1 $(VersionPrefix) $(VersionPrefix)-$(VersionSuffix)-final From d2d5999be319195e490355c325e1ae1bb7576126 Mon Sep 17 00:00:00 2001 From: Brennan Bugbee Date: Thu, 19 Aug 2021 00:13:26 -0400 Subject: [PATCH 2/7] Enable connection string setting for `ServerlessHub` (#263) ```cs [SignalRConnection("ConnectionStringSetting")] public class ChatHub : ServerlessHub{ } ``` --- .../Config/OptionsSetup.cs | 2 +- .../TriggerBindings/ServerlessHub.cs | 21 +++++++++++++++++-- .../SignalRTriggerBindingProvider.cs | 9 +++++--- 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/src/SignalRServiceExtension/Config/OptionsSetup.cs b/src/SignalRServiceExtension/Config/OptionsSetup.cs index 34c0a5d..95ed88e 100644 --- a/src/SignalRServiceExtension/Config/OptionsSetup.cs +++ b/src/SignalRServiceExtension/Config/OptionsSetup.cs @@ -39,7 +39,7 @@ public OptionsSetup(IConfiguration configuration, ILoggerFactory loggerFactory, public void Configure(ServiceManagerOptions options) { - options.ConnectionString = configuration[connectionStringKey]; + options.ConnectionString = configuration.GetConnectionString(connectionStringKey) ?? configuration[connectionStringKey]; options.ServiceEndpoints = configuration.GetEndpoints(Constants.AzureSignalREndpoints).ToArray(); var serviceTransportTypeStr = configuration[Constants.ServiceTransportTypeName]; if (Enum.TryParse(serviceTransportTypeStr, out var transport)) diff --git a/src/SignalRServiceExtension/TriggerBindings/ServerlessHub.cs b/src/SignalRServiceExtension/TriggerBindings/ServerlessHub.cs index 9f73abc..ff128b3 100644 --- a/src/SignalRServiceExtension/TriggerBindings/ServerlessHub.cs +++ b/src/SignalRServiceExtension/TriggerBindings/ServerlessHub.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.IdentityModel.Tokens.Jwt; using System.Linq; +using System.Reflection; using System.Security.Claims; using System.Threading.Tasks; using Microsoft.AspNetCore.SignalR; @@ -40,9 +41,11 @@ public abstract class ServerlessHub : IDisposable /// protected ServerlessHub(IServiceHubContext hubContext = null, IServiceManager serviceManager = null) { + var hubContextAttribute = GetType().GetCustomAttribute(true); + var connectionString = hubContextAttribute?.Connection ?? Constants.AzureSignalRConnectionStringName; HubName = GetType().Name; - hubContext = hubContext ?? StaticServiceHubContextStore.Get().GetAsync(HubName).GetAwaiter().GetResult(); - _serviceManager = serviceManager ?? StaticServiceHubContextStore.Get().ServiceManager; + hubContext = hubContext ?? StaticServiceHubContextStore.Get(connectionString).GetAsync(HubName).GetAwaiter().GetResult(); + _serviceManager = serviceManager ?? StaticServiceHubContextStore.Get(connectionString).ServiceManager; Clients = hubContext.Clients; Groups = hubContext.Groups; UserGroups = hubContext.UserGroups; @@ -50,6 +53,20 @@ protected ServerlessHub(IServiceHubContext hubContext = null, IServiceManager se ClientManager = _hubContext?.ClientManager; } + /// + /// Customized settings to be passed into the serverless hub context. + /// + [AttributeUsage(AttributeTargets.Class)] + protected internal class SignalRConnectionAttribute : Attribute, IConnectionProvider + { + public SignalRConnectionAttribute(string connectionStringSetting) + { + Connection = connectionStringSetting; + } + + public string Connection { get; set; } = Constants.AzureSignalRConnectionStringName; + } + /// /// Gets an object that can be used to invoke methods on the clients connected to this hub. /// diff --git a/src/SignalRServiceExtension/TriggerBindings/SignalRTriggerBindingProvider.cs b/src/SignalRServiceExtension/TriggerBindings/SignalRTriggerBindingProvider.cs index 8c6565b..61aeaa5 100644 --- a/src/SignalRServiceExtension/TriggerBindings/SignalRTriggerBindingProvider.cs +++ b/src/SignalRServiceExtension/TriggerBindings/SignalRTriggerBindingProvider.cs @@ -12,6 +12,7 @@ using Microsoft.Azure.WebJobs.Host; using Microsoft.Azure.WebJobs.Host.Triggers; using Microsoft.Extensions.Logging; +using static Microsoft.Azure.WebJobs.Extensions.SignalRService.ServerlessHub; namespace Microsoft.Azure.WebJobs.Extensions.SignalRService { @@ -52,9 +53,9 @@ public async Task TryCreateAsync(TriggerBindingProviderContext var resolvedAttribute = GetParameterResolvedAttribute(attribute, parameterInfo); ValidateSignalRTriggerAttributeBinding(resolvedAttribute); - var accessKeys = _managerStore.GetOrAddByConnectionStringKey(attribute.ConnectionStringSetting).AccessKeys; + var accessKeys = _managerStore.GetOrAddByConnectionStringKey(resolvedAttribute.ConnectionStringSetting).AccessKeys; - var hubContext = await _managerStore.GetOrAddByConnectionStringKey(attribute.ConnectionStringSetting).GetAsync(resolvedAttribute.HubName); + var hubContext = await _managerStore.GetOrAddByConnectionStringKey(resolvedAttribute.ConnectionStringSetting).GetAsync(resolvedAttribute.HubName); return new SignalRTriggerBinding(parameterInfo, resolvedAttribute, _dispatcher, accessKeys, hubContext as ServiceHubContext); } @@ -66,6 +67,7 @@ internal SignalRTriggerAttribute GetParameterResolvedAttribute(SignalRTriggerAtt var category = attribute.Category; var @event = attribute.Event; var parameterNames = attribute.ParameterNames ?? Array.Empty(); + var connectionStringSetting = attribute.ConnectionStringSetting; // We have two models for C#, one is function based model which also work in multiple language // Another one is class based model, which is highly close to SignalR itself but must keep some conventions. @@ -87,6 +89,7 @@ internal SignalRTriggerAttribute GetParameterResolvedAttribute(SignalRTriggerAtt hubName = declaredType.Name; category = GetCategoryFromMethodName(method.Name); @event = GetEventFromMethodName(method.Name, category); + connectionStringSetting = declaredType.GetCustomAttribute()?.Connection ?? attribute.ConnectionStringSetting; } else { @@ -110,7 +113,7 @@ internal SignalRTriggerAttribute GetParameterResolvedAttribute(SignalRTriggerAtt ? parameterNamesFromAttribute : parameterNames; - return new SignalRTriggerAttribute(hubName, category, @event, parameterNames) { ConnectionStringSetting = attribute.ConnectionStringSetting }; + return new SignalRTriggerAttribute(hubName, category, @event, parameterNames) { ConnectionStringSetting = connectionStringSetting }; } private void ValidateSignalRTriggerAttributeBinding(SignalRTriggerAttribute attribute) From 7c8cb50389071f379429820b6c60b8a0a87e7a1e Mon Sep 17 00:00:00 2001 From: Zitong Yang Date: Mon, 30 Aug 2021 18:11:23 +0800 Subject: [PATCH 3/7] Update serverless SDK version to 1.11.0-preview1-10835 Also fixed broken tests --- build/dependencies.props | 2 +- .../Config/ServiceManagerStoreTests.cs | 4 ++-- .../Trigger/ServerlessHubTest.cs | 2 +- .../Trigger/SignalRTriggerResolverTests.cs | 6 +++--- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index 71a0038..acd008f 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -5,7 +5,7 @@ 0.3.0 - 1.9.1 + 1.11.0-preview1-10835 1.0.0 15.8.0 4.9.0 diff --git a/test/SignalRServiceExtension.Tests/Config/ServiceManagerStoreTests.cs b/test/SignalRServiceExtension.Tests/Config/ServiceManagerStoreTests.cs index b33695b..eef4f8f 100644 --- a/test/SignalRServiceExtension.Tests/Config/ServiceManagerStoreTests.cs +++ b/test/SignalRServiceExtension.Tests/Config/ServiceManagerStoreTests.cs @@ -67,7 +67,7 @@ public async Task DefaultRouterExists() })) .ConfigureWebJobs(b => b.AddSignalR()).Build(); var hubContext = await host.Services.GetRequiredService().GetOrAddByConnectionStringKey("key").GetAsync("hubName") as ServiceHubContext; - await Assert.ThrowsAsync(() => hubContext.NegotiateAsync()); + await Assert.ThrowsAsync(() => hubContext.NegotiateAsync().AsTask()); } } -} \ No newline at end of file +} diff --git a/test/SignalRServiceExtension.Tests/Trigger/ServerlessHubTest.cs b/test/SignalRServiceExtension.Tests/Trigger/ServerlessHubTest.cs index b090337..8151062 100644 --- a/test/SignalRServiceExtension.Tests/Trigger/ServerlessHubTest.cs +++ b/test/SignalRServiceExtension.Tests/Trigger/ServerlessHubTest.cs @@ -51,7 +51,7 @@ private async Task ServerlessHubUnitTest() [Fact] public async Task ServerlessHubSyncNegotiate() { - var hubContext = await new ServiceHubContextBuilder().WithOptions(o => o.ConnectionString = FakeEndpointUtils.GetFakeConnectionString(1).Single()).CreateAsync("hub", default); + var hubContext = await new ServiceManagerBuilder().WithOptions(o => o.ConnectionString = FakeEndpointUtils.GetFakeConnectionString(1).Single()).BuildServiceManager().CreateHubContextAsync("hub", default); var serviceManager = Mock.Of(); var myHub = new MyHub(hubContext, serviceManager); var connectionInfo = myHub.Negotiate("user"); diff --git a/test/SignalRServiceExtension.Tests/Trigger/SignalRTriggerResolverTests.cs b/test/SignalRServiceExtension.Tests/Trigger/SignalRTriggerResolverTests.cs index c3a3b8f..52d583d 100644 --- a/test/SignalRServiceExtension.Tests/Trigger/SignalRTriggerResolverTests.cs +++ b/test/SignalRServiceExtension.Tests/Trigger/SignalRTriggerResolverTests.cs @@ -18,10 +18,10 @@ public static IEnumerable SignatureTestData() var connectionId = "0f9c97a2f0bf4706afe87a14e0797b11"; var accessKeys = new AccessKey[] { - new AccessKey("7aab239577fd4f24bc919802fb629f5f","http://endpoint1",1), - new AccessKey("a5f2815d0d0c4b00bd27e832432f91ab","http://endpont2",1) + new AccessKey("https://endpoint1", "7aab239577fd4f24bc919802fb629f5f"), + new AccessKey("https://endpont2", "a5f2815d0d0c4b00bd27e832432f91ab") }; - var wrongAccessKeys = new AccessKey[] { new AccessKey(Guid.NewGuid().ToString(), "http://wrong", 1) }; + var wrongAccessKeys = new AccessKey[] { new AccessKey("http://wrong", Guid.NewGuid().ToString()) }; var signatures = new string[] { "sha256=7767effcb3946f3e1de039df4b986ef02c110b1469d02c0a06f41b3b727ab561", From cd7ae7dceb8e9642a3a91903b83e313c47ca194f Mon Sep 17 00:00:00 2001 From: Zitong Yang Date: Tue, 31 Aug 2021 10:21:51 +0800 Subject: [PATCH 4/7] Support reading identity-based `ServiceEndpoint` from `Configuration` By default, we allow two styles to configure identity-based `ServiceEndpoint`: named endpoint and nameless endpoint. The key of nameless endpoint reuses `ConnectionStringSetting`. The key of named endpoints is "Azure:SignalR:Endpoints". The following is a sample. ```json { "AzureSignalRConnectionString": { "serviceUri": "https://{SignalRHost}" }, "Azure": { "SignalR": { "Endpoints": { "eastUs": { "serviceUri": "https://{SignalRHost}" }, "westUs": { "serviceUri": "https://{SignalRHost}" } } } } } ``` --- .../Config/IConfigurationExtensions.cs | 72 +++++++++++++++++++ .../Config/OptionsSetup.cs | 14 +++- .../Config/ServiceManagerStore.cs | 8 ++- .../AzureSignalRClientTests.cs | 2 +- .../Config/OptionsSetupFacts.cs | 66 +++++++++++++++++ .../Config/ServiceManagerStoreTests.cs | 11 +-- .../Config/SingleAzureComponentFactory.cs | 17 +++++ .../JobhostEndToEnd.cs | 3 +- .../NegotiateContextAsyncConverterTest.cs | 2 +- .../Utils/TestHelpers.cs | 2 + 10 files changed, 184 insertions(+), 13 deletions(-) create mode 100644 src/SignalRServiceExtension/Config/IConfigurationExtensions.cs create mode 100644 test/SignalRServiceExtension.Tests/Config/OptionsSetupFacts.cs create mode 100644 test/SignalRServiceExtension.Tests/Config/SingleAzureComponentFactory.cs diff --git a/src/SignalRServiceExtension/Config/IConfigurationExtensions.cs b/src/SignalRServiceExtension/Config/IConfigurationExtensions.cs new file mode 100644 index 0000000..499b2d2 --- /dev/null +++ b/src/SignalRServiceExtension/Config/IConfigurationExtensions.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Azure.Core; +using Microsoft.Azure.SignalR; +using Microsoft.Extensions.Azure; +using Microsoft.Extensions.Configuration; + +namespace Microsoft.Azure.WebJobs.Extensions.SignalRService +{ + /// + /// A helper class to parse from configuration. + /// + /// + /// Copyied from https://github.com/Azure/azure-signalr/blob/e5a69b20daf7f7aa2786ff59df61433c4050c6ab/src/Microsoft.Azure.SignalR.Common/Endpoints/IConfigurationExtension.cs. + /// Although we have friend access to that internal class, referencing internal methods are not recommended as changes of internal methods might break this package. + /// + internal static class IConfigurationExtensions + { + public static IEnumerable GetEndpoints(this IConfiguration config, AzureComponentFactory azureComponentFactory) + { + foreach (IConfigurationSection child in config.GetChildren()) + { + if (child.TryGetNamedEndpointFromIdentity(azureComponentFactory, out ServiceEndpoint endpoint)) + { + yield return endpoint; + continue; + } + + foreach (ServiceEndpoint item in child.GetNamedEndpointsFromConnectionString()) + { + yield return item; + } + } + } + + public static IEnumerable GetNamedEndpointsFromConnectionString(this IConfigurationSection section) + { + string endpointName = section.Key; + if (section.Value != null) + { + yield return new ServiceEndpoint(section.Key, section.Value); + } + + if (section["primary"] != null) + { + yield return new ServiceEndpoint(section["primary"], EndpointType.Primary, endpointName); + } + + if (section["secondary"] != null) + { + yield return new ServiceEndpoint(section["secondary"], EndpointType.Secondary, endpointName); + } + } + + public static bool TryGetNamedEndpointFromIdentity(this IConfigurationSection section, AzureComponentFactory azureComponentFactory, out ServiceEndpoint endpoint) + { + string text = section["ServiceUri"]; + if (text != null) + { + string key = section.Key; + EndpointType value = section.GetValue("Type", EndpointType.Primary); + TokenCredential credential = azureComponentFactory.CreateTokenCredential(section); + endpoint = new ServiceEndpoint(new Uri(text), credential, value, key); + return true; + } + + endpoint = null; + return false; + } + } +} diff --git a/src/SignalRServiceExtension/Config/OptionsSetup.cs b/src/SignalRServiceExtension/Config/OptionsSetup.cs index 95ed88e..9b6f8b3 100644 --- a/src/SignalRServiceExtension/Config/OptionsSetup.cs +++ b/src/SignalRServiceExtension/Config/OptionsSetup.cs @@ -5,6 +5,7 @@ using System.Linq; using Microsoft.Azure.SignalR; using Microsoft.Azure.SignalR.Management; +using Microsoft.Extensions.Azure; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -15,10 +16,11 @@ namespace Microsoft.Azure.WebJobs.Extensions.SignalRService internal class OptionsSetup : IConfigureOptions, IOptionsChangeTokenSource { private readonly IConfiguration configuration; + private readonly AzureComponentFactory _azureComponentFactory; private readonly string connectionStringKey; private readonly ILogger logger; - public OptionsSetup(IConfiguration configuration, ILoggerFactory loggerFactory, string connectionStringKey) + public OptionsSetup(IConfiguration configuration, ILoggerFactory loggerFactory, AzureComponentFactory azureComponentFactory, string connectionStringKey) { if (loggerFactory is null) { @@ -31,6 +33,7 @@ public OptionsSetup(IConfiguration configuration, ILoggerFactory loggerFactory, } this.configuration = configuration ?? throw new ArgumentNullException(nameof(configuration)); + _azureComponentFactory = azureComponentFactory; this.connectionStringKey = connectionStringKey; logger = loggerFactory.CreateLogger(); } @@ -40,7 +43,14 @@ public OptionsSetup(IConfiguration configuration, ILoggerFactory loggerFactory, public void Configure(ServiceManagerOptions options) { options.ConnectionString = configuration.GetConnectionString(connectionStringKey) ?? configuration[connectionStringKey]; - options.ServiceEndpoints = configuration.GetEndpoints(Constants.AzureSignalREndpoints).ToArray(); + var endpoints = configuration.GetSection(Constants.AzureSignalREndpoints).GetEndpoints(_azureComponentFactory); + // Fall back to use a section to configure Azure identity + if (options.ConnectionString == null && configuration.GetSection(connectionStringKey).TryGetNamedEndpointFromIdentity(_azureComponentFactory, out var endpoint)) + { + endpoint.Name = string.Empty; + endpoints = endpoints.Append(endpoint); + } + options.ServiceEndpoints = endpoints.ToArray(); var serviceTransportTypeStr = configuration[Constants.ServiceTransportTypeName]; if (Enum.TryParse(serviceTransportTypeStr, out var transport)) { diff --git a/src/SignalRServiceExtension/Config/ServiceManagerStore.cs b/src/SignalRServiceExtension/Config/ServiceManagerStore.cs index 0418023..be1b8b1 100644 --- a/src/SignalRServiceExtension/Config/ServiceManagerStore.cs +++ b/src/SignalRServiceExtension/Config/ServiceManagerStore.cs @@ -5,9 +5,9 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; -using System.Reflection; using Microsoft.Azure.SignalR; using Microsoft.Azure.SignalR.Management; +using Microsoft.Extensions.Azure; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -17,13 +17,15 @@ namespace Microsoft.Azure.WebJobs.Extensions.SignalRService internal class ServiceManagerStore : IServiceManagerStore { private readonly ILoggerFactory loggerFactory; + private readonly AzureComponentFactory _azureComponentFactory; private readonly IConfiguration configuration; private readonly IEndpointRouter router; private readonly ConcurrentDictionary store = new ConcurrentDictionary(); - public ServiceManagerStore(IConfiguration configuration, ILoggerFactory loggerFactory, IEndpointRouter router = null) + public ServiceManagerStore(IConfiguration configuration, ILoggerFactory loggerFactory, AzureComponentFactory azureComponentFactory, IEndpointRouter router = null) { this.loggerFactory = loggerFactory; + _azureComponentFactory = azureComponentFactory; this.configuration = configuration; this.router = router; } @@ -46,7 +48,7 @@ public IInternalServiceHubContextStore GetByConfigurationKey(string connectionSt private IInternalServiceHubContextStore CreateHubContextStore(string connectionStringKey) { var services = new ServiceCollection() - .SetupOptions(new OptionsSetup(configuration, loggerFactory, connectionStringKey)) + .SetupOptions(new OptionsSetup(configuration, loggerFactory, _azureComponentFactory, connectionStringKey)) .PostConfigure(o => { if ((o.ServiceEndpoints == null || o.ServiceEndpoints.Length == 0) && string.IsNullOrWhiteSpace(o.ConnectionString)) diff --git a/test/SignalRServiceExtension.Tests/AzureSignalRClientTests.cs b/test/SignalRServiceExtension.Tests/AzureSignalRClientTests.cs index 1f72a02..fcab60d 100644 --- a/test/SignalRServiceExtension.Tests/AzureSignalRClientTests.cs +++ b/test/SignalRServiceExtension.Tests/AzureSignalRClientTests.cs @@ -38,7 +38,7 @@ public async Task GetClientConnectionInfo() var connectionStringKey = Constants.AzureSignalRConnectionStringName; var configDict = new Dictionary() { { Constants.ServiceTransportTypeName, "Transient" }, { connectionStringKey, connectionString } }; var configuration = new ConfigurationBuilder().AddInMemoryCollection(configDict).Build(); - var serviceManagerStore = new ServiceManagerStore(configuration, NullLoggerFactory.Instance, new TestRouter()); + var serviceManagerStore = new ServiceManagerStore(configuration, NullLoggerFactory.Instance, SingletonAzureComponentFactory.Instance, new TestRouter()); var azureSignalRClient = await SignalRUtils.GetAzureSignalRClientAsync(connectionStringKey, hubName, serviceManagerStore); var connectionInfo = await azureSignalRClient.GetClientConnectionInfoAsync(userId, idToken, claimTypeList, null); diff --git a/test/SignalRServiceExtension.Tests/Config/OptionsSetupFacts.cs b/test/SignalRServiceExtension.Tests/Config/OptionsSetupFacts.cs new file mode 100644 index 0000000..dbce289 --- /dev/null +++ b/test/SignalRServiceExtension.Tests/Config/OptionsSetupFacts.cs @@ -0,0 +1,66 @@ +using Microsoft.Azure.SignalR.Management; +using Microsoft.Azure.WebJobs.Extensions.SignalRService; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging.Abstractions; +using Xunit; + +namespace SignalRServiceExtension.Tests.Config +{ + public class OptionsSetupFacts + { + [Fact] + public void TestIdentityBasedSingleEndpointConfiguration() + { + var configuration = new ConfigurationBuilder().AddInMemoryCollection().Build(); + var sectionKey = "connection"; + var serviceUri = "http://localhost.signalr.com"; + configuration[$"{sectionKey}:serviceUri"] = serviceUri; + var optionsSetup = new OptionsSetup(configuration, NullLoggerFactory.Instance, SingletonAzureComponentFactory.Instance, sectionKey); + var options = new ServiceManagerOptions(); + optionsSetup.Configure(options); + + var endpoint = Assert.Single(options.ServiceEndpoints); + Assert.Equal(serviceUri, endpoint.Endpoint); + Assert.Equal(string.Empty, endpoint.Name); + } + + [Fact] + public void TestIdentityBasedMultiEndpointConfiguration() + { + var serviceUris = new string[] { "http://localhost.signalr.com", "http://localhost2.signalr.com" }; + var endpointNames = new string[] { "eastus", "westus" }; + var configuration = new ConfigurationBuilder().AddInMemoryCollection().Build(); + configuration[$"Azure:SignalR:Endpoints:{endpointNames[0]}:serviceUri"] = serviceUris[0]; + configuration[$"Azure:SignalR:Endpoints:{endpointNames[1]}:serviceUri"] = serviceUris[1]; + var optionsSetup = new OptionsSetup(configuration, NullLoggerFactory.Instance, SingletonAzureComponentFactory.Instance, "does_not_matter"); + var options = new ServiceManagerOptions(); + optionsSetup.Configure(options); + + Assert.Equal(2, options.ServiceEndpoints.Length); + Assert.Contains(options.ServiceEndpoints, e => endpointNames[0] == e.Name && serviceUris[0] == e.Endpoint); + Assert.Contains(options.ServiceEndpoints, e => endpointNames[1] == e.Name && serviceUris[1] == e.Endpoint); + } + + /// + /// Test when named endpoints and nameless endpoint are configured, both should be included. + /// + [Fact] + public void TestIdentityBasedMixedEndpointConfiguration() + { + var serviceUris = new string[] { "http://localhost.signalr.com", "http://localhost2.signalr.com" }; + var endpointName = "eastus"; + var namelessEndpointKey = "AzureSignalRConnection"; + var configuration = new ConfigurationBuilder().AddInMemoryCollection().Build(); + configuration[$"Azure:SignalR:Endpoints:{endpointName}:serviceUri"] = serviceUris[0]; + configuration[$"{namelessEndpointKey}:serviceUri"] = serviceUris[1]; + + var optionsSetup = new OptionsSetup(configuration, NullLoggerFactory.Instance, SingletonAzureComponentFactory.Instance, namelessEndpointKey); + var options = new ServiceManagerOptions(); + optionsSetup.Configure(options); + + Assert.Equal(2, options.ServiceEndpoints.Length); + Assert.Contains(options.ServiceEndpoints, e => endpointName == e.Name && serviceUris[0] == e.Endpoint); + Assert.Contains(options.ServiceEndpoints, e => string.Empty == e.Name && serviceUris[1] == e.Endpoint); + } + } +} diff --git a/test/SignalRServiceExtension.Tests/Config/ServiceManagerStoreTests.cs b/test/SignalRServiceExtension.Tests/Config/ServiceManagerStoreTests.cs index eef4f8f..b34cd6d 100644 --- a/test/SignalRServiceExtension.Tests/Config/ServiceManagerStoreTests.cs +++ b/test/SignalRServiceExtension.Tests/Config/ServiceManagerStoreTests.cs @@ -9,6 +9,7 @@ using Microsoft.Azure.SignalR.Management; using Microsoft.Azure.SignalR.Tests.Common; using Microsoft.Azure.WebJobs.Extensions.SignalRService; +using Microsoft.Extensions.Azure; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -41,13 +42,13 @@ public void ProductInfoValid() var connectionStringKey = "key"; configuration[connectionStringKey] = connectionString; configuration[Constants.FunctionsWorkerRuntime] = Constants.DotnetWorker; - + var productInfo = new ServiceCollection() - .AddSignalRServiceManager(new OptionsSetup(configuration, NullLoggerFactory.Instance, connectionStringKey)) + .AddSignalRServiceManager(new OptionsSetup(configuration, NullLoggerFactory.Instance, SingletonAzureComponentFactory.Instance, connectionStringKey)) .BuildServiceProvider() .GetRequiredService>() .Value.ProductInfo; - + Assert.NotNull(productInfo); var reg = new Regex(@"\[(\w*)=(\w*)\]"); var match = reg.Match(productInfo); @@ -65,9 +66,9 @@ public async Task DefaultRouterExists() { "key", FakeEndpointUtils.GetFakeConnectionString(1).Single() }, { Constants.ServiceTransportTypeName, ServiceTransportType.Persistent.ToString() } })) - .ConfigureWebJobs(b => b.AddSignalR()).Build(); + .ConfigureWebJobs(b => b.AddSignalR().Services.AddAzureClientsCore()).Build(); var hubContext = await host.Services.GetRequiredService().GetOrAddByConnectionStringKey("key").GetAsync("hubName") as ServiceHubContext; await Assert.ThrowsAsync(() => hubContext.NegotiateAsync().AsTask()); } } -} +} \ No newline at end of file diff --git a/test/SignalRServiceExtension.Tests/Config/SingleAzureComponentFactory.cs b/test/SignalRServiceExtension.Tests/Config/SingleAzureComponentFactory.cs new file mode 100644 index 0000000..ed3654c --- /dev/null +++ b/test/SignalRServiceExtension.Tests/Config/SingleAzureComponentFactory.cs @@ -0,0 +1,17 @@ +using Microsoft.Extensions.Azure; +using Microsoft.Extensions.DependencyInjection; + +namespace SignalRServiceExtension.Tests +{ + public static class SingletonAzureComponentFactory + { + public static readonly AzureComponentFactory Instance; + + static SingletonAzureComponentFactory() + { + var services = new ServiceCollection(); + services.AddAzureClientsCore(); + Instance = services.BuildServiceProvider().GetRequiredService(); + } + } +} diff --git a/test/SignalRServiceExtension.Tests/JobhostEndToEnd.cs b/test/SignalRServiceExtension.Tests/JobhostEndToEnd.cs index 0b1f642..8973a4a 100644 --- a/test/SignalRServiceExtension.Tests/JobhostEndToEnd.cs +++ b/test/SignalRServiceExtension.Tests/JobhostEndToEnd.cs @@ -13,6 +13,7 @@ using Microsoft.Azure.WebJobs.Extensions.SignalRService; using Microsoft.Azure.WebJobs.Host; using Microsoft.Azure.WebJobs.Host.Config; +using Microsoft.Extensions.Azure; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -138,7 +139,7 @@ public void WebhookProviderThrowExceptionTest() var builder = new HostBuilder(); builder.ConfigureWebJobs(b => { - b.AddSignalR(); + b.AddSignalR().Services.AddAzureClientsCore(); }); var host = builder.Build(); using (host) diff --git a/test/SignalRServiceExtension.Tests/NegotiateContextAsyncConverterTest.cs b/test/SignalRServiceExtension.Tests/NegotiateContextAsyncConverterTest.cs index ed71f62..b4b0894 100644 --- a/test/SignalRServiceExtension.Tests/NegotiateContextAsyncConverterTest.cs +++ b/test/SignalRServiceExtension.Tests/NegotiateContextAsyncConverterTest.cs @@ -32,7 +32,7 @@ public class NegotiateContextAsyncConverterTest public async Task EndpointsEqualFact() { var configuration = CreateTestConfiguration(); - var serviceManagerStore = new ServiceManagerStore(configuration, NullLoggerFactory.Instance); + var serviceManagerStore = new ServiceManagerStore(configuration, NullLoggerFactory.Instance, SingletonAzureComponentFactory.Instance); var converter = new NegotiationContextAsyncConverter(serviceManagerStore); var attribute = new SignalRNegotiationAttribute { HubName = HubName }; diff --git a/test/SignalRServiceExtension.Tests/Utils/TestHelpers.cs b/test/SignalRServiceExtension.Tests/Utils/TestHelpers.cs index 8c32698..5e7a6bc 100644 --- a/test/SignalRServiceExtension.Tests/Utils/TestHelpers.cs +++ b/test/SignalRServiceExtension.Tests/Utils/TestHelpers.cs @@ -10,6 +10,7 @@ using Microsoft.Azure.WebJobs; using Microsoft.Azure.WebJobs.Extensions.SignalRService; using Microsoft.Azure.WebJobs.Host.Config; +using Microsoft.Extensions.Azure; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -31,6 +32,7 @@ public static IHost NewHost(Type type, SignalRConfigProvider ext = null, Diction services.AddSingleton(ext); } services.AddSingleton(new TestExtensionConfig()); + services.AddAzureClientsCore(); }) .ConfigureWebJobs(webJobsBuilder => { From 3d3aba3d10a404626193e89df19d7aab310ddbac Mon Sep 17 00:00:00 2001 From: Zitong Yang Date: Fri, 3 Sep 2021 16:30:09 +0800 Subject: [PATCH 5/7] Move identity-based service endpoint parsing methods here The code won't exist in SDK anymore. Also move the tests here --- build/dependencies.props | 3 +- .../Config/IConfigurationExtensions.cs | 5 - ...e.WebJobs.Extensions.SignalRService.csproj | 1 + .../Config/IConfigurationExtensionsFacts.cs | 106 ++++++++++++++++++ .../SignalRServiceExtension.Tests.csproj | 1 + .../SingleAzureComponentFactory.cs | 0 6 files changed, 110 insertions(+), 6 deletions(-) create mode 100644 test/SignalRServiceExtension.Tests/Config/IConfigurationExtensionsFacts.cs rename test/SignalRServiceExtension.Tests/{Config => Utils}/SingleAzureComponentFactory.cs (100%) diff --git a/build/dependencies.props b/build/dependencies.props index acd008f..73f07be 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -5,7 +5,7 @@ 0.3.0 - 1.11.0-preview1-10835 + 1.11.0-preview1-10841 1.0.0 15.8.0 4.9.0 @@ -20,6 +20,7 @@ 3.0.0 5.0.3 5.0.0 + 1.1.0 1.0.30 diff --git a/src/SignalRServiceExtension/Config/IConfigurationExtensions.cs b/src/SignalRServiceExtension/Config/IConfigurationExtensions.cs index 499b2d2..ffaf0e5 100644 --- a/src/SignalRServiceExtension/Config/IConfigurationExtensions.cs +++ b/src/SignalRServiceExtension/Config/IConfigurationExtensions.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using Azure.Core; using Microsoft.Azure.SignalR; using Microsoft.Extensions.Azure; @@ -11,10 +10,6 @@ namespace Microsoft.Azure.WebJobs.Extensions.SignalRService /// /// A helper class to parse from configuration. /// - /// - /// Copyied from https://github.com/Azure/azure-signalr/blob/e5a69b20daf7f7aa2786ff59df61433c4050c6ab/src/Microsoft.Azure.SignalR.Common/Endpoints/IConfigurationExtension.cs. - /// Although we have friend access to that internal class, referencing internal methods are not recommended as changes of internal methods might break this package. - /// internal static class IConfigurationExtensions { public static IEnumerable GetEndpoints(this IConfiguration config, AzureComponentFactory azureComponentFactory) diff --git a/src/SignalRServiceExtension/Microsoft.Azure.WebJobs.Extensions.SignalRService.csproj b/src/SignalRServiceExtension/Microsoft.Azure.WebJobs.Extensions.SignalRService.csproj index 5af9d13..acdb48c 100644 --- a/src/SignalRServiceExtension/Microsoft.Azure.WebJobs.Extensions.SignalRService.csproj +++ b/src/SignalRServiceExtension/Microsoft.Azure.WebJobs.Extensions.SignalRService.csproj @@ -11,6 +11,7 @@ + diff --git a/test/SignalRServiceExtension.Tests/Config/IConfigurationExtensionsFacts.cs b/test/SignalRServiceExtension.Tests/Config/IConfigurationExtensionsFacts.cs new file mode 100644 index 0000000..cbbc0dd --- /dev/null +++ b/test/SignalRServiceExtension.Tests/Config/IConfigurationExtensionsFacts.cs @@ -0,0 +1,106 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Linq; +using Azure.Identity; +using Microsoft.Azure.SignalR; +using Microsoft.Azure.SignalR.Tests.Common; +using Microsoft.Azure.WebJobs.Extensions.SignalRService; +using Microsoft.Extensions.Azure; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Xunit; + +namespace SignalRServiceExtension.Tests.Config +{ + public class IConfigurationExtensionsFacts + { + [Fact] + public void TestGetNamedEndpointFromIdentityWithNoUri() + { + var services = new ServiceCollection(); + services.AddAzureClientsCore(); + var factory = services.BuildServiceProvider().GetRequiredService(); + var config = new ConfigurationBuilder().AddInMemoryCollection().Build(); + Assert.False(config.GetSection("eastus").TryGetNamedEndpointFromIdentity(factory, out _)); + } + + [Fact] + public void TestGetNamedEndpointFromIdentityWithOnlyUri() + { + var services = new ServiceCollection(); + services.AddAzureClientsCore(); + var factory = services.BuildServiceProvider().GetRequiredService(); + + var config = new ConfigurationBuilder().AddInMemoryCollection().Build(); + var uri = "http://signalr.service.uri.com:441"; + config["eastus:serviceUri"] = uri; + + Assert.True(config.GetSection("eastus").TryGetNamedEndpointFromIdentity(factory, out var endpoint)); + Assert.Equal("eastus", endpoint.Name); + Assert.Equal(uri, endpoint.Endpoint); + Assert.IsType((endpoint.AccessKey as AadAccessKey).TokenCredential); + Assert.Equal(EndpointType.Primary, endpoint.EndpointType); + } + + [Fact] + public void TestGetNamedEndpointFromIdentityWithAllEndpointField() + { + var services = new ServiceCollection(); + services.AddAzureClientsCore(); + var factory = services.BuildServiceProvider().GetRequiredService(); + + var config = new ConfigurationBuilder().AddInMemoryCollection().Build(); + var uri = "http://signalr.service.uri.com:441"; + config["eastus:serviceUri"] = uri; + config["eastus:credential"] = "managedidentity"; + config["eastus:type"] = "secondary"; + + Assert.True(config.GetSection("eastus").TryGetNamedEndpointFromIdentity(factory, out var endpoint)); + Assert.Equal("eastus", endpoint.Name); + Assert.Equal(uri, endpoint.Endpoint); + Assert.IsType((endpoint.AccessKey as AadAccessKey).TokenCredential); + Assert.Equal(EndpointType.Secondary, endpoint.EndpointType); + } + + [Fact] + public void TestGetNamedEndpointsFromConnectionString() + { + var connectionString = FakeEndpointUtils.GetFakeConnectionString(1).Single(); + var config = new ConfigurationBuilder().AddInMemoryCollection().Build(); + config["eastus"] = connectionString; + config["eastus:primary"] = connectionString; + config["eastus:secondary"] = connectionString; + var endpoints = config.GetSection("eastus").GetNamedEndpointsFromConnectionString().ToArray(); + Assert.Equal(3, endpoints.Length); + Assert.All(endpoints, e => Assert.Equal("eastus", e.Name)); + Assert.Equal(EndpointType.Primary, endpoints[0].EndpointType); + Assert.Equal(EndpointType.Primary, endpoints[1].EndpointType); + Assert.Equal(EndpointType.Secondary, endpoints[2].EndpointType); + } + + [Fact] + public void TestGetEndpointsFromIdentityAndConnectionString() + { + var services = new ServiceCollection(); + services.AddAzureClientsCore(); + var factory = services.BuildServiceProvider().GetRequiredService(); + var config = new ConfigurationBuilder().AddInMemoryCollection().Build(); + + var serviceUri = "http://signalr.service.uri.com:441"; + config["endpoints:eastus:serviceUri"] = serviceUri; + config["endpoints:westus:secondary"] = FakeEndpointUtils.GetFakeConnectionString(1).Single(); + + var endpoints = config.GetSection("endpoints").GetEndpoints(factory).ToArray(); + Assert.Collection(endpoints, e => + { + Assert.Equal("eastus", e.Name); + Assert.Equal(serviceUri, e.Endpoint); + }, e => + { + Assert.Equal("westus", e.Name); + Assert.Equal(EndpointType.Secondary, e.EndpointType); + }); + } + } +} diff --git a/test/SignalRServiceExtension.Tests/SignalRServiceExtension.Tests.csproj b/test/SignalRServiceExtension.Tests/SignalRServiceExtension.Tests.csproj index 898409a..8b73f0a 100644 --- a/test/SignalRServiceExtension.Tests/SignalRServiceExtension.Tests.csproj +++ b/test/SignalRServiceExtension.Tests/SignalRServiceExtension.Tests.csproj @@ -10,6 +10,7 @@ + diff --git a/test/SignalRServiceExtension.Tests/Config/SingleAzureComponentFactory.cs b/test/SignalRServiceExtension.Tests/Utils/SingleAzureComponentFactory.cs similarity index 100% rename from test/SignalRServiceExtension.Tests/Config/SingleAzureComponentFactory.cs rename to test/SignalRServiceExtension.Tests/Utils/SingleAzureComponentFactory.cs From c26741cc01d416a6af14931645009ec7a24fa81a Mon Sep 17 00:00:00 2001 From: Zitong Yang Date: Fri, 3 Sep 2021 16:31:47 +0800 Subject: [PATCH 6/7] Fix broken tests after SDK behaviour changes SDK won't check endpoint health with single endpoint anymore. --- .../Config/ServiceManagerStoreTests.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/SignalRServiceExtension.Tests/Config/ServiceManagerStoreTests.cs b/test/SignalRServiceExtension.Tests/Config/ServiceManagerStoreTests.cs index b34cd6d..f4b85cd 100644 --- a/test/SignalRServiceExtension.Tests/Config/ServiceManagerStoreTests.cs +++ b/test/SignalRServiceExtension.Tests/Config/ServiceManagerStoreTests.cs @@ -63,7 +63,8 @@ public async Task DefaultRouterExists() var host = builder .ConfigureAppConfiguration(b => b.AddInMemoryCollection( new Dictionary { - { "key", FakeEndpointUtils.GetFakeConnectionString(1).Single() }, + { "Azure:SignalR:Endpoints:A", FakeEndpointUtils.GetFakeConnectionString(1).Single() }, + { "Azure:SignalR:Endpoints:B", FakeEndpointUtils.GetFakeConnectionString(1).Single() }, { Constants.ServiceTransportTypeName, ServiceTransportType.Persistent.ToString() } })) .ConfigureWebJobs(b => b.AddSignalR().Services.AddAzureClientsCore()).Build(); From 2224ad475e934a392e9cec7a30c9edaa47c8ea88 Mon Sep 17 00:00:00 2001 From: yzt Date: Thu, 9 Sep 2021 13:55:19 +0800 Subject: [PATCH 7/7] Update SDK to 1.11 (#272) Also update self version to 1.6.0 --- build/dependencies.props | 2 +- version.props | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index 73f07be..8e474d6 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -5,7 +5,7 @@ 0.3.0 - 1.11.0-preview1-10841 + 1.11.0 1.0.0 15.8.0 4.9.0 diff --git a/version.props b/version.props index f97e276..72bf02e 100644 --- a/version.props +++ b/version.props @@ -1,6 +1,6 @@ - 1.5.1 + 1.6.0 preview1 $(VersionPrefix) $(VersionPrefix)-$(VersionSuffix)-final