-
Notifications
You must be signed in to change notification settings - Fork 103
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
Set up options and track changes in management SDK. #1081
Changes from 22 commits
f1d6d2c
ef79564
04d9b10
1840a4d
8cb960c
fa833d2
f9b358e
c4f2030
32636be
c83f4c9
17b6585
52dc94b
5315a43
d3a3b68
1399157
d32b810
80874da
6786043
3d6a3ec
37947ab
e3edf86
9a5c796
a9a6153
f6b7d91
2532535
514355a
13ec165
35c083e
4b73ac4
903f1c2
383ad4b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
// Copyright (c) Microsoft. All rights reserved. | ||
// Licensed under the MIT license. See LICENSE file in the project root for full license information. | ||
|
||
using Microsoft.Extensions.Configuration; | ||
using Microsoft.Extensions.FileProviders; | ||
using Microsoft.Extensions.Options; | ||
using Microsoft.Extensions.Primitives; | ||
|
||
namespace Microsoft.Azure.SignalR.Management.Configuration | ||
{ | ||
/// <summary> | ||
/// Sets up TargetOptions from ServiceManagerOptions and tracks changes . | ||
/// </summary> | ||
internal abstract class CascadeOptionsSetup<TargetOptions> : IConfigureOptions<TargetOptions>, IOptionsChangeTokenSource<TargetOptions> | ||
where TargetOptions : class | ||
{ | ||
private readonly ServiceManagerOptions _initialSource; | ||
private readonly IConfiguration _configuration; | ||
|
||
//Making 'configuration' optional avoids error when 'tokenSource' is unavailable. | ||
public CascadeOptionsSetup(IOptions<ServiceManagerOptions> initialSource, IConfiguration configuration = null) | ||
{ | ||
_initialSource = initialSource.Value; | ||
_configuration = configuration; | ||
} | ||
|
||
public string Name => Options.DefaultName; | ||
|
||
public void Configure(TargetOptions target) | ||
{ | ||
if (_configuration == null) | ||
{ | ||
Convert(target, _initialSource); | ||
} | ||
else | ||
{ | ||
var sourceOption = _configuration.GetSection(ServiceManagerOptions.Section).Get<ServiceManagerOptions>(); | ||
Convert(target, sourceOption); | ||
} | ||
} | ||
|
||
protected abstract void Convert(TargetOptions target, ServiceManagerOptions source); | ||
|
||
public IChangeToken GetChangeToken() => _configuration?.GetReloadToken() ?? NullChangeToken.Singleton; | ||
|
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
// Copyright (c) Microsoft. All rights reserved. | ||
// Licensed under the MIT license. See LICENSE file in the project root for full license information. | ||
|
||
using Microsoft.Azure.SignalR.Management.Configuration; | ||
using Microsoft.Extensions.Configuration; | ||
using Microsoft.Extensions.Options; | ||
|
||
namespace Microsoft.Azure.SignalR.Management | ||
{ | ||
internal class ServiceManagerContextSetup : CascadeOptionsSetup<ServiceManagerContext> | ||
{ | ||
public ServiceManagerContextSetup(IOptions<ServiceManagerOptions> initialSource, IConfiguration configuration = null) : base(initialSource, configuration) | ||
{ | ||
} | ||
|
||
protected override void Convert(ServiceManagerContext target, ServiceManagerOptions source) | ||
{ | ||
target.ServiceEndpoints = source.ServiceEndpoints ?? (source.ServiceEndpoint != null | ||
? (new ServiceEndpoint[] { source.ServiceEndpoint }) | ||
: (new ServiceEndpoint[] { new ServiceEndpoint(source.ConnectionString) })); | ||
target.ApplicationName = source.ApplicationName; | ||
target.ConnectionCount = source.ConnectionCount; | ||
target.Proxy = source.Proxy; | ||
target.ServiceTransportType = source.ServiceTransportType; | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,6 +3,7 @@ | |
|
||
using System; | ||
using System.Net; | ||
using Microsoft.Extensions.Configuration; | ||
|
||
namespace Microsoft.Azure.SignalR.Management | ||
{ | ||
|
@@ -11,6 +12,11 @@ namespace Microsoft.Azure.SignalR.Management | |
/// </summary> | ||
public class ServiceManagerOptions | ||
{ | ||
/// <summary> | ||
/// The section name used to get <see cref="ServiceManagerOptions"/> value from <see cref="IConfiguration.GetSection(string)"/> | ||
/// </summary> | ||
public const string Section = "Azure:SignalR"; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. intended to expose it to customer? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, I think customer can get the right position of configuration from |
||
|
||
/// <summary> | ||
/// Gets or sets the ApplicationName which will be prefixed to each hub name | ||
/// </summary> | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
// Copyright (c) Microsoft. All rights reserved. | ||
// Licensed under the MIT license. See LICENSE file in the project root for full license information. | ||
|
||
using Microsoft.Extensions.Configuration; | ||
using Microsoft.Extensions.FileProviders; | ||
using Microsoft.Extensions.Options; | ||
using Microsoft.Extensions.Primitives; | ||
|
||
namespace Microsoft.Azure.SignalR.Management.Configuration | ||
{ | ||
internal class ServiceManagerOptionsSetup : IConfigureOptions<ServiceManagerOptions>, IOptionsChangeTokenSource<ServiceManagerOptions> | ||
{ | ||
private readonly IConfiguration _configuration; | ||
|
||
public ServiceManagerOptionsSetup(IConfiguration configuration = null) | ||
{ | ||
_configuration = configuration; | ||
} | ||
|
||
public string Name => Options.DefaultName; | ||
|
||
public void Configure(ServiceManagerOptions options) | ||
{ | ||
if (_configuration != null) | ||
{ | ||
_configuration.GetSection(ServiceManagerOptions.Section).Bind(options); | ||
} | ||
} | ||
|
||
public IChangeToken GetChangeToken() | ||
{ | ||
return _configuration?.GetReloadToken() ?? NullChangeToken.Singleton; | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
// Copyright (c) Microsoft. All rights reserved. | ||
// Licensed under the MIT license. See LICENSE file in the project root for full license information. | ||
|
||
using Microsoft.Azure.SignalR.Management.Configuration; | ||
using Microsoft.Extensions.Configuration; | ||
using Microsoft.Extensions.Options; | ||
|
||
namespace Microsoft.Azure.SignalR.Management | ||
{ | ||
internal class ServiceOptionsSetup : CascadeOptionsSetup<ServiceOptions> | ||
{ | ||
public ServiceOptionsSetup(IOptions<ServiceManagerOptions> initialSource, IConfiguration configuration = null) : base(initialSource, configuration) | ||
{ | ||
} | ||
|
||
protected override void Convert(ServiceOptions target, ServiceManagerOptions source) | ||
{ | ||
target.ApplicationName = source.ApplicationName; | ||
target.Endpoints = source.ServiceEndpoints ?? (source.ServiceEndpoint != null | ||
? (new ServiceEndpoint[] { source.ServiceEndpoint }) | ||
: (new ServiceEndpoint[] { new ServiceEndpoint(source.ConnectionString) })); | ||
target.Proxy = source.Proxy; | ||
target.ConnectionCount = source.ConnectionCount; | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
// Copyright (c) Microsoft. All rights reserved. | ||
// Licensed under the MIT license. See LICENSE file in the project root for full license information. | ||
using System; | ||
using System.ComponentModel; | ||
using System.Reflection; | ||
using Microsoft.Azure.SignalR.Management.Configuration; | ||
using Microsoft.Extensions.DependencyInjection; | ||
using Microsoft.Extensions.Options; | ||
|
||
namespace Microsoft.Azure.SignalR.Management | ||
{ | ||
internal static class DependencyInjectionExtensions //TODO: not ready for public use | ||
{ | ||
/// <summary> | ||
/// Adds the essential SignalR Service Manager services to the specified services collection and configures <see cref="ServiceManagerOptions"/> with configuration instance registered in service collection. | ||
/// </summary> | ||
public static IServiceCollection AddSignalRServiceManager(this IServiceCollection services) | ||
{ | ||
services.AddSingleton<ServiceManagerOptionsSetup>() | ||
.AddSingleton<IConfigureOptions<ServiceManagerOptions>>(sp => sp.GetService<ServiceManagerOptionsSetup>()) | ||
.AddSingleton<IOptionsChangeTokenSource<ServiceManagerOptions>>(sp => sp.GetService<ServiceManagerOptionsSetup>()); | ||
services.PostConfigure<ServiceManagerOptions>(o => o.ValidateOptions()); | ||
services.AddSingleton<ServiceManagerContextSetup>() | ||
.AddSingleton<IConfigureOptions<ServiceManagerContext>>(sp => sp.GetService<ServiceManagerContextSetup>()) | ||
.AddSingleton<IOptionsChangeTokenSource<ServiceManagerContext>>(sp => sp.GetService<ServiceManagerContextSetup>()); | ||
services.AddSingleton<ServiceOptionsSetup>() | ||
.AddSingleton<IConfigureOptions<ServiceOptions>>(sp => sp.GetService<ServiceOptionsSetup>()) | ||
.AddSingleton<IOptionsChangeTokenSource<ServiceOptions>>(sp => sp.GetService<ServiceOptionsSetup>()); | ||
return services.TrySetProductInfo(); | ||
} | ||
|
||
/// <summary> | ||
/// Adds the essential SignalR Service Manager services to the specified services collection and registers an action used to configure <see cref="ServiceManagerOptions"/> | ||
/// </summary> | ||
public static IServiceCollection AddSignalRServiceManager(this IServiceCollection services, Action<ServiceManagerOptions> configure) | ||
{ | ||
services.Configure(configure); | ||
return services.AddSignalRServiceManager(); | ||
} | ||
|
||
/// <summary> | ||
/// Adds product info to <see cref="ServiceManagerContext"/> | ||
/// </summary> | ||
/// <returns></returns> | ||
[EditorBrowsable(EditorBrowsableState.Never)] | ||
public static IServiceCollection WithAssembly(this IServiceCollection services, Assembly assembly) | ||
{ | ||
var productInfo = ProductInfo.GetProductInfo(assembly); | ||
return services.Configure<ServiceManagerContext>(o => o.ProductInfo = productInfo); | ||
} | ||
|
||
private static IServiceCollection TrySetProductInfo(this IServiceCollection services) | ||
{ | ||
var assembly = Assembly.GetExecutingAssembly(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So this anyway executes even if There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, but if |
||
var productInfo = ProductInfo.GetProductInfo(assembly); | ||
return services.Configure<ServiceManagerContext>(o => o.ProductInfo = o.ProductInfo ?? productInfo); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,32 +4,47 @@ | |
using System; | ||
using System.ComponentModel; | ||
using System.Reflection; | ||
|
||
using Microsoft.Extensions.Configuration; | ||
using Microsoft.Extensions.DependencyInjection; | ||
using Microsoft.Extensions.Options; | ||
namespace Microsoft.Azure.SignalR.Management | ||
{ | ||
/// <summary> | ||
/// A builder for configuring <see cref="IServiceManager"/> instances. | ||
/// </summary> | ||
public class ServiceManagerBuilder : IServiceManagerBuilder | ||
public class ServiceManagerBuilder : IServiceManagerBuilder, IDisposable | ||
{ | ||
private readonly ServiceManagerOptions _options = new ServiceManagerOptions(); | ||
private readonly IServiceCollection _services = new ServiceCollection(); | ||
private Assembly _assembly; | ||
internal ServiceProvider ServiceProvider { get; private set; } | ||
|
||
/// <summary> | ||
/// Configures the <see cref="IServiceManager"/> instances. | ||
/// Registers an action used to configure <see cref="IServiceManager"/>. | ||
/// </summary> | ||
/// <param name="configure">A callback to configure the <see cref="IServiceManager"/>.</param> | ||
/// <returns>The same instance of the <see cref="ServiceManagerBuilder"/> for chaining.</returns> | ||
public ServiceManagerBuilder WithOptions(Action<ServiceManagerOptions> configure) | ||
{ | ||
configure?.Invoke(_options); | ||
_services.Configure(configure); | ||
return this; | ||
} | ||
|
||
/// <summary> | ||
/// Registers a configuration instance to configure <see cref="IServiceManager"/> | ||
/// </summary> | ||
/// <param name="config">The configuration instance.</param> | ||
/// <returns>The same instance of the <see cref="ServiceManagerBuilder"/> for chaining.</returns> | ||
internal ServiceManagerBuilder WithConfiguration(IConfiguration config) | ||
{ | ||
_services.AddSingleton(config); | ||
return this; | ||
} | ||
|
||
[EditorBrowsable(EditorBrowsableState.Never)] | ||
public ServiceManagerBuilder WithCallingAssembly() | ||
{ | ||
_assembly = Assembly.GetCallingAssembly(); | ||
_services.WithAssembly(_assembly); | ||
Y-Sindo marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return this; | ||
} | ||
|
||
|
@@ -39,16 +54,26 @@ public ServiceManagerBuilder WithCallingAssembly() | |
/// <returns>The instance of the <see cref="IServiceManager"/>.</returns> | ||
public IServiceManager Build() | ||
{ | ||
_options.ValidateOptions(); | ||
if (ServiceProvider != null) | ||
{ | ||
throw new InvalidOperationException($"Mulitple invocation of the method is not allowed."); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why we have such restriction is because we want UT to test Should the following case be a valid one? var manager1 = builder.WithOptions(o=>o.ConnectionString="one").Build();
var manager2 = builder.WithOptions(o=>o.ConnectionString="another").Build(); There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Now |
||
} | ||
|
||
_services.AddSignalRServiceManager(); | ||
ServiceProvider = _services.BuildServiceProvider(); | ||
var context = ServiceProvider.GetRequiredService<IOptions<ServiceManagerContext>>().Value; | ||
var productInfo = ProductInfo.GetProductInfo(_assembly); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. read from context? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fixed |
||
var context = new ServiceManagerContext() | ||
{ | ||
ProductInfo = productInfo | ||
}; | ||
Y-Sindo marked this conversation as resolved.
Show resolved
Hide resolved
|
||
context.SetValueFromOptions(_options); | ||
var restClientBuilder = new RestClientFactory(productInfo); | ||
return new ServiceManager(context, restClientBuilder); | ||
} | ||
|
||
/// <summary> | ||
/// Dispose unmanaged resources accociated with the builder and the instance built from it. | ||
/// </summary> | ||
public void Dispose() | ||
{ | ||
ServiceProvider?.Dispose(); | ||
ServiceProvider = null; | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what needs to be hot-reloaded is the multiple endpoint configure? So is it that we only need to track
ServiceManagerOptions
=>ServiceOptions
change? Does it work ifServiceManagerContext
inheritsServiceOptions
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, good idea.