Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add DefaultAzureCredential support to Akka.Discovery.Azure #778

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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 5 additions & 1 deletion src/discovery/azure/Akka.Discovery.Azure.Tests/ActorSpec.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,11 @@ public ActorSpec(ITestOutputHelper helper)
: base(Config, nameof(ClusterMemberTableClientSpec), helper)
{
var logger = Logging.GetLogger(Sys, nameof(ClusterMemberTableClient));
_client = new ClusterMemberTableClient(ServiceName, ConnectionString, TableName, logger);
var settings = AzureDiscoverySettings.Empty
.WithServiceName(ServiceName)
.WithConnectionString(ConnectionString)
.WithTableName(TableName);
_client = new ClusterMemberTableClient(settings, logger);
_rawClient = new TableClient(ConnectionString, TableName);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

using System;
using System.Net;
using Azure.Identity;
using FluentAssertions;
using FluentAssertions.Extensions;
using Xunit;
Expand Down Expand Up @@ -35,6 +36,8 @@ public void DefaultSettingsTest()
settings.PruneInterval.Should().Be(1.Hours());
settings.OperationTimeout.Should().Be(10.Seconds());
settings.EffectiveStaleTtlThreshold.Should().Be(new TimeSpan(settings.TtlHeartbeatInterval.Ticks * 5));
settings.AzureTableEndpoint.Should().BeNull();
settings.AzureAzureCredential.Should().BeNull();
}

[Fact(DisplayName = "Empty settings variable and default settings should match")]
Expand All @@ -53,11 +56,15 @@ public void EmptySettingsTest()
empty.PruneInterval.Should().Be(settings.PruneInterval);
empty.OperationTimeout.Should().Be(settings.OperationTimeout);
empty.EffectiveStaleTtlThreshold.Should().Be(settings.EffectiveStaleTtlThreshold);
settings.AzureTableEndpoint.Should().Be(settings.AzureTableEndpoint);
settings.AzureAzureCredential.Should().Be(settings.AzureAzureCredential);
}

[Fact(DisplayName = "Settings override should work properly")]
public void SettingsWithOverrideTest()
{
var uri = new Uri("https://whatever.com");
var credential = new DefaultAzureCredential();
var settings = AzureDiscoverySettings.Empty
.WithServiceName("a")
.WithPublicHostName("host")
Expand All @@ -67,7 +74,8 @@ public void SettingsWithOverrideTest()
.WithTtlHeartbeatInterval(1.Seconds())
.WithStaleTtlThreshold(2.Seconds())
.WithPruneInterval(3.Seconds())
.WithOperationTimeout(4.Seconds());
.WithOperationTimeout(4.Seconds())
.WithAzureCredential(uri, credential);

settings.ServiceName.Should().Be("a");
settings.HostName.Should().Be("host");
Expand All @@ -79,11 +87,15 @@ public void SettingsWithOverrideTest()
settings.PruneInterval.Should().Be(3.Seconds());
settings.OperationTimeout.Should().Be(4.Seconds());
settings.EffectiveStaleTtlThreshold.Should().Be(settings.StaleTtlThreshold);
settings.AzureTableEndpoint.Should().Be(uri);
settings.AzureAzureCredential.Should().Be(credential);
}

[Fact(DisplayName = "Setup override should work properly")]
public void SettingsWithSetupOverrideTest()
{
var uri = new Uri("https://whatever.com");
var credential = new DefaultAzureCredential();
var setup = new AzureDiscoverySetup()
.WithServiceName("a")
.WithPublicHostName("host")
Expand All @@ -93,7 +105,8 @@ public void SettingsWithSetupOverrideTest()
.WithTtlHeartbeatInterval(1.Seconds())
.WithStaleTtlThreshold(2.Seconds())
.WithPruneInterval(3.Seconds())
.WithOperationTimeout(4.Seconds());
.WithOperationTimeout(4.Seconds())
.WithAzureCredential(uri, credential);

var settings = setup.Apply(AzureDiscoverySettings.Empty);

Expand All @@ -107,6 +120,8 @@ public void SettingsWithSetupOverrideTest()
settings.PruneInterval.Should().Be(3.Seconds());
settings.OperationTimeout.Should().Be(4.Seconds());
settings.EffectiveStaleTtlThreshold.Should().Be(settings.StaleTtlThreshold);
settings.AzureTableEndpoint.Should().Be(uri);
settings.AzureAzureCredential.Should().Be(credential);
}

