diff --git a/.gitignore b/.gitignore index da1c17a..1303e37 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 a2390b1..afa8fa4 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 0000000..7d530f0 --- /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 6b97316..74626c2 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 979bd41..af76733 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 0000000..ebaa3b8 --- /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 0000000..d0aad39 --- /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 6553e4a..7ca919e 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";