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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/Common/src/Common/Discovery/IServiceInstance.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,16 @@ public interface IServiceInstance
/// </summary>
Uri Uri { get; }

/// <summary>
/// Gets the HTTP-based resolved address of the registered service instance, if available.
/// </summary>
Uri? NonSecureUri { get; }

/// <summary>
/// Gets the HTTPS-based resolved address of the registered service instance, if available.
/// </summary>
Uri? SecureUri { get; }

/// <summary>
/// Gets the key/value metadata associated with this service instance.
/// </summary>
Expand Down
2 changes: 2 additions & 0 deletions src/Common/src/Common/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
#nullable enable
Steeltoe.Common.Discovery.IServiceInstance.InstanceId.get -> string!
Steeltoe.Common.Discovery.IServiceInstance.NonSecureUri.get -> System.Uri?
Steeltoe.Common.Discovery.IServiceInstance.SecureUri.get -> System.Uri?
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,8 @@ private sealed class TestServiceInstance(string serviceId, string instanceId, Ur
public int Port { get; } = uri.Port;
public bool IsSecure { get; } = uri.Scheme == Uri.UriSchemeHttps;
public Uri Uri { get; } = uri;
public Uri? NonSecureUri => IsSecure ? null : Uri;
public Uri? SecureUri => IsSecure ? Uri : null;
public IReadOnlyDictionary<string, string?> Metadata { get; } = metadata;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ public sealed class ConfigurationServiceInstance : IServiceInstance
/// <inheritdoc />
public Uri Uri => new($"{(IsSecure ? Uri.UriSchemeHttps : Uri.UriSchemeHttp)}{Uri.SchemeDelimiter}{Host}:{Port}");

/// <inheritdoc />
public Uri? NonSecureUri => IsSecure ? null : Uri;

/// <inheritdoc />
public Uri? SecureUri => IsSecure ? Uri : null;

/// <inheritdoc cref="IServiceInstance.Metadata" />
public IDictionary<string, string?> Metadata { get; } = new Dictionary<string, string?>();
}
2 changes: 2 additions & 0 deletions src/Discovery/src/Configuration/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
#nullable enable
Steeltoe.Discovery.Configuration.ConfigurationServiceInstance.InstanceId.get -> string!
Steeltoe.Discovery.Configuration.ConfigurationServiceInstance.NonSecureUri.get -> System.Uri?
Steeltoe.Discovery.Configuration.ConfigurationServiceInstance.SecureUri.get -> System.Uri?
8 changes: 8 additions & 0 deletions src/Discovery/src/Consul/ConsulServiceInstance.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ internal sealed class ConsulServiceInstance : IServiceInstance
/// <inheritdoc />
public Uri Uri { get; }

/// <inheritdoc />
public Uri? NonSecureUri { get; }

/// <inheritdoc />
public Uri? SecureUri { get; }

public IReadOnlyList<string> Tags { get; }

/// <inheritdoc />
Expand All @@ -54,5 +60,7 @@ internal ConsulServiceInstance(ServiceEntry serviceEntry)
InstanceId = serviceEntry.Service.ID;
Port = serviceEntry.Service.Port;
Uri = new Uri($"{(IsSecure ? "https" : "http")}://{Host}:{Port}");
NonSecureUri = IsSecure ? null : Uri;
SecureUri = IsSecure ? Uri : null;
}
}
6 changes: 6 additions & 0 deletions src/Discovery/src/Consul/Registry/ConsulRegistration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ internal sealed class ConsulRegistration : IServiceInstance
/// <inheritdoc />
public Uri Uri => new($"{_optionsMonitor.CurrentValue.EffectiveScheme}://{Host}:{Port}");

/// <inheritdoc />
public Uri? NonSecureUri => IsSecure ? null : Uri;

/// <inheritdoc />
public Uri? SecureUri => IsSecure ? Uri : null;

public IReadOnlyList<string> Tags { get; }

/// <inheritdoc />
Expand Down
8 changes: 8 additions & 0 deletions src/Discovery/src/Consul/ThisServiceInstance.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ internal sealed class ThisServiceInstance : IServiceInstance
/// <inheritdoc />
public Uri Uri { get; }

/// <inheritdoc />
public Uri? NonSecureUri { get; }

/// <inheritdoc />
public Uri? SecureUri { get; }

/// <inheritdoc />
public IReadOnlyDictionary<string, string?> Metadata { get; }

Expand All @@ -44,5 +50,7 @@ public ThisServiceInstance(ConsulRegistration registration)
Port = registration.Port;
Metadata = registration.Metadata;
Uri = registration.Uri;
NonSecureUri = registration.NonSecureUri;
SecureUri = registration.SecureUri;
}
}
119 changes: 39 additions & 80 deletions src/Discovery/src/Eureka/AppInfo/ApplicationInfoCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