[Fact(DisplayName = "Settings constructor should throw on invalid values")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,11 @@ public ClusterMemberTableClientSpec(ITestOutputHelper helper)
: base("akka.loglevel = DEBUG", nameof(ClusterMemberTableClientSpec), helper)
{
var logger = Logging.GetLogger(Sys, nameof(ClusterMemberTableClient));
_client = new ClusterMemberTableClient(ServiceName, ConnectionString, TableName, logger);
var settings = AzureDiscoverySettings.Empty
.WithServiceName(ServiceName)
.WithConnectionString(ConnectionString)
.WithTableName(TableName);
_client = new ClusterMemberTableClient(settings, logger);
_rawClient = new TableClient(ConnectionString, TableName);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,8 @@ public static IEnumerable<object[]> StartupFactory()
};
builder.WithAzureDiscovery(setup);
}
// Could not test DefaultAzureCredential because that requires HTTPS and bearer token,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very helpful, thanks for adding this

// and azurite does not support that
};

foreach (var startup in startups)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,7 @@ private ClusterMemberTableClient Client
if(_clientDoNotUseDirectly != null)
return _clientDoNotUseDirectly;

_clientDoNotUseDirectly = new ClusterMemberTableClient(
serviceName: _settings.ServiceName,
connectionString: _settings.ConnectionString,
tableName: _settings.TableName,
log: _log);
_clientDoNotUseDirectly = new ClusterMemberTableClient(_settings, _log);

return _clientDoNotUseDirectly;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
<PackageReference Include="Akka.Cluster" Version="$(AkkaVersion)" />
<PackageReference Include="Akka.Hosting" Version="$(AkkaHostingVersion)" />
<PackageReference Include="Azure.Data.Tables" Version="12.6.1" />
<PackageReference Include="Azure.Identity" Version="1.6.1" />
<PackageReference Include="Google.Protobuf" Version="3.21.5" />
<PackageReference Include="Grpc.Tools" Version="2.48.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
Expand Down
20 changes: 20 additions & 0 deletions src/discovery/azure/Akka.Discovery.Azure/AkkaHostingExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using System.Net;
using Akka.Actor;
using Akka.Hosting;
using Azure.Identity;

namespace Akka.Discovery.Azure
{
Expand Down Expand Up @@ -55,6 +56,25 @@ public static class AkkaHostingExtensions
return builder.WithAzureDiscovery(setup);
}

public static AkkaConfigurationBuilder WithAzureDiscovery(
this AkkaConfigurationBuilder builder,
Uri azureTableEndpoint,
DefaultAzureCredential azureCredential,
string serviceName = null,
string publicHostname = null,
int? publicPort = null)
{
var setup = new AzureDiscoverySetup
{
AzureTableEndpoint = azureTableEndpoint,
AzureCredential = azureCredential,
ServiceName = serviceName,
HostName = publicHostname,
Port = publicPort
};
return builder.WithAzureDiscovery(setup);
}

/// <summary>
/// Adds Akka.Discovery.Azure support to the <see cref="ActorSystem"/>.
/// Note that this only adds the discovery plugin, you will still need to add ClusterBootstrap for
Expand Down
28 changes: 23 additions & 5 deletions src/discovery/azure/Akka.Discovery.Azure/AzureDiscoverySettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System;
using System.Net;
using Akka.Actor;
using Azure.Identity;

