Skip to content

Commit

Permalink
Add DefaultAzureCredential support to Akka.Discovery.Azure (#778)
Browse files Browse the repository at this point in the history
Co-authored-by: Aaron Stannard <aaron@petabridge.com>
  • Loading branch information
Arkatufus and Aaronontheweb committed Aug 24, 2022
1 parent fabb5f1 commit e0957ee
Show file tree
Hide file tree
Showing 10 changed files with 99 additions and 17 deletions.
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,
// 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

0 comments on commit e0957ee

Please sign in to comment.