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
7 changes: 7 additions & 0 deletions all.sln
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapr.Testcontainers", "src\
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapr.Testcontainers.Test", "test\Dapr.Testcontainers.Test\Dapr.Testcontainers.Test.csproj", "{5A93F96B-4D0E-479D-B540-29678A0998FA}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapr.IntegrationTest.DistributedLock", "test\Dapr.IntegrationTest.DistributedLock\Dapr.IntegrationTest.DistributedLock.csproj", "{E958E875-8DDE-4B25-BE3A-C0760EC89376}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -591,6 +593,10 @@ Global
{5A93F96B-4D0E-479D-B540-29678A0998FA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5A93F96B-4D0E-479D-B540-29678A0998FA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5A93F96B-4D0E-479D-B540-29678A0998FA}.Release|Any CPU.Build.0 = Release|Any CPU
{E958E875-8DDE-4B25-BE3A-C0760EC89376}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E958E875-8DDE-4B25-BE3A-C0760EC89376}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E958E875-8DDE-4B25-BE3A-C0760EC89376}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E958E875-8DDE-4B25-BE3A-C0760EC89376}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -700,6 +706,7 @@ Global
{CE5D4439-5B3C-4E97-B7E3-EB8610AEA3EF} = {27C5D71D-0721-4221-9286-B94AB07B58CF}
{A05D1519-6A82-498F-B7C9-3D14E08D35CA} = {27C5D71D-0721-4221-9286-B94AB07B58CF}
{5A93F96B-4D0E-479D-B540-29678A0998FA} = {0AF0FE8D-C234-4F04-8514-32206ACE01BD}
{E958E875-8DDE-4B25-BE3A-C0760EC89376} = {8462B106-175A-423A-BA94-BE0D39D0BD8E}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {65220BF2-EAE1-4CB2-AA58-EBE80768CB40}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,25 +26,20 @@ public sealed class LocalStorageCryptographyContainer
public static class Yaml
{
/// <summary>
/// Writes a
/// Writes the component yaml.
/// </summary>
/// <param name="folderPath"></param>
/// <param name="keyPath"></param>
/// <param name="fileName"></param>
/// <returns></returns>
public static string WriteCryptoYamlToFolder(string folderPath, string keyPath, string fileName = "local-crypto.yaml")
public static void WriteCryptoYamlToFolder(string folderPath, string keyPath, string fileName = "local-crypto.yaml")
{
var yaml = GetLocalStorageYaml(keyPath);
return WriteToFolder(folderPath, fileName, yaml);
}
WriteToFolder(folderPath, fileName, yaml);
}

private static string WriteToFolder(string folderPath, string fileName, string yaml)
private static void WriteToFolder(string folderPath, string fileName, string yaml)
{
Directory.CreateDirectory(folderPath);
var fullPath = Path.Combine(folderPath, fileName);
File.WriteAllText(fullPath, yaml);
return fullPath;
}
}

private static string GetLocalStorageYaml(string keyPath) =>
$@"apiVersion: dapr.io/v1alpha
Expand Down
13 changes: 6 additions & 7 deletions src/Dapr.Testcontainers/Containers/OllamaContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ namespace Dapr.Testcontainers.Containers;
public sealed class OllamaContainer : IAsyncStartable
{
private const int InternalPort = 11434;
private string _containerName = $"ollama-{Guid.NewGuid():N}";
private readonly string _containerName = $"ollama-{Guid.NewGuid():N}";

private readonly IContainer _container;

Expand Down Expand Up @@ -83,19 +83,18 @@ public static class Yaml
/// <summary>
/// Writes the component YAML.
/// </summary>
public static string WriteConversationYamlToFolder(string folderPath, string fileName = "ollama-conversation.yaml", string model = "smollm:135m", string cacheTtl = "10m", string endpoint = "http://localhost:11434/v1")
public static void WriteConversationYamlToFolder(string folderPath, string fileName = "ollama-conversation.yaml", string model = "smollm:135m", string cacheTtl = "10m", string endpoint = "http://localhost:11434/v1")
{
var yaml = GetConversationYaml(model, cacheTtl, endpoint);
return WriteToFolder(folderPath, fileName, yaml);
}
WriteToFolder(folderPath, fileName, yaml);
}

