Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

modify AzureMetadataRequestor to accept HttpClient in ctor. rewrite AzureInstanceMetadata tests to mock HttpClient instead of using in-proc server. #2358

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,254 +3,177 @@ 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;

/// <summary>
/// Tests the heartbeat functionality through actual local-only Http calls to mimic
/// end to end functionality as closely as possible.
/// </summary>
[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<HttpMessageHandler> 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<HttpMessageHandler> 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()
/// <summary>
/// Creates test data for heartbeat e2e.
/// </summary>
/// <returns>An Azure Instance Metadata Compute object suitable for use in testing.</returns>
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<HttpMessageHandler> 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<HttpMessageHandler>();
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<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
.Callback(() => throw new Exception("unit test forced exception"));
}
}

/// <summary>
/// Return a memory stream adequate for testing.
/// </summary>
/// <param name="inst">An Azure instance metadata compute object.</param>
/// <returns>Azure Instance Compute Metadata as a JSON-encoded MemoryStream.</returns>
private MemoryStream GetTestMetadataStream(AzureInstanceComputeMetadata inst = null)
{
if (inst == null)
else
{
inst = this.GetTestMetadata();
mockHttpMessageHandler
.Protected()
.Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(value: response);
}

DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(AzureInstanceComputeMetadata));

MemoryStream jsonStream = new MemoryStream();
serializer.WriteObject(jsonStream, inst);

return jsonStream;
}

/// <summary>
/// Creates test data for heartbeat e2e.
/// </summary>
/// <returns>An Azure Instance Metadata Compute object suitable for use in testing.</returns>
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;
}
}
}
Loading