namespace Akka.Discovery.Azure
{
Expand All @@ -23,7 +24,9 @@ public sealed class AzureDiscoverySettings
pruneInterval: TimeSpan.FromHours(1),
operationTimeout: TimeSpan.FromSeconds(10),
retryBackoff: TimeSpan.FromMilliseconds(500),
maximumRetryBackoff: TimeSpan.FromSeconds(5));
maximumRetryBackoff: TimeSpan.FromSeconds(5),
azureTableEndpoint: null,
azureCredential: null);

public static AzureDiscoverySettings Create(ActorSystem system)
=> Create(system.Settings.Config);
Expand All @@ -50,7 +53,9 @@ public static AzureDiscoverySettings Create(Configuration.Config config)
pruneInterval: cfg.GetTimeSpan("prune-interval"),
operationTimeout: cfg.GetTimeSpan("operation-timeout"),
retryBackoff: cfg.GetTimeSpan("retry-backoff"),
maximumRetryBackoff: cfg.GetTimeSpan("max-retry-backoff"));
maximumRetryBackoff: cfg.GetTimeSpan("max-retry-backoff"),
azureTableEndpoint: null,
azureCredential: null);
}

private AzureDiscoverySettings(
Expand All @@ -64,7 +69,9 @@ public static AzureDiscoverySettings Create(Configuration.Config config)
TimeSpan pruneInterval,
TimeSpan operationTimeout,
TimeSpan retryBackoff,
TimeSpan maximumRetryBackoff)
TimeSpan maximumRetryBackoff,
Uri azureTableEndpoint,
DefaultAzureCredential azureCredential)
{
if (ttlHeartbeatInterval <= TimeSpan.Zero)
throw new ArgumentException("Must be greater than zero", nameof(ttlHeartbeatInterval));
Expand Down Expand Up @@ -107,6 +114,8 @@ public static AzureDiscoverySettings Create(Configuration.Config config)
OperationTimeout = operationTimeout;
RetryBackoff = retryBackoff;
MaximumRetryBackoff = maximumRetryBackoff;
AzureTableEndpoint = azureTableEndpoint;
AzureAzureCredential = azureCredential;
}

public string ServiceName { get; }
Expand All @@ -120,6 +129,8 @@ public static AzureDiscoverySettings Create(Configuration.Config config)
public TimeSpan OperationTimeout { get; }
public TimeSpan RetryBackoff { get; }
public TimeSpan MaximumRetryBackoff { get; }
public Uri AzureTableEndpoint { get; }
public DefaultAzureCredential AzureAzureCredential { get; }

public override string ToString()
=> "[AzureDiscoverySettings](" +
Expand Down Expand Up @@ -167,6 +178,9 @@ public AzureDiscoverySettings WithOperationTimeout(TimeSpan operationTimeout)

public AzureDiscoverySettings WithRetryBackoff(TimeSpan retryBackoff, TimeSpan maximumRetryBackoff)
=> Copy(retryBackoff: retryBackoff, maximumRetryBackoff: maximumRetryBackoff);

public AzureDiscoverySettings WithAzureCredential(Uri azureTableEndpoint, DefaultAzureCredential credential)
=> Copy(azureTableEndpoint: azureTableEndpoint, credential: credential);

private AzureDiscoverySettings Copy(
string serviceName = null,
Expand All @@ -179,7 +193,9 @@ public AzureDiscoverySettings WithRetryBackoff(TimeSpan retryBackoff, TimeSpan m
TimeSpan? ttlHeartbeatInterval = null,
TimeSpan? operationTimeout = null,
TimeSpan? retryBackoff = null,
TimeSpan? maximumRetryBackoff = null)
TimeSpan? maximumRetryBackoff = null,
Uri azureTableEndpoint = null,
DefaultAzureCredential credential = null)
=> new AzureDiscoverySettings(
serviceName: serviceName ?? ServiceName,
hostName: host ?? HostName,
Expand All @@ -191,6 +207,8 @@ public AzureDiscoverySettings WithRetryBackoff(TimeSpan retryBackoff, TimeSpan m
pruneInterval: pruneInterval ?? PruneInterval,
operationTimeout: operationTimeout ?? OperationTimeout,
retryBackoff: retryBackoff ?? RetryBackoff,
maximumRetryBackoff: maximumRetryBackoff ?? MaximumRetryBackoff);
maximumRetryBackoff: maximumRetryBackoff ?? MaximumRetryBackoff,
azureTableEndpoint: azureTableEndpoint ?? AzureTableEndpoint,
azureCredential: credential ?? AzureAzureCredential);
}
}
16 changes: 16 additions & 0 deletions src/discovery/azure/Akka.Discovery.Azure/AzureDiscoverySetup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System;
using System.Collections.Generic;
using Akka.Actor.Setup;
using Azure.Identity;

