diff --git a/Masa.Framework.sln.DotSettings b/Masa.Framework.sln.DotSettings index 1e0e7b13e..38420906f 100644 --- a/Masa.Framework.sln.DotSettings +++ b/Masa.Framework.sln.DotSettings @@ -1,5 +1,6 @@  True + True True True True \ No newline at end of file diff --git a/src/BuildingBlocks/Development/Masa.BuildingBlocks.Development.DaprStarter/Constant.cs b/src/BuildingBlocks/Development/Masa.BuildingBlocks.Development.DaprStarter/DaprStarterConstant.cs similarity index 73% rename from src/BuildingBlocks/Development/Masa.BuildingBlocks.Development.DaprStarter/Constant.cs rename to src/BuildingBlocks/Development/Masa.BuildingBlocks.Development.DaprStarter/DaprStarterConstant.cs index e32be40df..8225a0d9b 100644 --- a/src/BuildingBlocks/Development/Masa.BuildingBlocks.Development.DaprStarter/Constant.cs +++ b/src/BuildingBlocks/Development/Masa.BuildingBlocks.Development.DaprStarter/DaprStarterConstant.cs @@ -3,11 +3,15 @@ namespace Masa.BuildingBlocks.Development.DaprStarter; -public static class Constant +public static class DaprStarterConstant { public const string DEFAULT_APPID_DELIMITER = "-"; - public const string DEFAULT_FILE_NAME = "dapr"; + public const string DEFAULT_DAPR_FILE_NAME = "dapr"; + + public const string DEFAULT_FILE_NAME = "daprd"; + + public const string DEFAULT_PROCESS_NAME = "dapr-starter"; public const string DEFAULT_ARGUMENT_PREFIX = "--"; diff --git a/src/BuildingBlocks/Development/Masa.BuildingBlocks.Development.DaprStarter/IDaprProcess.cs b/src/BuildingBlocks/Development/Masa.BuildingBlocks.Development.DaprStarter/IDaprProcess.cs index db173dd12..d8f39e0e3 100644 --- a/src/BuildingBlocks/Development/Masa.BuildingBlocks.Development.DaprStarter/IDaprProcess.cs +++ b/src/BuildingBlocks/Development/Masa.BuildingBlocks.Development.DaprStarter/IDaprProcess.cs @@ -3,6 +3,9 @@ namespace Masa.BuildingBlocks.Development.DaprStarter; +/// +/// Manage dapr sidecar start or stop +/// public interface IDaprProcess : IDisposable { void Start(); diff --git a/src/BuildingBlocks/Development/Masa.BuildingBlocks.Development.DaprStarter/Options/DaprOptions.cs b/src/BuildingBlocks/Development/Masa.BuildingBlocks.Development.DaprStarter/Options/DaprOptions.cs index 387ed3720..0646e883d 100644 --- a/src/BuildingBlocks/Development/Masa.BuildingBlocks.Development.DaprStarter/Options/DaprOptions.cs +++ b/src/BuildingBlocks/Development/Masa.BuildingBlocks.Development.DaprStarter/Options/DaprOptions.cs @@ -5,18 +5,19 @@ namespace Masa.BuildingBlocks.Development.DaprStarter; +#pragma warning disable S3236 /// /// dapr startup configuration information /// When the specified attribute is configured as null, the default value of the parameter is subject to the default value of dapr of the current version /// -public class DaprOptions +public class DaprOptions : DaprOptionsBase { /// /// The id for your application, used for service discovery /// public string? AppId { get; set; } - private string _appIdDelimiter = Constant.DEFAULT_APPID_DELIMITER; + private string _appIdDelimiter = DaprStarterConstant.DEFAULT_APPID_DELIMITER; /// /// Separator used to splice AppId and AppIdSuffix @@ -58,76 +59,31 @@ public string? AppIdSuffix /// public bool DisableAppIdSuffix { get; set; } - private int? _maxConcurrency; + private int? _maxConcurrency = -1; /// /// The concurrency level of the application, otherwise is unlimited - /// Must be greater than 0 + /// Must be greater than or equal -1 /// - public int? MaxConcurrency + public override int? MaxConcurrency { get => _maxConcurrency; set { if (value != null) - MasaArgumentException.ThrowIfLessThanOrEqual(value.Value, (ushort)0, nameof(MaxConcurrency)); + MasaArgumentException.ThrowIfLessThan(value.Value, -1, nameof(MaxConcurrency)); _maxConcurrency = value; } } - private ushort? _appPort; - - /// - /// The port your application is listening on - /// Required. Must be between 0-65535 - /// - public ushort? AppPort - { - get => _appPort; - set - { - if (value != null) - MasaArgumentException.ThrowIfLessThanOrEqual(value.Value, (ushort)0, nameof(AppPort)); - - _appPort = value; - } - } - - /// - /// The protocol (gRPC or HTTP) Dapr uses to talk to the application. Valid values are: http or grpc - /// - public Protocol? AppProtocol { get; set; } - - /// - /// Enable https when Dapr invokes the application - /// default: null (don't use https) - /// - public bool? EnableSsl { get; set; } - - /// - /// Dapr configuration file - /// default: - /// Linux & Mac: $HOME/.dapr/config.yaml - /// Windows: %USERPROFILE%\.dapr\config.yaml - /// - public string? Config { get; set; } - - /// - /// The path for components directory - /// default: - /// Linux & Mac: $HOME/.dapr/components - /// Windows: %USERPROFILE%\.dapr\components - /// - public string? ComponentPath { get; set; } - private ushort? _daprGrpcPort; /// /// The gRPC port for Dapr to listen on /// Must be greater than 0 /// - public ushort? DaprGrpcPort + public override ushort? DaprGrpcPort { get => _daprGrpcPort; set @@ -145,7 +101,7 @@ public ushort? DaprGrpcPort /// The HTTP port for Dapr to listen on /// Must be greater than 0 /// - public ushort? DaprHttpPort + public override ushort? DaprHttpPort { get => _daprHttpPort; set @@ -157,39 +113,13 @@ public ushort? DaprHttpPort } } - /// - /// Enable pprof profiling via an HTTP endpoint - /// - public bool? EnableProfiling { get; set; } - - /// - /// The image to build the code in. Input is: repository/image - /// - public string? Image { get; set; } - - /// - /// The log verbosity. Valid values are: debug, info, warn, error, fatal, or panic - /// default: info - /// - public LogLevel? LogLevel { get; set; } - - /// - /// default: localhost - /// - public string? PlacementHostAddress { get; set; } - - /// - /// Address for the Sentry CA service - /// - public string? SentryAddress { get; set; } - private ushort? _metricsPort; /// /// The port that Dapr sends its metrics information to /// Must be greater than 0 /// - public ushort? MetricsPort + public override ushort? MetricsPort { get => _metricsPort; set @@ -207,7 +137,7 @@ public ushort? MetricsPort /// The port for the profile server to listen on /// Must be greater than 0 /// - public ushort? ProfilePort + public override ushort? ProfilePort { get => _profilePort; set @@ -219,39 +149,32 @@ public ushort? ProfilePort } } - /// - /// Path to a unix domain socket dir mount. If specified - /// communication with the Dapr sidecar uses unix domain sockets for lower latency and greater throughput when compared to using TCP ports - /// Not available on Windows OS - /// - public string? UnixDomainSocket { get; set; } - - private int? _daprMaxRequestSize; + private int? _daprHttpMaxRequestSize; /// /// Max size of request body in MB. /// Must be greater than 0 /// - public int? DaprMaxRequestSize + public int? DaprHttpMaxRequestSize { - get => _daprMaxRequestSize; + get => _daprHttpMaxRequestSize; set { if (value != null) - MasaArgumentException.ThrowIfLessThanOrEqual(value.Value, (ushort)0, nameof(DaprMaxRequestSize)); + MasaArgumentException.ThrowIfLessThanOrEqual(value.Value, (ushort)0, nameof(DaprHttpMaxRequestSize)); - _daprMaxRequestSize = value; + _daprHttpMaxRequestSize = value; } } - private int _heartBeatInterval = Constant.DEFAULT_HEARTBEAT_INTERVAL; + private int _heartBeatInterval = DaprStarterConstant.DEFAULT_HEARTBEAT_INTERVAL; /// /// Heartbeat detection interval, used to detect dapr status /// default: 5000 ms /// Must be greater than 0 /// - public int HeartBeatInterval + public override int HeartBeatInterval { get => _heartBeatInterval; set @@ -262,16 +185,59 @@ public int HeartBeatInterval } } + public string PlacementHostAddress { get; set; } + /// /// Start the heartbeat check to ensure that the dapr program is active. /// When the heartbeat check is turned off, dapr will not start automatically after it exits abnormally. /// - public bool EnableHeartBeat { get; set; } = true; + public override bool EnableHeartBeat { get; set; } = true; - public bool CreateNoWindow { get; set; } = true; + public override bool CreateNoWindow { get; set; } = true; + + /// + /// Allowed HTTP origins (default "*") + /// + public string AllowedOrigins { get; set; } + + /// + /// Address for a Dapr control plane + /// + public string ControlPlaneAddress { get; set; } + + /// + /// Increasing max size of read buffer in KB to handle sending multi-KB headers (default 4) + /// + public int? DaprHttpReadBufferSize { get; set; } + + /// + /// gRPC port for the Dapr Internal API to listen on. + /// + public int? DaprInternalGrpcPort { get; set; } + + /// + /// Enable API logging for API calls + /// + public bool? EnableApiLogging { get; set; } + + /// + /// Enable prometheus metric (default true) + /// + public bool? EnableMetrics { get; set; } + + /// + /// Runtime mode for Dapr (default "standalone") + /// + public string Mode { get; set; } + + /// + /// Extended parameters, used to supplement unsupported parameters + /// + public string ExtendedParameter { get; set; } public bool IsIncompleteAppId() { return !DisableAppIdSuffix && (AppIdSuffix == null || AppIdSuffix.Trim() != string.Empty); } } +#pragma warning restore S3236 diff --git a/src/BuildingBlocks/Development/Masa.BuildingBlocks.Development.DaprStarter/Options/DaprOptionsBase.cs b/src/BuildingBlocks/Development/Masa.BuildingBlocks.Development.DaprStarter/Options/DaprOptionsBase.cs new file mode 100644 index 000000000..530bb9b57 --- /dev/null +++ b/src/BuildingBlocks/Development/Masa.BuildingBlocks.Development.DaprStarter/Options/DaprOptionsBase.cs @@ -0,0 +1,125 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +// ReSharper disable once CheckNamespace + +namespace Masa.BuildingBlocks.Development.DaprStarter; + +#pragma warning disable S3236 +public abstract class DaprOptionsBase +{ + private ushort? _appPort; + + /// + /// The port your application is listening on + /// Required. Must be between 0-65535 + /// + public ushort? AppPort + { + get => _appPort; + set + { + if (value != null) + MasaArgumentException.ThrowIfLessThanOrEqual(value.Value, (ushort)0, nameof(AppPort)); + + _appPort = value; + } + } + + /// + /// The protocol (gRPC or HTTP) Dapr uses to talk to the application. Valid values are: http or grpc + /// default: HTTP + /// + public Protocol? AppProtocol { get; protected set; } = Protocol.Http; + + /// + /// Enable https when Dapr invokes the application + /// Sets the URI scheme of the app to https and attempts an SSL connection + /// + public bool? EnableSsl { get; set; } + + /// + /// The gRPC port for Dapr to listen on + /// + // ReSharper disable once InconsistentNaming + public virtual ushort? DaprGrpcPort { get; set; } + + /// + /// The HTTP port for Dapr to listen on + /// + public virtual ushort? DaprHttpPort { get; set; } + + public virtual bool EnableHeartBeat { get; set; } + + public virtual int HeartBeatInterval { get; set; } + + public virtual bool CreateNoWindow { get; set; } + + /// + /// The concurrency level of the application, otherwise is unlimited + /// + public virtual int? MaxConcurrency { get; set; } + + /// + /// Dapr configuration file + /// default: config.yaml + /// The actual components-path is equal to Path.Combine(options.RootPath, options.Config) + /// + public string Config { get; set; } + + /// + /// The path for components directory + /// default: components + /// The actual components-path is equal to Path.Combine(options.RootPath, options.ComponentPath) + /// + public string ComponentPath { get; set; } + + /// + /// The root address of dapr configuration + /// daprd, dapr runtime required component configuration path + /// default:Linux/Mac: $HOME/.dapr/ + /// Windows: %USERPROFILE%\.dapr\ + /// + public string RootPath { get; set; } + + /// + /// The path to the dapr directory + /// defdult: Linux/Mac: /usr/local/bin + /// Windows: C:\dapr + /// + public string DaprRootPath { get; set; } + + /// + /// Enable pprof profiling via an HTTP endpoint + /// + public bool? EnableProfiling { get; set; } + + /// + /// The log verbosity. Valid values are: debug, info, warn, error, fatal, or panic + /// default: info + /// + public LogLevel? LogLevel { get; set; } + + /// + /// Address for the Sentry CA service + /// + public string? SentryAddress { get; set; } + + /// + /// The port that Dapr sends its metrics information to + /// + public virtual ushort? MetricsPort { get; set; } + + /// + /// The port for the profile server to listen on + /// + public virtual ushort? ProfilePort { get; set; } + + /// + /// Path to a unix domain socket dir mount. If specified + /// communication with the Dapr sidecar uses unix domain sockets for lower latency and greater throughput when compared to using TCP ports + /// Not available on Windows OS + /// + public string? UnixDomainSocket { get; set; } +} +#pragma warning restore S3236 diff --git a/src/Contrib/Development/Masa.Contrib.Development.DaprStarter.AspNetCore/DaprBackgroundService.cs b/src/Contrib/Development/Masa.Contrib.Development.DaprStarter.AspNetCore/DaprBackgroundService.cs index 62cfab234..b57edc807 100644 --- a/src/Contrib/Development/Masa.Contrib.Development.DaprStarter.AspNetCore/DaprBackgroundService.cs +++ b/src/Contrib/Development/Masa.Contrib.Development.DaprStarter.AspNetCore/DaprBackgroundService.cs @@ -11,18 +11,21 @@ public class DaprBackgroundService : BackgroundService private readonly DaprOptions _options; private readonly IHostApplicationLifetime _hostApplicationLifetime; private readonly ILogger? _logger; + private readonly IServiceProvider _serviceProvider; public DaprBackgroundService( IAppPortProvider appPortProvider, IDaprProcess daprProcess, IOptionsMonitor options, IHostApplicationLifetime hostApplicationLifetime, + IServiceProvider serviceProvider, ILogger? logger) { _appPortProvider = appPortProvider; _daprProcess = daprProcess; _options = options.CurrentValue; _hostApplicationLifetime = hostApplicationLifetime; + _serviceProvider = serviceProvider; _logger = logger; } @@ -41,21 +44,36 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) { _logger?.LogInformation("{Name} is Starting ...", nameof(DaprBackgroundService)); - CheckCompletionAppPort(_options); + CheckCompletionAppPortAndEnableSSl(_options); + PortUtils.CheckCompletionPort(_options, _serviceProvider); _daprProcess.Start(); } } - private void CheckCompletionAppPort(DaprOptions daprOptions) + private void CheckCompletionAppPortAndEnableSSl(DaprOptions daprOptions) { - if (daprOptions.AppPort == null) + if (daprOptions.AppPort == null || daprOptions.EnableSsl == null) { - CompletionAppPort(daprOptions); + if (daprOptions.EnableSsl == null && daprOptions.AppPort != null) + { + daprOptions.EnableSsl = _appPortProvider.GetEnableSsl(daprOptions.AppPort.Value); + } + else + { + CompletionAppPortAndEnableSSl(daprOptions); + } + } + else + { + if (daprOptions.EnableSsl != _appPortProvider.GetEnableSsl(daprOptions.AppPort.Value)) + { + throw new UserFriendlyException($"The current AppPort: {daprOptions.AppPort.Value} is not an {(daprOptions.EnableSsl is true ? "Https" : "Http")} port, Dapr failed to start"); + } } } - private void CompletionAppPort(DaprOptions daprOptions) + private void CompletionAppPortAndEnableSSl(DaprOptions daprOptions) { var item = _appPortProvider.GetAppPort(daprOptions.EnableSsl); if (daprOptions.EnableSsl == null) diff --git a/src/Contrib/Development/Masa.Contrib.Development.DaprStarter.AspNetCore/DefaultAppPortProvider.cs b/src/Contrib/Development/Masa.Contrib.Development.DaprStarter.AspNetCore/DefaultAppPortProvider.cs index fcc04b556..6eace1372 100644 --- a/src/Contrib/Development/Masa.Contrib.Development.DaprStarter.AspNetCore/DefaultAppPortProvider.cs +++ b/src/Contrib/Development/Masa.Contrib.Development.DaprStarter.AspNetCore/DefaultAppPortProvider.cs @@ -9,7 +9,36 @@ public class DefaultAppPortProvider : IAppPortProvider public DefaultAppPortProvider(IServer server) => _server = server; + public bool GetEnableSsl(ushort appPort) + { + var ports = GetPorts(); + if (ports.Any(p => p.Port == appPort)) + { + var port = ports.First(p => p.Port == appPort); + return port.Scheme.Equals(Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase); + } + + throw new UserFriendlyException($"The current port {appPort} is unavailable, Dapr failed to start"); + } + public (bool EnableSsl, ushort AppPort) GetAppPort(bool? enableSsl) + { + var ports = GetPorts(); + + if (ports.Count == 1) + { + return new(ports[0].Scheme.Equals(Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase), (ushort)ports[0].Item2); + } + + if (enableSsl is false && ports.Any(p => p.Scheme.Equals(Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase))) + { + return new(false, GetAppPort(ports, false)); + } + + return new(true, GetAppPort(ports, true)); + } + + private List<(string Scheme, int Port)> GetPorts() { var addresses = _server.Features.Get()?.Addresses; if (addresses is { IsReadOnly: false, Count: 0 }) @@ -19,19 +48,9 @@ public class DefaultAppPortProvider : IAppPortProvider .Select(address => new Uri(address)) .Where(address => address.Scheme.Equals(Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase) - || address.Scheme.Equals(Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase)) + || address.Scheme.Equals(Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase)) .Select(address => new ValueTuple(address.Scheme, address.Port)).ToList(); - - if (ports.Count == 1) - { - return new(ports[0].Item1.Equals(Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase), (ushort)ports[0].Item2); - } - - if (enableSsl is true && ports.Any(p => p.Item1.Equals(Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase))) - { - return new(true, GetAppPort(ports, true)); - } - return new(false, GetAppPort(ports, false)); + return ports; } public static ushort GetAppPort(List> ports, bool enableSsl) @@ -43,6 +62,7 @@ public static ushort GetAppPort(List> ports, bool enable .Select(p => (ushort)p.Item2) .FirstOrDefault(); } + return ports .Where(p => p.Item1.Equals(Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase)) .Select(p => (ushort)p.Item2) diff --git a/src/Contrib/Development/Masa.Contrib.Development.DaprStarter.AspNetCore/DefaultAvailabilityPortProvider.cs b/src/Contrib/Development/Masa.Contrib.Development.DaprStarter.AspNetCore/DefaultAvailabilityPortProvider.cs new file mode 100644 index 000000000..87b3a2bc9 --- /dev/null +++ b/src/Contrib/Development/Masa.Contrib.Development.DaprStarter.AspNetCore/DefaultAvailabilityPortProvider.cs @@ -0,0 +1,41 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Contrib.Development.DaprStarter.AspNetCore; + +[ExcludeFromCodeCoverage] +public class DefaultAvailabilityPortProvider : IAvailabilityPortProvider +{ + private readonly ILogger? _logger; + + public DefaultAvailabilityPortProvider(ILogger? logger = null) + { + _logger = logger; + } + + public ushort? GetAvailablePort(ushort startingPort, IEnumerable? reservedPorts = null) + { + MasaArgumentException.ThrowIfGreaterThan(startingPort, ushort.MaxValue); + try + { + var ipGlobalProperties = IPGlobalProperties.GetIPGlobalProperties(); + + var connectionsEndpoints = ipGlobalProperties.GetActiveTcpConnections().Select(c => c.LocalEndPoint); + var ports = connectionsEndpoints.Concat(ipGlobalProperties.GetActiveTcpListeners()) + .Concat(ipGlobalProperties.GetActiveUdpListeners()) + .Select(point => point.Port) + .ToList(); + if (reservedPorts != null) + { + ports.AddRange(reservedPorts); + } + + return (ushort)Enumerable.Range(startingPort, ushort.MaxValue - startingPort + 1).Except(ports).FirstOrDefault(); + } + catch (Exception ex) + { + _logger?.LogWarning(ex, "Failed to get available ports, startingPort: {StartingPort}", startingPort); + return null; + } + } +} diff --git a/src/Contrib/Development/Masa.Contrib.Development.DaprStarter.AspNetCore/IAppPortProvider.cs b/src/Contrib/Development/Masa.Contrib.Development.DaprStarter.AspNetCore/IAppPortProvider.cs index c2490405b..fe5e8b9db 100644 --- a/src/Contrib/Development/Masa.Contrib.Development.DaprStarter.AspNetCore/IAppPortProvider.cs +++ b/src/Contrib/Development/Masa.Contrib.Development.DaprStarter.AspNetCore/IAppPortProvider.cs @@ -5,5 +5,7 @@ namespace Masa.Contrib.Development.DaprStarter.AspNetCore; public interface IAppPortProvider { + bool GetEnableSsl(ushort appPort); + (bool EnableSsl, ushort AppPort) GetAppPort(bool? enableSsl); } diff --git a/src/Contrib/Development/Masa.Contrib.Development.DaprStarter.AspNetCore/IAvailabilityPortProvider.cs b/src/Contrib/Development/Masa.Contrib.Development.DaprStarter.AspNetCore/IAvailabilityPortProvider.cs new file mode 100644 index 000000000..99428c9e5 --- /dev/null +++ b/src/Contrib/Development/Masa.Contrib.Development.DaprStarter.AspNetCore/IAvailabilityPortProvider.cs @@ -0,0 +1,9 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Contrib.Development.DaprStarter.AspNetCore; + +public interface IAvailabilityPortProvider +{ + ushort? GetAvailablePort(ushort startingPort, IEnumerable? reservedPorts = null); +} diff --git a/src/Contrib/Development/Masa.Contrib.Development.DaprStarter.AspNetCore/Internal/PortUtils.cs b/src/Contrib/Development/Masa.Contrib.Development.DaprStarter.AspNetCore/Internal/PortUtils.cs new file mode 100644 index 000000000..ae0502df1 --- /dev/null +++ b/src/Contrib/Development/Masa.Contrib.Development.DaprStarter.AspNetCore/Internal/PortUtils.cs @@ -0,0 +1,57 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +// ReSharper disable once CheckNamespace + +namespace Masa.Contrib.Development.DaprStarter.AspNetCore; + +internal static class PortUtils +{ + public static void CheckCompletionPort(DaprOptions daprOptions, IServiceProvider serviceProvider) + { + var daprEnvironmentProvider = serviceProvider.GetRequiredService(); + + daprOptions.DaprHttpPort ??= daprEnvironmentProvider.GetHttpPort(); + daprOptions.DaprGrpcPort ??= daprEnvironmentProvider.GetGrpcPort(); + + var reservedPorts = new List(); + AddReservedPorts(daprOptions.DaprHttpPort); + AddReservedPorts(daprOptions.DaprGrpcPort); + AddReservedPorts(daprOptions.MetricsPort); + AddReservedPorts(daprOptions.ProfilePort); + AddReservedPorts(daprOptions.AppPort); + + var availabilityPortProvider = serviceProvider.GetRequiredService(); + + daprEnvironmentProvider.TrySetHttpPort(GetPortAndAddReservedPortsByAvailability(daprOptions.DaprHttpPort, 3500)); + daprEnvironmentProvider.TrySetGrpcPort(GetPortAndAddReservedPortsByAvailability(daprOptions.DaprGrpcPort, 5001)); + daprEnvironmentProvider.TrySetMetricsPort(GetPortAndAddReservedPortsByAvailability(daprOptions.MetricsPort, 9090)); + + // Environment variables need to be improved + bool IsAvailablePort([NotNullWhen(true)] ushort? port) + { + return port is > 0; + } + + void AddReservedPorts(ushort? port) + { + if (port is > 0) reservedPorts.Add(port.Value); + } + + ushort? GetPortAndAddReservedPortsByAvailability(ushort? port, ushort startingPort) + { + ushort? portByAvailability; + if (IsAvailablePort(port)) + { + portByAvailability = port.Value; + } + else + { + portByAvailability = availabilityPortProvider.GetAvailablePort(startingPort, reservedPorts); + if (portByAvailability != null) reservedPorts.Add(portByAvailability.Value); + } + + return portByAvailability; + } + } +} diff --git a/src/Contrib/Development/Masa.Contrib.Development.DaprStarter.AspNetCore/ServiceCollectionExtensions.cs b/src/Contrib/Development/Masa.Contrib.Development.DaprStarter.AspNetCore/ServiceCollectionExtensions.cs index bf279c6f2..77ddbe16f 100644 --- a/src/Contrib/Development/Masa.Contrib.Development.DaprStarter.AspNetCore/ServiceCollectionExtensions.cs +++ b/src/Contrib/Development/Masa.Contrib.Development.DaprStarter.AspNetCore/ServiceCollectionExtensions.cs @@ -49,15 +49,16 @@ private static IServiceCollection AddDaprStarter(this IServiceCollection service services.AddSingleton(); services.TryAddSingleton(); + services.TryAddSingleton(); action.Invoke(); var serviceProvider = services.BuildServiceProvider(); var options = serviceProvider.GetRequiredService>(); - var daprEnvironmentProvider = serviceProvider.GetRequiredService(); - daprEnvironmentProvider.CompleteDaprEnvironment(options.CurrentValue.DaprHttpPort, options.CurrentValue.DaprGrpcPort); if (isDelay) return services.AddHostedService(); + PortUtils.CheckCompletionPort(options.CurrentValue, serviceProvider); + ArgumentNullException.ThrowIfNull(options.CurrentValue.AppPort); var daprProcess = serviceProvider.GetRequiredService(); daprProcess.Start(); @@ -65,8 +66,9 @@ private static IServiceCollection AddDaprStarter(this IServiceCollection service } + + private sealed class DaprService { - } } diff --git a/src/Contrib/Development/Masa.Contrib.Development.DaprStarter.AspNetCore/_Imports.cs b/src/Contrib/Development/Masa.Contrib.Development.DaprStarter.AspNetCore/_Imports.cs index fc7e9a731..8b11e86ae 100644 --- a/src/Contrib/Development/Masa.Contrib.Development.DaprStarter.AspNetCore/_Imports.cs +++ b/src/Contrib/Development/Masa.Contrib.Development.DaprStarter.AspNetCore/_Imports.cs @@ -2,13 +2,14 @@ // Licensed under the MIT License. See LICENSE.txt in the project root for license information. global using Masa.BuildingBlocks.Development.DaprStarter; -global using Masa.Contrib.Development.DaprStarter; global using Masa.Contrib.Development.DaprStarter.AspNetCore; global using Microsoft.AspNetCore.Hosting.Server; global using Microsoft.AspNetCore.Hosting.Server.Features; global using Microsoft.Extensions.Configuration; +global using Microsoft.Extensions.DependencyInjection; global using Microsoft.Extensions.DependencyInjection.Extensions; global using Microsoft.Extensions.Hosting; global using Microsoft.Extensions.Logging; global using Microsoft.Extensions.Options; global using System.Diagnostics.CodeAnalysis; +global using System.Net.NetworkInformation; diff --git a/src/Contrib/Development/Masa.Contrib.Development.DaprStarter/CommandLineBuilder.cs b/src/Contrib/Development/Masa.Contrib.Development.DaprStarter/CommandLineBuilder.cs index 80704841f..a05d2157b 100644 --- a/src/Contrib/Development/Masa.Contrib.Development.DaprStarter/CommandLineBuilder.cs +++ b/src/Contrib/Development/Masa.Contrib.Development.DaprStarter/CommandLineBuilder.cs @@ -15,11 +15,11 @@ public CommandLineBuilder(string prefix) Arguments = new(); } - public CommandLineBuilder Add(string name, string value, bool isSkip = false) + public CommandLineBuilder Add(string name, Func valueFunc, bool isSkip = false) { if (!isSkip) { - Arguments.Add($"{Prefix}{name} {value}"); + Arguments.Add($"{Prefix}{name} {valueFunc.Invoke()}"); } return this; diff --git a/src/Contrib/Development/Masa.Contrib.Development.DaprStarter/DaprEnvironmentProvider.cs b/src/Contrib/Development/Masa.Contrib.Development.DaprStarter/DaprEnvironmentProvider.cs index bb1d11e7a..68042bc56 100644 --- a/src/Contrib/Development/Masa.Contrib.Development.DaprStarter/DaprEnvironmentProvider.cs +++ b/src/Contrib/Development/Masa.Contrib.Development.DaprStarter/DaprEnvironmentProvider.cs @@ -9,10 +9,14 @@ public class DaprEnvironmentProvider : IDaprEnvironmentProvider private const string HTTP_PORT = "DAPR_HTTP_PORT"; + private const string METRICS_PORT = "DAPR_METRICS_PORT"; + public ushort? GetHttpPort() => GetEnvironmentVariable(HTTP_PORT); public ushort? GetGrpcPort() => GetEnvironmentVariable(GRPC_PORT); + public ushort? GetMetricsPort() => GetEnvironmentVariable(METRICS_PORT); + private static ushort? GetEnvironmentVariable(string environment) { if (ushort.TryParse(Environment.GetEnvironmentVariable(environment), out ushort port)) @@ -25,33 +29,32 @@ public bool TrySetHttpPort(ushort? httpPort) { if (httpPort is > 0) { - SetHttpPort(httpPort.Value); + Environment.SetEnvironmentVariable(HTTP_PORT, httpPort.Value.ToString()); return true; } + return false; } - // ReSharper disable once InconsistentNaming public bool TrySetGrpcPort(ushort? grpcPort) { if (grpcPort is > 0) { - SetGrpcPort(grpcPort.Value); + Environment.SetEnvironmentVariable(GRPC_PORT, grpcPort.Value.ToString()); return true; } + return false; } - public void SetHttpPort(ushort httpPort) => Environment.SetEnvironmentVariable(HTTP_PORT, httpPort.ToString()); - - // ReSharper disable once InconsistentNaming - public void SetGrpcPort(ushort grpcPort) => Environment.SetEnvironmentVariable(GRPC_PORT, grpcPort.ToString()); - - // ReSharper disable once InconsistentNaming - public void CompleteDaprEnvironment(ushort? httpPort, ushort? grpcPort) + public bool TrySetMetricsPort(ushort? metricsPort) { - if (grpcPort is > 0) SetGrpcPort(grpcPort.Value); + if (metricsPort is > 0) + { + Environment.SetEnvironmentVariable(METRICS_PORT, metricsPort.Value.ToString()); + return true; + } - if (httpPort is > 0) SetHttpPort(httpPort.Value); + return false; } } diff --git a/src/Contrib/Development/Masa.Contrib.Development.DaprStarter/DaprProcess.cs b/src/Contrib/Development/Masa.Contrib.Development.DaprStarter/DaprProcess.cs index b4f6c9871..ae8028868 100644 --- a/src/Contrib/Development/Masa.Contrib.Development.DaprStarter/DaprProcess.cs +++ b/src/Contrib/Development/Masa.Contrib.Development.DaprStarter/DaprProcess.cs @@ -8,62 +8,67 @@ public class DaprProcess : DaprProcessBase, IDaprProcess { private readonly object _lock = new(); - private readonly IDaprProvider _daprProvider; + private readonly IDaprProcessProvider _daprProcessProvider; private readonly IProcessProvider _processProvider; private readonly ILogger? _logger; - private readonly IOptionsMonitor _daprOptions; private DaprProcessStatus Status { get; set; } - private System.Timers.Timer? _heartBeatTimer; - private Process? _process; - private int _retryTime; /// - /// record whether dapr is initialized for the first time + /// A sidecar that is terminated by the user or the system can only be started by start /// - private bool _isFirst = true; + private bool _isStopByManually; + + private System.Timers.Timer? _heartBeatTimer; + private Process? _process; + private int _retryTime; public DaprProcess( - IDaprProvider daprProvider, + IDaprProcessProvider daprProcessProvider, IProcessProvider processProvider, IOptionsMonitor daprOptions, IDaprEnvironmentProvider daprEnvironmentProvider, - ILogger? logger = null, - IOptions? masaAppConfigureOptions = null) : base(daprEnvironmentProvider, masaAppConfigureOptions) + IDaprProvider daprProvide, + ILogger? logger = null) + : base(daprEnvironmentProvider, daprProvide, daprOptions) { - _daprProvider = daprProvider; + _daprProcessProvider = daprProcessProvider; _processProvider = processProvider; _logger = logger; - _daprOptions = daprOptions; daprOptions.OnChange(Refresh); } public void Start() { + if (Status == DaprProcessStatus.Started) + { + _logger?.LogInformation("The sidecar has been successfully started. If you want to restart, please stop and start again"); + return; + } + lock (_lock) { - var options = ConvertToDaprCoreOptions(_daprOptions.CurrentValue); + _isStopByManually = false; + var sidecarOptions = ConvertToSidecarOptions(DaprOptions.CurrentValue); - StartCore(options); + StartCore(sidecarOptions); } } - private void StartCore(DaprCoreOptions options) + private void StartCore(SidecarOptions options) { UpdateStatus(DaprProcessStatus.Starting); var commandLineBuilder = CreateCommandLineBuilder(options); - StopCore(SuccessDaprOptions); - - if (_isFirst) - { - CompleteDaprEnvironment(options.DaprHttpPort, options.DaprGrpcPort); - } - _process = _daprProvider.DaprStart( + _process = _daprProcessProvider.DaprStart( + GetDefaultSidecarFileName(), commandLineBuilder.ToString(), options.CreateNoWindow, (_, args) => { - if (_isFirst) CheckAndCompleteDaprEnvironment(args.Data); + if (args.Data == null) + return; + + if (IsFirst) CheckAndCompleteDaprEnvironment(args.Data); }, () => UpdateStatus(DaprProcessStatus.Stopped)); _retryTime = 0; @@ -81,96 +86,123 @@ private void StartCore(DaprCoreOptions options) { _heartBeatTimer?.Start(); } - } - public void CompleteDaprEnvironment(ushort? httpPort, ushort? grpcPort) - { - var setHttpPortResult = DaprEnvironmentProvider.TrySetHttpPort(httpPort); - if (setHttpPortResult) - { - SuccessDaprOptions!.TrySetHttpPort(httpPort); - _logger?.LogInformation("Update Dapr environment variables, DaprHttpPort: {HttpPort}", httpPort); - } + // Register the child process to the job object to ensure that the child process terminates when the parent process terminates + // Windows only - var setGrpcPortResult = DaprEnvironmentProvider.TrySetGrpcPort(grpcPort); - if (setGrpcPortResult) + if (Environment.OSVersion.Platform != PlatformID.Win32NT) { - SuccessDaprOptions!.TrySetGrpcPort(grpcPort); - _logger?.LogInformation("Update Dapr environment variables, DAPR_GRPC_PORT: {grpcPort}", grpcPort); + //Only supported on windows + return; } - - if (setHttpPortResult && setGrpcPortResult) _isFirst = false; + ChildProcessTracker.AddProcess(_process); } - public void CheckAndCompleteDaprEnvironment(string? data) + private void CheckAndCompleteDaprEnvironment(string data) { - if (data == null) - return; - var httpPort = GetHttpPort(data); - var grpcPort = GetgRPCPort(data); + var grpcPort = GetGrpcPort(data); - CompleteDaprEnvironment(httpPort, grpcPort); + SuccessDaprOptions!.TrySetHttpPort(httpPort); + SuccessDaprOptions!.TrySetGrpcPort(grpcPort); + CompleteDaprEnvironment(); } - public void Stop() + private void CompleteDaprEnvironment() { - lock (_lock) + if (SuccessDaprOptions!.DaprHttpPort is > 0 && SuccessDaprOptions!.DaprGrpcPort is > 0) { - StopCore(SuccessDaprOptions); - _heartBeatTimer?.Stop(); + DaprEnvironmentProvider.TrySetHttpPort(SuccessDaprOptions.DaprHttpPort); + DaprEnvironmentProvider.TrySetGrpcPort(SuccessDaprOptions.DaprGrpcPort); + IsFirst = false; + _retryTime = 0; + UpdateStatus(DaprProcessStatus.Started); } } - private void StopCore(DaprCoreOptions? options) + /// + /// Only stop sidecars started by the current program + /// + public void Stop() { - if (options != null) + lock (_lock) { - // In https mode, the dapr process cannot be stopped by dapr stop - if (options.EnableSsl is true) - { - _process?.Kill(); - } - else + switch (Status) { - _daprProvider.DaprStop(options.AppId); + case DaprProcessStatus.Stopped: + _logger?.LogDebug("dapr sidecar stopped, do not repeat stop"); + return; + case DaprProcessStatus.Stopping: + _logger?.LogDebug("dapr sidecar is stopping, do not repeat, please wait..."); + return; + default: + if (SuccessDaprOptions == null) + { + _logger?.LogDebug("There is no dapr sidecar successfully launched by the current program"); + return; + } + + UpdateStatus(DaprProcessStatus.Stopping); + StopCore(); + _heartBeatTimer?.Stop(); + _isStopByManually = true; + return; } + } + } - if (options.DaprHttpPort != null) - CheckPortAndKill(options.DaprHttpPort.Value); - if (options.DaprGrpcPort != null) - CheckPortAndKill(options.DaprGrpcPort.Value); + private void StopCore() + { + // In https mode, the dapr process cannot be stopped by dapr stop + _process?.Kill(); + if (SuccessDaprOptions!.EnableSsl is not true) + { + _daprProcessProvider.DaprStop(GetDefaultDaprFileName(), SuccessDaprOptions.AppId); } + + if (SuccessDaprOptions.DaprHttpPort != null) + CheckPortAndKill(SuccessDaprOptions.DaprHttpPort.Value); + if (SuccessDaprOptions.DaprGrpcPort != null) + CheckPortAndKill(SuccessDaprOptions.DaprGrpcPort.Value); + + UpdateStatus(DaprProcessStatus.Stopped); } /// /// Refresh the dapr configuration, the source dapr process will be killed and the new dapr process will be restarted /// /// - public void Refresh(DaprOptions options) + private void Refresh(DaprOptions options) { lock (_lock) { - _logger?.LogDebug("Dapr configuration refresh, Dapr AppId is {AppId}, please wait...", SuccessDaprOptions!.AppId); + if (_isStopByManually) + { + _logger?.LogDebug("configuration update, you need to start dapr through Start (Restart is not supported due to manual stop of sidecar)"); + return; + } + var sidecarOptionsByRefresh = ConvertToSidecarOptions(options); if (SuccessDaprOptions != null) { - options.AppPort = SuccessDaprOptions.AppPort; - options.EnableSsl = SuccessDaprOptions.EnableSsl; - options.DaprHttpPort = SuccessDaprOptions.DaprHttpPort; - options.DaprGrpcPort = SuccessDaprOptions.DaprGrpcPort; + sidecarOptionsByRefresh.AppPort ??= SuccessDaprOptions.AppPort; + sidecarOptionsByRefresh.EnableSsl ??= SuccessDaprOptions.EnableSsl; + sidecarOptionsByRefresh.DaprHttpPort ??= SuccessDaprOptions.DaprHttpPort; + sidecarOptionsByRefresh.DaprGrpcPort ??= SuccessDaprOptions.DaprGrpcPort; + + if (sidecarOptionsByRefresh.Equals(SuccessDaprOptions)) + { + return; + } UpdateStatus(DaprProcessStatus.Restarting); - _logger?.LogDebug( - "Dapr configuration refresh, Dapr AppId is {AppId}, closing dapr, please wait...", - SuccessDaprOptions!.AppId); - StopCore(SuccessDaprOptions); + StopCore(); } - _isFirst = true; + IsFirst = true; SuccessDaprOptions = null; - _logger?.LogDebug("Dapr configuration refresh, Dapr AppId is {AppId}, restarting dapr, please wait...", options.AppId); - StartCore(ConvertToDaprCoreOptions(options)); + _logger?.LogDebug("Dapr sidecar configuration updated, Dapr AppId is {AppId}, restarting dapr, please wait...", options.AppId); + StartCore(sidecarOptionsByRefresh); } } @@ -186,7 +218,7 @@ private void CheckPortAndKill(ushort port) port, pId, process.Name, - Constant.DEFAULT_FILE_NAME); + DaprStarterConstant.DEFAULT_PROCESS_NAME); process.Kill(); } } @@ -196,45 +228,53 @@ private void HeartBeat() { lock (_lock) { - if (SuccessDaprOptions!.EnableSsl is true) - { - _logger?.LogDebug("The dapr status cannot be monitored in https mode, the check has been skipped"); + if (SuccessDaprOptions == null) return; + + var daprList = _daprProcessProvider.GetDaprList(GetDefaultDaprFileName(), SuccessDaprOptions.AppId, out bool isException); + if (daprList.Count > 1) + { + _logger?.LogDebug("dapr sidecar appears more than 1 same appid, this may cause error"); } - if (!_daprProvider.IsExist(SuccessDaprOptions!.AppId)) + if (!daprList.Any()) { - if (Status == DaprProcessStatus.Started || Status == DaprProcessStatus.Stopped) - { - _logger?.LogWarning("Dapr stopped, restarting, please wait..."); - StartCore(SuccessDaprOptions); - } - else if (Status == DaprProcessStatus.Starting) + if(isException) + return; + + switch (Status) { - if (_retryTime < Constant.DEFAULT_RETRY_TIME) - { + case DaprProcessStatus.Started: + _logger?.LogWarning("Dapr sidecar terminated abnormally, restarting, please wait..."); + StartCore(SuccessDaprOptions); + break; + case DaprProcessStatus.Starting when _retryTime < DaprStarterConstant.DEFAULT_RETRY_TIME: _retryTime++; _logger?.LogDebug("Dapr is not started: The {Retries}th heartbeat check. AppId is {AppId}", _retryTime, SuccessDaprOptions.AppId); - } - else - { + break; + case DaprProcessStatus.Starting: _logger?.LogWarning( "Dapr is not started: The {Retries}th heartbeat check. Dapr stopped, restarting, please wait...", _retryTime + 1); StartCore(SuccessDaprOptions); - } - } - else - { - _logger?.LogWarning("Dapr is restarting, the current state is {State}, please wait...", Status); + break; + case DaprProcessStatus.Restarting: + _logger?.LogWarning("Dapr is restarting, the current state is {State}, please wait...", Status); + break; } } else { - _retryTime = 0; - UpdateStatus(DaprProcessStatus.Started); + if (Status == DaprProcessStatus.Starting) + { + // Execute only when getting HttpPort, gRPCPort exception + var daprSidecar = daprList.First(); + SuccessDaprOptions.TrySetHttpPort(daprSidecar.HttpPort); + SuccessDaprOptions.TrySetGrpcPort(daprSidecar.GrpcPort); + UpdateStatus(DaprProcessStatus.Started); + } } } } diff --git a/src/Contrib/Development/Masa.Contrib.Development.DaprStarter/DaprProcessBase.cs b/src/Contrib/Development/Masa.Contrib.Development.DaprStarter/DaprProcessBase.cs index df1caef81..0e0c21fba 100644 --- a/src/Contrib/Development/Masa.Contrib.Development.DaprStarter/DaprProcessBase.cs +++ b/src/Contrib/Development/Masa.Contrib.Development.DaprStarter/DaprProcessBase.cs @@ -6,112 +6,203 @@ namespace Masa.Contrib.Development.DaprStarter; [ExcludeFromCodeCoverage] public abstract class DaprProcessBase { - private readonly IOptions? _masaAppConfigureOptions; - protected IDaprEnvironmentProvider DaprEnvironmentProvider { get; } - /// - /// Use after getting dapr AppId and global AppId fails - /// - private static readonly string DefaultAppId = (Assembly.GetEntryAssembly() ?? Assembly.GetCallingAssembly()).GetName().Name!.Replace( - ".", - Constant.DEFAULT_APPID_DELIMITER); + private readonly IDaprProvider _daprProvider; - private const string HTTP_PORT_PATTERN = @"HTTP Port: ([0-9]+)"; - private const string GRPC_PORT_PATTERN = @"gRPC Port: ([0-9]+)"; + private static readonly string[] HttpPortPatterns = { "http server is running on port ([0-9]+)" }; + private static readonly string[] GrpcPortPatterns = { "API gRPC server is running on port ([0-9]+)" }; + private static readonly string UserFilePath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); - internal DaprCoreOptions? SuccessDaprOptions; + internal SidecarOptions? SuccessDaprOptions; + protected readonly IOptionsMonitor DaprOptions; + private static string? _defaultDaprFileName; + private static string? _defaultSidecarFileName; + + /// + /// record whether dapr is initialized for the first time + /// + protected bool IsFirst = true; - protected DaprProcessBase(IDaprEnvironmentProvider daprEnvironmentProvider, IOptions? masaAppConfigureOptions) + protected DaprProcessBase( + IDaprEnvironmentProvider daprEnvironmentProvider, + IDaprProvider daprProvider, + IOptionsMonitor daprOptions) { DaprEnvironmentProvider = daprEnvironmentProvider; - _masaAppConfigureOptions = masaAppConfigureOptions; + _daprProvider = daprProvider; + DaprOptions = daprOptions; } - internal DaprCoreOptions ConvertToDaprCoreOptions(DaprOptions options) + internal SidecarOptions ConvertToSidecarOptions(DaprOptions options) { - var appId = options.AppId; - if (appId.IsNullOrWhiteSpace()) - appId = _masaAppConfigureOptions?.Value.AppId; - if (appId.IsNullOrWhiteSpace()) - appId = DefaultAppId; - if (options.IsIncompleteAppId()) - appId = $"{appId}{options.AppIdDelimiter}{options.AppIdSuffix ?? NetworkUtils.GetPhysicalAddress()}"; - DaprCoreOptions - dataOptions = new( - appId!, - options.AppPort ?? throw new ArgumentNullException(nameof(options), $"{options.AppPort} must be greater than 0"), - options.AppProtocol, - options.EnableSsl, - options.DaprGrpcPort ?? DaprEnvironmentProvider.GetGrpcPort(), - options.DaprHttpPort ?? DaprEnvironmentProvider.GetHttpPort(), - options.EnableHeartBeat) - { - HeartBeatInterval = options.HeartBeatInterval, - CreateNoWindow = options.CreateNoWindow, - MaxConcurrency = options.MaxConcurrency, - Config = options.Config, - ComponentPath = options.ComponentPath, - EnableProfiling = options.EnableProfiling, - Image = options.Image, - LogLevel = options.LogLevel, - PlacementHostAddress = options.PlacementHostAddress, - SentryAddress = options.PlacementHostAddress, - MetricsPort = options.MetricsPort, - ProfilePort = options.ProfilePort, - UnixDomainSocket = options.UnixDomainSocket, - DaprMaxRequestSize = options.DaprMaxRequestSize - }; - return dataOptions; + var sidecarOptions = new SidecarOptions( + _daprProvider.CompletionAppId(options.AppId), + options.AppPort, + options.AppProtocol, + options.EnableSsl) + { + EnableHeartBeat = options.EnableHeartBeat, + HeartBeatInterval = options.HeartBeatInterval, + CreateNoWindow = options.CreateNoWindow, + MaxConcurrency = options.MaxConcurrency, + Config = options.Config, + ComponentPath = options.ComponentPath, + EnableProfiling = options.EnableProfiling, + LogLevel = options.LogLevel, + SentryAddress = options.SentryAddress, + MetricsPort = options.MetricsPort, + ProfilePort = options.ProfilePort, + UnixDomainSocket = options.UnixDomainSocket, + DaprHttpMaxRequestSize = options.DaprHttpMaxRequestSize, + PlacementHostAddress = options.PlacementHostAddress, + AllowedOrigins = options.AllowedOrigins, + ControlPlaneAddress = options.ControlPlaneAddress, + DaprHttpReadBufferSize = options.DaprHttpReadBufferSize, + DaprInternalGrpcPort = options.DaprInternalGrpcPort, + EnableApiLogging = options.EnableApiLogging, + EnableMetrics = options.EnableMetrics, + Mode = options.Mode, + RootPath = options.RootPath, + DaprRootPath = options.DaprRootPath, + ExtendedParameter = options.ExtendedParameter + }; + sidecarOptions.TrySetHttpPort(options.DaprHttpPort ?? DaprEnvironmentProvider.GetHttpPort()); + sidecarOptions.TrySetGrpcPort(options.DaprGrpcPort ?? DaprEnvironmentProvider.GetGrpcPort()); + sidecarOptions.TrySetMetricsPort(options.MetricsPort ?? DaprEnvironmentProvider.GetMetricsPort()); + + if (sidecarOptions.EnableDefaultPlacementHostAddress && sidecarOptions.PlacementHostAddress.IsNullOrWhiteSpace()) + { + var port = Environment.OSVersion.Platform == PlatformID.Win32NT ? 6050 : 50005; + sidecarOptions.PlacementHostAddress = $"127.0.0.1:{port}"; + } + + if (sidecarOptions.RootPath.IsNullOrWhiteSpace()) + { + sidecarOptions.RootPath = Path.Combine(UserFilePath, ".dapr"); + } + + if (sidecarOptions.DaprRootPath.IsNullOrWhiteSpace()) + { + sidecarOptions.DaprRootPath = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "C:\\dapr" : "/usr/local/bin"; + } + + if (sidecarOptions.ComponentPath.IsNullOrWhiteSpace()) + { + sidecarOptions.ComponentPath = "components"; + } + + if (sidecarOptions.Config.IsNullOrWhiteSpace()) + { + sidecarOptions.Config = "config.yaml"; + } + + return sidecarOptions; } - internal CommandLineBuilder CreateCommandLineBuilder(DaprCoreOptions options) + internal CommandLineBuilder CreateCommandLineBuilder(SidecarOptions options) { - var commandLineBuilder = new CommandLineBuilder(Constant.DEFAULT_ARGUMENT_PREFIX); + var commandLineBuilder = new CommandLineBuilder(DaprStarterConstant.DEFAULT_ARGUMENT_PREFIX); commandLineBuilder - .Add("app-id", options.AppId) - .Add("app-port", options.AppPort.ToString()) - .Add("app-protocol", options.AppProtocol?.ToString().ToLower() ?? string.Empty, options.AppProtocol == null) - .Add("app-ssl", "", options.EnableSsl != true) - .Add("components-path", options.ComponentPath ?? string.Empty, options.ComponentPath == null) - .Add("app-max-concurrency", options.MaxConcurrency?.ToString() ?? string.Empty, options.MaxConcurrency == null) - .Add("config", options.Config ?? string.Empty, options.Config == null) - .Add("dapr-grpc-port", options.DaprGrpcPort?.ToString() ?? string.Empty, !(options.DaprGrpcPort > 0)) - .Add("dapr-http-port", options.DaprHttpPort?.ToString() ?? string.Empty, !(options.DaprHttpPort > 0)) - .Add("enable-profiling", options.EnableProfiling?.ToString().ToLower() ?? string.Empty, options.EnableProfiling == null) - .Add("image", options.Image ?? string.Empty, options.Image == null) - .Add("log-level", options.LogLevel?.ToString().ToLower() ?? string.Empty, options.LogLevel == null) - .Add("placement-host-address", options.PlacementHostAddress ?? string.Empty, options.PlacementHostAddress == null) - .Add("sentry-address", options.SentryAddress ?? string.Empty, options.SentryAddress == null) - .Add("metrics-port", options.MetricsPort?.ToString() ?? string.Empty, options.MetricsPort == null) - .Add("profile-port", options.ProfilePort?.ToString() ?? string.Empty, options.ProfilePort == null) - .Add("unix-domain-socket", options.UnixDomainSocket ?? string.Empty, options.UnixDomainSocket == null) - .Add("dapr-http-max-request-size", options.DaprMaxRequestSize?.ToString() ?? string.Empty, options.DaprMaxRequestSize == null); - - SuccessDaprOptions ??= options; + .Add("app-id", () => options.AppId) + .Add("app-port", () => options.GetAppPort().ToString()) + .Add("app-protocol", () => options.AppProtocol!.Value.ToString().ToLower(), options.AppProtocol == null) + .Add("app-ssl", () => "", options.EnableSsl != true) + .Add("components-path", () => Path.Combine(options.RootPath, options.ComponentPath)) + .Add("app-max-concurrency", () => options.MaxConcurrency!.Value.ToString(), options.MaxConcurrency == null) + .Add("config", () => Path.Combine(options.RootPath, options.Config)) + .Add("dapr-grpc-port", () => options.DaprGrpcPort!.Value.ToString(), !(options.DaprGrpcPort > 0)) + .Add("dapr-http-port", () => options.DaprHttpPort!.Value.ToString(), !(options.DaprHttpPort > 0)) + .Add("enable-profiling", () => options.EnableProfiling!.Value.ToString().ToLower(), options.EnableProfiling == null) + .Add("log-level", () => options.LogLevel!.Value.ToString().ToLower(), options.LogLevel == null) + .Add("sentry-address", () => options.SentryAddress!, options.SentryAddress == null) + .Add("metrics-port", () => options.MetricsPort!.Value.ToString(), options.MetricsPort == null) + .Add("profile-port", () => options.ProfilePort!.Value.ToString(), options.ProfilePort == null) + .Add("unix-domain-socket", () => options.UnixDomainSocket!, options.UnixDomainSocket == null) + .Add("dapr-http-max-request-size", () => options.DaprHttpMaxRequestSize!.Value.ToString(), + options.DaprHttpMaxRequestSize == null) + .Add("placement-host-address", () => options.PlacementHostAddress, options.PlacementHostAddress.IsNullOrWhiteSpace()) + .Add("allowed-origins", () => options.AllowedOrigins, options.AllowedOrigins.IsNullOrWhiteSpace()) + .Add("control-plane-address", () => options.ControlPlaneAddress, options.ControlPlaneAddress.IsNullOrWhiteSpace()) + .Add("dapr-http-read-buffer-size", () => options.DaprHttpReadBufferSize!.Value.ToString(), + options.DaprHttpReadBufferSize == null) + .Add("dapr-internal-grpc-port", () => options.DaprInternalGrpcPort!.Value.ToString(), options.DaprInternalGrpcPort == null) + .Add("enable-api-logging", () => "", options.EnableApiLogging is not true) + .Add("enable-metrics", () => "", options.EnableMetrics is not true) + .Add("mode", () => options.Mode, options.Mode.IsNullOrWhiteSpace()) + .Add(options.ExtendedParameter, () => "", options.ExtendedParameter.IsNullOrWhiteSpace()); + + if (!IsFirst) + return commandLineBuilder; + + SetDefaultFileName(options.DaprRootPath, options.RootPath); + SuccessDaprOptions = options; + return commandLineBuilder; } -#pragma warning disable S6444 - protected static ushort GetHttpPort(string data) + + #region GetHttpPort、GetGrpcPort + + /// + /// Get the HttpPort according to the output, but it is unreliable + /// After the prompt information output by dapr is adjusted, it may cause the failure to obtain the port + /// + /// + /// + protected static ushort? GetHttpPort(string data) { - ushort httpPort = 0; - var httpPortMatch = Regex.Matches(data, HTTP_PORT_PATTERN); - if (httpPortMatch.Count > 0) + foreach (var pattern in HttpPortPatterns) { - httpPort = ushort.Parse(httpPortMatch[0].Groups[1].ToString()); + var port = GetPort(data, pattern); + if (port is > 0) + return port; } - return httpPort; + + return null; } - protected static ushort GetgRPCPort(string data) + static ushort? GetPort(string data, string pattern) { - ushort grpcPort = 0; - var gRPCPortMatch = Regex.Matches(data, GRPC_PORT_PATTERN); - if (gRPCPortMatch.Count > 0) + ushort? port = null; + var regex = new Regex(pattern, RegexOptions.IgnoreCase, TimeSpan.FromSeconds(1)); + var match = regex.Matches(data); + if (match.Count > 0) { - grpcPort = ushort.Parse(gRPCPortMatch[0].Groups[1].ToString()); + port = ushort.Parse(match[0].Groups[1].ToString()); } - return grpcPort; + + return port; + } + + /// + /// Get the gRpcPort according to the output, but it is unreliable + /// After the prompt information output by dapr is adjusted, it may cause the failure to obtain the port + /// + /// + /// + protected static ushort? GetGrpcPort(string data) + { + foreach (var pattern in GrpcPortPatterns) + { + var port = GetPort(data, pattern); + if (port is > 0) + return port; + } + + return null; + } + + #endregion + + static string GetFileName(string fileName) => RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? $"{fileName}.exe" : fileName; + + protected static string GetDefaultDaprFileName() => _defaultDaprFileName!; + + protected static string GetDefaultSidecarFileName() => _defaultSidecarFileName!; + + private static void SetDefaultFileName(string daprRootPath, string sidecarRootPath) + { + _defaultDaprFileName = Path.Combine(daprRootPath, GetFileName(DaprStarterConstant.DEFAULT_DAPR_FILE_NAME)); + _defaultSidecarFileName = Path.Combine(sidecarRootPath, "bin", GetFileName(DaprStarterConstant.DEFAULT_FILE_NAME)); } -#pragma warning restore S6444 } diff --git a/src/Contrib/Development/Masa.Contrib.Development.DaprStarter/DaprProvider.cs b/src/Contrib/Development/Masa.Contrib.Development.DaprStarter/DaprProvider.cs index 7faa4fc44..41fd540e4 100644 --- a/src/Contrib/Development/Masa.Contrib.Development.DaprStarter/DaprProvider.cs +++ b/src/Contrib/Development/Masa.Contrib.Development.DaprStarter/DaprProvider.cs @@ -4,25 +4,24 @@ namespace Masa.Contrib.Development.DaprStarter; [ExcludeFromCodeCoverage] -public class DaprProvider : IDaprProvider +public class DaprProcessProvider : IDaprProcessProvider { - private readonly ILoggerFactory? _loggerFactory; - private readonly ILogger? _logger; + private readonly ILogger? _logger; - public DaprProvider(ILoggerFactory? loggerFactory) + public DaprProcessProvider(ILogger? logger = null) { - _loggerFactory = loggerFactory; - _logger = loggerFactory?.CreateLogger(); + _logger = logger; } - public List GetDaprList(string appId) + public List GetDaprList(string fileName, string appId, out bool isException) { - var processUtils = new ProcessUtils(_loggerFactory); + isException = false; + var processUtils = new ProcessUtils(_logger); processUtils.Exit += delegate { - _logger?.LogDebug("{Name} process has exited", Constant.DEFAULT_FILE_NAME); + _logger?.LogDebug("{Name} process has exited, appid: {AppId}", DaprStarterConstant.DEFAULT_PROCESS_NAME, appId); }; - processUtils.Run(Constant.DEFAULT_FILE_NAME, "list -o json", out string response, true, true); + processUtils.Run(fileName, "list -o json", out string response, true, true); List daprList = new(); try { @@ -45,18 +44,21 @@ public List GetDaprList(string appId) } catch (Exception exception) { + isException = true; _logger?.LogWarning(exception, "----- Error getting list of running dapr, response message is {Response}", response); return new List(); } + return daprList.Where(dapr => dapr.AppId == appId).ToList(); } - public Process DaprStart(string arguments, + public Process DaprStart(string fileName, + string arguments, bool createNoWindow, Action outputDataReceivedAction, Action exitAction) { - var processUtils = new ProcessUtils(_loggerFactory); + var processUtils = new ProcessUtils(_logger); processUtils.OutputDataReceived += delegate(object? sender, DataReceivedEventArgs args) { @@ -67,9 +69,9 @@ public Process DaprStart(string arguments, processUtils.Exit += delegate { exitAction.Invoke(); - _logger?.LogDebug("{Name} process has exited", Constant.DEFAULT_FILE_NAME); + _logger?.LogDebug("{Name} process has exited", DaprStarterConstant.DEFAULT_PROCESS_NAME); }; - return processUtils.Run(Constant.DEFAULT_FILE_NAME, $"run {arguments}", createNoWindow); + return processUtils.Run(fileName, $" {arguments}", createNoWindow); } private static void DaprProcess_OutputDataReceived(DataReceivedEventArgs e) @@ -112,15 +114,22 @@ private static void DaprProcess_ErrorDataReceived(object? sender, DataReceivedEv Console.ForegroundColor = color; } - public void DaprStop(string appId) + public void DaprStop(string fileName, string appId) { - var process = new ProcessUtils(_loggerFactory).Run( - $"{Constant.DEFAULT_FILE_NAME}", - $"stop {appId}", - createNoWindow: false, - isWait: false); - process.WaitForExit(); - } + var daprList = GetDaprList(fileName, appId, out _); - public bool IsExist(string appId) => GetDaprList(appId).Any(); + var pidList = daprList.Select(dapr => dapr.PId).ToList(); + foreach (var pid in pidList) + { + try + { + var process = Process.GetProcessById(pid); + process.Kill(); + } + catch (Exception ex) + { + _logger?.LogWarning(ex, "Error stopping dapr sidecar, appid is {AppId}, pid is {PId}", appId, pid); + } + } + } } diff --git a/src/Contrib/Development/Masa.Contrib.Development.DaprStarter/IDaprEnvironmentProvider.cs b/src/Contrib/Development/Masa.Contrib.Development.DaprStarter/IDaprEnvironmentProvider.cs index 364559292..355646d12 100644 --- a/src/Contrib/Development/Masa.Contrib.Development.DaprStarter/IDaprEnvironmentProvider.cs +++ b/src/Contrib/Development/Masa.Contrib.Development.DaprStarter/IDaprEnvironmentProvider.cs @@ -9,6 +9,8 @@ public interface IDaprEnvironmentProvider ushort? GetGrpcPort(); + ushort? GetMetricsPort(); + /// /// Set the HttpPort environment variable /// When httpPort is greater than 0, return true @@ -26,11 +28,9 @@ public interface IDaprEnvironmentProvider // ReSharper disable once InconsistentNaming bool TrySetGrpcPort(ushort? grpcPort); - void SetHttpPort(ushort httpPort); - - // ReSharper disable once InconsistentNaming - void SetGrpcPort(ushort grpcPort); - - // ReSharper disable once InconsistentNaming - void CompleteDaprEnvironment(ushort? httpPort, ushort? grpcPort); + /// + /// Set the metricsPort environment variable + /// When metricsPort is greater than 0, return true + /// + bool TrySetMetricsPort(ushort? metricsPort); } diff --git a/src/Contrib/Development/Masa.Contrib.Development.DaprStarter/IDaprProcessProvider.cs b/src/Contrib/Development/Masa.Contrib.Development.DaprStarter/IDaprProcessProvider.cs new file mode 100644 index 000000000..06927303f --- /dev/null +++ b/src/Contrib/Development/Masa.Contrib.Development.DaprStarter/IDaprProcessProvider.cs @@ -0,0 +1,18 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Contrib.Development.DaprStarter; + +public interface IDaprProcessProvider +{ + List GetDaprList(string fileName, string appId, out bool isException); + + Process DaprStart( + string fileName, + string arguments, + bool createNoWindow, + Action outputDataReceivedAction, + Action exitAction); + + void DaprStop(string fileName, string appId); +} diff --git a/src/Contrib/Development/Masa.Contrib.Development.DaprStarter/IDaprProvider.cs b/src/Contrib/Development/Masa.Contrib.Development.DaprStarter/IDaprProvider.cs index 1d0df3c3f..ee0d69f19 100644 --- a/src/Contrib/Development/Masa.Contrib.Development.DaprStarter/IDaprProvider.cs +++ b/src/Contrib/Development/Masa.Contrib.Development.DaprStarter/IDaprProvider.cs @@ -1,18 +1,18 @@ -// Copyright (c) MASA Stack All rights reserved. +// Copyright (c) MASA Stack All rights reserved. // Licensed under the MIT License. See LICENSE.txt in the project root for license information. -namespace Masa.Contrib.Development.DaprStarter; +// ReSharper disable once CheckNamespace + +namespace Masa.BuildingBlocks.Development.DaprStarter; public interface IDaprProvider { - List GetDaprList(string appId); - - Process DaprStart(string arguments, - bool createNoWindow, - Action outputDataReceivedAction, - Action exitAction); - - void DaprStop(string appId); - - bool IsExist(string appId); + /// + /// Complete dapr appid + /// + /// + string CompletionAppId(string? appId = null, + bool disableAppIdSuffix = false, + string? appIdSuffix = null, + string appIdDelimiter = DaprStarterConstant.DEFAULT_APPID_DELIMITER); } diff --git a/src/Contrib/Development/Masa.Contrib.Development.DaprStarter/Internal/ChildProcessTracker.cs b/src/Contrib/Development/Masa.Contrib.Development.DaprStarter/Internal/ChildProcessTracker.cs new file mode 100644 index 000000000..ddd0c8d4c --- /dev/null +++ b/src/Contrib/Development/Masa.Contrib.Development.DaprStarter/Internal/ChildProcessTracker.cs @@ -0,0 +1,151 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +// ReSharper disable once CheckNamespace + +namespace Masa.Contrib.Development.DaprStarter; + +/// +/// Allows processes to be automatically killed if this parent process unexpectedly quits. +/// This feature requires Windows 8 or greater. On Windows 7, nothing is done. +/// References: +/// https://stackoverflow.com/a/4657392/386091 +/// https://stackoverflow.com/a/9164742/386091 +[ExcludeFromCodeCoverage] +public static class ChildProcessTracker +{ + /// + /// Add the process to be tracked. If our current process is killed, the child processes + /// that we are tracking will be automatically killed, too. If the child process terminates + /// first, that's fine, too. + /// + public static void AddProcess(Process? process) + { + if (process == null) + return; + + if (SJobHandle != IntPtr.Zero) + { + bool success = AssignProcessToJobObject(SJobHandle, process.Handle); + if (!success && !process.HasExited) + throw new System.ComponentModel.Win32Exception(); + } + } +#pragma warning disable S3963 +#pragma warning disable S3877 + static ChildProcessTracker() + { + // This feature requires Windows 8 or later. To support Windows 7 requires + // registry settings to be added if you are using Visual Studio plus an + // app.manifest change. + // https://stackoverflow.com/a/4232259/386091 + // https://stackoverflow.com/a/9507862/386091 + if (Environment.OSVersion.Version < new Version(6, 2)) + return; + + // The job name is optional (and can be null) but it helps with diagnostics. + // If it's not null, it has to be unique. Use SysInternals' Handle command-line + // utility: handle -a ChildProcessTracker + string jobName = "ChildProcessTracker" + Environment.ProcessId; + SJobHandle = CreateJobObject(IntPtr.Zero, jobName); + + var info = new JobObjectBasicLimitInformation + { + // be killed, too. + // close the job handle, and when that happens, we want the child processes to + // This is the key flag. When our process is killed, Windows will automatically + LimitFlags = JobObjectLimits.JobObjectLimitKillOnJobClose + }; + + var extendedInfo = new JobObjectExtendedLimitInformation + { + BasicLimitInformation = info + }; + + int length = Marshal.SizeOf(typeof(JobObjectExtendedLimitInformation)); + IntPtr extendedInfoPtr = Marshal.AllocHGlobal(length); + try + { + Marshal.StructureToPtr(extendedInfo, extendedInfoPtr, false); + + if (!SetInformationJobObject(SJobHandle, JobObjectInfoType.ExtendedLimitInformation, + extendedInfoPtr, (uint)length)) + { + throw new System.ComponentModel.Win32Exception(); + } + } + finally + { + Marshal.FreeHGlobal(extendedInfoPtr); + } + } +#pragma warning restore S3877 +#pragma warning restore S3963 + + [DllImport("kernel32.dll", CharSet = CharSet.Unicode)] + static extern IntPtr CreateJobObject(IntPtr lpJobAttributes, string name); + + [DllImport("kernel32.dll")] + static extern bool SetInformationJobObject(IntPtr job, JobObjectInfoType infoType, IntPtr lpJobObjectInfo, uint cbJobObjectInfoLength); + + [DllImport("kernel32.dll", SetLastError = true)] + static extern bool AssignProcessToJobObject(IntPtr job, IntPtr process); + + // Windows will automatically close any open job handles when our process terminates. + // This can be verified by using SysInternals' Handle utility. When the job handle + // is closed, the child processes will be killed. + private static readonly IntPtr SJobHandle; +} + +public enum JobObjectInfoType +{ + AssociateCompletionPortInformation = 7, + BasicLimitInformation = 2, + BasicUiRestrictions = 4, + EndOfJobTimeInformation = 6, + ExtendedLimitInformation = 9, + SecurityLimitInformation = 5, + GroupInformation = 11 +} + +[StructLayout(LayoutKind.Sequential)] +public struct JobObjectBasicLimitInformation +{ + public Int64 PerProcessUserTimeLimit; + public Int64 PerJobUserTimeLimit; + public JobObjectLimits LimitFlags; + public UIntPtr MinimumWorkingSetSize; + public UIntPtr MaximumWorkingSetSize; + public UInt32 ActiveProcessLimit; + public Int64 Affinity; + public UInt32 PriorityClass; + public UInt32 SchedulingClass; +} + +[Flags] +public enum JobObjectLimits : uint +{ + JobObjectLimitKillOnJobClose = 0x2000 +} + +[StructLayout(LayoutKind.Sequential)] +public struct IoCounters +{ + public UInt64 ReadOperationCount; + public UInt64 WriteOperationCount; + public UInt64 OtherOperationCount; + public UInt64 ReadTransferCount; + public UInt64 WriteTransferCount; + public UInt64 OtherTransferCount; +} + +[StructLayout(LayoutKind.Sequential)] +public struct JobObjectExtendedLimitInformation +{ + public JobObjectBasicLimitInformation BasicLimitInformation; + public IoCounters IoInfo; + public UIntPtr ProcessMemoryLimit; + public UIntPtr JobMemoryLimit; + public UIntPtr PeakProcessMemoryUsed; + public UIntPtr PeakJobMemoryUsed; +} diff --git a/src/Contrib/Development/Masa.Contrib.Development.DaprStarter/Internal/DefaultDaprProvider.cs b/src/Contrib/Development/Masa.Contrib.Development.DaprStarter/Internal/DefaultDaprProvider.cs new file mode 100644 index 000000000..ae6d1e9cf --- /dev/null +++ b/src/Contrib/Development/Masa.Contrib.Development.DaprStarter/Internal/DefaultDaprProvider.cs @@ -0,0 +1,55 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +[assembly: InternalsVisibleTo("Masa.Contrib.Development.DaprStarter.Tests")] + +// ReSharper disable once CheckNamespace + +namespace Masa.Contrib.Development.DaprStarter; + +internal class DefaultDaprProvider : IDaprProvider +{ + private readonly IOptions? _masaAppConfigureOptions; + private readonly IConfiguration? _configuration; + private readonly IMasaConfiguration? _masaConfiguration; + + /// + /// Use after getting dapr AppId and global AppId fails + /// + private static readonly string DefaultAppId = ( + Assembly.GetEntryAssembly() ?? + Assembly.GetCallingAssembly()).GetName().Name!.Replace(".", DaprStarterConstant.DEFAULT_APPID_DELIMITER); + + public DefaultDaprProvider(IOptions? masaAppConfigureOptions = null, + IConfiguration? configuration = null, + IMasaConfiguration? masaConfiguration = null) + { + _masaAppConfigureOptions = masaAppConfigureOptions; + _configuration = configuration; + _masaConfiguration = masaConfiguration; + } + + public string CompletionAppId(string? appId = null, + bool disableAppIdSuffix = false, + string? appIdSuffix = null, + string appIdDelimiter = DaprStarterConstant.DEFAULT_APPID_DELIMITER) + { + string? actualAppId = appId; + if (actualAppId.IsNullOrWhiteSpace()) + actualAppId = _masaAppConfigureOptions?.Value.AppId; + if (actualAppId.IsNullOrWhiteSpace()) + actualAppId = DefaultAppId; + if (IsIncompleteAppId()) + actualAppId = $"{actualAppId}{appIdDelimiter}{appIdSuffix ?? NetworkUtils.GetPhysicalAddress()}"; + + var value = _configuration == null ? Environment.GetEnvironmentVariable(actualAppId) : _configuration?.GetSection(actualAppId).Value; + if (value.IsNullOrWhiteSpace()) + value = _masaConfiguration?.Local.GetSection(actualAppId).Value; + + if (value.IsNullOrWhiteSpace()) return actualAppId; + + return value; + + bool IsIncompleteAppId() => !disableAppIdSuffix && (appIdSuffix == null || appIdSuffix.Trim() != string.Empty); + } +} diff --git a/src/Contrib/Development/Masa.Contrib.Development.DaprStarter/Internal/Options/DaprCoreOptions.cs b/src/Contrib/Development/Masa.Contrib.Development.DaprStarter/Internal/Options/DaprCoreOptions.cs deleted file mode 100644 index 2f19d375c..000000000 --- a/src/Contrib/Development/Masa.Contrib.Development.DaprStarter/Internal/Options/DaprCoreOptions.cs +++ /dev/null @@ -1,157 +0,0 @@ -// Copyright (c) MASA Stack All rights reserved. -// Licensed under the MIT License. See LICENSE.txt in the project root for license information. - -// ReSharper disable once CheckNamespace - -namespace Masa.Contrib.Development.DaprStarter; - -[ExcludeFromCodeCoverage] -internal class DaprCoreOptions -{ - /// - /// The id for your application, used for service discovery - /// Required, no blanks allowed - /// - public string AppId { get; } - - /// - /// The port your application is listening on - /// - public ushort AppPort { get; } - - /// - /// The protocol (gRPC or HTTP) Dapr uses to talk to the application. Valid values are: http or grpc - /// - public Protocol? AppProtocol { get; } - - /// - /// Enable https when Dapr invokes the application - /// - public bool? EnableSsl { get; } - - /// - /// The gRPC port for Dapr to listen on - /// - // ReSharper disable once InconsistentNaming - public ushort? DaprGrpcPort { get; private set; } - - /// - /// The HTTP port for Dapr to listen on - /// - public ushort? DaprHttpPort { get; private set; } - - public bool EnableHeartBeat { get; private set; } - - public int HeartBeatInterval { get; set; } - - public bool CreateNoWindow { get; set; } = true; - - /// - /// The concurrency level of the application, otherwise is unlimited - /// - public int? MaxConcurrency { get; set; } - - /// - /// Dapr configuration file - /// default: - /// Linux & Mac: $HOME/.dapr/config.yaml - /// Windows: %USERPROFILE%\.dapr\config.yaml - /// - public string? Config { get; set; } - - /// - /// The path for components directory - /// default: - /// Linux & Mac: $HOME/.dapr/components - /// Windows: %USERPROFILE%\.dapr\components - /// - public string? ComponentPath { get; set; } - - /// - /// Enable pprof profiling via an HTTP endpoint - /// - public bool? EnableProfiling { get; set; } - - /// - /// The image to build the code in. Input is: repository/image - /// - public string? Image { get; set; } - - /// - /// The log verbosity. Valid values are: debug, info, warn, error, fatal, or panic - /// default: info - /// - public LogLevel? LogLevel { get; set; } - - /// - /// default: localhost - /// - public string? PlacementHostAddress { get; set; } - - /// - /// Address for the Sentry CA service - /// - public string? SentryAddress { get; set; } - - /// - /// The port that Dapr sends its metrics information to - /// - public ushort? MetricsPort { get; set; } - - /// - /// The port for the profile server to listen on - /// - public ushort? ProfilePort { get; set; } - - /// - /// Path to a unix domain socket dir mount. If specified - /// communication with the Dapr sidecar uses unix domain sockets for lower latency and greater throughput when compared to using TCP ports - /// Not available on Windows OS - /// - public string? UnixDomainSocket { get; set; } - - /// - /// Max size of request body in MB. - /// - public int? DaprMaxRequestSize { get; set; } - - // ReSharper disable once InconsistentNaming - public DaprCoreOptions( - string appId, - ushort appPort, - Protocol? appProtocol, - bool? enableSsl, - ushort? daprGRPCPort, - ushort? daprHttpPort, - bool enableHeartBeat) - { - AppId = appId; - AppPort = appPort; - AppProtocol = appProtocol; - EnableSsl = enableSsl; - DaprGrpcPort = daprGRPCPort; - DaprHttpPort = daprHttpPort; - EnableHeartBeat = enableHeartBeat; - } - - public bool TrySetHttpPort(ushort? httpPort) - { - if (DaprHttpPort == null && httpPort is > 0) - { - DaprHttpPort = httpPort; - return true; - } - return false; - } - - // ReSharper disable once InconsistentNaming - public bool TrySetGrpcPort(ushort? grpcPort) - { - if (DaprGrpcPort == null && grpcPort is > 0) - { - DaprGrpcPort = grpcPort; - return true; - } - return false; - } -} diff --git a/src/Contrib/Development/Masa.Contrib.Development.DaprStarter/Internal/Options/SidecarOptions.cs b/src/Contrib/Development/Masa.Contrib.Development.DaprStarter/Internal/Options/SidecarOptions.cs new file mode 100644 index 000000000..12cbec83c --- /dev/null +++ b/src/Contrib/Development/Masa.Contrib.Development.DaprStarter/Internal/Options/SidecarOptions.cs @@ -0,0 +1,188 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +// ReSharper disable once CheckNamespace + +namespace Masa.Contrib.Development.DaprStarter; + +#pragma warning disable S3236 +[ExcludeFromCodeCoverage] +internal class SidecarOptions : DaprOptionsBase +{ + /// + /// The id for your application, used for service discovery + /// Required, no blanks allowed + /// + public string AppId { get; } + + /// + /// Whether to use the default placement host address when no PlacementHostAddress is specified + /// default: true + /// + public bool EnableDefaultPlacementHostAddress { get; set; } = true; + + public string PlacementHostAddress { get; set; } + + /// + /// Allowed HTTP origins (default "*") + /// + public string AllowedOrigins { get; set; } + + /// + /// Address for a Dapr control plane + /// + public string ControlPlaneAddress { get; set; } + + /// + /// Increasing max size of request body in MB to handle uploading of big files (default 4) + /// + public int? DaprHttpMaxRequestSize { get; set; } + + /// + /// Increasing max size of read buffer in KB to handle sending multi-KB headers (default 4) + /// + public int? DaprHttpReadBufferSize { get; set; } + + /// + /// gRPC port for the Dapr Internal API to listen on. + /// + public int? DaprInternalGrpcPort { get; set; } + + /// + /// Enable API logging for API calls + /// + public bool? EnableApiLogging { get; set; } + + /// + /// Enable prometheus metric (default true) + /// + public bool? EnableMetrics { get; set; } + + /// + /// Runtime mode for Dapr (default "standalone") + /// + public string Mode { get; set; } + + /// + /// Extended parameters, used to supplement unsupported parameters + /// + public string ExtendedParameter { get; set; } + + // ReSharper disable once InconsistentNaming + public SidecarOptions( + string appId, + ushort? appPort, + Protocol? appProtocol, + bool? enableSsl) + { + AppId = appId; + AppPort = appPort; + AppProtocol = appProtocol; + EnableSsl = enableSsl; + } + + public bool TrySetHttpPort(ushort? httpPort) + { + if (DaprHttpPort == null && httpPort is > 0) + { + DaprHttpPort = httpPort; + return true; + } + + return false; + } + + // ReSharper disable once InconsistentNaming + public bool TrySetGrpcPort(ushort? grpcPort) + { + if (DaprGrpcPort == null && grpcPort is > 0) + { + DaprGrpcPort = grpcPort; + return true; + } + + return false; + } + + public bool TrySetProfilePort(ushort? profilePort) + { + if (ProfilePort == null && profilePort is > 0) + { + ProfilePort = profilePort; + return true; + } + + return false; + } + + public bool TrySetMetricsPort(ushort? metricsPort) + { + if (MetricsPort == null && metricsPort is > 0) + { + MetricsPort = metricsPort; + return true; + } + + return false; + } + + public ushort GetAppPort() + { + MasaArgumentException.ThrowIfNull(AppPort); + return AppPort.Value; + } + + public override int GetHashCode() + { + return GetContent().Aggregate(0, HashCode.Combine); + } + + public override bool Equals(object? obj) + { + if (this is null ^ obj is null) return false; + + if (obj is SidecarOptions other) + { + return other.GetContent().SequenceEqual(GetContent()); + } + + return false; + } + + private IEnumerable GetContent() + { + return new List + { + GetAppPort(), + AppProtocol, + EnableSsl, + DaprGrpcPort, + DaprHttpPort, + EnableHeartBeat, + HeartBeatInterval, + CreateNoWindow, + MaxConcurrency, + Config, + ComponentPath, + EnableProfiling, + LogLevel, + SentryAddress, + MetricsPort, + ProfilePort, + UnixDomainSocket, + DaprHttpReadBufferSize, + AppId, + EnableDefaultPlacementHostAddress, + PlacementHostAddress, + AllowedOrigins, + ControlPlaneAddress, + DaprHttpMaxRequestSize, + DaprInternalGrpcPort, + EnableApiLogging, + EnableMetrics, + Mode, + ExtendedParameter + }; + } +} +#pragma warning restore S3236 diff --git a/src/Contrib/Development/Masa.Contrib.Development.DaprStarter/Internal/ProcessUtils.cs b/src/Contrib/Development/Masa.Contrib.Development.DaprStarter/Internal/ProcessUtils.cs index c46db6cfd..1d6d0e5cc 100644 --- a/src/Contrib/Development/Masa.Contrib.Development.DaprStarter/Internal/ProcessUtils.cs +++ b/src/Contrib/Development/Masa.Contrib.Development.DaprStarter/Internal/ProcessUtils.cs @@ -8,11 +8,11 @@ namespace Masa.Contrib.Development.DaprStarter; [ExcludeFromCodeCoverage] internal sealed class ProcessUtils { - private readonly ILogger? _logger; + private readonly ILogger? _logger; - public ProcessUtils(ILoggerFactory? loggerFactory = null) + public ProcessUtils(ILogger? logger) { - _logger = loggerFactory?.CreateLogger(); + _logger = logger; } public Process Run( @@ -29,12 +29,12 @@ public Process Run( bool createNoWindow = true, bool isWait = false) { - _logger?.LogDebug("FileName: {FileName}, Arguments: {Arguments}", fileName, arguments); + _logger?.LogDebug("Execute the command: {Command} on {Name}", $"{fileName} {arguments}", "DaprStarter"); var processStartInfo = new ProcessStartInfo { FileName = fileName, Arguments = arguments, - UseShellExecute = !createNoWindow, + UseShellExecute = false, CreateNoWindow = createNoWindow }; var daprProcess = new Process() @@ -53,16 +53,15 @@ public Process Run( daprProcess.ErrorDataReceived += (_, args) => OnErrorDataReceived(args); } } + daprProcess.Start(); if (createNoWindow && !isWait) { daprProcess.BeginOutputReadLine(); daprProcess.BeginErrorReadLine(); } + daprProcess.Exited += (_, _) => OnExited(); - string command = $"{fileName} {arguments}"; - _logger?.LogDebug("Process: {ProcessName}, Command: {Command}, PID: {ProcessId} executed successfully", fileName, - command, daprProcess.Id); if (isWait) { @@ -73,6 +72,7 @@ public Process Run( { response = string.Empty; } + return daprProcess; } diff --git a/src/Contrib/Development/Masa.Contrib.Development.DaprStarter/ServiceCollectionExtensions.cs b/src/Contrib/Development/Masa.Contrib.Development.DaprStarter/ServiceCollectionExtensions.cs index e8d721294..a57d50a1c 100644 --- a/src/Contrib/Development/Masa.Contrib.Development.DaprStarter/ServiceCollectionExtensions.cs +++ b/src/Contrib/Development/Masa.Contrib.Development.DaprStarter/ServiceCollectionExtensions.cs @@ -34,7 +34,8 @@ private static IServiceCollection AddDaprStarter(this IServiceCollection service MasaApp.TrySetServiceCollection(services); services.TryAddSingleton(); - services.TryAddSingleton(); + services.TryAddSingleton(); + services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); return services; @@ -42,6 +43,5 @@ private static IServiceCollection AddDaprStarter(this IServiceCollection service private sealed class DaprService { - } } diff --git a/src/Contrib/Development/Masa.Contrib.Development.DaprStarter/_Imports.cs b/src/Contrib/Development/Masa.Contrib.Development.DaprStarter/_Imports.cs index 54d2bf6b8..fd696ecdc 100644 --- a/src/Contrib/Development/Masa.Contrib.Development.DaprStarter/_Imports.cs +++ b/src/Contrib/Development/Masa.Contrib.Development.DaprStarter/_Imports.cs @@ -1,10 +1,12 @@ // Copyright (c) MASA Stack All rights reserved. // Licensed under the MIT License. See LICENSE.txt in the project root for license information. +global using Masa.BuildingBlocks.Configuration; global using Masa.BuildingBlocks.Configuration.Options; global using Masa.BuildingBlocks.Data; global using Masa.BuildingBlocks.Development.DaprStarter; global using Masa.Contrib.Development.DaprStarter; +global using Masa.Utils.Caching.Memory; global using Microsoft.Extensions.Configuration; global using Microsoft.Extensions.DependencyInjection.Extensions; global using Microsoft.Extensions.Logging; @@ -13,6 +15,7 @@ global using System.Diagnostics.CodeAnalysis; global using System.Net.NetworkInformation; global using System.Reflection; +global using System.Runtime.CompilerServices; global using System.Runtime.InteropServices; global using System.Text.Json.Serialization; global using System.Text.RegularExpressions; diff --git a/src/Contrib/Development/Tests/Masa.Contrib.Development.DaprStarter.AspNetCore.Tests/DefaultAppPortProviderTest.cs b/src/Contrib/Development/Tests/Masa.Contrib.Development.DaprStarter.AspNetCore.Tests/DefaultAppPortProviderTest.cs index 96e829688..b5bfc6aaf 100644 --- a/src/Contrib/Development/Tests/Masa.Contrib.Development.DaprStarter.AspNetCore.Tests/DefaultAppPortProviderTest.cs +++ b/src/Contrib/Development/Tests/Masa.Contrib.Development.DaprStarter.AspNetCore.Tests/DefaultAppPortProviderTest.cs @@ -18,7 +18,7 @@ public void TestGetAppPort() } [DataTestMethod] - [DataRow(null, false, 5000)] + [DataRow(null, true, 5001)] [DataRow(false, false, 5000)] [DataRow(true, true, 5001)] public void TestGetAppPort2(bool? enableSsl, bool expectedEnableSsl, int expectedAppPort) @@ -34,6 +34,29 @@ public void TestGetAppPort2(bool? enableSsl, bool expectedEnableSsl, int expecte Assert.AreEqual(expectedEnableSsl, result.EnableSsl); } + [DataTestMethod] + [DataRow(5000, false)] + [DataRow(5001, true)] + [DataRow(6000, null)] + public void TestGetAppPort2(ushort appPort, bool? expectedEnableSsl) + { + var service = new Mock(); + IServerAddressesFeature serverAddressesFeature = new ServerAddressesFeature(); + serverAddressesFeature.Addresses.Add("https://localhost:5001"); + serverAddressesFeature.Addresses.Add("http://localhost:5000"); + service.Setup(s => s.Features.Get()).Returns(() => serverAddressesFeature).Verifiable(); + var provider = new DefaultAppPortProvider(service.Object); + var enableSsl = provider.GetEnableSsl(appPort); + if (expectedEnableSsl != null) + { + Assert.AreEqual(expectedEnableSsl, enableSsl); + } + else + { + Assert.ThrowsException(() => Assert.AreEqual(expectedEnableSsl, enableSsl)); + } + } + [DataTestMethod] [DataRow(null, true, 5001)] [DataRow(true, true, 5001)] diff --git a/src/Contrib/Development/Tests/Masa.Contrib.Development.DaprStarter.Tests/CommandLineBuilderTest.cs b/src/Contrib/Development/Tests/Masa.Contrib.Development.DaprStarter.Tests/CommandLineBuilderTest.cs index ccd098e24..211c31863 100644 --- a/src/Contrib/Development/Tests/Masa.Contrib.Development.DaprStarter.Tests/CommandLineBuilderTest.cs +++ b/src/Contrib/Development/Tests/Masa.Contrib.Development.DaprStarter.Tests/CommandLineBuilderTest.cs @@ -14,11 +14,11 @@ public class CommandLineBuilderTest [DataRow(5001, 5000, "--app-id test --dapr-grpc-port 5001 --dapr-http-port 5000")] public void TestToString(int? grpcPort, int? httpPort, string expectedResult) { - var builder = new CommandLineBuilder(Masa.BuildingBlocks.Development.DaprStarter.Constant.DEFAULT_ARGUMENT_PREFIX); + var builder = new CommandLineBuilder(DaprStarterConstant.DEFAULT_ARGUMENT_PREFIX); builder - .Add("app-id", "test", false) - .Add("dapr-grpc-port", grpcPort?.ToString() ?? string.Empty, !(grpcPort > 0)) - .Add("dapr-http-port", httpPort?.ToString() ?? string.Empty, !(httpPort > 0)); + .Add("app-id", () => "test", false) + .Add("dapr-grpc-port", () => grpcPort?.ToString() ?? string.Empty, !(grpcPort > 0)) + .Add("dapr-http-port", () => httpPort?.ToString() ?? string.Empty, !(httpPort > 0)); var result = builder.ToString(); Assert.AreEqual(expectedResult, result); } diff --git a/src/Contrib/Development/Tests/Masa.Contrib.Development.DaprStarter.Tests/DaprEnvironmentProviderTest.cs b/src/Contrib/Development/Tests/Masa.Contrib.Development.DaprStarter.Tests/DaprEnvironmentProviderTest.cs index 8befd7e64..91a52e1c5 100644 --- a/src/Contrib/Development/Tests/Masa.Contrib.Development.DaprStarter.Tests/DaprEnvironmentProviderTest.cs +++ b/src/Contrib/Development/Tests/Masa.Contrib.Development.DaprStarter.Tests/DaprEnvironmentProviderTest.cs @@ -55,31 +55,4 @@ public void TestTrySetGrpcPort() var port = _daprEnvironmentProvider.GetGrpcPort(); Assert.AreEqual(grpcPort, port); } - - [TestMethod] - // ReSharper disable once InconsistentNaming - public void TestCompleteDaprEnvironment() - { - // ReSharper disable once InconsistentNaming - ushort? grpcPort = null; - ushort? httpPort = null; - _daprEnvironmentProvider.CompleteDaprEnvironment(httpPort, grpcPort); - - grpcPort = _daprEnvironmentProvider.GetGrpcPort(); - Assert.AreEqual(null, grpcPort); - - httpPort = _daprEnvironmentProvider.GetHttpPort(); - Assert.AreEqual(null, httpPort); - - httpPort = 9; - grpcPort = 10; - - _daprEnvironmentProvider.CompleteDaprEnvironment(httpPort, grpcPort); - - httpPort = _daprEnvironmentProvider.GetHttpPort(); - Assert.AreEqual((ushort)9, httpPort); - - grpcPort = _daprEnvironmentProvider.GetGrpcPort(); - Assert.AreEqual((ushort)10, grpcPort); - } } diff --git a/src/Contrib/Development/Tests/Masa.Contrib.Development.DaprStarter.Tests/DefaultDaprProviderTest.cs b/src/Contrib/Development/Tests/Masa.Contrib.Development.DaprStarter.Tests/DefaultDaprProviderTest.cs new file mode 100644 index 000000000..aa6a0cb16 --- /dev/null +++ b/src/Contrib/Development/Tests/Masa.Contrib.Development.DaprStarter.Tests/DefaultDaprProviderTest.cs @@ -0,0 +1,226 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Contrib.Development.DaprStarter.Tests; + +[TestClass] +public class DefaultDaprProviderTest +{ + private static readonly string DefaultAppIdSuffix = NetworkUtils.GetPhysicalAddress(); + + private static readonly string DefaultAppId = ( + Assembly.GetEntryAssembly() ?? + Assembly.GetCallingAssembly()).GetName().Name!.Replace(".", DaprStarterConstant.DEFAULT_APPID_DELIMITER); + + [DataRow(null, "test2", "test2-{default-suffix}", "test3", false, null, null, "test2-{default-suffix}")] + [DataRow(null, "test2", "test2-{default-suffix}-2", "test3", false, null, null, "test2-{default-suffix}")] + [DataRow(null, null, "{default-appid}-{default-suffix}", "test3", false, null, null, "{default-appid}-{default-suffix}")] + [DataRow(null, null, "{default-appid}-{default-suffix}", "test3", false, null, null, "{default-appid}-{default-suffix}")] + [DataRow(null, "test2", "test2-masa", "test3", false, "masa", null, "test2-masa")] + [DataRow(null, "test2", "test2-masa-2", "test3", false, "masa", null, "test2-masa")] + [DataRow(null, null, "{default-appid}-masa", "test3", false, "masa", null, "{default-appid}-masa")] + [DataRow(null, null, "{default-appid}-masa-2", "test3", false, "masa", null, "{default-appid}-masa")] + [DataRow(null, "test2", "test2|masa", "test3", false, "masa", "|", "test2|masa")] + [DataRow(null, "test2", "test2|masa-2", "test3", false, "masa", "|", "test2|masa")] + [DataRow(null, null, "{default-appid}|masa", "test3", false, "masa", "|", "{default-appid}|masa")] + [DataRow(null, null, "{default-appid}|masa-2", "test3", false, "masa", "|", "{default-appid}|masa")] + [DataRow(null, "test2", "test2|{default-suffix}", "test3", false, null, "|", "test2|{default-suffix}")] + [DataRow(null, "test2", "test2|{default-suffix}-2", "test3", false, null, "|", "test2|{default-suffix}")] + [DataRow(null, null, "{default-appid}|{default-suffix}", "test3", false, null, "|", "{default-appid}|{default-suffix}")] + [DataRow(null, null, "{default-appid}|{default-suffix}-2", "test3", false, null, "|", "{default-appid}|{default-suffix}")] + [DataRow("test", "test2", "test-{default-suffix}", "test3", false, null, null, "test-{default-suffix}")] + [DataRow("test", "test2", "test-{default-suffix}-2", "test3", false, null, null, "test-{default-suffix}")] + [DataRow("test", null, "test-{default-suffix}", "test3", false, null, null, "test-{default-suffix}")] + [DataRow("test", null, "test-{default-suffix}-2", "test3", false, null, null, "test-{default-suffix}")] + [DataRow("test", "test2", "test-masa", "test3", false, "masa", null, "test-masa")] + [DataRow("test", "test2", "test-masa-2", "test3", false, "masa", null, "test-masa")] + [DataRow("test", null, "test-masa", "test3", false, "masa", null, "test-masa")] + [DataRow("test", null, "test-masa-2", "test3", false, "masa", null, "test-masa")] + [DataRow("test", "test2", "test|masa", "test3", false, "masa", "|", "test|masa")] + [DataRow("test", "test2", "test|masa-2", "test3", false, "masa", "|", "test|masa")] + [DataRow("test", null, "test|masa", "test3", false, "masa", "|", "test|masa")] + [DataRow("test", null, "test|masa-2", "test3", false, "masa", "|", "test|masa")] + [DataRow("test", "test2", "test|{default-suffix}", "test3", false, null, "|", "test|{default-suffix}")] + [DataRow("test", "test2", "test|{default-suffix}-2", "test3", false, null, "|", "test|{default-suffix}")] + [DataRow("test", null, "test|{default-suffix}", "test3", false, null, "|", "test|{default-suffix}")] + [DataRow("test", null, "test|{default-suffix}-2", "test3", false, null, "|", "test|{default-suffix}")] + [DataRow("test", "test2", "test", "test3", true, null, null, "test")] + [DataRow("test", "test2", "test-2", "test3", true, null, null, "test")] + [DataRow("test", null, "test", "test3", true, null, null, "test")] + [DataRow("test", null, "test-2", "test3", true, null, null, "test")] + [DataRow("test", "test2", "test", "test3", true, "masa", null, "test")] + [DataRow("test", "test2", "test-2", "test3", true, "masa", null, "test")] + [DataRow("test", null, "test", "test3", true, "masa", null, "test")] + [DataRow("test", null, "test-2", "test3", true, "masa", null, "test")] + [DataRow("test", "test2", "test", "test3", true, "masa", "|", "test")] + [DataRow("test", "test2", "test-2", "test3", true, "masa", "|", "test")] + [DataRow("test", null, "test", "test3", true, "masa", "|", "test")] + [DataRow("test", null, "test-2", "test3", true, "masa", "|", "test")] + [DataRow("test", "test2", "test", "test3", true, null, "|", "test")] + [DataRow("test", "test2", "test-2", "test3", true, null, "|", "test")] + [DataRow("test", null, "test", "test3", true, null, "|", "test")] + [DataRow("test", null, "test-2", "test3", true, null, "|", "test")] + [DataTestMethod] + public void TestCompletionAppId( + string? inputAppId, + string? inputGlobalAppId, + string? environmentAppIdKey, + string? environmentAppIdValue, + bool inputDisableAppIdSuffix, + string? inputAppIdSuffix, + string? inputAppIdDelimiter, + string expectedAppId) + { + if (environmentAppIdKey != null && environmentAppIdValue != null) + { + Environment.SetEnvironmentVariable( + environmentAppIdKey.Replace("{default-appid}", DefaultAppId).Replace("{default-suffix}", DefaultAppIdSuffix), + environmentAppIdValue); + } + + IOptions? masaAppConfigureOptions = inputGlobalAppId == null + ? null + : Options.Create(new MasaAppConfigureOptions() + { + AppId = inputGlobalAppId + }); + var defaultDaprProvider = new DefaultDaprProvider(masaAppConfigureOptions); + + var acceptAppId = inputAppIdDelimiter != null + ? defaultDaprProvider.CompletionAppId(inputAppId, inputDisableAppIdSuffix, inputAppIdSuffix, inputAppIdDelimiter) + : defaultDaprProvider.CompletionAppId(inputAppId, inputDisableAppIdSuffix, inputAppIdSuffix); + + + if (environmentAppIdKey != null && !environmentAppIdValue.IsNullOrWhiteSpace()) + { + Assert.AreEqual( + environmentAppIdKey.Replace("{default-appid}", DefaultAppId).Replace("{default-suffix}", DefaultAppIdSuffix) == + expectedAppId.Replace("{default-appid}", DefaultAppId).Replace("{default-suffix}", DefaultAppIdSuffix) + ? environmentAppIdValue + : expectedAppId.Replace("{default-appid}", DefaultAppId).Replace("{default-suffix}", DefaultAppIdSuffix), acceptAppId); + } + else + { + Assert.AreEqual( + expectedAppId.Replace("{default-appid}", DefaultAppId).Replace("{default-suffix}", DefaultAppIdSuffix), acceptAppId); + } + + if (environmentAppIdKey != null && environmentAppIdValue != null) + { + Environment.SetEnvironmentVariable( + environmentAppIdKey.Replace("{default-appid}", DefaultAppId).Replace("{default-suffix}", DefaultAppIdSuffix), + ""); + } + } + + [DataRow(null, "test2", false, null, null, "test2-{default-suffix}")] + [DataRow(null, null, false, null, null, "{default-appid}-{default-suffix}")] + [DataRow(null, "test2", false, "masa", null, "test2-masa")] + [DataRow(null, null, false, "masa", null, "{default-appid}-masa")] + [DataRow(null, "test2", false, "masa", "|", "test2|masa")] + [DataRow(null, null, false, "masa", "|", "{default-appid}|masa")] + [DataRow(null, "test2", false, null, "|", "test2|{default-suffix}")] + [DataRow(null, null, false, null, "|", "{default-appid}|{default-suffix}")] + [DataRow("test", "test2", false, null, null, "test-{default-suffix}")] + [DataRow("test", null, false, null, null, "test-{default-suffix}")] + [DataRow("test", "test2", false, "masa", null, "test-masa")] + [DataRow("test", null, false, "masa", null, "test-masa")] + [DataRow("test", "test2", false, "masa", "|", "test|masa")] + [DataRow("test", null, false, "masa", "|", "test|masa")] + [DataRow("test", "test2", false, null, "|", "test|{default-suffix}")] + [DataRow("test", null, false, null, "|", "test|{default-suffix}")] + [DataRow("test", "test2", true, null, null, "test")] + [DataRow("test", null, true, null, null, "test")] + [DataRow("test", "test2", true, "masa", null, "test")] + [DataRow("test", null, true, "masa", null, "test")] + [DataRow("test", "test2", true, "masa", "|", "test")] + [DataRow("test", null, true, "masa", "|", "test")] + [DataRow("test", "test2", true, null, "|", "test")] + [DataRow("test", null, true, null, "|", "test")] + [DataTestMethod] + public void TestCompletionAppIdByConfiguration( + string? inputAppId, + string? inputGlobalAppId, + bool inputDisableAppIdSuffix, + string? inputAppIdSuffix, + string? inputAppIdDelimiter, + string expectedAppId) + { + IOptions? masaAppConfigureOptions = inputGlobalAppId == null + ? null + : Options.Create(new MasaAppConfigureOptions() + { + AppId = inputGlobalAppId + }); + var configuration = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build(); + var defaultDaprProvider = new DefaultDaprProvider(masaAppConfigureOptions, configuration); + + var acceptAppId = inputAppIdDelimiter != null + ? defaultDaprProvider.CompletionAppId(inputAppId, inputDisableAppIdSuffix, inputAppIdSuffix, inputAppIdDelimiter) + : defaultDaprProvider.CompletionAppId(inputAppId, inputDisableAppIdSuffix, inputAppIdSuffix); + + + Assert.AreEqual( + expectedAppId == "test-masa" + ? "test3" + : expectedAppId.Replace("{default-appid}", DefaultAppId).Replace("{default-suffix}", DefaultAppIdSuffix), acceptAppId); + } + + [DataRow(null, "test2", false, null, null, "test2-{default-suffix}")] + [DataRow(null, null, false, null, null, "{default-appid}-{default-suffix}")] + [DataRow(null, "test2", false, "masa", null, "test2-masa")] + [DataRow(null, null, false, "masa", null, "{default-appid}-masa")] + [DataRow(null, "test2", false, "masa", "|", "test2|masa")] + [DataRow(null, null, false, "masa", "|", "{default-appid}|masa")] + [DataRow(null, "test2", false, null, "|", "test2|{default-suffix}")] + [DataRow(null, null, false, null, "|", "{default-appid}|{default-suffix}")] + [DataRow("test", "test2", false, null, null, "test-{default-suffix}")] + [DataRow("test", null, false, null, null, "test-{default-suffix}")] + [DataRow("test", "test2", false, "masa", null, "test-masa")] + [DataRow("test", null, false, "masa", null, "test-masa")] + [DataRow("test", "test2", false, "masa", "|", "test|masa")] + [DataRow("test", null, false, "masa", "|", "test|masa")] + [DataRow("test", "test2", false, null, "|", "test|{default-suffix}")] + [DataRow("test", null, false, null, "|", "test|{default-suffix}")] + [DataRow("test", "test2", true, null, null, "test")] + [DataRow("test", null, true, null, null, "test")] + [DataRow("test", "test2", true, "masa", null, "test")] + [DataRow("test", null, true, "masa", null, "test")] + [DataRow("test", "test2", true, "masa", "|", "test")] + [DataRow("test", null, true, "masa", "|", "test")] + [DataRow("test", "test2", true, null, "|", "test")] + [DataRow("test", null, true, null, "|", "test")] + [DataTestMethod] + public void TestCompletionAppIdByMasaConfiguration( + string? inputAppId, + string? inputGlobalAppId, + bool inputDisableAppIdSuffix, + string? inputAppIdSuffix, + string? inputAppIdDelimiter, + string expectedAppId) + { + IOptions? masaAppConfigureOptions = inputGlobalAppId == null + ? null + : Options.Create(new MasaAppConfigureOptions() + { + AppId = inputGlobalAppId + }); + var services = new ServiceCollection(); + services.AddMasaConfiguration(builder => { builder.AddJsonFile("appsettings.json"); }); + var serviceProvider = services.BuildServiceProvider(); + var defaultDaprProvider = new DefaultDaprProvider( + masaAppConfigureOptions, + serviceProvider.GetService(), + serviceProvider.GetService()); + + var acceptAppId = inputAppIdDelimiter != null + ? defaultDaprProvider.CompletionAppId(inputAppId, inputDisableAppIdSuffix, inputAppIdSuffix, inputAppIdDelimiter) + : defaultDaprProvider.CompletionAppId(inputAppId, inputDisableAppIdSuffix, inputAppIdSuffix); + + + Assert.AreEqual( + expectedAppId == "test-masa" + ? "test3" + : expectedAppId.Replace("{default-appid}", DefaultAppId).Replace("{default-suffix}", DefaultAppIdSuffix), acceptAppId); + } +} diff --git a/src/Contrib/Development/Tests/Masa.Contrib.Development.DaprStarter.Tests/Masa.Contrib.Development.DaprStarter.Tests.csproj b/src/Contrib/Development/Tests/Masa.Contrib.Development.DaprStarter.Tests/Masa.Contrib.Development.DaprStarter.Tests.csproj index 209e4e854..8e952906e 100644 --- a/src/Contrib/Development/Tests/Masa.Contrib.Development.DaprStarter.Tests/Masa.Contrib.Development.DaprStarter.Tests.csproj +++ b/src/Contrib/Development/Tests/Masa.Contrib.Development.DaprStarter.Tests/Masa.Contrib.Development.DaprStarter.Tests.csproj @@ -24,6 +24,7 @@ + diff --git a/src/Contrib/Development/Tests/Masa.Contrib.Development.DaprStarter.Tests/_Imports.cs b/src/Contrib/Development/Tests/Masa.Contrib.Development.DaprStarter.Tests/_Imports.cs index 74cdf9eb2..ebec82e72 100644 --- a/src/Contrib/Development/Tests/Masa.Contrib.Development.DaprStarter.Tests/_Imports.cs +++ b/src/Contrib/Development/Tests/Masa.Contrib.Development.DaprStarter.Tests/_Imports.cs @@ -1,8 +1,13 @@ // Copyright (c) MASA Stack All rights reserved. // Licensed under the MIT License. See LICENSE.txt in the project root for license information. +global using Masa.BuildingBlocks.Configuration; +global using Masa.BuildingBlocks.Configuration.Options; global using Masa.BuildingBlocks.Development.DaprStarter; global using Microsoft.AspNetCore.Builder; +global using Microsoft.Extensions.Configuration; global using Microsoft.Extensions.DependencyInjection; global using Microsoft.Extensions.Options; global using Microsoft.VisualStudio.TestTools.UnitTesting; +global using System.Net.NetworkInformation; +global using System.Reflection; diff --git a/src/Contrib/Development/Tests/Masa.Contrib.Development.DaprStarter.Tests/appsettings.json b/src/Contrib/Development/Tests/Masa.Contrib.Development.DaprStarter.Tests/appsettings.json index 729646383..3079eb181 100644 --- a/src/Contrib/Development/Tests/Masa.Contrib.Development.DaprStarter.Tests/appsettings.json +++ b/src/Contrib/Development/Tests/Masa.Contrib.Development.DaprStarter.Tests/appsettings.json @@ -4,5 +4,6 @@ }, "DaprOptions2": { "AppId": "Test2" - } + }, + "test-masa": "test3" }