diff --git a/.gitignore b/.gitignore index 8afdcb6..ae8a90c 100644 --- a/.gitignore +++ b/.gitignore @@ -452,3 +452,4 @@ $RECYCLE.BIN/ !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json +src/Scaler.Demo/OrderProcessor/appsettings.Development.json diff --git a/.vscode/launch.json b/.vscode/launch.json index 1da3396..41d4b7c 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -30,6 +30,31 @@ "name": ".NET Core Attach", "type": "coreclr", "request": "attach" + }, + { + // Use IntelliSense to find out which attributes exist for C# debugging + // Use hover for the description of the existing attributes + // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md + "name": "Order Processor", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + // If you have changed target frameworks, make sure to update the program path. + "program": "${workspaceFolder}/src/Scaler.Demo/OrderProcessor/bin/Debug/net6.0/Keda.CosmosDb.Scaler.Demo.OrderProcessor.dll", + "args": [], + "cwd": "${workspaceFolder}/src/Scaler.Demo/OrderProcessor", + "stopAtEntry": false, + // Enable launching a web browser when ASP.NET Core starts. For more information: https://aka.ms/VSCode-CS-LaunchJson-WebBrowser + "serverReadyAction": { + "action": "openExternally", + "pattern": "\\bNow listening on:\\s+(https?://\\S+)" + }, + "env": { + "ASPNETCORE_ENVIRONMENT": "Development", + }, + "sourceFileMap": { + "/Views": "${workspaceFolder}/Views" + } } ] } \ No newline at end of file diff --git a/src/Scaler.Demo/OrderGenerator/Program.cs b/src/Scaler.Demo/OrderGenerator/Program.cs index 77bfac5..d473a7a 100644 --- a/src/Scaler.Demo/OrderGenerator/Program.cs +++ b/src/Scaler.Demo/OrderGenerator/Program.cs @@ -132,8 +132,11 @@ private static async Task CreateOrderAsync(Container container, string article) private static async Task SetupAsync() { Console.WriteLine($"Creating database: {_cosmosDbConfig.DatabaseId}"); + using var cosmosClient = _cosmosDbConfig.Connection.Contains("AccountKey") + ? new CosmosClient(_cosmosDbConfig.Connection, new CosmosClientOptions { ConnectionMode = ConnectionMode.Gateway }) + : new CosmosClient(_cosmosDbConfig.Connection, new DefaultAzureCredential(), new CosmosClientOptions { ConnectionMode = ConnectionMode.Direct }); - Database database = await new CosmosClient(_cosmosDbConfig.Connection) + Database database = await cosmosClient .CreateDatabaseIfNotExistsAsync(_cosmosDbConfig.DatabaseId); Console.WriteLine($"Creating container: {_cosmosDbConfig.ContainerId} with throughput: {_cosmosDbConfig.ContainerThroughput} RU/s"); @@ -147,12 +150,14 @@ await database.CreateContainerIfNotExistsAsync( private static async Task TeardownAsync() { - var client = new CosmosClient(_cosmosDbConfig.Connection); + using var cosmosClient = _cosmosDbConfig.Connection.Contains("AccountKey") + ? new CosmosClient(_cosmosDbConfig.Connection, new CosmosClientOptions { ConnectionMode = ConnectionMode.Gateway }) + : new CosmosClient(_cosmosDbConfig.Connection, new DefaultAzureCredential(), new CosmosClientOptions { ConnectionMode = ConnectionMode.Direct }); try { Console.WriteLine($"Deleting database: {_cosmosDbConfig.DatabaseId}"); - await client.GetDatabase(_cosmosDbConfig.DatabaseId).DeleteAsync(); + await cosmosClient.GetDatabase(_cosmosDbConfig.DatabaseId).DeleteAsync(); } catch (CosmosException) { diff --git a/src/Scaler.Demo/OrderProcessor/Worker.cs b/src/Scaler.Demo/OrderProcessor/Worker.cs index eb2844a..17eec0c 100644 --- a/src/Scaler.Demo/OrderProcessor/Worker.cs +++ b/src/Scaler.Demo/OrderProcessor/Worker.cs @@ -30,7 +30,7 @@ public override async Task StartAsync(CancellationToken cancellationToken) ? new CosmosClient(_cosmosDbConfig.Connection) : new CosmosClient(_cosmosDbConfig.Connection, new DefaultAzureCredential()); - Database leaseDatabase = await new CosmosClient(_cosmosDbConfig.LeaseConnection) + Database leaseDatabase = await cosmosClient .CreateDatabaseIfNotExistsAsync(_cosmosDbConfig.LeaseDatabaseId, cancellationToken: cancellationToken); Container leaseContainer = await leaseDatabase @@ -42,7 +42,7 @@ public override async Task StartAsync(CancellationToken cancellationToken) // Change feed processor instance name should be unique for each container application. string instanceName = $"Instance-{Dns.GetHostName()}"; - _processor = new CosmosClient(_cosmosDbConfig.Connection) + _processor = cosmosClient .GetContainer(_cosmosDbConfig.DatabaseId, _cosmosDbConfig.ContainerId) .GetChangeFeedProcessorBuilder(_cosmosDbConfig.ProcessorName, ProcessOrdersAsync) .WithInstanceName(instanceName) diff --git a/src/Scaler.Tests/CosmosDbScalerServiceTests.cs b/src/Scaler.Tests/CosmosDbScalerServiceTests.cs index 2156b0e..58ade43 100644 --- a/src/Scaler.Tests/CosmosDbScalerServiceTests.cs +++ b/src/Scaler.Tests/CosmosDbScalerServiceTests.cs @@ -1,6 +1,7 @@ using System; using System.Threading.Tasks; using Google.Protobuf.Collections; +using Microsoft.Extensions.Logging; using Moq; using Newtonsoft.Json; using Xunit; @@ -15,7 +16,8 @@ public class CosmosDbScalerServiceTests public CosmosDbScalerServiceTests() { _metricProviderMock = new Mock(); - _cosmosDbScalerService = new CosmosDbScalerService(_metricProviderMock.Object); + var _loggerMock = new Mock>(); + _cosmosDbScalerService = new CosmosDbScalerService(_metricProviderMock.Object, _loggerMock.Object); } [Theory] diff --git a/src/Scaler/Services/CosmosDbMetricProvider.cs b/src/Scaler/Services/CosmosDbMetricProvider.cs index 8ca242b..355110a 100644 --- a/src/Scaler/Services/CosmosDbMetricProvider.cs +++ b/src/Scaler/Services/CosmosDbMetricProvider.cs @@ -41,10 +41,18 @@ public async Task GetPartitionCountAsync(ScalerMetadata scalerMetadata) while (iterator.HasMoreResults) { FeedResponse states = await iterator.ReadNextAsync(); - partitionCount += states.Where(state => state.EstimatedLag > 0).Count(); + + foreach (ChangeFeedProcessorState leaseState in states) + { + string host = leaseState.InstanceName == null ? $"not owned by any host currently" : $"owned by host {leaseState.InstanceName}"; + _logger.LogInformation("Lease [{LeaseToken}] {host} reports {EstimatedLag} as estimated lag.", leaseState.LeaseToken, host, leaseState.EstimatedLag); + + partitionCount += leaseState.EstimatedLag > 0 ? 1 : 0; + } } } + _logger.LogInformation("Returning active {partitionCount}", partitionCount); return partitionCount; } catch (CosmosException exception) diff --git a/src/Scaler/Services/CosmosDbScalerService.cs b/src/Scaler/Services/CosmosDbScalerService.cs index 0ce6c69..dc7950c 100644 --- a/src/Scaler/Services/CosmosDbScalerService.cs +++ b/src/Scaler/Services/CosmosDbScalerService.cs @@ -8,10 +8,12 @@ namespace Keda.CosmosDb.Scaler internal sealed class CosmosDbScalerService : ExternalScaler.ExternalScalerBase { private readonly ICosmosDbMetricProvider _metricProvider; + private readonly ILogger _logger; - public CosmosDbScalerService(ICosmosDbMetricProvider metricProvider) + public CosmosDbScalerService(ICosmosDbMetricProvider metricProvider, ILogger logger) { _metricProvider = metricProvider ?? throw new ArgumentNullException(nameof(metricProvider)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } public override async Task IsActive(ScaledObjectRef request, ServerCallContext context) @@ -19,6 +21,8 @@ public override async Task IsActive(ScaledObjectRef request, S var scalerMetadata = ScalerMetadata.Create(request); bool isActive = (await _metricProvider.GetPartitionCountAsync(scalerMetadata)) > 0L; + + _logger.LogInformation("Scaler is {status}", isActive ? "active" : "inactive"); return new IsActiveResponse { Result = isActive }; } @@ -34,6 +38,7 @@ public override async Task GetMetrics(GetMetricsRequest requ MetricValue_ = await _metricProvider.GetPartitionCountAsync(scalerMetadata), }); + _logger.LogInformation("Returning metric value {value} for metric {metric}", response.MetricValues[0].MetricValue_, response.MetricValues[0].MetricName); return response; } @@ -49,6 +54,7 @@ public override Task GetMetricSpec(ScaledObjectRef reques TargetSize = 1L, }); + _logger.LogInformation("Returning target size {size} for metric {metric}", response.MetricSpecs[0].TargetSize, response.MetricSpecs[0].MetricName); return Task.FromResult(response); } }