Wait for sidecar in DaprSecretStoreConfiguration (#838)

The secret store configuration provider was trying to access Dapr
during the app startup. If the app started faster than Dapr, it would
get an error trying to access the secrets. This commit lets the
provider wait for the sidecar to come up before making any requests.
If the sidecar does not come up at all, we will still fail.

https://github.com/dapr/dotnet-sdk/issues/779

Signed-off-by: Hal Spang <halspang@microsoft.com>
This commit is contained in:
halspang 2022-02-17 16:39:33 -08:00 committed by GitHub
parent dca6106af7
commit a6319dd4b6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 370 additions and 43 deletions

View File

@ -19,6 +19,7 @@
using Dapr.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using System.Collections.Generic;
using System;
/// <summary>
/// Secret Store Configuration Provider Sample.
@ -62,7 +63,8 @@
// configBuilder.AddDaprSecretStore("demosecrets", secretDescriptors, client);
// Add the secret store Configuration Provider to the configuration builder.
configBuilder.AddDaprSecretStore("demosecrets", client);
// Including a TimeSpan allows us to dictate how long we should wait for the Sidecar to start.
configBuilder.AddDaprSecretStore("demosecrets", client, TimeSpan.FromSeconds(10));
})
.ConfigureWebHostDefaults(webBuilder =>
{

View File

@ -313,6 +313,23 @@ namespace Dapr.Client
/// <param name="cancellationToken">A <see cref="CancellationToken" /> that can be used to cancel the operation.</param>
/// <returns>A <see cref="Task{T}" /> that will return the value when the operation has completed.</returns>
public abstract Task<bool> CheckHealthAsync(CancellationToken cancellationToken = default);
/// <summary>
/// Perform health-check of Dapr sidecar's outbound APIs. Return 'true' if the sidecar is healthy. Otherwise false. This method should
/// be used over <see cref="CheckHealthAsync(CancellationToken)"/> when the health of Dapr is being checked before it starts. This
/// health endpoint indicates that Dapr has stood up its APIs and is currently waiting on this application to report fully healthy.
/// </summary>
/// <param name="cancellationToken">A <see cref="CancellationToken" /> that can be used to cancel the operation.</param>
/// <returns>A <see cref="Task{T}" /> that will return the value when the operation has completed.</returns>
public abstract Task<bool> CheckOutboundHealthAsync(CancellationToken cancellationToken = default);
/// <summary>
/// Calls <see cref="CheckOutboundHealthAsync(CancellationToken)"/> until the sidecar is reporting as healthy. If the sidecar
/// does not become healthy, an exception will be thrown.
/// </summary>
/// <param name="cancellationToken">A <see cref="CancellationToken" /> that can be used to cancel the operation.</param>
/// <returns>A <see cref="Task" /> that will return when the operation has completed.</returns>
public abstract Task WaitForSidecarAsync(CancellationToken cancellationToken = default);
/// <summary>
/// Perform service invocation using the request provided by <paramref name="request" />. The response will

View File

@ -305,6 +305,34 @@ namespace Dapr.Client
}
}
public override async Task<bool> CheckOutboundHealthAsync(CancellationToken cancellationToken = default)
{
var path = "/v1.0/healthz/outbound";
var request = new HttpRequestMessage(HttpMethod.Get, new Uri(this.httpEndpoint, path));
try
{
var response = await this.httpClient.SendAsync(request, cancellationToken);
return response.IsSuccessStatusCode;
}
catch (HttpRequestException)
{
return false;
}
}
public override async Task WaitForSidecarAsync(CancellationToken cancellationToken = default)
{
while (true)
{
var response = await CheckOutboundHealthAsync(cancellationToken);
if (response)
{
break;
}
await Task.Delay(TimeSpan.FromMilliseconds(500), cancellationToken);
}
}
public override async Task<HttpResponseMessage> InvokeMethodWithResponseAsync(HttpRequestMessage request, CancellationToken cancellationToken = default)
{
ArgumentVerifier.ThrowIfNull(request, nameof(request));

View File

@ -17,6 +17,7 @@ using Dapr.Client;
using Microsoft.Extensions.Configuration;
using Dapr.Extensions.Configuration.DaprSecretStore;
using System.Linq;
using System.Threading;
namespace Dapr.Extensions.Configuration
{
@ -53,6 +54,37 @@ namespace Dapr.Extensions.Configuration
return configurationBuilder;
}
/// <summary>
/// Adds an <see cref="IConfigurationProvider"/> that reads configuration values from the Dapr Secret Store.
/// </summary>
/// <param name="configurationBuilder">The <see cref="IConfigurationBuilder"/> to add to.</param>
/// <param name="store">Dapr secret store name.</param>
/// <param name="secretDescriptors">The secrets to retrieve.</param>
/// <param name="client">The Dapr client.</param>
/// <param name="sidecarWaitTimeout">The <see cref="TimeSpan"/> used to configure the timeout waiting for Dapr.</param>
/// <returns>The <see cref="IConfigurationBuilder"/>.</returns>
public static IConfigurationBuilder AddDaprSecretStore(
this IConfigurationBuilder configurationBuilder,
string store,
IEnumerable<DaprSecretDescriptor> secretDescriptors,
DaprClient client,
TimeSpan sidecarWaitTimeout)
{
ArgumentVerifier.ThrowIfNullOrEmpty(store, nameof(store));
ArgumentVerifier.ThrowIfNull(secretDescriptors, nameof(secretDescriptors));
ArgumentVerifier.ThrowIfNull(client, nameof(client));
configurationBuilder.Add(new DaprSecretStoreConfigurationSource()
{
Store = store,
SecretDescriptors = secretDescriptors,
Client = client,
SidecarWaitTimeout = sidecarWaitTimeout
});
return configurationBuilder;
}
/// <summary>
/// Adds an <see cref="IConfigurationProvider"/> that reads configuration values from the Dapr Secret Store.
/// </summary>
@ -80,6 +112,36 @@ namespace Dapr.Extensions.Configuration
return configurationBuilder;
}
/// <summary>
/// Adds an <see cref="IConfigurationProvider"/> that reads configuration values from the Dapr Secret Store.
/// </summary>
/// <param name="configurationBuilder">The <see cref="IConfigurationBuilder"/> to add to.</param>
/// <param name="store">Dapr secret store name.</param>
/// <param name="metadata">A collection of metadata key-value pairs that will be provided to the secret store. The valid metadata keys and values are determined by the type of secret store used.</param>
/// <param name="client">The Dapr client</param>
/// <param name="sidecarWaitTimeout">The <see cref="TimeSpan"/> used to configure the timeout waiting for Dapr.</param>
/// <returns>The <see cref="IConfigurationBuilder"/>.</returns>
public static IConfigurationBuilder AddDaprSecretStore(
this IConfigurationBuilder configurationBuilder,
string store,
DaprClient client,
TimeSpan sidecarWaitTimeout,
IReadOnlyDictionary<string, string>? metadata = null)
{
ArgumentVerifier.ThrowIfNullOrEmpty(store, nameof(store));
ArgumentVerifier.ThrowIfNull(client, nameof(client));
configurationBuilder.Add(new DaprSecretStoreConfigurationSource()
{
Store = store,
Metadata = metadata,
Client = client,
SidecarWaitTimeout = sidecarWaitTimeout
});
return configurationBuilder;
}
/// <summary>
/// Adds an <see cref="IConfigurationProvider"/> that reads configuration values from the Dapr Secret Store.
/// </summary>
@ -113,6 +175,42 @@ namespace Dapr.Extensions.Configuration
return configurationBuilder;
}
/// <summary>
/// Adds an <see cref="IConfigurationProvider"/> that reads configuration values from the Dapr Secret Store.
/// </summary>
/// <param name="configurationBuilder">The <see cref="IConfigurationBuilder"/> to add to.</param>
/// <param name="store">Dapr secret store name.</param>
/// <param name="keyDelimiters">A collection of delimiters that will be replaced by ':' in the key of every secret.</param>
/// <param name="client">The Dapr client</param>
/// <param name="sidecarWaitTimeout">The <see cref="TimeSpan"/> used to configure the timeout waiting for Dapr.</param>
/// <returns>The <see cref="IConfigurationBuilder"/>.</returns>
public static IConfigurationBuilder AddDaprSecretStore(
this IConfigurationBuilder configurationBuilder,
string store,
DaprClient client,
IEnumerable<string>? keyDelimiters,
TimeSpan sidecarWaitTimeout)
{
ArgumentVerifier.ThrowIfNullOrEmpty(store, nameof(store));
ArgumentVerifier.ThrowIfNull(client, nameof(client));
var source = new DaprSecretStoreConfigurationSource
{
Store = store,
Client = client,
SidecarWaitTimeout = sidecarWaitTimeout
};
if (keyDelimiters != null)
{
source.KeyDelimiters = keyDelimiters.ToList();
}
configurationBuilder.Add(source);
return configurationBuilder;
}
/// <summary>
/// Adds an <see cref="IConfigurationProvider"/> that reads configuration values from the command line.
/// </summary>

