Skip to content

Commit 32709f0

Browse files
authored
Support Statsbeat in EU regions (#2082)
* Use a diff statsbeat ikey in eu regions * Use lowercase * Fix spotless * Reserve statsbeat config when applicable * Make test easier to understand * Remove unused constants * Decouple TelemetryClient * Add comments * Refactor * Fix spotless * Remove assignment * Fix tests * Convert url to string * Use regex * Strict protocol * No need to escape dash * Remove escape
1 parent 99c14ba commit 32709f0

File tree

4 files changed

+279
-7
lines changed

4 files changed

+279
-7
lines changed

agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/configuration/Configuration.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -191,10 +191,8 @@ public static class Statsbeat {
191191
// when something goes wrong.
192192
public boolean disabledAll = false;
193193

194-
public String instrumentationKey =
195-
"c4a29126-a7cb-47e5-b348-11414998b11e"; // workspace-aistatsbeat
196-
public String endpoint =
197-
DefaultEndpoints.INGESTION_ENDPOINT; // this supports the government cloud
194+
public String instrumentationKey;
195+
public String endpoint;
198196
public long shortIntervalSeconds = MINUTES.toSeconds(15); // default to 15 minutes
199197
public long longIntervalSeconds = DAYS.toSeconds(1); // default to daily
200198
}

agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/telemetry/ConnectionString.java

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import java.net.URL;
2727
import java.util.Map;
2828
import java.util.TreeMap;
29+
import javax.annotation.Nullable;
2930
import org.apache.commons.lang3.StringUtils;
3031
import org.slf4j.Logger;
3132
import org.slf4j.LoggerFactory;
@@ -52,16 +53,27 @@ public static void parseInto(String connectionString, TelemetryClient telemetryC
5253
}
5354

5455
public static void updateStatsbeatConnectionString(
55-
String ikey, String endpoint, TelemetryClient config)
56+
@Nullable String ikey, @Nullable String endpoint, TelemetryClient telemetryClient)
5657
throws InvalidConnectionStringException {
5758
if (Strings.isNullOrEmpty(ikey)) {
5859
logger.warn("Missing Statsbeat '" + Keywords.INSTRUMENTATION_KEY + "'");
5960
}
6061

61-
config.setStatsbeatInstrumentationKey(ikey);
62+
// if customer is in EU region and their statsbeat config is not in EU region, customer is
63+
// responsible for breaking the EU data boundary violation.
64+
// Statsbeat config setting has the highest precedence.
65+
if (ikey == null || ikey.isEmpty()) {
66+
StatsbeatConnectionString.InstrumentationKeyEndpointPair pair =
67+
StatsbeatConnectionString.getInstrumentationKeyAndEndpointPair(
68+
telemetryClient.getEndpointProvider().getIngestionEndpoint().toString());
69+
ikey = pair.instrumentationKey;
70+
endpoint = pair.endpoint;
71+
}
72+
73+
telemetryClient.setStatsbeatInstrumentationKey(ikey);
6274

6375
if (!Strings.isNullOrEmpty(endpoint)) {
64-
config
76+
telemetryClient
6577
.getEndpointProvider()
6678
.setStatsbeatEndpoint(toUrlOrThrow(endpoint, Keywords.INGESTION_ENDPOINT));
6779
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/*
2+
* ApplicationInsights-Java
3+
* Copyright (c) Microsoft Corporation
4+
* All rights reserved.
5+
*
6+
* MIT License
7+
* Permission is hereby granted, free of charge, to any person obtaining a copy of this
8+
* software and associated documentation files (the ""Software""), to deal in the Software
9+
* without restriction, including without limitation the rights to use, copy, modify, merge,
10+
* publish, distribute, sublicense, and/or sell copies of the Software, and to permit
11+
* persons to whom the Software is furnished to do so, subject to the following conditions:
12+
* The above copyright notice and this permission notice shall be included in all copies or
13+
* substantial portions of the Software.
14+
* THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
15+
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
16+
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
17+
* FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
18+
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19+
* DEALINGS IN THE SOFTWARE.
20+
*/
21+
22+
package com.microsoft.applicationinsights.agent.internal.telemetry;
23+
24+
import java.util.HashSet;
25+
import java.util.Set;
26+
import java.util.regex.Matcher;
27+
import java.util.regex.Pattern;
28+
import javax.annotation.Nullable;
29+
30+
final class StatsbeatConnectionString {
31+
32+
// visible for testing
33+
static final String EU_REGION_STATSBEAT_IKEY =
34+
"7dc56bab-3c0c-4e9f-9ebb-d1acadee8d0f"; // westeu-aistatsbeat
35+
static final String EU_REGION_STATSBEAT_ENDPOINT =
36+
"https://westeurope-5.in.applicationinsights.azure.com/";
37+
static final String NON_EU_REGION_STATSBEAT_IKEY =
38+
"c4a29126-a7cb-47e5-b348-11414998b11e"; // workspace-aistatsbeat
39+
static final String NON_EU_REGION_STATSBEAT_ENDPOINT =
40+
"https://westus-0.in.applicationinsights.azure.com/";
41+
42+
private static final Pattern pattern = Pattern.compile("^https?://(?:www\\.)?([^/.-]+)");
43+
44+
private static final Set<String> EU_REGION_GEO_SET = new HashSet<>(10);
45+
46+
static {
47+
EU_REGION_GEO_SET.add("westeurope");
48+
EU_REGION_GEO_SET.add("northeurope");
49+
EU_REGION_GEO_SET.add("francecentral");
50+
EU_REGION_GEO_SET.add("francesouth");
51+
EU_REGION_GEO_SET.add("germanywestcentral");
52+
EU_REGION_GEO_SET.add("norwayeast");
53+
EU_REGION_GEO_SET.add("norwaywest");
54+
EU_REGION_GEO_SET.add("swedencentral");
55+
EU_REGION_GEO_SET.add("switzerlandnorth");
56+
EU_REGION_GEO_SET.add("switzerlandwest");
57+
}
58+
59+
// visible for testing
60+
static InstrumentationKeyEndpointPair getInstrumentationKeyAndEndpointPair(
61+
String customerEndpoint) {
62+
String geo = getGeoWithoutStampSpecific(customerEndpoint);
63+
if (EU_REGION_GEO_SET.contains(geo.toLowerCase())) {
64+
return new InstrumentationKeyEndpointPair(
65+
EU_REGION_STATSBEAT_IKEY, EU_REGION_STATSBEAT_ENDPOINT);
66+
}
67+
68+
return new InstrumentationKeyEndpointPair(
69+
NON_EU_REGION_STATSBEAT_IKEY, NON_EU_REGION_STATSBEAT_ENDPOINT);
70+
}
71+
72+
// visible for testing
73+
@Nullable
74+
static String getGeoWithoutStampSpecific(String endpointUrl) {
75+
Matcher matcher = pattern.matcher(endpointUrl);
76+
if (matcher.find()) {
77+
return matcher.group(1);
78+
}
79+
80+
return null;
81+
}
82+
83+
private StatsbeatConnectionString() {}
84+
85+
static class InstrumentationKeyEndpointPair {
86+
public final String instrumentationKey;
87+
public final String endpoint;
88+
89+
public InstrumentationKeyEndpointPair(String instrumentationKey, String endpoint) {
90+
this.instrumentationKey = instrumentationKey;
91+
this.endpoint = endpoint;
92+
}
93+
}
94+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
/*
2+
* ApplicationInsights-Java
3+
* Copyright (c) Microsoft Corporation
4+
* All rights reserved.
5+
*
6+
* MIT License
7+
* Permission is hereby granted, free of charge, to any person obtaining a copy of this
8+
* software and associated documentation files (the ""Software""), to deal in the Software
9+
* without restriction, including without limitation the rights to use, copy, modify, merge,
10+
* publish, distribute, sublicense, and/or sell copies of the Software, and to permit
11+
* persons to whom the Software is furnished to do so, subject to the following conditions:
12+
* The above copyright notice and this permission notice shall be included in all copies or
13+
* substantial portions of the Software.
14+
* THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
15+
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
16+
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
17+
* FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
18+
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19+
* DEALINGS IN THE SOFTWARE.
20+
*/
21+
22+
package com.microsoft.applicationinsights.agent.internal.telemetry;
23+
24+
import static org.assertj.core.api.Assertions.assertThat;
25+
26+
import org.junit.jupiter.api.Test;
27+
28+
public class StatsbeatConnectionStringTest {
29+
30+
@Test
31+
public void testGetGeoWithoutStampSpecific() {
32+
String customerIngestionEndpoint = "https://fakehost-1.applicationinsights.azure.com/";
33+
assertThat(StatsbeatConnectionString.getGeoWithoutStampSpecific(customerIngestionEndpoint))
34+
.isEqualTo("fakehost");
35+
36+
customerIngestionEndpoint = "http://fakehost-2.example.com/";
37+
assertThat(StatsbeatConnectionString.getGeoWithoutStampSpecific(customerIngestionEndpoint))
38+
.isEqualTo("fakehost");
39+
40+
customerIngestionEndpoint = "https://fakehost1-3.com/";
41+
assertThat(StatsbeatConnectionString.getGeoWithoutStampSpecific(customerIngestionEndpoint))
42+
.isEqualTo("fakehost1");
43+
44+
customerIngestionEndpoint = "http://fakehost2-4.com/";
45+
assertThat(StatsbeatConnectionString.getGeoWithoutStampSpecific(customerIngestionEndpoint))
46+
.isEqualTo("fakehost2");
47+
48+
customerIngestionEndpoint = "http://www.fakehost3-5.com/";
49+
assertThat(StatsbeatConnectionString.getGeoWithoutStampSpecific(customerIngestionEndpoint))
50+
.isEqualTo("fakehost3");
51+
52+
customerIngestionEndpoint = "https://www.fakehostabc-6.com/";
53+
assertThat(StatsbeatConnectionString.getGeoWithoutStampSpecific(customerIngestionEndpoint))
54+
.isEqualTo("fakehostabc");
55+
56+
customerIngestionEndpoint = "http://fakehostabc-7.example.com/";
57+
assertThat(StatsbeatConnectionString.getGeoWithoutStampSpecific(customerIngestionEndpoint))
58+
.isEqualTo("fakehostabc");
59+
60+
customerIngestionEndpoint = "http://www.fakehostabc-8.example.com/";
61+
assertThat(StatsbeatConnectionString.getGeoWithoutStampSpecific(customerIngestionEndpoint))
62+
.isEqualTo("fakehostabc");
63+
64+
customerIngestionEndpoint = "https://fakehostabc1-9.com/";
65+
assertThat(StatsbeatConnectionString.getGeoWithoutStampSpecific(customerIngestionEndpoint))
66+
.isEqualTo("fakehostabc1");
67+
68+
customerIngestionEndpoint = "https://fakehostabc.com/";
69+
assertThat(StatsbeatConnectionString.getGeoWithoutStampSpecific(customerIngestionEndpoint))
70+
.isEqualTo("fakehostabc");
71+
72+
customerIngestionEndpoint = "https://fakehostabc/v2/track";
73+
assertThat(StatsbeatConnectionString.getGeoWithoutStampSpecific(customerIngestionEndpoint))
74+
.isEqualTo("fakehostabc");
75+
}
76+
77+
@Test
78+
public void testUpdateStatsbeatConnectionString() throws Exception {
79+
// case 1
80+
// customer ikey is in non-eu
81+
// Statsbeat config ikey is in eu
82+
// use Statsbeat config ikey
83+
TelemetryClient telemetryClient = TelemetryClient.createForTest();
84+
telemetryClient.setConnectionString(
85+
"InstrumentationKey=00000000-0000-0000-0000-000000000000;IngestionEndpoint=https://westus2-1.example.com/");
86+
String ikeyConfig = "00000000-0000-0000-0000-000000000001";
87+
String endpointConfig = "https://westeurope-2.example.com";
88+
ConnectionString.updateStatsbeatConnectionString(ikeyConfig, endpointConfig, telemetryClient);
89+
assertThat(telemetryClient.getStatsbeatInstrumentationKey()).isEqualTo(ikeyConfig);
90+
assertThat(telemetryClient.getEndpointProvider().getStatsbeatEndpointUrl().toString())
91+
.isEqualTo(endpointConfig + "/v2.1/track");
92+
93+
// case 2
94+
// customer ikey is in non-eu
95+
// Statsbeat config ikey is in non-eu
96+
// use Statsbeat config ikey
97+
ikeyConfig = "00000000-0000-0000-0000-000000000002";
98+
endpointConfig = "https://westus2-2.example.com";
99+
ConnectionString.updateStatsbeatConnectionString(ikeyConfig, endpointConfig, telemetryClient);
100+
assertThat(telemetryClient.getStatsbeatInstrumentationKey()).isEqualTo(ikeyConfig);
101+
assertThat(telemetryClient.getEndpointProvider().getStatsbeatEndpointUrl().toString())
102+
.isEqualTo(endpointConfig + "/v2.1/track");
103+
104+
// case 3
105+
// customer ikey is in non-eu
106+
// no Statsbeat config
107+
// use Statsbeat non-eu
108+
ConnectionString.updateStatsbeatConnectionString(null, null, telemetryClient);
109+
assertThat(telemetryClient.getStatsbeatInstrumentationKey())
110+
.isEqualTo(StatsbeatConnectionString.NON_EU_REGION_STATSBEAT_IKEY);
111+
assertThat(telemetryClient.getEndpointProvider().getStatsbeatEndpointUrl().toString())
112+
.isEqualTo(StatsbeatConnectionString.NON_EU_REGION_STATSBEAT_ENDPOINT + "v2.1/track");
113+
114+
// case 4
115+
// customer is in eu
116+
// Statsbeat config ikey is in non-eu
117+
// use Statsbeat config's ikey
118+
telemetryClient.setConnectionString(
119+
"InstrumentationKey=00000000-0000-0000-0000-000000000003;IngestionEndpoint=https://westeurope-1.example.com/");
120+
ikeyConfig = "00000000-0000-0000-0000-000000000004";
121+
endpointConfig = "https://westus2-4.example.com";
122+
ConnectionString.updateStatsbeatConnectionString(ikeyConfig, endpointConfig, telemetryClient);
123+
assertThat(telemetryClient.getStatsbeatInstrumentationKey()).isEqualTo(ikeyConfig);
124+
assertThat(telemetryClient.getEndpointProvider().getStatsbeatEndpointUrl().toString())
125+
.isEqualTo(endpointConfig + "/v2.1/track");
126+
127+
// case 5
128+
// customer is in eu
129+
// Statsbeat config ikey is in eu
130+
// use Statsbeat config's ikey
131+
ikeyConfig = "00000000-0000-0000-0000-000000000005";
132+
endpointConfig = "https://francesouth-1.example.com";
133+
ConnectionString.updateStatsbeatConnectionString(ikeyConfig, endpointConfig, telemetryClient);
134+
assertThat(telemetryClient.getStatsbeatInstrumentationKey()).isEqualTo(ikeyConfig);
135+
assertThat(telemetryClient.getEndpointProvider().getStatsbeatEndpointUrl().toString())
136+
.isEqualTo(endpointConfig + "/v2.1/track");
137+
138+
// case 6
139+
// customer is in eu
140+
// no statsbeat config
141+
// use Statsbeat eu
142+
ConnectionString.updateStatsbeatConnectionString(null, null, telemetryClient);
143+
assertThat(telemetryClient.getStatsbeatInstrumentationKey())
144+
.isEqualTo(StatsbeatConnectionString.EU_REGION_STATSBEAT_IKEY);
145+
assertThat(telemetryClient.getEndpointProvider().getStatsbeatEndpointUrl().toString())
146+
.isEqualTo(StatsbeatConnectionString.EU_REGION_STATSBEAT_ENDPOINT + "v2.1/track");
147+
}
148+
149+
@Test
150+
public void testGetInstrumentationKeyAndEndpointPairEuRegion() {
151+
StatsbeatConnectionString.InstrumentationKeyEndpointPair pair =
152+
StatsbeatConnectionString.getInstrumentationKeyAndEndpointPair(
153+
"https://northeurope-2.example.com/");
154+
assertThat(pair.instrumentationKey)
155+
.isEqualTo(StatsbeatConnectionString.EU_REGION_STATSBEAT_IKEY);
156+
assertThat(pair.endpoint).isEqualTo(StatsbeatConnectionString.EU_REGION_STATSBEAT_ENDPOINT);
157+
}
158+
159+
@Test
160+
public void testGetInstrumentationKeyAndEndpointPairNonEuRegion() {
161+
StatsbeatConnectionString.InstrumentationKeyEndpointPair pair =
162+
StatsbeatConnectionString.getInstrumentationKeyAndEndpointPair(
163+
"https://westus2-2.example.com/");
164+
assertThat(pair.instrumentationKey)
165+
.isEqualTo(StatsbeatConnectionString.NON_EU_REGION_STATSBEAT_IKEY);
166+
assertThat(pair.endpoint).isEqualTo(StatsbeatConnectionString.NON_EU_REGION_STATSBEAT_ENDPOINT);
167+
}
168+
}

0 commit comments

Comments
 (0)