From 5015eca6d497e317a28ed0dc3bc013315c34b53b Mon Sep 17 00:00:00 2001 From: Nishant <68707510+nimanch@users.noreply.github.com> Date: Tue, 23 Nov 2021 21:53:42 -0800 Subject: [PATCH] Handle Return Code From Get Module Logs Failure (#5846) The goal of this PR is to handle return code correctly when calling get module logs. Earlier Behavior 1. A Request to Edgelet component with an incorrect argument, returned a Bad Request but was never handled and lead to Stream Parsing failure when trying to parse module logs , giving a cryptic error New Behavior 1. Edge-Agent checks the correct HTTP Status code before trying to parse the Module Log Response Testing 1. Added Test Case for both Happy-Path and Bad-Path in TestGetModuleLogs Test in E2E Test ## Azure IoT Edge PR checklist: --- .../ModuleManagementHttpClientVersioned.cs | 12 ++++++ .../IotHub.cs | 26 +++++++------ .../EdgeAgentDirectMethods.cs | 38 +++++++++++++++++++ 3 files changed, 64 insertions(+), 12 deletions(-) diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Edgelet/versioning/ModuleManagementHttpClientVersioned.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Edgelet/versioning/ModuleManagementHttpClientVersioned.cs index 17003318b4f..a14acbd64ed 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Edgelet/versioning/ModuleManagementHttpClientVersioned.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Edgelet/versioning/ModuleManagementHttpClientVersioned.cs @@ -5,6 +5,7 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Edgelet.Versioning using System.Collections.Generic; using System.Globalization; using System.IO; + using System.Net; using System.Net.Http; using System.Net.Sockets; using System.Runtime.InteropServices; @@ -108,6 +109,17 @@ public virtual async Task GetModuleLogs(string module, bool follow, Opti async () => { HttpResponseMessage httpResponseMessage = await httpClient.SendAsync(httpRequest, HttpCompletionOption.ResponseHeadersRead, cancellationToken); + if (!httpResponseMessage.IsSuccessStatusCode) + { + switch (httpResponseMessage.StatusCode) + { + case HttpStatusCode.BadRequest: + throw new ArgumentException($"Request Returned Status Code {httpResponseMessage.StatusCode} with Message {await httpResponseMessage.Content.ReadAsStringAsync()}"); + default: + throw new EdgeletCommunicationException(await httpResponseMessage.Content.ReadAsStringAsync(), (int)httpResponseMessage.StatusCode); + } + } + return await httpResponseMessage.Content.ReadAsStreamAsync(); }, $"Get logs for {module}"); diff --git a/test/Microsoft.Azure.Devices.Edge.Test.Common/IotHub.cs b/test/Microsoft.Azure.Devices.Edge.Test.Common/IotHub.cs index 6fc017b5836..fed41c2fecc 100644 --- a/test/Microsoft.Azure.Devices.Edge.Test.Common/IotHub.cs +++ b/test/Microsoft.Azure.Devices.Edge.Test.Common/IotHub.cs @@ -92,17 +92,17 @@ public async Task CreateEdgeDeviceIdentityAsync(string deviceId, Option< string parentDeviceScope = parentDevice == null ? string.Empty : parentDevice.Scope; Log.Verbose($"Parent scope: {parentDeviceScope}"); var result = new Device(deviceId) - { - Authentication = new AuthenticationMechanism() - { - Type = authType, - X509Thumbprint = x509Thumbprint - }, - Capabilities = new DeviceCapabilities() - { - IotEdge = true - } - }; + { + Authentication = new AuthenticationMechanism() + { + Type = authType, + X509Thumbprint = x509Thumbprint + }, + Capabilities = new DeviceCapabilities() + { + IotEdge = true + } + }; result.ParentScopes.Add(parentDeviceScope); return result; }, @@ -184,7 +184,9 @@ public Task InvokeMethodAsync( { Log.Verbose($"Method '{method.MethodName}' on '{deviceId}/{moduleId}' returned: " + $"{result.Status}\n{result.GetPayloadAsJson()}"); - return result.Status == 200; + + // No Need to retry when server returns Bad Request. + return result.Status == 200 || result.Status == 400; }, e => { diff --git a/test/Microsoft.Azure.Devices.Edge.Test/EdgeAgentDirectMethods.cs b/test/Microsoft.Azure.Devices.Edge.Test/EdgeAgentDirectMethods.cs index e78d71decfc..5a51421edf9 100644 --- a/test/Microsoft.Azure.Devices.Edge.Test/EdgeAgentDirectMethods.cs +++ b/test/Microsoft.Azure.Devices.Edge.Test/EdgeAgentDirectMethods.cs @@ -56,6 +56,7 @@ await this.runtime.DeployConfigurationAsync( Context.Current.NestedEdge); await Task.Delay(30000); + // Verify RFC3339 Operation string since = DateTime.Now.AddDays(-1).ToString("yyyy-MM-dd'T'HH:mm:ssZ"); string until = DateTime.Now.AddDays(+1).ToString("yyyy-MM-dd'T'HH:mm:ssZ"); @@ -68,6 +69,43 @@ await this.runtime.DeployConfigurationAsync( string expected = string.Join('\n', Enumerable.Range(0, count)) + "\n"; LogResponse response = JsonConvert.DeserializeObject(result.GetPayloadAsJson()).Single(); Assert.AreEqual(expected, response.Payload); + + // Verify Unix Time Operation + since = DateTime.Now.AddDays(-1).ToUnixTimestamp().ToString(); + until = DateTime.Now.AddDays(+1).ToUnixTimestamp().ToString(); + + request = new ModuleLogsRequest("1.0", new List { new LogRequestItem(moduleName, new ModuleLogFilter(Option.Some(10), Option.Some(since), Option.Some(until), Option.None(), Option.None(), Option.None())) }, LogsContentEncoding.None, LogsContentType.Text); + + result = await this.IotHub.InvokeMethodAsync(this.runtime.DeviceId, ConfigModuleName.EdgeAgent, new CloudToDeviceMethod("GetModuleLogs", TimeSpan.FromSeconds(300), TimeSpan.FromSeconds(300)).SetPayloadJson(JsonConvert.SerializeObject(request)), token); + + Assert.AreEqual((int)HttpStatusCode.OK, result.Status); + + expected = string.Join('\n', Enumerable.Range(0, count)) + "\n"; + response = JsonConvert.DeserializeObject(result.GetPayloadAsJson()).Single(); + Assert.AreEqual(expected, response.Payload); + + // Verify Human Readable Time Operation + since = "1 hour".ToString(); + until = "1 second".ToString(); + + request = new ModuleLogsRequest("1.0", new List { new LogRequestItem(moduleName, new ModuleLogFilter(Option.Some(10), Option.Some(since), Option.Some(until), Option.None(), Option.None(), Option.None())) }, LogsContentEncoding.None, LogsContentType.Text); + + result = await this.IotHub.InvokeMethodAsync(this.runtime.DeviceId, ConfigModuleName.EdgeAgent, new CloudToDeviceMethod("GetModuleLogs", TimeSpan.FromSeconds(300), TimeSpan.FromSeconds(300)).SetPayloadJson(JsonConvert.SerializeObject(request)), token); + + Assert.AreEqual((int)HttpStatusCode.OK, result.Status); + + expected = string.Join('\n', Enumerable.Range(0, count)) + "\n"; + response = JsonConvert.DeserializeObject(result.GetPayloadAsJson()).Single(); + Assert.AreEqual(expected, response.Payload); + + // Verify Incorrect Timestamp gives correct error + since = DateTime.Now.AddDays(-1).ToString("yyyy-MM-dd'T'HH:mm"); + until = DateTime.Now.AddDays(+1).ToString("yyyy-MM-dd'T'HH:mm"); + + request = new ModuleLogsRequest("1.0", new List { new LogRequestItem(moduleName, new ModuleLogFilter(Option.Some(10), Option.Some(since), Option.Some(until), Option.None(), Option.None(), Option.None())) }, LogsContentEncoding.None, LogsContentType.Text); + + result = await this.IotHub.InvokeMethodAsync(this.runtime.DeviceId, ConfigModuleName.EdgeAgent, new CloudToDeviceMethod("GetModuleLogs", TimeSpan.FromSeconds(300), TimeSpan.FromSeconds(300)).SetPayloadJson(JsonConvert.SerializeObject(request)), token); + Assert.AreEqual((int)HttpStatusCode.BadRequest, result.Status); } [Test]