private static string WriteToFolder(string folderPath, string fileName, string yaml)
private static void WriteToFolder(string folderPath, string fileName, string yaml)
{
Directory.CreateDirectory(folderPath);
var fullPath = Path.Combine(folderPath, fileName);
File.WriteAllText(fullPath, yaml);
return fullPath;
}
}

private static string GetConversationYaml(string model, string cacheTtl, string endpoint) =>
$@"apiVersion: dapr.io/v1alpha
Expand Down
9 changes: 4 additions & 5 deletions src/Dapr.Testcontainers/Containers/RabbitMqContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,18 +85,17 @@ public static class Yaml
/// <summary>
/// Writes a PubSub YAML component.
/// </summary>
public static string WritePubSubYamlToFolder(string folderPath, string fileName = "rabbitmq-pubsub.yaml", string rabbitmqHost = "localhost:5672")
public static void WritePubSubYamlToFolder(string folderPath, string fileName = "rabbitmq-pubsub.yaml", string rabbitmqHost = "localhost:5672")
{
var yaml = GetPubSubYaml(rabbitmqHost);
return WriteToFolder(folderPath, fileName, yaml);
}
WriteToFolder(folderPath, fileName, yaml);
}

private static string WriteToFolder(string folderPath, string fileName, string yaml)
private static void WriteToFolder(string folderPath, string fileName, string yaml)
{
Directory.CreateDirectory(folderPath);
var fullPath = Path.Combine(folderPath, fileName);
File.WriteAllText(fullPath, yaml);
return fullPath;
}

private static string GetPubSubYaml(string rabbitmqHost) =>
Expand Down
15 changes: 7 additions & 8 deletions src/Dapr.Testcontainers/Containers/RedisContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,29 +82,28 @@ public static class Yaml
/// <summary>
/// Writes a state store YAML component.
/// </summary>
public static string WriteStateStoreYamlToFolder(string folderPath, string fileName = "redis-state.yaml",string redisHost = "localhost:6379",
public static void WriteStateStoreYamlToFolder(string folderPath, string fileName = "redis-state.yaml",string redisHost = "localhost:6379",
string? passwordSecretName = null)
{
var yaml = GetRedisStateStoreYaml(redisHost, passwordSecretName);
return WriteToFolder(folderPath, fileName, yaml);
}
WriteToFolder(folderPath, fileName, yaml);
}

/// <summary>
/// Writes a distributed lock YAML component.
/// </summary>
public static string WriteDistributedLockYamlToFolder(string folderPath, string fileName = "redis-lock.yaml",
public static void WriteDistributedLockYamlToFolder(string folderPath, string fileName = "redis-lock.yaml",
string redisHost = "localhost:6379", string? passwordSecretName = null)
{
var yaml = GetDistributedLockYaml(redisHost, passwordSecretName);
return WriteToFolder(folderPath, fileName, yaml);
}
WriteToFolder(folderPath, fileName, yaml);
}

private static string WriteToFolder(string folderPath, string fileName, string yaml)
private static void WriteToFolder(string folderPath, string fileName, string yaml)
{
Directory.CreateDirectory(folderPath);
var fullPath = Path.Combine(folderPath, fileName);
File.WriteAllText(fullPath, yaml);
return fullPath;
}

private static string BuildSecretBlock(string? passwordSecretName) =>
Expand Down
5 changes: 5 additions & 0 deletions src/Dapr.Testcontainers/Harnesses/DistributedLockHarness.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ public sealed class DistributedLockHarness : BaseHarness
private readonly RedisContainer _redis;
private readonly string componentsDir;

/// <summary>
/// The name of the producted distributed lock component.
/// </summary>
public const string DistributedLockComponentName = Constants.DaprComponentNames.DistributedLockComponentName;

/// <summary>
/// Provides an implementation harness for Dapr's distributed lock building block.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="coverlet.collector" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="xunit" />
<PackageReference Include="xunit.extensibility.core" />
<PackageReference Include="xunit.runner.visualstudio" />
</ItemGroup>

