Skip to content

Commit 0efd944

Browse files
authored
Add test logics for app insight (#735)
* 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
1 parent 3173203 commit 0efd944

8 files changed

Lines changed: 278 additions & 3 deletions

File tree

azure-pipelines-e2e-integration-tests.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ trigger: none
77

88
jobs:
99
- job: "End_to_end_integration_tests"
10+
variables:
11+
ApplicationInsightAgentVersion: 3.4.16
1012
displayName: 'End to end integration tests'
1113
strategy:
1214
maxParallel: 1
@@ -161,5 +163,8 @@ jobs:
161163
ConfluentCloudPassword: $(ConfluentCloudPassword)
162164
AzureWebJobsEventGridOutputBindingTopicUriString: $(AzureWebJobsEventGridOutputBindingTopicUriString)
163165
AzureWebJobsEventGridOutputBindingTopicKeyString: $(AzureWebJobsEventGridOutputBindingTopicKeyString)
166+
ApplicationInsightAPIKey: $(ApplicationInsightAPIKey)
167+
ApplicationInsightAPPID: $(ApplicationInsightAPPID)
168+
ApplicationInsightAgentVersion: $(ApplicationInsightAgentVersion)
164169
displayName: 'Build & Run tests'
165170
continueOnError: false

azure-pipelines.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ jobs:
7878
dependsOn: Build
7979
variables:
8080
buildNumber: $[ dependencies.Build.outputs['output.buildNumber'] ]
81+
ApplicationInsightAgentVersion: 3.4.16
8182
strategy:
8283
maxParallel: 1
8384
matrix:
@@ -224,6 +225,9 @@ jobs:
224225
ConfluentCloudPassword: $(ConfluentCloudPassword)
225226
AzureWebJobsEventGridOutputBindingTopicUriString: $(AzureWebJobsEventGridOutputBindingTopicUriString)
226227
AzureWebJobsEventGridOutputBindingTopicKeyString: $(AzureWebJobsEventGridOutputBindingTopicKeyString)
228+
ApplicationInsightAPIKey: $(ApplicationInsightAPIKey)
229+
ApplicationInsightAPPID: $(ApplicationInsightAPPID)
230+
ApplicationInsightAgentVersion: $(ApplicationInsightAgentVersion)
227231
displayName: 'Build & Run tests'
228232
continueOnError: false
229233

endtoendtests/Azure.Functions.Java.Tests.E2E/Azure.Functions.Java.Tests.E2E/Constants.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,11 @@ public static class Constants
8787
public static string ServiceBusConnectionStringSetting = Environment.GetEnvironmentVariable("AzureWebJobsServiceBus");
8888

8989
// Xunit Fixtures and Collections
90-
public const string FunctionAppCollectionName = "FunctionAppCollection";
90+
public const string FunctionAppCollectionName = "FunctionAppCollection";
91+
92+
// Application Insights
93+
public static string ApplicationInsightAPIKey = Environment.GetEnvironmentVariable("ApplicationInsightAPIKey");
94+
public static string ApplicationInsightAPPID = Environment.GetEnvironmentVariable("ApplicationInsightAPPID");
95+
public static string ApplicationInsightAgentVersion = Environment.GetEnvironmentVariable("ApplicationInsightAgentVersion");
9196
}
9297
}
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Net.Http.Headers;
4+
using System.Net.Http;
5+
using System.Threading.Tasks;
6+
using E2ETestCases.Utils;
7+
using Xunit;
8+
9+
namespace Azure.Functions.Java.Tests.E2E.Helpers.AppInsight
10+
{
11+
public class AppInsightHelper
12+
{
13+
private const string QueryEndpoint = "https://api.applicationinsights.io/v1/apps/{0}/query?{1}";
14+
// Trace data will be triggered as soon as node/java app starts.
15+
// Therefore, larger timespan is used here in case traces data will be tested last.
16+
// 15min is estimated on test suite max timeout plus some time buffer.
17+
private const string TracesParameter = "query=traces%7C%20where%20timestamp%20%20%3E%20ago(20min)%7C%20order%20by%20timestamp%20desc%20";
18+
19+
public static bool ValidateData(QueryType queryType, String currentSdkVersion)
20+
{
21+
int loopCount = 1;
22+
List<QueryResultRow> data = QueryAzureMonitorTelemetry(queryType).Result;
23+
24+
while (data == null || data.Count == 0)
25+
{
26+
if (loopCount > 0)
27+
{
28+
// Waiting for 60s for traces showing up in application insight portal
29+
// TODO: is there a more elegant way to wait?
30+
System.Threading.Thread.Sleep(60*1000);
31+
loopCount--;
32+
}
33+
else {
34+
throw new Exception("No Application Insights telemetry available");
35+
}
36+
}
37+
bool dataFound = false;
38+
foreach (QueryResultRow row in data)
39+
{
40+
if (row.sdkVersion.IndexOf(currentSdkVersion) >= 0)
41+
{
42+
dataFound = true;
43+
break;
44+
// TODO: Add extra checks when test apps generate similar telemetry, validate SDK version only for now
45+
}
46+
}
47+
return dataFound;
48+
}
49+
50+
public static async Task<List<QueryResultRow>> QueryAzureMonitorTelemetry(QueryType queryType, bool isInitialCheck = false)
51+
{
52+
List<QueryResultRow> aiResult;
53+
Func<List<QueryResultRow>, bool> retryCheck = (result) => result == null;
54+
string queryParemeter = "";
55+
switch (queryType)
56+
{
57+
//TODO: test other type of data as well, for now only test trace.
58+
//case QueryType.exceptions:
59+
// queryParemeter = ExceptionsParameter;
60+
// break;
61+
//case QueryType.dependencies:
62+
// queryParemeter = DependenciesParameter;
63+
// break;
64+
//case QueryType.requests:
65+
// queryParemeter = RequestParameter;
66+
// break;
67+
case QueryType.traces:
68+
queryParemeter = TracesParameter;
69+
break;
70+
//case QueryType.statsbeat:
71+
// queryParemeter = StatsbeatParameter;
72+
// break;
73+
}
74+
75+
aiResult = await QueryMonitorLogWithRestApi(queryType, queryParemeter);
76+
return aiResult;
77+
}
78+
79+
private static async Task<List<QueryResultRow>> QueryMonitorLogWithRestApi(QueryType queryType, string parameterString)
80+
{
81+
try
82+
{
83+
HttpClient client = new HttpClient();
84+
client.DefaultRequestHeaders.Accept.Add(
85+
new MediaTypeWithQualityHeaderValue("application/json"));
86+
87+
var apiKeyVal = Constants.ApplicationInsightAPIKey;
88+
var appIdVal = Constants.ApplicationInsightAPPID;
89+
90+
client.DefaultRequestHeaders.Add("x-api-key", apiKeyVal);
91+
var req = string.Format(QueryEndpoint, appIdVal, parameterString);
92+
var table = await GetHttpResponse(client, req);
93+
return GetJsonObjectFromQuery(table);
94+
}
95+
catch (Exception ex)
96+
{
97+
Console.WriteLine("Failed to query Azure Motinor Ex:" + ex.Message);
98+
}
99+
return null;
100+
}
101+
102+
// Get http request response
103+
public static async Task<string> GetHttpResponse(HttpClient client, string url)
104+
{
105+
Uri uri = new Uri(url);
106+
HttpResponseMessage response = null;
107+
try
108+
{
109+
response = client.GetAsync(uri).Result;
110+
}
111+
catch (Exception e)
112+
{
113+
Console.WriteLine("GetXhrResponse Error " + e.Message); return null;
114+
}
115+
if (response == null)
116+
{
117+
Console.WriteLine("GetXhrResponse Error: No Response"); return null;
118+
}
119+
if (!response.IsSuccessStatusCode)
120+
{
121+
Console.WriteLine($"Response failed. Status code {response.StatusCode}");
122+
}
123+
return await response.Content.ReadAsStringAsync();
124+
}
125+
126+
// Get latest query object with RestApiJsonSerializeRow format
127+
// prerequisite: query parameter is ordered by timestamp desc
128+
private static List<QueryResultRow> GetJsonObjectFromQuery(string table)
129+
{
130+
if (!(table?.Length > 0)) { return null; }
131+
RestApiJsonSerializeTable SerializeTable = Newtonsoft.Json.JsonConvert.DeserializeObject<RestApiJsonSerializeTable>(table);
132+
if (!(SerializeTable?.Tables?.Count > 0)) { return null; }
133+
List<QueryResultRow> kustoObjectList = new List<QueryResultRow>();
134+
List<RestApiJsonSerializeCols> columnObjects = SerializeTable.Tables[0].Columns;
135+
List<string[]> rows = SerializeTable.Tables[0].Rows;
136+
if (!(rows?.Count > 0)) { return null; }
137+
foreach (string[] row in rows)
138+
{
139+
QueryResultRow item = new QueryResultRow(columnObjects, row);
140+
kustoObjectList.Add(item);
141+
}
142+
return kustoObjectList;
143+
}
144+
}
145+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Text;
4+
5+
namespace E2ETestCases.Utils
6+
{
7+
// Query result types
8+
public enum QueryType
9+
{ requests, dependencies, exceptions, traces, statsbeat }
10+
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
using System.Collections.Generic;
2+
3+
namespace E2ETestCases.Utils
4+
{
5+
public class RestApiJsonSerializeCols
6+
{
7+
public string Name { get; set; }
8+
public string Type { get; set; }
9+
}
10+
11+
public class RestApiJsonSerializeObject
12+
{
13+
public string Name { get; set; }
14+
public List<RestApiJsonSerializeCols> Columns { get; set; }
15+
public List<string[]> Rows { get; set; }
16+
}
17+
18+
public class RestApiJsonSerializeTable
19+
{
20+
public List<RestApiJsonSerializeObject> Tables { get; set; }
21+
}
22+
23+
//TODO: add unit tests
24+
public class QueryResultRow
25+
{
26+
Dictionary<string, string> data = new Dictionary<string, string>();
27+
public QueryResultRow(List<RestApiJsonSerializeCols> cols, string[] rows)
28+
{
29+
for (int i = 0; i < rows.Length; i++)
30+
{
31+
data.Add(cols[i].Name, rows[i]);
32+
}
33+
}
34+
// Here are all fields might be used by Monitor log query results.
35+
// Only those fields in Dictionary(data) will be parsed.
36+
public string timestamp { get => getData("timestamp"); }
37+
public string id { get => getData("id"); }
38+
public string source { get => getData("source"); }
39+
public string name { get => getData("name"); }
40+
public string url { get => getData("url"); }
41+
public string target { get => getData("target"); }
42+
public string success { get => getData("success"); }
43+
public string resultCode { get => getData("resultCode"); }
44+
public string duration { get => getData("duration"); }
45+
public string performanceBucket { get => getData("performanceBucket"); }
46+
public string itemType { get => getData("itemType"); }
47+
public dynamic customDimensions { get => getData("customDimensions"); }
48+
public dynamic customMeasurements { get => getData("customMeasurements"); }
49+
public string operation_Name { get => getData("operation_Name"); }
50+
public string operation_Id { get => getData("operation_Id"); }
51+
public string operation_ParentId { get => getData("operation_ParentId"); }
52+
public string operation_SyntheticSource { get => getData("operation_SyntheticSource"); }
53+
public string session_Id { get => getData("session_Id"); }
54+
public string user_Id { get => getData("user_Id"); }
55+
public string user_AuthenticatedId { get => getData("user_AuthenticatedId"); }
56+
public string user_AccountId { get => getData("user_AccountId"); }
57+
public string application_Version { get => getData("application_Version"); }
58+
public string client_Type { get => getData("client_Type"); }
59+
public string client_Model { get => getData("client_Model"); }
60+
public string client_OS { get => getData("client_OS"); }
61+
public string client_IP { get => getData("client_IP"); }
62+
public string client_City { get => getData("client_City"); }
63+
public string client_StateOrProvince { get => getData("client_StateOrProvince"); }
64+
public string client_CountryOrRegion { get => getData("client_CountryOrRegion"); }
65+
public string client_Browser { get => getData("client_Browser"); }
66+
public string cloud_RoleName { get => getData("cloud_RoleName"); }
67+
public string cloud_RoleInstance { get => getData("cloud_RoleInstance"); }
68+
public string appId { get => getData("appId"); }
69+
public string appName { get => getData("appName"); }
70+
public string iKey { get => getData("iKey"); }
71+
public string sdkVersion { get => getData("sdkVersion"); }
72+
public string itemId { get => getData("itemId"); }
73+
public string itemCount { get => getData("itemCount"); }
74+
public string _ResourceId { get => getData("_ResourceId"); }
75+
public string problemId { get => getData("problemId"); }
76+
public string handledAt { get => getData(" handledAt"); }
77+
public string type { get => getData(" type"); }
78+
public string message { get => getData("message"); }
79+
public string assembly { get => getData("assembly"); }
80+
public string method { get => getData("method"); }
81+
public string outerType { get => getData("outerType"); }
82+
public string outerMessage { get => getData("outerMessage"); }
83+
public string outerAssembly { get => getData("outerAssembly"); }
84+
public string outerMethod { get => getData("outerMethod"); }
85+
public string innermostType { get => getData("innermostType"); }
86+
public string innermostMessage { get => getData("innermostMessage"); }
87+
public string innermostAssembly { get => getData("innermostAssembly"); }
88+
public string innermostMethod { get => getData("innermostMethod"); }
89+
public string severityLevel { get => getData("severityLevel"); }
90+
public string details { get => getData("details"); }
91+
92+
private string getData(string name)
93+
{
94+
return data.ContainsKey(name) ? data[name] : null;
95+
}
96+
}
97+
}
98+
99+

endtoendtests/Azure.Functions.Java.Tests.E2E/Azure.Functions.Java.Tests.E2E/HttpEndToEndTests.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the MIT License. See License.txt in the project root for license information.
33

4+
using Azure.Functions.Java.Tests.E2E.Helpers.AppInsight;
45
using System;
56
using System.Net;
67
using System.Threading.Tasks;
@@ -66,5 +67,11 @@ public async Task HttpTrigger_BindingName()
6667
{
6768
Assert.True(await Utilities.InvokeHttpTrigger("BindingName", "/testMessage", HttpStatusCode.OK, "testMessage"));
6869
}
70+
71+
[Fact]
72+
public void ApplicationInsightTest()
73+
{
74+
Assert.True(AppInsightHelper.ValidateData(E2ETestCases.Utils.QueryType.traces, Constants.ApplicationInsightAgentVersion));
75+
}
6976
}
7077
}

setup-tests-pipeline.ps1

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ if (-not $UseCoreToolsBuildFromIntegrationTests.IsPresent)
5757
Write-Host "Copying worker.config.json to worker directory"
5858
Copy-Item "$PSScriptRoot/worker.config.json" "$FUNC_CLI_DIRECTORY/workers/java" -Force -Verbose
5959
Write-Host "Copying worker.config.json and annotationLib to worker directory"
60-
Copy-Item "$PSScriptRoot/annotationLib" "$FUNC_CLI_DIRECTORY/workers/java/annotationLib" -Recurse -Verbose
60+
Copy-Item "$PSScriptRoot/annotationLib" "$FUNC_CLI_DIRECTORY/workers/java" -Recurse -Verbose -Force
6161

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

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

0 commit comments

Comments
 (0)