diff --git a/sdk/servicebus/Microsoft.Azure.WebJobs.Extensions.ServiceBus/CHANGELOG.md b/sdk/servicebus/Microsoft.Azure.WebJobs.Extensions.ServiceBus/CHANGELOG.md index b5f32997db4e2..617ad52d7d4dc 100644 --- a/sdk/servicebus/Microsoft.Azure.WebJobs.Extensions.ServiceBus/CHANGELOG.md +++ b/sdk/servicebus/Microsoft.Azure.WebJobs.Extensions.ServiceBus/CHANGELOG.md @@ -6,6 +6,7 @@ - Fixed issue where deadlettering a message without specifying properties to modify could throw an exception from out of proc extension. +- Include underlying exception details in RpcException when a failure occurs. ## 5.13.2 (2023-10-18) diff --git a/sdk/servicebus/Microsoft.Azure.WebJobs.Extensions.ServiceBus/src/Grpc/SettlementService.cs b/sdk/servicebus/Microsoft.Azure.WebJobs.Extensions.ServiceBus/src/Grpc/SettlementService.cs index 32b591d811fbb..6ce4b9c2e4a25 100644 --- a/sdk/servicebus/Microsoft.Azure.WebJobs.Extensions.ServiceBus/src/Grpc/SettlementService.cs +++ b/sdk/servicebus/Microsoft.Azure.WebJobs.Extensions.ServiceBus/src/Grpc/SettlementService.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. #if NET6_0_OR_GREATER +using System; using System.Collections.Generic; using System.Threading.Tasks; using Azure.Core.Amqp.Shared; @@ -31,65 +32,98 @@ public SettlementService() public override async Task Complete(CompleteRequest request, ServerCallContext context) { - if (_provider.ActionsCache.TryGetValue(request.Locktoken, out var tuple)) + try { - await tuple.Actions.CompleteMessageAsync( - tuple.Message, - context.CancellationToken).ConfigureAwait(false); - return new Empty(); + if (_provider.ActionsCache.TryGetValue(request.Locktoken, out var tuple)) + { + await tuple.Actions.CompleteMessageAsync( + tuple.Message, + context.CancellationToken).ConfigureAwait(false); + return new Empty(); + } } + catch (Exception ex) + { + throw new RpcException(new Status(StatusCode.Unknown, ex.ToString())); + } + throw new RpcException (new Status(StatusCode.FailedPrecondition, $"LockToken {request.Locktoken} not found.")); } public override async Task Abandon(AbandonRequest request, ServerCallContext context) { - if (_provider.ActionsCache.TryGetValue(request.Locktoken, out var tuple)) + try { - await tuple.Actions.AbandonMessageAsync( - tuple.Message, - DeserializeAmqpMap(request.PropertiesToModify), - context.CancellationToken).ConfigureAwait(false); - return new Empty(); + if (_provider.ActionsCache.TryGetValue(request.Locktoken, out var tuple)) + { + await tuple.Actions.AbandonMessageAsync( + tuple.Message, + DeserializeAmqpMap(request.PropertiesToModify), + context.CancellationToken).ConfigureAwait(false); + return new Empty(); + } + } + catch (Exception ex) + { + throw new RpcException(new Status(StatusCode.Unknown, ex.ToString())); } + throw new RpcException (new Status(StatusCode.FailedPrecondition, $"LockToken {request.Locktoken} not found.")); } public override async Task Defer(DeferRequest request, ServerCallContext context) { - if (_provider.ActionsCache.TryGetValue(request.Locktoken, out var tuple)) + try + { + if (_provider.ActionsCache.TryGetValue(request.Locktoken, out var tuple)) + { + await tuple.Actions.DeferMessageAsync( + tuple.Message, + DeserializeAmqpMap(request.PropertiesToModify), + context.CancellationToken).ConfigureAwait(false); + return new Empty(); + } + } + catch (Exception ex) { - await tuple.Actions.DeferMessageAsync( - tuple.Message, - DeserializeAmqpMap(request.PropertiesToModify), - context.CancellationToken).ConfigureAwait(false); - return new Empty(); + throw new RpcException(new Status(StatusCode.Unknown, ex.ToString())); } + throw new RpcException (new Status(StatusCode.FailedPrecondition, $"LockToken {request.Locktoken} not found.")); } public override async Task Deadletter(DeadletterRequest request, ServerCallContext context) { - if (_provider.ActionsCache.TryGetValue(request.Locktoken, out var tuple)) + try { - if (request.PropertiesToModify == null || request.PropertiesToModify == ByteString.Empty) + if (_provider.ActionsCache.TryGetValue(request.Locktoken, out var tuple)) { - await tuple.Actions.DeadLetterMessageAsync( - tuple.Message, - request.DeadletterReason, - request.DeadletterErrorDescription, - context.CancellationToken).ConfigureAwait(false); - } - else - { - await tuple.Actions.DeadLetterMessageAsync( - tuple.Message, - DeserializeAmqpMap(request.PropertiesToModify), - request.DeadletterReason, - request.DeadletterErrorDescription, - context.CancellationToken).ConfigureAwait(false); + if (request.PropertiesToModify == null || request.PropertiesToModify == ByteString.Empty) + { + await tuple.Actions.DeadLetterMessageAsync( + tuple.Message, + request.DeadletterReason, + request.DeadletterErrorDescription, + context.CancellationToken).ConfigureAwait(false); + } + else + { + await tuple.Actions.DeadLetterMessageAsync( + tuple.Message, + DeserializeAmqpMap(request.PropertiesToModify), + request.DeadletterReason, + request.DeadletterErrorDescription, + context.CancellationToken).ConfigureAwait(false); + } + + return new Empty(); } - return new Empty(); } + catch (Exception ex) + { + throw new RpcException(new Status(StatusCode.Unknown, ex.ToString())); + } + throw new RpcException (new Status(StatusCode.FailedPrecondition, $"LockToken {request.Locktoken} not found.")); } diff --git a/sdk/servicebus/Microsoft.Azure.WebJobs.Extensions.ServiceBus/src/Microsoft.Azure.WebJobs.Extensions.ServiceBus.csproj b/sdk/servicebus/Microsoft.Azure.WebJobs.Extensions.ServiceBus/src/Microsoft.Azure.WebJobs.Extensions.ServiceBus.csproj index a58f8fdefb82d..f24982fa73ddb 100644 --- a/sdk/servicebus/Microsoft.Azure.WebJobs.Extensions.ServiceBus/src/Microsoft.Azure.WebJobs.Extensions.ServiceBus.csproj +++ b/sdk/servicebus/Microsoft.Azure.WebJobs.Extensions.ServiceBus/src/Microsoft.Azure.WebJobs.Extensions.ServiceBus.csproj @@ -51,4 +51,8 @@ + + + + diff --git a/sdk/servicebus/Microsoft.Azure.WebJobs.Extensions.ServiceBus/tests/Grpc/ServiceBusGrpcEndToEndTests.cs b/sdk/servicebus/Microsoft.Azure.WebJobs.Extensions.ServiceBus/tests/Grpc/ServiceBusGrpcEndToEndTests.cs index 4fcfd9f5df2d5..c1fb392d7cc0f 100644 --- a/sdk/servicebus/Microsoft.Azure.WebJobs.Extensions.ServiceBus/tests/Grpc/ServiceBusGrpcEndToEndTests.cs +++ b/sdk/servicebus/Microsoft.Azure.WebJobs.Extensions.ServiceBus/tests/Grpc/ServiceBusGrpcEndToEndTests.cs @@ -116,9 +116,10 @@ public async Task BindToMessageAndDeadletterWithNoPropertiesToModify() } [Test] - public async Task BindToBatchAndDeadletter() + public async Task BindToBatchAndDeadletterExceptionValidation() { - var host = BuildHost(); + // this test expects errors so set skipValidation=true + var host = BuildHost(skipValidation: true); var settlementImpl = host.Services.GetRequiredService(); var provider = host.Services.GetRequiredService(); ServiceBusBindToBatchAndDeadletter.SettlementService = settlementImpl; @@ -339,6 +340,37 @@ await SettlementService.Deadletter( Assert.AreEqual("description", deadletterMessage.DeadLetterErrorDescription); Assert.AreEqual("reason", deadletterMessage.DeadLetterReason); Assert.AreEqual(42, deadletterMessage.ApplicationProperties["key"]); + + var exception = Assert.ThrowsAsync( + async () => + await SettlementService.Complete( + new CompleteRequest { Locktoken = message.LockToken }, + new MockServerCallContext())); + StringAssert.Contains( + "Azure.Messaging.ServiceBus.ServiceBusException: The lock supplied is invalid.", + exception.ToString()); + + exception = Assert.ThrowsAsync( + async () => + await SettlementService.Defer( + new DeferRequest { Locktoken = message.LockToken }, + new MockServerCallContext())); + StringAssert.Contains( + "Azure.Messaging.ServiceBus.ServiceBusException: The lock supplied is invalid.", + exception.ToString()); + + exception = Assert.ThrowsAsync( + async () => + await SettlementService.Deadletter( + new DeadletterRequest() { Locktoken = message.LockToken }, + new MockServerCallContext())); + StringAssert.Contains( + "Azure.Messaging.ServiceBus.ServiceBusException: The lock supplied is invalid.", + exception.ToString()); + + // The service doesn't seem to throw when an already settled message gets abandoned over the mgmt link. Will need to discuss + // with service team. + _waitHandle1.Set(); } }