<ItemGroup>
<Using Include="Xunit"/>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\Dapr.DistributedLock\Dapr.DistributedLock.csproj" />
<ProjectReference Include="..\..\src\Dapr.Testcontainers\Dapr.Testcontainers.csproj" />
</ItemGroup>

</Project>
171 changes: 171 additions & 0 deletions test/Dapr.IntegrationTest.DistributedLock/DistributedLockTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
using Dapr.DistributedLock;
using Dapr.DistributedLock.Extensions;
using Dapr.DistributedLock.Models;
using Dapr.Testcontainers.Common;
using Dapr.Testcontainers.Common.Options;
using Dapr.Testcontainers.Harnesses;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

namespace Dapr.IntegrationTest.DistributedLock;

public sealed class DistributedLockTests
{
[Fact]
public async Task ShouldAcquireAndReleaseLock()
{
var options = new DaprRuntimeOptions();
var componentsDir = TestDirectoryManager.CreateTestDirectory("distributedlock-components");
var resourceId = $"resource-{Guid.NewGuid():N}";
var owner = $"owner-{Guid.NewGuid():N}";

await using var environment = await DaprTestEnvironment.CreateWithPooledNetworkAsync();
await environment.StartAsync();

var harness = new DaprHarnessBuilder(options, environment).BuildDistributedLock(componentsDir);
await using var testApp = await DaprHarnessBuilder.ForHarness(harness)
.ConfigureServices(builder =>
{
builder.Services.AddDaprDistributedLock((sp, clientBuilder) =>
{
var config = sp.GetRequiredService<IConfiguration>();
var grpcEndpoint = config["DAPR_GRPC_ENDPOINT"];
if (!string.IsNullOrEmpty(grpcEndpoint))
clientBuilder.UseGrpcEndpoint(grpcEndpoint);
});
})
.BuildAndStartAsync();

const string componentName = DistributedLockHarness.DistributedLockComponentName;
Assert.NotNull(componentName);

using var scope = testApp.CreateScope();
var client = scope.ServiceProvider.GetRequiredService<DaprDistributedLockClient>();

var acquired = await client.TryLockAsync(componentName, resourceId, owner, expiryInSeconds: 10);
Assert.NotNull(acquired);

var unlock = await client.TryUnlockAsync(componentName, resourceId, owner);
Assert.Equal(LockStatus.Success, unlock.Status);
}

[Fact]
public async Task ShouldEnforceExclusivityAndReturnExpectedUnlockStatuses()
{
var options = new DaprRuntimeOptions();
var componentsDir = TestDirectoryManager.CreateTestDirectory("distributedlock-components");
var resourceId = $"resource-{Guid.NewGuid():N}";
var owner1 = $"owner-{Guid.NewGuid():N}";
var owner2 = $"owner-{Guid.NewGuid():N}";

await using var environment = await DaprTestEnvironment.CreateWithPooledNetworkAsync();
await environment.StartAsync();

var harness = new DaprHarnessBuilder(options, environment).BuildDistributedLock(componentsDir);
await using var testApp = await DaprHarnessBuilder.ForHarness(harness)
.ConfigureServices(builder =>
{
builder.Services.AddDaprDistributedLock((sp, clientBuilder) =>
{
var config = sp.GetRequiredService<IConfiguration>();
var grpcEndpoint = config["DAPR_GRPC_ENDPOINT"];
if (!string.IsNullOrEmpty(grpcEndpoint))
clientBuilder.UseGrpcEndpoint(grpcEndpoint);
});
})
.BuildAndStartAsync();

const string componentName = DistributedLockHarness.DistributedLockComponentName;
Assert.NotNull(componentName);

using var scope = testApp.CreateScope();
var client = scope.ServiceProvider.GetRequiredService<DaprDistributedLockClient>();

var lock1 = await client.TryLockAsync(componentName, resourceId, owner1, expiryInSeconds: 20);
Assert.NotNull(lock1);

// While owner1 holds the lock, owner2 should not be able to acquire it.
var lock2 = await client.TryLockAsync(componentName, resourceId, owner2, expiryInSeconds: 20);
Assert.Null(lock2);

// Wrong owner tries to unlock -> should indicate ownership mismatch.
var wrongUnlock = await client.TryUnlockAsync(componentName, resourceId, owner2);
Assert.Equal(LockStatus.LockBelongsToOthers, wrongUnlock.Status);

// Correct owner unlocks -> success.
var correctUnlock = await client.TryUnlockAsync(componentName, resourceId, owner1);
Assert.Equal(LockStatus.Success, correctUnlock.Status);

// Unlocking again after release -> lock does not exist.
var secondUnlock = await client.TryUnlockAsync(componentName, resourceId, owner1);
Assert.Equal(LockStatus.LockDoesNotExist, secondUnlock.Status);
}

[Fact]
public async Task ShouldAllowAcquireAfterExpiry()
{
var options = new DaprRuntimeOptions();
var componentsDir = TestDirectoryManager.CreateTestDirectory("distributedlock-components");
var resourceId = $"resource-{Guid.NewGuid():N}";
var owner1 = $"owner-{Guid.NewGuid():N}";
var owner2 = $"owner-{Guid.NewGuid():N}";

await using var environment = await DaprTestEnvironment.CreateWithPooledNetworkAsync();
await environment.StartAsync();

var harness = new DaprHarnessBuilder(options, environment).BuildDistributedLock(componentsDir);
await using var testApp = await DaprHarnessBuilder.ForHarness(harness)
.ConfigureServices(builder =>
{
builder.Services.AddDaprDistributedLock((sp, clientBuilder) =>
{
var config = sp.GetRequiredService<IConfiguration>();
var grpcEndpoint = config["DAPR_GRPC_ENDPOINT"];
if (!string.IsNullOrEmpty(grpcEndpoint))
clientBuilder.UseGrpcEndpoint(grpcEndpoint);
});
})
.BuildAndStartAsync();

const string componentName = DistributedLockHarness.DistributedLockComponentName;
Assert.NotNull(componentName);

using var scope = testApp.CreateScope();
var client = scope.ServiceProvider.GetRequiredService<DaprDistributedLockClient>();

// Acquire a short-lived lock and *do not* unlock it.
var first = await client.TryLockAsync(componentName, resourceId, owner1, expiryInSeconds: 2);
Assert.NotNull(first);

// Poll until the lock becomes available and owner2 can acquire it.
var acquiredByOwner2 = await WaitUntilAsync(
async () => await client.TryLockAsync(componentName, resourceId, owner2, expiryInSeconds: 10),
isSuccess: lr => lr is not null,
timeout: TimeSpan.FromSeconds(30),
pollInterval: TimeSpan.FromMilliseconds(250));

Assert.NotNull(acquiredByOwner2);

var unlock2 = await client.TryUnlockAsync(componentName, resourceId, owner2);
Assert.Equal(LockStatus.Success, unlock2.Status);
}

private static async Task<T?> WaitUntilAsync<T>(
Func<Task<T?>> action,
Func<T?, bool> isSuccess,
TimeSpan timeout,
TimeSpan pollInterval)
{
using var cts = new CancellationTokenSource(timeout);
while (!cts.IsCancellationRequested)
{
var result = await action();
if (isSuccess(result))
return result;

await Task.Delay(pollInterval, cts.Token);
}

throw new TimeoutException($"Condition was not met within {timeout}.");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<RootNamespace>Dapr.E2E.Test.Jobs</RootNamespace>
</PropertyGroup>

<ItemGroup>
Expand Down
2 changes: 1 addition & 1 deletion test/Dapr.IntegrationTest.Jobs/JobFailurePolicyTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

namespace Dapr.E2E.Test.Jobs;
namespace Dapr.IntegrationTest.Jobs;

public sealed class JobFailurePolicyTests
{
Expand Down
2 changes: 1 addition & 1 deletion test/Dapr.IntegrationTest.Jobs/JobManagementTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

namespace Dapr.E2E.Test.Jobs;
namespace Dapr.IntegrationTest.Jobs;

public sealed class JobManagementTests
{
Expand Down
Loading