From e278f87b0b3aa563ab055918e988baec9ec155a3 Mon Sep 17 00:00:00 2001 From: Sean Feldman Date: Thu, 23 Aug 2018 22:34:55 -0600 Subject: [PATCH 1/7] Use plugin name in exception thrown by message sender and reciever (#559) * Update MessageReceiver.cs * Update MessageSender.cs --- src/Microsoft.Azure.ServiceBus/Core/MessageReceiver.cs | 6 +++--- src/Microsoft.Azure.ServiceBus/Core/MessageSender.cs | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Microsoft.Azure.ServiceBus/Core/MessageReceiver.cs b/src/Microsoft.Azure.ServiceBus/Core/MessageReceiver.cs index ca402b28..3a4818a8 100644 --- a/src/Microsoft.Azure.ServiceBus/Core/MessageReceiver.cs +++ b/src/Microsoft.Azure.ServiceBus/Core/MessageReceiver.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft. All rights reserved. +// Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. namespace Microsoft.Azure.ServiceBus.Core @@ -911,7 +911,7 @@ public override void RegisterPlugin(ServiceBusPlugin serviceBusPlugin) } if (this.RegisteredPlugins.Any(p => p.Name == serviceBusPlugin.Name)) { - throw new ArgumentException(nameof(serviceBusPlugin), Resources.PluginAlreadyRegistered.FormatForUser(nameof(serviceBusPlugin))); + throw new ArgumentException(nameof(serviceBusPlugin), Resources.PluginAlreadyRegistered.FormatForUser(serviceBusPlugin.Name)); } this.RegisteredPlugins.Add(serviceBusPlugin); } @@ -1690,4 +1690,4 @@ Rejected GetRejectedOutcome(IDictionary propertiesToModify, stri return rejected; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.Azure.ServiceBus/Core/MessageSender.cs b/src/Microsoft.Azure.ServiceBus/Core/MessageSender.cs index d0257ee9..72613d65 100644 --- a/src/Microsoft.Azure.ServiceBus/Core/MessageSender.cs +++ b/src/Microsoft.Azure.ServiceBus/Core/MessageSender.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft. All rights reserved. +// Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. namespace Microsoft.Azure.ServiceBus.Core @@ -389,7 +389,7 @@ public override void RegisterPlugin(ServiceBusPlugin serviceBusPlugin) if (this.RegisteredPlugins.Any(p => p.GetType() == serviceBusPlugin.GetType())) { - throw new ArgumentException(nameof(serviceBusPlugin), Resources.PluginAlreadyRegistered.FormatForUser(nameof(serviceBusPlugin))); + throw new ArgumentException(nameof(serviceBusPlugin), Resources.PluginAlreadyRegistered.FormatForUser(serviceBusPlugin.Name)); } this.RegisteredPlugins.Add(serviceBusPlugin); } @@ -765,4 +765,4 @@ ArraySegment GetNextDeliveryTag() return new ArraySegment(BitConverter.GetBytes(deliveryId)); } } -} \ No newline at end of file +} From 9a8edface5f40fe07f28fdd8054304e5c639fce5 Mon Sep 17 00:00:00 2001 From: Neeraj Makam Date: Fri, 21 Sep 2018 17:28:18 -0700 Subject: [PATCH 2/7] Updating min amqp version to 2.3.5 since it contains an important reliability fix (#573) --- .../Microsoft.Azure.ServiceBus.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.Azure.ServiceBus/Microsoft.Azure.ServiceBus.csproj b/src/Microsoft.Azure.ServiceBus/Microsoft.Azure.ServiceBus.csproj index 61bb28b9..d4faa8c4 100644 --- a/src/Microsoft.Azure.ServiceBus/Microsoft.Azure.ServiceBus.csproj +++ b/src/Microsoft.Azure.ServiceBus/Microsoft.Azure.ServiceBus.csproj @@ -23,7 +23,7 @@ - + From 9b72b1f65430638c058a1d8eeaf81f362497253d Mon Sep 17 00:00:00 2001 From: Neeraj Makam Date: Fri, 21 Sep 2018 19:36:02 -0700 Subject: [PATCH 3/7] Making Queue/Topic/Subscription descriptions forward compatible (#563) + Minor bugs (#572) Fixes- #563 - Making Queue/Topic/Subscription descriptions forward compatible by storing all unknown properties in a list and relaying it back during Update. #564 - Equals() should not use == to check for null #562 - NRE when content = null. NRE when GetRules() is invoked on rules containing custom data types. --- .../Filters/XmlObjectConvertor.cs | 5 ++ .../Management/AuthorizationRules.cs | 2 +- .../Management/ManagementClient.cs | 8 ++- .../Management/QueueDescription.cs | 17 +++-- .../Management/QueueDescriptionExtensions.cs | 66 +++++++++++++------ .../Management/SubscriptionDescription.cs | 7 ++ .../SubscriptionDescriptionExtensions.cs | 54 +++++++++++---- .../Management/TopicDescription.cs | 10 ++- .../Management/TopicDescriptionExtensions.cs | 55 ++++++++++++---- ...rovals.ApproveAzureServiceBus.approved.txt | 2 +- 10 files changed, 168 insertions(+), 58 deletions(-) diff --git a/src/Microsoft.Azure.ServiceBus/Filters/XmlObjectConvertor.cs b/src/Microsoft.Azure.ServiceBus/Filters/XmlObjectConvertor.cs index 0f8eb914..721fb8f5 100644 --- a/src/Microsoft.Azure.ServiceBus/Filters/XmlObjectConvertor.cs +++ b/src/Microsoft.Azure.ServiceBus/Filters/XmlObjectConvertor.cs @@ -13,6 +13,11 @@ internal class XmlObjectConvertor internal static object ParseValueObject(XElement element) { var prefix = element.GetPrefixOfNamespace(XNamespace.Get(ManagementClientConstants.XmlSchemaNs)); + if (string.IsNullOrWhiteSpace(prefix)) + { + return element.Value; + } + var type = element.Attribute(XName.Get("type", ManagementClientConstants.XmlSchemaInstanceNs)).Value; switch (type.Substring(prefix.Length + 1)) { diff --git a/src/Microsoft.Azure.ServiceBus/Management/AuthorizationRules.cs b/src/Microsoft.Azure.ServiceBus/Management/AuthorizationRules.cs index 41ed4ea0..697286ed 100644 --- a/src/Microsoft.Azure.ServiceBus/Management/AuthorizationRules.cs +++ b/src/Microsoft.Azure.ServiceBus/Management/AuthorizationRules.cs @@ -51,7 +51,7 @@ public override bool Equals(object obj) public bool Equals(AuthorizationRules other) { - if (other == null || this.Count != other.Count) + if (ReferenceEquals(other, null) || this.Count != other.Count) { return false; } diff --git a/src/Microsoft.Azure.ServiceBus/Management/ManagementClient.cs b/src/Microsoft.Azure.ServiceBus/Management/ManagementClient.cs index 49981827..7ec885d5 100644 --- a/src/Microsoft.Azure.ServiceBus/Management/ManagementClient.cs +++ b/src/Microsoft.Azure.ServiceBus/Management/ManagementClient.cs @@ -247,6 +247,8 @@ public virtual async Task GetSubscriptionAsync(string t /// No sufficient permission to perform this operation. You should check to ensure that your has the correct credentials to perform this operation. /// The server is busy. You should wait before you retry the operation. /// An internal error or an unexpected exception occured. + /// Note - Only following data types are deserialized in Filters and Action parameters - string,int,long,bool,double,DateTime. + /// Other data types would return its string value. public virtual async Task GetRuleAsync(string topicPath, string subscriptionName, string ruleName, CancellationToken cancellationToken = default) { EntityNameHelper.CheckValidTopicName(topicPath); @@ -436,7 +438,9 @@ public virtual async Task> GetSubscriptionsAsync( /// The server is busy. You should wait before you retry the operation. /// An internal error or an unexpected exception occured. /// You can simulate pages of list of entities by manipulating and . - /// skip(0)+count(100) gives first 100 entities. skip(100)+count(100) gives the next 100 entities. + /// skip(0)+count(100) gives first 100 entities. skip(100)+count(100) gives the next 100 entities. + /// Note - Only following data types are deserialized in Filters and Action parameters - string,int,long,bool,double,DateTime. + /// Other data types would return its string value. public virtual async Task> GetRulesAsync(string topicPath, string subscriptionName, int count = 100, int skip = 0, CancellationToken cancellationToken = default) { EntityNameHelper.CheckValidTopicName(topicPath); @@ -895,7 +899,7 @@ private static async Task ValidateHttpResponse(HttpResponseMessage re return null; } - var exceptionMessage = await response.Content?.ReadAsStringAsync(); + var exceptionMessage = await (response.Content?.ReadAsStringAsync() ?? Task.FromResult(string.Empty)); exceptionMessage = ParseDetailIfAvailable(exceptionMessage) ?? response.ReasonPhrase; if (response.StatusCode == HttpStatusCode.Unauthorized) diff --git a/src/Microsoft.Azure.ServiceBus/Management/QueueDescription.cs b/src/Microsoft.Azure.ServiceBus/Management/QueueDescription.cs index 21f6daf7..ac68eb14 100644 --- a/src/Microsoft.Azure.ServiceBus/Management/QueueDescription.cs +++ b/src/Microsoft.Azure.ServiceBus/Management/QueueDescription.cs @@ -4,6 +4,7 @@ namespace Microsoft.Azure.ServiceBus.Management { using System; + using System.Collections.Generic; using Microsoft.Azure.ServiceBus.Primitives; /// @@ -283,6 +284,12 @@ public string UserMetadata } } + /// + /// List of properties that were retrieved using GetQueue but is not understood by this version of client is stored here. + /// These will be sent back to the service as-is when UpdateQueue is called on this QueueDescription. + /// + internal List UnknownProperties { get; set; } + public override int GetHashCode() { return this.Path?.GetHashCode() ?? base.GetHashCode(); @@ -294,14 +301,10 @@ public override bool Equals(object obj) return this.Equals(other); } - public bool Equals(QueueDescription other) + public bool Equals(QueueDescription otherDescription) { - if (other == null) - { - return false; - } - - if (this.Path.Equals(other.Path, StringComparison.OrdinalIgnoreCase) + if (otherDescription is QueueDescription other + && this.Path.Equals(other.Path, StringComparison.OrdinalIgnoreCase) && this.AutoDeleteOnIdle.Equals(other.AutoDeleteOnIdle) && this.DefaultMessageTimeToLive.Equals(other.DefaultMessageTimeToLive) && (!this.RequiresDuplicateDetection || this.DuplicateDetectionHistoryTimeWindow.Equals(other.DuplicateDetectionHistoryTimeWindow)) diff --git a/src/Microsoft.Azure.ServiceBus/Management/QueueDescriptionExtensions.cs b/src/Microsoft.Azure.ServiceBus/Management/QueueDescriptionExtensions.cs index 0de90bd2..24c796ad 100644 --- a/src/Microsoft.Azure.ServiceBus/Management/QueueDescriptionExtensions.cs +++ b/src/Microsoft.Azure.ServiceBus/Management/QueueDescriptionExtensions.cs @@ -12,31 +12,39 @@ internal static class QueueDescriptionExtensions { public static XDocument Serialize(this QueueDescription description) { + var queueDescriptionElements = new List() + { + new XElement(XName.Get("LockDuration", ManagementClientConstants.SbNs), XmlConvert.ToString(description.LockDuration)), + new XElement(XName.Get("MaxSizeInMegabytes", ManagementClientConstants.SbNs), XmlConvert.ToString(description.MaxSizeInMB)), + new XElement(XName.Get("RequiresDuplicateDetection", ManagementClientConstants.SbNs), XmlConvert.ToString(description.RequiresDuplicateDetection)), + new XElement(XName.Get("RequiresSession", ManagementClientConstants.SbNs), XmlConvert.ToString(description.RequiresSession)), + description.DefaultMessageTimeToLive != TimeSpan.MaxValue ? new XElement(XName.Get("DefaultMessageTimeToLive", ManagementClientConstants.SbNs), XmlConvert.ToString(description.DefaultMessageTimeToLive)) : null, + new XElement(XName.Get("DeadLetteringOnMessageExpiration", ManagementClientConstants.SbNs), XmlConvert.ToString(description.EnableDeadLetteringOnMessageExpiration)), + description.RequiresDuplicateDetection && description.DuplicateDetectionHistoryTimeWindow != default ? + new XElement(XName.Get("DuplicateDetectionHistoryTimeWindow", ManagementClientConstants.SbNs), XmlConvert.ToString(description.DuplicateDetectionHistoryTimeWindow)) + : null, + new XElement(XName.Get("MaxDeliveryCount", ManagementClientConstants.SbNs), XmlConvert.ToString(description.MaxDeliveryCount)), + new XElement(XName.Get("EnableBatchedOperations", ManagementClientConstants.SbNs), XmlConvert.ToString(description.EnableBatchedOperations)), + description.AuthorizationRules?.Serialize(), + new XElement(XName.Get("Status", ManagementClientConstants.SbNs), description.Status.ToString()), + description.ForwardTo != null ? new XElement(XName.Get("ForwardTo", ManagementClientConstants.SbNs), description.ForwardTo) : null, + description.UserMetadata != null ? new XElement(XName.Get("UserMetadata", ManagementClientConstants.SbNs), description.UserMetadata) : null, + description.AutoDeleteOnIdle != TimeSpan.MaxValue ? new XElement(XName.Get("AutoDeleteOnIdle", ManagementClientConstants.SbNs), XmlConvert.ToString(description.AutoDeleteOnIdle)) : null, + new XElement(XName.Get("EnablePartitioning", ManagementClientConstants.SbNs), XmlConvert.ToString(description.EnablePartitioning)), + description.ForwardDeadLetteredMessagesTo != null ? new XElement(XName.Get("ForwardDeadLetteredMessagesTo", ManagementClientConstants.SbNs), description.ForwardDeadLetteredMessagesTo) : null + }; + + if (description.UnknownProperties != null) + { + queueDescriptionElements.AddRange(description.UnknownProperties); + } + return new XDocument( new XElement(XName.Get("entry", ManagementClientConstants.AtomNs), new XElement(XName.Get("content", ManagementClientConstants.AtomNs), new XAttribute("type", "application/xml"), new XElement(XName.Get("QueueDescription", ManagementClientConstants.SbNs), - new XElement(XName.Get("LockDuration", ManagementClientConstants.SbNs), XmlConvert.ToString(description.LockDuration)), - new XElement(XName.Get("MaxSizeInMegabytes", ManagementClientConstants.SbNs), XmlConvert.ToString(description.MaxSizeInMB)), - new XElement(XName.Get("RequiresDuplicateDetection", ManagementClientConstants.SbNs), XmlConvert.ToString(description.RequiresDuplicateDetection)), - new XElement(XName.Get("RequiresSession", ManagementClientConstants.SbNs), XmlConvert.ToString(description.RequiresSession)), - description.DefaultMessageTimeToLive != TimeSpan.MaxValue ? new XElement(XName.Get("DefaultMessageTimeToLive", ManagementClientConstants.SbNs), XmlConvert.ToString(description.DefaultMessageTimeToLive)) : null, - new XElement(XName.Get("DeadLetteringOnMessageExpiration", ManagementClientConstants.SbNs), XmlConvert.ToString(description.EnableDeadLetteringOnMessageExpiration)), - description.RequiresDuplicateDetection && description.DuplicateDetectionHistoryTimeWindow != default ? - new XElement(XName.Get("DuplicateDetectionHistoryTimeWindow", ManagementClientConstants.SbNs), XmlConvert.ToString(description.DuplicateDetectionHistoryTimeWindow)) - : null, - new XElement(XName.Get("MaxDeliveryCount", ManagementClientConstants.SbNs), XmlConvert.ToString(description.MaxDeliveryCount)), - new XElement(XName.Get("EnableBatchedOperations", ManagementClientConstants.SbNs), XmlConvert.ToString(description.EnableBatchedOperations)), - description.AuthorizationRules?.Serialize(), - new XElement(XName.Get("Status", ManagementClientConstants.SbNs), description.Status.ToString()), - description.ForwardTo != null ? new XElement(XName.Get("ForwardTo", ManagementClientConstants.SbNs), description.ForwardTo) : null, - description.UserMetadata != null ? new XElement(XName.Get("UserMetadata", ManagementClientConstants.SbNs), description.UserMetadata) : null, - description.AutoDeleteOnIdle != TimeSpan.MaxValue ? new XElement(XName.Get("AutoDeleteOnIdle", ManagementClientConstants.SbNs), XmlConvert.ToString(description.AutoDeleteOnIdle)) : null, - new XElement(XName.Get("EnablePartitioning", ManagementClientConstants.SbNs), XmlConvert.ToString(description.EnablePartitioning)), - description.ForwardDeadLetteredMessagesTo != null ? new XElement(XName.Get("ForwardDeadLetteredMessagesTo", ManagementClientConstants.SbNs), description.ForwardDeadLetteredMessagesTo) : null - )) - )); + queueDescriptionElements.ToArray())))); } public static QueueDescription ParseFromContent(string xml) @@ -131,6 +139,24 @@ private static QueueDescription ParseFromEntryElement(XElement xEntry) case "AuthorizationRules": qd.AuthorizationRules = AuthorizationRules.ParseFromXElement(element); break; + case "AccessedAt": + case "CreatedAt": + case "MessageCount": + case "SizeInBytes": + case "UpdatedAt": + case "CountDetails": + // Ignore known properties + // Do nothing + break; + default: + // For unknown properties, keep them as-is for forward proof. + if (qd.UnknownProperties == null) + { + qd.UnknownProperties = new List(); + } + + qd.UnknownProperties.Add(element); + break; } } diff --git a/src/Microsoft.Azure.ServiceBus/Management/SubscriptionDescription.cs b/src/Microsoft.Azure.ServiceBus/Management/SubscriptionDescription.cs index abb4f4c0..ad9d7e88 100644 --- a/src/Microsoft.Azure.ServiceBus/Management/SubscriptionDescription.cs +++ b/src/Microsoft.Azure.ServiceBus/Management/SubscriptionDescription.cs @@ -4,6 +4,7 @@ namespace Microsoft.Azure.ServiceBus.Management { using System; + using System.Collections.Generic; using Microsoft.Azure.ServiceBus.Primitives; /// @@ -248,6 +249,12 @@ public string UserMetadata } } + /// + /// List of properties that were retrieved using GetSubscription but is not understood by this version of client is stored here. + /// These will be sent back to the service as-is when UpdateSubscription is called on this SubscriptionDescription. + /// + internal List UnknownProperties { get; set; } + internal RuleDescription DefaultRuleDescription { get; set; } public override int GetHashCode() diff --git a/src/Microsoft.Azure.ServiceBus/Management/SubscriptionDescriptionExtensions.cs b/src/Microsoft.Azure.ServiceBus/Management/SubscriptionDescriptionExtensions.cs index 4e5ee832..fef6a9d9 100644 --- a/src/Microsoft.Azure.ServiceBus/Management/SubscriptionDescriptionExtensions.cs +++ b/src/Microsoft.Azure.ServiceBus/Management/SubscriptionDescriptionExtensions.cs @@ -147,6 +147,24 @@ private static SubscriptionDescription ParseFromEntryElement(string topicName, X subscriptionDesc.ForwardDeadLetteredMessagesTo = element.Value; } break; + case "AccessedAt": + case "CreatedAt": + case "MessageCount": + case "SizeInBytes": + case "UpdatedAt": + case "CountDetails": + // Ignore known properties + // Do nothing + break; + default: + // For unknown properties, keep them as-is for forward proof. + if (subscriptionDesc.UnknownProperties == null) + { + subscriptionDesc.UnknownProperties = new List(); + } + + subscriptionDesc.UnknownProperties.Add(element); + break; } } @@ -155,24 +173,34 @@ private static SubscriptionDescription ParseFromEntryElement(string topicName, X public static XDocument Serialize(this SubscriptionDescription description) { + var subscriptionDescriptionElements = new List() + { + new XElement(XName.Get("LockDuration", ManagementClientConstants.SbNs), XmlConvert.ToString(description.LockDuration)), + new XElement(XName.Get("RequiresSession", ManagementClientConstants.SbNs), XmlConvert.ToString(description.RequiresSession)), + description.DefaultMessageTimeToLive != TimeSpan.MaxValue ? new XElement(XName.Get("DefaultMessageTimeToLive", ManagementClientConstants.SbNs), XmlConvert.ToString(description.DefaultMessageTimeToLive)) : null, + new XElement(XName.Get("DeadLetteringOnMessageExpiration", ManagementClientConstants.SbNs), XmlConvert.ToString(description.EnableDeadLetteringOnMessageExpiration)), + new XElement(XName.Get("DeadLetteringOnFilterEvaluationExceptions", ManagementClientConstants.SbNs), XmlConvert.ToString(description.EnableDeadLetteringOnFilterEvaluationExceptions)), + description.DefaultRuleDescription != null ? description.DefaultRuleDescription.SerializeRule("DefaultRuleDescription") : null, + new XElement(XName.Get("MaxDeliveryCount", ManagementClientConstants.SbNs), XmlConvert.ToString(description.MaxDeliveryCount)), + new XElement(XName.Get("EnableBatchedOperations", ManagementClientConstants.SbNs), XmlConvert.ToString(description.EnableBatchedOperations)), + new XElement(XName.Get("Status", ManagementClientConstants.SbNs), description.Status.ToString()), + description.ForwardTo != null ? new XElement(XName.Get("ForwardTo", ManagementClientConstants.SbNs), description.ForwardTo) : null, + description.UserMetadata != null ? new XElement(XName.Get("UserMetadata", ManagementClientConstants.SbNs), description.UserMetadata) : null, + description.ForwardDeadLetteredMessagesTo != null ? new XElement(XName.Get("ForwardDeadLetteredMessagesTo", ManagementClientConstants.SbNs), description.ForwardDeadLetteredMessagesTo) : null, + description.AutoDeleteOnIdle != TimeSpan.MaxValue ? new XElement(XName.Get("AutoDeleteOnIdle", ManagementClientConstants.SbNs), XmlConvert.ToString(description.AutoDeleteOnIdle)) : null + }; + + if (description.UnknownProperties != null) + { + subscriptionDescriptionElements.AddRange(description.UnknownProperties); + } + return new XDocument( new XElement(XName.Get("entry", ManagementClientConstants.AtomNs), new XElement(XName.Get("content", ManagementClientConstants.AtomNs), new XAttribute("type", "application/xml"), new XElement(XName.Get("SubscriptionDescription", ManagementClientConstants.SbNs), - new XElement(XName.Get("LockDuration", ManagementClientConstants.SbNs), XmlConvert.ToString(description.LockDuration)), - new XElement(XName.Get("RequiresSession", ManagementClientConstants.SbNs), XmlConvert.ToString(description.RequiresSession)), - description.DefaultMessageTimeToLive != TimeSpan.MaxValue ? new XElement(XName.Get("DefaultMessageTimeToLive", ManagementClientConstants.SbNs), XmlConvert.ToString(description.DefaultMessageTimeToLive)) : null, - new XElement(XName.Get("DeadLetteringOnMessageExpiration", ManagementClientConstants.SbNs), XmlConvert.ToString(description.EnableDeadLetteringOnMessageExpiration)), - new XElement(XName.Get("DeadLetteringOnFilterEvaluationExceptions", ManagementClientConstants.SbNs), XmlConvert.ToString(description.EnableDeadLetteringOnFilterEvaluationExceptions)), - description.DefaultRuleDescription != null ? description.DefaultRuleDescription.SerializeRule("DefaultRuleDescription") : null, - new XElement(XName.Get("MaxDeliveryCount", ManagementClientConstants.SbNs), XmlConvert.ToString(description.MaxDeliveryCount)), - new XElement(XName.Get("EnableBatchedOperations", ManagementClientConstants.SbNs), XmlConvert.ToString(description.EnableBatchedOperations)), - new XElement(XName.Get("Status", ManagementClientConstants.SbNs), description.Status.ToString()), - description.ForwardTo != null ? new XElement(XName.Get("ForwardTo", ManagementClientConstants.SbNs), description.ForwardTo) : null, - description.UserMetadata != null ? new XElement(XName.Get("UserMetadata", ManagementClientConstants.SbNs), description.UserMetadata) : null, - description.ForwardDeadLetteredMessagesTo != null ? new XElement(XName.Get("ForwardDeadLetteredMessagesTo", ManagementClientConstants.SbNs), description.ForwardDeadLetteredMessagesTo) : null, - description.AutoDeleteOnIdle != TimeSpan.MaxValue ? new XElement(XName.Get("AutoDeleteOnIdle", ManagementClientConstants.SbNs), XmlConvert.ToString(description.AutoDeleteOnIdle)) : null + subscriptionDescriptionElements )) )); } diff --git a/src/Microsoft.Azure.ServiceBus/Management/TopicDescription.cs b/src/Microsoft.Azure.ServiceBus/Management/TopicDescription.cs index d09ec9b9..d279a54d 100644 --- a/src/Microsoft.Azure.ServiceBus/Management/TopicDescription.cs +++ b/src/Microsoft.Azure.ServiceBus/Management/TopicDescription.cs @@ -4,6 +4,7 @@ namespace Microsoft.Azure.ServiceBus.Management { using System; + using System.Collections.Generic; /// /// Represents the metadata description of the topic. @@ -187,6 +188,12 @@ public string UserMetadata } } + /// + /// List of properties that were retrieved using GetTopic but is not understood by this version of client is stored here. + /// These will be sent back to the service as-is when UpdateTopic is called on this TopicDescription. + /// + internal List UnknownProperties { get; set; } + public override int GetHashCode() { return this.Path?.GetHashCode() ?? base.GetHashCode(); @@ -200,7 +207,8 @@ public override bool Equals(object obj) public bool Equals(TopicDescription otherDescription) { - if (otherDescription is TopicDescription other && this.Path.Equals(other.Path, StringComparison.OrdinalIgnoreCase) + if (otherDescription is TopicDescription other + && this.Path.Equals(other.Path, StringComparison.OrdinalIgnoreCase) && this.AutoDeleteOnIdle.Equals(other.AutoDeleteOnIdle) && this.DefaultMessageTimeToLive.Equals(other.DefaultMessageTimeToLive) && (!this.RequiresDuplicateDetection || this.DuplicateDetectionHistoryTimeWindow.Equals(other.DuplicateDetectionHistoryTimeWindow)) diff --git a/src/Microsoft.Azure.ServiceBus/Management/TopicDescriptionExtensions.cs b/src/Microsoft.Azure.ServiceBus/Management/TopicDescriptionExtensions.cs index bec4d147..94efd583 100644 --- a/src/Microsoft.Azure.ServiceBus/Management/TopicDescriptionExtensions.cs +++ b/src/Microsoft.Azure.ServiceBus/Management/TopicDescriptionExtensions.cs @@ -110,6 +110,25 @@ private static TopicDescription ParseFromEntryElement(XElement xEntry) case "AuthorizationRules": topicDesc.AuthorizationRules = AuthorizationRules.ParseFromXElement(element); break; + case "AccessedAt": + case "CreatedAt": + case "MessageCount": + case "SizeInBytes": + case "UpdatedAt": + case "CountDetails": + case "SubscriptionCount": + // Ignore known properties + // Do nothing + break; + default: + // For unknown properties, keep them as-is for forward proof. + if (topicDesc.UnknownProperties == null) + { + topicDesc.UnknownProperties = new List(); + } + + topicDesc.UnknownProperties.Add(element); + break; } } @@ -118,24 +137,34 @@ private static TopicDescription ParseFromEntryElement(XElement xEntry) public static XDocument Serialize(this TopicDescription description) { + var topicDescriptionElements = new List + { + description.DefaultMessageTimeToLive != TimeSpan.MaxValue ? new XElement(XName.Get("DefaultMessageTimeToLive", ManagementClientConstants.SbNs), XmlConvert.ToString(description.DefaultMessageTimeToLive)) : null, + new XElement(XName.Get("MaxSizeInMegabytes", ManagementClientConstants.SbNs), XmlConvert.ToString(description.MaxSizeInMB)), + new XElement(XName.Get("RequiresDuplicateDetection", ManagementClientConstants.SbNs), XmlConvert.ToString(description.RequiresDuplicateDetection)), + description.RequiresDuplicateDetection && description.DuplicateDetectionHistoryTimeWindow != default ? + new XElement(XName.Get("DuplicateDetectionHistoryTimeWindow", ManagementClientConstants.SbNs), XmlConvert.ToString(description.DuplicateDetectionHistoryTimeWindow)) + : null, + new XElement(XName.Get("EnableBatchedOperations", ManagementClientConstants.SbNs), XmlConvert.ToString(description.EnableBatchedOperations)), + description.AuthorizationRules?.Serialize(), + new XElement(XName.Get("Status", ManagementClientConstants.SbNs), description.Status.ToString()), + description.UserMetadata != null ? new XElement(XName.Get("UserMetadata", ManagementClientConstants.SbNs), description.UserMetadata) : null, + new XElement(XName.Get("SupportOrdering", ManagementClientConstants.SbNs), XmlConvert.ToString(description.SupportOrdering)), + description.AutoDeleteOnIdle != TimeSpan.MaxValue ? new XElement(XName.Get("AutoDeleteOnIdle", ManagementClientConstants.SbNs), XmlConvert.ToString(description.AutoDeleteOnIdle)) : null, + new XElement(XName.Get("EnablePartitioning", ManagementClientConstants.SbNs), XmlConvert.ToString(description.EnablePartitioning)) + }; + + if (description.UnknownProperties != null) + { + topicDescriptionElements.AddRange(description.UnknownProperties); + } + XDocument doc = new XDocument( new XElement(XName.Get("entry", ManagementClientConstants.AtomNs), new XElement(XName.Get("content", ManagementClientConstants.AtomNs), new XAttribute("type", "application/xml"), new XElement(XName.Get("TopicDescription", ManagementClientConstants.SbNs), - description.DefaultMessageTimeToLive != TimeSpan.MaxValue ? new XElement(XName.Get("DefaultMessageTimeToLive", ManagementClientConstants.SbNs), XmlConvert.ToString(description.DefaultMessageTimeToLive)) : null, - new XElement(XName.Get("MaxSizeInMegabytes", ManagementClientConstants.SbNs), XmlConvert.ToString(description.MaxSizeInMB)), - new XElement(XName.Get("RequiresDuplicateDetection", ManagementClientConstants.SbNs), XmlConvert.ToString(description.RequiresDuplicateDetection)), - description.RequiresDuplicateDetection && description.DuplicateDetectionHistoryTimeWindow != default ? - new XElement(XName.Get("DuplicateDetectionHistoryTimeWindow", ManagementClientConstants.SbNs), XmlConvert.ToString(description.DuplicateDetectionHistoryTimeWindow)) - : null, - new XElement(XName.Get("EnableBatchedOperations", ManagementClientConstants.SbNs), XmlConvert.ToString(description.EnableBatchedOperations)), - description.AuthorizationRules?.Serialize(), - new XElement(XName.Get("Status", ManagementClientConstants.SbNs), description.Status.ToString()), - description.UserMetadata != null ? new XElement(XName.Get("UserMetadata", ManagementClientConstants.SbNs), description.UserMetadata) : null, - new XElement(XName.Get("SupportOrdering", ManagementClientConstants.SbNs), XmlConvert.ToString(description.SupportOrdering)), - description.AutoDeleteOnIdle != TimeSpan.MaxValue ? new XElement(XName.Get("AutoDeleteOnIdle", ManagementClientConstants.SbNs), XmlConvert.ToString(description.AutoDeleteOnIdle)) : null, - new XElement(XName.Get("EnablePartitioning", ManagementClientConstants.SbNs), XmlConvert.ToString(description.EnablePartitioning)) + topicDescriptionElements )) )); diff --git a/test/Microsoft.Azure.ServiceBus.UnitTests/API/ApiApprovals.ApproveAzureServiceBus.approved.txt b/test/Microsoft.Azure.ServiceBus.UnitTests/API/ApiApprovals.ApproveAzureServiceBus.approved.txt index 6cf6f801..58825105 100644 --- a/test/Microsoft.Azure.ServiceBus.UnitTests/API/ApiApprovals.ApproveAzureServiceBus.approved.txt +++ b/test/Microsoft.Azure.ServiceBus.UnitTests/API/ApiApprovals.ApproveAzureServiceBus.approved.txt @@ -721,7 +721,7 @@ namespace Microsoft.Azure.ServiceBus.Management public Microsoft.Azure.ServiceBus.Management.EntityStatus Status { get; set; } public string UserMetadata { get; set; } public override bool Equals(object obj) { } - public bool Equals(Microsoft.Azure.ServiceBus.Management.QueueDescription other) { } + public bool Equals(Microsoft.Azure.ServiceBus.Management.QueueDescription otherDescription) { } public override int GetHashCode() { } } public class QueueRuntimeInfo From cb9c95e41f4290155d434802432187131e2c4030 Mon Sep 17 00:00:00 2001 From: Neeraj Makam Date: Mon, 24 Sep 2018 11:28:23 -0700 Subject: [PATCH 4/7] Bugfix #544 - Receiving deferred message from sessionful partitioned entity. (#574) For a partitioned session queue, sessionId is required for receiving deferred messages. Passing the value now. Fixes #544 --- .../Core/MessageReceiver.cs | 4 +++ .../TransportType.cs | 2 +- .../QueueSessionTests.cs | 26 +++++++++++++++++++ 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.Azure.ServiceBus/Core/MessageReceiver.cs b/src/Microsoft.Azure.ServiceBus/Core/MessageReceiver.cs index 3a4818a8..79fe5643 100644 --- a/src/Microsoft.Azure.ServiceBus/Core/MessageReceiver.cs +++ b/src/Microsoft.Azure.ServiceBus/Core/MessageReceiver.cs @@ -1141,6 +1141,10 @@ protected virtual async Task> OnReceiveDeferredMessageAsync(long[ } amqpRequestMessage.Map[ManagementConstants.Properties.SequenceNumbers] = sequenceNumbers; amqpRequestMessage.Map[ManagementConstants.Properties.ReceiverSettleMode] = (uint)(this.ReceiveMode == ReceiveMode.ReceiveAndDelete ? 0 : 1); + if (!string.IsNullOrWhiteSpace(this.SessionIdInternal)) + { + amqpRequestMessage.Map[ManagementConstants.Properties.SessionId] = this.SessionIdInternal; + } var response = await this.ExecuteRequestResponseAsync(amqpRequestMessage).ConfigureAwait(false); diff --git a/src/Microsoft.Azure.ServiceBus/TransportType.cs b/src/Microsoft.Azure.ServiceBus/TransportType.cs index 3c78a775..0252533b 100644 --- a/src/Microsoft.Azure.ServiceBus/TransportType.cs +++ b/src/Microsoft.Azure.ServiceBus/TransportType.cs @@ -18,7 +18,7 @@ public enum TransportType /// Uses AMQP over WebSockets /// /// This runs on port 443 with wss URI scheme. This could be used in scenarios where traffic to port 5671 is blocked. - /// To setup a proxy connection, please configure system default proxy. Proxy currently is supported only in net451+ framework. + /// To setup a proxy connection, please configure system default proxy. Proxy currently is supported only in .NET 4.5.1 and higher or .NET Core 2.1 and higher. AmqpWebSockets = 1 } } diff --git a/test/Microsoft.Azure.ServiceBus.UnitTests/QueueSessionTests.cs b/test/Microsoft.Azure.ServiceBus.UnitTests/QueueSessionTests.cs index 4d03b04f..38eb9afd 100644 --- a/test/Microsoft.Azure.ServiceBus.UnitTests/QueueSessionTests.cs +++ b/test/Microsoft.Azure.ServiceBus.UnitTests/QueueSessionTests.cs @@ -203,6 +203,32 @@ public async Task PeekSessionAsyncTest(string queueName) } } + [Theory] + [MemberData(nameof(TestPermutations))] + [DisplayTestMethodName] + public async Task ReceiveDeferredMessageForSessionTest(string qName) + { + var sessionId = Guid.NewGuid().ToString("N").Substring(0, 8); + var messageId = Guid.NewGuid().ToString("N").Substring(0, 8); + + var sender = new MessageSender(TestUtility.NamespaceConnectionString, qName); + await sender.SendAsync(new Message() { SessionId = sessionId, MessageId = messageId }); + + var sessionClient = new SessionClient(TestUtility.NamespaceConnectionString, qName); + var messageSession = await sessionClient.AcceptMessageSessionAsync(sessionId); + var msg = await messageSession.ReceiveAsync(); + var seqNum = msg.SystemProperties.SequenceNumber; + await messageSession.DeferAsync(msg.SystemProperties.LockToken); + var msg2 = await messageSession.ReceiveDeferredMessageAsync(seqNum); + + Assert.Equal(seqNum, msg2.SystemProperties.SequenceNumber); + Assert.Equal(messageId, msg2.MessageId); + + await sender.CloseAsync(); + await sessionClient.CloseAsync(); + await messageSession.CloseAsync(); + } + [Fact] [DisplayTestMethodName] public async Task AcceptSessionThrowsSessionCannotBeLockedException() From c91efcbead37f4897fe7309c562fd8308bb3f21a Mon Sep 17 00:00:00 2001 From: Neeraj Makam Date: Mon, 24 Sep 2018 15:08:41 -0700 Subject: [PATCH 5/7] Updating xmlDoc (#575) Fixes comments in #572 --- src/Microsoft.Azure.ServiceBus/Management/QueueDescription.cs | 4 ++-- .../Management/SubscriptionDescription.cs | 4 ++-- src/Microsoft.Azure.ServiceBus/Management/TopicDescription.cs | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Microsoft.Azure.ServiceBus/Management/QueueDescription.cs b/src/Microsoft.Azure.ServiceBus/Management/QueueDescription.cs index ac68eb14..e8dd924f 100644 --- a/src/Microsoft.Azure.ServiceBus/Management/QueueDescription.cs +++ b/src/Microsoft.Azure.ServiceBus/Management/QueueDescription.cs @@ -285,8 +285,8 @@ public string UserMetadata } /// - /// List of properties that were retrieved using GetQueue but is not understood by this version of client is stored here. - /// These will be sent back to the service as-is when UpdateQueue is called on this QueueDescription. + /// List of properties that were retrieved using GetQueue but are not understood by this version of client is stored here. + /// The list will be sent back when an already retrieved QueueDescription will be used in UpdateQueue call. /// internal List UnknownProperties { get; set; } diff --git a/src/Microsoft.Azure.ServiceBus/Management/SubscriptionDescription.cs b/src/Microsoft.Azure.ServiceBus/Management/SubscriptionDescription.cs index ad9d7e88..693a9ec1 100644 --- a/src/Microsoft.Azure.ServiceBus/Management/SubscriptionDescription.cs +++ b/src/Microsoft.Azure.ServiceBus/Management/SubscriptionDescription.cs @@ -250,8 +250,8 @@ public string UserMetadata } /// - /// List of properties that were retrieved using GetSubscription but is not understood by this version of client is stored here. - /// These will be sent back to the service as-is when UpdateSubscription is called on this SubscriptionDescription. + /// List of properties that were retrieved using GetSubscription but are not understood by this version of client is stored here. + /// The list will be sent back when an already retrieved SubscriptionDescription will be used in UpdateSubscription call. /// internal List UnknownProperties { get; set; } diff --git a/src/Microsoft.Azure.ServiceBus/Management/TopicDescription.cs b/src/Microsoft.Azure.ServiceBus/Management/TopicDescription.cs index d279a54d..7f600b63 100644 --- a/src/Microsoft.Azure.ServiceBus/Management/TopicDescription.cs +++ b/src/Microsoft.Azure.ServiceBus/Management/TopicDescription.cs @@ -189,8 +189,8 @@ public string UserMetadata } /// - /// List of properties that were retrieved using GetTopic but is not understood by this version of client is stored here. - /// These will be sent back to the service as-is when UpdateTopic is called on this TopicDescription. + /// List of properties that were retrieved using GetTopic but are not understood by this version of client is stored here. + /// The list will be sent back when an already retrieved TopicDescription will be used in UpdateTopic call. /// internal List UnknownProperties { get; set; } From b7b1106cfb8a63b4989d85e9ad3055a3158b8a18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20N=C3=B8vik?= Date: Tue, 25 Sep 2018 02:28:35 +0200 Subject: [PATCH 6/7] Gracefully handle RenewMessageLockTask (#548) In the MessageReceivePump class the RenewMessageLockTask is now canceled gracefully instead of raise a TaskCancelledException and the catch it again. The code now doesn't await the canceled task, but await a new task which just returns the canceled task. And check if the delay task is canceled to determine if the RenewMessageLockTask should be completed or not. Fixes #547. --- .../MessageReceivePump.cs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/Microsoft.Azure.ServiceBus/MessageReceivePump.cs b/src/Microsoft.Azure.ServiceBus/MessageReceivePump.cs index 99a08427..fa517e95 100644 --- a/src/Microsoft.Azure.ServiceBus/MessageReceivePump.cs +++ b/src/Microsoft.Azure.ServiceBus/MessageReceivePump.cs @@ -231,7 +231,16 @@ async Task RenewMessageLockTask(Message message, CancellationToken renewLockCanc { var amount = MessagingUtilities.CalculateRenewAfterDuration(message.SystemProperties.LockedUntilUtc); MessagingEventSource.Log.MessageReceiverPumpRenewMessageStart(this.messageReceiver.ClientId, message, amount); - await Task.Delay(amount, renewLockCancellationToken).ConfigureAwait(false); + + // We're awaiting the task created by 'ContinueWith' to avoid awaiting the Delay task which may be canceled + // by the renewLockCancellationToken. This way we prevent a TaskCanceledException. + var delayTask = await Task.Delay(amount, renewLockCancellationToken) + .ContinueWith(t => t, TaskContinuationOptions.ExecuteSynchronously) + .ConfigureAwait(false); + if (delayTask.IsCanceled) + { + break; + } if (!this.pumpCancellationToken.IsCancellationRequested && !renewLockCancellationToken.IsCancellationRequested) @@ -248,10 +257,9 @@ async Task RenewMessageLockTask(Message message, CancellationToken renewLockCanc { MessagingEventSource.Log.MessageReceiverPumpRenewMessageException(this.messageReceiver.ClientId, message, exception); - // TaskCanceled is expected here as renewTasks will be cancelled after the Complete call is made. - // ObjectDisposedException should only happen here because the CancellationToken was disposed at which point + // ObjectDisposedException should only happen here because the CancellationToken was disposed at which point // this renew exception is not relevant anymore. Lets not bother user with this exception. - if (!(exception is TaskCanceledException) && !(exception is ObjectDisposedException)) + if (!(exception is ObjectDisposedException)) { await this.RaiseExceptionReceived(exception, ExceptionReceivedEventArgsAction.RenewLock).ConfigureAwait(false); } From 989af3b2ce8d1cfc04f2c72ccd11b01dd5958d14 Mon Sep 17 00:00:00 2001 From: Neeraj Makam Date: Fri, 28 Sep 2018 14:30:22 -0700 Subject: [PATCH 7/7] Bumping version to 3.1.1. (#577) Adding copyrights to nuspec --- .../Microsoft.Azure.ServiceBus.csproj | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.Azure.ServiceBus/Microsoft.Azure.ServiceBus.csproj b/src/Microsoft.Azure.ServiceBus/Microsoft.Azure.ServiceBus.csproj index d4faa8c4..d1b2e44a 100644 --- a/src/Microsoft.Azure.ServiceBus/Microsoft.Azure.ServiceBus.csproj +++ b/src/Microsoft.Azure.ServiceBus/Microsoft.Azure.ServiceBus.csproj @@ -2,8 +2,9 @@ This is the next generation Azure Service Bus .NET Standard client library that focuses on queues & topics. For more information about Service Bus, see https://azure.microsoft.com/en-us/services/service-bus/ - 3.1.0 + 3.1.1 Microsoft + © Microsoft Corporation. All rights reserved. netstandard2.0 ../../build/keyfile.snk true