View File

@ -14,6 +14,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Dapr.Client;
using Microsoft.Extensions.Configuration;
@ -25,6 +26,8 @@ namespace Dapr.Extensions.Configuration.DaprSecretStore
/// </summary>
internal class DaprSecretStoreConfigurationProvider : ConfigurationProvider
{
internal static readonly TimeSpan DefaultSidecarWaitTimeout = TimeSpan.FromSeconds(5);
private readonly string store;
private readonly bool normalizeKey;
@ -37,39 +40,56 @@ namespace Dapr.Extensions.Configuration.DaprSecretStore
private readonly DaprClient client;
private readonly TimeSpan sidecarWaitTimeout;
/// <summary>
/// Creates a new instance of <see cref="DaprSecretStoreConfigurationProvider"/>.
/// </summary>
/// <param name="store">Dapr Secre Store name.</param>
/// <param name="store">Dapr Secret Store name.</param>
/// <param name="normalizeKey">Indicates whether any key delimiters should be replaced with the delimiter ":".</param>
/// <param name="secretDescriptors">The secrets to retrieve.</param>
/// <param name="client">Dapr client used to retrieve Secrets</param>
public DaprSecretStoreConfigurationProvider(string store, bool normalizeKey, IEnumerable<DaprSecretDescriptor> secretDescriptors, DaprClient client)
{
ArgumentVerifier.ThrowIfNullOrEmpty(store, nameof(store));
ArgumentVerifier.ThrowIfNull(secretDescriptors, nameof(secretDescriptors));
ArgumentVerifier.ThrowIfNull(client, nameof(client));
if (secretDescriptors.Count() == 0)
{
throw new ArgumentException("No secret descriptor was provided", nameof(secretDescriptors));
}
this.store = store;
this.normalizeKey = normalizeKey;
this.secretDescriptors = secretDescriptors;
this.client = client;
public DaprSecretStoreConfigurationProvider(
string store,
bool normalizeKey,
IEnumerable<DaprSecretDescriptor> secretDescriptors,
DaprClient client) : this(store, normalizeKey, null, secretDescriptors, client, DefaultSidecarWaitTimeout)
{
}
/// <summary>
/// Creates a new instance of <see cref="DaprSecretStoreConfigurationProvider"/>.
/// </summary>
/// <param name="store">Dapr Secre Store name.</param>
/// <param name="store">Dapr Secret Store name.</param>
/// <param name="normalizeKey">Indicates whether any key delimiters should be replaced with the delimiter ":".</param>
/// <param name="keyDelimiters">A collection of delimiters that will be replaced by ':' in the key of every secret.</param>
/// <param name="secretDescriptors">The secrets to retrieve.</param>
/// <param name="client">Dapr client used to retrieve Secrets</param>
public DaprSecretStoreConfigurationProvider(string store, bool normalizeKey, IList<string>? keyDelimiters, IEnumerable<DaprSecretDescriptor> secretDescriptors, DaprClient client)
public DaprSecretStoreConfigurationProvider(
string store,
bool normalizeKey,
IList<string>? keyDelimiters,
IEnumerable<DaprSecretDescriptor> secretDescriptors,
DaprClient client) : this(store, normalizeKey, keyDelimiters, secretDescriptors, client, DefaultSidecarWaitTimeout)
{
}
/// <summary>
/// Creates a new instance of <see cref="DaprSecretStoreConfigurationProvider"/>.
/// </summary>
/// <param name="store">Dapr Secret Store name.</param>
/// <param name="normalizeKey">Indicates whether any key delimiters should be replaced with the delimiter ":".</param>
/// <param name="keyDelimiters">A collection of delimiters that will be replaced by ':' in the key of every secret.</param>
/// <param name="secretDescriptors">The secrets to retrieve.</param>
/// <param name="client">Dapr client used to retrieve Secrets</param>
/// <param name="sidecarWaitTimeout">The <see cref="TimeSpan"/> used to configure the timeout waiting for Dapr.</param>
public DaprSecretStoreConfigurationProvider(
string store,
bool normalizeKey,
IList<string>? keyDelimiters,
IEnumerable<DaprSecretDescriptor> secretDescriptors,
DaprClient client,
TimeSpan sidecarWaitTimeout)
{
ArgumentVerifier.ThrowIfNullOrEmpty(store, nameof(store));
ArgumentVerifier.ThrowIfNull(secretDescriptors, nameof(secretDescriptors));
@ -85,35 +105,57 @@ namespace Dapr.Extensions.Configuration.DaprSecretStore
this.keyDelimiters = keyDelimiters;
this.secretDescriptors = secretDescriptors;
this.client = client;
this.sidecarWaitTimeout = sidecarWaitTimeout;
}
/// <summary>
/// Creates a new instance of <see cref="DaprSecretStoreConfigurationProvider"/>.
/// </summary>
/// <param name="store">Dapr Secre Store name.</param>
/// <param name="store">Dapr Secret Store name.</param>
/// <param name="normalizeKey">Indicates whether any key delimiters should be replaced with the delimiter ":".</param>
/// <param name="metadata">A collection of metadata key-value pairs that will be provided to the secret store. The valid metadata keys and values are determined by the type of secret store used.</param>
/// <param name="client">Dapr client used to retrieve Secrets</param>
public DaprSecretStoreConfigurationProvider(string store, bool normalizeKey, IReadOnlyDictionary<string, string>? metadata, DaprClient client)
public DaprSecretStoreConfigurationProvider(
string store,
bool normalizeKey,
IReadOnlyDictionary<string, string>? metadata,
DaprClient client) : this(store, normalizeKey, null, metadata, client, DefaultSidecarWaitTimeout)
{
ArgumentVerifier.ThrowIfNullOrEmpty(store, nameof(store));
ArgumentVerifier.ThrowIfNull(client, nameof(client));
this.store = store;
this.normalizeKey = normalizeKey;
this.metadata = metadata;
this.client = client;
}
/// <summary>
/// Creates a new instance of <see cref="DaprSecretStoreConfigurationProvider"/>.
/// </summary>
/// <param name="store">Dapr Secre Store name.</param>
/// <param name="store">Dapr Secret Store name.</param>
/// <param name="normalizeKey">Indicates whether any key delimiters should be replaced with the delimiter ":".</param>
/// <param name="keyDelimiters">A collection of delimiters that will be replaced by ':' in the key of every secret.</param>
/// <param name="metadata">A collection of metadata key-value pairs that will be provided to the secret store. The valid metadata keys and values are determined by the type of secret store used.</param>
/// <param name="client">Dapr client used to retrieve Secrets</param>
public DaprSecretStoreConfigurationProvider(string store, bool normalizeKey, IList<string>? keyDelimiters, IReadOnlyDictionary<string, string>? metadata, DaprClient client)
public DaprSecretStoreConfigurationProvider(
string store,
bool normalizeKey,
IList<string>? keyDelimiters,
IReadOnlyDictionary<string, string>? metadata,
DaprClient client) : this(store, normalizeKey, keyDelimiters, metadata, client, DefaultSidecarWaitTimeout)
{
}
/// <summary>
/// Creates a new instance of <see cref="DaprSecretStoreConfigurationProvider"/>.
/// </summary>
/// <param name="store">Dapr Secret Store name.</param>
/// <param name="normalizeKey">Indicates whether any key delimiters should be replaced with the delimiter ":".</param>
/// <param name="keyDelimiters">A collection of delimiters that will be replaced by ':' in the key of every secret.</param>
/// <param name="metadata">A collection of metadata key-value pairs that will be provided to the secret store. The valid metadata keys and values are determined by the type of secret store used.</param>
/// <param name="client">Dapr client used to retrieve Secrets</param>
/// <param name="sidecarWaitTimeout">The <see cref="TimeSpan"/> used to configure the timeout waiting for Dapr.</param>
public DaprSecretStoreConfigurationProvider(
string store,
bool normalizeKey,
IList<string>? keyDelimiters,
IReadOnlyDictionary<string, string>? metadata,
DaprClient client,
TimeSpan sidecarWaitTimeout)
{
ArgumentVerifier.ThrowIfNullOrEmpty(store, nameof(store));
ArgumentVerifier.ThrowIfNull(client, nameof(client));
@ -123,6 +165,7 @@ namespace Dapr.Extensions.Configuration.DaprSecretStore
this.keyDelimiters = keyDelimiters;
this.metadata = metadata;
this.client = client;
this.sidecarWaitTimeout = sidecarWaitTimeout;
}
private string NormalizeKey(string key)
@ -144,6 +187,12 @@ namespace Dapr.Extensions.Configuration.DaprSecretStore
{
var data = new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase);
// Wait for the Dapr Sidecar to report healthy before attempting to fetch secrets.
using (var tokenSource = new CancellationTokenSource(sidecarWaitTimeout))
{
await client.WaitForSidecarAsync(tokenSource.Token);
}
if (secretDescriptors != null)
{
foreach (var secretDescriptor in secretDescriptors)

View File

@ -13,6 +13,7 @@
using System;
using System.Collections.Generic;
using System.Threading;
using Dapr.Client;
using Microsoft.Extensions.Configuration;
@ -54,6 +55,11 @@ namespace Dapr.Extensions.Configuration.DaprSecretStore
/// </summary>
public DaprClient Client { get; set; } = default!;
/// <summary>
/// Gets or sets the <see cref="TimeSpan"/> that is used to control the timeout waiting for the Dapr sidecar to become healthly.
/// </summary>
public TimeSpan? SidecarWaitTimeout { get; set; }
/// <inheritdoc />
public IConfigurationProvider Build(IConfigurationBuilder builder)
{
@ -64,11 +70,11 @@ namespace Dapr.Extensions.Configuration.DaprSecretStore
throw new ArgumentException($"{nameof(Metadata)} must be null when {nameof(SecretDescriptors)} is set", nameof(Metadata));
}
return new DaprSecretStoreConfigurationProvider(Store, NormalizeKey, KeyDelimiters, SecretDescriptors, Client);
return new DaprSecretStoreConfigurationProvider(Store, NormalizeKey, KeyDelimiters, SecretDescriptors, Client, SidecarWaitTimeout ?? DaprSecretStoreConfigurationProvider.DefaultSidecarWaitTimeout);
}
else
{
return new DaprSecretStoreConfigurationProvider(Store, NormalizeKey, KeyDelimiters, Metadata, Client);
return new DaprSecretStoreConfigurationProvider(Store, NormalizeKey, KeyDelimiters, Metadata, Client, SidecarWaitTimeout ?? DaprSecretStoreConfigurationProvider.DefaultSidecarWaitTimeout);
}
}
}

