Skip to content

Commit 0a279b7

Browse files
Better error management for Azure Service Bus usage collection (#5228)
* Minor cleanup * Better error message * Azure Service Bus namespace * Improved error management * Approvals * More approvals * More approvals * Revert formatting * Revert formatting
1 parent 99d8a55 commit 0a279b7

7 files changed

+68
-50
lines changed

src/ServiceControl.Transports.ASBS.Tests/ApprovalFiles/AzureQueryTests.TestConnectionWithEmptySettings.approved.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ ClientId is a required setting
44
ClientSecret is a required setting
55

66
Connection attempted with the following settings:
7-
ServiceBus name not set, defaulted to "testmenow"
7+
Azure Service Bus namespace not set, defaulted to "testmenow"
88
SubscriptionId not set, using the first found subscription
99
TenantId not set
1010
ClientId not set

src/ServiceControl.Transports.ASBS.Tests/ApprovalFiles/AzureQueryTests.TestConnectionWithInvalidClientIdSettings.approved.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ Connection test to AzureServiceBus failed:
22
ClientSecretCredential authentication failed: AADSTS700016: Application with identifier 'not valid' was not found in the directory
33

44
Connection attempted with the following settings:
5-
ServiceBus name not set, defaulted to "testmenow"
5+
Azure Service Bus namespace not set, defaulted to "testmenow"
66
SubscriptionId set
77
TenantId set
88
ClientId set

src/ServiceControl.Transports.ASBS.Tests/ApprovalFiles/AzureQueryTests.TestConnectionWithInvalidSubscriptionIdSettings.approved.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ Connection test to AzureServiceBus failed:
22
The GUID for subscription is invalid not valid.
33

44
Connection attempted with the following settings:
5-
ServiceBus name not set, defaulted to "testmenow"
5+
Azure Service Bus namespace not set, defaulted to "testmenow"
66
SubscriptionId set
77
TenantId set
88
ClientId set

src/ServiceControl.Transports.ASBS.Tests/ApprovalFiles/AzureQueryTests.TestConnectionWithInvalidTenantIdSetting.approved.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ Connection settings to AzureServiceBus have some errors:
22
Invalid tenant id provided. You can locate your tenant id by following the instructions listed here: https://learn.microsoft.com/partner-center/find-ids-and-domain-names (Parameter 'tenantId')
33

44
Connection attempted with the following settings:
5-
ServiceBus name not set, defaulted to "testmenow"
5+
Azure Service Bus namespace not set, defaulted to "testmenow"
66
SubscriptionId set
77
TenantId set
88
ClientId set

src/ServiceControl.Transports.ASBS.Tests/ApprovalFiles/AzureQueryTests.TestConnectionWithMissingLastSlashInManagementUrl.approved.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Connection test to AzureServiceBus was successful
22

33
Connection settings used:
4-
ServiceBus name not set, defaulted to "xxxxx"
4+
Azure Service Bus namespace not set, defaulted to "xxxxx"
55
SubscriptionId set
66
TenantId set
77
ClientId set

src/ServiceControl.Transports.ASBS.Tests/ApprovalFiles/AzureQueryTests.TestConnectionWithValidSettings.approved.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Connection test to AzureServiceBus was successful
22

33
Connection settings used:
4-
ServiceBus name not set, defaulted to "xxxxx"
4+
Azure Service Bus namespace not set, defaulted to "xxxxx"
55
SubscriptionId set
66
TenantId set
77
ClientId set

src/ServiceControl.Transports.ASBS/AzureQuery.cs

Lines changed: 62 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ namespace ServiceControl.Transports.ASBS;
2525
public class AzureQuery(ILogger<AzureQuery> logger, TimeProvider timeProvider, TransportSettings transportSettings)
2626
: BrokerThroughputQuery(logger, "AzureServiceBus")
2727
{
28+
const string CompleteMessageMetricName = "CompleteMessage";
29+
const string MicrosoftServicebusNamespacesMetricsNamespace = "Microsoft.ServiceBus/Namespaces";
30+
2831
string serviceBusName = string.Empty;
2932
ArmClient? armClient;
3033
TokenCredential? credential;
@@ -56,12 +59,12 @@ protected override void InitializeCore(ReadOnlyDictionary<string, string> settin
5659
{
5760
// Extract ServiceBus name from connection string
5861
serviceBusName = ExtractServiceBusName();
59-
logger.LogInformation("ServiceBus name extracted from connection string");
60-
Diagnostics.AppendLine($"ServiceBus name not set, defaulted to \"{serviceBusName}\"");
62+
logger.LogInformation("Azure Service Bus namespace name extracted from connection string");
63+
Diagnostics.AppendLine($"Azure Service Bus namespace not set, defaulted to \"{serviceBusName}\"");
6164
}
6265
else
6366
{
64-
Diagnostics.AppendLine($"ServiceBus name set to \"{serviceBusName}\"");
67+
Diagnostics.AppendLine($"Azure Service Bus namespace set to \"{serviceBusName}\"");
6568
}
6669

6770
if (!settings.TryGetValue(AzureServiceBusSettings.SubscriptionId, out string? subscriptionId))
@@ -145,12 +148,7 @@ protected override void InitializeCore(ReadOnlyDictionary<string, string> settin
145148

146149
(ArmEnvironment armEnvironment, MetricsClientAudience metricsClientAudience) GetEnvironment()
147150
{
148-
if (managementUrlParsed == null)
149-
{
150-
return (ArmEnvironment.AzurePublicCloud, MetricsClientAudience.AzurePublicCloud);
151-
}
152-
153-
if (managementUrlParsed == ArmEnvironment.AzurePublicCloud.Endpoint)
151+
if (managementUrlParsed == null || managementUrlParsed == ArmEnvironment.AzurePublicCloud.Endpoint)
154152
{
155153
return (ArmEnvironment.AzurePublicCloud, MetricsClientAudience.AzurePublicCloud);
156154
}
@@ -173,9 +171,8 @@ protected override void InitializeCore(ReadOnlyDictionary<string, string> settin
173171
string options = string.Join(", ",
174172
new[]
175173
{
176-
ArmEnvironment.AzurePublicCloud, ArmEnvironment.AzureGermany, ArmEnvironment.AzureGovernment,
177-
ArmEnvironment.AzureChina
178-
}.Select(armEnvironment => $"\"{armEnvironment.Endpoint}\""));
174+
ArmEnvironment.AzurePublicCloud, ArmEnvironment.AzureGermany, ArmEnvironment.AzureGovernment, ArmEnvironment.AzureChina
175+
}.Select(environment => $"\"{environment.Endpoint}\""));
179176
InitialiseErrors.Add($"Management url configuration is invalid, available options are {options}");
180177

181178
return (ArmEnvironment.AzurePublicCloud, MetricsClientAudience.AzurePublicCloud);
@@ -202,7 +199,7 @@ public string ExtractServiceBusName()
202199

203200
public override async IAsyncEnumerable<QueueThroughput> GetThroughputPerDay(IBrokerQueue brokerQueue,
204201
DateOnly startDate,
205-
[EnumeratorCancellation] CancellationToken cancellationToken = default)
202+
[EnumeratorCancellation] CancellationToken cancellationToken)
206203
{
207204
logger.LogInformation($"Gathering metrics for \"{brokerQueue.QueueName}\" queue");
208205

@@ -219,7 +216,11 @@ public override async IAsyncEnumerable<QueueThroughput> GetThroughputPerDay(IBro
219216
var data = new Dictionary<DateOnly, QueueThroughput>();
220217
while (currentDate <= endDate)
221218
{
222-
data.Add(currentDate, new QueueThroughput { TotalThroughput = 0, DateUTC = currentDate });
219+
data.Add(currentDate, new QueueThroughput
220+
{
221+
TotalThroughput = 0,
222+
DateUTC = currentDate
223+
});
223224
currentDate = currentDate.AddDays(1);
224225
}
225226

@@ -242,8 +243,8 @@ async Task<MetricsClient> InitializeMetricsClient(CancellationToken cancellation
242243
}
243244

244245
var serviceBusNamespaceResource = await armClient
245-
.GetServiceBusNamespaceResource(resourceId).GetAsync(cancellationToken)
246-
?? throw new Exception($"Could not find ServiceBus with resource Id: \"{resourceId}\"");
246+
.GetServiceBusNamespaceResource(resourceId).GetAsync(cancellationToken)
247+
?? throw new Exception($"Could not find an Azure Service Bus namespace with resource Id: \"{resourceId}\"");
247248

248249
// Determine the region of the namespace
249250
var regionName = serviceBusNamespaceResource.Value.Data.Location.Name;
@@ -281,8 +282,8 @@ async Task<IReadOnlyList<MetricValue>> GetMetrics(string queueName, DateOnly sta
281282

282283
var response = await metricsClient.QueryResourcesAsync(
283284
[resourceId!],
284-
["CompleteMessage"],
285-
"Microsoft.ServiceBus/namespaces",
285+
[CompleteMessageMetricName],
286+
MicrosoftServicebusNamespacesMetricsNamespace,
286287
new MetricsQueryResourcesOptions
287288
{
288289
Filter = $"EntityName eq '{queueName}'",
@@ -291,37 +292,57 @@ async Task<IReadOnlyList<MetricValue>> GetMetrics(string queueName, DateOnly sta
291292
},
292293
cancellationToken);
293294

294-
var metricValues =
295-
response.Value.Values.FirstOrDefault()?.Metrics.FirstOrDefault()?.TimeSeries.FirstOrDefault()?.Values ?? [];
295+
var metricQueryResult = response.Value.Values.SingleOrDefault(mr => mr.Namespace == MicrosoftServicebusNamespacesMetricsNamespace);
296296

297-
return metricValues.AsReadOnly();
297+
if (metricQueryResult is null)
298+
{
299+
throw new Exception($"No metrics query results returned for {MicrosoftServicebusNamespacesMetricsNamespace}");
300+
}
301+
302+
var metricResult = metricQueryResult.GetMetricByName(CompleteMessageMetricName);
303+
304+
if (metricResult.Error.Message is not null)
305+
{
306+
throw new Exception($"Metrics query result for '{metricResult.Name}' failed: {metricResult.Error.Message}");
307+
}
308+
309+
var timeSeries = metricResult.TimeSeries.SingleOrDefault();
310+
311+
if (timeSeries is null)
312+
{
313+
throw new Exception($"Metrics query result for '{metricResult.Name}' contained no time series");
314+
}
315+
316+
return timeSeries.Values.AsReadOnly();
298317
}
299318

300319
public override async IAsyncEnumerable<IBrokerQueue> GetQueueNames(
301-
[EnumeratorCancellation] CancellationToken cancellationToken = default)
320+
[EnumeratorCancellation] CancellationToken cancellationToken)
302321
{
303322
var validNamespaces = await GetValidNamespaceNames(cancellationToken);
304323

305324
SubscriptionResource? subscription = await armClient!.GetDefaultSubscriptionAsync(cancellationToken);
306325
var namespaces = subscription.GetServiceBusNamespacesAsync(cancellationToken);
307326

308-
await foreach (var serviceBusNamespaceResource in namespaces.WithCancellation(cancellationToken))
327+
await foreach (var serviceBusNamespaceResource in namespaces)
309328
{
310-
if (validNamespaces.Contains(serviceBusNamespaceResource.Data.Name))
329+
if (!validNamespaces.Contains(serviceBusNamespaceResource.Data.Name))
311330
{
312-
resourceId = serviceBusNamespaceResource.Id;
331+
continue;
332+
}
313333

314-
await foreach (var queue in serviceBusNamespaceResource.GetServiceBusQueues()
315-
.WithCancellation(cancellationToken))
316-
{
317-
yield return new DefaultBrokerQueue(queue.Data.Name);
318-
}
334+
resourceId = serviceBusNamespaceResource.Id;
319335

320-
yield break;
336+
await foreach (var queue in serviceBusNamespaceResource.GetServiceBusQueues()
337+
.WithCancellation(cancellationToken))
338+
{
339+
yield return new DefaultBrokerQueue(queue.Data.Name);
321340
}
341+
342+
yield break;
322343
}
323344

324-
throw new Exception($"Could not find a ServiceBus named \"{serviceBusName}\"");
345+
throw new Exception($"Could not find a Azure Service Bus namespace named \"{serviceBusName}\"");
325346
}
326347

327348
// ArmEnvironment Audience Values: https://github.com/Azure/azure-sdk-for-net/blob/main/sdk/resourcemanager/Azure.ResourceManager/src/ArmEnvironment.cs
@@ -338,12 +359,9 @@ async Task<HashSet<string>> GetValidNamespaceNames(CancellationToken cancellatio
338359
{
339360
var validNamespaces = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { serviceBusName };
340361

341-
if (!ServiceBusDomains.TryGetValue(armEnvironment, out var serviceBusCloudDomain))
342-
{
343-
// Worst case: the DNS lookup finds nothing additional to match
344-
serviceBusCloudDomain = "servicebus.windows.net";
345-
}
362+
var serviceBusCloudDomain = ServiceBusDomains.GetValueOrDefault(armEnvironment, "servicebus.windows.net");
346363

364+
// Worst case: the DNS lookup finds nothing additional to match
347365
var queryDomain = $"{serviceBusName}.{serviceBusCloudDomain}";
348366
var validDomainTail = $".{serviceBusCloudDomain}.";
349367

@@ -365,12 +383,12 @@ async Task<HashSet<string>> GetValidNamespaceNames(CancellationToken cancellatio
365383

366384
public override KeyDescriptionPair[] Settings =>
367385
[
368-
new KeyDescriptionPair(AzureServiceBusSettings.ServiceBusName, AzureServiceBusSettings.ServiceBusNameDescription),
369-
new KeyDescriptionPair(AzureServiceBusSettings.ClientId, AzureServiceBusSettings.ClientIdDescription),
370-
new KeyDescriptionPair(AzureServiceBusSettings.ClientSecret, AzureServiceBusSettings.ClientSecretDescription),
371-
new KeyDescriptionPair(AzureServiceBusSettings.TenantId, AzureServiceBusSettings.TenantIdDescription),
372-
new KeyDescriptionPair(AzureServiceBusSettings.SubscriptionId, AzureServiceBusSettings.SubscriptionIdDescription),
373-
new KeyDescriptionPair(AzureServiceBusSettings.ManagementUrl, AzureServiceBusSettings.ManagementUrlDescription)
386+
new(AzureServiceBusSettings.ServiceBusName, AzureServiceBusSettings.ServiceBusNameDescription),
387+
new(AzureServiceBusSettings.ClientId, AzureServiceBusSettings.ClientIdDescription),
388+
new(AzureServiceBusSettings.ClientSecret, AzureServiceBusSettings.ClientSecretDescription),
389+
new(AzureServiceBusSettings.TenantId, AzureServiceBusSettings.TenantIdDescription),
390+
new(AzureServiceBusSettings.SubscriptionId, AzureServiceBusSettings.SubscriptionIdDescription),
391+
new(AzureServiceBusSettings.ManagementUrl, AzureServiceBusSettings.ManagementUrlDescription)
374392
];
375393

376394
protected override async Task<(bool Success, List<string> Errors)> TestConnectionCore(
@@ -405,4 +423,4 @@ public static class AzureServiceBusSettings
405423
public static readonly string ManagementUrl = "ASB/ManagementUrl";
406424
public static readonly string ManagementUrlDescription = "Azure management URL";
407425
}
408-
}
426+
}

0 commit comments

Comments
 (0)