namespace Akka.Discovery.Azure
{
Expand All @@ -23,6 +24,8 @@ public sealed class AzureDiscoverySetup: Setup
public TimeSpan? OperationTimeout { get; set; }
public TimeSpan? RetryBackoff { get; set; }
public TimeSpan? MaximumRetryBackoff { get; set; }
public Uri AzureTableEndpoint { get; set; }
public DefaultAzureCredential AzureCredential { get; set; }

public AzureDiscoverySetup WithServiceName(string serviceName)
{
Expand Down Expand Up @@ -84,6 +87,13 @@ public AzureDiscoverySetup WithRetryBackoff(TimeSpan retryBackoff, TimeSpan maxi
MaximumRetryBackoff = maximumRetryBackoff;
return this;
}

public AzureDiscoverySetup WithAzureCredential(Uri azureTableEndpoint, DefaultAzureCredential credential)
{
AzureTableEndpoint = azureTableEndpoint;
AzureCredential = credential;
return this;
}

public override string ToString()
{
Expand All @@ -110,6 +120,10 @@ public override string ToString()
props.Add($"{nameof(RetryBackoff)}:{RetryBackoff}");
if(MaximumRetryBackoff != null)
props.Add($"{nameof(MaximumRetryBackoff)}:{MaximumRetryBackoff}");
if(AzureTableEndpoint != null)
props.Add($"{nameof(AzureTableEndpoint)}:{AzureTableEndpoint}");
if(AzureCredential != null)
props.Add($"{nameof(AzureCredential)}:{AzureCredential}");

return $"[AzureDiscoverySetup]({string.Join(", ", props)})";
}
Expand All @@ -136,6 +150,8 @@ public AzureDiscoverySettings Apply(AzureDiscoverySettings setting)
setting = setting.WithOperationTimeout(OperationTimeout.Value);
if (RetryBackoff != null && MaximumRetryBackoff != null)
setting = setting.WithRetryBackoff(RetryBackoff.Value, MaximumRetryBackoff.Value);
if (AzureTableEndpoint != null && AzureCredential != null)
setting = setting.WithAzureCredential(AzureTableEndpoint, AzureCredential);

return setting;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
// </copyright>
// -----------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Net;
Expand All @@ -14,6 +15,7 @@
using Akka.Event;
using Azure;
using Azure.Data.Tables;
using Azure.Identity;

namespace Akka.Discovery.Azure
{
Expand All @@ -25,11 +27,15 @@ internal class ClusterMemberTableClient
private bool _initialized;
private ClusterMember _entity;

public ClusterMemberTableClient(string serviceName, string connectionString, string tableName, ILoggingAdapter log)
public ClusterMemberTableClient(
AzureDiscoverySettings settings,
ILoggingAdapter log)
{
_log = log;
_serviceName = serviceName;
_client = new TableClient(connectionString, tableName);
_serviceName = settings.ServiceName;
_client = (settings.AzureAzureCredential != null && settings.AzureTableEndpoint != null)
? new TableClient(settings.AzureTableEndpoint, settings.TableName, settings.AzureAzureCredential)
: new TableClient(settings.ConnectionString, settings.TableName);
}

/// <summary>
Expand Down