View File

@ -19,6 +19,7 @@ namespace Dapr.Client.Test
using System.Net.Http.Json;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Dapr.Client;
using Xunit;
@ -308,6 +309,78 @@ namespace Dapr.Client.Test
Assert.False(result);
}
[Fact]
public async Task CheckOutboundHealthAsync_Success()
{
await using var client = TestClient.CreateForDaprClient(c =>
{
c.UseGrpcEndpoint("http://localhost").UseHttpEndpoint("https://test-endpoint:3501").UseJsonSerializationOptions(this.jsonSerializerOptions);
});
var request = await client.CaptureHttpRequestAsync<bool>(async daprClient => await daprClient.CheckOutboundHealthAsync());
Assert.Equal(request.Request.Method, HttpMethod.Get);
Assert.Equal(new Uri("https://test-endpoint:3501/v1.0/healthz/outbound").AbsoluteUri, request.Request.RequestUri.AbsoluteUri);
var result = await request.CompleteAsync(new HttpResponseMessage());
Assert.True(result);
}
[Fact]
public async Task CheckOutboundHealthAsync_NotSuccess()
{
await using var client = TestClient.CreateForDaprClient(c =>
{
c.UseGrpcEndpoint("http://localhost").UseHttpEndpoint("https://test-endpoint:3501").UseJsonSerializationOptions(this.jsonSerializerOptions);
});
var request = await client.CaptureHttpRequestAsync<bool>(async daprClient => await daprClient.CheckOutboundHealthAsync());
Assert.Equal(request.Request.Method, HttpMethod.Get);
Assert.Equal(new Uri("https://test-endpoint:3501/v1.0/healthz/outbound").AbsoluteUri, request.Request.RequestUri.AbsoluteUri);
var result = await request.CompleteAsync(new HttpResponseMessage(HttpStatusCode.InternalServerError));
Assert.False(result);
}
[Fact]
public async Task CheckOutboundHealthAsync_WrapsRequestException()
{
await using var client = TestClient.CreateForDaprClient(c =>
{
c.UseGrpcEndpoint("http://localhost").UseHttpEndpoint("https://test-endpoint:3501").UseJsonSerializationOptions(this.jsonSerializerOptions);
});
var request = await client.CaptureHttpRequestAsync<bool>(async daprClient => await daprClient.CheckOutboundHealthAsync());
Assert.Equal(request.Request.Method, HttpMethod.Get);
Assert.Equal(new Uri("https://test-endpoint:3501/v1.0/healthz/outbound").AbsoluteUri, request.Request.RequestUri.AbsoluteUri);
var result = await request.CompleteWithExceptionAndResultAsync(new HttpRequestException());
Assert.False(result);
}
[Fact]
public async Task WaitForSidecarAsync_SuccessWhenSidecarHealthy()
{
await using var client = TestClient.CreateForDaprClient();
var request = await client.CaptureHttpRequestAsync(async daprClient => await daprClient.WaitForSidecarAsync());
// If we don't throw, we're good.
await request.CompleteAsync(new HttpResponseMessage());
}
[Fact]
public async Task WaitForSidecarAsync_NotSuccessWhenSidecarNotHealthy()
{
await using var client = TestClient.CreateForDaprClient();
using var cts = new CancellationTokenSource();
var waitRequest = await client.CaptureHttpRequestAsync(async daprClient => await daprClient.WaitForSidecarAsync(cts.Token));
var healthRequest = await client.CaptureHttpRequestAsync<bool>(async daprClient => await daprClient.CheckOutboundHealthAsync());
cts.Cancel();
await healthRequest.CompleteAsync(new HttpResponseMessage(HttpStatusCode.InternalServerError));
await Assert.ThrowsAsync<TaskCanceledException>(async () => await waitRequest.CompleteWithExceptionAsync(new TaskCanceledException()));
}
[Fact]
public async Task InvokeMethodAsync_WrapsHttpRequestException()
{

View File

@ -125,6 +125,7 @@ namespace Dapr.Extensions.Configuration.Test
};
var daprClient = new DaprClientBuilder()
.UseHttpClientFactory(() => httpClient)
.UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient })
.Build();
@ -152,6 +153,7 @@ namespace Dapr.Extensions.Configuration.Test
};
var daprClient = new DaprClientBuilder()
.UseHttpClientFactory(() => httpClient)
.UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient })
.Build();
@ -178,6 +180,7 @@ namespace Dapr.Extensions.Configuration.Test
};
var daprClient = new DaprClientBuilder()
.UseHttpClientFactory(() => httpClient)
.UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient })
.Build();
@ -250,6 +253,7 @@ namespace Dapr.Extensions.Configuration.Test
};
var daprClient = new DaprClientBuilder()
.UseHttpClientFactory(() => httpClient)
.UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient })
.Build();
@ -277,6 +281,7 @@ namespace Dapr.Extensions.Configuration.Test
};
var daprClient = new DaprClientBuilder()
.UseHttpClientFactory(() => httpClient)
.UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient })
.Build();
@ -303,6 +308,7 @@ namespace Dapr.Extensions.Configuration.Test
};
var daprClient = new DaprClientBuilder()
.UseHttpClientFactory(() => httpClient)
.UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient })
.Build();
@ -328,6 +334,7 @@ namespace Dapr.Extensions.Configuration.Test
};
var daprClient = new DaprClientBuilder()
.UseHttpClientFactory(() => httpClient)
.UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient })
.Build();
@ -353,6 +360,7 @@ namespace Dapr.Extensions.Configuration.Test
};
var daprClient = new DaprClientBuilder()
.UseHttpClientFactory(() => httpClient)
.UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient })
.Build();
@ -377,6 +385,7 @@ namespace Dapr.Extensions.Configuration.Test
};
var daprClient = new DaprClientBuilder()
.UseHttpClientFactory(() => httpClient)
.UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient })
.Build();
@ -408,6 +417,7 @@ namespace Dapr.Extensions.Configuration.Test
};
var daprClient = new DaprClientBuilder()
.UseHttpClientFactory(() => httpClient)
.UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient })
.Build();
@ -440,6 +450,7 @@ namespace Dapr.Extensions.Configuration.Test
};
var daprClient = new DaprClientBuilder()
.UseHttpClientFactory(() => httpClient)
.UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient })
.Build();
@ -473,6 +484,7 @@ namespace Dapr.Extensions.Configuration.Test
};
var daprClient = new DaprClientBuilder()
.UseHttpClientFactory(() => httpClient)
.UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient })
.Build();
@ -506,6 +518,7 @@ namespace Dapr.Extensions.Configuration.Test
};
var daprClient = new DaprClientBuilder()
.UseHttpClientFactory(() => httpClient)
.UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient })
.Build();
@ -531,25 +544,33 @@ namespace Dapr.Extensions.Configuration.Test
Handler = async (entry) =>
{
// The following is an attempt at handling multiple secret descriptors for unit tests.
var content = await entry.Request.Content.ReadAsStringAsync();
if (content.Contains("secretName--value"))
if (entry.Request.RequestUri.AbsoluteUri.Contains("healthz"))
{
await SendResponseWithSecrets(new Dictionary<string, string>()
{
["secretName--value"] = "secret",
}, entry);
await SendEmptyResponse(entry);
}
else if (content.Contains("otherSecretName≡value"))
else
{
await SendResponseWithSecrets(new Dictionary<string, string>()
var content = await entry.Request.Content.ReadAsStringAsync();
if (content.Contains("secretName--value"))
{
["otherSecretName≡value"] = "secret",
}, entry);
}
await SendResponseWithSecrets(new Dictionary<string, string>()
{
["secretName--value"] = "secret",
}, entry);
}
else if (content.Contains("otherSecretName≡value"))
{
await SendResponseWithSecrets(new Dictionary<string, string>()
{
["otherSecretName≡value"] = "secret",
}, entry);
}
}
}
};
var daprClient = new DaprClientBuilder()
.UseHttpClientFactory(() => httpClient)
.UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient })
.Build();
@ -588,6 +609,7 @@ namespace Dapr.Extensions.Configuration.Test
};
var daprClient = new DaprClientBuilder()
.UseHttpClientFactory(() => httpClient)
.UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient })
.Build();
@ -616,6 +638,7 @@ namespace Dapr.Extensions.Configuration.Test
};
var daprClient = new DaprClientBuilder()
.UseHttpClientFactory(() => httpClient)
.UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient })
.Build();
@ -646,6 +669,7 @@ namespace Dapr.Extensions.Configuration.Test
};
var daprClient = new DaprClientBuilder()
.UseHttpClientFactory(() => httpClient)
.UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient })
.Build();
@ -675,6 +699,7 @@ namespace Dapr.Extensions.Configuration.Test
};
var daprClient = new DaprClientBuilder()
.UseHttpClientFactory(() => httpClient)
.UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient })
.Build();
@ -709,6 +734,7 @@ namespace Dapr.Extensions.Configuration.Test
};
var daprClient = new DaprClientBuilder()
.UseHttpClientFactory(() => httpClient)
.UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient })
.Build();
@ -725,6 +751,27 @@ namespace Dapr.Extensions.Configuration.Test
config["first_secret--value"].Should().Be("secret1");
}
[Fact]
public void LoadSecrets_FailsIfSidecarNotAvailable()
{
var httpClient = new TestHttpClient()
{
Handler = async (entry) =>
{
await SendEmptyResponse(entry, HttpStatusCode.InternalServerError);
}
};
var daprClient = new DaprClientBuilder()
.UseHttpClientFactory(() => httpClient)
.UseGrpcChannelOptions(new GrpcChannelOptions { HttpClient = httpClient })
.Build();
Assert.Throws<TaskCanceledException>(() => CreateBuilder()
.AddDaprSecretStore("store", daprClient, TimeSpan.FromMilliseconds(1))
.Build());
}
private IConfigurationBuilder CreateBuilder()
{
return new ConfigurationBuilder();
@ -755,5 +802,12 @@ namespace Dapr.Extensions.Configuration.Test
var response = GrpcUtils.CreateResponse(HttpStatusCode.OK, streamContent);
entry.Completion.SetResult(response);
}
private async Task SendEmptyResponse(TestHttpClient.Entry entry, HttpStatusCode code = HttpStatusCode.OK)
{
var response = new Autogenerated.GetSecretResponse();
var streamContent = await GrpcUtils.CreateResponseContent(response);
entry.Completion.SetResult(GrpcUtils.CreateResponse(code, streamContent));
}
}
}