From 5562534aa4bd8b39b1bd19b97c24bda4c1f942f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Mrzyg=C5=82=C3=B3d?= Date: Tue, 9 Apr 2024 14:31:29 +0200 Subject: [PATCH] fix(estimation): :adhesive_bandage: Fixed calculation of Burstable SKU in PostgreSQL --- .gitignore | 4 +- .../KeyVault/KeyVaultUsagePatternsTests.cs | 3 - .../Reworked/PostgreSQL/PostgreSQLTests.cs | 34 +++++ ace-tests/Usings.cs | 4 +- ace-tests/azure-cost-estimator-tests.csproj | 1 + .../retail-api/postgresql/burstable.json | 142 ++++++++++++++++++ .../reworked/postgresql/fix-261.bicep | 20 +++ .../PostgreSQLFlexibleQueryFilter.cs | 28 ++-- 8 files changed, 217 insertions(+), 19 deletions(-) create mode 100644 ace-tests/Reworked/PostgreSQL/PostgreSQLTests.cs create mode 100644 ace-tests/mocked-responses/retail-api/postgresql/burstable.json create mode 100644 ace-tests/templates/reworked/postgresql/fix-261.bicep diff --git a/.gitignore b/.gitignore index da1c17a8..1303e37d 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,6 @@ **/dist/** **/.terraform **/.terraform.lock.hcl -**/tfplan \ No newline at end of file +**/tfplan +**/.mono +**.csv \ No newline at end of file diff --git a/ace-tests/Reworked/KeyVault/KeyVaultUsagePatternsTests.cs b/ace-tests/Reworked/KeyVault/KeyVaultUsagePatternsTests.cs index a2390b1c..afa8fa4c 100644 --- a/ace-tests/Reworked/KeyVault/KeyVaultUsagePatternsTests.cs +++ b/ace-tests/Reworked/KeyVault/KeyVaultUsagePatternsTests.cs @@ -1,6 +1,3 @@ -using System.Text.Json; -using ACE; - namespace ACE_Tests.Reworked.KeyVault; [Parallelizable(ParallelScope.Self)] diff --git a/ace-tests/Reworked/PostgreSQL/PostgreSQLTests.cs b/ace-tests/Reworked/PostgreSQL/PostgreSQLTests.cs new file mode 100644 index 00000000..7d530f00 --- /dev/null +++ b/ace-tests/Reworked/PostgreSQL/PostgreSQLTests.cs @@ -0,0 +1,34 @@ +namespace ACE_Tests.Reworked.PostgreSQL; + +[Parallelizable(ParallelScope.Self)] +public class PostgreSQLTests +{ + [Test] + public void PostgreSQL_Basic_WhenThereIsBurstableSKU_ItShouldNotThrowExceptionAndGiveCorrectEstimation() + { + var outputFilename = $"ace_test_{DateTime.Now.Ticks}"; + var exitCode = Program.Main([ + "templates/reworked/postgresql/fix-261.bicep", + "cf70b558-b930-45e4-9048-ebcefb926adf", + "arm-estimator-tests-rg", + "--generateJsonOutput", + "--jsonOutputFilename", + outputFilename, + "--debug", + "--mocked-retail-api-response-path", + "mocked-responses/retail-api/postgresql/burstable.json" + ]); + + Assert.That(exitCode, Is.EqualTo(0)); + + var outputFile = File.ReadAllText($"{outputFilename}.json"); + var output = JsonSerializer.Deserialize(outputFile, Shared.JsonSerializerOptions); + + Assert.That(output, Is.Not.Null); + Assert.Multiple(() => + { + Assert.That(output.TotalCost.OriginalValue, Is.EqualTo(14.990300000000001d)); + Assert.That(output.TotalResourceCount, Is.EqualTo(1)); + }); + } +} diff --git a/ace-tests/Usings.cs b/ace-tests/Usings.cs index 6b97316f..74626c21 100644 --- a/ace-tests/Usings.cs +++ b/ace-tests/Usings.cs @@ -1,2 +1,4 @@ global using NUnit.Framework; -global using ACE_Tests; \ No newline at end of file +global using ACE_Tests; +global using ACE; +global using System.Text.Json; \ No newline at end of file diff --git a/ace-tests/azure-cost-estimator-tests.csproj b/ace-tests/azure-cost-estimator-tests.csproj index 979bd419..af767336 100644 --- a/ace-tests/azure-cost-estimator-tests.csproj +++ b/ace-tests/azure-cost-estimator-tests.csproj @@ -48,6 +48,7 @@ + diff --git a/ace-tests/mocked-responses/retail-api/postgresql/burstable.json b/ace-tests/mocked-responses/retail-api/postgresql/burstable.json new file mode 100644 index 00000000..ebaa3b87 --- /dev/null +++ b/ace-tests/mocked-responses/retail-api/postgresql/burstable.json @@ -0,0 +1,142 @@ +{ + "Url": "https://prices.azure.com/api/retail/prices?currencyCode='USD'&$filter=priceType eq 'Consumption' and serviceId eq 'DZH3199QPQTD' and ((armRegionName eq 'westeurope' and skuName eq 'B1MS' and productName eq 'Azure Database for PostgreSQL Flexible Server Burstable BS Series Compute') or (armRegionName eq 'westeurope' and productName eq 'Az DB for PostgreSQL Flexible Server Storage'))", + "BillingCurrency": "USD", + "CustomerEntityId": "Default", + "CustomerEntityType": "Retail", + "Items": [ + { + "currencyCode": "USD", + "tierMinimumUnits": 0.0, + "retailPrice": 0.104, + "unitPrice": 0.104, + "armRegionName": "westeurope", + "location": "EU West", + "effectiveStartDate": "2023-10-01T00:00:00Z", + "meterId": "1c8e77ad-b61c-5291-be61-cf764b588c92", + "meterName": "PMD V2 Throughput Prov Throughput (MBps)", + "productId": "DZH318Z0DCRX", + "skuId": "DZH318Z0DCRX/00LF", + "productName": "Az DB for PostgreSQL Flexible Server Storage", + "skuName": "PMD V2 Throughput", + "serviceName": "Azure Database for PostgreSQL", + "serviceId": "DZH3199QPQTD", + "serviceFamily": "Databases", + "unitOfMeasure": "1/Month", + "type": "Consumption", + "isPrimaryMeterRegion": true, + "armSkuName": "V2 throughput" + }, + { + "currencyCode": "USD", + "tierMinimumUnits": 0.0, + "retailPrice": 0.1369, + "unitPrice": 0.1369, + "armRegionName": "westeurope", + "location": "EU West", + "effectiveStartDate": "2021-06-01T00:00:00Z", + "meterId": "36845e2a-736f-5a9d-af4e-b2dc7547a06f", + "meterName": "Storage Data Stored", + "productId": "DZH318Z0DCRX", + "skuId": "DZH318Z0DCRX/001G", + "productName": "Az DB for PostgreSQL Flexible Server Storage", + "skuName": "Storage", + "serviceName": "Azure Database for PostgreSQL", + "serviceId": "DZH3199QPQTD", + "serviceFamily": "Databases", + "unitOfMeasure": "1 GB/Month", + "type": "Consumption", + "isPrimaryMeterRegion": true, + "armSkuName": "Storage" + }, + { + "currencyCode": "USD", + "tierMinimumUnits": 0.0, + "retailPrice": 0.1369, + "unitPrice": 0.1369, + "armRegionName": "westeurope", + "location": "EU West", + "effectiveStartDate": "2023-10-01T00:00:00Z", + "meterId": "50d9a511-fb6e-5113-8003-5a1ef7abca11", + "meterName": "Premium Managed Disk V2 Data Stored", + "productId": "DZH318Z0DCRX", + "skuId": "DZH318Z0DCRX/00C6", + "productName": "Az DB for PostgreSQL Flexible Server Storage", + "skuName": "Premium Managed Disk V2", + "serviceName": "Azure Database for PostgreSQL", + "serviceId": "DZH3199QPQTD", + "serviceFamily": "Databases", + "unitOfMeasure": "1 GiB/Month", + "type": "Consumption", + "isPrimaryMeterRegion": true, + "armSkuName": "Premium Managed Disk" + }, + { + "currencyCode": "USD", + "tierMinimumUnits": 0.0, + "retailPrice": 0.0199, + "unitPrice": 0.0199, + "armRegionName": "westeurope", + "location": "EU West", + "effectiveStartDate": "2021-12-01T00:00:00Z", + "meterId": "5e31969e-0b91-5186-81a1-7475fabe1262", + "meterName": "B1MS", + "productId": "DZH318Z0DCRL", + "skuId": "DZH318Z0DCRL/003J", + "productName": "Azure Database for PostgreSQL Flexible Server Burstable BS Series Compute", + "skuName": "B1MS", + "serviceName": "Azure Database for PostgreSQL", + "serviceId": "DZH3199QPQTD", + "serviceFamily": "Databases", + "unitOfMeasure": "1 Hour", + "type": "Consumption", + "isPrimaryMeterRegion": true, + "armSkuName": "B1MS" + }, + { + "currencyCode": "USD", + "tierMinimumUnits": 0.0, + "retailPrice": 0.0595, + "unitPrice": 0.0595, + "armRegionName": "westeurope", + "location": "EU West", + "effectiveStartDate": "2023-03-01T00:00:00Z", + "meterId": "75ef37c1-50db-5bc2-8bf5-ba9b97da69d2", + "meterName": "IOPS Scaling Provisioned IOPS", + "productId": "DZH318Z0DCRX", + "skuId": "DZH318Z0DCRX/00CG", + "productName": "Az DB for PostgreSQL Flexible Server Storage", + "skuName": "IOPS Scaling", + "serviceName": "Azure Database for PostgreSQL", + "serviceId": "DZH3199QPQTD", + "serviceFamily": "Databases", + "unitOfMeasure": "1/Month", + "type": "Consumption", + "isPrimaryMeterRegion": true, + "armSkuName": "IOPS Scaling" + }, + { + "currencyCode": "USD", + "tierMinimumUnits": 0.0, + "retailPrice": 0.026, + "unitPrice": 0.026, + "armRegionName": "westeurope", + "location": "EU West", + "effectiveStartDate": "2023-10-01T00:00:00Z", + "meterId": "a3b9a7f9-8335-5ff1-b3bb-b8b82c26ac2a", + "meterName": "PMD V2 IOPS Provisioned IOPS", + "productId": "DZH318Z0DCRX", + "skuId": "DZH318Z0DCRX/00LJ", + "productName": "Az DB for PostgreSQL Flexible Server Storage", + "skuName": "PMD V2 IOPS", + "serviceName": "Azure Database for PostgreSQL", + "serviceId": "DZH3199QPQTD", + "serviceFamily": "Databases", + "unitOfMeasure": "1/Month", + "type": "Consumption", + "isPrimaryMeterRegion": true, + "armSkuName": "V2 IOPS" + } + ], + "NextPageLink": null, + "Count": 6 +} \ No newline at end of file diff --git a/ace-tests/templates/reworked/postgresql/fix-261.bicep b/ace-tests/templates/reworked/postgresql/fix-261.bicep new file mode 100644 index 00000000..d0aad394 --- /dev/null +++ b/ace-tests/templates/reworked/postgresql/fix-261.bicep @@ -0,0 +1,20 @@ +param parSuffix string = utcNow('yyyyMMddhhmmss') +param parLocation string = resourceGroup().location + +resource db1 'Microsoft.DBforPostgreSQL/flexibleServers@2022-12-01' = { +#disable-next-line use-stable-resource-identifiers + name: 'postgresqlserver1-${parSuffix}' + location: parLocation + sku: { + name: 'Standard_B1ms' + tier: 'Burstable' + } + properties: { + createMode: 'Default' + administratorLogin: 'login' + administratorLoginPassword: 'password' + storage: { + storageSizeGB: 64 + } + } +} diff --git a/ace/Products/PostgreSQL/PostgreSQLFlexibleQueryFilter.cs b/ace/Products/PostgreSQL/PostgreSQLFlexibleQueryFilter.cs index 6553e4aa..7ca919e7 100644 --- a/ace/Products/PostgreSQL/PostgreSQLFlexibleQueryFilter.cs +++ b/ace/Products/PostgreSQL/PostgreSQLFlexibleQueryFilter.cs @@ -1,20 +1,13 @@ using ACE.WhatIf; using Microsoft.Extensions.Logging; -using System; using System.Text.Json; -internal class PostgreSQLFlexibleQueryFilter : IQueryFilter +internal class PostgreSQLFlexibleQueryFilter(WhatIfAfterBeforeChange afterState, ILogger logger) : IQueryFilter { private const string ServiceId = "DZH3199QPQTD"; - private readonly WhatIfAfterBeforeChange afterState; - private readonly ILogger logger; - - public PostgreSQLFlexibleQueryFilter(WhatIfAfterBeforeChange afterState, ILogger logger) - { - this.afterState = afterState; - this.logger = logger; - } + private readonly WhatIfAfterBeforeChange afterState = afterState; + private readonly ILogger logger = logger; public string? GetFiltersBasedOnDesiredState(string location) { @@ -28,29 +21,36 @@ public PostgreSQLFlexibleQueryFilter(WhatIfAfterBeforeChange afterState, ILogger var tierId = this.afterState.sku?.tier; var skuParts = sku.Split("_"); var familyId = skuParts[1]; - var cores = skuParts[2]; - var skuName = $"{cores} vCore"; + string? skuProductName; string? storageProductName; string? productName; + string? skuName; // Note that for some reasons some product names for PostgreSQL contain non-breaking space \u00A0 // meaning query won't work if we miss them. We may use armSkuName in the future if (tierId == "Burstable") { + // Always fun when there're magical edge cases :F + if(familyId.Equals("B1ms", StringComparison.CurrentCultureIgnoreCase) || familyId.Equals("B2s", StringComparison.CurrentCultureIgnoreCase)) + { + familyId = familyId.ToUpper(); + } + skuName = familyId; - skuProductName = "Burstable BS"; storageProductName = "Az DB for PostgreSQL Flexible Server Storage"; - productName = $"Azure Database for PostgreSQL Flexible Server {skuProductName} Series Compute"; + productName = $"Azure Database for PostgreSQL Flexible Server Burstable BS Series Compute"; } else if (tierId == "GeneralPurpose") { + skuName = $"{skuParts[2]} vCore"; skuProductName = $"General Purpose\u00A0{familyId}\u00A0"; storageProductName = "Az DB for PostgreSQL Flexible Server Storage"; productName = $"Azure Database for PostgreSQL Flexible Server {skuProductName}Series Compute"; } else { + skuName = $"{skuParts[2]} vCore"; skuProductName = $"Memory Optimized\u00A0{familyId}\u00A0"; storageProductName = "Az DB for PostgreSQL Flexible Server Storage";