From 19657936e4f61aaaf6ca5540ec988286cb310840 Mon Sep 17 00:00:00 2001 From: Tom Kerkhove Date: Fri, 10 Jan 2020 12:13:49 +0100 Subject: [PATCH] Provide scrapers for App Services (App Plan, Web App, Function) (#820) * Provide docs for new scrapers * Provide app service scrapers Signed-off-by: Tom Kerkhove * Support slots for Azure Function App Signed-off-by: Tom Kerkhove * Provide better support for slots Signed-off-by: Tom Kerkhove --- changelog/content/experimental/unreleased.md | 3 + docs/configuration/v1.x/metrics/app-plan.md | 34 +++++ .../v1.x/metrics/function-app.md | 38 ++++++ docs/configuration/v1.x/metrics/index.md | 3 + docs/configuration/v1.x/metrics/web-app.md | 42 ++++++ docs/metrics/labels.md | 2 + .../AppPlanResourceDefinition.cs | 16 +++ .../FunctionAppResourceDefinition.cs | 21 +++ .../ResourceTypes/WebAppResourceDefinition.cs | 22 ++++ .../Configuration/Model/ResourceType.cs | 5 +- .../Core/AzureResourceDeserializerFactory.cs | 9 ++ .../v1/Mapping/V1MappingProfile.cs | 8 +- .../Model/ResourceTypes/AppPlanResourceV1.cs | 13 ++ .../ResourceTypes/FunctionAppResourceV1.cs | 18 +++ .../Model/ResourceTypes/WebAppResourceV1.cs | 18 +++ .../v1/Providers/AppPlanDeserializer.cs | 26 ++++ .../v1/Providers/FunctionAppDeserializer.cs | 29 ++++ .../v1/Providers/WebAppDeserializer.cs | 29 ++++ .../Factories/MetricScraperFactory.cs | 6 + .../ResourceTypes/AppPlanScraper.cs | 29 ++++ .../ResourceTypes/FunctionAppScraper.cs | 42 ++++++ .../ResourceTypes/WebAppScraper.cs | 42 ++++++ .../Factories/MetricValidatorFactory.cs | 6 + .../ResourceTypes/AppPlanMetricValidator.cs | 25 ++++ .../FunctionAppMetricValidator.cs | 25 ++++ .../ResourceTypes/WebAppMetricValidator.cs | 25 ++++ .../Metrics/v1/MetricsDeclarationBuilder.cs | 67 ++++++++++ .../v1/Providers/AppPlanDeserializerTests.cs | 44 +++++++ .../Providers/FunctionAppDeserializerTests.cs | 63 +++++++++ .../v1/Providers/WebAppDeserializerTests.cs | 63 +++++++++ ...nMetricsDeclarationValidationStepsTests.cs | 107 +++++++++++++++ ...pMetricsDeclarationValidationStepsTests.cs | 107 +++++++++++++++ ...pMetricsDeclarationValidationStepsTests.cs | 124 ++++++++++++++++++ src/metric-config.yaml | 38 +++++- 34 files changed, 1146 insertions(+), 3 deletions(-) create mode 100644 docs/configuration/v1.x/metrics/app-plan.md create mode 100644 docs/configuration/v1.x/metrics/function-app.md create mode 100644 docs/configuration/v1.x/metrics/web-app.md create mode 100644 src/Promitor.Core.Scraping/Configuration/Model/Metrics/ResourceTypes/AppPlanResourceDefinition.cs create mode 100644 src/Promitor.Core.Scraping/Configuration/Model/Metrics/ResourceTypes/FunctionAppResourceDefinition.cs create mode 100644 src/Promitor.Core.Scraping/Configuration/Model/Metrics/ResourceTypes/WebAppResourceDefinition.cs create mode 100644 src/Promitor.Core.Scraping/Configuration/Serialization/v1/Model/ResourceTypes/AppPlanResourceV1.cs create mode 100644 src/Promitor.Core.Scraping/Configuration/Serialization/v1/Model/ResourceTypes/FunctionAppResourceV1.cs create mode 100644 src/Promitor.Core.Scraping/Configuration/Serialization/v1/Model/ResourceTypes/WebAppResourceV1.cs create mode 100644 src/Promitor.Core.Scraping/Configuration/Serialization/v1/Providers/AppPlanDeserializer.cs create mode 100644 src/Promitor.Core.Scraping/Configuration/Serialization/v1/Providers/FunctionAppDeserializer.cs create mode 100644 src/Promitor.Core.Scraping/Configuration/Serialization/v1/Providers/WebAppDeserializer.cs create mode 100644 src/Promitor.Core.Scraping/ResourceTypes/AppPlanScraper.cs create mode 100644 src/Promitor.Core.Scraping/ResourceTypes/FunctionAppScraper.cs create mode 100644 src/Promitor.Core.Scraping/ResourceTypes/WebAppScraper.cs create mode 100644 src/Promitor.Scraper.Host/Validation/MetricDefinitions/ResourceTypes/AppPlanMetricValidator.cs create mode 100644 src/Promitor.Scraper.Host/Validation/MetricDefinitions/ResourceTypes/FunctionAppMetricValidator.cs create mode 100644 src/Promitor.Scraper.Host/Validation/MetricDefinitions/ResourceTypes/WebAppMetricValidator.cs create mode 100644 src/Promitor.Scraper.Tests.Unit/Serialization/v1/Providers/AppPlanDeserializerTests.cs create mode 100644 src/Promitor.Scraper.Tests.Unit/Serialization/v1/Providers/FunctionAppDeserializerTests.cs create mode 100644 src/Promitor.Scraper.Tests.Unit/Serialization/v1/Providers/WebAppDeserializerTests.cs create mode 100644 src/Promitor.Scraper.Tests.Unit/Validation/Metrics/ResourceTypes/AppPlanMetricsDeclarationValidationStepsTests.cs create mode 100644 src/Promitor.Scraper.Tests.Unit/Validation/Metrics/ResourceTypes/FunctionAppMetricsDeclarationValidationStepsTests.cs create mode 100644 src/Promitor.Scraper.Tests.Unit/Validation/Metrics/ResourceTypes/WebAppMetricsDeclarationValidationStepsTests.cs diff --git a/changelog/content/experimental/unreleased.md b/changelog/content/experimental/unreleased.md index edee69634..16e3c1509 100644 --- a/changelog/content/experimental/unreleased.md +++ b/changelog/content/experimental/unreleased.md @@ -6,4 +6,7 @@ version: --- - {{% tag added %}} Azure Virtual Machine Scale Set Scraper ([docs](https://promitor.io/configuration/v1.x/metrics/virtual-machine-scale-set) | [#310](https://github.com/tomkerkhove/promitor/issues/310)) +- {{% tag added %}} Azure App Plan Scraper ([docs](https://promitor.io/configuration/v1.x/metrics/app-plan) | [#315](https://github.com/tomkerkhove/promitor/issues/315)) +- {{% tag added %}} Azure Web App Scraper ([docs](https://promitor.io/configuration/v1.x/metrics/web-app) | [#762](https://github.com/tomkerkhove/promitor/issues/762)) +- {{% tag added %}} Azure Function App Scraper ([docs](https://promitor.io/configuration/v1.x/metrics/function-app) | [#366](https://github.com/tomkerkhove/promitor/issues/366)) - {{% tag changed %}} Metric labels for dimensions are now always lower-cased diff --git a/docs/configuration/v1.x/metrics/app-plan.md b/docs/configuration/v1.x/metrics/app-plan.md new file mode 100644 index 000000000..88f4c0252 --- /dev/null +++ b/docs/configuration/v1.x/metrics/app-plan.md @@ -0,0 +1,34 @@ +--- +layout: default +title: Azure App Plan Declaration +--- + +## Azure App Plan - ![Availability Badge](https://img.shields.io/badge/Available%20Starting-v1.2-green.svg) + +You can declare to scrape an Azure App Plan via the `AppPlan` resource +type. + +The following fields need to be provided: + +- `appPlanName` - The name of the Azure App Plan + +All supported metrics are documented in the official [Azure Monitor documentation](https://docs.microsoft.com/en-us/azure/azure-monitor/platform/metrics-supported#microsoftwebserverfarms). + +Example: + +```yaml +name: azure_app_plan_percentage_memory +description: "Average percentage of memory usage on an Azure App Plan" +resourceType: AppPlan +azureMetricConfiguration: + metricName: MemoryPercentage + aggregation: + type: Average +resources: +- appPlanName: promitor-app-plan +``` + + +[← back to metrics declarations](/configuration/v1.x/metrics)
+[← back to introduction](/) + diff --git a/docs/configuration/v1.x/metrics/function-app.md b/docs/configuration/v1.x/metrics/function-app.md new file mode 100644 index 000000000..84f292e57 --- /dev/null +++ b/docs/configuration/v1.x/metrics/function-app.md @@ -0,0 +1,38 @@ +--- +layout: default +title: Azure Function App Declaration +--- + +## Azure Function App - ![Availability Badge](https://img.shields.io/badge/Available%20Starting-v1.2-green.svg) + +You can declare to scrape an Azure Function App via the `FunctionApp` resource +type. + +The following fields need to be provided: + +- `functionAppName` - The name of the Azure Function App + +All supported metrics are documented in the official [Azure Monitor documentation](https://docs.microsoft.com/en-us/azure/azure-monitor/platform/metrics-supported#microsoftwebsites-functions). + +The following scraper-specific metric label will be added: + +- `slot_name` - Name of the deployment slot. If none is specified, `production` will be used. + +Example: + +```yaml +name: azure_function_requests +description: "Amount of requests for an Azure Function App" +resourceType: FunctionApp +azureMetricConfiguration: + metricName: Requests + aggregation: + type: Total +resources: +- functionAppName: promitor-function-app +``` + + +[← back to metrics declarations](/configuration/v1.x/metrics)
+[← back to introduction](/) + diff --git a/docs/configuration/v1.x/metrics/index.md b/docs/configuration/v1.x/metrics/index.md index e129362a2..05428475b 100644 --- a/docs/configuration/v1.x/metrics/index.md +++ b/docs/configuration/v1.x/metrics/index.md @@ -111,11 +111,13 @@ service supported by Azure Monitor. We also provide a simplified way to scrape the following Azure resources: +- [Azure App Plan](app-plan) - [Azure Cache for Redis](redis-cache) - [Azure Container Instances](container-instances) - [Azure Container Registry](container-registry) - [Azure Cosmos DB](cosmos-db) - [Azure Database for PostgreSQL](postgresql) +- [Azure Function App](function-app) - [Azure Network Interface](network-interface) - [Azure Service Bus Queue](service-bus-queue) - [Azure SQL Database](sql-database) @@ -123,6 +125,7 @@ We also provide a simplified way to scrape the following Azure resources: - [Azure Storage Queue](storage-queue) - [Azure Virtual Machine](virtual-machine) - [Azure Virtual Machine Scale Set (VMSS)](virtual-machine-scale-set) +- [Azure Web App](web-app) Want to help out? Create an issue and [contribute a new scraper](https://github.com/tomkerkhove/promitor/blob/master/adding-a-new-scraper.md). diff --git a/docs/configuration/v1.x/metrics/web-app.md b/docs/configuration/v1.x/metrics/web-app.md new file mode 100644 index 000000000..def082ab6 --- /dev/null +++ b/docs/configuration/v1.x/metrics/web-app.md @@ -0,0 +1,42 @@ +--- +layout: default +title: Azure Web App Declaration +--- + +## Azure Web App - ![Availability Badge](https://img.shields.io/badge/Available%20Starting-v1.2-green.svg) + +You can declare to scrape an Azure Web App via the `WebApp` resource +type. + +The following fields need to be provided: + +- `webAppName` - The name of the Azure Web App +- `slotName` - The name of the deployment slot *(optional)* + +All supported metrics are documented in the official [Azure Monitor documentation](https://docs.microsoft.com/en-us/azure/azure-monitor/platform/metrics-supported#microsoftwebsites-excluding-functions). + +The following scraper-specific metric label will be added: + +- `slot_name` - Name of the deployment slot. If none is specified, `production` will be used. + +Example: + +```yaml +name: azure_web_app_requests +description: "Amount of requests for an Azure Web App" +resourceType: WebApp +azureMetricConfiguration: + metricName: Requests + aggregation: + type: Total +resources: +- webAppName: promitor-web-app + slot: staging +- webAppName: promitor-web-app + slot: production +``` + + +[← back to metrics declarations](/configuration/v1.x/metrics)
+[← back to introduction](/) + diff --git a/docs/metrics/labels.md b/docs/metrics/labels.md index d486f0e38..444f56154 100644 --- a/docs/metrics/labels.md +++ b/docs/metrics/labels.md @@ -37,9 +37,11 @@ Every scraper can provide additional labels to provide more information. Currently we support this for: +- Azure Function App - Azure Service Bus - Azure SQL Database - Azure Storage Queue +- Azure Web App For more information, we recommend reading the [scraper-specific documentation](./../configuration/v1.x/metrics/#supported-azure-services). diff --git a/src/Promitor.Core.Scraping/Configuration/Model/Metrics/ResourceTypes/AppPlanResourceDefinition.cs b/src/Promitor.Core.Scraping/Configuration/Model/Metrics/ResourceTypes/AppPlanResourceDefinition.cs new file mode 100644 index 000000000..0da1cf6c0 --- /dev/null +++ b/src/Promitor.Core.Scraping/Configuration/Model/Metrics/ResourceTypes/AppPlanResourceDefinition.cs @@ -0,0 +1,16 @@ +namespace Promitor.Core.Scraping.Configuration.Model.Metrics.ResourceTypes +{ + public class AppPlanResourceDefinition : AzureResourceDefinition + { + public AppPlanResourceDefinition(string resourceGroupName, string appPlanName) + : base(ResourceType.AppPlan, resourceGroupName) + { + AppPlanName = appPlanName; + } + + /// + /// The name of the Azure App Plan to get metrics for. + /// + public string AppPlanName { get; set; } + } +} \ No newline at end of file diff --git a/src/Promitor.Core.Scraping/Configuration/Model/Metrics/ResourceTypes/FunctionAppResourceDefinition.cs b/src/Promitor.Core.Scraping/Configuration/Model/Metrics/ResourceTypes/FunctionAppResourceDefinition.cs new file mode 100644 index 000000000..1f20e081d --- /dev/null +++ b/src/Promitor.Core.Scraping/Configuration/Model/Metrics/ResourceTypes/FunctionAppResourceDefinition.cs @@ -0,0 +1,21 @@ +namespace Promitor.Core.Scraping.Configuration.Model.Metrics.ResourceTypes +{ + public class FunctionAppResourceDefinition : AzureResourceDefinition + { + public FunctionAppResourceDefinition(string resourceGroupName, string functionAppName) + : base(ResourceType.FunctionApp, resourceGroupName) + { + FunctionAppName = functionAppName; + } + + /// + /// The name of the Azure Function App to get metrics for. + /// + public string FunctionAppName { get; set; } + + /// + /// The name of the deployment slot. + /// + public string SlotName { get; set; } + } +} \ No newline at end of file diff --git a/src/Promitor.Core.Scraping/Configuration/Model/Metrics/ResourceTypes/WebAppResourceDefinition.cs b/src/Promitor.Core.Scraping/Configuration/Model/Metrics/ResourceTypes/WebAppResourceDefinition.cs new file mode 100644 index 000000000..ecc178981 --- /dev/null +++ b/src/Promitor.Core.Scraping/Configuration/Model/Metrics/ResourceTypes/WebAppResourceDefinition.cs @@ -0,0 +1,22 @@ +namespace Promitor.Core.Scraping.Configuration.Model.Metrics.ResourceTypes +{ + public class WebAppResourceDefinition : AzureResourceDefinition + { + public WebAppResourceDefinition(string resourceGroupName, string webAppName, string slotName) + : base(ResourceType.WebApp, resourceGroupName) + { + WebAppName = webAppName; + SlotName = slotName; + } + + /// + /// The name of the Azure Web App. + /// + public string WebAppName { get; set; } + + /// + /// The name of the deployment slot. + /// + public string SlotName { get; set; } + } +} \ No newline at end of file diff --git a/src/Promitor.Core.Scraping/Configuration/Model/ResourceType.cs b/src/Promitor.Core.Scraping/Configuration/Model/ResourceType.cs index a48a0282a..1d86ec9807 100644 --- a/src/Promitor.Core.Scraping/Configuration/Model/ResourceType.cs +++ b/src/Promitor.Core.Scraping/Configuration/Model/ResourceType.cs @@ -15,6 +15,9 @@ public enum ResourceType PostgreSql = 10, SqlDatabase = 11, SqlManagedInstance = 12, - VirtualMachineScaleSet = 13 + VirtualMachineScaleSet = 13, + AppPlan = 14, + WebApp = 15, + FunctionApp = 16 } } \ No newline at end of file diff --git a/src/Promitor.Core.Scraping/Configuration/Serialization/v1/Core/AzureResourceDeserializerFactory.cs b/src/Promitor.Core.Scraping/Configuration/Serialization/v1/Core/AzureResourceDeserializerFactory.cs index 0a3cd4456..374d8c661 100644 --- a/src/Promitor.Core.Scraping/Configuration/Serialization/v1/Core/AzureResourceDeserializerFactory.cs +++ b/src/Promitor.Core.Scraping/Configuration/Serialization/v1/Core/AzureResourceDeserializerFactory.cs @@ -60,6 +60,15 @@ public IDeserializer GetDeserializerFor(ResourceType case ResourceType.VirtualMachineScaleSet: var virtualMachineScaleSetLogger = _loggerFactory.CreateLogger(); return new VirtualMachineScaleSetDeserializer(virtualMachineScaleSetLogger); + case ResourceType.AppPlan: + var appPlanLogger = _loggerFactory.CreateLogger(); + return new AppPlanDeserializer(appPlanLogger); + case ResourceType.WebApp: + var webAppLogger = _loggerFactory.CreateLogger(); + return new WebAppDeserializer(webAppLogger); + case ResourceType.FunctionApp: + var functionAppLogger = _loggerFactory.CreateLogger(); + return new FunctionAppDeserializer(functionAppLogger); default: throw new ArgumentOutOfRangeException($"Resource Type {resourceType} not supported."); } diff --git a/src/Promitor.Core.Scraping/Configuration/Serialization/v1/Mapping/V1MappingProfile.cs b/src/Promitor.Core.Scraping/Configuration/Serialization/v1/Mapping/V1MappingProfile.cs index 073f205c3..f5190f2ab 100644 --- a/src/Promitor.Core.Scraping/Configuration/Serialization/v1/Mapping/V1MappingProfile.cs +++ b/src/Promitor.Core.Scraping/Configuration/Serialization/v1/Mapping/V1MappingProfile.cs @@ -35,6 +35,9 @@ public V1MappingProfile() CreateMap(); CreateMap(); CreateMap(); + CreateMap(); + CreateMap(); + CreateMap(); CreateMap(); @@ -54,7 +57,10 @@ public V1MappingProfile() .Include() .Include() .Include() - .Include(); + .Include() + .Include() + .Include() + .Include(); } } } diff --git a/src/Promitor.Core.Scraping/Configuration/Serialization/v1/Model/ResourceTypes/AppPlanResourceV1.cs b/src/Promitor.Core.Scraping/Configuration/Serialization/v1/Model/ResourceTypes/AppPlanResourceV1.cs new file mode 100644 index 000000000..71dcf5865 --- /dev/null +++ b/src/Promitor.Core.Scraping/Configuration/Serialization/v1/Model/ResourceTypes/AppPlanResourceV1.cs @@ -0,0 +1,13 @@ +namespace Promitor.Core.Scraping.Configuration.Serialization.v1.Model.ResourceTypes +{ + /// + /// Contains the configuration required to scrape a app plan. + /// + public class AppPlanResourceV1 : AzureResourceDefinitionV1 + { + /// + /// The name of the Azure App Plan to get metrics for. + /// + public string AppPlanName { get; set; } + } +} diff --git a/src/Promitor.Core.Scraping/Configuration/Serialization/v1/Model/ResourceTypes/FunctionAppResourceV1.cs b/src/Promitor.Core.Scraping/Configuration/Serialization/v1/Model/ResourceTypes/FunctionAppResourceV1.cs new file mode 100644 index 000000000..2effc05a8 --- /dev/null +++ b/src/Promitor.Core.Scraping/Configuration/Serialization/v1/Model/ResourceTypes/FunctionAppResourceV1.cs @@ -0,0 +1,18 @@ +namespace Promitor.Core.Scraping.Configuration.Serialization.v1.Model.ResourceTypes +{ + /// + /// Contains the configuration required to scrape an Azure Function App. + /// + public class FunctionAppResourceV1 : AzureResourceDefinitionV1 + { + /// + /// The name of the Azure Function App to get metrics for. + /// + public string FunctionAppName { get; set; } + + /// + /// The name of the deployment slot. + /// + public string SlotName { get; set; } + } +} diff --git a/src/Promitor.Core.Scraping/Configuration/Serialization/v1/Model/ResourceTypes/WebAppResourceV1.cs b/src/Promitor.Core.Scraping/Configuration/Serialization/v1/Model/ResourceTypes/WebAppResourceV1.cs new file mode 100644 index 000000000..fe0eb8116 --- /dev/null +++ b/src/Promitor.Core.Scraping/Configuration/Serialization/v1/Model/ResourceTypes/WebAppResourceV1.cs @@ -0,0 +1,18 @@ +namespace Promitor.Core.Scraping.Configuration.Serialization.v1.Model.ResourceTypes +{ + /// + /// Contains the configuration required to scrape an Azure Web App. + /// + public class WebAppResourceV1 : AzureResourceDefinitionV1 + { + /// + /// The name of the Azure Web App. + /// + public string WebAppName { get; set; } + + /// + /// The name of the deployment slot. + /// + public string SlotName { get; set; } + } +} diff --git a/src/Promitor.Core.Scraping/Configuration/Serialization/v1/Providers/AppPlanDeserializer.cs b/src/Promitor.Core.Scraping/Configuration/Serialization/v1/Providers/AppPlanDeserializer.cs new file mode 100644 index 000000000..fb014f231 --- /dev/null +++ b/src/Promitor.Core.Scraping/Configuration/Serialization/v1/Providers/AppPlanDeserializer.cs @@ -0,0 +1,26 @@ +using Microsoft.Extensions.Logging; +using Promitor.Core.Scraping.Configuration.Serialization.v1.Model; +using Promitor.Core.Scraping.Configuration.Serialization.v1.Model.ResourceTypes; +using YamlDotNet.RepresentationModel; + +namespace Promitor.Core.Scraping.Configuration.Serialization.v1.Providers +{ + public class AppPlanDeserializer : ResourceDeserializer + { + private const string AppPlanNameTag = "appPlanName"; + + public AppPlanDeserializer(ILogger logger) : base(logger) + { + } + + protected override AzureResourceDefinitionV1 DeserializeResource(YamlMappingNode node) + { + var appPlanName = node.GetString(AppPlanNameTag); + + return new AppPlanResourceV1 + { + AppPlanName= appPlanName + }; + } + } +} diff --git a/src/Promitor.Core.Scraping/Configuration/Serialization/v1/Providers/FunctionAppDeserializer.cs b/src/Promitor.Core.Scraping/Configuration/Serialization/v1/Providers/FunctionAppDeserializer.cs new file mode 100644 index 000000000..9c588bff7 --- /dev/null +++ b/src/Promitor.Core.Scraping/Configuration/Serialization/v1/Providers/FunctionAppDeserializer.cs @@ -0,0 +1,29 @@ +using Microsoft.Extensions.Logging; +using Promitor.Core.Scraping.Configuration.Serialization.v1.Model; +using Promitor.Core.Scraping.Configuration.Serialization.v1.Model.ResourceTypes; +using YamlDotNet.RepresentationModel; + +namespace Promitor.Core.Scraping.Configuration.Serialization.v1.Providers +{ + public class FunctionAppDeserializer : ResourceDeserializer + { + private const string FunctionAppNameTag = "functionAppName"; + private const string SlotNameTag = "slotName"; + + public FunctionAppDeserializer(ILogger logger) : base(logger) + { + } + + protected override AzureResourceDefinitionV1 DeserializeResource(YamlMappingNode node) + { + var functionAppName = node.GetString(FunctionAppNameTag); + var slotName = node.GetString(SlotNameTag); + + return new FunctionAppResourceV1 + { + FunctionAppName = functionAppName, + SlotName = slotName + }; + } + } +} diff --git a/src/Promitor.Core.Scraping/Configuration/Serialization/v1/Providers/WebAppDeserializer.cs b/src/Promitor.Core.Scraping/Configuration/Serialization/v1/Providers/WebAppDeserializer.cs new file mode 100644 index 000000000..27c8fd8b1 --- /dev/null +++ b/src/Promitor.Core.Scraping/Configuration/Serialization/v1/Providers/WebAppDeserializer.cs @@ -0,0 +1,29 @@ +using Microsoft.Extensions.Logging; +using Promitor.Core.Scraping.Configuration.Serialization.v1.Model; +using Promitor.Core.Scraping.Configuration.Serialization.v1.Model.ResourceTypes; +using YamlDotNet.RepresentationModel; + +namespace Promitor.Core.Scraping.Configuration.Serialization.v1.Providers +{ + public class WebAppDeserializer : ResourceDeserializer + { + private const string WebAppNameTag = "webAppName"; + private const string SlotNameTag = "slotName"; + + public WebAppDeserializer(ILogger logger) : base(logger) + { + } + + protected override AzureResourceDefinitionV1 DeserializeResource(YamlMappingNode node) + { + var webAppName = node.GetString(WebAppNameTag); + var slotName = node.GetString(SlotNameTag); + + return new WebAppResourceV1 + { + WebAppName = webAppName, + SlotName = slotName, + }; + } + } +} diff --git a/src/Promitor.Core.Scraping/Factories/MetricScraperFactory.cs b/src/Promitor.Core.Scraping/Factories/MetricScraperFactory.cs index 0f0257915..daa9872a4 100644 --- a/src/Promitor.Core.Scraping/Factories/MetricScraperFactory.cs +++ b/src/Promitor.Core.Scraping/Factories/MetricScraperFactory.cs @@ -67,6 +67,12 @@ public IScraper CreateScraper(ResourceType metricDefini return new SqlManagedInstanceScraper(scraperConfiguration); case ResourceType.VirtualMachineScaleSet: return new VirtualMachineScaleSetScraper(scraperConfiguration); + case ResourceType.AppPlan: + return new AppPlanScraper(scraperConfiguration); + case ResourceType.WebApp: + return new WebAppScraper(scraperConfiguration); + case ResourceType.FunctionApp: + return new FunctionAppScraper(scraperConfiguration); default: throw new ArgumentOutOfRangeException(); } diff --git a/src/Promitor.Core.Scraping/ResourceTypes/AppPlanScraper.cs b/src/Promitor.Core.Scraping/ResourceTypes/AppPlanScraper.cs new file mode 100644 index 000000000..0ca999ae2 --- /dev/null +++ b/src/Promitor.Core.Scraping/ResourceTypes/AppPlanScraper.cs @@ -0,0 +1,29 @@ +using System; +using System.Threading.Tasks; +using Microsoft.Azure.Management.Monitor.Fluent.Models; +using Promitor.Core.Scraping.Configuration.Model.Metrics; +using Promitor.Core.Scraping.Configuration.Model.Metrics.ResourceTypes; + +namespace Promitor.Core.Scraping.ResourceTypes +{ + internal class AppPlanScraper : Scraper + { + private const string ResourceUriTemplate = "subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Web/serverfarms/{2}"; + + public AppPlanScraper(ScraperConfiguration scraperConfiguration) + : base(scraperConfiguration) + { + } + + protected override async Task ScrapeResourceAsync(string subscriptionId, ScrapeDefinition scrapeDefinition, AppPlanResourceDefinition resource, AggregationType aggregationType, TimeSpan aggregationInterval) + { + var resourceUri = string.Format(ResourceUriTemplate, AzureMetadata.SubscriptionId, scrapeDefinition.ResourceGroupName, resource.AppPlanName); + + var metricName = scrapeDefinition.AzureMetricConfiguration.MetricName; + var dimensionName = scrapeDefinition.AzureMetricConfiguration.Dimension?.Name; + var foundMetricValue = await AzureMonitorClient.QueryMetricAsync(metricName,dimensionName, aggregationType, aggregationInterval, resourceUri); + + return new ScrapeResult(subscriptionId, scrapeDefinition.ResourceGroupName, resource.AppPlanName, resourceUri, foundMetricValue); + } + } +} \ No newline at end of file diff --git a/src/Promitor.Core.Scraping/ResourceTypes/FunctionAppScraper.cs b/src/Promitor.Core.Scraping/ResourceTypes/FunctionAppScraper.cs new file mode 100644 index 000000000..1c2ebeac4 --- /dev/null +++ b/src/Promitor.Core.Scraping/ResourceTypes/FunctionAppScraper.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.Azure.Management.Monitor.Fluent.Models; +using Promitor.Core.Scraping.Configuration.Model.Metrics; +using Promitor.Core.Scraping.Configuration.Model.Metrics.ResourceTypes; + +namespace Promitor.Core.Scraping.ResourceTypes +{ + internal class FunctionAppScraper : Scraper + { + private const string ResourceUriTemplate = "subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Web/sites/{2}"; + + public FunctionAppScraper(ScraperConfiguration scraperConfiguration) + : base(scraperConfiguration) + { + } + + protected override async Task ScrapeResourceAsync(string subscriptionId, ScrapeDefinition scrapeDefinition, FunctionAppResourceDefinition resource, AggregationType aggregationType, TimeSpan aggregationInterval) + { + var slotName = string.IsNullOrWhiteSpace(resource.SlotName) ? "production" : resource.SlotName; + var resourceUri = string.Format(ResourceUriTemplate, AzureMetadata.SubscriptionId, scrapeDefinition.ResourceGroupName, resource.FunctionAppName); + + // Production slot should not be suffixed in resource URI + if (slotName != "production") + { + resourceUri += $"/slots/{slotName}"; + } + + var metricName = scrapeDefinition.AzureMetricConfiguration.MetricName; + var dimensionName = scrapeDefinition.AzureMetricConfiguration.Dimension?.Name; + var foundMetricValue = await AzureMonitorClient.QueryMetricAsync(metricName, dimensionName, aggregationType, aggregationInterval, resourceUri); + + var customLabels = new Dictionary + { + {"slot_name",slotName } + }; + + return new ScrapeResult(subscriptionId, scrapeDefinition.ResourceGroupName, resource.FunctionAppName, resourceUri, foundMetricValue, customLabels); + } + } +} \ No newline at end of file diff --git a/src/Promitor.Core.Scraping/ResourceTypes/WebAppScraper.cs b/src/Promitor.Core.Scraping/ResourceTypes/WebAppScraper.cs new file mode 100644 index 000000000..eff9364af --- /dev/null +++ b/src/Promitor.Core.Scraping/ResourceTypes/WebAppScraper.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.Azure.Management.Monitor.Fluent.Models; +using Promitor.Core.Scraping.Configuration.Model.Metrics; +using Promitor.Core.Scraping.Configuration.Model.Metrics.ResourceTypes; + +namespace Promitor.Core.Scraping.ResourceTypes +{ + internal class WebAppScraper : Scraper + { + private const string ResourceUriTemplate = "subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Web/sites/{2}"; + + public WebAppScraper(ScraperConfiguration scraperConfiguration) + : base(scraperConfiguration) + { + } + + protected override async Task ScrapeResourceAsync(string subscriptionId, ScrapeDefinition scrapeDefinition, WebAppResourceDefinition resource, AggregationType aggregationType, TimeSpan aggregationInterval) + { + var slotName = string.IsNullOrWhiteSpace(resource.SlotName) ? "production" : resource.SlotName; + var resourceUri = string.Format(ResourceUriTemplate, AzureMetadata.SubscriptionId, scrapeDefinition.ResourceGroupName, resource.WebAppName); + + // Production slot should not be suffixed in resource URI + if (slotName != "production") + { + resourceUri += $"/slots/{slotName}"; + } + + var metricName = scrapeDefinition.AzureMetricConfiguration.MetricName; + var dimensionName = scrapeDefinition.AzureMetricConfiguration.Dimension?.Name; + var foundMetricValue = await AzureMonitorClient.QueryMetricAsync(metricName, dimensionName, aggregationType, aggregationInterval, resourceUri); + + var customLabels = new Dictionary + { + {"slot_name", slotName} + }; + + return new ScrapeResult(subscriptionId, scrapeDefinition.ResourceGroupName, resource.WebAppName, resourceUri, foundMetricValue, customLabels); + } + } +} \ No newline at end of file diff --git a/src/Promitor.Scraper.Host/Validation/Factories/MetricValidatorFactory.cs b/src/Promitor.Scraper.Host/Validation/Factories/MetricValidatorFactory.cs index 57df7355f..0e7c15ee8 100644 --- a/src/Promitor.Scraper.Host/Validation/Factories/MetricValidatorFactory.cs +++ b/src/Promitor.Scraper.Host/Validation/Factories/MetricValidatorFactory.cs @@ -37,6 +37,12 @@ internal static IMetricValidator GetValidatorFor(ResourceType resourceType) return new SqlManagedInstanceMetricValidator(); case ResourceType.VirtualMachineScaleSet: return new VirtualMachineScaleSetMetricValidator(); + case ResourceType.WebApp: + return new WebAppMetricValidator(); + case ResourceType.AppPlan: + return new AppPlanMetricValidator(); + case ResourceType.FunctionApp: + return new FunctionAppMetricValidator(); } throw new ArgumentOutOfRangeException(nameof(resourceType), $"No validation rules are defined for metric type '{resourceType}'"); diff --git a/src/Promitor.Scraper.Host/Validation/MetricDefinitions/ResourceTypes/AppPlanMetricValidator.cs b/src/Promitor.Scraper.Host/Validation/MetricDefinitions/ResourceTypes/AppPlanMetricValidator.cs new file mode 100644 index 000000000..bf36d2520 --- /dev/null +++ b/src/Promitor.Scraper.Host/Validation/MetricDefinitions/ResourceTypes/AppPlanMetricValidator.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using System.Linq; +using GuardNet; +using Promitor.Core.Scraping.Configuration.Model.Metrics; +using Promitor.Core.Scraping.Configuration.Model.Metrics.ResourceTypes; +using Promitor.Scraper.Host.Validation.MetricDefinitions.Interfaces; + +namespace Promitor.Scraper.Host.Validation.MetricDefinitions.ResourceTypes +{ + internal class AppPlanMetricValidator : IMetricValidator + { + public IEnumerable Validate(MetricDefinition metricDefinition) + { + Guard.NotNull(metricDefinition, nameof(metricDefinition)); + + foreach (var resourceDefinition in metricDefinition.Resources.Cast()) + { + if (string.IsNullOrWhiteSpace(resourceDefinition.AppPlanName)) + { + yield return "No app plan name is configured"; + } + } + } + } +} \ No newline at end of file diff --git a/src/Promitor.Scraper.Host/Validation/MetricDefinitions/ResourceTypes/FunctionAppMetricValidator.cs b/src/Promitor.Scraper.Host/Validation/MetricDefinitions/ResourceTypes/FunctionAppMetricValidator.cs new file mode 100644 index 000000000..efeb832fb --- /dev/null +++ b/src/Promitor.Scraper.Host/Validation/MetricDefinitions/ResourceTypes/FunctionAppMetricValidator.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using System.Linq; +using GuardNet; +using Promitor.Core.Scraping.Configuration.Model.Metrics; +using Promitor.Core.Scraping.Configuration.Model.Metrics.ResourceTypes; +using Promitor.Scraper.Host.Validation.MetricDefinitions.Interfaces; + +namespace Promitor.Scraper.Host.Validation.MetricDefinitions.ResourceTypes +{ + internal class FunctionAppMetricValidator : IMetricValidator + { + public IEnumerable Validate(MetricDefinition metricDefinition) + { + Guard.NotNull(metricDefinition, nameof(metricDefinition)); + + foreach (var resourceDefinition in metricDefinition.Resources.Cast()) + { + if (string.IsNullOrWhiteSpace(resourceDefinition.FunctionAppName)) + { + yield return "No function app name is configured"; + } + } + } + } +} \ No newline at end of file diff --git a/src/Promitor.Scraper.Host/Validation/MetricDefinitions/ResourceTypes/WebAppMetricValidator.cs b/src/Promitor.Scraper.Host/Validation/MetricDefinitions/ResourceTypes/WebAppMetricValidator.cs new file mode 100644 index 000000000..5847756db --- /dev/null +++ b/src/Promitor.Scraper.Host/Validation/MetricDefinitions/ResourceTypes/WebAppMetricValidator.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using System.Linq; +using GuardNet; +using Promitor.Core.Scraping.Configuration.Model.Metrics; +using Promitor.Core.Scraping.Configuration.Model.Metrics.ResourceTypes; +using Promitor.Scraper.Host.Validation.MetricDefinitions.Interfaces; + +namespace Promitor.Scraper.Host.Validation.MetricDefinitions.ResourceTypes +{ + internal class WebAppMetricValidator : IMetricValidator + { + public IEnumerable Validate(MetricDefinition metricDefinition) + { + Guard.NotNull(metricDefinition, nameof(metricDefinition)); + + foreach (var resourceDefinition in metricDefinition.Resources.Cast()) + { + if (string.IsNullOrWhiteSpace(resourceDefinition.WebAppName)) + { + yield return "No web app name is configured"; + } + } + } + } +} \ No newline at end of file diff --git a/src/Promitor.Scraper.Tests.Unit/Builders/Metrics/v1/MetricsDeclarationBuilder.cs b/src/Promitor.Scraper.Tests.Unit/Builders/Metrics/v1/MetricsDeclarationBuilder.cs index 7431608ad..6227a39e2 100644 --- a/src/Promitor.Scraper.Tests.Unit/Builders/Metrics/v1/MetricsDeclarationBuilder.cs +++ b/src/Promitor.Scraper.Tests.Unit/Builders/Metrics/v1/MetricsDeclarationBuilder.cs @@ -162,6 +162,50 @@ public string Build(IMapper mapper) return this; } + public MetricsDeclarationBuilder WithAppPlanMetric(string metricName = "promitor-app-plan", string metricDescription = "Description for a metric", string appPlanName = "promitor-app-plan", string azureMetricName = "TotalRequests") + { + var azureMetricConfiguration = CreateAzureMetricConfiguration(azureMetricName); + var resource = new AppPlanResourceV1 + { + AppPlanName = appPlanName + }; + + var metric = new MetricDefinitionV1 + { + Name = metricName, + Description = metricDescription, + AzureMetricConfiguration = azureMetricConfiguration, + Resources = new List { resource }, + ResourceType = ResourceType.AppPlan + }; + + _metrics.Add(metric); + + return this; + } + + public MetricsDeclarationBuilder WithFunctionAppMetric(string metricName = "promitor-fuction-app", string metricDescription = "Description for a metric", string functionAppName = "promitor-fuction-app", string azureMetricName = "TotalRequests") + { + var azureMetricConfiguration = CreateAzureMetricConfiguration(azureMetricName); + var resource = new FunctionAppResourceV1 + { + FunctionAppName = functionAppName + }; + + var metric = new MetricDefinitionV1 + { + Name = metricName, + Description = metricDescription, + AzureMetricConfiguration = azureMetricConfiguration, + Resources = new List { resource }, + ResourceType = ResourceType.FunctionApp + }; + + _metrics.Add(metric); + + return this; + } + public MetricsDeclarationBuilder WithAzureStorageQueueMetric(string metricName = "promitor", string metricDescription = "Description for a metric", string queueName = "promitor-queue", string accountName = "promitor-account", string sasToken = "?sig=promitor", string azureMetricName = AzureStorageConstants.Queues.Metrics.MessageCount) { var azureMetricConfiguration = CreateAzureMetricConfiguration(azureMetricName); @@ -213,6 +257,29 @@ public string Build(IMapper mapper) return this; } + public MetricsDeclarationBuilder WithWebAppMetric(string metricName = "promitor-web-app", string metricDescription = "Description for a metric", string webAppName = "promitor-web-app-name", string slotName = "production", string azureMetricName = "Total") + { + var azureMetricConfiguration = CreateAzureMetricConfiguration(azureMetricName); + var resource = new WebAppResourceV1 + { + WebAppName = webAppName, + SlotName = slotName + }; + + var metric = new MetricDefinitionV1 + { + Name = metricName, + Description = metricDescription, + AzureMetricConfiguration = azureMetricConfiguration, + Resources = new List { resource }, + ResourceType = ResourceType.WebApp + }; + + _metrics.Add(metric); + + return this; + } + public MetricsDeclarationBuilder WithVirtualMachineScaleSetMetric(string metricName = "promitor-virtual-machine-scale-set", string metricDescription = "Description for a metric", string scaleSetName = "promitor-scale-set-name", string azureMetricName = "Total") { var azureMetricConfiguration = CreateAzureMetricConfiguration(azureMetricName); diff --git a/src/Promitor.Scraper.Tests.Unit/Serialization/v1/Providers/AppPlanDeserializerTests.cs b/src/Promitor.Scraper.Tests.Unit/Serialization/v1/Providers/AppPlanDeserializerTests.cs new file mode 100644 index 000000000..e398dd008 --- /dev/null +++ b/src/Promitor.Scraper.Tests.Unit/Serialization/v1/Providers/AppPlanDeserializerTests.cs @@ -0,0 +1,44 @@ +using System.ComponentModel; +using Promitor.Core.Scraping.Configuration.Serialization; +using Promitor.Core.Scraping.Configuration.Serialization.v1.Model; +using Promitor.Core.Scraping.Configuration.Serialization.v1.Model.ResourceTypes; +using Promitor.Core.Scraping.Configuration.Serialization.v1.Providers; +using Xunit; + +namespace Promitor.Scraper.Tests.Unit.Serialization.v1.Providers +{ + [Category("Unit")] + public class AppPlanDeserializerTests : ResourceDeserializerTest + { + private readonly AppPlanDeserializer _deserializer; + + public AppPlanDeserializerTests() + { + _deserializer = new AppPlanDeserializer(Logger); + } + + [Fact] + public void Deserialize_AppPlanNameSupplied_SetsName() + { + YamlAssert.PropertySet( + _deserializer, + "appPlanName: promitor-app-plan", + "promitor-app-plan", + r => r.AppPlanName); + } + + [Fact] + public void Deserialize_AppPlanNameNotSupplied_Null() + { + YamlAssert.PropertyNull( + _deserializer, + "resourceGroupName: promitor-group", + r => r.AppPlanName); + } + + protected override IDeserializer CreateDeserializer() + { + return new AppPlanDeserializer(Logger); + } + } +} diff --git a/src/Promitor.Scraper.Tests.Unit/Serialization/v1/Providers/FunctionAppDeserializerTests.cs b/src/Promitor.Scraper.Tests.Unit/Serialization/v1/Providers/FunctionAppDeserializerTests.cs new file mode 100644 index 000000000..d3ba1ae14 --- /dev/null +++ b/src/Promitor.Scraper.Tests.Unit/Serialization/v1/Providers/FunctionAppDeserializerTests.cs @@ -0,0 +1,63 @@ +using System.ComponentModel; +using Promitor.Core.Scraping.Configuration.Serialization; +using Promitor.Core.Scraping.Configuration.Serialization.v1.Model; +using Promitor.Core.Scraping.Configuration.Serialization.v1.Model.ResourceTypes; +using Promitor.Core.Scraping.Configuration.Serialization.v1.Providers; +using Xunit; + +namespace Promitor.Scraper.Tests.Unit.Serialization.v1.Providers +{ + [Category("Unit")] + public class FunctionAppDeserializerTests : ResourceDeserializerTest + { + private readonly FunctionAppDeserializer _deserializer; + + public FunctionAppDeserializerTests() + { + _deserializer = new FunctionAppDeserializer(Logger); + } + + [Fact] + public void Deserialize_FunctionAppNameSupplied_SetsName() + { + YamlAssert.PropertySet( + _deserializer, + "functionAppName: promitor-function-app", + "promitor-function-app", + r => r.FunctionAppName); + } + + [Fact] + public void Deserialize_FunctionAppNameNotSupplied_Null() + { + YamlAssert.PropertyNull( + _deserializer, + "resourceGroupName: promitor-group", + r => r.FunctionAppName); + } + + [Fact] + public void Deserialize_SlotNameSupplied_SetsName() + { + YamlAssert.PropertySet( + _deserializer, + "slotName: staging", + "staging", + r => r.SlotName); + } + + [Fact] + public void Deserialize_SlotNameNotSupplied_Null() + { + YamlAssert.PropertyNull( + _deserializer, + "resourceGroupName: promitor-group", + r => r.SlotName); + } + + protected override IDeserializer CreateDeserializer() + { + return new FunctionAppDeserializer(Logger); + } + } +} diff --git a/src/Promitor.Scraper.Tests.Unit/Serialization/v1/Providers/WebAppDeserializerTests.cs b/src/Promitor.Scraper.Tests.Unit/Serialization/v1/Providers/WebAppDeserializerTests.cs new file mode 100644 index 000000000..143afedb8 --- /dev/null +++ b/src/Promitor.Scraper.Tests.Unit/Serialization/v1/Providers/WebAppDeserializerTests.cs @@ -0,0 +1,63 @@ +using System.ComponentModel; +using Promitor.Core.Scraping.Configuration.Serialization; +using Promitor.Core.Scraping.Configuration.Serialization.v1.Model; +using Promitor.Core.Scraping.Configuration.Serialization.v1.Model.ResourceTypes; +using Promitor.Core.Scraping.Configuration.Serialization.v1.Providers; +using Xunit; + +namespace Promitor.Scraper.Tests.Unit.Serialization.v1.Providers +{ + [Category("Unit")] + public class WebAppDeserializerTests : ResourceDeserializerTest + { + private readonly WebAppDeserializer _deserializer; + + public WebAppDeserializerTests() + { + _deserializer = new WebAppDeserializer(Logger); + } + + [Fact] + public void Deserialize_WebAppNameSupplied_SetsName() + { + YamlAssert.PropertySet( + _deserializer, + "webAppName: promitor-web-app", + "promitor-web-app", + r => r.WebAppName); + } + + [Fact] + public void Deserialize_WebAppNameNotSupplied_Null() + { + YamlAssert.PropertyNull( + _deserializer, + "resourceGroupName: promitor-group", + r => r.WebAppName); + } + + [Fact] + public void Deserialize_SlotNameSupplied_SetsName() + { + YamlAssert.PropertySet( + _deserializer, + "slotName: staging", + "staging", + r => r.SlotName); + } + + [Fact] + public void Deserialize_SlotNameNotSupplied_Null() + { + YamlAssert.PropertyNull( + _deserializer, + "resourceGroupName: promitor-group", + r => r.SlotName); + } + + protected override IDeserializer CreateDeserializer() + { + return new WebAppDeserializer(Logger); + } + } +} diff --git a/src/Promitor.Scraper.Tests.Unit/Validation/Metrics/ResourceTypes/AppPlanMetricsDeclarationValidationStepsTests.cs b/src/Promitor.Scraper.Tests.Unit/Validation/Metrics/ResourceTypes/AppPlanMetricsDeclarationValidationStepsTests.cs new file mode 100644 index 000000000..799b91c92 --- /dev/null +++ b/src/Promitor.Scraper.Tests.Unit/Validation/Metrics/ResourceTypes/AppPlanMetricsDeclarationValidationStepsTests.cs @@ -0,0 +1,107 @@ +using System.ComponentModel; +using AutoMapper; +using Promitor.Core.Scraping.Configuration.Serialization.v1.Mapping; +using Promitor.Scraper.Host.Validation.Steps; +using Promitor.Scraper.Tests.Unit.Builders.Metrics.v1; +using Promitor.Scraper.Tests.Unit.Stubs; +using Xunit; + +namespace Promitor.Scraper.Tests.Unit.Validation.Metrics.ResourceTypes +{ + [Category("Unit")] + public class AppPlanMetricsDeclarationValidationStepsTests + { + private readonly IMapper _mapper; + + public AppPlanMetricsDeclarationValidationStepsTests() + { + var config = new MapperConfiguration(c => c.AddProfile()); + _mapper = config.CreateMapper(); + } + + [Fact] + public void AppPlanMetricsDeclaration_DeclarationWithoutAzureMetricName_Succeeds() + { + // Arrange + var rawDeclaration = MetricsDeclarationBuilder.WithMetadata() + .WithAppPlanMetric(azureMetricName: string.Empty) + .Build(_mapper); + var metricsDeclarationProvider = new MetricsDeclarationProviderStub(rawDeclaration, _mapper); + + // Act + var scrapingScheduleValidationStep = new MetricsDeclarationValidationStep(metricsDeclarationProvider); + var validationResult = scrapingScheduleValidationStep.Run(); + + // Assert + Assert.False(validationResult.IsSuccessful, "Validation is successful"); + } + + [Fact] + public void AppPlanMetricsDeclaration_DeclarationWithoutMetricDescription_Succeeded() + { + // Arrange + var rawDeclaration = MetricsDeclarationBuilder.WithMetadata() + .WithAppPlanMetric(metricDescription: string.Empty) + .Build(_mapper); + var metricsDeclarationProvider = new MetricsDeclarationProviderStub(rawDeclaration, _mapper); + + // Act + var scrapingScheduleValidationStep = new MetricsDeclarationValidationStep(metricsDeclarationProvider); + var validationResult = scrapingScheduleValidationStep.Run(); + + // Assert + Assert.True(validationResult.IsSuccessful, "Validation was not successful"); + } + + [Fact] + public void AppPlanMetricsDeclaration_DeclarationWithoutMetricName_Fails() + { + // Arrange + var rawDeclaration = MetricsDeclarationBuilder.WithMetadata() + .WithAppPlanMetric(string.Empty) + .Build(_mapper); + var metricsDeclarationProvider = new MetricsDeclarationProviderStub(rawDeclaration, _mapper); + + // Act + var scrapingScheduleValidationStep = new MetricsDeclarationValidationStep(metricsDeclarationProvider); + var validationResult = scrapingScheduleValidationStep.Run(); + + // Assert + Assert.False(validationResult.IsSuccessful, "Validation is successful"); + } + + [Fact] + public void AppPlanMetricsDeclaration_DeclarationWithoutAppPlanName_Fails() + { + // Arrange + var rawDeclaration = MetricsDeclarationBuilder.WithMetadata() + .WithAppPlanMetric(appPlanName: string.Empty) + .Build(_mapper); + var metricsDeclarationProvider = new MetricsDeclarationProviderStub(rawDeclaration, _mapper); + + // Act + var scrapingScheduleValidationStep = new MetricsDeclarationValidationStep(metricsDeclarationProvider); + var validationResult = scrapingScheduleValidationStep.Run(); + + // Assert + Assert.False(validationResult.IsSuccessful, "Validation is successful"); + } + + [Fact] + public void AppPlanMetricsDeclaration_ValidDeclaration_Succeeds() + { + // Arrange + var rawMetricsDeclaration = MetricsDeclarationBuilder.WithMetadata() + .WithAppPlanMetric() + .Build(_mapper); + var metricsDeclarationProvider = new MetricsDeclarationProviderStub(rawMetricsDeclaration, _mapper); + + // Act + var scrapingScheduleValidationStep = new MetricsDeclarationValidationStep(metricsDeclarationProvider); + var validationResult = scrapingScheduleValidationStep.Run(); + + // Assert + Assert.True(validationResult.IsSuccessful, "Validation was not successful"); + } + } +} \ No newline at end of file diff --git a/src/Promitor.Scraper.Tests.Unit/Validation/Metrics/ResourceTypes/FunctionAppMetricsDeclarationValidationStepsTests.cs b/src/Promitor.Scraper.Tests.Unit/Validation/Metrics/ResourceTypes/FunctionAppMetricsDeclarationValidationStepsTests.cs new file mode 100644 index 000000000..623664137 --- /dev/null +++ b/src/Promitor.Scraper.Tests.Unit/Validation/Metrics/ResourceTypes/FunctionAppMetricsDeclarationValidationStepsTests.cs @@ -0,0 +1,107 @@ +using System.ComponentModel; +using AutoMapper; +using Promitor.Core.Scraping.Configuration.Serialization.v1.Mapping; +using Promitor.Scraper.Host.Validation.Steps; +using Promitor.Scraper.Tests.Unit.Builders.Metrics.v1; +using Promitor.Scraper.Tests.Unit.Stubs; +using Xunit; + +namespace Promitor.Scraper.Tests.Unit.Validation.Metrics.ResourceTypes +{ + [Category("Unit")] + public class FunctionAppMetricsDeclarationValidationStepsTests + { + private readonly IMapper _mapper; + + public FunctionAppMetricsDeclarationValidationStepsTests() + { + var config = new MapperConfiguration(c => c.AddProfile()); + _mapper = config.CreateMapper(); + } + + [Fact] + public void FunctionAppMetricsDeclaration_DeclarationWithoutAzureMetricName_Succeeds() + { + // Arrange + var rawDeclaration = MetricsDeclarationBuilder.WithMetadata() + .WithFunctionAppMetric(azureMetricName: string.Empty) + .Build(_mapper); + var metricsDeclarationProvider = new MetricsDeclarationProviderStub(rawDeclaration, _mapper); + + // Act + var scrapingScheduleValidationStep = new MetricsDeclarationValidationStep(metricsDeclarationProvider); + var validationResult = scrapingScheduleValidationStep.Run(); + + // Assert + Assert.False(validationResult.IsSuccessful, "Validation is successful"); + } + + [Fact] + public void FunctionAppMetricsDeclaration_DeclarationWithoutMetricDescription_Succeeded() + { + // Arrange + var rawDeclaration = MetricsDeclarationBuilder.WithMetadata() + .WithFunctionAppMetric(metricDescription: string.Empty) + .Build(_mapper); + var metricsDeclarationProvider = new MetricsDeclarationProviderStub(rawDeclaration, _mapper); + + // Act + var scrapingScheduleValidationStep = new MetricsDeclarationValidationStep(metricsDeclarationProvider); + var validationResult = scrapingScheduleValidationStep.Run(); + + // Assert + Assert.True(validationResult.IsSuccessful, "Validation was not successful"); + } + + [Fact] + public void FunctionAppMetricsDeclaration_DeclarationWithoutMetricName_Fails() + { + // Arrange + var rawDeclaration = MetricsDeclarationBuilder.WithMetadata() + .WithFunctionAppMetric(string.Empty) + .Build(_mapper); + var metricsDeclarationProvider = new MetricsDeclarationProviderStub(rawDeclaration, _mapper); + + // Act + var scrapingScheduleValidationStep = new MetricsDeclarationValidationStep(metricsDeclarationProvider); + var validationResult = scrapingScheduleValidationStep.Run(); + + // Assert + Assert.False(validationResult.IsSuccessful, "Validation is successful"); + } + + [Fact] + public void FunctionAppMetricsDeclaration_DeclarationWithoutFunctionAppName_Fails() + { + // Arrange + var rawDeclaration = MetricsDeclarationBuilder.WithMetadata() + .WithFunctionAppMetric(functionAppName: string.Empty) + .Build(_mapper); + var metricsDeclarationProvider = new MetricsDeclarationProviderStub(rawDeclaration, _mapper); + + // Act + var scrapingScheduleValidationStep = new MetricsDeclarationValidationStep(metricsDeclarationProvider); + var validationResult = scrapingScheduleValidationStep.Run(); + + // Assert + Assert.False(validationResult.IsSuccessful, "Validation is successful"); + } + + [Fact] + public void FunctionAppMetricsDeclaration_ValidDeclaration_Succeeds() + { + // Arrange + var rawMetricsDeclaration = MetricsDeclarationBuilder.WithMetadata() + .WithFunctionAppMetric() + .Build(_mapper); + var metricsDeclarationProvider = new MetricsDeclarationProviderStub(rawMetricsDeclaration, _mapper); + + // Act + var scrapingScheduleValidationStep = new MetricsDeclarationValidationStep(metricsDeclarationProvider); + var validationResult = scrapingScheduleValidationStep.Run(); + + // Assert + Assert.True(validationResult.IsSuccessful, "Validation was not successful"); + } + } +} \ No newline at end of file diff --git a/src/Promitor.Scraper.Tests.Unit/Validation/Metrics/ResourceTypes/WebAppMetricsDeclarationValidationStepsTests.cs b/src/Promitor.Scraper.Tests.Unit/Validation/Metrics/ResourceTypes/WebAppMetricsDeclarationValidationStepsTests.cs new file mode 100644 index 000000000..f5e8fc3fa --- /dev/null +++ b/src/Promitor.Scraper.Tests.Unit/Validation/Metrics/ResourceTypes/WebAppMetricsDeclarationValidationStepsTests.cs @@ -0,0 +1,124 @@ +using System.ComponentModel; +using AutoMapper; +using Promitor.Core.Scraping.Configuration.Serialization.v1.Mapping; +using Promitor.Scraper.Host.Validation.Steps; +using Promitor.Scraper.Tests.Unit.Builders.Metrics.v1; +using Promitor.Scraper.Tests.Unit.Stubs; +using Xunit; + +namespace Promitor.Scraper.Tests.Unit.Validation.Metrics.ResourceTypes +{ + [Category("Unit")] + public class WebAppMetricsDeclarationValidationStepsTests + { + private readonly IMapper _mapper; + + public WebAppMetricsDeclarationValidationStepsTests() + { + var config = new MapperConfiguration(c => c.AddProfile()); + _mapper = config.CreateMapper(); + } + + [Fact] + public void WebAppMetricsDeclaration_DeclarationWithoutAzureMetricName_Succeeds() + { + // Arrange + var rawDeclaration = MetricsDeclarationBuilder.WithMetadata() + .WithWebAppMetric(azureMetricName: string.Empty) + .Build(_mapper); + var metricsDeclarationProvider = new MetricsDeclarationProviderStub(rawDeclaration, _mapper); + + // Act + var scrapingScheduleValidationStep = new MetricsDeclarationValidationStep(metricsDeclarationProvider); + var validationResult = scrapingScheduleValidationStep.Run(); + + // Assert + Assert.False(validationResult.IsSuccessful, "Validation is successful"); + } + + [Fact] + public void WebAppMetricsDeclaration_DeclarationWithoutMetricDescription_Succeeded() + { + // Arrange + var rawDeclaration = MetricsDeclarationBuilder.WithMetadata() + .WithWebAppMetric(metricDescription: string.Empty) + .Build(_mapper); + var metricsDeclarationProvider = new MetricsDeclarationProviderStub(rawDeclaration, _mapper); + + // Act + var scrapingScheduleValidationStep = new MetricsDeclarationValidationStep(metricsDeclarationProvider); + var validationResult = scrapingScheduleValidationStep.Run(); + + // Assert + Assert.True(validationResult.IsSuccessful, "Validation was not successful"); + } + + [Fact] + public void WebAppMetricsDeclaration_DeclarationWithoutMetricName_Fails() + { + // Arrange + var rawDeclaration = MetricsDeclarationBuilder.WithMetadata() + .WithWebAppMetric(string.Empty) + .Build(_mapper); + var metricsDeclarationProvider = new MetricsDeclarationProviderStub(rawDeclaration, _mapper); + + // Act + var scrapingScheduleValidationStep = new MetricsDeclarationValidationStep(metricsDeclarationProvider); + var validationResult = scrapingScheduleValidationStep.Run(); + + // Assert + Assert.False(validationResult.IsSuccessful, "Validation is successful"); + } + + [Fact] + public void WebAppMetricsDeclaration_DeclarationWithoutWebAppName_Fails() + { + // Arrange + var rawDeclaration = MetricsDeclarationBuilder.WithMetadata() + .WithWebAppMetric(webAppName: string.Empty) + .Build(_mapper); + var metricsDeclarationProvider = new MetricsDeclarationProviderStub(rawDeclaration, _mapper); + + // Act + var scrapingScheduleValidationStep = new MetricsDeclarationValidationStep(metricsDeclarationProvider); + var validationResult = scrapingScheduleValidationStep.Run(); + + // Assert + Assert.False(validationResult.IsSuccessful, "Validation is successful"); + } + + [Fact] + public void WebAppMetricsDeclaration_DeclarationWithoutSlotName_Succeeds() + { + // Arrange + var rawDeclaration = MetricsDeclarationBuilder.WithMetadata() + .WithWebAppMetric(slotName: string.Empty) + .Build(_mapper); + var metricsDeclarationProvider = new MetricsDeclarationProviderStub(rawDeclaration, _mapper); + + // Act + var scrapingScheduleValidationStep = new MetricsDeclarationValidationStep(metricsDeclarationProvider); + var validationResult = scrapingScheduleValidationStep.Run(); + + // Assert + Assert.True(validationResult.IsSuccessful, "Validation was not successful"); + } + + [Fact] + public void WebAppMetricsDeclaration_ValidDeclaration_Succeeds() + { + // Arrange + var rawMetricsDeclaration = MetricsDeclarationBuilder.WithMetadata() + .WithWebAppMetric() + .Build(_mapper); + var metricsDeclarationProvider = new MetricsDeclarationProviderStub(rawMetricsDeclaration, _mapper); + + // Act + var scrapingScheduleValidationStep = new MetricsDeclarationValidationStep(metricsDeclarationProvider); + var validationResult = scrapingScheduleValidationStep.Run(); + + // Assert + Assert.True(validationResult.IsSuccessful, "Validation was not successful"); + } + } +} \ No newline at end of file diff --git a/src/metric-config.yaml b/src/metric-config.yaml index 2a6aa3830..2158afa0f 100644 --- a/src/metric-config.yaml +++ b/src/metric-config.yaml @@ -95,4 +95,40 @@ metrics: type: Average resources: - serverName: promitor - databaseName: promitor-db-1 \ No newline at end of file + databaseName: promitor-db-1 + - name: azure_web_app_cpu + description: "Amount of CPU time used for an Azure Web App" + resourceType: WebApp + azureMetricConfiguration: + metricName: CpuTime + aggregation: + type: Total + resources: + - webAppName: promitor-web-app + resourceGroupName: promitor-sources + - webAppName: promitor-web-app + resourceGroupName: promitor-sources + slotName: staging + - name: azure_function_requests + description: "Amount of requests for an Azure Function App" + resourceType: FunctionApp + azureMetricConfiguration: + metricName: Requests + aggregation: + type: Total + resources: + - functionAppName: promitor-function-app + resourceGroupName: promitor-sources + - functionAppName: promitor-function-app + resourceGroupName: promitor-sources + slotName: staging + - name: azure_app_plan_percentage_cpu + description: "Average percentage of memory usage on an Azure App Plan" + resourceType: AppPlan + azureMetricConfiguration: + metricName: MemoryPercentage + aggregation: + type: Average + resources: + - appPlanName: promitor-app-plan + resourceGroupName: promitor-sources \ No newline at end of file