From d95c82b7d47a4f3b3b20c715424619fe22502629 Mon Sep 17 00:00:00 2001 From: Matias Quaranta Date: Fri, 30 Aug 2019 12:09:18 -0700 Subject: [PATCH 1/7] Gateway should return body if available --- .../src/GatewayStoreClient.cs | 28 +++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/GatewayStoreClient.cs b/Microsoft.Azure.Cosmos/src/GatewayStoreClient.cs index af8e9c114d..ef3c54e796 100644 --- a/Microsoft.Azure.Cosmos/src/GatewayStoreClient.cs +++ b/Microsoft.Azure.Cosmos/src/GatewayStoreClient.cs @@ -82,20 +82,16 @@ internal static async Task ParseResponseAsync(HttpRespo { if ((int)responseMessage.StatusCode < 400) { - MemoryStream bufferedStream = new MemoryStream(); - - await responseMessage.Content.CopyToAsync(bufferedStream); - - bufferedStream.Position = 0; - INameValueCollection headers = GatewayStoreClient.ExtractResponseHeaders(responseMessage); - return new DocumentServiceResponse(bufferedStream, headers, responseMessage.StatusCode, serializerSettings); + Stream contentStream = await GatewayStoreClient.BufferContentIfAvailableAsync(responseMessage); + return new DocumentServiceResponse(contentStream, headers, responseMessage.StatusCode, serializerSettings); } else if (request != null && request.IsValidStatusCodeForExceptionlessRetry((int)responseMessage.StatusCode)) { INameValueCollection headers = GatewayStoreClient.ExtractResponseHeaders(responseMessage); - return new DocumentServiceResponse(null, headers, responseMessage.StatusCode, serializerSettings); + Stream contentStream = await GatewayStoreClient.BufferContentIfAvailableAsync(responseMessage); + return new DocumentServiceResponse(contentStream, headers, responseMessage.StatusCode, serializerSettings); } else { @@ -226,6 +222,22 @@ internal static bool IsAllowedRequestHeader(string headerName) return true; } + private static async Task BufferContentIfAvailableAsync(HttpResponseMessage responseMessage) + { + if (responseMessage.Content == null) + { + return null; + } + + MemoryStream bufferedStream = new MemoryStream(); + + await responseMessage.Content.CopyToAsync(bufferedStream); + + bufferedStream.Position = 0; + + return bufferedStream; + } + [SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope", Justification = "Disposable object returned by method")] private async Task PrepareRequestMessageAsync( DocumentServiceRequest request, From f4002cc7d29283cd21bdd56d9524c87e14d3dff7 Mon Sep 17 00:00:00 2001 From: Matias Quaranta Date: Fri, 30 Aug 2019 12:16:16 -0700 Subject: [PATCH 2/7] Ensure error message is present --- .../src/Handler/ResponseMessage.cs | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/Microsoft.Azure.Cosmos/src/Handler/ResponseMessage.cs b/Microsoft.Azure.Cosmos/src/Handler/ResponseMessage.cs index b5c1ea917c..465a987e20 100644 --- a/Microsoft.Azure.Cosmos/src/Handler/ResponseMessage.cs +++ b/Microsoft.Azure.Cosmos/src/Handler/ResponseMessage.cs @@ -131,6 +131,7 @@ public virtual ResponseMessage EnsureSuccessStatusCode() { if (!this.IsSuccessStatusCode) { + this.EnsureErrorMessage(); string message = $"Response status code does not indicate success: {(int)this.StatusCode} Substatus: {(int)this.Headers.SubStatusCode} Reason: ({this.ErrorMessage})."; throw new CosmosException( @@ -197,5 +198,31 @@ private void CheckDisposed() throw new ObjectDisposedException(this.GetType().ToString()); } } + + private void EnsureErrorMessage() + { + if (this.Error != null + || !string.IsNullOrEmpty(this.ErrorMessage)) + { + return; + } + + if (this.content != null && this.content.CanRead) + { + Error error = Resource.LoadFrom(this.content); + if (error != null) + { + // Error format is not consistent across modes + if (!string.IsNullOrEmpty(error.Message)) + { + this.ErrorMessage = error.Message; + } + else + { + this.ErrorMessage = error.ToString(); + } + } + } + } } } \ No newline at end of file From 349d638371dd5d546676b54de18abaf0b00d12d0 Mon Sep 17 00:00:00 2001 From: Matias Quaranta Date: Fri, 30 Aug 2019 12:40:28 -0700 Subject: [PATCH 3/7] Test to validate gateway response --- .../GatewayStoreModelTest.cs | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/GatewayStoreModelTest.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/GatewayStoreModelTest.cs index 4268ca8870..ebdc9341ea 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/GatewayStoreModelTest.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/GatewayStoreModelTest.cs @@ -148,6 +148,58 @@ public async Task TestRetries() } + [TestMethod] + public async Task TestErrorResponsesProvideBody() + { + string testContent = "Content"; + Func> sendFunc = async request => + { + return new HttpResponseMessage(HttpStatusCode.Conflict) { Content = new StringContent(testContent) }; + }; + + Mock mockDocumentClient = new Mock(); + mockDocumentClient.Setup(client => client.ServiceEndpoint).Returns(new Uri("https://foo")); + + GlobalEndpointManager endpointManager = new GlobalEndpointManager(mockDocumentClient.Object, new ConnectionPolicy()); + ISessionContainer sessionContainer = new SessionContainer(string.Empty); + DocumentClientEventSource eventSource = DocumentClientEventSource.Instance; + HttpMessageHandler messageHandler = new MockMessageHandler(sendFunc); + GatewayStoreModel storeModel = new GatewayStoreModel( + endpointManager, + sessionContainer, + TimeSpan.FromSeconds(5), + ConsistencyLevel.Eventual, + eventSource, + null, + new UserAgentContainer(), + ApiType.None, + messageHandler); + + using (new ActivityScope(Guid.NewGuid())) + { + using (DocumentServiceRequest request = + DocumentServiceRequest.Create( + Documents.OperationType.Query, + Documents.ResourceType.Document, + new Uri("https://foo.com/dbs/db1/colls/coll1", UriKind.Absolute), + new MemoryStream(Encoding.UTF8.GetBytes("content1")), + AuthorizationTokenType.PrimaryMasterKey, + null)) + { + request.UseStatusCodeForFailures = true; + request.UseStatusCodeFor429 = true; + + DocumentServiceResponse response = await storeModel.ProcessMessageAsync(request); + Assert.IsNotNull(response.ResponseBody); + using (StreamReader reader = new StreamReader(response.ResponseBody)) + { + Assert.AreEqual(testContent, await reader.ReadToEndAsync()); + } + } + } + + } + /// /// Tests that empty session token is sent for operations on Session Consistent resources like /// Databases, Collections, Users, Permissions, PartitionKeyRanges, DatabaseAccounts and Offers From 9874a57f96aeaecf454471b13b38e96bfb8f6e86 Mon Sep 17 00:00:00 2001 From: Matias Quaranta Date: Fri, 30 Aug 2019 13:44:51 -0700 Subject: [PATCH 4/7] Json support --- .../src/Handler/ResponseMessage.cs | 29 ++++++++++++++----- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Handler/ResponseMessage.cs b/Microsoft.Azure.Cosmos/src/Handler/ResponseMessage.cs index 465a987e20..3ef5a6d2c8 100644 --- a/Microsoft.Azure.Cosmos/src/Handler/ResponseMessage.cs +++ b/Microsoft.Azure.Cosmos/src/Handler/ResponseMessage.cs @@ -207,19 +207,32 @@ private void EnsureErrorMessage() return; } - if (this.content != null && this.content.CanRead) + if (this.content != null + && this.content.CanRead) { - Error error = Resource.LoadFrom(this.content); - if (error != null) + try { - // Error format is not consistent across modes - if (!string.IsNullOrEmpty(error.Message)) + Error error = Resource.LoadFrom(this.content); + if (error != null) { - this.ErrorMessage = error.Message; + // Error format is not consistent across modes + if (!string.IsNullOrEmpty(error.Message)) + { + this.ErrorMessage = error.Message; + } + else + { + this.ErrorMessage = error.ToString(); + } } - else + } + catch (Newtonsoft.Json.JsonReaderException) + { + // Content is not Json + this.content.Position = 0; + using (StreamReader streamReader = new StreamReader(this.content)) { - this.ErrorMessage = error.ToString(); + this.ErrorMessage = streamReader.ReadToEnd(); } } } From ea7b2b04730dcf570d5e75429811961d9249682f Mon Sep 17 00:00:00 2001 From: Matias Quaranta Date: Fri, 30 Aug 2019 13:45:05 -0700 Subject: [PATCH 5/7] EnsureSuccessStatus tests --- .../CosmosExceptionTests.cs | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosExceptionTests.cs diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosExceptionTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosExceptionTests.cs new file mode 100644 index 0000000000..874be09dc4 --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosExceptionTests.cs @@ -0,0 +1,90 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos +{ + using System; + using System.IO; + using System.Net; + using System.Net.Http; + using System.Text; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.Azure.Cosmos.Common; + using Microsoft.Azure.Cosmos.Routing; + using Microsoft.Azure.Documents; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using Moq; + using Newtonsoft.Json; + + [TestClass] + public class CosmosExceptionTests + { + [TestMethod] + public void EnsureSuccessStatusCode_DontThrowOnSuccess() + { + ResponseMessage responseMessage = new ResponseMessage(HttpStatusCode.OK); + responseMessage.EnsureSuccessStatusCode(); + } + + [TestMethod] + public void EnsureSuccessStatusCode_ThrowsOnFailure() + { + ResponseMessage responseMessage = new ResponseMessage(HttpStatusCode.NotFound); + Assert.ThrowsException(() => responseMessage.EnsureSuccessStatusCode()); + } + + [TestMethod] + public void EnsureSuccessStatusCode_ThrowsOnFailure_ContainsBody() + { + string testContent = "TestContent"; + using (MemoryStream memoryStream = new MemoryStream()) + { + StreamWriter sw = new StreamWriter(memoryStream); + sw.Write(testContent); + sw.Flush(); + memoryStream.Seek(0, SeekOrigin.Begin); + + ResponseMessage responseMessage = new ResponseMessage(HttpStatusCode.NotFound) { Content = memoryStream }; + try + { + responseMessage.EnsureSuccessStatusCode(); + Assert.Fail("Should have thrown"); + } + catch(CosmosException exception) + { + Assert.IsTrue(exception.Message.Contains(testContent)); + } + } + } + + [TestMethod] + public void EnsureSuccessStatusCode_ThrowsOnFailure_ContainsJsonBody() + { + string message = "TestContent"; + Error error = new Error(); + error.Code = "code"; + error.Message = message; + string testContent = JsonConvert.SerializeObject(error); + using (MemoryStream memoryStream = new MemoryStream()) + { + StreamWriter sw = new StreamWriter(memoryStream); + sw.Write(testContent); + sw.Flush(); + memoryStream.Seek(0, SeekOrigin.Begin); + + ResponseMessage responseMessage = new ResponseMessage(HttpStatusCode.NotFound) { Content = memoryStream }; + try + { + responseMessage.EnsureSuccessStatusCode(); + Assert.Fail("Should have thrown"); + } + catch (CosmosException exception) + { + Assert.IsTrue(exception.Message.Contains(message)); + } + } + } + } +} From fee823fd2afc40be99a275e2df9d75954e595f74 Mon Sep 17 00:00:00 2001 From: Matias Quaranta Date: Fri, 30 Aug 2019 13:50:17 -0700 Subject: [PATCH 6/7] Extra lines --- Microsoft.Azure.Cosmos/src/GatewayStoreClient.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/GatewayStoreClient.cs b/Microsoft.Azure.Cosmos/src/GatewayStoreClient.cs index ef3c54e796..f7d0364f8f 100644 --- a/Microsoft.Azure.Cosmos/src/GatewayStoreClient.cs +++ b/Microsoft.Azure.Cosmos/src/GatewayStoreClient.cs @@ -230,11 +230,8 @@ private static async Task BufferContentIfAvailableAsync(HttpResponseMess } MemoryStream bufferedStream = new MemoryStream(); - await responseMessage.Content.CopyToAsync(bufferedStream); - bufferedStream.Position = 0; - return bufferedStream; } From 239e1185e75c128ae66aff9120be735ea2030b26 Mon Sep 17 00:00:00 2001 From: Matias Quaranta Date: Fri, 30 Aug 2019 16:20:50 -0700 Subject: [PATCH 7/7] changelog --- changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/changelog.md b/changelog.md index 7739173610..5ddec6a70c 100644 --- a/changelog.md +++ b/changelog.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - [#726](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/726) Query iterator HasMoreResults now returns false if an exception is hit +- [#753](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/753) Reason was not being propagated for Conflict exceptions ## [3.1.1](https://www.nuget.org/packages/Microsoft.Azure.Cosmos/3.1.1) - 2019-08-12