Skip to content

Commit

Permalink
Add test logics for app insight (#735)
Browse files Browse the repository at this point in the history
* add test logics for app insight

correct app id name

add waiting for traces

test api key

add envs used by agent

test

update envs referecing

clean up

test version

update nightly e2e tests pipeline

* update agent version - fix wrong copy path
  • Loading branch information
kaibocai authored Oct 12, 2023
1 parent 3173203 commit 0efd944
Show file tree
Hide file tree
Showing 8 changed files with 278 additions and 3 deletions.
5 changes: 5 additions & 0 deletions azure-pipelines-e2e-integration-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ trigger: none

jobs:
- job: "End_to_end_integration_tests"
variables:
ApplicationInsightAgentVersion: 3.4.16
displayName: 'End to end integration tests'
strategy:
maxParallel: 1
Expand Down Expand Up @@ -161,5 +163,8 @@ jobs:
ConfluentCloudPassword: $(ConfluentCloudPassword)
AzureWebJobsEventGridOutputBindingTopicUriString: $(AzureWebJobsEventGridOutputBindingTopicUriString)
AzureWebJobsEventGridOutputBindingTopicKeyString: $(AzureWebJobsEventGridOutputBindingTopicKeyString)
ApplicationInsightAPIKey: $(ApplicationInsightAPIKey)
ApplicationInsightAPPID: $(ApplicationInsightAPPID)
ApplicationInsightAgentVersion: $(ApplicationInsightAgentVersion)
displayName: 'Build & Run tests'
continueOnError: false
4 changes: 4 additions & 0 deletions azure-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ jobs:
dependsOn: Build
variables:
buildNumber: $[ dependencies.Build.outputs['output.buildNumber'] ]
ApplicationInsightAgentVersion: 3.4.16
strategy:
maxParallel: 1
matrix:
Expand Down Expand Up @@ -224,6 +225,9 @@ jobs:
ConfluentCloudPassword: $(ConfluentCloudPassword)
AzureWebJobsEventGridOutputBindingTopicUriString: $(AzureWebJobsEventGridOutputBindingTopicUriString)
AzureWebJobsEventGridOutputBindingTopicKeyString: $(AzureWebJobsEventGridOutputBindingTopicKeyString)
ApplicationInsightAPIKey: $(ApplicationInsightAPIKey)
ApplicationInsightAPPID: $(ApplicationInsightAPPID)
ApplicationInsightAgentVersion: $(ApplicationInsightAgentVersion)
displayName: 'Build & Run tests'
continueOnError: false

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,11 @@ public static class Constants
public static string ServiceBusConnectionStringSetting = Environment.GetEnvironmentVariable("AzureWebJobsServiceBus");

// Xunit Fixtures and Collections
public const string FunctionAppCollectionName = "FunctionAppCollection";
public const string FunctionAppCollectionName = "FunctionAppCollection";

// Application Insights
public static string ApplicationInsightAPIKey = Environment.GetEnvironmentVariable("ApplicationInsightAPIKey");
public static string ApplicationInsightAPPID = Environment.GetEnvironmentVariable("ApplicationInsightAPPID");
public static string ApplicationInsightAgentVersion = Environment.GetEnvironmentVariable("ApplicationInsightAgentVersion");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
using System;
using System.Collections.Generic;
using System.Net.Http.Headers;
using System.Net.Http;
using System.Threading.Tasks;
using E2ETestCases.Utils;
using Xunit;

namespace Azure.Functions.Java.Tests.E2E.Helpers.AppInsight
{
public class AppInsightHelper
{
private const string QueryEndpoint = "https://api.applicationinsights.io/v1/apps/{0}/query?{1}";
// Trace data will be triggered as soon as node/java app starts.
// Therefore, larger timespan is used here in case traces data will be tested last.
// 15min is estimated on test suite max timeout plus some time buffer.
private const string TracesParameter = "query=traces%7C%20where%20timestamp%20%20%3E%20ago(20min)%7C%20order%20by%20timestamp%20desc%20";

public static bool ValidateData(QueryType queryType, String currentSdkVersion)
{
int loopCount = 1;
List<QueryResultRow> data = QueryAzureMonitorTelemetry(queryType).Result;

while (data == null || data.Count == 0)
{
if (loopCount > 0)
{
// Waiting for 60s for traces showing up in application insight portal
// TODO: is there a more elegant way to wait?
System.Threading.Thread.Sleep(60*1000);
loopCount--;
}
else {
throw new Exception("No Application Insights telemetry available");
}
}
bool dataFound = false;
foreach (QueryResultRow row in data)
{
if (row.sdkVersion.IndexOf(currentSdkVersion) >= 0)
{
dataFound = true;
break;
// TODO: Add extra checks when test apps generate similar telemetry, validate SDK version only for now
}
}
return dataFound;
}

public static async Task<List<QueryResultRow>> QueryAzureMonitorTelemetry(QueryType queryType, bool isInitialCheck = false)
{
List<QueryResultRow> aiResult;
Func<List<QueryResultRow>, bool> retryCheck = (result) => result == null;
string queryParemeter = "";
switch (queryType)
{
//TODO: test other type of data as well, for now only test trace.
//case QueryType.exceptions:
// queryParemeter = ExceptionsParameter;
// break;
//case QueryType.dependencies:
// queryParemeter = DependenciesParameter;
// break;
//case QueryType.requests:
// queryParemeter = RequestParameter;
// break;
case QueryType.traces:
queryParemeter = TracesParameter;
break;
//case QueryType.statsbeat:
// queryParemeter = StatsbeatParameter;
// break;
}

aiResult = await QueryMonitorLogWithRestApi(queryType, queryParemeter);
return aiResult;
}

private static async Task<List<QueryResultRow>> QueryMonitorLogWithRestApi(QueryType queryType, string parameterString)
{
try
{
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));

var apiKeyVal = Constants.ApplicationInsightAPIKey;
var appIdVal = Constants.ApplicationInsightAPPID;

client.DefaultRequestHeaders.Add("x-api-key", apiKeyVal);
var req = string.Format(QueryEndpoint, appIdVal, parameterString);
var table = await GetHttpResponse(client, req);
return GetJsonObjectFromQuery(table);
}
catch (Exception ex)
{
Console.WriteLine("Failed to query Azure Motinor Ex:" + ex.Message);
}
return null;
}

// Get http request response
public static async Task<string> GetHttpResponse(HttpClient client, string url)
{
Uri uri = new Uri(url);
HttpResponseMessage response = null;
try
{
response = client.GetAsync(uri).Result;
}
catch (Exception e)
{
Console.WriteLine("GetXhrResponse Error " + e.Message); return null;
}
if (response == null)
{
Console.WriteLine("GetXhrResponse Error: No Response"); return null;
}
if (!response.IsSuccessStatusCode)
{
Console.WriteLine($"Response failed. Status code {response.StatusCode}");
}
return await response.Content.ReadAsStringAsync();
}

// Get latest query object with RestApiJsonSerializeRow format
// prerequisite: query parameter is ordered by timestamp desc
private static List<QueryResultRow> GetJsonObjectFromQuery(string table)
{
if (!(table?.Length > 0)) { return null; }
RestApiJsonSerializeTable SerializeTable = Newtonsoft.Json.JsonConvert.DeserializeObject<RestApiJsonSerializeTable>(table);
if (!(SerializeTable?.Tables?.Count > 0)) { return null; }
List<QueryResultRow> kustoObjectList = new List<QueryResultRow>();
List<RestApiJsonSerializeCols> columnObjects = SerializeTable.Tables[0].Columns;
List<string[]> rows = SerializeTable.Tables[0].Rows;
if (!(rows?.Count > 0)) { return null; }
foreach (string[] row in rows)
{
QueryResultRow item = new QueryResultRow(columnObjects, row);
kustoObjectList.Add(item);
}
return kustoObjectList;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System;
using System.Collections.Generic;
using System.Text;

namespace E2ETestCases.Utils
{
// Query result types
public enum QueryType
{ requests, dependencies, exceptions, traces, statsbeat }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
using System.Collections.Generic;

namespace E2ETestCases.Utils
{
public class RestApiJsonSerializeCols
{
public string Name { get; set; }
public string Type { get; set; }
}

public class RestApiJsonSerializeObject
{
public string Name { get; set; }
public List<RestApiJsonSerializeCols> Columns { get; set; }
public List<string[]> Rows { get; set; }
}

public class RestApiJsonSerializeTable
{
public List<RestApiJsonSerializeObject> Tables { get; set; }
}

//TODO: add unit tests
public class QueryResultRow
{
Dictionary<string, string> data = new Dictionary<string, string>();
public QueryResultRow(List<RestApiJsonSerializeCols> cols, string[] rows)
{
for (int i = 0; i < rows.Length; i++)
{
data.Add(cols[i].Name, rows[i]);
}
}
// Here are all fields might be used by Monitor log query results.
// Only those fields in Dictionary(data) will be parsed.
public string timestamp { get => getData("timestamp"); }
public string id { get => getData("id"); }
public string source { get => getData("source"); }
public string name { get => getData("name"); }
public string url { get => getData("url"); }
public string target { get => getData("target"); }
public string success { get => getData("success"); }
public string resultCode { get => getData("resultCode"); }
public string duration { get => getData("duration"); }
public string performanceBucket { get => getData("performanceBucket"); }
public string itemType { get => getData("itemType"); }
public dynamic customDimensions { get => getData("customDimensions"); }
public dynamic customMeasurements { get => getData("customMeasurements"); }
public string operation_Name { get => getData("operation_Name"); }
public string operation_Id { get => getData("operation_Id"); }
public string operation_ParentId { get => getData("operation_ParentId"); }
public string operation_SyntheticSource { get => getData("operation_SyntheticSource"); }
public string session_Id { get => getData("session_Id"); }
public string user_Id { get => getData("user_Id"); }
public string user_AuthenticatedId { get => getData("user_AuthenticatedId"); }
public string user_AccountId { get => getData("user_AccountId"); }
public string application_Version { get => getData("application_Version"); }
public string client_Type { get => getData("client_Type"); }
public string client_Model { get => getData("client_Model"); }
public string client_OS { get => getData("client_OS"); }
public string client_IP { get => getData("client_IP"); }
public string client_City { get => getData("client_City"); }
public string client_StateOrProvince { get => getData("client_StateOrProvince"); }
public string client_CountryOrRegion { get => getData("client_CountryOrRegion"); }
public string client_Browser { get => getData("client_Browser"); }
public string cloud_RoleName { get => getData("cloud_RoleName"); }
public string cloud_RoleInstance { get => getData("cloud_RoleInstance"); }
public string appId { get => getData("appId"); }
public string appName { get => getData("appName"); }
public string iKey { get => getData("iKey"); }
public string sdkVersion { get => getData("sdkVersion"); }
public string itemId { get => getData("itemId"); }
public string itemCount { get => getData("itemCount"); }
public string _ResourceId { get => getData("_ResourceId"); }
public string problemId { get => getData("problemId"); }
public string handledAt { get => getData(" handledAt"); }
public string type { get => getData(" type"); }
public string message { get => getData("message"); }
public string assembly { get => getData("assembly"); }
public string method { get => getData("method"); }
public string outerType { get => getData("outerType"); }
public string outerMessage { get => getData("outerMessage"); }
public string outerAssembly { get => getData("outerAssembly"); }
public string outerMethod { get => getData("outerMethod"); }
public string innermostType { get => getData("innermostType"); }
public string innermostMessage { get => getData("innermostMessage"); }
public string innermostAssembly { get => getData("innermostAssembly"); }
public string innermostMethod { get => getData("innermostMethod"); }
public string severityLevel { get => getData("severityLevel"); }
public string details { get => getData("details"); }

private string getData(string name)
{
return data.ContainsKey(name) ? data[name] : null;
}
}
}


Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using Azure.Functions.Java.Tests.E2E.Helpers.AppInsight;
using System;
using System.Net;
using System.Threading.Tasks;
Expand Down Expand Up @@ -66,5 +67,11 @@ public async Task HttpTrigger_BindingName()
{
Assert.True(await Utilities.InvokeHttpTrigger("BindingName", "/testMessage", HttpStatusCode.OK, "testMessage"));
}

[Fact]
public void ApplicationInsightTest()
{
Assert.True(AppInsightHelper.ValidateData(E2ETestCases.Utils.QueryType.traces, Constants.ApplicationInsightAgentVersion));
}
}
}
4 changes: 2 additions & 2 deletions setup-tests-pipeline.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ if (-not $UseCoreToolsBuildFromIntegrationTests.IsPresent)
Write-Host "Copying worker.config.json to worker directory"
Copy-Item "$PSScriptRoot/worker.config.json" "$FUNC_CLI_DIRECTORY/workers/java" -Force -Verbose
Write-Host "Copying worker.config.json and annotationLib to worker directory"
Copy-Item "$PSScriptRoot/annotationLib" "$FUNC_CLI_DIRECTORY/workers/java/annotationLib" -Recurse -Verbose
Copy-Item "$PSScriptRoot/annotationLib" "$FUNC_CLI_DIRECTORY/workers/java" -Recurse -Verbose -Force

# Download application insights agent from maven central
$ApplicationInsightsAgentFile = [System.IO.Path]::Combine($PSScriptRoot, $ApplicationInsightsAgentFilename)
Expand Down Expand Up @@ -136,5 +136,5 @@ if (-not $UseCoreToolsBuildFromIntegrationTests.IsPresent)
New-Item -path $PSScriptRoot\agent -type file -name "functions.codeless"

Write-Host "Copying the unsigned Application Insights Agent to worker directory"
Copy-Item "$PSScriptRoot/agent" "$FUNC_CLI_DIRECTORY/workers/java/agent" -Recurse -Verbose
Copy-Item "$PSScriptRoot/agent" "$FUNC_CLI_DIRECTORY/workers/java" -Recurse -Verbose -Force
}

0 comments on commit 0efd944

Please sign in to comment.