diff --git a/changelog/content/experimental/unreleased.md b/changelog/content/experimental/unreleased.md index 927a807b3..3da923a6e 100644 --- a/changelog/content/experimental/unreleased.md +++ b/changelog/content/experimental/unreleased.md @@ -15,6 +15,7 @@ version: - {{% tag added %}} Support for scraping Azure Express Route circuits ([docs](https://promitor.io/configuration/v2.x/metrics/express-route-circuit) | [#1251](https://github.com/tomkerkhove/promitor/issues/1251) | Contributed by [@bluepixbe](https://github.com/bluepixbe) 🎉). - {{% tag added %}} Support for scraping Azure Application Gateway ([docs](https://promitor.io/configuration/v2.x/metrics/application-gateway) | [#1251](https://github.com/tomkerkhove/promitor/issues/313) | Contributed by [@bluepixbe](https://github.com/bluepixbe) 🎉). - {{% tag added %}} Support for scraping Azure Network Gateway ([docs](https://promitor.io/configuration/v2.x/metrics/network-gateway) | [#1264](https://github.com/tomkerkhove/promitor/issues/1264) | Contributed by [@bluepixbe](https://github.com/bluepixbe) 🎉). +- {{% tag added %}} Support for scraping Azure Kubernetes Service ([docs](https://promitor.io/configuration/v2.x/metrics/kubernetes) | [#333](https://github.com/tomkerkhove/promitor/issues/333) | Contributed by [@jkataja](https://github.com/jkataja) 🎉). - {{% tag added %}} New validation rule to ensure at least one resource or resource collection is configured to scrape - {{% tag added %}} Provide suggestions when unknown fields are found in the metrics config. ([#1105](https://github.com/tomkerkhove/promitor/issues/1105) | Contributed by [@adamconnelly](https://github.com/adamconnelly) 🎉). - {{% tag added %}} Add validation to ensure the scraping schedule is a valid Cron expression. ([#1103](https://github.com/tomkerkhove/promitor/issues/1103) | Contributed by [@adamconnelly](https://github.com/adamconnelly) 🎉). diff --git a/docs/configuration/v2.x/metrics/index.md b/docs/configuration/v2.x/metrics/index.md index af3c0b444..edd10d296 100644 --- a/docs/configuration/v2.x/metrics/index.md +++ b/docs/configuration/v2.x/metrics/index.md @@ -134,6 +134,7 @@ We also provide a simplified way to scrape the following Azure resources: - [Azure IoT Hub](iot-hub) - [Azure IoT Hub Device Provisioning Service (DPS)](iot-hub-device-provisioning-service) - [Azure Key Vault](key-vault) +- [Azure Kubernetes Service](kubernetes) - [Azure Logic Apps](logic-apps) - [Azure Network Gateway](network-gateway) - [Azure Network Interface](network-interface) diff --git a/docs/configuration/v2.x/metrics/kubernetes.md b/docs/configuration/v2.x/metrics/kubernetes.md new file mode 100644 index 000000000..38e82c933 --- /dev/null +++ b/docs/configuration/v2.x/metrics/kubernetes.md @@ -0,0 +1,36 @@ +--- +layout: default +title: Azure Kubernetes Service Declaration +--- + +## Azure Kubernetes Service + +![Availability Badge](https://img.shields.io/badge/Available%20Starting-v2.0-green.svg)![Resource Discovery Support Badge](https://img.shields.io/badge/Support%20for%20Resource%20Discovery-Yes-green.svg) + +You can declare to scrape an Azure Kubernetes Service (AKS) +via the `KubernetesService` resource type. + +The following fields need to be provided: + +- `clusterName` - The name of the Azure Kubernetes Service + +All supported metrics are documented in the official [Azure Monitor documentation](https://docs.microsoft.com/en-us/azure/azure-monitor/platform/metrics-supported#microsoftcontainerservicemanagedclusters). + +Example: + +```yaml +name: azure_kubernetes_available_cpu_cores +description: "Available CPU cores in cluster" +resourceType: KubernetesService +azureMetricConfiguration: + metricName: kube_node_status_allocatable_cpu_cores + aggregation: + type: Average +resources: +- clusterName: promitor-aks +``` + + +[← back to metrics declarations](/configuration/v2.x/metrics)
+[← back to introduction](/) + diff --git a/docs/configuration/v2.x/resource-discovery.md b/docs/configuration/v2.x/resource-discovery.md index 3680bb2ff..3f981061b 100644 --- a/docs/configuration/v2.x/resource-discovery.md +++ b/docs/configuration/v2.x/resource-discovery.md @@ -101,6 +101,7 @@ Dynamic resource discovery is supported for the following scrapers: - [Azure IoT Hub](metrics/iot-hub) - [Azure IoT Hub Device Provisioning Service (DPS)](metrics/iot-hub-device-provisioning-service) - [Azure Key Vault](metrics/key-vault) +- [Azure Kubernetes Service](metrics/kubernetes) - [Azure Logic Apps](metrics/logic-apps) - [Azure Network Gateway](metrics/network-gateway) - [Azure Network Interface](metrics/network-interface) diff --git a/src/Promitor.Agents.ResourceDiscovery/Graph/ResourceDiscoveryFactory.cs b/src/Promitor.Agents.ResourceDiscovery/Graph/ResourceDiscoveryFactory.cs index 78a83a5cd..6a1fa5022 100644 --- a/src/Promitor.Agents.ResourceDiscovery/Graph/ResourceDiscoveryFactory.cs +++ b/src/Promitor.Agents.ResourceDiscovery/Graph/ResourceDiscoveryFactory.cs @@ -34,6 +34,8 @@ public static ResourceDiscoveryQuery UseResourceDiscoveryFor(ResourceType resour return new IoTHubDiscoveryQuery(); case ResourceType.KeyVault: return new KeyVaultDiscoveryQuery(); + case ResourceType.KubernetesService: + return new KubernetesServiceDiscoveryQuery(); case ResourceType.LogicApp: return new LogicAppDiscoveryQuery(); case ResourceType.NetworkGateway: diff --git a/src/Promitor.Agents.ResourceDiscovery/Graph/ResourceTypes/KubernetesServiceDiscoveryQuery.cs b/src/Promitor.Agents.ResourceDiscovery/Graph/ResourceTypes/KubernetesServiceDiscoveryQuery.cs new file mode 100644 index 000000000..56d20984a --- /dev/null +++ b/src/Promitor.Agents.ResourceDiscovery/Graph/ResourceTypes/KubernetesServiceDiscoveryQuery.cs @@ -0,0 +1,21 @@ +using GuardNet; +using Newtonsoft.Json.Linq; +using Promitor.Core.Contracts; +using Promitor.Core.Contracts.ResourceTypes; + +namespace Promitor.Agents.ResourceDiscovery.Graph.ResourceTypes +{ + public class KubernetesServiceDiscoveryQuery : ResourceDiscoveryQuery + { + public override string[] ResourceTypes => new[] { "Microsoft.ContainerService/managedClusters" }; + public override string[] ProjectedFieldNames => new[] { "subscriptionId", "resourceGroup", "type", "name" }; + + public override AzureResourceDefinition ParseResults(JToken resultRowEntry) + { + Guard.NotNull(resultRowEntry, nameof(resultRowEntry)); + + var resource = new KubernetesServiceResourceDefinition(resultRowEntry[0]?.ToString(), resultRowEntry[1]?.ToString(), resultRowEntry[3]?.ToString()); + return resource; + } + } +} diff --git a/src/Promitor.Agents.Scraper/Validation/Factories/MetricValidatorFactory.cs b/src/Promitor.Agents.Scraper/Validation/Factories/MetricValidatorFactory.cs index 65cba4146..3f25508c3 100644 --- a/src/Promitor.Agents.Scraper/Validation/Factories/MetricValidatorFactory.cs +++ b/src/Promitor.Agents.Scraper/Validation/Factories/MetricValidatorFactory.cs @@ -41,6 +41,8 @@ internal static IMetricValidator GetValidatorFor(ResourceType resourceType) return new IoTHubMetricValidator(); case ResourceType.KeyVault: return new KeyVaultMetricValidator(); + case ResourceType.KubernetesService: + return new KubernetesServiceMetricValidator(); case ResourceType.LogicApp: return new LogicAppMetricValidator(); case ResourceType.NetworkGateway: diff --git a/src/Promitor.Agents.Scraper/Validation/MetricDefinitions/ResourceTypes/KubernetesServiceMetricValidator.cs b/src/Promitor.Agents.Scraper/Validation/MetricDefinitions/ResourceTypes/KubernetesServiceMetricValidator.cs new file mode 100644 index 000000000..da96b90e7 --- /dev/null +++ b/src/Promitor.Agents.Scraper/Validation/MetricDefinitions/ResourceTypes/KubernetesServiceMetricValidator.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using System.Linq; +using GuardNet; +using Promitor.Core.Scraping.Configuration.Model.Metrics; +using Promitor.Agents.Scraper.Validation.MetricDefinitions.Interfaces; +using Promitor.Core.Contracts.ResourceTypes; + +namespace Promitor.Agents.Scraper.Validation.MetricDefinitions.ResourceTypes +{ + internal class KubernetesServiceMetricValidator : IMetricValidator + { + public IEnumerable Validate(MetricDefinition metricDefinition) + { + Guard.NotNull(metricDefinition, nameof(metricDefinition)); + + foreach (var resourceDefinition in metricDefinition.Resources.Cast()) + { + if (string.IsNullOrWhiteSpace(resourceDefinition.ClusterName)) + { + yield return "No Azure Kubernetes Service cluster name is configured"; + } + } + } + } +} \ No newline at end of file diff --git a/src/Promitor.Core.Contracts/ResourceType.cs b/src/Promitor.Core.Contracts/ResourceType.cs index 1b84d5383..950400757 100644 --- a/src/Promitor.Core.Contracts/ResourceType.cs +++ b/src/Promitor.Core.Contracts/ResourceType.cs @@ -31,6 +31,7 @@ public enum ResourceType EventHubs = 26, ExpressRouteCircuit = 27, ApplicationGateway = 28, - NetworkGateway = 29 + NetworkGateway = 29, + KubernetesService = 30 } } \ No newline at end of file diff --git a/src/Promitor.Core.Contracts/ResourceTypes/KubernetesServiceResourceDefinition.cs b/src/Promitor.Core.Contracts/ResourceTypes/KubernetesServiceResourceDefinition.cs new file mode 100644 index 000000000..3c04b533e --- /dev/null +++ b/src/Promitor.Core.Contracts/ResourceTypes/KubernetesServiceResourceDefinition.cs @@ -0,0 +1,13 @@ +namespace Promitor.Core.Contracts.ResourceTypes +{ + public class KubernetesServiceResourceDefinition : AzureResourceDefinition + { + public KubernetesServiceResourceDefinition(string subscriptionId, string resourceGroupName, string clusterName) + : base(ResourceType.KubernetesService, subscriptionId, resourceGroupName, clusterName) + { + ClusterName = clusterName; + } + + public string ClusterName { get; } + } +} \ 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 78a420b57..3123bda1f 100644 --- a/src/Promitor.Core.Scraping/Configuration/Serialization/v1/Core/AzureResourceDeserializerFactory.cs +++ b/src/Promitor.Core.Scraping/Configuration/Serialization/v1/Core/AzureResourceDeserializerFactory.cs @@ -66,6 +66,9 @@ public IDeserializer GetDeserializerFor(ResourceType case ResourceType.KeyVault: var keyVaultLogger = _loggerFactory.CreateLogger(); return new KeyVaultDeserializer(keyVaultLogger); + case ResourceType.KubernetesService: + var kubernetesServiceLogger = _loggerFactory.CreateLogger(); + return new KubernetesServiceDeserializer(kubernetesServiceLogger); case ResourceType.LogicApp: var logicAppLogger = _loggerFactory.CreateLogger(); return new LogicAppDeserializer(logicAppLogger); 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 9e45d04d2..cfbc962b3 100644 --- a/src/Promitor.Core.Scraping/Configuration/Serialization/v1/Mapping/V1MappingProfile.cs +++ b/src/Promitor.Core.Scraping/Configuration/Serialization/v1/Mapping/V1MappingProfile.cs @@ -38,6 +38,7 @@ public V1MappingProfile() CreateMap(); CreateMap(); CreateMap(); + CreateMap(); CreateMap(); CreateMap(); CreateMap(); @@ -74,6 +75,7 @@ public V1MappingProfile() .Include() .Include() .Include() + .Include() .Include() .Include() .Include() diff --git a/src/Promitor.Core.Scraping/Configuration/Serialization/v1/Model/ResourceTypes/KubernetesServiceResourceV1.cs b/src/Promitor.Core.Scraping/Configuration/Serialization/v1/Model/ResourceTypes/KubernetesServiceResourceV1.cs new file mode 100644 index 000000000..d66019ea8 --- /dev/null +++ b/src/Promitor.Core.Scraping/Configuration/Serialization/v1/Model/ResourceTypes/KubernetesServiceResourceV1.cs @@ -0,0 +1,13 @@ +namespace Promitor.Core.Scraping.Configuration.Serialization.v1.Model.ResourceTypes +{ + /// + /// Contains the configuration required to scrape an Azure Kubernetes Service. + /// + public class KubernetesServiceResourceV1 : AzureResourceDefinitionV1 + { + /// + /// The name of the Azure Kubernetes Service to get metrics for. + /// + public string ClusterName { get; set; } + } +} diff --git a/src/Promitor.Core.Scraping/Configuration/Serialization/v1/Providers/KubernetesServiceDeserializer.cs b/src/Promitor.Core.Scraping/Configuration/Serialization/v1/Providers/KubernetesServiceDeserializer.cs new file mode 100644 index 000000000..dc62faf7c --- /dev/null +++ b/src/Promitor.Core.Scraping/Configuration/Serialization/v1/Providers/KubernetesServiceDeserializer.cs @@ -0,0 +1,14 @@ +using Microsoft.Extensions.Logging; +using Promitor.Core.Scraping.Configuration.Serialization.v1.Model.ResourceTypes; + +namespace Promitor.Core.Scraping.Configuration.Serialization.v1.Providers +{ + public class KubernetesServiceDeserializer : ResourceDeserializer + { + public KubernetesServiceDeserializer(ILogger logger) : base(logger) + { + Map(resource => resource.ClusterName) + .IsRequired(); + } + } +} diff --git a/src/Promitor.Core.Scraping/Factories/MetricScraperFactory.cs b/src/Promitor.Core.Scraping/Factories/MetricScraperFactory.cs index bfb0fcf36..d62c83763 100644 --- a/src/Promitor.Core.Scraping/Factories/MetricScraperFactory.cs +++ b/src/Promitor.Core.Scraping/Factories/MetricScraperFactory.cs @@ -62,6 +62,8 @@ public IScraper CreateScraper(ResourceType metricDefin return new IoTHubScraper(scraperConfiguration); case ResourceType.KeyVault: return new KeyVaultScraper(scraperConfiguration); + case ResourceType.KubernetesService: + return new KubernetesServiceScraper(scraperConfiguration); case ResourceType.NetworkGateway: return new NetworkGatewayScraper(scraperConfiguration); case ResourceType.NetworkInterface: @@ -91,7 +93,7 @@ public IScraper CreateScraper(ResourceType metricDefin case ResourceType.WebApp: return new WebAppScraper(scraperConfiguration); default: - throw new ArgumentOutOfRangeException(); + throw new ArgumentOutOfRangeException("metricDefinitionResourceType", metricDefinitionResourceType, "Matching scraper not found"); } } } diff --git a/src/Promitor.Core.Scraping/ResourceTypes/KubernetesServiceScraper.cs b/src/Promitor.Core.Scraping/ResourceTypes/KubernetesServiceScraper.cs new file mode 100644 index 000000000..fc98e629c --- /dev/null +++ b/src/Promitor.Core.Scraping/ResourceTypes/KubernetesServiceScraper.cs @@ -0,0 +1,22 @@ +using Promitor.Core.Contracts; +using Promitor.Core.Contracts.ResourceTypes; +using Promitor.Core.Scraping.Configuration.Model.Metrics; + +namespace Promitor.Core.Scraping.ResourceTypes +{ + public class KubernetesServiceScraper : AzureMonitorScraper + { + private const string ResourceUriTemplate = "subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.ContainerService/managedClusters/{2}"; + + public KubernetesServiceScraper(ScraperConfiguration scraperConfiguration) : + base(scraperConfiguration) + { + } + + /// + protected override string BuildResourceUri(string subscriptionId, ScrapeDefinition scrapeDefinition, KubernetesServiceResourceDefinition resource) + { + return string.Format(ResourceUriTemplate, subscriptionId, scrapeDefinition.ResourceGroupName, resource.ClusterName); + } + } +} \ No newline at end of file diff --git a/src/Promitor.Tests.Unit/Builders/Metrics/v1/MetricsDeclarationBuilder.cs b/src/Promitor.Tests.Unit/Builders/Metrics/v1/MetricsDeclarationBuilder.cs index 7fc9d5452..5b9bea311 100644 --- a/src/Promitor.Tests.Unit/Builders/Metrics/v1/MetricsDeclarationBuilder.cs +++ b/src/Promitor.Tests.Unit/Builders/Metrics/v1/MetricsDeclarationBuilder.cs @@ -608,6 +608,23 @@ public MetricsDeclarationBuilder WithKeyVaultMetric(string metricName = "promito return this; } + public MetricsDeclarationBuilder WithKubernetesServiceMetric(string metricName = "promitor-aks", + string metricDescription = "Description for a metric", + string clusterName = "promitor-aks", + string azureMetricName = "kube_node_status_condition", + string resourceDiscoveryGroupName = "", + bool omitResource = false) + { + var resource = new KubernetesServiceResourceV1 + { + ClusterName = clusterName + }; + + CreateAndAddMetricDefinition(ResourceType.KubernetesService, metricName, metricDescription, resourceDiscoveryGroupName, omitResource, azureMetricName, resource); + + return this; + } + private void CreateAndAddMetricDefinition(ResourceType resourceType, string metricName, string metricDescription, string resourceDiscoveryGroupName, bool omitResource, string azureMetricName, AzureResourceDefinitionV1 resource, string metricDimension = null) { CreateAndAddMetricDefinition(resourceType, metricName, metricDescription, resourceDiscoveryGroupName, omitResource, azureMetricName, new List {resource}, metricDimension); diff --git a/src/Promitor.Tests.Unit/Serialization/v1/Providers/KubernetesServiceDeserializerTests.cs b/src/Promitor.Tests.Unit/Serialization/v1/Providers/KubernetesServiceDeserializerTests.cs new file mode 100644 index 000000000..8f9f25038 --- /dev/null +++ b/src/Promitor.Tests.Unit/Serialization/v1/Providers/KubernetesServiceDeserializerTests.cs @@ -0,0 +1,45 @@ +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 System.ComponentModel; +using Xunit; + +namespace Promitor.Tests.Unit.Serialization.v1.Providers +{ + [Category("Unit")] + public class KubernetesServiceDeserializerTests : ResourceDeserializerTest + { + private readonly KubernetesServiceDeserializer _deserializer; + + public KubernetesServiceDeserializerTests() + { + _deserializer = new KubernetesServiceDeserializer(Logger); + } + + [Fact] + public void Deserialize_ClusterNameSupplied_SetsClusterName() + { + const string clusterName = "promitor-aks"; + YamlAssert.PropertySet( + _deserializer, + $"clusterName: {clusterName}", + clusterName, + r => r.ClusterName); + } + + [Fact] + public void Deserialize_ClusterNameNotSupplied_Null() + { + YamlAssert.PropertyNull( + _deserializer, + "resourceGroupName: promitor-group", + r => r.ClusterName); + } + + protected override IDeserializer CreateDeserializer() + { + return new KubernetesServiceDeserializer(Logger); + } + } +} \ No newline at end of file diff --git a/src/Promitor.Tests.Unit/Validation/Scraper/Metrics/ResourceTypes/KubernetesServiceMetricsDeclarationValidationStepsTests.cs b/src/Promitor.Tests.Unit/Validation/Scraper/Metrics/ResourceTypes/KubernetesServiceMetricsDeclarationValidationStepsTests.cs new file mode 100644 index 000000000..d593057d9 --- /dev/null +++ b/src/Promitor.Tests.Unit/Validation/Scraper/Metrics/ResourceTypes/KubernetesServiceMetricsDeclarationValidationStepsTests.cs @@ -0,0 +1,132 @@ +using System.ComponentModel; +using Microsoft.Extensions.Logging.Abstractions; +using Promitor.Agents.Scraper.Validation.Steps; +using Promitor.Tests.Unit.Builders.Metrics.v1; +using Promitor.Tests.Unit.Stubs; +using Xunit; + +namespace Promitor.Tests.Unit.Validation.Scraper.Metrics.ResourceTypes +{ + [Category("Unit")] + public class KubernetesServiceMetricsDeclarationValidationStepsTests : MetricsDeclarationValidationStepsTests + { + [Fact] + public void KubernetesServiceMetricsDeclaration_DeclarationWithoutAzureMetricName_Fails() + { + // Arrange + var rawDeclaration = MetricsDeclarationBuilder.WithMetadata() + .WithKubernetesServiceMetric(azureMetricName: string.Empty) + .Build(Mapper); + var metricsDeclarationProvider = new MetricsDeclarationProviderStub(rawDeclaration, Mapper); + + // Act + var scrapingScheduleValidationStep = new MetricsDeclarationValidationStep(metricsDeclarationProvider, NullLogger.Instance); + var validationResult = scrapingScheduleValidationStep.Run(); + + // Assert + PromitorAssert.ValidationFailed(validationResult); + } + + [Fact] + public void KubernetesServiceMetricsDeclaration_DeclarationWithoutMetricDescription_Succeeded() + { + // Arrange + var rawDeclaration = MetricsDeclarationBuilder.WithMetadata() + .WithKubernetesServiceMetric(metricDescription: string.Empty) + .Build(Mapper); + var metricsDeclarationProvider = new MetricsDeclarationProviderStub(rawDeclaration, Mapper); + + // Act + var scrapingScheduleValidationStep = new MetricsDeclarationValidationStep(metricsDeclarationProvider, NullLogger.Instance); + var validationResult = scrapingScheduleValidationStep.Run(); + + // Assert + PromitorAssert.ValidationIsSuccessful(validationResult); + } + + [Fact] + public void KubernetesServiceMetricsDeclaration_DeclarationWithoutMetricName_Fails() + { + // Arrange + var rawDeclaration = MetricsDeclarationBuilder.WithMetadata() + .WithKubernetesServiceMetric(string.Empty) + .Build(Mapper); + var metricsDeclarationProvider = new MetricsDeclarationProviderStub(rawDeclaration, Mapper); + + // Act + var scrapingScheduleValidationStep = new MetricsDeclarationValidationStep(metricsDeclarationProvider, NullLogger.Instance); + var validationResult = scrapingScheduleValidationStep.Run(); + + // Assert + PromitorAssert.ValidationFailed(validationResult); + } + + [Fact] + public void KubernetesServiceMetricsDeclaration_DeclarationWithoutClusterName_Fails() + { + // Arrange + var rawDeclaration = MetricsDeclarationBuilder.WithMetadata() + .WithKubernetesServiceMetric(clusterName: string.Empty) + .Build(Mapper); + var metricsDeclarationProvider = new MetricsDeclarationProviderStub(rawDeclaration, Mapper); + + // Act + var scrapingScheduleValidationStep = new MetricsDeclarationValidationStep(metricsDeclarationProvider, NullLogger.Instance); + var validationResult = scrapingScheduleValidationStep.Run(); + + // Assert + PromitorAssert.ValidationFailed(validationResult); + } + + [Fact] + public void KubernetesServiceMetricsDeclaration_DeclarationWithoutResourceAndResourceDiscoveryGroupInfo_Fails() + { + // Arrange + var rawMetricsDeclaration = MetricsDeclarationBuilder.WithMetadata() + .WithKubernetesServiceMetric(omitResource: true) + .Build(Mapper); + var metricsDeclarationProvider = new MetricsDeclarationProviderStub(rawMetricsDeclaration, Mapper); + + // Act + var scrapingScheduleValidationStep = new MetricsDeclarationValidationStep(metricsDeclarationProvider, NullLogger.Instance); + var validationResult = scrapingScheduleValidationStep.Run(); + + // Assert + Assert.False(validationResult.IsSuccessful, "Validation was not successful"); + } + + [Fact] + public void KubernetesServiceMetricsDeclaration_DeclarationWithoutResourceButWithResourceDiscoveryGroupInfo_Succeeds() + { + // Arrange + var rawMetricsDeclaration = MetricsDeclarationBuilder.WithMetadata() + .WithKubernetesServiceMetric(omitResource: true, resourceDiscoveryGroupName:"sample-collection") + .Build(Mapper); + var metricsDeclarationProvider = new MetricsDeclarationProviderStub(rawMetricsDeclaration, Mapper); + + // Act + var scrapingScheduleValidationStep = new MetricsDeclarationValidationStep(metricsDeclarationProvider, NullLogger.Instance); + var validationResult = scrapingScheduleValidationStep.Run(); + + // Assert + PromitorAssert.ValidationIsSuccessful(validationResult); + } + + [Fact] + public void KubernetesServiceMetricsDeclaration_ValidDeclaration_Succeeds() + { + // Arrange + var rawMetricsDeclaration = MetricsDeclarationBuilder.WithMetadata() + .WithKubernetesServiceMetric() + .Build(Mapper); + var metricsDeclarationProvider = new MetricsDeclarationProviderStub(rawMetricsDeclaration, Mapper); + + // Act + var scrapingScheduleValidationStep = new MetricsDeclarationValidationStep(metricsDeclarationProvider, NullLogger.Instance); + var validationResult = scrapingScheduleValidationStep.Run(); + + // Assert + PromitorAssert.ValidationIsSuccessful(validationResult); + } + } +}