Skip to content

Commit ecb2125

Browse files
committed
add aks resource provider
1 parent 9ea505d commit ecb2125

File tree

6 files changed

+379
-158
lines changed

6 files changed

+379
-158
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.contrib.azure.resource;
7+
8+
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
9+
import io.opentelemetry.sdk.resources.Resource;
10+
import io.opentelemetry.semconv.incubating.CloudIncubatingAttributes;
11+
import io.opentelemetry.semconv.incubating.K8sIncubatingAttributes;
12+
import java.util.HashMap;
13+
import java.util.Map;
14+
import java.util.Optional;
15+
import java.util.function.Supplier;
16+
17+
public class AzureAksResourceProvider extends CloudResourceProvider {
18+
19+
private static final Map<String, AzureVmResourceProvider.Entry> COMPUTE_MAPPING = new HashMap<>();
20+
21+
static {
22+
COMPUTE_MAPPING.put(
23+
"resourceGroupName",
24+
new AzureVmResourceProvider.Entry(
25+
K8sIncubatingAttributes.K8S_CLUSTER_NAME, AzureAksResourceProvider::parseClusterName));
26+
}
27+
28+
// visible for testing
29+
static String parseClusterName(String resourceGroup) {
30+
// Code inspired by
31+
// https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/exporter/datadogexporter/internal/hostmetadata/internal/azure/provider.go#L36
32+
String[] splitAll = resourceGroup.split("_");
33+
if (splitAll.length == 4 && splitAll[0].equalsIgnoreCase("mc")) {
34+
return splitAll[splitAll.length - 2];
35+
}
36+
return resourceGroup;
37+
}
38+
39+
// Environment variable that is set when running on Kubernetes
40+
static final String KUBERNETES_SERVICE_HOST = "KUBERNETES_SERVICE_HOST";
41+
private final Supplier<Optional<String>> client;
42+
private final Map<String, String> environment;
43+
44+
// SPI
45+
public AzureAksResourceProvider() {
46+
this(AzureMetadataService.defaultClient(), System.getenv());
47+
}
48+
49+
// visible for testing
50+
public AzureAksResourceProvider(
51+
Supplier<Optional<String>> client, Map<String, String> environment) {
52+
this.client = client;
53+
this.environment = environment;
54+
}
55+
56+
@Override
57+
public int order() {
58+
// run after the fast cloud resource providers that only check environment variables
59+
// and before the AKS provider
60+
return 100;
61+
}
62+
63+
@Override
64+
public Resource createResource(ConfigProperties configProperties) {
65+
if (environment.get(KUBERNETES_SERVICE_HOST) == null) {
66+
return Resource.empty();
67+
}
68+
return client
69+
.get()
70+
.map(
71+
body ->
72+
AzureVmResourceProvider.parseMetadata(
73+
body, COMPUTE_MAPPING, CloudIncubatingAttributes.CloudPlatformValues.AZURE_AKS))
74+
.orElse(Resource.empty());
75+
}
76+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.contrib.azure.resource;
7+
8+
import com.fasterxml.jackson.core.JsonFactory;
9+
import java.io.IOException;
10+
import java.net.MalformedURLException;
11+
import java.net.URL;
12+
import java.time.Duration;
13+
import java.util.Objects;
14+
import java.util.Optional;
15+
import java.util.function.Supplier;
16+
import java.util.logging.Level;
17+
import java.util.logging.Logger;
18+
import okhttp3.OkHttpClient;
19+
import okhttp3.Request;
20+
import okhttp3.Response;
21+
22+
public class AzureMetadataService {
23+
static final JsonFactory JSON_FACTORY = new JsonFactory();
24+
private static final URL METADATA_URL;
25+
26+
static {
27+
try {
28+
METADATA_URL = new URL("http://169.254.169.254/metadata/instance?api-version=2021-02-01");
29+
} catch (MalformedURLException e) {
30+
throw new IllegalStateException(e);
31+
}
32+
}
33+
34+
private AzureMetadataService() {}
35+
36+
private static final Duration TIMEOUT = Duration.ofSeconds(1);
37+
38+
private static final Logger logger = Logger.getLogger(AzureMetadataService.class.getName());
39+
40+
static Supplier<Optional<String>> defaultClient() {
41+
return () -> fetchMetadata(METADATA_URL);
42+
}
43+
44+
// visible for testing
45+
static Optional<String> fetchMetadata(URL url) {
46+
OkHttpClient client =
47+
new OkHttpClient.Builder()
48+
.callTimeout(TIMEOUT)
49+
.connectTimeout(TIMEOUT)
50+
.readTimeout(TIMEOUT)
51+
.build();
52+
53+
Request request = new Request.Builder().url(url).get().addHeader("Metadata", "true").build();
54+
55+
try (Response response = client.newCall(request).execute()) {
56+
int responseCode = response.code();
57+
if (responseCode != 200) {
58+
logger.log(
59+
Level.FINE,
60+
"Error response from "
61+
+ url
62+
+ " code ("
63+
+ responseCode
64+
+ ") text "
65+
+ response.message());
66+
return Optional.empty();
67+
}
68+
69+
return Optional.of(Objects.requireNonNull(response.body()).string());
70+
} catch (IOException e) {
71+
logger.log(Level.FINE, "Failed to fetch Azure VM metadata", e);
72+
return Optional.empty();
73+
}
74+
}
75+
}

azure-resources/src/main/java/io/opentelemetry/contrib/azure/resource/AzureVmResourceProvider.java

+47-75
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
import static io.opentelemetry.semconv.incubating.OsIncubatingAttributes.OS_TYPE;
1616
import static io.opentelemetry.semconv.incubating.OsIncubatingAttributes.OS_VERSION;
1717

18-
import com.fasterxml.jackson.core.JsonFactory;
1918
import com.fasterxml.jackson.core.JsonParser;
2019
import com.fasterxml.jackson.core.JsonToken;
2120
import io.opentelemetry.api.common.AttributeKey;
@@ -25,58 +24,54 @@
2524
import io.opentelemetry.sdk.resources.Resource;
2625
import io.opentelemetry.semconv.incubating.CloudIncubatingAttributes;
2726
import java.io.IOException;
28-
import java.net.MalformedURLException;
29-
import java.net.URL;
30-
import java.time.Duration;
3127
import java.util.HashMap;
3228
import java.util.Map;
33-
import java.util.Objects;
3429
import java.util.Optional;
3530
import java.util.function.BiConsumer;
31+
import java.util.function.Function;
3632
import java.util.function.Supplier;
3733
import java.util.logging.Level;
3834
import java.util.logging.Logger;
39-
import okhttp3.OkHttpClient;
40-
import okhttp3.Request;
41-
import okhttp3.Response;
4235
import org.jetbrains.annotations.NotNull;
4336

4437
public class AzureVmResourceProvider extends CloudResourceProvider {
4538

46-
private static final Map<String, AttributeKey<String>> COMPUTE_MAPPING = new HashMap<>();
39+
static class Entry {
40+
final AttributeKey<String> key;
41+
final Function<String, String> transform;
4742

48-
static {
49-
COMPUTE_MAPPING.put("location", CLOUD_REGION);
50-
COMPUTE_MAPPING.put("resourceId", CLOUD_RESOURCE_ID);
51-
COMPUTE_MAPPING.put("vmId", HOST_ID);
52-
COMPUTE_MAPPING.put("name", HOST_NAME);
53-
COMPUTE_MAPPING.put("vmSize", HOST_TYPE);
54-
COMPUTE_MAPPING.put("osType", OS_TYPE);
55-
COMPUTE_MAPPING.put("version", OS_VERSION);
56-
COMPUTE_MAPPING.put("vmScaleSetName", AttributeKey.stringKey("azure.vm.scaleset.name"));
57-
COMPUTE_MAPPING.put("sku", AttributeKey.stringKey("azure.vm.sku"));
58-
}
59-
60-
private static final JsonFactory JSON_FACTORY = new JsonFactory();
43+
Entry(AttributeKey<String> key) {
44+
this(key, Function.identity());
45+
}
6146

62-
private static final Duration TIMEOUT = Duration.ofSeconds(1);
47+
Entry(AttributeKey<String> key, Function<String, String> transform) {
48+
this.key = key;
49+
this.transform = transform;
50+
}
51+
}
6352

64-
private static final Logger logger = Logger.getLogger(AzureVmResourceProvider.class.getName());
65-
private static final URL METADATA_URL;
53+
private static final Map<String, Entry> COMPUTE_MAPPING = new HashMap<>();
6654

6755
static {
68-
try {
69-
METADATA_URL = new URL("http://169.254.169.254/metadata/instance?api-version=2021-02-01");
70-
} catch (MalformedURLException e) {
71-
throw new IllegalStateException(e);
72-
}
56+
COMPUTE_MAPPING.put("location", new Entry(CLOUD_REGION));
57+
COMPUTE_MAPPING.put("resourceId", new Entry(CLOUD_RESOURCE_ID));
58+
COMPUTE_MAPPING.put("vmId", new Entry(HOST_ID));
59+
COMPUTE_MAPPING.put("name", new Entry(HOST_NAME));
60+
COMPUTE_MAPPING.put("vmSize", new Entry(HOST_TYPE));
61+
COMPUTE_MAPPING.put("osType", new Entry(OS_TYPE));
62+
COMPUTE_MAPPING.put("version", new Entry(OS_VERSION));
63+
COMPUTE_MAPPING.put(
64+
"vmScaleSetName", new Entry(AttributeKey.stringKey("azure.vm.scaleset.name")));
65+
COMPUTE_MAPPING.put("sku", new Entry(AttributeKey.stringKey("azure.vm.sku")));
7366
}
7467

68+
private static final Logger logger = Logger.getLogger(AzureVmResourceProvider.class.getName());
69+
7570
private final Supplier<Optional<String>> client;
7671

7772
// SPI
7873
public AzureVmResourceProvider() {
79-
this(() -> fetchMetadata(METADATA_URL));
74+
this(AzureMetadataService.defaultClient());
8075
}
8176

8277
// visible for testing
@@ -87,20 +82,26 @@ public AzureVmResourceProvider(Supplier<Optional<String>> client) {
8782
@Override
8883
public int order() {
8984
// run after the fast cloud resource providers that only check environment variables
85+
// and after the AKS provider
9086
return 100;
9187
}
9288

9389
@Override
9490
public Resource createResource(ConfigProperties config) {
95-
return client.get().map(AzureVmResourceProvider::parseMetadata).orElse(Resource.empty());
91+
return client
92+
.get()
93+
.map(
94+
body ->
95+
parseMetadata(
96+
body, COMPUTE_MAPPING, CloudIncubatingAttributes.CloudPlatformValues.AZURE_VM))
97+
.orElse(Resource.empty());
9698
}
9799

98-
private static Resource parseMetadata(String body) {
99-
AttributesBuilder builder =
100-
azureAttributeBuilder(CloudIncubatingAttributes.CloudPlatformValues.AZURE_VM);
101-
try (JsonParser parser = JSON_FACTORY.createParser(body)) {
100+
static Resource parseMetadata(String body, Map<String, Entry> computeMapping, String platform) {
101+
AttributesBuilder builder = azureAttributeBuilder(platform);
102+
try (JsonParser parser = AzureMetadataService.JSON_FACTORY.createParser(body)) {
102103
parser.nextToken();
103-
parseResponse(parser, builder);
104+
parseResponse(parser, builder, computeMapping);
104105
} catch (IOException e) {
105106
logger.log(Level.FINE, "Can't get Azure VM metadata", e);
106107
}
@@ -115,7 +116,9 @@ static AttributesBuilder azureAttributeBuilder(String platform) {
115116
return builder;
116117
}
117118

118-
static void parseResponse(JsonParser parser, AttributesBuilder builder) throws IOException {
119+
static void parseResponse(
120+
JsonParser parser, AttributesBuilder builder, Map<String, Entry> computeMapping)
121+
throws IOException {
119122
if (!parser.isExpectedStartObjectToken()) {
120123
logger.log(Level.FINE, "Couldn't parse ECS metadata, invalid JSON");
121124
return;
@@ -126,7 +129,7 @@ static void parseResponse(JsonParser parser, AttributesBuilder builder) throws I
126129
(name, value) -> {
127130
try {
128131
if (name.equals("compute")) {
129-
consumeCompute(parser, builder);
132+
consumeCompute(parser, builder, computeMapping);
130133
} else {
131134
parser.skipChildren();
132135
}
@@ -136,14 +139,15 @@ static void parseResponse(JsonParser parser, AttributesBuilder builder) throws I
136139
});
137140
}
138141

139-
private static void consumeCompute(JsonParser parser, AttributesBuilder builder)
142+
private static void consumeCompute(
143+
JsonParser parser, AttributesBuilder builder, Map<String, Entry> computeMapping)
140144
throws IOException {
141145
consumeJson(
142146
parser,
143147
(computeName, computeValue) -> {
144-
AttributeKey<String> key = COMPUTE_MAPPING.get(computeName);
145-
if (key != null) {
146-
builder.put(key, computeValue);
148+
Entry entry = computeMapping.get(computeName);
149+
if (entry != null) {
150+
builder.put(entry.key, entry.transform.apply(computeValue));
147151
} else {
148152
try {
149153
parser.skipChildren();
@@ -160,36 +164,4 @@ private static void consumeJson(JsonParser parser, BiConsumer<String, String> co
160164
consumer.accept(parser.currentName(), parser.nextTextValue());
161165
}
162166
}
163-
164-
// visible for testing
165-
static Optional<String> fetchMetadata(URL url) {
166-
OkHttpClient client =
167-
new OkHttpClient.Builder()
168-
.callTimeout(TIMEOUT)
169-
.connectTimeout(TIMEOUT)
170-
.readTimeout(TIMEOUT)
171-
.build();
172-
173-
Request request = new Request.Builder().url(url).get().addHeader("Metadata", "true").build();
174-
175-
try (Response response = client.newCall(request).execute()) {
176-
int responseCode = response.code();
177-
if (responseCode != 200) {
178-
logger.log(
179-
Level.FINE,
180-
"Error response from "
181-
+ url
182-
+ " code ("
183-
+ responseCode
184-
+ ") text "
185-
+ response.message());
186-
return Optional.empty();
187-
}
188-
189-
return Optional.of(Objects.requireNonNull(response.body()).string());
190-
} catch (IOException e) {
191-
logger.log(Level.FINE, "Failed to fetch Azure VM metadata", e);
192-
return Optional.empty();
193-
}
194-
}
195167
}

0 commit comments

Comments
 (0)