diff --git a/Steeltoe.Debug.ruleset b/Steeltoe.Debug.ruleset index 24c4c6487d..2b34016877 100644 --- a/Steeltoe.Debug.ruleset +++ b/Steeltoe.Debug.ruleset @@ -24,6 +24,7 @@ + diff --git a/Steeltoe.Release.ruleset b/Steeltoe.Release.ruleset index 821ac4d5cc..e8b6bf3f74 100644 --- a/Steeltoe.Release.ruleset +++ b/Steeltoe.Release.ruleset @@ -17,6 +17,7 @@ + @@ -42,6 +43,7 @@ + diff --git a/src/Common/src/Abstractions/DynamicTypeAccess/PackageResolver.cs b/src/Common/src/Abstractions/DynamicTypeAccess/PackageResolver.cs index 571f16d5bd..769b412f95 100644 --- a/src/Common/src/Abstractions/DynamicTypeAccess/PackageResolver.cs +++ b/src/Common/src/Abstractions/DynamicTypeAccess/PackageResolver.cs @@ -88,6 +88,7 @@ protected TypeAccessor ResolveType(params string[] typeNames) foreach (string typeName in typeNames) { +#pragma warning disable S134 // Control flow statements "if", "switch", "for", "foreach", "while", "do" and "try" should not be nested too deeply try { Type type = assembly.GetType(typeName, true)!; @@ -97,6 +98,7 @@ protected TypeAccessor ResolveType(params string[] typeNames) { exceptions.Add(exception); } +#pragma warning restore S134 // Control flow statements "if", "switch", "for", "foreach", "while", "do" and "try" should not be nested too deeply } } catch (Exception exception) when (exception is ArgumentException or IOException or BadImageFormatException) diff --git a/src/Common/src/Common/Configuration/PropertyPlaceHolderHelper.cs b/src/Common/src/Common/Configuration/PropertyPlaceHolderHelper.cs index c0817522c0..66a356bb49 100644 --- a/src/Common/src/Common/Configuration/PropertyPlaceHolderHelper.cs +++ b/src/Common/src/Common/Configuration/PropertyPlaceHolderHelper.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information. +using System.Linq; using System.Text; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; @@ -77,99 +78,85 @@ public static IEnumerable> GetResolvedConfiguration private static string ParseStringValue(string property, IConfiguration configuration, ISet visitedPlaceHolders, ILogger logger = null, bool useEmptyStringIfNotFound = false) { - if (configuration == null) - { - return property; - } - - if (string.IsNullOrEmpty(property)) + if (configuration == null || string.IsNullOrEmpty(property)) { return property; } int startIndex = property.IndexOf(Prefix, StringComparison.Ordinal); - if (startIndex == -1) - { - return property; - } - var result = new StringBuilder(property); - - while (startIndex != -1) + int endIndex; + while (startIndex != -1 && (endIndex = FindEndIndex(result, startIndex)) != -1) { - int endIndex = FindEndIndex(result, startIndex); - - if (endIndex != -1) - { - string placeholder = result.Substring(startIndex + Prefix.Length, endIndex); - string originalPlaceholder = placeholder; + string placeholder = result.Substring(startIndex + Prefix.Length, endIndex); - if (!visitedPlaceHolders.Add(originalPlaceholder)) - { - throw new InvalidOperationException($"Found circular placeholder reference '{originalPlaceholder}' in property definitions."); - } + string originalPlaceholder = placeholder; - // Recursive invocation, parsing placeholders contained in the placeholder key. - placeholder = ParseStringValue(placeholder, configuration, visitedPlaceHolders); + if (!visitedPlaceHolders.Add(originalPlaceholder)) + { + throw new InvalidOperationException($"Found circular placeholder reference '{originalPlaceholder}' in property definitions."); + } - // Handle array references foo:bar[1]:baz format -> foo:bar:1:baz - string lookup = placeholder.Replace('[', ':').Replace("]", string.Empty, StringComparison.Ordinal); + // Recursive invocation, parsing placeholders contained in the placeholder key. + placeholder = ParseStringValue(placeholder, configuration, visitedPlaceHolders); - // Now obtain the value for the fully resolved key... - string propVal = configuration[lookup]; + // Handle array references foo:bar[1]:baz format -> foo:bar:1:baz + string lookup = placeholder.Replace('[', ':').Replace("]", string.Empty, StringComparison.Ordinal); - if (propVal == null) - { - int separatorIndex = placeholder.IndexOf(Separator, StringComparison.Ordinal); - - if (separatorIndex != -1) - { - string actualPlaceholder = placeholder.Substring(0, separatorIndex); - string defaultValue = placeholder.Substring(separatorIndex + Separator.Length); - propVal = configuration[actualPlaceholder] ?? defaultValue; - } - else if (useEmptyStringIfNotFound) - { - propVal = string.Empty; - } - } + // Now obtain the value for the fully resolved key... + string propVal = configuration[lookup]; - // Attempt to resolve as a spring-compatible placeholder - if (propVal == null) - { - // Replace Spring delimiters ('.') with MS-friendly delimiters (':') so Spring placeholders can also be resolved - lookup = placeholder.Replace('.', ':'); - propVal = configuration[lookup]; - } + propVal ??= ResolveValue(configuration, placeholder, propVal, useEmptyStringIfNotFound); - if (propVal != null) - { - // Recursive invocation, parsing placeholders contained in these - // previously resolved placeholder value. - propVal = ParseStringValue(propVal, configuration, visitedPlaceHolders); - result.Replace(startIndex, endIndex + Suffix.Length, propVal); - logger?.LogDebug("Resolved placeholder '{placeholder}'", placeholder); - startIndex = result.IndexOf(Prefix, startIndex + propVal.Length); - } - else - { - // Proceed with unprocessed value. - startIndex = result.IndexOf(Prefix, endIndex + Prefix.Length); - } - - visitedPlaceHolders.Remove(originalPlaceholder); + if (propVal != null) + { + // Recursive invocation, parsing placeholders contained in these + // previously resolved placeholder value. + propVal = ParseStringValue(propVal, configuration, visitedPlaceHolders); + result.Replace(startIndex, endIndex + Suffix.Length, propVal); + logger?.LogDebug("Resolved placeholder '{placeholder}'", placeholder); + startIndex = result.IndexOf(Prefix, startIndex + propVal.Length); } else { - startIndex = -1; + // Proceed with unprocessed value. + startIndex = result.IndexOf(Prefix, endIndex + Prefix.Length); } + + visitedPlaceHolders.Remove(originalPlaceholder); + } return result.ToString(); } + private static string ResolveValue(IConfiguration configuration, string placeholder, string propVal, bool useEmptyStringIfNotFound) + { + int separatorIndex = placeholder.IndexOf(Separator, StringComparison.Ordinal); + + if (separatorIndex != -1) + { + string actualPlaceholder = placeholder.Substring(0, separatorIndex); + string defaultValue = placeholder.Substring(separatorIndex + Separator.Length); + propVal = configuration[actualPlaceholder] ?? defaultValue; + } + else if (useEmptyStringIfNotFound) + { + propVal = string.Empty; + } + + // Attempt to resolve as a spring-compatible placeholder + if (propVal == null) + { + // Replace Spring delimiters ('.') with MS-friendly delimiters (':') so Spring placeholders can also be resolved + string lookup = placeholder.Replace('.', ':'); + propVal = configuration[lookup]; + } + return propVal; + } + private static int FindEndIndex(StringBuilder property, int startIndex) { int index = startIndex + Prefix.Length; diff --git a/src/Common/src/Common/Net/InetUtils.cs b/src/Common/src/Common/Net/InetUtils.cs index 817e30dc0b..b90c2f05a9 100644 --- a/src/Common/src/Common/Net/InetUtils.cs +++ b/src/Common/src/Common/Net/InetUtils.cs @@ -46,39 +46,30 @@ public IPAddress FindFirstNonLoopbackAddress() try { int lowest = int.MaxValue; - NetworkInterface[] interfaces = NetworkInterface.GetAllNetworkInterfaces(); + NetworkInterface[] interfaces = NetworkInterface.GetAllNetworkInterfaces() + .Where(x => x.OperationalStatus == OperationalStatus.Up && !x.IsReceiveOnly && !IgnoreInterface(x.Name)) + .ToArray(); foreach (NetworkInterface @interface in interfaces) { - if (@interface.OperationalStatus == OperationalStatus.Up && !@interface.IsReceiveOnly) + _logger?.LogTrace("Testing interface: {name}, {id}", @interface.Name, @interface.Id); + + IPInterfaceProperties props = @interface.GetIPProperties(); + IPv4InterfaceProperties ipProps = props.GetIPv4Properties(); + + if (ipProps.Index < lowest || result == null) + { + lowest = ipProps.Index; + } + else { - _logger?.LogTrace("Testing interface: {name}, {id}", @interface.Name, @interface.Id); - - IPInterfaceProperties props = @interface.GetIPProperties(); - IPv4InterfaceProperties ipProps = props.GetIPv4Properties(); - - if (ipProps.Index < lowest || result == null) - { - lowest = ipProps.Index; - } - else - { - continue; - } - - if (!IgnoreInterface(@interface.Name)) - { - foreach (UnicastIPAddressInformation addressInfo in props.UnicastAddresses) - { - IPAddress address = addressInfo.Address; - - if (IsInet4Address(address) && !IsLoopbackAddress(address) && IsPreferredAddress(address)) - { - _logger?.LogTrace("Found non-loopback interface: {name}", @interface.Name); - result = address; - } - } - } + continue; + } + foreach (UnicastIPAddressInformation addressInfo in props.UnicastAddresses + .Where(x => IsInet4Address(x.Address) && !IsLoopbackAddress(x.Address) && IsPreferredAddress(x.Address))) + { + _logger?.LogTrace("Found non-loopback interface: {name}", @interface.Name); + result = addressInfo.Address; } } } @@ -195,30 +186,18 @@ internal HostInfo ConvertAddress(IPAddress address) internal IPAddress ResolveHostAddress(string hostName) { - IPAddress result = null; - try { IPAddress[] results = Dns.GetHostAddresses(hostName); - if (results.Length > 0) - { - foreach (IPAddress address in results) - { - if (address.AddressFamily == AddressFamily.InterNetwork) - { - result = address; - break; - } - } - } + return Array.Find(results, x => x.AddressFamily == AddressFamily.InterNetwork); } catch (Exception e) { _logger?.LogWarning(e, "Unable to resolve host address"); } - return result; + return null; } internal string ResolveHostName() diff --git a/src/Common/src/Common/Reflection/ReflectionHelpers.cs b/src/Common/src/Common/Reflection/ReflectionHelpers.cs index e68528e800..34c6abeb44 100644 --- a/src/Common/src/Common/Reflection/ReflectionHelpers.cs +++ b/src/Common/src/Common/Reflection/ReflectionHelpers.cs @@ -213,16 +213,17 @@ public static Type FindType(string[] assemblyNames, string[] typeNames) { Assembly assembly = FindAssembly(assemblyName); - if (assembly != null) + if (assembly == null) { - foreach (string type in typeNames) - { - Type result = FindType(assembly, type); + continue; + } + foreach (string type in typeNames) + { + Type result = FindType(assembly, type); - if (result != null) - { - return result; - } + if (result != null) + { + return result; } } } @@ -462,12 +463,7 @@ private static void TryLoadAssembliesWithAttribute() try { Assembly assemblyRef = loadContext.LoadFromAssemblyPath(assembly); - - // haven't been able to get actual type comparison to work (assembly of the attribute not found?), falling back on matching the type name - if (CustomAttributeData.GetCustomAttributes(assemblyRef).Any(attr => attr.AttributeType.FullName == typeof(T).FullName)) - { - FindAssembly(filename); - } + FindAssemblyByFullName(assemblyRef, filename); } catch { @@ -477,6 +473,15 @@ private static void TryLoadAssembliesWithAttribute() } } + private static void FindAssemblyByFullName(Assembly assemblyRef, string filename) + { + // haven't been able to get actual type comparison to work (assembly of the attribute not found?), falling back on matching the type name + if (CustomAttributeData.GetCustomAttributes(assemblyRef).Any(attr => attr.AttributeType.FullName == typeof(T).FullName)) + { + FindAssembly(filename); + } + } + /// /// Build a list of file paths that are relevant to this task. /// diff --git a/src/Configuration/src/ConfigServer/ConfigServerConfigurationProvider.cs b/src/Configuration/src/ConfigServer/ConfigServerConfigurationProvider.cs index 00c9146d94..fe9ecdf0d7 100644 --- a/src/Configuration/src/ConfigServer/ConfigServerConfigurationProvider.cs +++ b/src/Configuration/src/ConfigServer/ConfigServerConfigurationProvider.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information. +using System; using System.Globalization; using System.Net; using System.Net.Http.Json; @@ -226,21 +227,19 @@ public override void Load() { return await DoLoadAsync(updateDictionary, cancellationToken); } - catch (ConfigServerException e) + catch (ConfigServerException e) when (attempts < Settings.RetryAttempts) { Logger.LogInformation(e, "Failed fetching configuration from server at: {uri}.", Settings.Uri); attempts++; - if (attempts < Settings.RetryAttempts) - { - Thread.CurrentThread.Join(backOff); - int nextBackOff = (int)(backOff * Settings.RetryMultiplier); - backOff = Math.Min(nextBackOff, Settings.RetryMaxInterval); - } - else - { - throw; - } + Thread.CurrentThread.Join(backOff); + int nextBackOff = (int)(backOff * Settings.RetryMultiplier); + backOff = Math.Min(nextBackOff, Settings.RetryMaxInterval); + } + catch (ConfigServerException e) when (attempts >= Settings.RetryAttempts) + { + Logger.LogInformation(e, "Failed fetching configuration for the final time from server at: {uri} .", Settings.Uri); + throw; } } while (true); @@ -275,37 +274,7 @@ public override void Load() Logger.LogInformation("Located environment name: {name}, profiles: {profiles}, labels: {label}, version: {version}, state: {state}", env.Name, env.Profiles, env.Label, env.Version, env.State); - if (updateDictionary) - { - var data = new Dictionary(StringComparer.OrdinalIgnoreCase); - - if (!string.IsNullOrEmpty(env.State)) - { - data["spring:cloud:config:client:state"] = env.State; - } - - if (!string.IsNullOrEmpty(env.Version)) - { - data["spring:cloud:config:client:version"] = env.Version; - } - - IList sources = env.PropertySources; - int index = sources.Count - 1; - - for (; index >= 0; index--) - { - AddPropertySource(sources[index], data); - } - - // Adds client settings (e.g. spring:cloud:config:uri, etc.) back to the (new) Data dictionary - AddConfigServerClientSettings(data); - - if (!AreDictionariesEqual(Data, data)) - { - Data = data; - OnReload(); - } - } + UpdateDictionaryData(updateDictionary, env); return env; } @@ -326,6 +295,42 @@ public override void Load() return null; } + private void UpdateDictionaryData(bool updateDictionary, ConfigEnvironment env) + { + if (updateDictionary) + { + return; + } + var data = new Dictionary(StringComparer.OrdinalIgnoreCase); + + if (!string.IsNullOrEmpty(env.State)) + { + data["spring:cloud:config:client:state"] = env.State; + } + + if (!string.IsNullOrEmpty(env.Version)) + { + data["spring:cloud:config:client:version"] = env.Version; + } + + IList sources = env.PropertySources; + int index = sources.Count - 1; + + for (; index >= 0; index--) + { + AddPropertySource(sources[index], data); + } + + // Adds client settings (e.g. spring:cloud:config:uri, etc.) back to the (new) Data dictionary + AddConfigServerClientSettings(data); + + if (!AreDictionariesEqual(Data, data)) + { + Data = data; + OnReload(); + } + } + private static bool AreDictionariesEqual(IDictionary first, IDictionary second) { return first.Count == second.Count && first.Keys.All(firstKey => @@ -377,16 +382,7 @@ internal void UpdateSettingsFromDiscovery(IEnumerable instance settings.Username = username; settings.Password = password; } - - if (metaData.TryGetValue("configPath", out string? path)) - { - if (uri.EndsWith('/') && path.StartsWith('/')) - { - uri = uri.Substring(0, uri.Length - 1); - } - - uri += path; - } + uri += ExtractPath(metaData, uri); } endpoints.Append(uri); @@ -400,6 +396,15 @@ internal void UpdateSettingsFromDiscovery(IEnumerable instance } } + private string ExtractPath(IDictionary metaData, string uri) + { + if (metaData.TryGetValue("configPath", out string? path)) + { + return uri.EndsWith('/') && path.StartsWith('/')? path.Substring(1, path.Length - 1) : path; + } + return string.Empty; + } + internal async Task ProvideRuntimeReplacementsAsync(IDiscoveryClient? discoveryClientFromDi, CancellationToken cancellationToken) { if (_configServerDiscoveryService is not null) @@ -535,21 +540,20 @@ private void AddConfigServerClientSettings(IDictionary data) Logger.LogInformation("Config Server returned status: {statusCode} invoking path: {requestUri}", response.StatusCode, WebUtility.UrlEncode(requestUri)); - if (response.StatusCode != HttpStatusCode.OK) - { - if (response.StatusCode == HttpStatusCode.NotFound) - { - return null; - } - - // Throw if status >= 400 - if (response.StatusCode >= HttpStatusCode.BadRequest) - { - // HttpClientErrorException - throw new HttpRequestException( - $"Config Server returned status: {response.StatusCode} invoking path: {WebUtility.UrlEncode(requestUri)}"); - } + if (response.StatusCode == HttpStatusCode.NotFound) + { + return null; + } + // Throw if status >= 400 + else if (response.StatusCode >= HttpStatusCode.BadRequest) + { + // HttpClientErrorException + throw new HttpRequestException( + $"Config Server returned status: {response.StatusCode} invoking path: {WebUtility.UrlEncode(requestUri)}"); + } + else if (response.StatusCode != HttpStatusCode.OK) + { return null; } diff --git a/src/Configuration/src/Encryption/EncryptionResolverProvider.cs b/src/Configuration/src/Encryption/EncryptionResolverProvider.cs index db31e4bfbc..94473cb521 100644 --- a/src/Configuration/src/Encryption/EncryptionResolverProvider.cs +++ b/src/Configuration/src/Encryption/EncryptionResolverProvider.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information. +using System; using System.Text.RegularExpressions; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; @@ -193,22 +194,16 @@ public void Dispose() { HashSet disposables = []; - foreach (IConfigurationProvider provider in Providers) + foreach (IDisposable disposable in GetDisposables(Providers)) { - if (provider is IDisposable disposable) - { - disposables.Add(disposable); - } + disposables.Add(disposable); } if (Configuration != null) { - foreach (IConfigurationProvider provider in Configuration.Providers) + foreach (IDisposable disposable in GetDisposables(Configuration.Providers)) { - if (provider is IDisposable disposable) - { - disposables.Add(disposable); - } + disposables.Add(disposable); } } @@ -220,4 +215,17 @@ public void Dispose() _isDisposed = true; } } + + private HashSet GetDisposables(IEnumerable providers) + { + HashSet disposables = []; + foreach (IConfigurationProvider provider in providers) + { + if (provider is IDisposable disposable) + { + disposables.Add(disposable); + } + } + return disposables; + } } diff --git a/src/Configuration/src/Placeholder/PlaceholderResolverProvider.cs b/src/Configuration/src/Placeholder/PlaceholderResolverProvider.cs index 3d49dacd38..89abfc634a 100644 --- a/src/Configuration/src/Placeholder/PlaceholderResolverProvider.cs +++ b/src/Configuration/src/Placeholder/PlaceholderResolverProvider.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information. +using System; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; @@ -173,22 +174,16 @@ public void Dispose() { HashSet disposables = []; - foreach (IConfigurationProvider provider in Providers) + foreach (IDisposable disposable in GetDisposables(Providers)) { - if (provider is IDisposable disposable) - { - disposables.Add(disposable); - } + disposables.Add(disposable); } if (Configuration != null) { - foreach (IConfigurationProvider provider in Configuration.Providers) + foreach (IDisposable disposable in GetDisposables(Configuration.Providers)) { - if (provider is IDisposable disposable) - { - disposables.Add(disposable); - } + disposables.Add(disposable); } } @@ -200,4 +195,17 @@ public void Dispose() _isDisposed = true; } } + + private HashSet GetDisposables(IEnumerable providers) + { + HashSet disposables = []; + foreach (IConfigurationProvider provider in providers) + { + if (provider is IDisposable disposable) + { + disposables.Add(disposable); + } + } + return disposables; + } } diff --git a/src/Connectors/src/Connectors/ConnectionStringPostProcessor.cs b/src/Connectors/src/Connectors/ConnectionStringPostProcessor.cs index f139c54458..4922ea877f 100644 --- a/src/Connectors/src/Connectors/ConnectionStringPostProcessor.cs +++ b/src/Connectors/src/Connectors/ConnectionStringPostProcessor.cs @@ -139,14 +139,11 @@ private void SetConnectionString(IDictionary configurationData, // Take the connection string from appsettings.json as baseline, then merge cloud-provided secrets into it. connectionStringBuilder.ConnectionString = secretValue; } - else + // Never merge separately-defined secrets from appsettings.json into the connection string. + // Earlier Steeltoe versions used to do that, which raised the question what takes precedence. + else if (!IsPartOfConnectionString(secretName)) { - // Never merge separately-defined secrets from appsettings.json into the connection string. - // Earlier Steeltoe versions used to do that, which raised the question what takes precedence. - if (!IsPartOfConnectionString(secretName)) - { - separateSecrets[secretName] = secretValue; - } + separateSecrets[secretName] = secretValue; } } } diff --git a/src/Connectors/src/Connectors/MongoDb/MongoDbConnectionStringBuilder.cs b/src/Connectors/src/Connectors/MongoDb/MongoDbConnectionStringBuilder.cs index 9cf767dc40..034ee88344 100644 --- a/src/Connectors/src/Connectors/MongoDb/MongoDbConnectionStringBuilder.cs +++ b/src/Connectors/src/Connectors/MongoDb/MongoDbConnectionStringBuilder.cs @@ -117,7 +117,9 @@ private string ToConnectionString() return builder.Uri.AbsoluteUri; } +#pragma warning disable S3776 // Cognitive Complexity of methods should not be too high private void FromConnectionString(string? connectionString, bool preserveUnknownSettings) +#pragma warning restore S3776 // Cognitive Complexity of methods should not be too high { if (preserveUnknownSettings) { @@ -131,56 +133,54 @@ private void FromConnectionString(string? connectionString, bool preserveUnknown _settings.Clear(); } - if (!string.IsNullOrEmpty(connectionString)) + if (string.IsNullOrEmpty(connectionString)) { - if (connectionString.Contains(',')) - { - // MongoDB allows multiple servers in the connection string, but we haven't found any service bindings that actually use that. - throw new NotImplementedException("Support for multiple servers is not implemented. Please open a GitHub issue if you need this."); - } + return; + } + if (connectionString.Contains(',')) + { + // MongoDB allows multiple servers in the connection string, but we haven't found any service bindings that actually use that. + throw new NotImplementedException("Support for multiple servers is not implemented. Please open a GitHub issue if you need this."); + } - // MongoDB allows semicolon as separator for query string parameters, to provide backwards compatibility. - connectionString = connectionString.Replace(';', '&'); + // MongoDB allows semicolon as separator for query string parameters, to provide backwards compatibility. + connectionString = connectionString.Replace(';', '&'); - var uri = new Uri(connectionString); + var uri = new Uri(connectionString); - if (!string.IsNullOrEmpty(uri.UserInfo)) - { - string[] parts = uri.UserInfo.Split(':', 2); + if (!string.IsNullOrEmpty(uri.UserInfo)) + { + string[] parts = uri.UserInfo.Split(':', 2); - _settings[KnownKeywords.Username] = Uri.UnescapeDataString(parts[0]); + _settings[KnownKeywords.Username] = Uri.UnescapeDataString(parts[0]); - if (parts.Length == 2) - { - _settings[KnownKeywords.Password] = Uri.UnescapeDataString(parts[1]); - } + if (parts.Length == 2) + { + _settings[KnownKeywords.Password] = Uri.UnescapeDataString(parts[1]); } + } - _settings[KnownKeywords.Server] = uri.Host; + _settings[KnownKeywords.Server] = uri.Host; - if (uri.Port != -1) - { - _settings[KnownKeywords.Port] = uri.Port.ToString(CultureInfo.InvariantCulture); - } + if (uri.Port != -1) + { + _settings[KnownKeywords.Port] = uri.Port.ToString(CultureInfo.InvariantCulture); + } - if (uri.AbsolutePath.StartsWith('/') && uri.AbsolutePath.Length > 1) - { - _settings[KnownKeywords.AuthenticationDatabase] = Uri.UnescapeDataString(uri.AbsolutePath[1..]); - } + if (uri.AbsolutePath.StartsWith('/') && uri.AbsolutePath.Length > 1) + { + _settings[KnownKeywords.AuthenticationDatabase] = Uri.UnescapeDataString(uri.AbsolutePath[1..]); + } - NameValueCollection queryCollection = HttpUtility.ParseQueryString(uri.Query); + NameValueCollection queryCollection = HttpUtility.ParseQueryString(uri.Query); - foreach (string? key in queryCollection.AllKeys) - { - if (key != null) - { - string? value = queryCollection.Get(key); + foreach (string? key in queryCollection.AllKeys.Where(x => x != null)) + { + string? value = queryCollection.Get(key); - if (value != null) - { - _settings[key] = value; - } - } + if (key != null && value != null) + { + _settings[key] = value; } } } diff --git a/src/Connectors/src/Connectors/Redis/RedisConnectionStringBuilder.cs b/src/Connectors/src/Connectors/Redis/RedisConnectionStringBuilder.cs index 10c3d9b534..ee6a09d663 100644 --- a/src/Connectors/src/Connectors/Redis/RedisConnectionStringBuilder.cs +++ b/src/Connectors/src/Connectors/Redis/RedisConnectionStringBuilder.cs @@ -87,36 +87,37 @@ private void FromConnectionString(string? connectionString) { _settings.Clear(); - if (!string.IsNullOrEmpty(connectionString)) + if (string.IsNullOrEmpty(connectionString)) { - foreach (string option in connectionString.Split(',').Where(element => !string.IsNullOrWhiteSpace(element))) + return; + } + foreach (string option in connectionString.Split(',').Where(element => !string.IsNullOrWhiteSpace(element))) + { + int equalsIndex = option.IndexOf('='); + + if (equalsIndex != -1) { - int equalsIndex = option.IndexOf('='); + string name = option[..equalsIndex].Trim(); + string value = option[(equalsIndex + 1)..].Trim(); + _settings[name] = value; + } + else if (option.Contains(',')) + { + // Redis allows multiple servers in the connection string, but we haven't found any service bindings that actually use that. + throw new NotImplementedException("Support for multiple servers is not implemented. Please open a GitHub issue if you need this."); + } + else + { + string[] hostWithPort = option.Split(':', 2); + _settings[KnownKeywords.Host] = hostWithPort[0]; - if (equalsIndex != -1) + if (hostWithPort.Length > 1) { - string name = option[..equalsIndex].Trim(); - string value = option[(equalsIndex + 1)..].Trim(); - _settings[name] = value; - } - else - { - if (option.Contains(',')) - { - // Redis allows multiple servers in the connection string, but we haven't found any service bindings that actually use that. - throw new NotImplementedException("Support for multiple servers is not implemented. Please open a GitHub issue if you need this."); - } - - string[] hostWithPort = option.Split(':', 2); - _settings[KnownKeywords.Host] = hostWithPort[0]; - - if (hostWithPort.Length > 1) - { - _settings[KnownKeywords.Port] = hostWithPort[1]; - } + _settings[KnownKeywords.Port] = hostWithPort[1]; } } } + } private static void AssertIsKnownKeyword(string keyword) diff --git a/src/Connectors/test/Connectors.Test/PostgreSql/PostgreSqlConnectorTests.cs b/src/Connectors/test/Connectors.Test/PostgreSql/PostgreSqlConnectorTests.cs index 3516f465ad..82e7cf92c5 100644 --- a/src/Connectors/test/Connectors.Test/PostgreSql/PostgreSqlConnectorTests.cs +++ b/src/Connectors/test/Connectors.Test/PostgreSql/PostgreSqlConnectorTests.cs @@ -760,10 +760,12 @@ private static IEnumerable ExtractConnectionStringParameters(string? con string name = nameValuePair[0]; string value = nameValuePair[1]; +#pragma warning disable S134 // Control flow statements "if", "switch", "for", "foreach", "while", "do" and "try" should not be nested too deeply if (TempFileKeys.Contains(name)) { value = File.ReadAllText(value); } +#pragma warning restore S134 // Control flow statements "if", "switch", "for", "foreach", "while", "do" and "try" should not be nested too deeply value = value.Replace("\n", Environment.NewLine, StringComparison.Ordinal); @@ -787,10 +789,12 @@ private static void CleanupTempFiles(params string?[] connectionStrings) string key = pair[0]; string value = pair[1]; +#pragma warning disable S134 // Control flow statements "if", "switch", "for", "foreach", "while", "do" and "try" should not be nested too deeply if (TempFileKeys.Contains(key) && File.Exists(value)) { File.Delete(value); } +#pragma warning restore S134 // Control flow statements "if", "switch", "for", "foreach", "while", "do" and "try" should not be nested too deeply } } } diff --git a/src/Discovery/src/Eureka/AppInfo/Applications.cs b/src/Discovery/src/Eureka/AppInfo/Applications.cs index 3bc2b5b9d9..46921e7793 100644 --- a/src/Discovery/src/Eureka/AppInfo/Applications.cs +++ b/src/Discovery/src/Eureka/AppInfo/Applications.cs @@ -259,14 +259,11 @@ private IList DoGetByVirtualHostName(string name, ConcurrentDictio { InstanceInfo inst = kvp.Value; - if (ReturnUpInstancesOnly) + if (ReturnUpInstancesOnly && inst.Status == InstanceStatus.Up) { - if (inst.Status == InstanceStatus.Up) - { - result.Add(inst); - } + result.Add(inst); } - else + else if(!ReturnUpInstancesOnly) { result.Add(inst); } diff --git a/src/Discovery/src/Eureka/DiscoveryClient.cs b/src/Discovery/src/Eureka/DiscoveryClient.cs index 0cd676a561..de0b64599f 100644 --- a/src/Discovery/src/Eureka/DiscoveryClient.cs +++ b/src/Discovery/src/Eureka/DiscoveryClient.cs @@ -168,17 +168,14 @@ public IList GetInstancesByVipAddressAndAppName(string vipAddress, return result; } - foreach (Application app in localRegionApps.GetRegisteredApplications()) + foreach (InstanceInfo instance in localRegionApps.GetRegisteredApplications().SelectMany(x => x.Instances)) { - foreach (InstanceInfo instance in app.Instances) - { - string instanceVipAddress = secure ? instance.SecureVipAddress : instance.VipAddress; + string instanceVipAddress = secure ? instance.SecureVipAddress : instance.VipAddress; - if (vipAddress.Equals(instanceVipAddress, StringComparison.OrdinalIgnoreCase) && - appName.Equals(instance.AppName, StringComparison.OrdinalIgnoreCase)) - { - result.Add(instance); - } + if (vipAddress.Equals(instanceVipAddress, StringComparison.OrdinalIgnoreCase) && + appName.Equals(instance.AppName, StringComparison.OrdinalIgnoreCase)) + { + result.Add(instance); } } @@ -259,15 +256,10 @@ private async void HandleInstanceStatusChanged(object sender, StatusChangedEvent logger.LogDebug("HandleInstanceStatusChanged {previousStatus}, {currentStatus}, {instanceId}, {dirty}", args.Previous, args.Current, args.InstanceId, info.IsDirty); - if (info.IsDirty) + if (info.IsDirty && await RegisterAsync(CancellationToken.None)) { - bool result = await RegisterAsync(CancellationToken.None); - - if (result) - { - info.IsDirty = false; - logger.LogInformation("HandleInstanceStatusChanged RegisterAsync succeeded"); - } + info.IsDirty = false; + logger.LogInformation("HandleInstanceStatusChanged RegisterAsync succeeded"); } } } @@ -434,16 +426,9 @@ protected internal async Task FetchFullRegistryAsync(CancellationT long startingCounter = registryFetchCounter; Applications fetched = null; - EurekaHttpResponse resp; - - if (string.IsNullOrEmpty(ClientConfiguration.RegistryRefreshSingleVipAddress)) - { - resp = await HttpClient.GetApplicationsAsync(cancellationToken); - } - else - { - resp = await HttpClient.GetVipAsync(ClientConfiguration.RegistryRefreshSingleVipAddress, cancellationToken); - } + EurekaHttpResponse resp = string.IsNullOrEmpty(ClientConfiguration.RegistryRefreshSingleVipAddress) + ? await HttpClient.GetApplicationsAsync(cancellationToken) + : await HttpClient.GetVipAsync(ClientConfiguration.RegistryRefreshSingleVipAddress, cancellationToken); logger.LogDebug("FetchFullRegistry returned: {StatusCode}, {Response}", resp.StatusCode, resp.Response != null ? resp.Response.ToString() : "null"); diff --git a/src/Discovery/src/Eureka/EurekaPostConfigurer.cs b/src/Discovery/src/Eureka/EurekaPostConfigurer.cs index 3ac74562ec..c9e7b6156d 100644 --- a/src/Discovery/src/Eureka/EurekaPostConfigurer.cs +++ b/src/Discovery/src/Eureka/EurekaPostConfigurer.cs @@ -71,7 +71,9 @@ public static void UpdateConfiguration(EurekaServiceInfo si, EurekaClientOptions /// /// Information about this application instance. /// +#pragma warning disable S3776 // Cognitive Complexity of methods should not be too high public static void UpdateConfiguration(IConfiguration configuration, EurekaInstanceOptions options, IApplicationInstanceInfo instanceInfo) +#pragma warning restore S3776 // Cognitive Complexity of methods should not be too high { string defaultIdEnding = $":{EurekaInstanceConfiguration.DefaultAppName}:{EurekaInstanceConfiguration.DefaultNonSecurePort}"; diff --git a/src/Discovery/src/Eureka/Transport/EurekaHttpClient.cs b/src/Discovery/src/Eureka/Transport/EurekaHttpClient.cs index 898da98fb3..a8dd00559b 100644 --- a/src/Discovery/src/Eureka/Transport/EurekaHttpClient.cs +++ b/src/Discovery/src/Eureka/Transport/EurekaHttpClient.cs @@ -25,9 +25,9 @@ public class EurekaHttpClient : IEurekaHttpClient private const int DefaultGetAccessTokenTimeout = 10000; // Milliseconds private static readonly char[] ColonDelimit = - { + [ ':' - }; + ]; private readonly IOptionsMonitor _configurationOptions; @@ -190,10 +190,6 @@ public virtual async Task> SendHeartBeatAsync(s { string responseBody = await response.Content.ReadAsStringAsync(cancellationToken); - if (response.IsSuccessStatusCode && string.IsNullOrEmpty(responseBody)) - { - // request was successful but body was empty. This is OK, we don't need a response body - } logger?.LogError(exception, "Failed to read heartbeat response. Response code: {responseCode}, Body: {responseBody}", response.StatusCode, responseBody); diff --git a/src/Management/src/Endpoint/Info/Contributor/GitInfoContributor.cs b/src/Management/src/Endpoint/Info/Contributor/GitInfoContributor.cs index d657ff5191..126e31bf4c 100755 --- a/src/Management/src/Endpoint/Info/Contributor/GitInfoContributor.cs +++ b/src/Management/src/Endpoint/Info/Contributor/GitInfoContributor.cs @@ -54,34 +54,31 @@ public async Task ContributeAsync(IInfoBuilder builder, CancellationToken cancel { string[] lines = await File.ReadAllLinesAsync(propertiesPath, cancellationToken); - if (lines.Length > 0) + if (lines.Length == 0) { - var dictionary = new Dictionary(); - - foreach (string line in lines) - { - if (line.StartsWith('#') || !line.StartsWith("git.", StringComparison.OrdinalIgnoreCase)) - { - continue; - } - - string[] keyValuePair = line.Split('='); - - if (keyValuePair.Length != 2) - { - continue; - } + _logger.LogWarning("Unable to find valid GitInfo at {GitInfoLocation}", propertiesPath); + return null; + } + var dictionary = new Dictionary(); - string key = keyValuePair[0].Trim().Replace('.', ':'); - string value = keyValuePair[1].Replace("\\:", ":", StringComparison.Ordinal); + foreach (string line in lines.Where(x => x.StartsWith("git.", StringComparison.OrdinalIgnoreCase))) + { + string[] keyValuePair = line.Split('='); - dictionary[key] = value; + if (keyValuePair.Length != 2) + { + continue; } - var builder = new ConfigurationBuilder(); - builder.AddInMemoryCollection(dictionary); - return builder.Build(); + string key = keyValuePair[0].Trim().Replace('.', ':'); + string value = keyValuePair[1].Replace("\\:", ":", StringComparison.Ordinal); + + dictionary[key] = value; } + + var builder = new ConfigurationBuilder(); + builder.AddInMemoryCollection(dictionary); + return builder.Build(); } else { diff --git a/src/Management/src/Endpoint/Loggers/LoggersEndpointMiddleware.cs b/src/Management/src/Endpoint/Loggers/LoggersEndpointMiddleware.cs index 61f7d41c5e..81c60fd252 100644 --- a/src/Management/src/Endpoint/Loggers/LoggersEndpointMiddleware.cs +++ b/src/Management/src/Endpoint/Loggers/LoggersEndpointMiddleware.cs @@ -57,25 +57,31 @@ protected override async Task InvokeEndpointHandlerAsync(HttpCo string loggerName = remaining.Value!.TrimStart('/'); Dictionary change = await DeserializeRequestAsync(request.Body); + return CheckLogger(loggerName, change); + } + } - change.TryGetValue("configuredLevel", out string? level); + return new LoggersRequest(); + } - _logger.LogDebug("Change Request: {name}, {level}", loggerName, level ?? "RESET"); + private LoggersRequest? CheckLogger(string loggerName, Dictionary change) + { - if (!string.IsNullOrEmpty(loggerName)) - { - if (!string.IsNullOrEmpty(level) && LoggerLevels.StringToLogLevel(level) == null) - { - _logger.LogDebug("Invalid LogLevel specified: {level}", level); - return null; - } + change.TryGetValue("configuredLevel", out string? level); - return new LoggersRequest(loggerName, level); - } - } + _logger.LogDebug("Change Request: {name}, {level}", loggerName, level ?? "RESET"); + + if (string.IsNullOrEmpty(loggerName)) + { + return new LoggersRequest(); + } + if (!string.IsNullOrEmpty(level) && LoggerLevels.StringToLogLevel(level) == null) + { + _logger.LogDebug("Invalid LogLevel specified: {level}", level); + return null; } - return new LoggersRequest(); + return new LoggersRequest(loggerName, level); } private async Task> DeserializeRequestAsync(Stream stream) diff --git a/src/Management/src/Endpoint/Metrics/MetricsEndpointMiddleware.cs b/src/Management/src/Endpoint/Metrics/MetricsEndpointMiddleware.cs index e5e2e14575..1d09252d48 100644 --- a/src/Management/src/Endpoint/Metrics/MetricsEndpointMiddleware.cs +++ b/src/Management/src/Endpoint/Metrics/MetricsEndpointMiddleware.cs @@ -69,18 +69,15 @@ internal IList> ParseTags(IQueryCollection query) { var results = new List>(); - foreach (KeyValuePair parameter in query) + foreach (KeyValuePair parameter in query.Where(x => x.Key.Equals("tag", StringComparison.OrdinalIgnoreCase))) { - if (parameter.Key.Equals("tag", StringComparison.OrdinalIgnoreCase)) + foreach (string? value in parameter.Value) { - foreach (string? value in parameter.Value) - { - KeyValuePair? pair = ParseTag(value); + KeyValuePair? pair = ParseTag(value); - if (pair != null && !results.Contains(pair.Value)) - { - results.Add(pair.Value); - } + if (pair != null && !results.Contains(pair.Value)) + { + results.Add(pair.Value); } } } diff --git a/src/Management/src/Endpoint/Metrics/Observer/EventCounterListener.cs b/src/Management/src/Endpoint/Metrics/Observer/EventCounterListener.cs index 93c737068f..d9c55ae173 100644 --- a/src/Management/src/Endpoint/Metrics/Observer/EventCounterListener.cs +++ b/src/Management/src/Endpoint/Metrics/Observer/EventCounterListener.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Collections.Concurrent; +using System.Collections.Generic; using System.Diagnostics.Metrics; using System.Diagnostics.Tracing; using System.Globalization; @@ -63,21 +64,18 @@ protected override void OnEventWritten(EventWrittenEventArgs eventData) { ArgumentGuard.NotNull(eventData); - if (!_isInitialized) - { - return; - } - try { - if (string.Equals(eventData.EventName, EventName, StringComparison.OrdinalIgnoreCase) && eventData.Payload != null) + + if (!_isInitialized || string.Equals(eventData.EventName, EventName, StringComparison.OrdinalIgnoreCase) || eventData.Payload == null) + { + return; + } + foreach (IDictionary? payload in eventData.Payload) { - foreach (IDictionary? payload in eventData.Payload) + if (payload != null) { - if (payload != null) - { - ExtractAndRecordMetric(eventData.EventSource.Name, payload); - } + ExtractAndRecordMetric(eventData.EventSource.Name, payload); } } } @@ -182,17 +180,10 @@ private void ExtractAndRecordMetric(string eventSourceName, IDictionary> labels = MakeLabels(payload.Value?.ToString()); + if (labels.Any()) { - string[] keyValuePairStrings = metadata.Split(','); - - foreach (string keyValuePairString in keyValuePairStrings) - { - string[] keyValuePair = keyValuePairString.Split(':'); - labelSet.Add(KeyValuePair.Create(keyValuePair[0], (object?)keyValuePair[1])); - } + labelSet.AddRange(labels); } break; @@ -217,6 +208,22 @@ private void ExtractAndRecordMetric(string eventSourceName, IDictionary> MakeLabels(string? metadata) + { + List> labelSet = new List>(); + if (!string.IsNullOrEmpty(metadata)) + { + string[] keyValuePairStrings = metadata.Split(','); + + foreach (string keyValuePairString in keyValuePairStrings) + { + string[] keyValuePair = keyValuePairString.Split(':'); + labelSet.Add(KeyValuePair.Create(keyValuePair[0], (object?)keyValuePair[1])); + } + } + return labelSet; + } + private Measurement ObserveDouble(string name, List> labelSet) { return new Measurement(_lastDoubleValue[name], labelSet); diff --git a/src/Management/src/Endpoint/ThreadDump/EventPipeThreadDumper.cs b/src/Management/src/Endpoint/ThreadDump/EventPipeThreadDumper.cs index 3a940e8b36..22e85cbdbe 100644 --- a/src/Management/src/Endpoint/ThreadDump/EventPipeThreadDumper.cs +++ b/src/Management/src/Endpoint/ThreadDump/EventPipeThreadDumper.cs @@ -338,7 +338,9 @@ private bool TryParseParameters(string input, ref string remaining, [NotNullWhen } // Much of this code is from PerfView/TraceLog.cs +#pragma warning disable S3776 // Cognitive Complexity of methods should not be too high private SourceLocation? GetSourceLine(TraceEventStackSource stackSource, StackSourceFrameIndex frameIndex, SymbolReader reader) +#pragma warning restore S3776 // Cognitive Complexity of methods should not be too high { TraceLog log = stackSource.TraceLog; uint codeAddress = (uint)frameIndex - (uint)StackSourceFrameIndex.Start; diff --git a/src/Management/src/Endpoint/Web/Hypermedia/HypermediaService.cs b/src/Management/src/Endpoint/Web/Hypermedia/HypermediaService.cs index d55039e648..e961d64aff 100644 --- a/src/Management/src/Endpoint/Web/Hypermedia/HypermediaService.cs +++ b/src/Management/src/Endpoint/Web/Hypermedia/HypermediaService.cs @@ -71,19 +71,16 @@ public Links Invoke(string baseUrl) { selfLink = new Link(baseUrl); } - else + else if (!string.IsNullOrEmpty(endpointOptions.Id)) { - if (!string.IsNullOrEmpty(endpointOptions.Id)) + if (!links.Entries.ContainsKey(endpointOptions.Id)) { - if (!links.Entries.ContainsKey(endpointOptions.Id)) - { - string linkPath = $"{baseUrl.TrimEnd('/')}/{endpointOptions.Path}"; - links.Entries.Add(endpointOptions.Id, new Link(linkPath)); - } - else - { - _logger.LogWarning("Duplicate endpoint ID detected: {DuplicateEndpointId}", endpointOptions.Id); - } + string linkPath = $"{baseUrl.TrimEnd('/')}/{endpointOptions.Path}"; + links.Entries.Add(endpointOptions.Id, new Link(linkPath)); + } + else + { + _logger.LogWarning("Duplicate endpoint ID detected: {DuplicateEndpointId}", endpointOptions.Id); } } } diff --git a/src/Management/src/Tracing/TracingBaseServiceCollectionExtensions.cs b/src/Management/src/Tracing/TracingBaseServiceCollectionExtensions.cs index c00c05e9c7..fd6b7173f4 100644 --- a/src/Management/src/Tracing/TracingBaseServiceCollectionExtensions.cs +++ b/src/Management/src/Tracing/TracingBaseServiceCollectionExtensions.cs @@ -50,7 +50,9 @@ public static IServiceCollection AddDistributedTracing(this IServiceCollection s /// /// configured for distributed tracing. /// +#pragma warning disable S3776 // Cognitive Complexity of methods should not be too high public static IServiceCollection AddDistributedTracing(this IServiceCollection services, Action? action) +#pragma warning restore S3776 // Cognitive Complexity of methods should not be too high { ArgumentGuard.NotNull(services); diff --git a/src/Management/src/Wavefront/Exporters/WavefrontMetricsExporter.cs b/src/Management/src/Wavefront/Exporters/WavefrontMetricsExporter.cs index a0b9b09163..b02b412052 100644 --- a/src/Management/src/Wavefront/Exporters/WavefrontMetricsExporter.cs +++ b/src/Management/src/Wavefront/Exporters/WavefrontMetricsExporter.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information. +using System.Runtime.CompilerServices; using Microsoft.Extensions.Logging; using OpenTelemetry; using OpenTelemetry.Metrics; @@ -58,49 +59,20 @@ public override ExportResult Export(in Batch batch) foreach (Metric metric in batch) { - bool isLong = ((int)metric.MetricType & 0b_0000_1111) == 0x0a; // I8 : signed 8 byte integer - bool isSum = metric.MetricType.IsSum(); try { - if (!metric.MetricType.IsHistogram()) + List metricReadings = new List(); + foreach (ref readonly MetricPoint metricPoint in metric.GetMetricPoints()) { - foreach (ref readonly MetricPoint metricPoint in metric.GetMetricPoints()) - { - long timestamp = metricPoint.EndTime.ToUnixTimeMilliseconds(); - double doubleValue; - - if (isLong) - { - doubleValue = isSum ? metricPoint.GetSumLong() : metricPoint.GetGaugeLastValueLong(); - } - else - { - doubleValue = isSum ? metricPoint.GetSumDouble() : metricPoint.GetGaugeLastValueDouble(); - } - - IDictionary tags = GetTags(metricPoint.Tags); - - _wavefrontSender.SendMetric(metric.Name.ToLowerInvariant(), doubleValue, timestamp, Options.Source, tags); - - metricCount++; - } + long timestamp = metricPoint.EndTime.ToUnixTimeMilliseconds(); + IDictionary tags = GetTags(metricPoint.Tags); + metricReadings.AddRange(GetMetricData(metric, metricPoint, timestamp, tags)); } - else + foreach (MetricReading metricReading in metricReadings) { - foreach (ref readonly MetricPoint metricPoint in metric.GetMetricPoints()) - { - long timestamp = metricPoint.EndTime.ToUnixTimeMilliseconds(); - - IDictionary tags = GetTags(metricPoint.Tags); - - _wavefrontSender.SendMetric($"{metric.Name.ToLowerInvariant()}_count", metricPoint.GetHistogramCount(), timestamp, Options.Source, - tags); - - _wavefrontSender.SendMetric($"{metric.Name.ToLowerInvariant()}_sum", metricPoint.GetHistogramSum(), timestamp, Options.Source, tags); - - metricCount += 2; - } + _wavefrontSender.SendMetric(metricReading.Key, metricReading.Value, metricReading.Timestamp, Options.Source, metricReading.Tags); + metricCount++; } } catch (Exception exception) @@ -113,6 +85,45 @@ public override ExportResult Export(in Batch batch) return ExportResult.Success; } + private List GetMetricData(Metric metric, MetricPoint metricPoint, long timestamp, IDictionary tags) + { + bool isLong = ((int)metric.MetricType & 0b_0000_1111) == 0x0a; // I8 : signed 8 byte integer + bool isSum = metric.MetricType.IsSum(); + List metrics = []; + if (!metric.MetricType.IsHistogram()) + { + double doubleValue; + + if (isLong) + { + doubleValue = isSum ? metricPoint.GetSumLong() : metricPoint.GetGaugeLastValueLong(); + } + else + { + doubleValue = isSum ? metricPoint.GetSumDouble() : metricPoint.GetGaugeLastValueDouble(); + } + metrics.Add(new MetricReading(metric.Name.ToLowerInvariant(), doubleValue) + { + Tags = tags, + Timestamp = timestamp + }); + } + else + { + metrics.Add(new MetricReading($"{metric.Name.ToLowerInvariant()}_count", metricPoint.GetHistogramCount()) + { + Tags = tags, + Timestamp = timestamp + }); + metrics.Add(new MetricReading($"{metric.Name.ToLowerInvariant()}_sum", metricPoint.GetHistogramSum()) + { + Tags = tags, + Timestamp = timestamp + }); + } + return metrics; + } + private IDictionary GetTags(ReadOnlyTagCollection inputTags) { IDictionary tags = inputTags.AsDictionary(); diff --git a/src/Management/src/Wavefront/MetricReading.cs b/src/Management/src/Wavefront/MetricReading.cs new file mode 100644 index 0000000000..a739d7715d --- /dev/null +++ b/src/Management/src/Wavefront/MetricReading.cs @@ -0,0 +1,27 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information. + +namespace Steeltoe.Management.Wavefront; + +internal class MetricReading +{ + public string Key { get; internal set; } + public double Value { get; internal set; } + public long? Timestamp { get; internal set; } + public IDictionary Tags { get; internal set; } = new Dictionary(); + + public MetricReading(string key, double value, long? timestamp, IDictionary tags) + { + Key = key; + Value = value; + Timestamp = timestamp; + Tags = tags; + } + + public MetricReading(string key, double value) + { + Key = key; + Value = value; + } +} diff --git a/src/Security/test/Authentication.Mtls.Test/ClientCertificateAuthenticationTests.cs b/src/Security/test/Authentication.Mtls.Test/ClientCertificateAuthenticationTests.cs index 7abe07486c..8adcc2b6b7 100644 --- a/src/Security/test/Authentication.Mtls.Test/ClientCertificateAuthenticationTests.cs +++ b/src/Security/test/Authentication.Mtls.Test/ClientCertificateAuthenticationTests.cs @@ -359,7 +359,9 @@ public async Task VerifyACustomHeaderFailsIfTheHeaderIsNotPresent() } [Fact] +#pragma warning disable S3776 // Cognitive Complexity of methods should not be too high public async Task VerifyNoEventWireUpWithAValidCertificateCreatesADefaultUser() +#pragma warning restore S3776 // Cognitive Complexity of methods should not be too high { TestServer server = CreateServer(new MutualTlsAuthenticationOptions { @@ -512,7 +514,9 @@ public async Task VerifyValidationEventPrincipalIsPropagated() Assert.Single(responseAsXml.Elements("claim")); } +#pragma warning disable S3776 // Cognitive Complexity of methods should not be too high private static TestServer CreateServer(MutualTlsAuthenticationOptions configureOptions, X509Certificate2 clientCertificate = null, Uri baseAddress = null, +#pragma warning restore S3776 // Cognitive Complexity of methods should not be too high bool wireUpHeaderMiddleware = false, string headerName = "") { IWebHostBuilder builder = new WebHostBuilder().Configure(app =>