Skip to content

Commit e08c0c2

Browse files
yash2710Copilot
andauthored
Add azure-cosmosdb e2e test image (#257)
* feat: add azure-cosmosdb e2e test image Adds ghcr.io/kedacore/tests-azure-cosmosdb, referenced by the KEDA azure-cosmosdb scaler e2e test (kedacore/keda#7557) but never previously built or published. Combines the two roles previously covered by ad-hoc, uncommitted local scripts into a single .NET worker image, selected via RUN_MODE: - RUN_MODE=processor (default): runs a real Cosmos DB ChangeFeedProcessorBuilder against the monitored container, producing authentic .NET SDK lease documents in the lease container. This is what the KEDA scaler's e2e test needs bootstrapped before it can measure change feed lag - the scaler only reads existing lease/change feed state via REST, it never creates leases itself. - RUN_MODE=generate: non-interactively inserts documents into the monitored container, to produce change feed backlog for testing. Configuration is read from either the CosmosDbConfig__* (.NET config binding) or COSMOS_* environment variables, matching both conventions already set by the KEDA e2e test's deployment template. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Yash Trivedi <yash2710@users.noreply.github.com> * fix: remove unused item generator, keep processor-only image The KEDA azure-cosmosdb e2e test only ever needs the change feed processor role (RUN_MODE=generate was unused by any consumer), so drop ItemGeneratorWorker.cs and the RUN_MODE switch in Program.cs - the image now always runs ChangeFeedProcessorWorker. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Yash Trivedi <yash2710@users.noreply.github.com> --------- Signed-off-by: Yash Trivedi <yash2710@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 636b4bb commit e08c0c2

5 files changed

Lines changed: 199 additions & 0 deletions

File tree

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
using Microsoft.Azure.Cosmos;
2+
3+
namespace CosmosDbTestTool
4+
{
5+
/// <summary>
6+
/// Runs a real Cosmos DB change feed processor against the monitored container, creating
7+
/// authentic .NET SDK lease documents in the lease container. This is what the KEDA
8+
/// azure-cosmosdb scaler e2e test needs bootstrapped before it can measure change feed lag -
9+
/// the scaler only reads lease/change-feed state via REST, it never creates leases itself.
10+
/// </summary>
11+
public class ChangeFeedProcessorWorker : BackgroundService
12+
{
13+
private readonly ILogger<ChangeFeedProcessorWorker> _logger;
14+
private readonly CosmosDbOptions _options;
15+
private CosmosClient? _client;
16+
private CosmosClient? _leaseClient;
17+
private ChangeFeedProcessor? _processor;
18+
19+
public ChangeFeedProcessorWorker(ILogger<ChangeFeedProcessorWorker> logger, IConfiguration configuration)
20+
{
21+
_logger = logger;
22+
_options = CosmosDbOptions.FromEnvironment(configuration);
23+
}
24+
25+
public override async Task StartAsync(CancellationToken cancellationToken)
26+
{
27+
var clientOptions = new CosmosClientOptions { ConnectionMode = ConnectionMode.Gateway };
28+
_client = new CosmosClient(_options.Connection, clientOptions);
29+
_leaseClient = _options.LeaseConnection == _options.Connection
30+
? _client
31+
: new CosmosClient(_options.LeaseConnection, clientOptions);
32+
33+
Database database = await _client.CreateDatabaseIfNotExistsAsync(_options.DatabaseId);
34+
Container monitoredContainer = await database.CreateContainerIfNotExistsAsync(_options.ContainerId, "/id");
35+
36+
Database leaseDatabase = _options.LeaseDatabaseId == _options.DatabaseId
37+
? database
38+
: await _leaseClient.CreateDatabaseIfNotExistsAsync(_options.LeaseDatabaseId);
39+
Container leaseContainer = await leaseDatabase.CreateContainerIfNotExistsAsync(_options.LeaseContainerId, "/id");
40+
41+
_processor = monitoredContainer
42+
.GetChangeFeedProcessorBuilder<dynamic>(_options.ProcessorName, HandleChangesAsync)
43+
.WithInstanceName(Environment.MachineName)
44+
.WithLeaseContainer(leaseContainer)
45+
.WithErrorNotification(HandleErrorAsync)
46+
.Build();
47+
48+
await _processor.StartAsync();
49+
_logger.LogInformation(
50+
"Change feed processor '{ProcessorName}' started on {Database}/{Container}, leases in {LeaseDatabase}/{LeaseContainer}",
51+
_options.ProcessorName, _options.DatabaseId, _options.ContainerId, _options.LeaseDatabaseId, _options.LeaseContainerId);
52+
53+
await base.StartAsync(cancellationToken);
54+
}
55+
56+
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
57+
{
58+
// All the work happens in the SDK's own change feed pump; just wait for shutdown.
59+
try
60+
{
61+
await Task.Delay(Timeout.Infinite, stoppingToken);
62+
}
63+
catch (TaskCanceledException)
64+
{
65+
// expected on shutdown
66+
}
67+
}
68+
69+
public override async Task StopAsync(CancellationToken cancellationToken)
70+
{
71+
if (_processor != null)
72+
{
73+
await _processor.StopAsync();
74+
}
75+
await base.StopAsync(cancellationToken);
76+
}
77+
78+
private Task HandleChangesAsync(IReadOnlyCollection<dynamic> changes, CancellationToken cancellationToken)
79+
{
80+
_logger.LogInformation("Processed {Count} change(s) from the change feed", changes.Count);
81+
return Task.CompletedTask;
82+
}
83+
84+
private Task HandleErrorAsync(string leaseToken, Exception exception)
85+
{
86+
_logger.LogError(exception, "Unhandled exception on lease {LeaseToken}", leaseToken);
87+
return Task.CompletedTask;
88+
}
89+
90+
public override void Dispose()
91+
{
92+
_client?.Dispose();
93+
if (_leaseClient != _client)
94+
{
95+
_leaseClient?.Dispose();
96+
}
97+
base.Dispose();
98+
}
99+
}
100+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
namespace CosmosDbTestTool
2+
{
3+
/// <summary>
4+
/// Resolves Cosmos DB configuration from either the ".NET style" double-underscore
5+
/// environment variables (CosmosDbConfig__X, bound automatically by IConfiguration) or
6+
/// the plain upper-snake-case variables (COSMOS_X). Both conventions are set by the KEDA
7+
/// azure_cosmosdb e2e test's deployment template, so either one alone is enough to run.
8+
/// </summary>
9+
public class CosmosDbOptions
10+
{
11+
public string Connection { get; private set; } = string.Empty;
12+
public string LeaseConnection { get; private set; } = string.Empty;
13+
public string DatabaseId { get; private set; } = string.Empty;
14+
public string ContainerId { get; private set; } = string.Empty;
15+
public string LeaseDatabaseId { get; private set; } = string.Empty;
16+
public string LeaseContainerId { get; private set; } = string.Empty;
17+
public string ProcessorName { get; private set; } = string.Empty;
18+
19+
public static CosmosDbOptions FromEnvironment(IConfiguration configuration)
20+
{
21+
string Require(string configKey, string legacyEnvVar)
22+
{
23+
string? value = configuration[$"CosmosDbConfig:{configKey}"];
24+
if (string.IsNullOrEmpty(value))
25+
{
26+
value = Environment.GetEnvironmentVariable(legacyEnvVar);
27+
}
28+
if (string.IsNullOrEmpty(value))
29+
{
30+
throw new InvalidOperationException(
31+
$"Missing required configuration. Set either 'CosmosDbConfig__{configKey}' or '{legacyEnvVar}'.");
32+
}
33+
return value;
34+
}
35+
36+
var options = new CosmosDbOptions
37+
{
38+
Connection = Require("Connection", "COSMOS_CONNECTION"),
39+
DatabaseId = Require("DatabaseId", "COSMOS_DATABASE_ID"),
40+
ContainerId = Require("ContainerId", "COSMOS_CONTAINER_ID"),
41+
LeaseDatabaseId = Require("LeaseDatabaseId", "COSMOS_LEASE_DATABASE_ID"),
42+
LeaseContainerId = Require("LeaseContainerId", "COSMOS_LEASE_CONTAINER_ID"),
43+
ProcessorName = Require("ProcessorName", "COSMOS_PROCESSOR_NAME"),
44+
};
45+
46+
// LeaseConnection defaults to the primary Connection when not set separately,
47+
// matching the KEDA azure-cosmosdb scaler's own metadata defaulting behavior.
48+
string? leaseConnection = configuration["CosmosDbConfig:LeaseConnection"];
49+
options.LeaseConnection = string.IsNullOrEmpty(leaseConnection) ? options.Connection : leaseConnection;
50+
51+
return options;
52+
}
53+
}
54+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<Project Sdk="Microsoft.NET.Sdk.Worker">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net8.0</TargetFramework>
5+
<Nullable>enable</Nullable>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.1" />
12+
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.19.5" />
13+
<PackageReference Include="Microsoft.Azure.Cosmos" Version="3.57.1" />
14+
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
15+
</ItemGroup>
16+
17+
</Project>
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
FROM mcr.microsoft.com/dotnet/runtime:8.0 AS base
2+
WORKDIR /app
3+
4+
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
5+
WORKDIR /src
6+
COPY ["CosmosDbTestTool.csproj", "."]
7+
RUN dotnet restore "CosmosDbTestTool.csproj"
8+
COPY . .
9+
WORKDIR "/src"
10+
RUN dotnet build "CosmosDbTestTool.csproj" -c Release -o /app/build
11+
12+
FROM build AS publish
13+
RUN dotnet publish "CosmosDbTestTool.csproj" -c Release -o /app/publish /p:UseAppHost=false
14+
15+
FROM base AS final
16+
WORKDIR /app
17+
COPY --from=publish /app/publish .
18+
ENTRYPOINT ["dotnet", "CosmosDbTestTool.dll"]
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using CosmosDbTestTool;
2+
3+
IHost host = Host.CreateDefaultBuilder(args)
4+
.ConfigureServices(services =>
5+
{
6+
services.AddHostedService<ChangeFeedProcessorWorker>();
7+
})
8+
.Build();
9+
10+
await host.RunAsync();

0 commit comments

Comments
 (0)