diff --git a/WEB/Src/WindowsServer/WindowsServer.Tests/AzureInstanceMetadataEndToEndTests.cs b/WEB/Src/WindowsServer/WindowsServer.Tests/AzureInstanceMetadataEndToEndTests.cs index 9711b95569..9ba06a6e6d 100644 --- a/WEB/Src/WindowsServer/WindowsServer.Tests/AzureInstanceMetadataEndToEndTests.cs +++ b/WEB/Src/WindowsServer/WindowsServer.Tests/AzureInstanceMetadataEndToEndTests.cs @@ -3,16 +3,20 @@ namespace Microsoft.ApplicationInsights.WindowsServer using System; using System.IO; using System.Net; + using System.Net.Http; using System.Runtime.Serialization.Json; using System.Text; using System.Threading; + using System.Threading.Tasks; + using Microsoft.ApplicationInsights.WindowsServer.Implementation; using Microsoft.ApplicationInsights.WindowsServer.Implementation.DataContracts; using Microsoft.ApplicationInsights.WindowsServer.Mock; -#if NETCOREAPP - using Microsoft.AspNetCore.Http; -#endif using Microsoft.VisualStudio.TestTools.UnitTesting; + + using Moq; + using Moq.Protected; + using Assert = Xunit.Assert; /// @@ -20,237 +24,156 @@ namespace Microsoft.ApplicationInsights.WindowsServer /// end to end functionality as closely as possible. /// [TestClass] -#if NETCOREAPP - [Ignore("Problems with in-proc test server. These tests are temporarially disabled while working on a fix. See #2357 and #2355.")] -#endif public class AzureInstanceMetadataEndToEndTests { - internal const string MockTestUri = "http://localhost:9922/"; - [TestMethod] - public void SpoofedResponseFromAzureIMSDoesntCrash() + public async Task SpoofedResponseFromAzureIMSDoesntCrash() { - var testMetadata = this.GetTestMetadata(); - string testPath = "spoofedResponse"; + // SETUP + var testMetadata = GetTestMetadata(); + Mock mockHttpMessageHandler = GetMockHttpMessageHandler(testMetadata); + var azureIms = new AzureMetadataRequestor(new HttpClient(mockHttpMessageHandler.Object)); - using (new AzureInstanceMetadataServiceMock( - AzureInstanceMetadataEndToEndTests.MockTestUri, - testPath, - (response) => - { - response.StatusCode = (int)HttpStatusCode.OK; + // ACT + var azureImsProps = new AzureComputeMetadataHeartbeatPropertyProvider(); + var azureIMSData = await azureIms.GetAzureComputeMetadataAsync(); - var jsonStream = this.GetTestMetadataStream(testMetadata); - response.SetContentLength(jsonStream.Length); - response.ContentType = "application/json"; - response.SetContentEncoding(Encoding.UTF8); - response.WriteStreamToBody(jsonStream); - })) + // VERIFY + foreach (string fieldName in azureImsProps.ExpectedAzureImsFields) { - var azureIms = new AzureMetadataRequestor - { - BaseAimsUri = string.Concat(AzureInstanceMetadataEndToEndTests.MockTestUri, testPath, "/") - }; - - var azureImsProps = new AzureComputeMetadataHeartbeatPropertyProvider(); - var azureIMSData = azureIms.GetAzureComputeMetadataAsync(); - azureIMSData.Wait(); - - foreach (string fieldName in azureImsProps.ExpectedAzureImsFields) - { - string fieldValue = azureIMSData.Result.GetValueForField(fieldName); - Assert.NotNull(fieldValue); - Assert.Equal(fieldValue, testMetadata.GetValueForField(fieldName)); - } + string fieldValue = azureIMSData.GetValueForField(fieldName); + Assert.NotNull(fieldValue); + Assert.Equal(fieldValue, testMetadata.GetValueForField(fieldName)); } } [TestMethod] - public void AzureImsResponseTooLargeStopsCollection() + public async Task AzureImsResponseExcludesMalformedValues() { - string testPath = "tooLarge"; - - using (new AzureInstanceMetadataServiceMock( - AzureInstanceMetadataEndToEndTests.MockTestUri, - testPath, - (response) => - { - response.StatusCode = (int)HttpStatusCode.OK; - - var tester = this.GetTestMetadata(); - - // ensure we will be outside the max allowed content size by setting a single text field to max length + 1 - var testStuff = new char[AzureMetadataRequestor.AzureImsMaxResponseBufferSize + 1]; - for (int i = 0; i < (AzureMetadataRequestor.AzureImsMaxResponseBufferSize + 1); ++i) - { - testStuff[i] = (char)((int)'a' + (i % 26)); - } + // SETUP + var testMetadata = GetTestMetadata(); + // make it a malicious-ish response... + testMetadata.Name = "Not allowed for VM names"; + testMetadata.ResourceGroupName = "Not allowed for resource group name"; + testMetadata.SubscriptionId = "Definitely-not-a GUID up here"; + + Mock mockHttpMessageHandler = GetMockHttpMessageHandler(testMetadata); + var azureIms = new AzureMetadataRequestor(new HttpClient(mockHttpMessageHandler.Object)); + + // ACT + var azureImsProps = new AzureComputeMetadataHeartbeatPropertyProvider(azureIms); + var hbeatProvider = new HeartbeatProviderMock(); + var result = await azureImsProps.SetDefaultPayloadAsync(hbeatProvider); + + // VERIFY + Assert.True(result); + Assert.Empty(hbeatProvider.HbeatProps["azInst_name"]); + Assert.Empty(hbeatProvider.HbeatProps["azInst_resourceGroupName"]); + Assert.Empty(hbeatProvider.HbeatProps["azInst_subscriptionId"]); + } - tester.Publisher = new string(testStuff); - var jsonStream = this.GetTestMetadataStream(tester); - response.SetContentLength(3 * jsonStream.Length); - response.ContentType = "application/json"; - response.SetContentEncoding(Encoding.UTF8); - response.WriteStreamToBody(jsonStream); - })) - { - var azureIms = new AzureMetadataRequestor - { - BaseAimsUri = string.Concat(AzureInstanceMetadataEndToEndTests.MockTestUri, testPath, "/") - }; + [TestMethod] + public async Task AzureImsResponseHandlesException() + { + // SETUP + var testMetadata = GetTestMetadata(); + var mockHttpMessageHandler = GetMockHttpMessageHandler(testMetadata, throwException: true); + var azureIms = new AzureMetadataRequestor(new HttpClient(mockHttpMessageHandler.Object)); - var azureIMSData = azureIms.GetAzureComputeMetadataAsync(); - azureIMSData.Wait(); + // ACT + var result = await azureIms.GetAzureComputeMetadataAsync(); - Assert.Null(azureIMSData.Result); - } + // VERIFY + Assert.Null(result); } [TestMethod] - public void AzureImsResponseExcludesMalformedValues() + public async Task AzureImsResponseUnsuccessful() { - string testPath = "malformedValues"; - using (new AzureInstanceMetadataServiceMock( - AzureInstanceMetadataEndToEndTests.MockTestUri, - testPath, - (response) => - { - response.StatusCode = (int)HttpStatusCode.OK; - - // make it a malicious-ish response... - var malformedData = this.GetTestMetadata(); - malformedData.Name = "Not allowed for VM names"; - malformedData.ResourceGroupName = "Not allowed for resource group name"; - malformedData.SubscriptionId = "Definitely-not-a GUID up here"; - var malformedJsonStream = this.GetTestMetadataStream(malformedData); - - response.SetContentLength(malformedJsonStream.Length); - response.ContentType = "application/json"; - response.SetContentEncoding(Encoding.UTF8); - response.WriteStreamToBody(malformedJsonStream); - })) - { - var azureIms = new AzureMetadataRequestor - { - BaseAimsUri = string.Concat(AzureInstanceMetadataEndToEndTests.MockTestUri, testPath, "/") - }; + // SETUP + var testMetadata = GetTestMetadata(); + var mockHttpMessageHandler = GetMockHttpMessageHandler(testMetadata, HttpStatusCode.Forbidden); + var azureIms = new AzureMetadataRequestor(new HttpClient(mockHttpMessageHandler.Object)); - var azureImsProps = new AzureComputeMetadataHeartbeatPropertyProvider(azureIms); - var hbeatProvider = new HeartbeatProviderMock(); - var azureIMSData = azureImsProps.SetDefaultPayloadAsync(hbeatProvider); - azureIMSData.Wait(); + // ACT + var azureIMSData = await azureIms.GetAzureComputeMetadataAsync(); - Assert.Empty(hbeatProvider.HbeatProps["azInst_name"]); - Assert.Empty(hbeatProvider.HbeatProps["azInst_resourceGroupName"]); - Assert.Empty(hbeatProvider.HbeatProps["azInst_subscriptionId"]); - } + // VERIFY + Assert.Null(azureIMSData); } - [TestMethod] - public void AzureImsResponseTimesOut() + /// + /// Creates test data for heartbeat e2e. + /// + /// An Azure Instance Metadata Compute object suitable for use in testing. + private static AzureInstanceComputeMetadata GetTestMetadata() => new AzureInstanceComputeMetadata() + { + Location = "US-West", + Name = "test-vm01", + Offer = "D9_USWest", + OsType = "Linux", + PlacementGroupId = "placement-grp", + PlatformFaultDomain = "0", + PlatformUpdateDomain = "0", + Publisher = "Microsoft", + ResourceGroupName = "test.resource-group_01", + Sku = "Windows_10", + SubscriptionId = Guid.NewGuid().ToString(), + Tags = "thisTag;thatTag", + Version = "10.8a", + VmId = Guid.NewGuid().ToString(), + VmSize = "A8", + VmScaleSetName = "ScaleName" + }; + + private static string SerializeAsJsonString(AzureInstanceComputeMetadata azureInstanceComputeMetadata) { - string testPath = "timeOut"; - using (new AzureInstanceMetadataServiceMock( - AzureInstanceMetadataEndToEndTests.MockTestUri, - testPath, - (response) => - { - // wait for longer than the request timeout - Thread.Sleep(TimeSpan.FromSeconds(5)); + DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(AzureInstanceComputeMetadata)); - response.StatusCode = (int)HttpStatusCode.OK; + string returnData; - var jsonStream = this.GetTestMetadataStream(); - response.SetContentLength(jsonStream.Length); - response.ContentType = "application/json"; - response.SetContentEncoding(Encoding.UTF8); - response.WriteStreamToBody(jsonStream); - })) + using (MemoryStream memoryStream = new MemoryStream()) { - var azureIms = new AzureMetadataRequestor - { - BaseAimsUri = string.Concat(AzureInstanceMetadataEndToEndTests.MockTestUri, testPath, "/"), - AzureImsRequestTimeout = TimeSpan.FromSeconds(1) - }; + serializer.WriteObject(memoryStream, azureInstanceComputeMetadata); + memoryStream.Position = 0; + StreamReader sr = new StreamReader(memoryStream); - var azureIMSData = azureIms.GetAzureComputeMetadataAsync(); - azureIMSData.Wait(); + returnData = sr.ReadToEnd(); - Assert.Null(azureIMSData.Result); + sr.Close(); + memoryStream.Close(); } + + return returnData; } - [TestMethod] - public void AzureImsResponseUnsuccessful() + private static Mock GetMockHttpMessageHandler(AzureInstanceComputeMetadata metadata, HttpStatusCode httpStatusCode = HttpStatusCode.OK, bool throwException = false) { - string testPath = "errorForbidden"; + var json = SerializeAsJsonString(metadata); - using (new AzureInstanceMetadataServiceMock( - AzureInstanceMetadataEndToEndTests.MockTestUri, - testPath, - (response) => - { - // don't send anything in content at all, or the context defaults to 200 OK - response.StatusCode = (int)HttpStatusCode.Forbidden; - })) + var mockHttpMessageHandler = new Mock(); + var response = new HttpResponseMessage { - var azureIms = new AzureMetadataRequestor - { - BaseAimsUri = string.Concat(AzureInstanceMetadataEndToEndTests.MockTestUri, testPath, "/") - }; - - var azureIMSData = azureIms.GetAzureComputeMetadataAsync(); - azureIMSData.Wait(); + StatusCode = httpStatusCode, + Content = new StringContent(json, Encoding.UTF8, "application/json"), + }; - Assert.Null(azureIMSData.Result); + if (throwException) + { + mockHttpMessageHandler + .Protected() + .Setup>("SendAsync", ItExpr.IsAny(), ItExpr.IsAny()) + .Callback(() => throw new Exception("unit test forced exception")); } - } - - /// - /// Return a memory stream adequate for testing. - /// - /// An Azure instance metadata compute object. - /// Azure Instance Compute Metadata as a JSON-encoded MemoryStream. - private MemoryStream GetTestMetadataStream(AzureInstanceComputeMetadata inst = null) - { - if (inst == null) + else { - inst = this.GetTestMetadata(); + mockHttpMessageHandler + .Protected() + .Setup>("SendAsync", ItExpr.IsAny(), ItExpr.IsAny()) + .ReturnsAsync(value: response); } - DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(AzureInstanceComputeMetadata)); - - MemoryStream jsonStream = new MemoryStream(); - serializer.WriteObject(jsonStream, inst); - - return jsonStream; - } - - /// - /// Creates test data for heartbeat e2e. - /// - /// An Azure Instance Metadata Compute object suitable for use in testing. - private AzureInstanceComputeMetadata GetTestMetadata() - { - return new AzureInstanceComputeMetadata() - { - Location = "US-West", - Name = "test-vm01", - Offer = "D9_USWest", - OsType = "Linux", - PlacementGroupId = "placement-grp", - PlatformFaultDomain = "0", - PlatformUpdateDomain = "0", - Publisher = "Microsoft", - ResourceGroupName = "test.resource-group_01", - Sku = "Windows_10", - SubscriptionId = Guid.NewGuid().ToString(), - Tags = "thisTag;thatTag", - Version = "10.8a", - VmId = Guid.NewGuid().ToString(), - VmSize = "A8", - VmScaleSetName = "ScaleName" - }; + return mockHttpMessageHandler; } } } diff --git a/WEB/Src/WindowsServer/WindowsServer.Tests/AzureInstanceMetadataTests.cs b/WEB/Src/WindowsServer/WindowsServer.Tests/AzureInstanceMetadataTests.cs index 2f17c683c9..97533b9cde 100644 --- a/WEB/Src/WindowsServer/WindowsServer.Tests/AzureInstanceMetadataTests.cs +++ b/WEB/Src/WindowsServer/WindowsServer.Tests/AzureInstanceMetadataTests.cs @@ -6,11 +6,16 @@ namespace Microsoft.ApplicationInsights.WindowsServer using System.Net.Http; using System.Runtime.Serialization.Json; using System.Text; + using System.Threading; using System.Threading.Tasks; using Microsoft.ApplicationInsights.WindowsServer.Implementation; using Microsoft.ApplicationInsights.WindowsServer.Implementation.DataContracts; using Microsoft.ApplicationInsights.WindowsServer.Mock; using Microsoft.VisualStudio.TestTools.UnitTesting; + + using Moq; + using Moq.Protected; + using Assert = Xunit.Assert; [TestClass] @@ -291,17 +296,20 @@ public void AzureIMSReturnsExpectedValuesForEachFieldAfterSerialization() } [TestMethod] - public void AzureIMSGetFailsWithException() + public async Task AzureIMSGetFailsWithException() { - var requestor = new AzureMetadataRequestor(makeAzureIMSRequestor: (string uri) => - { - throw new HttpRequestException("MaxResponseContentLength exceeded"); - }); + var mockHttpMessageHandler = new Mock(); + mockHttpMessageHandler + .Protected() + .Setup>("SendAsync", ItExpr.IsAny(), ItExpr.IsAny()) + .Callback(() => throw new HttpRequestException("unit test forced exception")); + + var requestor = new AzureMetadataRequestor(new HttpClient(mockHttpMessageHandler.Object)); try { - var result = requestor.GetAzureComputeMetadataAsync(); - Assert.Null(result.GetAwaiter().GetResult()); + var result = await requestor.GetAzureComputeMetadataAsync(); + Assert.Null(result); } catch { diff --git a/WEB/Src/WindowsServer/WindowsServer.Tests/Mock/AzureInstanceMetadataServiceMock.cs b/WEB/Src/WindowsServer/WindowsServer.Tests/Mock/AzureInstanceMetadataServiceMock.cs deleted file mode 100644 index 45aed9c9fd..0000000000 --- a/WEB/Src/WindowsServer/WindowsServer.Tests/Mock/AzureInstanceMetadataServiceMock.cs +++ /dev/null @@ -1,115 +0,0 @@ -namespace Microsoft.ApplicationInsights.WindowsServer.Mock -{ - using System; - using System.Net; - using System.Threading; - using System.Threading.Tasks; -#if NETCOREAPP - using System.Collections.Generic; - using Microsoft.AspNetCore.Builder; - using Microsoft.AspNetCore.Hosting; - using Microsoft.AspNetCore.Http; -#endif - - internal class AzureInstanceMetadataServiceMock : IDisposable - { -#if NETCOREAPP - private readonly IWebHost host; -#else - private readonly HttpListener listener; -#endif - private readonly CancellationTokenSource cts; - -#if NETCOREAPP - - internal AzureInstanceMetadataServiceMock(string baseUrl, string testName, Action onRequest = null) - { - ResponseHandlerMock.OnRequestDictionary.Add(testName, onRequest); - - this.cts = new CancellationTokenSource(); - this.host = new WebHostBuilder() - .UseKestrel() - .UseStartup() - .UseUrls(baseUrl + "?testName=" + testName) - .Build(); - - // Breaking change. This method is not available in NetCoreApp3 and greater - // Other methods do not have the same behavior and break existing tests. - // Task.Run(() => this.host.Run(this.cts.Token)); - } - -#else - - internal AzureInstanceMetadataServiceMock(string url, string testName, Action onRequest = null) - { - var alistener = new System.Net.Http.HttpClient(); - this.listener = new HttpListener(); - this.listener.Prefixes.Add(url); - this.listener.Start(); - this.cts = new CancellationTokenSource(); - - Task.Run( - () => - { - if (!this.cts.IsCancellationRequested) - { - HttpListenerContext context = this.listener.GetContext(); - if (onRequest != null) - { - onRequest(context.Response); - } - else - { - context.Response.StatusCode = 200; - } - - context.Response.OutputStream.Close(); - context.Response.Close(); - } - }, - this.cts.Token); - } -#endif - - public void Dispose() - { - this.cts.Cancel(false); -#if NETCOREAPP - this.host.Dispose(); -#else - this.listener.Abort(); - ((IDisposable)this.listener).Dispose(); -#endif - this.cts.Dispose(); - } - -#if NETCOREAPP - - public class ResponseHandlerMock - { - public static Dictionary> OnRequestDictionary { get; set; } = new Dictionary>(); - - public void Configure(IApplicationBuilder app) - { - app.Run(async (context) => - { - string testPath = context.Request.Path.Value.Trim('/'); - - if (ResponseHandlerMock.OnRequestDictionary.ContainsKey(testPath)) - { - Action onRequest = ResponseHandlerMock.OnRequestDictionary[testPath]; - onRequest(context.Response); - } - else - { - context.Response.StatusCode = 200; - await context.Response.WriteAsync("Hello World!"); - } - }); - } - } - -#endif - - } -} diff --git a/WEB/Src/WindowsServer/WindowsServer.Tests/WindowsServer.Tests.csproj b/WEB/Src/WindowsServer/WindowsServer.Tests/WindowsServer.Tests.csproj index b082240095..a1d68031b7 100644 --- a/WEB/Src/WindowsServer/WindowsServer.Tests/WindowsServer.Tests.csproj +++ b/WEB/Src/WindowsServer/WindowsServer.Tests/WindowsServer.Tests.csproj @@ -13,6 +13,7 @@ + diff --git a/WEB/Src/WindowsServer/WindowsServer/AzureInstanceMetadataTelemetryModule.cs b/WEB/Src/WindowsServer/WindowsServer/AzureInstanceMetadataTelemetryModule.cs index 4faf6e0f5b..ae1e6642d1 100644 --- a/WEB/Src/WindowsServer/WindowsServer/AzureInstanceMetadataTelemetryModule.cs +++ b/WEB/Src/WindowsServer/WindowsServer/AzureInstanceMetadataTelemetryModule.cs @@ -74,10 +74,7 @@ public void Initialize(TelemetryConfiguration unused) // to the core event log. try { - var heartbeatProperties = new AzureComputeMetadataHeartbeatPropertyProvider(); - Task.Factory.StartNew( - async () => await heartbeatProperties.SetDefaultPayloadAsync(hbeatManager) - .ConfigureAwait(false)); + Task.Factory.StartNew(async () => await SetDefaultPayloadAsync(hbeatManager).ConfigureAwait(false)); } catch (Exception heartbeatAquisitionException) { @@ -90,5 +87,13 @@ public void Initialize(TelemetryConfiguration unused) } } } + + private static async Task SetDefaultPayloadAsync(IHeartbeatPropertyManager heartbeatPropertyManager) + { + using (var heartbeatPropertyProvider = new AzureComputeMetadataHeartbeatPropertyProvider()) + { + await heartbeatPropertyProvider.SetDefaultPayloadAsync(heartbeatPropertyManager).ConfigureAwait(false); + } + } } } diff --git a/WEB/Src/WindowsServer/WindowsServer/Implementation/AzureComputeMetadataHeartbeatPropertyProvider.cs b/WEB/Src/WindowsServer/WindowsServer/Implementation/AzureComputeMetadataHeartbeatPropertyProvider.cs index 320f5130dd..9806995693 100644 --- a/WEB/Src/WindowsServer/WindowsServer/Implementation/AzureComputeMetadataHeartbeatPropertyProvider.cs +++ b/WEB/Src/WindowsServer/WindowsServer/Implementation/AzureComputeMetadataHeartbeatPropertyProvider.cs @@ -6,7 +6,7 @@ using System.Threading.Tasks; using Microsoft.ApplicationInsights.Extensibility.Implementation.Tracing; - internal class AzureComputeMetadataHeartbeatPropertyProvider + internal class AzureComputeMetadataHeartbeatPropertyProvider : IDisposable { internal const string HeartbeatPropertyPrefix = "azInst_"; // to ensure no collisions with base heartbeat properties @@ -34,13 +34,15 @@ internal class AzureComputeMetadataHeartbeatPropertyProvider "vmScaleSetName", }; + private readonly IAzureMetadataRequestor azureInstanceMetadataRequestor; + /// /// Flags that will tell us whether or not Azure VM metadata has been attempted to be gathered or not, and /// if we should even attempt to look for it in the first place. /// private bool isAzureMetadataCheckCompleted = false; - private IAzureMetadataRequestor azureInstanceMetadataRequestor = null; + private bool isDisposed; /// /// Initializes a new instance of the class. @@ -101,5 +103,28 @@ public async Task SetDefaultPayloadAsync(IHeartbeatPropertyManager provide return hasSetFields; } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + this.Dispose(disposing: true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (!this.isDisposed) + { + if (disposing) + { + if (this.azureInstanceMetadataRequestor is IDisposable disposableRequestor) + { + disposableRequestor.Dispose(); + } + } + + this.isDisposed = true; + } + } } } diff --git a/WEB/Src/WindowsServer/WindowsServer/Implementation/AzureMetadataRequestor.cs b/WEB/Src/WindowsServer/WindowsServer/Implementation/AzureMetadataRequestor.cs index 07585a23e2..723abe85c9 100644 --- a/WEB/Src/WindowsServer/WindowsServer/Implementation/AzureMetadataRequestor.cs +++ b/WEB/Src/WindowsServer/WindowsServer/Implementation/AzureMetadataRequestor.cs @@ -9,7 +9,7 @@ using Microsoft.ApplicationInsights.Extensibility; using Microsoft.ApplicationInsights.WindowsServer.Implementation.DataContracts; - internal class AzureMetadataRequestor : IAzureMetadataRequestor + internal class AzureMetadataRequestor : IAzureMetadataRequestor, IDisposable { /// /// Azure Instance Metadata Service exists on a single non-routable IP on machines configured @@ -19,21 +19,21 @@ internal class AzureMetadataRequestor : IAzureMetadataRequestor internal const string AzureImsJsonFormat = "format=json"; internal const int AzureImsMaxResponseBufferSize = 512; - /// - /// Default timeout for the web requests made to obtain Azure IMS data. Internal to expose to tests. - /// - internal TimeSpan AzureImsRequestTimeout = TimeSpan.FromSeconds(10); + private readonly HttpClient httpClient; /// - /// Private function for mocking out the actual call to IMS in unit tests. Available to internal only. + /// Default timeout for the web requests made to obtain Azure IMS data. /// - /// parameter sent to the func is a string representing the Uri to request Azure IMS data from. - /// An instance of AzureInstanceComputeMetadata or null. - private Func> azureIMSRequestor = null; + private TimeSpan azureImsRequestTimeout = TimeSpan.FromSeconds(10); + private bool isDisposed; - internal AzureMetadataRequestor(Func> makeAzureIMSRequestor = null) + public AzureMetadataRequestor(HttpClient httpClient = null) { - this.azureIMSRequestor = makeAzureIMSRequestor; + this.httpClient = httpClient ?? new HttpClient(); + + this.httpClient.MaxResponseContentBufferSize = AzureMetadataRequestor.AzureImsMaxResponseBufferSize; + this.httpClient.DefaultRequestHeaders.Add("Metadata", "True"); + this.httpClient.Timeout = this.azureImsRequestTimeout; } /// @@ -56,33 +56,44 @@ public Task GetAzureComputeMetadataAsync() return this.MakeAzureMetadataRequestAsync(metadataRequestUrl); } - private async Task MakeAzureMetadataRequestAsync(string metadataRequestUrl) + public void Dispose() { - AzureInstanceComputeMetadata requestResult = null; + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + this.Dispose(disposing: true); + GC.SuppressFinalize(this); + } - SdkInternalOperationsMonitor.Enter(); - try + protected virtual void Dispose(bool disposing) + { + if (!this.isDisposed) { - if (this.azureIMSRequestor != null) - { - requestResult = await this.azureIMSRequestor(metadataRequestUrl).ConfigureAwait(false); - } - else + if (disposing) { - requestResult = await this.MakeWebRequestAsync(metadataRequestUrl).ConfigureAwait(false); + this.httpClient.Dispose(); } + + this.isDisposed = true; + } + } + + private async Task MakeAzureMetadataRequestAsync(string metadataRequestUrl) + { + SdkInternalOperationsMonitor.Enter(); + try + { + return await this.MakeWebRequestAsync(metadataRequestUrl).ConfigureAwait(false); } catch (Exception ex) { WindowsServerEventSource.Log.AzureInstanceMetadataRequestFailure( metadataRequestUrl, ex.Message, ex.InnerException != null ? ex.InnerException.Message : string.Empty); + + return null; } finally { SdkInternalOperationsMonitor.Exit(); } - - return requestResult; } private async Task MakeWebRequestAsync(string requestUrl) @@ -90,20 +101,13 @@ private async Task MakeWebRequestAsync(string requ AzureInstanceComputeMetadata azureIms = null; DataContractJsonSerializer deserializer = new DataContractJsonSerializer(typeof(AzureInstanceComputeMetadata)); - using (var azureImsClient = new HttpClient()) - { - azureImsClient.MaxResponseContentBufferSize = AzureMetadataRequestor.AzureImsMaxResponseBufferSize; - azureImsClient.DefaultRequestHeaders.Add("Metadata", "True"); - azureImsClient.Timeout = this.AzureImsRequestTimeout; - - Stream content = await azureImsClient.GetStreamAsync(new Uri(requestUrl)).ConfigureAwait(false); - azureIms = (AzureInstanceComputeMetadata)deserializer.ReadObject(content); - content.Dispose(); + Stream content = await this.httpClient.GetStreamAsync(new Uri(requestUrl)).ConfigureAwait(false); + azureIms = (AzureInstanceComputeMetadata)deserializer.ReadObject(content); + content.Dispose(); - if (azureIms == null) - { - WindowsServerEventSource.Log.CannotObtainAzureInstanceMetadata(); - } + if (azureIms == null) + { + WindowsServerEventSource.Log.CannotObtainAzureInstanceMetadata(); } return azureIms;