using System.Collections;
using System.Collections.Concurrent;
using System.Collections.ObjectModel;
using System.Text;
using System.Text.Json;
using Steeltoe.Common;
Expand All @@ -17,11 +18,8 @@ namespace Steeltoe.Discovery.Eureka.AppInfo;
/// </summary>
public sealed class ApplicationInfoCollection : IReadOnlyCollection<ApplicationInfo>
{
private readonly object _addRemoveInstanceLock = new();

internal ConcurrentDictionary<string, ApplicationInfo> ApplicationMap { get; } = new();
internal ConcurrentDictionary<string, ConcurrentDictionary<string, InstanceInfo>> VipInstanceMap { get; } = new();
internal ConcurrentDictionary<string, ConcurrentDictionary<string, InstanceInfo>> SecureVipInstanceMap { get; } = new();

public string? AppsHashCode { get; internal set; }
public long? Version { get; private set; }
Expand Down Expand Up @@ -51,18 +49,25 @@ internal ApplicationInfoCollection(IList<ApplicationInfo> apps)
return ApplicationMap.GetValueOrDefault(appName.ToUpperInvariant());
}

internal List<InstanceInfo> GetInstancesBySecureVipAddress(string secureVipAddress)
internal ReadOnlyCollection<InstanceInfo> GetInstancesByVipAddress(string vipAddress)
{
ArgumentException.ThrowIfNullOrWhiteSpace(secureVipAddress);
ArgumentException.ThrowIfNullOrWhiteSpace(vipAddress);

return GetByVipAddress(secureVipAddress, SecureVipInstanceMap);
}
List<InstanceInfo> result = [];
string addressUpper = vipAddress.ToUpperInvariant();

internal List<InstanceInfo> GetInstancesByVipAddress(string vipAddress)
{
ArgumentException.ThrowIfNullOrWhiteSpace(vipAddress);
if (VipInstanceMap.TryGetValue(addressUpper, out ConcurrentDictionary<string, InstanceInfo>? instancesById))
{
foreach (InstanceInfo instance in instancesById.Values)
{
if ((ReturnUpInstancesOnly && instance.EffectiveStatus == InstanceStatus.Up) || !ReturnUpInstancesOnly)
{
result.Add(instance);
}
}
}

return GetByVipAddress(vipAddress, VipInstanceMap);
return result.AsReadOnly();
}

/// <inheritdoc />
Expand Down Expand Up @@ -92,79 +97,51 @@ internal void Add(ApplicationInfo app)

foreach (InstanceInfo instance in app.Instances)
{
AddInstanceToVip(instance);
AddToVipInstanceMap(instance);
}
}

private void AddInstanceToVip(InstanceInfo instance)
private void AddToVipInstanceMap(InstanceInfo instance)
{
foreach (string vipAddress in ExpandVipAddresses(instance.VipAddress))
foreach (string vipAddress in ExpandVipAddresses(instance))
{
AddInstanceToVip(instance, vipAddress, VipInstanceMap);
}
string addressUpper = vipAddress.ToUpperInvariant();

foreach (string secureVipAddress in ExpandVipAddresses(instance.SecureVipAddress))
{
AddInstanceToVip(instance, secureVipAddress, SecureVipInstanceMap);
ConcurrentDictionary<string, InstanceInfo> instancesById = VipInstanceMap.GetOrAdd(addressUpper, new ConcurrentDictionary<string, InstanceInfo>());
instancesById.AddOrUpdate(instance.InstanceId, _ => instance, (_, _) => instance);
}
}

private void AddInstanceToVip(InstanceInfo instance, string address, ConcurrentDictionary<string, ConcurrentDictionary<string, InstanceInfo>> dictionary)
private static HashSet<string> ExpandVipAddresses(InstanceInfo instance)
{
lock (_addRemoveInstanceLock)
{
string addressUpper = address.ToUpperInvariant();

if (!dictionary.TryGetValue(addressUpper, out ConcurrentDictionary<string, InstanceInfo>? instances))
{
instances = new ConcurrentDictionary<string, InstanceInfo>();
dictionary[addressUpper] = instances;
}
HashSet<string> addresses = [];

instances[instance.InstanceId] = instance;
if (instance.SecureVipAddress != null)
{
string[] secureAddresses = instance.SecureVipAddress.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
addresses.UnionWith(secureAddresses);
}
}

private static string[] ExpandVipAddresses(string? addresses)
{
if (string.IsNullOrWhiteSpace(addresses))
if (instance.VipAddress != null)
{
return [];
string[] vipAddresses = instance.VipAddress.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
addresses.UnionWith(vipAddresses);
}

return addresses.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
return addresses;
}

internal void RemoveInstanceFromVip(InstanceInfo instance)
internal void RemoveFromVipInstanceMap(InstanceInfo instance)
{
ArgumentNullException.ThrowIfNull(instance);

foreach (string vipAddress in ExpandVipAddresses(instance.VipAddress))
{
RemoveInstanceFromVip(instance, vipAddress, VipInstanceMap);
}

foreach (string secureVipAddress in ExpandVipAddresses(instance.SecureVipAddress))
{
RemoveInstanceFromVip(instance, secureVipAddress, SecureVipInstanceMap);
}
}

private void RemoveInstanceFromVip(InstanceInfo instance, string address,
ConcurrentDictionary<string, ConcurrentDictionary<string, InstanceInfo>> dictionary)
{
lock (_addRemoveInstanceLock)
foreach (string vipAddress in ExpandVipAddresses(instance))
{
string addressUpper = address.ToUpperInvariant();
string addressUpper = vipAddress.ToUpperInvariant();

if (dictionary.TryGetValue(addressUpper, out ConcurrentDictionary<string, InstanceInfo>? instances))
if (VipInstanceMap.TryGetValue(addressUpper, out ConcurrentDictionary<string, InstanceInfo>? instancesById))
{
_ = instances.TryRemove(instance.InstanceId, out _);

if (instances.IsEmpty)
{
_ = dictionary.TryRemove(addressUpper, out _);
}
instancesById.TryRemove(instance.InstanceId, out _);
}
}
}
Expand All @@ -190,11 +167,11 @@ internal void UpdateFromDelta(ApplicationInfoCollection delta)
case ActionType.Added:
case ActionType.Modified:
existingApp.Add(instance);
AddInstanceToVip(instance);
AddToVipInstanceMap(instance);
break;
case ActionType.Deleted:
existingApp.Remove(instance);
RemoveInstanceFromVip(instance);
RemoveFromVipInstanceMap(instance);
break;
}
}
Expand Down Expand Up @@ -261,22 +238,4 @@ internal static ApplicationInfoCollection FromJson(JsonApplications? jsonApplica

return apps;
}

