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

feat: POC of adding subscription_name to metric labels #2279

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using System.Collections.Generic;
using System.Net;
using System.Threading.Tasks;
using GuardNet;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Promitor.Agents.ResourceDiscovery.Graph.Exceptions;
using Promitor.Agents.ResourceDiscovery.Graph.Repositories.Interfaces;
using Promitor.Agents.ResourceDiscovery.Scheduling;
using Promitor.Core.Contracts;

namespace Promitor.Agents.ResourceDiscovery.Controllers.v2
{
/// <summary>
/// API endpoint to list Azure subscriptions
/// </summary>
[ApiController]
[Route("api/v2/subscriptions")]
public class SubscriptionsV2Controller : ControllerBase
{
private readonly JsonSerializerSettings _serializerSettings;

/// <summary>
/// Initializes a new instance of the <see cref="SubscriptionsV2Controller"/> class.
/// </summary>
public SubscriptionsV2Controller(IAzureResourceRepository azureResourceRepository)
{
Guard.NotNull(azureResourceRepository, nameof(azureResourceRepository));

_serializerSettings = new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore,
TypeNameHandling = TypeNameHandling.Objects
};
_serializerSettings.Converters.Add(new StringEnumConverter());
}

/// <summary>
/// List subscriptions
/// </summary>
[HttpGet(Name = "SubscriptionsV2_Get")]
[ProducesResponseType(typeof(List<AzureSubscription>), StatusCodes.Status200OK)]
public IActionResult Get()
{
var serializedResources = JsonConvert.SerializeObject(AzureSubscriptionDiscoveryBackgroundJob.CurrentSubscriptions, _serializerSettings);

var response = Content(serializedResources, "application/json");
response.StatusCode = (int) HttpStatusCode.OK;

return response;
}
}
}
15 changes: 15 additions & 0 deletions src/Promitor.Agents.ResourceDiscovery/Docs/Open-Api.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ public class AzureSubscriptionDiscoveryBackgroundJob : DiscoveryBackgroundJob, I
{
public const string MetricName = "promitor_azure_landscape_subscription_info";
public const string MetricDescription = "Provides information concerning the Azure subscriptions in the landscape that Promitor has access to.";


public static List<AzureSubscription> CurrentSubscriptions { get; private set; } = new List<AzureSubscription>();

public AzureSubscriptionDiscoveryBackgroundJob(string jobName, IAzureResourceRepository azureResourceRepository, ISystemMetricsPublisher systemMetricsPublisher, ILogger<AzureSubscriptionDiscoveryBackgroundJob> logger)
: base(azureResourceRepository, systemMetricsPublisher, logger)
{
Expand All @@ -32,6 +34,8 @@ public async Task ExecuteAsync(CancellationToken cancellationToken)

// Discover Azure subscriptions

var newList = new List<AzureSubscription>();

PagedPayload<AzureSubscriptionInformation> discoveredLandscape;
var currentPage = 1;
do
Expand All @@ -42,12 +46,14 @@ public async Task ExecuteAsync(CancellationToken cancellationToken)
foreach (var discoveredLandscapeItem in discoveredLandscape.Result)
{
ReportDiscoveredAzureInfo(discoveredLandscapeItem);
newList.Add(new AzureSubscription { Id = discoveredLandscapeItem.Id, Name = discoveredLandscapeItem.Name });
}

currentPage++;
}
while (discoveredLandscape.HasMore);

CurrentSubscriptions = newList;
Logger.LogTrace("Azure subscriptions discovered...");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@ public interface IResourceDiscoveryRepository
{
Task<List<AzureResourceDefinition>> GetResourceDiscoveryGroupAsync(string resourceDiscoveryGroupName);
Task<AgentHealthReport> GetHealthAsync();
Task<List<AzureSubscription>> GetSubscriptionsAsync();
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
Expand Down Expand Up @@ -49,6 +50,15 @@ public async Task<AgentHealthReport> GetHealthAsync()
return healthReport;
}

public async Task<List<AzureSubscription>> GetSubscriptionsAsync()
{
var uri = $"api/v2/subscriptions";
var rawResponse = await SendGetRequestAsync(uri);

var foundSubscriptions = JsonConvert.DeserializeObject<List<AzureSubscription>>(rawResponse, _serializerSettings);
return foundSubscriptions;
}

private async Task<string> SendGetRequestAsync(string uriPath)
{
var url = ComposeUrl(uriPath);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,10 @@ public async Task<AgentHealthReport> GetHealthAsync()
{
return await _resourceDiscoveryClient.GetHealthAsync();
}

public async Task<List<AzureSubscription>> GetSubscriptionsAsync()
{
return await _resourceDiscoveryClient.GetSubscriptionsAsync();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ public class StubResourceDiscoveryRepository : IResourceDiscoveryRepository
{
private static readonly List<AzureResourceDefinition> emptyResourceDefinitions = new List<AzureResourceDefinition>();
private static readonly AgentHealthReport healthReport = new AgentHealthReport();
private static readonly List<AzureSubscription> emptySubscriptions = new List<AzureSubscription>();

public Task<List<AzureResourceDefinition>> GetResourceDiscoveryGroupAsync(string resourceDiscoveryGroupName)
{
Expand All @@ -20,5 +21,10 @@ public Task<AgentHealthReport> GetHealthAsync()
{
return Task.FromResult(healthReport);
}

public Task<List<AzureSubscription>> GetSubscriptionsAsync()
{
return Task.FromResult(emptySubscriptions);
}
}
}
12 changes: 12 additions & 0 deletions src/Promitor.Agents.Scraper/Scheduling/ResourcesScrapingJob.cs
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,8 @@ public async Task ExecuteAsync(CancellationToken cancellationToken)

try
{
await UpdateSubscriptionMappings(cancellationToken);

var scrapeDefinitions = await GetAllScrapeDefinitions(cancellationToken);

await ScrapeMetrics(scrapeDefinitions, cancellationToken);
Expand All @@ -150,6 +152,16 @@ public async Task ExecuteAsync(CancellationToken cancellationToken)
}
}

private async Task UpdateSubscriptionMappings(CancellationToken cancellationToken)
{
var list = await _resourceDiscoveryRepository.GetSubscriptionsAsync();
foreach (var sub in list)
{
_azureScrapingSystemMetricsPublisher.RegisterSubscriptionMapping(sub.Id, sub.Name);
_metricSinkWriter.RegisterSubscriptionMapping(sub.Id, sub.Name);
}
}

private async Task<IReadOnlyCollection<ScrapeDefinition<IAzureResourceDefinition>>> GetAllScrapeDefinitions(CancellationToken cancellationToken)
{
var scrapeDefinitions = new ConcurrentBag<ScrapeDefinition<IAzureResourceDefinition>>();
Expand Down
8 changes: 8 additions & 0 deletions src/Promitor.Core.Contracts/AzureSubscription.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace Promitor.Core.Contracts
{
public class AzureSubscription
{
public string Name { get; set; }
public string Id { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace Promitor.Core.Metrics.Interfaces
{
public interface IAzureScrapingSystemMetricsPublisher : ISystemMetricsPublisher
{
/// <summary>
/// Registers or updates a subscription ID to name mapping
/// </summary>
/// <param name="id">Subscription ID.</param>
/// <param name="name">Subscription name.</param>
void RegisterSubscriptionMapping(string id, string name);

/// <summary>
/// Sets a new value for a measurement on a gauge
/// </summary>
Expand Down
25 changes: 25 additions & 0 deletions src/Promitor.Core/Metrics/Sinks/MetricSinkWriter.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
Expand All @@ -10,6 +11,8 @@ namespace Promitor.Core.Metrics.Sinks
public class MetricSinkWriter
{
private readonly List<IMetricSink> _configuredSinks;
private readonly ConcurrentDictionary<string, string> _subscriptionMappings = new ConcurrentDictionary<string, string>();

private ILogger Logger { get; }

public List<MetricSinkType> EnabledMetricSinks { get; }
Expand All @@ -26,10 +29,16 @@ public MetricSinkWriter(IEnumerable<IMetricSink> configuredSinks, ILogger<Metric
this.EnabledMetricSinks = metricSinks.Select(x => x.Type).Distinct().ToList();
}

public void RegisterSubscriptionMapping(string id, string name)
{
_subscriptionMappings[id] = name;
}

public async Task ReportMetricAsync(string metricName, string metricDescription, ScrapeResult scrapedMetricResult)
{
Guard.NotNullOrWhitespace(metricName, nameof(metricName));
Guard.NotNull(scrapedMetricResult, nameof(scrapedMetricResult));
addSubscriptionNameLabel(scrapedMetricResult.Labels);

var reportTasks = new List<Task>();
foreach (var sink in _configuredSinks)
Expand Down Expand Up @@ -62,6 +71,7 @@ public async Task ReportMetricAsync(string metricName, string metricDescription,
{
Guard.NotNullOrWhitespace(metricName, nameof(metricName));
Guard.NotNull(metricLabels, nameof(metricLabels));
addSubscriptionNameLabel(metricLabels);

var reportTasks = new List<Task>();
foreach (var sink in _configuredSinks)
Expand All @@ -72,5 +82,20 @@ public async Task ReportMetricAsync(string metricName, string metricDescription,

await Task.WhenAll(reportTasks);
}

private void addSubscriptionNameLabel(Dictionary<string, string> labels)
{
if (!labels.ContainsKey("subscription_id")) return;

string nameValue;
if (_subscriptionMappings.TryGetValue(labels["subscription_id"], out nameValue))
{
labels["subscription_name"] = nameValue;
}
else
{
labels["subscription_name"] = "";
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using GuardNet;
Expand All @@ -14,6 +15,7 @@ public class AzureScrapingSystemMetricsPublisher : IAzureScrapingSystemMetricsPu
private readonly ISystemMetricsPublisher _systemMetricsPublisher;
private readonly IMetricsDeclarationProvider _metricsDeclarationProvider;
private readonly IOptionsMonitor<PrometheusScrapingEndpointSinkConfiguration> _prometheusConfiguration;
private readonly ConcurrentDictionary<string, string> _subscriptionMappings = new ConcurrentDictionary<string, string>();

public AzureScrapingSystemMetricsPublisher(IMetricsDeclarationProvider metricsDeclarationProvider, ISystemMetricsPublisher systemMetricsPublisher, IOptionsMonitor<PrometheusScrapingEndpointSinkConfiguration> prometheusConfiguration)
{
Expand All @@ -25,6 +27,16 @@ public AzureScrapingSystemMetricsPublisher(IMetricsDeclarationProvider metricsDe
_metricsDeclarationProvider = metricsDeclarationProvider;
}

/// <summary>
/// Registers or updates a subscription ID to name mapping
/// </summary>
/// <param name="id">Subscription ID.</param>
/// <param name="name">Subscription name.</param>
public void RegisterSubscriptionMapping(string id, string name)
{
_subscriptionMappings[id] = name;
}

/// <summary>
/// Sets a new value for a measurement on a gauge
/// </summary>
Expand All @@ -42,14 +54,33 @@ public async Task WriteGaugeMeasurementAsync(string name, string description, do
labels.Add("tenant_id", metricsDeclaration.AzureMetadata.TenantId);
}

addSubscriptionNameLabel(labels);

var orderedLabels = labels.OrderByDescending(kvp => kvp.Key).ToDictionary(kvp => kvp.Key, kvp => kvp.Value);

await _systemMetricsPublisher.WriteGaugeMeasurementAsync(name, description, value, orderedLabels, enableMetricTimestamps);
}

public async Task WriteGaugeMeasurementAsync(string name, string description, double value, Dictionary<string, string> labels, bool includeTimestamp)
{
addSubscriptionNameLabel(labels);

await _systemMetricsPublisher.WriteGaugeMeasurementAsync(name, description, value, labels, includeTimestamp);
}

private void addSubscriptionNameLabel(Dictionary<string, string> labels)
{
if (!labels.ContainsKey("subscription_id")) return;

string nameValue;
if (_subscriptionMappings.TryGetValue(labels["subscription_id"], out nameValue))
{
labels["subscription_name"] = nameValue;
}
else
{
labels["subscription_name"] = "";
}
}
}
}