Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide support for scraping Azure Device Provisioning Service #1015

Merged
merged 8 commits into from
May 7, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog/content/experimental/unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ version: v1.6.0
- {{% tag added %}} Support for Kubernetes RBAC in Helm chart ([Helm Hub](https://hub.helm.sh/charts/promitor/promitor-agent-scraper) | [#951](https://github.com/tomkerkhove/promitor/issues/951))
- {{% tag added %}} Support for configuring Pod Security Policy in Helm Chart ([Helm Hub](https://hub.helm.sh/charts/promitor/promitor-agent-scraper) | [#952](https://github.com/tomkerkhove/promitor/issues/952))
- {{% tag added %}} Support for scraping Azure IoT Hub metrics ([docs](https://promitor.io/configuration/v1.x/metrics/iot-hub) | [#372](https://github.com/tomkerkhove/promitor/issues/372))
- {{% tag added %}} Support for scraping Azure IoT Hub Device Provisioning Service (DPS) metrics ([docs](https://promitor.io/configuration/v1.x/metrics/iot-hub-device-provisioning-service) | [#1014](https://github.com/tomkerkhove/promitor/issues/1014))
1 change: 1 addition & 0 deletions docs/configuration/v1.x/metrics/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ We also provide a simplified way to scrape the following Azure resources:
- [Azure Database for PostgreSQL](postgresql)
- [Azure Function App](function-app)
- [Azure IoT Hub](iot-hub)
- [Azure IoT Hub Device Provisioning Service (DPS)](iot-hub-device-provisioning-service)
- [Azure Network Interface](network-interface)
- [Azure Service Bus Queue](service-bus-queue)
- [Azure SQL Database](sql-database)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
layout: default
title: Azure IoT Hub Device Provisioning Service (DPS) Declaration
---

## Azure IoT Hub Device Provisioning Service (DPS) - ![Availability Badge](https://img.shields.io/badge/Available%20Starting-v1.6-green.svg)

You can declare to scrape an Azure IoT Hub Device Provisioning Service (DPS)
via the `DeviceProvisioningService` resource type.

The following fields need to be provided:

- `deviceProvisioningServiceName` - The name of the Azure IoT Hub Device Provisioning Service (DPS)

All supported metrics are documented in the official [Azure Monitor documentation](https://docs.microsoft.com/en-us/azure/azure-monitor/platform/metrics-supported#microsoftdevicesprovisioningservices).

Example:

```yaml
name: azure_dps_attestation_attempts
description: "The number of device attestations attempted"
resourceType: DeviceProvisioningService
azureMetricConfiguration:
metricName: AttestationAttempts
aggregation:
type: Total
resources:
- deviceProvisioningServiceName: promitor-1
- deviceProvisioningServiceName: promitor-2
```

<!-- markdownlint-disable MD033 -->
[&larr; back to metrics declarations](/configuration/v1.x/metrics)<br />
[&larr; back to introduction](/)
<!-- markdownlint-enable -->
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ internal static IMetricValidator GetValidatorFor(ResourceType resourceType)
return new SqlServerMetricValidator();
case ResourceType.IoTHub:
return new IoTHubMetricValidator();
case ResourceType.DeviceProvisioningService:
return new DeviceProvisioningServiceMetricValidator();
}

throw new ArgumentOutOfRangeException(nameof(resourceType), $"No validation rules are defined for metric type '{resourceType}'");
Expand Down
Original file line number Diff line number Diff line change
@@ -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.Agents.Scraper.Validation.MetricDefinitions.Interfaces;

namespace Promitor.Agents.Scraper.Validation.MetricDefinitions.ResourceTypes
{
internal class DeviceProvisioningServiceMetricValidator : IMetricValidator
{
public IEnumerable<string> Validate(MetricDefinition metricDefinition)
{
Guard.NotNull(metricDefinition, nameof(metricDefinition));

foreach (var resourceDefinition in metricDefinition.Resources.Cast<DeviceProvisioningServiceResourceDefinition>())
{
if (string.IsNullOrWhiteSpace(resourceDefinition.DeviceProvisioningServiceName))
{
yield return "No Azure IoT Hub Device Provisioning Service (DPS) name is configured";
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
namespace Promitor.Core.Scraping.Configuration.Model.Metrics.ResourceTypes
{
public class DeviceProvisioningServiceResourceDefinition : AzureResourceDefinition
{
public DeviceProvisioningServiceResourceDefinition(string subscriptionId, string resourceGroupName, string deviceProvisioningServiceName)
: base(ResourceType.DeviceProvisioningService, subscriptionId, resourceGroupName)
{
DeviceProvisioningServiceName = deviceProvisioningServiceName;
}

public string DeviceProvisioningServiceName { get; }

/// <inheritdoc />
public override string GetResourceName() => DeviceProvisioningServiceName;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,6 @@ public enum ResourceType
StorageAccount = 20,
ApiManagement = 21,
IoTHub = 22,
DeviceProvisioningService = 23
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ public IDeserializer<AzureResourceDefinitionV1> GetDeserializerFor(ResourceType
case ResourceType.IoTHub:
var iotHubLogger = _loggerFactory.CreateLogger<IoTHubDeserializer>();
return new IoTHubDeserializer(iotHubLogger);
case ResourceType.DeviceProvisioningService:
var deviceProvisioningServiceLogger = _loggerFactory.CreateLogger<DeviceProvisioningServiceDeserializer>();
return new DeviceProvisioningServiceDeserializer(deviceProvisioningServiceLogger);
default:
throw new ArgumentOutOfRangeException($"Resource Type {resourceType} not supported.");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ public V1MappingProfile()
CreateMap<BlobStorageResourceV1, BlobStorageResourceDefinition>();
CreateMap<FileStorageResourceV1, FileStorageResourceDefinition>();
CreateMap<IoTHubResourceV1, IoTHubResourceDefinition>();
CreateMap<DeviceProvisioningServiceResourceV1, DeviceProvisioningServiceResourceDefinition>();

CreateMap<MetricDefinitionV1, PrometheusMetricDefinition>();

Expand Down Expand Up @@ -72,7 +73,8 @@ public V1MappingProfile()
.Include<StorageAccountResourceV1, StorageAccountResourceDefinition>()
.Include<BlobStorageResourceV1, BlobStorageResourceDefinition>()
.Include<FileStorageResourceV1, FileStorageResourceDefinition>()
.Include<IoTHubResourceV1, IoTHubResourceDefinition>();
.Include<IoTHubResourceV1, IoTHubResourceDefinition>()
.Include<DeviceProvisioningServiceResourceV1, DeviceProvisioningServiceResourceDefinition>();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace Promitor.Core.Scraping.Configuration.Serialization.v1.Model.ResourceTypes
{
/// <summary>
/// Contains the configuration required to scrape an Azure IoT Hub Device Provisioning Service (DPS).
/// </summary>
public class DeviceProvisioningServiceResourceV1 : AzureResourceDefinitionV1
{
/// <summary>
/// The name of the Azure IoT Hub Device Provisioning Service (DPS) to get metrics for.
/// </summary>
public string DeviceProvisioningServiceName { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using Microsoft.Extensions.Logging;
using Promitor.Core.Scraping.Configuration.Serialization.v1.Model.ResourceTypes;

namespace Promitor.Core.Scraping.Configuration.Serialization.v1.Providers
{
public class DeviceProvisioningServiceDeserializer : ResourceDeserializer<DeviceProvisioningServiceResourceV1>
{
public DeviceProvisioningServiceDeserializer(ILogger<DeviceProvisioningServiceDeserializer> logger) : base(logger)
{
MapRequired(resource => resource.DeviceProvisioningServiceName);
}
}
}
26 changes: 26 additions & 0 deletions src/Promitor.Core.Scraping/DeviceProvisioningServiceScraper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using Promitor.Core.Scraping.Configuration.Model.Metrics;
using Promitor.Core.Scraping.Configuration.Model.Metrics.ResourceTypes;

// ReSharper disable All

namespace Promitor.Core.Scraping
{
/// <summary>
/// Scrapes an Azure IoT Hub Device Provisioning Service (DPS)
/// </summary>
public class DeviceProvisioningServiceScraper : AzureMonitorScraper<DeviceProvisioningServiceResourceDefinition>
{
private const string ResourceUriTemplate = "subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Devices/provisioningServices/{2}";

public DeviceProvisioningServiceScraper(ScraperConfiguration scraperConfiguration) :
base(scraperConfiguration)
{
}

/// <inheritdoc />
protected override string BuildResourceUri(string subscriptionId, ScrapeDefinition<IAzureResourceDefinition> scrapeDefinition, DeviceProvisioningServiceResourceDefinition resource)
{
return string.Format(ResourceUriTemplate, subscriptionId, scrapeDefinition.ResourceGroupName, resource.DeviceProvisioningServiceName);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ public IScraper<IAzureResourceDefinition> CreateScraper(ResourceType metricDefin
return new FileStorageScraper(scraperConfiguration);
case ResourceType.IoTHub:
return new IoTHubScraper(scraperConfiguration);
case ResourceType.DeviceProvisioningService:
return new DeviceProvisioningServiceScraper(scraperConfiguration);
default:
throw new ArgumentOutOfRangeException();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -603,5 +603,27 @@ public MetricsDeclarationBuilder WithSqlManagedInstanceMetric(

return this;
}

public MetricsDeclarationBuilder WithDeviceProvisioningServiceMetric(string metricName = "promitor-dps", string metricDescription = "Description for a metric", string deviceProvisioningServiceName = "promitor-dps", string azureMetricName = "AttestationAttempts")
{
var azureMetricConfiguration = CreateAzureMetricConfiguration(azureMetricName);
var resource = new DeviceProvisioningServiceResourceV1
{
DeviceProvisioningServiceName = deviceProvisioningServiceName
};

var metric = new MetricDefinitionV1
{
Name = metricName,
Description = metricDescription,
AzureMetricConfiguration = azureMetricConfiguration,
Resources = new List<AzureResourceDefinitionV1> { resource },
ResourceType = ResourceType.DeviceProvisioningService
};

_metrics.Add(metric);

return this;
}
}
}
Original file line number Diff line number Diff line change
@@ -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 DeviceProvisioningServiceDeserializerTests : ResourceDeserializerTest<DeviceProvisioningServiceDeserializer>
{
private readonly DeviceProvisioningServiceDeserializer _deserializer;

public DeviceProvisioningServiceDeserializerTests()
{
_deserializer = new DeviceProvisioningServiceDeserializer(Logger);
}

[Fact]
public void Deserialize_DeviceProvisioningServiceNameSupplied_SetsDeviceProvisioningServiceName()
{
const string deviceProvisioningServiceName = "promitor-dps";
YamlAssert.PropertySet<DeviceProvisioningServiceResourceV1, AzureResourceDefinitionV1, string>(
_deserializer,
$"deviceProvisioningServiceName: {deviceProvisioningServiceName}",
deviceProvisioningServiceName,
r => r.DeviceProvisioningServiceName);
}

[Fact]
public void Deserialize_DeviceProvisioningServiceNameNotSupplied_Null()
{
YamlAssert.PropertyNull<DeviceProvisioningServiceResourceV1, AzureResourceDefinitionV1>(
_deserializer,
"resourceGroupName: promitor-group",
r => r.DeviceProvisioningServiceName);
}

protected override IDeserializer<AzureResourceDefinitionV1> CreateDeserializer()
{
return new DeviceProvisioningServiceDeserializer(Logger);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
using System.ComponentModel;
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.Metrics.ResourceTypes
{
[Category("Unit")]
public class DeviceProvisioningServiceMetricsDeclarationValidationStepsTests : MetricsDeclarationValidationStepsTests
{
[Fact]
public void DeviceProvisioningServiceMetricsDeclaration_DeclarationWithoutAzureMetricName_Fails()
{
// Arrange
var rawDeclaration = MetricsDeclarationBuilder.WithMetadata()
.WithDeviceProvisioningServiceMetric(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 DeviceProvisioningServiceMetricsDeclaration_DeclarationWithoutMetricDescription_Succeeded()
{
// Arrange
var rawDeclaration = MetricsDeclarationBuilder.WithMetadata()
.WithDeviceProvisioningServiceMetric(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 DeviceProvisioningServiceMetricsDeclaration_DeclarationWithoutMetricName_Fails()
{
// Arrange
var rawDeclaration = MetricsDeclarationBuilder.WithMetadata()
.WithDeviceProvisioningServiceMetric(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 DeviceProvisioningServiceMetricsDeclaration_DeclarationWithoutDeviceProvisioningServiceName_Fails()
{
// Arrange
var rawDeclaration = MetricsDeclarationBuilder.WithMetadata()
.WithDeviceProvisioningServiceMetric(deviceProvisioningServiceName: 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 DeviceProvisioningServiceMetricsDeclaration_ValidDeclaration_Succeeds()
{
// Arrange
var rawMetricsDeclaration = MetricsDeclarationBuilder.WithMetadata()
.WithDeviceProvisioningServiceMetric()
.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");
}
}
}