private List<InstanceInfo> GetByVipAddress(string name, ConcurrentDictionary<string, ConcurrentDictionary<string, InstanceInfo>> dictionary)
{
List<InstanceInfo> result = [];

if (dictionary.TryGetValue(name.ToUpperInvariant(), out ConcurrentDictionary<string, InstanceInfo>? instances))
{
foreach (InstanceInfo instance in instances.Values.ToArray())
{
if ((ReturnUpInstancesOnly && instance.EffectiveStatus == InstanceStatus.Up) || !ReturnUpInstancesOnly)
{
result.Add(instance);
}
}
}

return result;
}
}
40 changes: 31 additions & 9 deletions src/Discovery/src/Eureka/EurekaDiscoveryClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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.Text;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Steeltoe.Common.Discovery;
Expand Down Expand Up @@ -156,12 +157,11 @@ public EurekaDiscoveryClient(EurekaApplicationInfoManager appInfoManager, Eureka
return Applications.GetRegisteredApplication(appName);
}

internal IReadOnlyList<InstanceInfo> GetInstancesByVipAddress(string vipAddress, bool secure)
internal IReadOnlyList<InstanceInfo> GetInstancesByVipAddress(string vipAddress)
{
ArgumentException.ThrowIfNullOrWhiteSpace(vipAddress);

List<InstanceInfo> instances = secure ? Applications.GetInstancesBySecureVipAddress(vipAddress) : Applications.GetInstancesByVipAddress(vipAddress);
return instances.AsReadOnly();
return Applications.GetInstancesByVipAddress(vipAddress);
}

/// <inheritdoc />
Expand Down Expand Up @@ -533,18 +533,40 @@ public Task<IList<IServiceInstance>> GetInstancesAsync(string serviceId, Cancell
{
ArgumentException.ThrowIfNullOrWhiteSpace(serviceId);

IReadOnlyList<InstanceInfo> nonSecureInstances = GetInstancesByVipAddress(serviceId, false);
IReadOnlyList<InstanceInfo> secureInstances = GetInstancesByVipAddress(serviceId, true);

IEnumerable<InstanceInfo> instances = secureInstances.Concat(nonSecureInstances).DistinctBy(instance => instance.InstanceId);
IReadOnlyList<InstanceInfo> instances = GetInstancesByVipAddress(serviceId);
IServiceInstance[] serviceInstances = instances.Select(instance => new EurekaServiceInstance(instance)).Cast<IServiceInstance>().ToArray();

_logger.LogDebug("Returning {Count} service instances: {ServiceInstances}", serviceInstances.Length,
string.Join(", ", serviceInstances.Select(instance => $"{instance.ServiceId}={instance.Uri}")));
if (_logger.IsEnabled(LogLevel.Debug))
{
string instanceNames = string.Join(", ", serviceInstances.Select(FormatServiceInstance));
_logger.LogDebug("Returning {Count} service instances for '{ServiceId}': {ServiceInstances}", serviceInstances.Length, serviceId, instanceNames);
}

return Task.FromResult<IList<IServiceInstance>>(serviceInstances);
}

private static string FormatServiceInstance(IServiceInstance instance)
{
var builder = new StringBuilder();

if (instance.SecureUri != null)
{
builder.Append(instance.SecureUri);
}

if (instance.NonSecureUri != null)
{
if (builder.Length > 0)
{
builder.Append(';');
}

builder.Append(instance.NonSecureUri);
}

return $"{instance.InstanceId}={builder}";
}

/// <inheritdoc />
public IServiceInstance GetLocalServiceInstance()
{
Expand Down
Loading